From 2617c8b7f158acf836f53d54efea54363f349a44 Mon Sep 17 00:00:00 2001 From: Maxime Date: Thu, 28 Feb 2019 11:23:29 +0100 Subject: [PATCH 1/3] New bid adapter for SmileWanted - Smilewanted BidAdapter module and test spec added --- modules/smilewantedBidAdapter.js | 84 ++++++++ modules/smilewantedBidAdapter.md | 31 +++ .../modules/smilewantedBidAdapter_spec.js | 183 ++++++++++++++++++ 3 files changed, 298 insertions(+) create mode 100644 modules/smilewantedBidAdapter.js create mode 100644 modules/smilewantedBidAdapter.md create mode 100644 test/spec/modules/smilewantedBidAdapter_spec.js diff --git a/modules/smilewantedBidAdapter.js b/modules/smilewantedBidAdapter.js new file mode 100644 index 00000000000..bdc7ca215cb --- /dev/null +++ b/modules/smilewantedBidAdapter.js @@ -0,0 +1,84 @@ +import * as utils from '../src/utils'; +import { config } from '../src/config'; +import { registerBidder } from '../src/adapters/bidderFactory'; + +export const spec = { + code: 'smilewanted', + aliases: ['smile', 'sw'], + + isBidRequestValid: function(bid) { + return !!(bid.params && bid.params.zoneId); + }, + + buildRequests: function(validBidRequests, bidderRequest) { + return validBidRequests.map(bid => { + var payload = { + zoneId: bid.params.zoneId, + currencyCode: config.getConfig('currency.adServerCurrency'), + bidfloor: bid.params.bidfloor || 0.0, + tagId: bid.adUnitCode, + sizes: bid.sizes.map(size => ({ + w: size[0], + h: size[1] + })), + pageDomain: utils.getTopWindowUrl(), + transactionId: bid.transactionId, + timeout: config.getConfig('bidderTimeout'), + bidId: bid.bidId, + prebidVersion: '$prebid.version$' + }; + + if (bidderRequest && bidderRequest.gdprConsent) { + payload.gdpr_consent = bidderRequest.gdprConsent.consentString; + payload.gdpr = bidderRequest.gdprConsent.gdprApplies; // we're handling the undefined case server side + } + var payloadString = JSON.stringify(payload); + return { + method: 'POST', + url: 'https://prebid.smilewanted.com', + data: payloadString, + }; + }); + }, + + interpretResponse: function(serverResponse, bidRequest) { + const bidResponses = []; + var response = serverResponse.body; + try { + if (response) { + const bidResponse = { + requestId: JSON.parse(bidRequest.data).bidId, + cpm: response.cpm, + width: response.width, + height: response.height, + creativeId: response.creativeId, + dealId: response.dealId, + currency: response.currency, + netRevenue: response.isNetCpm, + ttl: response.ttl, + referrer: utils.getTopWindowUrl(), + adUrl: response.adUrl, + ad: response.ad + }; + + bidResponses.push(bidResponse); + } + } catch (error) { + utils.logError('Error while parsing smilewanted response', error); + } + return bidResponses; + }, + + getUserSyncs: function(syncOptions, serverResponses) { + const syncs = [] + if (syncOptions.iframeEnabled && serverResponses.length > 0) { + syncs.push({ + type: 'iframe', + url: serverResponses[0].body.cSyncUrl + }); + } + return syncs; + } +} + +registerBidder(spec); diff --git a/modules/smilewantedBidAdapter.md b/modules/smilewantedBidAdapter.md new file mode 100644 index 00000000000..ddc25fe7456 --- /dev/null +++ b/modules/smilewantedBidAdapter.md @@ -0,0 +1,31 @@ +# Overview + +``` +Module Name: SmileWanted Bidder Adapter +Module Type: Bidder Adapter +Maintainer: maxime@smilewanted.com +``` + +# Description + +To use us as a bidder you must have an account and an active "zoneId" on our SmileWanted platform. + +# Test Parameters + +## Web +``` + var adUnits = [ + { + code: 'test-div', + sizes: [[300, 250]], + bids: [ + { + bidder: "smilewanted", + params: { + zoneId: 1 + } + } + ] + } + ]; +``` \ No newline at end of file diff --git a/test/spec/modules/smilewantedBidAdapter_spec.js b/test/spec/modules/smilewantedBidAdapter_spec.js new file mode 100644 index 00000000000..7b68d629834 --- /dev/null +++ b/test/spec/modules/smilewantedBidAdapter_spec.js @@ -0,0 +1,183 @@ +import { expect } from 'chai'; +import { spec } from 'modules/smilewantedBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; +import { config } from 'src/config'; +import * as utils from 'src/utils'; +import { requestBidsHook } from 'modules/consentManagement'; + +// Default params with optional ones +describe('smilewantedBidAdapterTests', function () { + var DEFAULT_PARAMS = [{ + adUnitCode: 'sw_300x250', + bidId: '12345', + sizes: [ + [300, 250], + [300, 200] + ], + bidder: 'smilewanted', + params: { + zoneId: '1234', + bidfloor: 2.50 + }, + requestId: 'request_abcd1234', + transactionId: 'trans_abcd1234' + }]; + + var BID_RESPONSE = { + body: { + cpm: 3, + width: 300, + height: 250, + creativeId: 'crea_sw_1', + currency: 'EUR', + isNetCpm: true, + ttl: 300, + adUrl: 'https://www.smilewanted.com', + ad: '< --- sw script --- >', + cSyncUrl: 'https://csync.smilewanted.com' + } + }; + + it('SmileWanted - Verify build request', function () { + config.setConfig({ + 'currency': { + 'adServerCurrency': 'EUR' + } + }); + const request = spec.buildRequests(DEFAULT_PARAMS); + expect(request[0]).to.have.property('url').and.to.equal('https://prebid.smilewanted.com'); + expect(request[0]).to.have.property('method').and.to.equal('POST'); + const requestContent = JSON.parse(request[0].data); + expect(requestContent).to.have.property('zoneId').and.to.equal('1234'); + expect(requestContent).to.have.property('currencyCode').and.to.equal('EUR'); + expect(requestContent).to.have.property('bidfloor').and.to.equal(2.50); + expect(requestContent).to.have.property('sizes'); + expect(requestContent.sizes[0]).to.have.property('w').and.to.equal(300); + expect(requestContent.sizes[0]).to.have.property('h').and.to.equal(250); + expect(requestContent.sizes[1]).to.have.property('w').and.to.equal(300); + expect(requestContent.sizes[1]).to.have.property('h').and.to.equal(200); + expect(requestContent).to.have.property('pageDomain').and.to.equal(utils.getTopWindowUrl()); + expect(requestContent).to.have.property('transactionId').and.to.not.equal(null).and.to.not.be.undefined; + }); + + describe('gdpr tests', function () { + afterEach(function () { + config.resetConfig(); + $$PREBID_GLOBAL$$.requestBids.removeAll(); + }); + + it('SmileWanted - Verify build request with GDPR', function () { + config.setConfig({ + 'currency': { + 'adServerCurrency': 'EUR' + }, + consentManagement: { + cmp: 'iab', + consentRequired: true, + timeout: 1000, + allowAuctionWithoutConsent: true + } + }); + const request = spec.buildRequests(DEFAULT_PARAMS, { + gdprConsent: { + consentString: 'BOO_ch7OO_ch7AKABBENA2-AAAAZ97_______9______9uz_Gv_r_f__33e8_39v_h_7_u___m_-zzV4-_lvQV1yPA1OrfArgFA', + gdprApplies: true + } + }); + const requestContent = JSON.parse(request[0].data); + expect(requestContent).to.have.property('gdpr').and.to.equal(true); + expect(requestContent).to.have.property('gdpr_consent').and.to.equal('BOO_ch7OO_ch7AKABBENA2-AAAAZ97_______9______9uz_Gv_r_f__33e8_39v_h_7_u___m_-zzV4-_lvQV1yPA1OrfArgFA'); + }); + + it('SmileWanted - Verify build request with GDPR without gdprApplies', function () { + config.setConfig({ + 'currency': { + 'adServerCurrency': 'EUR' + }, + consentManagement: { + cmp: 'iab', + consentRequired: true, + timeout: 1000, + allowAuctionWithoutConsent: true + } + }); + const request = spec.buildRequests(DEFAULT_PARAMS, { + gdprConsent: { + consentString: 'BOO_ch7OO_ch7AKABBENA2-AAAAZ97_______9______9uz_Gv_r_f__33e8_39v_h_7_u___m_-zzV4-_lvQV1yPA1OrfArgFA' + } + }); + const requestContent = JSON.parse(request[0].data); + expect(requestContent).to.not.have.property('gdpr'); + expect(requestContent).to.have.property('gdpr_consent').and.to.equal('BOO_ch7OO_ch7AKABBENA2-AAAAZ97_______9______9uz_Gv_r_f__33e8_39v_h_7_u___m_-zzV4-_lvQV1yPA1OrfArgFA'); + }); + }); + + it('SmileWanted - Verify parse response', function () { + const request = spec.buildRequests(DEFAULT_PARAMS); + const bids = spec.interpretResponse(BID_RESPONSE, request[0]); + expect(bids).to.have.lengthOf(1); + const bid = bids[0]; + expect(bid.cpm).to.equal(3); + expect(bid.adUrl).to.equal('https://www.smilewanted.com'); + expect(bid.ad).to.equal('< --- sw script --- >'); + expect(bid.width).to.equal(300); + expect(bid.height).to.equal(250); + expect(bid.creativeId).to.equal('crea_sw_1'); + expect(bid.currency).to.equal('EUR'); + expect(bid.netRevenue).to.equal(true); + expect(bid.ttl).to.equal(300); + expect(bid.requestId).to.equal(DEFAULT_PARAMS[0].bidId); + expect(bid.referrer).to.equal(utils.getTopWindowUrl()); + + expect(function () { + spec.interpretResponse(BID_RESPONSE, { + data: 'invalid Json' + }) + }).to.not.throw(); + }); + + it('SmileWanted - Verify bidder code', function () { + expect(spec.code).to.equal('smilewanted'); + }); + + it('SmileWanted - Verify bidder aliases', function () { + expect(spec.aliases).to.have.lengthOf(2); + expect(spec.aliases[0]).to.equal('smile'); + expect(spec.aliases[1]).to.equal('sw'); + }); + + it('SmileWanted - Verify if bid request valid', function () { + expect(spec.isBidRequestValid(DEFAULT_PARAMS[0])).to.equal(true); + expect(spec.isBidRequestValid({ + params: { + zoneId: 1234 + } + })).to.equal(true); + }); + + it('SmileWanted - Verify if params(zoneId) is not passed', function () { + expect(spec.isBidRequestValid({})).to.equal(false); + expect(spec.isBidRequestValid({ + params: {} + })).to.equal(false); + }); + + it('SmileWanted - Verify user sync', function () { + var syncs = spec.getUserSyncs({ + iframeEnabled: true + }, [BID_RESPONSE]); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.equal('https://csync.smilewanted.com'); + + syncs = spec.getUserSyncs({ + iframeEnabled: false + }, [BID_RESPONSE]); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: true + }, []); + expect(syncs).to.have.lengthOf(0); + }); +}); From c285b23ae31dad1d707de724e2ac89497a09f2b5 Mon Sep 17 00:00:00 2001 From: Maxime Date: Thu, 7 Mar 2019 14:15:27 +0100 Subject: [PATCH 2/3] Update to correct feedback from PR #3601 - Add comments - Default value for currencyCode - replacing "utils.getTopWindowUrl()" with bidderRequest.refererInfo - Update unit tests --- modules/smilewantedBidAdapter.js | 43 ++++++++++++++++--- .../modules/smilewantedBidAdapter_spec.js | 13 +++++- 2 files changed, 47 insertions(+), 9 deletions(-) diff --git a/modules/smilewantedBidAdapter.js b/modules/smilewantedBidAdapter.js index bdc7ca215cb..bf9a75b6650 100644 --- a/modules/smilewantedBidAdapter.js +++ b/modules/smilewantedBidAdapter.js @@ -6,28 +6,43 @@ export const spec = { code: 'smilewanted', aliases: ['smile', 'sw'], + /** + * Determines whether or not the given bid request is valid. + * + * @param {object} bid The bid to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ isBidRequestValid: function(bid) { return !!(bid.params && bid.params.zoneId); }, + /** + * Make a server request from the list of BidRequests. + * + * @param {BidRequest[]} validBidRequests A non-empty list of valid bid requests that should be sent to the Server. + * @return ServerRequest Info describing the request to the server. + */ buildRequests: function(validBidRequests, bidderRequest) { return validBidRequests.map(bid => { var payload = { zoneId: bid.params.zoneId, - currencyCode: config.getConfig('currency.adServerCurrency'), + currencyCode: config.getConfig('currency.adServerCurrency') || 'EUR', bidfloor: bid.params.bidfloor || 0.0, tagId: bid.adUnitCode, sizes: bid.sizes.map(size => ({ w: size[0], h: size[1] })), - pageDomain: utils.getTopWindowUrl(), transactionId: bid.transactionId, timeout: config.getConfig('bidderTimeout'), bidId: bid.bidId, prebidVersion: '$prebid.version$' }; + if (bidderRequest && bidderRequest.refererInfo) { + payload.pageDomain = bidderRequest.refererInfo.referer || ''; + } + if (bidderRequest && bidderRequest.gdprConsent) { payload.gdpr_consent = bidderRequest.gdprConsent.consentString; payload.gdpr = bidderRequest.gdprConsent.gdprApplies; // we're handling the undefined case server side @@ -41,6 +56,12 @@ export const spec = { }); }, + /** + * Unpack the response from the server into a list of bids. + * + * @param {*} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ interpretResponse: function(serverResponse, bidRequest) { const bidResponses = []; var response = serverResponse.body; @@ -56,7 +77,6 @@ export const spec = { currency: response.currency, netRevenue: response.isNetCpm, ttl: response.ttl, - referrer: utils.getTopWindowUrl(), adUrl: response.adUrl, ad: response.ad }; @@ -69,13 +89,22 @@ export const spec = { return bidResponses; }, + /** + * User syncs. + * + * @param {*} syncOptions Publisher prebid configuration. + * @param {*} serverResponses A successful response from the server. + * @return {Syncs[]} An array of syncs that should be executed. + */ getUserSyncs: function(syncOptions, serverResponses) { const syncs = [] if (syncOptions.iframeEnabled && serverResponses.length > 0) { - syncs.push({ - type: 'iframe', - url: serverResponses[0].body.cSyncUrl - }); + if (serverResponses[0].body.cSyncUrl === 'https://csync.smilewanted.com') { + syncs.push({ + type: 'iframe', + url: serverResponses[0].body.cSyncUrl + }); + } } return syncs; } diff --git a/test/spec/modules/smilewantedBidAdapter_spec.js b/test/spec/modules/smilewantedBidAdapter_spec.js index 7b68d629834..5a0ee57f090 100644 --- a/test/spec/modules/smilewantedBidAdapter_spec.js +++ b/test/spec/modules/smilewantedBidAdapter_spec.js @@ -56,10 +56,20 @@ describe('smilewantedBidAdapterTests', function () { expect(requestContent.sizes[0]).to.have.property('h').and.to.equal(250); expect(requestContent.sizes[1]).to.have.property('w').and.to.equal(300); expect(requestContent.sizes[1]).to.have.property('h').and.to.equal(200); - expect(requestContent).to.have.property('pageDomain').and.to.equal(utils.getTopWindowUrl()); + // expect(requestContent).to.have.property('pageDomain').and.to.equal('http://localhost/Prebid.js/integrationExamples/gpt/hello_world.html'); expect(requestContent).to.have.property('transactionId').and.to.not.equal(null).and.to.not.be.undefined; }); + it('SmileWanted - Verify build request with referrer', function () { + const request = spec.buildRequests(DEFAULT_PARAMS, { + refererInfo: { + referer: 'http://localhost/Prebid.js/integrationExamples/gpt/hello_world.html' + } + }); + const requestContent = JSON.parse(request[0].data); + expect(requestContent).to.have.property('pageDomain').and.to.equal('http://localhost/Prebid.js/integrationExamples/gpt/hello_world.html'); + }); + describe('gdpr tests', function () { afterEach(function () { config.resetConfig(); @@ -127,7 +137,6 @@ describe('smilewantedBidAdapterTests', function () { expect(bid.netRevenue).to.equal(true); expect(bid.ttl).to.equal(300); expect(bid.requestId).to.equal(DEFAULT_PARAMS[0].bidId); - expect(bid.referrer).to.equal(utils.getTopWindowUrl()); expect(function () { spec.interpretResponse(BID_RESPONSE, { From 63acd4fd23227809542065c5c38e00876e66aa94 Mon Sep 17 00:00:00 2001 From: Maxime Date: Fri, 8 Mar 2019 10:14:27 +0100 Subject: [PATCH 3/3] Delete of the commented spec --- test/spec/modules/smilewantedBidAdapter_spec.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/spec/modules/smilewantedBidAdapter_spec.js b/test/spec/modules/smilewantedBidAdapter_spec.js index 5a0ee57f090..489f393523a 100644 --- a/test/spec/modules/smilewantedBidAdapter_spec.js +++ b/test/spec/modules/smilewantedBidAdapter_spec.js @@ -56,7 +56,6 @@ describe('smilewantedBidAdapterTests', function () { expect(requestContent.sizes[0]).to.have.property('h').and.to.equal(250); expect(requestContent.sizes[1]).to.have.property('w').and.to.equal(300); expect(requestContent.sizes[1]).to.have.property('h').and.to.equal(200); - // expect(requestContent).to.have.property('pageDomain').and.to.equal('http://localhost/Prebid.js/integrationExamples/gpt/hello_world.html'); expect(requestContent).to.have.property('transactionId').and.to.not.equal(null).and.to.not.be.undefined; });