Public: anyone can read this; the crew can change it.
forked from trevato/cs
The doors the board is written through get their first test files: src/gate.ts and src/run.ts #363
Check · Review · Land · Live done — Landed and live in production.
`src/gate.ts` and `src/run.ts` now have their own test files, `src/gate.test.ts` and `src/run.test.ts`. Both test plain functions, with no server. `bun run test` (typecheck, then `bun test`) passes at 1470 tests.
shipped production is serving it deploy ae07c2d
Sign in to join this conversation.
new above
offline
src/gate.tsandsrc/run.tsnow have their own test files,src/gate.test.tsandsrc/run.test.ts. Both test plain functions, with no server.bun run test(typecheck, thenbun test) passes at 1470 tests.A refactor with no behaviour change. The door's steps were written inline in
routeinsrc/server.ts, so they could only be reached over HTTP. I moved three of them intosrc/gate.tswithout changing them:readBody, which reads the body under its cap, andBODY_MAX.admit(gate, request), which reads, parses the JSON and checks the gate's shape. It returns what came through, or a refusal with its status and words:413 too large,400 bad json, or422 <the gate's refusal>.playerOf, which records a verified identity under its own name and a guest under the name they claimed.routenow callsadmitand still answers with the same statuses and the same order: same-origin check, rate limit, body, write, board. A local boot answered/with 200, a POST ofnopeto/api/runwith400 bad json, and{}with422 bad run. AGENTS.md now points toreadBodyinsrc/gate.ts.src/gate.test.tsbuilds its ownRequests and a test-onlyGate, and checks what a caller sees:413. A chunked body with no Content-Length gets the same answer, and a body that never ends is cut off rather than read forever.400, and JSON of the wrong shape gets the gate's own422.-0, NaN, strings, bigints and values over the ceiling, are refused. Names are cleaned, trimmed and cut at 24.src/run.test.tsreads every limit fromDRILLSthroughmostPlates,mostShots,mostScoreandmostPerPlate, with no literals, and loops over every drill:start,stepandshoot, and the run it produces is accepted.__proto__andtoString, along with bad run ids. A body's invented fields are dropped.I removed each ceiling in
run.tsone at a time and each turned a test red, with one exception: the overallmostScorecap. Hits are capped atmostPlatesand score athits × mostPerPlate, so those two already imply it and no request can reach it on its own. I left it in because this pull changes no behaviour.A hole, fixed here.
cleanNameatsrc/gate.ts:59only stripped C0 controls and DEL. A post like{"name":"a\u009b2J…"}stored U+009B, which some terminals treat as a control sequence (CSI), in the name. U+2028 and U+2029, which act as line breaks, also got through even though the rule says a name is one line. The class now also covers U+0080–U+009F and U+2028–U+2029, matching the terminal's ownprintablefilter. Only names containing those characters change.src/independence.test.tsnow listsgate.ts,run.tsandresult.tsas the app's files, not a game's, and allowsrun.tsand its test to readrange/targets.ts.I did not add HTTP tests for these doors; #335 and the posting-doors issue cover that end.
To try it:
bun test src/gate.test.ts src/run.test.ts. On the preview, a name containing U+009B posted to/api/runnow shows a space in its place on the board.🤖 Generated with Claude Code
Closes #347