Skip to content

Commit

Permalink
SAS-15935 Passing the consent to the script execution instead of hand…
Browse files Browse the repository at this point in the history
…ling it in prebid (#16)
  • Loading branch information
gguridi authored Jul 2, 2024
1 parent 7eb51fc commit 3bca76d
Show file tree
Hide file tree
Showing 2 changed files with 43 additions and 201 deletions.
91 changes: 13 additions & 78 deletions modules/azerionedgeRtdProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ const SUBREAL_TIME_MODULE = 'azerionedge';
export const STORAGE_KEY = 'ht-pa-v1-a';

const IMPROVEDIGITAL_GVLID = '253';
const PURPOSES = ['1', '3', '5', '7', '9'];

export const storage = getStorageManager({
moduleType: MODULE_TYPE_RTD,
Expand All @@ -45,14 +44,21 @@ function getScriptURL(config) {
* Attach script tag to DOM
*
* @param {Object} config
* @param {Object} userConsent
*
* @return {void}
*/
export function attachScript(config) {
export function attachScript(config, userConsent) {
const script = getScriptURL(config);
loadExternalScript(script, SUBREAL_TIME_MODULE, () => {
if (typeof window.azerionPublisherAudiences === 'function') {
window.azerionPublisherAudiences(config.params?.process || {});
const publisherConfig = config.params?.process || {};
window.azerionPublisherAudiences({
...publisherConfig,
gdprApplies: userConsent?.gdpr?.gdprApplies,
gdprConsent: userConsent?.gdpr?.consentString,
uspConsent: userConsent?.usp,
});
}
});
}
Expand Down Expand Up @@ -109,79 +115,10 @@ export function setAudiencesToBidders(reqBidsConfigObj, config, audiences) {
* @return {boolean}
*/
function init(config, userConsent) {
if (hasUserConsented(userConsent)) {
attachScript(config);
}
attachScript(config, userConsent);
return true;
}

/**
* List the vendors consented coming from userConsent object.
*
* @param {Object} userConsent
*
* @return {Array}
*/
function getVendorsConsented(userConsent) {
const consents = userConsent?.gdpr?.vendorData?.vendor?.consents || {};
return Object.entries(consents).reduce((acc, [vendorId, consented]) => {
return consented ? [...acc, vendorId] : acc;
}, []);
}

/**
* List the purposes consented coming from userConsent object.
*
* @param {Object} userConsent
*
* @return {Array}
*/
export function getPurposesConsented(userConsent) {
const consents = userConsent?.gdpr?.vendorData?.purpose?.consents || {};
return Object.entries(consents).reduce((acc, [purposeId, consented]) => {
return consented ? [...acc, purposeId] : acc;
}, []);
}

/**
* Checks if GDPR gives us access through the userConsent object.
*
* @param {Object} userConsent
*
* @return {boolean}
*/
export function hasGDPRAccess(userConsent) {
const gdprApplies = userConsent?.gdpr?.gdprApplies;
const isVendorAllowed = getVendorsConsented(userConsent).includes(IMPROVEDIGITAL_GVLID);
const arePurposesAllowed = PURPOSES.every((purpose) => getPurposesConsented(userConsent).includes(purpose));
return !gdprApplies || (isVendorAllowed && arePurposesAllowed);
}

/**
* Checks if USP gives us access through the userConsent object.
*
* @param {Object} userConsent
*
* @return {boolean}
*/
export function hasUSPAccess(userConsent) {
const uspProvided = userConsent?.usp;
const hasProvidedUserNotice = uspProvided?.[1] !== 'N';
const hasNotOptedOut = uspProvided?.[2] !== 'Y';
return !uspProvided || (hasProvidedUserNotice && hasNotOptedOut);
}

/**
* Checks if GDPR/USP gives us access through the userConsent object.
*
* @param {Object} userConsent
*
* @return {boolean}
*/
export function hasUserConsented(userConsent) {
return hasGDPRAccess(userConsent) && hasUSPAccess(userConsent);
}

/**
* Real-time user audiences retrieval
*
Expand All @@ -198,11 +135,9 @@ export function getBidRequestData(
config,
userConsent
) {
if (hasUserConsented(userConsent)) {
const audiences = getAudiences();
if (audiences.length > 0) {
setAudiencesToBidders(reqBidsConfigObj, config, audiences);
}
const audiences = getAudiences();
if (audiences.length > 0) {
setAudiencesToBidders(reqBidsConfigObj, config, audiences);
}
callback();
}
Expand Down
153 changes: 30 additions & 123 deletions test/spec/modules/azerionedgeRtdProvider_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,12 @@ describe('Azerion Edge RTD submodule', function () {
const bidders = ['appnexus', 'improvedigital'];
const process = { key: 'value' };
const dataProvider = { name: 'azerionedge', waitForIt: true };
const tcfGDPRNotApplicable = { gdprApplies: false };
const uspNotProvided = { usp: undefined };
const ignoreConsent = {gdpr: tcfGDPRNotApplicable, usp: uspNotProvided};
const userConsent = {gdpr: {gdprApplies: 'gdpr-applies', consentString: 'consent-string'}, usp: 'usp'};

const resetAll = () => {
window.azerionPublisherAudiences.resetHistory();
loadExternalScript.resetHistory();
}

let reqBidsConfigObj;
let storageStub;
Expand All @@ -36,13 +39,11 @@ describe('Azerion Edge RTD submodule', function () {
let returned;

beforeEach(function () {
returned = azerionedgeRTD.azerionedgeSubmodule.init(dataProvider, ignoreConsent);
returned = azerionedgeRTD.azerionedgeSubmodule.init(dataProvider, userConsent);
});

it('should have the correct gvlid', () => {
expect(azerionedgeRTD.azerionedgeSubmodule.gvlid).to.equal(
IMPROVEDIGITAL_GVLID
);
expect(azerionedgeRTD.azerionedgeSubmodule.gvlid).to.equal(IMPROVEDIGITAL_GVLID);
});

it('should return true', function () {
Expand All @@ -58,18 +59,21 @@ describe('Azerion Edge RTD submodule', function () {
expect(loadExternalScript.args[0][0]).to.deep.equal(expected);
});

it('should call azerionPublisherAudiencesStub with empty configuration', function () {
expect(window.azerionPublisherAudiences.args[0][0]).to.deep.equal({});
[
['gdprApplies', userConsent.gdpr.gdprApplies],
['gdprConsent', userConsent.gdpr.consentString],
['uspConsent', userConsent.usp],
].forEach(([key, value]) => {
it(`should call azerionPublisherAudiencesStub with ${key}:${value}`, function () {
expect(window.azerionPublisherAudiences.args[0][0]).to.include({[key]: value});
});
});

describe('with key', function () {
beforeEach(function () {
window.azerionPublisherAudiences.resetHistory();
loadExternalScript.resetHistory();
returned = azerionedgeRTD.azerionedgeSubmodule.init({
...dataProvider,
params: { key },
}, ignoreConsent);
resetAll();
const config = { ...dataProvider, params: { key } };
returned = azerionedgeRTD.azerionedgeSubmodule.init(config, userConsent);
});

it('should return true', function () {
Expand All @@ -84,120 +88,23 @@ describe('Azerion Edge RTD submodule', function () {

describe('with process configuration', function () {
beforeEach(function () {
window.azerionPublisherAudiences.resetHistory();
loadExternalScript.resetHistory();
returned = azerionedgeRTD.azerionedgeSubmodule.init({
...dataProvider,
params: { process },
}, ignoreConsent);
resetAll();
const config = { ...dataProvider, params: { process } };
returned = azerionedgeRTD.azerionedgeSubmodule.init(config, userConsent);
});

it('should return true', function () {
expect(returned).to.equal(true);
});

it('should call azerionPublisherAudiencesStub with process configuration', function () {
expect(window.azerionPublisherAudiences.args[0][0]).to.deep.equal(
process
);
});
});
});

describe('GDPR access', () => {
const vendorConsented = { '253': true }
const purposesConsented = {'1': true, '3': true, '5': true, '7': true, '9': true};
const partialPurposesConsented = {'1': true, '3': true, '5': true, '7': true};
const tcfConsented = { gdprApplies: true, vendorData: { vendor: { consents: vendorConsented }, purpose: { consents: purposesConsented } } };
const tcfVendorNotConsented = { gdprApplies: true, vendorData: { purpose: {consents: purposesConsented} } };
const tcfPurposesNotConsented = { gdprApplies: true, vendorData: { vendor: { consents: vendorConsented } } };
const tcfPartialPurposesNotConsented = { gdprApplies: true, vendorData: { vendor: { consents: vendorConsented }, purpose: { consents: partialPurposesConsented } } };

[
['not applicable', tcfGDPRNotApplicable, true],
['tcf consented', tcfConsented, true],
['tcf vendor not consented', tcfVendorNotConsented, false],
['tcf purposes not consented', tcfPurposesNotConsented, false],
['tcp partial purposes not consented', tcfPartialPurposesNotConsented, false],
].forEach(([info, gdpr, expected]) => {
it(`for ${info} should return ${expected}`, () => {
expect(azerionedgeRTD.hasGDPRAccess({gdpr})).to.equal(expected);
});

it(`for ${info} should load=${expected} the external script`, () => {
azerionedgeRTD.azerionedgeSubmodule.init(dataProvider, {gdpr, usp: uspNotProvided});
expect(loadExternalScript.called).to.equal(expected);
});

describe('for bid request data', function () {
let callbackStub;

beforeEach(function () {
callbackStub = sinon.mock();
azerionedgeRTD.azerionedgeSubmodule.getBidRequestData(reqBidsConfigObj, callbackStub, dataProvider, {gdpr, usp: uspNotProvided});
});

it(`does call=${expected} the local storage looking for audiences`, function () {
expect(storageStub.called).to.equal(expected);
});

it('calls callback always', function () {
expect(callbackStub.called).to.be.true;
});
});
});
});

describe('USP acccess', () => {
const uspMalformed = -1;
const uspNotApplicable = '1---';
const uspUserNotifiedOptedOut = '1YY-';
const uspUserNotifiedNotOptedOut = '1YN-';
const uspUserNotifiedUnknownOptedOut = '1Y--';
const uspUserNotNotifiedOptedOut = '1NY-';
const uspUserNotNotifiedNotOptedOut = '1NN-';
const uspUserNotNotifiedUnknownOptedOut = '1N--';
const uspUserUnknownNotifiedOptedOut = '1-Y-';
const uspUserUnknownNotifiedNotOptedOut = '1-N-';
const uspUserUnknownNotifiedUnknownOptedOut = '1---';

[
['malformed', uspMalformed, true],
['not applicable', uspNotApplicable, true],
['not provided', uspNotProvided, true],
['user notified and opted out', uspUserNotifiedOptedOut, false],
['user notified and not opted out', uspUserNotifiedNotOptedOut, true],
['user notified and unknown opted out', uspUserNotifiedUnknownOptedOut, true],
['user not notified and opted out', uspUserNotNotifiedOptedOut, false],
['user not notified and not opted out', uspUserNotNotifiedNotOptedOut, false],
['user not notified and unknown opted out', uspUserNotNotifiedUnknownOptedOut, false],
['user unknown notified and opted out', uspUserUnknownNotifiedOptedOut, false],
['user unknown notified and not opted out', uspUserUnknownNotifiedNotOptedOut, true],
['user unknown notified and unknown opted out', uspUserUnknownNotifiedUnknownOptedOut, true],
].forEach(([info, usp, expected]) => {
it(`for ${info} should return ${expected}`, () => {
expect(azerionedgeRTD.hasUSPAccess({usp})).to.equal(expected);
});

it(`for ${info} should load=${expected} the external script`, () => {
azerionedgeRTD.azerionedgeSubmodule.init(dataProvider, {gdpr: tcfGDPRNotApplicable, usp});
expect(loadExternalScript.called).to.equal(expected);
});

describe('for bid request data', function () {
let callbackStub;

beforeEach(function () {
callbackStub = sinon.mock();
azerionedgeRTD.azerionedgeSubmodule.getBidRequestData(reqBidsConfigObj, callbackStub, dataProvider, {gdpr: tcfGDPRNotApplicable, usp});
});

it(`does call=${expected} the local storage looking for audiences`, function () {
expect(storageStub.called).to.equal(expected);
});

it('calls callback always', function () {
expect(callbackStub.called).to.be.true;
[
['gdprApplies', userConsent.gdpr.gdprApplies],
['gdprConsent', userConsent.gdpr.consentString],
['uspConsent', userConsent.usp],
...Object.entries(process),
].forEach(([key, value]) => {
it(`should call azerionPublisherAudiencesStub with ${key}:${value}`, function () {
expect(window.azerionPublisherAudiences.args[0][0]).to.include({[key]: value});
});
});
});
Expand Down

0 comments on commit 3bca76d

Please sign in to comment.