Skip to content

Commit

Permalink
Rubicon: support SupplyChain (schain) (#4315)
Browse files Browse the repository at this point in the history
* Add microadBidAdapter

* Remove unnecessary encodeURIComponent from microadBidAdapter

* Submit Advangelists Prebid Adapter

* Submit Advangelists Prebid Adapter 1.1

* Correct procudtion endpoint for prebid

* analytics update with wrapper name

* reverted error merge

* update changed default value of netRevenue to true

* Starting schain

* More tests for banner schain support

* Video tests

* Encoding tweaks, required fields and comments

* Removed .only() from tests

* Change requests per Bret
  • Loading branch information
tjeastmond authored and harpere committed Nov 8, 2019
1 parent 28d242d commit 12b7eed
Show file tree
Hide file tree
Showing 2 changed files with 203 additions and 2 deletions.
68 changes: 66 additions & 2 deletions modules/rubiconBidAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,10 @@ export const spec = {
utils.deepSetValue(data, 'regs.coppa', 1);
}

if (bidRequest.schain && hasValidSupplyChainParams(bidRequest.schain)) {
utils.deepSetValue(data, 'source.ext.schain', bidRequest.schain);
}

return {
method: 'POST',
url: VIDEO_ENDPOINT,
Expand All @@ -277,7 +281,7 @@ export const spec = {
url: FASTLANE_ENDPOINT,
data: spec.getOrderedParams(bidParams).reduce((paramString, key) => {
const propValue = bidParams[key];
return ((utils.isStr(propValue) && propValue !== '') || utils.isNumber(propValue)) ? `${paramString}${key}=${encodeURIComponent(propValue)}&` : paramString;
return ((utils.isStr(propValue) && propValue !== '') || utils.isNumber(propValue)) ? `${paramString}${encodeParam(key, propValue)}&` : paramString;
}, '') + `slots=1&rand=${Math.random()}`,
bidRequest
};
Expand Down Expand Up @@ -308,7 +312,7 @@ export const spec = {
url: FASTLANE_ENDPOINT,
data: spec.getOrderedParams(combinedSlotParams).reduce((paramString, key) => {
const propValue = combinedSlotParams[key];
return ((utils.isStr(propValue) && propValue !== '') || utils.isNumber(propValue)) ? `${paramString}${key}=${encodeURIComponent(propValue)}&` : paramString;
return ((utils.isStr(propValue) && propValue !== '') || utils.isNumber(propValue)) ? `${paramString}${encodeParam(key, propValue)}&` : paramString;
}, '') + `slots=${bidsInGroup.length}&rand=${Math.random()}`,
bidRequest: bidsInGroup
});
Expand All @@ -332,6 +336,8 @@ export const spec = {
'p_pos',
'gdpr',
'gdpr_consent',
'rp_schain',
'rf',
'tpid_tdid',
'tpid_liveintent.com',
'tg_v.LIseg',
Expand Down Expand Up @@ -482,9 +488,38 @@ export const spec = {
data['coppa'] = 1;
}

// if SupplyChain is supplied and contains all required fields
if (bidRequest.schain && hasValidSupplyChainParams(bidRequest.schain)) {
data.rp_schain = spec.serializeSupplyChain(bidRequest.schain);
}

return data;
},

/**
* Serializes schain params according to OpenRTB requirements
* @param {Object} supplyChain
* @returns {String}
*/
serializeSupplyChain: function (supplyChain) {
const supplyChainIsValid = hasValidSupplyChainParams(supplyChain);
if (!supplyChainIsValid) return '';
const { ver, complete, nodes } = supplyChain;
return `${ver},${complete}!${spec.serializeSupplyChainNodes(nodes)}`;
},

/**
* Properly sorts schain object params
* @param {Array} nodes
* @returns {String}
*/
serializeSupplyChainNodes: function (nodes) {
const nodePropOrder = ['asi', 'sid', 'hp', 'rid', 'name', 'domain'];
return nodes.map(node => {
return nodePropOrder.map(prop => encodeURIComponent(node[prop] || '')).join(',');
}).join('!');
},

/**
* @param {*} responseObj
* @param {BidRequest|Object.<string, BidRequest[]>} bidRequest - if request was SRA the bidRequest argument will be a keyed BidRequest array object,
Expand Down Expand Up @@ -982,6 +1017,35 @@ export function hasValidVideoParams(bid) {
return isValid;
}

/**
* Make sure the required params are present
* @param {Object} schain
* @param {Bool}
*/
export function hasValidSupplyChainParams(schain) {
let isValid = false;
const requiredFields = ['asi', 'sid', 'hp'];
if (!schain.nodes) return isValid;
isValid = schain.nodes.reduce((status, node) => {
if (!status) return status;
return requiredFields.every(field => node[field]);
}, true);
if (!isValid) utils.logError('Rubicon: required schain params missing');
return isValid;
}

/**
* Creates a URL key value param, encoding the
* param unless the key is schain
* @param {String} key
* @param {String} param
* @returns {String}
*/
export function encodeParam(key, param) {
if (key === 'rp_schain') return `rp_schain=${param}`;
return `${key}=${encodeURIComponent(param)}`;
}

/**
* split array into multiple arrays of defined size
* @param {Array} array
Expand Down
137 changes: 137 additions & 0 deletions test/spec/modules/rubiconBidAdapter_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,49 @@ describe('the rubicon adapter', function () {
* @param {Array.<overrideProps>} [indexOverMap]
* @return {{status: string, cpm: number, zone_id: *, size_id: *, impression_id: *, ad_id: *, creative_id: string, type: string, targeting: *[]}}
*/

function getBidderRequest() {
return {
bidderCode: 'rubicon',
auctionId: 'c45dd708-a418-42ec-b8a7-b70a6c6fab0a',
bidderRequestId: '178e34bad3658f',
bids: [
{
bidder: 'rubicon',
params: {
accountId: '14062',
siteId: '70608',
zoneId: '335918',
userId: '12346',
keywords: ['a', 'b', 'c'],
inventory: {
rating: '5-star', // This actually should not be sent to frank!! causes 400
prodtype: ['tech', 'mobile']
},
visitor: {
ucat: 'new',
lastsearch: 'iphone',
likes: ['sports', 'video games']
},
position: 'atf',
referrer: 'localhost',
latLong: [40.7607823, '111.8910325']
},
adUnitCode: '/19968336/header-bid-tag-0',
code: 'div-1',
sizes: [[300, 250], [320, 50]],
bidId: '2ffb201a808da7',
bidderRequestId: '178e34bad3658f',
auctionId: 'c45dd708-a418-42ec-b8a7-b70a6c6fab0a',
transactionId: 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b'
}
],
start: 1472239426002,
auctionStart: 1472239426000,
timeout: 5000
};
};

function createResponseAdByIndex(i, sizeId, indexOverMap) {
const overridePropMap = (indexOverMap && indexOverMap[i] && typeof indexOverMap[i] === 'object') ? indexOverMap[i] : {};
const overrideProps = Object.keys(overridePropMap).reduce((aggregate, key) => {
Expand Down Expand Up @@ -2267,4 +2310,98 @@ describe('the rubicon adapter', function () {
});
});
});

