diff --git a/adapters/mediasquare/mediasquare.go b/adapters/mediasquare/mediasquare.go new file mode 100644 index 0000000000..0c88557caa --- /dev/null +++ b/adapters/mediasquare/mediasquare.go @@ -0,0 +1,124 @@ +package mediasquare + +import ( + "fmt" + "net/http" + + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" +) + +type adapter struct { + endpoint string +} + +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { + return &adapter{ + endpoint: config.Endpoint, + }, nil +} + +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + var ( + requestData []*adapters.RequestData + errs []error + ) + if request == nil || request.Imp == nil { + errs = append(errs, errorWritter(" request", nil, true)) + return nil, errs + } + + msqParams := initMsqParams(request) + msqParams.Test = (request.Test == int8(1)) + for _, imp := range request.Imp { + var ( + bidderExt adapters.ExtImpBidder + msqExt openrtb_ext.ImpExtMediasquare + currentCode = msqParametersCodes{ + AdUnit: imp.TagID, + AuctionId: request.ID, + BidId: imp.ID, + } + ) + + if err := jsonutil.Unmarshal(imp.Ext, &bidderExt); err != nil { + errs = append(errs, errorWritter(" imp[ext]", err, len(imp.Ext) == 0)) + continue + } + if err := jsonutil.Unmarshal(bidderExt.Bidder, &msqExt); err != nil { + errs = append(errs, errorWritter(" imp-bidder[ext]", err, len(bidderExt.Bidder) == 0)) + continue + } + currentCode.Owner = msqExt.Owner + currentCode.Code = msqExt.Code + + if ok := currentCode.setContent(imp); ok { + msqParams.Codes = append(msqParams.Codes, currentCode) + } + } + + req, err := a.makeRequest(request, &msqParams) + if err != nil { + errs = append(errs, err) + } else if req != nil { + requestData = append(requestData, req) + } + return requestData, errs +} + +func (a *adapter) makeRequest(request *openrtb2.BidRequest, msqParams *msqParameters) (requestData *adapters.RequestData, err error) { + var requestJsonBytes []byte + if msqParams == nil { + err = errorWritter(" msqParams", nil, true) + return + } + if requestJsonBytes, err = jsonutil.Marshal(msqParams); err == nil { + var headers http.Header = headerList + requestData = &adapters.RequestData{ + Method: "POST", + Uri: a.endpoint, + Body: requestJsonBytes, + Headers: headers, + ImpIDs: openrtb_ext.GetImpIDs(request.Imp), + } + } else { + err = errorWritter(" jsonutil.Marshal", err, false) + } + + return +} + +func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + var ( + bidderResponse *adapters.BidderResponse + errs []error + ) + if response.StatusCode != http.StatusOK { + switch response.StatusCode { + case http.StatusBadRequest: + errs = []error{&errortypes.BadInput{Message: fmt.Sprintf(" Unexpected status code: %d.", response.StatusCode)}} + default: + errs = []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf(" Unexpected status code: %d. Run with request.debug = 1 for more info.", response.StatusCode), + }} + } + return bidderResponse, errs + } + + var msqResp msqResponse + if err := jsonutil.Unmarshal(response.Body, &msqResp); err != nil { + errs = []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf(" Unexprected status code: %d. Bad server response: %s.", + http.StatusNotAcceptable, err.Error())}, + } + return bidderResponse, errs + } + bidderResponse = adapters.NewBidderResponseWithBidsCapacity(len(request.Imp)) + msqResp.getContent(bidderResponse) + + return bidderResponse, errs +} diff --git a/adapters/mediasquare/mediasquare_test.go b/adapters/mediasquare/mediasquare_test.go new file mode 100644 index 0000000000..3b8b8d4666 --- /dev/null +++ b/adapters/mediasquare/mediasquare_test.go @@ -0,0 +1,44 @@ +package mediasquare + +import ( + "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 TestBidderMediasquare(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderMediasquare, config.Adapter{ + Endpoint: "https://pbs-front.mediasquare.fr/msq_prebid"}, + config.Server{ExternalUrl: "https://pbs-front.mediasquare.fr/msq_prebid", GvlID: 1, DataCenter: "2"}) + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + adapterstest.RunJSONBidderTest(t, "mediasquaretest", bidder) +} + +func TestMakeRequests(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderMediasquare, config.Adapter{ + Endpoint: "https://pbs-front.mediasquare.fr/msq_prebid"}, + config.Server{ExternalUrl: "https://pbs-front.mediasquare.fr/msq_prebid", GvlID: 1, DataCenter: "2"}) + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + // MakeRequests : case request is empty. + resp, errs := bidder.MakeRequests(nil, nil) + expectingErrors := []error{errorWritter(" request", nil, true)} + assert.Equal(t, []*adapters.RequestData(nil), resp, "resp, was supposed to be empty result.") + assert.Equal(t, expectingErrors, errs, "errs, was supposed to be :", expectingErrors) + + // MakeRequests : case request.Imp is empty. + bidResquest := openrtb2.BidRequest{ID: "id-test", Imp: nil} + resp, errs = bidder.MakeRequests(&bidResquest, nil) + expectingErrors = []error{errorWritter(" request", nil, true)} + assert.Equal(t, []*adapters.RequestData(nil), resp, "resp, was supposed to be empty result.") + assert.Equal(t, expectingErrors, errs, "errs, was supposed to be :", expectingErrors) +} diff --git a/adapters/mediasquare/mediasquaretest/exemplary/multi-format.json b/adapters/mediasquare/mediasquaretest/exemplary/multi-format.json new file mode 100644 index 0000000000..52b244342e --- /dev/null +++ b/adapters/mediasquare/mediasquaretest/exemplary/multi-format.json @@ -0,0 +1,501 @@ +{ + "mockBidRequest": { + "id": "70e5672c-515b-406e-967c-fcc2b04de04f", + "imp": [ + { + "id": "2c35e25e-e7d3-41bf-b810-06a449f456b9", + "bidfloor": 1, + "bidfloorcur": "USD", + "banner": { + "w": 970, + "h": 250 + }, + "ext": { + "bidder": { + "owner": "test", + "code": "publishername_atf_desktop_rg_pave" + } + } + }, + { + "id": "2059a3e6-71a3-43ea-8290-b5ceb13d35a8", + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "format": [ + { "w": 300, "h": 250 }, + { "w": 300, "h": 600 }, + { "w": 120, "h": 600 } + ] + }, + "ext": { + "bidder": { + "owner": "test", + "code": "publishername_atf_desktop_rg_pave" + } + } + }, + { + "id": "2c35e25e-e7d3-41bf-b810-06a449f456c9", + "bidfloor": 1, + "bidfloorcur": "USD", + "tagid": "msq_tag_200123_native", + "banner": null, + "video": null, + "native": { + "ext": { + "title": { "required": true, "len": 80 }, + "body": { "required": true }, + "icon": { + "required": false, + "aspect_ratio": { + "min_width": 50, + "min_height": 50, + "ratio_width": 2, + "ratio_height": 3 + } + }, + "image": { + "required": false, + "aspect_ratio": { + "min_width": 300, + "min_height": 200, + "ratio_width": 2, + "ratio_height": 3 + } + }, + "clickUrl": { "required": true }, + "sizes": [ + [970, 250], + [728, 90] + ] + } + }, + "ext": { + "bidder": { + "owner": "test", + "code": "publishername_atf_desktop_rg_pave" + } + } + }, + { + "id": "2c35e25e-e7d3-41bf-b810-06a449f456d9", + "bidfloor": 1, + "tagid": "msq_tag_200123_native", + "banner": null, + "native": null, + "video": { + "mimes": ["video/mp4"], + "minduration": 10, + "maxduration": 23, + "placement": 1, + "w": 800, + "h": 600, + "plcmt": 1, + "ext": { + "context": "context-test", + "linearity": 0, + "playersize": [[800, 600]] + } + }, + "ext": { + "bidder": { + "owner": "test", + "code": "publishername_atf_desktop_rg_video" + } + } + } + ], + "app": { + "content": {}, + "domain": "debug.mediasquare.fr", + "id": "app-id-test", + "name": "debug.mediasquare.fr", + "publisher": { + "id": "MEDIA_SQUARE" + } + }, + "device": { + "devicetype": 1, + "geo": { + "country": "FRA", + "ipservice": 3 + }, + "ip": "92.154.6.0", + "language": "fr", + "ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36" + }, + "regs": { + "gdpr": 1, + "ext": { + "dsa": "mediasquare-test" + } + }, + "user": { + "consent": "there-is-a-real-cs-in-it" + }, + "test": 1 + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://pbs-front.mediasquare.fr/msq_prebid", + "body": { + "codes": [ + { + "adunit": "", + "auctionid": "70e5672c-515b-406e-967c-fcc2b04de04f", + "bidid": "2c35e25e-e7d3-41bf-b810-06a449f456b9", + "code": "publishername_atf_desktop_rg_pave", + "owner": "test", + "mediatypes": { + "banner": { + "sizes": [[970, 250]] + }, + "video": null, + "native": null + }, + "floor": { + "970x250": { + "floor": 1, + "currency": "USD" + } + } + }, + { + "adunit": "", + "auctionid": "70e5672c-515b-406e-967c-fcc2b04de04f", + "bidid": "2059a3e6-71a3-43ea-8290-b5ceb13d35a8", + "code": "publishername_atf_desktop_rg_pave", + "owner": "test", + "mediatypes": { + "banner": { + "sizes": [ + [300, 250], + [300, 600], + [120, 600] + ] + }, + "video": null, + "native": null + }, + "floor": { + "120x600": { + "floor": 0.01, + "currency": "USD" + }, + "300x250": { + "floor": 0.01, + "currency": "USD" + }, + "300x600": { + "floor": 0.01, + "currency": "USD" + } + } + }, + { + "owner": "test", + "code": "publishername_atf_desktop_rg_pave", + "adunit": "msq_tag_200123_native", + "auctionid": "70e5672c-515b-406e-967c-fcc2b04de04f", + "bidid": "2c35e25e-e7d3-41bf-b810-06a449f456c9", + "mediatypes": { + "video": null, + "banner": null, + "native": { + "address": null, + "title": { "required": true, "len": 80 }, + "body": { "required": true }, + "body2": null, + "type": "native", + "cta": null, + "displayUrl": null, + "downloads": null, + "icon": { + "required": false, + "aspect_ratio": { + "min_width": 50, + "min_height": 50, + "ratio_width": 2, + "ratio_height": 3 + } + }, + "image": { + "required": false, + "aspect_ratio": { + "min_width": 300, + "min_height": 200, + "ratio_width": 2, + "ratio_height": 3 + } + }, + "likes": null, + "phone": null, + "price": null, + "privacyIcon": null, + "privacyLink": null, + "rating": null, + "saleprice": null, + "sizes": [ + [970, 250], + [728, 90] + ], + "sponsoredBy": null, + "clickUrl": { "required": true } + } + }, + "floor": { + "*": { "floor": 1, "currency": "USD" }, + "970x250": { "floor": 1, "currency": "USD" }, + "728x90": { "floor": 1, "currency": "USD" } + } + }, + { + "owner": "test", + "code": "publishername_atf_desktop_rg_video", + "adunit": "msq_tag_200123_native", + "auctionid": "70e5672c-515b-406e-967c-fcc2b04de04f", + "bidid": "2c35e25e-e7d3-41bf-b810-06a449f456d9", + "mediatypes": { + "video": { + "mimes": ["video/mp4"], + "minbitrate": null, + "maxbitrate": null, + "minduration": 10, + "maxduration": 23, + "placement": 1, + "linearity": 0, + "w": 800, + "h": 600, + "playbackmethod": null, + "playersize": [[800, 600]], + "api": null, + "boxingallower": null, + "context": "context-test", + "delivery": null, + "plcmt": 1, + "protocols": null, + "skip": null, + "skipafter": null, + "startdelay": null + }, + "banner": null, + "native": null + }, + "floor": { + "*": { "floor": 1 }, + "800x600": { "floor": 1 } + } + } + ], + "gdpr": { + "consent_required": true, + "consent_string": "there-is-a-real-cs-in-it" + }, + "dsa": "mediasquare-test", + "tech": { + "device": { + "geo": { + "ipservice": 3, + "country": "FRA" + }, + "ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36", + "ip": "92.154.6.0", + "devicetype": 1, + "language": "fr" + }, + "app": { + "id": "app-id-test", + "name": "debug.mediasquare.fr", + "domain": "debug.mediasquare.fr", + "publisher": { + "id": "MEDIA_SQUARE" + }, + "content": {} + } + }, + "type": "pbs", + "test": true + }, + "impIDs": [ + "2c35e25e-e7d3-41bf-b810-06a449f456b9", + "2059a3e6-71a3-43ea-8290-b5ceb13d35a8", + "2c35e25e-e7d3-41bf-b810-06a449f456c9", + "2c35e25e-e7d3-41bf-b810-06a449f456d9" + ] + }, + + "mockResponse": { + "status": 200, + "body": { + "infos": { + "version": "1.6.1", + "description": "mediasquare prebid client endpoint" + }, + "cookies": null, + "responses": [ + { + "ad": "\u003c!-- This is an example --\u003e", + "bid_id": "2c35e25e-e7d3-41bf-b810-06a449f456b9", + "bidder": "fakeBidder", + "code": "test/publishername_atf_desktop_rg_pave", + "cpm": 2, + "increment": 2, + "currency": "USD", + "creative_id": "fakeBidder|fakeCreative", + "width": 250, + "net_revenue": true, + "transaction_id": "2c35e25e-e7d3-41bf-b810-06a449f456b9", + "ttl": 20000, + "adomain": ["mediasquare.fr"], + "hasConsent": true + }, + { + "ad": "\u003c!-- This is an example --\u003e", + "bid_id": "2059a3e6-71a3-43ea-8290-b5ceb13d35a8", + "bidder": "fakeBidder", + "code": "test/publishername_atf_desktop_rg_pave", + "cpm": 0.02, + "increment": 0.02, + "currency": "USD", + "creative_id": "fakeBidder|fakeCreative", + "width": 250, + "net_revenue": true, + "transaction_id": "2059a3e6-71a3-43ea-8290-b5ceb13d35a8", + "ttl": 20000, + "adomain": ["mediasquare.fr"], + "hasConsent": true + }, + { + "bid_id": "2c35e25e-e7d3-41bf-b810-06a449f456c9", + "bidder": "fakeBidder", + "code": "test/publishername_atf_desktop_rg_pave", + "cpm": 2, + "increment": 2, + "currency": "USD", + "creative_id": "fakeBidder|fakeCreative", + "net_revenue": true, + "transaction_id": "2c35e25e-e7d3-41bf-b810-06a449f456c9", + "ttl": 20000, + "native": { + "clickUrl": "http: //i.am.a/URL", + "title": "Learn about this awesome thing" + }, + "adomain": ["mediasquare.fr"], + "hasConsent": true + }, + { + "bid_id": "2c35e25e-e7d3-41bf-b810-06a449f456d9", + "bidder": "fakeBidder", + "code": "test/publishername_atf_desktop_rg_video", + "cpm": 2, + "increment": 2, + "currency": "USD", + "creative_id": "fakeBidder|fakeCreative", + "net_revenue": true, + "transaction_id": "2c35e25e-e7d3-41bf-b810-06a449f456d9", + "ttl": 20000, + "video": { + "xml": "Some Xml Vast", + "url": "https://dummy.domain.tv/some_vast" + }, + "adomain": ["mediasquare.fr"], + "hasConsent": true + } + ], + "Calc_cpm": { + "2059a3e6-71a3-43ea-8290-b5ceb13d35a8": { + "TmpCpmMax": 0, + "CpmMax": 0.02 + }, + "2c35e25e-e7d3-41bf-b810-06a449f456b9": { + "TmpCpmMax": 0, + "CpmMax": 2 + }, + "2c35e25e-e7d3-41bf-b810-06a449f456c9": { + "TmpCpmMax": 0, + "CpmMax": 2 + }, + "2c35e25e-e7d3-41bf-b810-06a449f456d9": { + "TmpCpmMax": 0, + "CpmMax": 2 + } + } + } + } + } + ], + "expectedBidResponses": [ + { + "Currency": "USD", + "Bids": [ + { + "Bid": { + "id": "", + "impid": "2c35e25e-e7d3-41bf-b810-06a449f456b9", + "price": 2, + "adm": "\u003c!-- This is an example --\u003e", + "adomain": ["mediasquare.fr"], + "crid": "fakeBidder|fakeCreative", + "w": 250, + "mtype": 1 + }, + "BidMeta": { + "advertiserDomains": ["mediasquare.fr"], + "mediaType": "banner" + }, + "type": "banner" + }, + { + "Bid": { + "id": "", + "impid": "2059a3e6-71a3-43ea-8290-b5ceb13d35a8", + "price": 0.02, + "adm": "\u003c!-- This is an example --\u003e", + "adomain": ["mediasquare.fr"], + "crid": "fakeBidder|fakeCreative", + "w": 250, + "mtype": 1 + }, + "BidMeta": { + "advertiserDomains": ["mediasquare.fr"], + "mediaType": "banner" + }, + "type": "banner" + }, + { + "Bid": { + "id": "", + "impid": "2c35e25e-e7d3-41bf-b810-06a449f456c9", + "price": 2, + "adomain": ["mediasquare.fr"], + "crid": "fakeBidder|fakeCreative", + "mtype": 4 + }, + "BidMeta": { + "advertiserDomains": ["mediasquare.fr"], + "mediaType": "native" + }, + "type": "native" + }, + { + "Bid": { + "id": "", + "impid": "2c35e25e-e7d3-41bf-b810-06a449f456d9", + "price": 2, + "adomain": ["mediasquare.fr"], + "crid": "fakeBidder|fakeCreative", + "mtype": 2 + }, + "BidMeta": { + "advertiserDomains": ["mediasquare.fr"], + "mediaType": "video" + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/mediasquare/mediasquaretest/exemplary/simple-banner.json b/adapters/mediasquare/mediasquaretest/exemplary/simple-banner.json new file mode 100644 index 0000000000..4f8030b3ff --- /dev/null +++ b/adapters/mediasquare/mediasquaretest/exemplary/simple-banner.json @@ -0,0 +1,262 @@ +{ + "mockBidRequest": { + "id": "70e5672c-515b-406e-967c-fcc2b04de04f", + "imp": [ + { + "id": "2c35e25e-e7d3-41bf-b810-06a449f456b9", + "bidfloor": 1, + "bidfloorcur": "USD", + "banner": { + "w": 970, + "h": 250 + }, + "ext": { + "bidder": { + "owner": "test", + "code": "publishername_atf_desktop_rg_pave" + } + } + }, + { + "id": "2059a3e6-71a3-43ea-8290-b5ceb13d35a8", + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "format": [ + { "w": 300, "h": 250 }, + { "w": 300, "h": 600 }, + { "w": 120, "h": 600 } + ] + }, + "ext": { + "bidder": { + "owner": "test", + "code": "publishername_atf_desktop_rg_pave" + } + } + } + ], + "app": { + "content": {}, + "domain": "debug.mediasquare.fr", + "id": "app-id-test", + "name": "debug.mediasquare.fr", + "publisher": { + "id": "MEDIA_SQUARE" + } + }, + "device": { + "devicetype": 1, + "geo": { + "country": "FRA", + "ipservice": 3 + }, + "ip": "92.154.6.0", + "language": "fr", + "ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36" + }, + "regs": { + "gdpr": 1, + "ext": { + "dsa": "mediasquare-test" + } + }, + "user": { + "consent": "there-is-a-real-cs-in-it" + }, + "test": 1 + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://pbs-front.mediasquare.fr/msq_prebid", + "body": { + "codes": [ + { + "adunit": "", + "auctionid": "70e5672c-515b-406e-967c-fcc2b04de04f", + "bidid": "2c35e25e-e7d3-41bf-b810-06a449f456b9", + "code": "publishername_atf_desktop_rg_pave", + "owner": "test", + "mediatypes": { + "banner": { + "sizes": [[970, 250]] + }, + "video": null, + "native": null + }, + "floor": { + "970x250": { + "floor": 1, + "currency": "USD" + } + } + }, + { + "adunit": "", + "auctionid": "70e5672c-515b-406e-967c-fcc2b04de04f", + "bidid": "2059a3e6-71a3-43ea-8290-b5ceb13d35a8", + "code": "publishername_atf_desktop_rg_pave", + "owner": "test", + "mediatypes": { + "banner": { + "sizes": [ + [300, 250], + [300, 600], + [120, 600] + ] + }, + "video": null, + "native": null + }, + "floor": { + "120x600": { + "floor": 0.01, + "currency": "USD" + }, + "300x250": { + "floor": 0.01, + "currency": "USD" + }, + "300x600": { + "floor": 0.01, + "currency": "USD" + } + } + } + ], + "gdpr": { + "consent_required": true, + "consent_string": "there-is-a-real-cs-in-it" + }, + "dsa": "mediasquare-test", + "tech": { + "device": { + "geo": { + "ipservice": 3, + "country": "FRA" + }, + "ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36", + "ip": "92.154.6.0", + "devicetype": 1, + "language": "fr" + }, + "app": { + "id": "app-id-test", + "name": "debug.mediasquare.fr", + "domain": "debug.mediasquare.fr", + "publisher": { + "id": "MEDIA_SQUARE" + }, + "content": {} + } + }, + "type": "pbs", + "test": true + }, + "impIDs": [ + "2c35e25e-e7d3-41bf-b810-06a449f456b9", + "2059a3e6-71a3-43ea-8290-b5ceb13d35a8" + ] + }, + + "mockResponse": { + "status": 200, + "body": { + "infos": { + "version": "1.6.1", + "description": "mediasquare prebid client endpoint" + }, + "cookies": null, + "responses": [ + { + "ad": "\u003c!-- This is an example --\u003e", + "bid_id": "2c35e25e-e7d3-41bf-b810-06a449f456b9", + "bidder": "fakeBidder", + "code": "test/publishername_atf_desktop_rg_pave", + "cpm": 2, + "increment": 2, + "currency": "USD", + "creative_id": "fakeBidder|fakeCreative", + "width": 250, + "net_revenue": true, + "transaction_id": "2c35e25e-e7d3-41bf-b810-06a449f456b9", + "ttl": 20000, + "adomain": ["mediasquare.fr"], + "dsa": "dsa-mediasquare", + "hasConsent": true + }, + { + "ad": "\u003c!-- This is an example --\u003e", + "bid_id": "2059a3e6-71a3-43ea-8290-b5ceb13d35a8", + "bidder": "fakeBidder", + "code": "test/publishername_atf_desktop_rg_pave", + "cpm": 0.02, + "increment": 0.02, + "currency": "USD", + "creative_id": "fakeBidder|fakeCreative", + "width": 250, + "net_revenue": true, + "transaction_id": "2059a3e6-71a3-43ea-8290-b5ceb13d35a8", + "ttl": 20000, + "adomain": ["mediasquare.fr"], + "hasConsent": true + } + ], + "Calc_cpm": { + "2059a3e6-71a3-43ea-8290-b5ceb13d35a8": { + "TmpCpmMax": 0, + "CpmMax": 0.02 + }, + "2c35e25e-e7d3-41bf-b810-06a449f456b9": { + "TmpCpmMax": 0, + "CpmMax": 2 + } + } + } + } + } + ], + "expectedBidResponses": [ + { + "Currency": "USD", + "Bids": [ + { + "Bid": { + "id": "", + "impid": "2c35e25e-e7d3-41bf-b810-06a449f456b9", + "price": 2, + "adm": "\u003c!-- This is an example --\u003e", + "adomain": ["mediasquare.fr"], + "crid": "fakeBidder|fakeCreative", + "w": 250, + "mtype": 1 + }, + "BidMeta": { + "advertiserDomains": ["mediasquare.fr"], + "mediaType": "banner" + }, + "type": "banner" + }, + { + "Bid": { + "id": "", + "impid": "2059a3e6-71a3-43ea-8290-b5ceb13d35a8", + "price": 0.02, + "adm": "\u003c!-- This is an example --\u003e", + "adomain": ["mediasquare.fr"], + "crid": "fakeBidder|fakeCreative", + "w": 250, + "mtype": 1 + }, + "BidMeta": { + "advertiserDomains": ["mediasquare.fr"], + "mediaType": "banner" + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/mediasquare/mediasquaretest/exemplary/simple-native.json b/adapters/mediasquare/mediasquaretest/exemplary/simple-native.json new file mode 100644 index 0000000000..611fb7f96b --- /dev/null +++ b/adapters/mediasquare/mediasquaretest/exemplary/simple-native.json @@ -0,0 +1,233 @@ +{ + "mockBidRequest": { + "id": "70e5672c-515b-406e-967c-fcc2b04de04f", + "imp": [ + { + "id": "2c35e25e-e7d3-41bf-b810-06a449f456b9", + "bidfloor": 1, + "bidfloorcur": "USD", + "tagid": "msq_tag_200123_native", + "banner": null, + "video": null, + "native": { + "ext": { + "title": { "required": true, "len": 80 }, + "body": { "required": true }, + "icon": { + "required": false, + "aspect_ratio": { + "min_width": 50, + "min_height": 50, + "ratio_width": 2, + "ratio_height": 3 + } + }, + "image": { + "required": false, + "aspect_ratio": { + "min_width": 300, + "min_height": 200, + "ratio_width": 2, + "ratio_height": 3 + } + }, + "clickUrl": { "required": true }, + "sizes": [ + [970, 250], + [728, 90] + ] + } + }, + "ext": { + "bidder": { + "owner": "test", + "code": "publishername_atf_desktop_rg_pave" + } + } + } + ], + "app": { + "content": {}, + "domain": "debug.mediasquare.fr", + "id": "app-id-test", + "name": "debug.mediasquare.fr", + "publisher": { + "id": "MEDIA_SQUARE" + } + }, + "device": { + "devicetype": 1, + "geo": { + "country": "FRA", + "ipservice": 3 + }, + "ip": "92.154.6.0", + "language": "fr", + "ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36" + }, + "regs": { + "gdpr": 1, + "ext": { + "dsa": "mediasquare-test" + } + }, + "user": { + "consent": "there-is-a-real-cs-in-it" + }, + "test": 1 + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://pbs-front.mediasquare.fr/msq_prebid", + "body": { + "codes": [ + { + "owner": "test", + "code": "publishername_atf_desktop_rg_pave", + "adunit": "msq_tag_200123_native", + "auctionid": "70e5672c-515b-406e-967c-fcc2b04de04f", + "bidid": "2c35e25e-e7d3-41bf-b810-06a449f456b9", + "mediatypes": { + "video": null, + "banner": null, + "native": { + "address": null, + "title": { "required": true, "len": 80 }, + "body": { "required": true }, + "body2": null, + "type": "native", + "cta": null, + "displayUrl": null, + "downloads": null, + "icon": { + "required": false, + "aspect_ratio": { + "min_width": 50, + "min_height": 50, + "ratio_width": 2, + "ratio_height": 3 + } + }, + "image": { + "required": false, + "aspect_ratio": { + "min_width": 300, + "min_height": 200, + "ratio_width": 2, + "ratio_height": 3 + } + }, + "likes": null, + "phone": null, + "price": null, + "privacyIcon": null, + "privacyLink": null, + "rating": null, + "saleprice": null, + "sizes": [ + [970, 250], + [728, 90] + ], + "sponsoredBy": null, + "clickUrl": { "required": true } + } + }, + "floor": { + "*": { "floor": 1, "currency": "USD" }, + "970x250": { "floor": 1, "currency": "USD" }, + "728x90": { "floor": 1, "currency": "USD" } + } + } + ], + "gdpr": { + "consent_string": "there-is-a-real-cs-in-it", + "consent_required": true + }, + "type": "pbs", + "dsa": "mediasquare-test", + "tech": { + "device": { + "geo": { + "ipservice": 3, + "country": "FRA" + }, + "ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36", + "ip": "92.154.6.0", + "devicetype": 1, + "language": "fr" + }, + "app": { + "id": "app-id-test", + "name": "debug.mediasquare.fr", + "domain": "debug.mediasquare.fr", + "publisher": { + "id": "MEDIA_SQUARE" + }, + "content": {} + } + }, + "test": true + }, + "impIDs": ["2c35e25e-e7d3-41bf-b810-06a449f456b9"] + }, + + "mockResponse": { + "status": 200, + "body": { + "infos": { + "version": "1.6.1", + "description": "mediasquare prebid client endpoint" + }, + "cookies": null, + "responses": [ + { + "bid_id": "4e2d4580c1da1", + "bidder": "fakeBidder", + "code": "test/publishername_atf_desktop_rg_pave", + "cpm": 1, + "increment": 1, + "currency": "USD", + "creative_id": "fakeBidder|fakeCreative", + "net_revenue": true, + "transaction_id": "4e2d4580c1da1", + "ttl": 20000, + "native": { + "clickUrl": "http: //i.am.a/URL", + "title": "Learn about this awesome thing" + }, + "adomain": ["mediasquare.fr"], + "hasConsent": true + } + ], + "Calc_cpm": { "4e2d4580c1da1": { "TmpCpmMax": 0, "CpmMax": 1 } } + } + } + } + ], + "expectedBidResponses": [ + { + "Currency": "USD", + "Bids": [ + { + "bid": { + "id": "", + "impid": "4e2d4580c1da1", + "price": 1, + "adomain": ["mediasquare.fr"], + "crid": "fakeBidder|fakeCreative", + "mtype": 4 + }, + "bidmeta": { + "advertiserDomains": ["mediasquare.fr"], + "mediaType": "native" + }, + "type": "native", + "bidvideo": null, + "dealpriority": 0 + } + ] + } + ] +} diff --git a/adapters/mediasquare/mediasquaretest/exemplary/simple-video.json b/adapters/mediasquare/mediasquaretest/exemplary/simple-video.json new file mode 100644 index 0000000000..15a249764a --- /dev/null +++ b/adapters/mediasquare/mediasquaretest/exemplary/simple-video.json @@ -0,0 +1,202 @@ +{ + "mockBidRequest": { + "id": "70e5672c-515b-406e-967c-fcc2b04de04f", + "imp": [ + { + "id": "2c35e25e-e7d3-41bf-b810-06a449f456b9", + "bidfloor": 1, + "tagid": "msq_tag_200123_native", + "banner": null, + "native": null, + "video": { + "mimes": ["video/mp4"], + "minduration": 10, + "maxduration": 23, + "placement": 1, + "w": 800, + "h": 600, + "plcmt": 1, + "ext": { + "context": "context-test", + "linearity": 0, + "playersize": [[800, 600]] + } + }, + "ext": { + "bidder": { + "owner": "test", + "code": "publishername_atf_desktop_rg_video" + } + } + } + ], + "app": { + "content": {}, + "domain": "debug.mediasquare.fr", + "id": "app-id-test", + "name": "debug.mediasquare.fr", + "publisher": { + "id": "MEDIA_SQUARE" + } + }, + "device": { + "devicetype": 1, + "geo": { + "country": "FRA", + "ipservice": 3 + }, + "ip": "92.154.6.0", + "language": "fr", + "ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36" + }, + "regs": { + "gdpr": 1, + "ext": { + "dsa": "mediasquare-test" + } + }, + "user": { + "consent": "there-is-a-real-cs-in-it" + }, + "test": 1 + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://pbs-front.mediasquare.fr/msq_prebid", + "body": { + "codes": [ + { + "owner": "test", + "code": "publishername_atf_desktop_rg_video", + "adunit": "msq_tag_200123_native", + "auctionid": "70e5672c-515b-406e-967c-fcc2b04de04f", + "bidid": "2c35e25e-e7d3-41bf-b810-06a449f456b9", + "mediatypes": { + "video": { + "mimes": ["video/mp4"], + "minbitrate": null, + "maxbitrate": null, + "minduration": 10, + "maxduration": 23, + "placement": 1, + "linearity": 0, + "w": 800, + "h": 600, + "playbackmethod": null, + "playersize": [[800, 600]], + "api": null, + "boxingallower": null, + "context": "context-test", + "delivery": null, + "plcmt": 1, + "protocols": null, + "skip": null, + "skipafter": null, + "startdelay": null + }, + "banner": null, + "native": null + }, + "floor": { + "*": { "floor": 1 }, + "800x600": { "floor": 1 } + } + } + ], + "gdpr": { + "consent_string": "there-is-a-real-cs-in-it", + "consent_required": true + }, + "type": "pbs", + "dsa": "mediasquare-test", + "tech": { + "device": { + "geo": { + "ipservice": 3, + "country": "FRA" + }, + "ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36", + "ip": "92.154.6.0", + "devicetype": 1, + "language": "fr" + }, + "app": { + "id": "app-id-test", + "name": "debug.mediasquare.fr", + "domain": "debug.mediasquare.fr", + "publisher": { + "id": "MEDIA_SQUARE" + }, + "content": {} + } + }, + "test": true + }, + "impIDs": ["2c35e25e-e7d3-41bf-b810-06a449f456b9"] + }, + + "mockResponse": { + "status": 200, + "body": { + "infos": { + "version": "1.6.1", + "description": "mediasquare prebid client endpoint" + }, + "cookies": null, + "responses": [ + { + "bid_id": "4e2d4580c1da1", + "bidder": "fakeBidder", + "code": "test/publishername_atf_desktop_rg_pave", + "cpm": 1, + "increment": 1, + "currency": "USD", + "creative_id": "fakeBidder|fakeCreative", + "net_revenue": true, + "transaction_id": "4e2d4580c1da1", + "ttl": 20000, + "video": { + "xml": "Some Xml Vast", + "url": "https://dummy.domain.tv/some_vast" + }, + "adomain": ["mediasquare.fr"], + "dsa": { + "behalf": "dsa-test-behalf", + "paid": "dsa-test-paid" + }, + "hasConsent": true + } + ], + "Calc_cpm": { "4e2d4580c1da1": { "TmpCpmMax": 0, "CpmMax": 1 } } + } + } + } + ], + "expectedBidResponses": [ + { + "Currency": "USD", + "Bids": [ + { + "bid": { + "id": "", + "impid": "4e2d4580c1da1", + "price": 1, + "adomain": ["mediasquare.fr"], + "crid": "fakeBidder|fakeCreative", + "mtype": 2, + "ext": { + "dsa": { "behalf": "dsa-test-behalf", "paid": "dsa-test-paid" } + } + }, + "meta": { + "advertiserDomains": ["mediasquare.fr"], + "mediaType": "video" + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/mediasquare/mediasquaretest/supplemental/no-valid-imp-ext.json b/adapters/mediasquare/mediasquaretest/supplemental/no-valid-imp-ext.json new file mode 100644 index 0000000000..f550121b3a --- /dev/null +++ b/adapters/mediasquare/mediasquaretest/supplemental/no-valid-imp-ext.json @@ -0,0 +1,128 @@ +{ + "mockBidRequest": { + "id": "id-ok", + "imp": [ + { "id": "0" }, + { "id": "1", "ext": { "id-1": "content-1" } }, + { "id": "-42", "ext": { "prebid": -42 } }, + { "id": "-1", "ext": { "bidder": {} } }, + { + "id": "-0", + "native": { "request": "" }, + "ext": { "bidder": { "owner": "test", "code": 0 } } + }, + { + "id": "42", + "native": { "request": "" }, + "ext": { + "bidder": { + "owner": "test", + "code": "publishername_atf_desktop_rg_pave" + } + } + } + ], + "test": 1 + }, + "expectedMakeRequestsErrors": [ + { + "value": " imp[ext]: is empty.", + "comparison": "literal" + }, + { + "value": " imp-bidder[ext]: is empty.", + "comparison": "literal" + }, + { + "value": " imp[ext]: cannot unmarshal adapters.ExtImpBidder.Prebid: expect { or n, but found -", + "comparison": "literal" + }, + { + "value": " imp-bidder[ext]: cannot unmarshal openrtb_ext.ImpExtMediasquare.Code: expects \" or n, but found 0", + "comparison": "literal" + } + ], + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://pbs-front.mediasquare.fr/msq_prebid", + "body": { + "codes": [ + { + "adunit": "", + "auctionid": "id-ok", + "bidid": "42", + "code": "publishername_atf_desktop_rg_pave", + "owner": "test", + "mediatypes": { + "banner": null, + "video": null, + "native": { + "title": null, + "icon": null, + "image": null, + "clickUrl": null, + "displayUrl": null, + "privacyLink": null, + "privacyIcon": null, + "cta": null, + "rating": null, + "downloads": null, + "likes": null, + "price": null, + "saleprice": null, + "address": null, + "phone": null, + "body": null, + "body2": null, + "sponsoredBy": null, + "sizes": null, + "type": "native" + } + }, + "floor": { "*": {} } + } + ], + "gdpr": { "consent_required": false, "consent_string": "" }, + "type": "pbs", + "dsa": "", + "tech": { "device": null, "app": null }, + "test": true + }, + "impIDs": ["0", "1", "-42", "-1", "-0", "42"] + }, + + "mockResponse": { + "status": 200, + "body": { + "id": "id-ok", + "seatbid": [ + { + "seat": "mediasquare", + "bid": [ + { + "id": "42", + "impid": "1", + "price": 1.5, + "adm": "some-test-ad", + "crid": "test-crid", + "h": 50, + "w": 320, + "mtype": 1 + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [] + } + ] +} diff --git a/adapters/mediasquare/mediasquaretest/supplemental/no-valid-response.json b/adapters/mediasquare/mediasquaretest/supplemental/no-valid-response.json new file mode 100644 index 0000000000..3273618bd9 --- /dev/null +++ b/adapters/mediasquare/mediasquaretest/supplemental/no-valid-response.json @@ -0,0 +1,88 @@ +{ + "mockBidRequest": { + "imp": [ + { + "id": "2c35e25e-e7d3-41bf-b810-06a449f456b9", + "bidfloor": 1, + "tagid": "msq_tag_200123_native", + "banner": null, + "native": null, + "video": { + "mimes": ["video/mp4"], + "minduration": 10, + "maxduration": 23, + "placement": 1, + "w": 800, + "h": 600, + "plcmt": 1, + "ext": { + "context": "context-test", + "linearity": 0, + "playersize": [[800, 600]] + } + }, + "ext": null + } + ], + "app": null, + "device": null, + "regs": { + "gdpr": null, + "ext": { + "dsa": null + } + }, + "user": { + "ext": { + "consent": null + } + }, + "test": 1 + }, + "expectedMakeRequestsErrors": [ + { + "value": " imp-bidder[ext]: is empty.", + "comparison": "literal" + } + ], + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://pbs-front.mediasquare.fr/msq_prebid", + "body": { + "codes": null, + "gdpr": { + "consent_string": "", + "consent_required": false + }, + "type": "pbs", + "dsa": "", + "tech": { + "device": null, + "app": null + }, + "test": true + }, + "impIDs": ["2c35e25e-e7d3-41bf-b810-06a449f456b9"] + }, + + "mockResponse": { + "status": 200, + "body": { + "infos": { + "version": "42", + "description": "test-description", + "hostname": null + }, + "responses": "lol" + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": " Unexprected status code: 406. Bad server response: cannot unmarshal mediasquare.msqResponse.Responses: decode slice: expect [ or n, but found \".", + "comparison": "literal" + } + ] +} diff --git a/adapters/mediasquare/mediasquaretest/supplemental/status-400.json b/adapters/mediasquare/mediasquaretest/supplemental/status-400.json new file mode 100644 index 0000000000..fca42ab512 --- /dev/null +++ b/adapters/mediasquare/mediasquaretest/supplemental/status-400.json @@ -0,0 +1,153 @@ +{ + "mockBidRequest": { + "id": "70e5672c-515b-406e-967c-fcc2b04de04f", + "imp": [ + { + "id": "2c35e25e-e7d3-41bf-b810-06a449f456b9", + "bidfloor": 1, + "tagid": "msq_tag_200123_native", + "banner": null, + "native": null, + "video": { + "mimes": ["video/mp4"], + "minduration": 10, + "maxduration": 23, + "placement": 1, + "w": 800, + "h": 600, + "plcmt": 1, + "ext": { + "context": "context-test", + "linearity": 0, + "playersize": [[800, 600]] + } + }, + "ext": { + "bidder": { + "owner": "test", + "code": "publishername_atf_desktop_rg_video" + } + } + } + ], + "app": { + "content": {}, + "domain": "debug.mediasquare.fr", + "id": "app-id-test", + "name": "debug.mediasquare.fr", + "publisher": { + "id": "MEDIA_SQUARE" + } + }, + "device": { + "devicetype": 1, + "geo": { + "country": "FRA", + "ipservice": 3 + }, + "ip": "92.154.6.0", + "language": "fr", + "ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36" + }, + "regs": { + "gdpr": 1, + "ext": { + "dsa": null + } + }, + "user": { + "ext": { + "consent": "consent-covering" + } + }, + "test": 1 + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://pbs-front.mediasquare.fr/msq_prebid", + "body": { + "codes": [ + { + "owner": "test", + "code": "publishername_atf_desktop_rg_video", + "adunit": "msq_tag_200123_native", + "auctionid": "70e5672c-515b-406e-967c-fcc2b04de04f", + "bidid": "2c35e25e-e7d3-41bf-b810-06a449f456b9", + "mediatypes": { + "video": { + "mimes": ["video/mp4"], + "minbitrate": null, + "maxbitrate": null, + "minduration": 10, + "maxduration": 23, + "placement": 1, + "linearity": 0, + "w": 800, + "h": 600, + "playbackmethod": null, + "playersize": [[800, 600]], + "api": null, + "boxingallower": null, + "context": "context-test", + "delivery": null, + "plcmt": 1, + "protocols": null, + "skip": null, + "skipafter": null, + "startdelay": null + }, + "banner": null, + "native": null + }, + "floor": { + "*": { "floor": 1 }, + "800x600": { "floor": 1 } + } + } + ], + "gdpr": { + "consent_string": "consent-covering", + "consent_required": true + }, + "type": "pbs", + "dsa": "", + "tech": { + "device": { + "geo": { + "ipservice": 3, + "country": "FRA" + }, + "ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36", + "ip": "92.154.6.0", + "devicetype": 1, + "language": "fr" + }, + "app": { + "id": "app-id-test", + "name": "debug.mediasquare.fr", + "domain": "debug.mediasquare.fr", + "publisher": { + "id": "MEDIA_SQUARE" + }, + "content": {} + } + }, + "test": true + }, + "impIDs": ["2c35e25e-e7d3-41bf-b810-06a449f456b9"] + }, + "mockResponse": { + "status": 400, + "body": null + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": " Unexpected status code: 400.", + "comparison": "literal" + } + ] +} diff --git a/adapters/mediasquare/mediasquaretest/supplemental/status-not-200.json b/adapters/mediasquare/mediasquaretest/supplemental/status-not-200.json new file mode 100644 index 0000000000..b24da988fd --- /dev/null +++ b/adapters/mediasquare/mediasquaretest/supplemental/status-not-200.json @@ -0,0 +1,154 @@ +{ + "mockBidRequest": { + "id": "70e5672c-515b-406e-967c-fcc2b04de04f", + "imp": [ + { + "id": "2c35e25e-e7d3-41bf-b810-06a449f456b9", + "bidfloor": 1, + "tagid": "msq_tag_200123_native", + "banner": null, + "native": null, + "video": { + "mimes": ["video/mp4"], + "minduration": 10, + "maxduration": 23, + "placement": 1, + "w": 800, + "h": 600, + "plcmt": 1, + "ext": { + "context": "context-test", + "linearity": 0, + "playersize": [[800, 600]] + } + }, + "ext": { + "bidder": { + "owner": "test", + "code": "publishername_atf_desktop_rg_video" + } + } + } + ], + "app": { + "content": {}, + "domain": "debug.mediasquare.fr", + "id": "app-id-test", + "name": "debug.mediasquare.fr", + "publisher": { + "id": "MEDIA_SQUARE" + } + }, + "device": { + "devicetype": 1, + "geo": { + "country": "FRA", + "ipservice": 3 + }, + "ip": "92.154.6.0", + "language": "fr", + "ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36" + }, + "regs": { + "gdpr": 1, + "ext": { + "dsa": null + } + }, + "user": { + "ext": { + "gdpr": "consent-covering" + } + }, + "test": 1 + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://pbs-front.mediasquare.fr/msq_prebid", + "body": { + "codes": [ + { + "owner": "test", + "code": "publishername_atf_desktop_rg_video", + "adunit": "msq_tag_200123_native", + "auctionid": "70e5672c-515b-406e-967c-fcc2b04de04f", + "bidid": "2c35e25e-e7d3-41bf-b810-06a449f456b9", + "mediatypes": { + "video": { + "mimes": ["video/mp4"], + "minbitrate": null, + "maxbitrate": null, + "minduration": 10, + "maxduration": 23, + "placement": 1, + "linearity": 0, + "w": 800, + "h": 600, + "playbackmethod": null, + "playersize": [[800, 600]], + "api": null, + "boxingallower": null, + "context": "context-test", + "delivery": null, + "plcmt": 1, + "protocols": null, + "skip": null, + "skipafter": null, + "startdelay": null + }, + "banner": null, + "native": null + }, + "floor": { + "*": { "floor": 1 }, + "800x600": { "floor": 1 } + } + } + ], + "gdpr": { + "consent_string": "consent-covering", + "consent_required": true + }, + "type": "pbs", + "dsa": "", + "tech": { + "device": { + "geo": { + "ipservice": 3, + "country": "FRA" + }, + "ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36", + "ip": "92.154.6.0", + "devicetype": 1, + "language": "fr" + }, + "app": { + "id": "app-id-test", + "name": "debug.mediasquare.fr", + "domain": "debug.mediasquare.fr", + "publisher": { + "id": "MEDIA_SQUARE" + }, + "content": {} + } + }, + "test": true + }, + "impIDs": ["2c35e25e-e7d3-41bf-b810-06a449f456b9"] + }, + + "mockResponse": { + "status": 42, + "body": null + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": " Unexpected status code: 42. Run with request.debug = 1 for more info.", + "comparison": "literal" + } + ] +} diff --git a/adapters/mediasquare/params_test.go b/adapters/mediasquare/params_test.go new file mode 100644 index 0000000000..c9d2d5fe01 --- /dev/null +++ b/adapters/mediasquare/params_test.go @@ -0,0 +1,51 @@ +package mediasquare + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/v3/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.BidderMediasquare, 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.BidderMediasquare, json.RawMessage(p)); err == nil { + t.Errorf("Schema allowed invalid params: %s", p) + } + } +} + +var validParams = []string{ + `{"owner":"owner-test", "code": "code-test"}`, +} + +var invalidParams = []string{ + `{"owner":"owner-test", "code": 42}`, + `{"owner":"owner-test", "code": nil}`, + `{"owner":"owner-test", "code": ""}`, + `{"owner": 42, "code": "code-test"}`, + `{"owner": nil, "code": "code-test"}`, + `{"owner": "", "code": "code-test"}`, + `nil`, + ``, + `[]`, + `true`, +} diff --git a/adapters/mediasquare/parsers.go b/adapters/mediasquare/parsers.go new file mode 100644 index 0000000000..dd01b73efa --- /dev/null +++ b/adapters/mediasquare/parsers.go @@ -0,0 +1,84 @@ +package mediasquare + +import ( + "fmt" + + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/util/jsonutil" +) + +// parserDSA: Struct used to extracts dsa content of a jsonutil. +type parserDSA struct { + DSA interface{} `json:"dsa,omitempty"` +} + +// setContent: Unmarshal a []byte into the parserDSA struct. +func (parser *parserDSA) setContent(extJsonBytes []byte) error { + if len(extJsonBytes) > 0 { + if err := jsonutil.Unmarshal(extJsonBytes, parser); err != nil { + return errorWritter(" extJsonBytes", err, false) + } + return nil + } + return errorWritter(" extJsonBytes", nil, true) +} + +// getValue: Returns the DSA value as a string, defaultly returns empty-string. +func (parser parserDSA) getValue(request *openrtb2.BidRequest) (dsa string) { + if request == nil || request.Regs == nil { + return + } + parser.setContent(request.Regs.Ext) + if parser.DSA != nil { + dsa = fmt.Sprint(parser.DSA) + } + return +} + +// parserGDPR: Struct used to extract pair of GDPR/Consent of a jsonutil. +type parserGDPR struct { + GDPR interface{} `json:"gdpr,omitempty"` + Consent interface{} `json:"consent,omitempty"` +} + +// setContent: Unmarshal a []byte into the parserGDPR struct. +func (parser *parserGDPR) setContent(extJsonBytes []byte) error { + if len(extJsonBytes) > 0 { + if err := jsonutil.Unmarshal(extJsonBytes, parser); err != nil { + return errorWritter(" extJsonBytes", err, false) + } + return nil + } + return errorWritter(" extJsonBytes", nil, true) +} + +// value: Returns the consent or GDPR-string depending of the parserGDPR content, defaulty return empty-string. +func (parser *parserGDPR) value() (gdpr string) { + switch { + case parser.Consent != nil: + gdpr = fmt.Sprint(parser.Consent) + case parser.GDPR != nil: + gdpr = fmt.Sprint(parser.GDPR) + } + return +} + +// getValue: Returns the consent or GDPR-string depending on the openrtb2.User content, defaultly returns empty-string. +func (parser parserGDPR) getValue(field string, request *openrtb2.BidRequest) (gdpr string) { + if request != nil { + switch { + case field == "consent_requirement" && request.Regs != nil: + gdpr = "false" + if ptrInt8ToBool(request.Regs.GDPR) { + gdpr = "true" + } + case field == "consent_string" && request.User != nil: + gdpr = request.User.Consent + if len(gdpr) <= 0 { + parser.setContent(request.User.Ext) + gdpr = parser.value() + } + } + } + return +} diff --git a/adapters/mediasquare/structs.go b/adapters/mediasquare/structs.go new file mode 100644 index 0000000000..93af4d0bb5 --- /dev/null +++ b/adapters/mediasquare/structs.go @@ -0,0 +1,306 @@ +package mediasquare + +import ( + "fmt" + + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/util/jsonutil" +) + +// msqResponse: Bid-Response sent by mediasquare. +type msqResponse struct { + Infos struct { + Version string `json:"version"` + Description string `json:"description"` + Hostname string `json:"hostname,omitempty"` + } `json:"infos"` + Responses []msqResponseBids `json:"responses"` +} + +// msqParameters: Bid-Request sent to mediasquare. +type msqParameters struct { + Codes []msqParametersCodes `json:"codes"` + Gdpr struct { + ConsentRequired bool `json:"consent_required"` + ConsentString string `json:"consent_string"` + } `json:"gdpr"` + Type string `json:"type"` + DSA interface{} `json:"dsa,omitempty"` + Support msqSupport `json:"tech"` + Test bool `json:"test"` +} + +type msqResponseBidsVideo struct { + Xml string `json:"xml"` + Url string `json:"url"` +} + +type nativeResponseImg struct { + Url string `json:"url"` + Width *int `json:"width,omitempty"` + Height *int `json:"height,omitempty"` +} + +type msqResponseBidsNative struct { + ClickUrl string `json:"clickUrl,omitempty"` + ClickTrackers []string `json:"clickTrackers,omitempty"` + ImpressionTrackers []string `json:"impressionTrackers,omitempty"` + JavascriptTrackers []string `json:"javascriptTrackers,omitempty"` + Privacy *string `json:"privacy,omitempty"` + Title *string `json:"title,omitempty"` + Icon *nativeResponseImg `json:"icon,omitempty"` + Image *nativeResponseImg `json:"image,omitempty"` + Cta *string `json:"cta,omitempty"` + Rating *string `json:"rating,omitempty"` + Downloads *string `json:"downloads,omitempty"` + Likes *string `json:"likes,omitempty"` + Price *string `json:"price,omitempty"` + SalePrice *string `json:"saleprice,omitempty"` + Address *string `json:"address,omitempty"` + Phone *string `json:"phone,omitempty"` + Body *string `json:"body,omitempty"` + Body2 *string `json:"body2,omitempty"` + SponsoredBy *string `json:"sponsoredBy,omitempty"` + DisplayUrl *string `json:"displayUrl,omitempty"` +} + +type msqResponseBids struct { + ID string `json:"id"` + Ad string `json:"ad,omitempty"` + BidId string `json:"bid_id,omitempty"` + Bidder string `json:"bidder,omitempty"` + Cpm float64 `json:"cpm,omitempty"` + Currency string `json:"currency,omitempty"` + CreativeId string `json:"creative_id,omitempty"` + Height int64 `json:"height,omitempty"` + Width int64 `json:"width,omitempty"` + NetRevenue bool `json:"net_revenue,omitempty"` + TransactionId string `json:"transaction_id,omitempty"` + Ttl int `json:"ttl,omitempty"` + Video *msqResponseBidsVideo `json:"video,omitempty"` + Native *msqResponseBidsNative `json:"native,omitempty"` + ADomain []string `json:"adomain,omitempty"` + Dsa interface{} `json:"dsa,omitempty"` + BURL string `json:"burl,omitempty"` +} + +type msqSupport struct { + Device interface{} `json:"device"` + App interface{} `json:"app"` +} + +type msqParametersCodes struct { + AdUnit string `json:"adunit"` + AuctionId string `json:"auctionid"` + BidId string `json:"bidid"` + Code string `json:"code"` + Owner string `json:"owner"` + Mediatypes mediaTypes `json:"mediatypes,omitempty"` + Floor map[string]msqFloor `json:"floor,omitempty"` +} + +type msqFloor struct { + Price float64 `json:"floor,omitempty"` + Currency string `json:"currency,omitempty"` +} + +type mediaTypeNativeBasis struct { + Required bool `json:"required,omitempty"` + Len *int `json:"len,omitempty"` +} + +type mediaTypeNativeImage struct { + Required bool `json:"required"` + Sizes []*int `json:"sizes,omitempty"` + Aspect_ratio *struct { + Min_width *int `json:"min_width,omitempty"` + Min_height *int `json:"min_height,omitempty"` + Ratio_width *int `json:"ratio_width,omitempty"` + Ratio_height *int `json:"ratio_height,omitempty"` + } `json:"aspect_ratio,omitempty"` +} + +type mediaTypeNativeTitle struct { + Required bool `json:"required,omitempty"` + Len int `json:"len,omitempty"` +} + +type mediaTypeNative struct { + Title *mediaTypeNativeTitle `json:"title"` + Icon *mediaTypeNativeImage `json:"icon"` + Image *mediaTypeNativeImage `json:"image"` + Clickurl *mediaTypeNativeBasis `json:"clickUrl"` + Displayurl *mediaTypeNativeBasis `json:"displayUrl"` + Privacylink *mediaTypeNativeBasis `json:"privacyLink"` + Privacyicon *mediaTypeNativeBasis `json:"privacyIcon"` + Cta *mediaTypeNativeBasis `json:"cta"` + Rating *mediaTypeNativeBasis `json:"rating"` + Downloads *mediaTypeNativeBasis `json:"downloads"` + Likes *mediaTypeNativeBasis `json:"likes"` + Price *mediaTypeNativeBasis `json:"price"` + Saleprice *mediaTypeNativeBasis `json:"saleprice"` + Address *mediaTypeNativeBasis `json:"address"` + Phone *mediaTypeNativeBasis `json:"phone"` + Body *mediaTypeNativeBasis `json:"body"` + Body2 *mediaTypeNativeBasis `json:"body2"` + Sponsoredby *mediaTypeNativeBasis `json:"sponsoredBy"` + Sizes [][]int `json:"sizes"` + Type string `json:"type"` +} + +type mediaTypeVideo struct { + Mimes []string `json:"mimes"` + Minduration *int `json:"minduration"` + Maxduration *int `json:"maxduration"` + Protocols []*int `json:"protocols"` + Startdelay *int `json:"startdelay"` + Placement *int `json:"placement"` + Skip *int `json:"skip"` + Skipafter *int `json:"skipafter"` + Minbitrate *int `json:"minbitrate"` + Maxbitrate *int `json:"maxbitrate"` + Delivery []*int `json:"delivery"` + Playbackmethod []*int `json:"playbackmethod"` + Api []*int `json:"api"` + Linearity *int `json:"linearity"` + W *int `json:"w"` + H *int `json:"h"` + Boxingallowed *int `json:"boxingallower"` + PlayerSize [][]int `json:"playersize"` + Context string `json:"context"` + Plcmt *int `json:"plcmt,omitempty"` +} + +type mediaTypes struct { + Banner *mediaTypeBanner `json:"banner"` + Video *mediaTypeVideo `json:"video"` + Native *mediaTypeNative `json:"native"` +} + +type mediaTypeBanner struct { + Sizes [][]*int `json:"sizes"` +} + +func initMsqParams(request *openrtb2.BidRequest) (msqParams msqParameters) { + msqParams.Type = "pbs" + msqParams.Support = msqSupport{ + Device: request.Device, + App: request.App, + } + msqParams.Gdpr = struct { + ConsentRequired bool `json:"consent_required"` + ConsentString string `json:"consent_string"` + }{ + ConsentRequired: (parserGDPR{}).getValue("consent_requirement", request) == "true", + ConsentString: (parserGDPR{}).getValue("consent_string", request), + } + msqParams.DSA = (parserDSA{}).getValue(request) + + return +} + +// setContent: Loads currentImp into msqParams (*msqParametersCodes), +// returns (errs []error, ok bool) where `ok` express if mandatory content had been loaded. +func (msqParams *msqParametersCodes) setContent(currentImp openrtb2.Imp) (ok bool) { + var ( + currentMapFloors = make(map[string]msqFloor, 0) + currentFloor = msqFloor{ + Price: currentImp.BidFloor, + Currency: currentImp.BidFloorCur, + } + ) + + if currentImp.Video != nil { + ok = true + var video mediaTypeVideo + currentVideoBytes, _ := jsonutil.Marshal(currentImp.Video) + jsonutil.Unmarshal(currentVideoBytes, &video) + jsonutil.Unmarshal(currentImp.Video.Ext, &video) + + msqParams.Mediatypes.Video = &video + if msqParams.Mediatypes.Video != nil { + if currentImp.Video.W != nil && currentImp.Video.H != nil { + currentMapFloors[fmt.Sprintf("%dx%d", *(currentImp.Video.W), *(currentImp.Video.H))] = currentFloor + } + } + currentMapFloors["*"] = currentFloor + } + + if currentImp.Banner != nil { + ok = true + var banner mediaTypeBanner + jsonutil.Unmarshal(currentImp.Banner.Ext, &banner) + + msqParams.Mediatypes.Banner = &banner + switch { + case len(currentImp.Banner.Format) > 0: + for _, bannerFormat := range currentImp.Banner.Format { + currentMapFloors[fmt.Sprintf("%dx%d", bannerFormat.W, bannerFormat.H)] = currentFloor + msqParams.Mediatypes.Banner.Sizes = append(msqParams.Mediatypes.Banner.Sizes, + []*int{intToPtrInt(int(bannerFormat.W)), intToPtrInt(int(bannerFormat.H))}) + } + case currentImp.Banner.W != nil && currentImp.Banner.H != nil: + currentMapFloors[fmt.Sprintf("%dx%d", *(currentImp.Banner.W), *(currentImp.Banner.H))] = currentFloor + msqParams.Mediatypes.Banner.Sizes = append(msqParams.Mediatypes.Banner.Sizes, + []*int{intToPtrInt(int(*currentImp.Banner.W)), intToPtrInt(int(*currentImp.Banner.H))}) + } + + if msqParams.Mediatypes.Banner != nil { + for _, bannerSizes := range msqParams.Mediatypes.Banner.Sizes { + if len(bannerSizes) == 2 && bannerSizes[0] != nil && bannerSizes[1] != nil { + currentMapFloors[fmt.Sprintf("%dx%d", *(bannerSizes[0]), *(bannerSizes[1]))] = currentFloor + } + } + } + } + + if currentImp.Native != nil { + ok = true + var native = mediaTypeNative{Type: "native"} + jsonutil.Unmarshal(currentImp.Native.Ext, &native) + + msqParams.Mediatypes.Native = &native + for _, nativeSizes := range msqParams.Mediatypes.Native.Sizes { + if len(nativeSizes) == 2 { + currentMapFloors[fmt.Sprintf("%dx%d", nativeSizes[0], nativeSizes[1])] = currentFloor + } + } + currentMapFloors["*"] = currentFloor + } + + if len(currentMapFloors) > 0 { + msqParams.Floor = currentMapFloors + } + return +} + +// getContent: Loads msqResp content into the bidderResponse (*adapters.BidderResponse). +func (msqResp *msqResponse) getContent(bidderResponse *adapters.BidderResponse) { + var tmpBids []*adapters.TypedBid + for _, resp := range msqResp.Responses { + tmpTBid := adapters.TypedBid{ + BidType: resp.bidType(), + Bid: &openrtb2.Bid{ + ID: resp.ID, + ImpID: resp.BidId, + Price: resp.Cpm, + AdM: resp.Ad, + ADomain: resp.ADomain, + W: resp.Width, + H: resp.Height, + CrID: resp.CreativeId, + MType: resp.mType(), + BURL: resp.BURL, + Ext: resp.extBid(), + }, + BidMeta: resp.extBidPrebidMeta(), + } + tmpBids = append(tmpBids, &tmpTBid) + bidderResponse.Currency = resp.Currency + } + + if len(tmpBids) > 0 { + bidderResponse.Bids = tmpBids + } +} diff --git a/adapters/mediasquare/utils.go b/adapters/mediasquare/utils.go new file mode 100644 index 0000000000..6570436913 --- /dev/null +++ b/adapters/mediasquare/utils.go @@ -0,0 +1,108 @@ +package mediasquare + +import ( + "encoding/json" + "fmt" + + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v3/util/jsonutil" +) + +var headerList = map[string][]string{ + "Content-Type": {"application/json;charset=utf-8"}, + "Accept": {"application/json"}, +} + +var mediaTypeList = map[openrtb_ext.BidType]openrtb2.MarkupType{ + "banner": openrtb2.MarkupBanner, + "video": openrtb2.MarkupVideo, + "audio": openrtb2.MarkupAudio, + "native": openrtb2.MarkupNative, +} + +// mType: Returns the openrtb2.MarkupType from an msqResponseBids. +func (msqBids *msqResponseBids) mType() openrtb2.MarkupType { + switch { + case msqBids.Video != nil: + return mediaTypeList["video"] + case msqBids.Native != nil: + return mediaTypeList["native"] + default: + return mediaTypeList["banner"] + } +} + +// bidType: Returns the openrtb_ext.BidType from an msqResponseBids. +func (msqBids *msqResponseBids) bidType() openrtb_ext.BidType { + switch { + case msqBids.Video != nil: + return "video" + case msqBids.Native != nil: + return "native" + default: + return "banner" + } +} + +// extBid: Extracts the ExtBid from msqBids formated as (json.RawMessage). +func (msqBids *msqResponseBids) extBid() (raw json.RawMessage) { + extBid, _ := msqBids.loadExtBid() + if extBid.DSA != nil || extBid.Prebid != nil { + if bb, _ := jsonutil.Marshal(extBid); len(bb) > 0 { + raw = json.RawMessage(bb) + } + } + return +} + +// loadExtBid: Extracts the ExtBid from msqBids as (openrtb_ext.ExtBid, []error). +func (msqBids *msqResponseBids) loadExtBid() (extBid openrtb_ext.ExtBid, errs []error) { + if msqBids.Dsa != nil { + bb, err := jsonutil.Marshal(msqBids.Dsa) + if err != nil { + errs = append(errs, err) + } + if len(bb) > 0 { + var dsa openrtb_ext.ExtBidDSA + if err = jsonutil.Unmarshal(bb, &dsa); err != nil { + errs = append(errs, err) + } else { + extBid.DSA = &dsa + } + } + } + return +} + +// extBidPrebidMeta: Extracts the ExtBidPrebidMeta from msqBids as (*openrtb_ext.ExtBidPrebidMeta). +func (msqBids *msqResponseBids) extBidPrebidMeta() *openrtb_ext.ExtBidPrebidMeta { + var extBidMeta openrtb_ext.ExtBidPrebidMeta + if msqBids.ADomain != nil { + extBidMeta.AdvertiserDomains = msqBids.ADomain + } + extBidMeta.MediaType = string(msqBids.bidType()) + return &extBidMeta +} + +// ptrInt8ToBool: Returns (TRUE) when i equals 1. +func ptrInt8ToBool(i *int8) bool { + if i != nil { + return (*i == int8(1)) + } + return false +} + +// intToPtrInt: Returns a ptr_int(*int) for which *ptr_int = i. +func intToPtrInt(i int) *int { + val := int(i) + return &val +} + +// errorWritter: Returns a Custom error message. +func errorWritter(referer string, err error, isEmpty bool) error { + if isEmpty { + return fmt.Errorf("%s: is empty.", referer) + } + return fmt.Errorf("%s: %s", referer, err.Error()) +} diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index 67e28286f0..90ae025f4f 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -133,6 +133,7 @@ import ( "github.com/prebid/prebid-server/v3/adapters/marsmedia" "github.com/prebid/prebid-server/v3/adapters/mediago" "github.com/prebid/prebid-server/v3/adapters/medianet" + "github.com/prebid/prebid-server/v3/adapters/mediasquare" "github.com/prebid/prebid-server/v3/adapters/melozen" "github.com/prebid/prebid-server/v3/adapters/metax" "github.com/prebid/prebid-server/v3/adapters/mgid" @@ -365,6 +366,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderMediafuse: appnexus.Builder, openrtb_ext.BidderMediaGo: mediago.Builder, openrtb_ext.BidderMedianet: medianet.Builder, + openrtb_ext.BidderMediasquare: mediasquare.Builder, openrtb_ext.BidderMeloZen: melozen.Builder, openrtb_ext.BidderMetaX: metax.Builder, openrtb_ext.BidderMgid: mgid.Builder, diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index f7706ce5c2..2ab05e50d6 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -151,6 +151,7 @@ var coreBidderNames []BidderName = []BidderName{ BidderMediafuse, BidderMediaGo, BidderMedianet, + BidderMediasquare, BidderMeloZen, BidderMetaX, BidderMgid, @@ -480,6 +481,7 @@ const ( BidderMediafuse BidderName = "mediafuse" BidderMediaGo BidderName = "mediago" BidderMedianet BidderName = "medianet" + BidderMediasquare BidderName = "mediasquare" BidderMeloZen BidderName = "melozen" BidderMetaX BidderName = "metax" BidderMgid BidderName = "mgid" diff --git a/openrtb_ext/imp_mediasquare.go b/openrtb_ext/imp_mediasquare.go new file mode 100644 index 0000000000..8f1291e608 --- /dev/null +++ b/openrtb_ext/imp_mediasquare.go @@ -0,0 +1,6 @@ +package openrtb_ext + +type ImpExtMediasquare struct { + Owner string `json:"owner"` + Code string `json:"code"` +} diff --git a/static/bidder-info/mediasquare.yaml b/static/bidder-info/mediasquare.yaml new file mode 100644 index 0000000000..6056cc30a8 --- /dev/null +++ b/static/bidder-info/mediasquare.yaml @@ -0,0 +1,12 @@ +endpoint: "https://pbs-front.mediasquare.fr/msq_prebid" +endpointCompression: gzip +gvlVendorID: 791 +modifyingVastXmlAllowed: true +maintainer: + email: "alexandre.harribey@mediasquare.fr" +capabilities: + app: + mediaTypes: + - banner + - video + - native diff --git a/static/bidder-params/mediasquare.json b/static/bidder-params/mediasquare.json new file mode 100644 index 0000000000..db7d07201f --- /dev/null +++ b/static/bidder-params/mediasquare.json @@ -0,0 +1,19 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Mediasquare Adapter Params", + "description": "A schema which validates params accepted by the Mediasquare adapter", + "type": "object", + "properties": { + "owner": { + "type": "string", + "minLength": 1, + "description": "The owner provided for mediasquare." + }, + "code": { + "type": "string", + "minLength": 1, + "description": "The code provided for mediasquare." + } + }, + "required": ["owner", "code"] +}