"""§22.4a SLICE-3 integration — faceted list endpoint (PUC-3, §6.4). Through the real API: schema-declared facets, filter params (OR within / AND across), drill-down counts, malformed toggle, unknown-field 400, and meta_json persistence. Reuses the fake-Gitea harness from test_metadata_cache. """ from __future__ import annotations import asyncio import json import yaml from fastapi.testclient import TestClient from app import cache, db, gitea as gitea_mod from app.config import load_config from test_propose_vertical import ( # noqa: F401 (fixtures) app_with_fake_gitea, tmp_env, ) # The fake-Gitea harness seeds a single project whose id is the literal # 'default' (see test_s1_collection_grain_vertical), served at # /api/projects/default/rfcs. PID = "default" def _refresh(): cfg = load_config() asyncio.run(cache.refresh_meta_repo(cfg, gitea_mod.Gitea(cfg))) def _set_default_fields_schema(schema): db.conn().execute( "UPDATE collections SET config_json = ? WHERE id = 'default'", (json.dumps({"fields": schema}),)) def _seed(fake, slug, *, state="active", **front): fm = {"slug": slug, "title": slug.title(), "state": state, **front} body = yaml.safe_dump(fm, sort_keys=False).strip() fake.files[("wiggleverse", "meta", "main", f"rfcs/{slug}.md")] = { "content": f"---\n{body}\n---\n\nBody.\n", "sha": slug} def test_facets_and_counts_returned(app_with_fake_gitea): app, fake = app_with_fake_gitea with TestClient(app) as client: _set_default_fields_schema({ "priority": {"type": "enum", "values": ["P0", "P1", "P2"]}, "tags": {"type": "tags"}, }) _seed(fake, "a", priority="P0", tags=["checkout"]) _seed(fake, "b", priority="P0", tags=["cart"]) _seed(fake, "c", priority="P1", tags=["checkout", "cart"]) _refresh() res = client.get(f"/api/projects/{PID}/rfcs") assert res.status_code == 200 body = res.json() assert {i["slug"] for i in body["items"]} == {"a", "b", "c"} assert body["facets"]["priority"] == {"P0": 2, "P1": 1} assert body["facets"]["tags"] == {"checkout": 2, "cart": 2} assert body["facets"]["state"] == {"active": 3} def test_filter_params_compose(app_with_fake_gitea): app, fake = app_with_fake_gitea with TestClient(app) as client: _set_default_fields_schema({ "priority": {"type": "enum", "values": ["P0", "P1"]}, "tags": {"type": "tags"}, }) _seed(fake, "a", priority="P0", tags=["checkout"]) _seed(fake, "b", priority="P0", tags=["cart"]) _seed(fake, "c", priority="P1", tags=["checkout"]) _refresh() res = client.get( f"/api/projects/{PID}/rfcs", params={"priority": "P0", "tags": "checkout"}) assert {i["slug"] for i in res.json()["items"]} == {"a"} def test_unknown_filter_field_400(app_with_fake_gitea): app, fake = app_with_fake_gitea with TestClient(app) as client: _set_default_fields_schema( {"priority": {"type": "enum", "values": ["P0"]}}) _seed(fake, "a", priority="P0") _refresh() res = client.get(f"/api/projects/{PID}/rfcs", params={"nonsense": "x"}) assert res.status_code == 400 def test_malformed_toggle(app_with_fake_gitea): app, fake = app_with_fake_gitea with TestClient(app) as client: _set_default_fields_schema( {"priority": {"type": "enum", "values": ["P0", "P1"]}}) _seed(fake, "good", priority="P0") _seed(fake, "bad", priority="P9") # not in values → malformed (INV-3) _refresh() res = client.get(f"/api/projects/{PID}/rfcs", params={"malformed": "true"}) slugs = {i["slug"] for i in res.json()["items"]} assert slugs == {"bad"} def test_no_schema_no_facets(app_with_fake_gitea): # INV-5: the default document collection (no fields) returns empty facets. app, fake = app_with_fake_gitea with TestClient(app) as client: _seed(fake, "plain", tags=["whatever"]) _refresh() body = client.get(f"/api/projects/{PID}/rfcs").json() assert body["facets"] == {} def test_meta_json_persisted_at_ingest(app_with_fake_gitea): app, fake = app_with_fake_gitea with TestClient(app): _seed(fake, "withmeta", priority="P0", tags=["x"]) _refresh() row = db.conn().execute( "SELECT meta_json FROM cached_rfcs WHERE slug = 'withmeta'" ).fetchone() meta = json.loads(row["meta_json"]) assert meta["priority"] == "P0" assert meta["tags"] == ["x"]