"""G-15 — the §22 three-tier write-path resolver. `projects.content_repo_for_collection` and `projects.entry_location` resolve an entry's git location (org, content_repo, md_path) from its *collection* (collection → project → content_repo, plus the collection subfolder) instead of the deployment default. This is the keystone the branch/edit/body/graduation write paths share so an entry outside the default project's default collection reads/writes the correct file. """ from __future__ import annotations import tempfile from pathlib import Path from app import collections as collections_mod, db, projects as projects_mod from app.config import Config def _db() -> Config: cfg = Config( gitea_url="x", gitea_bot_user="x", gitea_bot_token="x", gitea_org="wiggleverse", registry_repo="registry", oauth_client_id="x", oauth_client_secret="x", app_url="x", secret_key="x", database_path=Path(tempfile.mkdtemp(prefix="g15loc-")) / "t.db", owner_gitea_login="x", webhook_secret="x", ) db.run_migrations(cfg) if db._CONN is not None: db._CONN.close() db._CONN = None db.init(cfg) return cfg def _seed(): # Default project (its content_repo is the deployment default) + a second # project with a DISTINCT content_repo, each with a default + a named # (subfolder) collection. db.conn().execute( "INSERT OR REPLACE INTO projects (id, name, content_repo, visibility, updated_at) " "VALUES ('default','Default','meta','public', datetime('now'))") db.conn().execute( "INSERT OR REPLACE INTO projects (id, name, content_repo, visibility, updated_at) " "VALUES ('rfc-app','RFC App','rfc-app-content','public', datetime('now'))") rows = [ ("default", "default", ""), ("features", "default", "features"), ("rfc-app", "rfc-app", ""), ("specs", "rfc-app", "specs"), ] for cid, pid, sub in rows: db.conn().execute( "INSERT OR REPLACE INTO collections (id, project_id, type, subfolder, " "initial_state, visibility, created_at, updated_at) VALUES " "(?,?, 'document', ?, 'super-draft','public', datetime('now'), datetime('now'))", (cid, pid, sub)) def test_content_repo_for_collection_resolves_per_project(): _db() _seed() # Default project's collections → the default content repo. assert projects_mod.content_repo_for_collection("default") == "meta" assert projects_mod.content_repo_for_collection("features") == "meta" # The second project's collections → its own content repo. assert projects_mod.content_repo_for_collection("rfc-app") == "rfc-app-content" assert projects_mod.content_repo_for_collection("specs") == "rfc-app-content" def test_content_repo_for_collection_unknown_is_none(): _db() _seed() assert projects_mod.content_repo_for_collection("nope") is None def test_entry_location_default_collection_repo_root(): cfg = _db() _seed() org, repo, path = projects_mod.entry_location(cfg, "default", "alpha") assert (org, repo, path) == ("wiggleverse", "meta", "rfcs/alpha.md") def test_entry_location_named_collection_uses_subfolder(): cfg = _db() _seed() org, repo, path = projects_mod.entry_location(cfg, "features", "beta") assert (org, repo, path) == ("wiggleverse", "meta", "features/rfcs/beta.md") def test_entry_location_other_project_distinct_repo(): cfg = _db() _seed() # Named collection in a non-default project: distinct repo AND subfolder. org, repo, path = projects_mod.entry_location(cfg, "specs", "gamma") assert (org, repo, path) == ("wiggleverse", "rfc-app-content", "specs/rfcs/gamma.md") # Default collection of the non-default project: distinct repo, repo root. org, repo, path = projects_mod.entry_location(cfg, "rfc-app", "delta") assert (org, repo, path) == ("wiggleverse", "rfc-app-content", "rfcs/delta.md") def test_entry_location_unknown_collection_falls_back_to_default_repo(): cfg = _db() _seed() # An entry whose collection_id is missing/unknown must still resolve to a # usable location (the deployment default repo, repo root) rather than an # empty repo — the legacy single-corpus behaviour. org, repo, path = projects_mod.entry_location(cfg, "nope", "epsilon") assert (org, repo, path) == ("wiggleverse", "meta", "rfcs/epsilon.md")