watchpr: start the conflict window at the arm
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/pre-commit Pipeline was successful
ci/woodpecker/pr/test Pipeline was successful

The window ran from the first non-mergeable poll even while the rule was
disarmed, so the poll that armed it confirmed a run it played no part in.
Measure from the later of the run's start and the arm; re-arming stays a
no-op so a base moving under every poll still confirms.
This commit is contained in:
2026-09-26 22:14:25 +10:00
parent 7ffa123e3d
commit 5928233a97
6 changed files with 218 additions and 41 deletions
+53
View File
@@ -256,3 +256,56 @@ func TestBaselineIsEmittedInJSONMode(t *testing.T) {
t.Errorf("clean baseline = %q, want no suppression field", buf.String())
}
}
// Under --json stderr is the baseline and warning channel, so it has to be one
// shape: a caller parsing it line by line must never meet a bare `warning:`
// line between two NDJSON records.
func TestJSONModeStderrIsAllRecords(t *testing.T) {
st := agent.PRState{
Ref: agent.PRRef{Owner: "unkin", Repo: "repo", Number: 7},
State: "open",
Mergeable: agent.MergeNo,
}
var buf bytes.Buffer
warn(&buf, true, "no Gitea token (%v); polling anonymously", errors.New("vault refused"))
emitBaselines(&buf, []agent.PRState{st}, 30*time.Second, true)
warn(&buf, true, "polling %s: %v", st.Ref.String(), errors.New("HTTP 502"))
lines := strings.Split(strings.TrimSpace(buf.String()), "\n")
if len(lines) != 3 {
t.Fatalf("stderr = %q, want 3 records", buf.String())
}
for _, line := range lines {
var rec map[string]any
if err := json.Unmarshal([]byte(line), &rec); err != nil {
t.Errorf("stderr line %q is not a JSON record: %v", line, err)
}
}
var first warnRecord
if err := json.Unmarshal([]byte(lines[0]), &first); err != nil || !strings.Contains(first.Warning, "vault refused") {
t.Errorf("first record = %q, want the token warning", lines[0])
}
buf.Reset()
warn(&buf, false, "polling %s: %v", st.Ref.String(), errors.New("HTTP 502"))
if got := buf.String(); !strings.HasPrefix(got, "warning: ") {
t.Errorf("plain-mode warning = %q, want the warning: prefix", got)
}
}
// An unknown mergeability disarms the conflict rule exactly as a non-mergeable
// baseline does, so the baseline has to name it too.
func TestBaselineNamesAnUnknownMergeability(t *testing.T) {
st := agent.PRState{
Ref: agent.PRRef{Owner: "unkin", Repo: "repo", Number: 7},
State: "open",
Mergeable: agent.MergeUnknown,
CIStatus: "success",
}
if got := suppressedAtBaseline(st); !strings.Contains(got, "mergeability unknown") {
t.Errorf("suppressed = %q, want it to name the unknown mergeability", got)
}
if got := baselineLine(st); !strings.Contains(got, "not alerting") {
t.Errorf("baselineLine = %q, want the suppression note", got)
}
}