Contexto
O projeto tem zero testes automatizados e nenhum test runner: package.json só expõe dev/build/start/typecheck/lint/db:migrate — não há test, nem Vitest/Jest nas devDependencies. A única "cobertura" hoje são scripts .mjs ad-hoc (scripts/validate-persistence.mjs, .claude/skills/*/scripts/validate-metrics.mjs) que a CI não roda (.github/workflows/ci.yml = typecheck + build). Isso é a dívida nº1 do backlog.
O melhor primeiro alvo já existe puro e pronto para teste: a fronteira anti-cheat do leaderboard em src/lib/room.ts:123 (sanitizeResults) + clampInt (room.ts:107). É código security-critical — a API roda com service_role e sanitizeResults é o único guardião das tabelas públicas matches/scores (ver comentário room.ts:93-104). Uma regressão aqui corrompe silenciosamente o ranking global (foi exatamente a classe de bug de #7 e #28), e nada na CI hoje pegaria.
O próprio docstring de sanitizeResults (room.ts:121) já afirma "coberto por scripts/validate-persistence.mjs" — mas isso é um script solto, não um teste versionado que roda no CI. Esta issue transforma essa promessa em rede de segurança real.
Escopo
- Adicionar Vitest como
devDependency (runner leve, dev-only — não entra no bundle de runtime, respeita HANDBOOK §7.1 sobre deps pesadas de runtime). Config mínima (vitest.config.ts) com environment: 'node' (as funções-alvo são puras, sem DOM).
- Script
"test": "vitest run" (e opcionalmente "test:watch": "vitest") em package.json.
- Primeiro suite
src/lib/room.test.ts cobrindo sanitizeResults e clampInt.
- A CI (
.github/workflows/ci.yml) passa a rodar pnpm test como etapa do gate.
Acceptance criteria (verificáveis)
Dica de abordagem
- Vitest integra com o tsconfig do Next sem transpiler extra;
import { sanitizeResults, clampInt, MAX_PLAUSIBLE_WPM } from './room'. clampInt hoje não é exportado (room.ts:107) — exportá-lo é uma mudança segura de 1 linha, ou testá-lo indiretamente via sanitizeResults (accuracy/errors).
- Reaproveite os casos que já existem em
scripts/validate-persistence.mjs como fonte de casos-verdade — a ideia é migrar aquele conhecimento ad-hoc para um teste versionado que a CI executa (o script pode continuar existindo ou ser aposentado numa fatia futura; não é escopo desta issue removê-lo).
- Gate (HANDBOOK §6):
pnpm typecheck && pnpm build && pnpm test. Como adiciona dep nova leve + mexe na CI, o PR cai na área de quórum (§7.2) — abra non-draft com a linha Solicito quórum (HANDBOOK §7).
Contexto
O projeto tem zero testes automatizados e nenhum test runner:
package.jsonsó expõedev/build/start/typecheck/lint/db:migrate— não hátest, nem Vitest/Jest nasdevDependencies. A única "cobertura" hoje são scripts.mjsad-hoc (scripts/validate-persistence.mjs,.claude/skills/*/scripts/validate-metrics.mjs) que a CI não roda (.github/workflows/ci.yml= typecheck + build). Isso é a dívida nº1 do backlog.O melhor primeiro alvo já existe puro e pronto para teste: a fronteira anti-cheat do leaderboard em
src/lib/room.ts:123(sanitizeResults) +clampInt(room.ts:107). É código security-critical — a API roda comservice_roleesanitizeResultsé o único guardião das tabelas públicasmatches/scores(ver comentárioroom.ts:93-104). Uma regressão aqui corrompe silenciosamente o ranking global (foi exatamente a classe de bug de #7 e #28), e nada na CI hoje pegaria.O próprio docstring de
sanitizeResults(room.ts:121) já afirma "coberto porscripts/validate-persistence.mjs" — mas isso é um script solto, não um teste versionado que roda no CI. Esta issue transforma essa promessa em rede de segurança real.Escopo
devDependency(runner leve, dev-only — não entra no bundle de runtime, respeita HANDBOOK §7.1 sobre deps pesadas de runtime). Config mínima (vitest.config.ts) comenvironment: 'node'(as funções-alvo são puras, sem DOM)."test": "vitest run"(e opcionalmente"test:watch": "vitest") empackage.json.src/lib/room.test.tscobrindosanitizeResultseclampInt..github/workflows/ci.yml) passa a rodarpnpm testcomo etapa do gate.Acceptance criteria (verificáveis)
pnpm testexiste e roda; sai verde com o novo suite.devDependencies(não emdependencies);pnpm buildcontinua verde e o bundle de produção não muda.src/lib/room.test.tscobre, no mínimo, parasanitizeResults:[];wpm > MAX_PLAUSIBLE_WPM(350) → descartada (não clampada) — caso do recorde 3596 WPM de Anti-cheat/Leaderboard: recorde impossível (3596 WPM) no Ranking Global — sem teto de plausibilidade #28;wpmnegativo /NaN/ não-finito → descartada;namevazio apóstrim→ descartada (score anônimo);name>MAX_NAME_LEN→ truncado em 20;accuracyfora de 0..100 → clampada para o intervalo;room.max_players→ cortado no cap;max_playersinválido → cap absoluto 12 (ABSOLUTE_MAX_PLAYERS);id/color/progress/finishedAt) preservados/normalizados como no código atual.clampInt:NaN/não-finito →min; arredonda; respeita[min,max].ci.yml) e falha o build se um teste quebrar.Dica de abordagem
import { sanitizeResults, clampInt, MAX_PLAUSIBLE_WPM } from './room'.clampInthoje não é exportado (room.ts:107) — exportá-lo é uma mudança segura de 1 linha, ou testá-lo indiretamente viasanitizeResults(accuracy/errors).scripts/validate-persistence.mjscomo fonte de casos-verdade — a ideia é migrar aquele conhecimento ad-hoc para um teste versionado que a CI executa (o script pode continuar existindo ou ser aposentado numa fatia futura; não é escopo desta issue removê-lo).pnpm typecheck && pnpm build && pnpm test. Como adiciona dep nova leve + mexe na CI, o PR cai na área de quórum (§7.2) — abra non-draft com a linhaSolicito quórum (HANDBOOK §7).