121 lines
5.7 KiB
Go
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)
|
|
})
|
|
}
|
|
}
|