From 6813398e3f3141caefdda1c499e6a711acec5b9d Mon Sep 17 00:00:00 2001 From: FeedAd GmbH Date: Thu, 2 May 2019 14:45:59 +0200 Subject: [PATCH 01/31] added file scaffold --- modules/feedadBidAdapter.js | 28 ++++++++++++++++++++++++++++ modules/feedadBidAdapter.md | 1 + 2 files changed, 29 insertions(+) create mode 100644 modules/feedadBidAdapter.js create mode 100644 modules/feedadBidAdapter.md diff --git a/modules/feedadBidAdapter.js b/modules/feedadBidAdapter.js new file mode 100644 index 00000000000..b6bd0a107b3 --- /dev/null +++ b/modules/feedadBidAdapter.js @@ -0,0 +1,28 @@ +import * as utils from 'src/utils'; +import {registerBidder} from 'src/adapters/bidderFactory'; +import {config} from 'src/config'; + +/** + * Bidder network identity code + * @type {string} + */ +const BIDDER_CODE = 'feedad'; + +export const spec = { + code: BIDDER_CODE, + isBidRequestValid: function (bid) { + }, + buildRequests: function (validBidRequests, bidderRequest) { + }, + interpretResponse: function (serverResponse, request) { + }, + getUserSyncs: function (syncOptions, serverResponses) { + }, + onTimeout: function (timeoutData) { + }, + onBidWon: function (bid) { + }, + onSetTargeting: function (bid) { + } +}; +registerBidder(spec); diff --git a/modules/feedadBidAdapter.md b/modules/feedadBidAdapter.md new file mode 100644 index 00000000000..464090415c4 --- /dev/null +++ b/modules/feedadBidAdapter.md @@ -0,0 +1 @@ +# TODO From 030a70979df5b255653b67ef87b101c99eb0bc43 Mon Sep 17 00:00:00 2001 From: FeedAd GmbH Date: Tue, 7 May 2019 16:42:46 +0200 Subject: [PATCH 02/31] added isBidRequestValid implementation --- modules/feedadBidAdapter.js | 79 +++++++++++++++++++++++++++++++++++-- 1 file changed, 76 insertions(+), 3 deletions(-) diff --git a/modules/feedadBidAdapter.js b/modules/feedadBidAdapter.js index b6bd0a107b3..22aa859f784 100644 --- a/modules/feedadBidAdapter.js +++ b/modules/feedadBidAdapter.js @@ -1,6 +1,6 @@ import * as utils from 'src/utils'; import {registerBidder} from 'src/adapters/bidderFactory'; -import {config} from 'src/config'; +import {BANNER, VIDEO} from '../src/mediaTypes'; /** * Bidder network identity code @@ -8,11 +8,84 @@ import {config} from 'src/config'; */ const BIDDER_CODE = 'feedad'; +/** + * The media types supported by FeedAd + * @type {MediaType[]} + */ +const MEDIA_TYPES = [VIDEO, BANNER]; + +/** + * Tag for logging + * @type {string} + */ +const TAG = '[FeedAd]'; + +/** + * Checks if the bid is compatible with FeedAd. + * + * @param {BidRequest} bid - the bid to check + * @return {boolean} true if the bid is valid + */ +function isBidRequestValid(bid) { + const clientToken = utils.deepAccess(bid, 'params.clientToken'); + if (!clientToken || !isValidClientToken(clientToken)) { + utils.logWarn(TAG, "missing or invalid parameter 'clientToken'. found value:", clientToken); + return false; + } + + const placementId = utils.deepAccess(bid, 'params.placementId'); + if (!placementId || !isValidPlacementId(placementId)) { + utils.logWarn(TAG, "missing or invalid parameter 'placementId'. found value:", placementId); + return false; + } + + return true; +} + +/** + * Checks if a client token is valid + * @param {string} clientToken - the client token + * @return {boolean} true if the token is valid + */ +function isValidClientToken(clientToken) { + return typeof clientToken === 'string' && clientToken.length > 0; +} + +/** + * Checks if the placement id is a valid FeedAd placement ID + * + * @param {string} placementId - the placement id + * @return {boolean} if the id is valid + */ +function isValidPlacementId(placementId) { + return placementId.length > 0; // TODO: add placement ID regex or convert any string to valid ID? +} + +/** + * Checks if the given media types contain unsupported settings + * @param {MediaTypes} mediaTypes - the media types to check + * @return {string[]} the unsupported settings, empty when all types are supported + */ +function checkMediaTypes(mediaTypes) { + const errors = []; + if (mediaTypes.native) { + errors.push("'native' ads are not supported"); + } + if (mediaTypes.video && mediaTypes.video.any(video => video.context !== 'outstream')) { + errors.push("only 'outstream' video context's are supported"); + } + return errors; +} + +/** + * @type {BidderSpec} + */ export const spec = { code: BIDDER_CODE, - isBidRequestValid: function (bid) { - }, + supportedMediaTypes: MEDIA_TYPES, + isBidRequestValid, buildRequests: function (validBidRequests, bidderRequest) { + utils.logMessage('buildRequests', JSON.stringify(validBidRequests), JSON.stringify(bidderRequest)); }, interpretResponse: function (serverResponse, request) { }, From 1ac46eb9bab0a22d8940df063a5b7d94b24e2dae Mon Sep 17 00:00:00 2001 From: FeedAd GmbH Date: Wed, 8 May 2019 14:01:15 +0200 Subject: [PATCH 03/31] added local prototype of ad integration --- modules/feedadBidAdapter.js | 98 +++++++++++++++++++++++++++++++------ 1 file changed, 84 insertions(+), 14 deletions(-) diff --git a/modules/feedadBidAdapter.js b/modules/feedadBidAdapter.js index 22aa859f784..50ae69af9bf 100644 --- a/modules/feedadBidAdapter.js +++ b/modules/feedadBidAdapter.js @@ -64,17 +64,89 @@ function isValidPlacementId(placementId) { /** * Checks if the given media types contain unsupported settings * @param {MediaTypes} mediaTypes - the media types to check - * @return {string[]} the unsupported settings, empty when all types are supported + * @return {MediaTypes} the unsupported settings, empty when all types are supported */ -function checkMediaTypes(mediaTypes) { - const errors = []; - if (mediaTypes.native) { - errors.push("'native' ads are not supported"); - } - if (mediaTypes.video && mediaTypes.video.any(video => video.context !== 'outstream')) { - errors.push("only 'outstream' video context's are supported"); +function filterSupportedMediaTypes(mediaTypes) { + return { + banner: mediaTypes.banner, + video: mediaTypes.video && mediaTypes.video.filter(video => video.context !== 'outstream'), + native: [] + }; +} + +/** + * Checks if the given media types are empty + * @param {MediaTypes} mediaTypes - the types to check + * @return {boolean} true if the types are empty + */ +function isMediaTypesEmpty(mediaTypes) { + return Object.keys(mediaTypes).every(type => mediaTypes[type].length === 0); +} + +/** + * Builds the bid request to the FeedAd Server + * @param {BidRequest[]} validBidRequests - all validated bid requests + * @param {object} bidderRequest - meta information + * @return {ServerRequest|ServerRequest[]} + */ +function buildRequests(validBidRequests, bidderRequest) { + let acceptableRequests = validBidRequests.filter(request => !isMediaTypesEmpty(filterSupportedMediaTypes(request.mediaTypes))); + return { + method: 'POST', + url: 'http://localhost:3000/bidRequests', + data: { + requests: acceptableRequests + }, + options: { + contentType: 'application/json' + } } - return errors; +} + +/** + * Adapts the FeedAd server response to Prebid format + * @param {ServerResponse} serverResponse - the FeedAd server response + * @param {BidRequest} request - the initial bid request + * @returns {Bid[]} the FeedAd bids + */ +function interpretResponse(serverResponse, request) { + const body = typeof serverResponse.body === "string" ? JSON.parse(serverResponse.body) : serverResponse.body; + return body.requests.map((req, idx) => ({ + requestId: req.bidId, + cpm: 0.5, + width: req.sizes[0][0], + height: req.sizes[0][1], + ad: createAdHTML(req), + ttl: 60, + creativeId: `feedad-${body.id}-${idx}`, + netRevenue: true, + currency: "EUR" + })); +} + +/** + * Creates the HTML content for a FeedAd creative + * @param {object} req - the server response body + * @return {string} the HTML string + */ +function createAdHTML(req) { + return ` + + +`; } /** @@ -84,11 +156,8 @@ export const spec = { code: BIDDER_CODE, supportedMediaTypes: MEDIA_TYPES, isBidRequestValid, - buildRequests: function (validBidRequests, bidderRequest) { - utils.logMessage('buildRequests', JSON.stringify(validBidRequests), JSON.stringify(bidderRequest)); - }, - interpretResponse: function (serverResponse, request) { - }, + buildRequests, + interpretResponse, getUserSyncs: function (syncOptions, serverResponses) { }, onTimeout: function (timeoutData) { @@ -98,4 +167,5 @@ export const spec = { onSetTargeting: function (bid) { } }; + registerBidder(spec); From fdcee20de4ab760ac47606343f617214b8781924 Mon Sep 17 00:00:00 2001 From: FeedAd GmbH Date: Mon, 20 May 2019 09:59:27 +0200 Subject: [PATCH 04/31] added implementation for placement ID validation --- modules/feedadBidAdapter.js | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/modules/feedadBidAdapter.js b/modules/feedadBidAdapter.js index 50ae69af9bf..07c601d17ae 100644 --- a/modules/feedadBidAdapter.js +++ b/modules/feedadBidAdapter.js @@ -20,6 +20,12 @@ const MEDIA_TYPES = [VIDEO, BANNER]; */ const TAG = '[FeedAd]'; +/** + * Pattern for valid placement IDs + * @type {RegExp} + */ +const PLACEMENT_ID_PATTERN = /^(([a-z0-9])+[-_]?)+$/; + /** * Checks if the bid is compatible with FeedAd. * @@ -52,13 +58,20 @@ function isValidClientToken(clientToken) { } /** - * Checks if the placement id is a valid FeedAd placement ID + * Checks if the given placement id is of a correct format. + * Valid IDs are words of lowercase letters from a to z and numbers from 0 to 9. + * The words can be separated by hyphens or underscores. + * Multiple separators must not follow each other. + * The whole placement ID must not be larger than 256 characters. * - * @param {string} placementId - the placement id - * @return {boolean} if the id is valid + * @param placementId - the placement id to verify + * @returns if the placement ID is valid. */ -function isValidPlacementId(placementId) { - return placementId.length > 0; // TODO: add placement ID regex or convert any string to valid ID? +export function isValidPlacementId(placementId) { + return typeof placementId === "string" + && placementId.length > 0 + && placementId.length <= 256 + && PLACEMENT_ID_PATTERN.test(placementId); } /** From 19380945d189139789b43de628581d0176ea389d Mon Sep 17 00:00:00 2001 From: FeedAd GmbH Date: Mon, 20 May 2019 10:32:42 +0200 Subject: [PATCH 05/31] fixed video context filter --- modules/feedadBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/feedadBidAdapter.js b/modules/feedadBidAdapter.js index 07c601d17ae..c6b705c4e6e 100644 --- a/modules/feedadBidAdapter.js +++ b/modules/feedadBidAdapter.js @@ -82,7 +82,7 @@ export function isValidPlacementId(placementId) { function filterSupportedMediaTypes(mediaTypes) { return { banner: mediaTypes.banner, - video: mediaTypes.video && mediaTypes.video.filter(video => video.context !== 'outstream'), + video: mediaTypes.video && mediaTypes.video.filter(video => video.context === 'outstream'), native: [] }; } From 478b00d4195872cd6da1631f0ca2dc0ce83ffc22 Mon Sep 17 00:00:00 2001 From: FeedAd GmbH Date: Mon, 20 May 2019 15:18:01 +0200 Subject: [PATCH 06/31] applied lint to feedad bid adapter --- modules/feedadBidAdapter.js | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/modules/feedadBidAdapter.js b/modules/feedadBidAdapter.js index c6b705c4e6e..ade684cdcec 100644 --- a/modules/feedadBidAdapter.js +++ b/modules/feedadBidAdapter.js @@ -67,11 +67,11 @@ function isValidClientToken(clientToken) { * @param placementId - the placement id to verify * @returns if the placement ID is valid. */ -export function isValidPlacementId(placementId) { - return typeof placementId === "string" - && placementId.length > 0 - && placementId.length <= 256 - && PLACEMENT_ID_PATTERN.test(placementId); +function isValidPlacementId(placementId) { + return typeof placementId === 'string' && + placementId.length > 0 && + placementId.length <= 256 && + PLACEMENT_ID_PATTERN.test(placementId); } /** @@ -123,7 +123,7 @@ function buildRequests(validBidRequests, bidderRequest) { * @returns {Bid[]} the FeedAd bids */ function interpretResponse(serverResponse, request) { - const body = typeof serverResponse.body === "string" ? JSON.parse(serverResponse.body) : serverResponse.body; + const body = typeof serverResponse.body === 'string' ? JSON.parse(serverResponse.body) : serverResponse.body; return body.requests.map((req, idx) => ({ requestId: req.bidId, cpm: 0.5, @@ -133,7 +133,7 @@ function interpretResponse(serverResponse, request) { ttl: 60, creativeId: `feedad-${body.id}-${idx}`, netRevenue: true, - currency: "EUR" + currency: 'EUR' })); } @@ -151,11 +151,11 @@ function createAdHTML(req) { placementId: '${req.params.placementId}', adOptions: {scaleMode: "parent_width"}, beforeAttach: (el, wrp) => { - wrp = document.createElement("div"); - wrp.style.width = '${req.sizes[0][0]}px'; - wrp.style.height = '${req.sizes[0][1]}px'; - el.appendChild(wrp); - return wrp; + wrp = document.createElement("div"); + wrp.style.width = '${req.sizes[0][0]}px'; + wrp.style.height = '${req.sizes[0][1]}px'; + el.appendChild(wrp); + return wrp; } }); @@ -174,10 +174,13 @@ export const spec = { getUserSyncs: function (syncOptions, serverResponses) { }, onTimeout: function (timeoutData) { + console.log('onTimeout', timeoutData); }, onBidWon: function (bid) { + console.log('onBidWon', bid); }, onSetTargeting: function (bid) { + console.log('onSetTargeting', bid); } }; From 494258e3f1a579eaabccd3937095d183f3b7fbe7 Mon Sep 17 00:00:00 2001 From: FeedAd GmbH Date: Mon, 20 May 2019 15:18:37 +0200 Subject: [PATCH 07/31] added unit test for bid request validation --- test/spec/modules/feedadBidAdapter_spec.js | 108 +++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 test/spec/modules/feedadBidAdapter_spec.js diff --git a/test/spec/modules/feedadBidAdapter_spec.js b/test/spec/modules/feedadBidAdapter_spec.js new file mode 100644 index 00000000000..f6573b44dd1 --- /dev/null +++ b/test/spec/modules/feedadBidAdapter_spec.js @@ -0,0 +1,108 @@ +import {expect} from 'chai'; +import {spec} from 'modules/feedadBidAdapter'; +import {newBidder} from 'src/adapters/bidderFactory'; +import {BANNER, NATIVE, VIDEO} from '../../../src/mediaTypes'; + +const CODE = 'feedad'; + +describe('FeedAdAdapter', function () { + const adapter = newBidder(spec); + + describe('Public API', function () { + it('should have the FeedAd bidder code', function () { + expect(spec.code).to.equal(CODE); + }); + it('should only support video and banner ads', function () { + expect(spec.supportedMediaTypes).to.be.a('array'); + expect(spec.supportedMediaTypes).to.include(BANNER); + expect(spec.supportedMediaTypes).to.include(VIDEO); + expect(spec.supportedMediaTypes).not.to.include(NATIVE); + }); + it('should include the BidderSpec functions', function () { + expect(spec.isBidRequestValid).to.be.a('function'); + expect(spec.buildRequests).to.be.a('function'); + expect(spec.interpretResponse).to.be.a('function'); + expect(spec.getUserSyncs).to.be.a('function'); + expect(spec.onTimeout).to.be.a('function'); + expect(spec.onBidWon).to.be.a('function'); + expect(spec.onSetTargeting).to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function () { + it('should detect missing params', function () { + let result = spec.isBidRequestValid({ + bidder: "feedad", + sizes: [] + }); + expect(result).to.equal(false); + }); + it('should detect missing client token', function () { + let result = spec.isBidRequestValid({ + bidder: "feedad", + sizes: [], + params: {placementId: "placement"} + }); + expect(result).to.equal(false); + }); + it('should detect zero length client token', function () { + let result = spec.isBidRequestValid({ + bidder: "feedad", + sizes: [], + params: {clientToken: "", placementId: "placement"} + }); + expect(result).to.equal(false); + }); + it('should detect missing placement id', function () { + let result = spec.isBidRequestValid({ + bidder: "feedad", + sizes: [], + params: {clientToken: "clientToken"} + }); + expect(result).to.equal(false); + }); + it('should detect zero length placement id', function () { + let result = spec.isBidRequestValid({ + bidder: "feedad", + sizes: [], + params: {clientToken: "clientToken", placementId: ""} + }); + expect(result).to.equal(false); + }); + it('should detect too long placement id', function () { + var placementId = ""; + for (var i = 0; i < 300; i++) { + placementId += "a"; + } + let result = spec.isBidRequestValid({ + bidder: "feedad", + sizes: [], + params: {clientToken: "clientToken", placementId} + }); + expect(result).to.equal(false); + }); + it('should detect invalid placement id', function () { + [ + "placement id with spaces", + "some|id", + "PLACEMENTID", + "placeme:ntId" + ].forEach(id => { + let result = spec.isBidRequestValid({ + bidder: "feedad", + sizes: [], + params: {clientToken: "clientToken", placementId: id} + }); + expect(result).to.equal(false); + }); + }); + it('should accept valid parameters', function () { + let result = spec.isBidRequestValid({ + bidder: "feedad", + sizes: [], + params: {clientToken: "clientToken", placementId: "placement-id"} + }); + expect(result).to.equal(true); + }); + }); +}); From c7111e8b497ca695a9488b1509ebadbc781ac984 Mon Sep 17 00:00:00 2001 From: FeedAd GmbH Date: Mon, 20 May 2019 17:00:51 +0200 Subject: [PATCH 08/31] added buildRequest unit test --- modules/feedadBidAdapter.js | 6 +- test/spec/modules/feedadBidAdapter_spec.js | 157 ++++++++++++++++++--- 2 files changed, 139 insertions(+), 24 deletions(-) diff --git a/modules/feedadBidAdapter.js b/modules/feedadBidAdapter.js index ade684cdcec..44887f5bdcd 100644 --- a/modules/feedadBidAdapter.js +++ b/modules/feedadBidAdapter.js @@ -82,8 +82,8 @@ function isValidPlacementId(placementId) { function filterSupportedMediaTypes(mediaTypes) { return { banner: mediaTypes.banner, - video: mediaTypes.video && mediaTypes.video.filter(video => video.context === 'outstream'), - native: [] + video: mediaTypes.video && mediaTypes.video.context === 'outstream' ? mediaTypes.video : undefined, + native: undefined }; } @@ -93,7 +93,7 @@ function filterSupportedMediaTypes(mediaTypes) { * @return {boolean} true if the types are empty */ function isMediaTypesEmpty(mediaTypes) { - return Object.keys(mediaTypes).every(type => mediaTypes[type].length === 0); + return Object.keys(mediaTypes).every(type => mediaTypes[type] === undefined); } /** diff --git a/test/spec/modules/feedadBidAdapter_spec.js b/test/spec/modules/feedadBidAdapter_spec.js index f6573b44dd1..4b92bd1276b 100644 --- a/test/spec/modules/feedadBidAdapter_spec.js +++ b/test/spec/modules/feedadBidAdapter_spec.js @@ -32,77 +32,192 @@ describe('FeedAdAdapter', function () { describe('isBidRequestValid', function () { it('should detect missing params', function () { let result = spec.isBidRequestValid({ - bidder: "feedad", + bidder: 'feedad', sizes: [] }); expect(result).to.equal(false); }); it('should detect missing client token', function () { let result = spec.isBidRequestValid({ - bidder: "feedad", + bidder: 'feedad', sizes: [], - params: {placementId: "placement"} + params: {placementId: 'placement'} }); expect(result).to.equal(false); }); it('should detect zero length client token', function () { let result = spec.isBidRequestValid({ - bidder: "feedad", + bidder: 'feedad', sizes: [], - params: {clientToken: "", placementId: "placement"} + params: {clientToken: '', placementId: 'placement'} }); expect(result).to.equal(false); }); it('should detect missing placement id', function () { let result = spec.isBidRequestValid({ - bidder: "feedad", + bidder: 'feedad', sizes: [], - params: {clientToken: "clientToken"} + params: {clientToken: 'clientToken'} }); expect(result).to.equal(false); }); it('should detect zero length placement id', function () { let result = spec.isBidRequestValid({ - bidder: "feedad", + bidder: 'feedad', sizes: [], - params: {clientToken: "clientToken", placementId: ""} + params: {clientToken: 'clientToken', placementId: ''} }); expect(result).to.equal(false); }); it('should detect too long placement id', function () { - var placementId = ""; + var placementId = ''; for (var i = 0; i < 300; i++) { - placementId += "a"; + placementId += 'a'; } let result = spec.isBidRequestValid({ - bidder: "feedad", + bidder: 'feedad', sizes: [], - params: {clientToken: "clientToken", placementId} + params: {clientToken: 'clientToken', placementId} }); expect(result).to.equal(false); }); it('should detect invalid placement id', function () { [ - "placement id with spaces", - "some|id", - "PLACEMENTID", - "placeme:ntId" + 'placement id with spaces', + 'some|id', + 'PLACEMENTID', + 'placeme:ntId' ].forEach(id => { let result = spec.isBidRequestValid({ - bidder: "feedad", + bidder: 'feedad', sizes: [], - params: {clientToken: "clientToken", placementId: id} + params: {clientToken: 'clientToken', placementId: id} }); expect(result).to.equal(false); }); }); it('should accept valid parameters', function () { let result = spec.isBidRequestValid({ - bidder: "feedad", + bidder: 'feedad', sizes: [], - params: {clientToken: "clientToken", placementId: "placement-id"} + params: {clientToken: 'clientToken', placementId: 'placement-id'} }); expect(result).to.equal(true); }); }); + + describe('buildRequests', function () { + it('should accept empty lists', function () { + let result = spec.buildRequests([]); + expect(result.data.requests).to.be.empty; + }); + it('should filter native media types', function () { + let bid = { + code: 'feedad', + mediaTypes: { + native: { + sizes: [[300, 250], [300, 600]], + } + }, + params: {clientToken: 'clientToken', placementId: 'placement-id'} + }; + let result = spec.buildRequests([bid]); + expect(result.data.requests).to.be.empty; + }); + it('should filter video media types without outstream context', function () { + let bid = { + code: 'feedad', + mediaTypes: { + video: { + context: 'instream' + } + }, + params: {clientToken: 'clientToken', placementId: 'placement-id'} + }; + let result = spec.buildRequests([bid]); + expect(result.data.requests).to.be.empty; + }); + it('should pass through outstream video media', function () { + let bid = { + code: 'feedad', + mediaTypes: { + video: { + context: 'outstream' + } + }, + params: {clientToken: 'clientToken', placementId: 'placement-id'} + }; + let result = spec.buildRequests([bid]); + expect(result.data.requests).to.be.lengthOf(1); + expect(result.data.requests[0]).to.deep.equal(bid); + }); + it('should pass through banner media', function () { + let bid = { + code: 'feedad', + mediaTypes: { + banner: { + sizes: [[320, 250]] + } + }, + params: {clientToken: 'clientToken', placementId: 'placement-id'} + }; + let result = spec.buildRequests([bid]); + expect(result.data.requests).to.be.lengthOf(1); + expect(result.data.requests[0]).to.deep.equal(bid); + }); + it('should detect empty media types', function () { + let bid = { + code: 'feedad', + mediaTypes: { + banner: undefined, + video: undefined, + native: undefined + }, + params: {clientToken: 'clientToken', placementId: 'placement-id'} + }; + let result = spec.buildRequests([bid]); + expect(result.data.requests).to.be.empty; + }); + it('should use POST', function () { + let bid = { + code: 'feedad', + mediaTypes: { + banner: { + sizes: [[320, 250]] + } + }, + params: {clientToken: 'clientToken', placementId: 'placement-id'} + }; + let result = spec.buildRequests([bid]); + expect(result.method).to.equal('POST'); + }); + it('should use the correct URL', function () { + let bid = { + code: 'feedad', + mediaTypes: { + banner: { + sizes: [[320, 250]] + } + }, + params: {clientToken: 'clientToken', placementId: 'placement-id'} + }; + let result = spec.buildRequests([bid]); + expect(result.url).to.equal('http://localhost:3000/bidRequests'); + }); + it('should specify the content type explicitly', function () { + let bid = { + code: 'feedad', + mediaTypes: { + banner: { + sizes: [[320, 250]] + } + }, + params: {clientToken: 'clientToken', placementId: 'placement-id'} + }; + let result = spec.buildRequests([bid]); + expect(result.options).to.deep.equal({ + contentType: 'application/json' + }) + }); + }); }); From e57779ccc637345554bb49b0500082810fdc96ef Mon Sep 17 00:00:00 2001 From: FeedAd GmbH Date: Wed, 22 May 2019 09:42:17 +0200 Subject: [PATCH 09/31] added unit tests for timeout and bid won callbacks --- modules/feedadBidAdapter.js | 25 +++++++++-- test/spec/modules/feedadBidAdapter_spec.js | 50 ++++++++++++++++++++++ 2 files changed, 71 insertions(+), 4 deletions(-) diff --git a/modules/feedadBidAdapter.js b/modules/feedadBidAdapter.js index 44887f5bdcd..edce1e022a4 100644 --- a/modules/feedadBidAdapter.js +++ b/modules/feedadBidAdapter.js @@ -1,6 +1,7 @@ import * as utils from 'src/utils'; import {registerBidder} from 'src/adapters/bidderFactory'; import {BANNER, VIDEO} from '../src/mediaTypes'; +import {ajax} from '../src/ajax'; /** * Bidder network identity code @@ -173,11 +174,27 @@ export const spec = { interpretResponse, getUserSyncs: function (syncOptions, serverResponses) { }, - onTimeout: function (timeoutData) { - console.log('onTimeout', timeoutData); + onTimeout: function (timeoutData, xhr) { + if (!timeoutData) { + return; + } + xhr = typeof xhr === 'function' ? xhr : ajax; + xhr('http://localhost:3000/onTimeout', null, JSON.stringify(timeoutData), { + withCredentials: true, + method: 'POST', + contentType: 'application/json' + }) }, - onBidWon: function (bid) { - console.log('onBidWon', bid); + onBidWon: function (bid, xhr) { + if (!bid) { + return; + } + xhr = typeof xhr === "function" ? xhr : ajax; + xhr('http://localhost:3000/onBidWon', null, JSON.stringify(bid), { + withCredentials: true, + method: 'POST', + contentType: 'application/json' + }) }, onSetTargeting: function (bid) { console.log('onSetTargeting', bid); diff --git a/test/spec/modules/feedadBidAdapter_spec.js b/test/spec/modules/feedadBidAdapter_spec.js index 4b92bd1276b..64c0804ed97 100644 --- a/test/spec/modules/feedadBidAdapter_spec.js +++ b/test/spec/modules/feedadBidAdapter_spec.js @@ -2,6 +2,8 @@ import {expect} from 'chai'; import {spec} from 'modules/feedadBidAdapter'; import {newBidder} from 'src/adapters/bidderFactory'; import {BANNER, NATIVE, VIDEO} from '../../../src/mediaTypes'; +import * as xhr from '../../../src/ajax'; +import * as sinon from 'sinon'; const CODE = 'feedad'; @@ -220,4 +222,52 @@ describe('FeedAdAdapter', function () { }) }); }); + + describe('onTimeout', function () { + let xhr; + + beforeEach(function () { + xhr = sinon.stub(); + }); + + it('should send parameters to backend', function () { + let params = {some: 'parameters'}; + spec.onTimeout(params, xhr); + expect(xhr.calledOnceWith('http://localhost:3000/onTimeout', null, JSON.stringify(params), { + withCredentials: true, + method: 'POST', + contentType: 'application/json' + })).to.be.true; + }); + + it('should do nothing on empty data', function () { + spec.onTimeout(undefined, xhr); + spec.onTimeout(null, xhr); + expect(xhr.called).to.be.false; + }); + }); + + describe('onBidWon', function () { + let xhr; + + beforeEach(function () { + xhr = sinon.stub(); + }); + + it('should send parameters to backend', function () { + let params = {some: 'parameters'}; + spec.onBidWon(params, xhr); + expect(xhr.calledOnceWith('http://localhost:3000/onBidWon', null, JSON.stringify(params), { + withCredentials: true, + method: 'POST', + contentType: 'application/json' + })).to.be.true; + }); + + it('should do nothing on empty data', function () { + spec.onBidWon(undefined, xhr); + spec.onBidWon(null, xhr); + expect(xhr.called).to.be.false; + }); + }); }); From 0af865ca0c37f8e5f0255169d5245a2a784e34c9 Mon Sep 17 00:00:00 2001 From: FeedAd GmbH Date: Mon, 3 Jun 2019 09:31:38 +0200 Subject: [PATCH 10/31] updated bid request to FeedAd API --- modules/feedadBidAdapter.js | 59 +++++++++++++++++++++++++++++++++---- 1 file changed, 54 insertions(+), 5 deletions(-) diff --git a/modules/feedadBidAdapter.js b/modules/feedadBidAdapter.js index edce1e022a4..465376692c7 100644 --- a/modules/feedadBidAdapter.js +++ b/modules/feedadBidAdapter.js @@ -3,6 +3,30 @@ import {registerBidder} from 'src/adapters/bidderFactory'; import {BANNER, VIDEO} from '../src/mediaTypes'; import {ajax} from '../src/ajax'; +/** + * Version of the FeedAd bid adapter + * @type {string} + */ +const VERSION = "1.0.0"; + +/** + * @typedef {object} FeedAdApiBidRequest + * @inner + * + * @property {number} ad_type + * @property {string} client_token + * @property {string} placement_id + * @property {string} sdk_version + * @property {boolean} app_hybrid + * + * @property {string} [app_bundle_id] + * @property {string} [app_name] + * @property {object} [custom_params] + * @property {number} [connectivity] + * @property {string} [device_adid] + * @property {string} [device_platform] + */ + /** * Bidder network identity code * @type {string} @@ -27,6 +51,9 @@ const TAG = '[FeedAd]'; */ const PLACEMENT_ID_PATTERN = /^(([a-z0-9])+[-_]?)+$/; +const API_ENDPOINT = 'https://feedad-backend-dev.appspot.com'; +const API_PATH_BID_REQUEST = '/1/prebid/web/bids'; + /** * Checks if the bid is compatible with FeedAd. * @@ -97,6 +124,21 @@ function isMediaTypesEmpty(mediaTypes) { return Object.keys(mediaTypes).every(type => mediaTypes[type] === undefined); } +/** + * Creates the bid request params the api expects from the prebid bid request + * @param {BidRequest} request - the validated prebid bid request + * @return {FeedAdApiBidRequest} + */ +function createApiBidRParams(request) { + return { + ad_type: 0, + client_token: request.params.clientToken, + placement_id: request.params.placementId, + sdk_version: `prebid_${VERSION}`, + app_hybrid: false, + }; +} + /** * Builds the bid request to the FeedAd Server * @param {BidRequest[]} validBidRequests - all validated bid requests @@ -105,16 +147,23 @@ function isMediaTypesEmpty(mediaTypes) { */ function buildRequests(validBidRequests, bidderRequest) { let acceptableRequests = validBidRequests.filter(request => !isMediaTypesEmpty(filterSupportedMediaTypes(request.mediaTypes))); + if (acceptableRequests.length === 0) { + return []; + } + let data = Object.assign({}, bidderRequest, { + bids: acceptableRequests.map(req => { + req.params = createApiBidRParams(req); + return req; + }) + }); return { method: 'POST', - url: 'http://localhost:3000/bidRequests', - data: { - requests: acceptableRequests - }, + url: `${API_ENDPOINT}${API_PATH_BID_REQUEST}`, + data, options: { contentType: 'application/json' } - } + }; } /** From 4d218990dcfbc578b2c76530b455c0189eac1d7a Mon Sep 17 00:00:00 2001 From: FeedAd GmbH Date: Mon, 3 Jun 2019 10:30:02 +0200 Subject: [PATCH 11/31] added parsing of feedad api bid response --- modules/feedadBidAdapter.js | 36 +++++++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/modules/feedadBidAdapter.js b/modules/feedadBidAdapter.js index 465376692c7..cd59d588836 100644 --- a/modules/feedadBidAdapter.js +++ b/modules/feedadBidAdapter.js @@ -27,6 +27,21 @@ const VERSION = "1.0.0"; * @property {string} [device_platform] */ +/** + * @typedef {object} FeedAdApiBidResponse + * @inner + * + * @property {string} ad - Ad HTML payload + * @property {number} cpm - number / float + * @property {string} creativeId - ID of creative for tracking + * @property {string} currency - 3-letter ISO 4217 currency-code + * @property {number} height - Height of creative returned in [].ad + * @property {boolean} netRevenue - Is the CPM net (true) or gross (false)? + * @property {string} requestId - bids[].bidId + * @property {number} ttl - Time to live for this ad + * @property {number} width - Width of creative returned in [].ad + */ + /** * Bidder network identity code * @type {string} @@ -173,17 +188,20 @@ function buildRequests(validBidRequests, bidderRequest) { * @returns {Bid[]} the FeedAd bids */ function interpretResponse(serverResponse, request) { + /** + * @type FeedAdApiBidResponse[] + */ const body = typeof serverResponse.body === 'string' ? JSON.parse(serverResponse.body) : serverResponse.body; - return body.requests.map((req, idx) => ({ - requestId: req.bidId, + return body.map((response, idx) => ({ + requestId: response.requestId, cpm: 0.5, - width: req.sizes[0][0], - height: req.sizes[0][1], - ad: createAdHTML(req), - ttl: 60, - creativeId: `feedad-${body.id}-${idx}`, - netRevenue: true, - currency: 'EUR' + width: response.width, + height: response.height, + ad: `${response.ad}`, + ttl: response.ttl, + creativeId: response.creativeId, + netRevenue: response.netRevenue, + currency: response.currency })); } From 318535843e3a815d921342c1591aefcb79565fe5 Mon Sep 17 00:00:00 2001 From: FeedAd GmbH Date: Wed, 5 Jun 2019 19:32:40 +0200 Subject: [PATCH 12/31] added transmisison of tracking events to FeedAd Api --- modules/feedadBidAdapter.js | 89 ++++++++++++++++++++++++++++++------- 1 file changed, 74 insertions(+), 15 deletions(-) diff --git a/modules/feedadBidAdapter.js b/modules/feedadBidAdapter.js index cd59d588836..7b3f6796492 100644 --- a/modules/feedadBidAdapter.js +++ b/modules/feedadBidAdapter.js @@ -42,6 +42,25 @@ const VERSION = "1.0.0"; * @property {number} width - Width of creative returned in [].ad */ +/** + * @typedef {object} FeedAdApiTrackingParams + * @inner + * + * app_hybrid {boolean} + * client_token {string} + * klass {'prebid_bidWon'|'prebid_bidTimeout'} + * placement_id {string} + * prebid_auction_id {string} + * prebid_bid_id {string} + * prebid_transaction_id {string} + * referer {string} + * sdk_version {string} + * [app_bundle_id] {string} + * [app_name] {string} + * [device_adid] {string} + * [device_platform] {1|2|3} 1 - Android | 2 - iOS | 3 - Windows + */ + /** * Bidder network identity code * @type {string} @@ -68,6 +87,13 @@ const PLACEMENT_ID_PATTERN = /^(([a-z0-9])+[-_]?)+$/; const API_ENDPOINT = 'https://feedad-backend-dev.appspot.com'; const API_PATH_BID_REQUEST = '/1/prebid/web/bids'; +const API_PATH_TRACK_REQUEST = '/1/prebid/web/events'; + +/** + * Stores temporary auction metadata + * @type {Object.} + */ +const BID_METADATA = {}; /** * Checks if the bid is compatible with FeedAd. @@ -171,6 +197,10 @@ function buildRequests(validBidRequests, bidderRequest) { return req; }) }); + data.bids.forEach(bid => BID_METADATA[bid.bidId] = { + referer: data.refererInfo.referer, + transactionId: bid.transactionId + }); return { method: 'POST', url: `${API_ENDPOINT}${API_PATH_BID_REQUEST}`, @@ -230,6 +260,32 @@ function createAdHTML(req) { `; } +/** + * Creates the parameters for the FeedAd tracking call + * @param {object} data - prebid data + * @param {'prebid_bidWon'|'prebid_bidTimeout'} klass - type of tracking call + * @return {FeedAdApiTrackingParams|null} + */ +function createTrackingParams(data, klass) { + const bidId = data.bidId || data.requestId; + if (!BID_METADATA.hasOwnProperty(bidId)) { + return null; + } + const {referer, transactionId} = BID_METADATA[bidId]; + delete BID_METADATA[bidId]; + return { + app_hybrid: false, + client_token: data.params[0].clientToken, + placement_id: data.params[0].placementId, + klass, + prebid_auction_id: data.auctionId, + prebid_bid_id: bidId, + prebid_transaction_id: transactionId, + referer, + sdk_version: VERSION + }; +} + /** * @type {BidderSpec} */ @@ -245,26 +301,29 @@ export const spec = { if (!timeoutData) { return; } - xhr = typeof xhr === 'function' ? xhr : ajax; - xhr('http://localhost:3000/onTimeout', null, JSON.stringify(timeoutData), { - withCredentials: true, - method: 'POST', - contentType: 'application/json' - }) + let params = createTrackingParams(timeoutData, 'prebid_bidTimeout'); + if (params) { + xhr = typeof xhr === 'function' ? xhr : ajax; + xhr(`${API_ENDPOINT}${API_PATH_TRACK_REQUEST}`, null, JSON.stringify(params), { + withCredentials: true, + method: 'POST', + contentType: 'application/json' + }); + } }, onBidWon: function (bid, xhr) { if (!bid) { return; } - xhr = typeof xhr === "function" ? xhr : ajax; - xhr('http://localhost:3000/onBidWon', null, JSON.stringify(bid), { - withCredentials: true, - method: 'POST', - contentType: 'application/json' - }) - }, - onSetTargeting: function (bid) { - console.log('onSetTargeting', bid); + let params = createTrackingParams(bid, 'prebid_bidWon'); + if (params) { + xhr = typeof xhr === 'function' ? xhr : ajax; + xhr(`${API_ENDPOINT}${API_PATH_TRACK_REQUEST}`, null, JSON.stringify(params), { + withCredentials: true, + method: 'POST', + contentType: 'application/json' + }); + } } }; From d7673e4f47a234a1d5dd4efdb139d3bd4f874820 Mon Sep 17 00:00:00 2001 From: FeedAd GmbH Date: Fri, 7 Jun 2019 10:16:20 +0200 Subject: [PATCH 13/31] code cleanup --- modules/feedadBidAdapter.js | 84 ++++++++++--------------------------- 1 file changed, 21 insertions(+), 63 deletions(-) diff --git a/modules/feedadBidAdapter.js b/modules/feedadBidAdapter.js index 7b3f6796492..e0bab956d02 100644 --- a/modules/feedadBidAdapter.js +++ b/modules/feedadBidAdapter.js @@ -221,43 +221,7 @@ function interpretResponse(serverResponse, request) { /** * @type FeedAdApiBidResponse[] */ - const body = typeof serverResponse.body === 'string' ? JSON.parse(serverResponse.body) : serverResponse.body; - return body.map((response, idx) => ({ - requestId: response.requestId, - cpm: 0.5, - width: response.width, - height: response.height, - ad: `${response.ad}`, - ttl: response.ttl, - creativeId: response.creativeId, - netRevenue: response.netRevenue, - currency: response.currency - })); -} - -/** - * Creates the HTML content for a FeedAd creative - * @param {object} req - the server response body - * @return {string} the HTML string - */ -function createAdHTML(req) { - return ` - - -`; + return typeof serverResponse.body === 'string' ? JSON.parse(serverResponse.body) : serverResponse.body; } /** @@ -287,35 +251,16 @@ function createTrackingParams(data, klass) { } /** - * @type {BidderSpec} + * Creates a tracking handler for the given event type + * @param klass - the event type + * @return {Function} the tracking handler function */ -export const spec = { - code: BIDDER_CODE, - supportedMediaTypes: MEDIA_TYPES, - isBidRequestValid, - buildRequests, - interpretResponse, - getUserSyncs: function (syncOptions, serverResponses) { - }, - onTimeout: function (timeoutData, xhr) { - if (!timeoutData) { - return; - } - let params = createTrackingParams(timeoutData, 'prebid_bidTimeout'); - if (params) { - xhr = typeof xhr === 'function' ? xhr : ajax; - xhr(`${API_ENDPOINT}${API_PATH_TRACK_REQUEST}`, null, JSON.stringify(params), { - withCredentials: true, - method: 'POST', - contentType: 'application/json' - }); - } - }, - onBidWon: function (bid, xhr) { - if (!bid) { +function trackingHandlerFactory(klass) { + return (data, xhr) => { + if (!data) { return; } - let params = createTrackingParams(bid, 'prebid_bidWon'); + let params = createTrackingParams(data, klass); if (params) { xhr = typeof xhr === 'function' ? xhr : ajax; xhr(`${API_ENDPOINT}${API_PATH_TRACK_REQUEST}`, null, JSON.stringify(params), { @@ -325,6 +270,19 @@ export const spec = { }); } } +} + +/** + * @type {BidderSpec} + */ +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: MEDIA_TYPES, + isBidRequestValid, + buildRequests, + interpretResponse, + onTimeout: trackingHandlerFactory('prebid_bidTimeout'), + onBidWon: trackingHandlerFactory('prebid_bidWon') }; registerBidder(spec); From 79a3cbc37e68060fd85bb1059d1a40d7bdfcd576 Mon Sep 17 00:00:00 2001 From: FeedAd GmbH Date: Fri, 7 Jun 2019 10:58:11 +0200 Subject: [PATCH 14/31] updated feedad unit tests for buildRequest method --- modules/feedadBidAdapter.js | 5 +- test/spec/modules/feedadBidAdapter_spec.js | 77 +++++++++++++++------- 2 files changed, 56 insertions(+), 26 deletions(-) diff --git a/modules/feedadBidAdapter.js b/modules/feedadBidAdapter.js index e0bab956d02..6b094caedd7 100644 --- a/modules/feedadBidAdapter.js +++ b/modules/feedadBidAdapter.js @@ -7,7 +7,7 @@ import {ajax} from '../src/ajax'; * Version of the FeedAd bid adapter * @type {string} */ -const VERSION = "1.0.0"; +const VERSION = '1.0.0'; /** * @typedef {object} FeedAdApiBidRequest @@ -187,6 +187,9 @@ function createApiBidRParams(request) { * @return {ServerRequest|ServerRequest[]} */ function buildRequests(validBidRequests, bidderRequest) { + if (!bidderRequest) { + return []; + } let acceptableRequests = validBidRequests.filter(request => !isMediaTypesEmpty(filterSupportedMediaTypes(request.mediaTypes))); if (acceptableRequests.length === 0) { return []; diff --git a/test/spec/modules/feedadBidAdapter_spec.js b/test/spec/modules/feedadBidAdapter_spec.js index 64c0804ed97..3a728accb36 100644 --- a/test/spec/modules/feedadBidAdapter_spec.js +++ b/test/spec/modules/feedadBidAdapter_spec.js @@ -1,15 +1,11 @@ import {expect} from 'chai'; import {spec} from 'modules/feedadBidAdapter'; -import {newBidder} from 'src/adapters/bidderFactory'; import {BANNER, NATIVE, VIDEO} from '../../../src/mediaTypes'; -import * as xhr from '../../../src/ajax'; import * as sinon from 'sinon'; const CODE = 'feedad'; describe('FeedAdAdapter', function () { - const adapter = newBidder(spec); - describe('Public API', function () { it('should have the FeedAd bidder code', function () { expect(spec.code).to.equal(CODE); @@ -20,14 +16,12 @@ describe('FeedAdAdapter', function () { expect(spec.supportedMediaTypes).to.include(VIDEO); expect(spec.supportedMediaTypes).not.to.include(NATIVE); }); - it('should include the BidderSpec functions', function () { + it('should export the BidderSpec functions', function () { expect(spec.isBidRequestValid).to.be.a('function'); expect(spec.buildRequests).to.be.a('function'); expect(spec.interpretResponse).to.be.a('function'); - expect(spec.getUserSyncs).to.be.a('function'); expect(spec.onTimeout).to.be.a('function'); expect(spec.onBidWon).to.be.a('function'); - expect(spec.onSetTargeting).to.be.a('function'); }); }); @@ -109,9 +103,16 @@ describe('FeedAdAdapter', function () { }); describe('buildRequests', function () { + const bidderRequest = { + refererInfo: { + referer: 'the referer' + }, + some: 'thing' + }; + it('should accept empty lists', function () { - let result = spec.buildRequests([]); - expect(result.data.requests).to.be.empty; + let result = spec.buildRequests([], bidderRequest); + expect(result).to.be.empty; }); it('should filter native media types', function () { let bid = { @@ -123,8 +124,8 @@ describe('FeedAdAdapter', function () { }, params: {clientToken: 'clientToken', placementId: 'placement-id'} }; - let result = spec.buildRequests([bid]); - expect(result.data.requests).to.be.empty; + let result = spec.buildRequests([bid], bidderRequest); + expect(result).to.be.empty; }); it('should filter video media types without outstream context', function () { let bid = { @@ -136,8 +137,8 @@ describe('FeedAdAdapter', function () { }, params: {clientToken: 'clientToken', placementId: 'placement-id'} }; - let result = spec.buildRequests([bid]); - expect(result.data.requests).to.be.empty; + let result = spec.buildRequests([bid], bidderRequest); + expect(result).to.be.empty; }); it('should pass through outstream video media', function () { let bid = { @@ -149,9 +150,9 @@ describe('FeedAdAdapter', function () { }, params: {clientToken: 'clientToken', placementId: 'placement-id'} }; - let result = spec.buildRequests([bid]); - expect(result.data.requests).to.be.lengthOf(1); - expect(result.data.requests[0]).to.deep.equal(bid); + let result = spec.buildRequests([bid], bidderRequest); + expect(result.data.bids).to.be.lengthOf(1); + expect(result.data.bids[0]).to.deep.equal(bid); }); it('should pass through banner media', function () { let bid = { @@ -163,9 +164,9 @@ describe('FeedAdAdapter', function () { }, params: {clientToken: 'clientToken', placementId: 'placement-id'} }; - let result = spec.buildRequests([bid]); - expect(result.data.requests).to.be.lengthOf(1); - expect(result.data.requests[0]).to.deep.equal(bid); + let result = spec.buildRequests([bid], bidderRequest); + expect(result.data.bids).to.be.lengthOf(1); + expect(result.data.bids[0]).to.deep.equal(bid); }); it('should detect empty media types', function () { let bid = { @@ -177,8 +178,8 @@ describe('FeedAdAdapter', function () { }, params: {clientToken: 'clientToken', placementId: 'placement-id'} }; - let result = spec.buildRequests([bid]); - expect(result.data.requests).to.be.empty; + let result = spec.buildRequests([bid], bidderRequest); + expect(result).to.be.empty; }); it('should use POST', function () { let bid = { @@ -190,7 +191,7 @@ describe('FeedAdAdapter', function () { }, params: {clientToken: 'clientToken', placementId: 'placement-id'} }; - let result = spec.buildRequests([bid]); + let result = spec.buildRequests([bid], bidderRequest); expect(result.method).to.equal('POST'); }); it('should use the correct URL', function () { @@ -203,8 +204,8 @@ describe('FeedAdAdapter', function () { }, params: {clientToken: 'clientToken', placementId: 'placement-id'} }; - let result = spec.buildRequests([bid]); - expect(result.url).to.equal('http://localhost:3000/bidRequests'); + let result = spec.buildRequests([bid], bidderRequest); + expect(result.url).to.equal('https://feedad-backend-dev.appspot.com/1/prebid/web/bids'); }); it('should specify the content type explicitly', function () { let bid = { @@ -216,11 +217,37 @@ describe('FeedAdAdapter', function () { }, params: {clientToken: 'clientToken', placementId: 'placement-id'} }; - let result = spec.buildRequests([bid]); + let result = spec.buildRequests([bid], bidderRequest); expect(result.options).to.deep.equal({ contentType: 'application/json' }) }); + it('should include the bidder request', function () { + let bid = { + code: 'feedad', + mediaTypes: { + banner: { + sizes: [[320, 250]] + } + }, + params: {clientToken: 'clientToken', placementId: 'placement-id'} + }; + let result = spec.buildRequests([bid, bid, bid], bidderRequest); + expect(result.data).to.deep.include(bidderRequest); + }); + it('should detect missing bidder request parameter', function () { + let bid = { + code: 'feedad', + mediaTypes: { + banner: { + sizes: [[320, 250]] + } + }, + params: {clientToken: 'clientToken', placementId: 'placement-id'} + }; + let result = spec.buildRequests([bid, bid, bid]); + expect(result).to.be.empty; + }); }); describe('onTimeout', function () { From d446300392b3c9ec73d6f777242b14d96de8d265 Mon Sep 17 00:00:00 2001 From: FeedAd GmbH Date: Fri, 7 Jun 2019 15:07:29 +0200 Subject: [PATCH 15/31] added unit tests for event tracking implementation --- modules/feedadBidAdapter.js | 26 +-- test/spec/modules/feedadBidAdapter_spec.js | 189 ++++++++++++++++----- 2 files changed, 162 insertions(+), 53 deletions(-) diff --git a/modules/feedadBidAdapter.js b/modules/feedadBidAdapter.js index 6b094caedd7..c55430b1a14 100644 --- a/modules/feedadBidAdapter.js +++ b/modules/feedadBidAdapter.js @@ -46,19 +46,19 @@ const VERSION = '1.0.0'; * @typedef {object} FeedAdApiTrackingParams * @inner * - * app_hybrid {boolean} - * client_token {string} - * klass {'prebid_bidWon'|'prebid_bidTimeout'} - * placement_id {string} - * prebid_auction_id {string} - * prebid_bid_id {string} - * prebid_transaction_id {string} - * referer {string} - * sdk_version {string} - * [app_bundle_id] {string} - * [app_name] {string} - * [device_adid] {string} - * [device_platform] {1|2|3} 1 - Android | 2 - iOS | 3 - Windows + * @property app_hybrid {boolean} + * @property client_token {string} + * @property klass {'prebid_bidWon'|'prebid_bidTimeout'} + * @property placement_id {string} + * @property prebid_auction_id {string} + * @property prebid_bid_id {string} + * @property prebid_transaction_id {string} + * @property referer {string} + * @property sdk_version {string} + * @property [app_bundle_id] {string} + * @property [app_name] {string} + * @property [device_adid] {string} + * @property [device_platform] {1|2|3} 1 - Android | 2 - iOS | 3 - Windows */ /** diff --git a/test/spec/modules/feedadBidAdapter_spec.js b/test/spec/modules/feedadBidAdapter_spec.js index 3a728accb36..bc2f5db9141 100644 --- a/test/spec/modules/feedadBidAdapter_spec.js +++ b/test/spec/modules/feedadBidAdapter_spec.js @@ -250,51 +250,160 @@ describe('FeedAdAdapter', function () { }); }); - describe('onTimeout', function () { - let xhr; - - beforeEach(function () { - xhr = sinon.stub(); - }); - - it('should send parameters to backend', function () { - let params = {some: 'parameters'}; - spec.onTimeout(params, xhr); - expect(xhr.calledOnceWith('http://localhost:3000/onTimeout', null, JSON.stringify(params), { - withCredentials: true, - method: 'POST', - contentType: 'application/json' - })).to.be.true; - }); + describe('event tracking calls', function () { + const clientToken = 'clientToken'; + const placementId = 'placement id'; + const auctionId = 'the auction id'; + const bidId = 'the bid id'; + const transactionId = 'the transaction id'; + const referer = 'the referer'; + const bidderRequest = { + refererInfo: { + referer: referer + }, + some: 'thing' + }; + const bid = { + 'bidder': 'feedad', + 'params': { + 'clientToken': 'fupp', + 'placementId': 'prebid-test' + }, + 'crumbs': { + 'pubcid': '6254a85f-bded-489a-9736-83c45d45ef1d' + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 250 + ] + ] + } + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': transactionId, + 'sizes': [ + [ + 300, + 250 + ] + ], + 'bidId': bidId, + 'bidderRequestId': '10739fe6fe2127', + 'auctionId': '5ac67dff-d971-4b56-84a3-345a87a1f786', + 'src': 'client', + 'bidRequestsCount': 1 + }; + const timeoutData = { + 'bidId': bidId, + 'bidder': 'feedad', + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'auctionId': auctionId, + 'params': [ + { + 'clientToken': clientToken, + 'placementId': placementId + } + ], + 'timeout': 3000 + }; + const bidWonData = { + 'bidderCode': 'feedad', + 'width': 300, + 'height': 250, + 'statusMessage': 'Bid available', + 'adId': '3a4529aa05114d', + 'requestId': bidId, + 'mediaType': 'banner', + 'source': 'client', + 'cpm': 0.5, + 'ad': 'ad content', + 'ttl': 60, + 'creativeId': 'feedad-21-0', + 'netRevenue': true, + 'currency': 'EUR', + 'auctionId': auctionId, + 'responseTimestamp': 1558365914596, + 'requestTimestamp': 1558365914506, + 'bidder': 'feedad', + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'timeToRespond': 90, + 'pbLg': '0.50', + 'pbMg': '0.50', + 'pbHg': '0.50', + 'pbAg': '0.50', + 'pbDg': '0.50', + 'pbCg': '', + 'size': '300x250', + 'adserverTargeting': { + 'hb_bidder': 'feedad', + 'hb_adid': '3a4529aa05114d', + 'hb_pb': '0.50', + 'hb_size': '300x250', + 'hb_source': 'client', + 'hb_format': 'banner' + }, + 'status': 'rendered', + 'params': [ + { + 'clientToken': clientToken, + 'placementId': placementId + } + ] + }; + const cases = [ + ['onTimeout', timeoutData, 'prebid_bidTimeout'], + ['onBidWon', bidWonData, 'prebid_bidWon'], + ]; - it('should do nothing on empty data', function () { - spec.onTimeout(undefined, xhr); - spec.onTimeout(null, xhr); - expect(xhr.called).to.be.false; - }); - }); + cases.forEach(([name, data, eventKlass]) => { + let subject = spec[name]; + describe(name + ' handler', function () { + let xhr; - describe('onBidWon', function () { - let xhr; + beforeEach(function () { + xhr = sinon.stub(); + }); - beforeEach(function () { - xhr = sinon.stub(); - }); + it('should do nothing on empty data', function () { + subject(undefined, xhr); + subject(null, xhr); + expect(xhr.called).to.be.false; + }); - it('should send parameters to backend', function () { - let params = {some: 'parameters'}; - spec.onBidWon(params, xhr); - expect(xhr.calledOnceWith('http://localhost:3000/onBidWon', null, JSON.stringify(params), { - withCredentials: true, - method: 'POST', - contentType: 'application/json' - })).to.be.true; - }); + it('should do nothing when bid metadata is not set', function () { + subject(data, xhr); + expect(xhr.callCount).to.equal(0); + }); - it('should do nothing on empty data', function () { - spec.onBidWon(undefined, xhr); - spec.onBidWon(null, xhr); - expect(xhr.called).to.be.false; + it('should send tracking params when correct metadata was set', function () { + spec.buildRequests([bid], bidderRequest); + let expectedData = { + app_hybrid: false, + client_token: clientToken, + placement_id: placementId, + klass: eventKlass, + prebid_auction_id: auctionId, + prebid_bid_id: bidId, + prebid_transaction_id: transactionId, + referer, + sdk_version: '1.0.0' + }; + subject(data, xhr); + expect(xhr.callCount).to.equal(1); + let call = xhr.getCall(0); + expect(call.args[0]).to.equal('https://feedad-backend-dev.appspot.com/1/prebid/web/events'); + expect(call.args[1]).to.be.null; + expect(JSON.parse(call.args[2])).to.deep.equal(expectedData); + expect(call.args[3]).to.deep.equal({ + withCredentials: true, + method: 'POST', + contentType: 'application/json' + }); + }) + }); }); }); }); From 766f23c1c85da907d8b328ea4e473b492f02a011 Mon Sep 17 00:00:00 2001 From: FeedAd GmbH Date: Fri, 7 Jun 2019 18:36:49 +0200 Subject: [PATCH 16/31] added unit test for interpretResponse method --- modules/feedadBidAdapter.js | 2 +- test/spec/modules/feedadBidAdapter_spec.js | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/modules/feedadBidAdapter.js b/modules/feedadBidAdapter.js index c55430b1a14..7c26e581e4c 100644 --- a/modules/feedadBidAdapter.js +++ b/modules/feedadBidAdapter.js @@ -265,7 +265,7 @@ function trackingHandlerFactory(klass) { } let params = createTrackingParams(data, klass); if (params) { - xhr = typeof xhr === 'function' ? xhr : ajax; + xhr = typeof xhr === 'function' ? xhr : ajax; // required to test calls to the ajax method because it cannot be mocked xhr(`${API_ENDPOINT}${API_PATH_TRACK_REQUEST}`, null, JSON.stringify(params), { withCredentials: true, method: 'POST', diff --git a/test/spec/modules/feedadBidAdapter_spec.js b/test/spec/modules/feedadBidAdapter_spec.js index bc2f5db9141..5bf64683380 100644 --- a/test/spec/modules/feedadBidAdapter_spec.js +++ b/test/spec/modules/feedadBidAdapter_spec.js @@ -250,6 +250,27 @@ describe('FeedAdAdapter', function () { }); }); + describe('interpretResponse', function () { + const body = [{ + foo: "bar", + sub: { + obj: 5 + } + }, { + bar: "foo" + }]; + + it('should convert string bodies to JSON', function () { + let result = spec.interpretResponse({body: JSON.stringify(body)}); + expect(result).to.deep.equal(body); + }); + + it('should pass through body objects', function () { + let result = spec.interpretResponse({body}); + expect(result).to.deep.equal(body); + }); + }); + describe('event tracking calls', function () { const clientToken = 'clientToken'; const placementId = 'placement id'; From 2d469c0bfba4ff11e1b5c1733a7ccadcf6551f7f Mon Sep 17 00:00:00 2001 From: FeedAd GmbH Date: Fri, 7 Jun 2019 18:50:04 +0200 Subject: [PATCH 17/31] added adapter documentation --- modules/feedadBidAdapter.md | 45 ++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/modules/feedadBidAdapter.md b/modules/feedadBidAdapter.md index 464090415c4..fd57025c29e 100644 --- a/modules/feedadBidAdapter.md +++ b/modules/feedadBidAdapter.md @@ -1 +1,44 @@ -# TODO +# Overview + +``` +Module Name: FeedAd Adapter +Module Type: Bidder Adapter +Maintainer: mail@feedad.com +``` + +# Description + +Prebid.JS adapter that connects to the FeedAd demand sources. + +# Test Parameters +``` + var adUnits = [ + { + code: 'test-div', + mediaTypes: { + banner: { // supports all banner sizes + sizes: [[300, 250]], + }, + video: { // supports only outstream video + context: 'outstream' + } + }, + bids: [ + { + bidder: "feedad", + params: { + clientToken: 'your-client-token' // see below for more info + placementId: 'your-placement-id' // see below for more info + } + } + ] + } + ]; +``` + +# Required Parameters + +| Parameter | Description | +| --------- | ----------- | +| `clientToken` | Your FeedAd web client token. You can view your client token inside the FeedAd admin panel. | +| `placementId` | You can choose placement IDs yourself. A placement ID should be named after the ad position inside your product. For example, if you want to display an ad inside a list of news articles, you could name it "ad-news-overview".
A placement ID may consist of lowercase `a-z`, `0-9`, `-` and `_`. You do not have to manually create the placement IDs before using them. Just specify them within the code, and they will appear in the FeedAd admin panel after the first request.
[Learn more](/concept/feed_ad/index.html) about Placement IDs and how they are grouped to play the same Creative. | From 138d1ff1a728ed8221b259cb90edbaef1d9a2131 Mon Sep 17 00:00:00 2001 From: FeedAd GmbH Date: Fri, 7 Jun 2019 19:06:04 +0200 Subject: [PATCH 18/31] added dedicated feedad example page --- integrationExamples/gpt/feedad_dfp.html | 96 +++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 integrationExamples/gpt/feedad_dfp.html diff --git a/integrationExamples/gpt/feedad_dfp.html b/integrationExamples/gpt/feedad_dfp.html new file mode 100644 index 00000000000..43305f87638 --- /dev/null +++ b/integrationExamples/gpt/feedad_dfp.html @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + +

