package k2v_test import ( k2v "code.notaphish.fyi/milas/garage-k2v-go" "context" "github.com/stretchr/testify/require" "math/rand/v2" "strconv" "testing" ) type fixture struct { t testing.TB ctx context.Context cli *k2v.Client bucket k2v.Bucket } func newFixture(t testing.TB) (*fixture, context.Context) { t.Helper() ctx := testContext(t) cli := k2v.NewClient(k2v.EndpointFromEnv(), k2v.KeyFromEnv()) t.Cleanup(cli.Close) f := &fixture{ t: t, ctx: ctx, cli: cli, bucket: k2v.Bucket("k2v-test"), } return f, ctx } func testContext(t testing.TB) context.Context { ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) return ctx } func randomKey(prefix string) string { return prefix + "-" + strconv.Itoa(rand.IntN(1000000)) } func randomPk() string { return randomKey("pk") } func randomSk() string { return randomKey("sk") } func TestClient_InsertItem(t *testing.T) { t.Parallel() f, ctx := newFixture(t) err := f.cli.InsertItem(ctx, f.bucket, randomPk(), randomSk(), "", []byte("hello")) require.NoError(t, err) } func TestClient_ReadItemNotExist(t *testing.T) { t.Parallel() f, ctx := newFixture(t) pk := randomPk() sk := randomSk() t.Run("Single", func(t *testing.T) { t.Parallel() item, ct, err := f.cli.ReadItemSingle(ctx, f.bucket, pk, sk) require.ErrorIs(t, err, k2v.NoSuchItemErr) require.Nil(t, item) require.Empty(t, ct) }) t.Run("Multi", func(t *testing.T) { t.Parallel() items, ct, err := f.cli.ReadItemMulti(ctx, f.bucket, pk, sk) require.ErrorIs(t, err, k2v.NoSuchItemErr) require.Empty(t, items) require.Empty(t, ct) }) } func TestClient_ReadItemTombstone(t *testing.T) { t.Parallel() f, ctx := newFixture(t) pk := randomPk() sk := randomSk() t.Logf("Creating item: PK=%s, SK=%s", pk, sk) err := f.cli.InsertItem(ctx, f.bucket, pk, sk, "", []byte("hello")) require.NoError(t, err) _, ct, err := f.cli.ReadItemSingle(ctx, f.bucket, pk, sk) require.NoError(t, err) err = f.cli.DeleteItem(ctx, f.bucket, pk, sk, ct) require.NoError(t, err) t.Run("Single", func(t *testing.T) { item, ct, err := f.cli.ReadItemSingle(ctx, f.bucket, pk, sk) require.ErrorIs(t, err, k2v.TombstoneItemErr) require.Nil(t, item) require.NotEmpty(t, ct) }) t.Run("Multi", func(t *testing.T) { items, ct, err := f.cli.ReadItemMulti(ctx, f.bucket, pk, sk) require.ErrorIs(t, err, k2v.TombstoneItemErr) require.Empty(t, items) require.NotEmpty(t, ct) }) } func TestClient_ReadItemSingleRevision(t *testing.T) { t.Parallel() f, ctx := newFixture(t) pk := randomPk() sk := randomSk() err := f.cli.InsertItem(ctx, f.bucket, pk, sk, "", []byte("hello")) require.NoError(t, err) t.Run("Single", func(t *testing.T) { t.Parallel() item, ct, err := f.cli.ReadItemSingle(ctx, f.bucket, pk, sk) require.NoError(t, err) require.Equal(t, "hello", string(item)) require.NotEmpty(t, ct) }) t.Run("Multi", func(t *testing.T) { t.Parallel() items, ct, err := f.cli.ReadItemMulti(ctx, f.bucket, pk, sk) require.NoError(t, err) require.Len(t, items, 1) require.Equal(t, "hello", string(items[0])) require.NotEmpty(t, ct) }) } func TestClient_ReadItemMultipleRevisions(t *testing.T) { t.Parallel() f, ctx := newFixture(t) pk := randomPk() sk := randomSk() err := f.cli.InsertItem(ctx, f.bucket, pk, sk, "", []byte("hello1")) require.NoError(t, err) // don't use a continuation token to intentionally create 2x concurrent revisions err = f.cli.InsertItem(ctx, f.bucket, pk, sk, "", []byte("hello2")) require.NoError(t, err) t.Run("Single", func(t *testing.T) { t.Parallel() item, ct, err := f.cli.ReadItemSingle(ctx, f.bucket, pk, sk) require.ErrorIs(t, err, k2v.ConcurrentItemsErr) require.Nil(t, item) require.NotEmpty(t, ct) }) t.Run("Multi", func(t *testing.T) { t.Parallel() items, ct, err := f.cli.ReadItemMulti(ctx, f.bucket, pk, sk) require.NoError(t, err) require.Len(t, items, 2) require.Equal(t, "hello1", string(items[0])) require.Equal(t, "hello2", string(items[1])) require.NotEmpty(t, ct) }) }