github.com/prebid/prebid-server/v2@v2.18.0/stored_requests/config/config_test.go (about) 1 package config 2 3 import ( 4 "context" 5 "encoding/json" 6 "errors" 7 "net/http" 8 "net/http/httptest" 9 "regexp" 10 "testing" 11 12 "github.com/stretchr/testify/assert" 13 14 sqlmock "github.com/DATA-DOG/go-sqlmock" 15 "github.com/julienschmidt/httprouter" 16 "github.com/prebid/prebid-server/v2/config" 17 "github.com/prebid/prebid-server/v2/metrics" 18 "github.com/prebid/prebid-server/v2/stored_requests" 19 "github.com/prebid/prebid-server/v2/stored_requests/backends/db_provider" 20 "github.com/prebid/prebid-server/v2/stored_requests/backends/empty_fetcher" 21 "github.com/prebid/prebid-server/v2/stored_requests/backends/http_fetcher" 22 "github.com/prebid/prebid-server/v2/stored_requests/events" 23 httpEvents "github.com/prebid/prebid-server/v2/stored_requests/events/http" 24 "github.com/stretchr/testify/mock" 25 ) 26 27 func typedConfig(dataType config.DataType, sr *config.StoredRequests) *config.StoredRequests { 28 sr.SetDataType(dataType) 29 return sr 30 } 31 32 func isEmptyCacheType(cache stored_requests.CacheJSON) bool { 33 cache.Save(context.Background(), map[string]json.RawMessage{"foo": json.RawMessage("true")}) 34 objs := cache.Get(context.Background(), []string{"foo"}) 35 return len(objs) == 0 36 } 37 38 func isMemoryCacheType(cache stored_requests.CacheJSON) bool { 39 cache.Save(context.Background(), map[string]json.RawMessage{"foo": json.RawMessage("true")}) 40 objs := cache.Get(context.Background(), []string{"foo"}) 41 return len(objs) == 1 42 } 43 44 func TestNewEmptyFetcher(t *testing.T) { 45 46 type testCase struct { 47 config *config.StoredRequests 48 emptyFetcher bool 49 description string 50 } 51 testCases := []testCase{ 52 { 53 config: &config.StoredRequests{}, 54 emptyFetcher: true, 55 description: "If the config is empty, an EmptyFetcher should be returned", 56 }, 57 { 58 config: &config.StoredRequests{ 59 Database: config.DatabaseConfig{ 60 ConnectionInfo: config.DatabaseConnection{ 61 Driver: "postgres", 62 }, 63 CacheInitialization: config.DatabaseCacheInitializer{ 64 Query: "test query", 65 }, 66 PollUpdates: config.DatabaseUpdatePolling{ 67 Query: "test poll query", 68 }, 69 FetcherQueries: config.DatabaseFetcherQueries{ 70 QueryTemplate: "", 71 }, 72 }, 73 }, 74 emptyFetcher: true, 75 description: "If Database fetcher query is not defined, but Database Cache init query and Database update polling query are defined EmptyFetcher should be returned", 76 }, 77 { 78 config: &config.StoredRequests{ 79 Database: config.DatabaseConfig{ 80 ConnectionInfo: config.DatabaseConnection{ 81 Driver: "postgres", 82 }, 83 CacheInitialization: config.DatabaseCacheInitializer{ 84 Query: "", 85 }, 86 PollUpdates: config.DatabaseUpdatePolling{ 87 Query: "", 88 }, 89 FetcherQueries: config.DatabaseFetcherQueries{ 90 QueryTemplate: "test fetcher query", 91 }, 92 }, 93 }, 94 emptyFetcher: false, 95 description: "If Database fetcher query is defined, but Database Cache init query and Database update polling query are not defined not EmptyFetcher (DBFetcher) should be returned", 96 }, 97 { 98 config: &config.StoredRequests{ 99 Database: config.DatabaseConfig{ 100 ConnectionInfo: config.DatabaseConnection{ 101 Driver: "postgres", 102 }, 103 CacheInitialization: config.DatabaseCacheInitializer{ 104 Query: "test cache query", 105 }, 106 PollUpdates: config.DatabaseUpdatePolling{ 107 Query: "test poll query", 108 }, 109 FetcherQueries: config.DatabaseFetcherQueries{ 110 QueryTemplate: "test fetcher query", 111 }, 112 }, 113 }, 114 emptyFetcher: false, 115 description: "If Database fetcher query is defined and Database Cache init query and Database update polling query are defined not EmptyFetcher (DBFetcher) should be returned", 116 }, 117 } 118 119 for _, test := range testCases { 120 fetcher := newFetcher(test.config, nil, db_provider.DbProviderMock{}) 121 assert.NotNil(t, fetcher, "The fetcher should be non-nil.") 122 if test.emptyFetcher { 123 assert.Equal(t, empty_fetcher.EmptyFetcher{}, fetcher, "Empty fetcher should be returned") 124 } else { 125 assert.NotEqual(t, empty_fetcher.EmptyFetcher{}, fetcher) 126 } 127 } 128 } 129 130 func TestNewHTTPFetcher(t *testing.T) { 131 fetcher := newFetcher(&config.StoredRequests{ 132 HTTP: config.HTTPFetcherConfig{ 133 Endpoint: "stored-requests.prebid.com", 134 }, 135 }, nil, nil) 136 if httpFetcher, ok := fetcher.(*http_fetcher.HttpFetcher); ok { 137 if httpFetcher.Endpoint != "stored-requests.prebid.com?" { 138 t.Errorf("The HTTP fetcher is using the wrong endpoint. Expected %s, got %s", "stored-requests.prebid.com?", httpFetcher.Endpoint) 139 } 140 } else { 141 t.Errorf("An HTTP Fetching config should return an HTTPFetcher. Got %v", fetcher) 142 } 143 } 144 145 func TestNewHTTPEvents(t *testing.T) { 146 handler := func(w http.ResponseWriter, r *http.Request) { 147 w.WriteHeader(http.StatusInternalServerError) 148 } 149 server1 := httptest.NewServer(http.HandlerFunc(handler)) 150 151 cfg := &config.StoredRequests{ 152 HTTPEvents: config.HTTPEventsConfig{ 153 Endpoint: server1.URL, 154 RefreshRate: 100, 155 Timeout: 1000, 156 }, 157 } 158 159 metricsMock := &metrics.MetricsEngineMock{} 160 161 evProducers := newEventProducers(cfg, server1.Client(), nil, metricsMock, nil) 162 assertSliceLength(t, evProducers, 1) 163 assertHttpWithURL(t, evProducers[0], server1.URL) 164 } 165 166 func TestNewEmptyCache(t *testing.T) { 167 cache := newCache(&config.StoredRequests{InMemoryCache: config.InMemoryCache{Type: "none"}}) 168 assert.True(t, isEmptyCacheType(cache.Requests), "The newCache method should return an empty Request cache") 169 assert.True(t, isEmptyCacheType(cache.Imps), "The newCache method should return an empty Imp cache") 170 assert.True(t, isEmptyCacheType(cache.Responses), "The newCache method should return an empty Responses cache") 171 assert.True(t, isEmptyCacheType(cache.Accounts), "The newCache method should return an empty Account cache") 172 } 173 174 func TestNewInMemoryCache(t *testing.T) { 175 cache := newCache(&config.StoredRequests{ 176 InMemoryCache: config.InMemoryCache{ 177 TTL: 60, 178 RequestCacheSize: 100, 179 ImpCacheSize: 100, 180 RespCacheSize: 100, 181 }, 182 }) 183 assert.True(t, isMemoryCacheType(cache.Requests), "The newCache method should return an in-memory Request cache for StoredRequests config") 184 assert.True(t, isMemoryCacheType(cache.Imps), "The newCache method should return an in-memory Imp cache for StoredRequests config") 185 assert.True(t, isMemoryCacheType(cache.Responses), "The newCache method should return an in-memory Responses cache for StoredResponses config") 186 assert.True(t, isEmptyCacheType(cache.Accounts), "The newCache method should return an empty Account cache for StoredRequests config") 187 } 188 189 func TestNewInMemoryAccountCache(t *testing.T) { 190 cache := newCache(typedConfig(config.AccountDataType, &config.StoredRequests{ 191 InMemoryCache: config.InMemoryCache{ 192 TTL: 60, 193 Size: 100, 194 }, 195 })) 196 assert.True(t, isMemoryCacheType(cache.Accounts), "The newCache method should return an in-memory Account cache for Accounts config") 197 assert.True(t, isEmptyCacheType(cache.Requests), "The newCache method should return an empty Request cache for Accounts config") 198 assert.True(t, isEmptyCacheType(cache.Imps), "The newCache method should return an empty Imp cache for Accounts config") 199 assert.True(t, isEmptyCacheType(cache.Responses), "The newCache method should return an empty Responses cache for Accounts config") 200 } 201 202 func TestNewDatabaseEventProducers(t *testing.T) { 203 metricsMock := &metrics.MetricsEngineMock{} 204 metricsMock.Mock.On("RecordStoredDataFetchTime", mock.Anything, mock.Anything).Return() 205 metricsMock.Mock.On("RecordStoredDataError", mock.Anything).Return() 206 207 cfg := &config.StoredRequests{ 208 Database: config.DatabaseConfig{ 209 CacheInitialization: config.DatabaseCacheInitializer{ 210 Timeout: 50, 211 Query: "SELECT id, requestData, type FROM stored_data", 212 }, 213 PollUpdates: config.DatabaseUpdatePolling{ 214 RefreshRate: 20, 215 Timeout: 50, 216 Query: "SELECT id, requestData, type FROM stored_data WHERE last_updated > $1", 217 }, 218 }, 219 } 220 client := &http.Client{} 221 provider, mock, err := db_provider.NewDbProviderMock() 222 if err != nil { 223 t.Fatalf("Failed to create mock: %v", err) 224 } 225 mock.ExpectQuery("^" + regexp.QuoteMeta(cfg.Database.CacheInitialization.Query) + "$").WillReturnError(errors.New("Query failed")) 226 227 evProducers := newEventProducers(cfg, client, provider, metricsMock, nil) 228 assertProducerLength(t, evProducers, 1) 229 230 assertExpectationsMet(t, mock) 231 metricsMock.AssertExpectations(t) 232 } 233 234 func TestNewEventsAPI(t *testing.T) { 235 router := httprouter.New() 236 newEventsAPI(router, "/test-endpoint") 237 if handle, _, _ := router.Lookup("POST", "/test-endpoint"); handle == nil { 238 t.Error("The newEventsAPI method didn't add a POST /test-endpoint route") 239 } 240 if handle, _, _ := router.Lookup("DELETE", "/test-endpoint"); handle == nil { 241 t.Error("The newEventsAPI method didn't add a DELETE /test-endpoint route") 242 } 243 } 244 245 func assertProducerLength(t *testing.T, producers []events.EventProducer, expectedLength int) { 246 t.Helper() 247 if len(producers) != expectedLength { 248 t.Errorf("Expected %d producers, but got %d", expectedLength, len(producers)) 249 } 250 } 251 252 func assertExpectationsMet(t *testing.T, mock sqlmock.Sqlmock) { 253 if err := mock.ExpectationsWereMet(); err != nil { 254 t.Errorf("sqlmock expectations were not met: %v", err) 255 } 256 } 257 258 func assertHttpWithURL(t *testing.T, ev events.EventProducer, url string) { 259 if casted, ok := ev.(*httpEvents.HTTPEvents); ok { 260 assertStringsEqual(t, casted.Endpoint, url) 261 } else { 262 t.Errorf("The EventProducer was not a *HTTPEvents") 263 } 264 } 265 266 func assertSliceLength(t *testing.T, producers []events.EventProducer, expected int) { 267 t.Helper() 268 269 if len(producers) != expected { 270 t.Fatalf("Expected %d EventProducers. Got: %v", expected, producers) 271 } 272 } 273 274 func assertStringsEqual(t *testing.T, actual string, expected string) { 275 t.Helper() 276 277 if actual != expected { 278 t.Fatalf("String %s did not match expected %s", actual, expected) 279 } 280 }