package operations import ( "errors" "strings" "testing" "orchestra/internal/domain" "orchestra/internal/registry" "orchestra/internal/review" "orchestra/internal/store" ) const shaA = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" const shaB = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" const shaBase = "0000000000000000000000000000000000000000" func evidence(result string) review.Evidence { return review.Evidence{ BaseSHA: shaBase, ResultSHA: result, Diff: "--- a/internal/attr/attr.go\n+++ b/internal/attr/attr.go\n+index lookup\n", GateCommand: "go test ./...", GateExit: 0, } } // atImplement walks a task to the implementation phase with both artifacts // sealed, which is where review becomes possible. func atImplement(t *testing.T, project registry.Project) (*store.Store, string) { t.Helper() s, id := phaseStore(t) if _, err := AdvanceWorkPhase(s, project, id, nil); err != nil { t.Fatal(err) } if _, err := AdvanceWorkPhase(s, project, id, sealed(t, research)); err != nil { t.Fatal(err) } if _, err := AdvanceWorkPhase(s, project, id, sealed(t, plan)); err != nil { t.Fatal(err) } return s, id } func finding(id string, sev review.Severity) review.Finding { return review.Finding{ ID: id, Severity: sev, File: "internal/attr/attr.go", Line: 81, Claim: "retry path acknowledges success before the durable append", Evidence: "line 81 returns before Append", } } // Minor findings are reported and the task stays eligible. The review is bound // to the commit it examined. func TestMinorOnlyReviewIsAccepted(t *testing.T) { project := registry.Project{ID: "p", QualityGate: "go test ./..."} s, id := atImplement(t, project) if _, err := EnterReview(s, project, id, evidence(shaA)); err != nil { t.Fatal(err) } if got, _ := s.Task(id); got.WorkPhase != domain.WorkPhaseReview { t.Fatalf("phase = %q", got.WorkPhase) } if _, err := RecordReview(s, project, id, review.Result{ResultSHA: shaA, Findings: []review.Finding{finding("f1", review.Minor)}}); err != nil { t.Fatal(err) } got, _ := s.Task(id) if got.WorkPhase != domain.WorkPhaseReview { t.Fatalf("a minor-only review must not send work back: %q", got.WorkPhase) } if !got.ReviewSatisfied(shaA) { t.Fatalf("review not satisfied for its own commit: %+v", got.Review) } // The same review says nothing about a different tree. if got.ReviewSatisfied(shaB) { t.Fatal("a review of one commit must not satisfy another") } } // A blocking finding returns the task to implementation, and the old review // cannot satisfy the new commit. func TestBlockingReviewReturnsWorkAndGoesStale(t *testing.T) { project := registry.Project{ID: "p", QualityGate: "go test ./..."} s, id := atImplement(t, project) if _, err := EnterReview(s, project, id, evidence(shaA)); err != nil { t.Fatal(err) } if _, err := RecordReview(s, project, id, review.Result{ResultSHA: shaA, Findings: []review.Finding{ finding("f1", review.Important), finding("f2", review.Minor), }}); err != nil { t.Fatal(err) } got, _ := s.Task(id) if got.WorkPhase != domain.WorkPhaseImplement { t.Fatalf("phase = %q, want implement", got.WorkPhase) } if got.ReviewSatisfied(shaA) { t.Fatal("a review with an important finding must not satisfy completion") } // The findings are readable for the implementation context. r, err := TaskReview(s, got) if err != nil || r == nil || len(r.Findings) != 2 { t.Fatalf("findings = %+v err=%v", r, err) } // Fixed at a new commit: a fresh review passes, and it is bound to B. if _, err := EnterReview(s, project, id, evidence(shaB)); err != nil { t.Fatal(err) } if _, err := RecordReview(s, project, id, review.Result{ResultSHA: shaB}); err != nil { t.Fatal(err) } got, _ = s.Task(id) if !got.ReviewSatisfied(shaB) { t.Fatalf("fresh review not satisfied: %+v", got.Review) } if got.ReviewSatisfied(shaA) { t.Fatal("the superseded commit must not look reviewed") } } // A review sealed against a commit other than the one under review is // rejected while the reviewing session still exists to redo it. func TestReviewForTheWrongCommitIsRejected(t *testing.T) { project := registry.Project{ID: "p", QualityGate: "go test ./..."} s, id := atImplement(t, project) if _, err := EnterReview(s, project, id, evidence(shaA)); err != nil { t.Fatal(err) } if got, _ := s.Task(id); got.ReviewTargetSHA != shaA { t.Fatalf("review target = %q", got.ReviewTargetSHA) } if _, err := RecordReview(s, project, id, review.Result{ResultSHA: shaB}); !errors.Is(err, domain.ErrInvalid) { t.Fatalf("want ErrInvalid, got %v", err) } if got, _ := s.Task(id); got.Review != nil { t.Fatalf("a mismatched review was recorded: %+v", got.Review) } if _, err := RecordReview(s, project, id, review.Result{ResultSHA: shaA}); err != nil { t.Fatal(err) } } func TestReviewEntryConditions(t *testing.T) { project := registry.Project{ID: "p", QualityGate: "go test ./..."} // Wrong phase. s, id := phaseStore(t) if _, err := EnterReview(s, project, id, evidence(shaA)); !errors.Is(err, ErrReviewNotEligible) { t.Fatalf("frame phase: want ErrReviewNotEligible, got %v", err) } // Failing gate, unanchored commits, empty diff, and a gate that never ran. s, id = atImplement(t, project) bad := map[string]review.Evidence{ "failing gate": func() review.Evidence { e := evidence(shaA); e.GateExit = 1; return e }(), "no result": func() review.Evidence { e := evidence(shaA); e.ResultSHA = "short"; return e }(), "no base": func() review.Evidence { e := evidence(shaA); e.BaseSHA = ""; return e }(), "no diff": func() review.Evidence { e := evidence(shaA); e.Diff = ""; return e }(), "gate skipped": func() review.Evidence { e := evidence(shaA); e.GateCommand = ""; return e }(), } for name, ev := range bad { if _, err := EnterReview(s, project, id, ev); !errors.Is(err, ErrReviewNotEligible) { t.Fatalf("%s: want ErrReviewNotEligible, got %v", name, err) } } // An unresolved question blocks entry. lease(t, s, id) if _, err := RequestHumanDecision(s, project, id, request("which behaviour is intended?")); err != nil { t.Fatal(err) } if _, err := EnterReview(s, project, id, evidence(shaA)); !errors.Is(err, ErrReviewNotEligible) { t.Fatalf("blocked task: want ErrReviewNotEligible, got %v", err) } } // A review can only be sealed by a reviewing session, and only in a shape that // is actually reviewable. func TestReviewResultRejections(t *testing.T) { project := registry.Project{ID: "p", QualityGate: "go test ./..."} s, id := atImplement(t, project) // Not in the review phase yet. if _, err := RecordReview(s, project, id, review.Result{ResultSHA: shaA}); !errors.Is(err, domain.ErrInvalid) { t.Fatalf("want ErrInvalid, got %v", err) } if _, err := EnterReview(s, project, id, evidence(shaA)); err != nil { t.Fatal(err) } long := strings.Repeat("x", 501) bad := map[string]review.Result{ "no sha": {Findings: []review.Finding{finding("f1", review.Minor)}}, "short sha": {ResultSHA: "abc"}, "no id": {ResultSHA: shaA, Findings: []review.Finding{{Severity: review.Minor, File: "a.go", Claim: "c", Evidence: "e"}}}, "duplicate id": {ResultSHA: shaA, Findings: []review.Finding{finding("f1", review.Minor), finding("f1", review.Blocker)}}, "bad severity": {ResultSHA: shaA, Findings: []review.Finding{{ID: "f", Severity: "invalid", File: "a.go", Claim: "c", Evidence: "e"}}}, "absolute path": {ResultSHA: shaA, Findings: []review.Finding{{ID: "f", Severity: review.Minor, File: "/etc/passwd", Claim: "c", Evidence: "e"}}}, "no evidence": {ResultSHA: shaA, Findings: []review.Finding{{ID: "f", Severity: review.Minor, File: "a.go", Claim: "c"}}}, "essay": {ResultSHA: shaA, Findings: []review.Finding{{ID: "f", Severity: review.Minor, File: "a.go", Claim: long, Evidence: "e"}}}, "multiline": {ResultSHA: shaA, Findings: []review.Finding{{ID: "f", Severity: review.Minor, File: "a.go", Claim: "one\ntwo", Evidence: "e"}}}, } for name, result := range bad { if _, err := RecordReview(s, project, id, result); !errors.Is(err, domain.ErrInvalid) { t.Fatalf("%s: want ErrInvalid, got %v", name, err) } } // A rejected review left no trace. if got, _ := s.Task(id); got.Review != nil { t.Fatalf("a rejected review was recorded: %+v", got.Review) } // Too many findings is also a rejection. flood := review.Result{ResultSHA: shaA} for i := 0; i < 41; i++ { flood.Findings = append(flood.Findings, finding(string(rune('a'+i%26))+strings.Repeat("z", i), review.Minor)) } if _, err := RecordReview(s, project, id, flood); !errors.Is(err, domain.ErrInvalid) { t.Fatalf("want ErrInvalid, got %v", err) } }