Let a proposed record be withdrawn (issue 1081)
A dropped proposal or a stale reservation stayed proposed forever: there was no way to say the operator decided against it. knowledge-base.withdraw takes a reserved or proposed record to withdrawn as a new version naming why, ends a waiting operator's ask and clears a pending review in the same version, and refuses an already accepted, superseded or withdrawn record. The number stays spent either way. The old Withdraw (the background job for reservations never proposed) is renamed WithdrawStale to make room for the new, caller-driven WithdrawRecord.
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
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 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")
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user