360 lines
13 KiB
Python
360 lines
13 KiB
Python
"""
|
|
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
|
|
|