Files
bootnode/api/tests/test_deploy.py
T
2026-02-10 14:57:33 -08:00

196 lines
6.4 KiB
Python

"""Tests for the deployment abstraction layer."""
import asyncio
import pytest
from bootnode.core.deploy import (
DeployTarget,
DockerDeployer,
KubernetesDeployer,
ProcessDeployer,
ServiceStatus,
ServiceType,
clear_deployer_cache,
get_deployer,
)
class TestServiceType:
"""Tests for ServiceType enum."""
def test_service_types_exist(self):
"""All expected service types should be defined."""
assert ServiceType.API.value == "api"
assert ServiceType.WEB.value == "web"
assert ServiceType.INDEXER.value == "indexer"
assert ServiceType.WEBHOOK_WORKER.value == "webhook-worker"
assert ServiceType.BUNDLER.value == "bundler"
def test_service_type_from_string(self):
"""ServiceType should be constructable from string."""
assert ServiceType("api") == ServiceType.API
assert ServiceType("web") == ServiceType.WEB
class TestServiceStatus:
"""Tests for ServiceStatus model."""
def test_service_status_basic(self):
"""ServiceStatus should work with minimal fields."""
status = ServiceStatus(
name="test",
running=True,
replicas=2,
ready_replicas=2,
)
assert status.name == "test"
assert status.running is True
assert status.replicas == 2
assert status.ready_replicas == 2
assert status.cpu_usage is None
assert status.memory_usage is None
def test_service_status_full(self):
"""ServiceStatus should work with all fields."""
status = ServiceStatus(
name="api",
running=True,
replicas=3,
ready_replicas=2,
cpu_usage=25.5,
memory_usage=512.0,
image="ghcr.io/hanzoai/bootnode:api-latest",
started_at="2024-01-15T10:00:00Z",
)
assert status.cpu_usage == 25.5
assert status.memory_usage == 512.0
assert status.image == "ghcr.io/hanzoai/bootnode:api-latest"
class TestDeployTarget:
"""Tests for DeployTarget ABC."""
def test_is_abstract(self):
"""DeployTarget should not be instantiable directly."""
with pytest.raises(TypeError):
DeployTarget()
class TestProcessDeployer:
"""Tests for ProcessDeployer."""
def test_init(self):
"""ProcessDeployer should initialize correctly."""
deployer = ProcessDeployer()
assert deployer.project_root is not None
assert len(deployer._processes) == 0
@pytest.mark.asyncio
async def test_status_not_running(self):
"""Status should show not running for unstarted service."""
deployer = ProcessDeployer()
status = await deployer.status(ServiceType.API)
assert status.running is False
assert status.replicas == 0
assert status.ready_replicas == 0
@pytest.mark.asyncio
async def test_destroy_not_running(self):
"""Destroy should succeed for service not running."""
deployer = ProcessDeployer()
result = await deployer.destroy(ServiceType.API)
assert result is True
@pytest.mark.asyncio
async def test_scale_zero(self):
"""Scale to zero should destroy service."""
deployer = ProcessDeployer()
result = await deployer.scale(ServiceType.API, 0)
assert result is True
class TestDockerDeployer:
"""Tests for DockerDeployer."""
def test_init_default(self):
"""DockerDeployer should initialize with defaults."""
deployer = DockerDeployer()
assert deployer.compose_file.name == "compose.yml"
assert deployer.project_name == "bootnode"
def test_init_custom(self):
"""DockerDeployer should accept custom compose file."""
deployer = DockerDeployer(
compose_file="/custom/docker-compose.yml",
project_name="custom",
)
assert str(deployer.compose_file) == "/custom/docker-compose.yml"
assert deployer.project_name == "custom"
def test_get_service_name(self):
"""Service name mapping should work correctly."""
deployer = DockerDeployer()
assert deployer._get_service_name(ServiceType.API) == "api"
assert deployer._get_service_name(ServiceType.WEBHOOK_WORKER) == "webhook-worker"
class TestKubernetesDeployer:
"""Tests for KubernetesDeployer."""
def test_init_default(self):
"""KubernetesDeployer should initialize with defaults."""
deployer = KubernetesDeployer()
assert deployer.namespace == "bootnode"
assert deployer.context is None
assert deployer._initialized is False
def test_init_custom(self):
"""KubernetesDeployer should accept custom namespace and context."""
deployer = KubernetesDeployer(
namespace="custom-ns",
context="my-context",
)
assert deployer.namespace == "custom-ns"
assert deployer.context == "my-context"
def test_get_deployment_name(self):
"""Deployment name mapping should work correctly."""
deployer = KubernetesDeployer()
assert deployer._get_deployment_name(ServiceType.API) == "bootnode-api"
assert deployer._get_deployment_name(ServiceType.WEB) == "bootnode-web"
def test_get_labels(self):
"""Labels should be generated correctly."""
deployer = KubernetesDeployer()
labels = deployer._get_labels(ServiceType.API)
assert labels["app.kubernetes.io/name"] == "api"
assert labels["app.kubernetes.io/part-of"] == "bootnode"
class TestFactory:
"""Tests for deployer factory."""
def test_get_deployer_returns_deploytarget(self):
"""get_deployer should return a DeployTarget instance."""
clear_deployer_cache()
deployer = get_deployer()
assert isinstance(deployer, DeployTarget)
def test_get_deployer_cached(self):
"""get_deployer should return cached instance."""
clear_deployer_cache()
deployer1 = get_deployer()
deployer2 = get_deployer()
assert deployer1 is deployer2
def test_clear_cache(self):
"""clear_deployer_cache should clear the cached instance."""
clear_deployer_cache()
deployer1 = get_deployer()
clear_deployer_cache()
deployer2 = get_deployer()
# They should be equal in value but may or may not be same object
# depending on config - just verify no error
assert deployer2 is not None