Commit 5bdd8eb8 authored by Paris Stentoumis's avatar Paris Stentoumis
Browse files

feat: added some tests

parent 4f406bab
Loading
Loading
Loading
Loading
Loading
+46 −0
Changes for tests/api/databus/test_dependencies.py: 46 added lines, 0 removed lines.
Original line number Diff line number Diff line
from unittest.mock import MagicMock, patch

from srm.api.databus.dependencies import get_deploy_service_use_case


def test_get_deploy_service_use_case_wires_required_repositories() -> None:
    session = MagicMock()
    connection_manager = MagicMock()

    with (
        patch("srm.api.databus.dependencies.SqlServiceSpecificationRepository") as specs,
        patch("srm.api.databus.dependencies.SqlServiceDeploymentUnitRepository") as units,
        patch(
            "srm.api.databus.dependencies.SqlServiceCapabilityRequirementRepository"
        ) as requirements,
        patch("srm.api.databus.dependencies.SqlServiceOrderRepository") as orders,
        patch("srm.api.databus.dependencies.SqlServiceInstanceRepository") as instances,
        patch("srm.api.databus.dependencies.SqlCapabilityInstanceRepository") as capabilities,
        patch("srm.api.databus.dependencies.SqlZoneRepository") as zones,
        patch("srm.api.databus.dependencies.SqlDomainRepository") as domains,
        patch("srm.api.databus.dependencies.NatsPublisher") as publisher,
        patch("srm.api.databus.dependencies.DeployServiceUseCase") as use_case,
    ):
        result = get_deploy_service_use_case(session, connection_manager)

    specs.assert_called_once_with(session)
    units.assert_called_once_with(session)
    requirements.assert_called_once_with(session)
    orders.assert_called_once_with(session)
    instances.assert_called_once_with(session)
    capabilities.assert_called_once_with(session)
    zones.assert_called_once_with(session)
    domains.assert_called_once_with(session)
    publisher.assert_called_once_with(connection_manager)
    use_case.assert_called_once_with(
        service_specifications=specs.return_value,
        service_deployment_units=units.return_value,
        service_capability_requirements=requirements.return_value,
        service_orders=orders.return_value,
        service_instances=instances.return_value,
        capability_instances=capabilities.return_value,
        zones=zones.return_value,
        domains=domains.return_value,
        publisher=publisher.return_value,
    )
    assert result is use_case.return_value
+250 −0
Changes for tests/application/test_deploy_service_command_coordinator.py: 250 added lines, 0 removed lines.
Original line number Diff line number Diff line
from __future__ import annotations

from dataclasses import dataclass
from datetime import datetime
from unittest.mock import AsyncMock, MagicMock
from uuid import UUID

from srm.api.databus.schemas import ValidatedEnvelopeV1
from srm.application.command_handlers.deploy_service import DeployServiceCommandCoordinator
from srm.application.use_cases.deploy_service import DeployServiceResult
from srm.domain.ports.databus.events import (
    OperationCompletedStatus,
    SrmOperationCompletedV1,
)


@dataclass
class FakeSessionContext:
    session: AsyncMock

    async def __aenter__(self) -> AsyncMock:
        return self.session

    async def __aexit__(self, *_args: object) -> None:
        return None


class FakeSessionMaker:
    def __init__(self) -> None:
        self.contexts: list[FakeSessionContext] = []

    def __call__(self) -> FakeSessionContext:
        context = FakeSessionContext(session=AsyncMock())
        self.contexts.append(context)
        return context


