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  }