describe.only('Supply Chain Support', function() {
const nodePropsOrder = ['asi', 'sid', 'hp', 'rid', 'name', 'domain'];
let bidRequests;
let schainConfig;

const getSupplyChainConfig = () => {
return {
ver: '1.0',
complete: 1,
nodes: [
{
asi: 'rubicon.com',
sid: '1234',
hp: 1,
rid: 'bid-request-1',
name: 'pub one',
domain: 'pub1.com'
},
{
asi: 'theexchange.com',
sid: '5678',
hp: 1,
rid: 'bid-request-2',
name: 'pub two',
domain: 'pub2.com'
},
{
asi: 'wesellads.com',
sid: '9876',
hp: 1,
rid: 'bid-request-3',
// name: 'alladsallthetime',
domain: 'alladsallthetime.com'
}
]
};
};

beforeEach(() => {
bidRequests = getBidderRequest();
schainConfig = getSupplyChainConfig();
bidRequests.bids[0].schain = schainConfig;
});

it('should properly serialize schain object with correct delimiters', () => {
const results = spec.buildRequests(bidRequests.bids, bidRequests);
const numNodes = schainConfig.nodes.length;
const schain = parseQuery(results[0].data).rp_schain;

// each node serialization should start with an !
expect(schain.match(/!/g).length).to.equal(numNodes);

// 5 commas per node plus 1 for version
expect(schain.match(/,/g).length).to.equal(numNodes * 5 + 1);
});

it('should send the proper version for the schain', () => {
const results = spec.buildRequests(bidRequests.bids, bidRequests);
const schain = parseQuery(results[0].data).rp_schain.split('!');
const version = schain.shift().split(',')[0];
expect(version).to.equal(bidRequests.bids[0].schain.ver);
});

it('should send the correct value for complete in schain', () => {
const results = spec.buildRequests(bidRequests.bids, bidRequests);
const schain = parseQuery(results[0].data).rp_schain.split('!');
const complete = schain.shift().split(',')[1];
expect(complete).to.equal(String(bidRequests.bids[0].schain.complete));
});

it('should send available params in the right order', () => {
const results = spec.buildRequests(bidRequests.bids, bidRequests);
const schain = parseQuery(results[0].data).rp_schain.split('!');
schain.shift();

schain.forEach((serializeNode, nodeIndex) => {
const nodeProps = serializeNode.split(',');
nodeProps.forEach((nodeProp, propIndex) => {
const node = schainConfig.nodes[nodeIndex];
const key = nodePropsOrder[propIndex];
expect(nodeProp).to.equal(node[key] ? String(node[key]) : '');
});
});
});

it('should copy the schain JSON to to bid.source.ext.schain', () => {
createVideoBidderRequest();
const schain = getSupplyChainConfig();
bidderRequest.bids[0].schain = schain;
const request = spec.buildRequests(bidderRequest.bids, bidderRequest);
expect(request[0].data.source.ext.schain).to.deep.equal(schain);
});
});
});

0 comments on commit 12b7eed

Please sign in to comment.