package process import ( "context" "errors" "sync" "testing" "github.com/nats-io/nats.go/jetstream" stdio "git.novox.be/novox/mesh-sdk/go" "git.novox.be/novox/mesh-sdk/go/asker" ) // fakeEntry is a fakeKV's KeyValueEntry: only Key, Value and Revision are read by State. type fakeEntry struct { jetstream.KeyValueEntry key string value []byte rev uint64 } func (e fakeEntry) Key() string { return e.key } func (e fakeEntry) Value() []byte { return e.value } func (e fakeEntry) Revision() uint64 { return e.rev } // fakeKV is an in-memory jetstream.KeyValue: embedding the nil interface satisfies every method this test // never calls, so only Get, Create and Update need a body (1188/AC2's "a fake KV"). type fakeKV struct { jetstream.KeyValue mu sync.Mutex values map[string][]byte revs map[string]uint64 next uint64 } func newFakeKV() *fakeKV { return &fakeKV{values: map[string][]byte{}, revs: map[string]uint64{}} } func (f *fakeKV) Get(_ context.Context, key string) (jetstream.KeyValueEntry, error) { f.mu.Lock() defer f.mu.Unlock() v, ok := f.values[key] if !ok { return nil, jetstream.ErrKeyNotFound } return fakeEntry{key: key, value: v, rev: f.revs[key]}, nil } func (f *fakeKV) Create(_ context.Context, key string, value []byte, _ ...jetstream.KVCreateOpt) (uint64, error) { f.mu.Lock() defer f.mu.Unlock() if _, ok := f.values[key]; ok { return 0, jetstream.ErrKeyExists } f.next++ f.values[key], f.revs[key] = value, f.next return f.next, nil } func (f *fakeKV) Update(_ context.Context, key string, value []byte, revision uint64) (uint64, error) { f.mu.Lock() defer f.mu.Unlock() if f.revs[key] != revision { return 0, jetstream.ErrKeyRevisionMismatch } f.next++ f.values[key], f.revs[key] = value, f.next return f.next, nil } func stateOn(kv jetstream.KeyValue) State { return State{Module: "mesh-mail", Name: "sends", kv: func(context.Context) (jetstream.KeyValue, error) { return kv, nil }} } // 1188/AC2: given the process connection's state type, when used as go/asker's Store, then it compiles and // behaves as the interface says, against a fake KV. func TestStateSatisfiesAskerStore(t *testing.T) { var _ asker.Store = State{} s := stateOn(newFakeKV()) if e, err := s.Get("ask.1"); err != nil || e != nil { t.Fatalf("Get of an absent key: %v, %v", e, err) } rev, err := s.Create("ask.1", map[string]any{"a": 1}) if err != nil { t.Fatalf("Create: %v", err) } e, err := s.Get("ask.1") if err != nil || e == nil || e.Revision != rev { t.Fatalf("Get after Create: %+v, %v", e, err) } if _, err := s.Create("ask.1", map[string]any{"a": 2}); !errors.Is(err, stdio.ErrChanged) { t.Fatalf("Create of an existing key: %v, want stdio.ErrChanged", err) } rev2, err := s.Update("ask.1", map[string]any{"a": 3}, rev) if err != nil { t.Fatalf("Update at the revision read: %v", err) } if _, err := s.Update("ask.1", map[string]any{"a": 4}, rev); !errors.Is(err, stdio.ErrChanged) { t.Fatalf("Update at a stale revision: %v, want stdio.ErrChanged", err) } if rev2 == rev { t.Fatal("Update did not advance the revision") } }