Skip to content

Tests in Vitest, one catalog of every SPARQL query, and docs generated from it - #61

Closed
prayaslashkari wants to merge 4 commits into
developmentfrom
test/vitest-suite
Closed

prayaslashkari wants to merge 4 commits into
developmentfrom
test/vitest-suite

Conversation

@prayaslashkari

Copy link
Copy Markdown
Collaborator

Why

Three recurring problems: fixing one question shape breaks another, there is no single reference for the SPARQL the app sends, and the checks that did exist were standalone scripts that read as "no tests". This PR replaces the scripts with a real test suite, records every query the app can send, and generates the query docs from the code so they cannot drift.

What changed

Tests, next to the code (Vitest)

  • npm test: 638 offline tests in 12 files, under a second. The seven scripts/check-*.mts files are ported with the same assertions, one test per question shape, so a failure names the shape.
  • New coverage: resultTransformer, sparqlErrors, scope, wire, queryBreadth, the dropdown row mappers, and executor (SPARQL client faked: step handoff, splitting on timeout, partial and fatal failures).
  • CI runs a single npm test, so new test files are picked up without touching the workflow.

One catalog of every query (src/engine/queries/catalog.ts)

  • 402 entries: 374 analysis shapes, the 8 dashboard questions, and the 10 dropdown, 2 popup and 6 probe queries that were never snapshotted before.
  • The snapshot test fails on any change and prints which entries moved, grouped by relationship and kind. CI writes the same list to the PR's run summary.
  • npm run sparql -- "<name>" prints the SPARQL for any entry.
  • Small refactor: LOOKUPS in src/engine/lookupQueries.ts pairs each dropdown and popup builder with its endpoint. The hooks and the catalog both use it, so they cannot disagree. No behaviour change, and the snapshot shows it.

Docs generated from the code

  • docs/QUERIES.md: every dropdown and popup query, a worked example per relationship, hydrate and probe queries.
  • docs/queries/: one generated page per dashboard question (8, was 5). The old pages described a six-step S2 pipeline the engine stopped running in March, so they were replaced outright rather than patched.
  • Both are snapshots, so CI fails when they drift. SPARQL quoted in docs/wiki is tested against the catalog in both directions.

Live tier

  • *.live.test.ts, skipped unless LIVE=1 (npm run test:live, about 50s, sequential).
  • New Live tests workflow: the dropdown queries on every PR. With the live-check label, it also runs scripts/sparql-live-diff.mts, which runs each query the PR changed in both versions (base and PR), warm, side by side, and fails if one that worked now fails.
  • scripts/flow-distance-check.mjs became src/engine/planner.live.test.ts.

Evidence the tests catch things

  • Snapshot body byte-identical after the move.
  • Reintroduced the 2026-09-16 reversed upstream trace: 72 shape tests fail. Reintroduced the 2026-09-20 filter-as-region ranking: 13 join-order tests fail.
  • Mutations in the transformer (MULTIPOLYGON guard, lat/lon order) and executor (slice dedupe, empty-target stop, known-too-large memory) each fail their test.
  • Changing the region fragment shared by the Substance and Material dropdowns reports exactly those 4 queries.
  • Live diff with the 2026-09-20 ranking bug reintroduced: the sample-filters shape goes from 1.8s to out-of-memory and the script exits 1.

Notes for review

  • CI moves from Node 20 to 22 (Vitest 5 needs it). Tests get their own tsconfig.test.json with Node types, so the app build still does not see Node APIs.
  • The live-check label needs creating in the repo.
  • On this PR only, development has no snapshot at the new path, so the blast-radius section says so and a live-check run would fail its comparison step. Both work from the next PR on.
  • The live workflow uses its own concurrency group: joining sawgraph-endpoint would let a queued PR run cancel a pending health check or cache warm.
  • The README has a table mapping the old script names to the new test files, for the changelogs and DEBUGGING.md entries that still mention them.

The seven scripts/check-*.mts files become co-located *.test.ts files with
the same assertions and messages, one test per question shape, so a failure
names the shape instead of exiting a script. CI runs a single npm test, which
picks up any new test file without a workflow change.

Parity: the SPARQL snapshot body is byte-identical after the move. The
2026-09-16 reversed upstream trace and the 2026-09-20 filter-as-region
ranking were both reintroduced and fail the new tests, as they failed the
scripts.

CI moves from Node 20 to 22, which Vitest 5 requires.
The snapshot covered the analysis-question pipeline only. The 10 dropdown
queries, the sample popup and the six scope probes were sent by the app and
recorded nowhere, so a change to a fragment they share moved them unseen.
They are now catalog entries, built through LOOKUPS, a table that pairs
each builder with its endpoint and that the hooks now call too, so the
catalog cannot disagree with the app.

docs/QUERIES.md is generated from the catalog and checked as a snapshot,
so it fails CI when it stops matching the code. npm run sparql prints any
entry. On pull requests, CI writes which entries moved to the run summary.

Demonstrated: dropping the + from administrativePartOf in the shared
sample-point region pattern now reports exactly the four region-aware
Substance and Material dropdown queries, and fails the doc.
Unit tests for the modules nothing tested: resultTransformer (WKT parsing,
deduplication, popup grouping), sparqlErrors, scope slicing, the cache wire
format, and the breadth warning. The dropdown hooks' row-to-option code
moves into exported functions, unchanged, so it is tested without React.

executor.test.ts fakes the SPARQL client and covers what passes between
steps, splitting on timeout and merging slices, partial and fatal failures.
Verified by mutation: removing slice deduplication, the empty-target stop,
or the known-too-large memory each fails exactly one test.
Live: *.live.test.ts run against the real endpoints with LIVE=1. Every
dropdown query must answer with the columns its hook reads, on every pull
request. The flow-distance check moves in from scripts/ and now runs through
the real executor.

scripts/sparql-live-diff.mts runs only the queries a change moved, base
version against new, warm, one at a time. Demonstrated by reintroducing the
2026-09-20 filter-as-region ranking: the sample-filters shape goes from
1.8s to out-of-memory and the script exits 1. The Live tests workflow runs
it on pull requests labelled live-check.

docs/queries described a six-step S2 pipeline the engine stopped running in
March. Each dashboard question is now a catalog entry with a generated page,
eight pages where there were five. Wiki SPARQL quotes are tested against the
catalog, and fail if either side changes.
@railway-app

railway-app Bot commented Oct 9, 2026 •

Copy link
Copy Markdown

🚅 Deployed to the explorer-app-pr-61 environment in sawgraph-explorer

Service Status Web Updated
sawgraph-api ❌ Build Failed (View Logs) Web Oct 9, 2026 at 4:29 am UTC
sawgraph-web ✅ Success (View Logs) Web Oct 9, 2026 at 4:24 am UTC
1 service not affected by this PR
  • sawgraph-db-dev

@railway-app
railway-app Bot temporarily deployed to sawgraph-explorer / explorer-app-pr-61 October 9, 2026 04:24 Destroyed
@prayaslashkari

Copy link
Copy Markdown
Collaborator Author

Closing in favour of tests/queryGeneration.test.ts on development.

@prayaslashkari
prayaslashkari deleted the test/vitest-suite branch October 13, 2026 18:48

This branch was successfully deployed

1 inactive deployment
sawgraph-explorer / explorer-app-pr-61 — 4f17d9bb Deployed Oct 9, 2026 by railway-app[bot]
sawgraph-explorer / development — 4f17d9bb Deployed Oct 9, 2026 by railway-app[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant