test: versioned wire-contract fixtures + drift guard (#351) - #382
Merged
Conversation
…351) Nothing in this repo asserted the two wire shapes pithead's fakes consume — drift was only ever caught by a manual live run (the confirmed case: #320's noop/throttled statuses landed here with pithead's poller still unaware of them). Add tests/contract/v1/{feed,control-status}.json, versioned plain-JSON fixtures a sibling repo's CI can fetch and byte-compare, plus a README stating what they cover and the change rule (touch the fixture in the same PR that changes the shape). Wire a guard into tests/run.sh that regenerates both shapes through the real code (api_refresh, _control_status — not hand-written JSON), normalizes the few fields that legitimately vary release-to-release or run-to-run (install provenance, a wall-clock timestamp; see the fixture README), and byte-diffs against the committed file. The status vocabulary is additionally cross- checked against every literal status word rigforge.sh hands to _control_status, straight off the call sites, so a new status can't land silently. No rigforge.sh lines changed, so no coverage impact.
…st rigforge.sh (#351) Three gaps in the #351 guard, all confirmed: - It regenerated control-status.json's terminal record shapes by calling rigforge.sh's _control_status() and reading the file it wrote, never touching util/control-server.py — so the receiver's own 503 no-history body, the `pending` status stage_pending() writes at /apply accept time, and the age_seconds _with_age() injects into every served body were all invisible to it (and could drift unnoticed). The status-vocabulary check was similarly one-sided: it only grepped rigforge.sh's call sites, never control-server.py's own "status" literals. Fixed by adding a server-driven leg (same spin-up/curl/kill harness as the "#236 black-box: the control server" section above it) that exercises the real util/control-server.py over HTTP for exactly the three things only it can produce, and by unioning the vocabulary grep across both files. - The fixture undersold the contract it was supposed to pin: no `pending` in statuses, no pending example, no age_seconds anywhere, and the 503 body undocumented. control-status.json and its README now cover all of it, including which keys are normalized (and why — serve-time-derived age_seconds, server-random change_id on the pending example) — still v1, purely additive. - The FEEDFX block deliberately kept the generic systemctl stub off PATH so API_CMD stayed the only faked external command, but missed that _health_json calls systemctl directly for service_active — on any rig genuinely running the xmrig unit that call returns true and byte-diffs the fixture. SERVICE_NAME is now pinned to a unit name no rig will ever have, so service_active/clock_pct_of_boost/throttling read false/null identically on a dev Mac, CI, and a mining rig. Verified: a corrupted no_history body in the fixture makes the new guard leg fail; a systemctl stub simulating a genuinely active xmrig unit flips service_active true without the SERVICE_NAME override and false with it. shellcheck 0.11.0 + shfmt -i 4 clean, markdownlint-cli2 0.22.1 clean, full tests/run.sh: 1728 passed, 0 failed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #351.
Pins the two wire shapes pithead's tier-2 fixtures depend on inside THIS repo, so drift trips CI here instead of a manual live run there (the v1.16 fixture-drift failure mode; #320's noop/throttled statuses were the confirmed example).
tests/contract/v1/feed.json— the canonical/2/summarybody (XMRig passthrough envelope + the fullrigforgeblock), produced through the realapi_refreshcode path.tests/contract/v1/control-status.json— the exhaustive/statusvocabulary:applied,failed,noop,rejected,rolled_back,started,throttled(the control-upgrade status contract: no in-progress state, no version echo, refusals collapse into 'failed' #320 additions included), plus real example bodies.tests/contract/v1/README.md— what the contract covers, its version, and the change rule: a wire-shape change touches the fixture in the same PR, paired with a pithead-side update.jq -Snormalization; every normalized key is documented in the README.Ordering: land this BEFORE the #344 branch — that PR adds
pending/age_secondsto/statusand should rebase onto this guard and update the fixture as its first customer.What was run:
shellcheck --severity=warning+shfmt -i 4 -don tests/run.sh and rigforge.sh (clean); markdownlint-cli2 0.22.1 on the new READMEs (0 errors); fullbash tests/run.shforeground twice, before and after wiring the guard: pass. Not run: the Linux/Docker-only suites (e2e, kcov) — zero rigforge.sh lines changed.🤖 Generated with Claude Code