github.com/prebid/prebid-server/v2@v2.18.0/stored_requests/fetcher_test.go (about) 1 package stored_requests 2 3 import ( 4 "context" 5 "encoding/json" 6 "errors" 7 "testing" 8 9 "github.com/prebid/prebid-server/v2/metrics" 10 "github.com/prebid/prebid-server/v2/stored_requests/caches/nil_cache" 11 12 "github.com/stretchr/testify/assert" 13 "github.com/stretchr/testify/mock" 14 ) 15 16 func setupFetcherWithCacheDeps() (*mockCache, *mockCache, *mockCache, *mockFetcher, AllFetcher, *metrics.MetricsEngineMock) { 17 reqCache := &mockCache{} 18 impCache := &mockCache{} 19 respCache := &mockCache{} 20 metricsEngine := &metrics.MetricsEngineMock{} 21 fetcher := &mockFetcher{} 22 afetcherWithCache := WithCache(fetcher, Cache{reqCache, impCache, respCache, &nil_cache.NilCache{}}, metricsEngine) 23 24 return reqCache, impCache, respCache, fetcher, afetcherWithCache, metricsEngine 25 } 26 27 func TestPerfectCache(t *testing.T) { 28 reqCache, impCache, respCache, fetcher, aFetcherWithCache, metricsEngine := setupFetcherWithCacheDeps() 29 impIDs := []string{"known"} 30 reqIDs := []string{"req-id"} 31 respIDs := []string{"resp-id"} 32 ctx := context.Background() 33 34 reqCache.On("Get", ctx, reqIDs).Return( 35 map[string]json.RawMessage{ 36 "req-id": json.RawMessage(`{"req":true}`), 37 }) 38 impCache.On("Get", ctx, impIDs).Return( 39 map[string]json.RawMessage{ 40 "known": json.RawMessage(`{}`), 41 }) 42 respCache.On("Get", ctx, respIDs).Return( 43 map[string]json.RawMessage{ 44 "resp-id": json.RawMessage(`{"req":true}`), 45 }) 46 metricsEngine.On("RecordStoredReqCacheResult", metrics.CacheHit, 1) 47 metricsEngine.On("RecordStoredReqCacheResult", metrics.CacheMiss, 0) 48 metricsEngine.On("RecordStoredImpCacheResult", metrics.CacheHit, 1) 49 metricsEngine.On("RecordStoredImpCacheResult", metrics.CacheMiss, 0) 50 51 reqData, impData, errs := aFetcherWithCache.FetchRequests(ctx, reqIDs, impIDs) 52 respData, fetchRespErrs := aFetcherWithCache.FetchResponses(ctx, respIDs) 53 54 reqCache.AssertExpectations(t) 55 impCache.AssertExpectations(t) 56 respCache.AssertExpectations(t) 57 fetcher.AssertExpectations(t) 58 metricsEngine.AssertExpectations(t) 59 assert.JSONEq(t, `{"req":true}`, string(reqData["req-id"]), "Fetch requests should fetch the right request data") 60 assert.JSONEq(t, `{"req":true}`, string(respData["resp-id"]), "Fetch responses should fetch the right response data") 61 assert.JSONEq(t, `{}`, string(impData["known"]), "FetchRequests should fetch the right imp data") 62 assert.Len(t, errs, 0, "FetchRequest shouldn't return any errors") 63 assert.Len(t, fetchRespErrs, 0, "FetchResponses shouldn't return any errors") 64 } 65 66 func TestImperfectCache(t *testing.T) { 67 reqCache, impCache, respCache, fetcher, aFetcherWithCache, metricsEngine := setupFetcherWithCacheDeps() 68 impIDs := []string{"cached", "uncached"} 69 respIDs := []string{"cached", "uncached"} 70 ctx := context.Background() 71 72 impCache.On("Get", ctx, impIDs).Return( 73 map[string]json.RawMessage{ 74 "cached": json.RawMessage(`true`), 75 }) 76 reqCache.On("Get", ctx, []string(nil)).Return( 77 map[string]json.RawMessage{}) 78 respCache.On("Get", ctx, respIDs).Return( 79 map[string]json.RawMessage{ 80 "cached": json.RawMessage(`true`), 81 }) 82 83 fetcher.On("FetchRequests", ctx, []string{}, []string{"uncached"}).Return( 84 map[string]json.RawMessage{}, 85 map[string]json.RawMessage{ 86 "uncached": json.RawMessage(`false`), 87 }, 88 []error{}, 89 ) 90 impCache.On("Save", ctx, 91 map[string]json.RawMessage{ 92 "uncached": json.RawMessage(`false`), 93 }) 94 95 fetcher.On("FetchResponses", ctx, []string{"uncached"}).Return( 96 map[string]json.RawMessage{ 97 "uncached": json.RawMessage(`false`), 98 }, 99 []error{}, 100 ) 101 respCache.On("Save", ctx, 102 map[string]json.RawMessage{ 103 "uncached": json.RawMessage(`false`), 104 }) 105 106 reqCache.On("Save", ctx, map[string]json.RawMessage{}) 107 108 metricsEngine.On("RecordStoredReqCacheResult", metrics.CacheHit, 0) 109 metricsEngine.On("RecordStoredReqCacheResult", metrics.CacheMiss, 0) 110 metricsEngine.On("RecordStoredImpCacheResult", metrics.CacheHit, 1) 111 metricsEngine.On("RecordStoredImpCacheResult", metrics.CacheMiss, 1) 112 113 reqData, impData, errs := aFetcherWithCache.FetchRequests(ctx, nil, impIDs) 114 respData, fetchRespErrs := aFetcherWithCache.FetchResponses(ctx, respIDs) 115 116 impCache.AssertExpectations(t) 117 respCache.AssertExpectations(t) 118 fetcher.AssertExpectations(t) 119 metricsEngine.AssertExpectations(t) 120 assert.Len(t, reqData, 0, "Fetch requests should return nil if no request IDs were passed") 121 assert.JSONEq(t, `true`, string(impData["cached"]), "FetchRequests should fetch the right imp data") 122 assert.JSONEq(t, `false`, string(impData["uncached"]), "FetchRequests should fetch the right imp data") 123 assert.JSONEq(t, `true`, string(respData["cached"]), "FetchResponses should fetch the right resp data") 124 assert.JSONEq(t, `false`, string(respData["uncached"]), "FetchResponses should fetch the right resp data") 125 assert.Len(t, errs, 0, "FetchRequest shouldn't return any errors") 126 assert.Len(t, fetchRespErrs, 0, "FetchResponses shouldn't return any errors") 127 } 128 129 func TestMissingData(t *testing.T) { 130 reqCache, impCache, respCache, fetcher, aFetcherWithCache, metricsEngine := setupFetcherWithCacheDeps() 131 impIDs := []string{"unknown"} 132 respIDs := []string{"unknown"} 133 ctx := context.Background() 134 135 impCache.On("Get", ctx, impIDs).Return( 136 map[string]json.RawMessage{}, 137 ) 138 reqCache.On("Get", ctx, []string(nil)).Return( 139 map[string]json.RawMessage{}) 140 141 respCache.On("Get", ctx, []string(respIDs)).Return( 142 map[string]json.RawMessage{}) 143 144 fetcher.On("FetchRequests", ctx, []string{}, impIDs).Return( 145 map[string]json.RawMessage{}, 146 map[string]json.RawMessage{}, 147 []error{ 148 errors.New("Data not found"), 149 }, 150 ) 151 152 fetcher.On("FetchResponses", ctx, respIDs).Return( 153 map[string]json.RawMessage{}, 154 []error{ 155 errors.New("Data not found"), 156 }, 157 ) 158 159 impCache.On("Save", ctx, 160 map[string]json.RawMessage{}, 161 ) 162 reqCache.On("Save", ctx, 163 map[string]json.RawMessage{}, 164 ) 165 166 respCache.On("Save", ctx, 167 map[string]json.RawMessage{}, 168 ) 169 metricsEngine.On("RecordStoredReqCacheResult", metrics.CacheHit, 0) 170 metricsEngine.On("RecordStoredReqCacheResult", metrics.CacheMiss, 0) 171 metricsEngine.On("RecordStoredImpCacheResult", metrics.CacheHit, 0) 172 metricsEngine.On("RecordStoredImpCacheResult", metrics.CacheMiss, 1) 173 174 reqData, impData, errs := aFetcherWithCache.FetchRequests(ctx, nil, impIDs) 175 respData, fetchRespErrs := aFetcherWithCache.FetchResponses(ctx, respIDs) 176 177 reqCache.AssertExpectations(t) 178 impCache.AssertExpectations(t) 179 respCache.AssertExpectations(t) 180 fetcher.AssertExpectations(t) 181 metricsEngine.AssertExpectations(t) 182 assert.Len(t, errs, 1, "FetchRequests for missing data should return an error") 183 assert.Len(t, fetchRespErrs, 1, "FetchResponses for missing data should return an error") 184 assert.Len(t, reqData, 0, "FetchRequests for missing data shouldn't return anything") 185 assert.Len(t, impData, 0, "FetchRequests for missing data shouldn't return anything") 186 assert.Len(t, respData, 0, "FetchRequests for missing data shouldn't return anything") 187 } 188 189 // Prevents #311 190 func TestCacheSaves(t *testing.T) { 191 reqCache, impCache, respCache, fetcher, aFetcherWithCache, metricsEngine := setupFetcherWithCacheDeps() 192 impIDs := []string{"abc", "abc"} 193 respIDs := []string{"abc", "abc"} 194 ctx := context.Background() 195 196 impCache.On("Get", ctx, impIDs).Return( 197 map[string]json.RawMessage{ 198 "abc": json.RawMessage(`{}`), 199 }) 200 respCache.On("Get", ctx, respIDs).Return( 201 map[string]json.RawMessage{ 202 "abc": json.RawMessage(`{}`), 203 }) 204 reqCache.On("Get", ctx, []string(nil)).Return( 205 map[string]json.RawMessage{}) 206 metricsEngine.On("RecordStoredReqCacheResult", metrics.CacheHit, 0) 207 metricsEngine.On("RecordStoredReqCacheResult", metrics.CacheMiss, 0) 208 metricsEngine.On("RecordStoredImpCacheResult", metrics.CacheHit, 2) 209 metricsEngine.On("RecordStoredImpCacheResult", metrics.CacheMiss, 0) 210 211 _, impData, errs := aFetcherWithCache.FetchRequests(ctx, nil, []string{"abc", "abc"}) 212 respData, fetchRespErrs := aFetcherWithCache.FetchResponses(ctx, respIDs) 213 214 impCache.AssertExpectations(t) 215 respCache.AssertExpectations(t) 216 fetcher.AssertExpectations(t) 217 metricsEngine.AssertExpectations(t) 218 assert.Len(t, impData, 1, "FetchRequests should return data only once for duplicate requests") 219 assert.JSONEq(t, `{}`, string(impData["abc"]), "FetchRequests should fetch the right imp data") 220 assert.JSONEq(t, `{}`, string(respData["abc"]), "FetchResponses should fetch the right resp data") 221 assert.Len(t, errs, 0, "FetchRequests with duplicate IDs shouldn't return an error") 222 assert.Len(t, fetchRespErrs, 0, "FetchResponses with duplicate IDs shouldn't return an error") 223 } 224 225 func setupAccountFetcherWithCacheDeps() (*mockCache, *mockFetcher, AllFetcher, *metrics.MetricsEngineMock) { 226 accCache := &mockCache{} 227 metricsEngine := &metrics.MetricsEngineMock{} 228 fetcher := &mockFetcher{} 229 afetcherWithCache := WithCache(fetcher, Cache{&nil_cache.NilCache{}, &nil_cache.NilCache{}, &nil_cache.NilCache{}, accCache}, metricsEngine) 230 231 return accCache, fetcher, afetcherWithCache, metricsEngine 232 } 233 234 func TestAccountCacheHit(t *testing.T) { 235 accCache, fetcher, aFetcherWithCache, metricsEngine := setupAccountFetcherWithCacheDeps() 236 cachedAccounts := []string{"known"} 237 ctx := context.Background() 238 239 // Test read from cache 240 accCache.On("Get", ctx, cachedAccounts).Return( 241 map[string]json.RawMessage{ 242 "known": json.RawMessage(`true`), 243 }) 244 245 metricsEngine.On("RecordAccountCacheResult", metrics.CacheHit, 1) 246 account, errs := aFetcherWithCache.FetchAccount(ctx, json.RawMessage("{}"), "known") 247 248 accCache.AssertExpectations(t) 249 fetcher.AssertExpectations(t) 250 metricsEngine.AssertExpectations(t) 251 assert.JSONEq(t, `true`, string(account), "FetchAccount should fetch the right account data") 252 assert.Len(t, errs, 0, "FetchAccount shouldn't return any errors") 253 } 254 255 func TestAccountCacheMiss(t *testing.T) { 256 accCache, fetcher, aFetcherWithCache, metricsEngine := setupAccountFetcherWithCacheDeps() 257 uncachedAccounts := []string{"uncached"} 258 uncachedAccountsData := map[string]json.RawMessage{ 259 "uncached": json.RawMessage(`true`), 260 } 261 ctx := context.Background() 262 263 // Test read from cache 264 accCache.On("Get", ctx, uncachedAccounts).Return(map[string]json.RawMessage{}) 265 accCache.On("Save", ctx, uncachedAccountsData) 266 fetcher.On("FetchAccount", ctx, json.RawMessage("{}"), "uncached").Return(uncachedAccountsData["uncached"], []error{}) 267 metricsEngine.On("RecordAccountCacheResult", metrics.CacheMiss, 1) 268 269 account, errs := aFetcherWithCache.FetchAccount(ctx, json.RawMessage("{}"), "uncached") 270 271 accCache.AssertExpectations(t) 272 fetcher.AssertExpectations(t) 273 metricsEngine.AssertExpectations(t) 274 assert.JSONEq(t, `true`, string(account), "FetchAccount should fetch the right account data") 275 assert.Len(t, errs, 0, "FetchAccount shouldn't return any errors") 276 } 277 func TestComposedCache(t *testing.T) { 278 c1 := &mockCache{} 279 c2 := &mockCache{} 280 c3 := &mockCache{} 281 c4 := &mockCache{} 282 impCache := &mockCache{} 283 cache := Cache{ 284 Requests: ComposedCache{c1, c2, c3, c4}, 285 Imps: impCache, 286 Responses: ComposedCache{c1, c2, c3, c4}, 287 } 288 metricsEngine := &metrics.MetricsEngineMock{} 289 fetcher := &mockFetcher{} 290 aFetcherWithCache := WithCache(fetcher, cache, metricsEngine) 291 reqIDs := []string{"1", "2", "3"} 292 impIDs := []string{} 293 ctx := context.Background() 294 295 c1.On("Get", ctx, reqIDs).Return( 296 map[string]json.RawMessage{ 297 "1": json.RawMessage(`{"id": "1"}`), 298 }) 299 c2.On("Get", ctx, []string{"2", "3"}).Return( 300 map[string]json.RawMessage{ 301 "2": json.RawMessage(`{"id": "2"}`), 302 }) 303 c3.On("Get", ctx, []string{"3"}).Return( 304 map[string]json.RawMessage{ 305 "3": json.RawMessage(`{"id": "3"}`), 306 }) 307 impCache.On("Get", ctx, []string{}).Return(map[string]json.RawMessage{}) 308 309 metricsEngine.On("RecordStoredReqCacheResult", metrics.CacheHit, 3) 310 metricsEngine.On("RecordStoredReqCacheResult", metrics.CacheMiss, 0) 311 metricsEngine.On("RecordStoredImpCacheResult", metrics.CacheHit, 0) 312 metricsEngine.On("RecordStoredImpCacheResult", metrics.CacheMiss, 0) 313 314 reqData, impData, errs := aFetcherWithCache.FetchRequests(ctx, reqIDs, impIDs) 315 respData, fetchRespErrs := aFetcherWithCache.FetchResponses(ctx, reqIDs) 316 317 c1.AssertExpectations(t) 318 c2.AssertExpectations(t) 319 c3.AssertExpectations(t) 320 impCache.AssertExpectations(t) 321 fetcher.AssertExpectations(t) 322 metricsEngine.AssertExpectations(t) 323 assert.Len(t, reqData, len(reqIDs), "FetchRequests should be able to return all request data from a composed cache") 324 assert.Len(t, respData, len(reqIDs), "FetchResponses should be able to return all response data from a composed cache") 325 assert.Len(t, impData, len(impIDs), "FetchRequests should be able to return all imp data from a composed cache") 326 assert.Len(t, errs, 0, "FetchRequests shouldn't return an error when trying to use a composed cache") 327 assert.Len(t, fetchRespErrs, 0, "FetchResponses shouldn't return an error when trying to use a composed cache") 328 assert.JSONEq(t, `{"id": "1"}`, string(reqData["1"]), "FetchRequests should fetch the right req data") 329 assert.JSONEq(t, `{"id": "2"}`, string(reqData["2"]), "FetchRequests should fetch the right req data") 330 assert.JSONEq(t, `{"id": "3"}`, string(reqData["3"]), "FetchRequests should fetch the right req data") 331 assert.JSONEq(t, `{"id": "1"}`, string(respData["1"]), "FetchResponses should fetch the right resp data") 332 assert.JSONEq(t, `{"id": "2"}`, string(respData["2"]), "FetchResponses should fetch the right resp data") 333 assert.JSONEq(t, `{"id": "3"}`, string(respData["3"]), "FetchResponses should fetch the right resp data") 334 } 335 336 type mockFetcher struct { 337 mock.Mock 338 } 339 340 func (f *mockFetcher) FetchRequests(ctx context.Context, requestIDs []string, impIDs []string) (map[string]json.RawMessage, map[string]json.RawMessage, []error) { 341 args := f.Called(ctx, requestIDs, impIDs) 342 return args.Get(0).(map[string]json.RawMessage), args.Get(1).(map[string]json.RawMessage), args.Get(2).([]error) 343 } 344 345 func (f *mockFetcher) FetchResponses(ctx context.Context, ids []string) (data map[string]json.RawMessage, errs []error) { 346 args := f.Called(ctx, ids) 347 return args.Get(0).(map[string]json.RawMessage), args.Get(1).([]error) 348 } 349 350 func (a *mockFetcher) FetchAccount(ctx context.Context, defaultAccountsJSON json.RawMessage, accountID string) (json.RawMessage, []error) { 351 args := a.Called(ctx, defaultAccountsJSON, accountID) 352 return args.Get(0).(json.RawMessage), args.Get(1).([]error) 353 } 354 355 func (f *mockFetcher) FetchCategories(ctx context.Context, primaryAdServer, publisherId, iabCategory string) (string, error) { 356 return "", nil 357 } 358 359 type mockCache struct { 360 mock.Mock 361 } 362 363 func (c *mockCache) Get(ctx context.Context, ids []string) map[string]json.RawMessage { 364 args := c.Called(ctx, ids) 365 return args.Get(0).(map[string]json.RawMessage) 366 } 367 368 func (c *mockCache) Save(ctx context.Context, data map[string]json.RawMessage) { 369 c.Called(ctx, data) 370 } 371 372 func (c *mockCache) Invalidate(ctx context.Context, ids []string) { 373 c.Called(ctx, ids) 374 }