refactor(test): tighten AX naming in tests
Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
2eedf1e937
commit
f144c5eb01
3 changed files with 833 additions and 833 deletions
304
events_test.go
304
events_test.go
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
524
scope_test.go
524
scope_test.go
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
838
store_test.go
838
store_test.go
File diff suppressed because it is too large
Load diff
Loading…
Add table
Reference in a new issue