Prebid.js Test

+
Div-1
+
+ +
+ + From e4c7214ed51f52eeb1ac0d534d0d9ea4e92afefa Mon Sep 17 00:00:00 2001 From: FeedAd GmbH Date: Fri, 7 Jun 2019 19:06:52 +0200 Subject: [PATCH 19/31] updated feedad adapter to use live system --- modules/feedadBidAdapter.js | 2 +- test/spec/modules/feedadBidAdapter_spec.js | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/feedadBidAdapter.js b/modules/feedadBidAdapter.js index 7c26e581e4c..b70314c5be4 100644 --- a/modules/feedadBidAdapter.js +++ b/modules/feedadBidAdapter.js @@ -85,7 +85,7 @@ const TAG = '[FeedAd]'; */ const PLACEMENT_ID_PATTERN = /^(([a-z0-9])+[-_]?)+$/; -const API_ENDPOINT = 'https://feedad-backend-dev.appspot.com'; +const API_ENDPOINT = 'https://api.feedad.com'; const API_PATH_BID_REQUEST = '/1/prebid/web/bids'; const API_PATH_TRACK_REQUEST = '/1/prebid/web/events'; diff --git a/test/spec/modules/feedadBidAdapter_spec.js b/test/spec/modules/feedadBidAdapter_spec.js index 5bf64683380..4bd163dacae 100644 --- a/test/spec/modules/feedadBidAdapter_spec.js +++ b/test/spec/modules/feedadBidAdapter_spec.js @@ -205,7 +205,7 @@ describe('FeedAdAdapter', function () { params: {clientToken: 'clientToken', placementId: 'placement-id'} }; let result = spec.buildRequests([bid], bidderRequest); - expect(result.url).to.equal('https://feedad-backend-dev.appspot.com/1/prebid/web/bids'); + expect(result.url).to.equal('https://api.feedad.com/1/prebid/web/bids'); }); it('should specify the content type explicitly', function () { let bid = { @@ -252,12 +252,12 @@ describe('FeedAdAdapter', function () { describe('interpretResponse', function () { const body = [{ - foo: "bar", + foo: 'bar', sub: { obj: 5 } }, { - bar: "foo" + bar: 'foo' }]; it('should convert string bodies to JSON', function () { @@ -415,7 +415,7 @@ describe('FeedAdAdapter', function () { subject(data, xhr); expect(xhr.callCount).to.equal(1); let call = xhr.getCall(0); - expect(call.args[0]).to.equal('https://feedad-backend-dev.appspot.com/1/prebid/web/events'); + expect(call.args[0]).to.equal('https://api.feedad.com/1/prebid/web/events'); expect(call.args[1]).to.be.null; expect(JSON.parse(call.args[2])).to.deep.equal(expectedData); expect(call.args[3]).to.deep.equal({ From 0eabd34facf3481b8e81672c75e36be841f21d2f Mon Sep 17 00:00:00 2001 From: FeedAd GmbH Date: Mon, 10 Jun 2019 15:39:00 +0200 Subject: [PATCH 20/31] updated FeedAd adapter placement ID regex --- modules/feedadBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/feedadBidAdapter.js b/modules/feedadBidAdapter.js index b70314c5be4..f180c2782cc 100644 --- a/modules/feedadBidAdapter.js +++ b/modules/feedadBidAdapter.js @@ -83,7 +83,7 @@ const TAG = '[FeedAd]'; * Pattern for valid placement IDs * @type {RegExp} */ -const PLACEMENT_ID_PATTERN = /^(([a-z0-9])+[-_]?)+$/; +const PLACEMENT_ID_PATTERN = /^(?:[a-z0-9]+[-_]?)+$/; const API_ENDPOINT = 'https://api.feedad.com'; const API_PATH_BID_REQUEST = '/1/prebid/web/bids'; From 1ff7caa53351aed660f35e8a632337f0b0f463df Mon Sep 17 00:00:00 2001 From: FeedAd GmbH Date: Tue, 11 Jun 2019 10:44:13 +0200 Subject: [PATCH 21/31] removed groups from FeedAd adapter placement ID regex --- modules/feedadBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/feedadBidAdapter.js b/modules/feedadBidAdapter.js index f180c2782cc..61caf416538 100644 --- a/modules/feedadBidAdapter.js +++ b/modules/feedadBidAdapter.js @@ -83,7 +83,7 @@ const TAG = '[FeedAd]'; * Pattern for valid placement IDs * @type {RegExp} */ -const PLACEMENT_ID_PATTERN = /^(?:[a-z0-9]+[-_]?)+$/; +const PLACEMENT_ID_PATTERN = /^[a-z0-9][a-z0-9_-]+[a-z0-9]$/; const API_ENDPOINT = 'https://api.feedad.com'; const API_PATH_BID_REQUEST = '/1/prebid/web/bids'; From 748590ea44e03edaa5a21e32f6460b15d67b832a Mon Sep 17 00:00:00 2001 From: FeedAd GmbH Date: Fri, 14 Jun 2019 11:51:42 +0200 Subject: [PATCH 22/31] removed dedicated feedad example page --- integrationExamples/gpt/feedad_dfp.html | 96 ------------------------- 1 file changed, 96 deletions(-) delete mode 100644 integrationExamples/gpt/feedad_dfp.html diff --git a/integrationExamples/gpt/feedad_dfp.html b/integrationExamples/gpt/feedad_dfp.html deleted file mode 100644 index 43305f87638..00000000000 --- a/integrationExamples/gpt/feedad_dfp.html +++ /dev/null @@ -1,96 +0,0 @@ - - - - - - - - - - - - - - -

Prebid.js Test

-
Div-1
-
- -
- - From 98e422ada066d20328cc4fad768df60ab3406500 Mon Sep 17 00:00:00 2001 From: FeedAd GmbH Date: Fri, 14 Jun 2019 11:52:57 +0200 Subject: [PATCH 23/31] updated imports in FeedAd adapter file to use relative paths --- modules/feedadBidAdapter.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/feedadBidAdapter.js b/modules/feedadBidAdapter.js index 61caf416538..2227d1c1116 100644 --- a/modules/feedadBidAdapter.js +++ b/modules/feedadBidAdapter.js @@ -1,5 +1,5 @@ -import * as utils from 'src/utils'; -import {registerBidder} from 'src/adapters/bidderFactory'; +import * as utils from '../src/utils'; +import {registerBidder} from '../src/adapters/bidderFactory'; import {BANNER, VIDEO} from '../src/mediaTypes'; import {ajax} from '../src/ajax'; From f2836f34de478f98207f0d4b7ef7256d39d13314 Mon Sep 17 00:00:00 2001 From: FeedAd GmbH Date: Fri, 14 Jun 2019 12:09:29 +0200 Subject: [PATCH 24/31] updated FeedAd adapter unit test to use sinon.useFakeXMLHttpRequest() --- modules/feedadBidAdapter.js | 5 ++- test/spec/modules/feedadBidAdapter_spec.js | 37 ++++++++++++---------- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/modules/feedadBidAdapter.js b/modules/feedadBidAdapter.js index 2227d1c1116..1e995ee8914 100644 --- a/modules/feedadBidAdapter.js +++ b/modules/feedadBidAdapter.js @@ -259,14 +259,13 @@ function createTrackingParams(data, klass) { * @return {Function} the tracking handler function */ function trackingHandlerFactory(klass) { - return (data, xhr) => { + return (data) => { if (!data) { return; } let params = createTrackingParams(data, klass); if (params) { - xhr = typeof xhr === 'function' ? xhr : ajax; // required to test calls to the ajax method because it cannot be mocked - xhr(`${API_ENDPOINT}${API_PATH_TRACK_REQUEST}`, null, JSON.stringify(params), { + ajax(`${API_ENDPOINT}${API_PATH_TRACK_REQUEST}`, null, JSON.stringify(params), { withCredentials: true, method: 'POST', contentType: 'application/json' diff --git a/test/spec/modules/feedadBidAdapter_spec.js b/test/spec/modules/feedadBidAdapter_spec.js index 4bd163dacae..3432a40eca4 100644 --- a/test/spec/modules/feedadBidAdapter_spec.js +++ b/test/spec/modules/feedadBidAdapter_spec.js @@ -383,20 +383,27 @@ describe('FeedAdAdapter', function () { let subject = spec[name]; describe(name + ' handler', function () { let xhr; + let requests; beforeEach(function () { - xhr = sinon.stub(); + xhr = sinon.useFakeXMLHttpRequest(); + requests = []; + xhr.onCreate = xhr => requests.push(xhr); + }); + + afterEach(function () { + xhr.restore(); }); it('should do nothing on empty data', function () { - subject(undefined, xhr); - subject(null, xhr); - expect(xhr.called).to.be.false; + subject(undefined); + subject(null); + expect(requests.length).to.equal(0); }); it('should do nothing when bid metadata is not set', function () { - subject(data, xhr); - expect(xhr.callCount).to.equal(0); + subject(data); + expect(requests.length).to.equal(0); }); it('should send tracking params when correct metadata was set', function () { @@ -412,17 +419,13 @@ describe('FeedAdAdapter', function () { referer, sdk_version: '1.0.0' }; - subject(data, xhr); - expect(xhr.callCount).to.equal(1); - let call = xhr.getCall(0); - expect(call.args[0]).to.equal('https://api.feedad.com/1/prebid/web/events'); - expect(call.args[1]).to.be.null; - expect(JSON.parse(call.args[2])).to.deep.equal(expectedData); - expect(call.args[3]).to.deep.equal({ - withCredentials: true, - method: 'POST', - contentType: 'application/json' - }); + subject(data); + expect(requests.length).to.equal(1); + let call = requests[0]; + expect(call.url).to.equal('https://api.feedad.com/1/prebid/web/events'); + expect(JSON.parse(call.requestBody)).to.deep.equal(expectedData); + expect(call.method).to.equal('POST'); + expect(call.requestHeaders).to.include({'Content-Type': 'application/json;charset=utf-8'}); }) }); }); From d9f91247e0fb943799b6202845e40707be0ce90c Mon Sep 17 00:00:00 2001 From: FeedAd GmbH Date: Wed, 7 Apr 2021 13:03:20 +0200 Subject: [PATCH 25/31] added GDPR fields to the FeedAd bid request --- modules/feedadBidAdapter.js | 4 +++ test/spec/modules/feedadBidAdapter_spec.js | 34 ++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/modules/feedadBidAdapter.js b/modules/feedadBidAdapter.js index 3992f2db5e0..e34e018b303 100644 --- a/modules/feedadBidAdapter.js +++ b/modules/feedadBidAdapter.js @@ -204,6 +204,10 @@ function buildRequests(validBidRequests, bidderRequest) { referer: data.refererInfo.referer, transactionId: bid.transactionId }); + if (bidderRequest && bidderRequest.gdprConsent) { + data.consentIabTcf = bidderRequest.gdprConsent.consentString; + data.gdprApplies = bidderRequest.gdprConsent.gdprApplies; + } return { method: 'POST', url: `${API_ENDPOINT}${API_PATH_BID_REQUEST}`, diff --git a/test/spec/modules/feedadBidAdapter_spec.js b/test/spec/modules/feedadBidAdapter_spec.js index 066ab6b21f6..58b24a9ca36 100644 --- a/test/spec/modules/feedadBidAdapter_spec.js +++ b/test/spec/modules/feedadBidAdapter_spec.js @@ -248,6 +248,40 @@ describe('FeedAdAdapter', function () { let result = spec.buildRequests([bid, bid, bid]); expect(result).to.be.empty; }); + it('should not include GDPR data if the bidder request has none available', function () { + let bid = { + code: 'feedad', + mediaTypes: { + banner: { + sizes: [[320, 250]] + } + }, + params: {clientToken: 'clientToken', placementId: 'placement-id'} + }; + let result = spec.buildRequests([bid], bidderRequest); + expect(result.data.gdprApplies).to.be.undefined; + expect(result.data.consentIabTcf).to.be.undefined; + }); + it('should include GDPR data if the bidder requests contains it', function () { + let bid = { + code: 'feedad', + mediaTypes: { + banner: { + sizes: [[320, 250]] + } + }, + params: {clientToken: 'clientToken', placementId: 'placement-id'} + }; + let request = Object.assign({}, bidderRequest, { + gdprConsent: { + consentString: 'the consent string', + gdprApplies: true + } + }); + let result = spec.buildRequests([bid], request); + expect(result.data.gdprApplies).to.equal(request.gdprConsent.gdprApplies); + expect(result.data.consentIabTcf).to.equal(request.gdprConsent.consentString); + }); }); describe('interpretResponse', function () { From 19456aa0e75fbe4f0d0d9235a9a1a0d8471dce83 Mon Sep 17 00:00:00 2001 From: FeedAd GmbH Date: Wed, 7 Apr 2021 13:04:44 +0200 Subject: [PATCH 26/31] removed video from supported media types of the FeedAd adapter --- modules/feedadBidAdapter.js | 4 ++-- test/spec/modules/feedadBidAdapter_spec.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/feedadBidAdapter.js b/modules/feedadBidAdapter.js index e34e018b303..056ca224672 100644 --- a/modules/feedadBidAdapter.js +++ b/modules/feedadBidAdapter.js @@ -1,6 +1,6 @@ import * as utils from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {BANNER} from '../src/mediaTypes.js'; import {ajax} from '../src/ajax.js'; /** @@ -71,7 +71,7 @@ const BIDDER_CODE = 'feedad'; * The media types supported by FeedAd * @type {MediaType[]} */ -const MEDIA_TYPES = [VIDEO, BANNER]; +const MEDIA_TYPES = [BANNER]; /** * Tag for logging diff --git a/test/spec/modules/feedadBidAdapter_spec.js b/test/spec/modules/feedadBidAdapter_spec.js index 58b24a9ca36..c16f7ef11f9 100644 --- a/test/spec/modules/feedadBidAdapter_spec.js +++ b/test/spec/modules/feedadBidAdapter_spec.js @@ -13,7 +13,7 @@ describe('FeedAdAdapter', function () { it('should only support video and banner ads', function () { expect(spec.supportedMediaTypes).to.be.a('array'); expect(spec.supportedMediaTypes).to.include(BANNER); - expect(spec.supportedMediaTypes).to.include(VIDEO); + expect(spec.supportedMediaTypes).not.to.include(VIDEO); expect(spec.supportedMediaTypes).not.to.include(NATIVE); }); it('should export the BidderSpec functions', function () { From 3ed82460ce49c3cde724f1a09146092d9c3a984a Mon Sep 17 00:00:00 2001 From: FeedAd GmbH Date: Wed, 7 Apr 2021 13:17:48 +0200 Subject: [PATCH 27/31] increased version code of FeedAd adapter to 1.0.2 --- modules/feedadBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/feedadBidAdapter.js b/modules/feedadBidAdapter.js index 056ca224672..9fbaea152b0 100644 --- a/modules/feedadBidAdapter.js +++ b/modules/feedadBidAdapter.js @@ -7,7 +7,7 @@ import {ajax} from '../src/ajax.js'; * Version of the FeedAd bid adapter * @type {string} */ -const VERSION = '1.0.0'; +const VERSION = '1.0.2'; /** * @typedef {object} FeedAdApiBidRequest From b8f178ce8edfd712245f02b61f470d988273c3df Mon Sep 17 00:00:00 2001 From: FeedAd GmbH Date: Wed, 7 Apr 2021 13:51:41 +0200 Subject: [PATCH 28/31] removed unnecessary check of bidder request --- modules/feedadBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/feedadBidAdapter.js b/modules/feedadBidAdapter.js index 9fbaea152b0..af544a723b7 100644 --- a/modules/feedadBidAdapter.js +++ b/modules/feedadBidAdapter.js @@ -204,7 +204,7 @@ function buildRequests(validBidRequests, bidderRequest) { referer: data.refererInfo.referer, transactionId: bid.transactionId }); - if (bidderRequest && bidderRequest.gdprConsent) { + if (bidderRequest.gdprConsent) { data.consentIabTcf = bidderRequest.gdprConsent.consentString; data.gdprApplies = bidderRequest.gdprConsent.gdprApplies; } From 67014fd2c0009f3d2a1c7931c19d80d50e578d0e Mon Sep 17 00:00:00 2001 From: FeedAd GmbH Date: Wed, 7 Apr 2021 14:10:50 +0200 Subject: [PATCH 29/31] fixed unit test testing for old FeedAd version --- test/spec/modules/feedadBidAdapter_spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/spec/modules/feedadBidAdapter_spec.js b/test/spec/modules/feedadBidAdapter_spec.js index c16f7ef11f9..8c7ba46374a 100644 --- a/test/spec/modules/feedadBidAdapter_spec.js +++ b/test/spec/modules/feedadBidAdapter_spec.js @@ -438,7 +438,7 @@ describe('FeedAdAdapter', function () { prebid_bid_id: bidId, prebid_transaction_id: transactionId, referer, - sdk_version: '1.0.0' + sdk_version: '1.0.2' }; subject(data); expect(server.requests.length).to.equal(1); From e7e7dfff9a018caa0f314b80d941ffbff9b4f3c9 Mon Sep 17 00:00:00 2001 From: FeedAd GmbH Date: Wed, 7 Apr 2021 14:51:14 +0200 Subject: [PATCH 30/31] removed video media type example from documentation file --- modules/feedadBidAdapter.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/modules/feedadBidAdapter.md b/modules/feedadBidAdapter.md index fd57025c29e..6f705df36b5 100644 --- a/modules/feedadBidAdapter.md +++ b/modules/feedadBidAdapter.md @@ -18,9 +18,6 @@ Prebid.JS adapter that connects to the FeedAd demand sources. mediaTypes: { banner: { // supports all banner sizes sizes: [[300, 250]], - }, - video: { // supports only outstream video - context: 'outstream' } }, bids: [ From c8242fb97a3f00a088702332689e97a53a953074 Mon Sep 17 00:00:00 2001 From: FeedAd GmbH Date: Fri, 9 Apr 2021 09:30:17 +0200 Subject: [PATCH 31/31] added gvlid to FeedAd adapter --- modules/feedadBidAdapter.js | 6 ++++++ test/spec/modules/feedadBidAdapter_spec.js | 3 +++ 2 files changed, 9 insertions(+) diff --git a/modules/feedadBidAdapter.js b/modules/feedadBidAdapter.js index af544a723b7..54a4ef0c998 100644 --- a/modules/feedadBidAdapter.js +++ b/modules/feedadBidAdapter.js @@ -61,6 +61,11 @@ const VERSION = '1.0.2'; * @property [device_platform] {1|2|3} 1 - Android | 2 - iOS | 3 - Windows */ +/** + * The IAB TCF 2.0 vendor ID for the FeedAd GmbH + */ +const TCF_VENDOR_ID = 781; + /** * Bidder network identity code * @type {string} @@ -283,6 +288,7 @@ function trackingHandlerFactory(klass) { */ export const spec = { code: BIDDER_CODE, + gvlid: TCF_VENDOR_ID, supportedMediaTypes: MEDIA_TYPES, isBidRequestValid, buildRequests, diff --git a/test/spec/modules/feedadBidAdapter_spec.js b/test/spec/modules/feedadBidAdapter_spec.js index 8c7ba46374a..6b75af0d55d 100644 --- a/test/spec/modules/feedadBidAdapter_spec.js +++ b/test/spec/modules/feedadBidAdapter_spec.js @@ -23,6 +23,9 @@ describe('FeedAdAdapter', function () { expect(spec.onTimeout).to.be.a('function'); expect(spec.onBidWon).to.be.a('function'); }); + it('should export the TCF vendor ID', function () { + expect(spec.gvlid).to.equal(781); + }) }); describe('isBidRequestValid', function () {