package link import ( "context" "encoding/json" "errors" "io" "log" "testing" "time" ) // heldControl is a merge announcement that records how it was settled. type heldControl struct { body []byte took, held, drops int } func (c *heldControl) Kind() string { return "source-moved" } func (c *heldControl) Body() []byte { return c.body } func (c *heldControl) Subject() string { return "" } func (c *heldControl) Redelivered() bool { return false } func (c *heldControl) HeldFor() time.Duration { return 0 } func (c *heldControl) Answer(context.Context, []byte) error { return nil } func (c *heldControl) Took() error { c.took++; return nil } func (c *heldControl) Hold(time.Duration) error { c.held++; return nil } func (c *heldControl) Drop() error { c.drops++; return nil } type sourceMover struct { Upgrader answer error } func (s sourceMover) SourceMoved(context.Context, SourceMoved) error { return s.answer } // **A merge is acknowledged only after the controller recorded it** (novox/hq ADR 0407 decision 3, issue 1092): an // answer of "not now" — the plan it belongs to could not be saved, the bus away, the lease lost — leaves the announcement // unacknowledged, held to be handed over again; only an answer that is not "not now" settles it. func TestAMergeTheControllerCouldNotRecordIsHeldAndNotAcknowledged(t *testing.T) { body, err := json.Marshal(SourceMoved{Owner: "novox", Repo: "mesh-catalog", Base: "main", Commit: "c1"}) if err != nil { t.Fatal(err) } s := &Server{log: log.New(io.Discard, "", 0)} for name, tc := range map[string]struct { answer error took, held int }{ "recorded": {nil, 1, 0}, "not now": {errors.Join(ErrTryAgain, errors.New("the plan is not saved")), 0, 1}, "an answer, not a wait": {errors.New("the mesh could not act on it"), 1, 0}, } { s.upgrader = sourceMover{answer: tc.answer} m := &heldControl{body: body} s.sourceMoved(context.Background(), m) if m.took != tc.took || m.held != tc.held || m.drops != 0 { t.Errorf("%s: acknowledged %d times and held %d times, want %d and %d", name, m.took, m.held, tc.took, tc.held) } } }