From 3e3ba8177d36fcd01014be5a44a11f1ee9689274 Mon Sep 17 00:00:00 2001 From: Bryan Gahagan Date: Mon, 30 Jul 2018 12:23:01 -0400 Subject: [PATCH] Sortable adapter: support different floors for each size (#2868) And some other minor fixes - Update the way keywords are send to the server - Use the correct creativeId --- modules/sortableBidAdapter.js | 22 ++++-- test/spec/modules/sortableBidAdapter_spec.js | 75 ++++++++++++++++++-- 2 files changed, 88 insertions(+), 9 deletions(-) diff --git a/modules/sortableBidAdapter.js b/modules/sortableBidAdapter.js index eeac1cf2796..4eec7228c5b 100644 --- a/modules/sortableBidAdapter.js +++ b/modules/sortableBidAdapter.js @@ -14,7 +14,19 @@ export const spec = { isBidRequestValid: function(bid) { const sortableConfig = config.getConfig('sortable'); const haveSiteId = (sortableConfig && !!sortableConfig.siteId) || bid.params.siteId; - return !!(bid.params.tagId && haveSiteId && bid.sizes && + const validFloor = !bid.params.floor || utils.isNumber(bid.params.floor); + const validSize = /\d+x\d+/; + const validFloorSizeMap = !bid.params.floorSizeMap || + (utils.isPlainObject(bid.params.floorSizeMap) && + Object.keys(bid.params.floorSizeMap).every(size => + size.match(validSize) && utils.isNumber(bid.params.floorSizeMap[size]) + )) + const validKeywords = !bid.params.keywords || + (utils.isPlainObject(bid.params.keywords) && + Object.keys(bid.params.keywords).every(key => + utils.isStr(key) && utils.isStr(bid.params.keywords[key]) + )) + return !!(bid.params.tagId && haveSiteId && validFloor && validFloorSizeMap && validKeywords && bid.sizes && bid.sizes.every(sizeArr => sizeArr.length == 2 && sizeArr.every(num => utils.isNumber(num)))); }, @@ -36,14 +48,16 @@ export const spec = { rv.bidfloor = bid.params.floor; } if (bid.params.keywords) { - let keywords = utils._map(bid.params.keywords, (foo, bar) => ({name: bar, value: foo})); - rv.ext.keywords = keywords; + rv.ext.keywords = bid.params.keywords; } if (bid.params.bidderParams) { utils._each(bid.params.bidderParams, (params, partner) => { rv.ext[partner] = params; }); } + if (bid.params.floorSizeMap) { + rv.ext.floorSizeMap = bid.params.floorSizeMap; + } return rv; }); const gdprConsent = bidderRequest && bidderRequest.gdprConsent; @@ -95,7 +109,7 @@ export const spec = { cpm: parseFloat(bid.price), width: parseInt(bid.w), height: parseInt(bid.h), - creativeId: bid.id, + creativeId: bid.crid || bid.id, dealId: bid.dealid || null, currency: 'USD', netRevenue: true, diff --git a/test/spec/modules/sortableBidAdapter_spec.js b/test/spec/modules/sortableBidAdapter_spec.js index 9f351e9764c..6f1c9efba84 100644 --- a/test/spec/modules/sortableBidAdapter_spec.js +++ b/test/spec/modules/sortableBidAdapter_spec.js @@ -19,6 +19,10 @@ describe('sortableBidAdapter', function() { 'keywords': { 'key1': 'val1', 'key2': 'val2' + }, + 'floorSizeMap': { + '728x90': 0.15, + '300x250': 1.20 } }, 'adUnitCode': 'adunit-code', @@ -58,6 +62,45 @@ describe('sortableBidAdapter', function() { bid.params = {}; expect(spec.isBidRequestValid(bid)).to.equal(false); }); + + it('should return false when the floorSizeMap is invalid', () => { + let bid = makeBid(); + bid.params.floorSizeMap = { + 'sixforty by foureighty': 1234 + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + bid.params.floorSizeMap = { + '728x90': 'three' + } + expect(spec.isBidRequestValid(bid)).to.equal(false); + bid.params.floorSizeMap = 'a'; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return true when the floorSizeMap is missing or empty', () => { + let bid = makeBid(); + bid.params.floorSizeMap = {}; + expect(spec.isBidRequestValid(bid)).to.equal(true); + delete bid.params.floorSizeMap; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + it('should return false when the keywords are invalid', () => { + let bid = makeBid(); + bid.params.keywords = { + 'badval': 1234 + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + bid.params.keywords = 'a'; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return true when the keywords are missing or empty', () => { + let bid = makeBid(); + bid.params.keywords = {}; + expect(spec.isBidRequestValid(bid)).to.equal(true); + delete bid.params.keywords; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); }); describe('buildRequests', () => { @@ -70,6 +113,10 @@ describe('sortableBidAdapter', function() { 'keywords': { 'key1': 'val1', 'key2': 'val2' + }, + 'floorSizeMap': { + '728x90': 0.15, + '300x250': 1.20 } }, 'sizes': [ @@ -102,14 +149,21 @@ describe('sortableBidAdapter', function() { }); it('includes the params in the bid request', () => { - expect(requestBody.imp[0].ext.keywords).to.deep.equal([ - {'name': 'key1', 'value': 'val1'}, - {'name': 'key2', 'value': 'val2'} - ]); + expect(requestBody.imp[0].ext.keywords).to.deep.equal( + {'key1': 'val1', + 'key2': 'val2'} + ); expect(requestBody.site.publisher.id).to.equal('example.com'); expect(requestBody.imp[0].tagid).to.equal('403370'); expect(requestBody.imp[0].bidfloor).to.equal(0.21); }); + + it('should have the floor size map set', () => { + expect(requestBody.imp[0].ext.floorSizeMap).to.deep.equal({ + '728x90': 0.15, + '300x250': 1.20 + }); + }); }); describe('interpretResponse', () => { @@ -122,6 +176,7 @@ describe('sortableBidAdapter', function() { 'bid': [ { 'id': '6vmb3isptf', + 'crid': 'sortablescreative', 'impid': '322add653672f68', 'price': 1.22, 'adm': '', @@ -144,7 +199,7 @@ describe('sortableBidAdapter', function() { 'cpm': 1.22, 'width': 728, 'height': 90, - 'creativeId': '6vmb3isptf', + 'creativeId': 'sortablescreative', 'dealId': null, 'currency': 'USD', 'netRevenue': true, @@ -159,6 +214,16 @@ describe('sortableBidAdapter', function() { expect(result[0]).to.deep.equal(expectedBid); }); + it('should handle a missing crid', () => { + let noCridResponse = makeResponse(); + delete noCridResponse.body.seatbid[0].bid[0].crid; + const fallbackCrid = noCridResponse.body.seatbid[0].bid[0].id; + let noCridResult = Object.assign({}, expectedBid, {'creativeId': fallbackCrid}); + let result = spec.interpretResponse(noCridResponse); + expect(result.length).to.equal(1); + expect(result[0]).to.deep.equal(noCridResult); + }); + it('should handle a missing nurl', () => { let noNurlResponse = makeResponse(); delete noNurlResponse.body.seatbid[0].bid[0].nurl;