diff --git a/integrationExamples/gpt/hello_world_emoteev.html b/integrationExamples/gpt/hello_world_emoteev.html
index 5a33e2d9701..f41ef308332 100644
--- a/integrationExamples/gpt/hello_world_emoteev.html
+++ b/integrationExamples/gpt/hello_world_emoteev.html
@@ -5,68 +5,99 @@
@@ -75,9 +106,9 @@
Basic Prebid.js Example
Div-1
diff --git a/modules/emoteevBidAdapter.js b/modules/emoteevBidAdapter.js
index 4436d39bb70..db84b6ea36d 100644
--- a/modules/emoteevBidAdapter.js
+++ b/modules/emoteevBidAdapter.js
@@ -22,11 +22,12 @@ import {
contains,
deepAccess,
isArray,
- getParameterByName
+ isInteger,
+ getParameterByName,
+ getCookie
} from '../src/utils';
import {config} from '../src/config';
import * as url from '../src/url';
-import {getCookie} from './pubCommonId';
export const BIDDER_CODE = 'emoteev';
@@ -60,6 +61,19 @@ export const ON_ADAPTER_CALLED = 'on_adapter_called';
export const ON_BID_WON = 'on_bid_won';
export const ON_BIDDER_TIMEOUT = 'on_bidder_timeout';
+export const IN_CONTENT = 'content';
+export const FOOTER = 'footer';
+export const OVERLAY = 'overlay';
+export const WALLPAPER = 'wallpaper';
+
+/**
+ * Vendor ID assigned to Emoteev from the Global Vendor & CMP List.
+ *
+ * See https://vendorlist.consensu.org/vendorinfo.json for more information.
+ * @type {number}
+ */
+export const VENDOR_ID = 15;
+
/**
* Pure function. See http://prebid.org/dev-docs/bidder-adaptor.html#valid-build-requests-array for detailed semantic.
*
@@ -71,6 +85,8 @@ export const isBidRequestValid = (bidRequest) => {
bidRequest &&
bidRequest.params &&
deepAccess(bidRequest, 'params.adSpaceId') &&
+ validateContext(deepAccess(bidRequest, 'params.context')) &&
+ validateExternalId(deepAccess(bidRequest, 'params.externalId')) &&
bidRequest.bidder === BIDDER_CODE &&
validateSizes(deepAccess(bidRequest, 'mediaTypes.banner.sizes')));
};
@@ -89,7 +105,7 @@ export const buildRequests = (env, debug, currency, validBidRequests, bidderRequ
return {
method: 'POST',
url: bidderUrl(env),
- data: JSON.stringify(requestsPayload(debug, currency, validBidRequests, bidderRequest))
+ data: JSON.stringify(requestsPayload(debug, currency, validBidRequests, bidderRequest)) // Keys with undefined values will be filtered out.
};
};
@@ -264,7 +280,23 @@ export const userSyncImageUrl = env => url.format({
* @param {Array>} sizes
* @returns {boolean} are sizes valid?
*/
-const validateSizes = sizes => isArray(sizes) && sizes.some(size => isArray(size) && size.length === 2);
+export const validateSizes = sizes => isArray(sizes) && sizes.length > 0 && sizes.every(size => isArray(size) && size.length === 2);
+
+/**
+ * Pure function.
+ *
+ * @param {string} context
+ * @returns {boolean} is param `context` valid?
+ */
+export const validateContext = context => contains([IN_CONTENT, FOOTER, OVERLAY, WALLPAPER], context);
+
+/**
+ * Pure function.
+ *
+ * @param {(number|null|undefined)} externalId
+ * @returns {boolean} is param `externalId` valid?
+ */
+export const validateExternalId = externalId => externalId === undefined || externalId === null || (isInteger(externalId) && externalId > 0);
/**
* Pure function.
@@ -282,6 +314,14 @@ export const conformBidRequest = bidRequest => {
};
};
+/**
+ * Pure function.
+ *
+ * @param {object} bidderRequest
+ * @returns {(boolean|undefined)} raw consent data.
+ */
+export const gdprConsent = (bidderRequest) => (deepAccess(bidderRequest, 'gdprConsent.vendorData.vendorConsents') || {})[VENDOR_ID];
+
/**
* Pure function.
*
@@ -306,7 +346,7 @@ export const requestsPayload = (debug, currency, validBidRequests, bidderRequest
isWebGLEnabled(document)),
userAgent: navigator.userAgent,
gdprApplies: deepAccess(bidderRequest, 'gdprConsent.gdprApplies'),
- gdprConsent: deepAccess(bidderRequest, 'gdprConsent.consentString'),
+ gdprConsent: gdprConsent(bidderRequest),
};
};
@@ -426,7 +466,7 @@ export const getDeviceInfo = (deviceDimensions, viewDimensions, documentDimensio
* Pure function
* @param {object} config pbjs config value
* @param {string} parameter Environment override from URL query param.
- * @returns One of [PRODUCTION, STAGING, DEVELOPMENT].
+ * @returns {string} One of [PRODUCTION, STAGING, DEVELOPMENT].
*/
export const resolveEnv = (config, parameter) => {
const configEnv = deepAccess(config, 'emoteev.env');
diff --git a/modules/emoteevBidAdapter.md b/modules/emoteevBidAdapter.md
index 88b0b21a96f..226a8374369 100644
--- a/modules/emoteevBidAdapter.md
+++ b/modules/emoteevBidAdapter.md
@@ -18,14 +18,16 @@ Module that connects to Emoteev's demand sources
code: 'test-div',
mediaTypes: {
banner: {
- sizes: [[300, 250]],
+ sizes: [[720, 90]],
}
},
bids: [
{
bidder: 'emoteev',
params: {
- adSpaceId: 5084
+ adSpaceId: 5084,
+ context: 'footer',
+ externalId: 42,
}
}
]
diff --git a/test/spec/modules/emoteevBidAdapter_spec.js b/test/spec/modules/emoteevBidAdapter_spec.js
index a5460ab939d..aa97b58ec38 100644
--- a/test/spec/modules/emoteevBidAdapter_spec.js
+++ b/test/spec/modules/emoteevBidAdapter_spec.js
@@ -15,20 +15,23 @@ import {
DEVELOPMENT,
EVENTS_PATH,
eventsUrl,
+ FOOTER,
+ gdprConsent,
getDeviceDimensions,
getDeviceInfo,
getDocumentDimensions,
getUserSyncs,
getViewDimensions,
+ IN_CONTENT,
interpretResponse,
isBidRequestValid,
- isWebGLEnabled,
ON_ADAPTER_CALLED,
ON_BID_WON,
ON_BIDDER_TIMEOUT,
onBidWon,
onAdapterCalled,
onTimeout,
+ OVERLAY,
PRODUCTION,
requestsPayload,
resolveDebug,
@@ -39,10 +42,14 @@ import {
USER_SYNC_IMAGE_PATH,
userSyncIframeUrl,
userSyncImageUrl,
+ validateSizes,
+ validateContext,
+ validateExternalId,
+ VENDOR_ID,
+ WALLPAPER,
} from 'modules/emoteevBidAdapter';
import * as url from '../../../src/url';
import * as utils from '../../../src/utils';
-import * as pubCommonId from '../../../modules/pubCommonId';
import {config} from '../../../src/config';
const cannedValidBidRequests = [{
@@ -53,7 +60,11 @@ const cannedValidBidRequests = [{
bidder: 'emoteev',
bidderRequestId: '1203b39fecc6a5',
crumbs: {pubcid: 'f3371d16-4e8b-42b5-a770-7e5be1fdf03d'},
- params: {adSpaceId: 5084},
+ params: {
+ adSpaceId: 5084,
+ context: IN_CONTENT,
+ externalId: 42
+ },
sizes: [[300, 250], [250, 300], [300, 600]],
transactionId: '58dbd732-7a39-45f1-b23e-1c24051a941c',
}];
@@ -74,7 +85,7 @@ const cannedBidderRequest = {
timeout: 3000,
gdprConsent: {
gdprApplies: true,
- consentString: 'my consentString'
+ vendorData: {vendorConsents: {[VENDOR_ID]: true}},
}
};
const serverResponse =
@@ -102,6 +113,8 @@ describe('emoteevBidAdapter', function () {
bidId: '23a45b4e3',
params: {
adSpaceId: 12345,
+ context: IN_CONTENT,
+ externalId: 42
},
mediaTypes: {
banner: {
@@ -120,6 +133,8 @@ describe('emoteevBidAdapter', function () {
bidder: '', // invalid bidder
params: {
adSpaceId: 12345,
+ context: IN_CONTENT,
+ externalId: 42
},
mediaTypes: {
banner: {
@@ -131,6 +146,21 @@ describe('emoteevBidAdapter', function () {
bidder: 'emoteev',
params: {
adSpaceId: '', // invalid adSpaceId
+ context: IN_CONTENT,
+ externalId: 42
+ },
+ mediaTypes: {
+ banner: {
+ sizes: [[750, 200]]
+ }
+ },
+ })).to.equal(false);
+ expect(isBidRequestValid({
+ bidder: 'emoteev',
+ params: {
+ adSpaceId: 12345,
+ context: 'something', // invalid context
+ externalId: 42
},
mediaTypes: {
banner: {
@@ -142,6 +172,21 @@ describe('emoteevBidAdapter', function () {
bidder: 'emoteev',
params: {
adSpaceId: 12345,
+ context: IN_CONTENT,
+ externalId: 'lol' // invalid externalId
+ },
+ mediaTypes: {
+ banner: {
+ sizes: [[750, 200]]
+ }
+ },
+ })).to.equal(false);
+ expect(isBidRequestValid({
+ bidder: 'emoteev',
+ params: {
+ adSpaceId: 12345,
+ context: IN_CONTENT,
+ externalId: 42
},
mediaTypes: {
banner: {
@@ -401,6 +446,39 @@ describe('emoteevBidAdapter', function () {
});
});
+ describe('gdprConsent', function () {
+ describe('gdpr applies, consent given', function () {
+ const bidderRequest = {
+ ...cannedBidderRequest,
+ gdprConsent: {
+ gdprApplies: true,
+ vendorData: {vendorConsents: {[VENDOR_ID]: true}},
+ }
+ };
+ expect(gdprConsent(bidderRequest)).to.deep.equal(true);
+ });
+ describe('gdpr applies, consent withdrawn', function () {
+ const bidderRequest = {
+ ...cannedBidderRequest,
+ gdprConsent: {
+ gdprApplies: true,
+ vendorData: {vendorConsents: {[VENDOR_ID]: false}},
+ }
+ };
+ expect(gdprConsent(bidderRequest)).to.deep.equal(false);
+ });
+ describe('gdpr applies, consent unknown', function () {
+ const bidderRequest = {
+ ...cannedBidderRequest,
+ gdprConsent: {
+ gdprApplies: true,
+ vendorData: {},
+ }
+ };
+ expect(gdprConsent(bidderRequest)).to.deep.equal(undefined);
+ });
+ });
+
describe('requestsPayload', function () {
const
currency = 'EUR',
@@ -418,7 +496,7 @@ describe('emoteevBidAdapter', function () {
'deviceInfo',
'userAgent',
'gdprApplies',
- 'gdprConsent'
+ 'gdprConsent',
);
expect(payload.bidRequests[0]).to.exist.and.have.all.keys(
@@ -449,7 +527,6 @@ describe('emoteevBidAdapter', function () {
);
expect(payload.userAgent).to.deep.equal(navigator.userAgent);
expect(payload.gdprApplies).to.deep.equal(cannedBidderRequest.gdprConsent.gdprApplies);
- expect(payload.gdprConsent).to.deep.equal(cannedBidderRequest.gdprConsent.consentString);
});
describe('getViewDimensions', function () {
@@ -665,7 +742,7 @@ describe('emoteevBidAdapter', function () {
let getParameterByNameSpy;
beforeEach(function () {
triggerPixelSpy = sinon.spy(utils, 'triggerPixel');
- getCookieSpy = sinon.spy(pubCommonId, 'getCookie');
+ getCookieSpy = sinon.spy(utils, 'getCookie');
getConfigSpy = sinon.spy(config, 'getConfig');
getParameterByNameSpy = sinon.spy(utils, 'getParameterByName');
});
@@ -692,7 +769,7 @@ describe('emoteevBidAdapter', function () {
};
spec.isBidRequestValid(validBidRequest);
sinon.assert.notCalled(utils.triggerPixel);
- sinon.assert.notCalled(pubCommonId.getCookie);
+ sinon.assert.notCalled(utils.getCookie);
sinon.assert.notCalled(config.getConfig);
sinon.assert.notCalled(utils.getParameterByName);
});
@@ -700,7 +777,7 @@ describe('emoteevBidAdapter', function () {
const invalidBidRequest = {};
spec.isBidRequestValid(invalidBidRequest);
sinon.assert.notCalled(utils.triggerPixel);
- sinon.assert.notCalled(pubCommonId.getCookie);
+ sinon.assert.notCalled(utils.getCookie);
sinon.assert.notCalled(config.getConfig);
sinon.assert.notCalled(utils.getParameterByName);
});
@@ -709,7 +786,7 @@ describe('emoteevBidAdapter', function () {
it('has intended side-effects', function () {
spec.buildRequests(cannedValidBidRequests, cannedBidderRequest);
sinon.assert.notCalled(utils.triggerPixel);
- sinon.assert.notCalled(pubCommonId.getCookie);
+ sinon.assert.notCalled(utils.getCookie);
sinon.assert.callCount(config.getConfig, 3);
sinon.assert.callCount(utils.getParameterByName, 2);
});
@@ -718,7 +795,7 @@ describe('emoteevBidAdapter', function () {
it('has intended side-effects', function () {
spec.interpretResponse(serverResponse);
sinon.assert.notCalled(utils.triggerPixel);
- sinon.assert.notCalled(pubCommonId.getCookie);
+ sinon.assert.notCalled(utils.getCookie);
sinon.assert.notCalled(config.getConfig);
sinon.assert.notCalled(utils.getParameterByName);
});
@@ -728,7 +805,7 @@ describe('emoteevBidAdapter', function () {
const bidObject = serverResponse.body[0];
spec.onBidWon(bidObject);
sinon.assert.calledOnce(utils.triggerPixel);
- sinon.assert.calledOnce(pubCommonId.getCookie);
+ sinon.assert.calledOnce(utils.getCookie);
sinon.assert.calledOnce(config.getConfig);
sinon.assert.calledOnce(utils.getParameterByName);
});
@@ -737,7 +814,7 @@ describe('emoteevBidAdapter', function () {
it('has intended side-effects', function () {
spec.onTimeout(cannedValidBidRequests[0]);
sinon.assert.calledOnce(utils.triggerPixel);
- sinon.assert.notCalled(pubCommonId.getCookie);
+ sinon.assert.notCalled(utils.getCookie);
sinon.assert.calledOnce(config.getConfig);
sinon.assert.calledOnce(utils.getParameterByName);
});
@@ -746,10 +823,44 @@ describe('emoteevBidAdapter', function () {
it('has intended side-effects', function () {
spec.getUserSyncs({});
sinon.assert.notCalled(utils.triggerPixel);
- sinon.assert.notCalled(pubCommonId.getCookie);
+ sinon.assert.notCalled(utils.getCookie);
sinon.assert.calledOnce(config.getConfig);
sinon.assert.calledOnce(utils.getParameterByName);
});
});
});
+
+ describe('validateSizes', function () {
+ it('only accepts valid array of sizes', function () {
+ expect(validateSizes([])).to.deep.equal(false);
+ expect(validateSizes([[]])).to.deep.equal(false);
+ expect(validateSizes([[450, 450], undefined])).to.deep.equal(false);
+ expect(validateSizes([[450, 450], 'size'])).to.deep.equal(false);
+ expect(validateSizes([[1, 1]])).to.deep.equal(true);
+ expect(validateSizes([[1, 1], [450, 450]])).to.deep.equal(true);
+ });
+ });
+
+ describe('validateContext', function () {
+ it('only accepts valid context', function () {
+ expect(validateContext(IN_CONTENT)).to.deep.equal(true);
+ expect(validateContext(FOOTER)).to.deep.equal(true);
+ expect(validateContext(OVERLAY)).to.deep.equal(true);
+ expect(validateContext(WALLPAPER)).to.deep.equal(true);
+ expect(validateContext(null)).to.deep.equal(false);
+ expect(validateContext('anything else')).to.deep.equal(false);
+ });
+ });
+
+ describe('validateExternalId', function () {
+ it('only accepts a positive integer or null', function () {
+ expect(validateExternalId(0)).to.deep.equal(false);
+ expect(validateExternalId(42)).to.deep.equal(true);
+ expect(validateExternalId(42.0)).to.deep.equal(true); // edge case: valid externalId
+ expect(validateExternalId(3.14159)).to.deep.equal(false);
+ expect(validateExternalId('externalId')).to.deep.equal(false);
+ expect(validateExternalId(undefined)).to.deep.equal(true);
+ expect(validateExternalId(null)).to.deep.equal(true);
+ });
+ });
});