Ir para o conteúdo

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 -race localmente reproduz o gate. O make test usa go 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.Setenv para exercitar knobs de ambiente (QUORUM_MAX_OUTPUT_BYTES, QUORUM_MAX_TARGET_BYTES, QUORUM_*_ARGS) sem vazar estado entre testes.
  • httptest.NewServer para isolar a dependência de rede do OSV.dev — nenhum teste unitário fala com a internet real.
  • Subtests t.Run com t.Skipf/t.Skip quando o ambiente não satisfaz a pré-condição (ex. bundledCrosswalkDir só 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 o descriptor.
  • TestTrivyV071AVDPrefix: trava a correção de drift — o Trivy ≥ ~0.60 removeu o campo AVDID e emite ids "AWS-0086" sem prefixo; o adapter restaura AVD-AWS-0086 (fixture iac_trivy_v071.json, 9 findings, todos AVD-AWS-0…) para bater com o crosswalk.
  • TestTfsecDerivesAVD: findings do tfsec que carregam um link AVD recebem CanonicalControl = aquele id (AVD-AWS-0089), habilitando correlação direta com o Trivy (que fala AVD nativo); findings sem link AVD mantêm CanonicalControl vazio.
  • TestDockleParse: linhas PASS/SKIP/IGNORE são descartadas; FATAL/WARN/INFO viram findings ImgHardening; códigos CIS-DI-* já canônicos; mapeamento WARN→MEDIUM, INFO→LOW (fixture alpine: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/k8s ou alpine:3.10), adicione-a ao testdata/ e trave a contagem esperada em TestRealParse_Counts — exatamente o padrão do iac_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 < 1 em 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 update roda cedo para falhar caso o DB de vulnerabilidades não possa ser obtido.
  • --offline desliga o OSV.dev nos cenários (a resolução depende de aliases locais), reduzindo flakiness de rede.
  • Relatórios iac.json/sca.json sã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: hyperfine para wall-clock, /usr/bin/time -v para RSS, mais um harness Go opcional (go test -bench) para o pipeline correlate/consensus.

7.2 Stress (proposto)

  • Objetivo: comportamento quando um scanner é morto por OOM ou excede --timeout/ProbeTime. O orchestrator já distingue timeout/killed(OOM)/unavailable; um teste de stress confirmaria isso ao vivo.
  • Como: rodar em um container com --memory baixo e targets pesados; assertar que o status por scanner é reportado corretamente (e exportado via --metrics, gauge quorum_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.go e crosswalk_test.go, faltando o equivalente E2E).
  • Tooling sugerido: toxiproxy para 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 scan contra 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 + gosec ao ci.yml.
  • [ ] Habilitar CodeQL (Go) em um workflow dedicado.
  • [ ] Job rodando quorum scan . (dogfooding SCA/IaC) com --fail-on high.
  • [ ] Escanear as imagens :full/:slim recém-construídas no release.yml antes do push.
  • [ ] govulncheck ./... como gate.
  • [ ] gitleaks para 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.Merge e correlate.Enrich (agrupamento por correlationKey, hashing de fingerprint) sobre grandes conjuntos de findings vindos de até 12 scanners.
  • crosswalk.Resolve sobre 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 -coverprofile no ci.yml e 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 o coverage.out já 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; incluir examples/k8s (§8).
  • [ ] P1govulncheck ./... como gate (§8).
  • [ ] P2 — Cobrir parsing de flags (--type/--format/--fail-on/--min-severity/--timeout) e --log-format em cmd/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.yml e os arquivos *_test.go de internal/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.go e cmd/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.mod e os workflows que pinam go-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: o ci.yml usa go test -race -covermode=atomic -coverprofile=coverage.out ./...; o Makefile test usa go 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/evals pontua 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 apenas multiDetected >= 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/k8s e examples/ci permaneçam alvos de scan válidos no repositório; o conftest pressupõe um bundle Rego em ./policy fornecido 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_Counts e 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 fixtures k8s_polaris.json/k8s_kubescore.json de TestRealParse_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.