test: testes automatizados das rotas criticas - #28
Merged
Conversation
So existia o smoke test, que valida "sobe e responde" - nao valida comportamento. Com contribuidores externos mexendo em codigo que o mantenedor nao escreveu, isso e risco real de regressao silenciosa. 25 testes com node:test (built-in do Node 22, sem dependencia nova), sem precisar de banco - rodam em qualquer ambiente e no CI antes do Mongo subir: - rbac: matriz de permissoes. Cobre o caso perigoso de permissao desconhecida/typo, que precisa NEGAR e nao liberar. - auth-service: bloqueio por tentativas (inclusive que o bloqueio responde 429 e nao 401, senao a UI diz "senha invalida" para uma conta travada) e recuperacao de senha (nao enumera contas, token so como hash, uso unico, destrava a conta). - tenant-isolation: varredura estatica que acusa consulta a colecao de tenant sem tenant_id no filtro - o "ponto mais sensivel do projeto" segundo o CONTRIBUTING. Sobre a varredura: a primeira versao acusou 25 casos, mas a maioria era falso positivo do meu extrator de argumento (filtro em variavel, filtro multi-linha, indireção via filterKBsByAccess). Corrigido, e com 7 testes do proprio detector - um detector quebrado passaria sempre e daria falsa seguranca. Sobraram 19 casos reais: consultas por _id/created_by sem tenant_id. Nao vazam leitura hoje (o id costuma vir de documento ja filtrado), mas falta defesa em profundidade. Congelei no padrao catraca (KNOWN_GAPS): o teste passa com os conhecidos e falha em QUALQUER novo. Corrigir 19 em lote sem poder testar contra o Mongo seria arriscado; devem sair um por vez, com teste manual. O teste do reset de senha achou um detalhe do proprio codigo: o token e reivindicado com updateOne condicional e checagem de modifiedCount (protecao contra corrida). O stub agora modela isso.
Contributor
There was a problem hiding this comment.
Pull request overview
This PR introduces a first set of automated unit tests (Node 22 node:test) focused on critical backend security/behavior guarantees (RBAC, auth hardening, and multi-tenant isolation), and wires them into the backend CI pipeline so they run before Mongo-dependent steps.
Changes:
- Adds unit tests for RBAC permission matrix and auth flows (login lockout + password reset properties).
- Adds a static “tenant isolation” guard test that scans module queries and blocks new unscoped tenant collection access (ratchet via
KNOWN_GAPS). - Updates backend
npm testto run unit tests and updates CI + contributing docs accordingly.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
CONTRIBUTING.md |
Updates contributor guidance to run unit + smoke tests and documents the tenant isolation guard behavior. |
backend/tests/tenant-isolation.test.js |
Adds static tenant query scanner + self-tests for the scanner + ratchet list for known gaps. |
backend/tests/rbac.test.js |
Adds unit tests locking down RBAC permission expectations and default-deny behavior. |
backend/tests/auth-service.test.js |
Adds unit tests for login lockout and password reset security properties using a Mongo stub. |
backend/package.json |
Switches npm test to run the unit test suite via node --test. |
.github/workflows/ci.yml |
Runs unit tests before DB setup/seed + smoke test in the backend CI job. |
Suppressed comments (1)
backend/tests/auth-service.test.js:57
- O stub de
updateMany()marca TODOS os tokens comoused = truee ignora o filtro/update recebidos. Isso diverge do comportamento real do Mongo (e do propriorequestPasswordReset()), podendo mascarar bugs ou quebrar testes futuros que dependam do estado dos tokens.
updateMany: async () => { store.tokens.forEach(t => { t.used = true; }); return {}; }
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| return store.tokens.find(t => | ||
| t.token_hash === q.token_hash && t.used === false && t.expires_at > new Date()) || null; | ||
| }, | ||
| insertOne: async (doc) => { if (name !== 'users') store.tokens.push(doc); return { insertedId: 'x' }; }, |
Comment on lines
+155
to
+162
| let match; | ||
| while ((match = pattern.exec(source)) !== null) { | ||
| const [, collection, method] = match; | ||
| const arg = extractFirstArgument(source, pattern.lastIndex); | ||
|
|
||
| // Filtro literal com tenant_id, ou variavel/spread de filtro que ja o carrega | ||
| const mentionsTenant = /tenant_id/.test(arg); | ||
| const usesScopedVar = [...scopedVars].some(v => new RegExp(`\\b${v}\\b`).test(arg)); |
This was referenced Aug 3, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
O problema
Só existia o smoke test, que valida "sobe e responde" — não valida comportamento. Com contribuidores externos mexendo em código que o mantenedor não escreveu, isso é risco real de regressão silenciosa.
O que entra
25 testes com
node:test(built-in do Node 22, sem dependência nova), que não precisam de banco — rodam em qualquer ambiente e no CI antes do Mongo subir:rbac.test.jsauth-service.test.jstenant-isolation.test.jstenant_idno filtroSobre a varredura de tenant
Esse é o ponto mais interessante — e o que mais me fez trabalhar.
A primeira versão acusou 25 casos, mas a maioria era falso positivo do meu próprio extrator de argumento: filtro montado em variável (
const baseMatch = { tenant_id }), filtro multi-linha, e indireção viafilterKBsByAccess(). Um teste que grita à toa é pior que teste nenhum — alguém acaba desligando. Corrigi o detector e adicionei 7 testes do próprio detector, porque um detector quebrado passaria sempre e daria falsa segurança.Sobraram 19 casos reais: consultas por
_id/created_bysemtenant_id. Não vazam leitura hoje (o id costuma vir de um documento já filtrado por tenant), mas falta defesa em profundidade — um id de outra organização chegando pela URL seria aceito. Exemplo emreview.routes.js:110:Não corrigi os 19 em lote, e essa foi uma decisão consciente: sem Mongo acessível daqui, eu não teria como verificar que adicionar o filtro não quebra funcionalidade legítima em Gamificação/GPS. Congelei no padrão catraca (
KNOWN_GAPS): o teste passa com os conhecidos e falha em qualquer novo. Devem sair um por vez, com teste manual.Isso rende uma boa leva de
good first issue— que era justamente o que faltava no PR #22.Um achado no caminho
O teste do reset de senha revelou um detalhe do próprio código: o token é reivindicado com
updateOnecondicional + checagem demodifiedCount(proteção contra corrida — duas requisições simultâneas não podem usar o mesmo link). Meu stub não modelava isso e o teste falhou, corretamente. O stub agora modela.Test plan
npm test→ 25 passam