def _valid_envelope(*, targets: list[dict[str, str]] | None = None) -> ValidatedEnvelopeV1:
    return ValidatedEnvelopeV1.model_validate(
        {
            "schema_version": "1.0",
            "operation_id": "11111111-2222-3333-4444-555555555555",
            "correlation_id": "corr-appinst-001",
            "requested_at": "2026-06-19T12:00:00Z",
            "app_provider_id": "VideoAppsCo",
            "source": "nbi_camara",
            "service_specification_id": "d4e5f6a7-0000-4000-8000-000000000001",
            "targets": targets
            if targets is not None
            else [
                {
                    "app_instance_id": "9a3f1c22-0000-4000-8000-000000000002",
                    "zone_id": "642f6105-7015-4af1-a4d1-e1ecb8437abc",
                }
            ],
            "deploy": {"instance_name": "video_analytics_berlin_01"},
        }
    )


async def test_handle_publishes_failed_before_start_for_stage2_schema_failure() -> None:
    session_maker = FakeSessionMaker()
    use_case_factory = MagicMock()
    publisher = AsyncMock()
    coordinator = DeployServiceCommandCoordinator(
        session_maker=session_maker,  # type: ignore[arg-type]
        use_case_factory=use_case_factory,
        publisher=publisher,
    )

    await coordinator.handle(_valid_envelope(targets=[]))

    assert session_maker.contexts == []
    use_case_factory.assert_not_called()
    assert publisher.publish.await_count == 2
    subject, payload = publisher.publish.await_args_list[0].args
    assert subject == "event.srm.operation.status"
    assert payload["state"] == "failed_before_start"
    subject, payload = publisher.publish.await_args_list[1].args
    assert subject == "event.srm.operation.completed"
    assert payload["status"] == "failed"


async def test_handle_commits_accept_before_publishing_accepted_then_completes() -> None:
    session_maker = FakeSessionMaker()
    accepted_order_id = UUID("aaaaaaaa-0000-4000-8000-000000000001")
    completed_event = SrmOperationCompletedV1(
        schema_version="1.0",
        operation_id=UUID("11111111-2222-3333-4444-555555555555"),
        status=OperationCompletedStatus.COMPLETED,
        service_order_id=accepted_order_id,
        instances=[
            {
                "service_instance_id": "9a3f1c22-0000-4000-8000-000000000002",
                "zone_id": "642f6105-7015-4af1-a4d1-e1ecb8437abc",
                "status": "completed",
            }
        ],
        correlation_id="corr-appinst-001",
        completed_at=datetime.fromisoformat("2026-06-19T12:01:00+00:00"),
    )
    accept_use_case = AsyncMock()
    accept_use_case.accept.return_value = DeployServiceResult(accepted_order_id=accepted_order_id)
    complete_use_case = AsyncMock()
    complete_use_case.complete.return_value = completed_event
    use_case_factory = MagicMock(side_effect=[accept_use_case, complete_use_case])
    publisher = AsyncMock()
    coordinator = DeployServiceCommandCoordinator(
        session_maker=session_maker,  # type: ignore[arg-type]
        use_case_factory=use_case_factory,
        publisher=publisher,
    )

    await coordinator.handle(_valid_envelope())

    assert len(session_maker.contexts) == 2
    session_maker.contexts[0].session.commit.assert_awaited_once()
    session_maker.contexts[0].session.rollback.assert_not_awaited()
    session_maker.contexts[1].session.commit.assert_awaited_once()
    session_maker.contexts[1].session.rollback.assert_not_awaited()
    accept_use_case.accept.assert_awaited_once()
    complete_use_case.complete.assert_awaited_once()
    assert publisher.publish.await_count == 2
    subject, payload = publisher.publish.await_args_list[0].args
    assert subject == "event.srm.operation.status"
    assert payload["state"] == "accepted"
    assert payload["service_order_id"] == str(accepted_order_id)
    subject, payload = publisher.publish.await_args_list[1].args
    assert subject == "event.srm.operation.completed"
    assert payload["status"] == "completed"
    assert payload["service_order_id"] == str(accepted_order_id)


