github.com/prebid/prebid-server/v2@v2.18.0/endpoints/info/bidders_detail_test.go (about)

     1  package info
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io"
     7  	"net/http"
     8  	"net/http/httptest"
     9  	"testing"
    10  
    11  	"github.com/julienschmidt/httprouter"
    12  	"github.com/prebid/prebid-server/v2/config"
    13  	"github.com/prebid/prebid-server/v2/openrtb_ext"
    14  	"github.com/stretchr/testify/assert"
    15  )
    16  
    17  func TestPrepareBiddersDetailResponse(t *testing.T) {
    18  	bidderAInfo := config.BidderInfo{Endpoint: "https://secureEndpoint.com", Disabled: false, Maintainer: &config.MaintainerInfo{Email: "bidderA"}}
    19  	bidderAResponse := []byte(`{"status":"ACTIVE","usesHttps":true,"maintainer":{"email":"bidderA"}}`)
    20  
    21  	bidderBInfo := config.BidderInfo{Endpoint: "http://unsecureEndpoint.com", Disabled: false, Maintainer: &config.MaintainerInfo{Email: "bidderB"}}
    22  	bidderBResponse := []byte(`{"status":"ACTIVE","usesHttps":false,"maintainer":{"email":"bidderB"}}`)
    23  
    24  	allResponseBidderA := bytes.Buffer{}
    25  	allResponseBidderA.WriteString(`{"a":`)
    26  	allResponseBidderA.Write(bidderAResponse)
    27  	allResponseBidderA.WriteString(`}`)
    28  
    29  	allResponseBidderAB := bytes.Buffer{}
    30  	allResponseBidderAB.WriteString(`{"a":`)
    31  	allResponseBidderAB.Write(bidderAResponse)
    32  	allResponseBidderAB.WriteString(`,"b":`)
    33  	allResponseBidderAB.Write(bidderBResponse)
    34  	allResponseBidderAB.WriteString(`}`)
    35  
    36  	var testCases = []struct {
    37  		description       string
    38  		givenBidders      config.BidderInfos
    39  		givenAliases      map[string]string
    40  		expectedResponses map[string][]byte
    41  		expectedError     string
    42  	}{
    43  		{
    44  			description:       "None",
    45  			givenBidders:      config.BidderInfos{},
    46  			givenAliases:      map[string]string{},
    47  			expectedResponses: map[string][]byte{"all": []byte(`{}`)},
    48  		},
    49  		{
    50  			description:       "One",
    51  			givenBidders:      config.BidderInfos{"a": bidderAInfo},
    52  			givenAliases:      map[string]string{},
    53  			expectedResponses: map[string][]byte{"a": bidderAResponse, "all": allResponseBidderA.Bytes()},
    54  		},
    55  		{
    56  			description:       "Many",
    57  			givenBidders:      config.BidderInfos{"a": bidderAInfo, "b": bidderBInfo},
    58  			givenAliases:      map[string]string{},
    59  			expectedResponses: map[string][]byte{"a": bidderAResponse, "b": bidderBResponse, "all": allResponseBidderAB.Bytes()},
    60  		},
    61  		{
    62  			description:   "Error - Map Details", // Returns error due to invalid alias.
    63  			givenBidders:  config.BidderInfos{"a": bidderAInfo},
    64  			givenAliases:  map[string]string{"zAlias": "z"},
    65  			expectedError: "base adapter z for alias zAlias not found",
    66  		},
    67  	}
    68  
    69  	for _, test := range testCases {
    70  		responses, err := prepareBiddersDetailResponse(test.givenBidders, test.givenAliases)
    71  
    72  		if test.expectedError == "" {
    73  			assert.Equal(t, test.expectedResponses, responses, test.description+":responses")
    74  			assert.NoError(t, err, test.expectedError, test.description+":err")
    75  		} else {
    76  			assert.Empty(t, responses, test.description+":responses")
    77  			assert.EqualError(t, err, test.expectedError, test.description+":err")
    78  		}
    79  	}
    80  }
    81  
    82  func TestMapDetails(t *testing.T) {
    83  	trueValue := true
    84  	falseValue := false
    85  
    86  	bidderAInfo := config.BidderInfo{Endpoint: "https://secureEndpoint.com", Disabled: false, Maintainer: &config.MaintainerInfo{Email: "bidderA"}}
    87  	bidderADetail := bidderDetail{Status: "ACTIVE", UsesHTTPS: &trueValue, Maintainer: &maintainer{Email: "bidderA"}}
    88  	aliasADetail := bidderDetail{Status: "ACTIVE", UsesHTTPS: &trueValue, Maintainer: &maintainer{Email: "bidderA"}, AliasOf: "a"}
    89  
    90  	bidderBInfo := config.BidderInfo{Endpoint: "http://unsecureEndpoint.com", Disabled: false, Maintainer: &config.MaintainerInfo{Email: "bidderB"}}
    91  	bidderBDetail := bidderDetail{Status: "ACTIVE", UsesHTTPS: &falseValue, Maintainer: &maintainer{Email: "bidderB"}}
    92  	aliasBDetail := bidderDetail{Status: "ACTIVE", UsesHTTPS: &falseValue, Maintainer: &maintainer{Email: "bidderB"}, AliasOf: "b"}
    93  
    94  	var testCases = []struct {
    95  		description     string
    96  		givenBidders    config.BidderInfos
    97  		givenAliases    map[string]string
    98  		expectedDetails map[string]bidderDetail
    99  		expectedError   string
   100  	}{
   101  		{
   102  			description:     "None",
   103  			givenBidders:    config.BidderInfos{},
   104  			givenAliases:    map[string]string{},
   105  			expectedDetails: map[string]bidderDetail{},
   106  		},
   107  		{
   108  			description:     "One Core Bidder",
   109  			givenBidders:    config.BidderInfos{"a": bidderAInfo},
   110  			givenAliases:    map[string]string{},
   111  			expectedDetails: map[string]bidderDetail{"a": bidderADetail},
   112  		},
   113  		{
   114  			description:     "Many Core Bidders",
   115  			givenBidders:    config.BidderInfos{"a": bidderAInfo, "b": bidderBInfo},
   116  			givenAliases:    map[string]string{},
   117  			expectedDetails: map[string]bidderDetail{"a": bidderADetail, "b": bidderBDetail},
   118  		},
   119  		{
   120  			description:     "One Alias",
   121  			givenBidders:    config.BidderInfos{"a": bidderAInfo},
   122  			givenAliases:    map[string]string{"aAlias": "a"},
   123  			expectedDetails: map[string]bidderDetail{"a": bidderADetail, "aAlias": aliasADetail},
   124  		},
   125  		{
   126  			description:     "Many Aliases - Same Core Bidder",
   127  			givenBidders:    config.BidderInfos{"a": bidderAInfo},
   128  			givenAliases:    map[string]string{"aAlias1": "a", "aAlias2": "a"},
   129  			expectedDetails: map[string]bidderDetail{"a": bidderADetail, "aAlias1": aliasADetail, "aAlias2": aliasADetail},
   130  		},
   131  		{
   132  			description:     "Many Aliases - Different Core Bidders",
   133  			givenBidders:    config.BidderInfos{"a": bidderAInfo, "b": bidderBInfo},
   134  			givenAliases:    map[string]string{"aAlias": "a", "bAlias": "b"},
   135  			expectedDetails: map[string]bidderDetail{"a": bidderADetail, "b": bidderBDetail, "aAlias": aliasADetail, "bAlias": aliasBDetail},
   136  		},
   137  		{
   138  			description:   "Error - Alias Without Core Bidder",
   139  			givenBidders:  config.BidderInfos{"a": bidderAInfo},
   140  			givenAliases:  map[string]string{"zAlias": "z"},
   141  			expectedError: "base adapter z for alias zAlias not found",
   142  		},
   143  	}
   144  
   145  	for _, test := range testCases {
   146  		details, err := mapDetails(test.givenBidders, test.givenAliases)
   147  
   148  		if test.expectedError == "" {
   149  			assert.Equal(t, test.expectedDetails, details, test.description+":details")
   150  			assert.NoError(t, err, test.expectedError, test.description+":err")
   151  		} else {
   152  			assert.Empty(t, details, test.description+":details")
   153  			assert.EqualError(t, err, test.expectedError, test.description+":err")
   154  		}
   155  	}
   156  }
   157  
   158  func TestMarshalDetailsResponse(t *testing.T) {
   159  	// Verifies omitempty is working correctly for bidderDetail, maintainer, capabilities, and aliasOf.
   160  	bidderDetailA := bidderDetail{Status: "ACTIVE", Maintainer: &maintainer{Email: "bidderA"}}
   161  	bidderDetailAResponse := []byte(`{"status":"ACTIVE","maintainer":{"email":"bidderA"}}`)
   162  
   163  	// Verifies omitempty is working correctly for capabilities.app / capabilities.site.
   164  	bidderDetailB := bidderDetail{Status: "ACTIVE", Maintainer: &maintainer{Email: "bidderB"}, Capabilities: &capabilities{App: &platform{MediaTypes: []string{"banner"}}}}
   165  	bidderDetailBResponse := []byte(`{"status":"ACTIVE","maintainer":{"email":"bidderB"},"capabilities":{"app":{"mediaTypes":["banner"]}}}`)
   166  
   167  	var testCases = []struct {
   168  		description      string
   169  		givenDetails     map[string]bidderDetail
   170  		expectedResponse map[string][]byte
   171  	}{
   172  		{
   173  			description:      "None",
   174  			givenDetails:     map[string]bidderDetail{},
   175  			expectedResponse: map[string][]byte{},
   176  		},
   177  		{
   178  			description:      "One",
   179  			givenDetails:     map[string]bidderDetail{"a": bidderDetailA},
   180  			expectedResponse: map[string][]byte{"a": bidderDetailAResponse},
   181  		},
   182  		{
   183  			description:      "Many",
   184  			givenDetails:     map[string]bidderDetail{"a": bidderDetailA, "b": bidderDetailB},
   185  			expectedResponse: map[string][]byte{"a": bidderDetailAResponse, "b": bidderDetailBResponse},
   186  		},
   187  	}
   188  
   189  	for _, test := range testCases {
   190  		response, err := marshalDetailsResponse(test.givenDetails)
   191  
   192  		assert.NoError(t, err, test.description+":err")
   193  		assert.Equal(t, test.expectedResponse, response, test.description+":response")
   194  	}
   195  }
   196  
   197  func TestMarshalAllResponse(t *testing.T) {
   198  	responses := map[string][]byte{
   199  		"a": []byte(`{"Status":"ACTIVE"}`),
   200  		"b": []byte(`{"Status":"DISABLED"}`),
   201  	}
   202  
   203  	result, err := marshalAllResponse(responses)
   204  
   205  	assert.NoError(t, err)
   206  	assert.Equal(t, []byte(`{"a":{"Status":"ACTIVE"},"b":{"Status":"DISABLED"}}`), result)
   207  }
   208  
   209  func TestMapDetailFromConfig(t *testing.T) {
   210  	trueValue := true
   211  	falseValue := false
   212  
   213  	var testCases = []struct {
   214  		description     string
   215  		givenBidderInfo config.BidderInfo
   216  		expected        bidderDetail
   217  	}{
   218  		{
   219  			description: "Enabled - All Values Present",
   220  			givenBidderInfo: config.BidderInfo{
   221  				Endpoint: "http://anyEndpoint",
   222  				Disabled: false,
   223  				Maintainer: &config.MaintainerInfo{
   224  					Email: "foo@bar.com",
   225  				},
   226  				Capabilities: &config.CapabilitiesInfo{
   227  					App:  &config.PlatformInfo{MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner}},
   228  					Site: &config.PlatformInfo{MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeVideo}},
   229  					DOOH: &config.PlatformInfo{MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeNative}},
   230  				},
   231  			},
   232  			expected: bidderDetail{
   233  				Status:    "ACTIVE",
   234  				UsesHTTPS: &falseValue,
   235  				Maintainer: &maintainer{
   236  					Email: "foo@bar.com",
   237  				},
   238  				Capabilities: &capabilities{
   239  					App:  &platform{MediaTypes: []string{"banner"}},
   240  					Site: &platform{MediaTypes: []string{"video"}},
   241  					DOOH: &platform{MediaTypes: []string{"native"}},
   242  				},
   243  				AliasOf: "",
   244  			},
   245  		},
   246  		{
   247  			description: "Disabled - All Values Present",
   248  			givenBidderInfo: config.BidderInfo{
   249  				Endpoint: "http://anyEndpoint",
   250  				Disabled: true,
   251  				Maintainer: &config.MaintainerInfo{
   252  					Email: "foo@bar.com",
   253  				},
   254  				Capabilities: &config.CapabilitiesInfo{
   255  					App:  &config.PlatformInfo{MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner}},
   256  					Site: &config.PlatformInfo{MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeVideo}},
   257  				},
   258  			},
   259  			expected: bidderDetail{
   260  				Status:    "DISABLED",
   261  				UsesHTTPS: nil,
   262  				Maintainer: &maintainer{
   263  					Email: "foo@bar.com",
   264  				},
   265  				Capabilities: nil,
   266  				AliasOf:      "",
   267  			},
   268  		},
   269  		{
   270  			description: "Enabled - No Values Present",
   271  			givenBidderInfo: config.BidderInfo{
   272  				Endpoint: "http://amyEndpoint",
   273  				Disabled: false,
   274  			},
   275  			expected: bidderDetail{
   276  				Status:    "ACTIVE",
   277  				UsesHTTPS: &falseValue,
   278  			},
   279  		},
   280  		{
   281  			description: "Enabled - Protocol - HTTP",
   282  			givenBidderInfo: config.BidderInfo{
   283  				Endpoint: "http://amyEndpoint",
   284  				Disabled: false,
   285  			},
   286  			expected: bidderDetail{
   287  				Status:    "ACTIVE",
   288  				UsesHTTPS: &falseValue,
   289  			},
   290  		},
   291  		{
   292  			description: "Enabled - Protocol - HTTPS",
   293  			givenBidderInfo: config.BidderInfo{
   294  				Endpoint: "https://amyEndpoint",
   295  				Disabled: false,
   296  			},
   297  			expected: bidderDetail{
   298  				Status:    "ACTIVE",
   299  				UsesHTTPS: &trueValue,
   300  			},
   301  		},
   302  		{
   303  			description: "Enabled - Protocol - HTTPS - Case Insensitive",
   304  			givenBidderInfo: config.BidderInfo{
   305  				Disabled: false,
   306  				Endpoint: "https://amyEndpoint",
   307  			},
   308  			expected: bidderDetail{
   309  				Status:    "ACTIVE",
   310  				UsesHTTPS: &trueValue,
   311  			},
   312  		},
   313  		{
   314  			description: "Enabled - Protocol - Unknown",
   315  			givenBidderInfo: config.BidderInfo{
   316  				Endpoint: "endpointWithoutProtocol",
   317  				Disabled: false,
   318  			},
   319  			expected: bidderDetail{
   320  				Status:    "ACTIVE",
   321  				UsesHTTPS: &falseValue,
   322  			},
   323  		},
   324  	}
   325  
   326  	for _, test := range testCases {
   327  		result := mapDetailFromConfig(test.givenBidderInfo)
   328  		assert.Equal(t, test.expected, result, test.description)
   329  	}
   330  }
   331  
   332  func TestMapMediaTypes(t *testing.T) {
   333  	var testCases = []struct {
   334  		description string
   335  		mediaTypes  []openrtb_ext.BidType
   336  		expected    []string
   337  	}{
   338  		{
   339  			description: "Nil",
   340  			mediaTypes:  nil,
   341  			expected:    nil,
   342  		},
   343  		{
   344  			description: "None",
   345  			mediaTypes:  []openrtb_ext.BidType{},
   346  			expected:    []string{},
   347  		},
   348  		{
   349  			description: "One",
   350  			mediaTypes:  []openrtb_ext.BidType{openrtb_ext.BidTypeBanner},
   351  			expected:    []string{"banner"},
   352  		},
   353  		{
   354  			description: "Many",
   355  			mediaTypes:  []openrtb_ext.BidType{openrtb_ext.BidTypeBanner, openrtb_ext.BidTypeVideo},
   356  			expected:    []string{"banner", "video"},
   357  		},
   358  	}
   359  
   360  	for _, test := range testCases {
   361  		result := mapMediaTypes(test.mediaTypes)
   362  		assert.ElementsMatch(t, test.expected, result, test.description)
   363  	}
   364  }
   365  
   366  func TestBiddersDetailHandler(t *testing.T) {
   367  	bidderAInfo := config.BidderInfo{Endpoint: "https://secureEndpoint.com", Disabled: false, Maintainer: &config.MaintainerInfo{Email: "bidderA"}}
   368  	bidderAResponse := []byte(`{"status":"ACTIVE","usesHttps":true,"maintainer":{"email":"bidderA"}}`)
   369  	aliasAResponse := []byte(`{"status":"ACTIVE","usesHttps":true,"maintainer":{"email":"bidderA"},"aliasOf":"appnexus"}`)
   370  
   371  	bidderBInfo := config.BidderInfo{Endpoint: "http://unsecureEndpoint.com", Disabled: false, Maintainer: &config.MaintainerInfo{Email: "bidderB"}}
   372  	bidderBResponse := []byte(`{"status":"ACTIVE","usesHttps":false,"maintainer":{"email":"bidderB"}}`)
   373  
   374  	allResponse := bytes.Buffer{}
   375  	allResponse.WriteString(`{"aAlias":`)
   376  	allResponse.Write(aliasAResponse)
   377  	allResponse.WriteString(`,"appnexus":`)
   378  	allResponse.Write(bidderAResponse)
   379  	allResponse.WriteString(`,"rubicon":`)
   380  	allResponse.Write(bidderBResponse)
   381  	allResponse.WriteString(`}`)
   382  
   383  	bidders := config.BidderInfos{"appnexus": bidderAInfo, "rubicon": bidderBInfo}
   384  	aliases := map[string]string{"aAlias": "appnexus"}
   385  
   386  	handler := NewBiddersDetailEndpoint(bidders, aliases)
   387  
   388  	var testCases = []struct {
   389  		description      string
   390  		givenBidder      string
   391  		expectedStatus   int
   392  		expectedHeaders  http.Header
   393  		expectedResponse []byte
   394  	}{
   395  		{
   396  			description:      "Bidder A",
   397  			givenBidder:      "appnexus",
   398  			expectedStatus:   http.StatusOK,
   399  			expectedHeaders:  http.Header{"Content-Type": []string{"application/json"}},
   400  			expectedResponse: bidderAResponse,
   401  		},
   402  		{
   403  			description:      "Bidder B",
   404  			givenBidder:      "rubicon",
   405  			expectedStatus:   http.StatusOK,
   406  			expectedHeaders:  http.Header{"Content-Type": []string{"application/json"}},
   407  			expectedResponse: bidderBResponse,
   408  		},
   409  		{
   410  			description:      "Bidder B - case insensitive",
   411  			givenBidder:      "RUBICON",
   412  			expectedStatus:   http.StatusOK,
   413  			expectedHeaders:  http.Header{"Content-Type": []string{"application/json"}},
   414  			expectedResponse: bidderBResponse,
   415  		},
   416  		{
   417  			description:      "Bidder A Alias",
   418  			givenBidder:      "aAlias",
   419  			expectedStatus:   http.StatusOK,
   420  			expectedHeaders:  http.Header{"Content-Type": []string{"application/json"}},
   421  			expectedResponse: aliasAResponse,
   422  		},
   423  		{
   424  			description:      "Bidder A Alias - case insensitive",
   425  			givenBidder:      "aAlias",
   426  			expectedStatus:   http.StatusOK,
   427  			expectedHeaders:  http.Header{"Content-Type": []string{"application/json"}},
   428  			expectedResponse: aliasAResponse,
   429  		},
   430  		{
   431  			description:      "All Bidders",
   432  			givenBidder:      "all",
   433  			expectedStatus:   http.StatusOK,
   434  			expectedHeaders:  http.Header{"Content-Type": []string{"application/json"}},
   435  			expectedResponse: allResponse.Bytes(),
   436  		},
   437  		{
   438  			description:      "All Bidders - Case insensitive",
   439  			givenBidder:      "All",
   440  			expectedStatus:   http.StatusOK,
   441  			expectedHeaders:  http.Header{"Content-Type": []string{"application/json"}},
   442  			expectedResponse: allResponse.Bytes(),
   443  		},
   444  		{
   445  			description:      "Invalid Bidder",
   446  			givenBidder:      "doesntExist",
   447  			expectedStatus:   http.StatusNotFound,
   448  			expectedHeaders:  http.Header{},
   449  			expectedResponse: []byte{},
   450  		},
   451  	}
   452  
   453  	for _, test := range testCases {
   454  		t.Run(test.description, func(t *testing.T) {
   455  			responseRecorder := httptest.NewRecorder()
   456  			handler(responseRecorder, nil, httprouter.Params{{
   457  				Key:   "bidderName",
   458  				Value: test.givenBidder,
   459  			}})
   460  
   461  			result := responseRecorder.Result()
   462  			assert.Equal(t, result.StatusCode, test.expectedStatus, test.description+":statuscode")
   463  
   464  			resultBody, _ := io.ReadAll(result.Body)
   465  			fmt.Println(string(test.expectedResponse))
   466  			assert.Equal(t, test.expectedResponse, resultBody, test.description+":body")
   467  
   468  			resultHeaders := result.Header
   469  			assert.Equal(t, test.expectedHeaders, resultHeaders, test.description+":headers")
   470  		})
   471  	}
   472  }