From 94d2b3a1d8ae16f299e4ade2ae720ec5026ae2cf Mon Sep 17 00:00:00 2001 From: TheMediaGrid Date: Thu, 12 Mar 2020 16:49:58 +0300 Subject: [PATCH 1/9] Added TheMediaGridNM Bid Adapter --- modules/gridNMBidAdapter.js | 248 ++++++++++++++ modules/gridNMBidAdapter.md | 33 ++ test/spec/modules/gridNMBidAdapter_spec.js | 376 +++++++++++++++++++++ test/spec/modules/vdoaiBidAdapter_spec.js | 210 ++++++------ 4 files changed, 762 insertions(+), 105 deletions(-) create mode 100644 modules/gridNMBidAdapter.js create mode 100644 modules/gridNMBidAdapter.md create mode 100644 test/spec/modules/gridNMBidAdapter_spec.js diff --git a/modules/gridNMBidAdapter.js b/modules/gridNMBidAdapter.js new file mode 100644 index 00000000000..550f2392cb4 --- /dev/null +++ b/modules/gridNMBidAdapter.js @@ -0,0 +1,248 @@ +import * as utils from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import { Renderer } from '../src/Renderer.js'; +import { VIDEO } from '../src/mediaTypes.js'; + +const BIDDER_CODE = 'gridNM'; +const ENDPOINT_URL = 'https://grid.bidswitch.net/hbnm'; +const SYNC_URL = 'https://x.bidswitch.net/sync?ssp=iponweblabs'; +const TIME_TO_LIVE = 360; +const RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; + +let hasSynced = false; + +const LOG_ERROR_MESS = { + noAuid: 'Bid from response has no auid parameter - ', + noAdm: 'Bid from response has no adm parameter - ', + noPrice: 'Bid from response has no price parameter - ', + noSizes: 'Bid from response has no w or h parameter - ', + wrongContentType: 'Bid from response has wrong content_type parameter - ', + noBid: 'Array of bid objects is empty', + noPlacementCode: 'Can\'t find in requested bids the bid with auid - ', + emptyUids: 'Uids should be not empty', + emptySeatbid: 'Seatbid array from response has empty item', + emptyResponse: 'Response is empty', + hasEmptySeatbidArray: 'Response has empty seatbid array', + hasNoArrayOfBids: 'Seatbid from response has no array of bid objects - ' +}; + +// const KEYS_FOR_PARAMS = ['secid','pubid','pubdata','floorcpm','source','video']; +// const KEYS_FOR_VIDEO = ['mimes','mind','maxd','protocols','size','linearity','skip','skipmin','skipafter','api','startdelay','placement','playbackmethod']; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [ VIDEO ], + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function(bid) { + let invalid = !bid.params.source || !utils.isStr(bid.params.source); + if (!invalid) { + invalid = !bid.params.video || !bid.params.video.protocols || !bid.params.video.mimes; + } + if (!invalid) { + const {protocols, mimes} = bid.params.video; + invalid = !utils.isArray(mimes) || mimes.filter((it) => !(it && utils.isStr(it))).length; + invalid = invalid || !utils.isArray(protocols) || protocols.filter((it) => !(utils.isNumber(it) && it > 0 && !(it % 1))).length; + } + return !invalid; + }, + /** + * Make a server request from the list of BidRequests. + * + * @param {BidRequest[]} validBidRequests - an array of bids + * @param {bidderRequest} bidderRequest bidder request object + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function(validBidRequests, bidderRequest) { + const bids = validBidRequests || []; + const requests = []; + + bids.forEach(bid => { + const {params, bidderRequestId, sizes} = bid; + const payload = { + sizes: utils.parseSizesInput(sizes).join(','), + r: bidderRequestId, + wrapperType: 'Prebid_js', + wrapperVersion: '$prebid.version$' + }; + + if (bidderRequest) { + if (bidderRequest.refererInfo && bidderRequest.refererInfo.referer) { + payload.u = bidderRequest.refererInfo.referer; + } + if (bidderRequest.timeout) { + payload.wtimeout = bidderRequest.timeout; + } + if (bidderRequest.gdprConsent) { + if (bidderRequest.gdprConsent.consentString) { + payload.gdpr_consent = bidderRequest.gdprConsent.consentString; + } + payload.gdpr_applies = + (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') + ? Number(bidderRequest.gdprConsent.gdprApplies) : 1; + } + if (bidderRequest.uspConsent) { + payload.us_privacy = bidderRequest.uspConsent; + } + } + + /* const content = {}; + KEYS_FOR_PARAMS.forEach((key) => { + if (params.hasOwnProperty(key) && params[key]) { + if (key === 'video') { + const paramsVideo = params.video; + const video = {}; + KEYS_FOR_VIDEO.forEach((vKey) => { + if (paramsVideo.hasOwnProperty(vKey)) { + video[vKey] = paramsVideo[vKey]; + } + }); + content.video = video; + } else { + content[key] = params[key]; + } + } + }); */ + + requests.push({ + method: 'POST', + url: ENDPOINT_URL + '?' + utils.parseQueryStringParameters(payload).replace(/\&$/, ''), + bid: bid, + data: params // content + }); + }); + + return requests; + }, + /** + * Unpack the response from the server into a list of bids. + * + * @param {*} serverResponse A successful response from the server. + * @param {*} bidRequest + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function(serverResponse, bidRequest) { + serverResponse = serverResponse && serverResponse.body; + const bidResponses = []; + + let errorMessage; + + if (!serverResponse) errorMessage = LOG_ERROR_MESS.emptyResponse; + else if (serverResponse.seatbid && !serverResponse.seatbid.length) { + errorMessage = LOG_ERROR_MESS.hasEmptySeatbidArray; + } + + if (!errorMessage && serverResponse.seatbid) { + const serverBid = _getBidFromResponse(serverResponse.seatbid[0]); + if (serverBid) { + if (!serverBid.auid) errorMessage = LOG_ERROR_MESS.noAuid + JSON.stringify(serverBid); + else if (!serverBid.adm) errorMessage = LOG_ERROR_MESS.noAdm + JSON.stringify(serverBid); + else if (!serverBid.price) errorMessage = LOG_ERROR_MESS.noPrice + JSON.stringify(serverBid); + else if (!serverBid.w || !serverBid.h) errorMessage = LOG_ERROR_MESS.noSizes + JSON.stringify(serverBid); + else if (serverBid.content_type !== 'video') errorMessage = LOG_ERROR_MESS.wrongContentType + serverBid.content_type; + if (!errorMessage) { + const bid = bidRequest.bid; + const bidResponse = { + requestId: bid.bidderRequestId, + bidderCode: spec.code, + cpm: serverBid.price, + width: serverBid.w, + height: serverBid.h, + creativeId: serverBid.auid, // bid.bidId, + currency: 'USD', + netRevenue: false, + ttl: TIME_TO_LIVE, + dealId: serverBid.dealid, + vastXml: serverBid.adm, + mediaType: VIDEO, + adResponse: { + content: serverBid.adm + } + }; + + if (!bid.renderer && (!bid.mediaTypes || !bid.mediaTypes.video || bid.mediaTypes.video.context === 'outstream')) { + bidResponse.renderer = createRenderer(bidResponse, { + id: bid.bidId, + url: RENDERER_URL + }); + } + bidResponses.push(bidResponse); + } + } + } + if (errorMessage) utils.logError(errorMessage); + return bidResponses; + }, + getUserSyncs: function (syncOptions, responses, gdprConsent, uspConsent) { + if (!hasSynced && syncOptions.pixelEnabled) { + let params = ''; + + if (gdprConsent && typeof gdprConsent.consentString === 'string') { + if (typeof gdprConsent.gdprApplies === 'boolean') { + params += `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; + } else { + params += `&gdpr_consent=${gdprConsent.consentString}`; + } + } + if (uspConsent) { + params += `&us_privacy=${uspConsent}`; + } + + hasSynced = true; + return { + type: 'image', + url: SYNC_URL + params + }; + } + } +}; + +function _getBidFromResponse(respItem) { + if (!respItem) { + utils.logError(LOG_ERROR_MESS.emptySeatbid); + } else if (!respItem.bid) { + utils.logError(LOG_ERROR_MESS.hasNoArrayOfBids + JSON.stringify(respItem)); + } else if (!respItem.bid[0]) { + utils.logError(LOG_ERROR_MESS.noBid); + } + return respItem && respItem.bid && respItem.bid[0]; +} + +function outstreamRender (bid) { + bid.renderer.push(() => { + window.ANOutstreamVideo.renderAd({ + targetId: bid.adUnitCode, + adResponse: bid.adResponse + }); + }); +} + +function createRenderer (bid, rendererParams) { + const renderer = Renderer.install({ + id: rendererParams.id, + url: rendererParams.url, + loaded: false + }); + + try { + renderer.setRender(outstreamRender); + } catch (err) { + utils.logWarn('Prebid Error calling setRender on renderer', err); + } + + return renderer; +} + +export function resetUserSync() { + hasSynced = false; +} + +export function getSyncUrl() { + return SYNC_URL; +} + +registerBidder(spec); diff --git a/modules/gridNMBidAdapter.md b/modules/gridNMBidAdapter.md new file mode 100644 index 00000000000..e81cb04d3a4 --- /dev/null +++ b/modules/gridNMBidAdapter.md @@ -0,0 +1,33 @@ +# Overview + +Module Name: The Grid Media Bidder Adapter +Module Type: Bidder Adapter +Maintainer: grid-tech@themediagrid.com + +# Description + +Module that connects to Grid demand source to fetch bids. +Grid bid adapter supports Banner and Video (instream and outstream). + +# Test Parameters +``` + var adUnits = [ + { + code: 'test-div', + sizes: [[728, 90]], + mediaTypes: { video: {} }, + bids: [ + { + bidder: "gridNM", + params: { + source: 'jwp', + video: { + mimes: ['video/mp4', 'video/x-ms-wmv'], + protocols: [1,2,3,4,5,6] + } + } + } + ] + } + ]; +``` diff --git a/test/spec/modules/gridNMBidAdapter_spec.js b/test/spec/modules/gridNMBidAdapter_spec.js new file mode 100644 index 00000000000..db46dd89015 --- /dev/null +++ b/test/spec/modules/gridNMBidAdapter_spec.js @@ -0,0 +1,376 @@ +import { expect } from 'chai'; +import { spec, resetUserSync, getSyncUrl } from 'modules/gridNMBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; + +describe('TheMediaGridNM Adapter', function () { + const adapter = newBidder(spec); + + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function () { + let bid = { + 'bidder': 'gridNM', + 'params': { + 'source': 'jwp', + 'video': { + 'mimes': ['video/mp4', 'video/x-ms-wmv'], + 'protocols': [1, 2, 3, 4, 5, 6] + } + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not passed', function () { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'source': 'jwp', + 'video': { + 'protocols': [1, 2, 3, 4, 5, 6] + } + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when required params has invalid values', function () { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'source': 'jwp', + 'video': { + 'mimes': ['video/mp4', 'video/x-ms-wmv'], + 'protocols': '1,2,3,4,5' + } + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + function parseRequestUrl(url) { + const res = {}; + url.replace(/^[^\?]+\?/, '').split('&').forEach((it) => { + const couple = it.split('='); + res[couple[0]] = decodeURIComponent(couple[1]); + }); + return res; + } + const bidderRequest = {refererInfo: {referer: 'https://example.com'}}; + const referrer = bidderRequest.refererInfo.referer; + let bidRequests = [ + { + 'bidder': 'gridNM', + 'params': { + 'source': 'jwp', + 'video': { + 'mimes': ['video/mp4', 'video/x-ms-wmv'], + 'protocols': [1, 2, 3, 4, 5, 6] + } + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }, + { + 'bidder': 'gridNM', + 'params': { + 'source': 'jwp', + 'pubid': 22, + 'video': { + 'mimes': ['video/mp4'], + 'protocols': [1, 2, 3], + 'skip': 1 + } + }, + 'adUnitCode': 'adunit-code-2', + 'sizes': [[728, 90]], + 'bidId': '3150ccb55da321', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + } + ]; + + it('should attach valid params to the tag', function () { + const requests = spec.buildRequests(bidRequests, bidderRequest); + const requestsSizes = ['300x250,300x600', '728x90']; + requests.forEach((req, i) => { + expect(req.url).to.be.an('string'); + const payload = parseRequestUrl(req.url); + expect(payload).to.have.property('u', referrer); + expect(payload).to.have.property('r', '22edbae2733bf6'); + expect(payload).to.have.property('wrapperType', 'Prebid_js'); + expect(payload).to.have.property('wrapperVersion', '$prebid.version$'); + expect(payload).to.have.property('sizes', requestsSizes[i]); + expect(req.data).to.deep.equal(bidRequests[i].params); + }); + }); + + it('if gdprConsent is present payload must have gdpr params', function () { + const [request] = spec.buildRequests([bidRequests[0]], {gdprConsent: {consentString: 'AAA', gdprApplies: true}, refererInfo: bidderRequest.refererInfo}); + expect(request.url).to.be.an('string'); + const payload = parseRequestUrl(request.url); + expect(payload).to.have.property('u', referrer); + expect(payload).to.have.property('gdpr_consent', 'AAA'); + expect(payload).to.have.property('gdpr_applies', '1'); + }); + + it('if gdprApplies is false gdpr_applies must be 0', function () { + const [request] = spec.buildRequests([bidRequests[0]], {gdprConsent: {consentString: 'AAA', gdprApplies: false}}); + expect(request.url).to.be.an('string'); + const payload = parseRequestUrl(request.url); + expect(payload).to.have.property('gdpr_consent', 'AAA'); + expect(payload).to.have.property('gdpr_applies', '0'); + }); + + it('if gdprApplies is undefined gdpr_applies must be 1', function () { + const [request] = spec.buildRequests([bidRequests[0]], {gdprConsent: {consentString: 'AAA'}}); + expect(request.url).to.be.an('string'); + const payload = parseRequestUrl(request.url); + expect(payload).to.have.property('gdpr_consent', 'AAA'); + expect(payload).to.have.property('gdpr_applies', '1'); + }); + + it('if usPrivacy is present payload must have us_privacy param', function () { + const bidderRequestWithUSP = Object.assign({uspConsent: '1YNN'}, bidderRequest); + const [request] = spec.buildRequests([bidRequests[0]], bidderRequestWithUSP); + expect(request.url).to.be.an('string'); + const payload = parseRequestUrl(request.url); + expect(payload).to.have.property('us_privacy', '1YNN'); + }); + }); + + describe('interpretResponse', function () { + const responses = [ + {'bid': [{'price': 1.15, 'adm': '\n<\/Ad>\n<\/VAST>', 'content_type': 'video', 'auid': 11, 'h': 250, 'w': 300, dealid: 11}], 'seat': '2'}, + {'bid': [{'price': 0.5, 'adm': '\n<\/Ad>\n<\/VAST>', 'content_type': 'video', 'auid': 12, 'h': 600, 'w': 300}], 'seat': '2'}, + {'bid': [{'price': 0, 'auid': 3, 'h': 250, 'w': 300}], 'seat': '2'}, + {'bid': [{'price': 0, 'adm': '\n<\/Ad>\n<\/VAST>', 'h': 250, 'w': 300}], 'seat': '2'}, + undefined, + {'bid': [], 'seat': '2'}, + {'seat': '2'}, + ]; + + it('should get correct video bid response', function () { + const bidRequests = [ + { + 'bidder': 'gridNM', + 'params': { + 'source': 'jwp', + 'video': { + 'mimes': ['video/mp4', 'video/x-ms-wmv'], + 'protocols': [1, 2, 3, 4, 5, 6] + } + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '659423fff799cb', + 'bidderRequestId': '5f2009617a7c0a', + 'auctionId': '1cbd2feafe5e8b', + 'mediaTypes': { + 'video': { + 'context': 'instream' + } + } + }, + { + 'bidder': 'gridNM', + 'params': { + 'source': 'jwp', + 'video': { + 'mimes': ['video/mp4'], + 'protocols': [1, 2, 3, 4, 5], + 'skip': 1 + } + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '2bc598e42b6a', + 'bidderRequestId': '5f2009617a7c0a', + 'auctionId': '1cbd2feafe5e8b', + 'mediaTypes': { + 'video': { + 'context': 'instream' + } + } + } + ]; + const requests = spec.buildRequests(bidRequests); + const expectedResponse = [ + { + 'requestId': '5f2009617a7c0a', + 'cpm': 1.15, + 'creativeId': 11, + 'dealId': 11, + 'width': 300, + 'height': 250, + 'bidderCode': 'gridNM', + 'currency': 'USD', + 'mediaType': 'video', + 'netRevenue': false, + 'ttl': 360, + 'vastXml': '\n<\/Ad>\n<\/VAST>', + 'adResponse': { + 'content': '\n<\/Ad>\n<\/VAST>' + } + }, + { + 'requestId': '5f2009617a7c0a', + 'cpm': 0.5, + 'creativeId': 12, + 'dealId': undefined, + 'width': 300, + 'height': 600, + 'bidderCode': 'gridNM', + 'currency': 'USD', + 'mediaType': 'video', + 'netRevenue': false, + 'ttl': 360, + 'vastXml': '\n<\/Ad>\n<\/VAST>', + 'adResponse': { + 'content': '\n<\/Ad>\n<\/VAST>' + } + } + ]; + + requests.forEach((req, i) => { + const result = spec.interpretResponse({'body': {'seatbid': [responses[i]]}}, req); + expect(result[0]).to.deep.equal(expectedResponse[i]); + }); + }); + + it('handles wrong and nobid responses', function () { + responses.slice(2).forEach((resp) => { + const request = spec.buildRequests([{ + 'bidder': 'gridNM', + 'params': { + 'source': 'jwp', + 'video': { + 'mimes': ['video/mp4'], + 'protocols': [1, 2, 3, 4, 5], + 'skip': 1 + } + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '2bc598e42b6a', + 'bidderRequestId': '5f2009617a7c0a', + 'auctionId': '1cbd2feafe5e8b', + 'mediaTypes': { + 'video': { + 'context': 'instream' + } + } + }]); + const result = spec.interpretResponse({'body': {'seatbid': [resp]}}, request[0]); + expect(result.length).to.equal(0); + }); + }); + }); + + describe('user sync', function () { + const syncUrl = getSyncUrl(); + + beforeEach(function () { + resetUserSync(); + }); + + it('should register the Emily iframe', function () { + let syncs = spec.getUserSyncs({ + pixelEnabled: true + }); + + expect(syncs).to.deep.equal({type: 'image', url: syncUrl}); + }); + + it('should not register the Emily iframe more than once', function () { + let syncs = spec.getUserSyncs({ + pixelEnabled: true + }); + expect(syncs).to.deep.equal({type: 'image', url: syncUrl}); + + // when called again, should still have only been called once + syncs = spec.getUserSyncs(); + expect(syncs).to.equal(undefined); + }); + + it('should pass gdpr params if consent is true', function () { + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, { + gdprApplies: true, consentString: 'foo' + })).to.deep.equal({ + type: 'image', url: `${syncUrl}&gdpr=1&gdpr_consent=foo` + }); + }); + + it('should pass gdpr params if consent is false', function () { + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, { + gdprApplies: false, consentString: 'foo' + })).to.deep.equal({ + type: 'image', url: `${syncUrl}&gdpr=0&gdpr_consent=foo` + }); + }); + + it('should pass gdpr param gdpr_consent only when gdprApplies is undefined', function () { + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, { + consentString: 'foo' + })).to.deep.equal({ + type: 'image', url: `${syncUrl}&gdpr_consent=foo` + }); + }); + + it('should pass no params if gdpr consentString is not defined', function () { + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, {})).to.deep.equal({ + type: 'image', url: syncUrl + }); + }); + + it('should pass no params if gdpr consentString is a number', function () { + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, { + consentString: 0 + })).to.deep.equal({ + type: 'image', url: syncUrl + }); + }); + + it('should pass no params if gdpr consentString is null', function () { + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, { + consentString: null + })).to.deep.equal({ + type: 'image', url: syncUrl + }); + }); + + it('should pass no params if gdpr consentString is a object', function () { + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, { + consentString: {} + })).to.deep.equal({ + type: 'image', url: syncUrl + }); + }); + + it('should pass no params if gdpr is not defined', function () { + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, undefined)).to.deep.equal({ + type: 'image', url: syncUrl + }); + }); + + it('should pass usPrivacy param if it is available', function() { + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, {}, '1YNN')).to.deep.equal({ + type: 'image', url: `${syncUrl}&us_privacy=1YNN` + }); + }); + }); +}); diff --git a/test/spec/modules/vdoaiBidAdapter_spec.js b/test/spec/modules/vdoaiBidAdapter_spec.js index 507f3ffa3ac..2c53647e126 100644 --- a/test/spec/modules/vdoaiBidAdapter_spec.js +++ b/test/spec/modules/vdoaiBidAdapter_spec.js @@ -1,105 +1,105 @@ -import {assert, expect} from 'chai'; -import {spec} from 'modules/vdoaiBidAdapter'; -import {newBidder} from 'src/adapters/bidderFactory'; - -const ENDPOINT_URL = 'https://prebid.vdo.ai/auction'; - -describe('vdoaiBidAdapter', function () { - const adapter = newBidder(spec); - describe('isBidRequestValid', function () { - let bid = { - 'bidder': 'vdo.ai', - 'params': { - placementId: 'testPlacementId' - }, - 'adUnitCode': 'adunit-code', - 'sizes': [ - [300, 250] - ], - 'bidId': '1234asdf1234', - 'bidderRequestId': '1234asdf1234asdf', - 'auctionId': '61466567-d482-4a16-96f0-fe5f25ffbdf120' - }; - it('should return true where required params found', function () { - expect(spec.isBidRequestValid(bid)).to.equal(true); - }); - }); - describe('buildRequests', function () { - let bidRequests = [ - { - 'bidder': 'vdo.ai', - 'params': { - placementId: 'testPlacementId' - }, - 'sizes': [ - [300, 250] - ], - 'bidId': '23beaa6af6cdde', - 'bidderRequestId': '19c0c1efdf37e7', - 'auctionId': '61466567-d482-4a16-96f0-fe5f25ffbdf1', - } - ]; - - let bidderRequests = { - 'refererInfo': { - 'numIframes': 0, - 'reachedTop': true, - 'referer': 'https://example.com', - 'stack': ['https://example.com'] - } - }; - - const request = spec.buildRequests(bidRequests, bidderRequests); - it('sends bid request to our endpoint via POST', function () { - expect(request[0].method).to.equal('POST'); - }); - it('attaches source and version to endpoint URL as query params', function () { - expect(request[0].url).to.equal(ENDPOINT_URL); - }); - }); - - describe('interpretResponse', function () { - let bidRequest = [ - { - 'method': 'POST', - 'url': ENDPOINT_URL, - 'data': { - 'placementId': 'testPlacementId', - 'width': '300', - 'height': '200', - 'bidId': 'bidId123', - 'referer': 'www.example.com' - } - - } - ]; - let serverResponse = { - body: { - 'vdoCreative': '

