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