mesh/delivery-group group adr-0307-state-history delivered: every member is delivered
mesh/delivery delivered
mesh/merge-gate pass: builds mesh-tools, node-tools → ace, g14, novox, shanks; no bus step; every machine composes with the change as it did without (4 of …
mesh/repo-check pass: THE CHANGE ALTERS ITS OWN CHECK (merge-check.sh): main's version judged it; the change's judges the pull requests after it merges; it…
mesh/state.history answers every value a bucket still keeps for a key, oldest first, to whoever may get it, by the same watch the runtime already uses, so no new grant is needed.
153 lines
5.8 KiB
Go
153 lines
5.8 KiB
Go
package runtime
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/novox/mesh-tools/node-tools/internal/bus"
|
|
"github.com/novox/mesh-tools/node-tools/internal/launch"
|
|
mt "github.com/novox/mesh-tools/node-tools/internal/meshtest"
|
|
)
|
|
|
|
// historyBuses are three modules on one runtime, each reached as a bundle reaches it (novox/hq ADR
|
|
// 0307): issues keeps a state `issues` in a bucket keeping 8 values a key, triage reads it as
|
|
// `issues.issues`, and other was issued no state.
|
|
func historyBuses(t *testing.T) (owner, reader, other launch.Bus) {
|
|
t.Helper()
|
|
mesh := mt.New(t)
|
|
mesh.BucketKeeping(t, "issues_issues", 8)
|
|
m := mt.MembershipOf("issues", "anchor", false, nil)
|
|
m.State = []bus.StateIssued{{Name: "issues", Bucket: "issues_issues", Writes: true}}
|
|
mesh.Issue(t, m)
|
|
r := mt.MembershipOf("triage", "anchor", false, nil)
|
|
r.State = []bus.StateIssued{{Name: "issues.issues", Bucket: "issues_issues"}}
|
|
mesh.Issue(t, r)
|
|
mesh.Issue(t, mt.MembershipOf("other", "anchor", false, nil))
|
|
conn := connect(t, "node-tools", "anchor")
|
|
for _, module := range []string{"issues", "triage", "other"} {
|
|
conn.Follow(module)
|
|
}
|
|
mt.Until(t, func() error {
|
|
for _, module := range []string{"issues", "triage", "other"} {
|
|
if conn.Membership(module) == nil {
|
|
return errors.New(module + " not issued yet")
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
all := &consumers{conn: conn, logf: t.Logf, of: map[string]*moduleEvents{}}
|
|
return all.forModule("issues"), all.forModule("triage"), all.forModule("other")
|
|
}
|
|
|
|
// kept is one value of a history answer, by the field names on the wire.
|
|
type kept struct {
|
|
Key string `json:"key"`
|
|
Value json.RawMessage `json:"value"`
|
|
Revision uint64 `json:"revision"`
|
|
Op string `json:"op"`
|
|
At string `json:"at"`
|
|
}
|
|
|
|
func historyOf(t *testing.T, b launch.Bus, state, key string) []kept {
|
|
t.Helper()
|
|
raw, err := ask(b, "history", map[string]any{"state": state, "key": key})
|
|
if err != nil {
|
|
t.Fatalf("history of %s %s: %v", state, key, err)
|
|
}
|
|
var h []kept
|
|
if err := json.Unmarshal(raw, &h); err != nil || h == nil {
|
|
t.Fatalf("history answered %s, not an array", raw)
|
|
}
|
|
return h
|
|
}
|
|
|
|
// A key's history is every value the bucket keeps, oldest first, each with its revision, op and time
|
|
// in RFC 3339 — a delete with no value; an unknown key's is []. The owner and a reader are served it,
|
|
// and reading it writes nothing.
|
|
func TestAKeysHistoryIsEveryValueKeptOldestFirst(t *testing.T) {
|
|
owner, reader, _ := historyBuses(t)
|
|
before := time.Now().Add(-time.Second)
|
|
var revisions []uint64
|
|
for _, v := range []string{`{"state":"open"}`, `{"state":"fixing"}`, `{"state":"resolved"}`} {
|
|
raw, err := ask(owner, "put", map[string]any{"state": "issues", "key": "502", "value": json.RawMessage(v)})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var r struct {
|
|
Revision uint64 `json:"revision"`
|
|
}
|
|
_ = json.Unmarshal(raw, &r)
|
|
revisions = append(revisions, r.Revision)
|
|
}
|
|
if _, err := ask(owner, "put", map[string]any{"state": "issues", "key": "503", "value": 1}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := ask(owner, "delete", map[string]any{"state": "issues", "key": "502"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
for who, b := range map[string]launch.Bus{"the owner": owner, "a reader": reader} {
|
|
name := "issues"
|
|
if b == reader {
|
|
name = "issues.issues"
|
|
}
|
|
h := historyOf(t, b, name, "502")
|
|
if len(h) != 4 {
|
|
t.Fatalf("%s read %d values of 502, not 4: %+v", who, len(h), h)
|
|
}
|
|
for i, v := range []string{`{"state":"open"}`, `{"state":"fixing"}`, `{"state":"resolved"}`} {
|
|
if h[i].Op != "put" || string(h[i].Value) != v || h[i].Revision != revisions[i] || h[i].Key != "502" {
|
|
t.Errorf("%s: value %d is %+v", who, i, h[i])
|
|
}
|
|
}
|
|
if h[3].Op != "delete" || len(h[3].Value) != 0 && string(h[3].Value) != "null" || h[3].Revision <= revisions[2] {
|
|
t.Errorf("%s: the delete reads %+v", who, h[3])
|
|
}
|
|
for i, k := range h {
|
|
at, err := time.Parse(time.RFC3339, k.At)
|
|
if err != nil || at.Before(before) || at.After(time.Now().Add(time.Second)) {
|
|
t.Errorf("%s: value %d's time %q is not RFC 3339 and now: %v", who, i, k.At, err)
|
|
}
|
|
if i > 0 && k.Revision <= h[i-1].Revision {
|
|
t.Errorf("%s: not oldest first: %+v", who, h)
|
|
}
|
|
}
|
|
if h := historyOf(t, b, name, "999"); len(h) != 0 {
|
|
t.Errorf("%s: an unknown key's history is %+v, not []", who, h)
|
|
}
|
|
}
|
|
// The history is read, never written: the key is still deleted and keeps the same four values.
|
|
if raw, err := ask(owner, "get", map[string]any{"state": "issues", "key": "502"}); err != nil || string(raw) != "null" {
|
|
t.Fatalf("after history, 502 reads %s %v", raw, err)
|
|
}
|
|
if h := historyOf(t, owner, "issues", "502"); len(h) != 4 {
|
|
t.Fatalf("reading the history changed it: %+v", h)
|
|
}
|
|
// A key the bucket holds nothing for answers [] on the wire, not null.
|
|
if raw, _ := ask(owner, "history", map[string]any{"state": "issues", "key": "999"}); string(raw) != "[]" {
|
|
t.Fatalf("an unknown key's history is %s on the wire", raw)
|
|
}
|
|
}
|
|
|
|
// A module the bucket was not issued to is refused its history with the reason, as it is a get.
|
|
func TestAKeysHistoryIsRefusedToAModuleNotIssuedTheState(t *testing.T) {
|
|
owner, _, other := historyBuses(t)
|
|
if _, err := ask(owner, "put", map[string]any{"state": "issues", "key": "1", "value": 1}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, name := range []string{"issues", "issues.issues"} {
|
|
_, err := ask(other, "history", map[string]any{"state": name, "key": "1"})
|
|
if err == nil || !strings.Contains(err.Error(), "keeps and reads no state called") ||
|
|
!strings.Contains(err.Error(), "issued: none") {
|
|
t.Errorf("history of %s to a module not issued it: %v", name, err)
|
|
}
|
|
}
|
|
if _, err := ask(owner, "history", map[string]any{"state": "issues", "key": "a b"}); err == nil ||
|
|
!strings.Contains(err.Error(), "not a key") {
|
|
t.Errorf("history of a key the bus cannot hold: %v", err)
|
|
}
|
|
}
|