github.com/prebid/prebid-server/v2@v2.18.0/stored_requests/events/database/database_test.go (about)

     1  package database
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"regexp"
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/prebid/prebid-server/v2/config"
    11  	"github.com/prebid/prebid-server/v2/metrics"
    12  	"github.com/prebid/prebid-server/v2/stored_requests/backends/db_provider"
    13  	"github.com/prebid/prebid-server/v2/stored_requests/events"
    14  	"github.com/stretchr/testify/assert"
    15  	"github.com/stretchr/testify/mock"
    16  
    17  	sqlmock "github.com/DATA-DOG/go-sqlmock"
    18  )
    19  
    20  // FakeTime implements the Time interface
    21  type FakeTime struct {
    22  	time time.Time
    23  }
    24  
    25  func (mc *FakeTime) Now() time.Time {
    26  	return mc.time
    27  }
    28  
    29  const fakeQuery = "SELECT id, requestData, type FROM stored_data"
    30  
    31  func fakeQueryRegex() string {
    32  	return "^" + regexp.QuoteMeta(fakeQuery) + "$"
    33  }
    34  
    35  func TestFetchAllSuccess(t *testing.T) {
    36  	tests := []struct {
    37  		description          string
    38  		giveFakeTime         time.Time
    39  		giveMockRows         *sqlmock.Rows
    40  		wantLastUpdate       time.Time
    41  		wantSavedReqs        map[string]json.RawMessage
    42  		wantSavedImps        map[string]json.RawMessage
    43  		wantSavedResps       map[string]json.RawMessage
    44  		wantInvalidatedReqs  []string
    45  		wantInvalidatedImps  []string
    46  		wantInvalidatedResps []string
    47  	}{
    48  		{
    49  			description:    "saved reqs = 0, saved imps = 0, invalidated reqs = 0, invalidated imps = 0",
    50  			giveFakeTime:   time.Date(2020, time.July, 1, 12, 30, 0, 0, time.UTC),
    51  			giveMockRows:   sqlmock.NewRows([]string{"id", "data", "dataType"}),
    52  			wantLastUpdate: time.Date(2020, time.July, 1, 12, 30, 0, 0, time.UTC),
    53  		},
    54  		{
    55  			description:    "saved reqs > 0, saved imps = 0, saved resps = 0, invalidated reqs = 0, invalidated imps = 0, invalidated resps = 0",
    56  			giveFakeTime:   time.Date(2020, time.July, 1, 12, 30, 0, 0, time.UTC),
    57  			giveMockRows:   sqlmock.NewRows([]string{"id", "data", "dataType"}).AddRow("req-1", "true", "request"),
    58  			wantLastUpdate: time.Date(2020, time.July, 1, 12, 30, 0, 0, time.UTC),
    59  			wantSavedReqs:  map[string]json.RawMessage{"req-1": json.RawMessage(`true`)},
    60  			wantSavedImps:  map[string]json.RawMessage{},
    61  			wantSavedResps: map[string]json.RawMessage{},
    62  		},
    63  		{
    64  			description:    "saved reqs = 0, saved imps > 0, saved resps = 0, invalidated reqs = 0, invalidated imps = 0, invalidated resps = 0",
    65  			giveFakeTime:   time.Date(2020, time.July, 1, 12, 30, 0, 0, time.UTC),
    66  			giveMockRows:   sqlmock.NewRows([]string{"id", "data", "dataType"}).AddRow("imp-1", "true", "imp"),
    67  			wantLastUpdate: time.Date(2020, time.July, 1, 12, 30, 0, 0, time.UTC),
    68  			wantSavedReqs:  map[string]json.RawMessage{},
    69  			wantSavedImps:  map[string]json.RawMessage{"imp-1": json.RawMessage(`true`)},
    70  			wantSavedResps: map[string]json.RawMessage{},
    71  		},
    72  		{
    73  			description:    "saved reqs = 0, saved imps = 0, saved responses > 0, invalidated reqs = 0, invalidated imps = 0, invalidated responses = 0",
    74  			giveFakeTime:   time.Date(2020, time.July, 1, 12, 30, 0, 0, time.UTC),
    75  			giveMockRows:   sqlmock.NewRows([]string{"id", "data", "dataType"}).AddRow("resp-1", "true", "response"),
    76  			wantLastUpdate: time.Date(2020, time.July, 1, 12, 30, 0, 0, time.UTC),
    77  			wantSavedReqs:  map[string]json.RawMessage{},
    78  			wantSavedImps:  map[string]json.RawMessage{},
    79  			wantSavedResps: map[string]json.RawMessage{"resp-1": json.RawMessage(`true`)},
    80  		},
    81  		{
    82  			description:    "saved reqs = 0, saved imps = 0, saved responses = 0, invalidated reqs > 0, invalidated imps = 0, invalidated responses = 0",
    83  			giveFakeTime:   time.Date(2020, time.July, 1, 12, 30, 0, 0, time.UTC),
    84  			giveMockRows:   sqlmock.NewRows([]string{"id", "data", "dataType"}).AddRow("req-1", "", "request"),
    85  			wantLastUpdate: time.Date(2020, time.July, 1, 12, 30, 0, 0, time.UTC),
    86  		},
    87  		{
    88  			description:    "saved reqs = 0, saved imps = 0, saved responses = 0, invalidated reqs = 0, invalidated imps > 0, invalidated responses = 0",
    89  			giveFakeTime:   time.Date(2020, time.July, 1, 12, 30, 0, 0, time.UTC),
    90  			giveMockRows:   sqlmock.NewRows([]string{"id", "data", "dataType"}).AddRow("imp-1", "", "imp"),
    91  			wantLastUpdate: time.Date(2020, time.July, 1, 12, 30, 0, 0, time.UTC),
    92  		},
    93  		{
    94  			description:    "saved reqs = 0, saved imps = 0, saved responses = 0, invalidated reqs = 0, invalidated imps = 0, invalidated responses > 0",
    95  			giveFakeTime:   time.Date(2020, time.July, 1, 12, 30, 0, 0, time.UTC),
    96  			giveMockRows:   sqlmock.NewRows([]string{"id", "data", "dataType"}).AddRow("resp-1", "", "response"),
    97  			wantLastUpdate: time.Date(2020, time.July, 1, 12, 30, 0, 0, time.UTC),
    98  		},
    99  		{
   100  			description:  "saved reqs > 0, saved imps > 0, saved responses > 0, invalidated reqs > 0, invalidated imps > 0, invalidated responses > 0",
   101  			giveFakeTime: time.Date(2020, time.July, 1, 12, 30, 0, 0, time.UTC),
   102  			giveMockRows: sqlmock.NewRows([]string{"id", "data", "dataType"}).
   103  				AddRow("req-1", "true", "request").
   104  				AddRow("imp-1", "true", "imp").
   105  				AddRow("req-2", "", "request").
   106  				AddRow("imp-2", "", "imp").
   107  				AddRow("resp-1", "true", "response").
   108  				AddRow("resp-2", "", "response"),
   109  			wantLastUpdate: time.Date(2020, time.July, 1, 12, 30, 0, 0, time.UTC),
   110  			wantSavedReqs:  map[string]json.RawMessage{"req-1": json.RawMessage(`true`)},
   111  			wantSavedImps:  map[string]json.RawMessage{"imp-1": json.RawMessage(`true`)},
   112  			wantSavedResps: map[string]json.RawMessage{"resp-1": json.RawMessage(`true`)},
   113  		},
   114  	}
   115  
   116  	for _, tt := range tests {
   117  		provider, dbMock, _ := db_provider.NewDbProviderMock()
   118  		dbMock.ExpectQuery(fakeQueryRegex()).WillReturnRows(tt.giveMockRows)
   119  
   120  		metricsMock := &metrics.MetricsEngineMock{}
   121  		metricsMock.Mock.On("RecordStoredDataFetchTime", metrics.StoredDataLabels{
   122  			DataType:      metrics.RequestDataType,
   123  			DataFetchType: metrics.FetchAll,
   124  		}, mock.Anything).Return()
   125  
   126  		eventProducer := NewDatabaseEventProducer(DatabaseEventProducerConfig{
   127  			Provider:         provider,
   128  			RequestType:      config.RequestDataType,
   129  			CacheInitTimeout: 100 * time.Millisecond,
   130  			CacheInitQuery:   fakeQuery,
   131  			MetricsEngine:    metricsMock,
   132  		})
   133  		eventProducer.time = &FakeTime{time: tt.giveFakeTime}
   134  		err := eventProducer.Run()
   135  
   136  		assert.Nil(t, err, tt.description)
   137  		assert.Equal(t, tt.wantLastUpdate, eventProducer.lastUpdate, tt.description)
   138  
   139  		var saves events.Save
   140  		// Read data from saves channel with timeout to avoid test suite deadlock
   141  		select {
   142  		case saves = <-eventProducer.Saves():
   143  		case <-time.After(20 * time.Millisecond):
   144  		}
   145  		var invalidations events.Invalidation
   146  		// Read data from invalidations channel with timeout to avoid test suite deadlock
   147  		select {
   148  		case invalidations = <-eventProducer.Invalidations():
   149  		case <-time.After(20 * time.Millisecond):
   150  		}
   151  
   152  		assert.Equal(t, tt.wantSavedReqs, saves.Requests, tt.description)
   153  		assert.Equal(t, tt.wantSavedImps, saves.Imps, tt.description)
   154  		assert.Equal(t, tt.wantSavedResps, saves.Responses, tt.description)
   155  		assert.Equal(t, tt.wantInvalidatedReqs, invalidations.Requests, tt.description)
   156  		assert.Equal(t, tt.wantInvalidatedImps, invalidations.Imps, tt.description)
   157  		assert.Equal(t, tt.wantInvalidatedResps, invalidations.Responses, tt.description)
   158  
   159  		metricsMock.AssertExpectations(t)
   160  	}
   161  }
   162  
   163  func TestFetchAllErrors(t *testing.T) {
   164  	tests := []struct {
   165  		description       string
   166  		giveFakeTime      time.Time
   167  		giveTimeoutMS     int
   168  		giveMockRows      *sqlmock.Rows
   169  		wantRecordedError metrics.StoredDataError
   170  		wantLastUpdate    time.Time
   171  	}{
   172  		{
   173  			description:       "fetch all timeout",
   174  			giveFakeTime:      time.Date(2020, time.July, 1, 12, 30, 0, 0, time.UTC),
   175  			giveMockRows:      nil,
   176  			wantRecordedError: metrics.StoredDataErrorNetwork,
   177  			wantLastUpdate:    time.Time{},
   178  		},
   179  		{
   180  			description:       "fetch all query error",
   181  			giveFakeTime:      time.Date(2020, time.July, 1, 12, 30, 0, 0, time.UTC),
   182  			giveTimeoutMS:     100,
   183  			giveMockRows:      nil,
   184  			wantRecordedError: metrics.StoredDataErrorUndefined,
   185  			wantLastUpdate:    time.Time{},
   186  		},
   187  		{
   188  			description:   "fetch all row error",
   189  			giveFakeTime:  time.Date(2020, time.July, 1, 12, 30, 0, 0, time.UTC),
   190  			giveTimeoutMS: 100,
   191  			giveMockRows: sqlmock.NewRows([]string{"id", "data", "dataType"}).
   192  				AddRow("stored-req-id", "true", "request").
   193  				RowError(0, errors.New("Some row error.")),
   194  			wantRecordedError: metrics.StoredDataErrorUndefined,
   195  			wantLastUpdate:    time.Time{},
   196  		},
   197  	}
   198  
   199  	for _, tt := range tests {
   200  		provider, dbMock, _ := db_provider.NewDbProviderMock()
   201  		if tt.giveMockRows == nil {
   202  			dbMock.ExpectQuery(fakeQueryRegex()).WillReturnError(errors.New("Query failed."))
   203  		} else {
   204  			dbMock.ExpectQuery(fakeQueryRegex()).WillReturnRows(tt.giveMockRows)
   205  		}
   206  
   207  		metricsMock := &metrics.MetricsEngineMock{}
   208  		metricsMock.Mock.On("RecordStoredDataFetchTime", metrics.StoredDataLabels{
   209  			DataType:      metrics.RequestDataType,
   210  			DataFetchType: metrics.FetchAll,
   211  		}, mock.Anything).Return()
   212  		metricsMock.Mock.On("RecordStoredDataError", metrics.StoredDataLabels{
   213  			DataType: metrics.RequestDataType,
   214  			Error:    tt.wantRecordedError,
   215  		}).Return()
   216  
   217  		eventProducer := NewDatabaseEventProducer(DatabaseEventProducerConfig{
   218  			Provider:         provider,
   219  			RequestType:      config.RequestDataType,
   220  			CacheInitTimeout: time.Duration(tt.giveTimeoutMS) * time.Millisecond,
   221  			CacheInitQuery:   fakeQuery,
   222  			MetricsEngine:    metricsMock,
   223  		})
   224  		eventProducer.time = &FakeTime{time: tt.giveFakeTime}
   225  		err := eventProducer.Run()
   226  
   227  		assert.NotNil(t, err, tt.description)
   228  		assert.Equal(t, tt.wantLastUpdate, eventProducer.lastUpdate, tt.description)
   229  
   230  		var saves events.Save
   231  		// Read data from saves channel with timeout to avoid test suite deadlock
   232  		select {
   233  		case saves = <-eventProducer.Saves():
   234  		case <-time.After(10 * time.Millisecond):
   235  		}
   236  		var invalidations events.Invalidation
   237  		// Read data from invalidations channel with timeout to avoid test suite deadlock
   238  		select {
   239  		case invalidations = <-eventProducer.Invalidations():
   240  		case <-time.After(10 * time.Millisecond):
   241  		}
   242  
   243  		assert.Nil(t, saves.Requests, tt.description)
   244  		assert.Nil(t, saves.Imps, tt.description)
   245  		assert.Nil(t, saves.Responses, tt.description)
   246  		assert.Nil(t, invalidations.Requests, tt.description)
   247  		assert.Nil(t, invalidations.Imps, tt.description)
   248  		assert.Nil(t, invalidations.Responses, tt.description)
   249  
   250  		metricsMock.AssertExpectations(t)
   251  	}
   252  }
   253  
   254  func TestFetchDeltaSuccess(t *testing.T) {
   255  	tests := []struct {
   256  		description          string
   257  		giveFakeTime         time.Time
   258  		giveMockRows         *sqlmock.Rows
   259  		wantLastUpdate       time.Time
   260  		wantSavedReqs        map[string]json.RawMessage
   261  		wantSavedImps        map[string]json.RawMessage
   262  		wantSavedResps       map[string]json.RawMessage
   263  		wantInvalidatedReqs  []string
   264  		wantInvalidatedImps  []string
   265  		wantInvalidatedResps []string
   266  	}{
   267  		{
   268  			description:    "saved reqs = 0, saved imps = 0, saved resps = 0, invalidated reqs = 0, invalidated imps = 0, invalidated resps = 0",
   269  			giveFakeTime:   time.Date(2020, time.July, 1, 12, 30, 0, 0, time.UTC),
   270  			giveMockRows:   sqlmock.NewRows([]string{"id", "data", "dataType"}),
   271  			wantLastUpdate: time.Date(2020, time.July, 1, 12, 30, 0, 0, time.UTC),
   272  		},
   273  		{
   274  			description:    "saved reqs > 0, saved imps = 0, saved resps = 0, invalidated reqs = 0, invalidated imps = 0, invalidated resps = 0",
   275  			giveFakeTime:   time.Date(2020, time.July, 1, 12, 30, 0, 0, time.UTC),
   276  			giveMockRows:   sqlmock.NewRows([]string{"id", "data", "dataType"}).AddRow("req-1", "true", "request"),
   277  			wantLastUpdate: time.Date(2020, time.July, 1, 12, 30, 0, 0, time.UTC),
   278  			wantSavedReqs:  map[string]json.RawMessage{"req-1": json.RawMessage(`true`)},
   279  			wantSavedImps:  map[string]json.RawMessage{},
   280  			wantSavedResps: map[string]json.RawMessage{},
   281  		},
   282  		{
   283  			description:    "saved reqs = 0, saved imps > 0, saved resps = 0, invalidated reqs = 0, invalidated imps = 0, invalidated resps = 0",
   284  			giveFakeTime:   time.Date(2020, time.July, 1, 12, 30, 0, 0, time.UTC),
   285  			giveMockRows:   sqlmock.NewRows([]string{"id", "data", "dataType"}).AddRow("imp-1", "true", "imp"),
   286  			wantLastUpdate: time.Date(2020, time.July, 1, 12, 30, 0, 0, time.UTC),
   287  			wantSavedReqs:  map[string]json.RawMessage{},
   288  			wantSavedImps:  map[string]json.RawMessage{"imp-1": json.RawMessage(`true`)},
   289  			wantSavedResps: map[string]json.RawMessage{},
   290  		},
   291  		{
   292  			description:    "saved reqs = 0, saved imps = 0, saved resps > 0, invalidated reqs = 0, invalidated imps = 0, invalidated resps = 0",
   293  			giveFakeTime:   time.Date(2020, time.July, 1, 12, 30, 0, 0, time.UTC),
   294  			giveMockRows:   sqlmock.NewRows([]string{"id", "data", "dataType"}).AddRow("resp-1", "true", "response"),
   295  			wantLastUpdate: time.Date(2020, time.July, 1, 12, 30, 0, 0, time.UTC),
   296  			wantSavedReqs:  map[string]json.RawMessage{},
   297  			wantSavedImps:  map[string]json.RawMessage{},
   298  			wantSavedResps: map[string]json.RawMessage{"resp-1": json.RawMessage(`true`)},
   299  		},
   300  		{
   301  			description:          "saved reqs = 0, saved imps = 0, saved resps = 0, invalidated reqs > 0, invalidated imps = 0, invalidated resps = 0, empty data",
   302  			giveFakeTime:         time.Date(2020, time.July, 1, 12, 30, 0, 0, time.UTC),
   303  			giveMockRows:         sqlmock.NewRows([]string{"id", "data", "dataType"}).AddRow("req-1", "", "request"),
   304  			wantLastUpdate:       time.Date(2020, time.July, 1, 12, 30, 0, 0, time.UTC),
   305  			wantInvalidatedReqs:  []string{"req-1"},
   306  			wantInvalidatedImps:  nil,
   307  			wantInvalidatedResps: nil,
   308  		},
   309  		{
   310  			description:          "saved reqs = 0, saved imps = 0, saved resps = 0, invalidated reqs = 0, invalidated imps = 0, invalidated resps > 0, null data",
   311  			giveFakeTime:         time.Date(2020, time.July, 1, 12, 30, 0, 0, time.UTC),
   312  			giveMockRows:         sqlmock.NewRows([]string{"id", "data", "dataType"}).AddRow("resp-1", "null", "response"),
   313  			wantLastUpdate:       time.Date(2020, time.July, 1, 12, 30, 0, 0, time.UTC),
   314  			wantInvalidatedReqs:  nil,
   315  			wantInvalidatedImps:  nil,
   316  			wantInvalidatedResps: []string{"resp-1"},
   317  		},
   318  		{
   319  			description:         "saved reqs = 0, saved imps = 0, saved resps = 0, invalidated reqs = 0, invalidated imps > 0, invalidated resps = 0, empty data",
   320  			giveFakeTime:        time.Date(2020, time.July, 1, 12, 30, 0, 0, time.UTC),
   321  			giveMockRows:        sqlmock.NewRows([]string{"id", "data", "dataType"}).AddRow("imp-1", "", "imp"),
   322  			wantLastUpdate:      time.Date(2020, time.July, 1, 12, 30, 0, 0, time.UTC),
   323  			wantInvalidatedImps: []string{"imp-1"},
   324  		},
   325  		{
   326  			description:         "saved reqs = 0, saved imps = 0, saved resps = 0, invalidated reqs = 0, invalidated imps > 0, invalidated resps = 0, null data",
   327  			giveFakeTime:        time.Date(2020, time.July, 1, 12, 30, 0, 0, time.UTC),
   328  			giveMockRows:        sqlmock.NewRows([]string{"id", "data", "dataType"}).AddRow("imp-1", "null", "imp"),
   329  			wantLastUpdate:      time.Date(2020, time.July, 1, 12, 30, 0, 0, time.UTC),
   330  			wantInvalidatedImps: []string{"imp-1"},
   331  		},
   332  		{
   333  			description:  "saved reqs > 0, saved imps > 0, saved resps > 0, invalidated reqs > 0, invalidated imps > 0, invalidated resps > 0",
   334  			giveFakeTime: time.Date(2020, time.July, 1, 12, 30, 0, 0, time.UTC),
   335  			giveMockRows: sqlmock.NewRows([]string{"id", "data", "dataType"}).
   336  				AddRow("req-1", "true", "request").
   337  				AddRow("imp-1", "true", "imp").
   338  				AddRow("resps-1", "true", "response").
   339  				AddRow("req-2", "", "request").
   340  				AddRow("imp-2", "", "imp").
   341  				AddRow("resps-2", "", "response"),
   342  			wantLastUpdate:       time.Date(2020, time.July, 1, 12, 30, 0, 0, time.UTC),
   343  			wantSavedReqs:        map[string]json.RawMessage{"req-1": json.RawMessage(`true`)},
   344  			wantSavedImps:        map[string]json.RawMessage{"imp-1": json.RawMessage(`true`)},
   345  			wantSavedResps:       map[string]json.RawMessage{"resps-1": json.RawMessage(`true`)},
   346  			wantInvalidatedReqs:  []string{"req-2"},
   347  			wantInvalidatedImps:  []string{"imp-2"},
   348  			wantInvalidatedResps: []string{"resps-2"},
   349  		},
   350  	}
   351  
   352  	for _, tt := range tests {
   353  		provider, dbMock, _ := db_provider.NewDbProviderMock()
   354  		dbMock.ExpectQuery(fakeQueryRegex()).WillReturnRows(tt.giveMockRows)
   355  
   356  		metricsMock := &metrics.MetricsEngineMock{}
   357  		metricsMock.Mock.On("RecordStoredDataFetchTime", metrics.StoredDataLabels{
   358  			DataType:      metrics.RequestDataType,
   359  			DataFetchType: metrics.FetchDelta,
   360  		}, mock.Anything).Return()
   361  
   362  		eventProducer := NewDatabaseEventProducer(DatabaseEventProducerConfig{
   363  			Provider:           provider,
   364  			RequestType:        config.RequestDataType,
   365  			CacheUpdateTimeout: 100 * time.Millisecond,
   366  			CacheUpdateQuery:   fakeQuery,
   367  			MetricsEngine:      metricsMock,
   368  		})
   369  		eventProducer.lastUpdate = time.Date(2020, time.June, 30, 6, 0, 0, 0, time.UTC)
   370  		eventProducer.time = &FakeTime{time: tt.giveFakeTime}
   371  		err := eventProducer.Run()
   372  
   373  		assert.Nil(t, err, tt.description)
   374  		assert.Equal(t, tt.wantLastUpdate, eventProducer.lastUpdate, tt.description)
   375  
   376  		var saves events.Save
   377  		// Read data from saves channel with timeout to avoid test suite deadlock
   378  		select {
   379  		case saves = <-eventProducer.Saves():
   380  		case <-time.After(20 * time.Millisecond):
   381  		}
   382  		var invalidations events.Invalidation
   383  		// Read data from invalidations channel with timeout to avoid test suite deadlock
   384  		select {
   385  		case invalidations = <-eventProducer.Invalidations():
   386  		case <-time.After(20 * time.Millisecond):
   387  		}
   388  
   389  		assert.Equal(t, tt.wantSavedReqs, saves.Requests, tt.description)
   390  		assert.Equal(t, tt.wantSavedImps, saves.Imps, tt.description)
   391  		assert.Equal(t, tt.wantSavedResps, saves.Responses, tt.description)
   392  		assert.Equal(t, tt.wantInvalidatedReqs, invalidations.Requests, tt.description)
   393  		assert.Equal(t, tt.wantInvalidatedImps, invalidations.Imps, tt.description)
   394  		assert.Equal(t, tt.wantInvalidatedResps, invalidations.Responses, tt.description)
   395  
   396  		metricsMock.AssertExpectations(t)
   397  	}
   398  }
   399  
   400  func TestFetchDeltaErrors(t *testing.T) {
   401  	tests := []struct {
   402  		description       string
   403  		giveFakeTime      time.Time
   404  		giveTimeoutMS     int
   405  		giveLastUpdate    time.Time
   406  		giveMockRows      *sqlmock.Rows
   407  		wantRecordedError metrics.StoredDataError
   408  		wantLastUpdate    time.Time
   409  	}{
   410  		{
   411  			description:       "fetch delta timeout",
   412  			giveFakeTime:      time.Date(2020, time.July, 1, 12, 30, 0, 0, time.UTC),
   413  			giveLastUpdate:    time.Date(2020, time.June, 30, 6, 0, 0, 0, time.UTC),
   414  			giveMockRows:      nil,
   415  			wantRecordedError: metrics.StoredDataErrorNetwork,
   416  			wantLastUpdate:    time.Date(2020, time.June, 30, 6, 0, 0, 0, time.UTC),
   417  		},
   418  		{
   419  			description:       "fetch delta query error",
   420  			giveFakeTime:      time.Date(2020, time.July, 1, 12, 30, 0, 0, time.UTC),
   421  			giveTimeoutMS:     100,
   422  			giveLastUpdate:    time.Date(2020, time.June, 30, 6, 0, 0, 0, time.UTC),
   423  			giveMockRows:      nil,
   424  			wantRecordedError: metrics.StoredDataErrorUndefined,
   425  			wantLastUpdate:    time.Date(2020, time.June, 30, 6, 0, 0, 0, time.UTC),
   426  		},
   427  		{
   428  			description:    "fetch delta row error",
   429  			giveFakeTime:   time.Date(2020, time.July, 1, 12, 30, 0, 0, time.UTC),
   430  			giveTimeoutMS:  100,
   431  			giveLastUpdate: time.Date(2020, time.June, 30, 6, 0, 0, 0, time.UTC),
   432  			giveMockRows: sqlmock.NewRows([]string{"id", "data", "dataType"}).
   433  				AddRow("stored-req-id", "true", "request").
   434  				RowError(0, errors.New("Some row error.")),
   435  			wantRecordedError: metrics.StoredDataErrorUndefined,
   436  			wantLastUpdate:    time.Date(2020, time.June, 30, 6, 0, 0, 0, time.UTC),
   437  		},
   438  	}
   439  
   440  	for _, tt := range tests {
   441  		provider, dbMock, _ := db_provider.NewDbProviderMock()
   442  		if tt.giveMockRows == nil {
   443  			dbMock.ExpectQuery(fakeQueryRegex()).WillReturnError(errors.New("Query failed."))
   444  		} else {
   445  			dbMock.ExpectQuery(fakeQueryRegex()).WillReturnRows(tt.giveMockRows)
   446  		}
   447  
   448  		metricsMock := &metrics.MetricsEngineMock{}
   449  		metricsMock.Mock.On("RecordStoredDataFetchTime", metrics.StoredDataLabels{
   450  			DataType:      metrics.RequestDataType,
   451  			DataFetchType: metrics.FetchDelta,
   452  		}, mock.Anything).Return()
   453  		metricsMock.Mock.On("RecordStoredDataError", metrics.StoredDataLabels{
   454  			DataType: metrics.RequestDataType,
   455  			Error:    tt.wantRecordedError,
   456  		}).Return()
   457  
   458  		eventProducer := NewDatabaseEventProducer(DatabaseEventProducerConfig{
   459  			Provider:           provider,
   460  			RequestType:        config.RequestDataType,
   461  			CacheUpdateTimeout: time.Duration(tt.giveTimeoutMS) * time.Millisecond,
   462  			CacheUpdateQuery:   fakeQuery,
   463  			MetricsEngine:      metricsMock,
   464  		})
   465  		eventProducer.lastUpdate = tt.giveLastUpdate
   466  		eventProducer.time = &FakeTime{time: tt.giveFakeTime}
   467  		err := eventProducer.Run()
   468  
   469  		assert.NotNil(t, err, tt.description)
   470  		assert.Equal(t, tt.wantLastUpdate, eventProducer.lastUpdate, tt.description)
   471  
   472  		var saves events.Save
   473  		// Read data from saves channel with timeout to avoid test suite deadlock
   474  		select {
   475  		case saves = <-eventProducer.Saves():
   476  		case <-time.After(10 * time.Millisecond):
   477  		}
   478  		var invalidations events.Invalidation
   479  		// Read data from invalidations channel with timeout to avoid test suite deadlock
   480  		select {
   481  		case invalidations = <-eventProducer.Invalidations():
   482  		case <-time.After(10 * time.Millisecond):
   483  		}
   484  
   485  		assert.Nil(t, saves.Requests, tt.description)
   486  		assert.Nil(t, saves.Imps, tt.description)
   487  		assert.Nil(t, saves.Responses, tt.description)
   488  		assert.Nil(t, invalidations.Requests, tt.description)
   489  		assert.Nil(t, invalidations.Imps, tt.description)
   490  		assert.Nil(t, invalidations.Responses, tt.description)
   491  
   492  		metricsMock.AssertExpectations(t)
   493  	}
   494  }