From 80996d1f2fb9173c1d1e84d1c603814dbff25563 Mon Sep 17 00:00:00 2001 From: QuentinGallard Date: Wed, 30 Jul 2025 17:14:52 +0200 Subject: [PATCH] SmileWanted endpoint now supports dynamic zoneId and integrates prebid server technology --- adapters/smilewanted/smilewanted.go | 25 ++++- adapters/smilewanted/smilewanted_test.go | 58 +++++++++- .../exemplary/simple-banner.json | 6 +- .../exemplary/simple-video.json | 3 +- .../supplemental/bad-server-response.json | 6 +- .../invalid-bid-response-body.json | 55 +++++++++ .../supplemental/status-code-204.json | 6 +- .../supplemental/status-code-400.json | 6 +- .../supplemental/unexpected-status-code.json | 6 +- .../supplemental/unknown-imp-id.json | 106 ++++++++++++++++++ openrtb_ext/imp_smilewanted.go | 6 + static/bidder-info/smilewanted.yaml | 2 +- 12 files changed, 265 insertions(+), 20 deletions(-) create mode 100644 adapters/smilewanted/smilewantedtest/supplemental/invalid-bid-response-body.json create mode 100644 adapters/smilewanted/smilewantedtest/supplemental/unknown-imp-id.json create mode 100644 openrtb_ext/imp_smilewanted.go diff --git a/adapters/smilewanted/smilewanted.go b/adapters/smilewanted/smilewanted.go index 5484c8419d9..3a8efdf0bb4 100644 --- a/adapters/smilewanted/smilewanted.go +++ b/adapters/smilewanted/smilewanted.go @@ -18,6 +18,27 @@ type adapter struct { } func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + var errs []error + + // Extract zoneId from the first impression if available + var zoneId string + if len(request.Imp) > 0 { + // Parse imp.ext + var bidderExt adapters.ExtImpBidder + if err := jsonutil.Unmarshal(request.Imp[0].Ext, &bidderExt); err == nil { + // Parse bidderExt.Bidder to get zoneId + var smilewantedExt openrtb_ext.ExtImpSmilewanted + if err := jsonutil.Unmarshal(bidderExt.Bidder, &smilewantedExt); err == nil { + zoneId = smilewantedExt.ZoneId + } + } + } + + // Build the endpoint URL with zoneId + endpoint := a.URI + if zoneId != "" { + endpoint = a.URI + zoneId + } request.AT = 1 //Defaulting to first price auction for all prebid requests @@ -36,11 +57,11 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.E return []*adapters.RequestData{{ Method: "POST", - Uri: a.URI, + Uri: endpoint, Body: reqJSON, Headers: headers, ImpIDs: openrtb_ext.GetImpIDs(request.Imp), - }}, []error{} + }}, errs } func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { diff --git a/adapters/smilewanted/smilewanted_test.go b/adapters/smilewanted/smilewanted_test.go index b63c2172e93..8e291eb505d 100644 --- a/adapters/smilewanted/smilewanted_test.go +++ b/adapters/smilewanted/smilewanted_test.go @@ -1,16 +1,22 @@ package smilewanted import ( + "encoding/json" + "math" + "net/http" "testing" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" "github.com/prebid/prebid-server/v3/adapters/adapterstest" "github.com/prebid/prebid-server/v3/config" "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/stretchr/testify/assert" ) func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderSmileWanted, config.Adapter{ - Endpoint: "http://example.com"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) + Endpoint: "http://example.com/go/"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -18,3 +24,53 @@ func TestJsonSamples(t *testing.T) { adapterstest.RunJSONBidderTest(t, "smilewantedtest", bidder) } + +// TestMakeRequestsJSONMarshalError tests the error handling when JSON marshalling fails +func TestMakeRequestsJSONMarshalError(t *testing.T) { + bidder := &adapter{ + URI: "http://example.com/go/", + } + + // Create a request with a float value that cannot be marshalled to JSON (NaN) + request := &openrtb2.BidRequest{ + ID: "test-request-id", + Imp: []openrtb2.Imp{{ + ID: "test-imp-id", + BidFloor: math.NaN(), // NaN cannot be marshalled to JSON + Ext: json.RawMessage(`{"bidder": {"zoneId": "test"}}`), + }}, + } + + _, errs := bidder.MakeRequests(request, &adapters.ExtraRequestInfo{}) + assert.NotNil(t, errs) + assert.Len(t, errs, 1) + assert.Contains(t, errs[0].Error(), "Json not encoded") +} + +// TestMakeBidsJSONUnmarshalError tests the error handling when unmarshalling external request fails +func TestMakeBidsJSONUnmarshalError(t *testing.T) { + bidder := &adapter{ + URI: "http://example.com/go/", + } + + internalRequest := &openrtb2.BidRequest{ + ID: "test-request-id", + Imp: []openrtb2.Imp{{ + ID: "test-imp-id", + }}, + } + + // Create external request with invalid JSON that will fail unmarshalling + externalRequest := &adapters.RequestData{ + Body: []byte(`{invalid json}`), + } + + response := &adapters.ResponseData{ + StatusCode: http.StatusOK, + Body: []byte(`{"id":"test-response-id","seatbid":[{"bid":[{"id":"test-bid-id","impid":"test-imp-id","price":1.0}]}]}`), + } + + _, errs := bidder.MakeBids(internalRequest, externalRequest, response) + assert.NotNil(t, errs) + assert.Len(t, errs, 1) +} diff --git a/adapters/smilewanted/smilewantedtest/exemplary/simple-banner.json b/adapters/smilewanted/smilewantedtest/exemplary/simple-banner.json index dccf94a8eb2..3a45c58544c 100644 --- a/adapters/smilewanted/smilewantedtest/exemplary/simple-banner.json +++ b/adapters/smilewanted/smilewantedtest/exemplary/simple-banner.json @@ -1,10 +1,10 @@ { "mockBidRequest": { "id": "test-request-id", + "at": 1, "imp": [ { "id": "test-imp-id", - "at" : 1, "banner": { "format": [ { @@ -25,10 +25,10 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://example.com", + "uri": "http://example.com/go/zone_code_test_display", "body": { "id": "test-request-id", - "at" : 1, + "at": 1, "imp": [ { "id": "test-imp-id", diff --git a/adapters/smilewanted/smilewantedtest/exemplary/simple-video.json b/adapters/smilewanted/smilewantedtest/exemplary/simple-video.json index a0880bbe510..e5c07db5208 100644 --- a/adapters/smilewanted/smilewantedtest/exemplary/simple-video.json +++ b/adapters/smilewanted/smilewantedtest/exemplary/simple-video.json @@ -1,6 +1,7 @@ { "mockBidRequest": { "id": "test-request-id", + "at": 1, "imp": [ { "id": "test-imp-id", @@ -22,7 +23,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://example.com", + "uri": "http://example.com/go/zone_code_test_video", "body": { "id": "test-request-id", "at": 1, diff --git a/adapters/smilewanted/smilewantedtest/supplemental/bad-server-response.json b/adapters/smilewanted/smilewantedtest/supplemental/bad-server-response.json index e6f59ab83a1..155b37c00d3 100644 --- a/adapters/smilewanted/smilewantedtest/supplemental/bad-server-response.json +++ b/adapters/smilewanted/smilewantedtest/supplemental/bad-server-response.json @@ -1,10 +1,10 @@ { "mockBidRequest": { "id": "test-request-id", + "at": 1, "imp": [ { "id": "test-imp-id", - "at" : 1, "banner": { "format": [ { @@ -24,10 +24,10 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://example.com", + "uri": "http://example.com/go/zone_code_test_display", "body": { "id": "test-request-id", - "at" : 1, + "at": 1, "imp": [ { "id": "test-imp-id", diff --git a/adapters/smilewanted/smilewantedtest/supplemental/invalid-bid-response-body.json b/adapters/smilewanted/smilewantedtest/supplemental/invalid-bid-response-body.json new file mode 100644 index 00000000000..0d6dd8b6abe --- /dev/null +++ b/adapters/smilewanted/smilewantedtest/supplemental/invalid-bid-response-body.json @@ -0,0 +1,55 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "zoneId": "testzone" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/go/testzone", + "body": { + "id": "test-request-id", + "at": 1, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "zoneId": "testzone" + } + } + } + ] + }, + "impIDs": ["test-imp-id"] + }, + "mockResponse": { + "status": 200, + "body": "invalid json response body" + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Bad server response: expect { or n, but found \".", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/smilewanted/smilewantedtest/supplemental/status-code-204.json b/adapters/smilewanted/smilewantedtest/supplemental/status-code-204.json index adc7c67006b..6df0b845bfc 100644 --- a/adapters/smilewanted/smilewantedtest/supplemental/status-code-204.json +++ b/adapters/smilewanted/smilewantedtest/supplemental/status-code-204.json @@ -1,10 +1,10 @@ { "mockBidRequest": { "id": "test-request-id", + "at": 1, "imp": [ { "id": "test-imp-id", - "at" : 1, "banner": { "format": [ { @@ -25,10 +25,10 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://example.com", + "uri": "http://example.com/go/zone_code_test_display", "body": { "id": "test-request-id", - "at" : 1, + "at": 1, "imp": [ { "id": "test-imp-id", diff --git a/adapters/smilewanted/smilewantedtest/supplemental/status-code-400.json b/adapters/smilewanted/smilewantedtest/supplemental/status-code-400.json index 78c6824009e..d38f2006158 100644 --- a/adapters/smilewanted/smilewantedtest/supplemental/status-code-400.json +++ b/adapters/smilewanted/smilewantedtest/supplemental/status-code-400.json @@ -1,10 +1,10 @@ { "mockBidRequest": { "id": "test-request-id", + "at": 1, "imp": [ { "id": "test-imp-id", - "at" : 1, "banner": { "format": [ { @@ -25,10 +25,10 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://example.com", + "uri": "http://example.com/go/zone_code_test_display", "body": { "id": "test-request-id", - "at" : 1, + "at": 1, "imp": [ { "id": "test-imp-id", diff --git a/adapters/smilewanted/smilewantedtest/supplemental/unexpected-status-code.json b/adapters/smilewanted/smilewantedtest/supplemental/unexpected-status-code.json index e422ca196d3..07a336ace47 100644 --- a/adapters/smilewanted/smilewantedtest/supplemental/unexpected-status-code.json +++ b/adapters/smilewanted/smilewantedtest/supplemental/unexpected-status-code.json @@ -1,10 +1,10 @@ { "mockBidRequest": { "id": "test-request-id", + "at": 1, "imp": [ { "id": "test-imp-id", - "at" : 1, "banner": { "format": [ { @@ -25,10 +25,10 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://example.com", + "uri": "http://example.com/go/zone_code_test_display", "body": { "id": "test-request-id", - "at" : 1, + "at": 1, "imp": [ { "id": "test-imp-id", diff --git a/adapters/smilewanted/smilewantedtest/supplemental/unknown-imp-id.json b/adapters/smilewanted/smilewantedtest/supplemental/unknown-imp-id.json new file mode 100644 index 00000000000..a0c62c8169b --- /dev/null +++ b/adapters/smilewanted/smilewantedtest/supplemental/unknown-imp-id.json @@ -0,0 +1,106 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-1", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "zoneId": "testzone" + } + } + }, + { + "id": "test-imp-2", + "video": { + "w": 640, + "h": 480, + "mimes": null + }, + "ext": { + "bidder": { + "zoneId": "testzone" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/go/testzone", + "body": { + "id": "test-request-id", + "at": 1, + "imp": [ + { + "id": "test-imp-1", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "zoneId": "testzone" + } + } + }, + { + "id": "test-imp-2", + "video": { + "w": 640, + "h": 480, + "mimes": null + }, + "ext": { + "bidder": { + "zoneId": "testzone" + } + } + } + ] + }, + "impIDs": ["test-imp-1", "test-imp-2"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-response-id", + "seatbid": [ + { + "bid": [ + { + "id": "test-bid-id", + "impid": "unknown-imp-id", + "price": 1.0, + "adm": "", + "crid": "creative-1" + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "test-bid-id", + "impid": "unknown-imp-id", + "price": 1.0, + "adm": "", + "crid": "creative-1" + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/openrtb_ext/imp_smilewanted.go b/openrtb_ext/imp_smilewanted.go new file mode 100644 index 00000000000..1ce68ab41ed --- /dev/null +++ b/openrtb_ext/imp_smilewanted.go @@ -0,0 +1,6 @@ +package openrtb_ext + +// ExtImpSmilewanted defines the contract for bidrequest.imp[i].ext.prebid.bidder.smilewanted +type ExtImpSmilewanted struct { + ZoneId string `json:"zoneId"` +} diff --git a/static/bidder-info/smilewanted.yaml b/static/bidder-info/smilewanted.yaml index e0648d417f9..986fd72cf6b 100644 --- a/static/bidder-info/smilewanted.yaml +++ b/static/bidder-info/smilewanted.yaml @@ -1,4 +1,4 @@ -endpoint: "https://prebid-server.smilewanted.com" +endpoint: "https://prebid-server.smilewanted.com/go/" maintainer: email: "tech@smilewanted.com" gvlVendorID: 639