github.com/prebid/prebid-server/v2@v2.18.0/firstpartydata/first_party_data_test.go (about)

     1  package firstpartydata
     2  
     3  import (
     4  	"encoding/json"
     5  	"os"
     6  	"path/filepath"
     7  	"testing"
     8  
     9  	"github.com/prebid/openrtb/v20/openrtb2"
    10  	"github.com/prebid/prebid-server/v2/errortypes"
    11  	"github.com/prebid/prebid-server/v2/openrtb_ext"
    12  	"github.com/prebid/prebid-server/v2/util/jsonutil"
    13  	"github.com/stretchr/testify/assert"
    14  	"github.com/stretchr/testify/require"
    15  )
    16  
    17  func TestExtractGlobalFPD(t *testing.T) {
    18  	testCases := []struct {
    19  		description string
    20  		input       openrtb_ext.RequestWrapper
    21  		expectedReq openrtb_ext.RequestWrapper
    22  		expectedFpd map[string][]byte
    23  	}{
    24  		{
    25  			description: "Site, app and user data present",
    26  			input: openrtb_ext.RequestWrapper{
    27  				BidRequest: &openrtb2.BidRequest{
    28  					ID: "bid_id",
    29  					Site: &openrtb2.Site{
    30  						ID:   "reqSiteId",
    31  						Page: "http://www.foobar.com/1234.html",
    32  						Publisher: &openrtb2.Publisher{
    33  							ID: "1",
    34  						},
    35  						Ext: json.RawMessage(`{"data":{"somesitefpd":"sitefpdDataTest"}}`),
    36  					},
    37  					User: &openrtb2.User{
    38  						ID:     "reqUserID",
    39  						Yob:    1982,
    40  						Gender: "M",
    41  						Ext:    json.RawMessage(`{"data":{"someuserfpd":"userfpdDataTest"}}`),
    42  					},
    43  					App: &openrtb2.App{
    44  						ID:  "appId",
    45  						Ext: json.RawMessage(`{"data":{"someappfpd":"appfpdDataTest"}}`),
    46  					},
    47  				},
    48  			},
    49  			expectedReq: openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{
    50  				ID: "bid_id",
    51  				Site: &openrtb2.Site{
    52  					ID:   "reqSiteId",
    53  					Page: "http://www.foobar.com/1234.html",
    54  					Publisher: &openrtb2.Publisher{
    55  						ID: "1",
    56  					},
    57  				},
    58  				User: &openrtb2.User{
    59  					ID:     "reqUserID",
    60  					Yob:    1982,
    61  					Gender: "M",
    62  				},
    63  				App: &openrtb2.App{
    64  					ID: "appId",
    65  				},
    66  			}},
    67  			expectedFpd: map[string][]byte{
    68  				"site": []byte(`{"somesitefpd":"sitefpdDataTest"}`),
    69  				"user": []byte(`{"someuserfpd":"userfpdDataTest"}`),
    70  				"app":  []byte(`{"someappfpd":"appfpdDataTest"}`),
    71  			},
    72  		},
    73  		{
    74  			description: "App FPD only present",
    75  			input: openrtb_ext.RequestWrapper{
    76  				BidRequest: &openrtb2.BidRequest{
    77  					ID: "bid_id",
    78  					Site: &openrtb2.Site{
    79  						ID:   "reqSiteId",
    80  						Page: "http://www.foobar.com/1234.html",
    81  						Publisher: &openrtb2.Publisher{
    82  							ID: "1",
    83  						},
    84  					},
    85  					App: &openrtb2.App{
    86  						ID:  "appId",
    87  						Ext: json.RawMessage(`{"data":{"someappfpd":"appfpdDataTest"}}`),
    88  					},
    89  				},
    90  			},
    91  			expectedReq: openrtb_ext.RequestWrapper{
    92  				BidRequest: &openrtb2.BidRequest{
    93  					ID: "bid_id",
    94  					Site: &openrtb2.Site{
    95  						ID:   "reqSiteId",
    96  						Page: "http://www.foobar.com/1234.html",
    97  						Publisher: &openrtb2.Publisher{
    98  							ID: "1",
    99  						},
   100  					},
   101  					App: &openrtb2.App{
   102  						ID: "appId",
   103  					},
   104  				},
   105  			},
   106  			expectedFpd: map[string][]byte{
   107  				"app":  []byte(`{"someappfpd":"appfpdDataTest"}`),
   108  				"user": nil,
   109  				"site": nil,
   110  			},
   111  		},
   112  		{
   113  			description: "User FPD only present",
   114  			input: openrtb_ext.RequestWrapper{
   115  				BidRequest: &openrtb2.BidRequest{
   116  					ID: "bid_id",
   117  					Site: &openrtb2.Site{
   118  						ID:   "reqSiteId",
   119  						Page: "http://www.foobar.com/1234.html",
   120  						Publisher: &openrtb2.Publisher{
   121  							ID: "1",
   122  						},
   123  					},
   124  					User: &openrtb2.User{
   125  						ID:     "reqUserID",
   126  						Yob:    1982,
   127  						Gender: "M",
   128  						Ext:    json.RawMessage(`{"data":{"someuserfpd":"userfpdDataTest"}}`),
   129  					},
   130  				},
   131  			},
   132  			expectedReq: openrtb_ext.RequestWrapper{
   133  				BidRequest: &openrtb2.BidRequest{
   134  					ID: "bid_id",
   135  					Site: &openrtb2.Site{
   136  						ID:   "reqSiteId",
   137  						Page: "http://www.foobar.com/1234.html",
   138  						Publisher: &openrtb2.Publisher{
   139  							ID: "1",
   140  						},
   141  					},
   142  					User: &openrtb2.User{
   143  						ID:     "reqUserID",
   144  						Yob:    1982,
   145  						Gender: "M",
   146  					},
   147  				},
   148  			},
   149  			expectedFpd: map[string][]byte{
   150  				"app":  nil,
   151  				"user": []byte(`{"someuserfpd":"userfpdDataTest"}`),
   152  				"site": nil,
   153  			},
   154  		},
   155  		{
   156  			description: "No FPD present in req",
   157  			input: openrtb_ext.RequestWrapper{
   158  				BidRequest: &openrtb2.BidRequest{
   159  					ID: "bid_id",
   160  					Site: &openrtb2.Site{
   161  						ID:   "reqSiteId",
   162  						Page: "http://www.foobar.com/1234.html",
   163  						Publisher: &openrtb2.Publisher{
   164  							ID: "1",
   165  						},
   166  					},
   167  					User: &openrtb2.User{
   168  						ID:     "reqUserID",
   169  						Yob:    1982,
   170  						Gender: "M",
   171  					},
   172  					App: &openrtb2.App{
   173  						ID: "appId",
   174  					},
   175  				},
   176  			},
   177  			expectedReq: openrtb_ext.RequestWrapper{
   178  				BidRequest: &openrtb2.BidRequest{
   179  					ID: "bid_id",
   180  					Site: &openrtb2.Site{
   181  						ID:   "reqSiteId",
   182  						Page: "http://www.foobar.com/1234.html",
   183  						Publisher: &openrtb2.Publisher{
   184  							ID: "1",
   185  						},
   186  					},
   187  					User: &openrtb2.User{
   188  						ID:     "reqUserID",
   189  						Yob:    1982,
   190  						Gender: "M",
   191  					},
   192  					App: &openrtb2.App{
   193  						ID: "appId",
   194  					},
   195  				},
   196  			},
   197  			expectedFpd: map[string][]byte{
   198  				"app":  nil,
   199  				"user": nil,
   200  				"site": nil,
   201  			},
   202  		},
   203  		{
   204  			description: "Site FPD only present",
   205  			input: openrtb_ext.RequestWrapper{
   206  				BidRequest: &openrtb2.BidRequest{
   207  					ID: "bid_id",
   208  					Site: &openrtb2.Site{
   209  						ID:   "reqSiteId",
   210  						Page: "http://www.foobar.com/1234.html",
   211  						Publisher: &openrtb2.Publisher{
   212  							ID: "1",
   213  						},
   214  						Ext: json.RawMessage(`{"data":{"someappfpd":true}}`),
   215  					},
   216  					App: &openrtb2.App{
   217  						ID: "appId",
   218  					},
   219  				},
   220  			},
   221  			expectedReq: openrtb_ext.RequestWrapper{
   222  				BidRequest: &openrtb2.BidRequest{
   223  					ID: "bid_id",
   224  					Site: &openrtb2.Site{
   225  						ID:   "reqSiteId",
   226  						Page: "http://www.foobar.com/1234.html",
   227  						Publisher: &openrtb2.Publisher{
   228  							ID: "1",
   229  						},
   230  					},
   231  					App: &openrtb2.App{
   232  						ID: "appId",
   233  					},
   234  				},
   235  			},
   236  			expectedFpd: map[string][]byte{
   237  				"app":  nil,
   238  				"user": nil,
   239  				"site": []byte(`{"someappfpd":true}`),
   240  			},
   241  		},
   242  	}
   243  	for _, test := range testCases {
   244  
   245  		inputReq := &test.input
   246  		fpd, err := ExtractGlobalFPD(inputReq)
   247  		assert.NoError(t, err, "Error should be nil")
   248  		err = inputReq.RebuildRequest()
   249  		assert.NoError(t, err, "Error should be nil")
   250  
   251  		assert.Equal(t, test.expectedReq.BidRequest, inputReq.BidRequest, "Incorrect input request after global fpd extraction")
   252  
   253  		assert.Equal(t, test.expectedFpd[userKey], fpd[userKey], "Incorrect User FPD")
   254  		assert.Equal(t, test.expectedFpd[appKey], fpd[appKey], "Incorrect App FPD")
   255  		assert.Equal(t, test.expectedFpd[siteKey], fpd[siteKey], "Incorrect Site FPDt")
   256  	}
   257  }
   258  
   259  func TestExtractOpenRtbGlobalFPD(t *testing.T) {
   260  	testCases := []struct {
   261  		description     string
   262  		input           openrtb2.BidRequest
   263  		output          openrtb2.BidRequest
   264  		expectedFpdData map[string][]openrtb2.Data
   265  	}{
   266  		{
   267  			description: "Site, app and user data present",
   268  			input: openrtb2.BidRequest{
   269  				ID: "bid_id",
   270  				Imp: []openrtb2.Imp{
   271  					{ID: "impid"},
   272  				},
   273  				Site: &openrtb2.Site{
   274  					ID: "reqSiteId",
   275  					Content: &openrtb2.Content{
   276  						Data: []openrtb2.Data{
   277  							{ID: "siteDataId1", Name: "siteDataName1"},
   278  							{ID: "siteDataId2", Name: "siteDataName2"},
   279  						},
   280  					},
   281  				},
   282  				User: &openrtb2.User{
   283  					ID:     "reqUserID",
   284  					Yob:    1982,
   285  					Gender: "M",
   286  					Data: []openrtb2.Data{
   287  						{ID: "userDataId1", Name: "userDataName1"},
   288  					},
   289  				},
   290  				App: &openrtb2.App{
   291  					ID: "appId",
   292  					Content: &openrtb2.Content{
   293  						Data: []openrtb2.Data{
   294  							{ID: "appDataId1", Name: "appDataName1"},
   295  						},
   296  					},
   297  				},
   298  			},
   299  			output: openrtb2.BidRequest{
   300  				ID: "bid_id",
   301  				Imp: []openrtb2.Imp{
   302  					{ID: "impid"},
   303  				},
   304  				Site: &openrtb2.Site{
   305  					ID:      "reqSiteId",
   306  					Content: &openrtb2.Content{},
   307  				},
   308  				User: &openrtb2.User{
   309  					ID:     "reqUserID",
   310  					Yob:    1982,
   311  					Gender: "M",
   312  				},
   313  				App: &openrtb2.App{
   314  					ID:      "appId",
   315  					Content: &openrtb2.Content{},
   316  				},
   317  			},
   318  			expectedFpdData: map[string][]openrtb2.Data{
   319  				siteContentDataKey: {{ID: "siteDataId1", Name: "siteDataName1"}, {ID: "siteDataId2", Name: "siteDataName2"}},
   320  				userDataKey:        {{ID: "userDataId1", Name: "userDataName1"}},
   321  				appContentDataKey:  {{ID: "appDataId1", Name: "appDataName1"}},
   322  			},
   323  		},
   324  		{
   325  			description: "No Site, app or user data present",
   326  			input: openrtb2.BidRequest{
   327  				ID: "bid_id",
   328  				Imp: []openrtb2.Imp{
   329  					{ID: "impid"},
   330  				},
   331  			},
   332  			output: openrtb2.BidRequest{
   333  				ID: "bid_id",
   334  				Imp: []openrtb2.Imp{
   335  					{ID: "impid"},
   336  				},
   337  			},
   338  			expectedFpdData: map[string][]openrtb2.Data{
   339  				siteContentDataKey: nil,
   340  				userDataKey:        nil,
   341  				appContentDataKey:  nil,
   342  			},
   343  		},
   344  		{
   345  			description: "Site only data present",
   346  			input: openrtb2.BidRequest{
   347  				ID: "bid_id",
   348  				Imp: []openrtb2.Imp{
   349  					{ID: "impid"},
   350  				},
   351  				Site: &openrtb2.Site{
   352  					ID:   "reqSiteId",
   353  					Page: "test/page",
   354  					Content: &openrtb2.Content{
   355  						Data: []openrtb2.Data{
   356  							{ID: "siteDataId1", Name: "siteDataName1"},
   357  						},
   358  					},
   359  				},
   360  			},
   361  			output: openrtb2.BidRequest{
   362  				ID: "bid_id",
   363  				Imp: []openrtb2.Imp{
   364  					{ID: "impid"},
   365  				},
   366  				Site: &openrtb2.Site{
   367  					ID:      "reqSiteId",
   368  					Page:    "test/page",
   369  					Content: &openrtb2.Content{},
   370  				},
   371  			},
   372  			expectedFpdData: map[string][]openrtb2.Data{
   373  				siteContentDataKey: {{ID: "siteDataId1", Name: "siteDataName1"}},
   374  				userDataKey:        nil,
   375  				appContentDataKey:  nil,
   376  			},
   377  		},
   378  		{
   379  			description: "App only data present",
   380  			input: openrtb2.BidRequest{
   381  				ID: "bid_id",
   382  				Imp: []openrtb2.Imp{
   383  					{ID: "impid"},
   384  				},
   385  				App: &openrtb2.App{
   386  					ID: "reqAppId",
   387  					Content: &openrtb2.Content{
   388  						Data: []openrtb2.Data{
   389  							{ID: "appDataId1", Name: "appDataName1"},
   390  						},
   391  					},
   392  				},
   393  			},
   394  			output: openrtb2.BidRequest{
   395  				ID: "bid_id",
   396  				Imp: []openrtb2.Imp{
   397  					{ID: "impid"},
   398  				},
   399  				App: &openrtb2.App{
   400  					ID:      "reqAppId",
   401  					Content: &openrtb2.Content{},
   402  				},
   403  			},
   404  			expectedFpdData: map[string][]openrtb2.Data{
   405  				siteContentDataKey: nil,
   406  				userDataKey:        nil,
   407  				appContentDataKey:  {{ID: "appDataId1", Name: "appDataName1"}},
   408  			},
   409  		},
   410  		{
   411  			description: "User only data present",
   412  			input: openrtb2.BidRequest{
   413  				ID: "bid_id",
   414  				Imp: []openrtb2.Imp{
   415  					{ID: "impid"},
   416  				},
   417  				Site: &openrtb2.Site{
   418  					ID: "reqSiteId",
   419  				},
   420  				App: &openrtb2.App{
   421  					ID: "reqAppId",
   422  				},
   423  				User: &openrtb2.User{
   424  					ID:     "reqUserId",
   425  					Yob:    1982,
   426  					Gender: "M",
   427  					Data: []openrtb2.Data{
   428  						{ID: "userDataId1", Name: "userDataName1"},
   429  					},
   430  				},
   431  			},
   432  			output: openrtb2.BidRequest{
   433  				ID: "bid_id",
   434  				Imp: []openrtb2.Imp{
   435  					{ID: "impid"},
   436  				},
   437  				Site: &openrtb2.Site{
   438  					ID: "reqSiteId",
   439  				},
   440  				App: &openrtb2.App{
   441  					ID: "reqAppId",
   442  				},
   443  				User: &openrtb2.User{
   444  					ID:     "reqUserId",
   445  					Yob:    1982,
   446  					Gender: "M",
   447  				},
   448  			},
   449  			expectedFpdData: map[string][]openrtb2.Data{
   450  				siteContentDataKey: nil,
   451  				userDataKey:        {{ID: "userDataId1", Name: "userDataName1"}},
   452  				appContentDataKey:  nil,
   453  			},
   454  		},
   455  	}
   456  	for _, test := range testCases {
   457  
   458  		inputReq := &test.input
   459  
   460  		res := ExtractOpenRtbGlobalFPD(inputReq)
   461  
   462  		assert.Equal(t, &test.output, inputReq, "Result request is incorrect")
   463  		assert.Equal(t, test.expectedFpdData[siteContentDataKey], res[siteContentDataKey], "siteContentData data is incorrect")
   464  		assert.Equal(t, test.expectedFpdData[userDataKey], res[userDataKey], "userData is incorrect")
   465  		assert.Equal(t, test.expectedFpdData[appContentDataKey], res[appContentDataKey], "appContentData is incorrect")
   466  
   467  	}
   468  }
   469  
   470  func TestExtractBidderConfigFPD(t *testing.T) {
   471  	testPath := "tests/extractbidderconfigfpd"
   472  
   473  	tests, err := os.ReadDir(testPath)
   474  	require.NoError(t, err, "Cannot Discover Tests")
   475  
   476  	for _, test := range tests {
   477  		t.Run(test.Name(), func(t *testing.T) {
   478  			path := filepath.Join(testPath, test.Name())
   479  
   480  			testFile, err := loadTestFile[fpdFile](path)
   481  			require.NoError(t, err, "Load Test File")
   482  
   483  			givenRequestExtPrebid := &openrtb_ext.ExtRequestPrebid{}
   484  			err = jsonutil.UnmarshalValid(testFile.InputRequestData, givenRequestExtPrebid)
   485  			require.NoError(t, err, "Cannot Load Test Conditions")
   486  
   487  			testRequest := &openrtb_ext.RequestExt{}
   488  			testRequest.SetPrebid(givenRequestExtPrebid)
   489  
   490  			// run test
   491  			results, err := ExtractBidderConfigFPD(testRequest)
   492  
   493  			// assert errors
   494  			if len(testFile.ValidationErrors) > 0 {
   495  				require.EqualError(t, err, testFile.ValidationErrors[0].Message, "Expected Error Not Received")
   496  			} else {
   497  				require.NoError(t, err, "Error Not Expected")
   498  				assert.Nil(t, testRequest.GetPrebid().BidderConfigs, "Bidder specific FPD config should be removed from request")
   499  			}
   500  
   501  			// assert fpd (with normalization for nicer looking tests)
   502  			for bidderName, expectedFPD := range testFile.BidderConfigFPD {
   503  				require.Contains(t, results, bidderName)
   504  
   505  				if expectedFPD.App != nil {
   506  					assert.JSONEq(t, string(expectedFPD.App), string(results[bidderName].App), "app is incorrect")
   507  				} else {
   508  					assert.Nil(t, results[bidderName].App, "app expected to be nil")
   509  				}
   510  
   511  				if expectedFPD.Site != nil {
   512  					assert.JSONEq(t, string(expectedFPD.Site), string(results[bidderName].Site), "site is incorrect")
   513  				} else {
   514  					assert.Nil(t, results[bidderName].Site, "site expected to be nil")
   515  				}
   516  
   517  				if expectedFPD.User != nil {
   518  					assert.JSONEq(t, string(expectedFPD.User), string(results[bidderName].User), "user is incorrect")
   519  				} else {
   520  					assert.Nil(t, results[bidderName].User, "user expected to be nil")
   521  				}
   522  			}
   523  		})
   524  	}
   525  }
   526  
   527  func TestResolveFPD(t *testing.T) {
   528  	testPath := "tests/resolvefpd"
   529  
   530  	tests, err := os.ReadDir(testPath)
   531  	require.NoError(t, err, "Cannot Discover Tests")
   532  
   533  	for _, test := range tests {
   534  		t.Run(test.Name(), func(t *testing.T) {
   535  			path := filepath.Join(testPath, test.Name())
   536  
   537  			testFile, err := loadTestFile[fpdFileForResolveFPD](path)
   538  			require.NoError(t, err, "Load Test File")
   539  
   540  			request := &openrtb2.BidRequest{}
   541  			err = jsonutil.UnmarshalValid(testFile.InputRequestData, &request)
   542  			require.NoError(t, err, "Cannot Load Request")
   543  
   544  			originalRequest := &openrtb2.BidRequest{}
   545  			err = jsonutil.UnmarshalValid(testFile.InputRequestData, &originalRequest)
   546  			require.NoError(t, err, "Cannot Load Request")
   547  
   548  			reqExtFPD := make(map[string][]byte)
   549  			reqExtFPD["site"] = testFile.GlobalFPD["site"]
   550  			reqExtFPD["app"] = testFile.GlobalFPD["app"]
   551  			reqExtFPD["user"] = testFile.GlobalFPD["user"]
   552  
   553  			reqFPD := make(map[string][]openrtb2.Data, 3)
   554  
   555  			reqFPDSiteContentData := testFile.GlobalFPD[siteContentDataKey]
   556  			if len(reqFPDSiteContentData) > 0 {
   557  				var siteConData []openrtb2.Data
   558  				err = jsonutil.UnmarshalValid(reqFPDSiteContentData, &siteConData)
   559  				if err != nil {
   560  					t.Errorf("Unable to unmarshal site.content.data:")
   561  				}
   562  				reqFPD[siteContentDataKey] = siteConData
   563  			}
   564  
   565  			reqFPDAppContentData := testFile.GlobalFPD[appContentDataKey]
   566  			if len(reqFPDAppContentData) > 0 {
   567  				var appConData []openrtb2.Data
   568  				err = jsonutil.UnmarshalValid(reqFPDAppContentData, &appConData)
   569  				if err != nil {
   570  					t.Errorf("Unable to unmarshal app.content.data: ")
   571  				}
   572  				reqFPD[appContentDataKey] = appConData
   573  			}
   574  
   575  			reqFPDUserData := testFile.GlobalFPD[userDataKey]
   576  			if len(reqFPDUserData) > 0 {
   577  				var userData []openrtb2.Data
   578  				err = jsonutil.UnmarshalValid(reqFPDUserData, &userData)
   579  				if err != nil {
   580  					t.Errorf("Unable to unmarshal app.content.data: ")
   581  				}
   582  				reqFPD[userDataKey] = userData
   583  			}
   584  
   585  			// run test
   586  			resultFPD, errL := ResolveFPD(request, testFile.BidderConfigFPD, reqExtFPD, reqFPD, testFile.BiddersWithGlobalFPD)
   587  
   588  			if len(errL) == 0 {
   589  				assert.Equal(t, request, originalRequest, "Original request should not be modified")
   590  
   591  				expectedResultKeys := []string{}
   592  				for k := range testFile.OutputRequestData {
   593  					expectedResultKeys = append(expectedResultKeys, k.String())
   594  				}
   595  				actualResultKeys := []string{}
   596  				for k := range resultFPD {
   597  					actualResultKeys = append(actualResultKeys, k.String())
   598  				}
   599  				require.ElementsMatch(t, expectedResultKeys, actualResultKeys)
   600  
   601  				for k, outputReq := range testFile.OutputRequestData {
   602  					bidderFPD := resultFPD[k]
   603  
   604  					if outputReq.Site != nil && len(outputReq.Site.Ext) > 0 {
   605  						resSiteExt := bidderFPD.Site.Ext
   606  						expectedSiteExt := outputReq.Site.Ext
   607  						bidderFPD.Site.Ext = nil
   608  						outputReq.Site.Ext = nil
   609  						assert.JSONEq(t, string(expectedSiteExt), string(resSiteExt), "site.ext is incorrect")
   610  						assert.Equal(t, outputReq.Site, bidderFPD.Site, "Site is incorrect")
   611  					}
   612  					if outputReq.App != nil && len(outputReq.App.Ext) > 0 {
   613  						resAppExt := bidderFPD.App.Ext
   614  						expectedAppExt := outputReq.App.Ext
   615  						bidderFPD.App.Ext = nil
   616  						outputReq.App.Ext = nil
   617  						assert.JSONEq(t, string(expectedAppExt), string(resAppExt), "app.ext is incorrect")
   618  						assert.Equal(t, outputReq.App, bidderFPD.App, "App is incorrect")
   619  					}
   620  					if outputReq.User != nil && len(outputReq.User.Ext) > 0 {
   621  						resUserExt := bidderFPD.User.Ext
   622  						expectedUserExt := outputReq.User.Ext
   623  						bidderFPD.User.Ext = nil
   624  						outputReq.User.Ext = nil
   625  						assert.JSONEq(t, string(expectedUserExt), string(resUserExt), "user.ext is incorrect")
   626  						assert.Equal(t, outputReq.User, bidderFPD.User, "User is incorrect")
   627  					}
   628  				}
   629  			} else {
   630  				assert.ElementsMatch(t, errL, testFile.ValidationErrors, "Incorrect first party data warning message")
   631  			}
   632  		})
   633  	}
   634  }
   635  
   636  func TestExtractFPDForBidders(t *testing.T) {
   637  	if specFiles, err := os.ReadDir("./tests/extractfpdforbidders"); err == nil {
   638  		for _, specFile := range specFiles {
   639  			path := filepath.Join("./tests/extractfpdforbidders/", specFile.Name())
   640  
   641  			testFile, err := loadTestFile[fpdFile](path)
   642  			require.NoError(t, err, "Load Test File")
   643  
   644  			var expectedRequest openrtb2.BidRequest
   645  			err = jsonutil.UnmarshalValid(testFile.OutputRequestData, &expectedRequest)
   646  			if err != nil {
   647  				t.Errorf("Unable to unmarshal input request: %s", path)
   648  			}
   649  
   650  			resultRequest := &openrtb_ext.RequestWrapper{}
   651  			resultRequest.BidRequest = &openrtb2.BidRequest{}
   652  			err = jsonutil.UnmarshalValid(testFile.InputRequestData, resultRequest.BidRequest)
   653  			assert.NoError(t, err, "Error should be nil")
   654  
   655  			resultFPD, errL := ExtractFPDForBidders(resultRequest)
   656  
   657  			if len(testFile.ValidationErrors) > 0 {
   658  				assert.Equal(t, len(testFile.ValidationErrors), len(errL), "Incorrect number of errors was returned")
   659  				assert.ElementsMatch(t, errL, testFile.ValidationErrors, "Incorrect errors were returned")
   660  				//in case or error no further assertions needed
   661  				continue
   662  			}
   663  			assert.Empty(t, errL, "Error should be empty")
   664  			assert.Equal(t, len(resultFPD), len(testFile.BiddersFPDResolved))
   665  
   666  			for bidderName, expectedValue := range testFile.BiddersFPDResolved {
   667  				actualValue := resultFPD[bidderName]
   668  				if expectedValue.Site != nil {
   669  					if len(expectedValue.Site.Ext) > 0 {
   670  						assert.JSONEq(t, string(expectedValue.Site.Ext), string(actualValue.Site.Ext), "Incorrect first party data")
   671  						expectedValue.Site.Ext = nil
   672  						actualValue.Site.Ext = nil
   673  					}
   674  					assert.Equal(t, expectedValue.Site, actualValue.Site, "Incorrect first party data")
   675  				}
   676  				if expectedValue.App != nil {
   677  					if len(expectedValue.App.Ext) > 0 {
   678  						assert.JSONEq(t, string(expectedValue.App.Ext), string(actualValue.App.Ext), "Incorrect first party data")
   679  						expectedValue.App.Ext = nil
   680  						actualValue.App.Ext = nil
   681  					}
   682  					assert.Equal(t, expectedValue.App, actualValue.App, "Incorrect first party data")
   683  				}
   684  				if expectedValue.User != nil {
   685  					if len(expectedValue.User.Ext) > 0 {
   686  						assert.JSONEq(t, string(expectedValue.User.Ext), string(actualValue.User.Ext), "Incorrect first party data")
   687  						expectedValue.User.Ext = nil
   688  						actualValue.User.Ext = nil
   689  					}
   690  					assert.Equal(t, expectedValue.User, actualValue.User, "Incorrect first party data")
   691  				}
   692  			}
   693  
   694  			if expectedRequest.Site != nil {
   695  				if len(expectedRequest.Site.Ext) > 0 {
   696  					assert.JSONEq(t, string(expectedRequest.Site.Ext), string(resultRequest.BidRequest.Site.Ext), "Incorrect site in request")
   697  					expectedRequest.Site.Ext = nil
   698  					resultRequest.BidRequest.Site.Ext = nil
   699  				}
   700  				assert.Equal(t, expectedRequest.Site, resultRequest.BidRequest.Site, "Incorrect site in request")
   701  			}
   702  			if expectedRequest.App != nil {
   703  				if len(expectedRequest.App.Ext) > 0 {
   704  					assert.JSONEq(t, string(expectedRequest.App.Ext), string(resultRequest.BidRequest.App.Ext), "Incorrect app in request")
   705  					expectedRequest.App.Ext = nil
   706  					resultRequest.BidRequest.App.Ext = nil
   707  				}
   708  				assert.Equal(t, expectedRequest.App, resultRequest.BidRequest.App, "Incorrect app in request")
   709  			}
   710  			if expectedRequest.User != nil {
   711  				if len(expectedRequest.User.Ext) > 0 {
   712  					assert.JSONEq(t, string(expectedRequest.User.Ext), string(resultRequest.BidRequest.User.Ext), "Incorrect user in request")
   713  					expectedRequest.User.Ext = nil
   714  					resultRequest.BidRequest.User.Ext = nil
   715  				}
   716  				assert.Equal(t, expectedRequest.User, resultRequest.BidRequest.User, "Incorrect user in request")
   717  			}
   718  		}
   719  	}
   720  }
   721  
   722  func TestResolveUser(t *testing.T) {
   723  	testCases := []struct {
   724  		description      string
   725  		fpdConfig        *openrtb_ext.ORTB2
   726  		bidRequestUser   *openrtb2.User
   727  		globalFPD        map[string][]byte
   728  		openRtbGlobalFPD map[string][]openrtb2.Data
   729  		expectedUser     *openrtb2.User
   730  		expectError      string
   731  	}{
   732  		{
   733  			description:  "FPD config and bid request user are not specified",
   734  			expectedUser: nil,
   735  		},
   736  		{
   737  			description:  "FPD config user only is specified",
   738  			fpdConfig:    &openrtb_ext.ORTB2{User: json.RawMessage(`{"id":"test"}`)},
   739  			expectedUser: &openrtb2.User{ID: "test"},
   740  		},
   741  		{
   742  			description:    "FPD config and bid request user are specified",
   743  			fpdConfig:      &openrtb_ext.ORTB2{User: json.RawMessage(`{"id":"test1"}`)},
   744  			bidRequestUser: &openrtb2.User{ID: "test2"},
   745  			expectedUser:   &openrtb2.User{ID: "test1"},
   746  		},
   747  		{
   748  			description:    "FPD config, bid request and global fpd user are specified, no input user ext",
   749  			fpdConfig:      &openrtb_ext.ORTB2{User: json.RawMessage(`{"id":"test1"}`)},
   750  			bidRequestUser: &openrtb2.User{ID: "test2"},
   751  			globalFPD:      map[string][]byte{userKey: []byte(`{"globalFPDUserData":"globalFPDUserDataValue"}`)},
   752  			expectedUser:   &openrtb2.User{ID: "test1", Ext: json.RawMessage(`{"data":{"globalFPDUserData":"globalFPDUserDataValue"}}`)},
   753  		},
   754  		{
   755  			description:    "FPD config, bid request user with ext and global fpd user are specified, no input user ext",
   756  			fpdConfig:      &openrtb_ext.ORTB2{User: json.RawMessage(`{"id":"test1"}`)},
   757  			bidRequestUser: &openrtb2.User{ID: "test2", Ext: json.RawMessage(`{"test":{"inputFPDUserData":"inputFPDUserDataValue"}}`)},
   758  			globalFPD:      map[string][]byte{userKey: []byte(`{"globalFPDUserData":"globalFPDUserDataValue"}`)},
   759  			expectedUser:   &openrtb2.User{ID: "test1", Ext: json.RawMessage(`{"data":{"globalFPDUserData":"globalFPDUserDataValue"},"test":{"inputFPDUserData":"inputFPDUserDataValue"}}`)},
   760  		},
   761  		{
   762  			description:    "FPD config, bid request and global fpd user are specified, with input user ext.data",
   763  			fpdConfig:      &openrtb_ext.ORTB2{User: json.RawMessage(`{"id": "test1"}`)},
   764  			bidRequestUser: &openrtb2.User{ID: "test2", Ext: json.RawMessage(`{"data":{"inputFPDUserData":"inputFPDUserDataValue"}}`)},
   765  			globalFPD:      map[string][]byte{userKey: []byte(`{"globalFPDUserData":"globalFPDUserDataValue"}`)},
   766  			expectedUser:   &openrtb2.User{ID: "test1", Ext: json.RawMessage(`{"data":{"globalFPDUserData":"globalFPDUserDataValue","inputFPDUserData":"inputFPDUserDataValue"}}`)},
   767  		},
   768  		{
   769  			description:    "FPD config, bid request and global fpd user are specified, with input user ext.data malformed",
   770  			fpdConfig:      &openrtb_ext.ORTB2{User: json.RawMessage(`{"id":"test1"}`)},
   771  			bidRequestUser: &openrtb2.User{ID: "test2", Ext: json.RawMessage(`{"data":{"inputFPDUserData":"inputFPDUserDataValue"}}`)},
   772  			globalFPD:      map[string][]byte{userKey: []byte(`malformed`)},
   773  			expectError:    "invalid first party data ext",
   774  		},
   775  		{
   776  			description:    "bid request and openrtb global fpd user are specified, no input user ext",
   777  			bidRequestUser: &openrtb2.User{ID: "test2"},
   778  			openRtbGlobalFPD: map[string][]openrtb2.Data{userDataKey: {
   779  				{ID: "DataId1", Name: "Name1"},
   780  				{ID: "DataId2", Name: "Name2"},
   781  			}},
   782  			expectedUser: &openrtb2.User{ID: "test2", Data: []openrtb2.Data{
   783  				{ID: "DataId1", Name: "Name1"},
   784  				{ID: "DataId2", Name: "Name2"},
   785  			}},
   786  		},
   787  		{
   788  			description:    "fpd config user, bid request and openrtb global fpd user are specified, no input user ext",
   789  			fpdConfig:      &openrtb_ext.ORTB2{User: json.RawMessage(`{"id":"test1"}`)},
   790  			bidRequestUser: &openrtb2.User{ID: "test2"},
   791  			openRtbGlobalFPD: map[string][]openrtb2.Data{userDataKey: {
   792  				{ID: "DataId1", Name: "Name1"},
   793  				{ID: "DataId2", Name: "Name2"},
   794  			}},
   795  			expectedUser: &openrtb2.User{ID: "test1", Data: []openrtb2.Data{
   796  				{ID: "DataId1", Name: "Name1"},
   797  				{ID: "DataId2", Name: "Name2"},
   798  			}},
   799  		},
   800  		{
   801  			description:    "fpd config user with ext, bid request and openrtb global fpd user are specified, no input user ext",
   802  			fpdConfig:      &openrtb_ext.ORTB2{User: json.RawMessage(`{"id":"test1","ext":{"test":1}}`)},
   803  			bidRequestUser: &openrtb2.User{ID: "test2"},
   804  			openRtbGlobalFPD: map[string][]openrtb2.Data{userDataKey: {
   805  				{ID: "DataId1", Name: "Name1"},
   806  				{ID: "DataId2", Name: "Name2"},
   807  			}},
   808  			expectedUser: &openrtb2.User{ID: "test1", Data: []openrtb2.Data{
   809  				{ID: "DataId1", Name: "Name1"},
   810  				{ID: "DataId2", Name: "Name2"},
   811  			},
   812  				Ext: json.RawMessage(`{"test":1}`)},
   813  		},
   814  		{
   815  			description:    "fpd config user with ext, bid requestuser with ext and openrtb global fpd user are specified, no input user ext",
   816  			fpdConfig:      &openrtb_ext.ORTB2{User: json.RawMessage(`{"id":"test1","ext":{"test":1}}`)},
   817  			bidRequestUser: &openrtb2.User{ID: "test2", Ext: json.RawMessage(`{"test":2,"key":"value"}`)},
   818  			openRtbGlobalFPD: map[string][]openrtb2.Data{userDataKey: {
   819  				{ID: "DataId1", Name: "Name1"},
   820  				{ID: "DataId2", Name: "Name2"},
   821  			}},
   822  			expectedUser: &openrtb2.User{ID: "test1", Data: []openrtb2.Data{
   823  				{ID: "DataId1", Name: "Name1"},
   824  				{ID: "DataId2", Name: "Name2"},
   825  			},
   826  				Ext: json.RawMessage(`{"key":"value","test":1}`)},
   827  		},
   828  		{
   829  			description:    "fpd config user with malformed ext, bid requestuser with ext and openrtb global fpd user are specified, no input user ext",
   830  			fpdConfig:      &openrtb_ext.ORTB2{User: json.RawMessage(`{"id": "test1","ext":{malformed}}`)},
   831  			bidRequestUser: &openrtb2.User{ID: "test2", Ext: json.RawMessage(`{"test":2,"key":"value"}`)},
   832  			openRtbGlobalFPD: map[string][]openrtb2.Data{userDataKey: {
   833  				{ID: "DataId1", Name: "Name1"},
   834  				{ID: "DataId2", Name: "Name2"},
   835  			}},
   836  			expectedUser: &openrtb2.User{ID: "test1", Data: []openrtb2.Data{
   837  				{ID: "DataId1", Name: "Name1"},
   838  				{ID: "DataId2", Name: "Name2"},
   839  			},
   840  				Ext: json.RawMessage(`{"key":"value","test":1}`),
   841  			},
   842  			expectError: "invalid first party data ext",
   843  		},
   844  	}
   845  	for _, test := range testCases {
   846  		t.Run(test.description, func(t *testing.T) {
   847  			resultUser, err := resolveUser(test.fpdConfig, test.bidRequestUser, test.globalFPD, test.openRtbGlobalFPD, "bidderA")
   848  
   849  			if len(test.expectError) > 0 {
   850  				assert.EqualError(t, err, test.expectError)
   851  			} else {
   852  				assert.NoError(t, err, "unexpected error returned")
   853  				assert.Equal(t, test.expectedUser, resultUser, "Result user is incorrect")
   854  			}
   855  		})
   856  	}
   857  }
   858  
   859  func TestResolveSite(t *testing.T) {
   860  	testCases := []struct {
   861  		description      string
   862  		fpdConfig        *openrtb_ext.ORTB2
   863  		bidRequestSite   *openrtb2.Site
   864  		globalFPD        map[string][]byte
   865  		openRtbGlobalFPD map[string][]openrtb2.Data
   866  		expectedSite     *openrtb2.Site
   867  		expectError      string
   868  	}{
   869  		{
   870  			description:  "FPD config and bid request site are not specified",
   871  			expectedSite: nil,
   872  		},
   873  		{
   874  			description: "FPD config site only is specified",
   875  			fpdConfig:   &openrtb_ext.ORTB2{Site: json.RawMessage(`{"id":"test"}`)},
   876  			expectError: "incorrect First Party Data for bidder bidderA: Site object is not defined in request, but defined in FPD config",
   877  		},
   878  		{
   879  			description:    "FPD config and bid request site are specified",
   880  			fpdConfig:      &openrtb_ext.ORTB2{Site: json.RawMessage(`{"id":"test1"}`)},
   881  			bidRequestSite: &openrtb2.Site{ID: "test2"},
   882  			expectedSite:   &openrtb2.Site{ID: "test1"},
   883  		},
   884  		{
   885  			description:    "FPD config, bid request and global fpd site are specified, no input site ext",
   886  			fpdConfig:      &openrtb_ext.ORTB2{Site: json.RawMessage(`{"id":"test1"}`)},
   887  			bidRequestSite: &openrtb2.Site{ID: "test2"},
   888  			globalFPD:      map[string][]byte{siteKey: []byte(`{"globalFPDSiteData":"globalFPDSiteDataValue"}`)},
   889  			expectedSite:   &openrtb2.Site{ID: "test1", Ext: json.RawMessage(`{"data":{"globalFPDSiteData":"globalFPDSiteDataValue"}}`)},
   890  		},
   891  		{
   892  			description:    "FPD config, bid request site with ext and global fpd site are specified, no input site ext",
   893  			fpdConfig:      &openrtb_ext.ORTB2{Site: json.RawMessage(`{"id":"test1"}`)},
   894  			bidRequestSite: &openrtb2.Site{ID: "test2", Ext: json.RawMessage(`{"test":{"inputFPDSiteData":"inputFPDSiteDataValue"}}`)},
   895  			globalFPD:      map[string][]byte{siteKey: []byte(`{"globalFPDSiteData":"globalFPDSiteDataValue"}`)},
   896  			expectedSite:   &openrtb2.Site{ID: "test1", Ext: json.RawMessage(`{"data":{"globalFPDSiteData":"globalFPDSiteDataValue"},"test":{"inputFPDSiteData":"inputFPDSiteDataValue"}}`)},
   897  		},
   898  		{
   899  			description:    "FPD config, bid request and global fpd site are specified, with input site ext.data",
   900  			fpdConfig:      &openrtb_ext.ORTB2{Site: json.RawMessage(`{"id":"test1"}`)},
   901  			bidRequestSite: &openrtb2.Site{ID: "test2", Ext: json.RawMessage(`{"data":{"inputFPDSiteData":"inputFPDSiteDataValue"}}`)},
   902  			globalFPD:      map[string][]byte{siteKey: []byte(`{"globalFPDSiteData":"globalFPDSiteDataValue"}`)},
   903  			expectedSite:   &openrtb2.Site{ID: "test1", Ext: json.RawMessage(`{"data":{"globalFPDSiteData":"globalFPDSiteDataValue","inputFPDSiteData":"inputFPDSiteDataValue"}}`)},
   904  		},
   905  		{
   906  			description:    "FPD config, bid request and global fpd site are specified, with input site ext.data malformed",
   907  			fpdConfig:      &openrtb_ext.ORTB2{Site: json.RawMessage(`{"id":"test1"}`)},
   908  			bidRequestSite: &openrtb2.Site{ID: "test2", Ext: json.RawMessage(`{"data":{"inputFPDSiteData":"inputFPDSiteDataValue"}}`)},
   909  			globalFPD:      map[string][]byte{siteKey: []byte(`malformed`)},
   910  			expectError:    "invalid first party data ext",
   911  		},
   912  		{
   913  			description:    "bid request and openrtb global fpd site are specified, no input site ext",
   914  			bidRequestSite: &openrtb2.Site{ID: "test2"},
   915  			openRtbGlobalFPD: map[string][]openrtb2.Data{siteContentDataKey: {
   916  				{ID: "DataId1", Name: "Name1"},
   917  				{ID: "DataId2", Name: "Name2"},
   918  			}},
   919  			expectedSite: &openrtb2.Site{ID: "test2", Content: &openrtb2.Content{Data: []openrtb2.Data{
   920  				{ID: "DataId1", Name: "Name1"},
   921  				{ID: "DataId2", Name: "Name2"},
   922  			}}},
   923  		},
   924  		{
   925  			description: "bid request with content and openrtb global fpd site are specified, no input site ext",
   926  			bidRequestSite: &openrtb2.Site{ID: "test2", Content: &openrtb2.Content{
   927  				ID: "InputSiteContentId",
   928  				Data: []openrtb2.Data{
   929  					{ID: "1", Name: "N1"},
   930  					{ID: "2", Name: "N2"},
   931  				},
   932  				Ext: json.RawMessage(`{"contentPresent":true}`),
   933  			}},
   934  			openRtbGlobalFPD: map[string][]openrtb2.Data{siteContentDataKey: {
   935  				{ID: "DataId1", Name: "Name1"},
   936  				{ID: "DataId2", Name: "Name2"},
   937  			}},
   938  			expectedSite: &openrtb2.Site{ID: "test2", Content: &openrtb2.Content{
   939  				ID: "InputSiteContentId",
   940  				Data: []openrtb2.Data{
   941  					{ID: "DataId1", Name: "Name1"},
   942  					{ID: "DataId2", Name: "Name2"},
   943  				},
   944  				Ext: json.RawMessage(`{"contentPresent":true}`),
   945  			}},
   946  		},
   947  		{
   948  			description:    "fpd config site, bid request and openrtb global fpd site are specified, no input site ext",
   949  			fpdConfig:      &openrtb_ext.ORTB2{Site: json.RawMessage(`{"id":"test1"}`)},
   950  			bidRequestSite: &openrtb2.Site{ID: "test2"},
   951  			openRtbGlobalFPD: map[string][]openrtb2.Data{siteContentDataKey: {
   952  				{ID: "DataId1", Name: "Name1"},
   953  				{ID: "DataId2", Name: "Name2"},
   954  			}},
   955  			expectedSite: &openrtb2.Site{ID: "test1", Content: &openrtb2.Content{Data: []openrtb2.Data{
   956  				{ID: "DataId1", Name: "Name1"},
   957  				{ID: "DataId2", Name: "Name2"},
   958  			}}},
   959  		},
   960  		{
   961  			description:    "fpd config site with ext, bid request and openrtb global fpd site are specified, no input site ext",
   962  			fpdConfig:      &openrtb_ext.ORTB2{Site: json.RawMessage(`{"id":"test1","ext":{"test":1}}`)},
   963  			bidRequestSite: &openrtb2.Site{ID: "test2"},
   964  			openRtbGlobalFPD: map[string][]openrtb2.Data{siteContentDataKey: {
   965  				{ID: "DataId1", Name: "Name1"},
   966  				{ID: "DataId2", Name: "Name2"},
   967  			}},
   968  			expectedSite: &openrtb2.Site{ID: "test1", Content: &openrtb2.Content{Data: []openrtb2.Data{
   969  				{ID: "DataId1", Name: "Name1"},
   970  				{ID: "DataId2", Name: "Name2"},
   971  			}},
   972  				Ext: json.RawMessage(`{"test":1}`)},
   973  		},
   974  		{
   975  			description:    "fpd config site with ext, bid request site with ext and openrtb global fpd site are specified, no input site ext",
   976  			fpdConfig:      &openrtb_ext.ORTB2{Site: json.RawMessage(`{"id":"test1","ext":{"test":1}}`)},
   977  			bidRequestSite: &openrtb2.Site{ID: "test2", Ext: json.RawMessage(`{"test":2,"key":"value"}`)},
   978  			openRtbGlobalFPD: map[string][]openrtb2.Data{siteContentDataKey: {
   979  				{ID: "DataId1", Name: "Name1"},
   980  				{ID: "DataId2", Name: "Name2"},
   981  			}},
   982  			expectedSite: &openrtb2.Site{ID: "test1", Content: &openrtb2.Content{Data: []openrtb2.Data{
   983  				{ID: "DataId1", Name: "Name1"},
   984  				{ID: "DataId2", Name: "Name2"},
   985  			}},
   986  				Ext: json.RawMessage(`{"key":"value","test":1}`)},
   987  		},
   988  		{
   989  			description:    "fpd config site with malformed ext, bid request site with ext and openrtb global fpd site are specified, no input site ext",
   990  			fpdConfig:      &openrtb_ext.ORTB2{Site: json.RawMessage(`{"id":"test1","ext":{malformed}}`)},
   991  			bidRequestSite: &openrtb2.Site{ID: "test2", Ext: json.RawMessage(`{"test":2,"key":"value"}`)},
   992  			openRtbGlobalFPD: map[string][]openrtb2.Data{siteContentDataKey: {
   993  				{ID: "DataId1", Name: "Name1"},
   994  				{ID: "DataId2", Name: "Name2"},
   995  			}},
   996  			expectedSite: &openrtb2.Site{ID: "test1", Content: &openrtb2.Content{Data: []openrtb2.Data{
   997  				{ID: "DataId1", Name: "Name1"},
   998  				{ID: "DataId2", Name: "Name2"},
   999  			}},
  1000  				Ext: json.RawMessage(`{"key":"value","test":1}`),
  1001  			},
  1002  			expectError: "invalid first party data ext",
  1003  		},
  1004  		{
  1005  			description:    "valid-id",
  1006  			bidRequestSite: &openrtb2.Site{ID: "1"},
  1007  			fpdConfig:      &openrtb_ext.ORTB2{Site: json.RawMessage(`{"id":"2"}`)},
  1008  			expectedSite:   &openrtb2.Site{ID: "2"},
  1009  		},
  1010  		{
  1011  			description:    "valid-page",
  1012  			bidRequestSite: &openrtb2.Site{Page: "1"},
  1013  			fpdConfig:      &openrtb_ext.ORTB2{Site: json.RawMessage(`{"page":"2"}`)},
  1014  			expectedSite:   &openrtb2.Site{Page: "2"},
  1015  		},
  1016  		{
  1017  			description:    "invalid-id",
  1018  			bidRequestSite: &openrtb2.Site{ID: "1"},
  1019  			fpdConfig:      &openrtb_ext.ORTB2{Site: json.RawMessage(`{"id":null}`)},
  1020  			expectError:    "incorrect First Party Data for bidder bidderA: Site object cannot set empty page if req.site.id is empty",
  1021  		},
  1022  		{
  1023  			description:    "invalid-page",
  1024  			bidRequestSite: &openrtb2.Site{Page: "1"},
  1025  			fpdConfig:      &openrtb_ext.ORTB2{Site: json.RawMessage(`{"page":null}`)},
  1026  			expectError:    "incorrect First Party Data for bidder bidderA: Site object cannot set empty page if req.site.id is empty",
  1027  		},
  1028  		{
  1029  			description:    "existing-err",
  1030  			bidRequestSite: &openrtb2.Site{ID: "1", Ext: []byte(`malformed`)},
  1031  			fpdConfig:      &openrtb_ext.ORTB2{Site: json.RawMessage(`{"ext":{"a":1}}`)},
  1032  			expectError:    "invalid request ext",
  1033  		},
  1034  		{
  1035  			description:    "fpd-err",
  1036  			bidRequestSite: &openrtb2.Site{ID: "1", Ext: []byte(`{"a":1}`)},
  1037  			fpdConfig:      &openrtb_ext.ORTB2{Site: json.RawMessage(`malformed`)},
  1038  			expectError:    "invalid first party data ext",
  1039  		},
  1040  	}
  1041  	for _, test := range testCases {
  1042  		t.Run(test.description, func(t *testing.T) {
  1043  			resultSite, err := resolveSite(test.fpdConfig, test.bidRequestSite, test.globalFPD, test.openRtbGlobalFPD, "bidderA")
  1044  
  1045  			if len(test.expectError) > 0 {
  1046  				assert.EqualError(t, err, test.expectError)
  1047  			} else {
  1048  				require.NoError(t, err, "unexpected error returned")
  1049  				assert.Equal(t, test.expectedSite, resultSite, "Result site is incorrect")
  1050  			}
  1051  		})
  1052  	}
  1053  }
  1054  
  1055  func TestResolveApp(t *testing.T) {
  1056  	testCases := []struct {
  1057  		description      string
  1058  		fpdConfig        *openrtb_ext.ORTB2
  1059  		bidRequestApp    *openrtb2.App
  1060  		globalFPD        map[string][]byte
  1061  		openRtbGlobalFPD map[string][]openrtb2.Data
  1062  		expectedApp      *openrtb2.App
  1063  		expectError      string
  1064  	}{
  1065  		{
  1066  			description: "FPD config and bid request app are not specified",
  1067  			expectedApp: nil,
  1068  		},
  1069  		{
  1070  			description: "FPD config app only is specified",
  1071  			fpdConfig:   &openrtb_ext.ORTB2{App: json.RawMessage(`{"id":"test"}`)},
  1072  			expectError: "incorrect First Party Data for bidder bidderA: App object is not defined in request, but defined in FPD config",
  1073  		},
  1074  		{
  1075  			description:   "FPD config and bid request app are specified",
  1076  			fpdConfig:     &openrtb_ext.ORTB2{App: json.RawMessage(`{"id":"test1"}`)},
  1077  			bidRequestApp: &openrtb2.App{ID: "test2"},
  1078  			expectedApp:   &openrtb2.App{ID: "test1"},
  1079  		},
  1080  		{
  1081  			description:   "FPD config, bid request and global fpd app are specified, no input app ext",
  1082  			fpdConfig:     &openrtb_ext.ORTB2{App: json.RawMessage(`{"id":"test1"}`)},
  1083  			bidRequestApp: &openrtb2.App{ID: "test2"},
  1084  			globalFPD:     map[string][]byte{appKey: []byte(`{"globalFPDAppData":"globalFPDAppDataValue"}`)},
  1085  			expectedApp:   &openrtb2.App{ID: "test1", Ext: json.RawMessage(`{"data":{"globalFPDAppData":"globalFPDAppDataValue"}}`)},
  1086  		},
  1087  		{
  1088  			description:   "FPD config, bid request app with ext and global fpd app are specified, no input app ext",
  1089  			fpdConfig:     &openrtb_ext.ORTB2{App: json.RawMessage(`{"id":"test1"}`)},
  1090  			bidRequestApp: &openrtb2.App{ID: "test2", Ext: json.RawMessage(`{"test":{"inputFPDAppData":"inputFPDAppDataValue"}}`)},
  1091  			globalFPD:     map[string][]byte{appKey: []byte(`{"globalFPDAppData":"globalFPDAppDataValue"}`)},
  1092  			expectedApp:   &openrtb2.App{ID: "test1", Ext: json.RawMessage(`{"data":{"globalFPDAppData":"globalFPDAppDataValue"},"test":{"inputFPDAppData":"inputFPDAppDataValue"}}`)},
  1093  		},
  1094  		{
  1095  			description:   "FPD config, bid request and global fpd app are specified, with input app ext.data",
  1096  			fpdConfig:     &openrtb_ext.ORTB2{App: json.RawMessage(`{"id":"test1"}`)},
  1097  			bidRequestApp: &openrtb2.App{ID: "test2", Ext: json.RawMessage(`{"data":{"inputFPDAppData":"inputFPDAppDataValue"}}`)},
  1098  			globalFPD:     map[string][]byte{appKey: []byte(`{"globalFPDAppData":"globalFPDAppDataValue"}`)},
  1099  			expectedApp:   &openrtb2.App{ID: "test1", Ext: json.RawMessage(`{"data":{"globalFPDAppData":"globalFPDAppDataValue","inputFPDAppData":"inputFPDAppDataValue"}}`)},
  1100  		},
  1101  		{
  1102  			description:   "FPD config, bid request and global fpd app are specified, with input app ext.data malformed",
  1103  			fpdConfig:     &openrtb_ext.ORTB2{App: json.RawMessage(`{"id":"test1"}`)},
  1104  			bidRequestApp: &openrtb2.App{ID: "test2", Ext: json.RawMessage(`{"data":{"inputFPDAppData":"inputFPDAppDataValue"}}`)},
  1105  			globalFPD:     map[string][]byte{appKey: []byte(`malformed`)},
  1106  			expectError:   "invalid first party data ext",
  1107  		},
  1108  		{
  1109  			description:   "bid request and openrtb global fpd app are specified, no input app ext",
  1110  			bidRequestApp: &openrtb2.App{ID: "test2"},
  1111  			openRtbGlobalFPD: map[string][]openrtb2.Data{appContentDataKey: {
  1112  				{ID: "DataId1", Name: "Name1"},
  1113  				{ID: "DataId2", Name: "Name2"},
  1114  			}},
  1115  			expectedApp: &openrtb2.App{ID: "test2", Content: &openrtb2.Content{Data: []openrtb2.Data{
  1116  				{ID: "DataId1", Name: "Name1"},
  1117  				{ID: "DataId2", Name: "Name2"},
  1118  			}}},
  1119  		},
  1120  		{
  1121  			description: "bid request with content and openrtb global fpd app are specified, no input app ext",
  1122  			bidRequestApp: &openrtb2.App{ID: "test2", Content: &openrtb2.Content{
  1123  				ID: "InputAppContentId",
  1124  				Data: []openrtb2.Data{
  1125  					{ID: "1", Name: "N1"},
  1126  					{ID: "2", Name: "N2"},
  1127  				},
  1128  				Ext: json.RawMessage(`{"contentPresent":true}`),
  1129  			}},
  1130  			openRtbGlobalFPD: map[string][]openrtb2.Data{appContentDataKey: {
  1131  				{ID: "DataId1", Name: "Name1"},
  1132  				{ID: "DataId2", Name: "Name2"},
  1133  			}},
  1134  			expectedApp: &openrtb2.App{ID: "test2", Content: &openrtb2.Content{
  1135  				ID: "InputAppContentId",
  1136  				Data: []openrtb2.Data{
  1137  					{ID: "DataId1", Name: "Name1"},
  1138  					{ID: "DataId2", Name: "Name2"},
  1139  				},
  1140  				Ext: json.RawMessage(`{"contentPresent":true}`),
  1141  			}},
  1142  		},
  1143  		{
  1144  			description:   "fpd config app, bid request and openrtb global fpd app are specified, no input app ext",
  1145  			fpdConfig:     &openrtb_ext.ORTB2{App: json.RawMessage(`{"id":"test1"}`)},
  1146  			bidRequestApp: &openrtb2.App{ID: "test2"},
  1147  			openRtbGlobalFPD: map[string][]openrtb2.Data{appContentDataKey: {
  1148  				{ID: "DataId1", Name: "Name1"},
  1149  				{ID: "DataId2", Name: "Name2"},
  1150  			}},
  1151  			expectedApp: &openrtb2.App{ID: "test1", Content: &openrtb2.Content{Data: []openrtb2.Data{
  1152  				{ID: "DataId1", Name: "Name1"},
  1153  				{ID: "DataId2", Name: "Name2"},
  1154  			}}},
  1155  		},
  1156  		{
  1157  			description:   "fpd config app with ext, bid request and openrtb global fpd app are specified, no input app ext",
  1158  			fpdConfig:     &openrtb_ext.ORTB2{App: json.RawMessage(`{"id":"test1","ext":{"test":1}}`)},
  1159  			bidRequestApp: &openrtb2.App{ID: "test2"},
  1160  			openRtbGlobalFPD: map[string][]openrtb2.Data{appContentDataKey: {
  1161  				{ID: "DataId1", Name: "Name1"},
  1162  				{ID: "DataId2", Name: "Name2"},
  1163  			}},
  1164  			expectedApp: &openrtb2.App{ID: "test1", Content: &openrtb2.Content{Data: []openrtb2.Data{
  1165  				{ID: "DataId1", Name: "Name1"},
  1166  				{ID: "DataId2", Name: "Name2"},
  1167  			}},
  1168  				Ext: json.RawMessage(`{"test":1}`)},
  1169  		},
  1170  		{
  1171  			description:   "fpd config app with ext, bid request app with ext and openrtb global fpd app are specified, no input app ext",
  1172  			fpdConfig:     &openrtb_ext.ORTB2{App: json.RawMessage(`{"id":"test1","ext":{"test":1}}`)},
  1173  			bidRequestApp: &openrtb2.App{ID: "test2", Ext: json.RawMessage(`{"test":2,"key":"value"}`)},
  1174  			openRtbGlobalFPD: map[string][]openrtb2.Data{appContentDataKey: {
  1175  				{ID: "DataId1", Name: "Name1"},
  1176  				{ID: "DataId2", Name: "Name2"},
  1177  			}},
  1178  			expectedApp: &openrtb2.App{ID: "test1", Content: &openrtb2.Content{Data: []openrtb2.Data{
  1179  				{ID: "DataId1", Name: "Name1"},
  1180  				{ID: "DataId2", Name: "Name2"},
  1181  			}},
  1182  				Ext: json.RawMessage(`{"key":"value","test":1}`)},
  1183  		},
  1184  		{
  1185  			description:   "fpd config app with malformed ext, bid request app with ext and openrtb global fpd app are specified, no input app ext",
  1186  			fpdConfig:     &openrtb_ext.ORTB2{App: json.RawMessage(`{"id":"test1","ext":{malformed}}`)},
  1187  			bidRequestApp: &openrtb2.App{ID: "test2", Ext: json.RawMessage(`{"test":2,"key":"value"}`)},
  1188  			openRtbGlobalFPD: map[string][]openrtb2.Data{appContentDataKey: {
  1189  				{ID: "DataId1", Name: "Name1"},
  1190  				{ID: "DataId2", Name: "Name2"},
  1191  			}},
  1192  			expectedApp: &openrtb2.App{ID: "test1", Content: &openrtb2.Content{Data: []openrtb2.Data{
  1193  				{ID: "DataId1", Name: "Name1"},
  1194  				{ID: "DataId2", Name: "Name2"},
  1195  			}},
  1196  				Ext: json.RawMessage(`{"key":"value","test":1}`),
  1197  			},
  1198  			expectError: "invalid first party data ext",
  1199  		},
  1200  	}
  1201  	for _, test := range testCases {
  1202  		t.Run(test.description, func(t *testing.T) {
  1203  			resultApp, err := resolveApp(test.fpdConfig, test.bidRequestApp, test.globalFPD, test.openRtbGlobalFPD, "bidderA")
  1204  
  1205  			if len(test.expectError) > 0 {
  1206  				assert.EqualError(t, err, test.expectError)
  1207  			} else {
  1208  				assert.NoError(t, err)
  1209  				assert.Equal(t, test.expectedApp, resultApp, "Result app is incorrect")
  1210  			}
  1211  		})
  1212  	}
  1213  }
  1214  
  1215  func TestBuildExtData(t *testing.T) {
  1216  	testCases := []struct {
  1217  		description string
  1218  		input       []byte
  1219  		expectedRes string
  1220  	}{
  1221  		{
  1222  			description: "Input object with int value",
  1223  			input:       []byte(`{"someData":123}`),
  1224  			expectedRes: `{"data":{"someData":123}}`,
  1225  		},
  1226  		{
  1227  			description: "Input object with bool value",
  1228  			input:       []byte(`{"someData":true}`),
  1229  			expectedRes: `{"data":{"someData":true}}`,
  1230  		},
  1231  		{
  1232  			description: "Input object with string value",
  1233  			input:       []byte(`{"someData":"true"}`),
  1234  			expectedRes: `{"data":{"someData":"true"}}`,
  1235  		},
  1236  		{
  1237  			description: "No input object",
  1238  			input:       []byte(`{}`),
  1239  			expectedRes: `{"data":{}}`,
  1240  		},
  1241  		{
  1242  			description: "Input object with object value",
  1243  			input:       []byte(`{"someData":{"moreFpdData":"fpddata"}}`),
  1244  			expectedRes: `{"data":{"someData":{"moreFpdData":"fpddata"}}}`,
  1245  		},
  1246  	}
  1247  
  1248  	for _, test := range testCases {
  1249  		actualRes := buildExtData(test.input)
  1250  		assert.JSONEq(t, test.expectedRes, string(actualRes), "Incorrect result data")
  1251  	}
  1252  }
  1253  
  1254  func loadTestFile[T any](filename string) (T, error) {
  1255  	var testFile T
  1256  
  1257  	b, err := os.ReadFile(filename)
  1258  	if err != nil {
  1259  		return testFile, err
  1260  	}
  1261  
  1262  	err = json.Unmarshal(b, &testFile)
  1263  	if err != nil {
  1264  		return testFile, err
  1265  	}
  1266  
  1267  	return testFile, nil
  1268  }
  1269  
  1270  type fpdFile struct {
  1271  	InputRequestData   json.RawMessage                                    `json:"inputRequestData,omitempty"`
  1272  	OutputRequestData  json.RawMessage                                    `json:"outputRequestData,omitempty"`
  1273  	BidderConfigFPD    map[openrtb_ext.BidderName]*openrtb_ext.ORTB2      `json:"bidderConfigFPD,omitempty"`
  1274  	BiddersFPDResolved map[openrtb_ext.BidderName]*ResolvedFirstPartyData `json:"biddersFPDResolved,omitempty"`
  1275  	GlobalFPD          map[string]json.RawMessage                         `json:"globalFPD,omitempty"`
  1276  	ValidationErrors   []*errortypes.BadInput                             `json:"validationErrors,omitempty"`
  1277  }
  1278  
  1279  type fpdFileForResolveFPD struct {
  1280  	InputRequestData     json.RawMessage                                `json:"inputRequestData,omitempty"`
  1281  	OutputRequestData    map[openrtb_ext.BidderName]openrtb2.BidRequest `json:"outputRequestData,omitempty"`
  1282  	BiddersWithGlobalFPD []string                                       `json:"biddersWithGlobalFPD,omitempty"`
  1283  	BidderConfigFPD      map[openrtb_ext.BidderName]*openrtb_ext.ORTB2  `json:"bidderConfigFPD,omitempty"`
  1284  	GlobalFPD            map[string]json.RawMessage                     `json:"globalFPD,omitempty"`
  1285  	ValidationErrors     []*errortypes.BadInput                         `json:"validationErrors,omitempty"`
  1286  }