github.com/prebid/prebid-server/v2@v2.18.0/floors/validate_test.go (about)

     1  package floors
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"testing"
     7  
     8  	"github.com/prebid/prebid-server/v2/config"
     9  	"github.com/prebid/prebid-server/v2/openrtb_ext"
    10  	"github.com/stretchr/testify/assert"
    11  )
    12  
    13  func TestValidateFloorParams(t *testing.T) {
    14  
    15  	tt := []struct {
    16  		name     string
    17  		floorExt *openrtb_ext.PriceFloorRules
    18  		Err      error
    19  	}{
    20  		{
    21  			name: "Valid Skip Rate",
    22  			floorExt: &openrtb_ext.PriceFloorRules{Data: &openrtb_ext.PriceFloorData{
    23  				ModelGroups: []openrtb_ext.PriceFloorModelGroup{{
    24  					ModelVersion: "Version 1",
    25  					Schema:       openrtb_ext.PriceFloorSchema{Fields: []string{"mediaType", "size", "domain"}, Delimiter: "|"},
    26  					Values: map[string]float64{
    27  						"banner|300x250|www.website.com":              1.01,
    28  						"banner|300x250|*":                            2.01,
    29  						"banner|300x600|www.website.com|www.test.com": 3.01,
    30  						"banner|300x600|*":                            4.01,
    31  					}, Default: 0.01},
    32  				}}},
    33  		},
    34  		{
    35  			name:     "Invalid Skip Rate at Root level",
    36  			floorExt: &openrtb_ext.PriceFloorRules{SkipRate: -10},
    37  			Err:      errors.New("Invalid SkipRate = '-10' at ext.prebid.floors.skiprate"),
    38  		},
    39  		{
    40  			name: "Invalid Skip Rate at Data level",
    41  			floorExt: &openrtb_ext.PriceFloorRules{Data: &openrtb_ext.PriceFloorData{
    42  				SkipRate: -10,
    43  				ModelGroups: []openrtb_ext.PriceFloorModelGroup{{
    44  					ModelVersion: "Version 1",
    45  					Schema:       openrtb_ext.PriceFloorSchema{Fields: []string{"mediaType", "size", "domain"}},
    46  					Values: map[string]float64{
    47  						"*|*|www.website.com": 15.01,
    48  						"*|*|*":               16.01,
    49  					}, Default: 0.01},
    50  				}}},
    51  			Err: errors.New("Invalid SkipRate = '-10' at ext.prebid.floors.data.skiprate"),
    52  		},
    53  		{
    54  			name:     "Invalid FloorMin ",
    55  			floorExt: &openrtb_ext.PriceFloorRules{FloorMin: -10},
    56  			Err:      errors.New("Invalid FloorMin = '-10', value should be >= 0"),
    57  		},
    58  		{
    59  			name: "Invalid FloorSchemaVersion 2",
    60  			floorExt: &openrtb_ext.PriceFloorRules{Data: &openrtb_ext.PriceFloorData{
    61  				FloorsSchemaVersion: 1,
    62  				ModelGroups: []openrtb_ext.PriceFloorModelGroup{{
    63  					ModelVersion: "Version 1",
    64  					Schema:       openrtb_ext.PriceFloorSchema{Fields: []string{"mediaType", "size", "domain"}, Delimiter: "|"},
    65  					Values: map[string]float64{
    66  						"banner|300x250|www.website.com": 1.01,
    67  						"banner|300x600|*":               4.01,
    68  					}, Default: 0.01},
    69  				}}},
    70  			Err: errors.New("Invalid FloorsSchemaVersion = '1', supported version 2"),
    71  		},
    72  		{
    73  			name: "Invalid FloorSchemaVersion -2",
    74  			floorExt: &openrtb_ext.PriceFloorRules{Data: &openrtb_ext.PriceFloorData{
    75  				FloorsSchemaVersion: -2,
    76  				ModelGroups: []openrtb_ext.PriceFloorModelGroup{{
    77  					ModelVersion: "Version 1",
    78  					Schema:       openrtb_ext.PriceFloorSchema{Fields: []string{"mediaType", "size", "domain"}, Delimiter: "|"},
    79  					Values: map[string]float64{
    80  						"banner|300x250|www.website.com": 1.01,
    81  						"banner|300x600|*":               4.01,
    82  					}, Default: 0.01},
    83  				}}},
    84  			Err: errors.New("Invalid FloorsSchemaVersion = '-2', supported version 2"),
    85  		},
    86  		{
    87  			name: "Valid FloorSchemaVersion 0",
    88  			floorExt: &openrtb_ext.PriceFloorRules{Data: &openrtb_ext.PriceFloorData{
    89  				FloorsSchemaVersion: 0,
    90  				ModelGroups: []openrtb_ext.PriceFloorModelGroup{{
    91  					ModelVersion: "Version 1",
    92  					Schema:       openrtb_ext.PriceFloorSchema{Fields: []string{"mediaType", "size", "domain"}, Delimiter: "|"},
    93  					Values: map[string]float64{
    94  						"banner|300x250|www.website.com": 1.01,
    95  						"banner|300x600|*":               4.01,
    96  					}, Default: 0.01},
    97  				}}},
    98  		},
    99  		{
   100  			name: "Valid FloorSchemaVersion 2",
   101  			floorExt: &openrtb_ext.PriceFloorRules{Data: &openrtb_ext.PriceFloorData{
   102  				FloorsSchemaVersion: 2,
   103  				ModelGroups: []openrtb_ext.PriceFloorModelGroup{{
   104  					ModelVersion: "Version 1",
   105  					Schema:       openrtb_ext.PriceFloorSchema{Fields: []string{"mediaType", "size", "domain"}, Delimiter: "|"},
   106  					Values: map[string]float64{
   107  						"banner|300x250|www.website.com": 1.01,
   108  						"banner|300x600|*":               4.01,
   109  					}, Default: 0.01},
   110  				}}},
   111  		},
   112  	}
   113  	for _, tc := range tt {
   114  		t.Run(tc.name, func(t *testing.T) {
   115  			actErr := validateFloorParams(tc.floorExt)
   116  			assert.Equal(t, actErr, tc.Err, tc.name)
   117  		})
   118  	}
   119  }
   120  
   121  func TestSelectValidFloorModelGroups(t *testing.T) {
   122  
   123  	tt := []struct {
   124  		name     string
   125  		floorExt *openrtb_ext.PriceFloorRules
   126  		account  config.Account
   127  		Err      []error
   128  	}{
   129  		{
   130  			name: "Invalid Skip Rate in model Group 1",
   131  			account: config.Account{
   132  				PriceFloors: config.AccountPriceFloors{
   133  					MaxRule:       100,
   134  					MaxSchemaDims: 5,
   135  				},
   136  			},
   137  			floorExt: &openrtb_ext.PriceFloorRules{Data: &openrtb_ext.PriceFloorData{
   138  				ModelGroups: []openrtb_ext.PriceFloorModelGroup{{
   139  					ModelWeight:  getIntPtr(50),
   140  					SkipRate:     110,
   141  					ModelVersion: "Version 1",
   142  					Schema:       openrtb_ext.PriceFloorSchema{Fields: []string{"mediaType", "size", "domain"}},
   143  					Values: map[string]float64{
   144  						"banner|300x250|www.website.com": 1.01,
   145  						"banner|300x250|*":               2.01,
   146  						"banner|300x600|www.website.com": 3.01,
   147  						"*|728x90|*":                     14.01,
   148  						"*|*|www.website.com":            15.01,
   149  						"*|*|*":                          16.01,
   150  					}, Default: 0.01},
   151  					{
   152  						ModelWeight:  getIntPtr(50),
   153  						SkipRate:     20,
   154  						ModelVersion: "Version 2",
   155  						Schema:       openrtb_ext.PriceFloorSchema{Fields: []string{"mediaType", "size", "domain"}},
   156  						Values: map[string]float64{
   157  							"banner|300x250|www.website.com": 1.01,
   158  							"banner|300x250|*":               2.01,
   159  							"*|*|www.website.com":            15.01,
   160  							"*|*|*":                          16.01,
   161  						}, Default: 0.01},
   162  				}}},
   163  			Err: []error{errors.New("Invalid Floor Model = 'Version 1' due to SkipRate = '110' is out of range (1-100)")},
   164  		},
   165  		{
   166  			name: "Invalid model weight Model Group 1",
   167  			account: config.Account{
   168  				PriceFloors: config.AccountPriceFloors{
   169  					MaxRule:       100,
   170  					MaxSchemaDims: 5,
   171  				},
   172  			},
   173  			floorExt: &openrtb_ext.PriceFloorRules{Data: &openrtb_ext.PriceFloorData{
   174  				ModelGroups: []openrtb_ext.PriceFloorModelGroup{{
   175  					ModelWeight:  getIntPtr(-1),
   176  					SkipRate:     10,
   177  					ModelVersion: "Version 1",
   178  					Schema:       openrtb_ext.PriceFloorSchema{Fields: []string{"mediaType", "size", "domain"}},
   179  					Values: map[string]float64{
   180  						"banner|300x250|www.website.com": 1.01,
   181  						"banner|300x250|*":               2.01,
   182  						"*|728x90|*":                     14.01,
   183  						"*|*|www.website.com":            15.01,
   184  						"*|*|*":                          16.01,
   185  					}, Default: 0.01},
   186  					{
   187  						ModelWeight:  getIntPtr(50),
   188  						SkipRate:     20,
   189  						ModelVersion: "Version 2",
   190  						Schema:       openrtb_ext.PriceFloorSchema{Fields: []string{"mediaType", "size", "domain"}},
   191  						Values: map[string]float64{
   192  							"banner|300x250|www.website.com": 1.01,
   193  							"banner|300x250|*":               2.01,
   194  							"*|728x90|*":                     14.01,
   195  							"*|*|www.website.com":            15.01,
   196  							"*|*|*":                          16.01,
   197  						}, Default: 0.01},
   198  				}}},
   199  			Err: []error{errors.New("Invalid Floor Model = 'Version 1' due to ModelWeight = '-1' is out of range (1-100)")},
   200  		},
   201  		{
   202  			name: "Invalid Default Value",
   203  			account: config.Account{
   204  				PriceFloors: config.AccountPriceFloors{
   205  					MaxRule:       100,
   206  					MaxSchemaDims: 5,
   207  				},
   208  			},
   209  			floorExt: &openrtb_ext.PriceFloorRules{Data: &openrtb_ext.PriceFloorData{
   210  				ModelGroups: []openrtb_ext.PriceFloorModelGroup{{
   211  					ModelWeight:  getIntPtr(50),
   212  					ModelVersion: "Version 1",
   213  					Schema:       openrtb_ext.PriceFloorSchema{Fields: []string{"mediaType", "size", "domain"}},
   214  					Values: map[string]float64{
   215  						"banner|300x250|www.website.com": 1.01,
   216  						"*|728x90|*":                     14.01,
   217  						"*|*|www.website.com":            15.01,
   218  						"*|*|*":                          16.01,
   219  					}, Default: -1.0000},
   220  				}}},
   221  			Err: []error{errors.New("Invalid Floor Model = 'Version 1' due to Default = '-1' is less than 0")},
   222  		},
   223  		{
   224  			name: "Invalid Number of Schema dimensions",
   225  			account: config.Account{
   226  				PriceFloors: config.AccountPriceFloors{
   227  					MaxRule:       100,
   228  					MaxSchemaDims: 2,
   229  				},
   230  			},
   231  			floorExt: &openrtb_ext.PriceFloorRules{Data: &openrtb_ext.PriceFloorData{
   232  				ModelGroups: []openrtb_ext.PriceFloorModelGroup{{
   233  					ModelVersion: "Version 1",
   234  					Schema:       openrtb_ext.PriceFloorSchema{Fields: []string{"mediaType", "size", "domain"}},
   235  					Values: map[string]float64{
   236  						"banner|300x250|www.website.com": 1.01,
   237  						"*|728x90|*":                     14.01,
   238  						"*|*|www.website.com":            15.01,
   239  						"*|*|*":                          16.01,
   240  					}},
   241  				}}},
   242  			Err: []error{errors.New("Invalid Floor Model = 'Version 1' due to number of schema fields = '3' are greater than limit 2")},
   243  		},
   244  		{
   245  			name: "Invalid Schema field creativeType",
   246  			account: config.Account{
   247  				PriceFloors: config.AccountPriceFloors{
   248  					MaxRule:       100,
   249  					MaxSchemaDims: 3,
   250  				},
   251  			},
   252  			floorExt: &openrtb_ext.PriceFloorRules{Data: &openrtb_ext.PriceFloorData{
   253  				ModelGroups: []openrtb_ext.PriceFloorModelGroup{{
   254  					ModelVersion: "Version 1",
   255  					Schema:       openrtb_ext.PriceFloorSchema{Fields: []string{"creativeType", "size", "domain"}},
   256  					Values: map[string]float64{
   257  						"banner|300x250|www.website.com": 1.01,
   258  						"*|728x90|*":                     14.01,
   259  						"*|*|www.website.com":            15.01,
   260  						"*|*|*":                          16.01,
   261  					}},
   262  				}}},
   263  			Err: []error{errors.New("Invalid schema dimension provided = 'creativeType' in Schema Fields = '[creativeType size domain]'")},
   264  		},
   265  		{
   266  			name: "Invalid Number of rules",
   267  			account: config.Account{
   268  				PriceFloors: config.AccountPriceFloors{
   269  					MaxRule:       3,
   270  					MaxSchemaDims: 5,
   271  				},
   272  			},
   273  			floorExt: &openrtb_ext.PriceFloorRules{Data: &openrtb_ext.PriceFloorData{
   274  				ModelGroups: []openrtb_ext.PriceFloorModelGroup{{
   275  					ModelVersion: "Version 1",
   276  					Schema:       openrtb_ext.PriceFloorSchema{Fields: []string{"mediaType", "size", "domain"}},
   277  					Values: map[string]float64{
   278  						"banner|300x250|www.website.com": 1.01,
   279  						"*|728x90|*":                     14.01,
   280  						"*|*|www.website.com":            15.01,
   281  						"*|*|*":                          16.01,
   282  					}},
   283  				}}},
   284  			Err: []error{errors.New("Invalid Floor Model = 'Version 1' due to number of rules = '4' are greater than limit 3")},
   285  		},
   286  		{
   287  			name: "No Modelgroup present",
   288  			floorExt: &openrtb_ext.PriceFloorRules{Data: &openrtb_ext.PriceFloorData{
   289  				ModelGroups: []openrtb_ext.PriceFloorModelGroup{}}},
   290  			Err: []error{errors.New("No model group present in floors.data")},
   291  		},
   292  	}
   293  	for _, tc := range tt {
   294  		t.Run(tc.name, func(t *testing.T) {
   295  			_, ErrList := selectValidFloorModelGroups(tc.floorExt.Data.ModelGroups, tc.account)
   296  			assert.Equal(t, ErrList, tc.Err, tc.name)
   297  		})
   298  	}
   299  }
   300  
   301  func TestValidateFloorRulesAndLowerValidRuleKey(t *testing.T) {
   302  
   303  	tt := []struct {
   304  		name         string
   305  		floorExt     *openrtb_ext.PriceFloorRules
   306  		Err          []error
   307  		expctedFloor map[string]float64
   308  	}{
   309  		{
   310  			name: "Invalid floor rule banner|300x600|www.website.com|www.test.com",
   311  			floorExt: &openrtb_ext.PriceFloorRules{Data: &openrtb_ext.PriceFloorData{
   312  				ModelGroups: []openrtb_ext.PriceFloorModelGroup{{
   313  					ModelVersion: "Version 1",
   314  					Schema:       openrtb_ext.PriceFloorSchema{Fields: []string{"mediaType", "size", "domain"}, Delimiter: "|"},
   315  					Values: map[string]float64{
   316  						"BANNER|300x250|WWW.WEBSITE.COM":              1.01,
   317  						"banner|300x250|*":                            2.01,
   318  						"banner|300x600|www.website.com|www.test.com": 3.01,
   319  						"banner|300x600|*":                            4.01,
   320  						"banner|728x90|www.website.com":               5.01,
   321  						"banner|728x90|*":                             6.01,
   322  						"banner|*|www.website.com":                    7.01,
   323  						"banner|*|*":                                  8.01,
   324  						"*|300x250|www.website.com":                   9.01,
   325  						"*|300x250|*":                                 10.01,
   326  						"*|300x600|www.website.com":                   11.01,
   327  						"*|300x600|*":                                 12.01,
   328  						"*|728x90|www.website.com":                    13.01,
   329  						"*|728x90|*":                                  14.01,
   330  						"*|*|www.website.com":                         15.01,
   331  						"*|*|*":                                       16.01,
   332  					}, Default: 0.01},
   333  				}}},
   334  			Err: []error{errors.New("Invalid Floor Rule = 'banner|300x600|www.website.com|www.test.com' for Schema Fields = '[mediaType size domain]'")},
   335  			expctedFloor: map[string]float64{
   336  				"banner|300x250|www.website.com": 1.01,
   337  				"banner|300x250|*":               2.01,
   338  				"banner|300x600|*":               4.01,
   339  				"banner|728x90|www.website.com":  5.01,
   340  				"banner|728x90|*":                6.01,
   341  				"banner|*|www.website.com":       7.01,
   342  				"banner|*|*":                     8.01,
   343  				"*|300x250|www.website.com":      9.01,
   344  				"*|300x250|*":                    10.01,
   345  				"*|300x600|www.website.com":      11.01,
   346  				"*|300x600|*":                    12.01,
   347  				"*|728x90|www.website.com":       13.01,
   348  				"*|728x90|*":                     14.01,
   349  				"*|*|www.website.com":            15.01,
   350  				"*|*|*":                          16.01,
   351  			},
   352  		},
   353  		{
   354  			name: "Invalid floor rule banner|300x600|www.website.com|www.test.com",
   355  			floorExt: &openrtb_ext.PriceFloorRules{Data: &openrtb_ext.PriceFloorData{
   356  				ModelGroups: []openrtb_ext.PriceFloorModelGroup{{
   357  					ModelVersion: "Version 1",
   358  					Schema:       openrtb_ext.PriceFloorSchema{Fields: []string{"mediaType", "size", "domain"}, Delimiter: "|"},
   359  					Values: map[string]float64{
   360  						"banner|300x250|www.website.com": 1.01,
   361  						"banner|300x250|*":               2.01,
   362  						"banner|300x600|www.website.com": 3.01,
   363  						"banner|300x600":                 4.01,
   364  						"banner|728x90|www.website.com":  5.01,
   365  						"banner|728x90|*":                6.01,
   366  						"banner|*|www.website.com":       7.01,
   367  						"banner|*|*":                     8.01,
   368  						"*|300x250|www.website.com":      9.01,
   369  						"*|300x250|*":                    10.01,
   370  						"*|300x600|www.website.com":      11.01,
   371  						"*|300x600|*":                    12.01,
   372  						"*|728x90|www.website.com":       13.01,
   373  						"*|728x90|*":                     14.01,
   374  						"*|*|www.website.com":            15.01,
   375  						"*|*|*":                          16.01,
   376  					}, Default: 0.01},
   377  				}}},
   378  			Err: []error{errors.New("Invalid Floor Rule = 'banner|300x600' for Schema Fields = '[mediaType size domain]'")},
   379  			expctedFloor: map[string]float64{
   380  				"banner|300x250|www.website.com": 1.01,
   381  				"banner|300x250|*":               2.01,
   382  				"banner|300x600|www.website.com": 3.01,
   383  				"banner|728x90|www.website.com":  5.01,
   384  				"banner|728x90|*":                6.01,
   385  				"banner|*|www.website.com":       7.01,
   386  				"banner|*|*":                     8.01,
   387  				"*|300x250|www.website.com":      9.01,
   388  				"*|300x250|*":                    10.01,
   389  				"*|300x600|www.website.com":      11.01,
   390  				"*|300x600|*":                    12.01,
   391  				"*|728x90|www.website.com":       13.01,
   392  				"*|728x90|*":                     14.01,
   393  				"*|*|www.website.com":            15.01,
   394  				"*|*|*":                          16.01,
   395  			},
   396  		},
   397  	}
   398  
   399  	for _, tc := range tt {
   400  		t.Run(tc.name, func(t *testing.T) {
   401  			ErrList := validateFloorRulesAndLowerValidRuleKey(tc.floorExt.Data.ModelGroups[0].Schema, tc.floorExt.Data.ModelGroups[0].Schema.Delimiter, tc.floorExt.Data.ModelGroups[0].Values)
   402  			assert.Equal(t, ErrList, tc.Err, tc.name)
   403  			assert.Equal(t, tc.floorExt.Data.ModelGroups[0].Values, tc.expctedFloor, tc.name)
   404  		})
   405  	}
   406  }
   407  
   408  func TestValidateSchemaDimensions(t *testing.T) {
   409  	tests := []struct {
   410  		name   string
   411  		fields []string
   412  		err    error
   413  	}{
   414  		{
   415  			name:   "valid_fields",
   416  			fields: []string{"deviceType", "size"},
   417  		},
   418  		{
   419  			name:   "invalid_fields",
   420  			fields: []string{"deviceType", "dealType"},
   421  			err:    fmt.Errorf("Invalid schema dimension provided = 'dealType' in Schema Fields = '[deviceType dealType]'"),
   422  		},
   423  	}
   424  	for _, tt := range tests {
   425  		t.Run(tt.name, func(t *testing.T) {
   426  			err := validateSchemaDimensions(tt.fields)
   427  			assert.Equal(t, tt.err, err)
   428  		})
   429  	}
   430  }