From ff16ec928822d05c4d8d73f780bb0369f1814d95 Mon Sep 17 00:00:00 2001 From: Mitschi Date: Sun, 21 Mar 2021 10:20:20 +0100 Subject: [PATCH 1/2] addefeend bid adapter implementation --- modules/addefendBidAdapter.js | 79 ++++++++ modules/addefendBidAdapter.md | 43 +++++ test/spec/modules/addefendBidAdapter_spec.js | 184 +++++++++++++++++++ 3 files changed, 306 insertions(+) create mode 100644 modules/addefendBidAdapter.js create mode 100644 modules/addefendBidAdapter.md create mode 100644 test/spec/modules/addefendBidAdapter_spec.js diff --git a/modules/addefendBidAdapter.js b/modules/addefendBidAdapter.js new file mode 100644 index 00000000000..18cafe829b5 --- /dev/null +++ b/modules/addefendBidAdapter.js @@ -0,0 +1,79 @@ +import {registerBidder} from '../src/adapters/bidderFactory.js'; + +const BIDDER_CODE = 'addefend'; + +export const spec = { + code: BIDDER_CODE, + hostname: 'https://addefend-platform.com', + + getHostname() { + return this.hostname; + }, + isBidRequestValid: function(bid) { + return (bid.sizes !== undefined && bid.bidId !== undefined && bid.params !== undefined && + (bid.params.pageId !== undefined && (typeof bid.params.pageId === 'string')) && + (bid.params.placementId !== undefined && (typeof bid.params.placementId === 'string'))); + }, + buildRequests: function(validBidRequests, bidderRequest) { + let bid = { + v: $$PREBID_GLOBAL$$.version, + auctionId: false, + pageId: false, + gdpr_consent: bidderRequest.gdprConsent && bidderRequest.gdprConsent.consentString ? bidderRequest.gdprConsent.consentString : '', + referer: bidderRequest.refererInfo.referer, + bids: [], + }; + + for (var i = 0; i < validBidRequests.length; i++) { + let vb = validBidRequests[i]; + let o = vb.params; + bid.auctionId = vb.auctionId; + o.bidId = vb.bidId; + o.transactionId = vb.transactionId; + o.sizes = []; + if (o.trafficTypes) { + bid.trafficTypes = o.trafficTypes; + } + delete o.trafficTypes; + + bid.pageId = o.pageId; + delete o.pageId; + + if (vb.sizes && Array.isArray(vb.sizes)) { + for (var j = 0; j < vb.sizes.length; j++) { + let s = vb.sizes[j]; + if (Array.isArray(s) && s.length == 2) { + o.sizes.push(s[0] + 'x' + s[1]); + } + } + } + bid.bids.push(o); + } + return [{ + method: 'POST', + url: this.getHostname() + '/bid', + options: { withCredentials: true }, + data: bid + }]; + }, + interpretResponse: function(serverResponse, request) { + const requiredKeys = ['requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', 'netRevenue', 'currency', 'advertiserDomains']; + const validBidResponses = []; + serverResponse = serverResponse.body; + if (serverResponse && (serverResponse.length > 0)) { + serverResponse.forEach((bid) => { + const bidResponse = {}; + for (const requiredKey of requiredKeys) { + if (!bid.hasOwnProperty(requiredKey)) { + return []; + } + bidResponse[requiredKey] = bid[requiredKey]; + } + validBidResponses.push(bidResponse); + }); + } + return validBidResponses; + } +} + +registerBidder(spec); diff --git a/modules/addefendBidAdapter.md b/modules/addefendBidAdapter.md new file mode 100644 index 00000000000..c66baa4c084 --- /dev/null +++ b/modules/addefendBidAdapter.md @@ -0,0 +1,43 @@ +# Overview + +``` +Module Name: AdDefend Bid Adapter +Module Type: Bidder Adapter +Maintainer: prebid@addefend.com +``` + +# Description + +Module that connects to AdDefend as a demand source. + +## Parameters +| Param | Description | Optional | Default | +| ------------- | ------------- | ----- | ----- | +| pageId | id assigned to the website in the AdDefend system. (ask AdDefend support) | no | - | +| placementId | id of the placement in the AdDefend system. (ask AdDefend support) | no | - | +| trafficTypes | can be a comma seperated list of the following traffic types:
ADBLOCK - user has a activated adblocker
PM - user has firefox private mode activated
NC - user has not given consent
NONE - user traffic is none of the above, this usually means this is a "normal" user.
WARNING: since the adapter only sends the trafficTypes once, all bids should have the same value. | yes | ADBLOCK | + + +# Test Parameters +``` + var adUnits = [ + { + code: 'test-div', + mediaTypes: { + banner: { + sizes: [[970, 250]], // a display size + } + }, + bids: [ + { + bidder: "addefend", + params: { + pageId: 887, + placementId: 3517, + trafficTypes: 'ADBLOCK,PM,NC' + } + } + ] + } + ]; +``` diff --git a/test/spec/modules/addefendBidAdapter_spec.js b/test/spec/modules/addefendBidAdapter_spec.js new file mode 100644 index 00000000000..ac01750e98f --- /dev/null +++ b/test/spec/modules/addefendBidAdapter_spec.js @@ -0,0 +1,184 @@ +import {expect} from 'chai'; +import {spec} from 'modules/addefendBidAdapter.js'; + +describe('addefendBidAdapter', () => { + const defaultBidRequest = { + bidId: 'd66fa86787e0b0ca900a96eacfd5f0bb', + auctionId: 'ccc4c7cdfe11cfbd74065e6dd28413d8', + transactionId: 'd58851660c0c4461e4aa06344fc9c0c6', + sizes: [[300, 250], [300, 600]], + params: { + pageId: 'stringid1', + placementId: 'stringid2' + } + }; + + const deepClone = function (val) { + return JSON.parse(JSON.stringify(val)); + }; + + const buildRequest = (buildRequest, bidderRequest) => { + if (!Array.isArray(buildRequest)) { + buildRequest = [buildRequest]; + } + + return spec.buildRequests(buildRequest, { + ...bidderRequest || {}, + refererInfo: { + referer: 'https://referer.example.com' + } + })[0]; + }; + + describe('isBidRequestValid', () => { + it('should return true when required params found', () => { + const bidRequest = deepClone(defaultBidRequest); + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }); + + it('pageId performs type checking', () => { + const bidRequest = deepClone(defaultBidRequest); + bidRequest.params.pageId = 1; // supposed to be a string + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + + it('placementId performs type checking', () => { + const bidRequest = deepClone(defaultBidRequest); + bidRequest.params.placementId = 1; // supposed to be a string + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + + it('should return false when required params are not passed', () => { + const bidRequest = deepClone(defaultBidRequest); + delete bidRequest.params; + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + }); + + describe('buildRequests', () => { + const bidRequest = deepClone(defaultBidRequest); + const request = buildRequest(bidRequest); + + it('sends bid request to endpoint via https using post', () => { + expect(request.method).to.equal('POST'); + expect(request.url.indexOf('https://')).to.equal(0); + expect(request.url).to.equal(`${spec.hostname}/bid`); + }); + + it('contains prebid version parameter', () => { + expect(request.data.v).to.equal($$PREBID_GLOBAL$$.version); + }); + + it('contains correct referer', () => { + expect(request.data.referer).to.equal('https://referer.example.com'); + }); + + it('contains auctionId', () => { + expect(request.data.auctionId).to.equal('ccc4c7cdfe11cfbd74065e6dd28413d8'); + }); + + it('contains pageId', () => { + expect(request.data.pageId).to.equal('stringid1'); + }); + + it('sends correct bid parameters', () => { + const bidRequest = deepClone(defaultBidRequest); + expect(request.data.bids).to.deep.equal([ { + bidId: bidRequest.bidId, + placementId: bidRequest.params.placementId, + sizes: [ '300x250', '300x600' ], + transactionId: 'd58851660c0c4461e4aa06344fc9c0c6' + } ]); + }); + + it('handles empty gdpr object', () => { + const bidRequest = deepClone(defaultBidRequest); + const request = buildRequest(bidRequest, { + gdprConsent: {} + }); + expect(request.data.gdpr_consent).to.be.equal(''); + }); + + it('handles non-existent gdpr object', () => { + const bidRequest = deepClone(defaultBidRequest); + const request = buildRequest(bidRequest, { + gdprConsent: null + }); + expect(request.data.gdpr_consent).to.be.equal(''); + }); + + it('handles properly filled gdpr string', () => { + const bidRequest = deepClone(defaultBidRequest); + const consentString = 'GDPR_CONSENT_STRING'; + const request = buildRequest(bidRequest, { + gdprConsent: { + gdprApplies: true, + consentString: consentString + } + }); + + expect(request.data.gdpr_consent).to.be.equal(consentString); + }); + }); + + describe('interpretResponse', () => { + it('should get correct bid response', () => { + const serverResponse = [ + { + 'width': 300, + 'height': 250, + 'creativeId': '29681110', + 'ad': '', + 'cpm': 0.5, + 'requestId': 'ccc4c7cdfe11cfbd74065e6dd28413d8', + 'ttl': 120, + 'netRevenue': true, + 'currency': 'EUR', + 'advertiserDomains': ['advertiser.example.com'] + } + ]; + + const expectedResponse = [ + { + 'requestId': 'ccc4c7cdfe11cfbd74065e6dd28413d8', + 'cpm': 0.5, + 'creativeId': '29681110', + 'width': 300, + 'height': 250, + 'ttl': 120, + 'currency': 'EUR', + 'ad': '', + 'netRevenue': true, + 'advertiserDomains': ['advertiser.example.com'] + } + ]; + + const result = spec.interpretResponse({body: serverResponse}); + expect(result.length).to.equal(expectedResponse.length); + Object.keys(expectedResponse[0]).forEach((key) => { + expect(result[0][key]).to.deep.equal(expectedResponse[0][key]); + }); + }); + + it('handles incomplete server response', () => { + const serverResponse = [ + { + 'ad': '', + 'cpm': 0.5, + 'requestId': 'ccc4c7cdfe11cfbd74065e6dd28413d8', + 'ttl': 60 + } + ]; + const result = spec.interpretResponse({body: serverResponse}); + + expect(result.length).to.equal(0); + }); + + it('handles nobid responses', () => { + const serverResponse = []; + const result = spec.interpretResponse({body: serverResponse}); + + expect(result.length).to.equal(0); + }); + }); +}); From ac4909cfc98997c252f0def1c021bc29060a8c2e Mon Sep 17 00:00:00 2001 From: Mitschi Date: Sun, 21 Mar 2021 10:38:42 +0100 Subject: [PATCH 2/2] fixed parameter documentation --- modules/addefendBidAdapter.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/addefendBidAdapter.md b/modules/addefendBidAdapter.md index c66baa4c084..f1ac3ff2c46 100644 --- a/modules/addefendBidAdapter.md +++ b/modules/addefendBidAdapter.md @@ -15,7 +15,7 @@ Module that connects to AdDefend as a demand source. | ------------- | ------------- | ----- | ----- | | pageId | id assigned to the website in the AdDefend system. (ask AdDefend support) | no | - | | placementId | id of the placement in the AdDefend system. (ask AdDefend support) | no | - | -| trafficTypes | can be a comma seperated list of the following traffic types:
ADBLOCK - user has a activated adblocker
PM - user has firefox private mode activated
NC - user has not given consent
NONE - user traffic is none of the above, this usually means this is a "normal" user.
WARNING: since the adapter only sends the trafficTypes once, all bids should have the same value. | yes | ADBLOCK | +| trafficTypes | comma seperated list of the following traffic types:
ADBLOCK - user has a activated adblocker
PM - user has firefox private mode activated
NC - user has not given consent
NONE - user traffic is none of the above, this usually means this is a "normal" user.
| yes | ADBLOCK | # Test Parameters @@ -32,9 +32,9 @@ Module that connects to AdDefend as a demand source. { bidder: "addefend", params: { - pageId: 887, - placementId: 3517, - trafficTypes: 'ADBLOCK,PM,NC' + pageId: "887", + placementId: "9398", + trafficTypes: "ADBLOCK" } } ]