from __future__ import annotations from types import SimpleNamespace import pytest import src.bootstrap.app_factory as app_factory from src.bootstrap.application import ApplicationComposition class RecordingJournal: def __init__(self) -> None: self.info_calls: list[ tuple[str, str, dict[str, object]] ] = [] def log_info( self, event: str, message: str, context: dict[str, object], ) -> None: self.info_calls.append( ( event, message, context, ) ) def log_critical( self, event: str, message: str, context: dict[str, object], ) -> None: del event, message, context def make_settings( *, storage_enabled: bool = True, ) -> SimpleNamespace: return SimpleNamespace( bot_token="test-token", bot_parse_mode="HTML", log_level="INFO", app_env="test", exchange_name="dzengi", default_symbol="BTC/USD_LEVERAGE", trade_stream=SimpleNamespace(enabled=True), market_data_storage=SimpleNamespace( enabled=storage_enabled, ), ) def test_create_app_builds_one_application_composition( monkeypatch: pytest.MonkeyPatch, ) -> None: settings = make_settings() bot = object() dispatcher = object() runtime = object() storage_lifecycle = object() storage_sink = object() storage_repository = object() storage = SimpleNamespace( lifecycle=storage_lifecycle, trade_observation_sink=storage_sink, trade_repository=storage_repository, ) journal = RecordingJournal() observed_runtime_settings: list[object] = [] registered_bots: list[object] = [] routed_dispatchers: list[object] = [] monkeypatch.setattr( app_factory, "load_settings", lambda: settings, ) monkeypatch.setattr( app_factory, "setup_logging", lambda level: None, ) monkeypatch.setattr( app_factory, "init_schema", lambda: None, ) monkeypatch.setattr( app_factory, "JournalService", lambda: journal, ) monkeypatch.setattr( app_factory, "build_market_data_storage", lambda received_settings: ( storage if received_settings is settings else None ), ) def build_runtime( received_settings: object, *, trade_observation_sink: object, checkpoint_storage: object, ) -> object: observed_runtime_settings.append(received_settings) assert trade_observation_sink is storage_sink assert checkpoint_storage is storage_repository return runtime monkeypatch.setattr( app_factory, "build_trade_stream_production_runtime", build_runtime, ) monkeypatch.setattr( app_factory, "Bot", lambda **kwargs: bot, ) monkeypatch.setattr( app_factory, "Dispatcher", lambda: dispatcher, ) monkeypatch.setattr( app_factory.NotificationTargetRegistry, "set_bot", registered_bots.append, ) monkeypatch.setattr( app_factory, "setup_routers", routed_dispatchers.append, ) application = app_factory.create_app() assert isinstance(application, ApplicationComposition) assert application.bot is bot assert application.dispatcher is dispatcher assert application.trade_stream_runtime is runtime assert ( application.market_data_storage_lifecycle is storage_lifecycle ) assert observed_runtime_settings == [settings] assert registered_bots == [bot] assert routed_dispatchers == [dispatcher] assert journal.info_calls[0][2]["trade_stream_enabled"] is True assert ( journal.info_calls[0][2]["market_data_storage_enabled"] is True ) def test_runtime_build_error_is_fatal( monkeypatch: pytest.MonkeyPatch, ) -> None: expected = RuntimeError("invalid Trade Stream settings") monkeypatch.setattr( app_factory, "load_settings", lambda: make_settings(storage_enabled=False), ) monkeypatch.setattr( app_factory, "setup_logging", lambda level: None, ) monkeypatch.setattr( app_factory, "init_schema", lambda: None, ) monkeypatch.setattr( app_factory, "JournalService", RecordingJournal, ) monkeypatch.setattr( app_factory, "build_market_data_storage", lambda settings: None, ) def fail_runtime_build( settings: object, *, trade_observation_sink: object, checkpoint_storage: object, ) -> None: del settings assert trade_observation_sink is None assert checkpoint_storage is None raise expected monkeypatch.setattr( app_factory, "build_trade_stream_production_runtime", fail_runtime_build, ) with pytest.raises(RuntimeError) as exc_info: app_factory.create_app() assert exc_info.value is expected