diff --git a/modules/adkernelBidAdapter.js b/modules/adkernelBidAdapter.js index 800c58225f4..a47faec961e 100644 --- a/modules/adkernelBidAdapter.js +++ b/modules/adkernelBidAdapter.js @@ -29,6 +29,7 @@ import { getBidFloor } from '../libraries/adkernelUtils/adkernelUtils.js'; * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid * @typedef {import('../src/adapters/bidderFactory.js').ServerRequest} ServerRequest * @typedef {import('../src/adapters/bidderFactory.js').UserSync} UserSync + * @typedef {import('../src/types/ortb/ext/dsa.d.ts').DSARequest} DSARequest */ const VIDEO_PARAMS = ['pos', 'context', 'placement', 'plcmt', 'api', 'mimes', 'protocols', 'playbackmethod', 'minduration', 'maxduration', @@ -124,9 +125,10 @@ export const spec = { 'zoneId' in bidRequest.params && !isNaN(Number(bidRequest.params.zoneId)) && bidRequest.params.zoneId > 0 && - bidRequest.mediaTypes && - (bidRequest.mediaTypes.banner || bidRequest.mediaTypes.video || - (bidRequest.mediaTypes.native && validateNativeAdUnit(bidRequest.mediaTypes.native)) + ( + isPlainObject(bidRequest?.mediaTypes?.banner) || + isPlainObject(bidRequest?.mediaTypes?.video) || + (isPlainObject(bidRequest?.mediaTypes?.native) && validateNativeAdUnit(bidRequest.mediaTypes.native)) ); }, @@ -224,6 +226,9 @@ export const spec = { if (isStr(rtbBid.ext.agency_name)) { deepSetValue(prBid, 'meta.agencyName', rtbBid.ext.agency_name); } + if (isPlainObject(rtbBid.ext.dsa)) { + deepSetValue(prBid, 'meta.dsa', rtbBid.ext.dsa); + } } return prBid; @@ -521,6 +526,42 @@ function makeSyncInfo(bidderRequest) { } } +/** + * Initialize DSA request + * @param fpd {Object} + */ +function makeDSARequest(fpd) { + /** + * @type {DSARequest} + */ + const pubDsa = fpd?.regs?.ext?.dsa; + if (!isPlainObject(pubDsa)) { + return; + } + const dsaObj = {}; + ['dsarequired', 'pubrender', 'datatopub'].forEach((dsaKey) => { + if (isNumber(pubDsa[dsaKey])) { + dsaObj[dsaKey] = pubDsa[dsaKey]; + } + }); + if (isArray(pubDsa.transparency) && pubDsa.transparency.every((v) => isPlainObject(v))) { + const tpData = []; + pubDsa.transparency.forEach((tpObj) => { + if (isStr(tpObj.domain) && tpObj.domain !== '' && isArray(tpObj.dsaparams) && tpObj.dsaparams.every((v) => isNumber(v))) { + tpData.push(tpObj); + } + }); + if (tpData.length > 0) { + dsaObj.transparency = tpData; + } + } + if (!isEmpty(dsaObj)) { + let res = {}; + deepSetValue(res, 'regs.ext.dsa', dsaObj); + return res; + } +} + /** * Builds complete rtb request * @param imps {Object} Collection of rtb impressions @@ -537,7 +578,8 @@ function buildRtbRequest(imps, bidderRequest, schain) { makeSiteOrApp(bidderRequest, fpd), makeUser(bidderRequest, fpd), makeRegulations(bidderRequest), - makeSyncInfo(bidderRequest) + makeSyncInfo(bidderRequest), + makeDSARequest(fpd) ); if (schain) { deepSetValue(req, 'source.ext.schain', schain); diff --git a/test/spec/modules/adkernelBidAdapter_spec.js b/test/spec/modules/adkernelBidAdapter_spec.js index 00679447a95..7688af8b615 100644 --- a/test/spec/modules/adkernelBidAdapter_spec.js +++ b/test/spec/modules/adkernelBidAdapter_spec.js @@ -4,327 +4,188 @@ import * as utils from 'src/utils'; import { NATIVE, BANNER, VIDEO } from 'src/mediaTypes'; import { config } from 'src/config'; import { parseDomain } from '../../../src/refererDetection.js'; - -describe('Adkernel adapter', function () { - const bid1_zone1 = { - bidder: 'adkernel', - params: { zoneId: 1, host: 'rtb.adkernel.com' }, - adUnitCode: 'ad-unit-1', - bidId: 'Bid_01', - bidderRequestId: 'req-001', - auctionId: 'auc-001', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 200]], - pos: 1 - } - }, - ortb2Imp: { - battr: [6, 7, 9], - pos: 2 - } - }; const bid2_zone2 = { - bidder: 'adkernel', - params: { zoneId: 2, host: 'rtb.adkernel.com' }, - adUnitCode: 'ad-unit-2', - bidId: 'Bid_02', - bidderRequestId: 'req-001', - auctionId: 'auc-001', - mediaTypes: { - banner: { - sizes: [[728, 90]] - } - }, - userIdAsEids: [ - { - source: 'crwdcntrl.net', - uids: [ - { atype: 1, id: '97d09fbba28542b7acbb6317c9534945a702b74c5993c352f332cfe83f40cdd9' } - ] - } - ] - }; const bid3_host2 = { - bidder: 'adkernel', - params: { zoneId: 1, host: 'rtb-private.adkernel.com' }, - adUnitCode: 'ad-unit-2', - bidId: 'Bid_02', - bidderRequestId: 'req-001', - auctionId: 'auc-001', - mediaTypes: { - banner: { - sizes: [[728, 90]] - } - } - }; const bid_without_zone = { - bidder: 'adkernel', - params: { host: 'rtb-private.adkernel.com' }, - adUnitCode: 'ad-unit-1', - bidId: 'Bid_W', - bidderRequestId: 'req-002', - auctionId: 'auc-002', - mediaTypes: { - banner: { - sizes: [[728, 90]] - } +import { mergeDeep } from "src/utils"; + +const UNIT_BANNER = { + bidder: 'adkernel', + params: { zoneId: 1, host: 'rtb.adkernel.com' }, + adUnitCode: 'ad-unit-1', + bidId: 'Bid_01', + bidderRequestId: 'req-001', + auctionId: 'auc-001', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 200]], + pos: 1 } - }; const bid_without_host = { - bidder: 'adkernel', - params: { zoneId: 1 }, - adUnitCode: 'ad-unit-1', - bidId: 'Bid_W', - bidderRequestId: 'req-002', - auctionId: 'auc-002', - mediaTypes: { - banner: { - sizes: [[728, 90]] - } + } +}; +const UNIT_VIDEO = { + bidder: 'adkernel', + transactionId: 'transaction-id', + bidId: 'Bid_01', + bidderRequestId: 'req-001', + auctionId: 'auc-001', + params: { zoneId: 1, host: 'rtb.adkernel.com' }, + mediaTypes: { + video: { + context: 'instream', + playerSize: [[640, 480]], + api: [1, 2], + placement: 1, + plcmt: 1, + skip: 1, + pos: 1 } - }; const bid_with_wrong_zoneId = { - bidder: 'adkernel', - params: { zoneId: 'wrong id', host: 'rtb.adkernel.com' }, - adUnitCode: 'ad-unit-2', - bidId: 'Bid_02', - bidderRequestId: 'req-002', - auctionId: 'auc-002', - mediaTypes: { - banner: { - sizes: [[728, 90]] + }, + adUnitCode: 'ad-unit-1' +}; +const UNIT_MULTIFORMAT = merge(UNIT_BANNER, UNIT_VIDEO); +const UNIT_NATIVE = { + bidder: 'adkernel', + params: { zoneId: 1, host: 'rtb.adkernel.com' }, + mediaTypes: { + native: { + title: { + required: true, + len: 80 + }, + body: { + required: true + }, + body2: { + required: true + }, + icon: { + required: true, + aspect_ratios: [{ min_width: 50, min_height: 50 }] + }, + image: { + required: true, + sizes: [300, 200] + }, + clickUrl: { + required: true + }, + rating: { + required: false + }, + price: { + required: false + }, + privacyLink: { + required: false + }, + cta: { + required: false + }, + sponsoredBy: { + required: false + }, + displayUrl: { + required: false } } - }; const bid_video = { - bidder: 'adkernel', - transactionId: '866394b8-5d37-4d49-803e-f1bdb595f73e', - bidId: 'Bid_Video', - bidderRequestId: '18b2a61ea5d9a7', - auctionId: 'de45acf1-9109-4e52-8013-f2b7cf5f6766', - params: { - zoneId: 1, - host: 'rtb.adkernel.com', - }, - mediaTypes: { - video: { - context: 'instream', - playerSize: [[640, 480]], - api: [1, 2], - placement: 1, - plcmt: 1, - skip: 1, - pos: 1 - } - }, - adUnitCode: 'ad-unit-1' - }; const bid_multiformat = { - bidder: 'adkernel', - params: { zoneId: 1, host: 'rtb.adkernel.com' }, - mediaTypes: { - banner: { sizes: [[300, 250], [300, 200]] }, - video: { context: 'instream', playerSize: [[640, 480]] } - }, - adUnitCode: 'ad-unit-1', - transactionId: 'f82c64b8-c602-42a4-9791-4a268f6559ed', - bidId: 'Bid_01', - bidderRequestId: 'req-001', - auctionId: 'auc-001' - }; - const bid_native = { - bidder: 'adkernel', - params: { zoneId: 1, host: 'rtb.adkernel.com' }, - mediaTypes: { - native: { - title: { - required: true, - len: 80 - }, - body: { - required: true - }, - body2: { - required: true - }, - icon: { - required: true, - aspect_ratios: [{ min_width: 50, min_height: 50 }] - }, - image: { - required: true, - sizes: [300, 200] - }, - clickUrl: { - required: true - }, - rating: { - required: false - }, - price: { - required: false - }, - privacyLink: { - required: false - }, - cta: { - required: false - }, - sponsoredBy: { - required: false - }, - displayUrl: { - required: false - } + }, + nativeOrtbRequest: { + ver: '1.2', + assets: [ + { + id: 0, required: 1, title: { len: 80 } + }, { id: 1, required: 1, data: { type: 2 } }, + { + id: 2, required: 1, data: { type: 10 } + }, { + id: 3, required: 1, img: { type: 1, wmin: 50, hmin: 50 } + }, { + id: 4, required: 1, img: { type: 3, w: 300, h: 200 } + }, { + id: 5, required: 0, data: { type: 3 } + }, { + id: 6, required: 0, data: { type: 6 } + }, { + id: 7, required: 0, data: { type: 12 } + }, { + id: 8, required: 0, data: { type: 1 } + }, { + id: 9, required: 0, data: { type: 11 } } - }, - nativeOrtbRequest: { - ver: '1.2', + ], + privacy: 1 + }, + adUnitCode: 'ad-unit-1', + transactionId: 'transaction-id', + bidId: 'Bid_01', + bidderRequestId: 'req-001', + auctionId: 'auc-001' +}; +const BID_BANNER = { + id: '1', + impid: 'Bid_01', + crid: '100_001', + price: 3.01, + nurl: 'https://rtb.com/win?i=ZjKoPYSFI3Y_0', + adm: '', + w: 300, + h: 250, + dealid: 'deal', + mtype: 1 +}; +const BID_VIDEO_NURL = { + id: 'sZSYq5zYMxo_0', + impid: 'Bid_01', + crid: '100_003', + price: 0.00145, + adid: '158801', + nurl: 'https://rtb.com/win?i=sZSYq5zYMxo_0&f=nurl', + cid: '16855', + mtype: 2 +}; +const BID_VIDEO_ADM = { + id: 'sZSYq5zYMxo_0', + impid: 'Bid_01', + crid: '100_003', + price: 0.00145, + adid: '158801', + adm: '', + nurl: 'https://rtb.com/win?i=sZSYq5zYMxo_0', + cid: '16855', + mtype: 2 +}; +const BID_USER_SYNC_ONLY = { + id: 'nobid1', + ext: { + adk_usersync: [{ type: 2, url: 'https://adk.sync.com/sync' }] + } +}; +const BID_NATIVE = { + id: 'someid_01', + impid: 'Bid_01', + price: 2.25, + adid: '4', + adm: JSON.stringify({ + native: { assets: [ - { - id: 0, required: 1, title: { len: 80 } - }, { id: 1, required: 1, data: { type: 2 } }, - { - id: 2, required: 1, data: { type: 10 } - }, { - id: 3, required: 1, img: { type: 1, wmin: 50, hmin: 50 } - }, { - id: 4, required: 1, img: { type: 3, w: 300, h: 200 } - }, { - id: 5, required: 0, data: { type: 3 } - }, { - id: 6, required: 0, data: { type: 6 } - }, { - id: 7, required: 0, data: { type: 12 } - }, { - id: 8, required: 0, data: { type: 1 } - }, { - id: 9, required: 0, data: { type: 11 } - } + { id: 0, title: { text: 'Title' } }, + { id: 3, data: { value: 'Description' } }, + { id: 4, data: { value: 'Additional description' } }, + { id: 1, img: { url: 'http://rtb.com/thumbnail?i=pTuOlf5KHUo_0&imgt=icon', w: 50, h: 50 } }, + { id: 2, img: { url: 'http://rtb.com/thumbnail?i=pTuOlf5KHUo_0', w: 300, h: 200 } }, + { id: 5, data: { value: 'Sponsor.com' } }, + { id: 14, data: { value: 'displayurl.com' } } ], - privacy: 1 - }, - adUnitCode: 'ad-unit-1', - transactionId: 'f82c64b8-c602-42a4-9791-4a268f6559ed', - bidId: 'Bid_01', - bidderRequestId: 'req-001', - auctionId: 'auc-001' - }; - - const bannerBidResponse = { - id: 'bid1', - seatbid: [{ - bid: [{ - id: '1', - impid: 'Bid_01', - crid: '100_001', - price: 3.01, - nurl: 'https://rtb.com/win?i=ZjKoPYSFI3Y_0', - adm: '', - w: 300, - h: 250, - dealid: 'deal', - mtype: 1 - }] - }], - ext: { - adk_usersync: [{ type: 1, url: 'https://adk.sync.com/sync' }] - } - }; const videoBidResponse = { - id: '47ce4badcf7482', - seatbid: [{ - bid: [{ - id: 'sZSYq5zYMxo_0', - impid: 'Bid_Video', - crid: '100_003', - price: 0.00145, - adid: '158801', - nurl: 'https://rtb.com/win?i=sZSYq5zYMxo_0&f=nurl', - cid: '16855', - mtype: 2 - }] - }], - }; const videoBidResponseWithAdm = { - id: '47ce4badcf7482', - seatbid: [{ - bid: [{ - id: 'sZSYq5zYMxo_0', - impid: 'Bid_Video', - crid: '100_003', - price: 0.00145, - adid: '158801', - adm: '', - nurl: 'https://rtb.com/win?i=sZSYq5zYMxo_0', - cid: '16855', - mtype: 2 - }] - }], - }; - const usersyncOnlyResponse = { - id: 'nobid1', - ext: { - adk_usersync: [{ type: 2, url: 'https://adk.sync.com/sync' }] + link: { url: 'http://rtb.com/click?i=pTuOlf5KHUo_0' }, + imptrackers: ['http://rtb.com/win?i=pTuOlf5KHUo_0&f=imp'] } - }; const nativeResponse = { - id: '56fbc713-b737-4651-9050-13376aed9818', - seatbid: [{ - bid: [{ - id: 'someid_01', - impid: 'Bid_01', - price: 2.25, - adid: '4', - adm: JSON.stringify({ - native: { - assets: [ - { id: 0, title: { text: 'Title' } }, - { id: 3, data: { value: 'Description' } }, - { id: 4, data: { value: 'Additional description' } }, - { id: 1, img: { url: 'http://rtb.com/thumbnail?i=pTuOlf5KHUo_0&imgt=icon', w: 50, h: 50 } }, - { id: 2, img: { url: 'http://rtb.com/thumbnail?i=pTuOlf5KHUo_0', w: 300, h: 200 } }, - { id: 5, data: { value: 'Sponsor.com' } }, - { id: 14, data: { value: 'displayurl.com' } } - ], - link: { url: 'http://rtb.com/click?i=pTuOlf5KHUo_0' }, - imptrackers: ['http://rtb.com/win?i=pTuOlf5KHUo_0&f=imp'] - } - }), - adomain: ['displayurl.com'], - cat: ['IAB1-4', 'IAB8-16', 'IAB25-5'], - cid: '1', - crid: '4', - mtype: 4, - ext: { - 'advertiser_id': 777, - 'advertiser_name': 'advertiser', - 'agency_name': 'agency' - } - }] - }], - bidid: 'pTuOlf5KHUo', - cur: 'EUR' - }; - const multiformat_response = { - id: '47ce4badcf7482', - seatbid: [{ - bid: [{ - id: 'sZSYq5zYMxo_0', - impid: 'Bid_01b__mf', - crid: '100_003', - price: 0.00145, - adid: '158801', - adm: '', - nurl: 'https://rtb.com/win?i=sZSYq5zYMxo_0&f=nurl', - cid: '16855', - mtype: 1 - }, { - id: 'sZSYq5zYMxo_1', - impid: 'Bid_01v__mf', - crid: '100_003', - price: 0.25, - adid: '158801', - nurl: 'https://rtb.com/win?i=sZSYq5zYMxo_1&f=nurl', - cid: '16855', - mtype: 2 - }] - }], - bidid: 'pTuOlf5KHUo', - cur: 'USD' - }; + }), + adomain: ['displayurl.com'], + mtype: 4, + cid: '1', + crid: '4' +}; +const DEFAULT_BIDDER_REQUEST = buildBidderRequest(); +describe('Adkernel adapter', function () { var sandbox; beforeEach(function () { sandbox = sinon.createSandbox(); @@ -335,19 +196,21 @@ describe('Adkernel adapter', function () { config.resetConfig(); }); - function buildBidderRequest(url = 'https://example.com/index.html', params = {}) { - return Object.assign({}, params, { refererInfo: { page: url, domain: parseDomain(url), reachedTop: true }, timeout: 3000, bidderCode: 'adkernel' }); - } - const DEFAULT_BIDDER_REQUEST = buildBidderRequest(); - - function buildRequest(bidRequests, bidderRequest = DEFAULT_BIDDER_REQUEST) { - bidderRequest.bids = bidRequests; - const pbRequests = spec.buildRequests(bidRequests, bidderRequest); - const rtbRequests = pbRequests.map(r => JSON.parse(r.data)); - return [pbRequests, rtbRequests]; - } - describe('input parameters validation', function () { + const BASE_ADUNIT = { + bidder: 'adkernel', + params: {}, + adUnitCode: 'ad-unit-1', + bidId: 'Bid_W', + bidderRequestId: 'req-002', + auctionId: 'auc-002', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + } + }; + it('empty request shouldn\'t generate exception', function () { expect(spec.isBidRequestValid({ bidderCode: 'adkernel' @@ -355,19 +218,29 @@ describe('Adkernel adapter', function () { }); it('request without zone shouldn\'t issue a request', function () { - expect(spec.isBidRequestValid(bid_without_zone)).to.be.equal(false); + expect( + spec.isBidRequestValid( + merge(BASE_ADUNIT, { params: { host: 'rtb-private.adkernel.com' } }) + ) + ).to.be.equal(false); }); it('request without host shouldn\'t issue a request', function () { - expect(spec.isBidRequestValid(bid_without_host)).to.be.equal(false); + expect(spec.isBidRequestValid( + merge(BASE_ADUNIT, { params: { zoneId: 1 } }) + )).to.be.equal(false); }); it('empty request shouldn\'t generate exception', function () { - expect(spec.isBidRequestValid(bid_with_wrong_zoneId)).to.be.equal(false); + expect(spec.isBidRequestValid( + merge(BASE_ADUNIT, { params: { zoneId: 'wrong id', host: 'rtb.adkernel.com' } }) + )).to.be.equal(false); }); - it('valid native requests should pass', () => { - expect(spec.isBidRequestValid(bid_native)).to.be.equal(true); + it('valid requests should pass', () => { + expect(spec.isBidRequestValid( + merge(BASE_ADUNIT, { params: { zoneId: 10, host: 'rtb.adkernel.com' } }) + )).to.be.true; }); }); @@ -375,7 +248,14 @@ describe('Adkernel adapter', function () { let bidRequest, bidRequests; before(function () { - [, bidRequests] = buildRequest([bid1_zone1]); + [, bidRequests] = buildRequest([ + merge(UNIT_BANNER, { + ortb2Imp: { + battr: [6, 7, 9], + pos: 2 + } + }) + ]); bidRequest = bidRequests[0]; }); @@ -428,13 +308,13 @@ describe('Adkernel adapter', function () { }); it('shouldn\'t contain gdpr nor ccpa information for default request', function () { - const [, bidRequests] = buildRequest([bid1_zone1]); + const [, bidRequests] = buildRequest([UNIT_BANNER]); expect(bidRequests[0]).to.not.have.property('regs'); expect(bidRequests[0]).to.not.have.property('user'); }); it('should contain gdpr-related information if consent is configured', function () { - const [, bidRequests] = buildRequest([bid1_zone1], + const [, bidRequests] = buildRequest([UNIT_BANNER], buildBidderRequest('https://example.com/index.html', { gdprConsent: { gdprApplies: true, consentString: 'test-consent-string', vendorData: {} }, uspConsent: '1YNN', @@ -452,14 +332,14 @@ describe('Adkernel adapter', function () { it('should contain coppa if configured', function () { config.setConfig({ coppa: true }); - const [, bidRequests] = buildRequest([bid1_zone1]); + const [, bidRequests] = buildRequest([UNIT_BANNER]); const bidRequest = bidRequests[0]; expect(bidRequest).to.have.property('regs'); expect(bidRequest.regs).to.have.property('coppa', 1); }); it('should\'t contain consent string if gdpr isn\'t applied', function () { - const [, bidRequests] = buildRequest([bid1_zone1], buildBidderRequest('https://example.com/index.html', { gdprConsent: { gdprApplies: false } })); + const [, bidRequests] = buildRequest([UNIT_BANNER], buildBidderRequest('https://example.com/index.html', { gdprConsent: { gdprApplies: false } })); const bidRequest = bidRequests[0]; expect(bidRequest).to.have.property('regs'); expect(bidRequest.regs.ext).to.be.eql({ 'gdpr': 0 }); @@ -467,24 +347,30 @@ describe('Adkernel adapter', function () { }); it('should forward default bidder timeout', function() { - const [, bidRequests] = buildRequest([bid1_zone1]); + const [, bidRequests] = buildRequest([UNIT_BANNER]); expect(bidRequests[0]).to.have.property('tmax', 3000); }); it('should set bidfloor if configured', function() { - const bid = Object.assign({}, bid1_zone1); - bid.getFloor = function() { - return { - currency: 'USD', - floor: 0.145 - }; - }; + const bid = merge(UNIT_BANNER, { + getFloor: function() { + return { + currency: 'USD', + floor: 0.145 + }; + } + }); const [, bidRequests] = buildRequest([bid]); expect(bidRequests[0].imp[0]).to.have.property('bidfloor', 0.145); }); it('should forward user ids if available', function() { - const bid = Object.assign({}, bid2_zone2); + const bid = merge(UNIT_BANNER, { + userIdAsEids: [{ + source: 'crwdcntrl.net', + uids: [{ atype: 1, id: '97d09fbba28542b7acbb6317c9534945a702b74c5993c352f332cfe83f40cdd9' }] + }] + }); const [, bidRequests] = buildRequest([bid]); expect(bidRequests[0]).to.have.property('user'); expect(bidRequests[0].user).to.have.property('ext'); @@ -498,7 +384,7 @@ describe('Adkernel adapter', function () { describe('video request building', function () { let bidRequests; before(function () { - [, bidRequests] = buildRequest([bid_video]); + [, bidRequests] = buildRequest([UNIT_VIDEO]); }); it('should have video object', function () { @@ -528,7 +414,7 @@ describe('Adkernel adapter', function () { describe('multiformat request building', function () { let pbRequests, bidRequests; before(() => { - [pbRequests, bidRequests] = buildRequest([bid_multiformat]); + [pbRequests, bidRequests] = buildRequest([UNIT_MULTIFORMAT]); }); it('should contain single request', function () { expect(bidRequests).to.have.length(1); @@ -543,7 +429,13 @@ describe('Adkernel adapter', function () { expect(bidRequests[0].imp[1].id).to.be.not.eql(bidRequests[0].imp[0].id); }); it('should collect ads back to same requestId', function() { - const bids = spec.interpretResponse({ body: multiformat_response }, pbRequests[0]); + const bids = spec.interpretResponse({ + body: createRtbBidResponse( + [ + merge(BID_BANNER, { impid: 'Bid_01b__mf' }), + merge(BID_VIDEO_NURL, { impid: 'Bid_01v__mf' }) + ]) + }, pbRequests[0]); expect(bids).to.have.length(2); expect(bids[0].requestId).to.be.eql('Bid_01'); expect(bids[0].mediaType).to.be.eql('banner'); @@ -553,18 +445,22 @@ describe('Adkernel adapter', function () { }); describe('requests routing', function () { + // let banner_unit_otherzone = extend(banner_unit, {params: extend(banner_unit.params, {zone: 2})}); + it('should issue a request for each host', function () { - const [pbRequests] = buildRequest([bid1_zone1, bid3_host2]); + const banner_unit_otherhost = merge(UNIT_BANNER, { params: { host: 'otherhost.com' } }); + const [pbRequests] = buildRequest([UNIT_BANNER, banner_unit_otherhost]); expect(pbRequests).to.have.length(2); - expect(pbRequests[0].url).to.have.string(`https://${bid1_zone1.params.host}/`); - expect(pbRequests[1].url).to.have.string(`https://${bid3_host2.params.host}/`); + expect(pbRequests[0].url).to.have.string(`https://${UNIT_BANNER.params.host}/`); + expect(pbRequests[1].url).to.have.string(`https://${banner_unit_otherhost.params.host}/`); }); it('should issue a request for each zone', function () { - const [pbRequests] = buildRequest([bid1_zone1, bid2_zone2]); + const banner_unit_otherzone = merge(UNIT_BANNER, { params: { zoneId: 2 } }); + const [pbRequests] = buildRequest([UNIT_BANNER, banner_unit_otherzone]); expect(pbRequests).to.have.length(2); - expect(pbRequests[0].url).to.include(`zone=${bid1_zone1.params.zoneId}`); - expect(pbRequests[1].url).to.include(`zone=${bid2_zone2.params.zoneId}`); + expect(pbRequests[0].url).to.include(`zone=${UNIT_BANNER.params.zoneId}`); + expect(pbRequests[1].url).to.include(`zone=${banner_unit_otherzone.params.zoneId}`); }); }); @@ -581,7 +477,7 @@ describe('Adkernel adapter', function () { } } }); - const [, bidRequests] = buildRequest([bid1_zone1]); + const [, bidRequests] = buildRequest([UNIT_BANNER]); expect(bidRequests).to.have.length(1); expect(bidRequests[0]).to.not.have.property('ext'); }); @@ -598,7 +494,7 @@ describe('Adkernel adapter', function () { } } }); - const [, bidRequests] = buildRequest([bid1_zone1]); + const [, bidRequests] = buildRequest([UNIT_BANNER]); expect(bidRequests).to.have.length(1); expect(bidRequests[0].ext).to.have.property('adk_usersync', 1); }); @@ -619,7 +515,7 @@ describe('Adkernel adapter', function () { } } }); - const [, bidRequests] = buildRequest([bid1_zone1]); + const [, bidRequests] = buildRequest([UNIT_BANNER]); expect(bidRequests).to.have.length(1); expect(bidRequests[0].ext).to.have.property('adk_usersync', 2); }); @@ -640,7 +536,7 @@ describe('Adkernel adapter', function () { } } }); - const [, bidRequests] = buildRequest([bid1_zone1]); + const [, bidRequests] = buildRequest([UNIT_BANNER]); expect(bidRequests).to.have.length(1); expect(bidRequests[0]).to.not.have.property('ext'); }); @@ -648,8 +544,8 @@ describe('Adkernel adapter', function () { describe('responses processing', function () { it('should return fully-initialized banner bid-response', function () { - const [pbRequests] = buildRequest([bid1_zone1]); - const resp = spec.interpretResponse({ body: bannerBidResponse }, pbRequests[0])[0]; + const [pbRequests] = buildRequest([UNIT_BANNER]); + const resp = spec.interpretResponse({ body: createRtbBidResponse(BID_BANNER) }, pbRequests[0])[0]; expect(resp).to.have.property('requestId', 'Bid_01'); expect(resp).to.have.property('cpm', 3.01); expect(resp).to.have.property('width', 300); @@ -665,9 +561,9 @@ describe('Adkernel adapter', function () { }); it('should return fully-initialized video bid-response', function () { - const [pbRequests] = buildRequest([bid_video]); - const resp = spec.interpretResponse({ body: videoBidResponse }, pbRequests[0])[0]; - expect(resp).to.have.property('requestId', 'Bid_Video'); + const [pbRequests] = buildRequest([UNIT_VIDEO]); + const resp = spec.interpretResponse({ body: createRtbBidResponse(BID_VIDEO_NURL) }, pbRequests[0])[0]; + expect(resp).to.have.property('requestId', 'Bid_01'); expect(resp.mediaType).to.equal(VIDEO); expect(resp.cpm).to.equal(0.00145); expect(resp.vastUrl).to.equal('https://rtb.com/win?i=sZSYq5zYMxo_0&f=nurl'); @@ -676,9 +572,9 @@ describe('Adkernel adapter', function () { }); it('should support vast xml in adm', function () { - const [pbRequests] = buildRequest([bid_video]); - const resp = spec.interpretResponse({ body: videoBidResponseWithAdm }, pbRequests[0])[0]; - expect(resp).to.have.property('requestId', 'Bid_Video'); + const [pbRequests] = buildRequest([UNIT_VIDEO]); + const resp = spec.interpretResponse({ body: createRtbBidResponse(BID_VIDEO_ADM) }, pbRequests[0])[0]; + expect(resp).to.have.property('requestId', 'Bid_01'); expect(resp.mediaType).to.equal(VIDEO); expect(resp.cpm).to.equal(0.00145); expect(resp.vastXml).to.equal(''); @@ -687,29 +583,54 @@ describe('Adkernel adapter', function () { expect(resp.height).to.equal(480); }); + it('should fill ad meta', () => { + const [pbRequests] = buildRequest([UNIT_BANNER]); + const resp = spec.interpretResponse({ + body: createRtbBidResponse(BID_BANNER, { + adomain: ['displayurl.com'], + cat: ['IAB1-4', 'IAB8-16', 'IAB25-5'], + ext: { + advertiser_id: 777, + advertiser_name: 'advertiser', + agency_name: 'agency', + } + }) + }, pbRequests[0])[0]; + expect(resp.meta.advertiserId).to.be.eql(777); + expect(resp.meta.advertiserName).to.be.eql('advertiser'); + expect(resp.meta.agencyName).to.be.eql('agency'); + expect(resp.meta.advertiserDomains).to.be.eql(['displayurl.com']); + expect(resp.meta.secondaryCatIds).to.be.eql(['IAB1-4', 'IAB8-16', 'IAB25-5']); + }); + it('should add nurl as pixel for banner response', function () { - const [pbRequests] = buildRequest([bid1_zone1]); - const resp = spec.interpretResponse({ body: bannerBidResponse }, pbRequests[0])[0]; - const expectedNurl = bannerBidResponse.seatbid[0].bid[0].nurl + '&px=1'; + const [pbRequests] = buildRequest([UNIT_BANNER]); + const resp = spec.interpretResponse({ body: createRtbBidResponse(BID_BANNER) }, pbRequests[0])[0]; + const expectedNurl = BID_BANNER.nurl + '&px=1'; expect(resp.ad).to.have.string(expectedNurl); }); it('should handle bidresponse with user-sync only', function () { - const [pbRequests] = buildRequest([bid1_zone1]); - const resp = spec.interpretResponse({ body: usersyncOnlyResponse }, pbRequests[0]); + const [pbRequests] = buildRequest([UNIT_BANNER]); + const resp = spec.interpretResponse({ body: BID_USER_SYNC_ONLY }, pbRequests[0]); expect(resp).to.have.length(0); }); it('should perform usersync', function () { + let bidResp = createRtbBidResponse(BID_BANNER, undefined, { + ext: { + adk_usersync: [{ type: 1, url: 'https://adk.sync.com/sync' }] + } + }); let syncs = spec.getUserSyncs({ iframeEnabled: true, pixelEnabled: true }, []); expect(syncs).to.have.length(0); - syncs = spec.getUserSyncs({ iframeEnabled: false, pixelEnabled: false }, [{ body: bannerBidResponse }]); + syncs = spec.getUserSyncs({ iframeEnabled: false, pixelEnabled: false }, [{ body: bidResp }]); expect(syncs).to.have.length(0); - syncs = spec.getUserSyncs({ iframeEnabled: true, pixelEnabled: true }, [{ body: bannerBidResponse }]); + syncs = spec.getUserSyncs({ iframeEnabled: true, pixelEnabled: true }, [{ body: bidResp }]); expect(syncs).to.have.length(1); expect(syncs[0]).to.have.property('type', 'iframe'); expect(syncs[0]).to.have.property('url', 'https://adk.sync.com/sync'); - syncs = spec.getUserSyncs({ iframeEnabled: false, pixelEnabled: true }, [{ body: usersyncOnlyResponse }]); + syncs = spec.getUserSyncs({ iframeEnabled: false, pixelEnabled: true }, [{ body: BID_USER_SYNC_ONLY }]); expect(syncs).to.have.length(1); expect(syncs[0]).to.have.property('type', 'image'); expect(syncs[0]).to.have.property('url', 'https://adk.sync.com/sync'); @@ -725,7 +646,7 @@ describe('Adkernel adapter', function () { describe('native support', () => { let bidRequests; before(function () { - [, bidRequests] = buildRequest([bid_native]); + [, bidRequests] = buildRequest([UNIT_NATIVE]); }); it('native request building', () => { @@ -748,17 +669,12 @@ describe('Adkernel adapter', function () { }); it('native response processing', () => { - const [pbRequests] = buildRequest([bid_native]); - const resp = spec.interpretResponse({ body: nativeResponse }, pbRequests[0])[0]; + const [pbRequests] = buildRequest([UNIT_NATIVE]); + const resp = spec.interpretResponse({ body: createRtbBidResponse(BID_NATIVE, undefined, { cur: 'EUR' }) }, pbRequests[0])[0]; expect(resp).to.have.property('requestId', 'Bid_01'); expect(resp).to.have.property('cpm', 2.25); expect(resp).to.have.property('currency', 'EUR'); expect(resp).to.have.property('meta'); - expect(resp.meta.advertiserId).to.be.eql(777); - expect(resp.meta.advertiserName).to.be.eql('advertiser'); - expect(resp.meta.agencyName).to.be.eql('agency'); - expect(resp.meta.advertiserDomains).to.be.eql(['displayurl.com']); - expect(resp.meta.secondaryCatIds).to.be.eql(['IAB1-4', 'IAB8-16', 'IAB25-5']); expect(resp).to.have.property('mediaType', NATIVE); expect(resp).to.have.property('native'); expect(resp.native).to.have.property('ortb'); @@ -787,10 +703,113 @@ describe('Adkernel adapter', function () { utils.triggerPixel.restore(); }); it('should trigger pixel for nurl', () => { - const [pbRequests] = buildRequest([bid_video]); - const bid = spec.interpretResponse({ body: videoBidResponseWithAdm }, pbRequests[0])[0]; + const [pbRequests] = buildRequest([UNIT_VIDEO]); + const bid = spec.interpretResponse({ body: createRtbBidResponse(BID_VIDEO_ADM) }, pbRequests[0])[0]; spec.onBidWon(bid); expect(utils.triggerPixel.callCount).to.equal(1); }); }); + + describe('DSA feature', () => { + it('should fill dsa pub info and parse advertiser info', () => { + const bid = merge(UNIT_BANNER); + const [pbRequests, rtbRequest] = buildRequest([bid], + buildBidderRequest('https://example.com/index.html', + { + ortb2: { + regs: { + ext: { + dsa: { + dsarequired: 1, + pubrender: 0, + datatopub: 1, + transparency: [ + { domain: 'good-domain', dsaparams: [1, 2] }, + { domain: 'bad-setup', dsaparams: ['1', 3] } + ] + } + } + } + } + })); + let dsa = rtbRequest[0].regs.ext.dsa; + expect(dsa).to.be.an('object'); + expect(dsa.dsarequired).to.be.eql(1); + expect(dsa.pubrender).to.be.eql(0); + expect(dsa.datatopub).to.be.eql(1); + expect(dsa.transparency).to.be.an('array').with.lengthOf(1); + expect(dsa.transparency[0].domain).to.be.eql('good-domain'); + expect(dsa.transparency[0].dsaparams).to.be.eql([1, 2]); + + const resp = spec.interpretResponse({ + body: createRtbBidResponse(BID_BANNER, { + ext: { + dsa: { + behalf: 'Advertiser', + paid: 'Advertiser', + adrender: 1, + transparency: [ + { domain: 'dsp1domain.com', dsaparams: [1, 2] } + ] + } + } + }) + }, pbRequests[0])[0]; + + dsa = resp.meta.dsa; + expect(dsa).to.be.an('object'); + expect(dsa.behalf).to.be.eql('Advertiser'); + expect(dsa.paid).to.be.eql('Advertiser'); + expect(dsa.adrender).to.be.eql(1); + expect(dsa.transparency).to.be.an('array').with.lengthOf(1); + expect(dsa.transparency[0]).to.be.an('object'); + expect(dsa.transparency[0].domain).to.be.eql('dsp1domain.com'); + expect(dsa.transparency[0].dsaparams).to.be.eql([1, 2]); + }); + }); }); + +function merge(obj, ext = {}) { + return mergeDeep({}, obj, ext); +} + +function buildBidderRequest(url = 'https://example.com/index.html', params = {}) { + return merge(params, { + refererInfo: { page: url, domain: parseDomain(url), reachedTop: true }, + timeout: 3000, + bidderCode: 'adkernel' + }); +} + +function buildRequest(adUnits, bidderRequest = DEFAULT_BIDDER_REQUEST) { + bidderRequest = { + ...bidderRequest, + bids: adUnits + }; + const pbRequests = spec.buildRequests(adUnits, bidderRequest); + const rtbRequests = pbRequests.map(r => JSON.parse(r.data)); + return [pbRequests, rtbRequests]; +} + +function createRtbBidResponse(baseBid, extBid = undefined, respExt = {}) { + if (!Array.isArray(baseBid)) { + baseBid = [baseBid]; + } + if (extBid === undefined) { + extBid = Array(baseBid.length).fill({}); + } else if (!Array.isArray(extBid)) { + extBid = [extBid]; + } + if (baseBid.length !== extBid.length) { + throw new Error('baseBid and extBid arrays should have the same length'); + } + const bids = baseBid.map((bid, index) => merge(bid, extBid[index])); + return Object.assign({ + id: 'xxx-xxx-xxx-xxx', + seatbid: [{ + bid: bids + }], + bidid: 'some-bid-id', + cur: 'USD' + }, respExt); +}