chore: use functional options to define the hook for when to create a new store

This commit is contained in:
Manuel de la Peña
2025-08-29 10:06:08 +02:00
parent 71fc8de907
commit 253aa04087
2 changed files with 88 additions and 30 deletions

View File

@@ -134,7 +134,7 @@ func Test_MYSQL_Non_UTF8(t *testing.T) {
func TestMySQLStorageTCK(t *testing.T) { func TestMySQLStorageTCK(t *testing.T) {
// The TCK needs the concrete type of the storage and the driver type returned by the Conn method. // The TCK needs the concrete type of the storage and the driver type returned by the Conn method.
s, err := tck.New[*Storage, *sql.DB](context.Background(), t, &MySQLStorageTCK{}, tck.PerTest) s, err := tck.New[*Storage, *sql.DB](context.Background(), t, &MySQLStorageTCK{}, tck.PerTest())
require.NoError(t, err) require.NoError(t, err)
suite.Run(t, &s) suite.Run(t, &s)

View File

@@ -13,18 +13,59 @@ import (
"github.com/gofiber/storage" "github.com/gofiber/storage"
) )
// CreationHook defines when the store should be created. // suiteHook defines when the store should be created.
// Please see [PerSuite] and [PerTest] for more details. // Please see [PerSuite] and [PerTest] for more details.
type CreationHook int type suiteHook int
const ( const (
// PerTest defines that the store should be created per test. // perTest defines that the store should be created per test.
PerTest CreationHook = iota perTest suiteHook = iota
// PerSuite defines that the store should be created per suite. // perSuite defines that the store should be created per suite.
PerSuite perSuite
) )
// suiteUpdater is the interface that must be implemented by the test suite.
// It defines how the [suiteOption]s update the suite.
type suiteUpdater interface {
updateHook(hook suiteHook) error
}
// suiteOption is the interface that must be implemented by the [suiteOption]s.
// It defines how the [suiteOption]s update the suite.
type suiteOption interface {
apply(s suiteUpdater) error
}
// suiteUpdateOption is the default implementation of the [suiteOption] interface.
// It is used to update the suite hook.
type suiteUpdateOption struct {
fn func(s suiteUpdater) error
}
// apply is the method that is called by the [suiteOption]s to update the suite.
func (o *suiteUpdateOption) apply(s suiteUpdater) error {
return o.fn(s)
}
// PerTest is a [suiteOption] that updates the suite hook to [perTest].
func PerTest() suiteOption {
return &suiteUpdateOption{
fn: func(s suiteUpdater) error {
return s.updateHook(perTest)
},
}
}
// PerSuite is a [suiteOption] that updates the suite hook to [perSuite].
func PerSuite() suiteOption {
return &suiteUpdateOption{
fn: func(s suiteUpdater) error {
return s.updateHook(perSuite)
},
}
}
// TCKSuite is the interface that must be implemented by the test suite. // TCKSuite is the interface that must be implemented by the test suite.
// It defines how to create a new store with a container. // It defines how to create a new store with a container.
// The generic parameters are the storage type and the driver type returned by the Conn method. // The generic parameters are the storage type and the driver type returned by the Conn method.
@@ -33,20 +74,26 @@ type TCKSuite[T storage.Storage, D any] interface {
} }
// New creates a new [StorageTestSuite] with the given [TCKSuite]. // New creates a new [StorageTestSuite] with the given [TCKSuite].
func New[T storage.Storage, D any](ctx context.Context, t *testing.T, tckSuite TCKSuite[T, D], creationHook CreationHook) (StorageTestSuite[T, D], error) { func New[T storage.Storage, D any](ctx context.Context, t *testing.T, tckSuite TCKSuite[T, D], opts ...suiteOption) (StorageTestSuite[T, D], error) {
if creationHook != PerSuite && creationHook != PerTest {
return StorageTestSuite[T, D]{}, fmt.Errorf("invalid creation hook: %d", creationHook)
}
if tckSuite == nil { if tckSuite == nil {
return StorageTestSuite[T, D]{}, fmt.Errorf("test suite is nil") return StorageTestSuite[T, D]{}, fmt.Errorf("test suite is nil")
} }
return StorageTestSuite[T, D]{ s := StorageTestSuite[T, D]{
ctx: ctx, ctx: ctx,
creationHook: creationHook, hook: perTest, // defaults to perTest
createFn: tckSuite.NewStoreWithContainer(), createFn: tckSuite.NewStoreWithContainer(),
}, nil }
for _, opt := range opts {
if err := opt.apply(&s); err != nil {
return StorageTestSuite[T, D]{}, fmt.Errorf("apply option: %w", err)
}
}
s.SetT(t)
return s, nil
} }
// StorageTestSuite is the test suite for the storage. // StorageTestSuite is the test suite for the storage.
@@ -54,14 +101,23 @@ func New[T storage.Storage, D any](ctx context.Context, t *testing.T, tckSuite T
// The generic parameters are the storage type and the driver type returned by the Conn method. // The generic parameters are the storage type and the driver type returned by the Conn method.
type StorageTestSuite[T storage.Storage, D any] struct { type StorageTestSuite[T storage.Storage, D any] struct {
suite.Suite suite.Suite
stats *suite.SuiteInformation stats *suite.SuiteInformation
ctx context.Context ctx context.Context
creationHook CreationHook hook suiteHook
createFn func(ctx context.Context, tb testing.TB) (T, testcontainers.Container, error) createFn func(ctx context.Context, tb testing.TB) (T, testcontainers.Container, error)
store storage.Storage store storage.Storage
closedStore bool closedStore bool
mu sync.Mutex mu sync.Mutex
ctr testcontainers.Container ctr testcontainers.Container
}
func (s *StorageTestSuite[T, D]) updateHook(hook suiteHook) error {
s.mu.Lock()
defer s.mu.Unlock()
s.hook = hook
return nil
} }
// cleanup is a helper function to cleanup the store and container. // cleanup is a helper function to cleanup the store and container.
@@ -101,7 +157,7 @@ func (s *StorageTestSuite[T, D]) HandleStats(_ string, stats *suite.SuiteInforma
// SetupSuite is a hook that is called when the suite is setup. // SetupSuite is a hook that is called when the suite is setup.
// It is used to create the store and container, only if the creation hook is [PerSuite]. // It is used to create the store and container, only if the creation hook is [PerSuite].
func (s *StorageTestSuite[T, D]) SetupSuite() { func (s *StorageTestSuite[T, D]) SetupSuite() {
if s.creationHook == PerSuite { if s.hook == perSuite {
t := s.T() t := s.T()
store, ctr, err := s.createFn(s.ctx, t) store, ctr, err := s.createFn(s.ctx, t)
@@ -110,6 +166,7 @@ func (s *StorageTestSuite[T, D]) SetupSuite() {
} }
s.store = store s.store = store
s.ctr = ctr s.ctr = ctr
s.closedStore = false
err = s.store.Reset() err = s.store.Reset()
s.Require().NoError(err) s.Require().NoError(err)
@@ -119,7 +176,7 @@ func (s *StorageTestSuite[T, D]) SetupSuite() {
// TearDownSuite is a hook that is called when the suite is torn down. // TearDownSuite is a hook that is called when the suite is torn down.
// It is used to cleanup the store and container, only if the creation hook is [PerSuite]. // It is used to cleanup the store and container, only if the creation hook is [PerSuite].
func (s *StorageTestSuite[T, D]) TearDownSuite() { func (s *StorageTestSuite[T, D]) TearDownSuite() {
if s.creationHook == PerSuite { if s.hook == perSuite {
s.Require().NoError(s.cleanup()) s.Require().NoError(s.cleanup())
} }
} }
@@ -127,7 +184,7 @@ func (s *StorageTestSuite[T, D]) TearDownSuite() {
// SetupTest is a hook that is called when the test is setup. // SetupTest is a hook that is called when the test is setup.
// It is used to create the store and container, only if the creation hook is [PerTest]. // It is used to create the store and container, only if the creation hook is [PerTest].
func (s *StorageTestSuite[T, D]) SetupTest() { func (s *StorageTestSuite[T, D]) SetupTest() {
if s.creationHook == PerTest { if s.hook == perTest {
t := s.T() t := s.T()
store, ctr, err := s.createFn(s.ctx, t) store, ctr, err := s.createFn(s.ctx, t)
@@ -136,6 +193,7 @@ func (s *StorageTestSuite[T, D]) SetupTest() {
} }
s.store = store s.store = store
s.ctr = ctr s.ctr = ctr
s.closedStore = false
err = s.store.Reset() err = s.store.Reset()
s.Require().NoError(err) s.Require().NoError(err)
@@ -145,7 +203,7 @@ func (s *StorageTestSuite[T, D]) SetupTest() {
// TearDownTest is a hook that is called when the test is torn down. // TearDownTest is a hook that is called when the test is torn down.
// It is used to cleanup the store and container, only if the creation hook is [PerTest]. // It is used to cleanup the store and container, only if the creation hook is [PerTest].
func (s *StorageTestSuite[T, D]) TearDownTest() { func (s *StorageTestSuite[T, D]) TearDownTest() {
if s.creationHook == PerTest { if s.hook == perTest {
s.Require().NoError(s.cleanup()) s.Require().NoError(s.cleanup())
} }
} }
@@ -212,7 +270,7 @@ func (s *StorageTestSuite[T, D]) TestGetExpired() {
s.Eventually(func() bool { s.Eventually(func() bool {
s.requireKeyNotExists("temp_key") s.requireKeyNotExists("temp_key")
return true return true
}, 2*time.Second, 100*time.Millisecond, "Key should expire") }, 2*time.Second, 1*time.Second, "Key should expire")
} }
func (s *StorageTestSuite[T, D]) TestDelete() { func (s *StorageTestSuite[T, D]) TestDelete() {