a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
178 lines
6.9 KiB
Go
178 lines
6.9 KiB
Go
package review
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// fixedReport is a representative report: a BLOCK verdict with three findings
|
|
// across two files (one critical, one error, one warning), a per-file risk
|
|
// ranking, a summary line, an adaptive depth, and a populated cost block.
|
|
func fixedReport() *ReviewReport {
|
|
return &ReviewReport{
|
|
Verdict: VerdictBlock,
|
|
Summary: "BLOCK: 3 finding(s) across 2 changed file(s)",
|
|
Depth: "deep",
|
|
Findings: []Finding{
|
|
{
|
|
File: "internal/svc/loop.go", Line: 12, Severity: SevWarning,
|
|
Rule: "go-loop-query-call", Category: "performance",
|
|
Message: "query in loop", Confidence: 0.8,
|
|
},
|
|
{
|
|
File: "internal/svc/handler.go", Line: 7, Severity: SevError,
|
|
Rule: "go-inverted-err-check", Category: "correctness",
|
|
Message: "inverted error check", Confidence: 0.9,
|
|
},
|
|
{
|
|
File: "internal/svc/handler.go", Line: 3, Severity: SevCritical,
|
|
Rule: "nil-deref", Category: "correctness",
|
|
Message: "possible nil dereference", Confidence: 0.95,
|
|
},
|
|
},
|
|
FileRisk: []FileRisk{
|
|
{File: "internal/svc/handler.go", Risk: "high", Findings: 2},
|
|
{File: "internal/svc/loop.go", Risk: "medium", Findings: 1},
|
|
},
|
|
Cost: &CostBreakdown{
|
|
InputTokens: 1234, OutputTokens: 456,
|
|
CacheReadTokens: 2000, CacheWriteTokens: 0,
|
|
USD: 0.012, Estimated: true, ElapsedMs: 4200,
|
|
},
|
|
}
|
|
}
|
|
|
|
// TestRenderSummary_AgentGolden pins the terse agent rendering byte-for-byte.
|
|
// Findings are ordered worst-first (critical → error → warning); the verdict
|
|
// line carries the severity histogram; one compact line per finding; one cost
|
|
// line. There is no narrative prose.
|
|
func TestRenderSummary_AgentGolden(t *testing.T) {
|
|
got := RenderSummary(fixedReport(), AudienceAgent)
|
|
|
|
want := "" +
|
|
"VERDICT: block (1 critical, 1 error, 1 warning)\n" +
|
|
"findings:\n" +
|
|
" internal/svc/handler.go:3 critical nil-deref — possible nil dereference\n" +
|
|
" internal/svc/handler.go:7 error go-inverted-err-check — inverted error check\n" +
|
|
" internal/svc/loop.go:12 warning go-loop-query-call — query in loop\n" +
|
|
"cost: in=1234 out=456 cache_r=2000 cache_w=0 usd=0.012000 elapsed=4.2s\n"
|
|
|
|
if got != want {
|
|
t.Fatalf("agent rendering drift:\n--- got ---\n%s\n--- want ---\n%s", got, want)
|
|
}
|
|
}
|
|
|
|
// TestRenderSummary_AgentTerse_NoNarrative asserts the agent rendering carries
|
|
// only the verdict line, the findings block, and the cost line — no markdown
|
|
// headers, no per-file risk section, no prose.
|
|
func TestRenderSummary_AgentTerse_NoNarrative(t *testing.T) {
|
|
got := RenderSummary(fixedReport(), AudienceAgent)
|
|
|
|
// One-line verdict first.
|
|
first := strings.SplitN(got, "\n", 2)[0]
|
|
if !strings.HasPrefix(first, "VERDICT: block") {
|
|
t.Fatalf("agent output must lead with a one-line VERDICT, got %q", first)
|
|
}
|
|
// Cost line present.
|
|
if !strings.Contains(got, "cost: in=1234 out=456 cache_r=2000 cache_w=0 usd=0.012000 elapsed=4.2s") {
|
|
t.Errorf("agent output must carry the cost line, got:\n%s", got)
|
|
}
|
|
// Prose / narrative markers from the human packet must NOT appear.
|
|
for _, marker := range []string{"## ", "Verdict:", "File risk:", "Findings (", "No inline findings", "finding(s) across"} {
|
|
if strings.Contains(got, marker) {
|
|
t.Errorf("agent output must not contain narrative marker %q, got:\n%s", marker, got)
|
|
}
|
|
}
|
|
// Every finding line is a single compact line under the findings block.
|
|
if c := strings.Count(got, " — "); c != 3 {
|
|
t.Errorf("expected 3 compact finding lines, found %d em-dashes in:\n%s", c, got)
|
|
}
|
|
}
|
|
|
|
// TestRenderSummary_HumanGolden pins the readable section packet byte-for-byte:
|
|
// the verdict header, the summary line, the per-file risk ranking, and the
|
|
// findings grouped by file and ordered by line, then the cost block.
|
|
func TestRenderSummary_HumanGolden(t *testing.T) {
|
|
got := RenderSummary(fixedReport(), AudienceHuman)
|
|
|
|
want := "" +
|
|
"Verdict: BLOCK\n" +
|
|
"BLOCK: 3 finding(s) across 2 changed file(s)\n" +
|
|
"\n" +
|
|
"File risk:\n" +
|
|
" high internal/svc/handler.go (2 finding(s))\n" +
|
|
" medium internal/svc/loop.go (1 finding(s))\n" +
|
|
"\n" +
|
|
"Findings (3):\n" +
|
|
"\n" +
|
|
"internal/svc/handler.go\n" +
|
|
" L3 critical possible nil dereference [correctness/nil-deref]\n" +
|
|
" L7 error inverted error check [correctness/go-inverted-err-check]\n" +
|
|
"\n" +
|
|
"internal/svc/loop.go\n" +
|
|
" L12 warning query in loop [performance/go-loop-query-call]\n" +
|
|
"\n" +
|
|
"cost: in=1234 out=456 cache_r=2000 cache_w=0 usd=0.012000 elapsed=4.2s\n"
|
|
|
|
if got != want {
|
|
t.Fatalf("human rendering drift:\n--- got ---\n%q\n--- want ---\n%q", got, want)
|
|
}
|
|
}
|
|
|
|
// TestRenderSummary_HumanIsSectionPacket asserts the human rendering carries the
|
|
// readable sections the agent form omits.
|
|
func TestRenderSummary_HumanIsSectionPacket(t *testing.T) {
|
|
got := RenderSummary(fixedReport(), AudienceHuman)
|
|
for _, want := range []string{"Verdict: BLOCK", "File risk:", "Findings (3):", "internal/svc/handler.go", "[correctness/nil-deref]"} {
|
|
if !strings.Contains(got, want) {
|
|
t.Errorf("human packet missing section marker %q, got:\n%s", want, got)
|
|
}
|
|
}
|
|
// The terse one-line verdict form must NOT be present in the human packet.
|
|
if strings.Contains(got, "VERDICT: block") {
|
|
t.Errorf("human packet must use the readable header, not the terse VERDICT line:\n%s", got)
|
|
}
|
|
}
|
|
|
|
// TestRenderSummary_DefaultIsHuman asserts AudienceHuman is the zero value, so a
|
|
// caller that does not opt in gets the readable packet.
|
|
func TestRenderSummary_DefaultIsHuman(t *testing.T) {
|
|
var aud Audience // zero value
|
|
if aud != AudienceHuman {
|
|
t.Fatalf("the zero-value Audience must be AudienceHuman, got %d", aud)
|
|
}
|
|
if RenderSummary(fixedReport(), aud) != RenderSummary(fixedReport(), AudienceHuman) {
|
|
t.Error("zero-value Audience must render identically to AudienceHuman")
|
|
}
|
|
}
|
|
|
|
// TestRenderSummary_NilReport asserts a nil report renders an empty APPROVE for
|
|
// both audiences without panicking.
|
|
func TestRenderSummary_NilReport(t *testing.T) {
|
|
agent := RenderSummary(nil, AudienceAgent)
|
|
if !strings.HasPrefix(agent, "VERDICT: approve") {
|
|
t.Errorf("nil report agent rendering must lead with approve, got %q", agent)
|
|
}
|
|
if !strings.Contains(agent, "cost: in=0 out=0") {
|
|
t.Errorf("nil report agent rendering must carry a zero cost line, got %q", agent)
|
|
}
|
|
human := RenderSummary(nil, AudienceHuman)
|
|
if !strings.Contains(human, "Verdict: APPROVE") || !strings.Contains(human, "No inline findings") {
|
|
t.Errorf("nil report human rendering must be an empty APPROVE packet, got %q", human)
|
|
}
|
|
}
|
|
|
|
// TestRenderSummary_NoCostStillEmitsZeroLine asserts a report with a nil Cost
|
|
// (the deterministic-only path) still renders a parseable zero cost line in both
|
|
// audiences.
|
|
func TestRenderSummary_NoCostStillEmitsZeroLine(t *testing.T) {
|
|
r := fixedReport()
|
|
r.Cost = nil
|
|
for _, aud := range []Audience{AudienceAgent, AudienceHuman} {
|
|
got := RenderSummary(r, aud)
|
|
if !strings.Contains(got, "cost: in=0 out=0 cache_r=0 cache_w=0 usd=0.000000 elapsed=0.0s") {
|
|
t.Errorf("audience %d must emit a zero cost line when Cost is nil, got:\n%s", aud, got)
|
|
}
|
|
}
|
|
}
|