diff --git a/testhelpers/tck/suite_test.go b/testhelpers/tck/suite_test.go new file mode 100644 index 00000000..af7a4d09 --- /dev/null +++ b/testhelpers/tck/suite_test.go @@ -0,0 +1,167 @@ +package tck + +import ( + "context" + "sync" + "testing" + "time" + + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + testcontainers "github.com/testcontainers/testcontainers-go" +) + +type mockErrorSuite struct{} + +func (s *mockErrorSuite) NewStore() func(ctx context.Context, tb testing.TB, ctr testcontainers.Container) (*mockStorage, error) { + return func(ctx context.Context, tb testing.TB, ctr testcontainers.Container) (*mockStorage, error) { + storage := &mockStorage{ + Map: &sync.Map{}, + } + + return storage, nil + } +} + +func (s *mockErrorSuite) NewContainer() func(ctx context.Context, tb testing.TB) (testcontainers.Container, error) { + return func(ctx context.Context, tb testing.TB) (testcontainers.Container, error) { + return nil, nil + } +} + +func TestNew(t *testing.T) { + t.Run("per-test/setup-suite", func(t *testing.T) { + s, err := New[*mockStorage, any](context.Background(), t, &mockErrorSuite{}, PerTest()) + require.NoError(t, err) + + s.SetupSuite() + + s.Require().Nil(s.store) + }) + + t.Run("per-suite/setup-test", func(t *testing.T) { + s, err := New[*mockStorage, any](context.Background(), t, &mockErrorSuite{}, PerSuite()) + require.NoError(t, err) + + s.SetupTest() + + s.Require().NotNil(s.store) + }) + + t.Run("cleanup/non-closed-store", func(t *testing.T) { + s, err := New[*mockStorage, any](context.Background(), t, &mockErrorSuite{}, PerTest()) + require.NoError(t, err) + + s.SetupSuite() + s.SetupTest() + + s.closedStore = false + + s.Require().NoError(s.cleanup()) + + mockStorage := s.store.(*mockStorage) + s.Require().Nil(mockStorage.Map) + }) + + t.Run("tck-is-nil", func(t *testing.T) { + s, err := New[*mockStorage, any, testcontainers.Container](context.Background(), t, nil, PerTest()) + require.Error(t, err) + require.Nil(t, s) + }) +} + +type mockStorage struct { + *sync.Map +} + +// implement the storage interface +func (s *mockStorage) Set(key string, value []byte, ttl time.Duration) error { + s.Store(key, string(value)) + + // in a goroutine, remove the key after ttl + if ttl > 0 { + go func() { + time.Sleep(ttl) + //nolint:errcheck + _ = s.Delete(key) + }() + } + + return nil +} + +func (s *mockStorage) Get(key string) ([]byte, error) { + val, ok := s.Load(key) + + if !ok { + // key expired or does not exist + return nil, nil + } + + return []byte(val.(string)), nil +} + +func (s *mockStorage) Delete(key string) error { + s.Map.Delete(key) + return nil +} + +func (s *mockStorage) Reset() error { + s.Map = &sync.Map{} + return nil +} + +func (s *mockStorage) Close() error { + s.Map = nil + return nil +} + +func (s *mockStorage) Conn() any { + return "mock conn" +} + +func (s *mockStorage) SetWithContext(ctx context.Context, key string, value []byte, ttl time.Duration) error { + if ctx.Err() != nil { + return ctx.Err() + } + + return s.Set(key, value, ttl) +} + +func (s *mockStorage) GetWithContext(ctx context.Context, key string) ([]byte, error) { + if ctx.Err() != nil { + return nil, ctx.Err() + } + + return s.Get(key) +} + +func (s *mockStorage) DeleteWithContext(ctx context.Context, key string) error { + if ctx.Err() != nil { + return ctx.Err() + } + + return s.Delete(key) +} + +func (s *mockStorage) ResetWithContext(ctx context.Context) error { + if ctx.Err() != nil { + return ctx.Err() + } + + return s.Reset() +} + +func TestRunPerTest(t *testing.T) { + s, err := New[*mockStorage, any](context.Background(), t, &mockErrorSuite{}, PerTest()) + require.NoError(t, err) + + suite.Run(t, s) +} + +func TestRunPerSuite(t *testing.T) { + s, err := New[*mockStorage, any](context.Background(), t, &mockErrorSuite{}, PerSuite()) + require.NoError(t, err) + + suite.Run(t, s) +}