github.com/prebid/prebid-server/v2@v2.18.0/gdpr/vendorlist-fetching_test.go (about) 1 package gdpr 2 3 import ( 4 "context" 5 "net/http" 6 "net/http/httptest" 7 "strconv" 8 "testing" 9 10 "github.com/stretchr/testify/assert" 11 12 "github.com/prebid/go-gdpr/api" 13 "github.com/prebid/go-gdpr/consentconstants" 14 "github.com/prebid/prebid-server/v2/config" 15 "github.com/prebid/prebid-server/v2/util/jsonutil" 16 ) 17 18 func TestFetcherDynamicLoadListExists(t *testing.T) { 19 // Loads the first vendor list during initialization by setting the latest vendor list version to 1. 20 // All other vendor lists will be dynamically loaded. 21 22 server := httptest.NewServer(http.HandlerFunc(mockServer(serverSettings{ 23 vendorListLatestVersion: 1, 24 vendorLists: map[int]map[int]string{ 25 3: { 26 1: vendorList1, 27 2: vendorList2, 28 }, 29 }, 30 }))) 31 defer server.Close() 32 33 test := test{ 34 description: "Dynamic Load - List Exists", 35 setup: testSetup{ 36 specVersion: 3, 37 listVersion: 2, 38 }, 39 expected: vendorList2Expected, 40 } 41 42 runTest(t, test, server) 43 } 44 45 func TestFetcherDynamicLoadListDoesntExist(t *testing.T) { 46 // Loads the first vendor list during initialization by setting the latest vendor list version to 1. 47 // All other vendor list load attempts will be done dynamically. 48 49 server := httptest.NewServer(http.HandlerFunc(mockServer(serverSettings{ 50 vendorListLatestVersion: 1, 51 vendorLists: map[int]map[int]string{ 52 3: { 53 1: vendorList1, 54 }, 55 }, 56 }))) 57 defer server.Close() 58 59 test := test{ 60 description: "No Fallback - Vendor Doesn't Exist", 61 setup: testSetup{ 62 specVersion: 3, 63 listVersion: 2, 64 }, 65 expected: testExpected{ 66 errorMessage: "gdpr vendor list spec version 3 list version 2 does not exist, or has not been loaded yet. Try again in a few minutes", 67 }, 68 } 69 70 runTest(t, test, server) 71 } 72 73 func TestFetcherThrottling(t *testing.T) { 74 server := httptest.NewServer(http.HandlerFunc(mockServer(serverSettings{ 75 vendorListLatestVersion: 1, 76 vendorLists: map[int]map[int]string{ 77 3: { 78 1: MarshalVendorList(vendorList{ 79 GVLSpecificationVersion: 3, 80 VendorListVersion: 1, 81 Vendors: map[string]*vendor{"12": {ID: 12, Purposes: []int{1}}}, 82 }), 83 2: MarshalVendorList(vendorList{ 84 GVLSpecificationVersion: 3, 85 VendorListVersion: 2, 86 Vendors: map[string]*vendor{"12": {ID: 12, Purposes: []int{1, 2}}}, 87 }), 88 3: MarshalVendorList(vendorList{ 89 GVLSpecificationVersion: 3, 90 VendorListVersion: 3, 91 Vendors: map[string]*vendor{"12": {ID: 12, Purposes: []int{1, 2, 3}}}, 92 }), 93 }, 94 }, 95 }))) 96 defer server.Close() 97 98 fetcher := NewVendorListFetcher(context.Background(), testConfig(), server.Client(), testURLMaker(server)) 99 100 // Dynamically Load List 2 Successfully 101 _, errList1 := fetcher(context.Background(), 3, 2) 102 assert.NoError(t, errList1) 103 104 // Fail To Load List 3 Due To Rate Limiting 105 // - The request is rate limited after dynamically list 2. 106 _, errList2 := fetcher(context.Background(), 3, 3) 107 assert.EqualError(t, errList2, "gdpr vendor list spec version 3 list version 3 does not exist, or has not been loaded yet. Try again in a few minutes") 108 } 109 110 func TestMalformedVendorlist(t *testing.T) { 111 server := httptest.NewServer(http.HandlerFunc(mockServer(serverSettings{ 112 vendorListLatestVersion: 1, 113 vendorLists: map[int]map[int]string{ 114 3: { 115 1: "malformed", 116 }, 117 }, 118 }))) 119 defer server.Close() 120 121 fetcher := NewVendorListFetcher(context.Background(), testConfig(), server.Client(), testURLMaker(server)) 122 _, err := fetcher(context.Background(), 3, 1) 123 124 // Fetching should fail since vendor list could not be unmarshalled. 125 assert.Error(t, err) 126 } 127 128 func TestServerUrlInvalid(t *testing.T) { 129 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})) 130 server.Close() 131 132 invalidURLGenerator := func(uint16, uint16) string { return " http://invalid-url-has-leading-whitespace" } 133 134 fetcher := NewVendorListFetcher(context.Background(), testConfig(), server.Client(), invalidURLGenerator) 135 _, err := fetcher(context.Background(), 3, 1) 136 137 assert.EqualError(t, err, "gdpr vendor list spec version 3 list version 1 does not exist, or has not been loaded yet. Try again in a few minutes") 138 } 139 140 func TestServerUnavailable(t *testing.T) { 141 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})) 142 server.Close() 143 144 fetcher := NewVendorListFetcher(context.Background(), testConfig(), server.Client(), testURLMaker(server)) 145 _, err := fetcher(context.Background(), 3, 1) 146 147 assert.EqualError(t, err, "gdpr vendor list spec version 3 list version 1 does not exist, or has not been loaded yet. Try again in a few minutes") 148 } 149 150 func TestVendorListURLMaker(t *testing.T) { 151 testCases := []struct { 152 description string 153 specVersion uint16 154 listVersion uint16 155 expectedURL string 156 }{ 157 { 158 description: "Spec version 2 latest list", 159 specVersion: 2, 160 listVersion: 0, 161 expectedURL: "https://vendor-list.consensu.org/v2/vendor-list.json", 162 }, 163 { 164 description: "Spec version 2 specific list", 165 specVersion: 2, 166 listVersion: 42, 167 expectedURL: "https://vendor-list.consensu.org/v2/archives/vendor-list-v42.json", 168 }, 169 { 170 description: "Spec version 3 latest list", 171 specVersion: 3, 172 listVersion: 0, 173 expectedURL: "https://vendor-list.consensu.org/v3/vendor-list.json", 174 }, 175 { 176 description: "Spec version 3 specific list", 177 specVersion: 3, 178 listVersion: 42, 179 expectedURL: "https://vendor-list.consensu.org/v3/archives/vendor-list-v42.json", 180 }, 181 } 182 183 for _, test := range testCases { 184 result := VendorListURLMaker(test.specVersion, test.listVersion) 185 assert.Equal(t, test.expectedURL, result) 186 } 187 } 188 189 type versionInfo struct { 190 specVersion uint16 191 listVersion uint16 192 } 193 type saver []versionInfo 194 195 func (s *saver) saveVendorLists(specVersion uint16, listVersion uint16, gvl api.VendorList) { 196 vi := versionInfo{ 197 specVersion: specVersion, 198 listVersion: listVersion, 199 } 200 *s = append(*s, vi) 201 } 202 203 func TestPreloadCache(t *testing.T) { 204 server := httptest.NewServer(http.HandlerFunc(mockServer(serverSettings{ 205 vendorListLatestVersion: 3, 206 vendorLists: map[int]map[int]string{ 207 1: { 208 1: MarshalVendorList(vendorList{ 209 GVLSpecificationVersion: 1, VendorListVersion: 1, 210 }), 211 2: MarshalVendorList(vendorList{ 212 GVLSpecificationVersion: 1, VendorListVersion: 2, 213 }), 214 3: MarshalVendorList(vendorList{ 215 GVLSpecificationVersion: 1, VendorListVersion: 3, 216 }), 217 }, 218 2: { 219 1: MarshalVendorList(vendorList{ 220 GVLSpecificationVersion: 2, VendorListVersion: 1, 221 }), 222 2: MarshalVendorList(vendorList{ 223 GVLSpecificationVersion: 2, VendorListVersion: 2, 224 }), 225 3: MarshalVendorList(vendorList{ 226 GVLSpecificationVersion: 2, VendorListVersion: 3, 227 }), 228 }, 229 3: { 230 1: MarshalVendorList(vendorList{ 231 GVLSpecificationVersion: 3, VendorListVersion: 1, 232 }), 233 2: MarshalVendorList(vendorList{ 234 GVLSpecificationVersion: 3, VendorListVersion: 2, 235 }), 236 3: MarshalVendorList(vendorList{ 237 GVLSpecificationVersion: 3, VendorListVersion: 3, 238 }), 239 }, 240 4: { 241 1: MarshalVendorList(vendorList{ 242 GVLSpecificationVersion: 4, VendorListVersion: 1, 243 }), 244 2: MarshalVendorList(vendorList{ 245 GVLSpecificationVersion: 4, VendorListVersion: 2, 246 }), 247 3: MarshalVendorList(vendorList{ 248 GVLSpecificationVersion: 4, VendorListVersion: 3, 249 }), 250 }, 251 }, 252 }))) 253 defer server.Close() 254 255 s := make(saver, 0, 5) 256 preloadCache(context.Background(), server.Client(), testURLMaker(server), s.saveVendorLists) 257 258 expectedLoadedVersions := []versionInfo{ 259 {specVersion: 2, listVersion: 2}, 260 {specVersion: 2, listVersion: 3}, 261 {specVersion: 3, listVersion: 1}, 262 {specVersion: 3, listVersion: 2}, 263 {specVersion: 3, listVersion: 3}, 264 } 265 266 assert.ElementsMatch(t, expectedLoadedVersions, s) 267 } 268 269 var vendorList1 = MarshalVendorList(vendorList{ 270 GVLSpecificationVersion: 3, 271 VendorListVersion: 1, 272 Vendors: map[string]*vendor{"12": {ID: 12, Purposes: []int{2}}}, 273 }) 274 275 var vendorList2 = MarshalVendorList(vendorList{ 276 GVLSpecificationVersion: 3, 277 VendorListVersion: 2, 278 Vendors: map[string]*vendor{"12": {ID: 12, Purposes: []int{2, 3}}}, 279 }) 280 281 var vendorList2Expected = testExpected{ 282 vendorListVersion: 2, 283 vendorID: 12, 284 vendorPurposes: map[int]bool{1: false, 2: true, 3: true}, 285 } 286 287 var vendorListFallbackExpected = testExpected{ 288 vendorListVersion: 215, // Values from hardcoded fallback file. 289 vendorID: 12, 290 vendorPurposes: map[int]bool{1: true, 2: false, 3: true}, 291 } 292 293 type vendorList struct { 294 GVLSpecificationVersion uint16 `json:"gvlSpecificationVersion"` 295 VendorListVersion uint16 `json:"vendorListVersion"` 296 Vendors map[string]*vendor `json:"vendors"` 297 } 298 299 type vendor struct { 300 ID uint16 `json:"id"` 301 Purposes []int `json:"purposes"` 302 LegIntPurposes []int `json:"legIntPurposes"` 303 FlexiblePurposes []int `json:"flexiblePurposes"` 304 SpecialFeatures []int `json:"specialFeatures"` 305 } 306 307 func MarshalVendorList(vendorList vendorList) string { 308 json, _ := jsonutil.Marshal(vendorList) 309 return string(json) 310 } 311 312 type serverSettings struct { 313 vendorListLatestVersion int 314 vendorLists map[int]map[int]string 315 } 316 317 // mockServer returns a handler which returns the given response for each global vendor list version. 318 // The latestVersion param can be used to mock "updates" which occur after PBS has been turned on. 319 // For example, if latestVersion is 3, but the responses map has data at "4", the server will return 320 // version "3" when asked for the latest version. 321 // 322 // This will help test lazy-fetches for versions which aren't there on app startup. 323 // 324 // If the "version" query param doesn't exist, it returns a 400. 325 // 326 // If the "version" query param points to a version which doesn't exist, it returns a 403. 327 // Don't ask why... that's just what the official page is doing. See https://vendor-list.consensu.org/v-9999/vendorlist.json 328 func mockServer(settings serverSettings) func(http.ResponseWriter, *http.Request) { 329 return func(w http.ResponseWriter, req *http.Request) { 330 specVersion := req.URL.Query().Get("specversion") 331 specVersionInt, err := strconv.Atoi(specVersion) 332 if err != nil { 333 w.WriteHeader(http.StatusBadRequest) 334 w.Write([]byte("Request had invalid spec version: " + specVersion)) 335 return 336 } 337 listVersion := req.URL.Query().Get("listversion") 338 listVersionInt, err := strconv.Atoi(listVersion) 339 if err != nil { 340 w.WriteHeader(http.StatusBadRequest) 341 w.Write([]byte("Request had invalid version: " + listVersion)) 342 return 343 } 344 if listVersionInt == 0 { 345 listVersionInt = settings.vendorListLatestVersion 346 } 347 specVersionVendorLists, ok := settings.vendorLists[specVersionInt] 348 if !ok { 349 w.WriteHeader(http.StatusForbidden) 350 w.Write([]byte("Version not found: spec version " + specVersion + " list version " + listVersion)) 351 return 352 } 353 response, ok := specVersionVendorLists[listVersionInt] 354 if !ok { 355 w.WriteHeader(http.StatusForbidden) 356 w.Write([]byte("Version not found: " + listVersion)) 357 return 358 } 359 w.Write([]byte(response)) 360 } 361 } 362 363 type test struct { 364 description string 365 setup testSetup 366 expected testExpected 367 } 368 369 type testSetup struct { 370 specVersion uint16 371 listVersion uint16 372 } 373 374 type testExpected struct { 375 errorMessage string 376 vendorListVersion uint16 377 vendorID uint16 378 vendorPurposes map[int]bool 379 } 380 381 func runTest(t *testing.T, test test, server *httptest.Server) { 382 config := testConfig() 383 fetcher := NewVendorListFetcher(context.Background(), config, server.Client(), testURLMaker(server)) 384 vendorList, err := fetcher(context.Background(), test.setup.specVersion, test.setup.listVersion) 385 386 if test.expected.errorMessage != "" { 387 assert.EqualError(t, err, test.expected.errorMessage, test.description+":error") 388 } else { 389 assert.NoError(t, err, test.description+":vendorlist") 390 assert.Equal(t, test.expected.vendorListVersion, vendorList.Version(), test.description+":vendorlistid") 391 vendor := vendorList.Vendor(test.expected.vendorID) 392 for id, expected := range test.expected.vendorPurposes { 393 result := vendor.Purpose(consentconstants.Purpose(id)) 394 assert.Equalf(t, expected, result, "%s:vendor-%d:purpose-%d", test.description, vendorList.Version(), id) 395 } 396 } 397 } 398 399 func testURLMaker(server *httptest.Server) func(uint16, uint16) string { 400 url := server.URL 401 return func(specVersion, listVersion uint16) string { 402 return url + "?specversion=" + strconv.Itoa(int(specVersion)) + "&listversion=" + strconv.Itoa(int(listVersion)) 403 } 404 } 405 406 func testConfig() config.GDPR { 407 return config.GDPR{ 408 Timeouts: config.GDPRTimeouts{ 409 InitVendorlistFetch: 60 * 1000, 410 ActiveVendorlistFetch: 1000 * 5, 411 }, 412 } 413 }