mesh/merge-gate head 684e8801c420 merged into trunk 3395af96bc34 — pass: builds mesh-knowledge → novox; no bus step; every machine composes with the change…
mesh/repo-check head 684e8801c420 merged into trunk 3395af96bc34 — pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-…
mesh/delivery delivered
WithdrawRecord only ended the waiting ask locally; the router kept it open, so the operator still saw Approve/Decline for a withdrawn record and an answer to it was dropped silently. It now also publishes a cancel on the router's subject once the withdraw is saved, as the controller's asker does; a cancel the router cannot be told of is logged and the withdraw still stands. The module's existing "uses": ["operator-channel"] already grants this; no manifest change needed, since the ask and cancel subjects are both under that seat.
124 lines
4.1 KiB
Go
124 lines
4.1 KiB
Go
package main
|
|
|
|
// Withdraw (novox/hq issue 1081): the operator's decision that a reserved or proposed record is dropped, so its
|
|
// number stays spent and the record stops asking anything of anyone.
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestAReservedRecordIsWithdrawnAtOnce(t *testing.T) {
|
|
eachStore(t, func(t *testing.T, s Store) {
|
|
w := imported(t, s)
|
|
n := w.reserved(t, "author@test")
|
|
key := Key{KindDecision, n}
|
|
out, err := w.k.WithdrawRecord("operator@shanks", key, "never proposed")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if out.Status != StatusWithdrawn || out.Record != key.String() {
|
|
t.Fatalf("%+v", out)
|
|
}
|
|
e := mustGet(t, w.k.Store, key)
|
|
if e.Current.Status != StatusWithdrawn {
|
|
t.Fatalf("status %q", e.Current.Status)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestAProposedRecordNeedingTheOperatorIsWithdrawnWithItsAskEndedAndItsReviewCleared(t *testing.T) {
|
|
eachStore(t, func(t *testing.T, s Store) {
|
|
w := imported(t, s)
|
|
n := w.reserved(t, "author@test")
|
|
key := Key{KindDecision, n}
|
|
p := valid(n)
|
|
p.NeedsOperator = true
|
|
p.Brief = &Brief{Headline: "a test record that needs the operator", Summary: "the operator is asked, then the proposal is withdrawn."}
|
|
if _, err := w.k.Propose("author@test", p); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
w.reviewAll(t)
|
|
before := mustGet(t, w.k.Store, key)
|
|
if before.Current.Status != StatusProposed || before.Current.Ask == nil || before.Current.Ask.Said != AskWaiting {
|
|
t.Fatalf("not waiting for the operator: %+v", before.Current)
|
|
}
|
|
why := "replaced by research on moving designs into the knowledge base"
|
|
out, err := w.k.WithdrawRecord("operator@shanks", key, why)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if out.Status != StatusWithdrawn {
|
|
t.Fatalf("status %q", out.Status)
|
|
}
|
|
after := mustGet(t, w.k.Store, key)
|
|
if after.Current.Status != StatusWithdrawn {
|
|
t.Fatalf("status %q", after.Current.Status)
|
|
}
|
|
if after.Versions != before.Versions+1 {
|
|
t.Fatalf("versions %d, want %d", after.Versions, before.Versions+1)
|
|
}
|
|
if after.Current.Ask == nil || after.Current.Ask.Said == AskWaiting {
|
|
t.Fatalf("the waiting ask was not ended: %+v", after.Current.Ask)
|
|
}
|
|
if !contains(w.router.cancelled, before.Current.Ask.ID) {
|
|
t.Fatalf("the router was not told to take back the ask %s: cancelled %v", before.Current.Ask.ID, w.router.cancelled)
|
|
}
|
|
if after.Current.Judging != nil {
|
|
t.Fatalf("a pending review was not cleared: %+v", after.Current.Judging)
|
|
}
|
|
vs, err := w.k.Store.Versions(context.Background(), key)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
last := vs[len(vs)-1]
|
|
if last.By != "operator@shanks" || !strings.Contains(last.Why, why) {
|
|
t.Fatalf("the new version is not by the caller with the why: %+v", last)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestAnAcceptedRecordRefusesWithdrawNamingWhy(t *testing.T) {
|
|
eachStore(t, func(t *testing.T, s Store) {
|
|
w := imported(t, s)
|
|
n := w.reserved(t, "author@test")
|
|
key := Key{KindDecision, n}
|
|
w.accepted(t, "author@test", valid(n))
|
|
if _, err := w.k.WithdrawRecord("operator@shanks", key, "no longer wanted"); err == nil || !strings.Contains(err.Error(), "accepted") {
|
|
t.Fatalf("err %v", err)
|
|
}
|
|
e := mustGet(t, w.k.Store, key)
|
|
if e.Current.Status != StatusAccepted {
|
|
t.Fatalf("an accepted record was changed: %q", e.Current.Status)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestAWithdrawnRecordRefusesWithdrawAgain(t *testing.T) {
|
|
eachStore(t, func(t *testing.T, s Store) {
|
|
w := imported(t, s)
|
|
n := w.reserved(t, "author@test")
|
|
key := Key{KindDecision, n}
|
|
if _, err := w.k.WithdrawRecord("operator@shanks", key, "first"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := w.k.WithdrawRecord("operator@shanks", key, "again"); err == nil || !strings.Contains(err.Error(), "withdrawn") {
|
|
t.Fatalf("err %v", err)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestWithdrawNeedsWhyAndRefusesARecordItHoldsNoneOf(t *testing.T) {
|
|
eachStore(t, func(t *testing.T, s Store) {
|
|
w := imported(t, s)
|
|
n := w.reserved(t, "author@test")
|
|
if _, err := w.k.WithdrawRecord("operator@shanks", Key{KindDecision, n}, " "); err == nil {
|
|
t.Fatal("an empty why was accepted")
|
|
}
|
|
if _, err := w.k.WithdrawRecord("operator@shanks", Key{KindDecision, 999999}, "why"); err == nil {
|
|
t.Fatal("a record it holds none of was accepted")
|
|
}
|
|
})
|
|
}
|