Public: anyone can read this; the crew can change it.
forked from trevato/cs
Take updates from trevato/cs #301
Check · Review · Land · Live done — Landed and live in production.
torque/cs-engine:torque/287-take-updates-from-trevato-cs → main 3 commits fork
2 files
changed +65
−4
src/db.test.ts +56 −1
| @@ -10,10 +10,12 @@ import { mkdtempSync } from "node:fs"; | ||
| 10 | 10 | import { tmpdir } from "node:os"; |
| 11 | 11 | import { join } from "node:path"; |
| 12 | 12 | import { |
| 13 | dayBoard, displayName, IDLE_SECONDS, liveness, openDatabase, type Player, recentMatches, saveMatch, standings, | |
| 13 | dayBoard, displayName, IDLE_SECONDS, liveness, openDatabase, type Player, recentMatches, recentRuns, | |
| 14 | runBoard, saveMatch, saveRun, standings, | |
| 14 | 15 | } from "./db.ts"; |
| 15 | 16 | import { playKey } from "./matchid.ts"; |
| 16 | 17 | import type { Result } from "./result.ts"; |
| 18 | import type { RangeRun } from "./run.ts"; | |
| 17 | 19 | |
| 18 | 20 | const dir = (): string => mkdtempSync(join(tmpdir(), "db-")); |
| 19 | 21 | |
| @@ -313,3 +315,56 @@ test("a post that knows more still moves every number up", () => { | ||
| 313 | 315 | expect([row.score_mine, row.score_theirs]).toEqual([3, 3]); |
| 314 | 316 | expect(liveness(row, Date.now())).toBe("live"); |
| 315 | 317 | }); |
| 318 | ||
| 319 | const range = (over: Partial<RangeRun> = {}): RangeRun => ({ | |
| 320 | runId: crypto.randomUUID(), | |
| 321 | name: "ignored — the player says the name", | |
| 322 | drill: "popup", | |
| 323 | score: 1400, hits: 14, shots: 20, seed: 7, seconds: 30, | |
| 324 | ...over, | |
| 325 | }); | |
| 326 | ||
| 327 | test("a run on the board is the run that was posted: a second post under its id cannot lower it", () => { | |
| 328 | // The matches' rule (`saveMatch` above) read for the range: a post that | |
| 329 | // arrives after the one the board is showing must not walk the numbers | |
| 330 | // backwards. A match accumulates, so a late post may raise it; a run is one | |
| 331 | // finished clock, so a second post under its id is the same run again and | |
| 332 | // changes nothing but the name it is shown under. | |
| 333 | const db = openDatabase(dir()); | |
| 334 | const id = crypto.randomUUID(); | |
| 335 | const kestrel = player("u-1", "kestrel"); | |
| 336 | expect(saveRun(db, kestrel, range({ runId: id, score: 1400, hits: 14, shots: 20 }))).toBe(true); | |
| 337 | // The same id again, claiming a worse run — a retry that raced, or a page | |
| 338 | // that posted twice. It is accepted (it is the same owner's id) and ignored. | |
| 339 | expect(saveRun(db, kestrel, range({ runId: id, score: 10, hits: 1, shots: 99, seed: 9, seconds: 5 }))).toBe(true); | |
| 340 | ||
| 341 | const row = recentRuns(db)[0]!; | |
| 342 | expect([row.score, row.hits, row.shots, row.seed, row.seconds]).toEqual([1400, 14, 20, 7, 30]); | |
| 343 | // One row, not two, and the board still ranks the run that was played. | |
| 344 | expect(recentRuns(db).length).toBe(1); | |
| 345 | expect(runBoard(db, "popup").map((r) => [r.score, r.hits, r.runs])).toEqual([[1400, 14, 1]]); | |
| 346 | ||
| 347 | // A different run is a different row, and the board takes the better of the | |
| 348 | // two and says there were two. | |
| 349 | expect(saveRun(db, kestrel, range({ score: 1600, hits: 16, shots: 18 }))).toBe(true); | |
| 350 | expect(runBoard(db, "popup").map((r) => [r.score, r.runs])).toEqual([[1600, 2]]); | |
| 351 | ||
| 352 | // A name is not a number: a player who renames themselves is shown renamed. | |
| 353 | expect(saveRun(db, player("u-1", "kestrel the second"), range({ runId: id }))).toBe(true); | |
| 354 | expect(recentRuns(db).find((r) => r.id === id)!.name).toBe("kestrel the second"); | |
| 355 | }); | |
| 356 | ||
| 357 | test("a run belongs to whoever posted it: nobody else writes over that id", () => { | |
| 358 | const db = openDatabase(dir()); | |
| 359 | const id = crypto.randomUUID(); | |
| 360 | expect(saveRun(db, player("u-1", "kestrel"), range({ runId: id, score: 1400 }))).toBe(true); | |
| 361 | // Another identity, and a guest, both bounce off it. | |
| 362 | expect(saveRun(db, player("u-2", "wren"), range({ runId: id, score: 9000 }))).toBe(false); | |
| 363 | expect(saveRun(db, guest("finch", "s9"), range({ runId: id, score: 9000 }))).toBe(false); | |
| 364 | expect(recentRuns(db)[0]!.score).toBe(1400); | |
| 365 | // A guest's run is that browser's: another browser cannot take its id either. | |
| 366 | const mine = crypto.randomUUID(); | |
| 367 | expect(saveRun(db, guest("finch", "s9"), range({ runId: mine, score: 800 }))).toBe(true); | |
| 368 | expect(saveRun(db, guest("finch", "s10"), range({ runId: mine, score: 9000 }))).toBe(false); | |
| 369 | expect(recentRuns(db).find((r) => r.id === mine)!.score).toBe(800); | |
| 370 | }); |
src/db.ts +9 −3
| @@ -343,6 +343,14 @@ export type RunRow = { | ||
| 343 | 343 | * rather than allowed to overwrite it. The page posts a run once, when its |
| 344 | 344 | * clock runs out, so in practice this only ever inserts; the update is there so |
| 345 | 345 | * that a retried post is the same row and not a second one. |
| 346 | * | |
| 347 | * A run that is on the board stays the run that was posted. A match's numbers | |
| 348 | * accumulate while it is played, so a late post may raise them (`saveMatch` | |
| 349 | * above); a run is one finished thirty seconds and has no later state to carry, | |
| 350 | * so a second post under an id already taken can only be the same run again. | |
| 351 | * Its numbers are therefore frozen at the first post and only the name is | |
| 352 | * refreshed — otherwise a re-post of an id is a way to write over a score that | |
| 353 | * is already ranked, and `runBoard` ranks by score. | |
| 346 | 354 | */ |
| 347 | 355 | export function saveRun(db: Database, player: Player, run: RangeRun): boolean { |
| 348 | 356 | const existing = db |
| @@ -356,9 +364,7 @@ export function saveRun(db: Database, player: Player, run: RangeRun): boolean { | ||
| 356 | 364 | `INSERT INTO run (id, session, user_id, name, drill, score, hits, shots, seed, seconds) |
| 357 | 365 | VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10) |
| 358 | 366 | ON CONFLICT(id) DO UPDATE SET |
| 359 | name = excluded.name, drill = excluded.drill, score = excluded.score, hits = excluded.hits, | |
| 360 | shots = excluded.shots, seed = excluded.seed, seconds = excluded.seconds, | |
| 361 | updated_at = datetime('now')`, | |
| 367 | name = excluded.name, updated_at = datetime('now')`, | |
| 362 | 368 | ).run( |
| 363 | 369 | run.runId, player.session, player.userId, player.name, run.drill, |
| 364 | 370 | run.score, run.hits, run.shots, run.seed, run.seconds, |
new above
offline