test: instala Vitest + primeiro suite anti-cheat (sanitizeResults) no CI (#31) - #33
Conversation
Instala Vitest (devDependency, dev-only — não entra no bundle de runtime) e versiona a fronteira anti-cheat do leaderboard, antes coberta só pelo script ad-hoc scripts/validate-persistence.mjs que a CI não roda. - vitest.config.ts (environment node; funções-alvo puras, sem DOM) - scripts test/test:watch em package.json - src/lib/room.test.ts: 21 casos cobrindo sanitizeResults (WPM implausível incl. 3596 de #28, name/trim/teto, clamp accuracy/errors/place/progress, cap de array, entradas degeneradas) e clampInt (arredonda, [min,max], NaN) - exporta clampInt (antes privado) para teste direto — mudança de 1 linha - CI roda `pnpm test` como etapa do gate (falha o build se um teste quebrar) Closes #31 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
@caioross is attempting to deploy a commit to the caioross' projects team on Vercel, but is not a member of this team. To resolve this issue, you can:
To read more about collaboration on Vercel, click here. |
There was a problem hiding this comment.
Code Review
This pull request integrates Vitest into the project, adding test scripts to package.json, configuring the test runner in vitest.config.ts, exporting clampInt from src/lib/room.ts, and creating a comprehensive test suite in src/lib/room.test.ts to validate the leaderboard's anti-cheat sanitization logic. Feedback highlights a bug in the sanitization of finishedAt where null values are incorrectly converted to 0 instead of being preserved, and provides a suggestion to add a test case to cover this scenario.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| expect(row.finishedAt).toBeNull(); // finishedAt não-finito → null | ||
| }); |
There was a problem hiding this comment.
Ao analisar a função sanitizeResults em src/lib/room.ts, identificamos um comportamento inesperado com o campo finishedAt.
Atualmente, a sanitização desse campo é feita da seguinte forma:
finishedAt: Number.isFinite(Number(row.finishedAt)) ? Number(row.finishedAt) : null
No JavaScript, Number(null) resulta em 0, e Number.isFinite(0) é true. Isso significa que se um resultado tiver finishedAt: null (o que é o padrão e perfeitamente válido para jogadores que não completaram a corrida ou abandonaram), o valor será incorretamente convertido para 0 (equivalente a 1970) em vez de ser preservado como null.
Para corrigir isso no arquivo src/lib/room.ts, a verificação deveria validar explicitamente se o valor não é nulo ou indefinido antes de convertê-lo:
finishedAt: row.finishedAt !== null && row.finishedAt !== undefined && Number.isFinite(Number(row.finishedAt)) ? Number(row.finishedAt) : null
Sugerimos adicionar este caso de teste para expor e garantir a correção desse comportamento.
| expect(row.finishedAt).toBeNull(); // finishedAt não-finito → null | |
| }); | |
| expect(row.finishedAt).toBeNull(); // finishedAt não-finito → null | |
| }); | |
| it("preserva finishedAt como null se for null ou undefined", () => { | |
| const [rowNull] = sanitizeResults([legit({ finishedAt: null })], ROOM); | |
| const [rowUndefined] = sanitizeResults([legit({ finishedAt: undefined })], ROOM); | |
| expect(rowNull.finishedAt).toBeNull(); | |
| expect(rowUndefined.finishedAt).toBeNull(); | |
| }); |
🩺 Quórum adversarial (HANDBOOK §7.2) — 3× APROVATrês lentes adversariais em paralelo, cada uma com default VETAR e obrigação de vetor
Nota de blindagem (follow-up, não-bloqueante): o teste do teto ancora em ⏸️ Parking-lot — pronto, merge parado pelo gate de produçãoO que falta: nada de código. Quórum 3×APROVA, CI ( |
🩺 Parecer do PR Doctor — QUÓRUM 3× APROVA → merge (HANDBOOK §7.2)Diff lido inteiro; CI verificado (o step Test rodou e passou no headSHA — 21 casos executam no CI de agora em diante). Três lentes adversariais:
Ganho estrutural: a promessa do docstring de |
Contexto
O projeto tinha zero testes versionados e nenhum runner: a única "cobertura" da fronteira anti-cheat era
scripts/validate-persistence.mjs, um script solto que a CI não roda (ci.yml= typecheck + build).sanitizeResults(src/lib/room.ts) é o único guardião das tabelas públicasmatches/scores— a engine é client-side e a API roda comservice_role, então uma regressão aqui corrompe o ranking global em silêncio (classe de bug de #7 e #28) e nada na CI pegaria.Este PR transforma a promessa do docstring de
sanitizeResultsnuma rede de segurança real, executada no CI.O que mudou (e por quê)
devDependency(dev-only — não entra no bundle de runtime; respeita HANDBOOK §7.1 sobre deps pesadas de runtime).vitest.config.tsmínimo:environment: 'node'(as funções-alvo são puras, sem DOM) einclude: src/**/*.test.ts.test(vitest run) etest:watchempackage.json.src/lib/room.test.ts— 21 casos cobrindosanitizeResults: input não-array →[]; WPM >MAX_PLAUSIBLE_WPMdescartado (inclui o recorde impossível de 3596 WPM de Anti-cheat/Leaderboard: recorde impossível (3596 WPM) no Ranking Global — sem teto de plausibilidade #28), WPM negativo/NaN/Infinitydescartado, teto exato aceito;namevazio/só-espaços/ausente/não-string descartado e> MAX_NAME_LENtruncado; clamp deaccuracy/errors/progress;place < 1→null; cap de array emmax_playerse teto absoluto12; campos cosméticos preservados/normalizados. EclampInt: arredonda, respeita[min,max],NaN/não-finito →min.clampInt(antes privado) para teste direto — mudança segura de 1 linha..github/workflows/ci.yml): novo passopnpm testno gate; um teste quebrado agora falha o build.Validação (gate real, HANDBOOK §6)
pnpm typecheck→ ✅ verdepnpm test→ ✅ 21 testes passaram (1 arquivo)pnpm build→ ✅ verde; bundle de produção inalterado (o suite não é importado por nenhuma rota; Vitest é dev-only)node scripts/validate-persistence.mjs→ ✅ 33/33 (mantido como fonte-verdade dos casos)pnpm lint→ N/A (sem config ESLint no repo; a CI não roda lint)Riscos
Baixo. Só adiciona superfície de teste e um passo de CI; nenhuma mudança de comportamento de runtime (a única edição em código de produto é tornar
clampIntexportado). Opnpm-lock.yamlcresce com a árvore dev do Vitest, que não vai para produção.Muda CI + adiciona dependência nova leve → cai na área de quórum.
Solicito quórum (HANDBOOK §7)
Closes #31