...

Source file src/github.com/launchdarkly/go-server-sdk/v6/internal/datastore/persistent_data_store_wrapper_test.go

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

     1  package datastore
     2  
     3  import (
     4  	"errors"
     5  	"sort"
     6  	"testing"
     7  	"time"
     8  
     9  	"github.com/launchdarkly/go-server-sdk/v6/internal/sharedtest/mocks"
    10  
    11  	"github.com/launchdarkly/go-server-sdk/v6/interfaces"
    12  	"github.com/launchdarkly/go-server-sdk/v6/internal"
    13  	s "github.com/launchdarkly/go-server-sdk/v6/internal/sharedtest"
    14  	"github.com/launchdarkly/go-server-sdk/v6/subsystems"
    15  	st "github.com/launchdarkly/go-server-sdk/v6/subsystems/ldstoretypes"
    16  
    17  	"github.com/stretchr/testify/assert"
    18  	"github.com/stretchr/testify/require"
    19  )
    20  
    21  type testCacheMode string
    22  
    23  const (
    24  	testUncached           testCacheMode = "uncached"
    25  	testCached             testCacheMode = "cached"
    26  	testCachedIndefinitely testCacheMode = "cached indefinitely"
    27  )
    28  
    29  func (m testCacheMode) isCached() bool {
    30  	return m != testUncached
    31  }
    32  
    33  func (m testCacheMode) ttl() time.Duration {
    34  	switch m {
    35  	case testCached:
    36  		return 30 * time.Second
    37  	case testCachedIndefinitely:
    38  		return -1
    39  	default:
    40  		return 0
    41  	}
    42  }
    43  
    44  func (m testCacheMode) isInfiniteTTL() bool {
    45  	return m.ttl() < 0
    46  }
    47  
    48  func makePersistentDataStoreWrapper(
    49  	t *testing.T,
    50  	mode testCacheMode,
    51  	core *mocks.MockPersistentDataStore,
    52  ) subsystems.DataStore {
    53  	broadcaster := internal.NewBroadcaster[interfaces.DataStoreStatus]()
    54  	dataStoreUpdates := NewDataStoreUpdateSinkImpl(broadcaster)
    55  	return NewPersistentDataStoreWrapper(core, dataStoreUpdates, mode.ttl(), s.NewTestLoggers())
    56  }
    57  
    58  func TestPersistentDataStoreWrapper(t *testing.T) {
    59  	allCacheModes := []testCacheMode{testUncached, testCached, testCachedIndefinitely}
    60  	cachedOnly := []testCacheMode{testCached, testCachedIndefinitely}
    61  
    62  	runTests := func(
    63  		name string,
    64  		test func(t *testing.T, mode testCacheMode),
    65  		forModes ...testCacheMode,
    66  	) {
    67  		if len(forModes) == 0 {
    68  			require.Fail(t, "didn't specify any testCacheModes")
    69  		}
    70  		t.Run(name, func(t *testing.T) {
    71  			for _, mode := range forModes {
    72  				t.Run(string(mode), func(t *testing.T) {
    73  					test(t, mode)
    74  				})
    75  			}
    76  		})
    77  	}
    78  
    79  	runTests("Get", testPersistentDataStoreWrapperGet, allCacheModes...)
    80  	runTests("GetAll", testPersistentDataStoreWrapperGetAll, allCacheModes...)
    81  	runTests("Upsert", testPersistentDataStoreWrapperUpsert, allCacheModes...)
    82  	runTests("Delete", testPersistentDataStoreWrapperDelete, allCacheModes...)
    83  	runTests("IsInitialized", testPersistentDataStoreWrapperIsInitialized, allCacheModes...)
    84  	runTests("update failures with cache", testPersistentDataStoreWrapperUpdateFailuresWithCache, cachedOnly...)
    85  
    86  	runTests("IsStatusMonitoringEnabled", func(t *testing.T, mode testCacheMode) {
    87  		testWithMockPersistentDataStore(t, "is always true", mode, func(t *testing.T, core *mocks.MockPersistentDataStore, w subsystems.DataStore) {
    88  			assert.True(t, w.IsStatusMonitoringEnabled())
    89  		})
    90  	}, allCacheModes...)
    91  }
    92  
    93  func testWithMockPersistentDataStore(
    94  	t *testing.T,
    95  	name string,
    96  	mode testCacheMode,
    97  	action func(t *testing.T, core *mocks.MockPersistentDataStore, w subsystems.DataStore),
    98  ) {
    99  	t.Run(name, func(t *testing.T) {
   100  		core := mocks.NewMockPersistentDataStore()
   101  		w := makePersistentDataStoreWrapper(t, mode, core)
   102  		defer w.Close()
   103  		action(t, core, w)
   104  	})
   105  }
   106  
   107  func testPersistentDataStoreWrapperGet(t *testing.T, mode testCacheMode) {
   108  	testWithMockPersistentDataStore(t, "existing item", mode, func(t *testing.T, core *mocks.MockPersistentDataStore, w subsystems.DataStore) {
   109  		itemv1 := mocks.MockDataItem{Key: "item", Version: 1}
   110  		itemv2 := mocks.MockDataItem{Key: itemv1.Key, Version: 2}
   111  
   112  		core.ForceSet(mocks.MockData, itemv1.Key, itemv1.ToSerializedItemDescriptor())
   113  		item, err := w.Get(mocks.MockData, itemv1.Key)
   114  		require.NoError(t, err)
   115  		require.Equal(t, itemv1.ToItemDescriptor(), item)
   116  
   117  		core.ForceSet(mocks.MockData, itemv1.Key, itemv2.ToSerializedItemDescriptor())
   118  		item, err = w.Get(mocks.MockData, itemv1.Key)
   119  		require.NoError(t, err)
   120  		if mode.isCached() {
   121  			require.Equal(t, itemv1.ToItemDescriptor(), item) // returns cached value, does not call getter
   122  		} else {
   123  			require.Equal(t, itemv2.ToItemDescriptor(), item) // no caching, calls getter
   124  		}
   125  	})
   126  
   127  	testWithMockPersistentDataStore(t, "unknown item", mode, func(t *testing.T, core *mocks.MockPersistentDataStore, w subsystems.DataStore) {
   128  		itemv1 := mocks.MockDataItem{Key: "key", Version: 1}
   129  
   130  		item, err := w.Get(mocks.MockData, itemv1.Key)
   131  		require.NoError(t, err)
   132  		require.Equal(t, st.ItemDescriptor{}.NotFound(), item)
   133  
   134  		core.ForceSet(mocks.MockData, itemv1.Key, itemv1.ToSerializedItemDescriptor())
   135  		item, err = w.Get(mocks.MockData, itemv1.Key)
   136  		require.NoError(t, err)
   137  		if mode.isCached() {
   138  			require.Equal(t, st.ItemDescriptor{}.NotFound(), item) // the cache retains a nil result
   139  		} else {
   140  			require.Equal(t, itemv1.ToItemDescriptor(), item) // no caching, calls getter
   141  		}
   142  	})
   143  
   144  	testWithMockPersistentDataStore(t, "deleted item", mode, func(t *testing.T, core *mocks.MockPersistentDataStore, w subsystems.DataStore) {
   145  		key := "item"
   146  		deletedItemDesc := st.ItemDescriptor{Version: 1}
   147  		serializedDeletedItemDesc := st.SerializedItemDescriptor{Version: 1}
   148  		itemv2 := mocks.MockDataItem{Key: key, Version: 2}
   149  
   150  		core.ForceSet(mocks.MockData, key, serializedDeletedItemDesc)
   151  		item, err := w.Get(mocks.MockData, key)
   152  		require.NoError(t, err)
   153  		assert.Equal(t, deletedItemDesc, item)
   154  
   155  		core.ForceSet(mocks.MockData, key, itemv2.ToSerializedItemDescriptor())
   156  		item, err = w.Get(mocks.MockData, key)
   157  		require.NoError(t, err)
   158  		if mode.isCached() {
   159  			require.Equal(t, deletedItemDesc, item) // it used the cached deleted item rather than calling the getter
   160  		} else {
   161  			require.Equal(t, itemv2.ToItemDescriptor(), item) // no caching, calls getter
   162  		}
   163  	})
   164  
   165  	testWithMockPersistentDataStore(t, "item that fails to deserialize", mode, func(t *testing.T, core *mocks.MockPersistentDataStore, w subsystems.DataStore) {
   166  		key := "item"
   167  		core.ForceSet(mocks.MockData, key, st.SerializedItemDescriptor{Version: 1, SerializedItem: []byte("BAD!")})
   168  
   169  		_, err := w.Get(mocks.MockData, key)
   170  		require.Error(t, err)
   171  		assert.Contains(t, err.Error(), "not a valid MockDataItem") // the error that our mock item deserializer returns
   172  	})
   173  
   174  	if mode.isCached() {
   175  		t.Run("cached", func(t *testing.T) {
   176  			testWithMockPersistentDataStore(t, "uses values from Init", mode, func(t *testing.T, core *mocks.MockPersistentDataStore, w subsystems.DataStore) {
   177  				itemv1 := mocks.MockDataItem{Key: "item", Version: 1}
   178  				itemv2 := mocks.MockDataItem{Key: itemv1.Key, Version: 2}
   179  
   180  				require.NoError(t, w.Init(mocks.MakeMockDataSet(itemv1)))
   181  
   182  				core.ForceSet(mocks.MockData, itemv1.Key, itemv2.ToSerializedItemDescriptor())
   183  				result, err := w.Get(mocks.MockData, itemv1.Key)
   184  				require.NoError(t, err)
   185  				require.Equal(t, itemv1.ToItemDescriptor(), result)
   186  			})
   187  
   188  			testWithMockPersistentDataStore(t, "coalesces requests for same key", mode, func(t *testing.T, core *mocks.MockPersistentDataStore, w subsystems.DataStore) {
   189  				queryStartedCh := core.EnableInstrumentedQueries(200 * time.Millisecond)
   190  
   191  				item := mocks.MockDataItem{Key: "key", Version: 9}
   192  				core.ForceSet(mocks.MockData, item.Key, item.ToSerializedItemDescriptor())
   193  
   194  				resultCh := make(chan int, 2)
   195  				go func() {
   196  					result, _ := w.Get(mocks.MockData, item.Key)
   197  					resultCh <- result.Version
   198  				}()
   199  				// We can't actually *guarantee* that our second query will start while the first one is still
   200  				// in progress, but the combination of waiting on queryStartedCh and the built-in delay in
   201  				// MockCoreWithInstrumentedQueries should make it extremely likely.
   202  				<-queryStartedCh
   203  				go func() {
   204  					result, _ := w.Get(mocks.MockData, item.Key)
   205  					resultCh <- result.Version
   206  				}()
   207  
   208  				result1 := <-resultCh
   209  				result2 := <-resultCh
   210  				assert.Equal(t, item.Version, result1)
   211  				assert.Equal(t, item.Version, result2)
   212  
   213  				assert.Len(t, queryStartedCh, 0) // core only received 1 query
   214  			})
   215  		})
   216  	}
   217  
   218  	testWithMockPersistentDataStore(t, "item whose version number doesn't come from the serialized data",
   219  		mode, func(t *testing.T, core *mocks.MockPersistentDataStore, w subsystems.DataStore) {
   220  			// This is a condition that currently can't happen, but if we ever move away from always putting the
   221  			// version number in the serialized JSON (i.e. if every persistent data store implementation has a
   222  			// separate place to keep the version) then PersistentDataStoreWrapper should be able to handle it.
   223  			item := mocks.MockDataItem{Key: "key", Version: 1}
   224  
   225  			sid := item.ToSerializedItemDescriptor()
   226  			sid.Version = 2
   227  
   228  			core.ForceSet(mocks.MockData, item.Key, sid)
   229  
   230  			id := item.ToItemDescriptor()
   231  			id.Version = 2
   232  
   233  			result, err := w.Get(mocks.MockData, item.Key)
   234  			assert.NoError(t, err)
   235  			assert.Equal(t, id, result)
   236  		})
   237  }
   238  
   239  func testPersistentDataStoreWrapperGetAll(t *testing.T, mode testCacheMode) {
   240  	testWithMockPersistentDataStore(t, "gets only items of one kind", mode, func(t *testing.T, core *mocks.MockPersistentDataStore, w subsystems.DataStore) {
   241  		item1 := mocks.MockDataItem{Key: "item1", Version: 1}
   242  		item2 := mocks.MockDataItem{Key: "item2", Version: 1}
   243  		otherItem1 := mocks.MockDataItem{Key: "item1", Version: 3, IsOtherKind: true}
   244  
   245  		core.ForceSet(mocks.MockData, item1.Key, item1.ToSerializedItemDescriptor())
   246  		core.ForceSet(mocks.MockData, item2.Key, item2.ToSerializedItemDescriptor())
   247  		core.ForceSet(mocks.MockOtherData, otherItem1.Key, otherItem1.ToSerializedItemDescriptor())
   248  
   249  		items, err := w.GetAll(mocks.MockData)
   250  		require.NoError(t, err)
   251  		require.Equal(t, 2, len(items))
   252  		sort.Slice(items, func(i, j int) bool { return items[i].Key < items[j].Key })
   253  		assert.Equal(t, []st.KeyedItemDescriptor{item1.ToKeyedItemDescriptor(), item2.ToKeyedItemDescriptor()}, items)
   254  
   255  		items, err = w.GetAll(mocks.MockOtherData)
   256  		require.NoError(t, err)
   257  		require.Equal(t, 1, len(items))
   258  		assert.Equal(t, []st.KeyedItemDescriptor{otherItem1.ToKeyedItemDescriptor()}, items)
   259  	})
   260  
   261  	testWithMockPersistentDataStore(t, "item that fails to deserialize", mode, func(t *testing.T, core *mocks.MockPersistentDataStore, w subsystems.DataStore) {
   262  		item1 := mocks.MockDataItem{Key: "item1", Version: 1}
   263  		core.ForceSet(mocks.MockData, item1.Key, item1.ToSerializedItemDescriptor())
   264  		core.ForceSet(mocks.MockData, "item2", st.SerializedItemDescriptor{Version: 1, SerializedItem: []byte("BAD!")})
   265  
   266  		_, err := w.GetAll(mocks.MockData)
   267  		require.Error(t, err)
   268  		assert.Contains(t, err.Error(), "not a valid MockDataItem") // the error that our mock item deserializer returns
   269  	})
   270  
   271  	if mode.isCached() {
   272  		t.Run("cached", func(t *testing.T) {
   273  			testWithMockPersistentDataStore(t, "uses values from Init", mode, func(t *testing.T, core *mocks.MockPersistentDataStore, w subsystems.DataStore) {
   274  				item1 := mocks.MockDataItem{Key: "item1", Version: 1}
   275  				item2 := mocks.MockDataItem{Key: "item2", Version: 1}
   276  
   277  				require.NoError(t, w.Init(mocks.MakeMockDataSet(item1, item2)))
   278  
   279  				core.ForceRemove(mocks.MockData, item2.Key)
   280  
   281  				items, err := w.GetAll(mocks.MockData)
   282  				require.NoError(t, err)
   283  				assert.Len(t, items, 2)
   284  			})
   285  
   286  			t.Run("uses fresh values if there has been an update", func(t *testing.T) {
   287  				core := mocks.NewMockPersistentDataStore()
   288  				w := makePersistentDataStoreWrapper(t, mode, core)
   289  				defer w.Close()
   290  
   291  				item1v1 := mocks.MockDataItem{Key: "item1", Version: 1}
   292  				item1v2 := mocks.MockDataItem{Key: "item1", Version: 2}
   293  				item2v1 := mocks.MockDataItem{Key: "item2", Version: 1}
   294  				item2v2 := mocks.MockDataItem{Key: "item2", Version: 2}
   295  
   296  				require.NoError(t, w.Init(mocks.MakeMockDataSet(item1v1, item2v2)))
   297  
   298  				// make a change to item1 using the wrapper - this should flush the cache
   299  				_, err := w.Upsert(mocks.MockData, item1v1.Key, item1v2.ToItemDescriptor())
   300  				require.NoError(t, err)
   301  
   302  				// make a change to item2 that bypasses the cache
   303  				core.ForceSet(mocks.MockData, item2v1.Key, item2v2.ToSerializedItemDescriptor())
   304  
   305  				// we should now see both changes since the cache was flushed
   306  				items, err := w.GetAll(mocks.MockData)
   307  				require.NoError(t, err)
   308  				sort.Slice(items, func(i, j int) bool { return items[i].Key < items[j].Key })
   309  				require.Equal(t, 2, items[1].Item.Version)
   310  			})
   311  
   312  			testWithMockPersistentDataStore(t, "uses values from Init", mode, func(t *testing.T, core *mocks.MockPersistentDataStore, w subsystems.DataStore) {
   313  				queryStartedCh := core.EnableInstrumentedQueries(200 * time.Millisecond)
   314  
   315  				item := mocks.MockDataItem{Key: "key", Version: 9}
   316  				core.ForceSet(mocks.MockData, item.Key, item.ToSerializedItemDescriptor())
   317  
   318  				resultCh := make(chan int, 2)
   319  				go func() {
   320  					result, _ := w.GetAll(mocks.MockData)
   321  					resultCh <- len(result)
   322  				}()
   323  				// We can't actually *guarantee* that our second query will start while the first one is still
   324  				// in progress, but the combination of waiting on queryStartedCh and the built-in delay in
   325  				// MockCoreWithInstrumentedQueries should make it extremely likely.
   326  				<-queryStartedCh
   327  				go func() {
   328  					result, _ := w.GetAll(mocks.MockData)
   329  					resultCh <- len(result)
   330  				}()
   331  
   332  				result1 := <-resultCh
   333  				result2 := <-resultCh
   334  				assert.Equal(t, 1, result1)
   335  				assert.Equal(t, 1, result2)
   336  
   337  				assert.Len(t, queryStartedCh, 0) // core only received 1 query
   338  			})
   339  		})
   340  	}
   341  }
   342  
   343  func testPersistentDataStoreWrapperUpsert(t *testing.T, mode testCacheMode) {
   344  	testWithMockPersistentDataStore(t, "successful", mode, func(t *testing.T, core *mocks.MockPersistentDataStore, w subsystems.DataStore) {
   345  		key := "item"
   346  		itemv1 := mocks.MockDataItem{Key: key, Version: 1}
   347  		itemv2 := mocks.MockDataItem{Key: key, Version: 2}
   348  
   349  		updated, err := w.Upsert(mocks.MockData, key, itemv1.ToItemDescriptor())
   350  		require.NoError(t, err)
   351  		assert.True(t, updated)
   352  		require.Equal(t, itemv1.ToSerializedItemDescriptor(), core.ForceGet(mocks.MockData, key))
   353  
   354  		updated, err = w.Upsert(mocks.MockData, key, itemv2.ToItemDescriptor())
   355  		require.NoError(t, err)
   356  		assert.True(t, updated)
   357  		require.Equal(t, itemv2.ToSerializedItemDescriptor(), core.ForceGet(mocks.MockData, key))
   358  
   359  		// if we have a cache, verify that the new item is now cached by writing a different value
   360  		// to the underlying data - Get should still return the cached item
   361  		if mode.isCached() {
   362  			itemv3 := mocks.MockDataItem{Key: key, Version: 3}
   363  			core.ForceSet(mocks.MockData, key, itemv3.ToSerializedItemDescriptor())
   364  		}
   365  
   366  		result, err := w.Get(mocks.MockData, key)
   367  		require.NoError(t, err)
   368  		assert.Equal(t, itemv2.ToItemDescriptor(), result)
   369  	})
   370  
   371  	testWithMockPersistentDataStore(t, "unsuccessful - lower version", mode, func(t *testing.T, core *mocks.MockPersistentDataStore, w subsystems.DataStore) {
   372  		key := "item"
   373  		itemv1 := mocks.MockDataItem{Key: key, Version: 1}
   374  		itemv2 := mocks.MockDataItem{Key: key, Version: 2}
   375  
   376  		updated, err := w.Upsert(mocks.MockData, key, itemv2.ToItemDescriptor())
   377  		require.NoError(t, err)
   378  		assert.True(t, updated)
   379  		require.Equal(t, itemv2.ToSerializedItemDescriptor(), core.ForceGet(mocks.MockData, key))
   380  
   381  		// In a cached store, we need to verify that after an unsuccessful upsert it will refresh the
   382  		// cache using the very latest data from the store - so here we'll sneak a higher-versioned
   383  		// item directly into the store.
   384  		itemv3 := mocks.MockDataItem{Key: key, Version: 3}
   385  		core.ForceSet(mocks.MockData, key, itemv3.ToSerializedItemDescriptor())
   386  
   387  		updated, err = w.Upsert(mocks.MockData, key, itemv1.ToItemDescriptor())
   388  		require.NoError(t, err)
   389  		assert.False(t, updated)
   390  
   391  		result, err := w.Get(mocks.MockData, key)
   392  		require.NoError(t, err)
   393  		assert.Equal(t, itemv3.ToItemDescriptor(), result)
   394  	})
   395  }
   396  
   397  func testPersistentDataStoreWrapperDelete(t *testing.T, mode testCacheMode) {
   398  	testWithMockPersistentDataStore(t, "successful", mode, func(t *testing.T, core *mocks.MockPersistentDataStore, w subsystems.DataStore) {
   399  		key := "item"
   400  		itemv1 := mocks.MockDataItem{Key: key, Version: 1}
   401  		deletedv2 := st.ItemDescriptor{Version: 2}
   402  
   403  		updated, err := w.Upsert(mocks.MockData, key, itemv1.ToItemDescriptor())
   404  		require.NoError(t, err)
   405  		assert.True(t, updated)
   406  		require.Equal(t, itemv1.ToSerializedItemDescriptor(), core.ForceGet(mocks.MockData, key))
   407  
   408  		updated, err = w.Upsert(mocks.MockData, key, deletedv2)
   409  		require.NoError(t, err)
   410  		assert.True(t, updated)
   411  
   412  		// if we have a cache, verify that the new item is now cached by writing a different value
   413  		// to the underlying data - Get should still return the cached item
   414  		if mode.isCached() {
   415  			itemv3 := mocks.MockDataItem{Key: key, Version: 3}
   416  			core.ForceSet(mocks.MockData, key, itemv3.ToSerializedItemDescriptor())
   417  		}
   418  
   419  		result, err := w.Get(mocks.MockData, itemv1.Key)
   420  		require.NoError(t, err)
   421  		assert.Equal(t, deletedv2, result)
   422  	})
   423  
   424  	testWithMockPersistentDataStore(t, "unsuccessful - lower version", mode, func(t *testing.T, core *mocks.MockPersistentDataStore, w subsystems.DataStore) {
   425  		key := "item"
   426  		itemv2 := mocks.MockDataItem{Key: key, Version: 2}
   427  		deletedv1 := st.ItemDescriptor{Version: 1}
   428  
   429  		updated, err := w.Upsert(mocks.MockData, key, itemv2.ToItemDescriptor())
   430  		require.NoError(t, err)
   431  		assert.True(t, updated)
   432  		require.Equal(t, itemv2.ToSerializedItemDescriptor(), core.ForceGet(mocks.MockData, key))
   433  
   434  		updated, err = w.Upsert(mocks.MockData, key, deletedv1)
   435  		require.NoError(t, err)
   436  		assert.False(t, updated)
   437  
   438  		result, err := w.Get(mocks.MockData, itemv2.Key)
   439  		require.NoError(t, err)
   440  		assert.Equal(t, itemv2.ToItemDescriptor(), result)
   441  	})
   442  }
   443  
   444  func testPersistentDataStoreWrapperIsInitialized(t *testing.T, mode testCacheMode) {
   445  	testWithMockPersistentDataStore(t, "won't call underlying IsInitialized if Init has been called", mode, func(t *testing.T, core *mocks.MockPersistentDataStore, w subsystems.DataStore) {
   446  		assert.False(t, w.IsInitialized())
   447  		assert.Equal(t, 1, core.InitQueriedCount)
   448  
   449  		require.NoError(t, w.Init(mocks.MakeMockDataSet()))
   450  
   451  		assert.True(t, w.IsInitialized())
   452  		assert.Equal(t, 1, core.InitQueriedCount)
   453  	})
   454  
   455  	if mode.isCached() {
   456  		testWithMockPersistentDataStore(t, "can cache true result", mode, func(t *testing.T, core *mocks.MockPersistentDataStore, w subsystems.DataStore) {
   457  			assert.Equal(t, 0, core.InitQueriedCount)
   458  
   459  			core.ForceSetInited(true)
   460  
   461  			assert.True(t, w.IsInitialized())
   462  			assert.Equal(t, 1, core.InitQueriedCount)
   463  
   464  			core.ForceSetInited(false)
   465  
   466  			assert.True(t, w.IsInitialized())
   467  			assert.Equal(t, 1, core.InitQueriedCount)
   468  		})
   469  
   470  		testWithMockPersistentDataStore(t, "can cache false result", mode, func(t *testing.T, core *mocks.MockPersistentDataStore, w subsystems.DataStore) {
   471  			assert.False(t, w.IsInitialized())
   472  			assert.Equal(t, 1, core.InitQueriedCount)
   473  
   474  			core.ForceSetInited(true)
   475  
   476  			assert.False(t, w.IsInitialized())
   477  			assert.Equal(t, 1, core.InitQueriedCount)
   478  		})
   479  	}
   480  }
   481  
   482  func testPersistentDataStoreWrapperUpdateFailuresWithCache(t *testing.T, mode testCacheMode) {
   483  	if mode.isInfiniteTTL() {
   484  		t.Run("infinite TTL", func(t *testing.T) {
   485  			testWithMockPersistentDataStore(t, "will update cache even if core Upsert fails", mode, func(t *testing.T, core *mocks.MockPersistentDataStore, w subsystems.DataStore) {
   486  				key := "key"
   487  				itemv1 := mocks.MockDataItem{Key: key, Version: 1}
   488  				itemv2 := mocks.MockDataItem{Key: key, Version: 2}
   489  
   490  				require.NoError(t, w.Init(mocks.MakeMockDataSet(itemv1)))
   491  				assert.Equal(t, itemv1.ToSerializedItemDescriptor(), core.ForceGet(mocks.MockData, key))
   492  
   493  				myError := errors.New("sorry")
   494  				core.SetFakeError(myError)
   495  				_, err := w.Upsert(mocks.MockData, key, itemv2.ToItemDescriptor())
   496  				assert.Equal(t, myError, err)
   497  				assert.Equal(t, itemv1.ToSerializedItemDescriptor(), core.ForceGet(mocks.MockData, key)) // underlying store still has old item
   498  
   499  				core.SetFakeError(nil)
   500  				item, err := w.Get(mocks.MockData, key)
   501  				require.NoError(t, err)
   502  				require.Equal(t, itemv2.ToItemDescriptor(), item) // cache has new item
   503  			})
   504  
   505  			testWithMockPersistentDataStore(t, "will update cache even if core Init fails", mode, func(t *testing.T, core *mocks.MockPersistentDataStore, w subsystems.DataStore) {
   506  				key := "key"
   507  				itemv1 := mocks.MockDataItem{Key: key, Version: 1}
   508  
   509  				myError := errors.New("sorry")
   510  				core.SetFakeError(myError)
   511  				err := w.Init(mocks.MakeMockDataSet(itemv1))
   512  				assert.Equal(t, myError, err)
   513  				assert.Equal(t, st.SerializedItemDescriptor{}.NotFound(), core.ForceGet(mocks.MockData, key)) // underlying store does not have data
   514  
   515  				core.SetFakeError(nil)
   516  				result, err := w.GetAll(mocks.MockData)
   517  				require.NoError(t, err)
   518  				require.Len(t, result, 1) // cache does have data
   519  			})
   520  		})
   521  	} else {
   522  		t.Run("finite TTL", func(t *testing.T) {
   523  			testWithMockPersistentDataStore(t, "won't update cache if core Upsert fails", mode, func(t *testing.T, core *mocks.MockPersistentDataStore, w subsystems.DataStore) {
   524  				key := "key"
   525  				itemv1 := mocks.MockDataItem{Key: key, Version: 1}
   526  				itemv2 := mocks.MockDataItem{Key: key, Version: 2}
   527  
   528  				require.NoError(t, w.Init(mocks.MakeMockDataSet(itemv1)))
   529  				assert.Equal(t, itemv1.ToSerializedItemDescriptor(), core.ForceGet(mocks.MockData, key))
   530  
   531  				myError := errors.New("sorry")
   532  				core.SetFakeError(myError)
   533  				_, err := w.Upsert(mocks.MockData, key, itemv2.ToItemDescriptor())
   534  				assert.Equal(t, myError, err)
   535  				assert.Equal(t, itemv1.ToSerializedItemDescriptor(), core.ForceGet(mocks.MockData, key)) // underlying store still has old item
   536  
   537  				core.SetFakeError(nil)
   538  				item, err := w.Get(mocks.MockData, key)
   539  				require.NoError(t, err)
   540  				require.Equal(t, itemv1.ToItemDescriptor(), item) // cache still has old item too
   541  			})
   542  
   543  			testWithMockPersistentDataStore(t, "won't update cache if core Init fails", mode, func(t *testing.T, core *mocks.MockPersistentDataStore, w subsystems.DataStore) {
   544  				key := "key"
   545  				itemv1 := mocks.MockDataItem{Key: key, Version: 1}
   546  
   547  				myError := errors.New("sorry")
   548  				core.SetFakeError(myError)
   549  				err := w.Init(mocks.MakeMockDataSet(itemv1))
   550  				assert.Equal(t, myError, err)
   551  				assert.Equal(t, st.SerializedItemDescriptor{}.NotFound(), core.ForceGet(mocks.MockData, key)) // underlying store does not have data
   552  
   553  				core.SetFakeError(nil)
   554  				result, err := w.GetAll(mocks.MockData)
   555  				require.NoError(t, err)
   556  				require.Len(t, result, 0) // cache does not have data either
   557  			})
   558  		})
   559  	}
   560  }
   561  

View as plain text