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.
89 lines
2.9 KiB
Go
89 lines
2.9 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// hq issue 488: the issue tracker's reserve takes the highest decision and research number from the knowledge base,
|
|
// which holds every record — reserved, withdrawn and proposed numbers included, since each is spent — rather than
|
|
// reading hq's folders, which empty when the records leave.
|
|
|
|
func TestTheHighestToolAnswersTheHighestNumberHeldPerKind(t *testing.T) {
|
|
eachStore(t, func(t *testing.T, s Store) {
|
|
w := imported(t, s)
|
|
tool := highestTool(w.k)
|
|
answer := func() Highest {
|
|
t.Helper()
|
|
out, err := tool.Run(map[string]any{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
raw, _ := json.Marshal(out)
|
|
var h Highest
|
|
if err := json.Unmarshal(raw, &h); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return h
|
|
}
|
|
if h := answer(); h.Decision != 290 || h.Research != 45 || h.Records != 11 {
|
|
t.Fatalf("%+v", h)
|
|
}
|
|
// A number reserved, and later withdrawn, is spent: it is the highest.
|
|
w.reserved(t, "author@g14") // 300
|
|
w.clock = w.clock.Add(DefaultReservedFor + time.Hour)
|
|
if _, err := w.k.WithdrawStale(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if h := answer(); h.Decision != 300 || h.Research != 45 {
|
|
t.Fatalf("after a reservation withdrawn: %+v", h)
|
|
}
|
|
if _, err := tool.Run(map[string]any{"kind": "decision"}); err == nil {
|
|
t.Fatal("an argument it does not take was taken")
|
|
}
|
|
})
|
|
// A knowledge base that holds nothing answers nothing: a 0 would start a counter at 1.
|
|
empty := newWorld(t, newMemStore(), fixture())
|
|
if _, err := highestTool(empty.k).Run(map[string]any{}); err == nil || !strings.Contains(err.Error(), "holds no record") {
|
|
t.Fatalf("an empty knowledge base: %v", err)
|
|
}
|
|
}
|
|
|
|
// hq issue 488: once the records have left hq, an import at a later commit finds none. It refuses, saying so,
|
|
// rather than reporting nothing created as a success.
|
|
func TestTheImportRefusesACommitWhereHqHoldsNoRecords(t *testing.T) {
|
|
dir := t.TempDir()
|
|
write := func(path, text string) {
|
|
t.Helper()
|
|
if err := writeFileAll(dir+"/"+path, text); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
write("COMMIT", "0123456789abcdef0123456789abcdef01234567\n")
|
|
write("02-DECISIONS/README.md", "# Decisions\n\nThey live in the knowledge base.\n")
|
|
write("01-RESEARCH/README.md", "# Research\n")
|
|
w := newWorld(t, newMemStore(), dirRecords{dir: dir})
|
|
for _, dry := range []bool{true, false} {
|
|
out, err := w.k.Import("import@test", ImportAsk{DryRun: dry}, dirRecords{dir: dir})
|
|
if err == nil || !strings.Contains(err.Error(), "holds no decision record or research effort") ||
|
|
!strings.Contains(err.Error(), "0123456789ab") {
|
|
t.Fatalf("dry %v: %+v %v", dry, out, err)
|
|
}
|
|
}
|
|
if all, _ := w.k.Store.All(context.Background()); len(all) != 0 {
|
|
t.Fatal("written")
|
|
}
|
|
}
|
|
|
|
func writeFileAll(path, text string) error {
|
|
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
|
return err
|
|
}
|
|
return os.WriteFile(path, []byte(text), 0o644)
|
|
}
|