From 7b269e11c42dac39a37cf8257724790857578657 Mon Sep 17 00:00:00 2001 From: Ben Stull Date: Sun, 7 Jun 2026 17:37:46 -0700 Subject: [PATCH] =?UTF-8?q?test(slice3):=20integration=20tests=20for=20fac?= =?UTF-8?q?eted=20list=20endpoint=20(=C2=A722.4a=20PUC-3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/tests/test_facets_api.py | 134 +++++++++++++++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100644 backend/tests/test_facets_api.py diff --git a/backend/tests/test_facets_api.py b/backend/tests/test_facets_api.py new file mode 100644 index 0000000..42cde65 --- /dev/null +++ b/backend/tests/test_facets_api.py @@ -0,0 +1,134 @@ +"""§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"]