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  }