adapter_test API

adapter_test

package

API reference for the adapter_test package.

F
function

TestInMemoryStoreGetSetKeys

Parameters

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

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

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

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

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

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
Email string gorm:"primaryKey"
Name string
F
function

TestGormEntityStoreStringKey

Parameters

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

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

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

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

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

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

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

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.

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

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

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.

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

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

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

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

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

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

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)
	}
}