""" Integration tests for deployment workflow """ import pytest from click.testing import CliRunner from unittest.mock import patch, Mock from src.cli.commands import app from src.database.session import db_manager from src.database.repositories import UserRepository, SiteDeploymentRepository from src.auth.service import AuthService from src.deployment.bunnynet import StorageZoneResult, PullZoneResult @pytest.fixture def cli_runner(): """Fixture to create a Click CLI test runner""" return CliRunner() @pytest.fixture def setup_test_admin(): """Create a test admin user in the database""" session = db_manager.get_session() try: user_repo = UserRepository(session) auth_service = AuthService(user_repo) try: user = auth_service.create_user_with_hashed_password( username="test_admin", password="test_password", role="Admin" ) yield user finally: if user_repo.get_by_username("test_admin"): test_user = user_repo.get_by_username("test_admin") if test_user: user_repo.delete(test_user.id) finally: session.close() @pytest.fixture def cleanup_test_sites(): """Clean up all site deployments before and after test""" def _cleanup(): session = db_manager.get_session() try: deployment_repo = SiteDeploymentRepository(session) sites = deployment_repo.get_all() for site in sites: deployment_repo.delete(site.id) finally: session.close() _cleanup() yield _cleanup() class TestProvisionSiteIntegration: """Integration tests for provision-site workflow""" @patch('src.cli.commands.get_bunny_account_api_key') @patch('src.cli.commands.BunnyNetClient') def test_full_provisioning_workflow( self, mock_client_class, mock_get_key, cli_runner, setup_test_admin, cleanup_test_sites ): """Test complete site provisioning workflow with database""" mock_get_key.return_value = "test_api_key" mock_client = Mock() mock_client_class.return_value = mock_client mock_client.create_storage_zone.return_value = StorageZoneResult( id=99999, name="test-integration-storage", password="test-integration-pass", region="DE" ) mock_client.create_pull_zone.return_value = PullZoneResult( id=88888, name="test-integration-cdn", hostname="test-integration.b-cdn.net" ) mock_client.add_custom_hostname.return_value = True result = cli_runner.invoke(app, [ 'provision-site', '--name', 'Test Integration Site', '--domain', 'test-integration.example.com', '--storage-name', 'test-integration-storage', '--region', 'DE', '--admin-user', 'test_admin', '--admin-password', 'test_password' ]) assert result.exit_code == 0 assert "Site provisioned successfully!" in result.output session = db_manager.get_session() try: deployment_repo = SiteDeploymentRepository(session) site = deployment_repo.get_by_hostname('test-integration.example.com') assert site is not None assert site.site_name == 'Test Integration Site' assert site.storage_zone_id == 99999 assert site.storage_zone_name == 'test-integration-storage' assert site.storage_zone_password == 'test-integration-pass' assert site.storage_zone_region == 'DE' assert site.pull_zone_id == 88888 assert site.pull_zone_bcdn_hostname == 'test-integration.b-cdn.net' finally: session.close() @patch('src.cli.commands.get_bunny_account_api_key') @patch('src.cli.commands.BunnyNetClient') def test_provision_then_list_sites( self, mock_client_class, mock_get_key, cli_runner, setup_test_admin, cleanup_test_sites ): """Test provisioning a site and then listing it""" mock_get_key.return_value = "test_api_key" mock_client = Mock() mock_client_class.return_value = mock_client mock_client.create_storage_zone.return_value = StorageZoneResult( id=99999, name="test-list-storage", password="test-pass", region="NY" ) mock_client.create_pull_zone.return_value = PullZoneResult( id=88888, name="test-list-cdn", hostname="test-list.b-cdn.net" ) mock_client.add_custom_hostname.return_value = True provision_result = cli_runner.invoke(app, [ 'provision-site', '--name', 'Test List Site', '--domain', 'test-list.example.com', '--storage-name', 'test-list-storage', '--region', 'NY', '--admin-user', 'test_admin', '--admin-password', 'test_password' ]) assert provision_result.exit_code == 0 list_result = cli_runner.invoke(app, [ 'list-sites', '--admin-user', 'test_admin', '--admin-password', 'test_password' ]) assert list_result.exit_code == 0 assert "test-list.example.com" in list_result.output assert "Test List Site" in list_result.output @patch('src.cli.commands.get_bunny_account_api_key') @patch('src.cli.commands.BunnyNetClient') def test_provision_get_and_remove_workflow( self, mock_client_class, mock_get_key, cli_runner, setup_test_admin, cleanup_test_sites ): """Test complete workflow: provision, get details, then remove""" mock_get_key.return_value = "test_api_key" mock_client = Mock() mock_client_class.return_value = mock_client mock_client.create_storage_zone.return_value = StorageZoneResult( id=77777, name="test-remove-storage", password="test-pass", region="LA" ) mock_client.create_pull_zone.return_value = PullZoneResult( id=66666, name="test-remove-cdn", hostname="test-remove.b-cdn.net" ) mock_client.add_custom_hostname.return_value = True provision_result = cli_runner.invoke(app, [ 'provision-site', '--name', 'Test Remove Site', '--domain', 'test-remove.example.com', '--storage-name', 'test-remove-storage', '--region', 'LA', '--admin-user', 'test_admin', '--admin-password', 'test_password' ]) assert provision_result.exit_code == 0 get_result = cli_runner.invoke(app, [ 'get-site', '--domain', 'test-remove.example.com', '--admin-user', 'test_admin', '--admin-password', 'test_password' ]) assert get_result.exit_code == 0 assert "Test Remove Site" in get_result.output assert "77777" in get_result.output assert "test-remove-storage" in get_result.output remove_result = cli_runner.invoke(app, [ 'remove-site', '--domain', 'test-remove.example.com', '--admin-user', 'test_admin', '--admin-password', 'test_password', '--yes' ]) assert remove_result.exit_code == 0 assert "has been removed" in remove_result.output session = db_manager.get_session() try: deployment_repo = SiteDeploymentRepository(session) site = deployment_repo.get_by_hostname('test-remove.example.com') assert site is None finally: session.close() class TestAttachDomainIntegration: """Integration tests for attach-domain workflow""" @patch('src.cli.commands.get_bunny_account_api_key') @patch('src.cli.commands.BunnyNetClient') def test_attach_domain_to_existing_storage( self, mock_client_class, mock_get_key, cli_runner, setup_test_admin, cleanup_test_sites ): """Test attaching a domain to an existing storage zone""" mock_get_key.return_value = "test_api_key" mock_client = Mock() mock_client_class.return_value = mock_client mock_client.find_storage_zone_by_name.return_value = StorageZoneResult( id=55555, name="existing-storage", password="existing-pass", region="SG" ) mock_client.create_pull_zone.return_value = PullZoneResult( id=44444, name="attach-test-cdn", hostname="attach-test.b-cdn.net" ) mock_client.add_custom_hostname.return_value = True result = cli_runner.invoke(app, [ 'attach-domain', '--name', 'Test Attach Site', '--domain', 'test-attach.example.com', '--storage-name', 'existing-storage', '--admin-user', 'test_admin', '--admin-password', 'test_password' ]) assert result.exit_code == 0 assert "Domain attached successfully!" in result.output session = db_manager.get_session() try: deployment_repo = SiteDeploymentRepository(session) site = deployment_repo.get_by_hostname('test-attach.example.com') assert site is not None assert site.site_name == 'Test Attach Site' assert site.storage_zone_id == 55555 assert site.storage_zone_name == 'existing-storage' assert site.pull_zone_id == 44444 finally: session.close() class TestDeploymentErrorHandling: """Integration tests for error handling in deployment workflow""" def test_provision_without_api_key(self, cli_runner, setup_test_admin): """Test provisioning fails gracefully without API key""" with patch('src.cli.commands.get_bunny_account_api_key', side_effect=ValueError("API key required")): result = cli_runner.invoke(app, [ 'provision-site', '--name', 'Test Site', '--domain', 'test.example.com', '--storage-name', 'test-storage', '--region', 'DE', '--admin-user', 'test_admin', '--admin-password', 'test_password' ]) assert result.exit_code != 0 assert "BUNNY_ACCOUNT_API_KEY" in result.output @patch('src.cli.commands.get_bunny_account_api_key') @patch('src.cli.commands.BunnyNetClient') def test_provision_duplicate_domain( self, mock_client_class, mock_get_key, cli_runner, setup_test_admin, cleanup_test_sites ): """Test provisioning fails when domain already exists""" mock_get_key.return_value = "test_api_key" mock_client = Mock() mock_client_class.return_value = mock_client mock_client.create_storage_zone.return_value = StorageZoneResult( id=12345, name="test-storage", password="test-pass", region="DE" ) mock_client.create_pull_zone.return_value = PullZoneResult( id=67890, name="test-cdn", hostname="test.b-cdn.net" ) mock_client.add_custom_hostname.return_value = True first_result = cli_runner.invoke(app, [ 'provision-site', '--name', 'First Site', '--domain', 'duplicate.example.com', '--storage-name', 'test-dup-storage-1', '--region', 'DE', '--admin-user', 'test_admin', '--admin-password', 'test_password' ]) assert first_result.exit_code == 0 second_result = cli_runner.invoke(app, [ 'provision-site', '--name', 'Second Site', '--domain', 'duplicate.example.com', '--storage-name', 'test-dup-storage-2', '--region', 'DE', '--admin-user', 'test_admin', '--admin-password', 'test_password' ]) assert second_result.exit_code != 0 assert "already exists" in second_result.output def test_get_nonexistent_site(self, cli_runner, setup_test_admin): """Test getting details of non-existent site""" result = cli_runner.invoke(app, [ 'get-site', '--domain', 'nonexistent.example.com', '--admin-user', 'test_admin', '--admin-password', 'test_password' ]) assert result.exit_code != 0 assert "not found" in result.output def test_remove_nonexistent_site(self, cli_runner, setup_test_admin): """Test removing non-existent site""" result = cli_runner.invoke(app, [ 'remove-site', '--domain', 'nonexistent.example.com', '--admin-user', 'test_admin', '--admin-password', 'test_password', '--yes' ]) assert result.exit_code != 0 assert "not found" in result.output