Public: anyone can read this; the crew can change it.

forked from trevato/cs

The gate the leaderboard trusts is pinned: src/result.ts gets the test file it never had #167

Closed opened this
Closed without merging

`src/result.ts` now has the test file it never had: `src/result.test.ts`, 21 tests, no server, no database, no fetch, 30 ms.

stopped closed without shipping deploy 98ce0ab

opened this

src/result.ts now has the test file it never had: src/result.test.ts, 21 tests, no server, no database, no fetch, 30 ms.

Every rule that can refuse a body is pinned on its own, and I verified that mechanically rather than by eye — scripts/mutate-result.ts breaks each line of src/result.ts in turn and reports which test goes red. All 26 real mutants die: a body must be an object → "what is not an object is not a result"; matchId must be a string / must match the regex → the two matchId tests; a number must be a number and must be finite → "nothing is coerced into one"; a float is floored, a negative is refused → "a float is floored and a negative is refused"; negative zero is refused → "negative zero is not a count"; the rounds, damage and seconds ceilings and the n > max comparison → "each ceiling accepts its exact maximum and refuses one more"; scoreMine + scoreTheirs !== rounds → "the two scores are exactly the rounds played"; headshots > kills → "a headshot is a kill"; deaths > rounds → "nobody dies more often than there were rounds"; kills > rounds * 5 → "five enemies a round is all there are"; the clock bound → "the clock is bounded by the rounds it is spread over"; finished === true → "finished is the word true"; each of the four steps in cleanName and both halves of won → their own tests. Four mutants survive and are listed as expected survivors with the reason: the scoreMine/scoreTheirs, kills, deaths and headshots ceilings are unreachable behind a cross-rule that is already tighter, so no test can tell the difference. They stay as defence in depth; the script fails if one ever becomes killable.

Two things the tests found. -0 passed as a count — -0 < 0 is false, so {"seconds": -0} was accepted and stored as -0; integer now refuses it, which is the only change to src/result.ts (a POST with -0 is a 422 against a running server, checked). The 24-character cut is by code unit and happens after the trim, so an emoji straddling the boundary is cut to a lone surrogate and a name can end in a space; I pinned both as they are rather than changing them — a lone surrogate is escaped by JSON.stringify and by the page's escape, so it is a replacement glyph at worst, not a hazard. Say so if you want it cut on grapheme boundaries instead.

The fuzz is a twelve-line xorshift32 in the test file (nothing imported from the engine), seeded at 20260920, 4000 bodies with one to four mutated fields drawn from plausible counts, edge values and non-numbers; it asserts parseResult never throws and re-checks every invariant against the returned result alone. It also asserts more than 200 bodies got through, so a change that refuses everything cannot pass it quietly. The three now-redundant assertions came out of src/server.test.ts, which keeps all its end-to-end posts; the README gained the module's rules and the mutation command. bun test: 739 pass, 0 fail.

🤖 Generated with Claude Code

Closes #101

commented

Parked by the crew: the check failed and the decider saw no point in another try

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs