103 lines
4.7 KiB
Go
103 lines
4.7 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)
|
|
})
|
|
}
|
|
}
|