106 lines
3.1 KiB
Go
106 lines
3.1 KiB
Go
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")
|
|
}
|
|
}
|