From 8fa698adc4ca124cab9fc69f1b485640fcf0a9ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rok=20Su=C5=A1nik?= Date: Thu, 8 Apr 2021 21:41:58 +0200 Subject: [PATCH] New Adapter: Zemanta (#1774) * add zemanta adapter * update openrtb package for zemanta * fix loop iterator reference bug * fix getMediaTypeForImp to match server behavior --- adapters/zemanta/params_test.go | 50 ++++++ adapters/zemanta/usersync.go | 12 ++ adapters/zemanta/usersync_test.go | 33 ++++ adapters/zemanta/zemanta.go | 147 +++++++++++++++++ adapters/zemanta/zemanta_test.go | 20 +++ .../zemanta/zemantatest/exemplary/banner.json | 133 ++++++++++++++++ .../zemanta/zemantatest/exemplary/native.json | 125 +++++++++++++++ .../zemantatest/params/race/banner.json | 10 ++ .../zemantatest/params/race/native.json | 10 ++ .../zemantatest/supplemental/app_request.json | 144 +++++++++++++++++ .../supplemental/optional_params.json | 148 ++++++++++++++++++ .../zemantatest/supplemental/status_204.json | 62 ++++++++ .../zemantatest/supplemental/status_400.json | 67 ++++++++ .../zemantatest/supplemental/status_418.json | 67 ++++++++ config/config.go | 2 + exchange/adapter_builders.go | 2 + openrtb_ext/bidders.go | 2 + openrtb_ext/imp_zemanta.go | 15 ++ static/bidder-info/zemanta.yaml | 12 ++ static/bidder-params/zemanta.json | 40 +++++ usersync/usersyncers/syncer.go | 2 + usersync/usersyncers/syncer_test.go | 1 + 22 files changed, 1104 insertions(+) create mode 100644 adapters/zemanta/params_test.go create mode 100644 adapters/zemanta/usersync.go create mode 100644 adapters/zemanta/usersync_test.go create mode 100644 adapters/zemanta/zemanta.go create mode 100644 adapters/zemanta/zemanta_test.go create mode 100644 adapters/zemanta/zemantatest/exemplary/banner.json create mode 100644 adapters/zemanta/zemantatest/exemplary/native.json create mode 100644 adapters/zemanta/zemantatest/params/race/banner.json create mode 100644 adapters/zemanta/zemantatest/params/race/native.json create mode 100644 adapters/zemanta/zemantatest/supplemental/app_request.json create mode 100644 adapters/zemanta/zemantatest/supplemental/optional_params.json create mode 100644 adapters/zemanta/zemantatest/supplemental/status_204.json create mode 100644 adapters/zemanta/zemantatest/supplemental/status_400.json create mode 100644 adapters/zemanta/zemantatest/supplemental/status_418.json create mode 100644 openrtb_ext/imp_zemanta.go create mode 100644 static/bidder-info/zemanta.yaml create mode 100644 static/bidder-params/zemanta.json diff --git a/adapters/zemanta/params_test.go b/adapters/zemanta/params_test.go new file mode 100644 index 00000000000..54e67c57bf1 --- /dev/null +++ b/adapters/zemanta/params_test.go @@ -0,0 +1,50 @@ +package zemanta + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/openrtb_ext" +) + +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json schema. %v", err) + } + + for _, p := range validParams { + if err := validator.Validate(openrtb_ext.BidderZemanta, json.RawMessage(p)); err != nil { + t.Errorf("Schema rejected valid params: %s", p) + } + } +} + +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json schema. %v", err) + } + + for _, p := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderZemanta, json.RawMessage(p)); err == nil { + t.Errorf("Schema allowed invalid params: %s", p) + } + } +} + +var validParams = []string{ + `{"publisher": {"id": "publisher-id"}}`, + `{"publisher": {"id": "publisher-id", "name": "publisher-name", "domain": "publisher-domain.com"}, "tagid": "tag-id", "bcat": ["bad-category"], "badv": ["bad-advertiser"]}`, +} + +var invalidParams = []string{ + `{"publisher": {"id": 1234}}`, + `{"publisher": {"id": "pub-id", "name": 1234}}`, + `{"publisher": {"id": "pub-id", "domain": 1234}}`, + `{"publisher": {"id": "pub-id"}, "tagid": 1234}`, + `{"publisher": {"id": "pub-id"}, "bcat": "not-array"}`, + `{"publisher": {"id": "pub-id"}, "bcat": [1234]}`, + `{"publisher": {"id": "pub-id"}, "badv": "not-array"}`, + `{"publisher": {"id": "pub-id"}, "badv": [1234]}`, +} diff --git a/adapters/zemanta/usersync.go b/adapters/zemanta/usersync.go new file mode 100644 index 00000000000..fec8a2feda8 --- /dev/null +++ b/adapters/zemanta/usersync.go @@ -0,0 +1,12 @@ +package zemanta + +import ( + "text/template" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" +) + +func NewZemantaSyncer(temp *template.Template) usersync.Usersyncer { + return adapters.NewSyncer("zemanta", temp, adapters.SyncTypeRedirect) +} diff --git a/adapters/zemanta/usersync_test.go b/adapters/zemanta/usersync_test.go new file mode 100644 index 00000000000..e56851be821 --- /dev/null +++ b/adapters/zemanta/usersync_test.go @@ -0,0 +1,33 @@ +package zemanta + +import ( + "testing" + "text/template" + + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/ccpa" + "github.com/prebid/prebid-server/privacy/gdpr" + "github.com/stretchr/testify/assert" +) + +func TestSyncer(t *testing.T) { + syncURL := "http://prebidtest.zemanta.com/usersync/prebidtest?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&cb=host%2Fsetuid%3Fbidder%3Dzemanta%26uid%3D__ZUID__" + syncURLTemplate := template.Must( + template.New("sync-template").Parse(syncURL), + ) + + syncer := NewZemantaSyncer(syncURLTemplate) + syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ + GDPR: gdpr.Policy{ + Signal: "A", + Consent: "B", + }, + CCPA: ccpa.Policy{ + Consent: "C", + }, + }) + + assert.NoError(t, err) + assert.Equal(t, "http://prebidtest.zemanta.com/usersync/prebidtest?gdpr=A&gdpr_consent=B&us_privacy=C&cb=host%2Fsetuid%3Fbidder%3Dzemanta%26uid%3D__ZUID__", syncInfo.URL) + assert.Equal(t, "redirect", syncInfo.Type) +} diff --git a/adapters/zemanta/zemanta.go b/adapters/zemanta/zemanta.go new file mode 100644 index 00000000000..ae547eb91aa --- /dev/null +++ b/adapters/zemanta/zemanta.go @@ -0,0 +1,147 @@ +package zemanta + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/mxmCherry/openrtb/v14/openrtb2" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/openrtb_ext" +) + +type adapter struct { + endpoint string +} + +// Builder builds a new instance of the Zemanta adapter for the given bidder with the given config. +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { + bidder := &adapter{ + endpoint: config.Endpoint, + } + return bidder, nil +} + +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + reqCopy := *request + + var errs []error + var zemantaExt openrtb_ext.ExtImpZemanta + for i := 0; i < len(reqCopy.Imp); i++ { + imp := reqCopy.Imp[i] + + var bidderExt adapters.ExtImpBidder + if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + errs = append(errs, err) + continue + } + if err := json.Unmarshal(bidderExt.Bidder, &zemantaExt); err != nil { + errs = append(errs, err) + continue + } + imp.TagID = zemantaExt.TagId + reqCopy.Imp[i] = imp + } + + publisher := &openrtb2.Publisher{ + ID: zemantaExt.Publisher.Id, + Name: zemantaExt.Publisher.Name, + Domain: zemantaExt.Publisher.Domain, + } + if reqCopy.Site != nil { + siteCopy := *reqCopy.Site + siteCopy.Publisher = publisher + reqCopy.Site = &siteCopy + } else if reqCopy.App != nil { + appCopy := *reqCopy.App + appCopy.Publisher = publisher + reqCopy.App = &appCopy + } + + if zemantaExt.BCat != nil { + reqCopy.BCat = zemantaExt.BCat + } + if zemantaExt.BAdv != nil { + reqCopy.BAdv = zemantaExt.BAdv + } + + requestJSON, err := json.Marshal(reqCopy) + if err != nil { + errs = append(errs, err) + return nil, errs + } + + requestData := &adapters.RequestData{ + Method: "POST", + Uri: a.endpoint, + Body: requestJSON, + } + + return []*adapters.RequestData{requestData}, nil +} + +func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if responseData.StatusCode == http.StatusNoContent { + return nil, nil + } + + if responseData.StatusCode == http.StatusBadRequest { + err := &errortypes.BadInput{ + Message: "Unexpected status code: 400. Bad request from publisher. Run with request.debug = 1 for more info.", + } + return nil, []error{err} + } + + if responseData.StatusCode != http.StatusOK { + err := &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info.", responseData.StatusCode), + } + return nil, []error{err} + } + + var response openrtb2.BidResponse + if err := json.Unmarshal(responseData.Body, &response); err != nil { + return nil, []error{err} + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp)) + bidResponse.Currency = response.Cur + + var errs []error + for _, seatBid := range response.SeatBid { + for i := range seatBid.Bid { + bid := seatBid.Bid[i] + bidType, err := getMediaTypeForImp(bid.ImpID, request.Imp) + if err != nil { + errs = append(errs, err) + continue + } + + b := &adapters.TypedBid{ + Bid: &bid, + BidType: bidType, + } + bidResponse.Bids = append(bidResponse.Bids, b) + } + } + + return bidResponse, errs +} + +func getMediaTypeForImp(impID string, imps []openrtb2.Imp) (openrtb_ext.BidType, error) { + for _, imp := range imps { + if imp.ID == impID { + if imp.Native != nil { + return openrtb_ext.BidTypeNative, nil + } else if imp.Banner != nil { + return openrtb_ext.BidTypeBanner, nil + } + } + } + + return "", &errortypes.BadInput{ + Message: fmt.Sprintf("Failed to find native/banner impression \"%s\" ", impID), + } +} diff --git a/adapters/zemanta/zemanta_test.go b/adapters/zemanta/zemanta_test.go new file mode 100644 index 00000000000..e63eb51005c --- /dev/null +++ b/adapters/zemanta/zemanta_test.go @@ -0,0 +1,20 @@ +package zemanta + +import ( + "testing" + + "github.com/prebid/prebid-server/adapters/adapterstest" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" +) + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderZemanta, config.Adapter{ + Endpoint: "http://example.com/bid"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "zemantatest", bidder) +} diff --git a/adapters/zemanta/zemantatest/exemplary/banner.json b/adapters/zemanta/zemantatest/exemplary/banner.json new file mode 100644 index 00000000000..16d52cf1c0f --- /dev/null +++ b/adapters/zemanta/zemantatest/exemplary/banner.json @@ -0,0 +1,133 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "publisher": { + "id": "publisher-id" + } + } + } + } + ], + "site": { + "page": "http://example.com" + }, + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36", + "h": 500, + "w": 1000 + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/bid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "publisher": { + "id": "publisher-id" + } + } + } + } + ], + "site": { + "page": "http://example.com", + "publisher": { + "id": "publisher-id" + } + }, + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36", + "h": 500, + "w": 1000 + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "5095a742-1c27-402b-ab6f-66b1bd53383b", + "seatbid": [ + { + "bid": [ + { + "id": "test-request-id", + "impid": "test-imp-id", + "price": 1000, + "nurl": "http://example.com/win/1000", + "adm": "
ad
", + "adomain": [ + "example.com" + ], + "cid": "test-cid", + "crid": "test-crid", + "cat": [ + "IAB13-4" + ], + "w": 300, + "h": 250 + } + ], + "seat": "acc-1876" + } + ], + "bidid": "43ccadf2-8b2e-11eb-b294-de4c49e99ff6", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-request-id", + "impid": "test-imp-id", + "price": 1000, + "nurl": "http://example.com/win/1000", + "adm": "
ad
", + "adomain": [ + "example.com" + ], + "cid": "test-cid", + "crid": "test-crid", + "cat": [ + "IAB13-4" + ], + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/zemanta/zemantatest/exemplary/native.json b/adapters/zemanta/zemantatest/exemplary/native.json new file mode 100644 index 00000000000..d32fd60382e --- /dev/null +++ b/adapters/zemanta/zemantatest/exemplary/native.json @@ -0,0 +1,125 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "native": { + "request": "{\"context\":1,\"plcmttype\":1,\"assets\":[{\"id\":0,\"required\":1,\"img\":{\"type\":3,\"w\":120,\"h\":100}},{\"id\":1,\"required\":1,\"title\":{\"len\":140}},{\"id\":2,\"data\":{\"type\":1}}],\"eventtrackers\":[{\"event\":1,\"methods\":[1]}]}", + "ver": "1.2" + }, + "ext": { + "bidder": { + "publisher": { + "id": "publisher-id" + } + } + } + } + ], + "site": { + "page": "http://example.com" + }, + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36", + "h": 500, + "w": 1000 + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/bid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "native": { + "request": "{\"context\":1,\"plcmttype\":1,\"assets\":[{\"id\":0,\"required\":1,\"img\":{\"type\":3,\"w\":120,\"h\":100}},{\"id\":1,\"required\":1,\"title\":{\"len\":140}},{\"id\":2,\"data\":{\"type\":1}}],\"eventtrackers\":[{\"event\":1,\"methods\":[1]}]}", + "ver": "1.2" + }, + "ext": { + "bidder": { + "publisher": { + "id": "publisher-id" + } + } + } + } + ], + "site": { + "page": "http://example.com", + "publisher": { + "id": "publisher-id" + } + }, + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36", + "h": 500, + "w": 1000 + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "5095a742-1c27-402b-ab6f-66b1bd53383b", + "seatbid": [ + { + "bid": [ + { + "id": "test-request-id", + "impid": "test-imp-id", + "price": 1000, + "nurl": "http://example.com/win/1000", + "adm": "{\"ver\":\"1.2\",\"assets\":[{\"id\":3,\"required\":1,\"img\":{\"url\":\"http://example.com/img/url\",\"w\":120,\"h\":100}},{\"id\":0,\"required\":1,\"title\":{\"text\":\"Test title\"}},{\"id\":5,\"data\":{\"value\":\"Test sponsor\"}}],\"link\":{\"url\":\"http://example.com/click/url\"},\"eventtrackers\":[{\"event\":1,\"method\":1,\"url\":\"http://example.com/impression\"}]}", + "adomain": [ + "example.com" + ], + "cid": "test-cid", + "crid": "test-crid", + "cat": [ + "IAB13-4" + ], + "w": 300, + "h": 250 + } + ], + "seat": "acc-1876" + } + ], + "bidid": "43ccadf2-8b2e-11eb-b294-de4c49e99ff6", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-request-id", + "impid": "test-imp-id", + "price": 1000, + "nurl": "http://example.com/win/1000", + "adm": "{\"ver\":\"1.2\",\"assets\":[{\"id\":3,\"required\":1,\"img\":{\"url\":\"http://example.com/img/url\",\"w\":120,\"h\":100}},{\"id\":0,\"required\":1,\"title\":{\"text\":\"Test title\"}},{\"id\":5,\"data\":{\"value\":\"Test sponsor\"}}],\"link\":{\"url\":\"http://example.com/click/url\"},\"eventtrackers\":[{\"event\":1,\"method\":1,\"url\":\"http://example.com/impression\"}]}", + "adomain": [ + "example.com" + ], + "cid": "test-cid", + "crid": "test-crid", + "cat": [ + "IAB13-4" + ], + "w": 300, + "h": 250 + }, + "type": "native" + } + ] + } + ] +} diff --git a/adapters/zemanta/zemantatest/params/race/banner.json b/adapters/zemanta/zemantatest/params/race/banner.json new file mode 100644 index 00000000000..05577a78e8b --- /dev/null +++ b/adapters/zemanta/zemantatest/params/race/banner.json @@ -0,0 +1,10 @@ +{ + "publisher": { + "id": "publisher-id", + "name": "publisher-name", + "domain": "publisher-domain.com" + }, + "tagid": "tag-id", + "bcat": ["bad-category"], + "badv": ["bad-advertiser"] +} \ No newline at end of file diff --git a/adapters/zemanta/zemantatest/params/race/native.json b/adapters/zemanta/zemantatest/params/race/native.json new file mode 100644 index 00000000000..05577a78e8b --- /dev/null +++ b/adapters/zemanta/zemantatest/params/race/native.json @@ -0,0 +1,10 @@ +{ + "publisher": { + "id": "publisher-id", + "name": "publisher-name", + "domain": "publisher-domain.com" + }, + "tagid": "tag-id", + "bcat": ["bad-category"], + "badv": ["bad-advertiser"] +} \ No newline at end of file diff --git a/adapters/zemanta/zemantatest/supplemental/app_request.json b/adapters/zemanta/zemantatest/supplemental/app_request.json new file mode 100644 index 00000000000..c8e7c4cf69f --- /dev/null +++ b/adapters/zemanta/zemantatest/supplemental/app_request.json @@ -0,0 +1,144 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "publisher": { + "id": "publisher-id" + } + } + } + } + ], + "app": { + "name": "test-app", + "bundle": "org.test", + "ver": "1.10", + "publisher": { + "id": "pub-id" + } + }, + "device": { + "ua": "Mozilla/5.0 (Linux; Android 11; sdk_gphone_x86_arm Build/RSR1.201013.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/83.0.4103.106 Mobile Safari/537.36", + "model": "sdk_gphone_x86_arm", + "os": "android", + "h": 735, + "w": 392 + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/bid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "publisher": { + "id": "publisher-id" + } + } + } + } + ], + "app": { + "name": "test-app", + "bundle": "org.test", + "ver": "1.10", + "publisher": { + "id": "publisher-id" + } + }, + "device": { + "ua": "Mozilla/5.0 (Linux; Android 11; sdk_gphone_x86_arm Build/RSR1.201013.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/83.0.4103.106 Mobile Safari/537.36", + "model": "sdk_gphone_x86_arm", + "os": "android", + "h": 735, + "w": 392 + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "5095a742-1c27-402b-ab6f-66b1bd53383b", + "seatbid": [ + { + "bid": [ + { + "id": "test-request-id", + "impid": "test-imp-id", + "price": 1000, + "nurl": "http://example.com/win/1000", + "adm": "
ad
", + "adomain": [ + "example.com" + ], + "cid": "test-cid", + "crid": "test-crid", + "cat": [ + "IAB13-4" + ], + "w": 300, + "h": 250 + } + ], + "seat": "acc-1876" + } + ], + "bidid": "43ccadf2-8b2e-11eb-b294-de4c49e99ff6", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-request-id", + "impid": "test-imp-id", + "price": 1000, + "nurl": "http://example.com/win/1000", + "adm": "
ad
", + "adomain": [ + "example.com" + ], + "cid": "test-cid", + "crid": "test-crid", + "cat": [ + "IAB13-4" + ], + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/zemanta/zemantatest/supplemental/optional_params.json b/adapters/zemanta/zemantatest/supplemental/optional_params.json new file mode 100644 index 00000000000..a69ceaa0c85 --- /dev/null +++ b/adapters/zemanta/zemantatest/supplemental/optional_params.json @@ -0,0 +1,148 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "publisher": { + "id": "publisher-id", + "name": "publisher-name", + "domain": "publisher-domain.com" + }, + "tagid": "tag-id", + "bcat": ["bad-category"], + "badv": ["bad-advertiser"] + } + } + } + ], + "site": { + "page": "http://example.com" + }, + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36", + "h": 500, + "w": 1000 + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/bid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "tagid": "tag-id", + "ext": { + "bidder": { + "publisher": { + "id": "publisher-id", + "name": "publisher-name", + "domain": "publisher-domain.com" + }, + "tagid": "tag-id", + "bcat": ["bad-category"], + "badv": ["bad-advertiser"] + } + } + } + ], + "bcat": ["bad-category"], + "badv": ["bad-advertiser"], + "site": { + "page": "http://example.com", + "publisher": { + "id": "publisher-id", + "name": "publisher-name", + "domain": "publisher-domain.com" + } + }, + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36", + "h": 500, + "w": 1000 + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "5095a742-1c27-402b-ab6f-66b1bd53383b", + "seatbid": [ + { + "bid": [ + { + "id": "test-request-id", + "impid": "test-imp-id", + "price": 1000, + "nurl": "http://example.com/win/1000", + "adm": "
ad
", + "adomain": [ + "example.com" + ], + "cid": "test-cid", + "crid": "test-crid", + "cat": [ + "IAB13-4" + ], + "w": 300, + "h": 250 + } + ], + "seat": "acc-1876" + } + ], + "bidid": "43ccadf2-8b2e-11eb-b294-de4c49e99ff6", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-request-id", + "impid": "test-imp-id", + "price": 1000, + "nurl": "http://example.com/win/1000", + "adm": "
ad
", + "adomain": [ + "example.com" + ], + "cid": "test-cid", + "crid": "test-crid", + "cat": [ + "IAB13-4" + ], + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/zemanta/zemantatest/supplemental/status_204.json b/adapters/zemanta/zemantatest/supplemental/status_204.json new file mode 100644 index 00000000000..9f668736953 --- /dev/null +++ b/adapters/zemanta/zemantatest/supplemental/status_204.json @@ -0,0 +1,62 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "publisher": { + "id": "publisher-id" + } + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/bid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "publisher": { + "id": "publisher-id" + } + } + } + } + ] + } + }, + "mockResponse": { + "status": 204, + "body": {} + } + } + ], + + "expectedBidResponses": [] +} diff --git a/adapters/zemanta/zemantatest/supplemental/status_400.json b/adapters/zemanta/zemantatest/supplemental/status_400.json new file mode 100644 index 00000000000..441162070d8 --- /dev/null +++ b/adapters/zemanta/zemantatest/supplemental/status_400.json @@ -0,0 +1,67 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "publisher": { + "id": "publisher-id" + } + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/bid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "publisher": { + "id": "publisher-id" + } + } + } + } + ] + } + }, + "mockResponse": { + "status": 400, + "body": {} + } + } + ], + + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 400. Bad request from publisher. Run with request.debug = 1 for more info.", + "comparison": "literal" + } + ] +} diff --git a/adapters/zemanta/zemantatest/supplemental/status_418.json b/adapters/zemanta/zemantatest/supplemental/status_418.json new file mode 100644 index 00000000000..08e26804806 --- /dev/null +++ b/adapters/zemanta/zemantatest/supplemental/status_418.json @@ -0,0 +1,67 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "publisher": { + "id": "publisher-id" + } + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/bid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "publisher": { + "id": "publisher-id" + } + } + } + } + ] + } + }, + "mockResponse": { + "status": 418, + "body": {} + } + } + ], + + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 418. Run with request.debug = 1 for more info.", + "comparison": "literal" + } + ] +} diff --git a/config/config.go b/config/config.go index 7b78fb26bde..bda531f2460 100644 --- a/config/config.go +++ b/config/config.go @@ -649,6 +649,7 @@ func (cfg *Configuration) setDerivedDefaults() { setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderYieldlab, "https://ad.yieldlab.net/mr?t=2&pid=9140838&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dyieldlab%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%25%25YL_UID%25%25") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderYieldmo, "https://ads.yieldmo.com/pbsync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirectUri="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dyieldmo%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderYieldone, "https://y.one.impact-ad.jp/hbs_cs?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirectUri="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dyieldone%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") + setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderZemanta, "http://prebidtest.zemanta.com/usersync/prebidtest?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&cb="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dzemanta%26uid%3D__ZUID__") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderZeroClickFraud, "https://s.0cf.io/sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dzeroclickfraud%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7Buid%7D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderBetween, "https://ads.betweendigital.com/match?bidder_id=pbs&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&callback_url="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dbetween%26gdpr%3D0%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUSER_ID%7D") } @@ -912,6 +913,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.yieldlab.endpoint", "https://ad.yieldlab.net/yp/") v.SetDefault("adapters.yieldmo.endpoint", "https://ads.yieldmo.com/exchange/prebid-server") v.SetDefault("adapters.yieldone.endpoint", "https://y.one.impact-ad.jp/hbs_imp") + v.SetDefault("adapters.zemanta.endpoint", "https://prebidtest.zemanta.com/api/bidder/prebidtest/bid/") v.SetDefault("adapters.zeroclickfraud.endpoint", "http://{{.Host}}/openrtb2?sid={{.SourceId}}") v.SetDefault("max_request_size", 1024*256) diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index 0ea28f8a610..801d4023d02 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -103,6 +103,7 @@ import ( "github.com/prebid/prebid-server/adapters/yieldlab" "github.com/prebid/prebid-server/adapters/yieldmo" "github.com/prebid/prebid-server/adapters/yieldone" + "github.com/prebid/prebid-server/adapters/zemanta" "github.com/prebid/prebid-server/adapters/zeroclickfraud" "github.com/prebid/prebid-server/openrtb_ext" ) @@ -215,6 +216,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderYieldlab: yieldlab.Builder, openrtb_ext.BidderYieldmo: yieldmo.Builder, openrtb_ext.BidderYieldone: yieldone.Builder, + openrtb_ext.BidderZemanta: zemanta.Builder, openrtb_ext.BidderZeroClickFraud: zeroclickfraud.Builder, } } diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 4d95babc988..8cc1bbd9547 100755 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -177,6 +177,7 @@ const ( BidderYieldlab BidderName = "yieldlab" BidderYieldmo BidderName = "yieldmo" BidderYieldone BidderName = "yieldone" + BidderZemanta BidderName = "zemanta" BidderZeroClickFraud BidderName = "zeroclickfraud" ) @@ -287,6 +288,7 @@ func CoreBidderNames() []BidderName { BidderYieldlab, BidderYieldmo, BidderYieldone, + BidderZemanta, BidderZeroClickFraud, } } diff --git a/openrtb_ext/imp_zemanta.go b/openrtb_ext/imp_zemanta.go new file mode 100644 index 00000000000..5baab6d2fd4 --- /dev/null +++ b/openrtb_ext/imp_zemanta.go @@ -0,0 +1,15 @@ +package openrtb_ext + +// ExtImpZemanta defines the contract for bidrequest.imp[i].ext.zemanta +type ExtImpZemanta struct { + Publisher ExtImpZemantaPublisher `json:"publisher"` + TagId string `json:"tagid"` + BCat []string `json:"bcat"` + BAdv []string `json:"badv"` +} + +type ExtImpZemantaPublisher struct { + Id string `json:"id"` + Name string `json:"name"` + Domain string `json:"domain"` +} diff --git a/static/bidder-info/zemanta.yaml b/static/bidder-info/zemanta.yaml new file mode 100644 index 00000000000..e38ec915f49 --- /dev/null +++ b/static/bidder-info/zemanta.yaml @@ -0,0 +1,12 @@ +maintainer: + email: prog-ops-team@outbrain.com +gvlVendorID: 164 +capabilities: + app: + mediaTypes: + - banner + - native + site: + mediaTypes: + - banner + - native diff --git a/static/bidder-params/zemanta.json b/static/bidder-params/zemanta.json new file mode 100644 index 00000000000..b122c7dea4b --- /dev/null +++ b/static/bidder-params/zemanta.json @@ -0,0 +1,40 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Zemanta Adapter Params", + "description": "A schema which validates params accepted by the Zemanta adapter", + + "type": "object", + "properties": { + "publisher": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "domain": { + "type": "string" + } + }, + "required": ["id"] + }, + "tagid": { + "type": "string" + }, + "bcat": { + "type": "array", + "items": { + "type": "string" + } + }, + "badv": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": ["publisher"] +} diff --git a/usersync/usersyncers/syncer.go b/usersync/usersyncers/syncer.go index f01e4e4b917..30c0ebf5f37 100644 --- a/usersync/usersyncers/syncer.go +++ b/usersync/usersyncers/syncer.go @@ -87,6 +87,7 @@ import ( "github.com/prebid/prebid-server/adapters/yieldlab" "github.com/prebid/prebid-server/adapters/yieldmo" "github.com/prebid/prebid-server/adapters/yieldone" + "github.com/prebid/prebid-server/adapters/zemanta" "github.com/prebid/prebid-server/adapters/zeroclickfraud" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" @@ -180,6 +181,7 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync insertIntoMap(cfg, syncers, openrtb_ext.BidderYieldlab, yieldlab.NewYieldlabSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderYieldmo, yieldmo.NewYieldmoSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderYieldone, yieldone.NewYieldoneSyncer) + insertIntoMap(cfg, syncers, openrtb_ext.BidderZemanta, zemanta.NewZemantaSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderZeroClickFraud, zeroclickfraud.NewZeroClickFraudSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderBetween, between.NewBetweenSyncer) diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index a389a007306..325fd9b551c 100755 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -96,6 +96,7 @@ func TestNewSyncerMap(t *testing.T) { string(openrtb_ext.BidderYieldlab): syncConfig, string(openrtb_ext.BidderYieldmo): syncConfig, string(openrtb_ext.BidderYieldone): syncConfig, + string(openrtb_ext.BidderZemanta): syncConfig, string(openrtb_ext.BidderZeroClickFraud): syncConfig, }, }