"""Unit tests: app factory edge cases (no live DB needed).""" from __future__ import annotations import logging from fastapi.testclient import TestClient import app.main as main_mod from app.config import get_settings from app.core import settings_overlay def test_create_app_warns_and_serves_api_only_without_static_dir( monkeypatch, tmp_path ) -> None: """If the frontend directory is missing, the API still boots (PLAN §7).""" monkeypatch.setattr( main_mod.settings, "static_dir", str(tmp_path / "definitely-missing") ) app2 = main_mod.create_app() client = TestClient(app2) # /api still works… assert client.get("/api/health").status_code == 200 # …but the static mount is absent (no index page). assert client.get("/").status_code == 404 def test_create_app_wires_caching_middleware() -> None: """Phase 33: the app factory always attaches the cache-busting middleware (by name) — even when the static dir is missing, so the /api/* no-touch guarantee holds in every environment.""" app2 = main_mod.create_app() # ``mw.cls`` is starlette's opaque ``_MiddlewareFactory`` protocol — # reach for ``__name__`` the same way starlette's own __repr__ does. middleware_names = [getattr(mw.cls, "__name__", "") for mw in app2.user_middleware] assert "CachingMiddleware" in middleware_names def test_create_app_boots_with_the_switch_on(monkeypatch) -> None: """Phase 168 (task 02/03): the boot prime with the kill switch ON — the ``settings overrides loaded count=…`` branch (the OFF branch is exercised by every other suite's app import; the overlay's engine is fail-soft, so a DB-down unit environment still boots). ``create_app`` also wires the chat runtime reset hook here (task 03) — the ``register_invalidator`` pin lives in ``test_settings_live_apply.py``.""" settings_overlay.reset_state_for_tests() monkeypatch.setenv("BOR_SETTINGS_DB", "1") app2 = main_mod.create_app() assert app2 is not None # restore: the switch reverts via monkeypatch; the cache clear # ensures a boot-time row set never leaks into later suites' # settings, and the hook slate is fresh for them too. settings_overlay.reset_state_for_tests() get_settings.cache_clear() def test_create_app_migration_failure_warns_and_boots( monkeypatch, caplog ) -> None: """Phase 173 (task 04, C3): a migration failure logs a WARNING and never blocks startup — the app still boots (with the pre-phase behavior: the migration is a convenience, not a dependency). The seam: the migration is monkeypatched to raise; the real migration is a no-op in a DB-less unit environment (the overlay's fail-soft read + the conftest-pinned empty ``BOR_DOCS_REPO``), so the boot path is otherwise unchanged.""" settings_overlay.reset_state_for_tests() def _boom() -> bool: raise RuntimeError("migration hiccup") monkeypatch.setattr(main_mod, "migrate_legacy_docs_creds", _boom) with caplog.at_level(logging.WARNING, logger="app"): app2 = main_mod.create_app() assert app2 is not None warnings = [ record.getMessage() for record in caplog.records if record.levelno == logging.WARNING ] assert "settings migration failed (RuntimeError)" in warnings # restore: the hook slate is fresh for the later suites, and the # cache clear keeps a boot-time row set from leaking into their # settings. settings_overlay.reset_state_for_tests() get_settings.cache_clear()