package tools import ( "testing" "git.d-ma.be/mathias/gitea-mcp/internal/gitea" ) // The ship gate is the load-bearing safety of tbd_ship: it must NEVER return // merge=true unless CI is fully green AND the base isn't review-protected. // Every non-green / unknown / protected state fails closed to PR-only (#40). func TestEvaluateShipGate(t *testing.T) { run := func(status, concl string) gitea.WorkflowRun { return gitea.WorkflowRun{Status: status, Conclusion: concl} } protected := &gitea.BranchProtection{Protected: true, RequiredApprovals: 1} open := &gitea.BranchProtection{Protected: false} tests := []struct { name string runs []gitea.WorkflowRun bp *gitea.BranchProtection wantMerge bool wantCI string }{ {"all green → merge", []gitea.WorkflowRun{run("completed", "success")}, open, true, "success"}, {"in_progress → no merge", []gitea.WorkflowRun{run("in_progress", "")}, open, false, "pending"}, {"queued → no merge", []gitea.WorkflowRun{run("queued", "")}, open, false, "pending"}, {"failed → no merge", []gitea.WorkflowRun{run("completed", "failure")}, open, false, "failed"}, {"cancelled → no merge (fail-closed)", []gitea.WorkflowRun{run("completed", "cancelled")}, open, false, "failed"}, {"no runs → no merge (no CI gate)", nil, open, false, "none"}, {"mixed success+queued → no merge", []gitea.WorkflowRun{run("completed", "success"), run("queued", "")}, open, false, "pending"}, {"green but protected → no merge", []gitea.WorkflowRun{run("completed", "success")}, protected, false, "success"}, {"no runs + protected → no merge", nil, protected, false, "none"}, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { g := evaluateShipGate(tc.runs, tc.bp) if g.merge != tc.wantMerge { t.Errorf("merge = %v, want %v (reason: %q)", g.merge, tc.wantMerge, g.reason) } if g.ciStatus != tc.wantCI { t.Errorf("ciStatus = %q, want %q", g.ciStatus, tc.wantCI) } if !tc.wantMerge && g.reason == "" { t.Errorf("no-merge decision must carry a reason") } if tc.wantMerge && g.reason != "" { t.Errorf("merge decision must have empty reason, got %q", g.reason) } }) } }