adapter_test
API
adapter_test
packageAPI reference for the adapter_test
package.
Imports
(17)
STD
context
STD
testing
INT
github.com/mirkobrombin/go-warp/v1/adapter
PKG
gorm.io/driver/sqlite
PKG
gorm.io/gorm
PKG
gorm.io/gorm/logger
STD
fmt
STD
strings
STD
time
INT
github.com/mirkobrombin/go-warp/v1/cache
INT
github.com/mirkobrombin/go-warp/v1/core
INT
github.com/mirkobrombin/go-warp/v1/merge
STD
errors
STD
os
PKG
github.com/alicebob/miniredis/v2
PKG
github.com/redis/go-redis/v9
INT
github.com/mirkobrombin/go-warp/v1/errors
F
function
TestInMemoryStoreGetSetKeys
Parameters
t
v1/adapter/adapter_test.go:10-29
func TestInMemoryStoreGetSetKeys(t *testing.T)
{
s := adapter.NewInMemoryStore[string]()
ctx := context.Background()
if _, ok, err := s.Get(ctx, "foo"); err != nil || ok {
t.Fatalf("Get: expected not found, got ok=%v err=%v", ok, err)
}
if err := s.Set(ctx, "foo", "bar"); err != nil {
t.Fatalf("Set: %v", err)
}
if v, ok, err := s.Get(ctx, "foo"); err != nil || !ok || v != "bar" {
t.Fatalf("Get: expected bar, got %v ok=%v err=%v", v, ok, err)
}
keys, err := s.Keys(ctx)
if err != nil {
t.Fatalf("Keys: %v", err)
}
if len(keys) != 1 || keys[0] != "foo" {
t.Fatalf("Keys: expected [foo], got %v", keys)
}
}
F
function
TestInMemoryStoreBatchCommitDelete
Parameters
t
v1/adapter/adapter_test.go:31-63
func TestInMemoryStoreBatchCommitDelete(t *testing.T)
{
s := adapter.NewInMemoryStore[string]()
ctx := context.Background()
if err := s.Set(ctx, "remove", "me"); err != nil {
t.Fatalf("Set: %v", err)
}
b, err := s.Batch(ctx)
if err != nil {
t.Fatalf("Batch: %v", err)
}
if err := b.Set(ctx, "foo", "bar"); err != nil {
t.Fatalf("Batch Set: %v", err)
}
if err := b.Delete(ctx, "remove"); err != nil {
t.Fatalf("Batch Delete: %v", err)
}
if err := b.Commit(ctx); err != nil {
t.Fatalf("Commit: %v", err)
}
if _, ok, _ := s.Get(ctx, "remove"); ok {
t.Fatalf("Delete: key still present")
}
if v, ok, err := s.Get(ctx, "foo"); err != nil || !ok || v != "bar" {
t.Fatalf("Get: expected bar, got %v ok=%v err=%v", v, ok, err)
}
keys, err := s.Keys(ctx)
if err != nil {
t.Fatalf("Keys: %v", err)
}
if len(keys) != 1 || keys[0] != "foo" {
t.Fatalf("Keys: expected [foo], got %v", keys)
}
}
S
struct
TestUser
v1/adapter/gorm_entity_store_test.go:13-16
type TestUser struct
Fields
| Name | Type | Description |
|---|---|---|
| ID | int | gorm:"primaryKey" |
| Name | string |
F
function
newGormEntityStore
Parameters
t
Returns
v1/adapter/gorm_entity_store_test.go:18-32
func newGormEntityStore(t *testing.T) (*adapter.GormEntityStore[TestUser], *gorm.DB)
{
t.Helper()
db, err := gorm.Open(sqlite.Open("file::memory:?cache=shared"), &gorm.Config{
Logger: logger.Default.LogMode(logger.Silent),
})
if err != nil {
t.Fatalf("failed to connect database: %v", err)
}
_ = db.AutoMigrate(&TestUser{})
// Use Int key parser because TestUser ID is int
store := adapter.NewGormEntityStore[TestUser](db, adapter.WithEntityKeyParser(adapter.ParseIntKey))
return store, db
}
F
function
TestGormEntityStoreGetSet
Parameters
t
v1/adapter/gorm_entity_store_test.go:34-64
func TestGormEntityStoreGetSet(t *testing.T)
{
s, _ := newGormEntityStore(t)
ctx := context.Background()
// Set
user := TestUser{ID: 1, Name: "Alice"}
if err := s.Set(ctx, "1", user); err != nil {
t.Fatalf("Set: %v", err)
}
// Get
v, ok, err := s.Get(ctx, "1")
if err != nil {
t.Fatalf("Get: %v", err)
}
if !ok {
t.Fatal("Get: expected found")
}
if v.Name != "Alice" {
t.Fatalf("Get: expected Alice, got %v", v.Name)
}
// Get not found
_, ok, err = s.Get(ctx, "999")
if err != nil {
t.Fatalf("Get 999: %v", err)
}
if ok {
t.Fatal("Get 999: expected not found")
}
}
F
function
TestGormEntityStoreKeys
Parameters
t
v1/adapter/gorm_entity_store_test.go:66-82
func TestGormEntityStoreKeys(t *testing.T)
{
s, _ := newGormEntityStore(t)
ctx := context.Background()
s.Set(ctx, "1", TestUser{ID: 1, Name: "A"})
s.Set(ctx, "2", TestUser{ID: 2, Name: "B"})
keys, err := s.Keys(ctx)
if err != nil {
t.Fatalf("Keys: %v", err)
}
// Order is not guaranteed, but usually sorted by PK
if len(keys) != 2 {
t.Fatalf("Keys: expected 2, got %d", len(keys))
}
}
F
function
TestGormEntityStoreBatch
Parameters
t
v1/adapter/gorm_entity_store_test.go:84-116
func TestGormEntityStoreBatch(t *testing.T)
{
s, db := newGormEntityStore(t)
ctx := context.Background()
b, _ := s.Batch(ctx)
b.Set(ctx, "1", TestUser{ID: 1, Name: "Batch1"})
b.Set(ctx, "2", TestUser{ID: 2, Name: "Batch2"})
// Pre-exist for delete
db.Create(&TestUser{ID: 3, Name: "DeleteMe"})
b.Delete(ctx, "3")
if err := b.Commit(ctx); err != nil {
t.Fatalf("Commit: %v", err)
}
// Verify
var count int64
db.Model(&TestUser{}).Count(&count)
if count != 2 {
t.Fatalf("Expected 2 users, got %d", count)
}
v, ok, _ := s.Get(ctx, "1")
if !ok || v.Name != "Batch1" {
t.Fatal("Batch1 failed")
}
v, ok, _ = s.Get(ctx, "3")
if ok {
t.Fatal("Batch delete failed")
}
}
S
struct
StringUser
Test with String PK
v1/adapter/gorm_entity_store_test.go:119-122
type StringUser struct
Fields
| Name | Type | Description |
|---|---|---|
| string | gorm:"primaryKey" | |
| Name | string |
F
function
TestGormEntityStoreStringKey
Parameters
t
v1/adapter/gorm_entity_store_test.go:124-139
func TestGormEntityStoreStringKey(t *testing.T)
{
db, _ := gorm.Open(sqlite.Open("file::memory:?cache=shared"), &gorm.Config{
Logger: logger.Default.LogMode(logger.Silent),
})
db.AutoMigrate(&StringUser{})
s := adapter.NewGormEntityStore[StringUser](db, adapter.WithEntityKeyColumn("email"))
ctx := context.Background()
s.Set(ctx, "[email protected]", StringUser{Email: "[email protected]", Name: "Foo"})
v, ok, _ := s.Get(ctx, "[email protected]")
if !ok || v.Name != "Foo" {
t.Fatal("Get failed")
}
}
F
function
newGormStore
Parameters
t
Returns
v1/adapter/gorm_store_test.go:19-31
func newGormStore[T any](t *testing.T) (*adapter.GormStore[T], *gorm.DB)
{
t.Helper()
db, err := gorm.Open(sqlite.Open("file::memory:?cache=shared"), &gorm.Config{
Logger: logger.Default.LogMode(logger.Silent),
})
if err != nil {
t.Fatalf("failed to connect database: %v", err)
}
_ = db.Migrator().DropTable("warp_kv_store")
return adapter.NewGormStore[T](db), db
}
F
function
TestGormStoreGetSetKeys
Parameters
t
v1/adapter/gorm_store_test.go:33-50
func TestGormStoreGetSetKeys(t *testing.T)
{
s, _ := newGormStore[string](t)
ctx := context.Background()
if err := s.Set(ctx, "foo", "bar"); err != nil {
t.Fatalf("Set: %v", err)
}
if v, ok, err := s.Get(ctx, "foo"); err != nil || !ok || v != "bar" {
t.Fatalf("Get: expected bar, got %v err %v", v, err)
}
keys, err := s.Keys(ctx)
if err != nil {
t.Fatalf("Keys: %v", err)
}
if len(keys) != 1 || keys[0] != "foo" {
t.Fatalf("Keys: expected [foo], got %v", keys)
}
}
F
function
TestGormStorePersistenceAndWarmup
Parameters
t
v1/adapter/gorm_store_test.go:52-73
func TestGormStorePersistenceAndWarmup(t *testing.T)
{
s, _ := newGormStore[string](t)
ctx := context.Background()
// warp1 writes a value which should persist
c1 := cache.NewInMemory[merge.Value[string]]()
w1 := core.New[string](c1, s, nil, merge.NewEngine[string]())
w1.Register("foo", core.ModeStrongLocal, time.Minute)
if err := w1.Set(ctx, "foo", "bar"); err != nil {
t.Fatalf("Set: %v", err)
}
// warp2 uses a fresh cache but same store; warmup should load persisted value
c2 := cache.NewInMemory[merge.Value[string]]()
w2 := core.New[string](c2, s, nil, merge.NewEngine[string]())
w2.Register("foo", core.ModeStrongLocal, time.Minute)
w2.Warmup(ctx)
if v, err := w2.Get(ctx, "foo"); err != nil || v != "bar" {
t.Fatalf("Warmup/Get: expected bar, got %v err %v", v, err)
}
}
F
function
TestGormStoreBatch
Parameters
t
v1/adapter/gorm_store_test.go:75-115
func TestGormStoreBatch(t *testing.T)
{
s, _ := newGormStore[string](t)
ctx := context.Background()
b, err := s.Batch(ctx)
if err != nil {
t.Fatalf("Batch: %v", err)
}
if err := b.Set(ctx, "k1", "v1"); err != nil {
t.Errorf("Batch Set: %v", err)
}
if err := b.Set(ctx, "k2", "v2"); err != nil {
t.Errorf("Batch Set: %v", err)
}
// Pre-populate k3 to delete it
s.Set(ctx, "k3", "v3")
if err := b.Delete(ctx, "k3"); err != nil {
t.Errorf("Batch Delete: %v", err)
}
if err := b.Commit(ctx); err != nil {
t.Fatalf("Commit: %v", err)
}
// Verify
v, ok, _ := s.Get(ctx, "k1")
if !ok || v != "v1" {
t.Errorf("k1 missing or wrong: %v", v)
}
v, ok, _ = s.Get(ctx, "k2")
if !ok || v != "v2" {
t.Errorf("k2 missing or wrong: %v", v)
}
_, ok, _ = s.Get(ctx, "k3")
if ok {
t.Errorf("k3 should be deleted")
}
}
F
function
TestGormStoreLargeBatch
Parameters
t
v1/adapter/gorm_store_test.go:117-154
func TestGormStoreLargeBatch(t *testing.T)
{
s, _ := newGormStore[string](t)
ctx := context.Background()
b, err := s.Batch(ctx)
if err != nil {
t.Fatalf("Batch: %v", err)
}
// 500 items to enable batching logic (chunk size 100)
count := 500
for i := 0; i < count; i++ {
key := fmt.Sprintf("k-%d", i)
val := fmt.Sprintf("v-%d", i)
if err := b.Set(ctx, key, val); err != nil {
t.Errorf("Set %d: %v", i, err)
}
}
if err := b.Commit(ctx); err != nil {
t.Fatalf("Commit: %v", err)
}
// Verify count
keys, err := s.Keys(ctx)
if err != nil {
t.Fatalf("Keys: %v", err)
}
if len(keys) != count {
t.Errorf("Expected %d keys, got %d", count, len(keys))
}
// Verify random sample
v, ok, _ := s.Get(ctx, "k-499")
if !ok || v != "v-499" {
t.Errorf("k-499 missing or wrong")
}
}
F
function
TestGormStoreLongKeys
Parameters
t
v1/adapter/gorm_store_test.go:156-178
func TestGormStoreLongKeys(t *testing.T)
{
s, _ := newGormStore[string](t)
ctx := context.Background()
// 1000 chars key
longKey := strings.Repeat("a", 1000)
val := "long-key-val"
if err := s.Set(ctx, longKey, val); err != nil {
t.Fatalf("Set: %v", err)
}
v, ok, err := s.Get(ctx, longKey)
if err != nil {
t.Fatalf("Get: %v", err)
}
if !ok {
t.Fatalf("Get: not found")
}
if v != val {
t.Fatalf("Get: expected %v, got %v", val, v)
}
}
F
function
TestGormStoreWithTableName
Parameters
t
v1/adapter/gorm_store_test.go:180-197
func TestGormStoreWithTableName(t *testing.T)
{
db, err := gorm.Open(sqlite.Open("file::memory:?cache=shared"), &gorm.Config{})
if err != nil {
t.Fatalf("failed to connect database: %v", err)
}
s := adapter.NewGormStore[string](db, adapter.WithGormTableName("custom_kv"))
ctx := context.Background()
if err := s.Set(ctx, "foo", "bar"); err != nil {
t.Fatalf("Set: %v", err)
}
// Verify table exists
if !db.Migrator().HasTable("custom_kv") {
t.Fatal("custom_kv table does not exist")
}
}
F
function
newRedisStore
newRedisStore returns a Redis-backed store and context for testing.
It also registers cleanup to flush data, close the client and stop the
underlying miniredis server.
Parameters
t
Returns
v1/adapter/redis_store_test.go:23-55
func newRedisStore[T any](t *testing.T) (*adapter.RedisStore[T], context.Context)
{
t.Helper()
addr := os.Getenv("WARP_TEST_REDIS_ADDR")
forceReal := os.Getenv("WARP_TEST_FORCE_REAL") == "true"
var client *redis.Client
ctx := context.Background()
if forceReal && addr == "" {
t.Fatal("WARP_TEST_FORCE_REAL is true but WARP_TEST_REDIS_ADDR is empty")
}
if addr != "" {
t.Logf("TestRedisStore: using real Redis at %s", addr)
client = redis.NewClient(&redis.Options{Addr: addr})
t.Cleanup(func() {
_ = client.FlushDB(ctx).Err()
_ = client.Close()
})
} else {
t.Log("TestRedisStore: using miniredis")
mr, err := miniredis.Run()
if err != nil {
t.Fatalf("miniredis run: %v", err)
}
client = redis.NewClient(&redis.Options{Addr: mr.Addr()})
t.Cleanup(func() {
_ = client.FlushDB(ctx).Err()
_ = client.Close()
mr.Close()
})
}
return adapter.NewRedisStore[T](client), ctx
}
F
function
TestRedisStoreGetSetKeys
Parameters
t
v1/adapter/redis_store_test.go:57-72
func TestRedisStoreGetSetKeys(t *testing.T)
{
s, ctx := newRedisStore[string](t)
if err := s.Set(ctx, "foo", "bar"); err != nil {
t.Fatalf("Set: %v", err)
}
if v, ok, err := s.Get(ctx, "foo"); err != nil || !ok || v != "bar" {
t.Fatalf("Get: expected bar, got %v err %v", v, err)
}
keys, err := s.Keys(ctx)
if err != nil {
t.Fatalf("Keys: %v", err)
}
if len(keys) != 1 || keys[0] != "foo" {
t.Fatalf("Keys: expected [foo], got %v", keys)
}
}
F
function
TestRedisStorePersistenceAndWarmup
Parameters
t
v1/adapter/redis_store_test.go:74-94
func TestRedisStorePersistenceAndWarmup(t *testing.T)
{
s, ctx := newRedisStore[string](t)
// warp1 writes a value which should persist in Redis
c1 := cache.NewInMemory[merge.Value[string]]()
w1 := core.New[string](c1, s, nil, merge.NewEngine[string]())
w1.Register("foo", core.ModeStrongLocal, time.Minute)
if err := w1.Set(ctx, "foo", "bar"); err != nil {
t.Fatalf("Set: %v", err)
}
// warp2 uses a fresh cache but same store; warmup should load persisted value
c2 := cache.NewInMemory[merge.Value[string]]()
w2 := core.New[string](c2, s, nil, merge.NewEngine[string]())
w2.Register("foo", core.ModeStrongLocal, time.Minute)
w2.Warmup(ctx)
if v, err := w2.Get(ctx, "foo"); err != nil || v != "bar" {
t.Fatalf("Warmup/Get: expected bar, got %v err %v", v, err)
}
}
F
function
newRedisStoreWithServer
newRedisStoreWithServer returns a Redis-backed store along with the
underlying miniredis server and client for tests that need to manipulate
the server state.
Parameters
t
v1/adapter/redis_store_test.go:99-135
func newRedisStoreWithServer[T any](t *testing.T) (*adapter.RedisStore[T], context.Context, *miniredis.Miniredis, *redis.Client)
{
t.Helper()
addr := os.Getenv("WARP_TEST_REDIS_ADDR")
forceReal := os.Getenv("WARP_TEST_FORCE_REAL") == "true"
var client *redis.Client
var mr *miniredis.Miniredis
ctx := context.Background()
if forceReal && addr == "" {
t.Fatal("WARP_TEST_FORCE_REAL is true but WARP_TEST_REDIS_ADDR is empty")
}
if addr != "" {
t.Logf("TestRedisStoreWithServer: using real Redis at %s", addr)
client = redis.NewClient(&redis.Options{Addr: addr})
t.Cleanup(func() {
_ = client.FlushDB(ctx).Err()
_ = client.Close()
})
} else {
t.Log("TestRedisStoreWithServer: using miniredis")
var err error
mr, err = miniredis.Run()
if err != nil {
t.Fatalf("miniredis run: %v", err)
}
client = redis.NewClient(&redis.Options{Addr: mr.Addr()})
t.Cleanup(func() {
_ = client.FlushDB(ctx).Err()
_ = client.Close()
if mr != nil {
mr.Close()
}
})
}
return adapter.NewRedisStore[T](client), ctx, mr, client
}
F
function
TestRedisStoreSetMarshalError
Parameters
t
v1/adapter/redis_store_test.go:137-143
func TestRedisStoreSetMarshalError(t *testing.T)
{
s, ctx := newRedisStore[chan int](t)
ch := make(chan int)
if err := s.Set(ctx, "foo", ch); err == nil {
t.Fatalf("expected marshal error")
}
}
F
function
TestRedisStoreGetUnmarshalError
Parameters
t
v1/adapter/redis_store_test.go:145-153
func TestRedisStoreGetUnmarshalError(t *testing.T)
{
s, ctx, _, client := newRedisStoreWithServer[string](t)
if err := client.Set(ctx, "foo", "invalid", 0).Err(); err != nil {
t.Fatalf("client.Set: %v", err)
}
if _, _, err := s.Get(ctx, "foo"); err == nil {
t.Fatalf("expected unmarshal error")
}
}
F
function
TestRedisStoreKeysScanError
Parameters
t
v1/adapter/redis_store_test.go:155-165
func TestRedisStoreKeysScanError(t *testing.T)
{
s, ctx, mr, _ := newRedisStoreWithServer[string](t)
if mr == nil {
t.Skip("skipping test requiring miniredis control")
}
mr.Close()
mr = nil
if _, err := s.Keys(ctx); err == nil {
t.Fatalf("expected scan error")
}
}
F
function
TestRedisStoreBatchCommitError
Parameters
t
v1/adapter/redis_store_test.go:167-184
func TestRedisStoreBatchCommitError(t *testing.T)
{
s, ctx, mr, _ := newRedisStoreWithServer[string](t)
b, err := s.Batch(ctx)
if err != nil {
t.Fatalf("Batch: %v", err)
}
if err := b.Set(ctx, "foo", "bar"); err != nil {
t.Fatalf("Batch Set: %v", err)
}
if mr == nil {
t.Skip("skipping test requiring miniredis control")
}
mr.Close()
mr = nil
if err := b.Commit(ctx); err == nil {
t.Fatalf("expected commit error")
}
}
F
function
TestRedisStoreSentinelErrors
Parameters
t
v1/adapter/redis_store_test.go:186-205
func TestRedisStoreSentinelErrors(t *testing.T)
{
t.Run("connection closed", func(t *testing.T) {
s, ctx, _, client := newRedisStoreWithServer[string](t)
_ = s.Set(ctx, "foo", "bar")
_ = client.Close()
if _, _, err := s.Get(ctx, "foo"); !errors.Is(err, warperrors.ErrConnectionClosed) {
t.Fatalf("expected connection closed, got %v", err)
}
})
t.Run("timeout", func(t *testing.T) {
s, ctx := newRedisStore[string](t)
tCtx, cancel := context.WithTimeout(ctx, time.Nanosecond)
defer cancel()
time.Sleep(time.Millisecond)
if _, _, err := s.Get(tCtx, "foo"); !errors.Is(err, warperrors.ErrTimeout) {
t.Fatalf("expected timeout, got %v", err)
}
})
}
F
function
TestRedisStoreWithByteCodec
Parameters
t
v1/adapter/redis_store_test.go:207-250
func TestRedisStoreWithByteCodec(t *testing.T)
{
mr, err := miniredis.Run()
if err != nil {
t.Fatalf("miniredis run: %v", err)
}
defer mr.Close()
client := redis.NewClient(&redis.Options{Addr: mr.Addr()})
defer client.Close()
// Use ByteCodec
store := adapter.NewRedisStore[[]byte](client, adapter.WithCodec(cache.ByteCodec{}))
ctx := context.Background()
key := "raw_bytes"
val := []byte("binary_data")
// Set via Store
if err := store.Set(ctx, key, val); err != nil {
t.Fatalf("Set failed: %v", err)
}
// Verify in Redis directly (should be raw string, not Gob encoded)
// Gob encoded usually starts with some header bytes.
// Raw string "binary_data" is just "binary_data".
got, err := client.Get(ctx, key).Result()
if err != nil {
t.Fatalf("client.Get failed: %v", err)
}
if got != string(val) {
t.Fatalf("expected raw value %q, got %q", string(val), got)
}
// Get via Store
res, ok, err := store.Get(ctx, key)
if err != nil {
t.Fatalf("store.Get failed: %v", err)
}
if !ok {
t.Fatalf("store.Get returned not found")
}
if string(res) != string(val) {
t.Fatalf("store.Get returned mismatch: got %q, want %q", res, val)
}
}