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"}`)) _ = f.index.Put("Two.decision.2", []byte(`{"instance":"decision/2"}`)) got, err := l.Query(buscalls.QueryRequest{Index: "by-title", Key: "One.>"}) if err != nil || len(got.Entries) != 1 || string(got.Entries[0].Value) != `{"instance":"decision/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 ledger"}`, 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 != "One_ledger.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) }) } }