Files
mesh-tools/node-tools/internal/ledger/read_test.go
T

121 lines
5.7 KiB
Go

package ledger
import (
"fmt"
"strings"
"testing"
"git.novox.be/novox/mesh-sdk/go/buscalls"
)
// 1966/AC15, the other reads of a ledger (get, query) and its repairs (ADR 0597 §8, §10; design 25 § 12.5).
func TestReadsAndRepairsOfALedger(t *testing.T) {
cases := []struct {
name string
run func(t *testing.T, l *Ledger, f fakes)
}{
{"1966/AC15 history answers an instance's operations oldest first with sequence and author", func(t *testing.T, l *Ledger, f fakes) {
op := buscalls.Author{Account: "ledger.anchor.m", Caller: "op@shanks"}
applied(t, l, "decision/1", "note", `{"says":"one"}`, owner)
applied(t, l, "decision/2", "note", `{"says":"elsewhere"}`, owner)
applied(t, l, "decision/1", "title", `{"title":"Two"}`, op)
applied(t, l, "decision/1", "note", `{"says":"three"}`, owner)
page, err := l.ReadHistory(buscalls.HistoryRequest{Instance: "decision/1", Limit: 2})
if err != nil {
t.Fatal(err)
}
var got []string
for _, o := range page.Operations {
got = append(got, fmt.Sprintf("%s@%d %s %s", o.Operation, o.Sequence, o.Author.Account, o.Author.Caller))
}
if strings.Join(got, "; ") != "note@1 ledger.anchor.m ; title@3 ledger.anchor.m op@shanks" || page.Next != 4 || page.Last != 4 {
t.Fatalf("the history is %q, next %d, last %d", got, page.Next, page.Last)
}
rest, _ := l.ReadHistory(buscalls.HistoryRequest{Instance: "decision/1", From: page.Next})
if len(rest.Operations) != 1 || rest.Operations[0].Sequence != 4 || rest.Next != 0 {
t.Fatalf("read on: %+v", rest)
}
}},
{"get answers the state and its sequence, null for an instance with none", func(t *testing.T, l *Ledger, f fakes) {
applied(t, l, "decision/1", "title", `{"title":"One"}`, owner)
got, err := l.Get(buscalls.InstanceRequest{Instance: "decision/1"})
if err != nil || got.Sequence != 1 || !strings.Contains(string(got.State), `"title":"One"`) {
t.Fatalf("got %+v, %v", got, err)
}
if none, err := l.Get(buscalls.InstanceRequest{Instance: "decision/9"}); err != nil || string(none.State) != "null" {
t.Fatalf("none is %+v, %v", none, err)
}
}},
{"query reads a declared index by key, and an undeclared one is refused", func(t *testing.T, l *Ledger, f fakes) {
_ = f.index.Put("One.decision.1", []byte(`{"instance":"decision/1","sequence":1}`))
_ = f.index.Put("Two.decision.2", []byte(`{"instance":"decision/2","sequence":2}`))
got, err := l.Query(buscalls.QueryRequest{Index: "by-title", Key: "One.>"})
if err != nil || len(got.Entries) != 1 || got.Entries[0] != (buscalls.IndexEntry{Instance: "decision/1", Sequence: 1}) {
t.Fatalf("queried %+v, %v", got, err)
}
if _, err := l.Query(buscalls.QueryRequest{Index: "by-date"}); err == nil || !strings.Contains(err.Error(), `no index "by-date"`) {
t.Fatalf("an undeclared index: %v", err)
}
}},
{"a lost state rebuilds from the history, an expired one from its snapshot, and lag says what is behind", func(t *testing.T, l *Ledger, f fakes) {
applied(t, l, "decision/1", "title", `{"title":"One"}`, owner)
applied(t, l, "decision/1", "note", `{"says":"kept"}`, owner)
want, _, _ := f.state.Get("decision.1")
f.state.kv = nil
lag, _ := l.Lag(buscalls.LedgerRequest{})
if len(lag.Instances) != 1 || lag.Instances[0].History != 2 || lag.Instances[0].State != 0 || lag.Instances[0].Snapshot != 2 {
t.Fatalf("lag says %+v", lag)
}
if got, err := l.Rebuild(buscalls.RebuildRequest{}); err != nil || got.Written != 1 {
t.Fatalf("rebuilt %+v, %v", got, err)
}
if got, _, _ := f.state.Get("decision.1"); string(record(t, got).State) != string(record(t, want).State) {
t.Fatalf("rebuilt from the history %s, want %s", got, want)
}
f.state.kv = nil
f.history.Expire()
if got, err := l.Rebuild(buscalls.RebuildRequest{Instance: "decision/1", From: buscalls.RebuildFromSnapshot}); err != nil || got.Written != 1 {
t.Fatalf("rebuilt %+v, %v", got, err)
}
if got, _, _ := f.state.Get("decision.1"); string(got) != string(want) {
t.Fatalf("rebuilt from the snapshot %s, want %s", got, want)
}
}},
{"reindex builds a declared index from the states, and lag says an index behind until it does", func(t *testing.T, l *Ledger, f fakes) {
applied(t, l, "decision/1", "title", `{"title":"One"}`, owner)
applied(t, l, "decision/2", "note", `{"says":"untitled"}`, owner)
_ = f.index.Put("stale.decision.9", []byte(`{}`))
if lag, _ := l.Lag(buscalls.LedgerRequest{}); len(lag.Indexes) != 1 || lag.Indexes[0].Behind != 1 {
t.Fatalf("lag says %+v", lag)
}
if got, err := l.Reindex(buscalls.ReindexRequest{Index: "by-title"}); err != nil || got.Written != 1 {
t.Fatalf("reindexed %+v, %v", got, err)
}
entries, _ := f.index.Entries("", 0)
if len(entries) != 1 || entries[0].Key != buscalls.IndexKey([]string{"One"}, "decision/1") || string(entries[0].Value) != `{"instance":"decision/1","sequence":1}` {
t.Fatalf("the index holds %+v", entries)
}
if lag, _ := l.Lag(buscalls.LedgerRequest{}); len(lag.Indexes) != 0 || len(lag.Instances) != 0 {
t.Fatalf("lag after reindex says %+v", lag)
}
}},
{"snapshot rewrites a snapshot behind its state", func(t *testing.T, l *Ledger, f fakes) {
applied(t, l, "decision/1", "title", `{"title":"One"}`, owner)
f.snapshots.kv = nil
if got, err := l.Snapshot(buscalls.InstanceRequest{}); err != nil || got.Written != 1 {
t.Fatalf("snapshot %+v, %v", got, err)
}
state, _, _ := f.state.Get("decision.1")
if snap, _, _ := f.snapshots.Get("decision.1"); string(snap) != string(state) {
t.Fatalf("the snapshot %s, the state %s", snap, state)
}
}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
l, f := newLedger()
c.run(t, l, f)
})
}
}