b392fa923c
The release wrap for the S6 scope shipped this slice (SPEC merge, per-collection enabled_models, type-driven entry noun). - test_s6_two_project_multicollection.py: an integrative pass proving the new per-collection knobs (§22.12 enabled_models, §22.4a noun) resolve independently per collection across two projects with no cross-project/-collection bleed. - CHANGELOG.md 0.45.0: the §20.4 entry + upgrade-steps (migration 031 is additive + automatic; per-collection enabled_models and typed collections are optional). Notes the two OPEN S6 items carried to a follow-up slice (per-type surfaces; request-to-join + cross-collection inbox). - VERSION + frontend/package.json → 0.45.0. Gate: backend 530 passed, frontend 30 passed, build green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
91 lines
3.9 KiB
Python
91 lines
3.9 KiB
Python
"""§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"
|