From b0852982ffaf06b7be5215a5e0d65623d5cb6a6c Mon Sep 17 00:00:00 2001 From: Erik Omlid Date: Wed, 26 Mar 2025 01:32:10 -0500 Subject: [PATCH 1/3] newspassidBidAdapter refactor --- modules/newspassid.md | 76 - modules/newspassidBidAdapter.js | 812 ++----- modules/newspassidBidAdapter.md | 40 + .../spec/modules/newspassidBidAdapter_spec.js | 2050 +++-------------- 4 files changed, 471 insertions(+), 2507 deletions(-) delete mode 100644 modules/newspassid.md create mode 100644 modules/newspassidBidAdapter.md diff --git a/modules/newspassid.md b/modules/newspassid.md deleted file mode 100644 index 6fa709e5ba6..00000000000 --- a/modules/newspassid.md +++ /dev/null @@ -1,76 +0,0 @@ ---- -Module Name: NewspassId Bidder Adapter -Module Type: Bidder Adapter -Maintainer: techsupport@newspassid.com -layout: bidder -title: Newspass ID -description: LMC Newspass ID Prebid JS Bidder Adapter -biddercode: newspassid -gdpr_supported: false -gvl_id: none -usp_supported: true -coppa_supported: false -schain_supported: true -dchain_supported: false -userIds: criteo, id5Id, tdid, identityLink, liveIntentId, parrableId, pubCommonId, lotamePanoramaId, sharedId, fabrickId -media_types: banner -safeframes_ok: true -deals_supported: true -floors_supported: false -fpd_supported: false -pbjs: true -pbs: false -prebid_member: false -multiformat_supported: will-bid-on-any ---- - -### Description - -LMC Newspass ID Prebid JS Bidder Adapter that connects to the NewspassId demand source(s). - -The Newspass bid adapter supports Banner mediaTypes ONLY. -This is intended for USA audiences only, and does not support GDPR - - -### Bid Params - -{: .table .table-bordered .table-striped } - -| Name | Scope | Description | Example | Type | -|-----------|----------|---------------------------|------------|----------| -| `siteId` | required | The site ID. | `"NPID0000001"` | `string` | -| `publisherId` | required | The publisher ID. | `"4204204201"` | `string` | -| `placementId` | required | The placement ID. | `"0420420421"` | `string` | -| `customData` | optional | publisher key-values used for targeting | `[{"settings":{},"targeting":{"key1": "value1", "key2": "value2"}}], ` | `array` | - -### Test Parameters - - -A test ad unit that will consistently return test creatives: - -``` - -//Banner adUnit - -adUnits = [{ - code: 'id-of-your-banner-div', - mediaTypes: { - banner: { - sizes: [[300, 250], [300,600]] - } - }, - bids: [{ - bidder: 'newspassid', - params: { - publisherId: 'NEWSPASS0001', /* an ID to identify the publisher account - required */ - siteId: '4204204201', /* An ID used to identify a site within a publisher account - required */ - placementId: '8000000015', /* an ID used to identify the piece of inventory - required - for appnexus test use 13144370. */ - customData: [{"settings": {}, "targeting": {"key": "value", "key2": ["value1", "value2"]}}],/* optional array with 'targeting' placeholder for passing publisher specific key-values for targeting. */ - } - }] - }]; -``` - -### Note: - -Please contact us at techsupport@newspassid.com for any assistance testing your implementation before going live into production. diff --git a/modules/newspassidBidAdapter.js b/modules/newspassidBidAdapter.js index d33b4e64297..170eb846da1 100644 --- a/modules/newspassidBidAdapter.js +++ b/modules/newspassidBidAdapter.js @@ -1,674 +1,164 @@ -import { - deepClone, - logInfo, - logError, - deepAccess, - logWarn, - deepSetValue, - isArray, - contains, - parseUrl, - generateUUID -} from '../src/utils.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { config } from '../src/config.js'; +import { deepSetValue } from '../src/utils.js'; +import { hasPurpose1Consent } from '../src/utils/gdpr.js'; +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER, NATIVE } from '../src/mediaTypes.js'; -import {config} from '../src/config.js'; -import {getPriceBucketString} from '../src/cpmBucketManager.js'; -import {getRefererInfo} from '../src/refererDetection.js'; + +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory.js').BidderSpec} BidderSpec + */ + const BIDDER_CODE = 'newspassid'; -const ORIGIN = 'https://bidder.newspassid.com' // applies only to auction & cookie -const AUCTIONURI = '/openrtb2/auction'; -const NEWSPASSCOOKIESYNC = '/static/load-cookie.html'; -const NEWSPASSVERSION = '1.1.4'; +const DEFAULT_CURRENCY = 'USD'; +const DEFAULT_NET_REVENUE = true; +const DEFAULT_TTL = 300; +const ENDPOINT_URL = 'https://npid.amspbs.com/v0/bid/request'; +const GVL_ID = 1317; +const SYNC_URL = 'https://npid.amspbs.com/v0/user/sync'; + +const converter = ortbConverter({ + imp(buildImp, bidRequest, context) { + const imp = buildImp(bidRequest, context); + deepSetValue(imp, 'ext.newspassid', { + accountId: resolveNewpassidAccountId(bidRequest), + groupId: bidRequest.params.groupId, + }) + return imp; + }, + context: { + ttl: DEFAULT_TTL, + netRevenue: DEFAULT_NET_REVENUE + } +}); + +/** + * Helper function to add params to url + * @param {string} url + * @param {object} params + * @returns {string} + */ +const addParamsToUrl = (url, params) => { + const urlObj = new URL(url); + Object.entries(params).forEach(([key, value]) => { + urlObj.searchParams.set(key, value); + }); + return urlObj.toString(); +}; + +/** + * Get the global accountId for the newspassid bidder + * @returns {string|null} + */ +const getGlobalAccountIdOrNull = () => { + const globalAccountId = config.getConfig('newspassid.accountId'); + if (globalAccountId) return globalAccountId; + return null; +}; + +/** + * Resolve the accountId for the newspassid bidder + * @param {BidRequest|undefined} bidRequest + * @returns {string|null} + */ +export const resolveNewpassidAccountId = (bidRequest) => { + if (typeof bidRequest !== 'object') return getGlobalAccountIdOrNull(); + + // get accountId from bidRequest params + const { params } = bidRequest; + if (params?.accountId) return params?.accountId; + + return getGlobalAccountIdOrNull(); +}; + +/** + * @type {BidderSpec} + */ export const spec = { - version: NEWSPASSVERSION, code: BIDDER_CODE, - supportedMediaTypes: [BANNER], - cookieSyncBag: {publisherId: null, siteId: null, userIdObject: {}}, // variables we want to make available to cookie sync - propertyBag: {config: null, pageId: null, buildRequestsStart: 0, buildRequestsEnd: 0, endpointOverride: null}, /* allow us to store vars in instance scope - needs to be an object to be mutable */ - config_defaults: { - 'logId': 'NEWSPASSID', - 'bidder': 'newspassid', - 'auctionUrl': ORIGIN + AUCTIONURI, - 'cookieSyncUrl': ORIGIN + NEWSPASSCOOKIESYNC - }, - loadConfiguredData(bid) { - if (this.propertyBag.config) { return; } - this.propertyBag.config = deepClone(this.config_defaults); - let bidder = bid.bidder || 'newspassid'; - this.propertyBag.config.logId = bidder.toUpperCase(); - this.propertyBag.config.bidder = bidder; - let bidderConfig = config.getConfig(bidder) || {}; - logInfo('got bidderConfig: ', deepClone(bidderConfig)); - let arrGetParams = this.getGetParametersAsObject(); - if (bidderConfig.endpointOverride) { - if (bidderConfig.endpointOverride.origin) { - this.propertyBag.endpointOverride = bidderConfig.endpointOverride.origin; - this.propertyBag.config.auctionUrl = bidderConfig.endpointOverride.origin + AUCTIONURI; - this.propertyBag.config.cookieSyncUrl = bidderConfig.endpointOverride.origin + NEWSPASSCOOKIESYNC; - } - if (bidderConfig.endpointOverride.cookieSyncUrl) { - this.propertyBag.config.cookieSyncUrl = bidderConfig.endpointOverride.cookieSyncUrl; - } - if (bidderConfig.endpointOverride.auctionUrl) { - this.propertyBag.endpointOverride = bidderConfig.endpointOverride.auctionUrl; - this.propertyBag.config.auctionUrl = bidderConfig.endpointOverride.auctionUrl; - } - } - try { - if (arrGetParams.hasOwnProperty('auction')) { - logInfo('GET: setting auction endpoint to: ' + arrGetParams.auction); - this.propertyBag.config.auctionUrl = arrGetParams.auction; - } - if (arrGetParams.hasOwnProperty('cookiesync')) { - logInfo('GET: setting cookiesync to: ' + arrGetParams.cookiesync); - this.propertyBag.config.cookieSyncUrl = arrGetParams.cookiesync; - } - } catch (e) {} - logInfo('set propertyBag.config to', this.propertyBag.config); - }, - getAuctionUrl() { - return this.propertyBag.config.auctionUrl; - }, - getCookieSyncUrl() { - return this.propertyBag.config.cookieSyncUrl; - }, - isBidRequestValid(bid) { - this.loadConfiguredData(bid); - logInfo('isBidRequestValid : ', config.getConfig(), bid); - let adUnitCode = bid.adUnitCode; // adunit[n].code - let err1 = 'VALIDATION FAILED : missing {param} : siteId, placementId and publisherId are REQUIRED'; - if (!(bid.params.hasOwnProperty('placementId'))) { - logError(err1.replace('{param}', 'placementId'), adUnitCode); - return false; - } - if (!this.isValidPlacementId(bid.params.placementId)) { - logError('VALIDATION FAILED : placementId must be exactly 10 numeric characters', adUnitCode); - return false; - } - if (!(bid.params.hasOwnProperty('publisherId'))) { - logError(err1.replace('{param}', 'publisherId'), adUnitCode); - return false; - } - if (!(bid.params.publisherId).toString().match(/^[a-zA-Z0-9\-]{12}$/)) { - logError('VALIDATION FAILED : publisherId must be exactly 12 alphanumeric characters including hyphens', adUnitCode); - return false; - } - if (!(bid.params.hasOwnProperty('siteId'))) { - logError(err1.replace('{param}', 'siteId'), adUnitCode); - return false; - } - if (!(bid.params.siteId).toString().match(/^[0-9]{10}$/)) { - logError('VALIDATION FAILED : siteId must be exactly 10 numeric characters', adUnitCode); - return false; - } - if (bid.params.hasOwnProperty('customParams')) { - logError('VALIDATION FAILED : customParams should be renamed to customData', adUnitCode); - return false; - } - if (bid.params.hasOwnProperty('customData')) { - if (!Array.isArray(bid.params.customData)) { - logError('VALIDATION FAILED : customData is not an Array', adUnitCode); - return false; - } - if (bid.params.customData.length < 1) { - logError('VALIDATION FAILED : customData is an array but does not contain any elements', adUnitCode); - return false; - } - if (!(bid.params.customData[0]).hasOwnProperty('targeting')) { - logError('VALIDATION FAILED : customData[0] does not contain "targeting"', adUnitCode); - return false; - } - if (typeof bid.params.customData[0]['targeting'] != 'object') { - logError('VALIDATION FAILED : customData[0] targeting is not an object', adUnitCode); - return false; - } - } - return true; - }, - isValidPlacementId(placementId) { - return placementId.toString().match(/^[0-9]{10}$/); - }, - buildRequests(validBidRequests, bidderRequest) { - this.loadConfiguredData(validBidRequests[0]); - this.propertyBag.buildRequestsStart = new Date().getTime(); - logInfo(`buildRequests time: ${this.propertyBag.buildRequestsStart} v ${NEWSPASSVERSION} validBidRequests`, deepClone(validBidRequests), 'bidderRequest', deepClone(bidderRequest)); - if (this.blockTheRequest()) { - return []; - } - let htmlParams = {'publisherId': '', 'siteId': ''}; - if (validBidRequests.length > 0) { - this.cookieSyncBag.userIdObject = Object.assign(this.cookieSyncBag.userIdObject, this.findAllUserIds(validBidRequests[0])); - this.cookieSyncBag.siteId = deepAccess(validBidRequests[0], 'params.siteId'); - this.cookieSyncBag.publisherId = deepAccess(validBidRequests[0], 'params.publisherId'); - htmlParams = validBidRequests[0].params; - } - logInfo('cookie sync bag', this.cookieSyncBag); - let singleRequest = config.getConfig('newspassid.singleRequest'); - singleRequest = singleRequest !== false; // undefined & true will be true - logInfo(`config newspassid.singleRequest : `, singleRequest); - let npRequest = {}; // we only want to set specific properties on this, not validBidRequests[0].params - logInfo('going to get ortb2 from bidder request...'); - let fpd = deepAccess(bidderRequest, 'ortb2', null); - logInfo('got fpd: ', fpd); - if (fpd && deepAccess(fpd, 'user')) { - logInfo('added FPD user object'); - npRequest.user = fpd.user; - } - const getParams = this.getGetParametersAsObject(); - const isTestMode = getParams['nptestmode'] || null; // this can be any string, it's used for testing ads - npRequest.device = {'w': window.innerWidth, 'h': window.innerHeight}; - let placementIdOverrideFromGetParam = this.getPlacementIdOverrideFromGetParam(); // null or string - let schain = null; - let tosendtags = validBidRequests.map(npBidRequest => { - var obj = {}; - let placementId = placementIdOverrideFromGetParam || this.getPlacementId(npBidRequest); // prefer to use a valid override param, else the bidRequest placement Id - obj.id = npBidRequest.bidId; // this causes an error if we change it to something else, even if you update the bidRequest object: "WARNING: Bidder newspass made bid for unknown request ID: mb7953.859498327448. Ignoring." - obj.tagid = placementId; - let parsed = parseUrl(this.getRefererInfo().page); - obj.secure = parsed.protocol === 'https' ? 1 : 0; - let arrBannerSizes = []; - if (!npBidRequest.hasOwnProperty('mediaTypes')) { - if (npBidRequest.hasOwnProperty('sizes')) { - logInfo('no mediaTypes detected - will use the sizes array in the config root'); - arrBannerSizes = npBidRequest.sizes; - } else { - logInfo('Cannot set sizes for banner type'); - } - } else { - if (npBidRequest.mediaTypes.hasOwnProperty(BANNER)) { - arrBannerSizes = npBidRequest.mediaTypes[BANNER].sizes; /* Note - if there is a sizes element in the config root it will be pushed into here */ - logInfo('setting banner size from the mediaTypes.banner element for bidId ' + obj.id + ': ', arrBannerSizes); - } - if (npBidRequest.mediaTypes.hasOwnProperty(NATIVE)) { - obj.native = npBidRequest.mediaTypes[NATIVE]; - logInfo('setting native object from the mediaTypes.native element: ' + obj.id + ':', obj.native); - } - } - if (arrBannerSizes.length > 0) { - obj.banner = { - topframe: 1, - w: arrBannerSizes[0][0] || 0, - h: arrBannerSizes[0][1] || 0, - format: arrBannerSizes.map(s => { - return {w: s[0], h: s[1]}; - }) - }; - } - obj.placementId = placementId; - deepSetValue(obj, 'ext.prebid', {'storedrequest': {'id': placementId}}); - obj.ext['newspassid'] = {}; - obj.ext['newspassid'].adUnitCode = npBidRequest.adUnitCode; // eg. 'mpu' - if (npBidRequest.params.hasOwnProperty('customData')) { - obj.ext['newspassid'].customData = npBidRequest.params.customData; - } - logInfo(`obj.ext.newspassid is `, obj.ext['newspassid']); - if (isTestMode != null) { - logInfo('setting isTestMode to ', isTestMode); - if (obj.ext['newspassid'].hasOwnProperty('customData')) { - for (let i = 0; i < obj.ext['newspassid'].customData.length; i++) { - obj.ext['newspassid'].customData[i]['targeting']['nptestmode'] = isTestMode; - } - } else { - obj.ext['newspassid'].customData = [{'settings': {}, 'targeting': {}}]; - obj.ext['newspassid'].customData[0].targeting['nptestmode'] = isTestMode; + gvlid: GVL_ID, + supportedMediaTypes: [BANNER, NATIVE, VIDEO], + + isBidRequestValid: function(bid) { + const accountId = resolveNewpassidAccountId(bid); + return !!(bid.params && accountId && bid.params.groupId); + }, + + buildRequests: function(bidRequests, bidderRequest) { + // convert to ortb using the converter utility + const data = converter.toORTB({ bidRequests, bidderRequest }); + + return [ + { + method: 'POST', + url: ENDPOINT_URL, + data: data, + options: { + withCredentials: true } } - if (fpd && deepAccess(fpd, 'site')) { - logInfo('adding fpd.site'); - if (deepAccess(obj, 'ext.newspassid.customData.0.targeting', false)) { - obj.ext.newspassid.customData[0].targeting = Object.assign(obj.ext.newspassid.customData[0].targeting, fpd.site); - } else { - deepSetValue(obj, 'ext.newspassid.customData.0.targeting', fpd.site); + ]; + }, + + interpretResponse: function(serverResponse, bidRequest) { + const response = serverResponse.body; + const bidResponses = []; + + if (!response || !response.seatbid || !response.seatbid[0].bid) { + return bidResponses; + } + + response.seatbid[0].bid.forEach(bid => { + bidResponses.push({ + requestId: bid.impid, + cpm: bid.price, + width: bid.w, + height: bid.h, + creativeId: bid.crid || bid.id, + currency: response.cur || DEFAULT_CURRENCY, + netRevenue: true, + ttl: DEFAULT_TTL, + ad: bid.adm, + meta: { + advertiserDomains: bid.adomain || [] } - } - if (!schain && deepAccess(npBidRequest, 'schain')) { - schain = npBidRequest.schain; - } - let gpid = deepAccess(npBidRequest, 'ortb2Imp.ext.gpid'); - if (gpid) { - deepSetValue(obj, 'ext.gpid', gpid); - } - return obj; + }); }); - let extObj = {}; - extObj['newspassid'] = {}; - extObj['newspassid']['np_pb_v'] = NEWSPASSVERSION; - extObj['newspassid']['np_rw'] = placementIdOverrideFromGetParam ? 1 : 0; - if (validBidRequests.length > 0) { - let userIds = this.cookieSyncBag.userIdObject; // 2021-01-06 - slight optimisation - we've already found this info - if (userIds.hasOwnProperty('pubcid')) { - extObj['newspassid'].pubcid = userIds.pubcid; - } - } - extObj['newspassid'].pv = this.getPageId(); // attach the page ID that will be common to all auction calls for this page if refresh() is called - let whitelistAdserverKeys = config.getConfig('newspassid.np_whitelist_adserver_keys'); - let useWhitelistAdserverKeys = isArray(whitelistAdserverKeys) && whitelistAdserverKeys.length > 0; - extObj['newspassid']['np_kvp_rw'] = useWhitelistAdserverKeys ? 1 : 0; - if (getParams.hasOwnProperty('npf')) { extObj['newspassid']['npf'] = getParams.npf === 'true' || getParams.npf === '1' ? 1 : 0; } - if (getParams.hasOwnProperty('nppf')) { extObj['newspassid']['nppf'] = getParams.nppf === 'true' || getParams.nppf === '1' ? 1 : 0; } - if (getParams.hasOwnProperty('nprp') && getParams.nprp.match(/^[0-3]$/)) { extObj['newspassid']['nprp'] = parseInt(getParams.nprp); } - if (getParams.hasOwnProperty('npip') && getParams.npip.match(/^\d+$/)) { extObj['newspassid']['npip'] = parseInt(getParams.npip); } - if (this.propertyBag.endpointOverride != null) { extObj['newspassid']['origin'] = this.propertyBag.endpointOverride; } - let userExtEids = deepAccess(validBidRequests, '0.userIdAsEids', []); // generate the UserIDs in the correct format for UserId module - npRequest.site = { - 'publisher': {'id': htmlParams.publisherId}, - 'page': this.getRefererInfo().page, - 'id': htmlParams.siteId + + return bidResponses; + }, + + getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent, gppConsent) { + if (!syncOptions.iframeEnabled) return []; // disable if iframe sync is disabled + if (!hasPurpose1Consent(gdprConsent)) return []; // disable if no purpose1 consent + if (config.getConfig('coppa') === true) return []; // disable syncs for coppa + + const params = { + gdpr: gdprConsent?.gdprApplies ? 1 : 0, + gdpr_consent: gdprConsent?.gdprApplies + ? encodeURIComponent(gdprConsent?.consentString || '') + : '', + gpp: encodeURIComponent(gppConsent?.gppString || ''), + gpp_sid: encodeURIComponent(gppConsent?.applicableSections || ''), + us_privacy: encodeURIComponent(uspConsent || ''), }; - npRequest.test = config.getConfig('debug') ? 1 : 0; - if (bidderRequest && bidderRequest.uspConsent) { - logInfo('ADDING USP consent info'); - deepSetValue(npRequest, 'regs.ext.us_privacy', bidderRequest.uspConsent); - } else { - logInfo('WILL NOT ADD USP consent info; no bidderRequest.uspConsent.'); - } - if (schain) { // we set this while iterating over the bids - logInfo('schain found'); - deepSetValue(npRequest, 'source.ext.schain', schain); - } - if (config.getConfig('coppa') === true) { - deepSetValue(npRequest, 'regs.coppa', 1); - } - if (singleRequest) { - logInfo('buildRequests starting to generate response for a single request'); - npRequest.id = generateUUID(); // Unique ID of the bid request, provided by the exchange. (REQUIRED) - npRequest.imp = tosendtags; - npRequest.ext = extObj; - deepSetValue(npRequest, 'user.ext.eids', userExtEids); - var ret = { - method: 'POST', - url: this.getAuctionUrl(), - data: JSON.stringify(npRequest), - bidderRequest: bidderRequest - }; - logInfo('buildRequests request data for single = ', deepClone(npRequest)); - this.propertyBag.buildRequestsEnd = new Date().getTime(); - logInfo(`buildRequests going to return for single at time ${this.propertyBag.buildRequestsEnd} (took ${this.propertyBag.buildRequestsEnd - this.propertyBag.buildRequestsStart}ms): `, ret); - return ret; - } - let arrRet = tosendtags.map(imp => { - logInfo('buildRequests starting to generate non-single response, working on imp : ', imp); - let npRequestSingle = Object.assign({}, npRequest); - npRequestSingle.id = generateUUID(); - npRequestSingle.imp = [imp]; - npRequestSingle.ext = extObj; - deepSetValue(npRequestSingle, 'user.ext.eids', userExtEids); - logInfo('buildRequests RequestSingle (for non-single) = ', npRequestSingle); - return { - method: 'POST', - url: this.getAuctionUrl(), - data: JSON.stringify(npRequestSingle), - bidderRequest: bidderRequest - }; + + const globalAccountId = resolveNewpassidAccountId({}); + if (globalAccountId) { + params.account = globalAccountId; + } + + let syncs = []; + + // iframe sync + syncs.push({ + type: 'iframe', + url: addParamsToUrl(SYNC_URL, params), }); - this.propertyBag.buildRequestsEnd = new Date().getTime(); - logInfo(`buildRequests going to return for non-single at time ${this.propertyBag.buildRequestsEnd} (took ${this.propertyBag.buildRequestsEnd - this.propertyBag.buildRequestsStart}ms): `, arrRet); - return arrRet; - }, - interpretResponse(serverResponse, request) { - if (request && request.bidderRequest && request.bidderRequest.bids) { this.loadConfiguredData(request.bidderRequest.bids[0]); } - let startTime = new Date().getTime(); - logInfo(`interpretResponse time: ${startTime}. buildRequests done -> interpretResponse start was ${startTime - this.propertyBag.buildRequestsEnd}ms`); - logInfo(`serverResponse, request`, deepClone(serverResponse), deepClone(request)); - serverResponse = serverResponse.body || {}; - let aucId = serverResponse.id; // this will be correct for single requests and non-single - if (!serverResponse.hasOwnProperty('seatbid')) { - return []; - } - if (typeof serverResponse.seatbid !== 'object') { - return []; - } - let arrAllBids = []; - let enhancedAdserverTargeting = config.getConfig('newspassid.enhancedAdserverTargeting'); - logInfo('enhancedAdserverTargeting', enhancedAdserverTargeting); - if (typeof enhancedAdserverTargeting == 'undefined') { - enhancedAdserverTargeting = true; - } - logInfo('enhancedAdserverTargeting', enhancedAdserverTargeting); - serverResponse.seatbid = injectAdIdsIntoAllBidResponses(serverResponse.seatbid); // we now make sure that each bid in the bidresponse has a unique (within page) adId attribute. - serverResponse.seatbid = this.removeSingleBidderMultipleBids(serverResponse.seatbid); - let whitelistAdserverKeys = config.getConfig('newspassid.np_whitelist_adserver_keys'); - let useWhitelistAdserverKeys = isArray(whitelistAdserverKeys) && whitelistAdserverKeys.length > 0; - for (let i = 0; i < serverResponse.seatbid.length; i++) { - let sb = serverResponse.seatbid[i]; - for (let j = 0; j < sb.bid.length; j++) { - let thisRequestBid = this.getBidRequestForBidId(sb.bid[j].impid, request.bidderRequest.bids); - logInfo(`seatbid:${i}, bid:${j} Going to set default w h for seatbid/bidRequest`, sb.bid[j], thisRequestBid); - const {defaultWidth, defaultHeight} = defaultSize(thisRequestBid); - let thisBid = this.addStandardProperties(sb.bid[j], defaultWidth, defaultHeight); - thisBid.meta = {advertiserDomains: thisBid.adomain || []}; - let bidType = deepAccess(thisBid, 'ext.prebid.type'); - logInfo(`this bid type is : ${bidType}`, j); - let adserverTargeting = {}; - if (enhancedAdserverTargeting) { - let allBidsForThisBidid = this.getAllBidsForBidId(thisBid.bidId, serverResponse.seatbid); - logInfo('Going to iterate allBidsForThisBidId', allBidsForThisBidid); - Object.keys(allBidsForThisBidid).forEach((bidderName, index, ar2) => { - logInfo(`adding adserverTargeting for ${bidderName} for bidId ${thisBid.bidId}`); - adserverTargeting['np_' + bidderName] = bidderName; - adserverTargeting['np_' + bidderName + '_crid'] = String(allBidsForThisBidid[bidderName].crid); - adserverTargeting['np_' + bidderName + '_adv'] = String(allBidsForThisBidid[bidderName].adomain); - adserverTargeting['np_' + bidderName + '_adId'] = String(allBidsForThisBidid[bidderName].adId); - adserverTargeting['np_' + bidderName + '_pb_r'] = getRoundedBid(allBidsForThisBidid[bidderName].price, allBidsForThisBidid[bidderName].ext.prebid.type); - if (allBidsForThisBidid[bidderName].hasOwnProperty('dealid')) { - adserverTargeting['np_' + bidderName + '_dealid'] = String(allBidsForThisBidid[bidderName].dealid); - } - }); - } else { - logInfo(`newspassid.enhancedAdserverTargeting is set to false, no per-bid keys will be sent to adserver.`); - } - let {seat: winningSeat, bid: winningBid} = this.getWinnerForRequestBid(thisBid.bidId, serverResponse.seatbid); - adserverTargeting['np_auc_id'] = String(aucId); - adserverTargeting['np_winner'] = String(winningSeat); - adserverTargeting['np_bid'] = 'true'; - if (enhancedAdserverTargeting) { - adserverTargeting['np_imp_id'] = String(winningBid.impid); - adserverTargeting['np_pb_r'] = getRoundedBid(winningBid.price, bidType); - adserverTargeting['np_adId'] = String(winningBid.adId); - adserverTargeting['np_size'] = `${winningBid.width}x${winningBid.height}`; - } - if (useWhitelistAdserverKeys) { // delete any un-whitelisted keys - logInfo('Going to filter out adserver targeting keys not in the whitelist: ', whitelistAdserverKeys); - Object.keys(adserverTargeting).forEach(function(key) { if (whitelistAdserverKeys.indexOf(key) === -1) { delete adserverTargeting[key]; } }); - } - thisBid.adserverTargeting = adserverTargeting; - arrAllBids.push(thisBid); - } - } - let endTime = new Date().getTime(); - logInfo(`interpretResponse going to return at time ${endTime} (took ${endTime - startTime}ms) Time from buildRequests Start -> interpretRequests End = ${endTime - this.propertyBag.buildRequestsStart}ms`, arrAllBids); - return arrAllBids; - }, - removeSingleBidderMultipleBids(seatbid) { - var ret = []; - for (let i = 0; i < seatbid.length; i++) { - let sb = seatbid[i]; - var retSeatbid = {'seat': sb.seat, 'bid': []}; - var bidIds = []; - for (let j = 0; j < sb.bid.length; j++) { - var candidate = sb.bid[j]; - if (contains(bidIds, candidate.impid)) { - continue; // we've already fully assessed this impid, found the highest bid from this seat for it - } - bidIds.push(candidate.impid); - for (let k = j + 1; k < sb.bid.length; k++) { - if (sb.bid[k].impid === candidate.impid && sb.bid[k].price > candidate.price) { - candidate = sb.bid[k]; - } - } - retSeatbid.bid.push(candidate); - } - ret.push(retSeatbid); - } - return ret; - }, - getUserSyncs(optionsType, serverResponse, gdprConsent, usPrivacy) { - logInfo('getUserSyncs optionsType', optionsType, 'serverResponse', serverResponse, 'usPrivacy', usPrivacy, 'cookieSyncBag', this.cookieSyncBag); - if (!serverResponse || serverResponse.length === 0) { - return []; - } - if (optionsType.iframeEnabled) { - var arrQueryString = []; - if (config.getConfig('debug')) { - arrQueryString.push('pbjs_debug=true'); - } - arrQueryString.push('usp_consent=' + (usPrivacy || '')); - for (let keyname in this.cookieSyncBag.userIdObject) { - arrQueryString.push(keyname + '=' + this.cookieSyncBag.userIdObject[keyname]); - } - arrQueryString.push('publisherId=' + this.cookieSyncBag.publisherId); - arrQueryString.push('siteId=' + this.cookieSyncBag.siteId); - arrQueryString.push('cb=' + Date.now()); - arrQueryString.push('bidder=' + this.propertyBag.config.bidder); - var strQueryString = arrQueryString.join('&'); - if (strQueryString.length > 0) { - strQueryString = '?' + strQueryString; - } - logInfo('getUserSyncs going to return cookie sync url : ' + this.getCookieSyncUrl() + strQueryString); - return [{ - type: 'iframe', - url: this.getCookieSyncUrl() + strQueryString - }]; - } - }, - getBidRequestForBidId(bidId, arrBids) { - for (let i = 0; i < arrBids.length; i++) { - if (arrBids[i].bidId === bidId) { // bidId in the request comes back as impid in the seatbid bids - return arrBids[i]; - } - } - return null; - }, - findAllUserIds(bidRequest) { - var ret = {}; - let searchKeysSingle = ['pubcid', 'tdid', 'idl_env', 'criteoId', 'lotamePanoramaId', 'fabrickId']; - if (bidRequest.hasOwnProperty('userId')) { - for (let arrayId in searchKeysSingle) { - let key = searchKeysSingle[arrayId]; - if (bidRequest.userId.hasOwnProperty(key)) { - if (typeof (bidRequest.userId[key]) == 'string') { - ret[key] = bidRequest.userId[key]; - } else if (typeof (bidRequest.userId[key]) == 'object') { - logError(`WARNING: findAllUserIds had to use first key in user object to get value for bid.userId key: ${key}. Prebid adapter should be updated.`); - ret[key] = bidRequest.userId[key][Object.keys(bidRequest.userId[key])[0]]; // cannot use Object.values - } else { - logError(`failed to get string key value for userId : ${key}`); - } - } - } - let lipbid = deepAccess(bidRequest.userId, 'lipb.lipbid'); - if (lipbid) { - ret['lipb'] = {'lipbid': lipbid}; - } - let id5id = deepAccess(bidRequest.userId, 'id5id.uid'); - if (id5id) { - ret['id5id'] = id5id; - } - let sharedid = deepAccess(bidRequest.userId, 'sharedid.id'); - if (sharedid) { - ret['sharedid'] = sharedid; - } - } - if (!ret.hasOwnProperty('pubcid')) { - let pubcid = deepAccess(bidRequest, 'crumbs.pubcid'); - if (pubcid) { - ret['pubcid'] = pubcid; // if built with old pubCommonId module - } - } - return ret; - }, - getPlacementId(bidRequest) { - return (bidRequest.params.placementId).toString(); - }, - getPlacementIdOverrideFromGetParam() { - let arr = this.getGetParametersAsObject(); - if (arr.hasOwnProperty('npstoredrequest')) { - if (this.isValidPlacementId(arr['npstoredrequest'])) { - logInfo(`using GET npstoredrequest ` + arr['npstoredrequest'] + ' to replace placementId'); - return arr['npstoredrequest']; - } else { - logError(`GET npstoredrequest FAILED VALIDATION - will not use it`); - } - } - return null; - }, - getGetParametersAsObject() { - let parsed = parseUrl(this.getRefererInfo().location); // was getRefererInfo().page but this is not backwards compatible - logInfo('getGetParametersAsObject found:', parsed.search); - return parsed.search; - }, - getRefererInfo() { - if (getRefererInfo().hasOwnProperty('location')) { - logInfo('FOUND location on getRefererInfo OK (prebid >= 7); will use getRefererInfo for location & page'); - return getRefererInfo(); - } else { - logInfo('DID NOT FIND location on getRefererInfo (prebid < 7); will use legacy code that ALWAYS worked reliably to get location & page ;-)'); - try { - return { - page: top.location.href, - location: top.location.href - }; - } catch (e) { - return { - page: window.location.href, - location: window.location.href - }; - } - } - }, - blockTheRequest() { - let npRequest = config.getConfig('newspassid.np_request'); - if (typeof npRequest == 'boolean' && !npRequest) { - logWarn(`Will not allow auction : np_request is set to false`); - return true; - } - return false; - }, - getPageId: function() { - if (this.propertyBag.pageId == null) { - let randPart = ''; - let allowable = '0123456789abcdefghijklmnopqrstuvwxyz'; - for (let i = 20; i > 0; i--) { - randPart += allowable[Math.floor(Math.random() * 36)]; - } - this.propertyBag.pageId = new Date().getTime() + '_' + randPart; - } - return this.propertyBag.pageId; - }, - addStandardProperties(seatBid, defaultWidth, defaultHeight) { - seatBid.cpm = seatBid.price; - seatBid.bidId = seatBid.impid; - seatBid.requestId = seatBid.impid; - seatBid.width = seatBid.w || defaultWidth; - seatBid.height = seatBid.h || defaultHeight; - seatBid.ad = seatBid.adm; - seatBid.netRevenue = true; - seatBid.creativeId = seatBid.crid; - seatBid.currency = 'USD'; - seatBid.ttl = 300; - return seatBid; - }, - getWinnerForRequestBid(requestBidId, serverResponseSeatBid) { - let thisBidWinner = null; - let winningSeat = null; - for (let j = 0; j < serverResponseSeatBid.length; j++) { - let theseBids = serverResponseSeatBid[j].bid; - let thisSeat = serverResponseSeatBid[j].seat; - for (let k = 0; k < theseBids.length; k++) { - if (theseBids[k].impid === requestBidId) { - if ((thisBidWinner == null) || (thisBidWinner.price < theseBids[k].price)) { - thisBidWinner = theseBids[k]; - winningSeat = thisSeat; - break; - } - } - } - } - return {'seat': winningSeat, 'bid': thisBidWinner}; - }, - getAllBidsForBidId(matchBidId, serverResponseSeatBid) { - let objBids = {}; - for (let j = 0; j < serverResponseSeatBid.length; j++) { - let theseBids = serverResponseSeatBid[j].bid; - let thisSeat = serverResponseSeatBid[j].seat; - for (let k = 0; k < theseBids.length; k++) { - if (theseBids[k].impid === matchBidId) { - if (objBids.hasOwnProperty(thisSeat)) { // > 1 bid for an adunit from a bidder - only use the one with the highest bid - if (objBids[thisSeat]['price'] < theseBids[k].price) { - objBids[thisSeat] = theseBids[k]; - } - } else { - objBids[thisSeat] = theseBids[k]; - } - } - } - } - return objBids; + + return syncs; } }; -export function injectAdIdsIntoAllBidResponses(seatbid) { - logInfo('injectAdIdsIntoAllBidResponses', seatbid); - for (let i = 0; i < seatbid.length; i++) { - let sb = seatbid[i]; - for (let j = 0; j < sb.bid.length; j++) { - sb.bid[j]['adId'] = `${sb.bid[j]['impid']}-${i}-np-${j}`; - } - } - return seatbid; -} -export function checkDeepArray(Arr) { - if (Array.isArray(Arr)) { - if (Array.isArray(Arr[0])) { - return Arr[0]; - } else { - return Arr; - } - } else { - return Arr; - } -} -export function defaultSize(thebidObj) { - if (!thebidObj) { - logInfo('defaultSize received empty bid obj! going to return fixed default size'); - return { - 'defaultHeight': 250, - 'defaultWidth': 300 - }; - } - const {sizes} = thebidObj; - const returnObject = {}; - returnObject.defaultWidth = checkDeepArray(sizes)[0]; - returnObject.defaultHeight = checkDeepArray(sizes)[1]; - return returnObject; -} -export function getRoundedBid(price, mediaType) { - const mediaTypeGranularity = config.getConfig(`mediaTypePriceGranularity.${mediaType}`); // might be string or object or nothing; if set then this takes precedence over 'priceGranularity' - let objBuckets = config.getConfig('customPriceBucket'); // this is always an object - {} if strBuckets is not 'custom' - let strBuckets = config.getConfig('priceGranularity'); // priceGranularity value, always a string ** if priceGranularity is set to an object then it's always 'custom' ** - let theConfigObject = getGranularityObject(mediaType, mediaTypeGranularity, strBuckets, objBuckets); - let theConfigKey = getGranularityKeyName(mediaType, mediaTypeGranularity, strBuckets); - logInfo('getRoundedBid. price:', price, 'mediaType:', mediaType, 'configkey:', theConfigKey, 'configObject:', theConfigObject, 'mediaTypeGranularity:', mediaTypeGranularity, 'strBuckets:', strBuckets); - let priceStringsObj = getPriceBucketString( - price, - theConfigObject, - config.getConfig('currency.granularityMultiplier') - ); - logInfo('priceStringsObj', priceStringsObj); - let granularityNamePriceStringsKeyMapping = { - 'medium': 'med', - 'custom': 'custom', - 'high': 'high', - 'low': 'low', - 'dense': 'dense' - }; - if (granularityNamePriceStringsKeyMapping.hasOwnProperty(theConfigKey)) { - let priceStringsKey = granularityNamePriceStringsKeyMapping[theConfigKey]; - logInfo('getRoundedBid: looking for priceStringsKey:', priceStringsKey); - return priceStringsObj[priceStringsKey]; - } - return priceStringsObj['auto']; -} -export function getGranularityKeyName(mediaType, mediaTypeGranularity, strBuckets) { - if (typeof mediaTypeGranularity === 'string') { - return mediaTypeGranularity; - } - if (typeof mediaTypeGranularity === 'object') { - return 'custom'; - } - if (typeof strBuckets === 'string') { - return strBuckets; - } - return 'auto'; // fall back to a default key - should literally never be needed. -} -export function getGranularityObject(mediaType, mediaTypeGranularity, strBuckets, objBuckets) { - if (typeof mediaTypeGranularity === 'object') { - return mediaTypeGranularity; - } - if (strBuckets === 'custom') { - return objBuckets; - } - return ''; -} + registerBidder(spec); -logInfo(`*BidAdapter ${NEWSPASSVERSION} was loaded`); diff --git a/modules/newspassidBidAdapter.md b/modules/newspassidBidAdapter.md new file mode 100644 index 00000000000..89d7bb97b41 --- /dev/null +++ b/modules/newspassidBidAdapter.md @@ -0,0 +1,40 @@ +Overview +======== + +``` +Module Name: NewsPassID Bid Adapter +Module Type: Bidder Adapter +Maintainer: techsupport@newspassid.com +``` + +Description +=========== + +Bid adapter to connect to Local Media Consortium's NewsPassID (NPID) demand source(s). + +# Bid Parameters + +| Key | Required | Example | Description | +| --- | -------- | ------- | ----------- | +| `accountId` | yes | `"01952070-3b08-7d30-9daf-93f1f7e4247f"` | this is the account ID associated with your publisher account with NewsPassID initiative | +| `groupId` | yes | `"leftrail-mobile-1"` | For associating the ad placement inventory with demand. This ID must be predefined by NewsPassID provider | + +# Test Parameters + +``` +var adUnits = [{ + code: 'newspass-test-div', + sizes: [[300, 250]], + bids: [{ + bidder: 'newspassid', + params: { + accountId: '123456', + groupId: 'test-group1' + }, + }] +}] +``` + +### Note: + +Please contact us at techsupport@newspassid.com for any assistance testing your implementation before going live into production. diff --git a/test/spec/modules/newspassidBidAdapter_spec.js b/test/spec/modules/newspassidBidAdapter_spec.js index 6468d4f530a..5f82ccfae5f 100644 --- a/test/spec/modules/newspassidBidAdapter_spec.js +++ b/test/spec/modules/newspassidBidAdapter_spec.js @@ -1,1799 +1,309 @@ -import { expect } from 'chai'; -import { spec, defaultSize } from 'modules/newspassidBidAdapter.js'; +import { spec } from 'modules/newspassidBidAdapter.js'; import { config } from 'src/config.js'; -import {getGranularityKeyName, getGranularityObject} from '../../../modules/newspassidBidAdapter.js'; -import * as utils from '../../../src/utils.js'; -const NEWSPASSURI = 'https://bidder.newspassid.com/openrtb2/auction'; -const BIDDER_CODE = 'newspassid'; -var validBidRequests = [ - { - adUnitCode: 'div-gpt-ad-1460505748561-0', - auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', - bidId: '2899ec066a91ff8', - bidRequestsCount: 1, +import { deepClone } from 'src/utils.js'; +import { resolveNewpassidAccountId } from '../../../modules/newspassidBidAdapter'; + +const TEST_ACCOUNT_ID = '123456'; + +describe('newspassidBidAdapter', function () { + const validBidRequest = { bidder: 'newspassid', - bidderRequestId: '1c1586b27a1b5c8', - crumbs: {pubcid: '203a0692-f728-4856-87f6-9a25a6b63715'}, - params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { id: '2899ec066a91ff8', tagid: 'undefined', secure: 1, banner: { format: [{ w: 300, h: 250 }, { w: 300, h: 600 }], h: 250, topframe: 1, w: 300 } } ] }, - sizes: [[300, 250], [300, 600]], - transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87' - } -]; -var validBidRequestsNoCustomData = [ - { - adUnitCode: 'div-gpt-ad-1460505748561-0', - auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', - bidId: '2899ec066a91ff8', - bidRequestsCount: 1, - bidder: 'newspassid', - bidderRequestId: '1c1586b27a1b5c8', - crumbs: {pubcid: '203a0692-f728-4856-87f6-9a25a6b63715'}, - params: { publisherId: '9876abcd12-3', placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { id: '2899ec066a91ff8', tagid: 'undefined', secure: 1, banner: { format: [{ w: 300, h: 250 }, { w: 300, h: 600 }], h: 250, topframe: 1, w: 300 } } ] }, - sizes: [[300, 250], [300, 600]], - transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87' - } -]; -var validBidRequestsMulti = [ - { - testId: 1, - adUnitCode: 'div-gpt-ad-1460505748561-0', - auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', - bidId: '2899ec066a91ff8', - bidRequestsCount: 1, - bidder: 'newspassid', - bidderRequestId: '1c1586b27a1b5c8', - crumbs: {pubcid: '203a0692-f728-4856-87f6-9a25a6b63715'}, - params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { id: '2899ec066a91ff8', tagid: 'undefined', secure: 1, banner: { format: [{ w: 300, h: 250 }, { w: 300, h: 600 }], h: 250, topframe: 1, w: 300 } } ] }, - sizes: [[300, 250], [300, 600]], - transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87' - }, - { - testId: 2, - adUnitCode: 'div-gpt-ad-1460505748561-0', - auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', - bidId: '2899ec066a91ff0', - bidRequestsCount: 1, - bidder: 'newspassid', - bidderRequestId: '1c1586b27a1b5c0', - crumbs: {pubcid: '203a0692-f728-4856-87f6-9a25a6b63715'}, - params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { id: '2899ec066a91ff8', tagid: 'undefined', secure: 1, banner: { format: [{ w: 300, h: 250 }, { w: 300, h: 600 }], h: 250, topframe: 1, w: 300 } } ] }, - sizes: [[300, 250], [300, 600]], - transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87' - } -]; -var validBidRequestsWithUserIdData = [ - { - adUnitCode: 'div-gpt-ad-1460505748561-0', - auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', - bidId: '2899ec066a91ff8', - bidRequestsCount: 1, - bidder: 'newspassid', - bidderRequestId: '1c1586b27a1b5c8', - crumbs: {pubcid: '203a0692-f728-4856-87f6-9a25a6b63715'}, - params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { id: '2899ec066a91ff8', tagid: 'undefined', secure: 1, banner: { format: [{ w: 300, h: 250 }, { w: 300, h: 600 }], h: 250, topframe: 1, w: 300 } } ] }, - sizes: [[300, 250], [300, 600]], - transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87', - userId: { - 'pubcid': '12345678', - 'tdid': '1111tdid', - 'id5id': { uid: '1111', ext: { linkType: 2, abTestingControlGroup: false } }, - 'criteoId': '1111criteoId', - 'idl_env': 'liverampId', - 'lipb': {'lipbid': 'lipbidId123'}, - 'parrableId': {'eid': '01.5678.parrableid'}, - 'sharedid': {'id': '01EAJWWNEPN3CYMM5N8M5VXY22', 'third': '01EAJWWNEPN3CYMM5N8M5VXY22'} + params: { + accountId: TEST_ACCOUNT_ID, + groupId: 'test-group1' }, - userIdAsEids: [ - { - 'source': 'pubcid.org', - 'uids': [ - { - 'id': '12345678', - 'atype': 1 - } - ] - }, - { - 'source': 'adserver.org', - 'uids': [{ - 'id': '1111tdid', - 'atype': 1, - 'ext': { - 'rtiPartner': 'TDID' - } - }] - }, - { - 'source': 'id5-sync.com', - 'uids': [{ - 'id': 'ID5-someId', - 'atype': 1, - }] - }, - { - 'source': 'criteoId', - 'uids': [{ - 'id': '1111criteoId', - 'atype': 1, - }] - }, - { - 'source': 'idl_env', - 'uids': [{ - 'id': 'liverampId', - 'atype': 1, - }] - }, - { - 'source': 'lipb', - 'uids': [{ - 'id': {'lipbid': 'lipbidId123'}, - 'atype': 1, - }] - }, - { - 'source': 'parrableId', - 'uids': [{ - 'id': {'eid': '01.5678.parrableid'}, - 'atype': 1, - }] + mediaTypes: { + banner: { + sizes: [[300, 250]] } - ] - } -]; -var validBidRequestsMinimal = [ - { - adUnitCode: 'div-gpt-ad-1460505748561-0', - auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', - bidId: '2899ec066a91ff8', - bidRequestsCount: 1, - bidder: 'newspassid', - bidderRequestId: '1c1586b27a1b5c8', - params: { publisherId: '9876abcd12-3', placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { id: '2899ec066a91ff8', tagid: 'undefined', secure: 1, banner: { format: [{ w: 300, h: 250 }, { w: 300, h: 600 }], h: 250, topframe: 1, w: 300 } } ] }, - sizes: [[300, 250], [300, 600]], - transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87' - } -]; -var validBidRequestsNoSizes = [ - { - adUnitCode: 'div-gpt-ad-1460505748561-0', - auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', - bidId: '2899ec066a91ff8', - bidRequestsCount: 1, - bidder: 'newspassid', - bidderRequestId: '1c1586b27a1b5c8', - crumbs: {pubcid: '203a0692-f728-4856-87f6-9a25a6b63715'}, - params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { id: '2899ec066a91ff8', tagid: 'undefined', secure: 1, banner: { format: [{ w: 300, h: 250 }, { w: 300, h: 600 }], h: 250, topframe: 1, w: 300 } } ] }, - transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87' - } -]; -var validBidRequestsWithBannerMediaType = [ - { - adUnitCode: 'div-gpt-ad-1460505748561-0', - auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', - bidId: '2899ec066a91ff8', - bidRequestsCount: 1, - bidder: 'newspassid', - bidderRequestId: '1c1586b27a1b5c8', - crumbs: {pubcid: '203a0692-f728-4856-87f6-9a25a6b63715'}, - params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { id: '2899ec066a91ff8', tagid: 'undefined', secure: 1, banner: { format: [{ w: 300, h: 250 }, { w: 300, h: 600 }], h: 250, topframe: 1, w: 300 } } ] }, - mediaTypes: {banner: {sizes: [[300, 250], [300, 600]]}}, - transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87' - } -]; -var validBidRequestsIsThisCamelCaseEnough = [ - { - 'bidder': 'newspassid', - 'testname': 'validBidRequestsIsThisCamelCaseEnough', - 'params': { - 'publisherId': 'newspassRUP0001', - 'placementId': '8000000009', - 'siteId': '4204204201', - 'customData': [ - { - 'settings': {}, - 'targeting': { - 'sens': 'f', - 'pt1': '/uk', - 'pt2': 'uk', - 'pt3': 'network-front', - 'pt4': 'ng', - 'pt5': [ - 'uk' - ], - 'pt7': 'desktop', - 'pt8': [ - 'tfmqxwj7q', - 'penl4dfdk', - 'sek9ghqwi' - ], - 'pt9': '|k0xw2vqzp33kklb3j5w4|||' - } - } - ], - 'userId': { - 'pubcid': '2ada6ae6-aeca-4e07-8922-a99b3aaf8a56' - }, - 'userIdAsEids': [ - { - 'source': 'pubcid.org', - 'uids': [ - { - 'id': '2ada6ae6-aeca-4e07-8922-a99b3aaf8a56', - 'atype': 1 - } - ] - } - ] }, - mediaTypes: {banner: {sizes: [[300, 250], [300, 600]]}}, - 'adUnitCode': 'some-ad', - 'transactionId': '02c1ea7d-0bf2-451b-a122-1420040d1cf8', - 'bidId': '2899ec066a91ff8', - 'bidderRequestId': '1c1586b27a1b5c8', - 'auctionId': '0456c9b7-5ab2-4fec-9e10-f418d3d1f04c', - 'src': 'client', - 'bidRequestsCount': 1, - 'bidderRequestsCount': 1, - 'bidderWinsCount': 0 - } -]; -var validBidderRequest = { - auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', - auctionStart: 1536838908986, - bidderCode: 'newspassid', - bidderRequestId: '1c1586b27a1b5c8', - bids: [{ - adUnitCode: 'div-gpt-ad-1460505748561-0', - auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', - bidId: '2899ec066a91ff8', - bidRequestsCount: 1, - bidder: 'newspassid', - bidderRequestId: '1c1586b27a1b5c8', - crumbs: {pubcid: '203a0692-f728-4856-87f6-9a25a6b63715'}, - params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { banner: { topframe: 1, w: 300, h: 250, format: [{ w: 300, h: 250 }, { w: 300, h: 600 }] }, id: '2899ec066a91ff8', secure: 1, tagid: 'undefined' } ] }, - sizes: [[300, 250], [300, 600]], - transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87' - }], - doneCbCallCount: 1, - start: 1536838908987, - timeout: 3000 -}; -var emptyObject = {}; -var validResponse = { - 'body': { - 'id': 'd6198807-7a53-4141-b2db-d2cb754d68ba', - 'seatbid': [ - { - 'bid': [ - { - 'id': '677903815252395017', - 'impid': '2899ec066a91ff8', - 'price': 0.5, - 'adm': '', - 'adid': '98493581', - 'adomain': [ - 'http://prebid.org' - ], - 'iurl': 'https://fra1-ib.adnxs.com/cr?id=98493581', - 'cid': '9325', - 'crid': '98493581', - 'cat': [ - 'IAB3-1' - ], - 'w': 300, - 'h': 600, - 'ext': { - 'prebid': { - 'type': 'banner' - }, - 'bidder': { - 'appnexus': { - 'brand_id': 555545, - 'auction_id': 6500448734132353000, - 'bidder_id': 2, - 'bid_ad_type': 0 - } - } - } - } - ], - 'seat': 'appnexus' - } - ], - 'cur': 'GBP', /* NOTE - this is where cur is, not in the seatbids. */ - 'ext': { - 'responsetimemillis': { - 'appnexus': 47, - 'openx': 30 - } + adUnitCode: 'test-div', + transactionId: '123456', + bidId: '789', + bidderRequestId: 'abc', + auctionId: 'xyz' + }; + + const validBidderRequest = { + bidderCode: 'newspassid', + auctionId: 'xyz', + bidderRequestId: 'abc', + bids: [validBidRequest], + gdprConsent: { + gdprApplies: true, + consentString: 'consent123' }, - 'timing': { - 'start': 1536848078.089177, - 'end': 1536848078.142203, - 'TimeTaken': 0.05302619934082031 + refererInfo: { + page: 'http://example.com' } - }, - 'headers': {} -}; -var validResponse2BidsSameAdunit = { - 'body': { - 'id': 'd6198807-7a53-4141-b2db-d2cb754d68ba', - 'seatbid': [ - { - 'bid': [ - { - 'id': '677903815252395017', - 'impid': '2899ec066a91ff8', - 'price': 0.5, - 'adm': '', - 'adid': '98493581', - 'adomain': [ - 'http://prebid.org' - ], - 'iurl': 'https://fra1-ib.adnxs.com/cr?id=98493581', - 'cid': '9325', - 'crid': '98493581', - 'cat': [ - 'IAB3-1' - ], - 'w': 300, - 'h': 600, - 'ext': { - 'prebid': { - 'type': 'banner' - }, - 'bidder': { - 'appnexus': { - 'brand_id': 555545, - 'auction_id': 6500448734132353000, - 'bidder_id': 2, - 'bid_ad_type': 0 - } - } - } - }, - { - 'id': '677903815252395010', - 'impid': '2899ec066a91ff8', - 'price': 0.9, - 'adm': '', - 'adid': '98493580', - 'adomain': [ - 'http://prebid.org' - ], - 'iurl': 'https://fra1-ib.adnxs.com/cr?id=98493581', - 'cid': '9320', - 'crid': '98493580', - 'cat': [ - 'IAB3-1' - ], - 'w': 300, - 'h': 250, - 'ext': { - 'prebid': { - 'type': 'banner' - }, - 'bidder': { - 'appnexus': { - 'brand_id': 555540, - 'auction_id': 6500448734132353000, - 'bidder_id': 2, - 'bid_ad_type': 0 - } - } - } - } ], - 'seat': 'npappnexus' - } - ], - 'cur': 'GBP', /* NOTE - this is where cur is, not in the seatbids. */ - 'ext': { - 'responsetimemillis': { - 'appnexus': 47, - 'openx': 30 - } - }, - 'timing': { - 'start': 1536848078.089177, - 'end': 1536848078.142203, - 'TimeTaken': 0.05302619934082031 - } - }, - 'headers': {} -}; -var validBidResponse1adWith2Bidders = { - 'body': { - 'id': '91221f96-b931-4acc-8f05-c2a1186fa5ac', - 'seatbid': [ - { - 'bid': [ - { - 'id': 'd6198807-7a53-4141-b2db-d2cb754d68ba', - 'impid': '2899ec066a91ff8', - 'price': 0.36754, - 'adm': '', - 'adid': '134928661', - 'adomain': [ - 'somecompany.com' - ], - 'iurl': 'https:\/\/ams1-ib.adnxs.com\/cr?id=134928661', - 'cid': '8825', - 'crid': '134928661', - 'cat': [ - 'IAB8-15', - 'IAB8-16', - 'IAB8-4', - 'IAB8-1', - 'IAB8-14', - 'IAB8-6', - 'IAB8-13', - 'IAB8-3', - 'IAB8-17', - 'IAB8-12', - 'IAB8-8', - 'IAB8-7', - 'IAB8-2', - 'IAB8-9', - 'IAB8', - 'IAB8-11' - ], - 'w': 300, - 'h': 250, - 'ext': { - 'prebid': { - 'type': 'banner' - }, - 'bidder': { - 'appnexus': { - 'brand_id': 14640, - 'auction_id': 1.8369641905139e+18, - 'bidder_id': 2, - 'bid_ad_type': 0 - } - } - } - } - ], - 'seat': 'appnexus' - }, - { - 'bid': [ - { - 'id': '75665207-a1ca-49db-ba0e-a5e9c7d26f32', - 'impid': '37fff511779365a', - 'price': 1.046, - 'adm': '
removed
', - 'adomain': [ - 'kx.com' - ], - 'crid': '13005', - 'w': 300, - 'h': 250, - 'ext': { - 'prebid': { - 'type': 'banner' - } - } - } - ], - 'seat': 'openx' - } - ], - 'ext': { - 'responsetimemillis': { - 'appnexus': 91, - 'openx': 109, - 'npappnexus': 46, - 'npbeeswax': 2, - 'pangaea': 91 - } - } - }, - 'headers': {} -}; -var multiRequest1 = [ - { - 'bidder': 'newspassid', - 'params': { - 'publisherId': 'newspassRUP0001', - 'siteId': '4204204201', - 'placementId': '0420420421', - 'customData': [ - { - 'settings': {}, - 'targeting': { - 'sens': 'f', - 'pt1': '/uk', - 'pt2': 'uk', - 'pt3': 'network-front', - 'pt4': 'ng', - 'pt5': [ - 'uk' - ], - 'pt7': 'desktop', - 'pt8': [ - 'tfmqxwj7q', - 'penl4dfdk', - 'uayf5jmv3', - 't8nyiude5', - 'sek9ghqwi' - ], - 'pt9': '|k0xw2vqzp33kklb3j5w4|||' - } - } - ] - }, - 'mediaTypes': { - 'banner': { - 'sizes': [ - [ - 300, - 250 - ], - [ - 300, - 600 - ] - ] - } - }, - 'adUnitCode': 'mpu', - 'transactionId': '6480bac7-31b5-4723-9145-ad8966660651', - 'sizes': [ - [ - 300, - 250 - ], - [ - 300, - 600 - ] - ], - 'bidId': '2d30e86db743a8', - 'bidderRequestId': '1d03a1dfc563fc', - 'auctionId': '592ee33b-fb2e-4c00-b2d5-383e99cac57f', - 'src': 'client', - 'bidRequestsCount': 1, - 'bidderRequestsCount': 1, - 'bidderWinsCount': 0 - }, - { - 'bidder': 'newspassid', - 'params': { - 'publisherId': 'newspassRUP0001', - 'siteId': '4204204201', - 'placementId': '0420420421', - 'customData': [ - { - 'settings': {}, - 'targeting': { - 'sens': 'f', - 'pt1': '/uk', - 'pt2': 'uk', - 'pt3': 'network-front', - 'pt4': 'ng', - 'pt5': [ - 'uk' - ], - 'pt7': 'desktop', - 'pt8': [ - 'tfmqxwj7q', - 'penl4dfdk', - 't8nxz6qzd', - 't8nyiude5', - 'sek9ghqwi' - ], - 'pt9': '|k0xw2vqzp33kklb3j5w4|||' - } - } - ] - }, - 'mediaTypes': { - 'banner': { - 'sizes': [ - [ - 728, - 90 - ], - [ - 970, - 250 - ] - ] - } - }, - 'adUnitCode': 'leaderboard', - 'transactionId': 'a49988e6-ae7c-46c4-9598-f18db49892a0', - 'sizes': [ - [ - 728, - 90 - ], - [ - 970, - 250 - ] - ], - 'bidId': '3025f169863b7f8', - 'bidderRequestId': '1d03a1dfc563fc', - 'auctionId': '592ee33b-fb2e-4c00-b2d5-383e99cac57f', - 'src': 'client', - 'bidRequestsCount': 1, - 'bidderRequestsCount': 1, - 'bidderWinsCount': 0 - } -]; -var multiBidderRequest1 = { - bidderRequest: { - 'bidderCode': 'newspassid', - 'auctionId': '592ee33b-fb2e-4c00-b2d5-383e99cac57f', - 'bidderRequestId': '1d03a1dfc563fc', - 'bids': [ - { - 'bidder': 'newspassid', - 'params': { - 'publisherId': 'newspassRUP0001', - 'siteId': '4204204201', - 'placementId': '0420420421', - 'customData': [ - { - 'settings': {}, - 'targeting': { - 'sens': 'f', - 'pt1': '/uk', - 'pt2': 'uk', - 'pt3': 'network-front', - 'pt4': 'ng', - 'pt5': [ - 'uk' - ], - 'pt7': 'desktop', - 'pt8': [ - 'tfmqxwj7q', - 'txeh7uyo0', - 't8nxz6qzd', - 't8nyiude5', - 'sek9ghqwi' - ], - 'pt9': '|k0xw2vqzp33kklb3j5w4|||' - } - } - ] - }, - 'mediaTypes': { - 'banner': { - 'sizes': [ - [ - 300, - 250 - ], - [ - 300, - 600 - ] - ] - } - }, - 'adUnitCode': 'mpu', - 'transactionId': '6480bac7-31b5-4723-9145-ad8966660651', - 'sizes': [ - [ - 300, - 250 - ], - [ - 300, - 600 - ] - ], - 'bidId': '2d30e86db743a8', - 'bidderRequestId': '1d03a1dfc563fc', - 'auctionId': '592ee33b-fb2e-4c00-b2d5-383e99cac57f', - 'src': 'client', - 'bidRequestsCount': 1, - 'bidderRequestsCount': 1, - 'bidderWinsCount': 0 - }, - { - 'bidder': 'newspassid', - 'params': { - 'publisherId': 'newspassRUP0001', - 'siteId': '4204204201', - 'placementId': '0420420421', - 'customData': [ - { - 'settings': {}, - 'targeting': { - 'sens': 'f', - 'pt1': '/uk', - 'pt2': 'uk', - 'pt3': 'network-front', - 'pt4': 'ng', - 'pt5': [ - 'uk' - ], - 'pt7': 'desktop', - 'pt8': [ - 'tfmqxwj7q', - 'penl4dfdk', - 't8nxz6qzd', - 't8nyiude5', - 'sek9ghqwi' - ], - 'pt9': '|k0xw2vqzp33kklb3j5w4|||' - } - } - ] - }, - 'mediaTypes': { - 'banner': { - 'sizes': [ - [ - 728, - 90 - ], - [ - 970, - 250 - ] - ] - } - }, - 'adUnitCode': 'leaderboard', - 'transactionId': 'a49988e6-ae7c-46c4-9598-f18db49892a0', - 'sizes': [ - [ - 728, - 90 - ], - [ - 970, - 250 - ] - ], - 'bidId': '3025f169863b7f8', - 'bidderRequestId': '1d03a1dfc563fc', - 'auctionId': '592ee33b-fb2e-4c00-b2d5-383e99cac57f', - 'src': 'client', - 'bidRequestsCount': 1, - 'bidderRequestsCount': 1, - 'bidderWinsCount': 0 - } - ], - 'auctionStart': 1592918645574, - 'timeout': 3000, - 'refererInfo': { - 'referer': 'http://some.referrer.com', - 'reachedTop': true, - 'numIframes': 0, - 'stack': [ - 'http://some.referrer.com' - ] - }, - 'start': 1592918645578 - } -}; -var multiResponse1 = { - 'body': { - 'id': '592ee33b-fb2e-4c00-b2d5-383e99cac57f', - 'seatbid': [ - { - 'bid': [ - { - 'id': '4419718600113204943', - 'impid': '2d30e86db743a8', - 'price': 0.2484, - 'adm': '', - 'adid': '119683582', - 'adomain': [ - 'https://someurl.com' - ], - 'iurl': 'https://ams1-ib.adnxs.com/cr?id=119683582', - 'cid': '9979', - 'crid': '119683582', - 'cat': [ - 'IAB3' - ], - 'w': 300, - 'h': 250, - 'ext': { - 'prebid': { - 'type': 'banner' - }, - 'bidder': { - 'newspassid': {}, - 'appnexus': { - 'brand_id': 734921, - 'auction_id': 2995348111857539600, - 'bidder_id': 2, - 'bid_ad_type': 0 - } - } - }, - 'cpm': 0.2484, - 'bidId': '2d30e86db743a8', - 'requestId': '2d30e86db743a8', - 'width': 300, - 'height': 250, - 'ad': '', - 'netRevenue': true, - 'creativeId': '119683582', - 'currency': 'USD', - 'ttl': 300, - 'originalCpm': 0.2484, - 'originalCurrency': 'USD' - }, - { - 'id': '18552976939844681', - 'impid': '3025f169863b7f8', - 'price': 0.0621, - 'adm': '', - 'adid': '120179216', - 'adomain': [ - 'appnexus.com' - ], - 'iurl': 'https://ams1-ib.adnxs.com/cr?id=120179216', - 'cid': '9979', - 'crid': '120179216', - 'w': 970, - 'h': 250, - 'ext': { - 'prebid': { - 'type': 'banner' - }, - 'bidder': { - 'newspassid': {}, - 'appnexus': { - 'brand_id': 1, - 'auction_id': 3449036134472542700, - 'bidder_id': 2, - 'bid_ad_type': 0 - } - } - }, - 'cpm': 0.0621, - 'bidId': '3025f169863b7f8', - 'requestId': '3025f169863b7f8', - 'width': 970, - 'height': 250, - 'ad': '', - 'netRevenue': true, - 'creativeId': '120179216', - 'currency': 'USD', - 'ttl': 300, - 'originalCpm': 0.0621, - 'originalCurrency': 'USD' - }, - { - 'id': '18552976939844999', - 'impid': '3025f169863b7f8', - 'price': 0.521, - 'adm': '', - 'adid': '120179216', - 'adomain': [ - 'appnexus.com' - ], - 'iurl': 'https://ams1-ib.adnxs.com/cr?id=120179216', - 'cid': '9999', - 'crid': '120179299', - 'w': 728, - 'h': 90, - 'ext': { - 'prebid': { - 'type': 'banner' - }, - 'bidder': { - 'newspassid': {}, - 'appnexus': { - 'brand_id': 1, - 'auction_id': 3449036134472542700, - 'bidder_id': 2, - 'bid_ad_type': 0 - } - } - }, - 'cpm': 0.521, - 'bidId': '3025f169863b7f8', - 'requestId': '3025f169863b7f8', - 'width': 728, - 'height': 90, - 'ad': '', - 'netRevenue': true, - 'creativeId': '120179299', - 'currency': 'USD', - 'ttl': 300, - 'originalCpm': 0.0621, - 'originalCurrency': 'USD' - } - ], - 'seat': 'npappnexus' - }, - { - 'bid': [ - { - 'id': '1c605e8a-4992-4ec6-8a5c-f82e2938c2db', - 'impid': '2d30e86db743a8', - 'price': 0.01, - 'adm': '
', - 'crid': '540463358', - 'w': 300, - 'h': 250, - 'ext': { - 'prebid': { - 'type': 'banner' - }, - 'bidder': { - 'newspassid': {} - } - }, - 'cpm': 0.01, - 'bidId': '2d30e86db743a8', - 'requestId': '2d30e86db743a8', - 'width': 300, - 'height': 250, - 'ad': '
', - 'netRevenue': true, - 'creativeId': '540463358', - 'currency': 'USD', - 'ttl': 300, - 'originalCpm': 0.01, - 'originalCurrency': 'USD' - }, - { - 'id': '3edeb4f7-d91d-44e2-8aeb-4a2f6d295ce5', - 'impid': '3025f169863b7f8', - 'price': 0.01, - 'adm': '
', - 'crid': '540221061', - 'w': 970, - 'h': 250, - 'ext': { - 'prebid': { - 'type': 'banner' - }, - 'bidder': { - 'newspassid': {} - } - }, - 'cpm': 0.01, - 'bidId': '3025f169863b7f8', - 'requestId': '3025f169863b7f8', - 'width': 970, - 'height': 250, - 'ad': '
', - 'netRevenue': true, - 'creativeId': '540221061', - 'currency': 'USD', - 'ttl': 300, - 'originalCpm': 0.01, - 'originalCurrency': 'USD' - } - ], - 'seat': 'openx' - } - ], - 'ext': { - 'debug': {}, - 'responsetimemillis': { - 'beeswax': 6, - 'openx': 91, - 'npappnexus': 40, - 'npbeeswax': 6 - } - } - }, - 'headers': {} -}; -describe('newspassid Adapter', function () { - describe('isBidRequestValid', function () { - let validBidReq = { - bidder: BIDDER_CODE, - params: { - placementId: '1310000099', - publisherId: '9876abcd12-3', - siteId: '1234567890' - } - }; - it('should return true when required params found', function () { - expect(spec.isBidRequestValid(validBidReq)).to.equal(true); - }); - var validBidReq2 = { - bidder: BIDDER_CODE, - params: { - placementId: '1310000099', - publisherId: '9876abcd12-3', - siteId: '1234567890', - customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}] - }, - siteId: 1234567890 + }; + + const serverResponse = { + body: { + seatbid: [{ + bid: [{ + impid: '789', + price: 2.5, + w: 300, + h: 250, + crid: 'creative123', + adm: '
ad
', + adomain: ['advertiser.com'] + }] + }], + cur: 'USD' } - it('should return true when required params found and all optional params are valid', function () { - expect(spec.isBidRequestValid(validBidReq2)).to.equal(true); - }); - var xEmptyPlacement = { - bidder: BIDDER_CODE, - params: { - placementId: '', - publisherId: '9876abcd12-3', - siteId: '1234567890' - } - }; - it('should not validate empty placementId', function () { - expect(spec.isBidRequestValid(xEmptyPlacement)).to.equal(false); - }); - var xMissingPlacement = { - bidder: BIDDER_CODE, - params: { - publisherId: '9876abcd12-3', - siteId: '1234567890' - } - }; - it('should not validate missing placementId', function () { - expect(spec.isBidRequestValid(xMissingPlacement)).to.equal(false); - }); - var xBadPlacement = { - bidder: BIDDER_CODE, - params: { - placementId: '123X45', - publisherId: '9876abcd12-3', - siteId: '1234567890' - } - }; - it('should not validate placementId with a non-numeric value', function () { - expect(spec.isBidRequestValid(xBadPlacement)).to.equal(false); - }); - var xBadPlacementTooShort = { - bidder: BIDDER_CODE, - params: { - placementId: 123456789, /* should be exactly 10 chars */ - publisherId: '9876abcd12-3', - siteId: '1234567890' - } - }; - it('should not validate placementId with a numeric value of wrong length', function () { - expect(spec.isBidRequestValid(xBadPlacementTooShort)).to.equal(false); - }); - var xBadPlacementTooLong = { - bidder: BIDDER_CODE, - params: { - placementId: 12345678901, /* should be exactly 10 chars */ - publisherId: '9876abcd12-3', - siteId: '1234567890' - } - }; - it('should not validate placementId with a numeric value of wrong length', function () { - expect(spec.isBidRequestValid(xBadPlacementTooLong)).to.equal(false); - }); - var xMissingPublisher = { - bidder: BIDDER_CODE, - params: { - placementId: '1234567890', - siteId: '1234567890' - } - }; - it('should not validate missing publisherId', function () { - expect(spec.isBidRequestValid(xMissingPublisher)).to.equal(false); - }); - var xMissingSiteId = { - bidder: BIDDER_CODE, - params: { - publisherId: '9876abcd12-3', - placementId: '1234567890', - } - }; - it('should not validate missing sitetId', function () { - expect(spec.isBidRequestValid(xMissingSiteId)).to.equal(false); - }); - var xBadPublisherTooShort = { - bidder: BIDDER_CODE, - params: { - placementId: '1234567890', - publisherId: '9876abcd12a', - siteId: '1234567890' - } - }; - it('should not validate publisherId being too short', function () { - expect(spec.isBidRequestValid(xBadPublisherTooShort)).to.equal(false); - }); - var xBadPublisherTooLong = { - bidder: BIDDER_CODE, - params: { - placementId: '1234567890', - publisherId: '9876abcd12abc', - siteId: '1234567890' - } - }; - it('should not validate publisherId being too long', function () { - expect(spec.isBidRequestValid(xBadPublisherTooLong)).to.equal(false); - }); - var publisherNumericOk = { - bidder: BIDDER_CODE, - params: { - placementId: '1234567890', - publisherId: 123456789012, - siteId: '1234567890' - } - }; - it('should validate publisherId being 12 digits', function () { - expect(spec.isBidRequestValid(publisherNumericOk)).to.equal(true); - }); - var xEmptyPublisher = { - bidder: BIDDER_CODE, - params: { - placementId: '1234567890', - publisherId: '', - siteId: '1234567890' - } - }; - it('should not validate empty publisherId', function () { - expect(spec.isBidRequestValid(xEmptyPublisher)).to.equal(false); - }); - var xBadSite = { - bidder: BIDDER_CODE, - params: { - placementId: '1234567890', - publisherId: '9876abcd12-3', - siteId: '12345Z' - } - }; - it('should not validate bad siteId', function () { - expect(spec.isBidRequestValid(xBadSite)).to.equal(false); - }); - it('should not validate siteId too long', function () { - expect(spec.isBidRequestValid(xBadSite)).to.equal(false); - }); - it('should not validate siteId too short', function () { - expect(spec.isBidRequestValid(xBadSite)).to.equal(false); - }); - var allNonStrings = { - bidder: BIDDER_CODE, - params: { - placementId: 1234567890, - publisherId: '9876abcd12-3', - siteId: 1234567890 - } - }; - it('should validate all numeric values being sent as non-string numbers', function () { - expect(spec.isBidRequestValid(allNonStrings)).to.equal(true); - }); - var emptySiteId = { - bidder: BIDDER_CODE, - params: { - placementId: 1234567890, - publisherId: '9876abcd12-3', - siteId: '' - } - }; - it('should not validate siteId being empty string (it is required now)', function () { - expect(spec.isBidRequestValid(emptySiteId)).to.equal(false); - }); - var xBadCustomData = { - bidder: BIDDER_CODE, - params: { - 'placementId': '1234567890', - 'publisherId': '9876abcd12-3', - 'siteId': '1234567890', - 'customData': 'this aint gonna work' - } - }; - it('should not validate customData not being an array', function () { - expect(spec.isBidRequestValid(xBadCustomData)).to.equal(false); - }); - var xBadCustomDataOldCustomdataValue = { - bidder: BIDDER_CODE, - params: { - 'placementId': '1234567890', - 'publisherId': '9876abcd12-3', - 'siteId': '1234567890', - 'customData': {'gender': 'bart', 'age': 'low'} - } - }; - it('should not validate customData being an object, not an array', function () { - expect(spec.isBidRequestValid(xBadCustomDataOldCustomdataValue)).to.equal(false); - }); - var xBadCustomDataZerocd = { - bidder: BIDDER_CODE, - params: { - 'placementId': '1111111110', - 'publisherId': '9876abcd12-3', - 'siteId': '1234567890', - 'customData': [] - } - }; - it('should not validate customData array having no elements', function () { - expect(spec.isBidRequestValid(xBadCustomDataZerocd)).to.equal(false); - }); - var xBadCustomDataNotargeting = { - bidder: BIDDER_CODE, - params: { - 'placementId': '1234567890', - 'publisherId': '9876abcd12-3', - 'customData': [{'settings': {}, 'xx': {'gender': 'bart', 'age': 'low'}}], - siteId: '1234567890' - } - }; - it('should not validate customData[] having no "targeting"', function () { - expect(spec.isBidRequestValid(xBadCustomDataNotargeting)).to.equal(false); - }); - var xBadCustomDataTgtNotObj = { - bidder: BIDDER_CODE, - params: { - 'placementId': '1234567890', - 'publisherId': '9876abcd12-3', - 'customData': [{'settings': {}, 'targeting': 'this should be an object'}], - siteId: '1234567890' - } - }; - it('should not validate customData[0].targeting not being an object', function () { - expect(spec.isBidRequestValid(xBadCustomDataTgtNotObj)).to.equal(false); - }); - var xBadCustomParams = { - bidder: BIDDER_CODE, - params: { - 'placementId': '1234567890', - 'publisherId': '9876abcd12-3', - 'siteId': '1234567890', - 'customParams': 'this key is no longer valid' - } - }; - it('should not validate customParams - this is a renamed key', function () { - expect(spec.isBidRequestValid(xBadCustomParams)).to.equal(false); + }; + + describe('gvlid', function() { + it('should expose gvlid', function() { + expect(spec.gvlid).to.equal(1317); }); }); - describe('buildRequests', function () { - it('sends bid request to NEWSPASSURI via POST', function () { - const request = spec.buildRequests(validBidRequests, validBidderRequest); - expect(request.url).to.equal(NEWSPASSURI); - expect(request.method).to.equal('POST'); - }); - it('sends data as a string', function () { - const request = spec.buildRequests(validBidRequests, validBidderRequest); - expect(request.data).to.be.a('string'); - }); - it('sends all bid parameters', function () { - const request = spec.buildRequests(validBidRequests, validBidderRequest); - expect(request).to.have.all.keys(['bidderRequest', 'data', 'method', 'url']); - }); - it('adds all parameters inside the ext object only', function () { - const request = spec.buildRequests(validBidRequests, validBidderRequest); - expect(request.data).to.be.a('string'); - var data = JSON.parse(request.data); - expect(data.imp[0].ext.newspassid.customData).to.be.an('array'); - expect(request).not.to.have.key('lotameData'); - expect(request).not.to.have.key('customData'); - }); - it('adds all parameters inside the ext object only - lightning', function () { - let localBidReq = JSON.parse(JSON.stringify(validBidRequests)); - const request = spec.buildRequests(localBidReq, validBidderRequest); - expect(request.data).to.be.a('string'); - var data = JSON.parse(request.data); - expect(data.imp[0].ext.newspassid.customData).to.be.an('array'); - expect(request).not.to.have.key('lotameData'); - expect(request).not.to.have.key('customData'); - }); - it('has correct bidder', function () { - const request = spec.buildRequests(validBidRequests, validBidderRequest); - expect(request.bidderRequest.bids[0].bidder).to.equal(BIDDER_CODE); - }); - it('handles mediaTypes element correctly', function () { - const request = spec.buildRequests(validBidRequestsWithBannerMediaType, validBidderRequest); - expect(request).to.have.all.keys(['bidderRequest', 'data', 'method', 'url']); - }); - it('handles no newspassid or custom data', function () { - const request = spec.buildRequests(validBidRequestsMinimal, validBidderRequest); - expect(request).to.have.all.keys(['bidderRequest', 'data', 'method', 'url']); - }); - it('should not crash when there is no sizes element at all', function () { - const request = spec.buildRequests(validBidRequestsNoSizes, validBidderRequest); - expect(request).to.have.all.keys(['bidderRequest', 'data', 'method', 'url']); - }); - it('should be able to handle non-single requests', function () { - config.setConfig({'newspassid': {'singleRequest': false}}); - const request = spec.buildRequests(validBidRequestsNoSizes, validBidderRequest); - expect(request).to.be.a('array'); - expect(request[0]).to.have.all.keys(['bidderRequest', 'data', 'method', 'url']); - config.setConfig({'newspassid': {'singleRequest': true}}); - }); - it('should not have imp[N].ext.newspassid.userId', function () { - let bidderRequest = validBidderRequest; - let bidRequests = validBidRequests; - bidRequests[0]['userId'] = { - 'digitrustid': {data: {id: 'DTID', keyv: 4, privacy: {optout: false}, producer: 'ABC', version: 2}}, - 'id5id': { uid: '1111', ext: { linkType: 2, abTestingControlGroup: false } }, - 'idl_env': '3333', - 'parrableid': 'eidVersion.encryptionKeyReference.encryptedValue', - 'pubcid': '5555', - 'tdid': '6666', - 'sharedid': {'id': '01EAJWWNEPN3CYMM5N8M5VXY22', 'third': '01EAJWWNEPN3CYMM5N8M5VXY22'} - }; - bidRequests[0]['userIdAsEids'] = validBidRequestsWithUserIdData[0]['userIdAsEids']; - const request = spec.buildRequests(bidRequests, bidderRequest); - const payload = JSON.parse(request.data); - let firstBid = payload.imp[0].ext.newspassid; - expect(firstBid).to.not.have.property('userId'); - delete validBidRequests[0].userId; // tidy up now, else it will screw with other tests - }); - it('should pick up the value of pubcid when built using the pubCommonId module (not userId)', function () { - let bidRequests = validBidRequests; - bidRequests[0]['userId'] = { - 'digitrustid': {data: {id: 'DTID', keyv: 4, privacy: {optout: false}, producer: 'ABC', version: 2}}, - 'id5id': { uid: '1111', ext: { linkType: 2, abTestingControlGroup: false } }, - 'idl_env': '3333', - 'parrableid': 'eidVersion.encryptionKeyReference.encryptedValue', - 'tdid': '6666', - 'sharedid': {'id': '01EAJWWNEPN3CYMM5N8M5VXY22', 'third': '01EAJWWNEPN3CYMM5N8M5VXY22'} - }; - bidRequests[0]['userIdAsEids'] = validBidRequestsWithUserIdData[0]['userIdAsEids']; - const request = spec.buildRequests(bidRequests, validBidderRequest); - const payload = JSON.parse(request.data); - expect(payload.ext.newspassid.pubcid).to.equal(bidRequests[0]['crumbs']['pubcid']); - delete validBidRequests[0].userId; // tidy up now, else it will screw with other tests - }); - it('should add a user.ext.eids object to contain user ID data in the new location (Nov 2019) Updated Aug 2020', function() { - const request = spec.buildRequests(validBidRequestsWithUserIdData, validBidderRequest); - const payload = JSON.parse(request.data); - expect(payload.user).to.exist; - expect(payload.user.ext).to.exist; - expect(payload.user.ext.eids).to.exist; - expect(payload.user.ext.eids[0]['source']).to.equal('pubcid.org'); - expect(payload.user.ext.eids[0]['uids'][0]['id']).to.equal('12345678'); - expect(payload.user.ext.eids[1]['source']).to.equal('adserver.org'); - expect(payload.user.ext.eids[1]['uids'][0]['id']).to.equal('1111tdid'); - expect(payload.user.ext.eids[2]['source']).to.equal('id5-sync.com'); - expect(payload.user.ext.eids[2]['uids'][0]['id']).to.equal('ID5-someId'); - expect(payload.user.ext.eids[3]['source']).to.equal('criteoId'); - expect(payload.user.ext.eids[3]['uids'][0]['id']).to.equal('1111criteoId'); - expect(payload.user.ext.eids[4]['source']).to.equal('idl_env'); - expect(payload.user.ext.eids[4]['uids'][0]['id']).to.equal('liverampId'); - expect(payload.user.ext.eids[5]['source']).to.equal('lipb'); - expect(payload.user.ext.eids[5]['uids'][0]['id']['lipbid']).to.equal('lipbidId123'); - expect(payload.user.ext.eids[6]['source']).to.equal('parrableId'); - expect(payload.user.ext.eids[6]['uids'][0]['id']['eid']).to.equal('01.5678.parrableid'); - }); - it('replaces the auction url for a config override', function () { - spec.propertyBag.config = null; - let fakeOrigin = 'http://sometestendpoint'; - config.setConfig({'newspassid': {'endpointOverride': {'origin': fakeOrigin}}}); - const request = spec.buildRequests(validBidRequests, validBidderRequest); - expect(request.url).to.equal(fakeOrigin + '/openrtb2/auction'); - expect(request.method).to.equal('POST'); - const data = JSON.parse(request.data); - expect(data.ext.newspassid.origin).to.equal(fakeOrigin); - config.setConfig({'newspassid': {'kvpPrefix': null, 'endpointOverride': null}}); - }); - it('replaces the FULL auction url for a config override', function () { - spec.propertyBag.config = null; - let fakeurl = 'http://sometestendpoint/myfullurl'; - config.setConfig({'newspassid': {'endpointOverride': {'auctionUrl': fakeurl}}}); - const request = spec.buildRequests(validBidRequests, validBidderRequest); - expect(request.url).to.equal(fakeurl); - expect(request.method).to.equal('POST'); - const data = JSON.parse(request.data); - expect(data.ext.newspassid.origin).to.equal(fakeurl); - config.setConfig({'newspassid': {'kvpPrefix': null, 'endpointOverride': null}}); - }); - it('should ignore kvpPrefix', function () { - spec.propertyBag.config = null; - config.setConfig({'newspassid': {'kvpPrefix': 'np'}}); - const request = spec.buildRequests(validBidRequests, validBidderRequest); - const result = spec.interpretResponse(validResponse, request); - expect(result[0].adserverTargeting).to.have.own.property('np_appnexus_crid'); - expect(utils.deepAccess(result[0].adserverTargeting, 'np_appnexus_crid')).to.equal('98493581'); - expect(utils.deepAccess(result[0].adserverTargeting, 'np_adId')).to.equal('2899ec066a91ff8-0-np-0'); - expect(utils.deepAccess(result[0].adserverTargeting, 'np_size')).to.equal('300x600'); - expect(utils.deepAccess(result[0].adserverTargeting, 'np_pb_r')).to.equal('0.50'); - expect(utils.deepAccess(result[0].adserverTargeting, 'np_bid')).to.equal('true'); + + describe('resolveNewpassidAccountId', function() { + afterEach(() => { config.resetConfig(); }); - it('should create a meta object on each bid returned', function () { - spec.propertyBag.config = null; - const request = spec.buildRequests(validBidRequests, validBidderRequest); - const result = spec.interpretResponse(validResponse, request); - expect(result[0]).to.have.own.property('meta'); - expect(result[0].meta.advertiserDomains[0]).to.equal('http://prebid.org'); - config.resetConfig(); - }); - it('should use nptestmode GET value if set', function() { - var specMock = utils.deepClone(spec); - specMock.getGetParametersAsObject = function() { - return {'nptestmode': 'mytestvalue_123'}; - }; - const request = specMock.buildRequests(validBidRequests, validBidderRequest); - const data = JSON.parse(request.data); - expect(data.imp[0].ext.newspassid.customData).to.be.an('array'); - expect(data.imp[0].ext.newspassid.customData[0].targeting.nptestmode).to.equal('mytestvalue_123'); - }); - it('should pass through GET params if present: npf, nppf, nprp, npip', function() { - var specMock = utils.deepClone(spec); - specMock.getGetParametersAsObject = function() { - return {npf: '1', nppf: '0', nprp: '2', npip: '123'}; - }; - const request = specMock.buildRequests(validBidRequests, validBidderRequest); - const data = JSON.parse(request.data); - expect(data.ext.newspassid.npf).to.equal(1); - expect(data.ext.newspassid.nppf).to.equal(0); - expect(data.ext.newspassid.nprp).to.equal(2); - expect(data.ext.newspassid.npip).to.equal(123); - }); - it('should pass through GET params if present: npf, nppf, nprp, npip with alternative values', function() { - var specMock = utils.deepClone(spec); - specMock.getGetParametersAsObject = function() { - return {npf: 'false', nppf: 'true', nprp: 'xyz', npip: 'hello'}; - }; - const request = specMock.buildRequests(validBidRequests, validBidderRequest); - const data = JSON.parse(request.data); - expect(data.ext.newspassid.npf).to.equal(0); - expect(data.ext.newspassid.nppf).to.equal(1); - expect(data.ext.newspassid).to.not.haveOwnProperty('nprp'); - expect(data.ext.newspassid).to.not.haveOwnProperty('npip'); + + it('should return null if no bidrequest object or no global account id set', function() { + expect(resolveNewpassidAccountId()).to.equal(null); }); - it('should use nptestmode GET value if set, even if there is no customdata in config', function() { - var specMock = utils.deepClone(spec); - specMock.getGetParametersAsObject = function() { - return {'nptestmode': 'mytestvalue_123'}; - }; - const request = specMock.buildRequests(validBidRequestsMinimal, validBidderRequest); - const data = JSON.parse(request.data); - expect(data.imp[0].ext.newspassid.customData).to.be.an('array'); - expect(data.imp[0].ext.newspassid.customData[0].targeting.nptestmode).to.equal('mytestvalue_123'); - }); - it('should use GET values auction=[encoded URL] & cookiesync=[encoded url] if set', function() { - spec.propertyBag.config = null; - var specMock = utils.deepClone(spec); - specMock.getGetParametersAsObject = function() { - return {}; - }; - let request = specMock.buildRequests(validBidRequestsMinimal, validBidderRequest); - let url = request.url; - expect(url).to.equal('https://bidder.newspassid.com/openrtb2/auction'); - let cookieUrl = specMock.getCookieSyncUrl(); - expect(cookieUrl).to.equal('https://bidder.newspassid.com/static/load-cookie.html'); - specMock = utils.deepClone(spec); - specMock.getGetParametersAsObject = function() { - return {'auction': 'https://www.someurl.com/auction', 'cookiesync': 'https://www.someurl.com/sync'}; - }; - request = specMock.buildRequests(validBidRequestsMinimal, validBidderRequest); - url = request.url; - expect(url).to.equal('https://www.someurl.com/auction'); - cookieUrl = specMock.getCookieSyncUrl(); - expect(cookieUrl).to.equal('https://www.someurl.com/sync'); - }); - it('should use a valid npstoredrequest GET value if set to override the placementId values, and set np_rw if we find it', function() { - var specMock = utils.deepClone(spec); - specMock.getGetParametersAsObject = function() { - return {'npstoredrequest': '1122334455'}; // 10 digits are valid - }; - const request = specMock.buildRequests(validBidRequestsMinimal, validBidderRequest); - const data = JSON.parse(request.data); - expect(data.ext.newspassid.np_rw).to.equal(1); - expect(data.imp[0].ext.prebid.storedrequest.id).to.equal('1122334455'); - }); - it('should NOT use an invalid npstoredrequest GET value if set to override the placementId values, and set np_rw to 0', function() { - var specMock = utils.deepClone(spec); - specMock.getGetParametersAsObject = function() { - return {'npstoredrequest': 'BADVAL'}; // 10 digits are valid - }; - const request = specMock.buildRequests(validBidRequestsMinimal, validBidderRequest); - const data = JSON.parse(request.data); - expect(data.ext.newspassid.np_rw).to.equal(0); - expect(data.imp[0].ext.prebid.storedrequest.id).to.equal('1310000099'); - }); - it('should pick up the config value of coppa & set it in the request', function () { - config.setConfig({'coppa': true}); - const request = spec.buildRequests(validBidRequestsNoSizes, validBidderRequest); - const payload = JSON.parse(request.data); - expect(payload.regs).to.include.keys('coppa'); - expect(payload.regs.coppa).to.equal(1); - config.resetConfig(); + + it('should return global account id if no bidrequest object and global account id set', function() { + config.setConfig({ + newspassid: { + accountId: TEST_ACCOUNT_ID + } + }); + expect(resolveNewpassidAccountId()).to.equal(TEST_ACCOUNT_ID); }); - it('should pick up the config value of coppa & only set it in the request if its true', function () { - config.setConfig({'coppa': false}); - const request = spec.buildRequests(validBidRequestsNoSizes, validBidderRequest); - const payload = JSON.parse(request.data); - expect(utils.deepAccess(payload, 'regs.coppa')).to.be.undefined; - config.resetConfig(); + }); + + describe('isBidRequestValid', function() { + it('should return true when required params are present', function() { + expect(spec.isBidRequestValid(validBidRequest)).to.be.true; + }); + + it('should return false when accountId is missing', function() { + const bid = deepClone(validBidRequest); + delete bid.params.accountId; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + + it('should return false when groupId is missing', function() { + const bid = deepClone(validBidRequest); + delete bid.params.groupId; + expect(spec.isBidRequestValid(bid)).to.be.false; }); - it('should should contain a unique page view id in the auction request which persists across calls', function () { - let request = spec.buildRequests(validBidRequests, validBidderRequest); - let payload = JSON.parse(request.data); - expect(utils.deepAccess(payload, 'ext.newspassid.pv')).to.be.a('string'); - request = spec.buildRequests(validBidRequestsIsThisCamelCaseEnough, validBidderRequest); - let payload2 = JSON.parse(request.data); - expect(utils.deepAccess(payload2, 'ext.newspassid.pv')).to.be.a('string'); - expect(utils.deepAccess(payload2, 'ext.newspassid.pv')).to.equal(utils.deepAccess(payload, 'ext.newspassid.pv')); + }); + + describe('buildRequests', function() { + it('should create request data', function() { + const requests = spec.buildRequests([validBidRequest], validBidderRequest); + expect(requests).to.have.lengthOf(1); + expect(requests[0].method).to.equal('POST'); + expect(requests[0].url).to.equal('https://npid.amspbs.com/v0/bid/request'); + expect(requests[0].options.withCredentials).to.be.true; + }); + + it('should include bidder params in ortb2 request', function() { + const requests = spec.buildRequests([validBidRequest], validBidderRequest); + const data = requests[0].data; + expect(data.imp[0].ext.newspassid.accountId).to.equal(TEST_ACCOUNT_ID); + }); + + it('should handle multiple bid requests', function() { + const secondBidRequest = deepClone(validBidRequest); + secondBidRequest.bidId = '790'; + const requests = spec.buildRequests([validBidRequest, secondBidRequest], validBidderRequest); + expect(requests).to.have.lengthOf(1); + expect(requests[0].data.imp).to.have.lengthOf(2); }); - it('should indicate that the whitelist was used when it contains valid data', function () { - config.setConfig({'newspassid': {'np_whitelist_adserver_keys': ['np_appnexus_pb', 'np_appnexus_imp_id']}}); - const request = spec.buildRequests(validBidRequests, validBidderRequest); - const payload = JSON.parse(request.data); - expect(payload.ext.newspassid.np_kvp_rw).to.equal(1); - config.resetConfig(); + }); + + describe('interpretResponse', function() { + it('should return empty array if no valid bids', function() { + const invalidResponse = {body: {}}; + const bids = spec.interpretResponse(invalidResponse); + expect(bids).to.be.empty; + }); + + it('should return empty array if no seatbid', function() { + const noSeatbidResponse = {body: {cur: 'USD'}}; + const bids = spec.interpretResponse(noSeatbidResponse); + expect(bids).to.be.empty; + }); + + it('should interpret valid server response', function() { + const bids = spec.interpretResponse(serverResponse); + expect(bids).to.have.lengthOf(1); + expect(bids[0]).to.deep.equal({ + requestId: '789', + cpm: 2.5, + width: 300, + height: 250, + creativeId: 'creative123', + currency: 'USD', + netRevenue: true, + ttl: 300, + ad: '
ad
', + meta: { + advertiserDomains: ['advertiser.com'] + } + }); }); - it('should indicate that the whitelist was not used when it contains no data', function () { - config.setConfig({'newspassid': {'np_whitelist_adserver_keys': []}}); - const request = spec.buildRequests(validBidRequests, validBidderRequest); - const payload = JSON.parse(request.data); - expect(payload.ext.newspassid.np_kvp_rw).to.equal(0); + }); + + describe('getUserSyncs', function() { + afterEach(() => { config.resetConfig(); }); - it('should indicate that the whitelist was not used when it is not set in the config', function () { - const request = spec.buildRequests(validBidRequests, validBidderRequest); - const payload = JSON.parse(request.data); - expect(payload.ext.newspassid.np_kvp_rw).to.equal(0); - }); - it('should handle ortb2 site data', function () { - let bidderRequest = JSON.parse(JSON.stringify(validBidderRequest)); - bidderRequest.ortb2 = { - 'site': { - 'name': 'example_ortb2_name', - 'domain': 'page.example.com', - 'cat': ['IAB2'], - 'sectioncat': ['IAB2-2'], - 'pagecat': ['IAB2-2'], - 'page': 'https://page.example.com/here.html', - 'ref': 'https://ref.example.com', - 'keywords': 'power tools, drills', - 'search': 'drill' + + it('should expect correct host', function() { + const syncs = spec.getUserSyncs({iframeEnabled: true}, [], {}, '', {}); + const url = new URL(syncs[0].url); + expect(url.host).to.equal('npid.amspbs.com'); + }); + + it('should expect correct pathname', function() { + const syncs = spec.getUserSyncs({iframeEnabled: true}, [], {}, '', {}); + const url = new URL(syncs[0].url); + expect(url.pathname).to.equal('/v0/user/sync'); + }); + + it('should return empty array when iframe sync option is disabled', function() { + const syncs = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: false}); + expect(syncs).to.be.empty; + }); + + it('should use iframe sync when iframe enabled', function() { + const syncs = spec.getUserSyncs({iframeEnabled: true}); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.equal('https://npid.amspbs.com/v0/user/sync?gdpr=0&gdpr_consent=&gpp=&gpp_sid=&us_privacy='); + }); + + it('should include GDPR params if purpose 1 is true', function() { + const consentString = 'CQO03QAQO03QAPoABABGBiEIAIAAAIAAAACQKSwAQKSgpLABApKAAAAA.QKSwAQKSgAAA.IAAA'; // purpose 1 true + const gdprConsent = { + gdprApplies: true, + consentString: consentString, + vendorData: { + purpose: { + consents: { + 1: true + } + } } }; - const request = spec.buildRequests(validBidRequests, bidderRequest); - const payload = JSON.parse(request.data); - expect(payload.imp[0].ext.newspassid.customData[0].targeting.name).to.equal('example_ortb2_name'); - expect(payload.user.ext).to.not.have.property('gender'); - }); - it('should add ortb2 site data when there is no customData already created', function () { - let bidderRequest = JSON.parse(JSON.stringify(validBidderRequest)); - bidderRequest.ortb2 = { - 'site': { - 'name': 'example_ortb2_name', - 'domain': 'page.example.com', - 'cat': ['IAB2'], - 'sectioncat': ['IAB2-2'], - 'pagecat': ['IAB2-2'], - 'page': 'https://page.example.com/here.html', - 'ref': 'https://ref.example.com', - 'keywords': 'power tools, drills', - 'search': 'drill' + const syncs = spec.getUserSyncs({iframeEnabled: true}, [], gdprConsent); + const url = new URL(syncs[0].url); + expect(url.searchParams.get('gdpr')).to.equal('1'); + expect(url.searchParams.get('gdpr_consent')).to.equal(encodeURIComponent(consentString)); + }); + + it('should disable user sync when purpose 1 is false', function() { + const consentString = 'CQO03QAQO03QAPoABABGBiEIAHAAAHAAAACQKSwAQKSgpLABApKAAAAA.QKSwAQKSgAAA.IAAA'; // purpose 1 false + const gdprConsent = { + gdprApplies: true, + consentString: consentString, + vendorData: { + purpose: { + consents: { + 1: false + } + } } }; - const request = spec.buildRequests(validBidRequestsNoCustomData, bidderRequest); - const payload = JSON.parse(request.data); - expect(payload.imp[0].ext.newspassid.customData[0].targeting.name).to.equal('example_ortb2_name'); - expect(payload.imp[0].ext.newspassid.customData[0].targeting).to.not.have.property('gender') - }); - it('should add ortb2 user data to the user object', function () { - let bidderRequest = JSON.parse(JSON.stringify(validBidderRequest)); - bidderRequest.ortb2 = { - 'user': { - 'gender': 'I identify as a box of rocks' - } + const syncs = spec.getUserSyncs({iframeEnabled: true}, [], gdprConsent); + expect(syncs).to.be.empty; + }); + + it('should include correct us_privacy param', function() { + const uspConsent = '1YNN'; + const syncs = spec.getUserSyncs({iframeEnabled: true}, [], {}, uspConsent, {}); + const url = new URL(syncs[0].url); + expect(url.searchParams.get('gdpr')).to.equal('0'); + expect(url.searchParams.get('gdpr_consent')).to.equal(''); + expect(url.searchParams.get('gpp')).to.equal(''); + expect(url.searchParams.get('gpp_sid')).to.equal(''); + expect(url.searchParams.get('us_privacy')).to.equal(uspConsent); + }); + + it('should include correct GPP params', function() { + const gppConsentString = 'DBABMA~1YNN'; + const gppSections = '2,6'; + const gppConsent = { + gppApplies: true, + gppString: gppConsentString, + applicableSections: gppSections }; - const request = spec.buildRequests(validBidRequests, bidderRequest); - const payload = JSON.parse(request.data); - expect(payload.user.gender).to.equal('I identify as a box of rocks'); - }); - it('handles schain object in each bidrequest (will be the same in each br)', function () { - let br = JSON.parse(JSON.stringify(validBidRequests)); - let schainConfigObject = { - 'ver': '1.0', - 'complete': 1, - 'nodes': [ - { - 'asi': 'bidderA.com', - 'sid': '00001', - 'hp': 1 + const syncs = spec.getUserSyncs({iframeEnabled: true}, [], {}, '', gppConsent); + const url = new URL(syncs[0].url); + expect(url.searchParams.get('gdpr')).to.equal('0'); + expect(url.searchParams.get('gdpr_consent')).to.equal(''); + expect(url.searchParams.get('gpp')).to.equal(encodeURIComponent(gppConsentString)); + expect(url.searchParams.get('gpp_sid')).to.equal(encodeURIComponent(gppSections)); + expect(url.searchParams.get('us_privacy')).to.equal(''); + }); + + it('should include account param when accountId is set in config', function() { + config.setConfig({ + newspassid: { + accountId: TEST_ACCOUNT_ID + } + }); + const syncs = spec.getUserSyncs({iframeEnabled: true}); + const url = new URL(syncs[0].url); + expect(url.searchParams.get('gdpr')).to.equal('0'); + expect(url.searchParams.get('gdpr_consent')).to.equal(''); + expect(url.searchParams.get('gpp')).to.equal(''); + expect(url.searchParams.get('gpp_sid')).to.equal(''); + expect(url.searchParams.get('us_privacy')).to.equal(''); + expect(url.searchParams.get('account')).to.equal(TEST_ACCOUNT_ID); + }); + + it('should have zero user syncs if coppa is true', function() { + config.setConfig({coppa: true}); + const syncs = spec.getUserSyncs({iframeEnabled: true}); + expect(syncs).to.be.empty; + }); + + it('should include all params when all are present', function() { + const consentString = 'CQO03QAQO03QAPoABABGBiEIAIAAAIAAAACQKSwAQKSgpLABApKAAAAA.QKSwAQKSgAAA.IAAA'; // purpose 1 true + const gdprConsent = { + gdprApplies: true, + consentString: consentString, + vendorData: { + purpose: { + consents: { + 1: true + } } - ] + } }; - br[0]['schain'] = schainConfigObject; - const request = spec.buildRequests(br, validBidderRequest); - const data = JSON.parse(request.data); - expect(data.source.ext).to.haveOwnProperty('schain'); - expect(data.source.ext.schain).to.deep.equal(schainConfigObject); // .deep.equal() : Target object deeply (but not strictly) equals `{a: 1}` - }); - }); - describe('interpretResponse', function () { - it('should build bid array', function () { - const request = spec.buildRequests(validBidRequests, validBidderRequest); - const result = spec.interpretResponse(validResponse, request); - expect(result.length).to.equal(1); - }); - it('should have all relevant fields', function () { - const request = spec.buildRequests(validBidRequests, validBidderRequest); - const result = spec.interpretResponse(validResponse, request); - const bid = result[0]; - expect(bid.cpm).to.equal(validResponse.body.seatbid[0].bid[0].cpm); - expect(bid.width).to.equal(validResponse.body.seatbid[0].bid[0].width); - expect(bid.height).to.equal(validResponse.body.seatbid[0].bid[0].height); - }); - it('should build bid array with usp/CCPA', function () { - let validBR = JSON.parse(JSON.stringify(validBidderRequest)); - validBR.uspConsent = '1YNY'; - const request = spec.buildRequests(validBidRequests, validBR); - const payload = JSON.parse(request.data); - expect(payload.user.ext.uspConsent).not.to.exist; - expect(payload.regs.ext.us_privacy).to.equal('1YNY'); - }); - it('should fail ok if no seatbid in server response', function () { - const result = spec.interpretResponse({}, {}); - expect(result).to.be.an('array'); - expect(result).to.be.empty; - }); - it('should fail ok if seatbid is not an array', function () { - const result = spec.interpretResponse({'body': {'seatbid': 'nothing_here'}}, {}); - expect(result).to.be.an('array'); - expect(result).to.be.empty; - }); - it('should correctly parse response where there are more bidders than ad slots', function () { - const request = spec.buildRequests(validBidRequests, validBidderRequest); - const result = spec.interpretResponse(validBidResponse1adWith2Bidders, request); - expect(result.length).to.equal(2); - }); - it('should have a ttl of 600', function () { - const request = spec.buildRequests(validBidRequests, validBidderRequest); - const result = spec.interpretResponse(validResponse, request); - expect(result[0].ttl).to.equal(300); - }); - it('should handle a valid whitelist, removing items not on the list & leaving others', function () { - config.setConfig({'newspassid': {'np_whitelist_adserver_keys': ['np_appnexus_crid', 'np_appnexus_adId']}}); - const request = spec.buildRequests(validBidRequests, validBidderRequest); - const result = spec.interpretResponse(validResponse, request); - expect(utils.deepAccess(result[0].adserverTargeting, 'np_appnexus_adv')).to.be.undefined; - expect(utils.deepAccess(result[0].adserverTargeting, 'np_appnexus_adId')).to.equal('2899ec066a91ff8-0-np-0'); - config.resetConfig(); - }); - it('should ignore a whitelist if enhancedAdserverTargeting is false', function () { - config.setConfig({'newspassid': {'np_whitelist_adserver_keys': ['np_appnexus_crid', 'np_appnexus_imp_id'], 'enhancedAdserverTargeting': false}}); - const request = spec.buildRequests(validBidRequests, validBidderRequest); - const result = spec.interpretResponse(validResponse, request); - expect(utils.deepAccess(result[0].adserverTargeting, 'np_appnexus_adv')).to.be.undefined; - expect(utils.deepAccess(result[0].adserverTargeting, 'np_appnexus_imp_id')).to.be.undefined; - config.resetConfig(); - }); - it('should correctly handle enhancedAdserverTargeting being false', function () { - config.setConfig({'newspassid': {'enhancedAdserverTargeting': false}}); - const request = spec.buildRequests(validBidRequests, validBidderRequest); - const result = spec.interpretResponse(validResponse, request); - expect(utils.deepAccess(result[0].adserverTargeting, 'np_appnexus_adv')).to.be.undefined; - expect(utils.deepAccess(result[0].adserverTargeting, 'np_appnexus_imp_id')).to.be.undefined; - config.resetConfig(); - }); - it('should add unique adId values to each bid', function() { - const request = spec.buildRequests(validBidRequests, validBidderRequest); - let validres = JSON.parse(JSON.stringify(validResponse2BidsSameAdunit)); - const result = spec.interpretResponse(validres, request); - expect(result.length).to.equal(1); - expect(result[0]['price']).to.equal(0.9); - expect(result[0]['adserverTargeting']['np_npappnexus_adId']).to.equal('2899ec066a91ff8-0-np-1'); - }); - it('should add np_auc_id (response id value)', function () { - const request = spec.buildRequests(validBidRequests, validBidderRequest); - let validres = JSON.parse(JSON.stringify(validBidResponse1adWith2Bidders)); - const result = spec.interpretResponse(validres, request); - expect(utils.deepAccess(result[0].adserverTargeting, 'np_auc_id')).to.equal(validBidResponse1adWith2Bidders.body.id); - }); - it('should correctly process an auction with 2 adunits & multiple bidders one of which bids for both adslots', function() { - let validres = JSON.parse(JSON.stringify(multiResponse1)); - let request = spec.buildRequests(multiRequest1, multiBidderRequest1.bidderRequest); - let result = spec.interpretResponse(validres, request); - expect(result.length).to.equal(4); // one of the 5 bids will have been removed - expect(result[1]['price']).to.equal(0.521); - expect(result[1]['impid']).to.equal('3025f169863b7f8'); - expect(result[1]['id']).to.equal('18552976939844999'); - expect(result[1]['adserverTargeting']['np_npappnexus_adId']).to.equal('3025f169863b7f8-0-np-2'); - validres = JSON.parse(JSON.stringify(multiResponse1)); - validres.body.seatbid[0].bid[1].price = 1.1; - validres.body.seatbid[0].bid[1].cpm = 1.1; - request = spec.buildRequests(multiRequest1, multiBidderRequest1.bidderRequest); - result = spec.interpretResponse(validres, request); - expect(result[1]['price']).to.equal(1.1); - expect(result[1]['impid']).to.equal('3025f169863b7f8'); - expect(result[1]['id']).to.equal('18552976939844681'); - expect(result[1]['adserverTargeting']['np_npappnexus_adId']).to.equal('3025f169863b7f8-0-np-1'); - }); - }); - describe('userSyncs', function () { - it('should fail gracefully if no server response', function () { - const result = spec.getUserSyncs('bad', false, emptyObject); - expect(result).to.be.empty; - }); - it('should fail gracefully if server response is empty', function () { - const result = spec.getUserSyncs('bad', [], emptyObject); - expect(result).to.be.empty; - }); - it('should append the various values if they exist', function() { - spec.buildRequests(validBidRequests, validBidderRequest); - const result = spec.getUserSyncs({iframeEnabled: true}, 'good server response', emptyObject); - expect(result).to.be.an('array'); - expect(result[0].url).to.include('publisherId=9876abcd12-3'); - expect(result[0].url).to.include('siteId=1234567890'); - }); - it('should append ccpa (usp data)', function() { - spec.buildRequests(validBidRequests, validBidderRequest); - const result = spec.getUserSyncs({iframeEnabled: true}, 'good server response', emptyObject, '1YYN'); - expect(result).to.be.an('array'); - expect(result[0].url).to.include('usp_consent=1YYN'); - }); - it('should use "" if no usp is sent to cookieSync', function() { - spec.buildRequests(validBidRequests, validBidderRequest); - const result = spec.getUserSyncs({iframeEnabled: true}, 'good server response', emptyObject); - expect(result).to.be.an('array'); - expect(result[0].url).to.include('usp_consent=&'); - }); - }); - describe('default size', function () { - it('should should return default sizes if no obj is sent', function () { - let obj = ''; - const result = defaultSize(obj); - expect(result.defaultHeight).to.equal(250); - expect(result.defaultWidth).to.equal(300); - }); - }); - describe('getGranularityKeyName', function() { - it('should return a string granularity as-is', function() { - const result = getGranularityKeyName('', 'this is it', ''); - expect(result).to.equal('this is it'); - }); - it('should return "custom" for a mediaTypeGranularity object', function() { - const result = getGranularityKeyName('', {}, ''); - expect(result).to.equal('custom'); - }); - it('should return "custom" for a mediaTypeGranularity object', function() { - const result = getGranularityKeyName('', false, 'string buckets'); - expect(result).to.equal('string buckets'); - }); - }); - describe('getGranularityObject', function() { - it('should return an object as-is', function() { - const result = getGranularityObject('', {'name': 'mark'}, '', ''); - expect(result.name).to.equal('mark'); - }); - it('should return an object as-is', function() { - const result = getGranularityObject('', false, 'custom', {'name': 'rupert'}); - expect(result.name).to.equal('rupert'); - }); - }); - describe('blockTheRequest', function() { - it('should return true if np_request is false', function() { - config.setConfig({'newspassid': {'np_request': false}}); - let result = spec.blockTheRequest(); - expect(result).to.be.true; - config.resetConfig(); - }); - it('should return false if np_request is true', function() { - config.setConfig({'newspassid': {'np_request': true}}); - let result = spec.blockTheRequest(); - expect(result).to.be.false; - config.resetConfig(); - }); - }); - describe('getPageId', function() { - it('should return the same Page ID for multiple calls', function () { - let result = spec.getPageId(); - expect(result).to.be.a('string'); - let result2 = spec.getPageId(); - expect(result2).to.equal(result); - }); - }); - describe('getBidRequestForBidId', function() { - it('should locate a bid inside a bid array', function () { - let result = spec.getBidRequestForBidId('2899ec066a91ff8', validBidRequestsMulti); - expect(result.testId).to.equal(1); - result = spec.getBidRequestForBidId('2899ec066a91ff0', validBidRequestsMulti); - expect(result.testId).to.equal(2); - }); - }); - describe('removeSingleBidderMultipleBids', function() { - it('should remove the multi bid by npappnexus for adslot 2d30e86db743a8', function() { - let validres = JSON.parse(JSON.stringify(multiResponse1)); - expect(validres.body.seatbid[0].bid.length).to.equal(3); - expect(validres.body.seatbid[0].seat).to.equal('npappnexus'); - let response = spec.removeSingleBidderMultipleBids(validres.body.seatbid); - expect(response.length).to.equal(2); - expect(response[0].bid.length).to.equal(2); - expect(response[0].seat).to.equal('npappnexus'); - expect(response[1].bid.length).to.equal(2); + const uspConsent = '1YNN'; + const gppConsentString = 'DBABMA~1YNN'; + const gppSections = '2,6'; + const gppConsent = { + gppApplies: true, + gppString: gppConsentString, + applicableSections: gppSections + }; + config.setConfig({ + newspassid: { + accountId: TEST_ACCOUNT_ID + } + }); + const syncs = spec.getUserSyncs({iframeEnabled: true}, [], gdprConsent, uspConsent, gppConsent); + const url = new URL(syncs[0].url); + expect(url.searchParams.get('gdpr')).to.equal('1'); + expect(url.searchParams.get('gdpr_consent')).to.equal(encodeURIComponent(consentString)); + expect(url.searchParams.get('gpp')).to.equal(encodeURIComponent(gppConsentString)); + expect(url.searchParams.get('gpp_sid')).to.equal(encodeURIComponent(gppSections)); + expect(url.searchParams.get('us_privacy')).to.equal(encodeURIComponent(uspConsent)); + expect(url.searchParams.get('account')).to.equal(TEST_ACCOUNT_ID); }); }); }); From 8477a9cd9c7820c3b0db8503a06144f32e72945c Mon Sep 17 00:00:00 2001 From: Erik Omlid Date: Wed, 26 Mar 2025 02:31:50 -0500 Subject: [PATCH 2/3] update to use setBidderConfig --- modules/newspassidBidAdapter.js | 4 ++-- modules/newspassidBidAdapter.md | 41 +++++++++++++++++++-------------- 2 files changed, 26 insertions(+), 19 deletions(-) diff --git a/modules/newspassidBidAdapter.js b/modules/newspassidBidAdapter.js index 170eb846da1..164d6a5f280 100644 --- a/modules/newspassidBidAdapter.js +++ b/modules/newspassidBidAdapter.js @@ -52,7 +52,7 @@ const addParamsToUrl = (url, params) => { * @returns {string|null} */ const getGlobalAccountIdOrNull = () => { - const globalAccountId = config.getConfig('newspassid.accountId'); + const globalAccountId = config.getConfig('accountId'); if (globalAccountId) return globalAccountId; return null; }; @@ -121,7 +121,7 @@ export const spec = { ttl: DEFAULT_TTL, ad: bid.adm, meta: { - advertiserDomains: bid.adomain || [] + advertiserDomains: bid.adomain || [], } }); }); diff --git a/modules/newspassidBidAdapter.md b/modules/newspassidBidAdapter.md index 89d7bb97b41..b005a0c9610 100644 --- a/modules/newspassidBidAdapter.md +++ b/modules/newspassidBidAdapter.md @@ -16,25 +16,32 @@ Bid adapter to connect to Local Media Consortium's NewsPassID (NPID) demand sour | Key | Required | Example | Description | | --- | -------- | ------- | ----------- | -| `accountId` | yes | `"01952070-3b08-7d30-9daf-93f1f7e4247f"` | this is the account ID associated with your publisher account with NewsPassID initiative | | `groupId` | yes | `"leftrail-mobile-1"` | For associating the ad placement inventory with demand. This ID must be predefined by NewsPassID provider | +| `accountId` | no | `"123456"` | this is the account ID associated with your publisher account with NewsPassID initiative | # Test Parameters +```javascript +pbjs.setBidderConfig({ + bidders: ['newspassid'], + config: { + accountId: '123456', + } +}); + +var adUnits = [ + { + code: 'newspass-test-div', + sizes: [[300, 250]], + bids: [ + { + bidder: 'newspassid', + params: { + accountId: '123456', // optional if you set in bidder config + groupId: 'test-group1' + }, + } + ] + } +] ``` -var adUnits = [{ - code: 'newspass-test-div', - sizes: [[300, 250]], - bids: [{ - bidder: 'newspassid', - params: { - accountId: '123456', - groupId: 'test-group1' - }, - }] -}] -``` - -### Note: - -Please contact us at techsupport@newspassid.com for any assistance testing your implementation before going live into production. From 090dcec48f79c3ffdb3366d935d23a985a3162bd Mon Sep 17 00:00:00 2001 From: Erik Omlid Date: Thu, 27 Mar 2025 14:41:27 -0500 Subject: [PATCH 3/3] revert to legacy params publisherId, placementId --- modules/newspassidBidAdapter.js | 37 ++++----- modules/newspassidBidAdapter.md | 20 ++--- .../spec/modules/newspassidBidAdapter_spec.js | 79 ++++++++++++++----- 3 files changed, 87 insertions(+), 49 deletions(-) diff --git a/modules/newspassidBidAdapter.js b/modules/newspassidBidAdapter.js index 164d6a5f280..fac9841318d 100644 --- a/modules/newspassidBidAdapter.js +++ b/modules/newspassidBidAdapter.js @@ -22,8 +22,8 @@ const converter = ortbConverter({ imp(buildImp, bidRequest, context) { const imp = buildImp(bidRequest, context); deepSetValue(imp, 'ext.newspassid', { - accountId: resolveNewpassidAccountId(bidRequest), - groupId: bidRequest.params.groupId, + publisher: resolveNewpassidPublisherId(bidRequest), + placementId: bidRequest.params.placementId, }) return imp; }, @@ -48,28 +48,28 @@ const addParamsToUrl = (url, params) => { }; /** - * Get the global accountId for the newspassid bidder + * Get the global publisherId for the newspassid bidder * @returns {string|null} */ -const getGlobalAccountIdOrNull = () => { - const globalAccountId = config.getConfig('accountId'); - if (globalAccountId) return globalAccountId; +const getGlobalPublisherIdOrNull = () => { + const globalPublisherId = config.getConfig('newspassid.publisherId'); + if (globalPublisherId) return globalPublisherId; return null; }; /** - * Resolve the accountId for the newspassid bidder + * Resolve the publisherId for the newspassid bidder * @param {BidRequest|undefined} bidRequest * @returns {string|null} */ -export const resolveNewpassidAccountId = (bidRequest) => { - if (typeof bidRequest !== 'object') return getGlobalAccountIdOrNull(); +export const resolveNewpassidPublisherId = (bidRequest) => { + if (typeof bidRequest !== 'object') return getGlobalPublisherIdOrNull(); - // get accountId from bidRequest params + // get publisherId from bidRequest params const { params } = bidRequest; - if (params?.accountId) return params?.accountId; + if (params?.publisherId) return params?.publisherId; - return getGlobalAccountIdOrNull(); + return getGlobalPublisherIdOrNull(); }; /** @@ -80,9 +80,9 @@ export const spec = { gvlid: GVL_ID, supportedMediaTypes: [BANNER, NATIVE, VIDEO], - isBidRequestValid: function(bid) { - const accountId = resolveNewpassidAccountId(bid); - return !!(bid.params && accountId && bid.params.groupId); + isBidRequestValid: function(bidRequest) { + const publisherId = resolveNewpassidPublisherId(bidRequest); + return !!(bidRequest.params && publisherId && bidRequest.params.placementId); }, buildRequests: function(bidRequests, bidderRequest) { @@ -144,9 +144,10 @@ export const spec = { us_privacy: encodeURIComponent(uspConsent || ''), }; - const globalAccountId = resolveNewpassidAccountId({}); - if (globalAccountId) { - params.account = globalAccountId; + const globalPublisherId = resolveNewpassidPublisherId({}); + if (globalPublisherId) { + // "publisher" is a convention on the server side + params.publisher = globalPublisherId; } let syncs = []; diff --git a/modules/newspassidBidAdapter.md b/modules/newspassidBidAdapter.md index b005a0c9610..aff1d902c3e 100644 --- a/modules/newspassidBidAdapter.md +++ b/modules/newspassidBidAdapter.md @@ -10,22 +10,22 @@ Maintainer: techsupport@newspassid.com Description =========== -Bid adapter to connect to Local Media Consortium's NewsPassID (NPID) demand source(s). +Bid adapter to connect to Local Media Consortium's NewsPassID (NPID) demand source(s). This adapter runs bid requests through ad server technology built and maintained by Aditude. # Bid Parameters | Key | Required | Example | Description | | --- | -------- | ------- | ----------- | -| `groupId` | yes | `"leftrail-mobile-1"` | For associating the ad placement inventory with demand. This ID must be predefined by NewsPassID provider | -| `accountId` | no | `"123456"` | this is the account ID associated with your publisher account with NewsPassID initiative | +| `publisherId` | yes | `"123456"` | For associating the publisher account for the NewsPassID initiative | +| `placementId` | yes | `"leftrail-mobile-1"` | For associating the ad placement inventory with demand. This ID must be predefined by NewsPassID provider | + # Test Parameters ```javascript -pbjs.setBidderConfig({ - bidders: ['newspassid'], - config: { - accountId: '123456', +pbjs.setConfig({ + newspassid: { + publisherId: '123456', } }); @@ -37,9 +37,9 @@ var adUnits = [ { bidder: 'newspassid', params: { - accountId: '123456', // optional if you set in bidder config - groupId: 'test-group1' - }, + publisherId: '123456', // optional if you set in bidder config + placementId: 'test-group1' + } } ] } diff --git a/test/spec/modules/newspassidBidAdapter_spec.js b/test/spec/modules/newspassidBidAdapter_spec.js index 5f82ccfae5f..dec630f3f6a 100644 --- a/test/spec/modules/newspassidBidAdapter_spec.js +++ b/test/spec/modules/newspassidBidAdapter_spec.js @@ -1,16 +1,17 @@ import { spec } from 'modules/newspassidBidAdapter.js'; import { config } from 'src/config.js'; import { deepClone } from 'src/utils.js'; -import { resolveNewpassidAccountId } from '../../../modules/newspassidBidAdapter'; - -const TEST_ACCOUNT_ID = '123456'; +import { resolveNewpassidPublisherId } from '../../../modules/newspassidBidAdapter'; describe('newspassidBidAdapter', function () { + const TEST_PUBLISHER_ID = '123456'; + const TEST_PLACEMENT_ID = 'test-group1'; + const validBidRequest = { bidder: 'newspassid', params: { - accountId: TEST_ACCOUNT_ID, - groupId: 'test-group1' + publisherId: TEST_PUBLISHER_ID, + placementId: TEST_PLACEMENT_ID }, mediaTypes: { banner: { @@ -61,22 +62,22 @@ describe('newspassidBidAdapter', function () { }); }); - describe('resolveNewpassidAccountId', function() { + describe('resolveNewpassidPublisherId', function() { afterEach(() => { config.resetConfig(); }); - it('should return null if no bidrequest object or no global account id set', function() { - expect(resolveNewpassidAccountId()).to.equal(null); + it('should return null if no bidrequest object or no global publisherId set', function() { + expect(resolveNewpassidPublisherId()).to.equal(null); }); - it('should return global account id if no bidrequest object and global account id set', function() { + it('should return global publisherId if no bidrequest object and global publisherId set', function() { config.setConfig({ newspassid: { - accountId: TEST_ACCOUNT_ID + publisherId: TEST_PUBLISHER_ID } }); - expect(resolveNewpassidAccountId()).to.equal(TEST_ACCOUNT_ID); + expect(resolveNewpassidPublisherId()).to.equal(TEST_PUBLISHER_ID); }); }); @@ -85,15 +86,15 @@ describe('newspassidBidAdapter', function () { expect(spec.isBidRequestValid(validBidRequest)).to.be.true; }); - it('should return false when accountId is missing', function() { + it('should return false when publisherId is missing', function() { const bid = deepClone(validBidRequest); - delete bid.params.accountId; + delete bid.params.publisherId; expect(spec.isBidRequestValid(bid)).to.be.false; }); - it('should return false when groupId is missing', function() { + it('should return false when placementId is missing', function() { const bid = deepClone(validBidRequest); - delete bid.params.groupId; + delete bid.params.placementId; expect(spec.isBidRequestValid(bid)).to.be.false; }); }); @@ -110,7 +111,43 @@ describe('newspassidBidAdapter', function () { it('should include bidder params in ortb2 request', function() { const requests = spec.buildRequests([validBidRequest], validBidderRequest); const data = requests[0].data; - expect(data.imp[0].ext.newspassid.accountId).to.equal(TEST_ACCOUNT_ID); + expect(data.imp[0].ext.newspassid.publisher).to.equal(TEST_PUBLISHER_ID); + expect(data.imp[0].ext.newspassid.placementId).to.equal(TEST_PLACEMENT_ID); + }); + + it('should use global publisherId when not set in bid params', function() { + const validBidRequestWithoutPublisherId = { + ...validBidRequest, + params: { + placementId: TEST_PLACEMENT_ID + }, + }; + config.setConfig({ + newspassid: { + publisherId: TEST_PUBLISHER_ID + } + }); + const requests = spec.buildRequests([validBidRequestWithoutPublisherId], validBidderRequest); + const data = requests[0].data; + expect(data.imp[0].ext.newspassid.publisher).to.equal(TEST_PUBLISHER_ID); + expect(data.imp[0].ext.newspassid.placementId).to.equal(TEST_PLACEMENT_ID); + }); + + it('should use publisherId from bidRequest first over global publisherId', function() { + config.setConfig({ + newspassid: { + publisherId: TEST_PUBLISHER_ID + } + }); + const validBidRequestWithDifferentPublisherId = { + ...validBidRequest, + params: { + publisherId: 'publisherId123' + } + }; + const requests = spec.buildRequests([validBidRequestWithDifferentPublisherId], validBidderRequest); + const data = requests[0].data; + expect(data.imp[0].ext.newspassid.publisher).to.equal('publisherId123'); }); it('should handle multiple bid requests', function() { @@ -248,10 +285,10 @@ describe('newspassidBidAdapter', function () { expect(url.searchParams.get('us_privacy')).to.equal(''); }); - it('should include account param when accountId is set in config', function() { + it('should include publisher param when publisherId is set in config', function() { config.setConfig({ newspassid: { - accountId: TEST_ACCOUNT_ID + publisherId: TEST_PUBLISHER_ID } }); const syncs = spec.getUserSyncs({iframeEnabled: true}); @@ -261,7 +298,7 @@ describe('newspassidBidAdapter', function () { expect(url.searchParams.get('gpp')).to.equal(''); expect(url.searchParams.get('gpp_sid')).to.equal(''); expect(url.searchParams.get('us_privacy')).to.equal(''); - expect(url.searchParams.get('account')).to.equal(TEST_ACCOUNT_ID); + expect(url.searchParams.get('publisher')).to.equal(encodeURIComponent(TEST_PUBLISHER_ID)); }); it('should have zero user syncs if coppa is true', function() { @@ -293,7 +330,7 @@ describe('newspassidBidAdapter', function () { }; config.setConfig({ newspassid: { - accountId: TEST_ACCOUNT_ID + publisherId: TEST_PUBLISHER_ID } }); const syncs = spec.getUserSyncs({iframeEnabled: true}, [], gdprConsent, uspConsent, gppConsent); @@ -303,7 +340,7 @@ describe('newspassidBidAdapter', function () { expect(url.searchParams.get('gpp')).to.equal(encodeURIComponent(gppConsentString)); expect(url.searchParams.get('gpp_sid')).to.equal(encodeURIComponent(gppSections)); expect(url.searchParams.get('us_privacy')).to.equal(encodeURIComponent(uspConsent)); - expect(url.searchParams.get('account')).to.equal(TEST_ACCOUNT_ID); + expect(url.searchParams.get('publisher')).to.equal(encodeURIComponent(TEST_PUBLISHER_ID)); }); }); });