github.com/prebid/prebid-server@v0.275.0/stored_requests/backends/http_fetcher/fetcher_test.go (about) 1 package http_fetcher 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "net/http" 8 "net/http/httptest" 9 "strings" 10 "testing" 11 "time" 12 13 "github.com/stretchr/testify/assert" 14 ) 15 16 func TestSingleReq(t *testing.T) { 17 fetcher, close := newTestFetcher(t, []string{"req-1"}, nil) 18 defer close() 19 20 reqData, impData, errs := fetcher.FetchRequests(context.Background(), []string{"req-1"}, nil) 21 assert.Empty(t, errs, "Unexpected errors fetching known requests") 22 assertMapKeys(t, reqData, "req-1") 23 assert.Empty(t, impData, "Unexpected imps returned fetching just requests") 24 } 25 26 func TestSeveralReqs(t *testing.T) { 27 fetcher, close := newTestFetcher(t, []string{"req-1", "req-2"}, nil) 28 defer close() 29 30 reqData, impData, errs := fetcher.FetchRequests(context.Background(), []string{"req-1", "req-2"}, nil) 31 assert.Empty(t, errs, "Unexpected errors fetching known requests") 32 assertMapKeys(t, reqData, "req-1", "req-2") 33 assert.Empty(t, impData, "Unexpected imps returned fetching just requests") 34 } 35 36 func TestSingleImp(t *testing.T) { 37 fetcher, close := newTestFetcher(t, nil, []string{"imp-1"}) 38 defer close() 39 40 reqData, impData, errs := fetcher.FetchRequests(context.Background(), nil, []string{"imp-1"}) 41 assert.Empty(t, errs, "Unexpected errors fetching known imps") 42 assert.Empty(t, reqData, "Unexpected requests returned fetching just imps") 43 assertMapKeys(t, impData, "imp-1") 44 } 45 46 func TestSeveralImps(t *testing.T) { 47 fetcher, close := newTestFetcher(t, nil, []string{"imp-1", "imp-2"}) 48 defer close() 49 50 reqData, impData, errs := fetcher.FetchRequests(context.Background(), nil, []string{"imp-1", "imp-2"}) 51 assert.Empty(t, errs, "Unexpected errors fetching known imps") 52 assert.Empty(t, reqData, "Unexpected requests returned fetching just imps") 53 assertMapKeys(t, impData, "imp-1", "imp-2") 54 } 55 56 func TestReqsAndImps(t *testing.T) { 57 fetcher, close := newTestFetcher(t, []string{"req-1"}, []string{"imp-1"}) 58 defer close() 59 60 reqData, impData, errs := fetcher.FetchRequests(context.Background(), []string{"req-1"}, []string{"imp-1"}) 61 assert.Empty(t, errs, "Unexpected errors fetching known reqs and imps") 62 assertMapKeys(t, reqData, "req-1") 63 assertMapKeys(t, impData, "imp-1") 64 } 65 66 func TestMissingValues(t *testing.T) { 67 fetcher, close := newEmptyFetcher(t, []string{"req-1", "req-2"}, []string{"imp-1"}) 68 defer close() 69 70 reqData, impData, errs := fetcher.FetchRequests(context.Background(), []string{"req-1", "req-2"}, []string{"imp-1"}) 71 assert.Empty(t, reqData, "Fetching unknown reqs should return no reqs") 72 assert.Empty(t, impData, "Fetching unknown imps should return no imps") 73 assert.Len(t, errs, 3, "Fetching 3 unknown reqs+imps should return 3 errors") 74 } 75 76 func TestFetchAccounts(t *testing.T) { 77 fetcher, close := newTestAccountFetcher(t, []string{"acc-1", "acc-2"}) 78 defer close() 79 80 accData, errs := fetcher.FetchAccounts(context.Background(), []string{"acc-1", "acc-2"}) 81 assert.Empty(t, errs, "Unexpected error fetching known accounts") 82 assertMapKeys(t, accData, "acc-1", "acc-2") 83 } 84 85 func TestFetchAccountsNoData(t *testing.T) { 86 fetcher, close := newFetcherBrokenBackend() 87 defer close() 88 89 accData, errs := fetcher.FetchAccounts(context.Background(), []string{"req-1"}) 90 assert.Len(t, errs, 1, "Fetching unknown account should have returned an error") 91 assert.Nil(t, accData, "Fetching unknown account should return nil account map") 92 } 93 94 func TestFetchAccountsBadJSON(t *testing.T) { 95 fetcher, close := newFetcherBadJSON() 96 defer close() 97 98 accData, errs := fetcher.FetchAccounts(context.Background(), []string{"req-1"}) 99 assert.Len(t, errs, 1, "Fetching account with broken json should have returned an error") 100 assert.Nil(t, accData, "Fetching account with broken json should return nil account map") 101 } 102 103 func TestFetchAccountsNoIDsProvided(t *testing.T) { 104 fetcher, close := newTestAccountFetcher(t, []string{"acc-1", "acc-2"}) 105 defer close() 106 107 accData, errs := fetcher.FetchAccounts(nil, []string{}) 108 assert.Empty(t, errs, "Unexpected error fetching empty account list") 109 assert.Nil(t, accData, "Fetching empty account list should return nil") 110 } 111 112 // Force build request failure by not providing a context 113 func TestFetchAccountsFailedBuildRequest(t *testing.T) { 114 fetcher, close := newTestAccountFetcher(t, []string{"acc-1", "acc-2"}) 115 defer close() 116 117 accData, errs := fetcher.FetchAccounts(nil, []string{"acc-1"}) 118 assert.Len(t, errs, 1, "Fetching accounts without context should result in error ") 119 assert.Nil(t, accData, "Fetching accounts without context should return nil") 120 } 121 122 // Force http error via request timeout 123 func TestFetchAccountsContextTimeout(t *testing.T) { 124 fetcher, close := newTestAccountFetcher(t, []string{"acc-1", "acc-2"}) 125 defer close() 126 127 ctx, cancel := context.WithTimeout(context.Background(), time.Duration(0)) 128 defer cancel() 129 accData, errs := fetcher.FetchAccounts(ctx, []string{"acc-1"}) 130 assert.Len(t, errs, 1, "Expected context timeout error") 131 assert.Nil(t, accData, "Unexpected account data returned instead of timeout") 132 } 133 134 func TestFetchAccount(t *testing.T) { 135 fetcher, close := newTestAccountFetcher(t, []string{"acc-1"}) 136 defer close() 137 138 account, errs := fetcher.FetchAccount(context.Background(), json.RawMessage(`{"disabled":true}`), "acc-1") 139 assert.Empty(t, errs, "Unexpected error fetching existing account") 140 assert.JSONEq(t, `{"disabled": true, "id":"acc-1"}`, string(account), "Unexpected account data fetching existing account") 141 } 142 143 func TestAccountMergeError(t *testing.T) { 144 fetcher, close := newTestAccountFetcher(t, []string{"acc-1"}) 145 defer close() 146 147 _, errs := fetcher.FetchAccount(context.Background(), json.RawMessage(`{"disabled"}`), "acc-1") 148 assert.Error(t, errs[0]) 149 assert.Equal(t, fmt.Errorf("Invalid JSON Document"), errs[0]) 150 } 151 152 func TestFetchAccountNoData(t *testing.T) { 153 fetcher, close := newFetcherBrokenBackend() 154 defer close() 155 156 unknownAccount, errs := fetcher.FetchAccount(context.Background(), json.RawMessage(`{disabled":true}`), "unknown-acc") 157 assert.NotEmpty(t, errs, "Retrieving unknown account should return error") 158 assert.Nil(t, unknownAccount, "Retrieving unknown account should return nil json.RawMessage") 159 } 160 161 func TestFetchAccountNoIDProvided(t *testing.T) { 162 fetcher, close := newTestAccountFetcher(t, nil) 163 defer close() 164 165 account, errs := fetcher.FetchAccount(context.Background(), json.RawMessage(`{disabled":true}`), "") 166 assert.Len(t, errs, 1, "Fetching account with empty id should error") 167 assert.Nil(t, account, "Fetching account with empty id should return nil") 168 } 169 170 func TestErrResponse(t *testing.T) { 171 fetcher, close := newFetcherBrokenBackend() 172 defer close() 173 reqData, impData, errs := fetcher.FetchRequests(context.Background(), []string{"req-1"}, []string{"imp-1"}) 174 assertMapKeys(t, reqData) 175 assertMapKeys(t, impData) 176 assert.Len(t, errs, 1) 177 } 178 179 func newFetcherBrokenBackend() (fetcher *HttpFetcher, closer func()) { 180 handler := func(w http.ResponseWriter, r *http.Request) { 181 w.WriteHeader(http.StatusInternalServerError) 182 } 183 server := httptest.NewServer(http.HandlerFunc(handler)) 184 return NewFetcher(server.Client(), server.URL), server.Close 185 } 186 187 func newFetcherBadJSON() (fetcher *HttpFetcher, closer func()) { 188 handler := func(w http.ResponseWriter, r *http.Request) { 189 w.Write([]byte(`broken JSON`)) 190 } 191 server := httptest.NewServer(http.HandlerFunc(handler)) 192 return NewFetcher(server.Client(), server.URL), server.Close 193 } 194 195 func newEmptyFetcher(t *testing.T, expectReqIDs []string, expectImpIDs []string) (fetcher *HttpFetcher, closer func()) { 196 handler := newHandler(t, expectReqIDs, expectImpIDs, jsonifyToNull) 197 server := httptest.NewServer(http.HandlerFunc(handler)) 198 return NewFetcher(server.Client(), server.URL), server.Close 199 } 200 201 func newTestFetcher(t *testing.T, expectReqIDs []string, expectImpIDs []string) (fetcher *HttpFetcher, closer func()) { 202 handler := newHandler(t, expectReqIDs, expectImpIDs, jsonifyID) 203 server := httptest.NewServer(http.HandlerFunc(handler)) 204 return NewFetcher(server.Client(), server.URL), server.Close 205 } 206 207 func newHandler(t *testing.T, expectReqIDs []string, expectImpIDs []string, jsonifier func(string) json.RawMessage) func(w http.ResponseWriter, r *http.Request) { 208 return func(w http.ResponseWriter, r *http.Request) { 209 query := r.URL.Query() 210 gotReqIDs := richSplit(query.Get("request-ids")) 211 gotImpIDs := richSplit(query.Get("imp-ids")) 212 213 assertMatches(t, gotReqIDs, expectReqIDs) 214 assertMatches(t, gotImpIDs, expectImpIDs) 215 216 reqIDResponse := make(map[string]json.RawMessage, len(gotReqIDs)) 217 impIDResponse := make(map[string]json.RawMessage, len(gotImpIDs)) 218 219 for _, reqID := range gotReqIDs { 220 if reqID != "" { 221 reqIDResponse[reqID] = jsonifier(reqID) 222 } 223 } 224 225 for _, impID := range gotImpIDs { 226 if impID != "" { 227 impIDResponse[impID] = jsonifier(impID) 228 } 229 } 230 231 respObj := responseContract{ 232 Requests: reqIDResponse, 233 Imps: impIDResponse, 234 } 235 236 if respBytes, err := json.Marshal(respObj); err != nil { 237 t.Errorf("failed to marshal responseContract in test: %v", err) 238 w.WriteHeader(http.StatusInternalServerError) 239 } else { 240 w.Write(respBytes) 241 } 242 } 243 } 244 245 func newTestAccountFetcher(t *testing.T, expectAccIDs []string) (fetcher *HttpFetcher, closer func()) { 246 handler := newAccountHandler(t, expectAccIDs) 247 server := httptest.NewServer(http.HandlerFunc(handler)) 248 return NewFetcher(server.Client(), server.URL), server.Close 249 } 250 251 func newAccountHandler(t *testing.T, expectAccIDs []string) func(w http.ResponseWriter, r *http.Request) { 252 return func(w http.ResponseWriter, r *http.Request) { 253 query := r.URL.Query() 254 gotAccIDs := richSplit(query.Get("account-ids")) 255 256 assertMatches(t, gotAccIDs, expectAccIDs) 257 258 accIDResponse := make(map[string]json.RawMessage, len(gotAccIDs)) 259 260 for _, accID := range gotAccIDs { 261 if accID != "" { 262 accIDResponse[accID] = json.RawMessage(`{"id":"` + accID + `"}`) 263 } 264 } 265 266 respObj := accountsResponseContract{ 267 Accounts: accIDResponse, 268 } 269 270 if respBytes, err := json.Marshal(respObj); err != nil { 271 t.Errorf("failed to marshal responseContract in test: %v", err) 272 w.WriteHeader(http.StatusInternalServerError) 273 } else { 274 w.Write(respBytes) 275 } 276 } 277 } 278 279 func assertMatches(t *testing.T, queryVals []string, expected []string) { 280 t.Helper() 281 282 if len(queryVals) == 1 && queryVals[0] == "" { 283 if len(expected) != 0 { 284 t.Errorf("Expected no query vals, but got %v", queryVals) 285 } 286 return 287 } 288 if len(queryVals) != len(expected) { 289 t.Errorf("Query vals did not match. Expected %v, got %v", expected, queryVals) 290 return 291 } 292 293 for _, expectedQuery := range expected { 294 found := false 295 for _, queryVal := range queryVals { 296 if queryVal == expectedQuery { 297 found = true 298 break 299 } 300 } 301 if !found { 302 t.Errorf("Query string missing expected key %s.", expectedQuery) 303 } 304 } 305 } 306 307 // Split the id values properly 308 func richSplit(queryVal string) []string { 309 // Get rid of the bounding [] 310 // Not doing real validation, as this is a test routine, and given a malformed input we want to fail anyway. 311 if len(queryVal) > 2 { 312 queryVal = queryVal[1 : len(queryVal)-1] 313 } 314 values := strings.Split(queryVal, "\",\"") 315 if len(values) > 0 { 316 //Fix leading and trailing " 317 if len(values[0]) > 0 { 318 values[0] = values[0][1:] 319 } 320 l := len(values) - 1 321 if len(values[l]) > 0 { 322 values[l] = values[l][:len(values[l])-1] 323 } 324 } 325 326 return values 327 } 328 329 func jsonifyID(id string) json.RawMessage { 330 if b, err := json.Marshal(id); err != nil { 331 return json.RawMessage([]byte("\"error encoding ID=" + id + "\"")) 332 } else { 333 return json.RawMessage(b) 334 } 335 } 336 337 func jsonifyToNull(id string) json.RawMessage { 338 return json.RawMessage("null") 339 } 340 341 func assertMapKeys(t *testing.T, m map[string]json.RawMessage, keys ...string) { 342 t.Helper() 343 344 if len(m) != len(keys) { 345 t.Errorf("Expected %d map keys. Map was: %v", len(keys), m) 346 } 347 348 for _, key := range keys { 349 if _, ok := m[key]; !ok { 350 t.Errorf("Map missing expected key %s. Data was %v", key, m) 351 } 352 } 353 }