Skip to content

Testes: instalar Vitest + primeiro suite na fronteira anti-cheat (sanitizeResults) e rodar no CI #31

Description

@caioross

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

  1. 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).
  2. Script "test": "vitest run" (e opcionalmente "test:watch": "vitest") em package.json.
  3. Primeiro suite src/lib/room.test.ts cobrindo sanitizeResults e clampInt.
  4. A CI (.github/workflows/ci.yml) passa a rodar pnpm test como etapa do gate.

Acceptance criteria (verificáveis)

  • pnpm test existe e roda; sai verde com o novo suite.
  • Vitest está em devDependencies (não em dependencies); pnpm build continua verde e o bundle de produção não muda.
  • src/lib/room.test.ts cobre, no mínimo, para sanitizeResults:
    • input não-array → [];
    • linha com 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;
    • linha com wpm negativo / NaN / não-finito → descartada;
    • name vazio após trim → descartada (score anônimo); name > MAX_NAME_LEN → truncado em 20;
    • accuracy fora de 0..100 → clampada para o intervalo;
    • array maior que room.max_players → cortado no cap; max_players inválido → cap absoluto 12 (ABSOLUTE_MAX_PLAYERS);
    • campos cosméticos (id/color/progress/finishedAt) preservados/normalizados como no código atual.
  • clampInt: NaN/não-finito → min; arredonda; respeita [min,max].
  • CI roda a suíte de testes (passo novo em ci.yml) e falha o build se um teste quebrar.

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).

Metadata

Metadata

Assignees

No one assigned

    Labels

    P1Alto valor — próximo da filaarea:infraCI/CD, build, tooling, deploy, depsarea:securityRLS, service_role, validação de entrada, segredos

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions