store: add mock store
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
This commit is contained in:
815
store/mock/mock.go
Normal file
815
store/mock/mock.go
Normal file
@@ -0,0 +1,815 @@
|
||||
package mock
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"go.unistack.org/micro/v4/store"
|
||||
)
|
||||
|
||||
// ExpectedWrite represents an expected Write operation
|
||||
type ExpectedWrite struct {
|
||||
key string
|
||||
value interface{}
|
||||
ttl time.Duration
|
||||
metadata map[string]string
|
||||
namespace string
|
||||
times int
|
||||
called int
|
||||
mutex sync.Mutex
|
||||
err error
|
||||
}
|
||||
|
||||
func (e *ExpectedWrite) match(key string, val interface{}, opts ...store.WriteOption) bool {
|
||||
e.mutex.Lock()
|
||||
defer e.mutex.Unlock()
|
||||
|
||||
// Check key match
|
||||
if e.key != "" && e.key != key {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check value match
|
||||
if e.value != nil && !reflect.DeepEqual(e.value, val) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check options
|
||||
options := store.NewWriteOptions(opts...)
|
||||
if e.ttl > 0 && e.ttl != options.TTL {
|
||||
return false
|
||||
}
|
||||
if e.namespace != "" && e.namespace != options.Namespace {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check if we've exceeded the expected times
|
||||
if e.times > 0 && e.called >= e.times {
|
||||
return false
|
||||
}
|
||||
|
||||
e.called++
|
||||
return true
|
||||
}
|
||||
|
||||
// ExpectedRead represents an expected Read operation
|
||||
type ExpectedRead struct {
|
||||
key string
|
||||
value interface{}
|
||||
times int
|
||||
called int
|
||||
mutex sync.Mutex
|
||||
err error
|
||||
}
|
||||
|
||||
func (e *ExpectedRead) match(key string, opts ...store.ReadOption) bool {
|
||||
e.mutex.Lock()
|
||||
defer e.mutex.Unlock()
|
||||
|
||||
// Check key match
|
||||
if e.key != "" && e.key != key {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check if we've exceeded the expected times
|
||||
if e.times > 0 && e.called >= e.times {
|
||||
return false
|
||||
}
|
||||
|
||||
e.called++
|
||||
return true
|
||||
}
|
||||
|
||||
// ExpectedDelete represents an expected Delete operation
|
||||
type ExpectedDelete struct {
|
||||
key string
|
||||
times int
|
||||
called int
|
||||
mutex sync.Mutex
|
||||
err error
|
||||
}
|
||||
|
||||
func (e *ExpectedDelete) match(key string, opts ...store.DeleteOption) bool {
|
||||
e.mutex.Lock()
|
||||
defer e.mutex.Unlock()
|
||||
|
||||
// Check key match
|
||||
if e.key != "" && e.key != key {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check if we've exceeded the expected times
|
||||
if e.times > 0 && e.called >= e.times {
|
||||
return false
|
||||
}
|
||||
|
||||
e.called++
|
||||
return true
|
||||
}
|
||||
|
||||
// ExpectedExists represents an expected Exists operation
|
||||
type ExpectedExists struct {
|
||||
key string
|
||||
times int
|
||||
called int
|
||||
mutex sync.Mutex
|
||||
err error
|
||||
}
|
||||
|
||||
func (e *ExpectedExists) match(key string, opts ...store.ExistsOption) bool {
|
||||
e.mutex.Lock()
|
||||
defer e.mutex.Unlock()
|
||||
|
||||
// Check key match
|
||||
if e.key != "" && e.key != key {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check if we've exceeded the expected times
|
||||
if e.times > 0 && e.called >= e.times {
|
||||
return false
|
||||
}
|
||||
|
||||
e.called++
|
||||
return true
|
||||
}
|
||||
|
||||
// ExpectedList represents an expected List operation
|
||||
type ExpectedList struct {
|
||||
times int
|
||||
called int
|
||||
mutex sync.Mutex
|
||||
err error
|
||||
keys []string
|
||||
}
|
||||
|
||||
func (e *ExpectedList) match(opts ...store.ListOption) bool {
|
||||
e.mutex.Lock()
|
||||
defer e.mutex.Unlock()
|
||||
|
||||
// Check if we've exceeded the expected times
|
||||
if e.times > 0 && e.called >= e.times {
|
||||
return false
|
||||
}
|
||||
|
||||
e.called++
|
||||
return true
|
||||
}
|
||||
|
||||
// Store is a mock implementation of the Store interface for testing
|
||||
type Store struct {
|
||||
expectedWrites []*ExpectedWrite
|
||||
expectedReads []*ExpectedRead
|
||||
expectedDeletes []*ExpectedDelete
|
||||
expectedExists []*ExpectedExists
|
||||
expectedLists []*ExpectedList
|
||||
|
||||
data map[string]interface{}
|
||||
exists map[string]bool
|
||||
ttls map[string]time.Time // key -> expiration time
|
||||
metadata map[string]map[string]string
|
||||
err error
|
||||
opts store.Options
|
||||
mutex sync.RWMutex
|
||||
}
|
||||
|
||||
// NewStore creates a new mock store
|
||||
func NewStore(opts ...store.Option) *Store {
|
||||
options := store.NewOptions(opts...)
|
||||
return &Store{
|
||||
data: make(map[string]interface{}),
|
||||
exists: make(map[string]bool),
|
||||
ttls: make(map[string]time.Time),
|
||||
metadata: make(map[string]map[string]string),
|
||||
opts: options,
|
||||
}
|
||||
}
|
||||
|
||||
// ExpectWrite creates an expectation for a Write operation
|
||||
func (m *Store) ExpectWrite(key string) *ExpectedWrite {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
|
||||
exp := &ExpectedWrite{key: key}
|
||||
m.expectedWrites = append(m.expectedWrites, exp)
|
||||
return exp
|
||||
}
|
||||
|
||||
// ExpectRead creates an expectation for a Read operation
|
||||
func (m *Store) ExpectRead(key string) *ExpectedRead {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
|
||||
exp := &ExpectedRead{key: key}
|
||||
m.expectedReads = append(m.expectedReads, exp)
|
||||
return exp
|
||||
}
|
||||
|
||||
// ExpectDelete creates an expectation for a Delete operation
|
||||
func (m *Store) ExpectDelete(key string) *ExpectedDelete {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
|
||||
exp := &ExpectedDelete{key: key}
|
||||
m.expectedDeletes = append(m.expectedDeletes, exp)
|
||||
return exp
|
||||
}
|
||||
|
||||
// ExpectExists creates an expectation for an Exists operation
|
||||
func (m *Store) ExpectExists(key string) *ExpectedExists {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
|
||||
exp := &ExpectedExists{key: key}
|
||||
m.expectedExists = append(m.expectedExists, exp)
|
||||
return exp
|
||||
}
|
||||
|
||||
// ExpectList creates an expectation for a List operation
|
||||
func (m *Store) ExpectList() *ExpectedList {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
|
||||
exp := &ExpectedList{}
|
||||
m.expectedLists = append(m.expectedLists, exp)
|
||||
return exp
|
||||
}
|
||||
|
||||
// WithValue sets the value to return for expected operations
|
||||
func (e *ExpectedWrite) WithValue(val interface{}) *ExpectedWrite {
|
||||
e.value = val
|
||||
return e
|
||||
}
|
||||
|
||||
// WithTTL sets the TTL for expected Write operations
|
||||
func (e *ExpectedWrite) WithTTL(ttl time.Duration) *ExpectedWrite {
|
||||
e.ttl = ttl
|
||||
return e
|
||||
}
|
||||
|
||||
// WithNamespace sets the namespace for expected operations
|
||||
func (e *ExpectedWrite) WithNamespace(ns string) *ExpectedWrite {
|
||||
e.namespace = ns
|
||||
return e
|
||||
}
|
||||
|
||||
// Times sets how many times the expectation should be called
|
||||
func (e *ExpectedWrite) Times(n int) *ExpectedWrite {
|
||||
e.times = n
|
||||
return e
|
||||
}
|
||||
|
||||
// WillReturnError sets an error to return for the expected operation
|
||||
func (e *ExpectedWrite) WillReturnError(err error) *ExpectedWrite {
|
||||
e.err = err
|
||||
return e
|
||||
}
|
||||
|
||||
// WithValue sets the value to return for expected Read operations
|
||||
func (e *ExpectedRead) WithValue(val interface{}) *ExpectedRead {
|
||||
e.value = val
|
||||
return e
|
||||
}
|
||||
|
||||
// Times sets how many times the expectation should be called
|
||||
func (e *ExpectedRead) Times(n int) *ExpectedRead {
|
||||
e.times = n
|
||||
return e
|
||||
}
|
||||
|
||||
// WillReturnError sets an error to return for the expected operation
|
||||
func (e *ExpectedRead) WillReturnError(err error) *ExpectedRead {
|
||||
e.err = err
|
||||
return e
|
||||
}
|
||||
|
||||
// Times sets how many times the expectation should be called
|
||||
func (e *ExpectedDelete) Times(n int) *ExpectedDelete {
|
||||
e.times = n
|
||||
return e
|
||||
}
|
||||
|
||||
// WillReturnError sets an error to return for the expected operation
|
||||
func (e *ExpectedDelete) WillReturnError(err error) *ExpectedDelete {
|
||||
e.err = err
|
||||
return e
|
||||
}
|
||||
|
||||
// Times sets how many times the expectation should be called
|
||||
func (e *ExpectedExists) Times(n int) *ExpectedExists {
|
||||
e.times = n
|
||||
return e
|
||||
}
|
||||
|
||||
// WillReturnError sets an error to return for the expected operation
|
||||
func (e *ExpectedExists) WillReturnError(err error) *ExpectedExists {
|
||||
e.err = err
|
||||
return e
|
||||
}
|
||||
|
||||
// WillReturn sets the keys to return for List operations
|
||||
func (e *ExpectedList) WillReturn(keys ...string) *ExpectedList {
|
||||
e.keys = keys
|
||||
return e
|
||||
}
|
||||
|
||||
// Times sets how many times the expectation should be called
|
||||
func (e *ExpectedList) Times(n int) *ExpectedList {
|
||||
e.times = n
|
||||
return e
|
||||
}
|
||||
|
||||
// WillReturnError sets an error to return for the expected operation
|
||||
func (e *ExpectedList) WillReturnError(err error) *ExpectedList {
|
||||
e.err = err
|
||||
return e
|
||||
}
|
||||
|
||||
// checkTTL checks if a key has expired
|
||||
func (m *Store) checkTTL(key string) bool {
|
||||
m.mutex.RLock()
|
||||
defer m.mutex.RUnlock()
|
||||
|
||||
if exp, ok := m.ttls[key]; ok {
|
||||
if time.Now().After(exp) {
|
||||
delete(m.data, key)
|
||||
delete(m.exists, key)
|
||||
delete(m.ttls, key)
|
||||
delete(m.metadata, key)
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// FastForward decrements all TTLs by the given duration
|
||||
func (m *Store) FastForward(d time.Duration) {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
|
||||
now := time.Now()
|
||||
for key, exp := range m.ttls {
|
||||
// Calculate remaining time before fast forward
|
||||
remaining := time.Until(exp)
|
||||
if remaining <= 0 {
|
||||
// Already expired, remove it
|
||||
delete(m.data, key)
|
||||
delete(m.exists, key)
|
||||
delete(m.ttls, key)
|
||||
delete(m.metadata, key)
|
||||
} else {
|
||||
// Apply fast forward
|
||||
newRemaining := remaining - d
|
||||
if newRemaining <= 0 {
|
||||
// Would expire after fast forward, remove it
|
||||
delete(m.data, key)
|
||||
delete(m.exists, key)
|
||||
delete(m.ttls, key)
|
||||
delete(m.metadata, key)
|
||||
} else {
|
||||
// Update expiration time
|
||||
m.ttls[key] = now.Add(newRemaining)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Name returns store name
|
||||
func (m *Store) Name() string {
|
||||
return m.opts.Name
|
||||
}
|
||||
|
||||
// Init initializes the mock store
|
||||
func (m *Store) Init(opts ...store.Option) error {
|
||||
if m.err != nil {
|
||||
return m.err
|
||||
}
|
||||
for _, o := range opts {
|
||||
o(&m.opts)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Connect is used when store needs to be connected
|
||||
func (m *Store) Connect(ctx context.Context) error {
|
||||
if m.err != nil {
|
||||
return m.err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Options returns the current options
|
||||
func (m *Store) Options() store.Options {
|
||||
return m.opts
|
||||
}
|
||||
|
||||
// Exists checks that key exists in store
|
||||
func (m *Store) Exists(ctx context.Context, key string, opts ...store.ExistsOption) error {
|
||||
if m.err != nil {
|
||||
return m.err
|
||||
}
|
||||
|
||||
// Check TTL first
|
||||
if !m.checkTTL(key) {
|
||||
return store.ErrNotFound
|
||||
}
|
||||
|
||||
// Find matching expectation
|
||||
m.mutex.Lock()
|
||||
for _, exp := range m.expectedExists {
|
||||
if exp.match(key, opts...) {
|
||||
m.mutex.Unlock()
|
||||
if exp.err != nil {
|
||||
return exp.err
|
||||
}
|
||||
if !m.exists[key] {
|
||||
return store.ErrNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
m.mutex.Unlock()
|
||||
|
||||
// If no expectation matched, use default behavior
|
||||
if !m.exists[key] {
|
||||
return store.ErrNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Read reads a single key name to provided value with optional ReadOptions
|
||||
func (m *Store) Read(ctx context.Context, key string, val interface{}, opts ...store.ReadOption) error {
|
||||
if m.err != nil {
|
||||
return m.err
|
||||
}
|
||||
|
||||
// Check TTL first
|
||||
if !m.checkTTL(key) {
|
||||
return store.ErrNotFound
|
||||
}
|
||||
|
||||
// Find matching expectation
|
||||
m.mutex.Lock()
|
||||
for _, exp := range m.expectedReads {
|
||||
if exp.match(key, opts...) {
|
||||
m.mutex.Unlock()
|
||||
if exp.err != nil {
|
||||
return exp.err
|
||||
}
|
||||
if !m.exists[key] {
|
||||
return store.ErrNotFound
|
||||
}
|
||||
|
||||
// Copy the value from expected or actual data
|
||||
data := exp.value
|
||||
if data == nil {
|
||||
data = m.data[key]
|
||||
}
|
||||
|
||||
if data != nil {
|
||||
// Simple type conversion for testing
|
||||
if target, ok := val.(*interface{}); ok {
|
||||
*target = data
|
||||
} else if target, ok := val.(*string); ok {
|
||||
if s, ok := data.(string); ok {
|
||||
*target = s
|
||||
} else {
|
||||
*target = fmt.Sprintf("%v", data)
|
||||
}
|
||||
} else if target, ok := val.(*int); ok {
|
||||
if i, ok := data.(int); ok {
|
||||
*target = i
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
m.mutex.Unlock()
|
||||
|
||||
// If no expectation matched, use default behavior
|
||||
if !m.exists[key] {
|
||||
return store.ErrNotFound
|
||||
}
|
||||
|
||||
if data, ok := m.data[key]; ok {
|
||||
if target, ok := val.(*interface{}); ok {
|
||||
*target = data
|
||||
} else if target, ok := val.(*string); ok {
|
||||
if s, ok := data.(string); ok {
|
||||
*target = s
|
||||
} else {
|
||||
*target = fmt.Sprintf("%v", data)
|
||||
}
|
||||
} else if target, ok := val.(*int); ok {
|
||||
if i, ok := data.(int); ok {
|
||||
*target = i
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Write writes a value to key name to the store with optional WriteOption
|
||||
func (m *Store) Write(ctx context.Context, key string, val interface{}, opts ...store.WriteOption) error {
|
||||
if m.err != nil {
|
||||
return m.err
|
||||
}
|
||||
|
||||
// Find matching expectation
|
||||
m.mutex.Lock()
|
||||
for _, exp := range m.expectedWrites {
|
||||
if exp.match(key, val, opts...) {
|
||||
m.mutex.Unlock()
|
||||
if exp.err != nil {
|
||||
return exp.err
|
||||
}
|
||||
|
||||
// Apply the write operation
|
||||
m.mutex.Lock()
|
||||
m.data[key] = val
|
||||
m.exists[key] = true
|
||||
|
||||
// Handle TTL
|
||||
options := store.NewWriteOptions(opts...)
|
||||
if options.TTL > 0 {
|
||||
m.ttls[key] = time.Now().Add(options.TTL)
|
||||
} else {
|
||||
delete(m.ttls, key) // Remove TTL if not set
|
||||
}
|
||||
|
||||
// Handle metadata
|
||||
if options.Metadata != nil {
|
||||
m.metadata[key] = make(map[string]string)
|
||||
for k, v := range options.Metadata {
|
||||
// Convert []string to string by joining with comma
|
||||
if len(v) > 0 {
|
||||
m.metadata[key][k] = strings.Join(v, ",")
|
||||
} else {
|
||||
m.metadata[key][k] = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
m.mutex.Unlock()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
m.mutex.Unlock()
|
||||
|
||||
// If no expectation matched, use default behavior
|
||||
m.mutex.Lock()
|
||||
m.data[key] = val
|
||||
m.exists[key] = true
|
||||
|
||||
options := store.NewWriteOptions(opts...)
|
||||
if options.TTL > 0 {
|
||||
m.ttls[key] = time.Now().Add(options.TTL)
|
||||
} else {
|
||||
delete(m.ttls, key)
|
||||
}
|
||||
|
||||
if options.Metadata != nil {
|
||||
m.metadata[key] = make(map[string]string)
|
||||
for k, v := range options.Metadata {
|
||||
// Convert []string to string by joining with comma
|
||||
if len(v) > 0 {
|
||||
m.metadata[key][k] = strings.Join(v, ",")
|
||||
} else {
|
||||
m.metadata[key][k] = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
m.mutex.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete removes the record with the corresponding key from the store
|
||||
func (m *Store) Delete(ctx context.Context, key string, opts ...store.DeleteOption) error {
|
||||
if m.err != nil {
|
||||
return m.err
|
||||
}
|
||||
|
||||
// Find matching expectation
|
||||
m.mutex.Lock()
|
||||
for _, exp := range m.expectedDeletes {
|
||||
if exp.match(key, opts...) {
|
||||
m.mutex.Unlock()
|
||||
if exp.err != nil {
|
||||
return exp.err
|
||||
}
|
||||
|
||||
m.mutex.Lock()
|
||||
delete(m.data, key)
|
||||
delete(m.exists, key)
|
||||
delete(m.ttls, key)
|
||||
delete(m.metadata, key)
|
||||
m.mutex.Unlock()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
m.mutex.Unlock()
|
||||
|
||||
// If no expectation matched, use default behavior
|
||||
m.mutex.Lock()
|
||||
delete(m.data, key)
|
||||
delete(m.exists, key)
|
||||
delete(m.ttls, key)
|
||||
delete(m.metadata, key)
|
||||
m.mutex.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
// List returns any keys that match, or an empty list with no error if none matched
|
||||
func (m *Store) List(ctx context.Context, opts ...store.ListOption) ([]string, error) {
|
||||
if m.err != nil {
|
||||
return nil, m.err
|
||||
}
|
||||
|
||||
// Find matching expectation
|
||||
m.mutex.Lock()
|
||||
for _, exp := range m.expectedLists {
|
||||
if exp.match(opts...) {
|
||||
m.mutex.Unlock()
|
||||
if exp.err != nil {
|
||||
return nil, exp.err
|
||||
}
|
||||
return exp.keys, nil
|
||||
}
|
||||
}
|
||||
m.mutex.Unlock()
|
||||
|
||||
// If no expectation matched, return actual keys
|
||||
m.mutex.RLock()
|
||||
defer m.mutex.RUnlock()
|
||||
|
||||
var keys []string
|
||||
for key := range m.data {
|
||||
// Check TTL
|
||||
if exp, ok := m.ttls[key]; ok {
|
||||
if time.Now().After(exp) {
|
||||
continue // Skip expired keys
|
||||
}
|
||||
}
|
||||
keys = append(keys, key)
|
||||
}
|
||||
|
||||
// Apply list options filtering
|
||||
options := store.NewListOptions(opts...)
|
||||
if options.Prefix != "" {
|
||||
var filtered []string
|
||||
for _, key := range keys {
|
||||
if len(key) >= len(options.Prefix) && key[:len(options.Prefix)] == options.Prefix {
|
||||
filtered = append(filtered, key)
|
||||
}
|
||||
}
|
||||
keys = filtered
|
||||
}
|
||||
|
||||
if options.Suffix != "" {
|
||||
var filtered []string
|
||||
for _, key := range keys {
|
||||
if len(key) >= len(options.Suffix) && key[len(key)-len(options.Suffix):] == options.Suffix {
|
||||
filtered = append(filtered, key)
|
||||
}
|
||||
}
|
||||
keys = filtered
|
||||
}
|
||||
|
||||
// Apply limit and offset
|
||||
if options.Limit > 0 && int(options.Limit) < len(keys) {
|
||||
end := int(options.Offset) + int(options.Limit)
|
||||
if end > len(keys) {
|
||||
end = len(keys)
|
||||
}
|
||||
if int(options.Offset) < len(keys) {
|
||||
keys = keys[options.Offset:end]
|
||||
} else {
|
||||
keys = []string{}
|
||||
}
|
||||
} else if options.Offset > 0 && int(options.Offset) < len(keys) {
|
||||
keys = keys[options.Offset:]
|
||||
} else if options.Offset >= uint(len(keys)) {
|
||||
keys = []string{}
|
||||
}
|
||||
|
||||
return keys, nil
|
||||
}
|
||||
|
||||
// Disconnect disconnects the mock store
|
||||
func (m *Store) Disconnect(ctx context.Context) error {
|
||||
if m.err != nil {
|
||||
return m.err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// String returns the name of the implementation
|
||||
func (m *Store) String() string {
|
||||
return "mock"
|
||||
}
|
||||
|
||||
// Watch returns events watcher
|
||||
func (m *Store) Watch(ctx context.Context, opts ...store.WatchOption) (store.Watcher, error) {
|
||||
if m.err != nil {
|
||||
return nil, m.err
|
||||
}
|
||||
return NewWatcher(), nil
|
||||
}
|
||||
|
||||
// Live returns store liveness
|
||||
func (m *Store) Live() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Ready returns store readiness
|
||||
func (m *Store) Ready() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Health returns store health
|
||||
func (m *Store) Health() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// ExpectationsWereMet checks that all expected operations were called the expected number of times
|
||||
func (m *Store) ExpectationsWereMet() error {
|
||||
m.mutex.RLock()
|
||||
defer m.mutex.RUnlock()
|
||||
|
||||
for _, exp := range m.expectedWrites {
|
||||
if exp.times > 0 && exp.called != exp.times {
|
||||
return fmt.Errorf("expected write for key %s to be called %d times, but was called %d times", exp.key, exp.times, exp.called)
|
||||
}
|
||||
}
|
||||
|
||||
for _, exp := range m.expectedReads {
|
||||
if exp.times > 0 && exp.called != exp.times {
|
||||
return fmt.Errorf("expected read for key %s to be called %d times, but was called %d times", exp.key, exp.times, exp.called)
|
||||
}
|
||||
}
|
||||
|
||||
for _, exp := range m.expectedDeletes {
|
||||
if exp.times > 0 && exp.called != exp.times {
|
||||
return fmt.Errorf("expected delete for key %s to be called %d times, but was called %d times", exp.key, exp.times, exp.called)
|
||||
}
|
||||
}
|
||||
|
||||
for _, exp := range m.expectedExists {
|
||||
if exp.times > 0 && exp.called != exp.times {
|
||||
return fmt.Errorf("expected exists for key %s to be called %d times, but was called %d times", exp.key, exp.times, exp.called)
|
||||
}
|
||||
}
|
||||
|
||||
for _, exp := range m.expectedLists {
|
||||
if exp.times > 0 && exp.called != exp.times {
|
||||
return fmt.Errorf("expected list to be called %d times, but was called %d times", exp.times, exp.called)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Watcher is a mock implementation of the Watcher interface
|
||||
type Watcher struct {
|
||||
events chan store.Event
|
||||
stop chan bool
|
||||
}
|
||||
|
||||
// NewWatcher creates a new mock watcher
|
||||
func NewWatcher() *Watcher {
|
||||
return &Watcher{
|
||||
events: make(chan store.Event, 1),
|
||||
stop: make(chan bool, 1),
|
||||
}
|
||||
}
|
||||
|
||||
// Next is a blocking call that returns the next event
|
||||
func (mw *Watcher) Next() (store.Event, error) {
|
||||
select {
|
||||
case event := <-mw.events:
|
||||
return event, nil
|
||||
case <-mw.stop:
|
||||
return nil, store.ErrWatcherStopped
|
||||
}
|
||||
}
|
||||
|
||||
// Stop stops the watcher
|
||||
func (mw *Watcher) Stop() {
|
||||
select {
|
||||
case mw.stop <- true:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
// SendEvent sends an event to the watcher (for testing purposes)
|
||||
func (mw *Watcher) SendEvent(event store.Event) {
|
||||
select {
|
||||
case mw.events <- event:
|
||||
default:
|
||||
// If channel is full, drop the event
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user