package main import ( "context" "strings" "testing" "gitea.stevedudenhoeffer.com/steve/majordomo/provider/fake" "gitea.stevedudenhoeffer.com/steve/executus/fanout" ) // TestReviewSwarm proves the light-tier path end-to-end against the fake // provider: a 2-model × 3-lens swarm runs, structured findings parse, and // consolidation produces one verdict-led section per model — no batteries, no // network. func TestReviewSwarm(t *testing.T) { fp := fake.New("fakeprov") // Model "hot" reports a high-severity finding on every lens; "cold" reports // nothing. Each model is called once per lens (3×), so enqueue 3 each. hot := `{"findings":[{"severity":"high","title":"SQL injection","detail":"unsanitized id in query"}]}` cold := `{"findings":[]}` for i := 0; i < 3; i++ { fp.Enqueue("hot", fake.Reply(hot)) fp.Enqueue("cold", fake.Reply(cold)) } hotM, err := fp.Model("hot") if err != nil { t.Fatal(err) } coldM, err := fp.Model("cold") if err != nil { t.Fatal(err) } models := []NamedModel{ {Name: "hot", Provider: "fakeprov", Model: hotM}, {Name: "cold", Provider: "fakeprov", Model: coldM}, } lenses := []Lens{{Name: "security"}, {Name: "correctness"}, {Name: "error-handling"}} results := Review(context.Background(), models, lenses, "some diff", fanout.Options[cell]{MaxConcurrent: 6, PerKey: map[string]int{"fakeprov": 3}}) // 2 models × 3 lenses = 6 cells, all successful. if len(results) != 6 { t.Fatalf("got %d cells, want 6", len(results)) } var hotFindings, coldFindings, errs int for _, r := range results { if r.Err != nil { errs++ continue } switch r.Model { case "hot": hotFindings += len(r.Findings) case "cold": coldFindings += len(r.Findings) } } if errs != 0 { t.Errorf("expected no cell errors, got %d", errs) } if hotFindings != 3 { // one per lens t.Errorf("hot model findings = %d, want 3", hotFindings) } if coldFindings != 0 { t.Errorf("cold model findings = %d, want 0", coldFindings) } report := Consolidate(results) if !strings.Contains(report, "hot — blocking issues found") { t.Errorf("hot section should lead with a blocking verdict:\n%s", report) } if !strings.Contains(report, "cold — no issues found") { t.Errorf("cold section should report no issues:\n%s", report) } if !strings.Contains(report, "SQL injection") { t.Errorf("report should surface the finding:\n%s", report) } } // TestConsolidateVerdicts checks the worst-severity-led header logic. func TestConsolidateVerdicts(t *testing.T) { got := Consolidate([]LensResult{ {Model: "m", Lens: "a", Findings: []Finding{{Severity: SevSmall, Title: "x"}}}, {Model: "m", Lens: "b", Findings: []Finding{{Severity: SevMedium, Title: "y"}}}, }) if !strings.Contains(got, "m — minor issues") { t.Errorf("medium-max should be 'minor issues', got:\n%s", got) } // An errored lens is surfaced in the header. got = Consolidate([]LensResult{ {Model: "m", Lens: "a", Findings: []Finding{{Severity: SevCritical, Title: "boom"}}}, {Model: "m", Lens: "b", Err: context.DeadlineExceeded}, }) if !strings.Contains(got, "blocking issues found") || !strings.Contains(got, "errored") { t.Errorf("critical + errored lens header wrong:\n%s", got) } }