From 0c2f567e1078230f6694e9ffa8bb0da5ab893c8c Mon Sep 17 00:00:00 2001 From: Harshad Mane Date: Fri, 6 Dec 2019 09:42:26 -0800 Subject: [PATCH] PubMatic bid adapter to support CCPA/USP (#4533) * added support for pubcommon, digitrust, id5id * added support for IdentityLink * changed the source for id5 * added unit test cases * changed source param for identityLink * added CCPA/USP support in PubMatic adapter --- modules/pubmaticBidAdapter.js | 12 +- test/spec/modules/pubmaticBidAdapter_spec.js | 109 +++++++++++++++++++ 2 files changed, 120 insertions(+), 1 deletion(-) diff --git a/modules/pubmaticBidAdapter.js b/modules/pubmaticBidAdapter.js index 34beb4d8745..4281c4a449e 100644 --- a/modules/pubmaticBidAdapter.js +++ b/modules/pubmaticBidAdapter.js @@ -917,6 +917,11 @@ export const spec = { }; } + // CCPA + if (bidderRequest && bidderRequest.uspConsent) { + utils.deepSetValue(payload, 'regs.ext.us_privacy', bidderRequest.uspConsent); + } + // coppa compliance if (config.getConfig('coppa') === true) { utils.deepSetValue(payload, 'regs.coppa', 1); @@ -1012,7 +1017,7 @@ export const spec = { /** * Register User Sync. */ - getUserSyncs: (syncOptions, responses, gdprConsent) => { + getUserSyncs: (syncOptions, responses, gdprConsent, uspConsent) => { let syncurl = USYNCURL + publisherId; // Attaching GDPR Consent Params in UserSync url @@ -1021,6 +1026,11 @@ export const spec = { syncurl += '&gdpr_consent=' + encodeURIComponent(gdprConsent.consentString || ''); } + // CCPA + if (uspConsent) { + syncurl += '&us_privacy=' + encodeURIComponent(uspConsent); + } + // coppa compliance if (config.getConfig('coppa') === true) { syncurl += '&coppa=1'; diff --git a/test/spec/modules/pubmaticBidAdapter_spec.js b/test/spec/modules/pubmaticBidAdapter_spec.js index b2c2da99d23..764897b596b 100644 --- a/test/spec/modules/pubmaticBidAdapter_spec.js +++ b/test/spec/modules/pubmaticBidAdapter_spec.js @@ -982,6 +982,43 @@ describe('PubMatic adapter', function () { expect(data.imp[0].ext.pmZoneId).to.equal(bidRequests[0].params.pmzoneid.split(',').slice(0, 50).map(id => id.trim()).join()); // pmzoneid }); + it('Request params check with USP/CCPA Consent', function () { + let bidRequest = { + uspConsent: '1NYN' + }; + let request = spec.buildRequests(bidRequests, bidRequest); + let data = JSON.parse(request.data); + expect(data.regs.ext.us_privacy).to.equal('1NYN');// USP/CCPAs + expect(data.at).to.equal(1); // auction type + expect(data.cur[0]).to.equal('USD'); // currency + expect(data.site.domain).to.be.a('string'); // domain should be set + expect(data.site.page).to.equal(bidRequests[0].params.kadpageurl); // forced pageURL + expect(data.site.publisher.id).to.equal(bidRequests[0].params.publisherId); // publisher Id + expect(data.user.yob).to.equal(parseInt(bidRequests[0].params.yob)); // YOB + expect(data.user.gender).to.equal(bidRequests[0].params.gender); // Gender + expect(data.device.geo.lat).to.equal(parseFloat(bidRequests[0].params.lat)); // Latitude + expect(data.device.geo.lon).to.equal(parseFloat(bidRequests[0].params.lon)); // Lognitude + expect(data.user.geo.lat).to.equal(parseFloat(bidRequests[0].params.lat)); // Latitude + expect(data.user.geo.lon).to.equal(parseFloat(bidRequests[0].params.lon)); // Lognitude + expect(data.ext.wrapper.wv).to.equal($$REPO_AND_VERSION$$); // Wrapper Version + expect(data.ext.wrapper.transactionId).to.equal(bidRequests[0].transactionId); // Prebid TransactionId + expect(data.ext.wrapper.wiid).to.equal(bidRequests[0].params.wiid); // OpenWrap: Wrapper Impression ID + expect(data.ext.wrapper.profile).to.equal(parseInt(bidRequests[0].params.profId)); // OpenWrap: Wrapper Profile ID + expect(data.ext.wrapper.version).to.equal(parseInt(bidRequests[0].params.verId)); // OpenWrap: Wrapper Profile Version ID + + expect(data.imp[0].id).to.equal(bidRequests[0].bidId); // Prebid bid id is passed as id + expect(data.imp[0].bidfloor).to.equal(parseFloat(bidRequests[0].params.kadfloor)); // kadfloor + expect(data.imp[0].tagid).to.equal('/15671365/DMDemo'); // tagid + expect(data.imp[0].banner.w).to.equal(300); // width + expect(data.imp[0].banner.h).to.equal(250); // height + expect(data.imp[0].ext.pmZoneId).to.equal(bidRequests[0].params.pmzoneid.split(',').slice(0, 50).map(id => id.trim()).join()); // pmzoneid + + // second request without USP/CCPA + let request2 = spec.buildRequests(bidRequests, {}); + let data2 = JSON.parse(request2.data); + expect(data2.regs).to.equal(undefined);// USP/CCPAs + }); + it('Request should have digitrust params', function() { window.DigiTrust = { getUser: function () { @@ -2444,5 +2481,77 @@ describe('PubMatic adapter', function () { expect(response[0].mediaType).to.equal('native'); }); }); + + describe('getUserSyncs', function() { + const syncurl = 'https://ads.pubmatic.com/AdServer/js/showad.js#PIX&kdntuid=1&p=5670'; + let sandbox; + beforeEach(function () { + sandbox = sinon.sandbox.create(); + }); + afterEach(function() { + sandbox.restore(); + }) + + it('execute only if iframeEnabled', function() { + expect(spec.getUserSyncs({ iframeEnabled: true }, {}, undefined, undefined)).to.deep.equal([{ + type: 'iframe', url: syncurl + }]); + expect(spec.getUserSyncs({ iframeEnabled: false }, {}, undefined, undefined)).to.equal(undefined); + }); + + it('CCPA/USP', function() { + expect(spec.getUserSyncs({ iframeEnabled: true }, {}, undefined, '1NYN')).to.deep.equal([{ + type: 'iframe', url: `${syncurl}&us_privacy=1NYN` + }]); + }); + + it('GDPR', function() { + expect(spec.getUserSyncs({ iframeEnabled: true }, {}, {gdprApplies: true, consentString: 'foo'}, undefined)).to.deep.equal([{ + type: 'iframe', url: `${syncurl}&gdpr=1&gdpr_consent=foo` + }]); + expect(spec.getUserSyncs({ iframeEnabled: true }, {}, {gdprApplies: false, consentString: 'foo'}, undefined)).to.deep.equal([{ + type: 'iframe', url: `${syncurl}&gdpr=0&gdpr_consent=foo` + }]); + expect(spec.getUserSyncs({ iframeEnabled: true }, {}, {gdprApplies: true, consentString: undefined}, undefined)).to.deep.equal([{ + type: 'iframe', url: `${syncurl}&gdpr=1&gdpr_consent=` + }]); + }); + + it('COPPA: true', function() { + sandbox.stub(config, 'getConfig').callsFake(key => { + const config = { + 'coppa': true + }; + return config[key]; + }); + expect(spec.getUserSyncs({ iframeEnabled: true }, {}, undefined, undefined)).to.deep.equal([{ + type: 'iframe', url: `${syncurl}&coppa=1` + }]); + }); + + it('COPPA: false', function() { + sandbox.stub(config, 'getConfig').callsFake(key => { + const config = { + 'coppa': false + }; + return config[key]; + }); + expect(spec.getUserSyncs({ iframeEnabled: true }, {}, undefined, undefined)).to.deep.equal([{ + type: 'iframe', url: `${syncurl}` + }]); + }); + + it('GDPR + COPPA:true + CCPA/USP', function() { + sandbox.stub(config, 'getConfig').callsFake(key => { + const config = { + 'coppa': true + }; + return config[key]; + }); + expect(spec.getUserSyncs({ iframeEnabled: true }, {}, {gdprApplies: true, consentString: 'foo'}, '1NYN')).to.deep.equal([{ + type: 'iframe', url: `${syncurl}&gdpr=1&gdpr_consent=foo&us_privacy=1NYN&coppa=1` + }]); + }); + }); }); });