I am an ad

', - 'price': 4.2, - 'adid': '12345asdfg', - 'currency': 'EUR', - 'statusMessage': 'Bid available', - 'requestId': 'bidId123', - 'width': 300, - 'height': 250, - 'netRevenue': true - } - }; - it('should get the correct bid response', function () { - let expectedResponse = [{ - 'requestId': 'bidId123', - 'cpm': 4.2, - 'width': 300, - 'height': 250, - 'creativeId': '12345asdfg', - 'currency': 'EUR', - 'netRevenue': true, - 'ttl': 3000, - 'ad': '

I am an ad

' - }]; - let result = spec.interpretResponse(serverResponse, bidRequest[0]); - expect(Object.keys(result)).to.deep.equal(Object.keys(expectedResponse)); - }); - }); -}); +import {assert, expect} from 'chai'; +import {spec} from 'modules/vdoaiBidAdapter.js'; +import {newBidder} from 'src/adapters/bidderFactory.js'; + +const ENDPOINT_URL = 'https://prebid.vdo.ai/auction'; + +describe('vdoaiBidAdapter', function () { + const adapter = newBidder(spec); + describe('isBidRequestValid', function () { + let bid = { + 'bidder': 'vdo.ai', + 'params': { + placementId: 'testPlacementId' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [ + [300, 250] + ], + 'bidId': '1234asdf1234', + 'bidderRequestId': '1234asdf1234asdf', + 'auctionId': '61466567-d482-4a16-96f0-fe5f25ffbdf120' + }; + it('should return true where required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + }); + describe('buildRequests', function () { + let bidRequests = [ + { + 'bidder': 'vdo.ai', + 'params': { + placementId: 'testPlacementId' + }, + 'sizes': [ + [300, 250] + ], + 'bidId': '23beaa6af6cdde', + 'bidderRequestId': '19c0c1efdf37e7', + 'auctionId': '61466567-d482-4a16-96f0-fe5f25ffbdf1', + } + ]; + + let bidderRequests = { + 'refererInfo': { + 'numIframes': 0, + 'reachedTop': true, + 'referer': 'https://example.com', + 'stack': ['https://example.com'] + } + }; + + const request = spec.buildRequests(bidRequests, bidderRequests); + it('sends bid request to our endpoint via POST', function () { + expect(request[0].method).to.equal('POST'); + }); + it('attaches source and version to endpoint URL as query params', function () { + expect(request[0].url).to.equal(ENDPOINT_URL); + }); + }); + + describe('interpretResponse', function () { + let bidRequest = [ + { + 'method': 'POST', + 'url': ENDPOINT_URL, + 'data': { + 'placementId': 'testPlacementId', + 'width': '300', + 'height': '200', + 'bidId': 'bidId123', + 'referer': 'www.example.com' + } + + } + ]; + let serverResponse = { + body: { + 'vdoCreative': '

