Files
agent-tools/internal/agent/watch_test.go
T
unkin-agent 05594113a2
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/test Pipeline was successful
ci/woodpecker/pr/pre-commit Pipeline was successful
Fix non-zero exit on error and debounce transient mergeable=false
agentpr/watchpr already propagated command errors to a non-zero exit, but
that behaviour had no regression coverage and the root command was not
constructible outside main(). watchpr also fired a spurious conflict alert
because Gitea computes mergeability asynchronously and can briefly report
mergeable=false right after a push. The docs additionally printed the
AppRole role_id literal UUID.

- Extract newRootCmd() in both cmd/agentpr and cmd/watchpr so main() only
  runs Execute and exits non-zero on error; add tests asserting Execute
  returns an error for a bad PR ref / malformed --repo / no args.
- Debounce mergeability loss in MeaningfulChange: only alert when
  mergeable=false persists across two consecutive polls (both prev and cur
  false, still open); update the table test for one-poll-false (benign),
  false-persisting (alert), and recovered false->true (benign).
- Refer to AGENT_APPROLE_ROLE_ID by env var in README.md/AGENTS.md without
  printing the literal role_id; keep the code default and env override.
2026-08-12 22:17:10 +10:00

138 lines
3.7 KiB
Go

package agent
import "testing"
func base() PRState {
return PRState{
Ref: PRRef{Owner: "unkin", Repo: "repo", Number: 1},
State: "open",
Merged: false,
HeadSHA: "abc123",
Mergeable: true,
CIStatus: "pending",
NonAgentComments: 0,
}
}
func TestMeaningfulChange(t *testing.T) {
tests := []struct {
name string
mutatePrev func(s *PRState)
mutate func(s *PRState)
wantChange bool
}{
{
name: "no change",
mutate: func(s *PRState) {},
wantChange: false,
},
{
name: "CI pending to success is benign",
mutate: func(s *PRState) { s.CIStatus = "success" },
wantChange: false,
},
{
name: "open to merged alerts",
mutate: func(s *PRState) { s.Merged = true; s.State = "closed" },
wantChange: true,
},
{
name: "open to closed without merge alerts",
mutate: func(s *PRState) { s.State = "closed" },
wantChange: true,
},
{
name: "new non-agent comment alerts",
mutate: func(s *PRState) { s.NonAgentComments = 1 },
wantChange: true,
},
{
name: "CI to failure alerts",
mutate: func(s *PRState) { s.CIStatus = "failure" },
wantChange: true,
},
{
name: "CI to error alerts",
mutate: func(s *PRState) { s.CIStatus = "error" },
wantChange: true,
},
{
// A single mergeable=false poll is debounced: Gitea often reports
// this transiently right after a push.
name: "mergeable true to false for one poll is benign",
mutate: func(s *PRState) { s.Mergeable = false },
wantChange: false,
},
{
// mergeable=false persisting into a second consecutive poll is a
// real conflict and alerts.
name: "mergeable false persisting a second poll alerts",
mutatePrev: func(s *PRState) { s.Mergeable = false },
mutate: func(s *PRState) { s.Mergeable = false },
wantChange: true,
},
{
// mergeable recovered (false then true) must not alert.
name: "mergeable recovered false to true is benign",
mutatePrev: func(s *PRState) { s.Mergeable = false },
mutate: func(s *PRState) {},
wantChange: false,
},
{
name: "new head sha alone is benign",
mutate: func(s *PRState) { s.HeadSHA = "def456" },
wantChange: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
prev := base()
if tt.mutatePrev != nil {
tt.mutatePrev(&prev)
}
cur := base()
tt.mutate(&cur)
got, reason := MeaningfulChange(prev, cur)
if got != tt.wantChange {
t.Errorf("MeaningfulChange() = %v (%q), want %v", got, reason, tt.wantChange)
}
if got && reason == "" {
t.Errorf("change reported without a reason")
}
})
}
}
// A comment that only the agent posts must not alert: the non-agent count is
// unchanged, so MeaningfulChange sees nothing.
func TestMeaningfulChangeAgentCommentIgnored(t *testing.T) {
prev := base()
cur := base() // agent commented, but NonAgentComments stayed 0
if got, _ := MeaningfulChange(prev, cur); got {
t.Errorf("agent-only comment should not alert")
}
}
// Once CI is already failing, staying failed must not re-alert.
func TestMeaningfulChangeStaysFailed(t *testing.T) {
prev := base()
prev.CIStatus = "failure"
cur := base()
cur.CIStatus = "failure"
if got, _ := MeaningfulChange(prev, cur); got {
t.Errorf("CI staying failed should not re-alert")
}
}
func TestCountNonAgentComments(t *testing.T) {
comments := []Comment{
{User: User{Login: "unkin-agent"}},
{User: User{Login: "ben"}},
{User: User{Login: "unkin-agent"}},
{User: User{Login: "reviewer"}},
}
if n := countNonAgentComments(comments, "unkin-agent"); n != 2 {
t.Errorf("countNonAgentComments = %d, want 2", n)
}
}