fix(services): readers that one bad answer could break, and the tests that should have caught it - #27
Merged
Merged
Conversation
… that should have caught it Two commits described tests that were never committed (71eb398's grading invariant, 9ee7d72's mocked-timer lifecycle checks), so the code that turns every answer into a dot or a LIVE badge ran unguarded: mutants of the grading, the health merge, the connection state machine and the polling chain all passed the old 18 tests. They are written here first, against the existing behaviour, then each fix below comes with a test that fails without it. - A `null` element in city's aggregate threw out of the health sweep, and the schedule re-armed only after a job settled, so health polling stopped for the life of the tab. Entries are now validated at ingest, and both pollers re-arm whatever a cycle does. - `metadata: null` on one Algora row threw mid-ingest and dropped all three origins' signals for the cycle. Rows are converted one at a time. - An abort or dropped connection while reading a 5xx body was caught with the JSON parse and became "down". It now propagates, so it stays unmeasured. - The dedupe prune rebuilt its memory from the drained queue, forgetting ids still in the upstream windows and re-counting ~66 signals as new. It is now an LRU per origin that only forgets ids that stopped being served. - city was fetched twice per sweep (its statusUrl is the aggregate). Once now. - Health reads bypass the HTTP cache: a cached aggregate could answer offline. - Cross-service values are type-checked at ingest and escaped/coerced at the innerHTML sinks (defence in depth); HubMap's tooltip markup moves to a Phaser-free module so it can be tested. - AO's debate card rotated on `time.now % 5000 < 50`; AlgoraZone could latch botBusy before code that throws. Both fixed. - Dead code removed: the signal normalizers nothing read, and unused exports. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This was referenced Sep 21, 2026
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.
Stacked on #26 — merge that first. This PR's diff is only its own commit.
The service layer turns every upstream answer into a dot, a count or a LIVE badge, and most of it had no test. Two earlier commits describe tests that were never committed: 71eb398 ("adds the invariant as a test") touched only
src/, and 9ee7d72 was "verified with mocked fetch and timers" that are nowhere in the repo. Mutating the grading, the health merge, the connection state machine or the polling chain left the old 18 tests green.So the tests come first, written against the existing behaviour. Each fix below then comes with a test that fails without it.
New tests (18 → 91 across the stack; 59 in this PR)
ecosystem-feed-merge.test.tsstubsfetch, not the client module, so the real readers run. It uses an inline registry with one of each case: streamed, direct ok, direct 503degradedthat city calls ok, CORS wall with and without aggregate cover, off-contractrunning, city, an artifact city names, a link, and a hidden entry. It asserts each node's kind, instrumentation and status, that links and files are never fetched, that an empty sweep keeps the snapshot, and that a hung aggregate is cut off at 10 s without costing the first-hand readings.data-bridge.test.tsruns on fake timers. It coversconnectinguntil signals and stats settle, andlivewhile the bulk requests still hang. It checks reachability from stats alone, from signals alone, and from neither. Polls start at 0, 25 s and 50 s: chained, never overlapping, with the 10 s cap.destroy()mid-poll leaves no timers.Fixes
nullelement in city's aggregate threw out of the health sweep. The next sweep is armed only after a job settles, so health polling stopped for the life of the tab and the last verdicts stayed on screen.metadata: nullon one Algora row threw mid-ingest (JSON.parse(null)returns null) and dropped all three origins' signals for that cycle.down.statusUrlis the aggregate), doubling load on the most expensive health endpoint.cache: "no-store".innerHTMLunchecked, and HubMap's tooltipescthrew on non-strings.src/ui/hub-tooltip.tswith tests.time.now % 5000 < 50, which a 60 Hz frame hits about three times per window and a slow frame can miss.botBusybefore code that could throw, stopping the belt for good.finally.Also removed: the signal normalizers nothing read (they were wrong on live data, e.g.
moc_blockchainclassified asai),fetchAODebateDetail,isConnected, and a dead CSS rule.Visible differences: a sidebar figure a service sends as something other than a number now shows
—instead of0orundefined. An off-contract Algora severity draws asmedium.What is verified, and what is not
check-health-jsonmain, and the console is clean🤖 Generated with Claude Code