The tests G added fold into existing ones as subtests (design 47 G §626, A-306: 171 tests down to 154, integration 9 down to 7). TestMeshWaitRefusesLoudlyAndNeverLoops runs its groups of one-second rows as parallel subtests, each on a runtime of its own, 15 s down to 5 s (A-311: the one-second floor of a wait's timeout stands, no clock is injected). The go.mod no longer requires meshconditionsclient, which no package loads (T4). Node names in the lines G adds are role names (T5). Builds: design 47 G §626, design 34 every-event §19, A-306, A-311; review round 2 findings T2, T3, T4, T5 Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
149 lines
7.2 KiB
Go
149 lines
7.2 KiB
Go
package ledger
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/buscalls"
|
|
)
|
|
|
|
var owner = buscalls.Author{Account: "ledger.anchor.m"}
|
|
|
|
// 1966/AC13, 1966/AC14 and the checks of an operation against its type (ADR 0597 §2, §5, §6, §8; design 25 § 12.4).
|
|
func TestApplyAppendsDerivesAndSnapshots(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
run func(t *testing.T, l *Ledger, f fakes)
|
|
}{
|
|
{"1966/AC13 an instance at sequence 4 is taken with 5, its state and snapshot the effect's result", func(t *testing.T, l *Ledger, f fakes) {
|
|
for i := 1; i <= 4; i++ {
|
|
applied(t, l, "decision/0290", "note", fmt.Sprintf(`{"says":"n%d"}`, i), owner)
|
|
}
|
|
if last, _ := f.history.Last("decision.0290"); last != 4 {
|
|
t.Fatalf("the instance is at %d, want 4", last)
|
|
}
|
|
got := applied(t, l, "decision/0290", "title", `{"title":"A ledger","ignored":"x"}`, owner)
|
|
if !got.Taken || got.Sequence != 5 || got.StateBehind {
|
|
t.Fatalf("answered %+v, want taken with 5", got)
|
|
}
|
|
raw, _, _ := f.state.Get("decision.0290")
|
|
state := record(t, raw)
|
|
want := `{"author":{"account":"ledger.anchor.m"},"notes":[{"says":"n1"},{"says":"n2"},{"says":"n3"},{"says":"n4"}],"sequence":5,"title":"A ledger"}`
|
|
if state.Sequence != 5 || string(state.State) != want {
|
|
t.Fatalf("the state is %d %s, want 5 %s", state.Sequence, state.State, want)
|
|
}
|
|
snap, _, _ := f.snapshots.Get("decision.0290")
|
|
if string(snap) != string(raw) {
|
|
t.Fatalf("the snapshot is %s, the state %s", snap, raw)
|
|
}
|
|
}},
|
|
{"1966/AC14 a full state bucket answers taken and state behind, and the next apply after room catches up", func(t *testing.T, l *Ledger, f fakes) {
|
|
applied(t, l, "decision/0290", "note", `{"says":"first"}`, owner)
|
|
f.state.Full = true
|
|
got := applied(t, l, "decision/0290", "note", `{"says":"while full"}`, owner)
|
|
if !got.Taken || got.Sequence != 2 || !got.StateBehind {
|
|
t.Fatalf("answered %+v, want taken with 2 and state behind", got)
|
|
}
|
|
if raw, _, _ := f.state.Get("decision.0290"); record(t, raw).Sequence != 1 {
|
|
t.Fatalf("the full state moved: %s", raw)
|
|
}
|
|
f.state.Full = false
|
|
got = applied(t, l, "decision/0290", "note", `{"says":"after room"}`, owner)
|
|
raw, _, _ := f.state.Get("decision.0290")
|
|
state := record(t, raw)
|
|
if got.StateBehind || state.Sequence != 3 || string(state.State) != `{"notes":[{"says":"first"},{"says":"while full"},{"says":"after room"}]}` {
|
|
t.Fatalf("after room: %+v, the state %d %s", got, state.Sequence, state.State)
|
|
}
|
|
}},
|
|
{"each effect: set an entry, remove it, remove the instance", func(t *testing.T, l *Ledger, f fakes) {
|
|
text := "the section's text"
|
|
applied(t, l, "decision/1", "section", fmt.Sprintf(`{"id":"a","text":%q,"digest":%q}`, text, Digest(text)), owner)
|
|
applied(t, l, "decision/1", "section", fmt.Sprintf(`{"id":"b","text":"b","digest":%q}`, Digest("b")), owner)
|
|
applied(t, l, "decision/1", "drop-section", `{"id":"a"}`, owner)
|
|
raw, _, _ := f.state.Get("decision.1")
|
|
if got := string(record(t, raw).State); got != fmt.Sprintf(`{"sections":{"b":{"digest":%q}}}`, Digest("b")) {
|
|
t.Fatalf("the state is %s", got)
|
|
}
|
|
applied(t, l, "decision/1", "withdraw", `{}`, owner)
|
|
raw, _, _ = f.state.Get("decision.1")
|
|
if got := record(t, raw); got.Sequence != 4 || string(got.State) != "null" {
|
|
t.Fatalf("the removed instance is %d %s", got.Sequence, got.State)
|
|
}
|
|
}},
|
|
{"an operation outside its type, a body off its schema and a digest off its text are refused", func(t *testing.T, l *Ledger, f fakes) {
|
|
for _, c := range []struct{ instance, operation, body, refusal string }{
|
|
{"listing/a17", "title", `{}`, `no type "listing"`},
|
|
{"decision/0290", "accept", `{}`, `no operation "accept"`},
|
|
{"decision/0290", "title", `{"title":7}`, "field title is a string"},
|
|
{"decision/0290", "section", `{"id":"a","text":"x","digest":"sha256:00"}`, "is not the digest of its text"},
|
|
{"decision/0290", "section", `{"text":"x"}`, "is not the digest"},
|
|
{"decision/0290", "drop-section", `{}`, "names its entry by its field id"},
|
|
{"decision", "title", `{}`, "is not an instance"},
|
|
{"decision/a.b", "title", `{}`, "is not an instance"},
|
|
} {
|
|
_, err := l.Apply(buscalls.ApplyRequest{Instance: c.instance, Operation: c.operation, Body: json.RawMessage(c.body)}, owner)
|
|
if err == nil || !strings.Contains(err.Error(), c.refusal) {
|
|
t.Errorf("%s %s %s: %v, want %q", c.instance, c.operation, c.body, err, c.refusal)
|
|
}
|
|
}
|
|
if last, _ := f.history.Last("decision.0290"); last != 0 {
|
|
t.Fatalf("a refused operation was appended: %d", last)
|
|
}
|
|
}},
|
|
}
|
|
for _, c := range cases {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
l, f := newLedger()
|
|
c.run(t, l, f)
|
|
})
|
|
}
|
|
// design 47 G §587: a ledger's fill is its history's against the ledger's cap, a store that cannot say it is an error naming
|
|
// the ledger; a removal takes an instance's operations, snapshot, state and index entries and answers what that was, an
|
|
// unknown instance answers none, and nothing of another instance goes.
|
|
t.Run("TheLedgerSaysItsFillAndRemovesAnInstanceWhole", func(t *testing.T) {
|
|
l, f := newLedger()
|
|
f.history.MaxBytes = 1 << 20
|
|
for _, n := range []string{"a", "b"} {
|
|
applied(t, l, "decision/0290", "note", `{"says":"`+n+`"}`, owner)
|
|
}
|
|
applied(t, l, "decision/0291", "note", `{"says":"other"}`, owner)
|
|
entry, _ := json.Marshal(buscalls.IndexEntry{Instance: "decision/0290", Sequence: 2})
|
|
other, _ := json.Marshal(buscalls.IndexEntry{Instance: "decision/0291", Sequence: 1})
|
|
_ = f.index.Put("t.decision.0290", entry)
|
|
_ = f.index.Put("t.decision.0291", other)
|
|
|
|
fill, err := l.Fill(buscalls.LedgerRequest{Ledger: "records"})
|
|
if err != nil || fill.Count != 3 || fill.Bytes == 0 || fill.MaxBytes != 1<<20 {
|
|
t.Fatalf("the fill is %+v (%v), want 3 messages against 1 MiB", fill, err)
|
|
}
|
|
got, err := l.Remove(buscalls.LedgerRemoveRequest{Ledger: "records", Instance: "decision/0290"})
|
|
if err != nil || got.Instance != "decision/0290" || got.Operations != 2 || got.Bytes == 0 {
|
|
t.Fatalf("the removal answered %+v (%v), want 2 operations and their bytes", got, err)
|
|
}
|
|
if kept, _ := f.history.Read("decision.0290", 1, 0); len(kept) != 0 {
|
|
t.Errorf("operations are left: %d", len(kept))
|
|
}
|
|
if raw, _, _ := f.state.Get("decision.0290"); raw != nil {
|
|
t.Errorf("the state is left: %s", raw)
|
|
}
|
|
if raw, _, _ := f.snapshots.Get("decision.0290"); raw != nil {
|
|
t.Errorf("the snapshot is left: %s", raw)
|
|
}
|
|
if held, _ := f.index.Entries("", 0); len(held) != 1 || held[0].Key != "t.decision.0291" {
|
|
t.Errorf("the index holds %v, want the other instance's entry alone", held)
|
|
}
|
|
if kept, _ := f.history.Read("decision.0291", 1, 0); len(kept) != 1 {
|
|
t.Errorf("another instance lost operations: %d left", len(kept))
|
|
}
|
|
if again, err := l.Remove(buscalls.LedgerRemoveRequest{Ledger: "records", Instance: "decision/0290"}); err != nil || again.Operations != 0 || again.Bytes != 0 {
|
|
t.Errorf("a removal said twice answered %+v (%v), want none", again, err)
|
|
}
|
|
f.history.FillFails = true
|
|
if _, err := l.Fill(buscalls.LedgerRequest{}); err == nil || !strings.Contains(err.Error(), "ledger records cannot say the fill") {
|
|
t.Errorf("a history that cannot say its fill answered %v", err)
|
|
}
|
|
})
|
|
}
|