github.com/prebid/prebid-server/v2@v2.18.0/floors/fetcher_test.go (about) 1 package floors 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "math/rand" 7 "net/http" 8 "net/http/httptest" 9 "reflect" 10 "testing" 11 "time" 12 13 "github.com/alitto/pond" 14 "github.com/coocood/freecache" 15 "github.com/prebid/prebid-server/v2/config" 16 "github.com/prebid/prebid-server/v2/metrics" 17 metricsConf "github.com/prebid/prebid-server/v2/metrics/config" 18 "github.com/prebid/prebid-server/v2/openrtb_ext" 19 "github.com/prebid/prebid-server/v2/util/ptrutil" 20 "github.com/prebid/prebid-server/v2/util/timeutil" 21 "github.com/stretchr/testify/assert" 22 ) 23 24 const MaxAge = "max-age" 25 26 func TestFetchQueueLen(t *testing.T) { 27 tests := []struct { 28 name string 29 fq FetchQueue 30 want int 31 }{ 32 { 33 name: "Queue is empty", 34 fq: make(FetchQueue, 0), 35 want: 0, 36 }, 37 { 38 name: "Queue is of lenght 1", 39 fq: make(FetchQueue, 1), 40 want: 1, 41 }, 42 } 43 for _, tt := range tests { 44 t.Run(tt.name, func(t *testing.T) { 45 if got := tt.fq.Len(); got != tt.want { 46 t.Errorf("FetchQueue.Len() = %v, want %v", got, tt.want) 47 } 48 }) 49 } 50 } 51 52 func TestFetchQueueLess(t *testing.T) { 53 type args struct { 54 i int 55 j int 56 } 57 tests := []struct { 58 name string 59 fq FetchQueue 60 args args 61 want bool 62 }{ 63 { 64 name: "first fetchperiod is less than second", 65 fq: FetchQueue{&fetchInfo{fetchTime: 10}, &fetchInfo{fetchTime: 20}}, 66 args: args{i: 0, j: 1}, 67 want: true, 68 }, 69 { 70 name: "first fetchperiod is greater than second", 71 fq: FetchQueue{&fetchInfo{fetchTime: 30}, &fetchInfo{fetchTime: 10}}, 72 args: args{i: 0, j: 1}, 73 want: false, 74 }, 75 } 76 for _, tt := range tests { 77 t.Run(tt.name, func(t *testing.T) { 78 if got := tt.fq.Less(tt.args.i, tt.args.j); got != tt.want { 79 t.Errorf("FetchQueue.Less() = %v, want %v", got, tt.want) 80 } 81 }) 82 } 83 } 84 85 func TestFetchQueueSwap(t *testing.T) { 86 type args struct { 87 i int 88 j int 89 } 90 tests := []struct { 91 name string 92 fq FetchQueue 93 args args 94 }{ 95 { 96 name: "Swap two elements at index i and j", 97 fq: FetchQueue{&fetchInfo{fetchTime: 30}, &fetchInfo{fetchTime: 10}}, 98 args: args{i: 0, j: 1}, 99 }, 100 } 101 for _, tt := range tests { 102 t.Run(tt.name, func(t *testing.T) { 103 fInfo1, fInfo2 := tt.fq[0], tt.fq[1] 104 tt.fq.Swap(tt.args.i, tt.args.j) 105 assert.Equal(t, fInfo1, tt.fq[1], "elements are not swapped") 106 assert.Equal(t, fInfo2, tt.fq[0], "elements are not swapped") 107 }) 108 } 109 } 110 111 func TestFetchQueuePush(t *testing.T) { 112 type args struct { 113 element interface{} 114 } 115 tests := []struct { 116 name string 117 fq *FetchQueue 118 args args 119 }{ 120 { 121 name: "Push element to queue", 122 fq: &FetchQueue{}, 123 args: args{element: &fetchInfo{fetchTime: 10}}, 124 }, 125 } 126 for _, tt := range tests { 127 t.Run(tt.name, func(t *testing.T) { 128 tt.fq.Push(tt.args.element) 129 q := *tt.fq 130 assert.Equal(t, q[0], &fetchInfo{fetchTime: 10}) 131 }) 132 } 133 } 134 135 func TestFetchQueuePop(t *testing.T) { 136 tests := []struct { 137 name string 138 fq *FetchQueue 139 want interface{} 140 }{ 141 { 142 name: "Pop element from queue", 143 fq: &FetchQueue{&fetchInfo{fetchTime: 10}}, 144 want: &fetchInfo{fetchTime: 10}, 145 }, 146 } 147 for _, tt := range tests { 148 t.Run(tt.name, func(t *testing.T) { 149 if got := tt.fq.Pop(); !reflect.DeepEqual(got, tt.want) { 150 t.Errorf("FetchQueue.Pop() = %v, want %v", got, tt.want) 151 } 152 }) 153 } 154 } 155 156 func TestFetchQueueTop(t *testing.T) { 157 tests := []struct { 158 name string 159 fq *FetchQueue 160 want *fetchInfo 161 }{ 162 { 163 name: "Get top element from queue", 164 fq: &FetchQueue{&fetchInfo{fetchTime: 20}}, 165 want: &fetchInfo{fetchTime: 20}, 166 }, 167 { 168 name: "Queue is empty", 169 fq: &FetchQueue{}, 170 want: nil, 171 }, 172 } 173 for _, tt := range tests { 174 t.Run(tt.name, func(t *testing.T) { 175 if got := tt.fq.Top(); !reflect.DeepEqual(got, tt.want) { 176 t.Errorf("FetchQueue.Top() = %v, want %v", got, tt.want) 177 } 178 }) 179 } 180 } 181 182 func TestValidatePriceFloorRules(t *testing.T) { 183 var zero = 0 184 var one_o_one = 101 185 var testURL = "abc.com" 186 type args struct { 187 configs config.AccountFloorFetch 188 priceFloors *openrtb_ext.PriceFloorRules 189 } 190 tests := []struct { 191 name string 192 args args 193 wantErr bool 194 }{ 195 { 196 name: "Price floor data is empty", 197 args: args{ 198 configs: config.AccountFloorFetch{ 199 Enabled: true, 200 URL: testURL, 201 Timeout: 5, 202 MaxFileSizeKB: 20, 203 MaxRules: 5, 204 MaxAge: 20, 205 Period: 10, 206 }, 207 priceFloors: &openrtb_ext.PriceFloorRules{}, 208 }, 209 wantErr: true, 210 }, 211 { 212 name: "Model group array is empty", 213 args: args{ 214 configs: config.AccountFloorFetch{ 215 Enabled: true, 216 URL: testURL, 217 Timeout: 5, 218 MaxFileSizeKB: 20, 219 MaxRules: 5, 220 MaxAge: 20, 221 Period: 10, 222 }, 223 priceFloors: &openrtb_ext.PriceFloorRules{ 224 Data: &openrtb_ext.PriceFloorData{}, 225 }, 226 }, 227 wantErr: true, 228 }, 229 { 230 name: "floor rules is empty", 231 args: args{ 232 configs: config.AccountFloorFetch{ 233 Enabled: true, 234 URL: testURL, 235 Timeout: 5, 236 MaxFileSizeKB: 20, 237 MaxRules: 5, 238 MaxAge: 20, 239 Period: 10, 240 }, 241 priceFloors: &openrtb_ext.PriceFloorRules{ 242 Data: &openrtb_ext.PriceFloorData{ 243 ModelGroups: []openrtb_ext.PriceFloorModelGroup{{ 244 Values: map[string]float64{}, 245 }}, 246 }, 247 }, 248 }, 249 wantErr: true, 250 }, 251 { 252 name: "floor rules is grater than max floor rules", 253 args: args{ 254 configs: config.AccountFloorFetch{ 255 Enabled: true, 256 URL: testURL, 257 Timeout: 5, 258 MaxFileSizeKB: 20, 259 MaxRules: 0, 260 MaxAge: 20, 261 Period: 10, 262 }, 263 priceFloors: &openrtb_ext.PriceFloorRules{ 264 Data: &openrtb_ext.PriceFloorData{ 265 ModelGroups: []openrtb_ext.PriceFloorModelGroup{{ 266 Values: map[string]float64{ 267 "*|*|www.website.com": 15.01, 268 }, 269 }}, 270 }, 271 }, 272 }, 273 wantErr: true, 274 }, 275 { 276 name: "Modelweight is zero", 277 args: args{ 278 configs: config.AccountFloorFetch{ 279 Enabled: true, 280 URL: testURL, 281 Timeout: 5, 282 MaxFileSizeKB: 20, 283 MaxRules: 1, 284 MaxAge: 20, 285 Period: 10, 286 }, 287 priceFloors: &openrtb_ext.PriceFloorRules{ 288 Data: &openrtb_ext.PriceFloorData{ 289 ModelGroups: []openrtb_ext.PriceFloorModelGroup{{ 290 Values: map[string]float64{ 291 "*|*|www.website.com": 15.01, 292 }, 293 ModelWeight: &zero, 294 }}, 295 }, 296 }, 297 }, 298 wantErr: true, 299 }, 300 { 301 name: "Modelweight is 101", 302 args: args{ 303 configs: config.AccountFloorFetch{ 304 Enabled: true, 305 URL: testURL, 306 Timeout: 5, 307 MaxFileSizeKB: 20, 308 MaxRules: 1, 309 MaxAge: 20, 310 Period: 10, 311 }, 312 priceFloors: &openrtb_ext.PriceFloorRules{ 313 Data: &openrtb_ext.PriceFloorData{ 314 ModelGroups: []openrtb_ext.PriceFloorModelGroup{{ 315 Values: map[string]float64{ 316 "*|*|www.website.com": 15.01, 317 }, 318 ModelWeight: &one_o_one, 319 }}, 320 }, 321 }, 322 }, 323 wantErr: true, 324 }, 325 { 326 name: "skiprate is 101", 327 args: args{ 328 configs: config.AccountFloorFetch{ 329 Enabled: true, 330 URL: testURL, 331 Timeout: 5, 332 MaxFileSizeKB: 20, 333 MaxRules: 1, 334 MaxAge: 20, 335 Period: 10, 336 }, 337 priceFloors: &openrtb_ext.PriceFloorRules{ 338 Data: &openrtb_ext.PriceFloorData{ 339 ModelGroups: []openrtb_ext.PriceFloorModelGroup{{ 340 Values: map[string]float64{ 341 "*|*|www.website.com": 15.01, 342 }, 343 SkipRate: 101, 344 }}, 345 }, 346 }, 347 }, 348 wantErr: true, 349 }, 350 { 351 name: "Default is -1", 352 args: args{ 353 configs: config.AccountFloorFetch{ 354 Enabled: true, 355 URL: testURL, 356 Timeout: 5, 357 MaxFileSizeKB: 20, 358 MaxRules: 1, 359 MaxAge: 20, 360 Period: 10, 361 }, 362 priceFloors: &openrtb_ext.PriceFloorRules{ 363 Data: &openrtb_ext.PriceFloorData{ 364 ModelGroups: []openrtb_ext.PriceFloorModelGroup{{ 365 Values: map[string]float64{ 366 "*|*|www.website.com": 15.01, 367 }, 368 Default: -1, 369 }}, 370 }, 371 }, 372 }, 373 wantErr: true, 374 }, 375 { 376 name: "Invalid skip rate in data", 377 args: args{ 378 configs: config.AccountFloorFetch{ 379 Enabled: true, 380 URL: testURL, 381 Timeout: 5, 382 MaxFileSizeKB: 20, 383 MaxRules: 1, 384 MaxAge: 20, 385 Period: 10, 386 }, 387 priceFloors: &openrtb_ext.PriceFloorRules{ 388 Data: &openrtb_ext.PriceFloorData{ 389 SkipRate: -44, 390 ModelGroups: []openrtb_ext.PriceFloorModelGroup{{ 391 Values: map[string]float64{ 392 "*|*|www.website.com": 15.01, 393 }, 394 }}, 395 }, 396 }, 397 }, 398 wantErr: true, 399 }, 400 { 401 name: "Invalid FetchRate", 402 args: args{ 403 configs: config.AccountFloorFetch{ 404 Enabled: true, 405 URL: testURL, 406 Timeout: 5, 407 MaxFileSizeKB: 20, 408 MaxRules: 1, 409 MaxAge: 20, 410 Period: 10, 411 }, 412 priceFloors: &openrtb_ext.PriceFloorRules{ 413 Data: &openrtb_ext.PriceFloorData{ 414 SkipRate: 10, 415 ModelGroups: []openrtb_ext.PriceFloorModelGroup{{ 416 Values: map[string]float64{ 417 "*|*|www.website.com": 15.01, 418 }, 419 }}, 420 FetchRate: ptrutil.ToPtr(-11), 421 }, 422 }, 423 }, 424 wantErr: true, 425 }, 426 } 427 for _, tt := range tests { 428 t.Run(tt.name, func(t *testing.T) { 429 if err := validateRules(tt.args.configs, tt.args.priceFloors); (err != nil) != tt.wantErr { 430 t.Errorf("validatePriceFloorRules() error = %v, wantErr %v", err, tt.wantErr) 431 } 432 }) 433 } 434 } 435 436 func TestFetchFloorRulesFromURL(t *testing.T) { 437 mockHandler := func(mockResponse []byte, mockStatus int) http.Handler { 438 return http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { 439 w.Header().Add("Content-Length", "645") 440 w.Header().Add(MaxAge, "20") 441 w.WriteHeader(mockStatus) 442 w.Write(mockResponse) 443 }) 444 } 445 446 type args struct { 447 configs config.AccountFloorFetch 448 } 449 tests := []struct { 450 name string 451 args args 452 response []byte 453 responseStatus int 454 want []byte 455 want1 int 456 wantErr bool 457 }{ 458 { 459 name: "Floor data is successfully returned", 460 args: args{ 461 configs: config.AccountFloorFetch{ 462 URL: "", 463 Timeout: 60, 464 Period: 300, 465 }, 466 }, 467 response: func() []byte { 468 data := `{"data":{"currency":"USD","modelgroups":[{"modelweight":40,"modelversion":"version1","default":5,"values":{"banner|300x600|www.website.com":3,"banner|728x90|www.website.com":5,"banner|300x600|*":4,"banner|300x250|*":2,"*|*|*":16,"*|300x250|*":10,"*|300x600|*":12,"*|300x600|www.website.com":11,"banner|*|*":8,"banner|300x250|www.website.com":1,"*|728x90|www.website.com":13,"*|300x250|www.website.com":9,"*|728x90|*":14,"banner|728x90|*":6,"banner|*|www.website.com":7,"*|*|www.website.com":15},"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]},"enabled":true,"floormin":1,"enforcement":{"enforcepbs":false,"floordeals":true}}` 469 return []byte(data) 470 }(), 471 responseStatus: 200, 472 want: func() []byte { 473 data := `{"data":{"currency":"USD","modelgroups":[{"modelweight":40,"modelversion":"version1","default":5,"values":{"banner|300x600|www.website.com":3,"banner|728x90|www.website.com":5,"banner|300x600|*":4,"banner|300x250|*":2,"*|*|*":16,"*|300x250|*":10,"*|300x600|*":12,"*|300x600|www.website.com":11,"banner|*|*":8,"banner|300x250|www.website.com":1,"*|728x90|www.website.com":13,"*|300x250|www.website.com":9,"*|728x90|*":14,"banner|728x90|*":6,"banner|*|www.website.com":7,"*|*|www.website.com":15},"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]},"enabled":true,"floormin":1,"enforcement":{"enforcepbs":false,"floordeals":true}}` 474 return []byte(data) 475 }(), 476 want1: 20, 477 wantErr: false, 478 }, 479 { 480 name: "Time out occured", 481 args: args{ 482 configs: config.AccountFloorFetch{ 483 URL: "", 484 Timeout: 0, 485 Period: 300, 486 }, 487 }, 488 want1: 0, 489 responseStatus: 200, 490 wantErr: true, 491 }, 492 { 493 name: "Invalid URL", 494 args: args{ 495 configs: config.AccountFloorFetch{ 496 URL: "%%", 497 Timeout: 10, 498 Period: 300, 499 }, 500 }, 501 want1: 0, 502 responseStatus: 200, 503 wantErr: true, 504 }, 505 { 506 name: "No response from server", 507 args: args{ 508 configs: config.AccountFloorFetch{ 509 URL: "", 510 Timeout: 10, 511 Period: 300, 512 }, 513 }, 514 want1: 0, 515 responseStatus: 500, 516 wantErr: true, 517 }, 518 { 519 name: "Invalid response", 520 args: args{ 521 configs: config.AccountFloorFetch{ 522 URL: "", 523 Timeout: 10, 524 Period: 300, 525 }, 526 }, 527 want1: 0, 528 response: []byte("1"), 529 responseStatus: 200, 530 wantErr: true, 531 }, 532 } 533 for _, tt := range tests { 534 t.Run(tt.name, func(t *testing.T) { 535 mockHttpServer := httptest.NewServer(mockHandler(tt.response, tt.responseStatus)) 536 defer mockHttpServer.Close() 537 538 if tt.args.configs.URL == "" { 539 tt.args.configs.URL = mockHttpServer.URL 540 } 541 pff := PriceFloorFetcher{ 542 httpClient: mockHttpServer.Client(), 543 } 544 got, got1, err := pff.fetchFloorRulesFromURL(tt.args.configs) 545 if (err != nil) != tt.wantErr { 546 t.Errorf("fetchFloorRulesFromURL() error = %v, wantErr %v", err, tt.wantErr) 547 return 548 } 549 if !reflect.DeepEqual(got, tt.want) { 550 t.Errorf("fetchFloorRulesFromURL() got = %v, want %v", got, tt.want) 551 } 552 if got1 != tt.want1 { 553 t.Errorf("fetchFloorRulesFromURL() got1 = %v, want %v", got1, tt.want1) 554 } 555 }) 556 } 557 } 558 559 func TestFetchFloorRulesFromURLInvalidMaxAge(t *testing.T) { 560 mockHandler := func(mockResponse []byte, mockStatus int) http.Handler { 561 return http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { 562 w.Header().Add("Content-Length", "645") 563 w.Header().Add(MaxAge, "abc") 564 w.WriteHeader(mockStatus) 565 w.Write(mockResponse) 566 }) 567 } 568 569 type args struct { 570 configs config.AccountFloorFetch 571 } 572 tests := []struct { 573 name string 574 args args 575 response []byte 576 responseStatus int 577 want []byte 578 want1 int 579 wantErr bool 580 }{ 581 { 582 name: "Floor data is successfully returned", 583 args: args{ 584 configs: config.AccountFloorFetch{ 585 URL: "", 586 Timeout: 60, 587 Period: 300, 588 }, 589 }, 590 response: func() []byte { 591 data := `{"data":{"currency":"USD","modelgroups":[{"modelweight":40,"modelversion":"version1","default":5,"values":{"banner|300x600|www.website.com":3,"banner|728x90|www.website.com":5,"banner|300x600|*":4,"banner|300x250|*":2,"*|*|*":16,"*|300x250|*":10,"*|300x600|*":12,"*|300x600|www.website.com":11,"banner|*|*":8,"banner|300x250|www.website.com":1,"*|728x90|www.website.com":13,"*|300x250|www.website.com":9,"*|728x90|*":14,"banner|728x90|*":6,"banner|*|www.website.com":7,"*|*|www.website.com":15},"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]},"enabled":true,"floormin":1,"enforcement":{"enforcepbs":false,"floordeals":true}}` 592 return []byte(data) 593 }(), 594 responseStatus: 200, 595 want: func() []byte { 596 data := `{"data":{"currency":"USD","modelgroups":[{"modelweight":40,"modelversion":"version1","default":5,"values":{"banner|300x600|www.website.com":3,"banner|728x90|www.website.com":5,"banner|300x600|*":4,"banner|300x250|*":2,"*|*|*":16,"*|300x250|*":10,"*|300x600|*":12,"*|300x600|www.website.com":11,"banner|*|*":8,"banner|300x250|www.website.com":1,"*|728x90|www.website.com":13,"*|300x250|www.website.com":9,"*|728x90|*":14,"banner|728x90|*":6,"banner|*|www.website.com":7,"*|*|www.website.com":15},"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]},"enabled":true,"floormin":1,"enforcement":{"enforcepbs":false,"floordeals":true}}` 597 return []byte(data) 598 }(), 599 want1: 0, 600 wantErr: false, 601 }, 602 } 603 for _, tt := range tests { 604 t.Run(tt.name, func(t *testing.T) { 605 mockHttpServer := httptest.NewServer(mockHandler(tt.response, tt.responseStatus)) 606 defer mockHttpServer.Close() 607 608 if tt.args.configs.URL == "" { 609 tt.args.configs.URL = mockHttpServer.URL 610 } 611 612 ppf := PriceFloorFetcher{ 613 httpClient: mockHttpServer.Client(), 614 } 615 got, got1, err := ppf.fetchFloorRulesFromURL(tt.args.configs) 616 if (err != nil) != tt.wantErr { 617 t.Errorf("fetchFloorRulesFromURL() error = %v, wantErr %v", err, tt.wantErr) 618 return 619 } 620 if !reflect.DeepEqual(got, tt.want) { 621 t.Errorf("fetchFloorRulesFromURL() got = %v, want %v", got, tt.want) 622 } 623 if got1 != tt.want1 { 624 t.Errorf("fetchFloorRulesFromURL() got1 = %v, want %v", got1, tt.want1) 625 } 626 }) 627 } 628 } 629 630 func TestFetchAndValidate(t *testing.T) { 631 mockHandler := func(mockResponse []byte, mockStatus int) http.Handler { 632 return http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { 633 w.Header().Add(MaxAge, "30") 634 w.WriteHeader(mockStatus) 635 w.Write(mockResponse) 636 }) 637 } 638 639 type args struct { 640 configs config.AccountFloorFetch 641 } 642 tests := []struct { 643 name string 644 args args 645 response []byte 646 responseStatus int 647 want *openrtb_ext.PriceFloorRules 648 want1 int 649 }{ 650 { 651 name: "Recieved valid price floor rules response", 652 args: args{ 653 configs: config.AccountFloorFetch{ 654 Enabled: true, 655 Timeout: 30, 656 MaxFileSizeKB: 700, 657 MaxRules: 30, 658 MaxAge: 60, 659 Period: 40, 660 }, 661 }, 662 response: func() []byte { 663 data := `{"currency":"USD","modelgroups":[{"modelweight":40,"modelversion":"version1","default":5,"values":{"banner|300x600|www.website.com":3,"banner|728x90|www.website.com":5,"banner|300x600|*":4,"banner|300x250|*":2,"*|*|*":16,"*|300x250|*":10,"*|300x600|*":12,"*|300x600|www.website.com":11,"banner|*|*":8,"banner|300x250|www.website.com":1,"*|728x90|www.website.com":13,"*|300x250|www.website.com":9,"*|728x90|*":14,"banner|728x90|*":6,"banner|*|www.website.com":7,"*|*|www.website.com":15},"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]}` 664 return []byte(data) 665 }(), 666 responseStatus: 200, 667 want: func() *openrtb_ext.PriceFloorRules { 668 var res openrtb_ext.PriceFloorRules 669 data := `{"currency":"USD","modelgroups":[{"modelweight":40,"modelversion":"version1","default":5,"values":{"banner|300x600|www.website.com":3,"banner|728x90|www.website.com":5,"banner|300x600|*":4,"banner|300x250|*":2,"*|*|*":16,"*|300x250|*":10,"*|300x600|*":12,"*|300x600|www.website.com":11,"banner|*|*":8,"banner|300x250|www.website.com":1,"*|728x90|www.website.com":13,"*|300x250|www.website.com":9,"*|728x90|*":14,"banner|728x90|*":6,"banner|*|www.website.com":7,"*|*|www.website.com":15},"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]}` 670 _ = json.Unmarshal([]byte(data), &res.Data) 671 return &res 672 }(), 673 want1: 30, 674 }, 675 { 676 name: "No response from server", 677 args: args{ 678 configs: config.AccountFloorFetch{ 679 Enabled: true, 680 Timeout: 30, 681 MaxFileSizeKB: 700, 682 MaxRules: 30, 683 MaxAge: 60, 684 Period: 40, 685 }, 686 }, 687 response: []byte{}, 688 responseStatus: 500, 689 want: nil, 690 want1: 0, 691 }, 692 { 693 name: "File is greater than MaxFileSize", 694 args: args{ 695 configs: config.AccountFloorFetch{ 696 Enabled: true, 697 Timeout: 30, 698 MaxFileSizeKB: 1, 699 MaxRules: 30, 700 MaxAge: 60, 701 Period: 40, 702 }, 703 }, 704 response: func() []byte { 705 data := `{"currency":"USD","floorProvider":"PM","floorsSchemaVersion":2,"modelGroups":[{"modelVersion":"M_0","modelWeight":1,"schema":{"fields":["domain"]},"values":{"missyusa.com":0.85,"www.missyusa.com":0.7}},{"modelVersion":"M_1","modelWeight":1,"schema":{"fields":["domain"]},"values":{"missyusa.com":1,"www.missyusa.com":1.85}},{"modelVersion":"M_2","modelWeight":5,"schema":{"fields":["domain"]},"values":{"missyusa.com":1.6,"www.missyusa.com":0.7}},{"modelVersion":"M_3","modelWeight":2,"schema":{"fields":["domain"]},"values":{"missyusa.com":1.9,"www.missyusa.com":0.75}},{"modelVersion":"M_4","modelWeight":1,"schema":{"fields":["domain"]},"values":{"www.missyusa.com":1.35,"missyusa.com":1.75}},{"modelVersion":"M_5","modelWeight":2,"schema":{"fields":["domain"]},"values":{"missyusa.com":1.4,"www.missyusa.com":0.9}},{"modelVersion":"M_6","modelWeight":43,"schema":{"fields":["domain"]},"values":{"www.missyusa.com":2,"missyusa.com":2}},{"modelVersion":"M_7","modelWeight":1,"schema":{"fields":["domain"]},"values":{"missyusa.com":1.4,"www.missyusa.com":1.85}},{"modelVersion":"M_8","modelWeight":3,"schema":{"fields":["domain"]},"values":{"www.missyusa.com":1.7,"missyusa.com":0.1}},{"modelVersion":"M_9","modelWeight":7,"schema":{"fields":["domain"]},"values":{"missyusa.com":1.9,"www.missyusa.com":1.05}},{"modelVersion":"M_10","modelWeight":9,"schema":{"fields":["domain"]},"values":{"www.missyusa.com":2,"missyusa.com":0.1}},{"modelVersion":"M_11","modelWeight":1,"schema":{"fields":["domain"]},"values":{"missyusa.com":0.45,"www.missyusa.com":1.5}},{"modelVersion":"M_12","modelWeight":8,"schema":{"fields":["domain"]},"values":{"missyusa.com":1.2,"www.missyusa.com":1.7}},{"modelVersion":"M_13","modelWeight":8,"schema":{"fields":["domain"]},"values":{"missyusa.com":0.85,"www.missyusa.com":0.75}},{"modelVersion":"M_14","modelWeight":1,"schema":{"fields":["domain"]},"values":{"missyusa.com":1.8,"www.missyusa.com":1}},{"modelVersion":"M_15","modelWeight":1,"schema":{"fields":["domain"]},"values":{"www.missyusa.com":1.2,"missyusa.com":1.75}},{"modelVersion":"M_16","modelWeight":2,"schema":{"fields":["domain"]},"values":{"missyusa.com":1,"www.missyusa.com":0.7}},{"modelVersion":"M_17","modelWeight":1,"schema":{"fields":["domain"]},"values":{"missyusa.com":0.45,"www.missyusa.com":0.35}},{"modelVersion":"M_18","modelWeight":3,"schema":{"fields":["domain"]},"values":{"missyusa.com":1.2,"www.missyusa.com":1.05}}],"skipRate":10}` 706 return []byte(data) 707 }(), 708 responseStatus: 200, 709 want: nil, 710 want1: 0, 711 }, 712 { 713 name: "Malformed response : json unmarshalling failed", 714 args: args{ 715 configs: config.AccountFloorFetch{ 716 Enabled: true, 717 Timeout: 30, 718 MaxFileSizeKB: 800, 719 MaxRules: 30, 720 MaxAge: 60, 721 Period: 40, 722 }, 723 }, 724 response: func() []byte { 725 data := `{"data":nil?}` 726 return []byte(data) 727 }(), 728 responseStatus: 200, 729 want: nil, 730 want1: 0, 731 }, 732 { 733 name: "Validations failed for price floor rules response", 734 args: args{ 735 configs: config.AccountFloorFetch{ 736 Enabled: true, 737 Timeout: 30, 738 MaxFileSizeKB: 700, 739 MaxRules: 30, 740 MaxAge: 60, 741 Period: 40, 742 }, 743 }, 744 response: func() []byte { 745 data := `{"data":{"currency":"USD","modelgroups":[]},"enabled":true,"floormin":1,"enforcement":{"enforcepbs":false,"floordeals":true}}` 746 return []byte(data) 747 }(), 748 responseStatus: 200, 749 want: nil, 750 want1: 0, 751 }, 752 } 753 for _, tt := range tests { 754 t.Run(tt.name, func(t *testing.T) { 755 mockHttpServer := httptest.NewServer(mockHandler(tt.response, tt.responseStatus)) 756 defer mockHttpServer.Close() 757 ppf := PriceFloorFetcher{ 758 httpClient: mockHttpServer.Client(), 759 } 760 tt.args.configs.URL = mockHttpServer.URL 761 got, got1 := ppf.fetchAndValidate(tt.args.configs) 762 if !reflect.DeepEqual(got, tt.want) { 763 t.Errorf("fetchAndValidate() got = %v, want %v", got, tt.want) 764 } 765 if got1 != tt.want1 { 766 t.Errorf("fetchAndValidate() got1 = %v, want %v", got1, tt.want1) 767 } 768 }) 769 } 770 } 771 772 func mockFetcherInstance(config config.PriceFloors, httpClient *http.Client, metricEngine metrics.MetricsEngine) *PriceFloorFetcher { 773 if !config.Enabled { 774 return nil 775 } 776 777 floorFetcher := PriceFloorFetcher{ 778 pool: pond.New(config.Fetcher.Worker, config.Fetcher.Capacity, pond.PanicHandler(workerPanicHandler)), 779 fetchQueue: make(FetchQueue, 0, 100), 780 fetchInProgress: make(map[string]bool), 781 configReceiver: make(chan fetchInfo, config.Fetcher.Capacity), 782 done: make(chan struct{}), 783 cache: freecache.NewCache(config.Fetcher.CacheSize * 1024 * 1024), 784 httpClient: httpClient, 785 time: &timeutil.RealTime{}, 786 metricEngine: metricEngine, 787 maxRetries: 10, 788 } 789 790 go floorFetcher.Fetcher() 791 792 return &floorFetcher 793 } 794 795 func TestFetcherWhenRequestGetSameURLInrequest(t *testing.T) { 796 refetchCheckInterval = 1 797 response := []byte(`{"currency":"USD","modelgroups":[{"modelweight":40,"modelversion":"version1","default":5,"values":{"banner|300x600|www.website.com":3,"banner|728x90|www.website.com":5,"banner|300x600|*":4,"banner|300x250|*":2,"*|*|*":16,"*|300x250|*":10,"*|300x600|*":12,"*|300x600|www.website.com":11,"banner|*|*":8,"banner|300x250|www.website.com":1,"*|728x90|www.website.com":13,"*|300x250|www.website.com":9,"*|728x90|*":14,"banner|728x90|*":6,"banner|*|www.website.com":7,"*|*|www.website.com":15},"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]}`) 798 mockHandler := func(mockResponse []byte, mockStatus int) http.Handler { 799 return http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { 800 w.WriteHeader(mockStatus) 801 w.Write(mockResponse) 802 }) 803 } 804 805 mockHttpServer := httptest.NewServer(mockHandler(response, 200)) 806 defer mockHttpServer.Close() 807 808 floorConfig := config.PriceFloors{ 809 Enabled: true, 810 Fetcher: config.PriceFloorFetcher{ 811 CacheSize: 1, 812 Worker: 5, 813 Capacity: 10, 814 }, 815 } 816 fetcherInstance := mockFetcherInstance(floorConfig, mockHttpServer.Client(), &metricsConf.NilMetricsEngine{}) 817 defer fetcherInstance.Stop() 818 819 fetchConfig := config.AccountPriceFloors{ 820 Enabled: true, 821 UseDynamicData: true, 822 Fetcher: config.AccountFloorFetch{ 823 Enabled: true, 824 URL: mockHttpServer.URL, 825 Timeout: 100, 826 MaxFileSizeKB: 1000, 827 MaxRules: 100, 828 MaxAge: 20, 829 Period: 1, 830 }, 831 } 832 833 for i := 0; i < 50; i++ { 834 fetcherInstance.Fetch(fetchConfig) 835 } 836 837 assert.Never(t, func() bool { return len(fetcherInstance.fetchQueue) > 1 }, time.Duration(2*time.Second), 100*time.Millisecond, "Queue Got more than one entry") 838 assert.Never(t, func() bool { return len(fetcherInstance.fetchInProgress) > 1 }, time.Duration(2*time.Second), 100*time.Millisecond, "Map Got more than one entry") 839 840 } 841 842 func TestFetcherDataPresentInCache(t *testing.T) { 843 floorConfig := config.PriceFloors{ 844 Enabled: true, 845 Fetcher: config.PriceFloorFetcher{ 846 CacheSize: 1, 847 Worker: 2, 848 Capacity: 5, 849 }, 850 } 851 852 fetcherInstance := mockFetcherInstance(floorConfig, http.DefaultClient, &metricsConf.NilMetricsEngine{}) 853 defer fetcherInstance.Stop() 854 855 fetchConfig := config.AccountPriceFloors{ 856 Enabled: true, 857 UseDynamicData: true, 858 Fetcher: config.AccountFloorFetch{ 859 Enabled: true, 860 URL: "http://test.com/floor", 861 Timeout: 100, 862 MaxFileSizeKB: 1000, 863 MaxRules: 100, 864 MaxAge: 20, 865 Period: 5, 866 }, 867 } 868 var res *openrtb_ext.PriceFloorRules 869 data := `{"data":{"currency":"USD","modelgroups":[{"modelweight":40,"modelversion":"version1","default":5,"values":{"banner|300x600|www.website.com":3,"banner|728x90|www.website.com":5,"banner|300x600|*":4,"banner|300x250|*":2,"*|*|*":16,"*|300x250|*":10,"*|300x600|*":12,"*|300x600|www.website.com":11,"banner|*|*":8,"banner|300x250|www.website.com":1,"*|728x90|www.website.com":13,"*|300x250|www.website.com":9,"*|728x90|*":14,"banner|728x90|*":6,"banner|*|www.website.com":7,"*|*|www.website.com":15},"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]},"enabled":true,"floormin":1,"enforcement":{"enforcepbs":false,"floordeals":true}}` 870 _ = json.Unmarshal([]byte(data), &res) 871 fetcherInstance.SetWithExpiry("http://test.com/floor", []byte(data), fetchConfig.Fetcher.MaxAge) 872 873 val, status := fetcherInstance.Fetch(fetchConfig) 874 assert.Equal(t, res, val, "Invalid value in cache or cache is empty") 875 assert.Equal(t, "success", status, "Floor fetch should be success") 876 } 877 878 func TestFetcherDataNotPresentInCache(t *testing.T) { 879 floorConfig := config.PriceFloors{ 880 Enabled: true, 881 Fetcher: config.PriceFloorFetcher{ 882 CacheSize: 1, 883 Worker: 2, 884 Capacity: 5, 885 }, 886 } 887 888 fetcherInstance := mockFetcherInstance(floorConfig, http.DefaultClient, &metricsConf.NilMetricsEngine{}) 889 defer fetcherInstance.Stop() 890 891 fetchConfig := config.AccountPriceFloors{ 892 Enabled: true, 893 UseDynamicData: true, 894 Fetcher: config.AccountFloorFetch{ 895 Enabled: true, 896 URL: "http://test.com/floor", 897 Timeout: 100, 898 MaxFileSizeKB: 1000, 899 MaxRules: 100, 900 MaxAge: 20, 901 Period: 5, 902 }, 903 } 904 fetcherInstance.SetWithExpiry("http://test.com/floor", nil, fetchConfig.Fetcher.MaxAge) 905 906 val, status := fetcherInstance.Fetch(fetchConfig) 907 908 assert.Equal(t, (*openrtb_ext.PriceFloorRules)(nil), val, "Floor data should be nil") 909 assert.Equal(t, "error", status, "Floor fetch should be error") 910 } 911 912 func TestFetcherEntryNotPresentInCache(t *testing.T) { 913 floorConfig := config.PriceFloors{ 914 Enabled: true, 915 Fetcher: config.PriceFloorFetcher{ 916 CacheSize: 1, 917 Worker: 2, 918 Capacity: 5, 919 MaxRetries: 10, 920 }, 921 } 922 923 fetcherInstance := NewPriceFloorFetcher(floorConfig, http.DefaultClient, &metricsConf.NilMetricsEngine{}) 924 defer fetcherInstance.Stop() 925 926 fetchConfig := config.AccountPriceFloors{ 927 Enabled: true, 928 UseDynamicData: true, 929 Fetcher: config.AccountFloorFetch{ 930 Enabled: true, 931 URL: "http://test.com/floor", 932 Timeout: 100, 933 MaxFileSizeKB: 1000, 934 MaxRules: 100, 935 MaxAge: 20, 936 Period: 5, 937 }, 938 } 939 940 val, status := fetcherInstance.Fetch(fetchConfig) 941 942 assert.Equal(t, (*openrtb_ext.PriceFloorRules)(nil), val, "Floor data should be nil") 943 assert.Equal(t, openrtb_ext.FetchInprogress, status, "Floor fetch should be error") 944 } 945 946 func TestFetcherDynamicFetchDisable(t *testing.T) { 947 floorConfig := config.PriceFloors{ 948 Enabled: true, 949 Fetcher: config.PriceFloorFetcher{ 950 CacheSize: 1, 951 Worker: 2, 952 Capacity: 5, 953 MaxRetries: 5, 954 }, 955 } 956 957 fetcherInstance := NewPriceFloorFetcher(floorConfig, http.DefaultClient, &metricsConf.NilMetricsEngine{}) 958 defer fetcherInstance.Stop() 959 960 fetchConfig := config.AccountPriceFloors{ 961 Enabled: true, 962 UseDynamicData: false, 963 Fetcher: config.AccountFloorFetch{ 964 Enabled: true, 965 URL: "http://test.com/floor", 966 Timeout: 100, 967 MaxFileSizeKB: 1000, 968 MaxRules: 100, 969 MaxAge: 20, 970 Period: 5, 971 }, 972 } 973 974 val, status := fetcherInstance.Fetch(fetchConfig) 975 976 assert.Equal(t, (*openrtb_ext.PriceFloorRules)(nil), val, "Floor data should be nil") 977 assert.Equal(t, openrtb_ext.FetchNone, status, "Floor fetch should be error") 978 } 979 980 func TestPriceFloorFetcherWorker(t *testing.T) { 981 var floorData openrtb_ext.PriceFloorData 982 response := []byte(`{"currency":"USD","modelgroups":[{"modelweight":40,"modelversion":"version1","default":5,"values":{"banner|300x600|www.website.com":3,"banner|728x90|www.website.com":5,"banner|300x600|*":4,"banner|300x250|*":2,"*|*|*":16,"*|300x250|*":10,"*|300x600|*":12,"*|300x600|www.website.com":11,"banner|*|*":8,"banner|300x250|www.website.com":1,"*|728x90|www.website.com":13,"*|300x250|www.website.com":9,"*|728x90|*":14,"banner|728x90|*":6,"banner|*|www.website.com":7,"*|*|www.website.com":15},"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]}`) 983 _ = json.Unmarshal(response, &floorData) 984 floorResp := &openrtb_ext.PriceFloorRules{ 985 Data: &floorData, 986 } 987 988 mockHandler := func(mockResponse []byte, mockStatus int) http.Handler { 989 return http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { 990 w.Header().Add(MaxAge, "5") 991 w.WriteHeader(mockStatus) 992 w.Write(mockResponse) 993 }) 994 } 995 996 mockHttpServer := httptest.NewServer(mockHandler(response, 200)) 997 defer mockHttpServer.Close() 998 999 fetcherInstance := PriceFloorFetcher{ 1000 pool: nil, 1001 fetchQueue: nil, 1002 fetchInProgress: nil, 1003 configReceiver: make(chan fetchInfo, 1), 1004 done: nil, 1005 cache: freecache.NewCache(1 * 1024 * 1024), 1006 httpClient: mockHttpServer.Client(), 1007 time: &timeutil.RealTime{}, 1008 metricEngine: &metricsConf.NilMetricsEngine{}, 1009 maxRetries: 10, 1010 } 1011 defer close(fetcherInstance.configReceiver) 1012 1013 fetchConfig := fetchInfo{ 1014 AccountFloorFetch: config.AccountFloorFetch{ 1015 Enabled: true, 1016 URL: mockHttpServer.URL, 1017 Timeout: 100, 1018 MaxFileSizeKB: 1000, 1019 MaxRules: 100, 1020 MaxAge: 20, 1021 Period: 1, 1022 }, 1023 } 1024 1025 fetcherInstance.worker(fetchConfig) 1026 dataInCache, _ := fetcherInstance.Get(mockHttpServer.URL) 1027 var gotFloorData *openrtb_ext.PriceFloorRules 1028 json.Unmarshal(dataInCache, &gotFloorData) 1029 assert.Equal(t, floorResp, gotFloorData, "Data should be stored in cache") 1030 1031 info := <-fetcherInstance.configReceiver 1032 assert.Equal(t, true, info.refetchRequest, "Recieved request is not refetch request") 1033 assert.Equal(t, mockHttpServer.URL, info.AccountFloorFetch.URL, "Recieved request with different url") 1034 1035 } 1036 1037 func TestPriceFloorFetcherWorkerRetry(t *testing.T) { 1038 mockHandler := func(mockResponse []byte, mockStatus int) http.Handler { 1039 return http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { 1040 w.WriteHeader(mockStatus) 1041 w.Write(mockResponse) 1042 }) 1043 } 1044 1045 mockHttpServer := httptest.NewServer(mockHandler(nil, 500)) 1046 defer mockHttpServer.Close() 1047 1048 fetcherInstance := PriceFloorFetcher{ 1049 pool: nil, 1050 fetchQueue: nil, 1051 fetchInProgress: nil, 1052 configReceiver: make(chan fetchInfo, 1), 1053 done: nil, 1054 cache: nil, 1055 httpClient: mockHttpServer.Client(), 1056 time: &timeutil.RealTime{}, 1057 metricEngine: &metricsConf.NilMetricsEngine{}, 1058 maxRetries: 5, 1059 } 1060 defer close(fetcherInstance.configReceiver) 1061 1062 fetchConfig := fetchInfo{ 1063 AccountFloorFetch: config.AccountFloorFetch{ 1064 Enabled: true, 1065 URL: mockHttpServer.URL, 1066 Timeout: 100, 1067 MaxFileSizeKB: 1000, 1068 MaxRules: 100, 1069 MaxAge: 20, 1070 Period: 1, 1071 }, 1072 } 1073 1074 fetcherInstance.worker(fetchConfig) 1075 1076 info := <-fetcherInstance.configReceiver 1077 assert.Equal(t, 1, info.retryCount, "Retry Count is not 1") 1078 } 1079 1080 func TestPriceFloorFetcherWorkerDefaultCacheExpiry(t *testing.T) { 1081 var floorData openrtb_ext.PriceFloorData 1082 response := []byte(`{"currency":"USD","modelgroups":[{"modelweight":40,"modelversion":"version1","default":5,"values":{"banner|300x600|www.website.com":3,"banner|728x90|www.website.com":5,"banner|300x600|*":4,"banner|300x250|*":2,"*|*|*":16,"*|300x250|*":10,"*|300x600|*":12,"*|300x600|www.website.com":11,"banner|*|*":8,"banner|300x250|www.website.com":1,"*|728x90|www.website.com":13,"*|300x250|www.website.com":9,"*|728x90|*":14,"banner|728x90|*":6,"banner|*|www.website.com":7,"*|*|www.website.com":15},"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]}`) 1083 _ = json.Unmarshal(response, &floorData) 1084 floorResp := &openrtb_ext.PriceFloorRules{ 1085 Data: &floorData, 1086 } 1087 1088 mockHandler := func(mockResponse []byte, mockStatus int) http.Handler { 1089 return http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { 1090 w.WriteHeader(mockStatus) 1091 w.Write(mockResponse) 1092 }) 1093 } 1094 1095 mockHttpServer := httptest.NewServer(mockHandler(response, 200)) 1096 defer mockHttpServer.Close() 1097 1098 fetcherInstance := &PriceFloorFetcher{ 1099 pool: nil, 1100 fetchQueue: nil, 1101 fetchInProgress: nil, 1102 configReceiver: make(chan fetchInfo, 1), 1103 done: nil, 1104 cache: freecache.NewCache(1 * 1024 * 1024), 1105 httpClient: mockHttpServer.Client(), 1106 time: &timeutil.RealTime{}, 1107 metricEngine: &metricsConf.NilMetricsEngine{}, 1108 maxRetries: 5, 1109 } 1110 1111 fetchConfig := fetchInfo{ 1112 AccountFloorFetch: config.AccountFloorFetch{ 1113 Enabled: true, 1114 URL: mockHttpServer.URL, 1115 Timeout: 100, 1116 MaxFileSizeKB: 1000, 1117 MaxRules: 100, 1118 MaxAge: 20, 1119 Period: 1, 1120 }, 1121 } 1122 1123 fetcherInstance.worker(fetchConfig) 1124 dataInCache, _ := fetcherInstance.Get(mockHttpServer.URL) 1125 var gotFloorData *openrtb_ext.PriceFloorRules 1126 json.Unmarshal(dataInCache, &gotFloorData) 1127 assert.Equal(t, floorResp, gotFloorData, "Data should be stored in cache") 1128 1129 info := <-fetcherInstance.configReceiver 1130 defer close(fetcherInstance.configReceiver) 1131 assert.Equal(t, true, info.refetchRequest, "Recieved request is not refetch request") 1132 assert.Equal(t, mockHttpServer.URL, info.AccountFloorFetch.URL, "Recieved request with different url") 1133 1134 } 1135 1136 func TestPriceFloorFetcherSubmit(t *testing.T) { 1137 response := []byte(`{"currency":"USD","modelgroups":[{"modelweight":40,"modelversion":"version1","default":5,"values":{"banner|300x600|www.website.com":3,"banner|728x90|www.website.com":5,"banner|300x600|*":4,"banner|300x250|*":2,"*|*|*":16,"*|300x250|*":10,"*|300x600|*":12,"*|300x600|www.website.com":11,"banner|*|*":8,"banner|300x250|www.website.com":1,"*|728x90|www.website.com":13,"*|300x250|www.website.com":9,"*|728x90|*":14,"banner|728x90|*":6,"banner|*|www.website.com":7,"*|*|www.website.com":15},"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]}`) 1138 mockHandler := func(mockResponse []byte, mockStatus int) http.Handler { 1139 return http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { 1140 w.WriteHeader(mockStatus) 1141 w.Write(mockResponse) 1142 }) 1143 } 1144 1145 mockHttpServer := httptest.NewServer(mockHandler(response, 200)) 1146 defer mockHttpServer.Close() 1147 1148 fetcherInstance := &PriceFloorFetcher{ 1149 pool: pond.New(1, 1), 1150 fetchQueue: make(FetchQueue, 0), 1151 fetchInProgress: nil, 1152 configReceiver: make(chan fetchInfo, 1), 1153 done: make(chan struct{}), 1154 cache: freecache.NewCache(1 * 1024 * 1024), 1155 httpClient: mockHttpServer.Client(), 1156 time: &timeutil.RealTime{}, 1157 metricEngine: &metricsConf.NilMetricsEngine{}, 1158 maxRetries: 5, 1159 } 1160 defer fetcherInstance.Stop() 1161 1162 fetchInfo := fetchInfo{ 1163 refetchRequest: false, 1164 fetchTime: time.Now().Unix(), 1165 AccountFloorFetch: config.AccountFloorFetch{ 1166 Enabled: true, 1167 URL: mockHttpServer.URL, 1168 Timeout: 100, 1169 MaxFileSizeKB: 1000, 1170 MaxRules: 100, 1171 MaxAge: 2, 1172 Period: 1, 1173 }, 1174 } 1175 1176 fetcherInstance.submit(&fetchInfo) 1177 1178 info := <-fetcherInstance.configReceiver 1179 assert.Equal(t, true, info.refetchRequest, "Recieved request is not refetch request") 1180 assert.Equal(t, mockHttpServer.URL, info.AccountFloorFetch.URL, "Recieved request with different url") 1181 1182 } 1183 1184 type testPool struct{} 1185 1186 func (t *testPool) TrySubmit(task func()) bool { 1187 return false 1188 } 1189 1190 func (t *testPool) Stop() {} 1191 1192 func TestPriceFloorFetcherSubmitFailed(t *testing.T) { 1193 fetcherInstance := &PriceFloorFetcher{ 1194 pool: &testPool{}, 1195 fetchQueue: make(FetchQueue, 0), 1196 fetchInProgress: nil, 1197 configReceiver: nil, 1198 done: nil, 1199 cache: nil, 1200 } 1201 defer fetcherInstance.pool.Stop() 1202 1203 fetchInfo := fetchInfo{ 1204 refetchRequest: false, 1205 fetchTime: time.Now().Unix(), 1206 AccountFloorFetch: config.AccountFloorFetch{ 1207 Enabled: true, 1208 URL: "http://test.com", 1209 Timeout: 100, 1210 MaxFileSizeKB: 1000, 1211 MaxRules: 100, 1212 MaxAge: 2, 1213 Period: 1, 1214 }, 1215 } 1216 1217 fetcherInstance.submit(&fetchInfo) 1218 assert.Equal(t, 1, len(fetcherInstance.fetchQueue), "Unable to submit the task") 1219 } 1220 1221 func getRandomNumber() int { 1222 //nolint: staticcheck // SA1019: rand.Seed has been deprecated since Go 1.20 and an alternative has been available since Go 1.0: As of Go 1.20 there is no reason to call Seed with a random value. 1223 rand.Seed(time.Now().UnixNano()) 1224 min := 1 1225 max := 10 1226 return rand.Intn(max-min+1) + min 1227 } 1228 1229 func TestFetcherWhenRequestGetDifferentURLInrequest(t *testing.T) { 1230 refetchCheckInterval = 1 1231 response := []byte(`{"currency":"USD","modelgroups":[{"modelweight":40,"modelversion":"version1","default":5,"values":{"banner|300x600|www.website.com":3,"banner|728x90|www.website.com":5,"banner|300x600|*":4,"banner|300x250|*":2,"*|*|*":16,"*|300x250|*":10,"*|300x600|*":12,"*|300x600|www.website.com":11,"banner|*|*":8,"banner|300x250|www.website.com":1,"*|728x90|www.website.com":13,"*|300x250|www.website.com":9,"*|728x90|*":14,"banner|728x90|*":6,"banner|*|www.website.com":7,"*|*|www.website.com":15},"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]}`) 1232 mockHandler := func(mockResponse []byte, mockStatus int) http.Handler { 1233 return http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { 1234 w.WriteHeader(mockStatus) 1235 w.Write(mockResponse) 1236 }) 1237 } 1238 1239 mockHttpServer := httptest.NewServer(mockHandler(response, 200)) 1240 defer mockHttpServer.Close() 1241 1242 floorConfig := config.PriceFloors{ 1243 Enabled: true, 1244 Fetcher: config.PriceFloorFetcher{ 1245 CacheSize: 1, 1246 Worker: 5, 1247 Capacity: 10, 1248 MaxRetries: 5, 1249 }, 1250 } 1251 fetcherInstance := mockFetcherInstance(floorConfig, mockHttpServer.Client(), &metricsConf.NilMetricsEngine{}) 1252 defer fetcherInstance.Stop() 1253 1254 fetchConfig := config.AccountPriceFloors{ 1255 Enabled: true, 1256 UseDynamicData: true, 1257 Fetcher: config.AccountFloorFetch{ 1258 Enabled: true, 1259 URL: mockHttpServer.URL, 1260 Timeout: 100, 1261 MaxFileSizeKB: 1000, 1262 MaxRules: 100, 1263 MaxAge: 5, 1264 Period: 1, 1265 }, 1266 } 1267 1268 for i := 0; i < 50; i++ { 1269 fetchConfig.Fetcher.URL = fmt.Sprintf("%s?id=%d", mockHttpServer.URL, getRandomNumber()) 1270 fetcherInstance.Fetch(fetchConfig) 1271 } 1272 1273 assert.Never(t, func() bool { return len(fetcherInstance.fetchQueue) > 10 }, time.Duration(2*time.Second), 100*time.Millisecond, "Queue Got more than one entry") 1274 assert.Never(t, func() bool { return len(fetcherInstance.fetchInProgress) > 10 }, time.Duration(2*time.Second), 100*time.Millisecond, "Map Got more than one entry") 1275 } 1276 1277 func TestFetchWhenPriceFloorsDisabled(t *testing.T) { 1278 floorConfig := config.PriceFloors{ 1279 Enabled: false, 1280 Fetcher: config.PriceFloorFetcher{ 1281 CacheSize: 1, 1282 Worker: 5, 1283 Capacity: 10, 1284 }, 1285 } 1286 fetcherInstance := NewPriceFloorFetcher(floorConfig, http.DefaultClient, &metricsConf.NilMetricsEngine{}) 1287 defer fetcherInstance.Stop() 1288 1289 fetchConfig := config.AccountPriceFloors{ 1290 Enabled: true, 1291 UseDynamicData: true, 1292 Fetcher: config.AccountFloorFetch{ 1293 Enabled: true, 1294 URL: "http://test.com/floors", 1295 Timeout: 100, 1296 MaxFileSizeKB: 1000, 1297 MaxRules: 100, 1298 MaxAge: 5, 1299 Period: 1, 1300 }, 1301 } 1302 1303 data, status := fetcherInstance.Fetch(fetchConfig) 1304 1305 assert.Equal(t, (*openrtb_ext.PriceFloorRules)(nil), data, "floor data should be nil as fetcher instance does not created") 1306 assert.Equal(t, openrtb_ext.FetchNone, status, "floor status should be none as fetcher instance does not created") 1307 }