async def test_handle_publishes_failed_completed_when_completion_raises_after_accept() -> None:
    session_maker = FakeSessionMaker()
    accepted_order_id = UUID("aaaaaaaa-0000-4000-8000-000000000001")
    accepted = DeployServiceResult(
        accepted_order_id=accepted_order_id,
        target_placements=[],
    )
    failed_event = SrmOperationCompletedV1(
        schema_version="1.0",
        operation_id=UUID("11111111-2222-3333-4444-555555555555"),
        status=OperationCompletedStatus.FAILED,
        service_order_id=accepted_order_id,
        error={
            "type": "about:blank",
            "title": "Deploy service failed after acceptance.",
            "status": 500,
            "detail": "adapter failed",
        },
        correlation_id="corr-appinst-001",
        completed_at=datetime.fromisoformat("2026-06-19T12:01:00+00:00"),
    )
    accept_use_case = AsyncMock()
    accept_use_case.accept.return_value = accepted
    complete_use_case = AsyncMock()
    complete_use_case.complete.side_effect = RuntimeError("adapter failed")
    fail_use_case = AsyncMock()
    fail_use_case.fail_after_accept.return_value = failed_event
    use_case_factory = MagicMock(side_effect=[accept_use_case, complete_use_case, fail_use_case])
    publisher = AsyncMock()
    coordinator = DeployServiceCommandCoordinator(
        session_maker=session_maker,  # type: ignore[arg-type]
        use_case_factory=use_case_factory,
        publisher=publisher,
    )

    await coordinator.handle(_valid_envelope())

    assert len(session_maker.contexts) == 3
    session_maker.contexts[0].session.commit.assert_awaited_once()
    session_maker.contexts[1].session.rollback.assert_awaited_once()
    session_maker.contexts[2].session.commit.assert_awaited_once()
    complete_use_case.complete.assert_awaited_once()
    fail_use_case.fail_after_accept.assert_awaited_once()
    assert publisher.publish.await_count == 2
    subject, payload = publisher.publish.await_args_list[0].args
    assert subject == "event.srm.operation.status"
    assert payload["state"] == "accepted"
    subject, payload = publisher.publish.await_args_list[1].args
    assert subject == "event.srm.operation.completed"
    assert payload["status"] == "failed"
    assert payload["service_order_id"] == str(accepted_order_id)


async def test_handle_republishes_accepted_for_non_terminal_duplicate() -> None:
    session_maker = FakeSessionMaker()
    duplicate_order_id = UUID("aaaaaaaa-0000-4000-8000-000000000001")
    accept_use_case = AsyncMock()
    accept_use_case.accept.return_value = DeployServiceResult(duplicate_order_id=duplicate_order_id)
    use_case_factory = MagicMock(return_value=accept_use_case)
    publisher = AsyncMock()
    coordinator = DeployServiceCommandCoordinator(
        session_maker=session_maker,  # type: ignore[arg-type]
        use_case_factory=use_case_factory,
        publisher=publisher,
    )

    await coordinator.handle(_valid_envelope())

    assert len(session_maker.contexts) == 1
    accept_use_case.accept.assert_awaited_once()
    publisher.publish.assert_awaited_once()
    subject, payload = publisher.publish.await_args.args
    assert subject == "event.srm.operation.status"
    assert payload["state"] == "accepted"
    assert payload["service_order_id"] == str(duplicate_order_id)


