package main import ( "strings" "testing" "time" "git.novox.be/novox/mesh-sdk/go/buscalls" ) // The health on the ledger (to-be 55 ยง1.1, AC56): it names where the records are served from and the move's last check, is // unhealthy for a ledger or a stream that does not answer, a head behind its history, an index behind the ledger and a stream // at its cap, and counts what waits on a person (an idle draft, a part waiting on the judge, a form-only check refused) without // failing; a part waiting more than 30 minutes is asked of the operator once, and the ask clears when it is judged. func TestTheHealthOnTheLedgerNamesTheSourceAndFailsForWhatTheLedgerOwes(t *testing.T) { w, f := ledgerWorld(t) ls := w.k.Store.(*ledgerStore) if err := ls.BuildIndex(t.Context()); err != nil { t.Fatal(err) } f.fill = buscalls.StreamFilled{Fill: buscalls.Fill{Count: 3, Bytes: 100, MaxBytes: 512 << 20}} base := HealthAnswer{State: "healthy", Healthy: true, Why: "the database serves"} ans := w.k.ledgerHealth(base) if !ans.Healthy || !strings.Contains(ans.Why, "served from the ledger") || !strings.Contains(ans.Why, "search index holds") { t.Fatalf("%+v", ans) } // Each thing the ledger owes, one at a time. for name, c := range map[string]struct { set func() want string }{ "a head behind": {func() { f.lag = buscalls.Lagged{Instances: []buscalls.InstanceLag{{Instance: "decision/0290", History: 9, State: 7}}} }, "behind their history"}, "a stream at a cap": {func() { f.fill.Bytes = f.fill.MaxBytes }, "at its cap"}, "a stream that does not answer": {func() { f.noFill = true }, "does not answer"}, } { f.lag, f.noFill = buscalls.Lagged{}, false f.fill = buscalls.StreamFilled{Fill: buscalls.Fill{Bytes: 100, MaxBytes: 512 << 20}} c.set() got := w.k.ledgerHealth(base) if got.Healthy || !strings.Contains(got.Why, c.want) { t.Errorf("%s: %+v", name, got) } } f.lag, f.noFill, f.fill = buscalls.Lagged{}, false, buscalls.StreamFilled{} // An index behind the ledger: a record the ledger holds and the index does not. ls.index.mu.Lock() ls.index.docsOf = map[Key][]string{} ls.index.mu.Unlock() if got := w.k.ledgerHealth(base); got.Healthy || !strings.Contains(got.Why, "behind the ledger") { t.Errorf("an index behind: %+v", got) } _ = ls.BuildIndex(t.Context()) // What waits on a person is counted and leaves it healthy; a part waiting more than 30 minutes is asked once. w.k.States.State(askedState).Put("decision.0300.abc", map[string]any{"part": "Context", "record": "ADR 0300", "at": w.clock.Add(-time.Hour)}) w.k.States.State(draftsState).Put("decision.0301", Draft{Kind: KindDecision, Number: 301, Author: "a@n", Touched: w.clock.AddDate(0, 0, -8)}) got := w.k.ledgerHealth(base) if !got.Healthy || !strings.Contains(got.Why, "1 part(s) waiting") || !strings.Contains(got.Why, "1 draft(s) untouched") { t.Fatalf("%+v", got) } w.k.ledgerHealth(base) if len(w.told) != 1 { t.Fatalf("the operator was asked %d times", len(w.told)) } w.k.States.State(askedState).Delete("decision.0300.abc") w.k.ledgerHealth(base) if len(w.cleared) != 1 { t.Fatalf("the ask was not cleared: %v", w.cleared) } } // The records served from the database say so, and fail nothing. func TestTheHealthOnTheDatabaseSaysSo(t *testing.T) { db := newMemStore() states := newFakeStates() m := newMovableStore(db, newLedgerStore(newFakeLedger(t), states), states) k := &KB{Store: m, States: states} ans := k.ledgerHealth(HealthAnswer{State: "healthy", Healthy: true, Why: "the database serves"}) if !ans.Healthy || !strings.Contains(ans.Why, "served from the database") { t.Fatalf("%+v", ans) } }