package chain import ( "encoding/json" "strings" "testing" "time" ) // **A pending value is not a standing assignment** (ADR 0261, ADR 0408, issue 1094): it names its node and module, holds no // time assigned, says `state: pending`, and every reader that skips a value with a state skips it. func TestAPendingValueIsNotAStandingAssignment(t *testing.T) { since := time.Date(2026, 10, 18, 3, 0, 0, 0, time.UTC) a := Assignment{Node: "anchor", Module: "notes", State: StatePending, Pending: &Pending{ID: "p-1", State: PendingWaiting, Build: "build-1", Since: since}} if a.Stands() { t.Fatal("a pending value stands") } raw, _ := json.Marshal(a) for _, want := range []string{`"node":"anchor"`, `"module":"notes"`, `"state":"pending"`, `"pending":{"id":"p-1","state":"waiting"`} { if !strings.Contains(string(raw), want) { t.Errorf("the value lacks %s: %s", want, raw) } } if strings.Contains(string(raw), `"assigned"`) { t.Errorf("a pending value names a time assigned: %s", raw) } // The wait carried into the value that stands: no state, so it stands, and the record stays. made := Assignment{Node: "anchor", Module: "notes", Assigned: since, Pending: &Pending{ID: "p-1", State: PendingApplied}} if !made.Stands() || !a.Pending.Open() || made.Pending.Open() { t.Fatalf("stands %v, open before %v, open after %v", made.Stands(), a.Pending.Open(), made.Pending.Open()) } var back Assignment if err := json.Unmarshal(raw, &back); err != nil || back.Pending == nil || back.Pending.Build != "build-1" || !back.Pending.Since.Equal(since) { t.Fatalf("read back %+v: %v", back, err) } now := since raised := Pending{ID: "p-2", State: PendingExpired, Raised: &now, Acknowledged: &now, Cleared: &now} body, _ := json.Marshal(raised) if !strings.Contains(string(body), `"raised"`) || !strings.Contains(string(body), `"acknowledged"`) || !strings.Contains(string(body), `"cleared"`) { t.Errorf("the marks of a wait: %s", body) } } func TestTheTransitionsOfAPendingAssignment(t *testing.T) { ok := [][2]string{{PendingWaiting, PendingExpired}, {PendingWaiting, PendingWithdrawn}, {PendingApplying, PendingApplied}, {PendingApplying, PendingRefused}} for _, p := range ok { if !CanSettle(p[0], p[1]) { t.Errorf("%s may end as %s", p[0], p[1]) } } for _, p := range [][2]string{{PendingWaiting, PendingApplied}, {PendingWaiting, PendingRefused}, {PendingApplying, PendingExpired}, {PendingApplying, PendingWithdrawn}, {PendingApplied, PendingExpired}, {PendingExpired, PendingWithdrawn}, {PendingWaiting, PendingWaiting}} { if CanSettle(p[0], p[1]) { t.Errorf("%s may not end as %s", p[0], p[1]) } } if !CanMove(PendingWaiting, PendingApplying) || !CanMove(PendingApplying, PendingWaiting) || CanMove(PendingApplied, PendingWaiting) { t.Error("a claim moves a wait to applying and back") } }