Files
miti99bot/internal/storage/memory_kv_test.go
T
tiennm99 9d680c58ad fix(storage): report missing CAS support as unsupported instead of conflict
- prefix.go: return errors.ErrUnsupported when inner store lacks CompareAndSwap, enabling fail-fast instead of infinite retries
- Adds comprehensive CAS semantics test coverage for all KV backends:
  * memory_kv_test.go: new tests for basic operations and CAS failures
  * prefix_test.go: tests for wrapped CAS errors and unsupported operations
  * dynamodb_kv_test.go, firestore_kv_test.go: CAS failure scenarios
- portfolio_test.go: test retry exhaustion, business-error short-circuit, concurrent updates, and fail-fast on unsupported CAS
2026-06-11 21:40:16 +07:00

92 lines
1.9 KiB
Go

package storage
import (
"context"
"errors"
"testing"
)
func TestMemoryKVStore_CompareAndSwap(t *testing.T) {
ctx := context.Background()
tests := []struct {
name string
existing []byte // nil = key absent before the call
expected []byte
val []byte
wantErr error
wantVal string // stored value after the call
}{
{
name: "create when absent",
existing: nil,
expected: nil,
val: []byte("v1"),
wantErr: nil,
wantVal: "v1",
},
{
name: "create when exists conflicts",
existing: []byte("v1"),
expected: nil,
val: []byte("v2"),
wantErr: ErrConflict,
wantVal: "v1",
},
{
name: "swap when expected matches",
existing: []byte("v1"),
expected: []byte("v1"),
val: []byte("v2"),
wantErr: nil,
wantVal: "v2",
},
{
name: "swap when expected stale conflicts",
existing: []byte("v2"),
expected: []byte("v1"),
val: []byte("v3"),
wantErr: ErrConflict,
wantVal: "v2",
},
{
name: "swap when key missing conflicts",
existing: nil,
expected: []byte("v1"),
val: []byte("v2"),
wantErr: ErrConflict,
wantVal: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := NewMemoryKVStore()
if tt.existing != nil {
if err := s.Put(ctx, "k", tt.existing); err != nil {
t.Fatalf("Put: %v", err)
}
}
err := s.CompareAndSwap(ctx, "k", tt.expected, tt.val)
if !errors.Is(err, tt.wantErr) {
t.Fatalf("CompareAndSwap: got %v, want %v", err, tt.wantErr)
}
got, err := s.Get(ctx, "k")
if tt.wantVal == "" {
if !errors.Is(err, ErrNotFound) {
t.Fatalf("Get after failed create: got (%q, %v), want ErrNotFound", got, err)
}
return
}
if err != nil {
t.Fatalf("Get: %v", err)
}
if string(got) != tt.wantVal {
t.Errorf("stored value = %q, want %q", got, tt.wantVal)
}
})
}
}