async def test_handle_republishes_terminal_event_for_terminal_duplicate() -> None:
    session_maker = FakeSessionMaker()
    duplicate_order_id = UUID("aaaaaaaa-0000-4000-8000-000000000001")
    completed_event = SrmOperationCompletedV1(
        schema_version="1.0",
        operation_id=UUID("11111111-2222-3333-4444-555555555555"),
        status=OperationCompletedStatus.COMPLETED,
        service_order_id=duplicate_order_id,
        instances=[
            {
                "service_instance_id": "9a3f1c22-0000-4000-8000-000000000002",
                "zone_id": "642f6105-7015-4af1-a4d1-e1ecb8437abc",
                "status": "completed",
            }
        ],
        correlation_id="corr-appinst-001",
        completed_at=datetime.fromisoformat("2026-06-19T12:01:00+00:00"),
    )
    accept_use_case = AsyncMock()
    accept_use_case.accept.return_value = DeployServiceResult(
        duplicate_order_id=duplicate_order_id,
        completed_event=completed_event,
    )
    use_case_factory = MagicMock(return_value=accept_use_case)
    publisher = AsyncMock()
    coordinator = DeployServiceCommandCoordinator(
        session_maker=session_maker,  # type: ignore[arg-type]
        use_case_factory=use_case_factory,
        publisher=publisher,
    )

    await coordinator.handle(_valid_envelope())

    assert len(session_maker.contexts) == 1
    accept_use_case.accept.assert_awaited_once()
    publisher.publish.assert_awaited_once()
    subject, payload = publisher.publish.await_args.args
    assert subject == "event.srm.operation.completed"
    assert payload["status"] == "completed"
    assert payload["service_order_id"] == str(duplicate_order_id)
+10 −0
Changes for tests/integration/test_repositories.py: 10 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -428,6 +428,16 @@ async def test_service_order_create_accepts_deactivate_capability(
    assert reloaded.order_type == ServiceOrderType.DEACTIVATE_CAPABILITY


async def test_service_order_get_by_operation_id(db_session: AsyncSession) -> None:
    zone = await SqlZoneRepository(db_session).create(_zone())
    repo = SqlServiceOrderRepository(db_session)
    saved = await repo.create(_service_order(zone.id))

    reloaded = await repo.get_by_operation_id(saved.operation_id)

    assert reloaded == saved


class TestChildRepositoriesGetById:
    """Deployment units and capability requirements are written via the aggregate
    root, so only their get_by_id is exercised directly."""
+61 −0
Changes for tests/unit/test_databus_events.py: 61 added lines, 0 removed lines.
Original line number Diff line number Diff line
from datetime import UTC, datetime
from uuid import uuid4

from srm.domain.ports.databus.events import (
    OperationCompletedStatus,
    OperationStatusState,
    SrmOperationCompletedV1,
    SrmOperationStatusV1,
)


def test_operation_status_event_dumps_contract_json() -> None:
    operation_id = uuid4()
    service_order_id = uuid4()

    event = SrmOperationStatusV1(
        schema_version="1.0",
        operation_id=operation_id,
        service_order_id=service_order_id,
        state=OperationStatusState.FAILED_BEFORE_START,
        capability="deploy",
        correlation_id="corr-1",
        emitted_at=datetime(2026, 6, 19, 12, 0, tzinfo=UTC),
    )

    assert event.model_dump(mode="json") == {
        "schema_version": "1.0",
        "operation_id": str(operation_id),
        "service_order_id": str(service_order_id),
        "service_instance_id": None,
        "capability": "deploy",
        "state": "failed_before_start",
        "metadata": {},
        "correlation_id": "corr-1",
        "emitted_at": "2026-06-19T12:00:00Z",
    }


def test_operation_completed_event_dumps_failed_contract_json() -> None:
    operation_id = uuid4()

    event = SrmOperationCompletedV1(
        schema_version="1.0",
        operation_id=operation_id,
        status=OperationCompletedStatus.FAILED,
        error={"type": "about:blank", "title": "Failed.", "status": 400},
        correlation_id="corr-1",
        completed_at=datetime(2026, 6, 19, 12, 0, tzinfo=UTC),
    )

    assert event.model_dump(mode="json") == {
        "schema_version": "1.0",
        "operation_id": str(operation_id),
        "status": "failed",
        "service_order_id": None,
        "instances": [],
        "metadata": {},
        "error": {"type": "about:blank", "title": "Failed.", "status": 400},
        "correlation_id": "corr-1",
        "completed_at": "2026-06-19T12:00:00Z",
    }
+1168 −0

File added.

Preview size limit exceeded, changes collapsed.