"""§22 S6 — two-project / multi-collection integrative pass. Proves the S6 surface holds across a deployment with two projects, each owning distinct collections, with the new per-collection knobs (§22.12 enabled_models, §22.4a type noun) resolving independently per collection — no cross-project or cross-collection bleed. Slug note: the resolver is slug-keyed, so this test uses distinct slugs across collections (the documented limitation — same-slug-across-collections model resolution is part of the broader collection-id-threading follow-up). """ from __future__ import annotations import json from fastapi.testclient import TestClient from app import collections, db, models_resolver from test_propose_vertical import app_with_fake_gitea, tmp_env # noqa: F401 from test_rfc_view_vertical import FakeProvider # noqa: F401 def _project(pid: str, visibility: str = "public") -> None: db.conn().execute( "INSERT OR REPLACE INTO projects (id, name, content_repo, visibility, config_json, updated_at) " "VALUES (?, ?, ?, ?, ?, datetime('now'))", (pid, pid.capitalize(), f"{pid}-content", visibility, None), ) def _collection(cid: str, project_id: str, *, ctype: str, enabled_models=None) -> None: cfg = None if enabled_models is None else json.dumps({"enabled_models": enabled_models}) db.conn().execute( "INSERT OR REPLACE INTO collections " "(id, project_id, type, subfolder, initial_state, visibility, name, config_json, " " created_at, updated_at) " "VALUES (?, ?, ?, ?, 'super-draft', 'public', ?, ?, datetime('now'), datetime('now'))", (cid, project_id, ctype, cid, cid.capitalize(), cfg), ) def _entry(slug: str, collection_id: str) -> None: db.conn().execute( "INSERT OR REPLACE INTO cached_rfcs (slug, title, state, collection_id) " "VALUES (?, ?, 'active', ?)", (slug, slug.upper(), collection_id), ) def _three_providers(app) -> None: app.state.providers.clear() for k in ("claude", "gemini", "gpt"): app.state.providers[k] = FakeProvider("TITLE: A\nDESCRIPTION: B") def test_two_projects_multicollection_model_universe_isolated(app_with_fake_gitea): app, _ = app_with_fake_gitea with TestClient(app): _three_providers(app) # alpha: a document collection narrowed to claude. _project("alpha") _collection("alpha-docs", "alpha", ctype="document", enabled_models=["claude"]) _entry("alpha-intro", "alpha-docs") # beta: two collections — a spec narrowed to gemini, a bdd left open. _project("beta") _collection("beta-specs", "beta", ctype="specification", enabled_models=["gemini"]) _collection("beta-features", "beta", ctype="bdd") # no narrowing → all three _entry("beta-runtime", "beta-specs") _entry("beta-login", "beta-features") # Each entry resolves against ITS collection's universe — no bleed. assert models_resolver.resolve_models_for_rfc("alpha-intro", app.state.providers) == ["claude"] assert models_resolver.resolve_models_for_rfc("beta-runtime", app.state.providers) == ["gemini"] assert models_resolver.resolve_models_for_rfc("beta-login", app.state.providers) == [ "claude", "gemini", "gpt" ] def test_two_projects_multicollection_type_noun_isolated(app_with_fake_gitea): app, _ = app_with_fake_gitea with TestClient(app): _project("alpha") _collection("alpha-docs", "alpha", ctype="document") _project("beta") _collection("beta-specs", "beta", ctype="specification") _collection("beta-features", "beta", ctype="bdd") assert collections.get_collection("alpha-docs")["entry_noun"] == "RFC" assert collections.get_collection("beta-specs")["entry_noun"] == "Spec" assert collections.get_collection("beta-features")["entry_noun"] == "Feature"