refactor(persistencia): extrai buildMatchRow/buildScoreRows de persistMatch e cobre com Vitest (#50) - #55
Conversation
…tMatch e cobre com Vitest (#50) O mapeamento results→matches/scores vivia inline numa função async privada, só alcançável atrás de uma corrida completa — zero teste. Agora é puro e determinístico em src/lib/room.ts (finished_at entra por parâmetro), e persistMatch só orquestra os dois inserts. Refactor puro: nenhum comportamento observável muda. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Caution The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased. |
|
@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. |
🔧 PR Doctor — conflito após #60; reparo por uniãoEste PR ficou CONFLICTING (
Não mergeio nesta rodada — teto de 2 merges atingido (#57, #60) e o conflito precisa ser resolvido antes. |
… a escrita (#56) (#96) As actions `settings`, `start`, `reset` e `claim-leader` faziam `.update()` sem desestruturar o `error` — e o supabase-js DEVOLVE `{ error }` em vez de lançar. Constraint violada (a 0004 pendente torna isso provável), RLS ou timeout viravam "deu certo" na tela do líder, com a sala parada no lobby e nenhum log. - `roomUpdateOutcome` em `src/lib/room.ts`: decisão pura (erro ⇒ 500, zero linhas confirmadas ⇒ 409 neutro, ≥1 linha ⇒ ok), coberta por 5 casos no Vitest. - `applyRoomUpdate` em `route.ts`: adaptador fino que encadeia `.select("code")`, loga o detalhe no servidor e devolve `NextResponse` de erro ou `null`. - `finish` passa a reportar o `error`, PRESERVANDO `flipped.length === 0` como "outro cliente já finalizou" ⇒ segue `ok: true` sem repersistir. - GET deixa de devolver `error.message` do PostgREST ao cliente (virava toast). Fora do escopo, declarado na PR: `persistMatch` (colide com a #55) e `src/app/api/rooms/route.ts`.
🔧 PR Doctor — ainda CONFLITANTE; reparo por união + re-quórumO refactor (extrair Ação (§5.D): na branch |
Uniao com origin/main (que trouxe #60/#79/#96/#89). Resolucao: - room.ts: mantidos os builders (buildMatchRow/buildScoreRows/MatchInsert/ ScoreInsert) E as funcoes de kick/vote/outcome que entraram na main. - route.ts: imports unificados; persistMatch segue como wrapper fino dos builders. - room.test.ts: imports unificados; adicionado kicked_ids:[] ao FULL_ROOM (campo virou obrigatorio em RoomRow via #79). Gate: typecheck OK, build OK, test 103 OK, validate-persistence 72/0.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
🏛️ Quórum §7.2 — APROVADA (refactor puro) + reparo por uniãoLente Domínio/persistência: APROVA, nenhum vetor. Equivalência comportamental confirmada campo a campo entre o mapeamento inline antigo e os novos Reparo por união: reencostado na main (#60/#79/#96/#89); resolvidos |
Uniao com origin/main (#55/#60/#79/#89/#96). Resolucao (tudo aditivo): - useRoom.ts: mantidos isSpectatorJoin + isSpectator E as importacoes/logica de votacao (#89, tallyVotes/pickVoteWinner/activeVotes/voteTally/myVote). - RoomView.tsx: mantido o branch isSpectator?SpectatorView:Race E os props isLeader/onKick do Race (#79), alem de voteTally/myVote no destructure. - room.test.ts: mantidos os blocos de isSpectatorJoin (#64) e votacao/outcome. Gate: typecheck OK, test 109 OK, build OK, validate-persistence 72/0.
Contexto
persistMatch(src/app/api/rooms/[code]/route.ts) mapeava osresultsjá sanitizados para aslinhas de
matches/scoresinline, dentro de uma funçãoasyncprivada só alcançável atrás deuma corrida multiplayer completa — lógica real (eleição de vencedor, arredondamentos, coerções) com
zero teste, alimentando o leaderboard global público.
O que mudou e por quê
src/lib/room.ts: novos builders puros e determinísticos, ao lado desanitizeResults:buildMatchRow(room, results, finishedAt): MatchInsert | null—nullquandoresultsestávazio (nenhuma
matchesórfã).finished_atentra por parâmetro justamente para manter afunção determinística/testável.
buildScoreRows(matchId, room, results): ScoreInsert[].MatchInsert/ScoreInsertexportados.route.ts:persistMatchvirou wrapper fino — chama os builders e faz só os doisinsert,mantendo o
try/catchbest-effort atual. Nenhuma aritmética de mapeamento sobrou inline.src/lib/room.test.ts: +16 casos cobrindo o contrato — vencedor = menorplace(com empate,placeausente caindo no fallback 99 e "ninguém colocado"),winner_wpm/wpm/accuracy/errorsarredondados e nunca
NaN,place0/ausente →null,finishedcoagido a booleano,snippetausente →snippet_title: null, eresultsvazio →null+ zero linhas de score.Refactor puro: nenhum comportamento observável muda em produção (a semântica sutil do
Math.round(x) || 0foi preservada de propósito — agora com contrato versionado em teste).Nota de contrato descoberta ao testar
r.place || nullsó normaliza falsy: umplacenegativo passaria direto. Não "consertei" issoaqui (seria mudança de comportamento fora do escopo) — na prática ele nunca chega aos builders porque
sanitizeResultsjá transformaplace < 1emnull. Um teste explícito versiona essa divisão deresponsabilidade em vez de deixá-la implícita.
Gate (resultado real, no worktree)
pnpm install --frozen-lockfile✅pnpm typecheck✅pnpm build✅pnpm test✅ — 36 testes, 0 falhas (era 20)node scripts/validate-persistence.mjs✅ 33/33node .claude/skills/cr-typing-engine/scripts/validate-metrics.mjs✅ 37/37pnpm lint= N/A (sem config ESLint no repo; a CI não roda lint)Riscos
Baixo. Diff restrito a
src/lib/room.ts(adição), ao corpo depersistMatche ao arquivo de teste.Nada toca a área sagrada (input da corrida), banco, migrations, RLS ou anti-cheat —
sanitizeResultssegue intacta como fronteira. O único risco é divergência acidental de mapeamento, e é exatamente
isso que os testes novos travam.
Sem Parecer do Conselho nesta issue — plano seguido é o da própria issue (espelha o padrão #31/#32).
Solicito quórum (HANDBOOK §7)
Closes #50