diff --git a/src/adapters/hiromedia.js b/src/adapters/hiromedia.js index 0d0a581edfd..3d37acc0f45 100644 --- a/src/adapters/hiromedia.js +++ b/src/adapters/hiromedia.js @@ -1,4 +1,4 @@ -/*jslint white:true, browser:true*/ +/*jslint white:true, browser:true, single: true*/ /*global $$PREBID_GLOBAL$$, require, module*/ /** @@ -38,7 +38,7 @@ var HiroMediaAdapter = function HiroMediaAdapter() { * @constant {number} * @private */ - var ADAPTER_VERSION = 2; + var ADAPTER_VERSION = 3; /** * Default bid param values @@ -57,9 +57,7 @@ var HiroMediaAdapter = function HiroMediaAdapter() { * @private */ var DEFAULT_BID_PARAMS = { - bidUrl: 'https://hb-rtb.ktdpublishers.com/bid/get', - allowedSize: [300,250], - sizeTolerance: 5 + bidUrl: 'https://hb-rtb.ktdpublishers.com/bid/get' }; /** @@ -98,8 +96,8 @@ var HiroMediaAdapter = function HiroMediaAdapter() { if (bidResponse) { bidObject.cpm = bidResponse.cpm; bidObject.ad = bidResponse.ad; - bidObject.width = bidInfo.selectedSize[0]; - bidObject.height = bidInfo.selectedSize[1]; + bidObject.width = bidResponse.width; + bidObject.height = bidResponse.height; } utils.logMessage('hiromedia.callBids, addBidResponse for ' + placementCode + ' status: ' + bidStatus); @@ -237,80 +235,6 @@ var HiroMediaAdapter = function HiroMediaAdapter() { } - /** - * Convert a `string` to an integer with radix 10. - * - * @memberof module:HiroMediaAdapter~ - * @private - * - * @param {string} value string to convert - * @return {number} the converted integer - */ - function parseInt10(value) { - return parseInt(value, 10); - } - - /** - * Return `true` if a given value is in a certain range, `false` otherwise - * - * Returns `true` if the distance between `allowedValue` and `value` - * is smaller than the value of `tolerance` - * - * @memberof module:HiroMediaAdapter~ - * @private - * - * @param {number} value the value to test - * @param {number} allowedValue the value to test against - * @param {number} tolerance tolerance value - * @return {Boolean} `true` if `dimension` is in range, `false` otherwise. - */ - function isValueInRange(value, allowedValue, tolerance) { - - value = parseInt10(value); - allowedValue = parseInt10(allowedValue); - tolerance = parseInt10(tolerance); - - return (allowedValue - tolerance) <= value && value <= (allowedValue + tolerance); - - } - - /** - * Returns `true` if a size array has both dimensions in range an allowed size array, - * `false` otherwise - * - * Each dimension of `size` will be checked against the corresponding dimension - * of `allowedSize` - * - * @memberof module:HiroMediaAdapter~ - * @private - * - * @param {module:HiroMediaAdapter~size} size size array to test - * @param {module:HiroMediaAdapter~size} allowedSize size array to test against - * @param {number} tolerance tolerance value (same for both dimensions) - * @return {Boolean} `true` if the dimensions of `size` are in range of the - * dimensions of `allowedSize`, `false` otherwise. - */ - function isSizeInRange(size, allowedSize, tolerance) { - return isValueInRange(allowedSize[0], size[0], tolerance) && isValueInRange(allowedSize[1], size[1], tolerance); - } - - /** - * Normalize sizes and return an array with sizes in WIDTHxHEIGHT format - * - * Simple wrapper around `util.parseSizesInput` - * - * @memberof module:HiroMediaAdapter~ - * @private - * - * @param {array} sizes array of sizes that are passed to `util.parseSizesInput` - * @return {array} normalized array of sizes. - */ - function normalizeSizes(sizes) { - return utils.parseSizesInput(sizes).map(function (size) { - return size.split('x'); - }); - } - /** * Apply default parameters to an object if the parameters are not set * @@ -344,7 +268,8 @@ var HiroMediaAdapter = function HiroMediaAdapter() { var batchParams = [ bidParams.bidUrl, bidParams.accountId, - bidInfo.selectedSize.join('x') + bidInfo.selectedSize, + bidInfo.additionalSizes ]; return batchParams.join('-'); @@ -355,10 +280,6 @@ var HiroMediaAdapter = function HiroMediaAdapter() { * Build a set of {@linkcode module:HiroMediaAdapter~bidInfo|bidInfo} objects based on the * bids sent to {@linkcode module:HiroMediaAdapter#callBids|callBids} * - * This routine determines if a bid request should be sent for the placement, it - * will set `selectedSize` based on `params.allowedSize` and calculate the batch - * key. - * * @memberof module:HiroMediaAdapter~ * @private * @@ -375,19 +296,16 @@ var HiroMediaAdapter = function HiroMediaAdapter() { bids.forEach(function (bid) { - var sizes = normalizeSizes(bid.sizes); + var sizes = utils.parseSizesInput(bid.sizes); var bidParams = defaultParams(bid.params); - var allowedSizes = normalizeSizes([bidParams.allowedSize])[0]; - var selectedSize = sizes.find(function (size) { - return isSizeInRange(size, allowedSizes, bidParams.sizeTolerance); - }); var hasValidBidRequest = utils.hasValidBidRequest(bidParams, REQUIRED_BID_PARAMS, BIDDER_CODE); - var shouldBid = hasValidBidRequest && (selectedSize !== undefined); + var shouldBid = hasValidBidRequest; var bidInfo = { bid: bid, bidParams: bidParams, - selectedSize: selectedSize, - shouldBid: shouldBid + shouldBid: shouldBid, + selectedSize: sizes[0], + additionalSizes: sizes.slice(1).join(',') }; if (shouldBid) { @@ -460,7 +378,7 @@ var HiroMediaAdapter = function HiroMediaAdapter() { } else { // Ensure we don't run on stale data - _bidStorage = []; + _bidStorage = []; } @@ -493,8 +411,8 @@ var HiroMediaAdapter = function HiroMediaAdapter() { browser: browser.name, browserVersion: browser.version, domain: domain, - selectedSize: utils.parseSizesInput([bidInfo.selectedSize]), - placementSizes: utils.parseSizesInput(bid.sizes) + selectedSize: bidInfo.selectedSize, + additionalSizes: bidInfo.additionalSizes }); } @@ -519,14 +437,6 @@ var HiroMediaAdapter = function HiroMediaAdapter() { // JSDoc typedefs - /** - * A size array where the width is the first array item and the height is - * the second array item. - * - * @typedef {array.} module:HiroMediaAdapter~size - * @private - */ - /** * Parameters for bids to HIRO Media adapter * @@ -534,8 +444,6 @@ var HiroMediaAdapter = function HiroMediaAdapter() { * @private * * @property {string} bidUrl the bid server endpoint url - * @property {module:HiroMediaAdapter~size} allowedSize allowed placement size - * @property {number} sizeTolerance custom tolerance for `allowedSize` */ /** @@ -545,7 +453,8 @@ var HiroMediaAdapter = function HiroMediaAdapter() { * @private * * @property {object} bid original bid passed to #callBids - * @property {module:HiroMediaAdapter~size} selectedSize the selected size of the placement + * @property {string} selectedSize the first size in the the placement sizes array + * @property {string} additionalSizes list of sizes in the placement sizes array besides the first * @property {string} batchKey key used for batching requests which have the same basic properties * @property {module:HiroMediaAdapter~bidParams} bidParams original params passed for bid in #callBids * @property {boolean} shouldBid flag to determine if the bid is valid for bidding or not diff --git a/test/spec/adapters/hiromedia_spec.js b/test/spec/adapters/hiromedia_spec.js index 4cbc166b6a3..b91bcca38ac 100644 --- a/test/spec/adapters/hiromedia_spec.js +++ b/test/spec/adapters/hiromedia_spec.js @@ -1,3 +1,4 @@ +/*jslint white: true, es6: true, single: true*/ /*jshint esversion:6*/ import { expect } from 'chai'; @@ -57,7 +58,7 @@ describe('hiromedia adapter', function () { }; // Helper function to generate a 'mock' bid object - const placement = (size) => { + const makePlacement = (size) => { placementId += 1; @@ -73,10 +74,10 @@ describe('hiromedia adapter', function () { }; // 300x250 are in the allowed size by default - const allowedPlacement = () => placement([300, 250]); + const tilePlacement = () => makePlacement([300, 250]); // anything else should have no bid by default - const unallowedPlacement = () => placement([728, 90]); + const leaderPlacement = () => makePlacement([728, 90]); describe('callbids', () => { @@ -99,99 +100,61 @@ describe('hiromedia adapter', function () { assertNoBids(); }); - it('responds with status `NO_BID` for placements that are not in the allowed size', () => { + it('invokes a bid request per placement', () => { - const params = { - bids: [unallowedPlacement()] - }; - - adapter.callBids(params); - sinon.assert.notCalled(loadScriptStub); - sinon.assert.calledOnce(addBidResponseStub); - - const placementCode = addBidResponseStub.getCall(0).args[0]; - const bidObject = addBidResponseStub.getCall(0).args[1]; - expect(placementCode).to.be.equal('div-gpt-ad-12345-1'); - expect(bidObject.getStatusCode()).to.be.equal(STATUS.NO_BID); - - }); - - it('invokes a single bid request for a single valid placement', () => { + const expectedRequests = [{ + batchKey: [DEFAULT_ENDPOINT,'1337','300x250',''].join('-'), + placementCode: 'div-gpt-ad-12345-1', + selectedSize: '300x250' + }, { + batchKey: [DEFAULT_ENDPOINT,'1337','728x90',''].join('-'), + placementCode: 'div-gpt-ad-12345-2', + selectedSize: '728x90' + }]; const params = { - bids: [allowedPlacement()] + bids: [tilePlacement(), leaderPlacement()] }; - + adapter.callBids(params); - sinon.assert.calledOnce(loadScriptStub); + sinon.assert.calledTwice(loadScriptStub); sinon.assert.notCalled(addBidResponseStub); - sinon.assert.calledOnce(hasValidBidRequestSpy); + sinon.assert.calledTwice(hasValidBidRequestSpy); - expect(hasValidBidRequestSpy.returnValues[0]).to.be.equal(true); + expectedRequests.forEach(function(request, index) { - // validate request - const bidRequest = loadScriptStub.getCall(0).args[0]; - const defaultBidUrl = urlParse(DEFAULT_ENDPOINT); - const bidUrl = urlParse(bidRequest); - const query = querystringify.parse(bidUrl.query); - - expect(bidUrl.hostname).to.equal(defaultBidUrl.hostname); - expect(bidUrl.pathname).to.equal(defaultBidUrl.pathname); - - // adapter version - expect(query).to.have.property('adapterVersion'); - - // callback - expect(query).to.have.property('callback').and.to.equal('$$PREBID_GLOBAL$$.' + DEFAULT_CALLBACK_NAME); + expect(hasValidBidRequestSpy.returnValues[index]).to.be.equal(true); - // batch key - expect(query).to.have.property('batchKey').and.to.equal([DEFAULT_ENDPOINT,'1337','300x250'].join('-')); + // validate request + const bidRequest = loadScriptStub.getCall(index).args[0]; + const defaultBidUrl = urlParse(DEFAULT_ENDPOINT); + const bidUrl = urlParse(bidRequest); + const query = querystringify.parse(bidUrl.query); - // placementCode - expect(query).to.have.property('placementCode').and.to.equal('div-gpt-ad-12345-1'); - - // account id - expect(query).to.have.property('accountId').and.to.equal('1337'); - - // selectedSize - expect(query).to.have.property('selectedSize').and.to.equal('300x250'); + expect(bidUrl.hostname).to.equal(defaultBidUrl.hostname); + expect(bidUrl.pathname).to.equal(defaultBidUrl.pathname); - // bid request size list - expect(query).to.have.property('placementSizes').and.to.equal('300x250'); + expect(query).to.have.property('adapterVersion').and.to.equal('3'); + expect(query).to.have.property('callback').and.to.equal('$$PREBID_GLOBAL$$.' + DEFAULT_CALLBACK_NAME); + expect(query).to.have.property('batchKey').and.to.equal(request.batchKey); + expect(query).to.have.property('placementCode').and.to.equal(request.placementCode); + expect(query).to.have.property('accountId').and.to.equal('1337'); + expect(query).to.have.property('selectedSize').and.to.equal(request.selectedSize); + expect(query).to.not.have.property('additionalSizes'); + expect(query).to.have.property('domain').and.to.equal(window.top.location.hostname); - // page url domain (hostname) - expect(query).to.have.property('domain').and.to.equal(window.top.location.hostname); + }); }); - // Test `params.allowedSize` default - it('finds an allowed size in a placement with multiple sizes', () => { - - const placement = allowedPlacement(); - - // add an non-allowed size, should be skipped - placement.sizes.unshift([300, 600]); - - const params = { - bids: [placement] - }; - - adapter.callBids(params); - sinon.assert.calledOnce(loadScriptStub); - - const bidRequest = loadScriptStub.getCall(0).args[0]; - const bidUrl = urlParse(bidRequest); - const query = querystringify.parse(bidUrl.query); - - expect(query).to.have.property('placementSizes').and.to.equal('300x600,300x250'); + // Test additionalSizes parameter + it('attaches multiple sizes to additionalSizes', () => { - }); - - // Test `params.sizeTolerance` default - it('tolerates sizes in default size tolerance range', () => { + const placement = tilePlacement(); - const placement = unallowedPlacement(); - placement.sizes.unshift([305, 245]); + // Append additional + placement.sizes.push([300, 600]); + placement.sizes.push([300, 900]); const params = { bids: [placement] @@ -204,76 +167,15 @@ describe('hiromedia adapter', function () { const bidUrl = urlParse(bidRequest); const query = querystringify.parse(bidUrl.query); - expect(query).to.have.property('selectedSize').and.to.equal('305x245'); - - }); - - // Test `params.allowedSize` negative match - it('accepts a custom allowed size per placement which makes no bid if no match is found', () => { - - const placement = allowedPlacement(); - placement.params.allowedSize = [728, 90]; - - const params = { - bids: [placement] - }; - - adapter.callBids(params); - sinon.assert.notCalled(loadScriptStub); - - }); - - // Test `params.allowedSize` match - it('accepts a custom allowed size per placement which bids if a match is found', () => { - - const placement = unallowedPlacement(); - placement.params.allowedSize = [728, 90]; - - const params = { - bids: [placement] - }; - - adapter.callBids(params); - sinon.assert.calledOnce(loadScriptStub); - - }); - - // Test `params.sizeTolerance` negative match - it('accepts a custom size tolerance which makes no bid if no match is found', () => { - - const placement = unallowedPlacement(); - placement.sizes.unshift([280, 270]); - placement.params.sizeTolerance = 10; - - const params = { - bids: [placement] - }; - - adapter.callBids(params); - sinon.assert.notCalled(loadScriptStub); - - }); - - // Test `params.sizeTolerance` match - it('accepts a custom size tolerance which makes a bid if a match is found', () => { - - const placement = unallowedPlacement(); - placement.sizes.unshift([280, 270]); - placement.params.sizeTolerance = 20; - - const params = { - bids: [placement] - }; - - adapter.callBids(params); - sinon.assert.calledOnce(loadScriptStub); + expect(query).to.have.property('selectedSize').and.to.equal('300x250'); + expect(query).to.have.property('additionalSizes').and.to.equal('300x600,300x900'); }); // Test `params.accountId` validation it('invalidates bids with no id', () => { - const placement = allowedPlacement(); + const placement = tilePlacement(); delete placement.params; const params = { @@ -290,7 +192,7 @@ describe('hiromedia adapter', function () { // Test `params.bidUrl` it('accepts a custom bid endpoint url', () => { - const placement = allowedPlacement(); + const placement = tilePlacement(); placement.params.bidUrl = DEFAULT_ENDPOINT + '?someparam=value'; const params = { @@ -315,7 +217,7 @@ describe('hiromedia adapter', function () { it('batches similar bid requests for similar sized placements', () => { const params = { - bids: [allowedPlacement(), allowedPlacement()] + bids: [tilePlacement(), tilePlacement()] }; adapter.callBids(params); @@ -350,50 +252,54 @@ describe('hiromedia adapter', function () { // the adapter set up correctly. it('adds a bid reponse for each pending bid', () => { - const response = { - batchKey: [DEFAULT_ENDPOINT,'1337','300x250'].join('-'), + const expectedResponses = [{ + batchKey: [DEFAULT_ENDPOINT,'1337','300x250',''].join('-'), + width: '300', + height: '250', cpm: 0.4, ad: '' - }; + }, { + batchKey: [DEFAULT_ENDPOINT,'1337','728x90',''].join('-'), + width: '728', + height: '90', + cpm: 0.4, + ad: '' + }]; + // Instead of the dead stub defined in the top scope, we'll use // one that mocks a response. loadScriptStub.restore(); + let id = 0; const activeLoadScriptStub = sandbox.stub(adloader, 'loadScript', (url) => { const handler = getResponseHandler(); - handler(response); + handler(expectedResponses[id]); + id += 1; }); const params = { - bids: [allowedPlacement(), allowedPlacement()] + bids: [tilePlacement(), leaderPlacement()] }; adapter.callBids(params); - // single request but two responses - sinon.assert.calledOnce(activeLoadScriptStub); - sinon.assert.calledTwice(addBidResponseStub); + sinon.assert.calledTwice(activeLoadScriptStub); + sinon.assert.calledTwice(addBidResponseStub); - const placementCode = addBidResponseStub.getCall(0).args[0]; - const secondPlacementCode = addBidResponseStub.getCall(1).args[0]; + expectedResponses.forEach((expectedResponse, i) => { - const bidObject = addBidResponseStub.getCall(0).args[1]; - const secondBidObject = addBidResponseStub.getCall(1).args[1]; + const placementCode = addBidResponseStub.getCall(i).args[0]; + const bidObject = addBidResponseStub.getCall(i).args[1]; - expect(placementCode).to.be.equal('div-gpt-ad-12345-1'); - expect(secondPlacementCode).to.be.equal('div-gpt-ad-12345-2'); + expect(placementCode).to.be.equal('div-gpt-ad-12345-' + (i + 1)); - expect(bidObject.getStatusCode()).to.be.equal(STATUS.GOOD); - expect(bidObject).to.have.property('cpm').and.to.equal(0.4); - expect(bidObject).to.have.property('ad').and.to.equal(''); - expect(bidObject).to.have.property('width').and.to.equal('300'); - expect(bidObject).to.have.property('height').and.to.equal('250'); + expect(bidObject.getStatusCode()).to.be.equal(STATUS.GOOD); + expect(bidObject).to.have.property('cpm').and.to.equal(expectedResponse.cpm); + expect(bidObject).to.have.property('ad').and.to.equal(expectedResponse.ad); + expect(bidObject).to.have.property('width').and.to.equal(expectedResponse.width); + expect(bidObject).to.have.property('height').and.to.equal(expectedResponse.height); - expect(secondBidObject.getStatusCode()).to.be.equal(STATUS.GOOD); - expect(secondBidObject).to.have.property('cpm').and.to.equal(0.4); - expect(secondBidObject).to.have.property('ad').and.to.equal(''); - expect(secondBidObject).to.have.property('width').and.to.equal('300'); - expect(secondBidObject).to.have.property('height').and.to.equal('250'); + }); }); @@ -404,7 +310,7 @@ describe('hiromedia adapter', function () { it('adds responses according to the sampling defined in the response', () => { const response = { - batchKey: [DEFAULT_ENDPOINT,'1337','300x250'].join('-'), + batchKey: [DEFAULT_ENDPOINT,'1337','300x250',''].join('-'), cpm: 0.4, chance: 0.25, ad: '' @@ -437,7 +343,7 @@ describe('hiromedia adapter', function () { }); const params = { - bids: [allowedPlacement()] + bids: [tilePlacement()] }; adapter.callBids(params);