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) }) } }