"""Test harness: a throwaway SQLite DB per test, get_db overridden, a TestClient that does NOT run lifespan startup (so schedulers/SLMM polling stay off).""" import uuid import pytest from datetime import datetime from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker from starlette.testclient import TestClient from backend.database import Base, get_db import backend.models as models # noqa: F401 (ensure all tables are registered on Base) @pytest.fixture() def db_session(tmp_path): db_file = tmp_path / "test.db" engine = create_engine(f"sqlite:///{db_file}", connect_args={"check_same_thread": False}) Base.metadata.create_all(bind=engine) TestingSession = sessionmaker(autocommit=False, autoflush=False, bind=engine) sess = TestingSession() try: yield sess finally: sess.close() engine.dispose() @pytest.fixture() def client(db_session): from backend.main import app # imported lazily so module side effects are contained def _override(): yield db_session app.dependency_overrides[get_db] = _override # No `with` → lifespan/startup events do not run (no scheduler/SLMM threads). c = TestClient(app) yield c app.dependency_overrides.pop(get_db, None) @pytest.fixture(autouse=True) def _reset_portal_lockout(): """Portal lockout state is a module-global dict; clear it between tests so one test's failed attempts can't lock out another.""" try: import backend.portal_auth as _pa if hasattr(_pa, "_failures"): _pa._failures.clear() except Exception: pass yield def make_project(db_session, name=None, **kwargs): """Insert and return a Project with a unique name.""" p = models.Project( id=str(uuid.uuid4()), name=name or f"Proj {uuid.uuid4().hex[:8]}", status="active", created_at=datetime.utcnow(), **kwargs, ) db_session.add(p) db_session.commit() return p