refactor(test): tighten AX naming in tests
All checks were successful
Security Scan / security (push) Successful in 9s
Test / test (push) Successful in 1m43s

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Virgil 2026-03-30 18:23:56 +00:00
parent 2eedf1e937
commit f144c5eb01
3 changed files with 833 additions and 833 deletions

View file

@ -16,31 +16,31 @@ import (
// ---------------------------------------------------------------------------
func TestEvents_Watch_Good_SpecificKey(t *testing.T) {
s, _ := New(":memory:")
defer s.Close()
storeInstance, _ := New(":memory:")
defer storeInstance.Close()
w := s.Watch("config", "theme")
defer s.Unwatch(w)
watcher := storeInstance.Watch("config", "theme")
defer storeInstance.Unwatch(watcher)
require.NoError(t, s.Set("config", "theme", "dark"))
require.NoError(t, storeInstance.Set("config", "theme", "dark"))
select {
case e := <-w.Events:
assert.Equal(t, EventSet, e.Type)
assert.Equal(t, "config", e.Group)
assert.Equal(t, "theme", e.Key)
assert.Equal(t, "dark", e.Value)
assert.False(t, e.Timestamp.IsZero())
case event := <-watcher.Events:
assert.Equal(t, EventSet, event.Type)
assert.Equal(t, "config", event.Group)
assert.Equal(t, "theme", event.Key)
assert.Equal(t, "dark", event.Value)
assert.False(t, event.Timestamp.IsZero())
case <-time.After(time.Second):
t.Fatal("timed out waiting for event")
}
// A Set to a different key in the same group should NOT trigger this watcher.
require.NoError(t, s.Set("config", "colour", "blue"))
require.NoError(t, storeInstance.Set("config", "colour", "blue"))
select {
case e := <-w.Events:
t.Fatalf("unexpected event for non-matching key: %+v", e)
case event := <-watcher.Events:
t.Fatalf("unexpected event for non-matching key: %+v", event)
case <-time.After(50 * time.Millisecond):
// Expected: no event.
}
@ -51,33 +51,33 @@ func TestEvents_Watch_Good_SpecificKey(t *testing.T) {
// ---------------------------------------------------------------------------
func TestEvents_Watch_Good_WildcardKey(t *testing.T) {
s, _ := New(":memory:")
defer s.Close()
storeInstance, _ := New(":memory:")
defer storeInstance.Close()
w := s.Watch("config", "*")
defer s.Unwatch(w)
watcher := storeInstance.Watch("config", "*")
defer storeInstance.Unwatch(watcher)
require.NoError(t, s.Set("config", "theme", "dark"))
require.NoError(t, s.Set("config", "colour", "blue"))
require.NoError(t, storeInstance.Set("config", "theme", "dark"))
require.NoError(t, storeInstance.Set("config", "colour", "blue"))
received := drainEvents(w.Events, 2, time.Second)
received := drainEvents(watcher.Events, 2, time.Second)
require.Len(t, received, 2)
assert.Equal(t, "theme", received[0].Key)
assert.Equal(t, "colour", received[1].Key)
}
func TestEvents_Watch_Good_GroupMismatch(t *testing.T) {
s, _ := New(":memory:")
defer s.Close()
storeInstance, _ := New(":memory:")
defer storeInstance.Close()
w := s.Watch("config", "*")
defer s.Unwatch(w)
watcher := storeInstance.Watch("config", "*")
defer storeInstance.Unwatch(watcher)
require.NoError(t, s.Set("other", "theme", "dark"))
require.NoError(t, storeInstance.Set("other", "theme", "dark"))
select {
case e := <-w.Events:
t.Fatalf("unexpected event for non-matching group: %+v", e)
case event := <-watcher.Events:
t.Fatalf("unexpected event for non-matching group: %+v", event)
case <-time.After(50 * time.Millisecond):
// Expected: no event.
}
@ -88,18 +88,18 @@ func TestEvents_Watch_Good_GroupMismatch(t *testing.T) {
// ---------------------------------------------------------------------------
func TestEvents_Watch_Good_WildcardAll(t *testing.T) {
s, _ := New(":memory:")
defer s.Close()
storeInstance, _ := New(":memory:")
defer storeInstance.Close()
w := s.Watch("*", "*")
defer s.Unwatch(w)
watcher := storeInstance.Watch("*", "*")
defer storeInstance.Unwatch(watcher)
require.NoError(t, s.Set("g1", "k1", "v1"))
require.NoError(t, s.Set("g2", "k2", "v2"))
require.NoError(t, s.Delete("g1", "k1"))
require.NoError(t, s.DeleteGroup("g2"))
require.NoError(t, storeInstance.Set("g1", "k1", "v1"))
require.NoError(t, storeInstance.Set("g2", "k2", "v2"))
require.NoError(t, storeInstance.Delete("g1", "k1"))
require.NoError(t, storeInstance.DeleteGroup("g2"))
received := drainEvents(w.Events, 4, time.Second)
received := drainEvents(watcher.Events, 4, time.Second)
require.Len(t, received, 4)
assert.Equal(t, EventSet, received[0].Type)
assert.Equal(t, EventSet, received[1].Type)
@ -112,36 +112,36 @@ func TestEvents_Watch_Good_WildcardAll(t *testing.T) {
// ---------------------------------------------------------------------------
func TestEvents_Unwatch_Good_StopsDelivery(t *testing.T) {
s, _ := New(":memory:")
defer s.Close()
storeInstance, _ := New(":memory:")
defer storeInstance.Close()
w := s.Watch("g", "k")
s.Unwatch(w)
watcher := storeInstance.Watch("g", "k")
storeInstance.Unwatch(watcher)
// Channel should be closed.
_, open := <-w.Events
_, open := <-watcher.Events
assert.False(t, open, "channel should be closed after Unwatch")
// Set after Unwatch should not panic or block.
require.NoError(t, s.Set("g", "k", "v"))
require.NoError(t, storeInstance.Set("g", "k", "v"))
}
func TestEvents_Unwatch_Good_Idempotent(t *testing.T) {
s, _ := New(":memory:")
defer s.Close()
storeInstance, _ := New(":memory:")
defer storeInstance.Close()
w := s.Watch("g", "k")
watcher := storeInstance.Watch("g", "k")
// Calling Unwatch multiple times should not panic.
s.Unwatch(w)
s.Unwatch(w) // second call is a no-op
storeInstance.Unwatch(watcher)
storeInstance.Unwatch(watcher) // second call is a no-op
}
func TestEvents_Unwatch_Good_NilWatcher(t *testing.T) {
s, _ := New(":memory:")
defer s.Close()
storeInstance, _ := New(":memory:")
defer storeInstance.Close()
s.Unwatch(nil)
storeInstance.Unwatch(nil)
}
// ---------------------------------------------------------------------------
@ -149,24 +149,24 @@ func TestEvents_Unwatch_Good_NilWatcher(t *testing.T) {
// ---------------------------------------------------------------------------
func TestEvents_Watch_Good_DeleteEvent(t *testing.T) {
s, _ := New(":memory:")
defer s.Close()
storeInstance, _ := New(":memory:")
defer storeInstance.Close()
w := s.Watch("g", "k")
defer s.Unwatch(w)
watcher := storeInstance.Watch("g", "k")
defer storeInstance.Unwatch(watcher)
require.NoError(t, s.Set("g", "k", "v"))
require.NoError(t, storeInstance.Set("g", "k", "v"))
// Drain the Set event.
<-w.Events
<-watcher.Events
require.NoError(t, s.Delete("g", "k"))
require.NoError(t, storeInstance.Delete("g", "k"))
select {
case e := <-w.Events:
assert.Equal(t, EventDelete, e.Type)
assert.Equal(t, "g", e.Group)
assert.Equal(t, "k", e.Key)
assert.Empty(t, e.Value, "Delete events should have empty Value")
case event := <-watcher.Events:
assert.Equal(t, EventDelete, event.Type)
assert.Equal(t, "g", event.Group)
assert.Equal(t, "k", event.Key)
assert.Empty(t, event.Value, "Delete events should have empty Value")
case <-time.After(time.Second):
t.Fatal("timed out waiting for delete event")
}
@ -177,26 +177,26 @@ func TestEvents_Watch_Good_DeleteEvent(t *testing.T) {
// ---------------------------------------------------------------------------
func TestEvents_Watch_Good_DeleteGroupEvent(t *testing.T) {
s, _ := New(":memory:")
defer s.Close()
storeInstance, _ := New(":memory:")
defer storeInstance.Close()
// A wildcard-key watcher for the group should receive DeleteGroup events.
w := s.Watch("g", "*")
defer s.Unwatch(w)
watcher := storeInstance.Watch("g", "*")
defer storeInstance.Unwatch(watcher)
require.NoError(t, s.Set("g", "a", "1"))
require.NoError(t, s.Set("g", "b", "2"))
require.NoError(t, storeInstance.Set("g", "a", "1"))
require.NoError(t, storeInstance.Set("g", "b", "2"))
// Drain Set events.
<-w.Events
<-w.Events
<-watcher.Events
<-watcher.Events
require.NoError(t, s.DeleteGroup("g"))
require.NoError(t, storeInstance.DeleteGroup("g"))
select {
case e := <-w.Events:
assert.Equal(t, EventDeleteGroup, e.Type)
assert.Equal(t, "g", e.Group)
assert.Empty(t, e.Key, "DeleteGroup events should have empty Key")
case event := <-watcher.Events:
assert.Equal(t, EventDeleteGroup, event.Type)
assert.Equal(t, "g", event.Group)
assert.Empty(t, event.Key, "DeleteGroup events should have empty Key")
case <-time.After(time.Second):
t.Fatal("timed out waiting for delete_group event")
}
@ -207,21 +207,21 @@ func TestEvents_Watch_Good_DeleteGroupEvent(t *testing.T) {
// ---------------------------------------------------------------------------
func TestEvents_OnChange_Good_Fires(t *testing.T) {
s, _ := New(":memory:")
defer s.Close()
storeInstance, _ := New(":memory:")
defer storeInstance.Close()
var events []Event
var mu sync.Mutex
unreg := s.OnChange(func(e Event) {
unregister := storeInstance.OnChange(func(event Event) {
mu.Lock()
events = append(events, e)
events = append(events, event)
mu.Unlock()
})
defer unreg()
defer unregister()
require.NoError(t, s.Set("g", "k", "v"))
require.NoError(t, s.Delete("g", "k"))
require.NoError(t, storeInstance.Set("g", "k", "v"))
require.NoError(t, storeInstance.Delete("g", "k"))
mu.Lock()
defer mu.Unlock()
@ -235,25 +235,25 @@ func TestEvents_OnChange_Good_Fires(t *testing.T) {
// ---------------------------------------------------------------------------
func TestEvents_OnChange_Good_Unregister(t *testing.T) {
s, _ := New(":memory:")
defer s.Close()
storeInstance, _ := New(":memory:")
defer storeInstance.Close()
var count atomic.Int32
unreg := s.OnChange(func(e Event) {
unregister := storeInstance.OnChange(func(event Event) {
count.Add(1)
})
require.NoError(t, s.Set("g", "k", "v1"))
require.NoError(t, storeInstance.Set("g", "k", "v1"))
assert.Equal(t, int32(1), count.Load())
unreg()
unregister()
require.NoError(t, s.Set("g", "k", "v2"))
require.NoError(t, storeInstance.Set("g", "k", "v2"))
assert.Equal(t, int32(1), count.Load(), "callback should not fire after unregister")
// Calling unreg again should not panic.
unreg()
// Calling unregister again should not panic.
unregister()
}
// ---------------------------------------------------------------------------
@ -261,16 +261,16 @@ func TestEvents_OnChange_Good_Unregister(t *testing.T) {
// ---------------------------------------------------------------------------
func TestEvents_OnChange_Good_ReentrantSubscriptions(t *testing.T) {
s, _ := New(":memory:")
defer s.Close()
storeInstance, _ := New(":memory:")
defer storeInstance.Close()
var callbackCount atomic.Int32
var unregister func()
unregister = s.OnChange(func(event Event) {
unregister = storeInstance.OnChange(func(event Event) {
callbackCount.Add(1)
nestedWatcher := s.Watch("nested", "*")
s.Unwatch(nestedWatcher)
nestedWatcher := storeInstance.Watch("nested", "*")
storeInstance.Unwatch(nestedWatcher)
if unregister != nil {
unregister()
@ -279,7 +279,7 @@ func TestEvents_OnChange_Good_ReentrantSubscriptions(t *testing.T) {
writeDone := make(chan error, 1)
go func() {
writeDone <- s.Set("g", "k", "v")
writeDone <- storeInstance.Set("g", "k", "v")
}()
select {
@ -292,7 +292,7 @@ func TestEvents_OnChange_Good_ReentrantSubscriptions(t *testing.T) {
assert.Equal(t, int32(1), callbackCount.Load())
// The callback unregistered itself, so later writes should not increment it.
require.NoError(t, s.Set("g", "k", "v2"))
require.NoError(t, storeInstance.Set("g", "k", "v2"))
assert.Equal(t, int32(1), callbackCount.Load())
}
@ -301,18 +301,18 @@ func TestEvents_OnChange_Good_ReentrantSubscriptions(t *testing.T) {
// ---------------------------------------------------------------------------
func TestEvents_Watch_Good_BufferFullDoesNotBlock(t *testing.T) {
s, _ := New(":memory:")
defer s.Close()
storeInstance, _ := New(":memory:")
defer storeInstance.Close()
w := s.Watch("g", "*")
defer s.Unwatch(w)
watcher := storeInstance.Watch("g", "*")
defer storeInstance.Unwatch(watcher)
// Fill the buffer (cap 16) plus extra writes. None should block.
done := make(chan struct{})
go func() {
defer close(done)
for i := range 32 {
require.NoError(t, s.Set("g", core.Sprintf("k%d", i), "v"))
require.NoError(t, storeInstance.Set("g", core.Sprintf("k%d", i), "v"))
}
}()
@ -327,7 +327,7 @@ func TestEvents_Watch_Good_BufferFullDoesNotBlock(t *testing.T) {
var received int
for range watcherEventBufferCapacity {
select {
case <-w.Events:
case <-watcher.Events:
received++
default:
}
@ -340,29 +340,29 @@ func TestEvents_Watch_Good_BufferFullDoesNotBlock(t *testing.T) {
// ---------------------------------------------------------------------------
func TestEvents_Watch_Good_MultipleWatchersSameKey(t *testing.T) {
s, _ := New(":memory:")
defer s.Close()
storeInstance, _ := New(":memory:")
defer storeInstance.Close()
w1 := s.Watch("g", "k")
w2 := s.Watch("g", "k")
defer s.Unwatch(w1)
defer s.Unwatch(w2)
firstWatcher := storeInstance.Watch("g", "k")
secondWatcher := storeInstance.Watch("g", "k")
defer storeInstance.Unwatch(firstWatcher)
defer storeInstance.Unwatch(secondWatcher)
require.NoError(t, s.Set("g", "k", "v"))
require.NoError(t, storeInstance.Set("g", "k", "v"))
// Both watchers should receive the event independently.
select {
case e := <-w1.Events:
assert.Equal(t, EventSet, e.Type)
case event := <-firstWatcher.Events:
assert.Equal(t, EventSet, event.Type)
case <-time.After(time.Second):
t.Fatal("w1 timed out")
t.Fatal("firstWatcher timed out")
}
select {
case e := <-w2.Events:
assert.Equal(t, EventSet, e.Type)
case event := <-secondWatcher.Events:
assert.Equal(t, EventSet, event.Type)
case <-time.After(time.Second):
t.Fatal("w2 timed out")
t.Fatal("secondWatcher timed out")
}
}
@ -371,39 +371,39 @@ func TestEvents_Watch_Good_MultipleWatchersSameKey(t *testing.T) {
// ---------------------------------------------------------------------------
func TestEvents_Watch_Good_ConcurrentWatchUnwatch(t *testing.T) {
s, _ := New(":memory:")
defer s.Close()
storeInstance, _ := New(":memory:")
defer storeInstance.Close()
const goroutines = 10
const ops = 50
var wg sync.WaitGroup
var waitGroup sync.WaitGroup
// Writers — continuously mutate the store.
wg.Go(func() {
waitGroup.Go(func() {
for i := range goroutines * ops {
_ = s.Set("g", core.Sprintf("k%d", i), "v")
_ = storeInstance.Set("g", core.Sprintf("k%d", i), "v")
}
})
// Watchers — add and remove watchers concurrently.
for range goroutines {
wg.Go(func() {
waitGroup.Go(func() {
for range ops {
w := s.Watch("g", "*")
watcher := storeInstance.Watch("g", "*")
// Drain a few events to exercise the channel path.
for range 3 {
select {
case <-w.Events:
case <-watcher.Events:
case <-time.After(time.Millisecond):
}
}
s.Unwatch(w)
storeInstance.Unwatch(watcher)
}
})
}
wg.Wait()
waitGroup.Wait()
// If we got here without a data race or panic, the test passes.
}
@ -412,24 +412,24 @@ func TestEvents_Watch_Good_ConcurrentWatchUnwatch(t *testing.T) {
// ---------------------------------------------------------------------------
func TestEvents_Watch_Good_ScopedStoreEvents(t *testing.T) {
s, _ := New(":memory:")
defer s.Close()
storeInstance, _ := New(":memory:")
defer storeInstance.Close()
sc, err := NewScoped(s, "tenant-a")
scopedStore, err := NewScoped(storeInstance, "tenant-a")
require.NoError(t, err)
// Watch on the underlying store with the full prefixed group name.
w := s.Watch("tenant-a:config", "theme")
defer s.Unwatch(w)
watcher := storeInstance.Watch("tenant-a:config", "theme")
defer storeInstance.Unwatch(watcher)
require.NoError(t, sc.Set("config", "theme", "dark"))
require.NoError(t, scopedStore.Set("config", "theme", "dark"))
select {
case e := <-w.Events:
assert.Equal(t, EventSet, e.Type)
assert.Equal(t, "tenant-a:config", e.Group)
assert.Equal(t, "theme", e.Key)
assert.Equal(t, "dark", e.Value)
case event := <-watcher.Events:
assert.Equal(t, EventSet, event.Type)
assert.Equal(t, "tenant-a:config", event.Group)
assert.Equal(t, "theme", event.Key)
assert.Equal(t, "dark", event.Value)
case <-time.After(time.Second):
t.Fatal("timed out waiting for scoped store event")
}
@ -451,20 +451,20 @@ func TestEvents_EventType_Good_String(t *testing.T) {
// ---------------------------------------------------------------------------
func TestEvents_Watch_Good_SetWithTTLEmitsEvent(t *testing.T) {
s, _ := New(":memory:")
defer s.Close()
storeInstance, _ := New(":memory:")
defer storeInstance.Close()
w := s.Watch("g", "k")
defer s.Unwatch(w)
watcher := storeInstance.Watch("g", "k")
defer storeInstance.Unwatch(watcher)
require.NoError(t, s.SetWithTTL("g", "k", "ttl-val", time.Hour))
require.NoError(t, storeInstance.SetWithTTL("g", "k", "ttl-val", time.Hour))
select {
case e := <-w.Events:
assert.Equal(t, EventSet, e.Type)
assert.Equal(t, "g", e.Group)
assert.Equal(t, "k", e.Key)
assert.Equal(t, "ttl-val", e.Value)
case event := <-watcher.Events:
assert.Equal(t, EventSet, event.Type)
assert.Equal(t, "g", event.Group)
assert.Equal(t, "k", event.Key)
assert.Equal(t, "ttl-val", event.Value)
case <-time.After(time.Second):
t.Fatal("timed out waiting for SetWithTTL event")
}
@ -475,13 +475,13 @@ func TestEvents_Watch_Good_SetWithTTLEmitsEvent(t *testing.T) {
// ---------------------------------------------------------------------------
// drainEvents collects up to n events from ch within the given timeout.
func drainEvents(ch <-chan Event, n int, timeout time.Duration) []Event {
func drainEvents(ch <-chan Event, count int, timeout time.Duration) []Event {
var events []Event
deadline := time.After(timeout)
for range n {
for range count {
select {
case e := <-ch:
events = append(events, e)
case event := <-ch:
events = append(events, event)
case <-deadline:
return events
}

View file

@ -14,52 +14,52 @@ import (
// ---------------------------------------------------------------------------
func TestScope_NewScoped_Good(t *testing.T) {
s, _ := New(":memory:")
defer s.Close()
storeInstance, _ := New(":memory:")
defer storeInstance.Close()
sc, err := NewScoped(s, "tenant-1")
scopedStore, err := NewScoped(storeInstance, "tenant-1")
require.NoError(t, err)
require.NotNil(t, sc)
assert.Equal(t, "tenant-1", sc.Namespace())
require.NotNil(t, scopedStore)
assert.Equal(t, "tenant-1", scopedStore.Namespace())
}
func TestScope_NewScoped_Good_AlphanumericHyphens(t *testing.T) {
s, _ := New(":memory:")
defer s.Close()
storeInstance, _ := New(":memory:")
defer storeInstance.Close()
valid := []string{"abc", "ABC", "123", "a-b-c", "tenant-42", "A1-B2"}
for _, ns := range valid {
sc, err := NewScoped(s, ns)
require.NoError(t, err, "namespace %q should be valid", ns)
require.NotNil(t, sc)
for _, namespace := range valid {
scopedStore, err := NewScoped(storeInstance, namespace)
require.NoError(t, err, "namespace %q should be valid", namespace)
require.NotNil(t, scopedStore)
}
}
func TestScope_NewScoped_Bad_Empty(t *testing.T) {
s, _ := New(":memory:")
defer s.Close()
storeInstance, _ := New(":memory:")
defer storeInstance.Close()
_, err := NewScoped(s, "")
_, err := NewScoped(storeInstance, "")
require.Error(t, err)
assert.Contains(t, err.Error(), "invalid")
}
func TestScope_NewScoped_Bad_InvalidChars(t *testing.T) {
s, _ := New(":memory:")
defer s.Close()
storeInstance, _ := New(":memory:")
defer storeInstance.Close()
invalid := []string{"foo.bar", "foo:bar", "foo bar", "foo/bar", "foo_bar", "tenant!", "@ns"}
for _, ns := range invalid {
_, err := NewScoped(s, ns)
require.Error(t, err, "namespace %q should be invalid", ns)
for _, namespace := range invalid {
_, err := NewScoped(storeInstance, namespace)
require.Error(t, err, "namespace %q should be invalid", namespace)
}
}
func TestScope_NewScopedWithQuota_Bad_InvalidNamespace(t *testing.T) {
s, _ := New(":memory:")
defer s.Close()
storeInstance, _ := New(":memory:")
defer storeInstance.Close()
_, err := NewScopedWithQuota(s, "tenant_a", QuotaConfig{MaxKeys: 1})
_, err := NewScopedWithQuota(storeInstance, "tenant_a", QuotaConfig{MaxKeys: 1})
require.Error(t, err)
assert.Contains(t, err.Error(), "store.NewScoped")
}
@ -69,104 +69,104 @@ func TestScope_NewScopedWithQuota_Bad_InvalidNamespace(t *testing.T) {
// ---------------------------------------------------------------------------
func TestScope_ScopedStore_Good_SetGet(t *testing.T) {
s, _ := New(":memory:")
defer s.Close()
storeInstance, _ := New(":memory:")
defer storeInstance.Close()
sc, _ := NewScoped(s, "tenant-a")
require.NoError(t, sc.Set("config", "theme", "dark"))
scopedStore, _ := NewScoped(storeInstance, "tenant-a")
require.NoError(t, scopedStore.Set("config", "theme", "dark"))
val, err := sc.Get("config", "theme")
value, err := scopedStore.Get("config", "theme")
require.NoError(t, err)
assert.Equal(t, "dark", val)
assert.Equal(t, "dark", value)
}
func TestScope_ScopedStore_Good_PrefixedInUnderlyingStore(t *testing.T) {
s, _ := New(":memory:")
defer s.Close()
storeInstance, _ := New(":memory:")
defer storeInstance.Close()
sc, _ := NewScoped(s, "tenant-a")
require.NoError(t, sc.Set("config", "key", "val"))
scopedStore, _ := NewScoped(storeInstance, "tenant-a")
require.NoError(t, scopedStore.Set("config", "key", "val"))
// The underlying store should have the prefixed group name.
val, err := s.Get("tenant-a:config", "key")
value, err := storeInstance.Get("tenant-a:config", "key")
require.NoError(t, err)
assert.Equal(t, "val", val)
assert.Equal(t, "val", value)
// Direct access without prefix should fail.
_, err = s.Get("config", "key")
_, err = storeInstance.Get("config", "key")
assert.True(t, core.Is(err, NotFoundError))
}
func TestScope_ScopedStore_Good_NamespaceIsolation(t *testing.T) {
s, _ := New(":memory:")
defer s.Close()
storeInstance, _ := New(":memory:")
defer storeInstance.Close()
a, _ := NewScoped(s, "tenant-a")
b, _ := NewScoped(s, "tenant-b")
alphaStore, _ := NewScoped(storeInstance, "tenant-a")
betaStore, _ := NewScoped(storeInstance, "tenant-b")
require.NoError(t, a.Set("config", "colour", "blue"))
require.NoError(t, b.Set("config", "colour", "red"))
require.NoError(t, alphaStore.Set("config", "colour", "blue"))
require.NoError(t, betaStore.Set("config", "colour", "red"))
va, err := a.Get("config", "colour")
alphaValue, err := alphaStore.Get("config", "colour")
require.NoError(t, err)
assert.Equal(t, "blue", va)
assert.Equal(t, "blue", alphaValue)
vb, err := b.Get("config", "colour")
betaValue, err := betaStore.Get("config", "colour")
require.NoError(t, err)
assert.Equal(t, "red", vb)
assert.Equal(t, "red", betaValue)
}
func TestScope_ScopedStore_Good_Delete(t *testing.T) {
s, _ := New(":memory:")
defer s.Close()
storeInstance, _ := New(":memory:")
defer storeInstance.Close()
sc, _ := NewScoped(s, "tenant-a")
require.NoError(t, sc.Set("g", "k", "v"))
require.NoError(t, sc.Delete("g", "k"))
scopedStore, _ := NewScoped(storeInstance, "tenant-a")
require.NoError(t, scopedStore.Set("g", "k", "v"))
require.NoError(t, scopedStore.Delete("g", "k"))
_, err := sc.Get("g", "k")
_, err := scopedStore.Get("g", "k")
assert.True(t, core.Is(err, NotFoundError))
}
func TestScope_ScopedStore_Good_DeleteGroup(t *testing.T) {
s, _ := New(":memory:")
defer s.Close()
storeInstance, _ := New(":memory:")
defer storeInstance.Close()
sc, _ := NewScoped(s, "tenant-a")
require.NoError(t, sc.Set("g", "a", "1"))
require.NoError(t, sc.Set("g", "b", "2"))
require.NoError(t, sc.DeleteGroup("g"))
scopedStore, _ := NewScoped(storeInstance, "tenant-a")
require.NoError(t, scopedStore.Set("g", "a", "1"))
require.NoError(t, scopedStore.Set("g", "b", "2"))
require.NoError(t, scopedStore.DeleteGroup("g"))
n, err := sc.Count("g")
count, err := scopedStore.Count("g")
require.NoError(t, err)
assert.Equal(t, 0, n)
assert.Equal(t, 0, count)
}
func TestScope_ScopedStore_Good_GetAll(t *testing.T) {
s, _ := New(":memory:")
defer s.Close()
storeInstance, _ := New(":memory:")
defer storeInstance.Close()
a, _ := NewScoped(s, "tenant-a")
b, _ := NewScoped(s, "tenant-b")
alphaStore, _ := NewScoped(storeInstance, "tenant-a")
betaStore, _ := NewScoped(storeInstance, "tenant-b")
require.NoError(t, a.Set("items", "x", "1"))
require.NoError(t, a.Set("items", "y", "2"))
require.NoError(t, b.Set("items", "z", "3"))
require.NoError(t, alphaStore.Set("items", "x", "1"))
require.NoError(t, alphaStore.Set("items", "y", "2"))
require.NoError(t, betaStore.Set("items", "z", "3"))
all, err := a.GetAll("items")
all, err := alphaStore.GetAll("items")
require.NoError(t, err)
assert.Equal(t, map[string]string{"x": "1", "y": "2"}, all)
allB, err := b.GetAll("items")
allB, err := betaStore.GetAll("items")
require.NoError(t, err)
assert.Equal(t, map[string]string{"z": "3"}, allB)
}
func TestScope_ScopedStore_Good_All(t *testing.T) {
s, _ := New(":memory:")
defer s.Close()
storeInstance, _ := New(":memory:")
defer storeInstance.Close()
scopedStore, _ := NewScoped(s, "tenant-a")
scopedStore, _ := NewScoped(storeInstance, "tenant-a")
require.NoError(t, scopedStore.Set("items", "first", "1"))
require.NoError(t, scopedStore.Set("items", "second", "2"))
@ -180,52 +180,52 @@ func TestScope_ScopedStore_Good_All(t *testing.T) {
}
func TestScope_ScopedStore_Good_Count(t *testing.T) {
s, _ := New(":memory:")
defer s.Close()
storeInstance, _ := New(":memory:")
defer storeInstance.Close()
sc, _ := NewScoped(s, "tenant-a")
require.NoError(t, sc.Set("g", "a", "1"))
require.NoError(t, sc.Set("g", "b", "2"))
scopedStore, _ := NewScoped(storeInstance, "tenant-a")
require.NoError(t, scopedStore.Set("g", "a", "1"))
require.NoError(t, scopedStore.Set("g", "b", "2"))
n, err := sc.Count("g")
count, err := scopedStore.Count("g")
require.NoError(t, err)
assert.Equal(t, 2, n)
assert.Equal(t, 2, count)
}
func TestScope_ScopedStore_Good_SetWithTTL(t *testing.T) {
s, _ := New(":memory:")
defer s.Close()
storeInstance, _ := New(":memory:")
defer storeInstance.Close()
sc, _ := NewScoped(s, "tenant-a")
require.NoError(t, sc.SetWithTTL("g", "k", "v", time.Hour))
scopedStore, _ := NewScoped(storeInstance, "tenant-a")
require.NoError(t, scopedStore.SetWithTTL("g", "k", "v", time.Hour))
val, err := sc.Get("g", "k")
value, err := scopedStore.Get("g", "k")
require.NoError(t, err)
assert.Equal(t, "v", val)
assert.Equal(t, "v", value)
}
func TestScope_ScopedStore_Good_SetWithTTL_Expires(t *testing.T) {
s, _ := New(":memory:")
defer s.Close()
storeInstance, _ := New(":memory:")
defer storeInstance.Close()
sc, _ := NewScoped(s, "tenant-a")
require.NoError(t, sc.SetWithTTL("g", "k", "v", 1*time.Millisecond))
scopedStore, _ := NewScoped(storeInstance, "tenant-a")
require.NoError(t, scopedStore.SetWithTTL("g", "k", "v", 1*time.Millisecond))
time.Sleep(5 * time.Millisecond)
_, err := sc.Get("g", "k")
_, err := scopedStore.Get("g", "k")
assert.True(t, core.Is(err, NotFoundError))
}
func TestScope_ScopedStore_Good_Render(t *testing.T) {
s, _ := New(":memory:")
defer s.Close()
storeInstance, _ := New(":memory:")
defer storeInstance.Close()
sc, _ := NewScoped(s, "tenant-a")
require.NoError(t, sc.Set("user", "name", "Alice"))
scopedStore, _ := NewScoped(storeInstance, "tenant-a")
require.NoError(t, scopedStore.Set("user", "name", "Alice"))
out, err := sc.Render("Hello {{ .name }}", "user")
renderedTemplate, err := scopedStore.Render("Hello {{ .name }}", "user")
require.NoError(t, err)
assert.Equal(t, "Hello Alice", out)
assert.Equal(t, "Hello Alice", renderedTemplate)
}
// ---------------------------------------------------------------------------
@ -233,19 +233,19 @@ func TestScope_ScopedStore_Good_Render(t *testing.T) {
// ---------------------------------------------------------------------------
func TestScope_Quota_Good_MaxKeys(t *testing.T) {
s, _ := New(":memory:")
defer s.Close()
storeInstance, _ := New(":memory:")
defer storeInstance.Close()
sc, err := NewScopedWithQuota(s, "tenant-a", QuotaConfig{MaxKeys: 5})
scopedStore, err := NewScopedWithQuota(storeInstance, "tenant-a", QuotaConfig{MaxKeys: 5})
require.NoError(t, err)
// Insert 5 keys across different groups — should be fine.
for i := range 5 {
require.NoError(t, sc.Set("g", keyName(i), "v"))
require.NoError(t, scopedStore.Set("g", keyName(i), "v"))
}
// 6th key should fail.
err = sc.Set("g", "overflow", "v")
err = scopedStore.Set("g", "overflow", "v")
require.Error(t, err)
assert.True(t, core.Is(err, QuotaExceededError), "expected QuotaExceededError, got: %v", err)
}
@ -268,97 +268,97 @@ func TestScope_Quota_Bad_QuotaCheckQueryError(t *testing.T) {
}
func TestScope_Quota_Good_MaxKeys_AcrossGroups(t *testing.T) {
s, _ := New(":memory:")
defer s.Close()
storeInstance, _ := New(":memory:")
defer storeInstance.Close()
sc, _ := NewScopedWithQuota(s, "tenant-a", QuotaConfig{MaxKeys: 3})
scopedStore, _ := NewScopedWithQuota(storeInstance, "tenant-a", QuotaConfig{MaxKeys: 3})
require.NoError(t, sc.Set("g1", "a", "1"))
require.NoError(t, sc.Set("g2", "b", "2"))
require.NoError(t, sc.Set("g3", "c", "3"))
require.NoError(t, scopedStore.Set("g1", "a", "1"))
require.NoError(t, scopedStore.Set("g2", "b", "2"))
require.NoError(t, scopedStore.Set("g3", "c", "3"))
// Total is now 3 — any new key should fail regardless of group.
err := sc.Set("g4", "d", "4")
err := scopedStore.Set("g4", "d", "4")
assert.True(t, core.Is(err, QuotaExceededError))
}
func TestScope_Quota_Good_UpsertDoesNotCount(t *testing.T) {
s, _ := New(":memory:")
defer s.Close()
storeInstance, _ := New(":memory:")
defer storeInstance.Close()
sc, _ := NewScopedWithQuota(s, "tenant-a", QuotaConfig{MaxKeys: 3})
scopedStore, _ := NewScopedWithQuota(storeInstance, "tenant-a", QuotaConfig{MaxKeys: 3})
require.NoError(t, sc.Set("g", "a", "1"))
require.NoError(t, sc.Set("g", "b", "2"))
require.NoError(t, sc.Set("g", "c", "3"))
require.NoError(t, scopedStore.Set("g", "a", "1"))
require.NoError(t, scopedStore.Set("g", "b", "2"))
require.NoError(t, scopedStore.Set("g", "c", "3"))
// Upserting existing key should succeed.
require.NoError(t, sc.Set("g", "a", "updated"))
require.NoError(t, scopedStore.Set("g", "a", "updated"))
val, err := sc.Get("g", "a")
value, err := scopedStore.Get("g", "a")
require.NoError(t, err)
assert.Equal(t, "updated", val)
assert.Equal(t, "updated", value)
}
func TestScope_Quota_Good_DeleteAndReInsert(t *testing.T) {
s, _ := New(":memory:")
defer s.Close()
storeInstance, _ := New(":memory:")
defer storeInstance.Close()
sc, _ := NewScopedWithQuota(s, "tenant-a", QuotaConfig{MaxKeys: 3})
scopedStore, _ := NewScopedWithQuota(storeInstance, "tenant-a", QuotaConfig{MaxKeys: 3})
require.NoError(t, sc.Set("g", "a", "1"))
require.NoError(t, sc.Set("g", "b", "2"))
require.NoError(t, sc.Set("g", "c", "3"))
require.NoError(t, scopedStore.Set("g", "a", "1"))
require.NoError(t, scopedStore.Set("g", "b", "2"))
require.NoError(t, scopedStore.Set("g", "c", "3"))
// Delete one key, then insert a new one — should work.
require.NoError(t, sc.Delete("g", "c"))
require.NoError(t, sc.Set("g", "d", "4"))
require.NoError(t, scopedStore.Delete("g", "c"))
require.NoError(t, scopedStore.Set("g", "d", "4"))
}
func TestScope_Quota_Good_ZeroMeansUnlimited(t *testing.T) {
s, _ := New(":memory:")
defer s.Close()
storeInstance, _ := New(":memory:")
defer storeInstance.Close()
sc, _ := NewScopedWithQuota(s, "tenant-a", QuotaConfig{MaxKeys: 0, MaxGroups: 0})
scopedStore, _ := NewScopedWithQuota(storeInstance, "tenant-a", QuotaConfig{MaxKeys: 0, MaxGroups: 0})
// Should be able to insert many keys and groups without error.
for i := range 100 {
require.NoError(t, sc.Set("g", keyName(i), "v"))
require.NoError(t, scopedStore.Set("g", keyName(i), "v"))
}
}
func TestScope_Quota_Good_ExpiredKeysExcluded(t *testing.T) {
s, _ := New(":memory:")
defer s.Close()
storeInstance, _ := New(":memory:")
defer storeInstance.Close()
sc, _ := NewScopedWithQuota(s, "tenant-a", QuotaConfig{MaxKeys: 3})
scopedStore, _ := NewScopedWithQuota(storeInstance, "tenant-a", QuotaConfig{MaxKeys: 3})
// Insert 3 keys, 2 with short TTL.
require.NoError(t, sc.SetWithTTL("g", "temp1", "v", 1*time.Millisecond))
require.NoError(t, sc.SetWithTTL("g", "temp2", "v", 1*time.Millisecond))
require.NoError(t, sc.Set("g", "permanent", "v"))
require.NoError(t, scopedStore.SetWithTTL("g", "temp1", "v", 1*time.Millisecond))
require.NoError(t, scopedStore.SetWithTTL("g", "temp2", "v", 1*time.Millisecond))
require.NoError(t, scopedStore.Set("g", "permanent", "v"))
time.Sleep(5 * time.Millisecond)
// After expiry, only 1 key counts — should be able to insert 2 more.
require.NoError(t, sc.Set("g", "new1", "v"))
require.NoError(t, sc.Set("g", "new2", "v"))
require.NoError(t, scopedStore.Set("g", "new1", "v"))
require.NoError(t, scopedStore.Set("g", "new2", "v"))
// Now at 3 — next should fail.
err := sc.Set("g", "new3", "v")
err := scopedStore.Set("g", "new3", "v")
assert.True(t, core.Is(err, QuotaExceededError))
}
func TestScope_Quota_Good_SetWithTTL_Enforced(t *testing.T) {
s, _ := New(":memory:")
defer s.Close()
storeInstance, _ := New(":memory:")
defer storeInstance.Close()
sc, _ := NewScopedWithQuota(s, "tenant-a", QuotaConfig{MaxKeys: 2})
scopedStore, _ := NewScopedWithQuota(storeInstance, "tenant-a", QuotaConfig{MaxKeys: 2})
require.NoError(t, sc.SetWithTTL("g", "a", "1", time.Hour))
require.NoError(t, sc.SetWithTTL("g", "b", "2", time.Hour))
require.NoError(t, scopedStore.SetWithTTL("g", "a", "1", time.Hour))
require.NoError(t, scopedStore.SetWithTTL("g", "b", "2", time.Hour))
err := sc.SetWithTTL("g", "c", "3", time.Hour)
err := scopedStore.SetWithTTL("g", "c", "3", time.Hour)
assert.True(t, core.Is(err, QuotaExceededError))
}
@ -367,111 +367,111 @@ func TestScope_Quota_Good_SetWithTTL_Enforced(t *testing.T) {
// ---------------------------------------------------------------------------
func TestScope_Quota_Good_MaxGroups(t *testing.T) {
s, _ := New(":memory:")
defer s.Close()
storeInstance, _ := New(":memory:")
defer storeInstance.Close()
sc, _ := NewScopedWithQuota(s, "tenant-a", QuotaConfig{MaxGroups: 3})
scopedStore, _ := NewScopedWithQuota(storeInstance, "tenant-a", QuotaConfig{MaxGroups: 3})
require.NoError(t, sc.Set("g1", "k", "v"))
require.NoError(t, sc.Set("g2", "k", "v"))
require.NoError(t, sc.Set("g3", "k", "v"))
require.NoError(t, scopedStore.Set("g1", "k", "v"))
require.NoError(t, scopedStore.Set("g2", "k", "v"))
require.NoError(t, scopedStore.Set("g3", "k", "v"))
// 4th group should fail.
err := sc.Set("g4", "k", "v")
err := scopedStore.Set("g4", "k", "v")
require.Error(t, err)
assert.True(t, core.Is(err, QuotaExceededError))
}
func TestScope_Quota_Good_MaxGroups_ExistingGroupOK(t *testing.T) {
s, _ := New(":memory:")
defer s.Close()
storeInstance, _ := New(":memory:")
defer storeInstance.Close()
sc, _ := NewScopedWithQuota(s, "tenant-a", QuotaConfig{MaxGroups: 2})
scopedStore, _ := NewScopedWithQuota(storeInstance, "tenant-a", QuotaConfig{MaxGroups: 2})
require.NoError(t, sc.Set("g1", "a", "1"))
require.NoError(t, sc.Set("g2", "b", "2"))
require.NoError(t, scopedStore.Set("g1", "a", "1"))
require.NoError(t, scopedStore.Set("g2", "b", "2"))
// Adding more keys to existing groups should be fine.
require.NoError(t, sc.Set("g1", "c", "3"))
require.NoError(t, sc.Set("g2", "d", "4"))
require.NoError(t, scopedStore.Set("g1", "c", "3"))
require.NoError(t, scopedStore.Set("g2", "d", "4"))
}
func TestScope_Quota_Good_MaxGroups_DeleteAndRecreate(t *testing.T) {
s, _ := New(":memory:")
defer s.Close()
storeInstance, _ := New(":memory:")
defer storeInstance.Close()
sc, _ := NewScopedWithQuota(s, "tenant-a", QuotaConfig{MaxGroups: 2})
scopedStore, _ := NewScopedWithQuota(storeInstance, "tenant-a", QuotaConfig{MaxGroups: 2})
require.NoError(t, sc.Set("g1", "k", "v"))
require.NoError(t, sc.Set("g2", "k", "v"))
require.NoError(t, scopedStore.Set("g1", "k", "v"))
require.NoError(t, scopedStore.Set("g2", "k", "v"))
// Delete a group, then create a new one.
require.NoError(t, sc.DeleteGroup("g1"))
require.NoError(t, sc.Set("g3", "k", "v"))
require.NoError(t, scopedStore.DeleteGroup("g1"))
require.NoError(t, scopedStore.Set("g3", "k", "v"))
}
func TestScope_Quota_Good_MaxGroups_ZeroUnlimited(t *testing.T) {
s, _ := New(":memory:")
defer s.Close()
storeInstance, _ := New(":memory:")
defer storeInstance.Close()
sc, _ := NewScopedWithQuota(s, "tenant-a", QuotaConfig{MaxGroups: 0})
scopedStore, _ := NewScopedWithQuota(storeInstance, "tenant-a", QuotaConfig{MaxGroups: 0})
for i := range 50 {
require.NoError(t, sc.Set(keyName(i), "k", "v"))
require.NoError(t, scopedStore.Set(keyName(i), "k", "v"))
}
}
func TestScope_Quota_Good_MaxGroups_ExpiredGroupExcluded(t *testing.T) {
s, _ := New(":memory:")
defer s.Close()
storeInstance, _ := New(":memory:")
defer storeInstance.Close()
sc, _ := NewScopedWithQuota(s, "tenant-a", QuotaConfig{MaxGroups: 2})
scopedStore, _ := NewScopedWithQuota(storeInstance, "tenant-a", QuotaConfig{MaxGroups: 2})
// Create 2 groups, one with only TTL keys.
require.NoError(t, sc.SetWithTTL("g1", "k", "v", 1*time.Millisecond))
require.NoError(t, sc.Set("g2", "k", "v"))
require.NoError(t, scopedStore.SetWithTTL("g1", "k", "v", 1*time.Millisecond))
require.NoError(t, scopedStore.Set("g2", "k", "v"))
time.Sleep(5 * time.Millisecond)
// g1's only key has expired, so group count should be 1 — we can create a new one.
require.NoError(t, sc.Set("g3", "k", "v"))
require.NoError(t, scopedStore.Set("g3", "k", "v"))
}
func TestScope_Quota_Good_BothLimits(t *testing.T) {
s, _ := New(":memory:")
defer s.Close()
storeInstance, _ := New(":memory:")
defer storeInstance.Close()
sc, _ := NewScopedWithQuota(s, "tenant-a", QuotaConfig{MaxKeys: 10, MaxGroups: 2})
scopedStore, _ := NewScopedWithQuota(storeInstance, "tenant-a", QuotaConfig{MaxKeys: 10, MaxGroups: 2})
require.NoError(t, sc.Set("g1", "a", "1"))
require.NoError(t, sc.Set("g2", "b", "2"))
require.NoError(t, scopedStore.Set("g1", "a", "1"))
require.NoError(t, scopedStore.Set("g2", "b", "2"))
// Group limit hit.
err := sc.Set("g3", "c", "3")
err := scopedStore.Set("g3", "c", "3")
assert.True(t, core.Is(err, QuotaExceededError))
// But adding to existing groups is fine (within key limit).
require.NoError(t, sc.Set("g1", "d", "4"))
require.NoError(t, scopedStore.Set("g1", "d", "4"))
}
func TestScope_Quota_Good_DoesNotAffectOtherNamespaces(t *testing.T) {
s, _ := New(":memory:")
defer s.Close()
storeInstance, _ := New(":memory:")
defer storeInstance.Close()
a, _ := NewScopedWithQuota(s, "tenant-a", QuotaConfig{MaxKeys: 2})
b, _ := NewScopedWithQuota(s, "tenant-b", QuotaConfig{MaxKeys: 2})
alphaStore, _ := NewScopedWithQuota(storeInstance, "tenant-a", QuotaConfig{MaxKeys: 2})
betaStore, _ := NewScopedWithQuota(storeInstance, "tenant-b", QuotaConfig{MaxKeys: 2})
require.NoError(t, a.Set("g", "a1", "v"))
require.NoError(t, a.Set("g", "a2", "v"))
require.NoError(t, b.Set("g", "b1", "v"))
require.NoError(t, b.Set("g", "b2", "v"))
require.NoError(t, alphaStore.Set("g", "a1", "v"))
require.NoError(t, alphaStore.Set("g", "a2", "v"))
require.NoError(t, betaStore.Set("g", "b1", "v"))
require.NoError(t, betaStore.Set("g", "b2", "v"))
// a is at limit — but b's keys don't count against a.
err := a.Set("g", "a3", "v")
// alphaStore is at limit — but betaStore's keys don't count against alphaStore.
err := alphaStore.Set("g", "a3", "v")
assert.True(t, core.Is(err, QuotaExceededError))
// b is also at limit independently.
err = b.Set("g", "b3", "v")
// betaStore is also at limit independently.
err = betaStore.Set("g", "b3", "v")
assert.True(t, core.Is(err, QuotaExceededError))
}
@ -480,86 +480,86 @@ func TestScope_Quota_Good_DoesNotAffectOtherNamespaces(t *testing.T) {
// ---------------------------------------------------------------------------
func TestScope_CountAll_Good_WithPrefix(t *testing.T) {
s, _ := New(":memory:")
defer s.Close()
storeInstance, _ := New(":memory:")
defer storeInstance.Close()
require.NoError(t, s.Set("ns-a:g1", "k1", "v"))
require.NoError(t, s.Set("ns-a:g1", "k2", "v"))
require.NoError(t, s.Set("ns-a:g2", "k1", "v"))
require.NoError(t, s.Set("ns-b:g1", "k1", "v"))
require.NoError(t, storeInstance.Set("ns-a:g1", "k1", "v"))
require.NoError(t, storeInstance.Set("ns-a:g1", "k2", "v"))
require.NoError(t, storeInstance.Set("ns-a:g2", "k1", "v"))
require.NoError(t, storeInstance.Set("ns-b:g1", "k1", "v"))
n, err := s.CountAll("ns-a:")
count, err := storeInstance.CountAll("ns-a:")
require.NoError(t, err)
assert.Equal(t, 3, n)
assert.Equal(t, 3, count)
n, err = s.CountAll("ns-b:")
count, err = storeInstance.CountAll("ns-b:")
require.NoError(t, err)
assert.Equal(t, 1, n)
assert.Equal(t, 1, count)
}
func TestScope_CountAll_Good_WithPrefix_Wildcards(t *testing.T) {
s, _ := New(":memory:")
defer s.Close()
storeInstance, _ := New(":memory:")
defer storeInstance.Close()
// Add keys in groups that look like wildcards.
require.NoError(t, s.Set("user_1", "k", "v"))
require.NoError(t, s.Set("user_2", "k", "v"))
require.NoError(t, s.Set("user%test", "k", "v"))
require.NoError(t, s.Set("user_test", "k", "v"))
require.NoError(t, storeInstance.Set("user_1", "k", "v"))
require.NoError(t, storeInstance.Set("user_2", "k", "v"))
require.NoError(t, storeInstance.Set("user%test", "k", "v"))
require.NoError(t, storeInstance.Set("user_test", "k", "v"))
// Prefix "user_" should ONLY match groups starting with "user_".
// Since we escape "_", it matches literal "_".
// Groups: "user_1", "user_2", "user_test" (3 total).
// "user%test" is NOT matched because "_" is literal.
n, err := s.CountAll("user_")
count, err := storeInstance.CountAll("user_")
require.NoError(t, err)
assert.Equal(t, 3, n)
assert.Equal(t, 3, count)
// Prefix "user%" should ONLY match "user%test".
n, err = s.CountAll("user%")
count, err = storeInstance.CountAll("user%")
require.NoError(t, err)
assert.Equal(t, 1, n)
assert.Equal(t, 1, count)
}
func TestScope_CountAll_Good_EmptyPrefix(t *testing.T) {
s, _ := New(":memory:")
defer s.Close()
storeInstance, _ := New(":memory:")
defer storeInstance.Close()
require.NoError(t, s.Set("g1", "k1", "v"))
require.NoError(t, s.Set("g2", "k2", "v"))
require.NoError(t, storeInstance.Set("g1", "k1", "v"))
require.NoError(t, storeInstance.Set("g2", "k2", "v"))
n, err := s.CountAll("")
count, err := storeInstance.CountAll("")
require.NoError(t, err)
assert.Equal(t, 2, n)
assert.Equal(t, 2, count)
}
func TestScope_CountAll_Good_ExcludesExpired(t *testing.T) {
s, _ := New(":memory:")
defer s.Close()
storeInstance, _ := New(":memory:")
defer storeInstance.Close()
require.NoError(t, s.Set("ns:g", "permanent", "v"))
require.NoError(t, s.SetWithTTL("ns:g", "temp", "v", 1*time.Millisecond))
require.NoError(t, storeInstance.Set("ns:g", "permanent", "v"))
require.NoError(t, storeInstance.SetWithTTL("ns:g", "temp", "v", 1*time.Millisecond))
time.Sleep(5 * time.Millisecond)
n, err := s.CountAll("ns:")
count, err := storeInstance.CountAll("ns:")
require.NoError(t, err)
assert.Equal(t, 1, n, "expired keys should not be counted")
assert.Equal(t, 1, count, "expired keys should not be counted")
}
func TestScope_CountAll_Good_Empty(t *testing.T) {
s, _ := New(":memory:")
defer s.Close()
storeInstance, _ := New(":memory:")
defer storeInstance.Close()
n, err := s.CountAll("nonexistent:")
count, err := storeInstance.CountAll("nonexistent:")
require.NoError(t, err)
assert.Equal(t, 0, n)
assert.Equal(t, 0, count)
}
func TestScope_CountAll_Bad_ClosedStore(t *testing.T) {
s, _ := New(":memory:")
s.Close()
storeInstance, _ := New(":memory:")
storeInstance.Close()
_, err := s.CountAll("")
_, err := storeInstance.CountAll("")
require.Error(t, err)
}
@ -568,15 +568,15 @@ func TestScope_CountAll_Bad_ClosedStore(t *testing.T) {
// ---------------------------------------------------------------------------
func TestScope_Groups_Good_WithPrefix(t *testing.T) {
s, _ := New(":memory:")
defer s.Close()
storeInstance, _ := New(":memory:")
defer storeInstance.Close()
require.NoError(t, s.Set("ns-a:g1", "k", "v"))
require.NoError(t, s.Set("ns-a:g2", "k", "v"))
require.NoError(t, s.Set("ns-a:g2", "k2", "v")) // duplicate group
require.NoError(t, s.Set("ns-b:g1", "k", "v"))
require.NoError(t, storeInstance.Set("ns-a:g1", "k", "v"))
require.NoError(t, storeInstance.Set("ns-a:g2", "k", "v"))
require.NoError(t, storeInstance.Set("ns-a:g2", "k2", "v")) // duplicate group
require.NoError(t, storeInstance.Set("ns-b:g1", "k", "v"))
groups, err := s.Groups("ns-a:")
groups, err := storeInstance.Groups("ns-a:")
require.NoError(t, err)
assert.Len(t, groups, 2)
assert.Contains(t, groups, "ns-a:g1")
@ -584,61 +584,61 @@ func TestScope_Groups_Good_WithPrefix(t *testing.T) {
}
func TestScope_Groups_Good_EmptyPrefix(t *testing.T) {
s, _ := New(":memory:")
defer s.Close()
storeInstance, _ := New(":memory:")
defer storeInstance.Close()
require.NoError(t, s.Set("g1", "k", "v"))
require.NoError(t, s.Set("g2", "k", "v"))
require.NoError(t, s.Set("g3", "k", "v"))
require.NoError(t, storeInstance.Set("g1", "k", "v"))
require.NoError(t, storeInstance.Set("g2", "k", "v"))
require.NoError(t, storeInstance.Set("g3", "k", "v"))
groups, err := s.Groups("")
groups, err := storeInstance.Groups("")
require.NoError(t, err)
assert.Len(t, groups, 3)
}
func TestScope_Groups_Good_Distinct(t *testing.T) {
s, _ := New(":memory:")
defer s.Close()
storeInstance, _ := New(":memory:")
defer storeInstance.Close()
// Multiple keys in the same group should produce one entry.
require.NoError(t, s.Set("g1", "a", "v"))
require.NoError(t, s.Set("g1", "b", "v"))
require.NoError(t, s.Set("g1", "c", "v"))
require.NoError(t, storeInstance.Set("g1", "a", "v"))
require.NoError(t, storeInstance.Set("g1", "b", "v"))
require.NoError(t, storeInstance.Set("g1", "c", "v"))
groups, err := s.Groups("")
groups, err := storeInstance.Groups("")
require.NoError(t, err)
assert.Len(t, groups, 1)
assert.Equal(t, "g1", groups[0])
}
func TestScope_Groups_Good_ExcludesExpired(t *testing.T) {
s, _ := New(":memory:")
defer s.Close()
storeInstance, _ := New(":memory:")
defer storeInstance.Close()
require.NoError(t, s.Set("ns:g1", "permanent", "v"))
require.NoError(t, s.SetWithTTL("ns:g2", "temp", "v", 1*time.Millisecond))
require.NoError(t, storeInstance.Set("ns:g1", "permanent", "v"))
require.NoError(t, storeInstance.SetWithTTL("ns:g2", "temp", "v", 1*time.Millisecond))
time.Sleep(5 * time.Millisecond)
groups, err := s.Groups("ns:")
groups, err := storeInstance.Groups("ns:")
require.NoError(t, err)
assert.Len(t, groups, 1, "group with only expired keys should be excluded")
assert.Equal(t, "ns:g1", groups[0])
}
func TestScope_Groups_Good_Empty(t *testing.T) {
s, _ := New(":memory:")
defer s.Close()
storeInstance, _ := New(":memory:")
defer storeInstance.Close()
groups, err := s.Groups("nonexistent:")
groups, err := storeInstance.Groups("nonexistent:")
require.NoError(t, err)
assert.Empty(t, groups)
}
func TestScope_Groups_Bad_ClosedStore(t *testing.T) {
s, _ := New(":memory:")
s.Close()
storeInstance, _ := New(":memory:")
storeInstance.Close()
_, err := s.Groups("")
_, err := storeInstance.Groups("")
require.Error(t, err)
}

File diff suppressed because it is too large Load diff