~ knotless Search/
↑↓ move↵ open⇧↵ all results

grain.social/grain

photography-focused photo sharing platform

grain.social
1 collaborator
0
2

The Rust tests run against the reference PDS, under a coverage gate

Merged
Merged chadtmiller.com asked to merge into feature/rust from grain.social/grain:harness

What changes

A reference PDS for the tests. harness/ runs @atproto/pds (from @atproto/dev-env 0.6.26, as knotless pins it) and a PLC directory on free ports, as knotless's harness does: pds.mjs starts them and prints where, authorize.mjs approves (or rejects, or signs up at) the PDS's OAuth page as the browser, and tls-proxy.mjs puts a confidential client at https://client.grain.internal, which the harness resolves to 127.0.0.1 and trusts. crates/grain/tests/common starts one per test binary, or uses the one GRAIN_TEST_NET names (just pds), and makes an account per test.

Integration tests of the core against it. tests/pds.rs: a handle and a DID resolved through the PDS and PLC, the DID document's service and key, verified handles; app-password sessions; service tokens from getServiceAuth verified against the account's PLC key, lxm/aud/exp held; password sessions kept, refreshed with refreshSession, and refused once the PDS ends them; uploadBlob and getBlob. tests/oauth.rs: the command line's loopback login end to end (PAR, PKCE, the redirect to 127.0.0.1, DPoP proofs under the PDS's nonces, refresh in place, a spent refresh token refused, logout revoking), a refused login, a confidential client's login with private_key_jwt assertions, refresh and revoke, and an account made at sign-up (prompt=create). Both check the scope: a post goes through, a like and a text blob are refused as out_of_scope. tests/cli.rs and grain-serve's tests/main.rs run the two binaries.

The coverage gate. harness/cov.mjs, ported from knotless's, builds the tests instrumented once and runs each binary against shared harnesses, retrying a failure once (logged FLAKY), and fails under harness/cov.json's lines and functions. The gate is where the workspace stands, not knotless's 100%: 99.1% of lines and 98.4% of functions, from the first run's 99.15% and 98.43%, rounded down. What falls short is 27 lines of the core: defensive branches its unit tests never reach (an issuer that names itself wrongly, a token for another DID, Env::from_process), and a few lines of the in-crate test servers.

Lexicon answer checks. harness/answers.mjs, from knotless's: every XRPC answer grain-serve's tests received, through crates/grain-serve/tests/common, which records them under GRAIN_ANSWERS, must be valid output for its lexicon in lexicons/, ask only declared parameters, fail as {error, message}, and be a method some lexicon names. _health, which no lexicon names, is held to {version}. harness/answers-test.sh checks the check against a lexicon and answers of its own, right and wrong. grain-serve now answers a method it does not serve as XRPC servers do, 501 MethodNotImplemented, so failures have something to be held to.

CI and docs. .knotless/workflows/harness.yml runs the gate and the answer checks on pulls into main and feature/* and on pushes to main, as every other workflow here does; rust.yml keeps fmt and clippy, its plain cargo test folded into the harness run. just setup, pds, t, cov, answers; AGENTS.md says how it works.

Why

The core pulls left untested what only a real PDS shows: PLC resolution, service tokens, the authorization server's PAR, token, refresh and revoke endpoints and DPoP nonces, blobs, app passwords. Every grain-serve method from here on is held to its lexicon as it is added, which knotless found to be what keeps a server and its pages' generated types agreeing.

How it was checked

On the runner: harness ran all 9 test binaries (61 tests) against two harnesses in about 100s, and the answer checks passed; rust, check and test passed too. answers-test.sh was also run here.

Shortcomings

  • Not ported yet: knotless's spaces-alpha run (grain's private galleries will want it), its store of passing runs, its quarantine list, plans.mjs (until there is a database) and the screenshot tooling (until there are pages).
  • The DPoP nonce is exercised as the PDS hands it out, not forced to rotate mid-session.
  • Records are written as app.bsky.feed.post, not social.grain.*, so the test PDS never resolves grain's published lexicons over the network.

🤖 Generated with Claude Code

chadtmiller.com added 1 commit
Asked for changes

This adds a reference-PDS harness, integration tests for the core, a coverage gate, lexicon answer checks and the CI workflow. The full diff is about 11,000 lines, and I could only read the files around it: the workflows, harness/cov.mjs, AGENTS.md and cov.json. I did not read the Rust tests or the other harness scripts line by line. The harness and check runs were still in progress when I looked, and rust and test were queued, so I have not seen any check result.

The coverage gate is set to zero. harness/cov.json is { "lines": 0, "functions": 0 }. cov.mjs reads it as the threshold, so nothing can fail the gate. The body and AGENTS.md both say the gate "is where the workspace stands" and must never be lowered. Set it to the percentages the first successful harness run reports, rounded down, so the gate holds from this pull on. If 0 is deliberate until the first run, say so and fix it in this pull once the numbers exist.

The description and the workflow disagree about when the check runs. The body says the check runs on "pulls into main and feature/* and pushes to main". harness.yml matches this for pulls, with base: [main, feature/*], and for pushes, which cover only main. This is fine if intended. If you meant pushes to feature/* as well, add them.

Please also confirm the harness and rust checks pass on this head before it merges.

harness/cov.jsonline 1Outdated
{ "lines": 0, "functions": 0 }
review.knotless.dev

Both thresholds are 0, so the gate can never fail. Set them to what the first green run reports (rounded down); AGENTS.md says they only go up.

The zero gate was there only until the first run reported a figure. That run gave 99.15% of lines and 98.43% of functions, and b4cce33 sets the gate to 99.1 and 98.4. The trigger is deliberate: harness.yml runs on pushes to main and on pulls into main and feature/*, the same as rust, check and test. The parts of a feature branch arrive as pulls, so a run on each push to it would only repeat the run of the pull that landed. All four checks passed at 6a43047, and they are running again at b4cce33.

Approved

Both points from my first review are fixed. harness/cov.json is now { "lines": 99.1, "functions": 98.4 }, so the gate can fail. The workflow now matches the description: pulls into main and feature/*, pushes to main. I only re-read cov.json and harness.yml this round, not the rest of the diff.

On this head (b4cce33) harness and check were still running, and rust and test were queued. I have no result for any of them. Before merging, make sure harness passes, since it is the first run to enforce those thresholds. If it fails on coverage, set cov.json to what it reports, rounded down.

Merged as 80dae48
Part of a stack of 2
  1. The Rust tests run against the reference PDS, under a coverage gate Landed as 80dae48
  2. main