a87e7d2c72
All 3 cloud models converged (all "minor" — example code, no blocking): - Consolidate: a model whose every lens errored now reads "review incomplete", not a misleading "no issues found" (all 3 models). + test. - Consolidate: swarm-cancelled (unattributed) cells now surface a "swarm cancelled — N cell(s) did not run" banner instead of vanishing (all 3). + test. - main: io.ReadAll(os.Stdin) error is surfaced (all 3); a TTY stdin no longer hangs forever (TTY guard, minimax). - providerOf: a bare tier name now keys its own PerKey bucket instead of all bare tiers collapsing onto "tier" (minimax, glm-5.2) — distinct tiers throttle independently. - Review doc reworded (the closure, not fanout, carries per-cell errors). Left as documented example-scope behavior: no per-cell timeout (caller supplies ctx), unknown-severity → lowest rank (no crash). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
129 lines
4.2 KiB
Go
129 lines
4.2 KiB
Go
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)
|
||
}
|
||
}
|
||
|
||
// TestConsolidateAllErrored: a model whose every lens errored must NOT be
|
||
// labelled "no issues found" (the gadfly P5 finding).
|
||
func TestConsolidateAllErrored(t *testing.T) {
|
||
got := Consolidate([]LensResult{
|
||
{Model: "m", Lens: "a", Err: context.DeadlineExceeded},
|
||
{Model: "m", Lens: "b", Err: context.DeadlineExceeded},
|
||
})
|
||
if !strings.Contains(got, "m — review incomplete") {
|
||
t.Errorf("all-errored model should be 'review incomplete', got:\n%s", got)
|
||
}
|
||
if strings.Contains(got, "no issues found") {
|
||
t.Errorf("all-errored model must not say 'no issues found':\n%s", got)
|
||
}
|
||
}
|
||
|
||
// TestConsolidateSwarmCancelled: dropped (unattributed) cells surface a banner.
|
||
func TestConsolidateSwarmCancelled(t *testing.T) {
|
||
got := Consolidate([]LensResult{
|
||
{Err: context.Canceled}, // dropped cell, no model
|
||
{Model: "m", Lens: "a", Findings: []Finding{{Severity: SevSmall, Title: "x"}}},
|
||
})
|
||
if !strings.Contains(got, "swarm cancelled") {
|
||
t.Errorf("dropped cells should surface a cancellation banner:\n%s", got)
|
||
}
|
||
}
|