Commit a6dfa65a authored by Dimitrios Gogos's avatar Dimitrios Gogos Committed by Paris Stentoumis
Browse files

fix: add failure handling tests for terminate service use case

parent 1effbe30
Loading
Loading
Loading
Loading
+71 −0
Changes for tests/application/test_terminate_service_command_coordinator.py: 71 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -164,3 +164,74 @@ async def test_handle_republishes_terminal_event_for_terminal_duplicate() -> Non
    assert subject == "event.srm.operation.completed"
    assert payload["status"] == "completed"
    assert payload["service_order_id"] == str(duplicate_order_id)


async def test_handle_publishes_failed_event_when_complete_raises() -> None:
    session_maker = FakeSessionMaker()
    accepted_order_id = UUID("aaaaaaaa-0000-4000-8000-000000000001")
    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": "failed", "status": 500},
        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 = TerminateServiceResult(
        accepted_order_id=accepted_order_id
    )
    complete_use_case = AsyncMock()
    complete_use_case.complete.side_effect = RuntimeError("adapter timeout")
    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 = TerminateServiceCommandCoordinator(
        session_maker=session_maker,  # type: ignore[arg-type]
        use_case_factory=use_case_factory,
        publisher=publisher,
    )

    await coordinator.handle(_valid_envelope())

    session_maker.contexts[1].session.rollback.assert_awaited_once()
    session_maker.contexts[2].session.commit.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[1].args
    assert subject == "event.srm.operation.completed"
    assert payload["status"] == "failed"


async def test_handle_publishes_fallback_failed_event_when_fail_after_accept_raises() -> None:
    session_maker = FakeSessionMaker()
    accepted_order_id = UUID("aaaaaaaa-0000-4000-8000-000000000001")
    accept_use_case = AsyncMock()
    accept_use_case.accept.return_value = TerminateServiceResult(
        accepted_order_id=accepted_order_id,
        target_zone_id=UUID("642f6105-7015-4af1-a4d1-e1ecb8437abc"),
    )
    complete_use_case = AsyncMock()
    complete_use_case.complete.side_effect = RuntimeError("adapter timeout")
    fail_use_case = AsyncMock()
    fail_use_case.fail_after_accept.side_effect = RuntimeError("database down")
    use_case_factory = MagicMock(side_effect=[accept_use_case, complete_use_case, fail_use_case])
    publisher = AsyncMock()
    coordinator = TerminateServiceCommandCoordinator(
        session_maker=session_maker,  # type: ignore[arg-type]
        use_case_factory=use_case_factory,
        publisher=publisher,
    )

    await coordinator.handle(_valid_envelope())

    session_maker.contexts[2].session.rollback.assert_awaited_once()
    assert publisher.publish.await_count == 2
    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)
    assert payload["instances"][0]["status"] == "failed"
    assert payload["instances"][0]["error"]["detail"] == "adapter timeout"
+56 −0
Changes for tests/unit/test_terminate_service_use_case.py: 56 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -288,3 +288,59 @@ async def test_accept_replays_completed_event_when_completed_operation_order_exi
            "status": "completed",
        }
    ]


async def test_fail_after_accept_marks_order_failed_and_reports_failed_instance() -> None:
    operation_id = uuid4()
    service_instance = _service_instance()
    service_order = _service_order(operation_id, target_service_instance_id=service_instance.id)
    service_orders = AsyncMock()
    service_orders.get_by_id.return_value = service_order

    completed_event = await _use_case(
        service_orders=service_orders,
        publisher=AsyncMock(),
    ).fail_after_accept(
        _command(operation_id, service_instance_id=service_instance.id),
        TerminateServiceResult(
            accepted_order_id=service_order.id,
            target_zone_id=service_instance.zone_id,
        ),
        RuntimeError("adapter timeout"),
    )

    service_orders.update_state.assert_awaited_once()
    assert service_orders.update_state.await_args.args[:2] == (
        service_order.id,
        ServiceOrderState.FAILED,
    )
    assert service_orders.update_state.await_args.kwargs["failure_detail"]["detail"] == (
        "adapter timeout"
    )
    assert completed_event.status == "failed"
    assert completed_event.error is None
    assert len(completed_event.instances) == 1
    assert completed_event.instances[0]["service_instance_id"] == str(service_instance.id)
    assert completed_event.instances[0]["status"] == "failed"
    assert completed_event.instances[0]["error"]["detail"] == "adapter timeout"


async def test_fail_after_accept_without_zone_reports_top_level_error() -> None:
    operation_id = uuid4()
    service_order = _service_order(operation_id)
    service_orders = AsyncMock()
    service_orders.get_by_id.return_value = service_order

    completed_event = await _use_case(
        service_orders=service_orders,
        publisher=AsyncMock(),
    ).fail_after_accept(
        _command(operation_id),
        TerminateServiceResult(accepted_order_id=service_order.id),
        RuntimeError("adapter timeout"),
    )

    assert completed_event.status == "failed"
    assert completed_event.instances == []
    assert completed_event.error is not None
    assert completed_event.error["detail"] == "adapter timeout"