"""Real-Postgres integration tests for project inception (milestone 297). What mocks can't prove: a decision's effects land through the real services (exclusions filter the always-on set, subscriptions bind, the design system points, the standard Systems seed once), the record is written last, a bad target applies nothing. """ import pytest import pytest_asyncio from scribe.models import async_session from scribe.models.project import Project from scribe.models.rulebook import Rulebook from scribe.services import inception as inception_svc from scribe.services import rulebooks as rulebooks_svc from scribe.services import canonical_systems as canonical_svc from scribe.services import systems as systems_svc from tests.helpers import ensure_user pytestmark = [pytest.mark.integration, pytest.mark.usefixtures("_dispose_engine")] @pytest_asyncio.fixture async def seeded(): """Owner, a fresh project, one always-on rulebook (with a rule) and one ordinary rulebook (with a rule).""" async with async_session() as s: owner = await ensure_user(s, "inception_owner") project = Project(user_id=owner.id, title="Inception target") s.add(project) await s.flush() ids = {"owner": owner.id, "pid": project.id} await s.commit() always = await rulebooks_svc.create_rulebook(ids["owner"], "Family standards") other = await rulebooks_svc.create_rulebook(ids["owner"], "Optional practices") async with async_session() as s: rb = await s.get(Rulebook, always.id) rb.always_on = True await s.commit() t1 = await rulebooks_svc.create_topic(always.id, ids["owner"], "git") await rulebooks_svc.create_rule(t1.id, ids["owner"], "dev is home", "Work on dev.") t2 = await rulebooks_svc.create_topic(other.id, ids["owner"], "docs") await rulebooks_svc.create_rule(t2.id, ids["owner"], "Write the why", "Record reasons.") ids.update({"always": always.id, "other": other.id}) return ids @pytest.mark.integration async def test_decide_applies_every_effect_and_records_last(seeded): owner, pid = seeded["owner"], seeded["pid"] # Undecided: the always-on rulebook binds, nothing subscribed, no Systems. assert [r.title for r in await rulebooks_svc.list_always_on_rules(owner, project_id=pid)] == ["dev is home"] defaults = await inception_svc.current_defaults(owner, pid) assert [r["id"] for r in defaults["always_on_rulebooks"]] == [seeded["always"]] assert [r["id"] for r in defaults["other_rulebooks"]] == [seeded["other"]] assert defaults["systems"] == 0 and defaults["design_system_id"] is None out = await inception_svc.decide(owner, pid, via="mcp", choices={ "exclude_always_on_rulebooks": [seeded["always"]], "subscribe_rulebooks": [seeded["other"]], "design_system_id": None, "seed_systems": True, }) assert out["effects"]["excluded"] == [seeded["always"]] assert out["effects"]["subscribed"] == [seeded["other"]] catalog = await canonical_svc.list_canonical_systems() assert len(out["effects"]["systems_seeded"]) == len(catalog) # Seeded Systems come out mapped, not needing a later reconciliation. seeded_systems = await systems_svc.list_systems(owner, pid) assert all(s.canonical_id is not None for s in seeded_systems) # The exclusion is total: the project's always-on set is empty, the # departure is named, the subscription binds. assert await rulebooks_svc.list_always_on_rules(owner, project_id=pid) == [] assert len(await rulebooks_svc.list_always_on_rules(owner)) == 1 # user-wide unchanged applicable = await rulebooks_svc.get_applicable_rules(pid, owner) assert [r["title"] for r in applicable["rules"]] == ["Write the why"] assert [e["id"] for e in applicable["excluded_always_on"]] == [seeded["always"]] assert [s["id"] for s in applicable["subscribed_rulebooks"]] == [seeded["other"]] # The record, written last, says why. async with async_session() as s: project = await s.get(Project, pid) assert inception_svc.is_decided(project) assert project.inception["via"] == "mcp" and project.inception["decided_by"] == owner assert project.inception["choices"]["exclude_always_on_rulebooks"] == [seeded["always"]] # Re-deciding with seed again mints nothing twice; include reverses the exclusion. again = await inception_svc.decide(owner, pid, via="ui", choices={"seed_systems": True}) assert again["effects"]["systems_seeded"] == [] assert len(await systems_svc.list_systems(owner, pid)) == len(catalog) await rulebooks_svc.include_always_on_rulebook_for_project(pid, seeded["always"], owner) assert [r.title for r in await rulebooks_svc.list_always_on_rules(owner, project_id=pid)] == ["dev is home"] @pytest.mark.integration async def test_a_bad_decision_applies_nothing(seeded): owner, pid = seeded["owner"], seeded["pid"] # Excluding a rulebook that is not always-on is refused BEFORE any effect. with pytest.raises(ValueError, match="not always-on"): await inception_svc.decide(owner, pid, via="mcp", choices={ "exclude_always_on_rulebooks": [seeded["other"]], "seed_systems": True, }) assert await systems_svc.list_systems(owner, pid) == [] with pytest.raises(ValueError, match="not found"): await inception_svc.decide(owner, pid, via="mcp", choices={"subscribe_rulebooks": [999999]}) with pytest.raises(ValueError, match="legacy"): await inception_svc.decide(owner, pid, via="legacy", choices={}) async with async_session() as s: project = await s.get(Project, pid) assert not inception_svc.is_decided(project) # An outsider cannot decide someone else's project. async with async_session() as s: other = await ensure_user(s, "inception_other") other_id = other.id await s.commit() with pytest.raises(ValueError, match="not found"): await inception_svc.decide(other_id, pid, via="mcp", choices={})