Big-Link-Man/tests/unit/test_deployment_commands.py

412 lines
15 KiB
Python

"""
Unit tests for deployment CLI commands
"""
import pytest
from click.testing import CliRunner
from unittest.mock import Mock, patch, MagicMock
from src.cli.commands import app
from src.database.models import User, SiteDeployment
from src.deployment.bunnynet import (
StorageZoneResult,
PullZoneResult,
BunnyNetAuthError,
BunnyNetResourceConflictError,
BunnyNetAPIError
)
@pytest.fixture
def cli_runner():
"""Fixture to create a Click CLI test runner"""
return CliRunner()
@pytest.fixture
def mock_admin_user():
"""Fixture for a mock admin user"""
user = Mock(spec=User)
user.id = 1
user.username = "admin"
user.role = "Admin"
user.is_admin.return_value = True
return user
@pytest.fixture
def mock_site_deployment():
"""Fixture for a mock site deployment"""
site = Mock(spec=SiteDeployment)
site.id = 1
site.site_name = "Test Site"
site.custom_hostname = "www.example.com"
site.storage_zone_id = 12345
site.storage_zone_name = "test-storage"
site.storage_zone_password = "test-pass"
site.storage_zone_region = "DE"
site.pull_zone_id = 67890
site.pull_zone_bcdn_hostname = "test-cdn.b-cdn.net"
site.created_at = Mock()
site.created_at.strftime.return_value = "2024-01-01 00:00:00"
site.updated_at = Mock()
site.updated_at.strftime.return_value = "2024-01-01 00:00:00"
return site
class TestProvisionSiteCommand:
"""Tests for provision-site CLI command"""
@patch('src.cli.commands.authenticate_admin')
@patch('src.cli.commands.get_bunny_account_api_key')
@patch('src.cli.commands.BunnyNetClient')
@patch('src.cli.commands.db_manager')
def test_provision_site_success(
self, mock_db, mock_client_class, mock_get_key, mock_auth, cli_runner, mock_admin_user
):
"""Test successful site provisioning"""
mock_auth.return_value = mock_admin_user
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-cdn.b-cdn.net"
)
mock_client.add_custom_hostname.return_value = True
mock_session = Mock()
mock_db.get_session.return_value = mock_session
mock_repo = Mock()
mock_repo.exists.return_value = False
mock_repo.create.return_value = Mock()
with patch('src.cli.commands.SiteDeploymentRepository', return_value=mock_repo):
result = cli_runner.invoke(app, [
'provision-site',
'--name', 'Test Site',
'--domain', 'www.example.com',
'--storage-name', 'test-storage',
'--region', 'DE',
'--admin-user', 'admin',
'--admin-password', 'password'
])
assert result.exit_code == 0
assert "Site provisioned successfully!" in result.output
assert "MANUAL DNS CONFIGURATION REQUIRED" in result.output
mock_client.create_storage_zone.assert_called_once_with("test-storage", "DE")
mock_client.create_pull_zone.assert_called_once()
mock_client.add_custom_hostname.assert_called_once()
@patch('src.cli.commands.authenticate_admin')
def test_provision_site_auth_failure(self, mock_auth, cli_runner):
"""Test provision-site with authentication failure"""
mock_auth.return_value = None
result = cli_runner.invoke(app, [
'provision-site',
'--name', 'Test Site',
'--domain', 'www.example.com',
'--storage-name', 'test-storage',
'--region', 'DE',
'--admin-user', 'admin',
'--admin-password', 'wrong'
])
assert result.exit_code != 0
assert "Authentication failed" in result.output
@patch('src.cli.commands.authenticate_admin')
@patch('src.cli.commands.get_bunny_account_api_key')
def test_provision_site_missing_api_key(self, mock_get_key, mock_auth, cli_runner, mock_admin_user):
"""Test provision-site with missing API key"""
mock_auth.return_value = mock_admin_user
mock_get_key.side_effect = ValueError("BUNNY_ACCOUNT_API_KEY environment variable is required")
result = cli_runner.invoke(app, [
'provision-site',
'--name', 'Test Site',
'--domain', 'www.example.com',
'--storage-name', 'test-storage',
'--region', 'DE',
'--admin-user', 'admin',
'--admin-password', 'password'
])
assert result.exit_code != 0
assert "BUNNY_ACCOUNT_API_KEY" in result.output
@patch('src.cli.commands.authenticate_admin')
@patch('src.cli.commands.get_bunny_account_api_key')
@patch('src.cli.commands.BunnyNetClient')
@patch('src.cli.commands.db_manager')
def test_provision_site_domain_exists(
self, mock_db, mock_client_class, mock_get_key, mock_auth, cli_runner, mock_admin_user
):
"""Test provision-site with existing domain"""
mock_auth.return_value = mock_admin_user
mock_get_key.return_value = "test_api_key"
mock_session = Mock()
mock_db.get_session.return_value = mock_session
mock_repo = Mock()
mock_repo.exists.return_value = True
with patch('src.cli.commands.SiteDeploymentRepository', return_value=mock_repo):
result = cli_runner.invoke(app, [
'provision-site',
'--name', 'Test Site',
'--domain', 'www.example.com',
'--storage-name', 'test-storage',
'--region', 'DE',
'--admin-user', 'admin',
'--admin-password', 'password'
])
assert result.exit_code != 0
assert "already exists" in result.output
class TestAttachDomainCommand:
"""Tests for attach-domain CLI command"""
@patch('src.cli.commands.authenticate_admin')
@patch('src.cli.commands.get_bunny_account_api_key')
@patch('src.cli.commands.BunnyNetClient')
@patch('src.cli.commands.db_manager')
def test_attach_domain_success(
self, mock_db, mock_client_class, mock_get_key, mock_auth, cli_runner, mock_admin_user
):
"""Test successful domain attachment"""
mock_auth.return_value = mock_admin_user
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=12345, name="existing-storage", password="test-pass", region="DE"
)
mock_client.create_pull_zone.return_value = PullZoneResult(
id=67890, name="test-cdn", hostname="test-cdn.b-cdn.net"
)
mock_client.add_custom_hostname.return_value = True
mock_session = Mock()
mock_db.get_session.return_value = mock_session
mock_repo = Mock()
mock_repo.exists.return_value = False
mock_repo.create.return_value = Mock()
with patch('src.cli.commands.SiteDeploymentRepository', return_value=mock_repo):
result = cli_runner.invoke(app, [
'attach-domain',
'--name', 'Test Site',
'--domain', 'www.example.com',
'--storage-name', 'existing-storage',
'--admin-user', 'admin',
'--admin-password', 'password'
])
assert result.exit_code == 0
assert "Domain attached successfully!" in result.output
assert "MANUAL DNS CONFIGURATION REQUIRED" in result.output
mock_client.find_storage_zone_by_name.assert_called_once_with("existing-storage")
@patch('src.cli.commands.authenticate_admin')
@patch('src.cli.commands.get_bunny_account_api_key')
@patch('src.cli.commands.BunnyNetClient')
@patch('src.cli.commands.db_manager')
def test_attach_domain_storage_not_found(
self, mock_db, mock_client_class, mock_get_key, mock_auth, cli_runner, mock_admin_user
):
"""Test attach-domain with non-existent storage zone"""
mock_auth.return_value = mock_admin_user
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 = None
mock_session = Mock()
mock_db.get_session.return_value = mock_session
mock_repo = Mock()
mock_repo.exists.return_value = False
with patch('src.cli.commands.SiteDeploymentRepository', return_value=mock_repo):
result = cli_runner.invoke(app, [
'attach-domain',
'--name', 'Test Site',
'--domain', 'www.example.com',
'--storage-name', 'nonexistent',
'--admin-user', 'admin',
'--admin-password', 'password'
])
assert result.exit_code != 0
assert "not found" in result.output
class TestListSitesCommand:
"""Tests for list-sites CLI command"""
@patch('src.cli.commands.authenticate_admin')
@patch('src.cli.commands.db_manager')
def test_list_sites_success(self, mock_db, mock_auth, cli_runner, mock_admin_user, mock_site_deployment):
"""Test successful sites listing"""
mock_auth.return_value = mock_admin_user
mock_session = Mock()
mock_db.get_session.return_value = mock_session
mock_repo = Mock()
mock_repo.get_all.return_value = [mock_site_deployment]
with patch('src.cli.commands.SiteDeploymentRepository', return_value=mock_repo):
result = cli_runner.invoke(app, [
'list-sites',
'--admin-user', 'admin',
'--admin-password', 'password'
])
assert result.exit_code == 0
assert "Total sites: 1" in result.output
assert "www.example.com" in result.output
@patch('src.cli.commands.authenticate_admin')
@patch('src.cli.commands.db_manager')
def test_list_sites_empty(self, mock_db, mock_auth, cli_runner, mock_admin_user):
"""Test listing sites when none exist"""
mock_auth.return_value = mock_admin_user
mock_session = Mock()
mock_db.get_session.return_value = mock_session
mock_repo = Mock()
mock_repo.get_all.return_value = []
with patch('src.cli.commands.SiteDeploymentRepository', return_value=mock_repo):
result = cli_runner.invoke(app, [
'list-sites',
'--admin-user', 'admin',
'--admin-password', 'password'
])
assert result.exit_code == 0
assert "No site deployments found" in result.output
class TestGetSiteCommand:
"""Tests for get-site CLI command"""
@patch('src.cli.commands.authenticate_admin')
@patch('src.cli.commands.db_manager')
def test_get_site_success(self, mock_db, mock_auth, cli_runner, mock_admin_user, mock_site_deployment):
"""Test successful site details retrieval"""
mock_auth.return_value = mock_admin_user
mock_session = Mock()
mock_db.get_session.return_value = mock_session
mock_repo = Mock()
mock_repo.get_by_hostname.return_value = mock_site_deployment
with patch('src.cli.commands.SiteDeploymentRepository', return_value=mock_repo):
result = cli_runner.invoke(app, [
'get-site',
'--domain', 'www.example.com',
'--admin-user', 'admin',
'--admin-password', 'password'
])
assert result.exit_code == 0
assert "Site Deployment Details" in result.output
assert "www.example.com" in result.output
assert "test-storage" in result.output
@patch('src.cli.commands.authenticate_admin')
@patch('src.cli.commands.db_manager')
def test_get_site_not_found(self, mock_db, mock_auth, cli_runner, mock_admin_user):
"""Test get-site with non-existent domain"""
mock_auth.return_value = mock_admin_user
mock_session = Mock()
mock_db.get_session.return_value = mock_session
mock_repo = Mock()
mock_repo.get_by_hostname.return_value = None
with patch('src.cli.commands.SiteDeploymentRepository', return_value=mock_repo):
result = cli_runner.invoke(app, [
'get-site',
'--domain', 'nonexistent.com',
'--admin-user', 'admin',
'--admin-password', 'password'
])
assert result.exit_code != 0
assert "not found" in result.output
class TestRemoveSiteCommand:
"""Tests for remove-site CLI command"""
@patch('src.cli.commands.authenticate_admin')
@patch('src.cli.commands.db_manager')
def test_remove_site_success(self, mock_db, mock_auth, cli_runner, mock_admin_user, mock_site_deployment):
"""Test successful site removal"""
mock_auth.return_value = mock_admin_user
mock_session = Mock()
mock_db.get_session.return_value = mock_session
mock_repo = Mock()
mock_repo.get_by_hostname.return_value = mock_site_deployment
mock_repo.delete.return_value = True
with patch('src.cli.commands.SiteDeploymentRepository', return_value=mock_repo):
result = cli_runner.invoke(app, [
'remove-site',
'--domain', 'www.example.com',
'--admin-user', 'admin',
'--admin-password', 'password',
'--yes'
])
assert result.exit_code == 0
assert "has been removed" in result.output
assert "does NOT delete resources from bunny.net" in result.output
@patch('src.cli.commands.authenticate_admin')
@patch('src.cli.commands.db_manager')
def test_remove_site_not_found(self, mock_db, mock_auth, cli_runner, mock_admin_user):
"""Test remove-site with non-existent domain"""
mock_auth.return_value = mock_admin_user
mock_session = Mock()
mock_db.get_session.return_value = mock_session
mock_repo = Mock()
mock_repo.get_by_hostname.return_value = None
with patch('src.cli.commands.SiteDeploymentRepository', return_value=mock_repo):
result = cli_runner.invoke(app, [
'remove-site',
'--domain', 'nonexistent.com',
'--admin-user', 'admin',
'--admin-password', 'password',
'--yes'
])
assert result.exit_code != 0
assert "not found" in result.output