postjevsql.git / tests / README.md
1# Chapter 13: tests, a real Postgres and a fake Jev
2
3Every test in this folder starts a real Postgres of its own, a throwaway one
4in a temporary directory, with the freshly built extension served in place.
5And every test points that Postgres at a fake Jev: `jev-mock` (from
6jevcrates), a real HTTP/2 server over real TLS on loopback, which answers
7however the test tells it to and remembers every request it was sent.
8
9```mermaid
10flowchart LR
11  T["a test (tokio-postgres)"] -->|"SQL over a Unix socket"| P[("a throwaway Postgres<br/>with the built extension")]
12  P -->|"HTTP/2 + TLS, trusted through jev.ca_file"| M["jev-mock on loopback"]
13  T -.->|"what arrived, byte for byte"| M
14```
15
16That last arrow is the point. A test does not only check that the query
17returned the right number. It goes to the mock and checks what was actually
18sent: how many requests, on how many connections, with which headers, how
19many at once, which streams were reset. "The query came back" can hide a
20dozen sins; the wire cannot.
21
22> **Aside.** Why not wall-clock time? Because the machine that runs the
23> suite is shared with other builds, and "this took under two seconds"
24> failed there while passing alone. So timing is measured where it means
25> something: the mock counts the peak number of requests in flight for
26> concurrency, the connections it accepted for redials, the retry header for
27> retries. A test may say a backoff waited *at least* its delay; it never
28> says *at most*.
29
30> **Try it.** `nix develop -c buck2 test //tests:happy_path` runs the
31> smallest one: one row, one Noul, and an assertion on every byte of the one
32> request it sends. Every test also has a twin for Postgres 17,
33> `//tests:happy_path-pg17`, and `nix develop -c buck2 test //...` runs the
34> whole suite on both majors.
35
36## The tests
37
38| File | What it holds the extension to |
39| --- | --- |
40| [happy_path.rs](happy_path.rs) | `jev_prob` end to end, asserting the exact request. |
41| [batching.rs](batching.rs) | The batch scan: concurrency on one connection, filtered rows never sent, LIMIT, shared requests, rescans, aggregates, windows, CTEs, subqueries, partitions, reach, refusal before any request, EXPLAIN. |
42| [dml.rs](dml.rs) | UPDATE, DELETE, INSERT … SELECT and SELECT … FOR UPDATE batched, and an EvalPlanQual recheck that never sends. |
43| [threshold.rs](threshold.rs) | `jev(row, q [, threshold])`: argument, then `jev.threshold`, then 0.5, sharing judgments with `jev_prob`. |
44| [score.rs](score.rs) | `jev_score` and `jev_score_norm`: 2–10 levels in the caller's order, one judgment between them. |
45| [choice.rs](choice.rs) | `jev_choice`: options in the caller's order, 2–255 sent, one answered without asking. |
46| [confidence.rs](confidence.rs) | `jev_confidence`: the vendor's figure, sharing the Choice's or Score's judgment; `'noul'` refused before sending. |
47| [eval.rs](eval.rs) | `jev_eval` and the `*_full` functions: typed composites from the same judgment and cache row. |
48| [label_tree.rs](label_tree.rs) | `jev_choice_tree`: one Choice per node, a round per request, each node cached. |
49| [shortlist.rs](shortlist.rs) | `jev_choice_shortlist`: chunks in one request, finalists in a second, each cached. |
50| [cache.rs](cache.rs) | The durable cache, and equal judgments sharing one request in a statement, including a call that asks again when a rescan drops the one it waited on. |
51| [canonical.rs](canonical.rs) | The row sent in a form that does not depend on the session and loses no digit. |
52| [budget.rs](budget.rs) | `jev.max_rows` and `jev.max_cost`: refused before the first request, and stopped mid-statement. |
53| [ratelimit.rs](ratelimit.rs) | The cluster-wide rate limit, timed at the mock: two backends sharing one ceiling, a 429 halving the rate, a cancel ending the wait. |
54| [retries.rs](retries.rs) | 408, 429 and 5xx retried at most twice, a server delay honoured, the retry count sent; any other status fails at once. |
55| [on_error.rs](on_error.rs) | `jev.on_error`: a 500 skips its row under `unsure` and fails the statement under `error`; a refused key raises under both. |
56| [cancel.rs](cancel.rs) | A timed-out or cancelled request resets its HTTP/2 stream, proven at the server. |
57| [keepalive.rs](keepalive.rs) | Pings while a scan runs and only then, and an unanswered one replacing the connection. |
58| [dns.rs](dns.rs) | Host names resolved through hickory on the executor, against `jev.dns_servers`. |
59| [latency.rs](latency.rs) | The extension's own overhead per call, against a mock that answers instantly. |
60| [stats.rs](stats.rs) | `jev_stats()` counts this session's work, and every retry, redial and answer is logged at DEBUG1 with its request id. |
61| [api_key.rs](api_key.rs) | The key from exactly one place: `TYPESAFE_API_KEY` or `jev.api_key_file`, never both. |
62| [privileges.rs](privileges.rs) | Spending takes an explicit GRANT, and no function is executable by PUBLIC. |
63| [settings.rs](settings.rs) | Settings checked when they are set, not only when used. |
64| [replay.rs](replay.rs) | Fixture replay by exact request bytes, and a request the fixture lacks failing with the request named. |
65| [gate_layout_parity.rs](gate_layout_parity.rs), [gate_unsure_band.rs](gate_unsure_band.rs), [gate_label_tree.rs](gate_label_tree.rs), [gate_ranking.rs](gate_ranking.rs) | Each release gate's recorded live answers replayed through the extension, so a change to what it sends fails naming the request (chapter 15). |
66| [sidecar.rs](sidecar.rs) | The scan over a postgres_fdw loopback foreign table, and a server listing this extension in `extensions` refused at plan time. |
67| [sidecar_cli.rs](sidecar_cli.rs) | The sidecar CLI against a throwaway target and sidecar (chapter 12). |
68
69## For the people who maintain it
70
71| Path | What |
72| --- | --- |
73| [support/](support/) | The library every test shares: the throwaway Postgres, the instance wired to the mock, the gates' rows and SQL. Chapter 14. |
74| [fixtures/](fixtures/) | The release gates' recorded live answers. Chapter 15. |
75| [BUCK](BUCK) | One `first_party_test` per file, each given the built extension, the server of its major and the fixtures through the environment. |
76| [run-check.sh](run-check.sh) | The repo's whole check, `nix develop -c buck2 test //...`, queued on a lock so two checks in one checkout do not race the same buck daemon. Mefi Studio runs it to verify finished agent work. |
77
78← Previous: [Chapter 12, cli/](../crates/postjevsql-sidecar/cli/) · Up: [postjevsql](../) · Next: [Chapter 14, support/](support/) →