...

Source file src/github.com/launchdarkly/go-server-sdk/v6/internal/sharedtest/mocks/mock_persistent_data_store.go

Documentation: github.com/launchdarkly/go-server-sdk/v6/internal/sharedtest/mocks

     1  package mocks
     2  
     3  import (
     4  	"sync"
     5  	"time"
     6  
     7  	"github.com/launchdarkly/go-server-sdk/v6/subsystems/ldstoretypes"
     8  
     9  	"golang.org/x/exp/maps"
    10  )
    11  
    12  // MockDatabaseInstance can be used with MockPersistentDataStore to simulate multiple data store
    13  // instances sharing the same underlying data space.
    14  type MockDatabaseInstance struct {
    15  	dataByPrefix   map[string]map[ldstoretypes.DataKind]map[string]ldstoretypes.SerializedItemDescriptor
    16  	initedByPrefix map[string]*bool
    17  }
    18  
    19  // NewMockDatabaseInstance creates an instance of MockDatabaseInstance.
    20  func NewMockDatabaseInstance() *MockDatabaseInstance {
    21  	return &MockDatabaseInstance{
    22  		dataByPrefix:   make(map[string]map[ldstoretypes.DataKind]map[string]ldstoretypes.SerializedItemDescriptor),
    23  		initedByPrefix: make(map[string]*bool),
    24  	}
    25  }
    26  
    27  // Clear removes all shared data.
    28  func (db *MockDatabaseInstance) Clear(prefix string) {
    29  	for _, m := range db.dataByPrefix[prefix] {
    30  		maps.Clear(m)
    31  	}
    32  	if v, ok := db.initedByPrefix[prefix]; ok {
    33  		*v = false
    34  	}
    35  }
    36  
    37  // MockPersistentDataStore is a test implementation of PersistentDataStore.
    38  type MockPersistentDataStore struct {
    39  	data                map[ldstoretypes.DataKind]map[string]ldstoretypes.SerializedItemDescriptor
    40  	persistOnlyAsString bool
    41  	fakeError           error
    42  	available           bool
    43  	inited              *bool
    44  	InitQueriedCount    int
    45  	queryDelay          time.Duration
    46  	queryStartedCh      chan struct{}
    47  	testTxHook          func()
    48  	closed              bool
    49  	lock                sync.Mutex
    50  }
    51  
    52  func newData() map[ldstoretypes.DataKind]map[string]ldstoretypes.SerializedItemDescriptor {
    53  	return map[ldstoretypes.DataKind]map[string]ldstoretypes.SerializedItemDescriptor{
    54  		MockData:      {},
    55  		MockOtherData: {},
    56  	}
    57  }
    58  
    59  // NewMockPersistentDataStore creates a test implementation of a persistent data store.
    60  func NewMockPersistentDataStore() *MockPersistentDataStore {
    61  	f := false
    62  	m := &MockPersistentDataStore{data: newData(), inited: &f, available: true}
    63  	return m
    64  }
    65  
    66  // NewMockPersistentDataStoreWithPrefix creates a test implementation of a persistent data store that uses
    67  // a MockDatabaseInstance to simulate a shared database.
    68  func NewMockPersistentDataStoreWithPrefix(
    69  	db *MockDatabaseInstance,
    70  	prefix string,
    71  ) *MockPersistentDataStore {
    72  	m := &MockPersistentDataStore{available: true}
    73  	if _, ok := db.dataByPrefix[prefix]; !ok {
    74  		db.dataByPrefix[prefix] = newData()
    75  		f := false
    76  		db.initedByPrefix[prefix] = &f
    77  	}
    78  	m.data = db.dataByPrefix[prefix]
    79  	m.inited = db.initedByPrefix[prefix]
    80  	return m
    81  }
    82  
    83  // EnableInstrumentedQueries puts the test store into a mode where all get operations begin by posting
    84  // a signal to a channel and then waiting for some amount of time, to test coalescing of requests.
    85  func (m *MockPersistentDataStore) EnableInstrumentedQueries(queryDelay time.Duration) <-chan struct{} {
    86  	m.lock.Lock()
    87  	defer m.lock.Unlock()
    88  	m.queryDelay = queryDelay
    89  	m.queryStartedCh = make(chan struct{}, 10)
    90  	return m.queryStartedCh
    91  }
    92  
    93  // ForceGet retrieves a serialized item directly from the test data with no other processing.
    94  func (m *MockPersistentDataStore) ForceGet(
    95  	kind ldstoretypes.DataKind,
    96  	key string,
    97  ) ldstoretypes.SerializedItemDescriptor {
    98  	m.lock.Lock()
    99  	defer m.lock.Unlock()
   100  	if ret, ok := m.data[kind][key]; ok {
   101  		return ret
   102  	}
   103  	return ldstoretypes.SerializedItemDescriptor{}.NotFound()
   104  }
   105  
   106  // ForceSet directly modifies an item in the test data.
   107  func (m *MockPersistentDataStore) ForceSet(
   108  	kind ldstoretypes.DataKind,
   109  	key string,
   110  	item ldstoretypes.SerializedItemDescriptor,
   111  ) {
   112  	m.lock.Lock()
   113  	defer m.lock.Unlock()
   114  	m.data[kind][key] = item
   115  }
   116  
   117  // ForceRemove deletes an item from the test data.
   118  func (m *MockPersistentDataStore) ForceRemove(kind ldstoretypes.DataKind, key string) {
   119  	m.lock.Lock()
   120  	defer m.lock.Unlock()
   121  	delete(m.data[kind], key)
   122  }
   123  
   124  // ForceSetInited changes the value that will be returned by IsInitialized().
   125  func (m *MockPersistentDataStore) ForceSetInited(inited bool) {
   126  	m.lock.Lock()
   127  	defer m.lock.Unlock()
   128  	*m.inited = inited
   129  }
   130  
   131  // SetAvailable changes the value that will be returned by IsStoreAvailable().
   132  func (m *MockPersistentDataStore) SetAvailable(available bool) {
   133  	m.lock.Lock()
   134  	defer m.lock.Unlock()
   135  	m.available = available
   136  }
   137  
   138  // SetFakeError causes subsequent store operations to return an error.
   139  func (m *MockPersistentDataStore) SetFakeError(fakeError error) {
   140  	m.lock.Lock()
   141  	defer m.lock.Unlock()
   142  	m.fakeError = fakeError
   143  }
   144  
   145  // SetPersistOnlyAsString sets whether the mock data store should behave like our Redis implementation,
   146  // where the item version is *not* persisted separately from the serialized item string (so the latter must
   147  // be parsed to get the version). If this is false (the default), it behaves instead like our DynamoDB
   148  // implementation, where the version metadata exists separately from the serialized string.
   149  func (m *MockPersistentDataStore) SetPersistOnlyAsString(value bool) {
   150  	m.lock.Lock()
   151  	defer m.lock.Unlock()
   152  	m.persistOnlyAsString = value
   153  }
   154  
   155  // SetTestTxHook sets a callback function that will be called during updates, to support the concurrent
   156  // modification tests in PersistentDataStoreTestSuite.
   157  func (m *MockPersistentDataStore) SetTestTxHook(hook func()) {
   158  	m.lock.Lock()
   159  	defer m.lock.Unlock()
   160  	m.testTxHook = hook
   161  }
   162  
   163  func (m *MockPersistentDataStore) startQuery() {
   164  	if m.queryStartedCh != nil {
   165  		m.queryStartedCh <- struct{}{}
   166  	}
   167  	if m.queryDelay > 0 {
   168  		<-time.After(m.queryDelay)
   169  	}
   170  }
   171  
   172  // Init is a standard PersistentDataStore method.
   173  func (m *MockPersistentDataStore) Init(allData []ldstoretypes.SerializedCollection) error {
   174  	m.lock.Lock()
   175  	defer m.lock.Unlock()
   176  	if m.fakeError != nil {
   177  		return m.fakeError
   178  	}
   179  	for _, mm := range m.data {
   180  		maps.Clear(mm)
   181  	}
   182  	for _, coll := range allData {
   183  		AssertNotNil(coll.Kind)
   184  		itemsMap := make(map[string]ldstoretypes.SerializedItemDescriptor)
   185  		for _, item := range coll.Items {
   186  			itemsMap[item.Key] = m.storableItem(item.Item)
   187  		}
   188  		m.data[coll.Kind] = itemsMap
   189  	}
   190  	*m.inited = true
   191  	return nil
   192  }
   193  
   194  // Get is a standard PersistentDataStore method.
   195  func (m *MockPersistentDataStore) Get(
   196  	kind ldstoretypes.DataKind,
   197  	key string,
   198  ) (ldstoretypes.SerializedItemDescriptor, error) {
   199  	AssertNotNil(kind)
   200  	m.lock.Lock()
   201  	defer m.lock.Unlock()
   202  	if m.fakeError != nil {
   203  		return ldstoretypes.SerializedItemDescriptor{}.NotFound(), m.fakeError
   204  	}
   205  	m.startQuery()
   206  	if item, ok := m.data[kind][key]; ok {
   207  		return m.retrievedItem(item), nil
   208  	}
   209  	return ldstoretypes.SerializedItemDescriptor{}.NotFound(), nil
   210  }
   211  
   212  // GetAll is a standard PersistentDataStore method.
   213  func (m *MockPersistentDataStore) GetAll(
   214  	kind ldstoretypes.DataKind,
   215  ) ([]ldstoretypes.KeyedSerializedItemDescriptor, error) {
   216  	AssertNotNil(kind)
   217  	m.lock.Lock()
   218  	defer m.lock.Unlock()
   219  	if m.fakeError != nil {
   220  		return nil, m.fakeError
   221  	}
   222  	m.startQuery()
   223  	ret := []ldstoretypes.KeyedSerializedItemDescriptor{}
   224  	for k, v := range m.data[kind] {
   225  		ret = append(ret, ldstoretypes.KeyedSerializedItemDescriptor{Key: k, Item: m.retrievedItem(v)})
   226  	}
   227  	return ret, nil
   228  }
   229  
   230  // Upsert is a standard PersistentDataStore method.
   231  func (m *MockPersistentDataStore) Upsert(
   232  	kind ldstoretypes.DataKind,
   233  	key string,
   234  	newItem ldstoretypes.SerializedItemDescriptor,
   235  ) (bool, error) {
   236  	AssertNotNil(kind)
   237  	m.lock.Lock()
   238  	defer m.lock.Unlock()
   239  	if m.fakeError != nil {
   240  		return false, m.fakeError
   241  	}
   242  	if m.testTxHook != nil {
   243  		m.testTxHook()
   244  	}
   245  	if oldItem, ok := m.data[kind][key]; ok {
   246  		oldVersion := oldItem.Version
   247  		if m.persistOnlyAsString {
   248  			// If persistOnlyAsString is true, simulate the kind of implementation where we can't see the
   249  			// version as a separate attribute in the database and must deserialize the item to get it.
   250  			oldDeserializedItem, _ := kind.Deserialize(oldItem.SerializedItem)
   251  			oldVersion = oldDeserializedItem.Version
   252  		}
   253  		if oldVersion >= newItem.Version {
   254  			return false, nil
   255  		}
   256  	}
   257  	m.data[kind][key] = m.storableItem(newItem)
   258  	return true, nil
   259  }
   260  
   261  // IsInitialized is a standard PersistentDataStore method.
   262  func (m *MockPersistentDataStore) IsInitialized() bool {
   263  	m.lock.Lock()
   264  	defer m.lock.Unlock()
   265  	m.InitQueriedCount++
   266  	return *m.inited
   267  }
   268  
   269  // IsStoreAvailable is a standard PersistentDataStore method.
   270  func (m *MockPersistentDataStore) IsStoreAvailable() bool {
   271  	m.lock.Lock()
   272  	defer m.lock.Unlock()
   273  	return m.available
   274  }
   275  
   276  // Close is a standard PersistentDataStore method.
   277  func (m *MockPersistentDataStore) Close() error {
   278  	m.lock.Lock()
   279  	defer m.lock.Unlock()
   280  	m.closed = true
   281  	return nil
   282  }
   283  
   284  func (m *MockPersistentDataStore) retrievedItem(
   285  	item ldstoretypes.SerializedItemDescriptor,
   286  ) ldstoretypes.SerializedItemDescriptor {
   287  	if m.persistOnlyAsString {
   288  		// This simulates the kind of store implementation that can't track metadata separately
   289  		return ldstoretypes.SerializedItemDescriptor{Version: 0, SerializedItem: item.SerializedItem}
   290  	}
   291  	return item
   292  }
   293  
   294  func (m *MockPersistentDataStore) storableItem(
   295  	item ldstoretypes.SerializedItemDescriptor,
   296  ) ldstoretypes.SerializedItemDescriptor {
   297  	if item.Deleted && !m.persistOnlyAsString {
   298  		// This simulates the kind of store implementation that *can* track metadata separately, so we don't
   299  		// have to persist the placeholder string for deleted items
   300  		return ldstoretypes.SerializedItemDescriptor{Version: item.Version, Deleted: true}
   301  	}
   302  	return item
   303  }
   304  

View as plain text