I am an ad

', + 'price': 4.2, + 'adid': '12345asdfg', + 'currency': 'EUR', + 'statusMessage': 'Bid available', + 'requestId': 'bidId123', + 'width': 300, + 'height': 250, + 'netRevenue': true + } + }; + it('should get the correct bid response', function () { + let expectedResponse = [{ + 'requestId': 'bidId123', + 'cpm': 4.2, + 'width': 300, + 'height': 250, + 'creativeId': '12345asdfg', + 'currency': 'EUR', + 'netRevenue': true, + 'ttl': 3000, + 'ad': '

I am an ad

' + }]; + let result = spec.interpretResponse(serverResponse, bidRequest[0]); + expect(Object.keys(result)).to.deep.equal(Object.keys(expectedResponse)); + }); + }); +}); From 3ba8fd846c1f5b58c98d77a1c11810615f4dc26c Mon Sep 17 00:00:00 2001 From: TheMediaGrid Date: Thu, 12 Mar 2020 17:08:03 +0300 Subject: [PATCH 2/9] Updated required params for TheMediaGridNM Bid Adapter --- modules/gridNMBidAdapter.js | 12 +- test/spec/modules/gridNMBidAdapter_spec.js | 121 ++++++++++++++++++--- 2 files changed, 112 insertions(+), 21 deletions(-) diff --git a/modules/gridNMBidAdapter.js b/modules/gridNMBidAdapter.js index 550f2392cb4..ba2698fdfab 100644 --- a/modules/gridNMBidAdapter.js +++ b/modules/gridNMBidAdapter.js @@ -39,14 +39,20 @@ export const spec = { * @return boolean True if this is a valid bid, and false otherwise. */ isBidRequestValid: function(bid) { - let invalid = !bid.params.source || !utils.isStr(bid.params.source); + let invalid = + !bid.params.source || !utils.isStr(bid.params.source) || + !bid.params.secid || !utils.isStr(bid.params.secid) || + !bid.params.pubid || !utils.isStr(bid.params.pubid); + if (!invalid) { invalid = !bid.params.video || !bid.params.video.protocols || !bid.params.video.mimes; } if (!invalid) { const {protocols, mimes} = bid.params.video; - invalid = !utils.isArray(mimes) || mimes.filter((it) => !(it && utils.isStr(it))).length; - invalid = invalid || !utils.isArray(protocols) || protocols.filter((it) => !(utils.isNumber(it) && it > 0 && !(it % 1))).length; + invalid = !utils.isArray(mimes) || !mimes.length || mimes.filter((it) => !(it && utils.isStr(it))).length; + if (!invalid) { + invalid = !utils.isArray(protocols) || !protocols.length || protocols.filter((it) => !(utils.isNumber(it) && it > 0 && !(it % 1))).length; + } } return !invalid; }, diff --git a/test/spec/modules/gridNMBidAdapter_spec.js b/test/spec/modules/gridNMBidAdapter_spec.js index db46dd89015..a7a1c02de5f 100644 --- a/test/spec/modules/gridNMBidAdapter_spec.js +++ b/test/spec/modules/gridNMBidAdapter_spec.js @@ -16,6 +16,8 @@ describe('TheMediaGridNM Adapter', function () { 'bidder': 'gridNM', 'params': { 'source': 'jwp', + 'secid': '11', + 'pubid': '22', 'video': { 'mimes': ['video/mp4', 'video/x-ms-wmv'], 'protocols': [1, 2, 3, 4, 5, 6] @@ -33,28 +35,102 @@ describe('TheMediaGridNM Adapter', function () { }); it('should return false when required params are not passed', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { - 'source': 'jwp', - 'video': { - 'protocols': [1, 2, 3, 4, 5, 6] + const paramsList = [ + { + 'source': 'jwp', + 'secid': '11', + 'pubid': '22', + 'video': { + 'protocols': [1, 2, 3, 4, 5, 6] + } + }, + { + 'source': 'jwp', + 'secid': '11', + 'pubid': '22', + 'video': { + 'mimes': ['video/mp4', 'video/x-ms-wmv'], + } + }, + { + 'secid': '11', + 'pubid': '22', + 'video': { + 'mimes': ['video/mp4', 'video/x-ms-wmv'], + 'protocols': [1, 2, 3, 4, 5, 6] + } + }, + { + 'source': 'jwp', + 'pubid': '22', + 'video': { + 'mimes': ['video/mp4', 'video/x-ms-wmv'], + 'protocols': [1, 2, 3, 4, 5, 6] + } + }, + { + 'source': 'jwp', + 'secid': '11', + 'video': { + 'mimes': ['video/mp4', 'video/x-ms-wmv'], + 'protocols': [1, 2, 3, 4, 5, 6] + } } - }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + ]; + paramsList.forEach((params) => { + const invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = params; + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); + }); }); it('should return false when required params has invalid values', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { - 'source': 'jwp', - 'video': { - 'mimes': ['video/mp4', 'video/x-ms-wmv'], - 'protocols': '1,2,3,4,5' + const paramsList = [ + { + 'source': 'jwp', + 'secid': '11', + 'pubid': '22', + 'video': { + 'mimes': ['video/mp4', 'video/x-ms-wmv'], + 'protocols': '1,2,3,4,5' + } + }, + { + 'source': 'jwp', + 'secid': '11', + 'pubid': '22', + 'video': { + 'mimes': [1, 2], + 'protocols': [1, 2, 3, 4, 5] + } + }, + { + 'source': 'jwp', + 'secid': 11, + 'pubid': '22', + 'video': { + 'mimes': ['video/mp4', 'video/x-ms-wmv'], + 'protocols': [1, 2, 3, 4, 5] + } + }, + { + 'source': 111, + 'secid': '11', + 'pubid': '22', + 'video': { + 'mimes': ['video/mp4', 'video/x-ms-wmv'], + 'protocols': [1, 2, 3, 4, 5] + } } - }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + ]; + + paramsList.forEach((params) => { + const invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = params; + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); + }); }); }); @@ -74,6 +150,8 @@ describe('TheMediaGridNM Adapter', function () { 'bidder': 'gridNM', 'params': { 'source': 'jwp', + 'secid': '11', + 'pubid': '22', 'video': { 'mimes': ['video/mp4', 'video/x-ms-wmv'], 'protocols': [1, 2, 3, 4, 5, 6] @@ -89,7 +167,8 @@ describe('TheMediaGridNM Adapter', function () { 'bidder': 'gridNM', 'params': { 'source': 'jwp', - 'pubid': 22, + 'secid': '11', + 'pubid': '22', 'video': { 'mimes': ['video/mp4'], 'protocols': [1, 2, 3], @@ -170,6 +249,8 @@ describe('TheMediaGridNM Adapter', function () { 'bidder': 'gridNM', 'params': { 'source': 'jwp', + 'secid': '11', + 'pubid': '22', 'video': { 'mimes': ['video/mp4', 'video/x-ms-wmv'], 'protocols': [1, 2, 3, 4, 5, 6] @@ -190,6 +271,8 @@ describe('TheMediaGridNM Adapter', function () { 'bidder': 'gridNM', 'params': { 'source': 'jwp', + 'secid': '11', + 'pubid': '22', 'video': { 'mimes': ['video/mp4'], 'protocols': [1, 2, 3, 4, 5], @@ -258,6 +341,8 @@ describe('TheMediaGridNM Adapter', function () { 'bidder': 'gridNM', 'params': { 'source': 'jwp', + 'secid': '11', + 'pubid': '22', 'video': { 'mimes': ['video/mp4'], 'protocols': [1, 2, 3, 4, 5], From b830678150bf3b3672557d17ca1e9f5cc6c78b3e Mon Sep 17 00:00:00 2001 From: TheMediaGrid Date: Tue, 17 Mar 2020 13:43:45 +0300 Subject: [PATCH 3/9] Update TheMediGridNM Bid Adapter --- modules/gridNMBidAdapter.js | 15 ++++++++------- modules/gridNMBidAdapter.md | 9 +++++++-- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/modules/gridNMBidAdapter.js b/modules/gridNMBidAdapter.js index ba2698fdfab..bc4a2652f03 100644 --- a/modules/gridNMBidAdapter.js +++ b/modules/gridNMBidAdapter.js @@ -12,10 +12,8 @@ const RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js let hasSynced = false; const LOG_ERROR_MESS = { - noAuid: 'Bid from response has no auid parameter - ', noAdm: 'Bid from response has no adm parameter - ', noPrice: 'Bid from response has no price parameter - ', - noSizes: 'Bid from response has no w or h parameter - ', wrongContentType: 'Bid from response has wrong content_type parameter - ', noBid: 'Array of bid objects is empty', noPlacementCode: 'Can\'t find in requested bids the bid with auid - ', @@ -145,20 +143,23 @@ export const spec = { if (!errorMessage && serverResponse.seatbid) { const serverBid = _getBidFromResponse(serverResponse.seatbid[0]); if (serverBid) { - if (!serverBid.auid) errorMessage = LOG_ERROR_MESS.noAuid + JSON.stringify(serverBid); - else if (!serverBid.adm) errorMessage = LOG_ERROR_MESS.noAdm + JSON.stringify(serverBid); + if (!serverBid.adm) errorMessage = LOG_ERROR_MESS.noAdm + JSON.stringify(serverBid); else if (!serverBid.price) errorMessage = LOG_ERROR_MESS.noPrice + JSON.stringify(serverBid); - else if (!serverBid.w || !serverBid.h) errorMessage = LOG_ERROR_MESS.noSizes + JSON.stringify(serverBid); else if (serverBid.content_type !== 'video') errorMessage = LOG_ERROR_MESS.wrongContentType + serverBid.content_type; if (!errorMessage) { const bid = bidRequest.bid; + if (!serverBid.w || !serverBid.h) { + const size = utils.parseSizesInput(bid.sizes)[0].split('x'); + serverBid.w = size[0]; + serverBid.h = size[1]; + } const bidResponse = { - requestId: bid.bidderRequestId, + requestId: bid.bidId, bidderCode: spec.code, cpm: serverBid.price, width: serverBid.w, height: serverBid.h, - creativeId: serverBid.auid, // bid.bidId, + creativeId: serverBid.auid || bid.bidderRequestId, currency: 'USD', netRevenue: false, ttl: TIME_TO_LIVE, diff --git a/modules/gridNMBidAdapter.md b/modules/gridNMBidAdapter.md index e81cb04d3a4..f06c5775064 100644 --- a/modules/gridNMBidAdapter.md +++ b/modules/gridNMBidAdapter.md @@ -14,13 +14,18 @@ Grid bid adapter supports Banner and Video (instream and outstream). var adUnits = [ { code: 'test-div', - sizes: [[728, 90]], - mediaTypes: { video: {} }, + mediaTypes: { + video: { + playerSize: [728, 90] + } + }, bids: [ { bidder: "gridNM", params: { source: 'jwp', + secid: '11', + pubid: '22', video: { mimes: ['video/mp4', 'video/x-ms-wmv'], protocols: [1,2,3,4,5,6] From 9675d0289f7dd7efd3822aff9df328faf2186a85 Mon Sep 17 00:00:00 2001 From: TheMediaGrid Date: Tue, 17 Mar 2020 16:59:54 +0300 Subject: [PATCH 4/9] Fix tests for TheMediaGridNM Bid Adapter --- test/spec/modules/gridNMBidAdapter_spec.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/test/spec/modules/gridNMBidAdapter_spec.js b/test/spec/modules/gridNMBidAdapter_spec.js index a7a1c02de5f..0dbaac0c526 100644 --- a/test/spec/modules/gridNMBidAdapter_spec.js +++ b/test/spec/modules/gridNMBidAdapter_spec.js @@ -234,9 +234,9 @@ describe('TheMediaGridNM Adapter', function () { describe('interpretResponse', function () { const responses = [ - {'bid': [{'price': 1.15, 'adm': '\n<\/Ad>\n<\/VAST>', 'content_type': 'video', 'auid': 11, 'h': 250, 'w': 300, dealid: 11}], 'seat': '2'}, - {'bid': [{'price': 0.5, 'adm': '\n<\/Ad>\n<\/VAST>', 'content_type': 'video', 'auid': 12, 'h': 600, 'w': 300}], 'seat': '2'}, - {'bid': [{'price': 0, 'auid': 3, 'h': 250, 'w': 300}], 'seat': '2'}, + {'bid': [{'price': 1.15, 'adm': '\n<\/Ad>\n<\/VAST>', 'content_type': 'video', 'h': 250, 'w': 300, 'dealid': 11}], 'seat': '2'}, + {'bid': [{'price': 0.5, 'adm': '\n<\/Ad>\n<\/VAST>', 'content_type': 'video', 'h': 600, 'w': 300}], 'seat': '2'}, + {'bid': [{'price': 0, 'h': 250, 'w': 300}], 'seat': '2'}, {'bid': [{'price': 0, 'adm': '\n<\/Ad>\n<\/VAST>', 'h': 250, 'w': 300}], 'seat': '2'}, undefined, {'bid': [], 'seat': '2'}, @@ -282,7 +282,7 @@ describe('TheMediaGridNM Adapter', function () { 'adUnitCode': 'adunit-code-1', 'sizes': [[300, 250], [300, 600]], 'bidId': '2bc598e42b6a', - 'bidderRequestId': '5f2009617a7c0a', + 'bidderRequestId': '1e8b5a465f404', 'auctionId': '1cbd2feafe5e8b', 'mediaTypes': { 'video': { @@ -294,9 +294,9 @@ describe('TheMediaGridNM Adapter', function () { const requests = spec.buildRequests(bidRequests); const expectedResponse = [ { - 'requestId': '5f2009617a7c0a', + 'requestId': '659423fff799cb', 'cpm': 1.15, - 'creativeId': 11, + 'creativeId': '5f2009617a7c0a', 'dealId': 11, 'width': 300, 'height': 250, @@ -311,9 +311,9 @@ describe('TheMediaGridNM Adapter', function () { } }, { - 'requestId': '5f2009617a7c0a', + 'requestId': '2bc598e42b6a', 'cpm': 0.5, - 'creativeId': 12, + 'creativeId': '1e8b5a465f404', 'dealId': undefined, 'width': 300, 'height': 600, @@ -352,7 +352,7 @@ describe('TheMediaGridNM Adapter', function () { 'adUnitCode': 'adunit-code-1', 'sizes': [[300, 250], [300, 600]], 'bidId': '2bc598e42b6a', - 'bidderRequestId': '5f2009617a7c0a', + 'bidderRequestId': '39d74f5b71464', 'auctionId': '1cbd2feafe5e8b', 'mediaTypes': { 'video': { From 52e593cbad6b272172d2417c3a80be9c53bd4002 Mon Sep 17 00:00:00 2001 From: TheMediaGrid Date: Tue, 31 Mar 2020 20:30:49 +0300 Subject: [PATCH 5/9] Fixes after review for TheMediaGridNM Bid Adapter --- modules/gridNMBidAdapter.js | 21 --------------------- modules/gridNMBidAdapter.md | 3 ++- 2 files changed, 2 insertions(+), 22 deletions(-) diff --git a/modules/gridNMBidAdapter.js b/modules/gridNMBidAdapter.js index bc4a2652f03..7e244f8293c 100644 --- a/modules/gridNMBidAdapter.js +++ b/modules/gridNMBidAdapter.js @@ -24,9 +24,6 @@ const LOG_ERROR_MESS = { hasNoArrayOfBids: 'Seatbid from response has no array of bid objects - ' }; -// const KEYS_FOR_PARAMS = ['secid','pubid','pubdata','floorcpm','source','video']; -// const KEYS_FOR_VIDEO = ['mimes','mind','maxd','protocols','size','linearity','skip','skipmin','skipafter','api','startdelay','placement','playbackmethod']; - export const spec = { code: BIDDER_CODE, supportedMediaTypes: [ VIDEO ], @@ -94,24 +91,6 @@ export const spec = { } } - /* const content = {}; - KEYS_FOR_PARAMS.forEach((key) => { - if (params.hasOwnProperty(key) && params[key]) { - if (key === 'video') { - const paramsVideo = params.video; - const video = {}; - KEYS_FOR_VIDEO.forEach((vKey) => { - if (paramsVideo.hasOwnProperty(vKey)) { - video[vKey] = paramsVideo[vKey]; - } - }); - content.video = video; - } else { - content[key] = params[key]; - } - } - }); */ - requests.push({ method: 'POST', url: ENDPOINT_URL + '?' + utils.parseQueryStringParameters(payload).replace(/\&$/, ''), diff --git a/modules/gridNMBidAdapter.md b/modules/gridNMBidAdapter.md index f06c5775064..6decdde7f4c 100644 --- a/modules/gridNMBidAdapter.md +++ b/modules/gridNMBidAdapter.md @@ -16,7 +16,8 @@ Grid bid adapter supports Banner and Video (instream and outstream). code: 'test-div', mediaTypes: { video: { - playerSize: [728, 90] + playerSize: [728, 90], + context: 'outstream' } }, bids: [ From 83fb2b4127232125106c18327fd643c751fc90d7 Mon Sep 17 00:00:00 2001 From: TheMediaGrid Date: Tue, 12 May 2020 19:19:39 +0300 Subject: [PATCH 6/9] Add support of multi-format in TheMediaGrid Bid Adapter --- modules/gridBidAdapter.js | 15 ++++++++++++- test/spec/modules/gridBidAdapter_spec.js | 28 +++++++++++++++++++++++- 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/modules/gridBidAdapter.js b/modules/gridBidAdapter.js index e19d8cc68a7..4ca631b23e5 100644 --- a/modules/gridBidAdapter.js +++ b/modules/gridBidAdapter.js @@ -51,10 +51,23 @@ export const spec = { bids.forEach(bid => { reqId = bid.bidderRequestId; - const {params: {uid}, adUnitCode} = bid; + const {params: {uid}, adUnitCode, mediaTypes} = bid; auids.push(uid); const sizesId = utils.parseSizesInput(bid.sizes); + const addedSizes = {}; + sizesId.forEach((sizeId) => { + addedSizes[sizeId] = true; + }); + const bannerSizesId = utils.parseSizesInput(utils.deepAccess(mediaTypes, 'banner.sizes')); + const videoSizesId = utils.parseSizesInput(utils.deepAccess(mediaTypes, 'video.playerSize')); + bannerSizesId.concat(videoSizesId).forEach((sizeId) => { + if (!addedSizes[sizeId]) { + addedSizes[sizeId] = true; + sizesId.push(sizeId); + } + }); + if (!slotsMapByUid[uid]) { slotsMapByUid[uid] = {}; } diff --git a/test/spec/modules/gridBidAdapter_spec.js b/test/spec/modules/gridBidAdapter_spec.js index 26ab27f5273..4f53d9dcb25 100644 --- a/test/spec/modules/gridBidAdapter_spec.js +++ b/test/spec/modules/gridBidAdapter_spec.js @@ -68,6 +68,14 @@ describe('TheMediaGrid Adapter', function () { }, 'adUnitCode': 'adunit-code-2', 'sizes': [[728, 90]], + 'mediaTypes': { + 'video': { + 'playerSize': [400, 600] + }, + 'banner': { + 'sizes': [[728, 90]] + } + }, 'bidId': '3150ccb55da321', 'bidderRequestId': '22edbae2733bf6', 'auctionId': '1d1a030790a475', @@ -79,6 +87,14 @@ describe('TheMediaGrid Adapter', function () { }, 'adUnitCode': 'adunit-code-1', 'sizes': [[300, 250], [300, 600]], + 'mediaTypes': { + 'video': { + 'playerSize': [400, 600] + }, + 'banner': { + 'sizes': [[300, 250], [300, 600]] + } + }, 'bidId': '42dbe3a7168a6a', 'bidderRequestId': '22edbae2733bf6', 'auctionId': '1d1a030790a475', @@ -97,13 +113,23 @@ describe('TheMediaGrid Adapter', function () { expect(payload).to.have.property('wrapperVersion', '$prebid.version$'); }); + it('sizes must be added from mediaTypes', function () { + const request = spec.buildRequests([bidRequests[0], bidRequests[1]], bidderRequest); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload).to.have.property('u', referrer); + expect(payload).to.have.property('auids', '1,1'); + expect(payload).to.have.property('sizes', '300x250,300x600,728x90,400x600'); + expect(payload).to.have.property('r', '22edbae2733bf6'); + }); + it('sizes must not be duplicated', function () { const request = spec.buildRequests(bidRequests, bidderRequest); expect(request.data).to.be.an('string'); const payload = parseRequest(request.data); expect(payload).to.have.property('u', referrer); expect(payload).to.have.property('auids', '1,1,2'); - expect(payload).to.have.property('sizes', '300x250,300x600,728x90'); + expect(payload).to.have.property('sizes', '300x250,300x600,728x90,400x600'); expect(payload).to.have.property('r', '22edbae2733bf6'); }); From 66f2ac8ca587c02ef37f0b842a0882bd36c8994f Mon Sep 17 00:00:00 2001 From: TheMediaGrid Date: Tue, 26 May 2020 19:00:07 +0300 Subject: [PATCH 7/9] Update sync url for grid and gridNM Bid Adapters --- modules/gridBidAdapter.js | 2 +- modules/gridNMBidAdapter.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/gridBidAdapter.js b/modules/gridBidAdapter.js index 4ca631b23e5..3a78a5fcf20 100644 --- a/modules/gridBidAdapter.js +++ b/modules/gridBidAdapter.js @@ -5,7 +5,7 @@ import { VIDEO, BANNER } from '../src/mediaTypes.js'; const BIDDER_CODE = 'grid'; const ENDPOINT_URL = 'https://grid.bidswitch.net/hb'; -const SYNC_URL = 'https://x.bidswitch.net/sync?ssp=iow_labs'; +const SYNC_URL = 'https://x.bidswitch.net/sync?ssp=themediagrid'; const TIME_TO_LIVE = 360; const RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; diff --git a/modules/gridNMBidAdapter.js b/modules/gridNMBidAdapter.js index 7e244f8293c..ffd6c1b250c 100644 --- a/modules/gridNMBidAdapter.js +++ b/modules/gridNMBidAdapter.js @@ -5,7 +5,7 @@ import { VIDEO } from '../src/mediaTypes.js'; const BIDDER_CODE = 'gridNM'; const ENDPOINT_URL = 'https://grid.bidswitch.net/hbnm'; -const SYNC_URL = 'https://x.bidswitch.net/sync?ssp=iponweblabs'; +const SYNC_URL = 'https://x.bidswitch.net/sync?ssp=themediagrid'; const TIME_TO_LIVE = 360; const RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; From 4fcf52289cd4a0ceb248f87fdfe896acc66e0d3d Mon Sep 17 00:00:00 2001 From: TheMediaGrid Date: Tue, 9 Jun 2020 18:23:01 +0300 Subject: [PATCH 8/9] TheMediaGrid Bid Adapter: added keywords adUnit parameter --- modules/gridBidAdapter.js | 24 ++++++++++++ modules/gridBidAdapter.md | 8 +++- test/spec/modules/gridBidAdapter_spec.js | 49 ++++++++++++++++++++++++ 3 files changed, 79 insertions(+), 2 deletions(-) diff --git a/modules/gridBidAdapter.js b/modules/gridBidAdapter.js index 3a78a5fcf20..308100ede71 100644 --- a/modules/gridBidAdapter.js +++ b/modules/gridBidAdapter.js @@ -47,6 +47,7 @@ export const spec = { const slotsMapByUid = {}; const sizeMap = {}; const bids = validBidRequests || []; + let pageKeywords; let reqId; bids.forEach(bid => { @@ -55,6 +56,15 @@ export const spec = { auids.push(uid); const sizesId = utils.parseSizesInput(bid.sizes); + if (!pageKeywords && !utils.isEmpty(bid.params.keywords)) { + const keywords = utils.transformBidderParamKeywords(bid.params.keywords); + + if (keywords.length > 0) { + keywords.forEach(deleteValues); + } + pageKeywords = keywords; + } + const addedSizes = {}; sizesId.forEach((sizeId) => { addedSizes[sizeId] = true; @@ -102,6 +112,10 @@ export const spec = { wrapperVersion: '$prebid.version$' }; + if (pageKeywords) { + payload.keywords = JSON.stringify(pageKeywords); + } + if (bidderRequest) { if (bidderRequest.refererInfo && bidderRequest.refererInfo.referer) { payload.u = bidderRequest.refererInfo.referer; @@ -180,6 +194,16 @@ export const spec = { } }; +function isPopulatedArray(arr) { + return !!(utils.isArray(arr) && arr.length > 0); +} + +function deleteValues(keyPairObj) { + if (isPopulatedArray(keyPairObj.value) && keyPairObj.value[0] === '') { + delete keyPairObj.value; + } +} + function _getBidFromResponse(respItem) { if (!respItem) { utils.logError(LOG_ERROR_MESS.emptySeatbid); diff --git a/modules/gridBidAdapter.md b/modules/gridBidAdapter.md index 4720ee3d808..77b9bbf0f36 100644 --- a/modules/gridBidAdapter.md +++ b/modules/gridBidAdapter.md @@ -32,7 +32,11 @@ Grid bid adapter supports Banner and Video (instream and outstream). bidder: "grid", params: { uid: 2, - priceType: 'gross' + priceType: 'gross', + keywords: { + brandsafety: ['disaster'], + topic: ['stress', 'fear'] + } } } ] @@ -51,4 +55,4 @@ Grid bid adapter supports Banner and Video (instream and outstream). ] } ]; -``` \ No newline at end of file +``` diff --git a/test/spec/modules/gridBidAdapter_spec.js b/test/spec/modules/gridBidAdapter_spec.js index 4f53d9dcb25..2d33cde15ba 100644 --- a/test/spec/modules/gridBidAdapter_spec.js +++ b/test/spec/modules/gridBidAdapter_spec.js @@ -165,6 +165,55 @@ describe('TheMediaGrid Adapter', function () { const payload = parseRequest(request.data); expect(payload).to.have.property('us_privacy', '1YNN'); }); + + it('should convert keyword params to proper form and attaches to request', function () { + const bidRequestWithKeywords = [].concat(bidRequests); + bidRequestWithKeywords[1] = Object.assign({}, + bidRequests[1], + { + params: { + uid: '1', + keywords: { + single: 'val', + singleArr: ['val'], + singleArrNum: [3], + multiValMixed: ['value1', 2, 'value3'], + singleValNum: 123, + emptyStr: '', + emptyArr: [''], + badValue: {'foo': 'bar'} // should be dropped + } + } + } + ); + + const request = spec.buildRequests(bidRequestWithKeywords, bidderRequest); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload.keywords).to.be.an('string'); + payload.keywords = JSON.parse(payload.keywords); + + expect(payload.keywords).to.deep.equal([{ + 'key': 'single', + 'value': ['val'] + }, { + 'key': 'singleArr', + 'value': ['val'] + }, { + 'key': 'singleArrNum', + 'value': ['3'] + }, { + 'key': 'multiValMixed', + 'value': ['value1', '2', 'value3'] + }, { + 'key': 'singleValNum', + 'value': ['123'] + }, { + 'key': 'emptyStr' + }, { + 'key': 'emptyArr' + }]); + }); }); describe('interpretResponse', function () { From bfe4f5ee0d1a7677369e5fc4d06ef79fab4c3bac Mon Sep 17 00:00:00 2001 From: TheMediaGrid Date: Tue, 16 Jun 2020 00:20:55 +0300 Subject: [PATCH 9/9] Update TheMediaGrid Bid Adapter to support keywords from config --- modules/gridBidAdapter.js | 23 ++++++++---- test/spec/modules/gridBidAdapter_spec.js | 46 ++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 7 deletions(-) diff --git a/modules/gridBidAdapter.js b/modules/gridBidAdapter.js index 308100ede71..d18effa349b 100644 --- a/modules/gridBidAdapter.js +++ b/modules/gridBidAdapter.js @@ -2,6 +2,7 @@ import * as utils from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import { Renderer } from '../src/Renderer.js'; import { VIDEO, BANNER } from '../src/mediaTypes.js'; +import {config} from '../src/config.js'; const BIDDER_CODE = 'grid'; const ENDPOINT_URL = 'https://grid.bidswitch.net/hb'; @@ -47,7 +48,7 @@ export const spec = { const slotsMapByUid = {}; const sizeMap = {}; const bids = validBidRequests || []; - let pageKeywords; + let pageKeywords = null; let reqId; bids.forEach(bid => { @@ -57,12 +58,7 @@ export const spec = { const sizesId = utils.parseSizesInput(bid.sizes); if (!pageKeywords && !utils.isEmpty(bid.params.keywords)) { - const keywords = utils.transformBidderParamKeywords(bid.params.keywords); - - if (keywords.length > 0) { - keywords.forEach(deleteValues); - } - pageKeywords = keywords; + pageKeywords = utils.transformBidderParamKeywords(bid.params.keywords); } const addedSizes = {}; @@ -104,6 +100,19 @@ export const spec = { }); }); + const configKeywords = utils.transformBidderParamKeywords({ + 'user': utils.deepAccess(config.getConfig('fpd.user'), 'keywords') || null, + 'context': utils.deepAccess(config.getConfig('fpd.context'), 'keywords') || null + }); + + if (configKeywords.length) { + pageKeywords = (pageKeywords || []).concat(configKeywords); + } + + if (pageKeywords && pageKeywords.length > 0) { + pageKeywords.forEach(deleteValues); + } + const payload = { auids: auids.join(','), sizes: utils.getKeys(sizeMap).join(','), diff --git a/test/spec/modules/gridBidAdapter_spec.js b/test/spec/modules/gridBidAdapter_spec.js index 2d33cde15ba..c8d04113aeb 100644 --- a/test/spec/modules/gridBidAdapter_spec.js +++ b/test/spec/modules/gridBidAdapter_spec.js @@ -1,6 +1,7 @@ import { expect } from 'chai'; import { spec, resetUserSync, getSyncUrl } from 'modules/gridBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; +import { config } from 'src/config.js'; describe('TheMediaGrid Adapter', function () { const adapter = newBidder(spec); @@ -214,6 +215,51 @@ describe('TheMediaGrid Adapter', function () { 'key': 'emptyArr' }]); }); + + it('should mix keyword param with keywords from config', function () { + const getConfigStub = sinon.stub(config, 'getConfig').callsFake( + arg => arg === 'fpd.user' ? {'keywords': ['a', 'b']} : arg === 'fpd.context' ? {'keywords': ['any words']} : null); + + const bidRequestWithKeywords = [].concat(bidRequests); + bidRequestWithKeywords[1] = Object.assign({}, + bidRequests[1], + { + params: { + uid: '1', + keywords: { + single: 'val', + singleArr: ['val'], + multiValMixed: ['value1', 2, 'value3'] + } + } + } + ); + + const request = spec.buildRequests(bidRequestWithKeywords, bidderRequest); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload.keywords).to.be.an('string'); + payload.keywords = JSON.parse(payload.keywords); + + expect(payload.keywords).to.deep.equal([{ + 'key': 'single', + 'value': ['val'] + }, { + 'key': 'singleArr', + 'value': ['val'] + }, { + 'key': 'multiValMixed', + 'value': ['value1', '2', 'value3'] + }, { + 'key': 'user', + 'value': ['a', 'b'] + }, { + 'key': 'context', + 'value': ['any words'] + }]); + + getConfigStub.restore(); + }); }); describe('interpretResponse', function () {