Files
mesh-sdk/go/stateobjects/tools_test.go
T

111 lines
3.9 KiB
Go

package stateobjects
import (
"errors"
"strings"
"testing"
"time"
"git.novox.be/novox/mesh-sdk/go/kvlimit"
"git.novox.be/novox/mesh-sdk/go/views"
)
type acts struct {
done []views.HandAct
err error
}
func (a *acts) RecordHandAct(h views.HandAct) error {
if a.err != nil {
return a.err
}
a.done = append(a.done, h)
return nil
}
// ADR 0666 §25: the four tools read what each store holds and drop only what is no longer live, each drop only with who
// asks and why, recorded as a hand act first.
func TestTheObjectToolsReadEverythingAndDropOnlyWhatIsNotLive(t *testing.T) {
m := newMemory()
m.modified = map[string]time.Time{}
v := m.values()
big := big(2 * kvlimit.ModuleStateValueBytes)
for _, k := range []string{"live", "gone"} {
if err := v.Put(k, big); err != nil {
t.Fatal(err)
}
}
// the two keys hold one object each (different keys): delete one key
if err := v.Delete("gone"); err != nil {
t.Fatal(err)
}
now := time.Date(2026, 11, 10, 12, 0, 0, 0, time.UTC)
rec := &acts{}
sentStore := newMemory()
sentStore.modified = map[string]time.Time{"reply.list.aa": now.Add(-48 * time.Hour), "event.done.bb": now.Add(-time.Hour)}
sentStore.objects = map[string][]byte{"reply.list.aa": []byte("1"), "event.done.bb": []byte("22")}
tools := Tools("m", []State{{Name: "views", Values: v}}, &Sent{Objects: memObjects{sentStore}}, rec, func() time.Time { return now })
by := map[string]func(map[string]any) (any, error){}
for _, tl := range tools {
by[tl.Name] = tl.Run
}
for _, name := range []string{"m_state_objects", "m_state_objects_drop", "m_sent_objects", "m_sent_objects_drop"} {
if by[name] == nil {
t.Fatalf("no tool %s", name)
}
}
read, err := by["m_state_objects"](map[string]any{})
if err != nil || read.(map[string]any)["key-gone"] != 1 {
t.Fatalf("the state's objects: %v, %v", read, err)
}
listed, err := by["m_sent_objects"](map[string]any{})
if objs := listed.(map[string]any)["objects"].([]SentObject); err != nil || len(objs) != 2 {
t.Fatalf("the sent objects: %v, %v", listed, err)
}
// each drop refuses without by and why, and records nothing
for _, tc := range []struct {
tool string
args map[string]any
}{
{"m_state_objects_drop", map[string]any{"why": "x"}},
{"m_state_objects_drop", map[string]any{"by": "a@b"}},
{"m_sent_objects_drop", map[string]any{"why": "x", "older-than": "24h"}},
{"m_sent_objects_drop", map[string]any{"by": "a@b", "why": "x", "older-than": "soon"}},
} {
if _, err := by[tc.tool](tc.args); err == nil {
t.Errorf("%s %v was accepted", tc.tool, tc.args)
}
}
if len(rec.done) != 0 {
t.Fatalf("a refused drop recorded %v", rec.done)
}
// the hand act is recorded first: one that cannot be recorded drops nothing
rec.err = errors.New("hand acts are down")
if _, err := by["m_state_objects_drop"](map[string]any{"by": "a@b", "why": "tidy"}); err == nil || !strings.Contains(err.Error(), "nothing was dropped") {
t.Fatalf("an unrecorded drop: %v", err)
}
if kept, _ := v.Kept(); len(kept) != 2 {
t.Fatalf("an unrecorded drop removed objects: %d left", len(kept))
}
rec.err = nil
// the state drop takes only the key-gone object
out, err := by["m_state_objects_drop"](map[string]any{"by": "a@b", "why": "tidy"})
if err != nil || len(out.(map[string]any)["dropped"].([]string)) != 1 {
t.Fatalf("the state drop: %v, %v", out, err)
}
if kept, _ := v.Kept(); len(kept) != 1 || kept[0].KeyGone {
t.Fatalf("the live object did not survive: %+v", kept)
}
// the sent drop takes only objects past the age
out, err = by["m_sent_objects_drop"](map[string]any{"by": "a@b", "why": "tidy", "older-than": "24h"})
if err != nil || len(out.(map[string]any)["dropped"].([]string)) != 1 {
t.Fatalf("the sent drop: %v, %v", out, err)
}
if _, ok := sentStore.objects["event.done.bb"]; !ok || len(sentStore.objects) != 1 {
t.Fatalf("the young sent object did not survive: %v", sentStore.objects)
}
if len(rec.done) != 2 {
t.Fatalf("%d hand acts, want 2", len(rec.done))
}
}