mk9-prebid
Version:
Header Bidding Management Library
749 lines (654 loc) • 24.8 kB
JavaScript
import { setConsentConfig, requestBidsHook, resetConsentData, userCMP, consentTimeout, allowAuction, staticConsentData, gdprScope } from 'modules/consentManagement.js';
import { gdprDataHandler } from 'src/adapterManager.js';
import * as utils from 'src/utils.js';
import { config } from 'src/config.js';
let expect = require('chai').expect;
describe('consentManagement', function () {
describe('setConsentConfig tests:', function () {
describe('empty setConsentConfig value', function () {
beforeEach(function () {
sinon.stub(utils, 'logInfo');
sinon.stub(utils, 'logWarn');
});
afterEach(function () {
utils.logInfo.restore();
utils.logWarn.restore();
config.resetConfig();
resetConsentData();
});
it('should use system default values', function () {
setConsentConfig({});
expect(userCMP).to.be.equal('iab');
expect(consentTimeout).to.be.equal(10000);
expect(gdprScope).to.be.equal(false);
sinon.assert.callCount(utils.logInfo, 3);
});
it('should exit consent manager if config is not an object', function () {
setConsentConfig('');
expect(userCMP).to.be.undefined;
sinon.assert.calledOnce(utils.logWarn);
});
it('should exit consent manager if gdpr not set with new config structure', function () {
setConsentConfig({ usp: { cmpApi: 'iab', timeout: 50 } });
expect(userCMP).to.be.undefined;
sinon.assert.calledOnce(utils.logWarn);
});
it('should exit consentManagement module if config is "undefined"', function() {
setConsentConfig(undefined);
expect(userCMP).to.be.undefined;
sinon.assert.calledOnce(utils.logWarn);
});
});
describe('valid setConsentConfig value', function () {
afterEach(function () {
config.resetConfig();
});
it('results in all user settings overriding system defaults', function () {
let allConfig = {
cmpApi: 'iab',
timeout: 7500,
allowAuctionWithoutConsent: false,
defaultGdprScope: true
};
setConsentConfig(allConfig);
expect(userCMP).to.be.equal('iab');
expect(consentTimeout).to.be.equal(7500);
expect(allowAuction).to.deep.equal({
value: false,
definedInConfig: true
});
expect(gdprScope).to.be.true;
});
it('should use new consent manager config structure for gdpr', function () {
setConsentConfig({
gdpr: { cmpApi: 'daa', timeout: 8700 }
});
expect(userCMP).to.be.equal('daa');
expect(consentTimeout).to.be.equal(8700);
});
it('should ignore config.usp and use config.gdpr, with default cmpApi', function () {
setConsentConfig({
gdpr: { timeout: 5000 },
usp: { cmpApi: 'daa', timeout: 50 }
});
expect(userCMP).to.be.equal('iab');
expect(consentTimeout).to.be.equal(5000);
});
it('should ignore config.usp and use config.gdpr, with default cmpAip and timeout', function () {
setConsentConfig({
gdpr: {},
usp: { cmpApi: 'daa', timeout: 50 }
});
expect(userCMP).to.be.equal('iab');
expect(consentTimeout).to.be.equal(10000);
});
it('should recognize config.gdpr, with default cmpAip and timeout', function () {
setConsentConfig({
gdpr: {}
});
expect(userCMP).to.be.equal('iab');
expect(consentTimeout).to.be.equal(10000);
});
it('should fallback to old consent manager config object if no config.gdpr', function () {
setConsentConfig({
cmpApi: 'iab',
timeout: 3333,
allowAuctionWithoutConsent: false,
gdpr: false
});
expect(userCMP).to.be.equal('iab');
expect(consentTimeout).to.be.equal(3333);
expect(allowAuction).to.deep.equal({
value: false,
definedInConfig: true
});
expect(gdprScope).to.be.equal(false);
});
});
describe('static consent string setConsentConfig value', () => {
afterEach(() => {
config.resetConfig();
});
it('results in user settings overriding system defaults for v1 spec', () => {
let staticConfig = {
cmpApi: 'static',
timeout: 7500,
allowAuctionWithoutConsent: false,
consentData: {
getConsentData: {
'gdprApplies': true,
'hasGlobalScope': false,
'consentData': 'BOOgjO9OOgjO9APABAENAi-AAAAWd7_______9____7_9uz_Gv_r_ff_3nW0739P1A_r_Oz_rm_-zzV44_lpQQRCEA'
},
getVendorConsents: {
'metadata': 'BOOgjO9OOgjO9APABAENAi-AAAAWd7_______9____7_9uz_Gv_r_ff_3nW0739P1A_r_Oz_rm_-zzV44_lpQQRCEA',
'gdprApplies': true,
'hasGlobalScope': false,
'isEU': true,
'cookieVersion': 1,
'created': '2018-05-29T07:45:48.522Z',
'lastUpdated': '2018-05-29T07:45:48.522Z',
'cmpId': 15,
'cmpVersion': 1,
'consentLanguage': 'EN',
'vendorListVersion': 34,
'maxVendorId': 359,
'purposeConsents': {
'1': true,
'2': true,
'3': true,
'4': true,
'5': true
},
'vendorConsents': {
'1': true,
'2': true,
'3': true,
'4': true,
'5': false
}
}
}
};
setConsentConfig(staticConfig);
expect(userCMP).to.be.equal('static');
expect(consentTimeout).to.be.equal(0); // should always return without a timeout when config is used
expect(allowAuction).to.deep.equal({
value: false,
definedInConfig: true
});
expect(staticConsentData).to.be.equal(staticConfig.consentData);
});
it('results in user settings overriding system defaults for v2 spec', () => {
let staticConfig = {
cmpApi: 'static',
timeout: 7500,
allowAuctionWithoutConsent: false,
consentData: {
getTCData: {
'tcString': 'COuqj-POu90rDBcBkBENAZCgAPzAAAPAACiQFwwBAABAA1ADEAbQC4YAYAAgAxAG0A',
'cmpId': 92,
'cmpVersion': 100,
'tcfPolicyVersion': 2,
'gdprApplies': true,
'isServiceSpecific': true,
'useNonStandardStacks': false,
'purposeOneTreatment': false,
'publisherCC': 'US',
'cmpStatus': 'loaded',
'eventStatus': 'tcloaded',
'outOfBand': {
'allowedVendors': {},
'discloseVendors': {}
},
'purpose': {
'consents': {
'1': true,
'2': true,
'3': true
},
'legitimateInterests': {
'1': false,
'2': false,
'3': false
}
},
'vendor': {
'consents': {
'1': false,
'2': true,
'3': false
},
'legitimateInterests': {
'1': false,
'2': true,
'3': false,
'4': false,
'5': false
}
},
'specialFeatureOptins': {
'1': false,
'2': false
},
'restrictions': {},
'publisher': {
'consents': {
'1': false,
'2': false,
'3': false
},
'legitimateInterests': {
'1': false,
'2': false,
'3': false
},
'customPurpose': {
'consents': {},
'legitimateInterests': {}
}
}
}
}
};
setConsentConfig(staticConfig);
expect(userCMP).to.be.equal('static');
expect(consentTimeout).to.be.equal(0); // should always return without a timeout when config is used
expect(allowAuction).to.deep.equal({
value: false,
definedInConfig: true
});
expect(gdprScope).to.be.equal(false);
expect(staticConsentData).to.be.equal(staticConfig.consentData);
});
});
});
describe('requestBidsHook tests:', function () {
let goodConfigWithCancelAuction = {
cmpApi: 'iab',
timeout: 7500,
allowAuctionWithoutConsent: false
};
let goodConfigWithAllowAuction = {
cmpApi: 'iab',
timeout: 7500,
allowAuctionWithoutConsent: true
};
let didHookReturn;
afterEach(function () {
gdprDataHandler.consentData = null;
resetConsentData();
});
describe('error checks:', function () {
beforeEach(function () {
didHookReturn = false;
sinon.stub(utils, 'logWarn');
sinon.stub(utils, 'logError');
});
afterEach(function () {
utils.logWarn.restore();
utils.logError.restore();
config.resetConfig();
resetConsentData();
});
it('should throw a warning and return to hooked function when an unknown CMP framework ID is used', function () {
let badCMPConfig = {
cmpApi: 'bad'
};
setConsentConfig(badCMPConfig);
expect(userCMP).to.be.equal(badCMPConfig.cmpApi);
requestBidsHook(() => {
didHookReturn = true;
}, {});
let consent = gdprDataHandler.getConsentData();
sinon.assert.calledOnce(utils.logWarn);
expect(didHookReturn).to.be.true;
expect(consent).to.be.null;
});
it('should throw proper errors when CMP is not found', function () {
setConsentConfig(goodConfigWithCancelAuction);
requestBidsHook(() => {
didHookReturn = true;
}, {});
let consent = gdprDataHandler.getConsentData();
// throw 2 errors; one for no bidsBackHandler and for CMP not being found (this is an error due to gdpr config)
sinon.assert.calledTwice(utils.logError);
expect(didHookReturn).to.be.false;
expect(consent).to.be.null;
});
});
describe('already known consentData:', function () {
let cmpStub = sinon.stub();
beforeEach(function () {
didHookReturn = false;
window.__cmp = function () { };
});
afterEach(function () {
config.resetConfig();
cmpStub.restore();
delete window.__cmp;
resetConsentData();
});
it('should bypass CMP and simply use previously stored consentData', function () {
let testConsentData = {
gdprApplies: true,
consentData: 'xyz'
};
cmpStub = sinon.stub(window, '__cmp').callsFake((...args) => {
args[2](testConsentData);
});
setConsentConfig(goodConfigWithAllowAuction);
requestBidsHook(() => { }, {});
cmpStub.restore();
// reset the stub to ensure it wasn't called during the second round of calls
cmpStub = sinon.stub(window, '__cmp').callsFake((...args) => {
args[2](testConsentData);
});
requestBidsHook(() => {
didHookReturn = true;
}, {});
let consent = gdprDataHandler.getConsentData();
expect(didHookReturn).to.be.true;
expect(consent.consentString).to.equal(testConsentData.consentData);
expect(consent.gdprApplies).to.be.true;
sinon.assert.notCalled(cmpStub);
});
it('should not set consent.gdprApplies to true if defaultGdprScope is true', function () {
let testConsentData = {
gdprApplies: false,
consentData: 'xyz'
};
cmpStub = sinon.stub(window, '__cmp').callsFake((...args) => {
args[2](testConsentData);
});
setConsentConfig({
cmpApi: 'iab',
timeout: 7500,
defaultGdprScope: true
});
requestBidsHook(() => {
didHookReturn = true;
}, {});
let consent = gdprDataHandler.getConsentData();
expect(consent.gdprApplies).to.be.false;
});
});
describe('iframe tests', function () {
let cmpPostMessageCb = () => { };
let stringifyResponse;
function createIFrameMarker(frameName) {
let ifr = document.createElement('iframe');
ifr.width = 0;
ifr.height = 0;
ifr.name = frameName;
document.body.appendChild(ifr);
return ifr;
}
function creatCmpMessageHandler(prefix, returnValue) {
return function (event) {
if (event && event.data) {
let data = event.data;
if (data[`${prefix}Call`]) {
let callId = data[`${prefix}Call`].callId;
let response = {
[`${prefix}Return`]: {
callId,
returnValue,
success: true
}
};
event.source.postMessage(stringifyResponse ? JSON.stringify(response) : response, '*');
}
}
}
}
function testIFramedPage(testName, messageFormatString, tarConsentString, ver) {
it(`should return the consent string from a postmessage + addEventListener response - ${testName}`, (done) => {
stringifyResponse = messageFormatString;
setConsentConfig(goodConfigWithAllowAuction);
requestBidsHook(() => {
let consent = gdprDataHandler.getConsentData();
sinon.assert.notCalled(utils.logError);
expect(consent.consentString).to.equal(tarConsentString);
expect(consent.gdprApplies).to.be.true;
expect(consent.apiVersion).to.equal(ver);
done();
}, {});
});
}
beforeEach(function () {
sinon.stub(utils, 'logError');
sinon.stub(utils, 'logWarn');
});
afterEach(function () {
utils.logError.restore();
utils.logWarn.restore();
config.resetConfig();
resetConsentData();
});
describe('v1 CMP workflow for safeframe page', function () {
let registerStub = sinon.stub();
let ifrSf = null;
beforeEach(function () {
didHookReturn = false;
window.$sf = {
ext: {
register: function () { },
cmp: function () { }
}
};
ifrSf = createIFrameMarker('__cmpLocator');
});
afterEach(function () {
delete window.$sf;
registerStub.restore();
document.body.removeChild(ifrSf);
});
it('should return the consent data from a safeframe callback', function () {
let testConsentData = {
data: {
msgName: 'cmpReturn',
vendorConsents: {
metadata: 'abc123def',
gdprApplies: true
},
vendorConsentData: {
consentData: 'abc123def',
gdprApplies: true
}
}
};
registerStub = sinon.stub(window.$sf.ext, 'register').callsFake((...args) => {
args[2](testConsentData.data.msgName, testConsentData.data);
});
setConsentConfig(goodConfigWithAllowAuction);
requestBidsHook(() => {
didHookReturn = true;
}, { adUnits: [{ sizes: [[300, 250]] }] });
let consent = gdprDataHandler.getConsentData();
sinon.assert.notCalled(utils.logWarn);
sinon.assert.notCalled(utils.logError);
expect(didHookReturn).to.be.true;
expect(consent.consentString).to.equal('abc123def');
expect(consent.gdprApplies).to.be.true;
expect(consent.apiVersion).to.equal(1);
});
});
describe('v1 CMP workflow for iframe pages', function () {
stringifyResponse = false;
let ifr1 = null;
beforeEach(function () {
ifr1 = createIFrameMarker('__cmpLocator');
cmpPostMessageCb = creatCmpMessageHandler('__cmp', {
consentData: 'encoded_consent_data_via_post_message',
gdprApplies: true,
});
window.addEventListener('message', cmpPostMessageCb, false);
});
afterEach(function () {
delete window.__cmp; // deletes the local copy made by the postMessage CMP call function
document.body.removeChild(ifr1);
window.removeEventListener('message', cmpPostMessageCb);
});
// Run tests with JSON response and String response
// from CMP window postMessage listener.
testIFramedPage('with/JSON response', false, 'encoded_consent_data_via_post_message', 1);
testIFramedPage('with/String response', true, 'encoded_consent_data_via_post_message', 1);
it('should contain correct V1 CMP definition', (done) => {
setConsentConfig(goodConfigWithAllowAuction);
requestBidsHook(() => {
const nbArguments = window.__cmp.toString().split('\n')[0].split(', ').length;
expect(nbArguments).to.equal(3);
done();
}, {});
});
});
describe('v2 CMP workflow for iframe pages:', function () {
stringifyResponse = false;
let ifr2 = null;
beforeEach(function () {
ifr2 = createIFrameMarker('__tcfapiLocator');
cmpPostMessageCb = creatCmpMessageHandler('__tcfapi', {
tcString: 'abc12345234',
gdprApplies: true,
purposeOneTreatment: false,
eventStatus: 'tcloaded'
});
window.addEventListener('message', cmpPostMessageCb, false);
});
afterEach(function () {
delete window.__tcfapi; // deletes the local copy made by the postMessage CMP call function
document.body.removeChild(ifr2);
window.removeEventListener('message', cmpPostMessageCb);
});
testIFramedPage('with/JSON response', false, 'abc12345234', 2);
testIFramedPage('with/String response', true, 'abc12345234', 2);
it('should contain correct v2 CMP definition', (done) => {
setConsentConfig(goodConfigWithAllowAuction);
requestBidsHook(() => {
const nbArguments = window.__tcfapi.toString().split('\n')[0].split(', ').length;
expect(nbArguments).to.equal(4);
done();
}, {});
});
});
});
describe('direct calls to CMP API tests', function () {
let cmpStub = sinon.stub();
beforeEach(function () {
didHookReturn = false;
sinon.stub(utils, 'logError');
sinon.stub(utils, 'logWarn');
});
afterEach(function () {
config.resetConfig();
cmpStub.restore();
utils.logError.restore();
utils.logWarn.restore();
resetConsentData();
});
describe('v1 CMP workflow for normal pages:', function () {
beforeEach(function () {
window.__cmp = function () { };
});
afterEach(function () {
delete window.__cmp;
});
it('performs lookup check and stores consentData for a valid existing user', function () {
let testConsentData = {
gdprApplies: true,
consentData: 'BOJy+UqOJy+UqABAB+AAAAAZ+A=='
};
cmpStub = sinon.stub(window, '__cmp').callsFake((...args) => {
args[2](testConsentData);
});
setConsentConfig(goodConfigWithAllowAuction);
requestBidsHook(() => {
didHookReturn = true;
}, {});
let consent = gdprDataHandler.getConsentData();
sinon.assert.notCalled(utils.logWarn);
sinon.assert.notCalled(utils.logError);
expect(didHookReturn).to.be.true;
expect(consent.consentString).to.equal(testConsentData.consentData);
expect(consent.gdprApplies).to.be.true;
expect(consent.apiVersion).to.equal(1);
});
});
describe('v2 CMP workflow for normal pages:', function () {
beforeEach(function() {
window.__tcfapi = function () { };
});
afterEach(function () {
delete window.__tcfapi;
});
it('performs lookup check and stores consentData for a valid existing user', function () {
let testConsentData = {
tcString: 'abc12345234',
gdprApplies: true,
purposeOneTreatment: false,
eventStatus: 'tcloaded'
};
cmpStub = sinon.stub(window, '__tcfapi').callsFake((...args) => {
args[2](testConsentData, true);
});
setConsentConfig(goodConfigWithAllowAuction);
requestBidsHook(() => {
didHookReturn = true;
}, {});
let consent = gdprDataHandler.getConsentData();
sinon.assert.notCalled(utils.logError);
expect(didHookReturn).to.be.true;
expect(consent.consentString).to.equal(testConsentData.tcString);
expect(consent.gdprApplies).to.be.true;
expect(consent.apiVersion).to.equal(2);
});
it('performs lookup check and stores consentData for a valid existing user with additional consent', function () {
let testConsentData = {
tcString: 'abc12345234',
addtlConsent: 'superduperstring',
gdprApplies: true,
purposeOneTreatment: false,
eventStatus: 'tcloaded'
};
cmpStub = sinon.stub(window, '__tcfapi').callsFake((...args) => {
args[2](testConsentData, true);
});
setConsentConfig(goodConfigWithAllowAuction);
requestBidsHook(() => {
didHookReturn = true;
}, {});
let consent = gdprDataHandler.getConsentData();
sinon.assert.notCalled(utils.logError);
expect(didHookReturn).to.be.true;
expect(consent.consentString).to.equal(testConsentData.tcString);
expect(consent.addtlConsent).to.equal(testConsentData.addtlConsent);
expect(consent.gdprApplies).to.be.true;
expect(consent.apiVersion).to.equal(2);
});
it('throws an error when processCmpData check fails + does not call requestBids callbcack even when allowAuction is true', function () {
let testConsentData = {};
let bidsBackHandlerReturn = false;
cmpStub = sinon.stub(window, '__tcfapi').callsFake((...args) => {
args[2](testConsentData);
});
setConsentConfig(goodConfigWithAllowAuction);
requestBidsHook(() => {
didHookReturn = true;
}, { bidsBackHandler: () => bidsBackHandlerReturn = true });
let consent = gdprDataHandler.getConsentData();
sinon.assert.calledOnce(utils.logError);
sinon.assert.notCalled(utils.logWarn);
expect(didHookReturn).to.be.false;
expect(bidsBackHandlerReturn).to.be.true;
expect(consent).to.be.null;
});
it('It still considers it a valid cmp response if gdprApplies is not a boolean', function () {
// gdprApplies is undefined, should just still store consent response but use whatever defaultGdprScope was
let testConsentData = {
tcString: 'abc12345234',
purposeOneTreatment: false,
eventStatus: 'tcloaded'
};
cmpStub = sinon.stub(window, '__tcfapi').callsFake((...args) => {
args[2](testConsentData, true);
});
setConsentConfig({
cmpApi: 'iab',
timeout: 7500,
defaultGdprScope: true
});
requestBidsHook(() => {
didHookReturn = true;
}, {});
let consent = gdprDataHandler.getConsentData();
sinon.assert.notCalled(utils.logError);
expect(didHookReturn).to.be.true;
expect(consent.consentString).to.equal(testConsentData.tcString);
expect(consent.gdprApplies).to.be.true;
expect(consent.apiVersion).to.equal(2);
});
});
});
});
});