35. Qualidade e a cadeia de testes
As doze categorias de teste que sustentam a plataforma, o que cada uma responde e como rodá-las.
A plataforma tem uma cadeia de testes em doze categorias, com um executor único. Ela vive em testes/ e roda contra um ambiente de pé:
A ordem não é estética. Unitário primeiro porque é o mais barato e o que localiza melhor: se a regra está errada na função, não adianta descobrir isso numa jornada de navegador quinze minutos depois. Aceitação vem perto do fim porque é o mais caro e o que menos informa sobre onde está o defeito. EHT vem depois dela porque é leitura — não derruba nada —, e failover fica por último porque é o único que deixa o ambiente diferente de como o encontrou por alguns segundos.
O que cada elo responde
Unitário — cada peça isolada faz o que promete. Backend em JUnit dentro de contêiner, para que a máquina não precise ter Java; frontend em Vitest.
Funcional — o sistema cumpre a regra de negócio, e não apenas responde. A diferença aparece na segregação: o endpoint responde com alegria, e a regra diz que quem respondeu não aprova. Um teste que só olha o status nunca descobre isso.
Regressão — o que já quebrou não voltou a quebrar. Cada caso corresponde a um defeito real e traz o sintoma que foi relatado, e não só a asserção. Um teste de regressão escrito por adivinhação cobre o que nunca falhou.
Integração — os módulos conversam nas costuras. É onde o sistema realmente falha: cada módulo passa no próprio teste e a resposta que vira achado, que vira risco, que aparece no painel, que abre o registro, é uma corrente de quatro elos que ninguém testa inteira por acidente.
Performance — velocidade e estabilidade sob concorrência. Mede percentil e não média: a média esconde a consulta que responde em 80ms quase sempre e em 4s uma vez a cada vinte, e é da quarta que o usuário lembra.
Segurança — o que a plataforma promete ser impossível é impossível. Não é varredura genérica: são as promessas deste produto, que é o que um atacante interno testaria primeiro — ver dado de outro tenant, alcançar endpoint sem permissão, forjar token, ler segredo pela API.
Usabilidade — o piso do que torna uma tela utilizável: teclado, rótulo associado, idioma declarado, ausência de rolagem horizontal no celular.
PWA — o aplicativo instala e sobrevive à perda de rede, e o cache recusa a API.
Aceitação — a jornada real fecha. "Carreguei o meu ROPA; onde vejo se estou conforme, o que falta e quem responde por isso?" Se essa jornada não fecha numa sessão, o resto não importa.
EHT — Engenharia de Homologação Técnica — o produto foi construído direito, e não só funciona. Um sistema inteiramente vulnerável passa em todos os testes funcionais. São quatro frentes:
• SAST, sobre o próprio código: segredo escrito no fonte, SQL montado com texto que pode vir de fora, verificação de certificado desligada, execução de processo do sistema, HTML cru na interface, pendência de segurança.
• Dependências com CVE: usa o relatório do OWASP Dependency-Check quando ele existe — daí saem CVSS, KEV e EPSS — e, quando não existe, confere offline as versões resolvidas no jar e o npm audit. E diz qual dos dois caminhos usou: um teste que passa sem base de dados e sem avisar é pior que não existir, porque produz confiança falsa.
Um achado tem três destinos possíveis, e a diferença entre eles é o que separa gestão de vulnerabilidade de teatro:
| Destino | Quando | Onde fica | |---|---|---| | corrigir | há versão corrigida na linha em uso | sobe a versão | | suprimir | é falso positivo demonstrável | supressoes-cve.xml, uma entrada por CVE, com o motivo | | registrar | é real e não tem correção na linha | riscos-conhecidos.json, com a análise de alcance e data de revisão |
Suprimir é a decisão de não olhar mais, então só entra o que se demonstra: o componente vulnerável não está no classpath, ou a versão presente já é posterior à corrigida e o casamento foi por CPE errada. O que é verdadeiro e apenas improvável vai para o registro, não para a supressão.
A suíte reprova quando aparece CVE alta fora do registro — é risco novo que ninguém olhou —, quando a data de revisão vence, e quando uma entrada do registro já não aparece na varredura, porque registro que só cresce deixa de ser lido. O relógio é o ponto: sem ele, "migramos depois" vira a decisão permanente que ninguém tomou.
• DAST, sobre a aplicação de pé: cabeçalhos, exposição de tecnologia, endpoint de gestão aberto, e resposta a entrada hostil sem vazar detalhe interno.
• Práticas: RLS ligada e forçada em toda tabela com tenant, gatilho de trilha, papel de conexão sem superusuário nem BYPASSRLS, esquema versionado.
Failover (HA) — o que acontece quando uma peça cai. Derruba o banco de verdade e verifica as três promessas que só aparecem sob falha: a saúde deixa de dizer UP em vez de fingir; a consulta responde erro em vez de lista vazia com 200 — zero num painel não parece defeito, parece boa notícia; e a aplicação volta sozinha, com a escrita confirmada antes da queda ainda lá. Não testa cluster: testa o comportamento do processo diante da perda da dependência crítica, que é a metade do problema que o código controla.
O que a cadeia não faz
Usabilidade não se prova por automação — quem prova é gente usando. O que está automatizado é o piso, não o teto. A aceitação cobre as jornadas conhecidas; jornada nova continua exigindo alguém do negócio olhando.
E nenhuma destas suítes substitui teste de penetração. Elas verificam as promessas específicas deste produto, não a superfície inteira.
Achados que a própria cadeia produziu
A suíte de segurança encontrou duas tabelas com tenant_id e sem Row-Level Security: iam.chave_de_acesso, que guarda credenciais de contas de serviço, e integration.indice_de_execucao, que revela quais fontes cada cliente monitora e desde quando. Corrigidas na migração V63.
A suíte de regressão fixou o trabalhador de serviço como público: ele nasceu devolvendo 401, e o registro falha em silêncio — o aplicativo simplesmente nunca se instalaria, sem erro na tela.
A suíte de segurança, ao ganhar o bloco da chave de integração, mostrou que nenhuma chave de serviço autenticava: a troca da chave por token lê iam.chave_de_acesso antes de haver tenant na sessão — o tenant é justamente o que a chave vai dizer —, e a política de leitura não casava com nada. Toda chamada com X-API-Key respondia 401 sem explicação. O login por senha já tinha a solução desde sempre: uma política válida só enquanto a janela grc.autenticando está aberta. A chave de serviço nunca ganhou a equivalente. Corrigido na migração V204.
A suíte de EHT, na primeira execução, reprovou seis vezes — e as seis eram defeitos dela, não do produto: contava visão como tabela sem RLS; cobrava gatilho de trilha da própria tabela de trilha, o que seria recursão; comparava f/f com o false/false que o psql imprime; media a versão do driver do PostgreSQL pelo número de flyway-database-postgresql; acusava concatenação de SQL num filtro montado só com literais; e, o mais instrutivo, acusava TODO pendente em dois comentários perfeitamente escritos, porque a busca sem distinção de caixa casava com a palavra portuguesa todo. Um verificador que acusa o certo é treinamento para ignorar verificador.