From 9f9f58978ae5e0521b27fc382fb3ee0d50e10f20 Mon Sep 17 00:00:00 2001 From: onetag-dev <38786435+onetag-dev@users.noreply.github.com> Date: Thu, 21 Jul 2022 12:22:15 +0200 Subject: [PATCH] onetag Bid Adapter : add support for price floors, supply chain, and gpid (#8675) * Adds support for price floor, supply chain, GPID * Removes unused import * Updates onetag test file * Remove trailing space * Add unit tests for schain validation function Co-authored-by: federico --- modules/onetagBidAdapter.js | 51 ++++++++++++++++--- test/spec/modules/onetagBidAdapter_spec.js | 57 ++++++++++++++++++++-- 2 files changed, 97 insertions(+), 11 deletions(-) diff --git a/modules/onetagBidAdapter.js b/modules/onetagBidAdapter.js index 89c614dba23..0364acd5d21 100644 --- a/modules/onetagBidAdapter.js +++ b/modules/onetagBidAdapter.js @@ -1,13 +1,13 @@ 'use strict'; -import {BANNER, VIDEO} from '../src/mediaTypes.js'; -import {INSTREAM, OUTSTREAM} from '../src/video.js'; -import {Renderer} from '../src/Renderer.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { INSTREAM, OUTSTREAM } from '../src/video.js'; +import { Renderer } from '../src/Renderer.js'; import {find} from '../src/polyfill.js'; -import {getStorageManager} from '../src/storageManager.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {createEidsArray} from './userId/eids.js'; -import {deepClone} from '../src/utils.js'; +import { getStorageManager } from '../src/storageManager.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { createEidsArray } from './userId/eids.js'; +import { deepClone, logError, deepAccess } from '../src/utils.js'; const ENDPOINT = 'https://onetag-sys.com/prebid-request'; const USER_SYNC_ENDPOINT = 'https://onetag-sys.com/usync/'; @@ -68,6 +68,9 @@ function buildRequests(validBidRequests, bidderRequest) { if (validBidRequests && validBidRequests.length !== 0 && validBidRequests[0].userId) { payload.userId = createEidsArray(validBidRequests[0].userId); } + if (validBidRequests && validBidRequests.length !== 0 && validBidRequests[0].schain && isSchainValid(validBidRequests[0].schain)) { + payload.schain = validBidRequests[0].schain; + } try { if (storage.hasLocalStorage()) { payload.onetagSid = storage.getDataFromLocalStorage('onetag_sid'); @@ -245,6 +248,7 @@ function requestsToBids(bidRequests) { // Other params videoObj['mediaTypeInfo'] = deepClone(bidRequest.mediaTypes.video); videoObj['type'] = VIDEO; + videoObj['priceFloors'] = getBidFloor(bidRequest, VIDEO, videoObj['playerSize']); return videoObj; }); const bannerBidRequests = bidRequests.filter(bidRequest => isValid(BANNER, bidRequest)).map(bidRequest => { @@ -253,6 +257,7 @@ function requestsToBids(bidRequests) { bannerObj['sizes'] = parseSizes(bidRequest); bannerObj['type'] = BANNER; bannerObj['mediaTypeInfo'] = deepClone(bidRequest.mediaTypes.banner); + bannerObj['priceFloors'] = getBidFloor(bidRequest, BANNER, bannerObj['sizes']); return bannerObj; }); return videoBidRequests.concat(bannerBidRequests); @@ -265,6 +270,7 @@ function setGeneralInfo(bidRequest) { this['bidderRequestId'] = bidRequest.bidderRequestId; this['auctionId'] = bidRequest.auctionId; this['transactionId'] = bidRequest.transactionId; + this['gpid'] = deepAccess(bidRequest, 'ortb2Imp.ext.gpid') || deepAccess(bidRequest, 'ortb2Imp.ext.data.pbadslot'); this['pubId'] = params.pubId; this['ext'] = params.ext; if (params.pubClick) { @@ -373,6 +379,37 @@ function getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent) { return syncs; } +function getBidFloor(bidRequest, mediaType, sizes) { + const priceFloors = []; + if (typeof bidRequest.getFloor === 'function') { + sizes.forEach(size => { + const floor = bidRequest.getFloor({ + currency: 'EUR', + mediaType: mediaType || '*', + size: [size.width, size.height] + }); + floor.size = deepClone(size); + if (!floor.floor) { floor.floor = null; } + priceFloors.push(floor); + }); + } + return priceFloors; +} + +export function isSchainValid(schain) { + let isValid = false; + const requiredFields = ['asi', 'sid', 'hp']; + if (!schain || !schain.nodes) return isValid; + isValid = schain.nodes.reduce((status, node) => { + if (!status) return status; + return requiredFields.every(field => node.hasOwnProperty(field)); + }, true); + if (!isValid) { + logError('OneTag: required schain params missing'); + } + return isValid; +} + export const spec = { code: BIDDER_CODE, gvlid: GVLID, diff --git a/test/spec/modules/onetagBidAdapter_spec.js b/test/spec/modules/onetagBidAdapter_spec.js index f335f2ec62a..2dc0a43bbb0 100644 --- a/test/spec/modules/onetagBidAdapter_spec.js +++ b/test/spec/modules/onetagBidAdapter_spec.js @@ -1,4 +1,4 @@ -import { spec, isValid, hasTypeVideo } from 'modules/onetagBidAdapter.js'; +import { spec, isValid, hasTypeVideo, isSchainValid } from 'modules/onetagBidAdapter.js'; import { expect } from 'chai'; import {find} from 'src/polyfill.js'; import { BANNER, VIDEO } from 'src/mediaTypes.js'; @@ -15,7 +15,21 @@ describe('onetag', function () { 'bidId': '30b31c1838de1e', 'bidderRequestId': '22edbae2733bf6', 'auctionId': '1d1a030790a475', - 'transactionId': 'qwerty123' + 'transactionId': 'qwerty123', + 'schain': { + 'validation': 'off', + 'config': { + 'ver': '1.0', + 'complete': 1, + 'nodes': [ + { + 'asi': 'indirectseller.com', + 'sid': '00001', + 'hp': 1 + } + ] + } + }, }; } @@ -193,7 +207,8 @@ describe('onetag', function () { 'context', 'playerSize', 'mediaTypeInfo', - 'type' + 'type', + 'priceFloors' ); } else if (isValid(BANNER, bid)) { expect(bid).to.have.all.keys( @@ -205,9 +220,13 @@ describe('onetag', function () { 'transactionId', 'mediaTypeInfo', 'sizes', - 'type' + 'type', + 'priceFloors' ); } + if (bid.schain && isSchainValid(bid.schain)) { + expect(data).to.have.all.keys('schain'); + } expect(bid.bidId).to.be.a('string'); expect(bid.pubId).to.be.a('string'); } @@ -358,6 +377,36 @@ describe('onetag', function () { expect(syncs[0].url).to.match(/(?:[?&](?:us_privacy=us_foo(?:[&][^&]*)*))+$/); }); }); + describe('isSchainValid', function () { + it('Should return false when schain is null or undefined', function () { + expect(isSchainValid(null)).to.be.false; + expect(isSchainValid(undefined)).to.be.false; + }); + it('Should return false when schain is missing nodes key', function () { + const schain = {'otherKey': 'otherValue'}; + expect(isSchainValid(schain)).to.be.false; + }); + it('Should return false when schain is missing one of the required SupplyChainNode attribute', function () { + const missingAsiNode = {'sid': '00001', 'hp': 1}; + const missingSidNode = {'asi': 'indirectseller.com', 'hp': 1}; + const missingHpNode = {'asi': 'indirectseller.com', 'sid': '00001'}; + expect(isSchainValid({'config': {'nodes': [missingAsiNode]}})).to.be.false; + expect(isSchainValid({'config': {'nodes': [missingSidNode]}})).to.be.false; + expect(isSchainValid({'config': {'nodes': [missingHpNode]}})).to.be.false; + }); + it('Should return true when schain contains all required attributes', function () { + const validSchain = { + 'nodes': [ + { + 'asi': 'indirectseller.com', + 'sid': '00001', + 'hp': 1 + } + ] + }; + expect(isSchainValid(validSchain)).to.be.true; + }) + }); }); function getBannerVideoResponse() {