diff --git a/contract/cache.go b/contract/cache.go index 01cfaad..803e38e 100644 --- a/contract/cache.go +++ b/contract/cache.go @@ -2,58 +2,189 @@ package contract import ( "context" + "encoding/json" "errors" "time" ) var ( // ErrCacheKeyNotFound is returned when a key does not exist in the cache. - // Callers should also supply additional context, such as the cache key. ErrCacheKeyNotFound = errors.New("cache key not found") - // ErrCacheUnsupportedOperation is returned when a method such as Forever - // or atomic operations is not supported by the cache backend. + // ErrCacheUnsupportedOperation is returned when a method such as + // atomic increment/decrement is not supported by the cache driver. ErrCacheUnsupportedOperation = errors.New("cache unsupported operation") ) -// Cache defines a generic cache contract inspired by Laravel's Cache Repository. -// It supports basic CRUD, atomic operations, and lazy-loading via Remember patterns. -type Cache interface { - // Get retrieves the value for the given key. - // Returns nil and an error if the key is missing or retrieval fails. - Get(ctx context.Context, key string) (any, error) +// CacheDriver defines the minimal contract that cache backends must +// implement. Drivers operate on raw bytes, leaving serialization to +// the [Cache] wrapper. A zero TTL means the entry should never expire. +type CacheDriver interface { + // Get retrieves the raw bytes for the given key. + // Returns [ErrCacheKeyNotFound] when the key is missing or expired. + Get(ctx context.Context, key string) ([]byte, error) - // Put stores a value for the given key with a TTL. - // Overwrites any existing value. - Put(ctx context.Context, key string, value any, ttl time.Duration) error + // Put stores raw bytes for the given key with a TTL. + // A zero TTL stores the entry without expiration. + Put(ctx context.Context, key string, value []byte, ttl time.Duration) error - // Delete removes the cached value for the given key. + // Delete removes the entry for the given key. // Does nothing if the key does not exist. Delete(ctx context.Context, key string) error - // Has returns true if the key exists in the cache and is not expired. + // Has returns true if the key exists and is not expired. Has(ctx context.Context, key string) (bool, error) +} + +// CacheCounter is an optional interface that cache drivers may +// implement to support atomic increment and decrement operations. +// When the driver does not implement this interface, the [Cache] +// wrapper returns [ErrCacheUnsupportedOperation]. +type CacheCounter interface { + // Increment atomically increases the integer value at key by + // the given delta. Returns the new value. + Increment(ctx context.Context, key string, delta int64) (int64, error) + + // Decrement atomically decreases the integer value at key by + // the given delta. Returns the new value. + Decrement(ctx context.Context, key string, delta int64) (int64, error) +} + +// Cache provides a type-safe caching layer over a [CacheDriver]. +// It handles JSON serialization and offers convenience methods such +// as Pull, Forever, Remember, and atomic counters. When generic +// methods become available in Go, the Get and Remember methods will +// be updated to return typed values directly. +type Cache struct { + driver CacheDriver +} + +// NewCache creates a new [Cache] that delegates storage to the given driver. +func NewCache(driver CacheDriver) *Cache { + return &Cache{driver: driver} +} + +// Driver returns the underlying [CacheDriver]. +func (cache *Cache) Driver() CacheDriver { + return cache.driver +} + +// Get retrieves the value for the given key and JSON-decodes it into +// the provided destination. Returns [ErrCacheKeyNotFound] when the +// key is missing. +func (cache *Cache) Get(ctx context.Context, key string, dest any) error { + raw, err := cache.driver.Get(ctx, key) + + if err != nil { + return err + } + + return json.Unmarshal(raw, dest) +} + +// Put JSON-encodes the value and stores it with the given TTL. +func (cache *Cache) Put(ctx context.Context, key string, value any, ttl time.Duration) error { + raw, err := json.Marshal(value) + + if err != nil { + return err + } + + return cache.driver.Put(ctx, key, raw, ttl) +} + +// Delete removes the cached value for the given key. +func (cache *Cache) Delete(ctx context.Context, key string) error { + return cache.driver.Delete(ctx, key) +} + +// Has returns true if the key exists in the cache and is not expired. +func (cache *Cache) Has(ctx context.Context, key string) (bool, error) { + return cache.driver.Has(ctx, key) +} + +// Pull retrieves and removes the value for the given key, decoding +// it into dest. Returns [ErrCacheKeyNotFound] when the key is missing. +func (cache *Cache) Pull(ctx context.Context, key string, dest any) error { + raw, err := cache.driver.Get(ctx, key) + + if err != nil { + return err + } - // Pull retrieves and removes the value for the given key. - // Returns nil and an error if the key is missing. - Pull(ctx context.Context, key string) (any, error) + if err := cache.driver.Delete(ctx, key); err != nil { + return err + } - // Forever stores a value permanently (no TTL). - // Only supported if the backend allows non-expiring keys. - Forever(ctx context.Context, key string, value any) error + return json.Unmarshal(raw, dest) +} + +// Forever stores a value permanently (zero TTL). +func (cache *Cache) Forever(ctx context.Context, key string, value any) error { + return cache.Put(ctx, key, value, 0) +} - // Increment atomically increases the integer value stored at key by the given - // amount. Returns the new value or an error if the operation fails. - Increment(ctx context.Context, key string, by int64) (int64, error) +// Increment atomically increases the integer value at key by the +// given delta. Returns [ErrCacheUnsupportedOperation] if the driver +// does not implement [CacheCounter]. +func (cache *Cache) Increment(ctx context.Context, key string, delta int64) (int64, error) { + counter, ok := cache.driver.(CacheCounter) - // Decrement atomically decreases the integer value stored at key by - // the given amount. Returns the new value or an error if the operation fails. - Decrement(ctx context.Context, key string, by int64) (int64, error) + if !ok { + return 0, ErrCacheUnsupportedOperation + } - // Remember attempts to get the value for the given key. - // If the key is missing, it calls the compute function, stores the result with the given TTL, and returns it. - Remember(ctx context.Context, key string, ttl time.Duration, compute func() (any, error)) (any, error) + return counter.Increment(ctx, key, delta) +} + +// Decrement atomically decreases the integer value at key by the +// given delta. Returns [ErrCacheUnsupportedOperation] if the driver +// does not implement [CacheCounter]. +func (cache *Cache) Decrement(ctx context.Context, key string, delta int64) (int64, error) { + counter, ok := cache.driver.(CacheCounter) + + if !ok { + return 0, ErrCacheUnsupportedOperation + } + + return counter.Decrement(ctx, key, delta) +} + +// Remember retrieves the cached value for the given key, decoding it +// into dest. If the key is not found, it calls compute, stores the +// result with the given TTL, and decodes it into dest. +func (cache *Cache) Remember(ctx context.Context, key string, ttl time.Duration, dest any, compute func() (any, error)) error { + raw, err := cache.driver.Get(ctx, key) + + if err == nil { + return json.Unmarshal(raw, dest) + } + + if !errors.Is(err, ErrCacheKeyNotFound) { + return err + } + + value, err := compute() + + if err != nil { + return err + } + + encoded, err := json.Marshal(value) + + if err != nil { + return err + } + + if err := cache.driver.Put(ctx, key, encoded, ttl); err != nil { + return err + } + + return json.Unmarshal(encoded, dest) +} - // RememberForever is like Remember but stores the computed result without TTL (permanently). - RememberForever(ctx context.Context, key string, compute func() (any, error)) (any, error) +// RememberForever is like [Cache.Remember] but stores the computed +// result without expiration. +func (cache *Cache) RememberForever(ctx context.Context, key string, dest any, compute func() (any, error)) error { + return cache.Remember(ctx, key, 0, dest, compute) } diff --git a/contract/database.go b/contract/database.go index 67feac5..1d707e2 100644 --- a/contract/database.go +++ b/contract/database.go @@ -15,56 +15,108 @@ var ( ErrDatabaseNestedTransaction = errors.New("nested transactions are not supported") ) -// Database defines a generic interface for interacting with a SQL-based datastore. -// It provides methods for executing queries, retrieving multiple or single records, -// and performing operations within a transaction context. -type Database interface { - // Close closes the database connection and releases any associated resources. - // It should be called when the database is no longer needed to prevent - // resource leaks. After calling Close, the Database instance should not - // be used for further operations. +// DatabaseDriver defines the interface for interacting with a SQL-based +// datastore. Implementations handle query execution, scanning, and +// transaction management. The [Database] wrapper provides type-safe +// convenience methods on top of this driver. +type DatabaseDriver interface { + // Close closes the database connection and releases associated resources. Close() error - // Ping verifies that the database connection is still alive and accessible. - // It sends a simple query to the database to test connectivity and returns - // an error if the connection is unavailable or the database is unreachable. - // This method is typically used for health checks and connection validation. - // Ping will also connect to the database if the connection was not established. + // Ping verifies that the database connection is still alive. Ping(ctx context.Context) error // Exec executes a SQL query that modifies data (e.g., INSERT, UPDATE, DELETE). - // It returns the number of rows affected. + // Returns the number of rows affected. Exec(ctx context.Context, query string, args ...any) (int64, error) - // ExecNamed executes a SQL query that modifies data using named parameters. + // ExecNamed executes a SQL query using named parameters. // The arg parameter should be a struct or map containing the named parameters. // Returns the number of rows affected. ExecNamed(ctx context.Context, query string, arg any) (int64, error) // Select executes a query and scans the results into dest. // Dest should be a pointer to a slice of structs (e.g., *[]User). - // Returns an error if the query fails or if scanning is unsuccessful. Select(ctx context.Context, query string, dest any, args ...any) error - // SelectNamed executes a query using named parameters and scans the results into dest. - // Dest should be a pointer to a slice of structs, and arg should contain the named parameters. - // Returns an error if the query fails or if scanning is unsuccessful. + // SelectNamed executes a query using named parameters and scans results into dest. + // Dest should be a pointer to a slice of structs. SelectNamed(ctx context.Context, query string, dest any, arg any) error - // Find executes a query expected to return a single row, - // and scans the result into dest. Dest should be a pointer to a struct. - // Returns an error if no row is found, multiple rows are returned, or scanning fails. + // Find executes a query expected to return a single row and scans + // the result into dest. Dest should be a pointer to a struct. Find(ctx context.Context, query string, dest any, args ...any) error - // FindNamed executes a query using named parameters expected to return a single row, - // and scans the result into dest. Dest should be a pointer to a struct, - // and arg should contain the named parameters. - // Returns an error if no row is found, multiple rows are returned, or scanning fails. + // FindNamed executes a query using named parameters expected to return + // a single row and scans the result into dest. FindNamed(ctx context.Context, query string, dest any, arg any) error - // WithTransaction executes the provided function fn within a database transaction. - // If fn returns an error, the transaction is rolled back. Otherwise, it is committed. - // The tx passed to fn implements the same Database interface and can be used - // for operations within the transaction scope. - WithTransaction(ctx context.Context, fn func(tx Database) error) error + // WithTransaction executes fn within a database transaction. If fn returns + // an error, the transaction is rolled back. Otherwise, it is committed. + WithTransaction(ctx context.Context, fn func(tx DatabaseDriver) error) error +} + +// Database provides a wrapper over a [DatabaseDriver] with convenience +// methods. When generic methods become available in Go, Find and Select +// will be updated to return typed values directly. +type Database struct { + driver DatabaseDriver +} + +// NewDatabase creates a new [Database] that delegates operations to the given driver. +func NewDatabase(driver DatabaseDriver) *Database { + return &Database{driver: driver} +} + +// Driver returns the underlying [DatabaseDriver]. +func (database *Database) Driver() DatabaseDriver { + return database.driver +} + +// Close closes the database connection and releases associated resources. +func (database *Database) Close() error { + return database.driver.Close() +} + +// Ping verifies that the database connection is still alive. +func (database *Database) Ping(ctx context.Context) error { + return database.driver.Ping(ctx) +} + +// Exec executes a SQL query that modifies data. Returns the number of rows affected. +func (database *Database) Exec(ctx context.Context, query string, args ...any) (int64, error) { + return database.driver.Exec(ctx, query, args...) +} + +// ExecNamed executes a SQL query using named parameters. Returns the number of rows affected. +func (database *Database) ExecNamed(ctx context.Context, query string, arg any) (int64, error) { + return database.driver.ExecNamed(ctx, query, arg) +} + +// Select executes a query and scans the results into dest. +// Dest should be a pointer to a slice of structs. +func (database *Database) Select(ctx context.Context, query string, dest any, args ...any) error { + return database.driver.Select(ctx, query, dest, args...) +} + +// SelectNamed executes a query using named parameters and scans results into dest. +func (database *Database) SelectNamed(ctx context.Context, query string, dest any, arg any) error { + return database.driver.SelectNamed(ctx, query, dest, arg) +} + +// Find executes a query expected to return a single row and scans the result into dest. +func (database *Database) Find(ctx context.Context, query string, dest any, args ...any) error { + return database.driver.Find(ctx, query, dest, args...) +} + +// FindNamed executes a query using named parameters expected to return a single row. +func (database *Database) FindNamed(ctx context.Context, query string, dest any, arg any) error { + return database.driver.FindNamed(ctx, query, dest, arg) +} + +// WithTransaction executes fn within a database transaction. +func (database *Database) WithTransaction(ctx context.Context, fn func(tx *Database) error) error { + return database.driver.WithTransaction(ctx, func(tx DatabaseDriver) error { + return fn(NewDatabase(tx)) + }) } diff --git a/contract/event.go b/contract/event.go index 8797dad..281f8f0 100644 --- a/contract/event.go +++ b/contract/event.go @@ -1,36 +1,76 @@ package contract -import "context" - -// EventPayload is a function that decodes an event's raw payload -// into the provided destination. The destination must be a pointer -// to the expected type. -type EventPayload = func(dest any) error +import ( + "context" + "encoding/json" +) // EventHandler is a callback function invoked when a subscribed -// event is received. It receives an [EventPayload] that can be -// used to decode the event data. -type EventHandler = func(payload EventPayload) +// event is received. It receives the raw JSON payload bytes. +type EventHandler = func(payload []byte) -// EventUnsubscribeFunc is a function returned by [Events.Subscribe] -// that cancels the subscription when called. It returns an error -// if the unsubscription fails. +// EventUnsubscribeFunc is a function returned by subscription +// that cancels the subscription when called. type EventUnsubscribeFunc = func() error -// Events defines the contract for a publish/subscribe event system. -// Implementations handle event routing between publishers and subscribers -// across different transport backends. -type Events interface { - // Publish sends the given payload to all subscribers of the named event. - // The payload is serialized by the implementation before delivery. - Publish(ctx context.Context, event string, payload any) error - - // Subscribe registers a handler for the named event and returns - // a function to cancel the subscription. The handler is called - // each time a matching event is received. +// EventDriver defines the contract for a publish/subscribe event +// system backend. Drivers handle raw byte delivery; the [Events] +// wrapper adds JSON serialization on top. +type EventDriver interface { + // Publish sends raw bytes to all subscribers of the named event. + Publish(ctx context.Context, event string, payload []byte) error + + // Subscribe registers a handler for the named event. The handler + // receives raw payload bytes. Returns a function to cancel the + // subscription. Subscribe(ctx context.Context, event string, handler EventHandler) (EventUnsubscribeFunc, error) - // Close shuts down the event system and releases any underlying - // connections or resources. + // Close shuts down the event system and releases resources. Close() error } + +// Events provides a type-safe event bus over an [EventDriver]. +// It handles JSON serialization of payloads and deserialization +// in subscriber callbacks. When generic methods become available +// in Go, Publish and Subscribe will accept typed values directly. +type Events struct { + driver EventDriver +} + +// NewEvents creates a new [Events] that delegates to the given driver. +func NewEvents(driver EventDriver) *Events { + return &Events{driver: driver} +} + +// Driver returns the underlying [EventDriver]. +func (events *Events) Driver() EventDriver { + return events.driver +} + +// Publish JSON-encodes the payload and sends it to all subscribers +// of the named event. +func (events *Events) Publish(ctx context.Context, event string, payload any) error { + encoded, err := json.Marshal(payload) + + if err != nil { + return err + } + + return events.driver.Publish(ctx, event, encoded) +} + +// Subscribe registers a handler for the named event. The handler +// receives a decode function that unmarshals the event payload into +// a destination pointer. Returns a function to cancel the subscription. +func (events *Events) Subscribe(ctx context.Context, event string, handler func(func(dest any) error)) (EventUnsubscribeFunc, error) { + return events.driver.Subscribe(ctx, event, func(payload []byte) { + handler(func(dest any) error { + return json.Unmarshal(payload, dest) + }) + }) +} + +// Close shuts down the event system and releases resources. +func (events *Events) Close() error { + return events.driver.Close() +} diff --git a/contract/mock/cache.go b/contract/mock/cache.go index 0b31a82..95cb8c1 100644 --- a/contract/mock/cache.go +++ b/contract/mock/cache.go @@ -10,13 +10,13 @@ import ( "time" ) -// NewCacheMock creates a new instance of CacheMock. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// NewCacheDriverMock creates a new instance of CacheDriverMock. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. // The first argument is typically a *testing.T value. -func NewCacheMock(t interface { +func NewCacheDriverMock(t interface { mock.TestingT Cleanup(func()) -}) *CacheMock { - mock := &CacheMock{} +}) *CacheDriverMock { + mock := &CacheDriverMock{} mock.Mock.Test(t) t.Cleanup(func() { mock.AssertExpectations(t) }) @@ -24,93 +24,21 @@ func NewCacheMock(t interface { return mock } -// CacheMock is an autogenerated mock type for the Cache type -type CacheMock struct { +// CacheDriverMock is an autogenerated mock type for the CacheDriver type +type CacheDriverMock struct { mock.Mock } -type CacheMock_Expecter struct { +type CacheDriverMock_Expecter struct { mock *mock.Mock } -func (_m *CacheMock) EXPECT() *CacheMock_Expecter { - return &CacheMock_Expecter{mock: &_m.Mock} +func (_m *CacheDriverMock) EXPECT() *CacheDriverMock_Expecter { + return &CacheDriverMock_Expecter{mock: &_m.Mock} } -// Decrement provides a mock function for the type CacheMock -func (_mock *CacheMock) Decrement(ctx context.Context, key string, by int64) (int64, error) { - ret := _mock.Called(ctx, key, by) - - if len(ret) == 0 { - panic("no return value specified for Decrement") - } - - var r0 int64 - var r1 error - if returnFunc, ok := ret.Get(0).(func(context.Context, string, int64) (int64, error)); ok { - return returnFunc(ctx, key, by) - } - if returnFunc, ok := ret.Get(0).(func(context.Context, string, int64) int64); ok { - r0 = returnFunc(ctx, key, by) - } else { - r0 = ret.Get(0).(int64) - } - if returnFunc, ok := ret.Get(1).(func(context.Context, string, int64) error); ok { - r1 = returnFunc(ctx, key, by) - } else { - r1 = ret.Error(1) - } - return r0, r1 -} - -// CacheMock_Decrement_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Decrement' -type CacheMock_Decrement_Call struct { - *mock.Call -} - -// Decrement is a helper method to define mock.On call -// - ctx context.Context -// - key string -// - by int64 -func (_e *CacheMock_Expecter) Decrement(ctx interface{}, key interface{}, by interface{}) *CacheMock_Decrement_Call { - return &CacheMock_Decrement_Call{Call: _e.mock.On("Decrement", ctx, key, by)} -} - -func (_c *CacheMock_Decrement_Call) Run(run func(ctx context.Context, key string, by int64)) *CacheMock_Decrement_Call { - _c.Call.Run(func(args mock.Arguments) { - var arg0 context.Context - if args[0] != nil { - arg0 = args[0].(context.Context) - } - var arg1 string - if args[1] != nil { - arg1 = args[1].(string) - } - var arg2 int64 - if args[2] != nil { - arg2 = args[2].(int64) - } - run( - arg0, - arg1, - arg2, - ) - }) - return _c -} - -func (_c *CacheMock_Decrement_Call) Return(n int64, err error) *CacheMock_Decrement_Call { - _c.Call.Return(n, err) - return _c -} - -func (_c *CacheMock_Decrement_Call) RunAndReturn(run func(ctx context.Context, key string, by int64) (int64, error)) *CacheMock_Decrement_Call { - _c.Call.Return(run) - return _c -} - -// Delete provides a mock function for the type CacheMock -func (_mock *CacheMock) Delete(ctx context.Context, key string) error { +// Delete provides a mock function for the type CacheDriverMock +func (_mock *CacheDriverMock) Delete(ctx context.Context, key string) error { ret := _mock.Called(ctx, key) if len(ret) == 0 { @@ -126,77 +54,19 @@ func (_mock *CacheMock) Delete(ctx context.Context, key string) error { return r0 } -// CacheMock_Delete_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Delete' -type CacheMock_Delete_Call struct { +// CacheDriverMock_Delete_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Delete' +type CacheDriverMock_Delete_Call struct { *mock.Call } // Delete is a helper method to define mock.On call // - ctx context.Context // - key string -func (_e *CacheMock_Expecter) Delete(ctx interface{}, key interface{}) *CacheMock_Delete_Call { - return &CacheMock_Delete_Call{Call: _e.mock.On("Delete", ctx, key)} -} - -func (_c *CacheMock_Delete_Call) Run(run func(ctx context.Context, key string)) *CacheMock_Delete_Call { - _c.Call.Run(func(args mock.Arguments) { - var arg0 context.Context - if args[0] != nil { - arg0 = args[0].(context.Context) - } - var arg1 string - if args[1] != nil { - arg1 = args[1].(string) - } - run( - arg0, - arg1, - ) - }) - return _c -} - -func (_c *CacheMock_Delete_Call) Return(err error) *CacheMock_Delete_Call { - _c.Call.Return(err) - return _c -} - -func (_c *CacheMock_Delete_Call) RunAndReturn(run func(ctx context.Context, key string) error) *CacheMock_Delete_Call { - _c.Call.Return(run) - return _c -} - -// Forever provides a mock function for the type CacheMock -func (_mock *CacheMock) Forever(ctx context.Context, key string, value any) error { - ret := _mock.Called(ctx, key, value) - - if len(ret) == 0 { - panic("no return value specified for Forever") - } - - var r0 error - if returnFunc, ok := ret.Get(0).(func(context.Context, string, any) error); ok { - r0 = returnFunc(ctx, key, value) - } else { - r0 = ret.Error(0) - } - return r0 -} - -// CacheMock_Forever_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Forever' -type CacheMock_Forever_Call struct { - *mock.Call -} - -// Forever is a helper method to define mock.On call -// - ctx context.Context -// - key string -// - value any -func (_e *CacheMock_Expecter) Forever(ctx interface{}, key interface{}, value interface{}) *CacheMock_Forever_Call { - return &CacheMock_Forever_Call{Call: _e.mock.On("Forever", ctx, key, value)} +func (_e *CacheDriverMock_Expecter) Delete(ctx interface{}, key interface{}) *CacheDriverMock_Delete_Call { + return &CacheDriverMock_Delete_Call{Call: _e.mock.On("Delete", ctx, key)} } -func (_c *CacheMock_Forever_Call) Run(run func(ctx context.Context, key string, value any)) *CacheMock_Forever_Call { +func (_c *CacheDriverMock_Delete_Call) Run(run func(ctx context.Context, key string)) *CacheDriverMock_Delete_Call { _c.Call.Run(func(args mock.Arguments) { var arg0 context.Context if args[0] != nil { @@ -206,47 +76,42 @@ func (_c *CacheMock_Forever_Call) Run(run func(ctx context.Context, key string, if args[1] != nil { arg1 = args[1].(string) } - var arg2 any - if args[2] != nil { - arg2 = args[2].(any) - } run( arg0, arg1, - arg2, ) }) return _c } -func (_c *CacheMock_Forever_Call) Return(err error) *CacheMock_Forever_Call { +func (_c *CacheDriverMock_Delete_Call) Return(err error) *CacheDriverMock_Delete_Call { _c.Call.Return(err) return _c } -func (_c *CacheMock_Forever_Call) RunAndReturn(run func(ctx context.Context, key string, value any) error) *CacheMock_Forever_Call { +func (_c *CacheDriverMock_Delete_Call) RunAndReturn(run func(ctx context.Context, key string) error) *CacheDriverMock_Delete_Call { _c.Call.Return(run) return _c } -// Get provides a mock function for the type CacheMock -func (_mock *CacheMock) Get(ctx context.Context, key string) (any, error) { +// Get provides a mock function for the type CacheDriverMock +func (_mock *CacheDriverMock) Get(ctx context.Context, key string) ([]byte, error) { ret := _mock.Called(ctx, key) if len(ret) == 0 { panic("no return value specified for Get") } - var r0 any + var r0 []byte var r1 error - if returnFunc, ok := ret.Get(0).(func(context.Context, string) (any, error)); ok { + if returnFunc, ok := ret.Get(0).(func(context.Context, string) ([]byte, error)); ok { return returnFunc(ctx, key) } - if returnFunc, ok := ret.Get(0).(func(context.Context, string) any); ok { + if returnFunc, ok := ret.Get(0).(func(context.Context, string) []byte); ok { r0 = returnFunc(ctx, key) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(any) + r0 = ret.Get(0).([]byte) } } if returnFunc, ok := ret.Get(1).(func(context.Context, string) error); ok { @@ -257,19 +122,19 @@ func (_mock *CacheMock) Get(ctx context.Context, key string) (any, error) { return r0, r1 } -// CacheMock_Get_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Get' -type CacheMock_Get_Call struct { +// CacheDriverMock_Get_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Get' +type CacheDriverMock_Get_Call struct { *mock.Call } // Get is a helper method to define mock.On call // - ctx context.Context // - key string -func (_e *CacheMock_Expecter) Get(ctx interface{}, key interface{}) *CacheMock_Get_Call { - return &CacheMock_Get_Call{Call: _e.mock.On("Get", ctx, key)} +func (_e *CacheDriverMock_Expecter) Get(ctx interface{}, key interface{}) *CacheDriverMock_Get_Call { + return &CacheDriverMock_Get_Call{Call: _e.mock.On("Get", ctx, key)} } -func (_c *CacheMock_Get_Call) Run(run func(ctx context.Context, key string)) *CacheMock_Get_Call { +func (_c *CacheDriverMock_Get_Call) Run(run func(ctx context.Context, key string)) *CacheDriverMock_Get_Call { _c.Call.Run(func(args mock.Arguments) { var arg0 context.Context if args[0] != nil { @@ -287,18 +152,18 @@ func (_c *CacheMock_Get_Call) Run(run func(ctx context.Context, key string)) *Ca return _c } -func (_c *CacheMock_Get_Call) Return(v any, err error) *CacheMock_Get_Call { - _c.Call.Return(v, err) +func (_c *CacheDriverMock_Get_Call) Return(bytes []byte, err error) *CacheDriverMock_Get_Call { + _c.Call.Return(bytes, err) return _c } -func (_c *CacheMock_Get_Call) RunAndReturn(run func(ctx context.Context, key string) (any, error)) *CacheMock_Get_Call { +func (_c *CacheDriverMock_Get_Call) RunAndReturn(run func(ctx context.Context, key string) ([]byte, error)) *CacheDriverMock_Get_Call { _c.Call.Return(run) return _c } -// Has provides a mock function for the type CacheMock -func (_mock *CacheMock) Has(ctx context.Context, key string) (bool, error) { +// Has provides a mock function for the type CacheDriverMock +func (_mock *CacheDriverMock) Has(ctx context.Context, key string) (bool, error) { ret := _mock.Called(ctx, key) if len(ret) == 0 { @@ -323,19 +188,19 @@ func (_mock *CacheMock) Has(ctx context.Context, key string) (bool, error) { return r0, r1 } -// CacheMock_Has_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Has' -type CacheMock_Has_Call struct { +// CacheDriverMock_Has_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Has' +type CacheDriverMock_Has_Call struct { *mock.Call } // Has is a helper method to define mock.On call // - ctx context.Context // - key string -func (_e *CacheMock_Expecter) Has(ctx interface{}, key interface{}) *CacheMock_Has_Call { - return &CacheMock_Has_Call{Call: _e.mock.On("Has", ctx, key)} +func (_e *CacheDriverMock_Expecter) Has(ctx interface{}, key interface{}) *CacheDriverMock_Has_Call { + return &CacheDriverMock_Has_Call{Call: _e.mock.On("Has", ctx, key)} } -func (_c *CacheMock_Has_Call) Run(run func(ctx context.Context, key string)) *CacheMock_Has_Call { +func (_c *CacheDriverMock_Has_Call) Run(run func(ctx context.Context, key string)) *CacheDriverMock_Has_Call { _c.Call.Run(func(args mock.Arguments) { var arg0 context.Context if args[0] != nil { @@ -353,158 +218,18 @@ func (_c *CacheMock_Has_Call) Run(run func(ctx context.Context, key string)) *Ca return _c } -func (_c *CacheMock_Has_Call) Return(b bool, err error) *CacheMock_Has_Call { +func (_c *CacheDriverMock_Has_Call) Return(b bool, err error) *CacheDriverMock_Has_Call { _c.Call.Return(b, err) return _c } -func (_c *CacheMock_Has_Call) RunAndReturn(run func(ctx context.Context, key string) (bool, error)) *CacheMock_Has_Call { - _c.Call.Return(run) - return _c -} - -// Increment provides a mock function for the type CacheMock -func (_mock *CacheMock) Increment(ctx context.Context, key string, by int64) (int64, error) { - ret := _mock.Called(ctx, key, by) - - if len(ret) == 0 { - panic("no return value specified for Increment") - } - - var r0 int64 - var r1 error - if returnFunc, ok := ret.Get(0).(func(context.Context, string, int64) (int64, error)); ok { - return returnFunc(ctx, key, by) - } - if returnFunc, ok := ret.Get(0).(func(context.Context, string, int64) int64); ok { - r0 = returnFunc(ctx, key, by) - } else { - r0 = ret.Get(0).(int64) - } - if returnFunc, ok := ret.Get(1).(func(context.Context, string, int64) error); ok { - r1 = returnFunc(ctx, key, by) - } else { - r1 = ret.Error(1) - } - return r0, r1 -} - -// CacheMock_Increment_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Increment' -type CacheMock_Increment_Call struct { - *mock.Call -} - -// Increment is a helper method to define mock.On call -// - ctx context.Context -// - key string -// - by int64 -func (_e *CacheMock_Expecter) Increment(ctx interface{}, key interface{}, by interface{}) *CacheMock_Increment_Call { - return &CacheMock_Increment_Call{Call: _e.mock.On("Increment", ctx, key, by)} -} - -func (_c *CacheMock_Increment_Call) Run(run func(ctx context.Context, key string, by int64)) *CacheMock_Increment_Call { - _c.Call.Run(func(args mock.Arguments) { - var arg0 context.Context - if args[0] != nil { - arg0 = args[0].(context.Context) - } - var arg1 string - if args[1] != nil { - arg1 = args[1].(string) - } - var arg2 int64 - if args[2] != nil { - arg2 = args[2].(int64) - } - run( - arg0, - arg1, - arg2, - ) - }) - return _c -} - -func (_c *CacheMock_Increment_Call) Return(n int64, err error) *CacheMock_Increment_Call { - _c.Call.Return(n, err) - return _c -} - -func (_c *CacheMock_Increment_Call) RunAndReturn(run func(ctx context.Context, key string, by int64) (int64, error)) *CacheMock_Increment_Call { - _c.Call.Return(run) - return _c -} - -// Pull provides a mock function for the type CacheMock -func (_mock *CacheMock) Pull(ctx context.Context, key string) (any, error) { - ret := _mock.Called(ctx, key) - - if len(ret) == 0 { - panic("no return value specified for Pull") - } - - var r0 any - var r1 error - if returnFunc, ok := ret.Get(0).(func(context.Context, string) (any, error)); ok { - return returnFunc(ctx, key) - } - if returnFunc, ok := ret.Get(0).(func(context.Context, string) any); ok { - r0 = returnFunc(ctx, key) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(any) - } - } - if returnFunc, ok := ret.Get(1).(func(context.Context, string) error); ok { - r1 = returnFunc(ctx, key) - } else { - r1 = ret.Error(1) - } - return r0, r1 -} - -// CacheMock_Pull_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Pull' -type CacheMock_Pull_Call struct { - *mock.Call -} - -// Pull is a helper method to define mock.On call -// - ctx context.Context -// - key string -func (_e *CacheMock_Expecter) Pull(ctx interface{}, key interface{}) *CacheMock_Pull_Call { - return &CacheMock_Pull_Call{Call: _e.mock.On("Pull", ctx, key)} -} - -func (_c *CacheMock_Pull_Call) Run(run func(ctx context.Context, key string)) *CacheMock_Pull_Call { - _c.Call.Run(func(args mock.Arguments) { - var arg0 context.Context - if args[0] != nil { - arg0 = args[0].(context.Context) - } - var arg1 string - if args[1] != nil { - arg1 = args[1].(string) - } - run( - arg0, - arg1, - ) - }) - return _c -} - -func (_c *CacheMock_Pull_Call) Return(v any, err error) *CacheMock_Pull_Call { - _c.Call.Return(v, err) - return _c -} - -func (_c *CacheMock_Pull_Call) RunAndReturn(run func(ctx context.Context, key string) (any, error)) *CacheMock_Pull_Call { +func (_c *CacheDriverMock_Has_Call) RunAndReturn(run func(ctx context.Context, key string) (bool, error)) *CacheDriverMock_Has_Call { _c.Call.Return(run) return _c } -// Put provides a mock function for the type CacheMock -func (_mock *CacheMock) Put(ctx context.Context, key string, value any, ttl time.Duration) error { +// Put provides a mock function for the type CacheDriverMock +func (_mock *CacheDriverMock) Put(ctx context.Context, key string, value []byte, ttl time.Duration) error { ret := _mock.Called(ctx, key, value, ttl) if len(ret) == 0 { @@ -512,7 +237,7 @@ func (_mock *CacheMock) Put(ctx context.Context, key string, value any, ttl time } var r0 error - if returnFunc, ok := ret.Get(0).(func(context.Context, string, any, time.Duration) error); ok { + if returnFunc, ok := ret.Get(0).(func(context.Context, string, []byte, time.Duration) error); ok { r0 = returnFunc(ctx, key, value, ttl) } else { r0 = ret.Error(0) @@ -520,21 +245,21 @@ func (_mock *CacheMock) Put(ctx context.Context, key string, value any, ttl time return r0 } -// CacheMock_Put_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Put' -type CacheMock_Put_Call struct { +// CacheDriverMock_Put_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Put' +type CacheDriverMock_Put_Call struct { *mock.Call } // Put is a helper method to define mock.On call // - ctx context.Context // - key string -// - value any +// - value []byte // - ttl time.Duration -func (_e *CacheMock_Expecter) Put(ctx interface{}, key interface{}, value interface{}, ttl interface{}) *CacheMock_Put_Call { - return &CacheMock_Put_Call{Call: _e.mock.On("Put", ctx, key, value, ttl)} +func (_e *CacheDriverMock_Expecter) Put(ctx interface{}, key interface{}, value interface{}, ttl interface{}) *CacheDriverMock_Put_Call { + return &CacheDriverMock_Put_Call{Call: _e.mock.On("Put", ctx, key, value, ttl)} } -func (_c *CacheMock_Put_Call) Run(run func(ctx context.Context, key string, value any, ttl time.Duration)) *CacheMock_Put_Call { +func (_c *CacheDriverMock_Put_Call) Run(run func(ctx context.Context, key string, value []byte, ttl time.Duration)) *CacheDriverMock_Put_Call { _c.Call.Run(func(args mock.Arguments) { var arg0 context.Context if args[0] != nil { @@ -544,9 +269,9 @@ func (_c *CacheMock_Put_Call) Run(run func(ctx context.Context, key string, valu if args[1] != nil { arg1 = args[1].(string) } - var arg2 any + var arg2 []byte if args[2] != nil { - arg2 = args[2].(any) + arg2 = args[2].([]byte) } var arg3 time.Duration if args[3] != nil { @@ -562,59 +287,83 @@ func (_c *CacheMock_Put_Call) Run(run func(ctx context.Context, key string, valu return _c } -func (_c *CacheMock_Put_Call) Return(err error) *CacheMock_Put_Call { +func (_c *CacheDriverMock_Put_Call) Return(err error) *CacheDriverMock_Put_Call { _c.Call.Return(err) return _c } -func (_c *CacheMock_Put_Call) RunAndReturn(run func(ctx context.Context, key string, value any, ttl time.Duration) error) *CacheMock_Put_Call { +func (_c *CacheDriverMock_Put_Call) RunAndReturn(run func(ctx context.Context, key string, value []byte, ttl time.Duration) error) *CacheDriverMock_Put_Call { _c.Call.Return(run) return _c } -// Remember provides a mock function for the type CacheMock -func (_mock *CacheMock) Remember(ctx context.Context, key string, ttl time.Duration, compute func() (any, error)) (any, error) { - ret := _mock.Called(ctx, key, ttl, compute) +// NewCacheCounterMock creates a new instance of CacheCounterMock. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewCacheCounterMock(t interface { + mock.TestingT + Cleanup(func()) +}) *CacheCounterMock { + mock := &CacheCounterMock{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} + +// CacheCounterMock is an autogenerated mock type for the CacheCounter type +type CacheCounterMock struct { + mock.Mock +} + +type CacheCounterMock_Expecter struct { + mock *mock.Mock +} + +func (_m *CacheCounterMock) EXPECT() *CacheCounterMock_Expecter { + return &CacheCounterMock_Expecter{mock: &_m.Mock} +} + +// Decrement provides a mock function for the type CacheCounterMock +func (_mock *CacheCounterMock) Decrement(ctx context.Context, key string, delta int64) (int64, error) { + ret := _mock.Called(ctx, key, delta) if len(ret) == 0 { - panic("no return value specified for Remember") + panic("no return value specified for Decrement") } - var r0 any + var r0 int64 var r1 error - if returnFunc, ok := ret.Get(0).(func(context.Context, string, time.Duration, func() (any, error)) (any, error)); ok { - return returnFunc(ctx, key, ttl, compute) + if returnFunc, ok := ret.Get(0).(func(context.Context, string, int64) (int64, error)); ok { + return returnFunc(ctx, key, delta) } - if returnFunc, ok := ret.Get(0).(func(context.Context, string, time.Duration, func() (any, error)) any); ok { - r0 = returnFunc(ctx, key, ttl, compute) + if returnFunc, ok := ret.Get(0).(func(context.Context, string, int64) int64); ok { + r0 = returnFunc(ctx, key, delta) } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(any) - } + r0 = ret.Get(0).(int64) } - if returnFunc, ok := ret.Get(1).(func(context.Context, string, time.Duration, func() (any, error)) error); ok { - r1 = returnFunc(ctx, key, ttl, compute) + if returnFunc, ok := ret.Get(1).(func(context.Context, string, int64) error); ok { + r1 = returnFunc(ctx, key, delta) } else { r1 = ret.Error(1) } return r0, r1 } -// CacheMock_Remember_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Remember' -type CacheMock_Remember_Call struct { +// CacheCounterMock_Decrement_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Decrement' +type CacheCounterMock_Decrement_Call struct { *mock.Call } -// Remember is a helper method to define mock.On call +// Decrement is a helper method to define mock.On call // - ctx context.Context // - key string -// - ttl time.Duration -// - compute func() (any, error) -func (_e *CacheMock_Expecter) Remember(ctx interface{}, key interface{}, ttl interface{}, compute interface{}) *CacheMock_Remember_Call { - return &CacheMock_Remember_Call{Call: _e.mock.On("Remember", ctx, key, ttl, compute)} +// - delta int64 +func (_e *CacheCounterMock_Expecter) Decrement(ctx interface{}, key interface{}, delta interface{}) *CacheCounterMock_Decrement_Call { + return &CacheCounterMock_Decrement_Call{Call: _e.mock.On("Decrement", ctx, key, delta)} } -func (_c *CacheMock_Remember_Call) Run(run func(ctx context.Context, key string, ttl time.Duration, compute func() (any, error))) *CacheMock_Remember_Call { +func (_c *CacheCounterMock_Decrement_Call) Run(run func(ctx context.Context, key string, delta int64)) *CacheCounterMock_Decrement_Call { _c.Call.Run(func(args mock.Arguments) { var arg0 context.Context if args[0] != nil { @@ -624,76 +373,69 @@ func (_c *CacheMock_Remember_Call) Run(run func(ctx context.Context, key string, if args[1] != nil { arg1 = args[1].(string) } - var arg2 time.Duration + var arg2 int64 if args[2] != nil { - arg2 = args[2].(time.Duration) - } - var arg3 func() (any, error) - if args[3] != nil { - arg3 = args[3].(func() (any, error)) + arg2 = args[2].(int64) } run( arg0, arg1, arg2, - arg3, ) }) return _c } -func (_c *CacheMock_Remember_Call) Return(v any, err error) *CacheMock_Remember_Call { - _c.Call.Return(v, err) +func (_c *CacheCounterMock_Decrement_Call) Return(n int64, err error) *CacheCounterMock_Decrement_Call { + _c.Call.Return(n, err) return _c } -func (_c *CacheMock_Remember_Call) RunAndReturn(run func(ctx context.Context, key string, ttl time.Duration, compute func() (any, error)) (any, error)) *CacheMock_Remember_Call { +func (_c *CacheCounterMock_Decrement_Call) RunAndReturn(run func(ctx context.Context, key string, delta int64) (int64, error)) *CacheCounterMock_Decrement_Call { _c.Call.Return(run) return _c } -// RememberForever provides a mock function for the type CacheMock -func (_mock *CacheMock) RememberForever(ctx context.Context, key string, compute func() (any, error)) (any, error) { - ret := _mock.Called(ctx, key, compute) +// Increment provides a mock function for the type CacheCounterMock +func (_mock *CacheCounterMock) Increment(ctx context.Context, key string, delta int64) (int64, error) { + ret := _mock.Called(ctx, key, delta) if len(ret) == 0 { - panic("no return value specified for RememberForever") + panic("no return value specified for Increment") } - var r0 any + var r0 int64 var r1 error - if returnFunc, ok := ret.Get(0).(func(context.Context, string, func() (any, error)) (any, error)); ok { - return returnFunc(ctx, key, compute) + if returnFunc, ok := ret.Get(0).(func(context.Context, string, int64) (int64, error)); ok { + return returnFunc(ctx, key, delta) } - if returnFunc, ok := ret.Get(0).(func(context.Context, string, func() (any, error)) any); ok { - r0 = returnFunc(ctx, key, compute) + if returnFunc, ok := ret.Get(0).(func(context.Context, string, int64) int64); ok { + r0 = returnFunc(ctx, key, delta) } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(any) - } + r0 = ret.Get(0).(int64) } - if returnFunc, ok := ret.Get(1).(func(context.Context, string, func() (any, error)) error); ok { - r1 = returnFunc(ctx, key, compute) + if returnFunc, ok := ret.Get(1).(func(context.Context, string, int64) error); ok { + r1 = returnFunc(ctx, key, delta) } else { r1 = ret.Error(1) } return r0, r1 } -// CacheMock_RememberForever_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RememberForever' -type CacheMock_RememberForever_Call struct { +// CacheCounterMock_Increment_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Increment' +type CacheCounterMock_Increment_Call struct { *mock.Call } -// RememberForever is a helper method to define mock.On call +// Increment is a helper method to define mock.On call // - ctx context.Context // - key string -// - compute func() (any, error) -func (_e *CacheMock_Expecter) RememberForever(ctx interface{}, key interface{}, compute interface{}) *CacheMock_RememberForever_Call { - return &CacheMock_RememberForever_Call{Call: _e.mock.On("RememberForever", ctx, key, compute)} +// - delta int64 +func (_e *CacheCounterMock_Expecter) Increment(ctx interface{}, key interface{}, delta interface{}) *CacheCounterMock_Increment_Call { + return &CacheCounterMock_Increment_Call{Call: _e.mock.On("Increment", ctx, key, delta)} } -func (_c *CacheMock_RememberForever_Call) Run(run func(ctx context.Context, key string, compute func() (any, error))) *CacheMock_RememberForever_Call { +func (_c *CacheCounterMock_Increment_Call) Run(run func(ctx context.Context, key string, delta int64)) *CacheCounterMock_Increment_Call { _c.Call.Run(func(args mock.Arguments) { var arg0 context.Context if args[0] != nil { @@ -703,9 +445,9 @@ func (_c *CacheMock_RememberForever_Call) Run(run func(ctx context.Context, key if args[1] != nil { arg1 = args[1].(string) } - var arg2 func() (any, error) + var arg2 int64 if args[2] != nil { - arg2 = args[2].(func() (any, error)) + arg2 = args[2].(int64) } run( arg0, @@ -716,12 +458,12 @@ func (_c *CacheMock_RememberForever_Call) Run(run func(ctx context.Context, key return _c } -func (_c *CacheMock_RememberForever_Call) Return(v any, err error) *CacheMock_RememberForever_Call { - _c.Call.Return(v, err) +func (_c *CacheCounterMock_Increment_Call) Return(n int64, err error) *CacheCounterMock_Increment_Call { + _c.Call.Return(n, err) return _c } -func (_c *CacheMock_RememberForever_Call) RunAndReturn(run func(ctx context.Context, key string, compute func() (any, error)) (any, error)) *CacheMock_RememberForever_Call { +func (_c *CacheCounterMock_Increment_Call) RunAndReturn(run func(ctx context.Context, key string, delta int64) (int64, error)) *CacheCounterMock_Increment_Call { _c.Call.Return(run) return _c } diff --git a/contract/mock/database.go b/contract/mock/database.go index 5fe7ac8..5f8ca55 100644 --- a/contract/mock/database.go +++ b/contract/mock/database.go @@ -10,13 +10,13 @@ import ( "github.com/studiolambda/cosmos/contract" ) -// NewDatabaseMock creates a new instance of DatabaseMock. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// NewDatabaseDriverMock creates a new instance of DatabaseDriverMock. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. // The first argument is typically a *testing.T value. -func NewDatabaseMock(t interface { +func NewDatabaseDriverMock(t interface { mock.TestingT Cleanup(func()) -}) *DatabaseMock { - mock := &DatabaseMock{} +}) *DatabaseDriverMock { + mock := &DatabaseDriverMock{} mock.Mock.Test(t) t.Cleanup(func() { mock.AssertExpectations(t) }) @@ -24,21 +24,21 @@ func NewDatabaseMock(t interface { return mock } -// DatabaseMock is an autogenerated mock type for the Database type -type DatabaseMock struct { +// DatabaseDriverMock is an autogenerated mock type for the DatabaseDriver type +type DatabaseDriverMock struct { mock.Mock } -type DatabaseMock_Expecter struct { +type DatabaseDriverMock_Expecter struct { mock *mock.Mock } -func (_m *DatabaseMock) EXPECT() *DatabaseMock_Expecter { - return &DatabaseMock_Expecter{mock: &_m.Mock} +func (_m *DatabaseDriverMock) EXPECT() *DatabaseDriverMock_Expecter { + return &DatabaseDriverMock_Expecter{mock: &_m.Mock} } -// Close provides a mock function for the type DatabaseMock -func (_mock *DatabaseMock) Close() error { +// Close provides a mock function for the type DatabaseDriverMock +func (_mock *DatabaseDriverMock) Close() error { ret := _mock.Called() if len(ret) == 0 { @@ -54,35 +54,35 @@ func (_mock *DatabaseMock) Close() error { return r0 } -// DatabaseMock_Close_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Close' -type DatabaseMock_Close_Call struct { +// DatabaseDriverMock_Close_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Close' +type DatabaseDriverMock_Close_Call struct { *mock.Call } // Close is a helper method to define mock.On call -func (_e *DatabaseMock_Expecter) Close() *DatabaseMock_Close_Call { - return &DatabaseMock_Close_Call{Call: _e.mock.On("Close")} +func (_e *DatabaseDriverMock_Expecter) Close() *DatabaseDriverMock_Close_Call { + return &DatabaseDriverMock_Close_Call{Call: _e.mock.On("Close")} } -func (_c *DatabaseMock_Close_Call) Run(run func()) *DatabaseMock_Close_Call { +func (_c *DatabaseDriverMock_Close_Call) Run(run func()) *DatabaseDriverMock_Close_Call { _c.Call.Run(func(args mock.Arguments) { run() }) return _c } -func (_c *DatabaseMock_Close_Call) Return(err error) *DatabaseMock_Close_Call { +func (_c *DatabaseDriverMock_Close_Call) Return(err error) *DatabaseDriverMock_Close_Call { _c.Call.Return(err) return _c } -func (_c *DatabaseMock_Close_Call) RunAndReturn(run func() error) *DatabaseMock_Close_Call { +func (_c *DatabaseDriverMock_Close_Call) RunAndReturn(run func() error) *DatabaseDriverMock_Close_Call { _c.Call.Return(run) return _c } -// Exec provides a mock function for the type DatabaseMock -func (_mock *DatabaseMock) Exec(ctx context.Context, query string, args ...any) (int64, error) { +// Exec provides a mock function for the type DatabaseDriverMock +func (_mock *DatabaseDriverMock) Exec(ctx context.Context, query string, args ...any) (int64, error) { var tmpRet mock.Arguments if len(args) > 0 { tmpRet = _mock.Called(ctx, query, args) @@ -113,8 +113,8 @@ func (_mock *DatabaseMock) Exec(ctx context.Context, query string, args ...any) return r0, r1 } -// DatabaseMock_Exec_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Exec' -type DatabaseMock_Exec_Call struct { +// DatabaseDriverMock_Exec_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Exec' +type DatabaseDriverMock_Exec_Call struct { *mock.Call } @@ -122,12 +122,12 @@ type DatabaseMock_Exec_Call struct { // - ctx context.Context // - query string // - args ...any -func (_e *DatabaseMock_Expecter) Exec(ctx interface{}, query interface{}, args ...interface{}) *DatabaseMock_Exec_Call { - return &DatabaseMock_Exec_Call{Call: _e.mock.On("Exec", +func (_e *DatabaseDriverMock_Expecter) Exec(ctx interface{}, query interface{}, args ...interface{}) *DatabaseDriverMock_Exec_Call { + return &DatabaseDriverMock_Exec_Call{Call: _e.mock.On("Exec", append([]interface{}{ctx, query}, args...)...)} } -func (_c *DatabaseMock_Exec_Call) Run(run func(ctx context.Context, query string, args ...any)) *DatabaseMock_Exec_Call { +func (_c *DatabaseDriverMock_Exec_Call) Run(run func(ctx context.Context, query string, args ...any)) *DatabaseDriverMock_Exec_Call { _c.Call.Run(func(args mock.Arguments) { var arg0 context.Context if args[0] != nil { @@ -152,18 +152,18 @@ func (_c *DatabaseMock_Exec_Call) Run(run func(ctx context.Context, query string return _c } -func (_c *DatabaseMock_Exec_Call) Return(n int64, err error) *DatabaseMock_Exec_Call { +func (_c *DatabaseDriverMock_Exec_Call) Return(n int64, err error) *DatabaseDriverMock_Exec_Call { _c.Call.Return(n, err) return _c } -func (_c *DatabaseMock_Exec_Call) RunAndReturn(run func(ctx context.Context, query string, args ...any) (int64, error)) *DatabaseMock_Exec_Call { +func (_c *DatabaseDriverMock_Exec_Call) RunAndReturn(run func(ctx context.Context, query string, args ...any) (int64, error)) *DatabaseDriverMock_Exec_Call { _c.Call.Return(run) return _c } -// ExecNamed provides a mock function for the type DatabaseMock -func (_mock *DatabaseMock) ExecNamed(ctx context.Context, query string, arg any) (int64, error) { +// ExecNamed provides a mock function for the type DatabaseDriverMock +func (_mock *DatabaseDriverMock) ExecNamed(ctx context.Context, query string, arg any) (int64, error) { ret := _mock.Called(ctx, query, arg) if len(ret) == 0 { @@ -188,8 +188,8 @@ func (_mock *DatabaseMock) ExecNamed(ctx context.Context, query string, arg any) return r0, r1 } -// DatabaseMock_ExecNamed_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ExecNamed' -type DatabaseMock_ExecNamed_Call struct { +// DatabaseDriverMock_ExecNamed_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ExecNamed' +type DatabaseDriverMock_ExecNamed_Call struct { *mock.Call } @@ -197,11 +197,11 @@ type DatabaseMock_ExecNamed_Call struct { // - ctx context.Context // - query string // - arg any -func (_e *DatabaseMock_Expecter) ExecNamed(ctx interface{}, query interface{}, arg interface{}) *DatabaseMock_ExecNamed_Call { - return &DatabaseMock_ExecNamed_Call{Call: _e.mock.On("ExecNamed", ctx, query, arg)} +func (_e *DatabaseDriverMock_Expecter) ExecNamed(ctx interface{}, query interface{}, arg interface{}) *DatabaseDriverMock_ExecNamed_Call { + return &DatabaseDriverMock_ExecNamed_Call{Call: _e.mock.On("ExecNamed", ctx, query, arg)} } -func (_c *DatabaseMock_ExecNamed_Call) Run(run func(ctx context.Context, query string, arg any)) *DatabaseMock_ExecNamed_Call { +func (_c *DatabaseDriverMock_ExecNamed_Call) Run(run func(ctx context.Context, query string, arg any)) *DatabaseDriverMock_ExecNamed_Call { _c.Call.Run(func(args mock.Arguments) { var arg0 context.Context if args[0] != nil { @@ -224,18 +224,18 @@ func (_c *DatabaseMock_ExecNamed_Call) Run(run func(ctx context.Context, query s return _c } -func (_c *DatabaseMock_ExecNamed_Call) Return(n int64, err error) *DatabaseMock_ExecNamed_Call { +func (_c *DatabaseDriverMock_ExecNamed_Call) Return(n int64, err error) *DatabaseDriverMock_ExecNamed_Call { _c.Call.Return(n, err) return _c } -func (_c *DatabaseMock_ExecNamed_Call) RunAndReturn(run func(ctx context.Context, query string, arg any) (int64, error)) *DatabaseMock_ExecNamed_Call { +func (_c *DatabaseDriverMock_ExecNamed_Call) RunAndReturn(run func(ctx context.Context, query string, arg any) (int64, error)) *DatabaseDriverMock_ExecNamed_Call { _c.Call.Return(run) return _c } -// Find provides a mock function for the type DatabaseMock -func (_mock *DatabaseMock) Find(ctx context.Context, query string, dest any, args ...any) error { +// Find provides a mock function for the type DatabaseDriverMock +func (_mock *DatabaseDriverMock) Find(ctx context.Context, query string, dest any, args ...any) error { var tmpRet mock.Arguments if len(args) > 0 { tmpRet = _mock.Called(ctx, query, dest, args) @@ -257,8 +257,8 @@ func (_mock *DatabaseMock) Find(ctx context.Context, query string, dest any, arg return r0 } -// DatabaseMock_Find_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Find' -type DatabaseMock_Find_Call struct { +// DatabaseDriverMock_Find_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Find' +type DatabaseDriverMock_Find_Call struct { *mock.Call } @@ -267,12 +267,12 @@ type DatabaseMock_Find_Call struct { // - query string // - dest any // - args ...any -func (_e *DatabaseMock_Expecter) Find(ctx interface{}, query interface{}, dest interface{}, args ...interface{}) *DatabaseMock_Find_Call { - return &DatabaseMock_Find_Call{Call: _e.mock.On("Find", +func (_e *DatabaseDriverMock_Expecter) Find(ctx interface{}, query interface{}, dest interface{}, args ...interface{}) *DatabaseDriverMock_Find_Call { + return &DatabaseDriverMock_Find_Call{Call: _e.mock.On("Find", append([]interface{}{ctx, query, dest}, args...)...)} } -func (_c *DatabaseMock_Find_Call) Run(run func(ctx context.Context, query string, dest any, args ...any)) *DatabaseMock_Find_Call { +func (_c *DatabaseDriverMock_Find_Call) Run(run func(ctx context.Context, query string, dest any, args ...any)) *DatabaseDriverMock_Find_Call { _c.Call.Run(func(args mock.Arguments) { var arg0 context.Context if args[0] != nil { @@ -302,18 +302,18 @@ func (_c *DatabaseMock_Find_Call) Run(run func(ctx context.Context, query string return _c } -func (_c *DatabaseMock_Find_Call) Return(err error) *DatabaseMock_Find_Call { +func (_c *DatabaseDriverMock_Find_Call) Return(err error) *DatabaseDriverMock_Find_Call { _c.Call.Return(err) return _c } -func (_c *DatabaseMock_Find_Call) RunAndReturn(run func(ctx context.Context, query string, dest any, args ...any) error) *DatabaseMock_Find_Call { +func (_c *DatabaseDriverMock_Find_Call) RunAndReturn(run func(ctx context.Context, query string, dest any, args ...any) error) *DatabaseDriverMock_Find_Call { _c.Call.Return(run) return _c } -// FindNamed provides a mock function for the type DatabaseMock -func (_mock *DatabaseMock) FindNamed(ctx context.Context, query string, dest any, arg any) error { +// FindNamed provides a mock function for the type DatabaseDriverMock +func (_mock *DatabaseDriverMock) FindNamed(ctx context.Context, query string, dest any, arg any) error { ret := _mock.Called(ctx, query, dest, arg) if len(ret) == 0 { @@ -329,8 +329,8 @@ func (_mock *DatabaseMock) FindNamed(ctx context.Context, query string, dest any return r0 } -// DatabaseMock_FindNamed_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FindNamed' -type DatabaseMock_FindNamed_Call struct { +// DatabaseDriverMock_FindNamed_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FindNamed' +type DatabaseDriverMock_FindNamed_Call struct { *mock.Call } @@ -339,11 +339,11 @@ type DatabaseMock_FindNamed_Call struct { // - query string // - dest any // - arg any -func (_e *DatabaseMock_Expecter) FindNamed(ctx interface{}, query interface{}, dest interface{}, arg interface{}) *DatabaseMock_FindNamed_Call { - return &DatabaseMock_FindNamed_Call{Call: _e.mock.On("FindNamed", ctx, query, dest, arg)} +func (_e *DatabaseDriverMock_Expecter) FindNamed(ctx interface{}, query interface{}, dest interface{}, arg interface{}) *DatabaseDriverMock_FindNamed_Call { + return &DatabaseDriverMock_FindNamed_Call{Call: _e.mock.On("FindNamed", ctx, query, dest, arg)} } -func (_c *DatabaseMock_FindNamed_Call) Run(run func(ctx context.Context, query string, dest any, arg any)) *DatabaseMock_FindNamed_Call { +func (_c *DatabaseDriverMock_FindNamed_Call) Run(run func(ctx context.Context, query string, dest any, arg any)) *DatabaseDriverMock_FindNamed_Call { _c.Call.Run(func(args mock.Arguments) { var arg0 context.Context if args[0] != nil { @@ -371,18 +371,18 @@ func (_c *DatabaseMock_FindNamed_Call) Run(run func(ctx context.Context, query s return _c } -func (_c *DatabaseMock_FindNamed_Call) Return(err error) *DatabaseMock_FindNamed_Call { +func (_c *DatabaseDriverMock_FindNamed_Call) Return(err error) *DatabaseDriverMock_FindNamed_Call { _c.Call.Return(err) return _c } -func (_c *DatabaseMock_FindNamed_Call) RunAndReturn(run func(ctx context.Context, query string, dest any, arg any) error) *DatabaseMock_FindNamed_Call { +func (_c *DatabaseDriverMock_FindNamed_Call) RunAndReturn(run func(ctx context.Context, query string, dest any, arg any) error) *DatabaseDriverMock_FindNamed_Call { _c.Call.Return(run) return _c } -// Ping provides a mock function for the type DatabaseMock -func (_mock *DatabaseMock) Ping(ctx context.Context) error { +// Ping provides a mock function for the type DatabaseDriverMock +func (_mock *DatabaseDriverMock) Ping(ctx context.Context) error { ret := _mock.Called(ctx) if len(ret) == 0 { @@ -398,18 +398,18 @@ func (_mock *DatabaseMock) Ping(ctx context.Context) error { return r0 } -// DatabaseMock_Ping_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Ping' -type DatabaseMock_Ping_Call struct { +// DatabaseDriverMock_Ping_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Ping' +type DatabaseDriverMock_Ping_Call struct { *mock.Call } // Ping is a helper method to define mock.On call // - ctx context.Context -func (_e *DatabaseMock_Expecter) Ping(ctx interface{}) *DatabaseMock_Ping_Call { - return &DatabaseMock_Ping_Call{Call: _e.mock.On("Ping", ctx)} +func (_e *DatabaseDriverMock_Expecter) Ping(ctx interface{}) *DatabaseDriverMock_Ping_Call { + return &DatabaseDriverMock_Ping_Call{Call: _e.mock.On("Ping", ctx)} } -func (_c *DatabaseMock_Ping_Call) Run(run func(ctx context.Context)) *DatabaseMock_Ping_Call { +func (_c *DatabaseDriverMock_Ping_Call) Run(run func(ctx context.Context)) *DatabaseDriverMock_Ping_Call { _c.Call.Run(func(args mock.Arguments) { var arg0 context.Context if args[0] != nil { @@ -422,18 +422,18 @@ func (_c *DatabaseMock_Ping_Call) Run(run func(ctx context.Context)) *DatabaseMo return _c } -func (_c *DatabaseMock_Ping_Call) Return(err error) *DatabaseMock_Ping_Call { +func (_c *DatabaseDriverMock_Ping_Call) Return(err error) *DatabaseDriverMock_Ping_Call { _c.Call.Return(err) return _c } -func (_c *DatabaseMock_Ping_Call) RunAndReturn(run func(ctx context.Context) error) *DatabaseMock_Ping_Call { +func (_c *DatabaseDriverMock_Ping_Call) RunAndReturn(run func(ctx context.Context) error) *DatabaseDriverMock_Ping_Call { _c.Call.Return(run) return _c } -// Select provides a mock function for the type DatabaseMock -func (_mock *DatabaseMock) Select(ctx context.Context, query string, dest any, args ...any) error { +// Select provides a mock function for the type DatabaseDriverMock +func (_mock *DatabaseDriverMock) Select(ctx context.Context, query string, dest any, args ...any) error { var tmpRet mock.Arguments if len(args) > 0 { tmpRet = _mock.Called(ctx, query, dest, args) @@ -455,8 +455,8 @@ func (_mock *DatabaseMock) Select(ctx context.Context, query string, dest any, a return r0 } -// DatabaseMock_Select_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Select' -type DatabaseMock_Select_Call struct { +// DatabaseDriverMock_Select_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Select' +type DatabaseDriverMock_Select_Call struct { *mock.Call } @@ -465,12 +465,12 @@ type DatabaseMock_Select_Call struct { // - query string // - dest any // - args ...any -func (_e *DatabaseMock_Expecter) Select(ctx interface{}, query interface{}, dest interface{}, args ...interface{}) *DatabaseMock_Select_Call { - return &DatabaseMock_Select_Call{Call: _e.mock.On("Select", +func (_e *DatabaseDriverMock_Expecter) Select(ctx interface{}, query interface{}, dest interface{}, args ...interface{}) *DatabaseDriverMock_Select_Call { + return &DatabaseDriverMock_Select_Call{Call: _e.mock.On("Select", append([]interface{}{ctx, query, dest}, args...)...)} } -func (_c *DatabaseMock_Select_Call) Run(run func(ctx context.Context, query string, dest any, args ...any)) *DatabaseMock_Select_Call { +func (_c *DatabaseDriverMock_Select_Call) Run(run func(ctx context.Context, query string, dest any, args ...any)) *DatabaseDriverMock_Select_Call { _c.Call.Run(func(args mock.Arguments) { var arg0 context.Context if args[0] != nil { @@ -500,18 +500,18 @@ func (_c *DatabaseMock_Select_Call) Run(run func(ctx context.Context, query stri return _c } -func (_c *DatabaseMock_Select_Call) Return(err error) *DatabaseMock_Select_Call { +func (_c *DatabaseDriverMock_Select_Call) Return(err error) *DatabaseDriverMock_Select_Call { _c.Call.Return(err) return _c } -func (_c *DatabaseMock_Select_Call) RunAndReturn(run func(ctx context.Context, query string, dest any, args ...any) error) *DatabaseMock_Select_Call { +func (_c *DatabaseDriverMock_Select_Call) RunAndReturn(run func(ctx context.Context, query string, dest any, args ...any) error) *DatabaseDriverMock_Select_Call { _c.Call.Return(run) return _c } -// SelectNamed provides a mock function for the type DatabaseMock -func (_mock *DatabaseMock) SelectNamed(ctx context.Context, query string, dest any, arg any) error { +// SelectNamed provides a mock function for the type DatabaseDriverMock +func (_mock *DatabaseDriverMock) SelectNamed(ctx context.Context, query string, dest any, arg any) error { ret := _mock.Called(ctx, query, dest, arg) if len(ret) == 0 { @@ -527,8 +527,8 @@ func (_mock *DatabaseMock) SelectNamed(ctx context.Context, query string, dest a return r0 } -// DatabaseMock_SelectNamed_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SelectNamed' -type DatabaseMock_SelectNamed_Call struct { +// DatabaseDriverMock_SelectNamed_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SelectNamed' +type DatabaseDriverMock_SelectNamed_Call struct { *mock.Call } @@ -537,11 +537,11 @@ type DatabaseMock_SelectNamed_Call struct { // - query string // - dest any // - arg any -func (_e *DatabaseMock_Expecter) SelectNamed(ctx interface{}, query interface{}, dest interface{}, arg interface{}) *DatabaseMock_SelectNamed_Call { - return &DatabaseMock_SelectNamed_Call{Call: _e.mock.On("SelectNamed", ctx, query, dest, arg)} +func (_e *DatabaseDriverMock_Expecter) SelectNamed(ctx interface{}, query interface{}, dest interface{}, arg interface{}) *DatabaseDriverMock_SelectNamed_Call { + return &DatabaseDriverMock_SelectNamed_Call{Call: _e.mock.On("SelectNamed", ctx, query, dest, arg)} } -func (_c *DatabaseMock_SelectNamed_Call) Run(run func(ctx context.Context, query string, dest any, arg any)) *DatabaseMock_SelectNamed_Call { +func (_c *DatabaseDriverMock_SelectNamed_Call) Run(run func(ctx context.Context, query string, dest any, arg any)) *DatabaseDriverMock_SelectNamed_Call { _c.Call.Run(func(args mock.Arguments) { var arg0 context.Context if args[0] != nil { @@ -569,18 +569,18 @@ func (_c *DatabaseMock_SelectNamed_Call) Run(run func(ctx context.Context, query return _c } -func (_c *DatabaseMock_SelectNamed_Call) Return(err error) *DatabaseMock_SelectNamed_Call { +func (_c *DatabaseDriverMock_SelectNamed_Call) Return(err error) *DatabaseDriverMock_SelectNamed_Call { _c.Call.Return(err) return _c } -func (_c *DatabaseMock_SelectNamed_Call) RunAndReturn(run func(ctx context.Context, query string, dest any, arg any) error) *DatabaseMock_SelectNamed_Call { +func (_c *DatabaseDriverMock_SelectNamed_Call) RunAndReturn(run func(ctx context.Context, query string, dest any, arg any) error) *DatabaseDriverMock_SelectNamed_Call { _c.Call.Return(run) return _c } -// WithTransaction provides a mock function for the type DatabaseMock -func (_mock *DatabaseMock) WithTransaction(ctx context.Context, fn func(tx contract.Database) error) error { +// WithTransaction provides a mock function for the type DatabaseDriverMock +func (_mock *DatabaseDriverMock) WithTransaction(ctx context.Context, fn func(tx contract.DatabaseDriver) error) error { ret := _mock.Called(ctx, fn) if len(ret) == 0 { @@ -588,7 +588,7 @@ func (_mock *DatabaseMock) WithTransaction(ctx context.Context, fn func(tx contr } var r0 error - if returnFunc, ok := ret.Get(0).(func(context.Context, func(tx contract.Database) error) error); ok { + if returnFunc, ok := ret.Get(0).(func(context.Context, func(tx contract.DatabaseDriver) error) error); ok { r0 = returnFunc(ctx, fn) } else { r0 = ret.Error(0) @@ -596,27 +596,27 @@ func (_mock *DatabaseMock) WithTransaction(ctx context.Context, fn func(tx contr return r0 } -// DatabaseMock_WithTransaction_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'WithTransaction' -type DatabaseMock_WithTransaction_Call struct { +// DatabaseDriverMock_WithTransaction_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'WithTransaction' +type DatabaseDriverMock_WithTransaction_Call struct { *mock.Call } // WithTransaction is a helper method to define mock.On call // - ctx context.Context -// - fn func(tx contract.Database) error -func (_e *DatabaseMock_Expecter) WithTransaction(ctx interface{}, fn interface{}) *DatabaseMock_WithTransaction_Call { - return &DatabaseMock_WithTransaction_Call{Call: _e.mock.On("WithTransaction", ctx, fn)} +// - fn func(tx contract.DatabaseDriver) error +func (_e *DatabaseDriverMock_Expecter) WithTransaction(ctx interface{}, fn interface{}) *DatabaseDriverMock_WithTransaction_Call { + return &DatabaseDriverMock_WithTransaction_Call{Call: _e.mock.On("WithTransaction", ctx, fn)} } -func (_c *DatabaseMock_WithTransaction_Call) Run(run func(ctx context.Context, fn func(tx contract.Database) error)) *DatabaseMock_WithTransaction_Call { +func (_c *DatabaseDriverMock_WithTransaction_Call) Run(run func(ctx context.Context, fn func(tx contract.DatabaseDriver) error)) *DatabaseDriverMock_WithTransaction_Call { _c.Call.Run(func(args mock.Arguments) { var arg0 context.Context if args[0] != nil { arg0 = args[0].(context.Context) } - var arg1 func(tx contract.Database) error + var arg1 func(tx contract.DatabaseDriver) error if args[1] != nil { - arg1 = args[1].(func(tx contract.Database) error) + arg1 = args[1].(func(tx contract.DatabaseDriver) error) } run( arg0, @@ -626,12 +626,12 @@ func (_c *DatabaseMock_WithTransaction_Call) Run(run func(ctx context.Context, f return _c } -func (_c *DatabaseMock_WithTransaction_Call) Return(err error) *DatabaseMock_WithTransaction_Call { +func (_c *DatabaseDriverMock_WithTransaction_Call) Return(err error) *DatabaseDriverMock_WithTransaction_Call { _c.Call.Return(err) return _c } -func (_c *DatabaseMock_WithTransaction_Call) RunAndReturn(run func(ctx context.Context, fn func(tx contract.Database) error) error) *DatabaseMock_WithTransaction_Call { +func (_c *DatabaseDriverMock_WithTransaction_Call) RunAndReturn(run func(ctx context.Context, fn func(tx contract.DatabaseDriver) error) error) *DatabaseDriverMock_WithTransaction_Call { _c.Call.Return(run) return _c } diff --git a/contract/mock/event.go b/contract/mock/event.go index 6121c70..862d683 100644 --- a/contract/mock/event.go +++ b/contract/mock/event.go @@ -10,13 +10,13 @@ import ( "github.com/studiolambda/cosmos/contract" ) -// NewEventsMock creates a new instance of EventsMock. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// NewEventDriverMock creates a new instance of EventDriverMock. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. // The first argument is typically a *testing.T value. -func NewEventsMock(t interface { +func NewEventDriverMock(t interface { mock.TestingT Cleanup(func()) -}) *EventsMock { - mock := &EventsMock{} +}) *EventDriverMock { + mock := &EventDriverMock{} mock.Mock.Test(t) t.Cleanup(func() { mock.AssertExpectations(t) }) @@ -24,21 +24,21 @@ func NewEventsMock(t interface { return mock } -// EventsMock is an autogenerated mock type for the Events type -type EventsMock struct { +// EventDriverMock is an autogenerated mock type for the EventDriver type +type EventDriverMock struct { mock.Mock } -type EventsMock_Expecter struct { +type EventDriverMock_Expecter struct { mock *mock.Mock } -func (_m *EventsMock) EXPECT() *EventsMock_Expecter { - return &EventsMock_Expecter{mock: &_m.Mock} +func (_m *EventDriverMock) EXPECT() *EventDriverMock_Expecter { + return &EventDriverMock_Expecter{mock: &_m.Mock} } -// Close provides a mock function for the type EventsMock -func (_mock *EventsMock) Close() error { +// Close provides a mock function for the type EventDriverMock +func (_mock *EventDriverMock) Close() error { ret := _mock.Called() if len(ret) == 0 { @@ -54,35 +54,35 @@ func (_mock *EventsMock) Close() error { return r0 } -// EventsMock_Close_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Close' -type EventsMock_Close_Call struct { +// EventDriverMock_Close_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Close' +type EventDriverMock_Close_Call struct { *mock.Call } // Close is a helper method to define mock.On call -func (_e *EventsMock_Expecter) Close() *EventsMock_Close_Call { - return &EventsMock_Close_Call{Call: _e.mock.On("Close")} +func (_e *EventDriverMock_Expecter) Close() *EventDriverMock_Close_Call { + return &EventDriverMock_Close_Call{Call: _e.mock.On("Close")} } -func (_c *EventsMock_Close_Call) Run(run func()) *EventsMock_Close_Call { +func (_c *EventDriverMock_Close_Call) Run(run func()) *EventDriverMock_Close_Call { _c.Call.Run(func(args mock.Arguments) { run() }) return _c } -func (_c *EventsMock_Close_Call) Return(err error) *EventsMock_Close_Call { +func (_c *EventDriverMock_Close_Call) Return(err error) *EventDriverMock_Close_Call { _c.Call.Return(err) return _c } -func (_c *EventsMock_Close_Call) RunAndReturn(run func() error) *EventsMock_Close_Call { +func (_c *EventDriverMock_Close_Call) RunAndReturn(run func() error) *EventDriverMock_Close_Call { _c.Call.Return(run) return _c } -// Publish provides a mock function for the type EventsMock -func (_mock *EventsMock) Publish(ctx context.Context, event string, payload any) error { +// Publish provides a mock function for the type EventDriverMock +func (_mock *EventDriverMock) Publish(ctx context.Context, event string, payload []byte) error { ret := _mock.Called(ctx, event, payload) if len(ret) == 0 { @@ -90,7 +90,7 @@ func (_mock *EventsMock) Publish(ctx context.Context, event string, payload any) } var r0 error - if returnFunc, ok := ret.Get(0).(func(context.Context, string, any) error); ok { + if returnFunc, ok := ret.Get(0).(func(context.Context, string, []byte) error); ok { r0 = returnFunc(ctx, event, payload) } else { r0 = ret.Error(0) @@ -98,20 +98,20 @@ func (_mock *EventsMock) Publish(ctx context.Context, event string, payload any) return r0 } -// EventsMock_Publish_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Publish' -type EventsMock_Publish_Call struct { +// EventDriverMock_Publish_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Publish' +type EventDriverMock_Publish_Call struct { *mock.Call } // Publish is a helper method to define mock.On call // - ctx context.Context // - event string -// - payload any -func (_e *EventsMock_Expecter) Publish(ctx interface{}, event interface{}, payload interface{}) *EventsMock_Publish_Call { - return &EventsMock_Publish_Call{Call: _e.mock.On("Publish", ctx, event, payload)} +// - payload []byte +func (_e *EventDriverMock_Expecter) Publish(ctx interface{}, event interface{}, payload interface{}) *EventDriverMock_Publish_Call { + return &EventDriverMock_Publish_Call{Call: _e.mock.On("Publish", ctx, event, payload)} } -func (_c *EventsMock_Publish_Call) Run(run func(ctx context.Context, event string, payload any)) *EventsMock_Publish_Call { +func (_c *EventDriverMock_Publish_Call) Run(run func(ctx context.Context, event string, payload []byte)) *EventDriverMock_Publish_Call { _c.Call.Run(func(args mock.Arguments) { var arg0 context.Context if args[0] != nil { @@ -121,9 +121,9 @@ func (_c *EventsMock_Publish_Call) Run(run func(ctx context.Context, event strin if args[1] != nil { arg1 = args[1].(string) } - var arg2 any + var arg2 []byte if args[2] != nil { - arg2 = args[2].(any) + arg2 = args[2].([]byte) } run( arg0, @@ -134,18 +134,18 @@ func (_c *EventsMock_Publish_Call) Run(run func(ctx context.Context, event strin return _c } -func (_c *EventsMock_Publish_Call) Return(err error) *EventsMock_Publish_Call { +func (_c *EventDriverMock_Publish_Call) Return(err error) *EventDriverMock_Publish_Call { _c.Call.Return(err) return _c } -func (_c *EventsMock_Publish_Call) RunAndReturn(run func(ctx context.Context, event string, payload any) error) *EventsMock_Publish_Call { +func (_c *EventDriverMock_Publish_Call) RunAndReturn(run func(ctx context.Context, event string, payload []byte) error) *EventDriverMock_Publish_Call { _c.Call.Return(run) return _c } -// Subscribe provides a mock function for the type EventsMock -func (_mock *EventsMock) Subscribe(ctx context.Context, event string, handler contract.EventHandler) (contract.EventUnsubscribeFunc, error) { +// Subscribe provides a mock function for the type EventDriverMock +func (_mock *EventDriverMock) Subscribe(ctx context.Context, event string, handler contract.EventHandler) (contract.EventUnsubscribeFunc, error) { ret := _mock.Called(ctx, event, handler) if len(ret) == 0 { @@ -172,8 +172,8 @@ func (_mock *EventsMock) Subscribe(ctx context.Context, event string, handler co return r0, r1 } -// EventsMock_Subscribe_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Subscribe' -type EventsMock_Subscribe_Call struct { +// EventDriverMock_Subscribe_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Subscribe' +type EventDriverMock_Subscribe_Call struct { *mock.Call } @@ -181,11 +181,11 @@ type EventsMock_Subscribe_Call struct { // - ctx context.Context // - event string // - handler contract.EventHandler -func (_e *EventsMock_Expecter) Subscribe(ctx interface{}, event interface{}, handler interface{}) *EventsMock_Subscribe_Call { - return &EventsMock_Subscribe_Call{Call: _e.mock.On("Subscribe", ctx, event, handler)} +func (_e *EventDriverMock_Expecter) Subscribe(ctx interface{}, event interface{}, handler interface{}) *EventDriverMock_Subscribe_Call { + return &EventDriverMock_Subscribe_Call{Call: _e.mock.On("Subscribe", ctx, event, handler)} } -func (_c *EventsMock_Subscribe_Call) Run(run func(ctx context.Context, event string, handler contract.EventHandler)) *EventsMock_Subscribe_Call { +func (_c *EventDriverMock_Subscribe_Call) Run(run func(ctx context.Context, event string, handler contract.EventHandler)) *EventDriverMock_Subscribe_Call { _c.Call.Run(func(args mock.Arguments) { var arg0 context.Context if args[0] != nil { @@ -208,12 +208,12 @@ func (_c *EventsMock_Subscribe_Call) Run(run func(ctx context.Context, event str return _c } -func (_c *EventsMock_Subscribe_Call) Return(v contract.EventUnsubscribeFunc, err error) *EventsMock_Subscribe_Call { +func (_c *EventDriverMock_Subscribe_Call) Return(v contract.EventUnsubscribeFunc, err error) *EventDriverMock_Subscribe_Call { _c.Call.Return(v, err) return _c } -func (_c *EventsMock_Subscribe_Call) RunAndReturn(run func(ctx context.Context, event string, handler contract.EventHandler) (contract.EventUnsubscribeFunc, error)) *EventsMock_Subscribe_Call { +func (_c *EventDriverMock_Subscribe_Call) RunAndReturn(run func(ctx context.Context, event string, handler contract.EventHandler) (contract.EventUnsubscribeFunc, error)) *EventDriverMock_Subscribe_Call { _c.Call.Return(run) return _c } diff --git a/contract/mock/hash.go b/contract/mock/hash.go index 93b7c6b..5e56001 100644 --- a/contract/mock/hash.go +++ b/contract/mock/hash.go @@ -162,3 +162,81 @@ func (_c *HasherMock_Hash_Call) RunAndReturn(run func(value []byte) ([]byte, err _c.Call.Return(run) return _c } + +// NewRehashableMock creates a new instance of RehashableMock. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewRehashableMock(t interface { + mock.TestingT + Cleanup(func()) +}) *RehashableMock { + mock := &RehashableMock{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} + +// RehashableMock is an autogenerated mock type for the Rehashable type +type RehashableMock struct { + mock.Mock +} + +type RehashableMock_Expecter struct { + mock *mock.Mock +} + +func (_m *RehashableMock) EXPECT() *RehashableMock_Expecter { + return &RehashableMock_Expecter{mock: &_m.Mock} +} + +// NeedsRehash provides a mock function for the type RehashableMock +func (_mock *RehashableMock) NeedsRehash(hash []byte) bool { + ret := _mock.Called(hash) + + if len(ret) == 0 { + panic("no return value specified for NeedsRehash") + } + + var r0 bool + if returnFunc, ok := ret.Get(0).(func([]byte) bool); ok { + r0 = returnFunc(hash) + } else { + r0 = ret.Get(0).(bool) + } + return r0 +} + +// RehashableMock_NeedsRehash_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'NeedsRehash' +type RehashableMock_NeedsRehash_Call struct { + *mock.Call +} + +// NeedsRehash is a helper method to define mock.On call +// - hash []byte +func (_e *RehashableMock_Expecter) NeedsRehash(hash interface{}) *RehashableMock_NeedsRehash_Call { + return &RehashableMock_NeedsRehash_Call{Call: _e.mock.On("NeedsRehash", hash)} +} + +func (_c *RehashableMock_NeedsRehash_Call) Run(run func(hash []byte)) *RehashableMock_NeedsRehash_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 []byte + if args[0] != nil { + arg0 = args[0].([]byte) + } + run( + arg0, + ) + }) + return _c +} + +func (_c *RehashableMock_NeedsRehash_Call) Return(b bool) *RehashableMock_NeedsRehash_Call { + _c.Call.Return(b) + return _c +} + +func (_c *RehashableMock_NeedsRehash_Call) RunAndReturn(run func(hash []byte) bool) *RehashableMock_NeedsRehash_Call { + _c.Call.Return(run) + return _c +} diff --git a/contract/mock/hooks.go b/contract/mock/hooks.go new file mode 100644 index 0000000..ed46244 --- /dev/null +++ b/contract/mock/hooks.go @@ -0,0 +1,319 @@ +// Code generated by mockery; DO NOT EDIT. +// github.com/vektra/mockery +// template: testify + +package mock + +import ( + mock "github.com/stretchr/testify/mock" + "github.com/studiolambda/cosmos/contract" +) + +// NewHooksMock creates a new instance of HooksMock. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewHooksMock(t interface { + mock.TestingT + Cleanup(func()) +}) *HooksMock { + mock := &HooksMock{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} + +// HooksMock is an autogenerated mock type for the Hooks type +type HooksMock struct { + mock.Mock +} + +type HooksMock_Expecter struct { + mock *mock.Mock +} + +func (_m *HooksMock) EXPECT() *HooksMock_Expecter { + return &HooksMock_Expecter{mock: &_m.Mock} +} + +// AfterResponse provides a mock function for the type HooksMock +func (_mock *HooksMock) AfterResponse(callbacks ...contract.AfterResponseHook) { + if len(callbacks) > 0 { + _mock.Called(callbacks) + } else { + _mock.Called() + } + + return +} + +// HooksMock_AfterResponse_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AfterResponse' +type HooksMock_AfterResponse_Call struct { + *mock.Call +} + +// AfterResponse is a helper method to define mock.On call +// - callbacks ...contract.AfterResponseHook +func (_e *HooksMock_Expecter) AfterResponse(callbacks ...interface{}) *HooksMock_AfterResponse_Call { + return &HooksMock_AfterResponse_Call{Call: _e.mock.On("AfterResponse", + append([]interface{}{}, callbacks...)...)} +} + +func (_c *HooksMock_AfterResponse_Call) Run(run func(callbacks ...contract.AfterResponseHook)) *HooksMock_AfterResponse_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 []contract.AfterResponseHook + var variadicArgs []contract.AfterResponseHook + if len(args) > 0 { + variadicArgs = args[0].([]contract.AfterResponseHook) + } + arg0 = variadicArgs + run( + arg0..., + ) + }) + return _c +} + +func (_c *HooksMock_AfterResponse_Call) Return() *HooksMock_AfterResponse_Call { + _c.Call.Return() + return _c +} + +func (_c *HooksMock_AfterResponse_Call) RunAndReturn(run func(callbacks ...contract.AfterResponseHook)) *HooksMock_AfterResponse_Call { + _c.Run(run) + return _c +} + +// AfterResponseFuncs provides a mock function for the type HooksMock +func (_mock *HooksMock) AfterResponseFuncs() []contract.AfterResponseHook { + ret := _mock.Called() + + if len(ret) == 0 { + panic("no return value specified for AfterResponseFuncs") + } + + var r0 []contract.AfterResponseHook + if returnFunc, ok := ret.Get(0).(func() []contract.AfterResponseHook); ok { + r0 = returnFunc() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]contract.AfterResponseHook) + } + } + return r0 +} + +// HooksMock_AfterResponseFuncs_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AfterResponseFuncs' +type HooksMock_AfterResponseFuncs_Call struct { + *mock.Call +} + +// AfterResponseFuncs is a helper method to define mock.On call +func (_e *HooksMock_Expecter) AfterResponseFuncs() *HooksMock_AfterResponseFuncs_Call { + return &HooksMock_AfterResponseFuncs_Call{Call: _e.mock.On("AfterResponseFuncs")} +} + +func (_c *HooksMock_AfterResponseFuncs_Call) Run(run func()) *HooksMock_AfterResponseFuncs_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *HooksMock_AfterResponseFuncs_Call) Return(vs []contract.AfterResponseHook) *HooksMock_AfterResponseFuncs_Call { + _c.Call.Return(vs) + return _c +} + +func (_c *HooksMock_AfterResponseFuncs_Call) RunAndReturn(run func() []contract.AfterResponseHook) *HooksMock_AfterResponseFuncs_Call { + _c.Call.Return(run) + return _c +} + +// BeforeWrite provides a mock function for the type HooksMock +func (_mock *HooksMock) BeforeWrite(callbacks ...contract.BeforeWriteHook) { + if len(callbacks) > 0 { + _mock.Called(callbacks) + } else { + _mock.Called() + } + + return +} + +// HooksMock_BeforeWrite_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'BeforeWrite' +type HooksMock_BeforeWrite_Call struct { + *mock.Call +} + +// BeforeWrite is a helper method to define mock.On call +// - callbacks ...contract.BeforeWriteHook +func (_e *HooksMock_Expecter) BeforeWrite(callbacks ...interface{}) *HooksMock_BeforeWrite_Call { + return &HooksMock_BeforeWrite_Call{Call: _e.mock.On("BeforeWrite", + append([]interface{}{}, callbacks...)...)} +} + +func (_c *HooksMock_BeforeWrite_Call) Run(run func(callbacks ...contract.BeforeWriteHook)) *HooksMock_BeforeWrite_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 []contract.BeforeWriteHook + var variadicArgs []contract.BeforeWriteHook + if len(args) > 0 { + variadicArgs = args[0].([]contract.BeforeWriteHook) + } + arg0 = variadicArgs + run( + arg0..., + ) + }) + return _c +} + +func (_c *HooksMock_BeforeWrite_Call) Return() *HooksMock_BeforeWrite_Call { + _c.Call.Return() + return _c +} + +func (_c *HooksMock_BeforeWrite_Call) RunAndReturn(run func(callbacks ...contract.BeforeWriteHook)) *HooksMock_BeforeWrite_Call { + _c.Run(run) + return _c +} + +// BeforeWriteFuncs provides a mock function for the type HooksMock +func (_mock *HooksMock) BeforeWriteFuncs() []contract.BeforeWriteHook { + ret := _mock.Called() + + if len(ret) == 0 { + panic("no return value specified for BeforeWriteFuncs") + } + + var r0 []contract.BeforeWriteHook + if returnFunc, ok := ret.Get(0).(func() []contract.BeforeWriteHook); ok { + r0 = returnFunc() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]contract.BeforeWriteHook) + } + } + return r0 +} + +// HooksMock_BeforeWriteFuncs_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'BeforeWriteFuncs' +type HooksMock_BeforeWriteFuncs_Call struct { + *mock.Call +} + +// BeforeWriteFuncs is a helper method to define mock.On call +func (_e *HooksMock_Expecter) BeforeWriteFuncs() *HooksMock_BeforeWriteFuncs_Call { + return &HooksMock_BeforeWriteFuncs_Call{Call: _e.mock.On("BeforeWriteFuncs")} +} + +func (_c *HooksMock_BeforeWriteFuncs_Call) Run(run func()) *HooksMock_BeforeWriteFuncs_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *HooksMock_BeforeWriteFuncs_Call) Return(vs []contract.BeforeWriteHook) *HooksMock_BeforeWriteFuncs_Call { + _c.Call.Return(vs) + return _c +} + +func (_c *HooksMock_BeforeWriteFuncs_Call) RunAndReturn(run func() []contract.BeforeWriteHook) *HooksMock_BeforeWriteFuncs_Call { + _c.Call.Return(run) + return _c +} + +// BeforeWriteHeader provides a mock function for the type HooksMock +func (_mock *HooksMock) BeforeWriteHeader(callbacks ...contract.BeforeWriteHeaderHook) { + if len(callbacks) > 0 { + _mock.Called(callbacks) + } else { + _mock.Called() + } + + return +} + +// HooksMock_BeforeWriteHeader_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'BeforeWriteHeader' +type HooksMock_BeforeWriteHeader_Call struct { + *mock.Call +} + +// BeforeWriteHeader is a helper method to define mock.On call +// - callbacks ...contract.BeforeWriteHeaderHook +func (_e *HooksMock_Expecter) BeforeWriteHeader(callbacks ...interface{}) *HooksMock_BeforeWriteHeader_Call { + return &HooksMock_BeforeWriteHeader_Call{Call: _e.mock.On("BeforeWriteHeader", + append([]interface{}{}, callbacks...)...)} +} + +func (_c *HooksMock_BeforeWriteHeader_Call) Run(run func(callbacks ...contract.BeforeWriteHeaderHook)) *HooksMock_BeforeWriteHeader_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 []contract.BeforeWriteHeaderHook + var variadicArgs []contract.BeforeWriteHeaderHook + if len(args) > 0 { + variadicArgs = args[0].([]contract.BeforeWriteHeaderHook) + } + arg0 = variadicArgs + run( + arg0..., + ) + }) + return _c +} + +func (_c *HooksMock_BeforeWriteHeader_Call) Return() *HooksMock_BeforeWriteHeader_Call { + _c.Call.Return() + return _c +} + +func (_c *HooksMock_BeforeWriteHeader_Call) RunAndReturn(run func(callbacks ...contract.BeforeWriteHeaderHook)) *HooksMock_BeforeWriteHeader_Call { + _c.Run(run) + return _c +} + +// BeforeWriteHeaderFuncs provides a mock function for the type HooksMock +func (_mock *HooksMock) BeforeWriteHeaderFuncs() []contract.BeforeWriteHeaderHook { + ret := _mock.Called() + + if len(ret) == 0 { + panic("no return value specified for BeforeWriteHeaderFuncs") + } + + var r0 []contract.BeforeWriteHeaderHook + if returnFunc, ok := ret.Get(0).(func() []contract.BeforeWriteHeaderHook); ok { + r0 = returnFunc() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]contract.BeforeWriteHeaderHook) + } + } + return r0 +} + +// HooksMock_BeforeWriteHeaderFuncs_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'BeforeWriteHeaderFuncs' +type HooksMock_BeforeWriteHeaderFuncs_Call struct { + *mock.Call +} + +// BeforeWriteHeaderFuncs is a helper method to define mock.On call +func (_e *HooksMock_Expecter) BeforeWriteHeaderFuncs() *HooksMock_BeforeWriteHeaderFuncs_Call { + return &HooksMock_BeforeWriteHeaderFuncs_Call{Call: _e.mock.On("BeforeWriteHeaderFuncs")} +} + +func (_c *HooksMock_BeforeWriteHeaderFuncs_Call) Run(run func()) *HooksMock_BeforeWriteHeaderFuncs_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *HooksMock_BeforeWriteHeaderFuncs_Call) Return(vs []contract.BeforeWriteHeaderHook) *HooksMock_BeforeWriteHeaderFuncs_Call { + _c.Call.Return(vs) + return _c +} + +func (_c *HooksMock_BeforeWriteHeaderFuncs_Call) RunAndReturn(run func() []contract.BeforeWriteHeaderHook) *HooksMock_BeforeWriteHeaderFuncs_Call { + _c.Call.Return(run) + return _c +} diff --git a/contract/mock/session.go b/contract/mock/session.go index 8013239..337546d 100644 --- a/contract/mock/session.go +++ b/contract/mock/session.go @@ -11,690 +11,6 @@ import ( "time" ) -// NewSessionMock creates a new instance of SessionMock. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func NewSessionMock(t interface { - mock.TestingT - Cleanup(func()) -}) *SessionMock { - mock := &SessionMock{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} - -// SessionMock is an autogenerated mock type for the Session type -type SessionMock struct { - mock.Mock -} - -type SessionMock_Expecter struct { - mock *mock.Mock -} - -func (_m *SessionMock) EXPECT() *SessionMock_Expecter { - return &SessionMock_Expecter{mock: &_m.Mock} -} - -// Clear provides a mock function for the type SessionMock -func (_mock *SessionMock) Clear() { - _mock.Called() - return -} - -// SessionMock_Clear_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Clear' -type SessionMock_Clear_Call struct { - *mock.Call -} - -// Clear is a helper method to define mock.On call -func (_e *SessionMock_Expecter) Clear() *SessionMock_Clear_Call { - return &SessionMock_Clear_Call{Call: _e.mock.On("Clear")} -} - -func (_c *SessionMock_Clear_Call) Run(run func()) *SessionMock_Clear_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *SessionMock_Clear_Call) Return() *SessionMock_Clear_Call { - _c.Call.Return() - return _c -} - -func (_c *SessionMock_Clear_Call) RunAndReturn(run func()) *SessionMock_Clear_Call { - _c.Run(run) - return _c -} - -// CreatedAt provides a mock function for the type SessionMock -func (_mock *SessionMock) CreatedAt() time.Time { - ret := _mock.Called() - - if len(ret) == 0 { - panic("no return value specified for CreatedAt") - } - - var r0 time.Time - if returnFunc, ok := ret.Get(0).(func() time.Time); ok { - r0 = returnFunc() - } else { - r0 = ret.Get(0).(time.Time) - } - return r0 -} - -// SessionMock_CreatedAt_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreatedAt' -type SessionMock_CreatedAt_Call struct { - *mock.Call -} - -// CreatedAt is a helper method to define mock.On call -func (_e *SessionMock_Expecter) CreatedAt() *SessionMock_CreatedAt_Call { - return &SessionMock_CreatedAt_Call{Call: _e.mock.On("CreatedAt")} -} - -func (_c *SessionMock_CreatedAt_Call) Run(run func()) *SessionMock_CreatedAt_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *SessionMock_CreatedAt_Call) Return(time1 time.Time) *SessionMock_CreatedAt_Call { - _c.Call.Return(time1) - return _c -} - -func (_c *SessionMock_CreatedAt_Call) RunAndReturn(run func() time.Time) *SessionMock_CreatedAt_Call { - _c.Call.Return(run) - return _c -} - -// Delete provides a mock function for the type SessionMock -func (_mock *SessionMock) Delete(key string) { - _mock.Called(key) - return -} - -// SessionMock_Delete_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Delete' -type SessionMock_Delete_Call struct { - *mock.Call -} - -// Delete is a helper method to define mock.On call -// - key string -func (_e *SessionMock_Expecter) Delete(key interface{}) *SessionMock_Delete_Call { - return &SessionMock_Delete_Call{Call: _e.mock.On("Delete", key)} -} - -func (_c *SessionMock_Delete_Call) Run(run func(key string)) *SessionMock_Delete_Call { - _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) - }) - return _c -} - -func (_c *SessionMock_Delete_Call) Return() *SessionMock_Delete_Call { - _c.Call.Return() - return _c -} - -func (_c *SessionMock_Delete_Call) RunAndReturn(run func(key string)) *SessionMock_Delete_Call { - _c.Run(run) - return _c -} - -// ExpiresAt provides a mock function for the type SessionMock -func (_mock *SessionMock) ExpiresAt() time.Time { - ret := _mock.Called() - - if len(ret) == 0 { - panic("no return value specified for ExpiresAt") - } - - var r0 time.Time - if returnFunc, ok := ret.Get(0).(func() time.Time); ok { - r0 = returnFunc() - } else { - r0 = ret.Get(0).(time.Time) - } - return r0 -} - -// SessionMock_ExpiresAt_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ExpiresAt' -type SessionMock_ExpiresAt_Call struct { - *mock.Call -} - -// ExpiresAt is a helper method to define mock.On call -func (_e *SessionMock_Expecter) ExpiresAt() *SessionMock_ExpiresAt_Call { - return &SessionMock_ExpiresAt_Call{Call: _e.mock.On("ExpiresAt")} -} - -func (_c *SessionMock_ExpiresAt_Call) Run(run func()) *SessionMock_ExpiresAt_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *SessionMock_ExpiresAt_Call) Return(time1 time.Time) *SessionMock_ExpiresAt_Call { - _c.Call.Return(time1) - return _c -} - -func (_c *SessionMock_ExpiresAt_Call) RunAndReturn(run func() time.Time) *SessionMock_ExpiresAt_Call { - _c.Call.Return(run) - return _c -} - -// ExpiresSoon provides a mock function for the type SessionMock -func (_mock *SessionMock) ExpiresSoon(delta time.Duration) bool { - ret := _mock.Called(delta) - - if len(ret) == 0 { - panic("no return value specified for ExpiresSoon") - } - - var r0 bool - if returnFunc, ok := ret.Get(0).(func(time.Duration) bool); ok { - r0 = returnFunc(delta) - } else { - r0 = ret.Get(0).(bool) - } - return r0 -} - -// SessionMock_ExpiresSoon_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ExpiresSoon' -type SessionMock_ExpiresSoon_Call struct { - *mock.Call -} - -// ExpiresSoon is a helper method to define mock.On call -// - delta time.Duration -func (_e *SessionMock_Expecter) ExpiresSoon(delta interface{}) *SessionMock_ExpiresSoon_Call { - return &SessionMock_ExpiresSoon_Call{Call: _e.mock.On("ExpiresSoon", delta)} -} - -func (_c *SessionMock_ExpiresSoon_Call) Run(run func(delta time.Duration)) *SessionMock_ExpiresSoon_Call { - _c.Call.Run(func(args mock.Arguments) { - var arg0 time.Duration - if args[0] != nil { - arg0 = args[0].(time.Duration) - } - run( - arg0, - ) - }) - return _c -} - -func (_c *SessionMock_ExpiresSoon_Call) Return(b bool) *SessionMock_ExpiresSoon_Call { - _c.Call.Return(b) - return _c -} - -func (_c *SessionMock_ExpiresSoon_Call) RunAndReturn(run func(delta time.Duration) bool) *SessionMock_ExpiresSoon_Call { - _c.Call.Return(run) - return _c -} - -// Extend provides a mock function for the type SessionMock -func (_mock *SessionMock) Extend(expiresAt time.Time) { - _mock.Called(expiresAt) - return -} - -// SessionMock_Extend_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Extend' -type SessionMock_Extend_Call struct { - *mock.Call -} - -// Extend is a helper method to define mock.On call -// - expiresAt time.Time -func (_e *SessionMock_Expecter) Extend(expiresAt interface{}) *SessionMock_Extend_Call { - return &SessionMock_Extend_Call{Call: _e.mock.On("Extend", expiresAt)} -} - -func (_c *SessionMock_Extend_Call) Run(run func(expiresAt time.Time)) *SessionMock_Extend_Call { - _c.Call.Run(func(args mock.Arguments) { - var arg0 time.Time - if args[0] != nil { - arg0 = args[0].(time.Time) - } - run( - arg0, - ) - }) - return _c -} - -func (_c *SessionMock_Extend_Call) Return() *SessionMock_Extend_Call { - _c.Call.Return() - return _c -} - -func (_c *SessionMock_Extend_Call) RunAndReturn(run func(expiresAt time.Time)) *SessionMock_Extend_Call { - _c.Run(run) - return _c -} - -// Get provides a mock function for the type SessionMock -func (_mock *SessionMock) Get(key string) (any, bool) { - ret := _mock.Called(key) - - if len(ret) == 0 { - panic("no return value specified for Get") - } - - var r0 any - var r1 bool - if returnFunc, ok := ret.Get(0).(func(string) (any, bool)); ok { - return returnFunc(key) - } - if returnFunc, ok := ret.Get(0).(func(string) any); ok { - r0 = returnFunc(key) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(any) - } - } - if returnFunc, ok := ret.Get(1).(func(string) bool); ok { - r1 = returnFunc(key) - } else { - r1 = ret.Get(1).(bool) - } - return r0, r1 -} - -// SessionMock_Get_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Get' -type SessionMock_Get_Call struct { - *mock.Call -} - -// Get is a helper method to define mock.On call -// - key string -func (_e *SessionMock_Expecter) Get(key interface{}) *SessionMock_Get_Call { - return &SessionMock_Get_Call{Call: _e.mock.On("Get", key)} -} - -func (_c *SessionMock_Get_Call) Run(run func(key string)) *SessionMock_Get_Call { - _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) - }) - return _c -} - -func (_c *SessionMock_Get_Call) Return(v any, b bool) *SessionMock_Get_Call { - _c.Call.Return(v, b) - return _c -} - -func (_c *SessionMock_Get_Call) RunAndReturn(run func(key string) (any, bool)) *SessionMock_Get_Call { - _c.Call.Return(run) - return _c -} - -// HasChanged provides a mock function for the type SessionMock -func (_mock *SessionMock) HasChanged() bool { - ret := _mock.Called() - - if len(ret) == 0 { - panic("no return value specified for HasChanged") - } - - var r0 bool - if returnFunc, ok := ret.Get(0).(func() bool); ok { - r0 = returnFunc() - } else { - r0 = ret.Get(0).(bool) - } - return r0 -} - -// SessionMock_HasChanged_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'HasChanged' -type SessionMock_HasChanged_Call struct { - *mock.Call -} - -// HasChanged is a helper method to define mock.On call -func (_e *SessionMock_Expecter) HasChanged() *SessionMock_HasChanged_Call { - return &SessionMock_HasChanged_Call{Call: _e.mock.On("HasChanged")} -} - -func (_c *SessionMock_HasChanged_Call) Run(run func()) *SessionMock_HasChanged_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *SessionMock_HasChanged_Call) Return(b bool) *SessionMock_HasChanged_Call { - _c.Call.Return(b) - return _c -} - -func (_c *SessionMock_HasChanged_Call) RunAndReturn(run func() bool) *SessionMock_HasChanged_Call { - _c.Call.Return(run) - return _c -} - -// HasExpired provides a mock function for the type SessionMock -func (_mock *SessionMock) HasExpired() bool { - ret := _mock.Called() - - if len(ret) == 0 { - panic("no return value specified for HasExpired") - } - - var r0 bool - if returnFunc, ok := ret.Get(0).(func() bool); ok { - r0 = returnFunc() - } else { - r0 = ret.Get(0).(bool) - } - return r0 -} - -// SessionMock_HasExpired_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'HasExpired' -type SessionMock_HasExpired_Call struct { - *mock.Call -} - -// HasExpired is a helper method to define mock.On call -func (_e *SessionMock_Expecter) HasExpired() *SessionMock_HasExpired_Call { - return &SessionMock_HasExpired_Call{Call: _e.mock.On("HasExpired")} -} - -func (_c *SessionMock_HasExpired_Call) Run(run func()) *SessionMock_HasExpired_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *SessionMock_HasExpired_Call) Return(b bool) *SessionMock_HasExpired_Call { - _c.Call.Return(b) - return _c -} - -func (_c *SessionMock_HasExpired_Call) RunAndReturn(run func() bool) *SessionMock_HasExpired_Call { - _c.Call.Return(run) - return _c -} - -// HasRegenerated provides a mock function for the type SessionMock -func (_mock *SessionMock) HasRegenerated() bool { - ret := _mock.Called() - - if len(ret) == 0 { - panic("no return value specified for HasRegenerated") - } - - var r0 bool - if returnFunc, ok := ret.Get(0).(func() bool); ok { - r0 = returnFunc() - } else { - r0 = ret.Get(0).(bool) - } - return r0 -} - -// SessionMock_HasRegenerated_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'HasRegenerated' -type SessionMock_HasRegenerated_Call struct { - *mock.Call -} - -// HasRegenerated is a helper method to define mock.On call -func (_e *SessionMock_Expecter) HasRegenerated() *SessionMock_HasRegenerated_Call { - return &SessionMock_HasRegenerated_Call{Call: _e.mock.On("HasRegenerated")} -} - -func (_c *SessionMock_HasRegenerated_Call) Run(run func()) *SessionMock_HasRegenerated_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *SessionMock_HasRegenerated_Call) Return(b bool) *SessionMock_HasRegenerated_Call { - _c.Call.Return(b) - return _c -} - -func (_c *SessionMock_HasRegenerated_Call) RunAndReturn(run func() bool) *SessionMock_HasRegenerated_Call { - _c.Call.Return(run) - return _c -} - -// MarkAsUnchanged provides a mock function for the type SessionMock -func (_mock *SessionMock) MarkAsUnchanged() { - _mock.Called() - return -} - -// SessionMock_MarkAsUnchanged_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'MarkAsUnchanged' -type SessionMock_MarkAsUnchanged_Call struct { - *mock.Call -} - -// MarkAsUnchanged is a helper method to define mock.On call -func (_e *SessionMock_Expecter) MarkAsUnchanged() *SessionMock_MarkAsUnchanged_Call { - return &SessionMock_MarkAsUnchanged_Call{Call: _e.mock.On("MarkAsUnchanged")} -} - -func (_c *SessionMock_MarkAsUnchanged_Call) Run(run func()) *SessionMock_MarkAsUnchanged_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *SessionMock_MarkAsUnchanged_Call) Return() *SessionMock_MarkAsUnchanged_Call { - _c.Call.Return() - return _c -} - -func (_c *SessionMock_MarkAsUnchanged_Call) RunAndReturn(run func()) *SessionMock_MarkAsUnchanged_Call { - _c.Run(run) - return _c -} - -// OriginalSessionID provides a mock function for the type SessionMock -func (_mock *SessionMock) OriginalSessionID() string { - ret := _mock.Called() - - if len(ret) == 0 { - panic("no return value specified for OriginalSessionID") - } - - var r0 string - if returnFunc, ok := ret.Get(0).(func() string); ok { - r0 = returnFunc() - } else { - r0 = ret.Get(0).(string) - } - return r0 -} - -// SessionMock_OriginalSessionID_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'OriginalSessionID' -type SessionMock_OriginalSessionID_Call struct { - *mock.Call -} - -// OriginalSessionID is a helper method to define mock.On call -func (_e *SessionMock_Expecter) OriginalSessionID() *SessionMock_OriginalSessionID_Call { - return &SessionMock_OriginalSessionID_Call{Call: _e.mock.On("OriginalSessionID")} -} - -func (_c *SessionMock_OriginalSessionID_Call) Run(run func()) *SessionMock_OriginalSessionID_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *SessionMock_OriginalSessionID_Call) Return(s string) *SessionMock_OriginalSessionID_Call { - _c.Call.Return(s) - return _c -} - -func (_c *SessionMock_OriginalSessionID_Call) RunAndReturn(run func() string) *SessionMock_OriginalSessionID_Call { - _c.Call.Return(run) - return _c -} - -// Put provides a mock function for the type SessionMock -func (_mock *SessionMock) Put(key string, value any) { - _mock.Called(key, value) - return -} - -// SessionMock_Put_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Put' -type SessionMock_Put_Call struct { - *mock.Call -} - -// Put is a helper method to define mock.On call -// - key string -// - value any -func (_e *SessionMock_Expecter) Put(key interface{}, value interface{}) *SessionMock_Put_Call { - return &SessionMock_Put_Call{Call: _e.mock.On("Put", key, value)} -} - -func (_c *SessionMock_Put_Call) Run(run func(key string, value any)) *SessionMock_Put_Call { - _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - var arg1 any - if args[1] != nil { - arg1 = args[1].(any) - } - run( - arg0, - arg1, - ) - }) - return _c -} - -func (_c *SessionMock_Put_Call) Return() *SessionMock_Put_Call { - _c.Call.Return() - return _c -} - -func (_c *SessionMock_Put_Call) RunAndReturn(run func(key string, value any)) *SessionMock_Put_Call { - _c.Run(run) - return _c -} - -// Regenerate provides a mock function for the type SessionMock -func (_mock *SessionMock) Regenerate() error { - ret := _mock.Called() - - if len(ret) == 0 { - panic("no return value specified for Regenerate") - } - - var r0 error - if returnFunc, ok := ret.Get(0).(func() error); ok { - r0 = returnFunc() - } else { - r0 = ret.Error(0) - } - return r0 -} - -// SessionMock_Regenerate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Regenerate' -type SessionMock_Regenerate_Call struct { - *mock.Call -} - -// Regenerate is a helper method to define mock.On call -func (_e *SessionMock_Expecter) Regenerate() *SessionMock_Regenerate_Call { - return &SessionMock_Regenerate_Call{Call: _e.mock.On("Regenerate")} -} - -func (_c *SessionMock_Regenerate_Call) Run(run func()) *SessionMock_Regenerate_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *SessionMock_Regenerate_Call) Return(err error) *SessionMock_Regenerate_Call { - _c.Call.Return(err) - return _c -} - -func (_c *SessionMock_Regenerate_Call) RunAndReturn(run func() error) *SessionMock_Regenerate_Call { - _c.Call.Return(run) - return _c -} - -// SessionID provides a mock function for the type SessionMock -func (_mock *SessionMock) SessionID() string { - ret := _mock.Called() - - if len(ret) == 0 { - panic("no return value specified for SessionID") - } - - var r0 string - if returnFunc, ok := ret.Get(0).(func() string); ok { - r0 = returnFunc() - } else { - r0 = ret.Get(0).(string) - } - return r0 -} - -// SessionMock_SessionID_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SessionID' -type SessionMock_SessionID_Call struct { - *mock.Call -} - -// SessionID is a helper method to define mock.On call -func (_e *SessionMock_Expecter) SessionID() *SessionMock_SessionID_Call { - return &SessionMock_SessionID_Call{Call: _e.mock.On("SessionID")} -} - -func (_c *SessionMock_SessionID_Call) Run(run func()) *SessionMock_SessionID_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *SessionMock_SessionID_Call) Return(s string) *SessionMock_SessionID_Call { - _c.Call.Return(s) - return _c -} - -func (_c *SessionMock_SessionID_Call) RunAndReturn(run func() string) *SessionMock_SessionID_Call { - _c.Call.Return(run) - return _c -} - // NewSessionDriverMock creates a new instance of SessionDriverMock. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. // The first argument is typically a *testing.T value. func NewSessionDriverMock(t interface { @@ -780,23 +96,23 @@ func (_c *SessionDriverMock_Delete_Call) RunAndReturn(run func(ctx context.Conte } // Get provides a mock function for the type SessionDriverMock -func (_mock *SessionDriverMock) Get(ctx context.Context, id string) (contract.Session, error) { +func (_mock *SessionDriverMock) Get(ctx context.Context, id string) (*contract.Session, error) { ret := _mock.Called(ctx, id) if len(ret) == 0 { panic("no return value specified for Get") } - var r0 contract.Session + var r0 *contract.Session var r1 error - if returnFunc, ok := ret.Get(0).(func(context.Context, string) (contract.Session, error)); ok { + if returnFunc, ok := ret.Get(0).(func(context.Context, string) (*contract.Session, error)); ok { return returnFunc(ctx, id) } - if returnFunc, ok := ret.Get(0).(func(context.Context, string) contract.Session); ok { + if returnFunc, ok := ret.Get(0).(func(context.Context, string) *contract.Session); ok { r0 = returnFunc(ctx, id) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(contract.Session) + r0 = ret.Get(0).(*contract.Session) } } if returnFunc, ok := ret.Get(1).(func(context.Context, string) error); ok { @@ -837,18 +153,18 @@ func (_c *SessionDriverMock_Get_Call) Run(run func(ctx context.Context, id strin return _c } -func (_c *SessionDriverMock_Get_Call) Return(session contract.Session, err error) *SessionDriverMock_Get_Call { +func (_c *SessionDriverMock_Get_Call) Return(session *contract.Session, err error) *SessionDriverMock_Get_Call { _c.Call.Return(session, err) return _c } -func (_c *SessionDriverMock_Get_Call) RunAndReturn(run func(ctx context.Context, id string) (contract.Session, error)) *SessionDriverMock_Get_Call { +func (_c *SessionDriverMock_Get_Call) RunAndReturn(run func(ctx context.Context, id string) (*contract.Session, error)) *SessionDriverMock_Get_Call { _c.Call.Return(run) return _c } // Save provides a mock function for the type SessionDriverMock -func (_mock *SessionDriverMock) Save(ctx context.Context, session contract.Session, ttl time.Duration) error { +func (_mock *SessionDriverMock) Save(ctx context.Context, session *contract.Session, ttl time.Duration) error { ret := _mock.Called(ctx, session, ttl) if len(ret) == 0 { @@ -856,7 +172,7 @@ func (_mock *SessionDriverMock) Save(ctx context.Context, session contract.Sessi } var r0 error - if returnFunc, ok := ret.Get(0).(func(context.Context, contract.Session, time.Duration) error); ok { + if returnFunc, ok := ret.Get(0).(func(context.Context, *contract.Session, time.Duration) error); ok { r0 = returnFunc(ctx, session, ttl) } else { r0 = ret.Error(0) @@ -871,21 +187,21 @@ type SessionDriverMock_Save_Call struct { // Save is a helper method to define mock.On call // - ctx context.Context -// - session contract.Session +// - session *contract.Session // - ttl time.Duration func (_e *SessionDriverMock_Expecter) Save(ctx interface{}, session interface{}, ttl interface{}) *SessionDriverMock_Save_Call { return &SessionDriverMock_Save_Call{Call: _e.mock.On("Save", ctx, session, ttl)} } -func (_c *SessionDriverMock_Save_Call) Run(run func(ctx context.Context, session contract.Session, ttl time.Duration)) *SessionDriverMock_Save_Call { +func (_c *SessionDriverMock_Save_Call) Run(run func(ctx context.Context, session *contract.Session, ttl time.Duration)) *SessionDriverMock_Save_Call { _c.Call.Run(func(args mock.Arguments) { var arg0 context.Context if args[0] != nil { arg0 = args[0].(context.Context) } - var arg1 contract.Session + var arg1 *contract.Session if args[1] != nil { - arg1 = args[1].(contract.Session) + arg1 = args[1].(*contract.Session) } var arg2 time.Duration if args[2] != nil { @@ -905,7 +221,7 @@ func (_c *SessionDriverMock_Save_Call) Return(err error) *SessionDriverMock_Save return _c } -func (_c *SessionDriverMock_Save_Call) RunAndReturn(run func(ctx context.Context, session contract.Session, ttl time.Duration) error) *SessionDriverMock_Save_Call { +func (_c *SessionDriverMock_Save_Call) RunAndReturn(run func(ctx context.Context, session *contract.Session, ttl time.Duration) error) *SessionDriverMock_Save_Call { _c.Call.Return(run) return _c } diff --git a/contract/request/session.go b/contract/request/session.go index 476b59f..d925a01 100644 --- a/contract/request/session.go +++ b/contract/request/session.go @@ -18,15 +18,15 @@ var ErrSessionNotFound = problem.Problem{ // Session retrieves the session from the request context using the // default [contract.SessionKey]. The boolean return value indicates // whether a session was found. -func Session(r *http.Request) (contract.Session, bool) { +func Session(r *http.Request) (*contract.Session, bool) { return SessionKeyed(r, contract.SessionKey) } // SessionKeyed retrieves the session from the request context using // the provided key. The boolean return value indicates whether a // session was found for that key. -func SessionKeyed(r *http.Request, key any) (contract.Session, bool) { - s, ok := r.Context().Value(key).(contract.Session) +func SessionKeyed(r *http.Request, key any) (*contract.Session, bool) { + s, ok := r.Context().Value(key).(*contract.Session) return s, ok } @@ -38,7 +38,7 @@ func SessionKeyed(r *http.Request, key any) (contract.Session, bool) { // WARNING: This function panics when the session is missing. Use // [SessionKeyed] for a non-panicking alternative, or ensure the // [framework.Recover] middleware is in place. -func MustSessionKeyed(r *http.Request, key any) contract.Session { +func MustSessionKeyed(r *http.Request, key any) *contract.Session { if s, ok := SessionKeyed(r, key); ok { return s } @@ -53,6 +53,6 @@ func MustSessionKeyed(r *http.Request, key any) contract.Session { // WARNING: This function panics when the session is missing. Use // [Session] for a non-panicking alternative, or ensure the // [framework.Recover] middleware is in place. -func MustSession(r *http.Request) contract.Session { +func MustSession(r *http.Request) *contract.Session { return MustSessionKeyed(r, contract.SessionKey) } diff --git a/contract/request/session_test.go b/contract/request/session_test.go index bb1fe29..8cd9c86 100644 --- a/contract/request/session_test.go +++ b/contract/request/session_test.go @@ -12,35 +12,18 @@ import ( "github.com/studiolambda/cosmos/contract/request" ) -// stubSession is a minimal implementation of contract.Session for testing. -type stubSession struct { - id string +func newTestSession(id string) *contract.Session { + return contract.NewSessionFrom(id, time.Now(), time.Now().Add(time.Hour), map[string]any{}) } -func (s stubSession) SessionID() string { return s.id } -func (s stubSession) OriginalSessionID() string { return s.id } -func (s stubSession) Get(string) (any, bool) { return nil, false } -func (s stubSession) Put(string, any) {} -func (s stubSession) Delete(string) {} -func (s stubSession) Extend(time.Time) {} -func (s stubSession) Regenerate() error { return nil } -func (s stubSession) Clear() {} -func (s stubSession) CreatedAt() time.Time { return time.Time{} } -func (s stubSession) ExpiresAt() time.Time { return time.Time{} } -func (s stubSession) HasExpired() bool { return false } -func (s stubSession) ExpiresSoon(time.Duration) bool { return false } -func (s stubSession) HasChanged() bool { return false } -func (s stubSession) HasRegenerated() bool { return false } -func (s stubSession) MarkAsUnchanged() {} - func TestSessionReturnsTrueWhenPresent(t *testing.T) { t.Parallel() - sess := stubSession{id: "sess-1"} + sess := newTestSession("sess-1") ctx := context.WithValue( context.Background(), contract.SessionKey, - contract.Session(sess), + sess, ) r := httptest.NewRequest( http.MethodGet, "/", nil, @@ -67,11 +50,11 @@ func TestSessionKeyedReturnsTrueWhenPresent(t *testing.T) { t.Parallel() type customKey struct{} - sess := stubSession{id: "sess-2"} + sess := newTestSession("sess-2") ctx := context.WithValue( context.Background(), customKey{}, - contract.Session(sess), + sess, ) r := httptest.NewRequest( http.MethodGet, "/", nil, @@ -116,11 +99,11 @@ func TestSessionKeyedReturnsFalseWhenWrongType(t *testing.T) { func TestMustSessionReturnsSessionWhenPresent(t *testing.T) { t.Parallel() - sess := stubSession{id: "sess-3"} + sess := newTestSession("sess-3") ctx := context.WithValue( context.Background(), contract.SessionKey, - contract.Session(sess), + sess, ) r := httptest.NewRequest( http.MethodGet, "/", nil, @@ -158,11 +141,11 @@ func TestMustSessionKeyedReturnsSessionWhenPresent(t *testing.T) { t.Parallel() type customKey struct{} - sess := stubSession{id: "sess-4"} + sess := newTestSession("sess-4") ctx := context.WithValue( context.Background(), customKey{}, - contract.Session(sess), + sess, ) r := httptest.NewRequest( http.MethodGet, "/", nil, diff --git a/contract/response/static.go b/contract/response/static.go index 821c190..da71d42 100644 --- a/contract/response/static.go +++ b/contract/response/static.go @@ -5,6 +5,7 @@ import ( "encoding/json" "encoding/xml" "errors" + "fmt" htmltemplate "html/template" "net/http" "net/url" @@ -12,6 +13,12 @@ import ( "text/template" ) +// ErrUnsafeRedirect is returned by [SafeRedirect] when the target +// URL is not a safe relative path. This prevents open redirect +// attacks where an attacker tricks users into visiting a malicious +// external site via your application's redirect endpoint. +var ErrUnsafeRedirect = errors.New("unsafe redirect URL") + // Raw writes raw byte data to the response writer with // the specified status code. If no Content-Type header has // been explicitly set on the response writer, it defaults @@ -206,12 +213,6 @@ func Redirect(w http.ResponseWriter, status int, url string) error { return Status(w, status) } -// ErrUnsafeRedirect is returned by [SafeRedirect] when the target -// URL is not a safe relative path. This prevents open redirect -// attacks where an attacker tricks users into visiting a malicious -// external site via your application's redirect endpoint. -var ErrUnsafeRedirect = errors.New("unsafe redirect URL: must be a relative path") - // SafeRedirect sends an HTTP redirect response only if the target // URL is a safe relative path (starts with "/" and does not contain // a scheme, host, or protocol-relative prefix "//"). This prevents @@ -226,7 +227,7 @@ var ErrUnsafeRedirect = errors.New("unsafe redirect URL: must be a relative path // - rawURL: The URL to redirect the user to (must be a relative path) func SafeRedirect(w http.ResponseWriter, status int, rawURL string) error { if !isRelativePath(rawURL) { - return ErrUnsafeRedirect + return fmt.Errorf("%w: `%s` must be a relative path", ErrUnsafeRedirect, rawURL) } return Redirect(w, status, rawURL) diff --git a/contract/session.go b/contract/session.go index 2fe0242..337a9c4 100644 --- a/contract/session.go +++ b/contract/session.go @@ -2,6 +2,9 @@ package contract import ( "context" + "crypto/rand" + "encoding/base64" + "sync" "time" ) @@ -11,105 +14,274 @@ type sessionKey struct{} // SessionKey is the context key used to store and retrieve the session from a context.Context. var SessionKey = sessionKey{} +// sessionIDLength is the number of random bytes used to generate +// a session ID. 32 bytes provides 256 bits of entropy. +const sessionIDLength = 32 + +// generateSessionID generates a cryptographically random session +// ID using crypto/rand and base64url encoding (43 characters). +func generateSessionID() (string, error) { + b := make([]byte, sessionIDLength) + + _, err := rand.Read(b) + + if err != nil { + return "", err + } + + return base64.RawURLEncoding.EncodeToString(b), nil +} + // Session represents a user session with data storage and lifecycle // management capabilities. It provides methods to store, retrieve, // and manage session data, as well as control session expiration -// and regeneration for security purposes. -type Session interface { - // SessionID returns the current session identifier. This may - // differ from the original session ID if the session has been - // regenerated. - SessionID() string - - // OriginalSessionID returns the session ID that was originally - // assigned to this session. This remains constant even if the +// and regeneration for security purposes. Access is protected by +// a mutex to ensure thread-safe operations. +type Session struct { + // originalID is the session ID assigned when the session was + // first created. This value remains constant even if the // session is regenerated. - OriginalSessionID() string - - // Get retrieves a value from the session by key. It returns - // the value and a boolean indicating whether the key exists - // in the session. The value can be of any type. - Get(key string) (any, bool) - - // Put stores a value in the session associated with the given - // key. If the key already exists, its value is overwritten. - // - // WARNING: When storing authentication-related state, callers - // MUST call Regenerate immediately after to prevent session - // fixation attacks. - Put(key string, value any) - - // Delete removes the value associated with the given key from - // the session. If the key does not exist, this is a no-op. - Delete(key string) - - // Extend updates the session's expiration time to the specified - // time. This is useful for extending a session's lifetime - // during active use. - Extend(expiresAt time.Time) - - // Regenerate creates a new session ID and associates it with - // this session. This is commonly used after authentication to - // prevent session fixation attacks. It returns an error if - // the regeneration process fails. - // - // WARNING: This method MUST be called after any authentication - // state change (login, logout, privilege escalation). - Regenerate() error - - // Clear removes all data from the session while maintaining - // the session itself. - Clear() - - // CreatedAt returns the absolute time the session was first - // created. This value never changes, even if the session is - // regenerated or extended. - CreatedAt() time.Time - - // ExpiresAt returns the time at which the session will expire. - ExpiresAt() time.Time - - // HasExpired returns true if the current time is past the - // session's expiration time. - HasExpired() bool - - // ExpiresSoon returns true if the session will expire within - // the specified duration from the current time. This is useful - // for triggering session renewal prompts. - ExpiresSoon(delta time.Duration) bool - - // HasChanged returns true if the session data has been modified - // since it was loaded. This is useful for determining whether - // the session needs to be persisted. - HasChanged() bool - - // HasRegenerated returns true if the session has been - // regenerated (i.e., the session ID has changed). This is - // useful for sending updated session identifiers to the - // client. - HasRegenerated() bool - - // MarkAsUnchanged sets the session as if nothing has changed, - // therefore avoiding saving the session when the request - // finishes. - MarkAsUnchanged() -} - -// SessionDriver defines the interface for persisting and retrieving session data. -// Implementations of SessionDriver are responsible for managing session storage, -// such as storing sessions in a database, cache, or file system. + originalID string + + // id is the current session identifier. This may differ from + // originalID if the session has been regenerated. + id string + + // createdAt records the absolute time the session was first created. + createdAt time.Time + + // expiresAt is the time at which the session will expire. + expiresAt time.Time + + // storage holds the session data as key-value pairs. + storage map[string]any + + // mutex protects concurrent access to the session fields. + mutex sync.Mutex + + // changed tracks whether the session data has been modified. + changed bool +} + +// NewSession creates a new session with the specified expiration +// time and initial storage data. It generates a cryptographically +// random session ID. The session is marked as changed to ensure it +// is persisted on first save. Returns an error if ID generation fails. +func NewSession(expiresAt time.Time, storage map[string]any) (*Session, error) { + id, err := generateSessionID() + + if err != nil { + return nil, err + } + + return &Session{ + originalID: id, + id: id, + createdAt: time.Now(), + expiresAt: expiresAt, + storage: storage, + changed: true, + }, nil +} + +// NewSessionFrom reconstructs a session from persisted data. Unlike +// [NewSession], it does not generate a new ID or mark the session as +// changed. This is used by session drivers when loading from storage. +func NewSessionFrom(id string, createdAt time.Time, expiresAt time.Time, storage map[string]any) *Session { + return &Session{ + originalID: id, + id: id, + createdAt: createdAt, + expiresAt: expiresAt, + storage: storage, + changed: false, + } +} + +// All returns a copy of all session data. This is used by session +// drivers when serializing the session for storage. +func (session *Session) All() map[string]any { + session.mutex.Lock() + defer session.mutex.Unlock() + + result := make(map[string]any, len(session.storage)) + + for k, v := range session.storage { + result[k] = v + } + + return result +} + +// SessionID returns the current session identifier. This may differ +// from the original session ID if the session has been regenerated. +func (session *Session) SessionID() string { + session.mutex.Lock() + defer session.mutex.Unlock() + + return session.id +} + +// OriginalSessionID returns the session identifier that was assigned +// when the session was initially created. +func (session *Session) OriginalSessionID() string { + session.mutex.Lock() + defer session.mutex.Unlock() + + return session.originalID +} + +// Get retrieves a value from the session storage by key. It returns +// the value and a boolean indicating whether the key exists. +func (session *Session) Get(key string) (any, bool) { + session.mutex.Lock() + defer session.mutex.Unlock() + + value, ok := session.storage[key] + + return value, ok +} + +// Put stores a value in the session associated with the given key. +// This operation marks the session as changed. +// +// WARNING: When storing authentication-related state, callers MUST +// call [Session.Regenerate] immediately after to prevent session +// fixation attacks. +func (session *Session) Put(key string, value any) { + session.mutex.Lock() + defer session.mutex.Unlock() + + session.storage[key] = value + session.changed = true +} + +// Delete removes a value from the session storage by key. +// This operation marks the session as changed. +func (session *Session) Delete(key string) { + session.mutex.Lock() + defer session.mutex.Unlock() + + delete(session.storage, key) + session.changed = true +} + +// Extend updates the session's expiration time. This operation +// marks the session as changed. +func (session *Session) Extend(expiresAt time.Time) { + session.mutex.Lock() + defer session.mutex.Unlock() + + session.expiresAt = expiresAt + session.changed = true +} + +// Regenerate generates a new cryptographically random session ID. +// The original session ID is preserved for cleanup. This operation +// marks the session as changed. +// +// WARNING: This method MUST be called after any authentication +// state change (login, logout, privilege escalation). +func (session *Session) Regenerate() error { + id, err := generateSessionID() + + if err != nil { + return err + } + + session.mutex.Lock() + defer session.mutex.Unlock() + + session.id = id + session.changed = true + + return nil +} + +// Clear removes all data from the session while maintaining the +// session itself. This operation marks the session as changed. +func (session *Session) Clear() { + session.mutex.Lock() + defer session.mutex.Unlock() + + clear(session.storage) + session.changed = true +} + +// CreatedAt returns the absolute time the session was first created. +func (session *Session) CreatedAt() time.Time { + session.mutex.Lock() + defer session.mutex.Unlock() + + return session.createdAt +} + +// ExpiresAt returns the time at which the session will expire. +func (session *Session) ExpiresAt() time.Time { + session.mutex.Lock() + defer session.mutex.Unlock() + + return session.expiresAt +} + +// HasExpired returns true if the current time is past the session's +// expiration time. +func (session *Session) HasExpired() bool { + session.mutex.Lock() + defer session.mutex.Unlock() + + return time.Now().After(session.expiresAt) +} + +// ExpiresSoon returns true if the session will expire within the +// specified duration from the current time. +func (session *Session) ExpiresSoon(delta time.Duration) bool { + session.mutex.Lock() + defer session.mutex.Unlock() + + now := time.Now() + warningTime := session.expiresAt.Add(-delta) + + return now.After(warningTime) && now.Before(session.expiresAt) +} + +// HasChanged returns true if the session data has been modified +// since it was loaded. +func (session *Session) HasChanged() bool { + session.mutex.Lock() + defer session.mutex.Unlock() + + return session.changed +} + +// HasRegenerated returns true if the session has been regenerated +// (i.e., the session ID has changed). +func (session *Session) HasRegenerated() bool { + session.mutex.Lock() + defer session.mutex.Unlock() + + return session.id != session.originalID +} + +// MarkAsUnchanged resets the change tracking flag, preventing +// the session from being persisted on the current request. +func (session *Session) MarkAsUnchanged() { + session.mutex.Lock() + defer session.mutex.Unlock() + + session.changed = false +} + +// SessionDriver defines the interface for persisting and retrieving +// session data. Implementations manage session storage in backends +// such as databases, caches, or file systems. type SessionDriver interface { - // Get retrieves a session from persistent storage by its ID. It returns the session - // and an error if the session cannot be found or if retrieval fails. If a session - // with the given ID does not exist, an error should be returned. - Get(ctx context.Context, id string) (Session, error) - - // Save persists a session to storage with the specified time-to-live (TTL). - // The TTL parameter indicates how long the session should be retained in storage - // before it can be automatically removed. It returns an error if the save operation fails. - Save(ctx context.Context, session Session, ttl time.Duration) error - - // Delete removes a session from persistent storage by its ID. It returns an error - // if the delete operation fails. + // Get retrieves a session from persistent storage by its ID. + Get(ctx context.Context, id string) (*Session, error) + + // Save persists a session to storage with the specified TTL. + Save(ctx context.Context, session *Session, ttl time.Duration) error + + // Delete removes a session from persistent storage by its ID. Delete(ctx context.Context, id string) error } diff --git a/framework/cache/memory.go b/framework/cache/memory.go index 490391b..fe5039c 100644 --- a/framework/cache/memory.go +++ b/framework/cache/memory.go @@ -2,7 +2,6 @@ package cache import ( "context" - "errors" "time" "github.com/studiolambda/cosmos/contract" @@ -10,9 +9,8 @@ import ( "github.com/patrickmn/go-cache" ) -// Memory implements [contract.Cache] using an in-memory store backed by -// patrickmn/go-cache. Note that this dependency is unmaintained since 2017; -// consider migrating to a maintained alternative for production use. +// Memory implements [contract.CacheDriver] and [contract.CacheCounter] +// using an in-memory store backed by patrickmn/go-cache. // // Memory is suitable for single-process applications and testing scenarios // where persistence across restarts is not required. @@ -29,27 +27,28 @@ func NewMemory(expiration time.Duration, cleanup time.Duration) *Memory { } } -// Get retrieves the value for the given key from the in-memory -// store. Returns contract.ErrCacheKeyNotFound when the key does -// not exist or has expired. The key is intentionally omitted from -// the error to prevent cache key enumeration attacks. -func (memory *Memory) Get(_ context.Context, key string) (any, error) { +// Get retrieves the raw bytes for the given key from the in-memory +// store. Returns [contract.ErrCacheKeyNotFound] when the key does +// not exist or has expired. +func (memory *Memory) Get(_ context.Context, key string) ([]byte, error) { val, found := memory.store.Get(key) if !found { return nil, contract.ErrCacheKeyNotFound } - return val, nil + raw, ok := val.([]byte) + + if !ok { + return nil, contract.ErrCacheKeyNotFound + } + + return raw, nil } -// Put stores a value in the in-memory cache with the given TTL. +// Put stores raw bytes in the in-memory cache with the given TTL. // A zero TTL uses the default expiration configured at creation. -// -// WARNING: Values are stored by reference. Callers must not mutate -// values after storing or after retrieval. For safety with mutable -// types, store copies or use value types. -func (memory *Memory) Put(_ context.Context, key string, value any, ttl time.Duration) error { +func (memory *Memory) Put(_ context.Context, key string, value []byte, ttl time.Duration) error { memory.store.Set(key, value, ttl) return nil @@ -70,39 +69,18 @@ func (memory *Memory) Has(_ context.Context, key string) (bool, error) { return found, nil } -// Pull retrieves and removes the value for the given key. -// Pull is not atomic; under concurrent access another caller may -// retrieve the same value before it is deleted. -func (memory *Memory) Pull(ctx context.Context, key string) (any, error) { - val, err := memory.Get(ctx, key) - - if err != nil { - return nil, err - } - - if err := memory.Delete(ctx, key); err != nil { - return nil, err - } - - return val, nil -} - -// Forever stores a value permanently with no expiration. -// -// WARNING: Values are stored by reference. Callers must not mutate -// values after storing or after retrieval. For safety with mutable -// types, store copies or use value types. -func (memory *Memory) Forever(_ context.Context, key string, value any) error { - memory.store.Set(key, value, cache.NoExpiration) - - return nil +// Store returns the underlying go-cache instance. This is primarily +// useful for testing scenarios where direct access to the store is +// needed, such as storing int64 values for increment/decrement tests. +func (memory *Memory) Store() *cache.Cache { + return memory.store } // Increment atomically increases the integer value stored at key by // the given amount. Returns [contract.ErrCacheKeyNotFound] if the key // does not exist. -func (memory *Memory) Increment(_ context.Context, key string, by int64) (int64, error) { - result, err := memory.store.IncrementInt64(key, by) +func (memory *Memory) Increment(_ context.Context, key string, delta int64) (int64, error) { + result, err := memory.store.IncrementInt64(key, delta) if err != nil { return 0, contract.ErrCacheKeyNotFound @@ -114,8 +92,8 @@ func (memory *Memory) Increment(_ context.Context, key string, by int64) (int64, // Decrement atomically decreases the integer value stored at key by // the given amount. Returns [contract.ErrCacheKeyNotFound] if the key // does not exist. -func (memory *Memory) Decrement(_ context.Context, key string, by int64) (int64, error) { - result, err := memory.store.DecrementInt64(key, by) +func (memory *Memory) Decrement(_ context.Context, key string, delta int64) (int64, error) { + result, err := memory.store.DecrementInt64(key, delta) if err != nil { return 0, contract.ErrCacheKeyNotFound @@ -123,55 +101,3 @@ func (memory *Memory) Decrement(_ context.Context, key string, by int64) (int64, return result, nil } - -// Remember retrieves the cached value for the given key, or -// computes and stores it if the key is not found. The compute -// function is only called on a cache miss. -// -// WARNING: This method is not protected against thundering herd -// (cache stampede). Under high concurrency, multiple goroutines -// may observe a cache miss simultaneously and all invoke the -// compute function. For expensive computations, callers should -// use golang.org/x/sync/singleflight to deduplicate concurrent -// calls for the same key. -func (memory *Memory) Remember(ctx context.Context, key string, ttl time.Duration, compute func() (any, error)) (any, error) { - value, err := memory.Get(ctx, key) - - if err == nil { - return value, nil - } - - if !errors.Is(err, contract.ErrCacheKeyNotFound) { - return nil, err - } - - value, err = compute() - - if err != nil { - return nil, err - } - - return value, memory.Put(ctx, key, value, ttl) -} - -// RememberForever retrieves the cached value for the given key, or -// computes and stores it permanently if the key is not found. -func (memory *Memory) RememberForever(ctx context.Context, key string, compute func() (any, error)) (any, error) { - value, err := memory.Get(ctx, key) - - if err == nil { - return value, nil - } - - if !errors.Is(err, contract.ErrCacheKeyNotFound) { - return nil, err - } - - value, err = compute() - - if err != nil { - return nil, err - } - - return value, memory.Forever(ctx, key, value) -} diff --git a/framework/cache/memory_test.go b/framework/cache/memory_test.go index b9b3062..a338c8b 100644 --- a/framework/cache/memory_test.go +++ b/framework/cache/memory_test.go @@ -2,7 +2,6 @@ package cache_test import ( "context" - "errors" "testing" "time" @@ -18,13 +17,13 @@ func TestMemoryGetReturnsStoredValue(t *testing.T) { ctx := context.Background() mem := cache.NewMemory(5*time.Minute, 10*time.Minute) - err := mem.Put(ctx, "key", "value", 5*time.Minute) + err := mem.Put(ctx, "key", []byte("value"), 5*time.Minute) require.NoError(t, err) val, err := mem.Get(ctx, "key") require.NoError(t, err) - require.Equal(t, "value", val) + require.Equal(t, []byte("value"), val) } func TestMemoryGetReturnsNotFoundForMissingKey(t *testing.T) { @@ -44,16 +43,16 @@ func TestMemoryPutOverwritesExistingValue(t *testing.T) { ctx := context.Background() mem := cache.NewMemory(5*time.Minute, 10*time.Minute) - err := mem.Put(ctx, "key", "old", 5*time.Minute) + err := mem.Put(ctx, "key", []byte("old"), 5*time.Minute) require.NoError(t, err) - err = mem.Put(ctx, "key", "new", 5*time.Minute) + err = mem.Put(ctx, "key", []byte("new"), 5*time.Minute) require.NoError(t, err) val, err := mem.Get(ctx, "key") require.NoError(t, err) - require.Equal(t, "new", val) + require.Equal(t, []byte("new"), val) } func TestMemoryDeleteRemovesKey(t *testing.T) { @@ -62,7 +61,7 @@ func TestMemoryDeleteRemovesKey(t *testing.T) { ctx := context.Background() mem := cache.NewMemory(5*time.Minute, 10*time.Minute) - err := mem.Put(ctx, "key", "value", 5*time.Minute) + err := mem.Put(ctx, "key", []byte("value"), 5*time.Minute) require.NoError(t, err) err = mem.Delete(ctx, "key") @@ -90,7 +89,7 @@ func TestMemoryHasReturnsTrueForExistingKey(t *testing.T) { ctx := context.Background() mem := cache.NewMemory(5*time.Minute, 10*time.Minute) - err := mem.Put(ctx, "key", "value", 5*time.Minute) + err := mem.Put(ctx, "key", []byte("value"), 5*time.Minute) require.NoError(t, err) found, err := mem.Has(ctx, "key") @@ -111,59 +110,14 @@ func TestMemoryHasReturnsFalseForMissingKey(t *testing.T) { require.False(t, found) } -func TestMemoryPullReturnsAndRemovesValue(t *testing.T) { - t.Parallel() - - ctx := context.Background() - mem := cache.NewMemory(5*time.Minute, 10*time.Minute) - - err := mem.Put(ctx, "key", "value", 5*time.Minute) - require.NoError(t, err) - - val, err := mem.Pull(ctx, "key") - - require.NoError(t, err) - require.Equal(t, "value", val) - - _, err = mem.Get(ctx, "key") - - require.ErrorIs(t, err, contract.ErrCacheKeyNotFound) -} - -func TestMemoryPullReturnsErrorForMissingKey(t *testing.T) { - t.Parallel() - - ctx := context.Background() - mem := cache.NewMemory(5*time.Minute, 10*time.Minute) - - _, err := mem.Pull(ctx, "missing") - - require.ErrorIs(t, err, contract.ErrCacheKeyNotFound) -} - -func TestMemoryForeverStoresPermanently(t *testing.T) { - t.Parallel() - - ctx := context.Background() - mem := cache.NewMemory(5*time.Minute, 10*time.Minute) - - err := mem.Forever(ctx, "key", "permanent") - require.NoError(t, err) - - val, err := mem.Get(ctx, "key") - - require.NoError(t, err) - require.Equal(t, "permanent", val) -} - func TestMemoryIncrementIncreasesValue(t *testing.T) { t.Parallel() ctx := context.Background() mem := cache.NewMemory(5*time.Minute, 10*time.Minute) - err := mem.Put(ctx, "counter", int64(10), 5*time.Minute) - require.NoError(t, err) + // go-cache Increment requires the value to be stored as int64 directly + mem.Store().Set("counter", int64(10), 5*time.Minute) result, err := mem.Increment(ctx, "counter", 5) @@ -188,8 +142,7 @@ func TestMemoryDecrementDecreasesValue(t *testing.T) { ctx := context.Background() mem := cache.NewMemory(5*time.Minute, 10*time.Minute) - err := mem.Put(ctx, "counter", int64(10), 5*time.Minute) - require.NoError(t, err) + mem.Store().Set("counter", int64(10), 5*time.Minute) result, err := mem.Decrement(ctx, "counter", 3) @@ -208,276 +161,126 @@ func TestMemoryDecrementReturnsErrorForMissingKey(t *testing.T) { require.ErrorIs(t, err, contract.ErrCacheKeyNotFound) } -func TestMemoryRememberReturnsCachedValue(t *testing.T) { +func TestMemoryPutZeroTTLUsesDefault(t *testing.T) { t.Parallel() ctx := context.Background() mem := cache.NewMemory(5*time.Minute, 10*time.Minute) - err := mem.Put(ctx, "key", "cached", 5*time.Minute) - require.NoError(t, err) - - called := false - - val, err := mem.Remember( - ctx, "key", 5*time.Minute, func() (any, error) { - called = true - return "computed", nil - }, - ) - + err := mem.Put(ctx, "key", []byte("value"), 0) require.NoError(t, err) - require.Equal(t, "cached", val) - require.False(t, called) -} - -func TestMemoryRememberComputesOnMiss(t *testing.T) { - t.Parallel() - - ctx := context.Background() - mem := cache.NewMemory(5*time.Minute, 10*time.Minute) - - val, err := mem.Remember( - ctx, "key", 5*time.Minute, func() (any, error) { - return "computed", nil - }, - ) - - require.NoError(t, err) - require.Equal(t, "computed", val) - - val, err = mem.Get(ctx, "key") - - require.NoError(t, err) - require.Equal(t, "computed", val) -} - -func TestMemoryRememberReturnsComputeError(t *testing.T) { - t.Parallel() - - ctx := context.Background() - mem := cache.NewMemory(5*time.Minute, 10*time.Minute) - computeErr := errors.New("compute failed") - - _, err := mem.Remember( - ctx, "key", 5*time.Minute, func() (any, error) { - return nil, computeErr - }, - ) - - require.ErrorIs(t, err, computeErr) -} - -func TestMemoryRememberForeverReturnsCachedValue(t *testing.T) { - t.Parallel() - ctx := context.Background() - mem := cache.NewMemory(5*time.Minute, 10*time.Minute) - - err := mem.Forever(ctx, "key", "cached") - require.NoError(t, err) - - called := false - - val, err := mem.RememberForever( - ctx, "key", func() (any, error) { - called = true - return "computed", nil - }, - ) + val, err := mem.Get(ctx, "key") require.NoError(t, err) - require.Equal(t, "cached", val) - require.False(t, called) + require.Equal(t, []byte("value"), val) } -func TestMemoryRememberForeverComputesOnMiss(t *testing.T) { +func TestCacheWrapperGetDecodesJSON(t *testing.T) { t.Parallel() ctx := context.Background() mem := cache.NewMemory(5*time.Minute, 10*time.Minute) + c := contract.NewCache(mem) - val, err := mem.RememberForever( - ctx, "key", func() (any, error) { - return "computed", nil - }, - ) - + err := c.Put(ctx, "key", "hello", 5*time.Minute) require.NoError(t, err) - require.Equal(t, "computed", val) - val, err = mem.Get(ctx, "key") + var result string + err = c.Get(ctx, "key", &result) require.NoError(t, err) - require.Equal(t, "computed", val) + require.Equal(t, "hello", result) } -func TestMemoryRememberForeverReturnsComputeError(t *testing.T) { +func TestCacheWrapperPullReturnsAndRemoves(t *testing.T) { t.Parallel() ctx := context.Background() mem := cache.NewMemory(5*time.Minute, 10*time.Minute) - computeErr := errors.New("compute failed") - - _, err := mem.RememberForever( - ctx, "key", func() (any, error) { - return nil, computeErr - }, - ) - - require.ErrorIs(t, err, computeErr) -} + c := contract.NewCache(mem) -func TestMemoryPutStoresVariousTypes(t *testing.T) { - t.Parallel() - - ctx := context.Background() - mem := cache.NewMemory(5*time.Minute, 10*time.Minute) - - err := mem.Put(ctx, "int", 42, 5*time.Minute) - require.NoError(t, err) - - err = mem.Put(ctx, "bool", true, 5*time.Minute) - require.NoError(t, err) - - err = mem.Put( - ctx, "slice", []string{"a", "b"}, 5*time.Minute, - ) + err := c.Put(ctx, "key", "value", 5*time.Minute) require.NoError(t, err) - intVal, err := mem.Get(ctx, "int") - require.NoError(t, err) - require.Equal(t, 42, intVal) + var result string + err = c.Pull(ctx, "key", &result) - boolVal, err := mem.Get(ctx, "bool") require.NoError(t, err) - require.Equal(t, true, boolVal) + require.Equal(t, "value", result) - sliceVal, err := mem.Get(ctx, "slice") - require.NoError(t, err) - require.Equal(t, []string{"a", "b"}, sliceVal) + err = c.Get(ctx, "key", &result) + require.ErrorIs(t, err, contract.ErrCacheKeyNotFound) } -func TestMemoryIncrementWithoutExternalMutex(t *testing.T) { +func TestCacheWrapperForever(t *testing.T) { t.Parallel() ctx := context.Background() mem := cache.NewMemory(5*time.Minute, 10*time.Minute) + c := contract.NewCache(mem) - err := mem.Put(ctx, "counter", int64(0), 5*time.Minute) + err := c.Forever(ctx, "key", "permanent") require.NoError(t, err) - result, err := mem.Increment(ctx, "counter", 10) + var result string + err = c.Get(ctx, "key", &result) require.NoError(t, err) - require.Equal(t, int64(10), result) - - result, err = mem.Increment(ctx, "counter", 5) - - require.NoError(t, err) - require.Equal(t, int64(15), result) - - val, err := mem.Get(ctx, "counter") - - require.NoError(t, err) - require.Equal(t, int64(15), val) + require.Equal(t, "permanent", result) } -func TestMemoryDecrementWithoutExternalMutex(t *testing.T) { +func TestCacheWrapperRememberReturnsCachedValue(t *testing.T) { t.Parallel() ctx := context.Background() mem := cache.NewMemory(5*time.Minute, 10*time.Minute) + c := contract.NewCache(mem) - err := mem.Put(ctx, "counter", int64(20), 5*time.Minute) - require.NoError(t, err) - - result, err := mem.Decrement(ctx, "counter", 7) - - require.NoError(t, err) - require.Equal(t, int64(13), result) - - result, err = mem.Decrement(ctx, "counter", 3) - + err := c.Put(ctx, "key", "cached", 5*time.Minute) require.NoError(t, err) - require.Equal(t, int64(10), result) - - val, err := mem.Get(ctx, "counter") - - require.NoError(t, err) - require.Equal(t, int64(10), val) -} -func TestMemoryRememberDistinguishesKeyNotFoundFromOtherErrors(t *testing.T) { - t.Parallel() - - ctx := context.Background() - mem := cache.NewMemory(5*time.Minute, 10*time.Minute) - - // When key is missing, compute is called and result is stored. - val, err := mem.Remember( - ctx, "key", 5*time.Minute, func() (any, error) { - return "computed", nil - }, - ) - - require.NoError(t, err) - require.Equal(t, "computed", val) + called := false + var result string - // Verify the value was stored. - stored, err := mem.Get(ctx, "key") + err = c.Remember(ctx, "key", 5*time.Minute, &result, func() (any, error) { + called = true + return "computed", nil + }) require.NoError(t, err) - require.Equal(t, "computed", stored) + require.Equal(t, "cached", result) + require.False(t, called) } -func TestMemoryRememberForeverDistinguishesKeyNotFoundFromOtherErrors(t *testing.T) { +func TestCacheWrapperRememberComputesOnMiss(t *testing.T) { t.Parallel() ctx := context.Background() mem := cache.NewMemory(5*time.Minute, 10*time.Minute) + c := contract.NewCache(mem) - // When key is missing, compute is called and result is stored permanently. - val, err := mem.RememberForever( - ctx, "key", func() (any, error) { - return "computed", nil - }, - ) - - require.NoError(t, err) - require.Equal(t, "computed", val) + var result string - // Verify the value was stored. - stored, err := mem.Get(ctx, "key") + err := c.Remember(ctx, "key", 5*time.Minute, &result, func() (any, error) { + return "computed", nil + }) require.NoError(t, err) - require.Equal(t, "computed", stored) + require.Equal(t, "computed", result) } -func TestMemoryIncrementReturnsErrorForNonIntegerValue(t *testing.T) { +func TestCacheWrapperIncrementDelegatesToDriver(t *testing.T) { t.Parallel() ctx := context.Background() mem := cache.NewMemory(5*time.Minute, 10*time.Minute) + c := contract.NewCache(mem) - err := mem.Put(ctx, "key", "not-a-number", 5*time.Minute) - require.NoError(t, err) - - _, err = mem.Increment(ctx, "key", 1) - - require.ErrorIs(t, err, contract.ErrCacheKeyNotFound) -} - -func TestMemoryDecrementReturnsErrorForNonIntegerValue(t *testing.T) { - t.Parallel() + mem.Store().Set("counter", int64(10), 5*time.Minute) - ctx := context.Background() - mem := cache.NewMemory(5*time.Minute, 10*time.Minute) + result, err := c.Increment(ctx, "counter", 5) - err := mem.Put(ctx, "key", "not-a-number", 5*time.Minute) require.NoError(t, err) - - _, err = mem.Decrement(ctx, "key", 1) - - require.ErrorIs(t, err, contract.ErrCacheKeyNotFound) + require.Equal(t, int64(15), result) } diff --git a/framework/cache/redis.go b/framework/cache/redis.go index 4b46e70..f62ca9b 100644 --- a/framework/cache/redis.go +++ b/framework/cache/redis.go @@ -15,9 +15,8 @@ import ( // of the go-redis package. type RedisOptions = redis.Options -// RedisClient implements contract.Cache using Redis as the backing -// store. It is defined as a type conversion of redis.Client so that -// cache methods can be attached without wrapping in a separate struct. +// RedisClient implements [contract.CacheDriver] and [contract.CacheCounter] +// using Redis as the backing store. type RedisClient redis.Client // NewRedis creates a RedisClient from the given connection options. @@ -31,12 +30,10 @@ func NewRedisFrom(client *redis.Client) *RedisClient { return (*RedisClient)(client) } -// Get retrieves a value by key. Returns -// contract.ErrCacheKeyNotFound when the key does not exist. -// The key is intentionally omitted from the error to prevent -// cache key enumeration attacks. -func (client *RedisClient) Get(ctx context.Context, key string) (any, error) { - value, err := (*redis.Client)(client).Get(ctx, key).Result() +// Get retrieves raw bytes by key. Returns +// [contract.ErrCacheKeyNotFound] when the key does not exist. +func (client *RedisClient) Get(ctx context.Context, key string) ([]byte, error) { + value, err := (*redis.Client)(client).Get(ctx, key).Bytes() if errors.Is(err, redis.Nil) { return nil, contract.ErrCacheKeyNotFound @@ -49,9 +46,9 @@ func (client *RedisClient) Get(ctx context.Context, key string) (any, error) { return value, nil } -// Put stores a value with the given TTL. A zero TTL means the key +// Put stores raw bytes with the given TTL. A zero TTL means the key // will not expire. -func (client *RedisClient) Put(ctx context.Context, key string, value any, ttl time.Duration) error { +func (client *RedisClient) Put(ctx context.Context, key string, value []byte, ttl time.Duration) error { return (*redis.Client)(client).Set(ctx, key, value, ttl).Err() } @@ -71,80 +68,16 @@ func (client *RedisClient) Has(ctx context.Context, key string) (bool, error) { return count > 0, nil } -// Pull atomically retrieves and deletes a key using Redis GETDEL. -// Returns [contract.ErrCacheKeyNotFound] when the key does not exist. -func (client *RedisClient) Pull(ctx context.Context, key string) (any, error) { - value, err := (*redis.Client)(client).GetDel(ctx, key).Result() - - if errors.Is(err, redis.Nil) { - return nil, contract.ErrCacheKeyNotFound - } - - if err != nil { - return nil, err - } - - return value, nil -} - -// Forever stores a value with no expiration. Values are serialized for storage. -func (client *RedisClient) Forever(ctx context.Context, key string, value any) error { - return client.Put(ctx, key, value, 0) -} - // Increment atomically increases the integer value stored at key by -// the given amount. Unlike the in-memory implementation, Redis -// auto-creates the key with value 0 if it does not exist before -// incrementing. -func (client *RedisClient) Increment(ctx context.Context, key string, by int64) (int64, error) { - return (*redis.Client)(client).IncrBy(ctx, key, by).Result() +// the given amount. Redis auto-creates the key with value 0 if it +// does not exist before incrementing. +func (client *RedisClient) Increment(ctx context.Context, key string, delta int64) (int64, error) { + return (*redis.Client)(client).IncrBy(ctx, key, delta).Result() } // Decrement atomically decreases the integer value stored at key by -// the given amount. Unlike the in-memory implementation, Redis -// auto-creates the key with value 0 if it does not exist before -// decrementing. -func (client *RedisClient) Decrement(ctx context.Context, key string, by int64) (int64, error) { - return (*redis.Client)(client).DecrBy(ctx, key, by).Result() -} - -// Remember retrieves the cached value for key, or computes and -// stores it with the given TTL on a cache miss. Non-"key not -// found" errors from Get are returned immediately without calling -// compute. -// -// WARNING: This method is not protected against thundering herd -// (cache stampede). Under high concurrency, multiple goroutines -// may observe a cache miss simultaneously and all invoke the -// compute function. For expensive computations, callers should -// use golang.org/x/sync/singleflight to deduplicate concurrent -// calls for the same key. -func (client *RedisClient) Remember(ctx context.Context, key string, ttl time.Duration, compute func() (any, error)) (any, error) { - val, err := client.Get(ctx, key) - - if err == nil { - return val, nil - } - - if !errors.Is(err, contract.ErrCacheKeyNotFound) { - return nil, err - } - - val, err = compute() - - if err != nil { - return nil, err - } - - if err := client.Put(ctx, key, val, ttl); err != nil { - return nil, err - } - - return val, nil -} - -// RememberForever retrieves the cached value for key, or computes -// and stores it permanently on a cache miss. -func (client *RedisClient) RememberForever(ctx context.Context, key string, compute func() (any, error)) (any, error) { - return client.Remember(ctx, key, 0, compute) +// the given amount. Redis auto-creates the key with value 0 if it +// does not exist before decrementing. +func (client *RedisClient) Decrement(ctx context.Context, key string, delta int64) (int64, error) { + return (*redis.Client)(client).DecrBy(ctx, key, delta).Result() } diff --git a/framework/database/sql.go b/framework/database/sql.go index 907388f..e0a4866 100644 --- a/framework/database/sql.go +++ b/framework/database/sql.go @@ -10,7 +10,7 @@ import ( "github.com/jmoiron/sqlx" ) -// SQL implements contract.Database using sqlx for query execution, +// SQL implements [contract.DatabaseDriver] using sqlx for query execution, // named parameters, and struct scanning. It supports both direct // connections and transactions through the shared [sqlx.ExtContext] // interface. @@ -38,10 +38,7 @@ func (tx *sqlTx) Close() error { // // WARNING: No default query timeout is applied. Long-running or // runaway queries will block indefinitely unless the caller -// passes a context.Context with a deadline or timeout. For -// production use, configure statement timeouts at the database -// driver level (e.g., statement_timeout for PostgreSQL) or wrap -// all query contexts with context.WithTimeout. +// passes a context.Context with a deadline or timeout. // // WARNING: The default connection pool has no limits on open connections. // Use [SQL.Configure] to set appropriate pool limits for production: @@ -62,32 +59,17 @@ func NewSQL(driver string, dsn string) (*SQL, error) { } // NewSQLFrom wraps an existing sqlx.DB connection in a SQL instance. -// This is useful when you need to configure the connection pool or -// driver options before handing it to the framework. -// -// WARNING: No default query timeout is applied. See [NewSQL] for -// recommendations on configuring query timeouts. -// -// WARNING: The default connection pool has no limits on open connections. -// Use [SQL.Configure] to set appropriate pool limits for production: -// -// db.Configure(func(raw *sql.DB) { -// raw.SetMaxOpenConns(25) -// raw.SetMaxIdleConns(5) -// raw.SetConnMaxLifetime(5 * time.Minute) -// }) func NewSQLFrom(db *sqlx.DB) *SQL { return &SQL{db: db, raw: db} } -// Ping verifies that the database connection is still alive, -// returning an error if the check fails. +// Ping verifies that the database connection is still alive. func (database *SQL) Ping(ctx context.Context) error { return database.raw.PingContext(ctx) } // Exec executes a query that modifies data (INSERT, UPDATE, DELETE) -// using positional arguments. It returns the number of rows affected. +// using positional arguments. Returns the number of rows affected. func (database *SQL) Exec(ctx context.Context, query string, args ...any) (int64, error) { result, err := database.db.ExecContext(ctx, query, args...) @@ -99,7 +81,7 @@ func (database *SQL) Exec(ctx context.Context, query string, args ...any) (int64 } // ExecNamed executes a query that modifies data using a named -// parameter struct or map. It returns the number of rows affected. +// parameter struct or map. Returns the number of rows affected. func (database *SQL) ExecNamed(ctx context.Context, query string, arg any) (int64, error) { result, err := sqlx.NamedExecContext(ctx, database.db, query, arg) @@ -116,9 +98,8 @@ func (database *SQL) Select(ctx context.Context, query string, dest any, args .. return sqlx.SelectContext(ctx, database.db, dest, query, args...) } -// SelectNamed executes a query that returns multiple rows, scanning -// the results into the dest slice using a named parameter struct or -// map. +// SelectNamed executes a query that returns multiple rows using +// named parameters. func (database *SQL) SelectNamed(ctx context.Context, query string, dest any, arg any) error { rows, err := sqlx.NamedQueryContext(ctx, database.db, query, arg) @@ -133,7 +114,7 @@ func (database *SQL) SelectNamed(ctx context.Context, query string, dest any, ar // Find executes a query expected to return a single row, scanning // the result into dest. If no row is found, the returned error wraps -// both sql.ErrNoRows and contract.ErrDatabaseNoRows. +// both sql.ErrNoRows and [contract.ErrDatabaseNoRows]. func (database *SQL) Find(ctx context.Context, query string, dest any, args ...any) error { if err := sqlx.GetContext(ctx, database.db, dest, query, args...); err != nil { if errors.Is(err, sql.ErrNoRows) { @@ -146,10 +127,8 @@ func (database *SQL) Find(ctx context.Context, query string, dest any, args ...a return nil } -// FindNamed executes a query expected to return a single row using a -// named parameter struct or map, scanning the result into dest. If no -// row is found, the returned error wraps both sql.ErrNoRows and -// contract.ErrDatabaseNoRows. +// FindNamed executes a query expected to return a single row using +// named parameters. func (database *SQL) FindNamed(ctx context.Context, query string, dest any, arg any) error { rows, err := sqlx.NamedQueryContext(ctx, database.db, query, arg) @@ -175,14 +154,12 @@ func (database *SQL) FindNamed(ctx context.Context, query string, dest any, arg } // WithTransaction executes fn inside a database transaction. If fn -// returns an error, the transaction is rolled back and both the -// original error and any rollback error are joined. If fn succeeds, -// the transaction is committed. Nested transactions are not supported -// and return contract.ErrDatabaseNestedTransaction. +// returns an error, the transaction is rolled back. If fn succeeds, +// the transaction is committed. Nested transactions are not supported. // // If fn panics, the transaction is rolled back before the panic is // re-raised, preventing connection pool leaks. -func (database *SQL) WithTransaction(ctx context.Context, fn func(tx contract.Database) error) (retErr error) { +func (database *SQL) WithTransaction(ctx context.Context, fn func(tx contract.DatabaseDriver) error) (retErr error) { if _, ok := database.db.(*sqlx.Tx); ok { return contract.ErrDatabaseNestedTransaction } @@ -221,19 +198,7 @@ func (database *SQL) Close() error { return database.raw.Close() } -// Configure exposes the underlying *sql.DB for connection pool -// tuning. The provided function receives the raw *sql.DB so -// callers can set pool parameters such as SetMaxOpenConns, -// SetMaxIdleConns, and SetConnMaxLifetime without importing -// database/sql directly. -// -// Example: -// -// db.Configure(func(raw *sql.DB) { -// raw.SetMaxOpenConns(25) -// raw.SetMaxIdleConns(10) -// raw.SetConnMaxLifetime(5 * time.Minute) -// }) +// Configure exposes the underlying *sql.DB for connection pool tuning. func (database *SQL) Configure(fn func(*sql.DB)) { fn(database.raw.DB) } diff --git a/framework/database/sql_test.go b/framework/database/sql_test.go deleted file mode 100644 index 059c498..0000000 --- a/framework/database/sql_test.go +++ /dev/null @@ -1,463 +0,0 @@ -//go:build cgo - -package database_test - -import ( - "context" - "database/sql" - "errors" - "testing" - - "github.com/studiolambda/cosmos/contract" - "github.com/studiolambda/cosmos/framework/database" - - _ "github.com/mattn/go-sqlite3" - "github.com/stretchr/testify/require" -) - -func newTestDB(t *testing.T) *database.SQL { - t.Helper() - - db, err := database.NewSQL("sqlite3", ":memory:") - - require.NoError(t, err) - - t.Cleanup(func() { - require.NoError(t, db.Close()) - }) - - return db -} - -func newTestDBWithUsers(t *testing.T) *database.SQL { - t.Helper() - - db := newTestDB(t) - ctx := context.Background() - - _, err := db.Exec(ctx, `CREATE TABLE users ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - name TEXT NOT NULL, - email TEXT NOT NULL - )`) - require.NoError(t, err) - - return db -} - -func TestPingSucceeds(t *testing.T) { - t.Parallel() - - db := newTestDB(t) - - err := db.Ping(context.Background()) - - require.NoError(t, err) -} - -func TestExecInsertsRow(t *testing.T) { - t.Parallel() - - db := newTestDBWithUsers(t) - ctx := context.Background() - - affected, err := db.Exec(ctx, "INSERT INTO users (name, email) VALUES (?, ?)", "alice", "alice@example.com") - - require.NoError(t, err) - require.Equal(t, int64(1), affected) -} - -func TestExecNamedInsertsRow(t *testing.T) { - t.Parallel() - - db := newTestDBWithUsers(t) - ctx := context.Background() - - arg := map[string]any{"name": "bob", "email": "bob@example.com"} - affected, err := db.ExecNamed(ctx, "INSERT INTO users (name, email) VALUES (:name, :email)", arg) - - require.NoError(t, err) - require.Equal(t, int64(1), affected) -} - -func TestSelectReturnsMultipleRows(t *testing.T) { - t.Parallel() - - db := newTestDBWithUsers(t) - ctx := context.Background() - - _, err := db.Exec(ctx, "INSERT INTO users (name, email) VALUES (?, ?)", "alice", "a@x.com") - require.NoError(t, err) - - _, err = db.Exec(ctx, "INSERT INTO users (name, email) VALUES (?, ?)", "bob", "b@x.com") - require.NoError(t, err) - - type user struct { - ID int `db:"id"` - Name string `db:"name"` - Email string `db:"email"` - } - - var users []user - err = db.Select(ctx, "SELECT id, name, email FROM users ORDER BY id", &users) - - require.NoError(t, err) - require.Len(t, users, 2) - require.Equal(t, "alice", users[0].Name) - require.Equal(t, "bob", users[1].Name) -} - -func TestSelectPropagatesContext(t *testing.T) { - t.Parallel() - - db := newTestDB(t) - ctx := context.Background() - - _, err := db.Exec(ctx, `CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT NOT NULL)`) - require.NoError(t, err) - - _, err = db.Exec(ctx, `INSERT INTO users (id, name) VALUES (?, ?)`, 1, "alice") - require.NoError(t, err) - - _, err = db.Exec(ctx, `INSERT INTO users (id, name) VALUES (?, ?)`, 2, "bob") - require.NoError(t, err) - - type user struct { - ID int `db:"id"` - Name string `db:"name"` - } - - var users []user - - err = db.Select(ctx, `SELECT id, name FROM users ORDER BY id`, &users) - - require.NoError(t, err) - require.Len(t, users, 2) - require.Equal(t, "alice", users[0].Name) - require.Equal(t, "bob", users[1].Name) -} - -func TestSelectNamedReturnsMultipleRows(t *testing.T) { - t.Parallel() - - db := newTestDBWithUsers(t) - ctx := context.Background() - - _, err := db.Exec(ctx, "INSERT INTO users (name, email) VALUES (?, ?)", "alice", "a@x.com") - require.NoError(t, err) - - _, err = db.Exec(ctx, "INSERT INTO users (name, email) VALUES (?, ?)", "bob", "b@x.com") - require.NoError(t, err) - - type user struct { - ID int `db:"id"` - Name string `db:"name"` - Email string `db:"email"` - } - - var users []user - arg := map[string]any{"domain": "%x.com"} - err = db.SelectNamed(ctx, "SELECT id, name, email FROM users WHERE email LIKE :domain ORDER BY id", &users, arg) - - require.NoError(t, err) - require.Len(t, users, 2) - require.Equal(t, "alice", users[0].Name) -} - -func TestFindReturnsSingleRow(t *testing.T) { - t.Parallel() - - db := newTestDBWithUsers(t) - ctx := context.Background() - - _, err := db.Exec(ctx, "INSERT INTO users (name, email) VALUES (?, ?)", "alice", "a@x.com") - require.NoError(t, err) - - type user struct { - ID int `db:"id"` - Name string `db:"name"` - Email string `db:"email"` - } - - var found user - err = db.Find(ctx, "SELECT id, name, email FROM users WHERE name = ?", &found, "alice") - - require.NoError(t, err) - require.Equal(t, "alice", found.Name) -} - -func TestFindWrapsErrNoRows(t *testing.T) { - t.Parallel() - - db := newTestDBWithUsers(t) - ctx := context.Background() - - type user struct { - ID int `db:"id"` - Name string `db:"name"` - } - - var found user - err := db.Find(ctx, "SELECT id, name FROM users WHERE name = ?", &found, "ghost") - - require.Error(t, err) - require.ErrorIs(t, err, sql.ErrNoRows) - require.ErrorIs(t, err, contract.ErrDatabaseNoRows) -} - -func TestFindNamedReturnsSingleRow(t *testing.T) { - t.Parallel() - - db := newTestDBWithUsers(t) - ctx := context.Background() - - _, err := db.Exec(ctx, "INSERT INTO users (name, email) VALUES (?, ?)", "alice", "a@x.com") - require.NoError(t, err) - - type user struct { - ID int `db:"id"` - Name string `db:"name"` - Email string `db:"email"` - } - - var found user - arg := map[string]any{"name": "alice"} - err = db.FindNamed(ctx, "SELECT id, name, email FROM users WHERE name = :name", &found, arg) - - require.NoError(t, err) - require.Equal(t, "alice", found.Name) -} - -func TestFindNamedWrapsErrNoRows(t *testing.T) { - t.Parallel() - - db := newTestDBWithUsers(t) - ctx := context.Background() - - type user struct { - ID int `db:"id"` - Name string `db:"name"` - } - - var found user - arg := map[string]any{"name": "ghost"} - err := db.FindNamed(ctx, "SELECT id, name FROM users WHERE name = :name", &found, arg) - - require.Error(t, err) - require.ErrorIs(t, err, sql.ErrNoRows) - require.ErrorIs(t, err, contract.ErrDatabaseNoRows) -} - -func TestFindNamedReturnsErrDatabaseNoRows(t *testing.T) { - t.Parallel() - - db := newTestDB(t) - ctx := context.Background() - - _, err := db.Exec(ctx, `CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT NOT NULL)`) - require.NoError(t, err) - - type user struct { - ID int `db:"id"` - Name string `db:"name"` - } - - var found user - - err = db.FindNamed(ctx, `SELECT id, name FROM users WHERE id = :id`, &found, map[string]any{"id": 999}) - - require.Error(t, err) - require.ErrorIs(t, err, contract.ErrDatabaseNoRows) -} - -func TestFindNamedReturnsResultWhenFound(t *testing.T) { - t.Parallel() - - db := newTestDB(t) - ctx := context.Background() - - _, err := db.Exec(ctx, `CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT NOT NULL)`) - require.NoError(t, err) - - _, err = db.Exec(ctx, `INSERT INTO users (id, name) VALUES (?, ?)`, 1, "alice") - require.NoError(t, err) - - type user struct { - ID int `db:"id"` - Name string `db:"name"` - } - - var found user - - err = db.FindNamed(ctx, `SELECT id, name FROM users WHERE id = :id`, &found, map[string]any{"id": 1}) - - require.NoError(t, err) - require.Equal(t, 1, found.ID) - require.Equal(t, "alice", found.Name) -} - -func TestWithTransactionCommitsOnSuccess(t *testing.T) { - t.Parallel() - - db := newTestDBWithUsers(t) - ctx := context.Background() - - err := db.WithTransaction(ctx, func(tx contract.Database) error { - _, err := tx.Exec(ctx, "INSERT INTO users (name, email) VALUES (?, ?)", "alice", "a@x.com") - return err - }) - require.NoError(t, err) - - type user struct { - ID int `db:"id"` - Name string `db:"name"` - } - - var found user - err = db.Find(ctx, "SELECT id, name FROM users WHERE name = ?", &found, "alice") - - require.NoError(t, err) - require.Equal(t, "alice", found.Name) -} - -func TestWithTransactionRollsBackOnError(t *testing.T) { - t.Parallel() - - db := newTestDBWithUsers(t) - ctx := context.Background() - - sentinel := errors.New("rollback me") - - err := db.WithTransaction(ctx, func(tx contract.Database) error { - _, err := tx.Exec(ctx, "INSERT INTO users (name, email) VALUES (?, ?)", "alice", "a@x.com") - require.NoError(t, err) - - return sentinel - }) - require.ErrorIs(t, err, sentinel) - - type user struct { - ID int `db:"id"` - Name string `db:"name"` - } - - var users []user - err = db.Select(ctx, "SELECT id, name FROM users", &users) - - require.NoError(t, err) - require.Empty(t, users) -} - -func TestWithTransactionRejectsNesting(t *testing.T) { - t.Parallel() - - db := newTestDB(t) - ctx := context.Background() - - err := db.WithTransaction(ctx, func(tx contract.Database) error { - return tx.WithTransaction(ctx, func(inner contract.Database) error { - return nil - }) - }) - - require.ErrorIs(t, err, contract.ErrDatabaseNestedTransaction) -} - -func TestWithTransactionPanicRollsBack(t *testing.T) { - t.Parallel() - - db := newTestDB(t) - ctx := context.Background() - - _, err := db.Exec(ctx, "CREATE TABLE items (id INTEGER PRIMARY KEY, name TEXT)") - - require.NoError(t, err) - - require.Panics(t, func() { - _ = db.WithTransaction(ctx, func(tx contract.Database) error { - _, err := tx.Exec(ctx, "INSERT INTO items (name) VALUES (?)", "should-be-rolled-back") - - if err != nil { - return err - } - - panic("unexpected failure") - }) - }) - - // The row must not exist because the transaction was rolled back. - var count int - err = db.Find(ctx, "SELECT COUNT(*) FROM items", &count) - - require.NoError(t, err) - require.Equal(t, 0, count) -} - -func TestWithTransactionPanicPreservesValue(t *testing.T) { - t.Parallel() - - db := newTestDB(t) - ctx := context.Background() - - var recovered any - - func() { - defer func() { - recovered = recover() - }() - - _ = db.WithTransaction(ctx, func(tx contract.Database) error { - panic("test-panic-value") - }) - }() - - require.Equal(t, "test-panic-value", recovered) -} - -func TestCloseOnTransactionWrapperIsNoOp(t *testing.T) { - t.Parallel() - - db := newTestDBWithUsers(t) - ctx := context.Background() - - err := db.WithTransaction(ctx, func(tx contract.Database) error { - // Closing the transaction wrapper should be a no-op and - // must not close the underlying connection pool. - err := tx.Close() - require.NoError(t, err) - - // The transaction should still be usable after Close. - _, err = tx.Exec(ctx, "INSERT INTO users (name, email) VALUES (?, ?)", "alice", "a@x.com") - return err - }) - require.NoError(t, err) - - // The pool should still be functional after the transaction completes. - err = db.Ping(ctx) - require.NoError(t, err) - - type user struct { - ID int `db:"id"` - Name string `db:"name"` - } - - var found user - err = db.Find(ctx, "SELECT id, name FROM users WHERE name = ?", &found, "alice") - - require.NoError(t, err) - require.Equal(t, "alice", found.Name) -} - -func TestConfigureSetsPoolLimits(t *testing.T) { - t.Parallel() - - db := newTestDB(t) - - db.Configure(func(raw *sql.DB) { - raw.SetMaxOpenConns(10) - raw.SetMaxIdleConns(5) - }) - - err := db.Ping(context.Background()) - require.NoError(t, err) -} diff --git a/framework/event/amqp.go b/framework/event/amqp.go index ea5cf0d..71ba1aa 100644 --- a/framework/event/amqp.go +++ b/framework/event/amqp.go @@ -2,7 +2,6 @@ package event import ( "context" - "encoding/json" "errors" "fmt" "log/slog" @@ -143,40 +142,28 @@ func NewAMQPBrokerFrom( }, nil } -// Publish sends an event with the given name and payload to all -// subscribers listening for that event. The payload is serialized -// to JSON before being published to the topic exchange using the -// event name as the routing key. -// -// Publishing is thread-safe and respects context cancellation. -// If the context is cancelled before the publish completes, the -// operation will be aborted and an error returned. +// Publish sends raw payload bytes to all subscribers of the named event. func (broker *AMQPBroker) Publish( ctx context.Context, event string, - payload any, + payload []byte, ) error { if err := validateEvent(event); err != nil { return err } - encoded, err := json.Marshal(payload) - if err != nil { - return err - } - broker.mu.Lock() defer broker.mu.Unlock() - err = broker.pubCh.PublishWithContext( + err := broker.pubCh.PublishWithContext( ctx, broker.exchange, event, false, false, amqp091.Publishing{ - ContentType: "application/json", - Body: encoded, + ContentType: "application/octet-stream", + Body: payload, }, ) @@ -195,8 +182,8 @@ func (broker *AMQPBroker) Publish( false, false, amqp091.Publishing{ - ContentType: "application/json", - Body: encoded, + ContentType: "application/octet-stream", + Body: payload, }, ) } @@ -292,9 +279,7 @@ func (broker *AMQPBroker) Subscribe( } }() - handler(func(dest any) error { - return json.Unmarshal(delivery.Body, dest) - }) + handler(delivery.Body) }() } }) diff --git a/framework/event/memory.go b/framework/event/memory.go index 41fbd1d..fdbf8ef 100644 --- a/framework/event/memory.go +++ b/framework/event/memory.go @@ -2,7 +2,6 @@ package event import ( "context" - "encoding/json" "errors" "log/slog" "strconv" @@ -13,62 +12,28 @@ import ( "github.com/studiolambda/cosmos/contract" ) -// ErrBrokerClosed is returned when attempting operations on a closed -// broker. -// Once a broker is closed, it cannot be reused and a new instance -// must be created. +// ErrBrokerClosed is returned when attempting operations on a closed broker. var ErrBrokerClosed = errors.New("broker is closed") // DefaultMaxConcurrentDeliveries is the maximum number of // concurrent handler goroutines allowed per MemoryBroker. -// This prevents goroutine exhaustion under high event throughput -// with slow handlers. const DefaultMaxConcurrentDeliveries = 1024 -// MemoryBroker implements the EventBroker interface using only in-memory +// MemoryBroker implements [contract.EventDriver] using only in-memory // data structures with no external dependencies. -// It provides a lightweight, zero-configuration broker ideal for testing, -// local development, and single-instance applications. -// All message delivery happens asynchronously in separate goroutines with -// panic recovery to ensure one handler's failure doesn't affect others. -// Concurrent deliveries are bounded by a semaphore to prevent goroutine -// exhaustion. // // Wildcard patterns: '*' matches a single dot-separated token, // '#' matches zero or more tokens (must be the last token in the pattern). type MemoryBroker struct { - // mu protects concurrent access to the handlers map during - // subscribe and unsubscribe operations. - mu sync.RWMutex - - // handlers stores event handlers organized by subscription pattern - // and unique handler ID. - // The outer map key is the pattern (which may contain wildcards), - // and the inner map associates handler IDs with their handlers. + mu sync.RWMutex handlers map[string]map[string]contract.EventHandler - - // nextID generates unique identifiers for each subscribed handler - // to enable precise unsubscribe operations. - nextID atomic.Uint64 - - // closed indicates whether the broker has been closed. - // Once closed, all operations return ErrBrokerClosed. - closed atomic.Bool - - // sem limits the number of concurrent delivery goroutines to - // prevent resource exhaustion under high throughput. - sem chan struct{} - - // wg tracks in-flight deliveries so [Close] can wait for - // them to complete. - wg sync.WaitGroup + nextID atomic.Uint64 + closed atomic.Bool + sem chan struct{} + wg sync.WaitGroup } -// NewMemoryBroker creates a new in-memory event broker with no external -// dependencies or configuration required. -// The broker is ready to use immediately and supports concurrent access -// from multiple goroutines. -// It must be closed with Close when no longer needed to release resources. +// NewMemoryBroker creates a new in-memory event broker. func NewMemoryBroker() *MemoryBroker { return &MemoryBroker{ handlers: make(map[string]map[string]contract.EventHandler), @@ -76,24 +41,12 @@ func NewMemoryBroker() *MemoryBroker { } } -// Publish sends an event with the given payload to all matching subscribers. -// The payload is JSON-encoded and delivered asynchronously to handlers -// whose subscription patterns match the event name. -// Handlers are invoked in separate goroutines with panic recovery, -// ensuring one handler's failure doesn't affect others. -// -// Wildcard matching supports: -// - "*" matches a single token (e.g., "user.*.created" matches -// "user.123.created") -// - "#" matches zero or more tokens (e.g., "logs.#" matches "logs", -// "logs.error", "logs.error.database") -// -// Returns an error if JSON encoding fails or if the broker is closed. -// The context is checked once at the start of the publish operation. +// Publish sends raw payload bytes to all matching subscribers. +// Handlers are invoked asynchronously with panic recovery. func (broker *MemoryBroker) Publish( ctx context.Context, event string, - payload any, + payload []byte, ) error { if broker.closed.Load() { return ErrBrokerClosed @@ -107,12 +60,6 @@ func (broker *MemoryBroker) Publish( return err } - encoded, err := json.Marshal(payload) - - if err != nil { - return err - } - broker.mu.RLock() var matched []contract.EventHandler @@ -137,7 +84,7 @@ func (broker *MemoryBroker) Publish( broker.wg.Done() }() - broker.deliverToHandler(handler, encoded) + broker.deliverToHandler(handler, payload) }() } @@ -145,19 +92,7 @@ func (broker *MemoryBroker) Publish( } // Subscribe registers a handler for events matching the given pattern. -// The pattern supports wildcards: -// - "*" matches a single token (e.g., "user.*.created") -// - "#" matches zero or more tokens (e.g., "logs.#") -// -// Multiple handlers can subscribe to the same pattern and all will receive -// messages (fan-out). -// Handlers are invoked asynchronously in separate goroutines. -// -// Returns an unsubscribe function that removes only this specific handler -// subscription. -// Returns an error if the broker is closed. -// The context is used only for the subscription setup, not for the handler -// lifecycle. +// Returns an unsubscribe function. func (broker *MemoryBroker) Subscribe( ctx context.Context, event string, @@ -198,10 +133,7 @@ func (broker *MemoryBroker) Subscribe( }, nil } -// Close shuts down the broker and removes all subscribed handlers. -// After Close is called, all operations return ErrBrokerClosed and the -// broker cannot be reused. -// Close waits for all in-flight deliveries to complete before returning. +// Close shuts down the broker and waits for in-flight deliveries. func (broker *MemoryBroker) Close() error { broker.closed.Store(true) @@ -215,12 +147,11 @@ func (broker *MemoryBroker) Close() error { return nil } -// deliverToHandler invokes a handler with the encoded payload, -// recovering from any panic to prevent handler failures from -// affecting the broker. +// deliverToHandler invokes a handler with the raw payload, +// recovering from any panic. func (broker *MemoryBroker) deliverToHandler( handler contract.EventHandler, - encoded []byte, + payload []byte, ) { defer func() { if recovered := recover(); recovered != nil { @@ -231,15 +162,10 @@ func (broker *MemoryBroker) deliverToHandler( } }() - handler(func(dest any) error { - return json.Unmarshal(encoded, dest) - }) + handler(payload) } -// matchEvent checks if a subscription pattern matches an -// event name. It supports dot-separated tokens with wildcards: -// - "*" matches exactly one token -// - "#" matches zero or more tokens +// matchEvent checks if a subscription pattern matches an event name. func matchEvent(pattern, event string) bool { if pattern == event { return true @@ -251,9 +177,7 @@ func matchEvent(pattern, event string) bool { return matchEventParts(patternParts, eventParts) } -// matchEventParts recursively matches event parts against -// pattern parts with wildcard support. It handles "*" for -// single-token and "#" for multi-token matching. +// matchEventParts recursively matches event parts against pattern parts. func matchEventParts(pattern, event []string) bool { if len(pattern) == 0 { return len(event) == 0 diff --git a/framework/event/memory_test.go b/framework/event/memory_test.go index 84ee767..999dfad 100644 --- a/framework/event/memory_test.go +++ b/framework/event/memory_test.go @@ -2,6 +2,7 @@ package event_test import ( "context" + "encoding/json" "errors" "strings" "sync" @@ -9,7 +10,6 @@ import ( "testing" "time" - "github.com/studiolambda/cosmos/contract" "github.com/studiolambda/cosmos/framework/event" "github.com/stretchr/testify/require" @@ -31,12 +31,12 @@ func TestMemoryBrokerPublishAndSubscribe(t *testing.T) { wg.Add(1) unsub, err := broker.Subscribe( - ctx, "user.created", func(payload contract.EventPayload) { + ctx, "user.created", func(payload []byte) { defer wg.Done() var msg string - _ = payload(&msg) + _ = json.Unmarshal(payload, &msg) received = msg }, @@ -45,7 +45,9 @@ func TestMemoryBrokerPublishAndSubscribe(t *testing.T) { require.NoError(t, err) require.NotNil(t, unsub) - err = broker.Publish(ctx, "user.created", "hello") + data, _ := json.Marshal("hello") + + err = broker.Publish(ctx, "user.created", data) require.NoError(t, err) @@ -70,7 +72,7 @@ func TestMemoryBrokerWildcardStar(t *testing.T) { wg.Add(1) _, err := broker.Subscribe( - ctx, "user.*.created", func(payload contract.EventPayload) { + ctx, "user.*.created", func(payload []byte) { defer wg.Done() atomic.AddInt64(&received, 1) }, @@ -78,7 +80,7 @@ func TestMemoryBrokerWildcardStar(t *testing.T) { require.NoError(t, err) - err = broker.Publish(ctx, "user.123.created", "data") + err = broker.Publish(ctx, "user.123.created", []byte("data")) require.NoError(t, err) @@ -100,12 +102,12 @@ func TestMemoryBrokerPublishDoesNotDeadlockWithSubscribeInHandler(t *testing.T) done := make(chan struct{}) _, err := broker.Subscribe( - ctx, "test.event", func(payload contract.EventPayload) { + ctx, "test.event", func(payload []byte) { // This Subscribe call requires broker.mu.Lock(). // If Publish still holds broker.mu.RLock() during // dispatch, this will deadlock. _, _ = broker.Subscribe( - ctx, "other.event", func(payload contract.EventPayload) {}, + ctx, "other.event", func(payload []byte) {}, ) close(done) @@ -114,7 +116,7 @@ func TestMemoryBrokerPublishDoesNotDeadlockWithSubscribeInHandler(t *testing.T) require.NoError(t, err) - err = broker.Publish(ctx, "test.event", "data") + err = broker.Publish(ctx, "test.event", []byte("data")) require.NoError(t, err) select { @@ -141,7 +143,7 @@ func TestMemoryBrokerWildcardHash(t *testing.T) { wg.Add(3) _, err := broker.Subscribe( - ctx, "logs.#", func(payload contract.EventPayload) { + ctx, "logs.#", func(payload []byte) { defer wg.Done() atomic.AddInt64(&received, 1) }, @@ -149,13 +151,13 @@ func TestMemoryBrokerWildcardHash(t *testing.T) { require.NoError(t, err) - err = broker.Publish(ctx, "logs", "data1") + err = broker.Publish(ctx, "logs", []byte("data1")) require.NoError(t, err) - err = broker.Publish(ctx, "logs.error", "data2") + err = broker.Publish(ctx, "logs.error", []byte("data2")) require.NoError(t, err) - err = broker.Publish(ctx, "logs.error.database", "data3") + err = broker.Publish(ctx, "logs.error.database", []byte("data3")) require.NoError(t, err) wg.Wait() @@ -179,7 +181,7 @@ func TestMemoryBrokerExactMatch(t *testing.T) { wg.Add(1) _, err := broker.Subscribe( - ctx, "user.created", func(payload contract.EventPayload) { + ctx, "user.created", func(payload []byte) { defer wg.Done() atomic.AddInt64(&received, 1) }, @@ -187,7 +189,7 @@ func TestMemoryBrokerExactMatch(t *testing.T) { require.NoError(t, err) - err = broker.Publish(ctx, "user.created", "data") + err = broker.Publish(ctx, "user.created", []byte("data")) require.NoError(t, err) wg.Wait() @@ -208,14 +210,14 @@ func TestMemoryBrokerNoMatchDoesNotDeliver(t *testing.T) { var received int64 _, err := broker.Subscribe( - ctx, "user.created", func(payload contract.EventPayload) { + ctx, "user.created", func(payload []byte) { atomic.AddInt64(&received, 1) }, ) require.NoError(t, err) - err = broker.Publish(ctx, "order.created", "data") + err = broker.Publish(ctx, "order.created", []byte("data")) require.NoError(t, err) time.Sleep(50 * time.Millisecond) @@ -236,7 +238,7 @@ func TestMemoryBrokerUnsubscribeStopsDelivery(t *testing.T) { var received int64 unsub, err := broker.Subscribe( - ctx, "user.created", func(payload contract.EventPayload) { + ctx, "user.created", func(payload []byte) { atomic.AddInt64(&received, 1) }, ) @@ -246,7 +248,7 @@ func TestMemoryBrokerUnsubscribeStopsDelivery(t *testing.T) { err = unsub() require.NoError(t, err) - err = broker.Publish(ctx, "user.created", "data") + err = broker.Publish(ctx, "user.created", []byte("data")) require.NoError(t, err) time.Sleep(50 * time.Millisecond) @@ -273,7 +275,7 @@ func TestMemoryBrokerMultipleSubscribers(t *testing.T) { _, err := broker.Subscribe( ctx, "user.created", - func(payload contract.EventPayload) { + func(payload []byte) { defer wg.Done() atomic.AddInt64(&received, 1) }, @@ -281,7 +283,7 @@ func TestMemoryBrokerMultipleSubscribers(t *testing.T) { require.NoError(t, err) } - err := broker.Publish(ctx, "user.created", "data") + err := broker.Publish(ctx, "user.created", []byte("data")) require.NoError(t, err) wg.Wait() @@ -298,7 +300,7 @@ func TestMemoryBrokerPublishAfterCloseReturnsError(t *testing.T) { err := broker.Close() require.NoError(t, err) - err = broker.Publish(ctx, "user.created", "data") + err = broker.Publish(ctx, "user.created", []byte("data")) require.ErrorIs(t, err, event.ErrBrokerClosed) } @@ -315,7 +317,7 @@ func TestMemoryBrokerSubscribeAfterCloseReturnsError(t *testing.T) { _, err = broker.Subscribe( ctx, "user.created", - func(payload contract.EventPayload) {}, + func(payload []byte) {}, ) require.ErrorIs(t, err, event.ErrBrokerClosed) @@ -336,7 +338,7 @@ func TestMemoryBrokerHandlerPanicDoesNotCrashBroker(t *testing.T) { wg.Add(1) _, err := broker.Subscribe( - ctx, "user.created", func(payload contract.EventPayload) { + ctx, "user.created", func(payload []byte) { defer wg.Done() panic("handler panic") }, @@ -344,7 +346,7 @@ func TestMemoryBrokerHandlerPanicDoesNotCrashBroker(t *testing.T) { require.NoError(t, err) - err = broker.Publish(ctx, "user.created", "data") + err = broker.Publish(ctx, "user.created", []byte("data")) require.NoError(t, err) wg.Wait() @@ -362,7 +364,7 @@ func TestMemoryBrokerContextCancellationStopsPublish(t *testing.T) { _ = broker.Close() }) - err := broker.Publish(ctx, "user.created", "data") + err := broker.Publish(ctx, "user.created", []byte("data")) require.Error(t, err) } @@ -388,17 +390,19 @@ func TestMemoryBrokerPayloadUnmarshal(t *testing.T) { wg.Add(1) _, err := broker.Subscribe( - ctx, "user.created", func(payload contract.EventPayload) { + ctx, "user.created", func(payload []byte) { defer wg.Done() - _ = payload(&receivedUser) + _ = json.Unmarshal(payload, &receivedUser) }, ) require.NoError(t, err) + userData, _ := json.Marshal(User{Name: "Alice", Age: 30}) + err = broker.Publish( - ctx, "user.created", User{Name: "Alice", Age: 30}, + ctx, "user.created", userData, ) require.NoError(t, err) @@ -409,7 +413,7 @@ func TestMemoryBrokerPayloadUnmarshal(t *testing.T) { require.Equal(t, 30, receivedUser.Age) } -func TestMemoryBrokerPublishInvalidPayloadReturnsError(t *testing.T) { +func TestMemoryBrokerPublishNilPayloadSucceeds(t *testing.T) { t.Parallel() ctx := context.Background() @@ -419,9 +423,9 @@ func TestMemoryBrokerPublishInvalidPayloadReturnsError(t *testing.T) { _ = broker.Close() }) - err := broker.Publish(ctx, "user.created", make(chan int)) + err := broker.Publish(ctx, "user.created", nil) - require.Error(t, err) + require.NoError(t, err) } func TestMemoryBrokerCloseWaitsForInFlightDeliveries(t *testing.T) { @@ -433,7 +437,7 @@ func TestMemoryBrokerCloseWaitsForInFlightDeliveries(t *testing.T) { var completed atomic.Bool _, err := broker.Subscribe( - ctx, "slow.event", func(payload contract.EventPayload) { + ctx, "slow.event", func(payload []byte) { time.Sleep(100 * time.Millisecond) completed.Store(true) }, @@ -441,7 +445,7 @@ func TestMemoryBrokerCloseWaitsForInFlightDeliveries(t *testing.T) { require.NoError(t, err) - err = broker.Publish(ctx, "slow.event", "data") + err = broker.Publish(ctx, "slow.event", []byte("data")) require.NoError(t, err) err = broker.Close() @@ -459,7 +463,7 @@ func TestMemoryBrokerCloseClearsHandlers(t *testing.T) { _, err := broker.Subscribe( ctx, "user.created", - func(payload contract.EventPayload) {}, + func(payload []byte) {}, ) require.NoError(t, err) @@ -467,7 +471,7 @@ func TestMemoryBrokerCloseClearsHandlers(t *testing.T) { err = broker.Close() require.NoError(t, err) - err = broker.Publish(ctx, "user.created", "data") + err = broker.Publish(ctx, "user.created", []byte("data")) require.ErrorIs(t, err, event.ErrBrokerClosed) } @@ -486,7 +490,7 @@ func TestMemoryBrokerUnsubscribeOneDoesNotAffectOther(t *testing.T) { var wg sync.WaitGroup unsub1, err := broker.Subscribe( - ctx, "user.created", func(payload contract.EventPayload) { + ctx, "user.created", func(payload []byte) { atomic.AddInt64(&received, 1) }, ) @@ -496,7 +500,7 @@ func TestMemoryBrokerUnsubscribeOneDoesNotAffectOther(t *testing.T) { wg.Add(1) _, err = broker.Subscribe( - ctx, "user.created", func(payload contract.EventPayload) { + ctx, "user.created", func(payload []byte) { defer wg.Done() atomic.AddInt64(&received, 1) }, @@ -507,7 +511,7 @@ func TestMemoryBrokerUnsubscribeOneDoesNotAffectOther(t *testing.T) { err = unsub1() require.NoError(t, err) - err = broker.Publish(ctx, "user.created", "data") + err = broker.Publish(ctx, "user.created", []byte("data")) require.NoError(t, err) wg.Wait() @@ -525,7 +529,7 @@ func TestMemoryBrokerPublishRejectsEmptyEvent(t *testing.T) { _ = broker.Close() }) - err := broker.Publish(ctx, "", "data") + err := broker.Publish(ctx, "", []byte("data")) require.Error(t, err) require.ErrorIs(t, err, event.ErrInvalidEvent) @@ -541,7 +545,7 @@ func TestMemoryBrokerPublishRejectsControlCharacters(t *testing.T) { _ = broker.Close() }) - err := broker.Publish(ctx, "user.\tcreated", "data") + err := broker.Publish(ctx, "user.\tcreated", []byte("data")) require.Error(t, err) require.ErrorIs(t, err, event.ErrInvalidEvent) @@ -559,7 +563,7 @@ func TestMemoryBrokerPublishRejectsTooLongEvent(t *testing.T) { longEvent := strings.Repeat("a", 256) - err := broker.Publish(ctx, longEvent, "data") + err := broker.Publish(ctx, longEvent, []byte("data")) require.Error(t, err) require.ErrorIs(t, err, event.ErrInvalidEvent) @@ -576,7 +580,7 @@ func TestMemoryBrokerSubscribeRejectsEmptyEvent(t *testing.T) { }) unsub, err := broker.Subscribe( - ctx, "", func(payload contract.EventPayload) {}, + ctx, "", func(payload []byte) {}, ) require.Nil(t, unsub) @@ -594,7 +598,7 @@ func TestMemoryBrokerValidationErrorsWrapErrInvalidEvent(t *testing.T) { _ = broker.Close() }) - err := broker.Publish(ctx, "", "data") + err := broker.Publish(ctx, "", []byte("data")) require.Error(t, err) require.True(t, errors.Is(err, event.ErrInvalidEvent)) diff --git a/framework/event/mqtt.go b/framework/event/mqtt.go index df4b412..6f95bea 100644 --- a/framework/event/mqtt.go +++ b/framework/event/mqtt.go @@ -2,7 +2,6 @@ package event import ( "context" - "encoding/json" "fmt" "log/slog" "net/url" @@ -324,16 +323,12 @@ func (broker *MQTTBroker) route(pb *paho.Publish) { } }() - h(func(dest any) error { - return json.Unmarshal(pb.Payload, dest) - }) + h(pb.Payload) }(handler) } } // deliverToHandler invokes a single handler with panic recovery. -// Recovered panics are logged via slog so they remain visible for -// debugging without propagating to the caller. func (broker *MQTTBroker) deliverToHandler( handler contract.EventHandler, topic string, @@ -349,41 +344,27 @@ func (broker *MQTTBroker) deliverToHandler( } }() - handler(func(dest any) error { - return json.Unmarshal(payload, dest) - }) + handler(payload) } -// Publish sends an event with the given name and payload to all -// subscribers listening for that event. The payload is serialized -// to JSON and the event name is converted to MQTT topic format. -// -// Publishing is thread-safe and respects context cancellation. -// The operation uses the configured QoS level for delivery -// guarantees. +// Publish sends raw payload bytes to all subscribers of the named event. func (broker *MQTTBroker) Publish( ctx context.Context, event string, - payload any, + payload []byte, ) error { if err := validateEvent(event); err != nil { return err } - encoded, err := json.Marshal(payload) - - if err != nil { - return err - } - topic := convertTopic(event) - _, err = broker.client.Publish(ctx, &paho.Publish{ + _, err := broker.client.Publish(ctx, &paho.Publish{ Topic: topic, QoS: broker.qos, - Payload: encoded, + Payload: payload, Properties: &paho.PublishProperties{ - ContentType: "application/json", + ContentType: "application/octet-stream", }, }) diff --git a/framework/event/mqtt_internal_test.go b/framework/event/mqtt_internal_test.go index c7f445a..3f60ccf 100644 --- a/framework/event/mqtt_internal_test.go +++ b/framework/event/mqtt_internal_test.go @@ -166,7 +166,7 @@ func TestMQTTBrokerRouteDeliversToMatchingHandler(t *testing.T) { broker := newTestMQTTBroker(map[string]map[string]contract.EventHandler{ "user/created": { - "1": func(payload contract.EventPayload) { + "1": func(payload []byte) { called.Store(true) }, }, @@ -189,7 +189,7 @@ func TestMQTTBrokerRouteDeliversToWildcardHandler(t *testing.T) { broker := newTestMQTTBroker(map[string]map[string]contract.EventHandler{ "user/+": { - "1": func(payload contract.EventPayload) { + "1": func(payload []byte) { called.Store(true) }, }, @@ -212,7 +212,7 @@ func TestMQTTBrokerRouteDoesNotDeliverToNonMatching(t *testing.T) { broker := newTestMQTTBroker(map[string]map[string]contract.EventHandler{ "user/created": { - "1": func(payload contract.EventPayload) { + "1": func(payload []byte) { called.Store(true) }, }, @@ -235,10 +235,10 @@ func TestMQTTBrokerRouteDeliversToMultipleHandlersSamePattern(t *testing.T) { broker := newTestMQTTBroker(map[string]map[string]contract.EventHandler{ "user/created": { - "1": func(payload contract.EventPayload) { + "1": func(payload []byte) { count.Add(1) }, - "2": func(payload contract.EventPayload) { + "2": func(payload []byte) { count.Add(1) }, }, @@ -261,12 +261,12 @@ func TestMQTTBrokerRouteFanOutAcrossPatterns(t *testing.T) { broker := newTestMQTTBroker(map[string]map[string]contract.EventHandler{ "user/created": { - "1": func(payload contract.EventPayload) { + "1": func(payload []byte) { count.Add(1) }, }, "user/+": { - "2": func(payload contract.EventPayload) { + "2": func(payload []byte) { count.Add(1) }, }, @@ -289,10 +289,10 @@ func TestMQTTBrokerRouteUnmarshalsJSONPayload(t *testing.T) { broker := newTestMQTTBroker(map[string]map[string]contract.EventHandler{ "user/created": { - "1": func(payload contract.EventPayload) { + "1": func(payload []byte) { var value string - _ = payload(&value) + _ = json.Unmarshal(payload, &value) received.Store(value) }, @@ -326,12 +326,12 @@ func TestMQTTBrokerRouteDispatchesAsynchronously(t *testing.T) { broker := newTestMQTTBroker(map[string]map[string]contract.EventHandler{ "events/test": { - "1": func(payload contract.EventPayload) { + "1": func(payload []byte) { started.Done() <-gate finished.Done() }, - "2": func(payload contract.EventPayload) { + "2": func(payload []byte) { started.Done() <-gate finished.Done() @@ -374,12 +374,12 @@ func TestMQTTBrokerRouteRecoversPanic(t *testing.T) { broker := newTestMQTTBroker(map[string]map[string]contract.EventHandler{ "events/panic": { - "1": func(payload contract.EventPayload) { + "1": func(payload []byte) { panic("test panic") }, }, "events/+": { - "2": func(payload contract.EventPayload) { + "2": func(payload []byte) { secondCalled.Store(true) }, }, @@ -401,7 +401,7 @@ func TestMQTTBrokerRoutePanicDoesNotPropagate(t *testing.T) { broker := &MQTTBroker{ handlers: map[string]map[string]contract.EventHandler{ "user/created": { - "1": func(payload contract.EventPayload) { + "1": func(payload []byte) { panic("handler panic") }, }, @@ -426,10 +426,10 @@ func TestMQTTBrokerRoutePanicDoesNotAffectOtherHandlers(t *testing.T) { broker := &MQTTBroker{ handlers: map[string]map[string]contract.EventHandler{ "user/created": { - "1": func(payload contract.EventPayload) { + "1": func(payload []byte) { panic("handler panic") }, - "2": func(payload contract.EventPayload) { + "2": func(payload []byte) { called.Store(true) }, }, @@ -455,7 +455,7 @@ func TestMQTTBrokerHandlePublishRoutesMessage(t *testing.T) { broker := &MQTTBroker{ handlers: map[string]map[string]contract.EventHandler{ "user/created": { - "1": func(payload contract.EventPayload) { + "1": func(payload []byte) { called.Store(true) }, }, @@ -483,7 +483,7 @@ func TestMQTTBrokerHandlePublishRecoversPanic(t *testing.T) { broker := &MQTTBroker{ handlers: map[string]map[string]contract.EventHandler{ "user/created": { - "1": func(payload contract.EventPayload) { + "1": func(payload []byte) { panic("handler panic") }, }, diff --git a/framework/event/nats.go b/framework/event/nats.go index 140acea..357d3e2 100644 --- a/framework/event/nats.go +++ b/framework/event/nats.go @@ -3,7 +3,7 @@ package event import ( "context" "crypto/tls" - "encoding/json" + "fmt" "log/slog" "strings" @@ -227,28 +227,17 @@ func NewNATSBrokerFrom(conn *nats.Conn) *NATSBroker { } } -// Publish sends an event with the given payload to all subscribers. -// The payload is JSON-encoded before transmission. -// The event name is used as the NATS subject. -// -// Returns an error if JSON encoding fails or if the publish operation fails. -// The context is used for operation timeout and cancellation. +// Publish sends raw payload bytes to all subscribers of the named event. func (broker *NATSBroker) Publish( ctx context.Context, event string, - payload any, + payload []byte, ) error { if err := validateEvent(event); err != nil { return err } - encoded, err := json.Marshal(payload) - - if err != nil { - return err - } - - return broker.conn.Publish(event, encoded) + return broker.conn.Publish(event, payload) } // Subscribe registers a handler for events matching the given pattern. @@ -280,9 +269,7 @@ func (broker *NATSBroker) Subscribe( } }() - handler(func(dest any) error { - return json.Unmarshal(msg.Data, dest) - }) + handler(msg.Data) }) if err != nil { diff --git a/framework/event/redis.go b/framework/event/redis.go index 6b88e61..ec6a3b8 100644 --- a/framework/event/redis.go +++ b/framework/event/redis.go @@ -2,7 +2,6 @@ package event import ( "context" - "encoding/json" "fmt" "log/slog" "strings" @@ -13,14 +12,9 @@ import ( "github.com/redis/go-redis/v9" ) -// RedisBroker implements contract.EventBus using Redis Pub/Sub. +// RedisBroker implements [contract.EventDriver] using Redis Pub/Sub. // It maps the "#" multi-level wildcard to Redis's "*" glob pattern // for topic subscriptions. -// -// Wildcard patterns: '#' is translated to Redis glob '*' which matches -// any characters including dots. Single-token matching ('*') is not -// faithfully represented in Redis Pub/Sub and may match across token -// boundaries. type RedisBroker struct { client *redis.Client wg sync.WaitGroup @@ -47,26 +41,17 @@ func NewRedisBrokerFrom(client *redis.Client) *RedisBroker { } } -// Publish serializes the payload as JSON and publishes it to the -// given Redis channel. -func (broker *RedisBroker) Publish(ctx context.Context, event string, payload any) error { +// Publish sends raw payload bytes to the given Redis channel. +func (broker *RedisBroker) Publish(ctx context.Context, event string, payload []byte) error { if err := validateEvent(event); err != nil { return err } - encoded, err := json.Marshal(payload) - - if err != nil { - return err - } - - return broker.client.Publish(ctx, event, encoded).Err() + return broker.client.Publish(ctx, event, payload).Err() } // Subscribe registers a handler for messages matching the given // event pattern. The "#" wildcard is translated to Redis's "*" glob. -// It returns an unsubscribe function that closes the subscription -// and waits for the delivery goroutine to finish. func (broker *RedisBroker) Subscribe( ctx context.Context, event string, @@ -92,9 +77,7 @@ func (broker *RedisBroker) Subscribe( } }() - handler(func(dest any) error { - return json.Unmarshal([]byte(message.Payload), dest) - }) + handler([]byte(message.Payload)) }() } }() diff --git a/framework/go.mod b/framework/go.mod index 214706e..01c6003 100644 --- a/framework/go.mod +++ b/framework/go.mod @@ -6,7 +6,6 @@ require ( github.com/eclipse/paho.golang v0.23.0 github.com/jmoiron/sqlx v1.4.0 github.com/matthewhartstonge/argon2 v1.4.3 - github.com/mattn/go-sqlite3 v1.14.42 github.com/nats-io/nats.go v1.48.0 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/rabbitmq/amqp091-go v1.10.0 @@ -26,6 +25,7 @@ require ( github.com/gorilla/websocket v1.5.3 // indirect github.com/klauspost/compress v1.18.2 // indirect github.com/kr/pretty v0.3.1 // indirect + github.com/mattn/go-sqlite3 v1.14.42 // indirect github.com/nats-io/nkeys v0.4.12 // indirect github.com/nats-io/nuid v1.0.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect diff --git a/framework/session/cache.go b/framework/session/cache.go index edb0ad8..a212ef0 100644 --- a/framework/session/cache.go +++ b/framework/session/cache.go @@ -2,51 +2,52 @@ package session import ( "context" - "errors" + "encoding/json" "fmt" "time" "github.com/studiolambda/cosmos/contract" ) -// CacheDriver implements contract.SessionDriver by storing sessions -// in any contract.Cache backend. Sessions are keyed with a -// configurable prefix to avoid collisions with other cached data. +// CacheDriver implements [contract.SessionDriver] by storing sessions +// in any [contract.CacheDriver] backend. Sessions are JSON-serialized +// and keyed with a configurable prefix to avoid collisions. // -// WARNING: Session data is stored as-is without encryption. When -// using a remote backend such as Redis, session contents are -// transmitted and persisted in plaintext. For applications that -// store sensitive data in sessions, callers should either encrypt -// values before calling Put or use a cache backend that provides -// transport and at-rest encryption (e.g., Redis with TLS and disk -// encryption). +// WARNING: Session data is stored without encryption. When using a +// remote backend such as Redis, session contents are transmitted and +// persisted in plaintext. For sensitive data, callers should encrypt +// values before calling Put or use a backend with transport/at-rest +// encryption. type CacheDriver struct { - cache contract.Cache + cache contract.CacheDriver options CacheDriverOptions } // CacheDriverOptions holds configuration for the CacheDriver. -// The Prefix is prepended to session IDs when forming cache keys. type CacheDriverOptions struct { Prefix string } -// ErrCacheDriverInvalidType is returned when a value retrieved from the -// cache cannot be type-asserted to contract.Session, indicating a -// serialization mismatch or cache key collision. -var ErrCacheDriverInvalidType = errors.New("invalid cache type") +// sessionData is the serializable representation of a session for +// storage in the cache backend. +type sessionData struct { + ID string `json:"id"` + CreatedAt time.Time `json:"created_at"` + ExpiresAt time.Time `json:"expires_at"` + Storage map[string]any `json:"storage"` +} // NewCacheDriver creates a CacheDriver with the default key prefix -// "cosmos.sessions". Use NewCacheDriverWith for custom options. -func NewCacheDriver(cache contract.Cache) *CacheDriver { +// "cosmos.sessions". +func NewCacheDriver(cache contract.CacheDriver) *CacheDriver { return NewCacheDriverWith(cache, CacheDriverOptions{ Prefix: "cosmos.sessions", }) } // NewCacheDriverWith creates a CacheDriver with the given cache -// backend and options, allowing a custom key prefix. -func NewCacheDriverWith(cache contract.Cache, options CacheDriverOptions) *CacheDriver { +// backend and options. +func NewCacheDriverWith(cache contract.CacheDriver, options CacheDriverOptions) *CacheDriver { return &CacheDriver{ cache: cache, options: options, @@ -59,27 +60,39 @@ func (driver *CacheDriver) key(id string) string { return fmt.Sprintf("%s.%s", driver.options.Prefix, id) } -// Get retrieves a session from the cache by its ID. It returns -// ErrCacheDriverInvalidType if the cached value is not a valid -// contract.Session. -func (driver *CacheDriver) Get(ctx context.Context, id string) (contract.Session, error) { - cacheKey := driver.key(id) - value, err := driver.cache.Get(ctx, cacheKey) +// Get retrieves a session from the cache by its ID. +func (driver *CacheDriver) Get(ctx context.Context, id string) (*contract.Session, error) { + raw, err := driver.cache.Get(ctx, driver.key(id)) if err != nil { return nil, err } - if session, ok := value.(contract.Session); ok { - return session, nil + var data sessionData + + if err := json.Unmarshal(raw, &data); err != nil { + return nil, err } - return nil, ErrCacheDriverInvalidType + return contract.NewSessionFrom(data.ID, data.CreatedAt, data.ExpiresAt, data.Storage), nil } // Save persists a session in the cache with the given TTL. -func (driver *CacheDriver) Save(ctx context.Context, session contract.Session, ttl time.Duration) error { - return driver.cache.Put(ctx, driver.key(session.SessionID()), session, ttl) +func (driver *CacheDriver) Save(ctx context.Context, session *contract.Session, ttl time.Duration) error { + data := sessionData{ + ID: session.SessionID(), + CreatedAt: session.CreatedAt(), + ExpiresAt: session.ExpiresAt(), + Storage: session.All(), + } + + raw, err := json.Marshal(data) + + if err != nil { + return err + } + + return driver.cache.Put(ctx, driver.key(session.SessionID()), raw, ttl) } // Delete removes a session from the cache by its ID. diff --git a/framework/session/cache_test.go b/framework/session/cache_test.go index 39b827f..96b5c96 100644 --- a/framework/session/cache_test.go +++ b/framework/session/cache_test.go @@ -18,30 +18,38 @@ func TestCacheDriverGetReturnsSession(t *testing.T) { t.Parallel() ctx := context.Background() - cacheMock := mock.NewCacheMock(t) - sessionMock := mock.NewSessionMock(t) + cacheMock := mock.NewCacheDriverMock(t) + sess := contract.NewSessionFrom("abc123", time.Now(), time.Now().Add(time.Hour), map[string]any{"user": "test"}) + + // The CacheDriver stores JSON; simulate what Save would have stored. cacheMock.On( "Get", tmock.Anything, "cosmos.sessions.abc123", - ).Return(sessionMock, nil).Once() + ).Return([]byte(`{"id":"abc123","created_at":"2025-01-01T00:00:00Z","expires_at":"2025-01-01T01:00:00Z","storage":{"user":"test"}}`), nil).Once() driver := session.NewCacheDriver(cacheMock) - sess, err := driver.Get(ctx, "abc123") + result, err := driver.Get(ctx, "abc123") require.NoError(t, err) - require.Equal(t, sessionMock, sess) + require.Equal(t, "abc123", result.SessionID()) + + val, ok := result.Get("user") + require.True(t, ok) + require.Equal(t, "test", val) + + _ = sess // reference to avoid unused } func TestCacheDriverGetReturnsErrorWhenCacheFails(t *testing.T) { t.Parallel() ctx := context.Background() - cacheMock := mock.NewCacheMock(t) + cacheMock := mock.NewCacheDriverMock(t) cacheErr := errors.New("cache failure") cacheMock.On( "Get", tmock.Anything, "cosmos.sessions.abc123", - ).Return(nil, cacheErr).Once() + ).Return([]byte(nil), cacheErr).Once() driver := session.NewCacheDriver(cacheMock) _, err := driver.Get(ctx, "abc123") @@ -49,41 +57,41 @@ func TestCacheDriverGetReturnsErrorWhenCacheFails(t *testing.T) { require.ErrorIs(t, err, cacheErr) } -func TestCacheDriverGetReturnsInvalidTypeError(t *testing.T) { +func TestCacheDriverGetReturnsErrorForInvalidJSON(t *testing.T) { t.Parallel() ctx := context.Background() - cacheMock := mock.NewCacheMock(t) + cacheMock := mock.NewCacheDriverMock(t) cacheMock.On( "Get", tmock.Anything, "cosmos.sessions.abc123", - ).Return("not-a-session", nil).Once() + ).Return([]byte("not-json"), nil).Once() driver := session.NewCacheDriver(cacheMock) _, err := driver.Get(ctx, "abc123") - require.ErrorIs(t, err, session.ErrCacheDriverInvalidType) + require.Error(t, err) } func TestCacheDriverSavePersistsSession(t *testing.T) { t.Parallel() ctx := context.Background() - cacheMock := mock.NewCacheMock(t) - sessionMock := mock.NewSessionMock(t) + cacheMock := mock.NewCacheDriverMock(t) ttl := 2 * time.Hour - sessionMock.On("SessionID").Return("session-id-123").Once() + sess := contract.NewSessionFrom("session-id-123", time.Now(), time.Now().Add(ttl), map[string]any{"key": "val"}) + cacheMock.On( "Put", tmock.Anything, "cosmos.sessions.session-id-123", - sessionMock, + tmock.AnythingOfType("[]uint8"), ttl, ).Return(nil).Once() driver := session.NewCacheDriver(cacheMock) - err := driver.Save(ctx, sessionMock, ttl) + err := driver.Save(ctx, sess, ttl) require.NoError(t, err) } @@ -92,7 +100,7 @@ func TestCacheDriverDeleteRemovesSession(t *testing.T) { t.Parallel() ctx := context.Background() - cacheMock := mock.NewCacheMock(t) + cacheMock := mock.NewCacheDriverMock(t) cacheMock.On( "Delete", tmock.Anything, "cosmos.sessions.abc123", @@ -108,31 +116,30 @@ func TestCacheDriverWithUsesEmptyPrefixWhenDefault(t *testing.T) { t.Parallel() ctx := context.Background() - cacheMock := mock.NewCacheMock(t) - sessionMock := mock.NewSessionMock(t) + cacheMock := mock.NewCacheDriverMock(t) cacheMock.On( "Get", tmock.Anything, ".abc123", - ).Return(sessionMock, nil).Once() + ).Return([]byte(`{"id":"abc123","created_at":"2025-01-01T00:00:00Z","expires_at":"2025-01-01T01:00:00Z","storage":{}}`), nil).Once() driver := session.NewCacheDriverWith( cacheMock, session.CacheDriverOptions{}, ) - sess, err := driver.Get(ctx, "abc123") + result, err := driver.Get(ctx, "abc123") require.NoError(t, err) - require.Equal(t, sessionMock, sess) + require.Equal(t, "abc123", result.SessionID()) } func TestCacheDriverGetReturnsNotFoundError(t *testing.T) { t.Parallel() ctx := context.Background() - cacheMock := mock.NewCacheMock(t) + cacheMock := mock.NewCacheDriverMock(t) cacheMock.On( "Get", tmock.Anything, "cosmos.sessions.missing", - ).Return(nil, contract.ErrCacheKeyNotFound).Once() + ).Return([]byte(nil), contract.ErrCacheKeyNotFound).Once() driver := session.NewCacheDriver(cacheMock) _, err := driver.Get(ctx, "missing") diff --git a/framework/session/middleware.go b/framework/session/middleware.go index 49a0adf..ad4900a 100644 --- a/framework/session/middleware.go +++ b/framework/session/middleware.go @@ -33,16 +33,11 @@ type MiddlewareOptions struct { // Partitioned enables the CHIPS partitioned cookie attribute. Partitioned bool - // TTL is the total lifetime of a session from creation or - // renewal. + // TTL is the total lifetime of a session from creation or renewal. TTL time.Duration // MaxLifetime is the absolute maximum duration a session may // exist from its initial creation, regardless of activity. - // When a session exceeds this age, it is force-regenerated - // on the next request. This prevents indefinite session - // extension through continued use. - // // A zero value disables the absolute lifetime check. // Default: 24 hours. MaxLifetime time.Duration @@ -52,16 +47,10 @@ type MiddlewareOptions struct { ExpirationDelta time.Duration // Key is the context key under which the session is stored. - // Defaults to contract.SessionKey when using Middleware. Key any // ErrorHandler is an optional callback invoked when internal - // session operations (save, delete, regenerate) fail. When - // nil, errors from these operations are silently discarded - // to preserve backward compatibility. - // - // Callers should use this to log or monitor session driver - // errors in production. + // session operations fail. When nil, errors are silently discarded. ErrorHandler func(error) } @@ -76,27 +65,19 @@ const ( // DefaultTTL is the default total session lifetime. DefaultTTL = 2 * time.Hour - // DefaultMaxLifetime is the default absolute maximum session - // age from initial creation. After this duration, the session - // is force-regenerated regardless of activity. + // DefaultMaxLifetime is the default absolute maximum session age. DefaultMaxLifetime = 24 * time.Hour ) -// expectedSessionIDLength is the expected length of a valid -// session ID string. A 32-byte random value encoded with -// base64url (no padding) produces exactly 43 characters. +// expectedSessionIDLength is the expected length of a valid session ID. const expectedSessionIDLength = 43 -// validSessionIDPattern matches exactly 43 base64url characters -// (A-Z, a-z, 0-9, hyphen, underscore) with no padding. +// validSessionIDPattern matches exactly 43 base64url characters. var validSessionIDPattern = regexp.MustCompile( `^[A-Za-z0-9_-]{43}$`, ) -// validSessionID reports whether the given ID has the expected -// format for a session identifier: exactly 43 characters using -// only the base64url alphabet. This prevents cache key injection -// and avoids unnecessary cache lookups for malformed IDs. +// validSessionID reports whether the given ID has the expected format. func validSessionID(id string) bool { if len(id) != expectedSessionIDLength { return false @@ -106,13 +87,8 @@ func validSessionID(id string) bool { } // currentSession loads an existing session from the cookie-provided -// ID or creates a fresh one when no valid session is found. The -// cookie value is validated against the expected session ID format -// before performing a cache lookup. Expired sessions and sessions -// that exceed MaxLifetime are deleted from the driver and replaced -// with a fresh session to prevent stale auth data from reaching -// the handler. -func currentSession(r *http.Request, driver contract.SessionDriver, options MiddlewareOptions) (contract.Session, error) { +// ID or creates a fresh one when no valid session is found. +func currentSession(r *http.Request, driver contract.SessionDriver, options MiddlewareOptions) (*contract.Session, error) { id := request.CookieValue(r, options.Name) if id != "" && validSessionID(id) { @@ -120,13 +96,13 @@ func currentSession(r *http.Request, driver contract.SessionDriver, options Midd if session.HasExpired() { _ = driver.Delete(r.Context(), id) - return NewSession(time.Now().Add(options.TTL), map[string]any{}) + return contract.NewSession(time.Now().Add(options.TTL), map[string]any{}) } if options.MaxLifetime > 0 && time.Since(session.CreatedAt()) >= options.MaxLifetime { _ = driver.Delete(r.Context(), id) - return NewSession(time.Now().Add(options.TTL), map[string]any{}) + return contract.NewSession(time.Now().Add(options.TTL), map[string]any{}) } session.MarkAsUnchanged() @@ -135,17 +111,11 @@ func currentSession(r *http.Request, driver contract.SessionDriver, options Midd } } - return NewSession(time.Now().Add(options.TTL), map[string]any{}) + return contract.NewSession(time.Now().Add(options.TTL), map[string]any{}) } // withDefaults returns a copy of the options with secure defaults -// applied to any zero-valued fields. This ensures that callers who -// construct a partial MiddlewareOptions still get sensible cookie -// attributes (SameSite=Lax, Path="/") and sensible session -// parameters (DefaultCookie name, DefaultTTL, DefaultMaxLifetime, -// DefaultExpirationDelta, contract.SessionKey). The Secure flag -// is NOT defaulted so that callers can set it to false for local -// development; use [Middleware] for secure-by-default behaviour. +// applied to any zero-valued fields. func (options MiddlewareOptions) withDefaults() MiddlewareOptions { if options.Name == "" { options.Name = DefaultCookie @@ -179,8 +149,6 @@ func (options MiddlewareOptions) withDefaults() MiddlewareOptions { } // reportError invokes the configured error handler if set. -// When no handler is configured, the error is silently discarded -// to preserve backward compatibility. func reportError(options MiddlewareOptions, err error) { if err != nil && options.ErrorHandler != nil { options.ErrorHandler(err) @@ -188,15 +156,7 @@ func reportError(options MiddlewareOptions, err error) { } // MiddlewareWith returns a session middleware configured with the -// given driver and options. It loads or creates a session per -// request, attaches it to the context, and persists changes via a -// BeforeWriteHeader hook that handles expiration, regeneration, -// and cookie updates. Zero-valued option fields are replaced with -// secure defaults via withDefaults. -// -// When MaxLifetime is set and the session's age (measured from -// its CreatedAt timestamp) exceeds that duration, the session is -// force-regenerated to prevent indefinite extension. +// given driver and options. func MiddlewareWith(driver contract.SessionDriver, options MiddlewareOptions) framework.Middleware { options = options.withDefaults() @@ -275,10 +235,7 @@ func MiddlewareWith(driver contract.SessionDriver, options MiddlewareOptions) fr } // Middleware returns a session middleware using the given driver and -// sensible defaults: cookie name "cosmos.session", 2-hour TTL, -// 24-hour max lifetime, 15-minute expiration delta, secure + -// HttpOnly + SameSite=Lax, stored under contract.SessionKey in -// the request context. +// sensible defaults. func Middleware(driver contract.SessionDriver) framework.Middleware { return MiddlewareWith(driver, MiddlewareOptions{ Name: DefaultCookie, diff --git a/framework/session/middleware_test.go b/framework/session/middleware_test.go index 5e25a14..1812fb1 100644 --- a/framework/session/middleware_test.go +++ b/framework/session/middleware_test.go @@ -37,14 +37,14 @@ func TestMiddlewareCookieExists(t *testing.T) { require.Len(t, cookies, 1) require.Len(t, cache.Calls, 1) - require.Equal(t, cache.Calls[0].Arguments.Get(1).(contract.Session).SessionID(), cookies[0].Value) + require.Equal(t, cache.Calls[0].Arguments.Get(1).(*contract.Session).SessionID(), cookies[0].Value) require.Equal(t, session.DefaultCookie, cookies[0].Name) } func TestMiddlewareLoadsExistingSession(t *testing.T) { t.Parallel() - existingSession, err := session.NewSession( + existingSession, err := contract.NewSession( time.Now().Add(2*time.Hour), map[string]any{"user_id": 42}, ) @@ -159,7 +159,7 @@ func TestMiddlewareCreatesNewSessionWhenDriverFails(t *testing.T) { func TestMiddlewareWithExpiredSessionRegenerates(t *testing.T) { t.Parallel() - expiredSession, err := session.NewSession( + expiredSession, err := contract.NewSession( time.Now().Add(-1*time.Hour), map[string]any{}, ) @@ -203,7 +203,7 @@ func TestMiddlewareWithExpiredSessionRegenerates(t *testing.T) { func TestMiddlewareWithExpirationDeltaExtendsSession(t *testing.T) { t.Parallel() - soonSession, err := session.NewSession( + soonSession, err := contract.NewSession( time.Now().Add(5*time.Minute), map[string]any{}, ) @@ -309,7 +309,7 @@ func TestMiddlewareErrorHandlerCalledOnSaveError(t *testing.T) { func TestMiddlewareSessionNotSavedWhenUnchanged(t *testing.T) { t.Parallel() - existingSession, err := session.NewSession( + existingSession, err := contract.NewSession( time.Now().Add(2*time.Hour), map[string]any{}, ) @@ -383,7 +383,7 @@ func TestMiddlewareDoesNotSetCookieWhenSaveFails(t *testing.T) { func TestMiddlewareRegenerateDeletesOldSession(t *testing.T) { t.Parallel() - existingSession, err := session.NewSession( + existingSession, err := contract.NewSession( time.Now().Add(2*time.Hour), map[string]any{}, ) @@ -430,7 +430,7 @@ func TestMiddlewareRegenerateDeletesOldSession(t *testing.T) { func TestMiddlewareCreatesNewSessionWhenExpired(t *testing.T) { t.Parallel() - expiredSession, err := session.NewSession( + expiredSession, err := contract.NewSession( time.Now().Add(-1*time.Hour), map[string]any{"stale": true}, ) @@ -484,7 +484,7 @@ func TestMiddlewareCreatesNewSessionWhenExpired(t *testing.T) { func TestMiddlewareWithDefaultsMaxLifetime(t *testing.T) { t.Parallel() - oldSession, err := session.NewSession( + oldSession, err := contract.NewSession( time.Now().Add(2*time.Hour), map[string]any{"user_id": 7}, ) diff --git a/framework/session/session.go b/framework/session/session.go index 60db814..b975db6 100644 --- a/framework/session/session.go +++ b/framework/session/session.go @@ -1,268 +1,6 @@ +// Package session provides session middleware and drivers for the +// Cosmos framework. The [contract.Session] struct holds session data, +// and [contract.SessionDriver] defines the persistence interface. +// This package provides middleware that integrates sessions into +// HTTP request handling and driver implementations. package session - -import ( - "crypto/rand" - "encoding/base64" - "sync" - "time" -) - -// Session represents an HTTP session with data storage, expiration -// management, and change tracking. It maintains the current and -// original session IDs, supports key-value storage, and tracks -// modifications for persistence decisions. Access to the session -// is protected by a mutex to ensure thread-safe operations. -type Session struct { - // originalID is the session ID assigned when the session was - // first created. This value remains constant even if the - // session is regenerated. - originalID string - - // id is the current session identifier. This may differ from - // OriginalID if the session has been regenerated (e.g., for - // security purposes after authentication). - id string - - // createdAt records the absolute time the session was first - // created. This timestamp is immutable and used by the - // middleware to enforce a maximum absolute session lifetime, - // preventing indefinite session extension through activity. - createdAt time.Time - - // expiresAt is the time at which the session will expire - // and be considered invalid. - expiresAt time.Time - - // storage holds the session data as key-value pairs. Keys - // are strings and values can be of any type. - storage map[string]any - - // mutex protects concurrent access to the session fields. - mutex sync.Mutex - - // changed tracks whether the session data has been modified - // since it was loaded, indicating whether it needs to be - // persisted. - changed bool -} - -// sessionIDLength is the number of random bytes used to generate -// a session ID. 32 bytes provides 256 bits of entropy, which is -// encoded to a 43-character base64url string. -const sessionIDLength = 32 - -// generateSessionID generates a cryptographically random session -// ID using crypto/rand and base64url encoding. The resulting ID -// is 43 characters long with 256 bits of entropy and contains no -// embedded timestamp, unlike UUID v7. -func generateSessionID() (string, error) { - b := make([]byte, sessionIDLength) - - _, err := rand.Read(b) - - if err != nil { - return "", err - } - - return base64.RawURLEncoding.EncodeToString(b), nil -} - -// NewSession creates a new session with the specified expiration -// time and initial storage data. It generates a cryptographically -// random session ID for both the original and current session IDs. -// The session is marked as changed to ensure it is persisted on -// first save. It returns an error if ID generation fails. -func NewSession(expiresAt time.Time, storage map[string]any) (*Session, error) { - id, err := generateSessionID() - - if err != nil { - return nil, err - } - - return &Session{ - originalID: id, - id: id, - createdAt: time.Now(), - expiresAt: expiresAt, - storage: storage, - // Mark changed so the session is persisted on first save. - changed: true, - }, nil -} - -// SessionID returns the current session identifier. This may differ from the original -// session ID if the session has been regenerated. -func (session *Session) SessionID() string { - session.mutex.Lock() - defer session.mutex.Unlock() - - return session.id -} - -// OriginalSessionID returns the session identifier that was assigned when the session -// was initially created. This value does not change even if the session is regenerated. -func (session *Session) OriginalSessionID() string { - session.mutex.Lock() - defer session.mutex.Unlock() - - return session.originalID -} - -// Get retrieves a value from the session storage by key. It returns the value and a -// boolean indicating whether the key exists in the session. -func (session *Session) Get(key string) (any, bool) { - session.mutex.Lock() - defer session.mutex.Unlock() - - value, ok := session.storage[key] - - return value, ok -} - -// Put stores a value in the session storage associated with the -// given key. If the key already exists, its value is overwritten. -// This operation marks the session as changed. -// -// WARNING: When storing authentication-related state (e.g., -// user IDs, roles, or privilege levels), callers MUST call -// [Session.Regenerate] immediately after to prevent session -// fixation attacks. Failing to regenerate the session ID -// after authentication changes allows an attacker who knows -// the pre-authentication session ID to hijack the elevated -// session. -func (session *Session) Put(key string, value any) { - session.mutex.Lock() - defer session.mutex.Unlock() - - session.storage[key] = value - session.changed = true -} - -// Delete removes a value from the session storage by key. If the key does not exist, -// the storage is unaffected but the session is still marked as changed. -func (session *Session) Delete(key string) { - session.mutex.Lock() - defer session.mutex.Unlock() - - delete(session.storage, key) - session.changed = true -} - -// Extend updates the session's expiration time to the specified time. This is useful -// for extending the session's lifetime during active use. This operation marks the -// session as changed. -func (session *Session) Extend(expiresAt time.Time) { - session.mutex.Lock() - defer session.mutex.Unlock() - - session.expiresAt = expiresAt - session.changed = true -} - -// Regenerate generates a new cryptographically random session ID -// for the session. This is commonly used for security purposes -// such as preventing session fixation attacks after user -// authentication. The original session ID is preserved and can be -// retrieved via OriginalSessionID. This operation marks the -// session as changed. It returns an error if ID generation fails. -// -// WARNING: This method MUST be called after any authentication -// state change (login, logout, privilege escalation). Without -// regeneration, an attacker who obtained the session ID before -// authentication can use it to access the authenticated session. -func (session *Session) Regenerate() error { - id, err := generateSessionID() - - if err != nil { - return err - } - - session.mutex.Lock() - defer session.mutex.Unlock() - - session.id = id - session.changed = true - - return nil -} - -// Clear removes all data from the session storage while keeping the session itself intact. -// The session ID and expiration time remain unchanged. This operation marks the session -// as changed. -func (session *Session) Clear() { - session.mutex.Lock() - defer session.mutex.Unlock() - - clear(session.storage) - session.changed = true -} - -// ExpiresAt returns the time at which the session will expire -// and be considered invalid. -func (session *Session) ExpiresAt() time.Time { - session.mutex.Lock() - defer session.mutex.Unlock() - - return session.expiresAt -} - -// CreatedAt returns the absolute time the session was first -// created. This value never changes, even if the session is -// regenerated or extended. It is used by the middleware to -// enforce [MiddlewareOptions.MaxLifetime]. -func (session *Session) CreatedAt() time.Time { - session.mutex.Lock() - defer session.mutex.Unlock() - - return session.createdAt -} - -// HasExpired checks whether the session has already expired by comparing the current -// time against the expiration time. -func (session *Session) HasExpired() bool { - session.mutex.Lock() - defer session.mutex.Unlock() - - return time.Now().After(session.expiresAt) -} - -// ExpiresSoon checks whether the session will expire within the specified duration -// from the current time. This is useful for triggering session renewal prompts or -// warnings before the session actually expires. -func (session *Session) ExpiresSoon(delta time.Duration) bool { - session.mutex.Lock() - defer session.mutex.Unlock() - - now := time.Now() - warningTime := session.expiresAt.Add(-delta) - - return now.After(warningTime) && now.Before(session.expiresAt) -} - -// HasChanged returns true if the session data has been modified since it was loaded. -// This is useful for determining whether the session needs to be persisted to storage. -func (session *Session) HasChanged() bool { - session.mutex.Lock() - defer session.mutex.Unlock() - - return session.changed -} - -// HasRegenerated returns true if the session has been regenerated, meaning the current -// session ID differs from the original session ID. This is useful for determining whether -// an updated session identifier should be sent to the client. -func (session *Session) HasRegenerated() bool { - session.mutex.Lock() - defer session.mutex.Unlock() - - return session.id != session.originalID -} - -// MarkAsUnchanged sets the session as if nothing has changed, therefore avoiding saving -// the session when the request finishes. -func (session *Session) MarkAsUnchanged() { - session.mutex.Lock() - defer session.mutex.Unlock() - - session.changed = false -} diff --git a/framework/session/session_test.go b/framework/session/session_test.go index a7110b2..770f5a1 100644 --- a/framework/session/session_test.go +++ b/framework/session/session_test.go @@ -4,7 +4,7 @@ import ( "testing" "time" - "github.com/studiolambda/cosmos/framework/session" + "github.com/studiolambda/cosmos/contract" "github.com/stretchr/testify/require" ) @@ -12,7 +12,7 @@ import ( func TestNewSessionReturnsNonNilSession(t *testing.T) { t.Parallel() - sess, err := session.NewSession( + sess, err := contract.NewSession( time.Now().Add(2*time.Hour), map[string]any{}, ) @@ -24,7 +24,7 @@ func TestNewSessionReturnsNonNilSession(t *testing.T) { func TestNewSessionGeneratesSessionID(t *testing.T) { t.Parallel() - sess, err := session.NewSession( + sess, err := contract.NewSession( time.Now().Add(2*time.Hour), map[string]any{}, ) @@ -36,7 +36,7 @@ func TestNewSessionGeneratesSessionID(t *testing.T) { func TestNewSessionSetsOriginalIDSameAsSessionID(t *testing.T) { t.Parallel() - sess, err := session.NewSession( + sess, err := contract.NewSession( time.Now().Add(2*time.Hour), map[string]any{}, ) @@ -48,7 +48,7 @@ func TestNewSessionSetsOriginalIDSameAsSessionID(t *testing.T) { func TestNewSessionIsMarkedAsChanged(t *testing.T) { t.Parallel() - sess, err := session.NewSession( + sess, err := contract.NewSession( time.Now().Add(2*time.Hour), map[string]any{}, ) @@ -62,7 +62,7 @@ func TestNewSessionSetsCreatedAt(t *testing.T) { before := time.Now() - sess, err := session.NewSession( + sess, err := contract.NewSession( time.Now().Add(2*time.Hour), map[string]any{}, ) @@ -77,7 +77,7 @@ func TestNewSessionSetsExpiresAt(t *testing.T) { expiresAt := time.Now().Add(2 * time.Hour) - sess, err := session.NewSession(expiresAt, map[string]any{}) + sess, err := contract.NewSession(expiresAt, map[string]any{}) require.NoError(t, err) require.Equal(t, expiresAt, sess.ExpiresAt()) @@ -88,7 +88,7 @@ func TestNewSessionWithInitialStorage(t *testing.T) { storage := map[string]any{"user_id": 42} - sess, err := session.NewSession( + sess, err := contract.NewSession( time.Now().Add(2*time.Hour), storage, ) @@ -104,13 +104,13 @@ func TestNewSessionWithInitialStorage(t *testing.T) { func TestNewSessionGeneratesUniqueIDs(t *testing.T) { t.Parallel() - sess1, err := session.NewSession( + sess1, err := contract.NewSession( time.Now().Add(2*time.Hour), map[string]any{}, ) require.NoError(t, err) - sess2, err := session.NewSession( + sess2, err := contract.NewSession( time.Now().Add(2*time.Hour), map[string]any{}, ) @@ -122,7 +122,7 @@ func TestNewSessionGeneratesUniqueIDs(t *testing.T) { func TestSessionGetReturnsStoredValue(t *testing.T) { t.Parallel() - sess, err := session.NewSession( + sess, err := contract.NewSession( time.Now().Add(2*time.Hour), map[string]any{"key": "value"}, ) @@ -138,7 +138,7 @@ func TestSessionGetReturnsStoredValue(t *testing.T) { func TestSessionGetReturnsFalseForMissingKey(t *testing.T) { t.Parallel() - sess, err := session.NewSession( + sess, err := contract.NewSession( time.Now().Add(2*time.Hour), map[string]any{}, ) @@ -153,7 +153,7 @@ func TestSessionGetReturnsFalseForMissingKey(t *testing.T) { func TestSessionPutStoresValue(t *testing.T) { t.Parallel() - sess, err := session.NewSession( + sess, err := contract.NewSession( time.Now().Add(2*time.Hour), map[string]any{}, ) @@ -172,7 +172,7 @@ func TestSessionPutStoresValue(t *testing.T) { func TestSessionPutMarksAsChanged(t *testing.T) { t.Parallel() - sess, err := session.NewSession( + sess, err := contract.NewSession( time.Now().Add(2*time.Hour), map[string]any{}, ) @@ -188,7 +188,7 @@ func TestSessionPutMarksAsChanged(t *testing.T) { func TestSessionPutOverwritesExistingValue(t *testing.T) { t.Parallel() - sess, err := session.NewSession( + sess, err := contract.NewSession( time.Now().Add(2*time.Hour), map[string]any{"key": "old"}, ) @@ -206,7 +206,7 @@ func TestSessionPutOverwritesExistingValue(t *testing.T) { func TestSessionDeleteRemovesKey(t *testing.T) { t.Parallel() - sess, err := session.NewSession( + sess, err := contract.NewSession( time.Now().Add(2*time.Hour), map[string]any{"key": "value"}, ) @@ -223,7 +223,7 @@ func TestSessionDeleteRemovesKey(t *testing.T) { func TestSessionDeleteMarksAsChanged(t *testing.T) { t.Parallel() - sess, err := session.NewSession( + sess, err := contract.NewSession( time.Now().Add(2*time.Hour), map[string]any{"key": "value"}, ) @@ -239,7 +239,7 @@ func TestSessionDeleteMarksAsChanged(t *testing.T) { func TestSessionDeleteMissingKeyIsNoOp(t *testing.T) { t.Parallel() - sess, err := session.NewSession( + sess, err := contract.NewSession( time.Now().Add(2*time.Hour), map[string]any{}, ) @@ -255,7 +255,7 @@ func TestSessionDeleteMissingKeyIsNoOp(t *testing.T) { func TestSessionClearRemovesAllData(t *testing.T) { t.Parallel() - sess, err := session.NewSession( + sess, err := contract.NewSession( time.Now().Add(2*time.Hour), map[string]any{"a": 1, "b": 2, "c": 3}, ) @@ -276,7 +276,7 @@ func TestSessionClearRemovesAllData(t *testing.T) { func TestSessionClearMarksAsChanged(t *testing.T) { t.Parallel() - sess, err := session.NewSession( + sess, err := contract.NewSession( time.Now().Add(2*time.Hour), map[string]any{"key": "value"}, ) @@ -292,7 +292,7 @@ func TestSessionClearMarksAsChanged(t *testing.T) { func TestSessionExtendUpdatesExpiresAt(t *testing.T) { t.Parallel() - sess, err := session.NewSession( + sess, err := contract.NewSession( time.Now().Add(1*time.Hour), map[string]any{}, ) @@ -308,7 +308,7 @@ func TestSessionExtendUpdatesExpiresAt(t *testing.T) { func TestSessionExtendMarksAsChanged(t *testing.T) { t.Parallel() - sess, err := session.NewSession( + sess, err := contract.NewSession( time.Now().Add(1*time.Hour), map[string]any{}, ) @@ -324,7 +324,7 @@ func TestSessionExtendMarksAsChanged(t *testing.T) { func TestSessionRegenerateChangesSessionID(t *testing.T) { t.Parallel() - sess, err := session.NewSession( + sess, err := contract.NewSession( time.Now().Add(2*time.Hour), map[string]any{}, ) @@ -342,7 +342,7 @@ func TestSessionRegenerateChangesSessionID(t *testing.T) { func TestSessionRegeneratePreservesOriginalID(t *testing.T) { t.Parallel() - sess, err := session.NewSession( + sess, err := contract.NewSession( time.Now().Add(2*time.Hour), map[string]any{}, ) @@ -360,7 +360,7 @@ func TestSessionRegeneratePreservesOriginalID(t *testing.T) { func TestSessionRegenerateMarksAsChanged(t *testing.T) { t.Parallel() - sess, err := session.NewSession( + sess, err := contract.NewSession( time.Now().Add(2*time.Hour), map[string]any{}, ) @@ -378,7 +378,7 @@ func TestSessionRegenerateMarksAsChanged(t *testing.T) { func TestSessionHasExpiredWhenPastExpiresAt(t *testing.T) { t.Parallel() - sess, err := session.NewSession( + sess, err := contract.NewSession( time.Now().Add(-1*time.Hour), map[string]any{}, ) @@ -390,7 +390,7 @@ func TestSessionHasExpiredWhenPastExpiresAt(t *testing.T) { func TestSessionHasNotExpiredWhenBeforeExpiresAt(t *testing.T) { t.Parallel() - sess, err := session.NewSession( + sess, err := contract.NewSession( time.Now().Add(2*time.Hour), map[string]any{}, ) @@ -402,7 +402,7 @@ func TestSessionHasNotExpiredWhenBeforeExpiresAt(t *testing.T) { func TestSessionExpiresSoonWhenWithinDelta(t *testing.T) { t.Parallel() - sess, err := session.NewSession( + sess, err := contract.NewSession( time.Now().Add(5*time.Minute), map[string]any{}, ) @@ -414,7 +414,7 @@ func TestSessionExpiresSoonWhenWithinDelta(t *testing.T) { func TestSessionExpiresSoonReturnsFalseWhenFarFromExpiry(t *testing.T) { t.Parallel() - sess, err := session.NewSession( + sess, err := contract.NewSession( time.Now().Add(2*time.Hour), map[string]any{}, ) @@ -426,7 +426,7 @@ func TestSessionExpiresSoonReturnsFalseWhenFarFromExpiry(t *testing.T) { func TestSessionExpiresSoonReturnsFalseWhenAlreadyExpired(t *testing.T) { t.Parallel() - sess, err := session.NewSession( + sess, err := contract.NewSession( time.Now().Add(-1*time.Hour), map[string]any{}, ) @@ -438,7 +438,7 @@ func TestSessionExpiresSoonReturnsFalseWhenAlreadyExpired(t *testing.T) { func TestSessionHasRegeneratedAfterRegenerate(t *testing.T) { t.Parallel() - sess, err := session.NewSession( + sess, err := contract.NewSession( time.Now().Add(2*time.Hour), map[string]any{}, ) @@ -455,7 +455,7 @@ func TestSessionHasRegeneratedAfterRegenerate(t *testing.T) { func TestSessionMarkAsUnchangedResetsChangedFlag(t *testing.T) { t.Parallel() - sess, err := session.NewSession( + sess, err := contract.NewSession( time.Now().Add(2*time.Hour), map[string]any{}, ) @@ -471,7 +471,7 @@ func TestSessionMarkAsUnchangedResetsChangedFlag(t *testing.T) { func TestSessionRegenerateGeneratesValidID(t *testing.T) { t.Parallel() - sess, err := session.NewSession( + sess, err := contract.NewSession( time.Now().Add(2*time.Hour), map[string]any{}, ) @@ -487,7 +487,7 @@ func TestSessionRegenerateGeneratesValidID(t *testing.T) { func TestSessionPreservesDataAfterRegenerate(t *testing.T) { t.Parallel() - sess, err := session.NewSession( + sess, err := contract.NewSession( time.Now().Add(2*time.Hour), map[string]any{"user_id": 42}, )