Automated fixes: interface{} → any, range-over-int, t.Context(),
wg.Go(), strings.SplitSeq, strings.Builder, slices.Contains,
maps helpers, min/max builtins.
Co-Authored-By: Virgil <virgil@lethean.io>
584 lines
16 KiB
Go
584 lines
16 KiB
Go
package store
|
|
|
|
import (
|
|
"errors"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// NewScoped — constructor validation
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func TestNewScoped_Good(t *testing.T) {
|
|
s, _ := New(":memory:")
|
|
defer s.Close()
|
|
|
|
sc, err := NewScoped(s, "tenant-1")
|
|
require.NoError(t, err)
|
|
require.NotNil(t, sc)
|
|
assert.Equal(t, "tenant-1", sc.Namespace())
|
|
}
|
|
|
|
func TestNewScoped_Good_AlphanumericHyphens(t *testing.T) {
|
|
s, _ := New(":memory:")
|
|
defer s.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)
|
|
}
|
|
}
|
|
|
|
func TestNewScoped_Bad_Empty(t *testing.T) {
|
|
s, _ := New(":memory:")
|
|
defer s.Close()
|
|
|
|
_, err := NewScoped(s, "")
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "invalid")
|
|
}
|
|
|
|
func TestNewScoped_Bad_InvalidChars(t *testing.T) {
|
|
s, _ := New(":memory:")
|
|
defer s.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)
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// ScopedStore — basic CRUD
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func TestScopedStore_Good_SetGet(t *testing.T) {
|
|
s, _ := New(":memory:")
|
|
defer s.Close()
|
|
|
|
sc, _ := NewScoped(s, "tenant-a")
|
|
require.NoError(t, sc.Set("config", "theme", "dark"))
|
|
|
|
val, err := sc.Get("config", "theme")
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "dark", val)
|
|
}
|
|
|
|
func TestScopedStore_Good_PrefixedInUnderlyingStore(t *testing.T) {
|
|
s, _ := New(":memory:")
|
|
defer s.Close()
|
|
|
|
sc, _ := NewScoped(s, "tenant-a")
|
|
require.NoError(t, sc.Set("config", "key", "val"))
|
|
|
|
// The underlying store should have the prefixed group name.
|
|
val, err := s.Get("tenant-a:config", "key")
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "val", val)
|
|
|
|
// Direct access without prefix should fail.
|
|
_, err = s.Get("config", "key")
|
|
assert.True(t, errors.Is(err, ErrNotFound))
|
|
}
|
|
|
|
func TestScopedStore_Good_NamespaceIsolation(t *testing.T) {
|
|
s, _ := New(":memory:")
|
|
defer s.Close()
|
|
|
|
a, _ := NewScoped(s, "tenant-a")
|
|
b, _ := NewScoped(s, "tenant-b")
|
|
|
|
require.NoError(t, a.Set("config", "colour", "blue"))
|
|
require.NoError(t, b.Set("config", "colour", "red"))
|
|
|
|
va, err := a.Get("config", "colour")
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "blue", va)
|
|
|
|
vb, err := b.Get("config", "colour")
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "red", vb)
|
|
}
|
|
|
|
func TestScopedStore_Good_Delete(t *testing.T) {
|
|
s, _ := New(":memory:")
|
|
defer s.Close()
|
|
|
|
sc, _ := NewScoped(s, "tenant-a")
|
|
require.NoError(t, sc.Set("g", "k", "v"))
|
|
require.NoError(t, sc.Delete("g", "k"))
|
|
|
|
_, err := sc.Get("g", "k")
|
|
assert.True(t, errors.Is(err, ErrNotFound))
|
|
}
|
|
|
|
func TestScopedStore_Good_DeleteGroup(t *testing.T) {
|
|
s, _ := New(":memory:")
|
|
defer s.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"))
|
|
|
|
n, err := sc.Count("g")
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 0, n)
|
|
}
|
|
|
|
func TestScopedStore_Good_GetAll(t *testing.T) {
|
|
s, _ := New(":memory:")
|
|
defer s.Close()
|
|
|
|
a, _ := NewScoped(s, "tenant-a")
|
|
b, _ := NewScoped(s, "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"))
|
|
|
|
all, err := a.GetAll("items")
|
|
require.NoError(t, err)
|
|
assert.Equal(t, map[string]string{"x": "1", "y": "2"}, all)
|
|
|
|
allB, err := b.GetAll("items")
|
|
require.NoError(t, err)
|
|
assert.Equal(t, map[string]string{"z": "3"}, allB)
|
|
}
|
|
|
|
func TestScopedStore_Good_Count(t *testing.T) {
|
|
s, _ := New(":memory:")
|
|
defer s.Close()
|
|
|
|
sc, _ := NewScoped(s, "tenant-a")
|
|
require.NoError(t, sc.Set("g", "a", "1"))
|
|
require.NoError(t, sc.Set("g", "b", "2"))
|
|
|
|
n, err := sc.Count("g")
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 2, n)
|
|
}
|
|
|
|
func TestScopedStore_Good_SetWithTTL(t *testing.T) {
|
|
s, _ := New(":memory:")
|
|
defer s.Close()
|
|
|
|
sc, _ := NewScoped(s, "tenant-a")
|
|
require.NoError(t, sc.SetWithTTL("g", "k", "v", time.Hour))
|
|
|
|
val, err := sc.Get("g", "k")
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "v", val)
|
|
}
|
|
|
|
func TestScopedStore_Good_SetWithTTL_Expires(t *testing.T) {
|
|
s, _ := New(":memory:")
|
|
defer s.Close()
|
|
|
|
sc, _ := NewScoped(s, "tenant-a")
|
|
require.NoError(t, sc.SetWithTTL("g", "k", "v", 1*time.Millisecond))
|
|
time.Sleep(5 * time.Millisecond)
|
|
|
|
_, err := sc.Get("g", "k")
|
|
assert.True(t, errors.Is(err, ErrNotFound))
|
|
}
|
|
|
|
func TestScopedStore_Good_Render(t *testing.T) {
|
|
s, _ := New(":memory:")
|
|
defer s.Close()
|
|
|
|
sc, _ := NewScoped(s, "tenant-a")
|
|
require.NoError(t, sc.Set("user", "name", "Alice"))
|
|
|
|
out, err := sc.Render("Hello {{ .name }}", "user")
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "Hello Alice", out)
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Quota enforcement — MaxKeys
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func TestQuota_Good_MaxKeys(t *testing.T) {
|
|
s, _ := New(":memory:")
|
|
defer s.Close()
|
|
|
|
sc, err := NewScopedWithQuota(s, "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"))
|
|
}
|
|
|
|
// 6th key should fail.
|
|
err = sc.Set("g", "overflow", "v")
|
|
require.Error(t, err)
|
|
assert.True(t, errors.Is(err, ErrQuotaExceeded), "expected ErrQuotaExceeded, got: %v", err)
|
|
}
|
|
|
|
func TestQuota_Good_MaxKeys_AcrossGroups(t *testing.T) {
|
|
s, _ := New(":memory:")
|
|
defer s.Close()
|
|
|
|
sc, _ := NewScopedWithQuota(s, "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"))
|
|
|
|
// Total is now 3 — any new key should fail regardless of group.
|
|
err := sc.Set("g4", "d", "4")
|
|
assert.True(t, errors.Is(err, ErrQuotaExceeded))
|
|
}
|
|
|
|
func TestQuota_Good_UpsertDoesNotCount(t *testing.T) {
|
|
s, _ := New(":memory:")
|
|
defer s.Close()
|
|
|
|
sc, _ := NewScopedWithQuota(s, "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"))
|
|
|
|
// Upserting existing key should succeed.
|
|
require.NoError(t, sc.Set("g", "a", "updated"))
|
|
|
|
val, err := sc.Get("g", "a")
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "updated", val)
|
|
}
|
|
|
|
func TestQuota_Good_DeleteAndReInsert(t *testing.T) {
|
|
s, _ := New(":memory:")
|
|
defer s.Close()
|
|
|
|
sc, _ := NewScopedWithQuota(s, "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"))
|
|
|
|
// 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"))
|
|
}
|
|
|
|
func TestQuota_Good_ZeroMeansUnlimited(t *testing.T) {
|
|
s, _ := New(":memory:")
|
|
defer s.Close()
|
|
|
|
sc, _ := NewScopedWithQuota(s, "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"))
|
|
}
|
|
}
|
|
|
|
func TestQuota_Good_ExpiredKeysExcluded(t *testing.T) {
|
|
s, _ := New(":memory:")
|
|
defer s.Close()
|
|
|
|
sc, _ := NewScopedWithQuota(s, "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"))
|
|
|
|
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"))
|
|
|
|
// Now at 3 — next should fail.
|
|
err := sc.Set("g", "new3", "v")
|
|
assert.True(t, errors.Is(err, ErrQuotaExceeded))
|
|
}
|
|
|
|
func TestQuota_Good_SetWithTTL_Enforced(t *testing.T) {
|
|
s, _ := New(":memory:")
|
|
defer s.Close()
|
|
|
|
sc, _ := NewScopedWithQuota(s, "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))
|
|
|
|
err := sc.SetWithTTL("g", "c", "3", time.Hour)
|
|
assert.True(t, errors.Is(err, ErrQuotaExceeded))
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Quota enforcement — MaxGroups
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func TestQuota_Good_MaxGroups(t *testing.T) {
|
|
s, _ := New(":memory:")
|
|
defer s.Close()
|
|
|
|
sc, _ := NewScopedWithQuota(s, "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"))
|
|
|
|
// 4th group should fail.
|
|
err := sc.Set("g4", "k", "v")
|
|
require.Error(t, err)
|
|
assert.True(t, errors.Is(err, ErrQuotaExceeded))
|
|
}
|
|
|
|
func TestQuota_Good_MaxGroups_ExistingGroupOK(t *testing.T) {
|
|
s, _ := New(":memory:")
|
|
defer s.Close()
|
|
|
|
sc, _ := NewScopedWithQuota(s, "tenant-a", QuotaConfig{MaxGroups: 2})
|
|
|
|
require.NoError(t, sc.Set("g1", "a", "1"))
|
|
require.NoError(t, sc.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"))
|
|
}
|
|
|
|
func TestQuota_Good_MaxGroups_DeleteAndRecreate(t *testing.T) {
|
|
s, _ := New(":memory:")
|
|
defer s.Close()
|
|
|
|
sc, _ := NewScopedWithQuota(s, "tenant-a", QuotaConfig{MaxGroups: 2})
|
|
|
|
require.NoError(t, sc.Set("g1", "k", "v"))
|
|
require.NoError(t, sc.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"))
|
|
}
|
|
|
|
func TestQuota_Good_MaxGroups_ZeroUnlimited(t *testing.T) {
|
|
s, _ := New(":memory:")
|
|
defer s.Close()
|
|
|
|
sc, _ := NewScopedWithQuota(s, "tenant-a", QuotaConfig{MaxGroups: 0})
|
|
|
|
for i := range 50 {
|
|
require.NoError(t, sc.Set(keyName(i), "k", "v"))
|
|
}
|
|
}
|
|
|
|
func TestQuota_Good_MaxGroups_ExpiredGroupExcluded(t *testing.T) {
|
|
s, _ := New(":memory:")
|
|
defer s.Close()
|
|
|
|
sc, _ := NewScopedWithQuota(s, "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"))
|
|
|
|
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"))
|
|
}
|
|
|
|
func TestQuota_Good_BothLimits(t *testing.T) {
|
|
s, _ := New(":memory:")
|
|
defer s.Close()
|
|
|
|
sc, _ := NewScopedWithQuota(s, "tenant-a", QuotaConfig{MaxKeys: 10, MaxGroups: 2})
|
|
|
|
require.NoError(t, sc.Set("g1", "a", "1"))
|
|
require.NoError(t, sc.Set("g2", "b", "2"))
|
|
|
|
// Group limit hit.
|
|
err := sc.Set("g3", "c", "3")
|
|
assert.True(t, errors.Is(err, ErrQuotaExceeded))
|
|
|
|
// But adding to existing groups is fine (within key limit).
|
|
require.NoError(t, sc.Set("g1", "d", "4"))
|
|
}
|
|
|
|
func TestQuota_Good_DoesNotAffectOtherNamespaces(t *testing.T) {
|
|
s, _ := New(":memory:")
|
|
defer s.Close()
|
|
|
|
a, _ := NewScopedWithQuota(s, "tenant-a", QuotaConfig{MaxKeys: 2})
|
|
b, _ := NewScopedWithQuota(s, "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"))
|
|
|
|
// a is at limit — but b's keys don't count against a.
|
|
err := a.Set("g", "a3", "v")
|
|
assert.True(t, errors.Is(err, ErrQuotaExceeded))
|
|
|
|
// b is also at limit independently.
|
|
err = b.Set("g", "b3", "v")
|
|
assert.True(t, errors.Is(err, ErrQuotaExceeded))
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// CountAll
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func TestCountAll_Good_WithPrefix(t *testing.T) {
|
|
s, _ := New(":memory:")
|
|
defer s.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"))
|
|
|
|
n, err := s.CountAll("ns-a:")
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 3, n)
|
|
|
|
n, err = s.CountAll("ns-b:")
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 1, n)
|
|
}
|
|
|
|
func TestCountAll_Good_EmptyPrefix(t *testing.T) {
|
|
s, _ := New(":memory:")
|
|
defer s.Close()
|
|
|
|
require.NoError(t, s.Set("g1", "k1", "v"))
|
|
require.NoError(t, s.Set("g2", "k2", "v"))
|
|
|
|
n, err := s.CountAll("")
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 2, n)
|
|
}
|
|
|
|
func TestCountAll_Good_ExcludesExpired(t *testing.T) {
|
|
s, _ := New(":memory:")
|
|
defer s.Close()
|
|
|
|
require.NoError(t, s.Set("ns:g", "permanent", "v"))
|
|
require.NoError(t, s.SetWithTTL("ns:g", "temp", "v", 1*time.Millisecond))
|
|
time.Sleep(5 * time.Millisecond)
|
|
|
|
n, err := s.CountAll("ns:")
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 1, n, "expired keys should not be counted")
|
|
}
|
|
|
|
func TestCountAll_Good_Empty(t *testing.T) {
|
|
s, _ := New(":memory:")
|
|
defer s.Close()
|
|
|
|
n, err := s.CountAll("nonexistent:")
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 0, n)
|
|
}
|
|
|
|
func TestCountAll_Bad_ClosedStore(t *testing.T) {
|
|
s, _ := New(":memory:")
|
|
s.Close()
|
|
|
|
_, err := s.CountAll("")
|
|
require.Error(t, err)
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Groups
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func TestGroups_Good_WithPrefix(t *testing.T) {
|
|
s, _ := New(":memory:")
|
|
defer s.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"))
|
|
|
|
groups, err := s.Groups("ns-a:")
|
|
require.NoError(t, err)
|
|
assert.Len(t, groups, 2)
|
|
assert.Contains(t, groups, "ns-a:g1")
|
|
assert.Contains(t, groups, "ns-a:g2")
|
|
}
|
|
|
|
func TestGroups_Good_EmptyPrefix(t *testing.T) {
|
|
s, _ := New(":memory:")
|
|
defer s.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"))
|
|
|
|
groups, err := s.Groups("")
|
|
require.NoError(t, err)
|
|
assert.Len(t, groups, 3)
|
|
}
|
|
|
|
func TestGroups_Good_Distinct(t *testing.T) {
|
|
s, _ := New(":memory:")
|
|
defer s.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"))
|
|
|
|
groups, err := s.Groups("")
|
|
require.NoError(t, err)
|
|
assert.Len(t, groups, 1)
|
|
assert.Equal(t, "g1", groups[0])
|
|
}
|
|
|
|
func TestGroups_Good_ExcludesExpired(t *testing.T) {
|
|
s, _ := New(":memory:")
|
|
defer s.Close()
|
|
|
|
require.NoError(t, s.Set("ns:g1", "permanent", "v"))
|
|
require.NoError(t, s.SetWithTTL("ns:g2", "temp", "v", 1*time.Millisecond))
|
|
time.Sleep(5 * time.Millisecond)
|
|
|
|
groups, err := s.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 TestGroups_Good_Empty(t *testing.T) {
|
|
s, _ := New(":memory:")
|
|
defer s.Close()
|
|
|
|
groups, err := s.Groups("nonexistent:")
|
|
require.NoError(t, err)
|
|
assert.Empty(t, groups)
|
|
}
|
|
|
|
func TestGroups_Bad_ClosedStore(t *testing.T) {
|
|
s, _ := New(":memory:")
|
|
s.Close()
|
|
|
|
_, err := s.Groups("")
|
|
require.Error(t, err)
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Helpers
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func keyName(i int) string {
|
|
return "key-" + string(rune('a'+i%26))
|
|
}
|