import asyncio from typing import Any from unittest.mock import AsyncMock import pytest from fastapi import FastAPI from app import main from app.config import Settings class FakeBroker: instance: "FakeBroker" def __init__(self, *_: Any, **__: Any) -> None: type(self).instance = self self.started = False self.stopped = False async def start(self) -> None: self.started = True async def stop(self) -> None: self.stopped = True class CooperativePublisher: instance: "CooperativePublisher" def __init__(self, *_: Any, **__: Any) -> None: type(self).instance = self self.stopped = False async def run(self, stop_event: asyncio.Event) -> None: await stop_event.wait() self.stopped = True class StuckPublisher: instance: "StuckPublisher" def __init__(self, *_: Any, **__: Any) -> None: type(self).instance = self self.cancelled = False async def run(self, _: asyncio.Event) -> None: try: await asyncio.Event().wait() finally: self.cancelled = True async def test_lifespan_starts_and_stops_resources( monkeypatch: pytest.MonkeyPatch, ) -> None: declare_topology = AsyncMock() close_database = AsyncMock() monkeypatch.setattr(main, "RabbitBroker", FakeBroker) monkeypatch.setattr(main, "OutboxPublisher", CooperativePublisher) monkeypatch.setattr(main, "declare_topology", declare_topology) monkeypatch.setattr(main, "close_database", close_database) async with main.lifespan(FastAPI()): assert FakeBroker.instance.started is True assert CooperativePublisher.instance.stopped is True assert FakeBroker.instance.stopped is True declare_topology.assert_awaited_once_with(FakeBroker.instance) close_database.assert_awaited_once_with() async def test_lifespan_cancels_stuck_publisher( monkeypatch: pytest.MonkeyPatch, ) -> None: monkeypatch.setattr(main, "RabbitBroker", FakeBroker) monkeypatch.setattr(main, "OutboxPublisher", StuckPublisher) monkeypatch.setattr(main, "declare_topology", AsyncMock()) monkeypatch.setattr(main, "close_database", AsyncMock()) monkeypatch.setattr( main, "settings", Settings(outbox_publish_timeout=0.001, outbox_poll_interval=0.001), ) async with main.lifespan(FastAPI()): pass assert StuckPublisher.instance.cancelled is True assert FakeBroker.instance.stopped is True