Skip to content

Testes de Rate-Limit — Mural Olímpico ​

Documentação do script scripts/test-portal-rate-limit.mjs e sistema de relatórios.


Visão Geral ​

O script simula picos de acesso reais (ex: 1.500 alunos em 3-4 minutos) ao Mural Olímpico (Método A: matrícula + data de nascimento) para validar:

  1. Rate limits (por escola e por IP)
  2. Lockout progressivo (por identificador/matrícula)
  3. Estabilidade do banco sob carga massiva
  4. Anti-timing e mensagens genéricas (sem enumeração)

Pré-requisitos ​

  1. Node 18+ (fetch nativo)
  2. Edge Function portal-escola deployada no Supabase
  3. Dados de alunos (opcional):
    sql
    SELECT matricula, data_nascimento 
    FROM alunos 
    WHERE escola_id = '00000000-0000-0000-0000-000000000003' AND ativo = true;
    Salvar como JSON: [{"matricula":"001","dataNascimento":"2012-05-15"}, ...]
  4. Limpar tentativas anteriores (para resultados limpos):
    sql
    DELETE FROM portal_login_tentativas WHERE escola_id = '00000000-0000-0000-0000-000000000003';

Uso ​

bash
# Todos os cenários (com pausas de 5min entre cada)
node scripts/test-portal-rate-limit.mjs

# Cenário específico
node scripts/test-portal-rate-limit.mjs --only=legitimo
node scripts/test-portal-rate-limit.mjs --only=bruteforce
node scripts/test-portal-rate-limit.mjs --only=inexistente
node scripts/test-portal-rate-limit.mjs --only=stress

# Com dados reais de alunos
node scripts/test-portal-rate-limit.mjs --alunos=alunos.json

# Ajustar concorrência
node scripts/test-portal-rate-limit.mjs --concurrency=100

# Pausa personalizada entre cenários (em segundos)
node scripts/test-portal-rate-limit.mjs --pause=120

# Dry run (não envia requests)
node scripts/test-portal-rate-limit.mjs --dry-run

Cenários ​

Cenário 1: Tráfego Legítimo (sem retry) ​

  • Envia TOTAL_STUDENTS requests simultâneos com dados de alunos
  • Mede quantos passam (200) vs quantos são bloqueados (429)
  • Esperado: Maioria passa, rate limit ativa em picos >600 req/min

Cenário 1B: Legítimo com Retry ​

  • Mesmo que o 1, mas com retry automático ao receber 429
  • Simula comportamento real do frontend
  • Requer --alunos=arquivo.json com dados reais
  • Esperado: 100% dos alunos conseguem logar eventualmente

Cenário 2: Brute Force (lockout progressivo) ​

Testa o lockout em 3 fases com espera entre cada:

FaseTentativasLockoutEspera
13 erros1 minuto70s
2+3 erros (total 6)5 minutos310s
3+4 erros (total 10)30 minutos—
  • Após fase 3, testa se login CORRETO também é bloqueado (lockout por identificador, não por dados)
  • Duração total: ~7 minutos
  • Esperado: 429 detectado em cada transição de tier

Nota sobre acumulação: O lockout é cumulativo em janela de 24h. Se rodar o cenário 2x seguidas sem limpar o banco, o segundo teste já começa com falhas acumuladas e pode pular tiers.

Cenário 3: Matrícula Inexistente ​

  • Envia 20 requests com matrículas fictícias
  • Verifica se as respostas são genéricas (sem revelar se matrícula existe)
  • Esperado: Todas as respostas HTTP 401 com mensagem idêntica

Cenário 4: Stress (burst máximo) ​

  • Dispara 1.200 requests simultaneamente (sem pool de concorrência)
  • Simula o pior caso possível de burst
  • Esperado: 0 erros 500, 0 erros de rede, rate limit contém o burst

Sistema de Relatórios ​

Arquitetura ​

scripts/
├── test-portal-rate-limit.mjs      ← Roda testes, exporta JSON
├── gerar-relatorio.mjs             ← Lê JSON, gera .md
└── relatorios/                     ← Criada automaticamente
    ├── resultados_stress_2026-03-08_03h45.json
    └── relatorio_stress_2026-03-08_03h45.md

Fluxo Automático ​

  1. O script de teste executa os cenários
  2. Ao finalizar, salva resultados em scripts/relatorios/resultados_[cenário]_[data].json
  3. Chama automaticamente gerar-relatorio.mjs que gera o .md na mesma pasta

Nomes dos Arquivos ​

Formato: relatorio_[cenário]_YYYY-MM-DD_HHhMM.md

Exemplos:

  • relatorio_bruteforce_2026-03-08_03h45.md
  • relatorio_todos_2026-03-08_14h20.md
  • relatorio_stress_2026-03-09_09h00.md

Gerar Relatório Manualmente ​

bash
node scripts/gerar-relatorio.mjs scripts/relatorios/resultados_stress_2026-03-08_03h45.json

Conteúdo do Relatório MD ​

  • Configuração: Gateway, escola, concorrência, período
  • Resumo por cenário: Tabela com contagens de status, duração, throughput, veredicto PASS/FAIL
  • Latência: p50/p95/p99/max por cenário
  • Rate limit breakdown: Contagem por tipo (escola, IP, lockout)
  • Lockouts detectados: Tentativa e mensagem (cenário brute force)
  • Veredicto final: PASS se 0 erros 500 e 0 erros de rede em todos os cenários

Interpretação de Resultados ​

Veredicto PASS/FAIL ​

  • PASS: 0 erros HTTP 500, 0 erros de rede. Rate limits podem ter ativado (esperado).
  • FAIL: Erros 500 ou falhas de rede indicam instabilidade (pool de conexões, timeout, crash).

Erros Comuns ​

SintomaCausa ProvávelSolução
Muitos erros de rede (status 0)Pool de conexões esgotadoVerificar singleton createSupabaseSystem
500s em burstEdge Function crashouVerificar logs Supabase
Rate limit não ativaContadores zeradosVerificar portal_login_tentativas
Lockout não detectadoTentativas anteriores na janela 24hLimpar tabela antes do teste
Todos os cenários 429Teste anterior poluiu contadoresDELETE FROM portal_login_tentativas WHERE escola_id = '...'

Otimizações de Infraestrutura ​

Singleton createSupabaseSystem() ​

O cliente System é cacheado em variável de módulo (singleton por Deno isolate). Isso evita a criação de múltiplas conexões por request, reduzindo o consumo de pool de ~6x para ~1x por request.

Fire-and-Forget em Writes de Sucesso ​

No path de sucesso do login, registrarTentativa e registrarLog executam sem await, retornando a resposta ao aluno imediatamente.


Referências ​