mk9-prebid
Version:
Header Bidding Management Library
589 lines (508 loc) • 22.1 kB
JavaScript
import { expect } from 'chai';
import { spec } from 'modules/yieldmoBidAdapter.js';
import * as utils from 'src/utils.js';
describe('YieldmoAdapter', function () {
const BANNER_ENDPOINT = 'https://ads.yieldmo.com/exchange/prebid';
const VIDEO_ENDPOINT = 'https://ads.yieldmo.com/exchange/prebidvideo';
const mockBannerBid = (rootParams = {}, params = {}) => ({
bidder: 'yieldmo',
params: {
bidFloor: 0.1,
...params,
},
adUnitCode: 'adunit-code',
mediaTypes: {
banner: {
sizes: [
[300, 250],
[300, 600],
],
},
},
bidId: '30b31c1838de1e',
bidderRequestId: '22edbae2733bf6',
auctionId: '1d1a030790a475',
crumbs: {
pubcid: 'c604130c-0144-4b63-9bf2-c2bd8c8d86da',
},
userId: {
tdid: '8d146286-91d4-4958-aff4-7e489dd1abd6'
},
...rootParams
});
const mockVideoBid = (rootParams = {}, params = {}, videoParams = {}) => ({
bidder: 'yieldmo',
adUnitCode: 'adunit-code-video',
bidId: '321video123',
mediaTypes: {
video: {
playerSize: [640, 480],
context: 'instream',
mimes: ['video/mp4']
},
},
params: {
placementId: '123',
...params,
video: {
placement: 1,
maxduration: 30,
startdelay: 10,
protocols: [2, 3],
api: [2, 3],
skipppable: true,
playbackmethod: [1, 2],
...videoParams
}
},
...rootParams
});
const mockBidderRequest = (params = {}, bids = [mockBannerBid()]) => ({
bidderCode: 'yieldmo',
auctionId: 'e3a336ad-2761-4a1c-b421-ecc7c5294a34',
bidderRequestId: '14c4ede8c693f',
bids,
auctionStart: 1520001292880,
timeout: 3000,
start: 1520001292884,
doneCbCallCount: 0,
refererInfo: {
numIframes: 1,
reachedTop: true,
referer: 'yieldmo.com',
},
...params
});
const mockGetFloor = floor => ({getFloor: () => ({ currency: 'USD', floor })});
describe('isBidRequestValid', function () {
describe('Banner:', function () {
it('should return true when necessary information is found', function () {
expect(spec.isBidRequestValid(mockBannerBid())).to.be.true;
});
it('should return false when necessary information is not found', function () {
// empty bid
expect(spec.isBidRequestValid({})).to.be.false;
// empty bidId
expect(spec.isBidRequestValid(mockBannerBid({bidId: ''}))).to.be.false;
// empty adUnitCode
expect(spec.isBidRequestValid(mockBannerBid({adUnitCode: ''}))).to.be.false;
let invalidBid = mockBannerBid();
delete invalidBid.mediaTypes.banner;
expect(spec.isBidRequestValid(invalidBid)).to.be.false;
});
});
describe('Instream video:', function () {
const getVideoBidWithoutParam = (key, paramToRemove) => {
let bid = mockVideoBid();
delete utils.deepAccess(bid, key)[paramToRemove];
return bid;
}
it('should return true when necessary information is found', function () {
expect(spec.isBidRequestValid(mockVideoBid())).to.be.true;
});
it('should return false when necessary information is not found', function () {
// empty bidId
expect(spec.isBidRequestValid(mockVideoBid({bidId: ''}))).to.be.false;
// empty adUnitCode
expect(spec.isBidRequestValid(mockVideoBid({adUnitCode: ''}))).to.be.false;
});
it('should return false when required mediaTypes.video.* param is not found', function () {
const getBidAndExclude = paramToRemove => getVideoBidWithoutParam('mediaTypes.video', paramToRemove);
expect(spec.isBidRequestValid(getBidAndExclude('playerSize'))).to.be.false;
expect(spec.isBidRequestValid(getBidAndExclude('mimes'))).to.be.false;
});
it('should return false when required bid.params.* is not found', function () {
const getBidAndExclude = paramToRemove => getVideoBidWithoutParam('params', paramToRemove);
expect(spec.isBidRequestValid(getBidAndExclude('placementId'))).to.be.false;
expect(spec.isBidRequestValid(getBidAndExclude('video'))).to.be.false;
});
it('should return false when required bid.params.video.* is not found', function () {
const getBidAndExclude = paramToRemove => getVideoBidWithoutParam('params.video', paramToRemove);
expect(spec.isBidRequestValid(getBidAndExclude('placement'))).to.be.false;
expect(spec.isBidRequestValid(getBidAndExclude('maxduration'))).to.be.false;
expect(spec.isBidRequestValid(getBidAndExclude('startdelay'))).to.be.false;
expect(spec.isBidRequestValid(getBidAndExclude('protocols'))).to.be.false;
expect(spec.isBidRequestValid(getBidAndExclude('api'))).to.be.false;
});
});
});
describe('buildRequests', function () {
const build = (bidRequests, bidderReq = mockBidderRequest()) => spec.buildRequests(bidRequests, bidderReq);
const buildAndGetPlacementInfo = (bidRequests, index = 0, bidderReq = mockBidderRequest()) =>
utils.deepAccess(build(bidRequests, bidderReq), `${index}.data.p`);
const buildAndGetData = (bidRequests, index = 0, bidderReq = mockBidderRequest()) =>
utils.deepAccess(build(bidRequests, bidderReq), `${index}.data`) || {};
describe('Banner:', function () {
it('should attempt to send banner bid requests to the endpoint via GET', function () {
const requests = build([mockBannerBid()]);
expect(requests.length).to.equal(1);
expect(requests[0].method).to.equal('GET');
expect(requests[0].url).to.be.equal(BANNER_ENDPOINT);
});
it('should not blow up if crumbs is undefined', function () {
expect(function () {
build([mockBannerBid({crumbs: undefined})]);
}).not.to.throw();
});
it('should place bid information into the p parameter of data', function () {
let bidArray = [mockBannerBid()];
expect(buildAndGetPlacementInfo(bidArray)).to.equal(
'[{"placement_id":"adunit-code","callback_id":"30b31c1838de1e","sizes":[[300,250],[300,600]],"bidFloor":0.1}]'
);
// multiple placements
bidArray.push(mockBannerBid(
{adUnitCode: 'adunit-2', bidId: '123a', bidderRequestId: '321', auctionId: '222'}, {bidFloor: 0.2}));
expect(buildAndGetPlacementInfo(bidArray)).to.equal(
'[{"placement_id":"adunit-code","callback_id":"30b31c1838de1e","sizes":[[300,250],[300,600]],"bidFloor":0.1},' +
'{"placement_id":"adunit-2","callback_id":"123a","sizes":[[300,250],[300,600]],"bidFloor":0.2}]'
);
});
it('should add placement id if given', function () {
let bidArray = [mockBannerBid({}, {placementId: 'ym_1293871298'})];
let placementInfo = buildAndGetPlacementInfo(bidArray);
expect(placementInfo).to.include('"ym_placement_id":"ym_1293871298"');
expect(placementInfo).not.to.include('"ym_placement_id":"ym_0987654321"');
bidArray.push(mockBannerBid({}, {placementId: 'ym_0987654321'}));
placementInfo = buildAndGetPlacementInfo(bidArray);
expect(placementInfo).to.include('"ym_placement_id":"ym_1293871298"');
expect(placementInfo).to.include('"ym_placement_id":"ym_0987654321"');
});
it('should add additional information to data parameter of request', function () {
const data = buildAndGetData([mockBannerBid()]);
expect(data.hasOwnProperty('page_url')).to.be.true;
expect(data.hasOwnProperty('bust')).to.be.true;
expect(data.hasOwnProperty('pr')).to.be.true;
expect(data.hasOwnProperty('scrd')).to.be.true;
expect(data.dnt).to.be.false;
expect(data.hasOwnProperty('description')).to.be.true;
expect(data.hasOwnProperty('title')).to.be.true;
expect(data.hasOwnProperty('h')).to.be.true;
expect(data.hasOwnProperty('w')).to.be.true;
expect(data.hasOwnProperty('pubcid')).to.be.true;
expect(data.userConsent).to.equal('{"gdprApplies":"","cmp":""}');
expect(data.us_privacy).to.equal('');
});
it('should add pubcid as parameter of request', function () {
const pubcid = 'c604130c-0144-4b63-9bf2-c2bd8c8d86da2';
const pubcidBid = mockBannerBid({crumbs: undefined, userId: {pubcid}});
expect(buildAndGetData([pubcidBid]).pubcid).to.deep.equal(pubcid);
});
it('should add unified id as parameter of request', function () {
const unifiedIdBid = mockBannerBid({crumbs: undefined});
expect(buildAndGetData([unifiedIdBid]).tdid).to.deep.equal(mockBannerBid().userId.tdid);
});
it('should add CRITEO RTUS id as parameter of request', function () {
const criteoId = 'aff4';
const criteoIdBid = mockBannerBid({crumbs: undefined, userId: { criteoId }});
expect(buildAndGetData([criteoIdBid]).cri_prebid).to.deep.equal(criteoId);
});
it('should add gdpr information to request if available', () => {
const gdprConsent = {
consentString: 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==',
vendorData: {blerp: 1},
gdprApplies: true,
};
const data = buildAndGetData([mockBannerBid()], 0, mockBidderRequest({gdprConsent}));
expect(data.userConsent).equal(
JSON.stringify({
gdprApplies: true,
cmp: 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==',
})
);
});
it('should add ccpa information to request if available', () => {
const uspConsent = '1YNY';
const data = buildAndGetData([mockBannerBid()], 0, mockBidderRequest({uspConsent}));
expect(data.us_privacy).equal(uspConsent);
});
it('should add schain if it is in the bidRequest', () => {
const schain = {
ver: '1.0',
complete: 1,
nodes: [{asi: 'indirectseller.com', sid: '00001', hp: 1}],
};
const data = buildAndGetData([mockBannerBid({schain})]);
expect(data.schain).equal(JSON.stringify(schain));
});
it('should process floors module if available', function () {
const placementsData = JSON.parse(buildAndGetPlacementInfo([
mockBannerBid({...mockGetFloor(3.99)}),
mockBannerBid({...mockGetFloor(1.23)}, { bidFloor: 1.1 }),
]));
expect(placementsData[0].bidFloor).to.equal(3.99);
expect(placementsData[1].bidFloor).to.equal(1.23);
});
it('should use bidFloor if no floors module is available', function() {
const placementsData = JSON.parse(buildAndGetPlacementInfo([
mockBannerBid({}, { bidFloor: 1.2 }),
mockBannerBid({}, { bidFloor: 0.7 }),
]));
expect(placementsData[0].bidFloor).to.equal(1.2);
expect(placementsData[1].bidFloor).to.equal(0.7);
});
it('should not write 0 bidfloor value by default', function() {
const placementsData = JSON.parse(buildAndGetPlacementInfo([mockBannerBid()]));
expect(placementsData[0].bidfloor).to.undefined;
});
it('should not exceed max url length', () => {
const longString = new Array(8000).join('a');
const localWindow = utils.getWindowTop();
const originalTitle = localWindow.document.title;
localWindow.document.title = longString;
const request = spec.buildRequests(
[mockBannerBid()],
mockBidderRequest({
refererInfo: {
numIframes: 1,
reachedTop: true,
referer: longString,
},
})
)[0];
const url = `${request.url}?${utils.parseQueryStringParameters(request.data)}`;
expect(url.length).equal(8000);
localWindow.document.title = originalTitle;
});
it('should only shortcut properties rather then completely remove it', () => {
const longString = new Array(7516).join('a');
const localWindow = utils.getWindowTop();
const originalTitle = localWindow.document.title;
localWindow.document.title = `testtitle${longString}`;
const request = spec.buildRequests(
[mockBannerBid()],
mockBidderRequest({
refererInfo: {
numIframes: 1,
reachedTop: true,
referer: longString,
},
})
)[0];
expect(request.data.title.length).greaterThan(0);
localWindow.document.title = originalTitle;
});
it('should add ats_envelope to banner bid request', function() {
const envelope = 'test_envelope';
const requests = build([mockBannerBid({}, { lr_env: envelope })]);
expect(requests[0].data.ats_envelope).to.equal(envelope);
});
it('should add gpid to the banner bid request', function () {
let bidArray = [mockBannerBid({
ortb2Imp: {
ext: { data: { pbadslot: '/6355419/Travel/Europe/France/Paris' } },
}
})];
let placementInfo = buildAndGetPlacementInfo(bidArray);
expect(placementInfo).to.include('"gpid":"/6355419/Travel/Europe/France/Paris"');
});
});
describe('Instream video:', function () {
let videoBid;
const buildVideoBidAndGetVideoParam = () => build([videoBid])[0].data.imp[0].video;
beforeEach(() => {
videoBid = mockVideoBid();
});
it('should attempt to send video bid requests to the endpoint via POST', function () {
const requests = build([videoBid]);
expect(requests.length).to.equal(1);
expect(requests[0].method).to.equal('POST');
expect(requests[0].url).to.be.equal(VIDEO_ENDPOINT);
});
it('should add mediaTypes.video prop to the imp.video prop', function () {
utils.deepAccess(videoBid, 'mediaTypes.video')['minduration'] = 40;
expect(buildVideoBidAndGetVideoParam().minduration).to.equal(40);
});
it('should override mediaTypes.video prop if params.video prop is present', function () {
utils.deepAccess(videoBid, 'mediaTypes.video')['minduration'] = 50;
utils.deepAccess(videoBid, 'params.video')['minduration'] = 40;
expect(buildVideoBidAndGetVideoParam().minduration).to.equal(40);
});
it('should add mediaTypes.video.mimes prop to the imp.video', function () {
utils.deepAccess(videoBid, 'mediaTypes.video')['minduration'] = ['video/mp4'];
expect(buildVideoBidAndGetVideoParam().minduration).to.deep.equal(['video/mp4']);
});
it('should override mediaTypes.video.mimes prop if params.video.mimes is present', function () {
utils.deepAccess(videoBid, 'mediaTypes.video')['mimes'] = ['video/mp4'];
utils.deepAccess(videoBid, 'params.video')['mimes'] = ['video/mkv'];
expect(buildVideoBidAndGetVideoParam().mimes).to.deep.equal(['video/mkv']);
});
describe('video.skip state check', () => {
it('should not set video.skip if neither *.video.skip nor *.video.skippable is present', function () {
utils.deepAccess(videoBid, 'mediaTypes.video')['skippable'] = false;
utils.deepAccess(videoBid, 'params.video')['skippable'] = false;
expect(buildVideoBidAndGetVideoParam().skip).to.undefined;
});
it('should set video.skip=1 if mediaTypes.video.skip is present', function () {
utils.deepAccess(videoBid, 'mediaTypes.video')['skip'] = 1;
expect(buildVideoBidAndGetVideoParam().skip).to.equal(1);
});
it('should set video.skip=1 if params.video.skip is present', function () {
utils.deepAccess(videoBid, 'params.video')['skip'] = 1;
expect(buildVideoBidAndGetVideoParam().skip).to.equal(1);
});
it('should set video.skip=1 if mediaTypes.video.skippable is present', function () {
utils.deepAccess(videoBid, 'mediaTypes.video')['skippable'] = true;
expect(buildVideoBidAndGetVideoParam().skip).to.equal(1);
});
it('should set video.skip=1 if mediaTypes.video.skippable is present', function () {
utils.deepAccess(videoBid, 'params.video')['skippable'] = true;
expect(buildVideoBidAndGetVideoParam().skip).to.equal(1);
});
it('should set video.skip=1 if mediaTypes.video.skippable is present', function () {
utils.deepAccess(videoBid, 'mediaTypes.video')['skippable'] = false;
utils.deepAccess(videoBid, 'params.video')['skippable'] = true;
expect(buildVideoBidAndGetVideoParam().skip).to.equal(1);
});
it('should not set video.skip if params.video.skippable is false', function () {
utils.deepAccess(videoBid, 'mediaTypes.video')['skippable'] = true;
utils.deepAccess(videoBid, 'params.video')['skippable'] = false;
expect(buildVideoBidAndGetVideoParam().skip).to.undefined;
});
});
it('should process floors module if available', function () {
const requests = build([
mockVideoBid({...mockGetFloor(3.99)}),
mockVideoBid({...mockGetFloor(1.23)}, { bidfloor: 1.1 }),
]);
const imps = requests[0].data.imp;
expect(imps[0].bidfloor).to.equal(3.99);
expect(imps[1].bidfloor).to.equal(1.23);
});
it('should use bidfloor if no floors module is available', function() {
const requests = build([
mockVideoBid({}, { bidfloor: 1.2 }),
mockVideoBid({}, { bidfloor: 0.7 }),
]);
const imps = requests[0].data.imp;
expect(imps[0].bidfloor).to.equal(1.2);
expect(imps[1].bidfloor).to.equal(0.7);
});
it('should have 0 bidfloor value by default', function() {
const requests = build([mockVideoBid()]);
expect(requests[0].data.imp[0].bidfloor).to.equal(0);
});
it('should add ats_envelope to video bid request', function() {
const envelope = 'test_envelope';
const requests = build([mockVideoBid({}, { lr_env: envelope })]);
expect(requests[0].data.ats_envelope).to.equal(envelope);
});
it('should add schain if it is in the bidRequest', () => {
const schain = {
ver: '1.0',
complete: 1,
nodes: [{
asi: 'indirectseller.com',
sid: '00001',
hp: 1
}],
};
expect(buildAndGetData([mockVideoBid({schain})]).schain).to.deep.equal(schain);
});
it('should add ip to the video bidRequest', () => {
const device = {
ip: '111.222.333.444'
};
expect(buildAndGetData([mockVideoBid(null, {device})]).device.ip).to.be.equal(device.ip);
});
it('should add gpid to the video request', function () {
const ortb2Imp = {
ext: { data: { pbadslot: '/6355419/Travel/Europe/France/Paris' } },
};
expect(buildAndGetData([mockVideoBid({ortb2Imp})]).imp[0].ext.gpid).to.be.equal(ortb2Imp.ext.data.pbadslot);
});
});
});
describe('interpretResponse', function () {
const mockServerResponse = () => ({
body: [{
callback_id: '21989fdbef550a',
cpm: 3.45455,
width: 300,
height: 250,
ad: '<html><head></head><body><script>//GEX ad object</script>' +
'<div id="ym_123" class="ym"></div><script>//js code</script></body></html>',
creative_id: '9874652394875',
adomain: ['www.example.com'],
}],
header: 'header?',
});
it('should correctly reorder the server response', function () {
const newResponse = spec.interpretResponse(mockServerResponse());
expect(newResponse.length).to.be.equal(1);
expect(newResponse[0]).to.deep.equal({
requestId: '21989fdbef550a',
cpm: 3.45455,
width: 300,
height: 250,
creativeId: '9874652394875',
currency: 'USD',
netRevenue: true,
ttl: 300,
ad: '<html><head></head><body><script>//GEX ad object</script>' +
'<div id="ym_123" class="ym"></div><script>//js code</script></body></html>',
meta: {
advertiserDomains: ['www.example.com'],
mediaType: 'banner',
},
});
});
it('should correctly reorder video bids', function () {
const response = mockServerResponse();
const seatbid = [
{
bid: {
adm: '<?xml version="1.0" encoding="UTF-8"?>',
adomain: ['www.example.com'],
crid: 'dd65c0a7536aff',
impid: '91ea8bba1',
price: 1.5,
},
},
];
const bidRequest = {
data: {
imp: [
{
id: '91ea8bba1',
video: {
h: 250,
w: 300,
},
},
],
},
};
response.body.seatbid = seatbid;
const newResponse = spec.interpretResponse(response, bidRequest);
expect(newResponse.length).to.be.equal(2);
expect(newResponse[1]).to.deep.equal({
cpm: 1.5,
creativeId: 'dd65c0a7536aff',
currency: 'USD',
height: 250,
mediaType: 'video',
meta: {
advertiserDomains: ['www.example.com'],
mediaType: 'video',
},
netRevenue: true,
requestId: '91ea8bba1',
ttl: 300,
vastXml: '<?xml version="1.0" encoding="UTF-8"?>',
width: 300,
});
});
it('should not add responses if the cpm is 0 or null', function () {
let response = mockServerResponse();
response.body[0].cpm = 0;
expect(spec.interpretResponse(response)).to.deep.equal([]);
response.body[0].cpm = null;
expect(spec.interpretResponse(response)).to.deep.equal([]);
});
});
describe('getUserSync', function () {
it('should return a tracker with type and url as parameters', function () {
expect(spec.getUserSyncs()).to.deep.equal([]);
});
});
});