Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add GDPR and UID module support to Undertone adapter #4102

Merged
merged 8 commits into from
Aug 27, 2019
40 changes: 32 additions & 8 deletions modules/undertoneBidAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,16 @@ function extractDomainFromHost(pageHost) {
return domain;
}

function getGdprQueryParams(gdprConsent) {
if (!gdprConsent) {
return null;
}

let gdpr = gdprConsent.gdprApplies ? '1' : '0';
let gdprstr = gdprConsent.consentString ? gdprConsent.consentString : '';
return `gdpr=${gdpr}&gdprstr=${gdprstr}`;
}

export const spec = {
code: BIDDER_CODE,
isBidRequestValid: function(bid) {
Expand All @@ -51,15 +61,24 @@ export const spec = {
},
buildRequests: function(validBidRequests, bidderRequest) {
const payload = {
'x-ut-hb-params': []
'x-ut-hb-params': [],
'commons': {
'adapterVersion': '$prebid.version$',
'uids': validBidRequests[0].userId
}
};
const referer = bidderRequest.refererInfo.referer;
const hostname = urlUtils.parse(referer).hostname;
let domain = extractDomainFromHost(hostname);
const pageUrl = getCanonicalUrl() || referer;

const pubid = validBidRequests[0].params.publisherId;
const REQ_URL = `${URL}?pid=${pubid}&domain=${domain}`;
let reqUrl = `${URL}?pid=${pubid}&domain=${domain}`;

let gdprParams = getGdprQueryParams(bidderRequest.gdprConsent);
if (gdprParams) {
reqUrl += `&${gdprParams}`;
}

validBidRequests.map(bidReq => {
const bid = {
Expand All @@ -76,7 +95,7 @@ export const spec = {
});
return {
method: 'POST',
url: REQ_URL,
url: reqUrl,
withCredentials: true,
data: JSON.stringify(payload)
};
Expand Down Expand Up @@ -107,23 +126,28 @@ export const spec = {
},
getUserSyncs: function(syncOptions, serverResponses, gdprConsent) {
const syncs = [];
if (gdprConsent && gdprConsent.gdprApplies === true) {
return syncs;

let gdprParams = getGdprQueryParams(gdprConsent);
let iframeGdprParams = '';
let pixelGdprParams = '';
if (gdprParams) {
iframeGdprParams += `?${gdprParams}`;
pixelGdprParams += `&${gdprParams}`;
}

if (syncOptions.iframeEnabled) {
syncs.push({
type: 'iframe',
url: FRAME_USER_SYNC
url: FRAME_USER_SYNC + iframeGdprParams
});
} else if (syncOptions.pixelEnabled) {
syncs.push({
type: 'image',
url: PIXEL_USER_SYNC_1
url: PIXEL_USER_SYNC_1 + pixelGdprParams
},
{
type: 'image',
url: PIXEL_USER_SYNC_2
url: PIXEL_USER_SYNC_2 + pixelGdprParams
});
}
return syncs;
Expand Down
139 changes: 118 additions & 21 deletions test/spec/modules/undertoneBidAdapter_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const validBidReq = {
},
sizes: [[300, 250], [300, 600]],
bidId: '263be71e91dd9d',
auctionId: '9ad1fa8d-2297-4660-a018-b39945054746',
auctionId: '9ad1fa8d-2297-4660-a018-b39945054747',
};

const invalidBidReq = {
Expand Down Expand Up @@ -44,12 +44,46 @@ const bidReq = [{
auctionId: '6c22f5a5-59df-4dc6-b92c-f433bcf0a874'
}];

const bidReqUserIds = [{
bidder: BIDDER_CODE,
params: {
placementId: '10433394',
publisherId: 12345
},
sizes: [[300, 250], [300, 600]],
bidId: '263be71e91dd9d',
auctionId: '9ad1fa8d-2297-4660-a018-b39945054746',
userId: {
tdid: '123456',
digitrustid: {data: {id: 'DTID', keyv: 4, privacy: {optout: false}, producer: 'ABC', version: 2}}
}
},
{
bidder: BIDDER_CODE,
params: {
publisherId: 12345
},
sizes: [[1, 1]],
bidId: '453cf42d72bb3c',
auctionId: '6c22f5a5-59df-4dc6-b92c-f433bcf0a874'
}];

const bidderReq = {
refererInfo: {
referer: 'http://prebid.org/dev-docs/bidder-adaptor.html'
}
};

const bidderReqGdpr = {
refererInfo: {
referer: 'http://prebid.org/dev-docs/bidder-adaptor.html'
},
gdprConsent: {
gdprApplies: true,
consentString: 'acdefgh'
}
};

const validBidRes = {
ad: '<div>Hello</div>',
publisherId: 12345,
Expand Down Expand Up @@ -103,7 +137,7 @@ describe('Undertone Adapter', function () {
});
});
describe('build request', function () {
it('should send request to correct url via POST', function () {
it('should send request to correct url via POST not in GDPR', function () {
const request = spec.buildRequests(bidReq, bidderReq);
const domainStart = bidderReq.refererInfo.referer.indexOf('//');
const domainEnd = bidderReq.refererInfo.referer.indexOf('/', domainStart + 2);
Expand All @@ -112,6 +146,16 @@ describe('Undertone Adapter', function () {
expect(request.url).to.equal(REQ_URL);
expect(request.method).to.equal('POST');
});
it('should send request to correct url via POST when in GDPR', function () {
const request = spec.buildRequests(bidReq, bidderReqGdpr);
const domainStart = bidderReq.refererInfo.referer.indexOf('//');
const domainEnd = bidderReq.refererInfo.referer.indexOf('/', domainStart + 2);
const domain = bidderReq.refererInfo.referer.substring(domainStart + 2, domainEnd);
let gdpr = bidderReqGdpr.gdprConsent.gdprApplies ? 1 : 0
const REQ_URL = `${URL}?pid=${bidReq[0].params.publisherId}&domain=${domain}&gdpr=${gdpr}&gdprstr=${bidderReqGdpr.gdprConsent.consentString}`;
expect(request.url).to.equal(REQ_URL);
expect(request.method).to.equal('POST');
});
it('should have all relevant fields', function () {
const request = spec.buildRequests(bidReq, bidderReq);
const bid1 = JSON.parse(request.data)['x-ut-hb-params'][0];
Expand All @@ -127,6 +171,14 @@ describe('Undertone Adapter', function () {
expect(bid2.publisherId).to.equal(12345);
expect(bid2.params).to.be.an('object');
});
it('should send all userIds data to server', function () {
const request = spec.buildRequests(bidReqUserIds, bidderReq);
const bidCommons = JSON.parse(request.data)['commons'];
expect(bidCommons).to.be.an('object');
expect(bidCommons.uids).to.be.an('object');
expect(bidCommons.uids.tdid).to.equal('123456');
expect(bidCommons.uids.digitrustid.data.id).to.equal('DTID');
});
});

describe('interpretResponse', function () {
Expand Down Expand Up @@ -160,25 +212,70 @@ describe('Undertone Adapter', function () {
});

describe('getUserSyncs', () => {
it('verifies gdpr consent checked', () => {
const options = ({ iframeEnabled: true, pixelEnabled: true });
expect(spec.getUserSyncs(options, {}, { gdprApplies: true }).length).to.equal(0);
});

it('Verifies sync iframe option', function () {
const result = spec.getUserSyncs({ iframeEnabled: true, pixelEnabled: true });
expect(result).to.have.lengthOf(1);
expect(result[0].type).to.equal('iframe');
expect(result[0].url).to.equal('//cdn.undertone.com/js/usersync.html');
});
let testParams = [
{
name: 'with iframe and no gdpr data',
arguments: [{ iframeEnabled: true, pixelEnabled: true }, {}, null],
expect: {
type: 'iframe',
pixels: ['//cdn.undertone.com/js/usersync.html']
}
},
{
name: 'with iframe and gdpr on',
arguments: [{ iframeEnabled: true, pixelEnabled: true }, {}, {gdprApplies: true, consentString: '234234'}],
expect: {
type: 'iframe',
pixels: ['//cdn.undertone.com/js/usersync.html?gdpr=1&gdprstr=234234']
}
},
{
name: 'with iframe and no gdpr off',
arguments: [{ iframeEnabled: true, pixelEnabled: true }, {}, {gdprApplies: false}],
expect: {
type: 'iframe',
pixels: ['//cdn.undertone.com/js/usersync.html?gdpr=0&gdprstr=']
}
},
{
name: 'with pixels and no gdpr data',
arguments: [{ pixelEnabled: true }, {}, null],
expect: {
type: 'image',
pixels: ['//usr.undertone.com/userPixel/syncOne?id=1&of=2',
'//usr.undertone.com/userPixel/syncOne?id=2&of=2']
}
},
{
name: 'with pixels and gdpr on',
arguments: [{ pixelEnabled: true }, {}, {gdprApplies: true, consentString: '234234'}],
expect: {
type: 'image',
pixels: ['//usr.undertone.com/userPixel/syncOne?id=1&of=2&gdpr=1&gdprstr=234234',
'//usr.undertone.com/userPixel/syncOne?id=2&of=2&gdpr=1&gdprstr=234234']
}
},
{
name: 'with pixels and gdpr off',
arguments: [{ pixelEnabled: true }, {}, {gdprApplies: false}],
expect: {
type: 'image',
pixels: ['//usr.undertone.com/userPixel/syncOne?id=1&of=2&gdpr=0&gdprstr=',
'//usr.undertone.com/userPixel/syncOne?id=2&of=2&gdpr=0&gdprstr=']
}
}
];

it('Verifies sync image option', function () {
const result = spec.getUserSyncs({ pixelEnabled: true });
expect(result).to.have.lengthOf(2);
expect(result[0].type).to.equal('image');
expect(result[0].url).to.equal('//usr.undertone.com/userPixel/syncOne?id=1&of=2');
expect(result[1].type).to.equal('image');
expect(result[1].url).to.equal('//usr.undertone.com/userPixel/syncOne?id=2&of=2');
});
for (let i = 0; i < testParams.length; i++) {
let currParams = testParams[i];
it(currParams.name, function () {
const result = spec.getUserSyncs.apply(this, currParams.arguments);
expect(result).to.have.lengthOf(currParams.expect.pixels.length);
for (let ix = 0; ix < currParams.expect.pixels.length; ix++) {
expect(result[ix].url).to.equal(currParams.expect.pixels[ix]);
expect(result[ix].type).to.equal(currParams.expect.type);
}
});
}
});
});