"""Verify /api/setup/seed creates a default station with all recipes assigned. DEVIATION FROM PLAN: The real setup endpoint is NOT a single /api/setup/initialize. The setup is split into separate endpoints: - POST /api/setup/init-db (creates tables) - POST /api/setup/seed (seeds users + demo recipe + stations) The seed body is {"password": "..."} with no load_demo_data flag (demo data is always seeded). The station seed is added inside /api/setup/seed. The seed endpoint uses async_session_factory directly (not get_db dependency injection), so we must monkeypatch routers.setup.engine and routers.setup.async_session_factory to point at the test SQLite engine, exactly as done in test_setup.py. """ import pytest from httpx import AsyncClient from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from src.backend.config import settings from src.backend.models.orm.station import Station, StationRecipeAssignment from src.backend.models.orm.recipe import Recipe from src.backend.tests.conftest import test_engine, TestSessionFactory SETUP_PWD = "test-setup-pwd" @pytest.fixture(autouse=True) def enable_setup(monkeypatch): """Enable setup endpoints and redirect engine/session to test SQLite DB.""" monkeypatch.setattr(settings, "setup_password", SETUP_PWD) import src.backend.api.routers.setup as setup_mod monkeypatch.setattr(setup_mod, "engine", test_engine) monkeypatch.setattr(setup_mod, "async_session_factory", TestSessionFactory) async def _seed(client: AsyncClient) -> dict: """Init DB tables then run seed; return seed response JSON.""" resp = await client.post("/api/setup/init-db", json={"password": SETUP_PWD}) assert resp.status_code == 200, resp.text resp = await client.post("/api/setup/seed", json={"password": SETUP_PWD}) assert resp.status_code == 200, resp.text return resp.json() @pytest.mark.asyncio async def test_setup_seed_creates_default_station_and_assigns_recipes( client: AsyncClient, db_session: AsyncSession, ): """After seeding, ST-DEFAULT station must exist and all active recipes assigned.""" await _seed(client) # Default station must exist and be active. result = await db_session.execute( select(Station).where(Station.code == "ST-DEFAULT") ) default = result.scalar_one_or_none() assert default is not None, "ST-DEFAULT station was not created" assert default.active is True # All active recipes must be assigned to the default station. active_recipes_result = await db_session.execute( select(Recipe).where(Recipe.active == True) ) active_recipes = active_recipes_result.scalars().all() assert len(active_recipes) > 0, "demo seed must create at least one active recipe" assignments_result = await db_session.execute( select(StationRecipeAssignment).where( StationRecipeAssignment.station_id == default.id ) ) n_assignments = len(assignments_result.scalars().all()) assert n_assignments == len(active_recipes), ( f"Expected {len(active_recipes)} assignment(s), got {n_assignments}" ) @pytest.mark.asyncio async def test_setup_seed_station_idempotent( client: AsyncClient, db_session: AsyncSession, ): """Running seed twice must not duplicate ST-DEFAULT or its assignments.""" # First run — creates everything. await _seed(client) # Second run — recipe DEMO-001 already exists → seed returns early. # ST-DEFAULT must still exist (not re-created, not duplicated). resp = await client.post("/api/setup/seed", json={"password": SETUP_PWD}) assert resp.status_code == 200, resp.text stations_result = await db_session.execute( select(Station).where(Station.code == "ST-DEFAULT") ) stations = stations_result.scalars().all() assert len(stations) == 1, f"Expected exactly 1 ST-DEFAULT, got {len(stations)}"