Testes¶
Esta seção descreve a estratégia de testes do Quorum (quorum-sec-scan, v0.8.3 — revisão 2026-07-04): o que já existe no código hoje (as-is), como executá-lo e o que é proposto como evolução. O Quorum é uma ferramenta de consensus security scanning CLI/Docker escrita em Go 1.26; portanto, partes clássicas de um plano de testes corporativo que pressupõem um frontend web, banco de dados relacional ou API REST são declaradas N/A com justificativa técnica. O princípio do produto "false split > false merge" e o axioma operacional "0 findings não é prova de segurança" também orientam as escolhas de teste: preferimos quebrar cedo (testes de contrato, -race, gates de consenso no E2E) a passar silenciosamente.
Documentos relacionados: Arquitetura · CI/CD · Cadeia de suprimentos · DESIGN.md (§5 testes de contrato, §6 matriz de correlação, §14 status dos scanners).
1. Visão geral da pirâmide de testes¶
O Quorum concentra peso na base (unitários + testes de contrato determinísticos sobre fixtures) e mantém um topo enxuto porém real (E2E com scanners reais). Não há camada de testes de UI porque não há UI. A base cresceu junto com o produto: os 12 scanners (trivy, grype, checkov, kics, dockle, kubescape, polaris, kube-score, terrascan, tfsec, regula, conftest) têm cada um sua própria fixture e teste de contrato, e o consenso agora é comprovado sobre dados reais em SCA, IaC e K8s (crosswalk AWS/Azure/GCP/k8s).
flowchart TB
E2E["E2E real (e2e.yml)<br/>scanners reais → consenso cross-engine<br/>alpine:3.10 + examples/terraform"]
INT["Integração / pipeline<br/>parse real → correlate → crosswalk → consensus<br/>(realdata_test.go, pipeline_test.go, crosswalk_test.go)"]
UNIT["Unitários + Contrato (12 adapters)<br/>adapters, orchestrator, alias, cache, consensus,<br/>crosswalk, filter, severity, purl, model, report, metrics, cmd"]
UNIT --> INT --> E2E
style UNIT fill:#dff0d8,stroke:#3c763d
style INT fill:#fcf8e3,stroke:#8a6d3b
style E2E fill:#d9edf7,stroke:#31708f
| Camada | Onde vive | Determinístico? | Roda em | Gate de PR |
|---|---|---|---|---|
| Unitário | internal/**/<pkg>_test.go, cmd/quorum/scan_test.go |
Sim | go test -race -covermode=atomic ./... (ci.yml) |
Sim |
| Contrato (parsers, 12 adapters) | internal/adapter/adapter_test.go + realdata_test.go vs internal/adapter/testdata/*.json |
Sim (fixtures versionadas) | go test ./... |
Sim |
| Integração (pipeline + crosswalk) | internal/adapter/realdata_test.go, internal/correlate/pipeline_test.go, internal/crosswalk/crosswalk_test.go |
Sim (fixtures + crosswalk de repo real) | go test ./... |
Sim |
| Evals da camada advisory | internal/evals/ (casos golden → cobertura de remediação, relevância de referência OWASP, taxa de verify-the-fix) |
Sim (sem modelo — fakes + pack/corpus reais) | go test ./... |
Sim |
| E2E (consenso real) | .github/workflows/e2e.yml |
Não (rede/DBs externos) | GitHub Actions | Sim (push/PR/manual) |
2. Executando localmente¶
Alvos relevantes do Makefile:
make test # go test ./... → unit + contrato + integração
make vet # go vet ./...
make all # vet + test + build
make build # binário em ./dist/quorum (CGO_ENABLED=0, -trimpath)
make run ARGS="scan <target> --type image --scanners trivy,grype"
Variações úteis (não encapsuladas em um alvo, mas suportadas):
go test -race ./... # race detector (igual à CI)
go test ./internal/adapter/... # os 12 testes de contrato dos adapters
go test -run TestMVPConsensus ./internal/correlate
go test -run TestShippedCrosswalkResolves ./internal/crosswalk # trava o crosswalk do repo
go test -cover ./... # cobertura por pacote (resumo)
go test -race -covermode=atomic -coverprofile=coverage.out ./... && go tool cover -func=coverage.out
A CI usa exatamente
go test -race -covermode=atomic -coverprofile=coverage.out ./...(veja CI/CD), então rodar com-racelocalmente reproduz o gate. Omake testusago test ./...(sem-race/cobertura); ambos são válidos, mas a CI é a referência.
3. Testes unitários (atual)¶
Cobrem lógica pura e regras de negócio, sem dependências externas (sem rede, sem binários de scanner — exceto os testes de contrato, que leem fixtures do disco).
| Pacote | Arquivo | O que verifica |
|---|---|---|
internal/severity |
severity_test.go |
FromCVSS (faixas → severidade), FromDockle (FATAL/WARN/INFO), Max, AtLeast, Parse. |
internal/model |
model_test.go |
Ordenação de rank de severidade (TestSeverityRankOrdering). |
internal/purl |
purl_test.go |
Build, NameVersion, Name — normalização de Package URLs usadas na chave de correlação de SCA. |
internal/filter |
filter_test.go |
--min-severity (Apply), baseline .quorumignore (match por fingerprint e por correlationKey), baseline ausente. |
internal/cache |
store_test.go |
Put/Get/persistência em disco (cria diretório pai, perm 0600), reabertura, segurança em *Store nil e modo em memória (caminho vazio). |
internal/alias |
alias_test.go |
Cliente OSV via httptest (aliases, erro 500, retry em 503, sem retry em 404, rejeição de id malformado), preferência por aliases locais (zero rede), cache após 1ª resolução, degradação graciosa em falha de rede. |
internal/orchestrator |
orchestrator_test.go |
Fan-out e merge cross-scanner, status ran/unavailable/error, scanner desconhecido descartado com aviso, timeout de probe (ProbeTime) marcando unavailable, ScannerRun serializando duração em ms. |
internal/consensus |
consensus_test.go |
Merge agrupa por chave e conta scanners distintos (TestMergeGroupsAndCountsDistinctScanners); diversidade de engines eleva a confiança (TestMergeDiversityRaisesConfidence). |
internal/report |
report_test.go |
SARIF (schema 2.1.0, partialFingerprints, level, detectionCount), JSON (summary.totalFindings/multiDetected), XML (quorumReport), ParseFormat (sarif/json/xml case-insensitive, rejeita pdf). |
internal/report |
metrics_test.go |
WriteMetrics (flag --metrics, textfile Prometheus): quorum_scan_duration_seconds, quorum_scanner_up{scanner,status}, quorum_scanner_findings, quorum_findings_after_consensus, quorum_findings_total{severity}, quorum_multi_detected, e cabeçalhos # TYPE. |
internal/adapter |
adapter_test.go |
Infra comum dos adapters: splitArgs (parsing de args entre aspas), capWriter/maxOutputBytes (cap de DoS QUORUM_MAX_OUTPUT_BYTES), redactSecretText (redação de segredos tipo AKIA…), extraArgs (passthrough QUORUM_<SCANNER>_ARGS, ex. QUORUM_CHECKOV_ARGS). |
cmd/quorum |
scan_test.go |
validateTargetRef (recusa targets iniciados por -, injeção de argumento), checkTargetSize (cap QUORUM_MAX_TARGET_BYTES, imagens ignoradas), emit (saída com filepath.Clean), resolveCrosswalkDir (flag/default/fallback /opt/quorum/crosswalk), isDir. |
Pontos de atenção (boas práticas já adotadas no código):
- Casos table-driven (
TestFromCVSS,TestRealParse_Counts,TestSplitArgs). t.TempDir()para isolamento de FS (cache, baseline, crosswalk, output).t.Setenvpara exercitar knobs de ambiente (QUORUM_MAX_OUTPUT_BYTES,QUORUM_MAX_TARGET_BYTES,QUORUM_*_ARGS) sem vazar estado entre testes.httptest.NewServerpara isolar a dependência de rede do OSV.dev — nenhum teste unitário fala com a internet real.- Subtests
t.Runcomt.Skipf/t.Skipquando o ambiente não satisfaz a pré-condição (ex.bundledCrosswalkDirsó existe na imagem Docker).
4. Testes de contrato dos adapters (atual)¶
São a guarda de formato: eles fazem parse de saídas reais e versionadas de cada scanner e quebram antes da produção quando uma ferramenta muda seu JSON (DESIGN §5). Vivem em internal/adapter/ e leem fixtures de internal/adapter/testdata/. Há um teste de contrato por scanner — os 12 adapters (trivy, grype, checkov, kics, dockle, kubescape, polaris, kube-score, terrascan, tfsec, regula, conftest).
Fixtures presentes (internal/adapter/testdata/):
├── trivy_image.json grype_image.json
├── sca_trivy_alpine.json sca_grype_alpine.json
├── iac_trivy.json iac_trivy_v071.json
├── iac_checkov.json iac_kics.json
├── iac_tfsec.json iac_terrascan.json
├── iac_regula.json policy_conftest.json
├── k8s_polaris.json k8s_kubescore.json
└── img_dockle_alpine.json
Garantias travadas pelos testes de contrato:
flowchart LR
F["Fixture JSON<br/>(saída real do scanner)"] --> P["adapter.parse()"]
P --> C["model.Finding canônico"]
C --> A{"asserções"}
A --> A1["contagem de findings + FindingType"]
A --> A2["VulnID / PURL / Severity / CVSS"]
A --> A3["Confirmed (autoritativo)"]
A --> A4["CanonicalControl (AVD / CIS / C-####)"]
A --> A5["Aliases (CVE/GHSA)"]
TestRealParse_Counts trava as contagens exatas e o FindingType por fixture (invariantes: Scanner/Title não vazios):
| Scanner | Fixture | Findings | Tipo |
|---|---|---|---|
| trivy (IaC) | iac_trivy.json |
12 | MISCONFIG |
| checkov (IaC) | iac_checkov.json |
17 | MISCONFIG |
| kics (IaC) | iac_kics.json |
9 | MISCONFIG |
| trivy (SCA) | sca_trivy_alpine.json |
1 | VULN |
| grype (SCA) | sca_grype_alpine.json |
1 | VULN |
| polaris (K8s) | k8s_polaris.json |
3 | K8S_POSTURE |
| kube-score (K8s) | k8s_kubescore.json |
2 | K8S_POSTURE |
| tfsec (IaC) | iac_tfsec.json |
2 | MISCONFIG |
| terrascan (IaC) | iac_terrascan.json |
2 | MISCONFIG |
| regula (IaC) | iac_regula.json |
2 | MISCONFIG |
| conftest (policy) | policy_conftest.json |
2 | MISCONFIG |
Casos notáveis (e por que existem):
TestTrivyParse/TestGrypeParse: mapeamento dos campos canônicos (PURL, severity, CVSS,Confirmed), Trivy repassando AVD (AVD-AWS-0086) e fallback de versão do Grype para odescriptor.TestTrivyV071AVDPrefix: trava a correção de drift — o Trivy ≥ ~0.60 removeu o campoAVDIDe emite ids "AWS-0086" sem prefixo; o adapter restauraAVD-AWS-0086(fixtureiac_trivy_v071.json, 9 findings, todosAVD-AWS-0…) para bater com o crosswalk.TestTfsecDerivesAVD: findings do tfsec que carregam um link AVD recebemCanonicalControl= aquele id (AVD-AWS-0089), habilitando correlação direta com o Trivy (que fala AVD nativo); findings sem link AVD mantêmCanonicalControlvazio.TestDockleParse: linhas PASS/SKIP/IGNORE são descartadas; FATAL/WARN/INFO viram findingsImgHardening; códigosCIS-DI-*já canônicos; mapeamento WARN→MEDIUM, INFO→LOW (fixturealpine:3.10, 3 findings).
Manutenção: ao subir a versão suportada de um scanner, capture uma nova fixture (rodando o scanner via imagem/binário oficial contra
examples/terraform,examples/k8soualpine:3.10), adicione-a aotestdata/e trave a contagem esperada emTestRealParse_Counts— exatamente o padrão doiac_trivy_v071.json.
5. Testes de integração / pipeline e consenso (atual)¶
Diferem dos unitários por exercitar o caminho de dados real end-to-end (sem rede): parse → correlate.Enrich → crosswalk → consensus.Merge, usando o crosswalk real do repositório (../../crosswalk: aws.yaml, azure.yaml, gcp.yaml, k8s.yaml). São a prova determinística de que o consenso acontece sobre dados reais.
5.1 Pipeline de SCA e regras de merge (correlate/pipeline_test.go)¶
| Teste | Invariante de produto |
|---|---|
TestMVPConsensus |
Trivy(CVE) + Grype(GHSA) → após resolução de aliases, 1 finding, detectionCount=2, confidence∈(0,1], severidade CRITICAL. Critério de "done" do MVP. |
TestUnmappedNeverMerges |
Misconfigs sem controle canônico resolvido são marcados Unmapped e não fazem merge (false split > false merge). |
TestCrosswalkMerges |
Com uma regra de crosswalk mapeando Checkov + Trivy ao mesmo AVD-AWS-0091, eles fazem merge (detectionCount=2). Exercita o loader YAML real via crosswalk.Load. |
5.2 Consenso sobre dados reais (adapter/realdata_test.go)¶
Fazem parse de saídas reais e as conduzem pelo pipeline + crosswalk + consensus. Cobrem SCA, IaC e as três novas famílias de correlação (IaC multi-engine, K8s, terrascan↔trivy).
| Teste | Invariante de produto |
|---|---|
TestRealSCAConsensus |
Trivy+Grype concordam em CVE-2021-36159 (apk-tools) → 1 finding, detectionCount=2, severidade CRITICAL. |
TestRealIaCConsensus |
Trivy+Checkov+KICS sobre o mesmo Terraform: consenso 3-way em AVD-AWS-0090/0089/0092/0057; 2-way em AVD-AWS-0132 (só Trivy+Checkov o cobrem). |
TestK8sCrosswalkConsensus |
Kubescape (C-0016, nível workload) + Polaris (privilegeEscalationAllowed, nível container) para o mesmo conceito → merge via crosswalk k8s + chave em nível de objeto, detectionCount=2. |
TestK8sThreeWayConsensus |
Kubescape (C-0057) + Polaris (runAsPrivileged) + kube-score (container-security-context-privileged) — "container privilegiado" → detectionCount=3. |
TestTerrascanCrosswalkConsensus |
terrascan AC_AWS_0210 (ACL pública) faz crosswalk para AVD-AWS-0092 e merge com o AVD nativo do Trivy no mesmo bucket S3 → detectionCount=2. Trava o mapeamento terrascan→AVD. |
Por que o hub é AVD (cloud) e C-#### (k8s): o crosswalk é DERIVADO da saída real e escolhe o hub que minimiza false merge. tfsec e trivy já emitem AVD e auto-correlacionam; o kubescape ancora as regras k8s em
C-####, e polaris/kube-score convergem para ele. RBAC permanece single-engine (RBAC do kubescape requer contexto de cluster) — documentado, não testado em consenso.
5.3 Carga e resolução do crosswalk (crosswalk/crosswalk_test.go)¶
O pacote internal/crosswalk agora tem testes dedicados que exercitam o loader e travam os dados do repositório:
| Teste | O que garante |
|---|---|
TestLoadAndResolve |
Loader de documento list; arquivos não-.yaml ignorados; scanner case-insensitive, id de regra verbatim; regra desconhecida não resolve (nunca "adivinha"). |
TestLoadVersionedDocument |
Loader do documento versionado (schemaVersion: 1 + controls:). |
TestShippedCrosswalkResolves |
Carrega o crosswalk real (../../crosswalk) e checa 16 mapeamentos multi-cloud representativos — AWS (RDS/KMS/CloudTrail/VPC flow logs), Azure (secure transfer, min TLS, purge protection do Key Vault), GCP (uniform access, RDP, SSL do Cloud SQL) e k8s (privileged, host network, host PID/IPC, secrets). Uma edição ruim no crosswalk quebra o teste. |
TestLoadMissingDirIsEmptyNotError |
Diretório ausente → crosswalk vazio, sem erro (degradação graciosa). |
Esses testes, junto do E2E (§6), fecham o ciclo: consenso comprovado deterministicamente (fixtures + crosswalk do repo) e não-deterministicamente com binários ao vivo.
6. Testes E2E (atual)¶
.github/workflows/e2e.yml — "Prova end-to-end de que o consenso realmente acontece com scanners REAIS (não fixtures)". Dispara em push para main, em pull_request e em workflow_dispatch.
Fluxo:
flowchart TB
B["Build quorum (go build)"] --> I["Instala scanners reais<br/>trivy 0.71.2, grype 0.114.0, checkov (pipx)"]
I --> V["Verifica versões + grype db update + docker pull alpine:3.10 + quorum list-scanners"]
V --> IAC["IaC: quorum scan examples/terraform<br/>--scanners trivy,checkov --offline"]
V --> SCA["SCA: quorum scan alpine:3.10<br/>--scanners trivy,grype --offline"]
IAC --> GI{"summary.multiDetected >= 1 ?"}
SCA --> GS{"summary.multiDetected >= 1 ?"}
GI -- não --> FAIL["::error:: sem consenso IaC → falha"]
GS -- não --> FAIL2["::error:: sem consenso SCA → falha"]
GI -- sim --> OK
GS -- sim --> OK["upload iac.json + sca.json"]
Características-chave:
- Gate de consenso explícito: o job falha se
jq '.summary.multiDetected'for< 1em qualquer cenário — ou seja, falha se nenhum finding for corroborado por dois engines. Protege contra regressões silenciosas na correlação. - Mesma imagem local para ambos os scanners (
docker pull alpine:3.10) para garantir que Trivy e Grype resolvam o mesmo artefato. grype db updateroda cedo para falhar caso o DB de vulnerabilidades não possa ser obtido.--offlinedesliga o OSV.dev nos cenários (a resolução depende de aliases locais), reduzindo flakiness de rede.- Relatórios
iac.json/sca.jsonsão publicados como artefatos (if: always()).
Escopo do E2E vs. fixtures: o E2E ao vivo exercita 3 engines (trivy+checkov em IaC, trivy+grype em SCA) — os outros 9 scanners e os cenários K8s/multi-cloud são cobertos deterministicamente pelos testes de contrato/consenso (§4–§5). Isso mantém o E2E rápido e estável enquanto as fixtures garantem amplitude de cobertura.
N/A — DAST de aplicação: não há servidor/endpoint a atacar; o "E2E" aqui é um pipeline de scanning, não uma aplicação web.
7. Testes de carga, stress e caos (proposto)¶
Não existem hoje. Sendo uma CLI batch (sem servidor de longa duração), as métricas relevantes são throughput de scan e degradação sob recursos escassos, não RPS.
7.1 Carga (proposto)¶
- Objetivo: medir tempo total e uso de memória ao escanear targets grandes (monorepos, imagens com muitos pacotes) com o pool completo de 12 scanners em fan-out.
- Como: uma matriz de targets sintéticos crescentes + medição de
--timeout, picos de RSS e wall time; assertar regressão contra um baseline. Já existem guardrails de DoS no código (QUORUM_MAX_OUTPUT_BYTES=512MiB,QUORUM_MAX_TARGET_BYTES=20GiB) para exercitar nos extremos. - Tooling sugerido:
hyperfinepara wall-clock,/usr/bin/time -vpara RSS, mais um harness Go opcional (go test -bench) para o pipelinecorrelate/consensus.
7.2 Stress (proposto)¶
- Objetivo: comportamento quando um scanner é morto por OOM ou excede
--timeout/ProbeTime. O orchestrator já distinguetimeout/killed(OOM)/unavailable; um teste de stress confirmaria isso ao vivo. - Como: rodar em um container com
--memorybaixo e targets pesados; assertar que o status por scanner é reportado corretamente (e exportado via--metrics, gaugequorum_scanner_up{status}) e que o relatório nunca afirma "0 findings = seguro".
7.3 Caos (proposto)¶
- Objetivo: robustez a dependências instáveis: OSV.dev intermitente/lento, registry inacessível, DB do Grype ausente, fixture/crosswalk corrompido.
- Como: injeção de falhas (proxy retornando 5xx/latência para OSV, bloqueio de rede, crosswalk inválido) e verificação de degradação graciosa (já coberta em nível unitário em
alias_test.goecrosswalk_test.go, faltando o equivalente E2E). - Tooling sugerido:
toxiproxypara a rede; jobs "chaos" dedicados no Actions.
8. Testes de segurança (SAST/DAST/IAST/SCA/Container/IaC)¶
O Quorum faz dogfooding: sendo uma ferramenta de segurança, ele usa a si mesmo (e seus próprios scanners OSS) para cobrir partes desta matriz. Os exemplos examples/terraform, examples/k8s e examples/ci servem tanto como fixtures quanto como alvos de dogfooding.
| Disciplina | Status | Como hoje / proposta | Tooling |
|---|---|---|---|
| SCA (dependências) | Parcial via dogfooding | O E2E escaneia alpine:3.10 com trivy+grype; propor quorum scan . sobre o próprio repo Go + govulncheck na CI |
Trivy, Grype, (propor) govulncheck |
| IaC scan | Atual via dogfooding | O E2E escaneia examples/terraform com trivy+checkov (consenso); tfsec/terrascan/regula/kics cobertos nos testes de contrato/consenso |
Trivy, Checkov, KICS, tfsec, terrascan, regula |
| K8s posture | Atual (fixtures) | consenso kubescape×polaris×kube-score via o crosswalk k8s (realdata_test.go); propor dogfooding sobre examples/k8s no E2E |
kubescape, polaris, kube-score |
| Policy-as-code | Atual (fixture) | conftest roda o Rego do usuário (./policy); teste de contrato em policy_conftest.json |
conftest (OPA/Rego) |
| Container scan | Atual via dogfooding | E2E sobre alpine:3.10; propor escanear as próprias imagens :full/:slim do projeto no release.yml |
Trivy, Grype, Dockle |
| SAST | Proposto | go vet já roda na CI; propor golangci-lint + gosec + CodeQL (Go) |
golangci-lint, gosec, CodeQL |
| DAST | N/A | Não há superfície de runtime (sem API/servidor/web) a atacar dinamicamente | — |
| IAST | N/A | IAST exige instrumentar uma app rodando servindo requisições; o modelo de CLI batch não se aplica | — |
| Cadeia de suprimentos / proveniência | Atual (endurecido) | Imagens e binários assinados keyless (cosign OIDC, com retry) + atestação SLSA de build-provenance e SBOM SPDX atestado (actions/attest-sbom) por imagem e por binário (GoReleaser/syft), bases pinadas por sha256, scanners embarcados verificados por checksum. O knowledge pack + crosswalk também recebem uma atestação SLSA de build-provenance a cada release (release.yml job knowledge; verifique com gh attestation verify knowledge/owasp/corpus.yaml). A GitHub Action composta cosign-verifica :full e auto-monta /var/run/docker.sock; a tag v0 móvel é avançada por tag-major.yml a cada release semver |
cosign, actions/attest-build-provenance, actions/attest-sbom, GoReleaser/syft, gh attestation |
| Secret scanning | Parcial | O código já redige segredos nos findings (redactSecretText, testado); não há secret scan dedicado no repo — propor gitleaks na CI |
(interno) + (propor) gitleaks |
Dogfooding como teste de segurança: rodar
quorum scancontra os próprios artefatos do projeto (repo, Terraform/K8s de exemplo, imagens publicadas) é simultaneamente um teste funcional e o próprio scan de segurança IaC/K8s/Container/SCA do Quorum.
Checklist de hardening de segurança na CI (proposta):
- [ ] Adicionar
golangci-lint+gosecaoci.yml. - [ ] Habilitar CodeQL (Go) em um workflow dedicado.
- [ ] Job rodando
quorum scan .(dogfooding SCA/IaC) com--fail-on high. - [ ] Escanear as imagens
:full/:slimrecém-construídas norelease.ymlantes do push. - [ ]
govulncheck ./...como gate. - [ ]
gitleakspara segredos no histórico do repo.
9. Testes de API / CLI¶
N/A — API REST/HTTP: o Quorum não expõe API HTTP, então não há testes de contrato de API, OpenAPI ou fuzzing de endpoint.
CLI (a verdadeira "API" do produto): parcialmente coberta, com lacunas claras.
| Aspecto da CLI | Status | Onde |
|---|---|---|
Validação de target (recusa -…, injeção de argumento) |
Atual | cmd/quorum/scan_test.go (validateTargetRef) |
Cap de tamanho de target (QUORUM_MAX_TARGET_BYTES) |
Atual | cmd/quorum/scan_test.go (checkTargetSize) |
--output com filepath.Clean (path traversal) |
Atual | cmd/quorum/scan_test.go (emit) |
Resolução de --crosswalk (flag/default/fallback) |
Atual | cmd/quorum/scan_test.go (resolveCrosswalkDir) |
Passthrough por scanner (QUORUM_<SCANNER>_ARGS) |
Atual | internal/adapter/adapter_test.go (extraArgs, splitArgs) |
--metrics (textfile Prometheus) |
Atual | internal/report/metrics_test.go (WriteMetrics) |
list-scanners (smoke) |
Atual | ci.yml (Smoke) e e2e.yml (Verify) |
scan end-to-end real (--scanners/--format/--output/--offline) |
Atual (E2E) | e2e.yml |
--log-format text\|json, --quiet, --baseline, --cache |
Lacuna | propor cobertura dedicada |
Parsing/validação de flags (--type, --format, --fail-on, --min-severity, --timeout) |
Lacuna | propor table tests em cmd/quorum |
Exit codes (0 ok / 1 gate --fail-on / 2 erro de uso/runtime) |
Lacuna | propor testes E2E assertando $? por cenário |
Flags advisory (--advice, --advice-provider, --fix) e advise-index |
Lacuna | propor table tests para parsing/gating de flags (--offline bloqueia remote, remote recusa --fix) |
Proposta de teste de exit-code (acionável em E2E):
# 0 = nenhum finding atingiu o --fail-on
quorum scan examples/terraform --type repo --min-severity critical --fail-on critical; test $? -eq 0
# 1 = gate disparou
quorum scan alpine:3.10 --type image --fail-on low; test $? -eq 1
# 2 = uso inválido
quorum scan; test $? -eq 2
10. Performance¶
Não há benchmarks no código hoje. Candidatos a hot-spot para testing.B:
consensus.Mergeecorrelate.Enrich(agrupamento porcorrelationKey, hashing de fingerprint) sobre grandes conjuntos de findings vindos de até 12 scanners.crosswalk.Resolvesobre a tabela combinada AWS/Azure/GCP/k8s.- Geração de relatório SARIF/JSON/XML e escrita de métricas em alto volume.
- Fan-out do orchestrator (overhead de goroutine/timeout) com o pool completo.
Proposta:
go test -bench=. -benchmem ./internal/consensus/... ./internal/correlate/... ./internal/crosswalk/... ./internal/report/...
Métricas-alvo a estabelecer como baseline: tempo do merge-pipeline para N findings (ex. 1k, 10k), allocations/op e wall-clock de um scan :full completo (12 scanners) contra um target de referência.
11. Cobertura¶
11.1 Como é medida (atual)¶
A cobertura agora é coletada na CI (ci.yml): o passo de teste roda go test -race -covermode=atomic -coverprofile=coverage.out ./..., um passo "Coverage summary" imprime o total (go tool cover -func → última linha) no $GITHUB_STEP_SUMMARY, e coverage.out é publicado como artefato (actions/upload-artifact). Localmente:
go test -covermode=atomic -coverprofile=coverage.out ./...
go tool cover -func=coverage.out # resumo por função + total
go tool cover -html=coverage.out -o cover.html
go test -covermode=atomic -coverpkg=./... ./... # cobertura cross-package
11.2 Estado real por pacote¶
Todo pacote de produto agora tem um _test.go dedicado — os antigos buracos de cobertura direta (consensus, crosswalk, purl, model) foram fechados.
| Pacote | Tem _test.go? |
|---|---|
internal/adapter |
Sim (12 contrato + realdata + infra) |
internal/orchestrator |
Sim |
internal/correlate (pipeline) |
Sim |
internal/consensus |
Sim (consensus_test.go) |
internal/crosswalk |
Sim (crosswalk_test.go) |
internal/alias |
Sim |
internal/cache |
Sim |
internal/filter |
Sim |
internal/severity |
Sim |
internal/purl |
Sim (purl_test.go) |
internal/model |
Sim (model_test.go) |
internal/report |
Sim (report_test.go + metrics_test.go) |
internal/evals |
Sim (evals_test.go — casos golden da camada advisory) |
cmd/quorum |
Sim (target/size/output/crosswalk) |
11.3 Metas (proposto)¶
- Meta global: ≥ 80% de cobertura de linhas (
-coverpkg=./...). - Componentes críticos (
correlate,consensus,crosswalk,severity,report): ≥ 90% — são o núcleo do produto. - Gate de não-regressão: a coleta já existe; o que falta é adicionar um threshold (falhar a CI se a cobertura total cair abaixo do baseline).
Checklist de cobertura:
- [x] Coletar
-coverprofilenoci.ymle publicar o artefato (coverage.out). - [x] Imprimir o total no resumo do job.
- [ ] Definir um threshold mínimo global e por pacote crítico (gate de não-regressão).
- [ ] Publicar uma tendência histórica de cobertura (badge/serviço externo).
12. Matriz mestra: tipo → status → tooling¶
| Tipo de teste | Status | Onde / como | Tooling |
|---|---|---|---|
| Unitário | Atual | internal/**/*_test.go, cmd/quorum/scan_test.go |
go test -race -covermode=atomic |
| Contrato (formato de scanner, 12 adapters) | Atual | internal/adapter/*_test.go + testdata/*.json |
go test, fixtures versionadas |
| Integração (pipeline) | Atual | realdata_test.go, pipeline_test.go |
go test |
| Consenso / crosswalk (AWS/Azure/GCP/k8s) | Atual | realdata_test.go, crosswalk_test.go (crosswalk real) |
go test |
| Evals da camada advisory | Atual | internal/evals/evals_test.go (cobertura de remediação, relevância de ref OWASP, taxa de verify-the-fix) |
go test, pack/corpus reais |
| E2E (consenso real) | Atual | .github/workflows/e2e.yml |
trivy, grype, checkov, jq, Actions |
| Smoke (CLI) | Atual | ci.yml / e2e.yml (list-scanners) |
binário compilado |
| Race detection | Atual | ci.yml (go test -race) |
Go race detector |
| Estático (vet) | Atual | ci.yml (go vet) |
go vet |
| Cobertura | Atual | coletada no ci.yml (resumo + artefato); threshold §11.3 proposto |
go tool cover |
| Métricas (Prometheus) | Atual | internal/report/metrics_test.go (--metrics) |
go test |
| Performance / Benchmark | Proposto | consensus/correlate/crosswalk/report |
go test -bench, hyperfine |
| Carga | Proposto | targets grandes, fan-out de 12 scanners | hyperfine, time -v |
| Stress (OOM/timeout) | Proposto | container com --memory baixo |
limites do Docker |
| Caos (deps instáveis) | Proposto | OSV 5xx/latência, registry off | toxiproxy |
| SAST | Proposto | lint/sec estático do código Go | golangci-lint, gosec, CodeQL |
| SCA | Parcial / dogfooding | E2E alpine:3.10; propor self-scan + govulncheck |
Trivy, Grype, govulncheck |
| IaC scan | Atual / dogfooding | E2E examples/terraform; fixtures para os 6 engines IaC |
Trivy, Checkov, KICS, tfsec, terrascan, regula |
| K8s posture | Atual / fixtures | consenso 2/3-way realdata_test.go |
kubescape, polaris, kube-score |
| Policy-as-code | Atual / fixture | teste de contrato policy_conftest.json |
conftest (Rego) |
| Container scan | Atual / dogfooding | E2E alpine:3.10; propor self-scan das imagens |
Trivy, Grype, Dockle |
| DAST | N/A | sem superfície de runtime | — |
| IAST | N/A | sem app rodando instrumentável | — |
| API REST testing | N/A | sem API HTTP | — |
| Cadeia de suprimentos / proveniência | Atual | atestação SLSA + SBOM SPDX re-verificada; knowledge pack/crosswalk atestados; Action cosign-verifica :full; checksums de scanner |
cosign, atestação SLSA/SBOM, gh attestation |
| Secret scanning | Parcial | redação interna testada; scan de repo proposto | (interno), (propor) gitleaks |
| Exit codes (CLI) | Proposto | assertar $? por cenário em E2E |
shell + Actions |
13. Backlog priorizado de testes (acionável)¶
- [ ] P0 — Definir um threshold de cobertura no
ci.yml(gate de não-regressão sobre ocoverage.outjá coletado, §11). - [ ] P0 — Testes de exit-code (0/1/2) em E2E, cobrindo
--fail-on(§9). - [ ] P1 — SAST na CI:
golangci-lint+gosec+ CodeQL (§8). - [ ] P1 — Dogfooding na CI:
quorum scan .e self-scan das imagens publicadas; incluirexamples/k8s(§8). - [ ] P1 —
govulncheck ./...como gate (§8). - [ ] P2 — Cobrir parsing de flags (
--type/--format/--fail-on/--min-severity/--timeout) e--log-formatemcmd/quorum(§9). - [ ] P2 — Benchmarks de
consensus/correlate/crosswalk/report(§10). - [ ] P3 — Caos de rede para o OSV.dev via
toxiproxy(§7.3). - [ ] P3 — Stress de OOM/timeout em container com memória limitada (§7.2).
Premissas¶
- As-is verificado no código: as afirmações "Atual" foram checadas lendo
Makefile,.github/workflows/ci.yml,.github/workflows/e2e.ymle os arquivos*_test.godeinternal/adapter(adapter_test.go,realdata_test.go),internal/correlate/pipeline_test.go,internal/crosswalk/crosswalk_test.go,internal/evals/evals_test.go,internal/report/metrics_test.goecmd/quorum/scan_test.go. "Proposto" indica explicitamente o que não existe hoje. - Versão do produto: v0.8.3 (revisão 2026-07-04); Go 1.26 (conforme
go.mode os workflows que pinamgo-version: "1.26"). - 12 scanners cada um com teste de contrato e fixture: trivy, grype, checkov, kics, dockle, kubescape, polaris, kube-score, terrascan, tfsec, regula, conftest.
-race+ cobertura na CI: oci.ymlusago test -race -covermode=atomic -coverprofile=coverage.out ./...; oMakefile testusago test ./...(sem-race/cobertura). Ambos são válidos; a CI é a referência.- A cobertura é coletada hoje (resumo + artefato
coverage.out); o que ainda não existe é um threshold/gate de não-regressão e as metas numéricas (80%/90%), que permanecem propostas. - A camada advisory é opt-in e apenas de apresentação: o harness
internal/evalspontua os caminhos advisory determinísticos (cobertura de remediação da Fase 0, relevância de referência OWASP da Fase 2) mais a taxa de verify-the-fix, usando fakes + o pack/corpus reais — sem modelo pesado na CI. Sem--advice, a saída é byte-idêntica, então os testes acima permanecem inalterados. - O E2E não é determinístico: depende dos releases dos scanners (trivy 0.71.2, grype 0.114.0, checkov via pipx), do DB do Grype e da imagem
alpine:3.10; mudanças upstream podem afetar as contagens. Por isso os gates de E2E checam apenasmultiDetected >= 1, não contagens exatas (essas ficam nos testes de contrato determinísticos). O E2E ao vivo cobre 3 engines; o resto e os cenários K8s/multi-cloud são cobertos por fixtures. - DAST/IAST/API REST/web/DB relacional = N/A porque o Quorum é uma ferramenta CLI/Docker batch, sem servidor, API HTTP, frontend, banco de dados relacional ou autenticação. A camada advisory de IA/LLM é opt-in e desligada por padrão; quando desabilitada o produto permanece livre de IA, então os testes determinísticos não carregam dependência de LLM.
- Dogfooding pressupõe que
examples/terraform,examples/k8seexamples/cipermaneçam alvos de scan válidos no repositório; o conftest pressupõe um bundle Rego em./policyfornecido pelo usuário.
Lacunas conhecidas¶
- O conteúdo de cada fixture JSON individual não foi inspecionado além do que os testes asseguram; as contagens citadas vêm das asserções (
TestRealParse_Countse os testes de consenso). - Números reais de cobertura (percentuais) não são citados neste documento — o valor total aparece no resumo do job de CI a cada execução, mas não há um baseline versionado fixo.
- Os cenários de consenso K8s (
TestK8sCrosswalkConsensus,TestK8sThreeWayConsensus,TestTerrascanCrosswalkConsensus) usam findings construídos em código (não fixtures de arquivo) para os engines não parseados naquele caso; a captura real por scanner de K8s vive nas fixturesk8s_polaris.json/k8s_kubescore.jsondeTestRealParse_Counts. - RBAC (kubescape) permanece single-engine porque requer contexto de cluster; não há teste de consenso para essa família — é uma decisão arquitetural documentada, não uma lacuna de teste.