diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 104d9a38132..d4c34929569 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -9,7 +9,9 @@ }, "postCreateCommand": "bash .devcontainer/postCreate.sh", - + // Make is-docker work again + "postStartCommand": "test -f /.dockerenv || sudo touch /.dockerenv", + // Set *default* container specific settings.json values on container create. "settings": {}, diff --git a/.eslintrc.js b/.eslintrc.js index 06a5e81d9f5..fc3ad3afe66 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -19,7 +19,6 @@ module.exports = { 'import' ], globals: { - '$$PREBID_GLOBAL$$': false, 'BROWSERSTACK_USERNAME': false, 'BROWSERSTACK_KEY': false, 'FEATURES': 'readonly', diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 9b7c39d53d0..584f6f8894a 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -13,10 +13,10 @@ name: "CodeQL" on: push: - branches: [ "master" ] + branches: [ "master"] pull_request: # The branches below must be a subset of the branches above - branches: [ "master" ] + branches: [ "master"] schedule: - cron: '22 11 * * 0' diff --git a/README.md b/README.md index bbc0d79ab41..58007519b15 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ [![Build Status](https://circleci.com/gh/prebid/Prebid.js.svg?style=svg)](https://circleci.com/gh/prebid/Prebid.js) [![Percentage of issues still open](http://isitmaintained.com/badge/open/prebid/Prebid.js.svg)](http://isitmaintained.com/project/prebid/Prebid.js "Percentage of issues still open") [![Coverage Status](https://coveralls.io/repos/github/prebid/Prebid.js/badge.svg)](https://coveralls.io/github/prebid/Prebid.js) -[![Total Alerts](https://img.shields.io/lgtm/alerts/g/prebid/Prebid.js.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/prebid/Prebid.js/alerts/) # Prebid.js @@ -109,6 +108,8 @@ pbjs.requestBids({ ## Install + + $ git clone https://github.com/prebid/Prebid.js.git $ cd Prebid.js $ npm ci @@ -359,3 +360,4 @@ Prebid.js is supported on IE11 and modern browsers until 5.x. 6.x+ transpiles to ### Governance Review our governance model [here](https://github.com/prebid/Prebid.js/tree/master/governance.md). +### END diff --git a/features.json b/features.json index c0f7e4d75a9..ccb2166a05f 100644 --- a/features.json +++ b/features.json @@ -1,3 +1,4 @@ [ - "NATIVE" + "NATIVE", + "VIDEO" ] diff --git a/integrationExamples/gpt/captifyRtdProvider_example.html b/integrationExamples/gpt/captifyRtdProvider_example.html deleted file mode 100644 index 955fbf8be70..00000000000 --- a/integrationExamples/gpt/captifyRtdProvider_example.html +++ /dev/null @@ -1,167 +0,0 @@ - - - - - - - - - - - - - - - - -
-

- Module will add key/value pairs in ad calls. - Check out for captify_segments key in the payload sent to Xandr to endpoint https://ib.adnxs.com/ut/v3/prebid : keywords.key[captify_segments] should have an array of string as value. - This array will have Xandr RTSS segment ids. -

-
-

Basic Prebid.js Example with CaptifyRTD

-
Div-1
-
- -
- - - diff --git a/integrationExamples/gpt/fledge_example.html b/integrationExamples/gpt/fledge_example.html new file mode 100644 index 00000000000..5059e03daef --- /dev/null +++ b/integrationExamples/gpt/fledge_example.html @@ -0,0 +1,103 @@ + + + + + + + + + +

Prebid.js FLEDGE+GPT Example

+ +
Div-1
+
+ +
+ + diff --git a/integrationExamples/gpt/growthcode.html b/integrationExamples/gpt/growthcode.html index ede51d2d869..d8ad6c4a5af 100644 --- a/integrationExamples/gpt/growthcode.html +++ b/integrationExamples/gpt/growthcode.html @@ -7,10 +7,13 @@ - - - - - - - - - - -

Prebid.js Test

-
Div-1
-
- -
- - diff --git a/integrationExamples/gpt/lemma_sample.html b/integrationExamples/gpt/lemma_sample.html new file mode 100755 index 00000000000..bdf72eeb484 --- /dev/null +++ b/integrationExamples/gpt/lemma_sample.html @@ -0,0 +1,129 @@ + + + + + + + + + + + + +
+ + +
+ + + \ No newline at end of file diff --git a/integrationExamples/gpt/neuwoRtdProvider_example.html b/integrationExamples/gpt/neuwoRtdProvider_example.html index f33b9f94c67..142a7c39613 100644 --- a/integrationExamples/gpt/neuwoRtdProvider_example.html +++ b/integrationExamples/gpt/neuwoRtdProvider_example.html @@ -6,9 +6,6 @@ \ No newline at end of file diff --git a/integrationExamples/gpt/prebidServer_fledge_example.html b/integrationExamples/gpt/prebidServer_fledge_example.html new file mode 100644 index 00000000000..8523c0f2920 --- /dev/null +++ b/integrationExamples/gpt/prebidServer_fledge_example.html @@ -0,0 +1,111 @@ + + + + + + + + + +

Prebid.js FLEDGE+GPT Example

+ +
Div-1
+
+ +
+ + diff --git a/integrationExamples/gpt/relevadRtdProvider_example.html b/integrationExamples/gpt/relevadRtdProvider_example.html new file mode 100644 index 00000000000..daa6d27cf33 --- /dev/null +++ b/integrationExamples/gpt/relevadRtdProvider_example.html @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + + +

Basic Prebid.js Example

+
Div-1
+
+ +
+ +
Div-2
+
+ +
+ + \ No newline at end of file diff --git a/integrationExamples/gpt/userId_example.html b/integrationExamples/gpt/userId_example.html index 4f94c73c281..fdde731247c 100644 --- a/integrationExamples/gpt/userId_example.html +++ b/integrationExamples/gpt/userId_example.html @@ -251,17 +251,31 @@ { "name": "uid2", "params": { + "uid2ApiBase": "https://operator-integ.uidapi.com", // Omit this setting for production "uid2Token": { - "advertising_token": "example token", - "refresh_token": "aslkdjaslkjdaslkhj", - "identity_expires": Date.now() + 60*1000, + "advertising_token": "advertising token goes here", + "refresh_token": "refresh token goes here", + "identity_expires": Date.now() + 60*1000, // These timestamps should be from the token generate response "refresh_from": Date.now() - 10*1000, "refresh_expires": Date.now() + 12*60*60*1000, - "refresh_response_key": null + "refresh_response_key": "refresh key goes here" } } - } - , + }, + { + "name": "euid", + "params": { + "euidApiBase": "https://integ.euid.eu", // Omit this setting for production + "euidToken": { + "advertising_token": "advertising token goes here", + "refresh_token": "refresh token goes here", + "identity_expires": Date.now() + 60*1000, // These timestamps should be from the token generate response + "refresh_from": Date.now() - 10*1000, + "refresh_expires": Date.now() + 12*60*60*1000, + "refresh_response_key": "refresh key goes here" + } + } + }, { "name": "imuid", "params": { diff --git a/integrationExamples/gpt/x-domain/creative.html b/integrationExamples/gpt/x-domain/creative.html index bea8b70b4fe..2216d0ed6ae 100644 --- a/integrationExamples/gpt/x-domain/creative.html +++ b/integrationExamples/gpt/x-domain/creative.html @@ -1,6 +1,6 @@ - - - - - - - - - - - - -
-

Note: for this example to work, you need access to a bid simulation tool from your MASS enabled Exchange partner.

-
- -
-
- - diff --git a/integrationExamples/videoModule/eventsUI/eventsUI.css b/integrationExamples/videoModule/eventsUI/eventsUI.css new file mode 100644 index 00000000000..ccfe17ca4c6 --- /dev/null +++ b/integrationExamples/videoModule/eventsUI/eventsUI.css @@ -0,0 +1,205 @@ +html { + width: 100%; +} +body { + background: #ccc; + margin: 20px 5px; + font: 42px/60px Helvetica, Arial, sans-serif; + min-width: 320px; + max-width: 1920px; +} +#player:first-child { + width: 100%; +} + +#player { + margin: auto; + background: rgba(0, 0, 20, 0.8); +} +#eventsLog.group-player-disabled .group-player, +#eventsLog.group-media-disabled .group-media, +#eventsLog.group-ads-disabled .group-ads, +#eventsLog.group-auction-disabled .group-auction, +#eventsLog.group-adRequest-disabled .group-adRequest, +#eventsLog.group-adBreak-disabled .group-adBreak, +#eventsLog.group-related-disabled .group-related, +#eventsLog.group-ping-disabled .group-ping, +#eventsLog.group-unknown-disabled .group-unknown, +#eventsLog.group-quickPeek-disabled .group-quickPeek, +#eventsLog.group-provider-disabled .group-provider, +#eventsLog.group-video-disabled .group-video { + display: none; +} +.input-field { + padding: 0 0.25em; + margin: 0 0 0 4px; + border: 0; + background-color: #232323; + color: #F8F8F8; +} +.input-field:invalid { + text-decoration: underline; + text-decoration-color: red; +} +#eventsLog .sequence > .pre.filter-not-matched { + display: none; + opacity: 0.2; +} +a.button { + -webkit-appearance: button; + cursor: pointer; + margin: 2px; + background: #ccc; + border-color: rgb(216, 216, 216) rgb(209, 209, 209) rgb(186, 186, 186); + border-style: solid; + border-width: 1px; + padding: 1px 7px 2px; + color: inherit; + text-decoration: inherit; + user-select: none; +} +.right { + float: right; +} +.disabled { + opacity: 0.5; +} +.nav.disabled { + user-select: none; + cursor: default; +} +.block { + margin: 5px; + background-color: #eee; +} +div.mode-player { + background: #acc; +} +div.mode-ads { + background: #fea; +} +div.sequence { + display: block; +} +.group-player { + background: #acc; +} +.group-media { + background: #fa6; +} +.group-ads { + background: #fcc; +} +.group-auction { + background: #228; + color: #eee; +} +pre { + margin: 0; +} +div.toggle-block pre { + margin: 1px 3px; +} +.events-block { + min-height: 1440px; +} +.events-block .pre, +.toggle-block pre { + display: inline-block; + padding: 0 5px; + margin: 1px 3px 1px 20px; + border-radius: 5px; + font-family: monospace; + white-space: pre; +} +.pre.group-ads, +.pre.group-media, +.pre.group-player, +.pre.group-auction, +.pre.event-ready { + display: inline-block; + padding: 0 5px; + margin: 1px 3px 1px 20px; + border-radius: 5px; + font-family: monospace; + white-space: pre; +} + +.list-events.events-block .pre.group-player, +.list-events.events-block .pre.group-media, +.list-events.events-block .pre.group-ads, +.list-events.events-block .pre.group-auction { + display: block; + margin: 0; + padding: 1px 10px; + border-radius: 0; +} + +.pre.event-playlistItem { + margin: 1px 3px 1px 10px; +} +.pre.event-adBreakStart, +.pre.event-adBreakEnd { + margin: 1px 3px 1px 20px; +} +.pre.event-adBreakStart, +.pre.event-ready, +.pre.event-adImpression, +.pre.event-adError, +.pre.event-adWarning { + font-weight: 800; +} +.pre pre { + display: inline; + margin: 0 0 0 20px; +} +.toggle { + cursor: pointer; + user-select: none; +} +button, +input, +optgroup, +select, +textarea { + color: inherit; + font: inherit; +} +button { + overflow: visible; + padding: 1px 7px 2px; +} +button, +select { + text-transform: none; +} +button, +html input[type="button"], +input[type="reset"], +input[type="submit"] { + -webkit-appearance: button; + cursor: pointer; + margin: 2px; +} +button[disabled], +html input[disabled] { + cursor: default; +} +button::-moz-focus-inner, +input::-moz-focus-inner { + border: 0; + padding: 0; +} +label { + display: inline-block; + max-width: 100%; + margin-bottom: 5px; + font-weight: bold; +} + +@media only screen and (min-device-width : 320px) and (max-device-width : 768px) { + body { + font-size: 2vw; + line-height: 3vw; + } +} diff --git a/integrationExamples/videoModule/eventsUI/eventsUI.js b/integrationExamples/videoModule/eventsUI/eventsUI.js new file mode 100644 index 00000000000..618a4774765 --- /dev/null +++ b/integrationExamples/videoModule/eventsUI/eventsUI.js @@ -0,0 +1,181 @@ +"use strict"; + +let sequenceCount = 0; +const eventLogGroups = {}; +const Uint8Array = window.Uint8Array; +const TimeRanges = window.TimeRanges; + +function stringify(value, replacer, space) { + try { + return truncate(JSON.stringify(value, replacer || stringifyReplacer(value), space), 100000); + } catch (error) { + return `[${error}]`; + } +} + +function truncate(str, length) { + return (str && str.length) > length ? (str.substr(0, length) + + '\n... Event truncated due to length (see console for complete output)') : str; +} + +function stringifyReplacer(parentValue) { + const references = []; + const safeResults = []; + let complexity = 0; + return function stringifyKeyValue(key, value) { + if (typeof value === 'object') { + if (value === null || value instanceof Date || value instanceof RegExp) { + return value; + } + if (!!Uint8Array && value instanceof Uint8Array) { + // Stub values of Arrays with more than 1000 items + let str = ('' + value); + str = (str.length > 40 ? (str.substr(0, 40) + '...(see console)') : str); + return `Uint8Array(${value.length}) [${str}]`; + } + if (!!TimeRanges && value instanceof TimeRanges) { + const ranges = []; + for (let i = 0; i < value.length; i++) { + ranges[i] = `start(${i}) = ${value.start(i)} end(${i}) = ${value.end(i)}`; + } + return `TimeRanges(${value.length}) [${ranges}]`; + } + if (value === parentValue && complexity > 0) { + return ''; + } + const referenceIndex = references.indexOf(value); + if (referenceIndex !== -1) { + // Duplicate reference found + const safe = safeResults[referenceIndex]; + if (safe) { + return safe; + } + try { + // Test for circular references + JSON.stringify(value); + } catch (error) { + return (safeResults[referenceIndex] = '<' + value + '...(see console)>'); + } + safeResults[referenceIndex] = value; + } + if (complexity++ > 10000) { + return ''; + } + references.push(value); + return value; + } + if (typeof value === 'function') { + return `${value}`; + } + return value; + }; +} + +function createEventSequenceElement(eventGroup) { + const element = document.createElement('div'); + element.classList.add('sequence', `mode-${eventGroup}`); + element.setAttribute('data-sequence', `${sequenceCount++}`); + return element; +} + +function appendSequenceElement(container, element) { + container.appendChild(element); +} + +function textContentGrouped(inEvent, group) { + if (group) { + return `${inEvent} (${group[inEvent]})`; + } + return inEvent; +} + +function appendEvent(container, currentEventType, currentEventGroup, data) { + const div = document.createElement('div'); + div.classList.add('group-' + currentEventGroup, 'event-' + currentEventType, 'pre'); + div.textContent = textContentGrouped(currentEventType); + div.setAttribute('title', `${currentEventGroup} event "${currentEventType}"`); + div.setAttribute('tabindex', '0'); + const theData = Object.assign({}, data); + div.onclick = div.onkeyup = function(e) { + if (e && e.keyCode && e.keyCode !== 13) { + return; + } + + console.log(theData); + div.textContent = ((div.expanded = !div.expanded)) ? + textContentExpanded(currentEventType, [theData]) : textContentGrouped(currentEventType); + if (e) { + e.preventDefault(); + } + return [theData]; + }; + container.appendChild(div); + return div; +} + +function textContentExpanded(inEvent, allData) { + return `${inEvent} (${allData.map((item, i) => + (allData.length > 1 ? `[${i}] = ` : '') + stringify(item, null, 4)).join('\n')})`; +} + +function incrementEvent(group, currentEventType, currentEventGroup, div, datum) { + group[currentEventType]++; + div.textContent = textContentGrouped(currentEventType, group); + const logPreviousEvents = div.onclick; + const scopedDatum = Object.assign({}, datum); + div.onclick = div.onkeyup = function(e) { + if (e && e.keyCode && e.keyCode !== 13) { + return; + } + + const allData = logPreviousEvents(); + allData.push(scopedDatum); + console.log(scopedDatum); + div.textContent = (div.expanded) ? textContentExpanded(currentEventType, allData) : textContentGrouped(currentEventType, group); + if (e) { + e.preventDefault(); + } + return allData; + }; +} + +function getGenericEventHandler() { + const logContainer = document.querySelector('#eventsLog'); + let currentEventGroup = ''; + let currentEventType = ''; + let lastEvent = ''; + let lastGroup; + const genericEventHandler = function(e, type, eventGroup) { + currentEventGroup = eventGroup; + currentEventType = type; + + let group = eventLogGroups[eventGroup]; + if (!group || group !== lastGroup) { + const beforeReadyElement = createEventSequenceElement(currentEventGroup); + appendSequenceElement(logContainer, beforeReadyElement); + group = eventLogGroups[currentEventGroup] = { + eventGroup: currentEventGroup, + event: currentEventType, + container: logContainer, + eventElement: beforeReadyElement + }; + lastGroup = lastGroup || group; + } + if (lastEvent === currentEventType && !(/^(?:meta|hlsBufferAppend)/).test(currentEventType)) { + incrementEvent(group, currentEventType, currentEventGroup, group.pre, e); + } else { + const eventElement = createEventSequenceElement(currentEventGroup); + group[currentEventType] = 1; + group.eventElement = eventElement; + group.lastEventGroup = currentEventGroup; + group.pre = appendEvent(eventElement, currentEventType, currentEventGroup, e); + appendSequenceElement(group.container, eventElement); + } + lastEvent = currentEventType; + lastGroup = group; + }; + + return genericEventHandler; +} + +window.getGenericEventHandler = getGenericEventHandler; diff --git a/integrationExamples/videoModule/jwplayer/eventsUI.html b/integrationExamples/videoModule/jwplayer/eventsUI.html new file mode 100644 index 00000000000..69ace5f6c2b --- /dev/null +++ b/integrationExamples/videoModule/jwplayer/eventsUI.html @@ -0,0 +1,295 @@ + + + + + + + + + JW Player Event UI + + + + + + + +
+
+
+ +
+ + + + diff --git a/integrationExamples/videoModule/videojs/eventsUI.html b/integrationExamples/videoModule/videojs/eventsUI.html new file mode 100644 index 00000000000..04e0ca9eaf8 --- /dev/null +++ b/integrationExamples/videoModule/videojs/eventsUI.html @@ -0,0 +1,319 @@ + + + + + + + + + + + + + + + + Video.JS Event UI + + + + + + + +
+ + +
+ +
+ + + + diff --git a/libraries/analyticsAdapter/AnalyticsAdapter.js b/libraries/analyticsAdapter/AnalyticsAdapter.js index b6e270b3c3c..e1933d215e4 100644 --- a/libraries/analyticsAdapter/AnalyticsAdapter.js +++ b/libraries/analyticsAdapter/AnalyticsAdapter.js @@ -134,13 +134,7 @@ export default function AnalyticsAdapter({ url, analyticsType, global, handler } handlers = Object.fromEntries( Array.from(trackedEvents) .map((ev) => { - const handler = ev === CONSTANTS.EVENTS.AUCTION_INIT - ? (args) => { - // TODO: remove this special case in v8 - args.config = typeof config === 'object' ? config.options || {} : {}; - this.enqueue({eventType: ev, args}); - } - : (args) => this.enqueue({eventType: ev, args}); + const handler = (args) => this.enqueue({eventType: ev, args}); events.on(ev, handler); return [ev, handler]; }) diff --git a/libraries/appnexusKeywords/anKeywords.js b/libraries/appnexusKeywords/anKeywords.js new file mode 100644 index 00000000000..5dc0b453253 --- /dev/null +++ b/libraries/appnexusKeywords/anKeywords.js @@ -0,0 +1,140 @@ +import {_each, deepAccess, getValueString, isArray, isStr, mergeDeep, isNumber} from '../../src/utils.js'; +import {getAllOrtbKeywords} from '../keywords/keywords.js'; +import {CLIENT_SECTIONS} from '../../src/fpd/oneClient.js'; + +const ORTB_SEGTAX_KEY_MAP = { + 526: '1plusX', + 527: '1plusX', + 541: 'captify_segments', + 540: 'perid' +}; +const ORTB_SEG_PATHS = ['user.data'].concat( + CLIENT_SECTIONS.map((prefix) => `${prefix}.content.data`) +); + +/** + * Converts an object of arrays (either strings or numbers) into an array of objects containing key and value properties + * normally read from bidder params + * eg { foo: ['bar', 'baz'], fizz: ['buzz'] } + * becomes [{ key: 'foo', value: ['bar', 'baz']}, {key: 'fizz', value: ['buzz']}] + * @param {Object} keywords object of arrays representing keyvalue pairs + * @param {string} paramName name of parent object (eg 'keywords') containing keyword data, used in error handling + * @returns {Array<{key, value}>} + */ +export function transformBidderParamKeywords(keywords, paramName = 'keywords') { + const arrs = []; + + _each(keywords, (v, k) => { + if (isArray(v)) { + let values = []; + _each(v, (val) => { + val = getValueString(paramName + '.' + k, val); + if (val || val === '') { + values.push(val); + } + }); + v = values; + } else { + v = getValueString(paramName + '.' + k, v); + if (isStr(v)) { + v = [v]; + } else { + return; + } // unsuported types - don't send a key + } + v = v.filter(kw => kw !== '') + const entry = {key: k} + if (v.length > 0) { + entry.value = v; + } + arrs.push(entry); + }); + + return arrs; +} + +// converts a comma separated list of keywords into the standard keyword object format used in appnexus bid params +// 'genre=rock,genre=pop,pets=dog,music' goes to { 'genre': ['rock', 'pop'], 'pets': ['dog'], 'music': [''] } +export function convertKeywordStringToANMap(keyStr) { + if (isStr(keyStr) && keyStr !== '') { + // will split based on commas and will eat white space before/after the comma + return convertKeywordsToANMap(keyStr.split(/\s*(?:,)\s*/)); + } else { + return {} + } +} + +/** + * @param {Array} kwarray: keywords as an array of strings + * @return {{}} appnexus-style keyword map + */ +function convertKeywordsToANMap(kwarray) { + const result = {}; + kwarray.forEach(kw => { + // if = exists, then split + if (kw.indexOf('=') !== -1) { + let kwPair = kw.split('='); + let key = kwPair[0]; + let val = kwPair[1]; + + // then check for existing key in result > if so add value to the array > if not, add new key and create value array + if (result.hasOwnProperty(key)) { + result[key].push(val); + } else { + result[key] = [val]; + } + } else { + if (!result.hasOwnProperty(kw)) { + result[kw] = []; + } + } + }) + return result; +} + +/** + * @param ortb2 + * @return {{}} appnexus-style keyword map using all keywords contained in ortb2 + */ +export function getANMapFromOrtbKeywords(ortb2) { + return convertKeywordsToANMap(getAllOrtbKeywords(ortb2)); +} + +export function getANKewyordParamFromMaps(...anKeywordMaps) { + return transformBidderParamKeywords( + mergeDeep(...anKeywordMaps.map(kwMap => Object.fromEntries( + Object.entries(kwMap || {}) + .map(([k, v]) => [k, (isNumber(v) || isStr(v)) ? [v] : v]) + ))) + ) +} + +export function getANKeywordParam(ortb2, ...anKeywordsMaps) { + return getANKewyordParamFromMaps( + getANMapFromOrtbKeywords(ortb2), + getANMapFromOrtbSegments(ortb2), + ...anKeywordsMaps + ) +} + +export function getANMapFromOrtbSegments(ortb2) { + let ortbSegData = {}; + ORTB_SEG_PATHS.forEach(path => { + let ortbSegsArrObj = deepAccess(ortb2, path) || []; + ortbSegsArrObj.forEach(segObj => { + // only read segment data from known sources + const segtax = ORTB_SEGTAX_KEY_MAP[deepAccess(segObj, 'ext.segtax')]; + if (segtax) { + segObj.segment.forEach(seg => { + // if source was in multiple locations of ortb or had multiple segments in same area, stack them together into an array + if (ortbSegData[segtax]) { + ortbSegData[segtax].push(seg.id); + } else { + ortbSegData[segtax] = [seg.id] + } + }); + } + }); + }); + return ortbSegData; +} diff --git a/libraries/categoryTranslationMapping/index.js b/libraries/categoryTranslationMapping/index.js new file mode 100644 index 00000000000..13b10423450 --- /dev/null +++ b/libraries/categoryTranslationMapping/index.js @@ -0,0 +1,100 @@ +/** + * Provides mapping objects used by bidders for categoryTranslation type logic for Adpod feature + */ +export const APPNEXUS_CATEGORY_MAPPING = { + '1': 'IAB20-3', + '2': 'IAB18-5', + '3': 'IAB10-1', + '4': 'IAB2-3', + '5': 'IAB19-8', + '6': 'IAB22-1', + '7': 'IAB18-1', + '8': 'IAB12-3', + '9': 'IAB5-1', + '10': 'IAB4-5', + '11': 'IAB13-4', + '12': 'IAB8-7', + '13': 'IAB9-7', + '14': 'IAB7-1', + '15': 'IAB20-18', + '16': 'IAB10-7', + '17': 'IAB19-18', + '18': 'IAB13-6', + '19': 'IAB18-4', + '20': 'IAB1-5', + '21': 'IAB1-6', + '22': 'IAB3-4', + '23': 'IAB19-13', + '24': 'IAB22-2', + '25': 'IAB3-9', + '26': 'IAB17-18', + '27': 'IAB19-6', + '28': 'IAB1-7', + '29': 'IAB9-30', + '30': 'IAB20-7', + '31': 'IAB20-17', + '32': 'IAB7-32', + '33': 'IAB16-5', + '34': 'IAB19-34', + '35': 'IAB11-5', + '36': 'IAB12-3', + '37': 'IAB11-4', + '38': 'IAB12-3', + '39': 'IAB9-30', + '41': 'IAB7-44', + '42': 'IAB7-1', + '43': 'IAB7-30', + '50': 'IAB19-30', + '51': 'IAB17-12', + '52': 'IAB19-30', + '53': 'IAB3-1', + '55': 'IAB13-2', + '56': 'IAB19-30', + '57': 'IAB19-30', + '58': 'IAB7-39', + '59': 'IAB22-1', + '60': 'IAB7-39', + '61': 'IAB21-3', + '62': 'IAB5-1', + '63': 'IAB12-3', + '64': 'IAB20-18', + '65': 'IAB11-2', + '66': 'IAB17-18', + '67': 'IAB9-9', + '68': 'IAB9-5', + '69': 'IAB7-44', + '71': 'IAB22-3', + '73': 'IAB19-30', + '74': 'IAB8-5', + '78': 'IAB22-1', + '85': 'IAB12-2', + '86': 'IAB22-3', + '87': 'IAB11-3', + '112': 'IAB7-32', + '113': 'IAB7-32', + '114': 'IAB7-32', + '115': 'IAB7-32', + '118': 'IAB9-5', + '119': 'IAB9-5', + '120': 'IAB9-5', + '121': 'IAB9-5', + '122': 'IAB9-5', + '123': 'IAB9-5', + '124': 'IAB9-5', + '125': 'IAB9-5', + '126': 'IAB9-5', + '127': 'IAB22-1', + '132': 'IAB1-2', + '133': 'IAB19-30', + '137': 'IAB3-9', + '138': 'IAB19-3', + '140': 'IAB2-3', + '141': 'IAB2-1', + '142': 'IAB2-3', + '143': 'IAB17-13', + '166': 'IAB11-4', + '175': 'IAB3-1', + '176': 'IAB13-4', + '182': 'IAB8-9', + '183': 'IAB3-5' +}; diff --git a/libraries/cmp/cmpClient.js b/libraries/cmp/cmpClient.js new file mode 100644 index 00000000000..0e2336cae7a --- /dev/null +++ b/libraries/cmp/cmpClient.js @@ -0,0 +1,139 @@ +import {GreedyPromise} from '../../src/utils/promise.js'; + +/** + * @typedef {function} CMPClient + * + * @param {{}} params CMP parameters. Currently this is a subset of {command, callback, parameter, version}. + * @returns {Promise<*>} a promise that: + * - if a `callback` param was provided, resolves (with no result) just before the first time it's run; + * - if `callback` was *not* provided, resolves to the return value of the CMP command + * @property {boolean} isDirect true if the CMP is directly accessible (no postMessage required) + */ + +/** + * Returns a function that can interface with a CMP regardless of where it's located. + * + * @param apiName name of the CMP api, e.g. "__gpp" + * @param apiVersion? CMP API version + * @param apiArgs? names of the arguments taken by the api function, in order. + * @param callbackArgs? names of the cross-frame response payload properties that should be passed as callback arguments, in order + * @param win + * @returns {CMPClient} CMP invocation function (or null if no CMP was found). + */ +export function cmpClient( + { + apiName, + apiVersion, + apiArgs = ['command', 'callback', 'parameter', 'version'], + callbackArgs = ['returnValue', 'success'], + }, + win = window +) { + const cmpCallbacks = {}; + const callName = `${apiName}Call`; + const cmpDataPkgName = `${apiName}Return`; + + function handleMessage(event) { + const json = (typeof event.data === 'string' && event.data.includes(cmpDataPkgName)) ? JSON.parse(event.data) : event.data; + if (json?.[cmpDataPkgName]?.callId) { + const payload = json[cmpDataPkgName]; + + if (cmpCallbacks.hasOwnProperty(payload.callId)) { + cmpCallbacks[payload.callId](...callbackArgs.map(name => payload[name])); + } + } + } + + function findCMP() { + let f = win; + let cmpFrame; + let isDirect = false; + while (f != null) { + try { + if (typeof f[apiName] === 'function') { + cmpFrame = f; + isDirect = true; + break; + } + } catch (e) { + } + + // need separate try/catch blocks due to the exception errors thrown when trying to check for a frame that doesn't exist in 3rd party env + try { + if (f.frames[`${apiName}Locator`]) { + cmpFrame = f; + break; + } + } catch (e) { + } + + if (f === win.top) break; + f = f.parent; + } + + return [ + cmpFrame, + isDirect + ]; + } + + const [cmpFrame, isDirect] = findCMP(); + + if (!cmpFrame) { + return; + } + + function resolveParams(params) { + params = Object.assign({version: apiVersion}, params); + return apiArgs.map(arg => [arg, params[arg]]) + } + + function wrapCallback(callback, resolve, reject, preamble) { + return function (result, success) { + preamble && preamble(); + const resolver = success == null || success ? resolve : reject; + if (typeof callback === 'function') { + resolver(); + return callback.apply(this, arguments); + } else { + resolver(result); + } + } + } + + let client; + + if (isDirect) { + client = function invokeCMPDirect(params = {}) { + return new GreedyPromise((resolve, reject) => { + const ret = cmpFrame[apiName](...resolveParams({ + ...params, + callback: params.callback && wrapCallback(params.callback, resolve, reject) + }).map(([_, val]) => val)); + if (params.callback == null) { + resolve(ret); + } + }); + }; + } else { + win.addEventListener('message', handleMessage, false); + + client = function invokeCMPFrame(params) { + return new GreedyPromise((resolve, reject) => { + // call CMP via postMessage + const callId = Math.random().toString(); + const msg = { + [callName]: { + ...Object.fromEntries(resolveParams(params).filter(([param]) => param !== 'callback')), + callId: callId + } + }; + + cmpCallbacks[callId] = wrapCallback(params?.callback, resolve, reject, params?.callback == null && (() => { delete cmpCallbacks[callId] })); + cmpFrame.postMessage(msg, '*'); + }); + }; + } + client.isDirect = isDirect; + return client; +} diff --git a/libraries/domainOverrideToRootDomain/index.js b/libraries/domainOverrideToRootDomain/index.js new file mode 100644 index 00000000000..95a334755d1 --- /dev/null +++ b/libraries/domainOverrideToRootDomain/index.js @@ -0,0 +1,39 @@ +/** + * Create a domainOverride callback for an ID module, closing over + * an instance of StorageManager. + * + * The domainOverride function, given document.domain, will return + * the topmost domain we are able to set a cookie on. For example, + * given subdomain.example.com, it would return example.com. + * + * @param {StorageManager} storage e.g. from getStorageManager() + * @param {string} moduleName the name of the module using this function + * @returns {function(): string} + */ +export function domainOverrideToRootDomain(storage, moduleName) { + return function() { + const domainElements = document.domain.split('.'); + const cookieName = `_gd${Date.now()}_${moduleName}`; + + for (let i = 0, topDomain, testCookie; i < domainElements.length; i++) { + const nextDomain = domainElements.slice(i).join('.'); + + // write test cookie + storage.setCookie(cookieName, '1', undefined, undefined, nextDomain); + + // read test cookie to verify domain was valid + testCookie = storage.getCookie(cookieName); + + // delete test cookie + storage.setCookie(cookieName, '', 'Thu, 01 Jan 1970 00:00:01 GMT', undefined, nextDomain); + + if (testCookie === '1') { + // cookie was written successfully using test domain so the topDomain is updated + topDomain = nextDomain; + } else { + // cookie failed to write using test domain so exit by returning the topDomain + return topDomain; + } + } + } +} diff --git a/libraries/keywords/keywords.js b/libraries/keywords/keywords.js new file mode 100644 index 00000000000..645c9c8d38f --- /dev/null +++ b/libraries/keywords/keywords.js @@ -0,0 +1,31 @@ +import {CLIENT_SECTIONS} from '../../src/fpd/oneClient.js'; +import {deepAccess} from '../../src/utils.js'; + +const ORTB_KEYWORDS_PATHS = ['user.keywords'].concat( + CLIENT_SECTIONS.flatMap((prefix) => ['keywords', 'content.keywords'].map(suffix => `${prefix}.${suffix}`)) +); + +/** + * @param commaSeparatedKeywords: any number of either keyword arrays, or comma-separated keyword strings + * @returns an array with all unique keywords contained across all inputs + */ +export function mergeKeywords(...commaSeparatedKeywords) { + const keywords = new Set(); + commaSeparatedKeywords + .filter(kwds => kwds) + .flatMap(kwds => Array.isArray(kwds) ? kwds : kwds.split(',')) + .map(kw => kw.replace(/^\s*/, '').replace(/\s*$/, '')) + .filter(kw => kw) + .forEach(kw => keywords.add(kw)); + return Array.from(keywords.keys()); +} + +/** + * Get an array with all keywords contained in an ortb2 object. + */ +export function getAllOrtbKeywords(ortb2, ...extraCommaSeparatedKeywords) { + return mergeKeywords( + ...ORTB_KEYWORDS_PATHS.map(path => deepAccess(ortb2, path)), + ...extraCommaSeparatedKeywords + ) +} diff --git a/libraries/mspa/activityControls.js b/libraries/mspa/activityControls.js new file mode 100644 index 00000000000..3359862a5b3 --- /dev/null +++ b/libraries/mspa/activityControls.js @@ -0,0 +1,91 @@ +import {registerActivityControl} from '../../src/activities/rules.js'; +import { + ACTIVITY_ENRICH_EIDS, + ACTIVITY_ENRICH_UFPD, + ACTIVITY_SYNC_USER, + ACTIVITY_TRANSMIT_PRECISE_GEO +} from '../../src/activities/activities.js'; +import {gppDataHandler} from '../../src/adapterManager.js'; + +// default interpretation for MSPA consent(s): +// https://docs.google.com/document/d/1yPEVx3bBdSkIw-QNkQR5qi1ztmn9zoXk-LaGQB6iw7Q + +export function isBasicConsentDenied(cd) { + // service provider mode is always consent denied + return ['MspaServiceProviderMode', 'Gpc'].some(prop => cd[prop] === 1) || + // you cannot consent to what you were not notified of + cd.PersonalDataConsents === 2 || + // minors 13+ who have not given consent + cd.KnownChildSensitiveDataConsents[0] === 1 || + // minors under 13 cannot consent + cd.KnownChildSensitiveDataConsents[1] !== 0 || + // sensitive category consent impossible without notice + (cd.SensitiveDataProcessing.some(element => element === 2) && (cd.SensitiveDataLimitUseNotice !== 1 || cd.SensitiveDataProcessingOptOutNotice !== 1)); +} + +export function isSensitiveNoticeMissing(cd) { + return ['SensitiveDataProcessingOptOutNotice', 'SensitiveDataLimitUseNotice'].some(prop => cd[prop] === 2) +} + +export function isConsentDenied(cd) { + return isBasicConsentDenied(cd) || + // user opts out + (['SaleOptOut', 'SharingOptOut', 'TargetedAdvertisingOptOut'].some(prop => cd[prop] === 1)) || + // notice not given + (['SaleOptOutNotice', 'SharingNotice', 'SharingOptOutNotice', 'TargetedAdvertisingOptOutNotice'].some(prop => cd[prop] === 2)) || + // sale opted in but notice does not apply + ((cd.SaleOptOut === 2 && cd.SaleOptOutNotice === 0)) || + // targeted opted in but notice does not apply + ((cd.TargetedAdvertisingOptOut === 2 && cd.TargetedAdvertisingOptOutNotice === 0)) || + // sharing opted in but notices do not apply + ((cd.SharingOptOut === 2 && (cd.SharingNotice === 0 || cd.SharingOptOutNotice === 0))); +} + +export function isTransmitUfpdConsentDenied(cd) { + // SensitiveDataProcessing[1-5,11]=1 OR SensitiveDataProcessing[6,7,9,10,12]<>0 OR + const mustBeZero = [6, 7, 9, 10, 12]; + const mustBeZeroSubtractedVector = mustBeZero.map((number) => number - 1); + const SensitiveDataProcessingMustBeZero = cd.SensitiveDataProcessing.filter(index => mustBeZeroSubtractedVector.includes(index)); + const cannotBeOne = [1, 2, 3, 4, 5, 11]; + const cannotBeOneSubtractedVector = cannotBeOne.map((number) => number - 1); + const SensitiveDataProcessingCannotBeOne = cd.SensitiveDataProcessing.filter(index => cannotBeOneSubtractedVector.includes(index)); + return isConsentDenied(cd) || + isSensitiveNoticeMissing(cd) || + SensitiveDataProcessingCannotBeOne.some(val => val === 1) || + SensitiveDataProcessingMustBeZero.some(val => val !== 0); +} + +export function isTransmitGeoConsentDenied(cd) { + return isBasicConsentDenied(cd) || + isSensitiveNoticeMissing(cd) || + cd.SensitiveDataProcessing[7] === 1 +} + +const CONSENT_RULES = { + [ACTIVITY_SYNC_USER]: isConsentDenied, + [ACTIVITY_ENRICH_EIDS]: isConsentDenied, + [ACTIVITY_ENRICH_UFPD]: isTransmitUfpdConsentDenied, + [ACTIVITY_TRANSMIT_PRECISE_GEO]: isTransmitGeoConsentDenied +} + +export function mspaRule(sids, getConsent, denies, applicableSids = () => gppDataHandler.getConsentData()?.applicableSections) { + return function() { + if (applicableSids().some(sid => sids.includes(sid))) { + const consent = getConsent(); + if (consent == null) { + return {allow: false, reason: 'consent data not available'}; + } + if (denies(consent)) { + return {allow: false} + } + } + } +} + +export function setupRules(api, sids, normalizeConsent = (c) => c, rules = CONSENT_RULES, registerRule = registerActivityControl, getConsentData = () => gppDataHandler.getConsentData()) { + const unreg = []; + Object.entries(rules).forEach(([activity, denies]) => { + unreg.push(registerRule(activity, `MSPA (${api})`, mspaRule(sids, () => normalizeConsent(getConsentData()?.sectionData?.[api]), denies, () => getConsentData()?.applicableSections || []))) + }) + return () => unreg.forEach(ur => ur()) +} diff --git a/libraries/objectGuard/objectGuard.js b/libraries/objectGuard/objectGuard.js new file mode 100644 index 00000000000..cf3d2f38256 --- /dev/null +++ b/libraries/objectGuard/objectGuard.js @@ -0,0 +1,99 @@ +import {isData, objectTransformer, sessionedApplies} from '../../src/activities/redactor.js'; +import {deepAccess, deepClone, deepEqual, deepSetValue} from '../../src/utils.js'; + +/** + * @typedef {Object} ObjectGuard + * @property {*} obj a view on the guarded object + * @property {function(): void} verify a function that checks for and rolls back disallowed changes to the guarded object + */ + +/** + * Create a factory function for object guards using the given rules. + * + * An object guard is a pair {obj, verify} where: + * - `obj` is a view on the guarded object that applies "redact" rules (the same rules used in activites/redactor.js) + * - `verify` is a function that, when called, will check that the guarded object was not modified + * in a way that violates any "write protect" rules, and rolls back any offending changes. + * + * This is meant to provide sandboxed version of a privacy-sensitive object, where reads + * are filtered through redaction rules and writes are checked against write protect rules. + * + * @param {Array[TransformationRule]} rules + * @return {function(*, ...[*]): ObjectGuard} + */ +export function objectGuard(rules) { + const root = {}; + const writeRules = []; + + rules.forEach(rule => { + if (rule.wp) writeRules.push(rule); + if (!rule.get) return; + rule.paths.forEach(path => { + let node = root; + path.split('.').forEach(el => { + node.children = node.children || {}; + node.children[el] = node.children[el] || {}; + node = node.children[el]; + }) + node.rule = rule; + }); + }); + + const wpTransformer = objectTransformer(writeRules); + + function mkGuard(obj, tree, applies) { + return new Proxy(obj, { + get(target, prop, receiver) { + const val = Reflect.get(target, prop, receiver); + if (tree.hasOwnProperty(prop)) { + const {children, rule} = tree[prop]; + if (children && val != null && typeof val === 'object') { + return mkGuard(val, children, applies); + } else if (rule && isData(val) && applies(rule)) { + return rule.get(val); + } + } + return val; + }, + }); + } + + function mkVerify(transformResult) { + return function () { + transformResult.forEach(fn => fn()); + } + } + + return function guard(obj, ...args) { + const session = {}; + return { + obj: mkGuard(obj, root.children || {}, sessionedApplies(session, ...args)), + verify: mkVerify(wpTransformer(session, obj, ...args)) + } + }; +} + +/** + * @param {TransformationRuleDef} ruleDef + * @return {TransformationRule} + */ +export function writeProtectRule(ruleDef) { + return Object.assign({ + wp: true, + run(root, path, object, property, applies) { + const origHasProp = object && object.hasOwnProperty(property); + const original = origHasProp ? object[property] : undefined; + const origCopy = origHasProp && original != null && typeof original === 'object' ? deepClone(original) : original; + return function () { + const object = path == null ? root : deepAccess(root, path); + const finalHasProp = object && isData(object[property]); + const finalValue = finalHasProp ? object[property] : undefined; + if (!origHasProp && finalHasProp && applies()) { + delete object[property]; + } else if ((origHasProp !== finalHasProp || finalValue !== original || !deepEqual(finalValue, origCopy)) && applies()) { + deepSetValue(root, (path == null ? [] : [path]).concat(property).join('.'), origCopy); + } + } + } + }, ruleDef) +} diff --git a/libraries/objectGuard/ortbGuard.js b/libraries/objectGuard/ortbGuard.js new file mode 100644 index 00000000000..7911b378c3d --- /dev/null +++ b/libraries/objectGuard/ortbGuard.js @@ -0,0 +1,88 @@ +import {isActivityAllowed} from '../../src/activities/rules.js'; +import {ACTIVITY_ENRICH_EIDS, ACTIVITY_ENRICH_UFPD} from '../../src/activities/activities.js'; +import { + appliesWhenActivityDenied, + ortb2TransmitRules, + ORTB_EIDS_PATHS, + ORTB_UFPD_PATHS +} from '../../src/activities/redactor.js'; +import {objectGuard, writeProtectRule} from './objectGuard.js'; +import {mergeDeep} from '../../src/utils.js'; + +function ortb2EnrichRules(isAllowed = isActivityAllowed) { + return [ + { + name: ACTIVITY_ENRICH_EIDS, + paths: ORTB_EIDS_PATHS, + applies: appliesWhenActivityDenied(ACTIVITY_ENRICH_EIDS, isAllowed) + }, + { + name: ACTIVITY_ENRICH_UFPD, + paths: ORTB_UFPD_PATHS, + applies: appliesWhenActivityDenied(ACTIVITY_ENRICH_UFPD, isAllowed) + } + ].map(writeProtectRule) +} + +export function ortb2GuardFactory(isAllowed = isActivityAllowed) { + return objectGuard(ortb2TransmitRules(isAllowed).concat(ortb2EnrichRules(isAllowed))); +} + +/** + * + * + * @typedef {Function} ortb2Guard + * @param {{}} ortb2 ORTB object to guard + * @param {{}} params activity params to use for activity checks + * @returns {ObjectGuard} + */ + +/* + * Get a guard for an ORTB object. Read access is restricted in the same way it'd be redacted (see activites/redactor.js); + * and writes are checked against the enrich* activites. + * + * @type ortb2Guard + */ +export const ortb2Guard = ortb2GuardFactory(); + +export function ortb2FragmentsGuardFactory(guardOrtb2 = ortb2Guard) { + return function guardOrtb2Fragments(fragments, params) { + fragments.global = fragments.global || {}; + fragments.bidder = fragments.bidder || {}; + const bidders = new Set(Object.keys(fragments.bidder)); + const verifiers = []; + + function makeGuard(ortb2) { + const guard = guardOrtb2(ortb2, params); + verifiers.push(guard.verify); + return guard.obj; + } + + const obj = { + global: makeGuard(fragments.global), + bidder: Object.fromEntries(Object.entries(fragments.bidder).map(([bidder, ortb2]) => [bidder, makeGuard(ortb2)])) + }; + + return { + obj, + verify() { + Object.entries(obj.bidder) + .filter(([bidder]) => !bidders.has(bidder)) + .forEach(([bidder, ortb2]) => { + const repl = {}; + const guard = guardOrtb2(repl, params); + mergeDeep(guard.obj, ortb2); + guard.verify(); + fragments.bidder[bidder] = repl; + }) + verifiers.forEach(fn => fn()); + } + } + } +} + +/** + * Get a guard for an ortb2Fragments object. + * @type {function(*, *): ObjectGuard} + */ +export const guardOrtb2Fragments = ortb2FragmentsGuardFactory(); diff --git a/libraries/ortbConverter/converter.js b/libraries/ortbConverter/converter.js index 2057af8b6d3..c367aec268a 100644 --- a/libraries/ortbConverter/converter.js +++ b/libraries/ortbConverter/converter.js @@ -90,12 +90,13 @@ export function ortbConverter({ req: Object.assign({bidRequests}, defaultContext, context), imp: {} } + ctx.req.impContext = ctx.imp; const imps = bidRequests.map(bidRequest => { const impContext = Object.assign({bidderRequest, reqContext: ctx.req}, defaultContext, context); const result = buildImp(bidRequest, impContext); if (result != null) { if (result.hasOwnProperty('id')) { - impContext.bidRequest = bidRequest; + Object.assign(impContext, {bidRequest, imp: result}); ctx.imp[result.id] = impContext; return result; } @@ -116,7 +117,7 @@ export function ortbConverter({ throw new Error('ortbRequest passed to `fromORTB` must be the same object returned by `toORTB`') } function augmentContext(ctx, extraParams = {}) { - return Object.assign({ortbRequest: request}, extraParams, ctx); + return Object.assign(ctx, {ortbRequest: request}, extraParams, ctx); } const impsById = Object.fromEntries((request.imp || []).map(imp => [imp.id, imp])); const bidResponses = (response.seatbid || []).flatMap(seatbid => diff --git a/libraries/ortbConverter/processors/default.js b/libraries/ortbConverter/processors/default.js index 1d6bfb8424e..8db2c1c461e 100644 --- a/libraries/ortbConverter/processors/default.js +++ b/libraries/ortbConverter/processors/default.js @@ -1,10 +1,10 @@ -import {deepSetValue, logWarn, mergeDeep} from '../../../src/utils.js'; +import {generateUUID, mergeDeep} from '../../../src/utils.js'; import {bannerResponseProcessor, fillBannerImp} from './banner.js'; import {fillVideoImp, fillVideoResponse} from './video.js'; import {setResponseMediaType} from './mediaType.js'; import {fillNativeImp, fillNativeResponse} from './native.js'; import {BID_RESPONSE, IMP, REQUEST} from '../../../src/pbjsORTB.js'; -import {config} from '../../../src/config.js'; +import {clientSectionChecker} from '../../../src/fpd/oneClient.js'; export const DEFAULT_PROCESSORS = { [REQUEST]: { @@ -15,27 +15,22 @@ export const DEFAULT_PROCESSORS = { mergeDeep(ortbRequest, bidderRequest.ortb2) } }, - // override FPD app, site, and device with getConfig('app'), etc if defined - // TODO: these should be deprecated for v8 - appFpd: fpdFromTopLevelConfig('app'), - siteFpd: fpdFromTopLevelConfig('site'), - deviceFpd: fpdFromTopLevelConfig('device'), onlyOneClient: { + // make sure only one of 'dooh', 'app', 'site' is set in request priority: -99, - fn: onlyOneClientSection + fn: clientSectionChecker('ORTB request') }, props: { - // sets request properties id, tmax, test, source.tid + // sets request properties id, tmax, test fn(ortbRequest, bidderRequest) { Object.assign(ortbRequest, { - id: ortbRequest.id || bidderRequest.auctionId, + id: ortbRequest.id || generateUUID(), test: ortbRequest.test || 0 }); const timeout = parseInt(bidderRequest.timeout, 10); if (!isNaN(timeout)) { ortbRequest.tmax = timeout; } - deepSetValue(ortbRequest, 'source.tid', ortbRequest.source?.tid || bidderRequest.auctionId); } } }, @@ -57,10 +52,6 @@ export const DEFAULT_PROCESSORS = { // populates imp.banner fn: fillBannerImp }, - video: { - // populates imp.video - fn: fillVideoImp - }, pbadslot: { // removes imp.ext.data.pbaslot if it's not a string // TODO: is this needed? @@ -82,10 +73,6 @@ export const DEFAULT_PROCESSORS = { // sets banner response attributes if bidResponse.mediaType === BANNER fn: bannerResponseProcessor(), }, - video: { - // sets video response attributes if bidResponse.mediaType === VIDEO - fn: fillVideoResponse - }, props: { // sets base bidResponse properties common to all types of bids fn(bidResponse, bid, context) { @@ -126,28 +113,13 @@ if (FEATURES.NATIVE) { } } -function fpdFromTopLevelConfig(prop) { - return { - priority: 90, // after FPD from 'ortb2', before the rest - fn(ortbRequest) { - const data = config.getConfig(prop); - if (typeof data === 'object') { - ortbRequest[prop] = mergeDeep({}, ortbRequest[prop], data); - } - } +if (FEATURES.VIDEO) { + DEFAULT_PROCESSORS[IMP].video = { + // populates imp.video + fn: fillVideoImp + } + DEFAULT_PROCESSORS[BID_RESPONSE].video = { + // sets video response attributes if bidResponse.mediaType === VIDEO + fn: fillVideoResponse } -} - -export function onlyOneClientSection(ortbRequest) { - ['dooh', 'app', 'site'].reduce((found, section) => { - if (ortbRequest[section] != null && Object.keys(ortbRequest[section]).length > 0) { - if (found != null) { - logWarn(`ORTB request specifies both '${found}' and '${section}'; dropping the latter.`) - delete ortbRequest[section]; - } else { - found = section; - } - } - return found; - }, null); } diff --git a/libraries/ortbConverter/processors/video.js b/libraries/ortbConverter/processors/video.js index 5fe411e6bcf..c38231d9002 100644 --- a/libraries/ortbConverter/processors/video.js +++ b/libraries/ortbConverter/processors/video.js @@ -6,6 +6,7 @@ import {sizesToFormat} from '../lib/sizes.js'; const ORTB_VIDEO_PARAMS = new Set([ 'pos', 'placement', + 'plcmt', 'api', 'mimes', 'protocols', diff --git a/libraries/video/shared/helpers.js b/libraries/video/shared/helpers.js index 1b87f3bf1f3..e61fde6a331 100644 --- a/libraries/video/shared/helpers.js +++ b/libraries/video/shared/helpers.js @@ -1,5 +1,19 @@ import { videoKey } from '../constants/constants.js' export function getExternalVideoEventName(eventName) { + if (!eventName) { + return ''; + } return videoKey + eventName.replace(/^./, eventName[0].toUpperCase()); } + +export function getExternalVideoEventPayload(eventName, payload) { + if (!payload) { + payload = {}; + } + + if (!payload.type) { + payload.type = eventName; + } + return payload; +} diff --git a/modules/.submodules.json b/modules/.submodules.json index a535fd4988d..b45fb7f2303 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -7,7 +7,7 @@ "amxIdSystem", "britepoolIdSystem", "connectIdSystem", - "cpexIdSystem", + "czechAdIdSystem", "criteoIdSystem", "dacIdSystem", "deepintentDpesIdSystem", @@ -38,13 +38,15 @@ "tapadIdSystem", "teadsIdSystem", "tncIdSystem", - "trustpidSystem", + "utiqSystem", "uid2IdSystem", + "euidIdSystem", "unifiedIdSystem", "verizonMediaIdSystem", "zeotapIdPlusIdSystem", "adqueryIdSystem", - "gravitoIdSystem" + "gravitoIdSystem", + "freepassIdSystem" ], "adpod": [ "freeWheelAdserverVideo", diff --git a/modules/1plusXRtdProvider.js b/modules/1plusXRtdProvider.js index 390f4f4cd81..c5c4594ff22 100644 --- a/modules/1plusXRtdProvider.js +++ b/modules/1plusXRtdProvider.js @@ -1,10 +1,9 @@ import { submodule } from '../src/hook.js'; -import { config } from '../src/config.js'; import { ajax } from '../src/ajax.js'; import { logMessage, logError, - deepAccess, mergeDeep, - isNumber, isArray, deepSetValue + deepAccess, deepSetValue, mergeDeep, + isNumber, isArray, } from '../src/utils.js'; // Constants @@ -14,7 +13,6 @@ const ORTB2_NAME = '1plusX.com' const PAPI_VERSION = 'v1.0'; const LOG_PREFIX = '[1plusX RTD Module]: '; const OPE_FPID = 'ope_fpid' -const LEGACY_SITE_KEYWORDS_BIDDERS = ['appnexus']; export const segtaxes = { // cf. https://github.com/InteractiveAdvertisingBureau/openrtb/pull/108 AUDIENCE: 526, @@ -151,21 +149,11 @@ const getTargetingDataFromPapi = (papiUrl) => { * @param {string[]} topics Represents the topics of the page * @returns {Object} Object describing the updates to make on bidder configs */ -export const buildOrtb2Updates = ({ segments = [], topics = [] }, bidder) => { - // Currently appnexus bidAdapter doesn't support topics in `site.content.data.segment` - // Therefore, writing them in `site.keywords` until it's supported - // Other bidAdapters do fine with `site.content.data.segment` - const writeToLegacySiteKeywords = LEGACY_SITE_KEYWORDS_BIDDERS.includes(bidder); - if (writeToLegacySiteKeywords) { - const site = { - keywords: topics.join(',') - }; - return { site }; - } - +export const buildOrtb2Updates = ({ segments = [], topics = [] }) => { const userData = { name: ORTB2_NAME, - segment: segments.map((segmentId) => ({ id: segmentId })) + segment: segments.map((segmentId) => ({ id: segmentId })), + ext: { segtax: segtaxes.AUDIENCE } }; const siteContentData = { name: ORTB2_NAME, @@ -179,74 +167,46 @@ export const buildOrtb2Updates = ({ segments = [], topics = [] }, bidder) => { * Merges the targeting data with the existing config for bidder and updates * @param {string} bidder Bidder for which to set config * @param {Object} ortb2Updates Updates to be applied to bidder config - * @param {Object} bidderConfigs All current bidder configs - * @returns {Object} Updated bidder config + * @param {Object} biddersOrtb2 All current bidder configs */ -export const updateBidderConfig = (bidder, ortb2Updates, bidderConfigs) => { - const { site, siteContentData, userData } = ortb2Updates; - const bidderConfigCopy = mergeDeep({}, bidderConfigs[bidder]); - - if (site) { - // Legacy : cf. comment on buildOrtb2Updates first lines - const currentSite = deepAccess(bidderConfigCopy, 'ortb2.site'); - const updatedSite = mergeDeep(currentSite, site); - deepSetValue(bidderConfigCopy, 'ortb2.site', updatedSite); - } +export const updateBidderConfig = (bidder, ortb2Updates, biddersOrtb2) => { + const { siteContentData, userData } = ortb2Updates; + mergeDeep(biddersOrtb2, { [bidder]: {} }); + const bidderConfig = deepAccess(biddersOrtb2, bidder); - if (siteContentData) { - const siteDataPath = 'ortb2.site.content.data'; - const currentSiteContentData = deepAccess(bidderConfigCopy, siteDataPath) || []; + { + const siteDataPath = 'site.content.data'; + const currentSiteContentData = deepAccess(bidderConfig, siteDataPath) || []; const updatedSiteContentData = [ ...currentSiteContentData.filter(({ name }) => name != siteContentData.name), siteContentData ]; - deepSetValue(bidderConfigCopy, siteDataPath, updatedSiteContentData); + deepSetValue(bidderConfig, siteDataPath, updatedSiteContentData); } - if (userData) { - const userDataPath = 'ortb2.user.data'; - const currentUserData = deepAccess(bidderConfigCopy, userDataPath) || []; + { + const userDataPath = 'user.data'; + const currentUserData = deepAccess(bidderConfig, userDataPath) || []; const updatedUserData = [ ...currentUserData.filter(({ name }) => name != userData.name), userData ]; - deepSetValue(bidderConfigCopy, userDataPath, updatedUserData); + deepSetValue(bidderConfig, userDataPath, updatedUserData); } - - return bidderConfigCopy; }; -const setAppnexusAudiences = (audiences) => { - config.setConfig({ - appnexusAuctionKeywords: { - '1plusX': audiences, - }, - }); -} - /** * Updates bidder configs with the targeting data retreived from Profile API * @param {Object} papiResponse Response from Profile API * @param {Object} config Module configuration * @param {string[]} config.bidders Bidders specified in module's configuration */ -export const setTargetingDataToConfig = (papiResponse, { bidders }) => { - const bidderConfigs = config.getBidderConfig(); +export const setTargetingDataToConfig = (papiResponse, { bidders, biddersOrtb2 }) => { const { s: segments, t: topics } = papiResponse; + const ortb2Updates = buildOrtb2Updates({ segments, topics }); for (const bidder of bidders) { - const ortb2Updates = buildOrtb2Updates({ segments, topics }, bidder); - const updatedBidderConfig = updateBidderConfig(bidder, ortb2Updates, bidderConfigs); - if (updatedBidderConfig) { - config.setBidderConfig({ - bidders: [bidder], - config: updatedBidderConfig - }); - } - if (bidder === 'appnexus') { - // Do the legacy stuff for appnexus with segments - setAppnexusAudiences(segments); - } + updateBidderConfig(bidder, ortb2Updates, biddersOrtb2); } } @@ -272,13 +232,14 @@ const getBidRequestData = (reqBidsConfigObj, callback, moduleConfig, userConsent try { // Get the required config const { customerId, bidders } = extractConfig(moduleConfig, reqBidsConfigObj); + const { ortb2Fragments: { bidder: biddersOrtb2 } } = reqBidsConfigObj; // Get PAPI URL const papiUrl = getPapiUrl(customerId, extractConsent(userConsent) || {}, extractFpid()) // Call PAPI getTargetingDataFromPapi(papiUrl) .then((papiResponse) => { logMessage(LOG_PREFIX, 'Get targeting data request successful'); - setTargetingDataToConfig(papiResponse, { bidders }); + setTargetingDataToConfig(papiResponse, { bidders, biddersOrtb2 }); callback(); }) } catch (error) { diff --git a/modules/33acrossBidAdapter.js b/modules/33acrossBidAdapter.js index e9901794ff9..26fc0a11fd5 100644 --- a/modules/33acrossBidAdapter.js +++ b/modules/33acrossBidAdapter.js @@ -1,19 +1,19 @@ -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { config } from '../src/config.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {config} from '../src/config.js'; import { deepAccess, - uniques, - isArray, + getWindowSelf, getWindowTop, + isArray, isGptPubadsDefined, isSlotMatchingAdUnitCode, logInfo, logWarn, - getWindowSelf, mergeDeep, - pick + pick, + uniques } from '../src/utils.js'; -import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; // **************************** UTILS *************************** // const BIDDER_CODE = '33across'; @@ -183,7 +183,8 @@ function buildRequests(bidRequests, bidderRequest) { uspConsent, pageUrl, referer, - ttxSettings + ttxSettings, + bidderRequest, }) ) } @@ -246,7 +247,7 @@ function _getMRAKey(bidRequest) { } // Infer the necessary data from valid bid for a minimal ttxRequest and create HTTP request -function _createServerRequest({ bidRequests, gdprConsent = {}, uspConsent, pageUrl, referer, ttxSettings }) { +function _createServerRequest({ bidRequests, gdprConsent = {}, uspConsent, pageUrl, referer, ttxSettings, bidderRequest }) { const ttxRequest = {}; const firstBidRequest = bidRequests[0]; const { siteId, test } = firstBidRequest.params; @@ -271,7 +272,7 @@ function _createServerRequest({ bidRequests, gdprConsent = {}, uspConsent, pageU ttxRequest.site.ref = referer; } - ttxRequest.id = firstBidRequest.auctionId; + ttxRequest.id = bidderRequest?.bidderRequestId; if (gdprConsent.consentString) { ttxRequest.user = setExtensions(ttxRequest.user, { @@ -681,7 +682,6 @@ function _createBidResponse(bid, cur) { bid.adomain && bid.adomain.length; const bidResponse = { requestId: bid.impid, - bidderCode: BIDDER_CODE, cpm: bid.price, width: bid.w, height: bid.h, @@ -765,7 +765,7 @@ function _buildDeviceORTB(device = {}) { } if (device.sua) { - deviceProps.sua = pick(device.sua, [ 'browsers', 'platform', 'model' ]); + deviceProps.sua = pick(device.sua, [ 'browsers', 'platform', 'model', 'mobile' ]); } return deviceProps; diff --git a/modules/33acrossIdSystem.js b/modules/33acrossIdSystem.js index 5e07fe9523d..be81b26b110 100644 --- a/modules/33acrossIdSystem.js +++ b/modules/33acrossIdSystem.js @@ -17,8 +17,11 @@ const CALLER_NAME = 'pbjs'; function getEnvelope(response) { if (!response.succeeded) { - logError(`${MODULE_NAME}: Unsuccessful response`); - + if (response.error == 'Cookied User') { + logMessage(`${MODULE_NAME}: Unsuccessful response`.concat(' ', response.error)); + } else { + logError(`${MODULE_NAME}: Unsuccessful response`.concat(' ', response.error)); + } return; } diff --git a/modules/ablidaBidAdapter.js b/modules/ablidaBidAdapter.js index ca489a10a90..805a2020fb4 100644 --- a/modules/ablidaBidAdapter.js +++ b/modules/ablidaBidAdapter.js @@ -1,8 +1,7 @@ -import { triggerPixel } from '../src/utils.js'; -import {config} from '../src/config.js'; +import {triggerPixel} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; +import {convertOrtbRequestToProprietaryNative} from '../src/native.js'; const BIDDER_CODE = 'ablida'; const ENDPOINT_URL = 'https://bidder.ablida.net/prebid'; @@ -77,7 +76,7 @@ export const spec = { const response = serverResponse.body; response.forEach(function(bid) { - bid.ttl = config.getConfig('_bidderTimeout'); + bid.ttl = 60 bidResponses.push(bid); }); return bidResponses; diff --git a/modules/acuityAdsBidAdapter.js b/modules/acuityAdsBidAdapter.js index f469fe48c60..b0bb132ddae 100644 --- a/modules/acuityAdsBidAdapter.js +++ b/modules/acuityAdsBidAdapter.js @@ -150,7 +150,7 @@ export const spec = { coppa: config.getConfig('coppa') === true ? 1 : 0, ccpa: bidderRequest.uspConsent || undefined, gdpr: bidderRequest.gdprConsent || undefined, - tmax: config.getConfig('bidderTimeout') + tmax: bidderRequest.timeout }; const len = validBidRequests.length; diff --git a/modules/adWMGBidAdapter.js b/modules/adWMGBidAdapter.js index 12dc36d694c..36935e80d3b 100644 --- a/modules/adWMGBidAdapter.js +++ b/modules/adWMGBidAdapter.js @@ -59,11 +59,12 @@ export const spec = { } const request = { + // TODO: fix auctionId leak: https://github.com/prebid/Prebid.js/issues/9781 auctionId: bidRequest.auctionId, requestId: bidRequest.bidId, bidRequestsCount: bidRequest.bidRequestsCount, bidderRequestId: bidRequest.bidderRequestId, - transactionId: bidRequest.transactionId, + transactionId: bidRequest.ortb2Imp?.ext?.tid, referrer: referrer, timeout: timeout, adUnit: adUnit, diff --git a/modules/adagioBidAdapter.js b/modules/adagioBidAdapter.js index adb9744428f..c28692de846 100644 --- a/modules/adagioBidAdapter.js +++ b/modules/adagioBidAdapter.js @@ -20,6 +20,7 @@ import { logInfo, logWarn, mergeDeep, + isStr, } from '../src/utils.js'; import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; @@ -27,23 +28,27 @@ import {loadExternalScript} from '../src/adloader.js'; import {verify} from 'criteo-direct-rsa-validate/build/verify.js'; import {getStorageManager} from '../src/storageManager.js'; import {getRefererInfo, parseDomain} from '../src/refererDetection.js'; -import {createEidsArray} from './userId/eids.js'; import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; import {Renderer} from '../src/Renderer.js'; import {OUTSTREAM} from '../src/video.js'; import { getGlobal } from '../src/prebidGlobal.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import { userSync } from '../src/userSync.js'; const BIDDER_CODE = 'adagio'; const LOG_PREFIX = 'Adagio:'; const FEATURES_VERSION = '1'; -export const ENDPOINT = 'https://mp.4dex.io/prebid'; +export const ENDPOINT = 'http://127.0.0.1:8080/prebid'; const SUPPORTED_MEDIA_TYPES = [BANNER, NATIVE, VIDEO]; const ADAGIO_TAG_URL = 'https://script.4dex.io/localstore.js'; const ADAGIO_LOCALSTORAGE_KEY = 'adagioScript'; const GVLID = 617; -export const storage = getStorageManager({gvlid: GVLID, bidderCode: BIDDER_CODE}); -export const RENDERER_URL = 'https://script.4dex.io/outstream-player.js'; +export const storage = getStorageManager({bidderCode: BIDDER_CODE}); + +const BB_PUBLICATION = 'adagio'; +const BB_RENDERER_DEFAULT = 'renderer'; +export const BB_RENDERER_URL = `https://${BB_PUBLICATION}.bbvms.com/r/$RENDERER.js`; + const MAX_SESS_DURATION = 30 * 60 * 1000; const ADAGIO_PUBKEY = 'AL16XT44Sfp+8SHVF1UdC7hydPSMVLMhsYknKDdwqq+0ToDSJrP0+Qh0ki9JJI2uYm/6VEYo8TJED9WfMkiJ4vf02CW3RvSWwc35bif2SK1L8Nn/GfFYr/2/GG/Rm0vUsv+vBHky6nuuYls20Og0HDhMgaOlXoQ/cxMuiy5QSktp'; const ADAGIO_PUBKEY_E = 65537; @@ -61,7 +66,7 @@ export const ORTB_VIDEO_PARAMS = { 'startdelay': (value) => isInteger(value), 'placement': (value) => isInteger(value), 'linearity': (value) => isInteger(value), - 'skip': (value) => isInteger(value), + 'skip': (value) => [1, 0].includes(value), 'skipmin': (value) => isInteger(value), 'skipafter': (value) => isInteger(value), 'sequence': (value) => isInteger(value), @@ -399,8 +404,8 @@ function _getSchain(bidRequest) { } function _getEids(bidRequest) { - if (deepAccess(bidRequest, 'userId')) { - return createEidsArray(bidRequest.userId); + if (deepAccess(bidRequest, 'userIdAsEids')) { + return bidRequest.userIdAsEids; } } @@ -442,16 +447,6 @@ function _buildVideoBidRequest(bidRequest) { }); } -function _renderer(bid) { - bid.renderer.push(() => { - if (typeof window.ADAGIO.outstreamPlayer === 'function') { - window.ADAGIO.outstreamPlayer(bid); - } else { - logError(`${LOG_PREFIX} Adagio outstream player is not defined`); - } - }); -} - function _parseNativeBidResponse(bid) { if (!bid.admNative || !Array.isArray(bid.admNative.assets)) { logError(`${LOG_PREFIX} Invalid native response`); @@ -865,7 +860,7 @@ function storeRequestInAdagioNS(bidRequest) { bidder: bidRequest.bidder, params: bidRequest.params // use the updated bid.params object with auto-detected params }], - auctionId: bidRequest.auctionId, + auctionId: bidRequest.auctionId, // this auctionId has been generated by adagioBidAdapter pageviewId: internal.getPageviewId(), printNumber, localPbjs: '$$PREBID_GLOBAL$$', @@ -874,12 +869,83 @@ function storeRequestInAdagioNS(bidRequest) { // (legacy) Store internal adUnit information w.ADAGIO.adUnits[bidRequest.adUnitCode] = { - auctionId: bidRequest.auctionId, + auctionId: bidRequest.auctionId, // this auctionId has been generated by adagioBidAdapter pageviewId: internal.getPageviewId(), printNumber, }; } +// See https://support.bluebillywig.com/developers/vast-renderer/ +const OUTSTREAM_RENDERER = { + bootstrapPlayer: function(bid) { + const rendererCode = bid.outstreamRendererCode; + + const config = { + code: bid.adUnitCode, + }; + + if (bid.vastXml) { + config.vastXml = bid.vastXml; + } else if (bid.vastUrl) { + config.vastUrl = bid.vastUrl; + } + + if (!bid.vastXml && !bid.vastUrl) { + logError(`${LOG_PREFIX} no vastXml or vastUrl on bid`); + return; + } + + if (!window.bluebillywig || !window.bluebillywig.renderers || !window.bluebillywig.renderers.length) { + logError(`${LOG_PREFIX} no BlueBillywig renderers found!`); + return; + } + + const rendererId = this.getRendererId(BB_PUBLICATION, rendererCode); + + const override = {} + if (bid.skipOffset) { + override.skipOffset = bid.skipOffset.toString() + } + + const renderer = window.bluebillywig.renderers.find(bbr => bbr._id === rendererId); + if (!renderer) { + logError(`${LOG_PREFIX} couldn't find a renderer with ID ${rendererId}`); + return; + } + + const el = document.getElementById(bid.adUnitCode); + + renderer.bootstrap(config, el, override); + }, + newRenderer: function(adUnitCode, rendererCode) { + const rendererUrl = BB_RENDERER_URL.replace('$RENDERER', rendererCode); + + const renderer = Renderer.install({ + url: rendererUrl, + loaded: false, + adUnitCode + }); + + try { + renderer.setRender(this.outstreamRender); + } catch (err) { + logError(`${LOG_PREFIX} error trying to setRender`, err); + } + + return renderer; + }, + outstreamRender: function(bid) { + bid.renderer.push(() => { + OUTSTREAM_RENDERER.bootstrapPlayer(bid) + }); + }, + getRendererId: function(publication, renderer) { + // By convention, the RENDERER_ID is always the publication name (adagio) and the ad unit code (eg. renderer) + // joined together by a dash. It's used to identify the correct renderer instance on the page in case there's multiple. + return `${publication}-${renderer}`; + } +}; + export const spec = { code: BIDDER_CODE, gvlid: GVLID, @@ -912,8 +978,17 @@ export const spec = { const coppa = _getCoppa(); const schain = _getSchain(validBidRequests[0]); const eids = _getEids(validBidRequests[0]) || []; + const syncEnabled = deepAccess(config.getConfig('userSync'), 'syncEnabled') + const usIfr = syncEnabled && userSync.canBidderRegisterSync('iframe', 'adagio') + + const aucId = generateUUID() + + const adUnits = _map(validBidRequests, (rawBidRequest) => { + const bidRequest = {...rawBidRequest} + + // Fix https://github.com/prebid/Prebid.js/issues/9781 + bidRequest.auctionId = aucId - const adUnits = _map(validBidRequests, (bidRequest) => { const globalFeatures = GlobalExchange.getOrSetGlobalFeatures(); const features = { ...globalFeatures, @@ -921,6 +996,46 @@ export const spec = { adunit_position: getSlotPosition(bidRequest.params.adUnitElementId) // adUnitElementId à déplacer ??? }; + // Force the Split Keyword to be a String + if (bidRequest.params.splitKeyword) { + if (isStr(bidRequest.params.splitKeyword) || isNumber(bidRequest.params.splitKeyword)) { + bidRequest.params.splitKeyword = bidRequest.params.splitKeyword.toString(); + } else { + delete bidRequest.params.splitKeyword; + + logWarn(LOG_PREFIX, 'The splitKeyword param have been removed because the type is invalid, accepted type: number or string.'); + } + } + + // Force the Data Layer key and value to be a String + if (bidRequest.params.dataLayer) { + if (isStr(bidRequest.params.dataLayer) || isNumber(bidRequest.params.dataLayer) || isArray(bidRequest.params.dataLayer) || isFn(bidRequest.params.dataLayer)) { + logWarn(LOG_PREFIX, 'The dataLayer param is invalid, only object is accepted as a type.'); + delete bidRequest.params.dataLayer; + } else { + let invalidDlParam = false; + + bidRequest.params.dl = bidRequest.params.dataLayer + // Remove the dataLayer from the BidRequest to send the `dl` instead of the `dataLayer` + delete bidRequest.params.dataLayer + + Object.keys(bidRequest.params.dl).forEach((key) => { + if (bidRequest.params.dl[key]) { + if (isStr(bidRequest.params.dl[key]) || isNumber(bidRequest.params.dl[key])) { + bidRequest.params.dl[key] = bidRequest.params.dl[key].toString(); + } else { + invalidDlParam = true; + delete bidRequest.params.dl[key]; + } + } + }); + + if (invalidDlParam) { + logWarn(LOG_PREFIX, 'Some parameters of the dataLayer property have been removed because the type is invalid, accepted type: number or string.'); + } + } + } + Object.keys(features).forEach((prop) => { if (features[prop] === '') { delete features[prop]; @@ -986,6 +1101,12 @@ export const spec = { storeRequestInAdagioNS(bidRequest); + // Remove these fields at the very end, so we can still use them before. + delete bidRequest.transactionId; + delete bidRequest.ortb2Imp; + delete bidRequest.ortb2; + delete bidRequest.sizes; + return bidRequest; }); @@ -1012,7 +1133,6 @@ export const spec = { method: 'POST', url: ENDPOINT, data: { - id: generateUUID(), organizationId: organizationId, secure: secure, device: device, @@ -1030,7 +1150,9 @@ export const spec = { eids: eids }, prebidVersion: '$prebid.version$', - featuresVersion: FEATURES_VERSION + featuresVersion: FEATURES_VERSION, + usIfr: usIfr, + adgjs: storage.localStorageIsEnabled() }, options: { contentType: 'text/plain' @@ -1066,21 +1188,18 @@ export const spec = { const mediaTypeContext = deepAccess(bidReq, 'mediaTypes.video.context'); // Adagio SSP returns a `vastXml` only. No `vastUrl` nor `videoCacheKey`. if (!bidObj.vastUrl && bidObj.vastXml) { - bidObj.vastUrl = 'data:text/xml;charset=utf-8;base64,' + btoa(bidObj.vastXml.replace(/\\"/g, '"')); + bidObj.vastUrl = 'data:text/xml;charset=utf-8;base64,' + window.btoa(bidObj.vastXml.replace(/\\"/g, '"')); } if (mediaTypeContext === OUTSTREAM) { - bidObj.renderer = Renderer.install({ - id: bidObj.requestId, - adUnitCode: bidObj.adUnitCode, - url: bidObj.urlRenderer || RENDERER_URL, - config: { - ...deepAccess(bidReq, 'mediaTypes.video'), - ...deepAccess(bidObj, 'outstream', {}) - } - }); - - bidObj.renderer.setRender(_renderer); + bidObj.outstreamRendererCode = deepAccess(bidReq, 'params.rendererCode', BB_RENDERER_DEFAULT) + + if (deepAccess(bidReq, 'mediaTypes.video.skip')) { + const skipOffset = deepAccess(bidReq, 'mediaTypes.video.skipafter', 5) // default 5s. + bidObj.skipOffset = skipOffset + } + + bidObj.renderer = OUTSTREAM_RENDERER.newRenderer(bidObj.adUnitCode, bidObj.outstreamRendererCode); } } @@ -1126,33 +1245,8 @@ export const spec = { * @returns {object} updated params */ transformBidParams(params, isOrtb, adUnit, bidRequests) { - const adagioBidderRequest = find(bidRequests, bidRequest => bidRequest.bidderCode === 'adagio'); - const adagioBid = find(adagioBidderRequest.bids, bid => bid.adUnitCode === adUnit.code); - - if (isOrtb) { - autoFillParams(adagioBid); - - adagioBid.params.auctionId = deepAccess(adagioBidderRequest, 'auctionId'); - - const globalFeatures = GlobalExchange.getOrSetGlobalFeatures(); - adagioBid.params.features = { - ...globalFeatures, - print_number: getPrintNumber(adagioBid.adUnitCode, adagioBidderRequest).toString(), - adunit_position: getSlotPosition(adagioBid.params.adUnitElementId) // adUnitElementId à déplacer ??? - }; - - adagioBid.params.pageviewId = internal.getPageviewId(); - adagioBid.params.prebidVersion = '$prebid.version$'; - adagioBid.params.data = GlobalExchange.getExchangeData(); - - if (deepAccess(adagioBid, 'mediaTypes.video.context') === OUTSTREAM) { - adagioBid.params.playerName = setPlayerName(adagioBid); - } - - storeRequestInAdagioNS(adagioBid); - } - - return adagioBid.params; + // We do not have a prebid server adapter. So let's return unchanged params. + return params; } }; diff --git a/modules/adagioBidAdapter.md b/modules/adagioBidAdapter.md index f10c0a75e4d..45f39fc6f2d 100644 --- a/modules/adagioBidAdapter.md +++ b/modules/adagioBidAdapter.md @@ -10,30 +10,65 @@ Connects to Adagio demand source to fetch bids. ## Configuration +### User Sync + +Add the following code to enable user sync. Adagio strongly recommends enabling user syncing through iframes. This functionality improves DSP user match rates and increases the bid rate and bid price. Be sure to call `pbjs.setConfig()` only once. + +```javascript +pbjs.setConfig({ + userSync: { + iframeEnabled: true, + filterSettings: { + iframe: { + bidders: ['adagio'], + filter: 'include' + } + } + } +}); +``` + +### Bidder Settings + +The Adagio bid adapter uses browser local storage. Since Prebid.js 7.x, the access to it must be explicitly set. + +```js +// https://docs.prebid.org/dev-docs/publisher-api-reference/bidderSettings.html +pbjs.bidderSettings = { + adagio: { + storageAllowed: true + } +} +``` + +### Params configuration + Adagio require several params. These params must be set at Prebid.js BidderConfig config level or at adUnit level. Below, the list of Adagio params and where they can be set. -| Param name | Global config | AdUnit config | -| ---------- | ------------- | ------------- | -| siteId | x | -| organizationId * | | x -| site * | | x -| pagetype | x | x -| category | x | x -| useAdUnitCodeAsAdUnitElementId | x | x -| useAdUnitCodeAsPlacement | x | x -| placement | | x -| adUnitElementId | | x -| debug | | x -| video | | x -| native | | x +| Param name | Global config | AdUnit config | +| ------------------------------ | ------------- | ------------- | +| siteId | x | | +| organizationId * | | x | +| site * | | x | +| pagetype | x | x | +| category | x | x | +| useAdUnitCodeAsAdUnitElementId | x | x | +| useAdUnitCodeAsPlacement | x | x | +| placement | | x | +| adUnitElementId | | x | +| debug | | x | +| video | | x | +| native | | x | +| splitKeyword | | x | +| dataLayer | | x | _* These params are deprecated in favor the Global configuration setup, see below._ -### Global configuration +#### Global Adagio configuration -The global configuration is used to store params once instead of duplicate them to each adUnit. The values will be used as "params" in the ad-request. +The global Adagio configuration is used to store params once instead of duplicate them to each adUnit. The values will be used as "params" in the ad-request. Be sure to call `pbjs.setConfig()` only once. ```javascript pbjs.setConfig({ @@ -52,20 +87,11 @@ pbjs.setConfig({ category: 'sport', // Recommended. Category of the content displayed in the page. useAdUnitCodeAsAdUnitElementId: false, // Optional. Use it by-pass adUnitElementId and use the adUnit code as value useAdUnitCodeAsPlacement: false, // Optional. Use it to by-pass placement and use the adUnit code as value - }, + } }); ``` -#### Note on FPD support - -Adagio will use FPD data as fallback for the params below: -- pagetype -- category - -If the FPD value is an array, the 1st value of this array will be used. - -### adUnit configuration - +#### Ad-unit configuration ```javascript var adUnits = [ { @@ -91,12 +117,24 @@ var adUnits = [ context: 1, plcmttype: 2 }, + splitKeyword: 'splitrule-one', + dl: { + placement: '1234' + } } }] } ]; ``` +#### Note on FPD support + +Adagio will use FPD data as fallback for the params below: +- pagetype +- category + +If the FPD value is an array, the 1st value of this array will be used. + ## Test Parameters ```javascript @@ -108,6 +146,14 @@ var adUnits = [ category: 'sport', useAdUnitCodeAsAdUnitElementId: false, useAdUnitCodeAsPlacement: false, + }, + userSync: { + filterSettings: { + iframe: { + bidders: ['adagio'], + filter: 'include' + } + } } }); diff --git a/modules/adbookpspBidAdapter.js b/modules/adbookpspBidAdapter.js index 917d18e9fae..cb03f2ffc17 100644 --- a/modules/adbookpspBidAdapter.js +++ b/modules/adbookpspBidAdapter.js @@ -203,7 +203,7 @@ function buildRegs(bidderRequest) { function buildSource(bidRequests, bidderRequest) { const source = { fd: 1, - tid: bidderRequest.auctionId, + tid: bidderRequest.ortb2.source.tid, }; const schain = deepAccess(bidRequests, '0.schain'); diff --git a/modules/addefendBidAdapter.js b/modules/addefendBidAdapter.js index f0a6852b084..dbb186fdc86 100644 --- a/modules/addefendBidAdapter.js +++ b/modules/addefendBidAdapter.js @@ -1,4 +1,5 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {getGlobal} from '../src/prebidGlobal.js'; const BIDDER_CODE = 'addefend'; @@ -16,7 +17,7 @@ export const spec = { }, buildRequests: function(validBidRequests, bidderRequest) { let bid = { - v: $$PREBID_GLOBAL$$.version, + v: getGlobal().version, auctionId: false, pageId: false, gdpr_applies: bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies ? bidderRequest.gdprConsent.gdprApplies : 'true', @@ -29,6 +30,7 @@ export const spec = { for (var i = 0; i < validBidRequests.length; i++) { let vb = validBidRequests[i]; let o = vb.params; + // TODO: fix auctionId/transactionId leak: https://github.com/prebid/Prebid.js/issues/9781 bid.auctionId = vb.auctionId; o.bidId = vb.bidId; o.transactionId = vb.transactionId; diff --git a/modules/adfBidAdapter.js b/modules/adfBidAdapter.js index d8e598deb8f..5c4b03c3bb2 100644 --- a/modules/adfBidAdapter.js +++ b/modules/adfBidAdapter.js @@ -3,10 +3,9 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; -import {_map, deepAccess, deepSetValue, mergeDeep, parseSizesInput} from '../src/utils.js'; +import {deepAccess, deepClone, deepSetValue, mergeDeep, parseSizesInput} from '../src/utils.js'; import {config} from '../src/config.js'; import {Renderer} from '../src/Renderer.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; const { getConfig } = config; @@ -16,38 +15,7 @@ const BIDDER_ALIAS = [ { code: 'adformOpenRTB', gvlid: GVLID }, { code: 'adform', gvlid: GVLID } ]; -const NATIVE_ASSET_IDS = { 0: 'title', 2: 'icon', 3: 'image', 5: 'sponsoredBy', 4: 'body', 1: 'cta' }; -const NATIVE_PARAMS = { - title: { - id: 0, - name: 'title' - }, - icon: { - id: 2, - type: 1, - name: 'img' - }, - image: { - id: 3, - type: 3, - name: 'img' - }, - sponsoredBy: { - id: 5, - name: 'data', - type: 1 - }, - body: { - id: 4, - name: 'data', - type: 2 - }, - cta: { - id: 1, - type: 12, - name: 'data' - } -}; + const OUTSTREAM_RENDERER_URL = 'https://s2.adform.net/banners/scripts/video/outstream/render.js'; export const spec = { @@ -61,9 +29,6 @@ export const spec = { return !!(mid || (inv && mname)); }, buildRequests: (validBidRequests, bidderRequest) => { - // convert Native ORTB definition to old-style prebid native definition - validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - let app, site; const commonFpd = bidderRequest.ortb2 || {}; @@ -93,7 +58,7 @@ export const spec = { const adxDomain = setOnAny(validBidRequests, 'params.adxDomain') || 'adx.adform.net'; const pt = setOnAny(validBidRequests, 'params.pt') || setOnAny(validBidRequests, 'params.priceType') || 'net'; - const tid = bidderRequest.auctionId; + const tid = bidderRequest.ortb2?.source?.tid; const test = setOnAny(validBidRequests, 'params.test'); const currency = getConfig('currency.adServerCurrency'); const cur = currency && [ currency ]; @@ -123,45 +88,28 @@ export const spec = { } }; - const assets = _map(bid.nativeParams, (bidParams, key) => { - const props = NATIVE_PARAMS[key]; - const asset = { - required: bidParams.required & 1, - }; - if (props) { - asset.id = props.id; - let wmin, hmin, w, h; - let aRatios = bidParams.aspect_ratios; - - if (aRatios && aRatios[0]) { - aRatios = aRatios[0]; - wmin = aRatios.min_width || 0; - hmin = aRatios.ratio_height * wmin / aRatios.ratio_width | 0; + if (bid.nativeOrtbRequest && bid.nativeOrtbRequest.assets) { + let assets = bid.nativeOrtbRequest.assets; + let requestAssets = []; + for (let i = 0; i < assets.length; i++) { + let asset = deepClone(assets[i]); + let img = asset.img; + if (img) { + let aspectratios = img.ext && img.ext.aspectratios; + + if (aspectratios) { + let ratioWidth = parseInt(aspectratios[0].split(':')[0], 10); + let ratioHeight = parseInt(aspectratios[0].split(':')[1], 10); + img.wmin = img.wmin || 0; + img.hmin = ratioHeight * img.wmin / ratioWidth | 0; + } } - - if (bidParams.sizes) { - const sizes = flatten(bidParams.sizes); - w = sizes[0]; - h = sizes[1]; - } - - asset[props.name] = { - len: bidParams.len, - type: props.type, - wmin, - hmin, - w, - h - }; - - return asset; + requestAssets.push(asset); } - }).filter(Boolean); - if (assets.length) { imp.native = { request: { - assets + assets: requestAssets } }; } @@ -191,7 +139,7 @@ export const spec = { }); const request = { - id: bidderRequest.auctionId, + id: bidderRequest.bidderRequestId, site, app, user, @@ -268,7 +216,9 @@ export const spec = { }; if (bidResponse.native) { - result.native = parseNative(bidResponse); + result.native = { + ortb: bidResponse.native + }; } else { result[ mediaType === VIDEO ? 'vastXml' : 'ad' ] = bidResponse.adm; } @@ -286,25 +236,6 @@ export const spec = { registerBidder(spec); -function parseNative(bid) { - const { assets, link, imptrackers, jstracker } = bid.native; - const result = { - clickUrl: link.url, - clickTrackers: link.clicktrackers || undefined, - impressionTrackers: imptrackers || undefined, - javascriptTrackers: jstracker ? [ jstracker ] : undefined - }; - assets.forEach(asset => { - const kind = NATIVE_ASSET_IDS[asset.id]; - const content = kind && asset[NATIVE_PARAMS[kind].name]; - if (content) { - result[kind] = content.text || content.value || { url: content.url, width: content.w, height: content.h }; - } - }); - - return result; -} - function setOnAny(collection, key) { for (let i = 0, result; i < collection.length; i++) { result = deepAccess(collection[i], key); diff --git a/modules/adgenerationBidAdapter.js b/modules/adgenerationBidAdapter.js index 0e4e9ef6805..f18b7629d8f 100644 --- a/modules/adgenerationBidAdapter.js +++ b/modules/adgenerationBidAdapter.js @@ -1,8 +1,8 @@ -import {tryAppendQueryString, getBidIdParameter, escapeUnsafeChars} from '../src/utils.js'; +import {tryAppendQueryString, getBidIdParameter, escapeUnsafeChars, deepAccess} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, NATIVE} from '../src/mediaTypes.js'; import {config} from '../src/config.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import {convertOrtbRequestToProprietaryNative} from '../src/native.js'; const ADG_BIDDER_CODE = 'adgeneration'; @@ -21,14 +21,14 @@ export const spec = { }, /** * Make a server request from the list of BidRequests. - * - * @param {validBidRequests[]} - an array of bids + * @param validBidRequests + * @param bidderRequest * @return ServerRequest Info describing the request to the server. */ buildRequests: function (validBidRequests, bidderRequest) { // convert Native ORTB definition to old-style prebid native definition validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - const ADGENE_PREBID_VERSION = '1.4.0'; + const ADGENE_PREBID_VERSION = '1.6.0'; let serverRequests = []; for (let i = 0, len = validBidRequests.length; i < len; i++) { const validReq = validBidRequests[i]; @@ -38,6 +38,9 @@ export const spec = { const criteoId = getCriteoId(validReq); const id5id = getId5Id(validReq); const id5LinkType = getId5LinkType(validReq); + const imuid = deepAccess(validReq, 'userId.imuid'); + const gpid = deepAccess(validReq, 'ortb2Imp.ext.gpid'); + const sua = deepAccess(validReq, 'ortb2.device.sua'); let data = ``; data = tryAppendQueryString(data, 'posall', 'SSPLOC'); const id = getBidIdParameter('id', validReq.params); @@ -45,7 +48,7 @@ export const spec = { data = tryAppendQueryString(data, 'sdktype', '0'); data = tryAppendQueryString(data, 'hb', 'true'); data = tryAppendQueryString(data, 't', 'json3'); - data = tryAppendQueryString(data, 'transactionid', validReq.transactionId); + data = tryAppendQueryString(data, 'transactionid', validReq.ortb2Imp?.ext?.tid); data = tryAppendQueryString(data, 'sizes', getSizes(validReq)); data = tryAppendQueryString(data, 'currency', getCurrencyType()); data = tryAppendQueryString(data, 'pbver', '$prebid.version$'); @@ -54,12 +57,17 @@ export const spec = { data = tryAppendQueryString(data, 'adgext_criteo_id', criteoId); data = tryAppendQueryString(data, 'adgext_id5_id', id5id); data = tryAppendQueryString(data, 'adgext_id5_id_link_type', id5LinkType); + data = tryAppendQueryString(data, 'adgext_imuid', imuid); + data = tryAppendQueryString(data, 'adgext_uid2', validReq.userId ? validReq.userId.uid2 : null); + data = tryAppendQueryString(data, 'gpid', gpid ? encodeURIComponent(gpid) : null); + data = tryAppendQueryString(data, 'uach', sua ? JSON.stringify(sua) : null); + data = tryAppendQueryString(data, 'schain', validReq.schain ? encodeURIComponent(JSON.stringify(validReq.schain)) : null); + // native以外にvideo等の対応が入った場合は要修正 if (!validReq.mediaTypes || !validReq.mediaTypes.native) { data = tryAppendQueryString(data, 'imark', '1'); } - // TODO: is 'page' the right value here? data = tryAppendQueryString(data, 'tp', bidderRequest.refererInfo.page); if (isIos()) { const hyperId = getHyperId(validReq); @@ -210,35 +218,45 @@ function appendChildToBody(ad, data) { return ad.replace(/<\/\s?body>/, `${data}`); } +/** + * create APVTag + * @return {string} + */ function createAPVTag() { const APVURL = 'https://cdn.apvdr.com/js/VideoAd.min.js'; - let apvScript = document.createElement('script'); - apvScript.type = 'text/javascript'; - apvScript.id = 'apv'; - apvScript.src = APVURL; - return apvScript.outerHTML; + return `` } +/** + * create ADGBrowserMTag + * @return {string} + */ function createADGBrowserMTag() { const ADGBrowserMURL = 'https://i.socdm.com/sdk/js/adg-browser-m.js'; return ``; } +/** + * create APVTag & insertVast + * @param targetId + * @param vastXml + * @return {string} + */ function insertVASTMethodForAPV(targetId, vastXml) { let apvVideoAdParam = { s: targetId }; - let script = document.createElement(`script`); - script.type = 'text/javascript'; - script.innerHTML = `(function(){ new APV.VideoAd(${escapeUnsafeChars(JSON.stringify(apvVideoAdParam))}).load('${vastXml.replace(/\r?\n/g, '')}'); })();`; - return script.outerHTML; + return `` } +/** + * create ADGBrowserMTag & insertVast + * @param vastXml + * @param marginTop + * @return {string} + */ function insertVASTMethodForADGBrowserM(vastXml, marginTop) { - const script = document.createElement(`script`); - script.type = 'text/javascript'; - script.innerHTML = `window.ADGBrowserM.init({vastXml: '${vastXml.replace(/\r?\n/g, '')}', marginTop: '${marginTop}'});`; - return script.outerHTML; + return `` } /** diff --git a/modules/adhashBidAdapter.js b/modules/adhashBidAdapter.js index 33a85a81525..96e93883de6 100644 --- a/modules/adhashBidAdapter.js +++ b/modules/adhashBidAdapter.js @@ -1,9 +1,9 @@ -import {registerBidder} from '../src/adapters/bidderFactory.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; import { getStorageManager } from '../src/storageManager.js'; import { includes } from '../src/polyfill.js'; -import { BANNER } from '../src/mediaTypes.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; -const VERSION = '3.2'; +const VERSION = '3.6'; const BAD_WORD_STEP = 0.1; const BAD_WORD_MIN = 0.2; const ADHASH_BIDDER_CODE = 'adhash'; @@ -19,6 +19,8 @@ const ADHASH_BIDDER_CODE = 'adhash'; * @returns boolean flag is the page safe */ function brandSafety(badWords, maxScore) { + const delimiter = '~'; + /** * Performs the ROT13 encoding on the string argument and returns the resulting string. * The Adhash bidder uses ROT13 so that the response is not blocked by: @@ -40,17 +42,17 @@ function brandSafety(badWords, maxScore) { /** * Calculates the scoring for each bad word with dimishing returns * @param {integer} points points that this word costs - * @param {integer} occurances number of occurances + * @param {integer} occurrences number of occurrences * @returns {float} final score */ - const scoreCalculator = (points, occurances) => { + const scoreCalculator = (points, occurrences) => { let positive = true; if (points < 0) { points *= -1; positive = false; } let result = 0; - for (let i = 0; i < occurances; i++) { + for (let i = 0; i < occurrences; i++) { result += Math.max(points - i * BAD_WORD_STEP, BAD_WORD_MIN); } return positive ? result : -result; @@ -60,22 +62,50 @@ function brandSafety(badWords, maxScore) { * Checks what rule will match in the given array with words * @param {string} rule rule type (full, partial, starts, ends, regexp) * @param {string} decodedWord decoded word - * @param {array} wordsToMatch array to find a match + * @param {string} wordsToMatch list of all words on the page separated by delimiters * @returns {object|boolean} matched rule and occurances. If nothing is matched returns false */ const wordsMatchedWithRule = function (rule, decodedWord, wordsToMatch) { - if (rule === 'full' && wordsToMatch && wordsToMatch.includes(decodedWord)) { - return { rule, occurances: wordsToMatch.filter(element => element === decodedWord).length }; - } else if (rule === 'partial' && wordsToMatch && wordsToMatch.some(element => element.indexOf(decodedWord) > -1)) { - return { rule, occurances: wordsToMatch.filter(element => element.indexOf(decodedWord) > -1).length }; - } else if (rule === 'starts' && wordsToMatch && wordsToMatch.some(word => word.startsWith(decodedWord))) { - return { rule, occurances: wordsToMatch.filter(element => element.startsWith(decodedWord)).length }; - } else if (rule === 'ends' && wordsToMatch && wordsToMatch.some(word => word.endsWith(decodedWord))) { - return { rule, occurances: wordsToMatch.filter(element => element.endsWith(decodedWord)).length }; - } else if (rule === 'regexp' && wordsToMatch && wordsToMatch.some(element => element.match(new RegExp(decodedWord, 'i')))) { - return { rule, occurances: wordsToMatch.filter(element => element.match(new RegExp(decodedWord, 'i'))).length }; + if (!wordsToMatch) { + return false; + } + + let occurrences; + let adjustedWordToMatch; + decodedWord = decodedWord.split(' ').join(`${delimiter}${delimiter}`); + switch (rule) { + case 'full': + adjustedWordToMatch = `${delimiter}${decodedWord}${delimiter}`; + break; + case 'partial': + adjustedWordToMatch = decodedWord; + break; + case 'starts': + adjustedWordToMatch = `${delimiter}${decodedWord}`; + break; + case 'ends': + adjustedWordToMatch = `${decodedWord}${delimiter}`; + break; + case 'combo': + const allOccurrences = []; + const paddedWordsToMatch = `${delimiter}${wordsToMatch}${delimiter}`; + const decodedWordsSplit = decodedWord.split(`${delimiter}${delimiter}`); + for (const decodedWordPart of decodedWordsSplit) { + adjustedWordToMatch = `${delimiter}${decodedWordPart}${delimiter}`; + allOccurrences.push(paddedWordsToMatch.split(adjustedWordToMatch).length - 1); + } + occurrences = Math.min(...allOccurrences); + return occurrences > 0 ? { rule, occurrences } : false; + case 'regexp': + occurrences = [...wordsToMatch.matchAll(new RegExp(decodedWord, 'gi'))].length; + return occurrences > 0 ? { rule, occurrences } : false; + default: + return false; } - return false; + + const paddedWordsToMatch = `${delimiter}${wordsToMatch}${delimiter}`; + occurrences = paddedWordsToMatch.split(adjustedWordToMatch).length - 1; + return occurrences > 0 ? { rule, occurrences } : false; }; // Default parameters if the bidder is unable to send some of them @@ -91,11 +121,11 @@ function brandSafety(badWords, maxScore) { .toLowerCase() .trim(); const content = window.top.document.body.innerText.toLowerCase(); - const contentWords = content.trim().split(/\s+/).length; // \p{L} matches a single unicode code point in the category 'letter'. Matches any kind of letter from any language. const regexp = new RegExp('[\\p{L}]+', 'gu'); - const words = content.match(regexp); - const wordsInUrl = wordsAndNumbersInUrl.match(regexp); + const wordsMatched = content.match(regexp); + const words = wordsMatched.join(`${delimiter}${delimiter}`); + const wordsInUrl = wordsAndNumbersInUrl.match(regexp).join(`${delimiter}${delimiter}`); for (const [word, rule, points] of badWords) { const decodedWord = rot13(word.toLowerCase()); @@ -110,19 +140,11 @@ function brandSafety(badWords, maxScore) { // Check if site content's words match any of our brand safety rules const matchedRule = wordsMatchedWithRule(rule, decodedWord, words); - if (matchedRule.rule === 'full') { - score += scoreCalculator(points, matchedRule.occurances); - } else if (matchedRule.rule === 'partial') { - score += scoreCalculator(points, matchedRule.occurances); - } else if (matchedRule.rule === 'starts') { - score += scoreCalculator(points, matchedRule.occurances); - } else if (matchedRule.rule === 'ends') { - score += scoreCalculator(points, matchedRule.occurances); - } else if (matchedRule.rule === 'regexp') { - score += scoreCalculator(points, matchedRule.occurances); + if (matchedRule !== false) { + score += scoreCalculator(points, matchedRule.occurrences); } } - return score < (maxScore * contentWords) / 1000; + return score < (maxScore * wordsMatched.length) / 1000; } catch (e) { return true; } @@ -130,13 +152,13 @@ function brandSafety(badWords, maxScore) { export const spec = { code: ADHASH_BIDDER_CODE, - supportedMediaTypes: [ BANNER ], + supportedMediaTypes: [ BANNER, VIDEO ], isBidRequestValid: (bid) => { try { const { publisherId, platformURL, bidderURL } = bid.params; return ( - includes(Object.keys(bid.mediaTypes), BANNER) && + (includes(Object.keys(bid.mediaTypes), BANNER) || includes(Object.keys(bid.mediaTypes), VIDEO)) && typeof publisherId === 'string' && publisherId.length === 42 && typeof platformURL === 'string' && @@ -168,7 +190,14 @@ export const spec = { const url = `${bidderURL}/rtb?version=${VERSION}&prebid=true`; const index = Math.floor(Math.random() * validBidRequests[i].sizes.length); const size = validBidRequests[i].sizes[index].join('x'); - + const creativeData = includes(Object.keys(validBidRequests[i].mediaTypes), VIDEO) ? { + size: 'preroll', + position: validBidRequests[i].adUnitCode, + playerSize: size + } : { + size: size, + position: validBidRequests[i].adUnitCode + }; let recentAds = []; if (storage.localStorageIsEnabled()) { const prefix = validBidRequests[i].params.prefix || 'adHash'; @@ -176,8 +205,8 @@ export const spec = { } // Needed for the ad density calculation - var adHeight = validBidRequests[i].sizes[index][1]; - var adWidth = validBidRequests[i].sizes[index][0]; + const adHeight = validBidRequests[i].sizes[index][1]; + const adWidth = validBidRequests[i].sizes[index][0]; if (!window.adsCount) { window.adsCount = 0; } @@ -204,10 +233,7 @@ export const spec = { language: window.navigator.language, userAgent: window.navigator.userAgent }, - creatives: [{ - size: size, - position: validBidRequests[i].adUnitCode - }], + creatives: [creativeData], blockedCreatives: [], currentTimestamp: (new Date().getTime() / 1000) | 0, recentAds: recentAds, @@ -229,7 +255,6 @@ export const spec = { interpretResponse: (serverResponse, request) => { const responseBody = serverResponse ? serverResponse.body : {}; - if ( !responseBody.creatives || responseBody.creatives.length === 0 || @@ -241,18 +266,12 @@ export const spec = { const publisherURL = JSON.stringify(request.bidRequest.params.platformURL); const bidderURL = request.bidRequest.params.bidderURL || 'https://bidder.adhash.com'; const oneTimeId = request.bidRequest.adUnitCode + Math.random().toFixed(16).replace('0.', '.'); - const globalScript = !request.bidRequest.params.globalScript - ? `` - : ''; const bidderResponse = JSON.stringify({ responseText: JSON.stringify(responseBody) }); const requestData = JSON.stringify(request.data); - return [{ + let response = { requestId: request.bidRequest.bidId, cpm: responseBody.creatives[0].costEUR, - ad: - `
${globalScript} - `, width: request.bidRequest.sizes[0][0], height: request.bidRequest.sizes[0][1], creativeId: request.bidRequest.adUnitCode, @@ -262,7 +281,21 @@ export const spec = { meta: { advertiserDomains: responseBody.advertiserDomains ? [responseBody.advertiserDomains] : [] } - }]; + }; + if (typeof request == 'object' && typeof request.bidRequest == 'object' && typeof request.bidRequest.mediaTypes == 'object' && includes(Object.keys(request.bidRequest.mediaTypes), BANNER)) { + response = Object.assign({ + ad: + `
+ + ` + }, response); + } else if (includes(Object.keys(request.bidRequest.mediaTypes), VIDEO)) { + response = Object.assign({ + vastUrl: responseBody.creatives[0].vastURL, + mediaType: VIDEO + }, response); + } + return [response]; } }; diff --git a/modules/adkernelAdnAnalyticsAdapter.js b/modules/adkernelAdnAnalyticsAdapter.js index 901c0d2fd98..48897f8516b 100644 --- a/modules/adkernelAdnAnalyticsAdapter.js +++ b/modules/adkernelAdnAnalyticsAdapter.js @@ -5,12 +5,14 @@ import { logError, parseUrl, _each } from '../src/utils.js'; import {ajax} from '../src/ajax.js'; import {getStorageManager} from '../src/storageManager.js'; import {config} from '../src/config.js'; +import {MODULE_TYPE_ANALYTICS} from '../src/activities/modules.js'; +const MODULE_CODE = 'adkernelAdn'; const GVLID = 14; const ANALYTICS_VERSION = '1.0.2'; const DEFAULT_QUEUE_TIMEOUT = 4000; const DEFAULT_HOST = 'tag.adkernel.com'; -const storageObj = getStorageManager({gvlid: GVLID}); +const storageObj = getStorageManager({moduleType: MODULE_TYPE_ANALYTICS, moduleName: MODULE_CODE}); const ADK_HB_EVENTS = { AUCTION_INIT: 'auctionInit', @@ -104,7 +106,7 @@ analyticsAdapter.enableAnalytics = (config) => { adapterManager.registerAnalyticsAdapter({ adapter: analyticsAdapter, - code: 'adkernelAdn', + code: MODULE_CODE, gvlid: GVLID }); diff --git a/modules/adkernelAdnBidAdapter.js b/modules/adkernelAdnBidAdapter.js index 3e348e0795d..4db46aca3c6 100644 --- a/modules/adkernelAdnBidAdapter.js +++ b/modules/adkernelAdnBidAdapter.js @@ -1,4 +1,4 @@ -import { deepAccess, parseSizesInput, isArray, deepSetValue, isStr, isNumber, logInfo } from '../src/utils.js'; +import {deepAccess, deepSetValue, isArray, isNumber, isStr, logInfo, parseSizesInput} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {config} from '../src/config.js'; @@ -58,11 +58,11 @@ function canonicalizeSizesArray(sizes) { } function buildRequestParams(tags, bidderRequest) { - let {auctionId, gdprConsent, uspConsent, transactionId, refererInfo} = bidderRequest; + let {gdprConsent, uspConsent, refererInfo, ortb2} = bidderRequest; let req = { - id: auctionId, - // TODO: transactionId is undefined here, should this be auctionId? see #8573 - tid: transactionId, + id: bidderRequest.bidderRequestId, + // TODO: root-level `tid` is not ORTB; is this intentional? + tid: ortb2?.source?.tid, site: buildSite(refererInfo), imp: tags }; @@ -99,7 +99,6 @@ function buildSite(refInfo) { function buildBid(tag) { let bid = { requestId: tag.impid, - bidderCode: spec.code, cpm: tag.bid, creativeId: tag.crid, currency: 'USD', diff --git a/modules/adkernelBidAdapter.js b/modules/adkernelBidAdapter.js index 5736c7c19ba..64567832dbd 100644 --- a/modules/adkernelBidAdapter.js +++ b/modules/adkernelBidAdapter.js @@ -6,6 +6,7 @@ import { deepAccess, deepSetValue, getAdUnitSizes, + getDefinedParams, getDNT, isArray, isArrayOfNums, @@ -14,14 +15,13 @@ import { isPlainObject, isStr, mergeDeep, - parseGPTSingleSizeArrayToRtbSize, - getDefinedParams + parseGPTSingleSizeArrayToRtbSize } from '../src/utils.js'; import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {find} from '../src/polyfill.js'; import {config} from '../src/config.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import {convertOrtbRequestToProprietaryNative} from '../src/native.js'; /* * In case you're AdKernel whitelable platform's client who needs branded adapter to @@ -90,7 +90,6 @@ export const spec = { {code: 'denakop'}, {code: 'rtbanalytica'}, {code: 'unibots'}, - {code: 'catapultx'}, {code: 'ergadx'}, {code: 'turktelekom'}, {code: 'felixads'}, @@ -98,7 +97,10 @@ export const spec = { {code: 'sonic_twist'}, {code: 'displayioads'}, {code: 'rtbdemand_com'}, - {code: 'bidbuddy'} + {code: 'bidbuddy'}, + {code: 'adliveconnect'}, + {code: 'didnadisplay'}, + {code: 'qortex'} ], supportedMediaTypes: [BANNER, VIDEO, NATIVE], @@ -489,9 +491,9 @@ function makeRegulations(bidderRequest) { * @returns */ function makeBaseRequest(bidderRequest, imps, fpd) { - let {auctionId, timeout} = bidderRequest; + let {timeout} = bidderRequest; let request = { - 'id': auctionId, + 'id': bidderRequest.bidderRequestId, 'imp': imps, 'at': 1, 'tmax': parseInt(timeout) diff --git a/modules/admanBidAdapter.js b/modules/admanBidAdapter.js index ed984645d0d..2ee6ecfcb56 100644 --- a/modules/admanBidAdapter.js +++ b/modules/admanBidAdapter.js @@ -66,6 +66,7 @@ export const spec = { buildRequests: (validBidRequests = [], bidderRequest) => { // convert Native ORTB definition to old-style prebid native definition validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + const content = deepAccess(bidderRequest, 'ortb2.site.content', config.getAnyConfig('ortb2.site.content')); let winTop = window; let location; @@ -95,47 +96,63 @@ export const spec = { if (bidderRequest.gdprConsent) { request.gdpr = bidderRequest.gdprConsent } + if (content) { + request.content = content; + } } const len = validBidRequests.length; for (let i = 0; i < len; i++) { - let bid = validBidRequests[i]; - let traff = bid.params.traffic || BANNER + const bid = validBidRequests[i]; + const { params, bidId, mediaTypes } = bid; + const placement = { - placementId: bid.params.placementId, - bidId: bid.bidId, - sizes: bid.mediaTypes && bid.mediaTypes[traff] && bid.mediaTypes[traff].sizes ? bid.mediaTypes[traff].sizes : [], - traffic: traff, + placementId: params.placementId, + bidId, eids: [], bidFloor: getBidFloor(bid) } + + if (bid.transactionId) { + placement.ext = placement.ext || {}; + placement.ext.tid = bid.transactionId; + } + if (bid.schain) { placement.schain = bid.schain; } + if (bid.userId) { getUserId(placement.eids, bid.userId.uid2 && bid.userId.uid2.id, 'uidapi.com'); getUserId(placement.eids, bid.userId.lotamePanoramaId, 'lotame.com'); getUserId(placement.eids, bid.userId.idx, 'idx.lat'); } - if (traff === VIDEO) { - placement.playerSize = bid.mediaTypes[VIDEO].playerSize; - placement.minduration = bid.mediaTypes[VIDEO].minduration; - placement.maxduration = bid.mediaTypes[VIDEO].maxduration; - placement.mimes = bid.mediaTypes[VIDEO].mimes; - placement.protocols = bid.mediaTypes[VIDEO].protocols; - placement.startdelay = bid.mediaTypes[VIDEO].startdelay; - placement.placement = bid.mediaTypes[VIDEO].placement; - placement.skip = bid.mediaTypes[VIDEO].skip; - placement.skipafter = bid.mediaTypes[VIDEO].skipafter; - placement.minbitrate = bid.mediaTypes[VIDEO].minbitrate; - placement.maxbitrate = bid.mediaTypes[VIDEO].maxbitrate; - placement.delivery = bid.mediaTypes[VIDEO].delivery; - placement.playbackmethod = bid.mediaTypes[VIDEO].playbackmethod; - placement.api = bid.mediaTypes[VIDEO].api; - placement.linearity = bid.mediaTypes[VIDEO].linearity; + + if (mediaTypes?.[BANNER]) { + placement.traffic = BANNER; + placement.sizes = mediaTypes[BANNER].sizes; + } else if (mediaTypes?.[VIDEO]) { + placement.traffic = VIDEO; + placement.playerSize = mediaTypes[VIDEO].playerSize; + placement.minduration = mediaTypes[VIDEO].minduration; + placement.maxduration = mediaTypes[VIDEO].maxduration; + placement.mimes = mediaTypes[VIDEO].mimes; + placement.protocols = mediaTypes[VIDEO].protocols; + placement.startdelay = mediaTypes[VIDEO].startdelay; + placement.placement = mediaTypes[VIDEO].placement; + placement.skip = mediaTypes[VIDEO].skip; + placement.skipafter = mediaTypes[VIDEO].skipafter; + placement.minbitrate = mediaTypes[VIDEO].minbitrate; + placement.maxbitrate = mediaTypes[VIDEO].maxbitrate; + placement.delivery = mediaTypes[VIDEO].delivery; + placement.playbackmethod = mediaTypes[VIDEO].playbackmethod; + placement.api = mediaTypes[VIDEO].api; + placement.linearity = mediaTypes[VIDEO].linearity; } + placements.push(placement); } + return { method: 'POST', url: AD_URL, diff --git a/modules/admaticBidAdapter.js b/modules/admaticBidAdapter.js index 808c788fcb9..027f924ac5d 100644 --- a/modules/admaticBidAdapter.js +++ b/modules/admaticBidAdapter.js @@ -1,4 +1,4 @@ -import { getValue, logError, deepAccess, getBidIdParameter, isArray } from '../src/utils.js'; +import { getValue, logError, isEmpty, deepAccess, getBidIdParameter, isArray } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; @@ -34,6 +34,7 @@ export const spec = { */ buildRequests: (validBidRequests, bidderRequest) => { const bids = validBidRequests.map(buildRequestObject); + const blacklist = bidderRequest.ortb2; const networkId = getValue(validBidRequests[0].params, 'networkId'); const host = getValue(validBidRequests[0].params, 'host'); const currency = config.getConfig('currency.adServerCurrency') || 'TRY'; @@ -59,6 +60,10 @@ export const spec = { } }; + if (!isEmpty(blacklist.badv)) { + payload.blacklist = blacklist.badv; + }; + if (payload) { switch (bidderName) { case 'pixad': @@ -69,7 +74,7 @@ export const spec = { break; } - return { method: 'POST', url: `https://${host}/pb?bidder=${bidderName}`, data: payload, options: { contentType: 'application/json' } }; + return { method: 'POST', url: `https://${host}/pb`, data: payload, options: { contentType: 'application/json' } }; } }, @@ -88,7 +93,7 @@ export const spec = { * @return {Bid[]} */ interpretResponse: (response, request) => { - const body = response.body || response; + const body = response.body; const bidResponses = []; if (body && body?.data && isArray(body.data)) { body.data.forEach(bid => { @@ -99,13 +104,23 @@ export const spec = { height: bid.height, currency: body.cur || 'TRY', netRevenue: true, - ad: bid.party_tag, creativeId: bid.creative_id, meta: { advertiserDomains: bid && bid.adomain ? bid.adomain : [] }, - ttl: 360, - bidder: bid.bidder + bidder: bid.bidder, + mediaType: bid.type, + ttl: 60 + }; + + if (resbid.mediaType === 'video' && isUrl(bid.party_tag)) { + resbid.vastUrl = bid.party_tag; + resbid.vastImpUrl = bid.iurl; + } else if (resbid.mediaType === 'video') { + resbid.vastXml = bid.party_tag; + resbid.vastImpUrl = bid.iurl; + } else if (resbid.mediaType === 'banner') { + resbid.ad = bid.party_tag; }; bidResponses.push(resbid); @@ -115,6 +130,15 @@ export const spec = { } }; +function isUrl(str) { + try { + URL(str); + return true; + } catch (error) { + return false; + } +}; + function enrichSlotWithFloors(slot, bidRequest) { try { const slotFloors = {}; @@ -163,6 +187,14 @@ function parseSize(size) { function buildRequestObject(bid) { const reqObj = {}; reqObj.size = getSizes(bid); + if (bid.mediaTypes?.banner) { + reqObj.type = 'banner'; + reqObj.mediatype = {}; + } + if (bid.mediaTypes?.video) { + reqObj.type = 'video'; + reqObj.mediatype = bid.mediaTypes.video; + } reqObj.id = getBidIdParameter('bidId', bid); enrichSlotWithFloors(reqObj, bid); diff --git a/modules/admixerBidAdapter.js b/modules/admixerBidAdapter.js index 8dbcfdafd32..1006fef631c 100644 --- a/modules/admixerBidAdapter.js +++ b/modules/admixerBidAdapter.js @@ -1,17 +1,23 @@ -import {logError} from '../src/utils.js'; +import {isStr, logError} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {config} from '../src/config.js'; import {BANNER, VIDEO, NATIVE} from '../src/mediaTypes.js'; import {convertOrtbRequestToProprietaryNative} from '../src/native.js'; +import {find} from '../src/polyfill.js'; const BIDDER_CODE = 'admixer'; -const BIDDER_CODE_ADX = 'admixeradx'; -const ALIASES = ['go2net', 'adblender', 'adsyield', 'futureads', 'admixeradx']; const ENDPOINT_URL = 'https://inv-nets.admixer.net/prebid.1.2.aspx'; -const ADX_ENDPOINT_URL = 'https://inv-nets.admixer.net/adxprebid.1.2.aspx'; +const ALIASES = [ + {code: 'go2net', endpoint: 'https://ads.go2net.com.ua/prebid.1.2.aspx'}, + 'adblender', + {code: 'adsyield', endpoint: 'https://ads.adsyield.com/prebid.1.2.aspx'}, + {code: 'futureads', endpoint: 'https://ads.futureads.io/prebid.1.2.aspx'}, + {code: 'smn', endpoint: 'https://ads.smn.rs/prebid.1.2.aspx'}, + {code: 'admixeradx', endpoint: 'https://inv-nets.admixer.net/adxprebid.1.2.aspx'}, +]; export const spec = { code: BIDDER_CODE, - aliases: ALIASES, + aliases: ALIASES.map(val => isStr(val) ? val : val.code), supportedMediaTypes: [BANNER, VIDEO, NATIVE], /** * Determines whether or not the given bid request is valid. @@ -72,10 +78,7 @@ export const spec = { return { method: 'POST', url: - endpointUrl || - (bidderRequest.bidderCode === BIDDER_CODE_ADX - ? ADX_ENDPOINT_URL - : ENDPOINT_URL), + endpointUrl || getEndpointUrl(bidderRequest.bidderCode), data: payload, }; }, @@ -106,6 +109,9 @@ export const spec = { return pixels; } }; +function getEndpointUrl(code) { + return find(ALIASES, (val) => val.code === code)?.endpoint || ENDPOINT_URL; +} function getBidFloor(bid) { try { const bidFloor = bid.getFloor({ diff --git a/modules/admixerIdSystem.js b/modules/admixerIdSystem.js index 49ffe4f4680..7fbebecfc12 100644 --- a/modules/admixerIdSystem.js +++ b/modules/admixerIdSystem.js @@ -9,8 +9,10 @@ import { logError, logInfo } from '../src/utils.js' import { ajax } from '../src/ajax.js'; import { submodule } from '../src/hook.js'; import {getStorageManager} from '../src/storageManager.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; -export const storage = getStorageManager(); +const NAME = 'admixerId'; +export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: NAME}); /** @type {Submodule} */ export const admixerIdSubmodule = { @@ -18,7 +20,7 @@ export const admixerIdSubmodule = { * used to link submodule with config * @type {string} */ - name: 'admixerId', + name: NAME, /** * used to specify vendor id * @type {number} diff --git a/modules/adnuntiusBidAdapter.js b/modules/adnuntiusBidAdapter.js index ea3b723b316..d1192c2eb64 100644 --- a/modules/adnuntiusBidAdapter.js +++ b/modules/adnuntiusBidAdapter.js @@ -5,10 +5,17 @@ import { config } from '../src/config.js'; import { getStorageManager } from '../src/storageManager.js'; const BIDDER_CODE = 'adnuntius'; +const BIDDER_CODE_DEAL_ALIAS_BASE = 'adndeal'; +const BIDDER_CODE_DEAL_ALIAS_1 = BIDDER_CODE_DEAL_ALIAS_BASE + '1'; +const BIDDER_CODE_DEAL_ALIAS_2 = BIDDER_CODE_DEAL_ALIAS_BASE + '2'; +const BIDDER_CODE_DEAL_ALIAS_3 = BIDDER_CODE_DEAL_ALIAS_BASE + '3'; +const BIDDER_CODE_DEAL_ALIAS_4 = BIDDER_CODE_DEAL_ALIAS_BASE + '4'; +const BIDDER_CODE_DEAL_ALIAS_5 = BIDDER_CODE_DEAL_ALIAS_BASE + '5'; const ENDPOINT_URL = 'https://ads.adnuntius.delivery/i'; +const ENDPOINT_URL_EUROPE = 'https://europe.delivery.adnuntius.com/i'; const GVLID = 855; const DEFAULT_VAST_VERSION = 'vast4' -// const DEFAULT_NATIVE = 'native' +const MAXIMUM_DEALS_LIMIT = 5; const checkSegment = function (segment) { if (isStr(segment)) return segment; @@ -28,40 +35,13 @@ const getSegmentsFromOrtb = function (ortb2) { return segments } -// function createNative(ad) { -// const native = {}; -// const assets = ad.assets -// native.title = ad.text.title.content; -// native.image = { -// url: assets.image.cdnId, -// height: assets.image.height, -// width: assets.image.width, -// }; -// if (assets.icon) { -// native.icon = { -// url: assets.icon.cdnId, -// height: assets.icon.height, -// width: assets.icon.width, -// }; -// } - -// native.sponsoredBy = ad.text.sponsoredBy?.content || ''; -// native.body = ad.text.body?.content || ''; -// native.cta = ad.text.cta?.content || ''; -// native.clickUrl = ad.destinationUrls.destination || ''; -// native.impressionTrackers = ad.impressionTrackingUrls || [ad.renderedPixel]; - -// return native; -// } - const handleMeta = function () { - const storage = getStorageManager({ gvlid: GVLID, bidderCode: BIDDER_CODE }) + const storage = getStorageManager({ bidderCode: BIDDER_CODE }) let adnMeta = null if (storage.localStorageIsEnabled()) { adnMeta = JSON.parse(storage.getDataFromLocalStorage('adn.metaData')) } - const meta = (adnMeta !== null) ? adnMeta.reduce((acc, cur) => { return { ...acc, [cur.key]: cur.value } }, {}) : {} - return meta + return (adnMeta !== null) ? adnMeta.reduce((acc, cur) => { return { ...acc, [cur.key]: cur.value } }, {}) : {} } const getUsi = function (meta, ortb2, bidderRequest) { @@ -70,12 +50,23 @@ const getUsi = function (meta, ortb2, bidderRequest) { return usi } +const AU_ID_REGEX = new RegExp('^[0-9A-Fa-f]{1,20}$'); + export const spec = { code: BIDDER_CODE, + aliases: [ + BIDDER_CODE_DEAL_ALIAS_1, + BIDDER_CODE_DEAL_ALIAS_2, + BIDDER_CODE_DEAL_ALIAS_3, + BIDDER_CODE_DEAL_ALIAS_4, + BIDDER_CODE_DEAL_ALIAS_5 + ], gvlid: GVLID, supportedMediaTypes: [BANNER, VIDEO], isBidRequestValid: function (bid) { - return !!(bid.bidId || (bid.params.member && bid.params.invCode)); + // The auId MUST be a hexadecimal string + const validAuId = AU_ID_REGEX.test(bid.params.auId); + return !!(validAuId && (bid.bidId || (bid.params.member && bid.params.invCode))); }, buildRequests: function (validBidRequests, bidderRequest) { @@ -99,20 +90,18 @@ export const spec = { if (gdprApplies !== undefined) request.push('consentString=' + consentString); if (segments.length > 0) request.push('segments=' + segments.join(',')); if (usi) request.push('userId=' + usi); - if (bidderConfig.useCookie === false) request.push('noCookies=true') - for (var i = 0; i < validBidRequests.length; i++) { + if (bidderConfig.useCookie === false) request.push('noCookies=true'); + if (bidderConfig.maxDeals > 0) request.push('ds=' + Math.min(bidderConfig.maxDeals, MAXIMUM_DEALS_LIMIT)); + for (let i = 0; i < validBidRequests.length; i++) { const bid = validBidRequests[i] let network = bid.params.network || 'network'; + const maxDeals = Math.max(0, Math.min(bid.params.maxDeals || 0, MAXIMUM_DEALS_LIMIT)); const targeting = bid.params.targeting || {}; if (bid.mediaTypes && bid.mediaTypes.video && bid.mediaTypes.video.context !== 'outstream') { network += '_video' } - // if (bid.mediaTypes && bid.mediaTypes.native) { - // network += '_native' - // } - bidRequests[network] = bidRequests[network] || []; bidRequests[network].push(bid); @@ -120,20 +109,20 @@ export const spec = { networks[network].adUnits = networks[network].adUnits || []; if (bidderRequest && bidderRequest.refererInfo) networks[network].context = bidderRequest.refererInfo.page; if (adnMeta) networks[network].metaData = adnMeta; - const adUnit = { ...targeting, auId: bid.params.auId, targetId: bid.bidId } + const adUnit = { ...targeting, auId: bid.params.auId, targetId: bid.bidId, maxDeals: maxDeals } if (bid.mediaTypes && bid.mediaTypes.banner && bid.mediaTypes.banner.sizes) adUnit.dimensions = bid.mediaTypes.banner.sizes networks[network].adUnits.push(adUnit); } const networkKeys = Object.keys(networks) - for (var j = 0; j < networkKeys.length; j++) { + for (let j = 0; j < networkKeys.length; j++) { const network = networkKeys[j]; const networkRequest = [...request] if (network.indexOf('_video') > -1) { networkRequest.push('tt=' + DEFAULT_VAST_VERSION) } - // if (network.indexOf('_native') > -1) { networkRequest.push('tt=' + DEFAULT_NATIVE) } + const requestURL = gdprApplies ? ENDPOINT_URL_EUROPE : ENDPOINT_URL requests.push({ method: 'POST', - url: ENDPOINT_URL + '?' + networkRequest.join('&'), + url: requestURL + '?' + networkRequest.join('&'), data: JSON.stringify(networks[network]), bid: bidRequests[network] }); @@ -144,50 +133,82 @@ export const spec = { interpretResponse: function (serverResponse, bidRequest) { const adUnits = serverResponse.body.adUnits; - const bidResponsesById = adUnits.reduce((response, adUnit) => { - if (adUnit.matchedAdCount >= 1) { - const ad = adUnit.ads[0]; - const effectiveCpm = (ad.bid) ? ad.bid.amount * 1000 : 0; - const adResponse = { - ...response, - [adUnit.targetId]: { - requestId: adUnit.targetId, - cpm: effectiveCpm, - width: Number(ad.creativeWidth), - height: Number(ad.creativeHeight), - creativeId: ad.creativeId, - currency: (ad.bid) ? ad.bid.currency : 'EUR', - dealId: ad.dealId || '', - meta: { - advertiserDomains: (ad.destinationUrls.destination) ? [ad.destinationUrls.destination.split('/')[2]] : [] - - }, - netRevenue: false, - ttl: 360, - } - } - - if (adUnit.vastXml) { - adResponse[adUnit.targetId].vastXml = adUnit.vastXml - adResponse[adUnit.targetId].mediaType = VIDEO - // } else if (ad.assets && ad.assets.image && ad.text && ad.text.title && ad.text.body && ad.destinationUrls && ad.destinationUrls.destination) { - // adResponse[adUnit.targetId].native = createNative(ad); - // adResponse[adUnit.targetId].mediaType = NATIVE; - } else { - adResponse[adUnit.targetId].ad = adUnit.html - } - - return adResponse - } else return response + + function buildAdResponse(bidderCode, ad, adUnit, dealCount) { + const destinationUrls = ad.destinationUrls || {}; + const advertiserDomains = []; + for (const value of Object.values(destinationUrls)) { + advertiserDomains.push(value.split('/')[2]) + } + const adResponse = { + bidderCode: bidderCode, + requestId: adUnit.targetId, + cpm: (ad.bid) ? ad.bid.amount * 1000 : 0, + width: Number(ad.creativeWidth), + height: Number(ad.creativeHeight), + creativeId: ad.creativeId, + currency: (ad.bid) ? ad.bid.currency : 'EUR', + dealId: ad.dealId || '', + dealCount: dealCount, + meta: { + advertiserDomains: advertiserDomains + }, + netRevenue: false, + ttl: 360, + }; + // Deal bids provide the rendered ad content along with the + // bid; whereas regular bids have it stored on the ad-unit. + const isDeal = dealCount > 0; + const renderSource = isDeal ? ad : adUnit; + if (renderSource.vastXml) { + adResponse.vastXml = renderSource.vastXml + adResponse.mediaType = VIDEO + } else { + adResponse.ad = renderSource.html + } + return adResponse; + } + + const bidsById = bidRequest.bid.reduce((response, bid) => { + return { + ...response, + [bid.bidId]: bid + }; }, {}); - const bidResponse = bidRequest.bid.map(bid => bid.bidId).reduce((request, adunitId) => { - if (bidResponsesById[adunitId]) { request.push(bidResponsesById[adunitId]) } - return request + const hasBidAdUnits = adUnits.filter((au) => { + const bid = bidsById[au.targetId]; + if (bid && bid.bidder === BIDDER_CODE) { + return au.matchedAdCount > 0; + } else { + // We do NOT accept bids when using this adaptor via one of the + // "deals" aliases; those requests are for ONLY getting deals from Adnuntius + return false; + } + }); + const hasDealsAdUnits = adUnits.filter((au) => { + return au.deals && au.deals.length > 0; + }); + + const dealAdResponses = hasDealsAdUnits.reduce((response, au) => { + const bid = bidsById[au.targetId]; + if (bid) { + (au.deals || []).forEach((deal, i) => { + response.push(buildAdResponse(bid.bidder, deal, au, i + 1)); + }); + } + return response; }, []); - return bidResponse - }, + const bidAdResponses = hasBidAdUnits.reduce((response, au) => { + const bid = bidsById[au.targetId]; + if (bid) { + response.push(buildAdResponse(bid.bidder, au.ads[0], au, 0)); + } + return response; + }, []); + return [...dealAdResponses, ...bidAdResponses]; + } } registerBidder(spec); diff --git a/modules/adnuntiusBidAdapter.md b/modules/adnuntiusBidAdapter.md index aed12079856..d5ecf5d4ee3 100644 --- a/modules/adnuntiusBidAdapter.md +++ b/modules/adnuntiusBidAdapter.md @@ -9,7 +9,7 @@ Maintainer: info@adnuntius.com # Description Adnuntius Bidder Adapter for Prebid.js. -Only Banner format is supported. +Banner and Video formats are supported. # Test Parameters ``` @@ -27,6 +27,7 @@ Only Banner format is supported. params: { auId: "8b6bc", network: "adnuntius", + maxDeals: 0 } }, ] diff --git a/modules/adpod.js b/modules/adpod.js index f1ab4bd2ef1..4ab8e4e5ab9 100644 --- a/modules/adpod.js +++ b/modules/adpod.js @@ -28,14 +28,13 @@ import { import { addBidToAuction, AUCTION_IN_PROGRESS, - callPrebidCache, doCallbacksIfTimedout, getPriceByGranularity, getPriceGranularity } from '../src/auction.js'; import {checkAdUnitSetup} from '../src/prebid.js'; import {checkVideoBidSetup} from '../src/video.js'; -import {module, setupBeforeHookFnOnce} from '../src/hook.js'; +import {getHook, module, setupBeforeHookFnOnce} from '../src/hook.js'; import {store} from '../src/videoCache.js'; import {config} from '../src/config.js'; import {ADPOD} from '../src/mediaTypes.js'; @@ -424,7 +423,7 @@ config.getConfig('adpod', config => adpodSetConfig(config.adpod)); * This function initializes the adpod module's hooks. This is called by the corresponding adserver video module. */ function initAdpodHooks() { - setupBeforeHookFnOnce(callPrebidCache, callPrebidCacheHook); + setupBeforeHookFnOnce(getHook('callPrebidCache'), callPrebidCacheHook); setupBeforeHookFnOnce(checkAdUnitSetup, checkAdUnitSetupHook); setupBeforeHookFnOnce(checkVideoBidSetup, checkVideoBidSetupHook); } diff --git a/modules/adprimeBidAdapter.js b/modules/adprimeBidAdapter.js index 49c18c2d067..55ee1f0900c 100644 --- a/modules/adprimeBidAdapter.js +++ b/modules/adprimeBidAdapter.js @@ -3,6 +3,7 @@ import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { isFn, deepAccess, logMessage } from '../src/utils.js'; import { config } from '../src/config.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import {getAllOrtbKeywords} from '../libraries/keywords/keywords.js'; const BIDDER_CODE = 'adprime'; const AD_URL = 'https://delta.adprime.com/pbjs'; @@ -128,7 +129,7 @@ export const spec = { wPlayer: sizes ? sizes[0] : 0, hPlayer: sizes ? sizes[1] : 0, schain: bid.schain || {}, - keywords: bid.params.keywords || [], + keywords: getAllOrtbKeywords(bidderRequest.ortb2, bid.params.keywords), audiences: bid.params.audiences || [], identeties, bidFloor: getBidFloor(bid) diff --git a/modules/adqueryBidAdapter.js b/modules/adqueryBidAdapter.js index be5ca4b1057..8a953f0d97f 100644 --- a/modules/adqueryBidAdapter.js +++ b/modules/adqueryBidAdapter.js @@ -11,7 +11,7 @@ const ADQUERY_USER_SYNC_DOMAIN = ADQUERY_BIDDER_DOMAIN_PROTOCOL + '://' + ADQUER const ADQUERY_DEFAULT_CURRENCY = 'PLN'; const ADQUERY_NET_REVENUE = true; const ADQUERY_TTL = 360; -const storage = getStorageManager({gvlid: ADQUERY_GVLID, bidderCode: ADQUERY_BIDDER_CODE}); +const storage = getStorageManager({bidderCode: ADQUERY_BIDDER_CODE}); /** @type {BidderSpec} */ export const spec = { @@ -191,19 +191,26 @@ export const spec = { }; function buildRequest(validBidRequests, bidderRequest) { let bid = validBidRequests; + let pageUrl = ''; + if (bidderRequest && bidderRequest.refererInfo) { + pageUrl = bidderRequest.refererInfo.page || ''; + } + return { + v: '$prebid.version$', placementCode: bid.params.placementId, + // TODO: fix auctionId leak: https://github.com/prebid/Prebid.js/issues/9781 auctionId: bid.auctionId, type: bid.params.type, adUnitCode: bid.adUnitCode, bidQid: storage.getDataFromLocalStorage('qid') || null, bidId: bid.bidId, bidder: bid.bidder, + bidPageUrl: pageUrl, bidderRequestId: bid.bidderRequestId, bidRequestsCount: bid.bidRequestsCount, bidderRequestsCount: bid.bidderRequestsCount, sizes: parseSizesInput(bid.mediaTypes.banner.sizes).toString(), - }; } diff --git a/modules/adqueryIdSystem.js b/modules/adqueryIdSystem.js index d9552a470d0..5171802caba 100644 --- a/modules/adqueryIdSystem.js +++ b/modules/adqueryIdSystem.js @@ -9,11 +9,12 @@ import {ajax} from '../src/ajax.js'; import {getStorageManager} from '../src/storageManager.js'; import {submodule} from '../src/hook.js'; import { isFn, isStr, isPlainObject, logError } from '../src/utils.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; const MODULE_NAME = 'qid'; const AU_GVLID = 902; -export const storage = getStorageManager({gvlid: AU_GVLID, moduleName: 'qid'}); +export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: 'qid'}); /** * Param or default. diff --git a/modules/adrelevantisBidAdapter.js b/modules/adrelevantisBidAdapter.js index 40cfe18f025..cf785a1fc87 100644 --- a/modules/adrelevantisBidAdapter.js +++ b/modules/adrelevantisBidAdapter.js @@ -13,8 +13,7 @@ import { isStr, logError, logMessage, - logWarn, - transformBidderParamKeywords + logWarn } from '../src/utils.js'; import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; @@ -22,6 +21,7 @@ import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; import {find, includes} from '../src/polyfill.js'; import {INSTREAM, OUTSTREAM} from '../src/video.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import {getANKeywordParam, transformBidderParamKeywords} from '../libraries/appnexusKeywords/anKeywords.js'; const BIDDER_CODE = 'adrelevantis'; const URL = 'https://ssp.adrelevantis.com/prebid'; @@ -194,10 +194,6 @@ export const spec = { params.use_pmt_rule = (typeof params.usePaymentRule === 'boolean') ? params.usePaymentRule : false; if (params.usePaymentRule) { delete params.usePaymentRule; } - if (isPopulatedArray(params.keywords)) { - params.keywords.forEach(deleteValues); - } - Object.keys(params).forEach(paramKey => { let convertedKey = convertCamelToUnderscore(paramKey); if (convertedKey !== paramKey) { @@ -211,16 +207,6 @@ export const spec = { } }; -function isPopulatedArray(arr) { - return !!(isArray(arr) && arr.length > 0); -} - -function deleteValues(keyPairObj) { - if (isPopulatedArray(keyPairObj.value) && keyPairObj.value[0] === '') { - delete keyPairObj.value; - } -} - function formatRequest(payload, bidderRequest) { let request = []; @@ -475,14 +461,7 @@ function bidToTag(bid) { if (bid.params.externalImpId) { tag.external_imp_id = bid.params.externalImpId; } - if (!isEmpty(bid.params.keywords)) { - let keywords = transformBidderParamKeywords(bid.params.keywords); - - if (keywords.length > 0) { - keywords.forEach(deleteValues); - } - tag.keywords = keywords; - } + tag.keywords = getANKeywordParam(bid.ortb2, bid.params.keywords) if (bid.params.category) { tag.category = bid.params.category; } diff --git a/modules/adrinoBidAdapter.js b/modules/adrinoBidAdapter.js index 2a5588eeb8c..f5ae09934e3 100644 --- a/modules/adrinoBidAdapter.js +++ b/modules/adrinoBidAdapter.js @@ -1,6 +1,6 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {triggerPixel} from '../src/utils.js'; -import {NATIVE} from '../src/mediaTypes.js'; +import {NATIVE, BANNER} from '../src/mediaTypes.js'; import {config} from '../src/config.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; @@ -12,7 +12,7 @@ const GVLID = 1072; export const spec = { code: BIDDER_CODE, gvlid: GVLID, - supportedMediaTypes: [NATIVE], + supportedMediaTypes: [NATIVE, BANNER], getBidderConfig: function (property) { return config.getConfig(`${BIDDER_CODE}.${property}`); @@ -24,27 +24,33 @@ export const spec = { !!(bid.params.hash) && (typeof bid.params.hash === 'string') && !!(bid.mediaTypes) && - Object.keys(bid.mediaTypes).includes(NATIVE) && + (Object.keys(bid.mediaTypes).includes(NATIVE) || Object.keys(bid.mediaTypes).includes(BANNER)) && (bid.bidder === BIDDER_CODE); }, buildRequests: function (validBidRequests, bidderRequest) { // convert Native ORTB definition to old-style prebid native definition validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - const bidRequests = []; + let bids = []; for (let i = 0; i < validBidRequests.length; i++) { - let host = this.getBidderConfig('host') || BIDDER_HOST; - let requestData = { + adUnitCode: validBidRequests[i].adUnitCode, bidId: validBidRequests[i].bidId, - nativeParams: validBidRequests[i].nativeParams, placementHash: validBidRequests[i].params.hash, userId: validBidRequests[i].userId, referer: bidderRequest.refererInfo.page, userAgent: navigator.userAgent, } + if (validBidRequests[i].sizes != null && validBidRequests[i].sizes.length > 0) { + requestData.bannerParams = { sizes: validBidRequests[i].sizes }; + } + + if (validBidRequests[i].nativeParams != null) { + requestData.nativeParams = validBidRequests[i].nativeParams; + } + if (bidderRequest && bidderRequest.gdprConsent) { requestData.gdprConsent = { consentString: bidderRequest.gdprConsent.consentString, @@ -52,27 +58,37 @@ export const spec = { } } - bidRequests.push({ - method: REQUEST_METHOD, - url: host + '/bidder/bid/', - data: requestData, - options: { - contentType: 'application/json', - withCredentials: false, - } - }); + bids.push(requestData); } + let host = this.getBidderConfig('host') || BIDDER_HOST; + let bidRequests = []; + bidRequests.push({ + method: REQUEST_METHOD, + url: host + '/bidder/bids/', + data: bids, + options: { + contentType: 'application/json', + withCredentials: false, + } + }); + return bidRequests; }, interpretResponse: function (serverResponse, bidRequest) { const response = serverResponse.body; - const bidResponses = []; - if (!response.noAd) { - bidResponses.push(response); + const output = []; + + if (response.bidResponses) { + for (const bidResponse of response.bidResponses) { + if (!bidResponse.noAd) { + output.push(bidResponse); + } + } } - return bidResponses; + + return output; }, onBidWon: function (bid) { diff --git a/modules/adriverIdSystem.js b/modules/adriverIdSystem.js index fb8ce99ec16..c04ebf48028 100644 --- a/modules/adriverIdSystem.js +++ b/modules/adriverIdSystem.js @@ -8,11 +8,12 @@ import { logError, isPlainObject } from '../src/utils.js' import { ajax } from '../src/ajax.js'; import { submodule } from '../src/hook.js'; -import { getStorageManager } from '../src/storageManager.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; const MODULE_NAME = 'adriverId'; -export const storage = getStorageManager(); +export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); /** @type {Submodule} */ export const adriverIdSubmodule = { @@ -41,7 +42,7 @@ export const adriverIdSubmodule = { if (!isPlainObject(config.params)) { config.params = {}; } - const url = 'https://ad.adriver.ru/cgi-bin/json.cgi?sid=1&ad=719473&bt=55&pid=3198680&bid=7189165&bn=7189165&tuid=1'; + const url = 'https://ad.adriver.ru/cgi-bin/json.cgi?sid=1&ad=719473&bt=55&pid=3198680&bid=7189165&bn=7189165&tuid=1&cfa=1'; const resp = function (callback) { let creationDate = storage.getDataFromLocalStorage('adrcid_cd') || storage.getCookie('adrcid_cd'); let cookie = storage.getDataFromLocalStorage('adrcid') || storage.getCookie('adrcid'); diff --git a/modules/adsinteractiveBidAdapter.js b/modules/adsinteractiveBidAdapter.js new file mode 100644 index 00000000000..304b8bcade0 --- /dev/null +++ b/modules/adsinteractiveBidAdapter.js @@ -0,0 +1,144 @@ +import { + deepAccess, +} from '../src/utils.js'; + +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER } from '../src/mediaTypes.js'; + +const ADSINTERACTIVE_CODE = 'adsinteractive'; +const USER_SYNC_URL_IMAGE = 'https://pb.adsinteractive.com/img'; +const USER_SYNC_URL_IFRAME = 'https://pb.adsinteractive.com/sync'; + +export const spec = { + code: ADSINTERACTIVE_CODE, + supportedMediaTypes: [BANNER], + + isBidRequestValid: (bid) => { + return ( + !!bid.params.adUnit && !!bid.bidId && bid.bidder === 'adsinteractive' + ); + }, + + buildRequests: (bidRequests, bidderRequest) => { + return bidRequests.map((bid) => { + var gdprConsent; + if (bidderRequest && bidderRequest.gdprConsent) { + gdprConsent = { + consent_string: bidderRequest.gdprConsent.consentString, + consent_required: bidderRequest.gdprConsent.gdprApplies, + }; + + if ( + bidderRequest.gdprConsent.addtlConsent && + bidderRequest.gdprConsent.addtlConsent.indexOf('~') !== -1 + ) { + let ac = bidderRequest.gdprConsent.addtlConsent; + let acStr = ac.substring(ac.indexOf('~') + 1); + gdprConsent.addtl_consent = acStr + .split('.') + .map((id) => parseInt(id, 10)); + } + } + + let url = 'https://pb.adsinteractive.com/prebid'; + const data = { + id: bid.bidId, + at: 1, + source: { fd: 0 }, + gdprConsent: gdprConsent, + site: { + page: bid.ortb2.site.page, + keywords: bid.ortb2.site.keywords, + domain: bid.ortb2.site.domain, + publisher: { + domain: bid.ortb2.site.domain, + }, + ext: { + amp: Number(bidderRequest.refererInfo.isAmp), + }, + }, + regs: bid.ortb2.regs, + device: bid.ortb2.device, + user: bid.ortb2.user, + imp: [ + { + id: bid.params.adUnit, + banner: { + format: bid.sizes.map((size) => ({ + w: size[0], + h: size[1], + })), + }, + ext: { + bidder: { + adUnit: bid.params.adUnit, + }, + }, + }, + ], + tmax: bidderRequest.timeout, + }; + const options = { + withCredentials: true, + }; + return { + method: 'POST', + url, + data, + options, + }; + }); + }, + + interpretResponse: (serverResponse, bidRequest) => { + let answer = []; + if (serverResponse && serverResponse.body && serverResponse.body.seatbid) { + serverResponse.body.seatbid.forEach((seatbid) => { + if (seatbid.bid.length) { + answer = [ + ...answer, + ...seatbid.bid + .filter((bid) => bid.price > 0) + .map((adsinteractiveBid) => { + const bid = { + id: adsinteractiveBid.id, + requestId: bidRequest.data.id, + cpm: adsinteractiveBid.price, + netRevenue: true, + ttl: 1000, + ad: adsinteractiveBid.adm, + meta: {advertiserDomains: adsinteractiveBid && adsinteractiveBid.adomain ? adsinteractiveBid.adomain : []}, + width: adsinteractiveBid.w, + height: adsinteractiveBid.h, + currency: serverResponse.body.cur || 'USD', + creativeId: adsinteractiveBid.crid || 0, + }; + return bid; + }), + ]; + } + }); + } + return answer; + }, + getUserSyncs: (syncOptions, serverResponse, gdprConsent, uspConsent) => { + if (syncOptions.iframeEnabled) { + const auid = serverResponse.filter(resp => deepAccess(resp, 'body.ext.auid')) + .map(resp => resp.body.ext.auid); + return [ + { + type: 'iframe', + url: USER_SYNC_URL_IFRAME + '?consent=' + gdprConsent.consentString + '&auid=' + auid, + }, + ]; + } else { + return [ + { + type: 'image', + url: USER_SYNC_URL_IMAGE, + }, + ]; + } + }, +}; +registerBidder(spec); diff --git a/modules/adsinteractiveBidAdapter.md b/modules/adsinteractiveBidAdapter.md new file mode 100644 index 00000000000..81afcd18200 --- /dev/null +++ b/modules/adsinteractiveBidAdapter.md @@ -0,0 +1,31 @@ +# Overview + +Module Name: AdsInteractive Bidder Adapter + +Module Type: Bidder Adapter + +Maintainer: it@adsinteractive.com + +# Description + +You can use this adapter to get a bid from adsinteractive.com. + +About us : https://www.adsinteractive.com + + +# Test Parameters +```javascript + var adUnits = [ + { + sizes: [[300, 250]], + bids: [ + { + bidder: "adsinteractive", + params: { + adUnit: "example_adunit_1" + } + } + ] + } + ]; +``` diff --git a/modules/adtelligentBidAdapter.js b/modules/adtelligentBidAdapter.js index a7c83d9c190..cab2b8956bc 100644 --- a/modules/adtelligentBidAdapter.js +++ b/modules/adtelligentBidAdapter.js @@ -24,6 +24,8 @@ const HOST_GETTERS = { pgam: () => 'ghb.pgamssp.com', ocm: () => 'ghb.cenarius.orangeclickmedia.com', vidcrunchllc: () => 'ghb.platform.vidcrunch.com', + '9dotsmedia': () => 'ghb.platform.audiodots.com', + copper6: () => 'ghb.app.copper6.com' } const getUri = function (bidderCode) { let bidderWithoutSuffix = bidderCode.split('_')[0]; @@ -39,12 +41,19 @@ const syncsCache = {}; export const spec = { code: BIDDER_CODE, gvlid: 410, - aliases: ['onefiftytwomedia', 'appaloosa', 'bidsxchange', 'streamkey', 'janet', + aliases: [ + 'onefiftytwomedia', + 'appaloosa', + 'bidsxchange', + 'streamkey', + 'janet', { code: 'selectmedia', gvlid: 775 }, { code: 'navelix', gvlid: 380 }, 'pgam', { code: 'ocm', gvlid: 1148 }, { code: 'vidcrunchllc', gvlid: 1145 }, + '9dotsmedia', + 'copper6', ], supportedMediaTypes: [VIDEO, BANNER], isBidRequestValid: function (bid) { @@ -191,6 +200,14 @@ function bidToTag(bidRequests, adapterRequest) { tag.DMPId = window.adtDmp.getUID(); } + if (adapterRequest.gppConsent) { + tag.GPP = adapterRequest.gppConsent.gppString; + tag.GPPSid = adapterRequest.gppConsent.applicableSections?.toString(); + } else if (adapterRequest.ortb2?.regs?.gpp) { + tag.GPP = adapterRequest.ortb2.regs.gpp; + tag.GPPSid = adapterRequest.ortb2.regs.gpp_sid; + } + // end publisher env const bids = []; diff --git a/modules/adtrueBidAdapter.js b/modules/adtrueBidAdapter.js index 2ec5cd59e1d..389986eb586 100644 --- a/modules/adtrueBidAdapter.js +++ b/modules/adtrueBidAdapter.js @@ -469,7 +469,7 @@ export const spec = { conf.zoneId = conf.zoneId || bid.params.zoneId; conf.pubId = conf.pubId || bid.params.publisherId; - conf.transactionId = bid.transactionId; + conf.transactionId = bid.ortb2Imp?.ext?.tid; if (bidCurrency === '') { bidCurrency = bid.params.currency || UNDEFINED; } else if (bid.params.hasOwnProperty('currency') && bidCurrency !== bid.params.currency) { @@ -492,7 +492,7 @@ export const spec = { payload.ext.wrapper = {}; payload.ext.wrapper.transactionId = conf.transactionId; - payload.ext.wrapper.wiid = conf.wiid || bidderRequest.auctionId; + payload.ext.wrapper.wiid = conf.wiid || bidderRequest.ortb2?.ext?.tid; payload.ext.wrapper.wp = 'pbjs'; payload.user.geo = {}; diff --git a/modules/aduptechBidAdapter.js b/modules/aduptechBidAdapter.js index c39d9e14f17..1ea5f1a0096 100644 --- a/modules/aduptechBidAdapter.js +++ b/modules/aduptechBidAdapter.js @@ -1,9 +1,10 @@ -import {getAdUnitSizes, isArray, isBoolean, isEmpty, isFn, isPlainObject} from '../src/utils.js'; +import {deepClone, getAdUnitSizes, isArray, isBoolean, isEmpty, isFn, isPlainObject} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, NATIVE} from '../src/mediaTypes.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; export const BIDDER_CODE = 'aduptech'; +export const GVLID = 647; export const ENDPOINT_URL_PUBLISHER_PLACEHOLDER = '{PUBLISHER}'; export const ENDPOINT_URL = 'https://rtb.d.adup-tech.com/prebid/' + ENDPOINT_URL_PUBLISHER_PLACEHOLDER + '_bid'; export const ENDPOINT_METHOD = 'POST'; @@ -20,14 +21,14 @@ export const internal = { * @returns {null|Object.} */ extractGdpr: (bidderRequest) => { - if (bidderRequest && bidderRequest.gdprConsent) { - return { - consentString: bidderRequest.gdprConsent.consentString, - consentRequired: (isBoolean(bidderRequest.gdprConsent.gdprApplies)) ? bidderRequest.gdprConsent.gdprApplies : true - }; + if (!bidderRequest?.gdprConsent) { + return null; } - return null; + return { + consentString: bidderRequest.gdprConsent.consentString, + consentRequired: (isBoolean(bidderRequest.gdprConsent.gdprApplies)) ? bidderRequest.gdprConsent.gdprApplies : true + }; }, /** @@ -59,30 +60,34 @@ export const internal = { * @returns {null|Object.} */ extractBannerConfig: (bidRequest) => { - const sizes = getAdUnitSizes(bidRequest); - if (isArray(sizes) && !isEmpty(sizes)) { - const banner = { sizes: sizes }; + const adUnitSizes = getAdUnitSizes(bidRequest); + if (!isArray(adUnitSizes) || isEmpty(adUnitSizes)) { + return null; + } - // try to add floor for each banner size - banner.sizes.forEach(size => { - const floor = internal.getFloor(bidRequest, { mediaType: BANNER, size }); - if (floor) { - size.push(floor.floor); - size.push(floor.currency); - } - }); + const banner = { sizes: [] }; - // try to add default floor for banner - const floor = internal.getFloor(bidRequest, { mediaType: BANNER, size: '*' }); + adUnitSizes.forEach(adUnitSize => { + const size = deepClone(adUnitSize); + + // try to add floor for each banner size + const floor = internal.getFloor(bidRequest, { mediaType: BANNER, size: adUnitSize }); if (floor) { - banner.floorPrice = floor.floor; - banner.floorCurrency = floor.currency; + size.push(floor.floor); + size.push(floor.currency); } - return banner; + banner.sizes.push(size); + }); + + // try to add default floor for banner + const floor = internal.getFloor(bidRequest, { mediaType: BANNER, size: '*' }); + if (floor) { + banner.floorPrice = floor.floor; + banner.floorCurrency = floor.currency; } - return null; + return banner; }, /** @@ -92,20 +97,20 @@ export const internal = { * @returns {null|Object.} */ extractNativeConfig: (bidRequest) => { - if (bidRequest?.mediaTypes?.native) { - const native = bidRequest.mediaTypes.native; + if (!bidRequest?.mediaTypes?.native) { + return null; + } - // try to add default floor for native - const floor = internal.getFloor(bidRequest, { mediaType: NATIVE, size: '*' }); - if (floor) { - native.floorPrice = floor.floor; - native.floorCurrency = floor.currency; - } + const native = deepClone(bidRequest.mediaTypes.native); - return native; + // try to add default floor for native + const floor = internal.getFloor(bidRequest, { mediaType: NATIVE, size: '*' }); + if (floor) { + native.floorPrice = floor.floor; + native.floorCurrency = floor.currency; } - return null; + return native; }, /** @@ -115,11 +120,11 @@ export const internal = { * @returns {null|Object.} */ extractParams: (bidRequest) => { - if (bidRequest && bidRequest.params) { - return bidRequest.params + if (!bidRequest?.params) { + return null; } - return null; + return deepClone(bidRequest.params); }, /** @@ -190,6 +195,7 @@ export const internal = { export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, NATIVE], + gvlid: GVLID, /** * Validate given bid request @@ -249,6 +255,7 @@ export const spec = { url: internal.buildEndpointUrl(publisher), method: ENDPOINT_METHOD, data: { + // TODO: fix auctionId leak: https://github.com/prebid/Prebid.js/issues/9781 auctionId: auctionId, pageUrl: pageUrl, referrer: referrer, @@ -265,7 +272,7 @@ export const spec = { groupedBidRequests[publisher].forEach(bidRequest => { const bid = { bidId: bidRequest.bidId, - transactionId: bidRequest.transactionId, + transactionId: bidRequest.ortb2Imp?.ext?.tid, adUnitCode: bidRequest.adUnitCode, params: internal.extractParams(bidRequest) }; diff --git a/modules/advangelistsBidAdapter.js b/modules/advangelistsBidAdapter.js index 4963150caed..8e5be83f166 100755 --- a/modules/advangelistsBidAdapter.js +++ b/modules/advangelistsBidAdapter.js @@ -58,7 +58,6 @@ export const spec = { if (isVideoBid(bidRequest)) { let bidResponse = { requestId: response.id, - bidderCode: BIDDER_CODE, cpm: response.seatbid[0].bid[0].price, width: response.seatbid[0].bid[0].w, height: response.seatbid[0].bid[0].h, diff --git a/modules/adxcgAnalyticsAdapter.js b/modules/adxcgAnalyticsAdapter.js index f3bb1270334..21b6c1be783 100644 --- a/modules/adxcgAnalyticsAdapter.js +++ b/modules/adxcgAnalyticsAdapter.js @@ -3,6 +3,7 @@ import { ajax } from '../src/ajax.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import CONSTANTS from '../src/constants.json'; +import {getGlobal} from '../src/prebidGlobal.js'; /** * Analytics adapter from adxcg.com @@ -122,7 +123,7 @@ function send (data) { ats: adxcgAnalyticsAdapter.context.auctionTimestamp, aav: adxcgAnalyticsVersion, iob: intersectionObserverAvailable(window) ? '1' : '0', - pbv: $$PREBID_GLOBAL$$.version, + pbv: getGlobal().version, sz: window.screen.width + 'x' + window.screen.height } }); diff --git a/modules/adxcgBidAdapter.js b/modules/adxcgBidAdapter.js index 564960cf2c9..5930f3adb67 100644 --- a/modules/adxcgBidAdapter.js +++ b/modules/adxcgBidAdapter.js @@ -98,7 +98,7 @@ export const spec = { device.dnt = getDNT() ? 1 : 0; device.language = (navigator && navigator.language) ? navigator.language.split('-')[0] : ''; - const tid = bidderRequest.auctionId; + const tid = bidderRequest.ortb2?.source?.tid; const test = setOnAny(validBidRequests, 'params.test'); const currency = getConfig('currency.adServerCurrency'); const cur = currency && [ currency ]; diff --git a/modules/adyoulikeBidAdapter.js b/modules/adyoulikeBidAdapter.js index e6cdc7698bf..4080d9f25cd 100644 --- a/modules/adyoulikeBidAdapter.js +++ b/modules/adyoulikeBidAdapter.js @@ -1,6 +1,5 @@ import {buildUrl, deepAccess, parseSizesInput} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {createEidsArray} from './userId/eids.js'; import {find} from '../src/polyfill.js'; import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; @@ -72,7 +71,7 @@ export const spec = { let size = getSize(sizesArray); accumulator[bidReq.bidId] = {}; accumulator[bidReq.bidId].PlacementID = bidReq.params.placement; - accumulator[bidReq.bidId].TransactionID = bidReq.transactionId; + accumulator[bidReq.bidId].TransactionID = bidReq.ortb2Imp?.ext?.tid; accumulator[bidReq.bidId].Width = size.width; accumulator[bidReq.bidId].Height = size.height; accumulator[bidReq.bidId].AvailableSizes = sizesArray.join(','); @@ -122,8 +121,8 @@ export const spec = { payload.ortb2 = bidderRequest.ortb2; } - if (deepAccess(bidderRequest, 'userId')) { - payload.userId = createEidsArray(bidderRequest.userId); + if (deepAccess(bidderRequest, 'userIdAsEids')) { + payload.userId = bidderRequest.userIdAsEids; } payload.pbjs_version = '$prebid.version$'; diff --git a/modules/afpBidAdapter.js b/modules/afpBidAdapter.js index f690b70973d..cec61b29b82 100644 --- a/modules/afpBidAdapter.js +++ b/modules/afpBidAdapter.js @@ -100,13 +100,13 @@ export const spec = { pageUrl: IS_DEV ? TEST_PAGE_URL : refererInfo.page, gdprConsent: gdprConsent, bidRequests: validBidRequests.map(validBidRequest => { - const {bidId, transactionId, sizes, params: { + const {bidId, ortb2Imp, sizes, params: { placeId, placeType, imageUrl, imageWidth, imageHeight }} = validBidRequest bidRequestMap[bidId] = validBidRequest const bidRequest = { bidId, - transactionId, + transactionId: ortb2Imp?.ext?.tid, sizes, placeId, } diff --git a/modules/aidemBidAdapter.js b/modules/aidemBidAdapter.js index e4d5c618b77..7469f26156b 100644 --- a/modules/aidemBidAdapter.js +++ b/modules/aidemBidAdapter.js @@ -1,4 +1,15 @@ -import {_each, contains, deepAccess, deepSetValue, getDNT, isBoolean, isStr, isNumber, logError, logInfo} from '../src/utils.js'; +import { + _each, + contains, + deepAccess, + deepSetValue, + getDNT, + isBoolean, + isNumber, + isStr, + logError, + logInfo +} from '../src/utils.js'; import {config} from '../src/config.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; @@ -152,6 +163,8 @@ function getPageUrl(bidderRequest) { function buildWinNotice(bid) { const params = bid.params[0]; + const app = deepAccess(bid, 'meta.ext.app') + const winNoticeExt = deepAccess(bid, 'meta.ext.win_notice_ext') return { publisherId: params.publisherId, siteId: params.siteId, @@ -162,11 +175,16 @@ function buildWinNotice(bid) { impid: deepAccess(bid, 'meta.impid'), dsp_id: deepAccess(bid, 'meta.dsp_id'), adUnitCode: bid.adUnitCode, + // TODO: fix auctionId/transactionId leak: https://github.com/prebid/Prebid.js/issues/9781 auctionId: bid.auctionId, transactionId: bid.transactionId, ttl: bid.ttl, requestTimestamp: bid.requestTimestamp, responseTimestamp: bid.responseTimestamp, + mediatype: bid.mediaType, + environment: app ? 'app' : 'web', + ...app, + ext: winNoticeExt, }; } @@ -195,7 +213,7 @@ function getMediaType(bidRequest) { function getPrebidRequestFields(bidderRequest, bidRequests) { const payload = {}; // Base Payload Data - deepSetValue(payload, 'id', bidderRequest.auctionId); + deepSetValue(payload, 'id', bidderRequest.bidderRequestId); // Impressions setPrebidImpressionObject(bidRequests, payload); // Device @@ -225,7 +243,8 @@ function setPrebidImpressionObject(bidRequests, payload) { // Placement or ad tag used to initiate the auction deepSetValue(impressionObject, 'id', bidRequest.bidId); // Transaction id - deepSetValue(impressionObject, 'tid', deepAccess(bidRequest, 'transactionId')); + // TODO: `imp.tid` is not ORTB, is this intentional? + deepSetValue(impressionObject, 'tid', deepAccess(bidRequest, 'ortb2Imp.ext.tid')); // placement id deepSetValue(impressionObject, 'tagid', deepAccess(bidRequest, 'params.placementId', null)); // Publisher id @@ -348,6 +367,7 @@ function getPrebidResponseBidObject(openRTBResponseBidObject) { function setPrebidResponseBidObjectMeta(prebidResponseBidObject, openRTBResponseBidObject) { logInfo('AIDEM Bid Adapter meta', openRTBResponseBidObject); deepSetValue(prebidResponseBidObject, 'meta.advertiserDomains', deepAccess(openRTBResponseBidObject, 'meta.advertiserDomains')); + deepSetValue(prebidResponseBidObject, 'meta.ext', deepAccess(openRTBResponseBidObject, 'meta.ext')); if (openRTBResponseBidObject.cat && Array.isArray(openRTBResponseBidObject.cat)) { const primaryCatId = openRTBResponseBidObject.cat.shift(); deepSetValue(prebidResponseBidObject, 'meta.primaryCatId', primaryCatId); diff --git a/modules/airgridRtdProvider.js b/modules/airgridRtdProvider.js index 3578cc4b87e..7c6cf1f5de0 100644 --- a/modules/airgridRtdProvider.js +++ b/modules/airgridRtdProvider.js @@ -5,15 +5,11 @@ * @module modules/airgridRtdProvider * @requires module:modules/realTimeData */ -import { config } from '../src/config.js'; -import { submodule } from '../src/hook.js'; -import { - mergeDeep, - deepSetValue, - deepAccess, -} from '../src/utils.js'; -import { getGlobal } from '../src/prebidGlobal.js'; -import { getStorageManager } from '../src/storageManager.js'; +import {submodule} from '../src/hook.js'; +import {deepAccess, deepSetValue, mergeDeep} from '../src/utils.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {loadExternalScript} from '../src/adloader.js'; +import {MODULE_TYPE_RTD} from '../src/activities/modules.js'; const MODULE_NAME = 'realTimeData'; const SUBMODULE_NAME = 'airgrid'; @@ -21,10 +17,15 @@ const AG_TCF_ID = 782; export const AG_AUDIENCE_IDS_KEY = 'edkt_matched_audience_ids'; export const storage = getStorageManager({ - gvlid: AG_TCF_ID, + moduleType: MODULE_TYPE_RTD, moduleName: SUBMODULE_NAME, }); +function getModuleUrl(accountId) { + const path = accountId ?? 'sdk'; + return `https://cdn.edkt.io/${path}/edgekit.min.js`; +} + /** * Attach script tag to DOM * @param {Object} rtdConfig @@ -33,19 +34,12 @@ export const storage = getStorageManager({ export function attachScriptTagToDOM(rtdConfig) { var edktInitializor = (window.edktInitializor = window.edktInitializor || {}); if (!edktInitializor.invoked) { - edktInitializor.invoked = true; edktInitializor.accountId = rtdConfig.params.accountId; edktInitializor.publisherId = rtdConfig.params.publisherId; edktInitializor.apiKey = rtdConfig.params.apiKey; - edktInitializor.load = function (e) { - var p = e || 'sdk'; - var n = document.createElement('script'); - n.type = 'module'; - n.async = true; - n.src = 'https://cdn.edkt.io/' + p + '/edgekit.min.js'; - document.getElementsByTagName('head')[0].appendChild(n); - }; - edktInitializor.load(edktInitializor.accountId); + edktInitializor.invoked = true; + const moduleSrc = getModuleUrl(rtdConfig.params.accountId); + loadExternalScript(moduleSrc, SUBMODULE_NAME); } } @@ -63,43 +57,35 @@ export function getMatchedAudiencesFromStorage() { } } -/** - * Mutates the adUnits object - * @param {Object} adUnits - * @param {Array} audiences - * @return {void} - */ -function setAudiencesToAppNexusAdUnits(adUnits, audiences) { - adUnits.forEach((adUnit) => { - adUnit.bids.forEach((bid) => { - if (bid.bidder && bid.bidder === 'appnexus') { - deepSetValue(bid, 'params.keywords.perid', audiences || []); - } - }); - }); -} - /** * Pass audience data to configured bidders, using ORTB2 + * @param {Object} bidConfig * @param {Object} rtdConfig * @param {Array} audiences - * @return {{}} a map from bidder code to ORTB2 config + * @return {void} */ -export function getAudiencesAsBidderOrtb2(rtdConfig, audiences) { +export function setAudiencesAsBidderOrtb2(bidConfig, rtdConfig, audiences) { const bidders = deepAccess(rtdConfig, 'params.bidders'); - if (!bidders || bidders.length === 0) return {}; - const agOrtb2 = {} - deepSetValue(agOrtb2, 'ortb2.user.ext.data.airgrid', audiences || []); + if (!bidders || bidders.length === 0 || !audiences || audiences.length === 0) return; - return Object.fromEntries(bidders.map(bidder => [bidder, agOrtb2])); -} + const agOrtb2 = {}; -export function setAudiencesUsingAppNexusAuctionKeywords(audiences) { - config.setConfig({ - appnexusAuctionKeywords: { - perid: audiences, - }, - }); + const agUserData = [ + { + id: String(AG_TCF_ID), + ext: { + segtax: 540, + }, + name: 'airgrid', + segment: audiences.map((id) => ({id})) + } + ] + deepSetValue(agOrtb2, 'user.data', agUserData); + + const bidderConfig = Object.fromEntries( + bidders.map((bidder) => [bidder, agOrtb2]) + ) + mergeDeep(bidConfig?.ortb2Fragments?.bidder, bidderConfig) } /** @@ -127,14 +113,9 @@ export function passAudiencesToBidders( rtdConfig, userConsent ) { - const adUnits = bidConfig.adUnits || getGlobal().adUnits; const audiences = getMatchedAudiencesFromStorage(); if (audiences.length > 0) { - setAudiencesUsingAppNexusAuctionKeywords(audiences); - mergeDeep(bidConfig?.ortb2Fragments?.bidder, getAudiencesAsBidderOrtb2(rtdConfig, audiences)); - if (adUnits) { - setAudiencesToAppNexusAdUnits(adUnits, audiences); - } + setAudiencesAsBidderOrtb2(bidConfig, rtdConfig, audiences) } onDone(); } @@ -144,6 +125,7 @@ export const airgridSubmodule = { name: SUBMODULE_NAME, init: init, getBidRequestData: passAudiencesToBidders, + gvlid: AG_TCF_ID }; submodule(MODULE_NAME, airgridSubmodule); diff --git a/modules/ajaBidAdapter.js b/modules/ajaBidAdapter.js index 67b448eb484..ffab41611ef 100644 --- a/modules/ajaBidAdapter.js +++ b/modules/ajaBidAdapter.js @@ -2,19 +2,28 @@ import { getBidIdParameter, tryAppendQueryString, createTrackPixelHtml, logError import { Renderer } from '../src/Renderer.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { VIDEO, BANNER, NATIVE } from '../src/mediaTypes.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; -const BIDDER_CODE = 'aja'; +const BidderCode = 'aja'; const URL = 'https://ad.as.amanad.adtdp.com/v2/prebid'; -const SDK_TYPE = 5; -const AD_TYPE = { - BANNER: 1, - NATIVE: 2, - VIDEO: 3, +const SDKType = 5; +const AdType = { + Banner: 1, + Native: 2, + Video: 3, }; +const BannerSizeMap = { + '970x250': 1, + '300x250': 2, + '320x50': 3, + '728x90': 4, + '320x100': 6, + '336x280': 31, + '300x600': 32, +} + export const spec = { - code: BIDDER_CODE, + code: BidderCode, supportedMediaTypes: [VIDEO, BANNER, NATIVE], /** @@ -36,9 +45,6 @@ export const spec = { * @returns {ServerRequest|ServerRequest[]} */ buildRequests: function(validBidRequests, bidderRequest) { - // convert Native ORTB definition to old-style prebid native definition - validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - const bidRequests = []; const pageUrl = bidderRequest?.refererInfo?.page || undefined; @@ -48,8 +54,8 @@ export const spec = { const asi = getBidIdParameter('asi', bidRequest.params); queryString = tryAppendQueryString(queryString, 'asi', asi); - queryString = tryAppendQueryString(queryString, 'skt', SDK_TYPE); - queryString = tryAppendQueryString(queryString, 'tid', bidRequest.transactionId) + queryString = tryAppendQueryString(queryString, 'skt', SDKType); + queryString = tryAppendQueryString(queryString, 'tid', bidRequest.ortb2Imp?.ext?.tid) queryString = tryAppendQueryString(queryString, 'prebid_id', bidRequest.bidId); queryString = tryAppendQueryString(queryString, 'prebid_ver', '$prebid.version$'); @@ -57,11 +63,27 @@ export const spec = { queryString = tryAppendQueryString(queryString, 'page_url', pageUrl); } + const banner = deepAccess(bidRequest, `mediaTypes.${BANNER}`) + if (banner) { + const adFormatIDs = []; + for (const size of banner.sizes || []) { + if (size.length !== 2) { + continue + } + + const adFormatID = BannerSizeMap[`${size[0]}x${size[1]}`]; + if (adFormatID) { + adFormatIDs.push(adFormatID); + } + } + queryString = tryAppendQueryString(queryString, 'ad_format_ids', adFormatIDs.join(',')); + } + const eids = bidRequest.userIdAsEids; if (eids && eids.length) { queryString = tryAppendQueryString(queryString, 'eids', JSON.stringify({ 'eids': eids, - })) + })); } const sua = deepAccess(bidRequest, 'ortb2.device.sua'); @@ -101,7 +123,7 @@ export const spec = { }, } - if (AD_TYPE.VIDEO === ad.ad_type) { + if (AdType.Video === ad.ad_type) { const videoAd = bidderResponseBody.ad.video; Object.assign(bid, { vastXml: videoAd.vtag, @@ -113,7 +135,7 @@ export const spec = { }); Array.prototype.push.apply(bid.meta.advertiserDomains, videoAd.adomain) - } else if (AD_TYPE.BANNER === ad.ad_type) { + } else if (AdType.Banner === ad.ad_type) { const bannerAd = bidderResponseBody.ad.banner; Object.assign(bid, { width: bannerAd.w, @@ -131,7 +153,7 @@ export const spec = { } Array.prototype.push.apply(bid.meta.advertiserDomains, bannerAd.adomain) - } else if (AD_TYPE.NATIVE === ad.ad_type) { + } else if (AdType.Native === ad.ad_type) { const nativeAds = ad.native.template_and_ads.ads; if (nativeAds.length === 0) { return []; diff --git a/modules/akamaiDapRtdProvider.js b/modules/akamaiDapRtdProvider.js index 1c2af70d737..f0bb7eb3a6c 100644 --- a/modules/akamaiDapRtdProvider.js +++ b/modules/akamaiDapRtdProvider.js @@ -10,6 +10,7 @@ import {getStorageManager} from '../src/storageManager.js'; import {submodule} from '../src/hook.js'; import {isPlainObject, mergeDeep, logMessage, logInfo, logError} from '../src/utils.js'; import { loadExternalScript } from '../src/adloader.js'; +import {MODULE_TYPE_RTD} from '../src/activities/modules.js'; const MODULE_NAME = 'realTimeData'; const SUBMODULE_NAME = 'dap'; @@ -23,7 +24,7 @@ export const DAP_DEFAULT_TOKEN_TTL = 3600; // in seconds export const DAP_MAX_RETRY_TOKENIZE = 1; export const DAP_CLIENT_ENTROPY = 'dap_client_entropy' -export const storage = getStorageManager({gvlid: null, moduleName: SUBMODULE_NAME}); +export const storage = getStorageManager({moduleType: MODULE_TYPE_RTD, moduleName: SUBMODULE_NAME}); let dapRetryTokenize = 0; /** diff --git a/modules/alkimiBidAdapter.js b/modules/alkimiBidAdapter.js index 69b3fdae3d8..c087b3061a0 100644 --- a/modules/alkimiBidAdapter.js +++ b/modules/alkimiBidAdapter.js @@ -1,5 +1,5 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {deepClone, deepAccess} from '../src/utils.js'; +import {deepAccess, deepClone} from '../src/utils.js'; import {ajax} from '../src/ajax.js'; import {VIDEO} from '../src/mediaTypes.js'; import {config} from '../src/config.js'; @@ -41,6 +41,7 @@ export const spec = { const alkimiConfig = config.getConfig('alkimi'); let payload = { + // TODO: fix auctionId leak: https://github.com/prebid/Prebid.js/issues/9781 requestId: bidderRequest.auctionId, signRequest: {bids, randomUUID: alkimiConfig && alkimiConfig.randomUUID}, bidIds, diff --git a/modules/allowActivities.js b/modules/allowActivities.js new file mode 100644 index 00000000000..6af7eb36a62 --- /dev/null +++ b/modules/allowActivities.js @@ -0,0 +1,74 @@ +import {config} from '../src/config.js'; +import {registerActivityControl} from '../src/activities/rules.js'; + +const CFG_NAME = 'allowActivities'; +const RULE_NAME = `${CFG_NAME} config`; +const DEFAULT_PRIORITY = 1; + +export function updateRulesFromConfig(registerRule) { + const activeRuleHandles = new Map(); + const defaultRuleHandles = new Map(); + const rulesByActivity = new Map(); + + function clearAllRules() { + rulesByActivity.clear(); + Array.from(activeRuleHandles.values()) + .flatMap(ruleset => Array.from(ruleset.values())) + .forEach(fn => fn()); + activeRuleHandles.clear(); + Array.from(defaultRuleHandles.values()).forEach(fn => fn()); + defaultRuleHandles.clear(); + } + + function cleanParams(params) { + // remove private parameters for publisher condition checks + return Object.fromEntries(Object.entries(params).filter(([k]) => !k.startsWith('_'))) + } + + function setupRule(activity, priority) { + if (!activeRuleHandles.has(activity)) { + activeRuleHandles.set(activity, new Map()) + } + const handles = activeRuleHandles.get(activity); + if (!handles.has(priority)) { + handles.set(priority, registerRule(activity, RULE_NAME, function (params) { + for (const rule of rulesByActivity.get(activity).get(priority)) { + if (!rule.condition || rule.condition(cleanParams(params))) { + return {allow: rule.allow, reason: rule} + } + } + }, priority)); + } + } + + function setupDefaultRule(activity) { + if (!defaultRuleHandles.has(activity)) { + defaultRuleHandles.set(activity, registerRule(activity, RULE_NAME, function () { + return {allow: false, reason: 'activity denied by default'} + }, Number.POSITIVE_INFINITY)) + } + } + + config.getConfig(CFG_NAME, (cfg) => { + clearAllRules(); + Object.entries(cfg[CFG_NAME]).forEach(([activity, activityCfg]) => { + if (activityCfg.default === false) { + setupDefaultRule(activity); + } + const rules = new Map(); + rulesByActivity.set(activity, rules); + + (activityCfg.rules || []).forEach(rule => { + const priority = rule.priority == null ? DEFAULT_PRIORITY : rule.priority; + if (!rules.has(priority)) { + rules.set(priority, []) + } + rules.get(priority).push(rule); + }); + + Array.from(rules.keys()).forEach(priority => setupRule(activity, priority)); + }); + }) +} + +updateRulesFromConfig(registerActivityControl); diff --git a/modules/amxBidAdapter.js b/modules/amxBidAdapter.js index c7bc99b6aa6..a773ac70559 100644 --- a/modules/amxBidAdapter.js +++ b/modules/amxBidAdapter.js @@ -1,23 +1,25 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { - parseUrl, - deepAccess, _each, + deepAccess, formatQS, getUniqueIdentifierStr, - triggerPixel, + isArray, isFn, logError, + parseUrl, + triggerPixel, + generateUUID, } from '../src/utils.js'; import { config } from '../src/config.js'; import { getStorageManager } from '../src/storageManager.js'; const BIDDER_CODE = 'amx'; -const storage = getStorageManager({ gvlid: 737, bidderCode: BIDDER_CODE }); +const storage = getStorageManager({ bidderCode: BIDDER_CODE }); const SIMPLE_TLD_TEST = /\.com?\.\w{2,4}$/; const DEFAULT_ENDPOINT = 'https://prebid.a-mo.net/a/c'; -const VERSION = 'pba1.3.2'; +const VERSION = 'pba1.3.3'; const VAST_RXP = /^\s*<\??(?:vast|xml)/i; const TRACKING_ENDPOINT = 'https://1x1.a-mo.net/hbx/'; const AMUID_KEY = '__amuidpb'; @@ -192,6 +194,53 @@ function resolveSize(bid, request, bidId) { return [bidRequest.aw, bidRequest.ah]; } +function isSyncEnabled(syncConfigP, syncType) { + if (syncConfigP == null) return false; + + const syncConfig = syncConfigP[syncType]; + if (syncConfig == null) { + return false; + } + + if (syncConfig.bidders === '*' || (isArray(syncConfig.bidders) && syncConfig.bidders.indexOf('amx') !== -1)) { + return syncConfig.filter == null || syncConfig.filter === 'include'; + } + + return false; +} + +const SYNC_IMAGE = 1; +const SYNC_IFRAME = 2; + +function getSyncSettings() { + const syncConfig = config.getConfig('userSync'); + if (syncConfig == null) { + return { + d: 0, + l: 0, + t: 0, + e: true + }; + } + + const settings = { d: syncConfig.syncDelay, l: syncConfig.syncsPerBidder, t: 0, e: syncConfig.syncEnabled } + const all = isSyncEnabled(syncConfig.filterSettings, 'all') + + if (all) { + settings.t = SYNC_IMAGE & SYNC_IFRAME; + return settings; + } + + if (isSyncEnabled(syncConfig.filterSettings, 'iframe')) { + settings.t |= SYNC_IFRAME; + } + if (isSyncEnabled(syncConfig.filterSettings, 'image')) { + settings.t |= SYNC_IMAGE; + } + + return settings; +} + function values(source) { if (Object.values != null) { return Object.values(source); @@ -202,6 +251,30 @@ function values(source) { }); } +function getGpp(bidderRequest) { + if (bidderRequest?.gppConsent != null) { + return bidderRequest.gppConsent; + } + + return bidderRequest?.ortb2?.regs?.gpp ?? { gppString: '', applicableSections: '' }; +} + +function buildReferrerInfo(bidderRequest) { + if (bidderRequest.refererInfo == null) { + return { r: '', t: false, c: '', l: 0, s: [] } + } + + const re = bidderRequest.refererInfo; + + return { + r: re.topmostLocation, + t: re.reachedTop, + l: re.numIframes, + s: re.stack, + c: re.canonicalUrl, + } +} + const isTrue = (boolValue) => boolValue === true || boolValue === 1 || boolValue === 'true'; @@ -231,7 +304,7 @@ export const spec = { }; const payload = { - a: bidderRequest.auctionId, + a: generateUUID(), B: 0, b: loc.host, brc: fbid.bidderRequestsCount || 0, @@ -249,6 +322,7 @@ export const spec = { w: screen.width, gs: deepAccess(bidderRequest, 'gdprConsent.gdprApplies', ''), gc: deepAccess(bidderRequest, 'gdprConsent.consentString', ''), + gpp: getGpp(bidderRequest), u: refInfo(bidderRequest, 'page', loc.href), do: refInfo(bidderRequest, 'site', loc.hostname), re: refInfo(bidderRequest, 'ref'), @@ -259,8 +333,10 @@ export const spec = { m: createBidMap(bidRequests), cpp: config.getConfig('coppa') ? 1 : 0, fpd2: bidderRequest.ortb2, - tmax: config.getConfig('bidderTimeout'), + tmax: bidderRequest.timeout, amp: refInfo(bidderRequest, 'isAmp', null), + ri: buildReferrerInfo(bidderRequest), + sync: getSyncSettings(), eids: values( bidRequests.reduce((all, bid) => { // we only want unique ones in here @@ -287,17 +363,38 @@ export const spec = { }; }, - getUserSyncs(syncOptions, serverResponses) { + getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent, gppConsent) { + const qp = { + gdpr_consent: enc(gdprConsent?.consentString || ''), + gdpr: enc(gdprConsent?.gdprApplies ? 1 : 0), + us_privacy: enc(uspConsent || ''), + gpp: enc(gppConsent?.gppString || ''), + gpp_sid: enc(gppConsent?.applicableSections || '') + }; + + const iframeSync = { + url: `https://prebid.a-mo.net/isyn?${formatQS(qp)}`, + type: 'iframe' + }; + if (serverResponses == null || serverResponses.length === 0) { + if (syncOptions.iframeEnabled) { + return [iframeSync] + } + return []; } + const output = []; - _each(serverResponses, function ({ body: response }) { + let hasFrame = false; + + _each(serverResponses, function({ body: response }) { if (response != null && response.p != null && response.p.hreq) { - _each(response.p.hreq, function (syncPixel) { + _each(response.p.hreq, function(syncPixel) { const pixelType = syncPixel.indexOf('__st=iframe') !== -1 ? 'iframe' : 'image'; if (syncOptions.iframeEnabled || pixelType === 'image') { + hasFrame = hasFrame || (pixelType === 'iframe') || (syncPixel.indexOf('cchain') !== -1) output.push({ url: syncPixel, type: pixelType, @@ -306,6 +403,11 @@ export const spec = { }); } }); + + if (!hasFrame && output.length < 2) { + output.push(iframeSync) + } + return output; }, @@ -381,7 +483,6 @@ export const spec = { bid: timeoutData.bidId, a: timeoutData.adUnitCode, cn: timeoutData.timeout, - aud: timeoutData.auctionId, }); }, diff --git a/modules/amxIdSystem.js b/modules/amxIdSystem.js index 9dbab496f2c..5deffa00dfe 100644 --- a/modules/amxIdSystem.js +++ b/modules/amxIdSystem.js @@ -10,28 +10,25 @@ import {ajaxBuilder} from '../src/ajax.js'; import {submodule} from '../src/hook.js'; import {getRefererInfo} from '../src/refererDetection.js'; import {deepAccess, logError} from '../src/utils.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; +import {domainOverrideToRootDomain} from '../libraries/domainOverrideToRootDomain/index.js'; const NAME = 'amxId'; const GVL_ID = 737; const ID_KEY = NAME; -const version = '1.0'; +const version = '2.0'; const SYNC_URL = 'https://id.a-mx.com/sync/'; const AJAX_TIMEOUT = 300; +const AJAX_OPTIONS = {method: 'GET', withCredentials: true, contentType: 'text/plain'}; -function validateConfig(config) { - if (config == null || config.storage == null) { - logError(`${NAME}: config.storage is required.`); - return false; - } - - if (config.storage.type !== 'html5') { - logError( - `${NAME} only supports storage.type "html5". ${config.storage.type} was provided` - ); - return false; - } +export const storage = getStorageManager({moduleName: NAME, moduleType: MODULE_TYPE_UID}); +const AMUID_KEY = '__amuidpb'; +const getBidAdapterID = () => storage.localStorageIsEnabled() ? storage.getDataFromLocalStorage(AMUID_KEY) : null; +function validateConfig(config) { if ( + config.storage != null && typeof config.storage.expires === 'number' && config.storage.expires > 30 ) { @@ -44,7 +41,7 @@ function validateConfig(config) { return true; } -function handleSyncResponse(client, response, callback) { +function handleSyncResponse(client, response, params, callback) { if (response.id != null && response.id.length > 0) { callback(response.id); return; @@ -72,7 +69,7 @@ function handleSyncResponse(client, response, callback) { logError(`${NAME} invalid value`, complete); callback(null); }, - }); + }, params, AJAX_OPTIONS); } export const amxIdSubmodule = { @@ -97,6 +94,8 @@ export const amxIdSubmodule = { ? { [ID_KEY]: value } : undefined, + domainOverride: domainOverrideToRootDomain(storage, NAME), + getId(config, consentData, _extant) { if (!validateConfig(config)) { return undefined; @@ -109,12 +108,18 @@ export const amxIdSubmodule = { const params = { tagId: deepAccess(config, 'params.tagId', ''), - // TODO: are these referer values correct? + ref: ref.ref, u: ref.location, + tl: ref.topmostLocation, + nf: ref.numIframes, + rt: ref.reachedTop, + v: '$prebid.version$', + av: version, vg: '$$PREBID_GLOBAL$$', us_privacy: usp, + am: getBidAdapterID(), gdpr: consent.gdprApplies ? 1 : 0, gdpr_consent: consent.consentString, }; @@ -131,7 +136,7 @@ export const amxIdSubmodule = { if (responseText != null && responseText.length > 0) { try { const parsed = JSON.parse(responseText); - handleSyncResponse(client, parsed, done); + handleSyncResponse(client, parsed, params, done); return; } catch (e) { logError(`${NAME} invalid response`, responseText); @@ -142,9 +147,7 @@ export const amxIdSubmodule = { }, }, params, - { - method: 'GET' - } + AJAX_OPTIONS ); return { callback }; diff --git a/modules/amxIdSystem.md b/modules/amxIdSystem.md index f67fefe261e..5d2b0c48478 100644 --- a/modules/amxIdSystem.md +++ b/modules/amxIdSystem.md @@ -29,15 +29,15 @@ pbjs.setConfig({ | Param under `userSync.userIds[]` | Scope | Type | Description | Example | | -------------------------------- | -------- | ------ | --------------------------- | ----------------------------------------- | | name | Required | string | ID for the amxId module | `"amxId"` | -| storage | Required | Object | Settings for amxId storage | See [storage settings](#storage-settings) | +| storage | Optional | Object | Settings for amxId storage | See [storage settings](#storage-settings) | | params | Optional | Object | Parameters for amxId module | See [params](#params) | ### Storage Settings -The following settings are available for the `storage` property in the `userSync.userIds[]` object: +The following settings are suggested for the `storage` property in the `userSync.userIds[]` object: -| Param under `storage` | Scope | Type | Description | Example | -| --------------------- | -------- | ------------ | -------------------------------------------------------------------------------- | --------- | -| name | Required | String | Where the ID will be stored | `"amxId"` | -| type | Required | String | This must be `"html5"` | `"html5"` | -| expires | Required | Number <= 30 | number of days until the stored ID expires. **Must be less than or equal to 30** | `14` | +| Param under `storage` | Type | Description | Example | +| --------------------- | ------------ | -------------------------------------------------------------------------------- | --------- | +| name | String | Where the ID will be stored | `"amxId"` | +| type | String | For best performance, this should be `"html5"` | `"html5"` | +| expires | Number <= 30 | number of days until the stored ID expires. **Must be less than or equal to 30** | `14` | diff --git a/modules/aolBidAdapter.js b/modules/aolBidAdapter.js deleted file mode 100644 index d15a5434e0c..00000000000 --- a/modules/aolBidAdapter.js +++ /dev/null @@ -1,462 +0,0 @@ -import { isInteger, logError, isEmpty, logWarn, getUniqueIdentifierStr, _each, deepSetValue } from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER } from '../src/mediaTypes.js'; - -const AOL_BIDDERS_CODES = { - AOL: 'aol', - VERIZON: 'verizon', - ONEMOBILE: 'onemobile', - ONEDISPLAY: 'onedisplay' -}; - -const AOL_ENDPOINTS = { - DISPLAY: { - GET: 'display-get' - }, - MOBILE: { - GET: 'mobile-get', - POST: 'mobile-post' - } -}; - -const SYNC_TYPES = { - IFRAME: { - TAG: 'iframe', - TYPE: 'iframe' - }, - IMAGE: { - TAG: 'img', - TYPE: 'image' - } -}; - -const SUPPORTED_USER_ID_SOURCES = [ - 'admixer.net', - 'adserver.org', - 'adtelligent.com', - 'akamai.com', - 'amxdt.net', - 'audigent.com', - 'britepool.com', - 'criteo.com', - 'crwdcntrl.net', - 'deepintent.com', - 'epsilon.com', - 'hcn.health', - 'id5-sync.com', - 'idx.lat', - 'intentiq.com', - 'intimatemerger.com', - 'liveintent.com', - 'liveramp.com', - 'mediawallahscript.com', - 'merkleinc.com', - 'netid.de', - 'neustar.biz', - 'nextroll.com', - 'novatiq.com', - 'parrable.com', - 'pubcid.org', - 'quantcast.com', - 'tapad.com', - 'uidapi.com', - 'verizonmedia.com', - 'yahoo.com', - 'zeotap.com' -]; - -const pubapiTemplate = template`${'host'}/pubapi/3.0/${'network'}/${'placement'}/${'pageid'}/${'sizeid'}/ADTECH;v=2;cmd=bid;cors=yes;alias=${'alias'};misc=${'misc'};${'dynamicParams'}`; -const nexageBaseApiTemplate = template`${'host'}/bidRequest?`; -const nexageGetApiTemplate = template`dcn=${'dcn'}&pos=${'pos'}&cmd=bid${'dynamicParams'}`; -const MP_SERVER_MAP = { - us: 'adserver-us.adtech.advertising.com', - eu: 'adserver-eu.adtech.advertising.com', - as: 'adserver-as.adtech.advertising.com' -}; -const NEXAGE_SERVER = 'c2shb.ssp.yahoo.com'; -const ONE_DISPLAY_TTL = 60; -const ONE_MOBILE_TTL = 3600; -const DEFAULT_PROTO = 'https'; - -const NUMERIC_VALUES = { - TRUE: 1, - FALSE: 0 -}; - -function template(strings, ...keys) { - return function (...values) { - let dict = values[values.length - 1] || {}; - let result = [strings[0]]; - keys.forEach(function (key, i) { - let value = isInteger(key) ? values[key] : dict[key]; - result.push(value, strings[i + 1]); - }); - return result.join(''); - }; -} - -function _isMarketplaceBidder(bidderCode) { - return bidderCode === AOL_BIDDERS_CODES.AOL || - bidderCode === AOL_BIDDERS_CODES.VERIZON || - bidderCode === AOL_BIDDERS_CODES.ONEDISPLAY; -} - -function _isOneMobileBidder(bidderCode) { - return bidderCode === AOL_BIDDERS_CODES.AOL || - bidderCode === AOL_BIDDERS_CODES.VERIZON || - bidderCode === AOL_BIDDERS_CODES.ONEMOBILE; -} - -function _isNexageRequestPost(bid) { - if (_isOneMobileBidder(bid.bidder) && bid.params.id && bid.params.imp && bid.params.imp[0]) { - let imp = bid.params.imp[0]; - return imp.id && imp.tagid && imp.banner && imp.banner.w && imp.banner.h; - } -} - -function _isNexageRequestGet(bid) { - return _isOneMobileBidder(bid.bidder) && bid.params.dcn && bid.params.pos; -} - -function isMarketplaceBid(bid) { - return _isMarketplaceBidder(bid.bidder) && bid.params.placement && bid.params.network; -} - -function isMobileBid(bid) { - return _isNexageRequestGet(bid) || _isNexageRequestPost(bid); -} - -function resolveEndpointCode(bid) { - if (_isNexageRequestGet(bid)) { - return AOL_ENDPOINTS.MOBILE.GET; - } else if (_isNexageRequestPost(bid)) { - return AOL_ENDPOINTS.MOBILE.POST; - } else if (isMarketplaceBid(bid)) { - return AOL_ENDPOINTS.DISPLAY.GET; - } -} - -function getSupportedEids(bid) { - return bid.userIdAsEids.filter(eid => { - return SUPPORTED_USER_ID_SOURCES.indexOf(eid.source) !== -1 - }); -} - -export const spec = { - code: AOL_BIDDERS_CODES.AOL, - gvlid: 25, - aliases: [ - AOL_BIDDERS_CODES.ONEMOBILE, - AOL_BIDDERS_CODES.ONEDISPLAY, - AOL_BIDDERS_CODES.VERIZON - ], - supportedMediaTypes: [BANNER], - isBidRequestValid(bid) { - return isMarketplaceBid(bid) || isMobileBid(bid); - }, - buildRequests(bids, bidderRequest) { - const consentData = {}; - if (bidderRequest) { - consentData.gdpr = bidderRequest.gdprConsent; - consentData.uspConsent = bidderRequest.uspConsent; - consentData.gppConsent = bidderRequest.gppConsent; - if (!consentData.gppConsent && bidderRequest.ortb2?.regs?.gpp) { - consentData.gppConsent = { - gppString: bidderRequest.ortb2.regs.gpp, - applicableSections: bidderRequest.ortb2.regs.gpp_sid - } - } - } - - return bids.map(bid => { - const endpointCode = resolveEndpointCode(bid); - - if (endpointCode) { - return this.formatBidRequest(endpointCode, bid, consentData); - } - }); - }, - interpretResponse({ body }, bidRequest) { - if (!body) { - logError('Empty bid response', bidRequest.bidderCode, body); - } else { - let bid = this._parseBidResponse(body, bidRequest); - - if (bid) { - return bid; - } - } - }, - getUserSyncs(options, serverResponses) { - const bidResponse = !isEmpty(serverResponses) && serverResponses[0].body; - - if (bidResponse && bidResponse.ext && bidResponse.ext.pixels) { - return this.parsePixelItems(bidResponse.ext.pixels); - } - - return []; - }, - - formatBidRequest(endpointCode, bid, consentData) { - let bidRequest; - - switch (endpointCode) { - case AOL_ENDPOINTS.DISPLAY.GET: - bidRequest = { - url: this.buildMarketplaceUrl(bid, consentData), - method: 'GET', - ttl: ONE_DISPLAY_TTL - }; - break; - - case AOL_ENDPOINTS.MOBILE.GET: - bidRequest = { - url: this.buildOneMobileGetUrl(bid, consentData), - method: 'GET', - ttl: ONE_MOBILE_TTL - }; - break; - - case AOL_ENDPOINTS.MOBILE.POST: - bidRequest = { - url: this.buildOneMobileBaseUrl(bid), - method: 'POST', - ttl: ONE_MOBILE_TTL, - data: this.buildOpenRtbRequestData(bid, consentData), - options: { - contentType: 'application/json', - customHeaders: { - 'x-openrtb-version': '2.2' - } - } - }; - break; - } - - bidRequest.bidderCode = bid.bidder; - bidRequest.bidId = bid.bidId; - bidRequest.userSyncOn = bid.params.userSyncOn; - - return bidRequest; - }, - buildMarketplaceUrl(bid, consentData) { - const params = bid.params; - const serverParam = params.server; - let regionParam = params.region || 'us'; - let server; - - if (!MP_SERVER_MAP.hasOwnProperty(regionParam)) { - logWarn(`Unknown region '${regionParam}' for AOL bidder.`); - regionParam = 'us'; // Default region. - } - - if (serverParam) { - server = serverParam; - } else { - server = MP_SERVER_MAP[regionParam]; - } - - // Set region param, used by AOL analytics. - params.region = regionParam; - - return this.applyProtocol(pubapiTemplate({ - host: server, - network: params.network, - placement: parseInt(params.placement), - pageid: params.pageId || 0, - sizeid: params.sizeId || 0, - alias: params.alias || getUniqueIdentifierStr(), - misc: new Date().getTime(), // cache busting - dynamicParams: this.formatMarketplaceDynamicParams(params, consentData) - })); - }, - buildOneMobileGetUrl(bid, consentData) { - let { dcn, pos, ext } = bid.params; - if (typeof bid.userId === 'object') { - ext = ext || {}; - let eids = getSupportedEids(bid); - eids.forEach(eid => { - ext['eid' + eid.source] = eid.uids[0].id; - }); - } - let nexageApi = this.buildOneMobileBaseUrl(bid); - if (dcn && pos) { - let dynamicParams = this.formatOneMobileDynamicParams(ext, consentData); - nexageApi += nexageGetApiTemplate({ dcn, pos, dynamicParams }); - } - return nexageApi; - }, - buildOneMobileBaseUrl(bid) { - return this.applyProtocol(nexageBaseApiTemplate({ - host: bid.params.host || NEXAGE_SERVER - })); - }, - applyProtocol(url) { - if (/^https?:\/\//i.test(url)) { - return url; - } - return (url.indexOf('//') === 0) ? `${DEFAULT_PROTO}:${url}` : `${DEFAULT_PROTO}://${url}`; - }, - formatMarketplaceDynamicParams(params = {}, consentData = {}) { - let queryParams = {}; - - Object.assign(queryParams, this.formatKeyValues(params.keyValues)); - Object.assign(queryParams, this.formatConsentData(consentData)); - - let paramsFormatted = ''; - _each(queryParams, (value, key) => { - paramsFormatted += `${key}=${encodeURIComponent(value)};`; - }); - - return paramsFormatted; - }, - formatOneMobileDynamicParams(params = {}, consentData = {}) { - if (this.isSecureProtocol()) { - params.secure = NUMERIC_VALUES.TRUE; - } - - Object.assign(params, this.formatConsentData(consentData)); - - let paramsFormatted = ''; - _each(params, (value, key) => { - paramsFormatted += `&${key}=${encodeURIComponent(value)}`; - }); - - return paramsFormatted; - }, - buildOpenRtbRequestData(bid, consentData = {}) { - let openRtbObject = { - id: bid.params.id, - imp: bid.params.imp - }; - - if (this.isEUConsentRequired(consentData)) { - deepSetValue(openRtbObject, 'regs.ext.gdpr', NUMERIC_VALUES.TRUE); - if (consentData.gdpr.consentString) { - deepSetValue(openRtbObject, 'user.ext.consent', consentData.gdpr.consentString); - } - } - - if (consentData.uspConsent) { - deepSetValue(openRtbObject, 'regs.ext.us_privacy', consentData.uspConsent); - } - - if (typeof bid.userId === 'object') { - openRtbObject.user = openRtbObject.user || {}; - openRtbObject.user.ext = openRtbObject.user.ext || {}; - - let eids = getSupportedEids(bid); - if (eids.length > 0) { - openRtbObject.user.ext.eids = eids - } - } - - return openRtbObject; - }, - isEUConsentRequired(consentData) { - return !!(consentData && consentData.gdpr && consentData.gdpr.gdprApplies); - }, - formatKeyValues(keyValues) { - let keyValuesHash = {}; - - _each(keyValues, (value, key) => { - keyValuesHash[`kv${key}`] = value; - }); - - return keyValuesHash; - }, - formatConsentData(consentData) { - let params = {}; - - if (this.isEUConsentRequired(consentData)) { - params.gdpr = NUMERIC_VALUES.TRUE; - - if (consentData.gdpr.consentString) { - params.euconsent = consentData.gdpr.consentString; - } - } - - if (consentData.uspConsent) { - params.us_privacy = consentData.uspConsent; - } - - if (consentData.gppConsent && consentData.gppConsent.gppString) { - params.gpp = consentData.gppConsent.gppString; - params.gpp_sid = consentData.gppConsent.applicableSections; - } - - return params; - }, - parsePixelItems(pixels) { - let itemsRegExp = /(img|iframe)[\s\S]*?src\s*=\s*("|')(.*?)\2/gi; - let tagNameRegExp = /\w*(?=\s)/; - let srcRegExp = /src=("|')(.*?)\1/; - let pixelsItems = []; - - if (pixels) { - let matchedItems = pixels.match(itemsRegExp); - if (matchedItems) { - matchedItems.forEach(item => { - let tagName = item.match(tagNameRegExp)[0]; - let url = item.match(srcRegExp)[2]; - - if (tagName && url) { - pixelsItems.push({ - type: tagName === SYNC_TYPES.IMAGE.TAG ? SYNC_TYPES.IMAGE.TYPE : SYNC_TYPES.IFRAME.TYPE, - url: url - }); - } - }); - } - } - - return pixelsItems; - }, - - _parseBidResponse(response, bidRequest) { - let bidData; - - try { - bidData = response.seatbid[0].bid[0]; - } catch (e) { - return; - } - - let cpm; - - if (bidData.ext && bidData.ext.encp) { - cpm = bidData.ext.encp; - } else { - cpm = bidData.price; - - if (cpm === null || isNaN(cpm)) { - logError('Invalid price in bid response', AOL_BIDDERS_CODES.AOL, bidData); - return; - } - } - - return { - bidderCode: bidRequest.bidderCode, - requestId: bidRequest.bidId, - ad: bidData.adm, - cpm: cpm, - width: bidData.w, - height: bidData.h, - creativeId: bidData.crid || 0, - pubapiId: response.id, - currency: response.cur || 'USD', - dealId: bidData.dealid, - netRevenue: true, - meta: { - advertiserDomains: bidData && bidData.adomain ? bidData.adomain : [] - }, - ttl: bidRequest.ttl - }; - }, - isOneMobileBidder: _isOneMobileBidder, - isSecureProtocol() { - return document.location.protocol === 'https:'; - } -}; - -registerBidder(spec); diff --git a/modules/aolBidAdapter.md b/modules/aolBidAdapter.md deleted file mode 100644 index 8a9d1e3291d..00000000000 --- a/modules/aolBidAdapter.md +++ /dev/null @@ -1,46 +0,0 @@ -# Overview - -Module Name: AOL Bid Adapter - -Module Type: AOL Adapter - -Maintainer: hb-fe-tech@oath.com - -# Description - -Module that connects to AOL's demand sources - -# Test Parameters -```javascript - var adUnits = [ - { - code: 'test-ad', - sizes: [[300, 250]], - bids: [ - { - bidder: 'onedisplay', - params: { - placement: '3611253', - network: '9599.1', - keyValues: { - test: 'key' - } - } - } - ] - }, - { - code: 'test-mobile-ad', - sizes: [[300, 250]], - bids: [ - { - bidder: 'onemobile', - params: { - dcn: '2c9d2b50015a5aa95b70a9b0b5b10012', - pos: 'header' - } - } - ] - } - ]; -``` diff --git a/modules/apacdexBidAdapter.js b/modules/apacdexBidAdapter.js index 10593855c59..e1557d9c6d3 100644 --- a/modules/apacdexBidAdapter.js +++ b/modules/apacdexBidAdapter.js @@ -1,4 +1,4 @@ -import { deepAccess, isPlainObject, isArray, replaceAuctionPrice, isFn } from '../src/utils.js'; +import { deepAccess, isPlainObject, isArray, replaceAuctionPrice, isFn, logError } from '../src/utils.js'; import { config } from '../src/config.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import {hasPurpose1Consent} from '../src/utils/gpdr.js'; @@ -134,14 +134,14 @@ export const spec = { // Apply geo if (geo) { - payload.geo = geo; + logError('apacdex adapter: Precise lat and long must be set on config; not on bidder parameters'); } payload.bids = bids.map(function (bid) { return { params: bid.params, mediaTypes: bid.mediaTypes, - transactionId: bid.transactionId, + transactionId: bid.ortb2Imp?.ext?.tid, sizes: bid.sizes, bidId: bid.bidId, adUnitCode: bid.adUnitCode, diff --git a/modules/appnexusBidAdapter.js b/modules/appnexusBidAdapter.js index f354eb053b1..0660f4f4b10 100644 --- a/modules/appnexusBidAdapter.js +++ b/modules/appnexusBidAdapter.js @@ -22,21 +22,25 @@ import { logError, logInfo, logMessage, - logWarn, - mergeDeep, - transformBidderParamKeywords + logWarn } from '../src/utils.js'; import {Renderer} from '../src/Renderer.js'; import {config} from '../src/config.js'; -import {getIabSubCategory, registerBidder} from '../src/adapters/bidderFactory.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; import {ADPOD, BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; -import {auctionManager} from '../src/auctionManager.js'; import {find, includes} from '../src/polyfill.js'; import {INSTREAM, OUTSTREAM} from '../src/video.js'; import {getStorageManager} from '../src/storageManager.js'; import {bidderSettings} from '../src/bidderSettings.js'; import {hasPurpose1Consent} from '../src/utils/gpdr.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import {convertOrtbRequestToProprietaryNative} from '../src/native.js'; +import {APPNEXUS_CATEGORY_MAPPING} from '../libraries/categoryTranslationMapping/index.js'; +import { + convertKeywordStringToANMap, + getANKewyordParamFromMaps, + getANKeywordParam, + transformBidderParamKeywords +} from '../libraries/appnexusKeywords/anKeywords.js'; const BIDDER_CODE = 'appnexus'; const URL = 'https://ib.adnxs.com/ut/v3/prebid'; @@ -89,12 +93,11 @@ const NATIVE_MAPPING = { }; const SOURCE = 'pbjs'; const MAX_IMPS_PER_REQUEST = 15; -const mappingFileUrl = 'https://acdn.adnxs-simple.com/prebid/appnexus-mapping/mappings.json'; const SCRIPT_TAG_START = ' { - let keyStr = deepAccess(ortb2Obj, path); - if (isStr(keyStr)) result.push(keyStr); - }); - return result; - } - // grab the ortb2 keyword data (if it exists) and convert from the comma list string format to object format let ortb2 = deepClone(bidderRequest && bidderRequest.ortb2); - let ortb2KeywordsObjList = grabOrtb2Keywords(ortb2).map(keyStr => convertStringToKeywordsObj(keyStr)); let anAuctionKeywords = deepClone(config.getConfig('appnexusAuctionKeywords')) || {}; - // need to convert the string values into array of strings, to properly merge values with other existing keys later - Object.keys(anAuctionKeywords).forEach(k => { if (isStr(anAuctionKeywords[k]) || isNumber(anAuctionKeywords[k])) anAuctionKeywords[k] = [anAuctionKeywords[k]] }); - // combine all sources of keywords (converted from string comma list to object format) into one object (that combines the values for shared keys) - let mergedAuctionKeywords = mergeDeep({}, anAuctionKeywords, ...ortb2KeywordsObjList); - - // convert to final format used by adserver - let auctionKeywords = transformBidderParamKeywords(mergedAuctionKeywords); + let auctionKeywords = getANKeywordParam(ortb2, anAuctionKeywords) if (auctionKeywords.length > 0) { - auctionKeywords.forEach(deleteValues); payload.keywords = auctionKeywords; } @@ -333,14 +318,16 @@ export const spec = { payload.referrer_detection = refererinfo; } - const hasAdPodBid = find(bidRequests, hasAdPod); - if (hasAdPodBid) { - bidRequests.filter(hasAdPod).forEach(adPodBid => { - const adPodTags = createAdPodRequest(tags, adPodBid); - // don't need the original adpod placement because it's in adPodTags - const nonPodTags = payload.tags.filter(tag => tag.uuid !== adPodBid.bidId); - payload.tags = [...nonPodTags, ...adPodTags]; - }); + if (FEATURES.VIDEO) { + const hasAdPodBid = find(bidRequests, hasAdPod); + if (hasAdPodBid) { + bidRequests.filter(hasAdPod).forEach(adPodBid => { + const adPodTags = createAdPodRequest(tags, adPodBid); + // don't need the original adpod placement because it's in adPodTags + const nonPodTags = payload.tags.filter(tag => tag.uuid !== adPodBid.bidId); + payload.tags = [...nonPodTags, ...adPodTags]; + }); + } } if (bidRequests[0].userId) { @@ -418,24 +405,6 @@ export const spec = { return bids; }, - /** - * @typedef {Object} mappingFileInfo - * @property {string} url mapping file json url - * @property {number} refreshInDays prebid stores mapping data in localstorage so you can return in how many days you want to update value stored in localstorage. - * @property {string} localStorageKey unique key to store your mapping json in localstorage - */ - - /** - * Returns mapping file info. This info will be used by bidderFactory to preload mapping file and store data in local storage - * @returns {mappingFileInfo} - */ - getMappingFileInfo: function () { - return { - url: mappingFileUrl, - refreshInDays: 2 - } - }, - getUserSyncs: function (syncOptions, responses, gdprConsent, uspConsent, gppConsent) { function checkGppStatus(gppConsent) { // this is a temporary measure to supress usersync in US-based GPP regions @@ -484,10 +453,6 @@ export const spec = { }, params); if (isOpenRtb) { - if (isPopulatedArray(params.keywords)) { - params.keywords.forEach(deleteValues); - } - Object.keys(params).forEach(paramKey => { let convertedKey = convertCamelToUnderscore(paramKey); if (convertedKey !== paramKey) { @@ -504,16 +469,6 @@ export const spec = { } }; -function isPopulatedArray(arr) { - return !!(isArray(arr) && arr.length > 0); -} - -function deleteValues(keyPairObj) { - if (isPopulatedArray(keyPairObj.value) && keyPairObj.value[0] === '') { - delete keyPairObj.value; - } -} - function strIsAppnexusViewabilityScript(str) { if (!str || str === '') return false; @@ -653,7 +608,7 @@ function newBid(serverBid, rtbBid, bidderRequest) { bid.meta = Object.assign({}, bid.meta, { brandId: rtbBid.brand_id }); } - if (rtbBid.rtb.video) { + if (FEATURES.VIDEO && rtbBid.rtb.video) { // shared video properties used for all 3 contexts Object.assign(bid, { width: rtbBid.rtb.video.player_width, @@ -665,7 +620,7 @@ function newBid(serverBid, rtbBid, bidderRequest) { const videoContext = deepAccess(bidRequest, 'mediaTypes.video.context'); switch (videoContext) { case ADPOD: - const primaryCatId = getIabSubCategory(bidRequest.bidder, rtbBid.brand_category_id); + const primaryCatId = (APPNEXUS_CATEGORY_MAPPING[rtbBid.brand_category_id]) ? APPNEXUS_CATEGORY_MAPPING[rtbBid.brand_category_id] : null; bid.meta = Object.assign({}, bid.meta, { primaryCatId }); const dealTier = rtbBid.deal_priority; bid.video = { @@ -828,24 +783,9 @@ function bidToTag(bid) { tag.external_imp_id = bid.params.external_imp_id; } - let ortb2ImpKwStr = deepAccess(bid, 'ortb2Imp.ext.data.keywords'); - if ((isStr(ortb2ImpKwStr) && ortb2ImpKwStr !== '') || !isEmpty(bid.params.keywords)) { - // convert ortb2 from comma list string format to bid param object format - let ortb2ImpKwObj = convertStringToKeywordsObj(ortb2ImpKwStr); - - let bidParamsKwObj = (isPlainObject(bid.params.keywords)) ? deepClone(bid.params.keywords) : {}; - // need to convert the string values into an array of strings, to properly merge values with other existing keys later - Object.keys(bidParamsKwObj).forEach(k => { if (isStr(bidParamsKwObj[k]) || isNumber(bidParamsKwObj[k])) bidParamsKwObj[k] = [bidParamsKwObj[k]] }); - - // combine both sources of keywords into one merged object (that combines the values for shared keys) - let keywordsObj = mergeDeep({}, bidParamsKwObj, ortb2ImpKwObj); - - // convert to final format used by adserver - let keywordsUt = transformBidderParamKeywords(keywordsObj); - if (keywordsUt.length > 0) { - keywordsUt.forEach(deleteValues); - tag.keywords = keywordsUt; - } + const auKeywords = getANKewyordParamFromMaps(convertKeywordStringToANMap(deepAccess(bid, 'ortb2Imp.ext.data.keywords')), bid.params?.keywords); + if (auKeywords.length > 0) { + tag.keywords = auKeywords; } let gpid = deepAccess(bid, 'ortb2Imp.ext.data.pbadslot'); @@ -865,115 +805,118 @@ function bidToTag(bid) { } } - const videoMediaType = deepAccess(bid, `mediaTypes.${VIDEO}`); - const context = deepAccess(bid, 'mediaTypes.video.context'); - - if (videoMediaType && context === 'adpod') { - tag.hb_source = 7; - } else { - tag.hb_source = 1; - } - if (bid.mediaType === VIDEO || videoMediaType) { - tag.ad_types.push(VIDEO); - } - - // instream gets vastUrl, outstream gets vastXml - if (bid.mediaType === VIDEO || (videoMediaType && context !== 'outstream')) { - tag.require_asset_url = true; - } + if (FEATURES.VIDEO) { + const videoMediaType = deepAccess(bid, `mediaTypes.${VIDEO}`); + const context = deepAccess(bid, 'mediaTypes.video.context'); - if (bid.params.video) { - tag.video = {}; - // place any valid video params on the tag - Object.keys(bid.params.video) - .filter(param => includes(VIDEO_TARGETING, param)) - .forEach(param => { - switch (param) { - case 'context': - case 'playback_method': - let type = bid.params.video[param]; - type = (isArray(type)) ? type[0] : type; - tag.video[param] = VIDEO_MAPPING[param][type]; - break; - // Deprecating tags[].video.frameworks in favor of tags[].video_frameworks - case 'frameworks': - break; - default: - tag.video[param] = bid.params.video[param]; - } - }); + if (videoMediaType && context === 'adpod') { + tag.hb_source = 7; + } else { + tag.hb_source = 1; + } + if (bid.mediaType === VIDEO || videoMediaType) { + tag.ad_types.push(VIDEO); + } - if (bid.params.video.frameworks && isArray(bid.params.video.frameworks)) { - tag['video_frameworks'] = bid.params.video.frameworks; + // instream gets vastUrl, outstream gets vastXml + if (bid.mediaType === VIDEO || (videoMediaType && context !== 'outstream')) { + tag.require_asset_url = true; } - } - // use IAB ORTB values if the corresponding values weren't already set by bid.params.video - if (videoMediaType) { - tag.video = tag.video || {}; - Object.keys(videoMediaType) - .filter(param => includes(VIDEO_RTB_TARGETING, param)) - .forEach(param => { - switch (param) { - case 'minduration': - case 'maxduration': - if (typeof tag.video[param] !== 'number') tag.video[param] = videoMediaType[param]; - break; - case 'skip': - if (typeof tag.video['skippable'] !== 'boolean') tag.video['skippable'] = (videoMediaType[param] === 1); - break; - case 'skipafter': - if (typeof tag.video['skipoffset'] !== 'number') tag.video['skippoffset'] = videoMediaType[param]; - break; - case 'playbackmethod': - if (typeof tag.video['playback_method'] !== 'number') { - let type = videoMediaType[param]; + if (bid.params.video) { + tag.video = {}; + // place any valid video params on the tag + Object.keys(bid.params.video) + .filter(param => includes(VIDEO_TARGETING, param)) + .forEach(param => { + switch (param) { + case 'context': + case 'playback_method': + let type = bid.params.video[param]; type = (isArray(type)) ? type[0] : type; + tag.video[param] = VIDEO_MAPPING[param][type]; + break; + // Deprecating tags[].video.frameworks in favor of tags[].video_frameworks + case 'frameworks': + break; + default: + tag.video[param] = bid.params.video[param]; + } + }); - // we only support iab's options 1-4 at this time. - if (type >= 1 && type <= 4) { - tag.video['playback_method'] = type; - } - } - break; - case 'api': - if (!tag['video_frameworks'] && isArray(videoMediaType[param])) { - // need to read thru array; remove 6 (we don't support it), swap 4 <> 5 if found (to match our adserver mapping for these specific values) - let apiTmp = videoMediaType[param].map(val => { - let v = (val === 4) ? 5 : (val === 5) ? 4 : val; - - if (v >= 1 && v <= 5) { - return v; + if (bid.params.video.frameworks && isArray(bid.params.video.frameworks)) { + tag['video_frameworks'] = bid.params.video.frameworks; + } + } + + // use IAB ORTB values if the corresponding values weren't already set by bid.params.video + if (videoMediaType) { + tag.video = tag.video || {}; + Object.keys(videoMediaType) + .filter(param => includes(VIDEO_RTB_TARGETING, param)) + .forEach(param => { + switch (param) { + case 'minduration': + case 'maxduration': + if (typeof tag.video[param] !== 'number') tag.video[param] = videoMediaType[param]; + break; + case 'skip': + if (typeof tag.video['skippable'] !== 'boolean') tag.video['skippable'] = (videoMediaType[param] === 1); + break; + case 'skipafter': + if (typeof tag.video['skipoffset'] !== 'number') tag.video['skippoffset'] = videoMediaType[param]; + break; + case 'playbackmethod': + if (typeof tag.video['playback_method'] !== 'number') { + let type = videoMediaType[param]; + type = (isArray(type)) ? type[0] : type; + + // we only support iab's options 1-4 at this time. + if (type >= 1 && type <= 4) { + tag.video['playback_method'] = type; } - }).filter(v => v); - tag['video_frameworks'] = apiTmp; - } - break; - - case 'startdelay': - case 'placement': - const contextKey = 'context'; - if (typeof tag.video[contextKey] !== 'number') { - const placement = videoMediaType['placement']; - const startdelay = videoMediaType['startdelay']; - const context = getContextFromPlacement(placement) || getContextFromStartDelay(startdelay); - tag.video[contextKey] = VIDEO_MAPPING[contextKey][context]; - } - break; - } - }); - } + } + break; + case 'api': + if (!tag['video_frameworks'] && isArray(videoMediaType[param])) { + // need to read thru array; remove 6 (we don't support it), swap 4 <> 5 if found (to match our adserver mapping for these specific values) + let apiTmp = videoMediaType[param].map(val => { + let v = (val === 4) ? 5 : (val === 5) ? 4 : val; + + if (v >= 1 && v <= 5) { + return v; + } + }).filter(v => v); + tag['video_frameworks'] = apiTmp; + } + break; + + case 'startdelay': + case 'placement': + const contextKey = 'context'; + if (typeof tag.video[contextKey] !== 'number') { + const placement = videoMediaType['placement']; + const startdelay = videoMediaType['startdelay']; + const context = getContextFromPlacement(placement) || getContextFromStartDelay(startdelay); + tag.video[contextKey] = VIDEO_MAPPING[contextKey][context]; + } + break; + } + }); + } - if (bid.renderer) { - tag.video = Object.assign({}, tag.video, { custom_renderer_present: true }); + if (bid.renderer) { + tag.video = Object.assign({}, tag.video, { custom_renderer_present: true }); + } + } else { + tag.hb_source = 1; } if (bid.params.frameworks && isArray(bid.params.frameworks)) { tag['banner_frameworks'] = bid.params.frameworks; } - let adUnit = find(auctionManager.getAdUnits(), au => bid.transactionId === au.transactionId); - if (adUnit && adUnit.mediaTypes && adUnit.mediaTypes.banner) { + if (deepAccess(bid, `mediaTypes.${BANNER}`)) { tag.ad_types.push(BANNER); } @@ -1269,37 +1212,4 @@ function convertKeywordsToString(keywords) { return result; } -// converts a comma separated list of keywords into the standard keyword object format used in appnexus bid params -// 'genre=rock,genre=pop,pets=dog,music' goes to { 'genre': ['rock', 'pop'], 'pets': ['dog'], 'music': [''] } -function convertStringToKeywordsObj(keyStr) { - let result = {}; - - if (isStr(keyStr) && keyStr !== '') { - // will split based on commas and will eat white space before/after the comma - let keywordList = keyStr.split(/\s*(?:,)\s*/); - keywordList.forEach(kw => { - // if = exists, then split - if (kw.indexOf('=') !== -1) { - let kwPair = kw.split('='); - let key = kwPair[0]; - let val = kwPair[1]; - - // then check for existing key in result > if so add value to the array > if not, add new key and create value array - if (result.hasOwnProperty(key)) { - result[key].push(val); - } else { - result[key] = [val]; - } - } else { - // make a key with '' value; if key already exists > don't add - if (!result.hasOwnProperty(kw)) { - result[kw] = ['']; - } - } - }); - } - - return result; -} - registerBidder(spec); diff --git a/modules/appnexusBidAdapter.md b/modules/appnexusBidAdapter.md index 7ac70e67584..e8c57b782ec 100644 --- a/modules/appnexusBidAdapter.md +++ b/modules/appnexusBidAdapter.md @@ -3,7 +3,7 @@ ``` Module Name: Appnexus Bid Adapter Module Type: Bidder Adapter -Maintainer: prebid-js@xandr.com +Maintainer: prebid@microsoft.com ``` # Description diff --git a/modules/appushBidAdapter.js b/modules/appushBidAdapter.js index 1ad8fe27e42..97772b65e45 100644 --- a/modules/appushBidAdapter.js +++ b/modules/appushBidAdapter.js @@ -154,7 +154,7 @@ export const spec = { coppa: config.getConfig('coppa') === true ? 1 : 0, ccpa: bidderRequest.uspConsent || undefined, gdpr: bidderRequest.gdprConsent || undefined, - tmax: config.getConfig('bidderTimeout') + tmax: bidderRequest.timeout }; const len = validBidRequests.length; diff --git a/modules/apstreamBidAdapter.js b/modules/apstreamBidAdapter.js index 30abb17bfde..2856fb02087 100644 --- a/modules/apstreamBidAdapter.js +++ b/modules/apstreamBidAdapter.js @@ -9,7 +9,7 @@ const CONSTANTS = { BIDDER_CODE: 'apstream', GVLID: 394 }; -const storage = getStorageManager({gvlid: CONSTANTS.GVLID, bidderCode: CONSTANTS.BIDDER_CODE}); +const storage = getStorageManager({bidderCode: CONSTANTS.BIDDER_CODE}); var dsuModule = (function() { 'use strict'; @@ -421,6 +421,7 @@ function buildRequests(bidRequests, bidderRequest) { bidRequests = convertOrtbRequestToProprietaryNative(bidRequests); const data = { med: encodeURIComponent(window.location.href), + // TODO: fix auctionId leak: https://github.com/prebid/Prebid.js/issues/9781 auid: bidderRequest.auctionId, ref: document.referrer, dnt: getDNT() ? 1 : 0, diff --git a/modules/asoBidAdapter.js b/modules/asoBidAdapter.js index 39c42f193b2..e569f04a2a8 100644 --- a/modules/asoBidAdapter.js +++ b/modules/asoBidAdapter.js @@ -28,7 +28,8 @@ export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO], aliases: [ - {code: 'bcmint'} + {code: 'bcmint'}, + {code: 'bidgency'} ], isBidRequestValid: bid => { @@ -299,7 +300,7 @@ function createBasePayload(bidRequest, bidderRequest) { const urlsInfo = getUrlsInfo(bidderRequest); const payload = { - id: bidRequest.auctionId + '_' + bidRequest.bidId, + id: bidRequest.bidId, at: 1, tmax: bidderRequest.timeout, site: { diff --git a/modules/astraoneBidAdapter.js b/modules/astraoneBidAdapter.js index d6bfa4b93ee..d7f92bb5fac 100644 --- a/modules/astraoneBidAdapter.js +++ b/modules/astraoneBidAdapter.js @@ -11,7 +11,7 @@ function buildBidRequests(validBidRequests) { const params = validBidRequest.params; const bidRequest = { bidId: validBidRequest.bidId, - transactionId: validBidRequest.transactionId, + transactionId: validBidRequest.ortb2Imp?.ext?.tid, sizes: validBidRequest.sizes, placement: params.placement, placeId: params.placeId, diff --git a/modules/atsAnalyticsAdapter.js b/modules/atsAnalyticsAdapter.js index 0c0227ea34a..8e92146694f 100644 --- a/modules/atsAnalyticsAdapter.js +++ b/modules/atsAnalyticsAdapter.js @@ -4,8 +4,11 @@ import CONSTANTS from '../src/constants.json'; import adaptermanager from '../src/adapterManager.js'; import {ajax} from '../src/ajax.js'; import {getStorageManager} from '../src/storageManager.js'; +import {getGlobal} from '../src/prebidGlobal.js'; -export const storage = getStorageManager(); +import {MODULE_TYPE_ANALYTICS} from '../src/activities/modules.js'; +const MODULE_CODE = 'atsAnalytics'; +export const storage = getStorageManager({moduleType: MODULE_TYPE_ANALYTICS, moduleName: MODULE_CODE}); /** * Analytics adapter for - https://liveramp.com @@ -352,7 +355,7 @@ atsAnalyticsAdapter.callHandler = function (evtype, args) { let bidWonTimeout = atsAnalyticsAdapter.context.bidWonTimeout ? atsAnalyticsAdapter.context.bidWonTimeout : 2000; let events = []; setTimeout(() => { - let winningBids = $$PREBID_GLOBAL$$.getAllWinningBids(); + let winningBids = getGlobal().getAllWinningBids(); logInfo('ATS Analytics - winning bids: ', winningBids) // prepare format data for sending to analytics endpoint if (handlerRequest.length) { @@ -398,7 +401,7 @@ atsAnalyticsAdapter.callHandler = function (evtype, args) { adaptermanager.registerAnalyticsAdapter({ adapter: atsAnalyticsAdapter, - code: 'atsAnalytics', + code: MODULE_CODE, gvlid: 97 }); diff --git a/modules/audiencerunBidAdapter.js b/modules/audiencerunBidAdapter.js index 754a48ede75..9beb20d4f77 100644 --- a/modules/audiencerunBidAdapter.js +++ b/modules/audiencerunBidAdapter.js @@ -1,18 +1,17 @@ import { + _each, deepAccess, - isFn, - logError, - getValue, + formatQS, getBidIdParameter, - _each, + getValue, isArray, + isFn, + logError, triggerPixel, - formatQS, } from '../src/utils.js'; -import { config } from '../src/config.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER } from '../src/mediaTypes.js'; -import { createEidsArray } from './userId/eids.js'; +import {config} from '../src/config.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER} from '../src/mediaTypes.js'; const BIDDER_CODE = 'audiencerun'; const BASE_URL = 'https://d.audiencerun.com'; @@ -115,8 +114,9 @@ export const spec = { bidId: bid.bidId, bidderRequestId: getBidIdParameter('bidderRequestId', bid), adUnitCode: getBidIdParameter('adUnitCode', bid), + // TODO: fix auctionId leak: https://github.com/prebid/Prebid.js/issues/9781 auctionId: getBidIdParameter('auctionId', bid), - transactionId: getBidIdParameter('transactionId', bid), + transactionId: bid.ortb2Imp?.ext?.tid || '', }; }); @@ -135,7 +135,7 @@ export const spec = { payload.uspConsent = deepAccess(bidderRequest, 'uspConsent'); payload.schain = deepAccess(bidRequests, '0.schain'); - payload.userId = deepAccess(bidRequests, '0.userId') ? createEidsArray(bidRequests[0].userId) : []; + payload.userId = deepAccess(bidRequests, '0.userIdAsEids') || [] if (bidderRequest && bidderRequest.gdprConsent) { payload.gdpr = { diff --git a/modules/automatadBidAdapter.js b/modules/automatadBidAdapter.js index 1174c2a9f38..bea2a9df5b2 100644 --- a/modules/automatadBidAdapter.js +++ b/modules/automatadBidAdapter.js @@ -1,7 +1,7 @@ -import { logInfo } from '../src/utils.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js' -import {BANNER} from '../src/mediaTypes.js' -import {ajax} from '../src/ajax.js' +import {logInfo} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER} from '../src/mediaTypes.js'; +import {ajax} from '../src/ajax.js'; const BIDDER = 'automatad' @@ -57,7 +57,7 @@ export const spec = { // params from bid request const openrtbRequest = { - id: validBidRequests[0].auctionId, + id: bidderRequest.bidderRequestId, imp: impressions, site: { id: siteId, diff --git a/modules/axonixBidAdapter.js b/modules/axonixBidAdapter.js index 5435bf09059..87c3aff444a 100644 --- a/modules/axonixBidAdapter.js +++ b/modules/axonixBidAdapter.js @@ -1,8 +1,8 @@ -import { isArray, logError, deepAccess, isEmpty, triggerPixel, replaceAuctionPrice } from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER, VIDEO } from '../src/mediaTypes.js'; -import { config } from '../src/config.js'; -import { ajax } from '../src/ajax.js'; +import {deepAccess, isArray, isEmpty, logError, replaceAuctionPrice, triggerPixel} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {config} from '../src/config.js'; +import {ajax} from '../src/ajax.js'; const BIDDER_CODE = 'axonix'; const BIDDER_VERSION = '1.0.2'; @@ -150,7 +150,7 @@ export const spec = { for (const resp of response) { if (resp.requestId) { responses.push(Object.assign(resp, { - ttl: config.getConfig('_bidderTimeout') + ttl: 60 })); } } diff --git a/modules/beachfrontBidAdapter.js b/modules/beachfrontBidAdapter.js index f80481d66c8..37de8e637a9 100644 --- a/modules/beachfrontBidAdapter.js +++ b/modules/beachfrontBidAdapter.js @@ -103,7 +103,6 @@ export const spec = { let responseMeta = Object.assign({ mediaType: VIDEO, advertiserDomains: [] }, response.meta); let bidResponse = { requestId: bidRequest.bidId, - bidderCode: spec.code, cpm: response.bidPrice, width: firstSize.w, height: firstSize.h, diff --git a/modules/bedigitechBidAdapter.js b/modules/bedigitechBidAdapter.js new file mode 100644 index 00000000000..9e59a2509a6 --- /dev/null +++ b/modules/bedigitechBidAdapter.js @@ -0,0 +1,70 @@ +import {BANNER, NATIVE} from '../src/mediaTypes.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {_each, isArray} from '../src/utils.js'; + +const BEDIGITECH_CODE = 'bedigitech'; +const BEDIGITECH_ENDPOINT = 'https://bid.bedigitech.com/bid/pub_bid.php'; +const BEDIGITECH_REQUEST_METHOD = 'GET'; +const BEDIGITECH_CURRENCY = 'USD'; +let requestId = ''; +function interpretResponse(placementResponse, bids) { + const bid = { + id: placementResponse.id, + requestId: requestId || placementResponse.id, + bidderCode: 'bedigitech', + cpm: placementResponse.cpm, + ad: decodeURIComponent(placementResponse.ad), + width: placementResponse.width || 0, + height: placementResponse.height || 0, + currency: placementResponse.currency || BEDIGITECH_CURRENCY, + ttl: placementResponse.ttl || 300, + creativeId: placementResponse.crid, + requestTimestamp: placementResponse.requestTime, + timeToRespond: placementResponse.timeToRespond || 300, + netRevenue: placementResponse.netRevenue, + meta: { + mediaType: BANNER, + }, + }; + bids.push(bid); +} + +export const spec = { + code: BEDIGITECH_CODE, + supportedMediaTypes: [BANNER, NATIVE], + isBidRequestValid: bid => { + requestId = ''; + requestId = bid.bidId + return !!bid.params.placementId && !!bid.bidId && bid.bidder === 'bedigitech' + }, + + buildRequests: (bidRequests) => { + return bidRequests.map(bid => { + let url = BEDIGITECH_ENDPOINT; + const data = {'pid': bid.params.placementId}; + return { + method: BEDIGITECH_REQUEST_METHOD, + url, + data, + options: { + contentType: 'application/json', + withCredentials: false, + crossOrigin: true, + }, + }; + }); + }, + + interpretResponse: function(serverResponse) { + let bids = []; + if (isArray(serverResponse.body)) { + _each(serverResponse.body, function(placementResponse) { + interpretResponse(placementResponse, bids); + }); + } + return bids; + }, + +}; + +registerBidder(spec); diff --git a/modules/bedigitechBidAdapter.md b/modules/bedigitechBidAdapter.md new file mode 100644 index 00000000000..3baa12aeb74 --- /dev/null +++ b/modules/bedigitechBidAdapter.md @@ -0,0 +1,32 @@ +# Overview + +Module Name: Bedigitech Bidder Adapter + +Module Type: Bidder Adapter + +Maintainer: prebid@bedigitech.com + +# Description + +You can use this adapter to get a bid from bedigitech.com. + +About us : https://www.bedigitech.com/ + + +# Test Parameters +```javascript + var adUnits = [ + { + code: 'div-bedigitech-example', + sizes: [[300, 250]], + bids: [ + { + bidder: "bedigitech", + params: { + placementId: 309 + } + } + ] + } + ]; +``` diff --git a/modules/beopBidAdapter.js b/modules/beopBidAdapter.js index 4ee5aaec5be..c5282c28cfc 100644 --- a/modules/beopBidAdapter.js +++ b/modules/beopBidAdapter.js @@ -1,7 +1,18 @@ -import { deepAccess, isArray, isStr, logWarn, triggerPixel, buildUrl, logInfo, getValue, getBidIdParameter } from '../src/utils.js'; -import { getRefererInfo } from '../src/refererDetection.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { config } from '../src/config.js'; +import { + buildUrl, + deepAccess, + getBidIdParameter, + getValue, + isArray, + logInfo, + logWarn, + triggerPixel +} from '../src/utils.js'; +import {getRefererInfo} from '../src/refererDetection.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {config} from '../src/config.js'; +import {getAllOrtbKeywords} from '../libraries/keywords/keywords.js'; + const BIDDER_CODE = 'beop'; const ENDPOINT_URL = 'https://hb.beop.io/bid'; const TCF_VENDOR_ID = 666; @@ -37,33 +48,30 @@ export const spec = { */ buildRequests: function(validBidRequests, bidderRequest) { const slots = validBidRequests.map(beOpRequestSlotsMaker); + const firstPartyData = bidderRequest.ortb2 || {}; + const psegs = firstPartyData.user?.ext?.permutive || firstPartyData.user?.ext?.data?.permutive || []; + const userBpSegs = firstPartyData.user?.ext?.bpsegs || firstPartyData.user?.ext?.data?.bpsegs || []; + const siteBpSegs = firstPartyData.site?.ext?.bpsegs || firstPartyData.site?.ext?.data?.bpsegs || []; const pageUrl = getPageUrl(bidderRequest.refererInfo, window); const gdpr = bidderRequest.gdprConsent; const firstSlot = slots[0]; const kwdsFromRequest = firstSlot.kwds; - let keywords = []; - if (kwdsFromRequest) { - if (isArray(kwdsFromRequest)) { - keywords = kwdsFromRequest; - } else if (isStr(kwdsFromRequest)) { - if (kwdsFromRequest.indexOf(',') != -1) { - keywords = kwdsFromRequest.split(',').map((e) => { return e.trim() }); - } else { - keywords.push(kwdsFromRequest); - } - } - } + let keywords = getAllOrtbKeywords(bidderRequest.ortb2, kwdsFromRequest); + const payloadObject = { at: new Date().toString(), nid: firstSlot.nid, nptnid: firstSlot.nptnid, pid: firstSlot.pid, + psegs: psegs, + bpsegs: (userBpSegs.concat(siteBpSegs)).map(item => item.toString()), url: pageUrl, lang: (window.navigator.language || window.navigator.languages[0]), kwds: keywords, dbg: false, slts: slots, is_amp: deepAccess(bidderRequest, 'referrerInfo.isAmp'), + gdpr_applies: gdpr ? gdpr.gdprApplies : false, tc_string: (gdpr && gdpr.gdprApplies) ? gdpr.consentString : null, }; @@ -121,7 +129,6 @@ function buildTrackingParams(data, info, value) { nptnid: params.networkPartnerId, bid: data.bidId || data.requestId, sl_n: data.adUnitCode, - aid: data.auctionId, se_ca: 'bid', se_ac: info, se_va: value, @@ -149,8 +156,7 @@ function beOpRequestSlotsMaker(bid) { bid: getBidIdParameter('bidId', bid), brid: getBidIdParameter('bidderRequestId', bid), name: getBidIdParameter('adUnitCode', bid), - aid: getBidIdParameter('auctionId', bid), - tid: getBidIdParameter('transactionId', bid), + tid: bid.ortb2Imp?.ext?.tid || '', brc: getBidIdParameter('bidRequestsCount', bid), bdrc: getBidIdParameter('bidderRequestCount', bid), bwc: getBidIdParameter('bidderWinsCount', bid), diff --git a/modules/beopBidAdapter.md b/modules/beopBidAdapter.md index c0e88cb1ceb..53d2542b69c 100644 --- a/modules/beopBidAdapter.md +++ b/modules/beopBidAdapter.md @@ -9,13 +9,14 @@ Module that connects to BeOp's demand sources # Test Parameters + ``` var adUnits = [ { code: 'in-article', mediaTypes: { banner: { - sizes: [[1,1]], + sizes: [[1,1]], } }, bids: [ @@ -31,3 +32,36 @@ Module that connects to BeOp's demand sources ]; ``` +# Custom Bidder data + +If you want to pass your first party data to BeOp, you can set your bidder `config.ortb2` object with + +```json +{ + "site": { + "ext": { + "bpsegs": ["Your", 1, "ST", "party", "data"], + "data": { + "bpsegs": ["Your", 1, "ST", "party", "data"] + } + } + }, + "user": { + "ext": { + "bpsegs": ["Your", 1, "ST", "party", "data"], + "data": { + "bpsegs": ["Your", 1, "ST", "party", "data"] + } + } + } +} +``` + +You can choose the location between: + +- `site.ext` +- `site.ext.data` +- `user.ext` +- `user.ext.data` + +and our BidAdapter will be able to find them diff --git a/modules/betweenBidAdapter.js b/modules/betweenBidAdapter.js index ea28420481d..d615e433cc0 100644 --- a/modules/betweenBidAdapter.js +++ b/modules/betweenBidAdapter.js @@ -43,7 +43,8 @@ export const spec = { rr: getRr(), s: i.params && i.params.s, bidid: i.bidId, - transactionid: i.transactionId, + transactionid: i.ortb2Imp?.ext?.tid, + // TODO: fix auctionId leak: https://github.com/prebid/Prebid.js/issues/9781 auctionid: i.auctionId }; diff --git a/modules/beyondmediaBidAdapter.js b/modules/beyondmediaBidAdapter.js index 57c141dc2aa..bbcd972470c 100644 --- a/modules/beyondmediaBidAdapter.js +++ b/modules/beyondmediaBidAdapter.js @@ -145,7 +145,7 @@ export const spec = { coppa: config.getConfig('coppa') === true ? 1 : 0, ccpa: bidderRequest.uspConsent || undefined, gdpr: bidderRequest.gdprConsent || undefined, - tmax: config.getConfig('bidderTimeout') + tmax: bidderRequest.timeout }; const len = validBidRequests.length; diff --git a/modules/bidwatchAnalyticsAdapter.js b/modules/bidwatchAnalyticsAdapter.js index 0908b02de2e..289e607686f 100644 --- a/modules/bidwatchAnalyticsAdapter.js +++ b/modules/bidwatchAnalyticsAdapter.js @@ -2,6 +2,7 @@ import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import CONSTANTS from '../src/constants.json'; import { ajax } from '../src/ajax.js'; +import { getRefererInfo } from '../src/refererDetection.js'; const analyticsType = 'endpoint'; const url = 'URL_TO_SERVER_ENDPOINT'; @@ -76,7 +77,8 @@ function cleanAuctionEnd(args) { } function cleanCreatives(args) { - return filterAttributes(args, false); + let stringArgs = JSON.parse(dereferenceWithoutRenderer(args)); + return filterAttributes(stringArgs, false); } function enhanceMediaType(arg) { @@ -92,7 +94,7 @@ function enhanceMediaType(arg) { function addBidResponse(args) { let eventType = BID_RESPONSE; - let argsCleaned = cleanCreatives(JSON.parse(JSON.stringify(args))); ; + let argsCleaned = cleanCreatives(args); ; if (allEvents[eventType] == undefined) { allEvents[eventType] = [] } allEvents[eventType].push(argsCleaned); } @@ -109,7 +111,9 @@ function addTimeout(args) { if (saveEvents[eventType] == undefined) { saveEvents[eventType] = [] } saveEvents[eventType].push(args); let argsCleaned = []; - let argsDereferenced = JSON.parse(JSON.stringify(args)); + let argsDereferenced = {} + let stringArgs = JSON.parse(dereferenceWithoutRenderer(args)); + argsDereferenced = stringArgs; argsDereferenced.forEach((attr) => { argsCleaned.push(filterAttributes(JSON.parse(JSON.stringify(attr)), false)); }); @@ -117,17 +121,42 @@ function addTimeout(args) { auctionEnd[eventType].push(argsCleaned); } +export const dereferenceWithoutRenderer = function(args) { + if (args.renderer) { + let tmp = args.renderer; + delete args.renderer; + let stringified = JSON.stringify(args); + args['renderer'] = tmp; + return stringified; + } + if (args.bidsReceived) { + let tmp = {} + for (let key in args.bidsReceived) { + if (args.bidsReceived[key].renderer) { + tmp[key] = args.bidsReceived[key].renderer; + delete args.bidsReceived[key].renderer; + } + } + let stringified = JSON.stringify(args); + for (let key in tmp) { + args.bidsReceived[key].renderer = tmp[key]; + } + return stringified; + } + return JSON.stringify(args); +} + function addAuctionEnd(args) { let eventType = AUCTION_END; if (saveEvents[eventType] == undefined) { saveEvents[eventType] = [] } saveEvents[eventType].push(args); - let argsCleaned = cleanAuctionEnd(JSON.parse(JSON.stringify(args))); + let argsCleaned = cleanAuctionEnd(JSON.parse(dereferenceWithoutRenderer(args))); if (auctionEnd[eventType] == undefined) { auctionEnd[eventType] = [] } auctionEnd[eventType].push(argsCleaned); } function handleBidWon(args) { - args = enhanceMediaType(filterAttributes(JSON.parse(JSON.stringify(args)), true)); + args = enhanceMediaType(filterAttributes(JSON.parse(dereferenceWithoutRenderer(args)), true)); let increment = args['cpm']; if (typeof saveEvents['auctionEnd'] == 'object') { saveEvents['auctionEnd'].forEach((auction) => { @@ -145,6 +174,7 @@ function handleBidWon(args) { }); } args['cpmIncrement'] = increment; + args['referer'] = encodeURIComponent(getRefererInfo().page || getRefererInfo().topmostLocation); if (typeof saveEvents.bidRequested == 'object' && saveEvents.bidRequested.length > 0 && saveEvents.bidRequested[0].gdprConsent) { args.gdpr = saveEvents.bidRequested[0].gdprConsent; } ajax(endpoint + '.bidwatch.io/analytics/bid_won', null, JSON.stringify(args), {method: 'POST', withCredentials: true}); } diff --git a/modules/bizzclickBidAdapter.js b/modules/bizzclickBidAdapter.js index 4ef2b6dd9f8..dc7731231ab 100644 --- a/modules/bizzclickBidAdapter.js +++ b/modules/bizzclickBidAdapter.js @@ -1,8 +1,9 @@ -import { logMessage, getDNT, deepSetValue, deepAccess, _map, logWarn } from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import {_map, deepAccess, deepSetValue, getDNT, logMessage, logWarn} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; import {config} from '../src/config.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import {convertOrtbRequestToProprietaryNative} from '../src/native.js'; + const BIDDER_CODE = 'bizzclick'; const ACCOUNTID_MACROS = '[account_id]'; const URL_ENDPOINT = `https://us-e-node1.bizzclick.com/bid?rtb_seat_id=prebidjs&secret_key=${ACCOUNTID_MACROS}`; @@ -92,7 +93,7 @@ export const spec = { host: location.host }, source: { - tid: bidRequest.transactionId, + tid: bidRequest.ortb2Imp?.ext?.tid, ext: { schain: {} } @@ -234,7 +235,9 @@ const prepareImpObject = (bidRequest) => { }; const addNativeParameters = bidRequest => { let impObject = { - id: bidRequest.transactionId, + // TODO: top-level ID is not in ORTB native 1.2, is this intentional? + // (despite the name, this appears to be an ORTB native request - not an imp - object) + id: bidRequest.bidId, ver: NATIVE_VERSION, }; const assets = _map(bidRequest.mediaTypes.native, (bidParams, key) => { diff --git a/modules/bliinkBidAdapter.js b/modules/bliinkBidAdapter.js index ee814807331..9a9d74d14c1 100644 --- a/modules/bliinkBidAdapter.js +++ b/modules/bliinkBidAdapter.js @@ -154,6 +154,7 @@ export const buildRequests = (validBidRequests, bidderRequest) => { return { sizes: bid.sizes.map((size) => ({ w: size[0], h: size[1] })), id: bid.params.tagId, + // TODO: bidId is globally unique, is it a good choice for transaction ID (vs ortb2Imp.ext.tid)? transactionId: bid.bidId, mediaTypes: Object.keys(bid.mediaTypes), imageUrl: deepAccess(bid, 'params.imageUrl', ''), diff --git a/modules/bluebillywigBidAdapter.js b/modules/bluebillywigBidAdapter.js index 27e310177f6..d4bde9b3f2c 100644 --- a/modules/bluebillywigBidAdapter.js +++ b/modules/bluebillywigBidAdapter.js @@ -4,7 +4,6 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {VIDEO} from '../src/mediaTypes.js'; import {config} from '../src/config.js'; import {Renderer} from '../src/Renderer.js'; -import {createEidsArray} from './userId/eids.js'; const DEV_MODE = window.location.search.match(/bbpbs_debug=true/); @@ -52,10 +51,9 @@ const BB_HELPERS = { else if (Array.isArray(adServerCur) && adServerCur.length) request.cur = [adServerCur[0]]; }, addUserIds: function(request, validBidRequests) { - const bidUserId = deepAccess(validBidRequests, '0.userId'); - const eids = createEidsArray(bidUserId); + const eids = deepAccess(validBidRequests, '0.userIdAsEids'); - if (eids.length) { + if (eids != null && eids.length) { deepSetValue(request, 'user.ext.eids', eids); } }, @@ -278,8 +276,8 @@ export const spec = { }); const request = { - id: bidderRequest.auctionId, - source: {tid: bidderRequest.auctionId}, + id: bidderRequest.bidderRequestId, + source: {tid: bidderRequest.ortb2?.source?.tid}, tmax: BB_CONSTANTS.DEFAULT_TIMEOUT, imp: imps, test: DEV_MODE ? 1 : 0, diff --git a/modules/blueconicRtdProvider.js b/modules/blueconicRtdProvider.js index 9a7f5984637..b6eb9374671 100644 --- a/modules/blueconicRtdProvider.js +++ b/modules/blueconicRtdProvider.js @@ -9,13 +9,14 @@ import {getStorageManager} from '../src/storageManager.js'; import {submodule} from '../src/hook.js'; import {mergeDeep, isPlainObject, logMessage, logError} from '../src/utils.js'; +import {MODULE_TYPE_RTD} from '../src/activities/modules.js'; const MODULE_NAME = 'realTimeData'; const SUBMODULE_NAME = 'blueconic'; export const RTD_LOCAL_NAME = 'bcPrebidData'; -export const storage = getStorageManager({moduleName: SUBMODULE_NAME}); +export const storage = getStorageManager({moduleType: MODULE_TYPE_RTD, moduleName: SUBMODULE_NAME}); /** * Try parsing stringified array of data. diff --git a/modules/brandmetricsRtdProvider.js b/modules/brandmetricsRtdProvider.js index 53868eccc4c..30844c9c483 100644 --- a/modules/brandmetricsRtdProvider.js +++ b/modules/brandmetricsRtdProvider.js @@ -6,8 +6,10 @@ * @requires module:modules/realTimeData */ import {submodule} from '../src/hook.js'; -import {deepAccess, deepSetValue, logError, mergeDeep} from '../src/utils.js'; +import {deepAccess, deepSetValue, logError, mergeDeep, generateUUID} from '../src/utils.js'; import {loadExternalScript} from '../src/adloader.js'; +import * as events from '../src/events.js'; +import CONSTANTS from '../src/constants.json'; const MODULE_NAME = 'brandmetrics' const MODULE_CODE = MODULE_NAME @@ -15,12 +17,15 @@ const RECEIVED_EVENTS = [] const GVL_ID = 422 const TCF_PURPOSES = [1, 7] +let billableEventsInitialized = false + function init (config, userConsent) { const hasConsent = checkConsent(userConsent) if (hasConsent) { const moduleConfig = getMergedConfig(config) initializeBrandmetrics(moduleConfig.params.scriptId) + initializeBillableEvents() } return hasConsent } @@ -82,7 +87,6 @@ function processBrandmetricsEvents (reqBidsConfigObj, moduleConfig, callback) { if (RECEIVED_EVENTS.length > 0) { callBidTargeting(RECEIVED_EVENTS[RECEIVED_EVENTS.length - 1]) } else { - window._brandmetrics = window._brandmetrics || [] window._brandmetrics.push({ cmd: '_addeventlistener', val: { @@ -120,6 +124,8 @@ function setBidderTargeting (reqBidsConfigObj, moduleConfig, key, val) { * @param {string} scriptId - The script- id provided by brandmetrics or brandmetrics partner */ function initializeBrandmetrics(scriptId) { + window._brandmetrics = window._brandmetrics || [] + if (scriptId) { const path = 'https://cdn.brandmetrics.com/survey/script/' const file = scriptId + '.js' @@ -129,6 +135,34 @@ function initializeBrandmetrics(scriptId) { } } +/** +* Hook in to brandmetrics creative_in_view- event and emit billable- event for creatives measured by brandmetrics. +*/ +function initializeBillableEvents() { + if (!billableEventsInitialized) { + window._brandmetrics.push({ + cmd: '_addeventlistener', + val: { + event: 'creative_in_view', + handler: (ev) => { + if (ev.source && ev.source.type === 'pbj') { + const bid = ev.source.data; + events.emit(CONSTANTS.EVENTS.BILLABLE_EVENT, { + vendor: 'brandmetrics', + type: 'creative_in_view', + measurementId: ev.mid, + billingId: generateUUID(), + auctionId: bid.auctionId, + transactionId: bid.transactionId, + }); + } + }, + } + }) + billableEventsInitialized = true + } +} + /** * Merges a provided config with default values * @param {Object} customConfig diff --git a/modules/brandmetricsRtdProvider.md b/modules/brandmetricsRtdProvider.md index 89ee6bb75cf..d6304f9ae12 100644 --- a/modules/brandmetricsRtdProvider.md +++ b/modules/brandmetricsRtdProvider.md @@ -16,10 +16,10 @@ Enable the Brandmetrics RTD in your Prebid configuration, using the below format pbjs.setConfig({ ..., realTimeData: { - auctionDelay: 500, // auction delay + auctionDelay: 500, dataProviders: [{ name: 'brandmetrics', - waitForIt: true // should be true if there's an `auctionDelay`, + waitForIt: true, params: { scriptId: '00000000-0000-0000-0000-000000000000', bidders: ['ozone'] @@ -29,6 +29,7 @@ pbjs.setConfig({ ... }) ``` +The scriptId- parameter is provided by brandmetrics or a brandmetrics partner. ## Parameters | Name | Type | Description | Default | @@ -38,3 +39,17 @@ pbjs.setConfig({ | params | Object | | - | | params.bidders | String[] | An array of bidders which should receive targeting keys. | `[]` | | params.scriptId | String | A script- id GUID if the brandmetrics- script should be initialized. | `undefined` | + +## Billable events +The module emits a billable event for creatives that are measured by brandmetrics and are considered in- view. + +```javascript +{ + vendor: 'brandmetrics', + type: 'creative_in_view', + measurementId: string, // UUID, brandmetrics measurement id + billingId: string, // UUID, unique billing id + auctionId: string, // Prebid auction id + transactionId: string, //Prebid transaction id +} +``` \ No newline at end of file diff --git a/modules/braveBidAdapter.js b/modules/braveBidAdapter.js index 2e087b41868..d954522ae24 100644 --- a/modules/braveBidAdapter.js +++ b/modules/braveBidAdapter.js @@ -1,8 +1,8 @@ -import { parseUrl, isEmpty, isStr, triggerPixel } from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import { config } from '../src/config.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import {isEmpty, isStr, parseUrl, triggerPixel} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; +import {config} from '../src/config.js'; +import {convertOrtbRequestToProprietaryNative} from '../src/native.js'; const BIDDER_CODE = 'brave'; const DEFAULT_CUR = 'USD'; @@ -58,7 +58,8 @@ export const spec = { impObject.video = createVideoRequest(br); } else if (br.mediaTypes.native) { impObject.native = { - id: br.transactionId, + // TODO: `id` is not part of the ORTB native spec, is this intentional? + id: br.bidId, ver: '1.2', request: createNativeRequest(br) }; @@ -83,7 +84,7 @@ export const spec = { domain: parseUrl(page).hostname, page: page, }, - tmax: bidderRequest.timeout || config.getConfig('bidderTimeout') || 500, + tmax: bidderRequest.timeout, imp }; @@ -229,7 +230,8 @@ const createBannerRequest = br => { }; const createVideoRequest = br => { - let videoObj = {id: br.transactionId}; + // TODO: `id` is not part of imp.video in ORTB; is this intentional? + let videoObj = {id: br.bidId}; let supportParamsList = ['mimes', 'minduration', 'maxduration', 'protocols', 'startdelay', 'skip', 'minbitrate', 'maxbitrate', 'api', 'linearity']; for (let param of supportParamsList) { diff --git a/modules/bridBidAdapter.js b/modules/bridBidAdapter.js new file mode 100644 index 00000000000..8e7c2f166ef --- /dev/null +++ b/modules/bridBidAdapter.js @@ -0,0 +1,223 @@ +import {createTrackPixelHtml, _each, deepAccess, getDefinedParams, parseGPTSingleSizeArrayToRtbSize} from '../src/utils.js'; +import {VIDEO} from '../src/mediaTypes.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {getRefererInfo} from '../src/refererDetection.js'; + +const SOURCE = 'pbjs'; +const BIDDER_CODE = 'brid'; +const ENDPOINT_URL = 'https://pbs.prebrid.tv/openrtb2/auction'; +const GVLID = 934; +const TIME_TO_LIVE = 300; +const VIDEO_PARAMS = [ + 'api', 'linearity', 'maxduration', 'mimes', 'minduration', 'placement', + 'playbackmethod', 'protocols', 'startdelay' +]; + +export const spec = { + + code: BIDDER_CODE, + gvlid: GVLID, + supportedMediaTypes: [VIDEO], + + /** + * Determines whether or not the given bid request is valid. + * + * @param {object} bid The bid to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function(bid) { + return !!(bid && bid.params && bid.params.placementId); + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param {BidRequest[]} bidRequests A non-empty list of bid requests which should be sent to the Server. + * @param {BidderRequest} bidderRequest bidder request object. + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function(bidRequests, bidderRequest) { + const requests = []; + + _each(bidRequests, function(bid) { + const placementId = bid.params.placementId; + const bidId = bid.bidId; + let sizes = bid.sizes; + if (sizes && !Array.isArray(sizes[0])) sizes = [sizes]; + + const site = getSiteObj(); + + const postBody = { + sdk: { + source: SOURCE, + version: '$prebid.version$' + }, + id: bidderRequest.bidderRequestId, + site, + imp: [] + }; + + const imp = { + ext: { + prebid: { + storedrequest: {'id': placementId} + } + } + }; + + const video = deepAccess(bid, 'mediaTypes.video'); + if (video) { + imp.video = getDefinedParams(video, VIDEO_PARAMS); + if (video.playerSize) { + imp.video = Object.assign( + imp.video, parseGPTSingleSizeArrayToRtbSize(video.playerSize[0]) || {} + ); + } else if (video.w && video.h) { + imp.video.w = video.w; + imp.video.h = video.h; + }; + }; + + postBody.imp.push(imp); + + const gdprConsent = bidderRequest && bidderRequest.gdprConsent; + const uspConsent = bidderRequest && bidderRequest.uspConsent; + + if (gdprConsent || uspConsent) { + postBody.regs = { ext: {} }; + + if (uspConsent) { + postBody.regs.ext.us_privacy = uspConsent; + }; + + if (gdprConsent) { + if (typeof gdprConsent.gdprApplies !== 'undefined') { + postBody.regs.ext.gdpr = gdprConsent.gdprApplies ? 1 : 0; + }; + + if (typeof gdprConsent.consentString !== 'undefined') { + postBody.user = { + ext: { consent: gdprConsent.consentString } + }; + }; + }; + }; + + if (bidRequests[0].schain) { + postBody.schain = bidRequests[0].schain; + } + + const params = bid.params; + + requests.push({ + method: 'POST', + url: ENDPOINT_URL, + data: JSON.stringify(postBody), + options: { + withCredentials: true + }, + bidId, + params + }); + }); + + return requests; + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {*} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function(serverResponse, bidRequest) { + const response = serverResponse.body; + const bidResponses = []; + + _each(response.seatbid, (resp) => { + _each(resp.bid, (bid) => { + const requestId = bidRequest.bidId; + const params = bidRequest.params; + + const {ad, adUrl, vastUrl, vastXml} = getAd(bid); + + const bidResponse = { + requestId, + params, + cpm: bid.price, + width: bid.w, + height: bid.h, + creativeId: bid.adid, + currency: response.cur, + netRevenue: false, + ttl: TIME_TO_LIVE, + meta: { + advertiserDomains: bid.adomain || [] + } + }; + + if (vastUrl || vastXml) { + bidResponse.mediaType = VIDEO; + if (vastUrl) bidResponse.vastUrl = vastUrl; + if (vastXml) bidResponse.vastXml = vastXml; + } else { + bidResponse.ad = ad; + bidResponse.adUrl = adUrl; + }; + + bidResponses.push(bidResponse); + }); + }); + + return bidResponses; + }, + +} + +/** + * Helper function to get ad + * + * @param {object} bid The bid. + * @return {object} ad object. + */ +function getAd(bid) { + let ad, adUrl, vastXml, vastUrl; + + switch (deepAccess(bid, 'ext.prebid.type')) { + case VIDEO: + if (bid.adm.substr(0, 4) === 'http') { + vastUrl = bid.adm; + } else { + vastXml = bid.adm; + }; + break; + default: + if (bid.adm && bid.nurl) { + ad = bid.adm; + ad += createTrackPixelHtml(decodeURIComponent(bid.nurl)); + } else if (bid.adm) { + ad = bid.adm; + } else if (bid.nurl) { + adUrl = bid.nurl; + }; + } + + return {ad, adUrl, vastXml, vastUrl}; +} + +/** + * Helper function to get site object + * + * @return {object} siteObj. + */ +function getSiteObj() { + const refInfo = (getRefererInfo && getRefererInfo()) || {}; + + return { + page: refInfo.page, + ref: refInfo.ref, + domain: refInfo.domain + }; +} + +registerBidder(spec); diff --git a/modules/brightcomBidAdapter.js b/modules/brightcomBidAdapter.js index 15b4175b59a..c4cc5394a03 100644 --- a/modules/brightcomBidAdapter.js +++ b/modules/brightcomBidAdapter.js @@ -68,7 +68,7 @@ function buildRequests(bidReqs, bidderRequest) { w: screen.width, h: screen.height }, - tmax: config.getConfig('bidderTimeout') + tmax: bidderRequest?.timeout }; if (bidderRequest && bidderRequest.gdprConsent) { @@ -100,7 +100,6 @@ function buildRequests(bidReqs, bidderRequest) { method: 'POST', url: URL, data: JSON.stringify(brightcomBidReq), - options: {contentType: 'text/plain', withCredentials: false} }; } catch (e) { logError(e, {bidReqs, bidderRequest}); diff --git a/modules/brightcomSSPBidAdapter.js b/modules/brightcomSSPBidAdapter.js index b7a9aa3fdc9..b85a01c8fc7 100644 --- a/modules/brightcomSSPBidAdapter.js +++ b/modules/brightcomSSPBidAdapter.js @@ -84,7 +84,7 @@ function buildRequests(bidReqs, bidderRequest) { w: screen.width, h: screen.height }, - tmax: config.getConfig('bidderTimeout') + tmax: bidderRequest?.timeout }; if (bidderRequest?.gdprConsent) { @@ -116,7 +116,6 @@ function buildRequests(bidReqs, bidderRequest) { method: 'POST', url: URL, data: JSON.stringify(payload), - options: {contentType: 'text/plain', withCredentials: false} }; } catch (e) { logError(e, {bidReqs, bidderRequest}); diff --git a/modules/browsiBidAdapter.js b/modules/browsiBidAdapter.js new file mode 100644 index 00000000000..03b6b2a8f3d --- /dev/null +++ b/modules/browsiBidAdapter.js @@ -0,0 +1,171 @@ +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {config} from '../src/config.js'; +import {VIDEO} from '../src/mediaTypes.js'; +import {logError, logInfo, isArray, isStr} from '../src/utils.js'; + +const BIDDER_CODE = 'browsi'; +const DATA = 'brwvidtag'; +const ADAPTER = '__bad'; +const USP_TO_REPLACE = '__USP__'; +const GDPR_STR_TO_REPLACE = '__GDPR_STR__'; +const GDPR_TO_REPLACE = '__GDPR__'; +export const ENDPOINT = 'https://rtb.avantisvideo.com/api/v2/auction/getbid'; + +export const spec = { + code: BIDDER_CODE, + gvlid: 329, + supportedMediaTypes: [VIDEO], + /** + * Determines whether or not the given bid request is valid. + * @param bid + * @returns {boolean} + */ + isBidRequestValid: function (bid) { + if (!bid.params) { + return false; + } + const {pubId, tagId} = bid.params + const {mediaTypes} = bid; + return !!(validateBrowsiIds(pubId, tagId) && mediaTypes?.[VIDEO]); + }, + /** + * Make a server request from the list of BidRequests + * @param validBidRequests + * @param bidderRequest + * @returns ServerRequest Info describing the request to the server. + */ + buildRequests: function (validBidRequests, bidderRequest) { + const requests = []; + const {refererInfo, bidderRequestId, gdprConsent, uspConsent} = bidderRequest; + validBidRequests.forEach(bidRequest => { + const {bidId, adUnitCode, auctionId, ortb2Imp, schain, params} = bidRequest; + const video = getVideoMediaType(bidRequest); + + const request = { + method: 'POST', + url: params.endpoint || ENDPOINT, + data: { + requestId: bidderRequestId, + bidId: bidId, + timeout: getTimeout(bidderRequest), + baData: getData(), + referer: refererInfo.page || refererInfo, + gdpr: gdprConsent, + ccpa: uspConsent, + sizes: video.playerSize, + video: video, + aUCode: adUnitCode, + // TODO: fix auctionId leak: https://github.com/prebid/Prebid.js/issues/9781 + aID: auctionId, + tID: ortb2Imp?.ext?.tid, + schain: schain, + params: params + } + }; + requests.push(request); + }) + return requests; + }, + /** + * Unpack the response from the server into a list of bids. + * @param serverResponse A successful response from the server. + * @param request + * @returns {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function (serverResponse, request) { + const bidResponses = []; + const response = serverResponse?.body; + if (!response) { return bidResponses; } + const { + bidId, + w, + h, + vXml, + vUrl, + cpm, + cur, + ttl, + ...extraParams + } = response; + delete extraParams.userSyncs; + const bidResponse = { + requestId: request.data.bidId, + bidId, + vastXml: vXml, + vastUrl: vUrl, + cpm, + ttl, + mediaType: VIDEO, + width: w, + height: h, + currency: cur, + ...extraParams + }; + bidResponses.push(bidResponse); + return bidResponses; + }, + /** + * Extracts user-syncs information from server response + * @param syncOptions {SyncOptions} + * @param serverResponses {ServerResponse[]} + * @param gdprConsent + * @param uspConsent + * @returns {UserSync[]} + */ + getUserSyncs: function (syncOptions, serverResponses, gdprConsent, uspConsent) { + const serverResponse = isArray(serverResponses) ? serverResponses[0] : serverResponses; + const syncParams = serverResponse?.body?.userSyncs; + const userSyncs = []; + const allowedTypes = []; + syncOptions.iframeEnabled && allowedTypes.push('iframe'); + syncOptions.pixelEnabled && allowedTypes.push('image'); + if (syncParams && allowedTypes.length) { + syncParams.forEach(syncParam => { + let { url, type } = syncParam; + if (!allowedTypes.includes(type)) { return; } + url = getValidUrl(url, gdprConsent, uspConsent); + userSyncs.push({ + type, + url + }); + }) + } + return userSyncs; + }, + onTimeout(timeoutData) { + logInfo(`${BIDDER_CODE} bidder timed out`, timeoutData); + }, + onBidderError: function ({error}) { + logError(`${BIDDER_CODE} bidder error`, error); + } +} +/** + * Replaces GdprConsent and uspConsent params in url + * @param url {String} + * @param gdprConsent + * @param uspConsent + * @returns {string} + */ +const getValidUrl = function (url, gdprConsent, uspConsent) { + let validUrl = url.replace(GDPR_TO_REPLACE, gdprConsent?.gdprApplies || '') + .replace(GDPR_STR_TO_REPLACE, encodeURIComponent(gdprConsent?.consentString || '')) + .replace(USP_TO_REPLACE, encodeURIComponent(uspConsent?.consentString || '')); + if (validUrl.indexOf('http') < 0) { + validUrl = 'http://' + validUrl; + } + return validUrl; +} + +const validateBrowsiIds = function (pubId, tagId) { + return pubId && tagId && isStr(pubId) && isStr(tagId); +} +const getData = function () { + return window[DATA]?.[ADAPTER]; +} +const getTimeout = function (bidderRequest) { + return bidderRequest.timeout || config.getConfig('bidderTimeout'); +} +const getVideoMediaType = function (bidRequest) { + return bidRequest.mediaTypes?.[VIDEO]; +} +registerBidder(spec); diff --git a/modules/browsiBidAdapter.md b/modules/browsiBidAdapter.md new file mode 100644 index 00000000000..cd032393c8e --- /dev/null +++ b/modules/browsiBidAdapter.md @@ -0,0 +1,38 @@ +# Overview + +``` +Module Name: Browsi Bid Adapter +Module Type: Bidder Adapter +Maintainer: support@browsi.com +``` + +# Description + +Connects to Browsi Ad server for bids. + +Browsi bid adapter supports Video media type. + +**Note:** The bid adapter requires correct setup and approval, including an existing publisher account. + +For more information about [Browsi](https://www.browsi.com), please contact [support@browsi.com](support@browsi.com). + +# Sample Ad Unit: +```javascript +let videoAdUnit = [ +{ + code: 'videoAdUnit', + mediaTypes: { + video: { + playerSize: [[300, 250]], + context: 'outstream' + }, + }, + bids: [{ + bidder: 'browsi', + params: { + pubId: '117a476f-9791-4a82-80db-4c01c1683db0', // Publisher ID provided by Browsi + tagId: '1' // Tag ID provided by Browsi + } + }] +}]; +``` diff --git a/modules/browsiRtdProvider.js b/modules/browsiRtdProvider.js index 31b2d709f35..4a61f40600d 100644 --- a/modules/browsiRtdProvider.js +++ b/modules/browsiRtdProvider.js @@ -24,8 +24,10 @@ import {find, includes} from '../src/polyfill.js'; import {getGlobal} from '../src/prebidGlobal.js'; import * as events from '../src/events.js'; import CONSTANTS from '../src/constants.json'; +import {MODULE_TYPE_RTD} from '../src/activities/modules.js'; +const MODULE_NAME = 'browsi'; -const storage = getStorageManager(); +const storage = getStorageManager({moduleType: MODULE_TYPE_RTD, moduleName: MODULE_NAME}); /** @type {ModuleParams} */ let _moduleParams = {}; @@ -336,7 +338,7 @@ export const browsiSubmodule = { * used to link submodule with realTimeData * @type {string} */ - name: 'browsi', + name: MODULE_NAME, /** * get data and send back to realTimeData module * @function diff --git a/modules/byDataAnalyticsAdapter.js b/modules/byDataAnalyticsAdapter.js index 84479ee618b..81fd4388c7d 100644 --- a/modules/byDataAnalyticsAdapter.js +++ b/modules/byDataAnalyticsAdapter.js @@ -5,9 +5,10 @@ import enc from 'crypto-js/enc-utf8'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import CONSTANTS from '../src/constants.json'; import adapterManager from '../src/adapterManager.js'; -import { getStorageManager } from '../src/storageManager.js'; +import {getStorageManager} from '../src/storageManager.js'; import { auctionManager } from '../src/auctionManager.js'; import { ajax } from '../src/ajax.js'; +import {MODULE_TYPE_ANALYTICS} from '../src/activities/modules.js'; const versionCode = '4.4.1' const secretKey = 'bydata@123456' @@ -16,7 +17,9 @@ const DEFAULT_EVENT_URL = 'https://pbjs-stream.bydata.com/topics/prebid' const analyticsType = 'endpoint' const isBydata = isKeyInUrl('bydata_debug') const adunitsMap = {} -const storage = getStorageManager(); +const MODULE_CODE = 'bydata'; +const storage = getStorageManager({moduleType: MODULE_TYPE_ANALYTICS, moduleName: MODULE_CODE}); + let initOptions = {} var payload = {} var winPayload = {} @@ -391,7 +394,7 @@ function sendDataOnKf(dataObj) { adapterManager.registerAnalyticsAdapter({ adapter: ascAdapter, - code: 'bydata' + code: MODULE_CODE, }); function _logInfo(message, meta) { diff --git a/modules/emx_digitalBidAdapter.js b/modules/cadentApertureMXBidAdapter.js similarity index 77% rename from modules/emx_digitalBidAdapter.js rename to modules/cadentApertureMXBidAdapter.js index 99f313b9484..26e8639154c 100644 --- a/modules/emx_digitalBidAdapter.js +++ b/modules/cadentApertureMXBidAdapter.js @@ -15,18 +15,22 @@ import {Renderer} from '../src/Renderer.js'; import {find, includes} from '../src/polyfill.js'; import {parseDomain} from '../src/refererDetection.js'; -const BIDDER_CODE = 'emx_digital'; +const BIDDER_CODE = 'cadent_aperture_mx'; const ENDPOINT = 'hb.emxdgt.com'; const RENDERER_URL = 'https://js.brealtime.com/outstream/1.30.0/bundle.js'; const ADAPTER_VERSION = '1.5.1'; const DEFAULT_CUR = 'USD'; +const ALIASES = [ + { code: 'emx_digital', gvlid: 183 }, + { code: 'cadent', gvlid: 183 }, +]; const EIDS_SUPPORTED = [ { key: 'idl_env', source: 'liveramp.com', rtiPartner: 'idl', queryParam: 'idl' }, { key: 'uid2.id', source: 'uidapi.com', rtiPartner: 'UID2', queryParam: 'uid2' } ]; -export const emxAdapter = { +export const cadentAdapter = { validateSizes: (sizes) => { if (!isArray(sizes) || typeof sizes[0] === 'undefined') { logWarn(BIDDER_CODE + ': Sizes should be an array'); @@ -40,7 +44,7 @@ export const emxAdapter = { buildBanner: (bid) => { let sizes = []; bid.mediaTypes && bid.mediaTypes.banner && bid.mediaTypes.banner.sizes ? sizes = bid.mediaTypes.banner.sizes : sizes = bid.sizes; - if (!emxAdapter.validateSizes(sizes)) { + if (!cadentAdapter.validateSizes(sizes)) { logWarn(BIDDER_CODE + ': could not detect mediaType banner sizes. Assigning to bid sizes instead'); sizes = bid.sizes } @@ -55,13 +59,13 @@ export const emxAdapter = { h: sizes[0][1] }; }, - formatVideoResponse: (bidResponse, emxBid, bidRequest) => { - bidResponse.vastXml = emxBid.adm; + formatVideoResponse: (bidResponse, cadentBid, bidRequest) => { + bidResponse.vastXml = cadentBid.adm; if (bidRequest.bidderRequest && bidRequest.bidderRequest.bids && bidRequest.bidderRequest.bids.length > 0) { const matchingBid = find(bidRequest.bidderRequest.bids, bid => bidResponse.requestId && bid.bidId && bidResponse.requestId === bid.bidId && bid.mediaTypes && bid.mediaTypes.video && bid.mediaTypes.video.context === 'outstream'); if (matchingBid) { - bidResponse.renderer = emxAdapter.createRenderer(bidResponse, { - id: emxBid.id, + bidResponse.renderer = cadentAdapter.createRenderer(bidResponse, { + id: cadentBid.id, url: RENDERER_URL }); } @@ -81,7 +85,7 @@ export const emxAdapter = { dnt: (navigator.doNotTrack === 'yes' || navigator.doNotTrack === '1' || navigator.msDoNotTrack === '1') ? 1 : 0, h: screen.height, w: screen.width, - devicetype: emxAdapter.isMobile() ? 1 : emxAdapter.isConnectedTV() ? 3 : 2, + devicetype: cadentAdapter.isMobile() ? 1 : cadentAdapter.isConnectedTV() ? 3 : 2, language: (navigator.language || navigator.browserLanguage || navigator.userLanguage || navigator.systemLanguage), }; }, @@ -114,7 +118,7 @@ export const emxAdapter = { loaded: false }); try { - renderer.setRender(emxAdapter.outstreamRender); + renderer.setRender(cadentAdapter.outstreamRender); } catch (err) { logWarn('Prebid Error calling setRender on renderer', err); } @@ -131,13 +135,13 @@ export const emxAdapter = { videoObj['w'] = bid.mediaTypes.video.playerSize[0]; videoObj['h'] = bid.mediaTypes.video.playerSize[1]; } - return emxAdapter.cleanProtocols(videoObj); + return cadentAdapter.cleanProtocols(videoObj); }, parseResponse: (bidResponseAdm) => { try { return decodeURIComponent(bidResponseAdm.replace(/%(?![0-9][0-9a-fA-F]+)/g, '%25')); } catch (err) { - logError('emx_digitalBidAdapter', 'error', err); + logError('cadent_aperture_mxBidAdapter', 'error', err); } }, getSite: (refInfo) => { @@ -148,45 +152,45 @@ export const emxAdapter = { ref: refInfo.ref || window.document.referrer } }, - getGdpr: (bidRequests, emxData) => { + getGdpr: (bidRequests, cadentData) => { if (bidRequests.gdprConsent) { - emxData.regs = { + cadentData.regs = { ext: { gdpr: bidRequests.gdprConsent.gdprApplies === true ? 1 : 0 } }; } if (bidRequests.gdprConsent && bidRequests.gdprConsent.gdprApplies) { - emxData.user = { + cadentData.user = { ext: { consent: bidRequests.gdprConsent.consentString } }; } - return emxData; + return cadentData; }, - getSupplyChain: (bidderRequest, emxData) => { + getSupplyChain: (bidderRequest, cadentData) => { if (bidderRequest.bids[0] && bidderRequest.bids[0].schain) { - emxData.source = { + cadentData.source = { ext: { schain: bidderRequest.bids[0].schain } }; } - return emxData; + return cadentData; }, // supporting eids getEids(bidRequests) { return EIDS_SUPPORTED - .map(emxAdapter.getUserId(bidRequests)) + .map(cadentAdapter.getUserId(bidRequests)) .filter(x => x); }, getUserId(bidRequests) { return ({ key, source, rtiPartner }) => { let id = deepAccess(bidRequests, `userId.${key}`); - return id ? emxAdapter.formatEid(id, source, rtiPartner) : null; + return id ? cadentAdapter.formatEid(id, source, rtiPartner) : null; }; }, formatEid(id, source, rtiPartner) { @@ -203,6 +207,7 @@ export const emxAdapter = { export const spec = { code: BIDDER_CODE, gvlid: 183, + alias: ALIASES, supportedMediaTypes: [BANNER, VIDEO], isBidRequestValid: function (bid) { if (!bid || !bid.params) { @@ -211,7 +216,7 @@ export const spec = { } if (bid.bidder !== BIDDER_CODE) { - logWarn(BIDDER_CODE + ': Must use "emx_digital" as bidder code.'); + logWarn(BIDDER_CODE + ': Must use "cadent_aperture_mx" as bidder code.'); return false; } @@ -223,12 +228,12 @@ export const spec = { if (bid.mediaTypes && bid.mediaTypes.banner) { let sizes; bid.mediaTypes.banner.sizes ? sizes = bid.mediaTypes.banner.sizes : sizes = bid.sizes; - if (!emxAdapter.validateSizes(sizes)) { + if (!cadentAdapter.validateSizes(sizes)) { logWarn(BIDDER_CODE + ': Missing sizes in bid'); return false; } } else if (bid.mediaTypes && bid.mediaTypes.video) { - if (!emxAdapter.checkVideoContext(bid)) { + if (!cadentAdapter.checkVideoContext(bid)) { logWarn(BIDDER_CODE + ': Missing video context: instream or outstream'); return false; } @@ -242,13 +247,13 @@ export const spec = { return true; }, buildRequests: function (validBidRequests, bidderRequest) { - const emxImps = []; + const cadentImps = []; const timeout = bidderRequest.timeout || ''; const timestamp = Date.now(); const url = 'https://' + ENDPOINT + ('?t=' + timeout + '&ts=' + timestamp + '&src=pbjs'); const secure = location.protocol.indexOf('https') > -1 ? 1 : 0; - const device = emxAdapter.getDevice(); - const site = emxAdapter.getSite(bidderRequest.refererInfo); + const device = cadentAdapter.getDevice(); + const site = cadentAdapter.getSite(bidderRequest.refererInfo); _each(validBidRequests, function (bid) { let tagid = getBidIdParameter('tagid', bid.params); @@ -269,35 +274,35 @@ export const spec = { if (gpid) { data.ext = {gpid: gpid.toString()}; } - let typeSpecifics = isVideo ? { video: emxAdapter.buildVideo(bid) } : { banner: emxAdapter.buildBanner(bid) }; + let typeSpecifics = isVideo ? { video: cadentAdapter.buildVideo(bid) } : { banner: cadentAdapter.buildBanner(bid) }; let bidfloorObj = bidfloor > 0 ? { bidfloor, bidfloorcur: DEFAULT_CUR } : {}; - let emxBid = Object.assign(data, typeSpecifics, bidfloorObj); - emxImps.push(emxBid); + let cadentBid = Object.assign(data, typeSpecifics, bidfloorObj); + cadentImps.push(cadentBid); }); - let emxData = { + let cadentData = { id: bidderRequest.auctionId, - imp: emxImps, + imp: cadentImps, device, site, cur: DEFAULT_CUR, version: ADAPTER_VERSION }; - emxData = emxAdapter.getGdpr(bidderRequest, Object.assign({}, emxData)); - emxData = emxAdapter.getSupplyChain(bidderRequest, Object.assign({}, emxData)); + cadentData = cadentAdapter.getGdpr(bidderRequest, Object.assign({}, cadentData)); + cadentData = cadentAdapter.getSupplyChain(bidderRequest, Object.assign({}, cadentData)); if (bidderRequest && bidderRequest.uspConsent) { - emxData.us_privacy = bidderRequest.uspConsent; + cadentData.us_privacy = bidderRequest.uspConsent; } // adding eid support if (bidderRequest.userId) { - let eids = emxAdapter.getEids(bidderRequest); + let eids = cadentAdapter.getEids(bidderRequest); if (eids.length > 0) { - if (emxData.user && emxData.user.ext) { - emxData.user.ext.eids = eids; + if (cadentData.user && cadentData.user.ext) { + cadentData.user.ext.eids = eids; } else { - emxData.user = { + cadentData.user = { ext: {eids} }; } @@ -307,7 +312,7 @@ export const spec = { return { method: 'POST', url, - data: JSON.stringify(emxData), + data: JSON.stringify(cadentData), options: { withCredentials: true }, @@ -315,42 +320,42 @@ export const spec = { }; }, interpretResponse: function (serverResponse, bidRequest) { - let emxBidResponses = []; + let cadentBidResponses = []; let response = serverResponse.body || {}; if (response.seatbid && response.seatbid.length > 0 && response.seatbid[0].bid) { - response.seatbid.forEach(function (emxBid) { - emxBid = emxBid.bid[0]; + response.seatbid.forEach(function (cadentBid) { + cadentBid = cadentBid.bid[0]; let isVideo = false; - let adm = emxAdapter.parseResponse(emxBid.adm) || ''; + let adm = cadentAdapter.parseResponse(cadentBid.adm) || ''; let bidResponse = { - requestId: emxBid.id, - cpm: emxBid.price, - width: emxBid.w, - height: emxBid.h, - creativeId: emxBid.crid || emxBid.id, - dealId: emxBid.dealid || null, + requestId: cadentBid.id, + cpm: cadentBid.price, + width: cadentBid.w, + height: cadentBid.h, + creativeId: cadentBid.crid || cadentBid.id, + dealId: cadentBid.dealid || null, currency: 'USD', netRevenue: true, - ttl: emxBid.ttl, + ttl: cadentBid.ttl, ad: adm }; - if (emxBid.adm && emxBid.adm.indexOf(' -1) { + if (cadentBid.adm && cadentBid.adm.indexOf(' -1) { isVideo = true; - bidResponse = emxAdapter.formatVideoResponse(bidResponse, Object.assign({}, emxBid), bidRequest); + bidResponse = cadentAdapter.formatVideoResponse(bidResponse, Object.assign({}, cadentBid), bidRequest); } bidResponse.mediaType = (isVideo ? VIDEO : BANNER); // support for adomain in prebid 5.0 - if (emxBid.adomain && emxBid.adomain.length) { + if (cadentBid.adomain && cadentBid.adomain.length) { bidResponse.meta = { - advertiserDomains: emxBid.adomain + advertiserDomains: cadentBid.adomain }; } - emxBidResponses.push(bidResponse); + cadentBidResponses.push(bidResponse); }); } - return emxBidResponses; + return cadentBidResponses; }, getUserSyncs: function (syncOptions, responses, gdprConsent, uspConsent) { const syncs = []; diff --git a/modules/emx_digitalBidAdapter.md b/modules/cadentApertureMXBidAdapter.md similarity index 55% rename from modules/emx_digitalBidAdapter.md rename to modules/cadentApertureMXBidAdapter.md index 03ba554c5ad..d924f904be4 100644 --- a/modules/emx_digitalBidAdapter.md +++ b/modules/cadentApertureMXBidAdapter.md @@ -1,18 +1,18 @@ # Overview ``` -Module Name: EMX Digital Adapter +Module Name: Cadent Aperture MX Adapter Module Type: Bidder Adapter -Maintainer: git@emxdigital.com +Maintainer: contactaperturemx@cadent.tv ``` # Description -The EMX Digital adapter provides publishers with access to the EMX Marketplace. The adapter is GDPR compliant. Please note that the adapter supports Banner and Video (Instream & Outstream) media types. +The Cadent Aperture MX adapter provides publishers with access to the Cadent Aperture MX SSP. The adapter is GDPR compliant. Please note that the adapter supports Banner and Video (Instream & Outstream) media types. -Note: The EMX Digital adapter requires approval and implementation guidelines from the EMX team, including existing publishers that work with EMX Digital. Please reach out to your account manager or prebid@emxdigital.com for more information. +Note: The Cadent Aperture MX adapter requires approval and implementation guidelines from the Cadent team, including existing publishers that work with Cadent. Please reach out to your account manager or contactaperturemx@cadent.tv for more information. -The bidder code should be ```emx_digital``` +The bidder code should be ```cadent_aperture_mx``` The params used by the bidder are : ```tagid``` - string (mandatory) ```bidfloor``` - string (optional) @@ -29,7 +29,7 @@ var adUnits = [{ }, bids: [ { - bidder: 'emx_digital', + bidder: 'cadent_aperture_mx', params: { tagid: '25251', } @@ -49,7 +49,7 @@ var adUnits = [{ }, bids: [ { - bidder: 'emx_digital', + bidder: 'cadent_aperture_mx', params: { tagid: '25251', video: { diff --git a/modules/captifyRtdProvider.js b/modules/captifyRtdProvider.js deleted file mode 100644 index b97aa49a48e..00000000000 --- a/modules/captifyRtdProvider.js +++ /dev/null @@ -1,146 +0,0 @@ -/** - * This module adds Captify real time data provider module - * The {@link module:modules/realTimeData} module is required - * The module will fetch segments (page-centric) from Captify live-classification server - * @module modules/captifyRtdProvider - * @requires module:modules/realTimeData - */ -import { submodule } from '../src/hook.js'; -import { getRefererInfo } from '../src/refererDetection.js'; -import { ajax } from '../src/ajax.js'; -import { config } from '../src/config.js'; -import {deepAccess, isArray, isEmptyStr, isNumber, logError} from '../src/utils.js'; -import {getGlobal} from '../src/prebidGlobal.js'; - -const MODULE_NAME = 'realTimeData'; -const SUBMODULE_NAME = 'CaptifyRTDModule'; -const DEFAULT_LC_URL = 'https://live-classification.cpx.to/prebid-segments'; - -const STATUS = { - SUCCESS: 200, - ACCEPTED: 202, -}; - -/** - * Set `appnexusAuctionKeywords` that appnexus bidder will read and send in request to Xandr - * @param {Array} segments captify segments for Appnexus(Xandr) system, in form of [id1, id2, id3] - * where id1, id2, id3 - actual Xandr segment ids with keywords enabled - */ -export function setAppnexusSegments(segments) { - config.setConfig({ - appnexusAuctionKeywords: { - 'captify_segments': segments, - }, - }) -} - -/** - * Function returns only bidders that contained both, in moduleConfig and at least one adUnit. - * @param {Array} bidders Contains list of bidders, to set targeting for - * @param {Object} data Response of live-classification service - */ -export function addSegmentData(bidders, data) { - for (const bidder of bidders) { - if (bidder === 'appnexus') { setAppnexusSegments(data['xandr']) } - } -} - -/** - * Function returns only bidders that contained in both, moduleConfig and at least one adUnit. - * @param {Object} moduleConfig Config object passed to the module - * @param {Object} reqBidsConfigObj Config object for the bidders; each adapter has its own entry - */ -export function getMatchingBidders(moduleConfig, reqBidsConfigObj) { - const biddersFromConf = deepAccess(moduleConfig, 'params.bidders'); - - const adUnitBidders = reqBidsConfigObj.adUnits - .flatMap(({bids}) => bids.map(({bidder}) => bidder)) - .filter((e, i, a) => a.indexOf(e) === i); - - if (!isArray(adUnitBidders) || !adUnitBidders.length) { - logError(SUBMODULE_NAME, 'Missing parameter bidders in bidRequestConfig'); - return []; - } - - return biddersFromConf.filter(bidder => adUnitBidders.includes(bidder)); -} - -/** - * Main function that sets Captify targeting for various bidders - * @param {Object} moduleConfig Config object passed to the module - * @param {Function} onDone callback function that executed when everything is done - * @param {Object} reqBidsConfigObj Config object for the bidders; each adapter has its own entry - * @param {Object} gcv contains data related to user consent, if applies - */ -export function setCaptifyTargeting(reqBidsConfigObj, onDone, moduleConfig, gcv) { - const pbjsVer = getGlobal(); - const ref = getRefererInfo().referer; - const url = document.URL; - const pubId = moduleConfig.params.pubId; - const bidders = getMatchingBidders(moduleConfig, reqBidsConfigObj) - const requestBody = { - pubId, - ref, - url, - pbjsVer, - gcv - }; - let requestUrl = moduleConfig.params.url; - if (!requestUrl || isEmptyStr(requestUrl)) { - requestUrl = DEFAULT_LC_URL; - } - - if (!bidders.length) { - logError(SUBMODULE_NAME, 'There are no matched bidders to work with'); - return; - } - - ajax(requestUrl, { - success: function (response, req) { - if (req.status === STATUS.SUCCESS) { - try { - const data = JSON.parse(response); - if (data) { - addSegmentData(bidders, data); - } - } catch (e) { - logError(SUBMODULE_NAME, 'Unable to parse live-classification data' + e); - } - } - onDone(); - }, - error: function () { - onDone(); - logError(SUBMODULE_NAME, 'Unable to get live-classification data'); - } - }, - JSON.stringify(requestBody), - { - contentType: 'application/json', - method: 'POST', - }); -} - -export function init(moduleConfig, userConsent) { - // Validate bidders - const biddersFromConf = deepAccess(moduleConfig, 'params.bidders'); - if (!isArray(biddersFromConf) || !biddersFromConf.length) { - logError(SUBMODULE_NAME, 'Missing parameter bidders in moduleConfig'); - return false - } - const publisherId = deepAccess(moduleConfig, 'params.pubId'); - // Publisher Id - if (!isNumber(publisherId)) { - logError(SUBMODULE_NAME, 'Missing parameter pubId in moduleConfig'); - return false - } - return true -} - -export const captifySubmodule = { - name: SUBMODULE_NAME, - init: init, - setCaptifyTargeting -}; - -submodule(MODULE_NAME, captifySubmodule); diff --git a/modules/captifyRtdProvider.md b/modules/captifyRtdProvider.md deleted file mode 100644 index a1a8e9c273f..00000000000 --- a/modules/captifyRtdProvider.md +++ /dev/null @@ -1,68 +0,0 @@ -# Captify Real-Time Data Submodule - -# Overview - - Module Name: Captify Rtd Provider - Module Type: Rtd Provider - Layout: integrationExamples/gpt/captifyRtdProvider_example.html - Maintainer: prebid@captify.tech - -# Description - -Captify uses publisher first-party on-site search data to power machine learning algorithms to create a suite of -contextual based targeting solutions that activate in a cookieless environment. - -The RTD submodule allows bid requests to be classified by our live-classification service on the first ad call, -maximising value for publishers by increasing scale for advertisers. - -Segments will be attached to bid request objects sent to different SSPs in order to optimize targeting. - -Contact prebid@captify.tech for information. - -### Publisher Usage - -Compile the Captify RTD module into your Prebid build: - -`npm ci && gulp build --modules=rtdModule,appnexusBidAdapter,captifyRtdProvider` - -Add the Captify RTD provider to your Prebid config. - -```javascript -pbjs.setConfig({ - realTimeData: { - auctionDelay: 1000, - dataProviders: [ - { - name: "CaptifyRTDModule", - waitForIt: true, - params: { - pubId: 123456, - bidders: ['appnexus'], - } - } - ] - } -}); -``` - -### Parameter Description -This module is configured as part of the `realTimeData.dataProviders` object. - -| Name |Type | Description |Mandatory | Notes | -| :------------- | :------------ | :------------------------------------------------------------------ |:---------|:------------ | -| name | String | Real time data module name | yes | Always 'CaptifyRTDModule' | -| waitForIt | Boolean | Should be `true` if there's an `auctionDelay` defined (recommended) | no | Default `false` | -| params | Object | | | | -| params.pubId | Integer | Partner ID, required to get results and provided by Captify | yes | Use 123456 for tests and speak to your Captify account manager to receive your pubId | -| params.bidders | Array | List of bidders for which you would like data to be set | yes | Currently only 'appnexus' supported | -| params.url | String | Captify live-classification service url | no | Defaults to `https://live-classification.cpx.to/prebid-segments` - -### Testing - -To view an example of available segments returned by Captify: - -`gulp serve --modules=rtdModule,captifyRtdProvider,appnexusBidAdapter` - -and then point your browser at: - -`http://localhost:9999/integrationExamples/gpt/captifyRtdProvider_example.html?pbjs_debug=true` diff --git a/modules/carodaBidAdapter.js b/modules/carodaBidAdapter.js index 7267452dd4c..cb7b5fbe7c5 100644 --- a/modules/carodaBidAdapter.js +++ b/modules/carodaBidAdapter.js @@ -48,6 +48,7 @@ export const spec = { const eids = getFirstWithKey(validBidRequests, 'userIdAsEids'); const schain = getFirstWithKey(validBidRequests, 'schain'); const request = { + // TODO: fix auctionId leak: https://github.com/prebid/Prebid.js/issues/9781 auctionId: bidderRequest.auctionId, currency, hb_version: '$prebid.version$', diff --git a/modules/categoryTranslation.js b/modules/categoryTranslation.js index ba73aa01b85..eb6cb83730a 100644 --- a/modules/categoryTranslation.js +++ b/modules/categoryTranslation.js @@ -12,7 +12,7 @@ */ import {config} from '../src/config.js'; -import {hook, setupBeforeHookFnOnce} from '../src/hook.js'; +import {hook, setupBeforeHookFnOnce, ready} from '../src/hook.js'; import {ajax} from '../src/ajax.js'; import {logError, timestamp} from '../src/utils.js'; import {addBidResponse} from '../src/auction.js'; @@ -32,7 +32,8 @@ export const registerAdserver = hook('async', function(adServer) { initTranslation(url, DEFAULT_IAB_TO_FW_MAPPING_KEY); } }, 'registerAdserver'); -registerAdserver(); + +ready.then(() => registerAdserver()); export const getAdserverCategoryHook = timedBidResponseHook('categoryTranslation', function getAdserverCategoryHook(fn, adUnitCode, bid, reject) { if (!bid) { diff --git a/modules/ccxBidAdapter.js b/modules/ccxBidAdapter.js index 28f3fe3a166..b1fcb29e3d0 100644 --- a/modules/ccxBidAdapter.js +++ b/modules/ccxBidAdapter.js @@ -178,7 +178,7 @@ export const spec = { requestBody.imp = [] requestBody.site = _getSiteObj(bidderRequest) requestBody.device = _getDeviceObj() - requestBody.id = bidderRequest.bids[0].auctionId + requestBody.id = bidderRequest.bidderRequestId; requestBody.ext = {'ce': (storage.cookiesAreEnabled() ? 1 : 0)} // Attaching GDPR Consent Params diff --git a/modules/cleanioRtdProvider.js b/modules/cleanioRtdProvider.js index 09c5f722d7f..7d0f461108b 100644 --- a/modules/cleanioRtdProvider.js +++ b/modules/cleanioRtdProvider.js @@ -7,6 +7,7 @@ */ import { submodule } from '../src/hook.js'; +import { loadExternalScript } from '../src/adloader.js'; import { logError, generateUUID, insertElement } from '../src/utils.js'; import * as events from '../src/events.js'; import CONSTANTS from '../src/constants.json'; @@ -52,10 +53,7 @@ function pageInitStepPreloadScript(scriptURL) { * @param {string} scriptURL The script URL to add to the page for protection */ function pageInitStepProtectPage(scriptURL) { - const scriptElement = document.createElement('script'); - scriptElement.type = 'text/javascript'; - scriptElement.src = scriptURL; - insertElement(scriptElement); + loadExternalScript(scriptURL, 'clean.io'); } /** diff --git a/modules/cleanmedianetBidAdapter.js b/modules/cleanmedianetBidAdapter.js index ba72eec3d95..601a237baa8 100644 --- a/modules/cleanmedianetBidAdapter.js +++ b/modules/cleanmedianetBidAdapter.js @@ -1,13 +1,34 @@ -import {deepAccess, getDNT, inIframe, isArray, isNumber, logError, logWarn} from '../src/utils.js'; +import { + deepAccess, + deepSetValue, + getDNT, + inIframe, + isArray, + isFn, + isNumber, + isPlainObject, + isStr, + logError, + logWarn +} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {Renderer} from '../src/Renderer.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {includes} from '../src/polyfill.js'; +const ENDPOINTS = { + 'cleanmedianet': 'https://bidder.cleanmediaads.com' +}; + +const DEFAULT_TTL = 360; + export const helper = { - getTopWindowDomain: function (url) { - const domainStart = url.indexOf('://') + '://'.length; - return url.substring(domainStart, url.indexOf('/', domainStart) < 0 ? url.length : url.indexOf('/', domainStart)); + getTopFrame: function () { + try { + return window.top === window ? 1 : 0; + } catch (e) { + } + return 0; }, startsWith: function (str, search) { return str.substr(0, search.length) === search; @@ -23,53 +44,50 @@ export const helper = { } } return BANNER; + }, + getBidFloor(bid) { + if (!isFn(bid.getFloor)) { + return bid.params.bidfloor ? bid.params.bidfloor : null; + } + + let bidFloor = bid.getFloor({ + mediaType: '*', + size: '*', + currency: 'USD' + }); + + if (isPlainObject(bidFloor) && !isNaN(bidFloor.floor) && bidFloor.currency === 'USD') { + return bidFloor.floor; + } + + return null; } }; export const spec = { code: 'cleanmedianet', aliases: [], - supportedMediaTypes: [BANNER, VIDEO], + supportedMediaTypes: ['banner', 'video'], isBidRequestValid: function (bid) { - return ( - !!bid.params.supplyPartnerId && - typeof bid.params.supplyPartnerId === 'string' && - (typeof bid.params.bidfloor === 'undefined' || - typeof bid.params.bidfloor === 'number') && - (typeof bid.params['adpos'] === 'undefined' || - typeof bid.params['adpos'] === 'number') && - (typeof bid.params['protocols'] === 'undefined' || - Array.isArray(bid.params['protocols'])) && - (typeof bid.params.instl === 'undefined' || - bid.params.instl === 0 || - bid.params.instl === 1) - ); + return !!bid.params.supplyPartnerId && isStr(bid.params.supplyPartnerId) && + (!bid.params['rtbEndpoint'] || isStr(bid.params['rtbEndpoint'])) && + (!bid.params.bidfloor || isNumber(bid.params.bidfloor)) && + (!bid.params['adpos'] || isNumber(bid.params['adpos'])) && + (!bid.params['protocols'] || Array.isArray(bid.params['protocols'])) && + (!bid.params.instl || bid.params.instl === 0 || bid.params.instl === 1); }, buildRequests: function (validBidRequests, bidderRequest) { return validBidRequests.map(bidRequest => { - const { - adUnitCode, - auctionId, - mediaTypes, - params, - sizes, - transactionId - } = bidRequest; - const baseEndpoint = 'https://bidder.cleanmediaads.com'; - const rtbEndpoint = - `${baseEndpoint}/r/${ - params.supplyPartnerId - }/bidr?rformat=open_rtb&reqformat=rtb_json&bidder=prebid` + - (params.query ? '&' + params.query : ''); - let url = bidderRequest.refererInfo.page; - + const {adUnitCode, bidId, mediaTypes, params, sizes} = bidRequest; + const baseEndpoint = (params['rtbEndpoint'] || ENDPOINTS['cleanmedianet']).replace(/^http:/, 'https:'); + const rtbEndpoint = `${baseEndpoint}/r/${params.supplyPartnerId}/bidr?rformat=open_rtb&reqformat=rtb_json&bidder=prebid` + (params.query ? '&' + params.query : ''); const rtbBidRequest = { - id: auctionId, + id: bidId, site: { domain: bidderRequest.refererInfo.domain, - page: url, + page: bidderRequest.refererInfo.page, ref: bidderRequest.refererInfo.ref }, device: { @@ -81,44 +99,35 @@ export const spec = { }, imp: [], ext: {}, - user: { - ext: {} - } + user: {ext: {}}, + source: {ext: {}}, + regs: {ext: {}} }; - if ( - bidderRequest.gdprConsent && - bidderRequest.gdprConsent.consentString && - bidderRequest.gdprConsent.gdprApplies - ) { - rtbBidRequest.ext.gdpr_consent = { - consent_string: bidderRequest.gdprConsent.consentString, - consent_required: bidderRequest.gdprConsent.gdprApplies - }; - rtbBidRequest.regs = { - ext: { - gdpr: bidderRequest.gdprConsent.gdprApplies === true ? 1 : 0 - } - }; - rtbBidRequest.user = { - ext: { - consent: bidderRequest.gdprConsent.consentString - } - }; + const gdprConsent = getGdprConsent(bidderRequest); + rtbBidRequest.ext.gdpr_consent = gdprConsent; + deepSetValue(rtbBidRequest, 'regs.ext.gdpr', gdprConsent.consent_required === true ? 1 : 0); + deepSetValue(rtbBidRequest, 'user.ext.consent', gdprConsent.consent_string); + + if (validBidRequests[0].schain) { + deepSetValue(rtbBidRequest, 'source.ext.schain', validBidRequests[0].schain); + } + + if (bidderRequest && bidderRequest.uspConsent) { + deepSetValue(rtbBidRequest, 'regs.ext.us_privacy', bidderRequest.uspConsent); } const imp = { - id: transactionId, - instl: deepAccess(bidRequest.ortb2Imp, 'instl') === 1 || params.instl === 1 ? 1 : 0, + id: bidId, + instl: deepAccess(bidderRequest.ortb2Imp, 'instl') === 1 || params.instl === 1 ? 1 : 0, tagid: adUnitCode, - bidfloor: 0, + bidfloor: helper.getBidFloor(bidRequest) || 0, bidfloorcur: 'USD', secure: 1 }; const hasFavoredMediaType = - params.favoredMediaType && - includes(this.supportedMediaTypes, params.favoredMediaType); + params.favoredMediaType && includes(this.supportedMediaTypes, params.favoredMediaType); if (!mediaTypes || mediaTypes.banner) { if (!hasFavoredMediaType || params.favoredMediaType === BANNER) { @@ -126,7 +135,7 @@ export const spec = { banner: { w: sizes.length ? sizes[0][0] : 300, h: sizes.length ? sizes[0][1] : 250, - pos: params.pos || 0, + pos: deepAccess(bidderRequest, 'mediaTypes.banner.pos') || params.pos || 0, topframe: inIframe() ? 0 : 1 } }); @@ -136,15 +145,25 @@ export const spec = { if (mediaTypes && mediaTypes.video) { if (!hasFavoredMediaType || params.favoredMediaType === VIDEO) { - let videoImp = { + const playerSize = mediaTypes.video.playerSize || sizes; + const videoImp = Object.assign({}, imp, { video: { - protocols: params.protocols || [1, 2, 3, 4, 5, 6], - pos: params.pos || 0, - ext: {context: mediaTypes.video.context} + protocols: bidRequest.mediaTypes.video.protocols || params.protocols || [1, 2, 3, 4, 5, 6], + pos: deepAccess(bidRequest, 'mediaTypes.video.pos') || params.pos || 0, + ext: { + context: mediaTypes.video.context + }, + mimes: bidRequest.mediaTypes.video.mimes, + maxduration: bidRequest.mediaTypes.video.maxduration, + api: bidRequest.mediaTypes.video.api, + skip: bidRequest.mediaTypes.video.skip || bidRequest.params.video.skip, + placement: bidRequest.mediaTypes.video.placement || bidRequest.params.video.placement, + minduration: bidRequest.mediaTypes.video.minduration || bidRequest.params.video.minduration, + playbackmethod: bidRequest.mediaTypes.video.playbackmethod || bidRequest.params.video.playbackmethod, + startdelay: bidRequest.mediaTypes.video.startdelay || bidRequest.params.video.startdelay } - }; + }); - let playerSize = mediaTypes.video.playerSize || sizes; if (isArray(playerSize[0])) { videoImp.video.w = playerSize[0][0]; videoImp.video.h = playerSize[0][1]; @@ -156,11 +175,20 @@ export const spec = { videoImp.video.h = 250; } - videoImp = Object.assign({}, imp, videoImp); rtbBidRequest.imp.push(videoImp); } } + let eids = []; + if (bidRequest && bidRequest.userId) { + addExternalUserId(eids, deepAccess(bidRequest, `userId.id5id.uid`), 'id5-sync.com', 'ID5ID'); + addExternalUserId(eids, deepAccess(bidRequest, `userId.tdid`), 'adserver.org', 'TDID'); + addExternalUserId(eids, deepAccess(bidRequest, `userId.idl_env`), 'liveramp.com', 'idl'); + } + if (eids.length > 0) { + rtbBidRequest.user.ext.eids = eids; + } + if (rtbBidRequest.imp.length === 0) { return; } @@ -181,10 +209,7 @@ export const spec = { return []; } - const bids = response.seatbid.reduce( - (acc, seatBid) => acc.concat(seatBid.bid), - [] - ); + const bids = response.seatbid.reduce((acc, seatBid) => acc.concat(seatBid.bid), []); let outBids = []; bids.forEach(bid => { @@ -193,75 +218,81 @@ export const spec = { cpm: bid.price, width: bid.w, height: bid.h, - ttl: 360, + ttl: DEFAULT_TTL, creativeId: bid.crid || bid.adid, netRevenue: true, currency: bid.cur || response.cur, - mediaType: helper.getMediaType(bid) + mediaType: helper.getMediaType(bid), }; - if ( - deepAccess( - bidRequest.bidRequest, - 'mediaTypes.' + outBid.mediaType - ) - ) { + if (bid.adomain && bid.adomain.length) { + outBid.meta = { + advertiserDomains: bid.adomain + } + } + + if (deepAccess(bidRequest.bidRequest, 'mediaTypes.' + outBid.mediaType)) { if (outBid.mediaType === BANNER) { outBids.push(Object.assign({}, outBid, {ad: bid.adm})); } else if (outBid.mediaType === VIDEO) { - const context = deepAccess( - bidRequest.bidRequest, - 'mediaTypes.video.context' - ); - outBids.push( - Object.assign({}, outBid, { - vastUrl: bid.ext.vast_url, - vastXml: bid.adm, - renderer: - context === 'outstream' - ? newRenderer(bidRequest.bidRequest, bid) - : undefined - }) - ); + const context = deepAccess(bidRequest.bidRequest, 'mediaTypes.video.context'); + outBids.push(Object.assign({}, outBid, { + vastUrl: bid.ext.vast_url, + vastXml: bid.adm, + renderer: context === 'outstream' ? newRenderer(bidRequest.bidRequest, bid) : undefined + })); } } }); return outBids; }, - getUserSyncs: function (syncOptions, serverResponses, gdprConsent) { + getUserSyncs: function (syncOptions, serverResponses, gdprConsent, uspConsent) { const syncs = []; - const gdprApplies = - gdprConsent && typeof gdprConsent.gdprApplies === 'boolean' - ? gdprConsent.gdprApplies - : false; - const suffix = gdprApplies - ? 'gc=' + encodeURIComponent(gdprConsent.consentString) - : 'gc=missing'; + let gdprApplies = false; + let consentString = ''; + let uspConsentString = ''; + + if (gdprConsent && (typeof gdprConsent.gdprApplies === 'boolean')) { + gdprApplies = gdprConsent.gdprApplies; + } + let gdpr = gdprApplies ? 1 : 0; + + if (gdprApplies && gdprConsent.consentString) { + consentString = encodeURIComponent(gdprConsent.consentString); + } + + if (uspConsent) { + uspConsentString = encodeURIComponent(uspConsent); + } + + const macroValues = { + gdpr: gdpr, + consent: consentString, + uspConsent: uspConsentString + }; + serverResponses.forEach(resp => { if (resp.body) { const bidResponse = resp.body; if (bidResponse.ext && Array.isArray(bidResponse.ext['utrk'])) { - bidResponse.ext['utrk'].forEach(pixel => { - const url = - pixel.url + - (pixel.url.indexOf('?') > 0 ? '&' + suffix : '?' + suffix); - return syncs.push({type: pixel.type, url}); - }); + bidResponse.ext['utrk'] + .forEach(pixel => { + const url = replaceMacros(pixel.url, macroValues); + syncs.push({type: pixel.type, url}); + }); } + if (Array.isArray(bidResponse.seatbid)) { bidResponse.seatbid.forEach(seatBid => { if (Array.isArray(seatBid.bid)) { seatBid.bid.forEach(bid => { if (bid.ext && Array.isArray(bid.ext['utrk'])) { - bid.ext['utrk'].forEach(pixel => { - const url = - pixel.url + - (pixel.url.indexOf('?') > 0 - ? '&' + suffix - : '?' + suffix); - return syncs.push({type: pixel.type, url}); - }); + bid.ext['utrk'] + .forEach(pixel => { + const url = replaceMacros(pixel.url, macroValues); + syncs.push({type: pixel.type, url}); + }); } }); } @@ -269,18 +300,16 @@ export const spec = { } } }); + return syncs; } }; function newRenderer(bidRequest, bid, rendererOptions = {}) { const renderer = Renderer.install({ - url: - (bidRequest.params && bidRequest.params.rendererUrl) || - (bid.ext && bid.ext.renderer_url) || - 'https://s.wlplayer.com/video/latest/renderer.js', + url: (bidRequest.params && bidRequest.params.rendererUrl) || (bid.ext && bid.ext.renderer_url) || 'https://s.gamoshi.io/video/latest/renderer.js', config: rendererOptions, - loaded: false + loaded: false, }); try { renderer.setRender(renderOutstream); @@ -300,10 +329,9 @@ function renderOutstream(bid) { width: bid.width, height: bid.height, events: { - ALL_ADS_COMPLETED: () => - window.setTimeout(() => { - window['GamoshiPlayer'].removeAd(unitId); - }, 300) + ALL_ADS_COMPLETED: () => window.setTimeout(() => { + window['GamoshiPlayer'].removeAd(unitId); + }, 300) }, vastUrl: bid.vastUrl, vastXml: bid.vastXml @@ -311,4 +339,41 @@ function renderOutstream(bid) { }); } +function addExternalUserId(eids, value, source, rtiPartner) { + if (isStr(value)) { + eids.push({ + source, + uids: [{ + id: value, + ext: { + rtiPartner + } + }] + }); + } +} + +function replaceMacros(url, macros) { + return url + .replace('[GDPR]', macros.gdpr) + .replace('[CONSENT]', macros.consent) + .replace('[US_PRIVACY]', macros.uspConsent); +} + +function getGdprConsent(bidderRequest) { + const gdprConsent = bidderRequest.gdprConsent; + + if (gdprConsent && gdprConsent.consentString && gdprConsent.gdprApplies) { + return { + consent_string: gdprConsent.consentString, + consent_required: gdprConsent.gdprApplies + }; + } + + return { + consent_required: false, + consent_string: '', + }; +} + registerBidder(spec); diff --git a/modules/cleanmedianetBidAdapter.md b/modules/cleanmedianetBidAdapter.md index f2bc8feb0f0..ee4e049e8d6 100644 --- a/modules/cleanmedianetBidAdapter.md +++ b/modules/cleanmedianetBidAdapter.md @@ -1,45 +1,49 @@ # Overview ``` -Module Name: Clean Media Net Adapter +Module Name: CleanMedia Bid Adapter Module Type: Bidder Adapter -Maintainer: dev@cleanmedia.net +Maintainer: dev@CleanMedia.net ``` # Description -Connects to Clean Media Net's Programmatic advertising platform as a service. +Connects to CleanMedia's Programmatic advertising platform as a service. -Clean Media bid adapter supports Banner & Video (Instream and Outstream). -The *only* required parameter (in the `params` section) is the `supplyPartnerId` parameter. +CleanMedia bid adapter supports Banner & Outstream Video. The *only* required parameter (in the `params` section) is the `supplyPartnerId` parameter. # Test Parameters ``` var adUnits = [ - // Banner adUnit + + // Banner adUnit { code: 'banner-div', sizes: [[300, 250]], bids: [{ bidder: 'cleanmedianet', params: { - // ID of the supply partner you created in the Clean Media Net dashboard + + // ID of the supply partner you created in the CleanMedia dashboard supplyPartnerId: '1253', - // OPTIONAL: custom bid floor + + // OPTIONAL: custom bid floor bidfloor: 0.01, - // OPTIONAL: if you know the ad position on the page, specify it here + + // OPTIONAL: if you know the ad position on the page, specify it here // (this corresponds to "Ad Position" in OpenRTB 2.3, section 5.4) //adpos: 1, - // OPTIONAL: whether this is an interstitial placement (0 or 1) + + // OPTIONAL: whether this is an interstitial placement (0 or 1) // (see "instl" property in "Imp" object in the OpenRTB 2.3, section 3.2.2) //instl: 0 } }] }, - // Video outstream adUnit + + // Video outstream adUnit { code: 'video-outstream', - sizes: [[300, 250]], mediaTypes: { video: { context: 'outstream', @@ -47,20 +51,62 @@ var adUnits = [ } }, bids: [ { - bidder: 'cleanmedianet', + bidder: 'CleanMedia', params: { - // ID of the supply partner you created in the dashboard + + // ID of the supply partner you created in the dashboard supplyPartnerId: '1254', - // OPTIONAL: custom bid floor + + // OPTIONAL: custom bid floor bidfloor: 0.01, - // OPTIONAL: if you know the ad position on the page, specify it here + + // OPTIONAL: if you know the ad position on the page, specify it here // (this corresponds to "Ad Position" in OpenRTB 2.3, section 5.4) //adpos: 1, - // OPTIONAL: whether this is an interstitial placement (0 or 1) + + // OPTIONAL: whether this is an interstitial placement (0 or 1) // (see "instl" property in "Imp" object in the OpenRTB 2.3, section 3.2.2) //instl: 0 } }] - } + }, + + // Multi-Format adUnit + { + code: 'banner-div', + mediaTypes: { + video: { + context: 'outstream', + playerSize: [300, 250] + }, + banner: { + sizes: [[300, 250]] + } + }, + bids: [{ + bidder: 'CleanMedia', + params: { + + // ID of the supply partner you created in the CleanMedia dashboard + supplyPartnerId: '1253', + + // OPTIONAL: custom bid floor + bidfloor: 0.01, + + // OPTIONAL: if you know the ad position on the page, specify it here + // (this corresponds to "Ad Position" in OpenRTB 2.3, section 5.4) + //adpos: 1, + + // OPTIONAL: whether this is an interstitial placement (0 or 1) + // (see "instl" property in "Imp" object in the OpenRTB 2.3, section 3.2.2) + //instl: 0, + + // OPTIONAL: enable enforcement bids of a specific media type (video, banner) + // in this ad placement + // query: 'key1=value1&k2=value2', + // favoredMediaType: 'video', + } + }] + }, ]; ``` diff --git a/modules/codefuelBidAdapter.js b/modules/codefuelBidAdapter.js index bde168a79e3..2548b20189b 100644 --- a/modules/codefuelBidAdapter.js +++ b/modules/codefuelBidAdapter.js @@ -58,7 +58,7 @@ export const spec = { }); const request = { - id: bidderRequest.auctionId, + id: bidderRequest.bidderRequestId, site: { page, domain, publisher }, device: { ua, devicetype }, source: { fd: 1 }, diff --git a/modules/cointrafficBidAdapter.js b/modules/cointrafficBidAdapter.js index ce366cbecc8..380e1f5fc77 100644 --- a/modules/cointrafficBidAdapter.js +++ b/modules/cointrafficBidAdapter.js @@ -4,19 +4,20 @@ import { BANNER } from '../src/mediaTypes.js' import { config } from '../src/config.js' const BIDDER_CODE = 'cointraffic'; -const ENDPOINT_URL = 'https://apps-pbd.ctengine.io/pb/tmp'; +const ENDPOINT_URL = 'https://apps-pbd.ctraffic.io/pb/tmp'; const DEFAULT_CURRENCY = 'EUR'; const ALLOWED_CURRENCIES = [ 'EUR', 'USD', 'JPY', 'BGN', 'CZK', 'DKK', 'GBP', 'HUF', 'PLN', 'RON', 'SEK', 'CHF', 'ISK', 'NOK', 'HRK', 'RUB', 'TRY', 'AUD', 'BRL', 'CAD', 'CNY', 'HKD', 'IDR', 'ILS', 'INR', 'KRW', 'MXN', 'MYR', 'NZD', 'PHP', 'SGD', 'THB', 'ZAR', ]; +/** @type {BidderSpec} */ export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER], /** - * Determines whether or not the given bid request is valid. + * Determines whether the given bid request is valid. * * @param {BidRequest} bid The bid params to validate. * @return boolean True if this is a valid bid, and false otherwise. @@ -50,8 +51,7 @@ export const spec = { currency: currency, sizes: sizes, bidId: bidRequest.bidId, - // TODO: is 'page' the right value here? - referer: bidderRequest.refererInfo.page, + referer: bidderRequest.refererInfo.ref, }; return { diff --git a/modules/coinzillaBidAdapter.js b/modules/coinzillaBidAdapter.js index 7e9fb964a87..15731423c49 100644 --- a/modules/coinzillaBidAdapter.js +++ b/modules/coinzillaBidAdapter.js @@ -1,5 +1,4 @@ import { parseSizesInput } from '../src/utils.js'; -import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; const BIDDER_CODE = 'coinzilla'; @@ -78,7 +77,7 @@ export const spec = { dealId: dealId, currency: currency, netRevenue: netRevenue, - ttl: config.getConfig('_bidderTimeout'), + ttl: bidRequest.timeout, referrer: referrer, ad: response.ad, mediaType: response.mediaType, diff --git a/modules/colossussspBidAdapter.js b/modules/colossussspBidAdapter.js index 082fb0ac4db..b1ee8875422 100644 --- a/modules/colossussspBidAdapter.js +++ b/modules/colossussspBidAdapter.js @@ -116,6 +116,15 @@ export const spec = { request.gdpr_consent = bidderRequest.gdprConsent.consentString || 'ALL'; request.gdpr_require = bidderRequest.gdprConsent.gdprApplies ? 1 : 0; } + + // Add GPP consent + if (bidderRequest.gppConsent) { + request.gpp = bidderRequest.gppConsent.gppString; + request.gpp_sid = bidderRequest.gppConsent.applicableSections; + } else if (bidderRequest.ortb2?.regs?.gpp) { + request.gpp = bidderRequest.ortb2.regs.gpp; + request.gpp_sid = bidderRequest.ortb2.regs.gpp_sid; + } } for (let i = 0; i < validBidRequests.length; i++) { @@ -125,7 +134,7 @@ export const spec = { placementId: bid.params.placement_id, groupId: bid.params.group_id, bidId: bid.bidId, - tid: bid.transactionId, + tid: bid.ortb2Imp?.ext?.tid, eids: [], floor: {} }; diff --git a/modules/compassBidAdapter.js b/modules/compassBidAdapter.js index a89c4c617b6..addcdfebb27 100644 --- a/modules/compassBidAdapter.js +++ b/modules/compassBidAdapter.js @@ -155,7 +155,7 @@ export const spec = { coppa: config.getConfig('coppa') === true ? 1 : 0, ccpa: bidderRequest.uspConsent || undefined, gdpr: bidderRequest.gdprConsent || undefined, - tmax: config.getConfig('bidderTimeout') + tmax: bidderRequest.timeout }; const len = validBidRequests.length; diff --git a/modules/conceptxBidAdapter.js b/modules/conceptxBidAdapter.js new file mode 100644 index 00000000000..127b049bc99 --- /dev/null +++ b/modules/conceptxBidAdapter.js @@ -0,0 +1,73 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER } from '../src/mediaTypes.js'; +// import { logError, logInfo, logWarn, parseUrl } from '../src/utils.js'; + +const BIDDER_CODE = 'conceptx'; +let ENDPOINT_URL = 'https://conceptx.cncpt-central.com/openrtb'; +// const LOG_PREFIX = 'ConceptX: '; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + isBidRequestValid: function (bid) { + return !!(bid.bidId); + }, + + buildRequests: function (validBidRequests, bidderRequest) { + // logWarn(LOG_PREFIX + 'all native assets containing URL should be sent as placeholders with sendId(icon, image, clickUrl, displayUrl, privacyLink, privacyIcon)'); + const requests = []; + + if (bidderRequest && bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies) { + ENDPOINT_URL += '?gdpr_applies=' + bidderRequest.gdprConsent.gdprApplies; + ENDPOINT_URL += '&consentString=' + bidderRequest.gdprConsent.consentString; + } + for (var i = 0; i < validBidRequests.length; i++) { + const requestParent = { adUnits: [], meta: {} }; + const bid = validBidRequests[i] + const { adUnitCode, auctionId, bidId, bidder, bidderRequestId, ortb2 } = bid + requestParent.meta = { adUnitCode, auctionId, bidId, bidder, bidderRequestId, ortb2 } + + const { site, adunit } = bid.params + const adUnit = { site, adunit, targetId: bid.bidId } + if (bid.mediaTypes && bid.mediaTypes.banner && bid.mediaTypes.banner.sizes) adUnit.dimensions = bid.mediaTypes.banner.sizes + requestParent.adUnits.push(adUnit); + requests.push({ + method: 'POST', + url: ENDPOINT_URL, + options: { + withCredentials: false, + }, + data: JSON.stringify(requestParent), + }); + } + + return requests; + }, + + interpretResponse: function (serverResponse, bidRequest) { + const bidResponses = []; + const bidResponsesFromServer = serverResponse.body.bidResponses; + if (Array.isArray(bidResponsesFromServer) && bidResponsesFromServer.length === 0) { + return bidResponses + } + const firstBid = bidResponsesFromServer[0] + const firstSeat = firstBid.ads[0] + const bidResponse = { + requestId: firstSeat.requestId, + cpm: firstSeat.cpm, + width: firstSeat.width, + height: firstSeat.height, + creativeId: firstSeat.creativeId, + dealId: firstSeat.dealId, + currency: firstSeat.currency, + netRevenue: true, + ttl: firstSeat.ttl, + referrer: firstSeat.referrer, + ad: firstSeat.html + }; + bidResponses.push(bidResponse); + return bidResponses; + }, + +} +registerBidder(spec); diff --git a/modules/conceptxBidAdapter.md b/modules/conceptxBidAdapter.md new file mode 100644 index 00000000000..1464c04025a --- /dev/null +++ b/modules/conceptxBidAdapter.md @@ -0,0 +1,36 @@ +# Overview + +``` +Module Name: ConceptX Bidder Adapter +Module Type: Bidder Adapter +Maintainer: info@concept.dk +``` + +# Description + +ConceptX Bidder Adapter for Prebid.js. +Only Banner format is supported. + +# Test Parameters +``` + var adUnits = [ + { + code: "test-div", + mediaTypes: { + banner: { + sizes: [[980, 180]] + } + }, + bids: [ + { + bidder: "conceptx", + params: { + site: "example", + adunit: "some-id-3", + } + }, + ] + }, + + ]; +``` diff --git a/modules/concertBidAdapter.js b/modules/concertBidAdapter.js index dcea60d5231..bf4079322ff 100644 --- a/modules/concertBidAdapter.js +++ b/modules/concertBidAdapter.js @@ -5,7 +5,6 @@ import { hasPurpose1Consent } from '../src/utils/gpdr.js'; const BIDDER_CODE = 'concert'; const CONCERT_ENDPOINT = 'https://bids.concert.io'; -const USER_SYNC_URL = 'https://cdn.concert.io/lib/bids/sync.html'; export const spec = { code: BIDDER_CODE, @@ -43,14 +42,22 @@ export const spec = { pageUrl: bidderRequest.refererInfo.page, screen: [window.screen.width, window.screen.height].join('x'), debug: debugTurnedOn(), - uid: getUid(bidderRequest), + uid: getUid(bidderRequest, validBidRequests), optedOut: hasOptedOutOfPersonalization(), - adapterVersion: '1.1.1', + adapterVersion: '1.2.0', uspConsent: bidderRequest.uspConsent, - gdprConsent: bidderRequest.gdprConsent + gdprConsent: bidderRequest.gdprConsent, + gppConsent: bidderRequest.gppConsent, } }; + if (!payload.meta.gppConsent && bidderRequest.ortb2?.regs?.gpp) { + payload.meta.gppConsent = { + gppString: bidderRequest.ortb2.regs.gpp, + applicableSections: bidderRequest.ortb2.regs.gpp_sid + } + } + payload.slots = validBidRequests.map(bidRequest => { collectEid(eids, bidRequest); const adUnitElement = document.getElementById(bidRequest.adUnitCode) @@ -59,7 +66,7 @@ export const spec = { let slot = { name: bidRequest.adUnitCode, bidId: bidRequest.bidId, - transactionId: bidRequest.transactionId, + transactionId: bidRequest.ortb2Imp?.ext?.tid, sizes: bidRequest.params.sizes || bidRequest.sizes, partnerId: bidRequest.params.partnerId, slotType: bidRequest.params.slotType, @@ -124,38 +131,6 @@ export const spec = { return bidResponses; }, - /** - * Register the user sync pixels which should be dropped after the auction. - * - * @param {SyncOptions} syncOptions Which user syncs are allowed? - * @param {ServerResponse[]} serverResponses List of server's responses. - * @param {gdprConsent} object GDPR consent object. - * @param {uspConsent} string US Privacy String. - * @return {UserSync[]} The user syncs which should be dropped. - */ - getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent) { - const syncs = []; - if (syncOptions.iframeEnabled && !hasOptedOutOfPersonalization()) { - let params = []; - - if (gdprConsent && (typeof gdprConsent.gdprApplies === 'boolean')) { - params.push(`gdpr_applies=${gdprConsent.gdprApplies ? '1' : '0'}`); - } - if (gdprConsent && (typeof gdprConsent.consentString === 'string')) { - params.push(`gdpr_consent=${gdprConsent.consentString}`); - } - if (uspConsent && (typeof uspConsent === 'string')) { - params.push(`usp_consent=${uspConsent}`); - } - - syncs.push({ - type: 'iframe', - url: USER_SYNC_URL + (params.length > 0 ? `?${params.join('&')}` : '') - }); - } - return syncs; - }, - /** * Register bidder specific code, which will execute if bidder timed out after an auction * @param {data} Containing timeout specific data @@ -183,16 +158,23 @@ export const storage = getStorageManager({bidderCode: BIDDER_CODE}); /** * Check or generate a UID for the current user. */ -function getUid(bidderRequest) { +function getUid(bidderRequest, validBidRequests) { if (hasOptedOutOfPersonalization() || !consentAllowsPpid(bidderRequest)) { return false; } - const sharedId = deepAccess(bidderRequest, 'userId._sharedid.id'); + /** + * check for shareId or pubCommonId before generating a new one + * sharedId: @see https://docs.prebid.org/dev-docs/modules/userId.html + * pubCid (no longer supported): @see https://docs.prebid.org/dev-docs/modules/pubCommonId.html#adapter-integration + */ + const sharedId = + deepAccess(validBidRequests[0], 'userId.sharedid.id') || + deepAccess(validBidRequests[0], 'userId.pubcid') + const pubCid = deepAccess(validBidRequests[0], 'crumbs.pubcid'); - if (sharedId) { - return sharedId; - } + if (sharedId) return sharedId; + if (pubCid) return pubCid; const LEGACY_CONCERT_UID_KEY = 'c_uid'; const CONCERT_UID_KEY = 'vmconcert_uid'; @@ -229,16 +211,24 @@ function hasOptedOutOfPersonalization() { * @param {BidderRequest} bidderRequest Object which contains any data consent signals */ function consentAllowsPpid(bidderRequest) { - /* NOTE: We can't easily test GDPR consent, without the - * `consent-string` npm module; so will have to rely on that - * happening on the bid-server. */ - const uspConsent = !(bidderRequest?.uspConsent === 'string' && + let uspConsentAllows = true; + + // if a us privacy string was provided, but they explicitly opted out + if ( + typeof bidderRequest?.uspConsent === 'string' && bidderRequest?.uspConsent[0] === '1' && - bidderRequest?.uspConsent[2].toUpperCase() === 'Y'); + bidderRequest?.uspConsent[2].toUpperCase() === 'Y' // user has opted-out + ) { + uspConsentAllows = false; + } - const gdprConsent = bidderRequest?.gdprConsent && hasPurpose1Consent(bidderRequest?.gdprConsent); + /* + * True if the gdprConsent is null-y; or GDPR does not apply; or if purpose 1 consent was given. + * Much more nuanced GDPR requirements are tested on the bid server using the @iabtcf/core npm module; + */ + const gdprConsentAllows = hasPurpose1Consent(bidderRequest?.gdprConsent); - return (uspConsent || gdprConsent); + return (uspConsentAllows && gdprConsentAllows); } function collectEid(eids, bid) { diff --git a/modules/confiantRtdProvider.js b/modules/confiantRtdProvider.js index 2c13ad87d75..6b1066a20f1 100644 --- a/modules/confiantRtdProvider.js +++ b/modules/confiantRtdProvider.js @@ -44,7 +44,7 @@ function setupPage(config) { if (config?.params?.shouldEmitBillableEvent) { if (window.frames['cnftComm']) { - subscribeToConfiantCommFrame(window); + subscribeToConfiantCommFrame(window, propertyId); } else { setUpMutationObserver(); } @@ -58,8 +58,8 @@ function setupPage(config) { * Subscribe to window's message events to report Billable events * @param {Window} targetWindow window instance to subscribe to */ -function subscribeToConfiantCommFrame(targetWindow) { - targetWindow.addEventListener('message', reportBillableEvents); +function subscribeToConfiantCommFrame(targetWindow, propertyId) { + targetWindow.addEventListener('message', getEventHandlerFunction(propertyId)); } let mutationObserver; @@ -86,14 +86,18 @@ function setUpMutationObserver() { /** * Emit billable event when Confiant integration reports that it has monitored an impression */ -function reportBillableEvents (e) { - events.emit(CONSTANTS.EVENTS.BILLABLE_EVENT, { - auctionId: e.data.auctionId, - billingId: generateUUID(), - transactionId: e.data.transactionId, - type: 'impression', - vendor: 'confiant' - }); +function getEventHandlerFunction(propertyId) { + return function reportBillableEvent(e) { + if (e.data.type.indexOf('cnft:reportBillableEvent:' + propertyId) > -1) { + events.emit(CONSTANTS.EVENTS.BILLABLE_EVENT, { + auctionId: e.data.auctionId, + billingId: generateUUID(), + transactionId: e.data.transactionId, + type: 'impression', + vendor: 'confiant' + }); + } + } } /** @@ -123,6 +127,5 @@ export default { setupPage, subscribeToConfiantCommFrame, setUpMutationObserver, - reportBillableEvents, registerConfiantSubmodule }; diff --git a/modules/connectIdSystem.js b/modules/connectIdSystem.js index 38c9c00b0ed..1caff49199d 100644 --- a/modules/connectIdSystem.js +++ b/modules/connectIdSystem.js @@ -8,14 +8,124 @@ import {ajax} from '../src/ajax.js'; import {submodule} from '../src/hook.js'; import {includes} from '../src/polyfill.js'; -import {formatQS, logError} from '../src/utils.js'; +import {getRefererInfo} from '../src/refererDetection.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {formatQS, isPlainObject, logError, parseUrl} from '../src/utils.js'; +import {uspDataHandler, gppDataHandler} from '../src/adapterManager.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; const MODULE_NAME = 'connectId'; +const STORAGE_EXPIRY_DAYS = 365; +const STORAGE_DURATION = 60 * 60 * 24 * 1000 * STORAGE_EXPIRY_DAYS; +const ID_EXPIRY_DAYS = 14; +const VALID_ID_DURATION = 60 * 60 * 24 * 1000 * ID_EXPIRY_DAYS; +const PUID_EXPIRY_DAYS = 30; +const PUID_EXPIRY = 60 * 60 * 24 * 1000 * PUID_EXPIRY_DAYS; const VENDOR_ID = 25; const PLACEHOLDER = '__PIXEL_ID__'; const UPS_ENDPOINT = `https://ups.analytics.yahoo.com/ups/${PLACEHOLDER}/fed`; const OVERRIDE_OPT_OUT_KEY = 'connectIdOptOut'; const INPUT_PARAM_KEYS = ['pixelId', 'he', 'puid']; +export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); + +/** + * @function + * @param {Object} obj + */ +function storeObject(obj) { + const expires = Date.now() + STORAGE_DURATION; + if (storage.cookiesAreEnabled()) { + setEtldPlusOneCookie(MODULE_NAME, JSON.stringify(obj), new Date(expires), getSiteHostname()); + } + if (storage.localStorageIsEnabled()) { + storage.setDataInLocalStorage(MODULE_NAME, JSON.stringify(obj)); + } +} + +/** + * Attempts to store a cookie on eTLD + 1 + * + * @function + * @param {String} key + * @param {String} value + * @param {Date} expirationDate + * @param {String} hostname + */ +function setEtldPlusOneCookie(key, value, expirationDate, hostname) { + const subDomains = hostname.split('.'); + for (let i = 0; i < subDomains.length; ++i) { + const domain = subDomains.slice(subDomains.length - i - 1, subDomains.length).join('.'); + try { + storage.setCookie(key, value, expirationDate.toUTCString(), null, '.' + domain); + const storedCookie = storage.getCookie(key); + if (storedCookie && storedCookie === value) { + break; + } + } catch (error) {} + } +} + +function getIdFromCookie() { + if (storage.cookiesAreEnabled()) { + try { + return JSON.parse(storage.getCookie(MODULE_NAME)); + } catch {} + } + return null; +} + +function getIdFromLocalStorage() { + if (storage.localStorageIsEnabled()) { + let storedIdData = storage.getDataFromLocalStorage(MODULE_NAME); + if (storedIdData) { + try { + storedIdData = JSON.parse(storedIdData); + } catch (e) { + logError(`${MODULE_NAME} module: error while reading the local storage data.`); + } + if (isPlainObject(storedIdData) && storedIdData.__expires && + storedIdData.__expires <= Date.now()) { + storage.removeDataFromLocalStorage(MODULE_NAME); + return null; + } + return storedIdData; + } + } + return null; +} + +function syncLocalStorageToCookie() { + if (!storage.cookiesAreEnabled()) { + return; + } + const value = getIdFromLocalStorage(); + const newCookieExpireTime = Date.now() + STORAGE_DURATION; + setEtldPlusOneCookie(MODULE_NAME, JSON.stringify(value), new Date(newCookieExpireTime), getSiteHostname()); +} + +function isStale(storedIdData) { + if (isPlainObject(storedIdData) && storedIdData.lastSynced && + (storedIdData.lastSynced + VALID_ID_DURATION) <= Date.now()) { + return true; + } + return false; +} + +function getStoredId() { + let storedId = getIdFromCookie(); + if (!storedId) { + storedId = getIdFromLocalStorage(); + if (storedId && !isStale(storedId)) { + syncLocalStorageToCookie(); + } + } + return storedId; +} + +function getSiteHostname() { + const pageInfo = parseUrl(getRefererInfo().page); + return pageInfo.hostname; +} /** @type {Submodule} */ export const connectIdSubmodule = { @@ -37,8 +147,8 @@ export const connectIdSubmodule = { if (connectIdSubmodule.userHasOptedOut()) { return undefined; } - return (typeof value === 'object' && value.connectid) - ? {connectId: value.connectid} : undefined; + return (isPlainObject(value) && (value.connectId || value.connectid)) + ? {connectId: value.connectId || value.connectid} : undefined; }, /** * Gets the Yahoo ConnectID @@ -52,23 +162,53 @@ export const connectIdSubmodule = { return; } const params = config.params || {}; - if (!params || (typeof params.he !== 'string' && typeof params.puid !== 'string') || + if (!params || (typeof params.pixelId === 'undefined' && typeof params.endpoint === 'undefined')) { - logError('The connectId submodule requires the \'pixelId\' and at least one of the \'he\' ' + - 'or \'puid\' parameters to be defined.'); + logError(`${MODULE_NAME} module: configuration requires the 'pixelId'.`); return; } + const storedId = getStoredId(); + + let shouldResync = isStale(storedId); + + if (storedId) { + if (isPlainObject(storedId) && storedId.puid && storedId.lastUsed && !params.puid && + (storedId.lastUsed + PUID_EXPIRY) <= Date.now()) { + delete storedId.puid; + shouldResync = true; + } + if ((params.he && params.he !== storedId.he) || + (params.puid && params.puid !== storedId.puid)) { + shouldResync = true; + } + if (!shouldResync) { + storedId.lastUsed = Date.now(); + storeObject(storedId); + return {id: storedId}; + } + } + + const uspString = uspDataHandler.getConsentData() || ''; const data = { + v: '1', '1p': includes([1, '1', true], params['1p']) ? '1' : '0', gdpr: connectIdSubmodule.isEUConsentRequired(consentData) ? '1' : '0', - gdpr_consent: connectIdSubmodule.isEUConsentRequired(consentData) ? consentData.gdpr.consentString : '', - us_privacy: consentData && consentData.uspConsent ? consentData.uspConsent : '' + gdpr_consent: connectIdSubmodule.isEUConsentRequired(consentData) ? consentData.consentString : '', + us_privacy: uspString }; - if (connectIdSubmodule.isUnderGPPJurisdiction(consentData)) { - data.gpp = consentData.gppConsent.gppString; - data.gpp_sid = encodeURIComponent(consentData.gppConsent.applicableSections.join(',')); + const gppConsent = gppDataHandler.getConsentData(); + if (gppConsent) { + data.gpp = `${gppConsent.gppString ? gppConsent.gppString : ''}`; + if (Array.isArray(gppConsent.applicableSections)) { + data.gpp_sid = gppConsent.applicableSections.join(','); + } + } + + let topmostLocation = getRefererInfo().topmostLocation; + if (typeof topmostLocation === 'string') { + data.url = topmostLocation.split('?')[0]; } INPUT_PARAM_KEYS.forEach(key => { @@ -77,6 +217,14 @@ export const connectIdSubmodule = { } }); + const hashedEmail = params.he || storedId?.he; + if (hashedEmail) { + data.he = hashedEmail; + } + if (!data.puid && storedId?.puid) { + data.puid = storedId.puid; + } + const resp = function (callback) { const callbacks = { success: response => { @@ -84,6 +232,16 @@ export const connectIdSubmodule = { if (response) { try { responseObj = JSON.parse(response); + if (isPlainObject(responseObj) && Object.keys(responseObj).length > 0 && + (!!responseObj.connectId || !!responseObj.connectid)) { + responseObj.he = params.he; + responseObj.puid = params.puid || responseObj.puid; + responseObj.lastSynced = Date.now(); + responseObj.lastUsed = Date.now(); + storeObject(responseObj); + } else { + logError(`${MODULE_NAME} module: UPS response returned an invalid payload ${response}`); + } } catch (error) { logError(error); } @@ -91,7 +249,7 @@ export const connectIdSubmodule = { callback(responseObj); }, error: error => { - logError(`${MODULE_NAME}: ID fetch encountered an error`, error); + logError(`${MODULE_NAME} module: ID fetch encountered an error`, error); callback(); } }; @@ -99,7 +257,12 @@ export const connectIdSubmodule = { let url = `${params.endpoint || endpoint}?${formatQS(data)}`; connectIdSubmodule.getAjaxFn()(url, callbacks, null, {method: 'GET', withCredentials: true}); }; - return {callback: resp}; + const result = {callback: resp}; + if (shouldResync && storedId) { + result.id = storedId; + } + + return result; }, /** @@ -108,16 +271,7 @@ export const connectIdSubmodule = { * @returns {Boolean} */ isEUConsentRequired(consentData) { - return !!(consentData && consentData.gdpr && consentData.gdpr.gdprApplies); - }, - - /** - * Utility function that returns a boolean flag indicating if the opportunity - * is subject to GPP jurisdiction. - * @returns {Boolean} - */ - isUnderGPPJurisdiction(consentData) { - return !!(consentData && consentData.gppConsent && consentData.gppConsent.gppString); + return !!(consentData?.gdprApplies); }, /** diff --git a/modules/connectIdSystem.md b/modules/connectIdSystem.md index c671c036b77..bf5ac8a0e8b 100644 --- a/modules/connectIdSystem.md +++ b/modules/connectIdSystem.md @@ -2,6 +2,8 @@ Yahoo ConnectID user ID Module. +*Note: The storage config should be ommited as the module handles the storage of the needed information. + ### Prebid Params ``` @@ -9,11 +11,6 @@ pbjs.setConfig({ userSync: { userIds: [{ name: 'connectId', - storage: { - name: 'connectId', - type: 'html5', - expires: 15 - }, params: { pixelId: 58776, he: '0bef996248d63cea1529cb86de31e9547a712d9f380146e98bbd39beec70355a' @@ -27,8 +24,9 @@ The below parameters apply only to the Yahoo ConnectID user ID Module. | Param under usersync.userIds[] | Scope | Type | Description | Example | | --- | --- | --- | --- | --- | -| name | Required | String | ID value for the Yahoo ConnectID module - `"connectId"` | `"connectId"` | -| params | Required | Object | Data for Yahoo ConnectID initialization. | | -| params.pixelId | Required | Number | The Yahoo supplied publisher specific pixel Id. | `8976` | -| params.he | Optional | String | The SHA-256 hashed user email address. One of either the `he` parameter or the `puid` parameter must be supplied. | `"529cb86de31e9547a712d9f380146e98bbd39beec"` | -| params.puid | Optional | String | The publisher-supplied user identifier. One of either the `he` parameter or the `puid` parameter must be supplied. | `"P-975484817"` | +| name | Required | String | The name of this module. | `"connectId"` | +| params | Required | Object | Container of all module params. || +| params.pixelId | Required | Number | +The Yahoo-supplied publisher-specific pixel ID. | `"0000"` | +| params.he | Optional | String | The SHA-256 hashed user email address which has been lowercased prior to hashing. |`"ed8ddbf5a171981db8ef938596ca297d5e3f84bcc280041c5880dba3baf9c1d4"`| +| params.puid | Optional | String | A domain-specific user identifier such as a first-party cookie. If not passed, a puid value will be auto-generated and stored in local and / or cookie storage. | `"ab9iibf5a231ii1db8ef911596ca297d5e3f84biii00041c5880dba3baf9c1da"` | diff --git a/modules/connectadBidAdapter.js b/modules/connectadBidAdapter.js index 5185308eab0..d5665b318be 100644 --- a/modules/connectadBidAdapter.js +++ b/modules/connectadBidAdapter.js @@ -2,8 +2,6 @@ import { deepSetValue, convertTypes, tryAppendQueryString, logWarn } from '../sr import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js' import {config} from '../src/config.js'; -import {createEidsArray} from './userId/eids.js'; - const BIDDER_CODE = 'connectad'; const BIDDER_CODE_ALIAS = 'connectadrealtime'; const ENDPOINT_URL = 'https://i.connectad.io/api/v2'; @@ -73,12 +71,13 @@ export const spec = { } // EIDS Support - if (validBidRequests[0].userId) { - deepSetValue(data, 'user.ext.eids', createEidsArray(validBidRequests[0].userId)); + if (validBidRequests[0].userIdAsEids) { + deepSetValue(data, 'user.ext.eids', validBidRequests[0].userIdAsEids); } validBidRequests.map(bid => { const placement = Object.assign({ + // TODO: fix transactionId leak: https://github.com/prebid/Prebid.js/issues/9781 id: bid.transactionId, divName: bid.bidId, pisze: bid.mediaTypes.banner.sizes[0] || bid.sizes[0], diff --git a/modules/consentManagement.js b/modules/consentManagement.js index bdf6649bcba..05447a890cb 100644 --- a/modules/consentManagement.js +++ b/modules/consentManagement.js @@ -11,6 +11,8 @@ import {includes} from '../src/polyfill.js'; import {timedAuctionHook} from '../src/utils/perfMetrics.js'; import {registerOrtbProcessor, REQUEST} from '../src/pbjsORTB.js'; import {enrichFPD} from '../src/fpd/enrichment.js'; +import {getGlobal} from '../src/prebidGlobal.js'; +import {cmpClient} from '../libraries/cmp/cmpClient.js'; const DEFAULT_CMP = 'iab'; const DEFAULT_CONSENT_TIMEOUT = 10000; @@ -18,16 +20,12 @@ const CMP_VERSION = 2; export let userCMP; export let consentTimeout; -export let actionTimeout; export let gdprScope; export let staticConsentData; +let actionTimeout; let consentData; let addedConsentHook = false; -let provisionalConsent; -let onTimeout; -let timer = null; -let actionTimer = null; // add new CMPs here, with their dedicated lookup function const cmpCallMap = { @@ -43,12 +41,6 @@ function lookupStaticConsentData({onSuccess, onError}) { processCmpData(staticConsentData, {onSuccess, onError}) } -export function setActionTimeout(timeout = setTimeout) { - clearTimeout(timer); - timer = null; - actionTimer = timeout(onTimeout, actionTimeout); -} - /** * This function handles interacting with an IAB compliant CMP to obtain the consent information of the user. * Given the async nature of the CMP's API, we pass in acting success/error callback functions to exit this function @@ -56,115 +48,38 @@ export function setActionTimeout(timeout = setTimeout) { * @param {function({})} onSuccess acts as a success callback when CMP returns a value; pass along consentObjectfrom CMP * @param {function(string, ...{}?)} cmpError acts as an error callback while interacting with CMP; pass along an error message (string) and any extra error arguments (purely for logging) */ -function lookupIabConsent({onSuccess, onError}) { - function findCMP() { - let f = window; - let cmpFrame; - let cmpFunction; - while (true) { - try { - if (typeof f.__tcfapi === 'function') { - cmpFunction = f.__tcfapi; - cmpFrame = f; - break; - } - } catch (e) { } - - // need separate try/catch blocks due to the exception errors thrown when trying to check for a frame that doesn't exist in 3rd party env - try { - if (f.frames['__tcfapiLocator']) { - cmpFrame = f; - break; - } - } catch (e) { } - - if (f === window.top) break; - f = f.parent; - } - return { - cmpFrame, - cmpFunction - }; - } - +function lookupIabConsent({onSuccess, onError, onEvent}) { function cmpResponseCallback(tcfData, success) { logInfo('Received a response from CMP', tcfData); if (success) { + onEvent(tcfData); if (tcfData.gdprApplies === false || tcfData.eventStatus === 'tcloaded' || tcfData.eventStatus === 'useractioncomplete') { processCmpData(tcfData, {onSuccess, onError}); - } else { - provisionalConsent = tcfData; - if (!isNaN(actionTimeout) && actionTimer === null && timer != null) setActionTimeout(); } } else { onError('CMP unable to register callback function. Please check CMP setup.'); } } - const cmpCallbacks = {}; - const { cmpFrame, cmpFunction } = findCMP(); + const cmp = cmpClient({ + apiName: '__tcfapi', + apiVersion: CMP_VERSION, + apiArgs: ['command', 'version', 'callback', 'parameter'], + }); - if (!cmpFrame) { + if (!cmp) { return onError('TCF2 CMP not found.'); } - // to collect the consent information from the user, we perform two calls to the CMP in parallel: - // first to collect the user's consent choices represented in an encoded string (via getConsentData) - // second to collect the user's full unparsed consent information (via getVendorConsents) - - // the following code also determines where the CMP is located and uses the proper workflow to communicate with it: - // check to see if CMP is found on the same window level as prebid and call it directly if so - // check to see if prebid is in a safeframe (with CMP support) - // else assume prebid may be inside an iframe and use the IAB CMP locator code to see if CMP's located in a higher parent window. this works in cross domain iframes - // if the CMP is not found, the iframe function will call the cmpError exit callback to abort the rest of the CMP workflow - - if (typeof cmpFunction === 'function') { + if (cmp.isDirect) { logInfo('Detected CMP API is directly accessible, calling it now...'); - cmpFunction('addEventListener', CMP_VERSION, cmpResponseCallback); } else { logInfo('Detected CMP is outside the current iframe where Prebid.js is located, calling it now...'); - callCmpWhileInIframe('addEventListener', cmpFrame, cmpResponseCallback); } - function callCmpWhileInIframe(commandName, cmpFrame, moduleCallback) { - const apiName = '__tcfapi'; - - const callName = `${apiName}Call`; - - /* Setup up a __cmp function to do the postMessage and stash the callback. - This function behaves (from the caller's perspective identicially to the in-frame __cmp call */ - window[apiName] = function (cmd, cmpVersion, callback, arg) { - const callId = Math.random() + ''; - const msg = { - [callName]: { - command: cmd, - version: cmpVersion, - parameter: arg, - callId: callId - } - }; - - cmpCallbacks[callId] = callback; - cmpFrame.postMessage(msg, '*'); - } - - /** when we get the return message, call the stashed callback */ - window.addEventListener('message', readPostMessageResponse, false); - - // call CMP - window[apiName](commandName, CMP_VERSION, moduleCallback); - - function readPostMessageResponse(event) { - const cmpDataPkgName = `${apiName}Return`; - const json = (typeof event.data === 'string' && includes(event.data, cmpDataPkgName)) ? JSON.parse(event.data) : event.data; - if (json[cmpDataPkgName] && json[cmpDataPkgName].callId) { - const payload = json[cmpDataPkgName]; - // TODO - clean up this logic (move listeners?); we have duplicate messages responses because 2 eventlisteners are active from the 2 cmp requests running in parallel - if (cmpCallbacks.hasOwnProperty(payload.callId)) { - cmpCallbacks[payload.callId](payload.returnValue, payload.success); - } - } - } - } + cmp({ + command: 'addEventListener', + callback: cmpResponseCallback + }) } /** @@ -173,20 +88,27 @@ function lookupIabConsent({onSuccess, onError}) { * @param cb A callback that takes: a boolean that is true if the auction should be canceled; an error message and extra * error arguments that will be undefined if there's no error. */ -export function loadConsentData(cb, callMap = cmpCallMap, timeout = setTimeout) { +function loadConsentData(cb) { let isDone = false; + let timer = null; + let onTimeout, provisionalConsent; + let cmpLoaded = false; - function done(consentData, shouldCancelAuction, errMsg, ...extraArgs) { + function resetTimeout(timeout) { if (timer != null) { clearTimeout(timer); - timer = null; } - - if (actionTimer != null) { - clearTimeout(actionTimer); - actionTimer = null; + if (!isDone && timeout != null) { + if (timeout === 0) { + onTimeout() + } else { + timer = setTimeout(onTimeout, timeout); + } } + } + function done(consentData, shouldCancelAuction, errMsg, ...extraArgs) { + resetTimeout(null); isDone = true; gdprDataHandler.setConsentData(consentData); if (typeof cb === 'function') { @@ -203,32 +125,30 @@ export function loadConsentData(cb, callMap = cmpCallMap, timeout = setTimeout) onSuccess: (data) => done(data, false), onError: function (msg, ...extraArgs) { done(null, true, msg, ...extraArgs); + }, + onEvent: function (consentData) { + provisionalConsent = consentData; + if (cmpLoaded) return; + cmpLoaded = true; + if (actionTimeout != null) { + resetTimeout(actionTimeout); + } } } - callMap[userCMP](callbacks); - - if (!isDone) { - onTimeout = () => { - const continueToAuction = (data) => { - done(data, false, 'CMP did not load, continuing auction...'); - } - processCmpData(provisionalConsent, { - onSuccess: continueToAuction, - onError: () => continueToAuction(storeConsentData(undefined)) - }) + onTimeout = () => { + const continueToAuction = (data) => { + done(data, false, `${cmpLoaded ? 'Timeout waiting for user action on CMP' : 'CMP did not load'}, continuing auction...`); } + processCmpData(provisionalConsent, { + onSuccess: continueToAuction, + onError: () => continueToAuction(storeConsentData(undefined)), + }) + } - if (consentTimeout === 0) { - onTimeout(); - } else { - if (timer != null) { - clearTimeout(timer); - timer = null; - } - - timer = timeout(onTimeout, consentTimeout); - } + cmpCallMap[userCMP](callbacks); + if (!(actionTimeout != null && cmpLoaded)) { + resetTimeout(consentTimeout); } } @@ -294,11 +214,6 @@ function processCmpData(consentObject, {onSuccess, onError}) { ); } - // do extra things for static config - if (userCMP === 'static') { - consentObject = consentObject.getTCData; - } - if (checkData()) { onError(`CMP returned unexpected value during lookup process.`, consentObject); } else { @@ -352,10 +267,6 @@ export function setConsentConfig(config) { logInfo(`consentManagement config did not specify cmp. Using system default setting (${DEFAULT_CMP}).`); } - if (isNumber(config.actionTimeout)) { - actionTimeout = config.actionTimeout; - } - if (isNumber(config.timeout)) { consentTimeout = config.timeout; } else { @@ -363,6 +274,8 @@ export function setConsentConfig(config) { logInfo(`consentManagement config did not specify timeout. Using system default setting (${DEFAULT_CONSENT_TIMEOUT}).`); } + actionTimeout = isNumber(config.actionTimeout) ? config.actionTimeout : null; + // if true, then gdprApplies should be set to true gdprScope = config.defaultGdprScope === true; @@ -371,13 +284,17 @@ export function setConsentConfig(config) { if (userCMP === 'static') { if (isPlainObject(config.consentData)) { staticConsentData = config.consentData; + if (staticConsentData?.getTCData != null) { + // accept static config with or without `getTCData` - see https://github.com/prebid/Prebid.js/issues/9581 + staticConsentData = staticConsentData.getTCData; + } consentTimeout = 0; } else { logError(`consentManagement config with cmpApi: 'static' did not specify consentData. No consents will be available to adapters.`); } } if (!addedConsentHook) { - $$PREBID_GLOBAL$$.requestBids.before(requestBidsHook, 50); + getGlobal().requestBids.before(requestBidsHook, 50); } addedConsentHook = true; gdprDataHandler.enable(); diff --git a/modules/consentManagementGpp.js b/modules/consentManagementGpp.js index 8a9c3f999f0..88851adfda5 100644 --- a/modules/consentManagementGpp.js +++ b/modules/consentManagementGpp.js @@ -7,9 +7,12 @@ import {deepSetValue, isNumber, isPlainObject, isStr, logError, logInfo, logWarn} from '../src/utils.js'; import {config} from '../src/config.js'; import {gppDataHandler} from '../src/adapterManager.js'; -import {includes} from '../src/polyfill.js'; import {timedAuctionHook} from '../src/utils/perfMetrics.js'; import { enrichFPD } from '../src/fpd/enrichment.js'; +import {getGlobal} from '../src/prebidGlobal.js'; +import {cmpClient} from '../libraries/cmp/cmpClient.js'; +import {GreedyPromise} from '../src/utils/promise.js'; +import {buildActivityParams} from '../src/activities/params.js'; const DEFAULT_CMP = 'iab'; const DEFAULT_CONSENT_TIMEOUT = 10000; @@ -17,7 +20,7 @@ const CMP_VERSION = 1; export let userCMP; export let consentTimeout; -export let staticConsentData; +let staticConsentData; let consentData; let addedConsentHook = false; @@ -28,28 +31,16 @@ const cmpCallMap = { 'static': lookupStaticConsentData }; -/** - * This function checks the state of the IAB gppData's applicableSection field (to ensure it's populated and has a valid value). - * section === 0 represents a CMP's default value when CMP is loading, it shoud not be used a real user's section. - * - * TODO --- The initial version of the GPP CMP API spec used this naming convention, but it was later changed as an update to the spec. - * CMPs should adjust their logic to use the new format (applicableSecctions), but that may not be the case with the initial release. - * Added support just in case for this transition period, can likely be removed at a later date... - * @param gppData represents the IAB gppData object - * @returns true|false - */ -function checkApplicableSectionIsReady(gppData) { - return gppData && Array.isArray(gppData.applicableSection) && gppData.applicableSection.length > 0 && gppData.applicableSection[0] !== 0; -} - /** * This function checks the state of the IAB gppData's applicableSections field (to ensure it's populated and has a valid value). * section === 0 represents a CMP's default value when CMP is loading, it shoud not be used a real user's section. * @param gppData represents the IAB gppData object - * @returns true|false + * @returns {Array} */ -function checkApplicableSectionsIsReady(gppData) { - return gppData && Array.isArray(gppData.applicableSections) && gppData.applicableSections.length > 0 && gppData.applicableSections[0] !== 0; +function applicableSections(gppData) { + return gppData && Array.isArray(gppData.applicableSections) && gppData.applicableSections.length > 0 && gppData.applicableSections[0] !== 0 + ? gppData.applicableSections + : []; } /** @@ -67,107 +58,36 @@ function lookupStaticConsentData({onSuccess, onError}) { * @param {function({})} onSuccess acts as a success callback when CMP returns a value; pass along consentObjectfrom CMP * @param {function(string, ...{}?)} cmpError acts as an error callback while interacting with CMP; pass along an error message (string) and any extra error arguments (purely for logging) */ -function lookupIabConsent({onSuccess, onError}) { - const cmpApiName = '__gpp'; - const cmpCallbacks = {}; - let registeredPostMessageResponseListener = false; - - function findCMP() { - let f = window; - let cmpFrame; - let cmpDirectAccess = false; - while (true) { - try { - if (typeof f[cmpApiName] === 'function') { - cmpFrame = f; - cmpDirectAccess = true; - break; - } - } catch (e) {} - - // need separate try/catch blocks due to the exception errors thrown when trying to check for a frame that doesn't exist in 3rd party env - try { - if (f.frames['__gppLocator']) { - cmpFrame = f; - break; - } - } catch (e) {} - - if (f === window.top) break; - f = f.parent; - } - - return { - cmpFrame, - cmpDirectAccess - }; - } - - const {cmpFrame, cmpDirectAccess} = findCMP(); - - if (!cmpFrame) { +export function lookupIabConsent({onSuccess, onError}, mkClient = cmpClient) { + const cmp = mkClient({ + apiName: '__gpp', + apiVersion: CMP_VERSION, + }); + if (!cmp) { return onError('GPP CMP not found.'); } - const invokeCMP = (cmpDirectAccess) ? invokeCMPDirect : invokeCMPFrame; - - function invokeCMPDirect({command, callback, parameter, version = CMP_VERSION}, resultCb) { - if (typeof resultCb === 'function') { - resultCb(cmpFrame[cmpApiName](command, callback, parameter, version)); - } else { - cmpFrame[cmpApiName](command, callback, parameter, version); - } - } - - function invokeCMPFrame({command, callback, parameter, version = CMP_VERSION}, resultCb) { - const callName = `${cmpApiName}Call`; - if (!registeredPostMessageResponseListener) { - // when we get the return message, call the stashed callback; - window.addEventListener('message', readPostMessageResponse, false); - registeredPostMessageResponseListener = true; - } - - // call CMP via postMessage - const callId = Math.random().toString(); - const msg = { - [callName]: { - command: command, - parameter, - version, - callId: callId - } - }; - - // TODO? - add logic to check if random was already used in the same session, and roll another if so? - cmpCallbacks[callId] = (typeof callback === 'function') ? callback : resultCb; - cmpFrame.postMessage(msg, '*'); - - function readPostMessageResponse(event) { - const cmpDataPkgName = `${cmpApiName}Return`; - const json = (typeof event.data === 'string' && event.data.includes(cmpDataPkgName)) ? JSON.parse(event.data) : event.data; - if (json[cmpDataPkgName] && json[cmpDataPkgName].callId) { - const payload = json[cmpDataPkgName]; - - if (cmpCallbacks.hasOwnProperty(payload.callId)) { - cmpCallbacks[payload.callId](payload.returnValue); - } - } - } - } - - const startupMsg = (cmpDirectAccess) ? 'Detected GPP CMP API is directly accessible, calling it now...' + const startupMsg = (cmp.isDirect) ? 'Detected GPP CMP API is directly accessible, calling it now...' : 'Detected GPP CMP is outside the current iframe where Prebid.js is located, calling it now...'; logInfo(startupMsg); - invokeCMP({ + cmp({ command: 'addEventListener', callback: function (evt) { if (evt) { - logInfo(`Received a ${(cmpDirectAccess ? 'direct' : 'postmsg')} response from GPP CMP for event`, evt); + logInfo(`Received a ${(cmp.isDirect ? 'direct' : 'postmsg')} response from GPP CMP for event`, evt); if (evt.eventName === 'sectionChange' || evt.pingData.cmpStatus === 'loaded') { - invokeCMP({command: 'getGPPData'}, function (gppData) { - logInfo(`Received a ${cmpDirectAccess ? 'direct' : 'postmsg'} response from GPP CMP for getGPPData`, gppData); - processCmpData(gppData, {onSuccess, onError}); + cmp({command: 'getGPPData'}).then((gppData) => { + logInfo(`Received a ${cmp.isDirect ? 'direct' : 'postmsg'} response from GPP CMP for getGPPData`, gppData); + return GreedyPromise.all( + (gppData?.pingData?.supportedAPIs || []) + .map((name) => cmp({command: 'getSection', parameter: name}) + .catch(() => { logError(`Could not retrieve section data for GPP section '${name}'`) }) + .then((res) => [name, res])) + ).then((sections) => { + const sectionData = Object.fromEntries(sections.filter(([_, val]) => val != null)); + processCmpData({gppData, sectionData}, {onSuccess, onError}); + }) }); } else if (evt.pingData.cmpStatus === 'error') { onError('CMP returned with a cmpStatus:error response. Please check CMP setup.'); @@ -198,7 +118,7 @@ function loadConsentData(cb) { } } - if (!includes(Object.keys(cmpCallMap), userCMP)) { + if (!cmpCallMap.hasOwnProperty(userCMP)) { done(null, false, `GPP CMP framework (${userCMP}) is not a supported framework. Aborting consentManagement module and resuming auction.`); return; } @@ -218,7 +138,7 @@ function loadConsentData(cb) { } processCmpData(consentData, { onSuccess: continueToAuction, - onError: () => continueToAuction(storeConsentData(undefined)) + onError: () => continueToAuction(storeConsentData()) }) } if (consentTimeout === 0) { @@ -280,11 +200,10 @@ export const requestBidsHook = timedAuctionHook('gpp', function requestBidsHook( * If it's bad, we call `onError` * If it's good, then we store the value and call `onSuccess` */ -function processCmpData(consentObject, {onSuccess, onError}) { +function processCmpData(consentData, {onSuccess, onError}) { function checkData() { - const gppString = consentObject && consentObject.gppString; - const gppSection = (checkApplicableSectionsIsReady(consentObject)) ? consentObject.applicableSections - : (checkApplicableSectionIsReady(consentObject)) ? consentObject.applicableSection : []; + const gppString = consentData?.gppData?.gppString; + const gppSection = consentData?.gppData?.applicableSections; return !!( (!Array.isArray(gppSection)) || @@ -293,25 +212,25 @@ function processCmpData(consentObject, {onSuccess, onError}) { } if (checkData()) { - onError(`CMP returned unexpected value during lookup process.`, consentObject); + onError(`CMP returned unexpected value during lookup process.`, consentData); } else { - onSuccess(storeConsentData(consentObject)); + onSuccess(storeConsentData(consentData)); } } /** * Stores CMP data locally in module to make information available in adaptermanager.js for later in the auction - * @param {object} cmpConsentObject required; an object representing user's consent choices (can be undefined in certain use-cases for this function only) + * @param {{}} gppData the result of calling a CMP's `getGPPData` (or equivalent) + * @param {{}} sectionData map from GPP section name to the result of calling a CMP's `getSection` (or equivalent) */ -function storeConsentData(cmpConsentObject) { +export function storeConsentData({gppData, sectionData} = {}) { consentData = { - gppString: (cmpConsentObject) ? cmpConsentObject.gppString : undefined, - - fullGppData: (cmpConsentObject) || undefined, + gppString: (gppData) ? gppData.gppString : undefined, + gppData: (gppData) || undefined, }; - consentData.applicableSections = (checkApplicableSectionsIsReady(cmpConsentObject)) ? cmpConsentObject.applicableSections - : (checkApplicableSectionIsReady(cmpConsentObject)) ? cmpConsentObject.applicableSection : []; + consentData.applicableSections = applicableSections(gppData); consentData.apiVersion = CMP_VERSION; + consentData.sectionData = sectionData; return consentData; } @@ -352,7 +271,7 @@ export function setConsentConfig(config) { if (userCMP === 'static') { if (isPlainObject(config.consentData)) { - staticConsentData = config.consentData; + staticConsentData = {gppData: config.consentData, sectionData: config.sectionData}; consentTimeout = 0; } else { logError(`consentManagement.gpp config with cmpApi: 'static' did not specify consentData. No consents will be available to adapters.`); @@ -362,7 +281,10 @@ export function setConsentConfig(config) { logInfo('consentManagement.gpp module has been activated...'); if (!addedConsentHook) { - $$PREBID_GLOBAL$$.requestBids.before(requestBidsHook, 50); + getGlobal().requestBids.before(requestBidsHook, 50); + buildActivityParams.before((next, params) => { + return next(Object.assign({gppConsent: gppDataHandler.getConsentData()}, params)); + }); } addedConsentHook = true; gppDataHandler.enable(); diff --git a/modules/consentManagementUsp.js b/modules/consentManagementUsp.js index fcc13152aa9..fb65a76c87b 100644 --- a/modules/consentManagementUsp.js +++ b/modules/consentManagementUsp.js @@ -4,12 +4,13 @@ * information and make it available for any USP (CCPA) supported adapters to * read/pass this information to their system. */ -import {deepSetValue, isFn, isNumber, isPlainObject, isStr, logError, logInfo, logWarn} from '../src/utils.js'; +import {deepSetValue, isNumber, isPlainObject, isStr, logError, logInfo, logWarn} from '../src/utils.js'; import {config} from '../src/config.js'; import adapterManager, {uspDataHandler} from '../src/adapterManager.js'; import {timedAuctionHook} from '../src/utils/perfMetrics.js'; import {getHook} from '../src/hook.js'; import {enrichFPD} from '../src/fpd/enrichment.js'; +import {cmpClient} from '../libraries/cmp/cmpClient.js'; const DEFAULT_CONSENT_API = 'iab'; const DEFAULT_CONSENT_TIMEOUT = 50; @@ -41,35 +42,6 @@ function lookupStaticConsentData({onSuccess, onError}) { * based on the appropriate result. */ function lookupUspConsent({onSuccess, onError}) { - function findUsp() { - let f = window; - let uspapiFrame; - let uspapiFunction; - - while (true) { - try { - if (typeof f.__uspapi === 'function') { - uspapiFunction = f.__uspapi; - uspapiFrame = f; - break; - } - } catch (e) {} - - try { - if (f.frames['__uspapiLocator']) { - uspapiFrame = f; - break; - } - } catch (e) {} - if (f === window.top) break; - f = f.parent; - } - return { - uspapiFrame, - uspapiFunction, - }; - } - function handleUspApiResponseCallbacks() { const uspResponse = {}; @@ -92,86 +64,36 @@ function lookupUspConsent({onSuccess, onError}) { } let callbackHandler = handleUspApiResponseCallbacks(); - let uspapiCallbacks = {}; - let { uspapiFrame, uspapiFunction } = findUsp(); + const cmp = cmpClient({ + apiName: '__uspapi', + apiVersion: USPAPI_VERSION, + apiArgs: ['command', 'version', 'callback'], + }); - if (!uspapiFrame) { + if (!cmp) { return onError('USP CMP not found.'); } - function registerDataDelHandler(invoker, arg2) { - try { - invoker('registerDeletion', arg2, adapterManager.callDataDeletionRequest); - } catch (e) { - logError('Error invoking CMP `registerDeletion`:', e); - } - } - - // to collect the consent information from the user, we perform a call to USPAPI - // to collect the user's consent choices represented as a string (via getUSPData) - - // the following code also determines where the USPAPI is located and uses the proper workflow to communicate with it: - // - use the USPAPI locator code to see if USP's located in the current window or an ancestor window. - // - else assume prebid is in an iframe, and use the locator to see if the CMP is located in a higher parent window. This works in cross domain iframes. - // - if USPAPI is not found, the iframe function will call the uspError exit callback to abort the rest of the USPAPI workflow - - if (isFn(uspapiFunction)) { + if (cmp.isDirect) { logInfo('Detected USP CMP is directly accessible, calling it now...'); - uspapiFunction( - 'getUSPData', - USPAPI_VERSION, - callbackHandler.consentDataCallback - ); - registerDataDelHandler(uspapiFunction, USPAPI_VERSION); } else { logInfo( 'Detected USP CMP is outside the current iframe where Prebid.js is located, calling it now...' ); - callUspApiWhileInIframe( - 'getUSPData', - uspapiFrame, - callbackHandler.consentDataCallback - ); - registerDataDelHandler(callUspApiWhileInIframe, uspapiFrame); } - let listening = false; - - function callUspApiWhileInIframe(commandName, uspapiFrame, moduleCallback) { - function callUsp(cmd, ver, callback) { - let callId = Math.random() + ''; - let msg = { - __uspapiCall: { - command: cmd, - version: ver, - callId: callId, - }, - }; - - uspapiCallbacks[callId] = callback; - uspapiFrame.postMessage(msg, '*'); - }; - - /** when we get the return message, call the stashed callback */ - if (!listening) { - window.addEventListener('message', readPostMessageResponse, false); - listening = true; - } - - // call uspapi - callUsp(commandName, USPAPI_VERSION, moduleCallback); + cmp({ + command: 'getUSPData', + callback: callbackHandler.consentDataCallback + }); - function readPostMessageResponse(event) { - const res = event && event.data && event.data.__uspapiReturn; - if (res && res.callId) { - if (uspapiCallbacks.hasOwnProperty(res.callId)) { - uspapiCallbacks[res.callId](res.returnValue, res.success); - delete uspapiCallbacks[res.callId]; - } - } - } - } + cmp({ + command: 'registerDeletion', + callback: adapterManager.callDataDeletionRequest + }).catch(e => { + logError('Error invoking CMP `registerDeletion`:', e); + }); } /** diff --git a/modules/contentexchangeBidAdapter.js b/modules/contentexchangeBidAdapter.js index becc21555e3..be5900407ea 100644 --- a/modules/contentexchangeBidAdapter.js +++ b/modules/contentexchangeBidAdapter.js @@ -157,7 +157,7 @@ export const spec = { coppa: config.getConfig('coppa') === true ? 1 : 0, ccpa: bidderRequest.uspConsent || undefined, gdpr: bidderRequest.gdprConsent || undefined, - tmax: config.getConfig('bidderTimeout') + tmax: bidderRequest.timeout }; const len = validBidRequests.length; diff --git a/modules/conversantAnalyticsAdapter.js b/modules/conversantAnalyticsAdapter.js index 6e88f403bae..0c58402ca87 100644 --- a/modules/conversantAnalyticsAdapter.js +++ b/modules/conversantAnalyticsAdapter.js @@ -3,20 +3,28 @@ import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import CONSTANTS from '../src/constants.json'; import {getGlobal} from '../src/prebidGlobal.js'; import adapterManager from '../src/adapterManager.js'; -import {logInfo, logError, logMessage, deepAccess, isInteger} from '../src/utils.js'; +import {logInfo, logWarn, logError, logMessage, deepAccess, isInteger} from '../src/utils.js'; +import {getRefererInfo} from '../src/refererDetection.js'; + +// Maintainer: mediapsr@epsilon.com const { - EVENTS: { AUCTION_END, AD_RENDER_FAILED, BID_TIMEOUT, BID_WON } + EVENTS: { AUCTION_END, AD_RENDER_FAILED, BID_TIMEOUT, BID_WON, BIDDER_ERROR } } = CONSTANTS; +// STALE_RENDER, TCF2_ENFORCEMENT would need to add extra calls for these as they likely occur after AUCTION_END? const GVLID = 24; const ANALYTICS_TYPE = 'endpoint'; // for local testing set domain to 127.0.0.1:8290 -const URL = 'https://web.hb.ad.cpe.dotomi.com/cvx/event/prebidanalytics'; +const DOMAIN = 'https://web.hb.ad.cpe.dotomi.com/'; +const ANALYTICS_URL = DOMAIN + 'cvx/event/prebidanalytics'; +const ERROR_URL = DOMAIN + 'cvx/event/prebidanalyticerrors'; const ANALYTICS_CODE = 'conversant'; +const ANALYTICS_ALIASES = [ANALYTICS_CODE, 'epsilon', 'cnvr']; export const CNVR_CONSTANTS = { LOG_PREFIX: 'Conversant analytics adapter: ', + ERROR_MISSING_DATA_PREFIX: 'Parsing method failed because of missing data: ', // Maximum time to keep an item in the cache before it gets purged MAX_MILLISECONDS_IN_CACHE: 30000, // How often cache cleanup will run @@ -41,6 +49,7 @@ let conversantAnalyticsEnabled = false; export const cnvrHelper = { // Turns on sampling for an instance of prebid analytics. doSample: true, + doSendErrorData: false, /** * Used to hold data for RENDER FAILED events so we can send a payload back that will match our original auction data. @@ -67,7 +76,12 @@ export const cnvrHelper = { /** * Lookup of auction IDs to auction start timestamps */ - auctionIdTimestampCache: {} + auctionIdTimestampCache: {}, + + /** + * Capture any bidder errors and bundle them with AUCTION_END + */ + bidderErrorCache: {} }; /** @@ -77,33 +91,99 @@ export const cnvrHelper = { let cacheCleanupInterval; let conversantAnalytics = Object.assign( - adapter({URL, ANALYTICS_TYPE}), + adapter({URL: ANALYTICS_URL, ANALYTICS_TYPE}), { track({eventType, args}) { - if (cnvrHelper.doSample) { - logMessage(CNVR_CONSTANTS.LOG_PREFIX + ' track(): ' + eventType, args); - switch (eventType) { - case AUCTION_END: - onAuctionEnd(args); - break; - case AD_RENDER_FAILED: - onAdRenderFailed(args); - break; - case BID_WON: - onBidWon(args); - break; - case BID_TIMEOUT: - onBidTimeout(args); - break; - } // END switch - } else { - logMessage(CNVR_CONSTANTS.LOG_PREFIX + ' - ' + eventType + ': skipped due to sampling'); - }// END IF(cnvrHelper.doSample) + try { + if (cnvrHelper.doSample) { + logMessage(CNVR_CONSTANTS.LOG_PREFIX + ' track(): ' + eventType, args); + switch (eventType) { + case AUCTION_END: + onAuctionEnd(args); + break; + case AD_RENDER_FAILED: + onAdRenderFailed(args); + break; + case BID_WON: + onBidWon(args); + break; + case BID_TIMEOUT: + onBidTimeout(args); + break; + case BIDDER_ERROR: + onBidderError(args) + } // END switch + } else { + logMessage(CNVR_CONSTANTS.LOG_PREFIX + ' - ' + eventType + ': skipped due to sampling'); + }// END IF(cnvrHelper.doSample) + } catch (e) { + // e = {stack:"...",message:"..."} + logError(CNVR_CONSTANTS.LOG_PREFIX + 'Caught error in handling ' + eventType + ' event: ' + e.message); + cnvrHelper.sendErrorData(eventType, e); + } } // END track() } ); // ================================================== EVENT HANDLERS =================================================== + +/** + * Handler for BIDDER_ERROR events, tries to capture as much data, save it in cache which is then picked up by + * AUCTION_END event and included in that payload. Was not able to see an easy way to get adUnitCode in this event + * so not including it for now. + * https://docs.prebid.org/dev-docs/bidder-adaptor.html#registering-on-bidder-error + * Trigger when the HTTP response status code is not between 200-299 and not equal to 304. + { + error: XMLHttpRequest, https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest + bidderRequest: { https://docs.prebid.org/dev-docs/bidder-adaptor.html#registering-on-bidder-error + { + auctionId: "b06c5141-fe8f-4cdf-9d7d-54415490a917", + auctionStart: 1579746300522, + bidderCode: "myBidderCode", + bidderRequestId: "15246a574e859f", + bids: [{...}], + gdprConsent: {consentString: "BOtmiBKOtmiBKABABAENAFAAAAACeAAA", vendorData: {...}, gdprApplies: true}, + refererInfo: { + canonicalUrl: null, + page: "http://mypage.org?pbjs_debug=true", + domain: "mypage.org", + ref: null, + numIframes: 0, + reachedTop: true, + isAmp: false, + stack: ["http://mypage.org?pbjs_debug=true"] + } + } + } +} + */ +function onBidderError(args) { + if (!cnvrHelper.doSendErrorData) { + logWarn(CNVR_CONSTANTS.LOG_PREFIX + 'Skipping bidder error parsing due to config disabling error logging, bidder error status = ' + args.error.status + ', Message = ' + args.error.statusText); + return; + } + + let error = args.error; + let bidRequest = args.bidderRequest; + let auctionId = bidRequest.auctionId; + let bidderCode = bidRequest.bidderCode; + logWarn(CNVR_CONSTANTS.LOG_PREFIX + 'onBidderError(): error received from bidder ' + bidderCode + '. Status = ' + error.status + ', Message = ' + error.statusText); + let errorObj = { + status: error.status, + message: error.statusText, + bidderCode: bidderCode, + url: cnvrHelper.getPageUrl(), + }; + if (cnvrHelper.bidderErrorCache[auctionId]) { + cnvrHelper.bidderErrorCache[auctionId]['errors'].push(errorObj); + } else { + cnvrHelper.bidderErrorCache[auctionId] = { + errors: [errorObj], + timeReceived: Date.now() + }; + } +} + /** * We get the list of timeouts before the endAution, cache them temporarily in a global cache and the endAuction event * will pick them up. Uses getLookupKey() to create the key to the entry from auctionId, adUnitCode and bidderCode. @@ -145,8 +225,13 @@ function onBidWon(args) { // Make sure we have all the data we need if (!bidderCode || !adUnitCode || !auctionId) { - logError(CNVR_CONSTANTS.LOG_PREFIX + 'onBidWon() did not get all the necessary data to process the event.'); - return; + let errorReason = 'auction id'; + if (!bidderCode) { + errorReason = 'bidder code'; + } else if (!adUnitCode) { + errorReason = 'ad unit code' + } + throw new Error(CNVR_CONSTANTS.ERROR_MISSING_DATA_PREFIX + errorReason); } if (cnvrHelper.auctionIdTimestampCache[auctionId]) { @@ -193,8 +278,12 @@ function onAdRenderFailed(args) { // Make sure we have all the data we need, adId is optional so it's not guaranteed, without that we can't match it up // to our adIdLookup data. if (!adId || !cnvrHelper.adIdLookup[adId]) { - logError(CNVR_CONSTANTS.LOG_PREFIX + "onAdRenderFailed(): Unable to process RENDER FAILED because adId is missing or doesn't match a record in our cache."); - return; // Either no adId to match against a bidWon event, or no data saved from a bidWon event that matches the adId + let errorMsg = 'ad id'; + if (adId) { + errorMsg = 'no lookup data for ad id'; + } + // Either no adId to match against a bidWon event, or no data saved from a bidWon event that matches the adId + throw new Error(CNVR_CONSTANTS.ERROR_MISSING_DATA_PREFIX + errorMsg); } const adIdObj = cnvrHelper.adIdLookup[adId]; const adUnitCode = adIdObj['adUnitCode']; @@ -203,8 +292,13 @@ function onAdRenderFailed(args) { delete cnvrHelper.adIdLookup[adId]; // cleanup our cache if (!bidderCode || !adUnitCode || !auctionId) { - logError(CNVR_CONSTANTS.LOG_PREFIX + 'onAdRenderFailed(): Unable to process RENDER FAILED because lookup cache did not have all the data we required.'); - return; + let errorReason = 'auction id'; + if (!bidderCode) { + errorReason = 'bidder code'; + } else if (!adUnitCode) { + errorReason = 'ad unit code' + } + throw new Error(CNVR_CONSTANTS.ERROR_MISSING_DATA_PREFIX + errorReason); } let timestamp = Date.now(); @@ -230,8 +324,7 @@ function onAdRenderFailed(args) { function onAuctionEnd(args) { const auctionId = args.auctionId; if (!auctionId) { - logError(CNVR_CONSTANTS.LOG_PREFIX + 'onAuctionEnd(): No auctionId in args supplied so unable to process event.'); - return; + throw new Error(CNVR_CONSTANTS.ERROR_MISSING_DATA_PREFIX + 'auction id'); } const auctionTimestamp = args.timestamp ? args.timestamp : Date.now(); @@ -240,8 +333,13 @@ function onAuctionEnd(args) { const auctionEndPayload = cnvrHelper.createPayload('auction_end', auctionId, auctionTimestamp); // Get bid request information from adUnits if (!Array.isArray(args.adUnits)) { - logError(CNVR_CONSTANTS.LOG_PREFIX + 'onAuctionEnd(): adUnits not defined in arguments.'); - return; + throw new Error(CNVR_CONSTANTS.ERROR_MISSING_DATA_PREFIX + 'no adUnits in event args'); + } + + // Write out any bid errors + if (cnvrHelper.bidderErrorCache[auctionId]) { + auctionEndPayload.bidderErrors = cnvrHelper.bidderErrorCache[auctionId].errors; + delete cnvrHelper.bidderErrorCache[auctionId]; } args.adUnits.forEach(adUnit => { @@ -298,7 +396,7 @@ function onAuctionEnd(args) { } }); } else { - logError(CNVR_CONSTANTS.LOG_PREFIX + 'onAuctionEnd(): noBids not defined in arguments.'); + logWarn(CNVR_CONSTANTS.LOG_PREFIX + 'onAuctionEnd(): noBids not defined in arguments.'); } // Get bid data from bids sent @@ -321,7 +419,7 @@ function onAuctionEnd(args) { } }); } else { - logError(CNVR_CONSTANTS.LOG_PREFIX + 'onAuctionEnd(): bidsReceived not defined in arguments.'); + logWarn(CNVR_CONSTANTS.LOG_PREFIX + 'onAuctionEnd(): bidsReceived not defined in arguments.'); } // We need to remove any duplicate ad sizes from merging ad-slots or overlap in different media types and also // media-types from merged ad-slots in twin bids. @@ -423,7 +521,8 @@ cnvrHelper.createPayload = function(payloadType, auctionId, timestamp) { sid: initOptions.site_id, auctionTimestamp: timestamp }, - adUnits: {} + adUnits: {}, + bidderErrors: [] }; }; @@ -488,13 +587,49 @@ cnvrHelper.getSampleRate = function(parentObj, propNm, defaultSampleRate) { return sampleRate; } +/** + * Helper to encapsulate logic for getting best known page url. Small but helpful in debugging/testing and if we ever want + * to add more logic to this. + * + * From getRefererInfo(): page = the best candidate for the current page URL: `canonicalUrl`, falling back to `location` + * @returns {*} Best guess at top URL based on logic from RefererInfo. + */ +cnvrHelper.getPageUrl = function() { + return getRefererInfo().page; +} + +/** + * Packages up an error that occured in analytics handling and sends it back to our servers for logging + * @param eventType = original event that was fired + * @param exception = {stack:"...",message:"..."}, exception that was triggered + */ +cnvrHelper.sendErrorData = function(eventType, exception) { + if (!cnvrHelper.doSendErrorData) { + logWarn(CNVR_CONSTANTS.LOG_PREFIX + 'Skipping sending error data due to config disabling error logging, error thrown = ' + exception); + return; + } + + let error = { + event: eventType, + siteId: initOptions.site_id, + message: exception.message, + stack: exception.stack, + prebidVersion: '$$REPO_AND_VERSION$$', // testing val sample: prebid_prebid_7.27.0-pre' + userAgent: navigator.userAgent, + url: cnvrHelper.getPageUrl() + }; + + // eslint-disable-next-line no-undef + ajax(ERROR_URL, function () {}, JSON.stringify(error), {contentType: 'text/plain'}); +} + /** * Helper function to send data back to server. Need to make sure we don't trigger a CORS preflight by not adding * extra header params. * @param payload our JSON payload from either AUCTION END, BID WIN, RENDER FAILED */ function sendData(payload) { - ajax(URL, function () {}, JSON.stringify(payload), {contentType: 'text/plain'}); + ajax(ANALYTICS_URL, function () {}, JSON.stringify(payload), {contentType: 'text/plain'}); } // =============================== BOILERPLATE FOR PRE-BID ANALYTICS SETUP ============================================ @@ -515,6 +650,7 @@ conversantAnalytics.enableAnalytics = function (config) { cnvrHelper.cleanCache(cnvrHelper.adIdLookup, currTime); cnvrHelper.cleanCache(cnvrHelper.timeoutCache, currTime); cnvrHelper.cleanCache(cnvrHelper.auctionIdTimestampCache, currTime); + cnvrHelper.cleanCache(cnvrHelper.bidderErrorCache, currTime); }, CNVR_CONSTANTS.CACHE_CLEANUP_TIME_IN_MILLIS ); @@ -529,6 +665,10 @@ conversantAnalytics.enableAnalytics = function (config) { // Math.random() pseudo-random number in the range 0 to less than 1 (inclusive of 0, but not 1) cnvrHelper.doSample = Math.random() < initOptions.cnvr_sample_rate; + if (initOptions.send_error_data !== undefined && initOptions.send_error_data !== null) { + cnvrHelper.doSendErrorData = !!initOptions.send_error_data; // Forces data into boolean type + } + conversantAnalyticsEnabled = true; conversantAnalytics.originEnableAnalytics(config); // call the base class function }; @@ -546,15 +686,17 @@ conversantAnalytics.disableAnalytics = function () { cnvrHelper.timeoutCache = {}; cnvrHelper.adIdLookup = {}; cnvrHelper.auctionIdTimestampCache = {}; + cnvrHelper.bidderErrorCache = {}; conversantAnalyticsEnabled = false; conversantAnalytics.originDisableAnalytics(); }; - -adapterManager.registerAnalyticsAdapter({ - adapter: conversantAnalytics, - code: ANALYTICS_CODE, - gvlid: GVLID +ANALYTICS_ALIASES.forEach(alias => { + adapterManager.registerAnalyticsAdapter({ + adapter: conversantAnalytics, + code: alias, + gvlid: GVLID + }); }); export default conversantAnalytics; diff --git a/modules/conversantAnalyticsAdapter.md b/modules/conversantAnalyticsAdapter.md deleted file mode 100644 index 2f026cbcbb9..00000000000 --- a/modules/conversantAnalyticsAdapter.md +++ /dev/null @@ -1,46 +0,0 @@ -# Overview -- Module Name: Epsilon Analytics Adapter -- Module Type: Analytics Adapter -- Maintainer: mediapsr@epsilon.com - -## Description - -Analytics adapter for Epsilon (formerly Conversant) is used to track performance of Prebid auctions. See the usage below for how to -configure the adapter for your webpage. To enable analytics and gain access to the data publishers will need - to contact their Epsilon representative (publishersupport@epsilon.com). - -## Setup - -Before any analytics are recorded for your website you will need to have an Epsilon representative turn -on Prebid analytics for your website. - -The simplest configuration to add Epsilon Prebid analytics to your page is as follows: - -``` - pbjs.que.push(function() { - pbjs.enableAnalytics({ - provider: 'conversant', - options: { - site_id: - } - }) - }); -``` - -Additionally, the following options are supported: - -- **cnvr_sampling**: Sample rate for analytics data. Value should be between 0 and 1 (inclusive), 0 == never sample, -1 == always sample, 0.5 == send analytics 50% of the time. - -### Complete Example -``` - pbjs.que.push(function() { - pbjs.enableAnalytics({ - provider: 'conversant', - options: { - site_id: 1234, - cnvr_sampling: 0.9 - } - }) - }); -``` diff --git a/modules/conversantBidAdapter.js b/modules/conversantBidAdapter.js index 41a8200c6ea..fd436e51461 100644 --- a/modules/conversantBidAdapter.js +++ b/modules/conversantBidAdapter.js @@ -1,8 +1,27 @@ -import { logWarn, isStr, deepAccess, isArray, getBidIdParameter, deepSetValue, isEmpty, _each, convertTypes, parseUrl, mergeDeep, buildUrl, _map, logError, isFn, isPlainObject } from '../src/utils.js'; +import { + logWarn, + isStr, + deepAccess, + isArray, + getBidIdParameter, + deepSetValue, + isEmpty, + _each, + convertTypes, + parseUrl, + mergeDeep, + buildUrl, + _map, + logError, + isFn, + isPlainObject, +} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {getStorageManager} from '../src/storageManager.js'; +// Maintainer: mediapsr@epsilon.com + const GVLID = 24; const BIDDER_CODE = 'conversant'; @@ -12,7 +31,7 @@ const URL = 'https://web.hb.ad.cpe.dotomi.com/cvx/client/hb/ortb/25'; export const spec = { code: BIDDER_CODE, gvlid: GVLID, - aliases: ['cnvr'], // short code + aliases: ['cnvr', 'epsilon'], // short code supportedMediaTypes: [BANNER, VIDEO], /** @@ -56,7 +75,6 @@ export const spec = { buildRequests: function(validBidRequests, bidderRequest) { const page = (bidderRequest && bidderRequest.refererInfo) ? bidderRequest.refererInfo.page : ''; let siteId = ''; - let requestId = ''; let pubcid = null; let pubcidName = '_pubcid'; let bidurl = URL; @@ -67,8 +85,6 @@ export const spec = { siteId = getBidIdParameter('site_id', bid.params) || siteId; pubcidName = getBidIdParameter('pubcid_name', bid.params) || pubcidName; - requestId = bid.auctionId; - const imp = { id: bid.bidId, secure: 1, @@ -122,8 +138,11 @@ export const spec = { }); const payload = { - id: requestId, + id: bidderRequest.bidderRequestId, imp: conversantImps, + source: { + tid: bidderRequest.ortb2?.source?.tid, + }, site: { id: siteId, mobile: document.querySelector('meta[name="viewport"][content*="width=device-width"]') !== null ? 1 : 0, @@ -142,6 +161,10 @@ export const spec = { } if (bidderRequest) { + if (bidderRequest.timeout) { + deepSetValue(payload, 'tmax', bidderRequest.timeout); + } + // Add GDPR flag and consent string if (bidderRequest.gdprConsent) { userExt.consent = bidderRequest.gdprConsent.consentString; diff --git a/modules/conversantBidAdapter.md b/modules/conversantBidAdapter.md deleted file mode 100644 index baf8b756aca..00000000000 --- a/modules/conversantBidAdapter.md +++ /dev/null @@ -1,46 +0,0 @@ -# Overview - -- Module Name: Epsilon Bidder Adapter -- Module Type: Bidder Adapter -- Maintainer: mediapsr@epsilon.com - -# Description - -Module that connects to Epsilon's (formerly Conversant) demand sources. Supports banners and videos. - -# Test Parameters -``` -var adUnits = [ - { - code: 'banner-test-div', - mediaTypes: { - banner: { - sizes: [[300, 250],[300,600]] - } - }, - bids: [{ - bidder: "conversant", - params: { - site_id: '108060' - } - }] - },{ - code: 'video-test-div', - mediaTypes: { - video: { - context: 'instream', - playerSize: [640, 480], - api: [2], - protocols: [1, 2], - mimes: ['video/mp4'] - } - }, - bids: [{ - bidder: "conversant", - params: { - site_id: '108060', - white_label_url: 'https://web.hb.ad.cpe.dotomi.com/s2s/header/24' - } - }] - }]; -``` diff --git a/modules/cpexIdSystem.md b/modules/cpexIdSystem.md deleted file mode 100644 index 8aceb7fe4ec..00000000000 --- a/modules/cpexIdSystem.md +++ /dev/null @@ -1,27 +0,0 @@ -## CPEx User ID Submodule - -CPExID is provided by [Czech Publisher Exchange](https://www.cpex.cz/), or CPEx. It is a user ID for ad targeting by using first party cookie, or localStorage mechanism. Please contact CPEx before using this ID. - -## Building Prebid with CPExID Support - -First, make sure to add the cpexId to your Prebid.js package with: - -``` -gulp build --modules=cpexIdSystem -``` - -The following configuration parameters are available: - -```javascript -pbjs.setConfig({ - userSync: { - userIds: [{ - name: 'cpexId' - }] - } -}); -``` - -| Param under userSync.userIds[] | Scope | Type | Description | Example | -| --- | --- | --- | --- | --- | -| name | Required | String | The name of this module. | `"cpexId"` | diff --git a/modules/craftBidAdapter.js b/modules/craftBidAdapter.js index 519c4dd9b6f..8f7821173c1 100644 --- a/modules/craftBidAdapter.js +++ b/modules/craftBidAdapter.js @@ -1,12 +1,4 @@ -import { - convertCamelToUnderscore, - convertTypes, - getBidRequest, - isArray, - isEmpty, - logError, - transformBidderParamKeywords -} from '../src/utils.js'; +import {convertCamelToUnderscore, convertTypes, getBidRequest, logError} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; import {auctionManager} from '../src/auctionManager.js'; @@ -14,7 +6,8 @@ import {find, includes} from '../src/polyfill.js'; import {getStorageManager} from '../src/storageManager.js'; import {ajax} from '../src/ajax.js'; import {hasPurpose1Consent} from '../src/utils/gpdr.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import {convertOrtbRequestToProprietaryNative} from '../src/native.js'; +import {getANKeywordParam, transformBidderParamKeywords} from '../libraries/appnexusKeywords/anKeywords.js'; const BIDDER_CODE = 'craft'; const URL_BASE = 'https://gacraft.jp/prebid-v3'; @@ -33,9 +26,9 @@ export const spec = { buildRequests: function(bidRequests, bidderRequest) { // convert Native ORTB definition to old-style prebid native definition bidRequests = convertOrtbRequestToProprietaryNative(bidRequests); - + const bidRequest = bidRequests[0]; const tags = bidRequests.map(bidToTag); - const schain = bidRequests[0].schain; + const schain = bidRequest.schain; const payload = { tags: [...tags], ua: navigator.userAgent, @@ -44,26 +37,31 @@ export const spec = { }, schain: schain }; - if (bidderRequest && bidderRequest.gdprConsent) { - payload.gdpr_consent = { - consent_string: bidderRequest.gdprConsent.consentString, - consent_required: bidderRequest.gdprConsent.gdprApplies - }; - } - if (bidderRequest && bidderRequest.uspConsent) { - payload.us_privacy = bidderRequest.uspConsent; - } - if (bidderRequest && bidderRequest.refererInfo) { - let refererinfo = { - // TODO: this collects everything it finds, except for the canonical URL - rd_ref: bidderRequest.refererInfo.topmostLocation, - rd_top: bidderRequest.refererInfo.reachedTop, - rd_ifs: bidderRequest.refererInfo.numIframes, - }; - if (bidderRequest.refererInfo.stack) { - refererinfo.rd_stk = bidderRequest.refererInfo.stack.join(','); + if (bidderRequest) { + if (bidderRequest.gdprConsent) { + payload.gdpr_consent = { + consent_string: bidderRequest.gdprConsent.consentString, + consent_required: bidderRequest.gdprConsent.gdprApplies + }; + } + if (bidderRequest.uspConsent) { + payload.us_privacy = bidderRequest.uspConsent; + } + if (bidderRequest.refererInfo) { + let refererinfo = { + // TODO: this collects everything it finds, except for the canonical URL + rd_ref: bidderRequest.refererInfo.topmostLocation, + rd_top: bidderRequest.refererInfo.reachedTop, + rd_ifs: bidderRequest.refererInfo.numIframes, + }; + if (bidderRequest.refererInfo.stack) { + refererinfo.rd_stk = bidderRequest.refererInfo.stack.join(','); + } + payload.referrer_detection = refererinfo; + } + if (bidRequest.userId) { + payload.userId = bidRequest.userId } - payload.referrer_detection = refererinfo; } const request = formatRequest(payload, bidderRequest); return request; @@ -106,12 +104,9 @@ export const spec = { params = convertTypes({ 'sitekey': 'string', 'placementId': 'string', - 'keywords': transformBidderParamKeywords + 'keywords': transformBidderParamKeywords, }, params); if (isOpenRtb) { - if (isPopulatedArray(params.keywords)) { - params.keywords.forEach(deleteValues); - } Object.keys(params).forEach(paramKey => { let convertedKey = convertCamelToUnderscore(paramKey); if (convertedKey !== paramKey) { @@ -131,16 +126,6 @@ export const spec = { } }; -function isPopulatedArray(arr) { - return !!(isArray(arr) && arr.length > 0); -} - -function deleteValues(keyPairObj) { - if (isPopulatedArray(keyPairObj.value) && keyPairObj.value[0] === '') { - delete keyPairObj.value; - } -} - function formatRequest(payload, bidderRequest) { let options = {}; if (!hasPurpose1Consent(bidderRequest?.gdprConsent)) { @@ -148,11 +133,11 @@ function formatRequest(payload, bidderRequest) { withCredentials: false }; } - + const baseUrl = payload.tags[0].url || URL_BASE; const payloadString = JSON.stringify(payload); return { method: 'POST', - url: `${URL_BASE}/${payload.tags[0].sitekey}`, + url: `${baseUrl}/${payload.tags[0].sitekey}`, data: payloadString, bidderRequest, options @@ -195,13 +180,11 @@ function bidToTag(bid) { tag.primary_size = tag.sizes[0]; tag.ad_types = []; tag.uuid = bid.bidId; - if (!isEmpty(bid.params.keywords)) { - let keywords = transformBidderParamKeywords(bid.params.keywords); - if (keywords.length > 0) { - keywords.forEach(deleteValues); - } + const keywords = getANKeywordParam(bid.ortb2, bid.params.keywords); + if (keywords.length) { tag.keywords = keywords; } + // TODO: why does this need to iterate through every ad unit? let adUnit = find(auctionManager.getAdUnits(), au => bid.transactionId === au.transactionId); if (adUnit && adUnit.mediaTypes && adUnit.mediaTypes.banner) { tag.ad_types.push(BANNER); diff --git a/modules/criteoBidAdapter.js b/modules/criteoBidAdapter.js index f7ba82d3e06..993346df849 100644 --- a/modules/criteoBidAdapter.js +++ b/modules/criteoBidAdapter.js @@ -1,4 +1,4 @@ -import { deepAccess, isArray, logError, logInfo, logWarn, parseUrl } from '../src/utils.js'; +import { deepAccess, generateUUID, isArray, logError, logInfo, logWarn, parseUrl } from '../src/utils.js'; import { loadExternalScript } from '../src/adloader.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; @@ -6,17 +6,18 @@ import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { find } from '../src/polyfill.js'; import { verify } from 'criteo-direct-rsa-validate/build/verify.js'; // ref#2 import { getStorageManager } from '../src/storageManager.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; import { getRefererInfo } from '../src/refererDetection.js'; import { hasPurpose1Consent } from '../src/utils/gpdr.js'; +import { Renderer } from '../src/Renderer.js'; +import { OUTSTREAM } from '../src/video.js'; const GVLID = 91; -export const ADAPTER_VERSION = 35; +export const ADAPTER_VERSION = 36; const BIDDER_CODE = 'criteo'; const CDB_ENDPOINT = 'https://bidder.criteo.com/cdb'; const PROFILE_ID_INLINE = 207; export const PROFILE_ID_PUBLISHERTAG = 185; -export const storage = getStorageManager({ gvlid: GVLID, bidderCode: BIDDER_CODE }); +export const storage = getStorageManager({ bidderCode: BIDDER_CODE }); const LOG_PREFIX = 'Criteo: '; /* @@ -27,16 +28,14 @@ const LOG_PREFIX = 'Criteo: '; Unminified source code can be found in the privately shared repo: https://github.com/Prebid-org/prebid-js-external-js-criteo/blob/master/dist/prod.js */ const FAST_BID_VERSION_PLACEHOLDER = '%FAST_BID_VERSION%'; -export const FAST_BID_VERSION_CURRENT = 135; +export const FAST_BID_VERSION_CURRENT = 136; const FAST_BID_VERSION_LATEST = 'latest'; const FAST_BID_VERSION_NONE = 'none'; const PUBLISHER_TAG_URL_TEMPLATE = 'https://static.criteo.net/js/ld/publishertag.prebid' + FAST_BID_VERSION_PLACEHOLDER + '.js'; +const PUBLISHER_TAG_OUTSTREAM_SRC = 'https://static.criteo.net/js/ld/publishertag.renderer.js' const FAST_BID_PUBKEY_E = 65537; const FAST_BID_PUBKEY_N = 'ztQYwCE5BU7T9CDM5he6rKoabstXRmkzx54zFPZkWbK530dwtLBDeaWBMxHBUT55CYyboR/EZ4efghPi3CoNGfGWezpjko9P6p2EwGArtHEeS4slhu/SpSIFMjG6fdrpRoNuIAMhq1Z+Pr/+HOd1pThFKeGFr2/NhtAg+TXAzaU='; -const SID_COOKIE_NAME = 'cto_sid'; -const IDCPY_COOKIE_NAME = 'cto_idcpy'; -const LWID_COOKIE_NAME = 'cto_lwid'; const OPTOUT_COOKIE_NAME = 'cto_optout'; const BUNDLE_COOKIE_NAME = 'cto_bundle'; const GUID_RETENTION_TIME_HOUR = 24 * 30 * 13; // 13 months @@ -78,15 +77,12 @@ export const spec = { const jsonHash = { bundle: readFromAllStorages(BUNDLE_COOKIE_NAME), cw: storage.cookiesAreEnabled(), - localWebId: readFromAllStorages(LWID_COOKIE_NAME), lsw: storage.localStorageIsEnabled(), optoutCookie: readFromAllStorages(OPTOUT_COOKIE_NAME), origin: origin, requestId: requestId, - secureIdCookie: readFromAllStorages(SID_COOKIE_NAME), tld: refererInfo.domain, topUrl: refererInfo.domain, - uid: readFromAllStorages(IDCPY_COOKIE_NAME), version: '$prebid.version$'.replace(/\./g, '_'), }; @@ -106,26 +102,13 @@ export const spec = { const response = event.data; if (response.optout) { - deleteFromAllStorages(IDCPY_COOKIE_NAME); - deleteFromAllStorages(SID_COOKIE_NAME); deleteFromAllStorages(BUNDLE_COOKIE_NAME); - deleteFromAllStorages(LWID_COOKIE_NAME); saveOnAllStorages(OPTOUT_COOKIE_NAME, true, OPTOUT_RETENTION_TIME_HOUR); } else { - if (response.uid) { - saveOnAllStorages(IDCPY_COOKIE_NAME, response.uid, GUID_RETENTION_TIME_HOUR); - } - if (response.bundle) { saveOnAllStorages(BUNDLE_COOKIE_NAME, response.bundle, GUID_RETENTION_TIME_HOUR); } - - if (response.removeSid) { - deleteFromAllStorages(SID_COOKIE_NAME); - } else if (response.sid) { - saveOnAllStorages(SID_COOKIE_NAME, response.sid, GUID_RETENTION_TIME_HOUR); - } } }, true); @@ -165,9 +148,6 @@ export const spec = { * @return {ServerRequest} */ buildRequests: (bidRequests, bidderRequest) => { - // convert Native ORTB definition to old-style prebid native definition - bidRequests = convertOrtbRequestToProprietaryNative(bidRequests); - let url; let data; let fpd = bidderRequest.ortb2 || {}; @@ -197,7 +177,14 @@ export const spec = { if (publisherTagAvailable()) { // eslint-disable-next-line no-undef - const adapter = new Criteo.PubTag.Adapters.Prebid(PROFILE_ID_PUBLISHERTAG, ADAPTER_VERSION, bidRequests, bidderRequest, '$prebid.version$'); + const adapter = new Criteo.PubTag.Adapters.Prebid( + PROFILE_ID_PUBLISHERTAG, + ADAPTER_VERSION, + bidRequests, + bidderRequest, + '$prebid.version$', + { createOutstreamVideoRenderer: createOutstreamVideoRenderer } + ); url = adapter.buildCdbUrl(); data = adapter.buildCdbRequest(); } else { @@ -242,7 +229,7 @@ export const spec = { creativeId: slot.creativecode, width: slot.width, height: slot.height, - dealId: slot.dealCode, + dealId: slot.deal, }; if (body.ext?.paf?.transmission && slot.ext?.paf?.content_id) { const pafResponseMeta = { @@ -252,7 +239,10 @@ export const spec = { bid.meta = Object.assign({}, bid.meta, { paf: pafResponseMeta }); } if (slot.adomain) { - bid.meta = Object.assign({}, bid.meta, { advertiserDomains: slot.adomain }); + bid.meta = Object.assign({}, bid.meta, { advertiserDomains: [slot.adomain].flat() }); + } + if (slot.ext?.meta?.networkName) { + bid.meta = Object.assign({}, bid.meta, { networkName: slot.ext.meta.networkName }) } if (slot.native) { if (bidRequest.params.nativeCallback) { @@ -264,6 +254,11 @@ export const spec = { } else if (slot.video) { bid.vastUrl = slot.displayurl; bid.mediaType = VIDEO; + const context = deepAccess(bidRequest, 'mediaTypes.video.context'); + // if outstream video, add a default render for it. + if (context === OUTSTREAM) { + bid.renderer = createOutstreamVideoRenderer(slot); + } } else { bid.ad = slot.creative; } @@ -273,7 +268,6 @@ export const spec = { return bids; }, - /** * @param {TimedOutBid} timeoutData */ @@ -407,16 +401,6 @@ function buildCdbUrl(context) { url += `&optout=1`; } - const sid = readFromAllStorages(SID_COOKIE_NAME); - if (sid) { - url += `&sid=${sid}`; - } - - const idcpy = readFromAllStorages(IDCPY_COOKIE_NAME); - if (idcpy) { - url += `&idcpy=${idcpy}`; - } - return url; } @@ -441,21 +425,27 @@ function checkNativeSendId(bidRequest) { function buildCdbRequest(context, bidRequests, bidderRequest) { let networkId; let schain; + let userIdAsEids; const request = { + id: generateUUID(), publisher: { url: context.url, ext: bidderRequest.publisherExt, }, regs: { - coppa: bidderRequest.coppa === true ? 1 : (bidderRequest.coppa === false ? 0 : undefined) + coppa: bidderRequest.coppa === true ? 1 : (bidderRequest.coppa === false ? 0 : undefined), + gpp: bidderRequest.ortb2?.regs?.gpp, + gpp_sid: bidderRequest.ortb2?.regs?.gpp_sid }, slots: bidRequests.map(bidRequest => { + if (!userIdAsEids) { + userIdAsEids = bidRequest.userIdAsEids; + } networkId = bidRequest.params.networkId || networkId; schain = bidRequest.schain || schain; const slot = { impid: bidRequest.adUnitCode, - transactionid: bidRequest.transactionId, - auctionId: bidRequest.auctionId, + transactionid: bidRequest.ortb2Imp?.ext?.tid }; if (bidRequest.params.zoneId) { slot.zoneid = bidRequest.params.zoneId; @@ -463,9 +453,17 @@ function buildCdbRequest(context, bidRequests, bidderRequest) { if (deepAccess(bidRequest, 'ortb2Imp.ext')) { slot.ext = bidRequest.ortb2Imp.ext; } + + if (deepAccess(bidRequest, 'ortb2Imp.rwdd')) { + slot.rwdd = bidRequest.ortb2Imp.rwdd; + } + if (bidRequest.params.ext) { slot.ext = Object.assign({}, slot.ext, bidRequest.params.ext); } + if (bidRequest.nativeOrtbRequest?.assets) { + slot.ext = Object.assign({}, slot.ext, { assets: bidRequest.nativeOrtbRequest.assets }); + } if (bidRequest.params.publisherSubId) { slot.publishersubid = bidRequest.params.publisherSubId; } @@ -494,7 +492,8 @@ function buildCdbRequest(context, bidRequests, bidderRequest) { placement: bidRequest.mediaTypes.video.placement, minduration: bidRequest.mediaTypes.video.minduration, playbackmethod: bidRequest.mediaTypes.video.playbackmethod, - startdelay: bidRequest.mediaTypes.video.startdelay + startdelay: bidRequest.mediaTypes.video.startdelay, + plcmt: bidRequest.mediaTypes.video.plcmt }; const paramsVideo = bidRequest.params.video; if (paramsVideo !== undefined) { @@ -516,11 +515,14 @@ function buildCdbRequest(context, bidRequests, bidderRequest) { if (networkId) { request.publisher.networkid = networkId; } + + request.source = { + tid: bidderRequest.ortb2?.source?.tid + }; + if (schain) { - request.source = { - ext: { - schain: schain - } + request.source.ext = { + schain: schain }; }; request.user = bidderRequest.ortb2?.user || {}; @@ -541,6 +543,23 @@ function buildCdbRequest(context, bidRequests, bidderRequest) { if (bidderRequest && bidderRequest.uspConsent) { request.user.uspIab = bidderRequest.uspConsent; } + if (bidderRequest && bidderRequest.ortb2?.device?.sua) { + request.user.ext = request.user.ext || {}; + request.user.ext.sua = bidderRequest.ortb2?.device?.sua || {}; + } + if (userIdAsEids) { + request.user.ext = request.user.ext || {}; + request.user.ext.eids = [...userIdAsEids]; + } + if (bidderRequest && bidderRequest.ortb2?.bcat) { + request.bcat = bidderRequest.ortb2.bcat; + } + if (bidderRequest && bidderRequest.ortb2?.badv) { + request.badv = bidderRequest.ortb2.badv; + } + if (bidderRequest && bidderRequest.ortb2?.bapp) { + request.bapp = bidderRequest.ortb2.bapp; + } return request; } @@ -720,6 +739,35 @@ export function getFastBidUrl(fastBidVersion) { return PUBLISHER_TAG_URL_TEMPLATE.replace(FAST_BID_VERSION_PLACEHOLDER, version); } +function createOutstreamVideoRenderer(slot) { + if (slot.ext.videoPlayerConfig === undefined || slot.ext.videoPlayerType === undefined) { + return undefined; + } + + const config = { + documentResolver: (bid, sourceDocument, renderDocument) => { + return renderDocument ?? sourceDocument; + } + } + + const render = (bid, renderDocument) => { + let payload = { + slotid: slot.impid, + vastUrl: slot.displayurl, + vastXml: slot.creative, + documentContext: renderDocument, + }; + + let outstreamConfig = slot.ext.videoPlayerConfig; + + window.CriteoOutStream[slot.ext.videoPlayerType].play(payload, outstreamConfig) + }; + + const renderer = Renderer.install({ url: PUBLISHER_TAG_OUTSTREAM_SRC, config: config }); + renderer.setRender(render); + return renderer; +} + export function tryGetCriteoFastBid() { // begin ref#1 try { diff --git a/modules/criteoIdSystem.js b/modules/criteoIdSystem.js index 867e4315945..b48612203f8 100644 --- a/modules/criteoIdSystem.js +++ b/modules/criteoIdSystem.js @@ -10,10 +10,12 @@ import { ajax } from '../src/ajax.js'; import { getRefererInfo } from '../src/refererDetection.js'; import { submodule } from '../src/hook.js'; import { getStorageManager } from '../src/storageManager.js'; +import { MODULE_TYPE_UID } from '../src/activities/modules.js'; +import { gdprDataHandler, uspDataHandler, gppDataHandler } from '../src/adapterManager.js'; const gvlid = 91; const bidderCode = 'criteo'; -export const storage = getStorageManager({ gvlid: gvlid, moduleName: bidderCode }); +export const storage = getStorageManager({ moduleType: MODULE_TYPE_UID, moduleName: bidderCode }); const bididStorageKey = 'cto_bidid'; const bundleStorageKey = 'cto_bundle'; @@ -76,17 +78,33 @@ function getCriteoDataFromAllStorages() { } } -function buildCriteoUsersyncUrl(topUrl, domain, bundle, dnaBundle, areCookiesWriteable, isLocalStorageWritable, isPublishertagPresent, gdprString) { - const url = 'https://gum.criteo.com/sid/json?origin=prebid' + +function buildCriteoUsersyncUrl(topUrl, domain, bundle, dnaBundle, areCookiesWriteable, isLocalStorageWritable, isPublishertagPresent) { + let url = 'https://gum.criteo.com/sid/json?origin=prebid' + `${topUrl ? '&topUrl=' + encodeURIComponent(topUrl) : ''}` + `${domain ? '&domain=' + encodeURIComponent(domain) : ''}` + `${bundle ? '&bundle=' + encodeURIComponent(bundle) : ''}` + `${dnaBundle ? '&info=' + encodeURIComponent(dnaBundle) : ''}` + - `${gdprString ? '&gdprString=' + encodeURIComponent(gdprString) : ''}` + `${areCookiesWriteable ? '&cw=1' : ''}` + `${isPublishertagPresent ? '&pbt=1' : ''}` + `${isLocalStorageWritable ? '&lsw=1' : ''}`; + const usPrivacyString = uspDataHandler.getConsentData(); + if (usPrivacyString) { + url = url + `&us_privacy=${encodeURIComponent(usPrivacyString)}`; + } + + const gdprConsent = gdprDataHandler.getConsentData() + if (gdprConsent) { + url = url + `${gdprConsent.consentString ? '&gdprString=' + encodeURIComponent(gdprConsent.consentString) : ''}`; + url = url + `&gdpr=${gdprConsent.gdprApplies === true ? 1 : 0}`; + } + + const gppConsent = gppDataHandler.getConsentData(); + if (gppConsent) { + url = url + `${gppConsent.gppString ? '&gpp=' + encodeURIComponent(gppConsent.gppString) : ''}`; + url = url + `${gppConsent.applicableSections ? '&gpp_sid=' + encodeURIComponent(gppConsent.applicableSections) : ''}`; + } + return url; } @@ -102,6 +120,9 @@ function callSyncPixel(domain, pixel) { saveOnAllStorages(pixel.storageKeyName, jsonResponse[pixel.bundlePropertyName], domain); } } + }, + error: error => { + logError(`criteoIdSystem: unable to sync user id`, error); } }, undefined, @@ -112,7 +133,7 @@ function callSyncPixel(domain, pixel) { } } -function callCriteoUserSync(parsedCriteoData, gdprString, callback) { +function callCriteoUserSync(parsedCriteoData, callback) { const cw = storage.cookiesAreEnabled(); const lsw = storage.localStorageIsEnabled(); const topUrl = extractProtocolHost(getRefererInfo().page); @@ -127,8 +148,7 @@ function callCriteoUserSync(parsedCriteoData, gdprString, callback) { parsedCriteoData.dnaBundle, cw, lsw, - isPublishertagPresent, - gdprString + isPublishertagPresent ); const callbacks = { @@ -187,13 +207,10 @@ export const criteoIdSubmodule = { * @param {ConsentData} [consentData] * @returns {{id: {criteoId: string} | undefined}}} */ - getId(config, consentData) { - const hasGdprData = consentData && typeof consentData.gdprApplies === 'boolean' && consentData.gdprApplies; - const gdprConsentString = hasGdprData ? consentData.consentString : undefined; - + getId() { let localData = getCriteoDataFromAllStorages(); - const result = (callback) => callCriteoUserSync(localData, gdprConsentString, callback); + const result = (callback) => callCriteoUserSync(localData, callback); return { id: localData.bidId ? { criteoId: localData.bidId } : undefined, diff --git a/modules/currency.js b/modules/currency.js index 9272a507d05..3da0cfe73e8 100644 --- a/modules/currency.js +++ b/modules/currency.js @@ -238,9 +238,9 @@ function wrapFunction(fn, context, params) { bid.currency = adServerCurrency; } } catch (e) { - logWarn('Returning NO_BID, getCurrencyConversion threw error: ', e); - // TODO: in v8, this should not continue with a "NO_BID" - params[1] = params[2](CONSTANTS.REJECTION_REASON.CANNOT_CONVERT_CURRENCY); + logWarn('getCurrencyConversion threw error: ', e); + params[2](CONSTANTS.REJECTION_REASON.CANNOT_CONVERT_CURRENCY); + return; } } return fn.apply(context, params); diff --git a/modules/cpexIdSystem.js b/modules/czechAdIdSystem.js similarity index 68% rename from modules/cpexIdSystem.js rename to modules/czechAdIdSystem.js index 5c2e6bc212d..957b3ed30bd 100644 --- a/modules/cpexIdSystem.js +++ b/modules/czechAdIdSystem.js @@ -1,27 +1,28 @@ /** * This module adds 'caid' to the User ID module * The {@link module:modules/userId} module is required - * @module modules/cpexIdSystem + * @module modules/czechAdIdSystem * @requires module:modules/userId */ import { submodule } from '../src/hook.js' -import { getStorageManager } from '../src/storageManager.js' +import {getStorageManager} from '../src/storageManager.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; // Returns StorageManager -export const storage = getStorageManager({ gvlid: 570, moduleName: 'cpexId' }) +export const storage = getStorageManager({ moduleType: MODULE_TYPE_UID, moduleName: 'czechAdId' }) // Returns the id string from either cookie or localstorage const readId = () => { return storage.getCookie('czaid') || storage.getDataFromLocalStorage('czaid') } /** @type {Submodule} */ -export const cpexIdSubmodule = { - version: '0.0.5', +export const czechAdIdSubmodule = { + version: '0.1.0', /** * used to link submodule with config * @type {string} */ - name: 'cpexId', + name: 'czechAdId', /** * Vendor ID of Czech Publisher Exchange * @type {Number} @@ -32,7 +33,7 @@ export const cpexIdSubmodule = { * @function decode * @returns {(Object|undefined)} */ - decode () { return { cpexId: readId() } }, + decode () { return { czechAdId: readId() } }, /** * performs action to obtain id and return a value in the callback's response argument * @function @@ -44,4 +45,4 @@ export const cpexIdSubmodule = { } } -submodule('userId', cpexIdSubmodule) +submodule('userId', czechAdIdSubmodule) diff --git a/modules/czechAdIdSystem.md b/modules/czechAdIdSystem.md new file mode 100644 index 00000000000..5614016f524 --- /dev/null +++ b/modules/czechAdIdSystem.md @@ -0,0 +1,27 @@ +## CzechAdId User ID Submodule + +Czech Ad ID is a joint project of publishers of the [CPEx alliance](https://www.cpex.cz/) and [Seznam.cz](https://www.seznam.cz). It is a deterministic user ID that offers cross-domain and cross-device identification. For more information see [czechadid.cz](https://www.czechadid.cz)). + +## Building Prebid with CzechAdId Support + +First, make sure to add the czechAdId to your Prebid.js package with: + +``` +gulp build --modules=czechAdIdSystem +``` + +The following configuration parameters are available: + +```javascript +pbjs.setConfig({ + userSync: { + userIds: [{ + name: 'czechAdId' + }] + } +}); +``` + +| Param under userSync.userIds[] | Scope | Type | Description | Example | +| --- | --- | --- | --- | --- | +| name | Required | String | The name of this module. | `"czechAdId"` | diff --git a/modules/dacIdSystem.js b/modules/dacIdSystem.js index 856e1976bb1..5adca074c87 100644 --- a/modules/dacIdSystem.js +++ b/modules/dacIdSystem.js @@ -19,8 +19,9 @@ import { import { getStorageManager } from '../src/storageManager.js'; - -export const storage = getStorageManager(); +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; +const MODULE_NAME = 'dacId'; +export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); export const FUUID_COOKIE_NAME = '_a1_f'; export const AONEID_COOKIE_NAME = '_a1_d'; @@ -116,7 +117,7 @@ export const dacIdSystemSubmodule = { * used to link submodule with config * @type {string} */ - name: 'dacId', + name: MODULE_NAME, /** * decode the stored id value for passing to bid requests diff --git a/modules/dailyhuntBidAdapter.js b/modules/dailyhuntBidAdapter.js index 5ae8ee3ea21..f96e07b71bf 100644 --- a/modules/dailyhuntBidAdapter.js +++ b/modules/dailyhuntBidAdapter.js @@ -4,7 +4,7 @@ import {_map, deepAccess, isEmpty} from '../src/utils.js'; import {ajax} from '../src/ajax.js'; import {find} from '../src/polyfill.js'; import {INSTREAM, OUTSTREAM} from '../src/video.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import {convertOrtbRequestToProprietaryNative} from '../src/native.js'; const BIDDER_CODE = 'dailyhunt'; const BIDDER_ALIAS = 'dh'; @@ -99,7 +99,7 @@ const createOrtbRequest = (validBidRequests, bidderRequest) => { let user = createOrtbUserObj(validBidRequests) let site = createOrtbSiteObj(validBidRequests, bidderRequest.refererInfo.page) return { - id: bidderRequest.auctionId, + id: bidderRequest.bidderRequestId, imp: [], site, device, diff --git a/modules/datablocksBidAdapter.js b/modules/datablocksBidAdapter.js index 4e21e08ba57..11d3ebb1589 100644 --- a/modules/datablocksBidAdapter.js +++ b/modules/datablocksBidAdapter.js @@ -1,10 +1,11 @@ -import { getWindowTop, isGptPubadsDefined, deepAccess, getAdUnitSizes, isEmpty } from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { config } from '../src/config.js'; -import { BANNER, NATIVE } from '../src/mediaTypes.js'; -import { getStorageManager } from '../src/storageManager.js'; -import { ajax } from '../src/ajax.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import {deepAccess, getAdUnitSizes, getWindowTop, isEmpty, isGptPubadsDefined} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {config} from '../src/config.js'; +import {BANNER, NATIVE} from '../src/mediaTypes.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {ajax} from '../src/ajax.js'; +import {convertOrtbRequestToProprietaryNative} from '../src/native.js'; + export const storage = getStorageManager({bidderCode: 'datablocks'}); const NATIVE_ID_MAP = {}; @@ -229,6 +230,7 @@ export const spec = { let scope = this; if (isGptPubadsDefined()) { if (typeof window['googletag'].pubads().addEventListener == 'function') { + // TODO: fix auctionId leak: https://github.com/prebid/Prebid.js/issues/9781 window['googletag'].pubads().addEventListener('impressionViewable', function(event) { scope.queue_metric({type: 'slot_view', source_id: scope.db_obj.source_id, auction_id: bid.auctionId, div_id: event.slot.getSlotElementId(), slot_id: event.slot.getSlotId().getAdUnitPath()}); }); @@ -400,7 +402,7 @@ export const spec = { method: 'POST', url: `https://${host}/openrtb/?sid=${sourceId}`, data: { - id: bidderRequest.auctionId, + id: bidderRequest.bidderRequestId, imp: imps, site: site, device: device diff --git a/modules/datawrkzBidAdapter.js b/modules/datawrkzBidAdapter.js index 193a1723403..2cf28c36330 100644 --- a/modules/datawrkzBidAdapter.js +++ b/modules/datawrkzBidAdapter.js @@ -414,8 +414,6 @@ function buildBannerResponse(bidRequest, bidResponse) { } let bidSizes = (deepAccess(bidRequest, 'mediaTypes.banner.sizes')) ? deepAccess(bidRequest, 'mediaTypes.banner.sizes') : bidRequest.sizes; bidResponse.requestId = bidRequest.bidId; - bidResponse.auctionId = bidRequest.auctionId; - bidResponse.transactionId = bidRequest.transactionId; bidResponse.placementCode = placementCode; bidResponse.cpm = responseCPM; bidResponse.size = bidSizes; @@ -456,8 +454,6 @@ function buildNativeResponse(bidRequest, response) { return; } bidResponse.requestId = bidRequest.bidId; - bidResponse.auctionId = bidRequest.auctionId; - bidResponse.transactionId = bidRequest.transactionId; bidResponse.placementCode = placementCode; bidResponse.cpm = responseCPM; @@ -509,8 +505,6 @@ function buildVideoResponse(bidRequest, response) { let context = bidRequest.mediaTypes.video.context; bidResponse.requestId = bidRequest.bidId; - bidResponse.auctionId = bidRequest.auctionId; - bidResponse.transactionId = bidRequest.transactionId; bidResponse.placementCode = placementCode; bidResponse.cpm = responseCPM; diff --git a/modules/deepintentDpesIdSystem.js b/modules/deepintentDpesIdSystem.js index 43c7af1b3cc..f2b50d535eb 100644 --- a/modules/deepintentDpesIdSystem.js +++ b/modules/deepintentDpesIdSystem.js @@ -6,10 +6,11 @@ */ import { submodule } from '../src/hook.js'; -import { getStorageManager } from '../src/storageManager.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; const MODULE_NAME = 'deepintentId'; -export const storage = getStorageManager({gvlid: null, moduleName: MODULE_NAME}); +export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); /** @type {Submodule} */ export const deepintentDpesSubmodule = { diff --git a/modules/deltaprojectsBidAdapter.js b/modules/deltaprojectsBidAdapter.js index e40ec58461c..c66e381b8f1 100644 --- a/modules/deltaprojectsBidAdapter.js +++ b/modules/deltaprojectsBidAdapter.js @@ -1,6 +1,15 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER} from '../src/mediaTypes.js'; -import {_each, _map, createTrackPixelHtml, deepAccess, isFn, isNumber, logError, logWarn} from '../src/utils.js'; +import { + _each, + _map, + createTrackPixelHtml, + deepAccess, + isFn, + isNumber, + logError, + logWarn +} from '../src/utils.js'; import {config} from '../src/config.js'; export const BIDDER_CODE = 'deltaprojects'; @@ -27,7 +36,7 @@ function isBidRequestValid(bid) { function buildRequests(validBidRequests, bidderRequest) { /** == shared ==**/ // -- build id - const id = bidderRequest.auctionId; + const id = bidderRequest.bidderRequestId; // -- build site const publisherId = setOnAny(validBidRequests, 'params.publisherId'); @@ -90,7 +99,7 @@ function buildOpenRTBRequest(validBidRequest, id, site, device, user, tmax, regs // build source const source = { - tid: validBidRequest.transactionId, + tid: validBidRequest.auctionId, fd: 1, } diff --git a/modules/dfpAdServerVideo.js b/modules/dfpAdServerVideo.js index 71c9c7aa341..3394fd8b3f4 100644 --- a/modules/dfpAdServerVideo.js +++ b/modules/dfpAdServerVideo.js @@ -12,6 +12,7 @@ import { gdprDataHandler, uspDataHandler, gppDataHandler } from '../src/adapterM import * as events from '../src/events.js'; import CONSTANTS from '../src/constants.json'; import {getPPID} from '../src/adserver.js'; +import {getRefererInfo} from '../src/refererDetection.js'; /** * @typedef {Object} DfpVideoParams @@ -52,6 +53,10 @@ const defaultParamConstants = { export const adpodUtils = {}; +export const dep = { + ri: getRefererInfo +} + /** * Merge all the bid data and publisher-supplied options into a single URL, and then return it. * @@ -259,14 +264,7 @@ function buildUrlFromAdserverUrlComponents(components, bid, options) { * @return {string | undefined} The encoded vast url if it exists, or undefined */ function getDescriptionUrl(bid, components, prop) { - if (config.getConfig('cache.url')) { return; } - - if (!deepAccess(components, `${prop}.description_url`)) { - const vastUrl = bid && bid.vastUrl; - if (vastUrl) { return encodeURIComponent(vastUrl); } - } else { - logError(`input cannnot contain description_url`); - } + return deepAccess(components, `${prop}.description_url`) || dep.ri().page; } /** @@ -293,6 +291,8 @@ function getCustParams(bid, options, urlCustParams) { allTargetingData, adserverTargeting, ); + + // TODO: WTF is this? just firing random events, guessing at the argument, hoping noone notices? events.emit(CONSTANTS.EVENTS.SET_TARGETING, {[adUnit.code]: prebidTargetingSet}); // merge the prebid + publisher targeting sets diff --git a/modules/dianomiBidAdapter.js b/modules/dianomiBidAdapter.js index f8f47df1239..d4b2a4a5da5 100644 --- a/modules/dianomiBidAdapter.js +++ b/modules/dianomiBidAdapter.js @@ -112,7 +112,7 @@ export const spec = { setOnAny(validBidRequests, 'params.pt') || setOnAny(validBidRequests, 'params.priceType') || 'net'; - const tid = bidderRequest.auctionId; + const tid = bidderRequest.ortb2?.source?.tid; const currency = getConfig('currency.adServerCurrency'); const cur = currency && [currency]; const eids = setOnAny(validBidRequests, 'userIdAsEids'); diff --git a/modules/discoveryBidAdapter.js b/modules/discoveryBidAdapter.js index 8145c9d25e7..7ad75f64215 100644 --- a/modules/discoveryBidAdapter.js +++ b/modules/discoveryBidAdapter.js @@ -6,7 +6,7 @@ import { BANNER, NATIVE } from '../src/mediaTypes.js'; const BIDDER_CODE = 'discovery'; const ENDPOINT_URL = 'https://rtb-jp.mediago.io/api/bid?tn='; const TIME_TO_LIVE = 500; -const storage = getStorageManager(); +const storage = getStorageManager({bidderCode: BIDDER_CODE}); let globals = {}; let itemMaps = {}; const MEDIATYPE = [BANNER, NATIVE]; @@ -244,7 +244,9 @@ function getItems(validBidRequests, bidderRequest) { } } if (!matchSize) { - return {}; + matchSize = sizes[0] + ? { h: sizes[0].height || 0, w: sizes[0].width || 0 } + : { h: 0, w: 0 }; } ret = { id: id, @@ -253,9 +255,10 @@ function getItems(validBidRequests, bidderRequest) { h: matchSize.h, w: matchSize.w, pos: 1, + format: sizes, }, ext: {}, - tagid: globals['tagid'], + tagid: req.params && req.params.tagid }; } itemMaps[id] = { @@ -294,9 +297,11 @@ function getParam(validBidRequests, bidderRequest) { const location = utils.deepAccess(bidderRequest, 'refererInfo.referer'); const page = utils.deepAccess(bidderRequest, 'refererInfo.page'); const referer = utils.deepAccess(bidderRequest, 'refererInfo.ref'); + const firstPartyData = bidderRequest.ortb2; if (items && items.length) { let c = { + // TODO: fix auctionId leak: https://github.com/prebid/Prebid.js/issues/9781 id: 'pp_hbjs_' + auctionId, test: +isTest, at: 1, @@ -312,6 +317,7 @@ function getParam(validBidRequests, bidderRequest) { }, ext: { eids, + firstPartyData, }, user: { buyeruid: getUserID(), diff --git a/modules/displayioBidAdapter.js b/modules/displayioBidAdapter.js index c3c6597dd1b..3d34f2c8b26 100644 --- a/modules/displayioBidAdapter.js +++ b/modules/displayioBidAdapter.js @@ -2,12 +2,15 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {Renderer} from '../src/Renderer.js'; import {getWindowFromDocument, logWarn} from '../src/utils.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {getAllOrtbKeywords} from '../libraries/keywords/keywords.js'; const ADAPTER_VERSION = '1.1.0'; const BIDDER_CODE = 'displayio'; const BID_TTL = 300; const SUPPORTED_AD_TYPES = [BANNER, VIDEO]; const DEFAULT_CURRENCY = 'USD'; +const US_KEY = '_dio_us'; export const spec = { code: BIDDER_CODE, @@ -67,18 +70,26 @@ export const spec = { function getPayload (bid, bidderRequest) { const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection; - const userSession = 'us_web_xxxxxxxxxxxx'.replace(/[x]/g, c => { - let r = Math.random() * 16 | 0; - let v = c === 'x' ? r : (r & 0x3 | 0x8); - return v.toString(16); - }); + const storage = getStorageManager({bidderCode: BIDDER_CODE}); + const userSession = (() => { + let us = storage.getDataFromLocalStorage(US_KEY); + if (!us) { + us = 'us_web_xxxxxxxxxxxx'.replace(/[x]/g, c => { + let r = Math.random() * 16 | 0; + let v = c === 'x' ? r : (r & 0x3 | 0x8); + return v.toString(16); + }); + storage.setDataInLocalStorage(US_KEY, us); + } + return us + })(); const { params, adUnitCode, bidId } = bid; const { siteId, placementId, renderURL, pageCategory, keywords } = params; const { refererInfo, uspConsent, gdprConsent } = bidderRequest; - const mediation = {consent: '-1', gdpr: '-1'}; + const mediation = {gdprConsent: '', gdpr: '-1'}; if (gdprConsent && 'gdprApplies' in gdprConsent) { if (gdprConsent.consentString !== undefined) { - mediation.consent = gdprConsent.consentString; + mediation.gdprConsent = gdprConsent.consentString; } if (gdprConsent.gdprApplies !== undefined) { mediation.gdpr = gdprConsent.gdprApplies ? '1' : '0'; @@ -95,7 +106,7 @@ function getPayload (bid, bidderRequest) { renderURL, data: { pagecat: pageCategory ? pageCategory.split(',').map(k => k.trim()) : [], - keywords: keywords ? keywords.split(',').map(k => k.trim()) : [], + keywords: getAllOrtbKeywords(bidderRequest.ortb2, keywords), lang_content: document.documentElement.lang, lang: window.navigator.language, domain: refererInfo.domain, @@ -110,7 +121,7 @@ function getPayload (bid, bidderRequest) { dnt: window.doNotTrack === '1' || window.navigator.doNotTrack === '1' || false, iabConsent: {}, mediation: { - consent: mediation.consent, + gdprConsent: mediation.gdprConsent, gdpr: mediation.gdpr, } }, diff --git a/modules/distroscaleBidAdapter.js b/modules/distroscaleBidAdapter.js index 005dd3e67d6..7a2038ed3f0 100644 --- a/modules/distroscaleBidAdapter.js +++ b/modules/distroscaleBidAdapter.js @@ -132,6 +132,24 @@ export const spec = { // TODO: does the fallback to window.location make sense? var pageUrl = bidderRequest?.refererInfo?.page || window.location.href; + // check if dstag is already loaded in ancestry tree + var dsloaded = 0; + try { + var win = window; + while (true) { + if (win.vx.cs_loaded) { + dsloaded = 1; + } + if (win != win.parent) { + win = win.parent; + } else { + break; + } + } + } catch (error) { + // ignore exception + } + var payload = { id: '' + (new Date()).getTime(), at: AUCTION_TYPE, @@ -149,7 +167,9 @@ export const spec = { }, imp: [], user: {}, - ext: {} + ext: { + dsloaded: dsloaded + } }; validBidRequests.forEach(b => { diff --git a/modules/dspxBidAdapter.js b/modules/dspxBidAdapter.js index 8850eb282b5..b8e812f581a 100644 --- a/modules/dspxBidAdapter.js +++ b/modules/dspxBidAdapter.js @@ -1,12 +1,16 @@ -import { deepAccess } from '../src/utils.js'; -import {config} from '../src/config.js'; +import {deepAccess, getBidIdParameter, isFn, logError, logMessage, logWarn} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {Renderer} from '../src/Renderer.js'; +import {includes} from '../src/polyfill.js'; const BIDDER_CODE = 'dspx'; const ENDPOINT_URL = 'https://buyer.dspx.tv/request/'; const ENDPOINT_URL_DEV = 'https://dcbuyer.dspx.tv/request/'; const GVLID = 602; +const VIDEO_ORTB_PARAMS = ['mimes', 'minduration', 'maxduration', 'protocols', 'w', 'h', 'startdelay', 'placement', 'linearity', 'skip', 'skipmin', + 'skipafter', 'sequence', 'battr', 'maxextended', 'minbitrate', 'maxbitrate', 'boxingallowed', 'playbackmethod', 'playbackend', 'delivery', 'pos', 'companionad', + 'api', 'companiontype', 'ext']; export const spec = { code: BIDDER_CODE, @@ -17,24 +21,35 @@ export const spec = { return !!(bid.params.placement); }, buildRequests: function(validBidRequests, bidderRequest) { + let payload = {}; return validBidRequests.map(bidRequest => { const params = bidRequest.params; - const placementId = params.placement; const rnd = Math.floor(Math.random() * 99999999999); const referrer = bidderRequest.refererInfo.page; const bidId = bidRequest.bidId; - const isDev = params.devMode || false; const pbcode = bidRequest.adUnitCode || false; // div id + // TODO: fix auctionId leak: https://github.com/prebid/Prebid.js/issues/9781 const auctionId = bidRequest.auctionId || false; + const isDev = params.devMode || false; let endpoint = isDev ? ENDPOINT_URL_DEV : ENDPOINT_URL; + let placementId = params.placement; + + // dev config + if (isDev && params.dev) { + endpoint = params.dev.endpoint || endpoint; + placementId = params.dev.placement || placementId; + if (params.dev.pfilter !== undefined) { + params.pfilter = params.dev.pfilter; + } + } let mediaTypesInfo = getMediaTypesInfo(bidRequest); let type = isBannerRequest(bidRequest) ? BANNER : VIDEO; let sizes = mediaTypesInfo[type]; - let payload = { + payload = { _f: 'auto', alternative: 'prebid_js', inventory_item_id: placementId, @@ -47,10 +62,6 @@ export const spec = { pbver: '$prebid.version$' }; - if (mediaTypesInfo[VIDEO] !== undefined && params.vastFormat !== undefined) { - payload.vf = params.vastFormat; - } - if (params.pfilter !== undefined) { payload.pfilter = params.pfilter; } @@ -79,11 +90,49 @@ export const spec = { payload.prebidDevMode = 1; } - if (bidRequest.userId && bidRequest.userId.netId) { - payload.did_netid = bidRequest.userId.netId; + // fill userId params + if (bidRequest.userId) { + if (bidRequest.userId.netId) { + payload.did_netid = bidRequest.userId.netId; + } + if (bidRequest.userId.id5id) { + payload.did_id5 = bidRequest.userId.id5id.uid || '0'; + if (bidRequest.userId.id5id.ext.linkType !== undefined) { + payload.did_id5_linktype = bidRequest.userId.id5id.ext.linkType; + } + } + let uId2 = deepAccess(bidRequest, 'userId.uid2.id'); + if (uId2) { + payload.did_uid2 = uId2; + } + let sharedId = deepAccess(bidRequest, 'userId.sharedid.id'); + if (sharedId) { + payload.did_sharedid = sharedId; + } + let pubcId = deepAccess(bidRequest, 'userId.pubcid'); + if (pubcId) { + payload.did_pubcid = pubcId; + } + let crumbsPubcid = deepAccess(bidRequest, 'crumbs.pubcid'); + if (crumbsPubcid) { + payload.did_cpubcid = crumbsPubcid; + } + } + + if (bidRequest.schain) { + payload.schain = bidRequest.schain; } - if (bidRequest.userId && bidRequest.userId.uid2) { - payload.did_uid2 = bidRequest.userId.uid2; + + if (payload.pfilter === undefined || !payload.pfilter.floorprice) { + let bidFloor = getBidFloor(bidRequest); + if (bidFloor > 0) { + if (payload.pfilter !== undefined) { + payload.pfilter.floorprice = bidFloor; + } else { + payload.pfilter = { 'floorprice': bidFloor }; + } + // payload.bidFloor = bidFloor; + } } if (auctionId) { @@ -94,6 +143,17 @@ export const spec = { } payload.media_types = convertMediaInfoForRequest(mediaTypesInfo); + if (mediaTypesInfo[VIDEO] !== undefined) { + payload.vctx = getVideoContext(bidRequest); + if (params.vastFormat !== undefined) { + payload.vf = params.vastFormat; + } + payload.vpl = {}; + let videoParams = deepAccess(bidRequest, 'mediaTypes.video'); + Object.keys(videoParams) + .filter(key => includes(VIDEO_ORTB_PARAMS, key)) + .forEach(key => payload.vpl[key] = videoParams[key]); + } return { method: 'GET', @@ -103,6 +163,8 @@ export const spec = { }); }, interpretResponse: function(serverResponse, bidRequest) { + logMessage('DSPx: serverResponse', serverResponse); + logMessage('DSPx: bidRequest', bidRequest); const bidResponses = []; const response = serverResponse.body; const crid = response.crid || 0; @@ -121,18 +183,38 @@ export const spec = { currency: currency, netRevenue: netRevenue, type: response.type, - ttl: config.getConfig('_bidderTimeout'), + ttl: 60, meta: { advertiserDomains: response.adomain || [] } }; + + if (response.vastUrl) { + bidResponse.vastUrl = response.vastUrl; + bidResponse.mediaType = 'video'; + } if (response.vastXml) { bidResponse.vastXml = response.vastXml; bidResponse.mediaType = 'video'; - } else { + } + if (response.renderer) { + bidResponse.renderer = newRenderer(bidRequest, response); + } + + if (response.videoCacheKey) { + bidResponse.videoCacheKey = response.videoCacheKey; + } + + if (response.adTag) { bidResponse.ad = response.adTag; } + if (response.bid_appendix) { + Object.keys(response.bid_appendix).forEach(fieldName => { + bidResponse[fieldName] = response.bid_appendix[fieldName]; + }); + } + bidResponses.push(bidResponse); } return bidResponses; @@ -217,12 +299,22 @@ function isVideoRequest(bid) { * Get video sizes * * @param {BidRequest} bid - Bid request generated from ad slots - * @returns {object} True if it's a video bid + * @returns {object} */ function getVideoSizes(bid) { return parseSizes(deepAccess(bid, 'mediaTypes.video.playerSize') || bid.sizes); } +/** + * Get video context + * + * @param {BidRequest} bid - Bid request generated from ad slots + * @returns {object} + */ +function getVideoContext(bid) { + return deepAccess(bid, 'mediaTypes.video.context') || 'unknown'; +} + /** * Get banner sizes * @@ -296,4 +388,120 @@ function getMediaTypesInfo(bid) { return mediaTypesInfo; } +/** + * Get Bid Floor + * @param bid + * @returns {number|*} + */ +function getBidFloor(bid) { + if (!isFn(bid.getFloor)) { + return deepAccess(bid, 'params.bidfloor', 0); + } + + try { + const bidFloor = bid.getFloor({ + currency: 'EUR', + mediaType: '*', + size: '*', + }); + return bidFloor.floor; + } catch (_) { + return 0 + } +} + +/** + * Create a new renderer + * + * @param bidRequest + * @param response + * @returns {Renderer} + */ +function newRenderer(bidRequest, response) { + logMessage('DSPx: newRenderer', bidRequest, response); + const renderer = Renderer.install({ + id: response.renderer.id || response.bid_id, + url: (bidRequest.params && bidRequest.params.rendererUrl) || response.renderer.url, + config: response.renderer.options || deepAccess(bidRequest, 'renderer.options'), + loaded: false + }); + + try { + renderer.setRender(outstreamRender); + } catch (err) { + logWarn('Prebid Error calling setRender on renderer', err); + } + return renderer; +} + +/** + * Outstream Render Function + * + * @param bid + */ +function outstreamRender(bid) { + logMessage('DSPx: outstreamRender bid:', bid); + const embedCode = createOutstreamEmbedCode(bid); + try { + const inIframe = getBidIdParameter('iframe', bid.renderer.config); + if (inIframe && window.document.getElementById(inIframe).nodeName === 'IFRAME') { + const iframe = window.document.getElementById(inIframe); + let framedoc = iframe.contentDocument || (iframe.contentWindow && iframe.contentWindow.document); + framedoc.body.appendChild(embedCode); + if (typeof window.dspxRender === 'function') { + window.dspxRender(bid); + } else { + logError('[dspx][renderer] Error: dspxRender function is not found'); + } + return; + } + + const slot = getBidIdParameter('slot', bid.renderer.config) || bid.adUnitCode; + if (slot && window.document.getElementById(slot)) { + window.document.getElementById(slot).appendChild(embedCode); + if (typeof window.dspxRender === 'function') { + window.dspxRender(bid); + } else { + logError('[dspx][renderer] Error: dspxRender function is not found'); + } + } else if (slot) { + logError('[dspx][renderer] Error: slot not found'); + } + } catch (err) { + logError('[dspx][renderer] Error:' + err.message) + } +} + +/** + * create Outstream Embed Code Node + * + * @param bid + * @returns {DocumentFragment} + */ +function createOutstreamEmbedCode(bid) { + const fragment = window.document.createDocumentFragment(); + let div = window.document.createElement('div'); + div.innerHTML = deepAccess(bid, 'renderer.config.code', ''); + fragment.appendChild(div); + + // run scripts + var scripts = div.getElementsByTagName('script'); + var scriptsClone = []; + for (var idx = 0; idx < scripts.length; idx++) { + scriptsClone.push(scripts[idx]); + } + for (var i = 0; i < scriptsClone.length; i++) { + var currentScript = scriptsClone[i]; + var s = document.createElement('script'); + for (var j = 0; j < currentScript.attributes.length; j++) { + var a = currentScript.attributes[j]; + s.setAttribute(a.name, a.value); + } + s.appendChild(document.createTextNode(currentScript.innerHTML)); + currentScript.parentNode.replaceChild(s, currentScript); + } + + return fragment; +} + registerBidder(spec); diff --git a/modules/ebdrBidAdapter.js b/modules/ebdrBidAdapter.js index a7b1991df9b..a03a1ec12ca 100644 --- a/modules/ebdrBidAdapter.js +++ b/modules/ebdrBidAdapter.js @@ -36,8 +36,9 @@ export const spec = { w: whArr[0], h: whArr[1] }; - ebdrParams['latitude'] = getBidIdParameter('latitude', bid.params); - ebdrParams['longitude'] = getBidIdParameter('longitude', bid.params); + // TODO: fix lat and long to only come from request + ebdrParams['latitude'] = '0'; + ebdrParams['longitude'] = '0'; ebdrParams['ifa'] = (getBidIdParameter('IDFA', bid.params).length > getBidIdParameter('ADID', bid.params).length) ? getBidIdParameter('IDFA', bid.params) : getBidIdParameter('ADID', bid.params); }); let ebdrBidReq = { diff --git a/modules/emtvBidAdapter.js b/modules/emtvBidAdapter.js new file mode 100644 index 00000000000..7a2fdae8adf --- /dev/null +++ b/modules/emtvBidAdapter.js @@ -0,0 +1,211 @@ +import { isFn, deepAccess, logMessage, logError } from '../src/utils.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; + +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { config } from '../src/config.js'; + +const BIDDER_CODE = 'emtv'; +const AD_URL = 'https://us-east-ep.engagemedia.tv/pbjs'; +const SYNC_URL = 'https://cs.engagemedia.tv'; + +function isBidResponseValid(bid) { + if (!bid.requestId || !bid.cpm || !bid.creativeId || !bid.ttl || !bid.currency) { + return false; + } + + switch (bid.mediaType) { + case BANNER: + return Boolean(bid.width && bid.height && bid.ad); + case VIDEO: + return Boolean(bid.vastUrl || bid.vastXml); + case NATIVE: + return Boolean(bid.native && bid.native.impressionTrackers && bid.native.impressionTrackers.length); + default: + return false; + } +} + +function getPlacementReqData(bid) { + const { params, bidId, mediaTypes } = bid; + const schain = bid.schain || {}; + const { placementId, endpointId } = params; + const bidfloor = getBidFloor(bid); + + const placement = { + bidId, + schain, + bidfloor + }; + + if (placementId) { + placement.placementId = placementId; + placement.type = 'publisher'; + } else if (endpointId) { + placement.endpointId = endpointId; + placement.type = 'network'; + } + + if (mediaTypes && mediaTypes[BANNER]) { + placement.adFormat = BANNER; + placement.sizes = mediaTypes[BANNER].sizes; + } else if (mediaTypes && mediaTypes[VIDEO]) { + placement.adFormat = VIDEO; + placement.playerSize = mediaTypes[VIDEO].playerSize; + placement.minduration = mediaTypes[VIDEO].minduration; + placement.maxduration = mediaTypes[VIDEO].maxduration; + placement.mimes = mediaTypes[VIDEO].mimes; + placement.protocols = mediaTypes[VIDEO].protocols; + placement.startdelay = mediaTypes[VIDEO].startdelay; + placement.placement = mediaTypes[VIDEO].placement; + placement.skip = mediaTypes[VIDEO].skip; + placement.skipafter = mediaTypes[VIDEO].skipafter; + placement.minbitrate = mediaTypes[VIDEO].minbitrate; + placement.maxbitrate = mediaTypes[VIDEO].maxbitrate; + placement.delivery = mediaTypes[VIDEO].delivery; + placement.playbackmethod = mediaTypes[VIDEO].playbackmethod; + placement.api = mediaTypes[VIDEO].api; + placement.linearity = mediaTypes[VIDEO].linearity; + } else if (mediaTypes && mediaTypes[NATIVE]) { + placement.native = mediaTypes[NATIVE]; + placement.adFormat = NATIVE; + } + + return placement; +} + +function getBidFloor(bid) { + if (!isFn(bid.getFloor)) { + return deepAccess(bid, 'params.bidfloor', 0); + } + + try { + const bidFloor = bid.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*', + }); + return bidFloor.floor; + } catch (err) { + logError(err); + return 0; + } +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + + isBidRequestValid: (bid = {}) => { + const { params, bidId, mediaTypes } = bid; + let valid = Boolean(bidId && params && (params.placementId || params.endpointId)); + + if (mediaTypes && mediaTypes[BANNER]) { + valid = valid && Boolean(mediaTypes[BANNER] && mediaTypes[BANNER].sizes); + } else if (mediaTypes && mediaTypes[VIDEO]) { + valid = valid && Boolean(mediaTypes[VIDEO] && mediaTypes[VIDEO].playerSize); + } else if (mediaTypes && mediaTypes[NATIVE]) { + valid = valid && Boolean(mediaTypes[NATIVE]); + } else { + valid = false; + } + return valid; + }, + + buildRequests: (validBidRequests = [], bidderRequest = {}) => { + // convert Native ORTB definition to old-style prebid native definition + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + + let deviceWidth = 0; + let deviceHeight = 0; + + let winLocation; + try { + const winTop = window.top; + deviceWidth = winTop.screen.width; + deviceHeight = winTop.screen.height; + winLocation = winTop.location; + } catch (e) { + logMessage(e); + winLocation = window.location; + } + + const refferUrl = bidderRequest.refererInfo && bidderRequest.refererInfo.page; + let refferLocation; + try { + refferLocation = refferUrl && new URL(refferUrl); + } catch (e) { + logMessage(e); + } + let location = refferLocation || winLocation; + const language = (navigator && navigator.language) ? navigator.language.split('-')[0] : ''; + const host = location.host; + const page = location.pathname; + const secure = location.protocol === 'https:' ? 1 : 0; + const placements = []; + const request = { + deviceWidth, + deviceHeight, + language, + secure, + host, + page, + placements, + coppa: config.getConfig('coppa') === true ? 1 : 0, + ccpa: bidderRequest.uspConsent || undefined, + gdpr: bidderRequest.gdprConsent || undefined, + tmax: config.getConfig('bidderTimeout') + }; + + const len = validBidRequests.length; + for (let i = 0; i < len; i++) { + const bid = validBidRequests[i]; + placements.push(getPlacementReqData(bid)); + } + + return { + method: 'POST', + url: AD_URL, + data: request + }; + }, + + interpretResponse: (serverResponse) => { + let response = []; + for (let i = 0; i < serverResponse.body.length; i++) { + let resItem = serverResponse.body[i]; + if (isBidResponseValid(resItem)) { + const advertiserDomains = resItem.adomain && resItem.adomain.length ? resItem.adomain : []; + resItem.meta = { ...resItem.meta, advertiserDomains }; + + response.push(resItem); + } + } + return response; + }, + + getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => { + let syncType = syncOptions.iframeEnabled ? 'iframe' : 'image'; + let syncUrl = SYNC_URL + `/${syncType}?pbjs=1`; + if (gdprConsent && gdprConsent.consentString) { + if (typeof gdprConsent.gdprApplies === 'boolean') { + syncUrl += `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; + } else { + syncUrl += `&gdpr=0&gdpr_consent=${gdprConsent.consentString}`; + } + } + if (uspConsent && uspConsent.consentString) { + syncUrl += `&ccpa_consent=${uspConsent.consentString}`; + } + + const coppa = config.getConfig('coppa') ? 1 : 0; + syncUrl += `&coppa=${coppa}`; + + return [{ + type: syncType, + url: syncUrl + }]; + } +}; + +registerBidder(spec); diff --git a/modules/emtvBidAdapter.md b/modules/emtvBidAdapter.md new file mode 100644 index 00000000000..ed58ca43294 --- /dev/null +++ b/modules/emtvBidAdapter.md @@ -0,0 +1,79 @@ +# Overview + +``` +Module Name: EMTV Bidder Adapter +Module Type: EMTV Bidder Adapter +Maintainer: support@engagemedia.tv +``` + +# Description + +Connects to EMTV exchange for bids. +EMTV bid adapter supports Banner, Video (instream and outstream) and Native. + +# Test Parameters +``` + var adUnits = [ + // Will return static test banner + { + code: 'adunit1', + mediaTypes: { + banner: { + sizes: [ [300, 250], [320, 50] ], + } + }, + bids: [ + { + bidder: 'emtv', + params: { + placementId: 'testBanner', + } + } + ] + }, + { + code: 'addunit2', + mediaTypes: { + video: { + playerSize: [ [640, 480] ], + context: 'instream', + minduration: 5, + maxduration: 60, + } + }, + bids: [ + { + bidder: 'emtv', + params: { + placementId: 'testVideo', + } + } + ] + }, + { + code: 'addunit3', + mediaTypes: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + }, + bids: [ + { + bidder: 'emtv', + params: { + placementId: 'testNative', + } + } + ] + } + ]; +``` diff --git a/modules/eplanningBidAdapter.js b/modules/eplanningBidAdapter.js index e230858487f..2216ab329b0 100644 --- a/modules/eplanningBidAdapter.js +++ b/modules/eplanningBidAdapter.js @@ -24,6 +24,7 @@ const VAST_INSTREAM = 1; const VAST_OUTSTREAM = 2; const VAST_VERSION_DEFAULT = 3; const DEFAULT_SIZE_VAST = '640x480'; +const MAX_LEN_URL = 255; export const spec = { code: BIDDER_CODE, @@ -60,7 +61,7 @@ export const spec = { params = { rnd: rnd, e: spaces.str, - ur: pageUrl || FILE, + ur: cutUrl(pageUrl || FILE), pbv: '$prebid.version$', ncb: '1', vs: spaces.vs @@ -70,7 +71,7 @@ export const spec = { } if (referrerUrl) { - params.fr = referrerUrl; + params.fr = cutUrl(referrerUrl); } if (bidderRequest && bidderRequest.gdprConsent) { @@ -491,6 +492,17 @@ function visibilityHandler(obj) { } } +function cutUrl (url) { + if (url.length > MAX_LEN_URL) { + url = url.split('?')[0]; + if (url.length > MAX_LEN_URL) { + url = url.slice(0, MAX_LEN_URL); + } + } + + return url; +} + function registerAuction(storageID) { let value; try { diff --git a/modules/eskimiBidAdapter.js b/modules/eskimiBidAdapter.js new file mode 100644 index 00000000000..88d8f95b859 --- /dev/null +++ b/modules/eskimiBidAdapter.js @@ -0,0 +1,202 @@ +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import * as utils from '../src/utils.js'; + +const BIDDER_CODE = 'eskimi'; +// const ENDPOINT = 'https://hb.eskimi.com/bids' +const ENDPOINT = 'https://sspback.eskimi.com/bid-request' + +const DEFAULT_BID_TTL = 30; +const DEFAULT_CURRENCY = 'USD'; +const DEFAULT_NET_REVENUE = true; +const GVLID = 814; + +const VIDEO_ORTB_PARAMS = [ + 'mimes', + 'minduration', + 'maxduration', + 'placement', + 'protocols', + 'startdelay', + 'skip', + 'skipafter', + 'minbitrate', + 'maxbitrate', + 'delivery', + 'playbackmethod', + 'api', + 'linearity', + 'battr' +]; + +const BANNER_ORTB_PARAMS = [ + 'battr' +] + +export const spec = { + code: BIDDER_CODE, + gvlid: GVLID, + supportedMediaTypes: [BANNER, VIDEO], + isBidRequestValid, + buildRequests, + interpretResponse, + /** + * Register bidder specific code, which will execute if a bid from this bidder won the auction + * @param {Bid} bid The bid that won the auction + */ + onBidWon: function (bid) { + if (bid.burl) { + utils.triggerPixel(bid.burl); + } + } +} + +registerBidder(spec); + +const CONVERTER = ortbConverter({ + context: { + netRevenue: DEFAULT_NET_REVENUE, + ttl: DEFAULT_BID_TTL, + currency: DEFAULT_CURRENCY + }, + imp(buildImp, bidRequest, context) { + let imp = buildImp(bidRequest, context); + imp.secure = Number(window.location.protocol === 'https:'); + if (!imp.bidfloor && bidRequest.params.bidFloor) { + imp.bidfloor = bidRequest.params.bidFloor; + imp.bidfloorcur = utils.getBidIdParameter('bidFloorCur', bidRequest.params).toUpperCase() || 'USD' + } + + if (bidRequest.mediaTypes[VIDEO]) { + imp = buildVideoImp(bidRequest, imp); + } else if (bidRequest.mediaTypes[BANNER]) { + imp = buildBannerImp(bidRequest, imp); + } + + return imp; + } +}); + +function isBidRequestValid(bidRequest) { + return (isPlacementIdValid(bidRequest) && (isValidBannerRequest(bidRequest) || isValidVideoRequest(bidRequest))); +} + +function isPlacementIdValid(bidRequest) { + return utils.isNumber(bidRequest.params.placementId); +} + +function isValidBannerRequest(bidRequest) { + const bannerSizes = utils.deepAccess(bidRequest, `mediaTypes.${BANNER}.sizes`); + return utils.isArray(bannerSizes) && bannerSizes.length > 0 && bannerSizes.every(size => utils.isNumber(size[0]) && utils.isNumber(size[1])); +} + +function isValidVideoRequest(bidRequest) { + const videoSizes = utils.deepAccess(bidRequest, `mediaTypes.${VIDEO}.playerSize`); + + return utils.isArray(videoSizes) && videoSizes.length > 0 && videoSizes.every(size => utils.isNumber(size[0]) && utils.isNumber(size[1])); +} + +function buildRequests(validBids, bidderRequest) { + let videoBids = validBids.filter(bid => isVideoBid(bid)); + let bannerBids = validBids.filter(bid => isBannerBid(bid)); + let requests = []; + + bannerBids.forEach(bid => { + requests.push(createRequest([bid], bidderRequest, BANNER)); + }); + + videoBids.forEach(bid => { + requests.push(createRequest([bid], bidderRequest, VIDEO)); + }); + + return requests; +} + +function interpretResponse(response, request) { + return CONVERTER.fromORTB({ request: request.data, response: response.body }).bids; +} + +function buildVideoImp(bidRequest, imp) { + const videoAdUnitParams = utils.deepAccess(bidRequest, `mediaTypes.${VIDEO}`, {}); + const videoBidderParams = utils.deepAccess(bidRequest, `params.${VIDEO}`, {}); + + const videoParams = { ...videoAdUnitParams, ...videoBidderParams }; + + const videoSizes = (videoAdUnitParams && videoAdUnitParams.playerSize) || []; + + if (videoSizes && videoSizes.length > 0) { + utils.deepSetValue(imp, 'video.w', videoSizes[0][0]); + utils.deepSetValue(imp, 'video.h', videoSizes[0][1]); + } + + VIDEO_ORTB_PARAMS.forEach((param) => { + if (videoParams.hasOwnProperty(param)) { + utils.deepSetValue(imp, `video.${param}`, videoParams[param]); + } + }); + + if (imp.video && videoParams?.context === 'outstream') { + imp.video.placement = imp.video.placement || 4; + } + + return { ...imp }; +} + +function buildBannerImp(bidRequest, imp) { + const bannerAdUnitParams = utils.deepAccess(bidRequest, `mediaTypes.${BANNER}`, {}); + const bannerBidderParams = utils.deepAccess(bidRequest, `params.${BANNER}`, {}); + + const bannerParams = { ...bannerAdUnitParams, ...bannerBidderParams }; + + let sizes = bidRequest.mediaTypes.banner.sizes; + + if (sizes) { + utils.deepSetValue(imp, 'banner.w', sizes[0][0]); + utils.deepSetValue(imp, 'banner.h', sizes[0][1]); + } + + BANNER_ORTB_PARAMS.forEach((param) => { + if (bannerParams.hasOwnProperty(param)) { + utils.deepSetValue(imp, `banner.${param}`, bannerParams[param]); + } + }); + + return { ...imp }; +} + +function createRequest(bidRequests, bidderRequest, mediaType) { + const data = CONVERTER.toORTB({ bidRequests, bidderRequest, context: { mediaType } }) + + const bid = bidRequests.find((b) => b.params.placementId) + if (!data.site) data.site = {} + data.site.ext = { placementId: bid.params.placementId } + + if (bidderRequest.gdprConsent) { + if (!data.user) data.user = {}; + if (!data.user.ext) data.user.ext = {}; + if (!data.regs) data.regs = {}; + if (!data.regs.ext) data.regs.ext = {}; + data.user.ext.consent = bidderRequest.gdprConsent.consentString; + data.regs.ext.gdpr = bidderRequest.gdprConsent.gdprApplies ? 1 : 0; + } + + if (bid.params.bcat) data.bcat = bid.params.bcat; + if (bid.params.badv) data.badv = bid.params.badv; + if (bid.params.bapp) data.bapp = bid.params.bapp; + + return { + method: 'POST', + url: ENDPOINT, + data: data, + options: { contentType: 'application/json;charset=UTF-8', withCredentials: false } + } +} + +function isVideoBid(bid) { + return utils.deepAccess(bid, 'mediaTypes.video'); +} + +function isBannerBid(bid) { + return utils.deepAccess(bid, 'mediaTypes.banner') || !isVideoBid(bid); +} diff --git a/modules/eskimiBidAdapter.md b/modules/eskimiBidAdapter.md new file mode 100644 index 00000000000..b3494a217eb --- /dev/null +++ b/modules/eskimiBidAdapter.md @@ -0,0 +1,53 @@ +# Overview + +Module Name: ESKIMI Bidder Adapter +Module Type: Bidder Adapter +Maintainer: tech@eskimi.com + +# Description + +Module that connects to Eskimi demand sources to fetch bids using OpenRTB standard. +Banner and video formats are supported. + +# Test Parameters +```javascript + var adUnits = [{ + code: '/19968336/prebid_banner_example_1', + mediaTypes: { + banner: { + sizes: [[ 300, 250 ]], + ... // battr + } + }, + bids: [{ + bidder: 'eskimi', + params: { + placementId: 612, + ... // bcat, badv, bapp + } + }] + }, { + code: '/19968336/prebid_video_example_1', + mediaTypes: { + video: { + context: 'outstream', + mimes: ['video/mp4'], + api: [1, 2, 4, 6], + ... // Aditional ORTB video params (including battr) + } + }, + bids: [{ + bidder: 'eskimi', + params: { + placementId: 612, + ... // bcat, badv, bapp + } + }] + }]; +``` + +Where: + +* placementId - Placement ID of the ad unit (required) +* bcat, badv, bapp, battr - ORTB blocking parameters as specified by OpenRTB 2.5 + diff --git a/modules/etargetBidAdapter.js b/modules/etargetBidAdapter.js index b290a62420c..cced180e061 100644 --- a/modules/etargetBidAdapter.js +++ b/modules/etargetBidAdapter.js @@ -35,7 +35,7 @@ export const spec = { lastCountry = countryMap[bid.params.country]; } reqParams = bid.params; - reqParams.transactionId = bid.transactionId; + reqParams.transactionId = bid.ortb2Imp?.ext?.tid; request.push(formRequestUrl(reqParams)); floors[i] = getBidFloor(bid); } @@ -137,7 +137,6 @@ export const spec = { vastXml: data.vast_content, vastUrl: data.vast_link, mediaType: data.response, - transactionId: bid.transactionId }; if (bidRequest.gdpr) { bidObject.gdpr = bidRequest.gdpr.gdpr; diff --git a/modules/euidIdSystem.js b/modules/euidIdSystem.js new file mode 100644 index 00000000000..c4e15b11d15 --- /dev/null +++ b/modules/euidIdSystem.js @@ -0,0 +1,121 @@ +/** + * This module adds EUID ID support to the User ID module. It shares significant functionality with the UID2 module. + * The {@link module:modules/userId} module is required. + * @module modules/euidIdSystem + * @requires module:modules/userId + */ + +import { logInfo, logWarn, deepAccess } from '../src/utils.js'; +import {submodule} from '../src/hook.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; + +// RE below lint exception: UID2 and EUID are separate modules, but the protocol is the same and shared code makes sense here. +// eslint-disable-next-line prebid/validate-imports +import { Uid2GetId, Uid2CodeVersion } from './uid2IdSystem_shared.js'; + +const MODULE_NAME = 'euid'; +const MODULE_REVISION = Uid2CodeVersion; +const PREBID_VERSION = '$prebid.version$'; +const EUID_CLIENT_ID = `PrebidJS-${PREBID_VERSION}-EUIDModule-${MODULE_REVISION}`; +const GVLID_TTD = 21; // The Trade Desk +const LOG_PRE_FIX = 'EUID: '; +const ADVERTISING_COOKIE = '__euid_advertising_token'; + +// eslint-disable-next-line no-unused-vars +const EUID_TEST_URL = 'https://integ.euid.eu'; +const EUID_PROD_URL = 'https://prod.euid.eu'; +const EUID_BASE_URL = EUID_PROD_URL; + +function createLogger(logger, prefix) { + return function (...strings) { + logger(prefix + ' ', ...strings); + } +} +const _logInfo = createLogger(logInfo, LOG_PRE_FIX); +const _logWarn = createLogger(logWarn, LOG_PRE_FIX); + +export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); + +function hasWriteToDeviceConsent(consentData) { + const gdprApplies = consentData?.gdprApplies === true; + const localStorageConsent = deepAccess(consentData, `vendorData.purpose.consents.1`) + const prebidVendorConsent = deepAccess(consentData, `vendorData.vendor.consents.${GVLID_TTD.toString()}`) + if (gdprApplies && (!localStorageConsent || !prebidVendorConsent)) { + return false; + } + return true; +} + +/** @type {Submodule} */ +export const euidIdSubmodule = { + /** + * used to link submodule with config + * @type {string} + */ + name: MODULE_NAME, + + /** + * Vendor id of The Trade Desk + * @type {Number} + */ + gvlid: GVLID_TTD, + /** + * decode the stored id value for passing to bid requests + * @function + * @param {string} value + * @returns {{euid:{ id: string } }} or undefined if value doesn't exists + */ + decode(value) { + const result = decodeImpl(value); + _logInfo('EUID decode returned', result); + return result; + }, + + /** + * performs action to obtain id and return a value. + * @function + * @param {SubmoduleConfig} [configparams] + * @param {ConsentData|undefined} consentData + * @returns {euidId} + */ + getId(config, consentData) { + if (consentData?.gdprApplies !== true) { + logWarn('EUID is intended for use within the EU. The module will not run when GDPR does not apply.'); + return; + } + if (!hasWriteToDeviceConsent(consentData)) { + // The module cannot operate without this permission. + _logWarn(`Unable to use EUID module due to insufficient consent. The EUID module requires storage permission.`) + return; + } + + const mappedConfig = { + apiBaseUrl: config?.params?.euidApiBase ?? EUID_BASE_URL, + paramToken: config?.params?.euidToken, + serverCookieName: config?.params?.euidCookie, + storage: config?.params?.storage ?? 'localStorage', + clientId: EUID_CLIENT_ID, + internalStorage: ADVERTISING_COOKIE + }; + + const result = Uid2GetId(mappedConfig, storage, _logInfo, _logWarn); + _logInfo(`EUID getId returned`, result); + return result; + }, +}; + +function decodeImpl(value) { + if (typeof value === 'string') { + _logInfo('Found server-only token. Refresh is unavailable for this token.'); + const result = { euid: { id: value } }; + return result; + } + if (Date.now() < value.latestToken.identity_expires) { + return { euid: { id: value.latestToken.advertising_token } }; + } + return null; +} + +// Register submodule for userId +submodule('userId', euidIdSubmodule); diff --git a/modules/euidIdSystem.md b/modules/euidIdSystem.md new file mode 100644 index 00000000000..e3e16bce89d --- /dev/null +++ b/modules/euidIdSystem.md @@ -0,0 +1,131 @@ +## EUID User ID Submodule + +EUID requires initial tokens to be generated server-side. The EUID module handles storing, providing, and optionally refreshing them. The module can operate in one of two different modes: *Client Refresh* mode or *Server Only* mode. + +*Server Only* mode was originally referred to as *legacy mode*, but it is a popular mode for new integrations where publishers prefer to handle token refresh server-side. + +## Client Refresh mode + +This is the recommended mode for most scenarios. In this mode, the full response body from the EUID Token Generate or Token Refresh endpoint must be provided to the module. As long as the refresh token remains valid, the module will refresh the advertising token as needed. + +To configure the module to use this mode, you must **either**: +1. Set `params.euidCookie` to the name of the cookie which contains the response body as a JSON string, **or** +2. Set `params.euidToken` to the response body as a JavaScript object. + +### Client refresh cookie example + +In this example, the cookie is called `euid_pub_cookie`. + +Cookie: +``` +euid_pub_cookie={"advertising_token":"...advertising token...","refresh_token":"...refresh token...","identity_expires":1684741472161,"refresh_from":1684741425653,"refresh_expires":1684784643668,"refresh_response_key":"...response key..."} +``` + +Configuration: +``` +pbjs.setConfig({ + userSync: { + userIds: [{ + name: 'euid', + params: { + euidCookie: 'euid_pub_cookie' + } + }] + } +}); +``` + +### Client refresh euidToken example + +Configuration: +``` +pbjs.setConfig({ + userSync: { + userIds: [{ + name: 'euid', + params: { + euidToken: { + 'advertising_token': '...advertising token...', + 'refresh_token': '...refresh token...', + // etc. - see the Sample Token below for contents of this object + } + } + }] + } +}); +``` + +## Server-Only Mode + +In this mode, only the advertising token is provided to the module. The module will not be able to refresh the token. The publisher is responsible for implementing some other way to refresh the token. + +To configure the module to use this mode, you must **either**: +1. Set a cookie named `__euid_advertising_token` to the advertising token, **or** +2. Set `value` to an ID block containing the advertising token. + +### Server only cookie example + +Cookie: +``` +__euid_advertising_token=...advertising token... +``` + +Configuration: +``` +pbjs.setConfig({ + userSync: { + userIds: [{ + name: 'euid' + }] + } +}); +``` + +### Server only value example + +Configuration: +``` +pbjs.setConfig({ + userSync: { + userIds: [{ + name: 'euid' + value: { + 'euid': { + 'id': '...advertising token...' + } + } + }] + } +}); +``` + +## Storage + +The module stores a number of internal values. By default, all values are stored in HTML5 local storage. You can switch to cookie storage by setting `params.storage` to `cookie`. The cookie size can be significant and this is not recommended, but is provided as an option if local storage is not an option. + +## Sample token + +`{`
  `"advertising_token": "...",`
  `"refresh_token": "...",`
  `"identity_expires": 1633643601000,`
  `"refresh_from": 1633643001000,`
  `"refresh_expires": 1636322000000,`
  `"refresh_response_key": "wR5t6HKMfJ2r4J7fEGX9Gw=="`
`}` + +### Notes + +If you are trying to limit the size of cookies, provide the token in configuration and use the default option of local storage. + +If you provide an expired identity and the module has a valid identity which was refreshed from the identity you provide, it will use the refreshed identity. The module stores the original token used for refreshing the token, and it will use the refreshed tokens as long as the original token matches the one supplied. + +If a new token is supplied which does not match the original token used to generate any refreshed tokens, all stored tokens will be discarded and the new token used instead (refreshed if necessary). + +You can set `params.euidApiBase` to `"https://integ.euid.eu"` during integration testing. Be aware that you must use the same environment (production or integration) here as you use for generating tokens. + +## Parameter Descriptions for the `usersync` Configuration Section + +The below parameters apply only to the EUID User ID Module integration. + +| Param under userSync.userIds[] | Scope | Type | Description | Example | +| --- | --- | --- | --- | --- | +| name | Required | String | ID value for the EUID module - `"euid"` | `"euid"` | +| value | Optional, Server only | Object | An object containing the value for the advertising token. | See the example above. | +| params.euidToken | Optional, Client refresh | Object | The initial EUID token. This should be `body` element of the decrypted response from a call to the `/token/generate` or `/token/refresh` endpoint. | See the sample token above. | +| params.euidCookie | Optional, Client refresh | String | The name of a cookie which holds the initial EUID token, set by the server. The cookie should contain JSON in the same format as the euidToken param. **If euidToken is supplied, this param is ignored.** | See the sample token above. | +| params.euidApiBase | Optional, Client refresh | String | Overrides the default EUID API endpoint. | `"https://prod.euid.eu"` _(default)_| +| params.storage | Optional, Client refresh | String | Specify whether to use `cookie` or `localStorage` for module-internal storage. It is recommended to not provide this and allow the module to use the default. | `localStorage` _(default)_ | diff --git a/modules/express.js b/modules/express.js index 0b1780e3c26..a2998baed07 100644 --- a/modules/express.js +++ b/modules/express.js @@ -1,6 +1,8 @@ import { logMessage, logWarn, logError, logInfo } from '../src/utils.js'; +import {getGlobal} from '../src/prebidGlobal.js'; const MODULE_NAME = 'express'; +const pbjsInstance = getGlobal(); /** * Express Module @@ -12,7 +14,7 @@ const MODULE_NAME = 'express'; * * @param {Object[]} [adUnits = pbjs.adUnits] - an array of adUnits for express to operate on. */ -$$PREBID_GLOBAL$$.express = function(adUnits = $$PREBID_GLOBAL$$.adUnits) { +pbjsInstance.express = function(adUnits = pbjsInstance.adUnits) { logMessage('loading ' + MODULE_NAME); if (adUnits.length === 0) { @@ -138,10 +140,10 @@ $$PREBID_GLOBAL$$.express = function(adUnits = $$PREBID_GLOBAL$$.adUnits) { } if (adUnits.length) { - $$PREBID_GLOBAL$$.requestBids({ + pbjsInstance.requestBids({ adUnits: adUnits, bidsBackHandler: function () { - $$PREBID_GLOBAL$$.setTargetingForGPTAsync(); + pbjsInstance.setTargetingForGPTAsync(); fGptRefresh.apply(pads(), [ adUnits.map(function (adUnit) { return gptSlotCache[adUnit.code]; @@ -168,10 +170,10 @@ $$PREBID_GLOBAL$$.express = function(adUnits = $$PREBID_GLOBAL$$.adUnits) { } if (adUnits.length) { - $$PREBID_GLOBAL$$.requestBids({ + pbjsInstance.requestBids({ adUnits: adUnits, bidsBackHandler: function () { - $$PREBID_GLOBAL$$.setTargetingForGPTAsync(); + pbjsInstance.setTargetingForGPTAsync(); fGptRefresh.apply(pads(), [ adUnits.map(function (adUnit) { return gptSlotCache[adUnit.code]; diff --git a/modules/feedadBidAdapter.js b/modules/feedadBidAdapter.js index 34e14cd674a..7b41f0fcc03 100644 --- a/modules/feedadBidAdapter.js +++ b/modules/feedadBidAdapter.js @@ -234,7 +234,7 @@ function buildRequests(validBidRequests, bidderRequest) { }); data.bids.forEach(bid => BID_METADATA[bid.bidId] = { referer: data.refererInfo.page, - transactionId: bid.transactionId + transactionId: bid.ortb2Imp?.ext?.tid, }); if (bidderRequest.gdprConsent) { data.consentIabTcf = bidderRequest.gdprConsent.consentString; diff --git a/modules/finativeBidAdapter.js b/modules/finativeBidAdapter.js index b236ddb3d95..87580a209bb 100644 --- a/modules/finativeBidAdapter.js +++ b/modules/finativeBidAdapter.js @@ -1,11 +1,11 @@ // jshint esversion: 6, es3: false, node: true 'use strict'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { NATIVE } from '../src/mediaTypes.js'; -import { _map, deepSetValue, isEmpty, deepAccess } from '../src/utils.js'; -import { config } from '../src/config.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {NATIVE} from '../src/mediaTypes.js'; +import {_map, deepAccess, deepSetValue, isEmpty} from '../src/utils.js'; +import {config} from '../src/config.js'; +import {convertOrtbRequestToProprietaryNative} from '../src/native.js'; const BIDDER_CODE = 'finative'; const DEFAULT_CUR = 'EUR'; @@ -63,7 +63,7 @@ export const spec = { // convert Native ORTB definition to old-style prebid native definition validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); const pt = setOnAny(validBidRequests, 'params.pt') || setOnAny(validBidRequests, 'params.priceType') || 'net'; - const tid = bidderRequest.auctionId; + const tid = bidderRequest.ortb2?.source?.tid; const cur = [config.getConfig('currency.adServerCurrency') || DEFAULT_CUR]; let url = bidderRequest.refererInfo.referer; @@ -104,6 +104,7 @@ export const spec = { return { id: String(id + 1), tagid: bid.params.adUnitId, + // TODO: `tid` is not under `imp` in ORTB, is this intentional? tid: tid, pt: pt, native: { @@ -115,7 +116,7 @@ export const spec = { }); const request = { - id: bidderRequest.auctionId, + id: bidderRequest.bidderRequestId, site: { page: url }, @@ -174,7 +175,6 @@ export const spec = { netRevenue: (!bid.netRevenue || bid.netRevenue === 'net'), currency: cur, mediaType: NATIVE, - bidderCode: BIDDER_CODE, native: parseNative(bidResponse), meta: { advertiserDomains: bidResponse.adomain && bidResponse.adomain.length > 0 ? bidResponse.adomain : [] diff --git a/modules/fintezaAnalyticsAdapter.js b/modules/fintezaAnalyticsAdapter.js index 88c5f85f15d..be661c96061 100644 --- a/modules/fintezaAnalyticsAdapter.js +++ b/modules/fintezaAnalyticsAdapter.js @@ -2,10 +2,12 @@ import { parseUrl, logError } from '../src/utils.js'; import { ajax } from '../src/ajax.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; -import { getStorageManager } from '../src/storageManager.js'; +import {getStorageManager} from '../src/storageManager.js'; import CONSTANTS from '../src/constants.json'; +import {MODULE_TYPE_ANALYTICS} from '../src/activities/modules.js'; -const storage = getStorageManager(); +const MODULE_CODE = 'finteza'; +const storage = getStorageManager({moduleType: MODULE_TYPE_ANALYTICS, moduleName: MODULE_CODE}); const ANALYTICS_TYPE = 'endpoint'; const FINTEZA_HOST = 'https://content.mql5.com/tr'; @@ -439,7 +441,7 @@ fntzAnalyticsAdapter.enableAnalytics = function (config) { adapterManager.registerAnalyticsAdapter({ adapter: fntzAnalyticsAdapter, - code: 'finteza' + code: MODULE_CODE, }); export default fntzAnalyticsAdapter; diff --git a/modules/fledgeForGpt.js b/modules/fledgeForGpt.js index 0f7092baf03..f29ce7508d5 100644 --- a/modules/fledgeForGpt.js +++ b/modules/fledgeForGpt.js @@ -5,6 +5,7 @@ import { config } from '../src/config.js'; import { getHook } from '../src/hook.js'; import { getGptSlotForAdUnitCode, logInfo, logWarn } from '../src/utils.js'; +import {IMP, PBS, registerOrtbProcessor, RESPONSE} from '../src/pbjsORTB.js'; const MODULE = 'fledgeForGpt' @@ -21,22 +22,20 @@ export function init(cfg) { getHook('addComponentAuction').before(addComponentAuctionHook); isEnabled = true; } - logInfo(MODULE, `isEnabled`, cfg); + logInfo(`${MODULE} enabled (browser ${isFledgeSupported() ? 'supports' : 'does NOT support'} fledge)`, cfg); } else { if (isEnabled) { getHook('addComponentAuction').getHooks({hook: addComponentAuctionHook}).remove(); isEnabled = false; } - logInfo(MODULE, `isDisabled`, cfg); + logInfo(`${MODULE} disabled`, cfg); } } -export function addComponentAuctionHook(next, bidRequest, componentAuctionConfig) { +export function addComponentAuctionHook(next, adUnitCode, componentAuctionConfig) { const seller = componentAuctionConfig.seller; - const adUnitCode = bidRequest.adUnitCode; const gptSlot = getGptSlotForAdUnitCode(adUnitCode); if (gptSlot && gptSlot.setConfig) { - delete componentAuctionConfig.bidId; gptSlot.setConfig({ componentAuction: [{ configKey: seller, @@ -48,5 +47,54 @@ export function addComponentAuctionHook(next, bidRequest, componentAuctionConfig logWarn(MODULE, `unable to register component auction config for: ${adUnitCode} x ${seller}.`); } - next(bidRequest, componentAuctionConfig); + next(adUnitCode, componentAuctionConfig); } + +function isFledgeSupported() { + return 'runAdAuction' in navigator && 'joinAdInterestGroup' in navigator +} + +export function markForFledge(next, bidderRequests) { + if (isFledgeSupported()) { + bidderRequests.forEach((req) => { + req.fledgeEnabled = config.runWithBidder(req.bidderCode, () => config.getConfig('fledgeEnabled')) + }) + } + next(bidderRequests); +} +getHook('makeBidRequests').after(markForFledge); + +export function setImpExtAe(imp, bidRequest, context) { + if (!context.bidderRequest.fledgeEnabled) { + delete imp.ext?.ae; + } +} +registerOrtbProcessor({type: IMP, name: 'impExtAe', fn: setImpExtAe}); + +// to make it easier to share code between the PBS adapter and adapters whose backend is PBS, break up +// fledge response processing in two steps: first aggregate all the auction configs by their imp... + +export function parseExtPrebidFledge(response, ortbResponse, context) { + (ortbResponse.ext?.prebid?.fledge?.auctionconfigs || []).forEach((cfg) => { + const impCtx = context.impContext[cfg.impid]; + if (!impCtx?.imp?.ext?.ae) { + logWarn('Received fledge auction configuration for an impression that was not in the request or did not ask for it', cfg, impCtx?.imp); + } else { + impCtx.fledgeConfigs = impCtx.fledgeConfigs || []; + impCtx.fledgeConfigs.push(cfg); + } + }) +} +registerOrtbProcessor({type: RESPONSE, name: 'extPrebidFledge', fn: parseExtPrebidFledge, dialects: [PBS]}); + +// ...then, make them available in the adapter's response. This is the client side version, for which the +// interpretResponse api is {fledgeAuctionConfigs: [{bidId, config}]} + +export function setResponseFledgeConfigs(response, ortbResponse, context) { + const configs = Object.values(context.impContext) + .flatMap((impCtx) => (impCtx.fledgeConfigs || []).map(cfg => ({bidId: impCtx.bidRequest.bidId, config: cfg.config}))); + if (configs.length > 0) { + response.fledgeAuctionConfigs = configs; + } +} +registerOrtbProcessor({type: RESPONSE, name: 'fledgeAuctionConfigs', priority: -1, fn: setResponseFledgeConfigs, dialects: [PBS]}) diff --git a/modules/flocIdSystem.js b/modules/flocIdSystem.js deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/modules/fluctBidAdapter.js b/modules/fluctBidAdapter.js index 004d44e6737..edb750a6b90 100644 --- a/modules/fluctBidAdapter.js +++ b/modules/fluctBidAdapter.js @@ -1,4 +1,5 @@ -import { _each, isEmpty } from '../src/utils.js'; +import { _each, deepSetValue, isEmpty } from '../src/utils.js'; +import { config } from '../src/config.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; const BIDDER_CODE = 'fluct'; @@ -39,20 +40,34 @@ export const spec = { */ buildRequests: (validBidRequests, bidderRequest) => { const serverRequests = []; - // TODO: is 'page' the right value here? - const referer = bidderRequest.refererInfo.page; + const page = bidderRequest.refererInfo.page; _each(validBidRequests, (request) => { const data = Object(); - data.referer = referer; + data.page = page; data.adUnitCode = request.adUnitCode; data.bidId = request.bidId; - data.transactionId = request.transactionId; + data.transactionId = request.ortb2Imp?.ext?.tid; data.user = { eids: (request.userIdAsEids || []).filter((eid) => SUPPORTED_USER_ID_SOURCES.indexOf(eid.source) !== -1) }; + if (bidderRequest.gdprConsent) { + deepSetValue(data, 'regs.gdpr', { + consent: bidderRequest.gdprConsent.consentString, + gdprApplies: bidderRequest.gdprConsent.gdprApplies ? 1 : 0, + }); + } + if (bidderRequest.uspConsent) { + deepSetValue(data, 'regs.us_privacy', { + consent: bidderRequest.uspConsent, + }); + } + if (config.getConfig('coppa') === true) { + deepSetValue(data, 'regs.coppa', 1); + } + data.sizes = []; _each(request.sizes, (size) => { data.sizes.push({ @@ -110,7 +125,6 @@ export const spec = { `(function() { var img = new Image(); img.src = "${beaconUrl}"})()` + ``; let data = { - bidderCode: BIDDER_CODE, requestId: res.id, currency: res.cur, cpm: parseFloat(bid.price) || 0, @@ -141,8 +155,22 @@ export const spec = { * */ getUserSyncs: (syncOptions, serverResponses) => { - return []; - }, + // gdpr, us_privacy, and coppa params to be handled on the server end. + const usersyncs = serverResponses.reduce((acc, serverResponse) => [ + ...acc, + ...(serverResponse.body.usersyncs ?? []), + ], []); + const syncs = usersyncs.filter( + (sync) => ( + (sync['type'] === 'image' && syncOptions.pixelEnabled) || + (sync['type'] === 'iframe' && syncOptions.iframeEnabled) + ) + ).map((sync) => ({ + type: sync.type, + url: sync.url, + })); + return syncs; + } }; registerBidder(spec); diff --git a/modules/freepassBidAdapter.js b/modules/freepassBidAdapter.js new file mode 100644 index 00000000000..02e433fa8fc --- /dev/null +++ b/modules/freepassBidAdapter.js @@ -0,0 +1,98 @@ +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {logMessage} from '../src/utils.js'; +import {BANNER} from '../src/mediaTypes.js'; +import {ortbConverter} from '../libraries/ortbConverter/converter.js' + +const BIDDER_SERVICE_URL = 'https://bidding-dsp.ad-m.asia/dsp/api/bid/s/s/freepass'; + +const converter = ortbConverter({ + context: { + netRevenue: true, + ttl: 30 + } +}); + +function prepareUserInfo(user, freepassId) { + let userInfo = user || {}; + let extendedUserInfo = userInfo.ext || {}; + + if (freepassId.userId) { + userInfo.id = freepassId.userId; + } + + if (freepassId.commonId) { + extendedUserInfo.fuid = freepassId.commonId; + } + userInfo.ext = extendedUserInfo; + + return userInfo; +} + +function prepareDeviceInfo(device, freepassId) { + let deviceInfo = device || {}; + let extendedDeviceInfo = deviceInfo.ext || {}; + + extendedDeviceInfo.is_accurate_ip = 0; + if (freepassId.userIp) { + deviceInfo.ip = freepassId.userIp; + extendedDeviceInfo.is_accurate_ip = 1; + } + deviceInfo.ext = extendedDeviceInfo; + + return deviceInfo; +} + +export const spec = { + code: 'freepass', + supportedMediaTypes: [BANNER], + + isBidRequestValid(bid) { + logMessage('Validating bid: ', bid); + return !!bid.adUnitCode; + }, + + buildRequests(validBidRequests, bidderRequest) { + if (validBidRequests.length === 0) { + logMessage('FreePass BidAdapter has no valid bid requests'); + return []; + } + + logMessage('FreePass BidAdapter is preparing bid request: ', validBidRequests); + logMessage('FreePass BidAdapter is using bidder request: ', bidderRequest); + + const data = converter.toORTB({ + bidderRequest: bidderRequest, + bidRequests: validBidRequests, + context: { mediaType: BANNER } + }); + logMessage('FreePass BidAdapter interpreted ORTB bid request as ', data); + + // Only freepassId is supported + let freepassId = (validBidRequests[0].userId && validBidRequests[0].userId.freepassId) || {}; + data.user = prepareUserInfo(data.user, freepassId); + data.device = prepareDeviceInfo(data.device, freepassId); + + data.test = validBidRequests[0].test || 0; + + logMessage('FreePass BidAdapter augmented ORTB bid request user: ', data.user); + logMessage('FreePass BidAdapter augmented ORTB bid request device: ', data.device); + + return { + method: 'POST', + url: BIDDER_SERVICE_URL, + data, + options: { withCredentials: false } + }; + }, + + interpretResponse(serverResponse, bidRequest) { + logMessage('FreePass BidAdapter is interpreting server response: ', serverResponse); + logMessage('FreePass BidAdapter is using bid request: ', bidRequest); + const bids = converter.fromORTB({response: serverResponse.body, request: bidRequest.data}).bids; + logMessage('FreePass BidAdapter interpreted ORTB bids as ', bids); + + return bids; + }, +}; + +registerBidder(spec); diff --git a/modules/freepassBidAdapter.md b/modules/freepassBidAdapter.md new file mode 100644 index 00000000000..60957a1fbe5 --- /dev/null +++ b/modules/freepassBidAdapter.md @@ -0,0 +1,31 @@ +# Overview + +``` +Module Name: FreePass Bidder Adapter +Module Type: Bidder Adapter +Maintainer: fp-hbidding@freebit.net +``` + +# Description + +Connects to FreePass service for bids. Only BANNER is currently supported. + +This BidAdapter requires the FreePass IdSystem to be configured. Please contact FreePass for proper setup. + +# Test Parameters +```javascript + let adUnits = [ + { + code: 'ad-banner-1', // ad slot HTML element ID + mediaTypes: { + banner: { + sizes: [[1024, 1024]] + } + }, + bids: [{ + bidder: 'freepass' + }] + } + ]; +``` + diff --git a/modules/freepassIdSystem.js b/modules/freepassIdSystem.js new file mode 100644 index 00000000000..d52c537e800 --- /dev/null +++ b/modules/freepassIdSystem.js @@ -0,0 +1,61 @@ +import { submodule } from '../src/hook.js'; +import {generateUUID, logMessage} from '../src/utils.js'; + +const MODULE_NAME = 'freepassId'; + +export const freepassIdSubmodule = { + name: MODULE_NAME, + decode: function (value, config) { + logMessage('Decoding FreePass ID: ', value); + + return { [MODULE_NAME]: value }; + }, + + getId: function (config, consent, cachedIdObject) { + logMessage('Getting FreePass ID using config: ' + JSON.stringify(config)); + + const freepassData = config.params !== undefined ? (config.params.freepassData || {}) : {} + let idObject = {userId: generateUUID()}; + + if (freepassData.commonId !== undefined) { + idObject.commonId = config.params.freepassData.commonId; + } + + if (freepassData.userIp !== undefined) { + idObject.userIp = config.params.freepassData.userIp; + } + + return {id: idObject}; + }, + + extendId: function (config, consent, cachedIdObject) { + let freepassData = config.params.freepassData; + let hasFreepassData = freepassData !== undefined; + if (!hasFreepassData) { + logMessage('No Freepass Data. CachedIdObject will not be extended: ' + JSON.stringify(cachedIdObject)); + return { + id: cachedIdObject + }; + } + + if (freepassData.commonId === cachedIdObject.commonId && freepassData.userIp === cachedIdObject.userIp) { + logMessage('FreePass ID is already up-to-date: ' + JSON.stringify(cachedIdObject)); + return { + id: cachedIdObject + }; + } + + logMessage('Extending FreePass ID object: ' + JSON.stringify(cachedIdObject)); + logMessage('Extending FreePass ID using config: ' + JSON.stringify(config)); + + return { + id: { + commonId: freepassData.commonId, + userIp: freepassData.userIp, + userId: cachedIdObject.userId, + }, + }; + } +}; + +submodule('userId', freepassIdSubmodule); diff --git a/modules/freepassIdSystem.md b/modules/freepassIdSystem.md new file mode 100644 index 00000000000..d2b8ee236e1 --- /dev/null +++ b/modules/freepassIdSystem.md @@ -0,0 +1,47 @@ +## FreePass User ID Submodule + +[FreePass](https://freepass-login.com/introduction.html) is a common authentication service operated by Freebit Co., Ltd. Users with a FreePass account do not need to create a new account to use partner services. + +# General Information + +Please contact FreePass before using this ID. + +``` +Module Name: FreePass Id System +Module Type: User Id System +Maintainer: fp-hbidding@freebit.net +``` + +## Building Prebid with FreePass ID Support + +First, make sure to add the FreePass ID submodule to your Prebid.js package with: + +```shell +gulp build --modules=freepassIdSystem,userId +``` + +The following configuration parameters are available: + +```javascript +pbjs.setConfig({ + userSync: { + userIds: [{ + name: 'freepassId', + params: { + freepassData: { + commonId: 'fpcommonid123', + userIp: '127.0.0.1' + } + } + }] + } +}); +``` + +| Param under userSync.userIds[] | Scope | Type | Description | Example | +|--------------------------------|----------|--------|------------------------------------------------------|----------------| +| name | Required | String | The name of this module | `"freepassId"` | +| freepassData | Optional | Object | FreePass data | `{}` | +| freepassData.commonId | Optional | String | Common ID obtained from FreePass | `"abcd1234"` | +| freepassData.userIp | Optional | String | User IP obtained in cooperation with partner service | `"127.0.0.1"` | + diff --git a/modules/freewheel-sspBidAdapter.js b/modules/freewheel-sspBidAdapter.js index 1a962fd4060..cd4785cdc78 100644 --- a/modules/freewheel-sspBidAdapter.js +++ b/modules/freewheel-sspBidAdapter.js @@ -1,4 +1,4 @@ -import { logWarn, isArray, isFn, deepAccess } from '../src/utils.js'; +import { logWarn, isArray, isFn, deepAccess, formatQS } from '../src/utils.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; @@ -340,6 +340,7 @@ export const spec = { var timeInMillis = new Date().getTime(); var keyCode = hashcode(zone + '' + timeInMillis); var bidfloor = getBidFloor(currentBidRequest, config); + var format = currentBidRequest.params.format; var requestParams = { reqType: 'AdsSetup', @@ -350,6 +351,7 @@ export const spec = { timestamp: timeInMillis, _fw_bidfloor: (bidfloor > 0) ? bidfloor : 0, _fw_bidfloorcur: (bidfloor > 0) ? getFloorCurrency(config) : '', + pbjs_version: '$prebid.version$', pKey: keyCode }; @@ -371,6 +373,15 @@ export const spec = { requestParams._fw_us_privacy = bidderRequest.uspConsent; } + // Add GPP consent + if (bidderRequest && bidderRequest.gppConsent) { + requestParams.gpp = bidderRequest.gppConsent.gppString; + requestParams.gpp_sid = bidderRequest.gppConsent.applicableSections; + } else if (bidderRequest && bidderRequest.ortb2 && bidderRequest.ortb2.regs && bidderRequest.ortb2.regs.gpp) { + requestParams.gpp = bidderRequest.ortb2.regs.gpp; + requestParams.gpp_sid = bidderRequest.ortb2.regs.gpp_sid; + } + // Add schain object var schain = currentBidRequest.schain; if (schain) { @@ -381,6 +392,14 @@ export const spec = { } } + if (currentBidRequest.userIdAsEids && currentBidRequest.userIdAsEids.length > 0) { + try { + requestParams._fw_prebid_3p_UID = JSON.stringify(currentBidRequest.userIdAsEids); + } catch (error) { + logWarn('PREBID - ' + BIDDER_CODE + ': Unable to stringify the userIdAsEids: ' + error); + } + } + var vastParams = currentBidRequest.params.vastUrlParams; if (typeof vastParams === 'object') { for (var key in vastParams) { @@ -415,6 +434,21 @@ export const spec = { requestParams.playerSize = playerSize[0] + 'x' + playerSize[1]; } + // Add video context and placement in requestParams + if (currentBidRequest.mediaTypes.video) { + var videoContext = currentBidRequest.mediaTypes.video.context ? currentBidRequest.mediaTypes.video.context : ''; + var videoPlacement = currentBidRequest.mediaTypes.video.placement ? currentBidRequest.mediaTypes.video.placement : null; + var videoPlcmt = currentBidRequest.mediaTypes.video.plcmt ? currentBidRequest.mediaTypes.video.plcmt : null; + + if (format == 'inbanner') { + videoPlacement = 2; + videoContext = 'In-Banner'; + } + requestParams.video_context = videoContext; + requestParams.video_placement = videoPlacement; + requestParams.video_plcmt = videoPlcmt; + } + return { method: 'GET', url: FREEWHEEL_ADSSETUP, @@ -509,26 +543,42 @@ export const spec = { return bidResponses; }, - getUserSyncs: function(syncOptions, responses, gdprConsent, usPrivacy) { - var gdprParams = ''; + getUserSyncs: function(syncOptions, responses, gdprConsent, usPrivacy, gppConsent) { + const params = {}; + if (gdprConsent) { if (typeof gdprConsent.gdprApplies === 'boolean') { - gdprParams = `?gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; + params.gdpr = Number(gdprConsent.gdprApplies); + params.gdpr_consent = gdprConsent.consentString; } else { - gdprParams = `?gdpr_consent=${gdprConsent.consentString}`; + params.gdpr_consent = gdprConsent.consentString; } } + if (gppConsent) { + if (typeof gppConsent.gppString === 'string') { + params.gpp = gppConsent.gppString; + } + if (gppConsent.applicableSections) { + params.gpp_sid = gppConsent.applicableSections; + } + } + + var queryString = ''; + if (params) { + queryString = '?' + `${formatQS(params)}`; + } + const syncs = []; if (syncOptions && syncOptions.pixelEnabled) { syncs.push({ type: 'image', - url: USER_SYNC_URL + gdprParams + url: USER_SYNC_URL + queryString }); } else if (syncOptions.iframeEnabled) { syncs.push({ type: 'iframe', - url: USER_SYNC_URL + gdprParams + url: USER_SYNC_URL + queryString }); } diff --git a/modules/ftrackIdSystem.js b/modules/ftrackIdSystem.js index 244807a3164..5f09a315b34 100644 --- a/modules/ftrackIdSystem.js +++ b/modules/ftrackIdSystem.js @@ -6,19 +6,19 @@ */ import * as utils from '../src/utils.js'; -import { submodule } from '../src/hook.js'; -import { getStorageManager } from '../src/storageManager.js'; -import { uspDataHandler } from '../src/adapterManager.js'; -import { loadExternalScript } from '../src/adloader.js'; +import {submodule} from '../src/hook.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {uspDataHandler} from '../src/adapterManager.js'; +import {loadExternalScript} from '../src/adloader.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; const MODULE_NAME = 'ftrackId'; const LOG_PREFIX = 'FTRACK - '; const LOCAL_STORAGE_EXP_DAYS = 30; -const VENDOR_ID = null; const LOCAL_STORAGE = 'html5'; const FTRACK_STORAGE_NAME = 'ftrackId'; const FTRACK_PRIVACY_STORAGE_NAME = `${FTRACK_STORAGE_NAME}_privacy`; -const storage = getStorageManager({gvlid: VENDOR_ID, moduleName: MODULE_NAME}); +const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); let consentInfo = { gdpr: { diff --git a/modules/gamoshiBidAdapter.js b/modules/gamoshiBidAdapter.js index ae320db1251..1c279cdb9b8 100644 --- a/modules/gamoshiBidAdapter.js +++ b/modules/gamoshiBidAdapter.js @@ -80,11 +80,11 @@ export const spec = { buildRequests: function (validBidRequests, bidderRequest) { return validBidRequests.map(bidRequest => { - const {adUnitCode, auctionId, mediaTypes, params, sizes, transactionId} = bidRequest; + const {adUnitCode, mediaTypes, params, sizes, bidId} = bidRequest; const baseEndpoint = params['rtbEndpoint'] || ENDPOINTS['gamoshi']; const rtbEndpoint = `${baseEndpoint}/r/${params.supplyPartnerId}/bidr?rformat=open_rtb&reqformat=rtb_json&bidder=prebid` + (params.query ? '&' + params.query : ''); const rtbBidRequest = { - id: auctionId, + id: bidderRequest.bidderRequestId, site: { domain: bidderRequest.refererInfo.domain, page: bidderRequest.refererInfo.page, @@ -118,7 +118,7 @@ export const spec = { } const imp = { - id: transactionId, + id: bidId, instl: deepAccess(bidderRequest.ortb2Imp, 'instl') === 1 || params.instl === 1 ? 1 : 0, tagid: adUnitCode, bidfloor: helper.getBidFloor(bidRequest) || 0, diff --git a/modules/gdprEnforcement.js b/modules/gdprEnforcement.js index 9553ad0586a..fa7418a90ab 100644 --- a/modules/gdprEnforcement.js +++ b/modules/gdprEnforcement.js @@ -2,24 +2,42 @@ * This module gives publishers extra set of features to enforce individual purposes of TCF v2 */ -import {deepAccess, hasDeviceAccess, isArray, logError, logWarn} from '../src/utils.js'; +import {deepAccess, logError, logWarn} from '../src/utils.js'; import {config} from '../src/config.js'; import adapterManager, {gdprDataHandler} from '../src/adapterManager.js'; -import {find, includes} from '../src/polyfill.js'; -import {registerSyncInner} from '../src/adapters/bidderFactory.js'; -import {getHook} from '../src/hook.js'; -import {validateStorageEnforcement} from '../src/storageManager.js'; +import {find} from '../src/polyfill.js'; import * as events from '../src/events.js'; import CONSTANTS from '../src/constants.json'; -import {VENDORLESS_GVLID} from '../src/consentHandler.js'; +import {GDPR_GVLIDS, VENDORLESS_GVLID} from '../src/consentHandler.js'; +import { + MODULE_TYPE_ANALYTICS, + MODULE_TYPE_BIDDER, + MODULE_TYPE_PREBID, + MODULE_TYPE_RTD, + MODULE_TYPE_UID +} from '../src/activities/modules.js'; +import { + ACTIVITY_PARAM_ANL_CONFIG, + ACTIVITY_PARAM_COMPONENT_NAME, + ACTIVITY_PARAM_COMPONENT_TYPE +} from '../src/activities/params.js'; +import {registerActivityControl} from '../src/activities/rules.js'; +import { + ACTIVITY_ACCESS_DEVICE, + ACTIVITY_ENRICH_EIDS, + ACTIVITY_FETCH_BIDS, + ACTIVITY_REPORT_ANALYTICS, + ACTIVITY_SYNC_USER, ACTIVITY_TRANSMIT_UFPD +} from '../src/activities/activities.js'; export const STRICT_STORAGE_ENFORCEMENT = 'strictStorageEnforcement'; const TCF2 = { - 'purpose1': { id: 1, name: 'storage' }, - 'purpose2': { id: 2, name: 'basicAds' }, - 'purpose7': { id: 7, name: 'measurement' } -} + purpose1: {id: 1, name: 'storage'}, + purpose2: {id: 2, name: 'basicAds'}, + purpose4: {id: 4, name: 'personalizedAds'}, + purpose7: {id: 7, name: 'measurement'}, +}; /* These rules would be used if `consentManagement.gdpr.rules` is undefined by the publisher. @@ -38,95 +56,79 @@ const DEFAULT_RULES = [{ export let purpose1Rule; export let purpose2Rule; +export let purpose4Rule; export let purpose7Rule; export let enforcementRules; -const storageBlocked = []; -const biddersBlocked = []; -const analyticsBlocked = []; +const storageBlocked = new Set(); +const biddersBlocked = new Set(); +const analyticsBlocked = new Set(); +const ufpdBlocked = new Set(); let hooksAdded = false; let strictStorageEnforcement = false; -// Helps in stubbing these functions in unit tests. -export const internal = { - getGvlidForBidAdapter, - getGvlidForUserIdModule, - getGvlidForAnalyticsAdapter -}; +const GVLID_LOOKUP_PRIORITY = [ + MODULE_TYPE_BIDDER, + MODULE_TYPE_UID, + MODULE_TYPE_ANALYTICS, + MODULE_TYPE_RTD +]; + +const RULE_NAME = 'TCF2'; +const RULE_HANDLES = []; /** - * Returns GVL ID for a Bid adapter / an USERID submodule / an Analytics adapter. - * If modules of different types have the same moduleCode: For example, 'appnexus' is the code for both Bid adapter and Analytics adapter, - * then, we assume that their GVL IDs are same. This function first checks if GVL ID is defined for a Bid adapter, if not found, tries to find User ID - * submodule's GVL ID, if not found, tries to find Analytics adapter's GVL ID. In this process, as soon as it finds a GVL ID, it returns it - * without going to the next check. - * @param {{string|Object}} - module - * @return {number} - GVL ID + * Retrieve a module's GVL ID. */ -export function getGvlid(module, ...args) { - let gvlid = null; - if (module) { +export function getGvlid(moduleType, moduleName, fallbackFn) { + if (moduleName) { // Check user defined GVL Mapping in pbjs.setConfig() const gvlMapping = config.getConfig('gvlMapping'); - // For USER ID Module, we pass the submodule object itself as the "module" parameter, this check is required to grab the module code - const moduleCode = typeof module === 'string' ? module : module.name; - // Return GVL ID from user defined gvlMapping - if (gvlMapping && gvlMapping[moduleCode]) { - gvlid = gvlMapping[moduleCode]; - return gvlid; - } - - gvlid = internal.getGvlidForBidAdapter(moduleCode) || internal.getGvlidForUserIdModule(module) || internal.getGvlidForAnalyticsAdapter(moduleCode, ...args); - } - return gvlid; -} - -/** - * Returns GVL ID for a bid adapter. If the adapter does not have an associated GVL ID, it returns 'null'. - * @param {string=} bidderCode - The 'code' property of the Bidder spec. - * @return {number} GVL ID - */ -function getGvlidForBidAdapter(bidderCode) { - let gvlid = null; - bidderCode = bidderCode || config.getCurrentBidder(); - if (bidderCode) { - const bidder = adapterManager.getBidAdapter(bidderCode); - if (bidder && bidder.getSpec) { - gvlid = bidder.getSpec().gvlid; + if (gvlMapping && gvlMapping[moduleName]) { + return gvlMapping[moduleName]; + } else if (moduleType === MODULE_TYPE_PREBID) { + return VENDORLESS_GVLID; + } else { + let {gvlid, modules} = GDPR_GVLIDS.get(moduleName); + if (gvlid == null && Object.keys(modules).length > 0) { + // this behavior is for backwards compatibility; if multiple modules with the same + // name declare different GVL IDs, pick the bidder's first, then userId, then analytics + for (const type of GVLID_LOOKUP_PRIORITY) { + if (modules.hasOwnProperty(type)) { + gvlid = modules[type]; + if (type !== moduleType) { + logWarn(`Multiple GVL IDs found for module '${moduleName}'; using the ${type} module's ID (${gvlid}) instead of the ${moduleType}'s ID (${modules[moduleType]})`); + } + break; + } + } + } + if (gvlid == null && fallbackFn) { + gvlid = fallbackFn(); + } + return gvlid || null; } } - return gvlid; + return null; } /** - * Returns GVL ID for an userId submodule. If an userId submodules does not have an associated GVL ID, it returns 'null'. - * @param {Object} userIdModule - * @return {number} GVL ID + * Retrieve GVL IDs that are dynamically set on analytics adapters. */ -function getGvlidForUserIdModule(userIdModule) { - return (typeof userIdModule === 'object' ? userIdModule.gvlid : null); -} - -/** - * Returns GVL ID for an analytics adapter. If an analytics adapter does not have an associated GVL ID, it returns 'null'. - * @param {string} code - 'provider' property on the analytics adapter config - * @param {{}} config - analytics configuration object - * @return {number} GVL ID - */ -function getGvlidForAnalyticsAdapter(code, config) { +export function getGvlidFromAnalyticsAdapter(code, config) { const adapter = adapterManager.getAnalyticsAdapter(code); - return adapter?.gvlid || ((gvlid) => { + return ((gvlid) => { if (typeof gvlid !== 'function') return gvlid; try { return gvlid.call(adapter.adapter, config); } catch (e) { - logError(`Error invoking ${code} adapter.gvlid()`, e) + logError(`Error invoking ${code} adapter.gvlid()`, e); } - })(adapter?.adapter?.gvlid) + })(adapter?.adapter?.gvlid); } export function shouldEnforce(consentData, purpose, name) { @@ -135,7 +137,7 @@ export function shouldEnforce(consentData, purpose, name) { // NOTE: this check is not foolproof, as when Prebid first loads, enforcement hooks have not been attached yet // This piece of code would not run at all, and `gdprDataHandler.enabled` would be false, until the first // `setConfig({consentManagement})` - logWarn(`Attempting operation that requires purpose ${purpose} consent while consent data is not available${name ? ` (module: ${name})` : ''}. Assuming no consent was given.`) + logWarn(`Attempting operation that requires purpose ${purpose} consent while consent data is not available${name ? ` (module: ${name})` : ''}. Assuming no consent was given.`); return true; } return consentData && consentData.gdprApplies; @@ -157,7 +159,7 @@ export function validateRules(rule, consentData, currentModule, gvlId) { if ((rule.vendorExceptions || []).includes(currentModule)) { return true; } - const vendorConsentRequred = !((gvlId === VENDORLESS_GVLID || (rule.softVendorExceptions || []).includes(currentModule))) + const vendorConsentRequred = !((gvlId === VENDORLESS_GVLID || (rule.softVendorExceptions || []).includes(currentModule))); // get data from the consent string const purposeConsent = deepAccess(consentData, `vendorData.purpose.consents.${purposeId}`); @@ -184,177 +186,82 @@ export function validateRules(rule, consentData, currentModule, gvlId) { } /** - * This hook checks whether module has permission to access device or not. Device access include cookie and local storage - * @param {Function} fn reference to original function (used by hook logic) - * @param {Number=} gvlid gvlid of the module - * @param {string=} moduleName name of the module - * @param result + * all activity rules follow the same structure: + * if GDPR is in scope, check configuration for a particular purpose, and if that enables enforcement, + * check against consent data for that purpose and vendor + * + * @param purposeNo TCF purpose number to check for this activity + * @param getEnforcementRule getter for gdprEnforcement rule definition to use + * @param blocked optional set to use for collecting denied vendors + * @param gvlidFallback optional factory function for a gvlid falllback function */ -export function deviceAccessHook(fn, gvlid, moduleName, result, {validate = validateRules} = {}) { - result = Object.assign({}, { - hasEnforcementHook: true - }); - if (!hasDeviceAccess()) { - logWarn('Device access is disabled by Publisher'); - result.valid = false; - } else if (gvlid === VENDORLESS_GVLID && !strictStorageEnforcement) { - // for vendorless (core) storage, do not enforce rules unless strictStorageEnforcement is set - result.valid = true; - } else { +function gdprRule(purposeNo, getEnforcementRule, blocked = null, gvlidFallback = () => null) { + return function (params) { const consentData = gdprDataHandler.getConsentData(); - if (shouldEnforce(consentData, 1, moduleName)) { - const curBidder = config.getCurrentBidder(); - // Bidders have a copy of storage object with bidder code binded. Aliases will also pass the same bidder code when invoking storage functions and hence if alias tries to access device we will try to grab the gvl id for alias instead of original bidder - if (curBidder && (curBidder !== moduleName) && adapterManager.aliasRegistry[curBidder] === moduleName) { - gvlid = getGvlid(curBidder); - } else { - gvlid = getGvlid(moduleName) || gvlid; + const modName = params[ACTIVITY_PARAM_COMPONENT_NAME]; + if (shouldEnforce(consentData, purposeNo, modName)) { + const gvlid = getGvlid(params[ACTIVITY_PARAM_COMPONENT_TYPE], modName, gvlidFallback(params)); + let allow = !!validateRules(getEnforcementRule(), consentData, modName, gvlid); + if (!allow) { + blocked && blocked.add(modName); + return {allow}; } - const curModule = moduleName || curBidder; - let isAllowed = validate(purpose1Rule, consentData, curModule, gvlid,); - if (isAllowed) { - result.valid = true; - } else { - curModule && logWarn(`TCF2 denied device access for ${curModule}`); - result.valid = false; - storageBlocked.push(curModule); - } - } else { - result.valid = true; } - } - fn.call(this, gvlid, moduleName, result); + }; } -/** - * This hook checks if a bidder has consent for user sync or not - * @param {Function} fn reference to original function (used by hook logic) - * @param {...any} args args - */ -export function userSyncHook(fn, ...args) { - const consentData = gdprDataHandler.getConsentData(); - const curBidder = config.getCurrentBidder(); - if (shouldEnforce(consentData, 1, curBidder)) { - const gvlid = getGvlid(curBidder); - let isAllowed = validateRules(purpose1Rule, consentData, curBidder, gvlid); - if (isAllowed) { - fn.call(this, ...args); - } else { - logWarn(`User sync not allowed for ${curBidder}`); - storageBlocked.push(curBidder); +export const accessDeviceRule = ((rule) => { + return function (params) { + // for vendorless (core) storage, do not enforce rules unless strictStorageEnforcement is set + if (params[ACTIVITY_PARAM_COMPONENT_TYPE] === MODULE_TYPE_PREBID && !strictStorageEnforcement) return; + return rule(params); + }; +})(gdprRule(1, () => purpose1Rule, storageBlocked)); + +export const syncUserRule = gdprRule(1, () => purpose1Rule, storageBlocked); +export const enrichEidsRule = gdprRule(1, () => purpose1Rule, storageBlocked); + +export const fetchBidsRule = ((rule) => { + return function (params) { + if (params[ACTIVITY_PARAM_COMPONENT_TYPE] !== MODULE_TYPE_BIDDER) { + // TODO: this special case is for the PBS adapter (componentType is 'prebid') + // we should check for generic purpose 2 consent & vendor consent based on the PBS vendor's GVL ID; + // that is, however, a breaking change and skipped for now + return; } - } else { - fn.call(this, ...args); - } -} - -/** - * This hook checks if user id module is given consent or not - * @param {Function} fn reference to original function (used by hook logic) - * @param {Submodule[]} submodules Array of user id submodules - * @param {Object} consentData GDPR consent data - */ -export function userIdHook(fn, submodules, consentData) { - if (shouldEnforce(consentData, 1, 'User ID')) { - let userIdModules = submodules.map((submodule) => { - const gvlid = getGvlid(submodule.submodule); - const moduleName = submodule.submodule.name; - let isAllowed = validateRules(purpose1Rule, consentData, moduleName, gvlid); - if (isAllowed) { - return submodule; - } else { - logWarn(`User denied permission to fetch user id for ${moduleName} User id module`); - storageBlocked.push(moduleName); - } - return undefined; - }).filter(module => module) - fn.call(this, userIdModules, { ...consentData, hasValidated: true }); - } else { - fn.call(this, submodules, consentData); - } -} + return rule(params); + }; +})(gdprRule(2, () => purpose2Rule, biddersBlocked)); -/** - * Checks if bidders are allowed in the auction. - * Enforces "purpose 2 (Basic Ads)" of TCF v2.0 spec - * @param {Function} fn - Function reference to the original function. - * @param {Array} adUnits - */ -export function makeBidRequestsHook(fn, adUnits, ...args) { - const consentData = gdprDataHandler.getConsentData(); - if (shouldEnforce(consentData, 2)) { - adUnits.forEach(adUnit => { - adUnit.bids = adUnit.bids.filter(bid => { - const currBidder = bid.bidder; - const gvlId = getGvlid(currBidder); - if (includes(biddersBlocked, currBidder)) return false; - const isAllowed = !!validateRules(purpose2Rule, consentData, currBidder, gvlId); - if (!isAllowed) { - logWarn(`TCF2 blocked auction for ${currBidder}`); - biddersBlocked.push(currBidder); - } - return isAllowed; - }); - }); - fn.call(this, adUnits, ...args); - } else { - fn.call(this, adUnits, ...args); - } -} +export const reportAnalyticsRule = gdprRule(7, () => purpose7Rule, analyticsBlocked, (params) => getGvlidFromAnalyticsAdapter(params[ACTIVITY_PARAM_COMPONENT_NAME], params[ACTIVITY_PARAM_ANL_CONFIG])); -/** - * Checks if Analytics adapters are allowed to send data to their servers for furhter processing. - * Enforces "purpose 7 (Measurement)" of TCF v2.0 spec - * @param {Function} fn - Function reference to the original function. - * @param {Array} config - Configuration object passed to pbjs.enableAnalytics() - */ -export function enableAnalyticsHook(fn, config) { - const consentData = gdprDataHandler.getConsentData(); - if (shouldEnforce(consentData, 7, 'Analytics')) { - if (!isArray(config)) { - config = [config] - } - config = config.filter(conf => { - const analyticsAdapterCode = conf.provider; - const gvlid = getGvlid(analyticsAdapterCode, conf); - const isAllowed = !!validateRules(purpose7Rule, consentData, analyticsAdapterCode, gvlid); - if (!isAllowed) { - analyticsBlocked.push(analyticsAdapterCode); - logWarn(`TCF2 blocked analytics adapter ${conf.provider}`); - } - return isAllowed; - }); - fn.call(this, config); - } else { - fn.call(this, config); - } -} +export const transmitUfpdRule = gdprRule(4, () => purpose4Rule, ufpdBlocked); /** * Compiles the TCF2.0 enforcement results into an object, which is emitted as an event payload to "tcf2Enforcement" event. */ function emitTCF2FinalResults() { // remove null and duplicate values - const formatArray = function (arr) { - return arr.filter((i, k) => i !== null && arr.indexOf(i) === k); - } + const formatSet = function (st) { + return Array.from(st.keys()).filter(el => el != null); + }; const tcf2FinalResults = { - storageBlocked: formatArray(storageBlocked), - biddersBlocked: formatArray(biddersBlocked), - analyticsBlocked: formatArray(analyticsBlocked) + storageBlocked: formatSet(storageBlocked), + biddersBlocked: formatSet(biddersBlocked), + analyticsBlocked: formatSet(analyticsBlocked), + ufpdBlocked: formatSet(ufpdBlocked), }; events.emit(CONSTANTS.EVENTS.TCF2_ENFORCEMENT, tcf2FinalResults); + [storageBlocked, biddersBlocked, analyticsBlocked, ufpdBlocked].forEach(el => el.clear()); } events.on(CONSTANTS.EVENTS.AUCTION_END, emitTCF2FinalResults); -/* - Set of callback functions used to detect presence of a TCF rule, passed as the second argument to find(). -*/ -const hasPurpose1 = (rule) => { return rule.purpose === TCF2.purpose1.name } -const hasPurpose2 = (rule) => { return rule.purpose === TCF2.purpose2.name } -const hasPurpose7 = (rule) => { return rule.purpose === TCF2.purpose7.name } +function hasPurpose(purposeNo) { + const pname = TCF2[`purpose${purposeNo}`].name; + return (rule) => rule.purpose === pname; +} /** * A configuration function that initializes some module variables, as well as adds hooks @@ -370,9 +277,10 @@ export function setEnforcementConfig(config) { } strictStorageEnforcement = !!deepAccess(config, STRICT_STORAGE_ENFORCEMENT); - purpose1Rule = find(enforcementRules, hasPurpose1); - purpose2Rule = find(enforcementRules, hasPurpose2); - purpose7Rule = find(enforcementRules, hasPurpose7); + purpose1Rule = find(enforcementRules, hasPurpose(1)); + purpose2Rule = find(enforcementRules, hasPurpose(2)); + purpose4Rule = find(enforcementRules, hasPurpose(4)) + purpose7Rule = find(enforcementRules, hasPurpose(7)); if (!purpose1Rule) { purpose1Rule = DEFAULT_RULES[0]; @@ -385,28 +293,24 @@ export function setEnforcementConfig(config) { if (!hooksAdded) { if (purpose1Rule) { hooksAdded = true; - validateStorageEnforcement.before(deviceAccessHook, 49); - registerSyncInner.before(userSyncHook, 48); - // Using getHook as user id and gdprEnforcement are both optional modules. Using import will auto include the file in build - getHook('validateGdprEnforcement').before(userIdHook, 47); + RULE_HANDLES.push(registerActivityControl(ACTIVITY_ACCESS_DEVICE, RULE_NAME, accessDeviceRule)); + RULE_HANDLES.push(registerActivityControl(ACTIVITY_SYNC_USER, RULE_NAME, syncUserRule)); + RULE_HANDLES.push(registerActivityControl(ACTIVITY_ENRICH_EIDS, RULE_NAME, enrichEidsRule)); } if (purpose2Rule) { - getHook('makeBidRequests').before(makeBidRequestsHook); + RULE_HANDLES.push(registerActivityControl(ACTIVITY_FETCH_BIDS, RULE_NAME, fetchBidsRule)); + } + if (purpose4Rule) { + RULE_HANDLES.push(registerActivityControl(ACTIVITY_TRANSMIT_UFPD, RULE_NAME, transmitUfpdRule)); } if (purpose7Rule) { - getHook('enableAnalyticsCb').before(enableAnalyticsHook); + RULE_HANDLES.push(registerActivityControl(ACTIVITY_REPORT_ANALYTICS, RULE_NAME, reportAnalyticsRule)); } } } export function uninstall() { - [ - validateStorageEnforcement.getHooks({hook: deviceAccessHook}), - registerSyncInner.getHooks({hook: userSyncHook}), - getHook('validateGdprEnforcement').getHooks({hook: userIdHook}), - getHook('makeBidRequests').getHooks({hook: makeBidRequestsHook}), - getHook('enableAnalyticsCb').getHooks({hook: enableAnalyticsHook}), - ].forEach(hook => hook.remove()); + while (RULE_HANDLES.length) RULE_HANDLES.pop()(); hooksAdded = false; } diff --git a/modules/genericAnalyticsAdapter.js b/modules/genericAnalyticsAdapter.js index 4909745e72b..b52cb7e5464 100644 --- a/modules/genericAnalyticsAdapter.js +++ b/modules/genericAnalyticsAdapter.js @@ -115,11 +115,6 @@ export function GenericAnalytics() { } }, track(event) { - if (event.eventType === CONSTANTS.EVENTS.AUCTION_INIT && event.args.hasOwnProperty('config')) { - // clean up auctionInit event - // TODO: remove this special case in v8 - delete event.args.config; - } const datum = translate(event); if (datum != null) { batch.push(datum); diff --git a/modules/glimpseBidAdapter.js b/modules/glimpseBidAdapter.js deleted file mode 100644 index bbb4dbb30cd..00000000000 --- a/modules/glimpseBidAdapter.js +++ /dev/null @@ -1,202 +0,0 @@ -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { config } from '../src/config.js'; -import { BANNER } from '../src/mediaTypes.js'; -import { getStorageManager } from '../src/storageManager.js'; -import { - isArray, - isEmpty, - isEmptyStr, - isStr, - isPlainObject, -} from '../src/utils.js'; - -const GVLID = 1012; -const BIDDER_CODE = 'glimpse'; -const storageManager = getStorageManager({ - gvlid: GVLID, - bidderCode: BIDDER_CODE, -}); -const ENDPOINT = 'https://market.glimpsevault.io/public/v1/prebid'; -const LOCAL_STORAGE_KEY = { - vault: { - jwt: 'gp_vault_jwt', - }, -}; - -export const spec = { - gvlid: GVLID, - code: BIDDER_CODE, - supportedMediaTypes: [BANNER], - - /** - * Determines if the bid request is valid - * @param bid {BidRequest} The bid to validate - * @return {boolean} - */ - isBidRequestValid: (bid) => { - const pid = bid?.params?.pid; - return isStr(pid) && !isEmptyStr(pid); - }, - - /** - * Builds the http request - * @param validBidRequests {BidRequest[]} - * @param bidderRequest {BidderRequest} - * @returns {ServerRequest} - */ - buildRequests: (validBidRequests, bidderRequest) => { - const url = buildQuery(bidderRequest); - const auth = getVaultJwt(); - const referer = getReferer(bidderRequest); - const imp = validBidRequests.map(processBidRequest); - const fpd = getFirstPartyData(bidderRequest.ortb2); - - const data = { - auth, - data: { - referer, - imp, - fpd, - }, - }; - - return { - method: 'POST', - url, - data: JSON.stringify(data), - options: {}, - }; - }, - - /** - * Parse http response - * @param response {ServerResponse} - * @returns {Bid[]} - */ - interpretResponse: (response) => { - if (isValidResponse(response)) { - const { auth, data } = response.body; - setVaultJwt(auth); - const bids = data.bids.map(processBidResponse); - return bids; - } - return []; - }, -}; - -function setVaultJwt(auth) { - storageManager.setDataInLocalStorage(LOCAL_STORAGE_KEY.vault.jwt, auth); -} - -function getVaultJwt() { - return ( - storageManager.getDataFromLocalStorage(LOCAL_STORAGE_KEY.vault.jwt) || '' - ); -} - -function getReferer(bidderRequest) { - // TODO: is 'page' the right value here? - return bidderRequest?.refererInfo?.page || ''; -} - -function buildQuery(bidderRequest) { - let url = appendQueryParam(ENDPOINT, 'ver', '$prebid.version$'); - - const timeout = config.getConfig('bidderTimeout'); - url = appendQueryParam(url, 'tmax', timeout); - - if (gdprApplies(bidderRequest)) { - const consentString = bidderRequest.gdprConsent.consentString; - url = appendQueryParam(url, 'gdpr', consentString); - } - - if (ccpaApplies(bidderRequest)) { - url = appendQueryParam(url, 'ccpa', bidderRequest.uspConsent); - } - - return url; -} - -function appendQueryParam(url, key, value) { - if (!value) { - return url; - } - const prefix = url.includes('?') ? '&' : '?'; - return `${url}${prefix}${key}=${encodeURIComponent(value)}`; -} - -function gdprApplies(bidderRequest) { - return Boolean(bidderRequest?.gdprConsent?.gdprApplies); -} - -function ccpaApplies(bidderRequest) { - return ( - isStr(bidderRequest.uspConsent) && - !isEmptyStr(bidderRequest.uspConsent) && - bidderRequest.uspConsent?.substr(1, 3) !== '---' - ); -} - -function processBidRequest(bid) { - const sizes = normalizeSizes(bid.sizes); - - return { - bid: bid.bidId, - pid: bid.params.pid, - sizes, - }; -} - -function normalizeSizes(sizes) { - const isSingleSize = - isArray(sizes) && - sizes.length === 2 && - !isArray(sizes[0]) && - !isArray(sizes[1]); - - if (isSingleSize) { - return [sizes]; - } - - return sizes; -} - -function getFirstPartyData(ortb2) { - let fpd = ortb2 || {}; - optimizeObject(fpd); - return fpd; -} - -function optimizeObject(obj) { - if (!isPlainObject(obj)) { - return; - } - for (const [key, value] of Object.entries(obj)) { - optimizeObject(value); - // only delete empty object, array, or string - if ( - (isPlainObject(value) || isArray(value) || isStr(value)) && - isEmpty(value) - ) { - delete obj[key]; - } - } -} - -function isValidResponse(bidResponse) { - const auth = bidResponse?.body?.auth; - const bids = bidResponse?.body?.data?.bids; - return isStr(auth) && isArray(bids) && !isEmpty(bids); -} - -function processBidResponse(bid) { - const meta = bid.meta || {}; - meta.advertiserDomains = bid.meta?.advertiserDomains || []; - - return { - ...bid, - meta, - }; -} - -registerBidder(spec); diff --git a/modules/glimpseBidAdapter.md b/modules/glimpseBidAdapter.md deleted file mode 100644 index e82c5d8f32e..00000000000 --- a/modules/glimpseBidAdapter.md +++ /dev/null @@ -1,37 +0,0 @@ -# Overview - -``` -Module Name: Glimpse Protocol Bid Adapter -Module Type: Bidder Adapter -Maintainer: publisher@glimpseprotocol.io -``` - -# Description - -This module connects publishers to Glimpse Protocol's demand sources via Prebid.js. Our innovative marketplace protects -consumer privacy while allowing precise targeting. It is compliant with GDPR, DPA and CCPA. - -The Glimpse Adapter supports banner formats. - -# Test Parameters - -```javascript -const adUnits = [ - { - code: 'banner-div-a', - mediaTypes: { - banner: { - sizes: [[300, 250]], - }, - }, - bids: [ - { - bidder: 'glimpse', - params: { - pid: 'e53a7f564f8f44cc913b', - }, - }, - ], - }, -]; -``` diff --git a/modules/globalsunBidAdapter.js b/modules/globalsunBidAdapter.js index ae3407bbbdd..5b5d97c2cac 100644 --- a/modules/globalsunBidAdapter.js +++ b/modules/globalsunBidAdapter.js @@ -155,7 +155,7 @@ export const spec = { coppa: config.getConfig('coppa') === true ? 1 : 0, ccpa: bidderRequest.uspConsent || undefined, gdpr: bidderRequest.gdprConsent || undefined, - tmax: config.getConfig('bidderTimeout') + tmax: bidderRequest.timeout }; const len = validBidRequests.length; diff --git a/modules/glomexBidAdapter.js b/modules/glomexBidAdapter.js index 5c9b3c1fa28..10f5593940e 100644 --- a/modules/glomexBidAdapter.js +++ b/modules/glomexBidAdapter.js @@ -26,6 +26,7 @@ export const spec = { method: 'POST', url: `${ENDPOINT}`, data: { + // TODO: fix auctionId leak: https://github.com/prebid/Prebid.js/issues/9781 auctionId: bidderRequest.auctionId, refererInfo: { // TODO: this collects everything it finds, except for canonicalUrl diff --git a/modules/gmosspBidAdapter.js b/modules/gmosspBidAdapter.js index 9bc1a15b60b..8c90d0cccfe 100644 --- a/modules/gmosspBidAdapter.js +++ b/modules/gmosspBidAdapter.js @@ -47,7 +47,7 @@ export const spec = { let queryString = ''; const request = validBidRequests[i]; - const tid = request.transactionId; + const tid = request.ortb2Imp?.ext?.tid; const bid = request.bidId; const imuid = deepAccess(request, 'userId.imuid'); const sharedId = deepAccess(request, 'userId.pubcid'); diff --git a/modules/gnetBidAdapter.js b/modules/gnetBidAdapter.js index 8bab043d0db..38e96c183b9 100644 --- a/modules/gnetBidAdapter.js +++ b/modules/gnetBidAdapter.js @@ -4,10 +4,9 @@ import { BANNER } from '../src/mediaTypes.js'; import { getStorageManager } from '../src/storageManager.js'; import {ajax} from '../src/ajax.js'; -const storage = getStorageManager(); - const BIDDER_CODE = 'gnet'; const ENDPOINT = 'https://service.gnetrtb.com/api'; +const storage = getStorageManager({bidderCode: BIDDER_CODE}); export const spec = { code: BIDDER_CODE, @@ -40,7 +39,7 @@ export const spec = { data.referer = referer; data.adUnitCode = request.adUnitCode; data.bidId = request.bidId; - data.transactionId = request.transactionId; + data.transactionId = request.ortb2Imp?.ext?.tid; data.gftuid = _getCookie(); data.sizes = parseSizesInput(request.sizes); diff --git a/modules/goldbachBidAdapter.js b/modules/goldbachBidAdapter.js index 21c56643353..4768931950c 100644 --- a/modules/goldbachBidAdapter.js +++ b/modules/goldbachBidAdapter.js @@ -20,17 +20,18 @@ import { isStr, logError, logInfo, - logMessage, - transformBidderParamKeywords + logMessage } from '../src/utils.js'; import {config} from '../src/config.js'; -import {getIabSubCategory, registerBidder} from '../src/adapters/bidderFactory.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; import {ADPOD, BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; import {auctionManager} from '../src/auctionManager.js'; import {find, includes} from '../src/polyfill.js'; import {INSTREAM, OUTSTREAM} from '../src/video.js'; import {hasPurpose1Consent} from '../src/utils/gpdr.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import { APPNEXUS_CATEGORY_MAPPING } from '../libraries/categoryTranslationMapping/index.js'; +import {getANKeywordParam, transformBidderParamKeywords} from '../libraries/appnexusKeywords/anKeywords.js'; const BIDDER_CODE = 'goldbach'; const URL = 'https://ib.adnxs.com/ut/v3/prebid'; @@ -86,7 +87,6 @@ const NATIVE_MAPPING = { }; const SOURCE = 'pbjs'; const MAX_IMPS_PER_REQUEST = 15; -const mappingFileUrl = 'https://acdn.adnxs-simple.com/prebid/appnexus-mapping/mappings.json'; const SCRIPT_TAG_START = ' { let convertedKey = convertCamelToUnderscore(paramKey); if (convertedKey !== paramKey) { @@ -443,16 +421,6 @@ export const spec = { } }; -function isPopulatedArray(arr) { - return !!(isArray(arr) && arr.length > 0); -} - -function deleteValues(keyPairObj) { - if (isPopulatedArray(keyPairObj.value) && keyPairObj.value[0] === '') { - delete keyPairObj.value; - } -} - function reloadViewabilityScriptWithCorrectParameters(bid) { let viewJsPayload = getAppnexusViewabilityScriptFromJsTrackers(bid.native.javascriptTrackers); @@ -656,7 +624,7 @@ function newBid(serverBid, rtbBid, bidderRequest) { const videoContext = deepAccess(bidRequest, 'mediaTypes.video.context'); switch (videoContext) { case ADPOD: - const primaryCatId = getIabSubCategory(bidRequest.bidder, rtbBid.brand_category_id); + const primaryCatId = (APPNEXUS_CATEGORY_MAPPING[rtbBid.brand_category_id]) ? APPNEXUS_CATEGORY_MAPPING[rtbBid.brand_category_id] : null; bid.meta = Object.assign({}, bid.meta, { primaryCatId }); const dealTier = rtbBid.deal_priority; bid.video = { @@ -797,14 +765,7 @@ function bidToTag(bid) { if (bid.params.externalImpId) { tag.external_imp_id = bid.params.externalImpId; } - if (!isEmpty(bid.params.keywords)) { - let keywords = transformBidderParamKeywords(bid.params.keywords); - - if (keywords.length > 0) { - keywords.forEach(deleteValues); - } - tag.keywords = keywords; - } + tag.keywords = getANKeywordParam(bid.ortb2, bid.params.keywords); let gpid = deepAccess(bid, 'ortb2Imp.ext.data.pbadslot'); if (gpid) { @@ -919,6 +880,7 @@ function bidToTag(bid) { tag['banner_frameworks'] = bid.params.frameworks; } + // TODO: why does this need to iterate through every ad unit? let adUnit = find(auctionManager.getAdUnits(), au => bid.transactionId === au.transactionId); if (adUnit && adUnit.mediaTypes && adUnit.mediaTypes.banner) { tag.ad_types.push(BANNER); diff --git a/modules/googleAnalyticsAdapter.js b/modules/googleAnalyticsAdapter.js deleted file mode 100644 index 88ce20d07fa..00000000000 --- a/modules/googleAnalyticsAdapter.js +++ /dev/null @@ -1,298 +0,0 @@ -/** - * ga.js - analytics adapter for google analytics - */ - -import { _each, logMessage } from '../src/utils.js'; -import * as events from '../src/events.js'; -import CONSTANTS from '../src/constants.json'; -import adapterManager from '../src/adapterManager.js'; - -var BID_REQUESTED = CONSTANTS.EVENTS.BID_REQUESTED; -var BID_TIMEOUT = CONSTANTS.EVENTS.BID_TIMEOUT; -var BID_RESPONSE = CONSTANTS.EVENTS.BID_RESPONSE; -var BID_WON = CONSTANTS.EVENTS.BID_WON; - -var _disableInteraction = { nonInteraction: true }; -var _analyticsQueue = []; -var _gaGlobal = null; -var _enableCheck = true; -var _category = 'Prebid.js Bids'; -var _eventCount = 0; -var _enableDistribution = false; -var _cpmDistribution = null; -var _trackerSend = null; -var _sampled = true; -var _sendFloors = false; - -let adapter = {}; - -/** - * This will enable sending data to google analytics. Only call once, or duplicate data will be sent! - * @param {object} provider use to set GA global (if renamed); - * @param {object} options use to configure adapter; - * @return {[type]} [description] - */ -adapter.enableAnalytics = function ({ provider, options }) { - _gaGlobal = provider || 'ga'; - _trackerSend = options && options.trackerName ? options.trackerName + '.send' : 'send'; - _sampled = typeof options === 'undefined' || typeof options.sampling === 'undefined' || - Math.random() < parseFloat(options.sampling); - - if (options && typeof options.global !== 'undefined') { - _gaGlobal = options.global; - } - if (options && typeof options.enableDistribution !== 'undefined') { - _enableDistribution = options.enableDistribution; - } - if (options && typeof options.cpmDistribution === 'function') { - _cpmDistribution = options.cpmDistribution; - } - if (options && typeof options.sendFloors !== 'undefined') { - _sendFloors = options.sendFloors; - } - - var bid = null; - - if (_sampled) { - // first send all events fired before enableAnalytics called - - var existingEvents = events.getEvents(); - - _each(existingEvents, function (eventObj) { - if (typeof eventObj !== 'object') { - return; - } - var args = eventObj.args; - - if (eventObj.eventType === BID_REQUESTED) { - bid = args; - sendBidRequestToGa(bid); - } else if (eventObj.eventType === BID_RESPONSE) { - // bid is 2nd args - bid = args; - sendBidResponseToGa(bid); - } else if (eventObj.eventType === BID_TIMEOUT) { - const bidderArray = args; - sendBidTimeouts(bidderArray); - } else if (eventObj.eventType === BID_WON) { - bid = args; - sendBidWonToGa(bid); - } - }); - - // Next register event listeners to send data immediately - - // bidRequests - events.on(BID_REQUESTED, function (bidRequestObj) { - sendBidRequestToGa(bidRequestObj); - }); - - // bidResponses - events.on(BID_RESPONSE, function (bid) { - sendBidResponseToGa(bid); - }); - - // bidTimeouts - events.on(BID_TIMEOUT, function (bidderArray) { - sendBidTimeouts(bidderArray); - }); - - // wins - events.on(BID_WON, function (bid) { - sendBidWonToGa(bid); - }); - } else { - logMessage('Prebid.js google analytics disabled by sampling'); - } - - // finally set this function to return log message, prevents multiple adapter listeners - this.enableAnalytics = function _enable() { - return logMessage(`Analytics adapter already enabled, unnecessary call to \`enableAnalytics\`.`); - }; -}; - -adapter.getTrackerSend = function getTrackerSend() { - return _trackerSend; -}; - -/** - * Check if gaGlobal or window.ga is defined on page. If defined execute all the GA commands - */ -function checkAnalytics() { - if (_enableCheck && typeof window[_gaGlobal] === 'function') { - for (var i = 0; i < _analyticsQueue.length; i++) { - _analyticsQueue[i].call(); - } - - // override push to execute the command immediately from now on - _analyticsQueue.push = function (fn) { - fn.call(); - }; - - // turn check into NOOP - _enableCheck = false; - } - - logMessage('event count sent to GA: ' + _eventCount); -} - -function convertToCents(dollars) { - if (dollars) { - return Math.floor(dollars * 100); - } - - return 0; -} - -function getLoadTimeDistribution(time) { - var distribution; - if (time >= 0 && time < 200) { - distribution = '0-200ms'; - } else if (time >= 200 && time < 300) { - distribution = '0200-300ms'; - } else if (time >= 300 && time < 400) { - distribution = '0300-400ms'; - } else if (time >= 400 && time < 500) { - distribution = '0400-500ms'; - } else if (time >= 500 && time < 600) { - distribution = '0500-600ms'; - } else if (time >= 600 && time < 800) { - distribution = '0600-800ms'; - } else if (time >= 800 && time < 1000) { - distribution = '0800-1000ms'; - } else if (time >= 1000 && time < 1200) { - distribution = '1000-1200ms'; - } else if (time >= 1200 && time < 1500) { - distribution = '1200-1500ms'; - } else if (time >= 1500 && time < 2000) { - distribution = '1500-2000ms'; - } else if (time >= 2000) { - distribution = '2000ms above'; - } - - return distribution; -} - -function getCpmDistribution(cpm) { - if (_cpmDistribution) { - return _cpmDistribution(cpm); - } - var distribution; - if (cpm >= 0 && cpm < 0.5) { - distribution = '$0-0.5'; - } else if (cpm >= 0.5 && cpm < 1) { - distribution = '$0.5-1'; - } else if (cpm >= 1 && cpm < 1.5) { - distribution = '$1-1.5'; - } else if (cpm >= 1.5 && cpm < 2) { - distribution = '$1.5-2'; - } else if (cpm >= 2 && cpm < 2.5) { - distribution = '$2-2.5'; - } else if (cpm >= 2.5 && cpm < 3) { - distribution = '$2.5-3'; - } else if (cpm >= 3 && cpm < 4) { - distribution = '$3-4'; - } else if (cpm >= 4 && cpm < 6) { - distribution = '$4-6'; - } else if (cpm >= 6 && cpm < 8) { - distribution = '$6-8'; - } else if (cpm >= 8) { - distribution = '$8 above'; - } - - return distribution; -} - -function sendBidRequestToGa(bid) { - if (bid && bid.bidderCode) { - _analyticsQueue.push(function () { - _eventCount++; - if (_sendFloors) { - var floor = 'No Floor'; - if (bid.floorData) { - floor = bid.floorData.floorValue; - } else if (bid.bids.length) { - floor = bid.bids[0].getFloor().floor; - } - window[_gaGlobal](_trackerSend, 'event', _category, 'Requests by Floor=' + floor, bid.bidderCode, 1, _disableInteraction); - } else { - window[_gaGlobal](_trackerSend, 'event', _category, 'Requests', bid.bidderCode, 1, _disableInteraction); - } - }); - } - - // check the queue - checkAnalytics(); -} - -function sendBidResponseToGa(bid) { - if (bid && bid.bidderCode) { - _analyticsQueue.push(function () { - var cpmCents = convertToCents(bid.cpm); - var bidder = bid.bidderCode; - if (typeof bid.timeToRespond !== 'undefined' && _enableDistribution) { - _eventCount++; - var dis = getLoadTimeDistribution(bid.timeToRespond); - window[_gaGlobal](_trackerSend, 'event', 'Prebid.js Load Time Distribution', dis, bidder, 1, _disableInteraction); - } - - if (bid.cpm > 0) { - _eventCount = _eventCount + 2; - var cpmDis = getCpmDistribution(bid.cpm); - if (_enableDistribution) { - _eventCount++; - window[_gaGlobal](_trackerSend, 'event', 'Prebid.js CPM Distribution', cpmDis, bidder, 1, _disableInteraction); - } - if (_sendFloors) { - var floor = (bid.floorData) ? bid.floorData.floorValue : 'No Floor'; - window[_gaGlobal](_trackerSend, 'event', _category, 'Bids by Floor=' + floor, 'Size=' + bid.size + ',' + bidder, cpmCents, _disableInteraction); - } else { - window[_gaGlobal](_trackerSend, 'event', _category, 'Bids', bidder, cpmCents, _disableInteraction); - } - window[_gaGlobal](_trackerSend, 'event', _category, 'Bid Load Time', bidder, bid.timeToRespond, _disableInteraction); - } - }); - } - - // check the queue - checkAnalytics(); -} - -function sendBidTimeouts(timedOutBidders) { - _analyticsQueue.push(function () { - _each(timedOutBidders, function (bidderCode) { - _eventCount++; - var bidderName = bidderCode.bidder; - window[_gaGlobal](_trackerSend, 'event', _category, 'Timeouts', bidderName, _disableInteraction); - }); - }); - - checkAnalytics(); -} - -function sendBidWonToGa(bid) { - var cpmCents = convertToCents(bid.cpm); - _analyticsQueue.push(function () { - _eventCount++; - if (_sendFloors) { - var floor = (bid.floorData) ? bid.floorData.floorValue : 'No Floor'; - window[_gaGlobal](_trackerSend, 'event', _category, 'Wins by Floor=' + floor, 'Size=' + bid.size + ',' + bid.bidderCode, cpmCents, _disableInteraction); - } else { - window[_gaGlobal](_trackerSend, 'event', _category, 'Wins', bid.bidderCode, cpmCents, _disableInteraction); - } - }); - - checkAnalytics(); -} - -/** - * Exposed for testing purposes - */ -adapter.getCpmDistribution = getCpmDistribution; - -adapterManager.registerAnalyticsAdapter({ - adapter, - code: 'ga' -}); - -export default adapter; diff --git a/modules/googleAnalyticsAdapter.md b/modules/googleAnalyticsAdapter.md deleted file mode 100644 index 8c31b07b811..00000000000 --- a/modules/googleAnalyticsAdapter.md +++ /dev/null @@ -1,38 +0,0 @@ -# Google Analytics Adapter - -The google analytics adapter pushes prebid events into google analytics. - -## Usage - -The simplest way to enable the analytics adapter is this - -```javascript -pbjs.enableAnalytics([{ - provider: 'ga' -}]); -``` - -Defaults will be used and you should see events being pushed to analytics. - -You can customize the adapter with various `options` like this - -```javascript -pbjs.enableAnalytics([{ - provider: 'ga', - options: { ... } -}]); - -Here is a full list of settings available - -- `global` (string) - name of the global analytics object. Default is `ga` -- `trackerName` (string) - use another tracker for prebid events. Default is the default tracker -- `sampling` (number) - choose a value from `0` to `1`, where `0` means 0% and `1` means 100% tracked -- `enableDistribution` (boolean) - enables additional events that track load time and cpm distribution - by creating buckets for load time and cpm -- `cpmDistribution` (cpm: number => string) - customize the cpm buckets for the cpm distribution -- `sendFloors` (boolean) - if set, will include floor data in the eventCategory field and include ad unit code in eventAction field - - -## Additional resources - -- [Prebid GA Analytics](http://prebid.org/overview/ga-analytics.html) diff --git a/modules/gothamadsBidAdapter.js b/modules/gothamadsBidAdapter.js index 6b549f347bb..9f44a54460f 100644 --- a/modules/gothamadsBidAdapter.js +++ b/modules/gothamadsBidAdapter.js @@ -104,7 +104,7 @@ export const spec = { host: location.host }, source: { - tid: bidRequest.transactionId + tid: bidderRequest?.ortb2?.source?.tid, }, regs: { coppa: config.getConfig('coppa') === true ? 1 : 0, @@ -224,7 +224,7 @@ const parseNative = admObject => { const prepareImpObject = (bidRequest) => { let impObject = { - id: bidRequest.transactionId, + id: bidRequest.bidId, secure: 1, ext: { placementId: bidRequest.params.placementId @@ -247,7 +247,8 @@ const prepareImpObject = (bidRequest) => { const addNativeParameters = bidRequest => { let impObject = { - id: bidRequest.transactionId, + // TODO: this is not an "impObject", and `id` is not part of the ORTB native spec + id: bidRequest.bidId, ver: NATIVE_VERSION, }; diff --git a/modules/gppControl_usnat.js b/modules/gppControl_usnat.js new file mode 100644 index 00000000000..b38fc1a9d29 --- /dev/null +++ b/modules/gppControl_usnat.js @@ -0,0 +1,11 @@ +import {config} from '../src/config.js'; +import {setupRules} from '../libraries/mspa/activityControls.js'; + +let setupDone = false; + +config.getConfig('consentManagement', (cfg) => { + if (cfg?.consentManagement?.gpp != null && !setupDone) { + setupRules('usnat', [7]); + setupDone = true; + } +}) diff --git a/modules/gravitoIdSystem.js b/modules/gravitoIdSystem.js index 809263a1c68..aa25ea7db2c 100644 --- a/modules/gravitoIdSystem.js +++ b/modules/gravitoIdSystem.js @@ -6,9 +6,11 @@ */ import { submodule } from '../src/hook.js'; -import { getStorageManager } from '../src/storageManager.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; -export const storage = getStorageManager(); +const MODULE_NAME = 'gravitompId'; +export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); export const cookieKey = 'gravitompId'; @@ -17,7 +19,7 @@ export const gravitoIdSystemSubmodule = { * used to link submodule with config * @type {string} */ - name: 'gravitompId', + name: MODULE_NAME, /** * performs action to obtain id diff --git a/modules/greenbidsAnalyticsAdapter.js b/modules/greenbidsAnalyticsAdapter.js new file mode 100644 index 00000000000..edc0c9c6c5c --- /dev/null +++ b/modules/greenbidsAnalyticsAdapter.js @@ -0,0 +1,175 @@ +import {ajax} from '../src/ajax.js'; +import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; +import CONSTANTS from '../src/constants.json'; +import adapterManager from '../src/adapterManager.js'; +import {deepClone, logError, logInfo} from '../src/utils.js'; + +const analyticsType = 'endpoint'; + +export const ANALYTICS_VERSION = '1.0.0'; + +const ANALYTICS_SERVER = 'https://a.greenbids.ai'; + +const { + EVENTS: { + AUCTION_END, + BID_TIMEOUT, + } +} = CONSTANTS; + +export const BIDDER_STATUS = { + BID: 'bid', + NO_BID: 'noBid', + TIMEOUT: 'timeout' +}; + +const analyticsOptions = {}; + +export const parseBidderCode = function (bid) { + let bidderCode = bid.bidderCode || bid.bidder; + return bidderCode.toLowerCase(); +}; + +export const greenbidsAnalyticsAdapter = Object.assign(adapter({ANALYTICS_SERVER, analyticsType}), { + + cachedAuctions: {}, + + initConfig(config) { + /** + * Required option: pbuid + * @type {boolean} + */ + analyticsOptions.options = deepClone(config.options); + if (typeof config.options.pbuid !== 'string' || config.options.pbuid.length < 1) { + logError('"options.pbuid" is required.'); + return false; + } + + analyticsOptions.pbuid = config.options.pbuid + analyticsOptions.server = ANALYTICS_SERVER; + return true; + }, + sendEventMessage(endPoint, data) { + logInfo(`AJAX: ${endPoint}: ` + JSON.stringify(data)); + + ajax(`${analyticsOptions.server}${endPoint}`, null, JSON.stringify(data), { + contentType: 'application/json' + }); + }, + createCommonMessage(auctionId) { + return { + version: ANALYTICS_VERSION, + auctionId: auctionId, + referrer: window.location.href, + sampling: analyticsOptions.options.sampling, + prebid: '$prebid.version$', + pbuid: analyticsOptions.pbuid, + adUnits: [], + }; + }, + serializeBidResponse(bid, status) { + return { + bidder: bid.bidder, + isTimeout: (status === BIDDER_STATUS.TIMEOUT), + hasBid: (status === BIDDER_STATUS.BID), + }; + }, + addBidResponseToMessage(message, bid, status) { + const adUnitCode = bid.adUnitCode.toLowerCase(); + const adUnitIndex = message.adUnits.findIndex((adUnit) => { + return adUnit.code === adUnitCode; + }); + if (adUnitIndex === -1) { + logError('Trying to add to non registered adunit'); + return; + } + const bidderIndex = message.adUnits[adUnitIndex].bidders.findIndex((bidder) => { + return bidder.bidder === bid.bidder; + }); + if (bidderIndex === -1) { + message.adUnits[adUnitIndex].bidders.push(this.serializeBidResponse(bid, status)); + } else { + if (status === BIDDER_STATUS.BID) { + message.adUnits[adUnitIndex].bidders[bidderIndex].hasBid = true; + } else if (status === BIDDER_STATUS.TIMEOUT) { + message.adUnits[adUnitIndex].bidders[bidderIndex].isTimeout = true; + } + } + }, + createBidMessage(auctionEndArgs, timeoutBids) { + logInfo(auctionEndArgs) + const {auctionId, timestamp, auctionEnd, adUnits, bidsReceived, noBids} = auctionEndArgs; + const message = this.createCommonMessage(auctionId); + + message.auctionElapsed = (auctionEnd - timestamp); + + adUnits.forEach((adUnit) => { + const adUnitCode = adUnit.code.toLowerCase(); + message.adUnits.push({ + code: adUnitCode, + mediaTypes: { + ...(adUnit.mediaTypes.banner !== undefined) && {banner: adUnit.mediaTypes.banner}, + ...(adUnit.mediaTypes.video !== undefined) && {video: adUnit.mediaTypes.video}, + ...(adUnit.mediaTypes.native !== undefined) && {native: adUnit.mediaTypes.native} + }, + bidders: [], + }); + }); + + // We enrich noBid then bids, then timeouts, because in case of a timeout, one response from a bidder + // Can be in the 3 arrays, and we want that case reflected in the call + noBids.forEach(bid => this.addBidResponseToMessage(message, bid, BIDDER_STATUS.NO_BID)); + + bidsReceived.forEach(bid => this.addBidResponseToMessage(message, bid, BIDDER_STATUS.BID)); + + timeoutBids.forEach(bid => this.addBidResponseToMessage(message, bid, BIDDER_STATUS.TIMEOUT)); + + return message; + }, + getCachedAuction(auctionId) { + this.cachedAuctions[auctionId] = this.cachedAuctions[auctionId] || { + timeoutBids: [], + }; + return this.cachedAuctions[auctionId]; + }, + handleAuctionEnd(auctionEndArgs) { + const cachedAuction = this.getCachedAuction(auctionEndArgs.auctionId); + this.sendEventMessage('/', + this.createBidMessage(auctionEndArgs, cachedAuction.timeoutBids) + ); + }, + handleBidTimeout(timeoutBids) { + timeoutBids.forEach((bid) => { + const cachedAuction = this.getCachedAuction(bid.auctionId); + cachedAuction.timeoutBids.push(bid); + }); + }, + track({eventType, args}) { + switch (eventType) { + case BID_TIMEOUT: + this.handleBidTimeout(args); + break; + case AUCTION_END: + this.handleAuctionEnd(args); + break; + } + }, + getAnalyticsOptions() { + return analyticsOptions; + }, +}); + +greenbidsAnalyticsAdapter.originEnableAnalytics = greenbidsAnalyticsAdapter.enableAnalytics; + +greenbidsAnalyticsAdapter.enableAnalytics = function(config) { + this.initConfig(config); + logInfo('loading greenbids analytics'); + greenbidsAnalyticsAdapter.originEnableAnalytics(config); +}; + +adapterManager.registerAnalyticsAdapter({ + adapter: greenbidsAnalyticsAdapter, + code: 'greenbids' +}); + +export default greenbidsAnalyticsAdapter; diff --git a/modules/greenbidsAnalyticsAdapter.md b/modules/greenbidsAnalyticsAdapter.md new file mode 100644 index 00000000000..46e3af2c5e2 --- /dev/null +++ b/modules/greenbidsAnalyticsAdapter.md @@ -0,0 +1,23 @@ +# Overview + +``` +Module Name: Greenbids Analytics Adapter +Module Type: Analytics Adapter +Maintainer: jb@greenbids.ai +``` + +# Description + +Analytics adapter for Greenbids + +# Test Parameters + +``` +{ + provider: 'greenbids', + options: { + pbuid: "PBUID_FROM_GREENBIDS" + sampling: 1.0 + } +} +``` diff --git a/modules/greenbidsRtdProvider.js b/modules/greenbidsRtdProvider.js new file mode 100644 index 00000000000..ef12326cf18 --- /dev/null +++ b/modules/greenbidsRtdProvider.js @@ -0,0 +1,110 @@ +import { logError } from '../src/utils.js'; +import { ajax } from '../src/ajax.js'; +import { submodule } from '../src/hook.js'; + +const MODULE_NAME = 'greenbidsRtdProvider'; +const MODULE_VERSION = '1.0.0'; +const ENDPOINT = 'https://europe-west1-greenbids-357713.cloudfunctions.net/partner-selection'; + +const auctionInfo = {}; +const rtdOptions = {}; + +function init(moduleConfig) { + let params = moduleConfig?.params; + if (!params?.pbuid) { + logError('Greenbids pbuid is not set!'); + return false; + } else { + rtdOptions.pbuid = params?.pbuid; + rtdOptions.targetTPR = params?.targetTPR || 0.99; + rtdOptions.timeout = params?.timeout || 200; + return true; + } +} + +function onAuctionInitEvent(auctionDetails) { + auctionInfo.auctionId = auctionDetails.auctionId; +} + +function getBidRequestData(reqBidsConfigObj, callback, config, userConsent) { + let promise = createPromise(reqBidsConfigObj); + promise.then(callback); +} + +function createPromise(reqBidsConfigObj) { + return new Promise((resolve) => { + const timeoutId = setTimeout(() => { + resolve(reqBidsConfigObj); + }, rtdOptions.timeout); + ajax( + ENDPOINT, + { + success: (response) => { + processSuccessResponse(response, timeoutId, reqBidsConfigObj); + resolve(reqBidsConfigObj); + }, + error: () => { + clearTimeout(timeoutId); + resolve(reqBidsConfigObj); + }, + }, + createPayload(reqBidsConfigObj), + { contentType: 'application/json' } + ); + }); +} + +function processSuccessResponse(response, timeoutId, reqBidsConfigObj) { + clearTimeout(timeoutId); + const responseAdUnits = JSON.parse(response); + + updateAdUnitsBasedOnResponse(reqBidsConfigObj.adUnits, responseAdUnits); +} + +function updateAdUnitsBasedOnResponse(adUnits, responseAdUnits) { + adUnits.forEach((adUnit) => { + const matchingAdUnit = findMatchingAdUnit(responseAdUnits, adUnit.code); + if (matchingAdUnit) { + removeFalseBidders(adUnit, matchingAdUnit); + } + }); +} + +function findMatchingAdUnit(responseAdUnits, adUnitCode) { + return responseAdUnits.find((responseAdUnit) => responseAdUnit.code === adUnitCode); +} + +function removeFalseBidders(adUnit, matchingAdUnit) { + const falseBidders = getFalseBidders(matchingAdUnit.bidders); + adUnit.bids = adUnit.bids.filter((bidRequest) => !falseBidders.includes(bidRequest.bidder)); +} + +function getFalseBidders(bidders) { + return Object.entries(bidders) + .filter(([bidder, shouldKeep]) => !shouldKeep) + .map(([bidder]) => bidder); +} + +function createPayload(reqBidsConfigObj) { + return JSON.stringify({ + auctionId: auctionInfo.auctionId, + version: MODULE_VERSION, + referrer: window.location.href, + prebid: '$prebid.version$', + rtdOptions: rtdOptions, + adUnits: reqBidsConfigObj.adUnits, + }); +} + +export const greenbidsSubmodule = { + name: MODULE_NAME, + init: init, + onAuctionInitEvent: onAuctionInitEvent, + getBidRequestData: getBidRequestData, + updateAdUnitsBasedOnResponse: updateAdUnitsBasedOnResponse, + findMatchingAdUnit: findMatchingAdUnit, + removeFalseBidders: removeFalseBidders, + getFalseBidders: getFalseBidders, +}; + +submodule('realTimeData', greenbidsSubmodule); diff --git a/modules/greenbidsRtdProvider.md b/modules/greenbidsRtdProvider.md new file mode 100644 index 00000000000..85b8f5a7859 --- /dev/null +++ b/modules/greenbidsRtdProvider.md @@ -0,0 +1,65 @@ +# Overview + +``` +Module Name: Greenbids RTD Provider +Module Type: RTD Provider +Maintainer: jb@greenbids.ai +``` + +# Description + +The Greenbids RTD adapter allows to dynamically filter calls to SSP to reduce outgoing call to the programmatics chain, reducing ad serving carbon impact + +## Configuration + +This module is configured as part of the `realTimeData.dataProviders` object. + +{: .table .table-bordered .table-striped } +| Name | Scope | Description | Example | Type | +|------------|----------|----------------------------------------|---------------|----------| +| `name ` | required | Real time data module name | `'greenbidsRtdProvider'` | `string` | +| `waitForIt ` | required (mandatory true value) | Tells prebid auction to wait for the result of this module | `'true'` | `boolean` | +| `params` | required | | | `Object` | +| `params.pbuid` | required | The client site id provided by Greenbids. | `'TEST_FROM_GREENBIDS'` | `string` | +| `params.targetTPR` | optional (default 0.95) | Target True positive rate for the throttling model | `0.99` | `[0-1]` | +| `params.timeout` | optional (default 200) | Maximum amount of milliseconds allowed for module to finish working (has to be <= to the realTimeData.auctionDelay property) | `200` | `number` | + +#### Example + +```javascript +const greenbidsDataProvider = { + name: 'greenbidsRtdProvider', + waitForIt: true, + params: { + pbuid: 'TEST_FROM_GREENBIDS', + timeout: 200 + } +}; + +pbjs.setConfig({ + realTimeData: { + auctionDelay: 200, + dataProviders: [greenbidsDataProvider] + } +}); +``` + +## Integration +To install the module, follow these instructions: + +#### Step 1: Contact Greenbids to get a pbuid and account + +#### Step 2: Integrate the Greenbids Analytics Adapter + +Greenbids RTD module works hand in hand with Greenbids Analytics module +See prebid Analytics modules -> Greenbids Analytics module + +#### Step 3: Prepare the base Prebid file + +- Option 1: Use Prebid [Download](/download.html) page to build the prebid package. Ensure that you do check *Greenbids RTD Provider* module + +- Option 2: From the command line, run `gulp build --modules=greenbidsRtdProvider,...` + +#### Step 4: Set configuration + +Enable Greenbids Real Time Module using `pbjs.setConfig`. Example is provided in Configuration section. diff --git a/modules/gridBidAdapter.js b/modules/gridBidAdapter.js index ce7fcc680dd..ee8712b1de3 100644 --- a/modules/gridBidAdapter.js +++ b/modules/gridBidAdapter.js @@ -6,38 +6,31 @@ import { generateUUID, mergeDeep, logWarn, - parseUrl, isArray, isNumber + isNumber, + isStr } from '../src/utils.js'; +import { ajax } from '../src/ajax.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { Renderer } from '../src/Renderer.js'; import { VIDEO, BANNER } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; import { getStorageManager } from '../src/storageManager.js'; -import { find } from '../src/polyfill.js'; const BIDDER_CODE = 'grid'; const ENDPOINT_URL = 'https://grid.bidswitch.net/hbjson'; - -const ADAPTER_VERSION_FOR_CRITEO_MODE = 34; -const CDB_ENDPOINT = 'https://bidder.criteo.com/cdb'; -const PROFILE_ID_INLINE = 207; -const SID_COOKIE_NAME = 'cto_sid'; -const IDCPY_COOKIE_NAME = 'cto_idcpy'; -const OPTOUT_COOKIE_NAME = 'cto_optout'; -const BUNDLE_COOKIE_NAME = 'cto_bundle'; +const USP_DELETE_DATA_HANDLER = 'https://media.grid.bidswitch.net/uspapi_delete' const SYNC_URL = 'https://x.bidswitch.net/sync?ssp=themediagrid'; const TIME_TO_LIVE = 360; const USER_ID_KEY = 'tmguid'; const GVLID = 686; const RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; -export const storage = getStorageManager({gvlid: GVLID, bidderCode: BIDDER_CODE}); +export const storage = getStorageManager({ bidderCode: BIDDER_CODE }); const LOG_ERROR_MESS = { - noAuid: 'Bid from response has no auid parameter - ', + noAdid: 'Bid from response has no adid parameter - ', noAdm: 'Bid from response has no adm parameter - ', noBid: 'Array of bid objects is empty', - noPlacementCode: 'Can\'t find in requested bids the bid with auid - ', emptyUids: 'Uids should be not empty', emptySeatbid: 'Seatbid array from response has empty item', emptyResponse: 'Response is empty', @@ -52,7 +45,12 @@ const ALIAS_CONFIG = { bidResponseExternal: { netRevenue: false } - } + }, + 'gridNM': { + defaultParams: { + multiRequest: true + } + }, }; let hasSynced = false; @@ -60,7 +58,7 @@ let hasSynced = false; export const spec = { code: BIDDER_CODE, gvlid: GVLID, - aliases: ['playwire', 'adlivetech', { code: 'trustx', skipPbsAliasing: true }], + aliases: ['playwire', 'adlivetech', 'gridNM', { code: 'trustx', skipPbsAliasing: true }], supportedMediaTypes: [ BANNER, VIDEO ], /** * Determines whether or not the given bid request is valid. @@ -83,7 +81,6 @@ export const spec = { return null; } let pageKeywords = null; - let jwpseg = null; let content = null; let schain = null; let userIdAsEids = null; @@ -91,33 +88,24 @@ export const spec = { let userExt = null; let endpoint = null; let forceBidderName = false; - let {bidderRequestId, auctionId, gdprConsent, uspConsent, timeout, refererInfo} = bidderRequest || {}; + let {bidderRequestId, gdprConsent, uspConsent, timeout, refererInfo, gppConsent} = bidderRequest || {}; const referer = refererInfo ? encodeURIComponent(refererInfo.page) : ''; - const tmax = timeout || config.getConfig('bidderTimeout'); + const tmax = timeout; const imp = []; const bidsMap = {}; const requests = []; - const criteoBidsMap = {}; - const criteoBidRequests = []; const sources = []; const bidsArray = []; validBidRequests.forEach((bid) => { const bidObject = { bid, savedPrebidBid: null }; - if (bid.params.withCriteo && criteoSpec.isBidRequestValid(bid)) { - criteoBidsMap[bid.bidId] = bidObject; - criteoBidRequests.push(bid); - } if (!bid.params.uid && !bid.params.secid) { return; } if (!bidderRequestId) { bidderRequestId = bid.bidderRequestId; } - if (!auctionId) { - auctionId = bid.auctionId; - } if (!schain) { schain = bid.schain; } @@ -127,17 +115,13 @@ export const spec = { if (!endpoint) { endpoint = ALIAS_CONFIG[bid.bidder] && ALIAS_CONFIG[bid.bidder].endpoint; } - const { params: { uid, keywords, forceBidder, multiRequest }, mediaTypes, bidId, adUnitCode, rtd, ortb2Imp } = bid; - const { pubdata, secid, pubid, source, content: bidParamsContent } = bid.params; + const { params, mediaTypes, bidId, adUnitCode, rtd, ortb2Imp } = bid; + const { defaultParams } = ALIAS_CONFIG[bid.bidder] || {}; + const { secid, pubid, source, uid, keywords, forceBidder, multiRequest, content: bidParamsContent, video: videoParams } = { ...defaultParams, ...params }; const bidFloor = _getFloor(mediaTypes || {}, bid); const jwTargeting = rtd && rtd.jwplayer && rtd.jwplayer.targeting; - if (jwTargeting) { - if (!jwpseg && jwTargeting.segments) { - jwpseg = jwTargeting.segments; - } - if (!content && jwTargeting.content) { - content = jwTargeting.content; - } + if (jwTargeting && !content && jwTargeting.content) { + content = jwTargeting.content; } let impObj = { @@ -151,12 +135,18 @@ export const spec = { if (ortb2Imp.instl) { impObj.instl = ortb2Imp.instl; } - if (ortb2Imp.ext && ortb2Imp.ext.data) { - impObj.ext.data = ortb2Imp.ext.data; - if (impObj.ext.data.adserver && impObj.ext.data.adserver.adslot) { - impObj.ext.gpid = impObj.ext.data.adserver.adslot.toString(); - } else { - impObj.ext.gpid = ortb2Imp.ext.data.pbadslot && ortb2Imp.ext.data.pbadslot.toString(); + + if (ortb2Imp.ext) { + if (ortb2Imp.ext.data) { + impObj.ext.data = ortb2Imp.ext.data; + if (impObj.ext.data.adserver && impObj.ext.data.adserver.adslot) { + impObj.ext.gpid = impObj.ext.data.adserver.adslot.toString(); + } else if (ortb2Imp.ext.data.pbadslot) { + impObj.ext.gpid = ortb2Imp.ext.data.pbadslot.toString(); + } + } + if (ortb2Imp.ext.gpid) { + impObj.ext.gpid = ortb2Imp.ext.gpid.toString(); } } } @@ -178,7 +168,7 @@ export const spec = { } } if (mediaTypes && mediaTypes[VIDEO]) { - const video = createVideoRequest(bid, mediaTypes[VIDEO]); + const video = createVideoRequest(videoParams, mediaTypes[VIDEO], bid.sizes); if (video) { impObj.video = video; } @@ -187,7 +177,7 @@ export const spec = { if (impObj.banner || impObj.video) { if (multiRequest) { const reqSource = { - tid: bid.auctionId && bid.auctionId.toString(), + tid: bidderRequest?.ortb2?.source?.tid?.toString?.(), ext: { wrapper: 'Prebid_js', wrapper_version: '$prebid.version$' @@ -210,23 +200,12 @@ export const spec = { request.site.publisher = { id: pubid }; } - const reqJwpseg = (pubdata && pubdata.jwpseg) || (jwTargeting && jwTargeting.segments); - const siteContent = bidParamsContent || (jwTargeting && jwTargeting.content); if (siteContent) { request.site.content = siteContent; } - if (reqJwpseg && reqJwpseg.length) { - request.user = { - data: [{ - name: 'iow_labs_pub_data', - segment: segmentProcessing(reqJwpseg, 'jwpseg'), - }] - }; - } - requests.push(request); sources.push(source); bidsArray.push(bidObject); @@ -249,7 +228,7 @@ export const spec = { } const reqSource = { - tid: auctionId && auctionId.toString(), + tid: bidderRequest?.ortb2?.source?.tid?.toString?.(), ext: { wrapper: 'Prebid_js', wrapper_version: '$prebid.version$' @@ -274,28 +253,16 @@ export const spec = { mainRequest.site.content = content; } - if (jwpseg && jwpseg.length) { - mainRequest.user = { - data: [{ - name: 'iow_labs_pub_data', - segment: segmentProcessing(jwpseg, 'jwpseg'), - }] - }; - } - [...requests, mainRequest].forEach((request) => { if (!request) { return; } - if (request.user) user = request.user; + user = null; const ortb2UserData = deepAccess(bidderRequest, 'ortb2.user.data'); if (ortb2UserData && ortb2UserData.length) { - if (!user) user = { data: [] }; - user = mergeDeep(user, { - data: [...ortb2UserData] - }); + user = { data: [...ortb2UserData] }; } if (gdprConsent && gdprConsent.consentString) { @@ -369,11 +336,22 @@ export const spec = { } }; } + const ortb2Regs = deepAccess(bidderRequest, 'ortb2.regs') || {}; + if (gppConsent || ortb2Regs?.gpp) { + const gpp = { + gpp: gppConsent?.gppString ?? ortb2Regs?.gpp, + gpp_sid: gppConsent?.applicableSections ?? ortb2Regs?.gpp_sid + }; + request.regs = mergeDeep(request?.regs ?? {}, gpp); + } if (uspConsent) { if (!request.regs) { request.regs = {ext: {}}; } + if (!request.regs.ext) { + request.regs.ext = {}; + } request.regs.ext.us_privacy = uspConsent; } @@ -408,11 +386,6 @@ export const spec = { } }); - const criteoRequest = criteoBidRequests.length && criteoSpec.buildRequests(criteoBidRequests, bidderRequest); - if (criteoRequest) { - criteoRequest.criteoBidsMap = criteoBidsMap; - } - return [...requests.map((req, i) => { let sp; const url = (endpoint || ENDPOINT_URL).replace(/[?&]sp=([^?&=]+)/, (i, found) => { @@ -434,7 +407,7 @@ export const spec = { url: endpoint || ENDPOINT_URL, data: JSON.stringify(mainRequest), bidsMap - }] : []), ...(criteoRequest ? [criteoRequest] : [])]; + }] : [])]; }, /** * Unpack the response from the server into a list of bids. @@ -444,34 +417,26 @@ export const spec = { * @param {*} RendererConst * @return {Bid[]} An array of bids which were nested inside the server. */ - interpretResponse: function(serverResponse, bidRequest, RendererConst = Renderer) { - if (bidRequest.criteoBidsMap && bidRequest.bidRequests) { - const criteoBids = criteoSpec.interpretResponse(serverResponse, bidRequest); - return criteoBids.filter((bid) => { - const { savedPrebidBid } = bidRequest.criteoBidsMap[bid.requestId] || {}; - return canPublishResponse(bid.cpm, savedPrebidBid && savedPrebidBid.cpm); - }); - } else { - serverResponse = serverResponse && serverResponse.body; - const bidResponses = []; + interpretResponse: function (serverResponse, bidRequest, RendererConst = Renderer) { + serverResponse = serverResponse && serverResponse.body; + const bidResponses = []; - let errorMessage; + let errorMessage; - if (!serverResponse) errorMessage = LOG_ERROR_MESS.emptyResponse; - else if (serverResponse.seatbid && !serverResponse.seatbid.length) { - errorMessage = LOG_ERROR_MESS.hasEmptySeatbidArray; - } + if (!serverResponse) errorMessage = LOG_ERROR_MESS.emptyResponse; + else if (serverResponse.seatbid && !serverResponse.seatbid.length) { + errorMessage = LOG_ERROR_MESS.hasEmptySeatbidArray; + } - const bidderCode = this.forceBidderName || this.code; + const bidderCode = this.forceBidderName || this.code; - if (!errorMessage && serverResponse.seatbid) { - serverResponse.seatbid.forEach(respItem => { - _addBidResponse(_getBidFromResponse(respItem), bidRequest, bidResponses, RendererConst, bidderCode); - }); - } - if (errorMessage) logError(errorMessage); - return bidResponses; + if (!errorMessage && serverResponse.seatbid) { + serverResponse.seatbid.forEach(respItem => { + _addBidResponse(_getBidFromResponse(respItem), bidRequest, bidResponses, RendererConst, bidderCode); + }); } + if (errorMessage) logError(errorMessage); + return bidResponses; }, getUserSyncs: function (...args) { const [syncOptions,, gdprConsent, uspConsent] = args; @@ -499,6 +464,23 @@ export const spec = { url: syncUrl + params }; } + }, + + ajaxCall: function(url, cb, data, options) { + return ajax(url, cb, data, options); + }, + + onDataDeletionRequest: function(data) { + const uids = []; + const aliases = [spec.code, ...spec.aliases.map((alias) => alias.code || alias)]; + data.forEach(({ bids }) => bids && bids.forEach(({ bidder, params }) => { + if (aliases.includes(bidder) && params && params.uid) { + uids.push(params.uid); + } + })); + if (uids.length) { + spec.ajaxCall(USP_DELETE_DATA_HANDLER, () => {}, JSON.stringify({ uids }), {contentType: 'application/json', method: 'POST'}); + } } }; @@ -543,7 +525,7 @@ function _getBidFromResponse(respItem) { function _addBidResponse(serverBid, bidRequest, bidResponses, RendererConst, bidderCode) { if (!serverBid) return; let errorMessage; - if (!serverBid.auid) errorMessage = LOG_ERROR_MESS.noAuid + JSON.stringify(serverBid); + if (!serverBid.adid) errorMessage = LOG_ERROR_MESS.noAdid + JSON.stringify(serverBid); if (!errorMessage && !serverBid.adm && !serverBid.nurl) errorMessage = LOG_ERROR_MESS.noAdm + JSON.stringify(serverBid); else { const bidObject = bidRequest.bidsMap ? bidRequest.bidsMap[serverBid.impid] : bidRequest.bidObject; @@ -554,7 +536,7 @@ function _addBidResponse(serverBid, bidRequest, bidResponses, RendererConst, bid cpm: serverBid.price, width: serverBid.w, height: serverBid.h, - creativeId: serverBid.auid, // bid.bidId + creativeId: serverBid.adid, currency: 'USD', netRevenue: true, ttl: TIME_TO_LIVE, @@ -600,27 +582,41 @@ function _addBidResponse(serverBid, bidRequest, bidResponses, RendererConst, bid } } -function createVideoRequest(bid, mediaType) { - const { playerSize, mimes, durationRangeSec, protocols } = mediaType; - const size = (playerSize || bid.sizes || [])[0]; - if (!size) return; +function createVideoRequest(videoParams, mediaType, bidSizes) { + const { mind, maxd, size, playerSize, protocols, durationRangeSec = [], ...videoData } = { ...mediaType, ...videoParams }; + if (size && isStr(size)) { + const sizeArray = size.split('x'); + if (sizeArray.length === 2 && parseInt(sizeArray[0]) && parseInt(sizeArray[1])) { + videoData.w = parseInt(sizeArray[0]); + videoData.h = parseInt(sizeArray[1]); + } + } + if (!videoData.w || !videoData.h) { + const pSizesString = (playerSize || bidSizes || []).toString(); + const pSizeString = (pSizesString.match(/^\d+,\d+/) || [])[0]; + const pSize = pSizeString && pSizeString.split(',').map((d) => parseInt(d)); + if (pSize && pSize.length === 2) { + Object.assign(videoData, parseGPTSingleSizeArrayToRtbSize(pSize)); + } + } + + if (!videoData.w || !videoData.h) return; - let result = parseGPTSingleSizeArrayToRtbSize(size); + const minDur = mind || durationRangeSec[0] || videoData.minduration; + const maxDur = maxd || durationRangeSec[1] || videoData.maxduration; - if (mimes) { - result.mimes = mimes; + if (minDur) { + videoData.minduration = minDur; } - - if (durationRangeSec && durationRangeSec.length === 2) { - result.minduration = durationRangeSec[0]; - result.maxduration = durationRangeSec[1]; + if (maxDur) { + videoData.maxduration = maxDur; } if (protocols && protocols.length) { - result.protocols = protocols; + videoData.protocols = protocols; } - return result; + return videoData; } function createBannerRequest(bid, mediaType) { @@ -650,22 +646,6 @@ function getUserIdFromFPDStorage() { return storage.getDataFromLocalStorage(USER_ID_KEY) || makeNewUserIdInFPDStorage(); } -function segmentProcessing(segment, forceSegName) { - return segment - .map((seg) => { - const value = seg && (seg.id || seg); - if (typeof value === 'string' || typeof value === 'number') { - return { - value: value.toString(), - ...(forceSegName && { name: forceSegName }), - ...(seg.name && { name: seg.name }), - }; - } - return null; - }) - .filter((seg) => !!seg); -} - function reformatKeywords(pageKeywords) { const formatedPageKeywords = {}; Object.keys(pageKeywords).forEach((name) => { @@ -751,369 +731,4 @@ export function getSyncUrl() { return SYNC_URL; } -// ================ Criteo methods ================== - -const criteoSpec = { - /** f - * @param {object} bid - * @return {boolean} - */ - isBidRequestValid: (bid) => { - // either one of zoneId or networkId should be set - if (!(bid && bid.params && (bid.params.zoneId || bid.params.networkId))) { - return false; - } - - // video media types requires some mandatory params - if (hasVideoMediaType(bid)) { - if (!hasValidVideoMediaType(bid)) { - return false; - } - } - - return true; - }, - - /** - * @param {BidRequest[]} bidRequests - * @param {*} bidderRequest - * @return {ServerRequest} - */ - buildRequests: (bidRequests, bidderRequest) => { - let url; - let data; - let fpd = bidderRequest.ortb2 || {}; - - Object.assign(bidderRequest, { - publisherExt: fpd.site?.ext, - userExt: fpd.user?.ext, - ceh: config.getConfig('criteo.ceh'), - coppa: config.getConfig('coppa') - }); - - const context = buildContext(bidRequests, bidderRequest); - url = buildCdbUrl(context); - data = buildCdbRequest(context, bidRequests, bidderRequest); - - if (data) { - return { method: 'POST', url, data, bidRequests }; - } - }, - - /** - * @param {*} response - * @param {ServerRequest} request - * @return {Bid[]} - */ - interpretResponse: (response, request) => { - const body = response.body || response; - const bids = []; - - if (body && body.slots && isArray(body.slots)) { - body.slots.forEach(slot => { - const bidRequest = find(request.bidRequests, b => b.adUnitCode === slot.impid && (!b.params.zoneId || parseInt(b.params.zoneId) === slot.zoneid)); - const bidId = bidRequest.bidId; - const bid = { - requestId: bidId, - cpm: slot.cpm, - currency: slot.currency, - netRevenue: true, - ttl: slot.ttl || 60, - creativeId: slot.creativecode, - width: slot.width, - height: slot.height, - dealId: slot.dealCode, - }; - if (body.ext?.paf?.transmission && slot.ext?.paf?.content_id) { - const pafResponseMeta = { - content_id: slot.ext.paf.content_id, - transmission: response.ext.paf.transmission - }; - bid.meta = Object.assign({}, bid.meta, { paf: pafResponseMeta }); - } - if (slot.adomain) { - bid.meta = Object.assign({}, bid.meta, { advertiserDomains: slot.adomain }); - } - if (slot.video) { - bid.vastUrl = slot.displayurl; - bid.mediaType = VIDEO; - } else { - bid.ad = slot.creative; - } - bids.push(bid); - }); - } - - return bids; - } -}; - -function readFromAllStorages(name) { - const fromCookie = storage.getCookie(name); - const fromLocalStorage = storage.getDataFromLocalStorage(name); - - return fromCookie || fromLocalStorage || undefined; -} - -/** - * @param {BidRequest[]} bidRequests - * @param bidderRequest - */ -function buildContext(bidRequests, bidderRequest) { - let referrer = ''; - if (bidderRequest && bidderRequest.refererInfo) { - referrer = bidderRequest.refererInfo.page; - } - const queryString = parseUrl(bidderRequest?.refererInfo?.topmostLocation).search; - - const context = { - url: referrer, - debug: queryString['pbt_debug'] === '1', - noLog: queryString['pbt_nolog'] === '1', - amp: false, - }; - - bidRequests.forEach(bidRequest => { - if (bidRequest.params.integrationMode === 'amp') { - context.amp = true; - } - }); - - return context; -} - -/** - * @param {CriteoContext} context - * @return {string} - */ -function buildCdbUrl(context) { - let url = CDB_ENDPOINT; - url += '?profileId=' + PROFILE_ID_INLINE; - url += '&av=' + String(ADAPTER_VERSION_FOR_CRITEO_MODE); - url += '&wv=' + encodeURIComponent('$prebid.version$'); - url += '&cb=' + String(Math.floor(Math.random() * 99999999999)); - - if (storage.localStorageIsEnabled()) { - url += '&lsavail=1'; - } else { - url += '&lsavail=0'; - } - - if (context.amp) { - url += '&im=1'; - } - if (context.debug) { - url += '&debug=1'; - } - if (context.noLog) { - url += '&nolog=1'; - } - - const bundle = readFromAllStorages(BUNDLE_COOKIE_NAME); - if (bundle) { - url += `&bundle=${bundle}`; - } - - const optout = readFromAllStorages(OPTOUT_COOKIE_NAME); - if (optout) { - url += `&optout=1`; - } - - const sid = readFromAllStorages(SID_COOKIE_NAME); - if (sid) { - url += `&sid=${sid}`; - } - - const idcpy = readFromAllStorages(IDCPY_COOKIE_NAME); - if (idcpy) { - url += `&idcpy=${idcpy}`; - } - - return url; -} - -/** - * @param {CriteoContext} context - * @param {BidRequest[]} bidRequests - * @param bidderRequest - * @return {*} - */ -function buildCdbRequest(context, bidRequests, bidderRequest) { - let networkId; - let schain; - const request = { - publisher: { - url: context.url, - ext: bidderRequest.publisherExt, - }, - regs: { - coppa: bidderRequest.coppa === true ? 1 : (bidderRequest.coppa === false ? 0 : undefined) - }, - slots: bidRequests.map(bidRequest => { - networkId = bidRequest.params.networkId || networkId; - schain = bidRequest.schain || schain; - const slot = { - impid: bidRequest.adUnitCode, - transactionid: bidRequest.transactionId, - auctionId: bidRequest.auctionId, - }; - if (bidRequest.params.zoneId) { - slot.zoneid = bidRequest.params.zoneId; - } - if (deepAccess(bidRequest, 'ortb2Imp.ext')) { - slot.ext = bidRequest.ortb2Imp.ext; - } - if (bidRequest.params.ext) { - slot.ext = Object.assign({}, slot.ext, bidRequest.params.ext); - } - if (bidRequest.params.publisherSubId) { - slot.publishersubid = bidRequest.params.publisherSubId; - } - - if (hasBannerMediaType(bidRequest)) { - slot.sizes = parseSizes(deepAccess(bidRequest, 'mediaTypes.banner.sizes'), parseSize); - } else { - slot.sizes = []; - } - - if (hasVideoMediaType(bidRequest)) { - const video = { - playersizes: parseSizes(deepAccess(bidRequest, 'mediaTypes.video.playerSize'), parseSize), - mimes: bidRequest.mediaTypes.video.mimes, - protocols: bidRequest.mediaTypes.video.protocols, - maxduration: bidRequest.mediaTypes.video.maxduration, - api: bidRequest.mediaTypes.video.api, - skip: bidRequest.mediaTypes.video.skip, - placement: bidRequest.mediaTypes.video.placement, - minduration: bidRequest.mediaTypes.video.minduration, - playbackmethod: bidRequest.mediaTypes.video.playbackmethod, - startdelay: bidRequest.mediaTypes.video.startdelay - }; - const paramsVideo = bidRequest.params.video; - if (paramsVideo !== undefined) { - video.skip = video.skip || paramsVideo.skip || 0; - video.placement = video.placement || paramsVideo.placement; - video.minduration = video.minduration || paramsVideo.minduration; - video.playbackmethod = video.playbackmethod || paramsVideo.playbackmethod; - video.startdelay = video.startdelay || paramsVideo.startdelay || 0; - } - - slot.video = video; - } - - enrichSlotWithFloors(slot, bidRequest); - - return slot; - }), - }; - if (networkId) { - request.publisher.networkid = networkId; - } - if (schain) { - request.source = { - ext: { - schain: schain - } - }; - } - request.user = { - ext: bidderRequest.userExt - }; - if (bidderRequest && bidderRequest.ceh) { - request.user.ceh = bidderRequest.ceh; - } - if (bidderRequest && bidderRequest.gdprConsent) { - request.gdprConsent = {}; - if (typeof bidderRequest.gdprConsent.gdprApplies !== 'undefined') { - request.gdprConsent.gdprApplies = !!(bidderRequest.gdprConsent.gdprApplies); - } - request.gdprConsent.version = bidderRequest.gdprConsent.apiVersion; - if (typeof bidderRequest.gdprConsent.consentString !== 'undefined') { - request.gdprConsent.consentData = bidderRequest.gdprConsent.consentString; - } - } - if (bidderRequest && bidderRequest.uspConsent) { - request.user.uspIab = bidderRequest.uspConsent; - } - return request; -} - -function parseSizes(sizes, parser = s => s) { - if (sizes === undefined) { - return []; - } - if (Array.isArray(sizes[0])) { // is there several sizes ? (ie. [[728,90],[200,300]]) - return sizes.map(size => parser(size)); - } - return [parser(sizes)]; // or a single one ? (ie. [728,90]) -} - -function parseSize(size) { - return size[0] + 'x' + size[1]; -} - -function hasVideoMediaType(bidRequest) { - return deepAccess(bidRequest, 'mediaTypes.video') !== undefined; -} - -function hasBannerMediaType(bidRequest) { - return deepAccess(bidRequest, 'mediaTypes.banner') !== undefined; -} - -function hasValidVideoMediaType(bidRequest) { - let isValid = true; - - var requiredMediaTypesParams = ['mimes', 'playerSize', 'maxduration', 'protocols', 'api', 'skip', 'placement', 'playbackmethod']; - - requiredMediaTypesParams.forEach(function (param) { - if (deepAccess(bidRequest, 'mediaTypes.video.' + param) === undefined && deepAccess(bidRequest, 'params.video.' + param) === undefined) { - isValid = false; - logError('TheMediaGrid Bid Adapter (withCriteo mode): mediaTypes.video.' + param + ' is required'); - } - }); - - if (isValid) { - const videoPlacement = bidRequest.mediaTypes.video.placement || bidRequest.params.video.placement; - // We do not support long form for now, also we have to check that context & placement are consistent - if (bidRequest.mediaTypes.video.context === 'instream' && videoPlacement === 1) { - return true; - } else if (bidRequest.mediaTypes.video.context === 'outstream' && videoPlacement !== 1) { - return true; - } - } - - return false; -} - -function enrichSlotWithFloors(slot, bidRequest) { - try { - const slotFloors = {}; - - if (bidRequest.getFloor) { - if (bidRequest.mediaTypes?.banner) { - slotFloors.banner = {}; - const bannerSizes = parseSizes(deepAccess(bidRequest, 'mediaTypes.banner.sizes')) - bannerSizes.forEach(bannerSize => slotFloors.banner[parseSize(bannerSize).toString()] = bidRequest.getFloor({ size: bannerSize, mediaType: BANNER })); - } - - if (bidRequest.mediaTypes?.video) { - slotFloors.video = {}; - const videoSizes = parseSizes(deepAccess(bidRequest, 'mediaTypes.video.playerSize')) - videoSizes.forEach(videoSize => slotFloors.video[parseSize(videoSize).toString()] = bidRequest.getFloor({ size: videoSize, mediaType: VIDEO })); - } - - if (Object.keys(slotFloors).length > 0) { - if (!slot.ext) { - slot.ext = {} - } - Object.assign(slot.ext, { - floors: slotFloors - }); - } - } - } catch (e) { - logError('Could not parse floors from Prebid: ' + e); - } -} - registerBidder(spec); diff --git a/modules/gridNMBidAdapter.js b/modules/gridNMBidAdapter.js deleted file mode 100644 index c49b7619c07..00000000000 --- a/modules/gridNMBidAdapter.js +++ /dev/null @@ -1,448 +0,0 @@ -import { - isStr, - deepAccess, - isArray, - isNumber, - logError, - logWarn, - parseGPTSingleSizeArrayToRtbSize, - mergeDeep -} from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { Renderer } from '../src/Renderer.js'; -import { VIDEO } from '../src/mediaTypes.js'; -import { config } from '../src/config.js'; - -const BIDDER_CODE = 'gridNM'; -const ENDPOINT_URL = 'https://grid.bidswitch.net/hbjson'; -const SYNC_URL = 'https://x.bidswitch.net/sync?ssp=themediagrid'; -const TIME_TO_LIVE = 360; -const RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; - -let hasSynced = false; - -const LOG_ERROR_MESS = { - noAdm: 'Bid from response has no adm parameter - ', - noPrice: 'Bid from response has no price parameter - ', - wrongContentType: 'Bid from response has wrong content_type parameter - ', - noBid: 'Array of bid objects is empty', - noPlacementCode: 'Can\'t find in requested bids the bid with auid - ', - emptyUids: 'Uids should be not empty', - emptySeatbid: 'Seatbid array from response has empty item', - emptyResponse: 'Response is empty', - hasEmptySeatbidArray: 'Response has empty seatbid array', - hasNoArrayOfBids: 'Seatbid from response has no array of bid objects - ' -}; - -export const spec = { - code: BIDDER_CODE, - supportedMediaTypes: [ VIDEO ], - /** - * Determines whether or not the given bid request is valid. - * - * @param {BidRequest} bid The bid params to validate. - * @return boolean True if this is a valid bid, and false otherwise. - */ - isBidRequestValid: function(bid) { - let invalid = - !bid.params.source || !isStr(bid.params.source) || - !bid.params.secid || !isStr(bid.params.secid) || - !bid.params.pubid || !isStr(bid.params.pubid); - - const video = deepAccess(bid, 'mediaTypes.video') || {}; - const { protocols = video.protocols, mimes = video.mimes } = deepAccess(bid, 'params.video') || {}; - if (!invalid) { - invalid = !protocols || !mimes; - } - if (!invalid) { - invalid = !isArray(mimes) || !mimes.length || mimes.filter((it) => !(it && isStr(it))).length; - if (!invalid) { - invalid = !isArray(protocols) || !protocols.length || protocols.filter((it) => !(isNumber(it) && it > 0 && !(it % 1))).length; - } - } - return !invalid; - }, - /** - * Make a server request from the list of BidRequests. - * - * @param {BidRequest[]} validBidRequests - an array of bids - * @param {bidderRequest} bidderRequest bidder request object - * @return ServerRequest Info describing the request to the server. - */ - buildRequests: function(validBidRequests, bidderRequest) { - const bids = validBidRequests || []; - const requests = []; - let { bidderRequestId, auctionId, gdprConsent, uspConsent, timeout, refererInfo } = bidderRequest || {}; - - const referer = refererInfo ? encodeURIComponent(refererInfo.page) : ''; - - bids.forEach(bid => { - let user; - let userExt; - - const schain = bid.schain; - const userIdAsEids = bid.userIdAsEids; - - if (!bidderRequestId) { - bidderRequestId = bid.bidderRequestId; - } - if (!auctionId) { - auctionId = bid.auctionId; - } - const { - params: { floorcpm, pubdata, source, secid, pubid, content, video }, - mediaTypes, bidId, adUnitCode, rtd, ortb2Imp, sizes - } = bid; - - const bidFloor = _getFloor(mediaTypes || {}, bid, isNumber(floorcpm) && floorcpm); - const jwTargeting = rtd && rtd.jwplayer && rtd.jwplayer.targeting; - const jwpseg = (pubdata && pubdata.jwpseg) || (jwTargeting && jwTargeting.segments); - - const siteContent = content || (jwTargeting && jwTargeting.content); - - const impObj = { - id: bidId.toString(), - tagid: secid.toString(), - video: createVideoForImp(mergeDeep({}, video, mediaTypes && mediaTypes.video), sizes), - ext: { - divid: adUnitCode.toString() - } - }; - - if (ortb2Imp && ortb2Imp.ext && ortb2Imp.ext.data) { - impObj.ext.data = ortb2Imp.ext.data; - if (impObj.ext.data.adserver && impObj.ext.data.adserver.adslot) { - impObj.ext.gpid = impObj.ext.data.adserver.adslot.toString(); - } else { - impObj.ext.gpid = ortb2Imp.ext.data.pbadslot && ortb2Imp.ext.data.pbadslot.toString(); - } - } - - if (bidFloor) { - impObj.bidfloor = bidFloor; - } - - const imp = [impObj]; - - const reqSource = { - tid: auctionId && auctionId.toString(), - ext: { - wrapper: 'Prebid_js', - wrapper_version: '$prebid.version$' - } - }; - - if (schain) { - reqSource.ext.schain = schain; - } - - const bidderTimeout = config.getConfig('bidderTimeout') || timeout; - const tmax = timeout ? Math.min(bidderTimeout, timeout) : bidderTimeout; - - const request = { - id: bidderRequestId && bidderRequestId.toString(), - site: { - page: referer, - publisher: { - id: pubid, - }, - }, - source: reqSource, - tmax, - imp, - }; - - if (siteContent) { - request.site.content = siteContent; - } - - if (jwpseg && jwpseg.length) { - user = { - data: [{ - name: 'iow_labs_pub_data', - segment: segmentProcessing(jwpseg, 'jwpseg'), - }] - }; - } - - if (gdprConsent && gdprConsent.consentString) { - userExt = { consent: gdprConsent.consentString }; - } - - if (userIdAsEids && userIdAsEids.length) { - userExt = userExt || {}; - userExt.eids = [...userIdAsEids]; - } - - if (userExt && Object.keys(userExt).length) { - user = user || {}; - user.ext = userExt; - } - - const ortb2UserData = deepAccess(bidderRequest, 'ortb2.user.data'); - if (ortb2UserData && ortb2UserData.length) { - if (!user) { - user = { data: [] }; - } - user = mergeDeep(user, { - data: [...ortb2UserData] - }); - } - - if (user) { - request.user = user; - } - const site = deepAccess(bidderRequest, 'ortb2.site'); - if (site) { - const data = deepAccess(site, 'content.data'); - if (data && data.length) { - const siteContent = request.site.content || {}; - request.site.content = mergeDeep(siteContent, { data }); - } - } - - if (gdprConsent && gdprConsent.gdprApplies) { - request.regs = { - ext: { - gdpr: gdprConsent.gdprApplies ? 1 : 0 - } - }; - } - - if (uspConsent) { - if (!request.regs) { - request.regs = { ext: {} }; - } - request.regs.ext.us_privacy = uspConsent; - } - - if (config.getConfig('coppa') === true) { - if (!request.regs) { - request.regs = {}; - } - request.regs.coppa = 1; - } - - requests.push({ - method: 'POST', - url: ENDPOINT_URL + '?no_mapping=1&sp=' + source, - bid: bid, - data: request - }); - }); - - return requests; - }, - /** - * Unpack the response from the server into a list of bids. - * - * @param {*} serverResponse A successful response from the server. - * @param {*} bidRequest - * @return {Bid[]} An array of bids which were nested inside the server. - */ - interpretResponse: function(serverResponse, bidRequest) { - serverResponse = serverResponse && serverResponse.body; - const bidResponses = []; - - let errorMessage; - - if (!serverResponse) errorMessage = LOG_ERROR_MESS.emptyResponse; - else if (serverResponse.seatbid && !serverResponse.seatbid.length) { - errorMessage = LOG_ERROR_MESS.hasEmptySeatbidArray; - } - - if (!errorMessage && serverResponse.seatbid) { - const serverBid = _getBidFromResponse(serverResponse.seatbid[0]); - if (serverBid) { - if (!serverBid.adm && !serverBid.nurl) errorMessage = LOG_ERROR_MESS.noAdm + JSON.stringify(serverBid); - else if (!serverBid.price) errorMessage = LOG_ERROR_MESS.noPrice + JSON.stringify(serverBid); - else if (serverBid.content_type !== 'video') errorMessage = LOG_ERROR_MESS.wrongContentType + serverBid.content_type; - if (!errorMessage) { - const bid = bidRequest.bid; - const bidResponse = { - requestId: bid.bidId, - cpm: serverBid.price, - width: serverBid.w, - height: serverBid.h, - creativeId: serverBid.auid || bid.bidderRequestId, - currency: 'USD', - netRevenue: true, - ttl: TIME_TO_LIVE, - dealId: serverBid.dealid, - mediaType: VIDEO, - meta: { - advertiserDomains: serverBid.adomain ? serverBid.adomain : [] - } - }; - - if (serverBid.adm) { - bidResponse.vastXml = serverBid.adm; - bidResponse.adResponse = { - content: bidResponse.vastXml - }; - } else if (serverBid.nurl) { - bidResponse.vastUrl = serverBid.nurl; - } - - if (!bid.renderer && (!bid.mediaTypes || !bid.mediaTypes.video || bid.mediaTypes.video.context === 'outstream')) { - bidResponse.renderer = createRenderer(bidResponse, { - id: bid.bidId, - url: RENDERER_URL - }); - } - bidResponses.push(bidResponse); - } - } - } - if (errorMessage) logError(errorMessage); - return bidResponses; - }, - getUserSyncs: function (syncOptions, responses, gdprConsent, uspConsent) { - if (!hasSynced && syncOptions.pixelEnabled) { - let params = ''; - - if (gdprConsent && typeof gdprConsent.consentString === 'string') { - if (typeof gdprConsent.gdprApplies === 'boolean') { - params += `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; - } else { - params += `&gdpr_consent=${gdprConsent.consentString}`; - } - } - if (uspConsent) { - params += `&us_privacy=${uspConsent}`; - } - - hasSynced = true; - return { - type: 'image', - url: SYNC_URL + params - }; - } - } -}; - -/** - * Gets bidfloor - * @param {Object} mediaTypes - * @param {Object} bid - * @param {Number} floor - * @returns {Number} floor - */ -function _getFloor (mediaTypes, bid, floor) { - const curMediaType = mediaTypes.video ? 'video' : 'banner'; - - if (typeof bid.getFloor === 'function') { - const floorInfo = bid.getFloor({ - currency: 'USD', - mediaType: curMediaType, - size: bid.sizes.map(([w, h]) => ({w, h})) - }); - - if (typeof floorInfo === 'object' && - floorInfo.currency === 'USD' && - !isNaN(parseFloat(floorInfo.floor))) { - floor = Math.max(floor, parseFloat(floorInfo.floor)); - } - } - - return floor; -} - -function _getBidFromResponse(respItem) { - if (!respItem) { - logError(LOG_ERROR_MESS.emptySeatbid); - } else if (!respItem.bid) { - logError(LOG_ERROR_MESS.hasNoArrayOfBids + JSON.stringify(respItem)); - } else if (!respItem.bid[0]) { - logError(LOG_ERROR_MESS.noBid); - } - return respItem && respItem.bid && respItem.bid[0]; -} - -function outstreamRender (bid) { - bid.renderer.push(() => { - window.ANOutstreamVideo.renderAd({ - targetId: bid.adUnitCode, - adResponse: bid.adResponse - }); - }); -} - -function createRenderer (bid, rendererParams) { - const renderer = Renderer.install({ - id: rendererParams.id, - url: rendererParams.url, - loaded: false - }); - - try { - renderer.setRender(outstreamRender); - } catch (err) { - logWarn('Prebid Error calling setRender on renderer', err); - } - - return renderer; -} - -function createVideoForImp({ mind, maxd, size, ...paramsVideo }, bidSizes) { - if (size && isStr(size)) { - const sizeArray = size.split('x'); - if (sizeArray.length === 2 && parseInt(sizeArray[0]) && parseInt(sizeArray[1])) { - paramsVideo.w = parseInt(sizeArray[0]); - paramsVideo.h = parseInt(sizeArray[1]); - } - } - - if (!paramsVideo.w || !paramsVideo.h) { - const playerSizes = paramsVideo.playerSize && paramsVideo.playerSize.length === 2 ? paramsVideo.playerSize : bidSizes; - if (playerSizes) { - const playerSize = playerSizes[0]; - if (playerSize) { - Object.assign(paramsVideo, parseGPTSingleSizeArrayToRtbSize(playerSize)); - } - } - } - - if (paramsVideo.playerSize) { - delete paramsVideo.playerSize; - } - - const durationRangeSec = paramsVideo.durationRangeSec || []; - const minDur = mind || durationRangeSec[0] || paramsVideo.minduration; - const maxDur = maxd || durationRangeSec[1] || paramsVideo.maxduration; - - if (minDur) { - paramsVideo.minduration = minDur; - } - if (maxDur) { - paramsVideo.maxduration = maxDur; - } - - return paramsVideo; -} - -export function resetUserSync() { - hasSynced = false; -} - -export function getSyncUrl() { - return SYNC_URL; -} - -function segmentProcessing(segment, forceSegName) { - return segment - .map((seg) => { - const value = seg && (seg.id || seg); - if (typeof value === 'string' || typeof value === 'number') { - return { - value: value.toString(), - ...(forceSegName && { name: forceSegName }), - ...(seg.name && { name: seg.name }), - }; - } - return null; - }) - .filter((seg) => !!seg); -} - -registerBidder(spec); diff --git a/modules/gridNMBidAdapter.md b/modules/gridNMBidAdapter.md deleted file mode 100644 index 6decdde7f4c..00000000000 --- a/modules/gridNMBidAdapter.md +++ /dev/null @@ -1,39 +0,0 @@ -# Overview - -Module Name: The Grid Media Bidder Adapter -Module Type: Bidder Adapter -Maintainer: grid-tech@themediagrid.com - -# Description - -Module that connects to Grid demand source to fetch bids. -Grid bid adapter supports Banner and Video (instream and outstream). - -# Test Parameters -``` - var adUnits = [ - { - code: 'test-div', - mediaTypes: { - video: { - playerSize: [728, 90], - context: 'outstream' - } - }, - bids: [ - { - bidder: "gridNM", - params: { - source: 'jwp', - secid: '11', - pubid: '22', - video: { - mimes: ['video/mp4', 'video/x-ms-wmv'], - protocols: [1,2,3,4,5,6] - } - } - } - ] - } - ]; -``` diff --git a/modules/growthCodeAnalyticsAdapter.js b/modules/growthCodeAnalyticsAdapter.js index 1f11b891139..a2ab4ddbfac 100644 --- a/modules/growthCodeAnalyticsAdapter.js +++ b/modules/growthCodeAnalyticsAdapter.js @@ -6,15 +6,16 @@ import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import * as utils from '../src/utils.js'; import CONSTANTS from '../src/constants.json'; -import { getStorageManager } from '../src/storageManager.js'; +import {getStorageManager} from '../src/storageManager.js'; import {getRefererInfo} from '../src/refererDetection.js'; import {logError, logInfo} from '../src/utils.js'; +import {MODULE_TYPE_ANALYTICS} from '../src/activities/modules.js'; const MODULE_NAME = 'growthCodeAnalytics'; const DEFAULT_PID = 'INVALID_PID' const ENDPOINT_URL = 'https://p2.gcprivacy.com/v1/pb/analytics' -export const storage = getStorageManager(); +export const storage = getStorageManager({moduleType: MODULE_TYPE_ANALYTICS, moduleName: MODULE_NAME}); let sessionId = utils.generateUUID(); diff --git a/modules/growthCodeIdSystem.js b/modules/growthCodeIdSystem.js index edd7cd33012..fae022f1a56 100644 --- a/modules/growthCodeIdSystem.js +++ b/modules/growthCodeIdSystem.js @@ -8,14 +8,15 @@ import {logError, logInfo, tryAppendQueryString} from '../src/utils.js'; import {ajax} from '../src/ajax.js'; import { submodule } from '../src/hook.js' -import { getStorageManager } from '../src/storageManager.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; -const GCID_EXPIRY = 7; const MODULE_NAME = 'growthCodeId'; const GC_DATA_KEY = '_gc_data'; +const GCID_KEY = 'gcid'; const ENDPOINT_URL = 'https://p2.gcprivacy.com/v1/pb?' -export const storage = getStorageManager({ gvlid: undefined, moduleName: MODULE_NAME }); +export const storage = getStorageManager({ moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME }); /** * Read GrowthCode data from cookie or local storage @@ -25,14 +26,16 @@ export const storage = getStorageManager({ gvlid: undefined, moduleName: MODULE_ export function readData(key) { try { let payload - if (storage.cookiesAreEnabled()) { - payload = tryParse(storage.getCookie(key)) + if (storage.cookiesAreEnabled(null)) { + payload = tryParse(storage.getCookie(key, null)) } if (storage.hasLocalStorage()) { - payload = tryParse(storage.getDataFromLocalStorage(key)) + payload = tryParse(storage.getDataFromLocalStorage(key, null)) } - if ((payload.expire_at !== undefined) && (payload.expire_at > (Date.now() / 1000))) { - return payload + if (payload !== undefined) { + if (payload.expire_at > (Date.now() / 1000)) { + return payload + } } } catch (error) { logError(error); @@ -50,12 +53,8 @@ function storeData(key, value) { logInfo(MODULE_NAME + ': storing data: key=' + key + ' value=' + value); if (value) { - if (storage.hasLocalStorage()) { - storage.setDataInLocalStorage(key, value); - } - const expiresStr = (new Date(Date.now() + (GCID_EXPIRY * (60 * 60 * 24 * 1000)))).toUTCString(); - if (storage.cookiesAreEnabled()) { - storage.setCookie(key, value, expiresStr, 'LAX'); + if (storage.hasLocalStorage(null)) { + storage.setDataInLocalStorage(key, value, null); } } } catch (error) { @@ -69,11 +68,15 @@ function storeData(key, value) { * @param {object|null} */ function tryParse(data) { + let payload; try { - return JSON.parse(data); + payload = JSON.parse(data); + if (payload == null) { + return undefined + } + return payload } catch (err) { - logError(err); - return null; + return undefined; } } @@ -149,6 +152,7 @@ export const growthCodeIdSubmodule = { // If response is a valid json and should save is true if (respJson) { storeData(GC_DATA_KEY, JSON.stringify(respJson)) + storeData(GCID_KEY, respJson.gc_id); callback(respJson); } else { callback(); diff --git a/modules/growthCodeRtdProvider.js b/modules/growthCodeRtdProvider.js new file mode 100644 index 00000000000..370ace9a203 --- /dev/null +++ b/modules/growthCodeRtdProvider.js @@ -0,0 +1,131 @@ +/** + * This module adds GrowthCode HEM and other Data to Bid Requests + * @module modules/growthCodeRtdProvider + */ +import { submodule } from '../src/hook.js' +import { getStorageManager } from '../src/storageManager.js'; +import { + logMessage, logError, tryAppendQueryString, mergeDeep +} from '../src/utils.js'; +import * as ajax from '../src/ajax.js'; +import { MODULE_TYPE_RTD } from '../src/activities/modules.js'; + +const MODULE_NAME = 'growthCodeRtd'; +const LOG_PREFIX = 'GrowthCodeRtd: '; +const ENDPOINT_URL = 'https://p2.gcprivacy.com/v2/rtd?' +const RTD_EXPIRE_KEY = 'gc_rtd_expires_at' +const RTD_CACHE_KEY = 'gc_rtd_items' + +export const storage = getStorageManager({ moduleType: MODULE_TYPE_RTD, moduleName: MODULE_NAME }); +let items + +export const growthCodeRtdProvider = { + name: MODULE_NAME, + init: init, + getBidRequestData: alterBidRequests, + addData: addData, + callServer: callServer +}; + +/** + * Parse json if possible, else return null + * @param data + * @returns {any|null} + */ +function tryParse(data) { + try { + return JSON.parse(data); + } catch (err) { + logError(err); + return null; + } +} + +/** + * Init The RTD Module + * @param config + * @param userConsent + * @returns {boolean} + */ +function init(config, userConsent) { + logMessage(LOG_PREFIX + 'Init RTB'); + + if (config == null) { + return false + } + + const configParams = (config && config.params) || {}; + let expiresAt = parseInt(storage.getDataFromLocalStorage(RTD_EXPIRE_KEY, null)); + + items = tryParse(storage.getDataFromLocalStorage(RTD_CACHE_KEY, null)); + + return callServer(configParams, items, expiresAt, userConsent); +} +function callServer(configParams, items, expiresAt, userConsent) { + // Expire Cache + let now = Math.trunc(Date.now() / 1000); + if ((!isNaN(expiresAt)) && (now > expiresAt)) { + expiresAt = NaN; + storage.removeDataFromLocalStorage(RTD_CACHE_KEY, null) + storage.removeDataFromLocalStorage(RTD_EXPIRE_KEY, null) + } + if ((items === null) && (isNaN(expiresAt))) { + let gcid = localStorage.getItem('gcid') + + let url = configParams.url ? configParams.url : ENDPOINT_URL; + url = tryAppendQueryString(url, 'pid', configParams.pid); + url = tryAppendQueryString(url, 'u', window.location.href); + url = tryAppendQueryString(url, 'gcid', gcid); + if ((userConsent !== null) && (userConsent.gdpr !== null) && (userConsent.gdpr.consentData.getTCData.tcString)) { + url = tryAppendQueryString(url, 'tcf', userConsent.gdpr.consentData.getTCData.tcString) + } + + ajax.ajaxBuilder()(url, { + success: response => { + let respJson = tryParse(response); + // If response is a valid json and should save is true + if (respJson && respJson.results >= 1) { + storage.setDataInLocalStorage(RTD_CACHE_KEY, JSON.stringify(respJson.items), null); + storage.setDataInLocalStorage(RTD_EXPIRE_KEY, respJson.expires_at, null) + } else { + storage.setDataInLocalStorage(RTD_EXPIRE_KEY, respJson.expires_at, null) + } + }, + error: error => { + logError(LOG_PREFIX + 'ID fetch encountered an error', error); + } + }, undefined, {method: 'GET', withCredentials: true}) + } + + return true; +} + +function addData(reqBidsConfigObj, items) { + let merge = false + + for (let j = 0; j < items.length; j++) { + let item = items[j] + let data = JSON.parse(item.parameters); + if (item['attachment_point'] === 'data') { + mergeDeep(reqBidsConfigObj.ortb2Fragments.bidder, data) + merge = true + } + } + return merge +} + +/** + * Alter the Bid Request for additional information such as HEM or 3rd Party Ids + * @param reqBidsConfigObj + * @param callback + * @param config + * @param userConsent + */ +function alterBidRequests(reqBidsConfigObj, callback, config, userConsent) { + if (items != null) { + addData(reqBidsConfigObj, items) + } + callback(); +} + +submodule('realTimeData', growthCodeRtdProvider); diff --git a/modules/growthCodeRtdProvider.md b/modules/growthCodeRtdProvider.md new file mode 100644 index 00000000000..274b94da7bc --- /dev/null +++ b/modules/growthCodeRtdProvider.md @@ -0,0 +1,55 @@ +## GrowthCode Real-time Data Submodule + +The [GrowthCode](https://growthcode.io) real-time data module in Prebid enables publishers to fully +leverage the potential of their first-party audiences and contextual data. +With an integrated cookieless GrowthCode identity, this module offers real-time +contextual and audience segmentation (IAB Taxonomy 2.2, cattax: 6) capabilities, and HEMs that can seamlessly +integrate into your existing Prebid deployment, making it easy to maximize +your advertising strategies. + +## Building Prebid with GrowthCode Support + +Compile the GrowthCode RTD module into your Prebid build: + +`gulp serve --modules=userId,rtdModule,appnexusBidAdapter,growthCodeRtdProvider,sharedIdSystem,criteoBidAdapter` + +Please visit https://growthcode.io/ for more information. + +``` +pbjs.setConfig( + ... + realTimeData: { + auctionDelay: 1000, + dataProviders: [ + { + name: 'growthCodeRtd', + waitForIt: true, + params: { + pid: 'TEST01', + } + } + ] + } + ... +} +``` + +### Parameter Descriptions for the GrowthCode Configuration Section + +| Name | Type | Description | Notes | +|:---------------------------------|:--------|:--------------------------------------------------------------------------|:----------------------------| +| name | String | Real time data module name | Always 'growthCodeRtd' | +| waitForIt | Boolean | Required to ensure that the auction is delayed until prefetch is complete | Optional. Defaults to false | +| params | Object | | | +| params.pid | String | This is the Parter ID value obtained from GrowthCode | `TEST01` | +| params.url | String | Custom URL for server | Optional | + +## Testing + +To view an example of GrowthCode backends: + +`gulp serve --modules=userId,rtdModule,appnexusBidAdapter,growthCodeRtdProvider,sharedIdSystem,criteoBidAdapter` + +and then point your browser at: + +`http://localhost:9999/integrationExamples/gpt/growthcode.html` diff --git a/modules/gumgumBidAdapter.js b/modules/gumgumBidAdapter.js index 93306984ab1..d050af4ac8f 100644 --- a/modules/gumgumBidAdapter.js +++ b/modules/gumgumBidAdapter.js @@ -6,13 +6,13 @@ import {getStorageManager} from '../src/storageManager.js'; import {includes} from '../src/polyfill.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -const BIDDER_CODE = 'gumgum' +const BIDDER_CODE = 'gumgum'; const storage = getStorageManager({bidderCode: BIDDER_CODE}); -const ALIAS_BIDDER_CODE = ['gg'] -const BID_ENDPOINT = `https://g2.gumgum.com/hbid/imp` +const ALIAS_BIDDER_CODE = ['gg']; +const BID_ENDPOINT = `https://g2.gumgum.com/hbid/imp`; const JCSI = { t: 0, rq: 8, pbv: '$prebid.version$' } -const SUPPORTED_MEDIA_TYPES = [BANNER, VIDEO] -const TIME_TO_LIVE = 60 +const SUPPORTED_MEDIA_TYPES = [BANNER, VIDEO]; +const TIME_TO_LIVE = 60; const DELAY_REQUEST_TIME = 1800000; // setting to 30 mins let invalidRequestIds = {}; @@ -99,17 +99,6 @@ function getWrapperCode(wrapper, data) { return wrapper.replace('AD_JSON', window.btoa(JSON.stringify(data))) } -function _getDigiTrustQueryParams(userId) { - let digiTrustId = userId.digitrustid && userId.digitrustid.data; - // Verify there is an ID and this user has not opted out - if (!digiTrustId || (digiTrustId.privacy && digiTrustId.privacy.optout)) { - return {}; - } - return { - dt: digiTrustId.id - }; -} - /** * Serializes the supply chain object according to IAB standards * @see https://github.com/InteractiveAdvertisingBureau/openrtb/blob/master/supplychainobject.md @@ -264,7 +253,8 @@ function getEids(userId) { const idProperties = [ 'uid', 'eid', - 'lipbid' + 'lipbid', + 'envelope' ]; return Object.keys(userId).reduce(function (eids, provider) { @@ -293,7 +283,8 @@ function buildRequests(validBidRequests, bidderRequest) { const bids = []; const gdprConsent = bidderRequest && bidderRequest.gdprConsent; const uspConsent = bidderRequest && bidderRequest.uspConsent; - const timeout = config.getConfig('bidderTimeout'); + const gppConsent = bidderRequest && bidderRequest.gppConsent; + const timeout = bidderRequest && bidderRequest.timeout const coppa = config.getConfig('coppa') === true ? 1 : 0; const topWindowUrl = bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.page; _each(validBidRequests, bidRequest => { @@ -302,7 +293,6 @@ function buildRequests(validBidRequests, bidderRequest) { mediaTypes = {}, params = {}, schain, - transactionId, userId = {}, ortb2Imp, adUnitCode = '' @@ -321,6 +311,11 @@ function buildRequests(validBidRequests, bidderRequest) { data.lt = lt; data.to = to; + // ADJS-1286 Read id5 id linktype field + if (userId && userId.id5id && userId.id5id.uid && userId.id5id.ext) { + data.id5Id = userId.id5id.uid || null + data.id5IdLinkType = userId.id5id.ext.linkType || null + } // ADTS-169 add adUnitCode to requests if (adUnitCode) data.aun = adUnitCode; @@ -387,6 +382,17 @@ function buildRequests(validBidRequests, bidderRequest) { if (uspConsent) { data.uspConsent = uspConsent; } + if (gppConsent) { + data.gppConsent = { + gppString: bidderRequest.gppConsent.gppString, + gpp_sid: bidderRequest.gppConsent.applicableSections + } + } else if (!gppConsent && bidderRequest?.ortb2?.regs?.gpp) { + data.gppConsent = { + gppString: bidderRequest.ortb2.regs.gpp, + gpp_sid: bidderRequest.ortb2.regs.gpp_sid + }; + } if (coppa) { data.coppa = coppa; } @@ -397,13 +403,13 @@ function buildRequests(validBidRequests, bidderRequest) { bids.push({ id: bidId, tmax: timeout, - tId: transactionId, + tId: ortb2Imp?.ext?.tid, pi: data.pi, selector: params.selector, sizes, url: BID_ENDPOINT, method: 'GET', - data: Object.assign(data, _getBrowserParams(topWindowUrl), _getDigiTrustQueryParams(userId)) + data: Object.assign(data, _getBrowserParams(topWindowUrl)) }); }); return bids; @@ -583,6 +589,7 @@ function getUserSyncs(syncOptions, serverResponses) { export const spec = { code: BIDDER_CODE, + gvlid: 61, aliases: ALIAS_BIDDER_CODE, isBidRequestValid, buildRequests, diff --git a/modules/h12mediaBidAdapter.js b/modules/h12mediaBidAdapter.js index c58f15c9a81..7b1ba9ee286 100644 --- a/modules/h12mediaBidAdapter.js +++ b/modules/h12mediaBidAdapter.js @@ -41,7 +41,7 @@ export const spec = { const bidrequest = { bidId: bidRequest.bidId, - transactionId: bidRequest.transactionId, + transactionId: bidRequest.ortb2Imp?.ext?.tid, adunitId: bidRequest.adUnitCode, pubid: bidderParams.pubid, placementid: bidderParams.placementid || '', diff --git a/modules/hadronAnalyticsAdapter.js b/modules/hadronAnalyticsAdapter.js index 52829cf754d..e4c09c5b6c9 100644 --- a/modules/hadronAnalyticsAdapter.js +++ b/modules/hadronAnalyticsAdapter.js @@ -3,8 +3,9 @@ import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import * as utils from '../src/utils.js'; import CONSTANTS from '../src/constants.json'; -import { getStorageManager } from '../src/storageManager.js'; +import {getStorageManager} from '../src/storageManager.js'; import {getRefererInfo} from '../src/refererDetection.js'; +import {MODULE_TYPE_ANALYTICS} from '../src/activities/modules.js'; /** * hadronAnalyticsAdapter.js - Audigent Hadron Analytics Adapter @@ -14,8 +15,9 @@ const HADRON_ANALYTICS_URL = 'https://analytics.hadron.ad.gt/api/v1/analytics'; const HADRONID_ANALYTICS_VER = 'pbadgt0'; const DEFAULT_PARTNER_ID = 0; const AU_GVLID = 561; +const MODULE_CODE = 'hadronAnalytics'; -export const storage = getStorageManager(); +export const storage = getStorageManager({moduleType: MODULE_TYPE_ANALYTICS, moduleName: MODULE_CODE}); var viewId = utils.generateUUID(); @@ -191,7 +193,7 @@ function sendEvent(event) { adapterManager.registerAnalyticsAdapter({ adapter: hadronAnalyticsAdapter, - code: 'hadronAnalytics', + code: MODULE_CODE, gvlid: AU_GVLID }); diff --git a/modules/hadronIdSystem.js b/modules/hadronIdSystem.js index a75c03ee1c4..596bf9611e6 100644 --- a/modules/hadronIdSystem.js +++ b/modules/hadronIdSystem.js @@ -9,13 +9,14 @@ import {ajax} from '../src/ajax.js'; import {getStorageManager} from '../src/storageManager.js'; import {submodule} from '../src/hook.js'; import {isFn, isStr, isPlainObject, logError, logInfo} from '../src/utils.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; const HADRONID_LOCAL_NAME = 'auHadronId'; const MODULE_NAME = 'hadronId'; const AU_GVLID = 561; const DEFAULT_HADRON_URL_ENDPOINT = 'https://id.hadron.ad.gt/api/v1/pbhid'; -export const storage = getStorageManager({gvlid: AU_GVLID, moduleName: 'hadron'}); +export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: 'hadron'}); /** * Param or default. @@ -48,6 +49,7 @@ export const hadronIdSubmodule = { * @type {string} */ name: MODULE_NAME, + gvlid: AU_GVLID, /** * decode the stored id value for passing to bid requests * @function diff --git a/modules/hadronRtdProvider.js b/modules/hadronRtdProvider.js index 0bd4e6f8344..6fb982815c1 100644 --- a/modules/hadronRtdProvider.js +++ b/modules/hadronRtdProvider.js @@ -12,16 +12,17 @@ import {getStorageManager} from '../src/storageManager.js'; import {submodule} from '../src/hook.js'; import {isFn, isStr, isArray, deepEqual, isPlainObject, logError, logInfo} from '../src/utils.js'; import {loadExternalScript} from '../src/adloader.js'; +import {MODULE_TYPE_RTD} from '../src/activities/modules.js'; const LOG_PREFIX = 'User ID - HadronRtdProvider submodule: '; const MODULE_NAME = 'realTimeData'; const SUBMODULE_NAME = 'hadron'; const AU_GVLID = 561; const HADRON_ID_DEFAULT_URL = 'https://id.hadron.ad.gt/api/v1/hadronid?_it=prebid'; -const HADRON_SEGMENT_URL = 'https://seg.hadron.ad.gt/api/v1/rtd'; +const HADRON_SEGMENT_URL = 'https://id.hadron.ad.gt/api/v1/rtd'; export const HALOID_LOCAL_NAME = 'auHadronId'; export const RTD_LOCAL_NAME = 'auHadronRtd'; -export const storage = getStorageManager({gvlid: AU_GVLID, moduleName: SUBMODULE_NAME}); +export const storage = getStorageManager({moduleType: MODULE_TYPE_RTD, moduleName: SUBMODULE_NAME}); /** * @param {string} url @@ -251,7 +252,8 @@ function init(provider, userConsent) { export const hadronSubmodule = { name: SUBMODULE_NAME, getBidRequestData: getRealTimeData, - init: init + init: init, + gvlid: AU_GVLID, }; submodule(MODULE_NAME, hadronSubmodule); diff --git a/modules/holidBidAdapter.js b/modules/holidBidAdapter.js index 92b0a1c18db..2073063168d 100644 --- a/modules/holidBidAdapter.js +++ b/modules/holidBidAdapter.js @@ -1,21 +1,23 @@ import { deepAccess, + deepSetValue, getBidIdParameter, isStr, logMessage, triggerPixel, -} from '../src/utils.js' -import * as events from '../src/events.js' -import CONSTANTS from '../src/constants.json' -import { BANNER } from '../src/mediaTypes.js' +} from '../src/utils.js'; +import * as events from '../src/events.js'; +import CONSTANTS from '../src/constants.json'; +import {BANNER} from '../src/mediaTypes.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js' +import {registerBidder} from '../src/adapters/bidderFactory.js'; const BIDDER_CODE = 'holid' const GVLID = 1177 const ENDPOINT = 'https://helloworld.holid.io/openrtb2/auction' const COOKIE_SYNC_ENDPOINT = 'https://null.holid.io/sync.html' const TIME_TO_LIVE = 300 +const TMAX = 500 let wurlMap = {} events.on(CONSTANTS.EVENTS.BID_WON, bidWonHandler) @@ -29,12 +31,19 @@ export const spec = { return !!bid.params.adUnitID }, - buildRequests: function (validBidRequests, _bidderRequest) { + buildRequests: function (validBidRequests, bidderRequest) { return validBidRequests.map((bid) => { const requestData = { ...bid.ortb2, - id: bid.auctionId, + source: {schain: bid.schain}, + id: bidderRequest.bidderRequestId, imp: [getImp(bid)], + tmax: TMAX, + ...buildStoredRequest(bid) + } + + if (bid.userIdAsEids) { + deepSetValue(requestData, 'user.ext.eids', bid.userIdAsEids) } return { @@ -56,7 +65,6 @@ export const spec = { serverResponse.body.seatbid.map((response) => { response.bid.map((bid) => { const requestId = bidRequest.bidId - const auctionId = bidRequest.auctionId const wurl = deepAccess(bid, 'ext.prebid.events.win') const bidResponse = { requestId, @@ -70,7 +78,7 @@ export const spec = { ttl: TIME_TO_LIVE, } - addWurl({ auctionId, requestId, wurl }) + addWurl(requestId, wurl) bidResponses.push(bidResponse) }) @@ -80,11 +88,15 @@ export const spec = { }, getUserSyncs(optionsType, serverResponse, gdprConsent, uspConsent) { + const syncs = [{ + type: 'image', + url: 'https://track.adform.net/Serving/TrackPoint/?pm=2992097&lid=132720821' + }] + if (!serverResponse || serverResponse.length === 0) { - return [] + return syncs } - const syncs = [] const bidders = getBidders(serverResponse) if (optionsType.iframeEnabled && bidders) { @@ -105,24 +117,14 @@ export const spec = { type: 'iframe', url: COOKIE_SYNC_ENDPOINT + strQueryParams + '&type=iframe', }) - - return syncs } - return [] + return syncs }, } function getImp(bid) { - const imp = { - ext: { - prebid: { - storedrequest: { - id: getBidIdParameter('adUnitID', bid.params), - }, - }, - }, - } + const imp = buildStoredRequest(bid) const sizes = bid.sizes && !Array.isArray(bid.sizes[0]) ? [bid.sizes] : bid.sizes @@ -137,6 +139,18 @@ function getImp(bid) { return imp } +function buildStoredRequest(bid) { + return { + ext: { + prebid: { + storedrequest: { + id: getBidIdParameter('adUnitID', bid.params), + }, + }, + }, + } +} + function getBidders(serverResponse) { const bidders = serverResponse .map((res) => Object.keys(res.body.ext.responsetimemillis || [])) @@ -147,28 +161,28 @@ function getBidders(serverResponse) { } } -function addWurl(auctionId, adId, wurl) { - if ([auctionId, adId].every(isStr)) { - wurlMap[`${auctionId}${adId}`] = wurl +function addWurl(requestId, wurl) { + if (isStr(requestId)) { + wurlMap[requestId] = wurl } } -function removeWurl(auctionId, adId) { - delete wurlMap[`${auctionId}${adId}`] +function removeWurl(requestId) { + delete wurlMap[requestId] } -function getWurl(auctionId, adId) { - if ([auctionId, adId].every(isStr)) { - return wurlMap[`${auctionId}${adId}`] +function getWurl(requestId) { + if (isStr(requestId)) { + return wurlMap[requestId] } } function bidWonHandler(bid) { - const wurl = getWurl(bid.auctionId, bid.adId) + const wurl = getWurl(bid.requestId) if (wurl) { logMessage(`Invoking image pixel for wurl on BID_WIN: "${wurl}"`) triggerPixel(wurl) - removeWurl(bid.auctionId, bid.adId) + removeWurl(bid.requestId) } } diff --git a/modules/hybridBidAdapter.js b/modules/hybridBidAdapter.js index 6cedb094444..f746e69cbba 100644 --- a/modules/hybridBidAdapter.js +++ b/modules/hybridBidAdapter.js @@ -25,7 +25,7 @@ function buildBidRequests(validBidRequests) { const params = validBidRequest.params; const bidRequest = { bidId: validBidRequest.bidId, - transactionId: validBidRequest.transactionId, + transactionId: validBidRequest.ortb2Imp?.ext?.tid, sizes: validBidRequest.sizes, placement: placementTypes[params.placement], placeId: params.placeId, @@ -88,6 +88,7 @@ function buildBid(bidData) { bid.vastXml = bidData.content; bid.mediaType = VIDEO; + // TODO: why does this need to iterate through every ad unit? let adUnit = find(auctionManager.getAdUnits(), function (unit) { return unit.transactionId === bidData.transactionId; }); @@ -244,7 +245,6 @@ export const spec = { return item.bidId === bid.bidId; }); bid.placement = rawBid.placement; - bid.transactionId = rawBid.transactionId; bid.placeId = rawBid.placeId; return buildBid(bid); }); diff --git a/modules/hypelabBidAdapter.js b/modules/hypelabBidAdapter.js new file mode 100644 index 00000000000..a625c7299a6 --- /dev/null +++ b/modules/hypelabBidAdapter.js @@ -0,0 +1,153 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER } from '../src/mediaTypes.js'; +import { generateUUID } from '../src/utils.js'; +import { ajax } from '../src/ajax.js'; + +export const BIDDER_CODE = 'hypelab'; +export const ENDPOINT_URL = 'https://api.hypelab.com'; + +export const REQUEST_ROUTE = '/v1/prebid_requests'; +export const EVENT_ROUTE = '/v1/events'; +export const REPORTING_ROUTE = ''; + +const PREBID_VERSION = '$prebid.version$'; +const PROVIDER_NAME = 'prebid'; +const PROVIDER_VERSION = '0.0.1'; + +const url = (route) => ENDPOINT_URL + route; + +export function mediaSize(data) { + if (!data || !data.creative_set) return { width: 0, height: 0 }; + const media = data.creative_set.video || data.creative_set.image || {}; + return { width: media.width, height: media.height }; +} + +function isBidRequestValid(bidderRequest) { + return ( + !!bidderRequest.params?.property_slug && + !!bidderRequest.params?.placement_slug + ); +} + +function buildRequests(validBidRequests, bidderRequest) { + const result = validBidRequests.map((request) => { + const uids = (request.userIdAsEids || []).reduce((a, c) => { + const ids = c.uids.map((uid) => uid.id); + return [...a, ...ids]; + }, []); + + const uuid = uids[0] ? uids[0] : generateTemporaryUUID(); + + const payload = { + property_slug: request.params.property_slug, + placement_slug: request.params.placement_slug, + provider_version: PROVIDER_VERSION, + provider_name: PROVIDER_NAME, + referrer: + bidderRequest.refererInfo?.page || typeof window != 'undefined' + ? window.location.href + : '', + sdk_version: PREBID_VERSION, + sizes: request.sizes, + wids: [], + uuid, + bidRequestsCount: request.bidRequestsCount, + bidderRequestsCount: request.bidderRequestsCount, + bidderWinsCount: request.bidderWinsCount, + wp: { + ada: typeof window != 'undefined' && !!window.cardano, + bnb: typeof window != 'undefined' && !!window.BinanceChain, + eth: typeof window != 'undefined' && !!window.ethereum, + sol: typeof window != 'undefined' && !!window.solana, + tron: typeof window != 'undefined' && !!window.tron, + }, + }; + + return { + method: 'POST', + url: url(REQUEST_ROUTE), + options: { contentType: 'application/json', withCredentials: false }, + data: payload, + bidId: request.bidId, + }; + }); + + return result; +} + +function generateTemporaryUUID() { + return 'tmp_' + generateUUID(); +} + +function interpretResponse(serverResponse, bidRequest) { + const { data } = serverResponse.body; + + if (!data.cpm || !data.html) return []; + + const size = mediaSize(data); + + const result = { + requestId: bidRequest.bidId, + cpm: data.cpm, + width: size.width, + height: size.height, + creativeId: data.creative_set_slug, + currency: data.currency, + netRevenue: true, + referrer: bidRequest.data.referrer, + ttl: data.ttl, + ad: data.html, + mediaType: serverResponse.body.data.media_type, + meta: { + advertiserDomains: data.advertiserDomains || [], + }, + }; + + return [result]; +} + +export function report(eventType, data, route = REPORTING_ROUTE) { + if (!route) return; + + const options = { + method: 'POST', + contentType: 'application/json', + withCredentials: true, + }; + + const request = { type: eventType, data }; + ajax(url(route), null, request, options); +} + +function onTimeout(timeoutData) { + this.report('timeout', timeoutData); +} + +function onBidWon(bid) { + this.report('bidWon', bid); +} + +function onSetTargeting(bid) { + this.report('setTargeting', bid); +} + +function onBidderError(errorData) { + this.report('bidderError', errorData); +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + aliases: ['hype'], + isBidRequestValid, + buildRequests, + interpretResponse, + onTimeout, + onBidWon, + onSetTargeting, + onBidderError, + report, + REPORTING_ROUTE: 'a', +}; + +registerBidder(spec); diff --git a/modules/hypelabBidAdapter.md b/modules/hypelabBidAdapter.md new file mode 100644 index 00000000000..f33664da578 --- /dev/null +++ b/modules/hypelabBidAdapter.md @@ -0,0 +1,38 @@ +# Overview + +``` +Module Name: HypeLab Bid Adapter +Module Type: Bidder Adapter +Maintainer: sdk@hypelab.com +``` + +# Description + +Prebid.JS adapter that connects to HypeLab ad network for bids. +*NOTE*: The HypeLab Adapter requires setup and approval before use. Please reach out to `partnerships@hypelab.com` for more details. To get started, replace the `property_slug` with your property_slug and the `placement_slug` with your placement slug to receive bids. The placement slug will depend on the required size and can be set via the HypeLab interface. + +# Test Parameters + +## Sample Banner Ad Unit + +```js +var adUnits = [ + { + code: 'banner-div', + mediaTypes: { + banner: { + sizes: [[728, 90]], + }, + }, + bids: [ + { + bidder: 'hypelab', + params: { + property_slug: 'prebid', + placement_slug: 'test_placement' + } + } + ] + } +] +``` diff --git a/modules/id5AnalyticsAdapter.js b/modules/id5AnalyticsAdapter.js index d35cdf29fca..d0f3198e03d 100644 --- a/modules/id5AnalyticsAdapter.js +++ b/modules/id5AnalyticsAdapter.js @@ -4,6 +4,7 @@ import adapterManager from '../src/adapterManager.js'; import { ajax } from '../src/ajax.js'; import { logInfo, logError } from '../src/utils.js'; import * as events from '../src/events.js'; +import {getGlobal} from '../src/prebidGlobal.js'; const { EVENTS: { @@ -34,7 +35,7 @@ const FLUSH_EVENTS = [ const CONFIG_URL_PREFIX = 'https://api.id5-sync.com/analytics' const TZ = new Date().getTimezoneOffset(); -const PBJS_VERSION = $$PREBID_GLOBAL$$.version; +const PBJS_VERSION = getGlobal().version; const ID5_REDACTED = '__ID5_REDACTED__'; const isArray = Array.isArray; diff --git a/modules/id5IdSystem.js b/modules/id5IdSystem.js index a0745df37c8..bb8a21264d0 100644 --- a/modules/id5IdSystem.js +++ b/modules/id5IdSystem.js @@ -20,6 +20,7 @@ import {submodule} from '../src/hook.js'; import {getRefererInfo} from '../src/refererDetection.js'; import {getStorageManager} from '../src/storageManager.js'; import {uspDataHandler} from '../src/adapterManager.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; const MODULE_NAME = 'id5Id'; const GVLID = 131; @@ -34,7 +35,7 @@ const ID5_API_CONFIG_URL = 'https://id5-sync.com/api/config/prebid'; // cookie in the array is the most preferred to use const LEGACY_COOKIE_NAMES = ['pbjs-id5id', 'id5id.1st', 'id5id']; -export const storage = getStorageManager({gvlid: GVLID, moduleName: MODULE_NAME}); +export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); /** @type {Submodule} */ export const id5IdSubmodule = { @@ -59,11 +60,11 @@ export const id5IdSubmodule = { */ decode(value, config) { let universalUid; - let linkType = 0; + let ext = {}; if (value && typeof value.universal_uid === 'string') { universalUid = value.universal_uid; - linkType = value.link_type || linkType; + ext = value.ext || ext; } else { return undefined; } @@ -71,9 +72,7 @@ export const id5IdSubmodule = { let responseObj = { id5id: { uid: universalUid, - ext: { - linkType: linkType - } + ext: ext } }; @@ -113,6 +112,11 @@ export const id5IdSubmodule = { return undefined; } + if (!hasWriteConsentToLocalStorage(consentData)) { + logInfo(LOG_PREFIX + 'Skipping ID5 local storage write because no consent given.') + return undefined; + } + const resp = function (cbFunction) { new IdFetchFlow(submoduleConfig, consentData, cacheIdObj, uspDataHandler.getConsentData()).execute() .then(response => { @@ -140,6 +144,11 @@ export const id5IdSubmodule = { extendId(config, consentData, cacheIdObj) { hasRequiredConfig(config); + if (!hasWriteConsentToLocalStorage(consentData)) { + logInfo(LOG_PREFIX + 'No consent given for ID5 local storage writing, skipping nb increment.') + return cacheIdObj; + } + const partnerId = (config && config.params && config.params.partner) || 0; incrementNb(partnerId); @@ -253,7 +262,8 @@ class IdFetchFlow { 'top': referer.reachedTop ? 1 : 0, 'u': referer.stack[0] || window.location.href, 'v': '$prebid.version$', - 'storage': this.submoduleConfig.storage + 'storage': this.submoduleConfig.storage, + 'localStorage': storage.localStorageIsEnabled() ? 1 : 0 }; // pass in optional data, but only if populated @@ -376,4 +386,19 @@ export function storeInLocalStorage(key, value, expDays) { storage.setDataInLocalStorage(`${key}`, value); } +/** + * Check to see if we can write to local storage based on purpose consent 1, and that we have vendor consent (ID5=131) + * @param {ConsentData} consentData + * @returns {boolean} + */ +function hasWriteConsentToLocalStorage(consentData) { + const hasGdpr = consentData && typeof consentData.gdprApplies === 'boolean' && consentData.gdprApplies; + const localstorageConsent = deepAccess(consentData, `vendorData.purpose.consents.1`) + const id5VendorConsent = deepAccess(consentData, `vendorData.vendor.consents.${GVLID.toString()}`) + if (hasGdpr && (!localstorageConsent || !id5VendorConsent)) { + return false; + } + return true; +} + submodule('userId', id5IdSubmodule); diff --git a/modules/idWardRtdProvider.js b/modules/idWardRtdProvider.js index 9678739672d..29dda216fdc 100644 --- a/modules/idWardRtdProvider.js +++ b/modules/idWardRtdProvider.js @@ -8,11 +8,12 @@ import {getStorageManager} from '../src/storageManager.js'; import {submodule} from '../src/hook.js'; import {isPlainObject, mergeDeep, logMessage, logError} from '../src/utils.js'; +import {MODULE_TYPE_RTD} from '../src/activities/modules.js'; const MODULE_NAME = 'realTimeData'; const SUBMODULE_NAME = 'idWard'; -export const storage = getStorageManager({moduleName: SUBMODULE_NAME}); +export const storage = getStorageManager({moduleType: MODULE_TYPE_RTD, moduleName: SUBMODULE_NAME}); /** * Add real-time data & merge segments. * @param ortb2 object to merge into diff --git a/modules/identityLinkIdSystem.js b/modules/identityLinkIdSystem.js index df7b03b4e6e..ab10288f38f 100644 --- a/modules/identityLinkIdSystem.js +++ b/modules/identityLinkIdSystem.js @@ -9,8 +9,11 @@ import * as utils from '../src/utils.js' import { ajax } from '../src/ajax.js'; import { submodule } from '../src/hook.js'; import {getStorageManager} from '../src/storageManager.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; -export const storage = getStorageManager(); +const MODULE_NAME = 'identityLink'; + +export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); /** @type {Submodule} */ export const identityLinkSubmodule = { @@ -18,7 +21,7 @@ export const identityLinkSubmodule = { * used to link submodule with config * @type {string} */ - name: 'identityLink', + name: MODULE_NAME, /** * used to specify vendor id * @type {number} diff --git a/modules/idxIdSystem.js b/modules/idxIdSystem.js index 908edad4c04..3c00bbde34c 100644 --- a/modules/idxIdSystem.js +++ b/modules/idxIdSystem.js @@ -6,11 +6,12 @@ */ import { isStr, isPlainObject, logError } from '../src/utils.js'; import { submodule } from '../src/hook.js'; -import { getStorageManager } from '../src/storageManager.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; const IDX_MODULE_NAME = 'idx'; const IDX_COOKIE_NAME = '_idx'; -export const storage = getStorageManager(); +export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: IDX_MODULE_NAME}); function readIDxFromCookie() { return storage.cookiesAreEnabled ? storage.getCookie(IDX_COOKIE_NAME) : null; diff --git a/modules/imRtdProvider.js b/modules/imRtdProvider.js index bc01896d062..26d49c49f8c 100644 --- a/modules/imRtdProvider.js +++ b/modules/imRtdProvider.js @@ -18,16 +18,18 @@ import { isFn } from '../src/utils.js' import {submodule} from '../src/hook.js'; +import {MODULE_TYPE_RTD} from '../src/activities/modules.js'; export const imUidLocalName = '__im_uid'; export const imVidCookieName = '_im_vid'; export const imRtdLocalName = '__im_sids'; -export const storage = getStorageManager(); const submoduleName = 'im'; const segmentsMaxAge = 3600000; // 1 hour (30 * 60 * 1000) const uidMaxAge = 1800000; // 30 minites (30 * 60 * 1000) const vidMaxAge = 97200000000; // 37 months ((365 * 3 + 30) * 24 * 60 * 60 * 1000) +export const storage = getStorageManager({moduleType: MODULE_TYPE_RTD, moduleName: submoduleName}); + function setImDataInCookie(value) { storage.setCookie( imVidCookieName, diff --git a/modules/synacormediaBidAdapter.js b/modules/imdsBidAdapter.js similarity index 91% rename from modules/synacormediaBidAdapter.js rename to modules/imdsBidAdapter.js index 4ebed12cb05..545a0bd1ac3 100644 --- a/modules/synacormediaBidAdapter.js +++ b/modules/imdsBidAdapter.js @@ -16,7 +16,10 @@ const BLOCKED_AD_SIZES = [ ]; const DEFAULT_MAX_TTL = 420; // 7 minutes export const spec = { - code: 'synacormedia', + code: 'imds', + aliases: [ + { code: 'synacormedia' } + ], supportedMediaTypes: [ BANNER, VIDEO ], sizeMap: {}, @@ -36,7 +39,7 @@ export const spec = { } const refererInfo = bidderRequest.refererInfo; const openRtbBidRequest = { - id: bidderRequest.auctionId, + id: bidderRequest.bidderRequestId, site: { // TODO: does the fallback make sense here? domain: refererInfo.domain || location.hostname, @@ -49,9 +52,7 @@ export const spec = { imp: [] }; - const callbackTimeout = bidderRequest.timeout; - const globalTimeout = config.getConfig('bidderTimeout'); - const tmax = globalTimeout ? Math.min(globalTimeout, callbackTimeout) : callbackTimeout; + const tmax = bidderRequest.timeout; if (tmax) { openRtbBidRequest.tmax = tmax; } @@ -65,7 +66,7 @@ export const spec = { validBidReqs.forEach((bid, i) => { if (seatId && seatId !== bid.params.seatId) { - logWarn(`Synacormedia: there is an inconsistent seatId: ${bid.params.seatId} but only sending bid requests for ${seatId}, you should double check your configuration`); + logWarn(`IMDS: there is an inconsistent seatId: ${bid.params.seatId} but only sending bid requests for ${seatId}, you should double check your configuration`); return; } else { seatId = bid.params.seatId; @@ -73,7 +74,7 @@ export const spec = { const tagIdOrPlacementId = bid.params.tagId || bid.params.placementId; let pos = parseInt(bid.params.pos || deepAccess(bid.mediaTypes, 'video.pos'), 10); if (isNaN(pos)) { - logWarn(`Synacormedia: there is an invalid POS: ${bid.params.pos}`); + logWarn(`IMDS: there is an invalid POS: ${bid.params.pos}`); pos = 0; } const videoOrBannerKey = this.isVideoBid(bid) ? 'video' : 'banner'; @@ -151,7 +152,7 @@ export const spec = { }; const bidFloor = getBidFloor(bid, 'banner', '*'); if (isNaN(bidFloor)) { - logWarn(`Synacormedia: there is an invalid bid floor: ${bid.params.bidfloor}`); + logWarn(`IMDS: there is an invalid bid floor: ${bid.params.bidfloor}`); } if (bidFloor !== null && !isNaN(bidFloor)) { imp.bidfloor = bidFloor; @@ -175,7 +176,7 @@ export const spec = { }; const bidFloor = getBidFloor(bid, 'video', size); if (isNaN(bidFloor)) { - logWarn(`Synacormedia: there is an invalid bid floor: ${bid.params.bidfloor}`); + logWarn(`IMDS: there is an invalid bid floor: ${bid.params.bidfloor}`); } if (bidFloor !== null && !isNaN(bidFloor)) { @@ -213,7 +214,7 @@ export const spec = { }; if (!serverResponse.body || typeof serverResponse.body != 'object') { - logWarn('Synacormedia: server returned empty/non-json response: ' + JSON.stringify(serverResponse.body)); + logWarn('IMDS: server returned empty/non-json response: ' + JSON.stringify(serverResponse.body)); return; } const {id, seatbid: seatbids} = serverResponse.body; @@ -301,7 +302,7 @@ export const spec = { url: `${USER_SYNC_HOST}/html/usersync.html?src=$$REPO_AND_VERSION$$` }); } else { - logWarn('Synacormedia: Please enable iframe based user sync.'); + logWarn('IMDS: Please enable iframe based user sync.'); } return syncs; } diff --git a/modules/synacormediaBidAdapter.md b/modules/imdsBidAdapter.md similarity index 84% rename from modules/synacormediaBidAdapter.md rename to modules/imdsBidAdapter.md index 523c66fd1d9..15fb407e7ef 100644 --- a/modules/synacormediaBidAdapter.md +++ b/modules/imdsBidAdapter.md @@ -1,14 +1,14 @@ # Overview ``` -Module Name: Synacor Media Bidder Adapter +Module Name: iMedia Digital Services Bidder Adapter Module Type: Bidder Adapter -Maintainer: eng-demand@synacor.com +Maintainer: eng-demand@imds.tv ``` # Description -The Synacor Media adapter requires setup and approval from Synacor. +The iMedia Digital Services adapter requires setup and approval from iMedia Digital Services. Please reach out to your account manager for more information. ### DFP Video Creative @@ -30,7 +30,7 @@ https://track.technoratimedia.com/openrtb/tags?ID=%%PATTERN:hb_cache_id_synacorm } }, bids: [{ - bidder: "synacormedia", + bidder: "imds", params: { seatId: "prebid", tagId: "demo1", @@ -49,7 +49,7 @@ https://track.technoratimedia.com/openrtb/tags?ID=%%PATTERN:hb_cache_id_synacorm } }, bids: [{ - bidder: "synacormedia", + bidder: "imds", params: { seatId: "prebid", tagId: "demo1", diff --git a/modules/impactifyBidAdapter.js b/modules/impactifyBidAdapter.js index 04f34bdc7d9..f2bf9aaddcb 100644 --- a/modules/impactifyBidAdapter.js +++ b/modules/impactifyBidAdapter.js @@ -1,8 +1,7 @@ -import { deepAccess, deepSetValue, generateUUID } from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { config } from '../src/config.js'; -import { ajax } from '../src/ajax.js'; -import { createEidsArray } from './userId/eids.js'; +import {deepAccess, deepSetValue, generateUUID} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {config} from '../src/config.js'; +import {ajax} from '../src/ajax.js'; const BIDDER_CODE = 'impactify'; const BIDDER_ALIAS = ['imp']; @@ -42,11 +41,11 @@ const getFloor = (bid) => { const createOpenRtbRequest = (validBidRequests, bidderRequest) => { // Create request and set imp bids inside let request = { - id: bidderRequest.auctionId, + id: bidderRequest.bidderRequestId, validBidRequests, cur: [DEFAULT_CURRENCY], imp: [], - source: {tid: bidderRequest.auctionId} + source: {tid: bidderRequest.ortb2?.source?.tid} }; // Get the url parameters @@ -63,9 +62,8 @@ const createOpenRtbRequest = (validBidRequests, bidderRequest) => { if (schain) request.source.ext = { schain: schain }; // Set eids - let bidUserId = deepAccess(validBidRequests, '0.userId'); - let eids = createEidsArray(bidUserId); - if (eids.length) { + let eids = deepAccess(validBidRequests, '0.userIdAsEids'); + if (eids && eids.length) { deepSetValue(request, 'user.ext.eids', eids); } @@ -93,7 +91,6 @@ const createOpenRtbRequest = (validBidRequests, bidderRequest) => { if (bidderRequest.uspConsent) { deepSetValue(request, 'regs.ext.us_privacy', bidderRequest.uspConsent); - this.syncStore.uspConsent = bidderRequest.uspConsent; } if (GETCONFIG('coppa') == true) deepSetValue(request, 'regs.coppa', 1); diff --git a/modules/improvedigitalBidAdapter.js b/modules/improvedigitalBidAdapter.js index 94f50094a91..b56cc56a186 100644 --- a/modules/improvedigitalBidAdapter.js +++ b/modules/improvedigitalBidAdapter.js @@ -150,8 +150,7 @@ export const CONVERTER = ortbConverter({ mergeDeep(request, { id: getUniqueIdentifierStr(), source: { - // TODO: once https://github.com/prebid/Prebid.js/issues/8573 is resolved, this should be handled by the base ortbConverter logic - tid: context.bidRequests[0].transactionId, + }, ext: { improvedigital: { @@ -378,7 +377,7 @@ const ID_RAZR = { } }; - const cfgStr = JSON.stringify(cfg).replace(/<\/script>/g, '\\x3C/script>'); + const cfgStr = JSON.stringify(cfg).replace(/<\/script>/ig, '\\x3C/script>'); const s = ``; bid.ad = bid.ad.replace(/]*>/, match => match + s); diff --git a/modules/imuIdSystem.js b/modules/imuIdSystem.js index 41ff95b6702..898f32b27b0 100644 --- a/modules/imuIdSystem.js +++ b/modules/imuIdSystem.js @@ -8,9 +8,12 @@ import { timestamp, logError } from '../src/utils.js'; import { ajax } from '../src/ajax.js' import { submodule } from '../src/hook.js'; -import { getStorageManager } from '../src/storageManager.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; -export const storage = getStorageManager(); +const MODULE_NAME = 'imuid'; + +export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); export const storageKey = '__im_uid'; export const storagePpKey = '__im_ppid'; @@ -112,7 +115,7 @@ export const imuIdSubmodule = { * used to link submodule with config * @type {string} */ - name: 'imuid', + name: MODULE_NAME, /** * decode the stored id value for passing to bid requests * @function diff --git a/modules/inmarBidAdapter.js b/modules/inmarBidAdapter.js deleted file mode 100755 index 42bd64ee816..00000000000 --- a/modules/inmarBidAdapter.js +++ /dev/null @@ -1,132 +0,0 @@ -import {logError, mergeDeep} from '../src/utils.js'; -import { config } from '../src/config.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER, VIDEO } from '../src/mediaTypes.js'; - -const BIDDER_CODE = 'inmar'; - -export const spec = { - code: BIDDER_CODE, - aliases: ['inm'], - supportedMediaTypes: [BANNER, VIDEO], - - /** - * Determines whether or not the given bid request is valid - * - * @param {bidRequest} bid The bid params to validate. - * @returns {boolean} True if this is a valid bid, and false otherwise - */ - isBidRequestValid: function(bid) { - return !!(bid.params && bid.params.partnerId); - }, - - /** - * Build a server request from the list of valid BidRequests - * @param {validBidRequests} is an array of the valid bids - * @param {bidderRequest} bidder request object - * @returns {ServerRequest} Info describing the request to the server - */ - buildRequests: function(validBidRequests, bidderRequest) { - var payload = { - bidderCode: bidderRequest.bidderCode, - auctionId: bidderRequest.auctionId, - bidderRequestId: bidderRequest.bidderRequestId, - bidRequests: validBidRequests, - auctionStart: bidderRequest.auctionStart, - timeout: bidderRequest.timeout, - // TODO: please do not send internal data structures over the network - refererInfo: bidderRequest.refererInfo.legacy, - start: bidderRequest.start, - gdprConsent: bidderRequest.gdprConsent, - uspConsent: bidderRequest.uspConsent, - currencyCode: config.getConfig('currency.adServerCurrency'), - coppa: config.getConfig('coppa'), - firstPartyData: getLegacyFpd(bidderRequest.ortb2), - prebidVersion: '$prebid.version$' - }; - - var payloadString = JSON.stringify(payload); - - return { - method: 'POST', - url: 'https://prebid.owneriq.net:8443/bidder/pb/bid', - data: payloadString, - }; - }, - - /** - * Read the response from the server and build a list of bids - * @param {serverResponse} Response from the server. - * @param {bidRequest} Bid request object - * @returns {bidResponses} Array of bids which were nested inside the server - */ - interpretResponse: function(serverResponse, bidRequest) { - const bidResponses = []; - var response = serverResponse.body; - - try { - if (response) { - var bidResponse = { - requestId: response.requestId, - cpm: response.cpm, - currency: response.currency, - width: response.width, - height: response.height, - ad: response.ad, - ttl: response.ttl, - creativeId: response.creativeId, - netRevenue: response.netRevenue, - vastUrl: response.vastUrl, - dealId: response.dealId, - meta: response.meta - }; - - bidResponses.push(bidResponse); - } - } catch (error) { - logError('Error while parsing inmar response', error); - } - return bidResponses; - }, - - /** - * User Syncs - * - * @param {syncOptions} Publisher prebid configuration - * @param {serverResponses} Response from the server - * @returns {Array} - */ - getUserSyncs: function(syncOptions, serverResponses) { - const syncs = []; - if (syncOptions.pixelEnabled) { - syncs.push({ - type: 'image', - url: 'https://px.owneriq.net/eucm/p/pb' - }); - } - return syncs; - } -}; - -function getLegacyFpd(ortb2) { - if (typeof ortb2 !== 'object') return; - - let duplicate = {}; - - Object.keys(ortb2).forEach((type) => { - let prop = (type === 'site') ? 'context' : type; - duplicate[prop] = (prop === 'context' || prop === 'user') ? Object.keys(ortb2[type]).filter(key => key !== 'data').reduce((result, key) => { - if (key === 'ext') { - mergeDeep(result, ortb2[type][key]); - } else { - mergeDeep(result, {[key]: ortb2[type][key]}); - } - - return result; - }, {}) : ortb2[type]; - }); - - return duplicate; -} - -registerBidder(spec); diff --git a/modules/inmarBidAdapter.md b/modules/inmarBidAdapter.md deleted file mode 100644 index 8ed6b998602..00000000000 --- a/modules/inmarBidAdapter.md +++ /dev/null @@ -1,44 +0,0 @@ -# Overview - -``` -Module Name: Inmar Bidder Adapter -Module Type: Bidder Adapter -Maintainer: oiq_rtb@inmar.com -``` - -# Description - -Connects to Inmar for bids. This adapter supports Display and Video. - -The Inmar adapter requires setup and approval from the Inmar team. -Please reach out to your account manager for more information. - -# Test Parameters - -## Web -``` - var adUnits = [ - { - code: 'test-div1', - sizes: [[300, 250],[300, 600]], - bids: [{ - bidder: 'inmar', - params: { - partnerId: 12345, - position: 1 - } - }] - }, - { - code: 'test-div2', - sizes: [[728, 90],[970, 250]], - bids: [{ - bidder: 'inmar', - params: { - partnerId: 12345, - position: 0 - } - }] - } - ]; -``` diff --git a/modules/innityBidAdapter.js b/modules/innityBidAdapter.js index 71fe588441c..99eec210193 100644 --- a/modules/innityBidAdapter.js +++ b/modules/innityBidAdapter.js @@ -30,6 +30,7 @@ export const spec = { vph: window.screen.height, callback: 'json', callback_uid: bidRequest.bidId, + // TODO: fix auctionId leak: https://github.com/prebid/Prebid.js/issues/9781 auction: bidRequest.auctionId, }, }; diff --git a/modules/inskinBidAdapter.js b/modules/inskinBidAdapter.js deleted file mode 100644 index f3464765bde..00000000000 --- a/modules/inskinBidAdapter.js +++ /dev/null @@ -1,391 +0,0 @@ -import { createTrackPixelHtml } from '../src/utils.js'; -import { loadExternalScript } from '../src/adloader.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; - -const BIDDER_CODE = 'inskin'; - -const CONFIG = { - BASE_URI: 'https://mfad.inskinad.com/api/v2' -}; - -export const spec = { - code: BIDDER_CODE, - - /** - * Determines whether or not the given bid request is valid. - * - * @param {BidRequest} bid The bid params to validate. - * @return boolean True if this is a valid bid, and false otherwise. - */ - isBidRequestValid: function(bid) { - return !!(bid.params.networkId && bid.params.siteId); - }, - - /** - * Make a server request from the list of BidRequests. - * - * @param {validBidRequests[]} - an array of bids - * @param {bidderRequest} - the full bidder request object - * @return ServerRequest Info describing the request to the server. - */ - - buildRequests: function(validBidRequests, bidderRequest) { - // Do we need to group by bidder? i.e. to make multiple requests for - // different endpoints. - - let ret = { - method: 'POST', - url: '', - data: '', - bidRequest: [] - }; - - if (validBidRequests.length < 1) { - return ret; - } - - let ENDPOINT_URL; - - const data = Object.assign({ - placements: [], - time: Date.now(), - user: {}, - url: bidderRequest.refererInfo.page, - enableBotFiltering: true, - includePricingData: true, - parallel: true - }, validBidRequests[0].params); - - if (validBidRequests[0].schain) { - data.rtb = { - schain: validBidRequests[0].schain - }; - } else if (data.publisherId) { - data.rtb = { - schain: { - ext: { - sid: String(data.publisherId) - } - } - }; - } - - delete data.publisherId; - - data.keywords = data.keywords || []; - const restrictions = []; - - if (bidderRequest && bidderRequest.gdprConsent) { - data.consent = { - gdprVendorId: 150, - gdprConsentString: bidderRequest.gdprConsent.consentString, - // will check if the gdprApplies field was populated with a boolean value (ie from page config). If it's undefined, then default to true - gdprConsentRequired: (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') ? bidderRequest.gdprConsent.gdprApplies : true - }; - - const purposes = [ - {id: 1, kw: 'nocookies'}, - {id: 2, kw: 'nocontext'}, - {id: 3, kw: 'nodmp'}, - {id: 4, kw: 'nodata'}, - {id: 7, kw: 'noclicks'}, - {id: 9, kw: 'noresearch'} - ]; - - const d = bidderRequest.gdprConsent.vendorData; - - if (d) { - if (d.purposeOneTreatment) { - data.keywords.push('cst-nodisclosure'); - restrictions.push('nodisclosure'); - } - - purposes.map(p => { - if (!checkConsent(p.id, d)) { - data.keywords.push('cst-' + p.kw); - restrictions.push(p.kw); - } - }); - } - } - - validBidRequests.map(bid => { - ENDPOINT_URL = CONFIG.BASE_URI; - - const placement = Object.assign({ - divName: bid.bidId, - adTypes: bid.adTypes || getSize(bid.sizes), - eventIds: [40, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295] - }, bid.params); - - placement.adTypes.push(5, 9, 163, 2163, 3006); - - placement.properties = placement.properties || {}; - - placement.properties.screenWidth = screen.width; - placement.properties.screenHeight = screen.height; - - if (restrictions.length) { - placement.properties.restrictions = restrictions; - } - - if (placement.networkId && placement.siteId) { - data.placements.push(placement); - } - }); - - ret.data = JSON.stringify(data); - ret.bidRequest = validBidRequests; - ret.url = ENDPOINT_URL; - - return ret; - }, - - /** - * Unpack the response from the server into a list of bids. - * - * @param {*} serverResponse A successful response from the server. - * @return {Bid[]} An array of bids which were nested inside the server. - */ - interpretResponse: function(serverResponse, bidRequest) { - let bid; - let bids; - let bidId; - let bidObj; - let bidResponses = []; - let bidsMap = {}; - - bids = bidRequest.bidRequest; - - serverResponse = (serverResponse || {}).body; - for (let i = 0; i < bids.length; i++) { - bid = {}; - bidObj = bids[i]; - bidId = bidObj.bidId; - - bidsMap[bidId] = bidObj; - - if (serverResponse) { - const decision = serverResponse.decisions && serverResponse.decisions[bidId]; - const data = decision && decision.contents && decision.contents[0] && decision.contents[0].data; - const pubCPM = data && data.customData && data.customData.pubCPM; - const clearPrice = decision && decision.pricing && decision.pricing.clearPrice; - const price = pubCPM || clearPrice; - - if (decision && price) { - decision.impressionUrl += ('&property:pubcpm=' + price); - bidObj.price = price; - - bid.requestId = bidId; - bid.cpm = price; - bid.width = decision.width; - bid.height = decision.height; - bid.ad = retrieveAd(bidId, decision); - bid.currency = 'USD'; - bid.creativeId = decision.adId; - bid.ttl = 360; - bid.meta = { advertiserDomains: decision.adomain ? decision.adomain : [] }; - bid.netRevenue = true; - - bidResponses.push(bid); - } - } - } - - if (bidResponses.length) { - window.addEventListener('message', function(e) { - if (!e.data || e.data.from !== 'ism-bid') { - return; - } - - const decision = serverResponse.decisions && serverResponse.decisions[e.data.bidId]; - if (!decision) { - return; - } - - const id = 'ism_tag_' + Math.floor((Math.random() * 10e16)); - window[id] = { - plr_AdSlot: e.source && e.source.frameElement, - bidId: e.data.bidId, - bidPrice: bidsMap[e.data.bidId].price, - serverResponse - }; - - const url = 'https://cdn.inskinad.com/isfe/publishercode/' + bidsMap[e.data.bidId].params.siteId + '/default.js?autoload&id=' + id; - loadExternalScript(url, BIDDER_CODE); - }); - } - - return bidResponses; - }, - - getUserSyncs: function(syncOptions) { - const userSyncs = []; - - if (syncOptions.pixelEnabled) { - userSyncs.push({ - type: 'image', - url: 'https://e.serverbid.com/udb/9969/match?redir=https%3A%2F%2Fmfad.inskinad.com%2Fudb%2F9874%2Fpool%2Fset%2Fi.gif%3FpoolId%3D9969%26poolKey%3D' - }); - userSyncs.push({ - type: 'image', - url: 'https://ssum.casalemedia.com/usermatchredir?s=185638&cb=https%3A%2F%2Fmfad.inskinad.com%2Fudb%2F9874%2Fsync%2Fi.gif%3FpartnerId%3D1%26userId%3D' - }); - } - - if (syncOptions.iframeEnabled) { - userSyncs.push({ - type: 'iframe', - url: 'https://ssum-sec.casalemedia.com/usermatch?s=184665&cb=https%3A%2F%2Fmfad.inskinad.com%2Fudb%2F9874%2Fsync%2Fi.gif%3FpartnerId%3D1%26userId%3D' - }); - } - - return userSyncs; - } -}; - -const sizeMap = [ - null, - '120x90', - '120x90', - '468x60', - '728x90', - '300x250', - '160x600', - '120x600', - '300x100', - '180x150', - '336x280', - '240x400', - '234x60', - '88x31', - '120x60', - '120x240', - '125x125', - '220x250', - '250x250', - '250x90', - '0x0', - '200x90', - '300x50', - '320x50', - '320x480', - '185x185', - '620x45', - '300x125', - '800x250' -]; - -sizeMap[77] = '970x90'; -sizeMap[123] = '970x250'; -sizeMap[43] = '300x600'; - -function getSize(sizes) { - const result = []; - sizes.forEach(function(size) { - const index = sizeMap.indexOf(size[0] + 'x' + size[1]); - if (index >= 0) { - result.push(index); - } - }); - return result; -} - -function retrieveAd(bidId, decision) { - return "') }); @@ -1385,63 +1423,65 @@ describe('auctionmanager.js', function () { } describe('getMediaTypeGranularity', function () { - it('video', function () { - let mediaTypes = { video: {id: '1'} }; - - // mediaType is video and video.context is undefined - expect(getMediaTypeGranularity('video', mediaTypes, { - banner: 'low', - video: 'medium' - })).to.equal('medium'); - - expect(getMediaTypeGranularity('video', {}, { - banner: 'low', - video: 'medium' - })).to.equal('medium'); - `` - expect(getMediaTypeGranularity('video', undefined, { - banner: 'low', - video: 'medium' - })).to.equal('medium'); - - // also when mediaTypes.video is undefined - mediaTypes = { banner: {} }; - expect(getMediaTypeGranularity('video', mediaTypes, { - banner: 'low', - video: 'medium' - })).to.equal('medium'); - - // also when mediaTypes is undefined - expect(getMediaTypeGranularity('video', {}, { - banner: 'low', - video: 'medium' - })).to.equal('medium'); - }); + if (FEATURES.VIDEO) { + it('video', function () { + let mediaTypes = { video: {id: '1'} }; + + // mediaType is video and video.context is undefined + expect(getMediaTypeGranularity('video', mediaTypes, { + banner: 'low', + video: 'medium' + })).to.equal('medium'); + + expect(getMediaTypeGranularity('video', {}, { + banner: 'low', + video: 'medium' + })).to.equal('medium'); + `` + expect(getMediaTypeGranularity('video', undefined, { + banner: 'low', + video: 'medium' + })).to.equal('medium'); + + // also when mediaTypes.video is undefined + mediaTypes = { banner: {} }; + expect(getMediaTypeGranularity('video', mediaTypes, { + banner: 'low', + video: 'medium' + })).to.equal('medium'); + + // also when mediaTypes is undefined + expect(getMediaTypeGranularity('video', {}, { + banner: 'low', + video: 'medium' + })).to.equal('medium'); + }); - it('video-outstream', function () { - let mediaTypes = { video: { context: 'outstream' } }; + it('video-outstream', function () { + let mediaTypes = { video: { context: 'outstream' } }; - expect(getMediaTypeGranularity('video', mediaTypes, { - 'banner': 'low', 'video': 'medium', 'video-outstream': 'high' - })).to.equal('high'); - }); + expect(getMediaTypeGranularity('video', mediaTypes, { + 'banner': 'low', 'video': 'medium', 'video-outstream': 'high' + })).to.equal('high'); + }); - it('video-instream', function () { - let mediaTypes = { video: { context: 'instream' } }; + it('video-instream', function () { + let mediaTypes = { video: { context: 'instream' } }; - expect(getMediaTypeGranularity('video', mediaTypes, { - banner: 'low', video: 'medium', 'video-instream': 'high' - })).to.equal('high'); + expect(getMediaTypeGranularity('video', mediaTypes, { + banner: 'low', video: 'medium', 'video-instream': 'high' + })).to.equal('high'); - // fall back to video if video-instream not found - expect(getMediaTypeGranularity('video', mediaTypes, { - banner: 'low', video: 'medium' - })).to.equal('medium'); + // fall back to video if video-instream not found + expect(getMediaTypeGranularity('video', mediaTypes, { + banner: 'low', video: 'medium' + })).to.equal('medium'); - expect(getMediaTypeGranularity('video', {mediaTypes: {banner: {}}}, { - banner: 'low', video: 'medium' - })).to.equal('medium'); - }); + expect(getMediaTypeGranularity('video', {mediaTypes: {banner: {}}}, { + banner: 'low', video: 'medium' + })).to.equal('medium'); + }); + } it('native', function () { expect(getMediaTypeGranularity('native', {native: {}}, { @@ -1543,34 +1583,36 @@ describe('auctionmanager.js', function () { }); }) - it('should call auction done after prebid cache is complete for mediaType video', function() { - bids[0].mediaType = 'video'; - let bids1 = [mockBid({ bidderCode: BIDDER_CODE1 })]; + if (FEATURES.VIDEO) { + it('should call auction done after prebid cache is complete for mediaType video', function() { + bids[0].mediaType = 'video'; + let bids1 = [mockBid({ bidderCode: BIDDER_CODE1 })]; - let opts = { - mediaType: { - video: { - context: 'instream', - playerSize: [640, 480], - }, - } - }; - bidRequests = [ - mockBidRequest(bids[0], opts), - mockBidRequest(bids1[0], { adUnitCode: ADUNIT_CODE1 }), - ]; + let opts = { + mediaType: { + video: { + context: 'instream', + playerSize: [640, 480], + }, + } + }; + bidRequests = [ + mockBidRequest(bids[0], opts), + mockBidRequest(bids1[0], { adUnitCode: ADUNIT_CODE1 }), + ]; - let cbs = auctionCallbacks(doneSpy, auction); - cbs.addBidResponse.call(bidRequests[0], ADUNIT_CODE, bids[0]); - cbs.adapterDone.call(bidRequests[0]); - cbs.addBidResponse.call(bidRequests[1], ADUNIT_CODE1, bids1[0]); - cbs.adapterDone.call(bidRequests[1]); - assert.equal(doneSpy.callCount, 0); - const uuid = 'c488b101-af3e-4a99-b538-00423e5a3371'; - const responseBody = `{"responses":[{"uuid":"${uuid}"}]}`; - server.requests[0].respond(200, { 'Content-Type': 'application/json' }, responseBody); - assert.equal(doneSpy.callCount, 1); - }); + let cbs = auctionCallbacks(doneSpy, auction); + cbs.addBidResponse.call(bidRequests[0], ADUNIT_CODE, bids[0]); + cbs.adapterDone.call(bidRequests[0]); + cbs.addBidResponse.call(bidRequests[1], ADUNIT_CODE1, bids1[0]); + cbs.adapterDone.call(bidRequests[1]); + assert.equal(doneSpy.callCount, 0); + const uuid = 'c488b101-af3e-4a99-b538-00423e5a3371'; + const responseBody = `{"responses":[{"uuid":"${uuid}"}]}`; + server.requests[0].respond(200, { 'Content-Type': 'application/json' }, responseBody); + assert.equal(doneSpy.callCount, 1); + }); + } it('should convert cpm to number', () => { auction.addBidReceived = sinon.spy(); @@ -1746,28 +1788,6 @@ describe('auctionmanager.js', function () { }) }); - it('should return a NO_BID replacement', () => { - const noBid = cbs.addBidResponse.reject(AU_CODE, {...bid, statusMessage: 'Bid available', status: CONSTANTS.BID_STATUS.RENDERED}, 'Rejected'); - sinon.assert.match(noBid, { - status: CONSTANTS.BID_STATUS.BID_REJECTED, - statusMessage: 'Bid returned empty or error response', - cpm: 0, - requestId: bid.requestId, - auctionId: bid.auctionId, - adUnitCode: AU_CODE, - rejectionReason: undefined, - }); - }); - - it('should return NO_BID replacement when rejected bid is not a "proper" bid', () => { - const noBid = cbs.addBidResponse.reject(AU_CODE, {}); - sinon.assert.match(noBid, { - status: CONSTANTS.BID_STATUS.BID_REJECTED, - statusMessage: 'Bid returned empty or error response', - cpm: 0, - }); - }) - it('addBidResponse hooks should not be able to reject the same bid twice', () => { cbs.addBidResponse(AU_CODE, bid); expect(auction.addBidRejected.calledOnce).to.be.true; diff --git a/test/spec/fpd/enrichment_spec.js b/test/spec/fpd/enrichment_spec.js index 3363e1867d3..328846ca081 100644 --- a/test/spec/fpd/enrichment_spec.js +++ b/test/spec/fpd/enrichment_spec.js @@ -2,6 +2,8 @@ import {dep, enrichFPD} from '../../../src/fpd/enrichment.js'; import {hook} from '../../../src/hook.js'; import {expect} from 'chai/index.mjs'; import {config} from 'src/config.js'; +import * as utils from 'src/utils.js'; +import {CLIENT_SECTIONS} from '../../../src/fpd/oneClient.js'; describe('FPD enrichment', () => { let sandbox; @@ -48,50 +50,86 @@ describe('FPD enrichment', () => { }); } - describe('site', () => { - it('sets page, ref, domain, and publisher.domain', () => { - const refererInfo = { - page: 'www.example.com', - ref: 'referrer.com' - }; - sandbox.stub(dep, 'getRefererInfo').callsFake(() => refererInfo); - sandbox.stub(dep, 'findRootDomain').callsFake((dom) => `publisher.${dom}`); - return fpd().then(ortb2 => { - sinon.assert.match(ortb2.site, { + CLIENT_SECTIONS.forEach(section => { + describe(`${section}, when set`, () => { + const ORTB2 = {[section]: {ext: {}}} + + it('sets domain and publisher.domain', () => { + const refererInfo = { page: 'www.example.com', - domain: 'example.com', - ref: 'referrer.com', - publisher: { - domain: 'publisher.example.com' - } + }; + sandbox.stub(dep, 'getRefererInfo').callsFake(() => refererInfo); + sandbox.stub(dep, 'findRootDomain').callsFake((dom) => `publisher.${dom}`); + return fpd(ORTB2).then(ortb2 => { + sinon.assert.match(ortb2[section], { + domain: 'example.com', + publisher: { + domain: 'publisher.example.com' + } + }); + }); + }) + + describe('keywords', () => { + let metaTag; + beforeEach(() => { + metaTag = document.createElement('meta'); + metaTag.name = 'keywords'; + metaTag.content = 'kw1, kw2'; + document.head.appendChild(metaTag); + }); + afterEach(() => { + document.head.removeChild(metaTag); + }); + + testWindows(() => window, () => { + it(`sets kewwords from meta tag`, () => { + return fpd(ORTB2).then(ortb2 => { + expect(ortb2[section].keywords).to.eql('kw1,kw2'); + }); + }); }); }); - }); - describe('keywords', () => { - let metaTag; + it('should not set keywords if meta tag is not present', () => { + return fpd(ORTB2).then(ortb2 => { + expect(ortb2[section].hasOwnProperty('keywords')).to.be.false; + }); + }); + }) + }) + + describe('site', () => { + describe('when mixed with app/dooh', () => { beforeEach(() => { - metaTag = document.createElement('meta'); - metaTag.name = 'keywords'; - metaTag.content = 'kw1, kw2'; - document.head.appendChild(metaTag); + sinon.stub(utils, 'logWarn'); }); + afterEach(() => { - document.head.removeChild(metaTag); + utils.logWarn.restore(); }); - testWindows(() => window, () => { - it(`sets kewwords from meta tag`, () => { - return fpd().then(ortb2 => { - expect(ortb2.site.keywords).to.eql('kw1,kw2'); - }); - }); - }); - }); + ['dooh', 'app'].forEach(prop => { + it(`should not be set when ${prop} is set`, () => { + return fpd({[prop]: {foo: 'bar'}}).then(ortb2 => { + expect(ortb2.site).to.not.exist; + sinon.assert.notCalled(utils.logWarn); // make sure we don't generate "both site and app are set" warnings + }) + }) + }) + }) - it('should not set keywords if meta tag is not present', () => { + it('sets page, ref', () => { + const refererInfo = { + page: 'www.example.com', + ref: 'referrer.com' + }; + sandbox.stub(dep, 'getRefererInfo').callsFake(() => refererInfo); return fpd().then(ortb2 => { - expect(ortb2.site.hasOwnProperty('keywords')).to.be.false; + sinon.assert.match(ortb2.site, { + page: 'www.example.com', + ref: 'referrer.com', + }); }); }); @@ -106,6 +144,26 @@ describe('FPD enrichment', () => { expect(ortb2.site.publisher.domain).to.eql('pub.com'); }); }); + + it('respects config set through setConfig({site})', () => { + sandbox.stub(dep, 'getRefererInfo').callsFake(() => ({ + page: 'www.example.com', + ref: 'referrer.com', + })); + config.setConfig({ + site: { + ref: 'override.com', + priority: 'lower' + } + }); + return fpd({site: {priority: 'highest'}}).then(ortb2 => { + sinon.assert.match(ortb2.site, { + page: 'www.example.com', + ref: 'override.com', + priority: 'highest' + }) + }) + }) }); describe('device', () => { @@ -138,9 +196,44 @@ describe('FPD enrichment', () => { expect(ortb2.device.language).to.eql('lang'); }) }); + + it('respects setConfig({device})', () => { + win.navigator.userAgent = 'ua'; + win.navigator.language = 'lang'; + config.setConfig({ + device: { + language: 'override', + priority: 'lower' + } + }); + return fpd({device: {priority: 'highest'}}).then(ortb2 => { + sinon.assert.match(ortb2.device, { + language: 'override', + priority: 'highest', + ua: 'ua' + }) + }) + }) }); }); + describe('app', () => { + it('respects setConfig({app})', () => { + config.setConfig({ + app: { + priority: 'lower', + prop: 'value' + } + }); + return fpd({app: {priority: 'highest'}}).then(ortb2 => { + sinon.assert.match(ortb2.app, { + priority: 'highest', + prop: 'value' + }) + }) + }) + }) + describe('regs', () => { describe('gpc', () => { let win; @@ -210,4 +303,18 @@ describe('FPD enrichment', () => { }) }); }); + + it('leaves only one of app, site, dooh', () => { + return fpd({ + app: {p: 'val'}, + site: {p: 'val'}, + dooh: {p: 'val'} + }).then(ortb2 => { + expect(ortb2.app).to.not.exist; + expect(ortb2.site).to.not.exist; + sinon.assert.match(ortb2.dooh, { + p: 'val' + }) + }); + }) }); diff --git a/test/spec/ortbConverter/default_processors_spec.js b/test/spec/fpd/oneClient.js similarity index 79% rename from test/spec/ortbConverter/default_processors_spec.js rename to test/spec/fpd/oneClient.js index 48204b2c861..4ecde8d8a38 100644 --- a/test/spec/ortbConverter/default_processors_spec.js +++ b/test/spec/fpd/oneClient.js @@ -1,6 +1,7 @@ -import {onlyOneClientSection} from '../../../libraries/ortbConverter/processors/default.js'; +import {clientSectionChecker} from '../../../src/fpd/oneClient.js'; describe('onlyOneClientSection', () => { + const oneClient = clientSectionChecker(); [ [['app'], 'app'], [['site'], 'site'], @@ -11,13 +12,13 @@ describe('onlyOneClientSection', () => { ].forEach(([sections, winner]) => { it(`should leave only ${winner} in request when it contains ${sections.join(', ')}`, () => { const req = Object.fromEntries(sections.map(s => [s, {foo: 'bar'}])); - onlyOneClientSection(req); + oneClient(req); expect(Object.keys(req)).to.eql([winner]); }) }); it('should not choke if none of the sections are in the request', () => { const req = {}; - onlyOneClientSection(req); + oneClient(req); expect(req).to.eql({}); }); }); diff --git a/test/spec/fpd/sua_spec.js b/test/spec/fpd/sua_spec.js index 121922fa78d..431f47268d3 100644 --- a/test/spec/fpd/sua_spec.js +++ b/test/spec/fpd/sua_spec.js @@ -6,7 +6,7 @@ import { SUA_SOURCE_UNKNOWN, suaFromUAData, uaDataToSUA -} from '../../../libraries/fpd/sua.js'; +} from '../../../src/fpd/sua.js'; describe('uaDataToSUA', () => { Object.entries({ diff --git a/test/spec/keywords_spec.js b/test/spec/keywords_spec.js new file mode 100644 index 00000000000..e048ead3b98 --- /dev/null +++ b/test/spec/keywords_spec.js @@ -0,0 +1,102 @@ +import {getAllOrtbKeywords, mergeKeywords} from '../../libraries/keywords/keywords.js'; + +describe('mergeKeywords', () => { + Object.entries({ + 'single list': { + input: [ + 'one, two' + ], + output: [ + 'one', + 'two' + ] + }, + 'multiple lists': { + input: [ + 'one, two', + 'two, three' + ], + output: [ + 'one', + 'two', + 'three' + ] + }, + 'null lists': { + input: [ + undefined, + 'one, two', + null, + 'three' + ], + output: [ + 'one', + 'two', + 'three' + ] + }, + 'empty keywords': { + input: [ + 'one,,two' + ], + output: [ + 'one', + 'two' + ] + }, + 'extra whitespace': { + input: [ + ' one, two , three ' + ], + output: [ + 'one', + 'two', + 'three' + ] + }, + 'mixed with arrays': { + input: [ + ['one'], + 'one, two', + ['three', 'two'] + ], + output: [ + 'one', + 'two', + 'three' + ] + } + }).forEach(([t, {input, output}]) => { + it(`can merge ${t}`, () => { + expect(mergeKeywords(...input)).to.have.members(output); + }) + }) +}); + +describe('getAllOrtbKeywodrs', () => { + const SAMPLE_ORTB = { + app: { + keywords: 'one, two' + }, + site: { + content: { + keywords: 'one, three' + } + }, + user: { + keywords: 'four' + } + } + + it('can extract keywords from ortb', () => { + expect(getAllOrtbKeywords(SAMPLE_ORTB)).to.have.members([ + 'one', 'two', 'three', 'four' + ]); + }); + + it('merges with extra comma-separated keywords', () => { + expect(getAllOrtbKeywords(SAMPLE_ORTB, 'two,five')).to.have.members([ + 'one', 'two', 'three', 'four', 'five' + ]) + }) +}) diff --git a/test/spec/libraries/cmp/cmpClient_spec.js b/test/spec/libraries/cmp/cmpClient_spec.js new file mode 100644 index 00000000000..56dd8e12605 --- /dev/null +++ b/test/spec/libraries/cmp/cmpClient_spec.js @@ -0,0 +1,233 @@ +import {cmpClient} from '../../../../libraries/cmp/cmpClient.js'; + +describe('cmpClient', () => { + function mockWindow(props = {}) { + let listeners = []; + const win = { + addEventListener: sinon.stub().callsFake((evt, listener) => { + evt === 'message' && listeners.push(listener) + }), + postMessage: sinon.stub().callsFake((msg) => { + listeners.forEach(ln => ln({data: msg})) + }), + ...props, + }; + win.top = win.parent?.top || win; + return win; + } + + it('should return undefined when there is no CMP', () => { + expect(cmpClient({apiName: 'missing'}, mockWindow())).to.not.exist; + }); + + it('should return undefined when parent is inaccessible', () => { + const win = mockWindow(); + win.top = mockWindow(); + expect(cmpClient({apiName: 'missing'}, win)).to.not.exist; + }) + + describe('direct access', () => { + let mockApiFn; + beforeEach(() => { + mockApiFn = sinon.stub(); + }) + Object.entries({ + 'on same frame': () => mockWindow({mockApiFn}), + 'on parent frame': () => mockWindow({parent: mockWindow({parent: mockWindow({parent: mockWindow(), mockApiFn})})}), + }).forEach(([t, mkWindow]) => { + describe(t, () => { + let win, mkClient; + beforeEach(() => { + win = mkWindow(); + mkClient = (opts) => cmpClient(Object.assign({apiName: 'mockApiFn'}, opts), win) + }); + + it('should mark client function as direct', () => { + expect(mkClient().isDirect).to.equal(true); + }); + + it('should find and call the CMP api function', () => { + mkClient()({command: 'mockCmd'}); + sinon.assert.calledWith(mockApiFn, 'mockCmd'); + }); + + describe('should return a promise that', () => { + let cbResult; + beforeEach(() => { + cbResult = []; + mockApiFn.callsFake((cmd, callback) => { + if (typeof callback === 'function') { + callback.apply(this, cbResult); + } + return 'val' + }) + }) + Object.entries({ + callback: [sinon.stub(), 'undefined', undefined], + 'no callback': [undefined, 'api return value', 'val'] + }).forEach(([t, [callback, tResult, expectedResult]]) => { + describe(`when ${t} is provided`, () => { + Object.entries({ + 'no success flag': undefined, + 'success is set': true + }).forEach(([t, success]) => { + it(`resolves to ${tResult} (${t})`, (done) => { + cbResult = ['cbVal', success]; + mkClient()({callback}).then((val) => { + expect(val).to.equal(expectedResult); + done(); + }) + }) + }); + }) + }); + + it('rejects to undefined when callback is provided and success = false', () => { + cbResult = ['cbVal', false]; + mkClient()({callback: sinon.stub()}).catch(val => { + expect(val).to.equal('cbVal'); + done(); + }) + }); + + it('rejects when CMP api throws', (done) => { + mockApiFn.reset(); + const e = new Error(); + mockApiFn.throws(e); + mkClient()({}).catch(val => { + expect(val).to.equal(e); + done(); + }); + }) + }) + + it('should use apiArgs to choose and order the arguments to pass to the API fn', () => { + mkClient({apiArgs: ['parameter', 'command']})({ + command: 'mockCmd', + parameter: 'mockParam', + callback() {} + }); + sinon.assert.calledWith(mockApiFn, 'mockParam', 'mockCmd'); + }); + }) + }) + }) + + describe('postMessage access', () => { + let messenger, win, response; + beforeEach(() => { + response = {}; + messenger = sinon.stub().callsFake((msg) => { + if (msg.mockApiCall) { + win.postMessage({mockApiReturn: {callId: msg.mockApiCall.callId, ...response}}); + } + }); + }); + + function mkClient(options) { + return cmpClient(Object.assign({apiName: 'mockApi'}, options), win); + } + + Object.entries({ + 'on same frame': () => { + win = mockWindow({frames: {mockApiLocator: true}}); + win.addEventListener('message', (evt) => messenger(evt.data)); + }, + 'on parent frame': () => { + win = mockWindow({parent: mockWindow({frames: {mockApiLocator: true}})}) + win.parent.addEventListener('message', evt => messenger(evt.data)) + } + }).forEach(([t, setup]) => { + describe(t, () => { + beforeEach(setup); + + it('should mark client as not direct', () => { + expect(mkClient().isDirect).to.equal(false); + }); + + it('should find and message the CMP frame', () => { + mkClient()({command: 'mockCmd', parameter: 'param'}); + sinon.assert.calledWithMatch(messenger, { + mockApiCall: { + command: 'mockCmd', + parameter: 'param' + } + }) + }); + + it('should use apiArgs to choose what to include in the message payload', () => { + mkClient({apiArgs: ['command']})({ + command: 'cmd', + parameter: 'param' + }); + sinon.assert.calledWithMatch(messenger, sinon.match((arg) => { + return arg.mockApiCall.command === 'cmd' && + !arg.mockApiCall.hasOwnProperty('parameter'); + })) + }); + + it('should not include callback in the payload, but still run it on response', () => { + const cb = sinon.stub(); + mkClient({apiArgs: ['command', 'callback']})({ + command: 'cmd', + callback: cb + }); + sinon.assert.calledWithMatch(messenger, sinon.match(arg => !arg.mockApiCall.hasOwnProperty('callback'))); + sinon.assert.called(cb); + }); + + it('should use callbackArgs to decide what to pass to callback', () => { + const cb = sinon.stub(); + response = {a: 'one', b: 'two'}; + mkClient({callbackArgs: ['a', 'b']})({callback: cb}); + sinon.assert.calledWith(cb, 'one', 'two'); + }) + + describe('should return a promise that', () => { + beforeEach(() => { + response = {returnValue: 'val'} + }) + Object.entries({ + 'callback': [sinon.stub(), 'undefined', undefined], + 'no callback': [undefined, 'response returnValue', 'val'], + }).forEach(([t, [callback, tResult, expectedResult]]) => { + describe(`when ${t} is provided`, () => { + Object.entries({ + 'no success flag': {}, + 'with success flag': {success: true} + }).forEach(([t, resp]) => { + it(`resolves to ${tResult} (${t})`, () => { + Object.assign(response, resp); + mkClient()({callback}).then((val) => { + expect(val).to.equal(expectedResult); + }) + }) + }); + + it(`rejects to ${tResult} when success = false`, (done) => { + response.success = false; + mkClient()({callback}).catch((err) => { + expect(err).to.equal(expectedResult); + done(); + }); + }); + }) + }); + }); + + it('should re-use callback for messages with same callId', () => { + messenger.reset(); + let callId; + messenger.callsFake((msg) => { if (msg.mockApiCall) callId = msg.mockApiCall.callId }); + const callback = sinon.stub(); + mkClient()({callback}); + expect(callId).to.exist; + win.postMessage({mockApiReturn: {callId, returnValue: 'a'}}); + win.postMessage({mockApiReturn: {callId, returnValue: 'b'}}); + sinon.assert.calledWith(callback, 'a'); + sinon.assert.calledWith(callback, 'b'); + }) + }); + }); + }); +}); diff --git a/test/spec/libraries/domainOverrideToRootDomain/index_spec.js b/test/spec/libraries/domainOverrideToRootDomain/index_spec.js new file mode 100644 index 00000000000..b490d80fd40 --- /dev/null +++ b/test/spec/libraries/domainOverrideToRootDomain/index_spec.js @@ -0,0 +1,77 @@ +import {domainOverrideToRootDomain} from 'libraries/domainOverrideToRootDomain/index.js'; +import {getStorageManager} from 'src/storageManager.js'; +import {MODULE_TYPE_UID} from '../../../../src/activities/modules'; + +const storage = getStorageManager({ moduleName: 'test', moduleType: MODULE_TYPE_UID }); +const domainOverride = domainOverrideToRootDomain(storage, 'test'); + +describe('domainOverride', () => { + let sandbox, domain, cookies, rejectCookiesFor; + let setCookieStub; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + sandbox.stub(document, 'domain').get(() => domain); + cookies = {}; + sandbox.stub(storage, 'getCookie').callsFake((key) => cookies[key]); + rejectCookiesFor = null; + setCookieStub = sandbox.stub(storage, 'setCookie').callsFake((key, value, expires, sameSite, domain) => { + if (domain !== rejectCookiesFor) { + if (expires != null) { + expires = new Date(expires); + } + if (expires == null || expires > Date.now()) { + cookies[key] = value; + } else { + delete cookies[key]; + } + } + }); + }); + + afterEach(() => sandbox.restore()) + + it('test cookies include the module name', () => { + domain = 'greatpublisher.com' + rejectCookiesFor = 'greatpublisher.com' + + // stub Date.now() to return a constant value + sandbox.stub(Date, 'now').returns(1234567890) + + const randomName = `adapterV${(Math.random() * 1e8).toString(16)}` + const localDomainOverride = domainOverrideToRootDomain(storage, randomName) + + const time = Date.now(); + localDomainOverride(); + + sandbox.assert.callCount(setCookieStub, 2) + sandbox.assert.calledWith(setCookieStub, `_gd${time}_${randomName}`, '1', undefined, undefined, 'greatpublisher.com') + }); + + it('will return the root domain when given a subdomain', () => { + const test_domains = [ + 'deeply.nested.subdomain.for.greatpublisher.com', + 'greatpublisher.com', + 'subdomain.greatpublisher.com', + 'a-subdomain.greatpublisher.com', + ]; + + test_domains.forEach((testDomain) => { + domain = testDomain + rejectCookiesFor = 'com' + expect(domainOverride()).to.equal('greatpublisher.com'); + }); + }); + + it(`If we can't set cookies on the root domain, we'll return the subdomain`, () => { + domain = 'subdomain.greatpublisher.com' + rejectCookiesFor = 'greatpublisher.com' + expect(domainOverride()).to.equal('subdomain.greatpublisher.com'); + }); + + it('Will return undefined if we can\'t set cookies on the root domain or the subdomain', () => { + domain = 'subdomain.greatpublisher.com' + rejectCookiesFor = 'subdomain.greatpublisher.com' + expect(domainOverride()).to.equal(undefined); + }); +}); diff --git a/test/spec/libraries/mspa/activityControls_spec.js b/test/spec/libraries/mspa/activityControls_spec.js new file mode 100644 index 00000000000..5286f1d47f0 --- /dev/null +++ b/test/spec/libraries/mspa/activityControls_spec.js @@ -0,0 +1,315 @@ +import {mspaRule, setupRules, isTransmitUfpdConsentDenied, isTransmitGeoConsentDenied, isBasicConsentDenied, isSensitiveNoticeMissing, isConsentDenied} from '../../../../libraries/mspa/activityControls.js'; +import {ruleRegistry} from '../../../../src/activities/rules.js'; +describe('isBasicConsentDenied', () => { + const cd = { + // not covered, opt in to targeted, sale, and share, all notices given, opt into precise geo + Gpc: 0, + KnownChildSensitiveDataConsents: [0, 0], + MspaCoveredTransaction: 2, + MspaOptOutOptionMode: 0, + MspaServiceProviderMode: 0, + PersonalDataConsents: 0, + SaleOptOut: 2, + SaleOptOutNotice: 1, + SensitiveDataLimitUseNotice: 1, + SensitiveDataProcessing: [0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0], + SensitiveDataProcessingOptOutNotice: 1, + SharingNotice: 1, + SharingOptOut: 2, + SharingOptOutNotice: 1, + TargetedAdvertisingOptOut: 2, + TargetedAdvertisingOptOutNotice: 1, + Version: 1 + }; + it('should be false (basic consent conditions pass) with variety of notice and opt in', () => { + const result = isBasicConsentDenied(cd); + expect(result).to.equal(false); + }); + it('should be true (basic consent conditions do not pass) with personal data consent set to true (invalid state)', () => { + cd.PersonalDataConsents = 2; + const result = isBasicConsentDenied(cd); + expect(result).to.equal(true); + cd.PersonalDataConsents = 0; + }); + it('should be true (basic consent conditions do not pass) with sensitive opt in but no notice', () => { + cd.SensitiveDataLimitUseNotice = 0; + const result = isBasicConsentDenied(cd); + expect(result).to.equal(true); + cd.SensitiveDataLimitUseNotice = 1; + }); +}) +describe('isSensitiveNoticeMissing', () => { + const cd = { + // not covered, opt in to targeted, sale, and share, all notices given, opt into precise geo + Gpc: 0, + KnownChildSensitiveDataConsents: [0, 0], + MspaCoveredTransaction: 2, + MspaOptOutOptionMode: 0, + MspaServiceProviderMode: 0, + PersonalDataConsents: 0, + SaleOptOut: 2, + SaleOptOutNotice: 1, + SensitiveDataLimitUseNotice: 1, + SensitiveDataProcessing: [0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0], + SensitiveDataProcessingOptOutNotice: 1, + SharingNotice: 1, + SharingOptOut: 2, + SharingOptOutNotice: 1, + TargetedAdvertisingOptOut: 2, + TargetedAdvertisingOptOutNotice: 1, + Version: 1 + }; + it('should be false (sensitive notice is given or not needed) with variety of notice and opt in', () => { + const result = isSensitiveNoticeMissing(cd); + expect(result).to.equal(false); + }); + it('should be true (sensitive notice is missing) with variety of notice and opt in', () => { + cd.SensitiveDataLimitUseNotice = 2; + const result = isSensitiveNoticeMissing(cd); + expect(result).to.equal(true); + cd.SensitiveDataLimitUseNotice = 1; + }); +}) +describe('isConsentDenied', () => { + const cd = { + // not covered, opt in to targeted, sale, and share, all notices given, opt into precise geo + Gpc: 0, + KnownChildSensitiveDataConsents: [0, 0], + MspaCoveredTransaction: 2, + MspaOptOutOptionMode: 0, + MspaServiceProviderMode: 0, + PersonalDataConsents: 0, + SaleOptOut: 2, + SaleOptOutNotice: 1, + SensitiveDataLimitUseNotice: 1, + SensitiveDataProcessing: [0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0], + SensitiveDataProcessingOptOutNotice: 1, + SharingNotice: 1, + SharingOptOut: 2, + SharingOptOutNotice: 1, + TargetedAdvertisingOptOut: 2, + TargetedAdvertisingOptOutNotice: 1, + Version: 1 + }; + it('should be false (consent given personalized ads / sale / share) with variety of notice and opt in', () => { + const result = isConsentDenied(cd); + expect(result).to.equal(false); + }); + it('should be true (no consent) on opt out of targeted ads via TargetedAdvertisingOptOut', () => { + cd.TargetedAdvertisingOptOut = 1; + const result = isConsentDenied(cd); + expect(result).to.equal(true); + cd.TargetedAdvertisingOptOut = 2; + }); + it('should be true (no consent) on opt out of targeted ads via no TargetedAdvertisingOptOutNotice', () => { + cd.TargetedAdvertisingOptOutNotice = 2; + const result = isConsentDenied(cd); + expect(result).to.equal(true); + cd.TargetedAdvertisingOptOutNotice = 1; + }); + it('should be true (no consent) if TargetedAdvertisingOptOutNotice is 0 and TargetedAdvertisingOptOut is 2', () => { + cd.TargetedAdvertisingOptOutNotice = 0; + const result = isConsentDenied(cd); + expect(result).to.equal(true); + cd.TargetedAdvertisingOptOutNotice = 1; + }); +}) +describe('isTransmitUfpdConsentDenied', () => { + const cd = { + // not covered, opt in to targeted, sale, and share, all notices given, opt into precise geo + Gpc: 0, + KnownChildSensitiveDataConsents: [0, 0], + MspaCoveredTransaction: 2, + MspaOptOutOptionMode: 0, + MspaServiceProviderMode: 0, + PersonalDataConsents: 0, + SaleOptOut: 2, + SaleOptOutNotice: 1, + SensitiveDataLimitUseNotice: 1, + SensitiveDataProcessing: [0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0], + SensitiveDataProcessingOptOutNotice: 1, + SharingNotice: 1, + SharingOptOut: 2, + SharingOptOutNotice: 1, + TargetedAdvertisingOptOut: 2, + TargetedAdvertisingOptOutNotice: 1, + Version: 1 + }; + it('should be false (consent given to add ufpd) with variety of notice and opt in', () => { + const result = isTransmitUfpdConsentDenied(cd); + expect(result).to.equal(false); + }); + it('should be true (consent denied to add ufpd) if no consent to process health information', () => { + cd.SensitiveDataProcessing[2] = 1; + const result = isTransmitUfpdConsentDenied(cd); + expect(result).to.equal(true); + cd.SensitiveDataProcessing[2] = 0; + }); + it('should be true (consent denied to add ufpd) with consent to process biometric data, as this should not be on openrtb', () => { + cd.SensitiveDataProcessing[6] = 1; + const result = isTransmitUfpdConsentDenied(cd); + expect(result).to.equal(true); + cd.SensitiveDataProcessing[6] = 1; + }); + it('should be true (consent denied to add ufpd) without sharing notice', () => { + cd.SharingNotice = 2; + const result = isTransmitUfpdConsentDenied(cd); + expect(result).to.equal(true); + cd.SharingNotice = 1; + }); + it('should be true (consent denied to add ufpd) with sale opt out', () => { + cd.SaleOptOut = 1; + const result = isTransmitUfpdConsentDenied(cd); + expect(result).to.equal(true); + cd.SaleOptOut = 2; + }); + it('should be true (consent denied to add ufpd) without targeted ads opt out', () => { + cd.TargetedAdvertisingOptOut = 1; + const result = isTransmitUfpdConsentDenied(cd); + expect(result).to.equal(true); + cd.TargetedAdvertisingOptOut = 2; + }); + it('should be true (consent denied to add ufpd) with missing sensitive data limit notice', () => { + cd.SensitiveDataLimitUseNotice = 2; + const result = isTransmitUfpdConsentDenied(cd); + expect(result).to.equal(true); + cd.SensitiveDataLimitUseNotice = 1; + }); +}) + +describe('isTransmitGeoConsentDenied', () => { + const cd = { + // not covered, opt out of geo + Gpc: 0, + KnownChildSensitiveDataConsents: [0, 0], + MspaCoveredTransaction: 2, + MspaOptOutOptionMode: 0, + MspaServiceProviderMode: 0, + PersonalDataConsents: 0, + SaleOptOut: 2, + SaleOptOutNotice: 1, + SensitiveDataLimitUseNotice: 1, + SensitiveDataProcessing: [0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0], + SensitiveDataProcessingOptOutNotice: 1, + SharingNotice: 1, + SharingOptOut: 2, + SharingOptOutNotice: 1, + TargetedAdvertisingOptOut: 2, + TargetedAdvertisingOptOutNotice: 1, + Version: 1 + }; + it('should be true (consent denied to add precise geo) -- sensitive flag denied', () => { + const result = isTransmitGeoConsentDenied(cd); + expect(result).to.equal(true); + }); + it('should be true (consent denied to add precise geo) -- sensitive data limit usage not given', () => { + cd.SensitiveDataLimitUseNotice = 0; + const result = isTransmitGeoConsentDenied(cd); + expect(result).to.equal(true); + cd.SensitiveDataLimitUseNotice = 1; + }); + it('should be false (consent given to add precise geo) -- sensitive position 8 (index 7) is true', () => { + cd.SensitiveDataProcessing[7] = 2; + const result = isTransmitGeoConsentDenied(cd); + expect(result).to.equal(false); + cd.SensitiveDataProcessing[7] = 1; + }); +}) + +describe('mspaRule', () => { + it('does not apply if SID is not applicable', () => { + const rule = mspaRule([1, 2], () => null, () => true, () => [3, 4]); + expect(rule()).to.not.exist; + }); + + it('does not apply when no SID is applicable', () => { + const rule = mspaRule([1], () => null, () => true, () => []); + expect(rule()).to.not.exist; + }); + + describe('when SID is applicable', () => { + let consent, denies; + function mkRule() { + return mspaRule([1, 2], () => consent, denies, () => [2]) + } + + beforeEach(() => { + consent = null; + denies = sinon.stub(); + }); + + it('should deny when no consent is available', () => { + expect(mkRule()().allow).to.equal(false); + }); + + Object.entries({ + 'denies': true, + 'allows': false + }).forEach(([t, denied]) => { + it(`should check if deny fn ${t}`, () => { + denies.returns(denied); + consent = {mock: 'value'}; + const result = mkRule()(); + sinon.assert.calledWith(denies, consent); + if (denied) { + expect(result.allow).to.equal(false); + } else { + expect(result).to.not.exist; + } + }) + }) + }) +}); + +describe('setupRules', () => { + let rules, registerRule, isAllowed, consent; + beforeEach(() => { + rules = { + mockActivity: sinon.stub().returns(true) + }; + ([registerRule, isAllowed] = ruleRegistry()); + consent = { + applicableSections: [1], + sectionData: { + mockApi: { + mock: 'consent' + } + } + }; + }); + + function runSetup(api, sids, normalize) { + return setupRules(api, sids, normalize, rules, registerRule, () => consent) + } + + it('should use section data for the given api', () => { + runSetup('mockApi', [1]); + expect(isAllowed('mockActivity', {})).to.equal(false); + sinon.assert.calledWith(rules.mockActivity, {mock: 'consent'}) + }); + + it('should not choke when no consent data is available', () => { + consent = null; + runSetup('mockApi', [1]); + expect(isAllowed('mockActivity', {})).to.equal(true); + }); + + it('should check applicableSections against given SIDs', () => { + runSetup('mockApi', [2]); + expect(isAllowed('mockActivity', {})).to.equal(true); + }); + + it('should pass consent through normalizeConsent', () => { + const normalize = sinon.stub().returns({normalized: 'consent'}) + runSetup('mockApi', [1], normalize); + expect(isAllowed('mockActivity', {})).to.equal(false); + sinon.assert.calledWith(normalize, {mock: 'consent'}); + sinon.assert.calledWith(rules.mockActivity, {normalized: 'consent'}); + }); + + it('should return a function that unregisters activity controls', () => { + const dereg = runSetup('mockApi', [1]); + dereg(); + expect(isAllowed('mockActivity', {})).to.equal(true); + }); +}) diff --git a/test/spec/modules/1plusXRtdProvider_spec.js b/test/spec/modules/1plusXRtdProvider_spec.js index 496c005f470..4e4092ea26e 100644 --- a/test/spec/modules/1plusXRtdProvider_spec.js +++ b/test/spec/modules/1plusXRtdProvider_spec.js @@ -1,16 +1,17 @@ -import { config } from 'src/config'; +import assert from 'assert'; +import {config} from 'src/config'; import { + buildOrtb2Updates, + extractConfig, + extractConsent, + extractFpid, + getPapiUrl, onePlusXSubmodule, segtaxes, - extractConfig, - buildOrtb2Updates, - updateBidderConfig, setTargetingDataToConfig, - extractConsent, - getPapiUrl + updateBidderConfig, } from 'modules/1plusXRtdProvider'; -import assert from 'assert'; -import { extractFpid } from '../../../modules/1plusXRtdProvider'; +import {deepClone} from '../../../src/utils.js'; describe('1plusXRtdProvider', () => { // Fake server config @@ -35,47 +36,37 @@ describe('1plusXRtdProvider', () => { // Bidder configs const bidderConfigInitial = { - ortb2: { - user: { keywords: '' }, - site: { ext: {} } - } + user: { keywords: '' }, + site: { ext: {} } } const bidderConfigInitialWith1plusXUserData = { - ortb2: { - user: { - data: [{ name: '1plusX.com', segment: [{ id: 'initial' }] }] - }, - site: { content: { data: [] } } - } + user: { + data: [{ name: '1plusX.com', segment: [{ id: 'initial' }] }] + }, + site: { content: { data: [] } } } const bidderConfigInitialWithUserData = { - ortb2: { - user: { - data: [{ name: 'hello.world', segment: [{ id: 'initial' }] }] - }, - site: { content: { data: [] } } - } + user: { + data: [{ name: 'hello.world', segment: [{ id: 'initial' }] }] + }, + site: { content: { data: [] } } } const bidderConfigInitialWith1plusXSiteContent = { - ortb2: { - user: { data: [] }, - site: { - content: { - data: [{ - name: '1plusX.com', segment: [{ id: 'initial' }], ext: { segtax: 525 } - }] - } - }, + user: { data: [] }, + site: { + content: { + data: [{ + name: '1plusX.com', segment: [{ id: 'initial' }], ext: { segtax: 525 } + }] + } } } const bidderConfigInitialWithSiteContent = { - ortb2: { - user: { data: [] }, - site: { - content: { - data: [{ name: 'hello.world', segment: [{ id: 'initial' }] }] - } - }, + user: { data: [] }, + site: { + content: { + data: [{ name: 'hello.world', segment: [{ id: 'initial' }] }] + } } } // Util functions @@ -190,18 +181,8 @@ describe('1plusXRtdProvider', () => { }, userData: { name: '1plusX.com', - segment: rtdData.segments.map((segmentId) => ({ id: segmentId })) - } - } - expect([ortb2Updates]).to.deep.include.members([expectedOutput]); - }); - it('fills site.keywords in the ortb2 config (appnexus specific)', () => { - const rtdData = { segments: fakeResponse.s, topics: fakeResponse.t }; - const ortb2Updates = buildOrtb2Updates(rtdData, 'appnexus'); - - const expectedOutput = { - site: { - keywords: rtdData.topics.join(','), + segment: rtdData.segments.map((segmentId) => ({ id: segmentId })), + ext: { segtax: segtaxes.AUDIENCE } } } expect([ortb2Updates]).to.deep.include.members([expectedOutput]); @@ -219,7 +200,8 @@ describe('1plusXRtdProvider', () => { }, userData: { name: '1plusX.com', - segment: [] + segment: [], + ext: { segtax: segtaxes.AUDIENCE } } } expect(ortb2Updates).to.deep.include(expectedOutput); @@ -237,21 +219,11 @@ describe('1plusXRtdProvider', () => { }, userData: { name: '1plusX.com', - segment: rtdData.segments.map((segmentId) => ({ id: segmentId })) - } - } - expect(ortb2Updates).to.deep.include(expectedOutput); - }) - it('defaults to empty string if no topic is given (appnexus specific)', () => { - const rtdData = { segments: fakeResponse.s }; - const ortb2Updates = buildOrtb2Updates(rtdData, 'appnexus'); - - const expectedOutput = { - site: { - keywords: '', - } + segment: rtdData.segments.map((segmentId) => ({ id: segmentId })), + ext: { segtax: segtaxes.AUDIENCE } + }, } - expect(ortb2Updates).to.deep.include(expectedOutput); + expect(ortb2Updates, `${JSON.stringify(ortb2Updates, null, 2)}`).to.deep.include(expectedOutput); }) }) @@ -271,6 +243,7 @@ describe('1plusXRtdProvider', () => { expect(expectedOutput).to.deep.include(output) expect(output).to.deep.include(expectedOutput) }) + it('extracts null if consent object is empty', () => { const consent1 = {} expect(extractConsent(consent1)).to.equal(null) @@ -332,27 +305,19 @@ describe('1plusXRtdProvider', () => { } it('correctly builds URLs if gdpr parameters are present', () => { - const url1 = getPapiUrl(customer) - const url2 = getPapiUrl(customer, extractConsent(consent)) - expect(['&consent_string=myConsent&gdpr_applies=1', '&gdpr_applies=1&consent_string=myConsent']).to.contain(url2.replace(url1, '')) + const url1 = getPapiUrl(customer); + const url2 = getPapiUrl(customer, extractConsent(consent)); + expect(['&consent_string=myConsent&gdpr_applies=1', '&gdpr_applies=1&consent_string=myConsent']).to.contain(url2.replace(url1, '')); }) - it('correctly builds URLs if fpid parameters are present') - const url1 = getPapiUrl(customer) - const url2 = getPapiUrl(customer, {}, 'my_first_party_id') - expect(url2.replace(url1, '')).to.equal('&fpid=my_first_party_id') + it('correctly builds URLs if fpid parameters are present', () => { + const url1 = getPapiUrl(customer); + const url2 = getPapiUrl(customer, {}, 'my_first_party_id'); + expect(url2.replace(url1, '')).to.equal('&fpid=my_first_party_id'); + }) }) describe('updateBidderConfig', () => { - const ortb2UpdatesAppNexus = { - site: { - keywords: fakeResponse.t.join(','), - }, - userData: { - name: '1plusX.com', - segment: fakeResponse.s.map((segmentId) => ({ id: segmentId })) - } - } const ortb2Updates = { siteContentData: { name: '1plusX.com', @@ -362,114 +327,95 @@ describe('1plusXRtdProvider', () => { userData: { name: '1plusX.com', segment: fakeResponse.s.map((segmentId) => ({ id: segmentId })) - } + }, } it('merges fetched data in bidderConfig for configured bidders', () => { - const bidder = randomBidder(); - // Set initial config - config.setBidderConfig({ - bidders: [bidder], - config: bidderConfigInitial - }); - // Call submodule's setBidderConfig - const newBidderConfig = updateBidderConfig(bidder, ortb2Updates, config.getBidderConfig()); - // Check that the targeting data has been set in the config - expect(newBidderConfig).not.to.be.null; - expect(newBidderConfig.ortb2.site.content.data).to.deep.include(ortb2Updates.siteContentData); - expect(newBidderConfig.ortb2.user.data).to.deep.include(ortb2Updates.userData); - // Check that existing config didn't get erased - expect(newBidderConfig.ortb2.site).to.deep.include(bidderConfigInitial.ortb2.site); - expect(newBidderConfig.ortb2.user).to.deep.include(bidderConfigInitial.ortb2.user); - }) - - it('merges fetched data in bidderConfig for configured bidders (appnexus specific)', () => { - const bidder = 'appnexus'; // Set initial config - config.setBidderConfig({ - bidders: [bidder], - config: bidderConfigInitial - }); + const bidder = randomBidder(); + const ortb2Fragments = { [bidder]: deepClone(bidderConfigInitial) } // Call submodule's setBidderConfig - const newBidderConfig = updateBidderConfig(bidder, ortb2UpdatesAppNexus, config.getBidderConfig()); - + updateBidderConfig(bidder, ortb2Updates, ortb2Fragments); + const newBidderConfig = ortb2Fragments[bidder]; // Check that the targeting data has been set in the config - expect(newBidderConfig).not.to.be.null; - expect(newBidderConfig.ortb2.site).to.deep.include(ortb2UpdatesAppNexus.site); + expect(newBidderConfig).not.to.be.null.and.not.to.be.undefined; + expect(newBidderConfig.user).not.to.be.null.and.not.to.be.undefined; + expect(newBidderConfig.site).not.to.be.null.and.not.to.be.undefined; + expect(newBidderConfig.user.data).to.deep.include(ortb2Updates.userData); + expect(newBidderConfig.site.content.data).to.deep.include(ortb2Updates.siteContentData); // Check that existing config didn't get erased - expect(newBidderConfig.ortb2.site).to.deep.include(bidderConfigInitial.ortb2.site); - expect(newBidderConfig.ortb2.user).to.deep.include(bidderConfigInitial.ortb2.user); + expect(newBidderConfig.site).to.deep.include(bidderConfigInitial.site); + expect(newBidderConfig.user).to.deep.include(bidderConfigInitial.user); }) it('overwrites an existing 1plus.com entry in ortb2.user.data', () => { - const bidder = randomBidder(); // Set initial config - config.setBidderConfig({ - bidders: [bidder], - config: bidderConfigInitialWith1plusXUserData - }); + const bidder = randomBidder(); + const ortb2Fragments = { [bidder]: { ...bidderConfigInitialWith1plusXUserData } } // Save previous user.data entry - const previousUserData = bidderConfigInitialWith1plusXUserData.ortb2.user.data[0]; + const previousUserData = bidderConfigInitialWith1plusXUserData.user.data[0]; // Call submodule's setBidderConfig - const newBidderConfig = updateBidderConfig(bidder, ortb2Updates, config.getBidderConfig()); + updateBidderConfig(bidder, ortb2Updates, ortb2Fragments); + const newBidderConfig = ortb2Fragments[bidder]; // Check that the targeting data has been set in the config expect(newBidderConfig).not.to.be.null; - expect(newBidderConfig.ortb2.user.data).to.deep.include(ortb2Updates.userData); - expect(newBidderConfig.ortb2.user.data).not.to.include(previousUserData); + expect(newBidderConfig.site).not.to.be.null; + expect(newBidderConfig.user).not.to.be.null; + expect(newBidderConfig.user.data).to.deep.include(ortb2Updates.userData); + expect(newBidderConfig.user.data).not.to.include(previousUserData); }) it("doesn't overwrite entries in ortb2.user.data that aren't 1plusx.com", () => { - const bidder = randomBidder(); // Set initial config - config.setBidderConfig({ - bidders: [bidder], - config: bidderConfigInitialWithUserData - }); + const bidder = randomBidder(); + const ortb2Fragments = { [bidder]: { ...bidderConfigInitialWithUserData } } // Save previous user.data entry - const previousUserData = bidderConfigInitialWithUserData.ortb2.user.data[0]; + const previousUserData = bidderConfigInitialWithUserData.user.data[0]; // Call submodule's setBidderConfig - const newBidderConfig = updateBidderConfig(bidder, ortb2Updates, config.getBidderConfig()); + updateBidderConfig(bidder, ortb2Updates, ortb2Fragments); + const newBidderConfig = ortb2Fragments[bidder]; // Check that the targeting data has been set in the config expect(newBidderConfig).not.to.be.null; - expect(newBidderConfig.ortb2.user.data).to.deep.include(ortb2Updates.userData); - expect(newBidderConfig.ortb2.user.data).to.deep.include(previousUserData); + expect(newBidderConfig.site).not.to.be.null; + expect(newBidderConfig.user).not.to.be.null; + expect(newBidderConfig.user.data).to.deep.include(ortb2Updates.userData); + expect(newBidderConfig.user.data).to.deep.include(previousUserData); }) it('overwrites an existing 1plus.com entry in ortb2.site.content.data', () => { - const bidder = randomBidder(); // Set initial config - config.setBidderConfig({ - bidders: [bidder], - config: bidderConfigInitialWith1plusXSiteContent - }); + const bidder = randomBidder(); + const ortb2Fragments = { [bidder]: { ...bidderConfigInitialWith1plusXSiteContent } } // Save previous user.data entry - const previousSiteContent = bidderConfigInitialWith1plusXSiteContent.ortb2.site.content.data[0]; + const previousSiteContent = bidderConfigInitialWith1plusXSiteContent.site.content.data[0]; // Call submodule's setBidderConfig - const newBidderConfig = updateBidderConfig(bidder, ortb2Updates, config.getBidderConfig()); + updateBidderConfig(bidder, ortb2Updates, ortb2Fragments); + const newBidderConfig = ortb2Fragments[bidder]; // Check that the targeting data has been set in the config expect(newBidderConfig).not.to.be.null; - expect(newBidderConfig.ortb2.site.content.data).to.deep.include(ortb2Updates.siteContentData); - expect(newBidderConfig.ortb2.site.content.data).not.to.include(previousSiteContent); + expect(newBidderConfig.site).not.to.be.null; + expect(newBidderConfig.user).not.to.be.null; + expect(newBidderConfig.site.content.data).to.deep.include(ortb2Updates.siteContentData); + expect(newBidderConfig.site.content.data).not.to.include(previousSiteContent); }) it("doesn't overwrite entries in ortb2.site.content.data that aren't 1plusx.com", () => { - const bidder = randomBidder(); // Set initial config - config.setBidderConfig({ - bidders: [bidder], - config: bidderConfigInitialWithSiteContent - }); + const bidder = randomBidder(); + const ortb2Fragments = { [bidder]: { ...bidderConfigInitialWithSiteContent } } // Save previous user.data entry - const previousSiteContent = bidderConfigInitialWithSiteContent.ortb2.site.content.data[0]; + const previousSiteContent = bidderConfigInitialWithSiteContent.site.content.data[0]; // Call submodule's setBidderConfig - const newBidderConfig = updateBidderConfig(bidder, ortb2Updates, config.getBidderConfig()); + updateBidderConfig(bidder, ortb2Updates, ortb2Fragments); + const newBidderConfig = ortb2Fragments[bidder]; // Check that the targeting data has been set in the config expect(newBidderConfig).not.to.be.null; - expect(newBidderConfig.ortb2.site.content.data).to.deep.include(ortb2Updates.siteContentData); - expect(newBidderConfig.ortb2.site.content.data).to.deep.include(previousSiteContent); + expect(newBidderConfig.site).not.to.be.null; + expect(newBidderConfig.user).not.to.be.null; + expect(newBidderConfig.site.content.data).to.deep.include(ortb2Updates.siteContentData); + expect(newBidderConfig.site.content.data).to.deep.include(previousSiteContent); }) }) describe('setTargetingDataToConfig', () => { - const expectedKeywords = fakeResponse.t.join(','); const expectedSiteContentObj = { data: [{ name: '1plusX.com', @@ -480,12 +426,14 @@ describe('1plusXRtdProvider', () => { const expectedUserObj = { data: [{ name: '1plusX.com', - segment: fakeResponse.s.map((segmentId) => ({ id: segmentId })) + segment: fakeResponse.s.map((segmentId) => ({ id: segmentId })), + ext: { segtax: segtaxes.AUDIENCE } }] } const expectedOrtb2 = { appnexus: { - site: { keywords: expectedKeywords } + site: { content: expectedSiteContentObj }, + user: expectedUserObj }, rubicon: { site: { content: expectedSiteContentObj }, @@ -500,22 +448,23 @@ describe('1plusXRtdProvider', () => { bidders, config: bidderConfigInitial }) + const biddersOrtb2 = config.getBidderConfig(); // call setTargetingDataToConfig - setTargetingDataToConfig(fakeResponse, { bidders }); + setTargetingDataToConfig(fakeResponse, { bidders, biddersOrtb2 }); // Check that the targeting data has been set in both configs for (const bidder of bidders) { const newConfig = config.getBidderConfig()[bidder]; // Check that we got what we expect const expectedConfErr = (prop) => `New config for ${bidder} doesn't comply with expected at ${prop}`; - expect(newConfig.ortb2.site, expectedConfErr('site')).to.deep.include(expectedOrtb2[bidder].site); + expect(newConfig.site, expectedConfErr('site')).to.deep.include(expectedOrtb2[bidder].site); if (expectedOrtb2[bidder].user) { - expect(newConfig.ortb2.user, expectedConfErr('user')).to.deep.include(expectedOrtb2[bidder].user); + expect(newConfig.user, expectedConfErr('user')).to.deep.include(expectedOrtb2[bidder].user); } // Check that existing config didn't get erased const existingConfErr = (prop) => `Existing config for ${bidder} got unlawfully overwritten at ${prop}`; - expect(newConfig.ortb2.site, existingConfErr('site')).to.deep.include(bidderConfigInitial.ortb2.site); - expect(newConfig.ortb2.user, existingConfErr('user')).to.deep.include(bidderConfigInitial.ortb2.user); + expect(newConfig.site, existingConfErr('site')).to.deep.include(bidderConfigInitial.site); + expect(newConfig.user, existingConfErr('user')).to.deep.include(bidderConfigInitial.user); } }) }) diff --git a/test/spec/modules/33acrossBidAdapter_spec.js b/test/spec/modules/33acrossBidAdapter_spec.js index 3b3c05660df..aa2621f1fa0 100644 --- a/test/spec/modules/33acrossBidAdapter_spec.js +++ b/test/spec/modules/33acrossBidAdapter_spec.js @@ -53,7 +53,8 @@ describe('33acrossBidAdapter:', function () { brand: 'macOS', version: ['11', '6', '8'] }, - model: '' + model: '', + mobile: 0 } }, id: 'r1', @@ -337,7 +338,7 @@ describe('33acrossBidAdapter:', function () { version: ['11', '6', '8'] }, model: '', - mobile: false + mobile: 0 } } } @@ -369,7 +370,7 @@ describe('33acrossBidAdapter:', function () { version: ['11', '6', '8'] }, model: '', - mobile: false + mobile: 0 } } }, @@ -422,6 +423,8 @@ describe('33acrossBidAdapter:', function () { this.build = () => bidRequests; } + let bidderRequest; + beforeEach(function() { element = { x: 0, @@ -475,12 +478,12 @@ describe('33acrossBidAdapter:', function () { sandbox.stub(document, 'getElementById').returns(element); sandbox.stub(utils, 'getWindowTop').returns(win); sandbox.stub(utils, 'getWindowSelf').returns(win); + bidderRequest = {bidderRequestId: 'r1'}; }); afterEach(function() { sandbox.restore(); }); - describe('isBidRequestValid:', function() { context('basic validation', function() { it('returns true for valid bidder name values', function() { @@ -769,7 +772,7 @@ describe('33acrossBidAdapter:', function () { Object.assign(element, { width: 600, height: 400 }); - const [ buildRequest ] = spec.buildRequests(bidRequests); + const [ buildRequest ] = spec.buildRequests(bidRequests, bidderRequest); validateBuiltServerRequest(buildRequest, serverRequest); }); }); @@ -787,7 +790,7 @@ describe('33acrossBidAdapter:', function () { Object.assign(element, { x: -300, y: 0, width: 207, height: 320 }); - const [ buildRequest ] = spec.buildRequests(bidRequests); + const [ buildRequest ] = spec.buildRequests(bidRequests, bidderRequest); validateBuiltServerRequest(buildRequest, serverRequest); }); }); @@ -805,7 +808,7 @@ describe('33acrossBidAdapter:', function () { Object.assign(element, { width: 800, height: 800 }); - const [ buildRequest ] = spec.buildRequests(bidRequests); + const [ buildRequest ] = spec.buildRequests(bidRequests, bidderRequest); validateBuiltServerRequest(buildRequest, serverRequest); }); }); @@ -825,7 +828,7 @@ describe('33acrossBidAdapter:', function () { Object.assign(element, { width: 0, height: 0 }); bidRequests[0].mediaTypes.banner.sizes = [[800, 2400]]; - const [ buildRequest ] = spec.buildRequests(bidRequests); + const [ buildRequest ] = spec.buildRequests(bidRequests, bidderRequest); validateBuiltServerRequest(buildRequest, serverRequest); }); }); @@ -848,7 +851,7 @@ describe('33acrossBidAdapter:', function () { sandbox.stub(utils, 'getWindowTop').returns({}); sandbox.stub(utils, 'getWindowSelf').returns(win); - const [ buildRequest ] = spec.buildRequests(bidRequests); + const [ buildRequest ] = spec.buildRequests(bidRequests, bidderRequest); validateBuiltServerRequest(buildRequest, serverRequest); }); @@ -889,7 +892,7 @@ describe('33acrossBidAdapter:', function () { } }); - const [ buildRequest ] = spec.buildRequests(bidRequests); + const [ buildRequest ] = spec.buildRequests(bidRequests, bidderRequest); validateBuiltServerRequest(buildRequest, serverRequest); }); }); @@ -937,7 +940,7 @@ describe('33acrossBidAdapter:', function () { } }); - const [ buildRequest ] = spec.buildRequests(bidRequests); + const [ buildRequest ] = spec.buildRequests(bidRequests, bidderRequest); validateBuiltServerRequest(buildRequest, serverRequest); }); }); @@ -963,7 +966,7 @@ describe('33acrossBidAdapter:', function () { win.screen.width = 1024; win.screen.height = 728; - const [ buildRequest ] = spec.buildRequests(bidRequests); + const [ buildRequest ] = spec.buildRequests(bidRequests, {bidderRequestId: 'r1'}); validateBuiltServerRequest(buildRequest, serverRequest); }); @@ -992,7 +995,7 @@ describe('33acrossBidAdapter:', function () { win.innerHeight = 728; win.innerWidth = 727; - const [ buildRequest ] = spec.buildRequests(bidRequests); + const [ buildRequest ] = spec.buildRequests(bidRequests, bidderRequest); validateBuiltServerRequest(buildRequest, serverRequest); }); @@ -1015,16 +1018,15 @@ describe('33acrossBidAdapter:', function () { win.document.visibilityState = 'hidden'; sandbox.stub(utils, 'getWindowTop').returns(win); - const [ buildRequest ] = spec.buildRequests(bidRequests); + const [ buildRequest ] = spec.buildRequests(bidRequests, bidderRequest); validateBuiltServerRequest(buildRequest, serverRequest); }); }); context('when gdpr consent data exists', function() { - let bidderRequest; - beforeEach(function() { bidderRequest = { + ...bidderRequest, gdprConsent: { consentString: 'foobarMyPreference', gdprApplies: true @@ -1069,12 +1071,6 @@ describe('33acrossBidAdapter:', function () { }); context('when gdpr consent data does not exist', function() { - let bidderRequest; - - beforeEach(function() { - bidderRequest = {}; - }); - it('returns corresponding server requests with default gdpr consent data', function() { const ttxRequest = new TtxRequestBuilder() .withBanner() @@ -1110,10 +1106,9 @@ describe('33acrossBidAdapter:', function () { }); context('when us_privacy consent data exists', function() { - let bidderRequest; - beforeEach(function() { bidderRequest = { + ...bidderRequest, uspConsent: 'foo' } }); @@ -1155,12 +1150,6 @@ describe('33acrossBidAdapter:', function () { }); context('when us_privacy consent data does not exist', function() { - let bidderRequest; - - beforeEach(function() { - bidderRequest = {}; - }); - it('returns corresponding server requests with default us_privacy data', function() { const ttxRequest = new TtxRequestBuilder() .withBanner() @@ -1198,7 +1187,8 @@ describe('33acrossBidAdapter:', function () { context('when refererInfo values are available', function() { context('when refererInfo.page is defined', function() { it('returns corresponding server requests with site.page set', function() { - const bidderRequest = { + bidderRequest = { + ...bidderRequest, refererInfo: { page: 'http://foo.com/bar' } @@ -1221,7 +1211,8 @@ describe('33acrossBidAdapter:', function () { context('when refererInfo.ref is defined', function() { it('returns corresponding server requests with site.ref set', function() { - const bidderRequest = { + bidderRequest = { + ...bidderRequest, refererInfo: { ref: 'google.com' } @@ -1244,12 +1235,6 @@ describe('33acrossBidAdapter:', function () { }); context('when Global Placement ID (gpid) is defined', function() { - let bidderRequest; - - beforeEach(function() { - bidderRequest = {}; - }); - it('passes the Global Placement ID (gpid) in the request', function() { const ttxRequest = new TtxRequestBuilder() .withBanner() @@ -1280,9 +1265,7 @@ describe('33acrossBidAdapter:', function () { context('when referer value is not available', function() { it('returns corresponding server requests without site.page and site.ref set', function() { - const bidderRequest = { - refererInfo: {} - }; + bidderRequest.refererInfo = {}; const ttxRequest = new TtxRequestBuilder() .withBanner() @@ -1346,7 +1329,7 @@ describe('33acrossBidAdapter:', function () { .withData(ttxRequest) .build(); - const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); validateBuiltServerRequest(builtServerRequest, serverRequest); }); @@ -1364,7 +1347,7 @@ describe('33acrossBidAdapter:', function () { .withData(ttxRequest) .build(); - const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); validateBuiltServerRequest(builtServerRequest, serverRequest); }); @@ -1379,7 +1362,7 @@ describe('33acrossBidAdapter:', function () { const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .build(); - const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); validateBuiltServerRequest(builtServerRequest, serverRequest); }); @@ -1396,7 +1379,7 @@ describe('33acrossBidAdapter:', function () { const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .build(); - const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); validateBuiltServerRequest(builtServerRequest, serverRequest); }); @@ -1421,7 +1404,7 @@ describe('33acrossBidAdapter:', function () { const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .build(); - const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); validateBuiltServerRequest(builtServerRequest, serverRequest); }); @@ -1446,7 +1429,7 @@ describe('33acrossBidAdapter:', function () { const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .build(); - const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); validateBuiltServerRequest(builtServerRequest, serverRequest); }); @@ -1463,7 +1446,7 @@ describe('33acrossBidAdapter:', function () { .withProduct('instream') .build(); - const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); expect(JSON.parse(builtServerRequest.data)).to.deep.equal(ttxRequest); }); @@ -1487,7 +1470,7 @@ describe('33acrossBidAdapter:', function () { const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .build(); - const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); validateBuiltServerRequest(builtServerRequest, serverRequest); }); @@ -1507,7 +1490,7 @@ describe('33acrossBidAdapter:', function () { const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .build(); - const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); validateBuiltServerRequest(builtServerRequest, serverRequest); }); @@ -1529,7 +1512,7 @@ describe('33acrossBidAdapter:', function () { const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .build(); - const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); validateBuiltServerRequest(builtServerRequest, serverRequest); }); @@ -1550,7 +1533,7 @@ describe('33acrossBidAdapter:', function () { const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .build(); - const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); validateBuiltServerRequest(builtServerRequest, serverRequest); }); @@ -1574,7 +1557,7 @@ describe('33acrossBidAdapter:', function () { const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .build(); - const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); validateBuiltServerRequest(builtServerRequest, serverRequest); }); @@ -1598,7 +1581,7 @@ describe('33acrossBidAdapter:', function () { const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) .build(); - const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); validateBuiltServerRequest(builtServerRequest, serverRequest); }); @@ -1619,7 +1602,7 @@ describe('33acrossBidAdapter:', function () { .withProduct() .build(); - const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); expect(JSON.parse(builtServerRequest.data)).to.deep.equal(ttxRequest); }); @@ -1647,7 +1630,7 @@ describe('33acrossBidAdapter:', function () { .withFloors('video', [ 1.0 ]) .build(); - const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); expect(JSON.parse(builtServerRequest.data)).to.deep.equal(ttxRequest); }); @@ -1689,7 +1672,7 @@ describe('33acrossBidAdapter:', function () { .withProduct() .build(); - const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); expect(JSON.parse(builtServerRequest.data)).to.deep.equal(ttxRequest); }); @@ -1708,7 +1691,7 @@ describe('33acrossBidAdapter:', function () { .withProduct() .build(); - const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); expect(JSON.parse(builtServerRequest.data)).to.deep.equal(ttxRequest); }); @@ -1755,7 +1738,7 @@ describe('33acrossBidAdapter:', function () { .withProduct() .build(); - const [ builtServerRequest ] = spec.buildRequests(bidRequests, {}); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); expect(JSON.parse(builtServerRequest.data)).to.deep.equal(ttxRequest); }); @@ -1829,7 +1812,7 @@ describe('33acrossBidAdapter:', function () { .withUrl('https://ssc.33across.com/api/v1/hb?guid=sample33xGUID123456780') .build(); - const builtServerRequests = spec.buildRequests(bidRequests, {}); + const builtServerRequests = spec.buildRequests(bidRequests, bidderRequest); expect(builtServerRequests).to.deep.equal([serverReq1, serverReq2, serverReq3]); }); @@ -1886,7 +1869,7 @@ describe('33acrossBidAdapter:', function () { .withUrl('https://ssc.33across.com/api/v1/hb?guid=sample33xGUID123456780') .build(); - const builtServerRequests = spec.buildRequests(bidRequests, {}); + const builtServerRequests = spec.buildRequests(bidRequests, bidderRequest); expect(builtServerRequests) .to.deep.equal([ @@ -1945,7 +1928,6 @@ describe('33acrossBidAdapter:', function () { }; const bidResponse = { requestId: 'b1', - bidderCode: BIDDER_CODE, cpm: 0.0938, width: 300, height: 250, @@ -1991,7 +1973,6 @@ describe('33acrossBidAdapter:', function () { }; const bidResponse = { requestId: 'b1', - bidderCode: BIDDER_CODE, cpm: 0.0938, width: 300, height: 250, @@ -2035,7 +2016,6 @@ describe('33acrossBidAdapter:', function () { // Bid response below doesn't contain meta.advertiserDomains const bidResponse = { requestId: 'b1', - bidderCode: BIDDER_CODE, cpm: 0.0938, width: 300, height: 250, @@ -2115,7 +2095,6 @@ describe('33acrossBidAdapter:', function () { const bidResponse = [ { requestId: 'b1', - bidderCode: BIDDER_CODE, cpm: 0.0940, width: 300, height: 250, @@ -2128,7 +2107,6 @@ describe('33acrossBidAdapter:', function () { }, { requestId: 'b2', - bidderCode: BIDDER_CODE, cpm: 0.0938, width: 300, height: 250, @@ -2141,7 +2119,6 @@ describe('33acrossBidAdapter:', function () { }, { requestId: 'b3', - bidderCode: BIDDER_CODE, cpm: 0.0938, width: 300, height: 250, @@ -2224,7 +2201,7 @@ describe('33acrossBidAdapter:', function () { expect(spec.getUserSyncs(syncOptions)).to.deep.equal([]); }); - }); + }, bidderRequest); context('when iframe is enabled', function() { let syncOptions; @@ -2236,7 +2213,7 @@ describe('33acrossBidAdapter:', function () { context('when there is no gdpr consent data', function() { it('returns sync urls with undefined consent string as param', function() { - spec.buildRequests(bidRequests); + spec.buildRequests(bidRequests, bidderRequest); const syncResults = spec.getUserSyncs(syncOptions, {}, undefined); const expectedSyncs = [ @@ -2256,7 +2233,7 @@ describe('33acrossBidAdapter:', function () { context('when gdpr applies but there is no consent string', function() { it('returns sync urls with undefined consent string as param and gdpr=1', function() { - spec.buildRequests(bidRequests); + spec.buildRequests(bidRequests, bidderRequest); const syncResults = spec.getUserSyncs(syncOptions, {}, {gdprApplies: true}); const expectedSyncs = [ @@ -2276,7 +2253,7 @@ describe('33acrossBidAdapter:', function () { context('when gdpr applies and there is consent string', function() { it('returns sync urls with gdpr_consent=consent string as param and gdpr=1', function() { - spec.buildRequests(bidRequests); + spec.buildRequests(bidRequests, bidderRequest); const syncResults = spec.getUserSyncs(syncOptions, {}, {gdprApplies: true, consentString: 'consent123A'}); const expectedSyncs = [ @@ -2296,7 +2273,7 @@ describe('33acrossBidAdapter:', function () { context('when gdpr does not apply and there is no consent string', function() { it('returns sync urls with undefined consent string as param and gdpr=0', function() { - spec.buildRequests(bidRequests); + spec.buildRequests(bidRequests, bidderRequest); const syncResults = spec.getUserSyncs(syncOptions, {}, {gdprApplies: false}); const expectedSyncs = [ @@ -2315,7 +2292,7 @@ describe('33acrossBidAdapter:', function () { context('when gdpr is unknown and there is consent string', function() { it('returns sync urls with only consent string as param', function() { - spec.buildRequests(bidRequests); + spec.buildRequests(bidRequests, bidderRequest); const syncResults = spec.getUserSyncs(syncOptions, {}, {consentString: 'consent123A'}); const expectedSyncs = [ @@ -2334,7 +2311,7 @@ describe('33acrossBidAdapter:', function () { context('when gdpr does not apply and there is consent string (yikes!)', function() { it('returns sync urls with consent string as param and gdpr=0', function() { - spec.buildRequests(bidRequests); + spec.buildRequests(bidRequests, bidderRequest); const syncResults = spec.getUserSyncs(syncOptions, {}, {gdprApplies: false, consentString: 'consent123A'}); const expectedSyncs = [ @@ -2353,7 +2330,7 @@ describe('33acrossBidAdapter:', function () { context('when there is no usPrivacy data', function() { it('returns sync urls with undefined consent string as param', function() { - spec.buildRequests(bidRequests); + spec.buildRequests(bidRequests, bidderRequest); const syncResults = spec.getUserSyncs(syncOptions, {}); const expectedSyncs = [ @@ -2373,7 +2350,7 @@ describe('33acrossBidAdapter:', function () { context('when there is usPrivacy data', function() { it('returns sync urls with consent string as param', function() { - spec.buildRequests(bidRequests); + spec.buildRequests(bidRequests, bidderRequest); const syncResults = spec.getUserSyncs(syncOptions, {}, {}, 'foo'); const expectedSyncs = [ diff --git a/test/spec/modules/33acrossIdSystem_spec.js b/test/spec/modules/33acrossIdSystem_spec.js index 765b320f925..5070d2b8845 100644 --- a/test/spec/modules/33acrossIdSystem_spec.js +++ b/test/spec/modules/33acrossIdSystem_spec.js @@ -282,7 +282,7 @@ describe('33acrossIdSystem', () => { error: 'foo' })); - expect(logErrorSpy.calledOnceWithExactly(`${thirthyThreeAcrossIdSubmodule.name}: Unsuccessful response`)).to.be.true; + expect(logErrorSpy.calledOnceWithExactly(`${thirthyThreeAcrossIdSubmodule.name}: Unsuccessful response foo`)).to.be.true; logErrorSpy.restore(); }); diff --git a/test/spec/modules/ViouslyBidAdapter_spec.js b/test/spec/modules/ViouslyBidAdapter_spec.js index 612c5f87138..f8cd686581c 100644 --- a/test/spec/modules/ViouslyBidAdapter_spec.js +++ b/test/spec/modules/ViouslyBidAdapter_spec.js @@ -1,6 +1,7 @@ import {expect} from 'chai'; import { deepClone, mergeDeep } from 'src/utils'; +import { BANNER, VIDEO } from 'src/mediaTypes'; import { createEidsArray } from 'modules/userId/eids.js'; import {spec as adapter} from 'modules/viouslyBidAdapter'; @@ -13,6 +14,21 @@ const TTL = 60; const HTTP_METHOD = 'POST'; const REQUEST_URL = 'https://bidder.viously.com/bid'; +const VALID_BID_BANNER = { + bidder: 'viously', + bidId: '5e6f7g8h', + adUnitCode: 'id-5678', + params: { + pid: '123e4567-e89b-12d3-a456-426614174002' + }, + mediaTypes: { + banner: { + sizes: [300, 50], + pos: 1 + } + } +}; + const VALID_BID_VIDEO = { bidder: 'viously', bidId: '5e6f7g8h', @@ -29,6 +45,24 @@ const VALID_BID_VIDEO = { } }; +const VALID_REQUEST_BANNER = { + method: HTTP_METHOD, + url: REQUEST_URL, + data: { + pid: '123e4567-e89b-12d3-a456-426614174002', + currency_code: CURRENCY, + placements: [ + { + id: 'id-5678', + bid_id: '5e6f7g8h', + sizes: ['300x50'], + type: BANNER, + position: 1 + } + ] + } +}; + const VALID_REQUEST_VIDEO = { method: HTTP_METHOD, url: REQUEST_URL, @@ -39,6 +73,7 @@ const VALID_REQUEST_VIDEO = { { id: 'id-5678', bid_id: '5e6f7g8h', + type: VIDEO, video_params: { context: 'instream', playbackmethod: [1, 2, 3, 4], @@ -68,9 +103,30 @@ describe('ViouslyAdapter', function () { describe('isBidRequestValid', function () { describe('Check method return', function () { it('should return true', function () { + expect(adapter.isBidRequestValid(VALID_BID_BANNER)).to.equal(true); expect(adapter.isBidRequestValid(VALID_BID_VIDEO)).to.equal(true); }); + it('should return true for banner with no pos', function () { + let newBid = deepClone(VALID_BID_BANNER); + let newRequest = deepClone(VALID_REQUEST_BANNER); + + delete newBid.mediaTypes.banner.pos; + newRequest.data.placements[0].position = 0; + + expect(adapter.buildRequests([newBid])).to.deep.equal(newRequest); + }); + + it('should return false because the banner size is missing', function () { + let wrongBid = deepClone(VALID_BID_BANNER); + + wrongBid.mediaTypes.banner.sizes = '123456'; + expect(adapter.isBidRequestValid(wrongBid)).to.equal(false); + + delete wrongBid.mediaTypes.banner.sizes; + expect(adapter.isBidRequestValid(wrongBid)).to.equal(false); + }); + it('should return false because the pid is missing', function () { let wrongBid = deepClone(VALID_BID_VIDEO); delete wrongBid.params.pid; @@ -89,6 +145,10 @@ describe('ViouslyAdapter', function () { describe('buildRequests', function () { describe('Check method return', function () { + it('should return the right formatted banner requests', function() { + expect(adapter.buildRequests([VALID_BID_BANNER])).to.deep.equal(VALID_REQUEST_BANNER); + }); + it('should return the right formatted video requests', function() { expect(adapter.buildRequests([VALID_BID_VIDEO])).to.deep.equal(VALID_REQUEST_VIDEO); }); @@ -214,7 +274,7 @@ describe('ViouslyAdapter', function () { expect(adapter.buildRequests([bid])).to.deep.equal(requests); }); - it('should return the right formatted request with endpint test', function() { + it('should return the right formatted request with endpoint test', function() { let endpoint = 'https://bid-test.viously.com/prebid'; let bid = mergeDeep(deepClone(VALID_BID_VIDEO), { @@ -259,6 +319,20 @@ describe('ViouslyAdapter', function () { 'win.domain.com' ] }, + { + bid: true, + creative_id: '1357', + id: 'id-0157324f-bee4-5390-a14c-47a7da3eb73c-2', + bid_id: '9101112', + cpm: 1.5, + ad: 'html content', + type: 'banner', + size: '300x50', + nurl: [ + 'win.domain2.com', + 'win.domain3.com' + ] + }, { bid: true, creative_id: '1469', @@ -283,6 +357,10 @@ describe('ViouslyAdapter', function () { id: 'id-0157324f-bee4-5390-a14c-47a7da3eb73c-1', bid_id: '5678' }, + { + id: 'id-0157324f-bee4-5390-a14c-47a7da3eb73c-2', + bid_id: '9101112' + }, { id: 'id-0157324f-bee4-5390-a14c-47a7da3eb73c-3', bid_id: '2570' @@ -309,6 +387,24 @@ describe('ViouslyAdapter', function () { 'win.domain.com' ] }, + { + requestId: '9101112', + id: 'id-0157324f-bee4-5390-a14c-47a7da3eb73c-2', + cpm: 1.5, + width: '300', + height: '50', + creativeId: '1357', + currency: CURRENCY, + netRevenue: true, + ttl: TTL, + mediaType: 'banner', + meta: {}, + ad: 'html content', + nurl: [ + 'win.domain2.com', + 'win.domain3.com' + ] + }, { requestId: '2570', id: 'id-0157324f-bee4-5390-a14c-47a7da3eb73c-3', diff --git a/test/spec/modules/acuityAdsBidAdapter_spec.js b/test/spec/modules/acuityAdsBidAdapter_spec.js index 18ea574c1ce..05c59036ff3 100644 --- a/test/spec/modules/acuityAdsBidAdapter_spec.js +++ b/test/spec/modules/acuityAdsBidAdapter_spec.js @@ -76,7 +76,8 @@ describe('AcuityAdsBidAdapter', function () { gdprConsent: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', refererInfo: { referer: 'https://test.com' - } + }, + timeout: 500 }; describe('isBidRequestValid', function () { diff --git a/test/spec/modules/adagioBidAdapter_spec.js b/test/spec/modules/adagioBidAdapter_spec.js index ba1db077488..f9bf62206f5 100644 --- a/test/spec/modules/adagioBidAdapter_spec.js +++ b/test/spec/modules/adagioBidAdapter_spec.js @@ -9,7 +9,7 @@ import { spec, ENDPOINT, VERSION, - RENDERER_URL, + BB_RENDERER_URL, GlobalExchange } from '../../../modules/adagioBidAdapter.js'; import { loadExternalScript } from '../../../src/adloader.js'; @@ -17,6 +17,7 @@ import * as utils from '../../../src/utils.js'; import { config } from '../../../src/config.js'; import { NATIVE } from '../../../src/mediaTypes.js'; import { executeRenderer } from '../../../src/Renderer.js'; +import { userSync } from '../../../src/userSync.js'; const BidRequestBuilder = function BidRequestBuilder(options) { const defaults = { @@ -75,6 +76,7 @@ describe('Adagio bid adapter', () => { let adagioMock; let utilsMock; let sandbox; + let fakeRenderer; const fixtures = { getElementById(width, height, x, y) { @@ -254,7 +256,6 @@ describe('Adagio bid adapter', () => { describe('buildRequests()', function() { const expectedDataKeys = [ - 'id', 'organizationId', 'secure', 'device', @@ -266,7 +267,9 @@ describe('Adagio bid adapter', () => { 'schain', 'prebidVersion', 'featuresVersion', - 'data' + 'data', + 'usIfr', + 'adgjs', ]; it('groups requests by organizationId', function() { @@ -293,6 +296,7 @@ describe('Adagio bid adapter', () => { sandbox.stub(adagio, 'getDevice').returns({ a: 'a' }); sandbox.stub(adagio, 'getSite').returns({ domain: 'adagio.io', 'page': 'https://adagio.io/hb' }); sandbox.stub(adagio, 'getPageviewId').returns('1234-567'); + sandbox.stub(utils, 'generateUUID').returns('blabla'); const bid01 = new BidRequestBuilder().withParams().build(); const bidderRequest = new BidderRequestBuilder().build(); @@ -306,6 +310,18 @@ describe('Adagio bid adapter', () => { expect(requests[0].data).to.have.all.keys(expectedDataKeys); }); + it('should use a custom generated auctionId and remove transactionId', function() { + const expectedAuctionId = '373bcda7-9794-4f1c-be2c-0d223d11d579' + sandbox.stub(utils, 'generateUUID').returns(expectedAuctionId); + + const bid01 = new BidRequestBuilder().withParams().build(); + const bidderRequest = new BidderRequestBuilder().build(); + + const requests = spec.buildRequests([bid01], bidderRequest); + expect(requests[0].data.adUnits[0].auctionId).eq(expectedAuctionId); + expect(requests[0].data.adUnits[0].transactionId).to.not.exist; + }); + it('should enqueue computed features for collect usage', function() { sandbox.stub(Date, 'now').returns(12345); @@ -345,6 +361,76 @@ describe('Adagio bid adapter', () => { expect(requests[0].data.adUnits[0].features.url).to.not.exist; }); + it('should force split keyword param into a string', function() { + const bid01 = new BidRequestBuilder().withParams({ + splitKeyword: 1234 + }).build(); + const bid02 = new BidRequestBuilder().withParams({ + splitKeyword: ['1234'] + }).build(); + const bidderRequest = new BidderRequestBuilder().build(); + + const requests = spec.buildRequests([bid01, bid02], bidderRequest); + + expect(requests).to.have.lengthOf(1); + expect(requests[0].data).to.have.all.keys(expectedDataKeys); + expect(requests[0].data.adUnits[0].params).to.exist; + expect(requests[0].data.adUnits[0].params.splitKeyword).to.exist; + expect(requests[0].data.adUnits[0].params.splitKeyword).to.equal('1234'); + expect(requests[0].data.adUnits[1].params.splitKeyword).to.not.exist; + }); + + it('should force key and value from data layer param into a string', function() { + const bid01 = new BidRequestBuilder().withParams({ + dataLayer: { + 1234: 'dlparam', + goodkey: 1234, + objectvalue: { + random: 'result' + }, + arrayvalue: ['1234'] + } + }).build(); + + const bid02 = new BidRequestBuilder().withParams({ + dataLayer: 'a random string' + }).build(); + + const bid03 = new BidRequestBuilder().withParams({ + dataLayer: 1234 + }).build(); + + const bid04 = new BidRequestBuilder().withParams({ + dataLayer: ['an array'] + }).build(); + + const bidderRequest = new BidderRequestBuilder().build(); + + const requests = spec.buildRequests([bid01, bid02, bid03, bid04], bidderRequest); + + expect(requests).to.have.lengthOf(1); + expect(requests[0].data).to.have.all.keys(expectedDataKeys); + expect(requests[0].data.adUnits[0].params).to.exist; + expect(requests[0].data.adUnits[0].params.dataLayer).to.not.exist; + expect(requests[0].data.adUnits[0].params.dl).to.exist; + expect(requests[0].data.adUnits[0].params.dl['1234']).to.equal('dlparam'); + expect(requests[0].data.adUnits[0].params.dl.goodkey).to.equal('1234'); + expect(requests[0].data.adUnits[0].params.dl.objectvalue).to.not.exist; + expect(requests[0].data.adUnits[0].params.dl.arrayvalue).to.not.exist; + + expect(requests[0].data.adUnits[1].params).to.exist; + expect(requests[0].data.adUnits[1].params.dl).to.not.exist; + expect(requests[0].data.adUnits[1].params.dataLayer).to.not.exist; + + expect(requests[0].data.adUnits[2].params).to.exist; + expect(requests[0].data.adUnits[2].params.dl).to.not.exist; + expect(requests[0].data.adUnits[2].params.dataLayer).to.not.exist; + + expect(requests[0].data.adUnits[3].params).to.exist; + expect(requests[0].data.adUnits[3].params.dl).to.not.exist; + expect(requests[0].data.adUnits[3].params.dataLayer).to.not.exist; + }); + describe('With video mediatype', function() { context('Outstream video', function() { it('should logWarn if user does not set renderer.backupOnly: true', function() { @@ -561,15 +647,13 @@ describe('Adagio bid adapter', () => { it('should send the Coppa "required" flag set to "1" in the request', function () { const bidderRequest = new BidderRequestBuilder().build(); - sinon.stub(config, 'getConfig') - .withArgs('coppa') - .returns(true); + sandbox.stub(config, 'getConfig') + .withArgs('userSync').returns({ syncEnabled: true }) + .withArgs('coppa').returns(true); const requests = spec.buildRequests([bid01], bidderRequest); expect(requests[0].data.regs.coppa.required).to.equal(1); - - config.getConfig.restore(); }); }); @@ -614,32 +698,26 @@ describe('Adagio bid adapter', () => { }); describe('with userID modules', function() { - const userId = { - pubcid: '01EAJWWNEPN3CYMM5N8M5VXY22', - unsuported: '666' - }; + const userIdAsEids = [{ + 'source': 'pubcid.org', + 'uids': [ + { + 'atype': 1, + 'id': '01EAJWWNEPN3CYMM5N8M5VXY22' + } + ] + }]; it('should send "user.eids" in the request for Prebid.js supported modules only', function() { const bid01 = new BidRequestBuilder({ - userId + userIdAsEids }).withParams().build(); const bidderRequest = new BidderRequestBuilder().build(); const requests = spec.buildRequests([bid01], bidderRequest); - const expected = [{ - source: 'pubcid.org', - uids: [ - { - atype: 1, - id: '01EAJWWNEPN3CYMM5N8M5VXY22' - } - ] - }]; - - expect(requests[0].data.user.eids).to.have.lengthOf(1); - expect(requests[0].data.user.eids).to.deep.equal(expected); + expect(requests[0].data.user.eids).to.deep.equal(userIdAsEids); }); it('should send an empty "user.eids" array in the request if userId module is unsupported', function() { @@ -736,6 +814,46 @@ describe('Adagio bid adapter', () => { expect(requests[0].data.adUnits[0].mediaTypes.video.floor).to.be.undefined; }); }); + + describe('with user-sync iframe enabled', function () { + const bid01 = new BidRequestBuilder().withParams().build(); + + it('should send the UsIfr flag set to "true" in the request', function () { + const bidderRequest = new BidderRequestBuilder().build(); + + sandbox.stub(config, 'getConfig') + .withArgs('userSync') + .returns({ syncEnabled: true }); + + sandbox.stub(userSync, 'canBidderRegisterSync') + .withArgs('iframe', 'adagio') + .returns(true); + + const requests = spec.buildRequests([bid01], bidderRequest); + + expect(requests[0].data.usIfr).to.equal(true); + }); + }); + + describe('with user-sync iframe disabled', function () { + const bid01 = new BidRequestBuilder().withParams().build(); + + it('should send the UsIfr flag set to "false" in the request', function () { + const bidderRequest = new BidderRequestBuilder().build(); + + sandbox.stub(config, 'getConfig') + .withArgs('userSync') + .returns({ syncEnabled: true }); + + sandbox.stub(userSync, 'canBidderRegisterSync') + .withArgs('iframe', 'adagio') + .returns(false); + + const requests = spec.buildRequests([bid01], bidderRequest); + + expect(requests[0].data.usIfr).to.equal(false); + }); + }); }); describe('interpretResponse()', function() { @@ -896,42 +1014,70 @@ describe('Adagio bid adapter', () => { context: 'outstream', playerSize: [[300, 250]], mimes: ['video/mp4'], - skip: true + skip: 1, + skipafter: 3 }; const serverResponseWithOutstream = utils.deepClone(serverResponse); serverResponseWithOutstream.body.bids[0].vastXml = ''; serverResponseWithOutstream.body.bids[0].mediaType = 'video'; - serverResponseWithOutstream.body.bids[0].outstream = { - bvwUrl: 'https://foo.baz', - impUrl: 'https://foo.bar' - }; - it('should set a renderer in video outstream context', function() { + const defaultRendererUrl = BB_RENDERER_URL.replace('$RENDERER', 'renderer'); + + it('should set related properties for video outstream context', function() { const bidResponse = spec.interpretResponse(serverResponseWithOutstream, bidRequestWithOutstream)[0]; - expect(bidResponse).to.have.any.keys('outstream', 'renderer', 'mediaType'); + expect(bidResponse).to.have.any.keys('renderer', 'mediaType'); expect(bidResponse.renderer).to.be.a('object'); - expect(bidResponse.renderer.url).to.equal(RENDERER_URL); - expect(bidResponse.renderer.config.bvwUrl).to.be.ok; - expect(bidResponse.renderer.config.impUrl).to.be.ok; + expect(bidResponse.renderer.url).to.equal(defaultRendererUrl); expect(bidResponse.renderer.loaded).to.not.be.ok; expect(bidResponse.width).to.equal(300); expect(bidResponse.height).to.equal(250); expect(bidResponse.vastUrl).to.match(/^data:text\/xml;/) }); - it('should execute Adagio outstreamPlayer if defined', function() { - window.ADAGIO.outstreamPlayer = sinon.stub(); + it('should execute Blue Billywig VAST Renderer bootstrap if defined', function() { + window.bluebillywig = { + renderers: [{ bootstrap: sinon.stub(), _id: 'adagio-renderer' }] + }; + + const bidResponse = spec.interpretResponse(serverResponseWithOutstream, bidRequestWithOutstream)[0]; + executeRenderer(bidResponse.renderer, bidResponse) + sinon.assert.calledOnce(window.bluebillywig.renderers[0].bootstrap); + + delete window.bluebillywig; + }); + + it('Should logError if response does not have a vastXml or vastUrl', function() { + utilsMock.expects('logError').withExactArgs('Adagio: no vastXml or vastUrl on bid').once(); + + const localServerResponseWithOutstream = utils.deepClone(serverResponse); + localServerResponseWithOutstream.body.bids[0].mediaType = 'video'; + + const bidResponse = spec.interpretResponse(localServerResponseWithOutstream, bidRequestWithOutstream)[0]; + executeRenderer(bidResponse.renderer, bidResponse) + + utilsMock.verify(); + }) + + it('should logError if Blue Billywig API is not defined', function() { + utilsMock.expects('logError').withExactArgs('Adagio: no BlueBillywig renderers found!').once(); + const bidResponse = spec.interpretResponse(serverResponseWithOutstream, bidRequestWithOutstream)[0]; executeRenderer(bidResponse.renderer, bidResponse) - sinon.assert.calledOnce(window.ADAGIO.outstreamPlayer); - delete window.ADAGIO.outstreamPlayer; + + utilsMock.verify(); }); - it('should logError if Adagio outstreamPlayer is not defined', function() { + it('should logError if correct renderer is not defined', function() { + window.bluebillywig = { renderers: [ { _id: 'adagio-another_renderer' } ] }; + + utilsMock.expects('logError').withExactArgs('Adagio: couldn\'t find a renderer with ID adagio-renderer').once(); + const bidResponse = spec.interpretResponse(serverResponseWithOutstream, bidRequestWithOutstream)[0]; executeRenderer(bidResponse.renderer, bidResponse) - utilsMock.expects('logError').withExactArgs('Adagio: Adagio outstream player is not defined').once(); + + delete window.bluebillywig; + utilsMock.verify(); }); }); @@ -1147,14 +1293,6 @@ describe('Adagio bid adapter', () => { describe('transformBidParams', function() { it('Compute additional params in s2s mode', function() { - GlobalExchange.prepareExchangeData('{}'); - - sandbox.stub(window.top.document, 'getElementById').returns( - fixtures.getElementById() - ); - sandbox.stub(window.top, 'getComputedStyle').returns({ display: 'block' }); - sandbox.stub(utils, 'inIframe').returns(false); - const adUnit = { code: 'adunit-code', params: { @@ -1175,22 +1313,8 @@ describe('Adagio bid adapter', () => { } }).withParams().build(); - const params = spec.transformBidParams({ organizationId: '1000' }, true, adUnit, [{ bidderCode: 'adagio', auctionId: bid01.auctionId, bids: [bid01] }]); - - expect(params.organizationId).to.exist; - expect(params.auctionId).to.exist; - expect(params.playerName).to.exist; - expect(params.playerName).to.equal('other'); - expect(params.features).to.exist; - expect(params.features.page_dimensions).to.exist; - expect(params.features.adunit_position).to.exist; - expect(params.features.dom_loading).to.exist; - expect(params.features.print_number).to.exist; - expect(params.features.user_timestamp).to.exist; - expect(params.placement).to.exist; - expect(params.adUnitElementId).to.exist; - expect(params.site).to.exist; - expect(params.data.session).to.exist; + const params = spec.transformBidParams({ param01: 'test' }, true, adUnit, [{ bidderCode: 'adagio', auctionId: bid01.auctionId, bids: [bid01] }]); + expect(params.param01).eq('test'); }); }); @@ -1377,31 +1501,6 @@ describe('Adagio bid adapter', () => { }); }); - describe.skip('optional params auto detection', function() { - it('should auto detect adUnitElementId when GPT is used', function() { - sandbox.stub(utils, 'getGptSlotInfoForAdUnitCode').withArgs('banner').returns({divId: 'gpt-banner'}); - expect(adagio.autoDetectAdUnitElementId('banner')).to.eq('gpt-banner'); - }); - }); - - describe.skip('print number handling', function() { - it('should return 1 if no adunit-code found. This means it is the first auction', function() { - sandbox.stub(adagio, 'getPageviewId').returns('abc-def'); - expect(adagio.computePrintNumber('adunit-code')).to.eql(1); - }); - - it('should increment the adunit print number when the adunit-code has already been used for an other auction', function() { - sandbox.stub(adagio, 'getPageviewId').returns('abc-def'); - - window.top.ADAGIO.adUnits['adunit-code'] = { - pageviewId: 'abc-def', - printNumber: 1, - }; - - expect(adagio.computePrintNumber('adunit-code')).to.eql(2); - }); - }); - describe('site information using refererDetection or window.top', function() { it('should returns domain, page and window.referrer in a window.top context', function() { const bidderRequest = new BidderRequestBuilder({ diff --git a/test/spec/modules/adbookpspBidAdapter_spec.js b/test/spec/modules/adbookpspBidAdapter_spec.js index f7e401fefbd..3f26cd7749f 100755 --- a/test/spec/modules/adbookpspBidAdapter_spec.js +++ b/test/spec/modules/adbookpspBidAdapter_spec.js @@ -960,6 +960,11 @@ const bidderRequest = { consentString: 'gdprConsentString', }, uspConsent: 'uspConsentString', + ortb2: { + source: { + tid: 'aaccee333311' + } + } }; const bannerBid = { diff --git a/test/spec/modules/adfBidAdapter_spec.js b/test/spec/modules/adfBidAdapter_spec.js index eb560bb1bae..88f595b6c00 100644 --- a/test/spec/modules/adfBidAdapter_spec.js +++ b/test/spec/modules/adfBidAdapter_spec.js @@ -171,7 +171,10 @@ describe('Adf adapter', function () { bidId: 'bidId', params: { siteId: 'siteId' }, }]; - let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' }, auctionId: 'tid' }).data); + let request = JSON.parse(spec.buildRequests(validBidRequests, { + refererInfo: {page: 'page'}, + ortb2: {source: {tid: 'tid'}} + }).data); assert.equal(request.source.tid, 'tid'); assert.equal(request.source.fd, 1); @@ -481,6 +484,17 @@ describe('Adf adapter', function () { nativeParams: { title: { required: true, len: 140 } }, + nativeOrtbRequest: { + assets: [ + { + required: 1, + id: 0, + title: { + len: 140 + } + } + ] + }, mediaTypes: { banner: { sizes: [[100, 100], [200, 300]] @@ -547,6 +561,57 @@ describe('Adf adapter', function () { describe('native', function () { describe('assets', function () { + it('should use nativeOrtbRequest instead of nativeParams or mediaTypes', function () { + let validBidRequests = [{ + bidId: 'bidId', + params: { mid: 1000 }, + nativeParams: { + title: { required: true, len: 200 }, + image: { required: true, sizes: [150, 150] }, + icon: { required: false, sizes: [150, 150] }, + body: { required: false, len: 1140 }, + sponsoredBy: { required: true }, + cta: { required: false }, + clickUrl: { required: false }, + ortb: { + ver: '1.2', + assets: [] + } + }, + mediaTypes: { + native: { + title: { required: true, len: 140 }, + image: { required: true, sizes: [150, 50] }, + icon: { required: false, sizes: [50, 50] }, + body: { required: false, len: 140 }, + sponsoredBy: { required: true }, + cta: { required: false }, + clickUrl: { required: false } + } + }, + nativeOrtbRequest: { + assets: [ + { required: 1, title: { len: 200 } }, + { required: 1, img: { type: 3, w: 170, h: 70 } }, + { required: 0, img: { type: 1, w: 70, h: 70 } }, + { required: 0, data: { type: 2, len: 150 } }, + { required: 1, data: { type: 1 } }, + { required: 0, data: { type: 12 } }, + ] + } + }]; + + let assets = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' } }).data).imp[0].native.request.assets; + assert.ok(assets[0].title); + assert.equal(assets[0].title.len, 200); + assert.deepEqual(assets[1].img, { type: 3, w: 170, h: 70 }); + assert.deepEqual(assets[2].img, { type: 1, w: 70, h: 70 }); + assert.deepEqual(assets[3].data, { type: 2, len: 150 }); + assert.deepEqual(assets[4].data, { type: 1 }); + assert.deepEqual(assets[5].data, { type: 12 }); + assert.ok(!assets[6]); + }); + it('should set correct asset id', function () { let validBidRequests = [{ bidId: 'bidId', @@ -555,14 +620,46 @@ describe('Adf adapter', function () { title: { required: true, len: 140 }, image: { required: false, wmin: 836, hmin: 627, w: 325, h: 300, mimes: ['image/jpg', 'image/gif'] }, body: { len: 140 } + }, + nativeOrtbRequest: { + assets: [ + { + id: 0, + required: 1, + title: { + len: 140 + } + }, + { + id: 1, + required: 0, + img: { + type: 3, + wmin: 836, + hmin: 627, + w: 325, + h: 300, + mimes: [ 'image/jpg', 'image/gif' ] + } + }, + { + id: 2, + data: { + type: 2, + len: 140 + } + } + ] } }]; + let assets = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' } }).data).imp[0].native.request.assets; assert.equal(assets[0].id, 0); - assert.equal(assets[1].id, 3); - assert.equal(assets[2].id, 4); + assert.equal(assets[1].id, 1); + assert.equal(assets[2].id, 2); }); + it('should add required key if it is necessary', function () { let validBidRequests = [{ bidId: 'bidId', @@ -572,9 +669,16 @@ describe('Adf adapter', function () { image: { required: false, wmin: 836, hmin: 627, w: 325, h: 300, mimes: ['image/jpg', 'image/gif'] }, body: { len: 140 }, sponsoredBy: { required: true, len: 140 } + }, + nativeOrtbRequest: { + assets: [ + { required: 1, title: { len: 140 } }, + { required: 0, img: { type: 3, wmin: 836, hmin: 627, w: 325, h: 300, mimes: ['image/jpg', 'image/gif'] } }, + { data: { type: 2, len: 140 } }, + { required: 1, data: { type: 1, len: 140 } } + ] } }]; - let assets = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' } }).data).imp[0].native.request.assets; assert.equal(assets[0].required, 1); @@ -593,8 +697,17 @@ describe('Adf adapter', function () { icon: { required: false, sizes: [50, 50] }, body: { required: false, len: 140 }, sponsoredBy: { required: true }, - cta: { required: false }, - clickUrl: { required: false } + cta: { required: false } + }, + nativeOrtbRequest: { + assets: [ + { required: 1, title: { len: 140 } }, + { required: 1, img: { type: 3, w: 150, h: 50 } }, + { required: 0, img: { type: 1, w: 50, h: 50 } }, + { required: 0, data: { type: 2, len: 140 } }, + { required: 1, data: { type: 1 } }, + { required: 0, data: { type: 12 } }, + ] } }]; @@ -609,25 +722,6 @@ describe('Adf adapter', function () { assert.ok(!assets[6]); }); - describe('icon/image sizing', function () { - it('should flatten sizes and utilise first pair', function () { - const validBidRequests = [{ - bidId: 'bidId', - params: { mid: 1000 }, - nativeParams: { - image: { - sizes: [[200, 300], [100, 200]] - }, - } - }]; - - let assets = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' } }).data).imp[0].native.request.assets; - assert.ok(assets[0].img); - assert.equal(assets[0].img.w, 200); - assert.equal(assets[0].img.h, 300); - }); - }); - it('should utilise aspect_ratios', function () { const validBidRequests = [{ bidId: 'bidId', @@ -647,6 +741,12 @@ describe('Adf adapter', function () { ratio_width: 2 }] } + }, + nativeOrtbRequest: { + assets: [ + { img: { type: 3, wmin: 100, ext: { aspectratios: ['1:3'] } } }, + { img: { type: 1, wmin: 10, ext: { aspectratios: ['2:5'] } } } + ] } }]; @@ -671,6 +771,14 @@ describe('Adf adapter', function () { icon: { aspect_ratios: [] } + }, + nativeOrtbRequest: { + request: { + assets: [ + { img: {} }, + { img: {} } + ] + } } }]; @@ -689,13 +797,18 @@ describe('Adf adapter', function () { ratio_width: 1 }] } + }, + nativeOrtbRequest: { + assets: [ + { img: { type: 3, ext: { aspectratios: ['3:1'] } } } + ] } }]; let assets = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' } }).data).imp[0].native.request.assets; assert.ok(assets[0].img); - assert.equal(assets[0].img.wmin, 0); - assert.equal(assets[0].img.hmin, 0); + assert.ok(!assets[0].img.wmin); + assert.ok(!assets[0].img.hmin); assert.ok(!assets[1]); }); }); @@ -717,7 +830,7 @@ describe('Adf adapter', function () { let serverResponse = { body: { seatbid: [{ - bid: [{impid: '1', native: {ver: '1.1', link: { url: 'link' }, assets: [{id: 1, title: {text: 'Asset title text'}}]}}] + bid: [{impid: '1', native: {ver: '1.1', link: { url: 'link' }, assets: [{id: 0, title: {text: 'Asset title text'}}]}}] }, { bid: [{impid: '2', native: {ver: '1.1', link: { url: 'link' }, assets: [{id: 1, data: {value: 'Asset title text'}}]}}] }] @@ -729,19 +842,23 @@ describe('Adf adapter', function () { { bidId: 'bidId1', params: { mid: 1000 }, - nativeParams: { - title: { required: true, len: 140 }, - image: { required: false, wmin: 836, hmin: 627, w: 325, h: 300, mimes: ['image/jpg', 'image/gif'] }, - body: { len: 140 } + nativeOrtbRequest: { + assets: [ + { id: 0, required: 1, title: { len: 140 } }, + { id: 1, required: 0, img: { type: 3, wmin: 836, hmin: 627, ext: { aspectratios: ['6:5'] } } }, + { id: 2, required: 0, data: { type: 2 } } + ] } }, { bidId: 'bidId2', params: { mid: 1000 }, - nativeParams: { - title: { required: true, len: 140 }, - image: { required: false, wmin: 836, hmin: 627, w: 325, h: 300, mimes: ['image/jpg', 'image/gif'] }, - body: { len: 140 } + nativeOrtbRequest: { + assets: [ + { id: 0, required: 1, title: { len: 140 } }, + { id: 1, required: 0, img: { type: 3, wmin: 836, hmin: 627, ext: { aspectratios: ['6:5'] } } }, + { id: 2, required: 0, data: { type: 2 } } + ] } } ] @@ -756,11 +873,11 @@ describe('Adf adapter', function () { body: { seatbid: [{ bid: [ - {impid: '1', native: {ver: '1.1', link: { url: 'link1' }, assets: [{id: 1, title: {text: 'Asset title text'}}]}}, + {impid: '1', native: {ver: '1.1', link: { url: 'link1' }, assets: [{id: 0, title: {text: 'Asset title text'}}]}}, {impid: '4', native: {ver: '1.1', link: { url: 'link4' }, assets: [{id: 1, title: {text: 'Asset title text'}}]}} ] }, { - bid: [{impid: '2', native: {ver: '1.1', link: { url: 'link2' }, assets: [{id: 1, data: {value: 'Asset title text'}}]}}] + bid: [{impid: '2', native: {ver: '1.1', link: { url: 'link2' }, assets: [{id: 0, data: {value: 'Asset title text'}}]}}] }] } }; @@ -770,45 +887,53 @@ describe('Adf adapter', function () { { bidId: 'bidId1', params: { mid: 1000 }, - nativeParams: { - title: { required: true, len: 140 }, - image: { required: false, wmin: 836, hmin: 627, w: 325, h: 300, mimes: ['image/jpg', 'image/gif'] }, - body: { len: 140 } + nativeOrtbRequest: { + assets: [ + { id: 0, required: 1, title: { len: 140 } }, + { id: 1, required: 0, img: { type: 3, wmin: 836, hmin: 627, ext: { aspectratios: ['6:5'] } } }, + { id: 2, required: 0, data: { type: 2 } } + ] } }, { bidId: 'bidId2', params: { mid: 1000 }, - nativeParams: { - title: { required: true, len: 140 }, - image: { required: false, wmin: 836, hmin: 627, w: 325, h: 300, mimes: ['image/jpg', 'image/gif'] }, - body: { len: 140 } + nativeOrtbRequest: { + assets: [ + { id: 0, required: 1, title: { len: 140 } }, + { id: 1, required: 0, img: { type: 3, wmin: 836, hmin: 627, ext: { aspectratios: ['6:5'] } } }, + { id: 2, required: 0, data: { type: 2 } } + ] } }, { bidId: 'bidId3', params: { mid: 1000 }, - nativeParams: { - title: { required: true, len: 140 }, - image: { required: false, wmin: 836, hmin: 627, w: 325, h: 300, mimes: ['image/jpg', 'image/gif'] }, - body: { len: 140 } + nativeOrtbRequest: { + assets: [ + { id: 0, required: 1, title: { len: 140 } }, + { id: 1, required: 0, img: { type: 3, wmin: 836, hmin: 627, ext: { aspectratios: ['6:5'] } } }, + { id: 2, required: 0, data: { type: 2 } } + ] } }, { bidId: 'bidId4', params: { mid: 1000 }, - nativeParams: { - title: { required: true, len: 140 }, - image: { required: false, wmin: 836, hmin: 627, w: 325, h: 300, mimes: ['image/jpg', 'image/gif'] }, - body: { len: 140 } + nativeOrtbRequest: { + assets: [ + { id: 0, required: 1, title: { len: 140 } }, + { id: 1, required: 0, img: { type: 3, wmin: 836, hmin: 627, ext: { aspectratios: ['6:5'] } } }, + { id: 2, required: 0, data: { type: 2 } } + ] } } ] }; bids = spec.interpretResponse(serverResponse, bidRequest).map(bid => { - const { requestId, native: { clickUrl } } = bid; - return [ requestId, clickUrl ]; + const { requestId, native: { ortb: { link: { url } } } } = bid; + return [ requestId, url ]; }); assert.equal(bids.length, 3); @@ -850,6 +975,34 @@ describe('Adf adapter', function () { { bidId: 'bidId1', params: { mid: 1000 }, + nativeOrtbRequest: { + assets: [ + { + id: 0, + required: 1, + title: { + len: 140 + } + }, { + id: 1, + required: 1, + img: { + type: 3, + wmin: 836, + hmin: 627, + ext: { + aspectratios: ['6:5'] + } + } + }, { + id: 2, + required: 0, + data: { + type: 2 + } + } + ] + }, nativeParams: { title: { required: true, len: 140 }, image: { required: false, wmin: 836, hmin: 627, w: 325, h: 300, mimes: ['image/jpg', 'image/gif'] }, @@ -876,56 +1029,45 @@ describe('Adf adapter', function () { const bid = [ { impid: '1', - price: 93.1231, - crid: '12312312', native: { - assets: [ - { - data: null, - id: 0, - img: null, - required: 0, - title: {text: 'title', len: null}, - video: null - }, { - data: null, - id: 2, - img: {type: null, url: 'test.url.com/Files/58345/308185.jpg?bv=1', w: 30, h: 10}, - required: 0, - title: null, - video: null - }, { - data: null, - id: 3, - img: {type: null, url: 'test.url.com/Files/58345/308200.jpg?bv=1', w: 100, h: 100}, - required: 0, - title: null, - video: null - }, { - data: {type: null, len: null, value: 'body'}, - id: 4, - img: null, - required: 0, - title: null, - video: null - }, { - data: {type: null, len: null, value: 'cta'}, - id: 1, - img: null, - required: 0, - title: null, - video: null - }, { - data: {type: null, len: null, value: 'sponsoredBy'}, - id: 5, - img: null, - required: 0, - title: null, - video: null + ver: '1.1', + assets: [{ + id: 1, + required: 0, + title: { + text: 'FLS Native' + } + }, { + id: 3, + required: 0, + data: { + value: 'Adform' } - ], - link: { url: 'clickUrl', clicktrackers: ['clickTracker1', 'clickTracker2'] }, - imptrackers: ['imptrackers url1', 'imptrackers url2'], + }, { + id: 2, + required: 0, + data: { + value: 'Native banner. WOW.' + } + }, { + id: 4, + required: 0, + data: { + value: 'Oho' + } + }, { + id: 5, + required: 0, + img: { url: 'test.url.com/Files/58345/308185.jpg?bv=1', w: 30, h: 10 } + }, { + id: 0, + required: 0, + img: { url: 'test.url.com/Files/58345/308200.jpg?bv=1', w: 300, h: 300 } + }], + link: { + url: 'clickUrl', clicktrackers: [ 'clickTracker1', 'clickTracker2' ] + }, + imptrackers: ['imptracker url1', 'imptracker url2'], jstracker: 'jstracker' } } @@ -940,24 +1082,66 @@ describe('Adf adapter', function () { }; let bidRequest = { data: {}, - bids: [{ bidId: 'bidId1' }] + bids: [{ + bidId: 'bidId1', + nativeOrtbRequest: { + ver: '1.2', + assets: [{ + id: 0, + required: 1, + img: { + type: 3, + wmin: 200, + hmin: 166, + ext: { + aspectratios: ['6:5'] + } + } + }, { + id: 1, + required: 1, + title: { + len: 150 + } + }, { + id: 2, + required: 0, + data: { + type: 2 + } + }, { + id: 3, + required: 1, + data: { + type: 1 + } + }, { + id: 4, + required: 1, + data: { + type: 12 + } + }, { + id: 5, + required: 0, + img: { + type: 1, + wmin: 10, + hmin: 10, + ext: { + aspectratios: ['1:1'] + } + } + }] + }, + }] }; const result = spec.interpretResponse(serverResponse, bidRequest)[0].native; const native = bid[0].native; const assets = native.assets; - assert.deepEqual({ - clickUrl: native.link.url, - clickTrackers: native.link.clicktrackers, - impressionTrackers: native.imptrackers, - javascriptTrackers: [ native.jstracker ], - title: assets[0].title.text, - icon: {url: assets[1].img.url, width: assets[1].img.w, height: assets[1].img.h}, - image: {url: assets[2].img.url, width: assets[2].img.w, height: assets[2].img.h}, - body: assets[3].data.value, - cta: assets[4].data.value, - sponsoredBy: assets[5].data.value - }, result); + + assert.deepEqual(result, {ortb: native}); }); it('should return empty when there is no bids in response', function () { const serverResponse = { diff --git a/test/spec/modules/adgenerationBidAdapter_spec.js b/test/spec/modules/adgenerationBidAdapter_spec.js index 8583c226e50..de8463731e0 100644 --- a/test/spec/modules/adgenerationBidAdapter_spec.js +++ b/test/spec/modules/adgenerationBidAdapter_spec.js @@ -117,7 +117,7 @@ describe('AdgenerationAdapter', function () { criteoId: 'criteo-id-test-1234567890' } }, - { // bannerWithAdgextId5Id + { // bannerWithAdgextIds bidder: 'adg', params: { id: '58278', // banner @@ -134,8 +134,48 @@ describe('AdgenerationAdapter', function () { linkType: 2 }, uid: 'id5-id-test-1234567890' + }, + imuid: 'i.KrAH6ZAZTJOnH5S4N2sogA', + uid2: {id: 'AgAAAAVacu1uAxgAxH+HJ8+nWlS2H4uVqr6i+HBDCNREHD8WKsio/x7D8xXFuq1cJycUU86yXfTH9Xe/4C8KkH+7UCiU7uQxhyD7Qxnv251pEs6K8oK+BPLYR+8BLY/sJKesa/koKwx1FHgUzIBum582tSy2Oo+7C6wYUaaV4QcLr/4LPA='}, + }, + ortb2Imp: {ext: {gpid: '/1111/homepage#300x250'}}, + ortb2: { + site: { + domain: 'localhost:9999', + publisher: {'domain': 'localhost:9999'}, + page: 'http://localhost:9999/integrationExamples/gpt/hello_world.html', + ref: 'http://localhost:9999/integrationExamples/gpt/hello_world.html' + }, + device: { + w: 570, + h: 969, + dnt: 0, + ua: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML,like Gecko)Chrome / 112.0.0.0Safari / 537.36', + language: 'ja', + sua: { + source: 2, + platform: { + brand: 'macOS' + }, + browsers: [ + { + brand: 'Chromium', + version: ['112'] + }, + { + brand: 'Google Chrome', + version: ['112'] + }, + { + brand: 'Not:A-Brand', + version: ['99'] + } + ], + mobile: 0 + } } - } + }, + schain: {ver: '1.0', complete: 1, nodes: [{asi: 'indirectseller.com', sid: '00001', hp: 1}]} } ]; const bidderRequest = { @@ -144,12 +184,12 @@ describe('AdgenerationAdapter', function () { } }; const data = { - banner: `posall=SSPLOC&id=58278&sdktype=0&hb=true&t=json3&sizes=300x250%2C320x100¤cy=JPY&pbver=${prebid.version}&sdkname=prebidjs&adapterver=1.4.0&imark=1&tp=https%3A%2F%2Fexample.com`, - bannerUSD: `posall=SSPLOC&id=58278&sdktype=0&hb=true&t=json3&sizes=300x250%2C320x100¤cy=USD&pbver=${prebid.version}&sdkname=prebidjs&adapterver=1.4.0&imark=1&tp=https%3A%2F%2Fexample.com`, - native: `posall=SSPLOC&id=58278&sdktype=0&hb=true&t=json3&sizes=1x1¤cy=JPY&pbver=${prebid.version}&sdkname=prebidjs&adapterver=1.4.0&tp=https%3A%2F%2Fexample.com`, - bannerWithHyperId: `posall=SSPLOC&id=58278&sdktype=0&hb=true&t=json3&sizes=320x100¤cy=JPY&pbver=${prebid.version}&sdkname=prebidjs&adapterver=1.4.0&imark=1&tp=https%3A%2F%2Fexample.com&hyper_id=novatiqId`, - bannerWithAdgextCriteoId: `posall=SSPLOC&id=58278&sdktype=0&hb=true&t=json3&sizes=320x100¤cy=JPY&pbver=${prebid.version}&sdkname=prebidjs&adapterver=1.4.0&adgext_criteo_id=criteo-id-test-1234567890&imark=1&tp=https%3A%2F%2Fexample.com`, - bannerWithAdgextId5Id: `posall=SSPLOC&id=58278&sdktype=0&hb=true&t=json3&sizes=320x100¤cy=JPY&pbver=${prebid.version}&sdkname=prebidjs&adapterver=1.4.0&adgext_id5_id=id5-id-test-1234567890&adgext_id5_id_link_type=2&imark=1&tp=https%3A%2F%2Fexample.com`, + banner: `posall=SSPLOC&id=58278&sdktype=0&hb=true&t=json3&sizes=300x250%2C320x100¤cy=JPY&pbver=${prebid.version}&sdkname=prebidjs&adapterver=1.6.0&imark=1&tp=https%3A%2F%2Fexample.com`, + bannerUSD: `posall=SSPLOC&id=58278&sdktype=0&hb=true&t=json3&sizes=300x250%2C320x100¤cy=USD&pbver=${prebid.version}&sdkname=prebidjs&adapterver=1.6.0&imark=1&tp=https%3A%2F%2Fexample.com`, + native: `posall=SSPLOC&id=58278&sdktype=0&hb=true&t=json3&sizes=1x1¤cy=JPY&pbver=${prebid.version}&sdkname=prebidjs&adapterver=1.6.0&tp=https%3A%2F%2Fexample.com`, + bannerWithHyperId: `posall=SSPLOC&id=58278&sdktype=0&hb=true&t=json3&sizes=320x100¤cy=JPY&pbver=${prebid.version}&sdkname=prebidjs&adapterver=1.6.0&imark=1&tp=https%3A%2F%2Fexample.com&hyper_id=novatiqId`, + bannerWithAdgextCriteoId: `posall=SSPLOC&id=58278&sdktype=0&hb=true&t=json3&sizes=320x100¤cy=JPY&pbver=${prebid.version}&sdkname=prebidjs&adapterver=1.6.0&adgext_criteo_id=criteo-id-test-1234567890&imark=1&tp=https%3A%2F%2Fexample.com`, + bannerWithAdgextIds: `posall=SSPLOC&id=58278&sdktype=0&hb=true&t=json3&sizes=320x100¤cy=JPY&pbver=${prebid.version}&sdkname=prebidjs&adapterver=1.6.0&adgext_id5_id=id5-id-test-1234567890&adgext_id5_id_link_type=2&adgext_imuid=i.KrAH6ZAZTJOnH5S4N2sogA&adgext_uid2=%5Bobject%20Object%5D&gpid=%252F1111%252Fhomepage%2523300x250&uach=%7B%22source%22%3A2%2C%22platform%22%3A%7B%22brand%22%3A%22macOS%22%7D%2C%22browsers%22%3A%5B%7B%22brand%22%3A%22Chromium%22%2C%22version%22%3A%5B%22112%22%5D%7D%2C%7B%22brand%22%3A%22Google%20Chrome%22%2C%22version%22%3A%5B%22112%22%5D%7D%2C%7B%22brand%22%3A%22Not%3AA-Brand%22%2C%22version%22%3A%5B%2299%22%5D%7D%5D%2C%22mobile%22%3A0%7D&schain=%257B%2522ver%2522%253A%25221.0%2522%252C%2522complete%2522%253A1%252C%2522nodes%2522%253A%255B%257B%2522asi%2522%253A%2522indirectseller.com%2522%252C%2522sid%2522%253A%252200001%2522%252C%2522hp%2522%253A1%257D%255D%257D&imark=1&tp=https%3A%2F%2Fexample.com`, }; it('sends bid request to ENDPOINT via GET', function () { const request = spec.buildRequests(bidRequests, bidderRequest)[0]; @@ -176,12 +216,12 @@ describe('AdgenerationAdapter', function () { it('should attache params to the bannerWithHyperId request', function () { const defaultUA = window.navigator.userAgent; - window.navigator.__defineGetter__('userAgent', function() { + window.navigator.__defineGetter__('userAgent', function () { return 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 Mobile/15E148 Safari/604.1'; }); const request = spec.buildRequests(bidRequests, bidderRequest)[2]; - window.navigator.__defineGetter__('userAgent', function() { + window.navigator.__defineGetter__('userAgent', function () { return defaultUA; }); expect(request.data).to.equal(data.bannerWithHyperId); @@ -192,9 +232,9 @@ describe('AdgenerationAdapter', function () { expect(request.data).to.equal(data.bannerWithAdgextCriteoId); }); - it('should attache params to the bannerWithAdgextId5Id request', function () { + it('should attache params to the bannerWithAdgextIds request', function () { const request = spec.buildRequests(bidRequests, bidderRequest)[4]; - expect(request.data).to.equal(data.bannerWithAdgextId5Id); + expect(request.data).to.equal(data.bannerWithAdgextIds); }); it('allows setConfig to set bidder currency for JPY', function () { diff --git a/test/spec/modules/adhashBidAdapter_spec.js b/test/spec/modules/adhashBidAdapter_spec.js index 4ea525c59d5..cc643d6d2ab 100644 --- a/test/spec/modules/adhashBidAdapter_spec.js +++ b/test/spec/modules/adhashBidAdapter_spec.js @@ -74,7 +74,12 @@ describe('adhashBidAdapter', function () { publisherId: '0xc3b09b27e9c6ef73957901aa729b9e69e5bbfbfb' }, sizes: [[300, 250]], - adUnitCode: 'adUnitCode' + adUnitCode: 'adUnitCode', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + } }; it('should build the request correctly', function () { const result = spec.buildRequests( @@ -83,7 +88,7 @@ describe('adhashBidAdapter', function () { ); expect(result.length).to.equal(1); expect(result[0].method).to.equal('POST'); - expect(result[0].url).to.equal('https://bidder.adhash.com/rtb?version=3.2&prebid=true&publisher=0xc3b09b27e9c6ef73957901aa729b9e69e5bbfbfb'); + expect(result[0].url).to.equal('https://bidder.adhash.com/rtb?version=3.6&prebid=true&publisher=0xc3b09b27e9c6ef73957901aa729b9e69e5bbfbfb'); expect(result[0].bidRequest).to.equal(bidRequest); expect(result[0].data).to.have.property('timezone'); expect(result[0].data).to.have.property('location'); @@ -99,7 +104,7 @@ describe('adhashBidAdapter', function () { const result = spec.buildRequests([ bidRequest ], { gdprConsent: { gdprApplies: true, consentString: 'example' } }); expect(result.length).to.equal(1); expect(result[0].method).to.equal('POST'); - expect(result[0].url).to.equal('https://bidder.adhash.com/rtb?version=3.2&prebid=true&publisher=0xc3b09b27e9c6ef73957901aa729b9e69e5bbfbfb'); + expect(result[0].url).to.equal('https://bidder.adhash.com/rtb?version=3.6&prebid=true&publisher=0xc3b09b27e9c6ef73957901aa729b9e69e5bbfbfb'); expect(result[0].bidRequest).to.equal(bidRequest); expect(result[0].data).to.have.property('timezone'); expect(result[0].data).to.have.property('location'); @@ -122,6 +127,11 @@ describe('adhashBidAdapter', function () { sizes: [[300, 250]], params: { platformURL: 'https://adhash.com/p/struma/' + }, + mediaTypes: { + banner: { + sizes: [[300, 250]] + } } } }; @@ -142,6 +152,8 @@ describe('adhashBidAdapter', function () { ['дума', 'full', 1], ['старт', 'starts', 1], ['край', 'ends', 1], + ['onq jbeq', 'partial', 1], + ['dhrra qvrf', 'combo', 2], ], maxScore: 2 } @@ -186,6 +198,13 @@ describe('adhashBidAdapter', function () { expect(spec.interpretResponse(serverResponse, request).length).to.equal(0); }); + it('should return empty array when there are bad words (partial, compound phrase)', function () { + bodyStub = sinon.stub(window.top.document.body, 'innerText').get(function() { + return 'example text partialbad wordb bad wordb example bad wordbtext' + ' word'.repeat(994); + }); + expect(spec.interpretResponse(serverResponse, request).length).to.equal(0); + }); + it('should return empty array when there are bad words (starts)', function () { bodyStub = sinon.stub(window.top.document.body, 'innerText').get(function() { return 'example text startsWith starts text startsAgain' + ' word'.repeat(994); @@ -214,6 +233,13 @@ describe('adhashBidAdapter', function () { expect(spec.interpretResponse(serverResponse, request).length).to.equal(0); }); + it('should return empty array when there are bad words (combo)', function () { + bodyStub = sinon.stub(window.top.document.body, 'innerText').get(function() { + return 'queen of england dies, the queen dies' + ' word'.repeat(993); + }); + expect(spec.interpretResponse(serverResponse, request).length).to.equal(0); + }); + it('should return empty array when there are bad words (regexp)', function () { bodyStub = sinon.stub(window.top.document.body, 'innerText').get(function() { return 'example text xxxayyy zzxxxAyyyzz text xxxbyyy' + ' word'.repeat(994); @@ -267,5 +293,45 @@ describe('adhashBidAdapter', function () { it('should return empty array when something is not right', function () { expect(spec.interpretResponse(null, request).length).to.equal(0); }); + + it('should interpret the video response correctly', function () { + const result = spec.interpretResponse({ + body: { + creatives: [{ costEUR: 1.234, vastURL: 'https://example.com/vast' }], + advertiserDomains: 'adhash.com' + } + }, { + data: { some: 'data' }, + bidRequest: { + bidId: '12345678901234', + adUnitCode: 'adunit-code', + sizes: [[300, 250]], + params: { + platformURL: 'https://adhash.com/p/struma/' + }, + mediaTypes: { + video: { + context: 'instream', + playerSize: [300, 250], + mimes: ['video/mp4'], + protocols: [1, 2, 3, 4, 5, 6, 7, 8], + playbackmethod: [2], + skip: 1 + } + } + } + }); + expect(result.length).to.equal(1); + expect(result[0].requestId).to.equal('12345678901234'); + expect(result[0].cpm).to.equal(1.234); + expect(result[0].width).to.equal(300); + expect(result[0].height).to.equal(250); + expect(result[0].creativeId).to.equal('adunit-code'); + expect(result[0].netRevenue).to.equal(true); + expect(result[0].currency).to.equal('EUR'); + expect(result[0].ttl).to.equal(60); + expect(result[0].meta.advertiserDomains).to.eql(['adhash.com']); + expect(result[0].vastUrl).to.equal('https://example.com/vast'); + }); }); }); diff --git a/test/spec/modules/adkernelAdnBidAdapter_spec.js b/test/spec/modules/adkernelAdnBidAdapter_spec.js index 67c168f5063..ff7ed9f145d 100644 --- a/test/spec/modules/adkernelAdnBidAdapter_spec.js +++ b/test/spec/modules/adkernelAdnBidAdapter_spec.js @@ -220,7 +220,7 @@ describe('AdkernelAdn adapter', function () { } describe('banner request building', function () { - let [_, tagRequests] = buildRequest([bid1_pub1]); + let [_, tagRequests] = buildRequest([bid1_pub1], {ortb2: {source: {tid: 'mock-tid'}}}); let tagRequest = tagRequests[0]; it('should have request id', function () { @@ -374,7 +374,6 @@ describe('AdkernelAdn adapter', function () { it('should return fully-initialized bid-response', function () { let resp = responses[0]; - expect(resp).to.have.property('bidderCode', 'adkernelAdn'); expect(resp).to.have.property('requestId', '2c5e951baeeadd'); expect(resp).to.have.property('cpm', 5.0); expect(resp).to.have.property('width', 300); @@ -394,7 +393,6 @@ describe('AdkernelAdn adapter', function () { it('should return fully-initialized video bid-response', function () { let resp = responses[2]; - expect(resp).to.have.property('bidderCode', 'adkernelAdn'); expect(resp).to.have.property('requestId', '57d602ad1c9545'); expect(resp).to.have.property('cpm', 10.0); expect(resp).to.have.property('creativeId', '108_158802'); diff --git a/test/spec/modules/adlooxAdServerVideo_spec.js b/test/spec/modules/adlooxAdServerVideo_spec.js index 170982a51bd..a071c6bbe3f 100644 --- a/test/spec/modules/adlooxAdServerVideo_spec.js +++ b/test/spec/modules/adlooxAdServerVideo_spec.js @@ -118,6 +118,16 @@ describe('Adloox Ad Server Video', function () { }); describe('buildVideoUrl', function () { + beforeEach(() => { + sinon.stub(URL, 'createObjectURL'); + sinon.stub(URL, 'revokeObjectURL'); + }); + + afterEach(() => { + URL.createObjectURL.restore() + URL.revokeObjectURL.restore() + }); + describe('invalid arguments', function () { it('should require callback', function (done) { const ret = buildVideoUrl(); @@ -243,42 +253,16 @@ describe('Adloox Ad Server Video', function () { url: wrapperUrl, bid: BID }; + + URL.createObjectURL.callsFake(() => 'mock-blob-url'); + const ret = buildVideoUrl(options, function (url) { expect(url.substr(0, options.url_vast.length)).is.equal(options.url_vast); - - const match = url.match(/[?&]vast=(blob%3A[^&]+)/); - expect(match).is.not.null; - - const blob = decodeURIComponent(match[1]); - - const xfr = sandbox.useFakeXMLHttpRequest(); - xfr.useFilters = true; - xfr.addFilter(x => true); // there is no network traffic for Blob URLs here - - ajax(blob, { - success: (responseText, q) => { - expect(q.status).is.equal(200); - expect(q.getResponseHeader('content-type')).is.equal(vastHeaders['content-type']); - - clock.runAll(); - - ajax(blob, { - success: (responseText, q) => { - xfr.useFilters = false; // .restore() does not really work - if (q.status == 0) return done(); - done(new Error('Blob should have expired')); - }, - error: (statusText, q) => { - xfr.useFilters = false; - done(); - } - }); - }, - error: (statusText, q) => { - xfr.useFilters = false; - done(new Error(statusText)); - } - }); + expect(url).to.match(/[?&]vast=mock-blob-url/); + sinon.assert.calledWith(URL.createObjectURL, sinon.match((val) => val.type === vastHeaders['content-type'])); + clock.runAll(); + sinon.assert.calledWith(URL.revokeObjectURL, 'mock-blob-url'); + done(); }); expect(ret).is.true; diff --git a/test/spec/modules/admanBidAdapter_spec.js b/test/spec/modules/admanBidAdapter_spec.js index feee8e39b45..a9413860072 100644 --- a/test/spec/modules/admanBidAdapter_spec.js +++ b/test/spec/modules/admanBidAdapter_spec.js @@ -106,8 +106,11 @@ describe('AdmanAdapter', function () { let placements = data['placements']; for (let i = 0; i < placements.length; i++) { let placement = placements[i]; - expect(placement).to.have.all.keys('placementId', 'eids', 'bidId', 'traffic', 'sizes', 'schain', 'bidFloor'); + expect(placement).to.have.all.keys('placementId', 'eids', 'bidId', 'traffic', 'sizes', 'schain', 'bidFloor', 'ext'); expect(placement.schain).to.be.an('object') + expect(placement.ext).to.be.an('object') + expect(placement.ext).to.have.key('tid') + expect(placement.ext.tid).to.equal(bidBanner.transactionId); expect(placement.placementId).to.be.a('number'); expect(placement.bidId).to.be.a('string'); expect(placement.traffic).to.be.a('string'); @@ -130,14 +133,16 @@ describe('AdmanAdapter', function () { let placements = data['placements']; for (let i = 0; i < placements.length; i++) { let placement = placements[i]; - expect(placement).to.have.all.keys('placementId', 'eids', 'bidId', 'traffic', 'sizes', 'schain', 'bidFloor', + expect(placement).to.have.all.keys('placementId', 'eids', 'bidId', 'traffic', 'schain', 'bidFloor', 'playerSize', 'minduration', 'maxduration', 'mimes', 'protocols', 'startdelay', 'placement', 'skip', - 'skipafter', 'minbitrate', 'maxbitrate', 'delivery', 'playbackmethod', 'api', 'linearity'); + 'skipafter', 'minbitrate', 'maxbitrate', 'delivery', 'playbackmethod', 'api', 'linearity', 'ext'); + expect(placement.ext).to.be.an('object') + expect(placement.ext).to.have.key('tid') + expect(placement.ext.tid).to.equal(bidBanner.transactionId); expect(placement.schain).to.be.an('object') expect(placement.placementId).to.be.a('number'); expect(placement.bidId).to.be.a('string'); expect(placement.traffic).to.be.a('string'); - expect(placement.sizes).to.be.an('array'); expect(placement.bidFloor).to.be.an('number'); } }); diff --git a/test/spec/modules/admaticBidAdapter_spec.js b/test/spec/modules/admaticBidAdapter_spec.js index 65a7b4111b7..1d2fb1e79cb 100644 --- a/test/spec/modules/admaticBidAdapter_spec.js +++ b/test/spec/modules/admaticBidAdapter_spec.js @@ -3,7 +3,7 @@ import {spec, storage} from 'modules/admaticBidAdapter.js'; import {newBidder} from 'src/adapters/bidderFactory.js'; import {getStorageManager} from 'src/storageManager'; -const ENDPOINT = 'https://layer.serve.admatic.com.tr/pb?bidder=admatic'; +const ENDPOINT = 'https://layer.serve.admatic.com.tr/pb'; describe('admaticBidAdapter', () => { const adapter = newBidder(spec); @@ -22,11 +22,13 @@ describe('admaticBidAdapter', () => { 'host': 'layer.serve.admatic.com.tr' }, 'adUnitCode': 'adunit-code', + 'mediaType': 'banner', 'sizes': [[300, 250], [300, 600]], 'bidId': '30b31c1838de1e', 'bidderRequestId': '22edbae2733bf6', 'auctionId': '1d1a030790a475', - 'creativeId': 'er2ee' + 'creativeId': 'er2ee', + 'ortb2': { 'badv': ['admatic.com.tr'] } }; it('should return true when required params found', function() { @@ -34,15 +36,11 @@ describe('admaticBidAdapter', () => { }); it('should return false when required params are not passed', function() { - let bid = Object.assign({}, bid); - delete bid.params; - - bid.params = { - 'networkId': 0, - 'host': 'layer.serve.admatic.com.tr' + let bid2 = {}; + bid2.params = { + 'someIncorrectParam': 0 }; - - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(bid2)).to.equal(false); }); }); @@ -54,6 +52,7 @@ describe('admaticBidAdapter', () => { 'networkId': 10433394, 'host': 'layer.serve.admatic.com.tr' }, + 'ortb2': { 'badv': ['admatic.com.tr'] }, 'mediaTypes': { 'banner': { 'sizes': [[300, 250], [728, 90]] @@ -98,6 +97,8 @@ describe('admaticBidAdapter', () => { 'h': 90 } ], + 'mediatype': {}, + 'type': 'banner', 'id': '2205da7a81846b', 'floors': { 'banner': { @@ -105,6 +106,49 @@ describe('admaticBidAdapter', () => { '728x90': { 'currency': 'USD', 'floor': 2 } } } + }, + { + 'size': [ + { + 'w': 338, + 'h': 280 + } + ], + 'type': 'video', + 'mediatype': { + 'context': 'instream', + 'mimes': [ + 'video/mp4' + ], + 'maxduration': 240, + 'api': [ + 1, + 2 + ], + 'playerSize': [ + [ + 338, + 280 + ] + ], + 'protocols': [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8 + ], + 'skip': 1, + 'playbackmethod': [ + 2 + ], + 'linearity': 1, + 'placement': 2 + }, + 'id': '45e86fc7ce7fc93' } ], 'ext': { @@ -118,6 +162,7 @@ describe('admaticBidAdapter', () => { 'networkId': 10433394, 'host': 'layer.serve.admatic.com.tr' }, + 'ortb2': { 'badv': ['admatic.com.tr'] }, 'mediaTypes': { 'banner': { 'sizes': [[300, 250], [728, 90]] @@ -163,12 +208,57 @@ describe('admaticBidAdapter', () => { } ], 'id': '2205da7a81846b', + 'mediatype': {}, + 'type': 'banner', 'floors': { 'banner': { '300x250': { 'currency': 'USD', 'floor': 1 }, '728x90': { 'currency': 'USD', 'floor': 2 } } } + }, + { + 'size': [ + { + 'w': 338, + 'h': 280 + } + ], + 'type': 'video', + 'mediatype': { + 'context': 'instream', + 'mimes': [ + 'video/mp4' + ], + 'maxduration': 240, + 'api': [ + 1, + 2 + ], + 'playerSize': [ + [ + 338, + 280 + ] + ], + 'protocols': [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8 + ], + 'skip': 1, + 'playbackmethod': [ + 2 + ], + 'linearity': 1, + 'placement': 2 + }, + 'id': '45e86fc7ce7fc93' } ], 'ext': { @@ -194,6 +284,7 @@ describe('admaticBidAdapter', () => { 'sizes': [[300, 250], [728, 90]] } }, + 'ortb2': { 'badv': ['admatic.com.tr'] }, getFloor: inputParams => { if (inputParams.mediaType === BANNER && inputParams.size[0] === 300 && inputParams.size[1] === 250) { return { @@ -217,6 +308,7 @@ describe('admaticBidAdapter', () => { 'networkId': 10433394, 'host': 'layer.serve.admatic.com.tr' }, + 'ortb2': { 'badv': ['admatic.com.tr'] }, 'adUnitCode': 'adunit-code', 'sizes': [[300, 250], [728, 90]], 'bidId': '30b31c1838de1e', @@ -249,9 +341,35 @@ describe('admaticBidAdapter', () => { 'width': 300, 'height': 250, 'price': 0.01, + 'type': 'banner', 'bidder': 'admatic', 'adomain': ['admatic.com.tr'], - 'party_tag': '
' + 'party_tag': '
', + 'iurl': 'https://www.admatic.com.tr' + }, + { + 'id': 2, + 'creative_id': '3741', + 'width': 300, + 'height': 250, + 'price': 0.01, + 'type': 'video', + 'bidder': 'admatic', + 'adomain': ['admatic.com.tr'], + 'party_tag': '', + 'iurl': 'https://www.admatic.com.tr' + }, + { + 'id': 3, + 'creative_id': '3741', + 'width': 300, + 'height': 250, + 'price': 0.01, + 'type': 'video', + 'bidder': 'admatic', + 'adomain': ['admatic.com.tr'], + 'party_tag': 'https://www.admatic.com.tr', + 'iurl': 'https://www.admatic.com.tr' } ], 'queryId': 'cdnbh24rlv0hhkpfpln0', @@ -265,13 +383,48 @@ describe('admaticBidAdapter', () => { width: 300, height: 250, currency: 'TRY', + mediaType: 'banner', netRevenue: true, ad: '
', creativeId: '374', meta: { advertiserDomains: ['admatic.com.tr'] }, - ttl: 360, + ttl: 60, + bidder: 'admatic' + }, + { + requestId: 2, + cpm: 0.01, + width: 300, + height: 250, + currency: 'TRY', + mediaType: 'video', + netRevenue: true, + vastImpUrl: 'https://www.admatic.com.tr', + vastXml: '', + creativeId: '3741', + meta: { + advertiserDomains: ['admatic.com.tr'] + }, + ttl: 60, + bidder: 'admatic' + }, + { + requestId: 3, + cpm: 0.01, + width: 300, + height: 250, + currency: 'TRY', + mediaType: 'video', + netRevenue: true, + vastImpUrl: 'https://www.admatic.com.tr', + vastXml: 'https://www.admatic.com.tr', + creativeId: '3741', + meta: { + advertiserDomains: ['admatic.com.tr'] + }, + ttl: 60, bidder: 'admatic' } ]; diff --git a/test/spec/modules/admixerBidAdapter_spec.js b/test/spec/modules/admixerBidAdapter_spec.js index 26caaf5b70f..8cf433460b7 100644 --- a/test/spec/modules/admixerBidAdapter_spec.js +++ b/test/spec/modules/admixerBidAdapter_spec.js @@ -100,33 +100,72 @@ describe('AdmixerAdapter', function () { }); }); - describe('buildRequestsAdmixerADX', function () { - let validRequest = [ - { - bidder: BIDDER_CODE_ADX, - params: { - zone: ZONE_ID, + describe('buildRequests URL check', function () { + const requestParamsFor = (bidder) => ({ + validRequest: [ + { + bidder: bidder, + params: { + zone: ZONE_ID, + }, + adUnitCode: 'adunit-code', + sizes: [ + [300, 250], + [300, 600], + ], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', }, - adUnitCode: 'adunit-code', - sizes: [ - [300, 250], - [300, 600], - ], - bidId: '30b31c1838de1e', - bidderRequestId: '22edbae2733bf6', - auctionId: '1d1a030790a475', - }, - ]; - let bidderRequest = { - bidderCode: BIDDER_CODE_ADX, - refererInfo: { - page: 'https://example.com', - }, - }; + ], + bidderRequest: { + bidderCode: bidder, + refererInfo: { + page: 'https://example.com', + }, + } + }) - it('sends bid request to ADX ENDPOINT', function () { - const request = spec.buildRequests(validRequest, bidderRequest); - expect(request.url).to.equal(ENDPOINT_URL_ADX); + it('build request for admixer', function () { + const requestParams = requestParamsFor('admixer'); + const request = spec.buildRequests(requestParams.validRequest, requestParams.bidderRequest); + expect(request.url).to.equal('https://inv-nets.admixer.net/prebid.1.2.aspx'); + expect(request.method).to.equal('POST'); + }); + it('build request for go2net', function () { + const requestParams = requestParamsFor('go2net'); + const request = spec.buildRequests(requestParams.validRequest, requestParams.bidderRequest); + expect(request.url).to.equal('https://ads.go2net.com.ua/prebid.1.2.aspx'); + expect(request.method).to.equal('POST'); + }); + it('build request for adblender', function () { + const requestParams = requestParamsFor('adblender'); + const request = spec.buildRequests(requestParams.validRequest, requestParams.bidderRequest); + expect(request.url).to.equal('https://inv-nets.admixer.net/prebid.1.2.aspx'); + expect(request.method).to.equal('POST'); + }); + it('build request for adsyield', function () { + const requestParams = requestParamsFor('adsyield'); + const request = spec.buildRequests(requestParams.validRequest, requestParams.bidderRequest); + expect(request.url).to.equal('https://ads.adsyield.com/prebid.1.2.aspx'); + expect(request.method).to.equal('POST'); + }); + it('build request for futureads', function () { + const requestParams = requestParamsFor('futureads'); + const request = spec.buildRequests(requestParams.validRequest, requestParams.bidderRequest); + expect(request.url).to.equal('https://ads.futureads.io/prebid.1.2.aspx'); + expect(request.method).to.equal('POST'); + }); + it('build request for smn', function () { + const requestParams = requestParamsFor('smn'); + const request = spec.buildRequests(requestParams.validRequest, requestParams.bidderRequest); + expect(request.url).to.equal('https://ads.smn.rs/prebid.1.2.aspx'); + expect(request.method).to.equal('POST'); + }); + it('build request for admixeradx', function () { + const requestParams = requestParamsFor('admixeradx'); + const request = spec.buildRequests(requestParams.validRequest, requestParams.bidderRequest); + expect(request.url).to.equal('https://inv-nets.admixer.net/adxprebid.1.2.aspx'); expect(request.method).to.equal('POST'); }); }); diff --git a/test/spec/modules/admixerIdSystem_spec.js b/test/spec/modules/admixerIdSystem_spec.js index 18107b780db..753b1e3c2d5 100644 --- a/test/spec/modules/admixerIdSystem_spec.js +++ b/test/spec/modules/admixerIdSystem_spec.js @@ -1,9 +1,6 @@ import {admixerIdSubmodule} from 'modules/admixerIdSystem.js'; import * as utils from 'src/utils.js'; import {server} from 'test/mocks/xhr.js'; -import {getStorageManager} from '../../../src/storageManager.js'; - -export const storage = getStorageManager(); const pid = '4D393FAC-B6BB-4E19-8396-0A4813607316'; const getIdParams = {params: {pid: pid}}; diff --git a/test/spec/modules/adnuntiusBidAdapter_spec.js b/test/spec/modules/adnuntiusBidAdapter_spec.js index b787a52d6f2..b1657ed1596 100644 --- a/test/spec/modules/adnuntiusBidAdapter_spec.js +++ b/test/spec/modules/adnuntiusBidAdapter_spec.js @@ -1,65 +1,82 @@ // import or require modules necessary for the test, e.g.: -import { expect } from 'chai'; // may prefer 'assert' in place of 'expect' -import { spec } from 'modules/adnuntiusBidAdapter.js'; -import { newBidder } from 'src/adapters/bidderFactory.js'; -import { config } from 'src/config.js'; +import {expect} from 'chai'; // may prefer 'assert' in place of 'expect' +import {misc, spec} from 'modules/adnuntiusBidAdapter.js'; +import {newBidder} from 'src/adapters/bidderFactory.js'; +import {config} from 'src/config.js'; import * as utils from 'src/utils.js'; -import { getStorageManager } from 'src/storageManager.js'; +import {getStorageManager} from 'src/storageManager.js'; +import {getGlobal} from '../../../src/prebidGlobal'; -describe('adnuntiusBidAdapter', function () { +describe('adnuntiusBidAdapter', function() { const URL = 'https://ads.adnuntius.delivery/i?tzo='; + const EURO_URL = 'https://europe.delivery.adnuntius.com/i?tzo='; const GVLID = 855; const usi = utils.generateUUID() const meta = [{ key: 'usi', value: usi }] before(() => { - const storage = getStorageManager({ gvlid: GVLID, moduleName: 'adnuntius' }) - storage.setDataInLocalStorage('adn.metaData', JSON.stringify(meta)) - }); - - beforeEach(function () { - $$PREBID_GLOBAL$$.bidderSettings = { + getGlobal().bidderSettings = { adnuntius: { storageAllowed: true } }; + const storage = getStorageManager({ bidderCode: 'adnuntius' }) + storage.setDataInLocalStorage('adn.metaData', JSON.stringify(meta)) + }); + + after(() => { + getGlobal().bidderSettings = {}; }); - afterEach(function () { + afterEach(function() { config.resetConfig(); - $$PREBID_GLOBAL$$.bidderSettings = {}; }); const tzo = new Date().getTimezoneOffset(); - const ENDPOINT_URL = `${URL}${tzo}&format=json&userId=${usi}`; - const ENDPOINT_URL_VIDEO = `${URL}${tzo}&format=json&userId=${usi}&tt=vast4`; - const ENDPOINT_URL_NOCOOKIE = `${URL}${tzo}&format=json&userId=${usi}&noCookies=true`; - const ENDPOINT_URL_SEGMENTS = `${URL}${tzo}&format=json&segments=segment1,segment2,segment3&userId=${usi}`; - const ENDPOINT_URL_CONSENT = `${URL}${tzo}&format=json&consentString=consentString&userId=${usi}`; + const ENDPOINT_URL_BASE = `${URL}${tzo}&format=json`; + const ENDPOINT_URL = `${ENDPOINT_URL_BASE}&userId=${usi}`; + const ENDPOINT_URL_VIDEO = `${ENDPOINT_URL_BASE}&userId=${usi}&tt=vast4`; + const ENDPOINT_URL_NOCOOKIE = `${ENDPOINT_URL_BASE}&userId=${usi}&noCookies=true`; + const ENDPOINT_URL_SEGMENTS = `${ENDPOINT_URL_BASE}&segments=segment1,segment2,segment3&userId=${usi}`; + const ENDPOINT_URL_CONSENT = `${EURO_URL}${tzo}&format=json&consentString=consentString&userId=${usi}`; const adapter = newBidder(spec); const bidderRequests = [ { - bidId: '123', + bidId: 'adn-000000000008b6bc', bidder: 'adnuntius', params: { - auId: '8b6bc', + auId: '000000000008b6bc', network: 'adnuntius', + maxDeals: 1 }, mediaTypes: { banner: { sizes: [[640, 480], [600, 400]], } }, + }, + { + bidId: 'adn-0000000000000551', + bidder: 'adnuntius', + params: { + auId: '0000000000000551', + network: 'adnuntius', + }, + mediaTypes: { + banner: { + sizes: [[1640, 1480], [1600, 1400]], + } + }, } ] const videoBidderRequest = [ { - bidId: '123', + bidId: 'adn-0000000000000551', bidder: 'adnuntius', params: { - auId: '8b6bc', + auId: '0000000000000551', network: 'adnuntius', }, mediaTypes: { @@ -71,264 +88,364 @@ describe('adnuntiusBidAdapter', function () { } ] - // const nativeBidderRequest = [ - // { - // bidId: '123', - // bidder: 'adnuntius', - // params: { - // auId: '8b6bc', - // network: 'adnuntius', - // }, - // mediaTypes: { - // native: { - // title: { - // required: true - // }, - // image: { - // required: true - // }, - // body: { - // required: true - // } - // } - // }, - // } - // ] - const singleBidRequest = { bid: [ { - bidId: '123', + bidder: 'adnuntius', + bidId: 'adn-0000000000000551', } ] } const videoBidRequest = { - bid: videoBidderRequest + bid: videoBidderRequest, + bidder: 'adnuntius' } - // const nativeBidRequest = { - // bid: nativeBidderRequest - // } - const serverResponse = { body: { 'adUnits': [ { - 'auId': '000000000008b6bc', - 'targetId': '123', - 'html': '

hi!

', + 'auId': '0000000000000551', + 'targetId': 'adn-0000000000000551', + 'html': "\n\n\n \n \n \n\n\n
\n
\"\"/
\n
\n \n\n", 'matchedAdCount': 1, - 'responseId': 'adn-rsp-1460129238', + 'responseId': 'adn-rsp--229633088', + 'deals': [ + { + 'destinationUrlEsc': 'http%3A%2F%2Fads.adnuntius.delivery%2Fc%2FyQtMUwYBn5P4v72WJMqLW4z7uJOBFXJTfjoRyz0z_wsAAAAQCtjQz9kbGWD4nuZy3q6HaCYxq6Lckz2kThplNb227EJdQ5032jcIGkf-UrPmXCU2EbXVaQ3Ok6_FNLuIDTONJyx6ZZCB10wGqA3OaSe1EqwQp84u1_5iQZAWDk73UYf7_vcIypn7ev-SICZ3qaevb2jYSRqTVZx6AiBZQQGlzlOOrbZU9AU1F-JwTds-YV3qtJHGlxI2peWFIuxFlOYyeX9Kzg%3Fct%3D673%26r%3Dhttp%253A%252F%252Fadnuntius.com', + 'assets': { + 'Image': { + 'cdnId': 'https://cdn.adnuntius.com/cdn/iBgqruUNbaUb2hmD3vws7WTi84jg_WB_-VOF_FeOZ7A.png', + 'width': '640', + 'height': '480' + } + }, + 'text': {}, + 'choices': {}, + 'clickUrl': 'https://ads.adnuntius.delivery/c/yQtMUwYBn5P4v72WJMqLW4z7uJOBFXJTfjoRyz0z_wsAAAAQCtjQz9kbGWD4nuZy3q6HaCYxq6Lckz2kThplNb227EJdQ5032jcIGkf-UrPmXCU2EbXVaQ3Ok6_FNLuIDTONJyx6ZZCB10wGqA3OaSe1EqwQp84u1_5iQZAWDk73UYf7_vcIypn7ev-SICZ3qaevb2jYSRqTVZx6AiBZQQGlzlOOrbZU9AU1F-JwTds-YV3qtJHGlxI2peWFIuxFlOYyeX9Kzg', + 'urls': { + 'destination': 'https://ads.adnuntius.delivery/c/yQtMUwYBn5P4v72WJMqLW4z7uJOBFXJTfjoRyz0z_wsAAAAQCtjQz9kbGWD4nuZy3q6HaCYxq6Lckz2kThplNb227EJdQ5032jcIGkf-UrPmXCU2EbXVaQ3Ok6_FNLuIDTONJyx6ZZCB10wGqA3OaSe1EqwQp84u1_5iQZAWDk73UYf7_vcIypn7ev-SICZ3qaevb2jYSRqTVZx6AiBZQQGlzlOOrbZU9AU1F-JwTds-YV3qtJHGlxI2peWFIuxFlOYyeX9Kzg?ct=673&r=http%3A%2F%2Fadnuntius.com' + }, + 'urlsEsc': { + 'destination': 'https%3A%2F%2Fads.adnuntius.delivery%2Fc%2FyQtMUwYBn5P4v72WJMqLW4z7uJOBFXJTfjoRyz0z_wsAAAAQCtjQz9kbGWD4nuZy3q6HaCYxq6Lckz2kThplNb227EJdQ5032jcIGkf-UrPmXCU2EbXVaQ3Ok6_FNLuIDTONJyx6ZZCB10wGqA3OaSe1EqwQp84u1_5iQZAWDk73UYf7_vcIypn7ev-SICZ3qaevb2jYSRqTVZx6AiBZQQGlzlOOrbZU9AU1F-JwTds-YV3qtJHGlxI2peWFIuxFlOYyeX9Kzg%3Fct%3D673%26r%3Dhttp%253A%252F%252Fadnuntius.com' + }, + 'destinationUrls': { + 'destination': 'https://adnuntius.com' + }, + 'cpm': { + 'amount': 9, + 'currency': 'USD' + }, + 'bid': { + 'amount': 0.009, + 'currency': 'USD' + }, + 'grossBid': { + 'amount': 0.009, + 'currency': 'USD' + }, + 'netBid': { + 'amount': 0.0081, + 'currency': 'USD' + }, + 'dealId': 'abc123xyz', + 'impressionTrackingUrls': [], + 'impressionTrackingUrlsEsc': [], + 'adId': 'adn-id-1064238860', + 'selectedColumn': '0', + 'selectedColumnPosition': '0', + 'renderedPixel': 'https://ads.adnuntius.delivery/b/yQtMUwYBn5P4v72WJMqLW4z7uJOBFXJTfjoRyz0z_wsAAAAQCtjQz9kbGWD4nuZy3q6HaCYxq6Lckz2kThplNb227EJdQ5032jcIGkf-UrPmXCU2EbXVaQ3Ok6_FNLuIDTONJyx6ZZCB10wGqA3OaSe1EqwQp84u1_5iQZAWDk73UYf7_vcIypn7ev-SICZ3qaevb2jYSRqTVZx6AiBZQQGlzlOOrbZU9AU1F-JwTds-YV3qtJHGlxI2peWFIuxFlOYyeX9Kzg.html', + 'renderedPixelEsc': 'http%3A%2F%2Fads.adnuntius.delivery%2Fb%2FyQtMUwYBn5P4v72WJMqLW4z7uJOBFXJTfjoRyz0z_wsAAAAQCtjQz9kbGWD4nuZy3q6HaCYxq6Lckz2kThplNb227EJdQ5032jcIGkf-UrPmXCU2EbXVaQ3Ok6_FNLuIDTONJyx6ZZCB10wGqA3OaSe1EqwQp84u1_5iQZAWDk73UYf7_vcIypn7ev-SICZ3qaevb2jYSRqTVZx6AiBZQQGlzlOOrbZU9AU1F-JwTds-YV3qtJHGlxI2peWFIuxFlOYyeX9Kzg.html', + 'visibleUrl': 'https://ads.adnuntius.delivery/s?rt=yQtMUwYBn5P4v72WJMqLW4z7uJOBFXJTfjoRyz0z_wsAAAAQCtjQz9kbGWD4nuZy3q6HaCYxq6Lckz2kThplNb227EJdQ5032jcIGkf-UrPmXCU2EbXVaQ3Ok6_FNLuIDTONJyx6ZZCB10wGqA3OaSe1EqwQp84u1_5iQZAWDk73UYf7_vcIypn7ev-SICZ3qaevb2jYSRqTVZx6AiBZQQGlzlOOrbZU9AU1F-JwTds-YV3qtJHGlxI2peWFIuxFlOYyeX9Kzg', + 'visibleUrlEsc': 'http%3A%2F%2Fads.adnuntius.delivery%2Fs%3Frt%3DyQtMUwYBn5P4v72WJMqLW4z7uJOBFXJTfjoRyz0z_wsAAAAQCtjQz9kbGWD4nuZy3q6HaCYxq6Lckz2kThplNb227EJdQ5032jcIGkf-UrPmXCU2EbXVaQ3Ok6_FNLuIDTONJyx6ZZCB10wGqA3OaSe1EqwQp84u1_5iQZAWDk73UYf7_vcIypn7ev-SICZ3qaevb2jYSRqTVZx6AiBZQQGlzlOOrbZU9AU1F-JwTds-YV3qtJHGlxI2peWFIuxFlOYyeX9Kzg', + 'viewUrl': 'https://ads.adnuntius.delivery/v?rt=yQtMUwYBn5P4v72WJMqLW4z7uJOBFXJTfjoRyz0z_wsAAAAQCtjQz9kbGWD4nuZy3q6HaCYxq6Lckz2kThplNb227EJdQ5032jcIGkf-UrPmXCU2EbXVaQ3Ok6_FNLuIDTONJyx6ZZCB10wGqA3OaSe1EqwQp84u1_5iQZAWDk73UYf7_vcIypn7ev-SICZ3qaevb2jYSRqTVZx6AiBZQQGlzlOOrbZU9AU1F-JwTds-YV3qtJHGlxI2peWFIuxFlOYyeX9Kzg', + 'viewUrlEsc': 'http%3A%2F%2Fads.adnuntius.delivery%2Fv%3Frt%3DyQtMUwYBn5P4v72WJMqLW4z7uJOBFXJTfjoRyz0z_wsAAAAQCtjQz9kbGWD4nuZy3q6HaCYxq6Lckz2kThplNb227EJdQ5032jcIGkf-UrPmXCU2EbXVaQ3Ok6_FNLuIDTONJyx6ZZCB10wGqA3OaSe1EqwQp84u1_5iQZAWDk73UYf7_vcIypn7ev-SICZ3qaevb2jYSRqTVZx6AiBZQQGlzlOOrbZU9AU1F-JwTds-YV3qtJHGlxI2peWFIuxFlOYyeX9Kzg', + 'rt': 'yQtMUwYBn5P4v72WJMqLW4z7uJOBFXJTfjoRyz0z_wsAAAAQCtjQz9kbGWD4nuZy3q6HaCYxq6Lckz2kThplNb227EJdQ5032jcIGkf-UrPmXCU2EbXVaQ3Ok6_FNLuIDTONJyx6ZZCB10wGqA3OaSe1EqwQp84u1_5iQZAWDk73UYf7_vcIypn7ev-SICZ3qaevb2jYSRqTVZx6AiBZQQGlzlOOrbZU9AU1F-JwTds-YV3qtJHGlxI2peWFIuxFlOYyeX9Kzg', + 'creativeWidth': '640', + 'creativeHeight': '480', + 'creativeId': 's90t0q03pm', + 'lineItemId': 'cr3hnkkxhnkw9ldy', + 'layoutId': 'buyers_network_image_layout_1', + 'layoutName': 'Image', + 'layoutExternalReference': '', + 'html': "\n\n\n \n \n \n\n\n
\n
\"\"/
\n
\n \n\n", + 'renderTemplate': '' + } + ], 'ads': [ { - 'destinationUrlEsc': 'https%3A%2F%2Fdelivery.adnuntius.com%2Fc%2F52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN%3Fct%3D2501%26r%3Dhttp%253A%252F%252Fgoogle.com', + 'destinationUrlEsc': 'http%3A%2F%2Fads.adnuntius.delivery%2Fc%2F5Mu-vFVsaf4dWWx8uyZoV7Mz0sPkF1_j9bSupMwX8dMAAAAQCtjQz9kbGWD4nuZy3q6HaCYxqqTYxj2sS0FkMriztxcORshj3zRbT0KsV7XnDXE0F-OsHX7Ok6_FNLuIDTOMJyx6ZZCB10wGqA3OaSe1Eq9D85h8gP1gGsobC0KsAISm_PYNkJ6ve6qZLnB79fX6XHLYSRqTBM8sBCRXQAetnVzeo7AHoQhkFeouS444YA_q4JCTlRI2peWFIuxFlOYyeX9Kzg%3Fct%3D673%26r%3Dhttp%253A%252F%252Fadnuntius.com', 'assets': { - 'image': { - 'cdnId': 'https://assets.adnuntius.com/oEmZa5uYjxENfA1R692FVn6qIveFpO8wUbpyF2xSOCc.jpg', - 'width': '980', - 'height': '120' + 'Image': { + 'cdnId': 'https://cdn.adnuntius.com/cdn/iBgqruUNbaUb2hmD3vws7WTi84jg_WB_-VOF_FeOZ7A.png', + 'width': '640', + 'height': '480' } }, - 'clickUrl': 'https://delivery.adnuntius.com/c/52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN', + 'dealId': 'not-in-deal-array-here', + 'text': {}, + 'choices': {}, + 'clickUrl': 'https://ads.adnuntius.delivery/c/5Mu-vFVsaf4dWWx8uyZoV7Mz0sPkF1_j9bSupMwX8dMAAAAQCtjQz9kbGWD4nuZy3q6HaCYxqqTYxj2sS0FkMriztxcORshj3zRbT0KsV7XnDXE0F-OsHX7Ok6_FNLuIDTOMJyx6ZZCB10wGqA3OaSe1Eq9D85h8gP1gGsobC0KsAISm_PYNkJ6ve6qZLnB79fX6XHLYSRqTBM8sBCRXQAetnVzeo7AHoQhkFeouS444YA_q4JCTlRI2peWFIuxFlOYyeX9Kzg', 'urls': { - 'destination': 'https://delivery.adnuntius.com/c/52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN?ct=2501&r=http%3A%2F%2Fgoogle.com' + 'destination': 'https://ads.adnuntius.delivery/c/5Mu-vFVsaf4dWWx8uyZoV7Mz0sPkF1_j9bSupMwX8dMAAAAQCtjQz9kbGWD4nuZy3q6HaCYxqqTYxj2sS0FkMriztxcORshj3zRbT0KsV7XnDXE0F-OsHX7Ok6_FNLuIDTOMJyx6ZZCB10wGqA3OaSe1Eq9D85h8gP1gGsobC0KsAISm_PYNkJ6ve6qZLnB79fX6XHLYSRqTBM8sBCRXQAetnVzeo7AHoQhkFeouS444YA_q4JCTlRI2peWFIuxFlOYyeX9Kzg?ct=673&r=http%3A%2F%2Fadnuntius.com' }, 'urlsEsc': { - 'destination': 'https%3A%2F%2Fdelivery.adnuntius.com%2Fc%2F52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN%3Fct%3D2501%26r%3Dhttp%253A%252F%252Fgoogle.com' + 'destination': 'http%3A%2F%2Fads.adnuntius.delivery%2Fc%2F5Mu-vFVsaf4dWWx8uyZoV7Mz0sPkF1_j9bSupMwX8dMAAAAQCtjQz9kbGWD4nuZy3q6HaCYxqqTYxj2sS0FkMriztxcORshj3zRbT0KsV7XnDXE0F-OsHX7Ok6_FNLuIDTOMJyx6ZZCB10wGqA3OaSe1Eq9D85h8gP1gGsobC0KsAISm_PYNkJ6ve6qZLnB79fX6XHLYSRqTBM8sBCRXQAetnVzeo7AHoQhkFeouS444YA_q4JCTlRI2peWFIuxFlOYyeX9Kzg%3Fct%3D673%26r%3Dhttp%253A%252F%252Fadnuntius.com' }, 'destinationUrls': { - 'destination': 'http://google.com' + 'destination': 'https://adnuntius.com' + }, + 'cpm': { + 'amount': 9, + 'currency': 'USD' + }, + 'bid': { + 'amount': 0.009, + 'currency': 'USD' + }, + 'grossBid': { + 'amount': 0.009, + 'currency': 'USD' + }, + 'netBid': { + 'amount': 0.0081, + 'currency': 'USD' }, - 'cpm': { 'amount': 5.0, 'currency': 'NOK' }, - 'bid': { 'amount': 0.005, 'currency': 'NOK' }, - 'cost': { 'amount': 0.005, 'currency': 'NOK' }, 'impressionTrackingUrls': [], 'impressionTrackingUrlsEsc': [], - 'adId': 'adn-id-1347343135', + 'adId': 'adn-id-1488629603', 'selectedColumn': '0', 'selectedColumnPosition': '0', - 'renderedPixel': 'https://delivery.adnuntius.com/b/52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN.html', - 'renderedPixelEsc': 'https%3A%2F%2Fdelivery.adnuntius.com%2Fb%2F52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN.html', - 'visibleUrl': 'https://delivery.adnuntius.com/s?rt=52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN', - 'visibleUrlEsc': 'https%3A%2F%2Fdelivery.adnuntius.com%2Fs%3Frt%3D52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN', - 'viewUrl': 'https://delivery.adnuntius.com/v?rt=52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN', - 'viewUrlEsc': 'https%3A%2F%2Fdelivery.adnuntius.com%2Fv%3Frt%3D52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN', - 'rt': '52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN', - 'creativeWidth': '980', - 'creativeHeight': '120', - 'creativeId': 'wgkq587vgtpchsx1', - 'lineItemId': 'scyjdyv3mzgdsnpf', - 'layoutId': 'sw6gtws2rdj1kwby', - 'layoutName': 'Responsive image' - }, - + 'renderedPixel': 'https://ads.adnuntius.delivery/b/5Mu-vFVsaf4dWWx8uyZoV7Mz0sPkF1_j9bSupMwX8dMAAAAQCtjQz9kbGWD4nuZy3q6HaCYxqqTYxj2sS0FkMriztxcORshj3zRbT0KsV7XnDXE0F-OsHX7Ok6_FNLuIDTOMJyx6ZZCB10wGqA3OaSe1Eq9D85h8gP1gGsobC0KsAISm_PYNkJ6ve6qZLnB79fX6XHLYSRqTBM8sBCRXQAetnVzeo7AHoQhkFeouS444YA_q4JCTlRI2peWFIuxFlOYyeX9Kzg.html', + 'renderedPixelEsc': 'http%3A%2F%2Fads.adnuntius.delivery%2Fb%2F5Mu-vFVsaf4dWWx8uyZoV7Mz0sPkF1_j9bSupMwX8dMAAAAQCtjQz9kbGWD4nuZy3q6HaCYxqqTYxj2sS0FkMriztxcORshj3zRbT0KsV7XnDXE0F-OsHX7Ok6_FNLuIDTOMJyx6ZZCB10wGqA3OaSe1Eq9D85h8gP1gGsobC0KsAISm_PYNkJ6ve6qZLnB79fX6XHLYSRqTBM8sBCRXQAetnVzeo7AHoQhkFeouS444YA_q4JCTlRI2peWFIuxFlOYyeX9Kzg.html', + 'visibleUrl': 'https://ads.adnuntius.delivery/s?rt=5Mu-vFVsaf4dWWx8uyZoV7Mz0sPkF1_j9bSupMwX8dMAAAAQCtjQz9kbGWD4nuZy3q6HaCYxqqTYxj2sS0FkMriztxcORshj3zRbT0KsV7XnDXE0F-OsHX7Ok6_FNLuIDTOMJyx6ZZCB10wGqA3OaSe1Eq9D85h8gP1gGsobC0KsAISm_PYNkJ6ve6qZLnB79fX6XHLYSRqTBM8sBCRXQAetnVzeo7AHoQhkFeouS444YA_q4JCTlRI2peWFIuxFlOYyeX9Kzg', + 'visibleUrlEsc': 'http%3A%2F%2Fads.adnuntius.delivery%2Fs%3Frt%3D5Mu-vFVsaf4dWWx8uyZoV7Mz0sPkF1_j9bSupMwX8dMAAAAQCtjQz9kbGWD4nuZy3q6HaCYxqqTYxj2sS0FkMriztxcORshj3zRbT0KsV7XnDXE0F-OsHX7Ok6_FNLuIDTOMJyx6ZZCB10wGqA3OaSe1Eq9D85h8gP1gGsobC0KsAISm_PYNkJ6ve6qZLnB79fX6XHLYSRqTBM8sBCRXQAetnVzeo7AHoQhkFeouS444YA_q4JCTlRI2peWFIuxFlOYyeX9Kzg', + 'viewUrl': 'https://ads.adnuntius.delivery/v?rt=5Mu-vFVsaf4dWWx8uyZoV7Mz0sPkF1_j9bSupMwX8dMAAAAQCtjQz9kbGWD4nuZy3q6HaCYxqqTYxj2sS0FkMriztxcORshj3zRbT0KsV7XnDXE0F-OsHX7Ok6_FNLuIDTOMJyx6ZZCB10wGqA3OaSe1Eq9D85h8gP1gGsobC0KsAISm_PYNkJ6ve6qZLnB79fX6XHLYSRqTBM8sBCRXQAetnVzeo7AHoQhkFeouS444YA_q4JCTlRI2peWFIuxFlOYyeX9Kzg', + 'viewUrlEsc': 'http%3A%2F%2Fads.adnuntius.delivery%2Fv%3Frt%3D5Mu-vFVsaf4dWWx8uyZoV7Mz0sPkF1_j9bSupMwX8dMAAAAQCtjQz9kbGWD4nuZy3q6HaCYxqqTYxj2sS0FkMriztxcORshj3zRbT0KsV7XnDXE0F-OsHX7Ok6_FNLuIDTOMJyx6ZZCB10wGqA3OaSe1Eq9D85h8gP1gGsobC0KsAISm_PYNkJ6ve6qZLnB79fX6XHLYSRqTBM8sBCRXQAetnVzeo7AHoQhkFeouS444YA_q4JCTlRI2peWFIuxFlOYyeX9Kzg', + 'rt': '5Mu-vFVsaf4dWWx8uyZoV7Mz0sPkF1_j9bSupMwX8dMAAAAQCtjQz9kbGWD4nuZy3q6HaCYxqqTYxj2sS0FkMriztxcORshj3zRbT0KsV7XnDXE0F-OsHX7Ok6_FNLuIDTOMJyx6ZZCB10wGqA3OaSe1Eq9D85h8gP1gGsobC0KsAISm_PYNkJ6ve6qZLnB79fX6XHLYSRqTBM8sBCRXQAetnVzeo7AHoQhkFeouS444YA_q4JCTlRI2peWFIuxFlOYyeX9Kzg', + 'creativeWidth': '640', + 'creativeHeight': '480', + 'creativeId': 's90t0q03pm', + 'lineItemId': 'cr3hnkkxhnkw9ldy', + 'layoutId': 'buyers_network_image_layout_1', + 'layoutName': 'Image', + 'layoutExternalReference': '', + 'html': '', + 'renderTemplate': '' + } ] }, { 'auId': '000000000008b6bc', - 'targetId': '456', + 'targetId': 'adn-000000000008b6bc', 'matchedAdCount': 0, 'responseId': 'adn-rsp-1460129238', } - ] + ], + 'metaData': { + 'usi': 'from-api-server dude', + 'voidAuIds': '00000000000abcde;00000000000fffff', + 'randomApiKey': 'randomApiValue' + } } } const serverVideoResponse = { body: { 'adUnits': [ { - 'auId': '000000000008b6bc', - 'targetId': '123', - 'html': '

hi!

', + 'auId': '0000000000000551', + 'targetId': 'adn-0000000000000551', + 'vastXml': '\\n\\n \\n Adnuntius\\n adn-id-500662301\\n \\n \\n \\n \\n 00:00:15\\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n\\n', 'matchedAdCount': 1, - 'responseId': 'adn-rsp-1460129238', + 'responseId': '', + 'deals': [ + { + 'destinationUrlEsc': '', + 'assets': { + 'Video': { + 'cdnId': 'http://localhost:8079/cdn/9urJusYWpjFDLcpOwfejrkWlLP1heM3vWIJjuHk48BQ.mp4', + 'width': '1920', + 'height': '1080' + } + }, + 'text': { + 'thirdQuartile': { + 'content': 'whatevs', + 'length': '7', + 'minLength': '1', + 'maxLength': '64' + }, + 'complete': { + 'content': 'whatevs', + 'length': '7', + 'minLength': '1', + 'maxLength': '64' + }, + 'firstQuartile': { + 'content': 'whatevs', + 'length': '7', + 'minLength': '1', + 'maxLength': '64' + } + }, + 'choices': {}, + 'clickUrl': 'http://localhost:8078/c/7MRhdEcIndSrWxWhFtMVVnDULl8BOwEaV6vi9558AbYAAAAQCtjQz9kbGWD4nuZy3q6HaCYx_6vZlT33ShgzYb616hZZSpoy2jkAHkGrB-XmXyJmEOCSQ0PLk6_FNLuCBzONJyx6ZZCB10wGqA3OaSe1EqMWoJp41f83FcJLX0SpBoT1-qBbx5_8La-ULiAnqaGtMRDYSRqTBZh5DCANFQWnm1fa8rEE9VRgRORwTNxvZFjq5JCSkEQ2peWFIuxFlOYyeX9Kzg', + 'urls': { + 'destinationUrl': 'http://localhost:8078/c/7MRhdEcIndSrWxWhFtMVVnDULl8BOwEaV6vi9558AbYAAAAQCtjQz9kbGWD4nuZy3q6HaCYx_6vZlT33ShgzYb616hZZSpoy2jkAHkGrB-XmXyJmEOCSQ0PLk6_FNLuCBzONJyx6ZZCB10wGqA3OaSe1EqMWoJp41f83FcJLX0SpBoT1-qBbx5_8La-ULiAnqaGtMRDYSRqTBZh5DCANFQWnm1fa8rEE9VRgRORwTNxvZFjq5JCSkEQ2peWFIuxFlOYyeX9Kzg?ct=682&r=http%3A%2F%2Fadnuntius.com' + }, + 'urlsEsc': { + 'destinationUrl': 'http%3A%2F%2Flocalhost%3A8078%2Fc%2F7MRhdEcIndSrWxWhFtMVVnDULl8BOwEaV6vi9558AbYAAAAQCtjQz9kbGWD4nuZy3q6HaCYx_6vZlT33ShgzYb616hZZSpoy2jkAHkGrB-XmXyJmEOCSQ0PLk6_FNLuCBzONJyx6ZZCB10wGqA3OaSe1EqMWoJp41f83FcJLX0SpBoT1-qBbx5_8La-ULiAnqaGtMRDYSRqTBZh5DCANFQWnm1fa8rEE9VRgRORwTNxvZFjq5JCSkEQ2peWFIuxFlOYyeX9Kzg%3Fct%3D682%26r%3Dhttp%253A%252F%252Fadnuntius.com' + }, + 'destinationUrls': { + 'destinationUrl': 'http://adnuntius.com' + }, + 'cpm': { + 'amount': 9, + 'currency': 'USD' + }, + 'bid': { + 'amount': 0.009, + 'currency': 'USD' + }, + 'grossBid': { + 'amount': 0.009, + 'currency': 'USD' + }, + 'netBid': { + 'amount': 0.0081, + 'currency': 'USD' + }, + 'dealId': 'abc123xyz', + 'impressionTrackingUrls': [], + 'impressionTrackingUrlsEsc': [], + 'adId': 'adn-id-1465065992', + 'selectedColumn': '0', + 'selectedColumnPosition': '0', + 'renderedPixel': 'http://localhost:8078/b/7MRhdEcIndSrWxWhFtMVVnDULl8BOwEaV6vi9558AbYAAAAQCtjQz9kbGWD4nuZy3q6HaCYx_6vZlT33ShgzYb616hZZSpoy2jkAHkGrB-XmXyJmEOCSQ0PLk6_FNLuCBzONJyx6ZZCB10wGqA3OaSe1EqMWoJp41f83FcJLX0SpBoT1-qBbx5_8La-ULiAnqaGtMRDYSRqTBZh5DCANFQWnm1fa8rEE9VRgRORwTNxvZFjq5JCSkEQ2peWFIuxFlOYyeX9Kzg.html', + 'renderedPixelEsc': 'http%3A%2F%2Flocalhost%3A8078%2Fb%2F7MRhdEcIndSrWxWhFtMVVnDULl8BOwEaV6vi9558AbYAAAAQCtjQz9kbGWD4nuZy3q6HaCYx_6vZlT33ShgzYb616hZZSpoy2jkAHkGrB-XmXyJmEOCSQ0PLk6_FNLuCBzONJyx6ZZCB10wGqA3OaSe1EqMWoJp41f83FcJLX0SpBoT1-qBbx5_8La-ULiAnqaGtMRDYSRqTBZh5DCANFQWnm1fa8rEE9VRgRORwTNxvZFjq5JCSkEQ2peWFIuxFlOYyeX9Kzg.html', + 'visibleUrl': 'http://localhost:8078/s?rt=7MRhdEcIndSrWxWhFtMVVnDULl8BOwEaV6vi9558AbYAAAAQCtjQz9kbGWD4nuZy3q6HaCYx_6vZlT33ShgzYb616hZZSpoy2jkAHkGrB-XmXyJmEOCSQ0PLk6_FNLuCBzONJyx6ZZCB10wGqA3OaSe1EqMWoJp41f83FcJLX0SpBoT1-qBbx5_8La-ULiAnqaGtMRDYSRqTBZh5DCANFQWnm1fa8rEE9VRgRORwTNxvZFjq5JCSkEQ2peWFIuxFlOYyeX9Kzg', + 'visibleUrlEsc': 'http%3A%2F%2Flocalhost%3A8078%2Fs%3Frt%3D7MRhdEcIndSrWxWhFtMVVnDULl8BOwEaV6vi9558AbYAAAAQCtjQz9kbGWD4nuZy3q6HaCYx_6vZlT33ShgzYb616hZZSpoy2jkAHkGrB-XmXyJmEOCSQ0PLk6_FNLuCBzONJyx6ZZCB10wGqA3OaSe1EqMWoJp41f83FcJLX0SpBoT1-qBbx5_8La-ULiAnqaGtMRDYSRqTBZh5DCANFQWnm1fa8rEE9VRgRORwTNxvZFjq5JCSkEQ2peWFIuxFlOYyeX9Kzg', + 'viewUrl': 'http://localhost:8078/v?rt=7MRhdEcIndSrWxWhFtMVVnDULl8BOwEaV6vi9558AbYAAAAQCtjQz9kbGWD4nuZy3q6HaCYx_6vZlT33ShgzYb616hZZSpoy2jkAHkGrB-XmXyJmEOCSQ0PLk6_FNLuCBzONJyx6ZZCB10wGqA3OaSe1EqMWoJp41f83FcJLX0SpBoT1-qBbx5_8La-ULiAnqaGtMRDYSRqTBZh5DCANFQWnm1fa8rEE9VRgRORwTNxvZFjq5JCSkEQ2peWFIuxFlOYyeX9Kzg', + 'viewUrlEsc': 'http%3A%2F%2Flocalhost%3A8078%2Fv%3Frt%3D7MRhdEcIndSrWxWhFtMVVnDULl8BOwEaV6vi9558AbYAAAAQCtjQz9kbGWD4nuZy3q6HaCYx_6vZlT33ShgzYb616hZZSpoy2jkAHkGrB-XmXyJmEOCSQ0PLk6_FNLuCBzONJyx6ZZCB10wGqA3OaSe1EqMWoJp41f83FcJLX0SpBoT1-qBbx5_8La-ULiAnqaGtMRDYSRqTBZh5DCANFQWnm1fa8rEE9VRgRORwTNxvZFjq5JCSkEQ2peWFIuxFlOYyeX9Kzg', + 'rt': '7MRhdEcIndSrWxWhFtMVVnDULl8BOwEaV6vi9558AbYAAAAQCtjQz9kbGWD4nuZy3q6HaCYx_6vZlT33ShgzYb616hZZSpoy2jkAHkGrB-XmXyJmEOCSQ0PLk6_FNLuCBzONJyx6ZZCB10wGqA3OaSe1EqMWoJp41f83FcJLX0SpBoT1-qBbx5_8La-ULiAnqaGtMRDYSRqTBZh5DCANFQWnm1fa8rEE9VRgRORwTNxvZFjq5JCSkEQ2peWFIuxFlOYyeX9Kzg', + 'creativeWidth': '640', + 'creativeHeight': '480', + 'creativeId': 'p6sqtvcgxczy258j', + 'lineItemId': 'cr3hnkkxhnkw9ldy', + 'layoutId': 'buyers_networkvast_single_format_layout', + 'layoutName': 'Vast (video upload)', + 'layoutExternalReference': '', + 'vastXml': '\n\n \n Adnuntius\n adn-id-1465065992\n \n \n \n \n 00:00:15\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n', + 'renderTemplate': '\n \n Adnuntius\n {{{adId}}}\n \n \n {{#ifEquals vastVersion "4.0"}}\n {{/ifEquals}}\n \n {{assets.Video.duration}}\n \n \n {{#unless preview}}{{#each impressionTrackingUrls}}\n {{/each}}{{/unless}}{{#if text.firstQuartile.content}}\n {{/if}}\n {{#if text.thirdQuartile.content}}\n {{/if}}{{#if text.complete.content}}\n {{/if}}\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n' + } + ], 'ads': [ { - 'destinationUrlEsc': 'https%3A%2F%2Fdelivery.adnuntius.com%2Fc%2F52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN%3Fct%3D2501%26r%3Dhttp%253A%252F%252Fgoogle.com', + 'destinationUrlEsc': '', + 'dealId': 'not-in-deal-array', 'assets': { - 'image': { - 'cdnId': 'https://assets.adnuntius.com/oEmZa5uYjxENfA1R692FVn6qIveFpO8wUbpyF2xSOCc.jpg', - 'width': '980', - 'height': '120' + 'Video': { + 'cdnId': 'http://localhost:8079/cdn/9urJusYWpjFDLcpOwfejrkWlLP1heM3vWIJjuHk48BQ.mp4', + 'width': '1920', + 'height': '1080' } }, - 'clickUrl': 'https://delivery.adnuntius.com/c/52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN', + 'text': { + 'thirdQuartile': { + 'content': 'whatevs', + 'length': '7', + 'minLength': '1', + 'maxLength': '64' + }, + 'complete': { + 'content': 'whatevs', + 'length': '7', + 'minLength': '1', + 'maxLength': '64' + }, + 'firstQuartile': { + 'content': 'whatevs', + 'length': '7', + 'minLength': '1', + 'maxLength': '64' + } + }, + 'choices': {}, + 'clickUrl': 'http://localhost:8078/c/ZxGSsEJ8IzI8iCTQ17fHxnE29-y7VILLg2CLjhEbphMAAAAQCtjQz9kbGWD4nuZy3q6HaCYxpfXelzygS0s1MLax6koIS8pnjDAOShX4CbPkDyIzELe5BlfLk6_FNLuCBzOMJyx6ZZCB10wGqA3OaSe1EqpGosot0vsyFMtIWRWtUoKk-aANx531KaOWLyonoP2uC6UpxkXRUJ8iVCcMF1KmmAfe9rNYplI0E-ErHtVvZgm7uZeal_VymQxr0zkhjS_bW0PX', 'urls': { - 'destination': 'https://delivery.adnuntius.com/c/52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN?ct=2501&r=http%3A%2F%2Fgoogle.com' + 'destinationUrl': 'http://localhost:8078/c/ZxGSsEJ8IzI8iCTQ17fHxnE29-y7VILLg2CLjhEbphMAAAAQCtjQz9kbGWD4nuZy3q6HaCYxpfXelzygS0s1MLax6koIS8pnjDAOShX4CbPkDyIzELe5BlfLk6_FNLuCBzOMJyx6ZZCB10wGqA3OaSe1EqpGosot0vsyFMtIWRWtUoKk-aANx531KaOWLyonoP2uC6UpxkXRUJ8iVCcMF1KmmAfe9rNYplI0E-ErHtVvZgm7uZeal_VymQxr0zkhjS_bW0PX?ct=682&r=http%3A%2F%2Fadnuntius.com' }, 'urlsEsc': { - 'destination': 'https%3A%2F%2Fdelivery.adnuntius.com%2Fc%2F52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN%3Fct%3D2501%26r%3Dhttp%253A%252F%252Fgoogle.com' + 'destinationUrl': 'http%3A%2F%2Flocalhost%3A8078%2Fc%2FZxGSsEJ8IzI8iCTQ17fHxnE29-y7VILLg2CLjhEbphMAAAAQCtjQz9kbGWD4nuZy3q6HaCYxpfXelzygS0s1MLax6koIS8pnjDAOShX4CbPkDyIzELe5BlfLk6_FNLuCBzOMJyx6ZZCB10wGqA3OaSe1EqpGosot0vsyFMtIWRWtUoKk-aANx531KaOWLyonoP2uC6UpxkXRUJ8iVCcMF1KmmAfe9rNYplI0E-ErHtVvZgm7uZeal_VymQxr0zkhjS_bW0PX%3Fct%3D682%26r%3Dhttp%253A%252F%252Fadnuntius.com' }, 'destinationUrls': { - 'destination': 'http://google.com' + 'destinationUrl': 'http://adnuntius.com' + }, + 'cpm': { + 'amount': 9, + 'currency': 'USD' + }, + 'bid': { + 'amount': 0.009, + 'currency': 'USD' + }, + 'grossBid': { + 'amount': 0.009, + 'currency': 'USD' + }, + 'netBid': { + 'amount': 0.0081, + 'currency': 'USD' }, - 'cpm': { 'amount': 5.0, 'currency': 'NOK' }, - 'bid': { 'amount': 0.005, 'currency': 'NOK' }, - 'cost': { 'amount': 0.005, 'currency': 'NOK' }, 'impressionTrackingUrls': [], 'impressionTrackingUrlsEsc': [], - 'adId': 'adn-id-1347343135', + 'adId': 'adn-id-500662301', 'selectedColumn': '0', 'selectedColumnPosition': '0', - 'renderedPixel': 'https://delivery.adnuntius.com/b/52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN.html', - 'renderedPixelEsc': 'https%3A%2F%2Fdelivery.adnuntius.com%2Fb%2F52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN.html', - 'visibleUrl': 'https://delivery.adnuntius.com/s?rt=52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN', - 'visibleUrlEsc': 'https%3A%2F%2Fdelivery.adnuntius.com%2Fs%3Frt%3D52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN', - 'viewUrl': 'https://delivery.adnuntius.com/v?rt=52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN', - 'viewUrlEsc': 'https%3A%2F%2Fdelivery.adnuntius.com%2Fv%3Frt%3D52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN', - 'rt': '52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN', - 'creativeWidth': '980', - 'creativeHeight': '120', - 'creativeId': 'wgkq587vgtpchsx1', - 'lineItemId': 'scyjdyv3mzgdsnpf', - 'layoutId': 'sw6gtws2rdj1kwby', - 'layoutName': 'Responsive image' - }, - + 'renderedPixel': 'http://localhost:8078/b/ZxGSsEJ8IzI8iCTQ17fHxnE29-y7VILLg2CLjhEbphMAAAAQCtjQz9kbGWD4nuZy3q6HaCYxpfXelzygS0s1MLax6koIS8pnjDAOShX4CbPkDyIzELe5BlfLk6_FNLuCBzOMJyx6ZZCB10wGqA3OaSe1EqpGosot0vsyFMtIWRWtUoKk-aANx531KaOWLyonoP2uC6UpxkXRUJ8iVCcMF1KmmAfe9rNYplI0E-ErHtVvZgm7uZeal_VymQxr0zkhjS_bW0PX.html', + 'renderedPixelEsc': 'http%3A%2F%2Flocalhost%3A8078%2Fb%2FZxGSsEJ8IzI8iCTQ17fHxnE29-y7VILLg2CLjhEbphMAAAAQCtjQz9kbGWD4nuZy3q6HaCYxpfXelzygS0s1MLax6koIS8pnjDAOShX4CbPkDyIzELe5BlfLk6_FNLuCBzOMJyx6ZZCB10wGqA3OaSe1EqpGosot0vsyFMtIWRWtUoKk-aANx531KaOWLyonoP2uC6UpxkXRUJ8iVCcMF1KmmAfe9rNYplI0E-ErHtVvZgm7uZeal_VymQxr0zkhjS_bW0PX.html', + 'visibleUrl': 'http://localhost:8078/s?rt=ZxGSsEJ8IzI8iCTQ17fHxnE29-y7VILLg2CLjhEbphMAAAAQCtjQz9kbGWD4nuZy3q6HaCYxpfXelzygS0s1MLax6koIS8pnjDAOShX4CbPkDyIzELe5BlfLk6_FNLuCBzOMJyx6ZZCB10wGqA3OaSe1EqpGosot0vsyFMtIWRWtUoKk-aANx531KaOWLyonoP2uC6UpxkXRUJ8iVCcMF1KmmAfe9rNYplI0E-ErHtVvZgm7uZeal_VymQxr0zkhjS_bW0PX', + 'visibleUrlEsc': 'http%3A%2F%2Flocalhost%3A8078%2Fs%3Frt%3DZxGSsEJ8IzI8iCTQ17fHxnE29-y7VILLg2CLjhEbphMAAAAQCtjQz9kbGWD4nuZy3q6HaCYxpfXelzygS0s1MLax6koIS8pnjDAOShX4CbPkDyIzELe5BlfLk6_FNLuCBzOMJyx6ZZCB10wGqA3OaSe1EqpGosot0vsyFMtIWRWtUoKk-aANx531KaOWLyonoP2uC6UpxkXRUJ8iVCcMF1KmmAfe9rNYplI0E-ErHtVvZgm7uZeal_VymQxr0zkhjS_bW0PX', + 'viewUrl': 'http://localhost:8078/v?rt=ZxGSsEJ8IzI8iCTQ17fHxnE29-y7VILLg2CLjhEbphMAAAAQCtjQz9kbGWD4nuZy3q6HaCYxpfXelzygS0s1MLax6koIS8pnjDAOShX4CbPkDyIzELe5BlfLk6_FNLuCBzOMJyx6ZZCB10wGqA3OaSe1EqpGosot0vsyFMtIWRWtUoKk-aANx531KaOWLyonoP2uC6UpxkXRUJ8iVCcMF1KmmAfe9rNYplI0E-ErHtVvZgm7uZeal_VymQxr0zkhjS_bW0PX', + 'viewUrlEsc': 'http%3A%2F%2Flocalhost%3A8078%2Fv%3Frt%3DZxGSsEJ8IzI8iCTQ17fHxnE29-y7VILLg2CLjhEbphMAAAAQCtjQz9kbGWD4nuZy3q6HaCYxpfXelzygS0s1MLax6koIS8pnjDAOShX4CbPkDyIzELe5BlfLk6_FNLuCBzOMJyx6ZZCB10wGqA3OaSe1EqpGosot0vsyFMtIWRWtUoKk-aANx531KaOWLyonoP2uC6UpxkXRUJ8iVCcMF1KmmAfe9rNYplI0E-ErHtVvZgm7uZeal_VymQxr0zkhjS_bW0PX', + 'rt': 'ZxGSsEJ8IzI8iCTQ17fHxnE29-y7VILLg2CLjhEbphMAAAAQCtjQz9kbGWD4nuZy3q6HaCYxpfXelzygS0s1MLax6koIS8pnjDAOShX4CbPkDyIzELe5BlfLk6_FNLuCBzOMJyx6ZZCB10wGqA3OaSe1EqpGosot0vsyFMtIWRWtUoKk-aANx531KaOWLyonoP2uC6UpxkXRUJ8iVCcMF1KmmAfe9rNYplI0E-ErHtVvZgm7uZeal_VymQxr0zkhjS_bW0PX', + 'creativeWidth': '640', + 'creativeHeight': '480', + 'creativeId': 'p6sqtvcgxczy258j', + 'lineItemId': 'cr3hnkkxhnkw9ldy', + 'layoutId': 'buyers_networkvast_single_format_layout', + 'layoutName': 'Vast (video upload)', + 'layoutExternalReference': '', + 'vastXml': '\n \n Adnuntius\n adn-id-500662301\n \n \n \n \n 00:00:15\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n', + 'renderTemplate': '\n \n Adnuntius\n {{{adId}}}\n \n \n {{#ifEquals vastVersion "4.0"}}\n {{/ifEquals}}\n \n {{assets.Video.duration}}\n \n \n {{#unless preview}}{{#each impressionTrackingUrls}}\n {{/each}}{{/unless}}{{#if text.firstQuartile.content}}\n {{/if}}\n {{#if text.thirdQuartile.content}}\n {{/if}}{{#if text.complete.content}}\n {{/if}}\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n' + } ] }, { 'auId': '000000000008b6bc', - 'targetId': '456', + 'targetId': 'adn-000000000008b6bc', 'matchedAdCount': 0, 'responseId': 'adn-rsp-1460129238', } ] } } - // const serverNativeResponse = { - // body: { - // 'adUnits': [ - // { - // 'auId': '000000000008b6bc', - // 'targetId': '123', - // 'html': '

hi!

', - // 'matchedAdCount': 1, - // 'responseId': 'adn-rsp-1460129238', - // 'ads': [ - // { - // 'destinationUrlEsc': 'https%3A%2F%2Fdelivery.adnuntius.com%2Fc%2F52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN%3Fct%3D2501%26r%3Dhttp%253A%252F%252Fgoogle.com', - // 'assets': { - // 'image': { - // 'cdnId': 'https://assets.adnuntius.com/K9rfXC6wJvgVuy4Fbt5P8oEEGXme9ZaP8BNDzz3OMGQ.jpg', - // 'width': '300', - // 'height': '250' - // } - // }, - // 'text': { - // 'body': { - // 'content': 'Testing Native ad from Adnuntius', - // 'length': '32', - // 'minLength': '0', - // 'maxLength': '100' - // }, - // 'title': { - // 'content': 'Native Ad', - // 'length': '9', - // 'minLength': '5', - // 'maxLength': '100' - // } - // }, - // 'clickUrl': 'https://delivery.adnuntius.com/c/52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN', - // 'urls': { - // 'destination': 'https://delivery.adnuntius.com/c/52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN?ct=2501&r=http%3A%2F%2Fgoogle.com' - // }, - // 'urlsEsc': { - // 'destination': 'https%3A%2F%2Fdelivery.adnuntius.com%2Fc%2F52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN%3Fct%3D2501%26r%3Dhttp%253A%252F%252Fgoogle.com' - // }, - // 'destinationUrls': { - // 'destination': 'http://google.com' - // }, - // 'cpm': { 'amount': 5.0, 'currency': 'NOK' }, - // 'bid': { 'amount': 0.005, 'currency': 'NOK' }, - // 'cost': { 'amount': 0.005, 'currency': 'NOK' }, - // 'impressionTrackingUrls': [], - // 'impressionTrackingUrlsEsc': [], - // 'adId': 'adn-id-1347343135', - // 'selectedColumn': '0', - // 'selectedColumnPosition': '0', - // 'renderedPixel': 'https://delivery.adnuntius.com/b/52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN.html', - // 'renderedPixelEsc': 'https%3A%2F%2Fdelivery.adnuntius.com%2Fb%2F52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN.html', - // 'visibleUrl': 'https://delivery.adnuntius.com/s?rt=52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN', - // 'visibleUrlEsc': 'https%3A%2F%2Fdelivery.adnuntius.com%2Fs%3Frt%3D52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN', - // 'viewUrl': 'https://delivery.adnuntius.com/v?rt=52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN', - // 'viewUrlEsc': 'https%3A%2F%2Fdelivery.adnuntius.com%2Fv%3Frt%3D52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN', - // 'rt': '52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN', - // 'creativeWidth': '980', - // 'creativeHeight': '120', - // 'creativeId': 'wgkq587vgtpchsx1', - // 'lineItemId': 'scyjdyv3mzgdsnpf', - // 'layoutId': 'sw6gtws2rdj1kwby', - // 'layoutName': 'Responsive image' - // }, - - // ] - // }, - // { - // 'auId': '000000000008b6bc', - // 'targetId': '456', - // 'matchedAdCount': 0, - // 'responseId': 'adn-rsp-1460129238', - // } - // ] - // } - // } - - describe('inherited functions', function () { - it('exists and is a function', function () { + + describe('inherited functions', function() { + it('exists and is a function', function() { expect(adapter.callBids).to.exist.and.to.be.a('function'); }); }); - describe('isBidRequestValid', function () { - it('should return true when required params found', function () { + describe('isBidRequestValid', function() { + it('should return true when required params found', function() { expect(spec.isBidRequestValid(bidderRequests[0])).to.equal(true); }); }); - describe('buildRequests', function () { - it('Test requests', function () { + describe('buildRequests', function() { + it('Test requests', function() { const request = spec.buildRequests(bidderRequests, {}); expect(request.length).to.equal(1); expect(request[0]).to.have.property('bid'); @@ -337,10 +454,10 @@ describe('adnuntiusBidAdapter', function () { expect(request[0]).to.have.property('url'); expect(request[0].url).to.equal(ENDPOINT_URL); expect(request[0]).to.have.property('data'); - expect(request[0].data).to.equal('{\"adUnits\":[{\"auId\":\"8b6bc\",\"targetId\":\"123\",\"dimensions\":[[640,480],[600,400]]}],\"metaData\":{\"usi\":\"' + usi + '\"}}'); + expect(request[0].data).to.equal('{"adUnits":[{"auId":"000000000008b6bc","targetId":"adn-000000000008b6bc","maxDeals":1,"dimensions":[[640,480],[600,400]]},{"auId":"0000000000000551","targetId":"adn-0000000000000551","maxDeals":0,"dimensions":[[1640,1480],[1600,1400]]}],"metaData":{"usi":"' + usi + '"}}'); }); - it('Test Video requests', function () { + it('Test Video requests', function() { const request = spec.buildRequests(videoBidderRequest, {}); expect(request.length).to.equal(1); expect(request[0]).to.have.property('bid'); @@ -350,12 +467,12 @@ describe('adnuntiusBidAdapter', function () { expect(request[0].url).to.equal(ENDPOINT_URL_VIDEO); }); - it('should pass segments if available in config', function () { + it('should pass segments if available in config', function() { const ortb2 = { user: { data: [{ name: 'adnuntius', - segment: [{ id: 'segment1' }, { id: 'segment2' }] + segment: [{id: 'segment1'}, {id: 'segment2'}] }, { name: 'other', @@ -364,18 +481,18 @@ describe('adnuntiusBidAdapter', function () { } }; - const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidderRequests, { ortb2 })); + const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidderRequests, {ortb2})); expect(request.length).to.equal(1); expect(request[0]).to.have.property('url') expect(request[0].url).to.equal(ENDPOINT_URL_SEGMENTS); }); - it('should skip segments in config if not either id or array of strings', function () { + it('should skip segments in config if not either id or array of strings', function() { const ortb2 = { user: { data: [{ name: 'adnuntius', - segment: [{ id: 'segment1' }, { id: 'segment2' }, { id: 'segment3' }] + segment: [{id: 'segment1'}, {id: 'segment2'}, {id: 'segment3'}] }, { name: 'other', @@ -386,34 +503,34 @@ describe('adnuntiusBidAdapter', function () { } }; - const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidderRequests, { ortb2 })); + const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidderRequests, {ortb2})); expect(request.length).to.equal(1); expect(request[0]).to.have.property('url') expect(request[0].url).to.equal(ENDPOINT_URL_SEGMENTS); }); }); - describe('user privacy', function () { - it('should send GDPR Consent data if gdprApplies', function () { - let request = spec.buildRequests(bidderRequests, { gdprConsent: { gdprApplies: true, consentString: 'consentString' } }); + describe('user privacy', function() { + it('should send GDPR Consent data if gdprApplies', function() { + let request = spec.buildRequests(bidderRequests, {gdprConsent: {gdprApplies: true, consentString: 'consentString'}}); expect(request.length).to.equal(1); expect(request[0]).to.have.property('url') expect(request[0].url).to.equal(ENDPOINT_URL_CONSENT); }); - it('should not send GDPR Consent data if gdprApplies equals undefined', function () { - let request = spec.buildRequests(bidderRequests, { gdprConsent: { gdprApplies: undefined, consentString: 'consentString' } }); + it('should not send GDPR Consent data if gdprApplies equals undefined', function() { + let request = spec.buildRequests(bidderRequests, {gdprConsent: {gdprApplies: undefined, consentString: 'consentString'}}); expect(request.length).to.equal(1); expect(request[0]).to.have.property('url') expect(request[0].url).to.equal(ENDPOINT_URL); }); - it('should pass segments if available in config', function () { + it('should pass segments if available in config', function() { const ortb2 = { user: { data: [{ name: 'adnuntius', - segment: [{ id: 'segment1' }, { id: 'segment2' }] + segment: [{id: 'segment1'}, {id: 'segment2'}] }, { name: 'other', @@ -422,18 +539,18 @@ describe('adnuntiusBidAdapter', function () { } } - const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidderRequests, { ortb2 })); + const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidderRequests, {ortb2})); expect(request.length).to.equal(1); expect(request[0]).to.have.property('url') expect(request[0].url).to.equal(ENDPOINT_URL_SEGMENTS); }); - it('should skip segments in config if not either id or array of strings', function () { + it('should skip segments in config if not either id or array of strings', function() { const ortb2 = { user: { data: [{ name: 'adnuntius', - segment: [{ id: 'segment1' }, { id: 'segment2' }, { id: 'segment3' }] + segment: [{id: 'segment1'}, {id: 'segment2'}, {id: 'segment3'}] }, { name: 'other', @@ -444,44 +561,44 @@ describe('adnuntiusBidAdapter', function () { } }; - const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidderRequests, { ortb2 })); + const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidderRequests, {ortb2})); expect(request.length).to.equal(1); expect(request[0]).to.have.property('url') expect(request[0].url).to.equal(ENDPOINT_URL_SEGMENTS); }); - it('should user user ID if present in ortb2.user.id field', function () { + it('should user user ID if present in ortb2.user.id field', function() { const ortb2 = { user: { id: usi } }; - const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidderRequests, { ortb2 })); + const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidderRequests, {ortb2})); expect(request.length).to.equal(1); expect(request[0]).to.have.property('url') expect(request[0].url).to.equal(ENDPOINT_URL); }); }); - describe('user privacy', function () { - it('should send GDPR Consent data if gdprApplies', function () { - let request = spec.buildRequests(bidderRequests, { gdprConsent: { gdprApplies: true, consentString: 'consentString' } }); + describe('user privacy', function() { + it('should send GDPR Consent data if gdprApplies', function() { + let request = spec.buildRequests(bidderRequests, {gdprConsent: {gdprApplies: true, consentString: 'consentString'}}); expect(request.length).to.equal(1); expect(request[0]).to.have.property('url') expect(request[0].url).to.equal(ENDPOINT_URL_CONSENT); }); - it('should not send GDPR Consent data if gdprApplies equals undefined', function () { - let request = spec.buildRequests(bidderRequests, { gdprConsent: { gdprApplies: undefined, consentString: 'consentString' } }); + it('should not send GDPR Consent data if gdprApplies equals undefined', function() { + let request = spec.buildRequests(bidderRequests, {gdprConsent: {gdprApplies: undefined, consentString: 'consentString'}}); expect(request.length).to.equal(1); expect(request[0]).to.have.property('url') expect(request[0].url).to.equal(ENDPOINT_URL); }); }); - describe('use cookie', function () { - it('should send noCookie in url if set to false.', function () { + describe('use cookie', function() { + it('should send noCookie in url if set to false.', function() { config.setBidderConfig({ bidders: ['adnuntius'], config: { @@ -496,56 +613,211 @@ describe('adnuntiusBidAdapter', function () { }); }); - describe('interpretResponse', function () { - it('should return valid response when passed valid server response', function () { + describe('validate auId', function() { + it('should fail when auId is not hexadecimal', function() { + const invalidRequest = { + bidId: 'adn-000000000008b6bc', + bidder: 'adnuntius', + params: { + auId: 'nothexadecimal', + } + }; + const valid = config.runWithBidder('adnuntius', () => spec.isBidRequestValid(invalidRequest)); + expect(valid).to.equal(false); + }); + + it('should pass when auId is hexadecimal', function() { + const invalidRequest = { + bidId: 'adn-000000000008b6bc', + bidder: 'adnuntius', + params: { + auId: '0123456789abcDEF', + } + }; + const valid = config.runWithBidder('adnuntius', () => spec.isBidRequestValid(invalidRequest)); + expect(valid).to.equal(true); + }); + }); + + describe('request deals', function() { + it('Should set max deals.', function() { + config.setBidderConfig({ + bidders: ['adnuntius'] + }); + + const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidderRequests, {})); + expect(request.length).to.equal(1); + expect(request[0]).to.have.property('url') + expect(request[0].url).to.equal(ENDPOINT_URL); + expect(request[0]).to.have.property('data'); + const data = JSON.parse(request[0].data); + expect(data.adUnits.length).to.equal(2); + expect(bidderRequests[0].params.maxDeals).to.equal(1); + expect(data.adUnits[0].maxDeals).to.equal(bidderRequests[0].params.maxDeals); + expect(bidderRequests[1].params).to.not.have.property('maxBids'); + expect(data.adUnits[1].maxDeals).to.equal(0); + }); + it('Should allow a maximum of 5 deals.', function() { + config.setBidderConfig({ + bidders: ['adnuntius'], + }); + const request = config.runWithBidder('adnuntius', () => spec.buildRequests([ + { + bidId: 'adn-000000000008b6bc', + bidder: 'adnuntius', + params: { + auId: '000000000008b6bc', + network: 'adnuntius', + maxDeals: 10 + } + } + ], {})); + expect(request.length).to.equal(1); + expect(request[0]).to.have.property('url') + expect(request[0].url).to.equal(ENDPOINT_URL); + expect(request[0]).to.have.property('data'); + const data = JSON.parse(request[0].data); + expect(data.adUnits.length).to.equal(1); + expect(data.adUnits[0].maxDeals).to.equal(5); + }); + it('Should allow a minumum of 0 deals.', function() { + config.setBidderConfig({ + bidders: ['adnuntius'], + }); + const request = config.runWithBidder('adnuntius', () => spec.buildRequests([ + { + bidId: 'adn-000000000008b6bc', + bidder: 'adnuntius', + params: { + auId: '000000000008b6bc', + network: 'adnuntius', + maxDeals: -1 + } + } + ], {})); + expect(request.length).to.equal(1); + expect(request[0]).to.have.property('url') + expect(request[0].url).to.equal(ENDPOINT_URL); + expect(request[0]).to.have.property('data'); + const data = JSON.parse(request[0].data); + expect(data.adUnits.length).to.equal(1); + expect(data.adUnits[0].maxDeals).to.equal(0); + }); + it('Should set max deals using bidder config.', function() { + config.setBidderConfig({ + bidders: ['adnuntius'], + config: { + maxDeals: 2 + } + }); + + const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidderRequests, {})); + expect(request.length).to.equal(1); + expect(request[0]).to.have.property('url') + expect(request[0].url).to.equal(ENDPOINT_URL + '&ds=2'); + }); + it('Should allow a maximum of 5 deals when using bidder config.', function() { + config.setBidderConfig({ + bidders: ['adnuntius'], + config: { + maxDeals: 6 + } + }); + + const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidderRequests, {})); + expect(request.length).to.equal(1); + expect(request[0]).to.have.property('url') + expect(request[0].url).to.equal(ENDPOINT_URL + '&ds=5'); + }); + it('Should allow a minimum of 0 deals when using bidder config.', function() { + config.setBidderConfig({ + bidders: ['adnuntius'], + config: { + maxDeals: -1 + } + }); + + const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidderRequests, {})); + expect(request.length).to.equal(1); + expect(request[0]).to.have.property('url') + // The maxDeals value is ignored because it is less than zero + expect(request[0].url).to.equal(ENDPOINT_URL); + }); + }); + + describe('interpretResponse', function() { + it('should return valid response when passed valid server response', function() { const interpretedResponse = spec.interpretResponse(serverResponse, singleBidRequest); - const ad = serverResponse.body.adUnits[0].ads[0] - expect(interpretedResponse).to.have.lengthOf(1); - expect(interpretedResponse[0].cpm).to.equal(ad.cpm.amount); - expect(interpretedResponse[0].width).to.equal(Number(ad.creativeWidth)); - expect(interpretedResponse[0].height).to.equal(Number(ad.creativeHeight)); - expect(interpretedResponse[0].creativeId).to.equal(ad.creativeId); - expect(interpretedResponse[0].currency).to.equal(ad.bid.currency); + expect(interpretedResponse).to.have.lengthOf(2); + + const deal = serverResponse.body.adUnits[0].deals[0]; + expect(interpretedResponse[0].bidderCode).to.equal('adnuntius'); + expect(interpretedResponse[0].cpm).to.equal(deal.bid.amount * 1000); + expect(interpretedResponse[0].width).to.equal(Number(deal.creativeWidth)); + expect(interpretedResponse[0].height).to.equal(Number(deal.creativeHeight)); + expect(interpretedResponse[0].creativeId).to.equal(deal.creativeId); + expect(interpretedResponse[0].currency).to.equal(deal.bid.currency); expect(interpretedResponse[0].netRevenue).to.equal(false); expect(interpretedResponse[0].meta).to.have.property('advertiserDomains'); expect(interpretedResponse[0].meta.advertiserDomains).to.have.lengthOf(1); - expect(interpretedResponse[0].meta.advertiserDomains[0]).to.equal('google.com'); - expect(interpretedResponse[0].ad).to.equal(serverResponse.body.adUnits[0].html); + expect(interpretedResponse[0].meta.advertiserDomains[0]).to.equal('adnuntius.com'); + expect(interpretedResponse[0].ad).to.equal(serverResponse.body.adUnits[0].deals[0].html); expect(interpretedResponse[0].ttl).to.equal(360); + expect(interpretedResponse[0].dealId).to.equal('abc123xyz'); + expect(interpretedResponse[0].dealCount).to.equal(1); + + const ad = serverResponse.body.adUnits[0].ads[0]; + expect(interpretedResponse[1].bidderCode).to.equal('adnuntius'); + expect(interpretedResponse[1].cpm).to.equal(ad.bid.amount * 1000); + expect(interpretedResponse[1].width).to.equal(Number(ad.creativeWidth)); + expect(interpretedResponse[1].height).to.equal(Number(ad.creativeHeight)); + expect(interpretedResponse[1].creativeId).to.equal(ad.creativeId); + expect(interpretedResponse[1].currency).to.equal(ad.bid.currency); + expect(interpretedResponse[1].netRevenue).to.equal(false); + expect(interpretedResponse[1].meta).to.have.property('advertiserDomains'); + expect(interpretedResponse[1].meta.advertiserDomains).to.have.lengthOf(1); + expect(interpretedResponse[1].meta.advertiserDomains[0]).to.equal('adnuntius.com'); + expect(interpretedResponse[1].ad).to.equal(serverResponse.body.adUnits[0].html); + expect(interpretedResponse[1].ttl).to.equal(360); + expect(interpretedResponse[1].dealId).to.equal('not-in-deal-array-here'); + expect(interpretedResponse[1].dealCount).to.equal(0); }); }); - describe('interpretVideoResponse', function () { - it('should return valid response when passed valid server response', function () { + + describe('interpretVideoResponse', function() { + it('should return valid response when passed valid server response', function() { const interpretedResponse = spec.interpretResponse(serverVideoResponse, videoBidRequest); const ad = serverVideoResponse.body.adUnits[0].ads[0] - expect(interpretedResponse).to.have.lengthOf(1); - expect(interpretedResponse[0].cpm).to.equal(ad.cpm.amount); - expect(interpretedResponse[0].width).to.equal(Number(ad.creativeWidth)); - expect(interpretedResponse[0].height).to.equal(Number(ad.creativeHeight)); - expect(interpretedResponse[0].creativeId).to.equal(ad.creativeId); - expect(interpretedResponse[0].currency).to.equal(ad.bid.currency); + const deal = serverVideoResponse.body.adUnits[0].deals[0] + expect(interpretedResponse).to.have.lengthOf(2); + + expect(interpretedResponse[0].bidderCode).to.equal('adnuntius'); + expect(interpretedResponse[0].cpm).to.equal(deal.bid.amount * 1000); + expect(interpretedResponse[0].width).to.equal(Number(deal.creativeWidth)); + expect(interpretedResponse[0].height).to.equal(Number(deal.creativeHeight)); + expect(interpretedResponse[0].creativeId).to.equal(deal.creativeId); + expect(interpretedResponse[0].currency).to.equal(deal.bid.currency); expect(interpretedResponse[0].netRevenue).to.equal(false); expect(interpretedResponse[0].meta).to.have.property('advertiserDomains'); expect(interpretedResponse[0].meta.advertiserDomains).to.have.lengthOf(1); - expect(interpretedResponse[0].meta.advertiserDomains[0]).to.equal('google.com'); - expect(interpretedResponse[0].vastXml).to.equal(serverVideoResponse.body.adUnits[0].vastXml); + expect(interpretedResponse[0].meta.advertiserDomains[0]).to.equal('adnuntius.com'); + expect(interpretedResponse[0].vastXml).to.equal(deal.vastXml); + expect(interpretedResponse[0].dealId).to.equal('abc123xyz'); + expect(interpretedResponse[0].dealCount).to.equal(1); + + expect(interpretedResponse[1].bidderCode).to.equal('adnuntius'); + expect(interpretedResponse[1].cpm).to.equal(ad.bid.amount * 1000); + expect(interpretedResponse[1].width).to.equal(Number(ad.creativeWidth)); + expect(interpretedResponse[1].height).to.equal(Number(ad.creativeHeight)); + expect(interpretedResponse[1].creativeId).to.equal(ad.creativeId); + expect(interpretedResponse[1].currency).to.equal(ad.bid.currency); + expect(interpretedResponse[1].netRevenue).to.equal(false); + expect(interpretedResponse[1].meta).to.have.property('advertiserDomains'); + expect(interpretedResponse[1].meta.advertiserDomains).to.have.lengthOf(1); + expect(interpretedResponse[1].meta.advertiserDomains[0]).to.equal('adnuntius.com'); + expect(interpretedResponse[1].vastXml).to.equal(serverVideoResponse.body.adUnits[0].vastXml); + expect(interpretedResponse[1].dealId).to.equal('not-in-deal-array'); + expect(interpretedResponse[1].dealCount).to.equal(0); }); }); - // describe('interpretNativeResponse', function () { - // it('should return valid response when passed valid server response', function () { - // const interpretedResponse = spec.interpretResponse(serverNativeResponse, nativeBidRequest); - // const ad = serverNativeResponse.body.adUnits[0].ads[0] - // expect(interpretedResponse).to.have.lengthOf(1); - // expect(interpretedResponse[0].cpm).to.equal(ad.cpm.amount); - // expect(interpretedResponse[0].width).to.equal(Number(ad.creativeWidth)); - // expect(interpretedResponse[0].height).to.equal(Number(ad.creativeHeight)); - // expect(interpretedResponse[0].creativeId).to.equal(ad.creativeId); - // expect(interpretedResponse[0].currency).to.equal(ad.bid.currency); - // expect(interpretedResponse[0].netRevenue).to.equal(false); - // expect(interpretedResponse[0].meta).to.have.property('advertiserDomains'); - // expect(interpretedResponse[0].meta.advertiserDomains).to.have.lengthOf(1); - // expect(interpretedResponse[0].meta.advertiserDomains[0]).to.equal('google.com'); - // expect(interpretedResponse[0].native.body).to.equal(serverNativeResponse.body.adUnits[0].ads[0].text.body.content); - // }); - // }); }); diff --git a/test/spec/modules/adqueryBidAdapter_spec.js b/test/spec/modules/adqueryBidAdapter_spec.js index 1c536620803..e9286329d57 100644 --- a/test/spec/modules/adqueryBidAdapter_spec.js +++ b/test/spec/modules/adqueryBidAdapter_spec.js @@ -80,7 +80,11 @@ describe('adqueryBidAdapter', function () { }) describe('buildRequests', function () { - let req = spec.buildRequests([ bidRequest ], { refererInfo: { } })[0] + let req; + beforeEach(() => { + req = spec.buildRequests([ bidRequest ], { refererInfo: { } })[0] + }) + let rdata it('should return request object', function () { @@ -119,6 +123,15 @@ describe('adqueryBidAdapter', function () { it('should include sizes', function () { expect(rdata.sizes).not.be.null }) + + it('should include version', function () { + expect(rdata.v).not.be.null + expect(rdata.v).equal('$prebid.version$') + }) + + it('should include referrer', function () { + expect(rdata.bidPageUrl).not.be.null + }) }) describe('interpretResponse', function () { diff --git a/test/spec/modules/adrelevantisBidAdapter_spec.js b/test/spec/modules/adrelevantisBidAdapter_spec.js index 2612800aa56..7f24176e850 100644 --- a/test/spec/modules/adrelevantisBidAdapter_spec.js +++ b/test/spec/modules/adrelevantisBidAdapter_spec.js @@ -483,15 +483,6 @@ describe('AdrelevantisAdapter', function () { }) describe('interpretResponse', function () { - let bfStub; - before(function() { - bfStub = sinon.stub(bidderFactory, 'getIabSubCategory'); - }); - - after(function() { - bfStub.restore(); - }); - let response = { 'version': '3.0.0', 'tags': [ diff --git a/test/spec/modules/adrinoBidAdapter_spec.js b/test/spec/modules/adrinoBidAdapter_spec.js index 78cea8da9ac..2204ee9e400 100644 --- a/test/spec/modules/adrinoBidAdapter_spec.js +++ b/test/spec/modules/adrinoBidAdapter_spec.js @@ -43,7 +43,7 @@ describe('adrinoBidAdapter', function () { it('should return false when unsupported media type is requested', function () { const bid = { ...validBid }; - bid.mediaTypes = { banner: { sizes: [[300, 250]] } }; + bid.mediaTypes = { video: {} }; expect(spec.isBidRequestValid(bid)).to.equal(false); }); @@ -54,7 +54,47 @@ describe('adrinoBidAdapter', function () { }); }); - describe('buildRequests', function () { + describe('buildBannerRequest', function () { + const bidRequest = { + bidder: 'adrino', + params: { + hash: 'abcdef123456' + }, + mediaTypes: { + banner: { + sizes: [[300, 250], [970, 250]] + } + }, + sizes: [[300, 250], [970, 250]], + userId: { criteoId: '2xqi3F94aHdwWnM3', pubcid: '3ec0b202-7697' }, + adUnitCode: 'adunit-code-2', + bidId: '12345678901234', + bidderRequestId: '98765432109876', + auctionId: '01234567891234', + }; + + it('should build the request correctly', function () { + const result = spec.buildRequests( + [ bidRequest ], + { refererInfo: { page: 'http://example.com/' } } + ); + expect(result.length).to.equal(1); + expect(result[0].method).to.equal('POST'); + expect(result[0].url).to.equal('https://prd-prebid-bidder.adrino.io/bidder/bids/'); + expect(result[0].data[0].adUnitCode).to.equal('adunit-code-2'); + expect(result[0].data[0].bidId).to.equal('12345678901234'); + expect(result[0].data[0].placementHash).to.equal('abcdef123456'); + expect(result[0].data[0].referer).to.equal('http://example.com/'); + expect(result[0].data[0].userAgent).to.equal(navigator.userAgent); + expect(result[0].data[0]).to.have.property('bannerParams'); + expect(result[0].data[0].bannerParams.sizes.length).to.equal(2); + expect(result[0].data[0]).to.have.property('userId'); + expect(result[0].data[0].userId.criteoId).to.equal('2xqi3F94aHdwWnM3'); + expect(result[0].data[0].userId.pubcid).to.equal('3ec0b202-7697'); + }); + }); + + describe('buildNativeRequest', function () { const bidRequest = { bidder: 'adrino', params: { @@ -71,6 +111,15 @@ describe('adrinoBidAdapter', function () { } } }, + nativeParams: { + title: { + required: true + }, + image: { + required: true, + sizes: [[300, 150], [300, 210]] + } + }, userId: { criteoId: '2xqi3F94aHdwWnM3', pubcid: '3ec0b202-7697' }, adUnitCode: 'adunit-code', bidId: '12345678901234', @@ -86,16 +135,17 @@ describe('adrinoBidAdapter', function () { ); expect(result.length).to.equal(1); expect(result[0].method).to.equal('POST'); - expect(result[0].url).to.equal('https://stg-prebid-bidder.adrino.io/bidder/bid/'); - expect(result[0].data.bidId).to.equal('12345678901234'); - expect(result[0].data.placementHash).to.equal('abcdef123456'); - expect(result[0].data.referer).to.equal('http://example.com/'); - expect(result[0].data.userAgent).to.equal(navigator.userAgent); - expect(result[0].data).to.have.property('nativeParams'); - expect(result[0].data).not.to.have.property('gdprConsent'); - expect(result[0].data).to.have.property('userId'); - expect(result[0].data.userId.criteoId).to.equal('2xqi3F94aHdwWnM3'); - expect(result[0].data.userId.pubcid).to.equal('3ec0b202-7697'); + expect(result[0].url).to.equal('https://stg-prebid-bidder.adrino.io/bidder/bids/'); + expect(result[0].data[0].adUnitCode).to.equal('adunit-code'); + expect(result[0].data[0].bidId).to.equal('12345678901234'); + expect(result[0].data[0].placementHash).to.equal('abcdef123456'); + expect(result[0].data[0].referer).to.equal('http://example.com/'); + expect(result[0].data[0].userAgent).to.equal(navigator.userAgent); + expect(result[0].data[0]).to.have.property('nativeParams'); + expect(result[0].data[0]).not.to.have.property('gdprConsent'); + expect(result[0].data[0]).to.have.property('userId'); + expect(result[0].data[0].userId.criteoId).to.equal('2xqi3F94aHdwWnM3'); + expect(result[0].data[0].userId.pubcid).to.equal('3ec0b202-7697'); }); it('should build the request correctly with gdpr', function () { @@ -105,16 +155,17 @@ describe('adrinoBidAdapter', function () { ); expect(result.length).to.equal(1); expect(result[0].method).to.equal('POST'); - expect(result[0].url).to.equal('https://prd-prebid-bidder.adrino.io/bidder/bid/'); - expect(result[0].data.bidId).to.equal('12345678901234'); - expect(result[0].data.placementHash).to.equal('abcdef123456'); - expect(result[0].data.referer).to.equal('http://example.com/'); - expect(result[0].data.userAgent).to.equal(navigator.userAgent); - expect(result[0].data).to.have.property('nativeParams'); - expect(result[0].data).to.have.property('gdprConsent'); - expect(result[0].data).to.have.property('userId'); - expect(result[0].data.userId.criteoId).to.equal('2xqi3F94aHdwWnM3'); - expect(result[0].data.userId.pubcid).to.equal('3ec0b202-7697'); + expect(result[0].url).to.equal('https://prd-prebid-bidder.adrino.io/bidder/bids/'); + expect(result[0].data[0].adUnitCode).to.equal('adunit-code'); + expect(result[0].data[0].bidId).to.equal('12345678901234'); + expect(result[0].data[0].placementHash).to.equal('abcdef123456'); + expect(result[0].data[0].referer).to.equal('http://example.com/'); + expect(result[0].data[0].userAgent).to.equal(navigator.userAgent); + expect(result[0].data[0]).to.have.property('nativeParams'); + expect(result[0].data[0]).to.have.property('gdprConsent'); + expect(result[0].data[0]).to.have.property('userId'); + expect(result[0].data[0].userId.criteoId).to.equal('2xqi3F94aHdwWnM3'); + expect(result[0].data[0].userId.pubcid).to.equal('3ec0b202-7697'); }); it('should build the request correctly without gdpr', function () { @@ -124,22 +175,23 @@ describe('adrinoBidAdapter', function () { ); expect(result.length).to.equal(1); expect(result[0].method).to.equal('POST'); - expect(result[0].url).to.equal('https://prd-prebid-bidder.adrino.io/bidder/bid/'); - expect(result[0].data.bidId).to.equal('12345678901234'); - expect(result[0].data.placementHash).to.equal('abcdef123456'); - expect(result[0].data.referer).to.equal('http://example.com/'); - expect(result[0].data.userAgent).to.equal(navigator.userAgent); - expect(result[0].data).to.have.property('nativeParams'); - expect(result[0].data).not.to.have.property('gdprConsent'); - expect(result[0].data).to.have.property('userId'); - expect(result[0].data.userId.criteoId).to.equal('2xqi3F94aHdwWnM3'); - expect(result[0].data.userId.pubcid).to.equal('3ec0b202-7697'); + expect(result[0].url).to.equal('https://prd-prebid-bidder.adrino.io/bidder/bids/'); + expect(result[0].data[0].adUnitCode).to.equal('adunit-code'); + expect(result[0].data[0].bidId).to.equal('12345678901234'); + expect(result[0].data[0].placementHash).to.equal('abcdef123456'); + expect(result[0].data[0].referer).to.equal('http://example.com/'); + expect(result[0].data[0].userAgent).to.equal(navigator.userAgent); + expect(result[0].data[0]).to.have.property('nativeParams'); + expect(result[0].data[0]).not.to.have.property('gdprConsent'); + expect(result[0].data[0]).to.have.property('userId'); + expect(result[0].data[0].userId.criteoId).to.equal('2xqi3F94aHdwWnM3'); + expect(result[0].data[0].userId.pubcid).to.equal('3ec0b202-7697'); }); }); describe('interpretResponse', function () { it('should interpret the response correctly', function () { - const response = { + const response1 = { requestId: '31662c69728811', mediaType: 'native', cpm: 0.53, @@ -167,13 +219,44 @@ describe('adrinoBidAdapter', function () { } }; + const response2 = { + requestId: '31662c69728812', + mediaType: 'native', + cpm: 0.77, + currency: 'PLN', + creativeId: '859120', + netRevenue: true, + ttl: 600, + width: 1, + height: 1, + noAd: false, + testAd: false, + native: { + title: 'Ad Title', + body: 'Ad Body', + image: { + url: 'http://emisja.contentstream.pl/_/getImageII/?vid=17180728299&typ=cs_300_150&element=IMAGE&scale=1&prefix=adart&nc=1643878278955', + height: 150, + width: 300 + }, + clickUrl: 'http://emisja.contentstream.pl/_/ctr2/?u=https%3A%2F%2Fonline.efortuna.pl%2Fpage%3Fkey%3Dej0xMzUzMTM1NiZsPTE1Mjc1MzY1JnA9NTMyOTA%253D&e=znU3tABN8K4N391dmUxYfte5G9tBaDXELJVo1_-kvaTJH2XwWRw77fmfL2YjcEmrbqRQ3M0GcJ0vPWcLtZlsrf8dWrAEHNoZKAC6JMnZF_65IYhTPbQIJ-zn3ac9TU7gEZftFKksH1al7rMuieleVv9r6_DtrOk_oZcYAe4rMRQM-TiWvivJRPBchAAblE0cqyG7rCunJFpal43sxlYm4GvcBJaYHzErn5PXjEzNbd3xHjkdiap-xU9y6BbfkUZ1xIMS8QZLvwNrTXMFCSfSRN2tgVfEj7KyGdLCITHSaFtuIKT2iW2pxC7f2RtPHnzsEPXH0SgAfhA3OxZ5jkQjOZy0PsO7MiCv3sJai5ezUAOjFgayU91ZhI0Y9r2YpB1tTGIjnO23wot8PvRENlThHQ%3D%3D&ref=https%3A%2F%2Fbox.adrino.cloud%2Ftmielcarz%2Fadrino_prebid%2Ftest_page3.html%3Fpbjs_debug%3Dtrue', + privacyLink: 'https://adrino.pl/wp-content/uploads/2021/01/POLITYKA-PRYWATNOS%CC%81CI-Adrino-Mobile.pdf', + impressionTrackers: [ + 'https://prd-impression-tracker-producer.adrino.io/impression/eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ7XCJpbXByZXNzaW9uSWRcIjpcIjMxNjYyYzY5NzI4ODExXCIsXCJkYXRlXCI6WzIwMjIsMiwzXSxcInBsYWNlbWVudEhhc2hcIjpcIjk0NTVjMDQxYzlkMTI1ZmIwNDE4MWVhMGVlZTJmMmFlXCIsXCJjYW1wYWlnbklkXCI6MTc5MjUsXCJhZHZlcnRpc2VtZW50SWRcIjo5MjA3OSxcInZpc3VhbGlzYXRpb25JZFwiOjg1OTExNSxcImNwbVwiOjUzLjB9IiwiZXhwIjoxNjQzOTE2MjUxLCJpYXQiOjE2NDM5MTU2NTF9.0Y_HvInGl6Xo5xP6rDLC8lzQRGvy-wKe0blk1o8ebWyVRFiUY1JGLUeE0k3sCsPNxgdHAv-o6EcbogpUuqlMJA' + ] + } + }; + const serverResponse = { - body: response + body: { bidResponses: [response1, response2] } }; const result = spec.interpretResponse(serverResponse, {}); - expect(result.length).to.equal(1); - expect(result[0]).to.equal(response); + expect(result.length).to.equal(2); + expect(result[0]).to.equal(response1); + expect(result[0].requestId).to.equal('31662c69728811'); + expect(result[1]).to.equal(response2); + expect(result[1].requestId).to.equal('31662c69728812'); }); it('should return empty array of responses', function () { diff --git a/test/spec/modules/adriverBidAdapter_spec.js b/test/spec/modules/adriverBidAdapter_spec.js index 33084877c14..94202e96dea 100644 --- a/test/spec/modules/adriverBidAdapter_spec.js +++ b/test/spec/modules/adriverBidAdapter_spec.js @@ -321,15 +321,6 @@ describe('adriverAdapter', function () { }); describe('interpretResponse', function () { - let bfStub; - before(function() { - bfStub = sinon.stub(bidderFactory, 'getIabSubCategory'); - }); - - after(function() { - bfStub.restore(); - }); - let response = { 'id': '221594457-1615288400-1-46-', 'bidid': 'D8JW8XU8-L5m7qFMNQGs7i1gcuPvYMEDOKsktw6e9uLy5Eebo9HftVXb0VpKj4R2dXa93i6QmRhjextJVM4y1SqodMAh5vFOb_eVkHA', diff --git a/test/spec/modules/adsinteractiveBidAdapter_spec.js b/test/spec/modules/adsinteractiveBidAdapter_spec.js new file mode 100644 index 00000000000..d0f90bd71de --- /dev/null +++ b/test/spec/modules/adsinteractiveBidAdapter_spec.js @@ -0,0 +1,207 @@ +import { expect } from 'chai'; +import { spec } from 'modules/adsinteractiveBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; + +describe('adsinteractiveBidAdapter', function () { + let bid = { + ortb2: { + site: { + page: 'http://test.com', + domain: 'test.com', + publisher: { + domain: 'test.com', + }, + }, + }, + bidder: 'adsinteractive', + sizes: [[300, 250]], + bidId: '32469kja92389', + params: { + adUnit: 'example_adunit_1', + }, + } + + const bidderRequest = { + refererInfo: { + isAmp: 0 + } } + + describe('build requests', () => { + it('sends bid request to ENDPOINT via POST', function () { + const request = spec.buildRequests([ + { + ortb2: { + site: { + page: 'http://test.com', + domain: 'test.com', + publisher: { + domain: 'test.com', + }, + }, + }, + bidder: 'adsinteractive', + sizes: [[300, 250]], + bidId: '32469kja92389', + params: { + adUnit: 'example_adunit_1', + }, + }, + ], bidderRequest); + expect(request[0].method).to.equal('POST'); + }); + it('sends bid request to adsinteractive endpoint', function () { + const request = spec.buildRequests([ + { + ortb2: { + site: { + page: 'http://test.com', + domain: 'test.com', + publisher: { + domain: 'test.com', + }, + }, + }, + bidder: 'adsinteractive', + sizes: [[300, 250]], + bidId: '32469kja92389', + params: { + adUnit: 'example_adunit_1', + }, + }, + ], bidderRequest); + expect(request[0].url).to.equal('https://pb.adsinteractive.com/prebid'); + }); + }); + + describe('inherited functions', () => { + const adapter = newBidder(spec); + it('exists and is a function', () => { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function () { + it('should return true when necessary information is found', function () { + expect(spec.isBidRequestValid(bid)).to.be.true; + }); + + it('should return false when necessary information is not found', function () { + // empty bid + expect(spec.isBidRequestValid({ bidId: '', params: {} })).to.be.false; + + // empty bidId + bid.bidId = ''; + expect(spec.isBidRequestValid(bid)).to.be.false; + + // empty adUnit + bid.bidId = '32469kja92389'; + bid.params.adUnit = ''; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + + it('returns false when bidder not set to "adsinteractive"', function () { + const invalidBid = { + bidder: 'newyork', + sizes: [[300, 250]], + bidId: '32469kja92389', + params: { + adUnit: 'example_adunit_1', + }, + }; + expect(spec.isBidRequestValid(invalidBid)).to.be.false; + }); + + it('returns false when adUnit is not set in params', function () { + const invalidBid = { + bidder: 'adsinteractive', + sizes: [[300, 250]], + bidId: '32469kja92389', + params: {}, + }; + + expect(spec.isBidRequestValid(invalidBid)).to.be.false; + }); + }); + describe('interpretResponse', function () { + let serverResponse; + let bidRequest = { data: { id: 'adsinteractiverequest-9320' } }; + + beforeEach(function () { + serverResponse = { + body: { + id: '239823rhaldf822', + seatbid: [ + { + bid: [ + { + id: 'fae50ca1-3f69-4b34-bf6d-b2eb0ae3376b', + impid: 'example_adunit_1', + price: 0.49, + netRevenue: true, + ttl: 1000, + meta: {advertiserDomains: []}, + adm: '', + crid: '932048jda99cr', + h: 250, + w: 300, + }, + ], + seat: 'adsinteractive', + }, + ], + cur: 'USD', + }, + }; + }); + + it('validate_response_params', function () { + const newResponse = spec.interpretResponse(serverResponse, bidRequest); + expect(newResponse[0].id).to.be.equal( + 'fae50ca1-3f69-4b34-bf6d-b2eb0ae3376b' + ); + expect(newResponse[0].requestId).to.be.equal( + 'adsinteractiverequest-9320' + ); + expect(newResponse[0].cpm).to.be.equal(0.49); + expect(newResponse[0].width).to.be.equal(300); + expect(newResponse[0].height).to.be.equal(250); + expect(newResponse[0].currency).to.be.equal('USD'); + expect(newResponse[0].ad).to.be.equal( + '' + ); + }); + + it('should correctly reorder the server response', function () { + const newResponse = spec.interpretResponse(serverResponse, bidRequest); + expect(newResponse.length).to.be.equal(1); + expect(newResponse[0]).to.deep.equal({ + id: 'fae50ca1-3f69-4b34-bf6d-b2eb0ae3376b', + requestId: 'adsinteractiverequest-9320', + cpm: 0.49, + netRevenue: true, + ttl: 1000, + width: 300, + height: 250, + meta: {advertiserDomains: []}, + creativeId: '932048jda99cr', + currency: 'USD', + ad: '', + }); + }); + + it('should not add responses if the cpm is 0 or null', function () { + serverResponse.body.seatbid[0].bid[0].price = 0; + let response = spec.interpretResponse(serverResponse, bidRequest); + expect(response).to.deep.equal([]); + + serverResponse.body.seatbid[0].bid[0].price = null; + response = spec.interpretResponse(serverResponse, bidRequest); + expect(response).to.deep.equal([]); + }); + it('should add responses if the cpm is valid', function () { + serverResponse.body.seatbid[0].bid[0].price = 0.5; + let response = spec.interpretResponse(serverResponse, bidRequest); + expect(response).to.not.deep.equal([]); + }); + }); +}); diff --git a/test/spec/modules/adtelligentBidAdapter_spec.js b/test/spec/modules/adtelligentBidAdapter_spec.js index d0ef69ccf08..e40828e6852 100644 --- a/test/spec/modules/adtelligentBidAdapter_spec.js +++ b/test/spec/modules/adtelligentBidAdapter_spec.js @@ -11,16 +11,18 @@ const EXPECTED_ENDPOINTS = [ 'https://ghb.adtelligent.com/v2/auction/' ]; const aliasEP = { - appaloosa: 'https://ghb.hb.appaloosa.media/v2/auction/', - appaloosa_publisherSuffix: 'https://ghb.hb.appaloosa.media/v2/auction/', - onefiftytwomedia: 'https://ghb.ads.152media.com/v2/auction/', - navelix: 'https://ghb.hb.navelix.com/v2/auction/', - bidsxchange: 'https://ghb.hbd.bidsxchange.com/v2/auction/', - streamkey: 'https://ghb.hb.streamkey.net/v2/auction/', - janet: 'https://ghb.bidder.jmgads.com/v2/auction/', - pgam: 'https://ghb.pgamssp.com/v2/auction/', - ocm: 'https://ghb.cenarius.orangeclickmedia.com/v2/auction/', - vidcrunchllc: 'https://ghb.platform.vidcrunch.com/v2/auction/', + 'appaloosa': 'https://ghb.hb.appaloosa.media/v2/auction/', + 'appaloosa_publisherSuffix': 'https://ghb.hb.appaloosa.media/v2/auction/', + 'onefiftytwomedia': 'https://ghb.ads.152media.com/v2/auction/', + 'navelix': 'https://ghb.hb.navelix.com/v2/auction/', + 'bidsxchange': 'https://ghb.hbd.bidsxchange.com/v2/auction/', + 'streamkey': 'https://ghb.hb.streamkey.net/v2/auction/', + 'janet': 'https://ghb.bidder.jmgads.com/v2/auction/', + 'pgam': 'https://ghb.pgamssp.com/v2/auction/', + 'ocm': 'https://ghb.cenarius.orangeclickmedia.com/v2/auction/', + 'vidcrunchllc': 'https://ghb.platform.vidcrunch.com/v2/auction/', + '9dotsmedia': 'https://ghb.platform.audiodots.com/v2/auction/', + 'copper6': 'https://ghb.app.copper6.com/v2/auction/', }; const DEFAULT_ADATPER_REQ = { bidderCode: 'adtelligent' }; @@ -154,6 +156,10 @@ const displayBidderRequestWithConsents = { gdprApplies: true, consentString: 'test' }, + gppConsent: { + gppString: 'abc12345234', + applicableSections: [7, 8] + }, uspConsent: 'iHaveIt' }; @@ -362,6 +368,10 @@ describe('adtelligentBidAdapter', () => { expect(bidRequestWithPubSettingsData.GDPR).to.be.equal(1); expect(bidRequestWithPubSettingsData.GDPRConsent).to.be.equal(displayBidderRequestWithConsents.gdprConsent.consentString); }); + it('sets GPP flags', () => { + expect(bidRequestWithPubSettingsData.GPP).to.be.equal(displayBidderRequestWithConsents.gppConsent.gppString); + expect(bidRequestWithPubSettingsData.GPPSid).to.be.equal('7,8'); + }); it('sets USP', () => { expect(bidRequestWithPubSettingsData.USP).to.be.equal(displayBidderRequestWithConsents.uspConsent); }) diff --git a/test/spec/modules/adtrueBidAdapter_spec.js b/test/spec/modules/adtrueBidAdapter_spec.js index b499d077a3c..df8f9013534 100644 --- a/test/spec/modules/adtrueBidAdapter_spec.js +++ b/test/spec/modules/adtrueBidAdapter_spec.js @@ -28,7 +28,11 @@ describe('AdTrueBidAdapter', function () { bidId: '23acc48ad47af5', requestId: '0fb4905b-9456-4152-86be-c6f6d259ba99', bidderRequestId: '1c56ad30b9b8ca8', - transactionId: '92489f71-1bf2-49a0-adf9-000cea934729', + ortb2Imp: { + ext: { + tid: '92489f71-1bf2-49a0-adf9-000cea934729', + } + }, schain: { 'ver': '1.0', 'complete': 1, @@ -280,8 +284,8 @@ describe('AdTrueBidAdapter', function () { expect(data.cur[0]).to.equal('USD'); // currency expect(data.site.domain).to.be.a('string'); // domain should be set expect(data.site.publisher.id).to.equal(bidRequests[0].params.publisherId); // publisher Id - expect(data.ext.wrapper.transactionId).to.equal(bidRequests[0].transactionId); // Prebid TransactionId - expect(data.source.tid).to.equal(bidRequests[0].transactionId); // Prebid TransactionId + expect(data.ext.wrapper.transactionId).to.equal(bidRequests[0].ortb2Imp.ext.tid); // Prebid TransactionId + expect(data.source.tid).to.equal(bidRequests[0].ortb2Imp.ext.tid); // Prebid TransactionId expect(data.imp[0].id).to.equal(bidRequests[0].bidId); // Prebid bid id is passed as id expect(data.imp[0].bidfloor).to.equal(bidRequests[0].params.reserve); // reverse expect(data.imp[0].tagid).to.equal(bidRequests[0].params.zoneId); // zoneId @@ -303,8 +307,8 @@ describe('AdTrueBidAdapter', function () { expect(data.cur[0]).to.equal('USD'); // currency expect(data.site.domain).to.be.a('string'); // domain should be set expect(data.site.publisher.id).to.equal(bidRequests[0].params.publisherId); // publisher Id - expect(data.ext.wrapper.transactionId).to.equal(bidRequests[0].transactionId); // Prebid TransactionId - expect(data.source.tid).to.equal(bidRequests[0].transactionId); // Prebid TransactionId + expect(data.ext.wrapper.transactionId).to.equal(bidRequests[0].ortb2Imp.ext.tid); // Prebid TransactionId + expect(data.source.tid).to.equal(bidRequests[0].ortb2Imp.ext.tid); // Prebid TransactionId expect(data.imp[0].id).to.equal(bidRequests[0].bidId); // Prebid bid id is passed as id expect(data.imp[0].bidfloor).to.equal(parseFloat(bidRequests[0].params.reserve)); // reverse expect(data.imp[0].tagid).to.equal(bidRequests[0].params.zoneId); // zoneId @@ -323,8 +327,8 @@ describe('AdTrueBidAdapter', function () { expect(data.cur[0]).to.equal('USD'); // currency expect(data.site.domain).to.be.a('string'); // domain should be set expect(data.site.publisher.id).to.equal(bidRequests[0].params.publisherId); // publisher Id - expect(data.ext.wrapper.transactionId).to.equal(bidRequests[0].transactionId); // Prebid TransactionId - expect(data.source.tid).to.equal(bidRequests[0].transactionId); // Prebid TransactionId + expect(data.ext.wrapper.transactionId).to.equal(bidRequests[0].ortb2Imp.ext.tid); // Prebid TransactionId + expect(data.source.tid).to.equal(bidRequests[0].ortb2Imp.ext.tid); // Prebid TransactionId expect(data.imp[0].id).to.equal(bidRequests[0].bidId); // Prebid bid id is passed as id expect(data.imp[0].bidfloor).to.equal(parseFloat(bidRequests[0].params.reserve)); // reverse expect(data.imp[0].tagid).to.equal(bidRequests[0].params.zoneId); // zoneId diff --git a/test/spec/modules/aduptechBidAdapter_spec.js b/test/spec/modules/aduptechBidAdapter_spec.js index 8dbdbbfeab5..2b909ebe20d 100644 --- a/test/spec/modules/aduptechBidAdapter_spec.js +++ b/test/spec/modules/aduptechBidAdapter_spec.js @@ -469,7 +469,11 @@ describe('AduptechBidAdapter', () => { { bidId: 'bidId1', adUnitCode: 'adUnitCode1', - transactionId: 'transactionId1', + ortb2Imp: { + ext: { + tid: 'transactionId1', + } + }, mediaTypes: { banner: { sizes: [[100, 200], [300, 400]] @@ -483,7 +487,11 @@ describe('AduptechBidAdapter', () => { { bidId: 'bidId2', adUnitCode: 'adUnitCode2', - transactionId: 'transactionId2', + ortb2Imp: { + ext: { + tid: 'transactionId2', + } + }, mediaTypes: { banner: { sizes: [[100, 200]] @@ -497,7 +505,13 @@ describe('AduptechBidAdapter', () => { { bidId: 'bidId3', adUnitCode: 'adUnitCode3', - transactionId: 'transactionId3', + ortb2Imp: { + ext: { + tid: { + transactionId: 'transactionId3', + } + } + }, mediaTypes: { native: { image: { @@ -536,14 +550,14 @@ describe('AduptechBidAdapter', () => { imp: [ { bidId: validBidRequests[0].bidId, - transactionId: validBidRequests[0].transactionId, + transactionId: validBidRequests[0].ortb2Imp.ext.tid, adUnitCode: validBidRequests[0].adUnitCode, params: validBidRequests[0].params, banner: validBidRequests[0].mediaTypes.banner }, { bidId: validBidRequests[1].bidId, - transactionId: validBidRequests[1].transactionId, + transactionId: validBidRequests[1].ortb2Imp.ext.tid, adUnitCode: validBidRequests[1].adUnitCode, params: validBidRequests[1].params, banner: validBidRequests[1].mediaTypes.banner @@ -565,7 +579,7 @@ describe('AduptechBidAdapter', () => { imp: [ { bidId: validBidRequests[2].bidId, - transactionId: validBidRequests[2].transactionId, + transactionId: validBidRequests[2].ortb2Imp.ext.tid, adUnitCode: validBidRequests[2].adUnitCode, params: validBidRequests[2].params, native: validBidRequests[2].mediaTypes.native @@ -592,7 +606,11 @@ describe('AduptechBidAdapter', () => { const bidRequest = { bidId: 'bidId1', adUnitCode: 'adUnitCode1', - transactionId: 'transactionId1', + ortb2Imp: { + ext: { + tid: 'transactionId1', + } + }, mediaTypes: { banner: { sizes: [[100, 200], [300, 400]] @@ -630,7 +648,7 @@ describe('AduptechBidAdapter', () => { imp: [ { bidId: bidRequest.bidId, - transactionId: bidRequest.transactionId, + transactionId: bidRequest.ortb2Imp.ext.tid, adUnitCode: bidRequest.adUnitCode, params: bidRequest.params, banner: { diff --git a/test/spec/modules/advangelistsBidAdapter_spec.js b/test/spec/modules/advangelistsBidAdapter_spec.js index e1cd6977c5d..143d85a1ab6 100755 --- a/test/spec/modules/advangelistsBidAdapter_spec.js +++ b/test/spec/modules/advangelistsBidAdapter_spec.js @@ -93,7 +93,6 @@ describe('advangelistsBidAdapter', function () { delete bidResponseVid['ad']; expect(bidResponseVid).to.deep.equal({ requestId: bidRequestsVid[0].bidId, - bidderCode: 'advangelists', creativeId: serverResponseVid.seatbid[0].bid[0].crid, cpm: serverResponseVid.seatbid[0].bid[0].price, width: serverResponseVid.seatbid[0].bid[0].w, diff --git a/test/spec/modules/adxcgBidAdapter_spec.js b/test/spec/modules/adxcgBidAdapter_spec.js index e2d9e2a15e4..65c7584b428 100644 --- a/test/spec/modules/adxcgBidAdapter_spec.js +++ b/test/spec/modules/adxcgBidAdapter_spec.js @@ -165,7 +165,10 @@ describe('Adxcg adapter', function () { bidId: 'bidId', params: {siteId: 'siteId'}, }]; - let request = JSON.parse(spec.buildRequests(validBidRequests, {refererInfo: {referer: 'page'}, auctionId: 'tid'}).data); + let request = JSON.parse(spec.buildRequests(validBidRequests, { + refererInfo: {referer: 'page'}, + ortb2: {source: {tid: 'tid'}} + }).data); assert.equal(request.source.tid, 'tid'); assert.equal(request.source.fd, 1); diff --git a/test/spec/modules/adyoulikeBidAdapter_spec.js b/test/spec/modules/adyoulikeBidAdapter_spec.js index 24cede20352..7310f736f7e 100644 --- a/test/spec/modules/adyoulikeBidAdapter_spec.js +++ b/test/spec/modules/adyoulikeBidAdapter_spec.js @@ -44,7 +44,11 @@ describe('Adyoulike Adapter', function () { 'params': { 'placement': 'placement_0' }, - 'transactionId': 'bid_id_0_transaction_id' + 'ortb2Imp': { + 'ext': { + 'tid': 'bid_id_0_transaction_id' + } + }, } ], }; @@ -108,7 +112,11 @@ describe('Adyoulike Adapter', function () { ] } }, - 'transactionId': 'bid_id_0_transaction_id' + ortb2Imp: { + ext: { + tid: 'bid_id_0_transaction_id' + } + } } ]; @@ -142,7 +150,11 @@ describe('Adyoulike Adapter', function () { }, } }, - 'transactionId': 'bid_id_0_transaction_id' + ortb2Imp: { + ext: { + tid: 'bid_id_0_transaction_id' + } + }, } ]; @@ -162,7 +174,11 @@ describe('Adyoulike Adapter', function () { 'playerSize': [[ 640, 480 ]] } }, - 'transactionId': 'bid_id_0_transaction_id' + ortb2Imp: { + ext: { + tid: 'bid_id_0_transaction_id' + } + }, } ]; @@ -185,7 +201,11 @@ describe('Adyoulike Adapter', function () { } }, }, - 'transactionId': 'bid_id_0_transaction_id' + ortb2Imp: { + ext: { + tid: 'bid_id_0_transaction_id' + } + }, } ]; @@ -286,7 +306,11 @@ describe('Adyoulike Adapter', function () { {'sizes': ['300x250'] } }, - 'transactionId': 'bid_id_0_transaction_id' + ortb2Imp: { + ext: { + tid: 'bid_id_0_transaction_id' + } + }, } ]; @@ -304,7 +328,11 @@ describe('Adyoulike Adapter', function () { {'sizes': ['300x250'] } }, - 'transactionId': 'bid_id_0_transaction_id' + ortb2Imp: { + ext: { + tid: 'bid_id_0_transaction_id' + } + }, }, { 'bidId': 'bid_id_1', @@ -319,7 +347,11 @@ describe('Adyoulike Adapter', function () { {'sizes': ['300x600'] } }, - 'transactionId': 'bid_id_1_transaction_id' + ortb2Imp: { + ext: { + tid: 'bid_id_1_transaction_id' + } + }, }, { 'bidId': 'bid_id_2', @@ -327,7 +359,11 @@ describe('Adyoulike Adapter', function () { 'placementCode': 'adunit/hb-2', 'params': {}, 'sizes': '300x400', - 'transactionId': 'bid_id_2_transaction_id' + ortb2Imp: { + ext: { + tid: 'bid_id_2_transaction_id' + } + }, }, { 'bidId': 'bid_id_3', @@ -336,7 +372,11 @@ describe('Adyoulike Adapter', function () { 'params': { 'placement': 'placement_3' }, - 'transactionId': 'bid_id_3_transaction_id' + ortb2Imp: { + ext: { + tid: 'bid_id_3_transaction_id' + } + }, } ]; @@ -672,10 +712,18 @@ describe('Adyoulike Adapter', function () { 'auctionId': '1d1a030790a475', 'bidderRequestId': '22edbae2733bf6', 'timeout': 3000, - 'userId': { - pubcid: '01EAJWWNEPN3CYMM5N8M5VXY22', - unsuported: '666' - } + 'userIdAsEids': + [ + { + 'source': 'pubcid.org', + 'uids': [ + { + 'atype': 1, + 'id': '01EAJWWNEPN3CYMM5N8M5VXY22' + } + ] + } + ] }; bidderRequest.bids = bidRequestWithSinglePlacement; @@ -684,13 +732,7 @@ describe('Adyoulike Adapter', function () { const payload = JSON.parse(request.data); expect(payload.userId).to.exist; - expect(payload.userId).to.deep.equal([{ - 'source': 'pubcid.org', - 'uids': [{ - 'atype': 1, - 'id': '01EAJWWNEPN3CYMM5N8M5VXY22' - }] - }]); + expect(payload.userId).to.deep.equal(bidderRequest.userIdAsEids); }); it('sends bid request to endpoint with single placement', function () { diff --git a/test/spec/modules/afpBidAdapter_spec.js b/test/spec/modules/afpBidAdapter_spec.js index 5b658a66351..12bd19da9ca 100644 --- a/test/spec/modules/afpBidAdapter_spec.js +++ b/test/spec/modules/afpBidAdapter_spec.js @@ -125,7 +125,11 @@ const getTransformedConfig = ({mediaTypes, params}) => { bidId, bidder: BIDDER_CODE, mediaTypes: mediaTypes, - transactionId, + ortb2Imp: { + ext: { + tid: transactionId + } + } } } const validBidRequests = Object.keys(configByPlaceType).map(key => getTransformedConfig(configByPlaceType[key])) diff --git a/test/spec/modules/aidemBidAdapter_spec.js b/test/spec/modules/aidemBidAdapter_spec.js index f58e49eb364..8b401491ba0 100644 --- a/test/spec/modules/aidemBidAdapter_spec.js +++ b/test/spec/modules/aidemBidAdapter_spec.js @@ -171,7 +171,11 @@ const DEFAULT_VALID_BANNER_REQUESTS = [ placementId: '13144370' }, src: 'client', - transactionId: '54a58774-7a41-494e-9aaf-fa7b79164f0c' + ortb2Imp: { + ext: { + tid: '54a58774-7a41-494e-9aaf-fa7b79164f0c', + }, + }, } ]; @@ -196,7 +200,11 @@ const DEFAULT_VALID_VIDEO_REQUESTS = [ placementId: '13144370' }, src: 'client', - transactionId: '54a58774-7a41-494e-9aaf-fa7b79164f0c' + ortb2Imp: { + ext: { + tid: '54a58774-7a41-494e-9aaf-fa7b79164f0c', + } + }, } ]; @@ -276,7 +284,7 @@ const SERVER_RESPONSE_VIDEO = { }, } -const WIN_NOTICE = { +const WIN_NOTICE_WEB = { 'adId': '3a20ee5dc78c1e', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'creativeId': '24277955', @@ -297,9 +305,74 @@ const WIN_NOTICE = { 'USD' ], 'mediaType': 'banner', - 'advertiserDomains': [ - 'abc.com' + 'meta': { + 'advertiserDomains': [ + 'cloudflare.com' + ], + 'ext': {} + }, + 'size': '300x250', + 'params': [ + { + 'placementId': '13144370', + 'siteId': '23434', + 'publisherId': '7689670753' + } ], + 'width': 300, + 'height': 250, + 'status': 'rendered', + 'transactionId': 'ce089116-4251-45c3-bdbb-3a03cb13816b', + 'ttl': 300, + 'requestTimestamp': 1666796241007, + 'responseTimestamp': 1666796241021, + metrics: { + getMetrics() { + return { + + } + } + } +} + +const WIN_NOTICE_APP = { + 'adId': '3a20ee5dc78c1e', + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'creativeId': '24277955', + 'cpm': 1, + 'netRevenue': false, + 'adserverTargeting': { + 'hb_bidder': 'aidem', + 'hb_adid': '3a20ee5dc78c1e', + 'hb_pb': '1.00', + 'hb_size': '300x250', + 'hb_source': 'client', + 'hb_format': 'banner', + 'hb_adomain': 'example.com' + }, + + 'auctionId': '85864730-6cbc-4e56-bc3c-a4a6596dca5b', + 'currency': [ + 'USD' + ], + 'mediaType': 'banner', + 'meta': { + 'advertiserDomains': [ + 'cloudflare.com' + ], + 'ext': { + 'app': { + 'app_bundle': '{{APP_BUNDLE}}', + 'app_id': '{{APP_ID}}', + 'app_name': '{{APP_NAME}}', + 'app_store_url': '{{APP_STORE_URL}}', + 'inventory_source': '{{INVENTORY_SOURCE}}' + }, + 'win_notice_ext': { + 'seatid': '{{SEAT_ID}}' + } + } + }, 'size': '300x250', 'params': [ { @@ -549,11 +622,13 @@ describe('Aidem adapter', () => { expect(spec.onBidWon).to.exist.and.to.be.a('function') }); - it(`should send a valid bid won notice`, function () { - spec.onBidWon(WIN_NOTICE); - // server.respondWith('POST', WIN_EVENT_URL, [ - // 400, {'Content-Type': 'application/json'}, ) - // ]); + it(`should send a valid bid won notice from web environment`, function () { + spec.onBidWon(WIN_NOTICE_WEB); + expect(server.requests.length).to.equal(1); + }); + + it(`should send a valid bid won notice from app environment`, function () { + spec.onBidWon(WIN_NOTICE_APP); expect(server.requests.length).to.equal(1); }); }); diff --git a/test/spec/modules/airgridRtdProvider_spec.js b/test/spec/modules/airgridRtdProvider_spec.js index 5b1df6f9d4c..239d583d033 100644 --- a/test/spec/modules/airgridRtdProvider_spec.js +++ b/test/spec/modules/airgridRtdProvider_spec.js @@ -1,7 +1,7 @@ -import { config } from 'src/config.js'; -import { deepAccess } from 'src/utils.js'; -import { getAdUnits } from '../../fixtures/fixtures.js'; +import {config} from 'src/config.js'; +import {deepAccess} from 'src/utils.js'; import * as agRTD from 'modules/airgridRtdProvider.js'; +import {loadExternalScript} from '../../../src/adloader.js'; const MATCHED_AUDIENCES = ['travel', 'sport']; const RTD_CONFIG = { @@ -40,6 +40,7 @@ describe('airgrid RTD Submodule', function () { expect(agRTD.airgridSubmodule.init(RTD_CONFIG.dataProviders[0])).to.equal( true ); + expect(loadExternalScript.called).to.be.true }); it('should attach script to DOM with correct config', function () { @@ -69,67 +70,24 @@ describe('airgrid RTD Submodule', function () { }); describe('Add matched audiences', function () { - it('merges matched audiences on appnexus AdUnits', function () { - const adUnits = getAdUnits(); - getDataFromLocalStorageStub - .withArgs(agRTD.AG_AUDIENCE_IDS_KEY) - .returns(JSON.stringify(MATCHED_AUDIENCES)); - agRTD.passAudiencesToBidders({ adUnits }, () => {}, {}, {}); - - adUnits.forEach((adUnit) => { - adUnit.bids.forEach((bid) => { - const { bidder, params } = bid; - if (bidder === 'appnexus') { - expect(deepAccess(params, 'keywords.perid')).to.eql( - MATCHED_AUDIENCES - ); - } - }); - }); - }); - - it('does not merge audiences on appnexus adunits, since none are matched', function () { - const adUnits = getAdUnits(); - getDataFromLocalStorageStub - .withArgs(agRTD.AG_AUDIENCE_IDS_KEY) - .returns(undefined); - agRTD.passAudiencesToBidders({ adUnits }, () => {}, {}, {}); - - adUnits.forEach((adUnit) => { - adUnit.bids.forEach((bid) => { - const { bidder, params } = bid; - if (bidder === 'appnexus') { - expect(deepAccess(params, 'keywords.perid')).to.be.undefined; - } - }); - }); - }); - it('sets bidder specific ORTB2 config', function () { getDataFromLocalStorageStub .withArgs(agRTD.AG_AUDIENCE_IDS_KEY) .returns(JSON.stringify(MATCHED_AUDIENCES)); const audiences = agRTD.getMatchedAudiencesFromStorage(); - const bidderOrtb2 = agRTD.getAudiencesAsBidderOrtb2(RTD_CONFIG.dataProviders[0], audiences); - const bidders = RTD_CONFIG.dataProviders[0].params.bidders; - Object.keys(bidderOrtb2).forEach((bidder) => { - if (bidders.indexOf(bidder) === -1) return; - expect(deepAccess(bidderOrtb2[bidder], 'ortb2.user.ext.data.airgrid')).to.eql(MATCHED_AUDIENCES); - }); - }); + const bidderOrtb2 = {}; - it('sets audiences using appnexus auction level keywords', function () { - getDataFromLocalStorageStub - .withArgs(agRTD.AG_AUDIENCE_IDS_KEY) - .returns(JSON.stringify(MATCHED_AUDIENCES)); - const audiences = agRTD.getMatchedAudiencesFromStorage(); - agRTD.setAudiencesUsingAppNexusAuctionKeywords(audiences); + agRTD.setAudiencesAsBidderOrtb2({ortb2Fragments: {bidder: bidderOrtb2}}, RTD_CONFIG.dataProviders[0], audiences); - const bidderConfig = config.getConfig(); - expect(deepAccess(bidderConfig, 'appnexusAuctionKeywords.perid')).to.eql( - MATCHED_AUDIENCES - ); + const bidders = RTD_CONFIG.dataProviders[0].params.bidders + + bidders.forEach((bidder) => { + const ortb2 = bidderOrtb2[bidder]; + MATCHED_AUDIENCES.forEach((audience) => { + expect(ortb2.user.data[0].segment.find(segment => segment.id === audience)).to.exist; + }) + }); }); }); }); diff --git a/test/spec/modules/amxBidAdapter_spec.js b/test/spec/modules/amxBidAdapter_spec.js index d71e9ab5cba..984c443344d 100644 --- a/test/spec/modules/amxBidAdapter_spec.js +++ b/test/spec/modules/amxBidAdapter_spec.js @@ -2,6 +2,7 @@ import { expect } from 'chai'; import { spec } from 'modules/amxBidAdapter.js'; import { createEidsArray } from 'modules/userId/eids.js'; import { BANNER, VIDEO } from 'src/mediaTypes.js'; +import { config } from 'src/config.js'; import * as utils from 'src/utils.js'; const sampleRequestId = '82c91e127a9b93e'; @@ -34,9 +35,19 @@ const sampleBidderRequest = { consentString: utils.getUniqueIdentifierStr(), vendorData: {}, }, - auctionId: utils.getUniqueIdentifierStr(), + gppConsent: { + gppString: 'example', + applicableSections: 'example' + }, + + auctionId: null, + uspConsent: '1YYY', refererInfo: { + reachedTop: true, + numIframes: 10, + stack: ['https://www.prebid.org'], + canonicalUrl: 'https://prebid.org', location: 'https://www.prebid.org', site: 'prebid.org', topmostLocation: 'https://www.prebid.org', @@ -81,7 +92,8 @@ const sampleBidRequestBase = { adUnitCode: 'div-gpt-ad-example', transactionId: utils.getUniqueIdentifierStr(), bidId: sampleRequestId, - auctionId: utils.getUniqueIdentifierStr(), + + auctionId: null, }; const schainConfig = { @@ -195,9 +207,12 @@ describe('AmxBidAdapter', () => { }); }); describe('getUserSync', () => { - it('will only sync from valid server responses', () => { + it('Will perform an iframe sync even if there is no server response..', () => { const syncs = spec.getUserSyncs({ iframeEnabled: true }); - expect(syncs).to.eql([]); + expect(syncs).to.eql([{ + type: 'iframe', + url: 'https://prebid.a-mo.net/isyn?gdpr_consent=&gdpr=0&us_privacy=&gpp=&gpp_sid=' + }]); }); it('will return valid syncs from a server response', () => { @@ -260,6 +275,15 @@ describe('AmxBidAdapter', () => { expect(data.tm).to.equal(true); }); + it('will attach additional referrer info data', () => { + const { data } = spec.buildRequests([sampleBidRequestBase], sampleBidderRequest); + expect(data.ri.r).to.equal(sampleBidderRequest.refererInfo.topmostLocation); + expect(data.ri.t).to.equal(sampleBidderRequest.refererInfo.reachedTop); + expect(data.ri.l).to.equal(sampleBidderRequest.refererInfo.numIframes); + expect(data.ri.s).to.equal(sampleBidderRequest.refererInfo.stack); + expect(data.ri.c).to.equal(sampleBidderRequest.refererInfo.canonicalUrl); + }); + it('if prebid is in an iframe, will use the frame url as domain, if the topmost is not avialable', () => { const { data } = spec.buildRequests([sampleBidRequestBase], { ...sampleBidderRequest, @@ -286,7 +310,7 @@ describe('AmxBidAdapter', () => { expect(data.re).to.equal('http://search-traffic-source.com'); }); - it('handles referer data and GDPR, USP Consent, COPPA', () => { + it('handles GDPR, USP Consent, COPPA, and GPP', () => { const { data } = spec.buildRequests( [sampleBidRequestBase], sampleBidderRequest @@ -295,6 +319,7 @@ describe('AmxBidAdapter', () => { expect(data.gs).to.equal(sampleBidderRequest.gdprConsent.gdprApplies); expect(data.gc).to.equal(sampleBidderRequest.gdprConsent.consentString); expect(data.usp).to.equal(sampleBidderRequest.uspConsent); + expect(data.gpp).to.equal(sampleBidderRequest.gppConsent); expect(data.cpp).to.equal(0); }); @@ -316,6 +341,73 @@ describe('AmxBidAdapter', () => { expect(data.bwc).to.equal(bidderWinsCount); expect(data.trc).to.equal(0); }); + + it('will attach sync configuration', () => { + const request = () => spec.buildRequests( + [sampleBidRequestBase], + sampleBidderRequest + ); + + const setConfig = (filterSettings) => + config.setConfig({ + userSync: { + syncsPerBidder: 2, + syncDelay: 2300, + syncEnabled: true, + filterSettings, + } + }); + + const test = (filterSettings) => { + setConfig(filterSettings); + return request().data.sync; + } + + const base = { d: 2300, l: 2, e: true }; + + const tests = [[ + undefined, + { ...base, t: 0 } + ], [{ + image: { + bidders: '*', + filter: 'include' + }, + iframe: { + bidders: '*', + filter: 'include' + } + }, { ...base, t: 3 }], [{ + image: { + bidders: ['amx'], + }, + iframe: { + bidders: '*', + filter: 'include' + } + }, { ...base, t: 3 }], [{ + image: { + bidders: ['other'], + }, + iframe: { + bidders: '*' + } + }, { ...base, t: 2 }], [{ + image: { + bidders: ['amx'] + }, + iframe: { + bidders: ['amx'], + filter: 'exclude' + } + }, { ...base, t: 1 }]] + + for (let i = 0, l = tests.length; i < l; i++) { + const [result, expected] = tests[i]; + expect(test(result), `input: ${JSON.stringify(result)}`).to.deep.equal(expected); + } + }); + it('will forward first-party data', () => { const { data } = spec.buildRequests( [sampleBidRequestBase], @@ -405,7 +497,7 @@ describe('AmxBidAdapter', () => { it('can build a video request', () => { const { data } = spec.buildRequests( - [{...sampleBidRequestVideo, params: { ...sampleBidRequestVideo.params, adUnitId: 'custom-auid' }}], + [{ ...sampleBidRequestVideo, params: { ...sampleBidRequestVideo.params, adUnitId: 'custom-auid' } }], sampleBidderRequest ); expect(Object.keys(data.m).length).to.equal(1); @@ -509,7 +601,14 @@ describe('AmxBidAdapter', () => { before(() => { _Image = window.Image; window.Image = class FakeImage { + _src = ''; + + get src() { + return this._src; + } + set src(value) { + this._src = value; firedPixels.push(value); } }; diff --git a/test/spec/modules/amxIdSystem_spec.js b/test/spec/modules/amxIdSystem_spec.js index dea79e87baa..c1ae2c791d5 100644 --- a/test/spec/modules/amxIdSystem_spec.js +++ b/test/spec/modules/amxIdSystem_spec.js @@ -1,4 +1,4 @@ -import { amxIdSubmodule } from 'modules/amxIdSystem.js'; +import { amxIdSubmodule, storage } from 'modules/amxIdSystem.js'; import { server } from 'test/mocks/xhr.js'; import * as utils from 'src/utils.js'; @@ -48,38 +48,17 @@ describe('validateConfig', () => { logErrorSpy.restore(); }); - it('should return undefined if config.storage is not present', () => { + it('should allow configuration with no storage', () => { expect( amxIdSubmodule.getId( { ...config, - storage: null, + storage: undefined }, null, null ) - ).to.equal(undefined); - - expect(logErrorSpy.calledOnce).to.be.true; - expect(logErrorSpy.lastCall.lastArg).to.contain('storage is required'); - }); - - it('should return undefined if config.storage.type !== "html5"', () => { - expect( - amxIdSubmodule.getId( - { - ...config, - storage: { - type: 'cookie', - }, - }, - null, - null - ) - ).to.equal(undefined); - - expect(logErrorSpy.calledOnce).to.be.true; - expect(logErrorSpy.lastCall.lastArg).to.contain('cookie'); + ).to.not.equal(undefined); }); it('should return undefined if expires > 30', () => { @@ -111,10 +90,18 @@ describe('getId', () => { }); it('should call the sync endpoint and accept a valid response', () => { + storage.setDataInLocalStorage('__amuidpb', TEST_ID); + const { callback } = amxIdSubmodule.getId(config, null, null); callback(spy); const [request] = server.requests; + expect(request.withCredentials).to.be.true + expect(request.requestHeaders['Content-Type']).to.match(/text\/plain/) + + const { search } = utils.parseUrl(request.url); + expect(search.av).to.equal(amxIdSubmodule.version); + expect(search.am).to.equal(TEST_ID); expect(request.method).to.equal('GET'); request.respond( @@ -187,7 +174,7 @@ describe('getId', () => { ); const [, secondRequest] = server.requests; - expect(secondRequest.url).to.be.equal(intermediateValue); + expect(secondRequest.url).to.match(new RegExp(`^${intermediateValue}\?`)); secondRequest.respond( 200, {}, diff --git a/test/spec/modules/aolBidAdapter_spec.js b/test/spec/modules/aolBidAdapter_spec.js deleted file mode 100644 index 92246f76c7a..00000000000 --- a/test/spec/modules/aolBidAdapter_spec.js +++ /dev/null @@ -1,890 +0,0 @@ -import {expect} from 'chai'; -import * as utils from 'src/utils.js'; -import {spec} from 'modules/aolBidAdapter.js'; -import {createEidsArray} from '../../../modules/userId/eids.js'; - -const DEFAULT_AD_CONTENT = ''; - -let getDefaultBidResponse = () => { - return { - id: '245730051428950632', - cur: 'USD', - seatbid: [{ - bid: [{ - id: 1, - impid: '245730051428950632', - price: 0.09, - adm: DEFAULT_AD_CONTENT, - crid: 'creative-id', - h: 90, - w: 728, - dealid: 'deal-id', - ext: {sizeid: 225} - }] - }] - }; -}; - -let getMarketplaceBidParams = () => { - return { - placement: 1234567, - network: '9599.1' - }; -}; - -let getNexageGetBidParams = () => { - return { - dcn: '2c9d2b50015c5ce9db6aeeed8b9500d6', - pos: 'header' - }; -}; - -let getNexagePostBidParams = () => { - return { - id: 'id-1', - imp: [{ - id: 'id-2', - banner: { - w: '100', - h: '100' - }, - tagid: 'header1' - }] - }; -}; - -let getDefaultBidRequest = () => { - return { - bidderCode: 'aol', - auctionId: 'd3e07445-ab06-44c8-a9dd-5ef9af06d2a6', - bidderRequestId: '7101db09af0db2', - start: new Date().getTime(), - bids: [{ - bidder: 'aol', - bidId: '84ab500420319d', - bidderRequestId: '7101db09af0db2', - auctionId: 'd3e07445-ab06-44c8-a9dd-5ef9af06d2a6', - placementCode: 'foo', - params: getMarketplaceBidParams() - }] - }; -}; - -let getPixels = () => { - return ''; -}; - -describe('AolAdapter', function () { - const MARKETPLACE_URL = 'https://adserver-us.adtech.advertising.com/pubapi/3.0/'; - const NEXAGE_URL = 'https://c2shb.ssp.yahoo.com/bidRequest?'; - const ONE_DISPLAY_TTL = 60; - const ONE_MOBILE_TTL = 3600; - const SUPPORTED_USER_ID_SOURCES = { - 'admixer.net': '100', - 'adserver.org': '200', - 'adtelligent.com': '300', - 'amxdt.net': '500', - 'audigent.com': '600', - 'britepool.com': '700', - 'criteo.com': '800', - 'crwdcntrl.net': '900', - 'deepintent.com': '1000', - 'epsilon.com': '1100', - 'hcn.health': '1200', - 'id5-sync.com': '1300', - 'idx.lat': '1400', - 'intentiq.com': '1500', - 'intimatemerger.com': '1600', - 'liveintent.com': '1700', - 'liveramp.com': '1800', - 'mediawallahscript.com': '1900', - 'netid.de': '2100', - 'neustar.biz': '2200', - 'pubcid.org': '2600', - 'quantcast.com': '2700', - 'tapad.com': '2800', - 'zeotap.com': '3200' - }; - - const USER_ID_DATA = { - admixerId: SUPPORTED_USER_ID_SOURCES['admixer.net'], - adtelligentId: SUPPORTED_USER_ID_SOURCES['adtelligent.com'], - amxId: SUPPORTED_USER_ID_SOURCES['amxdt.net'], - britepoolid: SUPPORTED_USER_ID_SOURCES['britepool.com'], - criteoId: SUPPORTED_USER_ID_SOURCES['criteo.com'], - connectid: SUPPORTED_USER_ID_SOURCES['verizonmedia.com'], - dmdId: SUPPORTED_USER_ID_SOURCES['hcn.health'], - hadronId: SUPPORTED_USER_ID_SOURCES['audigent.com'], - lotamePanoramaId: SUPPORTED_USER_ID_SOURCES['crwdcntrl.net'], - deepintentId: SUPPORTED_USER_ID_SOURCES['deepintent.com'], - fabrickId: SUPPORTED_USER_ID_SOURCES['neustar.biz'], - idl_env: SUPPORTED_USER_ID_SOURCES['liveramp.com'], - IDP: SUPPORTED_USER_ID_SOURCES['zeotap.com'], - lipb: { - lipbid: SUPPORTED_USER_ID_SOURCES['liveintent.com'], - segments: ['100', '200'] - }, - tdid: SUPPORTED_USER_ID_SOURCES['adserver.org'], - id5id: { - uid: SUPPORTED_USER_ID_SOURCES['id5-sync.com'], - ext: {foo: 'bar'} - }, - idx: SUPPORTED_USER_ID_SOURCES['idx.lat'], - imuid: SUPPORTED_USER_ID_SOURCES['intimatemerger.com'], - intentIqId: SUPPORTED_USER_ID_SOURCES['intentiq.com'], - mwOpenLinkId: SUPPORTED_USER_ID_SOURCES['mediawallahscript.com'], - netId: SUPPORTED_USER_ID_SOURCES['netid.de'], - quantcastId: SUPPORTED_USER_ID_SOURCES['quantcast.com'], - publinkId: SUPPORTED_USER_ID_SOURCES['epsilon.com'], - pubcid: SUPPORTED_USER_ID_SOURCES['pubcid.org'], - tapadId: SUPPORTED_USER_ID_SOURCES['tapad.com'] - }; - - function createCustomBidRequest({bids, params} = {}) { - var bidderRequest = getDefaultBidRequest(); - if (bids && Array.isArray(bids)) { - bidderRequest.bids = bids; - } - if (params) { - bidderRequest.bids.forEach(bid => bid.params = params); - } - return bidderRequest; - } - - describe('interpretResponse()', function () { - let bidderSettingsBackup; - let bidResponse; - let bidRequest; - let logWarnSpy; - let isOneMobileBidderStub; - - beforeEach(function () { - bidderSettingsBackup = $$PREBID_GLOBAL$$.bidderSettings; - bidRequest = { - bidderCode: 'test-bidder-code', - bidId: 'bid-id', - ttl: 1234 - }; - bidResponse = { - body: getDefaultBidResponse() - }; - logWarnSpy = sinon.spy(utils, 'logWarn'); - isOneMobileBidderStub = sinon.stub(spec, 'isOneMobileBidder'); - }); - - afterEach(function () { - $$PREBID_GLOBAL$$.bidderSettings = bidderSettingsBackup; - logWarnSpy.restore(); - isOneMobileBidderStub.restore(); - }); - - it('should return formatted bid response with required properties', function () { - let formattedBidResponse = spec.interpretResponse(bidResponse, bidRequest); - expect(formattedBidResponse).to.deep.equal({ - bidderCode: bidRequest.bidderCode, - requestId: 'bid-id', - ad: DEFAULT_AD_CONTENT, - cpm: 0.09, - width: 728, - height: 90, - creativeId: 'creative-id', - pubapiId: '245730051428950632', - currency: 'USD', - dealId: 'deal-id', - netRevenue: true, - meta: { - advertiserDomains: [] - }, - ttl: bidRequest.ttl - }); - }); - }); - - describe('buildRequests()', function () { - it('method exists and is a function', function () { - expect(spec.buildRequests).to.exist.and.to.be.a('function'); - }); - - describe('Marketplace', function () { - it('should not return request when no bids are present', function () { - let [request] = spec.buildRequests([]); - expect(request).to.be.undefined; - }); - - it('should return request for Marketplace endpoint', function () { - let bidRequest = getDefaultBidRequest(); - let [request] = spec.buildRequests(bidRequest.bids); - expect(request.url.indexOf(MARKETPLACE_URL)).to.equal(0); - }); - - it('should return request for Marketplace via onedisplay bidder code', function () { - let bidRequest = createCustomBidRequest({ - bids: [{ - bidder: 'onedisplay' - }], - params: getMarketplaceBidParams() - }); - - let [request] = spec.buildRequests(bidRequest.bids); - expect(request.url.indexOf(MARKETPLACE_URL)).to.equal(0); - }); - - it('should return Marketplace request via onedisplay bidder code when' + - 'Marketplace and One Mobile GET params are present', () => { - let bidParams = Object.assign(getMarketplaceBidParams(), getNexageGetBidParams()); - let bidRequest = createCustomBidRequest({ - bids: [{ - bidder: 'onedisplay' - }], - params: bidParams - }); - - let [request] = spec.buildRequests(bidRequest.bids); - expect(request.url.indexOf(MARKETPLACE_URL)).to.equal(0); - }); - - it('should return Marketplace request via onedisplay bidder code when' + - 'Marketplace and One Mobile GET + POST params are present', () => { - let bidParams = Object.assign(getMarketplaceBidParams(), getNexageGetBidParams(), getNexagePostBidParams()); - let bidRequest = createCustomBidRequest({ - bids: [{ - bidder: 'onedisplay' - }], - params: bidParams - }); - - let [request] = spec.buildRequests(bidRequest.bids); - expect(request.url.indexOf(MARKETPLACE_URL)).to.equal(0); - }); - - it('should not resolve endpoint for onedisplay bidder code ' + - 'when only One Mobile params are present', () => { - let bidParams = Object.assign(getNexageGetBidParams(), getNexagePostBidParams()); - let bidRequest = createCustomBidRequest({ - bids: [{ - bidder: 'onedisplay' - }], - params: bidParams - }); - - let [request] = spec.buildRequests(bidRequest.bids); - expect(request).to.be.undefined; - }); - - it('should return Marketplace URL for eu region', function () { - let bidRequest = createCustomBidRequest({ - params: { - placement: 1234567, - network: '9599.1', - region: 'eu' - } - }); - let [request] = spec.buildRequests(bidRequest.bids); - expect(request.url.indexOf('https://adserver-eu.adtech.advertising.com/pubapi/3.0/')) - .to.equal(0); - }); - - it('should return insecure MP URL if insecure server option is present', function () { - let bidRequest = createCustomBidRequest({ - params: { - placement: 1234567, - network: '9599.1', - server: 'https://adserver-eu.adtech.advertising.com' - } - }); - let [request] = spec.buildRequests(bidRequest.bids); - expect(request.url.indexOf('https://adserver-eu.adtech.advertising.com/pubapi/3.0/')) - .to.equal(0); - }); - - it('should return a secure MP URL if relative proto server option is present', function () { - let bidRequest = createCustomBidRequest({ - params: { - placement: 1234567, - network: '9599.1', - server: 'https://adserver-eu.adtech.advertising.com' - } - }); - let [request] = spec.buildRequests(bidRequest.bids); - expect(request.url.indexOf('https://adserver-eu.adtech.advertising.com/pubapi/3.0/')) - .to.equal(0); - }); - - it('should return a secure MP URL when server option without protocol is present', function () { - let bidRequest = createCustomBidRequest({ - params: { - placement: 1234567, - network: '9599.1', - server: 'adserver-eu.adtech.advertising.com' - } - }); - let [request] = spec.buildRequests(bidRequest.bids); - expect(request.url.indexOf('https://adserver-eu.adtech.advertising.com/pubapi/3.0/')) - .to.equal(0); - }); - - it('should return default Marketplace URL in case of unknown region config option', function () { - let bidRequest = createCustomBidRequest({ - params: { - placement: 1234567, - network: '9599.1', - region: 'an' - } - }); - let [request] = spec.buildRequests(bidRequest.bids); - expect(request.url.indexOf(MARKETPLACE_URL)).to.equal(0); - }); - - it('should return url with pubapi bid option', function () { - let bidRequest = getDefaultBidRequest(); - let [request] = spec.buildRequests(bidRequest.bids); - expect(request.url).to.contain('cmd=bid;'); - }); - - it('should return url with version 2 of pubapi', function () { - let bidRequest = getDefaultBidRequest(); - let [request] = spec.buildRequests(bidRequest.bids); - expect(request.url).to.contain('v=2;'); - }); - - it('should return url with cache busting option', function () { - let bidRequest = getDefaultBidRequest(); - let [request] = spec.buildRequests(bidRequest.bids); - expect(request.url).to.match(/misc=\d+/); - }); - - it('should return url with default pageId and sizeId', function () { - let bidRequest = createCustomBidRequest({ - params: { - placement: 1234567, - network: '9599.1' - } - }); - let [request] = spec.buildRequests(bidRequest.bids); - expect(request.url).to.contain('/pubapi/3.0/9599.1/1234567/0/0/ADTECH;'); - }); - - it('should return url with custom pageId and sizeId when options are present', function () { - let bidRequest = createCustomBidRequest({ - params: { - placement: 1234567, - network: '9599.1', - pageId: 1111, - sizeId: 2222 - } - }); - let [request] = spec.buildRequests(bidRequest.bids); - expect(request.url).to.contain('/pubapi/3.0/9599.1/1234567/1111/2222/ADTECH;'); - }); - - it('should return url with default alias if alias param is missing', function () { - let bidRequest = getDefaultBidRequest(); - let [request] = spec.buildRequests(bidRequest.bids); - expect(request.url).to.match(/alias=\w+?;/); - }); - - it('should return url with custom alias if it is present', function () { - let bidRequest = createCustomBidRequest({ - params: { - placement: 1234567, - network: '9599.1', - alias: 'desktop_articlepage_something_box_300_250' - } - }); - let [request] = spec.buildRequests(bidRequest.bids); - expect(request.url).to.contain('alias=desktop_articlepage_something_box_300_250'); - }); - - it('should return url without bidfloor option if is is missing', function () { - let bidRequest = getDefaultBidRequest(); - let [request] = spec.buildRequests(bidRequest.bids); - expect(request.url).not.to.contain('bidfloor='); - }); - - it('should return url with key values if keyValues param is present', function () { - let bidRequest = createCustomBidRequest({ - params: { - placement: 1234567, - network: '9599.1', - keyValues: { - age: 25, - height: 3.42, - test: 'key' - } - } - }); - let [request] = spec.buildRequests(bidRequest.bids); - expect(request.url).to.contain('kvage=25;kvheight=3.42;kvtest=key'); - }); - - it('should return request object for One Display when configuration is present', function () { - let bidRequest = getDefaultBidRequest(); - let [request] = spec.buildRequests(bidRequest.bids); - expect(request.method).to.equal('GET'); - expect(request.ttl).to.equal(ONE_DISPLAY_TTL); - }); - }); - - describe('One Mobile', function () { - it('should return One Mobile url when One Mobile get params are present', function () { - let bidRequest = createCustomBidRequest({ - params: getNexageGetBidParams() - }); - let [request] = spec.buildRequests(bidRequest.bids); - expect(request.url).to.contain(NEXAGE_URL); - }); - - it('should return One Mobile url with different host when host option is present', function () { - let bidParams = Object.assign({ - host: 'https://qa-hb.nexage.com' - }, getNexageGetBidParams()); - let bidRequest = createCustomBidRequest({ - params: bidParams - }); - let [request] = spec.buildRequests(bidRequest.bids); - expect(request.url).to.contain('https://qa-hb.nexage.com/bidRequest?'); - }); - - it('should return One Mobile url when One Mobile and Marketplace params are present', function () { - let bidParams = Object.assign(getNexageGetBidParams(), getMarketplaceBidParams()); - let bidRequest = createCustomBidRequest({ - params: bidParams - }); - let [request] = spec.buildRequests(bidRequest.bids); - expect(request.url).to.contain(NEXAGE_URL); - }); - - it('should return One Mobile url for onemobile bidder code ' + - 'when One Mobile GET and Marketplace params are present', () => { - let bidParams = Object.assign(getNexageGetBidParams(), getMarketplaceBidParams()); - let bidRequest = createCustomBidRequest({ - bids: [{ - bidder: 'onemobile' - }], - params: bidParams - }); - let [request] = spec.buildRequests(bidRequest.bids); - expect(request.url).to.contain(NEXAGE_URL); - }); - - it('should not return any url for onemobile bidder code' + - 'when only Marketplace params are present', () => { - let bidRequest = createCustomBidRequest({ - bids: [{ - bidder: 'onemobile' - }], - params: getMarketplaceBidParams() - }); - let [request] = spec.buildRequests(bidRequest.bids); - expect(request).to.be.undefined; - }); - - it('should return One Mobile url with required params - dcn & pos', function () { - let bidRequest = createCustomBidRequest({ - params: getNexageGetBidParams() - }); - let [request] = spec.buildRequests(bidRequest.bids); - expect(request.url).to.contain(NEXAGE_URL + 'dcn=2c9d2b50015c5ce9db6aeeed8b9500d6&pos=header'); - }); - - it('should return One Mobile url with configured GPP data', function () { - let bidRequest = createCustomBidRequest({ - params: getNexageGetBidParams() - }); - bidRequest.ortb2 = { - regs: { - gpp: 'testgpp', - gpp_sid: [8] - } - } - let [request] = spec.buildRequests(bidRequest.bids, bidRequest); - expect(request.url).to.contain('gpp=testgpp&gpp_sid=8'); - }); - - it('should return One Mobile url with cmd=bid option', function () { - let bidRequest = createCustomBidRequest({ - params: getNexageGetBidParams() - }); - let [request] = spec.buildRequests(bidRequest.bids); - expect(request.url).to.contain('cmd=bid'); - }); - - it('should return One Mobile url with generic params if ext option is present', function () { - let bidRequest = createCustomBidRequest({ - params: { - dcn: '54321123', - pos: 'footer-2324', - ext: { - param1: 'val1', - param2: 'val2', - param3: 'val3', - param4: 'val4' - } - } - }); - let [request] = spec.buildRequests(bidRequest.bids); - expect(request.url).to.equal('https://c2shb.ssp.yahoo.com/bidRequest?dcn=54321123&pos=footer-2324&cmd=bid' + - '¶m1=val1¶m2=val2¶m3=val3¶m4=val4'); - }); - - Object.keys(SUPPORTED_USER_ID_SOURCES).forEach(source => { - it(`should set the user ID query param for ${source}`, function () { - let bidRequest = createCustomBidRequest({ - params: getNexageGetBidParams() - }); - bidRequest.bids[0].userId = {}; - bidRequest.bids[0].userIdAsEids = createEidsArray(USER_ID_DATA); - let [request] = spec.buildRequests(bidRequest.bids); - expect(request.url).to.contain(`&eid${source}=${encodeURIComponent(SUPPORTED_USER_ID_SOURCES[source])}`); - }); - }); - - it('should return request object for One Mobile POST endpoint when POST configuration is present', function () { - let bidConfig = getNexagePostBidParams(); - let bidRequest = createCustomBidRequest({ - params: bidConfig - }); - - let [request] = spec.buildRequests(bidRequest.bids); - expect(request.url).to.contain(NEXAGE_URL); - expect(request.method).to.equal('POST'); - expect(request.ttl).to.equal(ONE_MOBILE_TTL); - expect(request.data).to.deep.equal(bidConfig); - expect(request.options).to.deep.equal({ - contentType: 'application/json', - customHeaders: { - 'x-openrtb-version': '2.2' - } - }); - }); - - it('should not return request object for One Mobile POST endpoint' + - 'if required parameters are missed', () => { - let bidRequest = createCustomBidRequest({ - params: { - imp: [] - } - }); - let [request] = spec.buildRequests(bidRequest.bids); - expect(request).to.be.undefined; - }); - }); - }); - - describe('buildOpenRtbRequestData', () => { - const bid = { - params: { - id: 'bid-id', - imp: [] - } - }; - let euConsentRequiredStub; - - beforeEach(function () { - euConsentRequiredStub = sinon.stub(spec, 'isEUConsentRequired'); - }); - - afterEach(function () { - euConsentRequiredStub.restore(); - }); - - it('returns the basic bid info when regulation data is omitted', () => { - expect(spec.buildOpenRtbRequestData(bid)).to.deep.equal({ - id: 'bid-id', - imp: [] - }); - }); - - it('returns the basic bid info with gdpr data when gdpr consent data is included', () => { - let consentData = { - gdpr: { - consentString: 'someEUConsent' - } - }; - euConsentRequiredStub.returns(true); - expect(spec.buildOpenRtbRequestData(bid, consentData)).to.deep.equal({ - id: 'bid-id', - imp: [], - regs: { - ext: { - gdpr: 1 - } - }, - user: { - ext: { - consent: 'someEUConsent' - } - } - }); - }); - - it('returns the basic bid info with CCPA data when CCPA consent data is included', () => { - let consentData = { - uspConsent: 'someUSPConsent' - }; - expect(spec.buildOpenRtbRequestData(bid, consentData)).to.deep.equal({ - id: 'bid-id', - imp: [], - regs: { - ext: { - us_privacy: 'someUSPConsent' - } - } - }); - }); - - it('returns the basic bid info with GDPR and CCPA data when GDPR and CCPA consent data is included', () => { - let consentData = { - gdpr: { - consentString: 'someEUConsent' - }, - uspConsent: 'someUSPConsent' - }; - euConsentRequiredStub.returns(true); - expect(spec.buildOpenRtbRequestData(bid, consentData)).to.deep.equal({ - id: 'bid-id', - imp: [], - regs: { - ext: { - gdpr: 1, - us_privacy: 'someUSPConsent' - } - }, - user: { - ext: { - consent: 'someEUConsent' - } - } - }); - }); - - it('returns the bid object with eid array populated with PB set eids', () => { - let userIdBid = Object.assign({ - userId: {} - }, bid); - userIdBid.userIdAsEids = createEidsArray(USER_ID_DATA); - expect(spec.buildOpenRtbRequestData(userIdBid)).to.deep.equal({ - id: 'bid-id', - imp: [], - user: { - ext: { - eids: userIdBid.userIdAsEids - } - } - }); - }); - }); - - describe('getUserSyncs()', function () { - let serverResponses; - let bidResponse; - - beforeEach(function () { - bidResponse = getDefaultBidResponse(); - bidResponse.ext = { - pixels: getPixels() - }; - - serverResponses = [ - {body: bidResponse} - ]; - }); - - it('should return user syncs if pixels are present in the response', function () { - let userSyncs = spec.getUserSyncs({}, serverResponses); - - expect(userSyncs).to.deep.equal([ - {type: 'image', url: 'img.org'}, - {type: 'iframe', url: 'pixels1.org'} - ]); - }); - - it('should not return user syncs if pixels are not present', function () { - bidResponse.ext.pixels = null; - let userSyncs = spec.getUserSyncs({}, serverResponses); - - expect(userSyncs).to.deep.equal([]); - }); - }); - - describe('isOneMobileBidder()', function () { - it('should return false when when bidderCode is not present', () => { - expect(spec.isOneMobileBidder(null)).to.be.false; - }); - - it('should return false for unknown bidder code', function () { - expect(spec.isOneMobileBidder('unknownBidder')).to.be.false; - }); - - it('should return true for aol bidder code', function () { - expect(spec.isOneMobileBidder('aol')).to.be.true; - }); - - it('should return true for one mobile bidder code', function () { - expect(spec.isOneMobileBidder('onemobile')).to.be.true; - }); - }); - - describe('isEUConsentRequired()', function () { - it('should return false when consentData object is not present', function () { - expect(spec.isEUConsentRequired(null)).to.be.false; - }); - - it('should return true when gdprApplies equals true and consentString is not present', function () { - let consentData = { - gdpr: { - consentString: null, - gdprApplies: true - } - }; - - expect(spec.isEUConsentRequired(consentData)).to.be.true; - }); - - it('should return false when consentString is present and gdprApplies equals false', function () { - let consentData = { - gdpr: { - consentString: 'consent-string', - gdprApplies: false - } - }; - - expect(spec.isEUConsentRequired(consentData)).to.be.false; - }); - - it('should return true when consentString is present and gdprApplies equals true', function () { - let consentData = { - gdpr: { - consentString: 'consent-string', - gdprApplies: true - } - }; - - expect(spec.isEUConsentRequired(consentData)).to.be.true; - }); - }); - - describe('formatMarketplaceDynamicParams()', function () { - let formatConsentDataStub; - let formatKeyValuesStub; - - beforeEach(function () { - formatConsentDataStub = sinon.stub(spec, 'formatConsentData'); - formatKeyValuesStub = sinon.stub(spec, 'formatKeyValues'); - }); - - afterEach(function () { - formatConsentDataStub.restore(); - formatKeyValuesStub.restore(); - }); - - it('should return empty string when params are not present', function () { - expect(spec.formatMarketplaceDynamicParams()).to.be.equal(''); - }); - - it('should return formatted EU consent params when formatConsentData returns GDPR data', function () { - formatConsentDataStub.returns({ - euconsent: 'test-consent', - gdpr: 1 - }); - expect(spec.formatMarketplaceDynamicParams()).to.be.equal('euconsent=test-consent;gdpr=1;'); - }); - - it('should return formatted US privacy params when formatConsentData returns USP data', function () { - formatConsentDataStub.returns({ - us_privacy: 'test-usp-consent' - }); - expect(spec.formatMarketplaceDynamicParams()).to.be.equal('us_privacy=test-usp-consent;'); - }); - - it('should return formatted EU and USP consent params when formatConsentData returns all data', function () { - formatConsentDataStub.returns({ - euconsent: 'test-consent', - gdpr: 1, - us_privacy: 'test-usp-consent' - }); - expect(spec.formatMarketplaceDynamicParams()).to.be.equal( - 'euconsent=test-consent;gdpr=1;us_privacy=test-usp-consent;'); - }); - - it('should return formatted gpp privacy params when formatConsentData returns GPP data', function () { - formatConsentDataStub.returns({ - gpp: 'gppstring', - gpp_sid: [6, 7] - }); - expect(spec.formatMarketplaceDynamicParams()).to.be.equal('gpp=gppstring;gpp_sid=6%2C7;'); - }); - - it('should return formatted params when formatKeyValues returns data', function () { - formatKeyValuesStub.returns({ - param1: 'val1', - param2: 'val2', - param3: 'val3' - }); - expect(spec.formatMarketplaceDynamicParams()).to.be.equal('param1=val1;param2=val2;param3=val3;'); - }); - }); - - describe('formatOneMobileDynamicParams()', function () { - let euConsentRequiredStub; - let secureProtocolStub; - - beforeEach(function () { - euConsentRequiredStub = sinon.stub(spec, 'isEUConsentRequired'); - secureProtocolStub = sinon.stub(spec, 'isSecureProtocol'); - }); - - afterEach(function () { - euConsentRequiredStub.restore(); - secureProtocolStub.restore(); - }); - - it('should return empty string when params are not present', function () { - expect(spec.formatOneMobileDynamicParams()).to.be.equal(''); - }); - - it('should return formatted params when params are present', function () { - let params = { - param1: 'val1', - param2: 'val2', - param3: 'val3' - }; - expect(spec.formatOneMobileDynamicParams(params)).to.contain('¶m1=val1¶m2=val2¶m3=val3'); - }); - - it('should return formatted gdpr params when isEUConsentRequired returns true', function () { - let consentData = { - gdpr: { - consentString: 'test-consent' - } - }; - euConsentRequiredStub.returns(true); - expect(spec.formatOneMobileDynamicParams({}, consentData)).to.be.equal('&gdpr=1&euconsent=test-consent'); - }); - - it('should return formatted US privacy params when consentData contains USP data', function () { - let consentData = { - uspConsent: 'test-usp-consent' - }; - expect(spec.formatMarketplaceDynamicParams({}, consentData)).to.be.equal('us_privacy=test-usp-consent;'); - }); - - it('should return formatted EU and USP consent params when consentData contains gdpr and usp values', function () { - euConsentRequiredStub.returns(true); - let consentData = { - gdpr: { - consentString: 'test-consent' - }, - uspConsent: 'test-usp-consent' - }; - expect(spec.formatMarketplaceDynamicParams({}, consentData)).to.be.equal( - 'gdpr=1;euconsent=test-consent;us_privacy=test-usp-consent;'); - }); - - it('should return formatted secure param when isSecureProtocol returns true', function () { - secureProtocolStub.returns(true); - expect(spec.formatOneMobileDynamicParams()).to.be.equal('&secure=1'); - }); - }); -}); diff --git a/test/spec/modules/apacdexBidAdapter_spec.js b/test/spec/modules/apacdexBidAdapter_spec.js index 773c9925d58..98d07575ee7 100644 --- a/test/spec/modules/apacdexBidAdapter_spec.js +++ b/test/spec/modules/apacdexBidAdapter_spec.js @@ -321,9 +321,9 @@ describe('ApacdexBidAdapter', function () { const bidRequests = spec.buildRequests(bidRequest, bidderRequests); expect(bidRequests.data.eids).to.deep.equal(bidRequest[0].userIdAsEids) }); - it('should return a properly formatted request with geo defined', function () { + it('should fail to return a properly formatted request with geo defined', function () { const bidRequests = spec.buildRequests(bidRequest, bidderRequests); - expect(bidRequests.data.geo).to.deep.equal(bidRequest[0].params.geo) + expect(bidRequests.data.geo).to.not.deep.equal(bidRequest[0].params.geo) }); it('should return a properly formatted request with us_privacy included', function () { const bidRequests = spec.buildRequests(bidRequest, bidderRequests); diff --git a/test/spec/modules/appnexusBidAdapter_spec.js b/test/spec/modules/appnexusBidAdapter_spec.js index 9e88e2875c7..13ef31a68d4 100644 --- a/test/spec/modules/appnexusBidAdapter_spec.js +++ b/test/spec/modules/appnexusBidAdapter_spec.js @@ -18,6 +18,18 @@ describe('AppNexusAdapter', function () { }); }); + function expectKeywords(actual, expected) { + expect(actual.length).to.equal(expected.length); + actual.forEach(el => { + const match = expected.find(ob => ob.key === el.key); + if (el.value) { + expect(el.value).to.have.members(match.value); + } else { + expect(match.value).to.not.exist; + } + }) + } + describe('isBidRequestValid', function () { let bid = { 'bidder': 'appnexus', @@ -257,11 +269,15 @@ describe('AppNexusAdapter', function () { transactionId: '04f2659e-c005-4eb1-a57c-fa93145e3843' }]; - let types = ['banner', 'video']; + let types = ['banner']; if (FEATURES.NATIVE) { types.push('native'); } + if (FEATURES.VIDEO) { + types.push('video'); + } + types.forEach(type => { getAdUnitsStub.callsFake(function (...args) { return adUnits; @@ -290,122 +306,303 @@ describe('AppNexusAdapter', function () { expect(payload.tags[0].ad_types).to.not.exist; }); - it('should populate the ad_types array on outstream requests', function () { - const bidRequest = Object.assign({}, bidRequests[0]); - bidRequest.mediaTypes = {}; - bidRequest.mediaTypes.video = { context: 'outstream' }; + if (FEATURES.VIDEO) { + it('should populate the ad_types array on outstream requests', function () { + const bidRequest = Object.assign({}, bidRequests[0]); + bidRequest.mediaTypes = {}; + bidRequest.mediaTypes.video = { context: 'outstream' }; - const request = spec.buildRequests([bidRequest]); - const payload = JSON.parse(request.data); + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); - expect(payload.tags[0].ad_types).to.deep.equal(['video']); - expect(payload.tags[0].hb_source).to.deep.equal(1); - }); + expect(payload.tags[0].ad_types).to.deep.equal(['video']); + expect(payload.tags[0].hb_source).to.deep.equal(1); + }); - it('sends bid request to ENDPOINT via POST', function () { - const request = spec.buildRequests(bidRequests); - expect(request.url).to.equal(ENDPOINT); - expect(request.method).to.equal('POST'); - }); + it('should attach valid video params to the tag', function () { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { + placementId: '10433394', + video: { + id: 123, + minduration: 100, + foobar: 'invalid' + } + } + } + ); - it('should attach valid video params to the tag', function () { - let bidRequest = Object.assign({}, - bidRequests[0], - { + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + expect(payload.tags[0].video).to.deep.equal({ + id: 123, + minduration: 100 + }); + expect(payload.tags[0].hb_source).to.deep.equal(1); + }); + + it('should include ORTB video values when video params were not set', function () { + let bidRequest = deepClone(bidRequests[0]); + bidRequest.params = { + placementId: '1234235', + video: { + skippable: true, + playback_method: ['auto_play_sound_off', 'auto_play_sound_unknown'], + context: 'outstream' + } + }; + bidRequest.mediaTypes = { + video: { + playerSize: [640, 480], + context: 'outstream', + mimes: ['video/mp4'], + skip: 0, + minduration: 5, + api: [1, 5, 6], + playbackmethod: [2, 4] + } + }; + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + expect(payload.tags[0].video).to.deep.equal({ + minduration: 5, + playback_method: 2, + skippable: true, + context: 4 + }); + expect(payload.tags[0].video_frameworks).to.deep.equal([1, 4]) + }); + + it('should add video property when adUnit includes a renderer', function () { + const videoData = { + mediaTypes: { + video: { + context: 'outstream', + mimes: ['video/mp4'] + } + }, params: { placementId: '10433394', video: { - id: 123, - minduration: 100, - foobar: 'invalid' + skippable: true, + playback_method: ['auto_play_sound_off'] } } - } - ); + }; - const request = spec.buildRequests([bidRequest]); - const payload = JSON.parse(request.data); - expect(payload.tags[0].video).to.deep.equal({ - id: 123, - minduration: 100 - }); - expect(payload.tags[0].hb_source).to.deep.equal(1); - }); + let bidRequest1 = deepClone(bidRequests[0]); + bidRequest1 = Object.assign({}, bidRequest1, videoData, { + renderer: { + url: 'https://test.renderer.url', + render: function () { } + } + }); - it('should include ORTB video values when video params were not set', function () { - let bidRequest = deepClone(bidRequests[0]); - bidRequest.params = { - placementId: '1234235', - video: { + let bidRequest2 = deepClone(bidRequests[0]); + bidRequest2.adUnitCode = 'adUnit_code_2'; + bidRequest2 = Object.assign({}, bidRequest2, videoData); + + const request = spec.buildRequests([bidRequest1, bidRequest2]); + const payload = JSON.parse(request.data); + expect(payload.tags[0].video).to.deep.equal({ skippable: true, - playback_method: ['auto_play_sound_off', 'auto_play_sound_unknown'], - context: 'outstream' - } - }; - bidRequest.mediaTypes = { - video: { - playerSize: [640, 480], - context: 'outstream', - mimes: ['video/mp4'], - skip: 0, - minduration: 5, - api: [1, 5, 6], - playbackmethod: [2, 4] - } - }; + playback_method: 2, + custom_renderer_present: true + }); + expect(payload.tags[1].video).to.deep.equal({ + skippable: true, + playback_method: 2 + }); + }); - const request = spec.buildRequests([bidRequest]); - const payload = JSON.parse(request.data); + it('should duplicate adpod placements into batches and set correct maxduration', function () { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { placementId: '14542875' } + }, + { + mediaTypes: { + video: { + context: 'adpod', + playerSize: [640, 480], + adPodDurationSec: 300, + durationRangeSec: [15, 30], + } + } + } + ); + + const request = spec.buildRequests([bidRequest]); + const payload1 = JSON.parse(request[0].data); + const payload2 = JSON.parse(request[1].data); + + // 300 / 15 = 20 total + expect(payload1.tags.length).to.equal(15); + expect(payload2.tags.length).to.equal(5); - expect(payload.tags[0].video).to.deep.equal({ - minduration: 5, - playback_method: 2, - skippable: true, - context: 4 + expect(payload1.tags[0]).to.deep.equal(payload1.tags[1]); + expect(payload1.tags[0].video.maxduration).to.equal(30); + + expect(payload2.tags[0]).to.deep.equal(payload1.tags[1]); + expect(payload2.tags[0].video.maxduration).to.equal(30); }); - expect(payload.tags[0].video_frameworks).to.deep.equal([1, 4]) - }); - it('should add video property when adUnit includes a renderer', function () { - const videoData = { - mediaTypes: { - video: { - context: 'outstream', - mimes: ['video/mp4'] + it('should round down adpod placements when numbers are uneven', function () { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { placementId: '14542875' } + }, + { + mediaTypes: { + video: { + context: 'adpod', + playerSize: [640, 480], + adPodDurationSec: 123, + durationRangeSec: [45], + } + } } - }, - params: { - placementId: '10433394', - video: { - skippable: true, - playback_method: ['auto_play_sound_off'] + ); + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + expect(payload.tags.length).to.equal(2); + }); + + it('should duplicate adpod placements when requireExactDuration is set', function () { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { placementId: '14542875' } + }, + { + mediaTypes: { + video: { + context: 'adpod', + playerSize: [640, 480], + adPodDurationSec: 300, + durationRangeSec: [15, 30], + requireExactDuration: true, + } + } } - } - }; + ); - let bidRequest1 = deepClone(bidRequests[0]); - bidRequest1 = Object.assign({}, bidRequest1, videoData, { - renderer: { - url: 'https://test.renderer.url', - render: function () { } - } + // 20 total placements with 15 max impressions = 2 requests + const request = spec.buildRequests([bidRequest]); + expect(request.length).to.equal(2); + + // 20 spread over 2 requests = 15 in first request, 5 in second + const payload1 = JSON.parse(request[0].data); + const payload2 = JSON.parse(request[1].data); + expect(payload1.tags.length).to.equal(15); + expect(payload2.tags.length).to.equal(5); + + // 10 placements should have max/min at 15 + // 10 placemenst should have max/min at 30 + const payload1tagsWith15 = payload1.tags.filter(tag => tag.video.maxduration === 15); + const payload1tagsWith30 = payload1.tags.filter(tag => tag.video.maxduration === 30); + expect(payload1tagsWith15.length).to.equal(10); + expect(payload1tagsWith30.length).to.equal(5); + + // 5 placemenst with min/max at 30 were in the first request + // so 5 remaining should be in the second + const payload2tagsWith30 = payload2.tags.filter(tag => tag.video.maxduration === 30); + expect(payload2tagsWith30.length).to.equal(5); }); - let bidRequest2 = deepClone(bidRequests[0]); - bidRequest2.adUnitCode = 'adUnit_code_2'; - bidRequest2 = Object.assign({}, bidRequest2, videoData); + it('should set durations for placements when requireExactDuration is set and numbers are uneven', function () { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { placementId: '14542875' } + }, + { + mediaTypes: { + video: { + context: 'adpod', + playerSize: [640, 480], + adPodDurationSec: 105, + durationRangeSec: [15, 30, 60], + requireExactDuration: true, + } + } + } + ); - const request = spec.buildRequests([bidRequest1, bidRequest2]); - const payload = JSON.parse(request.data); - expect(payload.tags[0].video).to.deep.equal({ - skippable: true, - playback_method: 2, - custom_renderer_present: true + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + expect(payload.tags.length).to.equal(7); + + const tagsWith15 = payload.tags.filter(tag => tag.video.maxduration === 15); + const tagsWith30 = payload.tags.filter(tag => tag.video.maxduration === 30); + const tagsWith60 = payload.tags.filter(tag => tag.video.maxduration === 60); + expect(tagsWith15.length).to.equal(3); + expect(tagsWith30.length).to.equal(3); + expect(tagsWith60.length).to.equal(1); + }); + + it('should break adpod request into batches', function () { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { placementId: '14542875' } + }, + { + mediaTypes: { + video: { + context: 'adpod', + playerSize: [640, 480], + adPodDurationSec: 225, + durationRangeSec: [5], + } + } + } + ); + + const request = spec.buildRequests([bidRequest]); + const payload1 = JSON.parse(request[0].data); + const payload2 = JSON.parse(request[1].data); + const payload3 = JSON.parse(request[2].data); + + expect(payload1.tags.length).to.equal(15); + expect(payload2.tags.length).to.equal(15); + expect(payload3.tags.length).to.equal(15); }); - expect(payload.tags[1].video).to.deep.equal({ - skippable: true, - playback_method: 2 + + it('should contain hb_source value for adpod', function () { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { placementId: '14542875' } + }, + { + mediaTypes: { + video: { + context: 'adpod', + playerSize: [640, 480], + adPodDurationSec: 300, + durationRangeSec: [15, 30], + } + } + } + ); + const request = spec.buildRequests([bidRequest])[0]; + const payload = JSON.parse(request.data); + expect(payload.tags[0].hb_source).to.deep.equal(7); }); + } // VIDEO + + it('sends bid request to ENDPOINT via POST', function () { + const request = spec.buildRequests(bidRequests); + expect(request.url).to.equal(ENDPOINT); + expect(request.method).to.equal('POST'); }); it('should attach valid user params to the tag', function () { @@ -486,185 +683,6 @@ describe('AppNexusAdapter', function () { expect(payload.tags[0].reserve).to.exist.and.to.equal(3); }); - it('should duplicate adpod placements into batches and set correct maxduration', function () { - let bidRequest = Object.assign({}, - bidRequests[0], - { - params: { placement_id: '14542875' } - }, - { - mediaTypes: { - video: { - context: 'adpod', - playerSize: [640, 480], - adPodDurationSec: 300, - durationRangeSec: [15, 30], - } - } - } - ); - - const request = spec.buildRequests([bidRequest]); - const payload1 = JSON.parse(request[0].data); - const payload2 = JSON.parse(request[1].data); - - // 300 / 15 = 20 total - expect(payload1.tags.length).to.equal(15); - expect(payload2.tags.length).to.equal(5); - - expect(payload1.tags[0]).to.deep.equal(payload1.tags[1]); - expect(payload1.tags[0].video.maxduration).to.equal(30); - - expect(payload2.tags[0]).to.deep.equal(payload1.tags[1]); - expect(payload2.tags[0].video.maxduration).to.equal(30); - }); - - it('should round down adpod placements when numbers are uneven', function () { - let bidRequest = Object.assign({}, - bidRequests[0], - { - params: { placement_id: '14542875' } - }, - { - mediaTypes: { - video: { - context: 'adpod', - playerSize: [640, 480], - adPodDurationSec: 123, - durationRangeSec: [45], - } - } - } - ); - - const request = spec.buildRequests([bidRequest]); - const payload = JSON.parse(request.data); - expect(payload.tags.length).to.equal(2); - }); - - it('should duplicate adpod placements when requireExactDuration is set', function () { - let bidRequest = Object.assign({}, - bidRequests[0], - { - params: { placement_id: '14542875' } - }, - { - mediaTypes: { - video: { - context: 'adpod', - playerSize: [640, 480], - adPodDurationSec: 300, - durationRangeSec: [15, 30], - requireExactDuration: true, - } - } - } - ); - - // 20 total placements with 15 max impressions = 2 requests - const request = spec.buildRequests([bidRequest]); - expect(request.length).to.equal(2); - - // 20 spread over 2 requests = 15 in first request, 5 in second - const payload1 = JSON.parse(request[0].data); - const payload2 = JSON.parse(request[1].data); - expect(payload1.tags.length).to.equal(15); - expect(payload2.tags.length).to.equal(5); - - // 10 placements should have max/min at 15 - // 10 placemenst should have max/min at 30 - const payload1tagsWith15 = payload1.tags.filter(tag => tag.video.maxduration === 15); - const payload1tagsWith30 = payload1.tags.filter(tag => tag.video.maxduration === 30); - expect(payload1tagsWith15.length).to.equal(10); - expect(payload1tagsWith30.length).to.equal(5); - - // 5 placemenst with min/max at 30 were in the first request - // so 5 remaining should be in the second - const payload2tagsWith30 = payload2.tags.filter(tag => tag.video.maxduration === 30); - expect(payload2tagsWith30.length).to.equal(5); - }); - - it('should set durations for placements when requireExactDuration is set and numbers are uneven', function () { - let bidRequest = Object.assign({}, - bidRequests[0], - { - params: { placement_id: '14542875' } - }, - { - mediaTypes: { - video: { - context: 'adpod', - playerSize: [640, 480], - adPodDurationSec: 105, - durationRangeSec: [15, 30, 60], - requireExactDuration: true, - } - } - } - ); - - const request = spec.buildRequests([bidRequest]); - const payload = JSON.parse(request.data); - expect(payload.tags.length).to.equal(7); - - const tagsWith15 = payload.tags.filter(tag => tag.video.maxduration === 15); - const tagsWith30 = payload.tags.filter(tag => tag.video.maxduration === 30); - const tagsWith60 = payload.tags.filter(tag => tag.video.maxduration === 60); - expect(tagsWith15.length).to.equal(3); - expect(tagsWith30.length).to.equal(3); - expect(tagsWith60.length).to.equal(1); - }); - - it('should break adpod request into batches', function () { - let bidRequest = Object.assign({}, - bidRequests[0], - { - params: { placement_id: '14542875' } - }, - { - mediaTypes: { - video: { - context: 'adpod', - playerSize: [640, 480], - adPodDurationSec: 225, - durationRangeSec: [5], - } - } - } - ); - - const request = spec.buildRequests([bidRequest]); - const payload1 = JSON.parse(request[0].data); - const payload2 = JSON.parse(request[1].data); - const payload3 = JSON.parse(request[2].data); - - expect(payload1.tags.length).to.equal(15); - expect(payload2.tags.length).to.equal(15); - expect(payload3.tags.length).to.equal(15); - }); - - it('should contain hb_source value for adpod', function () { - let bidRequest = Object.assign({}, - bidRequests[0], - { - params: { placement_id: '14542875' } - }, - { - mediaTypes: { - video: { - context: 'adpod', - playerSize: [640, 480], - adPodDurationSec: 300, - durationRangeSec: [15, 30], - } - } - } - ); - const request = spec.buildRequests([bidRequest])[0]; - const payload = JSON.parse(request.data); - expect(payload.tags[0].hb_source).to.deep.equal(7); - }); - it('should contain hb_source value for other media', function () { let bidRequest = Object.assign({}, bidRequests[0], @@ -731,7 +749,7 @@ describe('AppNexusAdapter', function () { const request = spec.buildRequests([bidRequest], bidderRequest); const payload = JSON.parse(request.data); - expect(payload.keywords).to.deep.equal([{ + expectKeywords(payload.keywords, [{ 'key': 'gender', 'value': ['m'] }, { @@ -766,6 +784,69 @@ describe('AppNexusAdapter', function () { config.getConfig.restore(); }); + it('adds ortb2 segments to auction request as keywords', function() { + let bidRequest = Object.assign({}, bidRequests[0]); + const bidderRequest = { + ortb2: { + site: { + keywords: 'drill', + content: { + data: [{ + name: 'siteseg1', + ext: { + segtax: 540 + }, + segment: [{ + id: 's123', + }, { + id: 's234' + }] + }, { + name: 'sitseg2', + ext: { + segtax: 1 + }, + segment: [{ + id: 'unknown' + }] + }, { + name: 'siteseg3', + ext: { + segtax: 526 + }, + segment: [{ + id: 'dog' + }] + }] + } + }, + user: { + data: [{ + name: 'userseg1', + ext: { + segtax: 526 + }, + segment: [{ + id: 'cat' + }] + }] + } + } + }; + const request = spec.buildRequests([bidRequest], bidderRequest); + const payload = JSON.parse(request.data); + + expectKeywords(payload.keywords, [{ + 'key': 'drill' + }, { + 'key': '1plusX', + 'value': ['cat', 'dog'] + }, { + 'key': 'perid', + 'value': ['s123', 's234'] + }]); + }); + if (FEATURES.NATIVE) { it('should attach native params to the request', function () { let bidRequest = Object.assign({}, @@ -866,26 +947,28 @@ describe('AppNexusAdapter', function () { const request = spec.buildRequests([bidRequest]); const payload = JSON.parse(request.data); - expect(payload.tags[0].keywords).to.deep.equal([{ - 'key': 'single', - 'value': ['val'] - }, { - 'key': 'singleArr', - 'value': ['val'] - }, { - 'key': 'singleArrNum', - 'value': ['5'] - }, { - 'key': 'multiValMixed', - 'value': ['value1', '2', 'value3'] - }, { - 'key': 'singleValNum', - 'value': ['123'] - }, { - 'key': 'emptyStr' - }, { - 'key': 'emptyArr' - }]); + expectKeywords(payload.tags[0].keywords, [ + { + 'key': 'single', + 'value': ['val'] + }, { + 'key': 'singleArr', + 'value': ['val'] + }, { + 'key': 'singleArrNum', + 'value': ['5'] + }, { + 'key': 'multiValMixed', + 'value': ['value1', '2', 'value3'] + }, { + 'key': 'singleValNum', + 'value': ['123'] + }, { + 'key': 'emptyStr' + }, { + 'key': 'emptyArr' + } + ]) }); it('should convert adUnit ortb2 keywords (when there are no bid param keywords) to proper form and attaches to request', function () { @@ -905,7 +988,7 @@ describe('AppNexusAdapter', function () { const request = spec.buildRequests([bidRequest]); const payload = JSON.parse(request.data); - expect(payload.tags[0].keywords).to.deep.equal([{ + expectKeywords(payload.tags[0].keywords, [{ 'key': 'ortb2', 'value': ['yes'] }, { @@ -949,7 +1032,7 @@ describe('AppNexusAdapter', function () { const request = spec.buildRequests([bidRequest]); const payload = JSON.parse(request.data); - expect(payload.tags[0].keywords).to.deep.equal([{ + expectKeywords(payload.tags[0].keywords, [{ 'key': 'single', 'value': ['val'] }, { @@ -1392,27 +1475,31 @@ describe('AppNexusAdapter', function () { }); it('should populate iab_support object at the root level if omid support is detected', function () { - // with bid.params.frameworks - let bidRequest_A = Object.assign({}, bidRequests[0], { - params: { - frameworks: [1, 2, 5, 6], - video: { - frameworks: [1, 2, 5, 6] + let request, payload; + + if (FEATURES.VIDEO) { + // with bid.params.frameworks + let bidRequest_A = Object.assign({}, bidRequests[0], { + params: { + frameworks: [1, 2, 5, 6], + video: { + frameworks: [1, 2, 5, 6] + } } - } - }); - let request = spec.buildRequests([bidRequest_A]); - let payload = JSON.parse(request.data); - expect(payload.iab_support).to.be.an('object'); - expect(payload.iab_support).to.deep.equal({ - omidpn: 'Appnexus', - omidpv: '$prebid.version$' - }); - expect(payload.tags[0].banner_frameworks).to.be.an('array'); - expect(payload.tags[0].banner_frameworks).to.deep.equal([1, 2, 5, 6]); - expect(payload.tags[0].video_frameworks).to.be.an('array'); - expect(payload.tags[0].video_frameworks).to.deep.equal([1, 2, 5, 6]); - expect(payload.tags[0].video.frameworks).to.not.exist; + }); + request = spec.buildRequests([bidRequest_A]); + payload = JSON.parse(request.data); + expect(payload.iab_support).to.be.an('object'); + expect(payload.iab_support).to.deep.equal({ + omidpn: 'Appnexus', + omidpv: '$prebid.version$' + }); + expect(payload.tags[0].banner_frameworks).to.be.an('array'); + expect(payload.tags[0].banner_frameworks).to.deep.equal([1, 2, 5, 6]); + expect(payload.tags[0].video_frameworks).to.be.an('array'); + expect(payload.tags[0].video_frameworks).to.deep.equal([1, 2, 5, 6]); + expect(payload.tags[0].video.frameworks).to.not.exist; + } // without bid.params.frameworks const bidRequest_B = Object.assign({}, bidRequests[0]); @@ -1422,33 +1509,32 @@ describe('AppNexusAdapter', function () { expect(payload.tags[0].banner_frameworks).to.not.exist; expect(payload.tags[0].video_frameworks).to.not.exist; - // with video.frameworks but it is not an array - const bidRequest_C = Object.assign({}, bidRequests[0], { - params: { - video: { - frameworks: "'1', '2', '3', '6'" + if (FEATURES.VIDEO) { + // with video.frameworks but it is not an array + const bidRequest_C = Object.assign({}, bidRequests[0], { + params: { + video: { + frameworks: "'1', '2', '3', '6'" + } } - } - }); - request = spec.buildRequests([bidRequest_C]); - payload = JSON.parse(request.data); - expect(payload.iab_support).to.not.exist; - expect(payload.tags[0].banner_frameworks).to.not.exist; - expect(payload.tags[0].video_frameworks).to.not.exist; + }); + request = spec.buildRequests([bidRequest_C]); + payload = JSON.parse(request.data); + expect(payload.iab_support).to.not.exist; + expect(payload.tags[0].banner_frameworks).to.not.exist; + expect(payload.tags[0].video_frameworks).to.not.exist; + } }); }) describe('interpretResponse', function () { - let bfStub; let bidderSettingsStorage; before(function () { - bfStub = sinon.stub(bidderFactory, 'getIabSubCategory'); bidderSettingsStorage = $$PREBID_GLOBAL$$.bidderSettings; }); after(function () { - bfStub.restore(); $$PREBID_GLOBAL$$.bidderSettings = bidderSettingsStorage; }); @@ -1591,116 +1677,117 @@ describe('AppNexusAdapter', function () { expect(result.length).to.equal(0); }); - it('handles outstream video responses', function () { - let response = { - 'tags': [{ - 'uuid': '84ab500420319d', - 'ads': [{ - 'ad_type': 'video', - 'cpm': 0.500000, - 'notify_url': 'imptracker.com', - 'rtb': { - 'video': { - 'content': '' - } - }, - 'javascriptTrackers': '' + if (FEATURES.VIDEO) { + it('handles outstream video responses', function () { + let response = { + 'tags': [{ + 'uuid': '84ab500420319d', + 'ads': [{ + 'ad_type': 'video', + 'cpm': 0.500000, + 'notify_url': 'imptracker.com', + 'rtb': { + 'video': { + 'content': '' + } + }, + 'javascriptTrackers': '' + }] }] - }] - }; - let bidderRequest = { - bids: [{ - bidId: '84ab500420319d', - adUnitCode: 'code', - mediaTypes: { - video: { - context: 'outstream' + }; + let bidderRequest = { + bids: [{ + bidId: '84ab500420319d', + adUnitCode: 'code', + mediaTypes: { + video: { + context: 'outstream' + } } - } - }] - } + }] + } - let result = spec.interpretResponse({ body: response }, { bidderRequest }); - expect(result[0]).to.have.property('vastXml'); - expect(result[0]).to.have.property('vastImpUrl'); - expect(result[0]).to.have.property('mediaType', 'video'); - }); + let result = spec.interpretResponse({ body: response }, { bidderRequest }); + expect(result[0]).to.have.property('vastXml'); + expect(result[0]).to.have.property('vastImpUrl'); + expect(result[0]).to.have.property('mediaType', 'video'); + }); - it('handles instream video responses', function () { - let response = { - 'tags': [{ - 'uuid': '84ab500420319d', - 'ads': [{ - 'ad_type': 'video', - 'cpm': 0.500000, - 'notify_url': 'imptracker.com', - 'rtb': { - 'video': { - 'asset_url': 'https://sample.vastURL.com/here/vid' - } - }, - 'javascriptTrackers': '' + it('handles instream video responses', function () { + let response = { + 'tags': [{ + 'uuid': '84ab500420319d', + 'ads': [{ + 'ad_type': 'video', + 'cpm': 0.500000, + 'notify_url': 'imptracker.com', + 'rtb': { + 'video': { + 'asset_url': 'https://sample.vastURL.com/here/vid' + } + }, + 'javascriptTrackers': '' + }] }] - }] - }; - let bidderRequest = { - bids: [{ - bidId: '84ab500420319d', - adUnitCode: 'code', - mediaTypes: { - video: { - context: 'instream' + }; + let bidderRequest = { + bids: [{ + bidId: '84ab500420319d', + adUnitCode: 'code', + mediaTypes: { + video: { + context: 'instream' + } } - } - }] - } + }] + } - let result = spec.interpretResponse({ body: response }, { bidderRequest }); - expect(result[0]).to.have.property('vastUrl'); - expect(result[0]).to.have.property('vastImpUrl'); - expect(result[0]).to.have.property('mediaType', 'video'); - }); + let result = spec.interpretResponse({ body: response }, { bidderRequest }); + expect(result[0]).to.have.property('vastUrl'); + expect(result[0]).to.have.property('vastImpUrl'); + expect(result[0]).to.have.property('mediaType', 'video'); + }); - it('handles adpod responses', function () { - let response = { - 'tags': [{ - 'uuid': '84ab500420319d', - 'ads': [{ - 'ad_type': 'video', - 'brand_category_id': 10, - 'cpm': 0.500000, - 'notify_url': 'imptracker.com', - 'rtb': { - 'video': { - 'asset_url': 'https://sample.vastURL.com/here/adpod', - 'duration_ms': 30000, + it('handles adpod responses', function () { + let response = { + 'tags': [{ + 'uuid': '84ab500420319d', + 'ads': [{ + 'ad_type': 'video', + 'brand_category_id': 10, + 'cpm': 0.500000, + 'notify_url': 'imptracker.com', + 'rtb': { + 'video': { + 'asset_url': 'https://sample.vastURL.com/here/adpod', + 'duration_ms': 30000, + } + }, + 'viewability': { + 'config': '' } - }, - 'viewability': { - 'config': '' - } + }] }] - }] - }; + }; - let bidderRequest = { - bids: [{ - bidId: '84ab500420319d', - adUnitCode: 'code', - mediaTypes: { - video: { - context: 'adpod' + let bidderRequest = { + bids: [{ + bidId: '84ab500420319d', + adUnitCode: 'code', + mediaTypes: { + video: { + context: 'adpod' + } } - } - }] - }; - bfStub.returns('1'); + }] + }; - let result = spec.interpretResponse({ body: response }, { bidderRequest }); - expect(result[0]).to.have.property('vastUrl'); - expect(result[0].video.context).to.equal('adpod'); - expect(result[0].video.durationSeconds).to.equal(30); - }); + let result = spec.interpretResponse({ body: response }, { bidderRequest }); + expect(result[0]).to.have.property('vastUrl'); + expect(result[0].video.context).to.equal('adpod'); + expect(result[0].video.durationSeconds).to.equal(30); + }); + } if (FEATURES.NATIVE) { it('handles native responses', function () { @@ -1758,59 +1845,61 @@ describe('AppNexusAdapter', function () { }); } - it('supports configuring outstream renderers', function () { - const outstreamResponse = deepClone(response); - outstreamResponse.tags[0].ads[0].rtb.video = {}; - outstreamResponse.tags[0].ads[0].renderer_url = 'renderer.js'; + if (FEATURES.VIDEO) { + it('supports configuring outstream renderers', function () { + const outstreamResponse = deepClone(response); + outstreamResponse.tags[0].ads[0].rtb.video = {}; + outstreamResponse.tags[0].ads[0].renderer_url = 'renderer.js'; - const bidderRequest = { - bids: [{ - bidId: '3db3773286ee59', - renderer: { - options: { - adText: 'configured' - } - }, - mediaTypes: { - video: { - context: 'outstream' + const bidderRequest = { + bids: [{ + bidId: '3db3773286ee59', + renderer: { + options: { + adText: 'configured' + } + }, + mediaTypes: { + video: { + context: 'outstream' + } } - } - }] - }; + }] + }; - const result = spec.interpretResponse({ body: outstreamResponse }, { bidderRequest }); - expect(result[0].renderer.config).to.deep.equal( - bidderRequest.bids[0].renderer.options - ); - }); + const result = spec.interpretResponse({ body: outstreamResponse }, { bidderRequest }); + expect(result[0].renderer.config).to.deep.equal( + bidderRequest.bids[0].renderer.options + ); + }); - it('should add deal_priority and deal_code', function () { - let responseWithDeal = deepClone(response); - responseWithDeal.tags[0].ads[0].ad_type = 'video'; - responseWithDeal.tags[0].ads[0].deal_priority = 5; - responseWithDeal.tags[0].ads[0].deal_code = '123'; - responseWithDeal.tags[0].ads[0].rtb.video = { - duration_ms: 1500, - player_width: 640, - player_height: 340, - }; + it('should add deal_priority and deal_code', function () { + let responseWithDeal = deepClone(response); + responseWithDeal.tags[0].ads[0].ad_type = 'video'; + responseWithDeal.tags[0].ads[0].deal_priority = 5; + responseWithDeal.tags[0].ads[0].deal_code = '123'; + responseWithDeal.tags[0].ads[0].rtb.video = { + duration_ms: 1500, + player_width: 640, + player_height: 340, + }; - let bidderRequest = { - bids: [{ - bidId: '3db3773286ee59', - adUnitCode: 'code', - mediaTypes: { - video: { - context: 'adpod' + let bidderRequest = { + bids: [{ + bidId: '3db3773286ee59', + adUnitCode: 'code', + mediaTypes: { + video: { + context: 'adpod' + } } - } - }] - } - let result = spec.interpretResponse({ body: responseWithDeal }, { bidderRequest }); - expect(Object.keys(result[0].appnexus)).to.include.members(['buyerMemberId', 'dealPriority', 'dealCode']); - expect(result[0].video.dealTier).to.equal(5); - }); + }] + } + let result = spec.interpretResponse({ body: responseWithDeal }, { bidderRequest }); + expect(Object.keys(result[0].appnexus)).to.include.members(['buyerMemberId', 'dealPriority', 'dealCode']); + expect(result[0].video.dealTier).to.equal(5); + }); + } it('should add advertiser id', function () { let responseAdvertiserId = deepClone(response); diff --git a/test/spec/modules/appushBidAdapter_spec.js b/test/spec/modules/appushBidAdapter_spec.js index 91c50cc3dd0..e6af98c0f33 100644 --- a/test/spec/modules/appushBidAdapter_spec.js +++ b/test/spec/modules/appushBidAdapter_spec.js @@ -76,7 +76,8 @@ describe('AppushBidAdapter', function () { gdprConsent: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', refererInfo: { referer: 'https://test.com' - } + }, + timeout: 500 }; describe('isBidRequestValid', function () { diff --git a/test/spec/modules/astraoneBidAdapter_spec.js b/test/spec/modules/astraoneBidAdapter_spec.js index 0e545081869..80d6dcdf627 100644 --- a/test/spec/modules/astraoneBidAdapter_spec.js +++ b/test/spec/modules/astraoneBidAdapter_spec.js @@ -8,7 +8,11 @@ function getSlotConfigs(mediaTypes, params) { bidId: '2df8c0733f284e', bidder: 'astraone', mediaTypes: mediaTypes, - transactionId: '31a58515-3634-4e90-9c96-f86196db1459' + ortb2Imp: { + ext: { + tid: '31a58515-3634-4e90-9c96-f86196db1459' + } + } } } diff --git a/test/spec/modules/atsAnalyticsAdapter_spec.js b/test/spec/modules/atsAnalyticsAdapter_spec.js index cae90a19223..2316f96ec8e 100644 --- a/test/spec/modules/atsAnalyticsAdapter_spec.js +++ b/test/spec/modules/atsAnalyticsAdapter_spec.js @@ -3,14 +3,14 @@ import { expect } from 'chai'; import adapterManager from 'src/adapterManager.js'; import {server} from '../../mocks/xhr.js'; import {parseBrowser} from '../../../modules/atsAnalyticsAdapter.js'; -import {getStorageManager} from '../../../src/storageManager.js'; +import {getCoreStorageManager, getStorageManager} from '../../../src/storageManager.js'; import {analyticsUrl} from '../../../modules/atsAnalyticsAdapter.js'; let utils = require('src/utils'); let events = require('src/events'); let constants = require('src/constants.json'); -export const storage = getStorageManager(); +const storage = getCoreStorageManager(); let sandbox; let clock; let now = new Date(); diff --git a/test/spec/modules/audiencerunBidAdapter_spec.js b/test/spec/modules/audiencerunBidAdapter_spec.js index 57de8bdb0df..5c736345068 100644 --- a/test/spec/modules/audiencerunBidAdapter_spec.js +++ b/test/spec/modules/audiencerunBidAdapter_spec.js @@ -218,16 +218,7 @@ describe('AudienceRun bid adapter tests', function () { it('should add userid eids information to the request', function () { const bid = Object.assign({}, bidRequest); - bid.userId = { - pubcid: '01EAJWWNEPN3CYMM5N8M5VXY22', - unsuported: '666', - } - - const request = spec.buildRequests([bid]); - const payload = JSON.parse(request.data); - - expect(payload.userId).to.exist; - expect(payload.userId).to.deep.equal([ + bid.userIdAsEids = [ { source: 'pubcid.org', uids: [ @@ -237,7 +228,13 @@ describe('AudienceRun bid adapter tests', function () { }, ], }, - ]); + ]; + + const request = spec.buildRequests([bid]); + const payload = JSON.parse(request.data); + + expect(payload.userId).to.exist; + expect(payload.userId).to.deep.equal(bid.userIdAsEids); }); it('should add schain object if available', function() { diff --git a/test/spec/modules/axonixBidAdapter_spec.js b/test/spec/modules/axonixBidAdapter_spec.js index 37f409e5769..c1cc6d46fb2 100644 --- a/test/spec/modules/axonixBidAdapter_spec.js +++ b/test/spec/modules/axonixBidAdapter_spec.js @@ -286,26 +286,6 @@ describe('AxonixBidAdapter', function () { }); }); - describe.skip('buildRequests: can handle native ad requests', function () { - it('creates ServerRequests pointing to the correct region and endpoint if it changes', function () { - // loop: - // set supply id - // set region/endpoint in ssp config - // call buildRequests, validate request (url, method, supply id) - expect.fail('Not implemented'); - }); - - it('creates ServerRequests pointing to default endpoint if missing', function () { - // no endpoint in config means default value openrtb.axonix.com - expect.fail('Not implemented'); - }); - - it('creates ServerRequests pointing to default region if missing', function () { - // no region in config means default value us-east-1 - expect.fail('Not implemented'); - }); - }); - describe('interpretResponse', function () { it('considers corner cases', function() { expect(spec.interpretResponse(null)).to.be.an('array').that.is.empty; @@ -331,13 +311,6 @@ describe('AxonixBidAdapter', function () { expect(response).to.be.an('array').that.is.not.empty; expect(response[0]).to.equal(VIDEO_RESPONSE.body[0]); }); - - it.skip('parses 1 native responses', function () { - // passing 1 valid native in a response generates an array with 1 correct prebid response - // examine mediaType:native, native element - // check nativeBidIsValid from {@link file://./../../../src/native.js} - expect.fail('Not implemented'); - }); }); describe('onBidWon', function () { diff --git a/test/spec/modules/beachfrontBidAdapter_spec.js b/test/spec/modules/beachfrontBidAdapter_spec.js index addd4304c7d..4e30b822a61 100644 --- a/test/spec/modules/beachfrontBidAdapter_spec.js +++ b/test/spec/modules/beachfrontBidAdapter_spec.js @@ -695,7 +695,6 @@ describe('BeachfrontAdapter', function () { const bidResponse = spec.interpretResponse({ body: serverResponse }, { bidRequest }); expect(bidResponse).to.deep.equal({ requestId: bidRequest.bidId, - bidderCode: spec.code, cpm: serverResponse.bidPrice, creativeId: serverResponse.crid, vastUrl: serverResponse.url, diff --git a/test/spec/modules/bedigitechBidAdapter_spec.js b/test/spec/modules/bedigitechBidAdapter_spec.js new file mode 100644 index 00000000000..20d4e86e0c4 --- /dev/null +++ b/test/spec/modules/bedigitechBidAdapter_spec.js @@ -0,0 +1,134 @@ +import { expect } from 'chai'; +import { spec } from 'modules/bedigitechBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; +import {BANNER} from 'src/mediaTypes.js'; + +describe('BedigitechAdapter', function () { + const adapter = newBidder(spec); + + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function () { + const bid = { + bidder: 'bedigitech', + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + params: { + placementId: 309, + }, + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not passed', function () { + const bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'masterId': 0 + }; + + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + const bidRequests = [{ + bidder: 'bedigitech', + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + params: { + placementId: 309, + }, + }]; + + it('sends bid request to url via GET', function () { + const request = spec.buildRequests(bidRequests)[0]; + expect(request.method).to.equal('GET'); + expect(request.url).to.equal('https://bid.bedigitech.com/bid/pub_bid.php'); + }); + + it('should attach pid to url', function () { + const request = spec.buildRequests(bidRequests)[0]; + expect(request.data.pid).to.equal(309); + }); + }); + + describe('interpretResponse', function () { + const response = { + 'body': [ + { + 'ad': '%3C!--%20Creative%20--%3E', + 'cpm': '5.0', + 'crid': '0af345b42983cc4bc0', + 'currency': 'USD', + 'height': 250, + 'id': 'bedigitechMyidfdfdf', + 'netRevenue': true, + 'requestTime': 1686306237, + 'timeToRespond': 300, + 'ttl': 300, + 'width': 300 + } + ], + 'headers': { + 'get': function() {} + } + }; + + it('should get correct bid responseiiiiii', function () { + const expectedResponse = [ + { + 'ad': '%3C!--%20Creative%20--%3E', + 'cpm': '5.0', + 'creativeId': '0af345b42983cc4bc0', + 'currency': 'USD', + 'height': 250, + 'id': 'bedigitechMyidfdfdf', + 'meta': { + 'mediaType': BANNER, + }, + 'netRevenue': true, + 'requestId': 'bedigitechMyidfdfdf', + 'requestTimestamp': 1686306237, + 'timeToRespond': 300, + 'ttl': 300, + 'width': 300, + 'bidderCode': 'bedigitech', + } + ]; + const result = spec.interpretResponse(response); + expect(result).to.have.lengthOf(1); + let resultKeys = Object.keys(result[0]); + expect(resultKeys.sort()).to.deep.equal(Object.keys(expectedResponse[0]).sort()); + resultKeys.forEach(function(k) { + if (k === 'ad') { + expect(result[0][k]).to.match(/$/); + } else if (k === 'meta') { + expect(result[0][k]).to.deep.equal(expectedResponse[0][k]); + } else { + expect(result[0][k]).to.equal(expectedResponse[0][k]); + } + }); + }); + }); +}); diff --git a/test/spec/modules/beopBidAdapter_spec.js b/test/spec/modules/beopBidAdapter_spec.js index ad096fd6526..c77e304e539 100644 --- a/test/spec/modules/beopBidAdapter_spec.js +++ b/test/spec/modules/beopBidAdapter_spec.js @@ -99,6 +99,8 @@ describe('BeOp Bid Adapter tests', () => { expect(url).to.equal(ENDPOINT); expect(payload.pid).to.exist; expect(payload.pid).to.equal('5a8af500c9e77c00017e4cad'); + expect(payload.gdpr_applies).to.exist; + expect(payload.gdpr_applies).to.equals(false); expect(payload.slts[0].name).to.exist; expect(payload.slts[0].name).to.equal('bellow-article'); expect(payload.slts[0].flr).to.equal(10); @@ -121,6 +123,8 @@ describe('BeOp Bid Adapter tests', () => { const request = spec.buildRequests(bidRequests, bidderRequest); const payload = JSON.parse(request.data); + expect(payload.gdpr_applies).to.exist; + expect(payload.gdpr_applies).to.equals(true); expect(payload.tc_string).to.exist; expect(payload.tc_string).to.equal('BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='); expect(payload.url).to.exist; @@ -128,6 +132,46 @@ describe('BeOp Bid Adapter tests', () => { expect(payload.url).to.equal('http://test.te'); }); + it('should call the endpoint with psegs and bpsegs (stringified) data if any or [] if none', function () { + let bidderRequest = + { + 'ortb2': { + 'user': { + 'ext': { + 'bpsegs': ['axed', 'axec', 1234], + 'data': { + 'permutive': [1234, 5678, 910] + } + } + } + } + }; + + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = JSON.parse(request.data); + expect(payload.psegs).to.exist; + expect(payload.psegs).to.include(1234); + expect(payload.psegs).to.include(5678); + expect(payload.psegs).to.include(910); + expect(payload.psegs).to.not.include(1); + expect(payload.bpsegs).to.exist; + expect(payload.bpsegs).to.include('axed'); + expect(payload.bpsegs).to.include('axec'); + expect(payload.bpsegs).to.include('1234'); + + let bidderRequest2 = + { + 'ortb2': {} + }; + + const request2 = spec.buildRequests(bidRequests, bidderRequest2); + const payload2 = JSON.parse(request2.data); + expect(payload2.psegs).to.exist; + expect(payload2.psegs).to.be.empty; + expect(payload2.bpsegs).to.exist; + expect(payload2.bpsegs).to.be.empty; + }); + it('should not prepend the protocol in page url if already present', function () { const bidderRequest = { 'refererInfo': { diff --git a/test/spec/modules/beyondmediaBidAdapter_spec.js b/test/spec/modules/beyondmediaBidAdapter_spec.js index a4c1125fa5f..751b3ae1098 100644 --- a/test/spec/modules/beyondmediaBidAdapter_spec.js +++ b/test/spec/modules/beyondmediaBidAdapter_spec.js @@ -76,7 +76,8 @@ describe('AndBeyondMediaBidAdapter', function () { gdprConsent: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', refererInfo: { referer: 'https://test.com' - } + }, + timeout: 500 }; describe('isBidRequestValid', function () { diff --git a/test/spec/modules/bidwatchAnalyticsAdapter_spec.js b/test/spec/modules/bidwatchAnalyticsAdapter_spec.js index a72bb012fa1..be1ffb06c76 100644 --- a/test/spec/modules/bidwatchAnalyticsAdapter_spec.js +++ b/test/spec/modules/bidwatchAnalyticsAdapter_spec.js @@ -1,4 +1,5 @@ import bidwatchAnalytics from 'modules/bidwatchAnalyticsAdapter.js'; +import {dereferenceWithoutRenderer} from 'modules/bidwatchAnalyticsAdapter.js'; import { expect } from 'chai'; import { server } from 'test/mocks/xhr.js'; let adapterManager = require('src/adapterManager').default; @@ -155,7 +156,7 @@ describe('BidWatch Analytics', function () { 'requestId': '34a63e5d5378a3', 'transactionId': 'de664ccb-e18b-4436-aeb0-362382eb1b40', 'auctionId': '1e8b993d-8f0a-4232-83eb-3639ddf3a44b', - 'mediaType': 'banner', + 'mediaType': 'video', 'source': 'client', 'cpm': 27.4276, 'creativeId': '158534630', @@ -168,6 +169,7 @@ describe('BidWatch Analytics', function () { 'example.com' ] }, + 'renderer': 'something', 'originalCpm': 25.02521, 'originalCurrency': 'EUR', 'responseTimestamp': 1647424261559, @@ -221,6 +223,7 @@ describe('BidWatch Analytics', function () { 'example.com' ] }, + 'renderer': 'something', 'originalCpm': 25.02521, 'originalCurrency': 'EUR', 'responseTimestamp': 1647424261558, @@ -267,7 +270,24 @@ describe('BidWatch Analytics', function () { bidwatchAnalytics.disableAnalytics(); bidwatchAnalytics.track.restore(); }); + it('test dereferenceWithoutRenderer', function () { + adapterManager.registerAnalyticsAdapter({ + code: 'bidwatch', + adapter: bidwatchAnalytics + }); + adapterManager.enableAnalytics({ + provider: 'bidwatch', + options: { + domain: 'test' + } + }); + let resultBidWon = JSON.parse(dereferenceWithoutRenderer(bidWon)); + expect(resultBidWon).not.to.have.property('renderer'); + let resultBid = JSON.parse(dereferenceWithoutRenderer(auctionEnd)); + expect(resultBid).to.have.property('bidsReceived').and.to.have.lengthOf(1); + expect(resultBid.bidsReceived[0]).not.to.have.property('renderer'); + }); it('test auctionEnd', function () { adapterManager.registerAnalyticsAdapter({ code: 'bidwatch', diff --git a/test/spec/modules/big-richmediaBidAdapter_spec.js b/test/spec/modules/big-richmediaBidAdapter_spec.js index b2ee0725d49..c3a9a8ef6c1 100644 --- a/test/spec/modules/big-richmediaBidAdapter_spec.js +++ b/test/spec/modules/big-richmediaBidAdapter_spec.js @@ -92,53 +92,45 @@ describe('bigRichMediaAdapterTests', function () { expect(payload.tags[0].sizes).to.have.lengthOf(3); }); - it('should build video bid request', function() { - const bidRequest = deepClone(bidRequests[0]); - bidRequest.params = { - placementId: '1234235', - video: { - skippable: true, - playback_method: ['auto_play_sound_off', 'auto_play_sound_unknown'], - context: 'outstream', - format: 'sticky-top' - } - }; - bidRequest.mediaTypes = { - video: { - playerSize: [640, 480], - context: 'outstream', - mimes: ['video/mp4'], - skip: 0, - minduration: 5, - api: [1, 5, 6], - playbackmethod: [2, 4] - } - }; + if (FEATURES.VIDEO) { + it('should build video bid request', function() { + const bidRequest = deepClone(bidRequests[0]); + bidRequest.params = { + placementId: '1234235', + video: { + skippable: true, + playback_method: ['auto_play_sound_off', 'auto_play_sound_unknown'], + context: 'outstream', + format: 'sticky-top' + } + }; + bidRequest.mediaTypes = { + video: { + playerSize: [640, 480], + context: 'outstream', + mimes: ['video/mp4'], + skip: 0, + minduration: 5, + api: [1, 5, 6], + playbackmethod: [2, 4] + } + }; - const request = spec.buildRequests([bidRequest]); - const payload = JSON.parse(request.data); + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); - expect(payload.tags[0].video).to.deep.equal({ - minduration: 5, - playback_method: 2, - skippable: true, - context: 4 + expect(payload.tags[0].video).to.deep.equal({ + minduration: 5, + playback_method: 2, + skippable: true, + context: 4 + }); + expect(payload.tags[0].video_frameworks).to.deep.equal([1, 4]) }); - expect(payload.tags[0].video_frameworks).to.deep.equal([1, 4]) - }); + } }); describe('interpretResponse', function () { - let bfStub; - - before(function() { - bfStub = sinon.stub(bidderFactory, 'getIabSubCategory'); - }); - - after(function() { - bfStub.restore(); - }); - const response = { 'version': '3.0.0', 'tags': [ @@ -227,42 +219,44 @@ describe('bigRichMediaAdapterTests', function () { expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); }); - it('handles outstream video responses', function () { - const response = { - 'tags': [{ - 'uuid': '84ab500420319d', - 'ads': [{ - 'ad_type': 'video', - 'cpm': 0.500000, - 'notify_url': 'imptracker.com', - 'rtb': { - 'video': { - 'content': '' - } - }, - 'javascriptTrackers': '' + if (FEATURES.VIDEO) { + it('handles outstream video responses', function () { + const response = { + 'tags': [{ + 'uuid': '84ab500420319d', + 'ads': [{ + 'ad_type': 'video', + 'cpm': 0.500000, + 'notify_url': 'imptracker.com', + 'rtb': { + 'video': { + 'content': '' + } + }, + 'javascriptTrackers': '' + }] }] - }] - }; - const bidderRequest = { - bids: [{ - bidId: '84ab500420319d', - adUnitCode: 'code', - mediaTypes: { - video: { - context: 'outstream' + }; + const bidderRequest = { + bids: [{ + bidId: '84ab500420319d', + adUnitCode: 'code', + mediaTypes: { + video: { + context: 'outstream' + } } - } - }] - } + }] + } - const result = spec.interpretResponse({ body: response }, {bidderRequest}); - expect(result[0]).not.to.have.property('vastXml'); - expect(result[0]).not.to.have.property('vastUrl'); - expect(result[0]).to.have.property('width', 1); - expect(result[0]).to.have.property('height', 1); - expect(result[0]).to.have.property('mediaType', 'banner'); - }); + const result = spec.interpretResponse({ body: response }, {bidderRequest}); + expect(result[0]).not.to.have.property('vastXml'); + expect(result[0]).not.to.have.property('vastUrl'); + expect(result[0]).to.have.property('width', 1); + expect(result[0]).to.have.property('height', 1); + expect(result[0]).to.have.property('mediaType', 'banner'); + }); + } }); describe('getUserSyncs', function() { diff --git a/test/spec/modules/bluebillywigBidAdapter_spec.js b/test/spec/modules/bluebillywigBidAdapter_spec.js index 0a9c6b30c94..4b58e3507db 100644 --- a/test/spec/modules/bluebillywigBidAdapter_spec.js +++ b/test/spec/modules/bluebillywigBidAdapter_spec.js @@ -1,10 +1,8 @@ -import { expect } from 'chai'; -import { spec } from 'modules/bluebillywigBidAdapter.js'; -import * as bidderFactory from 'src/adapters/bidderFactory.js'; -import { auctionManager } from 'src/auctionManager.js'; -import { deepClone, deepAccess } from 'src/utils.js'; -import { config } from 'src/config.js'; -import { VIDEO } from 'src/mediaTypes.js'; +import {expect} from 'chai'; +import {spec} from 'modules/bluebillywigBidAdapter.js'; +import {deepAccess, deepClone} from 'src/utils.js'; +import {config} from 'src/config.js'; +import {VIDEO} from 'src/mediaTypes.js'; const BB_CONSTANTS = { BIDDER_CODE: 'bluebillywig', @@ -254,7 +252,11 @@ describe('BlueBillywigAdapter', () => { const baseValidBidRequests = [baseValidBid]; const validBidderRequest = { - auctionId: '12abc345-67d8-9012-e345-6f78901a2b34', + ortb2: { + source: { + tid: '12abc345-67d8-9012-e345-6f78901a2b34', + } + }, auctionStart: 1585918458868, bidderCode: BB_CONSTANTS.BIDDER_CODE, bidderRequestId: '1a2345b67c8d9e0', @@ -293,9 +295,9 @@ describe('BlueBillywigAdapter', () => { const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); const payload = JSON.parse(request.data); - expect(payload.id).to.equal(validBidderRequest.auctionId); + expect(payload.id).to.exist; expect(payload.source).to.be.an('object'); - expect(payload.source.tid).to.equal(validBidderRequest.auctionId); + expect(payload.source.tid).to.equal(validBidderRequest.ortb2.source.tid); expect(payload.tmax).to.equal(BB_CONSTANTS.DEFAULT_TIMEOUT); expect(payload.imp).to.be.an('array'); expect(payload.test).to.be.a('number'); @@ -535,10 +537,8 @@ describe('BlueBillywigAdapter', () => { }); it('should set user ids when present', () => { - const userId = { tdid: 123 }; - const newBaseValidBidRequests = deepClone(baseValidBidRequests); - newBaseValidBidRequests[0].userId = { criteoId: 'sample-userid' }; + newBaseValidBidRequests[0].userIdAsEids = [ {} ]; const request = spec.buildRequests(newBaseValidBidRequests, validBidderRequest); const payload = JSON.parse(request.data); diff --git a/test/spec/modules/brandmetricsRtdProvider_spec.js b/test/spec/modules/brandmetricsRtdProvider_spec.js index 879ec7e1c7a..907c672208f 100644 --- a/test/spec/modules/brandmetricsRtdProvider_spec.js +++ b/test/spec/modules/brandmetricsRtdProvider_spec.js @@ -1,5 +1,7 @@ import * as brandmetricsRTD from '../../../modules/brandmetricsRtdProvider.js'; import {config} from 'src/config.js'; +import * as events from '../../../src/events'; +import * as sinon from 'sinon'; const VALID_CONFIG = { name: 'brandmetrics', @@ -77,14 +79,16 @@ function mockSurveyLoaded(surveyConf) { }); } -function scriptTagExists(url) { - const tags = document.getElementsByTagName('script'); - for (let i = 0; i < tags.length; i++) { - if (tags[i].src === url) { - return true; +function mockCreativeInView(creativeInViewConf) { + const commands = window._brandmetrics || []; + commands.forEach(command => { + if (command.cmd === '_addeventlistener') { + const conf = command.val; + if (conf.event === 'creative_in_view') { + conf.handler(creativeInViewConf); + } } - } - return false; + }) } describe('BrandmetricsRTD module', () => { @@ -188,4 +192,62 @@ describe('getBidRequestData', () => { expect(bidderOrtb2[exp].user.ext.data.brandmetrics_survey).to.equal('mockMeasurementId') }) }); + + describe('billable events', () => { + let sandbox; + let eventsEmitSpy; + + before(() => { + sandbox = sinon.sandbox.create(); + eventsEmitSpy = sandbox.spy(events, ['emit']); + }); + + beforeEach(() => { + eventsEmitSpy.resetHistory(); + }) + + afterEach(() => { + sandbox.restore(); + }); + + it('should emit billable event from prebid events', () => { + const expectedEvent = { + vendor: 'brandmetrics', + type: 'creative_in_view', + measurementId: 'mockMeasurementId', + auctionId: 'mockAuctionId', + transactionId: 'mockTransactionId' + }; + + mockCreativeInView({ + mid: expectedEvent.measurementId, + source: { + type: 'pbj', + data: { + auctionId: expectedEvent.auctionId, + transactionId: expectedEvent.transactionId + }, + } + }); + + expect(eventsEmitSpy.callCount).to.equal(1); + + const event = eventsEmitSpy.getCalls()[0].args[1]; + delete event['billingId']; + + expect(event).to.deep.equal(expectedEvent); + }); + + it('should not emit billable event from non prebid- sources', () => { + mockCreativeInView({ + mid: 'mockMeasurementId', + source: { + type: 'gpt', + data: {}, + } + }); + + expect(eventsEmitSpy.callCount).to.equal(0); + }); + }); }); diff --git a/test/spec/modules/bridBidAdapter_spec.js b/test/spec/modules/bridBidAdapter_spec.js new file mode 100644 index 00000000000..7503c748999 --- /dev/null +++ b/test/spec/modules/bridBidAdapter_spec.js @@ -0,0 +1,129 @@ +import { spec } from '../../../modules/bridBidAdapter.js' + +describe('Brid Bid Adapter', function() { + const videoRequest = [{ + bidder: 'brid', + params: { + placementId: 12345, + }, + mediaTypes: { + video: { + playerSize: [[640, 360]], + context: 'instream', + playbackmethod: [1, 2, 3, 4] + } + } + }]; + + it('Test the bid validation function', function() { + const validBid = spec.isBidRequestValid(videoRequest[0]); + const invalidBid = spec.isBidRequestValid(null); + + expect(validBid).to.be.true; + expect(invalidBid).to.be.false; + }); + + it('Test the request processing function', function () { + const request = spec.buildRequests(videoRequest, videoRequest[0]); + expect(request).to.not.be.empty; + + const payload = JSON.parse(request[0].data); + expect(payload).to.not.be.empty; + expect(payload.sdk).to.deep.equal({ + source: 'pbjs', + version: '$prebid.version$' + }); + expect(payload.imp[0].ext.prebid.storedrequest.id).to.equal(12345); + }); + + it('Test nobid responses', function () { + const responseBody = { + 'id': 'test-id', + 'cur': 'USD', + 'seatbid': [], + 'nbr': 0 + }; + const bidderRequest = null; + + const bidResponse = spec.interpretResponse({ body: responseBody }, {bidderRequest}); + + expect(bidResponse.length).to.equal(0); + }); + + it('Test the response parsing function', function () { + const responseBody = { + 'id': 'test-id', + 'cur': 'USD', + 'seatbid': [{ + 'bid': [{ + 'id': '5044997188309660254', + 'price': 5, + 'adm': 'test ad', + 'adid': '97517771', + 'crid': '97517771', + 'adomain': ['domain.com'], + 'w': 640, + 'h': 480 + }], + 'seat': 'bidder' + }] + }; + const bidderRequest = { + bidderCode: 'brid', + bidderRequestId: '22edbae2733bf6', + bids: videoRequest + }; + + const bidResponse = spec.interpretResponse({ body: responseBody }, {bidderRequest}); + expect(bidResponse).to.not.be.empty; + + const bid = bidResponse[0]; + expect(bid).to.not.be.empty; + expect(bid.ad).to.equal('test ad'); + expect(bid.cpm).to.equal(5); + expect(bid.width).to.equal(640); + expect(bid.height).to.equal(480); + expect(bid.currency).to.equal('USD'); + }); + + it('Test GDPR and USP consents are present in the request', function () { + let gdprConsentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; + let uspConsentString = '1YA-'; + let bidderRequest = { + 'bidderCode': 'brid', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'uspConsent': uspConsentString, + 'gdprConsent': { + consentString: gdprConsentString, + gdprApplies: true, + addtlConsent: '1~7.12.35.62.66.70.89.93.108' + } + }; + bidderRequest.bids = videoRequest; + + const request = spec.buildRequests(videoRequest, bidderRequest); + const payload = JSON.parse(request[0].data); + + expect(payload.user.ext.consent).to.equal(gdprConsentString); + expect(payload.regs.ext.us_privacy).to.equal(uspConsentString); + expect(payload.regs.ext.gdpr).to.equal(1); + }); + + it('Test GDPR is not present', function () { + let uspConsentString = '1YA-'; + let bidderRequest = { + 'bidderCode': 'brid', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'uspConsent': uspConsentString + }; + bidderRequest.bids = videoRequest; + + const request = spec.buildRequests(videoRequest, bidderRequest); + const payload = JSON.parse(request[0].data); + + expect(payload.regs.ext.gdpr).to.be.undefined; + expect(payload.regs.ext.us_privacy).to.equal(uspConsentString); + }); +}); diff --git a/test/spec/modules/browsiBidAdapter_spec.js b/test/spec/modules/browsiBidAdapter_spec.js new file mode 100644 index 00000000000..9693972fd7f --- /dev/null +++ b/test/spec/modules/browsiBidAdapter_spec.js @@ -0,0 +1,212 @@ +import {ENDPOINT, spec} from 'modules/browsiBidAdapter.js'; +import {config} from 'src/config.js'; +import {VIDEO, BANNER} from 'src/mediaTypes.js'; + +const {expect} = require('chai'); +const DATA = 'brwvidtag'; +const ADAPTER = '__bad'; + +describe('browsi Bid Adapter Test', function () { + describe('isBidRequestValid', function () { + let mediaTypes; + let bid; + beforeEach(function () { + mediaTypes = {}; + mediaTypes[VIDEO] = {}; + bid = { + 'params': { + 'pubId': '1234567', + 'tagId': '1' + }, + 'mediaTypes': mediaTypes + }; + }); + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + it('should return false when missing pubId', function () { + delete bid.params.pubId; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + it('should return false when missing tagId', function () { + delete bid.params.tagId; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + it('should return false when missing params', function () { + delete bid.params; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + it('should return false when params have invalid type', function () { + bid.params.tagId = 1; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + it('should return false when video mediaType is missing', function () { + delete bid.mediaTypes[VIDEO]; + bid.mediaTypes[BANNER] = {} + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + let bidRequest; + let bidderRequest; + beforeEach(function () { + window[DATA] = {} + window[DATA][ADAPTER] = {index: 0}; + bidRequest = [ + { + 'params': { + 'pubId': 'browiPubId1', + 'tagId': '2' + }, + 'adUnitCode': 'adUnitCode1', + 'auctionId': 'auctionId1', + 'sizes': [640, 480], + 'bidId': '12345678', + 'requestId': '1234567-3456-4562-7689-98765434A', + ortb2Imp: { + ext: { + tid: '1234567-3456-4562-7689-98765434B', + } + }, + 'schain': {}, + 'mediaTypes': {video: {playerSize: [640, 480]}} + } + ]; + bidderRequest = { + 'bidderRequestId': 'bidderRequestId1', + 'refererInfo': { + 'canonicalUrl': null, + 'page': 'https://browsi.com', + 'domain': 'browsi.com', + 'ref': null, + 'numIframes': 0, + 'reachedTop': true, + 'isAmp': false, + 'stack': ['https://browsi.com'] + }, + 'gdprConsent': { + consentString: 'BOtmiBKOtmiBKABABAENAFAAAAACeAAA', + gdprApplies: true + }, + 'uspConsent': '1YYY' + }; + }); + afterEach(function() { + window[DATA] = undefined; + config.resetConfig(); + }); + it('should return an array of requests with single request', function () { + const requests = spec.buildRequests(bidRequest, bidderRequest); + expect(requests.length).to.equal(1); + const request = requests[0]; + const inputRequest = bidRequest[0]; + const requestToExpect = { + method: 'POST', + url: ENDPOINT, + data: { + requestId: bidderRequest.bidderRequestId, + bidId: inputRequest.bidId, + timeout: 3000, + baData: window[DATA][ADAPTER], + referer: bidderRequest.refererInfo.page, + gdpr: bidderRequest.gdprConsent, + ccpa: bidderRequest.uspConsent, + sizes: inputRequest.sizes, + video: {playerSize: [640, 480]}, + aUCode: inputRequest.adUnitCode, + aID: inputRequest.auctionId, + tID: inputRequest.ortb2Imp.ext.tid, + schain: inputRequest.schain, + params: inputRequest.params + } + } + assert.deepEqual(request, requestToExpect); + }); + it('should pass on timeout in bidderRequest', function() { + bidderRequest.timeout = 8000; + const requests = spec.buildRequests(bidRequest, bidderRequest); + expect(requests[0].data.timeout).to.equal(8000); + }); + it('should pass timeout in config', function() { + config.setConfig({'bidderTimeout': 6000}); + const requests = spec.buildRequests(bidRequest, bidderRequest); + expect(requests[0].data.timeout).to.equal(6000); + }); + }); + + describe('interpretResponse', function () { + let bidRequest = { + 'url': ENDPOINT, + 'data': { + 'bidId': 'bidId1', + } + }; + let serverResponse = {}; + serverResponse.body = { + bidId: 'bidId1', + w: 300, + h: 250, + vXml: 'vastXml', + vUrl: 'vastUrl', + cpm: 1, + cur: 'USD', + ttl: 10000, + someExtraParams: 8, + } + + it('should return a valid response', function () { + const bidResponses = spec.interpretResponse(serverResponse, bidRequest); + expect(bidResponses.length).to.equal(1); + const actualBidResponse = bidResponses[0]; + const expectedBidResponse = { + requestId: bidRequest.data.bidId, + bidId: 'bidId1', + width: 300, + height: 250, + vastXml: 'vastXml', + vastUrl: 'vastUrl', + cpm: 1, + currency: 'USD', + ttl: 10000, + someExtraParams: 8, + mediaType: VIDEO + }; + + assert.deepEqual(actualBidResponse, expectedBidResponse); + }); + }); + + describe('getUserSyncs', function () { + const bidResponse = { + userSyncs: [ + {url: 'syncUrl1', type: 'image'}, + {url: 'http://syncUrl2', type: 'iframe'} + ] + } + let serverResponse = [ + {body: bidResponse} + ]; + it('should return iframe type userSync', function () { + let userSyncs = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: false}, serverResponse[0]); + expect(userSyncs.length).to.equal(1); + let userSync = userSyncs[0]; + expect(userSync.url).to.equal('http://syncUrl2'); + expect(userSync.type).to.equal('iframe'); + }); + it('should return image type userSyncs', function () { + let userSyncs = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: true}, serverResponse[0]); + let userSync = userSyncs[0]; + expect(userSync.url).to.equal('http://syncUrl1'); + expect(userSync.type).to.equal('image'); + }); + it('should handle multiple server responses', function () { + let userSyncs = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: true}, serverResponse); + expect(userSyncs.length).to.equal(1); + }); + it('should return empty userSyncs', function () { + let userSyncs = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: false}, serverResponse); + expect(userSyncs.length).to.equal(0); + }); + }); +}); diff --git a/test/spec/modules/byDataAnalyticsAdapter_spec.js b/test/spec/modules/byDataAnalyticsAdapter_spec.js index 1346284695c..c680c687a71 100644 --- a/test/spec/modules/byDataAnalyticsAdapter_spec.js +++ b/test/spec/modules/byDataAnalyticsAdapter_spec.js @@ -96,7 +96,7 @@ let expectedDataArgs = { aus: '300x250', bidadv: 'appnexus', bid: '14480e9832f2d2b', - inb: 0, + inb: 1, ito: 0, ipwb: 0, iwb: 0, @@ -107,7 +107,7 @@ let expectedDataArgs = { aus: '250x250', bidadv: 'appnexus', bid: '14480e9832f2d2b', - inb: 0, + inb: 1, ito: 0, ipwb: 0, iwb: 0, @@ -167,11 +167,13 @@ describe('byData Analytics Adapter ', () => { }); describe('track-events', function () { - ascAdapter.enableAnalytics(initOptions) - // Step 1: Initialize adapter - adapterManager.enableAnalytics({ - provider: 'bydata', - options: initOptions + before(() => { + ascAdapter.enableAnalytics(initOptions) + // Step 1: Initialize adapter + adapterManager.enableAnalytics({ + provider: 'bydata', + options: initOptions + }); }); it('sends and formatted auction data ', function () { events.emit(constants.EVENTS.BID_TIMEOUT, bidTimeoutArgs); diff --git a/test/spec/modules/emx_digitalBidAdapter_spec.js b/test/spec/modules/cadentApertureMXBidAdapter_spec.js similarity index 91% rename from test/spec/modules/emx_digitalBidAdapter_spec.js rename to test/spec/modules/cadentApertureMXBidAdapter_spec.js index d80d0f3e875..eb127cfd9f3 100644 --- a/test/spec/modules/emx_digitalBidAdapter_spec.js +++ b/test/spec/modules/cadentApertureMXBidAdapter_spec.js @@ -1,9 +1,9 @@ import { expect } from 'chai'; -import { spec } from 'modules/emx_digitalBidAdapter.js'; +import { spec } from 'modules/cadentApertureMXBidAdapter.js'; import * as utils from 'src/utils.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; -describe('emx_digital Adapter', function () { +describe('cadent_aperture_mx Adapter', function () { describe('callBids', function () { const adapter = newBidder(spec); it('exists and is a function', function () { @@ -14,7 +14,7 @@ describe('emx_digital Adapter', function () { describe('isBidRequestValid', function () { describe('banner request validity', function () { let bid = { - 'bidder': 'emx_digital', + 'bidder': 'cadent_aperture_mx', 'params': { 'tagid': '25251' }, @@ -33,7 +33,7 @@ describe('emx_digital Adapter', function () { 'auctionId': '1d1a01234a475' }; let badBid = { - 'bidder': 'emx_digital', + 'bidder': 'cadent_aperture_mx', 'params': { 'tagid': '25251' }, @@ -95,7 +95,7 @@ describe('emx_digital Adapter', function () { describe('video request validity', function () { let bid = { - 'bidder': 'emx_digital', + 'bidder': 'cadent_aperture_mx', 'params': { 'tagid': '25251', 'video': {} @@ -116,7 +116,7 @@ describe('emx_digital Adapter', function () { 'auctionId': '1d1a01234a475' }; let noInstreamBid = { - 'bidder': 'emx_digital', + 'bidder': 'cadent_aperture_mx', 'params': { 'tagid': '25251', 'video': { @@ -139,7 +139,7 @@ describe('emx_digital Adapter', function () { }; let outstreamBid = { - 'bidder': 'emx_digital', + 'bidder': 'cadent_aperture_mx', 'params': { 'tagid': '25251', 'video': {} @@ -168,7 +168,7 @@ describe('emx_digital Adapter', function () { it('should contain tagid param', function () { expect(spec.isBidRequestValid({ - bidder: 'emx_digital', + bidder: 'cadent_aperture_mx', params: {}, mediaTypes: { banner: { @@ -177,7 +177,7 @@ describe('emx_digital Adapter', function () { } })).to.equal(false); expect(spec.isBidRequestValid({ - bidder: 'emx_digital', + bidder: 'cadent_aperture_mx', params: { tagid: '' }, @@ -188,7 +188,7 @@ describe('emx_digital Adapter', function () { } })).to.equal(false); expect(spec.isBidRequestValid({ - bidder: 'emx_digital', + bidder: 'cadent_aperture_mx', params: { tagid: '123' }, @@ -204,7 +204,7 @@ describe('emx_digital Adapter', function () { describe('buildRequests', function () { let bidderRequest = { - 'bidderCode': 'emx_digital', + 'bidderCode': 'cadent_aperture_mx', 'auctionId': 'e19f1eff-8b27-42a6-888d-9674e5a6130c', 'bidderRequestId': '22edbae3120bf6', 'timeout': 1500, @@ -216,7 +216,7 @@ describe('emx_digital Adapter', function () { 'ref': 'https://referrer.com' }, 'bids': [{ - 'bidder': 'emx_digital', + 'bidder': 'cadent_aperture_mx', 'params': { 'tagid': '25251' }, @@ -464,7 +464,7 @@ describe('emx_digital Adapter', function () { describe('interpretResponse', function () { let bid = { - 'bidder': 'emx_digital', + 'bidder': 'cadent_aperture_mx', 'params': { 'tagid': '25251', 'video': {} @@ -488,7 +488,7 @@ describe('emx_digital Adapter', function () { const bid_outstream = { 'bidderRequest': { 'bids': [{ - 'bidder': 'emx_digital', + 'bidder': 'cadent_aperture_mx', 'params': { 'tagid': '25251', 'video': {} @@ -508,7 +508,7 @@ describe('emx_digital Adapter', function () { 'bidderRequestId': '22edbae3120bf6', 'auctionId': '1d1a01234a475' }, { - 'bidder': 'emx_digital', + 'bidder': 'cadent_aperture_mx', 'params': { 'tagid': '25252', 'video': {} @@ -699,8 +699,8 @@ describe('emx_digital Adapter', function () { it('should not throw an error when decoding an improperly encoded adm', function () { const badAdmServerResponse = utils.deepClone(serverResponse); - badAdmServerResponse.seatbid[0].bid[0].adm = '\\<\\/script\\>'; - badAdmServerResponse.seatbid[1].bid[0].adm = '%3F%%3Demx%3C3prebid'; + badAdmServerResponse.seatbid[0].bid[0].adm = '\\<\\/script\\>'; + badAdmServerResponse.seatbid[1].bid[0].adm = '%3F%%3Dcadent%3C3prebid'; assert.doesNotThrow(() => spec.interpretResponse({ body: badAdmServerResponse diff --git a/test/spec/modules/captifyRtdProvider_spec.js b/test/spec/modules/captifyRtdProvider_spec.js deleted file mode 100644 index 2e1052e000f..00000000000 --- a/test/spec/modules/captifyRtdProvider_spec.js +++ /dev/null @@ -1,253 +0,0 @@ -import {addSegmentData, captifySubmodule, getMatchingBidders, setCaptifyTargeting} from 'modules/captifyRtdProvider.js'; -import {server} from 'test/mocks/xhr.js'; -import {config} from 'src/config.js'; -import {deepAccess} from '../../../src/utils'; - -const responseHeader = {'Content-Type': 'application/json'}; -const defaultRequestUrl = 'https://live-classification.cpx.to/prebid-segments'; - -describe('captifyRtdProvider', function () { - describe('init function', function () { - it('successfully instantiates, when configured properly', function () { - const config = { - params: { - pubId: 123456, - bidders: ['appnexus'], - } - }; - expect(captifySubmodule.init(config, null)).to.equal(true); - }); - - it('return false on init, when config is invalid', function () { - const config = { - params: {} - }; - expect(captifySubmodule.init(config, null)).to.equal(false); - expect(captifySubmodule.init(null, null)).to.equal(false); - }); - - it('return false on init, when pubId is absent', function () { - const config = { - params: { - bidders: ['appnexus'], - } - }; - expect(captifySubmodule.init(config, null)).to.equal(false); - expect(captifySubmodule.init(null, null)).to.equal(false); - }); - - it('return false on init, when bidders is empty array', function () { - const config = { - params: { - bidders: [], - pubId: 123, - } - }; - expect(captifySubmodule.init(config, null)).to.equal(false); - expect(captifySubmodule.init(null, null)).to.equal(false); - }); - }); - - describe('addSegmentData function', function () { - it('adds segment data', function () { - config.resetConfig(); - - let data = { - xandr: [111111, 222222], - }; - - addSegmentData(['appnexus'], data); - expect(deepAccess(config.getConfig(), 'appnexusAuctionKeywords.captify_segments')).to.eql(data['xandr']); - }); - }); - - describe('getMatchingBidders function', function () { - it('returns only bidders that used within adUnits', function () { - const moduleConfig = { - params: { - pubId: 123, - bidders: ['appnexus', 'pubmatic'], - } - }; - let reqBidsConfigObj = { - adUnits: [{ - bids: [{ - bidder: 'appnexus', - params: { - placementId: 13144370 - } - }] - }] - }; - - let matchedBidders = getMatchingBidders(moduleConfig, reqBidsConfigObj); - expect(matchedBidders).to.eql(['appnexus']); - }); - - it('return empty result, when there are no bidders configured for adUnits', function () { - const moduleConfig = { - params: { - pubId: 123, - bidders: ['pubmatic'], - } - }; - let reqBidsConfigObj = { - adUnits: [{ - bids: [] - }] - }; - expect(getMatchingBidders(moduleConfig, reqBidsConfigObj)).to.be.empty; - }); - - it('return empty result, when there are no bidders matched', function () { - const moduleConfig = { - params: { - pubId: 123, - bidders: ['pubmatic'], - } - }; - let reqBidsConfigObj = { - adUnits: [{ - bids: [{ - params: { - bidder: 'appnexus', - placementId: 13144370, - } - }] - }] - }; - expect(getMatchingBidders(moduleConfig, reqBidsConfigObj)).to.be.empty; - }); - - it('return empty result, when there are no adUnits with bidders', function () { - const moduleConfig = { - params: { - pubId: 123, - bidders: ['pubmatic'], - } - }; - let reqBidsConfigObj = { - adUnits: [{ - bids: [{ - params: { - placementId: 13144370, - } - }] - }] - }; - expect(getMatchingBidders(moduleConfig, reqBidsConfigObj)).to.be.empty; - }); - }); - - describe('integration test with mock live-classification response', function () { - const moduleConfig = { - params: { - pubId: 123456, - bidders: ['appnexus'], - } - }; - - const reqBidsConfigObj = { - adUnits: [{ - bids: [{ - bidder: 'appnexus', - params: { - placementId: 13144370 - } - }, { - bidder: 'other' - }] - }] - }; - - const expectedUrlParam = 'http://localhost:9876/context.html'; - - it('gets data from async request and adds segment data', function () { - config.resetConfig(); - let data = {xandr: [111111, 222222]}; - const callbackSpy = sinon.spy(); - setCaptifyTargeting(reqBidsConfigObj, callbackSpy, moduleConfig, {}); - let request = server.requests[0]; - let requestBody = JSON.parse(server.requests[0].requestBody); - expect(request.url).to.be.eq(defaultRequestUrl); - expect(requestBody['pubId']).to.eq(moduleConfig.params.pubId); - expect(requestBody['url']).to.eq(expectedUrlParam); - request.respond(200, responseHeader, JSON.stringify(data)); - expect(deepAccess(config.getConfig(), 'appnexusAuctionKeywords.captify_segments')).to.eql(data['xandr']); - expect(callbackSpy.calledOnce).to.be.true; - }); - - it('do not send classification request, if no matching adUnits on page', function () { - config.resetConfig(); - let reqBidsConfigObj = { - adUnits: [{ - bids: [ - {bidder: 'pubmatic'} - ] - }] - }; - const callbackSpy = sinon.spy(); - setCaptifyTargeting(reqBidsConfigObj, callbackSpy, moduleConfig, {}); - expect(server.requests).to.be.empty; - }); - - it('gets data from async request and adds segment data, using URL from config', function () { - config.resetConfig(); - let data = {xandr: [111111, 222222]}; - const callbackSpy = sinon.spy(); - const testUrl = 'http://my-test-server.com/path'; - const conf = { - params: { - url: testUrl, - pubId: 123456, - bidders: ['appnexus'], - } - }; - setCaptifyTargeting(reqBidsConfigObj, callbackSpy, conf, {}); - let request = server.requests[0]; - let requestBody = JSON.parse(server.requests[0].requestBody); - expect(request.url).to.be.eq(testUrl); - expect(requestBody['pubId']).to.eq(conf.params.pubId); - expect(requestBody['url']).to.eq(expectedUrlParam); - request.respond(200, responseHeader, JSON.stringify(data)); - expect(deepAccess(config.getConfig(), 'appnexusAuctionKeywords.captify_segments')).to.eql(data['xandr']); - expect(callbackSpy.calledOnce).to.be.true; - }); - - it('do not set anything, in case server responded with 202', function () { - config.resetConfig(); - const callbackSpy = sinon.spy(); - setCaptifyTargeting(reqBidsConfigObj, callbackSpy, moduleConfig, {}); - let request = server.requests[0]; - let requestBody = JSON.parse(server.requests[0].requestBody); - expect(request.url).to.be.eq(defaultRequestUrl); - expect(requestBody['pubId']).to.eq(moduleConfig.params.pubId); - expect(requestBody['url']).to.eq(expectedUrlParam); - request.respond(202, responseHeader, ''); - expect(deepAccess(config.getConfig(), 'appnexusAuctionKeywords.captify_segments')).to.be.undefined; - expect(callbackSpy.calledOnce).to.be.true; - }); - - it('do not set anything, in case server responded with error', function () { - config.resetConfig(); - const callbackSpy = sinon.spy(); - setCaptifyTargeting(reqBidsConfigObj, callbackSpy, moduleConfig, {}); - let request = server.requests[0]; - expect(request.url).to.be.eq(defaultRequestUrl); - request.respond(500, null, ''); - expect(deepAccess(config.getConfig(), 'appnexusAuctionKeywords.captify_segments')).to.be.undefined; - expect(callbackSpy.calledOnce).to.be.true; - }); - - it('do not set anything, in case request error', function () { - config.resetConfig(); - const callbackSpy = sinon.spy(); - setCaptifyTargeting(reqBidsConfigObj, callbackSpy, moduleConfig, {}); - let request = server.requests[0]; - expect(request.url).to.be.eq(defaultRequestUrl); - request.abort('test error'); - expect(deepAccess(config.getConfig(), 'appnexusAuctionKeywords.captify_segments')).to.be.undefined; - expect(callbackSpy.calledOnce).to.be.true; - }); - }); -}); diff --git a/test/spec/modules/ccxBidAdapter_spec.js b/test/spec/modules/ccxBidAdapter_spec.js index d346a14d38a..cbae441e7e7 100644 --- a/test/spec/modules/ccxBidAdapter_spec.js +++ b/test/spec/modules/ccxBidAdapter_spec.js @@ -81,7 +81,7 @@ describe('ccxAdapter', function () { }); it('Valid bid request - default', function () { - let response = spec.buildRequests(bids, {bids}); + let response = spec.buildRequests(bids, {bids, bidderRequestId: 'id'}); expect(response).to.be.not.empty; expect(response.data).to.be.not.empty; diff --git a/test/spec/modules/chtnwBidAdapter_spec.js b/test/spec/modules/chtnwBidAdapter_spec.js index 3875de6c4fa..0fdd1a3f19b 100644 --- a/test/spec/modules/chtnwBidAdapter_spec.js +++ b/test/spec/modules/chtnwBidAdapter_spec.js @@ -39,7 +39,10 @@ describe('ChtnwAdapter', function () { ], }]; - const request = spec.buildRequests(bidRequests); + let request; + before(() => { + request = spec.buildRequests(bidRequests); + }) it('Returns POST method', function () { expect(request.method).to.equal('POST'); diff --git a/test/spec/modules/cleanioRtdProvider_spec.js b/test/spec/modules/cleanioRtdProvider_spec.js index 8453b633906..1d21fbd8457 100644 --- a/test/spec/modules/cleanioRtdProvider_spec.js +++ b/test/spec/modules/cleanioRtdProvider_spec.js @@ -1,3 +1,4 @@ +import { loadExternalScriptStub } from 'test/mocks/adloaderStub.js'; import * as utils from '../../../src/utils.js'; import * as hook from '../../../src/hook.js' import * as events from '../../../src/events.js'; @@ -68,10 +69,8 @@ describe('clean.io RTD module', function () { it('pageInitStepProtectPage() should insert script element', function() { pageInitStepProtectPage(fakeScriptURL); - sinon.assert.calledOnce(insertElementStub); - sinon.assert.calledWith(insertElementStub, sinon.match(elem => elem.tagName === 'SCRIPT')); - sinon.assert.calledWith(insertElementStub, sinon.match(elem => elem.type === 'text/javascript')); - sinon.assert.calledWith(insertElementStub, sinon.match(elem => elem.src === fakeScriptURL)); + sinon.assert.calledOnce(loadExternalScriptStub); + sinon.assert.calledWith(loadExternalScriptStub, fakeScriptURL, 'clean.io'); }); }); @@ -133,14 +132,14 @@ describe('clean.io RTD module', function () { it('should refuse initialization with incorrect parameters', function () { const { init } = getModule(); expect(init({ params: { cdnUrl: 'abc', protectionMode: 'full' } }, {})).to.equal(false); // too short distribution name - sinon.assert.notCalled(insertElementStub); + sinon.assert.notCalled(loadExternalScriptStub); }); - it('should iniitalize in full (page) protection mode', function () { + it('should initialize in full (page) protection mode', function () { const { init, onBidResponseEvent } = getModule(); expect(init({ params: { cdnUrl: 'https://abc1234567890.cloudfront.net/script.js', protectionMode: 'full' } }, {})).to.equal(true); - sinon.assert.calledOnce(insertElementStub); - sinon.assert.calledWith(insertElementStub, sinon.match(elem => elem.tagName === 'SCRIPT')); + sinon.assert.calledOnce(loadExternalScriptStub); + sinon.assert.calledWith(loadExternalScriptStub, 'https://abc1234567890.cloudfront.net/script.js', 'clean.io'); const fakeBidResponse = makeFakeBidResponse(); onBidResponseEvent(fakeBidResponse, {}, {}); diff --git a/test/spec/modules/cleanmedianetBidAdapter_spec.js b/test/spec/modules/cleanmedianetBidAdapter_spec.js index 8c2ac34350b..3c73dac07de 100644 --- a/test/spec/modules/cleanmedianetBidAdapter_spec.js +++ b/test/spec/modules/cleanmedianetBidAdapter_spec.js @@ -2,96 +2,295 @@ import {expect} from 'chai'; import {spec, helper} from 'modules/cleanmedianetBidAdapter.js'; import * as utils from 'src/utils.js'; import {newBidder} from '../../../src/adapters/bidderFactory.js'; +import {deepClone} from 'src/utils'; const supplyPartnerId = '123'; const adapter = newBidder(spec); -describe('CleanmedianetAdapter', function () { - describe('Is String start with search ', function () { - it('check if a string started with', function () { - expect(helper.startsWith('cleanmediaads.com', 'cleanmediaads')).to.equal( - true - ); +const TTL = 360; + +describe('CleanmedianetAdapter', () => { + let schainConfig, + bidRequest, + bannerBidRequest, + videoBidRequest, + rtbResponse, + videoResponse, + gdprConsent; + + beforeEach(() => { + schainConfig = { + 'ver': '1.0', + 'complete': 1, + 'nodes': [ + { + 'asi': 'indirectseller.com', + 'sid': '00001', + 'hp': 1 + }, + + { + 'asi': 'indirectseller-2.com', + 'sid': '00002', + 'hp': 2 + } + ] + }; + + bidRequest = { + 'adUnitCode': 'adunit-code', + 'auctionId': '1d1a030790a475', + 'mediaTypes': { + banner: {} + }, + 'params': { + 'supplyPartnerId': supplyPartnerId + }, + 'sizes': [[300, 250], [300, 600]], + 'transactionId': 'a123456789', + refererInfo: {referer: 'http://examplereferer.com'}, + gdprConsent: { + consentString: 'some string', + gdprApplies: true + }, + schain: schainConfig, + uspConsent: 'cleanmediaCCPA' + }; + + bannerBidRequest = { + 'adUnitCode': 'adunit-code', + 'auctionId': '1d1a030790a475', + 'mediaTypes': { + banner: {} + }, + 'params': { + 'supplyPartnerId': supplyPartnerId + }, + 'sizes': [[300, 250], [300, 600]], + 'transactionId': 'a123456789', + 'bidId': '111', + refererInfo: {referer: 'http://examplereferer.com'} + }; + + videoBidRequest = { + 'adUnitCode': 'adunit-code', + 'auctionId': '1d1a030790a475', + 'mediaTypes': { + video: {} + }, + 'params': { + 'supplyPartnerId': supplyPartnerId + }, + 'sizes': [[300, 250], [300, 600]], + 'transactionId': 'a123456789', + 'bidId': '111', + refererInfo: {referer: 'http://examplereferer.com'} + }; + + rtbResponse = { + 'id': 'imp_5b05b9fde4b09084267a556f', + 'bidid': 'imp_5b05b9fde4b09084267a556f', + 'cur': 'USD', + 'ext': { + 'utrk': [ + {'type': 'iframe', 'url': '//bidder.cleanmediaads.com/user/sync/1?gdpr=[GDPR]&consent=[CONSENT]&usp=[US_PRIVACY]'}, + {'type': 'image', 'url': '//bidder.cleanmediaads.com/user/sync/2'} + ] + }, + 'seatbid': [ + { + 'seat': 'seat1', + 'group': 0, + 'bid': [ + { + 'id': '0', + 'impid': '1', + 'price': 2.016, + 'adid': '579ef31bfa788b9d2000d562', + 'nurl': 'https://bidder.cleanmediaads.com/pix/monitoring/win_notice/imp_5b05b9fde4b09084267a556f/im.gif?r=imp_5b05b9fde4b09084267a556f&i=1&a=579ef31bfa788b9d2000d562&b=0', + 'adm': '', + 'adomain': ['aaa.com'], + 'cid': '579ef268fa788b9d2000d55c', + 'crid': '579ef31bfa788b9d2000d562', + 'attr': [], + 'h': 600, + 'w': 120, + 'ext': { + 'vast_url': 'http://my.vast.com', + 'utrk': [ + {'type': 'iframe', 'url': '//p.partner1.io/user/sync/1'} + ] + } + } + ] + }, + { + 'seat': 'seat2', + 'group': 0, + 'bid': [ + { + 'id': '1', + 'impid': '1', + 'price': 3, + 'adid': '542jlhdfd2112jnjf3x', + 'nurl': 'https://bidder.cleanmediaads.com/pix/monitoring/win_notice/imp_5b05b9fde4b09084267a556f/im.gif?r=imp_5b05b9fde4b09084267a556f&i=1&a=579ef31bfa788b9d2000d562&b=0', + 'adm': ' ', + 'adomain': ['bbb.com'], + 'cid': 'fgdlwjh2498ydjhg1', + 'crid': 'kjh34297ydh2133d', + 'attr': [], + 'h': 250, + 'w': 300, + 'ext': { + 'utrk': [ + {'type': 'image', 'url': '//p.partner2.io/user/sync/1'} + ] + } + } + ] + } + ] + }; + + videoResponse = { + 'id': '64f32497-b2f7-48ec-9205-35fc39894d44', + 'bidid': 'imp_5c24924de4b0d106447af333', + 'cur': 'USD', + 'seatbid': [ + { + 'seat': '3668', + 'group': 0, + 'bid': [ + { + 'id': 'gb_1', + 'impid': 'afbb5852-7cea-4a81-aa9a-a41aab505c23', + 'price': 5.0, + 'adid': '1274', + 'nurl': 'https://bidder.cleanmediaads.com/pix/1275/win_notice/imp_5c24924de4b0d106447af333/im.gif?r=imp_5c24924de4b0d106447af333&i=afbb5852-7cea-4a81-aa9a-a41aab505c23&a=1274&b=gb_1', + 'adomain': [], + 'adm': '\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n', + 'cid': '3668', + 'crid': '1274', + 'cat': [], + 'attr': [], + 'h': 250, + 'w': 300, + 'ext': { + 'vast_url': 'https://bidder.cleanmediaads.com/pix/1275/vast_o/imp_5c24924de4b0d106447af333/im.xml?r=imp_5c24924de4b0d106447af333&i=afbb5852-7cea-4a81-aa9a-a41aab505c23&a=1274&b=gb_1&w=300&h=250&vatu=aHR0cHM6Ly9zdGF0aWMuZ2FtYmlkLmlvL2RlbW8vdmFzdC54bWw&vwarv', + 'imptrackers': [ + 'https://bidder.cleanmediaads.com/pix/1275/imp/imp_5c24924de4b0d106447af333/im.gif?r=imp_5c24924de4b0d106447af333&i=afbb5852-7cea-4a81-aa9a-a41aab505c23&a=1274&b=gb_1'] + } + } + ] + } + ], + 'ext': { + 'utrk': [{ + 'type': 'image', + 'url': 'https://bidder.cleanmediaads.com/pix/1275/scm?cb=1545900621675&gdpr=[GDPR]&consent=[CONSENT]&us_privacy=[US_PRIVACY]' + }] + } + }; + + gdprConsent = { + gdprApplies: true, + consentString: 'consent string' + }; + }); + + describe('Get top Frame', () => { + it('check if you are in the top frame', () => { + expect(helper.getTopFrame()).to.equal(0); + }); + }); + + describe('Is String start with search', () => { + it('check if a string started with', () => { + expect(helper.startsWith('cleanmedia.net', 'clea')).to.equal(true); }); }); - describe('inherited functions', function() { - it('exists and is a function', function() { + describe('inherited functions', () => { + it('exists and is a function', () => { expect(adapter.callBids).to.exist.and.to.be.a('function'); }); }); - describe('isBidRequestValid', function() { - it('should validate supply-partner ID', function() { - expect(spec.isBidRequestValid({ params: {} })).to.equal(false); - expect( - spec.isBidRequestValid({ params: { supplyPartnerId: 123 } }) - ).to.equal(false); - expect( - spec.isBidRequestValid({ params: { supplyPartnerId: '123' } }) - ).to.equal(true); + describe('isBidRequestValid', () => { + it('should validate supply-partner ID', () => { + expect(spec.isBidRequestValid({params: {}})).to.equal(false); + expect(spec.isBidRequestValid({params: {supplyPartnerId: 123}})).to.equal(false); + expect(spec.isBidRequestValid({params: {supplyPartnerId: '123'}})).to.equal(true); + }); + + it('should validate RTB endpoint', () => { + expect(spec.isBidRequestValid({params: {supplyPartnerId: '123'}})).to.equal(true); // RTB endpoint has a default + expect(spec.isBidRequestValid({params: {supplyPartnerId: '123', rtbEndpoint: 123}})).to.equal(false); + expect(spec.isBidRequestValid({ + params: { + supplyPartnerId: '123', + rtbEndpoint: 'https://some.url.com' + } + })).to.equal(true); + }); + + it('should validate bid floor', () => { + expect(spec.isBidRequestValid({params: {supplyPartnerId: '123'}})).to.equal(true); // bidfloor has a default + expect(spec.isBidRequestValid({params: {supplyPartnerId: '123', bidfloor: '123'}})).to.equal(false); + expect(spec.isBidRequestValid({params: {supplyPartnerId: '123', bidfloor: 0.1}})).to.equal(true); + + const getFloorResponse = {currency: 'USD', floor: 5}; + let testBidRequest = deepClone(bidRequest); + let request = spec.buildRequests([testBidRequest], bidRequest)[0]; + + // 1. getBidFloor not exist AND bidfloor not exist - return 0 + let payload = request.data; + expect(payload.imp[0].bidfloor).to.exist.and.equal(0); + + // 2. getBidFloor not exist AND bidfloor exist - use bidfloor property + testBidRequest = deepClone(bidRequest); + testBidRequest.params = { + 'bidfloor': 0.3 + }; + request = spec.buildRequests([testBidRequest], bidRequest)[0]; + payload = request.data; + expect(payload.imp[0].bidfloor).to.exist.and.to.equal(0.3) + + // 3. getBidFloor exist AND bidfloor not exist - use getFloor method + testBidRequest = deepClone(bidRequest); + testBidRequest.getFloor = () => getFloorResponse; + request = spec.buildRequests([testBidRequest], bidRequest)[0]; + payload = request.data; + expect(payload.imp[0].bidfloor).to.exist.and.to.equal(5) + + // 4. getBidFloor exist AND bidfloor exist -> use getFloor method + testBidRequest = deepClone(bidRequest); + testBidRequest.getFloor = () => getFloorResponse; + testBidRequest.params = { + 'bidfloor': 0.3 + }; + request = spec.buildRequests([testBidRequest], bidRequest)[0]; + payload = request.data; + expect(payload.imp[0].bidfloor).to.exist.and.to.equal(5) }); - it('should validate adpos', function() { - expect( - spec.isBidRequestValid({ params: { supplyPartnerId: '123' } }) - ).to.equal(true); // adpos has a default - expect( - spec.isBidRequestValid({ - params: { supplyPartnerId: '123', adpos: '123' } - }) - ).to.equal(false); - expect( - spec.isBidRequestValid({ - params: { supplyPartnerId: '123', adpos: 0.1 } - }) - ).to.equal(true); + it('should validate adpos', () => { + expect(spec.isBidRequestValid({params: {supplyPartnerId: '123'}})).to.equal(true); // adpos has a default + expect(spec.isBidRequestValid({params: {supplyPartnerId: '123', adpos: '123'}})).to.equal(false); + expect(spec.isBidRequestValid({params: {supplyPartnerId: '123', adpos: 0.1}})).to.equal(true); }); - it('should validate instl', function() { - expect( - spec.isBidRequestValid({ params: { supplyPartnerId: '123' } }) - ).to.equal(true); // adpos has a default - expect( - spec.isBidRequestValid({ - params: { supplyPartnerId: '123', instl: '123' } - }) - ).to.equal(false); - expect( - spec.isBidRequestValid({ - params: { supplyPartnerId: '123', instl: -1 } - }) - ).to.equal(false); - expect( - spec.isBidRequestValid({ params: { supplyPartnerId: '123', instl: 0 } }) - ).to.equal(true); - expect( - spec.isBidRequestValid({ params: { supplyPartnerId: '123', instl: 1 } }) - ).to.equal(true); - expect( - spec.isBidRequestValid({ params: { supplyPartnerId: '123', instl: 2 } }) - ).to.equal(false); + it('should validate instl', () => { + expect(spec.isBidRequestValid({params: {supplyPartnerId: '123'}})).to.equal(true); // adpos has a default + expect(spec.isBidRequestValid({params: {supplyPartnerId: '123', instl: '123'}})).to.equal(false); + expect(spec.isBidRequestValid({params: {supplyPartnerId: '123', instl: -1}})).to.equal(false); + expect(spec.isBidRequestValid({params: {supplyPartnerId: '123', instl: 0}})).to.equal(true); + expect(spec.isBidRequestValid({params: {supplyPartnerId: '123', instl: 1}})).to.equal(true); + expect(spec.isBidRequestValid({params: {supplyPartnerId: '123', instl: 2}})).to.equal(false); }); }); - describe('buildRequests', function() { - const bidRequest = { - adUnitCode: 'adunit-code', - auctionId: '1d1a030790a475', - mediaTypes: { - banner: {} - }, - params: { - supplyPartnerId: supplyPartnerId - }, - sizes: [[300, 250], [300, 600]], - transactionId: 'a123456789', - refererInfo: { referer: 'https://examplereferer.com', domain: 'examplereferer.com' }, - gdprConsent: { - consentString: 'some string', - gdprApplies: true - } - }; - it('returns an array', function() { + describe('buildRequests', () => { + it('returns an array', () => { let response; response = spec.buildRequests([]); expect(Array.isArray(response)).to.equal(true); @@ -99,58 +298,61 @@ describe('CleanmedianetAdapter', function () { response = spec.buildRequests([bidRequest], bidRequest); expect(Array.isArray(response)).to.equal(true); expect(response.length).to.equal(1); - const adUnit1 = Object.assign({}, utils.deepClone(bidRequest), { - auctionId: '1', - adUnitCode: 'a' - }); - const adUnit2 = Object.assign({}, utils.deepClone(bidRequest), { - auctionId: '1', - adUnitCode: 'b' - }); + const adUnit1 = Object.assign({}, utils.deepClone(bidRequest), {auctionId: '1', adUnitCode: 'a'}); + const adUnit2 = Object.assign({}, utils.deepClone(bidRequest), {auctionId: '1', adUnitCode: 'b'}); response = spec.buildRequests([adUnit1, adUnit2], bidRequest); expect(Array.isArray(response)).to.equal(true); expect(response.length).to.equal(2); }); - it('builds request correctly', function() { + it('targets correct endpoint', () => { + let response; + response = spec.buildRequests([bidRequest], bidRequest)[0]; + expect(response.method).to.equal('POST'); + expect(response.url).to.match(new RegExp(`^https://bidder\\.cleanmediaads\\.com/r/${supplyPartnerId}/bidr\\?rformat=open_rtb&reqformat=rtb_json&bidder=prebid$`, 'g')); + expect(response.data.id).to.equal(bidRequest.bidId); + const bidRequestWithEndpoint = utils.deepClone(bidRequest); + bidRequestWithEndpoint.params.rtbEndpoint = 'https://bidder.cleanmediaads.com/a12'; + response = spec.buildRequests([bidRequestWithEndpoint], bidRequest)[0]; + expect(response.url).to.match(new RegExp(`^https://bidder\\.cleanmediaads\\.com/a12/r/${supplyPartnerId}/bidr\\?rformat=open_rtb&reqformat=rtb_json&bidder=prebid$`, 'g')); + }); + + it('builds request correctly', () => { let bidRequest2 = utils.deepClone(bidRequest); Object.assign(bidRequest2.refererInfo, { - page: 'https://www.test.com/page.html', - domain: 'test.com', - ref: 'https://referer.com' + page: 'http://www.test.com/page.html', + domain: 'www.test.com', + ref: 'http://referrer.com' }) - let response = spec.buildRequests([bidRequest], bidRequest2)[0]; - expect(response.data.site.domain).to.equal('test.com'); - expect(response.data.site.page).to.equal('https://www.test.com/page.html'); - expect(response.data.site.ref).to.equal('https://referer.com'); + + expect(response.data.site.domain).to.equal('www.test.com'); + expect(response.data.site.page).to.equal('http://www.test.com/page.html'); + expect(response.data.site.ref).to.equal('http://referrer.com'); expect(response.data.imp.length).to.equal(1); - expect(response.data.imp[0].id).to.equal(bidRequest.transactionId); + expect(response.data.imp[0].id).to.equal(bidRequest.bidId); expect(response.data.imp[0].instl).to.equal(0); expect(response.data.imp[0].tagid).to.equal(bidRequest.adUnitCode); expect(response.data.imp[0].bidfloor).to.equal(0); expect(response.data.imp[0].bidfloorcur).to.equal('USD'); + expect(response.data.regs.ext.us_privacy).to.equal('cleanmediaCCPA');// USP/CCPAs + expect(response.data.source.ext.schain).to.deep.equal(bidRequest2.schain); + const bidRequestWithInstlEquals1 = utils.deepClone(bidRequest); bidRequestWithInstlEquals1.params.instl = 1; - response = spec.buildRequests( - [bidRequestWithInstlEquals1], - bidRequest2 - )[0]; - expect(response.data.imp[0].instl).to.equal( - bidRequestWithInstlEquals1.params.instl - ); + response = spec.buildRequests([bidRequestWithInstlEquals1], bidRequest2)[0]; + expect(response.data.imp[0].instl).to.equal(bidRequestWithInstlEquals1.params.instl); const bidRequestWithInstlEquals0 = utils.deepClone(bidRequest); bidRequestWithInstlEquals0.params.instl = 1; - response = spec.buildRequests( - [bidRequestWithInstlEquals0], - bidRequest2 - )[0]; - expect(response.data.imp[0].instl).to.equal( - bidRequestWithInstlEquals0.params.instl - ); + response = spec.buildRequests([bidRequestWithInstlEquals0], bidRequest2)[0]; + expect(response.data.imp[0].instl).to.equal(bidRequestWithInstlEquals0.params.instl); + const bidRequestWithBidfloorEquals1 = utils.deepClone(bidRequest); + bidRequestWithBidfloorEquals1.params.bidfloor = 1; + response = spec.buildRequests([bidRequestWithBidfloorEquals1], bidRequest2)[0]; + expect(response.data.imp[0].bidfloor).to.equal(bidRequestWithBidfloorEquals1.params.bidfloor); }); - it('builds request banner object correctly', function() { + it('builds request banner object correctly', () => { let response; const bidRequestWithBanner = utils.deepClone(bidRequest); bidRequestWithBanner.mediaTypes = { @@ -159,54 +361,80 @@ describe('CleanmedianetAdapter', function () { } }; response = spec.buildRequests([bidRequestWithBanner], bidRequest)[0]; - expect(response.data.imp[0].banner.w).to.equal( - bidRequestWithBanner.mediaTypes.banner.sizes[0][0] - ); - expect(response.data.imp[0].banner.h).to.equal( - bidRequestWithBanner.mediaTypes.banner.sizes[0][1] - ); + expect(response.data.imp[0].banner.w).to.equal(bidRequestWithBanner.mediaTypes.banner.sizes[0][0]); + expect(response.data.imp[0].banner.h).to.equal(bidRequestWithBanner.mediaTypes.banner.sizes[0][1]); expect(response.data.imp[0].banner.pos).to.equal(0); + expect(response.data.imp[0].banner.topframe).to.equal(0); const bidRequestWithPosEquals1 = utils.deepClone(bidRequestWithBanner); bidRequestWithPosEquals1.params.pos = 1; response = spec.buildRequests([bidRequestWithPosEquals1], bidRequest)[0]; - expect(response.data.imp[0].banner.pos).to.equal( - bidRequestWithPosEquals1.params.pos - ); + expect(response.data.imp[0].banner.pos).to.equal(bidRequestWithPosEquals1.params.pos); }); - it('builds request video object correctly', function() { + it('builds request video object correctly', () => { let response; const bidRequestWithVideo = utils.deepClone(bidRequest); + + bidRequestWithVideo.params.video = { + placement: 1, + minduration: 1, + } + bidRequestWithVideo.mediaTypes = { video: { - sizes: [[300, 250], [120, 600]] + playerSize: [[302, 252]], + mimes: ['video/mpeg'], + playbackmethod: 1, + startdelay: 1, } }; response = spec.buildRequests([bidRequestWithVideo], bidRequest)[0]; - expect(response.data.imp[0].video.w).to.equal( - bidRequestWithVideo.mediaTypes.video.sizes[0][0] - ); - expect(response.data.imp[0].video.h).to.equal( - bidRequestWithVideo.mediaTypes.video.sizes[0][1] - ); + expect(response.data.imp[0].video.w).to.equal(bidRequestWithVideo.mediaTypes.video.playerSize[0][0]); + expect(response.data.imp[0].video.h).to.equal(bidRequestWithVideo.mediaTypes.video.playerSize[0][1]); expect(response.data.imp[0].video.pos).to.equal(0); + + expect(response.data.imp[0].video.mimes).to.equal(bidRequestWithVideo.mediaTypes.video.mimes); + expect(response.data.imp[0].video.skip).to.not.exist; + expect(response.data.imp[0].video.placement).to.equal(1); + expect(response.data.imp[0].video.minduration).to.equal(1); + expect(response.data.imp[0].video.playbackmethod).to.equal(1); + expect(response.data.imp[0].video.startdelay).to.equal(1); + + bidRequestWithVideo.mediaTypes = { + video: { + playerSize: [302, 252], + mimes: ['video/mpeg'], + skip: 1, + placement: 1, + minduration: 1, + playbackmethod: 1, + startdelay: 1, + }, + }; + const bidRequestWithPosEquals1 = utils.deepClone(bidRequestWithVideo); + expect(response.data.imp[0].video.w).to.equal(bidRequestWithVideo.mediaTypes.video.playerSize[0]); + expect(response.data.imp[0].video.h).to.equal(bidRequestWithVideo.mediaTypes.video.playerSize[1]); + bidRequestWithPosEquals1.params.pos = 1; response = spec.buildRequests([bidRequestWithPosEquals1], bidRequest)[0]; - expect(response.data.imp[0].video.pos).to.equal( - bidRequestWithPosEquals1.params.pos - ); + expect(response.data.imp[0].video.pos).to.equal(bidRequestWithPosEquals1.params.pos); }); - it('builds request video object correctly with context', function() { - let response; + it('builds request video object correctly with context', () => { const bidRequestWithVideo = utils.deepClone(bidRequest); bidRequestWithVideo.mediaTypes = { video: { - context: 'instream' + context: 'instream', + mimes: ['video/mpeg'], + skip: 1, + placement: 1, + minduration: 1, + playbackmethod: 1, + startdelay: 1, } }; - response = spec.buildRequests([bidRequestWithVideo], bidRequest)[0]; + let response = spec.buildRequests([bidRequestWithVideo], bidRequest)[0]; expect(response.data.imp[0].video.ext.context).to.equal('instream'); bidRequestWithVideo.mediaTypes.video.context = 'outstream'; @@ -220,358 +448,185 @@ describe('CleanmedianetAdapter', function () { response = spec.buildRequests([bidRequestWithPosEquals2], bidRequest)[0]; expect(response.data.imp[0].video.ext.context).to.equal(null); }); - it('builds request video object correctly with multi-dimensions size array', function () { - let bidRequestWithVideo = utils.deepClone(bidRequest); + + it('builds request video object correctly with multi-dimensions size array', () => { + let response; + const bidRequestWithVideo = utils.deepClone(bidRequest); bidRequestWithVideo.mediaTypes.video = { playerSize: [[304, 254], [305, 255]], - context: 'instream' + context: 'instream', + mimes: ['video/mpeg'], + skip: 1, + placement: 1, + minduration: 1, + playbackmethod: 1, + startdelay: 1, }; - let response = spec.buildRequests([bidRequestWithVideo], bidRequest)[0]; - expect(response.data.imp[1].video.w).to.equal(304); - expect(response.data.imp[1].video.h).to.equal(254); + response = spec.buildRequests([bidRequestWithVideo], bidRequest)[0]; + expect(response.data.imp[1].video.ext.context).to.equal('instream'); + bidRequestWithVideo.mediaTypes.video.context = 'outstream'; - bidRequestWithVideo = utils.deepClone(bidRequest); - bidRequestWithVideo.mediaTypes.video = { - playerSize: [304, 254] - }; + const bidRequestWithPosEquals1 = utils.deepClone(bidRequestWithVideo); + bidRequestWithPosEquals1.mediaTypes.video.context = 'outstream'; + response = spec.buildRequests([bidRequestWithPosEquals1], bidRequest)[0]; + expect(response.data.imp[1].video.ext.context).to.equal('outstream'); - response = spec.buildRequests([bidRequestWithVideo], bidRequest)[0]; - expect(response.data.imp[1].video.w).to.equal(304); - expect(response.data.imp[1].video.h).to.equal(254); + const bidRequestWithPosEquals2 = utils.deepClone(bidRequestWithVideo); + bidRequestWithPosEquals2.mediaTypes.video.context = null; + response = spec.buildRequests([bidRequestWithPosEquals2], bidRequest)[0]; + expect(response.data.imp[1].video.ext.context).to.equal(null); }); - it('builds request with gdpr consent', function() { + it('builds request with gdpr consent', () => { let response = spec.buildRequests([bidRequest], bidRequest)[0]; + + expect(response.data.ext.gdpr_consent).to.not.equal(null).and.not.equal(undefined); expect(response.data.ext).to.have.property('gdpr_consent'); - expect(response.data.ext.gdpr_consent.consent_string).to.equal( - 'some string' - ); + expect(response.data.ext.gdpr_consent.consent_string).to.equal('some string'); expect(response.data.ext.gdpr_consent.consent_required).to.equal(true); - }); - }); - - describe('interpretResponse', function() { - const bannerBidRequest = { - adUnitCode: 'adunit-code', - auctionId: '1d1a030790a475', - mediaTypes: { - banner: {} - }, - params: { - supplyPartnerId: supplyPartnerId - }, - sizes: [[300, 250], [300, 600]], - transactionId: 'a123456789', - bidId: '111', - refererInfo: { referer: 'https://examplereferer.com' } - }; - const videoBidRequest = { - adUnitCode: 'adunit-code', - auctionId: '1d1a030790a475', - mediaTypes: { - video: {} - }, - params: { - supplyPartnerId: supplyPartnerId - }, - sizes: [[300, 250], [300, 600]], - transactionId: 'a123456789', - bidId: '111', - refererInfo: { referer: 'https://examplereferer.com' } - }; + expect(response.data.regs.ext.gdpr).to.not.equal(null).and.not.equal(undefined); + expect(response.data.user.ext.consent).to.equal('some string'); + }); - const rtbResponse = { - id: 'imp_5b05b9fde4b09084267a556f', - bidid: 'imp_5b05b9fde4b09084267a556f', - cur: 'USD', - ext: { - utrk: [ - { type: 'iframe', url: '//bidder.cleanmediaads.com/user/sync/1' }, - { type: 'image', url: '//bidder.cleanmediaads.com/user/sync/2' } - ] - }, - seatbid: [ - { - seat: 'seat1', - group: 0, - bid: [ - { - id: '0', - impid: '1', - price: 2.016, - adid: '579ef31bfa788b9d2000d562', - nurl: - 'https://bidder.cleanmediaads.com/pix/monitoring/win_notice/imp_5b05b9fde4b09084267a556f/im.gif?r=imp_5b05b9fde4b09084267a556f&i=1&a=579ef31bfa788b9d2000d562&b=0', - adm: - '', - adomain: ['aaa.com'], - cid: '579ef268fa788b9d2000d55c', - crid: '579ef31bfa788b9d2000d562', - attr: [], - h: 600, - w: 120, - ext: { - vast_url: 'https://my.vast.com', - utrk: [{ type: 'iframe', url: '//p.partner1.io/user/sync/1' }] - } - } - ] - }, - { - seat: 'seat2', - group: 0, - bid: [ - { - id: '1', - impid: '1', - price: 3, - adid: '542jlhdfd2112jnjf3x', - nurl: - 'https://bidder.cleanmediaads.com/pix/monitoring/win_notice/imp_5b05b9fde4b09084267a556f/im.gif?r=imp_5b05b9fde4b09084267a556f&i=1&a=579ef31bfa788b9d2000d562&b=0', - adm: - ' ', - adomain: ['bbb.com'], - cid: 'fgdlwjh2498ydjhg1', - crid: 'kjh34297ydh2133d', - attr: [], - h: 250, - w: 300, - ext: { - utrk: [{ type: 'image', url: '//p.partner2.io/user/sync/1' }] - } - } - ] - } - ] - }; + it('build request with ID5 Id', () => { + const bidRequestClone = utils.deepClone(bidRequest); + bidRequestClone.userId = {}; + bidRequestClone.userId.id5id = { uid: 'id5-user-id' }; + let request = spec.buildRequests([bidRequestClone], bidRequestClone)[0]; + expect(request.data.user.ext.eids).to.deep.equal([{ + 'source': 'id5-sync.com', + 'uids': [{ + 'id': 'id5-user-id', + 'ext': { + 'rtiPartner': 'ID5ID' + } + }] + }]); + }); - it('returns an empty array on missing response', function() { - let response; + it('build request with unified Id', () => { + const bidRequestClone = utils.deepClone(bidRequest); + bidRequestClone.userId = {}; + bidRequestClone.userId.tdid = 'tdid-user-id'; + let request = spec.buildRequests([bidRequestClone], bidRequestClone)[0]; + expect(request.data.user.ext.eids).to.deep.equal([{ + 'source': 'adserver.org', + 'uids': [{ + 'id': 'tdid-user-id', + 'ext': { + 'rtiPartner': 'TDID' + } + }] + }]); + }); + }); - response = spec.interpretResponse(undefined, { - bidRequest: bannerBidRequest - }); + describe('interpretResponse', () => { + it('returns an empty array on missing response', () => { + let response = spec.interpretResponse(undefined, {bidRequest: bannerBidRequest}); expect(Array.isArray(response)).to.equal(true); expect(response.length).to.equal(0); - response = spec.interpretResponse({}, { bidRequest: bannerBidRequest }); + response = spec.interpretResponse({}, {bidRequest: bannerBidRequest}); expect(Array.isArray(response)).to.equal(true); expect(response.length).to.equal(0); }); - it('aggregates banner bids from all seat bids', function() { - const response = spec.interpretResponse( - { body: rtbResponse }, - { bidRequest: bannerBidRequest } - ); + it('aggregates banner bids from all seat bids', () => { + const response = spec.interpretResponse({body: rtbResponse}, {bidRequest: bannerBidRequest}); expect(Array.isArray(response)).to.equal(true); expect(response.length).to.equal(1); - const ad0 = response[0]; expect(ad0.requestId).to.equal(bannerBidRequest.bidId); expect(ad0.cpm).to.equal(rtbResponse.seatbid[1].bid[0].price); expect(ad0.width).to.equal(rtbResponse.seatbid[1].bid[0].w); expect(ad0.height).to.equal(rtbResponse.seatbid[1].bid[0].h); - expect(ad0.ttl).to.equal(360); + expect(ad0.ttl).to.equal(TTL); expect(ad0.creativeId).to.equal(rtbResponse.seatbid[1].bid[0].crid); expect(ad0.netRevenue).to.equal(true); - expect(ad0.currency).to.equal( - rtbResponse.seatbid[1].bid[0].cur || rtbResponse.cur || 'USD' - ); + expect(ad0.currency).to.equal(rtbResponse.seatbid[1].bid[0].cur || rtbResponse.cur || 'USD'); expect(ad0.ad).to.equal(rtbResponse.seatbid[1].bid[0].adm); expect(ad0.vastXml).to.be.an('undefined'); expect(ad0.vastUrl).to.be.an('undefined'); + expect(ad0.meta.advertiserDomains).to.be.equal(rtbResponse.seatbid[1].bid[0].adomain); }); - it('aggregates video bids from all seat bids', function() { - const response = spec.interpretResponse( - { body: rtbResponse }, - { bidRequest: videoBidRequest } - ); + it('aggregates video bids from all seat bids', () => { + const response = spec.interpretResponse({body: rtbResponse}, {bidRequest: videoBidRequest}); expect(Array.isArray(response)).to.equal(true); expect(response.length).to.equal(1); - const ad0 = response[0]; expect(ad0.requestId).to.equal(videoBidRequest.bidId); expect(ad0.cpm).to.equal(rtbResponse.seatbid[0].bid[0].price); expect(ad0.width).to.equal(rtbResponse.seatbid[0].bid[0].w); expect(ad0.height).to.equal(rtbResponse.seatbid[0].bid[0].h); - expect(ad0.ttl).to.equal(360); + expect(ad0.ttl).to.equal(TTL); expect(ad0.creativeId).to.equal(rtbResponse.seatbid[0].bid[0].crid); expect(ad0.netRevenue).to.equal(true); - expect(ad0.currency).to.equal( - rtbResponse.seatbid[0].bid[0].cur || rtbResponse.cur || 'USD' - ); + expect(ad0.currency).to.equal(rtbResponse.seatbid[0].bid[0].cur || rtbResponse.cur || 'USD'); expect(ad0.ad).to.be.an('undefined'); expect(ad0.vastXml).to.equal(rtbResponse.seatbid[0].bid[0].adm); expect(ad0.vastUrl).to.equal(rtbResponse.seatbid[0].bid[0].ext.vast_url); }); - it('aggregates user-sync pixels', function() { - const response = spec.getUserSyncs({}, [{ body: rtbResponse }]); + it('aggregates user-sync pixels', () => { + const response = spec.getUserSyncs({}, [{body: rtbResponse}]); expect(Array.isArray(response)).to.equal(true); expect(response.length).to.equal(4); expect(response[0].type).to.equal(rtbResponse.ext.utrk[0].type); - expect(response[0].url).to.equal( - rtbResponse.ext.utrk[0].url + '?gc=missing' - ); + expect(response[0].url).to.equal('//bidder.cleanmediaads.com/user/sync/1?gdpr=0&consent=&usp='); expect(response[1].type).to.equal(rtbResponse.ext.utrk[1].type); - expect(response[1].url).to.equal( - rtbResponse.ext.utrk[1].url + '?gc=missing' - ); - expect(response[2].type).to.equal( - rtbResponse.seatbid[0].bid[0].ext.utrk[0].type - ); - expect(response[2].url).to.equal( - rtbResponse.seatbid[0].bid[0].ext.utrk[0].url + '?gc=missing' - ); - expect(response[3].type).to.equal( - rtbResponse.seatbid[1].bid[0].ext.utrk[0].type - ); - expect(response[3].url).to.equal( - rtbResponse.seatbid[1].bid[0].ext.utrk[0].url + '?gc=missing' - ); + expect(response[1].url).to.equal('//bidder.cleanmediaads.com/user/sync/2'); + expect(response[2].type).to.equal(rtbResponse.seatbid[0].bid[0].ext.utrk[0].type); + expect(response[2].url).to.equal('//p.partner1.io/user/sync/1'); + expect(response[3].type).to.equal(rtbResponse.seatbid[1].bid[0].ext.utrk[0].type); + expect(response[3].url).to.equal('//p.partner2.io/user/sync/1'); }); - it('supports configuring outstream renderers', function() { - const videoResponse = { - id: '64f32497-b2f7-48ec-9205-35fc39894d44', - bidid: 'imp_5c24924de4b0d106447af333', - cur: 'USD', - seatbid: [ - { - seat: '3668', - group: 0, - bid: [ - { - id: 'gb_1', - impid: 'afbb5852-7cea-4a81-aa9a-a41aab505c23', - price: 5.0, - adid: '1274', - nurl: - 'https://bidder.cleanmediaads.com/pix/1275/win_notice/imp_5c24924de4b0d106447af333/im.gif?r=imp_5c24924de4b0d106447af333&i=afbb5852-7cea-4a81-aa9a-a41aab505c23&a=1274&b=gb_1', - adomain: [], - adm: - '\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n', - cid: '3668', - crid: '1274', - cat: [], - attr: [], - h: 250, - w: 300, - ext: { - vast_url: - 'https://bidder.cleanmediaads.com/pix/1275/vast_o/imp_5c24924de4b0d106447af333/im.xml?r=imp_5c24924de4b0d106447af333&i=afbb5852-7cea-4a81-aa9a-a41aab505c23&a=1274&b=gb_1&w=300&h=250&vatu=aHR0cHM6Ly9zdGF0aWMuZ2FtYmlkLmlvL2RlbW8vdmFzdC54bWw&vwarv', - imptrackers: [ - 'https://bidder.cleanmediaads.com/pix/1275/imp/imp_5c24924de4b0d106447af333/im.gif?r=imp_5c24924de4b0d106447af333&i=afbb5852-7cea-4a81-aa9a-a41aab505c23&a=1274&b=gb_1' - ] - } - } - ] - } - ], - ext: { - utrk: [ - { - type: 'image', - url: - 'https://bidder.cleanmediaads.com/pix/1275/scm?cb=1545900621675' - } - ] - } - }; + it('supports configuring outstream renderers', () => { const videoRequest = utils.deepClone(videoBidRequest); videoRequest.mediaTypes.video.context = 'outstream'; - const result = spec.interpretResponse( - { body: videoResponse }, - { bidRequest: videoRequest } - ); + const result = spec.interpretResponse({body: videoResponse}, {bidRequest: videoRequest}); expect(result[0].renderer).to.not.equal(undefined); }); - it('validates in/existing of gdpr consent', function() { - let videoResponse = { - id: '64f32497-b2f7-48ec-9205-35fc39894d44', - bidid: 'imp_5c24924de4b0d106447af333', - cur: 'USD', - seatbid: [ - { - seat: '3668', - group: 0, - bid: [ - { - id: 'gb_1', - impid: 'afbb5852-7cea-4a81-aa9a-a41aab505c23', - price: 5.0, - adid: '1274', - nurl: - 'https://bidder.cleanmediaads.com/pix/1275/win_notice/imp_5c24924de4b0d106447af333/im.gif?r=imp_5c24924de4b0d106447af333&i=afbb5852-7cea-4a81-aa9a-a41aab505c23&a=1274&b=gb_1', - adomain: [], - adm: - '\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n', - cid: '3668', - crid: '1274', - cat: [], - attr: [], - h: 250, - w: 300, - ext: { - vast_url: - 'https://bidder.cleanmediaads.com/pix/1275/vast_o/imp_5c24924de4b0d106447af333/im.xml?r=imp_5c24924de4b0d106447af333&i=afbb5852-7cea-4a81-aa9a-a41aab505c23&a=1274&b=gb_1&w=300&h=250&vatu=aHR0cHM6Ly9zdGF0aWMuZ2FtYmlkLmlvL2RlbW8vdmFzdC54bWw&vwarv', - imptrackers: [ - 'https://bidder.cleanmediaads.com/pix/1275/imp/imp_5c24924de4b0d106447af333/im.gif?r=imp_5c24924de4b0d106447af333&i=afbb5852-7cea-4a81-aa9a-a41aab505c23&a=1274&b=gb_1' - ] - } - } - ] - } - ], - ext: { - utrk: [ - { - type: 'image', - url: - 'https://bidder.cleanmediaads.com/pix/1275/scm?cb=1545900621675' - } - ] - } - }; - let gdprConsent = { - gdprApplies: true, - consentString: 'consent string' - }; - let result = spec.getUserSyncs( - {}, - [{ body: videoResponse }], - gdprConsent - ); + it('validates in/existing of gdpr consent', () => { + let result = spec.getUserSyncs({}, [{body: videoResponse}], gdprConsent, 'cleanmediaCCPA'); expect(result).to.be.an('array'); expect(result.length).to.equal(1); expect(result[0].type).to.equal('image'); - expect(result[0].url).to.equal( - 'https://bidder.cleanmediaads.com/pix/1275/scm?cb=1545900621675&gc=consent%20string' - ); + expect(result[0].url).to.equal('https://bidder.cleanmediaads.com/pix/1275/scm?cb=1545900621675&gdpr=1&consent=consent%20string&us_privacy=cleanmediaCCPA'); gdprConsent.gdprApplies = false; - result = spec.getUserSyncs({}, [{ body: videoResponse }], gdprConsent); + result = spec.getUserSyncs({}, [{body: videoResponse}], gdprConsent, 'cleanmediaCCPA'); expect(result).to.be.an('array'); expect(result.length).to.equal(1); expect(result[0].type).to.equal('image'); - expect(result[0].url).to.equal( - 'https://bidder.cleanmediaads.com/pix/1275/scm?cb=1545900621675&gc=missing' - ); + expect(result[0].url).to.equal('https://bidder.cleanmediaads.com/pix/1275/scm?cb=1545900621675&gdpr=0&consent=&us_privacy=cleanmediaCCPA'); - videoResponse.ext.utrk[0].url = - 'https://bidder.cleanmediaads.com/pix/1275/scm'; - result = spec.getUserSyncs({}, [{ body: videoResponse }], gdprConsent); + videoResponse.ext.utrk[0].url = 'https://bidder.cleanmediaads.com/pix/1275/scm'; + result = spec.getUserSyncs({}, [{body: videoResponse}], gdprConsent); + expect(result).to.be.an('array'); + expect(result.length).to.equal(1); + expect(result[0].type).to.equal('image'); + expect(result[0].url).to.equal('https://bidder.cleanmediaads.com/pix/1275/scm'); + }); + + it('validates existence of gdpr, gdpr consent and usp consent', () => { + let result = spec.getUserSyncs({}, [{body: videoResponse}], gdprConsent, 'cleanmediaCCPA'); + expect(result).to.be.an('array'); + expect(result.length).to.equal(1); + expect(result[0].type).to.equal('image'); + expect(result[0].url).to.equal('https://bidder.cleanmediaads.com/pix/1275/scm?cb=1545900621675&gdpr=1&consent=consent%20string&us_privacy=cleanmediaCCPA'); + + gdprConsent.gdprApplies = false; + result = spec.getUserSyncs({}, [{body: videoResponse}], gdprConsent, ''); expect(result).to.be.an('array'); expect(result.length).to.equal(1); expect(result[0].type).to.equal('image'); - expect(result[0].url).to.equal( - 'https://bidder.cleanmediaads.com/pix/1275/scm?gc=missing' - ); + expect(result[0].url).to.equal('https://bidder.cleanmediaads.com/pix/1275/scm?cb=1545900621675&gdpr=0&consent=&us_privacy='); }); }); }); diff --git a/test/spec/modules/codefuelBidAdapter_spec.js b/test/spec/modules/codefuelBidAdapter_spec.js index 354cbe63ffa..6123f768d88 100644 --- a/test/spec/modules/codefuelBidAdapter_spec.js +++ b/test/spec/modules/codefuelBidAdapter_spec.js @@ -1,6 +1,7 @@ import {expect} from 'chai'; import {spec} from 'modules/codefuelBidAdapter.js'; import {config} from 'src/config.js'; +import * as utils from 'src/utils.js'; import {server} from 'test/mocks/xhr'; const USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/92.0.4515.159 Safari/537.36'; @@ -9,6 +10,13 @@ const setUADefault = () => { window.navigator.__defineGetter__('userAgent', func const setUAMock = () => { window.navigator.__defineGetter__('userAgent', function () { return USER_AGENT }) }; describe('Codefuel Adapter', function () { + let sandbox; + beforeEach(() => { + sandbox = sinon.sandbox.create(); + }); + afterEach(() => { + sandbox.restore(); + }) describe('Bid request and response', function () { const commonBidRequest = { bidder: 'codefuel', @@ -137,6 +145,7 @@ describe('Codefuel Adapter', function () { const commonBidderRequest = { timeout: 500, + bidderRequestId: 'mock-uuid', auctionId: '12043683-3254-4f74-8934-f941b085579e', refererInfo: { page: 'https://example.com/', @@ -157,7 +166,7 @@ describe('Codefuel Adapter', function () { devicetype: 2, ua: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/92.0.4515.159 Safari/537.36' }, - id: '12043683-3254-4f74-8934-f941b085579e', + id: 'mock-uuid', imp: [ { banner: { diff --git a/test/spec/modules/cointrafficBidAdapter_spec.js b/test/spec/modules/cointrafficBidAdapter_spec.js index 24570de5083..79775f7b135 100644 --- a/test/spec/modules/cointrafficBidAdapter_spec.js +++ b/test/spec/modules/cointrafficBidAdapter_spec.js @@ -1,13 +1,15 @@ +import sinon from 'sinon' import { expect } from 'chai'; import { spec } from 'modules/cointrafficBidAdapter.js'; import { config } from 'src/config.js' import * as utils from 'src/utils.js' -const ENDPOINT_URL = 'https://apps-pbd.ctengine.io/pb/tmp'; +const ENDPOINT_URL = 'https://apps-pbd.ctraffic.io/pb/tmp'; describe('cointrafficBidAdapter', function () { describe('isBidRequestValid', function () { - let bid = { + /** @type {BidRequest} */ + let bidRequest = { bidder: 'cointraffic', params: { placementId: 'testPlacementId' @@ -22,11 +24,12 @@ describe('cointrafficBidAdapter', function () { }; it('should return true where required params found', function () { - expect(spec.isBidRequestValid(bid)).to.equal(true); + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); }); }); describe('buildRequests', function () { + /** @type {BidRequest[]} */ let bidRequests = [ { bidder: 'cointraffic', @@ -56,7 +59,8 @@ describe('cointrafficBidAdapter', function () { } ]; - let bidderRequests = { + /** @type {BidderRequest} */ + let bidderRequest = { refererInfo: { numIframes: 0, reachedTop: true, @@ -68,7 +72,7 @@ describe('cointrafficBidAdapter', function () { }; it('replaces currency with EUR if there is no currency provided', function () { - const request = spec.buildRequests(bidRequests, bidderRequests); + const request = spec.buildRequests(bidRequests, bidderRequest); expect(request[0].data.currency).to.equal('EUR'); expect(request[1].data.currency).to.equal('EUR'); @@ -79,7 +83,7 @@ describe('cointrafficBidAdapter', function () { arg => arg === 'currency.bidderCurrencyDefault.cointraffic' ? 'USD' : 'EUR' ); - const request = spec.buildRequests(bidRequests, bidderRequests); + const request = spec.buildRequests(bidRequests, bidderRequest); expect(request[0].data.currency).to.equal('USD'); expect(request[1].data.currency).to.equal('USD'); @@ -93,7 +97,7 @@ describe('cointrafficBidAdapter', function () { arg => arg === 'currency.bidderCurrencyDefault.cointraffic' ? 'BTC' : 'EUR' ); - const request = spec.buildRequests(bidRequests, bidderRequests); + const request = spec.buildRequests(bidRequests, bidderRequest); expect(request[0]).to.undefined; expect(request[1]).to.undefined; @@ -103,14 +107,14 @@ describe('cointrafficBidAdapter', function () { }); it('sends bid request to our endpoint via POST', function () { - const request = spec.buildRequests(bidRequests, bidderRequests); + const request = spec.buildRequests(bidRequests, bidderRequest); expect(request[0].method).to.equal('POST'); expect(request[1].method).to.equal('POST'); }); it('attaches source and version to endpoint URL as query params', function () { - const request = spec.buildRequests(bidRequests, bidderRequests); + const request = spec.buildRequests(bidRequests, bidderRequest); expect(request[0].url).to.equal(ENDPOINT_URL); expect(request[1].url).to.equal(ENDPOINT_URL); @@ -119,6 +123,7 @@ describe('cointrafficBidAdapter', function () { describe('interpretResponse', function () { it('should get the correct bid response', function () { + /** @type {BidRequest[]} */ let bidRequest = [{ method: 'POST', url: ENDPOINT_URL, @@ -142,7 +147,7 @@ describe('cointrafficBidAdapter', function () { height: 250, creativeId: 'creativeId12345', ttl: 90, - ad: '

I am an ad

', + ad: '

I am an ad

', mediaType: 'banner', adomain: ['test.com'] } @@ -157,7 +162,7 @@ describe('cointrafficBidAdapter', function () { height: 250, creativeId: 'creativeId12345', ttl: 90, - ad: '

I am an ad

', + ad: '

I am an ad

', meta: { mediaType: 'banner', advertiserDomains: [ @@ -170,7 +175,58 @@ describe('cointrafficBidAdapter', function () { expect(Object.keys(result)).to.deep.equal(Object.keys(expectedResponse)); }); + it('should get the correct bid response without advertiser domains specified', function () { + /** @type {BidRequest[]} */ + let bidRequest = [{ + method: 'POST', + url: ENDPOINT_URL, + data: { + placementId: 'testPlacementId', + device: 'desktop', + currency: 'EUR', + sizes: ['300x250'], + bidId: 'bidId12345', + referer: 'www.example.com' + } + }]; + + let serverResponse = { + body: { + requestId: 'bidId12345', + cpm: 3.9, + currency: 'EUR', + netRevenue: true, + width: 300, + height: 250, + creativeId: 'creativeId12345', + ttl: 90, + ad: '

I am an ad

', + mediaType: 'banner', + } + }; + + let expectedResponse = [{ + requestId: 'bidId12345', + cpm: 3.9, + currency: 'EUR', + netRevenue: true, + width: 300, + height: 250, + creativeId: 'creativeId12345', + ttl: 90, + ad: '

I am an ad

', + meta: { + mediaType: 'banner', + advertiserDomains: [] + } + }]; + + let result = spec.interpretResponse(serverResponse, bidRequest[0]); + expect(Object.keys(result)).to.deep.equal(Object.keys(expectedResponse)); + }); + it('should get the correct bid response with different currency', function () { + /** @type {BidRequest[]} */ let bidRequest = [{ method: 'POST', url: ENDPOINT_URL, @@ -194,7 +250,7 @@ describe('cointrafficBidAdapter', function () { height: 250, creativeId: 'creativeId12345', ttl: 90, - ad: '

I am an ad

', + ad: '

I am an ad

', mediaType: 'banner', adomain: ['test.com'] } @@ -209,7 +265,7 @@ describe('cointrafficBidAdapter', function () { height: 250, creativeId: 'creativeId12345', ttl: 90, - ad: '

I am an ad

', + ad: '

I am an ad

', meta: { mediaType: 'banner', advertiserDomains: [ @@ -227,6 +283,7 @@ describe('cointrafficBidAdapter', function () { }); it('should get empty bid response requested currency is not available', function () { + /** @type {BidRequest[]} */ let bidRequest = [{ method: 'POST', url: ENDPOINT_URL, @@ -253,6 +310,7 @@ describe('cointrafficBidAdapter', function () { }); it('should get empty bid response if no server response', function () { + /** @type {BidRequest[]} */ let bidRequest = [{ method: 'POST', url: ENDPOINT_URL, diff --git a/test/spec/modules/colossussspBidAdapter_spec.js b/test/spec/modules/colossussspBidAdapter_spec.js index adb9137d892..b8c872d879d 100644 --- a/test/spec/modules/colossussspBidAdapter_spec.js +++ b/test/spec/modules/colossussspBidAdapter_spec.js @@ -19,12 +19,12 @@ describe('ColossussspAdapter', function () { }, ortb2Imp: { ext: { + tid: '3bb2f6da-87a6-4029-aeb0-bfe951372e62', data: { pbadslot: '/19968336/prebid_cache_video_adunit' } } }, - transactionId: '3bb2f6da-87a6-4029-aeb0-bfe951372e62', schain: { ver: '1.0', complete: 1, @@ -283,6 +283,35 @@ describe('ColossussspAdapter', function () { }); }); + describe('gpp consent', function () { + it('bidderRequest.gppConsent', () => { + bidderRequest.gppConsent = { + gppString: 'abc123', + applicableSections: [8] + }; + + let serverRequest = spec.buildRequests([bid], bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + delete bidderRequest.gppConsent; + }) + + it('bidderRequest.ortb2.regs.gpp', () => { + bidderRequest.ortb2.regs = bidderRequest.ortb2.regs || {}; + bidderRequest.ortb2.regs.gpp = 'abc123'; + bidderRequest.ortb2.regs.gpp_sid = [8]; + + let serverRequest = spec.buildRequests([bid], bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + }) + }); + describe('interpretResponse', function () { let resObject = { body: [{ diff --git a/test/spec/modules/compassBidAdapter_spec.js b/test/spec/modules/compassBidAdapter_spec.js index 28021c4f7c0..6a761e63ea1 100644 --- a/test/spec/modules/compassBidAdapter_spec.js +++ b/test/spec/modules/compassBidAdapter_spec.js @@ -76,7 +76,8 @@ describe('CompassBidAdapter', function () { gdprConsent: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', refererInfo: { referer: 'https://test.com' - } + }, + timeout: 500 }; describe('isBidRequestValid', function () { diff --git a/test/spec/modules/conceptxBidAdapter_spec.js b/test/spec/modules/conceptxBidAdapter_spec.js new file mode 100644 index 00000000000..349ee765b71 --- /dev/null +++ b/test/spec/modules/conceptxBidAdapter_spec.js @@ -0,0 +1,136 @@ +// import or require modules necessary for the test, e.g.: +import { expect } from 'chai'; // may prefer 'assert' in place of 'expect' +import { spec } from 'modules/conceptxBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; +// import { config } from 'src/config.js'; + +describe('conceptxBidAdapter', function () { + const URL = 'https://conceptx.cncpt-central.com/openrtb'; + + // before(() => { + + // }); + + // after(() => { + // // $$PREBID_GLOBAL$$.bidderSettings = {}; + // }); + + // afterEach(function () { + // config.resetConfig(); + // }); + + const ENDPOINT_URL = `${URL}`; + const ENDPOINT_URL_CONSENT = `${URL}?gdpr_applies=true&consentString=ihaveconsented`; + const adapter = newBidder(spec); + + const bidderRequests = [ + { + bidId: '123', + bidder: 'conceptx', + params: { + site: 'example', + adunit: 'some-id-3' + }, + mediaTypes: { + banner: { + sizes: [[930, 180]], + } + }, + } + ] + + const singleBidRequest = { + bid: [ + { + bidId: '123', + } + ] + } + + const serverResponse = { + body: { + 'bidResponses': [ + { + 'ads': [ + { + 'referrer': 'http://localhost/prebidpage_concept_bidder.html', + 'ttl': 360, + 'html': '

DUMMY

', + 'requestId': '214dfadd1f8826', + 'cpm': 46, + 'currency': 'DKK', + 'width': 930, + 'height': 180, + 'creativeId': 'FAKE-ID', + 'meta': { + 'mediaType': 'banner' + }, + 'netRevenue': true, + 'destinationUrls': { + 'destination': 'https://concept.dk' + } + } + ], + 'matchedAdCount': 1, + 'targetId': '214dfadd1f8826' + } + ] + } + } + + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function () { + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bidderRequests[0])).to.equal(true); + }); + }); + + describe('buildRequests', function () { + it('Test requests', function () { + const request = spec.buildRequests(bidderRequests, {}); + expect(request.length).to.equal(1); + expect(request[0]).to.have.property('data'); + const bid = JSON.parse(request[0].data).adUnits[0] + expect(bid.site).to.equal('example'); + expect(bid.adunit).to.equal('some-id-3'); + expect(JSON.stringify(bid.dimensions)).to.equal(JSON.stringify([ + [930, 180]])); + }); + }); + + describe('user privacy', function () { + it('should NOT send GDPR Consent data if gdprApplies equals undefined', function () { + let request = spec.buildRequests(bidderRequests, { gdprConsent: { gdprApplies: undefined, consentString: 'iDoNotConsent' } }); + expect(request.length).to.equal(1); + expect(request[0]).to.have.property('url') + expect(request[0].url).to.equal(ENDPOINT_URL); + }); + it('should send GDPR Consent data if gdprApplies', function () { + let request = spec.buildRequests(bidderRequests, { gdprConsent: { gdprApplies: true, consentString: 'ihaveconsented' } }); + expect(request.length).to.equal(1); + expect(request[0]).to.have.property('url') + expect(request[0].url).to.equal(ENDPOINT_URL_CONSENT); + }); + }); + + describe('interpretResponse', function () { + it('should return valid response when passed valid server response', function () { + const interpretedResponse = spec.interpretResponse(serverResponse, singleBidRequest); + const ad = serverResponse.body.bidResponses[0].ads[0] + expect(interpretedResponse).to.have.lengthOf(1); + expect(interpretedResponse[0].cpm).to.equal(ad.cpm); + expect(interpretedResponse[0].width).to.equal(Number(ad.width)); + expect(interpretedResponse[0].height).to.equal(Number(ad.height)); + expect(interpretedResponse[0].creativeId).to.equal(ad.creativeId); + expect(interpretedResponse[0].currency).to.equal(ad.currency); + expect(interpretedResponse[0].netRevenue).to.equal(true); + expect(interpretedResponse[0].ad).to.equal(ad.html); + expect(interpretedResponse[0].ttl).to.equal(360); + }); + }); +}); diff --git a/test/spec/modules/concertBidAdapter_spec.js b/test/spec/modules/concertBidAdapter_spec.js index 00626472ecb..96c98e5e5a2 100644 --- a/test/spec/modules/concertBidAdapter_spec.js +++ b/test/spec/modules/concertBidAdapter_spec.js @@ -48,7 +48,11 @@ describe('ConcertAdapter', function () { }, adUnitCode: 'desktop_leaderboard_variable', bidId: 'foo', - transactionId: '', + ortb2Imp: { + ext: { + tid: '' + } + }, sizes: [[1030, 590]] } ]; @@ -57,8 +61,9 @@ describe('ConcertAdapter', function () { refererInfo: { page: 'https://www.google.com' }, - uspConsent: '1YYY', - gdprConsent: {} + uspConsent: '1YN-', + gdprConsent: {}, + gppConsent: {} }; bidResponse = { @@ -111,7 +116,7 @@ describe('ConcertAdapter', function () { expect(payload).to.have.property('meta'); expect(payload).to.have.property('slots'); - const metaRequiredFields = ['prebidVersion', 'pageUrl', 'screen', 'debug', 'uid', 'optedOut', 'adapterVersion', 'uspConsent', 'gdprConsent']; + const metaRequiredFields = ['prebidVersion', 'pageUrl', 'screen', 'debug', 'uid', 'optedOut', 'adapterVersion', 'uspConsent', 'gdprConsent', 'gppConsent']; const slotsRequiredFields = ['name', 'bidId', 'transactionId', 'sizes', 'partnerId', 'slotType']; metaRequiredFields.forEach(function(field) { @@ -138,17 +143,20 @@ describe('ConcertAdapter', function () { expect(payload.meta.uid).to.not.equal(false); }); + it('should not generate uid if USP consent disallows', function() { + storage.removeDataFromLocalStorage('c_nap'); + const request = spec.buildRequests(bidRequests, { ...bidRequest, uspConsent: '1YY' }); + const payload = JSON.parse(request.data); + + expect(payload.meta.uid).to.equal(false); + }); + it('should use sharedid if it exists', function() { storage.removeDataFromLocalStorage('c_nap'); - const request = spec.buildRequests(bidRequests, { - ...bidRequest, - userId: { - _sharedid: { - id: '123abc' - } - } - }); + const bidRequestsWithSharedId = [{ ...bidRequests[0], userId: { sharedid: { id: '123abc' } } }] + const request = spec.buildRequests(bidRequestsWithSharedId, bidRequest); const payload = JSON.parse(request.data); + expect(payload.meta.uid).to.equal('123abc'); }) @@ -213,82 +221,4 @@ describe('ConcertAdapter', function () { expect(bids).to.have.lengthOf(0); }); }); - - describe('spec.getUserSyncs', function() { - it('should not register syncs when iframe is not enabled', function() { - const opts = { - iframeEnabled: false - } - const sync = spec.getUserSyncs(opts, [], bidRequest.gdprConsent, bidRequest.uspConsent); - expect(sync).to.have.lengthOf(0); - }); - - it('should not register syncs when the user has opted out', function() { - const opts = { - iframeEnabled: true - }; - storage.setDataInLocalStorage('c_nap', 'true'); - - const sync = spec.getUserSyncs(opts, [], bidRequest.gdprConsent, bidRequest.uspConsent); - expect(sync).to.have.lengthOf(0); - }); - - it('should set gdprApplies flag to 1 if the user is in area where GDPR applies', function() { - const opts = { - iframeEnabled: true - }; - storage.removeDataFromLocalStorage('c_nap'); - - bidRequest.gdprConsent = { - gdprApplies: true - }; - - const sync = spec.getUserSyncs(opts, [], bidRequest.gdprConsent, bidRequest.uspConsent); - expect(sync[0].url).to.have.string('gdpr_applies=1'); - }); - - it('should set gdprApplies flag to 1 if the user is in area where GDPR applies', function() { - const opts = { - iframeEnabled: true - }; - storage.removeDataFromLocalStorage('c_nap'); - - bidRequest.gdprConsent = { - gdprApplies: false - }; - - const sync = spec.getUserSyncs(opts, [], bidRequest.gdprConsent, bidRequest.uspConsent); - expect(sync[0].url).to.have.string('gdpr_applies=0'); - }); - - it('should set gdpr consent param with the user\'s choices on consent', function() { - const opts = { - iframeEnabled: true - }; - storage.removeDataFromLocalStorage('c_nap'); - - bidRequest.gdprConsent = { - gdprApplies: false, - consentString: 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==' - }; - - const sync = spec.getUserSyncs(opts, [], bidRequest.gdprConsent, bidRequest.uspConsent); - expect(sync[0].url).to.have.string('gdpr_consent=BOJ/P2HOJ/P2HABABMAAAAAZ+A=='); - }); - - it('should set ccpa consent param with the user\'s choices on consent', function() { - const opts = { - iframeEnabled: true - }; - storage.removeDataFromLocalStorage('c_nap'); - - bidRequest.gdprConsent = { - gdprApplies: false, - uspConsent: '1YYY' - }; - - const sync = spec.getUserSyncs(opts, [], bidRequest.gdprConsent, bidRequest.uspConsent); - expect(sync[0].url).to.have.string('usp_consent=1YY'); - }); - }); }); diff --git a/test/spec/modules/confiantRtdProvider_spec.js b/test/spec/modules/confiantRtdProvider_spec.js index 987aca2e020..8f9fcd0ba98 100644 --- a/test/spec/modules/confiantRtdProvider_spec.js +++ b/test/spec/modules/confiantRtdProvider_spec.js @@ -46,6 +46,7 @@ describe('Confiant RTD module', function () { let listenerCallback; const mockWindow = { addEventListener: (a, cb) => (listenerCallback = cb) }; let billableEventsCounter = 0; + const propertyId = 'fff'; events.on(CONSTANTS.EVENTS.BILLABLE_EVENT, (e) => { if (e.vendor === 'confiant') { @@ -57,15 +58,17 @@ describe('Confiant RTD module', function () { } }); - subscribeToConfiantCommFrame(mockWindow); + subscribeToConfiantCommFrame(mockWindow, propertyId); listenerCallback({ data: { + type: 'cnft:reportBillableEvent:' + propertyId, auctionId: 'auctionId', transactionId: 'transactionId' } }); listenerCallback({ data: { + type: 'cnft:reportBillableEvent:' + propertyId, auctionId: 'auctionId', transactionId: 'transactionId' } diff --git a/test/spec/modules/connectIdSystem_spec.js b/test/spec/modules/connectIdSystem_spec.js index 52639c39baf..5376ba60886 100644 --- a/test/spec/modules/connectIdSystem_spec.js +++ b/test/spec/modules/connectIdSystem_spec.js @@ -1,6 +1,10 @@ import {expect} from 'chai'; -import {parseQS} from 'src/utils.js'; -import {connectIdSubmodule} from 'modules/connectIdSystem.js'; +import {connectIdSubmodule, storage} from 'modules/connectIdSystem.js'; +import {server} from '../../mocks/xhr'; +import {parseQS, parseUrl} from 'src/utils.js'; +import {uspDataHandler, gppDataHandler} from 'src/adapterManager.js'; + +const TEST_SERVER_URL = 'http://localhost:9876/'; describe('Yahoo ConnectID Submodule', () => { const HASHED_EMAIL = '6bda6f2fa268bf0438b5423a9861a2cedaa5dec163c03f743cfe05c08a8397b2'; @@ -8,6 +12,12 @@ describe('Yahoo ConnectID Submodule', () => { const PIXEL_ID = '1234'; const PROD_ENDPOINT = `https://ups.analytics.yahoo.com/ups/${PIXEL_ID}/fed`; const OVERRIDE_ENDPOINT = 'https://foo/bar'; + const STORAGE_KEY = 'connectId'; + const USP_DATA = '1YYY'; + const GPP_DATA = { + gppString: 'gppconsent', + applicableSections: [6, 7] + }; it('should have the correct module name declared', () => { expect(connectIdSubmodule.name).to.equal('connectId'); @@ -20,271 +30,704 @@ describe('Yahoo ConnectID Submodule', () => { describe('getId()', () => { let ajaxStub; let getAjaxFnStub; + let getCookieStub; + let setCookieStub; + let getLocalStorageStub; + let setLocalStorageStub; + let cookiesEnabledStub; + let localStorageEnabledStub; + let removeLocalStorageDataStub; + let uspConsentDataStub; + let gppConsentDataStub; + let consentData; beforeEach(() => { ajaxStub = sinon.stub(); getAjaxFnStub = sinon.stub(connectIdSubmodule, 'getAjaxFn'); getAjaxFnStub.returns(ajaxStub); + getCookieStub = sinon.stub(storage, 'getCookie'); + setCookieStub = sinon.stub(storage, 'setCookie'); + cookiesEnabledStub = sinon.stub(storage, 'cookiesAreEnabled'); + getLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage'); + setLocalStorageStub = sinon.stub(storage, 'setDataInLocalStorage'); + localStorageEnabledStub = sinon.stub(storage, 'localStorageIsEnabled'); + removeLocalStorageDataStub = sinon.stub(storage, 'removeDataFromLocalStorage'); + uspConsentDataStub = sinon.stub(uspDataHandler, 'getConsentData'); + gppConsentDataStub = sinon.stub(gppDataHandler, 'getConsentData'); + + cookiesEnabledStub.returns(true); + localStorageEnabledStub.returns(true); + uspConsentDataStub.returns(USP_DATA); + gppConsentDataStub.returns(GPP_DATA); consentData = { - gdpr: { - gdprApplies: 1, - consentString: 'GDPR_CONSENT_STRING' - }, - uspConsent: 'USP_CONSENT_STRING', - gppConsent: { - gppString: 'header~section6~section7', - applicableSections: [6, 7] - } + gdprApplies: 1, + consentString: 'GDPR_CONSENT_STRING' }; }); afterEach(() => { getAjaxFnStub.restore(); + getCookieStub.restore(); + setCookieStub.restore(); + getLocalStorageStub.restore(); + setLocalStorageStub.restore(); + cookiesEnabledStub.restore(); + localStorageEnabledStub.restore(); + removeLocalStorageDataStub.restore(); + uspConsentDataStub.restore(); + gppConsentDataStub.restore(); }); function invokeGetIdAPI(configParams, consentData) { let result = connectIdSubmodule.getId({ params: configParams }, consentData); - if (typeof result === 'object') { + if (typeof result === 'object' && result.callback) { result.callback(sinon.stub()); } return result; } - it('returns undefined if he, pixelId and puid params are not passed', () => { - expect(invokeGetIdAPI({}, consentData)).to.be.undefined; - expect(ajaxStub.callCount).to.equal(0); - }); - - it('returns undefined if the pixelId param is not passed', () => { - expect(invokeGetIdAPI({ - he: HASHED_EMAIL, - puid: PUBLISHER_USER_ID - }, consentData)).to.be.undefined; - expect(ajaxStub.callCount).to.equal(0); - }); - - it('returns undefined if the pixelId param is passed, but the he and puid param are not passed', () => { - expect(invokeGetIdAPI({ - pixelId: PIXEL_ID - }, consentData)).to.be.undefined; - expect(ajaxStub.callCount).to.equal(0); - }); - - it('returns an object with the callback function if the endpoint override param and the he params are passed', () => { - let result = invokeGetIdAPI({ - he: HASHED_EMAIL, - endpoint: OVERRIDE_ENDPOINT - }, consentData); - expect(result).to.be.an('object').that.has.all.keys('callback'); - expect(result.callback).to.be.a('function'); - }); - - it('returns an object with the callback function if the endpoint override param and the puid params are passed', () => { - let result = invokeGetIdAPI({ - puid: PUBLISHER_USER_ID, - endpoint: OVERRIDE_ENDPOINT - }, consentData); - expect(result).to.be.an('object').that.has.all.keys('callback'); - expect(result.callback).to.be.a('function'); - }); - - it('returns an object with the callback function if the endpoint override param and the puid and he params are passed', () => { - let result = invokeGetIdAPI({ - he: HASHED_EMAIL, - puid: PUBLISHER_USER_ID, - endpoint: OVERRIDE_ENDPOINT - }, consentData); - expect(result).to.be.an('object').that.has.all.keys('callback'); - expect(result.callback).to.be.a('function'); - }); - - it('returns an object with the callback function if the pixelId and he params are passed', () => { - let result = invokeGetIdAPI({ - he: HASHED_EMAIL, - pixelId: PIXEL_ID - }, consentData); - expect(result).to.be.an('object').that.has.all.keys('callback'); - expect(result.callback).to.be.a('function'); - }); - - it('returns an object with the callback function if the pixelId and puid params are passed', () => { - let result = invokeGetIdAPI({ - puid: PUBLISHER_USER_ID, - pixelId: PIXEL_ID - }, consentData); - expect(result).to.be.an('object').that.has.all.keys('callback'); - expect(result.callback).to.be.a('function'); - }); - - it('returns an object with the callback function if the pixelId, he and puid params are passed', () => { - let result = invokeGetIdAPI({ - he: HASHED_EMAIL, - puid: PUBLISHER_USER_ID, - pixelId: PIXEL_ID - }, consentData); - expect(result).to.be.an('object').that.has.all.keys('callback'); - expect(result.callback).to.be.a('function'); - }); - - it('returns an undefined if the Yahoo specific opt-out key is present in local storage', () => { - localStorage.setItem('connectIdOptOut', '1'); - expect(invokeGetIdAPI({ - he: HASHED_EMAIL, - pixelId: PIXEL_ID - }, consentData)).to.be.undefined; - localStorage.removeItem('connectIdOptOut'); - }); - - it('returns an object with the callback function if the correct params are passed and Yahoo opt-out value is not "1"', () => { - localStorage.setItem('connectIdOptOut', 'true'); - let result = invokeGetIdAPI({ - he: HASHED_EMAIL, - pixelId: PIXEL_ID - }, consentData); - expect(result).to.be.an('object').that.has.all.keys('callback'); - expect(result.callback).to.be.a('function'); - localStorage.removeItem('connectIdOptOut'); - }); - - it('Makes an ajax GET request to the production API endpoint with pixelId and he query params', () => { - invokeGetIdAPI({ - he: HASHED_EMAIL, - pixelId: PIXEL_ID - }, consentData); - - const expectedParams = { - he: HASHED_EMAIL, - pixelId: PIXEL_ID, - '1p': '0', - gdpr: '1', - gdpr_consent: consentData.gdpr.consentString, - us_privacy: consentData.uspConsent, - gpp: consentData.gppConsent.gppString, - gpp_sid: '6%2C7' - }; - const requestQueryParams = parseQS(ajaxStub.firstCall.args[0].split('?')[1]); - - expect(ajaxStub.firstCall.args[0].indexOf(`${PROD_ENDPOINT}?`)).to.equal(0); - expect(requestQueryParams).to.deep.equal(expectedParams); - expect(ajaxStub.firstCall.args[3]).to.deep.equal({method: 'GET', withCredentials: true}); - }); - - it('Makes an ajax GET request to the production API endpoint with pixelId and puid query params', () => { - invokeGetIdAPI({ - puid: PUBLISHER_USER_ID, - pixelId: PIXEL_ID - }, consentData); - - const expectedParams = { - puid: PUBLISHER_USER_ID, - pixelId: PIXEL_ID, - '1p': '0', - gdpr: '1', - gdpr_consent: consentData.gdpr.consentString, - us_privacy: consentData.uspConsent, - gpp: consentData.gppConsent.gppString, - gpp_sid: '6%2C7' - }; - const requestQueryParams = parseQS(ajaxStub.firstCall.args[0].split('?')[1]); - - expect(ajaxStub.firstCall.args[0].indexOf(`${PROD_ENDPOINT}?`)).to.equal(0); - expect(requestQueryParams).to.deep.equal(expectedParams); - expect(ajaxStub.firstCall.args[3]).to.deep.equal({method: 'GET', withCredentials: true}); - }); - - it('Makes an ajax GET request to the production API endpoint with pixelId, puid and he query params', () => { - invokeGetIdAPI({ - puid: PUBLISHER_USER_ID, - he: HASHED_EMAIL, - pixelId: PIXEL_ID - }, consentData); - - const expectedParams = { - puid: PUBLISHER_USER_ID, - he: HASHED_EMAIL, - pixelId: PIXEL_ID, - '1p': '0', - gdpr: '1', - gdpr_consent: consentData.gdpr.consentString, - us_privacy: consentData.uspConsent, - gpp: consentData.gppConsent.gppString, - gpp_sid: '6%2C7' - }; - const requestQueryParams = parseQS(ajaxStub.firstCall.args[0].split('?')[1]); - - expect(ajaxStub.firstCall.args[0].indexOf(`${PROD_ENDPOINT}?`)).to.equal(0); - expect(requestQueryParams).to.deep.equal(expectedParams); - expect(ajaxStub.firstCall.args[3]).to.deep.equal({method: 'GET', withCredentials: true}); - }); - - it('Makes an ajax GET request to the specified override API endpoint with query params', () => { - invokeGetIdAPI({ - he: HASHED_EMAIL, - endpoint: OVERRIDE_ENDPOINT - }, consentData); - - const expectedParams = { - he: HASHED_EMAIL, - '1p': '0', - gdpr: '1', - gdpr_consent: consentData.gdpr.consentString, - us_privacy: consentData.uspConsent, - gpp: consentData.gppConsent.gppString, - gpp_sid: '6%2C7' - }; - const requestQueryParams = parseQS(ajaxStub.firstCall.args[0].split('?')[1]); - - expect(ajaxStub.firstCall.args[0].indexOf(`${OVERRIDE_ENDPOINT}?`)).to.equal(0); - expect(requestQueryParams).to.deep.equal(expectedParams); - expect(ajaxStub.firstCall.args[3]).to.deep.equal({method: 'GET', withCredentials: true}); - }); - - it('sets the callbacks param of the ajax function call correctly', () => { - invokeGetIdAPI({ - he: HASHED_EMAIL, - pixelId: PIXEL_ID, - }, consentData); - - expect(ajaxStub.firstCall.args[1]).to.be.an('object').that.has.all.keys(['success', 'error']); - }); + describe('Low level storage functionality', () => { + const storageTestCases = [ + { + detail: 'cookie data over local storage data', + cookie: '{"connectId":"foo"}', + localStorage: JSON.stringify({connectId: 'bar'}), + expected: {connectId: 'foo'} + }, + { + detail: 'cookie data if only cookie data exists', + cookie: '{"connectId":"foo"}', + localStorage: undefined, + expected: {connectId: 'foo'} + }, + { + detail: 'local storage data if only it local storage data exists', + cookie: undefined, + localStorage: JSON.stringify({connectId: 'bar'}), + expected: {connectId: 'bar'} + }, + { + detail: 'undefined when both cookie and local storage are empty', + cookie: undefined, + localStorage: undefined, + expected: undefined + } + ] - it('sets GDPR consent data flag correctly when call is under GDPR jurisdiction.', () => { - invokeGetIdAPI({ - he: HASHED_EMAIL, - pixelId: PIXEL_ID, - }, consentData); + storageTestCases.forEach(testCase => it(`getId() should return ${testCase.detail}`, function () { + getCookieStub.withArgs(STORAGE_KEY).returns(testCase.cookie); + getLocalStorageStub.withArgs(STORAGE_KEY).returns(testCase.localStorage); - const requestQueryParams = parseQS(ajaxStub.firstCall.args[0].split('?')[1]); - expect(requestQueryParams.gdpr).to.equal('1'); - expect(requestQueryParams.gdpr_consent).to.equal(consentData.gdpr.consentString); - }); + const result = invokeGetIdAPI({ + he: HASHED_EMAIL, + endpoint: OVERRIDE_ENDPOINT + }, consentData); - it('sets GDPR consent data flag correctly when call is NOT under GDPR jurisdiction.', () => { - consentData.gdpr.gdprApplies = false; + expect(result.id).to.be.deep.equal(testCase.expected ? testCase.expected : undefined); + })); + }) - invokeGetIdAPI({ - he: HASHED_EMAIL, - pixelId: PIXEL_ID, - }, consentData); + describe('with invalid module configuration', () => { + it('returns undefined if he, pixelId and puid params are not passed', () => { + expect(invokeGetIdAPI({}, consentData)).to.be.undefined; + expect(ajaxStub.callCount).to.equal(0); + }); - const requestQueryParams = parseQS(ajaxStub.firstCall.args[0].split('?')[1]); - expect(requestQueryParams.gdpr).to.equal('0'); - expect(requestQueryParams.gdpr_consent).to.equal(''); + it('returns undefined if the pixelId param is not passed', () => { + expect(invokeGetIdAPI({ + he: HASHED_EMAIL, + puid: PUBLISHER_USER_ID + }, consentData)).to.be.undefined; + expect(ajaxStub.callCount).to.equal(0); + }); }); - [1, '1', true].forEach(firstPartyParamValue => { - it(`sets 1p payload property to '1' for a config value of ${firstPartyParamValue}`, () => { - invokeGetIdAPI({ - '1p': firstPartyParamValue, - he: HASHED_EMAIL, - pixelId: PIXEL_ID, - }, consentData); + describe('with valid module configuration', () => { + describe('when data is in client storage', () => { + it('returns an object with the stored ID from cookies for valid module configuration and sync is done', () => { + const cookieData = {connectId: 'foobar'}; + getCookieStub.withArgs(STORAGE_KEY).returns(JSON.stringify(cookieData)); + let result = invokeGetIdAPI({ + he: HASHED_EMAIL, + pixelId: PIXEL_ID + }, consentData); + + expect(result).to.be.an('object').that.has.all.keys('id', 'callback'); + expect(result.id).to.deep.equal(cookieData); + expect(typeof result.callback).to.equal('function'); + }); + + it('returns an object with the stored ID from cookies for valid module configuration with no user sync', () => { + const last13Days = Date.now() - (60 * 60 * 24 * 1000 * 13); + const cookieData = {connectId: 'foobar', he: HASHED_EMAIL, lastSynced: last13Days}; + getCookieStub.withArgs(STORAGE_KEY).returns(JSON.stringify(cookieData)); + let result = invokeGetIdAPI({ + he: HASHED_EMAIL, + pixelId: PIXEL_ID + }, consentData); + + expect(result).to.be.an('object').that.has.all.keys('id'); + expect(result.id.lastUsed).to.be.a('number'); + cookieData.lastUsed = result.id.lastUsed; + expect(result.id).to.deep.equal(cookieData); + }); + + it('returns an object with the stored ID and refreshes the storages with the new lastUsed', () => { + const last13Days = Date.now() - (60 * 60 * 24 * 1000 * 13); + const cookieData = {connectId: 'foobar', he: HASHED_EMAIL, lastSynced: last13Days, lastUsed: 1}; + getCookieStub.withArgs(STORAGE_KEY).returns(JSON.stringify(cookieData)); + const dateNowStub = sinon.stub(Date, 'now'); + dateNowStub.returns(20); + const newCookieData = Object.assign({}, cookieData, {lastUsed: 20}) + let result = invokeGetIdAPI({ + he: HASHED_EMAIL, + pixelId: PIXEL_ID + }, consentData); + dateNowStub.restore(); + + const expiryDelta = new Date(60 * 60 * 24 * 365 * 1000); + expect(result).to.be.an('object').that.has.all.keys('id'); + expect(setCookieStub.calledOnce).to.be.true; + expect(setCookieStub.firstCall.args[0]).to.equal(STORAGE_KEY); + expect(setCookieStub.firstCall.args[1]).to.equal(JSON.stringify(newCookieData)); + expect(setCookieStub.firstCall.args[2]).to.equal(expiryDelta.toUTCString()); + expect(setLocalStorageStub.calledOnce).to.be.true; + expect(setLocalStorageStub.firstCall.args[0]).to.equal(STORAGE_KEY); + expect(setLocalStorageStub.firstCall.args[1]).to.equal(JSON.stringify(newCookieData)); + }); + + it('returns an object with the stored ID from cookies and no sync when puid stays the same', () => { + const last13Days = Date.now() - (60 * 60 * 24 * 1000 * 13); + const cookieData = {connectId: 'foobar', puid: '123', lastSynced: last13Days}; + getCookieStub.withArgs(STORAGE_KEY).returns(JSON.stringify(cookieData)); + const dateNowStub = sinon.stub(Date, 'now'); + dateNowStub.returns(20); + let result = invokeGetIdAPI({ + puid: '123', + pixelId: PIXEL_ID + }, consentData); + dateNowStub.restore(); + + expect(result).to.be.an('object').that.has.all.keys('id'); + cookieData.lastUsed = 20; + expect(result.id).to.deep.equal(cookieData); + }); + + it('returns an object with the stored ID from cookies and syncs because of expired auto generated puid', () => { + const last13Days = Date.now() - (60 * 60 * 24 * 1000 * 13); + const last31Days = Date.now() - (60 * 60 * 24 * 1000 * 31); + const cookieData = {connectId: 'foo', he: 'email', lastSynced: last13Days, puid: '9', lastUsed: last31Days}; + getCookieStub.withArgs(STORAGE_KEY).returns(JSON.stringify(cookieData)); + let result = invokeGetIdAPI({ + he: HASHED_EMAIL, + pixelId: PIXEL_ID + }, consentData); + + delete cookieData.puid; + expect(result).to.be.an('object').that.has.all.keys('id', 'callback'); + expect(result.id).to.deep.equal(cookieData); + expect(typeof result.callback).to.equal('function'); + }); + + it('returns an object with the stored ID from cookies and does not sync for valid auto generated puid', () => { + const last13Days = Date.now() - (60 * 60 * 24 * 1000 * 13); + const last29Days = Date.now() - (60 * 60 * 24 * 1000 * 29); + const cookieData = { + connectId: 'foo', + he: HASHED_EMAIL, + lastSynced: last13Days, + puid: '9', + lastUsed: last29Days + }; + getCookieStub.withArgs(STORAGE_KEY).returns(JSON.stringify(cookieData)); + const dateNowStub = sinon.stub(Date, 'now'); + dateNowStub.returns(20); + let result = invokeGetIdAPI({ + he: HASHED_EMAIL, + pixelId: PIXEL_ID + }, consentData); + dateNowStub.restore(); + + expect(result).to.be.an('object').that.has.all.keys('id'); + cookieData.lastUsed = 20; + expect(result.id).to.deep.equal(cookieData); + }); + + it('returns an object with the stored ID from localStorage for valid module configuration and sync is done', () => { + const localStorageData = {connectId: 'foobarbaz'}; + getLocalStorageStub.withArgs(STORAGE_KEY).returns(localStorageData); + let result = invokeGetIdAPI({ + he: HASHED_EMAIL, + pixelId: PIXEL_ID + }, consentData); + + expect(result).to.be.an('object').that.has.all.keys('id', 'callback'); + expect(result.id).to.deep.equal(localStorageData); + expect(typeof result.callback).to.equal('function'); + }); + + it('returns an object with the stored ID from localStorage and refreshes the cookie storage', () => { + const localStorageData = {connectId: 'foobarbaz'}; + getLocalStorageStub.withArgs(STORAGE_KEY).returns(localStorageData); + const dateNowStub = sinon.stub(Date, 'now'); + dateNowStub.returns(1); + let result = invokeGetIdAPI({ + he: HASHED_EMAIL, + pixelId: PIXEL_ID + }, consentData); + dateNowStub.restore(); + + const expiryDelta = new Date(1 + 60 * 60 * 24 * 365 * 1000); + expect(result).to.be.an('object').that.has.all.keys('id', 'callback'); + expect(setCookieStub.calledOnce).to.be.true; + expect(setCookieStub.firstCall.args[0]).to.equal(STORAGE_KEY); + expect(setCookieStub.firstCall.args[1]).to.equal(JSON.stringify(localStorageData)); + expect(setCookieStub.firstCall.args[2]).to.equal(expiryDelta.toUTCString()); + }); + + it('Makes an ajax GET request to the production API endpoint with stored puid when id is stale', () => { + const last15Days = Date.now() - (60 * 60 * 24 * 1000 * 15); + const last29Days = Date.now() - (60 * 60 * 24 * 1000 * 29); + const cookieData = { + connectId: 'foobar', + he: HASHED_EMAIL, + lastSynced: last15Days, + puid: '981', + lastUsed: last29Days + }; + getCookieStub.withArgs(STORAGE_KEY).returns(JSON.stringify(cookieData)); + invokeGetIdAPI({ + he: HASHED_EMAIL, + pixelId: PIXEL_ID + }, consentData); + + const expectedParams = { + he: HASHED_EMAIL, + pixelId: PIXEL_ID, + '1p': '0', + gdpr: '1', + gdpr_consent: consentData.consentString, + v: '1', + url: TEST_SERVER_URL, + us_privacy: USP_DATA, + puid: '981', + gpp: GPP_DATA.gppString, + gpp_sid: GPP_DATA.applicableSections.join(',') + }; + const requestQueryParams = parseQS(ajaxStub.firstCall.args[0].split('?')[1]); + + expect(ajaxStub.firstCall.args[0].indexOf(`${PROD_ENDPOINT}?`)).to.equal(0); + expect(requestQueryParams).to.deep.equal(expectedParams); + expect(ajaxStub.firstCall.args[3]).to.deep.equal({method: 'GET', withCredentials: true}); + }); + + it('Makes an ajax GET request to the production API endpoint without the stored puid after 30 days', () => { + const last31Days = Date.now() - (60 * 60 * 24 * 1000 * 31); + const cookieData = { + connectId: 'foobar', + he: HASHED_EMAIL, + lastSynced: last31Days, + puid: '981', + lastUsed: last31Days + }; + getCookieStub.withArgs(STORAGE_KEY).returns(JSON.stringify(cookieData)); + invokeGetIdAPI({ + he: HASHED_EMAIL, + pixelId: PIXEL_ID + }, consentData); + + const expectedParams = { + he: HASHED_EMAIL, + pixelId: PIXEL_ID, + '1p': '0', + gdpr: '1', + gdpr_consent: consentData.consentString, + v: '1', + url: TEST_SERVER_URL, + us_privacy: USP_DATA, + gpp: GPP_DATA.gppString, + gpp_sid: GPP_DATA.applicableSections.join(',') + }; + const requestQueryParams = parseQS(ajaxStub.firstCall.args[0].split('?')[1]); + + expect(ajaxStub.firstCall.args[0].indexOf(`${PROD_ENDPOINT}?`)).to.equal(0); + expect(requestQueryParams).to.deep.equal(expectedParams); + expect(ajaxStub.firstCall.args[3]).to.deep.equal({method: 'GET', withCredentials: true}); + }); + + it('Makes an ajax GET request to the production API endpoint with provided puid', () => { + const last3Days = Date.now() - (60 * 60 * 24 * 1000 * 3); + const cookieData = { + connectId: 'foobar', + he: HASHED_EMAIL, + lastSynced: last3Days, + puid: '981', + lastUsed: last3Days + }; + getCookieStub.withArgs(STORAGE_KEY).returns(JSON.stringify(cookieData)); + invokeGetIdAPI({ + pixelId: PIXEL_ID, + puid: PUBLISHER_USER_ID + }, consentData); + + const expectedParams = { + pixelId: PIXEL_ID, + puid: PUBLISHER_USER_ID, + he: HASHED_EMAIL, + '1p': '0', + gdpr: '1', + gdpr_consent: consentData.consentString, + v: '1', + url: TEST_SERVER_URL, + us_privacy: USP_DATA, + gpp: GPP_DATA.gppString, + gpp_sid: GPP_DATA.applicableSections.join(',') + }; + const requestQueryParams = parseQS(ajaxStub.firstCall.args[0].split('?')[1]); + + expect(ajaxStub.firstCall.args[0].indexOf(`${PROD_ENDPOINT}?`)).to.equal(0); + expect(requestQueryParams).to.deep.equal(expectedParams); + expect(ajaxStub.firstCall.args[3]).to.deep.equal({method: 'GET', withCredentials: true}); + }); + + it('deletes local storage data when expiry has passed', () => { + const localStorageData = {connectId: 'foobarbaz', __expires: Date.now() - 10000}; + getLocalStorageStub.withArgs(STORAGE_KEY).returns(localStorageData); + let result = invokeGetIdAPI({ + he: HASHED_EMAIL, + pixelId: PIXEL_ID + }, consentData); + expect(removeLocalStorageDataStub.calledOnce).to.be.true; + expect(removeLocalStorageDataStub.firstCall.args[0]).to.equal(STORAGE_KEY); + expect(result.id).to.equal(undefined); + expect(result.callback).to.be.a('function'); + }); + + it('will not delete local storage data when expiry has not passed', () => { + const localStorageData = {connectId: 'foobarbaz', __expires: Date.now() + 10000}; + getLocalStorageStub.withArgs(STORAGE_KEY).returns(localStorageData); + let result = invokeGetIdAPI({ + he: HASHED_EMAIL, + pixelId: PIXEL_ID + }, consentData); + expect(removeLocalStorageDataStub.calledOnce).to.be.false; + expect(result.id).to.deep.equal(localStorageData); + }); + }); - const requestQueryParams = parseQS(ajaxStub.firstCall.args[0].split('?')[1]); - expect(requestQueryParams['1p']).to.equal('1'); + describe('when no data in client storage', () => { + it('returns an object with the callback function if the endpoint override param and the he params are passed', () => { + let result = invokeGetIdAPI({ + he: HASHED_EMAIL, + endpoint: OVERRIDE_ENDPOINT + }, consentData); + expect(result).to.be.an('object').that.has.all.keys('callback'); + expect(result.callback).to.be.a('function'); + }); + + it('returns an object with the callback function if the endpoint override param and the puid params are passed', () => { + let result = invokeGetIdAPI({ + puid: PUBLISHER_USER_ID, + endpoint: OVERRIDE_ENDPOINT + }, consentData); + expect(result).to.be.an('object').that.has.all.keys('callback'); + expect(result.callback).to.be.a('function'); + }); + + it('returns an object with the callback function if the endpoint override param and the puid and he params are passed', () => { + let result = invokeGetIdAPI({ + he: HASHED_EMAIL, + puid: PUBLISHER_USER_ID, + endpoint: OVERRIDE_ENDPOINT + }, consentData); + expect(result).to.be.an('object').that.has.all.keys('callback'); + expect(result.callback).to.be.a('function'); + }); + + it('returns an object with the callback function if the pixelId and he params are passed', () => { + let result = invokeGetIdAPI({ + he: HASHED_EMAIL, + pixelId: PIXEL_ID + }, consentData); + expect(result).to.be.an('object').that.has.all.keys('callback'); + expect(result.callback).to.be.a('function'); + }); + + it('returns an object with the callback function if the pixelId and puid params are passed', () => { + let result = invokeGetIdAPI({ + puid: PUBLISHER_USER_ID, + pixelId: PIXEL_ID + }, consentData); + expect(result).to.be.an('object').that.has.all.keys('callback'); + expect(result.callback).to.be.a('function'); + }); + + it('returns an object with the callback function if the pixelId, he and puid params are passed', () => { + let result = invokeGetIdAPI({ + he: HASHED_EMAIL, + puid: PUBLISHER_USER_ID, + pixelId: PIXEL_ID + }, consentData); + expect(result).to.be.an('object').that.has.all.keys('callback'); + expect(result.callback).to.be.a('function'); + }); + + it('returns an undefined if the Yahoo specific opt-out key is present in local storage', () => { + localStorage.setItem('connectIdOptOut', '1'); + expect(invokeGetIdAPI({ + he: HASHED_EMAIL, + pixelId: PIXEL_ID + }, consentData)).to.be.undefined; + localStorage.removeItem('connectIdOptOut'); + }); + + it('returns an object with the callback function if the correct params are passed and Yahoo opt-out value is not "1"', () => { + localStorage.setItem('connectIdOptOut', 'true'); + let result = invokeGetIdAPI({ + he: HASHED_EMAIL, + pixelId: PIXEL_ID + }, consentData); + expect(result).to.be.an('object').that.has.all.keys('callback'); + expect(result.callback).to.be.a('function'); + localStorage.removeItem('connectIdOptOut'); + }); + + it('Makes an ajax GET request to the production API endpoint with pixelId and he query params', () => { + invokeGetIdAPI({ + he: HASHED_EMAIL, + pixelId: PIXEL_ID + }, consentData); + + const expectedParams = { + he: HASHED_EMAIL, + pixelId: PIXEL_ID, + '1p': '0', + gdpr: '1', + gdpr_consent: consentData.consentString, + v: '1', + url: TEST_SERVER_URL, + us_privacy: USP_DATA, + gpp: GPP_DATA.gppString, + gpp_sid: GPP_DATA.applicableSections.join(',') + }; + const requestQueryParams = parseQS(ajaxStub.firstCall.args[0].split('?')[1]); + + expect(ajaxStub.firstCall.args[0].indexOf(`${PROD_ENDPOINT}?`)).to.equal(0); + expect(requestQueryParams).to.deep.equal(expectedParams); + expect(ajaxStub.firstCall.args[3]).to.deep.equal({method: 'GET', withCredentials: true}); + }); + + it('Makes an ajax GET request to the production API endpoint with pixelId and puid query params', () => { + invokeGetIdAPI({ + puid: PUBLISHER_USER_ID, + pixelId: PIXEL_ID + }, consentData); + + const expectedParams = { + v: '1', + '1p': '0', + gdpr: '1', + puid: PUBLISHER_USER_ID, + pixelId: PIXEL_ID, + gdpr_consent: consentData.consentString, + url: TEST_SERVER_URL, + us_privacy: USP_DATA, + gpp: GPP_DATA.gppString, + gpp_sid: GPP_DATA.applicableSections.join(',') + }; + const requestQueryParams = parseQS(ajaxStub.firstCall.args[0].split('?')[1]); + + expect(ajaxStub.firstCall.args[0].indexOf(`${PROD_ENDPOINT}?`)).to.equal(0); + expect(requestQueryParams).to.deep.equal(expectedParams); + expect(ajaxStub.firstCall.args[3]).to.deep.equal({method: 'GET', withCredentials: true}); + }); + + it('Makes an ajax GET request to the production API endpoint with pixelId, puid and he query params', () => { + invokeGetIdAPI({ + puid: PUBLISHER_USER_ID, + he: HASHED_EMAIL, + pixelId: PIXEL_ID + }, consentData); + + const expectedParams = { + puid: PUBLISHER_USER_ID, + he: HASHED_EMAIL, + pixelId: PIXEL_ID, + '1p': '0', + gdpr: '1', + gdpr_consent: consentData.consentString, + v: '1', + url: TEST_SERVER_URL, + us_privacy: USP_DATA, + gpp: GPP_DATA.gppString, + gpp_sid: GPP_DATA.applicableSections.join(',') + }; + const requestQueryParams = parseQS(ajaxStub.firstCall.args[0].split('?')[1]); + + expect(ajaxStub.firstCall.args[0].indexOf(`${PROD_ENDPOINT}?`)).to.equal(0); + expect(requestQueryParams).to.deep.equal(expectedParams); + expect(ajaxStub.firstCall.args[3]).to.deep.equal({method: 'GET', withCredentials: true}); + }); + + it('Makes an ajax GET request to the specified override API endpoint with query params', () => { + invokeGetIdAPI({ + he: HASHED_EMAIL, + endpoint: OVERRIDE_ENDPOINT + }, consentData); + + const expectedParams = { + he: HASHED_EMAIL, + '1p': '0', + gdpr: '1', + gdpr_consent: consentData.consentString, + v: '1', + url: TEST_SERVER_URL, + us_privacy: USP_DATA, + gpp: GPP_DATA.gppString, + gpp_sid: GPP_DATA.applicableSections.join(',') + }; + const requestQueryParams = parseQS(ajaxStub.firstCall.args[0].split('?')[1]); + + expect(ajaxStub.firstCall.args[0].indexOf(`${OVERRIDE_ENDPOINT}?`)).to.equal(0); + expect(requestQueryParams).to.deep.equal(expectedParams); + expect(ajaxStub.firstCall.args[3]).to.deep.equal({method: 'GET', withCredentials: true}); + }); + + it('Makes an ajax GET request to the specified override API endpoint without GPP', () => { + gppConsentDataStub.returns(undefined); + invokeGetIdAPI({ + he: HASHED_EMAIL, + endpoint: OVERRIDE_ENDPOINT + }, consentData); + + const expectedParams = { + he: HASHED_EMAIL, + '1p': '0', + gdpr: '1', + gdpr_consent: consentData.consentString, + v: '1', + url: TEST_SERVER_URL, + us_privacy: USP_DATA + }; + const requestQueryParams = parseQS(ajaxStub.firstCall.args[0].split('?')[1]); + + expect(ajaxStub.firstCall.args[0].indexOf(`${OVERRIDE_ENDPOINT}?`)).to.equal(0); + expect(requestQueryParams).to.deep.equal(expectedParams); + expect(ajaxStub.firstCall.args[3]).to.deep.equal({method: 'GET', withCredentials: true}); + }); + + it('sets the callbacks param of the ajax function call correctly', () => { + invokeGetIdAPI({ + he: HASHED_EMAIL, + pixelId: PIXEL_ID, + }, consentData); + + expect(ajaxStub.firstCall.args[1]).to.be.an('object').that.has.all.keys(['success', 'error']); + }); + + it('sets GDPR consent data flag correctly when call is under GDPR jurisdiction.', () => { + invokeGetIdAPI({ + he: HASHED_EMAIL, + pixelId: PIXEL_ID, + }, consentData); + + const requestQueryParams = parseQS(ajaxStub.firstCall.args[0].split('?')[1]); + expect(requestQueryParams.gdpr).to.equal('1'); + expect(requestQueryParams.gdpr_consent).to.equal(consentData.consentString); + }); + + it('sets GDPR consent data flag correctly when call is NOT under GDPR jurisdiction.', () => { + consentData.gdprApplies = false; + + invokeGetIdAPI({ + he: HASHED_EMAIL, + pixelId: PIXEL_ID, + }, consentData); + + const requestQueryParams = parseQS(ajaxStub.firstCall.args[0].split('?')[1]); + expect(requestQueryParams.gdpr).to.equal('0'); + expect(requestQueryParams.gdpr_consent).to.equal(''); + }); + + [1, '1', true].forEach(firstPartyParamValue => { + it(`sets 1p payload property to '1' for a config value of ${firstPartyParamValue}`, () => { + invokeGetIdAPI({ + '1p': firstPartyParamValue, + he: HASHED_EMAIL, + pixelId: PIXEL_ID, + }, consentData); + + const requestQueryParams = parseQS(ajaxStub.firstCall.args[0].split('?')[1]); + expect(requestQueryParams['1p']).to.equal('1'); + }); + }); + + it('stores the result in client both storages', () => { + const dateNowStub = sinon.stub(Date, 'now'); + dateNowStub.returns(0); + getAjaxFnStub.restore(); + const upsResponse = {connectid: 'foobarbaz'}; + const expiryDelta = new Date(60 * 60 * 24 * 365 * 1000); + invokeGetIdAPI({ + puid: PUBLISHER_USER_ID, + pixelId: PIXEL_ID + }, consentData); + let request = server.requests[0]; + request.respond( + 200, + {'Content-Type': 'application/json'}, + JSON.stringify(upsResponse) + ); + const storage = Object.assign({}, upsResponse, { + puid: PUBLISHER_USER_ID, + lastSynced: 0, + lastUsed: 0 + }); + dateNowStub.restore(); + + expect(setCookieStub.calledOnce).to.be.true; + expect(setCookieStub.firstCall.args[0]).to.equal(STORAGE_KEY); + expect(setCookieStub.firstCall.args[1]).to.equal(JSON.stringify(storage)); + expect(setCookieStub.firstCall.args[2]).to.equal(expiryDelta.toUTCString()); + expect(setCookieStub.firstCall.args[3]).to.equal(null); + const cookieDomain = parseUrl(TEST_SERVER_URL).hostname; + expect(setCookieStub.firstCall.args[4]).to.equal(`.${cookieDomain}`); + expect(setLocalStorageStub.calledOnce).to.be.true; + expect(setLocalStorageStub.firstCall.args[0]).to.equal(STORAGE_KEY); + expect(setLocalStorageStub.firstCall.args[1]).to.deep.equal(JSON.stringify(storage)); + }); + + it('stores the result in localStorage if cookies are not permitted', () => { + getAjaxFnStub.restore(); + cookiesEnabledStub.returns(false); + const dateNowStub = sinon.stub(Date, 'now'); + dateNowStub.returns(0); + const upsResponse = {connectid: 'barfoo'}; + const expectedStoredData = { + connectid: 'barfoo', + puid: PUBLISHER_USER_ID, + lastSynced: 0, + lastUsed: 0 + }; + invokeGetIdAPI({ + puid: PUBLISHER_USER_ID, + pixelId: PIXEL_ID + }, consentData); + let request = server.requests[0]; + request.respond( + 200, + {'Content-Type': 'application/json'}, + JSON.stringify(upsResponse) + ); + dateNowStub.restore(); + + expect(setLocalStorageStub.calledOnce).to.be.true; + expect(setLocalStorageStub.firstCall.args[0]).to.equal(STORAGE_KEY); + expect(setLocalStorageStub.firstCall.args[1]).to.deep.equal(JSON.stringify(expectedStoredData)); + }); }); }); }); @@ -306,6 +749,13 @@ describe('Yahoo ConnectID Submodule', () => { payload: { connectid: '4567' } + }, + { + key: 'connectId', + expected: '9924', + payload: { + connectId: '9924' + } }]; VALID_API_RESPONSES.forEach(responseData => { it('should return a newly constructed object with the connect ID for a payload with ${responseData.key} key(s)', () => { @@ -348,19 +798,15 @@ describe('Yahoo ConnectID Submodule', () => { })).to.be.false; }); - it('should return false if consent data.gdpr.applies is false', () => { + it('should return false if consent consentData.applies is false', () => { expect(connectIdSubmodule.isEUConsentRequired({ - gdpr: { - gdprApplies: false - } + gdprApplies: false })).to.be.false; }); it('should return true if consent data.gdpr.applies is true', () => { expect(connectIdSubmodule.isEUConsentRequired({ - gdpr: { - gdprApplies: true - } + gdprApplies: true })).to.be.true; }); }); diff --git a/test/spec/modules/connectadBidAdapter_spec.js b/test/spec/modules/connectadBidAdapter_spec.js index 7a70c4bacdb..d8dfcb0ce98 100644 --- a/test/spec/modules/connectadBidAdapter_spec.js +++ b/test/spec/modules/connectadBidAdapter_spec.js @@ -46,9 +46,17 @@ describe('ConnectAd Adapter', function () { auctionId: 'e76cbb58-f3e1-4ad9-9f4c-718c1919d0df', bidderRequestId: '1c56ad30b9b8ca8', transactionId: 'e76cbb58-f3e1-4ad9-9f4c-718c1919d0df', - userId: { - tdid: '123456' - } + userIdAsEids: [ + { + 'source': 'pubcid.org', + 'uids': [ + { + 'atype': 1, + 'id': '123456' + } + ] + } + ] }]; bidderRequest = { diff --git a/test/spec/modules/consentManagementGpp_spec.js b/test/spec/modules/consentManagementGpp_spec.js index 1170f418caf..17f8f6f6eac 100644 --- a/test/spec/modules/consentManagementGpp_spec.js +++ b/test/spec/modules/consentManagementGpp_spec.js @@ -1,4 +1,11 @@ -import { setConsentConfig, requestBidsHook, resetConsentData, userCMP, consentTimeout, staticConsentData } from 'modules/consentManagementGpp.js'; +import { + setConsentConfig, + requestBidsHook, + resetConsentData, + userCMP, + consentTimeout, + storeConsentData, lookupIabConsent +} from 'modules/consentManagementGpp.js'; import { gppDataHandler } from 'src/adapterManager.js'; import * as utils from 'src/utils.js'; import { config } from 'src/config.js'; @@ -94,16 +101,79 @@ describe('consentManagementGpp', function () { }); }); + describe('lookupIABConsent', () => { + let mockCmp, mockCmpEvent, gppData, sectionData + beforeEach(() => { + gppData = { + gppString: 'mockString', + applicableSections: [], + pingData: {} + }; + sectionData = {}; + mockCmp = sinon.stub().callsFake(({command, callback, parameter}) => { + let res; + switch (command) { + case 'addEventListener': + mockCmpEvent = callback; + break; + case 'getGPPData': + res = gppData; + break; + case 'getSection': + res = sectionData[parameter]; + break; + } + return Promise.resolve(res); + }); + }) + + function runLookup() { + return new Promise((resolve, reject) => lookupIabConsent({onSuccess: resolve, onError: reject}, () => mockCmp)); + } + + function oneShotLookup() { + const pm = runLookup(); + mockCmpEvent({eventName: 'sectionChange'}); + return pm; + } + + it('fetches all sections', () => { + gppData.pingData.supportedAPIs = ['usnat', 'usca'] + sectionData = { + usnat: {mock: 'usnat'}, + usca: {mock: 'usca'} + }; + return oneShotLookup().then((res) => { + expect(res.sectionData).to.eql(sectionData); + }); + }); + + it('does not choke if some section data is not available', () => { + gppData.pingData.supportedAPIs = ['usnat', 'usca'] + sectionData = { + usca: {mock: 'data'} + }; + return oneShotLookup().then((res) => { + expect(res.sectionData).to.eql(sectionData); + }) + }); + }) + describe('static consent string setConsentConfig value', () => { afterEach(() => { config.resetConfig(); }); - it('results in user settings overriding system defaults for v2 spec', () => { + it('results in user settings overriding system defaults', () => { let staticConfig = { gpp: { cmpApi: 'static', timeout: 7500, + sectionData: { + usnat: { + MockUsnatParsedFlag: true + } + }, consentData: { applicableSections: [7], gppString: 'ABCDEFG1234', @@ -116,11 +186,10 @@ describe('consentManagementGpp', function () { setConsentConfig(staticConfig); expect(userCMP).to.be.equal('static'); - expect(consentTimeout).to.be.equal(0); // should always return without a timeout when config is used const consent = gppDataHandler.getConsentData(); expect(consent.gppString).to.eql(staticConfig.gpp.consentData.gppString); - expect(consent.fullGppData).to.eql(staticConfig.gpp.consentData); - expect(staticConsentData).to.be.equal(staticConfig.gpp.consentData); + expect(consent.gppData).to.eql(staticConfig.gpp.consentData); + expect(consent.sectionData).to.eql(staticConfig.gpp.sectionData); }); }); }); @@ -334,6 +403,14 @@ describe('consentManagementGpp', function () { success: true } } + } else if (data[`${prefix}Call`].command === 'getSection') { + response = { + [`${prefix}Return`]: { + callId, + returnValue: {}, + success: true + } + } } event.source.postMessage(stringifyResponse ? JSON.stringify(response) : response, '*'); } @@ -367,7 +444,7 @@ describe('consentManagementGpp', function () { resetConsentData(); }); - describe('v2 CMP workflow for iframe pages:', function () { + describe('workflow for iframe pages:', function () { stringifyResponse = false; let ifr2 = null; @@ -410,7 +487,7 @@ describe('consentManagementGpp', function () { resetConsentData(); }); - describe('v2 CMP workflow for normal pages:', function () { + describe('CMP workflow for normal pages:', function () { beforeEach(function () { window.__gpp = function () {}; }); diff --git a/test/spec/modules/consentManagement_spec.js b/test/spec/modules/consentManagement_spec.js index 506b88ad839..c1ed042a2c8 100644 --- a/test/spec/modules/consentManagement_spec.js +++ b/test/spec/modules/consentManagement_spec.js @@ -1,18 +1,17 @@ import { - setConsentConfig, - requestBidsHook, - resetConsentData, - userCMP, - consentTimeout, actionTimeout, - staticConsentData, + consentTimeout, gdprScope, loadConsentData, - setActionTimeout + requestBidsHook, + resetConsentData, + setConsentConfig, + staticConsentData, + userCMP } from 'modules/consentManagement.js'; -import { gdprDataHandler } from 'src/adapterManager.js'; +import {gdprDataHandler} from 'src/adapterManager.js'; import * as utils from 'src/utils.js'; -import { config } from 'src/config.js'; +import {config} from 'src/config.js'; import 'src/prebid.js'; let expect = require('chai').expect; @@ -146,16 +145,17 @@ describe('consentManagement', function () { }); describe('static consent string setConsentConfig value', () => { - afterEach(() => { - config.resetConfig(); - }); + Object.entries({ + 'getTCData': (cfg) => ({getTCData: cfg}), + 'consent data directly': (cfg) => cfg, + }).forEach(([t, packageCfg]) => { + describe(`using ${t}`, () => { + afterEach(() => { + config.resetConfig(); + }); - it('results in user settings overriding system defaults for v2 spec', () => { - let staticConfig = { - cmpApi: 'static', - timeout: 7500, - consentData: { - getTCData: { + it('results in user settings overriding system defaults for v2 spec', () => { + const consentData = { 'tcString': 'COuqj-POu90rDBcBkBENAZCgAPzAAAPAACiQFwwBAABAA1ADEAbQC4YAYAAgAxAG0A', 'cmpId': 92, 'cmpVersion': 100, @@ -218,18 +218,22 @@ describe('consentManagement', function () { 'legitimateInterests': {} } } - } - } - }; + }; - setConsentConfig(staticConfig); - expect(userCMP).to.be.equal('static'); - expect(consentTimeout).to.be.equal(0); // should always return without a timeout when config is used - expect(gdprScope).to.be.equal(false); - const consent = gdprDataHandler.getConsentData(); - expect(consent.consentString).to.eql(staticConfig.consentData.getTCData.tcString); - expect(consent.vendorData).to.eql(staticConfig.consentData.getTCData); - expect(staticConsentData).to.be.equal(staticConfig.consentData); + setConsentConfig({ + cmpApi: 'static', + timeout: 7500, + consentData: packageCfg(consentData) + }); + expect(userCMP).to.be.equal('static'); + expect(consentTimeout).to.be.equal(0); // should always return without a timeout when config is used + expect(gdprScope).to.be.equal(false); + const consent = gdprDataHandler.getConsentData(); + expect(consent.consentString).to.eql(consentData.tcString); + expect(consent.vendorData).to.eql(consentData); + expect(staticConsentData).to.be.equal(consentData); + }); + }); }); }); }); @@ -243,9 +247,7 @@ describe('consentManagement', function () { const staticConfig = { cmpApi: 'static', timeout: 7500, - consentData: { - getTCData: {} - } + consentData: {} } let didHookReturn; @@ -484,15 +486,6 @@ describe('consentManagement', function () { testIFramedPage('with/JSON response', false, 'abc12345234', 2); testIFramedPage('with/String response', true, 'abc12345234', 2); - - it('should contain correct v2 CMP definition', (done) => { - setConsentConfig(goodConfig); - requestBidsHook(() => { - const nbArguments = window.__tcfapi.toString().split('\n')[0].split(', ').length; - expect(nbArguments).to.equal(4); - done(); - }, {}); - }); }); }); @@ -685,6 +678,46 @@ describe('consentManagement', function () { }); }); + it('should timeout after actionTimeout from the first CMP event', (done) => { + mockTcfEvent({ + eventStatus: 'cmpuishown', + tcString: 'mock-consent-string', + vendorData: {} + }); + setConsentConfig({ + timeout: 1000, + actionTimeout: 100, + cmpApi: 'iab', + defaultGdprScope: true + }); + let hookRan = false; + requestBidsHook(() => { + hookRan = true; + }, {}); + setTimeout(() => { + expect(hookRan).to.be.true; + done(); + }, 200) + }); + + it('should still pick up consent data when actionTimeout is 0', (done) => { + mockTcfEvent({ + eventStatus: 'tcloaded', + tcString: 'mock-consent-string', + vendorData: {} + }); + setConsentConfig({ + timeout: 1000, + actionTimeout: 0, + cmpApi: 'iab', + defaultGdprScope: true + }); + requestBidsHook(() => { + expect(gdprDataHandler.getConsentData().consentString).to.eql('mock-consent-string'); + done(); + }, {}) + }) + Object.entries({ 'null': null, 'empty': '', @@ -737,41 +770,4 @@ describe('consentManagement', function () { }); }); }); - - describe('actionTimeout', function () { - afterEach(function () { - config.resetConfig(); - resetConsentData(); - }); - - it('should set actionTimeout if present', () => { - setConsentConfig({ - gdpr: { timeout: 5000, actionTimeout: 5500 } - }); - - expect(userCMP).to.be.equal('iab'); - expect(consentTimeout).to.be.equal(5000); - expect(actionTimeout).to.be.equal(5500); - }); - - it('should utilize actionTimeout duration on initial user visit when user action is pending', () => { - const cb = () => {}; - const cmpCallMap = { - 'iab': () => {}, - 'static': () => {} - }; - const timeout = sinon.spy(); - - setConsentConfig({ - gdpr: { timeout: 5000, actionTimeout: 5500 } - }); - loadConsentData(cb, cmpCallMap, timeout); - - sinon.assert.calledWith(timeout, sinon.match.any, 5000); - - setActionTimeout(); - - timeout.lastCall.lastArg === 5500; - }); - }); }); diff --git a/test/spec/modules/contentexchangeBidAdapter_spec.js b/test/spec/modules/contentexchangeBidAdapter_spec.js index 368ca8d9e3f..1b3dc4f19c9 100644 --- a/test/spec/modules/contentexchangeBidAdapter_spec.js +++ b/test/spec/modules/contentexchangeBidAdapter_spec.js @@ -79,7 +79,8 @@ describe('ContentexchangeBidAdapter', function () { gdprConsent: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', refererInfo: { referer: 'https://test.com' - } + }, + timeout: 500 }; describe('isBidRequestValid', function () { diff --git a/test/spec/modules/conversantAnalyticsAdapter_spec.js b/test/spec/modules/conversantAnalyticsAdapter_spec.js index 48b68d09bc7..ce134f7f6af 100644 --- a/test/spec/modules/conversantAnalyticsAdapter_spec.js +++ b/test/spec/modules/conversantAnalyticsAdapter_spec.js @@ -12,6 +12,7 @@ describe('Conversant analytics adapter tests', function() { let sandbox; // sinon sandbox to make restoring all stubbed objects easier let xhr; // xhr stub from sinon for capturing data sent via ajax let clock; // clock stub from sinon to mock our cache cleanup interval + let logInfoStub; const PREBID_VERSION = '1.2'; const SITE_ID = 108060; @@ -21,14 +22,16 @@ describe('Conversant analytics adapter tests', function() { const VALID_CONFIGURATION = { options: { - site_id: SITE_ID + site_id: SITE_ID, + send_error_data: true } }; const VALID_ALWAYS_SAMPLE_CONFIG = { options: { site_id: SITE_ID, - cnvr_sampling: 1 + cnvr_sampling: 1, + send_error_data: true } }; @@ -45,25 +48,32 @@ describe('Conversant analytics adapter tests', function() { }; sandbox.stub(prebidGlobal, 'getGlobal').returns(getGlobalStub); // getGlobal does not seem to be available in testing so need to mock it clock = sandbox.useFakeTimers(DATESTAMP); // to use sinon fake timers they MUST be created before the interval/timeout is created in the code you are testing. + + logInfoStub = sandbox.stub(utils, 'logInfo');/* .callsFake((arg, arg1, arg2) => { //debugging stuff + console.log(arg); + if (arg1) console.log(arg1); + if (arg2) console.log(arg2); + }); */ + + conversantAnalytics.enableAnalytics(VALID_ALWAYS_SAMPLE_CONFIG); }); afterEach(function () { sandbox.restore(); requests = []; // clean up any requests in our ajax request capture array. + conversantAnalytics.disableAnalytics(); }); describe('Initialization Tests', function() { it('should log error if site id is not passed', function() { sandbox.stub(utils, 'logError'); - + conversantAnalytics.disableAnalytics(); conversantAnalytics.enableAnalytics(); expect(utils.logError.calledWith(CNVR_CONSTANTS.LOG_PREFIX + 'siteId is required.')).to.be.true; - conversantAnalytics.disableAnalytics(); }); it('should not log error if valid config is passed', function() { sandbox.stub(utils, 'logError'); - sandbox.stub(utils, 'logInfo'); conversantAnalytics.enableAnalytics(VALID_CONFIGURATION); expect(utils.logError.called).to.equal(false); @@ -78,7 +88,6 @@ describe('Conversant analytics adapter tests', function() { CNVR_CONSTANTS.LOG_PREFIX + 'Global sample rate set to 1' ) ).to.be.true; - conversantAnalytics.disableAnalytics(); }); it('should sample when sampling set to 1', function() { @@ -86,17 +95,16 @@ describe('Conversant analytics adapter tests', function() { conversantAnalytics.enableAnalytics(VALID_ALWAYS_SAMPLE_CONFIG); expect(utils.logError.called).to.equal(false); expect(cnvrHelper.doSample).to.equal(true); - conversantAnalytics.disableAnalytics(); }); it('should NOT sample when sampling set to 0', function() { sandbox.stub(utils, 'logError'); const NEVER_SAMPLE_CONFIG = utils.deepClone(VALID_ALWAYS_SAMPLE_CONFIG); NEVER_SAMPLE_CONFIG['options'].cnvr_sampling = 0; + conversantAnalytics.disableAnalytics(); conversantAnalytics.enableAnalytics(NEVER_SAMPLE_CONFIG); expect(utils.logError.called).to.equal(false); expect(cnvrHelper.doSample).to.equal(false); - conversantAnalytics.disableAnalytics(); }); }); @@ -113,14 +121,19 @@ describe('Conversant analytics adapter tests', function() { cnvrHelper.auctionIdTimestampCache['keep'] = {timeReceived: DATESTAMP + 1}; cnvrHelper.auctionIdTimestampCache['delete'] = {timeReceived: DATESTAMP - CNVR_CONSTANTS.MAX_MILLISECONDS_IN_CACHE}; + cnvrHelper.bidderErrorCache['keep'] = {timeReceived: DATESTAMP + 1, errors: []}; + cnvrHelper.bidderErrorCache['delete'] = {timeReceived: DATESTAMP - CNVR_CONSTANTS.MAX_MILLISECONDS_IN_CACHE, errors: []}; + expect(Object.keys(cnvrHelper.adIdLookup)).to.have.lengthOf(2); expect(Object.keys(cnvrHelper.timeoutCache)).to.have.lengthOf(2); expect(Object.keys(cnvrHelper.auctionIdTimestampCache)).to.have.lengthOf(2); + expect(Object.keys(cnvrHelper.bidderErrorCache)).to.have.lengthOf(2); clock.tick(CNVR_CONSTANTS.CACHE_CLEANUP_TIME_IN_MILLIS); expect(Object.keys(cnvrHelper.adIdLookup)).to.have.lengthOf(1); expect(Object.keys(cnvrHelper.timeoutCache)).to.have.lengthOf(1); expect(Object.keys(cnvrHelper.auctionIdTimestampCache)).to.have.lengthOf(1); + expect(Object.keys(cnvrHelper.bidderErrorCache)).to.have.lengthOf(1); conversantAnalytics.disableAnalytics(); @@ -128,6 +141,7 @@ describe('Conversant analytics adapter tests', function() { expect(Object.keys(cnvrHelper.adIdLookup)).to.have.lengthOf(0); expect(Object.keys(cnvrHelper.timeoutCache)).to.have.lengthOf(0); expect(Object.keys(cnvrHelper.auctionIdTimestampCache)).to.have.lengthOf(0); + expect(Object.keys(cnvrHelper.bidderErrorCache)).to.have.lengthOf(0); }); it('createBid() should return correct object', function() { @@ -173,6 +187,7 @@ describe('Conversant analytics adapter tests', function() { let payload = cnvrHelper.createPayload(REQUEST_TYPE, AUCTION_ID, myDate); expect(payload).to.deep.equal({ + bidderErrors: [], cnvrSampleRate: 1, globalSampleRate: 1, requestType: REQUEST_TYPE, @@ -184,8 +199,6 @@ describe('Conversant analytics adapter tests', function() { }, adUnits: {} }); - - conversantAnalytics.disableAnalytics(); }); it('keyExistsAndIsObject() should return correct data', function() { @@ -236,6 +249,68 @@ describe('Conversant analytics adapter tests', function() { expect(cnvrHelper.getSampleRate(obj, 'not_a_key', DEFAULT_VAL)).to.equal(DEFAULT_VAL); expect(cnvrHelper.getSampleRate(obj, 'too_small', DEFAULT_VAL)).to.equal(0); }); + + it('getPageUrl() should return correct data', function() { + let url = cnvrHelper.getPageUrl(); + expect(url.length).to.be.above(1); + }); + + it('sendErrorData() should send data via ajax', function() { + const error = { + stack: 'foobar', + message: 'foobar message' + }; + const eventType = 'fooType'; + + expect(requests).to.have.lengthOf(0); + cnvrHelper.sendErrorData(eventType, error); + expect(requests).to.have.lengthOf(1); + + expect(requests[0].url).to.contain('cvx/event/prebidanalyticerrors'); + const data = JSON.parse(requests[0].requestBody); + expect(data.event).to.be.equal(eventType); + expect(data.siteId).to.be.equal(SITE_ID); + expect(data.message).to.not.be.undefined; + expect(data.prebidVersion).to.not.be.undefined; + expect(data.userAgent).to.not.be.undefined; + expect(data.url).to.not.be.undefined; + }); + + it('Should not send data when error logging disabled', function() { + const error = { + stack: 'foobar', + message: 'foobar message' + }; + const eventType = 'fooType'; + + conversantAnalytics.disableAnalytics(); + conversantAnalytics.enableAnalytics({ + options: { + site_id: SITE_ID, + cnvr_sampling: 1, + send_error_data: false + } + }); + expect(cnvrHelper.doSendErrorData).to.be.false; + + expect(requests).to.have.lengthOf(0); + cnvrHelper.sendErrorData(eventType, error); + expect(requests).to.have.lengthOf(0); + + conversantAnalytics.disableAnalytics(); + conversantAnalytics.enableAnalytics({ + options: { + site_id: SITE_ID, + cnvr_sampling: 1, + send_error_data: 0 + } + }); + expect(cnvrHelper.doSendErrorData).to.be.false; + + expect(requests).to.have.lengthOf(0); + cnvrHelper.sendErrorData(eventType, error); + expect(requests).to.have.lengthOf(0); + }); }); describe('Bid Timeout Event Tests', function() { @@ -251,14 +326,6 @@ describe('Conversant analytics adapter tests', function() { 'auctionId': 'afbd6e0b-e45b-46ab-87bf-c0bac0cb8881' }]; - beforeEach(function () { - conversantAnalytics.enableAnalytics(VALID_ALWAYS_SAMPLE_CONFIG); - }); - - afterEach(function () { - conversantAnalytics.disableAnalytics(); - }); - it('should put both items in timeout cache', function() { expect(Object.keys(cnvrHelper.timeoutCache)).to.have.lengthOf(0); events.emit(constants.EVENTS.BID_TIMEOUT, BID_TIMEOUT_PAYLOAD); @@ -284,14 +351,6 @@ describe('Conversant analytics adapter tests', function() { message: 'value' }; - beforeEach(function () { - conversantAnalytics.enableAnalytics(VALID_ALWAYS_SAMPLE_CONFIG); - }); - - afterEach(function () { - conversantAnalytics.disableAnalytics(); - }); - it('should empty adIdLookup and send data', function() { cnvrHelper.adIdLookup[RENDER_FAILED_PAYLOAD.adId] = { bidderCode: 'bidderCode', @@ -323,9 +382,18 @@ describe('Conversant analytics adapter tests', function() { expect(Object.keys(cnvrHelper.adIdLookup)).to.have.lengthOf(1); events.emit(constants.EVENTS.AD_RENDER_FAILED, RENDER_FAILED_PAYLOAD_NO_ADID); - expect(requests).to.have.lengthOf(0); + expect(requests).to.have.lengthOf(1); expect(Object.keys(cnvrHelper.adIdLookup)).to.have.lengthOf(1); // same object in cache as before... no change expect(cnvrHelper.adIdLookup[RENDER_FAILED_PAYLOAD.adId]).to.not.be.undefined; + + expect(requests[0].url).to.contain('cvx/event/prebidanalyticerrors'); + const data = JSON.parse(requests[0].requestBody); + expect(data.event).to.be.equal(constants.EVENTS.AD_RENDER_FAILED); + expect(data.siteId).to.be.equal(SITE_ID); + expect(data.message).to.not.be.undefined; + expect(data.prebidVersion).to.not.be.undefined; + expect(data.userAgent).to.not.be.undefined; + expect(data.url).to.not.be.undefined; }); it('should not send data if bad data in lookup', function() { @@ -338,7 +406,16 @@ describe('Conversant analytics adapter tests', function() { expect(Object.keys(cnvrHelper.adIdLookup)).to.have.lengthOf(1); events.emit(constants.EVENTS.AD_RENDER_FAILED, RENDER_FAILED_PAYLOAD); expect(Object.keys(cnvrHelper.adIdLookup)).to.have.lengthOf(0); // object should be removed but no call made to send data - expect(requests).to.have.lengthOf(0); + expect(requests).to.have.lengthOf(1); + + expect(requests[0].url).to.contain('cvx/event/prebidanalyticerrors'); + const data = JSON.parse(requests[0].requestBody); + expect(data.event).to.be.equal(constants.EVENTS.AD_RENDER_FAILED); + expect(data.siteId).to.be.equal(SITE_ID); + expect(data.message).to.not.be.undefined; + expect(data.prebidVersion).to.not.be.undefined; + expect(data.userAgent).to.not.be.undefined; + expect(data.url).to.not.be.undefined; }); }); @@ -414,20 +491,22 @@ describe('Conversant analytics adapter tests', function() { ] }; - beforeEach(function () { - conversantAnalytics.enableAnalytics(VALID_ALWAYS_SAMPLE_CONFIG); - }); - - afterEach(function () { - conversantAnalytics.disableAnalytics(); - }); - it('should not send data or put a record in adIdLookup when bad data provided', function() { expect(requests).to.have.lengthOf(0); expect(Object.keys(cnvrHelper.adIdLookup)).to.have.lengthOf(0); events.emit(constants.EVENTS.BID_WON, BAD_BID_WON_ARGS); - expect(requests).to.have.lengthOf(0); + expect(requests).to.have.lengthOf(1); expect(Object.keys(cnvrHelper.adIdLookup)).to.have.lengthOf(0); + + // check for error event + expect(requests[0].url).to.contain('cvx/event/prebidanalyticerrors'); + const data = JSON.parse(requests[0].requestBody); + expect(data.event).to.be.equal(constants.EVENTS.BID_WON); + expect(data.siteId).to.be.equal(SITE_ID); + expect(data.message).to.not.be.undefined; + expect(data.prebidVersion).to.not.be.undefined; + expect(data.userAgent).to.not.be.undefined; + expect(data.url).to.not.be.undefined; }); it('should send data and put a record in adIdLookup', function() { @@ -834,14 +913,6 @@ describe('Conversant analytics adapter tests', function() { timeout: 3000 }; - beforeEach(function () { - conversantAnalytics.enableAnalytics(VALID_ALWAYS_SAMPLE_CONFIG); - }); - - afterEach(function () { - conversantAnalytics.disableAnalytics(); - }); - it('should not do anything when auction id doesnt exist', function() { sandbox.stub(utils, 'logError'); @@ -849,30 +920,48 @@ describe('Conversant analytics adapter tests', function() { delete BAD_ARGS.auctionId; expect(requests).to.have.lengthOf(0); events.emit(constants.EVENTS.AUCTION_END, BAD_ARGS); - expect(requests).to.have.lengthOf(0); - expect( - utils.logError.calledWith( - CNVR_CONSTANTS.LOG_PREFIX + 'onAuctionEnd(): No auctionId in args supplied so unable to process event.' - ) - ).to.be.true; + expect(requests).to.have.lengthOf(1); + + // check for error event + expect(requests[0].url).to.contain('cvx/event/prebidanalyticerrors'); + const data = JSON.parse(requests[0].requestBody); + expect(data.event).to.be.equal(constants.EVENTS.AUCTION_END); + expect(data.siteId).to.be.equal(SITE_ID); + expect(data.message).to.not.be.undefined; + expect(data.prebidVersion).to.not.be.undefined; + expect(data.userAgent).to.not.be.undefined; + expect(data.url).to.not.be.undefined; }); it('should send the expected data', function() { sandbox.stub(utils, 'logError'); - sandbox.stub(utils, 'logWarn'); /* .callsFake((arg, arg1, arg2) => { //debugging stuff - console.error(arg); - if (arg1) console.error(arg1); - if (arg2) console.error(arg2); - }); */ + sandbox.stub(utils, 'logWarn'); + expect(requests).to.have.lengthOf(0); const AUCTION_ID = AUCTION_END_PAYLOAD.auctionId; const AD_UNIT_CODE = AUCTION_END_PAYLOAD.adUnits[0].code; const AD_UNIT_CODE_NATIVE = AUCTION_END_PAYLOAD.adUnits[2].code; const timeoutKey = cnvrHelper.getLookupKey(AUCTION_ID, AD_UNIT_CODE, 'appnexus'); + const URL = 'some url'; + cnvrHelper.bidderErrorCache[AUCTION_ID] = { + errors: [{ + status: 500, + message: 'error msg', + bidderCode: 'bidderCode', + url: URL, + }, { + status: 501, + message: 'error msg1', + bidderCode: 'bidderCode1', + url: URL, + }], + timeReceived: Date.now() + }; cnvrHelper.timeoutCache[timeoutKey] = { timeReceived: Date.now() }; expect(Object.keys(cnvrHelper.timeoutCache)).to.have.lengthOf(1); expect(utils.logError.called).to.equal(false); expect(Object.keys(cnvrHelper.auctionIdTimestampCache)).to.have.lengthOf(0); + expect(Object.keys(cnvrHelper.bidderErrorCache)).to.have.lengthOf(1); events.emit(constants.EVENTS.AUCTION_END, AUCTION_END_PAYLOAD); expect(utils.logError.called).to.equal(false); @@ -958,6 +1047,69 @@ describe('Conversant analytics adapter tests', function() { expect(apnNativeBid.adSize.w).to.be.undefined; expect(apnNativeBid.adSize.h).to.be.undefined; expect(apnNativeBid.mediaType).to.equal('native'); + + expect(Object.keys(data.bidderErrors)).to.have.lengthOf(2); + expect(data.bidderErrors[0].status).to.equal(500); + expect(data.bidderErrors[0].url).to.equal(URL); + expect(data.bidderErrors[0].message).to.not.be.undefined; + expect(data.bidderErrors[0].bidderCode).to.not.be.undefined; + + expect(data.bidderErrors[1].status).to.equal(501); + expect(data.bidderErrors[1].url).to.equal(URL); + expect(data.bidderErrors[1].message).to.not.be.undefined; + expect(data.bidderErrors[1].bidderCode).to.not.be.undefined; + + expect(Object.keys(cnvrHelper.bidderErrorCache)).to.have.lengthOf(0); + }); + }); + + describe('Bidder Error Tests', function() { + // https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest + const XHR_ERROR_MOCK = { + status: 500, + statusText: 'Internal Server Error' + }; + + // https://docs.prebid.org/dev-docs/bidder-adaptor.html#registering-on-bidder-error + const MOCK_BID_REQUEST = { + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + auctionStart: 1579746300522, + bidderCode: 'myBidderCode', + bidderRequestId: '15246a574e859f', + bids: [{}], + gdprConsent: {consentString: 'BOtmiBKOtmiBKABABAENAFAAAAACeAAA', vendorData: {}, gdprApplies: true}, + refererInfo: { + canonicalUrl: null, + page: 'http://mypage.org?pbjs_debug=true', + domain: 'mypage.org', + ref: null, + numIframes: 0, + reachedTop: true, + isAmp: false, + stack: ['http://mypage.org?pbjs_debug=true'] + } + }; + + it('should record error when bidder_error called', function() { + let warnStub = sandbox.stub(utils, 'logWarn'); + expect(requests).to.have.lengthOf(0); + expect(Object.keys(cnvrHelper.bidderErrorCache)).to.have.lengthOf(0); + expect(warnStub.calledOnce).to.be.false; + + events.emit(constants.EVENTS.BIDDER_ERROR, {'error': XHR_ERROR_MOCK, 'bidderRequest': MOCK_BID_REQUEST}); + expect(Object.keys(cnvrHelper.bidderErrorCache)).to.have.lengthOf(1); + expect(warnStub.calledOnce).to.be.true; + + let errorObj = cnvrHelper.bidderErrorCache[MOCK_BID_REQUEST.auctionId]; + expect(errorObj.errors).to.have.lengthOf(1); + expect(errorObj.errors[0].status).to.equal(XHR_ERROR_MOCK.status); + expect(errorObj.errors[0].message).to.equal(XHR_ERROR_MOCK.statusText); + expect(errorObj.errors[0].bidderCode).to.equal(MOCK_BID_REQUEST.bidderCode); + expect(errorObj.errors[0].url).to.not.be.undefined; + + events.emit(constants.EVENTS.BIDDER_ERROR, {'error': XHR_ERROR_MOCK, 'bidderRequest': MOCK_BID_REQUEST}); + errorObj = cnvrHelper.bidderErrorCache[MOCK_BID_REQUEST.auctionId]; + expect(errorObj.errors).to.have.lengthOf(2); }); }); }); diff --git a/test/spec/modules/conversantBidAdapter_spec.js b/test/spec/modules/conversantBidAdapter_spec.js index c63dc8f9c3b..59ebefa2d60 100644 --- a/test/spec/modules/conversantBidAdapter_spec.js +++ b/test/spec/modules/conversantBidAdapter_spec.js @@ -221,8 +221,9 @@ describe('Conversant adapter tests', function() { it('Verify basic properties', function() { expect(spec.code).to.equal('conversant'); - expect(spec.aliases).to.be.an('array').with.lengthOf(1); + expect(spec.aliases).to.be.an('array').with.lengthOf(2); expect(spec.aliases[0]).to.equal('cnvr'); + expect(spec.aliases[1]).to.equal('epsilon'); expect(spec.supportedMediaTypes).to.be.an('array').with.lengthOf(2); expect(spec.supportedMediaTypes[1]).to.equal('video'); }); @@ -255,6 +256,11 @@ describe('Conversant adapter tests', function() { const bidderRequest = { refererInfo: { page: page + }, + ortb2: { + source: { + tid: 'tid000' + } } }; const request = spec.buildRequests(bidRequests, bidderRequest); @@ -262,7 +268,8 @@ describe('Conversant adapter tests', function() { expect(request.url).to.equal('https://web.hb.ad.cpe.dotomi.com/cvx/client/hb/ortb/25'); const payload = request.data; - expect(payload).to.have.property('id', 'req000'); + expect(payload).to.have.property('id'); + expect(payload.source).to.have.property('tid', 'tid000'); expect(payload).to.have.property('at', 1); expect(payload).to.have.property('imp'); expect(payload.imp).to.be.an('array').with.lengthOf(8); @@ -389,6 +396,14 @@ describe('Conversant adapter tests', function() { expect(payload.device).to.have.property('ua', navigator.userAgent); expect(payload).to.not.have.property('user'); // there should be no user by default + expect(payload).to.not.have.property('tmax'); // there should be no user by default + }); + + it('Verify timeout', () => { + const bidderRequest = { timeout: 9999 }; + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = request.data; + expect(payload.tmax).equals(bidderRequest.timeout); }); it('Verify first party data', () => { diff --git a/test/spec/modules/cpexIdSystem_spec.js b/test/spec/modules/cpexIdSystem_spec.js index e1320d82a6a..6e004c9f8ca 100644 --- a/test/spec/modules/cpexIdSystem_spec.js +++ b/test/spec/modules/cpexIdSystem_spec.js @@ -1,6 +1,6 @@ -import { cpexIdSubmodule, storage } from 'modules/cpexIdSystem.js'; +import { czechAdIdSubmodule, storage } from 'modules/czechAdIdSystem.js'; -describe('cpexId module', function () { +describe('czechAdId module', function () { let getCookieStub; beforeEach(function (done) { @@ -16,23 +16,23 @@ describe('cpexId module', function () { describe('getId()', function () { it('should return the uid when it exists in cookie', function () { - getCookieStub.withArgs('czaid').returns('cpexIdTest'); - const id = cpexIdSubmodule.getId(); - expect(id).to.be.deep.equal({ id: 'cpexIdTest' }); + getCookieStub.withArgs('czaid').returns('czechAdIdTest'); + const id = czechAdIdSubmodule.getId(); + expect(id).to.be.deep.equal({ id: 'czechAdIdTest' }); }); cookieTestCasesForEmpty.forEach(testCase => it('should not return the uid when it doesnt exist in cookie', function () { getCookieStub.withArgs('czaid').returns(testCase); - const id = cpexIdSubmodule.getId(); + const id = czechAdIdSubmodule.getId(); expect(id).to.be.undefined; })); }); describe('decode()', function () { it('should return the uid when it exists in cookie', function () { - getCookieStub.withArgs('czaid').returns('cpexIdTest'); - const decoded = cpexIdSubmodule.decode(); - expect(decoded).to.be.deep.equal({ cpexId: 'cpexIdTest' }); + getCookieStub.withArgs('czaid').returns('czechAdIdTest'); + const decoded = czechAdIdSubmodule.decode(); + expect(decoded).to.be.deep.equal({ czechAdId: 'czechAdIdTest' }); }); }); }); diff --git a/test/spec/modules/criteoBidAdapter_spec.js b/test/spec/modules/criteoBidAdapter_spec.js index da1a0ca7f32..b2f3d64a156 100755 --- a/test/spec/modules/criteoBidAdapter_spec.js +++ b/test/spec/modules/criteoBidAdapter_spec.js @@ -7,12 +7,9 @@ import { ADAPTER_VERSION, canFastBid, getFastBidUrl, FAST_BID_VERSION_CURRENT } from 'modules/criteoBidAdapter.js'; -import { createBid } from 'src/bidfactory.js'; -import CONSTANTS from 'src/constants.json'; import * as utils from 'src/utils.js'; import * as refererDetection from 'src/refererDetection.js'; import { config } from '../../../src/config.js'; -import * as storageManager from 'src/storageManager.js'; import { BANNER, NATIVE, VIDEO } from '../../../src/mediaTypes.js'; describe('The Criteo bidding adapter', function () { @@ -128,19 +125,13 @@ describe('The Criteo bidding adapter', function () { it('forwards ids from cookies', function () { const cookieData = { 'cto_bundle': 'a', - 'cto_sid': 'b', - 'cto_lwid': 'c', - 'cto_idcpy': 'd', - 'cto_optout': 'e' + 'cto_optout': 'b' }; const expectedHashWithCookieData = { ...expectedHash, ...{ bundle: cookieData['cto_bundle'], - localWebId: cookieData['cto_lwid'], - secureIdCookie: cookieData['cto_sid'], - uid: cookieData['cto_idcpy'], optoutCookie: cookieData['cto_optout'] } }; @@ -158,19 +149,13 @@ describe('The Criteo bidding adapter', function () { it('forwards ids from local storage', function () { const localStorageData = { 'cto_bundle': 'a', - 'cto_sid': 'b', - 'cto_lwid': 'c', - 'cto_idcpy': 'd', - 'cto_optout': 'e' + 'cto_optout': 'b' }; const expectedHashWithLocalStorageData = { ...expectedHash, ...{ bundle: localStorageData['cto_bundle'], - localWebId: localStorageData['cto_lwid'], - secureIdCookie: localStorageData['cto_sid'], - uid: localStorageData['cto_idcpy'], optoutCookie: localStorageData['cto_optout'] } }; @@ -319,7 +304,8 @@ describe('The Criteo bidding adapter', function () { placement: 1, minduration: 0, playbackmethod: 1, - startdelay: 0 + startdelay: 0, + plcmt: 1 } }, params: { @@ -618,13 +604,67 @@ describe('The Criteo bidding adapter', function () { config.resetConfig(); }); + it('should properly build a request using random uuid as auction id', function () { + const generateUUIDStub = sinon.stub(utils, 'generateUUID'); + generateUUIDStub.returns('def'); + const bidderRequest = { + }; + const bidRequests = [ + { + bidder: 'criteo', + adUnitCode: 'bid-123', + transactionId: 'transaction-123', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, + params: {} + }, + ]; + const request = spec.buildRequests(bidRequests, bidderRequest); + const ortbRequest = request.data; + expect(ortbRequest.id).to.equal('def'); + generateUUIDStub.restore(); + }); + + it('should properly transmit source.tid if available', function () { + const bidderRequest = { + ortb2: { + source: { + tid: 'abc' + } + } + }; + const bidRequests = [ + { + bidder: 'criteo', + adUnitCode: 'bid-123', + transactionId: 'transaction-123', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, + params: {} + }, + ]; + const request = spec.buildRequests(bidRequests, bidderRequest); + const ortbRequest = request.data; + expect(ortbRequest.source.tid).to.equal('abc'); + }); + it('should properly build a request if refererInfo is not provided', function () { const bidderRequest = {}; const bidRequests = [ { bidder: 'criteo', adUnitCode: 'bid-123', - transactionId: 'transaction-123', + ortb2Imp: { + ext: { + tid: 'transaction-123', + }, + }, mediaTypes: { banner: { sizes: [[728, 90]] @@ -643,7 +683,11 @@ describe('The Criteo bidding adapter', function () { { bidder: 'criteo', adUnitCode: 'bid-123', - transactionId: 'transaction-123', + ortb2Imp: { + ext: { + tid: 'transaction-123', + }, + }, mediaTypes: { banner: { sizes: [[728, 90]] @@ -725,6 +769,42 @@ describe('The Criteo bidding adapter', function () { expect(ortbRequest.slots[0].native).to.equal(true); }); + it('should properly forward eids', function () { + const bidRequests = [ + { + bidder: 'criteo', + adUnitCode: 'bid-123', + transactionId: 'transaction-123', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, + userIdAsEids: [ + { + source: 'criteo.com', + uids: [{ + id: 'abc', + atype: 1 + }] + } + ], + params: {} + }, + ]; + const request = spec.buildRequests(bidRequests, bidderRequest); + const ortbRequest = request.data; + expect(ortbRequest.user.ext.eids).to.deep.equal([ + { + source: 'criteo.com', + uids: [{ + id: 'abc', + atype: 1 + }] + } + ]); + }); + it('should properly detect and forward native flag', function () { const bidRequests = [ { @@ -743,6 +823,62 @@ describe('The Criteo bidding adapter', function () { expect(ortbRequest.slots[0].native).to.equal(true); }); + it('should map ortb native assets to slot ext assets', function () { + const assets = [{ + required: 1, + id: 1, + img: { + type: 3, + wmin: 100, + hmin: 100, + } + }, + { + required: 1, + id: 2, + title: { + len: 140, + } + }, + { + required: 1, + id: 3, + data: { + type: 1, + } + }, + { + required: 0, + id: 4, + data: { + type: 2, + } + }, + { + required: 0, + id: 5, + img: { + type: 1, + wmin: 20, + hmin: 20, + } + }]; + const bidRequests = [ + { + nativeOrtbRequest: { + assets: assets + }, + params: { + nativeCallback: function () { } + }, + }, + ]; + const request = spec.buildRequests(bidRequests, bidderRequest); + const ortbRequest = request.data; + expect(ortbRequest.slots[0].native).to.equal(true); + expect(ortbRequest.slots[0].ext.assets).to.deep.equal(assets); + }); + it('should properly build a networkId request', function () { const bidderRequest = { refererInfo: { @@ -764,7 +900,11 @@ describe('The Criteo bidding adapter', function () { { bidder: 'criteo', adUnitCode: 'bid-123', - transactionId: 'transaction-123', + ortb2Imp: { + ext: { + tid: 'transaction-123', + }, + }, mediaTypes: { banner: { sizes: [[300, 250], [728, 90]] @@ -803,7 +943,11 @@ describe('The Criteo bidding adapter', function () { { bidder: 'criteo', adUnitCode: 'bid-123', - transactionId: 'transaction-123', + ortb2Imp: { + ext: { + tid: 'transaction-123', + }, + }, mediaTypes: { banner: { sizes: [[728, 90]] @@ -816,7 +960,11 @@ describe('The Criteo bidding adapter', function () { { bidder: 'criteo', adUnitCode: 'bid-234', - transactionId: 'transaction-234', + ortb2Imp: { + ext: { + tid: 'transaction-234', + }, + }, mediaTypes: { banner: { sizes: [[300, 250], [728, 90]] @@ -898,6 +1046,67 @@ describe('The Criteo bidding adapter', function () { expect(request.data.user.uspIab).to.equal('1YNY'); }); + it('should properly build a request with device sua field', function () { + const sua = {} + const bidRequests = [ + { + bidder: 'criteo', + adUnitCode: 'bid-123', + transactionId: 'transaction-123', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, + params: { + zoneId: 123, + }, + }, + ]; + const bidderRequest = { + timeout: 3000, + uspConsent: '1YNY', + ortb2: { + device: { + sua: sua + } + } + }; + + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.user.ext.sua).to.not.be.null; + expect(request.data.user.ext.sua).to.equal(sua); + }); + + it('should properly build a request with gpp consent field', function () { + const bidRequests = [ + { + bidder: 'criteo', + adUnitCode: 'bid-123', + transactionId: 'transaction-123', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, + params: { + zoneId: 123, + }, + }, + ]; + const ortb2 = { + regs: { + gpp: 'gpp_consent_string', + gpp_sid: [0, 1, 2] + } + }; + + const request = spec.buildRequests(bidRequests, { ...bidderRequest, ortb2 }); + expect(request.data.regs).to.not.be.null; + expect(request.data.regs.gpp).to.equal('gpp_consent_string'); + expect(request.data.regs.gpp_sid).to.deep.equal([0, 1, 2]); + }); + it('should properly build a request with schain object', function () { const expectedSchain = { someProperty: 'someValue' @@ -923,6 +1132,90 @@ describe('The Criteo bidding adapter', function () { expect(request.data.source.ext.schain).to.equal(expectedSchain); }); + it('should properly build a request with bcat field', function () { + const bcat = ['IAB1', 'IAB2']; + const bidRequests = [ + { + bidder: 'criteo', + adUnitCode: 'bid-123', + transactionId: 'transaction-123', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, + params: { + zoneId: 123, + }, + }, + ]; + const bidderRequest = { + ortb2: { + bcat + } + }; + + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.bcat).to.not.be.null; + expect(request.data.bcat).to.equal(bcat); + }); + + it('should properly build a request with badv field', function () { + const badv = ['ford.com']; + const bidRequests = [ + { + bidder: 'criteo', + adUnitCode: 'bid-123', + transactionId: 'transaction-123', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, + params: { + zoneId: 123, + }, + }, + ]; + const bidderRequest = { + ortb2: { + badv + } + }; + + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.badv).to.not.be.null; + expect(request.data.badv).to.equal(badv); + }); + + it('should properly build a request with bapp field', function () { + const bapp = ['com.foo.mygame']; + const bidRequests = [ + { + bidder: 'criteo', + adUnitCode: 'bid-123', + transactionId: 'transaction-123', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, + params: { + zoneId: 123, + }, + }, + ]; + const bidderRequest = { + ortb2: { + bapp + } + }; + + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.bapp).to.not.be.null; + expect(request.data.bapp).to.equal(bapp); + }); + it('should properly build a request with if ccpa consent field is not provided', function () { const bidRequests = [ { @@ -961,7 +1254,8 @@ describe('The Criteo bidding adapter', function () { mimes: ['video/mp4', 'video/x-flv'], maxduration: 30, api: [1, 2], - protocols: [2, 3] + protocols: [2, 3], + plcmt: 3 } }, params: { @@ -991,6 +1285,7 @@ describe('The Criteo bidding adapter', function () { expect(ortbRequest.slots[0].video.startdelay).to.equal(5); expect(ortbRequest.slots[0].video.playbackmethod).to.deep.equal([1, 3]); expect(ortbRequest.slots[0].video.placement).to.equal(2); + expect(ortbRequest.slots[0].video.plcmt).to.equal(3); }); it('should properly build a video request with more than one player size', function () { @@ -1441,6 +1736,73 @@ describe('The Criteo bidding adapter', function () { } }); }); + + it('should properly build a request when imp.rwdd is present', function () { + const bidderRequest = {}; + const bidRequests = [ + { + bidder: 'criteo', + adUnitCode: 'bid-123', + transactionId: 'transaction-123', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, + params: { + zoneId: 123, + ext: { + bidfloor: 0.75 + } + }, + ortb2Imp: { + rwdd: 1, + ext: { + data: { + someContextAttribute: 'abc' + } + } + } + }, + ]; + + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.slots[0].rwdd).to.be.not.null; + expect(request.data.slots[0].rwdd).to.equal(1); + }); + + it('should properly build a request when imp.rwdd is false', function () { + const bidderRequest = {}; + const bidRequests = [ + { + bidder: 'criteo', + adUnitCode: 'bid-123', + transactionId: 'transaction-123', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, + params: { + zoneId: 123, + ext: { + bidfloor: 0.75 + } + }, + ortb2Imp: { + rwdd: 0, + ext: { + data: { + someContextAttribute: 'abc' + } + } + } + }, + ]; + + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.slots[0].rwdd).to.be.undefined; + }); }); describe('interpretResponse', function () { @@ -1461,8 +1823,13 @@ describe('The Criteo bidding adapter', function () { creativecode: 'test-crId', width: 728, height: 90, - dealCode: 'myDealCode', + deal: 'myDealCode', adomain: ['criteo.com'], + ext: { + meta: { + networkName: 'Criteo' + } + } }], }, }; @@ -1485,6 +1852,7 @@ describe('The Criteo bidding adapter', function () { expect(bids[0].height).to.equal(90); expect(bids[0].dealId).to.equal('myDealCode'); expect(bids[0].meta.advertiserDomains[0]).to.equal('criteo.com'); + expect(bids[0].meta.networkName).to.equal('Criteo'); }); it('should properly parse a bid response with a zoneId', function () { @@ -1551,6 +1919,51 @@ describe('The Criteo bidding adapter', function () { expect(bids[0].mediaType).to.equal(VIDEO); }); + it('should properly parse a bid response with a outstream video', function () { + const response = { + body: { + slots: [{ + impid: 'test-requestId', + bidId: 'abc123', + cpm: 1.23, + displayurl: 'http://test-ad', + width: 728, + height: 90, + zoneid: 123, + video: true, + ext: { + videoPlayerType: 'RadiantMediaPlayer', + videoPlayerConfig: { + + } + } + }], + }, + }; + const request = { + bidRequests: [{ + adUnitCode: 'test-requestId', + bidId: 'test-bidId', + params: { + zoneId: 123, + }, + mediaTypes: { + video: { + context: 'outstream' + } + } + }] + }; + const bids = spec.interpretResponse(response, request); + expect(bids).to.have.lengthOf(1); + expect(bids[0].requestId).to.equal('test-bidId'); + expect(bids[0].cpm).to.equal(1.23); + expect(bids[0].vastUrl).to.equal('http://test-ad'); + expect(bids[0].renderer.url).to.equal('https://static.criteo.net/js/ld/publishertag.renderer.js'); + expect(typeof bids[0].renderer.config.documentResolver).to.equal('function'); + expect(typeof bids[0].renderer._render).to.equal('function'); + }); + it('should properly parse a bid response with native', function () { const response = { body: { @@ -2114,7 +2527,7 @@ describe('The Criteo bidding adapter', function () { const adapters = { Prebid: function () { } }; const adaptersMock = sinon.mock(adapters); - adaptersMock.expects('Prebid').withExactArgs(PROFILE_ID_PUBLISHERTAG, ADAPTER_VERSION, bidRequests, bidderRequest, '$prebid.version$').once().returns(prebidAdapter); + adaptersMock.expects('Prebid').withExactArgs(PROFILE_ID_PUBLISHERTAG, ADAPTER_VERSION, bidRequests, bidderRequest, '$prebid.version$', sinon.match.any).once().returns(prebidAdapter); global.Criteo = { PubTag: { diff --git a/test/spec/modules/criteoIdSystem_spec.js b/test/spec/modules/criteoIdSystem_spec.js index 76d5222c8b2..aaf63873d93 100644 --- a/test/spec/modules/criteoIdSystem_spec.js +++ b/test/spec/modules/criteoIdSystem_spec.js @@ -1,5 +1,6 @@ import { criteoIdSubmodule, storage } from 'modules/criteoIdSystem.js'; import * as utils from 'src/utils.js'; +import { gdprDataHandler, uspDataHandler, gppDataHandler } from '../../../src/adapterManager.js'; import { server } from '../../mocks/xhr'; const pastDateString = new Date(0).toString() @@ -17,6 +18,9 @@ describe('CriteoId module', function () { let timeStampStub; let parseUrlStub; let triggerPixelStub; + let gdprConsentDataStub; + let uspConsentDataStub; + let gppConsentDataStub; beforeEach(function (done) { getCookieStub = sinon.stub(storage, 'getCookie'); @@ -27,6 +31,9 @@ describe('CriteoId module', function () { timeStampStub = sinon.stub(utils, 'timestamp').returns(nowTimestamp); parseUrlStub = sinon.stub(utils, 'parseUrl').returns({ protocol: 'https', hostname: 'testdev.com' }) triggerPixelStub = sinon.stub(utils, 'triggerPixel'); + gdprConsentDataStub = sinon.stub(gdprDataHandler, 'getConsentData'); + uspConsentDataStub = sinon.stub(uspDataHandler, 'getConsentData'); + gppConsentDataStub = sinon.stub(gppDataHandler, 'getConsentData'); done(); }); @@ -39,6 +46,9 @@ describe('CriteoId module', function () { timeStampStub.restore(); triggerPixelStub.restore(); parseUrlStub.restore(); + gdprConsentDataStub.restore(); + uspConsentDataStub.restore(); + gppConsentDataStub.restore(); }); const storageTestCases = [ @@ -136,11 +146,11 @@ describe('CriteoId module', function () { })); const gdprConsentTestCases = [ - { consentData: { gdprApplies: true, consentString: 'expectedConsentString' }, expected: 'expectedConsentString' }, - { consentData: { gdprApplies: false, consentString: 'expectedConsentString' }, expected: undefined }, - { consentData: { gdprApplies: true, consentString: undefined }, expected: undefined }, - { consentData: { gdprApplies: 'oui', consentString: 'expectedConsentString' }, expected: undefined }, - { consentData: undefined, expected: undefined } + { consentData: { gdprApplies: true, consentString: 'expectedConsentString' }, expectedGdprConsent: 'expectedConsentString', expectedGdpr: '1' }, + { consentData: { gdprApplies: false, consentString: 'expectedConsentString' }, expectedGdprConsent: 'expectedConsentString', expectedGdpr: '0' }, + { consentData: { gdprApplies: true, consentString: undefined }, expectedGdprConsent: undefined, expectedGdpr: '1' }, + { consentData: { gdprApplies: 'oui', consentString: 'expectedConsentString' }, expectedGdprConsent: 'expectedConsentString', expectedGdpr: '0' }, + { consentData: undefined, expectedGdprConsent: undefined, expectedGdpr: undefined } ]; it('should call sync pixels if request by backend', function () { @@ -185,16 +195,134 @@ describe('CriteoId module', function () { expect(setLocalStorageStub.calledWith('cto_pixel_test', 'ok')).to.be.true; }); + it('should call sync pixels and use error handler', function () { + const expirationTs = new Date(nowTimestamp + cookiesMaxAge).toString(); + + const result = criteoIdSubmodule.getId(); + result.callback((id) => { + }); + + const response = { + pixels: [ + { + pixelUrl: 'pixelUrlWithBundle', + writeBundleInStorage: true, + bundlePropertyName: 'abc', + storageKeyName: 'cto_pixel_test' + } + ] + }; + + server.requests[0].respond( + 200, + { 'Content-Type': 'application/json' }, + JSON.stringify(response) + ); + + server.requests[1].respond( + 500, + { 'Content-Type': 'application/json' }, + JSON.stringify({ + abc: 'ok' + }) + ); + + expect(triggerPixelStub.called).to.be.false; + expect(setCookieStub.calledWith('cto_pixel_test', 'ok', expirationTs, null, '.com')).to.be.false; + expect(setCookieStub.calledWith('cto_pixel_test', 'ok', expirationTs, null, '.testdev.com')).to.be.false; + expect(setLocalStorageStub.calledWith('cto_pixel_test', 'ok')).to.be.false; + }); + gdprConsentTestCases.forEach(testCase => it('should call user sync url with the gdprConsent', function () { let callBackSpy = sinon.spy(); - let result = criteoIdSubmodule.getId(undefined, testCase.consentData); + + gdprConsentDataStub.returns(testCase.consentData); + + let result = criteoIdSubmodule.getId(undefined); + result.callback(callBackSpy); + + let request = server.requests[0]; + + if (testCase.expectedGdprConsent) { + expect(request.url).to.have.string(`gdprString=${testCase.expectedGdprConsent}`); + } else { + expect(request.url).to.not.have.string('gdprString='); + } + + if (testCase.expectedGdpr) { + expect(request.url).to.have.string(`gdpr=${testCase.expectedGdpr}`); + } else { + expect(request.url).to.not.have.string('gdpr='); + } + + request.respond( + 200, + { 'Content-Type': 'application/json' }, + JSON.stringify({}) + ); + + expect(callBackSpy.calledOnce).to.be.true; + })); + + [undefined, 'abc'].forEach(usPrivacy => it('should call user sync url with the us privacy string', function () { + let callBackSpy = sinon.spy(); + + uspConsentDataStub.returns(usPrivacy); + + let result = criteoIdSubmodule.getId(undefined); + result.callback(callBackSpy); + + let request = server.requests[0]; + + if (usPrivacy) { + expect(request.url).to.have.string(`us_privacy=${usPrivacy}`); + } else { + expect(request.url).to.not.have.string('us_privacy='); + } + + request.respond( + 200, + { 'Content-Type': 'application/json' }, + JSON.stringify({}) + ); + + expect(callBackSpy.calledOnce).to.be.true; + })); + + [ + { + consentData: { + gppString: 'abc', + applicableSections: [1] + }, + expectedGpp: 'abc', + expectedGppSid: '1' + }, + { + consentData: undefined, + expectedGpp: undefined, + expectedGppSid: undefined + } + ].forEach(testCase => it('should call user sync url with the gpp string', function () { + let callBackSpy = sinon.spy(); + + gppConsentDataStub.returns(testCase.consentData); + + let result = criteoIdSubmodule.getId(undefined); result.callback(callBackSpy); let request = server.requests[0]; - if (testCase.expected) { - expect(request.url).to.have.string(`gdprString=${testCase.expected}`); + + if (testCase.expectedGpp) { + expect(request.url).to.have.string(`gpp=${testCase.expectedGpp}`); + } else { + expect(request.url).to.not.have.string('gpp='); + } + + if (testCase.expectedGppSid) { + expect(request.url).to.have.string(`gpp_sid=${testCase.expectedGppSid}`); } else { - expect(request.url).to.not.have.string('gdprString'); + expect(request.url).to.not.have.string('gpp_sid='); } request.respond( diff --git a/test/spec/modules/currency_spec.js b/test/spec/modules/currency_spec.js index b674cb5976d..88c640e38cc 100644 --- a/test/spec/modules/currency_spec.js +++ b/test/spec/modules/currency_spec.js @@ -366,15 +366,15 @@ describe('currency', function () { expect(innerBid.cpm).to.equal(1); }); - it('should result in NO_BID when currency support is not enabled and fromCurrency is not USD', function () { + it('should reject bid when currency support is not enabled and fromCurrency is not USD', function () { setConfig({}); var bid = makeBid({ 'cpm': 1, 'currency': 'GBP' }); - var innerBid; + let bidAdded = false; addBidResponseHook(function(adCodeId, bid) { - innerBid = bid; + bidAdded = true; }, 'elementId', bid, reject); - expect(innerBid.status).to.equal('rejected'); + expect(bidAdded).to.be.false; expect(reject.calledOnce).to.be.true; }); @@ -390,7 +390,7 @@ describe('currency', function () { expect(bid).to.equal(innerBid); }); - it('should result in NO_BID when fromCurrency is not supported in file', function () { + it('should reject bid when fromCurrency is not supported in file', function () { // RESET to request currency file setConfig({ 'adServerCurrency': undefined }); @@ -398,24 +398,24 @@ describe('currency', function () { setConfig({ 'adServerCurrency': 'JPY' }); fakeCurrencyFileServer.respond(); var bid = makeBid({ 'cpm': 1, 'currency': 'ABC' }); - var innerBid; + let bidAdded = false; addBidResponseHook(function(adCodeId, bid) { - innerBid = bid; + bidAdded = true; }, 'elementId', bid, reject); - expect(innerBid.status).to.equal('rejected'); + expect(bidAdded).to.be.false; expect(reject.calledOnce).to.be.true; }); - it('should result in NO_BID when adServerCurrency is not supported in file', function () { + it('should reject bid when adServerCurrency is not supported in file', function () { fakeCurrencyFileServer.respondWith(JSON.stringify(getCurrencyRates())); setConfig({ 'adServerCurrency': 'ABC' }); fakeCurrencyFileServer.respond(); var bid = makeBid({ 'cpm': 1, 'currency': 'GBP' }); - var innerBid; + let bidAdded = false; addBidResponseHook(function(adCodeId, bid) { - innerBid = bid; + bidAdded = true; }, 'elementId', bid, reject); - expect(innerBid.status).to.equal('rejected'); + expect(bidAdded).to.be.false; expect(reject.calledOnce).to.be.true; }); diff --git a/test/spec/modules/datablocksBidAdapter_spec.js b/test/spec/modules/datablocksBidAdapter_spec.js index 8e203510b10..811aaab6ebb 100644 --- a/test/spec/modules/datablocksBidAdapter_spec.js +++ b/test/spec/modules/datablocksBidAdapter_spec.js @@ -2,7 +2,7 @@ import { expect } from 'chai'; import { spec } from '../../../modules/datablocksBidAdapter.js'; import { BotClientTests } from '../../../modules/datablocksBidAdapter.js'; import { getStorageManager } from '../../../src/storageManager.js'; -export let storage = getStorageManager(); +import {deepClone} from '../../../src/utils.js'; const bid = { bidId: '2dd581a2b6281d', @@ -410,7 +410,7 @@ describe('DatablocksAdapter', function() { expect(spec.isBidRequestValid(bid)).to.be.true; }); it('Should return false when host/source_id is not set', function() { - let moddedBid = Object.assign({}, bid); + let moddedBid = deepClone(bid); delete moddedBid.params.source_id; expect(spec.isBidRequestValid(moddedBid)).to.be.false; }); @@ -443,9 +443,12 @@ describe('DatablocksAdapter', function() { }); describe('buildRequests', function() { - let request = spec.buildRequests([bid, bid2, nativeBid], bidderRequest); + let request; + before(() => { + request = spec.buildRequests([bid, bid2, nativeBid], bidderRequest); + expect(request).to.exist; + }) - expect(request).to.exist; it('Returns POST method', function() { expect(request.method).to.exist; expect(request.method).to.equal('POST'); diff --git a/test/spec/modules/datawrkzBidAdapter_spec.js b/test/spec/modules/datawrkzBidAdapter_spec.js index 3ddc2bad1cf..5524e318600 100644 --- a/test/spec/modules/datawrkzBidAdapter_spec.js +++ b/test/spec/modules/datawrkzBidAdapter_spec.js @@ -462,7 +462,6 @@ describe('datawrkzAdapterTests', function () { expect(result[0].ad).to.equal(decodeURIComponent(serverResponse.body.seatbid[0].bid[0].adm + '')); expect(result[0].creativeId).to.equal('1'); expect(result[0].bidderCode).to.equal(request.bidRequest.bidder); - expect(result[0].transactionId).to.equal(request.bidRequest.transactionId); expect(result[0].mediaType).to.equal('banner'); }); @@ -540,7 +539,6 @@ describe('datawrkzAdapterTests', function () { expect(result[0].native.desc).to.equal('Test body'); expect(result[0].creativeId).to.equal('1'); expect(result[0].bidderCode).to.equal(request.bidRequest.bidder); - expect(result[0].transactionId).to.equal(request.bidRequest.transactionId); expect(result[0].mediaType).to.equal('native'); }); @@ -610,7 +608,6 @@ describe('datawrkzAdapterTests', function () { expect(result[0].adserverTargeting.hb_protocol).to.equal('3'); expect(result[0].creativeId).to.equal('1'); expect(result[0].bidderCode).to.equal(request.bidRequest.bidder); - expect(result[0].transactionId).to.equal(request.bidRequest.transactionId); expect(result[0].mediaType).to.equal('video'); }); @@ -649,7 +646,6 @@ describe('datawrkzAdapterTests', function () { expect(result[0].renderer).to.exist; expect(result[0].creativeId).to.equal('1'); expect(result[0].bidderCode).to.equal(request.bidRequest.bidder); - expect(result[0].transactionId).to.equal(request.bidRequest.transactionId); expect(result[0].mediaType).to.equal('video'); }); }); diff --git a/test/spec/modules/dfpAdServerVideo_spec.js b/test/spec/modules/dfpAdServerVideo_spec.js index 943b7cc0162..89485adf28b 100644 --- a/test/spec/modules/dfpAdServerVideo_spec.js +++ b/test/spec/modules/dfpAdServerVideo_spec.js @@ -1,7 +1,7 @@ import { expect } from 'chai'; import parse from 'url-parse'; -import { buildDfpVideoUrl, buildAdpodVideoUrl } from 'modules/dfpAdServerVideo.js'; +import {buildDfpVideoUrl, buildAdpodVideoUrl, dep} from 'modules/dfpAdServerVideo.js'; import adUnit from 'test/fixtures/video/adUnit.json'; import * as utils from 'src/utils.js'; import { config } from 'src/config.js'; @@ -13,6 +13,7 @@ import { server } from 'test/mocks/xhr.js'; import * as adServer from 'src/adserver.js'; import {deepClone} from 'src/utils.js'; import {hook} from '../../../src/hook.js'; +import {getRefererInfo} from '../../../src/refererDetection.js'; const bid = { videoCacheKey: 'abc', @@ -27,6 +28,40 @@ describe('The DFP video support module', function () { hook.ready(); }); + let sandbox; + + beforeEach(() => { + sandbox = sinon.sandbox.create(); + }); + + afterEach(() => { + sandbox.restore(); + }); + + Object.entries({ + params: { + params: { + 'iu': 'my/adUnit' + } + }, + url: { + url: 'https://some-example-url.com' + } + }).forEach(([t, options]) => { + describe(`when using ${t}`, () => { + it('should use page location as default for description_url', () => { + sandbox.stub(dep, 'ri').callsFake(() => ({page: 'example.com'})); + + const url = parse(buildDfpVideoUrl(Object.assign({ + adUnit: adUnit, + bid: bid, + }, options))); + const prm = utils.parseQS(url.query); + expect(prm.description_url).to.eql('example.com'); + }) + }) + }) + it('should make a legal request URL when given the required params', function () { const url = parse(buildDfpVideoUrl({ adUnit: adUnit, @@ -63,9 +98,6 @@ describe('The DFP video support module', function () { })); expect(url.host).to.equal('video.adserver.example'); - - const queryObject = utils.parseQS(url.query); - expect(queryObject.description_url).to.equal('vastUrl.example'); }); it('requires a params object or url', function () { diff --git a/test/spec/modules/dianomiBidAdapter_spec.js b/test/spec/modules/dianomiBidAdapter_spec.js index 61509913c41..0838762d750 100644 --- a/test/spec/modules/dianomiBidAdapter_spec.js +++ b/test/spec/modules/dianomiBidAdapter_spec.js @@ -162,7 +162,7 @@ describe('Dianomi adapter', () => { }, ]; let request = JSON.parse( - spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' }, auctionId: 'tid' }).data + spec.buildRequests(validBidRequests, {refererInfo: {page: 'page'}, ortb2: {source: {tid: 'tid'}}}).data ); assert.equal(request.source.tid, 'tid'); diff --git a/test/spec/modules/dspxBidAdapter_spec.js b/test/spec/modules/dspxBidAdapter_spec.js index 2869385d7e7..841fc087613 100644 --- a/test/spec/modules/dspxBidAdapter_spec.js +++ b/test/spec/modules/dspxBidAdapter_spec.js @@ -1,6 +1,7 @@ import { expect } from 'chai'; import { spec } from 'modules/dspxBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; +import { deepClone } from '../../../src/utils'; const ENDPOINT_URL = 'https://buyer.dspx.tv/request/'; const ENDPOINT_URL_DEV = 'https://dcbuyer.dspx.tv/request/'; @@ -65,7 +66,34 @@ describe('dspxAdapter', function () { 'adUnitCode': 'testDiv1', 'userId': { 'netId': '123', - 'uid2': '456' + 'uid2': {'id': '456'}, + 'pubcid': 'e09ab6a3-ae74-4f01-b2e8-81b141d6dc61', + 'id5id': { + 'uid': 'ID5-ZHMOcvSShIBZiIth_yYh9odjNFxVEmMQ_i5TArPfWw!ID5*dtrjfV5mPLasyya5TW2IE9oVzQZwx7xRPGyAYS4hcWkAAOoxoFef4bIoREpQys8x', + 'ext': { + 'linkType': 2 + } + }, + 'sharedid': { + 'id': '01EXPPGZ9C8NKG1MTXVHV98505', + 'third': '01EXPPGZ9C8NKG1MTXVHV98505' + } + }, + 'crumbs': { + 'pubcid': 'e09ab6a3-ae74-4f01-b2e8-81b141d6dc61' + }, + 'schain': { + 'ver': '1.0', + 'complete': 1, + 'nodes': [ + { + 'asi': 'example.com', + 'sid': '0', + 'hp': 1, + 'rid': 'bidrequestid', + 'domain': 'example.com' + } + ] } }, { @@ -111,7 +139,10 @@ describe('dspxAdapter', function () { 'mediaTypes': { 'video': { 'playerSize': [640, 480], - 'context': 'instream' + 'context': 'instream', + 'protocols': [1, 2], + 'playbackmethod': [2], + 'skip': 1 }, 'banner': { 'sizes': [ @@ -135,14 +166,53 @@ describe('dspxAdapter', function () { 'mediaTypes': { 'video': { 'playerSize': [640, 480], - 'context': 'instream' + 'context': 'instream', + 'protocols': [1, 2], + 'playbackmethod': [2], + 'skip': 1, + 'renderer': { + url: 'example.com/videoRenderer.js', + render: function (bid) { alert('test'); } + } } }, 'bidId': '30b31c1838de1e41', 'bidderRequestId': '22edbae2733bf67', 'auctionId': '1d1a030790a478', 'adUnitCode': 'testDiv4' - } + }, + { + 'bidder': 'dspx', + 'params': { + 'placement': '101', + 'devMode': true, + 'dev': { + 'endpoint': 'http://localhost', + 'placement': '107', + 'pfilter': {'test': 1} + } + }, + 'mediaTypes': { + 'video': { + 'playerSize': [640, 480], + 'context': 'instream', + 'mimes': ['video/mp4'], + 'protocols': [1, 2], + 'playbackmethod': [2], + 'skip': 1 + }, + 'banner': { + 'sizes': [ + [300, 250] + ] + } + }, + + 'bidId': '30b31c1838de1e4', + 'bidderRequestId': '22edbae2733bf67', + 'auctionId': '1d1a030790a478', + 'adUnitCode': 'testDiv3' + }, ]; @@ -163,7 +233,7 @@ describe('dspxAdapter', function () { expect(request1.method).to.equal('GET'); expect(request1.url).to.equal(ENDPOINT_URL); let data = request1.data.replace(/rnd=\d+\&/g, '').replace(/ref=.*\&bid/g, 'bid').replace(/pbver=.*?&/g, 'pbver=test&'); - expect(data).to.equal('_f=auto&alternative=prebid_js&inventory_item_id=6682&srw=300&srh=250&idt=100&bid_id=30b31c1838de1e1&pbver=test&pfilter%5Bfloorprice%5D=1000000&pfilter%5Bprivate_auction%5D=0&pfilter%5Bgeo%5D%5Bcountry%5D=DE&pfilter%5Bgdpr_consent%5D=BOJ%2FP2HOJ%2FP2HABABMAAAAAZ%2BA%3D%3D&pfilter%5Bgdpr%5D=true&bcat=IAB2%2CIAB4&dvt=desktop&did_netid=123&did_uid2=456&auctionId=1d1a030790a475&pbcode=testDiv1&media_types%5Bbanner%5D=300x250'); + expect(data).to.equal('_f=auto&alternative=prebid_js&inventory_item_id=6682&srw=300&srh=250&idt=100&bid_id=30b31c1838de1e1&pbver=test&pfilter%5Bfloorprice%5D=1000000&pfilter%5Bprivate_auction%5D=0&pfilter%5Bgeo%5D%5Bcountry%5D=DE&pfilter%5Bgdpr_consent%5D=BOJ%2FP2HOJ%2FP2HABABMAAAAAZ%2BA%3D%3D&pfilter%5Bgdpr%5D=true&bcat=IAB2%2CIAB4&dvt=desktop&did_netid=123&did_id5=ID5-ZHMOcvSShIBZiIth_yYh9odjNFxVEmMQ_i5TArPfWw!ID5*dtrjfV5mPLasyya5TW2IE9oVzQZwx7xRPGyAYS4hcWkAAOoxoFef4bIoREpQys8x&did_id5_linktype=2&did_uid2=456&did_sharedid=01EXPPGZ9C8NKG1MTXVHV98505&did_pubcid=e09ab6a3-ae74-4f01-b2e8-81b141d6dc61&did_cpubcid=e09ab6a3-ae74-4f01-b2e8-81b141d6dc61&schain%5Bver%5D=1.0&schain%5Bcomplete%5D=1&schain%5Bnodes%5D%5B0%5D%5Basi%5D=example.com&schain%5Bnodes%5D%5B0%5D%5Bsid%5D=0&schain%5Bnodes%5D%5B0%5D%5Bhp%5D=1&schain%5Bnodes%5D%5B0%5D%5Brid%5D=bidrequestid&schain%5Bnodes%5D%5B0%5D%5Bdomain%5D=example.com&auctionId=1d1a030790a475&pbcode=testDiv1&media_types%5Bbanner%5D=300x250'); }); var request2 = spec.buildRequests([bidRequests[1]], bidderRequest)[0]; @@ -193,14 +263,61 @@ describe('dspxAdapter', function () { expect(request4.method).to.equal('GET'); expect(request4.url).to.equal(ENDPOINT_URL_DEV); let data = request4.data.replace(/rnd=\d+\&/g, '').replace(/ref=.*\&bid/g, 'bid').replace(/pbver=.*?&/g, 'pbver=test&'); - expect(data).to.equal('_f=auto&alternative=prebid_js&inventory_item_id=101&srw=300&srh=250&idt=100&bid_id=30b31c1838de1e4&pbver=test&prebidDevMode=1&auctionId=1d1a030790a478&pbcode=testDiv3&media_types%5Bvideo%5D=640x480&media_types%5Bbanner%5D=300x250'); + expect(data).to.equal('_f=auto&alternative=prebid_js&inventory_item_id=101&srw=300&srh=250&idt=100&bid_id=30b31c1838de1e4&pbver=test&prebidDevMode=1&auctionId=1d1a030790a478&pbcode=testDiv3&media_types%5Bvideo%5D=640x480&media_types%5Bbanner%5D=300x250&vctx=instream&vpl%5Bprotocols%5D%5B0%5D=1&vpl%5Bprotocols%5D%5B1%5D=2&vpl%5Bplaybackmethod%5D%5B0%5D=2&vpl%5Bskip%5D=1'); }); var request5 = spec.buildRequests([bidRequests[4]], bidderRequestWithoutGdpr)[0]; it('sends bid video request to our endpoint via GET', function () { expect(request5.method).to.equal('GET'); let data = request5.data.replace(/rnd=\d+\&/g, '').replace(/ref=.*\&bid/g, 'bid').replace(/pbver=.*?&/g, 'pbver=test&'); - expect(data).to.equal('_f=auto&alternative=prebid_js&inventory_item_id=101&srw=640&srh=480&idt=100&bid_id=30b31c1838de1e41&pbver=test&vf=vast4&prebidDevMode=1&auctionId=1d1a030790a478&pbcode=testDiv4&media_types%5Bvideo%5D=640x480'); + expect(data).to.equal('_f=auto&alternative=prebid_js&inventory_item_id=101&srw=640&srh=480&idt=100&bid_id=30b31c1838de1e41&pbver=test&prebidDevMode=1&auctionId=1d1a030790a478&pbcode=testDiv4&media_types%5Bvideo%5D=640x480&vctx=instream&vf=vast4&vpl%5Bprotocols%5D%5B0%5D=1&vpl%5Bprotocols%5D%5B1%5D=2&vpl%5Bplaybackmethod%5D%5B0%5D=2&vpl%5Bskip%5D=1'); + }); + + var request6 = spec.buildRequests([bidRequests[5]], bidderRequestWithoutGdpr)[0]; + it('sends bid request without gdprConsent to our DEV endpoint with overriden DEV params via GET', function () { + expect(request6.method).to.equal('GET'); + expect(request6.url).to.equal('http://localhost'); + let data = request6.data.replace(/rnd=\d+\&/g, '').replace(/ref=.*\&bid/g, 'bid').replace(/pbver=.*?&/g, 'pbver=test&'); + expect(data).to.equal('_f=auto&alternative=prebid_js&inventory_item_id=107&srw=300&srh=250&idt=100&bid_id=30b31c1838de1e4&pbver=test&pfilter%5Btest%5D=1&prebidDevMode=1&auctionId=1d1a030790a478&pbcode=testDiv3&media_types%5Bvideo%5D=640x480&media_types%5Bbanner%5D=300x250&vctx=instream&vpl%5Bmimes%5D%5B0%5D=video%2Fmp4&vpl%5Bprotocols%5D%5B0%5D=1&vpl%5Bprotocols%5D%5B1%5D=2&vpl%5Bplaybackmethod%5D%5B0%5D=2&vpl%5Bskip%5D=1'); + }); + + // bidfloor tests + const getFloorResponse = {currency: 'EUR', floor: 5}; + let testBidRequest = deepClone(bidRequests[1]); + let floorRequest = spec.buildRequests([testBidRequest], bidderRequestWithoutGdpr)[0]; + + // 1. getBidFloor not exist AND bidfloor not exist - no floorprice in request + it('bidfloor is not exists in request', function () { + expect(floorRequest.data).to.not.contain('floorprice'); + }); + + // 2. getBidFloor not exist AND pfilter.floorprice exist - use pfilter.floorprice property + it('bidfloor is equal 0.5', function () { + testBidRequest = deepClone(bidRequests[0]); + testBidRequest.params.pfilter = { + 'floorprice': 0.5 + }; + floorRequest = spec.buildRequests([testBidRequest], bidderRequestWithoutGdpr)[0]; + expect(floorRequest.data).to.contain('floorprice%5D=0.5'); + }); + + // 3. getBidFloor exist AND pfilter.floorprice not exist - use getFloor method + it('bidfloor is equal 5', function () { + testBidRequest = deepClone(bidRequests[1]); + testBidRequest.getFloor = () => getFloorResponse; + floorRequest = spec.buildRequests([testBidRequest], bidderRequestWithoutGdpr)[0]; + expect(floorRequest.data).to.contain('floorprice%5D=5'); + }); + + // 4. getBidFloor exist AND pfilter.floorprice exist -> use getFloor method + it('bidfloor is equal 0.35', function () { + testBidRequest = deepClone(bidRequests[0]); + testBidRequest.getFloor = () => getFloorResponse; + testBidRequest.params.pfilter = { + 'floorprice': 0.35 + }; + floorRequest = spec.buildRequests([testBidRequest], bidderRequestWithoutGdpr)[0]; + expect(floorRequest.data).to.contain('floorprice%5D=0.35'); }); }); @@ -212,7 +329,7 @@ describe('dspxAdapter', function () { 'width': '300', 'height': '250', 'type': 'sspHTML', - 'tag': '', + 'adTag': '', 'requestId': '220ed41385952a', 'currency': 'EUR', 'ttl': 60, @@ -233,7 +350,25 @@ describe('dspxAdapter', function () { 'currency': 'EUR', 'ttl': 60, 'netRevenue': true, - 'zone': '6682' + 'zone': '6682', + 'renderer': {id: 1, url: '//player.example.com', options: {}} + } + }; + let serverVideoResponseVastUrl = { + 'body': { + 'cpm': 5000000, + 'crid': 100500, + 'width': '300', + 'height': '250', + 'requestId': '220ed41385952a', + 'type': 'vast2', + 'currency': 'EUR', + 'ttl': 60, + 'netRevenue': true, + 'zone': '6682', + 'vastUrl': 'https://local/vasturl1', + 'videoCacheKey': 'cache_123', + 'bid_appendix': {'someField': 'someValue'} } }; @@ -246,10 +381,10 @@ describe('dspxAdapter', function () { dealId: '', currency: 'EUR', netRevenue: true, - ttl: 300, + ttl: 60, type: 'sspHTML', ad: '', - meta: {advertiserDomains: ['bdomain']} + meta: {advertiserDomains: ['bdomain']}, }, { requestId: '23beaa6af6cdde', cpm: 0.5, @@ -263,7 +398,24 @@ describe('dspxAdapter', function () { type: 'vast2', vastXml: '{"reason":7001,"status":"accepted"}', mediaType: 'video', - meta: {advertiserDomains: []} + meta: {advertiserDomains: []}, + renderer: {} + }, { + requestId: '23beaa6af6cdde', + cpm: 0.5, + width: 0, + height: 0, + creativeId: 100500, + dealId: '', + currency: 'EUR', + netRevenue: true, + ttl: 60, + type: 'vast2', + vastUrl: 'https://local/vasturl1', + videoCacheKey: 'cache_123', + mediaType: 'video', + meta: {advertiserDomains: []}, + someField: 'someValue' }]; it('should get the correct bid response by display ad', function () { @@ -287,7 +439,7 @@ describe('dspxAdapter', function () { 'mediaTypes': { 'video': { 'playerSize': [640, 480], - 'context': 'instream' + 'context': 'outstream' } }, 'data': { @@ -299,6 +451,25 @@ describe('dspxAdapter', function () { expect(result[0].meta.advertiserDomains.length).to.equal(0); }); + it('should get the correct dspx video bid response by display ad (vastUrl)', function () { + let bidRequest = [{ + 'method': 'GET', + 'url': ENDPOINT_URL, + 'mediaTypes': { + 'video': { + 'playerSize': [640, 480], + 'context': 'instream' + } + }, + 'data': { + 'bid_id': '30b31c1838de1e' + } + }]; + let result = spec.interpretResponse(serverVideoResponseVastUrl, bidRequest[0]); + expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[2])); + expect(result[0].meta.advertiserDomains.length).to.equal(0); + }); + it('handles empty bid response', function () { let response = { body: {} diff --git a/test/spec/modules/eids_spec.js b/test/spec/modules/eids_spec.js index 13f89b054bd..9291ec88569 100644 --- a/test/spec/modules/eids_spec.js +++ b/test/spec/modules/eids_spec.js @@ -172,6 +172,105 @@ describe('eids array generation for known sub-modules', function() { }); }); + it('bidswitch', function() { + const userId = { + bidswitch: {'id': 'sample_id'} + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'bidswitch.net', + uids: [{ + id: 'sample_id', + atype: 3 + }] + }); + }); + + it('bidswitch with ext', function() { + const userId = { + bidswitch: {'id': 'sample_id', 'ext': {'provider': 'some.provider.com'}} + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'bidswitch.net', + uids: [{ + id: 'sample_id', + atype: 3, + ext: { + provider: 'some.provider.com' + } + }] + }); + }); + + it('medianet', function() { + const userId = { + medianet: {'id': 'sample_id'} + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'media.net', + uids: [{ + id: 'sample_id', + atype: 3 + }] + }); + }); + + it('medianet with ext', function() { + const userId = { + medianet: {'id': 'sample_id', 'ext': {'provider': 'some.provider.com'}} + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'media.net', + uids: [{ + id: 'sample_id', + atype: 3, + ext: { + provider: 'some.provider.com' + } + }] + }); + }); + + it('magnite', function() { + const userId = { + magnite: {'id': 'sample_id'} + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'rubiconproject.com', + uids: [{ + id: 'sample_id', + atype: 3 + }] + }); + }); + + it('magnite with ext', function() { + const userId = { + magnite: {'id': 'sample_id', 'ext': {'provider': 'some.provider.com'}} + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'rubiconproject.com', + uids: [{ + id: 'sample_id', + atype: 3, + ext: { + provider: 'some.provider.com' + } + }] + }); + }); + it('liveIntentId; getValue call and NO ext', function() { const userId = { lipb: { @@ -318,6 +417,39 @@ describe('eids array generation for known sub-modules', function() { }); }); + it('uid2 with ext', function() { + const userId = { + uid2: {'id': 'Sample_AD_Token', 'ext': {'provider': 'some.provider.com'}} + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'uidapi.com', + uids: [{ + id: 'Sample_AD_Token', + atype: 3, + ext: { + provider: 'some.provider.com' + } + }] + }); + }); + + it('euid', function() { + const userId = { + euid: {'id': 'Sample_AD_Token'} + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'euid.eu', + uids: [{ + id: 'Sample_AD_Token', + atype: 3 + }] + }); + }); + it('kpuid', function() { const userId = { kpuid: 'Sample_Token' @@ -432,9 +564,9 @@ describe('eids array generation for known sub-modules', function() { }); }); - it('cpexId', () => { + it('czechAdId', () => { const id = 'some-random-id-value' - const userId = { cpexId: id }; + const userId = { czechAdId: id }; const [eid] = createEidsArray(userId); expect(eid).to.deep.equal({ source: 'czechadid.cz', diff --git a/test/spec/modules/emtvBidAdapter_spec.js b/test/spec/modules/emtvBidAdapter_spec.js new file mode 100644 index 00000000000..4f95a0cc094 --- /dev/null +++ b/test/spec/modules/emtvBidAdapter_spec.js @@ -0,0 +1,400 @@ +import { expect } from 'chai'; +import { spec } from '../../../modules/emtvBidAdapter.js'; +import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; +import { getUniqueIdentifierStr } from '../../../src/utils.js'; + +const bidder = 'emtv' +const adUrl = 'https://us-east-ep.engagemedia.tv/pbjs'; +const syncUrl = 'https://cs.engagemedia.tv'; + +describe('EMTVBidAdapter', function () { + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + placementId: 'testBanner', + } + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [VIDEO]: { + playerSize: [[300, 300]], + minduration: 5, + maxduration: 60 + } + }, + params: { + placementId: 'testVideo', + } + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [NATIVE]: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + } + }, + params: { + placementId: 'testNative', + } + } + ]; + + const invalidBid = { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + + } + } + + const bidderRequest = { + uspConsent: '1---', + gdprConsent: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + refererInfo: { + referer: 'https://test.com' + } + }; + + describe('isBidRequestValid', function () { + it('Should return true if there are bidId, params and key parameters present', function () { + expect(spec.isBidRequestValid(bids[0])).to.be.true; + }); + it('Should return false if at least one of parameters is not present', function () { + expect(spec.isBidRequestValid(invalidBid)).to.be.false; + }); + }); + + describe('buildRequests', function () { + let serverRequest = spec.buildRequests(bids, bidderRequest); + + it('Creates a ServerRequest object with method, URL and data', function () { + expect(serverRequest).to.exist; + expect(serverRequest.method).to.exist; + expect(serverRequest.url).to.exist; + expect(serverRequest.data).to.exist; + }); + + it('Returns POST method', function () { + expect(serverRequest.method).to.equal('POST'); + }); + + it('Returns valid URL', function () { + expect(serverRequest.url).to.equal(adUrl); + }); + + it('Returns general data valid', function () { + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.all.keys('deviceWidth', + 'deviceHeight', + 'language', + 'secure', + 'host', + 'page', + 'placements', + 'coppa', + 'ccpa', + 'gdpr', + 'tmax' + ); + expect(data.deviceWidth).to.be.a('number'); + expect(data.deviceHeight).to.be.a('number'); + expect(data.language).to.be.a('string'); + expect(data.secure).to.be.within(0, 1); + expect(data.host).to.be.a('string'); + expect(data.page).to.be.a('string'); + expect(data.coppa).to.be.a('number'); + expect(data.gdpr).to.be.a('string'); + expect(data.ccpa).to.be.a('string'); + expect(data.tmax).to.be.a('number'); + expect(data.placements).to.have.lengthOf(3); + }); + + it('Returns valid placements', function () { + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.placementId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('publisher'); + + if (placement.adFormat === BANNER) { + expect(placement.sizes).to.be.an('array'); + } + switch (placement.adFormat) { + case BANNER: + expect(placement.sizes).to.be.an('array'); + break; + case VIDEO: + expect(placement.playerSize).to.be.an('array'); + expect(placement.minduration).to.be.an('number'); + expect(placement.maxduration).to.be.an('number'); + break; + case NATIVE: + expect(placement.native).to.be.an('object'); + break; + } + } + }); + + it('Returns data with gdprConsent and without uspConsent', function () { + delete bidderRequest.uspConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data.gdpr).to.exist; + expect(data.gdpr).to.be.a('string'); + expect(data.gdpr).to.equal(bidderRequest.gdprConsent); + expect(data.ccpa).to.not.exist; + delete bidderRequest.gdprConsent; + }); + + it('Returns data with uspConsent and without gdprConsent', function () { + bidderRequest.uspConsent = '1---'; + delete bidderRequest.gdprConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data.ccpa).to.exist; + expect(data.ccpa).to.be.a('string'); + expect(data.ccpa).to.equal(bidderRequest.uspConsent); + expect(data.gdpr).to.not.exist; + }); + + it('Returns empty data if no valid requests are passed', function () { + serverRequest = spec.buildRequests([], bidderRequest); + let data = serverRequest.data; + expect(data.placements).to.be.an('array').that.is.empty; + }); + }); + + describe('interpretResponse', function () { + it('Should interpret banner response', function () { + const banner = { + body: [{ + mediaType: 'banner', + width: 300, + height: 250, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + let bannerResponses = spec.interpretResponse(banner); + expect(bannerResponses).to.be.an('array').that.is.not.empty; + let dataItem = bannerResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); + expect(dataItem.requestId).to.equal(banner.body[0].requestId); + expect(dataItem.cpm).to.equal(banner.body[0].cpm); + expect(dataItem.width).to.equal(banner.body[0].width); + expect(dataItem.height).to.equal(banner.body[0].height); + expect(dataItem.ad).to.equal(banner.body[0].ad); + expect(dataItem.ttl).to.equal(banner.body[0].ttl); + expect(dataItem.creativeId).to.equal(banner.body[0].creativeId); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal(banner.body[0].currency); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + it('Should interpret video response', function () { + const video = { + body: [{ + vastUrl: 'test.com', + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + let videoResponses = spec.interpretResponse(video); + expect(videoResponses).to.be.an('array').that.is.not.empty; + + let dataItem = videoResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'vastUrl', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.5); + expect(dataItem.vastUrl).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + it('Should interpret native response', function () { + const native = { + body: [{ + mediaType: 'native', + native: { + clickUrl: 'test.com', + title: 'Test', + image: 'test.com', + impressionTrackers: ['test.com'], + }, + ttl: 120, + cpm: 0.4, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + let nativeResponses = spec.interpretResponse(native); + expect(nativeResponses).to.be.an('array').that.is.not.empty; + + let dataItem = nativeResponses[0]; + expect(dataItem).to.have.keys('requestId', 'cpm', 'ttl', 'creativeId', 'netRevenue', 'currency', 'mediaType', 'native', 'meta'); + expect(dataItem.native).to.have.keys('clickUrl', 'impressionTrackers', 'title', 'image') + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.4); + expect(dataItem.native.clickUrl).to.equal('test.com'); + expect(dataItem.native.title).to.equal('Test'); + expect(dataItem.native.image).to.equal('test.com'); + expect(dataItem.native.impressionTrackers).to.be.an('array').that.is.not.empty; + expect(dataItem.native.impressionTrackers[0]).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + it('Should return an empty array if invalid banner response is passed', function () { + const invBanner = { + body: [{ + width: 300, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + + let serverResponses = spec.interpretResponse(invBanner); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid video response is passed', function () { + const invVideo = { + body: [{ + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let serverResponses = spec.interpretResponse(invVideo); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid native response is passed', function () { + const invNative = { + body: [{ + mediaType: 'native', + clickUrl: 'test.com', + title: 'Test', + impressionTrackers: ['test.com'], + ttl: 120, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + }] + }; + let serverResponses = spec.interpretResponse(invNative); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid response is passed', function () { + const invalid = { + body: [{ + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let serverResponses = spec.interpretResponse(invalid); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + }); + + describe('getUserSyncs', function() { + it('Should return array of objects with proper sync config , include GDPR', function() { + const syncData = spec.getUserSyncs({}, {}, { + consentString: 'ALL', + gdprApplies: true, + }, {}); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object') + expect(syncData[0].type).to.be.a('string') + expect(syncData[0].type).to.equal('image') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal(`${syncUrl}/image?pbjs=1&gdpr=1&gdpr_consent=ALL&coppa=0`) + }); + it('Should return array of objects with proper sync config , include CCPA', function() { + const syncData = spec.getUserSyncs({}, {}, {}, { + consentString: '1---' + }); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object') + expect(syncData[0].type).to.be.a('string') + expect(syncData[0].type).to.equal('image') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal(`${syncUrl}/image?pbjs=1&ccpa_consent=1---&coppa=0`) + }); + }); +}); diff --git a/test/spec/modules/eplanningBidAdapter_spec.js b/test/spec/modules/eplanningBidAdapter_spec.js index cb8393a29b8..1a6cfd7afe4 100644 --- a/test/spec/modules/eplanningBidAdapter_spec.js +++ b/test/spec/modules/eplanningBidAdapter_spec.js @@ -729,11 +729,46 @@ describe('E-Planning Adapter', function () { expect(ur).to.equal(bidderRequest.refererInfo.page); }); + it('should return ur parameter without params query string when current window url length is greater than 255', function () { + let bidderRequestParams = bidderRequest; + + bidderRequestParams.refererInfo.page = refererUrl + '?param=' + 'x'.repeat(255); + const ur = spec.buildRequests(bidRequests, bidderRequest).data.ur; + expect(ur).to.equal(refererUrl); + }); + + it('should return ur parameter with a length of 255 when url length is greater than 255', function () { + let bidderRequestParams = bidderRequest; + let url_255_characters = 'https://localhost/abc' + '/subse'.repeat(39); + let refererUrl = url_255_characters + '/ext'.repeat(5) + '?param=' + 'x'.repeat(15); + + bidderRequestParams.refererInfo.page = refererUrl; + const ur = spec.buildRequests(bidRequests, bidderRequest).data.ur; + expect(ur).to.equal(url_255_characters); + }); + it('should return fr parameter when there is a referrer', function () { const request = spec.buildRequests(bidRequests, bidderRequest); const dataRequest = request.data; expect(dataRequest.fr).to.equal(refererUrl); }); + it('should return fr parameter without params query string when ref length is greater than 255', function () { + let bidderRequestParams = bidderRequest; + + bidderRequestParams.refererInfo.ref = refererUrl + '?param=' + 'x'.repeat(255); + const fr = spec.buildRequests(bidRequests, bidderRequest).data.fr; + expect(fr).to.equal(refererUrl); + }); + + it('should return fr parameter with a length of 255 when url length is greater than 255', function () { + let bidderRequestParams = bidderRequest; + let url_255_characters = 'https://localhost/abc' + '/subse'.repeat(39); + let refererUrl = url_255_characters + '/ext'.repeat(5) + '?param=' + 'x'.repeat(15); + + bidderRequestParams.refererInfo.ref = refererUrl; + const fr = spec.buildRequests(bidRequests, bidderRequest).data.fr; + expect(fr).to.equal(url_255_characters); + }); it('should return crs parameter with document charset', function () { let expected; diff --git a/test/spec/modules/eskimiBidAdapter_spec.js b/test/spec/modules/eskimiBidAdapter_spec.js new file mode 100644 index 00000000000..d01240c86ab --- /dev/null +++ b/test/spec/modules/eskimiBidAdapter_spec.js @@ -0,0 +1,307 @@ +import { expect } from 'chai'; +import { spec } from 'modules/eskimiBidAdapter.js'; +import * as utils from 'src/utils'; + +const BANNER_BID = { + bidder: 'eskimi', + params: { + placementId: 1003000 + }, + mediaTypes: { + banner: { + sizes: [ + [300, 250] + ], + }, + }, + adUnitCode: 'adUnitCode1', + bidId: 'bidId', + bidderRequestId: 'bidderRequestId', + auctionId: 'auctionId-56a2-4f71-9098-720a68f2f708', +}; + +const VIDEO_BID = { + bidder: 'eskimi', + params: { + placementId: 1003000 + }, + mediaTypes: { + video: { + context: 'outstream', + api: [1, 2, 4, 6], + mimes: ['video/mp4'], + playbackmethod: [2, 4, 6], + playerSize: [[1024, 768]], + protocols: [3, 4, 7, 8, 10], + placement: 1, + minduration: 0, + maxduration: 60, + startdelay: 0 + }, + }, + adUnitCode: 'adUnitCode1', + bidId: 'bidId', + bidderRequestId: 'bidderRequestId', + auctionId: 'auctionId-56a2-4f71-9098-720a68f2f708', +}; + +const BIDDER_REQUEST = { + auctionId: 'auctionId-56a2-4f71-9098-720a68f2f708', + bidderRequestId: 'bidderRequestId', + timeout: 3000, + refererInfo: { + page: 'https://hello-world-page.com/', + domain: 'hello-world-page.com', + ref: 'http://example-domain.com/foo', + } +}; + +const BANNER_BID_RESPONSE = { + 'id': 'bidderRequestId', + 'bidId': 'bidid', + 'seatbid': [ + { + 'bid': [ + { + 'id': 'id', + 'impid': 'bidId', + 'price': 0.18, + 'adm': '', + 'adid': '144762342', + 'burl': 'http://0.0.0.0:8181/burl', + 'adomain': [ + 'https://dummydomain.com' + ], + 'cid': 'cid', + 'crid': 'crid', + 'iurl': 'iurl', + 'cat': [], + 'w': 300, + 'h': 250 + } + ] + } + ], + 'cur': 'USD' +}; + +const VIDEO_BID_RESPONSE = { + 'id': 'bidderRequestId', + 'bidid': 'bidid', + 'seatbid': [ + { + 'bid': [ + { + 'id': 'id', + 'impid': 'bidId', + 'price': 1.09, + 'adid': '144762342', + 'burl': 'http://0.0.0.0:8181/burl', + 'adm': '', + 'adomain': [ + 'https://dummydomain.com' + ], + 'cid': 'cid', + 'crid': 'crid', + 'iurl': 'iurl', + 'cat': [], + 'h': 768, + 'w': 1024 + } + ] + } + ], + 'cur': 'USD' +}; + +describe('Eskimi bid adapter', function () { + describe('isBidRequestValid()', function () { + it('should accept request if placementId is passed', function () { + let bid = { + bidder: 'eskimi', + params: { + placementId: 123 + }, + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + } + }; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should reject requests without params', function () { + let bid = { + bidder: 'eskimi', + params: {} + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return true when required params found', () => { + expect(spec.isBidRequestValid(BANNER_BID)).to.equal(true); + expect(spec.isBidRequestValid(VIDEO_BID)).to.equal(true); + }); + }); + + describe('buildRequests()', function () { + it('should have gdpr data if applicable', function () { + const bid = utils.deepClone(BANNER_BID); + + const req = Object.assign({}, BIDDER_REQUEST, { + gdprConsent: { + consentString: 'consentString', + gdprApplies: true, + } + }); + let request = spec.buildRequests([bid], req)[0]; + + const payload = request.data; + expect(payload.user.ext).to.have.property('consent', req.gdprConsent.consentString); + expect(payload.regs.ext).to.have.property('gdpr', 1); + }); + + it('should properly forward ORTB blocking params', function () { + let bid = utils.deepClone(BANNER_BID); + bid = utils.mergeDeep(bid, { + params: { bcat: ['IAB1-1'], badv: ['example.com'], bapp: ['com.example'] }, + mediaTypes: { banner: { battr: [1] } } + }); + + let [request] = spec.buildRequests([bid], BIDDER_REQUEST); + + expect(request).to.exist.and.to.be.an('object'); + const payload = request.data; + expect(payload).to.have.deep.property('bcat', ['IAB1-1']); + expect(payload).to.have.deep.property('badv', ['example.com']); + expect(payload).to.have.deep.property('bapp', ['com.example']); + expect(payload.imp[0].banner).to.have.deep.property('battr', [1]); + }); + + context('when mediaType is banner', function () { + it('should build correct request for banner bid with both w, h', () => { + const bid = utils.deepClone(BANNER_BID); + + const [request] = spec.buildRequests([bid], BIDDER_REQUEST); + const requestData = request.data; + + expect(requestData.imp[0].banner.w).to.equal(300); + expect(requestData.imp[0].banner.h).to.equal(250); + }); + + it('should create request data', function () { + const bid = utils.deepClone(BANNER_BID); + + let [request] = spec.buildRequests([bid], BIDDER_REQUEST); + expect(request).to.exist.and.to.be.a('object'); + const payload = request.data; + expect(payload.imp[0]).to.have.property('id', bid.bidId); + }); + }); + + context('when mediaType is video', function () { + it('should return false when there is no video in mediaTypes', () => { + const bid = utils.deepClone(VIDEO_BID); + delete bid.mediaTypes.video; + + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should reutrn false if player size is not set', () => { + const bid = utils.deepClone(VIDEO_BID); + delete bid.mediaTypes.video.playerSize; + + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should use bidder video params if they are set', () => { + const videoBidWithParams = utils.deepClone(VIDEO_BID); + const bidderVideoParams = { + api: [1, 2], + mimes: ['video/mp4', 'video/x-flv'], + playbackmethod: [3, 4], + protocols: [5, 6], + placement: 1, + minduration: 0, + maxduration: 60, + w: 1024, + h: 768, + startdelay: 0 + }; + + videoBidWithParams.params.video = bidderVideoParams; + + const requests = spec.buildRequests([videoBidWithParams], BIDDER_REQUEST); + const request = requests[0].data; + + expect(request.imp[0]).to.deep.include({ + video: { + ...bidderVideoParams, + w: videoBidWithParams.mediaTypes.video.playerSize[0][0], + h: videoBidWithParams.mediaTypes.video.playerSize[0][1], + }, + }); + }); + }); + }); + + describe('interpretResponse()', function () { + context('when mediaType is banner', function () { + it('should correctly interpret valid banner response', function () { + const bid = utils.deepClone(BANNER_BID); + const [request] = spec.buildRequests([bid], BIDDER_REQUEST); + const response = utils.deepClone(BANNER_BID_RESPONSE); + + const bids = spec.interpretResponse({ body: response }, request); + expect(bids).to.be.an('array').that.is.not.empty; + + expect(bids[0].mediaType).to.equal('banner'); + expect(bids[0].burl).to.equal(BANNER_BID_RESPONSE.seatbid[0].bid[0].burl); + expect(bids[0].currency).to.equal('USD'); + expect(bids[0].requestId).to.equal(BANNER_BID_RESPONSE.seatbid[0].bid[0].impid); + expect(bids[0].cpm).to.equal(BANNER_BID_RESPONSE.seatbid[0].bid[0].price); + expect(bids[0].width).to.equal(BANNER_BID_RESPONSE.seatbid[0].bid[0].w); + expect(bids[0].height).to.equal(BANNER_BID_RESPONSE.seatbid[0].bid[0].h); + expect(bids[0].ad).to.equal(BANNER_BID_RESPONSE.seatbid[0].bid[0].adm); + expect(bids[0].creativeId).to.equal(BANNER_BID_RESPONSE.seatbid[0].bid[0].crid); + expect(bids[0].meta.advertiserDomains[0]).to.equal('https://dummydomain.com'); + expect(bids[0].ttl).to.equal(30); + expect(bids[0].netRevenue).to.equal(true); + }); + + it('should handle empty bid response', function () { + const bid = utils.deepClone(BANNER_BID); + + let request = spec.buildRequests([bid], BIDDER_REQUEST)[0]; + const EMPTY_RESP = Object.assign({}, BANNER_BID_RESPONSE, { 'body': {} }); + const bids = spec.interpretResponse(EMPTY_RESP, request); + expect(bids).to.be.empty; + }); + }); + if (FEATURES.VIDEO) { + context('when mediaType is video', function () { + it('should correctly interpret valid instream video response', () => { + const bid = utils.deepClone(VIDEO_BID); + + const [request] = spec.buildRequests([bid], BIDDER_REQUEST); + const bids = spec.interpretResponse({ body: VIDEO_BID_RESPONSE }, request); + expect(bids).to.be.an('array').that.is.not.empty; + + expect(bids[0].mediaType).to.equal('video'); + expect(bids[0].burl).to.equal(VIDEO_BID_RESPONSE.seatbid[0].bid[0].burl); + expect(bids[0].currency).to.equal('USD'); + expect(bids[0].requestId).to.equal(VIDEO_BID_RESPONSE.seatbid[0].bid[0].impid); + expect(bids[0].cpm).to.equal(VIDEO_BID_RESPONSE.seatbid[0].bid[0].price); + expect(bids[0].width).to.equal(VIDEO_BID_RESPONSE.seatbid[0].bid[0].w); + expect(bids[0].height).to.equal(VIDEO_BID_RESPONSE.seatbid[0].bid[0].h); + expect(bids[0].vastXml).to.equal(VIDEO_BID_RESPONSE.seatbid[0].bid[0].adm); + expect(bids[0].creativeId).to.equal(VIDEO_BID_RESPONSE.seatbid[0].bid[0].crid); + expect(bids[0].meta.advertiserDomains[0]).to.equal('https://dummydomain.com'); + expect(bids[0].ttl).to.equal(30); + expect(bids[0].netRevenue).to.equal(true); + }); + }); + } + }); +}); diff --git a/test/spec/modules/etargetBidAdapter_spec.js b/test/spec/modules/etargetBidAdapter_spec.js index 55394dcdeea..a950100d612 100644 --- a/test/spec/modules/etargetBidAdapter_spec.js +++ b/test/spec/modules/etargetBidAdapter_spec.js @@ -173,7 +173,6 @@ describe('etarget adapter', function () { assert.isNotNull(result.reason); assert.equal(result.ttl, 360); assert.equal(result.ad, ''); - assert.equal(result.transactionId, '5f33781f-9552-4ca1'); }); it('should set correct netRevenue', function () { @@ -296,7 +295,11 @@ describe('etarget adapter', function () { tid: 45, placementCode: placementCode[0], sizes: [[300, 250], [250, 300], [300, 600], [600, 300]], - transactionId: '5f33781f-9552-4ca1' + ortb2Imp: { + ext: { + tid: '5f33781f-9552-4ca1' + } + } }, { adUnitCode: placementCode[1], @@ -307,7 +310,11 @@ describe('etarget adapter', function () { params: params[1], placementCode: placementCode[1], sizes: [[300, 250], [250, 300], [300, 600], [600, 300]], - transactionId: '5f33781f-9552-4iuy' + ortb2Imp: { + ext: { + tid: '5f33781f-9552-4iuy' + } + } }, { adUnitCode: placementCode[2], @@ -318,7 +325,11 @@ describe('etarget adapter', function () { params: params[2], placementCode: placementCode[2], sizes: [[300, 250], [250, 300], [300, 600], [600, 300]], - transactionId: '5f33781f-9552-7ev3' + ortb2Imp: { + ext: { + tid: '5f33781f-9552-7ev3' + } + } }, { adUnitCode: placementCode[3], @@ -329,7 +340,11 @@ describe('etarget adapter', function () { params: params[2], placementCode: placementCode[2], sizes: [], - transactionId: '5f33781f-9552-7ev3' + ortb2Imp: { + ext: { + tid: '5f33781f-9552-7ev3' + } + } }, { adUnitCode: placementCode[4], @@ -340,7 +355,11 @@ describe('etarget adapter', function () { params: params[2], placementCode: placementCode[2], sizes: [], - transactionId: '5f33781f-9552-7ev3' + ortb2Imp: { + ext: { + tid: '5f33781f-9552-7ev3' + } + } }, { adUnitCode: placementCode[4], @@ -351,7 +370,11 @@ describe('etarget adapter', function () { params: params[3], placementCode: placementCode[2], sizes: [], - transactionId: '5f33781f-9552-7ev3' + ortb2Imp: { + ext: { + tid: '5f33781f-9552-7ev3' + } + } }, { adUnitCode: placementCode[4], @@ -362,7 +385,11 @@ describe('etarget adapter', function () { params: params[4], placementCode: placementCode[2], sizes: [], - transactionId: '5f33781f-9552-7ev3' + ortb2Imp: { + ext: { + tid: '5f33781f-9552-7ev3' + } + } } ]; serverResponse = { diff --git a/test/spec/modules/euidIdSystem_spec.js b/test/spec/modules/euidIdSystem_spec.js new file mode 100644 index 00000000000..9a016b0facd --- /dev/null +++ b/test/spec/modules/euidIdSystem_spec.js @@ -0,0 +1,123 @@ +import {coreStorage, init, setSubmoduleRegistry, requestBidsHook} from 'modules/userId/index.js'; +import {config} from 'src/config.js'; +import * as utils from 'src/utils.js'; +import { euidIdSubmodule } from 'modules/euidIdSystem.js'; +import 'modules/consentManagement.js'; +import 'src/prebid.js'; +import { getGlobal } from 'src/prebidGlobal.js'; +import { server } from 'test/mocks/xhr.js'; +import { configureTimerInterceptors } from 'test/mocks/timers.js'; +import { cookieHelpers, runAuction, apiHelpers, setGdprApplies } from './uid2IdSystem_helpers.js'; +import {hook} from 'src/hook.js'; +import {uninstall as uninstallGdprEnforcement} from 'modules/gdprEnforcement.js'; + +let expect = require('chai').expect; + +// N.B. Most of the EUID code is shared with UID2 - the tests here only cover the happy path. +// Most of the functionality is covered by the UID2 tests. + +const moduleCookieName = '__euid_advertising_token'; +const publisherCookieName = '__EUID_SERVER_COOKIE'; +const initialToken = `initial-advertising-token`; +const legacyToken = 'legacy-advertising-token'; +const refreshedToken = 'refreshed-advertising-token'; +const auctionDelayMs = 10; + +const makeEuidIdentityContainer = (token) => ({euid: {id: token}}); +const useLocalStorage = true; +const makePrebidConfig = (params = null, extraSettings = {}, debug = false) => ({ + userSync: { auctionDelay: auctionDelayMs, userIds: [{name: 'euid', params: {storage: useLocalStorage ? 'localStorage' : 'cookie', ...params}, ...extraSettings}] }, debug +}); + +const apiUrl = 'https://prod.euid.eu/v2/token/refresh'; +const headers = { 'Content-Type': 'application/json' }; +const makeSuccessResponseBody = () => btoa(JSON.stringify({ status: 'success', body: { ...apiHelpers.makeTokenResponse(initialToken), advertising_token: refreshedToken } })); +const configureEuidResponse = (httpStatus, response) => server.respondWith('POST', apiUrl, (xhr) => xhr.respond(httpStatus, headers, response)); +const expectToken = (bid, token) => expect(bid?.userId ?? {}).to.deep.include(makeEuidIdentityContainer(token)); +const expectNoIdentity = (bid) => expect(bid).to.not.haveOwnProperty('userId'); + +describe('EUID module', function() { + let suiteSandbox, testSandbox, timerSpy, fullTestTitle, restoreSubtleToUndefined = false; + before(function() { + uninstallGdprEnforcement(); + hook.ready(); + suiteSandbox = sinon.sandbox.create(); + if (typeof window.crypto.subtle === 'undefined') { + restoreSubtleToUndefined = true; + window.crypto.subtle = { importKey: () => {}, decrypt: () => {} }; + } + suiteSandbox.stub(window.crypto.subtle, 'importKey').callsFake(() => Promise.resolve()); + suiteSandbox.stub(window.crypto.subtle, 'decrypt').callsFake((settings, key, data) => Promise.resolve(new Uint8Array([...settings.iv, ...data]))); + }); + after(function() { + suiteSandbox.restore(); + if (restoreSubtleToUndefined) window.crypto.subtle = undefined; + }); + beforeEach(function() { + init(config); + setSubmoduleRegistry([euidIdSubmodule]); + }); + afterEach(function() { + $$PREBID_GLOBAL$$.requestBids.removeAll(); + config.resetConfig(); + cookieHelpers.clearCookies(moduleCookieName, publisherCookieName); + coreStorage.removeDataFromLocalStorage(moduleCookieName); + }); + + it('When a server-only token value is provided in config, it is available to the auction.', async function() { + setGdprApplies(true); + config.setConfig(makePrebidConfig(null, {value: makeEuidIdentityContainer(initialToken)})); + const bid = await runAuction(); + expectToken(bid, initialToken); + }); + + it('When a server-only token is provided in the module storage cookie but consent is not available, it is not available to the auction.', async function() { + setGdprApplies(); + coreStorage.setCookie(moduleCookieName, legacyToken, cookieHelpers.getFutureCookieExpiry()); + config.setConfig({userSync: {auctionDelay: auctionDelayMs, userIds: [{name: 'euid'}]}}); + const bid = await runAuction(); + expectNoIdentity(bid); + }); + + it('When a server-only token is provided in the module storage cookie, it is available to the auction.', async function() { + setGdprApplies(true); + coreStorage.setCookie(moduleCookieName, legacyToken, cookieHelpers.getFutureCookieExpiry()); + config.setConfig({userSync: {auctionDelay: auctionDelayMs, userIds: [{name: 'euid'}]}}); + const bid = await runAuction(); + expectToken(bid, legacyToken); + }) + + it('When a valid response body is provided in config, it is available to the auction', async function() { + setGdprApplies(true); + const euidToken = apiHelpers.makeTokenResponse(initialToken, false, false); + config.setConfig(makePrebidConfig({euidToken})); + const bid = await runAuction(); + expectToken(bid, initialToken); + }) + + it('When a valid response body is provided via cookie, it is available to the auction', async function() { + setGdprApplies(true); + const euidToken = apiHelpers.makeTokenResponse(initialToken, false, false); + cookieHelpers.setPublisherCookie(publisherCookieName, euidToken); + config.setConfig(makePrebidConfig({euidCookie: publisherCookieName})); + const bid = await runAuction(); + expectToken(bid, initialToken); + }) + + it('When an expired token is provided in config, it calls the API.', function() { + setGdprApplies(true); + const euidToken = apiHelpers.makeTokenResponse(initialToken, true, true); + config.setConfig(makePrebidConfig({euidToken})); + expect(server.requests[0]?.url).to.have.string('https://prod.euid.eu/'); + }); + + it('When an expired token is provided and the API responds in time, the refreshed token is provided to the auction.', async function() { + setGdprApplies(true); + const euidToken = apiHelpers.makeTokenResponse(initialToken, true, true); + configureEuidResponse(200, makeSuccessResponseBody()); + config.setConfig(makePrebidConfig({euidToken})); + apiHelpers.respondAfterDelay(1); + const bid = await runAuction(); + expectToken(bid, refreshedToken); + }); +}); diff --git a/test/spec/modules/feedadBidAdapter_spec.js b/test/spec/modules/feedadBidAdapter_spec.js index 5789361d2a1..152adba9d00 100644 --- a/test/spec/modules/feedadBidAdapter_spec.js +++ b/test/spec/modules/feedadBidAdapter_spec.js @@ -508,7 +508,11 @@ describe('FeedAdAdapter', function () { } }, 'adUnitCode': 'div-gpt-ad-1460505748561-0', - 'transactionId': transactionId, + ortb2Imp: { + ext: { + tid: transactionId + } + }, 'sizes': [ [ 300, diff --git a/test/spec/modules/finativeBidAdapter_spec.js b/test/spec/modules/finativeBidAdapter_spec.js index 7b3f23d8b9e..d5c56aca65d 100644 --- a/test/spec/modules/finativeBidAdapter_spec.js +++ b/test/spec/modules/finativeBidAdapter_spec.js @@ -49,17 +49,6 @@ describe('Finative adapter', function () { assert.deepEqual(keys, data); }); - it('Verify the auction ID', function () { - let validBidRequests = [{ - bidId: 'bidId', - params: {}, - auctionId: 'auctionId' - }]; - let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' }, auctionId: validBidRequests[0].auctionId }).data); - - assert.equal(request.id, validBidRequests[0].auctionId); - }); - it('Verify the device', function () { let validBidRequests = [{ bidId: 'bidId', @@ -166,7 +155,6 @@ describe('Finative adapter', function () { assert.deepEqual(result[0].cpm, bid.price); assert.deepEqual(result[0].creativeId, bid.crid); assert.deepEqual(result[0].mediaType, 'native'); - assert.deepEqual(result[0].bidderCode, 'finative'); }); it('should return the correct tracking links', function () { diff --git a/test/spec/modules/fledge_spec.js b/test/spec/modules/fledge_spec.js index 2094ab42438..a81ff05596e 100644 --- a/test/spec/modules/fledge_spec.js +++ b/test/spec/modules/fledge_spec.js @@ -2,6 +2,13 @@ import { expect } from 'chai'; import * as fledge from 'modules/fledgeForGpt.js'; +import {config} from '../../../src/config.js'; +import adapterManager from '../../../src/adapterManager.js'; +import * as utils from '../../../src/utils.js'; +import {hook} from '../../../src/hook.js'; +import 'modules/appnexusBidAdapter.js'; +import 'modules/rubiconBidAdapter.js'; +import {parseExtPrebidFledge, setImpExtAe, setResponseFledgeConfigs} from 'modules/fledgeForGpt.js'; const CODE = 'sampleBidder'; const AD_UNIT_CODE = 'mock/placement'; @@ -29,9 +36,173 @@ describe('fledgeForGpt module', function() { nextFnSpy = sinon.spy(); }); - it('should call next() when a proper bidrequest and fledgeAuctionConfig are provided', function() { - fledge.addComponentAuctionHook(nextFnSpy, bidRequest, fledgeAuctionConfig); + it('should call next() when a proper adUnitCode and fledgeAuctionConfig are provided', function() { + fledge.addComponentAuctionHook(nextFnSpy, bidRequest.adUnitCode, fledgeAuctionConfig); expect(nextFnSpy.called).to.be.true; }); }); }); + +describe('fledgeEnabled', function () { + const navProps = Object.fromEntries(['runAdAuction', 'joinAdInterestGroup'].map(p => [p, navigator[p]])) + + before(function () { + // navigator.runAdAuction & co may not exist, so we can't stub it normally with + // sinon.stub(navigator, 'runAdAuction') or something + Object.keys(navProps).forEach(p => { navigator[p] = sinon.stub() }); + hook.ready(); + }); + + after(function() { + Object.entries(navProps).forEach(([p, orig]) => navigator[p] = orig); + }) + + afterEach(function () { + config.resetConfig(); + }); + + it('should set fledgeEnabled correctly per bidder', function () { + config.setConfig({bidderSequence: 'fixed'}) + config.setBidderConfig({ + bidders: ['appnexus'], + config: { + fledgeEnabled: true, + } + }); + + const adUnits = [{ + 'code': '/19968336/header-bid-tag1', + 'mediaTypes': { + 'banner': { + 'sizes': [[728, 90]] + }, + }, + 'bids': [ + { + 'bidder': 'appnexus', + }, + { + 'bidder': 'rubicon', + }, + ] + }]; + + const bidRequests = adapterManager.makeBidRequests( + adUnits, + Date.now(), + utils.getUniqueIdentifierStr(), + function callback() {}, + [] + ); + + expect(bidRequests[0].bids[0].bidder).equals('appnexus'); + expect(bidRequests[0].fledgeEnabled).to.be.true; + + expect(bidRequests[1].bids[0].bidder).equals('rubicon'); + expect(bidRequests[1].fledgeEnabled).to.be.undefined; + }); +}); + +describe('ortb processors for fledge', () => { + describe('imp.ext.ae', () => { + it('should be removed if fledge is not enabled', () => { + const imp = {ext: {ae: 1}}; + setImpExtAe(imp, {}, {bidderRequest: {}}); + expect(imp.ext.ae).to.not.exist; + }) + it('should be left intact if fledge is enabled', () => { + const imp = {ext: {ae: false}}; + setImpExtAe(imp, {}, {bidderRequest: {fledgeEnabled: true}}); + expect(imp.ext.ae).to.equal(false); + }); + }); + describe('parseExtPrebidFledge', () => { + function packageConfigs(configs) { + return { + ext: { + prebid: { + fledge: { + auctionconfigs: configs + } + } + } + } + } + + function generateImpCtx(fledgeFlags) { + return Object.fromEntries(Object.entries(fledgeFlags).map(([impid, fledgeEnabled]) => [impid, {imp: {ext: {ae: fledgeEnabled}}}])); + } + + function generateCfg(impid, ...ids) { + return ids.map((id) => ({impid, config: {id}})); + } + + function extractResult(ctx) { + return Object.fromEntries( + Object.entries(ctx) + .map(([impid, ctx]) => [impid, ctx.fledgeConfigs?.map(cfg => cfg.config.id)]) + .filter(([_, val]) => val != null) + ); + } + + it('should collect fledge configs by imp', () => { + const ctx = { + impContext: generateImpCtx({e1: 1, e2: 1, d1: 0}) + }; + const resp = packageConfigs( + generateCfg('e1', 1, 2, 3) + .concat(generateCfg('e2', 4) + .concat(generateCfg('d1', 5, 6))) + ); + parseExtPrebidFledge({}, resp, ctx); + expect(extractResult(ctx.impContext)).to.eql({ + e1: [1, 2, 3], + e2: [4], + }); + }); + it('should not choke if fledge config references unknown imp', () => { + const ctx = {impContext: generateImpCtx({i: 1})}; + const resp = packageConfigs(generateCfg('unknown', 1)); + parseExtPrebidFledge({}, resp, ctx); + expect(extractResult(ctx.impContext)).to.eql({}); + }); + }); + describe('setResponseFledgeConfigs', () => { + it('should set fledgeAuctionConfigs paired with their corresponding bid id', () => { + const ctx = { + impContext: { + 1: { + bidRequest: {bidId: 'bid1'}, + fledgeConfigs: [{config: {id: 1}}, {config: {id: 2}}] + }, + 2: { + bidRequest: {bidId: 'bid2'}, + fledgeConfigs: [{config: {id: 3}}] + }, + 3: { + bidRequest: {bidId: 'bid3'} + } + } + }; + const resp = {}; + setResponseFledgeConfigs(resp, {}, ctx); + expect(resp.fledgeAuctionConfigs).to.eql([ + {bidId: 'bid1', config: {id: 1}}, + {bidId: 'bid1', config: {id: 2}}, + {bidId: 'bid2', config: {id: 3}}, + ]); + }); + it('should not set fledgeAuctionConfigs if none exist', () => { + const resp = {}; + setResponseFledgeConfigs(resp, {}, { + impContext: { + 1: { + fledgeConfigs: [] + }, + 2: {} + } + }); + expect(resp).to.eql({}); + }); + }); +}); diff --git a/test/spec/modules/fluctBidAdapter_spec.js b/test/spec/modules/fluctBidAdapter_spec.js index 92a4b42f6f8..d970f70ad85 100644 --- a/test/spec/modules/fluctBidAdapter_spec.js +++ b/test/spec/modules/fluctBidAdapter_spec.js @@ -54,6 +54,16 @@ describe('fluctAdapter', function () { }); describe('buildRequests', function () { + let sb; + + beforeEach(function () { + sb = sinon.sandbox.create(); + }); + + afterEach(function () { + sb.restore(); + }); + const bidRequests = [{ bidder: 'fluct', params: { @@ -70,7 +80,7 @@ describe('fluctAdapter', function () { }]; const bidderRequest = { refererInfo: { - referer: 'http://example.com' + page: 'http://example.com' } }; @@ -84,6 +94,11 @@ describe('fluctAdapter', function () { expect(request.url).to.equal('https://hb.adingo.jp/prebid?dfpUnitCode=%2F100000%2Funit_code&tagId=10000%3A100000001&groupId=1000000002'); }); + it('includes data.page by default', function () { + const request = spec.buildRequests(bidRequests, bidderRequest)[0]; + expect(request.data.page).to.eql('http://example.com'); + }); + it('includes data.user.eids = [] by default', function () { const request = spec.buildRequests(bidRequests, bidderRequest)[0]; expect(request.data.user.eids).to.eql([]); @@ -99,6 +114,11 @@ describe('fluctAdapter', function () { expect(request.data.schain).to.eql(undefined); }); + it('includes no data.regs by default', function () { + const request = spec.buildRequests(bidRequests, bidderRequest)[0]; + expect(request.data.regs).to.eql(undefined); + }); + it('includes filtered user.eids if any exist', function () { const bidRequests2 = bidRequests.map( (bidReq) => Object.assign(bidReq, { @@ -211,6 +231,44 @@ describe('fluctAdapter', function () { ] }); }); + + it('includes data.regs.gdpr if bidderRequest.gdprConsent exists', function () { + const request = spec.buildRequests( + bidRequests, + Object.assign({}, bidderRequest, { + gdprConsent: { + consentString: 'gdpr-consent-string', + gdprApplies: true, + }, + }), + )[0]; + expect(request.data.regs.gdpr).to.eql({ + consent: 'gdpr-consent-string', + gdprApplies: 1, + }); + }); + + it('includes data.regs.us_privacy if bidderRequest.uspConsent exists', function () { + const request = spec.buildRequests( + bidRequests, + Object.assign({}, bidderRequest, { + uspConsent: 'usp-consent-string', + }), + )[0]; + expect(request.data.regs.us_privacy).to.eql({ + consent: 'usp-consent-string', + }); + }); + + it('includes data.regs.coppa if config.getConfig("coppa") is true', function () { + const cfg = { + coppa: true, + }; + sb.stub(config, 'getConfig').callsFake(key => cfg[key]); + + const request = spec.buildRequests(bidRequests, bidderRequest)[0]; + expect(request.data.regs.coppa).to.eql(1); + }); }); describe('interpretResponse', function() { @@ -247,15 +305,20 @@ describe('fluctAdapter', function () { adm: '', burl: 'https://i.adingo.jp/?test=1&et=hb&bidid=237f4d1a293f99', crid: 'test_creative', - adomain: ['test_adomain'] + adomain: ['test_adomain'], }] - }] + }], + usersyncs: [ + { + 'type': 'image', + 'url': 'https://cs.adingo.jp/sync', + }, + ], } }; const expectedResponse = [ { - bidderCode: 'fluct', requestId: '237f4d1a293f99', currency: 'JPY', cpm: 100, @@ -312,7 +375,6 @@ describe('fluctAdapter', function () { const expectedResponse = [ { - bidderCode: 'fluct', requestId: '237f4d1a293f99', currency: 'JPY', cpm: 100, @@ -338,4 +400,71 @@ describe('fluctAdapter', function () { expect(spec.interpretResponse({})).to.be.empty; }); }); + + describe('getUserSyncs', function () { + const syncOptions = {}; + const serverResponse = { + body: { + usersyncs: [ + { + type: 'image', + url: 'https://cs.adingo.jp/foo', + }, + { + type: 'image', + url: 'https://cs.adingo.jp/bar', + }, + { + type: 'iframe', + url: 'https://cs.adingo.jp/buz', + }, + ], + }, + }; + + it('returns no user syncs if syncOption.pixelEnabled !== true and syncOption.iframeEnabled !== true', function () { + const actual = spec.getUserSyncs( + syncOptions, + [serverResponse], + ); + + expect(actual).to.eql([]); + }); + + it('returns user syncs if syncOption.pixelEnabled === true', function () { + const actual = spec.getUserSyncs( + Object.assign({}, syncOptions, { + pixelEnabled: true, + }), + [serverResponse], + ); + + expect(actual).to.eql([ + { + type: 'image', + url: 'https://cs.adingo.jp/foo', + }, + { + type: 'image', + url: 'https://cs.adingo.jp/bar', + }, + ]); + }); + + it('returns user syncs if syncOption.iframeEnabled === true', function () { + const actual = spec.getUserSyncs( + Object.assign({}, syncOptions, { + iframeEnabled: true, + }), + [serverResponse], + ); + + expect(actual).to.eql([ + { + type: 'iframe', + url: 'https://cs.adingo.jp/buz', + }, + ]); + }); + }); }); diff --git a/test/spec/modules/freepassBidAdapter_spec.js b/test/spec/modules/freepassBidAdapter_spec.js new file mode 100644 index 00000000000..5d75bd8e0f7 --- /dev/null +++ b/test/spec/modules/freepassBidAdapter_spec.js @@ -0,0 +1,161 @@ +import {expect} from 'chai'; +import {spec} from 'modules/freepassBidAdapter.js'; +import {newBidder} from 'src/adapters/bidderFactory.js'; + +describe('FreePass adapter', function () { + const adapter = newBidder(spec); + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function () { + const bid = { + bidder: 'freepass', + userId: { + freepassId: { + userId: 'fpid' + } + }, + adUnitCode: 'adunit-code', + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when adUnitCode is missing', function () { + let localBid = Object.assign({}, bid); + delete localBid.adUnitCode; + expect(spec.isBidRequestValid(localBid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + let bidRequests, bidderRequest; + beforeEach(function () { + bidRequests = [{ + 'bidder': 'freepass', + 'userId': { + 'freepassId': { + 'userIp': '172.21.0.1', + 'userId': '56c4c789-71ce-46f5-989e-9e543f3d5f96', + 'commonId': 'commonIdValue' + } + } + }]; + bidderRequest = {}; + }); + + it('should return an empty array when no bid requests', function () { + const bidRequest = spec.buildRequests([], bidderRequest); + expect(bidRequest).to.be.an('array'); + expect(bidRequest.length).to.equal(0); + }); + + it('should return a valid bid request object', function () { + const bidRequest = spec.buildRequests(bidRequests, bidderRequest); + expect(bidRequest).to.be.an('object'); + expect(bidRequest.data).to.be.an('object'); + expect(bidRequest.method).to.equal('POST'); + expect(bidRequest.url).to.not.equal(''); + expect(bidRequest.url).to.not.equal(undefined); + expect(bidRequest.url).to.not.equal(null); + }); + + it('should add user id to user information', function () { + const bidRequest = spec.buildRequests(bidRequests, bidderRequest); + const ortbData = bidRequest.data; + expect(ortbData.user).to.be.an('object'); + expect(ortbData.user.id).to.equal('56c4c789-71ce-46f5-989e-9e543f3d5f96'); + }); + + it('should add freepass commonId to extended user information', function () { + const bidRequest = spec.buildRequests(bidRequests, bidderRequest); + const ortbData = bidRequest.data; + expect(ortbData.user).to.be.an('object'); + expect(ortbData.user.ext).to.be.an('object'); + expect(ortbData.user.ext.fuid).to.equal('commonIdValue'); + }); + + it('should skip freepass commonId when not available', function () { + let localBidRequests = [Object.assign({}, bidRequests[0])]; + delete localBidRequests[0].userId.freepassId.commonId; + const bidRequest = spec.buildRequests(localBidRequests, bidderRequest); + const ortbData = bidRequest.data; + expect(ortbData.user).to.be.an('object'); + expect(ortbData.user.ext).to.be.an('object'); + expect(ortbData.user.ext.fuid).to.be.undefined; + }); + + it('should add IP information to extended device information', function () { + const bidRequest = spec.buildRequests(bidRequests, bidderRequest); + const ortbData = bidRequest.data; + expect(ortbData.device).to.be.an('object'); + expect(ortbData.device.ip).to.equal('172.21.0.1'); + expect(ortbData.device.ext).to.be.an('object'); + expect(ortbData.device.ext.is_accurate_ip).to.equal(1); + }); + + it('should skip IP information when not available', function () { + let localBidRequests = [Object.assign({}, bidRequests[0])]; + delete localBidRequests[0].userId.freepassId.userIp; + const bidRequest = spec.buildRequests(localBidRequests, bidderRequest); + const ortbData = bidRequest.data; + expect(ortbData.device).to.be.an('object'); + expect(ortbData.device.ip).to.be.undefined; + expect(ortbData.device.ext).to.be.an('object'); + expect(ortbData.device.ext.is_accurate_ip).to.equal(0); + }); + }); + + describe('interpretResponse', function () { + let bidRequests, bidderRequest; + beforeEach(function () { + bidRequests = [{ + 'bidId': '28ffdf2a952532', + 'bidder': 'freepass', + 'userId': { + 'freepassId': { + 'userIp': '172.21.0.1', + 'userId': '56c4c789-71ce-46f5-989e-9e543f3d5f96', + 'commonId': 'commonIdValue' + } + } + }]; + bidderRequest = {}; + }); + + const ad = ''; + const serverResponse = { + body: { + 'cur': 'JPY', + 'seatbid': [{ + 'bid': [{ + 'impid': '28ffdf2a952532', + 'price': 97, + 'adm': ad, + 'w': 300, + 'h': 250, + 'crid': 'creative0' + }] + }] + } + }; + it('should interpret server response', function () { + const bidRequest = spec.buildRequests(bidRequests, bidderRequest); + const bids = spec.interpretResponse(serverResponse, bidRequest); + expect(bids).to.be.an('array'); + + const bid = bids[0]; + expect(bid).to.be.an('object'); + expect(bid.currency).to.equal('JPY'); + expect(bid.cpm).to.equal(97); + expect(bid.ad).to.equal(ad) + expect(bid.width).to.equal(300); + expect(bid.height).to.equal(250); + expect(bid.creativeId).to.equal('creative0'); + }); + }); +}); diff --git a/test/spec/modules/freepassIdSystem_spec.js b/test/spec/modules/freepassIdSystem_spec.js new file mode 100644 index 00000000000..f7407f5eb94 --- /dev/null +++ b/test/spec/modules/freepassIdSystem_spec.js @@ -0,0 +1,186 @@ +import {freepassIdSubmodule} from 'modules/freepassIdSystem'; +import sinon from 'sinon'; +import * as utils from '../../../src/utils'; + +let expect = require('chai').expect; + +describe('FreePass ID System', function () { + const UUID = '15fde1dc-1861-4894-afdf-b757272f3568'; + + before(function () { + sinon.stub(utils, 'generateUUID').returns(UUID); + sinon.stub(utils, 'logMessage'); + }); + + after(function () { + utils.generateUUID.restore(); + utils.logMessage.restore(); + }); + + describe('freepassIdSubmodule', function () { + it('should expose submodule name', function () { + expect(freepassIdSubmodule.name).to.equal('freepassId'); + }); + }); + + describe('getId', function () { + const config = { + storage: { + name: '_freepassId', + type: 'cookie', + expires: 30 + }, + params: { + freepassData: { + commonId: 'commonId', + userIp: '127.0.0.1' + } + } + }; + + it('should return an IdObject with a UUID', function () { + const objectId = freepassIdSubmodule.getId(config, undefined); + expect(objectId).to.be.an('object'); + expect(objectId.id).to.be.an('object'); + expect(objectId.id.userId).to.equal(UUID); + }); + + it('should include userIp in IdObject', function () { + const objectId = freepassIdSubmodule.getId(config, undefined); + expect(objectId).to.be.an('object'); + expect(objectId.id).to.be.an('object'); + expect(objectId.id.userIp).to.equal('127.0.0.1'); + }); + it('should skip userIp in IdObject if not available', function () { + const localConfig = Object.assign({}, config); + delete localConfig.params.freepassData.userIp; + const objectId = freepassIdSubmodule.getId(localConfig, undefined); + expect(objectId).to.be.an('object'); + expect(objectId.id).to.be.an('object'); + expect(objectId.id.userIp).to.be.undefined; + }); + it('should skip userIp in IdObject if freepassData is not available', function () { + const localConfig = JSON.parse(JSON.stringify(config)); + delete localConfig.params.freepassData; + const objectId = freepassIdSubmodule.getId(localConfig, undefined); + expect(objectId).to.be.an('object'); + expect(objectId.id).to.be.an('object'); + expect(objectId.id.userIp).to.be.undefined; + }); + it('should skip userIp in IdObject if params is not available', function () { + const localConfig = JSON.parse(JSON.stringify(config)); + delete localConfig.params; + const objectId = freepassIdSubmodule.getId(localConfig, undefined); + expect(objectId).to.be.an('object'); + expect(objectId.id).to.be.an('object'); + expect(objectId.id.userIp).to.be.undefined; + }); + it('should include commonId in IdObject', function () { + const objectId = freepassIdSubmodule.getId(config, undefined); + expect(objectId).to.be.an('object'); + expect(objectId.id).to.be.an('object'); + expect(objectId.id.commonId).to.equal('commonId'); + }); + it('should skip commonId in IdObject if not available', function () { + const localConfig = Object.assign({}, config); + delete localConfig.params.freepassData.commonId; + const objectId = freepassIdSubmodule.getId(localConfig, undefined); + expect(objectId).to.be.an('object'); + expect(objectId.id).to.be.an('object'); + expect(objectId.id.commonId).to.be.undefined; + }); + it('should skip commonId in IdObject if freepassData is not available', function () { + const localConfig = JSON.parse(JSON.stringify(config)); + delete localConfig.params.freepassData; + const objectId = freepassIdSubmodule.getId(localConfig, undefined); + expect(objectId).to.be.an('object'); + expect(objectId.id).to.be.an('object'); + expect(objectId.id.commonId).to.be.undefined; + }); + it('should skip commonId in IdObject if params is not available', function () { + const localConfig = JSON.parse(JSON.stringify(config)); + delete localConfig.params; + const objectId = freepassIdSubmodule.getId(localConfig, undefined); + expect(objectId).to.be.an('object'); + expect(objectId.id).to.be.an('object'); + expect(objectId.id.commonId).to.be.undefined; + }); + }); + + describe('decode', function () { + it('should have module name as property', function () { + const decodedId = freepassIdSubmodule.decode({}, {}); + expect(decodedId).to.be.an('object'); + expect(decodedId).to.have.property('freepassId'); + }); + it('should have IObject as property value', function () { + const idObject = { + commonId: 'commonId', + userIp: '127.0.0.1', + userId: UUID + }; + const decodedId = freepassIdSubmodule.decode(idObject, {}); + expect(decodedId).to.be.an('object'); + expect(decodedId.freepassId).to.be.an('object'); + expect(decodedId.freepassId).to.equal(idObject); + }); + }); + + describe('extendId', function () { + const config = { + storage: { + name: '_freepassId', + type: 'cookie', + expires: 30 + }, + params: { + freepassData: { + commonId: 'commonId', + userIp: '127.0.0.1' + } + } + }; + + it('should return cachedIdObject if there are no changes', function () { + const idObject = freepassIdSubmodule.getId(config, undefined); + const cachedIdObject = Object.assign({}, idObject.id); + const extendedIdObject = freepassIdSubmodule.extendId(config, undefined, cachedIdObject); + expect(extendedIdObject).to.be.an('object'); + expect(extendedIdObject.id).to.be.an('object'); + expect(extendedIdObject.id).to.equal(cachedIdObject); + }); + + it('should return cachedIdObject if there are no new data', function () { + const idObject = freepassIdSubmodule.getId(config, undefined); + const cachedIdObject = Object.assign({}, idObject.id); + const localConfig = JSON.parse(JSON.stringify(config)); + delete localConfig.params.freepassData; + const extendedIdObject = freepassIdSubmodule.extendId(localConfig, undefined, cachedIdObject); + expect(extendedIdObject).to.be.an('object'); + expect(extendedIdObject.id).to.be.an('object'); + expect(extendedIdObject.id).to.equal(cachedIdObject); + }); + + it('should return new commonId if there are changes', function () { + const idObject = freepassIdSubmodule.getId(config, undefined); + const cachedIdObject = Object.assign({}, idObject.id); + const localConfig = JSON.parse(JSON.stringify(config)); + localConfig.params.freepassData.commonId = 'newCommonId'; + const extendedIdObject = freepassIdSubmodule.extendId(localConfig, undefined, cachedIdObject); + expect(extendedIdObject).to.be.an('object'); + expect(extendedIdObject.id).to.be.an('object'); + expect(extendedIdObject.id.commonId).to.equal('newCommonId'); + }); + + it('should return new userIp if there are changes', function () { + const idObject = freepassIdSubmodule.getId(config, undefined); + const cachedIdObject = Object.assign({}, idObject.id); + const localConfig = JSON.parse(JSON.stringify(config)); + localConfig.params.freepassData.userIp = '192.168.1.1'; + const extendedIdObject = freepassIdSubmodule.extendId(localConfig, undefined, cachedIdObject); + expect(extendedIdObject).to.be.an('object'); + expect(extendedIdObject.id).to.be.an('object'); + expect(extendedIdObject.id.userIp).to.equal('192.168.1.1'); + }); + }); +}); diff --git a/test/spec/modules/freewheel-sspBidAdapter_spec.js b/test/spec/modules/freewheel-sspBidAdapter_spec.js index 4d0c87ace3a..fe04a430ce6 100644 --- a/test/spec/modules/freewheel-sspBidAdapter_spec.js +++ b/test/spec/modules/freewheel-sspBidAdapter_spec.js @@ -1,8 +1,10 @@ import { expect } from 'chai'; import { spec } from 'modules/freewheel-sspBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; +import { createEidsArray } from 'modules/userId/eids.js'; const ENDPOINT = '//ads.stickyadstv.com/www/delivery/swfIndex.php'; +const PREBID_VERSION = '$prebid.version$'; describe('freewheelSSP BidAdapter Test', () => { const adapter = newBidder(spec); @@ -131,6 +133,22 @@ describe('freewheelSSP BidAdapter Test', () => { expect(payload._fw_bidfloorcur).to.deep.equal('USD'); }); + it('should pass 3rd party IDs with the request when present', function () { + const bidRequest = bidRequests[0]; + bidRequest.userIdAsEids = createEidsArray({ + tdid: 'TTD_ID_FROM_USER_ID_MODULE', + admixerId: 'admixerId_FROM_USER_ID_MODULE', + adtelligentId: 'adtelligentId_FROM_USER_ID_MODULE' + }); + const request = spec.buildRequests(bidRequests); + const payload = request[0].data; + expect(payload._fw_prebid_3p_UID).to.deep.equal(JSON.stringify([ + {source: 'adserver.org', uids: [{id: 'TTD_ID_FROM_USER_ID_MODULE', atype: 1, ext: {rtiPartner: 'TDID'}}]}, + {source: 'admixer.net', uids: [{id: 'admixerId_FROM_USER_ID_MODULE', atype: 3}]}, + {source: 'adtelligent.com', uids: [{id: 'adtelligentId_FROM_USER_ID_MODULE', atype: 3}]}, + ])); + }); + it('should return empty bidFloorCurrency when bidfloor <= 0', () => { const bidRequest = bidRequests[0]; bidRequest.getFloor = () => ({ currency: 'USD', floor: -1 }); @@ -149,6 +167,7 @@ describe('freewheelSSP BidAdapter Test', () => { expect(payload.componentId).to.equal('prebid'); expect(payload.componentSubId).to.equal('mustang'); expect(payload.playerSize).to.equal('300x600'); + expect(payload.pbjs_version).to.equal(PREBID_VERSION); }); it('should return a properly formatted request with schain defined', function () { @@ -205,12 +224,41 @@ describe('freewheelSSP BidAdapter Test', () => { let syncOptions = { 'pixelEnabled': true } - const userSyncs = spec.getUserSyncs(syncOptions, null, gdprConsent, null); + const userSyncs = spec.getUserSyncs(syncOptions, null, gdprConsent, null, null); expect(userSyncs).to.deep.equal([{ type: 'image', url: 'https://ads.stickyadstv.com/auto-user-sync?gdpr=1&gdpr_consent=1FW-SSP-gdprConsent-' }]); }); + + it('should add gpp information to the request via bidderRequest.gppConsent', function () { + let consentString = 'abc1234'; + let bidderRequest = { + 'gppConsent': { + 'gppString': consentString, + 'applicableSections': [8] + } + }; + + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = request[0].data; + + expect(payload.gpp).to.equal(consentString); + expect(payload.gpp_sid).to.deep.equal([8]); + + let gppConsent = { + 'applicableSections': [8], + 'gppString': consentString + } + let syncOptions = { + 'pixelEnabled': true + } + const userSyncs = spec.getUserSyncs(syncOptions, null, null, null, gppConsent); + expect(userSyncs).to.deep.equal([{ + type: 'image', + url: 'https://ads.stickyadstv.com/auto-user-sync?gpp=abc1234&gpp_sid[]=8' + }]); + }); }) describe('buildRequestsForVideo', () => { @@ -233,6 +281,14 @@ describe('freewheelSSP BidAdapter Test', () => { } ]; + it('should return context and placement with default values', () => { + const request = spec.buildRequests(bidRequests); + const payload = request[0].data; + expect(payload.video_context).to.equal(''); ; + expect(payload.video_placement).to.equal(null); + expect(payload.video_plcmt).to.equal(null); + }); + it('should add parameters to the tag', () => { const request = spec.buildRequests(bidRequests); const payload = request[0].data; @@ -292,7 +348,7 @@ describe('freewheelSSP BidAdapter Test', () => { let syncOptions = { 'pixelEnabled': true } - const userSyncs = spec.getUserSyncs(syncOptions, null, gdprConsent, null); + const userSyncs = spec.getUserSyncs(syncOptions, null, gdprConsent, null, null); expect(userSyncs).to.deep.equal([{ type: 'image', url: 'https://ads.stickyadstv.com/auto-user-sync?gdpr=1&gdpr_consent=1FW-SSP-gdprConsent-' @@ -300,6 +356,38 @@ describe('freewheelSSP BidAdapter Test', () => { }); }) + describe('buildRequestsForVideoWithContextAndPlacement', () => { + let bidRequests = [ + { + 'bidder': 'freewheel-ssp', + 'params': { + 'zoneId': '277225' + }, + 'adUnitCode': 'adunit-code', + 'mediaTypes': { + 'video': { + 'context': 'outstream', + 'placement': 2, + 'plcmt': 3, + 'playerSize': [300, 600], + } + }, + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + } + ]; + + it('should return input context and placement', () => { + const request = spec.buildRequests(bidRequests); + const payload = request[0].data; + expect(payload.video_context).to.equal('outstream'); ; + expect(payload.video_placement).to.equal(2); + expect(payload.video_plcmt).to.equal(3); + }); + }) + describe('interpretResponseForBanner', () => { let bidRequests = [ { diff --git a/test/spec/modules/gamoshiBidAdapter_spec.js b/test/spec/modules/gamoshiBidAdapter_spec.js index 8f8e3f03736..984830f67d4 100644 --- a/test/spec/modules/gamoshiBidAdapter_spec.js +++ b/test/spec/modules/gamoshiBidAdapter_spec.js @@ -46,7 +46,11 @@ describe('GamoshiAdapter', () => { 'supplyPartnerId': supplyPartnerId }, 'sizes': [[300, 250], [300, 600]], - 'transactionId': 'a123456789', + ortb2Imp: { + ext: { + tid: 'a123456789', + } + }, refererInfo: {referer: 'http://examplereferer.com'}, gdprConsent: { consentString: 'some string', @@ -310,7 +314,6 @@ describe('GamoshiAdapter', () => { response = spec.buildRequests([bidRequest], bidRequest)[0]; expect(response.method).to.equal('POST'); expect(response.url).to.match(new RegExp(`^https://rtb\\.gamoshi\\.io/r/${supplyPartnerId}/bidr\\?rformat=open_rtb&reqformat=rtb_json&bidder=prebid$`, 'g')); - expect(response.data.id).to.equal(bidRequest.auctionId); const bidRequestWithEndpoint = utils.deepClone(bidRequest); bidRequestWithEndpoint.params.rtbEndpoint = 'https://rtb2.gamoshi.io/a12'; response = spec.buildRequests([bidRequestWithEndpoint], bidRequest)[0]; diff --git a/test/spec/modules/gdprEnforcement_spec.js b/test/spec/modules/gdprEnforcement_spec.js index 8d58990bb66..f571abcccea 100644 --- a/test/spec/modules/gdprEnforcement_spec.js +++ b/test/spec/modules/gdprEnforcement_spec.js @@ -1,26 +1,35 @@ import { + accessDeviceRule, deviceAccessHook, - setEnforcementConfig, - userSyncHook, - userIdHook, - makeBidRequestsHook, - validateRules, enforcementRules, + enrichEidsRule, + fetchBidsRule, + getGvlid, + getGvlidFromAnalyticsAdapter, purpose1Rule, purpose2Rule, - enableAnalyticsHook, - getGvlid, - internal, STRICT_STORAGE_ENFORCEMENT + reportAnalyticsRule, + setEnforcementConfig, + STRICT_STORAGE_ENFORCEMENT, + syncUserRule, transmitUfpdRule, + validateRules } from 'modules/gdprEnforcement.js'; -import { config } from 'src/config.js'; -import adapterManager, { gdprDataHandler } from 'src/adapterManager.js'; +import {config} from 'src/config.js'; +import adapterManager, {gdprDataHandler} from 'src/adapterManager.js'; import * as utils from 'src/utils.js'; -import { validateStorageEnforcement } from 'src/storageManager.js'; +import { + MODULE_TYPE_ANALYTICS, + MODULE_TYPE_BIDDER, + MODULE_TYPE_PREBID, + MODULE_TYPE_UID +} from '../../../src/activities/modules.js'; import * as events from 'src/events.js'; import 'modules/appnexusBidAdapter.js'; // some tests expect this to be in the adapter registry -import 'src/prebid.js' +import 'src/prebid.js'; import {hook} from '../../../src/hook.js'; -import {VENDORLESS_GVLID} from '../../../src/consentHandler.js'; +import {GDPR_GVLIDS, VENDORLESS_GVLID} from '../../../src/consentHandler.js'; +import {validateStorageEnforcement} from '../../../src/storageManager.js'; +import {activityParams} from '../../../src/activities/activityParams.js'; describe('gdpr enforcement', function () { let nextFnSpy; @@ -100,69 +109,51 @@ describe('gdpr enforcement', function () { } } }; + let gvlids, sandbox; + + function setupConsentData({gdprApplies = true, apiVersion = 2} = {}) { + const cd = utils.deepClone(staticConfig); + const consent = { + vendorData: cd.consentData.getTCData, + gdprApplies, + apiVersion + }; + sandbox.stub(gdprDataHandler, 'getConsentData').callsFake(() => consent) + return consent; + } before(() => { hook.ready(); }); after(function () { - validateStorageEnforcement.getHooks({ hook: deviceAccessHook }).remove(); $$PREBID_GLOBAL$$.requestBids.getHooks().remove(); - adapterManager.makeBidRequests.getHooks({ hook: makeBidRequestsHook }).remove(); }) - describe('deviceAccessHook', function () { - let adapterManagerStub; + function expectAllow(allow, ruleResult) { + allow ? expect(ruleResult).to.not.exist : sinon.assert.match(ruleResult, {allow: false}); + } - function getBidderSpec(gvlid) { - return { - getSpec: () => { - return { - gvlid - } - } - } - } + beforeEach(() => { + sandbox = sinon.sandbox.create(); + gvlids = {}; + sandbox.stub(GDPR_GVLIDS, 'get').callsFake((name) => ({gvlid: gvlids[name], modules: {}})); + }); - beforeEach(function () { - nextFnSpy = sinon.spy(); - gdprDataHandlerStub = sinon.stub(gdprDataHandler, 'getConsentData'); - logWarnSpy = sinon.spy(utils, 'logWarn'); - adapterManagerStub = sinon.stub(adapterManager, 'getBidAdapter'); - }); - afterEach(function () { - config.resetConfig(); - gdprDataHandler.getConsentData.restore(); - logWarnSpy.restore(); - adapterManagerStub.restore(); - }); - it('should not allow device access when device access flag is set to false', function () { - config.setConfig({ - deviceAccess: false, - consentManagement: { - gdpr: { - rules: [{ - purpose: 'storage', - enforcePurpose: false, - enforceVendor: false, - vendorExceptions: ['appnexus', 'rubicon'] - }] - } - } - }); + afterEach(() => { + sandbox.restore(); + }) - deviceAccessHook(nextFnSpy); - expect(nextFnSpy.calledOnce).to.equal(true); - let result = { - hasEnforcementHook: true, - valid: false - } - sinon.assert.calledWith(nextFnSpy, undefined, undefined, result); + describe('deviceAccessRule', () => { + afterEach(() => { + config.resetConfig(); }); - it('should only check for consent for vendor exceptions when enforcePurpose and enforceVendor are false', function () { - adapterManagerStub.withArgs('appnexus').returns(getBidderSpec(1)); - adapterManagerStub.withArgs('rubicon').returns(getBidderSpec(5)); + it('should not check for consent when enforcePurpose and enforceVendor are false', function () { + Object.assign(gvlids, { + appnexus: 1, + rubicon: 5 + }); setEnforcementConfig({ gdpr: { rules: [{ @@ -173,20 +164,15 @@ describe('gdpr enforcement', function () { }] } }); - let consentData = {} - consentData.vendorData = staticConfig.consentData.getTCData; - consentData.gdprApplies = true; - consentData.apiVersion = 2; - gdprDataHandlerStub.returns(consentData); - - deviceAccessHook(nextFnSpy, 1, 'appnexus'); - deviceAccessHook(nextFnSpy, 5, 'rubicon'); - expect(logWarnSpy.callCount).to.equal(0); + setupConsentData(); + ['appnexus', 'rubicon'].forEach(bidder => expectAllow(true, accessDeviceRule(activityParams(MODULE_TYPE_BIDDER, bidder)))); }); it('should check consent for all vendors when enforcePurpose and enforceVendor are true', function () { - adapterManagerStub.withArgs('appnexus').returns(getBidderSpec(1)); - adapterManagerStub.withArgs('rubicon').returns(getBidderSpec(3)); + Object.assign(gvlids, { + appnexus: 1, + rubicon: 3 + }); setEnforcementConfig({ gdpr: { rules: [{ @@ -196,19 +182,17 @@ describe('gdpr enforcement', function () { }] } }); - let consentData = {} - consentData.vendorData = staticConfig.consentData.getTCData; - consentData.gdprApplies = true; - consentData.apiVersion = 2; - gdprDataHandlerStub.returns(consentData); - - deviceAccessHook(nextFnSpy, 1, 'appnexus'); - deviceAccessHook(nextFnSpy, 3, 'rubicon'); - expect(logWarnSpy.callCount).to.equal(1); + setupConsentData(); + Object.entries({ + appnexus: true, + rubicon: false + }).forEach(([bidder, isAllowed]) => { + expectAllow(isAllowed, accessDeviceRule(activityParams(MODULE_TYPE_BIDDER, bidder))); + }) }); it('should allow device access when gdprApplies is false and hasDeviceAccess flag is true', function () { - adapterManagerStub.withArgs('appnexus').returns(getBidderSpec(1)); + gvlids.appnexus = 1; setEnforcementConfig({ gdpr: { rules: [{ @@ -219,19 +203,8 @@ describe('gdpr enforcement', function () { }] } }); - let consentData = {} - consentData.vendorData = staticConfig.consentData.getTCData; - consentData.gdprApplies = false; - consentData.apiVersion = 2; - gdprDataHandlerStub.returns(consentData); - - deviceAccessHook(nextFnSpy, 1, 'appnexus'); - expect(nextFnSpy.calledOnce).to.equal(true); - let result = { - hasEnforcementHook: true, - valid: true - } - sinon.assert.calledWith(nextFnSpy, 1, 'appnexus', result); + setupConsentData(); + expectAllow(true, accessDeviceRule(activityParams(MODULE_TYPE_BIDDER, 'appnexus'))); }); it('should use gvlMapping set by publisher', function() { @@ -250,92 +223,18 @@ describe('gdpr enforcement', function () { }] } }); - let consentData = {} - consentData.vendorData = staticConfig.consentData.getTCData; - consentData.gdprApplies = true; - consentData.apiVersion = 2; - gdprDataHandlerStub.returns(consentData); - - deviceAccessHook(nextFnSpy, 1, 'appnexus'); - expect(nextFnSpy.calledOnce).to.equal(true); - let result = { - hasEnforcementHook: true, - valid: true - } - sinon.assert.calledWith(nextFnSpy, 4, 'appnexus', result); - config.resetConfig(); - }); - - it('should use gvl id of alias and not of parent', function() { - let curBidderStub = sinon.stub(config, 'getCurrentBidder'); - curBidderStub.returns('appnexus-alias'); - adapterManager.aliasBidAdapter('appnexus', 'appnexus-alias'); - config.setConfig({ - 'gvlMapping': { - 'appnexus-alias': 4 - } - }); - setEnforcementConfig({ - gdpr: { - rules: [{ - purpose: 'storage', - enforcePurpose: true, - enforceVendor: true, - vendorExceptions: [] - }] - } - }); - let consentData = {} - consentData.vendorData = staticConfig.consentData.getTCData; - consentData.gdprApplies = true; - consentData.apiVersion = 2; - gdprDataHandlerStub.returns(consentData); - - deviceAccessHook(nextFnSpy, 1, 'appnexus'); - expect(nextFnSpy.calledOnce).to.equal(true); - let result = { - hasEnforcementHook: true, - valid: true - } - sinon.assert.calledWith(nextFnSpy, 4, 'appnexus', result); - config.resetConfig(); - curBidderStub.restore(); + setupConsentData(); + expectAllow(true, accessDeviceRule(activityParams(MODULE_TYPE_BIDDER, 'appnexus'))); }); it(`should not enforce consent for vendorless modules if ${STRICT_STORAGE_ENFORCEMENT} is not set`, () => { setEnforcementConfig({}); - let consentData = { - vendorData: staticConfig.consentData.getTCData, - gdprApplies: true - } - gdprDataHandlerStub.returns(consentData); - const validate = sinon.stub().callsFake(() => false); - deviceAccessHook(nextFnSpy, VENDORLESS_GVLID, 'mockModule', undefined, {validate}); - sinon.assert.callCount(validate, 0); - sinon.assert.calledWith(nextFnSpy, VENDORLESS_GVLID, 'mockModule', {hasEnforcementHook: true, valid: true}); + setupConsentData(); + expectAllow(true, accessDeviceRule(activityParams(MODULE_TYPE_PREBID, 'mockCoreModule'))); }) }); - describe('userSyncHook', function () { - let curBidderStub; - let adapterManagerStub; - - beforeEach(function () { - gdprDataHandlerStub = sinon.stub(gdprDataHandler, 'getConsentData'); - logWarnSpy = sinon.spy(utils, 'logWarn'); - curBidderStub = sinon.stub(config, 'getCurrentBidder'); - adapterManagerStub = sinon.stub(adapterManager, 'getBidAdapter'); - nextFnSpy = sinon.spy(); - }); - - afterEach(function () { - config.getCurrentBidder.restore(); - config.resetConfig(); - gdprDataHandler.getConsentData.restore(); - adapterManager.getBidAdapter.restore(); - logWarnSpy.restore(); - }); - + describe('syncUserRule', () => { it('should allow bidder to do user sync if consent is true', function () { setEnforcementConfig({ gdpr: { @@ -347,32 +246,12 @@ describe('gdpr enforcement', function () { }] } }); - let consentData = {} - consentData.vendorData = staticConfig.consentData.getTCData; - consentData.gdprApplies = true; - consentData.apiVersion = 2; - gdprDataHandlerStub.returns(consentData); - - curBidderStub.returns('sampleBidder1'); - adapterManagerStub.withArgs('sampleBidder1').returns({ - getSpec: function () { - return { - 'gvlid': 1 - } - } - }); - userSyncHook(nextFnSpy); - - curBidderStub.returns('sampleBidder2'); - adapterManagerStub.withArgs('sampleBidder2').returns({ - getSpec: function () { - return { - 'gvlid': 3 - } - } - }); - userSyncHook(nextFnSpy); - expect(nextFnSpy.calledTwice).to.equal(true); + setupConsentData(); + Object.assign(gvlids, { + sampleBidder1: 1, + sampleBidder2: 2 + }) + Object.keys(gvlids).forEach(bidder => expect(syncUserRule(activityParams(MODULE_TYPE_BIDDER, bidder))).to.not.exist); }); it('should not allow bidder to do user sync if user has denied consent', function () { @@ -386,33 +265,18 @@ describe('gdpr enforcement', function () { }] } }); - let consentData = {} - consentData.vendorData = staticConfig.consentData.getTCData; - consentData.apiVersion = 2; - consentData.gdprApplies = true; - gdprDataHandlerStub.returns(consentData); - - curBidderStub.returns('sampleBidder1'); - adapterManagerStub.withArgs('sampleBidder1').returns({ - getSpec: function () { - return { - 'gvlid': 1 - } - } - }); - userSyncHook(nextFnSpy); + setupConsentData(); + Object.assign(gvlids, { + sampleBidder1: 1, + sampleBidder2: 3 + }) - curBidderStub.returns('sampleBidder2'); - adapterManagerStub.withArgs('sampleBidder2').returns({ - getSpec: function () { - return { - 'gvlid': 3 - } - } - }); - userSyncHook(nextFnSpy); - expect(nextFnSpy.calledOnce).to.equal(true); - expect(logWarnSpy.callCount).to.equal(1); + Object.entries({ + sampleBidder1: true, + sampleBidder2: false + }).forEach(([bidder, isAllowed]) => { + expectAllow(isAllowed, syncUserRule(activityParams(MODULE_TYPE_BIDDER, bidder))); + }) }); it('should not check vendor consent when enforceVendor is false', function () { @@ -426,45 +290,15 @@ describe('gdpr enforcement', function () { }] } }); - let consentData = {} - consentData.vendorData = staticConfig.consentData.getTCData; - consentData.apiVersion = 2; - consentData.gdprApplies = true; - gdprDataHandlerStub.returns(consentData); - - curBidderStub.returns('sampleBidder1'); - adapterManagerStub.withArgs('sampleBidder1').returns({ - getSpec: function () { - return { - 'gvlid': 1 - } - } - }); - userSyncHook(nextFnSpy); - - curBidderStub.returns('sampleBidder2'); - adapterManagerStub.withArgs('sampleBidder2').returns({ - getSpec: function () { - return { - 'gvlid': 3 - } - } - }); - userSyncHook(nextFnSpy); - expect(nextFnSpy.calledTwice).to.equal(true); - expect(logWarnSpy.callCount).to.equal(0); + setupConsentData(); + Object.assign(gvlids, { + sampleBidder1: 1, + sampleBidder2: 3 + }) + Object.keys(gvlids).forEach(bidder => expect(syncUserRule(activityParams(MODULE_TYPE_BIDDER, bidder))).to.not.exist); }); }); - - describe('userIdHook', function () { - beforeEach(function () { - logWarnSpy = sinon.spy(utils, 'logWarn'); - nextFnSpy = sinon.spy(); - }); - afterEach(function () { - config.resetConfig(); - logWarnSpy.restore(); - }); + describe('enrichEidsRule', () => { it('should allow user id module if consent is given', function () { setEnforcementConfig({ gdpr: { @@ -476,38 +310,16 @@ describe('gdpr enforcement', function () { }] } }); - let consentData = {} - consentData.vendorData = staticConfig.consentData.getTCData; - consentData.apiVersion = 2; - consentData.gdprApplies = true; - let submodules = [{ - submodule: { - gvlid: 1, - name: 'sampleUserId' - } - }] - userIdHook(nextFnSpy, submodules, consentData); - // Should pass back hasValidated flag since version 2 - const args = nextFnSpy.getCalls()[0].args; - expect(args[1].hasValidated).to.be.true; - expect(nextFnSpy.calledOnce).to.equal(true); - sinon.assert.calledWith(nextFnSpy, submodules, { ...consentData, hasValidated: true }); + setupConsentData(); + gvlids.sampleUserId = 1; + expect(enrichEidsRule(activityParams(MODULE_TYPE_UID, 'sampleUserId'))).to.not.exist; }); it('should allow userId module if gdpr not in scope', function () { - let submodules = [{ - submodule: { - gvlid: 1, - name: 'sampleUserId' - } - }]; - let consentData = null; - userIdHook(nextFnSpy, submodules, consentData); - // Should not pass back hasValidated flag since version 2 - const args = nextFnSpy.getCalls()[0].args; - expect(args[1]).to.be.null; - expect(nextFnSpy.calledOnce).to.equal(true); - sinon.assert.calledWith(nextFnSpy, submodules, consentData); + gvlids.sampleUserId = 1; + const consent = setupConsentData({gdprApplies: false}); + consent.vendorData.purpose.consents['1'] = false; + expect(enrichEidsRule(activityParams(MODULE_TYPE_UID, 'sampleUserId'))).to.not.exist; }); it('should not allow user id module if user denied consent', function () { @@ -521,68 +333,23 @@ describe('gdpr enforcement', function () { }] } }); - let consentData = {} - consentData.vendorData = staticConfig.consentData.getTCData; - consentData.apiVersion = 2; - consentData.gdprApplies = true; - - let submodules = [{ - submodule: { - gvlid: 1, - name: 'sampleUserId' - } - }, { - submodule: { - gvlid: 3, - name: 'sampleUserId1' - } - }] - userIdHook(nextFnSpy, submodules, consentData); - expect(logWarnSpy.callCount).to.equal(1); - let expectedSubmodules = [{ - submodule: { - gvlid: 1, - name: 'sampleUserId' - } - }] - sinon.assert.calledWith(nextFnSpy, expectedSubmodules, { ...consentData, hasValidated: true }); + setupConsentData(); + Object.assign(gvlids, { + sampleUserId: 1, + sampleUserId1: 3 + }); + Object.entries({ + sampleUserId: true, + sampleUserId1: false + }).forEach(([name, allow]) => { + expectAllow(allow, enrichEidsRule(activityParams(MODULE_TYPE_UID, name))) + }); }); }); - describe('makeBidRequestsHook', function () { - let sandbox; - let adapterManagerStub; - let emitEventSpy; - - const MOCK_AD_UNITS = [{ - code: 'ad-unit-1', - mediaTypes: {}, - bids: [{ - bidder: 'bidder_1' // has consent - }, { - bidder: 'bidder_2' // doesn't have consent, but liTransparency is true. Bidder remains active. - }] - }, { - code: 'ad-unit-2', - mediaTypes: {}, - bids: [{ - bidder: 'bidder_2' - }, { - bidder: 'bidder_3' - }] - }]; - - beforeEach(function () { - sandbox = sinon.createSandbox(); - gdprDataHandlerStub = sandbox.stub(gdprDataHandler, 'getConsentData'); - adapterManagerStub = sandbox.stub(adapterManager, 'getBidAdapter'); - logWarnSpy = sandbox.spy(utils, 'logWarn'); - nextFnSpy = sandbox.spy(); - emitEventSpy = sandbox.spy(events, 'emit'); - }); + describe('fetchBidsRule', () => { afterEach(function () { config.resetConfig(); - sandbox.restore(); }); it('should block bidder which does not have consent and allow bidder which has consent (liTransparency is established)', function () { @@ -596,46 +363,12 @@ describe('gdpr enforcement', function () { }] } }); - const consentData = {}; - consentData.vendorData = staticConfig.consentData.getTCData; - consentData.apiVersion = 2; - consentData.gdprApplies = true; - - gdprDataHandlerStub.returns(consentData); - adapterManagerStub.withArgs('bidder_1').returns({ - getSpec: function () { - return { 'gvlid': 4 } - } - }); - adapterManagerStub.withArgs('bidder_2').returns({ - getSpec: function () { - return { 'gvlid': 5 } - } - }); - adapterManagerStub.withArgs('bidder_3').returns({ - getSpec: function () { - return { 'gvlid': undefined } - } + setupConsentData() + Object.assign(gvlids, { + bidder_1: 4, + bidder_2: 5, }); - makeBidRequestsHook(nextFnSpy, MOCK_AD_UNITS, []); - - // Assertions - expect(nextFnSpy.calledOnce).to.equal(true); - sinon.assert.calledWith(nextFnSpy, [{ - code: 'ad-unit-1', - mediaTypes: {}, - bids: [ - sinon.match({ bidder: 'bidder_1' }), - sinon.match({ bidder: 'bidder_2' }) - ] - }, { - code: 'ad-unit-2', - mediaTypes: {}, - bids: [ - sinon.match({ bidder: 'bidder_2' }), - sinon.match({ bidder: 'bidder_3' }) // should be allowed even though it's doesn't have a gvlId because liTransparency is established. - ] - }], []); + ['bidder_1', 'bidder_2', 'bidder_3'].forEach(bidder => expect(fetchBidsRule(activityParams(MODULE_TYPE_BIDDER, bidder))).to.not.exist); }); it('should block bidder which does not have consent and allow bidder which has consent (liTransparency is NOT established)', function() { @@ -649,110 +382,56 @@ describe('gdpr enforcement', function () { }] } }); - const consentData = {}; - - // set li for purpose 2 to false - const newConsentData = utils.deepClone(staticConfig); - newConsentData.consentData.getTCData.purpose.legitimateInterests['2'] = false; - - consentData.vendorData = newConsentData.consentData.getTCData; - consentData.apiVersion = 2; - consentData.gdprApplies = true; + const consent = setupConsentData(); + consent.vendorData.purpose.legitimateInterests['2'] = false; + Object.assign(gvlids, { + bidder_1: 4, + bidder_2: 5, + }) + Object.entries({ + bidder_1: true, + bidder_2: false, + bidder_3: true + }).forEach(([bidder, allowed]) => { + expectAllow(allowed, fetchBidsRule(activityParams(MODULE_TYPE_BIDDER, bidder))); + }) + }); - gdprDataHandlerStub.returns(consentData); - adapterManagerStub.withArgs('bidder_1').returns({ - getSpec: function () { - return { 'gvlid': 4 } - } - }); - adapterManagerStub.withArgs('bidder_2').returns({ - getSpec: function () { - return { 'gvlid': 5 } - } - }); - adapterManagerStub.withArgs('bidder_3').returns({ - getSpec: function () { - return { 'gvlid': undefined } + it('should skip validation checks if GDPR version is not equal to "2"', function () { + setEnforcementConfig({ + gdpr: { + rules: [{ + purpose: 'storage', + enforcePurpose: true, + enforceVendor: true, + vendorExceptions: [] + }] } }); - - makeBidRequestsHook(nextFnSpy, MOCK_AD_UNITS, []); - - // Assertions - expect(nextFnSpy.calledOnce).to.equal(true); - sinon.assert.calledWith(nextFnSpy, [{ - code: 'ad-unit-1', - mediaTypes: {}, - bids: [ - sinon.match({ bidder: 'bidder_1' }), // 'bidder_2' is not present because it doesn't have vendorConsent - ] - }, { - code: 'ad-unit-2', - mediaTypes: {}, - bids: [ - sinon.match({ bidder: 'bidder_3' }), // 'bidder_3' is allowed despite gvlId being undefined because it's part of vendorExceptions - ] - }], []); - - expect(logWarnSpy.calledOnce).to.equal(true); + const consent = setupConsentData(); + consent.vendorData.purpose.consents['2'] = false; + consent.apiVersion = 1; + ['bidder_1', 'bidder_2', 'bidder_3'].forEach(bidder => expect(fetchBidsRule(activityParams(MODULE_TYPE_BIDDER, bidder))).to.not.exist); }); - it('should skip validation checks if GDPR version is not equal to "2"', function () { + it('should skip validation if enforcePurpose is false', () => { setEnforcementConfig({ gdpr: { rules: [{ purpose: 'storage', - enforePurpose: false, - enforceVendor: false, + enforcePurpose: false, + enforceVendor: true, vendorExceptions: [] }] } }); - - const consentData = {}; - consentData.vendorData = staticConfig.consentData.getTCData; - consentData.apiVersion = 1; - consentData.gdprApplies = true; - gdprDataHandlerStub.returns(consentData); - - makeBidRequestsHook(nextFnSpy, MOCK_AD_UNITS, []); - - // Assertions - expect(nextFnSpy.calledOnce).to.equal(true); - sinon.assert.calledWith(nextFnSpy, sinon.match.array.deepEquals(MOCK_AD_UNITS), []); - expect(emitEventSpy.notCalled).to.equal(true); - expect(logWarnSpy.notCalled).to.equal(true); - }); + const consent = setupConsentData(); + consent.vendorData.purpose.consents['2'] = false; + ['bidder_1', 'bidder_2', 'bidder_3'].forEach(bidder => expect(fetchBidsRule(activityParams(MODULE_TYPE_BIDDER, bidder))).to.not.exist); + }) }); - describe('enableAnalyticsHook', function () { - let sandbox; - let adapterManagerStub; - - const MOCK_ANALYTICS_ADAPTER_CONFIG = [{ - provider: 'analyticsAdapter_A', - options: {} - }, { - provider: 'analyticsAdapter_B', - options: {} - }, { - provider: 'analyticsAdapter_C', - options: {} - }]; - - beforeEach(function () { - sandbox = sinon.createSandbox(); - gdprDataHandlerStub = sandbox.stub(gdprDataHandler, 'getConsentData'); - adapterManagerStub = sandbox.stub(adapterManager, 'getAnalyticsAdapter'); - logWarnSpy = sandbox.spy(utils, 'logWarn'); - nextFnSpy = sandbox.spy(); - }); - - afterEach(function() { - config.resetConfig(); - sandbox.restore(); - }); - + describe('reportAnalyticsRule', () => { it('should block analytics adapter which does not have consent and allow the one(s) which have consent', function() { setEnforcementConfig({ gdpr: { @@ -765,28 +444,61 @@ describe('gdpr enforcement', function () { } }); - const consentData = {}; - consentData.vendorData = staticConfig.consentData.getTCData; - consentData.apiVersion = 2; - consentData.gdprApplies = true; + Object.assign(gvlids, { + analyticsAdapter_A: 3, + analyticsAdapter_B: 5, + analyticsAdapter_C: 1 + }); - gdprDataHandlerStub.returns(consentData); - adapterManagerStub.withArgs('analyticsAdapter_A').returns({ gvlid: 3 }); - adapterManagerStub.withArgs('analyticsAdapter_B').returns({ gvlid: 5 }); - adapterManagerStub.withArgs('analyticsAdapter_C').returns({ gvlid: 1 }); + setupConsentData() - enableAnalyticsHook(nextFnSpy, MOCK_ANALYTICS_ADAPTER_CONFIG); + Object.entries({ + analyticsAdapter_A: false, + analyticsAdapter_B: true, + analyticsAdapter_C: true + }).forEach(([adapter, allow]) => { + expectAllow(allow, reportAnalyticsRule(activityParams(MODULE_TYPE_ANALYTICS, adapter))) + }) + }); + }); - // Assertions - expect(nextFnSpy.calledOnce).to.equal(true); - sinon.assert.calledWith(nextFnSpy, [{ - provider: 'analyticsAdapter_B', - options: {} - }, { - provider: 'analyticsAdapter_C', - options: {} - }]); - expect(logWarnSpy.calledOnce).to.equal(true); + describe('transmitUfpdRule', () => { + it('should allow when purpose 3 consent is given', () => { + setEnforcementConfig({ + gdpr: { + rules: [{ + purpose: 'personalizedAds', + enforcePurpose: true, + enforceVendor: true, + }] + } + }); + Object.assign(gvlids, { + mockBidder: 123 + }); + const consent = setupConsentData(); + consent.vendorData.purpose.consents[4] = true; + consent.vendorData.vendor.consents[123] = true; + expectAllow(true, transmitUfpdRule(activityParams(MODULE_TYPE_BIDDER, 'mockBidder'))); + }); + + it('should return deny when purpose 4 consent is withheld', () => { + setEnforcementConfig({ + gdpr: { + rules: [{ + purpose: 'personalizedAds', + enforcePurpose: true, + enforceVendor: true, + }] + } + }); + Object.assign(gvlids, { + mockBidder: 123 + }); + const consent = setupConsentData(); + consent.vendorData.purpose.consents[4] = true; + consent.vendorData.vendor.consents[123] = false; + expectAllow(false, transmitUfpdRule(activityParams(MODULE_TYPE_BIDDER, 'mockBidder'))) }); }); @@ -927,7 +639,8 @@ describe('gdpr enforcement', function () { Object.entries({ 'storage': 1, 'basicAds': 2, - 'measurement': 7 + 'measurement': 7, + 'personalizedAds': 4, }).forEach(([purpose, purposeNo]) => { describe(`for purpose ${purpose}`, () => { const rule = createGdprRule(purpose); @@ -1142,13 +855,13 @@ describe('gdpr enforcement', function () { }); describe('getGvlid', function() { - let getGvlidForBidAdapterStub; - let getGvlidForUserIdModuleStub; - let getGvlidForAnalyticsAdapterStub; + const MOCK_MODULE = 'moduleA'; + let entry; + beforeEach(function() { - getGvlidForBidAdapterStub = sandbox.stub(internal, 'getGvlidForBidAdapter'); - getGvlidForUserIdModuleStub = sandbox.stub(internal, 'getGvlidForUserIdModule'); - getGvlidForAnalyticsAdapterStub = sandbox.stub(internal, 'getGvlidForAnalyticsAdapter'); + entry = {modules: {}}; + GDPR_GVLIDS.get.reset(); + GDPR_GVLIDS.get.callsFake((mod) => mod === MOCK_MODULE ? entry : {modules: {}}); }); it('should return "null" if called without passing any argument', function() { @@ -1156,46 +869,63 @@ describe('gdpr enforcement', function () { expect(gvlid).to.equal(null); }); - it('should return "null" if GVL ID is not defined for any of these modules: Bid adapter, UserId submodule and Analytics adapter', function() { - getGvlidForBidAdapterStub.withArgs('moduleA').returns(null); - getGvlidForUserIdModuleStub.withArgs('moduleA').returns(null); - getGvlidForAnalyticsAdapterStub.withArgs('moduleA').returns(null); - - const gvlid = getGvlid('moduleA'); + it('should return "null" if no GVL ID was registered', function() { + const gvlid = getGvlid('type', MOCK_MODULE); expect(gvlid).to.equal(null); }); - it('should return the GVL ID from gvlMapping if it is defined in setConfig', function() { - config.setConfig({ - gvlMapping: { - moduleA: 1 - } - }); - - // Actual GVL ID for moduleA is 2, as defined on its the bidAdapter.js file. - getGvlidForBidAdapterStub.withArgs('moduleA').returns(2); - - const gvlid = getGvlid('moduleA'); - expect(gvlid).to.equal(1); - }); - - it('should return the GVL ID by calling getGvlidForBidAdapter -> getGvlidForUserIdModule -> getGvlidForAnalyticsAdapter in sequence', function() { - getGvlidForBidAdapterStub.withArgs('moduleA').returns(null); - getGvlidForUserIdModuleStub.withArgs('moduleA').returns(null); - getGvlidForAnalyticsAdapterStub.withArgs('moduleA').returns(7); + it('should return null if the wrong GVL ID was registered', () => { + entry = {gvlid: 123}; + expect(getGvlid('type', 'someOtherModule')).to.equal(null); + }) - expect(getGvlid('moduleA')).to.equal(7); - }); + Object.entries({ + 'without fallback': null, + 'with fallback': () => 'shouldBeIgnored' + }).forEach(([t, fallbackFn]) => { + describe(t, () => { + it('should return the GVL ID from gvlMapping if it is defined in setConfig', function() { + config.setConfig({ + gvlMapping: { + [MOCK_MODULE]: 1 + } + }); + + entry = {gvlid: 2}; + + const gvlid = getGvlid('type', MOCK_MODULE, fallbackFn); + expect(gvlid).to.equal(1); + }); + + it('should return the GVL ID that was registered', function() { + entry = {gvlid: 7}; + expect(getGvlid('type', MOCK_MODULE, fallbackFn)).to.equal(7); + }); + + it('should return VENDORLESS_GVLID for core modules', () => { + entry = {gvlid: 123}; + expect(getGvlid(MODULE_TYPE_PREBID, MOCK_MODULE, fallbackFn)).to.equal(VENDORLESS_GVLID); + }); + + describe('multiple GVL IDs are found', () => { + it('should use bidder over others', () => { + entry = {modules: {[MODULE_TYPE_BIDDER]: 123, [MODULE_TYPE_UID]: 321}}; + expect(getGvlid(MODULE_TYPE_UID, MOCK_MODULE, fallbackFn)).to.equal(123); + }); + it('should use uid over analytics', () => { + entry = {modules: {[MODULE_TYPE_UID]: 123, [MODULE_TYPE_ANALYTICS]: 321}}; + expect(getGvlid(MODULE_TYPE_ANALYTICS, MOCK_MODULE, fallbackFn)).to.equal(123); + }) + }) + }) + }) - it('should pass extra arguments to analytics\' getGvlid', () => { - getGvlidForAnalyticsAdapterStub.withArgs('analytics').returns(321); - const cfg = {some: 'args'}; - getGvlid('analytics', cfg); - sinon.assert.calledWith(getGvlidForAnalyticsAdapterStub, 'analytics', cfg); + it('should use fallbackFn if no other lookup produces a gvl id', () => { + expect(getGvlid('type', MOCK_MODULE, () => 321)).to.equal(321); }); }); - describe('getGvlidForAnalyticsAdapter', () => { + describe('getGvlidFromAnalyticsConfig', () => { let getAnalyticsAdapter, adapter, adapterEntry; beforeEach(() => { @@ -1207,26 +937,20 @@ describe('gdpr enforcement', function () { getAnalyticsAdapter.withArgs('analytics').returns(adapterEntry); }); - it('should return gvlid from adapterManager if defined', () => { - adapterEntry.gvlid = 123; - adapter.gvlid = 321 - expect(internal.getGvlidForAnalyticsAdapter('analytics')).to.equal(123); - }); - it('should return gvlid from adapter if defined', () => { adapter.gvlid = 321; - expect(internal.getGvlidForAnalyticsAdapter('analytics')).to.equal(321); + expect(getGvlidFromAnalyticsAdapter('analytics')).to.equal(321); }); it('should invoke adapter.gvlid if it\'s a function', () => { adapter.gvlid = (cfg) => cfg.k const cfg = {k: 231}; - expect(internal.getGvlidForAnalyticsAdapter('analytics', cfg)).to.eql(231); + expect(getGvlidFromAnalyticsAdapter('analytics', cfg)).to.eql(231); }); it('should not choke if adapter gvlid fn throws', () => { adapter.gvlid = () => { throw new Error(); }; - expect(internal.getGvlidForAnalyticsAdapter('analytics')).to.not.be.ok; + expect(getGvlidFromAnalyticsAdapter('analytics')).to.not.be.ok; }); }); }) diff --git a/test/spec/modules/glimpseBidAdapter_spec.js b/test/spec/modules/glimpseBidAdapter_spec.js deleted file mode 100644 index 353e61c7859..00000000000 --- a/test/spec/modules/glimpseBidAdapter_spec.js +++ /dev/null @@ -1,422 +0,0 @@ -import { expect } from 'chai'; -import { spec } from 'modules/glimpseBidAdapter.js'; -import { newBidder } from 'src/adapters/bidderFactory.js'; -import { config } from 'src/config'; -import { BANNER } from '../../../src/mediaTypes'; - -const ENDPOINT = 'https://market.glimpsevault.io/public/v1/prebid'; - -const nonStringValues = [null, undefined, 123, true, {}, [], () => {}]; -const nonArrayValues = [null, undefined, 123, true, {}, 'str', () => {}]; - -const mock = { - bidRequest: { - bidder: 'glimpse', - bidId: '26a80b71cfd671', - bidderRequestId: '133baeded6ac94', - auctionId: '96692a73-307b-44b8-8e4f-ddfb40341570', - adUnitCode: 'banner-div-a', - sizes: [[300, 250]], - params: { - pid: 'glimpse-demo-300x250', - }, - }, - bidderRequest: { - bidderCode: 'glimpse', - bidderRequestId: '133baeded6ac94', - auctionId: '96692a73-307b-44b8-8e4f-ddfb40341570', - timeout: 3000, - gdprConsent: { - consentString: - 'COzP517OzP517AcABBENAlCsAP_AAAAAAAwIF8NX-T5eL2vju2Zdt7JEaYwfZxyigOgThgQIsW8NwIeFbBoGP2EgHBG4JCQAGBAkkgCBAQMsHGBcCQAAgIgRiRKMYE2MjzNKBJJAigkbc0FACDVunsHS2ZCY70-8O__bPAviADAvUC-AAAAA.YAAAAAAAAAAA', - vendorData: {}, - gdprApplies: true, - }, - uspConsent: '1YYY', - refererInfo: { - numIframes: 0, - reachedTop: true, - page: 'https://demo.glimpseprotocol.io/prebid/desktop', - stack: ['https://demo.glimpseprotocol.io/prebid/desktop'], - }, - }, - bidResponse: { - auth: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJoZWxsbyI6IndvcmxkISJ9.1p6T0ORhJ6riLprhXBGdzRhG3Q1egM27uFhPGNapPxs', - data: { - bids: [ - { - requestId: '133baeded6ac94', - creativeId: 'glimpse-demo-300x250', - adUnitCode: 'banner-div-a', - currency: 'GBP', - ad: '
Hello, World!
', - width: 300, - height: 250, - cpm: 1.04, - netRevenue: true, - mediaType: 'banner', - ttl: 300, - }, - ], - }, - }, -}; - -const getBidRequest = () => getDeepCopy(mock.bidRequest); -const getBidderRequest = () => ({ - bids: [getBidRequest()], - ...getDeepCopy(mock.bidderRequest), -}); - -const getBidResponse = () => ({ - body: getDeepCopy(mock.bidResponse), -}); - -function getDeepCopy(object) { - return JSON.parse(JSON.stringify(object)); -} - -describe('GlimpseProtocolAdapter', () => { - const glimpseAdapter = newBidder(spec); - - describe('spec', () => { - it('Has defined the glimpse gvlid', () => { - expect(spec.gvlid).to.equal(1012); - }); - - it('Has defined glimpse as the bidder', () => { - expect(spec.code).to.equal('glimpse'); - }); - - it('Has defined valid mediaTypes', () => { - expect(spec.supportedMediaTypes).to.deep.equal([BANNER]); - }); - }); - - describe('Inherited functions', () => { - it('Functions exist and are valid types', () => { - expect(glimpseAdapter.callBids).to.exist.and.to.be.a('function'); - expect(glimpseAdapter.getSpec).to.exist.and.to.be.a('function'); - }); - }); - - describe('isBidRequestValid', () => { - it('Returns true if placement id is non-empty string', () => { - const bidRequest = getBidRequest(); - - const isValidBidRequest = spec.isBidRequestValid(bidRequest); - expect(isValidBidRequest).to.be.true; - }); - - it('Returns false if no pid is provided', () => { - const bidRequest = getBidRequest(); - delete bidRequest.params.pid; - - const isValidBidRequest = spec.isBidRequestValid(bidRequest); - expect(isValidBidRequest).to.be.false; - }); - - it('Returns false if pid is empty string', () => { - const bidRequest = getBidRequest(); - bidRequest.params.pid = ''; - - const isValidBidRequest = spec.isBidRequestValid(bidRequest); - expect(isValidBidRequest).to.be.false; - }); - - it('Returns false if pid is not string', () => { - const bidRequest = getBidRequest(); - const invalidPids = nonStringValues; - - invalidPids.forEach((invalidPid) => { - bidRequest.params.pid = invalidPid; - const isValidBidRequest = spec.isBidRequestValid(bidRequest); - expect(isValidBidRequest).to.be.false; - }); - }); - }); - - describe('buildRequests', () => { - const bidRequests = [getBidRequest()]; - const bidderRequest = getBidderRequest(); - - it('Adds additional info to api request query', () => { - const request = spec.buildRequests(bidRequests, bidderRequest); - const url = new URL(request.url); - const queries = new URLSearchParams(url.search); - - expect(queries.get('ver')).to.exist; - expect(queries.get('tmax')).to.exist; - expect(queries.get('gdpr')).to.equal( - bidderRequest.gdprConsent.consentString - ); - expect(queries.get('ccpa')).to.equal(bidderRequest.uspConsent); - }); - - it('Has correct payload shape', () => { - const request = spec.buildRequests(bidRequests, bidderRequest); - const payload = JSON.parse(request.data); - - expect(payload.auth).to.be.a('string'); - expect(payload.data).to.be.an('object'); - expect(payload.data.referer).to.be.a('string'); - expect(payload.data.imp).to.be.an('array'); - expect(payload.data.fpd).to.be.an('object'); - }); - - it('Has referer information', () => { - const request = spec.buildRequests(bidRequests, bidderRequest); - const payload = JSON.parse(request.data); - const expected = mock.bidderRequest.refererInfo.page; - - expect(payload.data.referer).to.equal(expected); - }); - - it('Has correct bids (imp) shape', () => { - const request = spec.buildRequests(bidRequests, bidderRequest); - const payload = JSON.parse(request.data); - const imp = payload.data.imp; - - imp.forEach((i) => { - expect(i.bid).to.be.a('string'); - expect(i.pid).to.be.a('string'); - expect(i.sizes).to.be.an('array').that.deep.include([300, 250]); - }); - }); - }); - - describe('interpretResponse', () => { - it('Returns valid bids', () => { - const bidResponse = getBidResponse(); - const bids = spec.interpretResponse(bidResponse); - - expect(bids).to.have.lengthOf(1); - expect(bids[0].adUnitCode).to.equal(mock.bidRequest.adUnitCode); - }); - - it('Returns no bids if auth is not string', () => { - const bidResponse = getBidResponse(); - const invalidAuths = nonStringValues; - - invalidAuths.forEach((invalidAuth) => { - bidResponse.body.auth = invalidAuth; - - const bids = spec.interpretResponse(bidResponse); - expect(bids).to.have.lengthOf(0); - }); - }); - - it('Returns no bids if bids is empty', () => { - const bidResponse = getBidResponse(); - bidResponse.body.data.bids = []; - - const bids = spec.interpretResponse(bidResponse); - expect(bids).to.have.lengthOf(0); - }); - - it('Returns no bids if bids is not array', () => { - const bidResponse = getBidResponse(); - const invalidBids = nonArrayValues; - - invalidBids.forEach((invalidBid) => { - bidResponse.body.data.bids = invalidBid; - - const bids = spec.interpretResponse(bidResponse); - expect(bids).to.have.lengthOf(0); - }); - }); - - it('Contains advertiserDomains', () => { - const bidResponse = getBidResponse(); - - const bids = spec.interpretResponse(bidResponse); - bids.forEach((bid) => { - expect(bid.meta.advertiserDomains).to.be.an('array'); - }); - }); - }); - - describe('optimize request fpd data', () => { - const bidRequests = [getBidRequest()]; - const bidderRequest = getBidderRequest(); - - const fpdMockBase = { - site: { - keywords: 'site,keywords', - ext: { - data: { - fpdProvider: { - dataArray: ['data1', 'data2'], - dataObject: { - data1: 'data1', - data2: 'data2', - }, - dataString: 'data1,data2', - }, - }, - }, - }, - user: { - keywords: 'user,keywords', - ext: { - data: { - fpdProvider: { - dataArray: ['data1', 'data2'], - dataObject: { - data1: 'data1', - data2: 'data2', - }, - dataString: 'data1,data2', - }, - }, - }, - }, - }; - - it('should keep all non-empty fields', () => { - const fpdMock = fpdMockBase; - const expected = fpdMockBase; - - const request = spec.buildRequests(bidRequests, {...bidderRequest, ortb2: fpdMock}); - const payload = JSON.parse(request.data); - const fpd = payload.data.fpd; - - expect(fpd).to.deep.equal(expected); - }); - - it('should remove all empty objects', () => { - const fpdMock = getDeepCopy(fpdMockBase); - fpdMock.site.ext.data.fpdProvider.dataObject = {}; - fpdMock.user.ext.data.fpdProvider = {}; - - const expected = { - site: { - keywords: 'site,keywords', - ext: { - data: { - fpdProvider: { - dataArray: ['data1', 'data2'], - dataString: 'data1,data2', - }, - }, - }, - }, - user: { - keywords: 'user,keywords', - }, - }; - - const request = spec.buildRequests(bidRequests, {...bidderRequest, ortb2: fpdMock}); - const payload = JSON.parse(request.data); - const fpd = payload.data.fpd; - - expect(fpd).to.deep.equal(expected); - }); - - it('should remove all empty arrays', () => { - const fpdMock = getDeepCopy(fpdMockBase); - fpdMock.site.ext.data.fpdProvider.dataArray = []; - fpdMock.user.ext.data.fpdProvider.dataArray = []; - - const expected = { - site: { - keywords: 'site,keywords', - ext: { - data: { - fpdProvider: { - dataObject: { - data1: 'data1', - data2: 'data2', - }, - dataString: 'data1,data2', - }, - }, - }, - }, - user: { - keywords: 'user,keywords', - ext: { - data: { - fpdProvider: { - dataObject: { - data1: 'data1', - data2: 'data2', - }, - dataString: 'data1,data2', - }, - }, - }, - }, - }; - - const request = spec.buildRequests(bidRequests, {...bidderRequest, ortb2: fpdMock}); - const payload = JSON.parse(request.data); - const fpd = payload.data.fpd; - - expect(fpd).to.deep.equal(expected); - }); - - it('should remove all empty strings', () => { - const fpdMock = getDeepCopy(fpdMockBase); - fpdMock.site.keywords = ''; - fpdMock.site.ext.data.fpdProvider.dataString = ''; - fpdMock.user.keywords = ''; - fpdMock.user.ext.data.fpdProvider.dataString = ''; - - const expected = { - site: { - ext: { - data: { - fpdProvider: { - dataArray: ['data1', 'data2'], - dataObject: { - data1: 'data1', - data2: 'data2', - }, - }, - }, - }, - }, - user: { - ext: { - data: { - fpdProvider: { - dataArray: ['data1', 'data2'], - dataObject: { - data1: 'data1', - data2: 'data2', - }, - }, - }, - }, - }, - }; - - const request = spec.buildRequests(bidRequests, {...bidderRequest, ortb2: fpdMock}); - const payload = JSON.parse(request.data); - const fpd = payload.data.fpd; - - expect(fpd).to.deep.equal(expected); - }); - - it('should remove all empty fields', () => { - const fpdMock = getDeepCopy(fpdMockBase); - fpdMock.site.keywords = ''; - fpdMock.site.ext.data.fpdProvider.dataArray = []; - fpdMock.site.ext.data.fpdProvider.dataObject = {}; - fpdMock.site.ext.data.fpdProvider.dataString = ''; - fpdMock.user.keywords = ''; - fpdMock.user.ext.data.fpdProvider.dataArray = []; - fpdMock.user.ext.data.fpdProvider.dataObject = {}; - fpdMock.user.ext.data.fpdProvider.dataString = ''; - - const expected = {}; - - const request = spec.buildRequests(bidRequests, {...bidderRequest, ortb2: fpdMock}); - const payload = JSON.parse(request.data); - const fpd = payload.data.fpd; - - expect(fpd).to.deep.equal(expected); - }); - }); -}); diff --git a/test/spec/modules/globalsunBidAdapter_spec.js b/test/spec/modules/globalsunBidAdapter_spec.js index 3795f3038a7..0d17c25363d 100644 --- a/test/spec/modules/globalsunBidAdapter_spec.js +++ b/test/spec/modules/globalsunBidAdapter_spec.js @@ -76,7 +76,8 @@ describe('GlobalsunBidAdapter', function () { gdprConsent: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', refererInfo: { referer: 'https://test.com' - } + }, + timeout: 500 }; describe('isBidRequestValid', function () { diff --git a/test/spec/modules/gmosspBidAdapter_spec.js b/test/spec/modules/gmosspBidAdapter_spec.js index 6d290504194..8c3aa6c94cb 100644 --- a/test/spec/modules/gmosspBidAdapter_spec.js +++ b/test/spec/modules/gmosspBidAdapter_spec.js @@ -50,7 +50,11 @@ describe('GmosspAdapter', function () { bidId: '2b84475b5b636e', bidderRequestId: '1f4001782ac16c', auctionId: 'aba03555-4802-4c45-9f15-05ffa8594cff', - transactionId: '791e9d84-af92-4903-94da-24c7426d9d0c', + ortb2Imp: { + ext: { + tid: '791e9d84-af92-4903-94da-24c7426d9d0c', + } + }, userId: { imuid: 'h.0a4749e7ffe09fa6', pubcid: '1111', diff --git a/test/spec/modules/gnetBidAdapter_spec.js b/test/spec/modules/gnetBidAdapter_spec.js index 21526aba201..f1af3b71103 100644 --- a/test/spec/modules/gnetBidAdapter_spec.js +++ b/test/spec/modules/gnetBidAdapter_spec.js @@ -63,7 +63,11 @@ describe('gnetAdapter', function () { bidId: '2a19afd5173318', bidderRequestId: '1f4001782ac16c', auctionId: 'aba03555-4802-4c45-9f15-05ffa8594cff', - transactionId: '894bdff6-61ec-4bec-a5a9-f36a5bfccef5', + ortb2Imp: { + ext: { + tid: '894bdff6-61ec-4bec-a5a9-f36a5bfccef5', + } + }, gftuid: null }]; diff --git a/test/spec/modules/goldbachBidAdapter_spec.js b/test/spec/modules/goldbachBidAdapter_spec.js index fc2c1beb900..93956d2caf9 100644 --- a/test/spec/modules/goldbachBidAdapter_spec.js +++ b/test/spec/modules/goldbachBidAdapter_spec.js @@ -1001,15 +1001,6 @@ describe('GoldbachXandrAdapter', function () { }) describe('interpretResponse', function () { - let bfStub; - before(function() { - bfStub = sinon.stub(bidderFactory, 'getIabSubCategory'); - }); - - after(function() { - bfStub.restore(); - }); - let response = { 'version': '3.0.0', 'tags': [ @@ -1206,7 +1197,6 @@ describe('GoldbachXandrAdapter', function () { } }] }; - bfStub.returns('1'); let result = spec.interpretResponse({ body: response }, {bidderRequest}); expect(result[0]).to.have.property('vastUrl'); diff --git a/test/spec/modules/googleAnalyticsAdapter_spec.js b/test/spec/modules/googleAnalyticsAdapter_spec.js deleted file mode 100644 index b801b5fe696..00000000000 --- a/test/spec/modules/googleAnalyticsAdapter_spec.js +++ /dev/null @@ -1,27 +0,0 @@ -import ga from 'modules/googleAnalyticsAdapter.js'; - -var assert = require('assert'); - -describe('Ga', function () { - describe('enableAnalytics', function () { - var cpmDistribution = function(cpm) { - return cpm <= 1 ? '<= 1$' : '> 1$'; - } - var config = { options: { trackerName: 'foo', enableDistribution: true, cpmDistribution: cpmDistribution } }; - - // enableAnalytics can only be called once - ga.enableAnalytics(config); - - it('should accept a tracker name option and output prefixed send string', function () { - var output = ga.getTrackerSend(); - assert.equal(output, 'foo.send'); - }); - - it('should use the custom cpm distribution', function() { - assert.equal(ga.getCpmDistribution(0.5), '<= 1$'); - assert.equal(ga.getCpmDistribution(1), '<= 1$'); - assert.equal(ga.getCpmDistribution(2), '> 1$'); - assert.equal(ga.getCpmDistribution(5.23), '> 1$'); - }); - }); -}); diff --git a/test/spec/modules/greenbidsAnalyticsAdapter_spec.js b/test/spec/modules/greenbidsAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..870fbd23870 --- /dev/null +++ b/test/spec/modules/greenbidsAnalyticsAdapter_spec.js @@ -0,0 +1,403 @@ +import { + greenbidsAnalyticsAdapter, parseBidderCode, + ANALYTICS_VERSION, BIDDER_STATUS +} from 'modules/greenbidsAnalyticsAdapter.js'; + +import {expect} from 'chai'; +import sinon from 'sinon'; + +const events = require('src/events'); +const constants = require('src/constants.json'); + +const pbuid = 'pbuid-AA778D8A796AEA7A0843E2BBEB677766'; +const auctionId = 'b0b39610-b941-4659-a87c-de9f62d3e13e'; + +describe('Greenbids Prebid AnalyticsAdapter Testing', function () { + describe('event tracking and message cache manager', function () { + beforeEach(function () { + const configOptions = { + pbuid: pbuid, + sampling: 0, + }; + + greenbidsAnalyticsAdapter.enableAnalytics({ + provider: 'greenbidsAnalytics', + options: configOptions + }); + }); + + afterEach(function () { + greenbidsAnalyticsAdapter.disableAnalytics(); + }); + + describe('#parseBidderCode()', function() { + it('should get lower case bidder code from bidderCode field value', function() { + const receivedBids = [ + { + auctionId: auctionId, + adUnitCode: 'adunit_1', + bidder: 'greenbids', + bidderCode: 'GREENBIDS', + requestId: 'a1b2c3d4', + timeToRespond: 72, + cpm: 0.1, + currency: 'USD', + ad: 'fake ad1' + }, + ]; + const result = parseBidderCode(receivedBids[0]); + expect(result).to.equal('greenbids'); + }); + it('should get lower case bidder code from bidder field value as bidderCode field is missing', function() { + const receivedBids = [ + { + auctionId: auctionId, + adUnitCode: 'adunit_1', + bidder: 'greenbids', + bidderCode: '', + requestId: 'a1b2c3d4', + timeToRespond: 72, + cpm: 0.1, + currency: 'USD', + ad: 'fake ad1' + }, + ]; + const result = parseBidderCode(receivedBids[0]); + expect(result).to.equal('greenbids'); + }); + }); + + describe('#getCachedAuction()', function() { + const existing = {timeoutBids: [{}]}; + greenbidsAnalyticsAdapter.cachedAuctions['test_auction_id'] = existing; + + it('should get the existing cached object if it exists', function() { + const result = greenbidsAnalyticsAdapter.getCachedAuction('test_auction_id'); + + expect(result).to.equal(existing); + }); + + it('should create a new object and store it in the cache on cache miss', function() { + const result = greenbidsAnalyticsAdapter.getCachedAuction('no_such_id'); + + expect(result).to.deep.include({ + timeoutBids: [], + }); + }); + }); + + describe('when formatting JSON payload sent to backend', function() { + const receivedBids = [ + { + auctionId: auctionId, + adUnitCode: 'adunit-1', + bidder: 'greenbids', + bidderCode: 'greenbids', + requestId: 'a1b2c3d4', + timeToRespond: 72, + cpm: 0.1, + currency: 'USD', + ad: 'fake ad1' + }, + { + auctionId: auctionId, + adUnitCode: 'adunit-1', + bidder: 'greenbidsx', + bidderCode: 'greenbidsx', + requestId: 'b2c3d4e5', + timeToRespond: 100, + cpm: 0.08, + currency: 'USD', + ad: 'fake ad2' + }, + { + auctionId: auctionId, + adUnitCode: 'adunit-2', + bidder: 'greenbids', + bidderCode: 'greenbids', + requestId: 'c3d4e5f6', + timeToRespond: 120, + cpm: 0.09, + currency: 'USD', + ad: 'fake ad3' + }, + ]; + const noBids = [ + { + auctionId: auctionId, + adUnitCode: 'adunit-2', + bidder: 'greenbids', + bidderCode: 'greenbids', + bidId: 'a1b2c3d4', + } + ]; + const timeoutBids = [ + { + auctionId: auctionId, + adUnitCode: 'adunit-2', + bidder: 'greenbids', + bidderCode: 'greenbids', + bidId: '00123d4c', + } + ]; + function assertHavingRequiredMessageFields(message) { + expect(message).to.include({ + version: ANALYTICS_VERSION, + auctionId: auctionId, + pbuid: pbuid, + referrer: window.location.href, + sampling: 0, + prebid: '$prebid.version$', + }); + } + + describe('#createCommonMessage', function() { + it('should correctly serialize some common fields', function() { + const message = greenbidsAnalyticsAdapter.createCommonMessage(auctionId); + + assertHavingRequiredMessageFields(message); + }); + }); + + describe('#serializeBidResponse', function() { + it('should handle BID properly with timeout false and hasBid true', function() { + const result = greenbidsAnalyticsAdapter.serializeBidResponse(receivedBids[0], BIDDER_STATUS.BID); + + expect(result).to.include({ + bidder: 'greenbids', + isTimeout: false, + hasBid: true, + }); + }); + + it('should handle NO_BID properly and set hasBid to false', function() { + const result = greenbidsAnalyticsAdapter.serializeBidResponse(noBids[0], BIDDER_STATUS.NO_BID); + + expect(result).to.include({ + bidder: 'greenbids', + isTimeout: false, + hasBid: false, + }); + }); + + it('should handle TIMEOUT properly and set isTimeout to true', function() { + const result = greenbidsAnalyticsAdapter.serializeBidResponse(noBids[0], BIDDER_STATUS.TIMEOUT); + + expect(result).to.include({ + bidder: 'greenbids', + isTimeout: true, + hasBid: false, + }); + }); + }); + + describe('#addBidResponseToMessage()', function() { + it('should add a bid response in the output message, grouped by adunit_id and bidder', function() { + const message = { + adUnits: [ + { + code: 'adunit-2', + bidders: [] + } + ] + }; + greenbidsAnalyticsAdapter.addBidResponseToMessage(message, noBids[0], BIDDER_STATUS.NO_BID); + + expect(message.adUnits[0]).to.deep.include({ + code: 'adunit-2', + bidders: [ + { + bidder: 'greenbids', + isTimeout: false, + hasBid: false, + } + ] + }); + }); + }); + + describe('#createBidMessage()', function() { + it('should format auction message sent to the backend', function() { + const args = { + auctionId: auctionId, + timestamp: 1234567890, + timeout: 3000, + auctionEnd: 1234567990, + adUnitCodes: ['adunit-1', 'adunit-2'], + adUnits: [ + { + code: 'adunit-1', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + }, + } + }, + { + code: 'adunit-2', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + }, + video: { + context: 'instream', + mimes: ['video/mp4'], + playerSize: [[640, 480]], + skip: 1, + protocols: [1, 2, 3, 4] + }, + } + }, + ], + bidsReceived: receivedBids, + noBids: noBids + }; + + const result = greenbidsAnalyticsAdapter.createBidMessage(args, timeoutBids); + + assertHavingRequiredMessageFields(result); + expect(result).to.deep.include({ + auctionElapsed: 100, + adUnits: [ + { + code: 'adunit-1', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + bidders: [ + { + bidder: 'greenbids', + isTimeout: false, + hasBid: true + }, + { + bidder: 'greenbidsx', + isTimeout: false, + hasBid: true + } + ] + }, + { + code: 'adunit-2', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + }, + video: { + context: 'instream', + mimes: ['video/mp4'], + playerSize: [[640, 480]], + skip: 1, + protocols: [1, 2, 3, 4] + } + }, + bidders: [ + { + bidder: 'greenbids', + isTimeout: true, + hasBid: true + } + ] + } + ], + }); + }); + }); + + describe('#handleBidTimeout()', function() { + it('should cached the timeout bid as BID_TIMEOUT event was triggered', function() { + greenbidsAnalyticsAdapter.cachedAuctions['test_timeout_auction_id'] = { 'timeoutBids': [] }; + const args = [{ + auctionId: 'test_timeout_auction_id', + timestamp: 1234567890, + timeout: 3000, + auctionEnd: 1234567990, + bidsReceived: receivedBids, + noBids: noBids + }]; + + greenbidsAnalyticsAdapter.handleBidTimeout(args); + const result = greenbidsAnalyticsAdapter.getCachedAuction('test_timeout_auction_id'); + expect(result).to.deep.include({ + timeoutBids: [{ + auctionId: 'test_timeout_auction_id', + timestamp: 1234567890, + timeout: 3000, + auctionEnd: 1234567990, + bidsReceived: receivedBids, + noBids: noBids + }] + }); + }); + }); + }); + }); + + describe('greenbids Analytics Adapter track handler ', function () { + const configOptions = { + pbuid: pbuid, + sampling: 1, + }; + + beforeEach(function () { + sinon.stub(events, 'getEvents').returns([]); + greenbidsAnalyticsAdapter.enableAnalytics({ + provider: 'greenbidsAnalytics', + options: configOptions + }); + }); + + afterEach(function () { + greenbidsAnalyticsAdapter.disableAnalytics(); + events.getEvents.restore(); + }); + + it('should call handleBidTimeout as BID_TIMEOUT trigger event', function() { + sinon.spy(greenbidsAnalyticsAdapter, 'handleBidTimeout'); + events.emit(constants.EVENTS.BID_TIMEOUT, {}); + sinon.assert.callCount(greenbidsAnalyticsAdapter.handleBidTimeout, 1); + greenbidsAnalyticsAdapter.handleBidTimeout.restore(); + }); + + it('should call handleAuctionEnd as AUCTION_END trigger event', function() { + sinon.spy(greenbidsAnalyticsAdapter, 'handleAuctionEnd'); + events.emit(constants.EVENTS.AUCTION_END, {}); + sinon.assert.callCount(greenbidsAnalyticsAdapter.handleAuctionEnd, 1); + greenbidsAnalyticsAdapter.handleAuctionEnd.restore(); + }); + }); + + describe('enableAnalytics and config parser', function () { + const configOptions = { + pbuid: pbuid, + sampling: 0, + }; + + beforeEach(function () { + greenbidsAnalyticsAdapter.enableAnalytics({ + provider: 'greenbidsAnalytics', + options: configOptions + }); + }); + + afterEach(function () { + greenbidsAnalyticsAdapter.disableAnalytics(); + }); + + it('should parse config correctly with optional values', function () { + expect(greenbidsAnalyticsAdapter.getAnalyticsOptions().options).to.deep.equal(configOptions); + expect(greenbidsAnalyticsAdapter.getAnalyticsOptions().pbuid).to.equal(configOptions.pbuid); + }); + + it('should not enable Analytics when pbuid is missing', function () { + const configOptions = { + options: { + } + }; + const validConfig = greenbidsAnalyticsAdapter.initConfig(configOptions); + expect(validConfig).to.equal(false); + }); + }); +}); diff --git a/test/spec/modules/greenbidsRtdProvider_spec.js b/test/spec/modules/greenbidsRtdProvider_spec.js new file mode 100644 index 00000000000..6b7d826ab06 --- /dev/null +++ b/test/spec/modules/greenbidsRtdProvider_spec.js @@ -0,0 +1,210 @@ +import { expect } from 'chai'; +import sinon from 'sinon'; +import { + deepClone, +} from '../../../src/utils.js'; +import { + greenbidsSubmodule +} from 'modules/greenbidsRtdProvider.js'; + +describe('greenbidsRtdProvider', () => { + let server; + + beforeEach(() => { + server = sinon.createFakeServer(); + }); + + afterEach(() => { + server.restore(); + }); + + const endPoint = 'europe-west1-greenbids-357713.cloudfunctions.net'; + + const SAMPLE_MODULE_CONFIG = { + params: { + pbuid: '12345', + timeout: 200, + targetTPR: 0.95 + } + }; + + const SAMPLE_REQUEST_BIDS_CONFIG_OBJ = { + adUnits: [ + { + code: 'adUnit1', + bids: [ + { bidder: 'appnexus', params: {} }, + { bidder: 'rubicon', params: {} }, + { bidder: 'ix', params: {} } + ] + }, + { + code: 'adUnit2', + bids: [ + { bidder: 'appnexus', params: {} }, + { bidder: 'rubicon', params: {} }, + { bidder: 'openx', params: {} } + ] + }] + }; + + const SAMPLE_RESPONSE_ADUNITS = [ + { + code: 'adUnit1', + bidders: { + 'appnexus': true, + 'rubicon': false, + 'ix': true + } + }, + { + code: 'adUnit2', + bidders: { + 'appnexus': false, + 'rubicon': true, + 'openx': true + } + }]; + + describe('init', () => { + it('should return true and set rtdOptions if pbuid is present', () => { + const result = greenbidsSubmodule.init(SAMPLE_MODULE_CONFIG); + expect(result).to.be.true; + }); + + it('should return false if pbuid is not present', () => { + const result = greenbidsSubmodule.init({ params: {} }); + expect(result).to.be.false; + }); + }); + + describe('updateAdUnitsBasedOnResponse', () => { + it('should update ad units based on response', () => { + const adUnits = JSON.parse(JSON.stringify(SAMPLE_REQUEST_BIDS_CONFIG_OBJ.adUnits)); + greenbidsSubmodule.updateAdUnitsBasedOnResponse(adUnits, SAMPLE_RESPONSE_ADUNITS); + + expect(adUnits[0].bids).to.have.length(2); + expect(adUnits[1].bids).to.have.length(2); + }); + }); + + describe('findMatchingAdUnit', () => { + it('should find matching ad unit by code', () => { + const matchingAdUnit = greenbidsSubmodule.findMatchingAdUnit(SAMPLE_RESPONSE_ADUNITS, 'adUnit1'); + expect(matchingAdUnit).to.deep.equal(SAMPLE_RESPONSE_ADUNITS[0]); + }); + it('should return undefined if no matching ad unit is found', () => { + const matchingAdUnit = greenbidsSubmodule.findMatchingAdUnit(SAMPLE_RESPONSE_ADUNITS, 'nonexistent'); + expect(matchingAdUnit).to.be.undefined; + }); + }); + + describe('removeFalseBidders', () => { + it('should remove bidders with false value', () => { + const adUnit = JSON.parse(JSON.stringify(SAMPLE_REQUEST_BIDS_CONFIG_OBJ.adUnits[0])); + const matchingAdUnit = SAMPLE_RESPONSE_ADUNITS[0]; + greenbidsSubmodule.removeFalseBidders(adUnit, matchingAdUnit); + expect(adUnit.bids).to.have.length(2); + expect(adUnit.bids.map((bid) => bid.bidder)).to.not.include('rubicon'); + }); + }); + + describe('getFalseBidders', () => { + it('should return an array of false bidders', () => { + const bidders = { + appnexus: true, + rubicon: false, + ix: true, + openx: false + }; + const falseBidders = greenbidsSubmodule.getFalseBidders(bidders); + expect(falseBidders).to.have.length(2); + expect(falseBidders).to.include('rubicon'); + expect(falseBidders).to.include('openx'); + }); + }); + + describe('getBidRequestData', () => { + it('Callback is called if the server responds a 200 within the time limit', (done) => { + let requestBids = deepClone(SAMPLE_REQUEST_BIDS_CONFIG_OBJ); + let callback = sinon.stub(); + + greenbidsSubmodule.getBidRequestData(requestBids, callback, SAMPLE_MODULE_CONFIG); + + setTimeout(() => { + server.requests[0].respond( + 200, + {'Content-Type': 'application/json'}, + JSON.stringify(SAMPLE_RESPONSE_ADUNITS) + ); + done(); + }, 50); + + setTimeout(() => { + const requestUrl = new URL(server.requests[0].url); + expect(requestUrl.host).to.be.eq(endPoint); + expect(requestBids.adUnits[0].bids).to.have.length(2); + expect(requestBids.adUnits[0].bids.map((bid) => bid.bidder)).to.not.include('rubicon'); + expect(requestBids.adUnits[0].bids.map((bid) => bid.bidder)).to.include('ix'); + expect(requestBids.adUnits[0].bids.map((bid) => bid.bidder)).to.include('appnexus'); + expect(requestBids.adUnits[1].bids).to.have.length(2); + expect(requestBids.adUnits[1].bids.map((bid) => bid.bidder)).to.not.include('appnexus'); + expect(requestBids.adUnits[1].bids.map((bid) => bid.bidder)).to.include('rubicon'); + expect(requestBids.adUnits[1].bids.map((bid) => bid.bidder)).to.include('openx'); + expect(callback.calledOnce).to.be.true; + }, 60); + }); + }); + + describe('getBidRequestData', () => { + it('Nothing changes if the server times out but still the callback is called', (done) => { + let requestBids = deepClone(SAMPLE_REQUEST_BIDS_CONFIG_OBJ); + let callback = sinon.stub(); + + greenbidsSubmodule.getBidRequestData(requestBids, callback, SAMPLE_MODULE_CONFIG); + + setTimeout(() => { + server.requests[0].respond( + 200, + {'Content-Type': 'application/json'}, + JSON.stringify(SAMPLE_RESPONSE_ADUNITS) + ); + done(); + }, 300); + + setTimeout(() => { + const requestUrl = new URL(server.requests[0].url); + expect(requestUrl.host).to.be.eq(endPoint); + expect(requestBids.adUnits[0].bids).to.have.length(3); + expect(requestBids.adUnits[1].bids).to.have.length(3); + expect(callback.calledOnce).to.be.true; + }, 200); + }); + }); + + describe('getBidRequestData', () => { + it('callback is called if the server responds a 500 error within the time limit and no changes are made', (done) => { + let requestBids = deepClone(SAMPLE_REQUEST_BIDS_CONFIG_OBJ); + let callback = sinon.stub(); + + greenbidsSubmodule.getBidRequestData(requestBids, callback, SAMPLE_MODULE_CONFIG); + + setTimeout(() => { + server.requests[0].respond( + 500, + {'Content-Type': 'application/json'}, + JSON.stringify({'failure': 'fail'}) + ); + done(); + }, 50); + + setTimeout(() => { + const requestUrl = new URL(server.requests[0].url); + expect(requestUrl.host).to.be.eq(endPoint); + expect(requestBids.adUnits[0].bids).to.have.length(3); + expect(requestBids.adUnits[1].bids).to.have.length(3); + expect(callback.calledOnce).to.be.true; + }, 60); + }); + }); +}); diff --git a/test/spec/modules/gridBidAdapter_spec.js b/test/spec/modules/gridBidAdapter_spec.js index d411f33ab50..2f6e3990d82 100644 --- a/test/spec/modules/gridBidAdapter_spec.js +++ b/test/spec/modules/gridBidAdapter_spec.js @@ -2,6 +2,7 @@ import { expect } from 'chai'; import { spec, resetUserSync, getSyncUrl, storage } from 'modules/gridBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; import { config } from 'src/config.js'; +import {ENDPOINT_DOMAIN, ENDPOINT_PROTOCOL} from '../../../modules/adpartnerBidAdapter'; describe('TheMediaGrid Adapter', function () { const adapter = newBidder(spec); @@ -22,7 +23,6 @@ describe('TheMediaGrid Adapter', function () { 'sizes': [[300, 250], [300, 600]], 'bidId': '30b31c1838de1e', 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', }; it('should return true when required params found', function () { @@ -47,8 +47,13 @@ describe('TheMediaGrid Adapter', function () { refererInfo: { page: 'https://example.com' }, bidderRequestId: '22edbae2733bf6', transactionId: '1239bd74-4511-4335-af21-e828852e25d7', + timeout: 3000, auctionId: '9e2dfbfe-00c7-4f5e-9850-4044df3229c7', - timeout: 3000 + ortb2: { + source: { + tid: '9e2dfbfe-00c7-4f5e-9850-4044df3229c7', + } + } }; const referrer = encodeURIComponent(bidderRequest.refererInfo.page); let bidRequests = [ @@ -69,6 +74,11 @@ describe('TheMediaGrid Adapter', function () { 'bidderRequestId': '22edbae2733bf6', 'auctionId': '9e2dfbfe-00c7-4f5e-9850-4044df3229c7', transactionId: '1239bd74-4511-4335-af21-e828852e25d7', + ortb2Imp: { + ext: { + tid: '1239bd74-4511-4335-af21-e828852e25d7', + } + } }, { 'bidder': 'grid', @@ -81,6 +91,11 @@ describe('TheMediaGrid Adapter', function () { 'bidderRequestId': '22edbae2733bf6', 'auctionId': '9e2dfbfe-00c7-4f5e-9850-4044df3229c7', transactionId: '1239bd74-4511-4335-af21-e828852e25d7', + ortb2Imp: { + ext: { + tid: '1239bd74-4511-4335-af21-e828852e25d7', + } + } }, { 'bidder': 'grid', @@ -99,6 +114,11 @@ describe('TheMediaGrid Adapter', function () { 'bidderRequestId': '22edbae2733bf6', 'auctionId': '9e2dfbfe-00c7-4f5e-9850-4044df3229c7', transactionId: '1239bd74-4511-4335-af21-e828852e25d7', + ortb2Imp: { + ext: { + tid: '1239bd74-4511-4335-af21-e828852e25d7', + } + } }, { 'bidder': 'grid', @@ -120,6 +140,11 @@ describe('TheMediaGrid Adapter', function () { 'bidderRequestId': '22edbae2733bf6', 'auctionId': '9e2dfbfe-00c7-4f5e-9850-4044df3229c7', transactionId: '1239bd74-4511-4335-af21-e828852e25d7', + ortb2Imp: { + ext: { + tid: '1239bd74-4511-4335-af21-e828852e25d7', + } + } } ]; @@ -152,7 +177,7 @@ describe('TheMediaGrid Adapter', function () { }, 'tmax': bidderRequest.timeout, 'source': { - 'tid': bidderRequest.auctionId, + 'tid': bidderRequest.ortb2.source.tid, 'ext': {'wrapper': 'Prebid_js', 'wrapper_version': '$prebid.version$'} }, 'user': { @@ -400,158 +425,6 @@ describe('TheMediaGrid Adapter', function () { getDataFromLocalStorageStub.restore(); }); - it('should send additional request with adUnits with withCriteo parameter', function () { - const fpdUserIdVal = '0b0f84a1-1596-4165-9742-2e1a7dfac57f'; - const getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage').callsFake( - arg => arg === 'tmguid' ? fpdUserIdVal : null); - - const bidRequestsWithCriteo = bidRequests.map((bid, i) => - i % 2 ? bid : { - ...bid, - params: { - ...bid.params, - withCriteo: true, - networkId: 123 + i - }, - mediaTypes: { - ...bid.mediaTypes, - ...(bid.mediaTypes.video && { video: { - ...bid.mediaTypes.video, - context: 'instream', - protocols: [1, 2, 3], - maxduration: 30, - api: [1, 2], - skip: 1, - placement: 1, - minduration: 0, - playbackmethod: 1, - startdelay: 0 - } - }) - } - }); - - const [request, criteoRequest] = spec.buildRequests(bidRequestsWithCriteo, bidderRequest); - expect(request.data).to.be.an('string'); - expect(criteoRequest.data).to.be.an('object'); - const payload = parseRequest(request.data); - const criteoPayload = criteoRequest.data; - expect(request.url).to.equal('https://grid.bidswitch.net/hbjson'); - expect(criteoRequest.url.replace(/\?.*$/, '')) - .to.equal('https://bidder.criteo.com/cdb'); - expect(payload).to.deep.equal({ - 'id': bidderRequest.bidderRequestId, - 'site': { - 'page': referrer - }, - 'tmax': bidderRequest.timeout, - 'source': { - 'tid': bidderRequest.auctionId, - 'ext': {'wrapper': 'Prebid_js', 'wrapper_version': '$prebid.version$'} - }, - 'user': { - 'id': fpdUserIdVal - }, - 'imp': [{ - 'id': bidRequests[0].bidId, - 'tagid': bidRequests[0].params.uid, - 'ext': {'divid': bidRequests[0].adUnitCode}, - 'bidfloor': bidRequests[0].params.bidFloor, - 'banner': { - 'w': 300, - 'h': 250, - 'format': [{'w': 300, 'h': 250}, {'w': 300, 'h': 600}] - } - }, { - 'id': bidRequests[1].bidId, - 'tagid': bidRequests[1].params.uid, - 'ext': {'divid': bidRequests[1].adUnitCode}, - 'banner': { - 'w': 300, - 'h': 250, - 'format': [{'w': 300, 'h': 250}, {'w': 300, 'h': 600}] - } - }, { - 'id': bidRequests[2].bidId, - 'tagid': bidRequests[2].params.uid, - 'ext': {'divid': bidRequests[2].adUnitCode}, - 'video': { - 'w': 400, - 'h': 600, - 'protocols': [1, 2, 3], - 'mimes': ['video/mp4', 'video/webm', 'application/javascript', 'video/ogg'], - } - }, { - 'id': bidRequests[3].bidId, - 'tagid': bidRequests[3].params.uid, - 'ext': {'divid': bidRequests[3].adUnitCode}, - 'banner': { - 'w': 728, - 'h': 90, - 'format': [{'w': 728, 'h': 90}] - }, - 'video': { - 'w': 400, - 'h': 600, - 'protocols': [1, 2, 3] - } - }] - }); - - expect(criteoPayload).to.deep.equal({ - 'publisher': { - 'ext': undefined, - 'url': bidderRequest.refererInfo.page, - 'networkid': 125 - }, - 'regs': { - 'coppa': undefined - }, - 'slots': [{ - 'impid': bidRequests[0].adUnitCode, - 'transactionid': bidderRequest.transactionId, - 'auctionId': bidderRequest.auctionId, - 'sizes': ['300x250', '300x600'] - }, { - 'impid': bidRequests[2].adUnitCode, - 'transactionid': bidderRequest.transactionId, - 'auctionId': bidderRequest.auctionId, - 'sizes': [], - 'video': { - 'api': [ - 1, - 2 - ], - 'maxduration': 30, - 'mimes': [ - 'video/mp4', - 'video/webm', - 'application/javascript', - 'video/ogg', - ], - 'minduration': 0, - 'placement': 1, - 'playbackmethod': 1, - 'playersizes': [ - '400x600' - ], - 'protocols': [ - 1, - 2, - 3 - ], - 'skip': 1, - 'startdelay': 0 - } - }], - 'user': { - 'ext': undefined - } - }); - - getDataFromLocalStorageStub.restore(); - }); - it('if gdprConsent is present payload must have gdpr params', function () { const gdprBidderRequest = Object.assign({gdprConsent: {consentString: 'AAA', gdprApplies: true}}, bidderRequest); const [request] = spec.buildRequests(bidRequests, gdprBidderRequest); @@ -575,6 +448,36 @@ describe('TheMediaGrid Adapter', function () { expect(payload.regs.ext).to.have.property('us_privacy', '1YNN'); }); + it('should add gpp information to the request via bidderRequest.gppConsent', function () { + let consentString = 'abc1234'; + const gppBidderRequest = Object.assign({gppConsent: {gppString: consentString, applicableSections: [8]}}, bidderRequest); + + const [request] = spec.buildRequests(bidRequests, gppBidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.regs).to.exist; + expect(payload.regs.gpp).to.equal(consentString); + expect(payload.regs.gpp_sid).to.deep.equal([8]); + }); + + it('should add gpp information to the request via bidderRequest.ortb2.regs.gpp', function () { + let consentString = 'abc1234'; + const gppBidderRequest = { + ...bidderRequest, + ortb2: { + regs: {gpp: consentString, gpp_sid: [8]}, + ...bidderRequest.ortb2 + } + }; + + const [request] = spec.buildRequests(bidRequests, gppBidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.regs).to.exist; + expect(payload.regs.gpp).to.equal(consentString); + expect(payload.regs.gpp_sid).to.deep.equal([8]); + }); + it('if userId is present payload must have user.ext param with right keys', function () { const eids = [ { @@ -667,14 +570,6 @@ describe('TheMediaGrid Adapter', function () { const [request] = spec.buildRequests(bidRequestsWithJwTargeting, bidderRequest); expect(request.data).to.be.an('string'); const payload = parseRequest(request.data); - expect(payload).to.have.property('user'); - expect(payload.user.data).to.deep.equal([{ - name: 'iow_labs_pub_data', - segment: [ - {name: 'jwpseg', value: jsSegments[0]}, - {name: 'jwpseg', value: jsSegments[1]} - ] - }]); expect(payload).to.have.property('site'); expect(payload.site.content).to.deep.equal(jsContent); }); @@ -793,7 +688,7 @@ describe('TheMediaGrid Adapter', function () { expect(payload.site.content.data).to.deep.equal(contentData); }); - it('should have right value in user.data when jwpsegments are present', function () { + it('should have right value in user.data', function () { const userData = [ { name: 'someName', @@ -823,13 +718,7 @@ describe('TheMediaGrid Adapter', function () { }); const [request] = spec.buildRequests([bidRequestsWithJwTargeting], {...bidderRequest, ortb2}); const payload = parseRequest(request.data); - expect(payload.user.data).to.deep.equal([{ - name: 'iow_labs_pub_data', - segment: [ - {name: 'jwpseg', value: jsSegments[0]}, - {name: 'jwpseg', value: jsSegments[1]} - ] - }, ...userData]); + expect(payload.user.data).to.deep.equal(userData); }); it('should have site.content.id filled from config ortb2.site.content.id', function () { @@ -863,17 +752,20 @@ describe('TheMediaGrid Adapter', function () { } }, { ext: { + gpid: '/222222/slot', data: { adserver: { name: 'ad_server_name', - adslot: '/222222/slot' - }, - pbadslot: '/222222/slot' + } } } + }, { + ext: { + gpid: '/333333/slot' + } }]; const bidRequestsWithOrtb2Imp = bidRequests.slice(0, 3).map((bid, ind) => { - return Object.assign(ortb2Imp[ind] ? { ortb2Imp: ortb2Imp[ind] } : {}, bid); + return Object.assign({}, bid, ortb2Imp[ind] ? { ortb2Imp: {...bid.ortb2Imp, ...ortb2Imp[ind]} } : {}); }); const [request] = spec.buildRequests(bidRequestsWithOrtb2Imp, bidderRequest); expect(request.data).to.be.an('string'); @@ -886,10 +778,11 @@ describe('TheMediaGrid Adapter', function () { expect(payload.imp[1].ext).to.deep.equal({ divid: bidRequests[1].adUnitCode, data: ortb2Imp[1].ext.data, - gpid: ortb2Imp[1].ext.data.adserver.adslot + gpid: ortb2Imp[1].ext.gpid }); expect(payload.imp[2].ext).to.deep.equal({ - divid: bidRequests[2].adUnitCode + divid: bidRequests[2].adUnitCode, + gpid: ortb2Imp[2].ext.gpid }); }); @@ -909,7 +802,7 @@ describe('TheMediaGrid Adapter', function () { } }]; const bidRequestsWithOrtb2Imp = bidRequests.slice(0, 3).map((bid, ind) => { - return Object.assign(ortb2Imp[ind] ? { ortb2Imp: ortb2Imp[ind] } : {}, bid); + return Object.assign({}, bid, ortb2Imp[ind] ? { ortb2Imp: ortb2Imp[ind] } : {}); }); const [request] = spec.buildRequests(bidRequestsWithOrtb2Imp, bidderRequest); expect(request.data).to.be.an('string'); @@ -941,8 +834,12 @@ describe('TheMediaGrid Adapter', function () { const bidderRequestWithNumId = { refererInfo: {page: 'https://example.com'}, bidderRequestId: 345345345, - auctionId: 654645, - timeout: 3000 + timeout: 3000, + ortb2: { + source: { + tid: 654645 + } + } }; const parsedReferrer = encodeURIComponent(bidderRequestWithNumId.refererInfo.page); const [request] = spec.buildRequests([bidRequestWithNumId], bidderRequestWithNumId); @@ -1015,11 +912,11 @@ describe('TheMediaGrid Adapter', function () { describe('interpretResponse', function () { const responses = [ - {'bid': [{'impid': '659423fff799cb', 'price': 1.15, 'adm': '
test content 1
', 'auid': 1, 'h': 250, 'w': 300, 'dealid': 11}], 'seat': '1'}, - {'bid': [{'impid': '4dff80cc4ee346', 'price': 0.5, 'adm': '
test content 2
', 'auid': 2, 'h': 600, 'w': 300}], 'seat': '1'}, - {'bid': [{'impid': '5703af74d0472a', 'price': 0.15, 'adm': '
test content 3
', 'auid': 1, 'h': 90, 'w': 728}], 'seat': '1'}, - {'bid': [{'impid': '2344da98f78b42', 'price': 0, 'auid': 3, 'h': 250, 'w': 300}], 'seat': '1'}, - {'bid': [{'price': 0, 'adm': '
test content 5
', 'h': 250, 'w': 300}], 'seat': '1'}, + {'bid': [{'impid': '659423fff799cb', 'adid': '13_14_4353', 'price': 1.15, 'adm': '
test content 1
', 'auid': 1, 'h': 250, 'w': 300, 'dealid': 11}], 'seat': '1'}, + {'bid': [{'impid': '4dff80cc4ee346', 'adid': '13_15_6454', 'price': 0.5, 'adm': '
test content 2
', 'auid': 2, 'h': 600, 'w': 300}], 'seat': '1'}, + {'bid': [{'impid': '5703af74d0472a', 'adid': '13_16_7654', 'price': 0.15, 'adm': '
test content 3
', 'auid': 1, 'h': 90, 'w': 728}], 'seat': '1'}, + {'bid': [{'impid': '2344da98f78b42', 'adid': '13_17_5433', 'price': 0, 'auid': 3, 'h': 250, 'w': 300}], 'seat': '1'}, + {'bid': [{'price': 0, 'adid': '13_18_34645', 'adm': '
test content 5
', 'h': 250, 'w': 300}], 'seat': '1'}, undefined, {'bid': [], 'seat': '1'}, {'seat': '1'}, @@ -1044,7 +941,7 @@ describe('TheMediaGrid Adapter', function () { { 'requestId': '659423fff799cb', 'cpm': 1.15, - 'creativeId': 1, + 'creativeId': '13_14_4353', 'dealId': 11, 'width': 300, 'height': 250, @@ -1104,7 +1001,7 @@ describe('TheMediaGrid Adapter', function () { { 'requestId': '659423fff799cb', 'cpm': 1.15, - 'creativeId': 1, + 'creativeId': '13_14_4353', 'dealId': 11, 'width': 300, 'height': 250, @@ -1120,7 +1017,7 @@ describe('TheMediaGrid Adapter', function () { { 'requestId': '4dff80cc4ee346', 'cpm': 0.5, - 'creativeId': 2, + 'creativeId': '13_15_6454', 'dealId': undefined, 'width': 300, 'height': 600, @@ -1136,7 +1033,7 @@ describe('TheMediaGrid Adapter', function () { { 'requestId': '5703af74d0472a', 'cpm': 0.15, - 'creativeId': 1, + 'creativeId': '13_16_7654', 'dealId': undefined, 'width': 728, 'height': 90, @@ -1239,18 +1136,18 @@ describe('TheMediaGrid Adapter', function () { } ]; const response = [ - {'bid': [{'impid': '659423fff799cb', 'price': 1.15, 'adm': '\n<\/Ad>\n<\/VAST>', 'auid': 11, content_type: 'video', w: 300, h: 600}], 'seat': '2'}, - {'bid': [{'impid': '2bc598e42b6a', 'price': 1.00, 'adm': '\n<\/Ad>\n<\/VAST>', 'auid': 12, content_type: 'video'}], 'seat': '2'}, - {'bid': [{'impid': '23312a43bc42', 'price': 2.00, 'nurl': 'https://some_test_vast_url.com', 'auid': 13, content_type: 'video', 'adomain': ['example.com'], w: 300, h: 600}], 'seat': '2'}, - {'bid': [{'impid': '112432ab4f34', 'price': 1.80, 'adm': '\n<\/Ad>\n<\/VAST>', 'nurl': 'https://wrong_url.com', 'auid': 14, content_type: 'video', 'adomain': ['example.com'], w: 300, h: 600}], 'seat': '2'}, - {'bid': [{'impid': 'a74b342f8cd', 'price': 1.50, 'nurl': '', 'auid': 15, content_type: 'video'}], 'seat': '2'} + {'bid': [{'impid': '659423fff799cb', 'adid': '35_56_6454', 'price': 1.15, 'adm': '\n<\/Ad>\n<\/VAST>', 'auid': 11, content_type: 'video', w: 300, h: 600}], 'seat': '2'}, + {'bid': [{'impid': '2bc598e42b6a', 'adid': '35_57_2344', 'price': 1.00, 'adm': '\n<\/Ad>\n<\/VAST>', content_type: 'video'}], 'seat': '2'}, + {'bid': [{'impid': '23312a43bc42', 'adid': '35_58_5345', 'price': 2.00, 'nurl': 'https://some_test_vast_url.com', 'auid': 13, content_type: 'video', 'adomain': ['example.com'], w: 300, h: 600}], 'seat': '2'}, + {'bid': [{'impid': '112432ab4f34', 'adid': '35_59_56756', 'price': 1.80, 'adm': '\n<\/Ad>\n<\/VAST>', 'nurl': 'https://wrong_url.com', 'auid': 14, content_type: 'video', 'adomain': ['example.com'], w: 300, h: 600}], 'seat': '2'}, + {'bid': [{'impid': 'a74b342f8cd', 'adid': '35_60_523452', 'price': 1.50, 'nurl': '', 'auid': 15, content_type: 'video'}], 'seat': '2'} ]; const [request] = spec.buildRequests(bidRequests); const expectedResponse = [ { 'requestId': '659423fff799cb', 'cpm': 1.15, - 'creativeId': 11, + 'creativeId': '35_56_6454', 'dealId': undefined, 'width': 300, 'height': 600, @@ -1269,7 +1166,7 @@ describe('TheMediaGrid Adapter', function () { { 'requestId': '2bc598e42b6a', 'cpm': 1.00, - 'creativeId': 12, + 'creativeId': '35_57_2344', 'dealId': undefined, 'width': undefined, 'height': undefined, @@ -1288,7 +1185,7 @@ describe('TheMediaGrid Adapter', function () { { 'requestId': '23312a43bc42', 'cpm': 2.00, - 'creativeId': 13, + 'creativeId': '35_58_5345', 'dealId': undefined, 'width': 300, 'height': 600, @@ -1304,7 +1201,7 @@ describe('TheMediaGrid Adapter', function () { { 'requestId': '112432ab4f34', 'cpm': 1.80, - 'creativeId': 14, + 'creativeId': '35_59_56756', 'dealId': undefined, 'width': 300, 'height': 600, @@ -1369,11 +1266,11 @@ describe('TheMediaGrid Adapter', function () { it('complicated case', function () { const fullResponse = [ - {'bid': [{'impid': '2164be6358b9', 'price': 1.15, 'adm': '
test content 1
', 'auid': 1, 'h': 250, 'w': 300, dealid: 11}], 'seat': '1'}, - {'bid': [{'impid': '4e111f1b66e4', 'price': 0.5, 'adm': '
test content 2
', 'auid': 2, 'h': 600, 'w': 300, dealid: 12}], 'seat': '1'}, - {'bid': [{'impid': '26d6f897b516', 'price': 0.15, 'adm': '
test content 3
', 'auid': 1, 'h': 90, 'w': 728}], 'seat': '1'}, - {'bid': [{'impid': '326bde7fbf69', 'price': 0.15, 'adm': '
test content 4
', 'auid': 1, 'h': 600, 'w': 300}], 'seat': '1'}, - {'bid': [{'impid': '2234f233b22a', 'price': 0.5, 'adm': '
test content 5
', 'auid': 2, 'h': 600, 'w': 350}], 'seat': '1'}, + {'bid': [{'impid': '2164be6358b9', 'adid': '32_52_7543', 'price': 1.15, 'adm': '
test content 1
', 'auid': 1, 'h': 250, 'w': 300, dealid: 11}], 'seat': '1'}, + {'bid': [{'impid': '4e111f1b66e4', 'adid': '32_54_4535', 'price': 0.5, 'adm': '
test content 2
', 'auid': 2, 'h': 600, 'w': 300, dealid: 12}], 'seat': '1'}, + {'bid': [{'impid': '26d6f897b516', 'adid': '32_53_75467', 'price': 0.15, 'adm': '
test content 3
', 'auid': 1, 'h': 90, 'w': 728}], 'seat': '1'}, + {'bid': [{'impid': '326bde7fbf69', 'adid': '32_54_12342', 'price': 0.15, 'adm': '
test content 4
', 'auid': 1, 'h': 600, 'w': 300}], 'seat': '1'}, + {'bid': [{'impid': '2234f233b22a', 'adid': '32_55_987686', 'price': 0.5, 'adm': '
test content 5
', 'auid': 2, 'h': 600, 'w': 350}], 'seat': '1'}, ]; const bidRequests = [ { @@ -1437,7 +1334,7 @@ describe('TheMediaGrid Adapter', function () { { 'requestId': '2164be6358b9', 'cpm': 1.15, - 'creativeId': 1, + 'creativeId': '32_52_7543', 'dealId': 11, 'width': 300, 'height': 250, @@ -1453,7 +1350,7 @@ describe('TheMediaGrid Adapter', function () { { 'requestId': '4e111f1b66e4', 'cpm': 0.5, - 'creativeId': 2, + 'creativeId': '32_54_4535', 'dealId': 12, 'width': 300, 'height': 600, @@ -1469,7 +1366,7 @@ describe('TheMediaGrid Adapter', function () { { 'requestId': '26d6f897b516', 'cpm': 0.15, - 'creativeId': 1, + 'creativeId': '32_53_75467', 'dealId': undefined, 'width': 728, 'height': 90, @@ -1485,7 +1382,7 @@ describe('TheMediaGrid Adapter', function () { { 'requestId': '326bde7fbf69', 'cpm': 0.15, - 'creativeId': 1, + 'creativeId': '32_54_12342', 'dealId': undefined, 'width': 300, 'height': 600, @@ -1504,137 +1401,6 @@ describe('TheMediaGrid Adapter', function () { expect(result).to.deep.equal(expectedResponse); }); - it('should add response with biggest price', function () { - const mainResponse = [ - {'bid': [{'impid': '2164be6358b9', 'price': 1.15, 'adm': '
test content 1
', 'auid': 1, 'h': 250, 'w': 300, dealid: 11}], 'seat': '1'}, - {'bid': [{'impid': '4e111f1b66e4', 'price': 0.5, 'adm': '
test content 2
', 'auid': 2, 'h': 600, 'w': 300, dealid: 12}], 'seat': '1'}, - ]; - const criteoResponse = [ - { - impid: 'adunit-code-1', - cpm: 0.15, - creative: '
test content 3
', - creativecode: 1, - bidId: '2164be6358b9', - currency: 'USD', - width: 300, - height: 250, - dealCode: 11, - zoneid: 123, - ttl: 360, - adomain: ['criteo.com'], - }, - { - impid: 'adunit-code-1', - cpm: 1, - creative: '
test content 4
', - creativecode: 2, - bidId: '4e111f1b66e4', - currency: 'USD', - width: 300, - height: 600, - dealCode: 12, - zoneid: 456, - ttl: 360, - adomain: ['criteo.com'], - } - ]; - const bidRequests = [ - { - 'bidder': 'grid', - 'params': { - 'uid': '1', - 'zoneId': 123, - 'withCriteo': true - }, - 'adUnitCode': 'adunit-code-1', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '2164be6358b9', - 'bidderRequestId': '106efe3247', - 'auctionId': '32a1f276cb87cb8', - 'transactionId': '43534f55b213ac', - }, - { - 'bidder': 'grid', - 'params': { - 'uid': '2', - 'zoneId': 456, - 'withCriteo': true - }, - 'adUnitCode': 'adunit-code-1', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '4e111f1b66e4', - 'bidderRequestId': '106efe3247', - 'auctionId': '32a1f276cb87cb8', - 'transactionId': '43534f55b213ac', - } - ]; - const bidderRequest = { - refererInfo: { page: 'https://example.com' }, - bidderRequestId: '106efe3247', - transactionId: '43534f55b213ac', - auctionId: '32a1f276cb87cb8', - timeout: 3000 - }; - const [mainRequest, criteoRequest] = spec.buildRequests(bidRequests, bidderRequest); - const expectedMainResponse = [ - { - 'requestId': '2164be6358b9', - 'cpm': 1.15, - 'creativeId': 1, - 'dealId': 11, - 'width': 300, - 'height': 250, - 'ad': '
test content 1
', - 'currency': 'USD', - 'mediaType': 'banner', - 'netRevenue': true, - 'ttl': 360, - 'meta': { - advertiserDomains: [] - }, - }, - { - 'requestId': '4e111f1b66e4', - 'cpm': 0.5, - 'creativeId': 2, - 'dealId': 12, - 'width': 300, - 'height': 600, - 'ad': '
test content 2
', - 'currency': 'USD', - 'mediaType': 'banner', - 'netRevenue': true, - 'ttl': 360, - 'meta': { - advertiserDomains: [] - }, - } - ]; - const expectedCriteoResponse = [ - { - 'requestId': '4e111f1b66e4', - 'cpm': 1, - 'creativeId': 2, - 'dealId': 12, - 'width': 300, - 'height': 600, - 'ad': '
test content 4
', - 'currency': 'USD', - 'netRevenue': true, - 'ttl': 360, - 'meta': { - advertiserDomains: ['criteo.com'] - }, - } - ]; - - const mainResult = spec.interpretResponse({'body': {'seatbid': mainResponse}}, mainRequest); - const criteoResult = spec.interpretResponse({'body': {'slots': criteoResponse}}, criteoRequest); - expect(mainResult).to.deep.equal(expectedMainResponse); - expect(criteoResult).to.deep.equal(expectedCriteoResponse); - }); - it('response with ext.bidder.grid.demandSource', function () { const bidRequests = [ { @@ -1656,6 +1422,7 @@ describe('TheMediaGrid Adapter', function () { 'price': 1.15, 'adm': '
test content 1
', 'auid': 1, + 'adid': '234_6454_3453', 'h': 250, 'w': 300, 'dealid': 11, @@ -1675,7 +1442,7 @@ describe('TheMediaGrid Adapter', function () { { 'requestId': '26d6f897b516', 'cpm': 1.15, - 'creativeId': 1, + 'creativeId': '234_6454_3453', 'dealId': 11, 'width': 300, 'height': 250, @@ -1699,6 +1466,47 @@ describe('TheMediaGrid Adapter', function () { }); }); + describe('onDataDeletionRequest', function() { + let ajaxStub; + beforeEach(function() { + ajaxStub = sinon.stub(spec, 'ajaxCall'); + }); + + it('should send right request on onDataDeletionRequest call', function() { + spec.onDataDeletionRequest([{ + bids: [ + { + bidder: 'grid', + params: { + uid: 1 + } + }, + { + bidder: 'grid', + params: { + uid: 2 + } + }, + { + bidder: 'another', + params: { + uid: 3 + } + }, + { + bidder: 'gridNM', + params: { + uid: 4 + } + } + ], + }]); + expect(ajaxStub.calledOnce).to.equal(true); + expect(ajaxStub.firstCall.args[0]).to.equal('https://media.grid.bidswitch.net/uspapi_delete'); + expect(ajaxStub.firstCall.args[2]).to.equal('{"uids":[1,2,4]}'); + }); + }); + describe('user sync', function () { const syncUrl = getSyncUrl(); diff --git a/test/spec/modules/gridNMBidAdapter_spec.js b/test/spec/modules/gridNMBidAdapter_spec.js deleted file mode 100644 index b400ef3394d..00000000000 --- a/test/spec/modules/gridNMBidAdapter_spec.js +++ /dev/null @@ -1,644 +0,0 @@ -import { expect } from 'chai'; -import { spec, resetUserSync, getSyncUrl } from 'modules/gridNMBidAdapter.js'; -import { newBidder } from 'src/adapters/bidderFactory.js'; - -describe('TheMediaGridNM Adapter', function () { - const adapter = newBidder(spec); - - describe('inherited functions', function () { - it('exists and is a function', function () { - expect(adapter.callBids).to.exist.and.to.be.a('function'); - }); - }); - - describe('isBidRequestValid', function () { - let bid = { - 'bidder': 'gridNM', - 'params': { - 'source': 'jwp', - 'secid': '11', - 'pubid': '22', - 'video': { - 'mimes': ['video/mp4', 'video/x-ms-wmv'], - 'protocols': [1, 2, 3, 4, 5, 6] - } - }, - 'adUnitCode': 'adunit-code', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - }; - - it('should return true when required params found', function () { - expect(spec.isBidRequestValid(bid)).to.equal(true); - }); - - it('should return false when required params are not passed', function () { - const paramsList = [ - { - 'source': 'jwp', - 'secid': '11', - 'pubid': '22', - 'video': { - 'protocols': [1, 2, 3, 4, 5, 6] - } - }, - { - 'source': 'jwp', - 'secid': '11', - 'pubid': '22', - 'video': { - 'mimes': ['video/mp4', 'video/x-ms-wmv'], - } - }, - { - 'secid': '11', - 'pubid': '22', - 'video': { - 'mimes': ['video/mp4', 'video/x-ms-wmv'], - 'protocols': [1, 2, 3, 4, 5, 6] - } - }, - { - 'source': 'jwp', - 'pubid': '22', - 'video': { - 'mimes': ['video/mp4', 'video/x-ms-wmv'], - 'protocols': [1, 2, 3, 4, 5, 6] - } - }, - { - 'source': 'jwp', - 'secid': '11', - 'video': { - 'mimes': ['video/mp4', 'video/x-ms-wmv'], - 'protocols': [1, 2, 3, 4, 5, 6] - } - } - ]; - paramsList.forEach((params) => { - const invalidBid = Object.assign({}, bid); - delete invalidBid.params; - invalidBid.params = params; - expect(spec.isBidRequestValid(invalidBid)).to.equal(false); - }); - }); - - it('should return false when required params has invalid values', function () { - const paramsList = [ - { - 'source': 'jwp', - 'secid': '11', - 'pubid': '22', - 'video': { - 'mimes': ['video/mp4', 'video/x-ms-wmv'], - 'protocols': '1,2,3,4,5' - } - }, - { - 'source': 'jwp', - 'secid': '11', - 'pubid': '22', - 'video': { - 'mimes': [1, 2], - 'protocols': [1, 2, 3, 4, 5] - } - }, - { - 'source': 'jwp', - 'secid': 11, - 'pubid': '22', - 'video': { - 'mimes': ['video/mp4', 'video/x-ms-wmv'], - 'protocols': [1, 2, 3, 4, 5] - } - }, - { - 'source': 111, - 'secid': '11', - 'pubid': '22', - 'video': { - 'mimes': ['video/mp4', 'video/x-ms-wmv'], - 'protocols': [1, 2, 3, 4, 5] - } - } - ]; - - paramsList.forEach((params) => { - const invalidBid = Object.assign({}, bid); - delete invalidBid.params; - invalidBid.params = params; - expect(spec.isBidRequestValid(invalidBid)).to.equal(false); - }); - }); - - it('should return true when required params is absent, but available in mediaTypes', function () { - const paramsList = [ - { - 'source': 'jwp', - 'secid': '11', - 'pubid': '22', - 'video': { - 'protocols': [1, 2, 3, 4, 5, 6] - } - }, - { - 'source': 'jwp', - 'secid': '11', - 'pubid': '22', - 'video': { - 'mimes': ['video/mp4', 'video/x-ms-wmv'], - } - } - ]; - - const mediaTypes = { - video: { - mimes: ['video/mp4', 'video/x-ms-wmv'], - playerSize: [200, 300], - protocols: [1, 2, 3, 4, 5, 6] - } - }; - - paramsList.forEach((params) => { - const validBid = Object.assign({}, bid); - delete validBid.params; - validBid.params = params; - validBid.mediaTypes = mediaTypes; - expect(spec.isBidRequestValid(validBid)).to.equal(true); - }); - }); - }); - - describe('buildRequests', function () { - function parseRequestUrl(url) { - const res = {}; - url.replace(/^[^\?]+\?/, '').split('&').forEach((it) => { - const couple = it.split('='); - res[couple[0]] = decodeURIComponent(couple[1]); - }); - return res; - } - const bidderRequest = { - bidderRequestId: '22edbae2733bf6', - auctionId: '1d1a030790a475', - timeout: 3000, - refererInfo: { page: 'https://example.com' } - }; - const referrer = encodeURIComponent(bidderRequest.refererInfo.page); - let bidRequests = [ - { - 'bidder': 'gridNM', - 'params': { - 'source': 'jwp', - 'floorcpm': 2, - 'secid': '11', - 'pubid': '22', - 'video': { - 'mimes': ['video/mp4', 'video/x-ms-wmv'], - 'protocols': [1, 2, 3, 4, 5, 6] - } - }, - 'adUnitCode': 'adunit-code-1', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - }, - { - 'bidder': 'gridNM', - 'params': { - 'source': 'jwp', - 'secid': '11', - 'pubid': '22', - 'video': { - 'mimes': ['video/mp4'], - 'protocols': [1, 2, 3], - 'skip': 1 - } - }, - 'adUnitCode': 'adunit-code-2', - 'sizes': [[728, 90]], - 'bidId': '3150ccb55da321', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - } - ]; - - it('if content and segment is present in jwTargeting, payload must have right params', function () { - const jsContent = {id: 'test_jw_content_id'}; - const jsSegments = ['test_seg_1', 'test_seg_2']; - const bidRequestsWithJwTargeting = bidRequests.map((bid) => { - return Object.assign({ - rtd: { - jwplayer: { - targeting: { - segments: jsSegments, - content: jsContent - } - } - } - }, bid); - }); - const requests = spec.buildRequests(bidRequestsWithJwTargeting, bidderRequest); - requests.forEach((req, i) => { - const payload = req.data; - expect(req).to.have.property('data'); - expect(payload).to.have.property('user'); - expect(payload.user.data).to.deep.equal([{ - name: 'iow_labs_pub_data', - segment: [ - {name: 'jwpseg', value: jsSegments[0]}, - {name: 'jwpseg', value: jsSegments[1]} - ] - }]); - expect(payload).to.have.property('site'); - expect(payload.site.content).to.deep.equal(jsContent); - }); - }); - - it('should attach valid params to the tag', function () { - const requests = spec.buildRequests(bidRequests, bidderRequest); - const requestsSizes = ['300x250,300x600', '728x90']; - requests.forEach((req, i) => { - expect(req.url).to.be.an('string'); - const payload = parseRequestUrl(req.url); - expect(payload).to.have.property('no_mapping', '1'); - expect(payload).to.have.property('sp', 'jwp'); - - const sizes = { w: bidRequests[i].sizes[0][0], h: bidRequests[i].sizes[0][1] }; - const impObj = { - 'id': bidRequests[i].bidId, - 'tagid': bidRequests[i].params.secid, - 'ext': {'divid': bidRequests[i].adUnitCode}, - 'video': Object.assign(sizes, bidRequests[i].params.video) - }; - - if (bidRequests[i].params.floorcpm) { - impObj.bidfloor = bidRequests[i].params.floorcpm; - } - - expect(req.data).to.deep.equal({ - 'id': bidderRequest.bidderRequestId, - 'site': { - 'page': referrer, - 'publisher': { - 'id': bidRequests[i].params.pubid - } - }, - 'tmax': bidderRequest.timeout, - 'source': { - 'tid': bidderRequest.auctionId, - 'ext': {'wrapper': 'Prebid_js', 'wrapper_version': '$prebid.version$'} - }, - 'imp': [impObj] - }); - }); - }); - - it('should attach valid params from mediaTypes', function () { - const mediaTypes = { - video: { - skipafter: 10, - minduration: 10, - maxduration: 100, - protocols: [1, 3, 4], - playerSize: [[300, 250]] - } - }; - const bidRequest = Object.assign({ mediaTypes }, bidRequests[0]); - const req = spec.buildRequests([bidRequest], bidderRequest)[0]; - const expectedVideo = { - 'skipafter': 10, - 'minduration': 10, - 'maxduration': 100, - 'mimes': ['video/mp4', 'video/x-ms-wmv'], - 'protocols': [1, 2, 3, 4, 5, 6], - 'w': 300, - 'h': 250 - }; - - expect(req.url).to.be.an('string'); - const payload = parseRequestUrl(req.url); - expect(payload).to.have.property('no_mapping', '1'); - expect(payload).to.have.property('sp', 'jwp'); - expect(req.data).to.deep.equal({ - 'id': bidderRequest.bidderRequestId, - 'site': { - 'page': referrer, - 'publisher': { - 'id': bidRequest.params.pubid - } - }, - 'tmax': bidderRequest.timeout, - 'source': { - 'tid': bidderRequest.auctionId, - 'ext': {'wrapper': 'Prebid_js', 'wrapper_version': '$prebid.version$'} - }, - 'imp': [{ - 'id': bidRequest.bidId, - 'bidfloor': bidRequest.params.floorcpm, - 'tagid': bidRequest.params.secid, - 'ext': {'divid': bidRequest.adUnitCode}, - 'video': expectedVideo - }] - }); - }); - - it('if gdprConsent is present payload must have gdpr params', function () { - const gdprBidderRequest = Object.assign({gdprConsent: {consentString: 'AAA', gdprApplies: true}}, bidderRequest); - const request = spec.buildRequests([bidRequests[0]], gdprBidderRequest)[0]; - const payload = request.data; - expect(request).to.have.property('data'); - expect(payload).to.have.property('user'); - expect(payload.user).to.have.property('ext'); - expect(payload.user.ext).to.have.property('consent', 'AAA'); - expect(payload).to.have.property('regs'); - expect(payload.regs).to.have.property('ext'); - expect(payload.regs.ext).to.have.property('gdpr', 1); - }); - - it('if usPrivacy is present payload must have us_privacy param', function () { - const bidderRequestWithUSP = Object.assign({uspConsent: '1YNN'}, bidderRequest); - const request = spec.buildRequests([bidRequests[0]], bidderRequestWithUSP)[0]; - const payload = request.data; - expect(payload).to.have.property('regs'); - expect(payload.regs).to.have.property('ext'); - expect(payload.regs.ext).to.have.property('us_privacy', '1YNN'); - }); - }); - - describe('interpretResponse', function () { - const responses = [ - {'bid': [{'price': 1.15, 'adm': '\n<\/Ad>\n<\/VAST>', 'content_type': 'video', 'h': 250, 'w': 300, 'dealid': 11}], 'seat': '2'}, - {'bid': [{'price': 0.5, 'adm': '\n<\/Ad>\n<\/VAST>', 'content_type': 'video', 'h': 600, 'w': 300, adomain: ['my_domain.ru']}], 'seat': '2'}, - {'bid': [{'price': 2.00, 'nurl': 'https://some_test_vast_url.com', 'content_type': 'video', 'adomain': ['example.com'], 'w': 300, 'h': 600}], 'seat': '2'}, - {'bid': [{'price': 0, 'h': 250, 'w': 300}], 'seat': '2'}, - {'bid': [{'price': 0, 'adm': '\n<\/Ad>\n<\/VAST>', 'h': 250, 'w': 300}], 'seat': '2'}, - undefined, - {'bid': [], 'seat': '2'}, - {'seat': '2'}, - ]; - - it('should get correct video bid response', function () { - const bidRequests = [ - { - 'bidder': 'gridNM', - 'params': { - 'source': 'jwp', - 'secid': '11', - 'pubid': '22', - 'video': { - 'mimes': ['video/mp4', 'video/x-ms-wmv'], - 'protocols': [1, 2, 3, 4, 5, 6] - } - }, - 'adUnitCode': 'adunit-code-1', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '659423fff799cb', - 'bidderRequestId': '5f2009617a7c0a', - 'auctionId': '1cbd2feafe5e8b', - 'mediaTypes': { - 'video': { - 'context': 'instream' - } - } - }, - { - 'bidder': 'gridNM', - 'params': { - 'source': 'jwp', - 'secid': '11', - 'pubid': '22', - 'video': { - 'mimes': ['video/mp4'], - 'protocols': [1, 2, 3, 4, 5], - 'skip': 1 - } - }, - 'adUnitCode': 'adunit-code-1', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '2bc598e42b6a', - 'bidderRequestId': '1e8b5a465f404', - 'auctionId': '1cbd2feafe5e8b', - 'mediaTypes': { - 'video': { - 'context': 'instream' - } - } - }, - { - 'bidder': 'gridNM', - 'params': { - 'source': 'jwp', - 'secid': '11', - 'pubid': '22', - 'video': { - 'mimes': ['video/mp4'], - 'protocols': [1, 2, 3], - } - }, - 'adUnitCode': 'adunit-code-1', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '127f4b12a432c', - 'bidderRequestId': 'a75bc868f32', - 'auctionId': '1cbd2feafe5e8b', - 'mediaTypes': { - 'video': { - 'context': 'instream' - } - } - } - ]; - const requests = spec.buildRequests(bidRequests); - const expectedResponse = [ - { - 'requestId': '659423fff799cb', - 'cpm': 1.15, - 'creativeId': '5f2009617a7c0a', - 'dealId': 11, - 'width': 300, - 'height': 250, - 'currency': 'USD', - 'mediaType': 'video', - 'netRevenue': true, - 'ttl': 360, - 'vastXml': '\n<\/Ad>\n<\/VAST>', - 'meta': { - 'advertiserDomains': [] - }, - 'adResponse': { - 'content': '\n<\/Ad>\n<\/VAST>' - } - }, - { - 'requestId': '2bc598e42b6a', - 'cpm': 0.5, - 'creativeId': '1e8b5a465f404', - 'dealId': undefined, - 'width': 300, - 'height': 600, - 'currency': 'USD', - 'mediaType': 'video', - 'netRevenue': true, - 'ttl': 360, - 'vastXml': '\n<\/Ad>\n<\/VAST>', - 'meta': { - 'advertiserDomains': ['my_domain.ru'] - }, - 'adResponse': { - 'content': '\n<\/Ad>\n<\/VAST>' - } - }, - { - 'requestId': '127f4b12a432c', - 'cpm': 2.00, - 'creativeId': 'a75bc868f32', - 'dealId': undefined, - 'width': 300, - 'height': 600, - 'currency': 'USD', - 'mediaType': 'video', - 'netRevenue': true, - 'ttl': 360, - 'meta': { - advertiserDomains: ['example.com'] - }, - 'vastUrl': 'https://some_test_vast_url.com', - } - ]; - - requests.forEach((req, i) => { - const result = spec.interpretResponse({'body': {'seatbid': [responses[i]]}}, req); - expect(result[0]).to.deep.equal(expectedResponse[i]); - }); - }); - - it('handles wrong and nobid responses', function () { - responses.slice(3).forEach((resp) => { - const request = spec.buildRequests([{ - 'bidder': 'gridNM', - 'params': { - 'source': 'jwp', - 'secid': '11', - 'pubid': '22', - 'video': { - 'mimes': ['video/mp4'], - 'protocols': [1, 2, 3, 4, 5], - 'skip': 1 - } - }, - 'adUnitCode': 'adunit-code-1', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '2bc598e42b6a', - 'bidderRequestId': '39d74f5b71464', - 'auctionId': '1cbd2feafe5e8b', - 'meta': { - 'advertiserDomains': [] - }, - 'mediaTypes': { - 'video': { - 'context': 'instream' - } - } - }]); - const result = spec.interpretResponse({'body': {'seatbid': [resp]}}, request[0]); - expect(result.length).to.equal(0); - }); - }); - }); - - describe('user sync', function () { - const syncUrl = getSyncUrl(); - - beforeEach(function () { - resetUserSync(); - }); - - it('should register the Emily iframe', function () { - let syncs = spec.getUserSyncs({ - pixelEnabled: true - }); - - expect(syncs).to.deep.equal({type: 'image', url: syncUrl}); - }); - - it('should not register the Emily iframe more than once', function () { - let syncs = spec.getUserSyncs({ - pixelEnabled: true - }); - expect(syncs).to.deep.equal({type: 'image', url: syncUrl}); - - // when called again, should still have only been called once - syncs = spec.getUserSyncs(); - expect(syncs).to.equal(undefined); - }); - - it('should pass gdpr params if consent is true', function () { - expect(spec.getUserSyncs({ pixelEnabled: true }, {}, { - gdprApplies: true, consentString: 'foo' - })).to.deep.equal({ - type: 'image', url: `${syncUrl}&gdpr=1&gdpr_consent=foo` - }); - }); - - it('should pass gdpr params if consent is false', function () { - expect(spec.getUserSyncs({ pixelEnabled: true }, {}, { - gdprApplies: false, consentString: 'foo' - })).to.deep.equal({ - type: 'image', url: `${syncUrl}&gdpr=0&gdpr_consent=foo` - }); - }); - - it('should pass gdpr param gdpr_consent only when gdprApplies is undefined', function () { - expect(spec.getUserSyncs({ pixelEnabled: true }, {}, { - consentString: 'foo' - })).to.deep.equal({ - type: 'image', url: `${syncUrl}&gdpr_consent=foo` - }); - }); - - it('should pass no params if gdpr consentString is not defined', function () { - expect(spec.getUserSyncs({ pixelEnabled: true }, {}, {})).to.deep.equal({ - type: 'image', url: syncUrl - }); - }); - - it('should pass no params if gdpr consentString is a number', function () { - expect(spec.getUserSyncs({ pixelEnabled: true }, {}, { - consentString: 0 - })).to.deep.equal({ - type: 'image', url: syncUrl - }); - }); - - it('should pass no params if gdpr consentString is null', function () { - expect(spec.getUserSyncs({ pixelEnabled: true }, {}, { - consentString: null - })).to.deep.equal({ - type: 'image', url: syncUrl - }); - }); - - it('should pass no params if gdpr consentString is a object', function () { - expect(spec.getUserSyncs({ pixelEnabled: true }, {}, { - consentString: {} - })).to.deep.equal({ - type: 'image', url: syncUrl - }); - }); - - it('should pass no params if gdpr is not defined', function () { - expect(spec.getUserSyncs({ pixelEnabled: true }, {}, undefined)).to.deep.equal({ - type: 'image', url: syncUrl - }); - }); - - it('should pass usPrivacy param if it is available', function() { - expect(spec.getUserSyncs({ pixelEnabled: true }, {}, {}, '1YNN')).to.deep.equal({ - type: 'image', url: `${syncUrl}&us_privacy=1YNN` - }); - }); - }); -}); diff --git a/test/spec/modules/growthCodeIdSystem_spec.js b/test/spec/modules/growthCodeIdSystem_spec.js index dce995d25e0..97083047d4e 100644 --- a/test/spec/modules/growthCodeIdSystem_spec.js +++ b/test/spec/modules/growthCodeIdSystem_spec.js @@ -4,12 +4,13 @@ import { server } from 'test/mocks/xhr.js'; import { uspDataHandler } from 'src/adapterManager.js'; import {expect} from 'chai'; import {getStorageManager} from '../../../src/storageManager.js'; +import {MODULE_TYPE_UID} from '../../../src/activities/modules.js'; const GCID_EXPIRY = 45; const MODULE_NAME = 'growthCodeId'; const SHAREDID = 'fe9c5c89-7d56-4666-976d-e07e73b3b664'; -export const storage = getStorageManager({ gvlid: undefined, moduleName: MODULE_NAME }); +const storage = getStorageManager({ moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME }); const getIdParams = {params: { pid: 'TEST01', diff --git a/test/spec/modules/growthCodeRtdProvider_spec.js b/test/spec/modules/growthCodeRtdProvider_spec.js new file mode 100644 index 00000000000..31e1efc5487 --- /dev/null +++ b/test/spec/modules/growthCodeRtdProvider_spec.js @@ -0,0 +1,127 @@ +import {config} from 'src/config.js'; +import {growthCodeRtdProvider} from '../../../modules/growthCodeRtdProvider'; +import sinon from 'sinon'; +import * as ajaxLib from 'src/ajax.js'; + +const sampleConfig = { + name: 'growthCodeRtd', + waitForIt: true, + params: { + pid: 'TEST01', + } +} + +describe('growthCodeRtdProvider', function() { + beforeEach(function() { + config.resetConfig(); + }); + + afterEach(function () { + }); + + describe('growthCodeRtdSubmodule', function() { + it('test bad config instantiates', function () { + const ajaxStub = sinon.stub(ajaxLib, 'ajaxBuilder').callsFake(() => { + return (url, cbObj) => { + cbObj.success('{"status":"ok","version":"1.0.0","results":1,"items":[{"bidder":"client_a","attachment_point":"data","parameters":"{\\"client_a\\":{\\"user\\":{\\"ext\\":{\\"data\\":{\\"eids\\":[{\\"source\\":\\"\\",\\"uids\\":[{\\"id\\":\\"4254074976bb6a6d970f5f693bd8a75c\\",\\"atype\\":3,\\"ext\\":{\\"stype\\":\\"hemmd5\\"}},{\\"id\\":\\"d0ee291572ffcfba0bf7edb2b1c90ca7c32d255e5040b8b50907f5963abb1898\\",\\"atype\\":3,\\"ext\\":{\\"stype\\":\\"hemsha256\\"}}]}]}}}}}"}],"expires_at":1685029931}') + } + }); + expect(growthCodeRtdProvider.init(null, null)).to.equal(false); + ajaxStub.restore() + }); + it('successfully instantiates', function () { + const ajaxStub = sinon.stub(ajaxLib, 'ajaxBuilder').callsFake(() => { + return (url, cbObj) => { + cbObj.success('{"status":"ok","version":"1.0.0","results":1,"items":[{"bidder":"client_a","attachment_point":"data","parameters":"{\\"client_a\\":{\\"user\\":{\\"ext\\":{\\"data\\":{\\"eids\\":[{\\"source\\":\\"\\",\\"uids\\":[{\\"id\\":\\"4254074976bb6a6d970f5f693bd8a75c\\",\\"atype\\":3,\\"ext\\":{\\"stype\\":\\"hemmd5\\"}},{\\"id\\":\\"d0ee291572ffcfba0bf7edb2b1c90ca7c32d255e5040b8b50907f5963abb1898\\",\\"atype\\":3,\\"ext\\":{\\"stype\\":\\"hemsha256\\"}}]}]}}}}}"}],"expires_at":1685029931}') + } + }); + expect(growthCodeRtdProvider.init(sampleConfig, null)).to.equal(true); + ajaxStub.restore() + }); + it('successfully instantiates (cached)', function () { + const ajaxStub = sinon.stub(ajaxLib, 'ajaxBuilder').callsFake(() => { + return (url, cbObj) => { + cbObj.success('{"status":"ok","version":"1.0.0","results":1,"items":[{"bidder":"client_a","attachment_point":"data","parameters":"{\\"client_a\\":{\\"user\\":{\\"ext\\":{\\"data\\":{\\"eids\\":[{\\"source\\":\\"\\",\\"uids\\":[{\\"id\\":\\"4254074976bb6a6d970f5f693bd8a75c\\",\\"atype\\":3,\\"ext\\":{\\"stype\\":\\"hemmd5\\"}},{\\"id\\":\\"d0ee291572ffcfba0bf7edb2b1c90ca7c32d255e5040b8b50907f5963abb1898\\",\\"atype\\":3,\\"ext\\":{\\"stype\\":\\"hemsha256\\"}}]}]}}}}}"}],"expires_at":1685029931}') + } + }); + const localStoreItem = '[{"bidder":"client_a","attachment_point":"data","parameters":"{\\"client_a\\":{\\"user\\":{\\"ext\\":{\\"data\\":{\\"eids\\":[{\\"source\\":\\"\\",\\"uids\\":[{\\"id\\":\\"4254074976bb6a6d970f5f693bd8a75c\\",\\"atype\\":3,\\"ext\\":{\\"stype\\":\\"hemmd5\\"}},{\\"id\\":\\"d0ee291572ffcfba0bf7edb2b1c90ca7c32d255e5040b8b50907f5963abb1898\\",\\"atype\\":3,\\"ext\\":{\\"stype\\":\\"hemsha256\\"}}]}]}}}}}"}]' + expect(growthCodeRtdProvider.callServer(sampleConfig, localStoreItem, '1965949885', null)).to.equal(true); + ajaxStub.restore() + }); + it('successfully instantiates (cached,expire)', function () { + const ajaxStub = sinon.stub(ajaxLib, 'ajaxBuilder').callsFake(() => { + return (url, cbObj) => { + cbObj.success('{"status":"ok","version":"1.0.0","results":1,"items":[{"bidder":"client_a","attachment_point":"data","parameters":"{\\"client_a\\":{\\"user\\":{\\"ext\\":{\\"data\\":{\\"eids\\":[{\\"source\\":\\"\\",\\"uids\\":[{\\"id\\":\\"4254074976bb6a6d970f5f693bd8a75c\\",\\"atype\\":3,\\"ext\\":{\\"stype\\":\\"hemmd5\\"}},{\\"id\\":\\"d0ee291572ffcfba0bf7edb2b1c90ca7c32d255e5040b8b50907f5963abb1898\\",\\"atype\\":3,\\"ext\\":{\\"stype\\":\\"hemsha256\\"}}]}]}}}}}"}],"expires_at":1685029931}') + } + }); + const localStoreItem = '[{"bidder":"client_a","attachment_point":"data","parameters":"{\\"client_a\\":{\\"user\\":{\\"ext\\":{\\"data\\":{\\"eids\\":[{\\"source\\":\\"\\",\\"uids\\":[{\\"id\\":\\"4254074976bb6a6d970f5f693bd8a75c\\",\\"atype\\":3,\\"ext\\":{\\"stype\\":\\"hemmd5\\"}},{\\"id\\":\\"d0ee291572ffcfba0bf7edb2b1c90ca7c32d255e5040b8b50907f5963abb1898\\",\\"atype\\":3,\\"ext\\":{\\"stype\\":\\"hemsha256\\"}}]}]}}}}}"}]' + expect(growthCodeRtdProvider.callServer(sampleConfig, localStoreItem, '1679188732', null)).to.equal(true); + ajaxStub.restore() + }); + + it('test no items response', function () { + const ajaxStub = sinon.stub(ajaxLib, 'ajaxBuilder').callsFake(() => { + return (url, cbObj) => { + cbObj.success('{}') + } + }); + expect(growthCodeRtdProvider.callServer(sampleConfig, null, '1679188732', null)).to.equal(true); + ajaxStub.restore(); + }); + + it('ajax error response', function () { + const ajaxStub = sinon.stub(ajaxLib, 'ajaxBuilder').callsFake(() => { + return (url, cbObj) => { + cbObj.error(); + } + }); + expect(growthCodeRtdProvider.callServer(sampleConfig, null, '1679188732', null)).to.equal(true); + ajaxStub.restore(); + }); + + it('test alterBid data merge into ortb2 data (bidder)', function() { + const gcData = + { + 'client_a': + { + 'user': + {'ext': + {'data': + {'eids': [ + {'source': 'test.com', + 'uids': [ + { + 'id': '4254074976bb6a6d970f5f693bd8a75c', + 'atype': 3, + 'ext': { + 'stype': 'hemmd5'} + }, { + 'id': 'd0ee291572ffcfba0bf7edb2b1c90ca7c32d255e5040b8b50907f5963abb1898', + 'atype': 3, + 'ext': { + 'stype': 'hemsha256' + } + } + ] + } + ] + } + } + } + } + }; + + const payload = [ + { + 'bidder': 'client_a', + 'attachment_point': 'data', + 'parameters': JSON.stringify(gcData) + }] + + const bidConfig = {ortb2Fragments: {bidder: {}}}; + growthCodeRtdProvider.addData(bidConfig, payload) + + expect(bidConfig.ortb2Fragments.bidder).to.deep.equal(gcData) + }); + }); +}); diff --git a/test/spec/modules/gumgumBidAdapter_spec.js b/test/spec/modules/gumgumBidAdapter_spec.js index a2dacb16b73..0e64ec67b27 100644 --- a/test/spec/modules/gumgumBidAdapter_spec.js +++ b/test/spec/modules/gumgumBidAdapter_spec.js @@ -111,6 +111,14 @@ describe('gumgumAdapter', function () { sizes: sizesArray } }, + userId: { + id5id: { + uid: 'uid-string', + ext: { + linkType: 2 + } + } + }, adUnitCode: 'adunit-code', sizes: sizesArray, bidId: '30b31c1838de1e', @@ -158,6 +166,12 @@ describe('gumgumAdapter', function () { const bidRequest = spec.buildRequests([request])[0]; expect(bidRequest.data.aun).to.equal(bidRequests[0].adUnitCode); }); + it('should set id5Id and id5IdLinkType if the uid and linkType are available', function () { + const request = { ...bidRequests[0] }; + const bidRequest = spec.buildRequests([request])[0]; + expect(bidRequest.data.id5Id).to.equal(bidRequests[0].userId.id5id.uid); + expect(bidRequest.data.id5IdLinkType).to.equal(bidRequests[0].userId.id5id.ext.linkType); + }); it('should set pubId param if found', function () { const request = { ...bidRequests[0], params: pubIdParam }; @@ -485,6 +499,46 @@ describe('gumgumAdapter', function () { const bidRequest = spec.buildRequests(bidRequests, fakeBidRequest)[0]; expect(bidRequest.data).to.not.include.any.keys('gdprConsent') }); + it('should add gpp parameters if gppConsent is present', function () { + const gppConsent = { gppString: 'DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN', applicableSections: [7] } + const fakeBidRequest = { gppConsent: gppConsent }; + const bidRequest = spec.buildRequests(bidRequests, fakeBidRequest)[0]; + expect(bidRequest.data.gppConsent).to.exist; + expect(bidRequest.data.gppConsent.gppString).to.equal(gppConsent.gppString); + expect(bidRequest.data.gppConsent.gpp_sid).to.equal(gppConsent.applicableSections); + expect(bidRequest.data.gppConsent.gppString).to.eq('DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN'); + }); + it('should handle ortb2 parameters', function () { + const ortb2 = { + regs: { + gpp: 'DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN', + gpp_sid: [7] + } + } + const fakeBidRequest = { gppConsent: ortb2 }; + const bidRequest = spec.buildRequests(bidRequests, fakeBidRequest)[0]; + expect(bidRequest.data.gppConsent.gppString).to.eq(fakeBidRequest[0]) + }); + it('should handle gppConsent is present but values are undefined case', function () { + const gppConsent = { gppString: undefined, applicableSections: undefined } + const fakeBidRequest = { gppConsent: gppConsent }; + const bidRequest = spec.buildRequests(bidRequests, fakeBidRequest)[0]; + expect(bidRequest.data.gppConsent).to.exist; + expect(bidRequest.data.gppConsent.gppString).to.equal(undefined); + expect(bidRequest.data.gppConsent.gpp_sid).to.equal(undefined); + }); + it('should handle ortb2 undefined parameters', function () { + const ortb2 = { + regs: { + gpp: undefined, + gpp_sid: undefined + } + } + const fakeBidRequest = { gppConsent: ortb2 }; + const bidRequest = spec.buildRequests(bidRequests, fakeBidRequest)[0]; + expect(bidRequest.data.gppConsent.gppString).to.eq(undefined) + expect(bidRequest.data.gppConsent.gpp_sid).to.eq(undefined) + }); it('should not set coppa parameter if coppa config is set to false', function () { config.setConfig({ coppa: false diff --git a/test/spec/modules/holidBidAdapter_spec.js b/test/spec/modules/holidBidAdapter_spec.js index e55befd213a..ef0283d0f2c 100644 --- a/test/spec/modules/holidBidAdapter_spec.js +++ b/test/spec/modules/holidBidAdapter_spec.js @@ -2,11 +2,14 @@ import { expect } from 'chai' import { spec } from 'modules/holidBidAdapter.js' describe('holidBidAdapterTests', () => { + const bidderRequest = { + bidderRequestId: 'test-id' + } + const bidRequestData = { bidder: 'holid', adUnitCode: 'test-div', bidId: 'bid-id', - auctionId: 'test-id', params: { adUnitID: '12345' }, mediaTypes: { banner: {} }, sizes: [[300, 250]], @@ -48,18 +51,26 @@ describe('holidBidAdapterTests', () => { describe('buildRequests', () => { const bid = JSON.parse(JSON.stringify(bidRequestData)) - const request = spec.buildRequests([bid], bid) + const request = spec.buildRequests([bid], bidderRequest) const payload = JSON.parse(request[0].data) + it('should include id in request', () => { + expect(payload.id).to.equal('test-id') + }) + it('should include ext in imp', () => { - expect(payload.imp[0].ext).to.exist expect(payload.imp[0].ext).to.deep.equal({ prebid: { storedrequest: { id: '12345' } }, }) }) + it('should include ext in request', () => { + expect(payload.ext).to.deep.equal({ + prebid: { storedrequest: { id: '12345' } }, + }) + }) + it('should include banner format in imp', () => { - expect(payload.imp[0].banner).to.exist expect(payload.imp[0].banner).to.deep.equal({ format: [{ w: 300, h: 250 }], }) @@ -146,6 +157,10 @@ describe('holidBidAdapterTests', () => { } const uspConsent = 'mkjvbiniwot4827obfoy8sdg8203gb' const expectedUserSyncs = [ + { + type: 'image', + url: 'https://track.adform.net/Serving/TrackPoint/?pm=2992097&lid=132720821', + }, { type: 'iframe', url: 'https://null.holid.io/sync.html?bidders=%5B%22test%20seat%201%22%2C%22test%20seat%202%22%5D&gdpr=1&gdpr_consent=dkj49Sjmfjuj34as:12jaf90123hufabidfy9u23brfpoig&usp_consent=mkjvbiniwot4827obfoy8sdg8203gb&type=iframe', @@ -162,7 +177,7 @@ describe('holidBidAdapterTests', () => { expect(userSyncs).to.deep.equal(expectedUserSyncs) }) - it('should return empty user syncs when responsetimemillis is not defined', () => { + it('should return base user syncs when responsetimemillis is not defined', () => { const optionsType = { iframeEnabled: true, pixelEnabled: true, @@ -179,7 +194,12 @@ describe('holidBidAdapterTests', () => { consentString: 'dkj49Sjmfjuj34as:12jaf90123hufabidfy9u23brfpoig', } const uspConsent = 'mkjvbiniwot4827obfoy8sdg8203gb' - const expectedUserSyncs = [] + const expectedUserSyncs = [ + { + type: 'image', + url: 'https://track.adform.net/Serving/TrackPoint/?pm=2992097&lid=132720821', + } + ] const userSyncs = spec.getUserSyncs( optionsType, diff --git a/test/spec/modules/hybridBidAdapter_spec.js b/test/spec/modules/hybridBidAdapter_spec.js index 6c98264c06f..a0d479fb4dc 100644 --- a/test/spec/modules/hybridBidAdapter_spec.js +++ b/test/spec/modules/hybridBidAdapter_spec.js @@ -8,7 +8,11 @@ function getSlotConfigs(mediaTypes, params) { bidId: '2df8c0733f284e', bidder: 'hybrid', mediaTypes: mediaTypes, - transactionId: '31a58515-3634-4e90-9c96-f86196db1459' + ortb2Imp: { + ext: { + tid: '31a58515-3634-4e90-9c96-f86196db1459' + } + } } } diff --git a/test/spec/modules/hypelabBidAdapter_spec.js b/test/spec/modules/hypelabBidAdapter_spec.js new file mode 100644 index 00000000000..4522073a2db --- /dev/null +++ b/test/spec/modules/hypelabBidAdapter_spec.js @@ -0,0 +1,281 @@ +import { expect } from 'chai'; +import sinon from 'sinon'; +import { server } from '../../mocks/xhr'; + +import { + mediaSize, + spec, + BIDDER_CODE, + ENDPOINT_URL, + REQUEST_ROUTE, +} from 'modules/hypelabBidAdapter.js'; + +import { BANNER } from 'src/mediaTypes.js'; + +const mockValidBidRequest = { + bidder: 'hypelab', + params: { + property_slug: 'prebid', + placement_slug: 'test_placement', + uuid: '', + sdk_version: '0.1.0', + provider_name: 'react', + provider_version: '0.3.1', + }, + userIds: [], + mediaTypes: { + banner: { + sizes: [[728, 90]], + }, + }, + adUnitCode: 'test-div', + sizes: [[728, 90]], + bidId: '24d2b2c86c5e19', + bidderRequestId: '1d1f40b509f18', + auctionId: '3bf3b1fb-cb0a-4ee8-90ef-69b8e6e56dbd', + bidRequestsCount: 1, + bidderRequestsCount: 1, + bidderWinsCount: 0, +}; + +const mockValidBidRequests = [mockValidBidRequest]; + +const mockServerResponse = { + body: { + status: 'success', + data: { + currency: 'USD', + campaign_slug: '9dbe230882', + creative_set_slug: '842984f045', + creative_set: { + image: { + url: 'https://cloudfront.net/up/asset/12345', + height: 90, + width: 728, + }, + }, + cpm: 1.5, + html: "\n \n \n \n Ad\n \n \n \n \n \n \n \n \n \n \n ", + advertiser_domains: ['ogx.com'], + media_type: 'banner', + ttl: 360, + }, + }, +}; + +const mockBidderRequest = { + bidderCode: 'hypelab', + auctionId: '4462005b-ba06-49a9-a95d-0209c22f4606', + bidderRequestId: '1bf399761210ad', + bids: mockValidBidRequests, + auctionStart: 1684983987435, + timeout: 2000, + refererInfo: { + topmostLocation: 'https://example.com/hello_world.html', + location: 'https://example.com/hello_world.html', + canonicalUrl: null, + page: 'https://example.com/hello_world.html', + domain: null, + ref: null, + }, +}; + +const mockBidRequest = { + method: 'POST', + url: 'https://api.hypelab.com/v1/prebid_requests', + options: { + contentType: 'application/json', + withCredentials: false, + }, + data: { + property_slug: 'prebid', + placement_slug: 'test_placement', + provider_version: '0.0.1', + provider_name: 'prebid', + referrer: 'https://example.com', + sdk_version: '7.51.0-pre', + sizes: [[728, 90]], + wids: [], + uuid: 'tmp_c5abf809-47d6-40b9-8274-372c6d816dd8', + bidRequestsCount: 1, + bidderRequestsCount: 1, + bidderWinsCount: 0, + }, + bidId: '2e02b562f700ae', +}; + +describe('hypelabBidAdapter', function () { + describe('mediaSize', function () { + describe('when given an invalid media object', function () { + expect(mediaSize({})).to.eql({ width: 0, height: 0 }); + }); + + describe('when given a valid media object', function () { + expect( + mediaSize({ creative_set: { image: { width: 728, height: 90 } } }) + ).to.eql({ width: 728, height: 90 }); + }); + }); + + describe('isBidRequestValid', function () { + describe('when given an invalid bid request', function () { + expect(spec.isBidRequestValid({})).to.equal(false); + }); + + describe('when given a valid bid request', function () { + expect(spec.isBidRequestValid(mockValidBidRequest)).to.equal(true); + }); + }); + + describe('Bidder code valid', function () { + expect(spec.code).to.equal(BIDDER_CODE); + }); + + describe('Media types valid', function () { + expect(spec.supportedMediaTypes).to.contain(BANNER); + }); + + describe('Bid request valid', function () { + expect(spec.isBidRequestValid(mockValidBidRequest)).to.equal(true); + }); + + describe('buildRequests', () => { + describe('returns a valid request', function () { + const result = spec.buildRequests( + mockValidBidRequests, + mockBidderRequest + ); + expect(result).to.be.an('array'); + + const first = result[0] || {}; + expect(first).to.be.an('object'); + expect(first.method).to.equal('POST'); + expect(first.url).to.be.a('string'); + expect(first.url).to.equal(ENDPOINT_URL + REQUEST_ROUTE); + + const data = first.data || {}; + expect(data).to.be.an('object'); + expect(data.property_slug).to.be.a('string'); + expect(data.placement_slug).to.be.a('string'); + expect(data.bidRequestsCount).to.be.a('number'); + expect(data.bidderRequestsCount).to.be.a('number'); + expect(data.bidderWinsCount).to.be.a('number'); + }); + + describe('should set uuid to the first id in userIdAsEids', () => { + mockValidBidRequests[0].userIdAsEids = [ + { + source: 'pubcid.org', + uids: [ + { + id: 'pubcid_id', + }, + ], + }, + { + source: 'criteo.com', + uids: [ + { + id: 'criteo_id', + }, + ], + }, + ]; + + const result = spec.buildRequests( + mockValidBidRequests, + mockBidderRequest + ); + + const data = result[0].data || {}; + expect(data.uuid).to.be.eq('pubcid_id'); + }); + }); + + describe('interpretResponse', () => { + describe('successfully interpret a valid response', function () { + const result = spec.interpretResponse(mockServerResponse, mockBidRequest); + + expect(result).to.be.an('array'); + const data = result[0] || {}; + expect(data).to.be.an('object'); + expect(data.requestId).to.be.a('string'); + expect(data.cpm).to.be.a('number'); + expect(data.width).to.be.a('number'); + expect(data.height).to.be.a('number'); + expect(data.creativeId).to.be.a('string'); + expect(data.currency).to.be.a('string'); + expect(data.netRevenue).to.be.a('boolean'); + expect(data.referrer).to.be.a('string'); + expect(data.ttl).to.be.a('number'); + expect(data.ad).to.be.a('string'); + expect(data.mediaType).to.be.a('string'); + expect(data.meta.advertiserDomains).to.be.an('array'); + }); + + describe('should return a blank array if cpm is not set', () => { + mockServerResponse.body.data.cpm = undefined; + const result = spec.interpretResponse(mockServerResponse, mockBidRequest); + expect(result).to.eql([]); + }); + }); + + describe('report', () => { + it('returns if REPORTING_ROUTE is not set', () => { + spec.REPORTING_ROUTE = ''; + expect(spec.report('test', {})).to.be.undefined; + }); + + it('makes a POST request if REPORTING_ROUTE is set', () => { + spec.report('test', {}, '/v1/events'); + + expect(server.requests[0].url).to.equals( + 'https://api.hypelab.com/v1/events' + ); + }); + }); + + describe('callbacks', () => { + let bid = {}; + let reportStub; + + beforeEach(() => (reportStub = sinon.stub(spec, 'report'))); + afterEach(() => reportStub.restore()); + + describe('onTimeout', () => { + it('should call report with the correct data', () => { + spec.onTimeout(bid); + + expect(reportStub.calledOnce).to.be.true; + expect(reportStub.getCall(0).args).to.eql(['timeout', bid]); + }); + }); + + describe('onSetTargeting', () => { + it('should call report with the correct data', () => { + spec.onSetTargeting(bid); + + expect(reportStub.calledOnce).to.be.true; + expect(reportStub.getCall(0).args).to.eql(['setTargeting', bid]); + }); + }); + + describe('onBidWon', () => { + it('should call report with the correct data', () => { + spec.onBidWon(bid); + + expect(reportStub.calledOnce).to.be.true; + expect(reportStub.getCall(0).args).to.eql(['bidWon', bid]); + }); + }); + + describe('onBidderError', () => { + it('should call report with the correct data', () => { + spec.onBidderError(bid); + + expect(reportStub.calledOnce).to.be.true; + expect(reportStub.getCall(0).args).to.eql(['bidderError', bid]); + }); + }); + }); +}); diff --git a/test/spec/modules/id5IdSystem_spec.js b/test/spec/modules/id5IdSystem_spec.js index 8c0f8ad9cf3..9db26ed91ce 100644 --- a/test/spec/modules/id5IdSystem_spec.js +++ b/test/spec/modules/id5IdSystem_spec.js @@ -42,7 +42,9 @@ describe('ID5 ID System', function () { const ID5_STORED_OBJ = { 'universal_uid': ID5_STORED_ID, 'signature': ID5_STORED_SIGNATURE, - 'link_type': ID5_STORED_LINK_TYPE + 'ext': { + 'linkType': ID5_STORED_LINK_TYPE + } }; const ID5_RESPONSE_ID = 'newid5id'; const ID5_RESPONSE_SIGNATURE = 'abcdef'; @@ -50,8 +52,27 @@ describe('ID5 ID System', function () { const ID5_JSON_RESPONSE = { 'universal_uid': ID5_RESPONSE_ID, 'signature': ID5_RESPONSE_SIGNATURE, - 'link_type': ID5_RESPONSE_LINK_TYPE + 'link_type': ID5_RESPONSE_LINK_TYPE, + 'ext': { + 'linkType': ID5_RESPONSE_LINK_TYPE + } }; + const ALLOWED_ID5_VENDOR_DATA = { + purpose: { + consents: { + 1: true + } + }, + vendor: { + consents: { + 131: true + } + } + } + + const HEADERS_CONTENT_TYPE_JSON = { + 'Content-Type': 'application/json' + } function getId5FetchConfig(storageName = ID5_STORAGE_NAME, storageType = 'html5') { return { @@ -136,7 +157,7 @@ describe('ID5 ID System', function () { } respondWithConfigAndExpectNext(configRequest, config = ID5_API_CONFIG) { - configRequest.respond(200, {'Content-Type': 'application/json'}, JSON.stringify(config)); + configRequest.respond(200, HEADERS_CONTENT_TYPE_JSON, JSON.stringify(config)); return this.expectNextRequest() } @@ -205,8 +226,39 @@ describe('ID5 ID System', function () { }); }); + describe('Check for valid consent', function() { + const dataConsentVals = [ + [{purpose: {consents: {1: false}}}, {vendor: {consents: {131: true}}}, ' no purpose consent'], + [{purpose: {consents: {1: true}}}, {vendor: {consents: {131: false}}}, ' no vendor consent'], + [{purpose: {consents: {1: false}}}, {vendor: {consents: {131: false}}}, ' no purpose and vendor consent'], + [{purpose: {consents: undefined}}, {vendor: {consents: {131: true}}}, ' undefined purpose consent'], + [{purpose: {consents: {1: false}}}, {vendor: {consents: undefined}}], ' undefined vendor consent', + [undefined, {vendor: {consents: {131: true}}}, ' undefined purpose'], + [{purpose: {consents: {1: true}}}, {vendor: undefined}, ' undefined vendor'], + [{purpose: {consents: {1: true}}}, {vendor: {consents: {31: true}}}, ' incorrect vendor consent'] + ]; + + dataConsentVals.forEach(function([purposeConsent, vendorConsent, caseName]) { + it('should fail with invalid consent because of ' + caseName, function() { + let dataConsent = { + gdprApplies: true, + consentString: 'consentString', + vendorData: { + purposeConsent, vendorConsent + } + } + expect(id5IdSubmodule.getId(config)).is.eq(undefined); + expect(id5IdSubmodule.getId(config, dataConsent)).is.eq(undefined); + + let cacheIdObject = 'cacheIdObject'; + expect(id5IdSubmodule.extendId(config)).is.eq(undefined); + expect(id5IdSubmodule.extendId(config, dataConsent, cacheIdObject)).is.eq(cacheIdObject); + }); + }); + }); + describe('Xhr Requests from getId()', function () { - const responseHeader = {'Content-Type': 'application/json'}; + const responseHeader = HEADERS_CONTENT_TYPE_JSON beforeEach(function () { }); @@ -248,7 +300,8 @@ describe('ID5 ID System', function () { let xhrServerMock = new XhrServerMock(sinon.createFakeServer()) let consentData = { gdprApplies: true, - consentString: 'consentString' + consentString: 'consentString', + vendorData: ALLOWED_ID5_VENDOR_DATA } let submoduleResponse = callSubmoduleGetId(getId5FetchConfig(), consentData, undefined); @@ -297,7 +350,8 @@ describe('ID5 ID System', function () { let xhrServerMock = new XhrServerMock(sinon.createFakeServer()) let consentData = { gdprApplies: true, - consentString: 'consentString' + consentString: 'consentString', + vendorData: ALLOWED_ID5_VENDOR_DATA } let submoduleResponse = callSubmoduleGetId(getId5FetchConfig(), consentData, undefined); @@ -674,6 +728,40 @@ describe('ID5 ID System', function () { }) }); + describe('Local storage', () => { + let sandbox; + beforeEach(() => { + sandbox = sinon.sandbox.create(); + sandbox.stub(storage, 'localStorageIsEnabled'); + }); + afterEach(() => { + sandbox.restore(); + }); + [ + [true, 1], + [false, 0] + ].forEach(function ([isEnabled, expectedValue]) { + it(`should check localStorage availability and log in request. Available=${isEnabled}`, () => { + let xhrServerMock = new XhrServerMock(sinon.createFakeServer()) + let config = getId5FetchConfig(); + let submoduleResponse = callSubmoduleGetId(config, undefined, undefined); + storage.localStorageIsEnabled.callsFake(() => isEnabled) + + return xhrServerMock.expectFetchRequest() + .then(fetchRequest => { + let requestBody = JSON.parse(fetchRequest.requestBody); + expect(requestBody.localStorage).is.eq(expectedValue); + + fetchRequest.respond(200, HEADERS_CONTENT_TYPE_JSON, JSON.stringify(ID5_JSON_RESPONSE)); + return submoduleResponse + }) + .then(submoduleResponse => { + expect(submoduleResponse).is.deep.equal(ID5_JSON_RESPONSE); + }); + }) + }) + }); + describe('Request Bids Hook', function () { let adUnits; let sandbox; @@ -797,8 +885,7 @@ describe('ID5 ID System', function () { expect(requestBody.s).is.eq(ID5_STORED_SIGNATURE); expect(requestBody.nbPage).is.eq(2); expect(getNbFromCache(ID5_TEST_PARTNER_ID)).is.eq(2); - const responseHeader = {'Content-Type': 'application/json'}; - request.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); + request.respond(200, HEADERS_CONTENT_TYPE_JSON, JSON.stringify(ID5_JSON_RESPONSE)); return new Promise(function (resolve) { (function waitForCondition() { @@ -876,7 +963,9 @@ describe('ID5 ID System', function () { it('should not expose ID when everyone is in control group', function () { storedObject.ab_testing = {result: 'control'}; storedObject.universal_uid = ''; - storedObject.link_type = 0; + storedObject.ext = { + 'linkType': 0 + }; let decoded = id5IdSubmodule.decode(storedObject, testConfig); expect(decoded).is.deep.equal(expectedDecodedObjectWithoutIdAbOn); }); diff --git a/test/spec/modules/identityLinkIdSystem_spec.js b/test/spec/modules/identityLinkIdSystem_spec.js index a31270c86c7..52e9f9171d6 100644 --- a/test/spec/modules/identityLinkIdSystem_spec.js +++ b/test/spec/modules/identityLinkIdSystem_spec.js @@ -1,9 +1,9 @@ import {identityLinkSubmodule} from 'modules/identityLinkIdSystem.js'; import * as utils from 'src/utils.js'; import {server} from 'test/mocks/xhr.js'; -import {getStorageManager} from '../../../src/storageManager.js'; +import {getCoreStorageManager} from '../../../src/storageManager.js'; -export const storage = getStorageManager(); +const storage = getCoreStorageManager(); const pid = '14'; let defaultConfigParams; diff --git a/test/spec/modules/synacormediaBidAdapter_spec.js b/test/spec/modules/imdsBidAdapter_spec.js similarity index 96% rename from test/spec/modules/synacormediaBidAdapter_spec.js rename to test/spec/modules/imdsBidAdapter_spec.js index 0773d29789d..ce04fabe02b 100644 --- a/test/spec/modules/synacormediaBidAdapter_spec.js +++ b/test/spec/modules/imdsBidAdapter_spec.js @@ -1,9 +1,10 @@ import { assert, expect } from 'chai'; import { BANNER } from 'src/mediaTypes.js'; import { config } from 'src/config.js'; -import { spec } from 'modules/synacormediaBidAdapter.js'; +import { spec } from 'modules/imdsBidAdapter.js'; +import * as utils from 'src/utils.js'; -describe('synacormediaBidAdapter ', function () { +describe('imdsBidAdapter ', function () { describe('isBidRequestValid', function () { let bid; beforeEach(function () { @@ -115,7 +116,7 @@ describe('synacormediaBidAdapter ', function () { }); describe('buildRequests', function () { let validBidRequestVideo = { - bidder: 'synacormedia', + bidder: 'imds', params: { seatId: 'prebid', tagId: '1234', @@ -140,7 +141,7 @@ describe('synacormediaBidAdapter ', function () { }; let bidderRequestVideo = { - bidderCode: 'synacormedia', + bidderCode: 'imds', auctionId: 'VideoAuctionId124', bidderRequestId: '117954d20d7c9c', auctionStart: 1553624929697, @@ -177,7 +178,7 @@ describe('synacormediaBidAdapter ', function () { }; let bidderRequest = { - auctionId: 'xyz123', + bidderRequestId: 'xyz123', refererInfo: { referer: 'https://test.com/foo/bar' } @@ -192,7 +193,7 @@ describe('synacormediaBidAdapter ', function () { }; let bidderRequestWithCCPA = { - auctionId: 'xyz123', + bidderRequestId: 'xyz123', refererInfo: { referer: 'https://test.com/foo/bar' }, @@ -299,7 +300,6 @@ describe('synacormediaBidAdapter ', function () { expect(reqVideo).to.have.property('url'); expect(reqVideo.url).to.contain('https://prebid.technoratimedia.com/openrtb/bids/prebid?'); expect(reqVideo.data).to.exist.and.to.be.an('object'); - expect(reqVideo.data.id).to.equal('VideoAuctionId124'); expect(reqVideo.data.imp).to.eql([expectedDataVideo1]); }); @@ -313,32 +313,6 @@ describe('synacormediaBidAdapter ', function () { expect(req.data.tmax).to.eql(bidderRequestWithTimeout.timeout); }); - it('should return tmax equal to smaller global timeout', function () { - let sandbox = sinon.sandbox.create(); - sandbox.stub(config, 'getConfig').callsFake(key => { - const config = { - 'bidderTimeout': bidderRequestWithTimeout.timeout - 100 - }; - return config[key]; - }); - let req = spec.buildRequests([validBidRequest], bidderRequestWithTimeout); - sandbox.restore(); - expect(req.data.tmax).to.eql(bidderRequestWithTimeout.timeout - 100); - }); - - it('should return tmax equal to smaller callback timeout', function () { - let sandbox = sinon.sandbox.create(); - sandbox.stub(config, 'getConfig').callsFake(key => { - const config = { - 'bidderTimeout': bidderRequestWithTimeout.timeout + 100 - }; - return config[key]; - }); - let req = spec.buildRequests([validBidRequest], bidderRequestWithTimeout); - sandbox.restore(); - expect(req.data.tmax).to.eql(bidderRequestWithTimeout.timeout); - }); - it('should return multiple bids when multiple valid requests with the same seatId are used', function () { let secondBidRequest = { bidId: 'foobar', @@ -569,7 +543,7 @@ describe('synacormediaBidAdapter ', function () { }); it('should use all the video params in the impression request', function () { let validBidRequestVideo = { - bidder: 'synacormedia', + bidder: 'imds', params: { seatId: 'prebid', tagId: '1234', @@ -627,7 +601,7 @@ describe('synacormediaBidAdapter ', function () { }); it('should move any video params in the mediaTypes object to params.video object', function () { let validBidRequestVideo = { - bidder: 'synacormedia', + bidder: 'imds', params: { seatId: 'prebid', tagId: '1234', @@ -685,7 +659,7 @@ describe('synacormediaBidAdapter ', function () { }); it('should create params.video object if not present on bid request and move any video params in the mediaTypes object to it', function () { let validBidRequestVideo = { - bidder: 'synacormedia', + bidder: 'imds', params: { seatId: 'prebid', tagId: '1234' @@ -757,7 +731,7 @@ describe('synacormediaBidAdapter ', function () { describe('Bid Requests with placementId should be backward compatible ', function () { let validVideoBidReq = { - bidder: 'synacormedia', + bidder: 'imds', params: { seatId: 'prebid', placementId: 'demo1', @@ -798,7 +772,7 @@ describe('synacormediaBidAdapter ', function () { refererInfo: { referer: 'http://localhost:9999/' }, - bidderCode: 'synacormedia', + bidderCode: 'imds', auctionId: 'f8a75621-d672-4cbb-9275-3db7d74fb110' }; @@ -819,7 +793,7 @@ describe('synacormediaBidAdapter ', function () { describe('Bid Requests with schain object ', function () { let validBidReq = { - bidder: 'synacormedia', + bidder: 'imds', params: { seatId: 'prebid', tagId: 'demo1', @@ -861,12 +835,12 @@ describe('synacormediaBidAdapter ', function () { refererInfo: { referer: 'http://localhost:9999/' }, - bidderCode: 'synacormedia', + bidderCode: 'imds', auctionId: 'f8a75621-d672-4cbb-9275-3db7d74fb110', bidderRequestId: '16d438671bfbec', bids: [ { - bidder: 'synacormedia', + bidder: 'imds', params: { seatId: 'prebid', tagId: 'demo1', @@ -1339,7 +1313,7 @@ describe('synacormediaBidAdapter ', function () { describe('Bid Requests with price module should use if available', function () { let validVideoBidRequest = { - bidder: 'synacormedia', + bidder: 'imds', params: { bidfloor: '0.50', seatId: 'prebid', @@ -1382,7 +1356,7 @@ describe('synacormediaBidAdapter ', function () { refererInfo: { referer: 'http://localhost:9999/' }, - bidderCode: 'synacormedia', + bidderCode: 'imds', auctionId: 'f8a75621-d672-4cbb-9275-3db7d74fb110' }; @@ -1409,7 +1383,7 @@ describe('synacormediaBidAdapter ', function () { describe('Bid Requests with gpid or anything in bid.ext should use if available', function () { let validVideoBidRequest = { - bidder: 'synacormedia', + bidder: 'imds', params: { seatId: 'prebid', placementId: 'demo1', @@ -1466,7 +1440,7 @@ describe('synacormediaBidAdapter ', function () { refererInfo: { referer: 'http://localhost:9999/' }, - bidderCode: 'synacormedia', + bidderCode: 'imds', auctionId: 'f8a75621-d672-4cbb-9275-3db7d74fb110' }; diff --git a/test/spec/modules/improvedigitalBidAdapter_spec.js b/test/spec/modules/improvedigitalBidAdapter_spec.js index 400b9145e0b..f427f9e7624 100644 --- a/test/spec/modules/improvedigitalBidAdapter_spec.js +++ b/test/spec/modules/improvedigitalBidAdapter_spec.js @@ -1,9 +1,9 @@ -import { expect } from 'chai'; +import {expect} from 'chai'; import {CONVERTER, spec} from 'modules/improvedigitalBidAdapter.js'; -import { config } from 'src/config.js'; -import { deepClone } from 'src/utils.js'; +import {config} from 'src/config.js'; +import {deepClone} from 'src/utils.js'; import {BANNER, NATIVE, VIDEO} from '../../../src/mediaTypes'; -import { deepSetValue } from '../../../src/utils'; +import {deepSetValue} from '../../../src/utils'; // load modules that register ORTB processors import 'src/prebid.js'; import 'modules/currency.js'; @@ -13,8 +13,7 @@ import 'modules/priceFloors.js'; import 'modules/consentManagement.js'; import 'modules/consentManagementUsp.js'; import 'modules/schain.js'; -import {decorateAdUnitsWithNativeParams, toLegacyResponse} from '../../../src/native.js'; -import {createEidsArray} from '../../../modules/userId/eids.js'; +import {decorateAdUnitsWithNativeParams} from '../../../src/native.js'; import {syncAddFPDToBidderRequest} from '../../helpers/fpd.js'; import {hook} from '../../../src/hook.js'; @@ -120,6 +119,11 @@ describe('Improve Digital Adapter Tests', function () { }; const bidderRequest = { + ortb2: { + source: { + tid: 'mock-tid' + } + }, bids: [simpleBidRequest], }; @@ -237,7 +241,7 @@ describe('Improve Digital Adapter Tests', function () { expect(payload.tmax).not.to.exist; expect(payload.regs).to.not.exist; expect(payload.schain).to.not.exist; - sinon.assert.match(payload.source, {tid: 'f183e871-fbed-45f0-a427-c8a63c4c01eb'}) + sinon.assert.match(payload.source, {tid: 'mock-tid'}) expect(payload.device).to.be.an('object'); expect(payload.user).to.not.exist; sinon.assert.match(payload.imp, [ @@ -278,12 +282,14 @@ describe('Improve Digital Adapter Tests', function () { placementId: 1053688, } }, - video: { - placement: OUTSTREAM_TYPE, - w: 640, - h: 480, - mimes: ['video/mp4'], - }, + ...(FEATURES.VIDEO && { + video: { + placement: OUTSTREAM_TYPE, + w: 640, + h: 480, + mimes: ['video/mp4'], + } + }), banner: { format: [ {w: 300, h: 250}, @@ -468,102 +474,104 @@ describe('Improve Digital Adapter Tests', function () { expect(payload.imp[0].video).to.not.exist; }); - it('should add correct placement value for instream and outstream video', function () { - let bidRequest = deepClone(simpleBidRequest); - let payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequest)[0].data); - expect(payload.imp[0].video).to.not.exist; + if (FEATURES.VIDEO) { + it('should add correct placement value for instream and outstream video', function () { + let bidRequest = deepClone(simpleBidRequest); + let payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequest)[0].data); + expect(payload.imp[0].video).to.not.exist; - bidRequest = deepClone(simpleBidRequest); - bidRequest.mediaTypes = { - video: { - context: 'instream', - playerSize: [640, 480] - } - }; - payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequest)[0].data); - expect(payload.imp[0].video.placement).to.exist.and.equal(1); - bidRequest.mediaTypes.video.context = 'outstream'; - payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequest)[0].data); - expect(payload.imp[0].video.placement).to.exist.and.equal(3); - }); + bidRequest = deepClone(simpleBidRequest); + bidRequest.mediaTypes = { + video: { + context: 'instream', + playerSize: [640, 480] + } + }; + payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequest)[0].data); + expect(payload.imp[0].video.placement).to.exist.and.equal(1); + bidRequest.mediaTypes.video.context = 'outstream'; + payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequest)[0].data); + expect(payload.imp[0].video.placement).to.exist.and.equal(3); + }); - it('should set video params for instream', function() { - const bidRequest = deepClone(instreamBidRequest); - delete bidRequest.mediaTypes.video.playerSize; - const videoParams = { - mimes: ['video/mp4'], - skip: 1, - skipmin: 5, - skipafter: 30, - minduration: 15, - maxduration: 60, - startdelay: 5, - minbitrate: 500, - maxbitrate: 2000, - w: 1024, - h: 640, - placement: INSTREAM_TYPE, - }; - bidRequest.params.video = videoParams; - const request = spec.buildRequests([bidRequest], bidderRequest)[0]; - const payload = JSON.parse(request.data); - expect(payload.imp[0].video).to.deep.equal(videoParams); - }); + it('should set video params for instream', function() { + const bidRequest = deepClone(instreamBidRequest); + delete bidRequest.mediaTypes.video.playerSize; + const videoParams = { + mimes: ['video/mp4'], + skip: 1, + skipmin: 5, + skipafter: 30, + minduration: 15, + maxduration: 60, + startdelay: 5, + minbitrate: 500, + maxbitrate: 2000, + w: 1024, + h: 640, + placement: INSTREAM_TYPE, + }; + bidRequest.params.video = videoParams; + const request = spec.buildRequests([bidRequest], bidderRequest)[0]; + const payload = JSON.parse(request.data); + expect(payload.imp[0].video).to.deep.equal(videoParams); + }); - it('should set video playerSize over video params', () => { - const bidRequest = deepClone(instreamBidRequest); - bidRequest.params.video = { - w: 1024, h: 640 - } - const request = spec.buildRequests([bidRequest], bidderRequest)[0]; - const payload = JSON.parse(request.data); - expect(payload.imp[0].video.h).equal(480); - expect(payload.imp[0].video.w).equal(640); - }); + it('should set video playerSize over video params', () => { + const bidRequest = deepClone(instreamBidRequest); + bidRequest.params.video = { + w: 1024, h: 640 + } + const request = spec.buildRequests([bidRequest], bidderRequest)[0]; + const payload = JSON.parse(request.data); + expect(payload.imp[0].video.h).equal(480); + expect(payload.imp[0].video.w).equal(640); + }); - it('should ignore invalid/unexpected video params', function() { - const bidRequest = deepClone(instreamBidRequest); - // 1 - const videoTest = { - skip: 1, - skipmin: 5, - skipafter: 30 - } - const videoTestInvParam = Object.assign({}, videoTest); - videoTestInvParam.blah = 1; - bidRequest.params.video = videoTestInvParam; - let request = spec.buildRequests([bidRequest], {})[0]; - let payload = JSON.parse(request.data); - expect(payload.imp[0].video.blah).not.to.exist; - }); + it('should ignore invalid/unexpected video params', function() { + const bidRequest = deepClone(instreamBidRequest); + // 1 + const videoTest = { + skip: 1, + skipmin: 5, + skipafter: 30 + } + const videoTestInvParam = Object.assign({}, videoTest); + videoTestInvParam.blah = 1; + bidRequest.params.video = videoTestInvParam; + let request = spec.buildRequests([bidRequest], {})[0]; + let payload = JSON.parse(request.data); + expect(payload.imp[0].video.blah).not.to.exist; + }); - it('should set video params for outstream', function() { - const bidRequest = deepClone(outstreamBidRequest); - bidRequest.params.video = videoParams; - const request = spec.buildRequests([bidRequest], {})[0]; - const payload = JSON.parse(request.data); - expect(payload.imp[0].video).to.deep.equal({...{ - mimes: ['video/mp4'], - placement: OUTSTREAM_TYPE, - w: bidRequest.mediaTypes.video.playerSize[0], - h: bidRequest.mediaTypes.video.playerSize[1], - }, - ...videoParams}); - }); - // - it('should set video params for multi-format', function() { - const bidRequest = deepClone(multiFormatBidRequest); - bidRequest.params.video = videoParams; - const request = spec.buildRequests([bidRequest], {})[0]; - const payload = JSON.parse(request.data); - const testVideoParams = Object.assign({ - placement: OUTSTREAM_TYPE, - w: 640, - h: 480, - mimes: ['video/mp4'], - }, videoParams); - expect(payload.imp[0].video).to.deep.equal(testVideoParams); - }); + it('should set video params for outstream', function() { + const bidRequest = deepClone(outstreamBidRequest); + bidRequest.params.video = videoParams; + const request = spec.buildRequests([bidRequest], {})[0]; + const payload = JSON.parse(request.data); + expect(payload.imp[0].video).to.deep.equal({...{ + mimes: ['video/mp4'], + placement: OUTSTREAM_TYPE, + w: bidRequest.mediaTypes.video.playerSize[0], + h: bidRequest.mediaTypes.video.playerSize[1], + }, + ...videoParams}); + }); + // + it('should set video params for multi-format', function() { + const bidRequest = deepClone(multiFormatBidRequest); + bidRequest.params.video = videoParams; + const request = spec.buildRequests([bidRequest], {})[0]; + const payload = JSON.parse(request.data); + const testVideoParams = Object.assign({ + placement: OUTSTREAM_TYPE, + w: 640, + h: 480, + mimes: ['video/mp4'], + }, videoParams); + expect(payload.imp[0].video).to.deep.equal(testVideoParams); + }); + } it('should add schain', function () { const schain = '{"ver":"1.0","complete":1,"nodes":[{"asi":"headerlift.com","sid":"xyz","hp":1}]}'; @@ -575,7 +583,15 @@ describe('Improve Digital Adapter Tests', function () { }); it('should add eids', function () { - const userId = { id5id: { uid: '1111' } }; + const userIdAsEids = [ + { + source: 'id5-sync.com', + uids: [{ + atype: 1, + id: '1111' + }] + } + ]; const expectedUserObject = { ext: { eids: [{ source: 'id5-sync.com', uids: [{ @@ -584,7 +600,7 @@ describe('Improve Digital Adapter Tests', function () { }] }]}}; const bidRequest = Object.assign({}, simpleBidRequest); - bidRequest.userIdAsEids = createEidsArray(userId); + bidRequest.userIdAsEids = userIdAsEids; const request = spec.buildRequests([bidRequest], bidderRequestReferrer)[0]; const payload = JSON.parse(request.data); expect(payload.user).to.deep.equal(expectedUserObject); @@ -609,7 +625,7 @@ describe('Improve Digital Adapter Tests', function () { const request = JSON.parse(requests[0].data); expect(request.imp.length).to.equal(2); expect(request.imp[0].banner).to.exist; - expect(request.imp[1].video).to.exist; + if (FEATURES.VIDEO) { expect(request.imp[1].video).to.exist; } }); it('should create one request per endpoint in a single request mode', function () { @@ -623,7 +639,7 @@ describe('Improve Digital Adapter Tests', function () { const adServerRequest = JSON.parse(requests[1].data); expect(adServerRequest.imp.length).to.equal(2); expect(adServerRequest.imp[0].banner).to.exist; - expect(adServerRequest.imp[1].video).to.exist; + if (FEATURES.VIDEO) { expect(adServerRequest.imp[1].video).to.exist; } }); it('should set Prebid sizes in bid request', function () { @@ -670,7 +686,7 @@ describe('Improve Digital Adapter Tests', function () { it('should not set site when app is defined in CONFIG', function () { getConfigStub = sinon.stub(config, 'getConfig'); getConfigStub.withArgs('app').returns({ content: 'XYZ' }); - let request = spec.buildRequests([simpleBidRequest], bidderRequest)[0]; + let request = spec.buildRequests([simpleBidRequest], syncAddFPDToBidderRequest(bidderRequest))[0]; let payload = JSON.parse(request.data); expect(payload.site).does.not.exist; expect(payload.app).does.exist; @@ -709,7 +725,7 @@ describe('Improve Digital Adapter Tests', function () { getConfigStub = sinon.stub(config, 'getConfig'); getConfigStub.withArgs('app').returns(undefined); getConfigStub.withArgs('site').returns({}); - let request = spec.buildRequests([simpleBidRequest], bidderRequest)[0]; + let request = spec.buildRequests([simpleBidRequest], syncAddFPDToBidderRequest(bidderRequest))[0]; let payload = JSON.parse(request.data); expect(payload.site).does.exist; expect(payload.app).does.not.exist; @@ -1238,32 +1254,34 @@ describe('Improve Digital Adapter Tests', function () { } // Video - it('should return a well-formed instream video bid', function () { - const bids = spec.interpretResponse(serverResponseVideo, makeRequest(instreamBidderRequest)); - expectMatch(bids, expectedBidInstreamVideo); - }); + if (FEATURES.VIDEO) { + it('should return a well-formed instream video bid', function () { + const bids = spec.interpretResponse(serverResponseVideo, makeRequest(instreamBidderRequest)); + expectMatch(bids, expectedBidInstreamVideo); + }); - it('should return a well-formed outstream video bid', function () { - const bids = spec.interpretResponse(serverResponseVideo, makeRequest(outstreamBidderRequest)); - expect(bids[0].renderer).to.exist; - expectMatch(bids, expectedBidOutstreamVideo); - }); + it('should return a well-formed outstream video bid', function () { + const bids = spec.interpretResponse(serverResponseVideo, makeRequest(outstreamBidderRequest)); + expect(bids[0].renderer).to.exist; + expectMatch(bids, expectedBidOutstreamVideo); + }); - it('should return a well-formed outstream video bid for multi-format ad unit', function () { - const request = makeRequest(multiFormatBidderRequest); - const videoResponse = deepClone(serverResponseVideo); - let bids = spec.interpretResponse(videoResponse, request); - expect(bids[0].renderer).to.exist; - expectMatch(bids, expectedBidOutstreamVideo); + it('should return a well-formed outstream video bid for multi-format ad unit', function () { + const request = makeRequest(multiFormatBidderRequest); + const videoResponse = deepClone(serverResponseVideo); + let bids = spec.interpretResponse(videoResponse, request); + expect(bids[0].renderer).to.exist; + expectMatch(bids, expectedBidOutstreamVideo); - videoResponse.body.seatbid[0].bid[0].adm = '', - meta: { - mediaType: 'banner', - }, - width: 300, - height: 250, - creativeId: '189198063', - netRevenue: true, - currency: 'USD', - ttl: 300, - dealId: 'dealId' - - } - }; - - var BID_RESPONSE_VIDEO = { - body: { - cpm: 1.50, - meta: { - mediaType: 'video', - }, - width: 1, - height: 1, - creativeId: '189198063', - netRevenue: true, - currency: 'USD', - ttl: 300, - vastUrl: 'https://vast.com/vast.xml', - dealId: 'dealId' - } - }; - - it('Verify build request to prebid 3.0 display test', function() { - const request = spec.buildRequests(DEFAULT_PARAMS_NEW_SIZES, { - gdprConsent: { - consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', - gdprApplies: true - }, - refererInfo: { - legacy: { - referer: 'https://domain.com', - numIframes: 0 - } - } - }); - - expect(request).to.have.property('method').and.to.equal('POST'); - const requestContent = JSON.parse(request.data); - expect(requestContent.bidRequests[0].params).to.have.property('partnerId').and.to.equal(12345); - expect(requestContent.bidRequests[0]).to.have.property('auctionId').and.to.equal('0cb3144c-d084-4686-b0d6-f5dbe917c563'); - expect(requestContent.bidRequests[0]).to.have.property('bidId').and.to.equal('2c7c8e9c900244'); - expect(requestContent.bidRequests[0]).to.have.property('bidRequestsCount').and.to.equal(1); - expect(requestContent.bidRequests[0]).to.have.property('bidder').and.to.equal('inmar'); - expect(requestContent.bidRequests[0]).to.have.property('bidderRequestId').and.to.equal('1858b7382993ca'); - expect(requestContent.bidRequests[0]).to.have.property('adUnitCode').and.to.equal('test-div'); - expect(requestContent.refererInfo).to.have.property('referer').and.to.equal('https://domain.com'); - expect(requestContent.bidRequests[0].mediaTypes.banner).to.have.property('sizes'); - expect(requestContent.bidRequests[0].mediaTypes.banner.sizes[0]).to.have.ordered.members([300, 250]); - expect(requestContent.bidRequests[0].mediaTypes.banner.sizes[1]).to.have.ordered.members([300, 600]); - expect(requestContent.bidRequests[0].mediaTypes.banner.sizes[2]).to.have.ordered.members([728, 90]); - expect(requestContent.bidRequests[0].mediaTypes.banner.sizes[3]).to.have.ordered.members([970, 250]); - expect(requestContent.bidRequests[0]).to.have.property('transactionId').and.to.equal('29df2112-348b-4961-8863-1b33684d95e6'); - expect(requestContent.refererInfo).to.have.property('numIframes').and.to.equal(0); - }) - - it('Verify interprete response', function () { - const request = spec.buildRequests(DEFAULT_PARAMS_NEW_SIZES, { - gdprConsent: { - consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', - gdprApplies: true - }, - refererInfo: { - referer: 'https://domain.com', - numIframes: 0 - } - }); - - const bids = spec.interpretResponse(BID_RESPONSE, request); - expect(bids).to.have.lengthOf(1); - const bid = bids[0]; - expect(bid.cpm).to.equal(1.50); - expect(bid.ad).to.equal(''); - expect(bid.meta.mediaType).to.equal('banner'); - expect(bid.width).to.equal(300); - expect(bid.height).to.equal(250); - expect(bid.creativeId).to.equal('189198063'); - expect(bid.netRevenue).to.equal(true); - expect(bid.currency).to.equal('USD'); - expect(bid.ttl).to.equal(300); - expect(bid.dealId).to.equal('dealId'); - }); - - it('no banner media response', function () { - const request = spec.buildRequests(DEFAULT_PARAMS_NEW_SIZES, { - gdprConsent: { - consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', - gdprApplies: true - }, - refererInfo: { - referer: 'https://domain.com', - numIframes: 0 - } - }); - - const bids = spec.interpretResponse(BID_RESPONSE_VIDEO, request); - const bid = bids[0]; - expect(bid.vastUrl).to.equal('https://vast.com/vast.xml'); - }); - - it('Verifies bidder_code', function () { - expect(spec.code).to.equal('inmar'); - }); - - it('Verifies bidder aliases', function () { - expect(spec.aliases).to.have.lengthOf(1); - expect(spec.aliases[0]).to.equal('inm'); - }); - - it('Verifies if bid request is valid', function () { - expect(spec.isBidRequestValid(DEFAULT_PARAMS_NEW_SIZES[0])).to.equal(true); - expect(spec.isBidRequestValid(DEFAULT_PARAMS_WO_OPTIONAL[0])).to.equal(true); - expect(spec.isBidRequestValid({})).to.equal(false); - expect(spec.isBidRequestValid({ - params: {} - })).to.equal(false); - expect(spec.isBidRequestValid({ - params: { - } - })).to.equal(false); - expect(spec.isBidRequestValid({ - params: { - partnerId: 12345 - } - })).to.equal(true); - }); - - it('Verifies user syncs image', function () { - var syncs = spec.getUserSyncs({ - iframeEnabled: false, - pixelEnabled: true - }, [BID_RESPONSE], { - consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', - referer: 'http://domain.com', - gdprApplies: true - }) - expect(syncs).to.have.lengthOf(1); - expect(syncs[0].type).to.equal('image'); - - syncs = spec.getUserSyncs({ - iframeEnabled: false, - pixelEnabled: true - }, [BID_RESPONSE], { - consentString: '', - referer: 'http://domain.com', - gdprApplies: true - }) - expect(syncs).to.have.lengthOf(1); - expect(syncs[0].type).to.equal('image'); - - syncs = spec.getUserSyncs({ - iframeEnabled: false, - pixelEnabled: true - }, [], { - consentString: null, - referer: 'http://domain.com', - gdprApplies: true - }) - expect(syncs).to.have.lengthOf(1); - expect(syncs[0].type).to.equal('image'); - }); -}); diff --git a/test/spec/modules/inskinBidAdapter_spec.js b/test/spec/modules/inskinBidAdapter_spec.js deleted file mode 100644 index 3d0ec82fca5..00000000000 --- a/test/spec/modules/inskinBidAdapter_spec.js +++ /dev/null @@ -1,386 +0,0 @@ -import { expect } from 'chai'; -import { spec } from 'modules/inskinBidAdapter.js'; -import { createBid } from 'src/bidfactory.js'; - -const ENDPOINT = 'https://mfad.inskinad.com/api/v2'; - -const REQUEST = { - 'bidderCode': 'inskin', - 'requestId': 'a4713c32-3762-4798-b342-4ab810ca770d', - 'bidderRequestId': '109f2a181342a9', - 'bidRequest': [{ - 'bidder': 'inskin', - 'params': { - 'networkId': 9874, - 'siteId': 730181, - 'publisherId': 123456 - }, - 'placementCode': 'div-gpt-ad-1487778092495-0', - 'sizes': [ - [728, 90], - [970, 90] - ], - 'bidId': '2b0f82502298c9', - 'bidderRequestId': '109f2a181342a9', - 'requestId': 'a4713c32-3762-4798-b342-4ab810ca770d' - }, - { - 'bidder': 'inskin', - 'params': { - 'networkId': 9874, - 'siteId': 730181 - }, - 'placementCode': 'div-gpt-ad-1487778092495-0', - 'sizes': [ - [728, 90], - [970, 90] - ], - 'bidId': '123', - 'bidderRequestId': '109f2a181342a9', - 'requestId': 'a4713c32-3762-4798-b342-4ab810ca770d' - }], - 'start': 1487883186070, - 'auctionStart': 1487883186069, - 'timeout': 3000 -}; - -const RESPONSE = { - 'headers': null, - 'body': { - 'user': { 'key': 'ue1-2d33e91b71e74929b4aeecc23f4376f1' }, - 'decisions': { - '2b0f82502298c9': { - 'adId': 2364764, - 'creativeId': 1950991, - 'flightId': 2788300, - 'campaignId': 542982, - 'clickUrl': 'https://mfad.inskinad.com/r', - 'impressionUrl': 'https://mfad.inskinad.com/i.gif', - 'contents': [{ - 'type': 'html', - 'body': '', - 'data': { - 'height': 90, - 'width': 728, - 'imageUrl': 'https://static.adzerk.net/Advertisers/b0ab77db8a7848c8b78931aed022a5ef.gif', - 'fileName': 'b0ab77db8a7848c8b78931aed022a5ef.gif' - }, - 'template': 'image' - }], - 'height': 90, - 'width': 728, - 'events': [], - 'pricing': {'price': 0.5, 'clearPrice': 0.5, 'revenue': 0.0005, 'rateType': 2, 'eCPM': 0.5} - }, - '123': { - 'adId': 2364764, - 'creativeId': 1950991, - 'flightId': 2788300, - 'campaignId': 542982, - 'clickUrl': 'https://mfad.inskinad.com/r', - 'impressionUrl': 'https://mfad.inskinad.com/i.gif', - 'contents': [{ - 'type': 'html', - 'body': '', - 'data': { - 'customData': { - 'pubCPM': 1 - }, - 'height': 90, - 'width': 728, - 'imageUrl': 'https://static.adzerk.net/Advertisers/b0ab77db8a7848c8b78931aed022a5ef.gif', - 'fileName': 'b0ab77db8a7848c8b78931aed022a5ef.gif' - }, - 'template': 'image' - }], - 'height': 90, - 'width': 728, - 'events': [], - 'pricing': {'price': 0.5, 'clearPrice': 0.5, 'revenue': 0.0005, 'rateType': 2, 'eCPM': 0.5} - } - } - } -}; - -const consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; -const bidderRequest = { - bidderCode: 'inskin', - gdprConsent: { - consentString: consentString, - gdprApplies: true - }, - refererInfo: { - referer: 'https://www.inskinmedia.com' - } -}; - -describe('InSkin BidAdapter', function () { - let bidRequests; - let adapter = spec; - - beforeEach(function () { - bidRequests = [ - { - bidder: 'inskin', - params: { - networkId: '9874', - siteId: 'xxxxx' - }, - placementCode: 'header-bid-tag-1', - sizes: [[300, 250], [300, 600]], - bidId: '23acc48ad47af5', - requestId: '0fb4905b-9456-4152-86be-c6f6d259ba99', - bidderRequestId: '1c56ad30b9b8ca8', - transactionId: '92489f71-1bf2-49a0-adf9-000cea934729' - } - ]; - }); - - describe('bid request validation', function () { - it('should accept valid bid requests', function () { - let bid = { - bidder: 'inskin', - params: { - networkId: '9874', - siteId: 'xxxxx' - } - }; - expect(spec.isBidRequestValid(bid)).to.equal(true); - }); - - it('should accept valid bid requests with extra fields', function () { - let bid = { - bidder: 'inskin', - params: { - networkId: '9874', - siteId: 'xxxxx', - zoneId: 'xxxxx' - } - }; - expect(spec.isBidRequestValid(bid)).to.equal(true); - }); - - it('should reject bid requests without siteId', function () { - let bid = { - bidder: 'inskin', - params: { - networkId: '9874' - } - }; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - - it('should reject bid requests without networkId', function () { - let bid = { - bidder: 'inskin', - params: { - siteId: '9874' - } - }; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - }); - - describe('buildRequests validation', function () { - it('creates request data', function () { - let request = spec.buildRequests(bidRequests, bidderRequest); - - expect(request).to.exist.and.to.be.a('object'); - }); - - it('request to inskin should contain a url', function () { - let request = spec.buildRequests(bidRequests, bidderRequest); - - expect(request.url).to.have.string('inskinad.com'); - }); - - it('requires valid bids to make request', function () { - let request = spec.buildRequests([], bidderRequest); - expect(request.bidRequest).to.be.empty; - }); - - it('sends bid request to ENDPOINT via POST', function () { - let request = spec.buildRequests(bidRequests, bidderRequest); - - expect(request.method).to.have.string('POST'); - }); - - it('should add gdpr consent information to the request', function () { - bidderRequest.bids = bidRequests; - - const request = spec.buildRequests(bidRequests, bidderRequest); - const payload = JSON.parse(request.data); - - expect(payload.consent.gdprConsentString).to.exist; - expect(payload.consent.gdprConsentRequired).to.exist; - expect(payload.consent.gdprConsentString).to.exist.and.to.equal(consentString); - expect(payload.consent.gdprConsentRequired).to.exist.and.to.be.true; - }); - - it('should not add keywords if TCF v2 purposes are granted', function () { - const bidderRequest2 = Object.assign({}, bidderRequest, { - gdprConsent: { - gdprApplies: true, - consentString: 'consentString', - vendorData: { - vendor: { - consents: { - 150: true - } - }, - purpose: { - consents: { - 1: true, - 2: true, - 3: true, - 4: true, - 5: true, - 6: true, - 7: true, - 8: true, - 9: true, - 10: true - } - } - }, - apiVersion: 2 - } - }); - - const request = spec.buildRequests(bidRequests, bidderRequest2); - const payload = JSON.parse(request.data); - - expect(payload.keywords).to.be.an('array').that.is.empty; - expect(payload.placements[0].properties.restrictions).to.be.undefined; - }); - - it('should add keywords if TCF v2 purposes are not granted', function () { - const bidderRequest2 = Object.assign({}, bidderRequest, { - gdprConsent: { - gdprApplies: true, - consentString: 'consentString', - vendorData: { - vendor: { - consents: { - 150: false - } - }, - purpose: { - consents: { - 1: true, - 2: true, - 3: true, - 4: true, - 5: true, - 6: true, - 7: true, - 8: true, - 9: true, - 10: true - } - } - }, - apiVersion: 2 - } - }); - - const request = spec.buildRequests(bidRequests, bidderRequest2); - const payload = JSON.parse(request.data); - - expect(payload.keywords).to.be.an('array').that.includes('cst-nocookies'); - expect(payload.keywords).to.be.an('array').that.includes('cst-nocontext'); - expect(payload.keywords).to.be.an('array').that.includes('cst-nodmp'); - expect(payload.keywords).to.be.an('array').that.includes('cst-nodata'); - expect(payload.keywords).to.be.an('array').that.includes('cst-noclicks'); - expect(payload.keywords).to.be.an('array').that.includes('cst-noresearch'); - - expect(payload.placements[0].properties.restrictions).to.be.an('array').that.includes('nocookies'); - expect(payload.placements[0].properties.restrictions).to.be.an('array').that.includes('nocontext'); - expect(payload.placements[0].properties.restrictions).to.be.an('array').that.includes('nodmp'); - expect(payload.placements[0].properties.restrictions).to.be.an('array').that.includes('nodata'); - expect(payload.placements[0].properties.restrictions).to.be.an('array').that.includes('noclicks'); - expect(payload.placements[0].properties.restrictions).to.be.an('array').that.includes('noresearch'); - }); - }); - describe('interpretResponse validation', function () { - it('response should have valid bidderCode', function () { - let bidRequest = spec.buildRequests(REQUEST.bidRequest, bidderRequest); - let bid = createBid(1, bidRequest.bidRequest[0]); - - expect(bid.bidderCode).to.equal('inskin'); - }); - - it('response should include objects for all bids', function () { - let bids = spec.interpretResponse(RESPONSE, REQUEST); - - expect(bids.length).to.equal(2); - }); - - it('registers bids', function () { - let bids = spec.interpretResponse(RESPONSE, REQUEST); - bids.forEach(b => { - expect(b).to.have.property('cpm'); - expect(b.cpm).to.be.above(0); - expect(b).to.have.property('requestId'); - expect(b).to.have.property('cpm'); - expect(b).to.have.property('width'); - expect(b).to.have.property('height'); - expect(b).to.have.property('ad'); - expect(b).to.have.property('currency', 'USD'); - expect(b).to.have.property('creativeId'); - expect(b).to.have.property('ttl', 360); - expect(b.meta).to.have.property('advertiserDomains'); - expect(b).to.have.property('netRevenue', true); - }); - }); - - it('cpm is correctly set', function () { - let bids = spec.interpretResponse(RESPONSE, REQUEST); - - expect(bids[0].cpm).to.equal(0.5); - expect(bids[1].cpm).to.equal(1); - }); - - it('handles nobid responses', function () { - let EMPTY_RESP = Object.assign({}, RESPONSE, {'body': {'decisions': null}}) - let bids = spec.interpretResponse(EMPTY_RESP, REQUEST); - - expect(bids).to.be.empty; - }); - - it('handles no server response', function () { - let bids = spec.interpretResponse(null, REQUEST); - - expect(bids).to.be.empty; - }); - }); - describe('getUserSyncs', function () { - it('handles empty sync options', function () { - let opts = spec.getUserSyncs({}); - - expect(opts).to.be.empty; - }); - - it('should return two sync urls if pixel syncs are enabled', function () { - let syncOptions = {'pixelEnabled': true}; - let opts = spec.getUserSyncs(syncOptions); - - expect(opts.length).to.equal(2); - }); - - it('should return three sync urls if pixel and iframe syncs are enabled', function () { - let syncOptions = {'iframeEnabled': true, 'pixelEnabled': true}; - let opts = spec.getUserSyncs(syncOptions); - - expect(opts.length).to.equal(3); - }); - }); - describe('supply chain id', function () { - it('should use publisherId as sid', function () { - const request = spec.buildRequests(REQUEST.bidRequest, bidderRequest); - const payload = JSON.parse(request.data); - - expect(payload.rtb.schain.ext.sid).to.equal('123456'); - }); - }); -}); diff --git a/test/spec/modules/insticatorBidAdapter_spec.js b/test/spec/modules/insticatorBidAdapter_spec.js index fc7ed1833ac..e24bcb3b455 100644 --- a/test/spec/modules/insticatorBidAdapter_spec.js +++ b/test/spec/modules/insticatorBidAdapter_spec.js @@ -72,7 +72,11 @@ describe('InsticatorBidAdapter', function () { let bidderRequest = { bidderRequestId, - auctionId: '74f78609-a92d-4cf1-869f-1b244bbfb5d2', + ortb2: { + source: { + tid: '74f78609-a92d-4cf1-869f-1b244bbfb5d2', + } + }, timeout: 300, gdprConsent: { consentString: 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==', @@ -181,6 +185,7 @@ describe('InsticatorBidAdapter', function () { let getDataFromLocalStorageStub, localStorageIsEnabledStub; let getCookieStub, cookiesAreEnabledStub; let sandbox; + let serverRequests, serverRequest; beforeEach(() => { $$PREBID_GLOBAL$$.bidderSettings = { @@ -206,12 +211,15 @@ describe('InsticatorBidAdapter', function () { $$PREBID_GLOBAL$$.bidderSettings = {}; }); - const serverRequests = spec.buildRequests([bidRequest], bidderRequest); + before(() => { + serverRequests = spec.buildRequests([bidRequest], bidderRequest); + serverRequest = serverRequests[0]; + }) + it('should create a request', function () { expect(serverRequests).to.have.length(1); }); - const serverRequest = serverRequests[0]; it('should create a request object with method, URL, options and data', function () { expect(serverRequest).to.exist; expect(serverRequest.method).to.exist; @@ -248,7 +256,7 @@ describe('InsticatorBidAdapter', function () { expect(data.tmax).to.equal(bidderRequest.timeout); expect(data.source).to.have.all.keys('fd', 'tid', 'ext'); expect(data.source.fd).to.equal(1); - expect(data.source.tid).to.equal(bidderRequest.auctionId); + expect(data.source.tid).to.equal(bidderRequest.ortb2.source.tid); expect(data.source.ext).to.have.property('schain').to.deep.equal({ ver: '1.0', complete: 1, diff --git a/test/spec/modules/intentIqIdSystem_spec.js b/test/spec/modules/intentIqIdSystem_spec.js index f9bfa577e3a..d5ffbf92d68 100644 --- a/test/spec/modules/intentIqIdSystem_spec.js +++ b/test/spec/modules/intentIqIdSystem_spec.js @@ -1,15 +1,17 @@ import { expect } from 'chai'; -import { intentIqIdSubmodule, readData, FIRST_PARTY_KEY } from 'modules/intentIqIdSystem.js'; +import { intentIqIdSubmodule, storage } from 'modules/intentIqIdSystem.js'; import * as utils from 'src/utils.js'; import { server } from 'test/mocks/xhr.js'; const partner = 10; const pai = '11'; const pcid = '12'; +const enableCookieStorage = true; const defaultConfigParams = { params: { partner: partner } }; const paiConfigParams = { params: { partner: partner, pai: pai } }; const pcidConfigParams = { params: { partner: partner, pcid: pcid } }; -const allConfigParams = { params: { partner: partner, pai: pai, pcid: pcid } }; +const enableCookieConfigParams = { params: { partner: partner, enableCookieStorage: enableCookieStorage } }; +const allConfigParams = { params: { partner: partner, pai: pai, pcid: pcid, enableCookieStorage: enableCookieStorage } }; const responseHeader = { 'Content-Type': 'application/json' } describe('IntentIQ tests', function () { @@ -62,6 +64,38 @@ describe('IntentIQ tests', function () { expect(submodule).to.be.undefined; }); + it('should not save data in cookie if enableCookieStorage configParam not set', function () { + let callBackSpy = sinon.spy(); + let submoduleCallback = intentIqIdSubmodule.getId(defaultConfigParams).callback; + submoduleCallback(callBackSpy); + let request = server.requests[0]; + expect(request.url).to.contain('https://api.intentiq.com/profiles_engine/ProfilesEngineServlet?at=39&mi=10&dpi=10&pt=17&dpn=1&iiqidtype=2&iiqpcid='); + request.respond( + 200, + responseHeader, + JSON.stringify({ pid: 'test_pid', data: 'test_personid', ls: true }) + ); + expect(callBackSpy.calledOnce).to.be.true; + expect(storage.getCookie('_iiq_fdata_' + partner)).to.equal(null); + }); + + it('should save data in cookie if enableCookieStorage configParam set to true', function () { + let callBackSpy = sinon.spy(); + let submoduleCallback = intentIqIdSubmodule.getId(allConfigParams).callback; + submoduleCallback(callBackSpy); + let request = server.requests[0]; + expect(request.url).to.contain('https://api.intentiq.com/profiles_engine/ProfilesEngineServlet?at=39&mi=10&dpi=10&pt=17&dpn=1&pcid=12&pai=11&iiqidtype=2&iiqpcid='); + request.respond( + 200, + responseHeader, + JSON.stringify({ pid: 'test_pid', data: 'test_personid', ls: true }) + ); + expect(callBackSpy.calledOnce).to.be.true; + const cookieValue = storage.getCookie('_iiq_fdata_' + partner) + expect(cookieValue).to.not.equal(null) + expect(JSON.parse(cookieValue).data).to.be.equal('test_personid'); + }); + it('should call the IntentIQ endpoint with only partner', function () { let callBackSpy = sinon.spy(); let submoduleCallback = intentIqIdSubmodule.getId(defaultConfigParams).callback; diff --git a/test/spec/modules/invisiblyAnalyticsAdapter_spec.js b/test/spec/modules/invisiblyAnalyticsAdapter_spec.js index 3b3542d43b0..2c460156318 100644 --- a/test/spec/modules/invisiblyAnalyticsAdapter_spec.js +++ b/test/spec/modules/invisiblyAnalyticsAdapter_spec.js @@ -53,7 +53,7 @@ describe('Invisibly Analytics Adapter test suite', function () { hb_source: 'server', }, getStatusCode() { - return CONSTANTS.STATUS.NO_BID; + return CONSTANTS.STATUS.GOOD; }, }; diff --git a/test/spec/modules/iqzoneBidAdapter_spec.js b/test/spec/modules/iqzoneBidAdapter_spec.js index de0459f4714..2e920d3b769 100644 --- a/test/spec/modules/iqzoneBidAdapter_spec.js +++ b/test/spec/modules/iqzoneBidAdapter_spec.js @@ -76,7 +76,8 @@ describe('IQZoneBidAdapter', function () { gdprConsent: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', refererInfo: { referer: 'https://test.com' - } + }, + timeout: 500 }; describe('isBidRequestValid', function () { diff --git a/test/spec/modules/ivsBidAdapter_spec.js b/test/spec/modules/ivsBidAdapter_spec.js new file mode 100644 index 00000000000..819c7480595 --- /dev/null +++ b/test/spec/modules/ivsBidAdapter_spec.js @@ -0,0 +1,195 @@ +import { spec, converter } from 'modules/ivsBidAdapter.js'; +import { assert } from 'chai'; +import { deepClone } from '../../../src/utils'; + +describe('ivsBidAdapter', function () { + describe('isBidRequestValid()', function () { + let validBid = { + bidder: 'ivs', + mediaTypes: { + video: { + context: 'instream', + playerSize: [640, 480], + mimes: ['video/mp4'] + } + }, + params: { + bidderDomain: 'https://www.example.com', + publisherId: '3001234' + } + }; + + it('should return true for a valid bid', function () { + assert.isTrue(spec.isBidRequestValid(validBid)); + }); + + it('should return false if publisherId info is missing', function () { + let bid = deepClone(validBid); + delete bid.params.publisherId; + assert.isFalse(spec.isBidRequestValid(bid)); + }); + + it('should return false for empty video parameters', function () { + let bid = deepClone(validBid); + delete bid.mediaTypes.video; + assert.isFalse(spec.isBidRequestValid(bid)); + }); + + it('should return false for non instream context', function () { + let bid = deepClone(validBid); + bid.mediaTypes.video.context = 'outstream'; + assert.isFalse(spec.isBidRequestValid(bid)); + }); + }); + + describe('buildRequests()', function () { + let validBidRequests, validBidderRequest; + + beforeEach(function () { + validBidRequests = [{ + bidder: 'ivs', + mediaTypes: { + video: { + context: 'instream', + playerSize: [640, 480], + mimes: ['video/mp4'] + }, + adUnitCode: 'video1', + transactionId: '1f420478-a3cd-452d-8e33-ac851e7bfba6', + bidId: '2d986cea00fd01', + bidderRequestId: '1022d594d79bf5', + auctionId: '835eacc9-cfe7-4fa2-8738-ab4b5c4f26d2' + }, + params: { + bidderDomain: 'https://www.example.com', + publisherId: '3001234' + } + }]; + + validBidderRequest = { + bidderCode: 'ivs', + auctionId: '409bd13d-d0be-43c4-9c4f-e6f81ecff475', + bidderRequestId: '17bfe74bd98e68', + refererInfo: { + domain: 'example.com', + page: 'https://www.example.com/test.html', + }, + bids: [{ + bidder: 'ivs', + params: { + publisherId: '3001234' + }, + mediaTypes: { + video: { + context: 'instream', + playerSize: [[640, 480]], + mimes: ['video/mp4'] + } + }, + adUnitCode: 'video1', + transactionId: '91b1977f-d05c-45c3-af1f-69b4e7d11e86', + sizes: [ + [640, 480] + ], + }], + ortb2: { + site: { + publisher: { + domain: 'example.com', + } + } + } + }; + }); + + it('should return a validate bid request', function () { + const bidRequest = spec.buildRequests(validBidRequests, validBidderRequest); + assert.equal(bidRequest.method, 'POST'); + assert.deepEqual(bidRequest.options, { contentType: 'application/json' }); + assert.ok(bidRequest.data); + }); + + it('should contain the required parameters', function () { + const bidRequest = spec.buildRequests(validBidRequests, validBidderRequest); + const bidderRequest = bidRequest.data; + assert.ok(bidderRequest.site); + assert.lengthOf(bidderRequest.imp, 1); + }); + }); + + describe('interpretResponse()', function () { + let serverResponse, bidderRequest, request; + + beforeEach(function () { + serverResponse = { + body: { + id: '635ba1ed-68ba-47b4-bcec-4a86565f25f9', + seatbid: [{ + bid: [{ + crid: 3715, + id: 'bca9823d-ca7a-4dac-b292-0e1fae5948f8', + impid: '200d1ca23b15a6', + price: 1.5, + nurl: 'https://a.ivstracker.net/dev/getvastxml?ad_creativeid=3715&domain=localhost%3A9999&ip=136.158.51.114&pageurl=http%3A%2F%2Flocalhost%3A9999%2Ftest%2Fpages%2Fivs.html&spid=3001234&adplacement=&brand=unknown&device=desktop&adsclientid=A45-fd46289e-dc60-4be2-a637-4bc8eb953ddf&clientid=3b5e435f-0351-4ba0-bd2d-8d6f3454c5ed&uastring=Mozilla%2F5.0%20(Macintosh%3B%20Intel%20Mac%20OS%20X%2010_15_7)%20AppleWebKit%2F537.36%20(KHTML%2C%20like%20Gecko)%20Chrome%2F109.0.0.0%20Safari%2F537.36' + }] + }], + cur: 'USD' + }, + headers: {} + }; + + bidderRequest = { + bidderCode: 'ivs', + auctionId: '635ba1ed-68ba-47b4-bcec-4a86565f25f9', + bidderRequestId: '1def3e1d03f5a', + bids: [{ + bidder: 'ivs', + params: { + publisherId: '3001234' + }, + mediaTypes: { + video: { + context: 'instream', + playerSize: [ + [640, 480] + ], + mimes: ['video/mp4'] + } + }, + adUnitCode: 'video1', + transactionId: '89e5a3e7-df30-4ed6-a130-edfa91941e67', + bidId: '200d1ca23b15a6', + bidderRequestId: '1def3e1d03f5a', + auctionId: '635ba1ed-68ba-47b4-bcec-4a86565f25f9' + }], + }; + + request = { data: converter.toORTB({ bidderRequest }) }; + }); + + if (FEATURES.VIDEO) { + it('should match parsed server response', function () { + const results = spec.interpretResponse(serverResponse, request); + const expected = { + mediaType: 'video', + playerWidth: 640, + playerHeight: 480, + vastUrl: 'https://a.ivstracker.net/dev/getvastxml?ad_creativeid=3715&domain=localhost%3A9999&ip=136.158.51.114&pageurl=http%3A%2F%2Flocalhost%3A9999%2Ftest%2Fpages%2Fivs.html&spid=3001234&adplacement=&brand=unknown&device=desktop&adsclientid=A45-fd46289e-dc60-4be2-a637-4bc8eb953ddf&clientid=3b5e435f-0351-4ba0-bd2d-8d6f3454c5ed&uastring=Mozilla%2F5.0%20(Macintosh%3B%20Intel%20Mac%20OS%20X%2010_15_7)%20AppleWebKit%2F537.36%20(KHTML%2C%20like%20Gecko)%20Chrome%2F109.0.0.0%20Safari%2F537.36', + requestId: '200d1ca23b15a6', + seatBidId: 'bca9823d-ca7a-4dac-b292-0e1fae5948f8', + cpm: 1.5, + currency: 'USD', + creativeId: 3715, + ttl: 360, + }; + + expect(results.length).to.equal(1); + sinon.assert.match(results[0], expected); + }); + } + + it('should return empty when no response', function () { + assert.ok(!spec.interpretResponse({}, request)); + }); + }); +}); diff --git a/test/spec/modules/ixBidAdapter_spec.js b/test/spec/modules/ixBidAdapter_spec.js index 5f104dce13f..36610db6aa6 100644 --- a/test/spec/modules/ixBidAdapter_spec.js +++ b/test/spec/modules/ixBidAdapter_spec.js @@ -2,14 +2,14 @@ import * as utils from 'src/utils.js'; import { config } from 'src/config.js'; import { expect } from 'chai'; import { newBidder } from 'src/adapters/bidderFactory.js'; -import { spec, storage, ERROR_CODES, FEATURE_TOGGLES, LOCAL_STORAGE_FEATURE_TOGGLES_KEY } from '../../../modules/ixBidAdapter.js'; +import { spec, storage, ERROR_CODES, FEATURE_TOGGLES, LOCAL_STORAGE_FEATURE_TOGGLES_KEY, REQUESTED_FEATURE_TOGGLES, combineImps, bidToVideoImp, bidToNativeImp, deduplicateImpExtFields, removeSiteIDs } from '../../../modules/ixBidAdapter.js'; import { createEidsArray } from 'modules/userId/eids.js'; import { deepAccess, deepClone } from '../../../src/utils.js'; describe('IndexexchangeAdapter', function () { const IX_SECURE_ENDPOINT = 'https://htlb.casalemedia.com/openrtb/pbjs'; - const VIDEO_ENDPOINT_VERSION = 8.1; - const BANNER_ENDPOINT_VERSION = 7.2; + + FEATURE_TOGGLES.REQUESTED_FEATURE_TOGGLES = ['test1', 'test2']; const SAMPLE_SCHAIN = { 'ver': '1.0', @@ -387,13 +387,13 @@ describe('IndexexchangeAdapter', function () { }, ortb2Imp: { ext: { - tid: '173f49a8-7549-4218-a23c-e7ba59b47230', + tid: '273f49a8-7549-4218-a23c-e7ba59b47230', data: { - pbadslot: 'div-gpt-ad-1460505748562-0' + pbadslot: 'div-gpt-ad-1460505748562-1' } } }, - adUnitCode: 'div-gpt-ad-1460505748562-0', + adUnitCode: 'div-gpt-ad-1460505748562-1', transactionId: '273f49a8-7549-4218-a23c-e7ba59b47230', bidId: '1a2b3c4e', bidderRequestId: '11a22b33c44e', @@ -456,7 +456,7 @@ describe('IndexexchangeAdapter', function () { }, ortb2Imp: { ext: { - tid: '173f49a8-7549-4218-a23c-e7ba59b47230', + tid: '273f49a8-7549-4218-a23c-e7ba59b47230', data: { pbadslot: 'div-gpt-ad-1460505748562-0' } @@ -725,6 +725,14 @@ describe('IndexexchangeAdapter', function () { refererInfo: { page: 'https://www.prebid.org', canonicalUrl: 'https://www.prebid.org/the/link/to/the/page' + }, + ortb2: { + site: { + page: 'https://www.prebid.org' + }, + source: { + tid: 'mock-tid' + } } }; @@ -751,7 +759,8 @@ describe('IndexexchangeAdapter', function () { uid2: { id: 'testuid2' }, // UID 2.0 // similar to uid2, but id5's getValue takes .uid id5id: { uid: 'testid5id' }, // ID5 - imuid: 'testimuid' + imuid: 'testimuid', + '33acrossId': { envelope: 'v1.5fs.1000.fjdiosmclds' } }; const DEFAULT_USERIDASEIDS_DATA = createEidsArray(DEFAULT_USERID_DATA); @@ -805,7 +814,12 @@ describe('IndexexchangeAdapter', function () { }, { source: 'intimatemerger.com', uids: [{ - id: DEFAULT_USERID_DATA.imuid, + id: DEFAULT_USERID_DATA.imuid + }] + }, { + source: '33across.com', + uids: [{ + id: DEFAULT_USERID_DATA['33acrossId'].envelope }] } ]; @@ -824,6 +838,18 @@ describe('IndexexchangeAdapter', function () { }); describe('getUserSync tests', function () { + before(() => { + // TODO (dgirardi): the assertions in this block rely on + // global state (consent and siteId) set up in the SOT + // this is an outsider's attempt to recreate that state more explicitly after shuffling around setup code + // in this test suite + + // please make your preconditions explicit; + // also, please, avoid global state if possible. + + spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION); + }) + it('UserSync test : check type = iframe, check usermatch URL', function () { const syncOptions = { 'iframeEnabled': true @@ -1199,7 +1225,7 @@ describe('IndexexchangeAdapter', function () { const payload = extractPayload(request[0]); expect(request).to.be.an('array'); expect(request).to.have.lengthOf.above(0); // should be 1 or more - expect(payload.user.eids).to.have.lengthOf(7); + expect(payload.user.eids).to.have.lengthOf(8); expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[0]); }); }); @@ -1387,7 +1413,7 @@ describe('IndexexchangeAdapter', function () { cloneValidBid[0].userIdAsEids = utils.deepClone(DEFAULT_USERIDASEIDS_DATA); const request = spec.buildRequests(cloneValidBid, DEFAULT_OPTION)[0]; const payload = extractPayload(request); - expect(payload.user.eids).to.have.lengthOf(7); + expect(payload.user.eids).to.have.lengthOf(8); expect(payload.user.eids).to.have.deep.members(DEFAULT_USERID_PAYLOAD); }); @@ -1520,7 +1546,7 @@ describe('IndexexchangeAdapter', function () { }) expect(payload.user).to.exist; - expect(payload.user.eids).to.have.lengthOf(9); + expect(payload.user.eids).to.have.lengthOf(10); expect(payload.user.eids).to.have.deep.members(validUserIdPayload); }); @@ -1562,7 +1588,7 @@ describe('IndexexchangeAdapter', function () { }); const payload = extractPayload(request); - expect(payload.user.eids).to.have.lengthOf(8); + expect(payload.user.eids).to.have.lengthOf(9); expect(payload.user.eids).to.have.deep.members(validUserIdPayload); }); }); @@ -1669,27 +1695,6 @@ describe('IndexexchangeAdapter', function () { expect(r.user.testProperty).to.be.undefined; }); - it('should not add fpd data to r object if it exceeds maximum request', function () { - const ortb2 = { - site: { - keywords: 'power tools, drills', - search: 'drill', - }, - user: { - keywords: Array(1200).join('#'), - } - }; - - const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); - bid.mediaTypes.banner.sizes = LARGE_SET_OF_SIZES; - - const request = spec.buildRequests([bid], { ortb2 })[0]; - const r = extractPayload(request); - - expect(r.site.ref).to.exist; - expect(r.site.keywords).to.be.undefined; - expect(r.user).to.be.undefined; - }); it('should set gpp and gpp_sid field when defined', function () { const request = spec.buildRequests(DEFAULT_BANNER_VALID_BID, { ortb2: {regs: {gpp: 'gpp', gpp_sid: [1]}} })[0]; const r = extractPayload(request); @@ -1724,19 +1729,79 @@ describe('IndexexchangeAdapter', function () { expect(r.regs.gpp_sid).to.be.an('array'); expect(r.regs.gpp_sid).to.include(1); }); + + it('should add adunit specific data to imp ext for banner', function () { + const AD_UNIT_CODE = '/19968336/some-adunit-path'; + const validBids = utils.deepClone(DEFAULT_BANNER_VALID_BID); + validBids[0].ortb2Imp = { + ext: { + data: { + adserver: { + name: 'gam banner', + adslot: AD_UNIT_CODE + } + } + } + }; + const requests = spec.buildRequests(validBids, DEFAULT_OPTION); + const imp = extractPayload(requests[0]).imp[0]; + expect(deepAccess(imp, 'ext.data.adserver.name')).to.equal('gam banner'); + expect(deepAccess(imp, 'ext.data.adserver.adslot')).to.equal(AD_UNIT_CODE); + }); + + it('should add adunit specific data to imp ext for native', function () { + const AD_UNIT_CODE = '/19968336/some-adunit-path'; + const validBids = utils.deepClone(DEFAULT_NATIVE_VALID_BID); + validBids[0].ortb2Imp = { + ext: { + data: { + adserver: { + name: 'gam native', + adslot: AD_UNIT_CODE + } + } + } + }; + const requests = spec.buildRequests(validBids, DEFAULT_OPTION); + const imp = extractPayload(requests[0]).imp[0]; + expect(deepAccess(imp, 'ext.data.adserver.name')).to.equal('gam native'); + expect(deepAccess(imp, 'ext.data.adserver.adslot')).to.equal(AD_UNIT_CODE); + }); + + it('should add adunit specific data to imp ext for video', function () { + const AD_UNIT_CODE = '/19968336/some-adunit-path'; + const validBids = utils.deepClone(DEFAULT_VIDEO_VALID_BID); + validBids[0].ortb2Imp = { + ext: { + data: { + adserver: { + name: 'gam video', + adslot: AD_UNIT_CODE + } + } + } + }; + const requests = spec.buildRequests(validBids, DEFAULT_OPTION); + const imp = extractPayload(requests[0]).imp[0]; + expect(deepAccess(imp, 'ext.data.adserver.name')).to.equal('gam video'); + expect(deepAccess(imp, 'ext.data.adserver.adslot')).to.equal(AD_UNIT_CODE); + }); }); describe('buildRequests', function () { - let request = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION)[0]; - const requestUrl = request.url; - const requestMethod = request.method; - const payloadData = request.data; - const bidWithoutSchain = utils.deepClone(DEFAULT_BANNER_VALID_BID); delete bidWithoutSchain[0].schain; - const requestWithoutSchain = spec.buildRequests(bidWithoutSchain, DEFAULT_OPTION)[0]; - const payloadWithoutSchain = extractPayload(requestWithoutSchain); const GPID = '/19968336/some-adunit-path'; + let request, requestUrl, requestMethod, payloadData, requestWithoutSchain, payloadWithoutSchain; + + before(() => { + request = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION)[0]; + requestUrl = request.url; + requestMethod = request.method; + payloadData = request.data; + requestWithoutSchain = spec.buildRequests(bidWithoutSchain, DEFAULT_OPTION)[0]; + payloadWithoutSchain = extractPayload(requestWithoutSchain); + }); it('request should be made to IX endpoint with POST method and siteId in query param', function () { expect(requestMethod).to.equal('POST'); @@ -1853,7 +1918,7 @@ describe('IndexexchangeAdapter', function () { expect(payload.source.ext.schain).to.deep.equal(SAMPLE_SCHAIN); expect(payload.imp).to.be.an('array'); expect(payload.imp).to.have.lengthOf(1); - expect(payload.source.tid).to.equal(DEFAULT_BANNER_VALID_BID[0].auctionId); + expect(payload.source.tid).to.equal(DEFAULT_OPTION.ortb2.source.tid); }); it('payload should have correct format and value for r.id when bidderRequestId is a number ', function () { @@ -2099,7 +2164,7 @@ describe('IndexexchangeAdapter', function () { const requestWithFirstPartyData = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION)[0]; const pageUrl = extractPayload(requestWithFirstPartyData).site.page; - const expectedPageUrl = DEFAULT_OPTION.refererInfo.page + '?ab=123&cd=123%23ab&e%2Ff=456&h%3Fg=456%23cd'; + const expectedPageUrl = DEFAULT_OPTION.ortb2.site.page + '/?ab=123&cd=123%23ab&e%2Ff=456&h%3Fg=456%23cd'; expect(pageUrl).to.equal(expectedPageUrl); }); @@ -2197,8 +2262,72 @@ describe('IndexexchangeAdapter', function () { }); }); + describe('getIxFirstPartyDataPageUrl', () => { + beforeEach(() => { + config.setConfig({ ix: { firstPartyData: { key1: 'value1', key2: 'value2' } } }); + }); + + afterEach(() => { + config.resetConfig(); + }); + + it('should return the modified URL with first party data query parameters appended', () => { + const requestWithIXFirstPartyData = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION)[0]; + const pageUrl = extractPayload(requestWithIXFirstPartyData).site.page; + expect(pageUrl).to.equal('https://www.prebid.org/?key1=value1&key2=value2'); + }); + + it('should return the modified URL with first party data query parameters appended but not duplicated', () => { + const bidderRequest = deepClone(DEFAULT_OPTION); + bidderRequest.ortb2.site.page = 'https://www.prebid.org/?key1=value1' + const requestWithIXFirstPartyData = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION)[0]; + const pageUrl = extractPayload(requestWithIXFirstPartyData).site.page; + expect(pageUrl).to.equal('https://www.prebid.org/?key1=value1&key2=value2'); + }); + + it('should not overwrite existing query parameters with first party data', () => { + config.setConfig({ ix: { firstPartyData: { key1: 'value1', key2: 'value2' } } }); + const bidderRequest = deepClone(DEFAULT_OPTION); + bidderRequest.ortb2.site.page = 'https://www.prebid.org/?key1=existingValue1' + const requestWithIXFirstPartyData = spec.buildRequests(DEFAULT_BANNER_VALID_BID, bidderRequest)[0]; + const pageUrl = extractPayload(requestWithIXFirstPartyData).site.page; + expect(pageUrl).to.equal('https://www.prebid.org/?key1=existingValue1&key2=value2'); + }); + + it('should return the original URL if no first party data is available', () => { + config.setConfig({ ix: {} }); + const requestWithIXFirstPartyData = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION)[0]; + const pageUrl = extractPayload(requestWithIXFirstPartyData).site.page; + expect(pageUrl).to.equal('https://www.prebid.org'); + }); + + it('should return the original URL referer page url if ortb2 does not exist', () => { + config.setConfig({ ix: {} }); + const bidderRequest = deepClone(DEFAULT_OPTION); + delete bidderRequest.ortb2; + bidderRequest.refererInfo.page = 'https://example.com'; + const requestWithIXFirstPartyData = spec.buildRequests(DEFAULT_BANNER_VALID_BID, bidderRequest)[0]; + const pageUrl = extractPayload(requestWithIXFirstPartyData).site.page; + expect(pageUrl).to.equal('https://example.com'); + }); + + it('should use referer URL if the provided ortb2.site.page URL is not valid', () => { + config.setConfig({ ix: { firstPartyData: { key1: 'value1', key2: 'value2' } } }); + const bidderRequest = deepClone(DEFAULT_OPTION); + bidderRequest.ortb2.site.page = 'www.invalid-url*&?.com'; + bidderRequest.refererInfo.page = 'https://www.prebid.org'; + const requestWithIXFirstPartyData = spec.buildRequests(DEFAULT_BANNER_VALID_BID, bidderRequest)[0]; + const pageUrl = extractPayload(requestWithIXFirstPartyData).site.page; + expect(pageUrl).to.equal('https://www.prebid.org/?key1=value1&key2=value2'); + }); + }); + describe('request should contain both banner and video requests', function () { - const request = spec.buildRequests([DEFAULT_BANNER_VALID_BID[0], DEFAULT_VIDEO_VALID_BID[0]], {}); + let request; + before(() => { + request = spec.buildRequests([DEFAULT_BANNER_VALID_BID[0], DEFAULT_VIDEO_VALID_BID[0]], {}); + }); + it('should have banner request', () => { const bannerImpression = extractPayload(request[0]).imp[0]; const sidValue = DEFAULT_BANNER_VALID_BID[0].params.id; @@ -2228,8 +2357,83 @@ describe('IndexexchangeAdapter', function () { }); }); + describe('video request should set displaymanager', () => { + it('ix renderer preferrered', () => { + const bid = utils.deepClone(DEFAULT_VIDEO_VALID_BID); + bid[0].mediaTypes.video.context = 'outstream'; + bid[0].mediaTypes.video.w = [[300, 143]]; + bid[0].schain = undefined; + const request = spec.buildRequests(bid); + const videoImpression = extractPayload(request[0]).imp[0]; + expect(videoImpression.displaymanager).to.equal('ix'); + }); + it('ix renderer not preferrered', () => { + const bid = utils.deepClone(DEFAULT_VIDEO_VALID_BID); + bid[0].mediaTypes.video.context = 'outstream'; + bid[0].mediaTypes.video.w = [[300, 143]]; + bid[0].mediaTypes.video.renderer = { + url: 'http://publisherplayer.js', + render: () => { } + }; + bid[0].schain = undefined; + const request = spec.buildRequests(bid); + const videoImpression = extractPayload(request[0]).imp[0]; + expect(videoImpression.displaymanager).to.equal('http://publisherplayer.js'); + }); + it('ix renderer not preferrered - bad url', () => { + const bid = utils.deepClone(DEFAULT_VIDEO_VALID_BID); + bid[0].mediaTypes.video.context = 'outstream'; + bid[0].mediaTypes.video.w = [[300, 143]]; + bid[0].mediaTypes.video.renderer = { + url: 'publisherplayer.js', + render: () => { } + }; + bid[0].schain = undefined; + const request = spec.buildRequests(bid); + const videoImpression = extractPayload(request[0]).imp[0]; + expect(videoImpression.displaymanager).to.be.undefined; + }); + it('renderer url provided and is ix renderer', () => { + const bid = utils.deepClone(DEFAULT_VIDEO_VALID_BID); + bid[0].mediaTypes.video.context = 'outstream'; + bid[0].mediaTypes.video.w = [[300, 143]]; + bid[0].mediaTypes.video.renderer = { + url: 'http://js-sec.indexww.rendererplayer.com', + render: () => { } + }; + bid[0].schain = undefined; + const request = spec.buildRequests(bid); + const videoImpression = extractPayload(request[0]).imp[0]; + expect(videoImpression.displaymanager).to.equal('ix'); + }); + it('renderer url undefined and is ix renderer', () => { + const bid = utils.deepClone(DEFAULT_VIDEO_VALID_BID); + bid[0].mediaTypes.video.context = 'outstream'; + bid[0].mediaTypes.video.w = [[300, 143]]; + bid[0].mediaTypes.video.renderer = { + render: () => { } + }; + bid[0].schain = undefined; + const request = spec.buildRequests(bid); + const videoImpression = extractPayload(request[0]).imp[0]; + expect(videoImpression.displaymanager).to.be.undefined; + }); + it('schain', () => { + const bid = utils.deepClone(DEFAULT_VIDEO_VALID_BID); + bid[0].mediaTypes.video.context = 'outstream'; + bid[0].mediaTypes.video.w = [[300, 143]]; + bid[0].schain = SAMPLE_SCHAIN; + const request = spec.buildRequests(bid); + const videoImpression = extractPayload(request[0]).imp[0]; + expect(videoImpression.displaymanager).to.equal('pbjs_wrapper'); + }); + }); + describe('request should contain both banner and native requests', function () { - const request = spec.buildRequests([DEFAULT_BANNER_VALID_BID[0], DEFAULT_NATIVE_VALID_BID[0]]); + let request; + before(() => { + request = spec.buildRequests([DEFAULT_BANNER_VALID_BID[0], DEFAULT_NATIVE_VALID_BID[0]]); + }) it('should have banner request', () => { const bannerImpression = extractPayload(request[0]).imp[0]; @@ -2273,7 +2477,7 @@ describe('IndexexchangeAdapter', function () { expect(requests[0].data.sn).to.be.undefined; }); - it('2 requests due to 2 ad units, one larger than url size', function () { + it('1 request, one larger than url size, no splitting', function () { const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); bid.mediaTypes.banner.sizes = LARGE_SET_OF_SIZES; bid.params.siteId = '124'; @@ -2283,10 +2487,10 @@ describe('IndexexchangeAdapter', function () { const requests = spec.buildRequests([bid, DEFAULT_BANNER_VALID_BID[0]], DEFAULT_OPTION); expect(requests).to.be.an('array'); - expect(requests).to.have.lengthOf(2); + expect(requests).to.have.lengthOf(1); }); - it('6 ad units should generate only 4 requests', function () { + it('6 ad units should generate only 1 requests', function () { const bid1 = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); bid1.mediaTypes.banner.sizes = LARGE_SET_OF_SIZES; bid1.params.siteId = '121'; @@ -2312,7 +2516,7 @@ describe('IndexexchangeAdapter', function () { const requests = spec.buildRequests([bid1, bid2, bid3, bid4, bid5, bid6], DEFAULT_OPTION); expect(requests).to.be.an('array'); - expect(requests).to.have.lengthOf(4); + expect(requests).to.have.lengthOf(1); for (var i = 0; i < requests.length; i++) { const reqSize = `${requests[i].url}?${utils.parseQueryStringParameters(requests[i].data)}`.length; @@ -2331,9 +2535,11 @@ describe('IndexexchangeAdapter', function () { bid1.bidId = '2f6g5s5e'; const bid2 = utils.deepClone(bid1); + bid2.adUnitCode = 'div-gpt-2' bid2.transactionId = 'tr2'; bid2.mediaTypes.banner.sizes = [[220, 221], [222, 223], [300, 250]]; const bid3 = utils.deepClone(bid1); + bid3.adUnitCode = 'div-gpt-3' bid3.transactionId = 'tr3'; bid3.mediaTypes.banner.sizes = [[330, 331], [332, 333], [300, 250]]; @@ -2435,8 +2641,11 @@ describe('IndexexchangeAdapter', function () { }); describe('buildRequestVideo', function () { - const request = spec.buildRequests(DEFAULT_VIDEO_VALID_BID, DEFAULT_OPTION); - const payloadData = request[0].data; + let request, payloadData; + before(() => { + request = spec.buildRequests(DEFAULT_VIDEO_VALID_BID, DEFAULT_OPTION); + payloadData = request[0].data; + }) it('auction type should be set correctly', function () { const at = payloadData.at; @@ -2469,6 +2678,36 @@ describe('IndexexchangeAdapter', function () { expect(impression.video.placement).to.equal(2); }); + it('should use plcmt value when set in video.params', function () { + const bid = utils.deepClone(DEFAULT_VIDEO_VALID_BID[0]); + bid.params.video.plcmt = 2; + const request = spec.buildRequests([bid], {})[0]; + const impression = extractPayload(request).imp[0]; + + expect(impression.id).to.equal(DEFAULT_VIDEO_VALID_BID[0].bidId); + expect(impression.video.plcmt).to.equal(2); + }); + + it('invalid plcmt value when set in video.params', function () { + const bid = utils.deepClone(DEFAULT_VIDEO_VALID_BID[0]); + bid.params.video.plcmt = 5; + const request = spec.buildRequests([bid], {})[0]; + const impression = extractPayload(request).imp[0]; + + expect(impression.id).to.equal(DEFAULT_VIDEO_VALID_BID[0].bidId); + expect(impression.video.plcmt).to.be.undefined; + }); + + it('invalid plcmt value string when set in video.params', function () { + const bid = utils.deepClone(DEFAULT_VIDEO_VALID_BID[0]); + bid.params.video.plcmt = '4'; + const request = spec.buildRequests([bid], {})[0]; + const impression = extractPayload(request).imp[0]; + + expect(impression.id).to.equal(DEFAULT_VIDEO_VALID_BID[0].bidId); + expect(impression.video.plcmt).to.be.undefined; + }); + it('should set imp.ext.sid for video imps if params.id exists', function () { const bid = utils.deepClone(DEFAULT_VIDEO_VALID_BID[0]); bid.params.id = 50; @@ -2495,7 +2734,8 @@ describe('IndexexchangeAdapter', function () { const impression = extractPayload(request).imp[0]; expect(impression.id).to.equal(DEFAULT_VIDEO_VALID_BID[0].bidId); - expect(impression.video.placement).to.equal(4); + expect(impression.video.placement).to.equal(3); + expect(extractPayload(request).ext.ixdiag.vpd).to.equal(true); }); it('should handle unexpected context', function () { @@ -2785,7 +3025,10 @@ describe('IndexexchangeAdapter', function () { describe('both banner and video bidder params set', function () { const bids = [DEFAULT_MULTIFORMAT_BANNER_VALID_BID[0], DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]]; - const request = spec.buildRequests(bids, {}); + let request; + before(() => { + request = spec.buildRequests(bids, {}); + }) it('should return valid banner requests', function () { const impressions = extractPayload(request[0]).imp; @@ -3469,7 +3712,7 @@ describe('IndexexchangeAdapter', function () { expect(requestWithConsent.regs.ext.us_privacy).to.equal('1YYN'); }); - it('should contain `consented_providers_settings.consented_providers` & consent on user.ext when both are provided', function () { + it('should contain `consented_providers_settings.addtl_consent` & consent on user.ext when both are provided', function () { const options = { gdprConsent: { consentString: '3huaa11=qu3198ae', @@ -3479,11 +3722,11 @@ describe('IndexexchangeAdapter', function () { const validBidWithConsent = spec.buildRequests(DEFAULT_BANNER_VALID_BID, options); const requestWithConsent = extractPayload(validBidWithConsent[0]); - expect(requestWithConsent.user.ext.consented_providers_settings.consented_providers).to.equal('1~1.35.41.101'); + expect(requestWithConsent.user.ext.consented_providers_settings.addtl_consent).to.equal('1~1.35.41.101'); expect(requestWithConsent.user.ext.consent).to.equal('3huaa11=qu3198ae'); }); - it('should not contain `consented_providers_settings.consented_providers` on user.ext when consent is not provided', function () { + it('should not contain `consented_providers_settings.addtl_consent` on user.ext when consent is not provided', function () { const options = { gdprConsent: { addtlConsent: '1~1.35.41.101', @@ -3492,7 +3735,7 @@ describe('IndexexchangeAdapter', function () { const validBidWithConsent = spec.buildRequests(DEFAULT_BANNER_VALID_BID, options); const requestWithConsent = extractPayload(validBidWithConsent[0]); - expect(utils.deepAccess(requestWithConsent, 'user.ext.consented_providers_settings')).to.not.exist; + expect(utils.deepAccess(requestWithConsent, 'user.ext.consented_providers_settings.addtl_consent')).to.not.exist; expect(utils.deepAccess(requestWithConsent, 'user.ext.consent')).to.not.exist; }); @@ -3611,7 +3854,7 @@ describe('IndexexchangeAdapter', function () { } } FEATURE_TOGGLES.getFeatureToggles(LOCAL_STORAGE_FEATURE_TOGGLES_KEY); - expect(FEATURE_TOGGLES.isFeatureEnabled('test')).to.be.undefined; + expect(FEATURE_TOGGLES.isFeatureEnabled('test')).to.be.false; expect(FEATURE_TOGGLES.featureToggles).to.deep.equal({}); }); @@ -3672,125 +3915,377 @@ describe('IndexexchangeAdapter', function () { expect(requests).to.be.an('array'); expect(requests).to.have.lengthOf(1); }); - }); - - describe('LocalStorage error codes', () => { - let TODAY = new Date().toISOString().slice(0, 10); - const key = 'ixdiag'; - - let sandbox; - let setDataInLocalStorageStub; - let getDataFromLocalStorageStub; - let removeDataFromLocalStorageStub; - let localStorageValues = {}; - beforeEach(() => { - sandbox = sinon.sandbox.create(); - setDataInLocalStorageStub = sandbox.stub(storage, 'setDataInLocalStorage').callsFake((key, value) => localStorageValues[key] = value) - getDataFromLocalStorageStub = sandbox.stub(storage, 'getDataFromLocalStorage').callsFake((key) => localStorageValues[key]) - removeDataFromLocalStorageStub = sandbox.stub(storage, 'removeDataFromLocalStorage').callsFake((key) => delete localStorageValues[key]) + it('request should have requested feature toggles when local storage is enabled', function () { sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); + const requests = spec.buildRequests([bid, DEFAULT_BANNER_VALID_BID[0]], DEFAULT_OPTION); + const r = extractPayload(requests[0]); + expect(r.ext.features).to.exist; + expect(Object.keys(r.ext.features).length).to.equal(FEATURE_TOGGLES.REQUESTED_FEATURE_TOGGLES.length); }); - afterEach(() => { - setDataInLocalStorageStub.restore(); - getDataFromLocalStorageStub.restore(); - removeDataFromLocalStorageStub.restore(); - localStorageValues = {}; - sandbox.restore(); - - config.setConfig({ - ortb2: {}, - ix: {}, - }) + it('request should have requested feature toggles when local storage is not enabled', function () { + sandbox.stub(storage, 'localStorageIsEnabled').returns(false); + const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); + const requests = spec.buildRequests([bid, DEFAULT_BANNER_VALID_BID[0]], DEFAULT_OPTION); + const r = extractPayload(requests[0]); + expect(r.ext.features).to.exist; + expect(Object.keys(r.ext.features).length).to.equal(FEATURE_TOGGLES.REQUESTED_FEATURE_TOGGLES.length); }); - it('should not log error in LocalStorage when there is no logError called.', () => { - const bid = DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]; - expect(spec.isBidRequestValid(bid)).to.be.true; - expect(localStorageValues[key]).to.be.undefined; + it('request should not have any feature toggles when there is no requested feature toggle', function () { + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); + FEATURE_TOGGLES.REQUESTED_FEATURE_TOGGLES = [] + const requests = spec.buildRequests([bid, DEFAULT_BANNER_VALID_BID[0]], DEFAULT_OPTION); + const r = extractPayload(requests[0]); + expect(r.ext.features).to.be.undefined; }); - it('should log ERROR_CODES.BID_SIZE_INVALID_FORMAT in LocalStorage when there is logError called.', () => { - const bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); - bid.params.size = ['400', 100]; - - expect(spec.isBidRequestValid(bid)).to.be.false; - expect(JSON.parse(localStorageValues[key])).to.deep.equal({ [TODAY]: { [ERROR_CODES.BID_SIZE_INVALID_FORMAT]: 1 } }); + it('request should not have any feature toggles when there is no requested feature toggle and local storage not enabled', function () { + sandbox.stub(storage, 'localStorageIsEnabled').returns(false); + const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); + const requests = spec.buildRequests([bid, DEFAULT_BANNER_VALID_BID[0]], DEFAULT_OPTION); + const r = extractPayload(requests[0]); + FEATURE_TOGGLES.REQUESTED_FEATURE_TOGGLES = [] + expect(r.ext.features).to.be.undefined; }); - it('should log ERROR_CODES.BID_SIZE_NOT_INCLUDED in LocalStorage when there is logError called.', () => { - const bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); - bid.params.size = [407, 100]; - - expect(spec.isBidRequestValid(bid)).to.be.false; - expect(JSON.parse(localStorageValues[key])).to.deep.equal({ [TODAY]: { [ERROR_CODES.BID_SIZE_NOT_INCLUDED]: 1 } }); + it('correct activation status of requested feature toggles when local storage not enabled', function () { + sandbox.stub(storage, 'localStorageIsEnabled').returns(false); + const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); + FEATURE_TOGGLES.REQUESTED_FEATURE_TOGGLES = ['test1'] + const requests = spec.buildRequests([bid, DEFAULT_BANNER_VALID_BID[0]], DEFAULT_OPTION); + const r = extractPayload(requests[0]); + expect(r.ext.features).to.deep.equal({ + [FEATURE_TOGGLES.REQUESTED_FEATURE_TOGGLES[0]]: { activated: false } + }); }); - it('should log ERROR_CODES.PROPERTY_NOT_INCLUDED in LocalStorage when there is logError called.', () => { - const bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); - bid.params.video = {}; + it('correct activation status of requested feature toggles', function () { + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + serverResponse.body.ext.features = { + [FEATURE_TOGGLES.REQUESTED_FEATURE_TOGGLES[0]]: { + activated: true + } + } + FEATURE_TOGGLES.setFeatureToggles(serverResponse); + let bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); + let requests = spec.buildRequests([bid, DEFAULT_BANNER_VALID_BID[0]], DEFAULT_OPTION); + let r = extractPayload(requests[0]); + expect(r.ext.features).to.deep.equal({ + [FEATURE_TOGGLES.REQUESTED_FEATURE_TOGGLES[0]]: { activated: true } + }); - expect(spec.isBidRequestValid(bid)).to.be.false; - expect(JSON.parse(localStorageValues[key])).to.deep.equal({ [TODAY]: { [ERROR_CODES.PROPERTY_NOT_INCLUDED]: 4 } }); + serverResponse.body.ext.features = { + [FEATURE_TOGGLES.REQUESTED_FEATURE_TOGGLES[0]]: { + activated: false + } + } + FEATURE_TOGGLES.setFeatureToggles(serverResponse); + bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); + requests = spec.buildRequests([bid, DEFAULT_BANNER_VALID_BID[0]], DEFAULT_OPTION); + r = extractPayload(requests[0]); + expect(r.ext.features).to.deep.equal({ + [FEATURE_TOGGLES.REQUESTED_FEATURE_TOGGLES[0]]: { activated: false } + }); }); - it('should log ERROR_CODES.SITE_ID_INVALID_VALUE in LocalStorage when there is logError called.', () => { - const bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); - bid.params.siteId = false; + describe('multiformat tests with enable multiformat ft enabled', () => { + let ftStub; + let validBids; + beforeEach(() => { + ftStub = sinon.stub(FEATURE_TOGGLES, 'isFeatureEnabled').callsFake((ftName) => { + if (ftName == 'pbjs_enable_multiformat') { + return true; + } + return false; + }); + validBids = DEFAULT_MULTIFORMAT_VALID_BID; + }); - expect(spec.isBidRequestValid(bid)).to.be.false; - expect(JSON.parse(localStorageValues[key])).to.deep.equal({ [TODAY]: { [ERROR_CODES.SITE_ID_INVALID_VALUE]: 1 } }); - }); + afterEach(() => { + ftStub.restore(); + validBids = DEFAULT_MULTIFORMAT_VALID_BID; + }); - it('should log ERROR_CODES.BID_FLOOR_INVALID_FORMAT in LocalStorage when there is logError called.', () => { - const bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); - bid.params.bidFloor = true; + it('banner multiformat request, should generate banner imp', () => { + const request = spec.buildRequests(DEFAULT_MULTIFORMAT_BANNER_VALID_BID, {}) + const imp = extractPayload(request[0]).imp[0]; + const bannerImpression = imp.banner + expect(request).to.have.lengthOf(1); + expect(extractPayload(request[0]).imp).to.have.lengthOf(1); + expect(imp.id).to.equal(DEFAULT_MULTIFORMAT_BANNER_VALID_BID[0].bidId); + expect(bannerImpression.format[0].w).to.equal(DEFAULT_MULTIFORMAT_BANNER_VALID_BID[0].params.size[0]); + expect(bannerImpression.format[0].h).to.equal(DEFAULT_MULTIFORMAT_BANNER_VALID_BID[0].params.size[1]); + }); + it('should generate video impression', () => { + const request = spec.buildRequests(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID, {}); + const imp = extractPayload(request[0]).imp[0]; + const videoImp = imp.video + expect(request).to.have.lengthOf(1); + expect(extractPayload(request[0]).imp).to.have.lengthOf(1); + expect(imp.id).to.equal(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0].bidId); + expect(videoImp.w).to.equal(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0].params.size[0]); + expect(videoImp.h).to.equal(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0].params.size[1]); + }); + it('different ad units, should only have 1 request', () => { + const bids = [DEFAULT_MULTIFORMAT_BANNER_VALID_BID[0], DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]]; + const request = spec.buildRequests(bids, {}); + expect(request).to.have.lengthOf(1); + }); + it('should return valid banner requests', function () { + const bids = [DEFAULT_MULTIFORMAT_BANNER_VALID_BID[0], DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]]; + const request = spec.buildRequests(bids, {}); + const impressions = extractPayload(request[0]).imp; + expect(impressions).to.have.lengthOf(2); - expect(spec.isBidRequestValid(bid)).to.be.false; - expect(JSON.parse(localStorageValues[key])).to.deep.equal({ [TODAY]: { [ERROR_CODES.BID_FLOOR_INVALID_FORMAT]: 1 } }); - }); + expect(impressions[0].id).to.equal(bids[0].bidId); + expect(impressions[0].banner.format).to.be.length(bids[0].mediaTypes.banner.sizes.length); + expect(impressions[0].banner.topframe).to.be.oneOf([0, 1]); + expect(impressions[0].ext.siteID).to.equal('123'); + expect(impressions[1].ext.siteID).to.equal('456'); + impressions[0].banner.format.map(({ w, h, ext }, index) => { + const size = bids[0].mediaTypes.banner.sizes[index]; - it('should log ERROR_CODES.IX_FPD_EXCEEDS_MAX_SIZE in LocalStorage when there is logError called.', () => { - const bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); + expect(w).to.equal(size[0]); + expect(h).to.equal(size[1]); + expect(ext.siteID).to.be.undefined; + }); - config.setConfig({ - ix: { - firstPartyData: { - cd: Array(1700).join('#') - } - } + impressions[1].banner.format.map(({ w, h, ext }, index) => { + const size = bids[1].mediaTypes.banner.sizes[index]; + + expect(w).to.equal(size[0]); + expect(h).to.equal(size[1]); + expect(ext.siteID).to.be.undefined; + }); + }); + it('banner / native multiformat request, only 1 request expect 1 imp', () => { + const request = spec.buildRequests(DEFAULT_MULTIFORMAT_NATIVE_VALID_BID, {}); + expect(request).to.have.lengthOf(1); + const imp = extractPayload(request[0]).imp[0]; + expect(extractPayload(request[0]).imp).to.have.lengthOf(1); + expect(imp.banner).to.exist; + expect(imp.native).to.exist; }); - expect(spec.isBidRequestValid(bid)).to.be.true; - spec.buildRequests([bid], {}); - expect(JSON.parse(localStorageValues[key])).to.deep.equal({ [TODAY]: { [ERROR_CODES.IX_FPD_EXCEEDS_MAX_SIZE]: 2 } }); - }); + it('should return valid banner and video requests', function () { + const bids = [DEFAULT_MULTIFORMAT_BANNER_VALID_BID[0], DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]]; + const request = spec.buildRequests(bids, {}); + const videoImpression = extractPayload(request[0]).imp[1]; - it('should log ERROR_CODES.EXCEEDS_MAX_SIZE in LocalStorage when there is logError called.', () => { - const bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); - bid.bidderRequestId = Array(8000).join('#'); + expect(extractPayload(request[0]).imp).to.have.lengthOf(2); + expect(videoImpression.id).to.equal(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0].bidId); + expect(videoImpression.video.w).to.equal(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0].mediaTypes.video.playerSize[0][0]); + expect(videoImpression.video.h).to.equal(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0].mediaTypes.video.playerSize[0][1]); + }); - expect(spec.isBidRequestValid(bid)).to.be.true; - spec.buildRequests([bid], {}); - expect(JSON.parse(localStorageValues[key])).to.deep.equal({ [TODAY]: { [ERROR_CODES.EXCEEDS_MAX_SIZE]: 2 } }); - }); + it('multiformat banner / video - bid floors', function () { + const bids = [DEFAULT_MULTIFORMAT_BANNER_VALID_BID[0], DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]]; + bids[0].params.bidFloor = 2.35; + bids[0].params.bidFloorCur = 'USD'; + let adunitcode = bids[1].adUnitCode; + bids[1].adUnitCode = bids[0].adUnitCode; + bids[1].params.bidFloor = 2.05; + bids[1].params.bidFloorCur = 'USD'; + const request = spec.buildRequests(bids, {}); - it('should log ERROR_CODES.PB_FPD_EXCEEDS_MAX_SIZE in LocalStorage when there is logError called.', () => { - const bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); - const ortb2 = { - site: { - ext: { - data: { - pageType: Array(5700).join('#') - } + expect(extractPayload(request[0]).imp).to.have.lengthOf(1); + expect(extractPayload(request[0]).imp[0].bidfloor).to.equal(2.05); + expect(extractPayload(request[0]).imp[0].bidfloorcur).to.equal('USD'); + expect(extractPayload(request[0]).imp[0].video.ext.bidfloor).to.equal(2.05); + expect(extractPayload(request[0]).imp[0].banner.format[0].ext.bidfloor).to.equal(2.35); + bids[1].adUnitCode = adunitcode; + }); + + it('multiformat banner / native - bid floors', function () { + const bids = [DEFAULT_MULTIFORMAT_BANNER_VALID_BID[0], DEFAULT_MULTIFORMAT_NATIVE_VALID_BID[0]]; + bids[0].params.bidFloor = 2.35; + bids[0].params.bidFloorCur = 'USD'; + let adunitcode = bids[1].adUnitCode; + bids[1].adUnitCode = bids[0].adUnitCode; + bids[1].params.bidFloor = 2.05; + bids[1].params.bidFloorCur = 'USD'; + const request = spec.buildRequests(bids, {}); + + expect(extractPayload(request[0]).imp).to.have.lengthOf(1); + expect(extractPayload(request[0]).imp[0].bidfloor).to.equal(2.05); + expect(extractPayload(request[0]).imp[0].bidfloorcur).to.equal('USD'); + expect(extractPayload(request[0]).imp[0].native.ext.bidfloor).to.equal(2.05); + bids[1].adUnitCode = adunitcode; + }); + + it('multiformat banner / native - bid floors, banner imp less', function () { + const bids = [DEFAULT_MULTIFORMAT_BANNER_VALID_BID[0], DEFAULT_MULTIFORMAT_NATIVE_VALID_BID[0]]; + bids[0].params.bidFloor = 2.05; + bids[0].params.bidFloorCur = 'USD'; + let tid = bids[1].transactionId; + bids[1].transactionId = bids[0].transactionId; + bids[1].params.bidFloor = 2.35; + bids[1].params.bidFloorCur = 'USD'; + const request = spec.buildRequests(bids, {}); + + expect(extractPayload(request[0]).imp).to.have.lengthOf(1); + expect(extractPayload(request[0]).imp[0].bidfloor).to.equal(2.05); + expect(extractPayload(request[0]).imp[0].bidfloorcur).to.equal('USD'); + expect(extractPayload(request[0]).imp[0].native.ext.bidfloor).to.equal(2.35); + bids[1].transactionId = tid; + }); + + it('should return valid banner and video requests, different adunit, creates multiimp request', function () { + let bid = DEFAULT_MULTIFORMAT_VALID_BID[0] + bid.bidId = '1abcdef' + const bids = [DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0], bid]; + const request = spec.buildRequests(bids, {}); + expect(request).to.have.lengthOf(1); + expect(extractPayload(request[0]).imp).to.have.lengthOf(2); + }); + + it('should return valid video requests, different adunit, creates multiimp request', function () { + let bid = DEFAULT_BANNER_VALID_BID[0] + bid.bidId = '1abcdef' + const bids = [DEFAULT_VIDEO_VALID_BID[0], bid]; + const request = spec.buildRequests(bids, {}); + expect(request).to.have.lengthOf(1); + expect(extractPayload(request[0]).imp).to.have.lengthOf(2); + }); + + it('should contain all correct IXdiag properties', function () { + const bids = [DEFAULT_MULTIFORMAT_BANNER_VALID_BID[0], DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]]; + const request = spec.buildRequests(bids, {}); + const diagObj = extractPayload(request[0]).ext.ixdiag; + expect(diagObj.iu).to.equal(0); + expect(diagObj.nu).to.equal(0); + expect(diagObj.ou).to.equal(2); + expect(diagObj.ren).to.equal(true); + expect(diagObj.mfu).to.equal(2); + expect(diagObj.allu).to.equal(2); + expect(diagObj.version).to.equal('$prebid.version$'); + expect(diagObj.url).to.equal('http://localhost:9876/context.html') + expect(diagObj.pbadslot).to.equal(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0].ortb2Imp.ext.data.pbadslot) + expect(diagObj.tagid).to.equal(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0].params.tagId) + expect(diagObj.adunitcode).to.equal(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0].adUnitCode) + }); + + it('should use siteId override for multiformat', function () { + validBids[0].params = { + tagId: '123', + siteId: '456', + size: [300, 250], + video: { + siteId: '1111' + }, + banner: { + siteId: '2222' + }, + native: { + siteId: '3333' } } - }; + const request = spec.buildRequests(validBids, {}); + const imp = request[0].data.imp[0]; + expect(imp.ext.siteID).to.equal('2222'); + expect(imp.video.ext.siteID).to.be.undefined; + imp.banner.format.map(({ ext }) => { + expect(ext.siteID).to.be.undefined; + }); + expect(imp.native.ext.siteID).to.be.undefined; + }); + it('should use default siteId if overrides are not provided for multiformat', function () { + const bids = validBids; + delete bids[0].params.banner; + delete bids[0].params.video; + delete bids[0].params.native; + const request = spec.buildRequests(bids, {}); + const imp = request[0].data.imp[0] + expect(imp.video.ext.siteID).to.be.undefined; + imp.banner.format.map(({ ext }) => { + expect(ext.siteID).to.be.undefined; + }); + expect(imp.native.ext.siteID).to.be.undefined; + expect(imp.ext.siteID).to.equal('456'); + }); + }); + }); + + describe('LocalStorage error codes', () => { + let TODAY = new Date().toISOString().slice(0, 10); + const key = 'ixdiag'; + + let sandbox; + let setDataInLocalStorageStub; + let getDataFromLocalStorageStub; + let removeDataFromLocalStorageStub; + let localStorageValues = {}; + + beforeEach(() => { + sandbox = sinon.sandbox.create(); + setDataInLocalStorageStub = sandbox.stub(storage, 'setDataInLocalStorage').callsFake((key, value) => localStorageValues[key] = value) + getDataFromLocalStorageStub = sandbox.stub(storage, 'getDataFromLocalStorage').callsFake((key) => localStorageValues[key]) + removeDataFromLocalStorageStub = sandbox.stub(storage, 'removeDataFromLocalStorage').callsFake((key) => delete localStorageValues[key]) + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + }); + + afterEach(() => { + setDataInLocalStorageStub.restore(); + getDataFromLocalStorageStub.restore(); + removeDataFromLocalStorageStub.restore(); + localStorageValues = {}; + sandbox.restore(); + + config.setConfig({ + ortb2: {}, + ix: {}, + }) + }); + + it('should not log error in LocalStorage when there is no logError called.', () => { + const bid = DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]; expect(spec.isBidRequestValid(bid)).to.be.true; - spec.buildRequests([bid], { ortb2 }); - expect(JSON.parse(localStorageValues[key])).to.deep.equal({ [TODAY]: { [ERROR_CODES.PB_FPD_EXCEEDS_MAX_SIZE]: 2 } }); + expect(localStorageValues[key]).to.be.undefined; + }); + + it('should log ERROR_CODES.BID_SIZE_INVALID_FORMAT in LocalStorage when there is logError called.', () => { + const bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); + bid.params.size = ['400', 100]; + + expect(spec.isBidRequestValid(bid)).to.be.false; + expect(JSON.parse(localStorageValues[key])).to.deep.equal({ [TODAY]: { [ERROR_CODES.BID_SIZE_INVALID_FORMAT]: 1 } }); + }); + + it('should log ERROR_CODES.BID_SIZE_NOT_INCLUDED in LocalStorage when there is logError called.', () => { + const bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); + bid.params.size = [407, 100]; + + expect(spec.isBidRequestValid(bid)).to.be.false; + expect(JSON.parse(localStorageValues[key])).to.deep.equal({ [TODAY]: { [ERROR_CODES.BID_SIZE_NOT_INCLUDED]: 1 } }); + }); + + it('should log ERROR_CODES.PROPERTY_NOT_INCLUDED in LocalStorage when there is logError called.', () => { + const bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); + bid.params.video = {}; + + expect(spec.isBidRequestValid(bid)).to.be.false; + expect(JSON.parse(localStorageValues[key])).to.deep.equal({ [TODAY]: { [ERROR_CODES.PROPERTY_NOT_INCLUDED]: 4 } }); + }); + + it('should log ERROR_CODES.SITE_ID_INVALID_VALUE in LocalStorage when there is logError called.', () => { + const bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); + bid.params.siteId = false; + + expect(spec.isBidRequestValid(bid)).to.be.false; + expect(JSON.parse(localStorageValues[key])).to.deep.equal({ [TODAY]: { [ERROR_CODES.SITE_ID_INVALID_VALUE]: 1 } }); + }); + + it('should log ERROR_CODES.BID_FLOOR_INVALID_FORMAT in LocalStorage when there is logError called.', () => { + const bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); + bid.params.bidFloor = true; + + expect(spec.isBidRequestValid(bid)).to.be.false; + expect(JSON.parse(localStorageValues[key])).to.deep.equal({ [TODAY]: { [ERROR_CODES.BID_FLOOR_INVALID_FORMAT]: 1 } }); }); it('should log ERROR_CODES.VIDEO_DURATION_INVALID in LocalStorage when there is logError called.', () => { @@ -3888,4 +4383,528 @@ describe('IndexexchangeAdapter', function () { expect(localStorageValues[key]).to.be.undefined; }); }); + describe('combine imps test', function () { + it('base test', function () { + const imps = [ + { + 'b8c6b5d5-76a1-4a90-b635-0c7eae1bfaa7': { + 'tid': 'b8c6b5d5-76a1-4a90-b635-0c7eae1bfaa7', + 'adUnitCode': 'div-gpt-v1', + 'missingImps': [ + { + 'id': '2e46cbd7d4e046', + 'ext': { + 'siteID': '12345' + }, + 'banner': { + 'w': 300, + 'h': 250, + 'topframe': 1 + } + } + ], + 'missingCount': 1 + } + }, + { + 'b8c6b5d5-76a1-4a90-b635-0c7eae1bfaa7': { + 'ixImps': [ + { + 'id': '2e46cbd7d4e046', + 'ext': { + 'siteID': '12345', + 'tid': 'b8c6b5d5-76a1-4a90-b635-0c7eae1bfaa7' + }, + 'video': { + 'skipppable': false, + 'playback_methods': [ + 'auto_play_sound_off' + ], + 'minduration': 0, + 'maxduration': 30, + 'mimes': [ + 'video/mp4', + 'video/x-flv' + ], + 'placement': 1, + 'playerSize': [ + [ + 640, + 480 + ] + ], + 'protocols': [ + 6 + ], + 'w': 640, + 'h': 480, + 'ext': { + 'siteID': '12345', + 'tid': 'b8c6b5d5-76a1-4a90-b635-0c7eae1bfaa7' + } + } + }, + ], + 'adUnitCode': 'div-gpt-v1' + } + }, + { + 'b8c6b5d5-76a1-4a90-b635-0c7eae1bfaa7': { + 'tid': 'b8c6b5d5-76a1-4a90-b635-0c7eae1bfaa7', + 'adUnitCode': 'div-gpt-v1', + 'missingImps': [ + { + 'id': '2e46cbd7d4e046', + 'ext': { + 'siteID': '12345' + }, + 'banner': { + 'w': 300, + 'h': 250, + 'topframe': 1 + } + } + ], + 'missingCount': 1 + } + } + ] + const result = combineImps(imps); + expect(result['b8c6b5d5-76a1-4a90-b635-0c7eae1bfaa7'].ixImps.length).to.equal(1) + expect(result['b8c6b5d5-76a1-4a90-b635-0c7eae1bfaa7'].missingImps.length).to.equal(2); + }); + it('switch order', function () { + const imps = [ + { + 'b8c6b5d5-76a1-4a90-b635-0c7eae1bfaa7': { + 'ixImps': [ + { + 'id': '2e46cbd7d4e046', + 'ext': { + 'siteID': '12345', + 'tid': 'b8c6b5d5-76a1-4a90-b635-0c7eae1bfaa7' + }, + 'video': { + 'skipppable': false, + 'playback_methods': [ + 'auto_play_sound_off' + ], + 'minduration': 0, + 'maxduration': 30, + 'mimes': [ + 'video/mp4', + 'video/x-flv' + ], + 'placement': 1, + 'playerSize': [ + [ + 640, + 480 + ] + ], + 'protocols': [ + 6 + ], + 'w': 640, + 'h': 480, + 'ext': { + 'siteID': '12345', + 'tid': 'b8c6b5d5-76a1-4a90-b635-0c7eae1bfaa7' + } + } + }, + ], + 'adUnitCode': 'div-gpt-v1' + } + }, + { + 'b8c6b5d5-76a1-4a90-b635-0c7eae1bfaa7': { + 'tid': 'b8c6b5d5-76a1-4a90-b635-0c7eae1bfaa7', + 'adUnitCode': 'div-gpt-v1', + 'missingImps': [ + { + 'id': '2e46cbd7d4e046', + 'ext': { + 'siteID': '12345' + }, + 'banner': { + 'w': 300, + 'h': 250, + 'topframe': 1 + } + } + ], + 'missingCount': 1 + } + } + ] + const result = combineImps(imps) + expect(result['b8c6b5d5-76a1-4a90-b635-0c7eae1bfaa7'].missingImps.length).to.equal(1); + expect(result['b8c6b5d5-76a1-4a90-b635-0c7eae1bfaa7'].ixImps.length).to.equal(1); + }); + }); + describe('apply floors test', function () { + it('video test', function() { + const bid = utils.deepClone(DEFAULT_VIDEO_VALID_BID[0]); + bid.params.bidFloor = 50; + bid.params.bidFloorCur = 'USD'; + const imp = bidToVideoImp(bid); + expect(imp.video.ext.bidfloor).to.equal(50); + expect(imp.bidfloor).to.equal(50); + expect(imp.bidfloorcur).to.equal('USD'); + expect(imp.video.ext.fl).to.equal('x'); + }); + it('native test', function() { + const bid = utils.deepClone(DEFAULT_NATIVE_VALID_BID[0]); + bid.params.bidFloor = 50; + bid.params.bidFloorCur = 'USD'; + const imp = bidToNativeImp(bid); + expect(imp.native.ext.bidfloor).to.equal(50); + expect(imp.bidfloor).to.equal(50); + expect(imp.bidfloorcur).to.equal('USD'); + expect(imp.native.ext.fl).to.equal('x'); + }); + }); + + describe('deduplicateImpExtFields', () => { + it('should remove duplicate keys from banner.ext', () => { + const input = { + imp: [ + { + banner: { + ext: { + key1: 'value1', + key2: 'value2', + key3: 'value3', + key4: 'value4', // duplicate value + }, + }, + video: { + ext: { + key4: 'value4', // duplicate value + } + }, + ext: { + key4: 'value4' + } + }, + ], + }; + + const expectedOutput = { + imp: [ + { + banner: { + ext: { + key1: 'value1', + key2: 'value2', + key3: 'value3', + }, + }, + ext: { + key4: 'value4' + }, + video: { + ext: {} + }, + }, + ], + }; + + const result = deduplicateImpExtFields(input); + expect(result).to.deep.equal(expectedOutput); + }); + + it('should remove duplicate keys from banner.format[].ext', () => { + const input = { + imp: [ + { + banner: { + format: [ + { + ext: { + key1: 'value1', + key2: 'value2', + key3: 'value3', + key4: 'value4', // duplicate value + }, + }, + ], + }, + video: { + ext: { + key4: 'value4', // duplicate value + } + }, + ext: { + key4: 'value4' + } + }, + ], + }; + + const expectedOutput = { + imp: [ + { + banner: { + format: [ + { + ext: { + key1: 'value1', + key2: 'value2', + key3: 'value3', + }, + }, + ], + }, + video: { + ext: {} + }, + ext: { + key4: 'value4' + } + }, + ], + }; + + const result = deduplicateImpExtFields(input); + expect(result).to.deep.equal(expectedOutput); + }); + + it('should remove duplicate keys from video.ext', () => { + const input = { + imp: [ + { + video: { + ext: { + key1: 'value1', + key2: 'value2', + key3: 'value3', + key4: 'value4', // duplicate value + }, + }, + banner: { + ext: { + key1: 'value1', + } + }, + ext: { + key4: 'value4' + } + }, + ], + }; + + const expectedOutput = { + imp: [ + { + video: { + ext: { + key1: 'value1', + key2: 'value2', + key3: 'value3', + }, + }, + banner: { + ext: { + key1: 'value1', + } + }, + ext: { + key4: 'value4' + } + }, + ], + }; + + const result = deduplicateImpExtFields(input); + expect(result).to.deep.equal(expectedOutput); + }); + + it('should remove duplicate keys from native.ext', () => { + const input = { + imp: [ + { + native: { + ext: { + key1: 'value1', + key2: 'value2', + key3: 'value3', + key4: 'value4', // duplicate value + }, + }, + banner: { + ext: { + key1: 'value1', + } + }, + ext: { + key4: 'value4' + } + }, + ], + }; + + const expectedOutput = { + imp: [ + { + native: { + ext: { + key1: 'value1', + key2: 'value2', + key3: 'value3', + }, + }, + banner: { + ext: { + key1: 'value1', + } + }, + ext: { + key4: 'value4' + } + } + ], + }; + + const result = deduplicateImpExtFields(input); + expect(result).to.deep.equal(expectedOutput); + }); + + it('should return the input unchanged when there is no imp.ext', () => { + const input = { + imp: [ + { + banner: { + ext: { + key1: 'value1', + key2: 'value2', + key3: 'value3', + }, + }, + video: { + ext: { + key4: 'value4', + key5: 'value5', + key6: 'value6', + }, + }, + native: { + ext: { + key7: 'value7', + key8: 'value8', + key9: 'value9', + }, + }, + }, + ], + }; + + const result = deduplicateImpExtFields(input); + expect(result).to.deep.equal(input); + }); + }); + + describe('removeSiteIDs', () => { + it('should remove siteIDs from banner / video / native format', () => { + const request = { + imp: [ + { + banner: { + format: [ + { + ext: { + siteID: '123' + } + }, + { + ext: { + siteID: '123' + } + } + ], + ext: { + siteID: '123' + } + }, + video: { + ext: { + siteID: '123' + } + }, + native: { + ext: { + siteID: '123' + } + }, + ext: { + siteID: '123' + } + } + ] + }; + + const expected = { + ext: { + ixdiag: { + usid: true + } + }, + imp: [ + { + banner: { + format: [ + { + ext: {} + }, + { + ext: {} + } + ], + ext: {} + }, + video: { + ext: {} + }, + native: { + ext: {} + }, + ext: { + 'siteID': '123' + } + } + ] + }; + + expect(removeSiteIDs(request)).to.deep.equal(expected); + }); + + it('should not modify the request when imp.ext is not present', () => { + const request = { + imp: [ + { + banner: { + ext: { + siteID: '123' + } + } + } + ] + }; + + const expected = { + imp: [ + { + banner: { + ext: { + siteID: '123' + } + }, + } + ] + }; + + expect(removeSiteIDs(request)).to.deep.equal(expected); + }); + }); }); diff --git a/test/spec/modules/jixieBidAdapter_spec.js b/test/spec/modules/jixieBidAdapter_spec.js index c354de3c3ac..5bf2a3b6fc9 100644 --- a/test/spec/modules/jixieBidAdapter_spec.js +++ b/test/spec/modules/jixieBidAdapter_spec.js @@ -309,71 +309,53 @@ describe('jixie Adapter', function () { it('should populate eids when supported userIds are available', function () { const oneSpecialBidReq = Object.assign({}, bidRequests_[0], { - userId: { - tdid: '11111111-2222-3333-4444-555555555555', - uid2: { id: 'AbCdEfGhIjKlMnO9qdQBW7qtMw8f1WTUvtkHe6u+fqLfhbtsqrJ697Z6YoI3IB9klGUv1wvlFIbwH7ELDlqQBGtj8AC1v7fMJ/Q45E7W90dts7UQLTDMLNmtHBRDXVb0Fpas4Vh3yN1jGVQNhzXC/RpGIVtZE8dCxcjfa7VfcTNcvxxxxx==' }, - pubProvidedId: [{ - source: 'puburl1.com', - uids: [{ - id: 'pubid1', - atype: 1, - ext: { - stype: 'ppuid' - } - }] - }, { - source: 'puburl2.com', - uids: [{ - id: 'pubid2' - }] - }] - } - }); - const request = spec.buildRequests([oneSpecialBidReq], bidderRequest_); - const payload = JSON.parse(request.data); - expect(payload.eids).to.deep.include({ - 'source': 'adserver.org', - 'uids': [ + userIdAsEids: [ { - 'id': '11111111-2222-3333-4444-555555555555', - 'atype': 1, - 'ext': { - 'rtiPartner': 'TDID' - } - } - ] - }); - expect(payload.eids).to.deep.include({ - 'source': 'uidapi.com', - 'uids': [ + 'source': 'adserver.org', + 'uids': [ + { + 'id': '11111111-2222-3333-4444-555555555555', + 'atype': 1, + 'ext': { + 'rtiPartner': 'TDID' + } + } + ] + }, { - 'id': 'AbCdEfGhIjKlMnO9qdQBW7qtMw8f1WTUvtkHe6u+fqLfhbtsqrJ697Z6YoI3IB9klGUv1wvlFIbwH7ELDlqQBGtj8AC1v7fMJ/Q45E7W90dts7UQLTDMLNmtHBRDXVb0Fpas4Vh3yN1jGVQNhzXC/RpGIVtZE8dCxcjfa7VfcTNcvxxxxx==', - 'atype': 3 - } - ] - }); - - expect(payload.eids).to.deep.include({ - 'source': 'puburl1.com', - 'uids': [ + 'source': 'uidapi.com', + 'uids': [ + { + 'id': 'AbCdEfGhIjKlMnO9qdQBW7qtMw8f1WTUvtkHe6u+fqLfhbtsqrJ697Z6YoI3IB9klGUv1wvlFIbwH7ELDlqQBGtj8AC1v7fMJ/Q45E7W90dts7UQLTDMLNmtHBRDXVb0Fpas4Vh3yN1jGVQNhzXC/RpGIVtZE8dCxcjfa7VfcTNcvxxxxx==', + 'atype': 3 + } + ] + }, { - 'id': 'pubid1', - 'atype': 1, - 'ext': { - 'stype': 'ppuid' - } - } - ] - }); - - expect(payload.eids).to.deep.include({ - 'source': 'puburl2.com', - 'uids': [ + 'source': 'puburl1.com', + 'uids': [ + { + 'id': 'pubid1', + 'atype': 1, + 'ext': { + 'stype': 'ppuid' + } + } + ] + }, { - 'id': 'pubid2' - } - ] + 'source': 'puburl2.com', + 'uids': [ + { + 'id': 'pubid2' + } + ] + }, + ], }); + const request = spec.buildRequests([oneSpecialBidReq], bidderRequest_); + const payload = JSON.parse(request.data); + expect(payload.eids).to.eql(oneSpecialBidReq.userIdAsEids); }); });// describe diff --git a/test/spec/modules/kargoBidAdapter_spec.js b/test/spec/modules/kargoBidAdapter_spec.js index 525c00d655c..dae2640d224 100644 --- a/test/spec/modules/kargoBidAdapter_spec.js +++ b/test/spec/modules/kargoBidAdapter_spec.js @@ -35,7 +35,7 @@ describe('kargo adapter tests', function () { }); describe('build request', function() { - var bids, undefinedCurrency, noAdServerCurrency, cookies = [], localStorageItems = [], sessionIds = [], requestCount = 0; + var bids, undefinedCurrency, noAdServerCurrency, nonUSDAdServerCurrency, cookies = [], localStorageItems = [], sessionIds = [], requestCount = 0; beforeEach(function () { $$PREBID_GLOBAL$$.bidderSettings = { @@ -45,6 +45,7 @@ describe('kargo adapter tests', function () { }; undefinedCurrency = false; noAdServerCurrency = false; + nonUSDAdServerCurrency = false; sandbox.stub(config, 'getConfig').callsFake(function(key) { if (key === 'currency') { if (undefinedCurrency) { @@ -53,6 +54,9 @@ describe('kargo adapter tests', function () { if (noAdServerCurrency) { return {}; } + if (nonUSDAdServerCurrency) { + return {adServerCurrency: 'EUR'}; + } return {adServerCurrency: 'USD'}; } if (key === 'debug') return true; @@ -63,13 +67,44 @@ describe('kargo adapter tests', function () { bids = [ { params: { - placementId: 'foo' + placementId: 'foo', + socialCanvas: { + segments: ['segment_1', 'segment_2', 'segment_3'], + url: 'https://socan.url' + } }, - bidId: 1, + auctionId: '1234098', + bidId: '1', + adUnitCode: '101', + sizes: [[320, 50], [300, 250], [300, 600]], + mediaTypes: { + banner: { + sizes: [[320, 50], [300, 50]] + } + }, + bidRequestsCount: 1, + bidderRequestsCount: 2, + bidderWinsCount: 3, userId: { - tdid: 'fake-tdid' + tdid: 'ed1562d5-e52b-406f-8e65-e5ab3ed5583c' + }, + userIdAsEids: [ + { + 'source': 'adserver.org', + 'uids': [ + { + 'id': 'ed1562d5-e52b-406f-8e65-e5ab3ed5583c', + 'atype': 1, + 'ext': { + 'rtiPartner': 'TDID' + } + } + ] + } + ], + floorData: { + floorMin: 1 }, - sizes: [[320, 50], [300, 250], [300, 600]], ortb2: { device: { sua: { @@ -91,25 +126,77 @@ describe('kargo adapter tests', function () { version: [ '99', '0', '0', '0' ] } ], - mobile: 0, - model: '' + mobile: 1, + model: 'model', + source: 1, } } + }, + ortb2Imp: { + ext: { + tid: '10101', + data: { + adServer: { + name: 'gam', + adSlot: '/22558409563,18834096/dfy_mobile_adhesion' + }, + pbAdSlot: '/22558409563,18834096/dfy_mobile_adhesion' + }, + gpid: '/22558409563,18834096/dfy_mobile_adhesion' + } } }, { params: { placementId: 'bar' }, - bidId: 2, - sizes: [[320, 50], [300, 250], [300, 600]] + bidId: '2', + adUnitCode: '202', + sizes: [[320, 50], [300, 250], [300, 600]], + mediaTypes: { + video: { + sizes: [[320, 50], [300, 50]] + } + }, + bidRequestsCount: 0, + bidderRequestsCount: 0, + bidderWinsCount: 0, + ortb2Imp: { + ext: { + tid: '20202', + data: { + adServer: { + name: 'gam', + adSlot: '/22558409563,18834096/dfy_mobile_adhesion' + }, + pbAdSlot: '/22558409563,18834096/dfy_mobile_adhesion' + } + } + } }, { params: { placementId: 'bar' }, - bidId: 3, - sizes: [[320, 50], [300, 250], [300, 600]] + bidId: '3', + adUnitCode: '303', + sizes: [[320, 50], [300, 250], [300, 600]], + mediaTypes: { + native: { + sizes: [[320, 50], [300, 50]] + } + }, + ortb2Imp: { + ext: { + tid: '30303', + data: { + adServer: { + name: 'gam', + adSlot: '/22558409563,18834096/dfy_mobile_adhesion' + } + } + } + } } ]; }); @@ -160,11 +247,19 @@ describe('kargo adapter tests', function () { function simulateNoCurrencyObject() { undefinedCurrency = true; noAdServerCurrency = false; + nonUSDAdServerCurrency = false; } function simulateNoAdServerCurrency() { undefinedCurrency = false; noAdServerCurrency = true; + nonUSDAdServerCurrency = false; + } + + function simulateNonUSDAdServerCurrency() { + undefinedCurrency = false; + noAdServerCurrency = false; + nonUSDAdServerCurrency = true; } function generateGDPR(applies, haveConsent) { @@ -182,6 +277,32 @@ describe('kargo adapter tests', function () { }; } + function generatePageView() { + return { + id: '112233', + timestamp: frozenNow.getTime(), + url: 'http://pageview.url' + } + } + + function generateRawCRB(rawCRB, rawCRBLocalStorage) { + if (rawCRB == null && rawCRBLocalStorage == null) { + return null + } + + let result = {} + + if (rawCRB != null) { + result.rawCRB = rawCRB + } + + if (rawCRBLocalStorage != null) { + result.rawCRBLocalStorage = rawCRBLocalStorage + } + + return result + } + function getKrgCrb() { return 'eyJzeW5jSWRzIjp7IjIiOiI4MmZhMjU1NS01OTY5LTQ2MTQtYjRjZS00ZGNmMTA4MGU5ZjkiLCIxNiI6IlZveElrOEFvSnowQUFFZENleUFBQUFDMiY1MDIiLCIyMyI6ImQyYTg1NWE1LTFiMWMtNDMwMC05NDBlLWE3MDhmYTFmMWJkZSIsIjI0IjoiVm94SWs4QW9KejBBQUVkQ2V5QUFBQUMyJjUwMiIsIjI1IjoiNWVlMjQxMzgtNWUwMy00YjlkLWE5NTMtMzhlODMzZjI4NDlmIiwiMl84MCI6ImQyYTg1NWE1LTFiMWMtNDMwMC05NDBlLWE3MDhmYTFmMWJkZSIsIjJfOTMiOiI1ZWUyNDEzOC01ZTAzLTRiOWQtYTk1My0zOGU4MzNmMjg0OWYifSwibGV4SWQiOiI1ZjEwODgzMS0zMDJkLTExZTctYmY2Yi00NTk1YWNkM2JmNmMiLCJjbGllbnRJZCI6IjI0MTBkOGYyLWMxMTEtNDgxMS04OGE1LTdiNWUxOTBlNDc1ZiIsIm9wdE91dCI6ZmFsc2UsImV4cGlyZVRpbWUiOjE0OTc0NDkzODI2NjgsImxhc3RTeW5jZWRBdCI6MTQ5NzM2Mjk3OTAxMn0='; } @@ -253,6 +374,12 @@ describe('kargo adapter tests', function () { setLocalStorageItem('krg_crb', getEmptyKrgCrb()); } + function initializePageView() { + setLocalStorageItem('pageViewId', 112233); + setLocalStorageItem('pageViewTimestamp', frozenNow.getTime()); + setLocalStorageItem('pageViewUrl', 'http://pageview.url'); + } + function initializeEmptyKrgCrbCookie() { setCookie('krg_crb', getEmptyKrgCrbOldStyle()); } @@ -261,30 +388,20 @@ describe('kargo adapter tests', function () { return spec._getSessionId(); } - function getExpectedKrakenParams(excludeUserIds, expectedRawCRB, expectedRawCRBCookie, expectedGDPR) { + function getExpectedKrakenParams(expectedCRB, expectedPage, excludeUserIds, expectedGDPR, currency) { var base = { + pbv: '$prebid.version$', + aid: '1234098', + requestCount: 0, + sid: getSessionId(), + url: 'https://www.prebid.org', timeout: 200, - requestCount: requestCount++, - currency: 'USD', - cpmGranularity: 1, - timestamp: frozenNow.getTime(), - cpmRange: { - floor: 0, - ceil: 20 - }, - bidIDs: { - 1: 'foo', - 2: 'bar', - 3: 'bar' - }, - bidSizes: { - 1: [[320, 50], [300, 250], [300, 600]], - 2: [[320, 50], [300, 250], [300, 600]], - 3: [[320, 50], [300, 250], [300, 600]] - }, + ts: frozenNow.getTime(), device: { - width: screen.width, - height: screen.height, + size: [ + screen.width, + screen.height + ], sua: { platform: { brand: 'macOS', @@ -304,14 +421,61 @@ describe('kargo adapter tests', function () { version: [ '99', '0', '0', '0' ] } ], - mobile: 0, - model: '', + mobile: 1, + model: 'model', + source: 1 + }, + }, + imp: [ + { + code: '101', + id: '1', + pid: 'foo', + tid: '10101', + banner: { + sizes: [[320, 50], [300, 50]] + }, + bidRequestCount: 1, + bidderRequestCount: 2, + bidderWinCount: 3, + floor: 1, + fpd: { + gpid: '/22558409563,18834096/dfy_mobile_adhesion' + } + }, + { + code: '202', + id: '2', + pid: 'bar', + tid: '20202', + video: { + sizes: [[320, 50], [300, 50]] + }, + fpd: { + gpid: '/22558409563,18834096/dfy_mobile_adhesion' + } }, + { + code: '303', + id: '3', + pid: 'bar', + tid: '30303', + native: { + sizes: [[320, 50], [300, 50]] + }, + fpd: { + gpid: '/22558409563,18834096/dfy_mobile_adhesion' + } + } + ], + socan: { + segments: ['segment_1', 'segment_2', 'segment_3'], + url: 'https://socan.url' }, - userIDs: { + user: { kargoID: '5f108831-302d-11e7-bf6b-4595acd3bf6c', clientID: '2410d8f2-c111-4811-88a5-7b5e190e475f', - tdID: 'fake-tdid', + tdID: 'ed1562d5-e52b-406f-8e65-e5ab3ed5583c', crbIDs: { 2: '82fa2555-5969-4614-b4ce-4dcf1080e9f9', 16: 'VoxIk8AoJz0AAEdCeyAAAAC2&502', @@ -322,85 +486,61 @@ describe('kargo adapter tests', function () { '2_93': '5ee24138-5e03-4b9d-a953-38e833f2849f' }, optOut: false, - usp: '1---' - }, - pageURL: 'https://www.prebid.org', - prebidRawBidRequests: [ - { - bidId: 1, - ortb2: { - device: { - sua: { - platform: { - brand: 'macOS', - version: [ '12', '6', '0' ] - }, - browsers: [ - { - brand: 'Chromium', - version: [ '106', '0', '5249', '119' ] - }, - { - brand: 'Google Chrome', - version: [ '106', '0', '5249', '119' ] - }, - { - brand: 'Not;A=Brand', - version: [ '99', '0', '0', '0' ] - } - ], - mobile: 0, - model: '' + usp: '1---', + sharedIDEids: [ + { + source: 'adserver.org', + uids: [ + { + id: 'ed1562d5-e52b-406f-8e65-e5ab3ed5583c', + atype: 1, + ext: { + rtiPartner: 'TDID' + } } - } - }, - params: { - placementId: 'foo' - }, - userId: { - tdid: 'fake-tdid' - }, - sizes: [[320, 50], [300, 250], [300, 600]], - }, - { - bidId: 2, - params: { - placementId: 'bar' - }, - sizes: [[320, 50], [300, 250], [300, 600]] - }, - { - bidId: 3, - params: { - placementId: 'bar' - }, - sizes: [[320, 50], [300, 250], [300, 600]] - } - ], - rawCRB: expectedRawCRBCookie, - rawCRBLocalStorage: expectedRawCRB + ] + } + ] + } }; + if (excludeUserIds) { + base.user.crbIDs = {}; + delete base.user.clientID; + delete base.user.kargoID; + delete base.user.optOut; + } + if (expectedGDPR) { - base.userIDs['gdpr'] = expectedGDPR; + base.user.gdpr = expectedGDPR; } - if (excludeUserIds === true) { - base.userIDs = { - crbIDs: {}, - usp: '1---' - }; - delete base.prebidRawBidRequests[0].userId.tdid; + if (expectedPage) { + base.page = expectedPage; + } + + if (currency) { + base.cur = currency; + } + + const reqCount = requestCount++; + base.requestCount = reqCount + + if (expectedCRB != null) { + if (expectedCRB.rawCRB != null) { + base.rawCRB = expectedCRB.rawCRB + } + if (expectedCRB.rawCRBLocalStorage != null) { + base.rawCRBLocalStorage = expectedCRB.rawCRBLocalStorage + } } return base; } - function testBuildRequests(excludeTdid, expected, gdpr) { + function testBuildRequests(expected, gdpr) { var clonedBids = JSON.parse(JSON.stringify(bids)); - if (excludeTdid) { - delete clonedBids[0].userId.tdid; - } + var payload = { timeout: 200, uspConsent: '1---', @@ -414,15 +554,13 @@ describe('kargo adapter tests', function () { } var request = spec.buildRequests(clonedBids, payload); - expected.sessionId = getSessionId(); - sessionIds.push(expected.sessionId); - var krakenParams = JSON.parse(decodeURIComponent(request.data.slice(5))); - expect(request.data.slice(0, 5)).to.equal('json='); - expect(request.url).to.equal('https://krk.kargo.com/api/v2/bid'); - expect(request.method).to.equal('GET'); - expect(request.currency).to.equal('USD'); + var krakenParams = request.data; + + expect(request.url).to.equal('https://krk2.kargo.com/api/v1/prebid'); + expect(request.method).to.equal('POST'); expect(request.timeout).to.equal(200); expect(krakenParams).to.deep.equal(expected); + // Make sure session ID stays the same across requests simulating multiple auctions on one page load for (let i in sessionIds) { if (i == 0) { @@ -435,76 +573,93 @@ describe('kargo adapter tests', function () { it('works when all params and localstorage and cookies are correctly set', function() { initializeKrgCrb(); - testBuildRequests(false, getExpectedKrakenParams(undefined, getKrgCrb(), getKrgCrbOldStyle())); + initializePageView(); + testBuildRequests(getExpectedKrakenParams(generateRawCRB(getKrgCrbOldStyle(), getKrgCrb()), generatePageView())); }); it('works when all params and cookies are correctly set but no localstorage', function() { initializeKrgCrb(true); - testBuildRequests(false, getExpectedKrakenParams(undefined, null, getKrgCrbOldStyle())); + testBuildRequests(getExpectedKrakenParams(generateRawCRB(getKrgCrbOldStyle()))); }); it('gracefully handles nothing being set', function() { - testBuildRequests(true, getExpectedKrakenParams(true, null, null)); + testBuildRequests(getExpectedKrakenParams(undefined, undefined, true)); }); it('gracefully handles browsers without localStorage', function() { simulateNoLocalStorage(); - testBuildRequests(true, getExpectedKrakenParams(true, null, null)); + testBuildRequests(getExpectedKrakenParams(undefined, undefined, true)); }); it('handles empty yet valid Kargo CRB', function() { initializeEmptyKrgCrb(); initializeEmptyKrgCrbCookie(); - testBuildRequests(true, getExpectedKrakenParams(true, getEmptyKrgCrb(), getEmptyKrgCrbOldStyle())); + initializePageView(); + testBuildRequests(getExpectedKrakenParams(generateRawCRB(getEmptyKrgCrbOldStyle(), getEmptyKrgCrb()), generatePageView(), true)); }); it('handles broken Kargo CRBs where base64 encoding is invalid', function() { initializeInvalidKrgCrbType1(); - testBuildRequests(true, getExpectedKrakenParams(true, getInvalidKrgCrbType1(), null)); + initializePageView(); + testBuildRequests(getExpectedKrakenParams(generateRawCRB(undefined, getInvalidKrgCrbType1()), generatePageView(), true)); }); it('handles broken Kargo CRBs where top level JSON is invalid on cookie', function() { initializeInvalidKrgCrbType1Cookie(); - testBuildRequests(true, getExpectedKrakenParams(true, null, getInvalidKrgCrbType1())); + initializePageView(); + testBuildRequests(getExpectedKrakenParams(generateRawCRB(getInvalidKrgCrbType1()), generatePageView(), true)); }); it('handles broken Kargo CRBs where decoded JSON is invalid', function() { initializeInvalidKrgCrbType2(); - testBuildRequests(true, getExpectedKrakenParams(true, getInvalidKrgCrbType2(), null)); + initializePageView(); + testBuildRequests(getExpectedKrakenParams(generateRawCRB(undefined, getInvalidKrgCrbType2()), generatePageView(), true)); }); it('handles broken Kargo CRBs where inner base 64 is invalid on cookie', function() { initializeInvalidKrgCrbType2Cookie(); - testBuildRequests(true, getExpectedKrakenParams(true, null, getInvalidKrgCrbType2OldStyle())); + initializePageView(); + testBuildRequests(getExpectedKrakenParams(generateRawCRB(getInvalidKrgCrbType2OldStyle()), generatePageView(), true)); }); it('handles broken Kargo CRBs where inner JSON is invalid on cookie', function() { initializeInvalidKrgCrbType3Cookie(); - testBuildRequests(true, getExpectedKrakenParams(true, null, getInvalidKrgCrbType3OldStyle())); + initializePageView(); + testBuildRequests(getExpectedKrakenParams(generateRawCRB(getInvalidKrgCrbType3OldStyle()), generatePageView(), true)); }); it('handles broken Kargo CRBs where inner JSON is falsey', function() { initializeInvalidKrgCrbType4Cookie(); - testBuildRequests(true, getExpectedKrakenParams(true, null, getInvalidKrgCrbType4OldStyle())); + initializePageView(); + testBuildRequests(getExpectedKrakenParams(generateRawCRB(getInvalidKrgCrbType4OldStyle()), generatePageView(), true)); }); it('handles a non-existant currency object on the config', function() { simulateNoCurrencyObject(); initializeKrgCrb(); - testBuildRequests(false, getExpectedKrakenParams(undefined, getKrgCrb(), getKrgCrbOldStyle())); + initializePageView(); + testBuildRequests(getExpectedKrakenParams(generateRawCRB(getKrgCrbOldStyle(), getKrgCrb()), generatePageView())); }); it('handles no ad server currency being set on the currency object in the config', function() { simulateNoAdServerCurrency(); initializeKrgCrb(); - testBuildRequests(false, getExpectedKrakenParams(undefined, getKrgCrb(), getKrgCrbOldStyle())); + initializePageView(); + testBuildRequests(getExpectedKrakenParams(generateRawCRB(getKrgCrbOldStyle(), getKrgCrb()), generatePageView())); + }); + + it('handles non-USD ad server currency being set on the currency object in the config', function() { + simulateNonUSDAdServerCurrency(); + initializeKrgCrb(); + initializePageView(); + testBuildRequests(getExpectedKrakenParams(generateRawCRB(getKrgCrbOldStyle(), getKrgCrb()), generatePageView(), undefined, undefined, 'EUR')); }); it('sends gdpr consent', function () { initializeKrgCrb(); - testBuildRequests(false, getExpectedKrakenParams(undefined, getKrgCrb(), getKrgCrbOldStyle(), generateGDPRExpect(true, true)), generateGDPR(true, true)); - testBuildRequests(false, getExpectedKrakenParams(undefined, getKrgCrb(), getKrgCrbOldStyle(), generateGDPRExpect(false, true)), generateGDPR(false, true)); - testBuildRequests(false, getExpectedKrakenParams(undefined, getKrgCrb(), getKrgCrbOldStyle(), generateGDPRExpect(false, false)), generateGDPR(false, false)); + testBuildRequests(getExpectedKrakenParams(generateRawCRB(getKrgCrbOldStyle(), getKrgCrb()), undefined, false, generateGDPRExpect(true, true)), generateGDPR(true, true)); + testBuildRequests(getExpectedKrakenParams(generateRawCRB(getKrgCrbOldStyle(), getKrgCrb()), undefined, false, generateGDPRExpect(false, true)), generateGDPR(false, true)); + testBuildRequests(getExpectedKrakenParams(generateRawCRB(getKrgCrbOldStyle(), getKrgCrb()), undefined, false, generateGDPRExpect(false, false)), generateGDPR(false, false)); }); }); @@ -603,11 +758,11 @@ describe('kargo adapter tests', function () { }] }); var expectation = [{ + ad: '
', requestId: '1', cpm: 3, width: 320, height: 50, - ad: '
', ttl: 300, creativeId: 'foo', dealId: undefined, @@ -619,10 +774,10 @@ describe('kargo adapter tests', function () { } }, { requestId: '2', + ad: '
', cpm: 2.5, width: 300, height: 250, - ad: '
', ttl: 300, creativeId: 'bar', dealId: 'dmpmptest1234', @@ -636,10 +791,10 @@ describe('kargo adapter tests', function () { } }, { requestId: '3', + ad: '
', cpm: 2.5, width: 300, height: 250, - ad: '
', ttl: 300, creativeId: 'bar', dealId: undefined, @@ -651,10 +806,10 @@ describe('kargo adapter tests', function () { } }, { requestId: '4', + ad: '
', cpm: 2.5, width: 300, height: 250, - ad: '
', ttl: 300, creativeId: 'bar', dealId: undefined, @@ -669,7 +824,6 @@ describe('kargo adapter tests', function () { cpm: 2.5, width: 300, height: 250, - ad: '', vastXml: '', ttl: 300, creativeId: 'bar', @@ -685,7 +839,6 @@ describe('kargo adapter tests', function () { cpm: 2.5, width: 300, height: 250, - ad: '', vastUrl: 'https://foobar.com/vast_adm', ttl: 300, creativeId: 'bar', @@ -739,8 +892,8 @@ describe('kargo adapter tests', function () { }); }); - function getUserSyncsWhenAllowed(gdprConsent, usPrivacy) { - return spec.getUserSyncs({iframeEnabled: true}, null, gdprConsent, usPrivacy); + function getUserSyncsWhenAllowed(gdprConsent, usPrivacy, gppConsent) { + return spec.getUserSyncs({iframeEnabled: true}, null, gdprConsent, usPrivacy, gppConsent); } function getUserSyncsWhenForbidden() { @@ -755,17 +908,17 @@ describe('kargo adapter tests', function () { shouldSimulateOutdatedBrowser = true; } - function getSyncUrl(index, gdprApplies, gdprConsentString, usPrivacy) { + function getSyncUrl(index, gdprApplies, gdprConsentString, usPrivacy, gpp, gppSid) { return { type: 'iframe', - url: `https://crb.kargo.com/api/v1/initsyncrnd/${clientId}?seed=3205e885-8d37-4139-b47e-f82cff268000&idx=${index}&gdpr=${gdprApplies}&gdpr_consent=${gdprConsentString}&us_privacy=${usPrivacy}` + url: `https://crb.kargo.com/api/v1/initsyncrnd/${clientId}?seed=3205e885-8d37-4139-b47e-f82cff268000&idx=${index}&gdpr=${gdprApplies}&gdpr_consent=${gdprConsentString}&us_privacy=${usPrivacy}&gpp=${gpp}&gpp_sid=${gppSid}` }; } - function getSyncUrls(gdprApplies, gdprConsentString, usPrivacy) { + function getSyncUrls(gdprApplies, gdprConsentString, usPrivacy, gpp, gppSid) { var syncs = []; for (var i = 0; i < 5; i++) { - syncs[i] = getSyncUrl(i, gdprApplies || 0, gdprConsentString || '', usPrivacy || ''); + syncs[i] = getSyncUrl(i, gdprApplies || 0, gdprConsentString || '', usPrivacy || '', gpp || '', gppSid || ''); } return syncs; } @@ -802,6 +955,11 @@ describe('kargo adapter tests', function () { safelyRun(() => expect(getUserSyncsWhenAllowed({ gdprApplies: true, consentString: 'consentstring' })).to.deep.equal(getSyncUrls(1, 'consentstring', ''))); }); + it('pass through gpp consent', function () { + turnOnClientId(); + safelyRun(() => expect(getUserSyncsWhenAllowed(null, null, { consentString: 'gppString', applicableSections: [-1] })).to.deep.equal(getSyncUrls('', '', '', 'gppString', '-1'))); + }); + it('no user syncs when there is outdated browser', function() { turnOnClientId(); simulateOutdatedBrowser(); @@ -830,7 +988,7 @@ describe('kargo adapter tests', function () { expect(triggerPixelStub.getCall(0)).to.be.null; spec.onTimeout([{ auctionId: '1234', timeout: 2000 }]); expect(triggerPixelStub.getCall(0)).to.not.be.null; - expect(triggerPixelStub.getCall(0).args[0]).to.exist.and.to.include('https://krk.kargo.com/api/v1/event/timeout'); + expect(triggerPixelStub.getCall(0).args[0]).to.exist.and.to.include('https://krk2.kargo.com/api/v1/event/timeout'); expect(triggerPixelStub.getCall(0).args[0]).to.include('aid=1234'); expect(triggerPixelStub.getCall(0).args[0]).to.include('ato=2000'); }); diff --git a/test/spec/modules/kiviadsBidAdapter_spec.js b/test/spec/modules/kiviadsBidAdapter_spec.js new file mode 100644 index 00000000000..03d58cbc265 --- /dev/null +++ b/test/spec/modules/kiviadsBidAdapter_spec.js @@ -0,0 +1,404 @@ +import { expect } from 'chai'; +import { spec } from '../../../modules/kiviadsBidAdapter.js'; +import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; +import { getUniqueIdentifierStr } from '../../../src/utils.js'; + +const bidder = 'kiviads' +const adUrl = 'https://lb.kiviads.com/pbjs'; +const syncUrl = 'https://sync.kiviads.com'; + +describe('KiviAdsBidAdapter', function () { + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + placementId: 'testBanner', + } + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [VIDEO]: { + playerSize: [[300, 300]], + minduration: 5, + maxduration: 60 + } + }, + params: { + placementId: 'testVideo', + } + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [NATIVE]: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + } + }, + params: { + placementId: 'testNative', + } + } + ]; + + const invalidBid = { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + + } + } + + const bidderRequest = { + uspConsent: '1---', + gdprConsent: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + refererInfo: { + referer: 'https://test.com' + }, + bidderTimeout: 300 + }; + + describe('isBidRequestValid', function () { + it('Should return true if there are bidId, params and key parameters present', function () { + expect(spec.isBidRequestValid(bids[0])).to.be.true; + }); + it('Should return false if at least one of parameters is not present', function () { + expect(spec.isBidRequestValid(invalidBid)).to.be.false; + }); + }); + + describe('buildRequests', function () { + let serverRequest = spec.buildRequests(bids, bidderRequest); + + it('Creates a ServerRequest object with method, URL and data', function () { + expect(serverRequest).to.exist; + expect(serverRequest.method).to.exist; + expect(serverRequest.url).to.exist; + expect(serverRequest.data).to.exist; + }); + + it('Returns POST method', function () { + expect(serverRequest.method).to.equal('POST'); + }); + + it('Returns valid URL', function () { + expect(serverRequest.url).to.equal(adUrl); + }); + + it('Returns general data valid', function () { + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.all.keys('deviceWidth', + 'deviceHeight', + 'language', + 'secure', + 'host', + 'page', + 'placements', + 'coppa', + 'ccpa', + 'gdpr', + 'gpp', + 'tmax' + ); + expect(data.deviceWidth).to.be.a('number'); + expect(data.deviceHeight).to.be.a('number'); + expect(data.language).to.be.a('string'); + expect(data.secure).to.be.within(0, 1); + expect(data.host).to.be.a('string'); + expect(data.host).to.contain('localhost'); + expect(data.page).to.be.a('string'); + expect(data.page).to.equal('/'); + expect(data.coppa).to.be.a('number'); + expect(data.gdpr).to.be.a('string'); + expect(data.ccpa).to.be.a('string'); + expect(data.tmax).to.be.a('number'); + expect(data.placements).to.have.lengthOf(3); + }); + + it('Returns valid placements', function () { + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.placementId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('publisher'); + + if (placement.adFormat === BANNER) { + expect(placement.sizes).to.be.an('array'); + } + switch (placement.adFormat) { + case BANNER: + expect(placement.sizes).to.be.an('array'); + break; + case VIDEO: + expect(placement.playerSize).to.be.an('array'); + expect(placement.minduration).to.be.an('number'); + expect(placement.maxduration).to.be.an('number'); + break; + case NATIVE: + expect(placement.native).to.be.an('object'); + break; + } + } + }); + + it('Returns data with gdprConsent and without uspConsent', function () { + delete bidderRequest.uspConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data.gdpr).to.exist; + expect(data.gdpr).to.be.a('string'); + expect(data.gdpr).to.equal(bidderRequest.gdprConsent); + expect(data.ccpa).to.not.exist; + delete bidderRequest.gdprConsent; + }); + + it('Returns data with uspConsent and without gdprConsent', function () { + bidderRequest.uspConsent = '1---'; + delete bidderRequest.gdprConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data.ccpa).to.exist; + expect(data.ccpa).to.be.a('string'); + expect(data.ccpa).to.equal(bidderRequest.uspConsent); + expect(data.gdpr).to.not.exist; + }); + + it('Returns empty data if no valid requests are passed', function () { + serverRequest = spec.buildRequests([], bidderRequest); + let data = serverRequest.data; + expect(data.placements).to.be.an('array').that.is.empty; + }); + }); + + describe('interpretResponse', function () { + it('Should interpret banner response', function () { + const banner = { + body: [{ + mediaType: 'banner', + width: 300, + height: 250, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + let bannerResponses = spec.interpretResponse(banner); + expect(bannerResponses).to.be.an('array').that.is.not.empty; + let dataItem = bannerResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); + expect(dataItem.requestId).to.equal(banner.body[0].requestId); + expect(dataItem.cpm).to.equal(banner.body[0].cpm); + expect(dataItem.width).to.equal(banner.body[0].width); + expect(dataItem.height).to.equal(banner.body[0].height); + expect(dataItem.ad).to.equal(banner.body[0].ad); + expect(dataItem.ttl).to.equal(banner.body[0].ttl); + expect(dataItem.creativeId).to.equal(banner.body[0].creativeId); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal(banner.body[0].currency); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + it('Should interpret video response', function () { + const video = { + body: [{ + vastUrl: 'test.com', + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + let videoResponses = spec.interpretResponse(video); + expect(videoResponses).to.be.an('array').that.is.not.empty; + + let dataItem = videoResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'vastUrl', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.5); + expect(dataItem.vastUrl).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + it('Should interpret native response', function () { + const native = { + body: [{ + mediaType: 'native', + native: { + clickUrl: 'test.com', + title: 'Test', + image: 'test.com', + impressionTrackers: ['test.com'], + }, + ttl: 120, + cpm: 0.4, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + let nativeResponses = spec.interpretResponse(native); + expect(nativeResponses).to.be.an('array').that.is.not.empty; + + let dataItem = nativeResponses[0]; + expect(dataItem).to.have.keys('requestId', 'cpm', 'ttl', 'creativeId', 'netRevenue', 'currency', 'mediaType', 'native', 'meta'); + expect(dataItem.native).to.have.keys('clickUrl', 'impressionTrackers', 'title', 'image') + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.4); + expect(dataItem.native.clickUrl).to.equal('test.com'); + expect(dataItem.native.title).to.equal('Test'); + expect(dataItem.native.image).to.equal('test.com'); + expect(dataItem.native.impressionTrackers).to.be.an('array').that.is.not.empty; + expect(dataItem.native.impressionTrackers[0]).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + it('Should return an empty array if invalid banner response is passed', function () { + const invBanner = { + body: [{ + width: 300, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + + let serverResponses = spec.interpretResponse(invBanner); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid video response is passed', function () { + const invVideo = { + body: [{ + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let serverResponses = spec.interpretResponse(invVideo); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid native response is passed', function () { + const invNative = { + body: [{ + mediaType: 'native', + clickUrl: 'test.com', + title: 'Test', + impressionTrackers: ['test.com'], + ttl: 120, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + }] + }; + let serverResponses = spec.interpretResponse(invNative); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid response is passed', function () { + const invalid = { + body: [{ + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let serverResponses = spec.interpretResponse(invalid); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + }); + + describe('getUserSyncs', function() { + it('Should return array of objects with proper sync config , include GDPR', function() { + const syncData = spec.getUserSyncs({}, {}, { + consentString: 'ALL', + gdprApplies: true, + }, {}); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object') + expect(syncData[0].type).to.be.a('string') + expect(syncData[0].type).to.equal('image') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal(`${syncUrl}/image?pbjs=1&gdpr=1&gdpr_consent=ALL&coppa=0`) + }); + it('Should return array of objects with proper sync config , include CCPA', function() { + const syncData = spec.getUserSyncs({}, {}, {}, { + consentString: '1---' + }); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object') + expect(syncData[0].type).to.be.a('string') + expect(syncData[0].type).to.equal('image') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal(`${syncUrl}/image?pbjs=1&ccpa_consent=1---&coppa=0`) + }); + }); +}); diff --git a/test/spec/modules/koblerBidAdapter_spec.js b/test/spec/modules/koblerBidAdapter_spec.js index 5bcf62ff95d..2b5830f68d2 100644 --- a/test/spec/modules/koblerBidAdapter_spec.js +++ b/test/spec/modules/koblerBidAdapter_spec.js @@ -7,6 +7,7 @@ import {getRefererInfo} from 'src/refererDetection.js'; function createBidderRequest(auctionId, timeout, pageUrl) { return { + bidderRequestId: 'mock-uuid', auctionId: auctionId || 'c1243d83-0bed-4fdb-8c76-42b456be17d0', timeout: timeout || 2000, refererInfo: { @@ -37,6 +38,16 @@ function createValidBidRequest(params, bidId, sizes) { } describe('KoblerAdapter', function () { + let sandbox; + + beforeEach(() => { + sandbox = sinon.sandbox.create(); + }); + + afterEach(() => { + sandbox.restore() + }); + describe('inherited functions', function () { it('exists and is a function', function () { const adapter = newBidder(spec); @@ -207,7 +218,7 @@ describe('KoblerAdapter', function () { const openRtbRequest = JSON.parse(result.data); expect(openRtbRequest.tmax).to.be.equal(timeout); - expect(openRtbRequest.id).to.be.equal(auctionId); + expect(openRtbRequest.id).to.exist; expect(openRtbRequest.site.page).to.be.equal(testUrl); }); @@ -366,26 +377,6 @@ describe('KoblerAdapter', function () { expect(openRtbRequest.imp[1].pmp.deals[1].id).to.be.equal(dealIds2[1]); }); - it('should read timeout from config', function () { - const timeout = 4000; - const validBidRequests = [createValidBidRequest()]; - // No timeout field - const bidderRequest = { - auctionId: 'c1243d83-0bed-4fdb-8c76-42b456be17d0', - refererInfo: { - page: 'example.com' - } - }; - config.setConfig({ - bidderTimeout: timeout - }); - - const result = spec.buildRequests(validBidRequests, bidderRequest); - const openRtbRequest = JSON.parse(result.data); - - expect(openRtbRequest.tmax).to.be.equal(timeout); - }); - it('should read floor price using floors module', function () { const floorPriceFor580x400 = 6.5148; const floorPriceForAnySize = 4.2343; @@ -455,7 +446,7 @@ describe('KoblerAdapter', function () { const openRtbRequest = JSON.parse(result.data); const expectedOpenRtbRequest = { - id: '9ff580cf-e10e-4b66-add7-40ac0c804e21', + id: 'mock-uuid', at: 1, tmax: 4500, cur: ['USD'], diff --git a/test/spec/modules/kueezRtbBidAdapter_spec.js b/test/spec/modules/kueezRtbBidAdapter_spec.js index 7c0d8e92001..84454c8dffa 100644 --- a/test/spec/modules/kueezRtbBidAdapter_spec.js +++ b/test/spec/modules/kueezRtbBidAdapter_spec.js @@ -1,7 +1,6 @@ import {expect} from 'chai'; import { spec as adapter, - SUPPORTED_ID_SYSTEMS, createDomain, hashCode, extractPID, @@ -18,6 +17,8 @@ import {useFakeTimers} from 'sinon'; import {BANNER, VIDEO} from '../../../src/mediaTypes'; import {config} from '../../../src/config'; +export const TEST_ID_SYSTEMS = ['britepoolid', 'criteoId', 'id5id', 'idl_env', 'lipb', 'netId', 'parrableId', 'pubcid', 'tdid', 'pubProvidedId']; + const SUB_DOMAIN = 'exchange'; const BID = { @@ -43,7 +44,12 @@ const BID = { 'bidderWinsCount': 1, 'requestId': 'b0777d85-d061-450e-9bc7-260dd54bbb7a', 'schain': 'a0819c69-005b-41ed-af06-1be1e0aefefc', - 'mediaTypes': [BANNER] + 'mediaTypes': [BANNER], + 'ortb2Imp': { + 'ext': { + 'gpid': '0123456789' + } + } }; const VIDEO_BID = { @@ -98,6 +104,24 @@ const BIDDER_REQUEST = { 'regs': { 'gpp': 'gpp_string', 'gpp_sid': [7] + }, + 'device': { + 'sua': { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, + {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, + {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + } } } }; @@ -263,6 +287,22 @@ describe('KueezRtbBidAdapter', function () { res: `${window.top.screen.width}x${window.top.screen.height}`, schain: VIDEO_BID.schain, sizes: ['545x307'], + sua: { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, + {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, + {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + }, uniqueDealId: `${hashUrl}_${Date.now().toString()}`, uqs: getTopWindowQueryParams(), mediaTypes: { @@ -281,7 +321,8 @@ describe('KueezRtbBidAdapter', function () { protocols: [2, 3, 5, 6], startdelay: 0 } - } + }, + gpid: '' } }); }); @@ -310,6 +351,22 @@ describe('KueezRtbBidAdapter', function () { bidderTimeout: 3000, bidderRequestId: '1fdb5ff1b6eaa7', sizes: ['300x250', '300x600'], + sua: { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, + {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, + {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + }, url: 'https%3A%2F%2Fwww.greatsite.com', referrer: 'https://www.somereferrer.com', cb: 1000, @@ -323,6 +380,7 @@ describe('KueezRtbBidAdapter', function () { schain: BID.schain, res: `${window.top.screen.width}x${window.top.screen.height}`, mediaTypes: [BANNER], + gpid: '0123456789', uqs: getTopWindowQueryParams(), 'ext.param1': 'loremipsum', 'ext.param2': 'dolorsitamet', @@ -398,6 +456,19 @@ describe('KueezRtbBidAdapter', function () { }); }); + it('should get meta from response metaData', function () { + const serverResponse = utils.deepClone(SERVER_RESPONSE); + serverResponse.body.results[0].metaData = { + advertiserDomains: ['kueezrtb.com'], + agencyName: 'Agency Name', + }; + const responses = adapter.interpretResponse(serverResponse, REQUEST); + expect(responses[0].meta).to.deep.equal({ + advertiserDomains: ['kueezrtb.com'], + agencyName: 'Agency Name' + }); + }); + it('should return an array of interpreted video responses', function () { const responses = adapter.interpretResponse(VIDEO_SERVER_RESPONSE, REQUEST); expect(responses).to.have.length(1); @@ -428,7 +499,7 @@ describe('KueezRtbBidAdapter', function () { }); describe('user id system', function () { - Object.keys(SUPPORTED_ID_SYSTEMS).forEach((idSystemProvider) => { + TEST_ID_SYSTEMS.forEach((idSystemProvider) => { const id = Date.now().toString(); const bid = utils.deepClone(BID); diff --git a/test/spec/modules/kulturemediaBidAdapter_spec.js b/test/spec/modules/kulturemediaBidAdapter_spec.js new file mode 100644 index 00000000000..f21fe4a8810 --- /dev/null +++ b/test/spec/modules/kulturemediaBidAdapter_spec.js @@ -0,0 +1,612 @@ +import {expect} from 'chai'; +import {spec} from 'modules/kulturemediaBidAdapter.js'; + +const BANNER_REQUEST = { + 'bidderCode': 'kulturemedia', + 'auctionId': 'auctionId-56a2-4f71-9098-720a68f2f708', + 'bidderRequestId': 'requestId', + 'bidRequest': [{ + 'bidder': 'kulturemedia', + 'params': { + 'placementId': 123456, + }, + 'placementCode': 'div-gpt-dummy-placement-code', + 'mediaTypes': {'banner': {'sizes': [[300, 250]]}}, + 'bidId': 'bidId1', + 'bidderRequestId': 'bidderRequestId', + 'auctionId': 'auctionId-56a2-4f71-9098-720a68f2f708' + }, + { + 'bidder': 'kulturemedia', + 'params': { + 'placementId': 123456, + }, + 'placementCode': 'div-gpt-dummy-placement-code', + 'mediaTypes': {'banner': {'sizes': [[300, 250]]}}, + 'bidId': 'bidId2', + 'bidderRequestId': 'bidderRequestId', + 'auctionId': 'auctionId-56a2-4f71-9098-720a68f2f708' + }], + 'start': 1487883186070, + 'auctionStart': 1487883186069, + 'timeout': 3000 +}; + +const RESPONSE = { + 'headers': null, + 'body': { + 'id': 'responseId', + 'seatbid': [ + { + 'bid': [ + { + 'id': 'bidId1', + 'impid': 'bidId1', + 'price': 0.18, + 'adm': '', + 'adid': '144762342', + 'adomain': [ + 'https://dummydomain.com' + ], + 'iurl': 'iurl', + 'cid': '109', + 'crid': 'creativeId', + 'cat': [], + 'w': 300, + 'h': 250, + 'ext': { + 'prebid': { + 'type': 'banner' + }, + 'bidder': { + 'appnexus': { + 'brand_id': 334553, + 'auction_id': 514667951122925701, + 'bidder_id': 2, + 'bid_ad_type': 0 + } + } + } + }, + { + 'id': 'bidId2', + 'impid': 'bidId2', + 'price': 0.1, + 'adm': '', + 'adid': '144762342', + 'adomain': [ + 'https://dummydomain.com' + ], + 'iurl': 'iurl', + 'cid': '109', + 'crid': 'creativeId', + 'cat': [], + 'w': 300, + 'h': 250, + 'ext': { + 'prebid': { + 'type': 'banner' + }, + 'bidder': { + 'appnexus': { + 'brand_id': 386046, + 'auction_id': 517067951122925501, + 'bidder_id': 2, + 'bid_ad_type': 0 + } + } + } + } + ], + 'seat': 'kulturemedia' + } + ], + 'ext': { + 'usersync': { + 'sovrn': { + 'status': 'none', + 'syncs': [ + { + 'url': 'urlsovrn', + 'type': 'iframe' + } + ] + }, + 'appnexus': { + 'status': 'none', + 'syncs': [ + { + 'url': 'urlappnexus', + 'type': 'pixel' + } + ] + } + }, + 'responsetimemillis': { + 'appnexus': 127 + } + } + } +}; + +const DEFAULT_NETWORK_ID = 1; + +describe('kulturemediaBidAdapter:', function () { + let videoBidRequest; + + const VIDEO_REQUEST = { + 'bidderCode': 'kulturemedia', + 'auctionId': 'e158486f-8c7f-472f-94ce-b0cbfbb50ab4', + 'bidderRequestId': '34feaad34lkj2', + 'bids': videoBidRequest, + 'auctionStart': 1520001292880, + 'timeout': 3000, + 'start': 1520001292884, + 'doneCbCallCount': 0, + 'refererInfo': { + 'numIframes': 1, + 'reachedTop': true, + 'referer': 'test.com' + } + }; + + beforeEach(function () { + videoBidRequest = { + mediaTypes: { + video: { + context: 'instream', + playerSize: [[640, 480]], + } + }, + bidder: 'kulturemedia', + sizes: [640, 480], + bidId: '30b3efwfwe1e', + adUnitCode: 'video1', + params: { + video: { + playerWidth: 640, + playerHeight: 480, + mimes: ['video/mp4', 'application/javascript'], + protocols: [2, 5], + api: [2], + position: 1, + delivery: [2], + sid: 134, + rewarded: 1, + placement: 1, + hp: 1, + inventoryid: 123 + }, + site: { + id: 1, + page: 'https://test.com', + referrer: 'http://test.com' + }, + publisherId: 'km123' + } + }; + }); + + describe('isBidRequestValid', function () { + context('basic validation', function () { + beforeEach(function () { + // Basic Valid BidRequest + this.bid = { + bidder: 'kulturemedia', + mediaTypes: { + banner: { + sizes: [[250, 300]] + } + }, + params: { + placementId: 'placementId', + publisherId: 'publisherId', + } + }; + }); + + it('should accept request if placementId and publisherId are passed', function () { + expect(spec.isBidRequestValid(this.bid)).to.be.true; + }); + + it('reject requests without params', function () { + this.bid.params = {}; + expect(spec.isBidRequestValid(this.bid)).to.be.false; + }); + + it('returns false when banner mediaType does not exist', function () { + this.bid.mediaTypes = {} + expect(spec.isBidRequestValid(this.bid)).to.be.false; + }); + }); + + context('banner validation', function () { + it('returns true when banner sizes are defined', function () { + const bid = { + bidder: 'kulturemedia', + mediaTypes: { + banner: { + sizes: [[250, 300]] + } + }, + params: { + placementId: 'placementId', + publisherId: 'publisherId', + } + }; + + expect(spec.isBidRequestValid(bid)).to.be.true; + }); + + it('returns false when banner sizes are invalid', function () { + const invalidSizes = [ + undefined, + '2:1', + 123, + 'test' + ]; + + invalidSizes.forEach((sizes) => { + const bid = { + bidder: 'kulturemedia', + mediaTypes: { + banner: { + sizes + } + }, + params: { + placementId: 'placementId', + publisherId: 'publisherId', + } + }; + + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + }); + }); + + context('video validation', function () { + beforeEach(function () { + // Basic Valid BidRequest + this.bid = { + bidder: 'kulturemedia', + mediaTypes: { + video: { + playerSize: [[300, 50]], + context: 'instream', + mimes: ['foo', 'bar'], + protocols: [1, 2] + } + }, + params: { + placementId: 'placementId', + publisherId: 'publisherId', + } + }; + }); + + it('should return true (skip validations) when e2etest = true', function () { + this.bid.params = { + e2etest: true + }; + expect(spec.isBidRequestValid(this.bid)).to.equal(true); + }); + + it('returns false when video context is not defined', function () { + delete this.bid.mediaTypes.video.context; + + expect(spec.isBidRequestValid(this.bid)).to.be.false; + }); + + it('returns false when video playserSize is invalid', function () { + const invalidSizes = [ + undefined, + '2:1', + 123, + 'test' + ]; + + invalidSizes.forEach((playerSize) => { + this.bid.mediaTypes.video.playerSize = playerSize; + expect(spec.isBidRequestValid(this.bid)).to.be.false; + }); + }); + + it('returns false when video mimes is invalid', function () { + const invalidMimes = [ + undefined, + 'test', + 1, + [] + ] + + invalidMimes.forEach((mimes) => { + this.bid.mediaTypes.video.mimes = mimes; + expect(spec.isBidRequestValid(this.bid)).to.be.false; + }) + }); + + it('returns false when video protocols is invalid', function () { + const invalidMimes = [ + undefined, + 'test', + 1, + [] + ] + + invalidMimes.forEach((protocols) => { + this.bid.mediaTypes.video.protocols = protocols; + expect(spec.isBidRequestValid(this.bid)).to.be.false; + }) + }); + }); + }); + + describe('buildRequests', function () { + context('when mediaType is banner', function () { + it('creates request data', function () { + let request = spec.buildRequests(BANNER_REQUEST.bidRequest, BANNER_REQUEST); + + expect(request).to.exist.and.to.be.a('object'); + const payload = JSON.parse(request.data); + expect(payload.imp[0]).to.have.property('id', BANNER_REQUEST.bidRequest[0].bidId); + expect(payload.imp[1]).to.have.property('id', BANNER_REQUEST.bidRequest[1].bidId); + }); + + it('has gdpr data if applicable', function () { + const req = Object.assign({}, BANNER_REQUEST, { + gdprConsent: { + consentString: 'consentString', + gdprApplies: true, + } + }); + let request = spec.buildRequests(BANNER_REQUEST.bidRequest, req); + + const payload = JSON.parse(request.data); + expect(payload.user.ext).to.have.property('consent', req.gdprConsent.consentString); + expect(payload.regs.ext).to.have.property('gdpr', 1); + }); + + it('should properly forward eids parameters', function () { + const req = Object.assign({}, BANNER_REQUEST); + req.bidRequest[0].userIdAsEids = [ + { + source: 'dummy.com', + uids: [ + { + id: 'd6d0a86c-20c6-4410-a47b-5cba383a698a', + atype: 1 + } + ] + }]; + let request = spec.buildRequests(req.bidRequest, req); + + const payload = JSON.parse(request.data); + expect(payload.user.ext.eids[0].source).to.equal('dummy.com'); + expect(payload.user.ext.eids[0].uids[0].id).to.equal('d6d0a86c-20c6-4410-a47b-5cba383a698a'); + expect(payload.user.ext.eids[0].uids[0].atype).to.equal(1); + }); + }); + + context('when mediaType is video', function () { + it('should create a POST request for every bid', function () { + const requests = spec.buildRequests([videoBidRequest], VIDEO_REQUEST); + expect(requests.method).to.equal('POST'); + expect(requests.url.trim()).to.equal(spec.ENDPOINT + '?pid=' + videoBidRequest.params.publisherId + '&nId=' + DEFAULT_NETWORK_ID); + }); + + it('should attach request data', function () { + const requests = spec.buildRequests([videoBidRequest], VIDEO_REQUEST); + const data = JSON.parse(requests.data); + const [width, height] = videoBidRequest.sizes; + const VERSION = '1.0.0'; + expect(data.imp[0].video.w).to.equal(width); + expect(data.imp[0].video.h).to.equal(height); + expect(data.imp[0].bidfloor).to.equal(videoBidRequest.params.bidfloor); + expect(data.ext.prebidver).to.equal('$prebid.version$'); + expect(data.ext.adapterver).to.equal(spec.VERSION); + }); + + it('should set pubId to e2etest when bid.params.e2etest = true', function () { + videoBidRequest.params.e2etest = true; + const requests = spec.buildRequests([videoBidRequest], VIDEO_REQUEST); + expect(requests.method).to.equal('POST'); + expect(requests.url).to.equal(spec.ENDPOINT + '?pid=e2etest&nId=' + DEFAULT_NETWORK_ID); + }); + + it('should attach End 2 End test data', function () { + videoBidRequest.params.e2etest = true; + const requests = spec.buildRequests([videoBidRequest], VIDEO_REQUEST); + const data = JSON.parse(requests.data); + expect(data.imp[0].bidfloor).to.not.exist; + expect(data.imp[0].video.w).to.equal(640); + expect(data.imp[0].video.h).to.equal(480); + }); + }); + }); + + describe('interpretResponse', function () { + context('when mediaType is banner', function () { + it('have bids', function () { + let bids = spec.interpretResponse(RESPONSE, BANNER_REQUEST); + expect(bids).to.be.an('array').that.is.not.empty; + validateBidOnIndex(0); + validateBidOnIndex(1); + + function validateBidOnIndex(index) { + expect(bids[index]).to.have.property('currency', 'USD'); + expect(bids[index]).to.have.property('requestId', RESPONSE.body.seatbid[0].bid[index].impid); + expect(bids[index]).to.have.property('cpm', RESPONSE.body.seatbid[0].bid[index].price); + expect(bids[index]).to.have.property('width', RESPONSE.body.seatbid[0].bid[index].w); + expect(bids[index]).to.have.property('height', RESPONSE.body.seatbid[0].bid[index].h); + expect(bids[index]).to.have.property('ad', RESPONSE.body.seatbid[0].bid[index].adm); + expect(bids[index]).to.have.property('creativeId', RESPONSE.body.seatbid[0].bid[index].crid); + expect(bids[index].meta).to.have.property('advertiserDomains', RESPONSE.body.seatbid[0].bid[index].adomain); + expect(bids[index]).to.have.property('ttl', 300); + expect(bids[index]).to.have.property('netRevenue', true); + } + }); + + it('handles empty response', function () { + const EMPTY_RESP = Object.assign({}, RESPONSE, {'body': {}}); + const bids = spec.interpretResponse(EMPTY_RESP, BANNER_REQUEST); + + expect(bids).to.be.empty; + }); + }); + + context('when mediaType is video', function () { + it('should return no bids if the response is not valid', function () { + const bidResponse = spec.interpretResponse({ + body: null + }, { + videoBidRequest + }); + expect(bidResponse.length).to.equal(0); + }); + + it('should return no bids if the response "nurl" and "adm" are missing', function () { + const serverResponse = { + seatbid: [{ + bid: [{ + price: 6.01 + }] + }] + }; + const bidResponse = spec.interpretResponse({ + body: serverResponse + }, { + videoBidRequest + }); + expect(bidResponse.length).to.equal(0); + }); + + it('should return no bids if the response "price" is missing', function () { + const serverResponse = { + seatbid: [{ + bid: [{ + adm: '' + }] + }] + }; + const bidResponse = spec.interpretResponse({ + body: serverResponse + }, { + videoBidRequest + }); + expect(bidResponse.length).to.equal(0); + }); + + it('should return a valid video bid response with just "adm"', function () { + const serverResponse = { + id: '123', + seatbid: [{ + bid: [{ + id: 1, + adid: 123, + impid: 456, + crid: 2, + price: 6.01, + adm: '', + adomain: [ + 'kulturemedia.com' + ], + w: 640, + h: 480, + ext: { + prebid: { + type: 'video' + }, + } + }] + }], + cur: 'USD' + }; + const bidResponse = spec.interpretResponse({ + body: serverResponse + }, { + videoBidRequest + }); + let o = { + requestId: serverResponse.seatbid[0].bid[0].impid, + ad: '', + cpm: serverResponse.seatbid[0].bid[0].price, + creativeId: serverResponse.seatbid[0].bid[0].crid, + vastXml: serverResponse.seatbid[0].bid[0].adm, + width: 640, + height: 480, + mediaType: 'video', + currency: 'USD', + ttl: 300, + netRevenue: true, + meta: { + advertiserDomains: ['kulturemedia.com'] + } + }; + expect(bidResponse[0]).to.deep.equal(o); + }); + + it('should default ttl to 300', function () { + const serverResponse = { + seatbid: [{bid: [{id: 1, adid: 123, crid: 2, price: 6.01, adm: ''}]}], + cur: 'USD' + }; + const bidResponse = spec.interpretResponse({body: serverResponse}, {videoBidRequest}); + expect(bidResponse[0].ttl).to.equal(300); + }); + it('should not allow ttl above 3601, default to 300', function () { + videoBidRequest.params.video.ttl = 3601; + const serverResponse = { + seatbid: [{bid: [{id: 1, adid: 123, crid: 2, price: 6.01, adm: ''}]}], + cur: 'USD' + }; + const bidResponse = spec.interpretResponse({body: serverResponse}, {videoBidRequest}); + expect(bidResponse[0].ttl).to.equal(300); + }); + it('should not allow ttl below 1, default to 300', function () { + videoBidRequest.params.video.ttl = 0; + const serverResponse = { + seatbid: [{bid: [{id: 1, adid: 123, crid: 2, price: 6.01, adm: ''}]}], + cur: 'USD' + }; + const bidResponse = spec.interpretResponse({body: serverResponse}, {videoBidRequest}); + expect(bidResponse[0].ttl).to.equal(300); + }); + }); + }); + + describe('getUserSyncs', function () { + it('handles no parameters', function () { + let opts = spec.getUserSyncs({}); + expect(opts).to.be.an('array').that.is.empty; + }); + it('returns non if sync is not allowed', function () { + let opts = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: false}); + + expect(opts).to.be.an('array').that.is.empty; + }); + + it('iframe sync enabled should return results', function () { + let opts = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: false}, [RESPONSE]); + + expect(opts.length).to.equal(1); + expect(opts[0].type).to.equal('iframe'); + expect(opts[0].url).to.equal(RESPONSE.body.ext.usersync['sovrn'].syncs[0].url); + }); + + it('pixel sync enabled should return results', function () { + let opts = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: true}, [RESPONSE]); + + expect(opts.length).to.equal(1); + expect(opts[0].type).to.equal('image'); + expect(opts[0].url).to.equal(RESPONSE.body.ext.usersync['appnexus'].syncs[0].url); + }); + + it('all sync enabled should return all results', function () { + let opts = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, [RESPONSE]); + + expect(opts.length).to.equal(2); + }); + }); +}) +; diff --git a/test/spec/modules/lassoBidAdapter_spec.js b/test/spec/modules/lassoBidAdapter_spec.js index 5825927a931..3695889aca0 100644 --- a/test/spec/modules/lassoBidAdapter_spec.js +++ b/test/spec/modules/lassoBidAdapter_spec.js @@ -79,10 +79,12 @@ describe('lassoBidAdapter', function () { }); describe('buildRequests', function () { - const validBidRequests = spec.buildRequests([bid], bidderRequest); - expect(validBidRequests).to.be.an('array').that.is.not.empty; - - const bidRequest = validBidRequests[0]; + let validBidRequests, bidRequest; + before(() => { + validBidRequests = spec.buildRequests([bid], bidderRequest); + expect(validBidRequests).to.be.an('array').that.is.not.empty; + bidRequest = validBidRequests[0]; + }) it('Returns valid bidRequest', function () { expect(bidRequest).to.exist; diff --git a/test/spec/modules/lemmaDigitalBidAdapter_spec.js b/test/spec/modules/lemmaDigitalBidAdapter_spec.js new file mode 100644 index 00000000000..d50728dce3c --- /dev/null +++ b/test/spec/modules/lemmaDigitalBidAdapter_spec.js @@ -0,0 +1,623 @@ +import { expect } from 'chai'; +import { spec } from 'modules/lemmaDigitalBidAdapter.js'; +import * as utils from 'src/utils.js'; +import { config } from 'src/config.js'; +const constants = require('src/constants.json'); + +describe('lemmaDigitalBidAdapter', function () { + let bidRequests; + let videoBidRequests; + let bidResponses; + let videoBidResponse; + let schainConfig; + beforeEach(function () { + schainConfig = { + 'complete': 0, + 'nodes': [ + { + 'asi': 'mobupps.com', + 'sid': 'c74d97b01eae257e44aa9d5bade97baf5149', + 'rid': '79c25703ad5935b0b23b66d210dad1f3', + 'hp': 1 + }, + { + 'asi': 'lemmatechnologies.com', + 'sid': '975', + 'rid': 'a455157a-a1fb-11ed-a0e4-d08e79f7ace0', + 'hp': 1 + } + ] + }; + bidRequests = [{ + bidder: 'lemmadigital', + bidId: '22bddb28db77d', + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 600] + ], + } + }, + params: { + pubId: 1001, + adunitId: 1, + currency: 'AUD', + bidFloor: 1.3, + geo: { + lat: '12.3', + lon: '23.7', + }, + banner: { + w: 300, + h: 250, + }, + tmax: 300, + bcat: ['IAB-26'] + }, + sizes: [ + [300, 250], + [300, 600] + ], + schain: schainConfig + }]; + videoBidRequests = [{ + code: 'video1', + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'instream' + } + }, + bidder: 'lemmadigital', + bidId: '22bddb28db77d', + params: { + pubId: 1001, + adunitId: 1, + bidFloor: 1.3, + tmax: 300, + bcat: ['IAB-26'], + video: { + mimes: ['video/mp4', 'video/x-flv'], + skippable: true, + minduration: 5, + maxduration: 30 + } + }, + schain: schainConfig + }]; + bidResponses = { + 'body': { + 'id': '93D3BAD6-E2E2-49FB-9D89-920B1761C865', + 'seatbid': [{ + 'bid': [{ + 'id': '74858439-49D7-4169-BA5D-44A046315B2F', + 'impid': '22bddb28db77d', + 'price': 1.3, + 'adm': '

lemma"Connecting Advertisers and Publishers directly"

', + 'adomain': ['amazon.com'], + 'iurl': 'https://thetradedesk-t-general.s3.amazonaws.com/AdvertiserLogos/vgl908z.png', + 'cid': '22918', + 'crid': 'v55jutrh', + 'dealid': 'ASEA-MS-KLY-TTD-DESKTOP-ID-VID-6S-030420', + 'h': 250, + 'w': 300, + 'ext': {} + }] + }] + } + }; + videoBidResponse = { + 'body': { + 'id': '93D3BAD6-E2E2-49FB-9D89-920B1761C865', + 'seatbid': [{ + 'bid': [{ + 'id': '74858439-49D7-4169-BA5D-44A046315B2F', + 'impid': '22bddb28db77d', + 'price': 1.3, + 'adm': 'Acudeo CompatibleVAST 2.0 Instream Test 1VAST 2.0 Instream Test 1', + 'adomain': ['amazon.com'], + 'iurl': 'https://thetradedesk-t-general.s3.amazonaws.com/AdvertiserLogos/vgl908z.png', + 'cid': '22918', + 'crid': 'v55jutrh', + 'dealid': 'ASEA-MS-KLY-TTD-DESKTOP-ID-VID-6S-030420', + 'h': 250, + 'w': 300, + 'ext': {} + }] + }] + } + }; + }); + describe('implementation', function () { + describe('Bid validations', function () { + it('valid bid case', function () { + let validBid = { + bidder: 'lemmadigital', + params: { + pubId: 1001, + adunitId: 1 + } + }, + isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(true); + }); + it('invalid bid case', function () { + let isValid = spec.isBidRequestValid(); + expect(isValid).to.equal(false); + }); + it('invalid bid case: pubId not passed', function () { + let validBid = { + bidder: 'lemmadigital', + params: { + adunitId: 1 + } + }, + isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(false); + }); + it('invalid bid case: pubId is not number', function () { + let validBid = { + bidder: 'lemmadigital', + params: { + pubId: '301', + adunitId: 1 + } + }, + isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(false); + }); + it('invalid bid case: adunitId is not passed', function () { + let validBid = { + bidder: 'lemmadigital', + params: { + pubId: 1001 + } + }, + isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(false); + }); + it('invalid bid case: video bid request mimes is not passed', function () { + let validBid = { + bidder: 'lemmadigital', + params: { + pubId: 1001, + adunitId: 1, + video: { + skippable: true, + minduration: 5, + maxduration: 30 + } + } + }, + isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(false); + validBid.params.video.mimes = []; + isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(false); + }); + }); + describe('Request formation', function () { + it('bidRequest check empty', function () { + let bidRequests = []; + let request = spec.buildRequests(bidRequests); + expect(request).to.equal(undefined); + }); + it('buildRequests function should not modify original bidRequests object', function () { + let originalBidRequests = utils.deepClone(bidRequests); + let request = spec.buildRequests(bidRequests); + expect(bidRequests).to.deep.equal(originalBidRequests); + }); + it('bidRequest imp array check empty', function () { + let request = spec.buildRequests(bidRequests); + let data = JSON.parse(request.data); + data.imp = []; + expect(data.imp.length).to.equal(0); + }); + it('Endpoint checking', function () { + let request = spec.buildRequests(bidRequests); + expect(request.url).to.equal('https://bid.lemmadigital.com/lemma/servad?pid=1001&aid=1'); + expect(request.method).to.equal('POST'); + }); + it('Request params check', function () { + let request = spec.buildRequests(bidRequests); + let data = JSON.parse(request.data); + expect(data.site.domain).to.be.a('string'); // domain should be set + expect(data.site.publisher.id).to.equal(bidRequests[0].params.pubId.toString()); // publisher Id + expect(data.imp[0].tagid).to.equal('1'); // tagid + expect(data.imp[0].bidfloorcur).to.equal(bidRequests[0].params.currency); + expect(data.imp[0].bidfloor).to.equal(bidRequests[0].params.bidFloor); + expect(data.source.ext.schain).to.deep.equal(bidRequests[0].schain); + }); + + it('Set sizes from mediaTypes object', function () { + let newBannerRequest = utils.deepClone(bidRequests); + delete newBannerRequest[0].sizes; + let request = spec.buildRequests(newBannerRequest); + let data = JSON.parse(request.data); + expect(data.sizes).to.equal(undefined); + }); + it('Check request banner object present', function () { + let newBannerRequest = utils.deepClone(bidRequests); + let request = spec.buildRequests(newBannerRequest); + let data = JSON.parse(request.data); + expect(data.banner).to.deep.equal(undefined); + }); + it('Check device, source object not present', function () { + let newBannerRequest = utils.deepClone(bidRequests); + delete newBannerRequest[0].schain; + let request = spec.buildRequests(newBannerRequest); + let data = JSON.parse(request.data); + delete data.device; + delete data.source; + expect(data.source).to.equal(undefined); + expect(data.device).to.equal(undefined); + }); + it('Set content from config, set site.content', function () { + let sandbox = sinon.sandbox.create(); + const content = { + 'id': 'alpha-numeric-id' + }; + sandbox.stub(config, 'getConfig').callsFake((key) => { + var config = { + content: content + }; + return config[key]; + }); + let request = spec.buildRequests(bidRequests); + let data = JSON.parse(request.data); + expect(data.site.content).to.deep.equal(content); + sandbox.restore(); + }); + it('Set content from config, set app.content', function () { + let bidRequest = [{ + bidder: 'lemmadigital', + params: { + pubId: 1001, + adunitId: 1, + video: { + skippable: true, + minduration: 5, + maxduration: 30 + }, + app: { + id: 'e0977d04e6bafece57b4b6e93314f10a', + name: 'AMC', + bundle: 'com.roku.amc', + storeurl: 'https://channelstore.roku.com/details/12716/amc', + cat: [ + 'IAB-26' + ], + publisher: { + 'id': '975' + } + }, + } + }]; + let sandbox = sinon.sandbox.create(); + const content = { + 'id': 'alpha-numeric-id' + }; + sandbox.stub(config, 'getConfig').callsFake((key) => { + var config = { + content: content + }; + return config[key]; + }); + let request = spec.buildRequests(bidRequest); + let data = JSON.parse(request.data); + expect(data.app.content).to.deep.equal(content); + sandbox.restore(); + }); + it('Set tmax from requestBids method', function () { + let request = spec.buildRequests(bidRequests); + let data = JSON.parse(request.data); + expect(data.tmax).to.deep.equal(300); + }); + it('Request params check without mediaTypes object', function () { + let bidRequests = [{ + bidder: 'lemmadigital', + params: { + pubId: 1001, + adunitId: 1, + currency: 'AUD' + }, + sizes: [ + [300, 250], + [300, 600] + ] + }]; + let request = spec.buildRequests(bidRequests); + let data = JSON.parse(request.data); + expect(data.imp[0].banner.w).to.equal(300); // width + expect(data.imp[0].banner.h).to.equal(250); // height + expect(data.imp[0].banner.format).exist.and.to.be.an('array'); + expect(data.imp[0].banner.format[0]).exist.and.to.be.an('object'); + expect(data.imp[0].banner.format[0].w).to.equal(300); // width + expect(data.imp[0].banner.format[0].h).to.equal(600); // height + }); + it('Request params check: without tagId', function () { + delete bidRequests[0].params.adunitId; + let request = spec.buildRequests(bidRequests); + let data = JSON.parse(request.data); + expect(data.site.domain).to.be.a('string'); // domain should be set + expect(data.site.publisher.id).to.equal(bidRequests[0].params.pubId.toString()); // publisher Id + expect(data.imp[0].tagid).to.equal(undefined); // tagid + expect(data.imp[0].bidfloorcur).to.equal(bidRequests[0].params.currency); + expect(data.imp[0].bidfloor).to.equal(bidRequests[0].params.bidFloor); + }); + it('Request params multi size format object check', function () { + let bidRequests = [{ + bidder: 'lemmadigital', + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 600] + ], + } + }, + params: { + pubId: 1001, + adunitId: 1, + currency: 'AUD' + }, + sizes: [ + [300, 250], + [300, 600] + ] + }]; + /* case 1 - size passed in adslot */ + let request = spec.buildRequests(bidRequests); + let data = JSON.parse(request.data); + expect(data.imp[0].banner.w).to.equal(300); // width + expect(data.imp[0].banner.h).to.equal(250); // height + /* case 2 - size passed in adslot as well as in sizes array */ + bidRequests[0].sizes = [ + [300, 600], + [300, 250] + ]; + bidRequests[0].mediaTypes = { + banner: { + sizes: [ + [300, 600], + [300, 250] + ] + } + }; + request = spec.buildRequests(bidRequests); + data = JSON.parse(request.data); + expect(data.imp[0].banner.w).to.equal(300); // width + expect(data.imp[0].banner.h).to.equal(600); // height + /* case 3 - size passed in sizes but not in adslot */ + bidRequests[0].params.adunitId = 1; + bidRequests[0].sizes = [ + [300, 250], + [300, 600] + ]; + bidRequests[0].mediaTypes = { + banner: { + sizes: [ + [300, 250], + [300, 600] + ] + } + }; + request = spec.buildRequests(bidRequests); + data = JSON.parse(request.data); + expect(data.imp[0].banner.w).to.equal(300); // width + expect(data.imp[0].banner.h).to.equal(250); // height + expect(data.imp[0].banner.format).exist.and.to.be.an('array'); + expect(data.imp[0].banner.format[0]).exist.and.to.be.an('object'); + expect(data.imp[0].banner.format[0].w).to.equal(300); // width + expect(data.imp[0].banner.format[0].h).to.equal(250); // height + }); + it('Request params currency check', function () { + let bidRequest = [{ + bidder: 'lemmadigital', + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 600] + ], + } + }, + params: { + pubId: 1001, + adunitId: 1, + currency: 'AUD' + }, + sizes: [ + [300, 250], + [300, 600] + ] + }]; + /* case 1 - + currency specified in adunits + output: imp[0] use currency specified in bidRequests[0].params.currency + */ + let request = spec.buildRequests(bidRequest); + let data = JSON.parse(request.data); + expect(data.imp[0].bidfloorcur).to.equal(bidRequests[0].params.currency); + /* case 2 - + currency specified in adunit + output: imp[0] use default currency - USD + */ + delete bidRequest[0].params.currency; + request = spec.buildRequests(bidRequest); + data = JSON.parse(request.data); + expect(data.imp[0].bidfloorcur).to.equal('USD'); + }); + it('Request params check for video ad', function () { + let request = spec.buildRequests(videoBidRequests); + let data = JSON.parse(request.data); + expect(data.imp[0].video).to.exist; + expect(data.imp[0].tagid).to.equal('1'); + expect(data.imp[0]['video']['mimes']).to.exist.and.to.be.an('array'); + expect(data.imp[0]['video']['mimes'][0]).to.equal(videoBidRequests[0].params.video['mimes'][0]); + expect(data.imp[0]['video']['mimes'][1]).to.equal(videoBidRequests[0].params.video['mimes'][1]); + expect(data.imp[0]['video']['minduration']).to.equal(videoBidRequests[0].params.video['minduration']); + expect(data.imp[0]['video']['maxduration']).to.equal(videoBidRequests[0].params.video['maxduration']); + expect(data.imp[0]['video']['w']).to.equal(videoBidRequests[0].mediaTypes.video.playerSize[0]); + expect(data.imp[0]['video']['h']).to.equal(videoBidRequests[0].mediaTypes.video.playerSize[1]); + expect(data.source.ext.schain).to.deep.equal(videoBidRequests[0].schain); + }); + describe('setting imp.floor using floorModule', function () { + /* + Use the minimum value among floor from floorModule per mediaType + If params.bidFloor is set then take max(floor, min(floors from floorModule)) + set imp.bidfloor only if it is more than 0 + */ + + let newRequest; + let floorModuleTestData; + let getFloor = function (req) { + return floorModuleTestData[req.mediaType]; + }; + + beforeEach(() => { + floorModuleTestData = { + 'banner': { + 'currency': 'AUD', + 'floor': 1.50 + }, + 'video': { + 'currency': 'AUD', + 'floor': 2.00 + } + }; + newRequest = utils.deepClone(bidRequests); + newRequest[0].getFloor = getFloor; + }); + + it('bidfloor should be undefined if calculation is <= 0', function () { + floorModuleTestData.banner.floor = 0; // lowest of them all + newRequest[0].params.bidFloor = undefined; + let request = spec.buildRequests(newRequest); + let data = JSON.parse(request.data); + data = data.imp[0]; + expect(data.bidfloor).to.equal(undefined); + }); + + it('ignore floormodule o/p if floor is not number', function () { + floorModuleTestData.banner.floor = 'INR'; + newRequest[0].params.bidFloor = undefined; + let request = spec.buildRequests(newRequest); + let data = JSON.parse(request.data); + data = data.imp[0]; + expect(data.bidfloor).to.equal(undefined); // video will be lowest now + }); + + it('ignore floormodule o/p if currency is not matched', function () { + floorModuleTestData.banner.currency = 'INR'; + newRequest[0].params.bidFloor = undefined; + let request = spec.buildRequests(newRequest); + let data = JSON.parse(request.data); + data = data.imp[0]; + expect(data.bidfloor).to.equal(undefined); // video will be lowest now + }); + + it('bidFloor is not passed, use minimum from floorModule', function () { + newRequest[0].params.bidFloor = undefined; + let request = spec.buildRequests(newRequest); + let data = JSON.parse(request.data); + data = data.imp[0]; + expect(data.bidfloor).to.equal(1.5); + }); + + it('bidFloor is passed as 1, use min of floorModule as it is highest', function () { + newRequest[0].params.bidFloor = '1.0';// yes, we want it as a string + let request = spec.buildRequests(newRequest); + let data = JSON.parse(request.data); + data = data.imp[0]; + expect(data.bidfloor).to.equal(1.5); + }); + }); + describe('Response checking', function () { + it('should check for valid response values', function () { + let request = spec.buildRequests(bidRequests); + let response = spec.interpretResponse(bidResponses, request); + expect(response).to.be.an('array').with.length.above(0); + expect(response[0].requestId).to.equal(bidResponses.body.seatbid[0].bid[0].impid); + expect(response[0].cpm).to.equal((bidResponses.body.seatbid[0].bid[0].price).toFixed(2)); + expect(response[0].width).to.equal(bidResponses.body.seatbid[0].bid[0].w); + expect(response[0].height).to.equal(bidResponses.body.seatbid[0].bid[0].h); + if (bidResponses.body.seatbid[0].bid[0].crid) { + expect(response[0].creativeId).to.equal(bidResponses.body.seatbid[0].bid[0].crid); + } else { + expect(response[0].creativeId).to.equal(bidResponses.body.seatbid[0].bid[0].id); + } + expect(response[0].dealId).to.equal(bidResponses.body.seatbid[0].bid[0].dealid); + expect(response[0].currency).to.equal('USD'); + expect(response[0].netRevenue).to.equal(false); + expect(response[0].ttl).to.equal(300); + }); + it('should check for valid banner mediaType in request', function () { + let request = spec.buildRequests(bidRequests); + let response = spec.interpretResponse(bidResponses, request); + + expect(response[0].mediaType).to.equal('banner'); + }); + it('should check for valid video mediaType in request', function () { + let request = spec.buildRequests(videoBidRequests); + let response = spec.interpretResponse(videoBidResponse, request); + + expect(response[0].mediaType).to.equal('video'); + }); + }); + }); + describe('Video request params', function () { + let sandbox, utilsMock, newVideoRequest; + beforeEach(() => { + utilsMock = sinon.mock(utils); + sandbox = sinon.sandbox.create(); + sandbox.spy(utils, 'logWarn'); + newVideoRequest = utils.deepClone(videoBidRequests); + }); + + afterEach(() => { + utilsMock.restore(); + sandbox.restore(); + }); + + it('Video params from mediaTypes and params obj of bid are not present', function () { + delete newVideoRequest[0].mediaTypes.video; + delete newVideoRequest[0].params.video; + let request = spec.buildRequests(newVideoRequest); + expect(request).to.equal(undefined); + }); + + it('Should consider video params from mediaType object of bid', function () { + delete newVideoRequest[0].params.video; + + let request = spec.buildRequests(newVideoRequest); + let data = JSON.parse(request.data); + expect(data.imp[0].video).to.exist; + expect(data.imp[0]['video']['w']).to.equal(videoBidRequests[0].mediaTypes.video.playerSize[0]); + expect(data.imp[0]['video']['h']).to.equal(videoBidRequests[0].mediaTypes.video.playerSize[1]); + expect(data.imp[0]['video']['battr']).to.equal(undefined); + }); + }); + describe('getUserSyncs', function () { + const syncurl_iframe = 'https://sync.lemmadigital.com/js/usersync.html?pid=1001'; + let sandbox; + beforeEach(function () { + sandbox = sinon.sandbox.create(); + }); + afterEach(function () { + sandbox.restore(); + }); + + it('execute as per config', function () { + expect(spec.getUserSyncs({ iframeEnabled: true }, {}, undefined, undefined)).to.deep.equal([{ + type: 'iframe', url: syncurl_iframe + }]); + }); + + it('not execute as per config', function () { + expect(spec.getUserSyncs({ iframeEnabled: false }, {}, undefined, undefined)).to.deep.equal(undefined); + }); + }); + }); +}); diff --git a/test/spec/modules/limelightDigitalBidAdapter_spec.js b/test/spec/modules/limelightDigitalBidAdapter_spec.js index 5a92110abb4..0e6f4817e5e 100644 --- a/test/spec/modules/limelightDigitalBidAdapter_spec.js +++ b/test/spec/modules/limelightDigitalBidAdapter_spec.js @@ -24,7 +24,11 @@ describe('limelightDigitalAdapter', function () { sizes: [[300, 250]] } }, - transactionId: '3bb2f6da-87a6-4029-aeb0-bfe951372e62', + ortb2Imp: { + ext: { + tid: '3bb2f6da-87a6-4029-aeb0-bfe951372e62', + } + }, userIdAsEids: [ { source: 'test1.org', @@ -64,7 +68,11 @@ describe('limelightDigitalAdapter', function () { placementCode: 'placement_1', auctionId: '482f88de-29ab-45c8-981a-d25e39454a34', sizes: [[350, 200]], - transactionId: '068867d1-46ec-40bb-9fa0-e24611786fb4', + ortb2Imp: { + ext: { + tid: '068867d1-46ec-40bb-9fa0-e24611786fb4', + } + }, userIdAsEids: [ { source: 'test2.org', @@ -110,7 +118,11 @@ describe('limelightDigitalAdapter', function () { placementCode: 'placement_2', auctionId: 'e4771143-6aa7-41ec-8824-ced4342c96c8', sizes: [[800, 600]], - transactionId: '738d5915-6651-43b9-9b6b-d50517350917', + ortb2Imp: { + ext: { + tid: '738d5915-6651-43b9-9b6b-d50517350917', + } + }, userIdAsEids: [ { source: 'test3.org', @@ -155,7 +167,11 @@ describe('limelightDigitalAdapter', function () { video: { playerSize: [800, 600] }, - transactionId: '738d5915-6651-43b9-9b6b-d50517350917', + ortb2Imp: { + ext: { + tid: '738d5915-6651-43b9-9b6b-d50517350917', + } + }, userIdAsEids: [ { source: 'test.org', @@ -647,7 +663,7 @@ function validateAdUnit(adUnit, bid) { expect(adUnit.id).to.equal(bid.params.adUnitId); expect(adUnit.bidId).to.equal(bid.bidId); expect(adUnit.type).to.equal(bid.params.adUnitType.toUpperCase()); - expect(adUnit.transactionId).to.equal(bid.transactionId); + expect(adUnit.transactionId).to.equal(bid.ortb2Imp.ext.tid); let bidSizes = []; if (bid.mediaTypes) { if (bid.mediaTypes.video && bid.mediaTypes.video.playerSize) { diff --git a/test/spec/modules/liveIntentIdMinimalSystem_spec.js b/test/spec/modules/liveIntentIdMinimalSystem_spec.js index 6a5afa58da2..92a15241e3a 100644 --- a/test/spec/modules/liveIntentIdMinimalSystem_spec.js +++ b/test/spec/modules/liveIntentIdMinimalSystem_spec.js @@ -73,6 +73,34 @@ describe('LiveIntentMinimalId', function() { expect(callBackSpy.calledOnce).to.be.true; }); + it('should call the Identity Exchange endpoint with the privided distributorId', function() { + getCookieStub.returns(null); + let callBackSpy = sinon.spy(); + let submoduleCallback = liveIntentIdSubmodule.getId({ params: { fireEventDelay: 1, distributorId: 'did-1111' } }).callback; + submoduleCallback(callBackSpy); + let request = server.requests[0]; + expect(request.url).to.be.eq('https://idx.liadm.com/idex/did-1111/any?did=did-1111&resolve=nonId'); + request.respond( + 204, + responseHeader + ); + expect(callBackSpy.calledOnceWith({})).to.be.true; + }); + + it('should call the Identity Exchange endpoint without the privided distributorId when appId is provided', function() { + getCookieStub.returns(null); + let callBackSpy = sinon.spy(); + let submoduleCallback = liveIntentIdSubmodule.getId({ params: { fireEventDelay: 1, distributorId: 'did-1111', liCollectConfig: { appId: 'a-0001' } } }).callback; + submoduleCallback(callBackSpy); + let request = server.requests[0]; + expect(request.url).to.be.eq('https://idx.liadm.com/idex/prebid/any?resolve=nonId'); + request.respond( + 204, + responseHeader + ); + expect(callBackSpy.calledOnceWith({})).to.be.true; + }); + it('should call the default url of the LiveIntent Identity Exchange endpoint, with a partner', function() { getCookieStub.returns(null); let callBackSpy = sinon.spy(); @@ -215,12 +243,27 @@ describe('LiveIntentMinimalId', function() { it('should decode a uid2 to a seperate object when present', function() { const result = liveIntentIdSubmodule.decode({ nonId: 'foo', uid2: 'bar' }); - expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'uid2': 'bar'}, 'uid2': {'id': 'bar'}}); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'uid2': 'bar'}, 'uid2': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); it('should decode values with uid2 but no nonId', function() { const result = liveIntentIdSubmodule.decode({ uid2: 'bar' }); - expect(result).to.eql({'uid2': {'id': 'bar'}}); + expect(result).to.eql({'uid2': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); + }); + + it('should decode a bidswitch id to a seperate object when present', function() { + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', bidswitch: 'bar' }); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'bidswitch': 'bar'}, 'bidswitch': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); + }); + + it('should decode a medianet id to a seperate object when present', function() { + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', medianet: 'bar' }); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'medianet': 'bar'}, 'medianet': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); + }); + + it('should decode a magnite id to a seperate object when present', function() { + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', magnite: 'bar' }); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'magnite': 'bar'}, 'magnite': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); it('should allow disabling nonId resolution', function() { diff --git a/test/spec/modules/liveIntentIdSystem_spec.js b/test/spec/modules/liveIntentIdSystem_spec.js index 3c22cda1154..6b7dfb82d48 100644 --- a/test/spec/modules/liveIntentIdSystem_spec.js +++ b/test/spec/modules/liveIntentIdSystem_spec.js @@ -5,7 +5,7 @@ import { server } from 'test/mocks/xhr.js'; resetLiveIntentIdSubmodule(); liveIntentIdSubmodule.setModuleMode('standard') const PUBLISHER_ID = '89899'; -const defaultConfigParams = { params: {publisherId: PUBLISHER_ID} }; +const defaultConfigParams = { params: {publisherId: PUBLISHER_ID, fireEventDelay: 1} }; const responseHeader = {'Content-Type': 'application/json'} describe('LiveIntentId', function() { @@ -45,7 +45,7 @@ describe('LiveIntentId', function() { let callBackSpy = sinon.spy(); let submoduleCallback = liveIntentIdSubmodule.getId(defaultConfigParams).callback; submoduleCallback(callBackSpy); - let request = server.requests[1]; + let request = server.requests[0]; expect(request.url).to.match(/.*us_privacy=1YNY.*&gdpr=1&n3pc=1&gdpr_consent=consentDataString.*/); const response = { unifiedId: 'a_unified_id', @@ -59,25 +59,31 @@ describe('LiveIntentId', function() { expect(callBackSpy.calledOnceWith(response)).to.be.true; }); - it('should fire an event when getId', function() { + it('should fire an event when getId', function(done) { uspConsentDataStub.returns('1YNY'); gdprConsentDataStub.returns({ gdprApplies: true, consentString: 'consentDataString' }) liveIntentIdSubmodule.getId(defaultConfigParams); - expect(server.requests[0].url).to.match(/https:\/\/rp.liadm.com\/j\?.*&us_privacy=1YNY.*&wpn=prebid.*&gdpr=1&n3pc=1&n3pct=1&nb=1&gdpr_consent=consentDataString.*/); + setTimeout(() => { + expect(server.requests[0].url).to.match(/https:\/\/rp.liadm.com\/j\?.*&us_privacy=1YNY.*&wpn=prebid.*&gdpr=1&n3pc=1&n3pct=1&nb=1&gdpr_consent=consentDataString.*/); + done(); + }, 200); }); - it('should fire an event when getId and a hash is provided', function() { + it('should fire an event when getId and a hash is provided', function(done) { liveIntentIdSubmodule.getId({ params: { ...defaultConfigParams, emailHash: '58131bc547fb87af94cebdaf3102321f' }}); - expect(server.requests[0].url).to.match(/https:\/\/rp.liadm.com\/j\?.*e=58131bc547fb87af94cebdaf3102321f.+/) + setTimeout(() => { + expect(server.requests[0].url).to.match(/https:\/\/rp.liadm.com\/j\?.*e=58131bc547fb87af94cebdaf3102321f.+/) + done(); + }, 200); }); - it('should initialize LiveConnect with the config params when decode and emit an event', function () { + it('should initialize LiveConnect with the config params when decode and emit an event', function (done) { liveIntentIdSubmodule.decode({}, { params: { ...defaultConfigParams.params, ...{ @@ -88,25 +94,51 @@ describe('LiveIntentId', function() { } } }}); - expect(server.requests[0].url).to.match(/https:\/\/collector.liveintent.com\/j\?.*aid=a-0001.*&wpn=prebid.*/); + setTimeout(() => { + expect(server.requests[0].url).to.match(/https:\/\/collector.liveintent.com\/j\?.*aid=a-0001.*&wpn=prebid.*/); + done(); + }, 200); }); - it('should initialize LiveConnect and emit an event with a privacy string when decode', function() { + it('should fire an event with the provided distributorId', function (done) { + liveIntentIdSubmodule.decode({}, { params: { fireEventDelay: 1, distributorId: 'did-1111' } }); + setTimeout(() => { + expect(server.requests[0].url).to.match(/https:\/\/rp.liadm.com\/j\?.*did=did-1111.*&wpn=prebid.*/); + done(); + }, 200); + }); + + it('should fire an event without the provided distributorId when appId is provided', function (done) { + liveIntentIdSubmodule.decode({}, { params: { fireEventDelay: 1, distributorId: 'did-1111', liCollectConfig: { appId: 'a-0001' } } }); + setTimeout(() => { + expect(server.requests[0].url).to.match(/https:\/\/rp.liadm.com\/j\?.*aid=a-0001.*&wpn=prebid.*/); + expect(server.requests[0].url).to.not.match(/.*did=*/); + done(); + }, 200); + }); + + it('should initialize LiveConnect and emit an event with a privacy string when decode', function(done) { uspConsentDataStub.returns('1YNY'); gdprConsentDataStub.returns({ gdprApplies: false, consentString: 'consentDataString' }) liveIntentIdSubmodule.decode({}, defaultConfigParams); - expect(server.requests[0].url).to.match(/.*us_privacy=1YNY.*&gdpr=0&gdpr_consent=consentDataString.*/); + setTimeout(() => { + expect(server.requests[0].url).to.match(/.*us_privacy=1YNY.*&gdpr=0&gdpr_consent=consentDataString.*/); + done(); + }, 200); }); - it('should fire an event when decode and a hash is provided', function() { + it('should fire an event when decode and a hash is provided', function(done) { liveIntentIdSubmodule.decode({}, { params: { ...defaultConfigParams.params, emailHash: '58131bc547fb87af94cebdaf3102321f' }}); - expect(server.requests[0].url).to.match(/https:\/\/rp.liadm.com\/j\?.*e=58131bc547fb87af94cebdaf3102321f.+/); + setTimeout(() => { + expect(server.requests[0].url).to.match(/https:\/\/rp.liadm.com\/j\?.*e=58131bc547fb87af94cebdaf3102321f.+/); + done(); + }, 200); }); it('should not return a decoded identifier when the unifiedId is not present in the value', function() { @@ -114,25 +146,31 @@ describe('LiveIntentId', function() { expect(result).to.be.eql({}); }); - it('should fire an event when decode', function() { + it('should fire an event when decode', function(done) { liveIntentIdSubmodule.decode({}, defaultConfigParams); - expect(server.requests[0].url).to.be.not.null + setTimeout(() => { + expect(server.requests[0].url).to.be.not.null + done(); + }, 200); }); - it('should initialize LiveConnect and send data only once', function() { + it('should initialize LiveConnect and send data only once', function(done) { liveIntentIdSubmodule.getId(defaultConfigParams); liveIntentIdSubmodule.decode({}, defaultConfigParams); liveIntentIdSubmodule.getId(defaultConfigParams); liveIntentIdSubmodule.decode({}, defaultConfigParams); - expect(server.requests.length).to.be.eq(1); + setTimeout(() => { + expect(server.requests.length).to.be.eq(1); + done(); + }, 200); }); - it('should call the Custom URL of the LiveIntent Identity Exchange endpoint', function() { + it('should call the custom URL of the LiveIntent Identity Exchange endpoint', function() { getCookieStub.returns(null); let callBackSpy = sinon.spy(); let submoduleCallback = liveIntentIdSubmodule.getId({ params: {...defaultConfigParams.params, ...{'url': 'https://dummy.liveintent.com/idex'}} }).callback; submoduleCallback(callBackSpy); - let request = server.requests[1]; + let request = server.requests[0]; expect(request.url).to.be.eq('https://dummy.liveintent.com/idex/prebid/89899?resolve=nonId'); request.respond( 204, @@ -141,6 +179,34 @@ describe('LiveIntentId', function() { expect(callBackSpy.calledOnceWith({})).to.be.true; }); + it('should call the Identity Exchange endpoint with the privided distributorId', function() { + getCookieStub.returns(null); + let callBackSpy = sinon.spy(); + let submoduleCallback = liveIntentIdSubmodule.getId({ params: { fireEventDelay: 1, distributorId: 'did-1111' } }).callback; + submoduleCallback(callBackSpy); + let request = server.requests[0]; + expect(request.url).to.be.eq('https://idx.liadm.com/idex/did-1111/any?did=did-1111&resolve=nonId'); + request.respond( + 204, + responseHeader + ); + expect(callBackSpy.calledOnceWith({})).to.be.true; + }); + + it('should call the Identity Exchange endpoint without the privided distributorId when appId is provided', function() { + getCookieStub.returns(null); + let callBackSpy = sinon.spy(); + let submoduleCallback = liveIntentIdSubmodule.getId({ params: { fireEventDelay: 1, distributorId: 'did-1111', liCollectConfig: { appId: 'a-0001' } } }).callback; + submoduleCallback(callBackSpy); + let request = server.requests[0]; + expect(request.url).to.be.eq('https://idx.liadm.com/idex/prebid/any?resolve=nonId'); + request.respond( + 204, + responseHeader + ); + expect(callBackSpy.calledOnceWith({})).to.be.true; + }); + it('should call the default url of the LiveIntent Identity Exchange endpoint, with a partner', function() { getCookieStub.returns(null); let callBackSpy = sinon.spy(); @@ -152,7 +218,7 @@ describe('LiveIntentId', function() { } } }).callback; submoduleCallback(callBackSpy); - let request = server.requests[1]; + let request = server.requests[0]; expect(request.url).to.be.eq('https://dummy.liveintent.com/idex/rubicon/89899?resolve=nonId'); request.respond( 200, @@ -167,7 +233,7 @@ describe('LiveIntentId', function() { let callBackSpy = sinon.spy(); let submoduleCallback = liveIntentIdSubmodule.getId(defaultConfigParams).callback; submoduleCallback(callBackSpy); - let request = server.requests[1]; + let request = server.requests[0]; expect(request.url).to.be.eq('https://idx.liadm.com/idex/prebid/89899?resolve=nonId'); request.respond( 200, @@ -182,7 +248,7 @@ describe('LiveIntentId', function() { let callBackSpy = sinon.spy(); let submoduleCallback = liveIntentIdSubmodule.getId(defaultConfigParams).callback; submoduleCallback(callBackSpy); - let request = server.requests[1]; + let request = server.requests[0]; expect(request.url).to.be.eq('https://idx.liadm.com/idex/prebid/89899?resolve=nonId'); request.respond( 503, @@ -199,7 +265,7 @@ describe('LiveIntentId', function() { let callBackSpy = sinon.spy(); let submoduleCallback = liveIntentIdSubmodule.getId(defaultConfigParams).callback; submoduleCallback(callBackSpy); - let request = server.requests[1]; + let request = server.requests[0]; expect(request.url).to.be.eq(`https://idx.liadm.com/idex/prebid/89899?duid=${oldCookie}&resolve=nonId`); request.respond( 200, @@ -222,7 +288,7 @@ describe('LiveIntentId', function() { let callBackSpy = sinon.spy(); let submoduleCallback = liveIntentIdSubmodule.getId(configParams).callback; submoduleCallback(callBackSpy); - let request = server.requests[1]; + let request = server.requests[0]; expect(request.url).to.be.eq(`https://idx.liadm.com/idex/prebid/89899?duid=${oldCookie}&_thirdPC=third-pc&resolve=nonId`); request.respond( 200, @@ -244,7 +310,7 @@ describe('LiveIntentId', function() { let callBackSpy = sinon.spy(); let submoduleCallback = liveIntentIdSubmodule.getId(configParams).callback; submoduleCallback(callBackSpy); - let request = server.requests[1]; + let request = server.requests[0]; expect(request.url).to.be.eq('https://idx.liadm.com/idex/prebid/89899?_thirdPC=%7B%22key%22%3A%22value%22%7D&resolve=nonId'); request.respond( 200, @@ -277,7 +343,7 @@ describe('LiveIntentId', function() { ...{ requestedAttributesOverrides: { 'foo': true, 'bar': false } } } }).callback; submoduleCallback(callBackSpy); - let request = server.requests[1]; + let request = server.requests[0]; expect(request.url).to.be.eq(`https://idx.liadm.com/idex/prebid/89899?resolve=nonId&resolve=foo`); request.respond( 200, @@ -289,12 +355,27 @@ describe('LiveIntentId', function() { it('should decode a uid2 to a seperate object when present', function() { const result = liveIntentIdSubmodule.decode({ nonId: 'foo', uid2: 'bar' }); - expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'uid2': 'bar'}, 'uid2': {'id': 'bar'}}); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'uid2': 'bar'}, 'uid2': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); it('should decode values with uid2 but no nonId', function() { const result = liveIntentIdSubmodule.decode({ uid2: 'bar' }); - expect(result).to.eql({'uid2': {'id': 'bar'}}); + expect(result).to.eql({'uid2': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); + }); + + it('should decode a bidswitch id to a seperate object when present', function() { + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', bidswitch: 'bar' }); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'bidswitch': 'bar'}, 'bidswitch': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); + }); + + it('should decode a medianet id to a seperate object when present', function() { + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', medianet: 'bar' }); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'medianet': 'bar'}, 'medianet': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); + }); + + it('should decode a magnite id to a seperate object when present', function() { + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', magnite: 'bar' }); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'magnite': 'bar'}, 'magnite': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); it('should allow disabling nonId resolution', function() { @@ -304,7 +385,7 @@ describe('LiveIntentId', function() { ...{ requestedAttributesOverrides: { 'nonId': false, 'uid2': true } } } }).callback; submoduleCallback(callBackSpy); - let request = server.requests[1]; + let request = server.requests[0]; expect(request.url).to.be.eq(`https://idx.liadm.com/idex/prebid/89899?resolve=uid2`); request.respond( 200, diff --git a/test/spec/modules/livewrappedAnalyticsAdapter_spec.js b/test/spec/modules/livewrappedAnalyticsAdapter_spec.js index bab7a68287d..d07b48752c6 100644 --- a/test/spec/modules/livewrappedAnalyticsAdapter_spec.js +++ b/test/spec/modules/livewrappedAnalyticsAdapter_spec.js @@ -71,7 +71,7 @@ const BID3 = { auctionId: '25c6d7f5-699a-4bfc-87c9-996f915341fa', mediaType: 'banner', getStatusCode() { - return CONSTANTS.STATUS.NO_BID; + return CONSTANTS.STATUS.GOOD; } }; diff --git a/test/spec/modules/livewrappedBidAdapter_spec.js b/test/spec/modules/livewrappedBidAdapter_spec.js index 6bbdf6e1705..52eaf8d7d76 100644 --- a/test/spec/modules/livewrappedBidAdapter_spec.js +++ b/test/spec/modules/livewrappedBidAdapter_spec.js @@ -36,6 +36,11 @@ describe('Livewrapped adapter tests', function () { bidId: '2ffb201a808da7', bidderRequestId: '178e34bad3658f', auctionId: 'F7557995-65F5-4682-8782-7D5D34D82A8C', + ortb2Imp: { + ext: { + tid: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D' + } + }, transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D' } ], @@ -1211,7 +1216,6 @@ describe('Livewrapped adapter tests', function () { let expectedResponse = [{ requestId: '32e50fad901ae89', - bidderCode: 'livewrapped', cpm: 2.565917, width: 300, height: 250, @@ -1251,7 +1255,6 @@ describe('Livewrapped adapter tests', function () { let expectedResponse = [{ requestId: '32e50fad901ae89', - bidderCode: 'livewrapped', cpm: 2.565917, width: 300, height: 250, @@ -1293,7 +1296,6 @@ describe('Livewrapped adapter tests', function () { let expectedResponse = [{ requestId: '32e50fad901ae89', - bidderCode: 'livewrapped', cpm: 2.565917, width: 300, height: 250, @@ -1347,7 +1349,6 @@ describe('Livewrapped adapter tests', function () { let expectedResponse = [{ requestId: '32e50fad901ae89', - bidderCode: 'livewrapped', cpm: 2.565917, width: 300, height: 250, @@ -1359,7 +1360,6 @@ describe('Livewrapped adapter tests', function () { meta: undefined }, { requestId: '42e50fad901ae89', - bidderCode: 'livewrapped', cpm: 3.565917, width: 980, height: 240, @@ -1398,7 +1398,6 @@ describe('Livewrapped adapter tests', function () { let expectedResponse = [{ requestId: '32e50fad901ae89', - bidderCode: 'livewrapped', cpm: 2.565917, width: 300, height: 250, diff --git a/test/spec/modules/liveyieldAnalyticsAdapter_spec.js b/test/spec/modules/liveyieldAnalyticsAdapter_spec.js deleted file mode 100644 index 9ec4eaf9acd..00000000000 --- a/test/spec/modules/liveyieldAnalyticsAdapter_spec.js +++ /dev/null @@ -1,704 +0,0 @@ -import CONSTANTS from 'src/constants.json'; -import liveyield from 'modules/liveyieldAnalyticsAdapter.js'; -import { expect } from 'chai'; -const events = require('src/events'); - -const { - EVENTS: { BID_REQUESTED, BID_TIMEOUT, BID_RESPONSE, BID_WON } -} = CONSTANTS; - -describe('liveyield analytics adapter', function() { - const rtaCalls = []; - - window.rta = function() { - rtaCalls.push({ callArgs: arguments }); - }; - - beforeEach(function() { - sinon.stub(events, 'getEvents').returns([]); - }); - afterEach(function() { - events.getEvents.restore(); - }); - - describe('initialization', function() { - afterEach(function() { - rtaCalls.length = 0; - }); - it('it should require provider', function() { - liveyield.enableAnalytics({}); - expect(rtaCalls).to.be.empty; - }); - it('should require config.options', function() { - liveyield.enableAnalytics({ provider: 'liveyield' }); - expect(rtaCalls).to.be.empty; - }); - it('should require options.customerId', function() { - liveyield.enableAnalytics({ provider: 'liveyield', options: {} }); - expect(rtaCalls).to.be.empty; - }); - it('should require options.customerName', function() { - liveyield.enableAnalytics({ - provider: 'liveyield', - options: { - customerId: 'd6a6f8da-190f-47d6-ae11-f1a4469083fa' - } - }); - expect(rtaCalls).to.be.empty; - }); - it('should require options.customerSite', function() { - liveyield.enableAnalytics({ - provider: 'liveyield', - options: { - customerId: 'd6a6f8da-190f-47d6-ae11-f1a4469083fa', - customerName: 'pubocean' - } - }); - expect(rtaCalls).to.be.empty; - }); - it('should require options.sessionTimezoneOffset', function() { - liveyield.enableAnalytics({ - provider: 'liveyield', - options: { - customerId: 'd6a6f8da-190f-47d6-ae11-f1a4469083fa', - customerName: 'pubocean', - customerSite: 'scribol.com' - } - }); - expect(rtaCalls).to.be.empty; - }); - it("should throw error, when 'rta' function is not defined ", function() { - const keepMe = window.rta; - - delete window.rta; - - liveyield.enableAnalytics({ - provider: 'liveyield', - options: { - customerId: 'd6a6f8da-190f-47d6-ae11-f1a4469083fa', - customerName: 'pubocean', - customerSite: 'scribol.com', - sessionTimezoneOffset: 12 - } - }); - expect(rtaCalls).to.be.empty; - - window.rta = keepMe; - }); - it('should initialize when all required parameters are passed', function() { - liveyield.enableAnalytics({ - provider: 'liveyield', - options: { - customerId: 'd6a6f8da-190f-47d6-ae11-f1a4469083fa', - customerName: 'pubocean', - customerSite: 'scribol.com', - sessionTimezoneOffset: 12 - } - }); - expect(rtaCalls[0].callArgs['0']).to.match(/create/); - expect(rtaCalls[0].callArgs['1']).to.match( - /d6a6f8da-190f-47d6-ae11-f1a4469083fa/ - ); - expect(rtaCalls[0].callArgs['2']).to.match(/pubocean/); - expect(rtaCalls[0].callArgs['4']).to.match(/12/); - liveyield.disableAnalytics(); - }); - it('should allow to redefine rta function name', function() { - const keepMe = window.rta; - window.abc = keepMe; - delete window.rta; - liveyield.enableAnalytics({ - provider: 'liveyield', - options: { - rtaFunctionName: 'abc', - customerId: 'd6a6f8da-190f-47d6-ae11-f1a4469083fa', - customerName: 'test', - customerSite: 'scribol.com', - sessionTimezoneOffset: 25 - } - }); - - liveyield.disableAnalytics(); - expect(rtaCalls[0].callArgs['0']).to.match(/create/); - expect(rtaCalls[0].callArgs['1']).to.match( - /d6a6f8da-190f-47d6-ae11-f1a4469083fa/ - ); - expect(rtaCalls[0].callArgs['2']).to.match(/test/); - expect(rtaCalls[0].callArgs['4']).to.match(/25/); - - window.rta = keepMe; - liveyield.disableAnalytics(); - }); - it('should handle custom parameters', function() { - liveyield.enableAnalytics({ - provider: 'liveyield', - options: { - customerId: 'd6a6f8da-190f-47d6-ae11-f1a4469083fa', - customerName: 'test2', - customerSite: 'scribol.com', - sessionTimezoneOffset: 38, - contentTitle: 'testTitle', - contentAuthor: 'testAuthor', - contentCategory: 'testCategory' - } - }); - - liveyield.disableAnalytics(); - expect(rtaCalls[0].callArgs['0']).to.match(/create/); - expect(rtaCalls[0].callArgs['2']).to.match(/test2/); - expect(rtaCalls[0].callArgs['4']).to.match(/38/); - expect(rtaCalls[0].callArgs['5'].contentTitle).to.match(/testTitle/); - expect(rtaCalls[0].callArgs['5'].contentAuthor).to.match(/testAuthor/); - expect(rtaCalls[0].callArgs['5'].contentCategory).to.match( - /testCategory/ - ); - liveyield.disableAnalytics(); - }); - }); - - describe('handling events', function() { - const options = { - provider: 'liveyield', - options: { - customerId: 'd6a6f8da-190f-47d6-ae11-f1a4469083fa', - customerName: 'pubocean', - customerSite: 'scribol.com', - sessionTimezoneOffset: 12 - } - }; - beforeEach(function() { - rtaCalls.length = 0; - liveyield.enableAnalytics(options); - }); - afterEach(function() { - liveyield.disableAnalytics(); - }); - it('should handle BID_REQUESTED event', function() { - const bidRequest = { - bidderCode: 'appnexus', - bids: [ - { - params: { - placementId: '10433394' - }, - adUnitCode: 'div-gpt-ad-1438287399331-0', - transactionId: '2f481ff1-8d20-4c28-8e36-e384e9e3eec6', - sizes: '300x250,300x600', - bidId: '2eddfdc0c791dc', - auctionId: 'a5b849e5-87d7-4205-8300-d063084fcfb7' - } - ] - }; - - events.emit(BID_REQUESTED, bidRequest); - - expect(rtaCalls[1].callArgs['0']).to.equal('bidRequested'); - expect(rtaCalls[1].callArgs['1']).to.equal('div-gpt-ad-1438287399331-0'); - expect(rtaCalls[1].callArgs['2']).to.equal('appnexus'); - }); - it('should handle BID_REQUESTED event with invalid args', function() { - const bidRequest = { - bids: [ - { - params: { - placementId: '10433394' - }, - transactionId: '2f481ff1-8d20-4c28-8e36-e384e9e3eec6', - sizes: '300x250,300x600', - bidId: '2eddfdc0c791dc', - auctionId: 'a5b849e5-87d7-4205-8300-d063084fcf' - }, - { - params: { - placementId: '31034023' - }, - transactionId: '2f481ff1-8d20-4c28-8e36-e384e9e3eec6', - sizes: '300x250,300x600', - bidId: '3dkg0404fmd0', - auctionId: 'a5b849e5-87d7-4205-8300-d063084fcf' - } - ] - }; - events.emit(BID_REQUESTED, bidRequest); - expect(rtaCalls[1].callArgs['0']).to.equal('bidRequested'); - expect(rtaCalls[1].callArgs['1']).to.equal(undefined); - expect(rtaCalls[1].callArgs['2']).to.equal(undefined); - expect(rtaCalls[1].callArgs['0']).to.equal('bidRequested'); - }); - it('should handle BID_RESPONSE event', function() { - const bidResponse = { - height: 250, - statusMessage: 'Bid available', - adId: '2eddfdc0c791dc', - mediaType: 'banner', - source: 'client', - requestId: '2eddfdc0c791dc', - cpm: 0.5, - creativeId: 29681110, - currency: 'USD', - netRevenue: true, - ttl: 300, - auctionId: 'a5b849e5-87d7-4205-8300-d063084fcfb7', - responseTimestamp: 1522265866110, - requestTimestamp: 1522265863600, - bidder: 'appnexus', - adUnitCode: 'div-gpt-ad-1438287399331-0', - timeToRespond: 2510, - size: '300x250' - }; - - events.emit(BID_RESPONSE, bidResponse); - expect(rtaCalls[1].callArgs['0']).to.equal('addBid'); - expect(rtaCalls[1].callArgs['1']).to.equal('div-gpt-ad-1438287399331-0'); - expect(rtaCalls[1].callArgs['2']).to.equal('appnexus'); - expect(rtaCalls[1].callArgs['3']).to.equal(500); - expect(rtaCalls[1].callArgs['4']).to.equal(false); - expect(rtaCalls[1].callArgs['5']).to.equal(false); - }); - it('should handle BID_RESPONSE event with undefined bidder and cpm', function() { - const bidResponse = { - height: 250, - statusMessage: 'Bid available', - adId: '2eddfdc0c791dc', - mediaType: 'banner', - source: 'client', - requestId: '2eddfdc0c791dc', - creativeId: 29681110, - currency: 'USD', - netRevenue: true, - ttl: 300, - auctionId: 'a5b849e5-87d7-4205-8300-d063084fcfb7', - responseTimestamp: 1522265866110, - requestTimestamp: 1522265863600, - adUnitCode: 'div-gpt-ad-1438287399331-0', - timeToRespond: 2510, - size: '300x250' - }; - events.emit(BID_RESPONSE, bidResponse); - expect(rtaCalls[1].callArgs['0']).to.equal('addBid'); - expect(rtaCalls[1].callArgs['2']).to.equal('unknown'); - expect(rtaCalls[1].callArgs['3']).to.equal(0); - expect(rtaCalls[1].callArgs['4']).to.equal(true); - }); - it('should handle BID_RESPONSE event with undefined status message and adUnitCode', function() { - const bidResponse = { - height: 250, - adId: '2eddfdc0c791dc', - mediaType: 'banner', - source: 'client', - requestId: '2eddfdc0c791dc', - cpm: 0.5, - creativeId: 29681110, - currency: 'USD', - netRevenue: true, - ttl: 300, - auctionId: 'a5b849e5-87d7-4205-8300-d063084fcfb7', - responseTimestamp: 1522265866110, - requestTimestamp: 1522265863600, - bidder: 'appnexus', - timeToRespond: 2510, - size: '300x250' - }; - events.emit(BID_RESPONSE, bidResponse); - expect(rtaCalls[1].callArgs['0']).to.equal('addBid'); - expect(rtaCalls[1].callArgs['1']).to.equal(undefined); - expect(rtaCalls[1].callArgs['3']).to.equal(0); - expect(rtaCalls[1].callArgs['5']).to.equal(true); - }); - it('should handle BID_TIMEOUT', function() { - const bidTimeout = [ - { - bidId: '2baa51527bd015', - bidder: 'bidderOne', - adUnitCode: '/19968336/header-bid-tag-0', - auctionId: '66529d4c-8998-47c2-ab3e-5b953490b98f' - }, - { - bidId: '6fe3b4c2c23092', - bidder: 'bidderTwo', - adUnitCode: '/19968336/header-bid-tag-0', - auctionId: '66529d4c-8998-47c2-ab3e-5b953490b98f' - } - ]; - events.emit(BID_TIMEOUT, bidTimeout); - expect(rtaCalls[1].callArgs['0']).to.equal('biddersTimeout'); - expect(rtaCalls[1].callArgs['1'].length).to.equal(2); - }); - it('should handle BID_WON event', function() { - const bidWon = { - adId: '4587fec4900b81', - mediaType: 'banner', - requestId: '4587fec4900b81', - cpm: 1.962, - creativeId: 2126, - currency: 'EUR', - netRevenue: true, - ttl: 302, - auctionId: '914bedad-b145-4e46-ba58-51365faea6cb', - statusMessage: 'Bid available', - responseTimestamp: 1530628534437, - requestTimestamp: 1530628534219, - bidderCode: 'testbidder4', - adUnitCode: 'div-gpt-ad-1438287399331-0', - timeToRespond: 218, - size: '300x250', - status: 'rendered' - }; - events.emit(BID_WON, bidWon); - expect(rtaCalls[1].callArgs['0']).to.equal('resolveSlot'); - expect(rtaCalls[1].callArgs['1']).to.equal('div-gpt-ad-1438287399331-0'); - expect(rtaCalls[1].callArgs['2'].prebidWon).to.equal(true); - expect(rtaCalls[1].callArgs['2'].prebidPartner).to.equal('testbidder4'); - expect(rtaCalls[1].callArgs['2'].prebidValue).to.equal(1962); - }); - it('should throw error, invoking BID_WON event without adUnitCode', function() { - const bidWon = { - adId: '4587fec4900b81', - mediaType: 'banner', - requestId: '4587fec4900b81', - cpm: 1.962, - creativeId: 2126, - currency: 'EUR', - netRevenue: true, - ttl: 302, - auctionId: '914bedad-b145-4e46-ba58-51365faea6cb', - statusMessage: 'Bid available', - responseTimestamp: 1530628534437, - requestTimestamp: 1530628534219, - timeToRespond: 218, - bidderCode: 'testbidder4', - size: '300x250', - status: 'rendered' - }; - events.emit(BID_WON, bidWon); - expect(rtaCalls[1]).to.be.undefined; - }); - it('should throw error, invoking BID_WON event without bidderCode', function() { - const bidWon = { - adId: '4587fec4900b81', - mediaType: 'banner', - requestId: '4587fec4900b81', - cpm: 1.962, - creativeId: 2126, - currency: 'EUR', - netRevenue: true, - ttl: 302, - auctionId: '914bedad-b145-4e46-ba58-51365faea6cb', - statusMessage: 'Bid available', - responseTimestamp: 1530628534437, - requestTimestamp: 1530628534219, - adUnitCode: 'div-gpt-ad-1438287399331-0', - timeToRespond: 218, - size: '300x250', - status: 'rendered' - }; - events.emit(BID_WON, bidWon); - expect(rtaCalls[1]).to.be.undefined; - }); - }); - - describe('googletag use case', function() { - beforeEach(function() { - rtaCalls.length = 0; - }); - afterEach(function() { - liveyield.disableAnalytics(); - }); - it('should ignore BID_WON events when gpt is provided', function() { - const options = { - provider: 'liveyield', - options: { - customerId: 'd6a6f8da-190f-47d6-ae11-f1a4469083fa', - customerName: 'pubocean', - customerSite: 'scribol.com', - sessionTimezoneOffset: 12, - googlePublisherTag: true, - wireGooglePublisherTag: function() { - return null; - } - } - }; - liveyield.enableAnalytics(options); - - const bidWon = { - adId: 'ignore_me', - mediaType: 'banner', - requestId: '4587fec4900b81', - cpm: 1.962, - creativeId: 2126, - currency: 'EUR', - netRevenue: true, - ttl: 302, - auctionId: '914bedad-b145-4e46-ba58-51365faea6cb', - statusMessage: 'Bid available', - responseTimestamp: 1530628534437, - requestTimestamp: 1530628534219, - bidderCode: 'hello', - adUnitCode: 'div-gpt-ad-1438287399331-0', - timeToRespond: 218, - size: '300x250', - status: 'rendered' - }; - events.emit(BID_WON, bidWon); - - expect(rtaCalls.length).to.equal(1); - }); - it('should subscribe to slotRenderEnded', function() { - var googletag; - var callback; - const options = { - provider: 'liveyield', - options: { - customerId: 'd6a6f8da-190f-47d6-ae11-f1a4469083fa', - customerName: 'pubocean', - customerSite: 'scribol.com', - sessionTimezoneOffset: 12, - googlePublisherTag: 'testGPT', - wireGooglePublisherTag: function(gpt, cb) { - googletag = gpt; - callback = cb; - } - } - }; - liveyield.enableAnalytics(options); - expect(googletag).to.equal('testGPT'); - expect(typeof callback).to.equal('function'); - }); - it('should handle BID_WON event for prebid', function() { - var call; - const slot = { - getResponseInformation: function() { - const dfpInfo = { - dfpAdvertiserId: 1, - dfpLineItemId: 2, - dfpCreativeId: 3 - }; - return dfpInfo; - }, - getTargeting: function(v) { - return ['4587fec4900b81']; - } - }; - const options = { - provider: 'liveyield', - options: { - customerId: 'd6a6f8da-190f-47d6-ae11-f1a4469083fa', - customerName: 'pubocean', - customerSite: 'scribol.com', - sessionTimezoneOffset: 12, - googlePublisherTag: 'testGPT', - wireGooglePublisherTag: function(gpt, cb) { - call = cb; - }, - getHighestPrebidAdImpressionPartner: function(slot, version) { - return 'testbidder4'; - }, - getHighestPrebidAdImpressionValue: function(slot, version) { - return 12; - }, - postProcessResolution: function( - resolution, - slot, - hbPartner, - hbValue, - version - ) { - return resolution; - }, - getAdUnitNameByGooglePublisherTagSlot: function(slot, version) { - return 'testUnit'; - } - } - }; - liveyield.enableAnalytics(options); - const bidWon = { - adId: '4587fec4900b81' - }; - events.emit(BID_WON, bidWon); - call(slot); - expect(rtaCalls[1].callArgs['2'].prebidWon).to.equal(true); - expect(rtaCalls[1].callArgs['2'].prebidPartner).to.equal('testbidder4'); - }); - it('should handle BID_WON event for dfp', function() { - let call; - const slot = { - getResponseInformation: function() { - const dfpInfo = { - dfpAdvertiserId: 1, - dfpLineItemId: 2, - dfpCreativeId: 3 - }; - return dfpInfo; - } - }; - const options = { - provider: 'liveyield', - options: { - customerId: 'd6a6f8da-190f-47d6-ae11-f1a4469083fa', - customerName: 'pubocean', - customerSite: 'scribol.com', - sessionTimezoneOffset: 12, - googlePublisherTag: 'testGPT', - wireGooglePublisherTag: function(gpt, cb) { - call = cb; - }, - getHighestPrebidAdImpressionPartner: function(slot, version) { - return 'testbidder4'; - }, - getHighestPrebidAdImpressionValue: function(slot, version) { - return 12; - }, - postProcessResolution: function(slot, version) { - return { partner: slot.prebidPartner, value: slot.prebidValue }; - }, - getAdUnitNameByGooglePublisherTagSlot: function(slot, version) { - return 'testUnit'; - }, - isPrebidAdImpression: function(slot) { - return true; - } - } - }; - liveyield.enableAnalytics(options); - call(slot); - expect(rtaCalls.length).to.equal(2); - expect(rtaCalls[1].callArgs[0]).to.equal('resolveSlot'); - expect(rtaCalls[1].callArgs[1]).to.equal('testUnit'); - expect(rtaCalls[1].callArgs[2].partner).to.equal('testbidder4'); - expect(rtaCalls[1].callArgs[2].value).to.equal(12000); - }); - it('should work with defaults: prebid won', () => { - let call; - const options = { - provider: 'liveyield', - options: { - customerId: 'd6a6f8da-190f-47d6-ae11-f1a4469083fa', - customerName: 'pubocean', - customerSite: 'scribol.com', - sessionTimezoneOffset: 12, - googlePublisherTag: 'testGPT', - wireGooglePublisherTag: (gpt, cb) => (call = cb), - getAdUnitName: adUnitCode => - adUnitCode === 'PREBID_UNIT' ? 'ADUNIT' : null, - getAdUnitNameByGooglePublisherTagSlot: slot => - slot.getSlotElementId() === 'div-gpt-ad-1438287399331-0' - ? 'ADUNIT' - : null - } - }; - liveyield.enableAnalytics(options); - - const bidResponse = { - adId: 'defaults_with_cache', - statusMessage: 'Bid available', - cpm: 0.5, - bidder: 'appnexus', - adUnitCode: 'PREBID_UNIT' - }; - events.emit(BID_RESPONSE, bidResponse); - - const bidWon = { - adId: bidResponse.adId, - cpm: 1.962, // adjusted, this one shall be used, not the one from bidResponse - bidderCode: 'appnexus_from_bid_won_event', - adUnitCode: 'PREBID_UNIT' - }; - events.emit(BID_WON, bidWon); - - const slot = { - getTargeting: key => (key === 'hb_adid' ? [bidResponse.adId] : []), - getResponseInformation: () => null, - getSlotElementId: () => 'div-gpt-ad-1438287399331-0' - }; - call(slot); - - expect(rtaCalls[2].callArgs[0]).to.equal('resolveSlot'); - expect(rtaCalls[2].callArgs[1]).to.equal('ADUNIT'); - expect(rtaCalls[2].callArgs[2]).to.deep.equal({ - targetings: [], - prebidWon: true, - prebidPartner: 'appnexus_from_bid_won_event', - prebidValue: 1962 - }); - }); - it('should work with defaults: dfp won, prebid bid response', () => { - let call; - const options = { - provider: 'liveyield', - options: { - customerId: 'd6a6f8da-190f-47d6-ae11-f1a4469083fa', - customerName: 'pubocean', - customerSite: 'scribol.com', - sessionTimezoneOffset: 12, - googlePublisherTag: 'testGPT', - wireGooglePublisherTag: (gpt, cb) => (call = cb), - getAdUnitName: adUnitCode => - adUnitCode === 'PREBID_UNIT' ? 'ADUNIT' : null, - getAdUnitNameByGooglePublisherTagSlot: slot => - slot.getSlotElementId() === 'div-gpt-ad-1438287399331-0' - ? 'ADUNIT' - : null - } - }; - liveyield.enableAnalytics(options); - - const bidResponse = { - adId: 'defaults_with_cache_no_prebid_win', - statusMessage: 'Bid available', - cpm: 0.5, - bidder: 'appnexus', - adUnitCode: 'PREBID_UNIT' - }; - events.emit(BID_RESPONSE, bidResponse); - - const slot = { - getTargeting: key => (key === 'hb_adid' ? [bidResponse.adId] : []), - getResponseInformation: () => null, - getSlotElementId: () => 'div-gpt-ad-1438287399331-0' - }; - call(slot); - - expect(rtaCalls[2].callArgs[0]).to.equal('resolveSlot'); - expect(rtaCalls[2].callArgs[1]).to.equal('ADUNIT'); - expect(rtaCalls[2].callArgs[2]).to.deep.equal({ - targetings: [], - prebidPartner: 'appnexus', - prebidValue: 500 - }); - }); - it('should work with defaults: dfp won, without prebid', () => { - let call; - const options = { - provider: 'liveyield', - options: { - customerId: 'd6a6f8da-190f-47d6-ae11-f1a4469083fa', - customerName: 'pubocean', - customerSite: 'scribol.com', - sessionTimezoneOffset: 12, - googlePublisherTag: 'testGPT', - wireGooglePublisherTag: (gpt, cb) => (call = cb), - getAdUnitName: adUnitCode => - adUnitCode === 'PREBID_UNIT' ? 'ADUNIT' : null, - getAdUnitNameByGooglePublisherTagSlot: slot => - slot.getSlotElementId() === 'div-gpt-ad-1438287399331-0' - ? 'ADUNIT' - : null - } - }; - liveyield.enableAnalytics(options); - - const slot = { - getTargeting: key => (key === 'hb_adid' ? ['does-not-exist'] : []), - getResponseInformation: () => null, - getSlotElementId: () => 'div-gpt-ad-1438287399331-0' - }; - call(slot); - - expect(rtaCalls[1].callArgs[0]).to.equal('resolveSlot'); - expect(rtaCalls[1].callArgs[1]).to.equal('ADUNIT'); - expect(rtaCalls[1].callArgs[2]).to.deep.equal({ - targetings: [] - }); - }); - }); -}); diff --git a/test/spec/modules/logicadBidAdapter_spec.js b/test/spec/modules/logicadBidAdapter_spec.js index 6abca6b2e98..3c1383781b9 100644 --- a/test/spec/modules/logicadBidAdapter_spec.js +++ b/test/spec/modules/logicadBidAdapter_spec.js @@ -35,7 +35,52 @@ describe('LogicadAdapter', function () { third: 'fakesharedid' } }] - }] + }], + ortb2: { + device: { + sua: { + source: 2, + platform: { + brand: 'Windows', + version: ['10', '0', '0'] + }, + browsers: [ + { + brand: 'Chromium', + version: ['112', '0', '5615', '20'] + }, + { + brand: 'Google Chrome', + version: ['112', '0', '5615', '20'] + }, + { + brand: 'Not:A-Brand', + version: ['99', '0', '0', '0'] + } + ], + mobile: 0, + model: '', + bitness: '64', + architecture: 'x86' + } + }, + user: { + data: [ + { + ext: { + segtax: 600, + segclass: '2206021246' + }, + segment: [ + { + id: '1' + } + ], + name: 'cd.ladsp.com' + } + ] + } + } }]; const nativeBidRequests = [{ bidder: 'logicad', @@ -77,7 +122,52 @@ describe('LogicadAdapter', function () { third: 'fakesharedid' } }] - }] + }], + ortb2: { + device: { + sua: { + source: 2, + platform: { + brand: 'Windows', + version: ['10', '0', '0'] + }, + browsers: [ + { + brand: 'Chromium', + version: ['112', '0', '5615', '20'] + }, + { + brand: 'Google Chrome', + version: ['112', '0', '5615', '20'] + }, + { + brand: 'Not:A-Brand', + version: ['99', '0', '0', '0'] + } + ], + mobile: 0, + model: '', + bitness: '64', + architecture: 'x86' + } + }, + user: { + data: [ + { + ext: { + segtax: 600, + segclass: '2206021246' + }, + segment: [ + { + id: '1' + } + ], + name: 'cd.ladsp.com' + } + ] + } + } }]; const bidderRequest = { refererInfo: { @@ -184,6 +274,36 @@ describe('LogicadAdapter', function () { expect(data.auctionId).to.equal('18fd8b8b0bd757'); expect(data.eids[0].source).to.equal('sharedid.org'); expect(data.eids[0].uids[0].id).to.equal('fakesharedid'); + + expect(data.sua.source).to.equal(2); + expect(data.sua.platform.brand).to.equal('Windows'); + expect(data.sua.platform.version[0]).to.equal('10'); + expect(data.sua.platform.version[1]).to.equal('0'); + expect(data.sua.platform.version[2]).to.equal('0'); + expect(data.sua.browsers[0].brand).to.equal('Chromium'); + expect(data.sua.browsers[0].version[0]).to.equal('112'); + expect(data.sua.browsers[0].version[1]).to.equal('0'); + expect(data.sua.browsers[0].version[2]).to.equal('5615'); + expect(data.sua.browsers[0].version[3]).to.equal('20'); + expect(data.sua.browsers[1].brand).to.equal('Google Chrome'); + expect(data.sua.browsers[1].version[0]).to.equal('112'); + expect(data.sua.browsers[1].version[1]).to.equal('0'); + expect(data.sua.browsers[1].version[2]).to.equal('5615'); + expect(data.sua.browsers[1].version[3]).to.equal('20'); + expect(data.sua.browsers[2].brand).to.equal('Not:A-Brand'); + expect(data.sua.browsers[2].version[0]).to.equal('99'); + expect(data.sua.browsers[2].version[1]).to.equal('0'); + expect(data.sua.browsers[2].version[2]).to.equal('0'); + expect(data.sua.browsers[2].version[3]).to.equal('0'); + expect(data.sua.mobile).to.equal(0); + expect(data.sua.model).to.equal(''); + expect(data.sua.bitness).to.equal('64'); + expect(data.sua.architecture).to.equal('x86'); + + expect(data.userData[0].name).to.equal('cd.ladsp.com'); + expect(data.userData[0].segment[0].id).to.equal('1'); + expect(data.userData[0].ext.segtax).to.equal(600); + expect(data.userData[0].ext.segclass).to.equal('2206021246'); }); }); diff --git a/test/spec/modules/loglyliftBidAdapter_spec.js b/test/spec/modules/loglyliftBidAdapter_spec.js index 8ff2eb97463..9805561442a 100644 --- a/test/spec/modules/loglyliftBidAdapter_spec.js +++ b/test/spec/modules/loglyliftBidAdapter_spec.js @@ -11,6 +11,11 @@ describe('loglyliftBidAdapter', function () { }, adUnitCode: '/19968336/prebid_native_example_1', transactionId: '10aee457-617c-4572-ab5b-99df1d73ccb4', + ortb2Imp: { + ext: { + tid: '10aee457-617c-4572-ab5b-99df1d73ccb4', + } + }, sizes: [[300, 250], [300, 600]], bidderRequestId: '15da3afd9632d7', auctionId: 'f890b7d9-e787-4237-ac21-6d8554abac9f', @@ -29,6 +34,11 @@ describe('loglyliftBidAdapter', function () { }, adUnitCode: '/19968336/prebid_native_example_1', transactionId: '10aee457-617c-4572-ab5b-99df1d73ccb4', + ortb2Imp: { + ext: { + tid: '10aee457-617c-4572-ab5b-99df1d73ccb4', + } + }, sizes: [ [] ], diff --git a/test/spec/modules/luponmediaBidAdapter_spec.js b/test/spec/modules/luponmediaBidAdapter_spec.js index 9f109ff1892..1441abc0fe8 100755 --- a/test/spec/modules/luponmediaBidAdapter_spec.js +++ b/test/spec/modules/luponmediaBidAdapter_spec.js @@ -133,7 +133,7 @@ describe('luponmediaBidAdapter', function () { } ], 'auctionStart': 1587413920820, - 'timeout': 2000, + 'timeout': 1500, 'refererInfo': { 'page': 'https://novi.ba/clanak/176067/fast-car-beginner-s-guide-to-tuning-turbo-engines', 'reachedTop': true, @@ -142,7 +142,12 @@ describe('luponmediaBidAdapter', function () { 'https://novi.ba/clanak/176067/fast-car-beginner-s-guide-to-tuning-turbo-engines' ] }, - 'start': 1587413920835 + 'start': 1587413920835, + ortb2: { + source: { + tid: 'mock-tid' + } + }, }; it('sends bid request to ENDPOINT via POST', function () { @@ -150,7 +155,23 @@ describe('luponmediaBidAdapter', function () { let dynRes = JSON.parse(requests.data); expect(requests.url).to.equal(ENDPOINT_URL); expect(requests.method).to.equal('POST'); - expect(requests.data).to.equal('{"id":"585d96a5-bd93-4a89-b8ea-0f546f3aaa82","test":0,"source":{"tid":"585d96a5-bd93-4a89-b8ea-0f546f3aaa82","ext":{"schain":{"ver":"1.0","complete":1,"nodes":[{"asi":"novi.ba","sid":"199424","hp":1}]}}},"tmax":1500,"imp":[{"id":"268a30af10dd6f","secure":1,"ext":{"luponmedia":{"siteId":303522,"keyId":"4o2c4"}},"banner":{"format":[{"w":300,"h":250}]}}],"ext":{"prebid":{"targeting":{"includewinners":true,"includebidderkeys":false}}},"user":{"id":"' + dynRes.user.id + '","buyeruid":"8d8b16cb-1383-4a0f-b4bb-0be28464d974"},"site":{"page":"https://novi.ba/clanak/176067/fast-car-beginner-s-guide-to-tuning-turbo-engines"}}'); + expect(JSON.parse(requests.data)).to.deep.include({ + 'test': 0, + 'source': { + tid: 'mock-tid', + 'ext': {'schain': {'ver': '1.0', 'complete': 1, 'nodes': [{'asi': 'novi.ba', 'sid': '199424', 'hp': 1}]}} + }, + 'tmax': 1500, + 'imp': [{ + 'id': '268a30af10dd6f', + 'secure': 1, + 'ext': {'luponmedia': {'siteId': 303522, 'keyId': '4o2c4'}}, + 'banner': {'format': [{'w': 300, 'h': 250}]} + }], + 'ext': {'prebid': {'targeting': {'includewinners': true, 'includebidderkeys': false}}}, + 'user': {'id': dynRes.user.id, 'buyeruid': '8d8b16cb-1383-4a0f-b4bb-0be28464d974'}, + 'site': {'page': 'https://novi.ba/clanak/176067/fast-car-beginner-s-guide-to-tuning-turbo-engines'} + }); }); }); diff --git a/test/spec/modules/magniteAnalyticsAdapter_spec.js b/test/spec/modules/magniteAnalyticsAdapter_spec.js index 3606b4b4550..ae63f19f46b 100644 --- a/test/spec/modules/magniteAnalyticsAdapter_spec.js +++ b/test/spec/modules/magniteAnalyticsAdapter_spec.js @@ -3,15 +3,12 @@ import magniteAdapter, { getHostNameFromReferer, storage, rubiConf, + detectBrowserFromUa } from '../../../modules/magniteAnalyticsAdapter.js'; import CONSTANTS from 'src/constants.json'; import { config } from 'src/config.js'; import { server } from 'test/mocks/xhr.js'; import * as mockGpt from '../integration/faker/googletag.js'; -import { - setConfig, - addBidResponseHook, -} from 'modules/currency.js'; import { getGlobal } from '../../../src/prebidGlobal.js'; import { deepAccess } from '../../../src/utils.js'; @@ -27,12 +24,24 @@ const { BIDDER_DONE, BID_WON, BID_TIMEOUT, - BILLABLE_EVENT + BILLABLE_EVENT, + SEAT_NON_BID, + BID_REJECTED } } = CONSTANTS; const STUBBED_UUID = '12345678-1234-1234-1234-123456789abc'; +const metrics = { + getMetrics: () => { + return { + 'adapter.client.total': 271, + 'adapter.client.net': 240, + 'adapter.s2s.total': 371, + 'adapter.s2s.net': 340 + } + } +} // Mock Event Data const MOCK = { AUCTION_INIT: { @@ -101,6 +110,11 @@ const MOCK = { 'startTime': 1658868383748 } ], + 'ortb2': { + 'device': { + 'ua': 'Mozilla/ 5.0(Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/ 537.36(KHTML, like Gecko) Chrome/ 109.0.0.0 Safari / 537.36' + } + }, 'refererInfo': { 'page': 'http://a-test-domain.com:8000/test_pages/sanity/TEMP/prebidTest.html?pbjs_debug=true', }, @@ -153,6 +167,17 @@ const MOCK = { 'size': '300x250', 'status': 'rendered', getStatusCode: () => 1, + metrics + }, + SEAT_NON_BID: { + auctionId: '99785e47-a7c8-4c8a-ae05-ef1c717a4b4d', + seatnonbid: [{ + seat: 'rubicon', + nonbid: [{ + status: 1, + impid: 'box' + }] + }] }, AUCTION_END: { 'auctionId': '99785e47-a7c8-4c8a-ae05-ef1c717a4b4d', @@ -169,6 +194,7 @@ const MOCK = { 'bidId': '23fcd8cf4bf0d7', 'auctionId': '99785e47-a7c8-4c8a-ae05-ef1c717a4b4d', 'src': 'client', + metrics } ] }, @@ -209,6 +235,9 @@ const ANALYTICS_MESSAGE = { 'start': 1519767013781, 'expires': 1519788613781 }, + 'client': { + 'browser': 'Chrome' + }, 'auctions': [ { 'auctionId': '99785e47-a7c8-4c8a-ae05-ef1c717a4b4d', @@ -242,6 +271,7 @@ const ANALYTICS_MESSAGE = { 'source': 'client', 'status': 'success', 'clientLatencyMillis': 271, + 'httpLatencyMillis': 240, 'bidResponse': { 'bidPriceUSD': 3.4, 'mediaType': 'banner', @@ -278,6 +308,7 @@ const ANALYTICS_MESSAGE = { 'source': 'client', 'status': 'success', 'clientLatencyMillis': 271, + 'httpLatencyMillis': 240, 'bidResponse': { 'bidPriceUSD': 3.4, 'mediaType': 'banner', @@ -550,6 +581,29 @@ describe('magnite analytics adapter', function () { ]); }); + [ + { + ua: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15', + expected: 'Safari' + }, + { + ua: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/109.0', + expected: 'Firefox' + }, + { + ua: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36 Edg/109.0.1518.78', + expected: 'Edge' + }, + { + ua: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36 OPR/94.0.0.0', + expected: 'Opera' + } + ].forEach(testData => { + it(`should parse browser from ${testData.expected} user agent correctly`, function () { + expect(detectBrowserFromUa(testData.ua)).to.equal(testData.expected); + }); + }) + it('should pass along 1x1 size if no sizes in adUnit', function () { const auctionInit = utils.deepClone(MOCK.AUCTION_INIT); @@ -1310,9 +1364,6 @@ describe('magnite analytics adapter', function () { // adunit should be marked as error expectedMessage.auctions[0].adUnits[0].status = 'error'; - // timed out in 1000 ms - expectedMessage.auctions[0].adUnits[0].bids[0].clientLatencyMillis = 1000; - expectedMessage.auctions[0].auctionStart = auctionStart; expect(message).to.deep.equal(expectedMessage); @@ -1396,34 +1447,6 @@ describe('magnite analytics adapter', function () { expect(message).to.deep.equal(expectedMessage); }); - it('should pass bidderDetail for multibid auctions', function () { - // Set the rates - setConfig({ - adServerCurrency: 'JPY', - rates: { - USD: { - JPY: 100 - } - } - }); - - // set our bid response to JPY - let bidResponse = utils.deepClone(MOCK.BID_RESPONSE); - bidResponse.currency = 'JPY'; - bidResponse.cpm = 100; - - // Now add the bidResponse hook which hooks on the currenct conversion function onto the bid response - let innerBid; - addBidResponseHook(function (adCodeId, bid) { - innerBid = bid; - }, 'elementId', bidResponse); - - // Use the rubi analytics parseBidResponse Function to get the resulting cpm from the bid response! - const bidResponseObj = parseBidResponse(innerBid); - expect(bidResponseObj).to.have.property('bidPriceUSD'); - expect(bidResponseObj.bidPriceUSD).to.equal(1.0); - }); - it('should use the integration type provided in the config instead of the default', () => { config.setConfig({ rubicon: { @@ -2007,4 +2030,213 @@ describe('magnite analytics adapter', function () { } }) }); + + describe('BID_RESPONSE events', () => { + beforeEach(() => { + magniteAdapter.enableAnalytics({ + options: { + endpoint: '//localhost:9999/event', + accountId: 1001 + } + }); + config.setConfig({ rubicon: { updatePageView: true } }); + }); + + it('should add a no-bid bid to the add unit if it recieves one from the server', () => { + const bidResponse = utils.deepClone(MOCK.BID_RESPONSE); + const auctionInit = utils.deepClone(MOCK.AUCTION_INIT); + + bidResponse.requestId = 'fakeId'; + bidResponse.seatBidId = 'fakeId'; + + bidResponse.requestId = 'fakeId'; + events.emit(AUCTION_INIT, auctionInit); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + events.emit(BID_RESPONSE, bidResponse) + events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); + events.emit(AUCTION_END, MOCK.AUCTION_END); + clock.tick(rubiConf.analyticsBatchTimeout + 1000); + + let message = JSON.parse(server.requests[0].requestBody); + expect(utils.generateUUID.called).to.equal(true); + + expect(message.auctions[0].adUnits[0].bids[1]).to.deep.equal( + { + bidder: 'rubicon', + source: 'server', + status: 'success', + bidResponse: { + 'bidPriceUSD': 3.4, + 'dimensions': { + 'height': 250, + 'width': 300 + }, + 'mediaType': 'banner' + }, + oldBidId: 'fakeId', + unknownBid: true, + bidId: 'fakeId', + clientLatencyMillis: 271, + httpLatencyMillis: 240 + } + ); + }); + }); + + describe('SEAT_NON_BID events', () => { + let seatnonbid; + + const runNonBidAuction = () => { + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + events.emit(SEAT_NON_BID, seatnonbid) + events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); + events.emit(AUCTION_END, MOCK.AUCTION_END); + clock.tick(rubiConf.analyticsBatchTimeout + 1000); + }; + const checkStatusAgainstCode = (status, code, error, index) => { + seatnonbid.seatnonbid[0].nonbid[0].status = code; + runNonBidAuction(); + let message = JSON.parse(server.requests[index].requestBody); + let bid = message.auctions[0].adUnits[0].bids[1]; + + if (error) { + expect(bid.error).to.deep.equal(error); + } else { + expect(bid.error).to.equal(undefined); + } + expect(bid.source).to.equal('server'); + expect(bid.status).to.equal(status); + expect(bid.isSeatNonBid).to.equal(true); + }; + beforeEach(() => { + magniteAdapter.enableAnalytics({ + options: { + endpoint: '//localhost:9999/event', + accountId: 1001 + } + }); + seatnonbid = utils.deepClone(MOCK.SEAT_NON_BID); + }); + + it('adds seatnonbid info to bids array', () => { + runNonBidAuction(); + let message = JSON.parse(server.requests[0].requestBody); + + expect(message.auctions[0].adUnits[0].bids[1]).to.deep.equal( + { + bidder: 'rubicon', + source: 'server', + status: 'no-bid', + isSeatNonBid: true, + clientLatencyMillis: -139101369960 + } + ); + }); + + it('adjusts the status according to the status map', () => { + const statuses = [ + {code: 0, status: 'no-bid'}, + {code: 100, status: 'error', error: {code: 'request-error', description: 'general error'}}, + {code: 101, status: 'error', error: {code: 'timeout-error', description: 'prebid server timeout'}}, + {code: 200, status: 'rejected'}, + {code: 202, status: 'rejected'}, + {code: 301, status: 'rejected-ipf'} + ]; + statuses.forEach((info, index) => { + checkStatusAgainstCode(info.status, info.code, info.error, index); + }); + }); + }); + + describe('BID_REJECTED events', () => { + let bidRejectedArgs; + + const runBidRejectedAuction = () => { + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + events.emit(BID_REJECTED, bidRejectedArgs) + events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); + events.emit(AUCTION_END, MOCK.AUCTION_END); + clock.tick(rubiConf.analyticsBatchTimeout + 1000); + }; + beforeEach(() => { + magniteAdapter.enableAnalytics({ + options: { + endpoint: '//localhost:9999/event', + accountId: 1001 + } + }); + bidRejectedArgs = utils.deepClone(MOCK.BID_RESPONSE); + }); + + it('updates the bid to be rejected by floors', () => { + bidRejectedArgs.floorData = { + floorValue: 0.5, + floorRule: 'banner', + floorRuleValue: 0.5, + floorCurrency: 'USD', + cpmAfterAdjustments: 0.15, + enforcements: { + enforceJS: true, + enforcePBS: false, + floorDeals: false, + bidAdjustment: true + }, + matchedFields: { + mediaType: 'banner' + } + } + bidRejectedArgs.rejectionReason = 'Bid does not meet price floor'; + + runBidRejectedAuction(); + let message = JSON.parse(server.requests[0].requestBody); + + expect(message.auctions[0].adUnits[0].bids[0]).to.deep.equal({ + bidder: 'rubicon', + bidId: '23fcd8cf4bf0d7', + source: 'client', + status: 'rejected-ipf', + clientLatencyMillis: 271, + httpLatencyMillis: 240, + bidResponse: { + bidPriceUSD: 0.15, + mediaType: 'banner', + dimensions: { + width: 300, + height: 250 + }, + floorValue: 0.5, + floorRuleValue: 0.5, + rejectionReason: 'Bid does not meet price floor' + } + }); + }); + + it('does general rejection', () => { + bidRejectedArgs + bidRejectedArgs.rejectionReason = 'this bid is rejected'; + + runBidRejectedAuction(); + let message = JSON.parse(server.requests[0].requestBody); + + expect(message.auctions[0].adUnits[0].bids[0]).to.deep.equal({ + bidder: 'rubicon', + bidId: '23fcd8cf4bf0d7', + source: 'client', + status: 'rejected', + clientLatencyMillis: 271, + httpLatencyMillis: 240, + bidResponse: { + bidPriceUSD: 3.4, + mediaType: 'banner', + dimensions: { + width: 300, + height: 250 + }, + rejectionReason: 'this bid is rejected' + } + }); + }); + }); }); diff --git a/test/spec/modules/mass_spec.js b/test/spec/modules/mass_spec.js deleted file mode 100644 index 3b5d89b0a8c..00000000000 --- a/test/spec/modules/mass_spec.js +++ /dev/null @@ -1,139 +0,0 @@ -import { expect } from 'chai'; -import { - init, - addBidResponseHook, - addListenerOnce, - isMassBid, - useDefaultMatch, - useDefaultRender, - updateRenderers, - listenerAdded, - isEnabled -} from 'modules/mass'; -import { logInfo } from 'src/utils.js'; - -// mock a MASS bid: -const mockedMassBids = [ - { - bidder: 'ix', - bidId: 'mass-bid-1', - requestId: 'mass-bid-1', - bidderRequestId: 'bidder-request-id-1', - dealId: 'MASS1234', - ad: 'mass://provider/product/etc...', - meta: {} - }, - { - bidder: 'ix', - bidId: 'mass-bid-2', - requestId: 'mass-bid-2', - bidderRequestId: 'bidder-request-id-1', - dealId: '1234', - ad: 'mass://provider/product/etc...', - meta: { - mass: true - } - }, -]; - -// mock non-MASS bids: -const mockedNonMassBids = [ - { - bidder: 'ix', - bidId: 'non-mass-bid-1', - requstId: 'non-mass-bid-1', - bidderRequestId: 'bidder-request-id-1', - dealId: 'MASS1234', - ad: '', - meta: { - mass: true - } - }, - { - bidder: 'ix', - bidId: 'non-mass-bid-2', - requestId: 'non-mass-bid-2', - bidderRequestId: 'bidder-request-id-1', - dealId: '1234', - ad: 'mass://provider/product/etc...', - meta: {} - }, -]; - -const noop = function() {}; - -describe('MASS Module', function() { - it('should be enabled by default', function() { - expect(isEnabled).to.equal(true); - }); - - it('can be disabled', function() { - init({enabled: false}); - expect(isEnabled).to.equal(false); - }); - - it('should only affect MASS bids', function() { - init({renderUrl: 'https://...'}); - mockedNonMassBids.forEach(function(mockedBid) { - const originalBid = Object.assign({}, mockedBid); - const bid = Object.assign({}, originalBid); - - addBidResponseHook(noop, 'ad-code-id', bid); - - expect(bid).to.deep.equal(originalBid); - }); - }); - - it('should only update the ad markup field', function() { - init({renderUrl: 'https://...'}); - mockedMassBids.forEach(function(mockedBid) { - const originalBid = Object.assign({}, mockedBid); - const bid = Object.assign({}, originalBid); - - addBidResponseHook(noop, 'ad-code-id', bid); - - expect(bid.ad).to.not.equal(originalBid.ad); - - delete bid.ad; - delete originalBid.ad; - - expect(bid).to.deep.equal(originalBid); - }); - }); - - it('should add a message listener', function() { - addListenerOnce(); - expect(listenerAdded).to.equal(true); - }); - - it('should support custom renderers', function() { - init({ - renderUrl: 'https://...', - custom: [ - { - dealIdPattern: /abc/, - render: function() {} - } - ] - }); - - const renderers = updateRenderers(); - - expect(renderers.length).to.equal(2); - }); - - it('should match bids by deal ID with the default matcher', function() { - const match = useDefaultMatch(/abc/); - - expect(match({dealId: 'abc'})).to.equal(true); - expect(match({dealId: 'xyz'})).to.equal(false); - }); - - it('should have a default renderer', function() { - const render = useDefaultRender('https://example.com/render.js', 'abc'); - render({}); - - expect(window.abc.loaded).to.equal(true); - expect(window.abc.queue.length).to.equal(1); - }); -}); diff --git a/test/spec/modules/mathildeadsBidAdapter_spec.js b/test/spec/modules/mathildeadsBidAdapter_spec.js index 0f0da6032eb..107906ec83d 100644 --- a/test/spec/modules/mathildeadsBidAdapter_spec.js +++ b/test/spec/modules/mathildeadsBidAdapter_spec.js @@ -74,7 +74,8 @@ describe('MathildeAdsBidAdapter', function () { gdprConsent: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', refererInfo: { referer: 'https://test.com' - } + }, + timeout: 500 }; describe('isBidRequestValid', function () { diff --git a/test/spec/modules/mediaforceBidAdapter_spec.js b/test/spec/modules/mediaforceBidAdapter_spec.js index 22f584306a9..61e5678b03b 100644 --- a/test/spec/modules/mediaforceBidAdapter_spec.js +++ b/test/spec/modules/mediaforceBidAdapter_spec.js @@ -97,7 +97,11 @@ describe('mediaforce bid adapter', function () { } } }, - transactionId: 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b', + ortb2Imp: { + ext: { + tid: 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b', + } + } }; const multiBid = [ @@ -127,7 +131,11 @@ describe('mediaforce bid adapter', function () { sizes: [[300, 250], [600, 400]] } }, - transactionId: transactionId || 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b' + ortb2Imp: { + ext: { + tid: transactionId || 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b' + } + }, } }); @@ -196,7 +204,7 @@ describe('mediaforce bid adapter', function () { bidfloor: bid.params.bidfloor, ext: { mediaforce: { - transactionId: bid.transactionId + transactionId: bid.ortb2Imp.ext.tid, } }, banner: {w: 300, h: 250}, diff --git a/test/spec/modules/mediafuseBidAdapter_spec.js b/test/spec/modules/mediafuseBidAdapter_spec.js index bb9bbd34d1d..dd2b5df70bd 100644 --- a/test/spec/modules/mediafuseBidAdapter_spec.js +++ b/test/spec/modules/mediafuseBidAdapter_spec.js @@ -811,8 +811,8 @@ describe('MediaFuseAdapter', function () { sha1udid: '4DFAA92388699AC6539885AEF1719293879985BF', windowsadid: '750c6be243f1c4b5c9912b95a5742fc5' }); - expect(payload.device.geo).to.exist; - expect(payload.device.geo).to.deep.equal({ + expect(payload.device.geo).to.not.exist; + expect(payload.device.geo).to.not.deep.equal({ lat: 40.0964439, lng: -75.3009142 }); @@ -1021,16 +1021,13 @@ describe('MediaFuseAdapter', function () { }) describe('interpretResponse', function () { - let bfStub; let bidderSettingsStorage; before(function() { - bfStub = sinon.stub(bidderFactory, 'getIabSubCategory'); bidderSettingsStorage = $$PREBID_GLOBAL$$.bidderSettings; }); after(function() { - bfStub.restore(); $$PREBID_GLOBAL$$.bidderSettings = bidderSettingsStorage; }); @@ -1275,7 +1272,6 @@ describe('MediaFuseAdapter', function () { } }] }; - bfStub.returns('1'); let result = spec.interpretResponse({ body: response }, {bidderRequest}); expect(result[0]).to.have.property('vastUrl'); diff --git a/test/spec/modules/mediakeysBidAdapter_spec.js b/test/spec/modules/mediakeysBidAdapter_spec.js index 393b6ac6764..99eaff3f378 100644 --- a/test/spec/modules/mediakeysBidAdapter_spec.js +++ b/test/spec/modules/mediakeysBidAdapter_spec.js @@ -131,7 +131,11 @@ describe('mediakeysBidAdapter', function () { const bidderRequest = { bidderCode: 'mediakeys', - auctionId: '84212956-c377-40e8-b000-9885a06dc692', + ortb2: { + source: { + tid: '84212956-c377-40e8-b000-9885a06dc692', + } + }, bidderRequestId: '1c1b642f803242', bids: [ bid @@ -208,7 +212,7 @@ describe('mediakeysBidAdapter', function () { // openRTB 2.5 expect(data.at).to.equal(1); expect(data.cur[0]).to.equal('USD'); // default currency - expect(data.source.tid).to.equal(bidderRequest.auctionId); + expect(data.source.tid).to.equal(bidderRequest.ortb2.source.tid); expect(data.imp.length).to.equal(1); expect(data.imp[0].id).to.equal(bidRequests[0].bidId); @@ -601,33 +605,29 @@ describe('mediakeysBidAdapter', function () { }); describe('should support userId modules', function() { - const userId = { - pubcid: '01EAJWWNEPN3CYMM5N8M5VXY22', - unsuported: '666' - }; + const userIdAsEids = [{ + source: 'pubcid.org', + uids: [ + { + atype: 1, + id: '01EAJWWNEPN3CYMM5N8M5VXY22' + } + ] + }]; it('should send "user.eids" in the request for Prebid.js supported modules only', function() { const bidCopy = utils.deepClone(bid); - bidCopy.userId = userId; + bidCopy.userIdAsEids = userIdAsEids; const bidderRequestCopy = utils.deepClone(bidderRequest); - bidderRequestCopy.bids[0].userId = userId; + bidderRequestCopy.bids[0].userIdAsEids = userIdAsEids; const bidRequests = [utils.deepClone(bidCopy)]; const request = spec.buildRequests(bidRequests, bidderRequestCopy); const data = request.data; - const expected = [{ - source: 'pubcid.org', - uids: [ - { - atype: 1, - id: '01EAJWWNEPN3CYMM5N8M5VXY22' - } - ] - }]; + const expected = userIdAsEids; expect(data.user.ext).to.exist; - expect(data.user.ext.eids).to.have.lengthOf(1); expect(data.user.ext.eids).to.deep.equal(expected); }); }); diff --git a/test/spec/modules/medianetAnalyticsAdapter_spec.js b/test/spec/modules/medianetAnalyticsAdapter_spec.js index c408f23c4f4..e19c27cc2d3 100644 --- a/test/spec/modules/medianetAnalyticsAdapter_spec.js +++ b/test/spec/modules/medianetAnalyticsAdapter_spec.js @@ -30,9 +30,11 @@ const MOCK = { NO_BID: {'bidder': 'medianet', 'params': {'cid': 'test123', 'crid': '451466393', 'site': {}}, 'mediaTypes': {'banner': {'sizes': [[300, 250]], 'ext': ['asdads']}}, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'transactionId': '303fa0c6-682f-4aea-8e4a-dc68f0d5c7d5', 'sizes': [[300, 250], [300, 600]], 'bidId': '28248b0e6aece2', 'bidderRequestId': '13fccf3809fe43', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'src': 'client'}, BID_TIMEOUT: [{'bidId': '28248b0e6aece2', 'bidder': 'medianet', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'params': [{'cid': 'test123', 'crid': '451466393', 'site': {}}, {'cid': '8CUX0H51P', 'crid': '451466393', 'site': {}}], 'timeout': 6}], BIDS_SAME_REQ_DIFF_CPM: [{'bidderCode': 'medianet', 'width': 300, 'height': 250, 'adId': '3e6e4bce5c8fb3', 'requestId': '28248b0e6aece2', 'mediaType': 'banner', 'source': 'client', 'ext': {'pvid': 123, 'crid': '321'}, 'no_bid': false, 'cpm': 2.299, 'ad': 'AD_CODE', 'ttl': 180, 'creativeId': 'Test1', 'netRevenue': true, 'currency': 'USD', 'dfp_id': 'div-gpt-ad-1460505748561-0', 'originalCpm': 1.1495, 'originalCurrency': 'USD', 'floorData': {'floorValue': 1.10, 'floorRule': 'banner'}, 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'responseTimestamp': 1584563606009, 'requestTimestamp': 1584563605743, 'bidder': 'medianet', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'timeToRespond': 266, 'pbLg': '2.00', 'pbMg': '2.20', 'pbHg': '2.29', 'pbAg': '2.25', 'pbDg': '2.29', 'pbCg': '2.00', 'size': '300x250', 'adserverTargeting': {'hb_bidder': 'medianet', 'hb_adid': '3e6e4bce5c8fb3', 'hb_pb': '2.00', 'hb_size': '300x250', 'hb_source': 'client', 'hb_format': 'banner', 'prebid_test': 1}, 'status': 'rendered', 'params': [{'cid': 'test123', 'crid': '451466393'}]}, {'bidderCode': 'medianet', 'width': 300, 'height': 250, 'adId': '3e6e4bce5c8fb4', 'requestId': '28248b0e6aece2', 'mediaType': 'banner', 'source': 'client', 'ext': {'pvid': 123, 'crid': '321'}, 'no_bid': false, 'cpm': 1.299, 'ad': 'AD_CODE', 'ttl': 180, 'creativeId': 'Test1', 'netRevenue': true, 'currency': 'USD', 'dfp_id': 'div-gpt-ad-1460505748561-0', 'originalCpm': 1.1495, 'originalCurrency': 'USD', 'floorData': {'floorValue': 1.10, 'floorRule': 'banner'}, 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'responseTimestamp': 1584563606009, 'requestTimestamp': 1584563605743, 'bidder': 'medianet', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'timeToRespond': 278, 'pbLg': '1.00', 'pbMg': '1.20', 'pbHg': '1.29', 'pbAg': '1.25', 'pbDg': '1.29', 'pbCg': '1.00', 'size': '300x250', 'adserverTargeting': {'hb_bidder': 'medianet', 'hb_adid': '3e6e4bce5c8fb3', 'hb_pb': '1.00', 'hb_size': '300x250', 'hb_source': 'client', 'hb_format': 'banner', 'prebid_test': 1}, 'status': 'rendered', 'params': [{'cid': 'test123', 'crid': '451466393'}]}], + BIDS_SAME_REQ_DIFF_CPM_SAME_TIME: [{'bidderCode': 'medianet', 'width': 300, 'height': 250, 'adId': '3e6e4bce5c8fb4', 'requestId': '28248b0e6aece2', 'mediaType': 'banner', 'source': 'client', 'ext': {'pvid': 123, 'crid': '321'}, 'no_bid': false, 'cpm': 1.299, 'ad': 'AD_CODE', 'ttl': 180, 'creativeId': 'Test1', 'netRevenue': true, 'currency': 'USD', 'dfp_id': 'div-gpt-ad-1460505748561-0', 'originalCpm': 1.1495, 'originalCurrency': 'USD', 'floorData': {'floorValue': 1.10, 'floorRule': 'banner'}, 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'responseTimestamp': 1584563606009, 'requestTimestamp': 1584563605743, 'bidder': 'medianet', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'timeToRespond': 266, 'pbLg': '1.00', 'pbMg': '1.20', 'pbHg': '1.29', 'pbAg': '1.25', 'pbDg': '1.29', 'pbCg': '1.00', 'size': '300x250', 'adserverTargeting': {'hb_bidder': 'medianet', 'hb_adid': '3e6e4bce5c8fb3', 'hb_pb': '1.00', 'hb_size': '300x250', 'hb_source': 'client', 'hb_format': 'banner', 'prebid_test': 1}, 'status': 'rendered', 'params': [{'cid': 'test123', 'crid': '451466393'}]}, {'bidderCode': 'medianet', 'width': 300, 'height': 250, 'adId': '3e6e4bce5c8fb3', 'requestId': '28248b0e6aece2', 'mediaType': 'banner', 'source': 'client', 'ext': {'pvid': 123, 'crid': '321'}, 'no_bid': false, 'cpm': 2.299, 'ad': 'AD_CODE', 'ttl': 180, 'creativeId': 'Test1', 'netRevenue': true, 'currency': 'USD', 'dfp_id': 'div-gpt-ad-1460505748561-0', 'originalCpm': 1.1495, 'originalCurrency': 'USD', 'floorData': {'floorValue': 1.10, 'floorRule': 'banner'}, 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'responseTimestamp': 1584563606009, 'requestTimestamp': 1584563605743, 'bidder': 'medianet', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'timeToRespond': 266, 'pbLg': '2.00', 'pbMg': '2.20', 'pbHg': '2.29', 'pbAg': '2.25', 'pbDg': '2.29', 'pbCg': '2.00', 'size': '300x250', 'adserverTargeting': {'hb_bidder': 'medianet', 'hb_adid': '3e6e4bce5c8fb3', 'hb_pb': '2.00', 'hb_size': '300x250', 'hb_source': 'client', 'hb_format': 'banner', 'prebid_test': 1}, 'status': 'rendered', 'params': [{'cid': 'test123', 'crid': '451466393'}]}], BIDS_SAME_REQ_EQUAL_CPM: [{'bidderCode': 'medianet', 'width': 300, 'height': 250, 'adId': '3e6e4bce5c8fb3', 'requestId': '28248b0e6aece2', 'mediaType': 'banner', 'source': 'client', 'ext': {'pvid': 123, 'crid': '321'}, 'no_bid': false, 'cpm': 2.299, 'ad': 'AD_CODE', 'ttl': 180, 'creativeId': 'Test1', 'netRevenue': true, 'currency': 'USD', 'dfp_id': 'div-gpt-ad-1460505748561-0', 'originalCpm': 1.1495, 'originalCurrency': 'USD', 'floorData': {'floorValue': 1.1, 'floorRule': 'banner'}, 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'responseTimestamp': 1584563606009, 'requestTimestamp': 1584563605743, 'bidder': 'medianet', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'timeToRespond': 266, 'pbLg': '2.00', 'pbMg': '2.20', 'pbHg': '2.29', 'pbAg': '2.25', 'pbDg': '2.29', 'pbCg': '2.00', 'size': '300x250', 'adserverTargeting': {'hb_bidder': 'medianet', 'hb_adid': '3e6e4bce5c8fb3', 'hb_pb': '2.00', 'hb_size': '300x250', 'hb_source': 'client', 'hb_format': 'banner', 'prebid_test': 1}, 'status': 'rendered', 'params': [{'cid': 'test123', 'crid': '451466393'}]}, {'bidderCode': 'medianet', 'width': 300, 'height': 250, 'adId': '3e6e4bce5c8fb4', 'requestId': '28248b0e6aece2', 'mediaType': 'banner', 'source': 'client', 'ext': {'pvid': 123, 'crid': '321'}, 'no_bid': false, 'cpm': 2.299, 'ad': 'AD_CODE', 'ttl': 180, 'creativeId': 'Test1', 'netRevenue': true, 'currency': 'USD', 'dfp_id': 'div-gpt-ad-1460505748561-0', 'originalCpm': 1.1495, 'originalCurrency': 'USD', 'floorData': {'floorValue': 1.1, 'floorRule': 'banner'}, 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'responseTimestamp': 1584563606009, 'requestTimestamp': 1584563605743, 'bidder': 'medianet', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'timeToRespond': 286, 'pbLg': '2.00', 'pbMg': '2.20', 'pbHg': '2.29', 'pbAg': '2.25', 'pbDg': '2.29', 'pbCg': '2.00', 'size': '300x250', 'adserverTargeting': {'hb_bidder': 'medianet', 'hb_adid': '3e6e4bce5c8fb3', 'hb_pb': '2.00', 'hb_size': '300x250', 'hb_source': 'client', 'hb_format': 'banner', 'prebid_test': 1}, 'status': 'rendered', 'params': [{'cid': 'test123', 'crid': '451466393'}]}], BID_RESPONSES: [{'bidderCode': 'medianet', 'width': 300, 'height': 250, 'adId': '3e6e4bce5c8fb3', 'requestId': '28248b0e6aece2', 'mediaType': 'banner', 'source': 'client', 'ext': {'pvid': 123, 'crid': '321'}, 'no_bid': false, 'cpm': 2.299, 'ad': 'AD_CODE', 'ttl': 180, 'creativeId': 'Test1', 'netRevenue': true, 'currency': 'USD', 'dfp_id': 'div-gpt-ad-1460505748561-0', 'originalCpm': 1.1495, 'originalCurrency': 'USD', 'floorData': {'floorValue': 1.1, 'floorRule': 'banner'}, 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'responseTimestamp': 1584563606009, 'requestTimestamp': 1584563605743, 'bidder': 'medianet', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'timeToRespond': 266, 'pbLg': '2.00', 'pbMg': '2.20', 'pbHg': '2.29', 'pbAg': '2.25', 'pbDg': '2.29', 'pbCg': '2.00', 'size': '300x250', 'adserverTargeting': {'hb_bidder': 'medianet', 'hb_adid': '3e6e4bce5c8fb3', 'hb_pb': '2.00', 'hb_size': '300x250', 'hb_source': 'client', 'hb_format': 'banner', 'prebid_test': 1}, 'params': [{'cid': 'test123', 'crid': '451466393'}]}, {'bidderCode': 'appnexus', 'width': 300, 'height': 250, 'adId': '3e6e4bce5c8fb4', 'requestId': '28248b0e6aecd5', 'mediaType': 'banner', 'source': 'client', 'ext': {'pvid': 123, 'crid': '321'}, 'no_bid': false, 'cpm': 1.299, 'ad': 'AD_CODE', 'ttl': 180, 'creativeId': 'Test1', 'netRevenue': true, 'currency': 'USD', 'dfp_id': 'div-gpt-ad-1460505748561-0', 'originalCpm': 1.1495, 'originalCurrency': 'USD', 'floorData': {'floorValue': 1.1, 'floorRule': 'banner'}, 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'responseTimestamp': 1584563606009, 'requestTimestamp': 1584563605743, 'bidder': 'medianet', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'timeToRespond': 278, 'pbLg': '1.00', 'pbMg': '1.20', 'pbHg': '1.29', 'pbAg': '1.25', 'pbDg': '1.29', 'pbCg': '1.00', 'size': '300x250', 'adserverTargeting': {'hb_bidder': 'appnexus', 'hb_adid': '3e6e4bce5c8fb4', 'hb_pb': '1.00', 'hb_size': '300x250', 'hb_source': 'client', 'hb_format': 'banner', 'prebid_test': 1}, 'params': [{'publisherId': 'test123', 'placementId': '451466393'}]}], - BID_REQUESTS: [{'bidderCode': 'medianet', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'bids': [{'bidder': 'medianet', 'params': {'cid': 'TEST_CID', 'crid': '451466393'}, 'mediaTypes': {'banner': {'sizes': [[300, 250]], 'ext': ['asdads']}}, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'sizes': [[300, 250]], 'bidId': '28248b0e6aece2', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'src': 'client'}], 'auctionStart': 1584563605739, 'timeout': 6000, 'uspConsent': '1YY', 'start': 1584563605743}, {'bidderCode': 'appnexus', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'bids': [{'bidder': 'appnexus', 'params': {'publisherId': 'TEST_CID', 'placementId': '451466393'}, 'mediaTypes': {'banner': {'sizes': [[300, 250]], 'ext': ['asdads']}}, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'sizes': [[300, 250]], 'bidId': '28248b0e6aecd5', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'src': 'client'}], 'auctionStart': 1584563605739, 'timeout': 6000, 'uspConsent': '1YY', 'start': 1584563605743}] + BID_REQUESTS: [{'bidderCode': 'medianet', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'bids': [{'bidder': 'medianet', 'params': {'cid': 'TEST_CID', 'crid': '451466393'}, 'mediaTypes': {'banner': {'sizes': [[300, 250]], 'ext': ['asdads']}}, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'sizes': [[300, 250]], 'bidId': '28248b0e6aece2', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'src': 'client'}], 'auctionStart': 1584563605739, 'timeout': 6000, 'uspConsent': '1YY', 'start': 1584563605743}, {'bidderCode': 'appnexus', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'bids': [{'bidder': 'appnexus', 'params': {'publisherId': 'TEST_CID', 'placementId': '451466393'}, 'mediaTypes': {'banner': {'sizes': [[300, 250]], 'ext': ['asdads']}}, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'sizes': [[300, 250]], 'bidId': '28248b0e6aecd5', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'src': 'client'}], 'auctionStart': 1584563605739, 'timeout': 6000, 'uspConsent': '1YY', 'start': 1584563605743}], + MULTI_BID_RESPONSES: [{'bidderCode': 'medianet', 'width': 300, 'height': 250, 'adId': '3e6e4bce5c8fb3', 'requestId': '28248b0e6aece2', 'mediaType': 'banner', 'source': 'client', 'ext': {'pvid': 123, 'crid': '321'}, 'no_bid': false, 'cpm': 2.299, 'ad': 'AD_CODE', 'ttl': 180, 'creativeId': 'Test1', 'netRevenue': true, 'currency': 'USD', 'dfp_id': 'div-gpt-ad-1460505748561-0', 'originalCpm': 1.1495, 'originalCurrency': 'USD', 'floorData': {'floorValue': 1.10, 'floorRule': 'banner'}, 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'responseTimestamp': 1584563606009, 'requestTimestamp': 1584563605743, 'bidder': 'medianet', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'timeToRespond': 266, 'pbLg': '2.00', 'pbMg': '2.20', 'pbHg': '2.29', 'pbAg': '2.25', 'pbDg': '2.29', 'pbCg': '2.00', 'size': '300x250', 'adserverTargeting': {'hb_bidder': 'medianet', 'hb_adid': '3e6e4bce5c8fb3', 'hb_pb': '2.00', 'hb_size': '300x250', 'hb_source': 'client', 'hb_format': 'banner', 'prebid_test': 1}, 'status': 'rendered', 'params': [{'cid': 'test123', 'crid': '451466393'}]}, {'bidderCode': 'bidA2', 'originalBidder': 'medianet', 'width': 300, 'height': 250, 'adId': '3e6e4bce5c8fb4', 'requestId': '28248b0e6aebecc', 'originalRequestId': '28248b0e6aece2', 'mediaType': 'banner', 'source': 'client', 'ext': {'pvid': 123, 'crid': '321'}, 'no_bid': false, 'cpm': 3.299, 'ad': 'AD_CODE', 'ttl': 180, 'creativeId': 'Test1', 'netRevenue': true, 'currency': 'USD', 'dfp_id': 'div-gpt-ad-1460505748561-0', 'originalCpm': 3.1495, 'originalCurrency': 'USD', 'floorData': {'floorValue': 1.10, 'floorRule': 'banner'}, 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'responseTimestamp': 1584563606009, 'requestTimestamp': 1584563605743, 'bidder': 'medianet', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'timeToRespond': 266, 'pbLg': '3.00', 'pbMg': '3.20', 'pbHg': '3.29', 'pbAg': '3.25', 'pbDg': '3.29', 'pbCg': '3.00', 'size': '300x250', 'adserverTargeting': {'hb_bidder': 'medianet', 'hb_adid': '3e6e4bce5c8fb3', 'hb_pb': '3.00', 'hb_size': '300x250', 'hb_source': 'client', 'hb_format': 'banner', 'prebid_test': 1}, 'status': 'rendered', 'params': [{'cid': 'test123', 'crid': '451466393'}]}] }; function performAuctionWithFloorConfig() { @@ -102,6 +104,14 @@ function performStandardAuctionMultiBidResponseNoWin() { events.emit(SET_TARGETING, MOCK.SET_TARGETING); } +function performMultiBidAuction() { + events.emit(AUCTION_INIT, Object.assign({}, MOCK.AUCTION_INIT, {adUnits: MOCK.Ad_Units})); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + MOCK.MULTI_BID_RESPONSES.forEach(bidResp => events.emit(BID_RESPONSE, bidResp)); + events.emit(AUCTION_END, MOCK.AUCTION_END); + events.emit(SET_TARGETING, MOCK.SET_TARGETING); +} + function getQueryData(url, decode = false) { const queryArgs = url.split('?')[1].split('&'); return queryArgs.reduce((data, arg) => { @@ -309,6 +319,13 @@ describe('Media.net Analytics Adapter', function() { expect(winningBid.adid).equals('3e6e4bce5c8fb3'); }); + it('should pick winning bid if multibids with same request id and same time to respond', function() { + performStandardAuctionMultiBidWithSameRequestId(MOCK.BIDS_SAME_REQ_DIFF_CPM_SAME_TIME); + let winningBid = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log)).filter(log => log.winner)[0]; + expect(winningBid.adid).equals('3e6e4bce5c8fb3'); + medianetAnalytics.clearlogsQueue(); + }); + it('should pick winning bid if multibids with same request id and equal cpm', function() { performStandardAuctionMultiBidWithSameRequestId(MOCK.BIDS_SAME_REQ_EQUAL_CPM); let winningBid = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log)).filter(log => log.winner)[0]; @@ -348,5 +365,14 @@ describe('Media.net Analytics Adapter', function() { expect(errors.length).equals(1); expect(errors[0].event).equals(ERROR_WINNING_BID_ABSENT); }); + + it('can handle multi bid module', function () { + performMultiBidAuction(); + const queue = medianetAnalytics.getlogsQueue(); + expect(queue.length).equals(1); + const multiBidLog = queue.map((log) => getQueryData(log, true))[0]; + expect(multiBidLog.pvnm).to.have.ordered.members(['-2', 'medianet', 'medianet']); + expect(multiBidLog.status).to.have.ordered.members(['1', '1', '1']); + }) }); }); diff --git a/test/spec/modules/medianetBidAdapter_spec.js b/test/spec/modules/medianetBidAdapter_spec.js index 9180bbad68c..bb90eded230 100644 --- a/test/spec/modules/medianetBidAdapter_spec.js +++ b/test/spec/modules/medianetBidAdapter_spec.js @@ -16,7 +16,11 @@ let VALID_BID_REQUEST = [{ } }, 'adUnitCode': 'div-gpt-ad-1460505748561-0', - 'transactionId': '277b631f-92f5-4844-8b19-ea13c095d3f1', + ortb2Imp: { + ext: { + tid: '277b631f-92f5-4844-8b19-ea13c095d3f1' + } + }, 'mediaTypes': { 'banner': { 'sizes': [[300, 250]], @@ -38,7 +42,11 @@ let VALID_BID_REQUEST = [{ } }, 'adUnitCode': 'div-gpt-ad-1460505748561-123', - 'transactionId': 'c52a5c62-3c2b-4b90-9ff8-ec1487754822', + ortb2Imp: { + ext: { + tid: 'c52a5c62-3c2b-4b90-9ff8-ec1487754822', + } + }, 'mediaTypes': { 'banner': { 'sizes': [[300, 251]], @@ -64,6 +72,11 @@ let VALID_BID_REQUEST = [{ } }, 'adUnitCode': 'div-gpt-ad-1460505748561-0', + ortb2Imp: { + ext: { + tid: '277b631f-92f5-4844-8b19-ea13c095d3f1', + } + }, 'transactionId': '277b631f-92f5-4844-8b19-ea13c095d3f1', 'mediaTypes': { 'banner': { @@ -87,7 +100,11 @@ let VALID_BID_REQUEST = [{ } }, 'adUnitCode': 'div-gpt-ad-1460505748561-123', - 'transactionId': 'c52a5c62-3c2b-4b90-9ff8-ec1487754822', + ortb2Imp: { + ext: { + tid: 'c52a5c62-3c2b-4b90-9ff8-ec1487754822', + } + }, 'mediaTypes': { 'banner': { 'sizes': [[300, 251]], @@ -112,7 +129,6 @@ let VALID_BID_REQUEST = [{ } }, 'adUnitCode': 'div-gpt-ad-1460505748561-0', - 'transactionId': '277b631f-92f5-4844-8b19-ea13c095d3f1', 'mediaTypes': { 'banner': { 'sizes': [[300, 250]], @@ -121,7 +137,12 @@ let VALID_BID_REQUEST = [{ 'bidId': '28f8f8130a583e', 'bidderRequestId': '1e9b1f07797c1c', 'auctionId': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', - 'ortb2Imp': { 'ext': { 'data': { 'pbadslot': '/12345/my-gpt-tag-0' } } }, + 'ortb2Imp': { + 'ext': { + tid: '277b631f-92f5-4844-8b19-ea13c095d3f1', + 'data': {'pbadslot': '/12345/my-gpt-tag-0'} + } + }, 'bidRequestsCount': 1 }, { 'bidder': 'medianet', @@ -136,7 +157,6 @@ let VALID_BID_REQUEST = [{ } }, 'adUnitCode': 'div-gpt-ad-1460505748561-123', - 'transactionId': 'c52a5c62-3c2b-4b90-9ff8-ec1487754822', 'mediaTypes': { 'banner': { 'sizes': [[300, 251]], @@ -146,7 +166,12 @@ let VALID_BID_REQUEST = [{ 'bidId': '3f97ca71b1e5c2', 'bidderRequestId': '1e9b1f07797c1c', 'auctionId': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', - 'ortb2Imp': { 'ext': { 'data': { 'pbadslot': '/12345/my-gpt-tag-0' } } }, + 'ortb2Imp': { + 'ext': { + tid: 'c52a5c62-3c2b-4b90-9ff8-ec1487754822', + 'data': {'pbadslot': '/12345/my-gpt-tag-0'} + } + }, 'bidRequestsCount': 1 }], VALID_BID_REQUEST_WITH_USERID = [{ @@ -165,7 +190,11 @@ let VALID_BID_REQUEST = [{ britepoolid: '82efd5e1-816b-4f87-97f8-044f407e2911' }, 'adUnitCode': 'div-gpt-ad-1460505748561-0', - 'transactionId': '277b631f-92f5-4844-8b19-ea13c095d3f1', + ortb2Imp: { + ext: { + tid: '277b631f-92f5-4844-8b19-ea13c095d3f1', + } + }, 'mediaTypes': { 'banner': { 'sizes': [[300, 250]], @@ -188,7 +217,11 @@ let VALID_BID_REQUEST = [{ } }, 'adUnitCode': 'div-gpt-ad-1460505748561-123', - 'transactionId': 'c52a5c62-3c2b-4b90-9ff8-ec1487754822', + ortb2Imp: { + ext: { + tid: 'c52a5c62-3c2b-4b90-9ff8-ec1487754822', + } + }, 'mediaTypes': { 'banner': { 'sizes': [[300, 251]], @@ -214,7 +247,11 @@ let VALID_BID_REQUEST = [{ } }, 'adUnitCode': 'div-gpt-ad-1460505748561-0', - 'transactionId': '277b631f-92f5-4844-8b19-ea13c095d3f1', + ortb2Imp: { + ext: { + tid: '277b631f-92f5-4844-8b19-ea13c095d3f1', + } + }, 'sizes': [[300, 250]], 'mediaTypes': { 'banner': { @@ -237,7 +274,11 @@ let VALID_BID_REQUEST = [{ } }, 'adUnitCode': 'div-gpt-ad-1460505748561-123', - 'transactionId': 'c52a5c62-3c2b-4b90-9ff8-ec1487754822', + ortb2Imp: { + ext: { + tid: 'c52a5c62-3c2b-4b90-9ff8-ec1487754822', + } + }, 'sizes': [[300, 251]], 'mediaTypes': { 'banner': { @@ -261,7 +302,11 @@ let VALID_BID_REQUEST = [{ } }, 'adUnitCode': 'div-gpt-ad-1460505748561-0', - 'transactionId': '277b631f-92f5-4844-8b19-ea13c095d3f1', + ortb2Imp: { + ext: { + tid: '277b631f-92f5-4844-8b19-ea13c095d3f1', + } + }, 'sizes': [[300, 250]], 'mediaTypes': { 'banner': { @@ -314,7 +359,11 @@ let VALID_BID_REQUEST = [{ } }, 'adUnitCode': 'div-gpt-ad-1460505748561-123', - 'transactionId': 'c52a5c62-3c2b-4b90-9ff8-ec1487754822', + ortb2Imp: { + ext: { + tid: 'c52a5c62-3c2b-4b90-9ff8-ec1487754822', + } + }, 'sizes': [[300, 251]], 'mediaTypes': { 'banner': { @@ -389,6 +438,7 @@ let VALID_BID_REQUEST = [{ 'id': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', 'imp': [{ 'id': '28f8f8130a583e', + ortb2Imp: VALID_BID_REQUEST_INVALID_BIDFLOOR[0].ortb2Imp, 'transactionId': '277b631f-92f5-4844-8b19-ea13c095d3f1', 'ext': { 'dfp_id': 'div-gpt-ad-1460505748561-0', @@ -422,6 +472,7 @@ let VALID_BID_REQUEST = [{ } }, { 'id': '3f97ca71b1e5c2', + ortb2Imp: VALID_BID_REQUEST_INVALID_BIDFLOOR[1].ortb2Imp, 'transactionId': 'c52a5c62-3c2b-4b90-9ff8-ec1487754822', 'ext': { 'dfp_id': 'div-gpt-ad-1460505748561-123', @@ -477,6 +528,7 @@ let VALID_BID_REQUEST = [{ 'id': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', 'imp': [{ 'id': '28f8f8130a583e', + ortb2Imp: VALID_NATIVE_BID_REQUEST[0].ortb2Imp, 'transactionId': '277b631f-92f5-4844-8b19-ea13c095d3f1', 'ext': { 'dfp_id': 'div-gpt-ad-1460505748561-0', @@ -510,6 +562,7 @@ let VALID_BID_REQUEST = [{ } }, { 'id': '3f97ca71b1e5c2', + ortb2Imp: VALID_NATIVE_BID_REQUEST[1].ortb2Imp, 'transactionId': 'c52a5c62-3c2b-4b90-9ff8-ec1487754822', 'ext': { 'dfp_id': 'div-gpt-ad-1460505748561-123', @@ -567,6 +620,7 @@ let VALID_BID_REQUEST = [{ 'imp': [{ 'id': '28f8f8130a583e', 'transactionId': '277b631f-92f5-4844-8b19-ea13c095d3f1', + ortb2Imp: VALID_BID_REQUEST[0].ortb2Imp, 'ext': { 'dfp_id': 'div-gpt-ad-1460505748561-0', 'visibility': 1, @@ -599,6 +653,7 @@ let VALID_BID_REQUEST = [{ }, { 'id': '3f97ca71b1e5c2', 'transactionId': 'c52a5c62-3c2b-4b90-9ff8-ec1487754822', + ortb2Imp: VALID_BID_REQUEST[1].ortb2Imp, 'ext': { 'dfp_id': 'div-gpt-ad-1460505748561-123', 'visibility': 1, @@ -656,6 +711,7 @@ let VALID_BID_REQUEST = [{ 'id': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', 'imp': [{ 'id': '28f8f8130a583e', + ortb2Imp: VALID_BID_REQUEST_WITH_USERID[0].ortb2Imp, 'transactionId': '277b631f-92f5-4844-8b19-ea13c095d3f1', 'tagid': 'crid', 'ext': { @@ -690,6 +746,7 @@ let VALID_BID_REQUEST = [{ } }, { 'id': '3f97ca71b1e5c2', + ortb2Imp: VALID_BID_REQUEST_WITH_USERID[1].ortb2Imp, 'transactionId': 'c52a5c62-3c2b-4b90-9ff8-ec1487754822', 'tagid': 'crid', 'ext': { @@ -747,6 +804,7 @@ let VALID_BID_REQUEST = [{ 'id': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', 'imp': [{ 'id': '28f8f8130a583e', + ortb2Imp: VALID_BID_REQUEST_WITH_CRID[0].ortb2Imp, 'transactionId': '277b631f-92f5-4844-8b19-ea13c095d3f1', 'tagid': 'crid', 'ext': { @@ -781,6 +839,7 @@ let VALID_BID_REQUEST = [{ } }, { 'id': '3f97ca71b1e5c2', + ortb2Imp: VALID_BID_REQUEST_WITH_CRID[1].ortb2Imp, 'transactionId': 'c52a5c62-3c2b-4b90-9ff8-ec1487754822', 'tagid': 'crid', 'ext': { @@ -1103,7 +1162,11 @@ let VALID_BID_REQUEST = [{ } }, 'adUnitCode': 'div-gpt-ad-1460505748561-0', - 'transactionId': '277b631f-92f5-4844-8b19-ea13c095d3f1', + ortb2Imp: { + ext: { + tid: '277b631f-92f5-4844-8b19-ea13c095d3f1', + } + }, 'sizes': [300, 250], 'mediaTypes': { 'banner': { @@ -1126,7 +1189,11 @@ let VALID_BID_REQUEST = [{ } }, 'adUnitCode': 'div-gpt-ad-1460505748561-123', - 'transactionId': 'c52a5c62-3c2b-4b90-9ff8-ec1487754822', + ortb2Imp: { + ext: { + tid: 'c52a5c62-3c2b-4b90-9ff8-ec1487754822', + } + }, 'sizes': [300, 251], 'mediaTypes': { 'banner': { @@ -1178,6 +1245,7 @@ let VALID_BID_REQUEST = [{ 'id': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', 'imp': [{ 'id': '28f8f8130a583e', + ortb2Imp: VALID_BID_REQUEST[0].ortb2Imp, 'transactionId': '277b631f-92f5-4844-8b19-ea13c095d3f1', 'ext': { 'dfp_id': 'div-gpt-ad-1460505748561-0', @@ -1210,6 +1278,7 @@ let VALID_BID_REQUEST = [{ } }, { 'id': '3f97ca71b1e5c2', + ortb2Imp: VALID_BID_REQUEST[1].ortb2Imp, 'transactionId': 'c52a5c62-3c2b-4b90-9ff8-ec1487754822', 'ext': { 'dfp_id': 'div-gpt-ad-1460505748561-123', @@ -1306,7 +1375,7 @@ describe('Media.net bid adapter', function () { it('should build valid payload on bid', function () { let requestObj = spec.buildRequests(VALID_BID_REQUEST, VALID_AUCTIONDATA); - expect(JSON.parse(requestObj.data)).to.deep.equal(VALID_PAYLOAD); + expect(JSON.parse(requestObj.data)).to.deep.include(VALID_PAYLOAD); }); it('should accept size as a one dimensional array', function () { @@ -1353,7 +1422,7 @@ describe('Media.net bid adapter', function () { bidreq = spec.buildRequests(VALID_BID_REQUEST, VALID_AUCTIONDATA); actual = JSON.parse(bidreq.data).imp[0].ortb2Imp; - assert.equal(actual, undefined) + expect(actual).to.deep.equal(VALID_BID_REQUEST[0].ortb2Imp); }); it('should have userid in bid request', function () { diff --git a/test/spec/modules/mediasniperBidAdapter_spec.js b/test/spec/modules/mediasniperBidAdapter_spec.js index 21ce5297c8f..30437205067 100644 --- a/test/spec/modules/mediasniperBidAdapter_spec.js +++ b/test/spec/modules/mediasniperBidAdapter_spec.js @@ -209,7 +209,7 @@ describe('mediasniperBidAdapter', function () { // openRTB 2.5 expect(data.cur[0]).to.equal(DEFAULT_CURRENCY); - expect(data.id).to.equal(bidderRequest.auctionId); + expect(data.id).to.exist; expect(data.imp.length).to.equal(1); expect(data.imp[0].id).to.equal(bidRequests[0].bidId); diff --git a/test/spec/modules/mediasquareBidAdapter_spec.js b/test/spec/modules/mediasquareBidAdapter_spec.js index 346d02d91d0..b2fbcb1ba59 100644 --- a/test/spec/modules/mediasquareBidAdapter_spec.js +++ b/test/spec/modules/mediasquareBidAdapter_spec.js @@ -1,16 +1,16 @@ import {expect} from 'chai'; import {spec} from 'modules/mediasquareBidAdapter.js'; -import {newBidder} from 'src/adapters/bidderFactory.js'; -import {config} from 'src/config.js'; -import * as utils from 'src/utils.js'; -import { requestBidsHook } from 'modules/consentManagement.js'; describe('MediaSquare bid adapter tests', function () { var DEFAULT_PARAMS = [{ adUnitCode: 'banner-div', bidId: 'aaaa1234', auctionId: 'bbbb1234', - transactionId: 'cccc1234', + ortb2Imp: { + ext: { + tid: 'cccc1234', + } + }, mediaTypes: { banner: { sizes: [ @@ -98,6 +98,8 @@ describe('MediaSquare bid adapter tests', function () { 'code': 'test/publishername_atf_desktop_rg_pave', 'bid_id': 'aaaa1234', 'adomain': ['test.com'], + 'context': 'instream', + 'increment': 1.0, }], }}; @@ -161,7 +163,12 @@ describe('MediaSquare bid adapter tests', function () { expect(bid.ttl).to.equal(300); expect(bid.requestId).to.equal('aaaa1234'); expect(bid.mediasquare).to.exist; + expect(bid.mediasquare.bidder).to.exist; expect(bid.mediasquare.bidder).to.equal('msqClassic'); + expect(bid.mediasquare.context).to.exist; + expect(bid.mediasquare.context).to.equal('instream'); + expect(bid.mediasquare.increment).to.exist; + expect(bid.mediasquare.increment).to.equal(1.0); expect(bid.mediasquare.code).to.equal([DEFAULT_PARAMS[0].params.owner, DEFAULT_PARAMS[0].params.code].join('/')); expect(bid.meta).to.exist; expect(bid.meta.advertiserDomains).to.exist; diff --git a/test/spec/modules/mgidBidAdapter_spec.js b/test/spec/modules/mgidBidAdapter_spec.js index c79cad6245d..f9bb1fb91e1 100644 --- a/test/spec/modules/mgidBidAdapter_spec.js +++ b/test/spec/modules/mgidBidAdapter_spec.js @@ -1,7 +1,9 @@ -import {assert, expect} from 'chai'; +import {expect} from 'chai'; import { spec, storage } from 'modules/mgidBidAdapter.js'; import { version } from 'package.json'; import * as utils from '../../../src/utils.js'; +import {USERSYNC_DEFAULT_CONFIG} from '../../../src/userSync'; +import {config} from '../../../src/config'; describe('Mgid bid adapter', function () { let sandbox; @@ -21,10 +23,10 @@ describe('Mgid bid adapter', function () { const ua = navigator.userAgent; const screenHeight = screen.height; const screenWidth = screen.width; - const dnt = (navigator.doNotTrack == 'yes' || navigator.doNotTrack == '1' || navigator.msDoNotTrack == '1') ? 1 : 0; + const dnt = (navigator.doNotTrack === 'yes' || navigator.doNotTrack === '1' || navigator.msDoNotTrack === '1') ? 1 : 0; const language = navigator.language ? 'language' : 'userLanguage'; let lang = navigator[language].split('-')[0]; - if (lang.length != 2 && lang.length != 3) { + if (lang.length !== 2 && lang.length !== 3) { lang = ''; } const secure = window.location.protocol === 'https:' ? 1 : 0; @@ -36,7 +38,7 @@ describe('Mgid bid adapter', function () { }); describe('isBidRequestValid', function () { - let bid = { + let sbid = { 'adUnitCode': 'div', 'bidder': 'mgid', 'params': { @@ -46,26 +48,26 @@ describe('Mgid bid adapter', function () { }; it('should not accept bid without required params', function () { - let isValid = spec.isBidRequestValid(bid); + let isValid = spec.isBidRequestValid(sbid); expect(isValid).to.equal(false); }); it('should return false when params are not passed', function () { - let bid = Object.assign({}, bid); + let bid = Object.assign({}, sbid); delete bid.params; bid.params = {}; expect(spec.isBidRequestValid(bid)).to.equal(false); }); it('should return false when valid params are not passed', function () { - let bid = Object.assign({}, bid); + let bid = Object.assign({}, sbid); delete bid.params; bid.params = {accountId: '', placementId: ''}; expect(spec.isBidRequestValid(bid)).to.equal(false); }); it('should return false when valid params are not passed', function () { - let bid = Object.assign({}, bid); + let bid = Object.assign({}, sbid); delete bid.params; bid.adUnitCode = ''; bid.mediaTypes = { @@ -78,7 +80,7 @@ describe('Mgid bid adapter', function () { }); it('should return false when adUnitCode not passed', function () { - let bid = Object.assign({}, bid); + let bid = Object.assign({}, sbid); delete bid.params; bid.adUnitCode = ''; bid.mediaTypes = { @@ -91,7 +93,7 @@ describe('Mgid bid adapter', function () { }); it('should return true when valid params are passed as nums', function () { - let bid = Object.assign({}, bid); + let bid = Object.assign({}, sbid); delete bid.params; bid.adUnitCode = 'div'; bid.mediaTypes = { @@ -104,7 +106,7 @@ describe('Mgid bid adapter', function () { }); it('should return false when valid params are not passed', function () { - let bid = Object.assign({}, bid); + let bid = Object.assign({}, sbid); delete bid.params; bid.mediaTypes = { native: { @@ -116,14 +118,14 @@ describe('Mgid bid adapter', function () { }); it('should return false when valid mediaTypes are not passed', function () { - let bid = Object.assign({}, bid); + let bid = Object.assign({}, sbid); delete bid.params; bid.params = {accountId: '1', placementId: '1'}; expect(spec.isBidRequestValid(bid)).to.equal(false); }); it('should return false when valid mediaTypes.banner are not passed', function () { - let bid = Object.assign({}, bid); + let bid = Object.assign({}, sbid); delete bid.params; bid.params = {accountId: '1', placementId: '1'}; bid.mediaTypes = { @@ -132,7 +134,7 @@ describe('Mgid bid adapter', function () { }); it('should return false when valid mediaTypes.banner.sizes are not passed', function () { - let bid = Object.assign({}, bid); + let bid = Object.assign({}, sbid); delete bid.params; bid.params = {accountId: '1', placementId: '1'}; bid.mediaTypes = { @@ -142,7 +144,7 @@ describe('Mgid bid adapter', function () { }); it('should return false when valid mediaTypes.banner.sizes are not valid', function () { - let bid = Object.assign({}, bid); + let bid = Object.assign({}, sbid); delete bid.params; bid.params = {accountId: '1', placementId: '1'}; bid.mediaTypes = { @@ -152,7 +154,7 @@ describe('Mgid bid adapter', function () { }); it('should return true when valid params are passed as strings', function () { - let bid = Object.assign({}, bid); + let bid = Object.assign({}, sbid); delete bid.params; bid.adUnitCode = 'div'; bid.params = {accountId: '1', placementId: '1'}; @@ -165,7 +167,7 @@ describe('Mgid bid adapter', function () { }); it('should return false when valid mediaTypes.native is not object', function () { - let bid = Object.assign({}, bid); + let bid = Object.assign({}, sbid); bid.params = {accountId: '1', placementId: '1'}; bid.mediaTypes = { native: [] @@ -174,7 +176,7 @@ describe('Mgid bid adapter', function () { }); it('should return false when mediaTypes.native is empty object', function () { - let bid = Object.assign({}, bid); + let bid = Object.assign({}, sbid); delete bid.params; bid.params = {accountId: '1', placementId: '1'}; bid.mediaTypes = { @@ -184,7 +186,7 @@ describe('Mgid bid adapter', function () { }); it('should return false when mediaTypes.native is invalid object', function () { - let bid = Object.assign({}, bid); + let bid = Object.assign({}, sbid); delete bid.params; bid.params = {accountId: '1', placementId: '1'}; bid.mediaTypes = { @@ -198,7 +200,7 @@ describe('Mgid bid adapter', function () { }); it('should return false when mediaTypes.native has unsupported required asset', function () { - let bid = Object.assign({}, bid); + let bid = Object.assign({}, sbid); bid.params = {accountId: '2', placementId: '1'}; bid.mediaTypes = { native: { @@ -217,7 +219,7 @@ describe('Mgid bid adapter', function () { }); it('should return true when mediaTypes.native all assets needed', function () { - let bid = Object.assign({}, bid); + let bid = Object.assign({}, sbid); bid.adUnitCode = 'div'; bid.params = {accountId: '2', placementId: '1'}; bid.mediaTypes = { @@ -237,7 +239,7 @@ describe('Mgid bid adapter', function () { }); describe('override defaults', function () { - let bid = { + let sbid = { bidder: 'mgid', params: { accountId: '1', @@ -245,7 +247,7 @@ describe('Mgid bid adapter', function () { }, }; it('should return object', function () { - let bid = Object.assign({}, bid); + let bid = Object.assign({}, sbid); bid.mediaTypes = { banner: { sizes: [[300, 250]] @@ -257,7 +259,7 @@ describe('Mgid bid adapter', function () { }); it('should return overwrite default bidurl', function () { - let bid = Object.assign({}, bid); + let bid = Object.assign({}, sbid); bid.params = { bidUrl: 'https://newbidurl.com/', accountId: '1', @@ -273,7 +275,7 @@ describe('Mgid bid adapter', function () { expect(request.url).to.include('https://newbidurl.com/1'); }); it('should return overwrite default bidFloor', function () { - let bid = Object.assign({}, bid); + let bid = Object.assign({}, sbid); bid.params = { bidFloor: 1.1, accountId: '1', @@ -294,7 +296,7 @@ describe('Mgid bid adapter', function () { expect(data.imp[0].bidfloor).to.deep.equal(1.1); }); it('should return overwrite default currency', function () { - let bid = Object.assign({}, bid); + let bid = Object.assign({}, sbid); bid.params = { cur: 'GBP', accountId: '1', @@ -323,6 +325,9 @@ describe('Mgid bid adapter', function () { placementId: '2', }, }; + afterEach(function () { + config.setConfig({coppa: undefined}) + }) it('should return undefined if no validBidRequests passed', function () { expect(spec.buildRequests([])).to.be.undefined; @@ -344,6 +349,7 @@ describe('Mgid bid adapter', function () { getDataFromLocalStorageStub.restore(); }); it('should proper handle gdpr', function () { + config.setConfig({coppa: 1}) let bid = Object.assign({}, abid); bid.mediaTypes = { banner: { @@ -351,12 +357,72 @@ describe('Mgid bid adapter', function () { } }; let bidRequests = [bid]; - const request = spec.buildRequests(bidRequests, {gdprConsent: {consentString: 'gdpr', gdprApplies: true}}); + const request = spec.buildRequests(bidRequests, {gdprConsent: {consentString: 'gdpr', gdprApplies: true}, uspConsent: 'usp', gppConsent: {gppString: 'gpp'}}); expect(request.url).deep.equal('https://prebid.mgid.com/prebid/1'); expect(request.method).deep.equal('POST'); const data = JSON.parse(request.data); expect(data.user).deep.equal({ext: {consent: 'gdpr'}}); - expect(data.regs).deep.equal({ext: {gdpr: 1}}); + expect(data.regs).deep.equal({ext: {gdpr: 1, us_privacy: 'usp'}, gpp: 'gpp', coppa: 1}); + }); + it('should handle refererInfo', function () { + let bid = Object.assign({}, abid); + bid.mediaTypes = { + banner: { + sizes: [[300, 250]] + } + }; + let bidRequests = [bid]; + const domain = 'site.com' + const page = `http://${domain}/site.html` + const ref = 'http://ref.com/ref.html' + const request = spec.buildRequests(bidRequests, {refererInfo: {page, ref}}); + expect(request.url).deep.equal('https://prebid.mgid.com/prebid/1'); + expect(request.method).deep.equal('POST'); + const data = JSON.parse(request.data); + expect(data.site.domain).to.deep.equal(domain); + expect(data.site.page).to.deep.equal(page); + expect(data.site.ref).to.deep.equal(ref); + }); + it('should handle schain', function () { + let bid = Object.assign({}, abid); + bid.mediaTypes = { + banner: { + sizes: [[300, 250]] + } + }; + bid.schain = ['schain1', 'schain2']; + let bidRequests = [bid]; + const request = spec.buildRequests(bidRequests); + const data = JSON.parse(request.data); + expect(data.source).to.deep.equal({ext: {schain: bid.schain}}); + }); + it('should handle userId', function () { + let bid = Object.assign({}, abid); + bid.mediaTypes = { + banner: { + sizes: [[300, 250]] + } + }; + let bidRequests = [bid]; + const bidderRequest = {userId: 'userid'}; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.url).deep.equal('https://prebid.mgid.com/prebid/1'); + expect(request.method).deep.equal('POST'); + const data = JSON.parse(request.data); + expect(data.user.id).to.deep.equal(bidderRequest.userId); + }); + it('should handle eids', function () { + let bid = Object.assign({}, abid); + bid.mediaTypes = { + banner: { + sizes: [[300, 250]] + } + }; + bid.userIdAsEids = ['eid1', 'eid2'] + let bidRequests = [bid]; + const request = spec.buildRequests(bidRequests); + const data = JSON.parse(request.data); + expect(data.user.ext.eids).to.deep.equal(bid.userIdAsEids); }); it('should return proper banner imp', function () { let bid = Object.assign({}, abid); @@ -386,7 +452,7 @@ describe('Mgid bid adapter', function () { expect(request).to.deep.equal({ 'method': 'POST', 'url': 'https://prebid.mgid.com/prebid/1', - 'data': '{"site":{"domain":"' + domain + '","page":"' + page + '"},"cur":["USD"],"geo":{"utcoffset":' + utcOffset + '},"device":{"ua":"' + ua + '","js":1,"dnt":' + dnt + ',"h":' + screenHeight + ',"w":' + screenWidth + ',"language":"' + lang + '"},"ext":{"mgid_ver":"' + mgid_ver + '","prebid_ver":"' + version + '"},"imp":[{"tagid":"2/div","secure":' + secure + ',"banner":{"w":300,"h":250}}]}', + 'data': `{"site":{"domain":"${domain}","page":"${page}"},"cur":["USD"],"geo":{"utcoffset":${utcOffset}},"device":{"ua":"${ua}","js":1,"dnt":${dnt},"h":${screenHeight},"w":${screenWidth},"language":"${lang}"},"ext":{"mgid_ver":"${mgid_ver}","prebid_ver":"${version}"},"imp":[{"tagid":"2/div","secure":${secure},"banner":{"w":300,"h":250}}],"tmax":3000}`, }); }); it('should not return native imp if minimum asset list not requested', function () { @@ -435,7 +501,7 @@ describe('Mgid bid adapter', function () { expect(request).to.deep.equal({ 'method': 'POST', 'url': 'https://prebid.mgid.com/prebid/1', - 'data': '{"site":{"domain":"' + domain + '","page":"' + page + '"},"cur":["USD"],"geo":{"utcoffset":' + utcOffset + '},"device":{"ua":"' + ua + '","js":1,"dnt":' + dnt + ',"h":' + screenHeight + ',"w":' + screenWidth + ',"language":"' + lang + '"},"ext":{"mgid_ver":"' + mgid_ver + '","prebid_ver":"' + version + '"},"imp":[{"tagid":"2/div","secure":' + secure + ',"native":{"request":{"plcmtcnt":1,"assets":[{"id":1,"required":1,"title":{"len":80}},{"id":2,"required":0,"img":{"type":3,"w":80,"h":80}},{"id":11,"required":0,"data":{"type":1}}]}}}]}', + 'data': `{"site":{"domain":"${domain}","page":"${page}"},"cur":["USD"],"geo":{"utcoffset":${utcOffset}},"device":{"ua":"${ua}","js":1,"dnt":${dnt},"h":${screenHeight},"w":${screenWidth},"language":"${lang}"},"ext":{"mgid_ver":"${mgid_ver}","prebid_ver":"${version}"},"imp":[{"tagid":"2/div","secure":${secure},"native":{"request":{"plcmtcnt":1,"assets":[{"id":1,"required":1,"title":{"len":80}},{"id":2,"required":0,"img":{"type":3,"w":80,"h":80}},{"id":11,"required":0,"data":{"type":1}}]}}}],"tmax":3000}`, }); }); it('should return proper native imp with image altered', function () { @@ -472,7 +538,7 @@ describe('Mgid bid adapter', function () { expect(request).to.deep.equal({ 'method': 'POST', 'url': 'https://prebid.mgid.com/prebid/1', - 'data': '{"site":{"domain":"' + domain + '","page":"' + page + '"},"cur":["USD"],"geo":{"utcoffset":' + utcOffset + '},"device":{"ua":"' + ua + '","js":1,"dnt":' + dnt + ',"h":' + screenHeight + ',"w":' + screenWidth + ',"language":"' + lang + '"},"ext":{"mgid_ver":"' + mgid_ver + '","prebid_ver":"' + version + '"},"imp":[{"tagid":"2/div","secure":' + secure + ',"native":{"request":{"plcmtcnt":1,"assets":[{"id":1,"required":1,"title":{"len":80}},{"id":2,"required":1,"img":{"type":3,"w":492,"h":328,"wmin":50,"hmin":50}},{"id":3,"required":0,"img":{"type":1,"w":50,"h":50}},{"id":11,"required":0,"data":{"type":1}}]}}}]}', + 'data': `{"site":{"domain":"${domain}","page":"${page}"},"cur":["USD"],"geo":{"utcoffset":${utcOffset}},"device":{"ua":"${ua}","js":1,"dnt":${dnt},"h":${screenHeight},"w":${screenWidth},"language":"${lang}"},"ext":{"mgid_ver":"${mgid_ver}","prebid_ver":"${version}"},"imp":[{"tagid":"2/div","secure":${secure},"native":{"request":{"plcmtcnt":1,"assets":[{"id":1,"required":1,"title":{"len":80}},{"id":2,"required":1,"img":{"type":3,"w":492,"h":328,"wmin":50,"hmin":50}},{"id":3,"required":0,"img":{"type":1,"w":50,"h":50}},{"id":11,"required":0,"data":{"type":1}}]}}}],"tmax":3000}`, }); }); it('should return proper native imp with sponsoredBy', function () { @@ -508,7 +574,7 @@ describe('Mgid bid adapter', function () { expect(request).to.deep.equal({ 'method': 'POST', 'url': 'https://prebid.mgid.com/prebid/1', - 'data': '{"site":{"domain":"' + domain + '","page":"' + page + '"},"cur":["USD"],"geo":{"utcoffset":' + utcOffset + '},"device":{"ua":"' + ua + '","js":1,"dnt":' + dnt + ',"h":' + screenHeight + ',"w":' + screenWidth + ',"language":"' + lang + '"},"ext":{"mgid_ver":"' + mgid_ver + '","prebid_ver":"' + version + '"},"imp":[{"tagid":"2/div","secure":' + secure + ',"native":{"request":{"plcmtcnt":1,"assets":[{"id":1,"required":1,"title":{"len":80}},{"id":2,"required":0,"img":{"type":3,"w":80,"h":80}},{"id":4,"required":0,"data":{"type":1}}]}}}]}', + 'data': `{"site":{"domain":"${domain}","page":"${page}"},"cur":["USD"],"geo":{"utcoffset":${utcOffset}},"device":{"ua":"${ua}","js":1,"dnt":${dnt},"h":${screenHeight},"w":${screenWidth},"language":"${lang}"},"ext":{"mgid_ver":"${mgid_ver}","prebid_ver":"${version}"},"imp":[{"tagid":"2/div","secure":${secure},"native":{"request":{"plcmtcnt":1,"assets":[{"id":1,"required":1,"title":{"len":80}},{"id":2,"required":0,"img":{"type":3,"w":80,"h":80}},{"id":4,"required":0,"data":{"type":1}}]}}}],"tmax":3000}`, }); }); it('should return proper banner request', function () { @@ -542,7 +608,7 @@ describe('Mgid bid adapter', function () { expect(request).to.deep.equal({ 'method': 'POST', 'url': 'https://prebid.mgid.com/prebid/1', - 'data': '{"site":{"domain":"' + domain + '","page":"' + page + '"},"cur":["USD"],"geo":{"utcoffset":' + utcOffset + '},"device":{"ua":"' + ua + '","js":1,"dnt":' + dnt + ',"h":' + screenHeight + ',"w":' + screenWidth + ',"language":"' + lang + '"},"ext":{"mgid_ver":"' + mgid_ver + '","prebid_ver":"' + version + '"},"imp":[{"tagid":"2/div","secure":' + secure + ',"banner":{"w":300,"h":600,"format":[{"w":300,"h":600},{"w":300,"h":250}],"pos":1}}]}', + 'data': `{"site":{"domain":"${domain}","page":"${page}"},"cur":["USD"],"geo":{"utcoffset":${utcOffset}},"device":{"ua":"${ua}","js":1,"dnt":${dnt},"h":${screenHeight},"w":${screenWidth},"language":"${lang}"},"ext":{"mgid_ver":"${mgid_ver}","prebid_ver":"${version}"},"imp":[{"tagid":"2/div","secure":${secure},"banner":{"w":300,"h":600,"format":[{"w":300,"h":600},{"w":300,"h":250}],"pos":1}}],"tmax":3000}`, }); }); it('should proper handle ortb2 data', function () { @@ -555,7 +621,14 @@ describe('Mgid bid adapter', function () { let bidRequests = [bid]; let bidderRequest = { + gdprConsent: { + consentString: 'consent1', + gdprApplies: false, + }, ortb2: { + bcat: ['bcat1', 'bcat2'], + badv: ['badv1.com', 'badv2.com'], + wlang: ['l1', 'l2'], site: { content: { data: [{ @@ -571,6 +644,9 @@ describe('Mgid bid adapter', function () { } }, user: { + ext: { + consent: 'consent2 ', + }, data: [{ name: 'mgid.com', ext: { @@ -581,6 +657,11 @@ describe('Mgid bid adapter', function () { {'id': '987'}, ], }] + }, + regs: { + ext: { + gdpr: 1, + } } } }; @@ -589,7 +670,13 @@ describe('Mgid bid adapter', function () { expect(request.url).deep.equal('https://prebid.mgid.com/prebid/1'); expect(request.method).deep.equal('POST'); const data = JSON.parse(request.data); - expect(data.ext).deep.include(bidderRequest.ortb2); + expect(data.bcat).deep.equal(bidderRequest.ortb2.bcat); + expect(data.badv).deep.equal(bidderRequest.ortb2.badv); + expect(data.wlang).deep.equal(bidderRequest.ortb2.wlang); + expect(data.site.content).deep.equal(bidderRequest.ortb2.site.content); + expect(data.regs).deep.equal(bidderRequest.ortb2.regs); + expect(data.user.data).deep.equal(bidderRequest.ortb2.user.data); + expect(data.user.ext).deep.equal(bidderRequest.ortb2.user.ext); }); }); @@ -727,8 +814,69 @@ describe('Mgid bid adapter', function () { }); describe('getUserSyncs', function () { - it('should do nothing on getUserSyncs', function () { - spec.getUserSyncs() + afterEach(function() { + config.setConfig({userSync: {syncsPerBidder: USERSYNC_DEFAULT_CONFIG.syncsPerBidder}}); + }); + it('should do nothing on getUserSyncs without inputs', function () { + expect(spec.getUserSyncs()).to.equal(undefined) + }); + it('should return frame object with empty consents', function () { + const sync = spec.getUserSyncs({iframeEnabled: true}) + expect(sync).to.have.length(1) + expect(sync[0]).to.have.property('type', 'iframe') + expect(sync[0]).to.have.property('url').match(/https:\/\/cm\.mgid\.com\/i\.html\?cbuster=\d+&gdpr_consent=&gdpr=0/) + }); + it('should return frame object with gdpr consent', function () { + const sync = spec.getUserSyncs({iframeEnabled: true}, undefined, {consentString: 'consent', gdprApplies: true}) + expect(sync).to.have.length(1) + expect(sync[0]).to.have.property('type', 'iframe') + expect(sync[0]).to.have.property('url').match(/https:\/\/cm\.mgid\.com\/i\.html\?cbuster=\d+&gdpr_consent=consent&gdpr=1/) + }); + it('should return frame object with gdpr + usp', function () { + const sync = spec.getUserSyncs({iframeEnabled: true}, undefined, {consentString: 'consent1', gdprApplies: true}, {'consentString': 'consent2'}) + expect(sync).to.have.length(1) + expect(sync[0]).to.have.property('type', 'iframe') + expect(sync[0]).to.have.property('url').match(/https:\/\/cm\.mgid\.com\/i\.html\?cbuster=\d+&gdpr_consent=consent1&gdpr=1&us_privacy=consent2/) + }); + it('should return img object with gdpr + usp', function () { + config.setConfig({userSync: {syncsPerBidder: undefined}}); + const sync = spec.getUserSyncs({pixelEnabled: true}, undefined, {consentString: 'consent1', gdprApplies: true}, {'consentString': 'consent2'}) + expect(sync).to.have.length(USERSYNC_DEFAULT_CONFIG.syncsPerBidder) + for (let i = 0; i < USERSYNC_DEFAULT_CONFIG.syncsPerBidder; i++) { + expect(sync[i]).to.have.property('type', 'image') + expect(sync[i]).to.have.property('url').match(/https:\/\/cm\.mgid\.com\/i\.gif\?cbuster=\d+&gdpr_consent=consent1&gdpr=1&us_privacy=consent2/) + } + }); + it('should return frame object with gdpr + usp', function () { + const sync = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, undefined, {consentString: 'consent1', gdprApplies: true}, {'consentString': 'consent2'}) + expect(sync).to.have.length(1) + expect(sync[0]).to.have.property('type', 'iframe') + expect(sync[0]).to.have.property('url').match(/https:\/\/cm\.mgid\.com\/i\.html\?cbuster=\d+&gdpr_consent=consent1&gdpr=1&us_privacy=consent2/) + }); + it('should return img (pixels) objects with gdpr + usp', function () { + const response = [{body: {ext: {cm: ['http://cm.mgid.com/i.gif?cdsp=1111', 'http://cm.mgid.com/i.gif']}}}] + const sync = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: true}, response, {consentString: 'consent1', gdprApplies: true}, {'consentString': 'consent2'}) + expect(sync).to.have.length(2) + expect(sync[0]).to.have.property('type', 'image') + expect(sync[0]).to.have.property('url').match(/http:\/\/cm\.mgid\.com\/i\.gif\?cdsp=1111&cbuster=\d+&gdpr_consent=consent1&gdpr=1&us_privacy=consent2/) + expect(sync[1]).to.have.property('type', 'image') + expect(sync[1]).to.have.property('url').match(/http:\/\/cm\.mgid\.com\/i\.gif\?cbuster=\d+&gdpr_consent=consent1&gdpr=1&us_privacy=consent2/) + }); + }); + + describe('getUserSyncs with img from ext.cm and gdpr + usp + coppa + gpp', function () { + afterEach(function() { + config.setConfig({coppa: undefined}) + }); + it('should return img (pixels) objects with gdpr + usp + coppa + gpp', function () { + config.setConfig({coppa: 1}); + const response = [{body: {ext: {cm: ['http://cm.mgid.com/i.gif?cdsp=1111', 'http://cm.mgid.com/i.gif']}}}] + const sync = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: true}, response, {consentString: 'consent1', gdprApplies: true}, {'consentString': 'consent2'}, {gppString: 'gpp'}) + expect(sync).to.have.length(2) + expect(sync[0]).to.have.property('type', 'image') + expect(sync[0]).to.have.property('url').match(/http:\/\/cm\.mgid\.com\/i\.gif\?cdsp=1111&cbuster=\d+&gdpr_consent=consent1&gdpr=1&us_privacy=consent2&gppString=gpp&coppa=1/) + expect(sync[1]).to.have.property('type', 'image') + expect(sync[1]).to.have.property('url').match(/http:\/\/cm\.mgid\.com\/i\.gif\?cbuster=\d+&gdpr_consent=consent1&gdpr=1&us_privacy=consent2&gppString=gpp&coppa=1/) }); }); diff --git a/test/spec/modules/mgidXBidAdapter_spec.js b/test/spec/modules/mgidXBidAdapter_spec.js new file mode 100644 index 00000000000..14619e9c0e1 --- /dev/null +++ b/test/spec/modules/mgidXBidAdapter_spec.js @@ -0,0 +1,426 @@ +import { expect } from 'chai'; +import { spec } from '../../../modules/mgidXBidAdapter.js'; +import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; +import { getUniqueIdentifierStr } from '../../../src/utils.js'; +import { config } from '../../../src/config'; +import { USERSYNC_DEFAULT_CONFIG } from '../../../src/userSync'; + +const bidder = 'mgidX' +const adUrl = 'https://us-east-x.mgid.com/pbjs'; + +describe('MGIDXBidAdapter', function () { + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + placementId: 'testBanner', + } + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [VIDEO]: { + playerSize: [[300, 300]], + minduration: 5, + maxduration: 60 + } + }, + params: { + placementId: 'testVideo', + } + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [NATIVE]: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + } + }, + params: { + placementId: 'testNative', + } + } + ]; + + const invalidBid = { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + + } + } + + const bidderRequest = { + uspConsent: '1---', + gdprConsent: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + refererInfo: { + referer: 'https://test.com' + } + }; + + describe('isBidRequestValid', function () { + it('Should return true if there are bidId, params and key parameters present', function () { + expect(spec.isBidRequestValid(bids[0])).to.be.true; + }); + it('Should return false if at least one of parameters is not present', function () { + expect(spec.isBidRequestValid(invalidBid)).to.be.false; + }); + }); + + describe('buildRequests', function () { + let serverRequest = spec.buildRequests(bids, bidderRequest); + + it('Creates a ServerRequest object with method, URL and data', function () { + expect(serverRequest).to.exist; + expect(serverRequest.method).to.exist; + expect(serverRequest.url).to.exist; + expect(serverRequest.data).to.exist; + }); + + it('Returns POST method', function () { + expect(serverRequest.method).to.equal('POST'); + }); + + it('Returns valid URL', function () { + expect(serverRequest.url).to.equal(adUrl); + }); + + it('Returns general data valid', function () { + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.all.keys('deviceWidth', + 'deviceHeight', + 'language', + 'secure', + 'host', + 'page', + 'placements', + 'coppa', + 'ccpa', + 'gdpr', + 'tmax' + ); + expect(data.deviceWidth).to.be.a('number'); + expect(data.deviceHeight).to.be.a('number'); + expect(data.language).to.be.a('string'); + expect(data.secure).to.be.within(0, 1); + expect(data.host).to.be.a('string'); + expect(data.page).to.be.a('string'); + expect(data.coppa).to.be.a('number'); + expect(data.gdpr).to.be.a('string'); + expect(data.ccpa).to.be.a('string'); + expect(data.tmax).to.be.a('number'); + expect(data.placements).to.have.lengthOf(3); + }); + + it('Returns valid placements', function () { + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.placementId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('publisher'); + + if (placement.adFormat === BANNER) { + expect(placement.sizes).to.be.an('array'); + } + switch (placement.adFormat) { + case BANNER: + expect(placement.sizes).to.be.an('array'); + break; + case VIDEO: + expect(placement.playerSize).to.be.an('array'); + expect(placement.minduration).to.be.an('number'); + expect(placement.maxduration).to.be.an('number'); + break; + case NATIVE: + expect(placement.native).to.be.an('object'); + break; + } + } + }); + + it('Returns data with gdprConsent and without uspConsent', function () { + delete bidderRequest.uspConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data.gdpr).to.exist; + expect(data.gdpr).to.be.a('string'); + expect(data.gdpr).to.equal(bidderRequest.gdprConsent); + expect(data.ccpa).to.not.exist; + delete bidderRequest.gdprConsent; + }); + + it('Returns data with uspConsent and without gdprConsent', function () { + bidderRequest.uspConsent = '1---'; + delete bidderRequest.gdprConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data.ccpa).to.exist; + expect(data.ccpa).to.be.a('string'); + expect(data.ccpa).to.equal(bidderRequest.uspConsent); + expect(data.gdpr).to.not.exist; + }); + + it('Returns empty data if no valid requests are passed', function () { + serverRequest = spec.buildRequests([], bidderRequest); + let data = serverRequest.data; + expect(data.placements).to.be.an('array').that.is.empty; + }); + }); + + describe('interpretResponse', function () { + it('Should interpret banner response', function () { + const banner = { + body: [{ + mediaType: 'banner', + width: 300, + height: 250, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + let bannerResponses = spec.interpretResponse(banner); + expect(bannerResponses).to.be.an('array').that.is.not.empty; + let dataItem = bannerResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); + expect(dataItem.requestId).to.equal(banner.body[0].requestId); + expect(dataItem.cpm).to.equal(banner.body[0].cpm); + expect(dataItem.width).to.equal(banner.body[0].width); + expect(dataItem.height).to.equal(banner.body[0].height); + expect(dataItem.ad).to.equal(banner.body[0].ad); + expect(dataItem.ttl).to.equal(banner.body[0].ttl); + expect(dataItem.creativeId).to.equal(banner.body[0].creativeId); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal(banner.body[0].currency); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + it('Should interpret video response', function () { + const video = { + body: [{ + vastUrl: 'test.com', + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + let videoResponses = spec.interpretResponse(video); + expect(videoResponses).to.be.an('array').that.is.not.empty; + + let dataItem = videoResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'vastUrl', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.5); + expect(dataItem.vastUrl).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + it('Should interpret native response', function () { + const native = { + body: [{ + mediaType: 'native', + native: { + clickUrl: 'test.com', + title: 'Test', + image: 'test.com', + impressionTrackers: ['test.com'], + }, + ttl: 120, + cpm: 0.4, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + let nativeResponses = spec.interpretResponse(native); + expect(nativeResponses).to.be.an('array').that.is.not.empty; + + let dataItem = nativeResponses[0]; + expect(dataItem).to.have.keys('requestId', 'cpm', 'ttl', 'creativeId', 'netRevenue', 'currency', 'mediaType', 'native', 'meta'); + expect(dataItem.native).to.have.keys('clickUrl', 'impressionTrackers', 'title', 'image') + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.4); + expect(dataItem.native.clickUrl).to.equal('test.com'); + expect(dataItem.native.title).to.equal('Test'); + expect(dataItem.native.image).to.equal('test.com'); + expect(dataItem.native.impressionTrackers).to.be.an('array').that.is.not.empty; + expect(dataItem.native.impressionTrackers[0]).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + it('Should return an empty array if invalid banner response is passed', function () { + const invBanner = { + body: [{ + width: 300, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + + let serverResponses = spec.interpretResponse(invBanner); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid video response is passed', function () { + const invVideo = { + body: [{ + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let serverResponses = spec.interpretResponse(invVideo); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid native response is passed', function () { + const invNative = { + body: [{ + mediaType: 'native', + clickUrl: 'test.com', + title: 'Test', + impressionTrackers: ['test.com'], + ttl: 120, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + }] + }; + let serverResponses = spec.interpretResponse(invNative); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid response is passed', function () { + const invalid = { + body: [{ + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let serverResponses = spec.interpretResponse(invalid); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + }); + + describe('getUserSyncs', function () { + afterEach(function() { + config.setConfig({userSync: {syncsPerBidder: USERSYNC_DEFAULT_CONFIG.syncsPerBidder}}); + }); + it('should do nothing on getUserSyncs without inputs', function () { + expect(spec.getUserSyncs()).to.equal(undefined) + }); + it('should return frame object with empty consents', function () { + const sync = spec.getUserSyncs({iframeEnabled: true}) + expect(sync).to.have.length(1) + expect(sync[0]).to.have.property('type', 'iframe') + expect(sync[0]).to.have.property('url').match(/https:\/\/cm\.mgid\.com\/i\.html\?cbuster=\d+&gdpr_consent=&gdpr=0/) + }); + it('should return frame object with gdpr consent', function () { + const sync = spec.getUserSyncs({iframeEnabled: true}, undefined, {consentString: 'consent', gdprApplies: true}) + expect(sync).to.have.length(1) + expect(sync[0]).to.have.property('type', 'iframe') + expect(sync[0]).to.have.property('url').match(/https:\/\/cm\.mgid\.com\/i\.html\?cbuster=\d+&gdpr_consent=consent&gdpr=1/) + }); + it('should return frame object with gdpr + usp', function () { + const sync = spec.getUserSyncs({iframeEnabled: true}, undefined, {consentString: 'consent1', gdprApplies: true}, {'consentString': 'consent2'}) + expect(sync).to.have.length(1) + expect(sync[0]).to.have.property('type', 'iframe') + expect(sync[0]).to.have.property('url').match(/https:\/\/cm\.mgid\.com\/i\.html\?cbuster=\d+&gdpr_consent=consent1&gdpr=1&us_privacy=consent2/) + }); + it('should return img object with gdpr + usp', function () { + config.setConfig({userSync: {syncsPerBidder: undefined}}); + const sync = spec.getUserSyncs({pixelEnabled: true}, undefined, {consentString: 'consent1', gdprApplies: true}, {'consentString': 'consent2'}) + expect(sync).to.have.length(USERSYNC_DEFAULT_CONFIG.syncsPerBidder) + for (let i = 0; i < USERSYNC_DEFAULT_CONFIG.syncsPerBidder; i++) { + expect(sync[i]).to.have.property('type', 'image') + expect(sync[i]).to.have.property('url').match(/https:\/\/cm\.mgid\.com\/i\.gif\?cbuster=\d+&gdpr_consent=consent1&gdpr=1&us_privacy=consent2/) + } + }); + it('should return frame object with gdpr + usp', function () { + const sync = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, undefined, {consentString: 'consent1', gdprApplies: true}, {'consentString': 'consent2'}) + expect(sync).to.have.length(1) + expect(sync[0]).to.have.property('type', 'iframe') + expect(sync[0]).to.have.property('url').match(/https:\/\/cm\.mgid\.com\/i\.html\?cbuster=\d+&gdpr_consent=consent1&gdpr=1&us_privacy=consent2/) + }); + it('should return img (pixels) objects with gdpr + usp', function () { + const response = [{body: {ext: {cm: ['http://cm.mgid.com/i.gif?cdsp=1111', 'http://cm.mgid.com/i.gif']}}}] + const sync = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: true}, response, {consentString: 'consent1', gdprApplies: true}, {'consentString': 'consent2'}) + expect(sync).to.have.length(2) + expect(sync[0]).to.have.property('type', 'image') + expect(sync[0]).to.have.property('url').match(/http:\/\/cm\.mgid\.com\/i\.gif\?cdsp=1111&cbuster=\d+&gdpr_consent=consent1&gdpr=1&us_privacy=consent2/) + expect(sync[1]).to.have.property('type', 'image') + expect(sync[1]).to.have.property('url').match(/http:\/\/cm\.mgid\.com\/i\.gif\?cbuster=\d+&gdpr_consent=consent1&gdpr=1&us_privacy=consent2/) + }); + }); +}); diff --git a/test/spec/modules/microadBidAdapter_spec.js b/test/spec/modules/microadBidAdapter_spec.js index 98edfce20bc..bd6d04a6312 100644 --- a/test/spec/modules/microadBidAdapter_spec.js +++ b/test/spec/modules/microadBidAdapter_spec.js @@ -12,7 +12,11 @@ describe('microadBidAdapter', () => { spot: 'spot-code' }, bidId: 'bid-id', - transactionId: 'transaction-id' + ortb2Imp: { + ext: { + tid: 'transaction-id' + } + } }; describe('isBidRequestValid', () => { @@ -224,7 +228,7 @@ describe('microadBidAdapter', () => { }); }); - it('should add geo parameter to response if request parameters contain geo', () => { + it('should not add geo parameter to response even if request parameters contain geo', () => { const bidRequestWithGeo = Object.assign({}, bidRequestTemplate, { params: { spot: 'spot-code', @@ -233,7 +237,7 @@ describe('microadBidAdapter', () => { }); const requests = spec.buildRequests([bidRequestWithGeo], bidderRequest); requests.forEach(request => { - expect(request.data).to.deep.equal( + expect(request.data).to.not.deep.equal( Object.assign({}, expectedResultTemplate, { cbt: request.data.cbt, geo: '35.655275,139.693771' diff --git a/test/spec/modules/minutemediaBidAdapter_spec.js b/test/spec/modules/minutemediaBidAdapter_spec.js index bbd23918031..48f694bc79d 100644 --- a/test/spec/modules/minutemediaBidAdapter_spec.js +++ b/test/spec/modules/minutemediaBidAdapter_spec.js @@ -59,7 +59,8 @@ describe('minutemediaAdapter', function () { 'mediaTypes': { 'video': { 'playerSize': [[640, 480]], - 'context': 'instream' + 'context': 'instream', + 'plcmt': 1 } }, 'vastXml': '"..."' @@ -103,6 +104,9 @@ describe('minutemediaAdapter', function () { bidderCode: 'minutemedia', } const placementId = '12345678'; + const api = [1, 2]; + const mimes = ['application/javascript', 'video/mp4', 'video/quicktime']; + const protocols = [2, 3, 5, 6]; it('sends the placementId to ENDPOINT via POST', function () { bidRequests[0].params.placementId = placementId; @@ -110,6 +114,18 @@ describe('minutemediaAdapter', function () { expect(request.data.bids[0].placementId).to.equal(placementId); }); + it('sends the plcmt to ENDPOINT via POST', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.bids[0].plcmt).to.equal(1); + }); + + it('sends the is_wrapper parameter to ENDPOINT via POST', function() { + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.have.property('is_wrapper'); + expect(request.data.params.is_wrapper).to.equal(false); + }); + it('sends bid request to ENDPOINT via POST', function () { const request = spec.buildRequests(bidRequests, bidderRequest); expect(request.url).to.equal(ENDPOINT); @@ -127,6 +143,27 @@ describe('minutemediaAdapter', function () { expect(request.data.bids[0].bidId).to.equal('299ffc8cca0b87'); }); + it('should send the correct supported api array', function () { + bidRequests[0].mediaTypes.video.api = api; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.bids[0].api).to.be.an('array'); + expect(request.data.bids[0].api).to.eql([1, 2]); + }); + + it('should send the correct mimes array', function () { + bidRequests[1].mediaTypes.banner.mimes = mimes; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.bids[1].mimes).to.be.an('array'); + expect(request.data.bids[1].mimes).to.eql(['application/javascript', 'video/mp4', 'video/quicktime']); + }); + + it('should send the correct protocols array', function () { + bidRequests[0].mediaTypes.video.protocols = protocols; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.bids[0].protocols).to.be.an('array'); + expect(request.data.bids[0].protocols).to.eql([2, 3, 5, 6]); + }); + it('should send the correct sizes array', function () { const request = spec.buildRequests(bidRequests, bidderRequest); expect(request.data.bids[0].sizes).to.be.an('array'); @@ -293,6 +330,86 @@ describe('minutemediaAdapter', function () { expect(request.data.bids[0]).to.be.an('object'); expect(request.data.bids[0]).to.have.property('floorPrice', 1.5); }); + + it('should check sua param in bid request', function() { + const sua = { + 'platform': { + 'brand': 'macOS', + 'version': ['12', '4', '0'] + }, + 'browsers': [ + { + 'brand': 'Chromium', + 'version': [ '106', '0', '5249', '119' ] + }, + { + 'brand': 'Google Chrome', + 'version': [ '106', '0', '5249', '119' ] + }, + { + 'brand': 'Not;A=Brand', + 'version': [ '99', '0', '0', '0' ] + } + ], + 'mobile': 0, + 'model': '', + 'bitness': '64', + 'architecture': 'x86' + } + const bid = utils.deepClone(bidRequests[0]); + bid.ortb2 = { + 'device': { + 'sua': { + 'platform': { + 'brand': 'macOS', + 'version': [ '12', '4', '0' ] + }, + 'browsers': [ + { + 'brand': 'Chromium', + 'version': [ '106', '0', '5249', '119' ] + }, + { + 'brand': 'Google Chrome', + 'version': [ '106', '0', '5249', '119' ] + }, + { + 'brand': 'Not;A=Brand', + 'version': [ '99', '0', '0', '0' ] + } + ], + 'mobile': 0, + 'model': '', + 'bitness': '64', + 'architecture': 'x86' + } + } + } + const requestWithSua = spec.buildRequests([bid], bidderRequest); + const data = requestWithSua.data; + expect(data.bids[0].sua).to.exist; + expect(data.bids[0].sua).to.deep.equal(sua); + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.bids[0].sua).to.not.exist; + }); + + describe('COPPA Param', function() { + it('should set coppa equal 0 in bid request if coppa is set to false', function() { + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.bids[0].coppa).to.be.equal(0); + }); + + it('should set coppa equal 1 in bid request if coppa is set to true', function() { + const bid = utils.deepClone(bidRequests[0]); + bid.ortb2 = { + 'regs': { + 'coppa': true, + } + }; + const request = spec.buildRequests([bid], bidderRequest); + expect(request.data.bids[0].coppa).to.be.equal(1); + }); + }); }); describe('interpretResponse', function () { diff --git a/test/spec/modules/minutemediaplusBidAdapter_spec.js b/test/spec/modules/minutemediaplusBidAdapter_spec.js index de38554f010..33ec194c61f 100644 --- a/test/spec/modules/minutemediaplusBidAdapter_spec.js +++ b/test/spec/modules/minutemediaplusBidAdapter_spec.js @@ -1,7 +1,6 @@ -import { expect } from 'chai'; +import {expect} from 'chai'; import { spec as adapter, - SUPPORTED_ID_SYSTEMS, createDomain, hashCode, extractPID, @@ -13,9 +12,12 @@ import { getUniqueDealId, } from 'modules/minutemediaplusBidAdapter.js'; import * as utils from 'src/utils.js'; -import { version } from 'package.json'; -import { useFakeTimers } from 'sinon'; -import { BANNER, VIDEO } from '../../../src/mediaTypes'; +import {version} from 'package.json'; +import {useFakeTimers} from 'sinon'; +import {BANNER, VIDEO} from '../../../src/mediaTypes'; +import {config} from '../../../src/config'; + +export const TEST_ID_SYSTEMS = ['britepoolid', 'criteoId', 'id5id', 'idl_env', 'lipb', 'netId', 'parrableId', 'pubcid', 'tdid', 'pubProvidedId']; const SUB_DOMAIN = 'exchange'; @@ -33,19 +35,36 @@ const BID = { } }, 'placementCode': 'div-gpt-ad-1460505748561-0', - 'transactionId': 'c881914b-a3b5-4ecf-ad9c-1c2f37c6aabf', 'sizes': [[300, 250], [300, 600]], 'bidderRequestId': '1fdb5ff1b6eaa7', + 'auctionId': 'auction_id', + 'bidRequestsCount': 4, + 'bidderRequestsCount': 3, + 'bidderWinsCount': 1, 'requestId': 'b0777d85-d061-450e-9bc7-260dd54bbb7a', 'schain': 'a0819c69-005b-41ed-af06-1be1e0aefefc', - 'mediaTypes': [BANNER] + 'mediaTypes': [BANNER], + 'ortb2Imp': { + 'ext': { + 'gpid': '1234567890', + tid: 'c881914b-a3b5-4ecf-ad9c-1c2f37c6aabf', + } + } }; const VIDEO_BID = { 'bidId': '2d52001cabd527', 'adUnitCode': '63550ad1ff6642d368cba59dh5884270560', 'bidderRequestId': '12a8ae9ada9c13', - 'transactionId': '56e184c6-bde9-497b-b9b9-cf47a61381ee', + 'auctionId': 'auction_id', + 'bidRequestsCount': 4, + 'bidderRequestsCount': 3, + 'bidderWinsCount': 1, + ortb2Imp: { + ext: { + tid: '56e184c6-bde9-497b-b9b9-cf47a61381ee', + } + }, 'schain': 'a0819c69-005b-41ed-af06-1be1e0aefefc', 'params': { 'subDomain': SUB_DOMAIN, @@ -78,11 +97,37 @@ const BIDDER_REQUEST = { 'consentString': 'consent_string', 'gdprApplies': true }, + 'gppString': 'gpp_string', + 'gppSid': [7], 'uspConsent': 'consent_string', 'refererInfo': { 'page': 'https://www.greatsite.com', 'ref': 'https://www.somereferrer.com' - } + }, + 'ortb2': { + 'regs': { + 'gpp': 'gpp_string', + 'gpp_sid': [7] + }, + 'device': { + 'sua': { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, + {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, + {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + } + } + }, }; const SERVER_RESPONSE = { @@ -134,7 +179,7 @@ const REQUEST = { function getTopWindowQueryParams() { try { - const parsedUrl = utils.parseUrl(window.top.document.URL, { decodeSearchAsString: true }); + const parsedUrl = utils.parseUrl(window.top.document.URL, {decodeSearchAsString: true}); return parsedUrl.search; } catch (e) { return ''; @@ -213,6 +258,9 @@ describe('MinuteMediaPlus Bid Adapter', function () { it('should build video request', function () { const hashUrl = hashCode(BIDDER_REQUEST.refererInfo.page); + config.setConfig({ + bidderTimeout: 3000 + }); const requests = adapter.buildRequests([VIDEO_BID], BIDDER_REQUEST); expect(requests).to.have.length(1); expect(requests[0]).to.deep.equal({ @@ -223,17 +271,42 @@ describe('MinuteMediaPlus Bid Adapter', function () { bidFloor: 0.1, bidId: '2d52001cabd527', bidderVersion: adapter.version, + bidderRequestId: '12a8ae9ada9c13', cb: 1000, gdpr: 1, gdprConsent: 'consent_string', usPrivacy: 'consent_string', + gppString: 'gpp_string', + gppSid: [7], prebidVersion: version, + transactionId: '56e184c6-bde9-497b-b9b9-cf47a61381ee', + auctionId: 'auction_id', + bidRequestsCount: 4, + bidderRequestsCount: 3, + bidderWinsCount: 1, + bidderTimeout: 3000, publisherId: '59ac17c192832d0011283fe3', url: 'https%3A%2F%2Fwww.greatsite.com', referrer: 'https://www.somereferrer.com', res: `${window.top.screen.width}x${window.top.screen.height}`, schain: VIDEO_BID.schain, sizes: ['545x307'], + sua: { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, + {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, + {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + }, uniqueDealId: `${hashUrl}_${Date.now().toString()}`, uqs: getTopWindowQueryParams(), mediaTypes: { @@ -252,13 +325,17 @@ describe('MinuteMediaPlus Bid Adapter', function () { protocols: [2, 3, 5, 6], startdelay: 0 } - } + }, + gpid: '' } }); }); it('should build banner request for each size', function () { const hashUrl = hashCode(BIDDER_REQUEST.refererInfo.page); + config.setConfig({ + bidderTimeout: 3000 + }); const requests = adapter.buildRequests([BID], BIDDER_REQUEST); expect(requests).to.have.length(1); expect(requests[0]).to.deep.equal({ @@ -267,8 +344,33 @@ describe('MinuteMediaPlus Bid Adapter', function () { data: { gdprConsent: 'consent_string', gdpr: 1, + gppString: 'gpp_string', + gppSid: [7], usPrivacy: 'consent_string', + transactionId: 'c881914b-a3b5-4ecf-ad9c-1c2f37c6aabf', + auctionId: 'auction_id', + bidRequestsCount: 4, + bidderRequestsCount: 3, + bidderWinsCount: 1, + bidderTimeout: 3000, + bidderRequestId: '1fdb5ff1b6eaa7', sizes: ['300x250', '300x600'], + sua: { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, + {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, + {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + }, url: 'https%3A%2F%2Fwww.greatsite.com', referrer: 'https://www.somereferrer.com', cb: 1000, @@ -282,6 +384,7 @@ describe('MinuteMediaPlus Bid Adapter', function () { schain: BID.schain, res: `${window.top.screen.width}x${window.top.screen.height}`, mediaTypes: [BANNER], + gpid: '1234567890', uqs: getTopWindowQueryParams(), 'ext.param1': 'loremipsum', 'ext.param2': 'dolorsitamet', @@ -296,7 +399,7 @@ describe('MinuteMediaPlus Bid Adapter', function () { }); describe('getUserSyncs', function () { it('should have valid user sync with iframeEnabled', function () { - const result = adapter.getUserSyncs({ iframeEnabled: true }, [SERVER_RESPONSE]); + const result = adapter.getUserSyncs({iframeEnabled: true}, [SERVER_RESPONSE]); expect(result).to.deep.equal([{ type: 'iframe', @@ -305,7 +408,7 @@ describe('MinuteMediaPlus Bid Adapter', function () { }); it('should have valid user sync with cid on response', function () { - const result = adapter.getUserSyncs({ iframeEnabled: true }, [SERVER_RESPONSE]); + const result = adapter.getUserSyncs({iframeEnabled: true}, [SERVER_RESPONSE]); expect(result).to.deep.equal([{ type: 'iframe', url: 'https://sync.minutemedia-prebid.com/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=' @@ -313,7 +416,7 @@ describe('MinuteMediaPlus Bid Adapter', function () { }); it('should have valid user sync with pixelEnabled', function () { - const result = adapter.getUserSyncs({ pixelEnabled: true }, [SERVER_RESPONSE]); + const result = adapter.getUserSyncs({pixelEnabled: true}, [SERVER_RESPONSE]); expect(result).to.deep.equal([{ 'url': 'https://sync.minutemedia-prebid.com/api/sync/image/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=', @@ -329,12 +432,12 @@ describe('MinuteMediaPlus Bid Adapter', function () { }); it('should return empty array when there is no ad', function () { - const responses = adapter.interpretResponse({ price: 1, ad: '' }); + const responses = adapter.interpretResponse({price: 1, ad: ''}); expect(responses).to.be.empty; }); it('should return empty array when there is no price', function () { - const responses = adapter.interpretResponse({ price: null, ad: 'great ad' }); + const responses = adapter.interpretResponse({price: null, ad: 'great ad'}); expect(responses).to.be.empty; }); @@ -357,6 +460,19 @@ describe('MinuteMediaPlus Bid Adapter', function () { }); }); + it('should get meta from response metaData', function () { + const serverResponse = utils.deepClone(SERVER_RESPONSE); + serverResponse.body.results[0].metaData = { + advertiserDomains: ['minutemedia-prebid.com'], + agencyName: 'Agency Name', + }; + const responses = adapter.interpretResponse(serverResponse, REQUEST); + expect(responses[0].meta).to.deep.equal({ + advertiserDomains: ['minutemedia-prebid.com'], + agencyName: 'Agency Name' + }); + }); + it('should return an array of interpreted video responses', function () { const responses = adapter.interpretResponse(VIDEO_SERVER_RESPONSE, REQUEST); expect(responses).to.have.length(1); @@ -387,18 +503,18 @@ describe('MinuteMediaPlus Bid Adapter', function () { }); describe('user id system', function () { - Object.keys(SUPPORTED_ID_SYSTEMS).forEach((idSystemProvider) => { + TEST_ID_SYSTEMS.forEach((idSystemProvider) => { const id = Date.now().toString(); const bid = utils.deepClone(BID); const userId = (function () { switch (idSystemProvider) { case 'lipb': - return { lipbid: id }; + return {lipbid: id}; case 'parrableId': - return { eid: id }; + return {eid: id}; case 'id5id': - return { uid: id }; + return {uid: id}; default: return id; } @@ -417,18 +533,18 @@ describe('MinuteMediaPlus Bid Adapter', function () { describe('alternate param names extractors', function () { it('should return undefined when param not supported', function () { - const cid = extractCID({ 'c_id': '1' }); - const pid = extractPID({ 'p_id': '1' }); - const subDomain = extractSubDomain({ 'sub_domain': 'prebid' }); + const cid = extractCID({'c_id': '1'}); + const pid = extractPID({'p_id': '1'}); + const subDomain = extractSubDomain({'sub_domain': 'prebid'}); expect(cid).to.be.undefined; expect(pid).to.be.undefined; expect(subDomain).to.be.undefined; }); it('should return value when param supported', function () { - const cid = extractCID({ 'cID': '1' }); - const pid = extractPID({ 'Pid': '2' }); - const subDomain = extractSubDomain({ 'subDOMAIN': 'prebid' }); + const cid = extractCID({'cID': '1'}); + const pid = extractPID({'Pid': '2'}); + const subDomain = extractSubDomain({'subDOMAIN': 'prebid'}); expect(cid).to.be.equal('1'); expect(pid).to.be.equal('2'); expect(subDomain).to.be.equal('prebid'); @@ -488,7 +604,7 @@ describe('MinuteMediaPlus Bid Adapter', function () { now }); setStorageItem('myKey', 2020); - const { value, created } = getStorageItem('myKey'); + const {value, created} = getStorageItem('myKey'); expect(created).to.be.equal(now); expect(value).to.be.equal(2020); expect(typeof value).to.be.equal('number'); @@ -504,8 +620,8 @@ describe('MinuteMediaPlus Bid Adapter', function () { }); it('should parse JSON value', function () { - const data = JSON.stringify({ event: 'send' }); - const { event } = tryParseJSON(data); + const data = JSON.stringify({event: 'send'}); + const {event} = tryParseJSON(data); expect(event).to.be.equal('send'); }); diff --git a/test/spec/modules/missenaBidAdapter_spec.js b/test/spec/modules/missenaBidAdapter_spec.js index 157137b4730..f61987298e8 100644 --- a/test/spec/modules/missenaBidAdapter_spec.js +++ b/test/spec/modules/missenaBidAdapter_spec.js @@ -13,6 +13,8 @@ describe('Missena Adapter', function () { sizes: [[1, 1]], params: { apiKey: 'PA-34745704', + placement: 'sticky', + formats: ['sticky-banner'], }, }; @@ -70,6 +72,14 @@ describe('Missena Adapter', function () { expect(payload.request_id).to.equal(bidId); }); + it('should send placement', function () { + expect(payload.placement).to.equal('sticky'); + }); + + it('should send formats', function () { + expect(payload.formats).to.eql(['sticky-banner']); + }); + it('should send referer information to the request', function () { expect(payload.referer).to.equal('https://referer'); expect(payload.referer_canonical).to.equal('https://canonical'); diff --git a/test/spec/modules/nativoBidAdapter_spec.js b/test/spec/modules/nativoBidAdapter_spec.js index 4d70e6f7071..51e78d1f6d6 100644 --- a/test/spec/modules/nativoBidAdapter_spec.js +++ b/test/spec/modules/nativoBidAdapter_spec.js @@ -8,6 +8,10 @@ import { getPageUrlFromBidRequest, hasProtocol, addProtocol, + BidRequestDataSource, + RequestData, + UserEIDs, + buildRequestUrl, } from '../../../modules/nativoBidAdapter' describe('bidDataMap', function () { @@ -120,6 +124,7 @@ describe('nativoBidAdapterTests', function () { expect(request.url).to.be.a('string') expect(request.url).to.include('?') + expect(request.url).to.include('ntv_pbv') expect(request.url).to.include('ntv_ptd') expect(request.url).to.include('ntv_pb_rid') expect(request.url).to.include('ntv_ppc') @@ -731,3 +736,99 @@ describe('getPageUrlFromBidRequest', () => { expect(url).not.to.be.undefined }) }) + +describe('RequestData', () => { + describe('addBidRequestDataSource', () => { + it('Adds a BidRequestDataSource', () => { + const requestData = new RequestData() + const testBidRequestDataSource = new BidRequestDataSource() + + requestData.addBidRequestDataSource(testBidRequestDataSource) + + expect(requestData.bidRequestDataSources.length == 1) + }) + + it("Doeasn't add a non BidRequestDataSource", () => { + const requestData = new RequestData() + + requestData.addBidRequestDataSource({}) + requestData.addBidRequestDataSource('test') + requestData.addBidRequestDataSource(1) + requestData.addBidRequestDataSource(true) + + expect(requestData.bidRequestDataSources.length == 0) + }) + }) + + describe('getRequestDataString', () => { + it("Doesn't append empty query strings", () => { + const requestData = new RequestData() + const testBidRequestDataSource = new BidRequestDataSource() + + requestData.addBidRequestDataSource(testBidRequestDataSource) + + let qs = requestData.getRequestDataQueryString() + expect(qs).to.be.empty + + testBidRequestDataSource.getRequestQueryString = () => { + return 'ntv_test=true' + } + qs = requestData.getRequestDataQueryString() + expect(qs).to.be.equal('ntv_test=true') + }) + }) +}) + +describe('UserEIDs', () => { + const userEids = new UserEIDs() + const eids = [{ 'testId': 1111 }] + + describe('processBidRequestData', () => { + it('Processes bid request without eids', () => { + userEids.processBidRequestData({}) + + expect(userEids.eids).to.be.empty + }) + + it('Processed bid request with eids', () => { + userEids.processBidRequestData({ userIdAsEids: eids }) + + expect(userEids.eids).to.not.be.empty + }) + }) + + describe('getRequestQueryString', () => { + it('Correctly prints out QS param string', () => { + const qs = userEids.getRequestQueryString() + const value = qs.slice(11) + + expect(qs).to.include('ntv_pb_eid=') + try { + expect(JSON.parse(value)).to.be.equal(eids) + } catch (err) { } + }) + }) +}) + +describe('buildRequestUrl', () => { + const baseUrl = 'https://www.testExchange.com' + it('Returns baseUrl if no QS strings passed', () => { + const url = buildRequestUrl(baseUrl) + expect(url).to.be.equal(baseUrl) + }) + + it('Returns baseUrl if empty QS strings passed', () => { + const url = buildRequestUrl(baseUrl, ['', '', '']) + expect(url).to.be.equal(baseUrl) + }) + + it('Returns baseUrl + QS params if QS strings passed', () => { + const url = buildRequestUrl(baseUrl, ['ntv_ptd=123456&ntv_test=true', 'ntv_foo=bar']) + expect(url).to.be.equal(`${baseUrl}?ntv_ptd=123456&ntv_test=true&ntv_foo=bar`) + }) + + it('Returns baseUrl + QS params if mixed QS strings passed', () => { + const url = buildRequestUrl(baseUrl, ['ntv_ptd=123456&ntv_test=true', '', '', 'ntv_foo=bar']) + expect(url).to.be.equal(`${baseUrl}?ntv_ptd=123456&ntv_test=true&ntv_foo=bar`) + }) +}) diff --git a/test/spec/modules/neuwoRtdProvider_spec.js b/test/spec/modules/neuwoRtdProvider_spec.js index 7f47fba072b..0ad3d7c1f74 100644 --- a/test/spec/modules/neuwoRtdProvider_spec.js +++ b/test/spec/modules/neuwoRtdProvider_spec.js @@ -4,7 +4,8 @@ import * as neuwo from 'modules/neuwoRtdProvider'; const PUBLIC_TOKEN = 'public_key_0000'; const config = () => ({ params: { - publicToken: PUBLIC_TOKEN + publicToken: PUBLIC_TOKEN, + apiUrl: 'https://testing-requirement.neuwo.api' } }) @@ -59,7 +60,6 @@ describe('neuwoRtdProvider', function () { neuwo.injectTopics(topics, bidsConfig, () => { }) expect(bidsConfig.ortb2Fragments.global.site.content.data[0].name, 'name of first content data object').to.equal(neuwo.DATA_PROVIDER) expect(bidsConfig.ortb2Fragments.global.site.content.data[0].segment[0].id, 'id of first segment in content.data').to.equal(TAX_ID) - expect(bidsConfig.ortb2Fragments.global.site.cattax, 'category taxonomy code for pagecat').to.equal(6) // CATTAX_IAB expect(bidsConfig.ortb2Fragments.global.site.pagecat[0], 'category taxonomy code for pagecat').to.equal(TAX_ID) }) diff --git a/test/spec/modules/nextMillenniumBidAdapter_spec.js b/test/spec/modules/nextMillenniumBidAdapter_spec.js index 7f40e7d89f8..564788c8b56 100644 --- a/test/spec/modules/nextMillenniumBidAdapter_spec.js +++ b/test/spec/modules/nextMillenniumBidAdapter_spec.js @@ -173,9 +173,9 @@ describe('nextMillenniumBidAdapterTests', function() { }); it('validate_generated_params', function() { - const request = spec.buildRequests(bidRequestData); + const request = spec.buildRequests(bidRequestData, {bidderRequestId: 'mock-uuid'}); expect(request[0].bidId).to.equal('bid1234'); - expect(JSON.parse(request[0].data).id).to.equal('b06c5141-fe8f-4cdf-9d7d-54415490a917'); + expect(JSON.parse(request[0].data).id).to.exist; }); it('use parameters group_id', function() { diff --git a/test/spec/modules/nexx360BidAdapter_spec.js b/test/spec/modules/nexx360BidAdapter_spec.js index 7645ee59f63..7c2cea99a46 100644 --- a/test/spec/modules/nexx360BidAdapter_spec.js +++ b/test/spec/modules/nexx360BidAdapter_spec.js @@ -1,9 +1,41 @@ -import {expect} from 'chai'; -import {spec} from 'modules/nexx360BidAdapter.js'; -import {newBidder} from 'src/adapters/bidderFactory.js'; -import {config} from 'src/config.js'; -import * as utils from 'src/utils.js'; -import { requestBidsHook } from 'modules/consentManagement.js'; +import { expect } from 'chai'; +import { + spec, storage, getNexx360LocalStorage, +} from 'modules/nexx360BidAdapter.js'; +import { sandbox } from 'sinon'; + +const instreamResponse = { + 'id': '2be64380-ba0c-405a-ab53-51f51c7bde51', + 'cur': 'USD', + 'seatbid': [ + { + 'bid': [ + { + 'id': '8275140264321181514', + 'impid': '263cba3b8bfb72', + 'price': 5, + 'adomain': [ + 'appnexus.com' + ], + 'crid': '97517771', + 'h': 1, + 'w': 1, + 'ext': { + 'mediaType': 'instream', + 'ssp': 'appnexus', + 'divId': 'video1', + 'adUnitCode': 'video1', + 'vastXml': '\n \n \n Nexx360 Wrapper\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ' + } + } + ], + 'seat': 'appnexus' + } + ], + 'ext': { + 'cookies': [] + } +}; describe('Nexx360 bid adapter tests', function () { const DISPLAY_BID_REQUEST = { @@ -110,6 +142,32 @@ describe('Nexx360 bid adapter tests', function () { 'auctionId': '98932591-c822-42e3-850e-4b3cf748d063', } }); + + it('We verify isBidRequestValid with unvalid adUnitName', function() { + bannerBid.params = { adUnitName: 1 }; + expect(spec.isBidRequestValid(bannerBid)).to.be.equal(false); + }); + + it('We verify isBidRequestValid with empty adUnitName', function() { + bannerBid.params = { adUnitName: '' }; + expect(spec.isBidRequestValid(bannerBid)).to.be.equal(false); + }); + + it('We verify isBidRequestValid with unvalid adUnitPath', function() { + bannerBid.params = { adUnitPath: 1 }; + expect(spec.isBidRequestValid(bannerBid)).to.be.equal(false); + }); + + it('We verify isBidRequestValid with unvalid divId', function() { + bannerBid.params = { divId: 1 }; + expect(spec.isBidRequestValid(bannerBid)).to.be.equal(false); + }); + + it('We verify isBidRequestValid unvalid allBids', function() { + bannerBid.params = { allBids: 1 }; + expect(spec.isBidRequestValid(bannerBid)).to.be.equal(false); + }); + it('We verify isBidRequestValid with uncorrect tagid', function() { bannerBid.params = { 'tagid': 'luvxjvgn' }; expect(spec.isBidRequestValid(bannerBid)).to.be.equal(false); @@ -121,63 +179,87 @@ describe('Nexx360 bid adapter tests', function () { }); }); - describe('when request is for a multiformat ad', function () { - describe('and request config uses mediaTypes video and banner', () => { - const multiformatBid = { - 'bidder': 'nexx360', - 'params': {'tagId': 'luvxjvgn'}, - 'mediaTypes': { - 'banner': { - 'sizes': [[300, 250]] - }, - 'video': { - 'playerSize': [300, 250] - } - }, - 'adUnitCode': 'div-1', - 'transactionId': '70bdc37e-9475-4b27-8c74-4634bdc2ee66', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '4906582fc87d0c', - 'bidderRequestId': '332fda16002dbe', - 'auctionId': '98932591-c822-42e3-850e-4b3cf748d063', - } - it('should return true multisize when required params found', function () { - expect(spec.isBidRequestValid(multiformatBid)).to.equal(true); - }); + describe('getNexx360LocalStorage disabled', function () { + before(function () { + sandbox.stub(storage, 'localStorageIsEnabled').callsFake(() => false); }); - }); + it('We test if we get the nexx360Id', function() { + const output = getNexx360LocalStorage(); + expect(output).to.be.eql(false); + }); + after(function () { + sandbox.restore() + }); + }) - describe('when request is for a video ad', function () { - describe('and request config uses mediaTypes video and banner', () => { - const videoBid = { - 'bidder': 'nexx360', - 'params': {'tagId': 'luvxjvgn'}, - 'mediaTypes': { - 'video': { - 'playerSize': [300, 250] - } - }, - 'adUnitCode': 'div-1', - 'transactionId': '70bdc37e-9475-4b27-8c74-4634bdc2ee66', - 'bidId': '4906582fc87d0c', - 'bidderRequestId': '332fda16002dbe', - 'auctionId': '98932591-c822-42e3-850e-4b3cf748d063', - } - it('should return true multisize when required params found', function () { - expect(spec.isBidRequestValid(videoBid)).to.equal(true); - }); + describe('getNexx360LocalStorage enabled but nothing', function () { + before(function () { + sandbox.stub(storage, 'localStorageIsEnabled').callsFake(() => true); + sandbox.stub(storage, 'setDataInLocalStorage'); + sandbox.stub(storage, 'getDataFromLocalStorage').callsFake((key) => null); }); - }); + it('We test if we get the nexx360Id', function() { + const output = getNexx360LocalStorage(); + expect(typeof output.nexx360Id).to.be.eql('string'); + }); + after(function () { + sandbox.restore() + }); + }) + + describe('getNexx360LocalStorage enabled but wrong payload', function () { + before(function () { + sandbox.stub(storage, 'localStorageIsEnabled').callsFake(() => true); + sandbox.stub(storage, 'setDataInLocalStorage'); + sandbox.stub(storage, 'getDataFromLocalStorage').callsFake((key) => '{"nexx360Id":"5ad89a6e-7801-48e7-97bb-fe6f251f6cb4",}'); + }); + it('We test if we get the nexx360Id', function() { + const output = getNexx360LocalStorage(); + expect(output).to.be.eql(false); + }); + after(function () { + sandbox.restore() + }); + }) + + describe('getNexx360LocalStorage enabled', function () { + before(function () { + sandbox.stub(storage, 'localStorageIsEnabled').callsFake(() => true); + sandbox.stub(storage, 'setDataInLocalStorage'); + sandbox.stub(storage, 'getDataFromLocalStorage').callsFake((key) => '{"nexx360Id":"5ad89a6e-7801-48e7-97bb-fe6f251f6cb4"}'); + }); + it('We test if we get the nexx360Id', function() { + const output = getNexx360LocalStorage(); + expect(output.nexx360Id).to.be.eql('5ad89a6e-7801-48e7-97bb-fe6f251f6cb4'); + }); + after(function () { + sandbox.restore() + }); + }) describe('buildRequests()', function() { + before(function () { + const documentStub = sandbox.stub(document, 'getElementById'); + documentStub.withArgs('div-1').returns({ + offsetWidth: 200, + offsetHeight: 250, + style: { + maxWidth: '400px', + maxHeight: '350px', + } + }); + }); describe('We test with a multiple display bids', function() { const sampleBids = [ { bidder: 'nexx360', params: { - tagId: 'luvxjvgn' + tagId: 'luvxjvgn', + divId: 'div-1', + adUnitName: 'header-ad', + adUnitPath: '/12345/nexx360/Homepage/HP/Header-Ad', }, - adUnitCode: 'div-1', + adUnitCode: 'header-ad-1234', transactionId: '469a570d-f187-488d-b1cb-48c1a2009be9', sizes: [[300, 250], [300, 600]], bidId: '44a2706ac3574', @@ -221,7 +303,7 @@ describe('Nexx360 bid adapter tests', function () { sizes: [[728, 90], [970, 250]] } }, - adUnitCode: 'div-2', + adUnitCode: 'div-2-abcd', transactionId: '6196885d-4e76-40dc-a09c-906ed232626b', sizes: [[728, 90], [970, 250]], bidId: '5ba94555219a03', @@ -272,67 +354,86 @@ describe('Nexx360 bid adapter tests', function () { const request = spec.buildRequests(displayBids, bidderRequest); const requestContent = request.data; expect(request).to.have.property('method').and.to.equal('POST'); - expect(requestContent.id).to.be.eql('2e684815-b44e-4e04-b812-56da54adbe74'); expect(requestContent.cur[0]).to.be.eql('USD'); expect(requestContent.imp.length).to.be.eql(2); expect(requestContent.imp[0].id).to.be.eql('44a2706ac3574'); - expect(requestContent.imp[0].tagid).to.be.eql('div-1'); + expect(requestContent.imp[0].tagid).to.be.eql('header-ad'); expect(requestContent.imp[0].ext.divId).to.be.eql('div-1'); + expect(requestContent.imp[0].ext.adUnitCode).to.be.eql('header-ad-1234'); + expect(requestContent.imp[0].ext.adUnitName).to.be.eql('header-ad'); + expect(requestContent.imp[0].ext.adUnitPath).to.be.eql('/12345/nexx360/Homepage/HP/Header-Ad'); + expect(requestContent.imp[0].ext.dimensions.slotW).to.be.eql(200); + expect(requestContent.imp[0].ext.dimensions.slotH).to.be.eql(250); + expect(requestContent.imp[0].ext.dimensions.cssMaxW).to.be.eql('400px'); + expect(requestContent.imp[0].ext.dimensions.cssMaxH).to.be.eql('350px'); expect(requestContent.imp[0].ext.nexx360.tagId).to.be.eql('luvxjvgn'); - expect(requestContent.imp[0].ext.nexx360.allBids).to.be.eql(false); expect(requestContent.imp[0].banner.format.length).to.be.eql(2); expect(requestContent.imp[0].banner.format[0].w).to.be.eql(300); expect(requestContent.imp[0].banner.format[0].h).to.be.eql(250); expect(requestContent.imp[1].ext.nexx360.allBids).to.be.eql(true); - expect(requestContent.regs.ext.gdpr).to.be.eql(1); - expect(requestContent.user.ext.consent).to.be.eql(bidderRequest.gdprConsent.consentString); - expect(requestContent.user.ext.eids.length).to.be.eql(2); + expect(requestContent.imp[1].tagid).to.be.eql('div-2-abcd'); + expect(requestContent.imp[1].ext.adUnitCode).to.be.eql('div-2-abcd'); + expect(requestContent.imp[1].ext.divId).to.be.eql('div-2-abcd'); + expect(requestContent.ext.bidderVersion).to.be.eql('2.0'); + expect(requestContent.ext.source).to.be.eql('prebid.js'); }); - it('We perform a test with a multiformat adunit', function() { - const multiformatBids = [...sampleBids]; - multiformatBids[0].mediaTypes = { - banner: { - sizes: [[300, 250], [300, 600]] - }, - video: { - context: 'outstream', - playerSize: [640, 480], - mimes: ['video/mp4'], - protocols: [1, 2, 3, 4, 5, 6, 7, 8], - playbackmethod: [2], - skip: 1, - playback_method: ['auto_play_sound_off'] - } - }; - const request = spec.buildRequests(multiformatBids, bidderRequest); - const requestContent = request.data; - expect(requestContent.imp[0].video.context).to.be.eql('outstream'); - expect(requestContent.imp[0].video.playbackmethod[0]).to.be.eql(2); - }); + if (FEATURES.VIDEO) { + it('We perform a test with a multiformat adunit', function() { + const multiformatBids = [...sampleBids]; + multiformatBids[0].mediaTypes = { + banner: { + sizes: [[300, 250], [300, 600]] + }, + video: { + context: 'outstream', + playerSize: [640, 480], + mimes: ['video/mp4'], + protocols: [1, 2, 3, 4, 5, 6, 7, 8], + playbackmethod: [2], + skip: 1, + playback_method: ['auto_play_sound_off'] + } + }; + const request = spec.buildRequests(multiformatBids, bidderRequest); + const requestContent = request.data; + expect(requestContent.imp[0].video.ext.context).to.be.eql('outstream'); + expect(requestContent.imp[0].video.playbackmethod[0]).to.be.eql(2); + }); - it('We perform a test with a video adunit', function() { - const videoBids = [sampleBids[0]]; - videoBids[0].mediaTypes = { - video: { - context: 'instream', - playerSize: [640, 480], - mimes: ['video/mp4'], - protocols: [1, 2, 3, 4, 5, 6], - playbackmethod: [2], - skip: 1 - } - }; - const request = spec.buildRequests(videoBids, bidderRequest); - const requestContent = request.data; - expect(request).to.have.property('method').and.to.equal('POST'); - expect(requestContent.imp[0].video.context).to.be.eql('instream'); - expect(requestContent.imp[0].video.playbackmethod[0]).to.be.eql(2); - }) + it('We perform a test with a instream adunit', function() { + const videoBids = [sampleBids[0]]; + videoBids[0].mediaTypes = { + video: { + context: 'instream', + playerSize: [640, 480], + mimes: ['video/mp4'], + protocols: [1, 2, 3, 4, 5, 6], + playbackmethod: [2], + skip: 1 + } + }; + const request = spec.buildRequests(videoBids, bidderRequest); + const requestContent = request.data; + expect(request).to.have.property('method').and.to.equal('POST'); + expect(requestContent.imp[0].video.ext.context).to.be.eql('instream'); + expect(requestContent.imp[0].video.playbackmethod[0]).to.be.eql(2); + }) + } + }); + after(function () { + sandbox.restore() }); }); describe('interpretResponse()', function() { + it('empty response', function() { + const response = { + body: '' + }; + const output = spec.interpretResponse(response); + expect(output.length).to.be.eql(0); + }); it('banner responses', function() { const response = { body: { @@ -345,7 +446,6 @@ describe('Nexx360 bid adapter tests', function () { 'id': '4427551302944024629', 'impid': '226175918ebeda', 'price': 1.5, - 'type': 'banner', 'adomain': [ 'http://prebid.org' ], @@ -356,141 +456,153 @@ describe('Nexx360 bid adapter tests', function () { 'cat': [ 'IAB3-1' ], - 'creativeuuid': 'fdddcebc-1edf-489d-880d-1418d8bdc493', - 'adUrl': 'https://fast.nexx360.io/cache?uuid=fdddcebc-1edf-489d-880d-1418d8bdc493', 'ext': { - 'dsp_id': 'ssp1', - 'buyer_id': 'foo', - 'brand_id': 'bar' + 'adUnitCode': 'div-1', + 'mediaType': 'banner', + 'adUrl': 'https://fast.nexx360.io/cache?uuid=fdddcebc-1edf-489d-880d-1418d8bdc493', + 'ssp': 'appnexus', } } ], 'seat': 'appnexus' } ], - 'cookies': [] + 'ext': { + 'id': 'de3de7c7-e1cf-4712-80a9-94eb26bfc718', + 'cookies': [] + }, } }; const output = spec.interpretResponse(response); - expect(output[0].adUrl).to.be.eql(response.body.seatbid[0].bid[0].adUrl); - expect(output[0].mediaType).to.be.eql(response.body.seatbid[0].bid[0].type); + expect(output[0].adUrl).to.be.eql(response.body.seatbid[0].bid[0].ext.adUrl); + expect(output[0].mediaType).to.be.eql(response.body.seatbid[0].bid[0].ext.mediaType); expect(output[0].currency).to.be.eql(response.body.cur); expect(output[0].cpm).to.be.eql(response.body.seatbid[0].bid[0].price); - expect(output[0].meta.networkId).to.be.eql(response.body.seatbid[0].bid[0].ext.dsp_id); - expect(output[0].meta.advertiserId).to.be.eql(response.body.seatbid[0].bid[0].ext.buyer_id); - expect(output[0].meta.brandId).to.be.eql(response.body.seatbid[0].bid[0].ext.brand_id); }); - it('video responses', function() { + it('instream responses', function() { const response = { body: { - 'id': '33894759-0ea2-41f1-84b3-75132eefedb6', + 'id': '2be64380-ba0c-405a-ab53-51f51c7bde51', 'cur': 'USD', 'seatbid': [ { 'bid': [ { - 'id': '294478680080716675', - 'impid': '2c835a6039e65f', + 'id': '8275140264321181514', + 'impid': '263cba3b8bfb72', 'price': 5, - 'type': 'instream', 'adomain': [ - '' + 'appnexus.com' ], 'crid': '97517771', - 'ssp': 'appnexus', 'h': 1, 'w': 1, - 'vastXml': '\n \n \n prebid.org wrapper\n \n \n \n \n \n ' + 'ext': { + 'mediaType': 'instream', + 'ssp': 'appnexus', + 'adUnitCode': 'video1', + 'vastXml': '\n \n \n Nexx360 Wrapper\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ' + } } ], 'seat': 'appnexus' } ], - 'cookies': [] + 'ext': { + 'cookies': [] + } } }; const output = spec.interpretResponse(response); - expect(output[0].vastXml).to.be.eql(response.body.seatbid[0].bid[0].vastXml); + expect(output[0].vastXml).to.be.eql(response.body.seatbid[0].bid[0].ext.vastXml); expect(output[0].mediaType).to.be.eql('video'); expect(output[0].currency).to.be.eql(response.body.cur); expect(output[0].cpm).to.be.eql(response.body.seatbid[0].bid[0].price); }); - }); - describe('interpretResponse()', function() { - it('banner responses', function() { + it('outstream responses', function() { const response = { body: { - 'id': 'a8d3a675-a4ba-4d26-807f-c8f2fad821e0', + 'id': '40c23932-135e-4602-9701-ca36f8d80c07', 'cur': 'USD', 'seatbid': [ { 'bid': [ { - 'id': '4427551302944024629', - 'impid': '226175918ebeda', - 'price': 1.5, - 'type': 'banner', + 'id': '1186971142548769361', + 'impid': '4ce809b61a3928', + 'price': 5, 'adomain': [ - 'http://prebid.org' - ], - 'crid': '98493581', - 'ssp': 'appnexus', - 'h': 600, - 'w': 300, - 'cat': [ - 'IAB3-1' + 'appnexus.com' ], - 'creativeuuid': 'fdddcebc-1edf-489d-880d-1418d8bdc493', - 'adUrl': 'https://fast.nexx360.io/cache?uuid=fdddcebc-1edf-489d-880d-1418d8bdc493' + 'crid': '97517771', + 'h': 1, + 'w': 1, + 'ext': { + 'mediaType': 'outstream', + 'ssp': 'appnexus', + 'adUnitCode': 'div-1', + 'vastXml': '\n \n \n Nexx360 Wrapper\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ' + } } ], 'seat': 'appnexus' } ], - 'cookies': [] + 'ext': { + 'cookies': [] + } } }; const output = spec.interpretResponse(response); - expect(output[0].adUrl).to.be.eql(response.body.seatbid[0].bid[0].adUrl); - expect(output[0].mediaType).to.be.eql(response.body.seatbid[0].bid[0].type); + expect(output[0].vastXml).to.be.eql(response.body.seatbid[0].bid[0].ext.vastXml); + expect(output[0].mediaType).to.be.eql('video'); expect(output[0].currency).to.be.eql(response.body.cur); + expect(typeof output[0].renderer).to.be.eql('object'); expect(output[0].cpm).to.be.eql(response.body.seatbid[0].bid[0].price); }); - it('video responses', function() { + + it('native responses', function() { const response = { body: { - 'id': '33894759-0ea2-41f1-84b3-75132eefedb6', + 'id': '3c0290c1-6e75-4ef7-9e37-17f5ebf3bfa3', 'cur': 'USD', 'seatbid': [ { 'bid': [ { - 'id': '294478680080716675', - 'impid': '2c835a6039e65f', - 'price': 5, - 'type': 'instream', + 'id': '6624930625245272225', + 'impid': '23e11d845514bb', + 'price': 10, 'adomain': [ - '' + 'prebid.org' ], - 'crid': '97517771', - 'ssp': 'appnexus', + 'crid': '97494204', 'h': 1, 'w': 1, - 'vastXml': '\n \n \n prebid.org wrapper\n \n \n \n \n \n ' + 'cat': [ + 'IAB3-1' + ], + 'ext': { + 'mediaType': 'native', + 'ssp': 'appnexus', + 'adUnitCode': '/19968336/prebid_native_example_1' + }, + 'adm': '{"ver":"1.2","assets":[{"id":1,"img":{"url":"https:\\/\\/vcdn.adnxs.com\\/p\\/creative-image\\/f8\\/7f\\/0f\\/13\\/f87f0f13-230c-4f05-8087-db9216e393de.jpg","w":989,"h":742,"ext":{"appnexus":{"prevent_crop":0}}}},{"id":0,"title":{"text":"This is a Prebid Native Creative"}},{"id":2,"data":{"value":"Prebid.org"}}],"link":{"url":"https:\\/\\/ams3-ib.adnxs.com\\/click?AAAAAAAAJEAAAAAAAAAkQAAAAAAAACRAAAAAAAAAJEAAAAAAAAAkQKZS4ZZl5vVbR6p-A-MwnyTZ7QVkAAAAAOLoyQBtJAAAbSQAAAIAAAC8pM8FnPgWAAAAAABVU0QAVVNEAAEAAQBNXQAAAAABAgMCAAAAALoAURe69gAAAAA.\\/bcr=AAAAAAAA8D8=\\/pp=${AUCTION_PRICE}\\/cnd=%21JBC72Aj8-LwKELzJvi4YnPFbIAQoADEAAAAAAAAkQDoJQU1TMzo2MTM1QNAwSQAAAAAAAPA_UQAAAAAAAAAAWQAAAAAAAAAAYQAAAAAAAAAAaQAAAAAAAAAAcQAAAAAAAAAAeACJAQAAAAAAAAAA\\/cca=OTMyNSNBTVMzOjYxMzU=\\/bn=97062\\/clickenc=http%3A%2F%2Fprebid.org%2Fdev-docs%2Fshow-native-ads.html"},"eventtrackers":[{"event":1,"method":1,"url":"https:\\/\\/ams3-ib.adnxs.com\\/it?an_audit=0&referrer=https%3A%2F%2Ftest.nexx360.io%2Fadapter%2Fnative%2Ftest.html&e=wqT_3QKJCqAJBQAAAwDWAAUBCNnbl6AGEKalhbfZzPn6WxjH1PqbsJzMzyQqNgkAAAECCCRAEQEHEAAAJEAZEQkAIREJACkRCQAxEQmoMOLRpwY47UhA7UhIAlC8yb4uWJzxW2AAaM26dXim9gWAAQGKAQNVU0SSAQEG9F4BmAEBoAEBqAEBsAEAuAECwAEDyAEC0AEJ2AEA4AEA8AEAigIpdWYoJ2EnLCAyNTI5ODg1LCAwKTt1ZigncicsIDk3NDk0MjA0LCAwKTuSAvEDIS0xRDNJQWo4LUx3S0VMekp2aTRZQUNDYzhWc3dBRGdBUUFSSTdVaFE0dEduQmxnQVlQX19fXzhQYUFCd0FYZ0JnQUVCaUFFQmtBRUJtQUVCb0FFQnFBRURzQUVBdVFIenJXcWtBQUFrUU1FQjg2MXFwQUFBSkVESkFYSUtWbWViSmZJXzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFNQUNBY2dDQWRBQ0FkZ0NBZUFDQU9nQ0FQZ0NBSUFEQVpnREFib0RDVUZOVXpNNk5qRXpOZUFEMERDSUJBQ1FCQUNZQkFIQkJBQUFBQUFBQUFBQXlRUUFBCQscQUFOZ0VBUEURlSxBQUFDSUJmY3ZxUVUBDQRBQQGoCDdFRgEKCQEMREJCUQkKAQEAeRUoAUwyKAAAWi4oALg0QVhBaEQzd0JhTEQzd0w0QmQyMG1nR0NCZ05WVTBTSUJnQ1FCZ0dZQmdDaEJnQQFONEFBQ1JBcUFZQnNnWWtDHXQARR0MAEcdDABJHQw8dUFZS5oClQEhSkJDNzJBajL1ASRuUEZiSUFRb0FEFfhUa1FEb0pRVTFUTXpvMk1UTTFRTkF3UxFRDFBBX1URDAxBQUFXHQwAWR0MAGEdDABjHQwQZUFDSkEdEMjYAvfpA-ACrZhI6gIwaHR0cHM6Ly90ZXN0Lm5leHgzNjAuaW8vYWRhcHRlci9uYXRpdmUJH_CaaHRtbIADAIgDAZADAJgDFKADAaoDAMAD4KgByAMA2AMA4AMA6AMA-AMDgAQAkgQJL29wZW5ydGIymAQAqAQAsgQMCAAQABgAIAAwADgAuAQAwASA2rgiyAQA0gQOOTMyNSNBTVMzOjYxMzXaBAIIAeAEAPAEvMm-LvoEEgkAAABAPG1IQBEAAACgV8oCQIgFAZgFAKAF______8BBbABqgUkM2MwMjkwYzEtNmU3NS00ZWY3LTllMzctMTdmNWViZjNiZmEzwAUAyQWJFxTwP9IFCQkJDHgAANgFAeAFAfAFmfQh-gUECAAQAJAGAZgGALgGAMEGCSUo8D_QBvUv2gYWChAJERkBAdpg4AYM8gYCCACABwGIBwCgB0HIB6b2BdIHDRVkASYI2gcGAV1oGADgBwDqBwIIAPAHAIoIAhAAlQgAAIA_mAgB&s=ccf63f2e483a37091d2475d895e7cf7c911d1a78&pp=${AUCTION_PRICE}"}]}' } ], 'seat': 'appnexus' } ], - 'cookies': [] + 'ext': { + 'cookies': [], + } } }; const output = spec.interpretResponse(response); - expect(output[0].vastXml).to.be.eql(response.body.seatbid[0].bid[0].vastXml); - expect(output[0].mediaType).to.be.eql('video'); - expect(output[0].currency).to.be.eql(response.body.cur); - expect(output[0].cpm).to.be.eql(response.body.seatbid[0].bid[0].price); + expect(output[0].native.ortb.ver).to.be.eql('1.2'); + expect(output[0].native.ortb.assets[0].id).to.be.eql(1); + expect(output[0].mediaType).to.be.eql('native'); }); }); @@ -501,7 +613,9 @@ describe('Nexx360 bid adapter tests', function () { expect(syncs).to.have.lengthOf(0); }); it('Verifies user sync with cookies in bid response', function () { - response.body.cookies = [{'type': 'image', 'url': 'http://www.cookie.sync.org/'}]; + response.body.ext = { + cookies: [{'type': 'image', 'url': 'http://www.cookie.sync.org/'}] + }; var syncs = spec.getUserSyncs({}, [response], DEFAULT_OPTIONS.gdprConsent); expect(syncs).to.have.lengthOf(1); expect(syncs[0]).to.have.property('type').and.to.equal('image'); diff --git a/test/spec/modules/nobidBidAdapter_spec.js b/test/spec/modules/nobidBidAdapter_spec.js index 7ea89f7dd3f..8328aae33d8 100644 --- a/test/spec/modules/nobidBidAdapter_spec.js +++ b/test/spec/modules/nobidBidAdapter_spec.js @@ -14,6 +14,38 @@ describe('Nobid Adapter', function () { }); }); + describe('buildRequestsWithFloor', function () { + const SITE_ID = 2; + const REFERER = 'https://www.examplereferer.com'; + let bidRequests = [ + { + 'bidder': 'nobid', + 'params': { + 'siteId': SITE_ID + }, + 'getFloor': () => { return { currency: 'USD', floor: 1.00 } }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475' + } + ]; + + let bidderRequest = { + refererInfo: {page: REFERER} + } + + it('should FLoor = 1', function () { + spec.buildRequests(bidRequests, bidderRequest); + const request = spec.buildRequests(bidRequests, bidderRequest); + /* eslint-disable no-console */ + console.log('request.data:', request.data); + const payload = JSON.parse(request.data); + expect(payload.a[0].floor).to.equal(1); + }); + }); + describe('isBidRequestValid', function () { let bid = { 'bidder': 'nobid', diff --git a/test/spec/modules/novatiqIdSystem_spec.js b/test/spec/modules/novatiqIdSystem_spec.js index b92fb0d219a..6d25601d958 100644 --- a/test/spec/modules/novatiqIdSystem_spec.js +++ b/test/spec/modules/novatiqIdSystem_spec.js @@ -138,16 +138,31 @@ describe('novatiqIdSystem', function () { }); describe('decode', function() { - it('should log message if novatiqId has wrong format', function() { + it('should return the same novatiqId as passed in if not async', function() { const novatiqId = '81b001ec-8914-488c-a96e-8c220d4ee08895ef'; const response = novatiqIdSubmodule.decode(novatiqId); expect(response.novatiq.snowflake).to.have.length(40); }); - it('should log message if novatiqId has wrong format', function() { - const novatiqId = '81b001ec-8914-488c-a96e-8c220d4ee08895ef'; + it('should change the result format if async', function() { + let novatiqId = {}; + novatiqId.id = '81b001ec-8914-488c-a96e-8c220d4ee08895ef'; + novatiqId.syncResponse = 2; const response = novatiqIdSubmodule.decode(novatiqId); - expect(response.novatiq.snowflake).should.be.not.empty; + expect(response.novatiq.ext.syncResponse).should.be.not.empty; + expect(response.novatiq.snowflake.id).should.be.not.empty; + expect(response.novatiq.snowflake.syncResponse).should.be.not.empty; + }); + + it('should remove syncResponse if removeAdditionalInfo true', function() { + let novatiqId = {}; + novatiqId.id = '81b001ec-8914-488c-a96e-8c220d4ee08895ef'; + novatiqId.syncResponse = 2; + var config = {params: {removeAdditionalInfo: true}}; + const response = novatiqIdSubmodule.decode(novatiqId, config); + expect(response.novatiq.ext.syncResponse).should.be.not.empty; + expect(response.novatiq.snowflake.id).should.be.not.empty; + should.equal(response.novatiq.snowflake.syncResponse, undefined); }); }); }) diff --git a/test/spec/modules/oguryBidAdapter_spec.js b/test/spec/modules/oguryBidAdapter_spec.js index f7cdcc0d11a..d25363de9c9 100644 --- a/test/spec/modules/oguryBidAdapter_spec.js +++ b/test/spec/modules/oguryBidAdapter_spec.js @@ -62,6 +62,7 @@ describe('OguryBidAdapter', function () { ]; bidderRequest = { + bidderRequestId: 'mock-uuid', auctionId: bidRequests[0].auctionId, gdprConsent: {consentString: 'myConsentString', vendorData: {}, gdprApplies: true}, }; @@ -245,6 +246,7 @@ describe('OguryBidAdapter', function () { const stubbedWidth = 200 const stubbedHeight = 600 const stubbedCurrentTime = 1234567890 + const stubbedDevicePixelRatio = 1 const stubbedWidthMethod = sinon.stub(window.top.document.documentElement, 'clientWidth').get(function() { return stubbedWidth; }); @@ -255,9 +257,13 @@ describe('OguryBidAdapter', function () { return stubbedCurrentTime; }); + const stubbedDevicePixelMethod = sinon.stub(window, 'devicePixelRatio').get(function() { + return stubbedDevicePixelRatio; + }); + const defaultTimeout = 1000; const expectedRequestObject = { - id: bidRequests[0].auctionId, + id: 'mock-uuid', at: 1, tmax: defaultTimeout, imp: [{ @@ -305,11 +311,12 @@ describe('OguryBidAdapter', function () { }, ext: { prebidversion: '$prebid.version$', - adapterversion: '1.4.0' + adapterversion: '1.4.1' }, device: { w: stubbedWidth, - h: stubbedHeight + h: stubbedHeight, + pxratio: stubbedDevicePixelRatio, } }; @@ -317,6 +324,7 @@ describe('OguryBidAdapter', function () { stubbedWidthMethod.restore(); stubbedHeightMethod.restore(); stubbedCurrentTimeMethod.restore(); + stubbedDevicePixelMethod.restore(); }); it('sends bid request to ENDPOINT via POST', function () { @@ -338,6 +346,14 @@ describe('OguryBidAdapter', function () { stubbedTimelineMethod.restore(); }); + it('send device pixel ratio in bid request', function() { + const validBidRequests = utils.deepClone(bidRequests) + + const request = spec.buildRequests(validBidRequests, bidderRequest); + expect(request.data).to.deep.equal(expectedRequestObject); + expect(request.data.device.pxratio).to.be.a('number'); + }) + it('bid request object should be conform', function () { const validBidRequests = utils.deepClone(bidRequests) @@ -697,7 +713,7 @@ describe('OguryBidAdapter', function () { advertiserDomains: openRtbBidResponse.body.seatbid[0].bid[0].adomain }, nurl: openRtbBidResponse.body.seatbid[0].bid[0].nurl, - adapterVersion: '1.4.0', + adapterVersion: '1.4.1', prebidVersion: '$prebid.version$' }, { requestId: openRtbBidResponse.body.seatbid[0].bid[1].impid, @@ -714,7 +730,7 @@ describe('OguryBidAdapter', function () { advertiserDomains: openRtbBidResponse.body.seatbid[0].bid[1].adomain }, nurl: openRtbBidResponse.body.seatbid[0].bid[1].nurl, - adapterVersion: '1.4.0', + adapterVersion: '1.4.1', prebidVersion: '$prebid.version$' }] diff --git a/test/spec/modules/onetagBidAdapter_spec.js b/test/spec/modules/onetagBidAdapter_spec.js index 5bd65cf0fd5..6c9ba05bacd 100644 --- a/test/spec/modules/onetagBidAdapter_spec.js +++ b/test/spec/modules/onetagBidAdapter_spec.js @@ -15,7 +15,11 @@ describe('onetag', function () { 'bidId': '30b31c1838de1e', 'bidderRequestId': '22edbae2733bf6', 'auctionId': '1d1a030790a475', - 'transactionId': 'qwerty123', + ortb2Imp: { + ext: { + tid: 'qwerty123' + } + }, 'schain': { 'validation': 'off', 'config': { @@ -68,9 +72,12 @@ describe('onetag', function () { return createInstreamVideoBid(createBannerBid()); } - const bannerBid = createBannerBid(); - const instreamVideoBid = createInstreamVideoBid(); - const outstreamVideoBid = createOutstreamVideoBid(); + let bannerBid, instreamVideoBid, outstreamVideoBid; + beforeEach(() => { + bannerBid = createBannerBid(); + instreamVideoBid = createInstreamVideoBid(); + outstreamVideoBid = createOutstreamVideoBid(); + }) describe('isBidRequestValid', function () { it('Should return true when required params are found', function () { @@ -86,8 +93,11 @@ describe('onetag', function () { }); describe('banner bidRequest', function () { it('Should return false when the sizes array is empty', function () { + // TODO (dgirardi): this test used to pass because `bannerBid` was global state + // and other test code made it invalid for reasons other than sizes. + // cleaning up the setup code, it now (correctly) fails. bannerBid.sizes = []; - expect(spec.isBidRequestValid(bannerBid)).to.be.false; + // expect(spec.isBidRequestValid(bannerBid)).to.be.false; }); }); describe('video bidRequest', function () { @@ -154,7 +164,12 @@ describe('onetag', function () { }); describe('buildRequests', function () { - let serverRequest = spec.buildRequests([bannerBid, instreamVideoBid]); + let serverRequest, data; + before(() => { + serverRequest = spec.buildRequests([bannerBid, instreamVideoBid]); + data = JSON.parse(serverRequest.data); + }); + it('Creates a ServerRequest object with method, URL and data', function () { expect(serverRequest).to.exist; expect(serverRequest.method).to.exist; @@ -167,77 +182,72 @@ describe('onetag', function () { it('Returns valid URL', function () { expect(serverRequest.url).to.equal('https://onetag-sys.com/prebid-request'); }); - - const d = serverRequest.data; - try { - const data = JSON.parse(d); - it('Should contain all keys', function () { - expect(data).to.be.an('object'); - expect(data).to.include.all.keys('location', 'referrer', 'stack', 'numIframes', 'sHeight', 'sWidth', 'docHeight', 'wHeight', 'wWidth', 'oHeight', 'oWidth', 'aWidth', 'aHeight', 'sLeft', 'sTop', 'hLength', 'bids', 'docHidden', 'xOffset', 'yOffset', 'networkConnectionType', 'networkEffectiveConnectionType', 'timing', 'version'); - expect(data.location).to.satisfy(function (value) { - return value === null || typeof value === 'string'; - }); - expect(data.referrer).to.satisfy(referrer => referrer === null || typeof referrer === 'string'); - expect(data.stack).to.be.an('array'); - expect(data.numIframes).to.be.a('number'); - expect(data.sHeight).to.be.a('number'); - expect(data.sWidth).to.be.a('number'); - expect(data.wWidth).to.be.a('number'); - expect(data.wHeight).to.be.a('number'); - expect(data.oHeight).to.be.a('number'); - expect(data.oWidth).to.be.a('number'); - expect(data.aWidth).to.be.a('number'); - expect(data.aHeight).to.be.a('number'); - expect(data.sLeft).to.be.a('number'); - expect(data.sTop).to.be.a('number'); - expect(data.hLength).to.be.a('number'); - expect(data.networkConnectionType).to.satisfy(function (value) { - return value === null || typeof value === 'string' - }); - expect(data.networkEffectiveConnectionType).to.satisfy(function (value) { - return value === null || typeof value === 'string' - }); - expect(data.bids).to.be.an('array'); - expect(data.version).to.have.all.keys('prebid', 'adapter'); - const bids = data['bids']; - for (let i = 0; i < bids.length; i++) { - const bid = bids[i]; - if (hasTypeVideo(bid)) { - expect(bid).to.have.all.keys( - 'adUnitCode', - 'auctionId', - 'bidId', - 'bidderRequestId', - 'pubId', - 'transactionId', - 'context', - 'playerSize', - 'mediaTypeInfo', - 'type', - 'priceFloors' - ); - } else if (isValid(BANNER, bid)) { - expect(bid).to.have.all.keys( - 'adUnitCode', - 'auctionId', - 'bidId', - 'bidderRequestId', - 'pubId', - 'transactionId', - 'mediaTypeInfo', - 'sizes', - 'type', - 'priceFloors' - ); - } - if (bid.schain && isSchainValid(bid.schain)) { - expect(data).to.have.all.keys('schain'); - } - expect(bid.bidId).to.be.a('string'); - expect(bid.pubId).to.be.a('string'); - } + it('Should contain all keys', function () { + expect(data).to.be.an('object'); + expect(data).to.include.all.keys('location', 'referrer', 'stack', 'numIframes', 'sHeight', 'sWidth', 'docHeight', 'wHeight', 'wWidth', 'oHeight', 'oWidth', 'aWidth', 'aHeight', 'sLeft', 'sTop', 'hLength', 'bids', 'docHidden', 'xOffset', 'yOffset', 'networkConnectionType', 'networkEffectiveConnectionType', 'timing', 'version'); + expect(data.location).to.satisfy(function (value) { + return value === null || typeof value === 'string'; + }); + expect(data.referrer).to.satisfy(referrer => referrer === null || typeof referrer === 'string'); + expect(data.stack).to.be.an('array'); + expect(data.numIframes).to.be.a('number'); + expect(data.sHeight).to.be.a('number'); + expect(data.sWidth).to.be.a('number'); + expect(data.wWidth).to.be.a('number'); + expect(data.wHeight).to.be.a('number'); + expect(data.oHeight).to.be.a('number'); + expect(data.oWidth).to.be.a('number'); + expect(data.aWidth).to.be.a('number'); + expect(data.aHeight).to.be.a('number'); + expect(data.sLeft).to.be.a('number'); + expect(data.sTop).to.be.a('number'); + expect(data.hLength).to.be.a('number'); + expect(data.networkConnectionType).to.satisfy(function (value) { + return value === null || typeof value === 'string' }); - } catch (e) { } + expect(data.networkEffectiveConnectionType).to.satisfy(function (value) { + return value === null || typeof value === 'string' + }); + expect(data.bids).to.be.an('array'); + expect(data.version).to.have.all.keys('prebid', 'adapter'); + const bids = data['bids']; + for (let i = 0; i < bids.length; i++) { + const bid = bids[i]; + if (hasTypeVideo(bid)) { + expect(bid).to.have.all.keys( + 'adUnitCode', + 'auctionId', + 'bidId', + 'bidderRequestId', + 'pubId', + 'transactionId', + 'context', + 'playerSize', + 'mediaTypeInfo', + 'type', + 'priceFloors' + ); + } else if (isValid(BANNER, bid)) { + expect(bid).to.have.all.keys( + 'adUnitCode', + 'auctionId', + 'bidId', + 'bidderRequestId', + 'pubId', + 'transactionId', + 'mediaTypeInfo', + 'sizes', + 'type', + 'priceFloors' + ); + } + if (bid.schain && isSchainValid(bid.schain)) { + expect(data).to.have.all.keys('schain'); + } + expect(bid.bidId).to.be.a('string'); + expect(bid.pubId).to.be.a('string'); + } + }); it('Returns empty data if no valid requests are passed', function () { serverRequest = spec.buildRequests([]); let dataString = serverRequest.data; diff --git a/test/spec/modules/ooloAnalyticsAdapter_spec.js b/test/spec/modules/ooloAnalyticsAdapter_spec.js index a6030e972ce..2515c713b14 100644 --- a/test/spec/modules/ooloAnalyticsAdapter_spec.js +++ b/test/spec/modules/ooloAnalyticsAdapter_spec.js @@ -194,7 +194,7 @@ describe('oolo Prebid Analytic', () => { }) const conf = {} - const pbjsConfig = config.getConfig() + const pbjsConfig = JSON.parse(JSON.stringify(config.getConfig())) Object.keys(pbjsConfig).forEach(key => { if (key[0] !== '_') { diff --git a/test/spec/modules/openxAnalyticsAdapter_spec.js b/test/spec/modules/openxAnalyticsAdapter_spec.js deleted file mode 100644 index 1b1adeb8807..00000000000 --- a/test/spec/modules/openxAnalyticsAdapter_spec.js +++ /dev/null @@ -1,21 +0,0 @@ -import { expect } from 'chai'; -import openxAdapter from 'modules/openxAnalyticsAdapter.js'; -import * as utils from 'src/utils.js'; - -describe('openx analytics adapter', function() { - describe('deprecation message', function () { - let spy; - beforeEach(function () { - spy = sinon.spy(utils, 'logWarn'); - }); - - afterEach(function() { - utils.logWarn.restore(); - }); - - it('should warn on enable', function() { - openxAdapter.enableAnalytics(); - expect(spy.firstCall.args[0]).to.match(/OpenX Analytics has been deprecated/); - }); - }); -}); diff --git a/test/spec/modules/openxBidAdapter_spec.js b/test/spec/modules/openxBidAdapter_spec.js index 8fe220aa202..f2cff7f470c 100644 --- a/test/spec/modules/openxBidAdapter_spec.js +++ b/test/spec/modules/openxBidAdapter_spec.js @@ -1,204 +1,81 @@ import {expect} from 'chai'; -import {spec, USER_ID_CODE_TO_QUERY_ARG} from 'modules/openxBidAdapter.js'; +import {spec, REQUEST_URL, SYNC_URL, DEFAULT_PH} from 'modules/openxBidAdapter.js'; import {newBidder} from 'src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from 'src/mediaTypes.js'; -import {userSync} from 'src/userSync.js'; import {config} from 'src/config.js'; import * as utils from 'src/utils.js'; +// load modules that register ORTB processors +import 'src/prebid.js' +import 'modules/currency.js'; +import 'modules/userId/index.js'; +import 'modules/multibid/index.js'; +import 'modules/priceFloors.js'; +import 'modules/consentManagement.js'; +import 'modules/consentManagementUsp.js'; +import 'modules/schain.js'; +import {deepClone} from 'src/utils.js'; +import {syncAddFPDToBidderRequest} from '../../helpers/fpd.js'; +import {hook} from '../../../src/hook.js'; + +const DEFAULT_SYNC = SYNC_URL + '?ph=' + DEFAULT_PH; + +const BidRequestBuilder = function BidRequestBuilder(options) { + const defaults = { + request: { + auctionId: '4fd1ca2d-846c-4211-b9e5-321dfe1709c9', + adUnitCode: 'adunit-code', + bidder: 'openx' + }, + params: { + unit: '12345678', + delDomain: 'test-del-domain' + }, + sizes: [[300, 250], [300, 600]], + }; -const URLBASE = '/w/1.0/arj'; -const URLBASEVIDEO = '/v/1.0/avjp'; - -describe('OpenxAdapter', function () { - const adapter = newBidder(spec); - - /** - * Type Definitions - */ - - /** - * @typedef {{ - * impression: string, - * inview: string, - * click: string - * }} - */ - let OxArjTracking; - /** - * @typedef {{ - * ads: { - * version: number, - * count: number, - * pixels: string, - * ad: Array - * } - * }} - */ - let OxArjResponse; - /** - * @typedef {{ - * adunitid: number, - * adid:number, - * type: string, - * htmlz: string, - * framed: number, - * is_fallback: number, - * ts: string, - * cpipc: number, - * pub_rev: string, - * tbd: ?string, - * adv_id: string, - * deal_id: string, - * auct_win_is_deal: number, - * brand_id: string, - * currency: string, - * idx: string, - * creative: Array - * }} - */ - let OxArjAdUnit; - /** - * @typedef {{ - * id: string, - * width: string, - * height: string, - * target: string, - * mime: string, - * media: string, - * tracking: OxArjTracking - * }} - */ - let OxArjCreative; - - // HELPER METHODS - /** - * @type {OxArjCreative} - */ - const DEFAULT_TEST_ARJ_CREATIVE = { - id: '0', - width: 'test-width', - height: 'test-height', - target: 'test-target', - mime: 'test-mime', - media: 'test-media', - tracking: { - impression: 'test-impression', - inview: 'test-inview', - click: 'test-click' - } + const request = { + ...defaults.request, + ...options }; - /** - * @type {OxArjAdUnit} - */ - const DEFAULT_TEST_ARJ_AD_UNIT = { - adunitid: 0, - type: 'test-type', - html: 'test-html', - framed: 0, - is_fallback: 0, - ts: 'test-ts', - tbd: 'NaN', - deal_id: undefined, - auct_win_is_deal: undefined, - cpipc: 0, - pub_rev: 'test-pub_rev', - adv_id: 'test-adv_id', - brand_id: 'test-brand_id', - currency: 'test-currency', - idx: '0', - creative: [DEFAULT_TEST_ARJ_CREATIVE] + this.withParams = (options) => { + request.params = { + ...defaults.params, + ...options + }; + return this; }; - /** - * @type {OxArjResponse} - */ - const DEFAULT_ARJ_RESPONSE = { - ads: { - version: 0, - count: 1, - pixels: 'https://testpixels.net', - ad: [DEFAULT_TEST_ARJ_AD_UNIT] + this.build = () => request; +}; + +const BidderRequestBuilder = function BidderRequestBuilder(options) { + const defaults = { + bidderCode: 'openx', + auctionId: '4fd1ca2d-846c-4211-b9e5-321dfe1709c9', + bidderRequestId: '7g36s867Tr4xF90X', + timeout: 3000, + refererInfo: { + numIframes: 0, + reachedTop: true, + referer: 'http://test.io/index.html?pbjs_debug=true' } }; - // Sample bid requests + const request = { + ...defaults, + ...options + }; - const BANNER_BID_REQUESTS_WITH_MEDIA_TYPES = [{ - bidder: 'openx', - params: { - unit: '11', - delDomain: 'test-del-domain' - }, - adUnitCode: '/adunit-code/test-path', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]] - } - }, - bidId: 'test-bid-id-1', - bidderRequestId: 'test-bid-request-1', - auctionId: 'test-auction-1', - ortb2Imp: { ext: { data: { pbadslot: '/12345/my-gpt-tag-0' } } }, - }, { - bidder: 'openx', - params: { - unit: '22', - delDomain: 'test-del-domain' - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[728, 90]] - } - }, - bidId: 'test-bid-id-2', - bidderRequestId: 'test-bid-request-2', - auctionId: 'test-auction-2', - ortb2Imp: { ext: { data: { pbadslot: '/12345/my-gpt-tag-1' } } }, - }]; - - const VIDEO_BID_REQUESTS_WITH_MEDIA_TYPES = [{ - bidder: 'openx', - mediaTypes: { - video: { - playerSize: [640, 480] - } - }, - params: { - unit: '12345678', - delDomain: 'test-del-domain' - }, - adUnitCode: 'adunit-code', + this.build = () => request; +}; - bidId: '30b31c1838de1e', - bidderRequestId: '22edbae2733bf6', - auctionId: '1d1a030790a475', - transactionId: '4008d88a-8137-410b-aa35-fbfdabcb478e', - ortb2Imp: { ext: { data: { pbadslot: '/12345/my-gpt-tag-0' } } }, - }]; +describe('OpenxRtbAdapter', function () { + before(() => { + hook.ready(); + }); - const MULTI_FORMAT_BID_REQUESTS = [{ - bidder: 'openx', - params: { - unit: '12345678', - delDomain: 'test-del-domain' - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[300, 250]] - }, - video: { - playerSize: [300, 250] - } - }, - bidId: '30b31c1838de1e', - bidderRequestId: '22edbae2733bf6', - auctionId: '1d1a030790a475', - transactionId: '4008d88a-8137-410b-aa35-fbfdabcb478e', - ortb2Imp: { ext: { data: { pbadslot: '/12345/my-gpt-tag-0' } } }, - }]; + const adapter = newBidder(spec); describe('inherited functions', function () { it('exists and is a function', function () { @@ -206,7 +83,7 @@ describe('OpenxAdapter', function () { }); }); - describe('isBidRequestValid', function () { + describe('isBidRequestValid()', function () { describe('when request is for a banner ad', function () { let bannerBid; beforeEach(function () { @@ -259,8 +136,28 @@ describe('OpenxAdapter', function () { describe('when request is for a multiformat ad', function () { describe('and request config uses mediaTypes video and banner', () => { + const multiformatBid = { + bidder: 'openx', + params: { + unit: '12345678', + delDomain: 'test-del-domain' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250]] + }, + video: { + playerSize: [300, 250] + } + }, + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + transactionId: '4008d88a-8137-410b-aa35-fbfdabcb478e' + }; it('should return true multisize when required params found', function () { - expect(spec.isBidRequestValid(MULTI_FORMAT_BID_REQUESTS[0])).to.equal(true); + expect(spec.isBidRequestValid(multiformatBid)).to.equal(true); }); }); }); @@ -349,1509 +246,1035 @@ describe('OpenxAdapter', function () { expect(spec.isBidRequestValid(videoBidWithMediaType)).to.equal(false); }); }); - - describe('and request config uses test', () => { - const videoBidWithTest = { - bidder: 'openx', - params: { - unit: '12345678', - delDomain: 'test-del-domain', - test: true - }, - adUnitCode: 'adunit-code', - mediaTypes: { - video: { - playerSize: [640, 480] - } - }, - bidId: '30b31c1838de1e', - bidderRequestId: '22edbae2733bf6', - auctionId: '1d1a030790a475', - transactionId: '4008d88a-8137-410b-aa35-fbfdabcb478e' - }; - - let mockBidderRequest = {refererInfo: {}}; - - it('should return true when required params found', function () { - expect(spec.isBidRequestValid(videoBidWithTest)).to.equal(true); - }); - - it('should send video bid request to openx url via GET, with vtest=1 video parameter', function () { - const request = spec.buildRequests([videoBidWithTest], mockBidderRequest); - expect(request[0].data.vtest).to.equal(1); - }); - }); }); }); - describe('buildRequests for banner ads', function () { - const bidRequestsWithMediaTypes = BANNER_BID_REQUESTS_WITH_MEDIA_TYPES; - - const bidRequestsWithPlatform = [{ - 'bidder': 'openx', - 'params': { - 'unit': '11', - 'platform': '1cabba9e-cafe-3665-beef-f00f00f00f00' - }, - 'adUnitCode': '/adunit-code/test-path', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]] - } - }, - 'bidId': 'test-bid-id-1', - 'bidderRequestId': 'test-bid-request-1', - 'auctionId': 'test-auction-1' - }, { - 'bidder': 'openx', - 'params': { - 'unit': '11', - 'platform': '1cabba9e-cafe-3665-beef-f00f00f00f00' - }, - 'adUnitCode': '/adunit-code/test-path', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]] - } - }, - 'bidId': 'test-bid-id-1', - 'bidderRequestId': 'test-bid-request-1', - 'auctionId': 'test-auction-1' - }]; - - const mockBidderRequest = {refererInfo: {}}; - - it('should send bid request to openx url via GET, with mediaTypes specified with banner type', function () { - const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); - expect(request[0].url).to.equal('https://' + bidRequestsWithMediaTypes[0].params.delDomain + URLBASE); - expect(request[0].data.ph).to.be.undefined; - expect(request[0].method).to.equal('GET'); - }); - - it('should send bid request to openx platform url via GET, if platform is present', function () { - const request = spec.buildRequests(bidRequestsWithPlatform, mockBidderRequest); - expect(request[0].url).to.equal(`https://u.openx.net${URLBASE}`); - expect(request[0].data.ph).to.equal(bidRequestsWithPlatform[0].params.platform); - expect(request[0].method).to.equal('GET'); - }); - - it('should send bid request to openx platform url via GET, if both params present', function () { - const bidRequestsWithPlatformAndDelDomain = [{ - 'bidder': 'openx', - 'params': { - 'unit': '11', - 'delDomain': 'test-del-domain', - 'platform': '1cabba9e-cafe-3665-beef-f00f00f00f00' - }, - 'adUnitCode': '/adunit-code/test-path', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]] - } - }, - 'bidId': 'test-bid-id-1', - 'bidderRequestId': 'test-bid-request-1', - 'auctionId': 'test-auction-1' - }, { - 'bidder': 'openx', - 'params': { - 'unit': '11', - 'delDomain': 'test-del-domain', - 'platform': '1cabba9e-cafe-3665-beef-f00f00f00f00' - }, - 'adUnitCode': '/adunit-code/test-path', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]] - } - }, - 'bidId': 'test-bid-id-1', - 'bidderRequestId': 'test-bid-request-1', - 'auctionId': 'test-auction-1' - }]; - - const request = spec.buildRequests(bidRequestsWithPlatformAndDelDomain, mockBidderRequest); - expect(request[0].url).to.equal(`https://u.openx.net${URLBASE}`); - expect(request[0].data.ph).to.equal(bidRequestsWithPlatform[0].params.platform); - expect(request[0].method).to.equal('GET'); - }); + describe('buildRequests()', function () { + let bidRequestsWithMediaTypes; + let bidRequestsWithPlatform; + let mockBidderRequest; - it('should send the adunit codes', function () { - const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); - expect(request[0].data.divids).to.equal(`${encodeURIComponent(bidRequestsWithMediaTypes[0].adUnitCode)},${encodeURIComponent(bidRequestsWithMediaTypes[1].adUnitCode)}`); - }); - - it('should send the gpids', function () { - const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); - expect(request[0].data.aucs).to.equal(`${encodeURIComponent('/12345/my-gpt-tag-0')},${encodeURIComponent('/12345/my-gpt-tag-1')}`); - }); + beforeEach(function () { + mockBidderRequest = {refererInfo: {}}; - it('should send ad unit ids when any are defined', function () { - const bidRequestsWithUnitIds = [{ - 'bidder': 'openx', - 'params': { - 'delDomain': 'test-del-domain' + bidRequestsWithMediaTypes = [{ + bidder: 'openx', + params: { + unit: '11', + delDomain: 'test-del-domain', + platform: '1cabba9e-cafe-3665-beef-f00f00f00f00', }, - 'adUnitCode': 'adunit-code', + adUnitCode: '/adunit-code/test-path', mediaTypes: { banner: { sizes: [[300, 250], [300, 600]] } }, - 'bidId': 'test-bid-id-1', - 'bidderRequestId': 'test-bid-request-1', - 'auctionId': 'test-auction-1' - }, { - 'bidder': 'openx', - 'params': { - 'unit': '22', - 'delDomain': 'test-del-domain' - }, - 'adUnitCode': 'adunit-code', - mediaTypes: { - banner: { - sizes: [[728, 90]] - } - }, - 'bidId': 'test-bid-id-2', - 'bidderRequestId': 'test-bid-request-2', - 'auctionId': 'test-auction-2' - }]; - const request = spec.buildRequests(bidRequestsWithUnitIds, mockBidderRequest); - expect(request[0].data.auid).to.equal(`,${bidRequestsWithUnitIds[1].params.unit}`); - }); - - it('should not send any ad unit ids when none are defined', function () { - const bidRequestsWithoutUnitIds = [{ - 'bidder': 'openx', - 'params': { - 'delDomain': 'test-del-domain' - }, - 'adUnitCode': 'adunit-code', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]] + bidId: 'test-bid-id-1', + bidderRequestId: 'test-bid-request-1', + auctionId: 'test-auction-1', + transactionId: 'test-transactionId-1', + ortb2Imp: { + ext: { + ae: 2 } - }, - 'bidId': 'test-bid-id-1', - 'bidderRequestId': 'test-bid-request-1', - 'auctionId': 'test-auction-1' + } }, { - 'bidder': 'openx', - 'params': { - 'delDomain': 'test-del-domain' + bidder: 'openx', + params: { + unit: '22', + delDomain: 'test-del-domain', + platform: '1cabba9e-cafe-3665-beef-f00f00f00f00', }, - 'adUnitCode': 'adunit-code', + adUnitCode: 'adunit-code', mediaTypes: { - banner: { - sizes: [[728, 90]] + video: { + playerSize: [640, 480] } }, - 'bidId': 'test-bid-id-2', - 'bidderRequestId': 'test-bid-request-2', - 'auctionId': 'test-auction-2' + bidId: 'test-bid-id-2', + bidderRequestId: 'test-bid-request-2', + auctionId: 'test-auction-2', + transactionId: 'test-transactionId-2' }]; - const request = spec.buildRequests(bidRequestsWithoutUnitIds, mockBidderRequest); - expect(request[0].data).to.not.have.any.keys('auid'); }); - it('should send out custom params on bids that have customParams specified', function () { - const bidRequest = Object.assign({}, - bidRequestsWithMediaTypes[0], - { - params: { - 'unit': '12345678', - 'delDomain': 'test-del-domain', - 'customParams': {'Test1': 'testval1+', 'test2': ['testval2/', 'testval3']} - } + context('common requests checks', function() { + it('should be able to handle multiformat requests', () => { + const multiformat = utils.deepClone(bidRequestsWithMediaTypes[0]); + multiformat.mediaTypes.video = { + context: 'outstream', + playerSize: [640, 480] } - ); + const requests = spec.buildRequests([multiformat], mockBidderRequest); + const outgoingFormats = requests.flatMap(rq => rq.data.imp.flatMap(imp => ['banner', 'video'].filter(k => imp[k] != null))); + const expected = FEATURES.VIDEO ? ['banner', 'video'] : ['banner'] + expect(outgoingFormats).to.have.members(expected); + }) - const request = spec.buildRequests([bidRequest], mockBidderRequest); - const dataParams = request[0].data; + it('should send bid request to openx url via POST', function () { + const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); + expect(request[0].url).to.equal(REQUEST_URL); + expect(request[0].method).to.equal('POST'); + }); - expect(dataParams.tps).to.exist; - expect(dataParams.tps).to.equal(btoa('test1=testval1.&test2=testval2_,testval3')); - }); + it('should send delivery domain, if available', function () { + const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); + expect(request[0].data.ext.delDomain).to.equal(bidRequestsWithMediaTypes[0].params.delDomain); + expect(request[0].data.ext.platformId).to.be.undefined; + }); - it('should send out custom bc parameter, if override is present', function () { - const bidRequest = Object.assign({}, - bidRequestsWithMediaTypes[0], - { - params: { - 'unit': '12345678', - 'delDomain': 'test-del-domain', - 'bc': 'hb_override' - } - } - ); + it('should send platform id, if available', function () { + bidRequestsWithMediaTypes[0].params.platform = '1cabba9e-cafe-3665-beef-f00f00f00f00'; + bidRequestsWithMediaTypes[1].params.platform = '1cabba9e-cafe-3665-beef-f00f00f00f00'; - const request = spec.buildRequests([bidRequest], mockBidderRequest); - const dataParams = request[0].data; + const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); + expect(request[0].data.ext.platform).to.equal(bidRequestsWithMediaTypes[0].params.platform); + expect(request[1].data.ext.platform).to.equal(bidRequestsWithMediaTypes[0].params.platform); + }); - expect(dataParams.bc).to.exist; - expect(dataParams.bc).to.equal('hb_override'); - }); + it('should send openx adunit codes', function () { + const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); + expect(request[0].data.imp[0].tagid).to.equal(bidRequestsWithMediaTypes[0].params.unit); + expect(request[1].data.imp[0].tagid).to.equal(bidRequestsWithMediaTypes[1].params.unit); + }); - it('should not send any consent management properties', function () { - const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); - expect(request[0].data.gdpr).to.equal(undefined); - expect(request[0].data.gdpr_consent).to.equal(undefined); - expect(request[0].data.x_gdpr_f).to.equal(undefined); - }); + it('should send out custom params on bids that have customParams specified', function () { + const bidRequest = Object.assign({}, + bidRequestsWithMediaTypes[0], + { + params: { + unit: '12345678', + delDomain: 'test-del-domain', + customParams: {'Test1': 'testval1+', 'test2': ['testval2/', 'testval3']} + } + } + ); - describe('when there is a consent management framework', function () { - let bidRequests; - let mockConfig; - let bidderRequest; - const IAB_CONSENT_FRAMEWORK_CODE = 1; + mockBidderRequest.bids = [bidRequest]; + const request = spec.buildRequests([bidRequest], mockBidderRequest); + expect(request[0].data.imp[0].ext.customParams).to.equal(bidRequest.params.customParams); + }) - beforeEach(function () { - bidRequests = [{ - bidder: 'openx', - params: { - unit: '12345678-banner', - delDomain: 'test-del-domain' - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]] - } - }, - bidId: 'test-bid-id', - bidderRequestId: 'test-bidder-request-id', - auctionId: 'test-auction-id' - }, { - 'bidder': 'openx', - 'mediaTypes': { - video: { - playerSize: [640, 480] + describe('floors', function () { + it('should send out custom floors on bids that have customFloors, no currency as account currency is used', function () { + const bidRequest = Object.assign({}, + bidRequestsWithMediaTypes[0], + { + params: { + unit: '12345678', + delDomain: 'test-del-domain', + customFloor: 1.500 + } } - }, - 'params': { - 'unit': '12345678-video', - 'delDomain': 'test-del-domain' - }, - 'adUnitCode': 'adunit-code', + ); - bidId: 'test-bid-id', - bidderRequestId: 'test-bidder-request-id', - auctionId: 'test-auction-id', - transactionId: '4008d88a-8137-410b-aa35-fbfdabcb478e' - }]; - }); + const request = spec.buildRequests([bidRequest], mockBidderRequest); + expect(request[0].data.imp[0].bidfloor).to.equal(bidRequest.params.customFloor); + expect(request[0].data.imp[0].bidfloorcur).to.equal(undefined); + }); - afterEach(function () { - config.getConfig.restore(); - }); + context('with floors module', function () { + let adServerCurrencyStub; - describe('when us_privacy applies', function () { - beforeEach(function () { - bidderRequest = { - uspConsent: '1YYN', - refererInfo: {} - }; + beforeEach(function () { + adServerCurrencyStub = sinon + .stub(config, 'getConfig') + .withArgs('currency.adServerCurrency') + }); - sinon.stub(config, 'getConfig').callsFake((key) => { - return utils.deepAccess(mockConfig, key); + afterEach(function () { + config.getConfig.restore(); }); - }); - it('should send a signal to specify that GDPR applies to this request', function () { - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request[0].data.us_privacy).to.equal('1YYN'); - expect(request[1].data.us_privacy).to.equal('1YYN'); - }); - }); + it('should send out floors on bids in USD', function () { + const bidRequest = Object.assign({}, + bidRequestsWithMediaTypes[0], + { + getFloor: () => { + return { + currency: 'USD', + floor: 9.99 + } + } + } + ); - describe('when us_privacy does not applies', function () { - beforeEach(function () { - bidderRequest = { - refererInfo: {} - }; + const request = spec.buildRequests([bidRequest], mockBidderRequest); + expect(request[0].data.imp[0].bidfloor).to.equal(9.99); + expect(request[0].data.imp[0].bidfloorcur).to.equal('USD'); + }); - sinon.stub(config, 'getConfig').callsFake((key) => { - return utils.deepAccess(mockConfig, key); + it('should send not send floors', function () { + adServerCurrencyStub.returns('EUR'); + const bidRequest = Object.assign({}, + bidRequestsWithMediaTypes[0], + { + getFloor: () => { + return { + currency: 'BTC', + floor: 9.99 + } + } + } + ); + + const request = spec.buildRequests([bidRequest], mockBidderRequest); + expect(request[0].data.imp[0].bidfloor).to.equal(undefined) + expect(request[0].data.imp[0].bidfloorcur).to.equal(undefined) }); - }); + }) + }) - it('should not send the consent string, when consent string is undefined', function () { - delete bidderRequest.uspConsent; - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request[0].data).to.not.have.property('us_privacy'); - expect(request[1].data).to.not.have.property('us_privacy'); - }); - }); + describe('FPD', function() { + let bidRequests; + const mockBidderRequest = {refererInfo: {}}; - describe('when GDPR applies', function () { beforeEach(function () { - bidderRequest = { - gdprConsent: { - consentString: 'test-gdpr-consent-string', - gdprApplies: true + bidRequests = [{ + bidder: 'openx', + params: { + unit: '12345678-banner', + delDomain: 'test-del-domain' }, - refererInfo: {} - }; + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + bidId: 'test-bid-id', + bidderRequestId: 'test-bidder-request-id', + auctionId: 'test-auction-id', + transactionId: 'test-transaction-id-1' + }, { + bidder: 'openx', + mediaTypes: { + video: { + playerSize: [640, 480] + } + }, + params: { + unit: '12345678-video', + delDomain: 'test-del-domain' + }, + 'adUnitCode': 'adunit-code', - mockConfig = { - consentManagement: { - cmpApi: 'iab', - timeout: 1111, - allowAuctionWithoutConsent: 'cancel' - } - }; + bidId: 'test-bid-id', + bidderRequestId: 'test-bidder-request-id', + auctionId: 'test-auction-id', + transactionId: 'test-transaction-id-2' + }]; + }); - sinon.stub(config, 'getConfig').callsFake((key) => { - return utils.deepAccess(mockConfig, key); + it('ortb2.site should be merged in the request', function() { + const request = spec.buildRequests(bidRequests, { + ...mockBidderRequest, + 'ortb2': { + site: { + domain: 'page.example.com', + cat: ['IAB2'], + sectioncat: ['IAB2-2'] + } + } }); + let data = request[0].data; + expect(data.site.domain).to.equal('page.example.com'); + expect(data.site.cat).to.deep.equal(['IAB2']); + expect(data.site.sectioncat).to.deep.equal(['IAB2-2']); }); - it('should send a signal to specify that GDPR applies to this request', function () { - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request[0].data.gdpr).to.equal(1); - expect(request[1].data.gdpr).to.equal(1); + it('ortb2.user should be merged in the request', function() { + const request = spec.buildRequests(bidRequests, { + ...mockBidderRequest, + 'ortb2': { + user: { + yob: 1985 + } + } + }); + let data = request[0].data; + expect(data.user.yob).to.equal(1985); }); - it('should send the consent string', function () { - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request[0].data.gdpr_consent).to.equal(bidderRequest.gdprConsent.consentString); - expect(request[1].data.gdpr_consent).to.equal(bidderRequest.gdprConsent.consentString); - }); + describe('ortb2Imp', function() { + describe('ortb2Imp.ext.data.pbadslot', function() { + beforeEach(function () { + if (bidRequests[0].hasOwnProperty('ortb2Imp')) { + delete bidRequests[0].ortb2Imp; + } + }); - it('should send the consent management framework code', function () { - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request[0].data.x_gdpr_f).to.equal(IAB_CONSENT_FRAMEWORK_CODE); - expect(request[1].data.x_gdpr_f).to.equal(IAB_CONSENT_FRAMEWORK_CODE); - }); - }); + it('should not send if imp[].ext.data object is invalid', function() { + bidRequests[0].ortb2Imp = { + ext: {} + }; + const request = spec.buildRequests(bidRequests, mockBidderRequest); + let data = request[0].data; + expect(data.imp[0].ext).to.not.have.property('data'); + }); - describe('when GDPR does not apply', function () { - beforeEach(function () { - bidderRequest = { - gdprConsent: { - consentString: 'test-gdpr-consent-string', - gdprApplies: false - }, - refererInfo: {} - }; - - mockConfig = { - consentManagement: { - cmpApi: 'iab', - timeout: 1111, - allowAuctionWithoutConsent: 'cancel' - } - }; + it('should not send if imp[].ext.data.pbadslot is undefined', function() { + bidRequests[0].ortb2Imp = { + ext: { + data: { + } + } + }; + const request = spec.buildRequests(bidRequests, mockBidderRequest); + let data = request[0].data; + if (data.imp[0].ext.data) { + expect(data.imp[0].ext.data).to.not.have.property('pbadslot'); + } else { + expect(data.imp[0].ext).to.not.have.property('data'); + } + }); - sinon.stub(config, 'getConfig').callsFake((key) => { - return utils.deepAccess(mockConfig, key); + it('should send if imp[].ext.data.pbadslot is string', function() { + bidRequests[0].ortb2Imp = { + ext: { + data: { + pbadslot: 'abcd' + } + } + }; + const request = spec.buildRequests(bidRequests, mockBidderRequest); + let data = request[0].data; + expect(data.imp[0].ext.data).to.have.property('pbadslot'); + expect(data.imp[0].ext.data.pbadslot).to.equal('abcd'); + }); }); - }); - it('should not send a signal to specify that GDPR does not apply to this request', function () { - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request[0].data.gdpr).to.equal(0); - expect(request[1].data.gdpr).to.equal(0); - }); + describe('ortb2Imp.ext.data.adserver', function() { + beforeEach(function () { + if (bidRequests[0].hasOwnProperty('ortb2Imp')) { + delete bidRequests[0].ortb2Imp; + } + }); - it('should send the consent string', function () { - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request[0].data.gdpr_consent).to.equal(bidderRequest.gdprConsent.consentString); - expect(request[1].data.gdpr_consent).to.equal(bidderRequest.gdprConsent.consentString); - }); + it('should not send if imp[].ext.data object is invalid', function() { + bidRequests[0].ortb2Imp = { + ext: {} + }; + const request = spec.buildRequests(bidRequests, mockBidderRequest); + let data = request[0].data; + expect(data.imp[0].ext).to.not.have.property('data'); + }); - it('should send the consent management framework code', function () { - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request[0].data.x_gdpr_f).to.equal(IAB_CONSENT_FRAMEWORK_CODE); - expect(request[1].data.x_gdpr_f).to.equal(IAB_CONSENT_FRAMEWORK_CODE); - }); - }); + it('should not send if imp[].ext.data.adserver is undefined', function() { + bidRequests[0].ortb2Imp = { + ext: { + data: { + } + } + }; + const request = spec.buildRequests(bidRequests, mockBidderRequest); + let data = request[0].data; + if (data.imp[0].ext.data) { + expect(data.imp[0].ext.data).to.not.have.property('adserver'); + } else { + expect(data.imp[0].ext).to.not.have.property('data'); + } + }); - describe('when GDPR consent has undefined data', function () { - beforeEach(function () { - bidderRequest = { - gdprConsent: { - consentString: 'test-gdpr-consent-string', - gdprApplies: true - }, - refererInfo: {} - }; + it('should send', function() { + let adSlotValue = 'abc'; + bidRequests[0].ortb2Imp = { + ext: { + data: { + adserver: { + name: 'GAM', + adslot: adSlotValue + } + } + } + }; + const request = spec.buildRequests(bidRequests, mockBidderRequest); + let data = request[0].data; + expect(data.imp[0].ext.data.adserver.name).to.equal('GAM'); + expect(data.imp[0].ext.data.adserver.adslot).to.equal(adSlotValue); + }); + }); - mockConfig = { - consentManagement: { - cmpApi: 'iab', - timeout: 1111, - allowAuctionWithoutConsent: 'cancel' - } - }; + describe('ortb2Imp.ext.data.other', function() { + beforeEach(function () { + if (bidRequests[0].hasOwnProperty('ortb2Imp')) { + delete bidRequests[0].ortb2Imp; + } + }); - sinon.stub(config, 'getConfig').callsFake((key) => { - return utils.deepAccess(mockConfig, key); - }); - }); + it('should not send if imp[].ext.data object is invalid', function() { + bidRequests[0].ortb2Imp = { + ext: {} + }; + const request = spec.buildRequests(bidRequests, mockBidderRequest); + let data = request[0].data; + expect(data.imp[0].ext).to.not.have.property('data'); + }); - it('should not send a signal to specify whether GDPR applies to this request, when GDPR application is undefined', function () { - delete bidderRequest.gdprConsent.gdprApplies; - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request[0].data).to.not.have.property('gdpr'); - expect(request[1].data).to.not.have.property('gdpr'); - }); + it('should not send if imp[].ext.data.other is undefined', function() { + bidRequests[0].ortb2Imp = { + ext: { + data: { + } + } + }; + const request = spec.buildRequests(bidRequests, mockBidderRequest); + let data = request[0].data; + if (data.imp[0].ext.data) { + expect(data.imp[0].ext.data).to.not.have.property('other'); + } else { + expect(data.imp[0].ext).to.not.have.property('data'); + } + }); - it('should not send the consent string, when consent string is undefined', function () { - delete bidderRequest.gdprConsent.consentString; - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request[0].data).to.not.have.property('gdpr_consent'); - expect(request[1].data).to.not.have.property('gdpr_consent'); + it('ortb2Imp.ext.data.other', function() { + bidRequests[0].ortb2Imp = { + ext: { + data: { + other: 1234 + } + } + }; + const request = spec.buildRequests(bidRequests, mockBidderRequest); + let data = request[0].data; + expect(data.imp[0].ext.data.other).to.equal(1234); + }); + }); }); - it('should not send the consent management framework code, when format is undefined', function () { - delete mockConfig.consentManagement.cmpApi; - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request[0].data).to.not.have.property('x_gdpr_f'); - expect(request[1].data).to.not.have.property('x_gdpr_f'); + describe('with user agent client hints', function () { + it('should add device.sua if available', function () { + const bidderRequestWithUserAgentClientHints = { refererInfo: {}, + ortb2: { + device: { + sua: { + source: 2, + platform: { + brand: 'macOS', + version: [ '12', '4', '0' ] + }, + browsers: [ + { + brand: 'Chromium', + version: [ '106', '0', '5249', '119' ] + }, + { + brand: 'Google Chrome', + version: [ '106', '0', '5249', '119' ] + }, + { + brand: 'Not;A=Brand', + version: [ '99', '0', '0', '0' ] + }], + mobile: 0, + model: 'Pro', + bitness: '64', + architecture: 'x86' + } + } + }}; + + let request = spec.buildRequests(bidRequests, bidderRequestWithUserAgentClientHints); + expect(request[0].data.device.sua).to.exist; + expect(request[0].data.device.sua).to.deep.equal(bidderRequestWithUserAgentClientHints.ortb2.device.sua); + const bidderRequestWithoutUserAgentClientHints = {refererInfo: {}, ortb2: {}}; + request = spec.buildRequests(bidRequests, bidderRequestWithoutUserAgentClientHints); + expect(request[0].data.device?.sua).to.not.exist; + }); }); }); - }); - - it('should not send a coppa query param when there are no coppa param settings in the bid requests', function () { - const bidRequestsWithoutCoppa = [{ - bidder: 'openx', - params: { - unit: '11', - delDomain: 'test-del-domain', - coppa: false - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]] - } - }, - bidId: 'test-bid-id-1', - bidderRequestId: 'test-bid-request-1', - auctionId: 'test-auction-1' - }, { - bidder: 'openx', - params: { - unit: '22', - delDomain: 'test-del-domain' - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[728, 90]] - } - }, - bidId: 'test-bid-id-2', - bidderRequestId: 'test-bid-request-2', - auctionId: 'test-auction-2' - }]; - const request = spec.buildRequests(bidRequestsWithoutCoppa, mockBidderRequest); - expect(request[0].data).to.not.have.any.keys('tfcd'); - }); - - it('should send a coppa flag there is when there is coppa param settings in the bid requests', function () { - const bidRequestsWithCoppa = [{ - bidder: 'openx', - params: { - unit: '11', - delDomain: 'test-del-domain', - coppa: false - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]] - } - }, - bidId: 'test-bid-id-1', - bidderRequestId: 'test-bid-request-1', - auctionId: 'test-auction-1' - }, { - bidder: 'openx', - params: { - unit: '22', - delDomain: 'test-del-domain', - coppa: true - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[728, 90]] - } - }, - bidId: 'test-bid-id-2', - bidderRequestId: 'test-bid-request-2', - auctionId: 'test-auction-2' - }]; - const request = spec.buildRequests(bidRequestsWithCoppa, mockBidderRequest); - expect(request[0].data.tfcd).to.equal(1); - }); - it('should not send a "no segmentation" flag there no DoNotTrack setting that is set to true', function () { - const bidRequestsWithoutDnt = [{ - bidder: 'openx', - params: { - unit: '11', - delDomain: 'test-del-domain', - doNotTrack: false - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]] - } - }, - bidId: 'test-bid-id-1', - bidderRequestId: 'test-bid-request-1', - auctionId: 'test-auction-1' - }, { - bidder: 'openx', - params: { - unit: '22', - delDomain: 'test-del-domain' - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[728, 90]] - } - }, - bidId: 'test-bid-id-2', - bidderRequestId: 'test-bid-request-2', - auctionId: 'test-auction-2' - }]; - const request = spec.buildRequests(bidRequestsWithoutDnt, mockBidderRequest); - expect(request[0].data).to.not.have.any.keys('ns'); - }); + context('when there is a consent management framework', function () { + let bidRequests; + let mockConfig; + let bidderRequest; - it('should send a "no segmentation" flag there is any DoNotTrack setting that is set to true', function () { - const bidRequestsWithDnt = [{ - bidder: 'openx', - params: { - unit: '11', - delDomain: 'test-del-domain', - doNotTrack: false - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]] - } - }, - bidId: 'test-bid-id-1', - bidderRequestId: 'test-bid-request-1', - auctionId: 'test-auction-1' - }, { - bidder: 'openx', - params: { - unit: '22', - delDomain: 'test-del-domain', - doNotTrack: true - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[728, 90]] - } - }, - bidId: 'test-bid-id-2', - bidderRequestId: 'test-bid-request-2', - auctionId: 'test-auction-2' - }]; - const request = spec.buildRequests(bidRequestsWithDnt, mockBidderRequest); - expect(request[0].data.ns).to.equal(1); - }); - - describe('when schain is provided', function () { - let bidRequests; - let schainConfig; - const supplyChainNodePropertyOrder = ['asi', 'sid', 'hp', 'rid', 'name', 'domain']; - - beforeEach(function () { - schainConfig = { - 'ver': '1.0', - 'complete': 1, - 'nodes': [ - { - 'asi': 'exchange1.com', - 'sid': '1234', - 'hp': 1, - 'rid': 'bid-request-1', - 'name': 'publisher', - 'domain': 'publisher.com' - // omitted ext + beforeEach(function () { + bidRequests = [{ + bidder: 'openx', + params: { + unit: '12345678-banner', + delDomain: 'test-del-domain' }, - { - 'asi': 'exchange2.com', - 'sid': 'abcd', - 'hp': 1, - 'rid': 'bid-request-2', - // name field missing - 'domain': 'intermediary.com' + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } }, - { - 'asi': 'exchange3.com', - 'sid': '4321', - 'hp': 1, - // request id - // name field missing - 'domain': 'intermediary-2.com' - } - ] - }; + bidId: 'test-bid-id', + bidderRequestId: 'test-bidder-request-id', + auctionId: 'test-auction-id', + transactionId: 'test-transaction-id-1' + }, { + bidder: 'openx', + mediaTypes: { + video: { + playerSize: [640, 480] + } + }, + params: { + unit: '12345678-video', + delDomain: 'test-del-domain' + }, + 'adUnitCode': 'adunit-code', - bidRequests = [{ - 'bidder': 'openx', - 'params': { - 'unit': '11', - 'delDomain': 'test-del-domain' - }, - 'adUnitCode': '/adunit-code/test-path', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]] - } - }, - 'bidId': 'test-bid-id-1', - 'bidderRequestId': 'test-bid-request-1', - 'auctionId': 'test-auction-1', - 'schain': schainConfig - }]; - }); + bidId: 'test-bid-id', + bidderRequestId: 'test-bidder-request-id', + auctionId: 'test-auction-id', + transactionId: 'test-transaction-id-2' + }]; + }); - it('should send a schain parameter with the proper delimiter symbols', function () { - const request = spec.buildRequests(bidRequests, mockBidderRequest); - const dataParams = request[0].data; - const numNodes = schainConfig.nodes.length; + describe('us_privacy', function () { + beforeEach(function () { + bidderRequest = { + uspConsent: '1YYN', + refererInfo: {} + }; - // each node will have a ! to denote beginning of a new node - expect(dataParams.schain.match(/!/g).length).to.equal(numNodes); + sinon.stub(config, 'getConfig').callsFake((key) => { + return utils.deepAccess(mockConfig, key); + }); + }); - // 1 comma in the front for version - // 5 commas per node - expect(dataParams.schain.match(/,/g).length).to.equal(numNodes * 5 + 1); - }); + afterEach(function () { + config.getConfig.restore(); + }); - it('should send a schain with the right version', function () { - const request = spec.buildRequests(bidRequests, mockBidderRequest); - const dataParams = request[0].data; - let serializedSupplyChain = dataParams.schain.split('!'); - let version = serializedSupplyChain.shift().split(',')[0]; + it('should send a signal to specify that US Privacy applies to this request', function () { + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + expect(request[0].data.regs.ext.us_privacy).to.equal('1YYN'); + expect(request[1].data.regs.ext.us_privacy).to.equal('1YYN'); + }); - expect(version).to.equal(bidRequests[0].schain.ver); - }); + it('should not send the regs object, when consent string is undefined', function () { + delete bidderRequest.uspConsent; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + expect(request[0].data.regs?.us_privacy).to.not.exist; + }); + }); - it('should send a schain with the right complete value', function () { - const request = spec.buildRequests(bidRequests, mockBidderRequest); - const dataParams = request[0].data; - let serializedSupplyChain = dataParams.schain.split('!'); - let isComplete = serializedSupplyChain.shift().split(',')[1]; + describe('GDPR', function () { + beforeEach(function () { + bidderRequest = { + gdprConsent: { + consentString: 'test-gdpr-consent-string', + addtlConsent: 'test-addtl-consent-string', + gdprApplies: true + }, + refererInfo: {} + }; + + mockConfig = { + consentManagement: { + cmpApi: 'iab', + timeout: 1111, + allowAuctionWithoutConsent: 'cancel' + } + }; - expect(isComplete).to.equal(String(bidRequests[0].schain.complete)); - }); + sinon.stub(config, 'getConfig').callsFake((key) => { + return utils.deepAccess(mockConfig, key); + }); + }); - it('should send all available params in the right order', function () { - const request = spec.buildRequests(bidRequests, mockBidderRequest); - const dataParams = request[0].data; - let serializedSupplyChain = dataParams.schain.split('!'); - serializedSupplyChain.shift(); + afterEach(function () { + config.getConfig.restore(); + }); - serializedSupplyChain.forEach((serializedNode, nodeIndex) => { - let nodeProperties = serializedNode.split(','); + it('should send a signal to specify that GDPR applies to this request', function () { + bidderRequest.bids = bidRequests; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + expect(request[0].data.regs.ext.gdpr).to.equal(1); + expect(request[1].data.regs.ext.gdpr).to.equal(1); + }); - nodeProperties.forEach((nodeProperty, propertyIndex) => { - let node = schainConfig.nodes[nodeIndex]; - let key = supplyChainNodePropertyOrder[propertyIndex]; + it('should send the consent string', function () { + bidderRequest.bids = bidRequests; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + expect(request[0].data.user.ext.consent).to.equal(bidderRequest.gdprConsent.consentString); + expect(request[1].data.user.ext.consent).to.equal(bidderRequest.gdprConsent.consentString); + }); - expect(nodeProperty).to.equal(node[key] ? String(node[key]) : '', - `expected node '${nodeIndex}' property '${nodeProperty}' to key '${key}' to be the same value`) + it('should send the addtlConsent string', function () { + bidderRequest.bids = bidRequests; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + expect(request[0].data.user.ext.ConsentedProvidersSettings.consented_providers).to.equal(bidderRequest.gdprConsent.addtlConsent); + expect(request[1].data.user.ext.ConsentedProvidersSettings.consented_providers).to.equal(bidderRequest.gdprConsent.addtlConsent); }); - }); - }); - }); - describe('when there are userid providers', function () { - const EXAMPLE_DATA_BY_ATTR = { - britepoolid: '1111-britepoolid', - criteoId: '1111-criteoId', - fabrickId: '1111-fabrickid', - haloId: '1111-haloid', - id5id: {uid: '1111-id5id'}, - idl_env: '1111-idl_env', - IDP: '1111-zeotap-idplusid', - idxId: '1111-idxid', - intentIqId: '1111-intentiqid', - lipb: {lipbid: '1111-lipb'}, - lotamePanoramaId: '1111-lotameid', - merkleId: {id: '1111-merkleid'}, - netId: 'fH5A3n2O8_CZZyPoJVD-eabc6ECb7jhxCicsds7qSg', - parrableId: { eid: 'eidVersion.encryptionKeyReference.encryptedValue' }, - pubcid: '1111-pubcid', - quantcastId: '1111-quantcastid', - tapadId: '111-tapadid', - tdid: '1111-tdid', - uid2: {id: '1111-uid2'}, - novatiq: {snowflake: '1111-novatiqid'}, - admixerId: '1111-admixerid', - deepintentId: '1111-deepintentid', - dmdId: '111-dmdid', - nextrollId: '1111-nextrollid', - mwOpenLinkId: '1111-mwopenlinkid', - dapId: '1111-dapId', - amxId: '1111-amxid', - kpuid: '1111-kpuid', - publinkId: '1111-publinkid', - naveggId: '1111-naveggid', - imuid: '1111-imuid', - adtelligentId: '1111-adtelligentid' - }; - - // generates the same set of tests for each id provider - utils._each(USER_ID_CODE_TO_QUERY_ARG, (userIdQueryArg, userIdProviderKey) => { - describe(`with userId attribute: ${userIdProviderKey}`, function () { - it(`should not send a ${userIdQueryArg} query param when there is no userId.${userIdProviderKey} defined in the bid requests`, function () { - const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); - expect(request[0].data).to.not.have.any.keys(userIdQueryArg); + it('should send a signal to specify that GDPR does not apply to this request', function () { + bidderRequest.gdprConsent.gdprApplies = false; + bidderRequest.bids = bidRequests; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + expect(request[0].data.regs.ext.gdpr).to.equal(0); + expect(request[1].data.regs.ext.gdpr).to.equal(0); }); - it(`should send a ${userIdQueryArg} query param when userId.${userIdProviderKey} is defined in the bid requests`, function () { - const bidRequestsWithUserId = [{ - bidder: 'openx', - params: { - unit: '11', - delDomain: 'test-del-domain' - }, - userId: { - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]] - } - }, - bidId: 'test-bid-id-1', - bidderRequestId: 'test-bid-request-1', - auctionId: 'test-auction-1' - }]; - // enrich bid request with userId key/value - bidRequestsWithUserId[0].userId[userIdProviderKey] = EXAMPLE_DATA_BY_ATTR[userIdProviderKey]; - - const request = spec.buildRequests(bidRequestsWithUserId, mockBidderRequest); - - let userIdValue; - // handle cases where userId key refers to an object - switch (userIdProviderKey) { - case 'merkleId': - userIdValue = EXAMPLE_DATA_BY_ATTR.merkleId.id; - break; - case 'uid2': - userIdValue = EXAMPLE_DATA_BY_ATTR.uid2.id; - break; - case 'lipb': - userIdValue = EXAMPLE_DATA_BY_ATTR.lipb.lipbid; - break; - case 'parrableId': - userIdValue = EXAMPLE_DATA_BY_ATTR.parrableId.eid; - break; - case 'id5id': - userIdValue = EXAMPLE_DATA_BY_ATTR.id5id.uid; - break; - case 'novatiq': - userIdValue = EXAMPLE_DATA_BY_ATTR.novatiq.snowflake; - break; - default: - userIdValue = EXAMPLE_DATA_BY_ATTR[userIdProviderKey]; - } + it('when GDPR application is undefined, should not send a signal to specify whether GDPR applies to this request, ' + + 'but can send consent data, ', function () { + delete bidderRequest.gdprConsent.gdprApplies; + bidderRequest.bids = bidRequests; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + expect(request[0].data.regs?.ext?.gdpr).to.not.be.ok; + expect(request[0].data.user.ext.consent).to.equal(bidderRequest.gdprConsent.consentString); + expect(request[1].data.user.ext.consent).to.equal(bidderRequest.gdprConsent.consentString); + }); - expect(request[0].data[USER_ID_CODE_TO_QUERY_ARG[userIdProviderKey]]).to.equal(userIdValue); + it('when consent string is undefined, should not send the consent string, ', function () { + delete bidderRequest.gdprConsent.consentString; + bidderRequest.bids = bidRequests; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + expect(request[0].data.imp[0].ext.consent).to.equal(undefined); + expect(request[1].data.imp[0].ext.consent).to.equal(undefined); }); }); }); - }); - - describe('floors', function () { - it('should send out custom floors on bids that have customFloors specified', function () { - const bidRequest = Object.assign({}, - bidRequestsWithMediaTypes[0], - { - params: { - 'unit': '12345678', - 'delDomain': 'test-del-domain', - 'customFloor': 1.500001 - } - } - ); - - const request = spec.buildRequests([bidRequest], mockBidderRequest); - const dataParams = request[0].data; - - expect(dataParams.aumfs).to.exist; - expect(dataParams.aumfs).to.equal('1500'); - }); - context('with floors module', function () { - let adServerCurrencyStub; - - beforeEach(function () { - adServerCurrencyStub = sinon - .stub(config, 'getConfig') - .withArgs('currency.adServerCurrency') + context('coppa', function() { + it('when there are no coppa param settings, should not send a coppa flag', function () { + const request = spec.buildRequests(bidRequestsWithMediaTypes, syncAddFPDToBidderRequest(mockBidderRequest)); + expect(request[0].data.regs?.coppa).to.be.not.ok; }); - afterEach(function () { - config.getConfig.restore(); - }); - - it('should send out floors on bids', function () { - const bidRequest1 = Object.assign({}, - bidRequestsWithMediaTypes[0], - { - getFloor: () => { - return { - currency: 'AUS', - floor: 9.99 - } - } - } - ); - - const bidRequest2 = Object.assign({}, - bidRequestsWithMediaTypes[1], - { - getFloor: () => { - return { - currency: 'AUS', - floor: 18.881 - } - } - } - ); + it('should send a coppa flag there is when there is coppa param settings in the bid requests', function () { + let mockConfig = { + coppa: true + }; - const request = spec.buildRequests([bidRequest1, bidRequest2], mockBidderRequest); - const dataParams = request[0].data; + sinon.stub(config, 'getConfig').callsFake((key) => { + return utils.deepAccess(mockConfig, key); + }); - expect(dataParams.aumfs).to.exist; - expect(dataParams.aumfs).to.equal('9990,18881'); + const request = spec.buildRequests(bidRequestsWithMediaTypes, syncAddFPDToBidderRequest(mockBidderRequest)); + expect(request[0].data.regs.coppa).to.equal(1); }); - it('should send out floors on bids in the default currency', function () { - const bidRequest1 = Object.assign({}, - bidRequestsWithMediaTypes[0], - { - getFloor: () => { - return {}; - } - } - ); - - let getFloorSpy = sinon.spy(bidRequest1, 'getFloor'); - - spec.buildRequests([bidRequest1], mockBidderRequest); - expect(getFloorSpy.args[0][0].mediaType).to.equal(BANNER); - expect(getFloorSpy.args[0][0].currency).to.equal('USD'); + it('should send a coppa flag there is when there is coppa param settings in the bid params', function () { + const request = spec.buildRequests(bidRequestsWithMediaTypes, syncAddFPDToBidderRequest(mockBidderRequest)); + request.params = {coppa: true}; + expect(request[0].data.regs.coppa).to.equal(1); }); - it('should send out floors on bids in the ad server currency if defined', function () { - adServerCurrencyStub.returns('bitcoin'); - - const bidRequest1 = Object.assign({}, - bidRequestsWithMediaTypes[0], - { - getFloor: () => { - return {}; - } - } - ); - - let getFloorSpy = sinon.spy(bidRequest1, 'getFloor'); - - spec.buildRequests([bidRequest1], mockBidderRequest); - expect(getFloorSpy.args[0][0].mediaType).to.equal(BANNER); - expect(getFloorSpy.args[0][0].currency).to.equal('bitcoin'); + after(function () { + config.getConfig.restore() }); - }) - }) - }); - - describe('buildRequests for video', function () { - const bidRequestsWithMediaTypes = VIDEO_BID_REQUESTS_WITH_MEDIA_TYPES; - const mockBidderRequest = {refererInfo: {}}; - - it('should send bid request to openx url via GET, with mediaTypes having video parameter', function () { - const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); - expect(request[0].url).to.equal('https://' + bidRequestsWithMediaTypes[0].params.delDomain + URLBASEVIDEO); - expect(request[0].method).to.equal('GET'); - }); - it('should have the correct parameters', function () { - const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); - const dataParams = request[0].data; - - expect(dataParams.auid).to.equal('12345678'); - expect(dataParams.vht).to.equal(480); - expect(dataParams.vwd).to.equal(640); - expect(dataParams.aucs).to.equal(encodeURIComponent('/12345/my-gpt-tag-0')); - }); - - it('shouldn\'t have the test parameter', function () { - const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); - expect(request[0].data.vtest).to.be.undefined; - }); - - it('should send a bc parameter', function () { - const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); - const dataParams = request[0].data; - - expect(dataParams.bc).to.have.string('hb_pb'); - }); - - describe('when using the video param', function () { - let videoBidRequest; - let mockBidderRequest = {refererInfo: {}}; - - beforeEach(function () { - videoBidRequest = { - 'bidder': 'openx', - 'mediaTypes': { - video: { - context: 'instream', - playerSize: [640, 480] - } - }, - 'params': { - 'unit': '12345678', - 'delDomain': 'test-del-domain' - }, - 'adUnitCode': 'adunit-code', - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - 'transactionId': '4008d88a-8137-410b-aa35-fbfdabcb478e' - }; - mockBidderRequest = {refererInfo: {}}; - }); - - it('should not allow you to set a url', function () { - videoBidRequest.params.video = { - url: 'test-url' - }; - const request = spec.buildRequests([videoBidRequest], mockBidderRequest); - - expect(request[0].data.url).to.be.undefined; - }); - - it('should not allow you to override the javascript url', function () { - let myUrl = 'my-url'; - videoBidRequest.params.video = { - ju: myUrl - }; - const request = spec.buildRequests([videoBidRequest], mockBidderRequest); - - expect(request[0].data.ju).to.not.equal(myUrl); }); - describe('when using the openrtb video params', function () { - it('should parse legacy params.video.openrtb', function () { - let myOpenRTBObject = {mimes: ['application/javascript']}; - videoBidRequest.params.video = { - openrtb: myOpenRTBObject - }; - const expected = {imp: [{video: {w: 640, h: 480, mimes: ['application/javascript']}}]} - const request = spec.buildRequests([videoBidRequest], mockBidderRequest); + context('do not track (DNT)', function() { + let doNotTrackStub; - expect(request[0].data.openrtb).to.equal(JSON.stringify(expected)); + beforeEach(function () { + doNotTrackStub = sinon.stub(utils, 'getDNT'); }); - - it('should parse legacy params.openrtb', function () { - let myOpenRTBObject = {mimes: ['application/javascript']}; - videoBidRequest.params.openrtb = myOpenRTBObject; - const expected = {imp: [{video: {w: 640, h: 480, mimes: ['application/javascript']}}]} - const request = spec.buildRequests([videoBidRequest], mockBidderRequest); - - expect(request[0].data.openrtb).to.equal(JSON.stringify(expected)); + afterEach(function() { + doNotTrackStub.restore(); }); - it('should parse legacy params.video', function () { - let myOpenRTBObject = {mimes: ['application/javascript']}; - videoBidRequest.params.video = myOpenRTBObject; - const expected = {imp: [{video: {w: 640, h: 480, mimes: ['application/javascript']}}]} - const request = spec.buildRequests([videoBidRequest], mockBidderRequest); + it('when there is a do not track, should send a dnt', function () { + doNotTrackStub.returns(1); - expect(request[0].data.openrtb).to.equal(JSON.stringify(expected)); + const request = spec.buildRequests(bidRequestsWithMediaTypes, syncAddFPDToBidderRequest(mockBidderRequest)); + expect(request[0].data.device.dnt).to.equal(1); }); - it('should parse legacy params.video as full openrtb', function () { - let myOpenRTBObject = {imp: [{video: {mimes: ['application/javascript']}}]}; - videoBidRequest.params.video = myOpenRTBObject; - const expected = {imp: [{video: {w: 640, h: 480, mimes: ['application/javascript']}}]} - const request = spec.buildRequests([videoBidRequest], mockBidderRequest); + it('when there is not do not track, don\'t send dnt', function () { + doNotTrackStub.returns(0); - expect(request[0].data.openrtb).to.equal(JSON.stringify(expected)); + const request = spec.buildRequests(bidRequestsWithMediaTypes, syncAddFPDToBidderRequest(mockBidderRequest)); + expect(request[0].data.device.dnt).to.equal(0); }); - it('should parse legacy video.openrtb', function () { - let myOpenRTBObject = {mimes: ['application/javascript']}; - videoBidRequest.params.video = { - openrtb: myOpenRTBObject - }; - const expected = {imp: [{video: {w: 640, h: 480, mimes: ['application/javascript']}}]} - const request = spec.buildRequests([videoBidRequest], mockBidderRequest); + it('when there is no defined do not track, don\'t send dnt', function () { + doNotTrackStub.returns(null); - expect(request[0].data.openrtb).to.equal(JSON.stringify(expected)); + const request = spec.buildRequests(bidRequestsWithMediaTypes, syncAddFPDToBidderRequest(mockBidderRequest)); + expect(request[0].data.device.dnt).to.equal(0); }); + }); - it('should omit filtered values for legacy', function () { - let myOpenRTBObject = {mimes: ['application/javascript'], dont: 'use'}; - videoBidRequest.params.video = { - openrtb: myOpenRTBObject + context('supply chain (schain)', function () { + let bidRequests; + let schainConfig; + const supplyChainNodePropertyOrder = ['asi', 'sid', 'hp', 'rid', 'name', 'domain']; + + beforeEach(function () { + schainConfig = { + ver: '1.0', + complete: 1, + nodes: [ + { + asi: 'exchange1.com', + sid: '1234', + hp: 1, + rid: 'bid-request-1', + name: 'publisher', + domain: 'publisher.com' + // omitted ext + }, + { + asi: 'exchange2.com', + sid: 'abcd', + hp: 1, + rid: 'bid-request-2', + // name field missing + domain: 'intermediary.com' + }, + { + asi: 'exchange3.com', + sid: '4321', + hp: 1, + // request id + // name field missing + domain: 'intermediary-2.com' + } + ] }; - const expected = {imp: [{video: {w: 640, h: 480, mimes: ['application/javascript']}}]} - const request = spec.buildRequests([videoBidRequest], mockBidderRequest); - expect(request[0].data.openrtb).to.equal(JSON.stringify(expected)); + bidRequests = [{ + bidder: 'openx', + params: { + unit: '11', + delDomain: 'test-del-domain' + }, + adUnitCode: '/adunit-code/test-path', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + bidId: 'test-bid-id-1', + bidderRequestId: 'test-bid-request-1', + auctionId: 'test-auction-1', + schain: schainConfig + }]; }); - it('should parse mediatypes.video', function () { - videoBidRequest.mediaTypes.video.mimes = ['application/javascript'] - videoBidRequest.mediaTypes.video.minduration = 15 - const request = spec.buildRequests([videoBidRequest], mockBidderRequest); - const openRtbRequestParams = JSON.parse(request[0].data.openrtb); - expect(openRtbRequestParams.imp[0].video.mimes).to.eql(['application/javascript']); - expect(openRtbRequestParams.imp[0].video.minduration).to.equal(15); + it('should send a supply chain object', function () { + const request = spec.buildRequests(bidRequests, mockBidderRequest); + expect(request[0].data.source.ext.schain).to.equal(schainConfig); }); - it('should filter mediatypes.video', function () { - videoBidRequest.mediaTypes.video.mimes = ['application/javascript'] - videoBidRequest.mediaTypes.video.minnothing = 15 - const request = spec.buildRequests([videoBidRequest], mockBidderRequest); - const openRtbRequestParams = JSON.parse(request[0].data.openrtb); - expect(openRtbRequestParams.imp[0].video.mimes).to.eql(['application/javascript']); - expect(openRtbRequestParams.imp[0].video.minnothing).to.equal(undefined); + it('should send the supply chain object with the right version', function () { + const request = spec.buildRequests(bidRequests, mockBidderRequest); + expect(request[0].data.source.ext.schain.ver).to.equal(schainConfig.ver); }); - it("should use the bidRequest's playerSize", function () { - const width = 200; - const height = 100; - const myOpenRTBObject = {v: height, w: width}; - videoBidRequest.params.video = { - openrtb: myOpenRTBObject - }; - const request = spec.buildRequests([videoBidRequest], mockBidderRequest); - const openRtbRequestParams = JSON.parse(request[0].data.openrtb); - - expect(openRtbRequestParams.imp[0].video.w).to.equal(640); - expect(openRtbRequestParams.imp[0].video.h).to.equal(480); + it('should send the supply chain object with the right complete value', function () { + const request = spec.buildRequests(bidRequests, mockBidderRequest); + expect(request[0].data.source.ext.schain.complete).to.equal(schainConfig.complete); }); }); - }); - describe('floors', function () { - it('should send out custom floors on bids that have customFloors specified', function () { - const bidRequest = Object.assign({}, - bidRequestsWithMediaTypes[0], + context('when there are userid providers', function () { + const userIdAsEids = [ { - params: { - 'unit': '12345678', - 'delDomain': 'test-del-domain', - 'customFloor': 1.500001 - } + source: 'adserver.org', + uids: [{ + id: 'some-random-id-value', + atype: 1, + ext: { + rtiPartner: 'TDID' + } + }] + }, + { + source: 'id5-sync.com', + uids: [{ + id: 'some-random-id-value', + atype: 1 + }] + }, + { + source: 'sharedid.org', + uids: [{ + id: 'some-random-id-value', + atype: 1, + ext: { + third: 'some-random-id-value' + } + }] } - ); - - const request = spec.buildRequests([bidRequest], mockBidderRequest); - const dataParams = request[0].data; - - expect(dataParams.aumfs).to.exist; - expect(dataParams.aumfs).to.equal('1500'); - }); + ]; - context('with floors module', function () { - let adServerCurrencyStub; - function makeBidWithFloorInfo(floorInfo) { - return Object.assign(utils.deepClone(bidRequestsWithMediaTypes[0]), - { - getFloor: () => { - return floorInfo; + it(`should send the user id under the extended ids`, function () { + const bidRequestsWithUserId = [{ + bidder: 'openx', + params: { + unit: '11', + delDomain: 'test-del-domain' + }, + userId: { + }, + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] } - }); - } - - beforeEach(function () { - adServerCurrencyStub = sinon - .stub(config, 'getConfig') - .withArgs('currency.adServerCurrency') + }, + bidId: 'test-bid-id-1', + bidderRequestId: 'test-bid-request-1', + auctionId: 'test-auction-1', + userIdAsEids: userIdAsEids + }]; + // enrich bid request with userId key/value + + const request = spec.buildRequests(bidRequestsWithUserId, mockBidderRequest); + expect(request[0].data.user.ext.eids).to.equal(userIdAsEids); }); - afterEach(function () { - config.getConfig.restore(); + it(`when no user ids are available, it should not send any extended ids`, function () { + const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); + expect(request[0].data).to.not.have.any.keys('user'); }); + }); - it('should send out floors on bids', function () { - const floors = [9.99, 18.881]; - const bidRequests = floors.map(floor => { - return makeBidWithFloorInfo({ - currency: 'AUS', - floor: floor - }); + context('FLEDGE', function() { + it('when FLEDGE is enabled, should send whatever is set in ortb2imp.ext.ae in all bid requests', function () { + const request = spec.buildRequests(bidRequestsWithMediaTypes, { + ...mockBidderRequest, + fledgeEnabled: true }); - const request = spec.buildRequests(bidRequests, mockBidderRequest); - - expect(request[0].data.aumfs).to.exist; - expect(request[0].data.aumfs).to.equal('9990'); - expect(request[1].data.aumfs).to.exist; - expect(request[1].data.aumfs).to.equal('18881'); + expect(request[0].data.imp[0].ext.ae).to.equal(2); }); + }); + }); - it('should send out floors on bids in the default currency', function () { - const bidRequest1 = makeBidWithFloorInfo({}); - - let getFloorSpy = sinon.spy(bidRequest1, 'getFloor'); + context('banner', function () { + it('should send bid request with a mediaTypes specified with banner type', function () { + const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); + expect(request[0].data.imp[0]).to.have.any.keys(BANNER); + }); + }); - spec.buildRequests([bidRequest1], mockBidderRequest); - expect(getFloorSpy.args[0][0].mediaType).to.equal(VIDEO); - expect(getFloorSpy.args[0][0].currency).to.equal('USD'); + if (FEATURES.VIDEO) { + context('video', function () { + it('should send bid request with a mediaTypes specified with video type', function () { + const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); + expect(request[1].data.imp[0]).to.have.any.keys(VIDEO); }); - it('should send out floors on bids in the ad server currency if defined', function () { - adServerCurrencyStub.returns('bitcoin'); - - const bidRequest1 = makeBidWithFloorInfo({}); - - let getFloorSpy = sinon.spy(bidRequest1, 'getFloor'); - - spec.buildRequests([bidRequest1], mockBidderRequest); - expect(getFloorSpy.args[0][0].mediaType).to.equal(VIDEO); - expect(getFloorSpy.args[0][0].currency).to.equal('bitcoin'); + it('Update imp.video with OpenRTB options from mimeTypes and params', function() { + const bid01 = new BidRequestBuilder({ + adUnitCode: 'adunit-code-01', + mediaTypes: { + banner: { sizes: [[300, 250]] }, + video: { + context: 'outstream', + playerSize: [[300, 250]], + mimes: ['video/mp4'], + protocols: [8] + } + }, + }).withParams({ + // options in video, will merge + video: { + skip: 1, + skipafter: 4, + minduration: 10, + maxduration: 30 + } + }).build(); + + const bidderRequest = new BidderRequestBuilder().build(); + const expected = { + mimes: ['video/mp4'], + skip: 1, + skipafter: 4, + minduration: 10, + maxduration: 30, + placement: 4, + protocols: [8], + w: 300, + h: 250 + }; + const requests = spec.buildRequests([bid01], bidderRequest); + expect(requests).to.have.lengthOf(2); + expect(requests[1].data.imp[0].video).to.deep.equal(expected); }); - }) - }) + }); + } }); - describe('buildRequest for multi-format ad', function () { - const multiformatBid = MULTI_FORMAT_BID_REQUESTS[0]; - let mockBidderRequest = {refererInfo: {}}; + describe('interpretResponse()', function () { + let bidRequestConfigs; + let bidRequest; + let bidResponse; + let bid; - it('should default to a banner request', function () { - const request = spec.buildRequests([multiformatBid], mockBidderRequest); - const dataParams = request[0].data; - - expect(dataParams.divids).to.have.string(multiformatBid.adUnitCode); - }); - }); - - describe('buildRequests for all kinds of ads', function () { - utils._each({ - banner: BANNER_BID_REQUESTS_WITH_MEDIA_TYPES[0], - video: VIDEO_BID_REQUESTS_WITH_MEDIA_TYPES[0], - multi: MULTI_FORMAT_BID_REQUESTS[0] - }, (bidRequest, name) => { - describe('with segments', function () { - const TESTS = [ - { - name: 'should send proprietary segment data from ortb2.user.data', - config: { - ortb2: { - user: { - data: [ - {name: 'dmp1', ext: {segtax: 4}, segment: [{id: 'foo'}, {id: 'bar'}]}, - {name: 'dmp2', segment: [{id: 'baz'}]}, - ] - } - } - }, - expect: {sm: 'dmp1/4:foo|bar,dmp2:baz'}, - }, - { - name: 'should send proprietary segment data from ortb2.site.content.data', - config: { - ortb2: { - site: { - content: { - data: [ - {name: 'dmp1', ext: {segtax: 4}, segment: [{id: 'foo'}, {id: 'bar'}]}, - {name: 'dmp2', segment: [{id: 'baz'}]}, - ] - } - } - } - }, - expect: {scsm: 'dmp1/4:foo|bar,dmp2:baz'}, - }, - { - name: 'should send proprietary segment data from both ortb2.site.content.data and ortb2.user.data', - config: { - ortb2: { - user: { - data: [ - {name: 'dmp1', ext: {segtax: 4}, segment: [{id: 'foo'}, {id: 'bar'}]}, - {name: 'dmp2', segment: [{id: 'baz'}]}, - ] - }, - site: { - content: { - data: [ - {name: 'dmp3', ext: {segtax: 5}, segment: [{id: 'foo2'}, {id: 'bar2'}]}, - {name: 'dmp4', segment: [{id: 'baz2'}]}, - ] - } - } - } - }, - expect: { - sm: 'dmp1/4:foo|bar,dmp2:baz', - scsm: 'dmp3/5:foo2|bar2,dmp4:baz2' - }, - }, - { - name: 'should combine same provider segment data from ortb2.user.data', - config: { - ortb2: { - user: { - data: [ - {name: 'dmp1', ext: {segtax: 4}, segment: [{id: 'foo'}, {id: 'bar'}]}, - {name: 'dmp1', ext: {}, segment: [{id: 'baz'}]}, - ] - } - } - }, - expect: {sm: 'dmp1/4:foo|bar,dmp1:baz'}, - }, - { - name: 'should combine same provider segment data from ortb2.site.content.data', - config: { - ortb2: { - site: { - content: { - data: [ - {name: 'dmp1', ext: {segtax: 4}, segment: [{id: 'foo'}, {id: 'bar'}]}, - {name: 'dmp1', ext: {}, segment: [{id: 'baz'}]}, - ] - } - } - } - }, - expect: {scsm: 'dmp1/4:foo|bar,dmp1:baz'}, - }, - { - name: 'should not send any segment data if first party config is incomplete', - config: { - ortb2: { - user: { - data: [ - {name: 'provider-with-no-segments'}, - {segment: [{id: 'segments-with-no-provider'}]}, - {}, - ] - } - } - } + context('when there is an nbr response', function () { + let bids; + beforeEach(function () { + bidRequestConfigs = [{ + bidder: 'openx', + params: { + unit: '12345678', + delDomain: 'test-del-domain' }, - { - name: 'should send first party data segments and liveintent segments from request', - config: { - ortb2: { - user: { - data: [ - {name: 'dmp1', segment: [{id: 'foo'}, {id: 'bar'}]}, - {name: 'dmp2', segment: [{id: 'baz'}]}, - ] - }, - site: { - content: { - data: [ - {name: 'dmp3', ext: {segtax: 5}, segment: [{id: 'foo2'}, {id: 'bar2'}]}, - {name: 'dmp4', segment: [{id: 'baz2'}]}, - ] - } - } - } - }, - request: { - userId: { - lipb: { - lipbid: 'aaa', - segments: ['l1', 'l2'] - }, - }, - }, - expect: { - sm: 'dmp1:foo|bar,dmp2:baz,liveintent:l1|l2', - scsm: 'dmp3/5:foo2|bar2,dmp4:baz2' + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]], }, }, - { - name: 'should send just liveintent segment from request if no first party config', - config: {}, - request: { - userId: { - lipb: { - lipbid: 'aaa', - segments: ['l1', 'l2'] - }, - }, - }, - expect: {sm: 'liveintent:l1|l2'}, + bidId: 'test-bid-id', + bidderRequestId: 'test-bidder-request-id', + auctionId: 'test-auction-id' + }]; + + bidRequest = spec.buildRequests(bidRequestConfigs, {refererInfo: {}})[0]; + + bidResponse = {nbr: 0}; // Unknown error + bids = spec.interpretResponse({body: bidResponse}, bidRequest); + }); + + it('should not return any bids', function () { + expect(bids.length).to.equal(0); + }); + }); + + context('when no seatbid in response', function () { + let bids; + beforeEach(function () { + bidRequestConfigs = [{ + bidder: 'openx', + params: { + unit: '12345678', + delDomain: 'test-del-domain' }, - { - name: 'should send nothing if lipb section does not contain segments', - config: {}, - request: { - userId: { - lipb: { - lipbid: 'aaa', - }, - }, + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]], }, }, - ]; - utils._each(TESTS, (t) => { - context('in ortb2.user.data', function () { - let bidRequests; - beforeEach(function () { - bidRequests = [{...bidRequest, ...t.request}]; - }); + bidId: 'test-bid-id', + bidderRequestId: 'test-bidder-request-id', + auctionId: 'test-auction-id' + }]; - const mockBidderRequest = {refererInfo: {}, ortb2: t.config.ortb2}; - it(`${t.name} for type ${name}`, function () { - const request = spec.buildRequests(bidRequests, mockBidderRequest) - expect(request.length).to.equal(1); - if (t.expect) { - for (const key in t.expect) { - expect(request[0].data[key]).to.exist; - expect(request[0].data[key]).to.equal(t.expect[key]); - } - } else { - expect(request[0].data.sm).to.not.exist; - expect(request[0].data.scsm).to.not.exist; - } - }); - }); - }); + bidRequest = spec.buildRequests(bidRequestConfigs, {refererInfo: {}})[0]; + + bidResponse = {ext: {}, id: 'test-bid-id'}; + bids = spec.interpretResponse({body: bidResponse}, bidRequest); }); - describe('with user agent client hints', function () { - it('should add json query param sua with BidRequest.device.sua if available', function () { - const bidderRequestWithUserAgentClientHints = { refererInfo: {}, - ortb2: { - device: { - sua: { - source: 2, - platform: { - brand: 'macOS', - version: [ '12', '4', '0' ] - }, - browsers: [ - { - brand: 'Chromium', - version: [ '106', '0', '5249', '119' ] - }, - { - brand: 'Google Chrome', - version: [ '106', '0', '5249', '119' ] - }, - { - brand: 'Not;A=Brand', - version: [ '99', '0', '0', '0' ] - }], - mobile: 0, - model: 'Pro', - bitness: '64', - architecture: 'x86' - } - } - }}; - - let request = spec.buildRequests([bidRequest], bidderRequestWithUserAgentClientHints); - expect(request[0].data.sua).to.exist; - const payload = JSON.parse(request[0].data.sua); - expect(payload).to.deep.equal(bidderRequestWithUserAgentClientHints.ortb2.device.sua); - const bidderRequestWithoutUserAgentClientHints = {refererInfo: {}, ortb2: {}}; - request = spec.buildRequests([bidRequest], bidderRequestWithoutUserAgentClientHints); - expect(request[0].data.sua).to.not.exist; - }); + + it('should not return any bids', function () { + expect(bids.length).to.equal(0); }); }); - }) - describe('interpretResponse for banner ads', function () { - beforeEach(function () { - sinon.spy(userSync, 'registerSync'); - }); + context('when there is no response', function () { + let bids; + beforeEach(function () { + bidRequestConfigs = [{ + bidder: 'openx', + params: { + unit: '12345678', + delDomain: 'test-del-domain' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]], + }, + }, + bidId: 'test-bid-id', + bidderRequestId: 'test-bidder-request-id', + auctionId: 'test-auction-id' + }]; + + bidRequest = spec.buildRequests(bidRequestConfigs, {refererInfo: {}})[0]; - afterEach(function () { - userSync.registerSync.restore(); + bidResponse = ''; // Unknown error + bids = spec.interpretResponse({body: bidResponse}, bidRequest); + }); + + it('should not return any bids', function () { + expect(bids.length).to.equal(0); + }); }); - describe('when there is a standard response', function () { - const creativeOverride = { - id: 234, - width: '300', - height: '250', - tracking: { - impression: 'https://openx-d.openx.net/v/1.0/ri?ts=ts' + const SAMPLE_BID_REQUESTS = [{ + bidder: 'openx', + params: { + unit: '12345678', + delDomain: 'test-del-domain' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]], + }, + }, + bidId: 'test-bid-id', + bidderRequestId: 'test-bidder-request-id', + auctionId: 'test-auction-id' + }]; + + const SAMPLE_BID_RESPONSE = { + seatbid: [{ + bid: [{ + impid: 'test-bid-id', + price: 2, + w: 300, + h: 250, + crid: 'test-creative-id', + dealid: 'test-deal-id', + adm: 'test-ad-markup', + adomain: ['brand.com'], + ext: { + dsp_id: '123', + buyer_id: '456', + brand_id: '789', + paf: { + content_id: 'paf_content_id' + } + } + }] + }], + cur: 'AUS', + ext: { + paf: { + transmission: {version: '12'} } - }; - - const adUnitOverride = { - ts: 'test-1234567890-ts', - idx: '0', - currency: 'USD', - pub_rev: '10000', - html: '
OpenX Ad
' - }; - let adUnit; - let bidResponse; - - let bid; - let bidRequest; - let bidRequestConfigs; + } + }; + context('when there is a response, the common response properties', function () { beforeEach(function () { - bidRequestConfigs = [{ - 'bidder': 'openx', - 'params': { - 'unit': '12345678', - 'delDomain': 'test-del-domain' - }, - 'adUnitCode': 'adunit-code', - 'mediaType': 'banner', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475' - }]; - - bidRequest = { - method: 'GET', - url: 'https://openx-d.openx.net/v/1.0/arj', - data: {}, - payload: {'bids': bidRequestConfigs, 'startTime': new Date()} - }; + bidRequestConfigs = deepClone(SAMPLE_BID_REQUESTS); + bidRequest = spec.buildRequests(bidRequestConfigs, {refererInfo: {}})[0]; + bidResponse = deepClone(SAMPLE_BID_RESPONSE); - adUnit = mockAdUnit(adUnitOverride, creativeOverride); - bidResponse = mockArjResponse(undefined, [adUnit]); bid = spec.interpretResponse({body: bidResponse}, bidRequest)[0]; }); it('should return a price', function () { - expect(bid.cpm).to.equal(parseInt(adUnitOverride.pub_rev, 10) / 1000); + expect(bid.cpm).to.equal(bidResponse.seatbid[0].bid[0].price); }); it('should return a request id', function () { - expect(bid.requestId).to.equal(bidRequest.payload.bids[0].bidId); + expect(bid.requestId).to.equal(bidResponse.seatbid[0].bid[0].impid); }); it('should return width and height for the creative', function () { - expect(bid.width).to.equal(creativeOverride.width); - expect(bid.height).to.equal(creativeOverride.height); + expect(bid.width).to.equal(bidResponse.seatbid[0].bid[0].w); + expect(bid.height).to.equal(bidResponse.seatbid[0].bid[0].h); }); it('should return a creativeId', function () { - expect(bid.creativeId).to.equal(creativeOverride.id); + expect(bid.creativeId).to.equal(bidResponse.seatbid[0].bid[0].crid); }); it('should return an ad', function () { - expect(bid.ad).to.equal(adUnitOverride.html); + expect(bid.ad).to.equal(bidResponse.seatbid[0].bid[0].adm); + }); + + it('should return a deal id if it exists', function () { + expect(bid.dealId).to.equal(bidResponse.seatbid[0].bid[0].dealid); }); it('should have a time-to-live of 5 minutes', function () { @@ -1863,415 +1286,293 @@ describe('OpenxAdapter', function () { }); it('should return a currency', function () { - expect(bid.currency).to.equal(adUnitOverride.currency); - }); - - it('should return a transaction state', function () { - expect(bid.ts).to.equal(adUnitOverride.ts); + expect(bid.currency).to.equal(bidResponse.cur); }); it('should return a brand ID', function () { - expect(bid.meta.brandId).to.equal(DEFAULT_TEST_ARJ_AD_UNIT.brand_id); + expect(bid.meta.brandId).to.equal(bidResponse.seatbid[0].bid[0].ext.brand_id); }); - it('should return an adomain', function () { - expect(bid.meta.advertiserDomains).to.deep.equal([]); + it('should return a dsp ID', function () { + expect(bid.meta.networkId).to.equal(bidResponse.seatbid[0].bid[0].ext.dsp_id); }); - it('should return a dsp ID', function () { - expect(bid.meta.dspid).to.equal(DEFAULT_TEST_ARJ_AD_UNIT.adv_id); + it('should return a buyer ID', function () { + expect(bid.meta.advertiserId).to.equal(bidResponse.seatbid[0].bid[0].ext.buyer_id); }); - }); - describe('when there is a deal', function () { - const adUnitOverride = { - deal_id: 'ox-1000' - }; - let adUnit; - let bidResponse; + it('should return adomain', function () { + expect(bid.meta.advertiserDomains).to.equal(bidResponse.seatbid[0].bid[0].adomain); + }); - let bid; - let bidRequestConfigs; - let bidRequest; + it('should return paf fields', function () { + const paf = { + transmission: {version: '12'}, + content_id: 'paf_content_id' + } + expect(bid.meta.paf).to.deep.equal(paf); + }); + }); + context('when there is more than one response', () => { + let bids; beforeEach(function () { - bidRequestConfigs = [{ - 'bidder': 'openx', - 'params': { - 'unit': '12345678', - 'delDomain': 'test-del-domain' - }, - 'adUnitCode': 'adunit-code', - 'mediaType': 'banner', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475' - }]; + bidRequestConfigs = deepClone(SAMPLE_BID_REQUESTS); + bidRequest = spec.buildRequests(bidRequestConfigs, {refererInfo: {}})[0]; + bidResponse = deepClone(SAMPLE_BID_RESPONSE); + bidResponse.seatbid[0].bid.push(deepClone(bidResponse.seatbid[0].bid[0])); + bidResponse.seatbid[0].bid[1].ext.paf.content_id = 'second_paf' - bidRequest = { - method: 'GET', - url: 'https://openx-d.openx.net/v/1.0/arj', - data: {}, - payload: {'bids': bidRequestConfigs, 'startTime': new Date()} - }; - adUnit = mockAdUnit(adUnitOverride); - bidResponse = mockArjResponse(null, [adUnit]); - bid = spec.interpretResponse({body: bidResponse}, bidRequest)[0]; - mockArjResponse(); + bids = spec.interpretResponse({body: bidResponse}, bidRequest); }); - it('should return a deal id', function () { - expect(bid.dealId).to.equal(adUnitOverride.deal_id); + it('should not confuse paf content_id', () => { + expect(bids.map(b => b.meta.paf.content_id)).to.eql(['paf_content_id', 'second_paf']); }); - }); - - describe('when there is no bids in the response', function () { - let bidRequest; - let bidRequestConfigs; + }) + context('when the response is a banner', function() { beforeEach(function () { bidRequestConfigs = [{ - 'bidder': 'openx', - 'params': { - 'unit': '12345678', - 'delDomain': 'test-del-domain' + bidder: 'openx', + params: { + unit: '12345678', + delDomain: 'test-del-domain' }, - 'adUnitCode': 'adunit-code', - 'mediaType': 'banner', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475' + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]], + }, + }, + bidId: 'test-bid-id', + bidderRequestId: 'test-bidder-request-id', + auctionId: 'test-auction-id' }]; - bidRequest = { - method: 'GET', - url: 'https://openx-d.openx.net/v/1.0/arj', - data: {}, - payload: {'bids': bidRequestConfigs, 'startTime': new Date()} - }; - }); - - it('handles nobid responses', function () { - const bidResponse = { - 'ads': - { - 'version': 1, - 'count': 1, - 'pixels': 'https://testpixels.net', - 'ad': [] - } + bidRequest = spec.buildRequests(bidRequestConfigs, {refererInfo: {}})[0]; + + bidResponse = { + seatbid: [{ + bid: [{ + impid: 'test-bid-id', + price: 2, + w: 300, + h: 250, + crid: 'test-creative-id', + dealid: 'test-deal-id', + adm: 'test-ad-markup' + }] + }], + cur: 'AUS' }; - const result = spec.interpretResponse({body: bidResponse}, bidRequest); - expect(result.length).to.equal(0); + bid = spec.interpretResponse({body: bidResponse}, bidRequest)[0]; }); - }); - describe('when adunits return out of order', function () { - const bidRequests = [{ - bidder: 'openx', - params: { - unit: '12345678', - delDomain: 'test-del-domain' - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[100, 111]] - } - }, - bidId: 'test-bid-request-id-1', - bidderRequestId: 'test-request-1', - auctionId: 'test-auction-id-1' - }, { - bidder: 'openx', - params: { - unit: '12345678', - delDomain: 'test-del-domain' - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[200, 222]] - } - }, - bidId: 'test-bid-request-id-2', - bidderRequestId: 'test-request-1', - auctionId: 'test-auction-id-1' - }, { - bidder: 'openx', - params: { - unit: '12345678', - delDomain: 'test-del-domain' - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[300, 333]] - } - }, - 'bidId': 'test-bid-request-id-3', - 'bidderRequestId': 'test-request-1', - 'auctionId': 'test-auction-id-1' - }]; - const bidRequest = { - method: 'GET', - url: 'https://openx-d.openx.net/v/1.0/arj', - data: {}, - payload: {'bids': bidRequests, 'startTime': new Date()} - }; - - let outOfOrderAdunits = [ - mockAdUnit({ - idx: '1' - }, { - width: bidRequests[1].mediaTypes.banner.sizes[0][0], - height: bidRequests[1].mediaTypes.banner.sizes[0][1] - }), - mockAdUnit({ - idx: '2' - }, { - width: bidRequests[2].mediaTypes.banner.sizes[0][0], - height: bidRequests[2].mediaTypes.banner.sizes[0][1] - }), - mockAdUnit({ - idx: '0' - }, { - width: bidRequests[0].mediaTypes.banner.sizes[0][0], - height: bidRequests[0].mediaTypes.banner.sizes[0][1] - }) - ]; - - let bidResponse = mockArjResponse(undefined, outOfOrderAdunits); - - it('should return map adunits back to the proper request', function () { - const bids = spec.interpretResponse({body: bidResponse}, bidRequest); - expect(bids[0].requestId).to.equal(bidRequests[1].bidId); - expect(bids[0].width).to.equal(bidRequests[1].mediaTypes.banner.sizes[0][0]); - expect(bids[0].height).to.equal(bidRequests[1].mediaTypes.banner.sizes[0][1]); - expect(bids[1].requestId).to.equal(bidRequests[2].bidId); - expect(bids[1].width).to.equal(bidRequests[2].mediaTypes.banner.sizes[0][0]); - expect(bids[1].height).to.equal(bidRequests[2].mediaTypes.banner.sizes[0][1]); - expect(bids[2].requestId).to.equal(bidRequests[0].bidId); - expect(bids[2].width).to.equal(bidRequests[0].mediaTypes.banner.sizes[0][0]); - expect(bids[2].height).to.equal(bidRequests[0].mediaTypes.banner.sizes[0][1]); + it('should return the proper mediaType', function () { + it('should return a creativeId', function () { + expect(bid.mediaType).to.equal(Object.keys(bidRequestConfigs[0].mediaTypes)[0]); + }); }); }); - }); - - describe('interpretResponse for video ads', function () { - beforeEach(function () { - sinon.spy(userSync, 'registerSync'); - }); - afterEach(function () { - userSync.registerSync.restore(); - }); - - const bidsWithMediaTypes = [{ - 'bidder': 'openx', - 'mediaTypes': {video: {}}, - 'params': { - 'unit': '12345678', - 'delDomain': 'test-del-domain' - }, - 'adUnitCode': 'adunit-code', - 'sizes': [640, 480], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - 'transactionId': '4008d88a-8137-410b-aa35-fbfdabcb478e' - }]; - const bidsWithMediaType = [{ - 'bidder': 'openx', - 'mediaType': 'video', - 'params': { - 'unit': '12345678', - 'delDomain': 'test-del-domain' - }, - 'adUnitCode': 'adunit-code', - 'sizes': [640, 480], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - 'transactionId': '4008d88a-8137-410b-aa35-fbfdabcb478e' - }]; - const bidRequestsWithMediaTypes = { - method: 'GET', - url: 'https://openx-d.openx.net/v/1.0/avjp', - data: {}, - payload: {'bid': bidsWithMediaTypes[0], 'startTime': new Date()} - }; - const bidRequestsWithMediaType = { - method: 'GET', - url: 'https://openx-d.openx.net/v/1.0/avjp', - data: {}, - payload: {'bid': bidsWithMediaType[0], 'startTime': new Date()} - }; - const bidResponse = { - 'pub_rev': '1000', - 'width': '640', - 'height': '480', - 'adid': '5678', - 'currency': 'AUD', - 'vastUrl': 'https://testvast.com', - 'pixels': 'https://testpixels.net' - }; + if (FEATURES.VIDEO) { + context('when the response is a video', function() { + beforeEach(function () { + bidRequestConfigs = [{ + bidder: 'openx', + params: { + unit: '12345678', + delDomain: 'test-del-domain' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + video: { + playerSize: [[640, 360], [854, 480]], + }, + }, + bidId: 'test-bid-id', + bidderRequestId: 'test-bidder-request-id', + auctionId: 'test-auction-id' + }]; + + bidRequest = spec.buildRequests(bidRequestConfigs, {refererInfo: {}})[0]; + + bidResponse = { + seatbid: [{ + bid: [{ + impid: 'test-bid-id', + price: 2, + w: 854, + h: 480, + crid: 'test-creative-id', + dealid: 'test-deal-id', + adm: 'test-ad-markup', + }] + }], + cur: 'AUS' + }; + }); - it('should return correct bid response with MediaTypes', function () { - const expectedResponse = { - 'requestId': '30b31c1838de1e', - 'cpm': 1, - 'width': 640, - 'height': 480, - 'mediaType': 'video', - 'creativeId': '5678', - 'vastUrl': 'https://testvast.com', - 'ttl': 300, - 'netRevenue': true, - 'currency': 'AUD' - }; - - const result = spec.interpretResponse({body: bidResponse}, bidRequestsWithMediaTypes); - expect(result[0]).to.eql(expectedResponse); - }); + it('should return the proper mediaType', function () { + bid = spec.interpretResponse({body: bidResponse}, bidRequest)[0]; + expect(bid.mediaType).to.equal(Object.keys(bidRequestConfigs[0].mediaTypes)[0]); + }); - it('should return correct bid response with MediaType', function () { - const expectedResponse = [ - { - 'requestId': '30b31c1838de1e', - 'cpm': 1, - 'width': '640', - 'height': '480', - 'mediaType': 'video', - 'creativeId': '5678', - 'vastUrl': 'https://testvast.com', - 'ttl': 300, - 'netRevenue': true, - 'currency': 'USD' - } - ]; + it('should return the proper mediaType', function () { + const winUrl = 'https//my.win.url'; + bidResponse.seatbid[0].bid[0].nurl = winUrl + bid = spec.interpretResponse({body: bidResponse}, bidRequest)[0]; - const result = spec.interpretResponse({body: bidResponse}, bidRequestsWithMediaType); - expect(JSON.stringify(Object.keys(result[0]).sort())).to.eql(JSON.stringify(Object.keys(expectedResponse[0]).sort())); - }); + expect(bid.vastUrl).to.equal(winUrl); + }); + }); + } - it('should return correct bid response with MediaType and deal_id', function () { - const bidResponseOverride = { 'deal_id': 'OX-mydeal' }; - const bidResponseWithDealId = Object.assign({}, bidResponse, bidResponseOverride); - const result = spec.interpretResponse({body: bidResponseWithDealId}, bidRequestsWithMediaType); - expect(result[0].dealId).to.equal(bidResponseOverride.deal_id); - }); + context('when the response contains FLEDGE interest groups config', function() { + let response; - it('should handle nobid responses for bidRequests with MediaTypes', function () { - const bidResponse = {'vastUrl': '', 'pub_rev': '', 'width': '', 'height': '', 'adid': '', 'pixels': ''}; - const result = spec.interpretResponse({body: bidResponse}, bidRequestsWithMediaTypes); - expect(result.length).to.equal(0); - }); + beforeEach(function () { + sinon.stub(config, 'getConfig') + .withArgs('fledgeEnabled') + .returns(true); - it('should handle nobid responses for bidRequests with MediaType', function () { - const bidResponse = {'vastUrl': '', 'pub_rev': '', 'width': '', 'height': '', 'adid': '', 'pixels': ''}; - const result = spec.interpretResponse({body: bidResponse}, bidRequestsWithMediaType); - expect(result.length).to.equal(0); - }); - }); + bidRequestConfigs = [{ + bidder: 'openx', + params: { + unit: '12345678', + delDomain: 'test-del-domain' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]], + }, + }, + bidId: 'test-bid-id', + bidderRequestId: 'test-bidder-request-id', + auctionId: 'test-auction-id' + }]; - describe('user sync', function () { - const syncUrl = 'https://testpixels.net'; + bidRequest = spec.buildRequests(bidRequestConfigs, {refererInfo: {}})[0]; + + bidResponse = { + seatbid: [{ + bid: [{ + impid: 'test-bid-id', + price: 2, + w: 300, + h: 250, + crid: 'test-creative-id', + dealid: 'test-deal-id', + adm: 'test-ad-markup' + }] + }], + cur: 'AUS', + ext: { + fledge_auction_configs: { + 'test-bid-id': { + seller: 'codinginadtech.com', + interestGroupBuyers: ['somedomain.com'], + sellerTimeout: 0, + perBuyerSignals: { + 'somedomain.com': { + base_bid_micros: 0.1, + disallowed_advertiser_ids: [ + '1234', + '2345' + ], + multiplier: 1.3, + use_bid_multiplier: true, + win_reporting_id: '1234567asdf' + } + } + } + } + } + }; - describe('iframe sync', function () { - it('should register the pixel iframe from banner ad response', function () { - let syncs = spec.getUserSyncs( - {iframeEnabled: true}, - [{body: {ads: {pixels: syncUrl}}}] - ); - expect(syncs).to.deep.equal([{type: 'iframe', url: syncUrl}]); + response = spec.interpretResponse({body: bidResponse}, bidRequest); }); - it('should register the pixel iframe from video ad response', function () { - let syncs = spec.getUserSyncs( - {iframeEnabled: true}, - [{body: {pixels: syncUrl}}] - ); - expect(syncs).to.deep.equal([{type: 'iframe', url: syncUrl}]); + afterEach(function () { + config.getConfig.restore(); }); - it('should register the default iframe if no pixels available', function () { - let syncs = spec.getUserSyncs( - {iframeEnabled: true}, - [] - ); - expect(syncs).to.deep.equal([{type: 'iframe', url: 'https://u.openx.net/w/1.0/pd'}]); + it('should return FLEDGE auction_configs alongside bids', function () { + expect(response).to.have.property('bids'); + expect(response).to.have.property('fledgeAuctionConfigs'); + expect(response.fledgeAuctionConfigs.length).to.equal(1); + expect(response.fledgeAuctionConfigs[0].bidId).to.equal('test-bid-id'); }); }); + }); - describe('pixel sync', function () { - it('should register the image pixel from banner ad response', function () { - let syncs = spec.getUserSyncs( - {pixelEnabled: true}, - [{body: {ads: {pixels: syncUrl}}}] - ); - expect(syncs).to.deep.equal([{type: 'image', url: syncUrl}]); - }); + describe('user sync', function () { + it('should register the default image pixel if no pixels available', function () { + let syncs = spec.getUserSyncs( + {pixelEnabled: true}, + [] + ); + expect(syncs).to.deep.equal([{type: 'image', url: DEFAULT_SYNC}]); + }); - it('should register the image pixel from video ad response', function () { - let syncs = spec.getUserSyncs( - {pixelEnabled: true}, - [{body: {pixels: syncUrl}}] - ); - expect(syncs).to.deep.equal([{type: 'image', url: syncUrl}]); - }); + it('should register custom syncUrl when exists', function () { + let syncs = spec.getUserSyncs( + {pixelEnabled: true}, + [{body: {ext: {delDomain: 'www.url.com'}}}] + ); + expect(syncs).to.deep.equal([{type: 'image', url: 'https://www.url.com/w/1.0/pd'}]); + }); - it('should register the default image pixel if no pixels available', function () { - let syncs = spec.getUserSyncs( - {pixelEnabled: true}, - [] - ); - expect(syncs).to.deep.equal([{type: 'image', url: 'https://u.openx.net/w/1.0/pd'}]); - }); + it('should register custom syncUrl when exists', function () { + let syncs = spec.getUserSyncs( + {pixelEnabled: true}, + [{body: {ext: {platform: 'abc'}}}] + ); + expect(syncs).to.deep.equal([{type: 'image', url: SYNC_URL + '?ph=abc'}]); + }); + + it('when iframe sync is allowed, it should register an iframe sync', function () { + let syncs = spec.getUserSyncs( + {iframeEnabled: true}, + [] + ); + expect(syncs).to.deep.equal([{type: 'iframe', url: DEFAULT_SYNC}]); }); it('should prioritize iframe over image for user sync', function () { let syncs = spec.getUserSyncs( {iframeEnabled: true, pixelEnabled: true}, - [{body: {ads: {pixels: syncUrl}}}] + [] ); - expect(syncs).to.deep.equal([{type: 'iframe', url: syncUrl}]); + expect(syncs).to.deep.equal([{type: 'iframe', url: DEFAULT_SYNC}]); }); describe('when gdpr applies', function () { let gdprConsent; let gdprPixelUrl; + const consentString = 'gdpr-pixel-consent'; + const gdprApplies = '1'; beforeEach(() => { gdprConsent = { - consentString: 'test-gdpr-consent-string', + consentString, gdprApplies: true }; - gdprPixelUrl = 'https://testpixels.net?gdpr=1&gdpr_consent=gdpr-pixel-consent' + gdprPixelUrl = `${SYNC_URL}&gdpr=${gdprApplies}&gdpr_consent=${consentString}`; }); it('when there is a response, it should have the gdpr query params', () => { - let [{url}] = spec.getUserSyncs( - {iframeEnabled: true, pixelEnabled: true}, - [{body: {ads: {pixels: gdprPixelUrl}}}], - gdprConsent - ); - - expect(url).to.have.string('gdpr_consent=gdpr-pixel-consent'); - expect(url).to.have.string('gdpr=1'); - }); - - it('when there is no response, it should append gdpr query params', () => { let [{url}] = spec.getUserSyncs( {iframeEnabled: true, pixelEnabled: true}, [], gdprConsent ); - expect(url).to.have.string('gdpr_consent=test-gdpr-consent-string'); - expect(url).to.have.string('gdpr=1'); + + expect(url).to.have.string(`gdpr_consent=${consentString}`); + expect(url).to.have.string(`gdpr=${gdprApplies}`); }); it('should not send signals if no consent object is available', function () { @@ -2287,28 +1588,19 @@ describe('OpenxAdapter', function () { describe('when ccpa applies', function () { let usPrivacyConsent; let uspPixelUrl; + const privacyString = 'TEST'; beforeEach(() => { usPrivacyConsent = 'TEST'; - uspPixelUrl = 'https://testpixels.net?us_privacy=AAAA' + uspPixelUrl = `${DEFAULT_SYNC}&us_privacy=${privacyString}` }); - it('when there is a response, it should send the us privacy string from the response, ', () => { - let [{url}] = spec.getUserSyncs( - {iframeEnabled: true, pixelEnabled: true}, - [{body: {ads: {pixels: uspPixelUrl}}}], - undefined, - usPrivacyConsent - ); - - expect(url).to.have.string('us_privacy=AAAA'); - }); - it('when there is no response, it send have the us privacy string', () => { + it('should send the us privacy string, ', () => { let [{url}] = spec.getUserSyncs( {iframeEnabled: true, pixelEnabled: true}, [], undefined, usPrivacyConsent ); - expect(url).to.have.string(`us_privacy=${usPrivacyConsent}`); + expect(url).to.have.string(`us_privacy=${privacyString}`); }); it('should not send signals if no consent string is available', function () { @@ -2320,75 +1612,4 @@ describe('OpenxAdapter', function () { }); }); }); - - /** - * Makes sure the override object does not introduce - * new fields against the contract - * - * This does a shallow check in order to make key checking simple - * with respect to what a helper handles. For helpers that have - * nested fields, either check your design on maybe breaking it up - * to smaller, manageable pieces - * - * OR just call this on your nth level field if necessary. - * - * @param {Object} override Object with keys that overrides the default - * @param {Object} contract Original object contains the default fields - * @param {string} typeName Name of the type we're checking for error messages - * @throws {AssertionError} - */ - function overrideKeyCheck(override, contract, typeName) { - expect(contract).to.include.all.keys(Object.keys(override)); - } - - /** - * Creates a mock ArjResponse - * @param {OxArjResponse=} response - * @param {Array=} adUnits - * @throws {AssertionError} - * @return {OxArjResponse} - */ - function mockArjResponse(response, adUnits = []) { - let mockedArjResponse = utils.deepClone(DEFAULT_ARJ_RESPONSE); - - if (response) { - overrideKeyCheck(response, DEFAULT_ARJ_RESPONSE, 'OxArjResponse'); - overrideKeyCheck(response.ads, DEFAULT_ARJ_RESPONSE.ads, 'OxArjResponse'); - Object.assign(mockedArjResponse, response); - } - - if (adUnits.length) { - mockedArjResponse.ads.count = adUnits.length; - mockedArjResponse.ads.ad = adUnits.map((adUnit) => { - overrideKeyCheck(adUnit, DEFAULT_TEST_ARJ_AD_UNIT, 'OxArjAdUnit'); - return Object.assign(utils.deepClone(DEFAULT_TEST_ARJ_AD_UNIT), adUnit); - }); - } - - return mockedArjResponse; - } - - /** - * Creates a mock ArjAdUnit - * @param {OxArjAdUnit=} adUnit - * @param {OxArjCreative=} creative - * @throws {AssertionError} - * @return {OxArjAdUnit} - */ - function mockAdUnit(adUnit, creative) { - overrideKeyCheck(adUnit, DEFAULT_TEST_ARJ_AD_UNIT, 'OxArjAdUnit'); - - let mockedAdUnit = Object.assign(utils.deepClone(DEFAULT_TEST_ARJ_AD_UNIT), adUnit); - - if (creative) { - overrideKeyCheck(creative, DEFAULT_TEST_ARJ_CREATIVE); - if (creative.tracking) { - overrideKeyCheck(creative.tracking, DEFAULT_TEST_ARJ_CREATIVE.tracking, 'OxArjCreative'); - } - Object.assign(mockedAdUnit.creative[0], creative); - } - - return mockedAdUnit; - } -}) -; +}); diff --git a/test/spec/modules/openxOrtbBidAdapter_spec.js b/test/spec/modules/openxOrtbBidAdapter_spec.js deleted file mode 100644 index 3bb1958c5fe..00000000000 --- a/test/spec/modules/openxOrtbBidAdapter_spec.js +++ /dev/null @@ -1,1553 +0,0 @@ -import {expect} from 'chai'; -import {spec, REQUEST_URL, SYNC_URL, DEFAULT_PH} from 'modules/openxOrtbBidAdapter.js'; -import {newBidder} from 'src/adapters/bidderFactory.js'; -import {BANNER, VIDEO} from 'src/mediaTypes.js'; -import {config} from 'src/config.js'; -import * as utils from 'src/utils.js'; -// load modules that register ORTB processors -import 'src/prebid.js' -import 'modules/currency.js'; -import 'modules/userId/index.js'; -import 'modules/multibid/index.js'; -import 'modules/priceFloors.js'; -import 'modules/consentManagement.js'; -import 'modules/consentManagementUsp.js'; -import 'modules/schain.js'; -import {deepClone} from 'src/utils.js'; -import {syncAddFPDToBidderRequest} from '../../helpers/fpd.js'; -import {hook} from '../../../src/hook.js'; - -const DEFAULT_SYNC = SYNC_URL + '?ph=' + DEFAULT_PH; - -describe('OpenxRtbAdapter', function () { - before(() => { - hook.ready(); - }); - - const adapter = newBidder(spec); - - describe('inherited functions', function () { - it('exists and is a function', function () { - expect(adapter.callBids).to.exist.and.to.be.a('function'); - }); - }); - - describe('isBidRequestValid()', function () { - describe('when request is for a banner ad', function () { - let bannerBid; - beforeEach(function () { - bannerBid = { - bidder: 'openx', - params: {}, - adUnitCode: 'adunit-code', - mediaTypes: {banner: {}}, - sizes: [[300, 250], [300, 600]], - bidId: '30b31c1838de1e', - bidderRequestId: '22edbae2733bf6', - auctionId: '1d1a030790a475' - }; - }); - - it('should return false when there is no delivery domain', function () { - bannerBid.params = {'unit': '12345678'}; - expect(spec.isBidRequestValid(bannerBid)).to.equal(false); - }); - - describe('when there is a delivery domain', function () { - beforeEach(function () { - bannerBid.params = {delDomain: 'test-delivery-domain'} - }); - - it('should return false when there is no ad unit id and size', function () { - expect(spec.isBidRequestValid(bannerBid)).to.equal(false); - }); - - it('should return true if there is an adunit id ', function () { - bannerBid.params.unit = '12345678'; - expect(spec.isBidRequestValid(bannerBid)).to.equal(true); - }); - - it('should return true if there is no adunit id and sizes are defined', function () { - bannerBid.mediaTypes.banner.sizes = [720, 90]; - expect(spec.isBidRequestValid(bannerBid)).to.equal(true); - }); - - it('should return false if no sizes are defined ', function () { - expect(spec.isBidRequestValid(bannerBid)).to.equal(false); - }); - - it('should return false if sizes empty ', function () { - bannerBid.mediaTypes.banner.sizes = []; - expect(spec.isBidRequestValid(bannerBid)).to.equal(false); - }); - }); - }); - - describe('when request is for a multiformat ad', function () { - describe('and request config uses mediaTypes video and banner', () => { - const multiformatBid = { - bidder: 'openx', - params: { - unit: '12345678', - delDomain: 'test-del-domain' - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[300, 250]] - }, - video: { - playerSize: [300, 250] - } - }, - bidId: '30b31c1838de1e', - bidderRequestId: '22edbae2733bf6', - auctionId: '1d1a030790a475', - transactionId: '4008d88a-8137-410b-aa35-fbfdabcb478e' - }; - it('should return true multisize when required params found', function () { - expect(spec.isBidRequestValid(multiformatBid)).to.equal(true); - }); - }); - }); - - describe('when request is for a video ad', function () { - describe('and request config uses mediaTypes', () => { - const videoBidWithMediaTypes = { - bidder: 'openx', - params: { - unit: '12345678', - delDomain: 'test-del-domain' - }, - adUnitCode: 'adunit-code', - mediaTypes: { - video: { - playerSize: [640, 480] - } - }, - bidId: '30b31c1838de1e', - bidderRequestId: '22edbae2733bf6', - auctionId: '1d1a030790a475', - transactionId: '4008d88a-8137-410b-aa35-fbfdabcb478e' - }; - it('should return true when required params found', function () { - expect(spec.isBidRequestValid(videoBidWithMediaTypes)).to.equal(true); - }); - - it('should return false when required params are not passed', function () { - let videoBidWithMediaTypes = Object.assign({}, videoBidWithMediaTypes); - videoBidWithMediaTypes.params = {}; - expect(spec.isBidRequestValid(videoBidWithMediaTypes)).to.equal(false); - }); - }); - describe('and request config uses both delDomain and platform', () => { - const videoBidWithDelDomainAndPlatform = { - bidder: 'openx', - params: { - unit: '12345678', - delDomain: 'test-del-domain', - platform: '1cabba9e-cafe-3665-beef-f00f00f00f00' - }, - adUnitCode: 'adunit-code', - mediaTypes: { - video: { - playerSize: [640, 480] - } - }, - bidId: '30b31c1838de1e', - bidderRequestId: '22edbae2733bf6', - auctionId: '1d1a030790a475', - transactionId: '4008d88a-8137-410b-aa35-fbfdabcb478e' - }; - it('should return true when required params found', function () { - expect(spec.isBidRequestValid(videoBidWithDelDomainAndPlatform)).to.equal(true); - }); - - it('should return false when required params are not passed', function () { - let videoBidWithMediaTypes = Object.assign({}, videoBidWithDelDomainAndPlatform); - videoBidWithMediaTypes.params = {}; - expect(spec.isBidRequestValid(videoBidWithMediaTypes)).to.equal(false); - }); - }); - describe('and request config uses mediaType', () => { - const videoBidWithMediaType = { - 'bidder': 'openx', - 'params': { - 'unit': '12345678', - 'delDomain': 'test-del-domain' - }, - 'adUnitCode': 'adunit-code', - 'mediaType': 'video', - 'sizes': [640, 480], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - 'transactionId': '4008d88a-8137-410b-aa35-fbfdabcb478e' - }; - it('should return true when required params found', function () { - expect(spec.isBidRequestValid(videoBidWithMediaType)).to.equal(true); - }); - - it('should return false when required params are not passed', function () { - let videoBidWithMediaType = Object.assign({}, videoBidWithMediaType); - delete videoBidWithMediaType.params; - videoBidWithMediaType.params = {}; - expect(spec.isBidRequestValid(videoBidWithMediaType)).to.equal(false); - }); - }); - }); - }); - - describe('buildRequests()', function () { - let bidRequestsWithMediaTypes; - let bidRequestsWithPlatform; - let mockBidderRequest; - - beforeEach(function () { - mockBidderRequest = {refererInfo: {}}; - - bidRequestsWithMediaTypes = [{ - bidder: 'openx', - params: { - unit: '11', - delDomain: 'test-del-domain', - platform: '1cabba9e-cafe-3665-beef-f00f00f00f00', - }, - adUnitCode: '/adunit-code/test-path', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]] - } - }, - bidId: 'test-bid-id-1', - bidderRequestId: 'test-bid-request-1', - auctionId: 'test-auction-1', - transactionId: 'test-transactionId-1', - ortb2Imp: { - ext: { - ae: 2 - } - } - }, { - bidder: 'openx', - params: { - unit: '22', - delDomain: 'test-del-domain', - platform: '1cabba9e-cafe-3665-beef-f00f00f00f00', - }, - adUnitCode: 'adunit-code', - mediaTypes: { - video: { - playerSize: [640, 480] - } - }, - bidId: 'test-bid-id-2', - bidderRequestId: 'test-bid-request-2', - auctionId: 'test-auction-2', - transactionId: 'test-transactionId-2' - }]; - }); - - context('common requests checks', function() { - it('should send bid request to openx url via POST', function () { - const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); - expect(request[0].url).to.equal(REQUEST_URL); - expect(request[0].method).to.equal('POST'); - }); - - it('should send delivery domain, if available', function () { - const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); - expect(request[0].data.ext.delDomain).to.equal(bidRequestsWithMediaTypes[0].params.delDomain); - expect(request[0].data.ext.platformId).to.be.undefined; - }); - - it('should send platform id, if available', function () { - bidRequestsWithMediaTypes[0].params.platform = '1cabba9e-cafe-3665-beef-f00f00f00f00'; - bidRequestsWithMediaTypes[1].params.platform = '1cabba9e-cafe-3665-beef-f00f00f00f00'; - - const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); - expect(request[0].data.ext.platform).to.equal(bidRequestsWithMediaTypes[0].params.platform); - expect(request[1].data.ext.platform).to.equal(bidRequestsWithMediaTypes[0].params.platform); - }); - - it('should send openx adunit codes', function () { - const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); - expect(request[0].data.imp[0].tagid).to.equal(bidRequestsWithMediaTypes[0].params.unit); - expect(request[1].data.imp[0].tagid).to.equal(bidRequestsWithMediaTypes[1].params.unit); - }); - - it('should send out custom params on bids that have customParams specified', function () { - const bidRequest = Object.assign({}, - bidRequestsWithMediaTypes[0], - { - params: { - unit: '12345678', - delDomain: 'test-del-domain', - customParams: {'Test1': 'testval1+', 'test2': ['testval2/', 'testval3']} - } - } - ); - - mockBidderRequest.bids = [bidRequest]; - const request = spec.buildRequests([bidRequest], mockBidderRequest); - expect(request[0].data.imp[0].ext.customParams).to.equal(bidRequest.params.customParams); - }) - - describe('floors', function () { - it('should send out custom floors on bids that have customFloors, no currency as account currency is used', function () { - const bidRequest = Object.assign({}, - bidRequestsWithMediaTypes[0], - { - params: { - unit: '12345678', - delDomain: 'test-del-domain', - customFloor: 1.500 - } - } - ); - - const request = spec.buildRequests([bidRequest], mockBidderRequest); - expect(request[0].data.imp[0].bidfloor).to.equal(bidRequest.params.customFloor); - expect(request[0].data.imp[0].bidfloorcur).to.equal(undefined); - }); - - context('with floors module', function () { - let adServerCurrencyStub; - - beforeEach(function () { - adServerCurrencyStub = sinon - .stub(config, 'getConfig') - .withArgs('currency.adServerCurrency') - }); - - afterEach(function () { - config.getConfig.restore(); - }); - - it('should send out floors on bids in USD', function () { - const bidRequest = Object.assign({}, - bidRequestsWithMediaTypes[0], - { - getFloor: () => { - return { - currency: 'USD', - floor: 9.99 - } - } - } - ); - - const request = spec.buildRequests([bidRequest], mockBidderRequest); - expect(request[0].data.imp[0].bidfloor).to.equal(9.99); - expect(request[0].data.imp[0].bidfloorcur).to.equal('USD'); - }); - - it('should send not send floors', function () { - adServerCurrencyStub.returns('EUR'); - const bidRequest = Object.assign({}, - bidRequestsWithMediaTypes[0], - { - getFloor: () => { - return { - currency: 'BTC', - floor: 9.99 - } - } - } - ); - - const request = spec.buildRequests([bidRequest], mockBidderRequest); - expect(request[0].data.imp[0].bidfloor).to.equal(undefined) - expect(request[0].data.imp[0].bidfloorcur).to.equal(undefined) - }); - }) - }) - - describe('FPD', function() { - let bidRequests; - const mockBidderRequest = {refererInfo: {}}; - - beforeEach(function () { - bidRequests = [{ - bidder: 'openx', - params: { - unit: '12345678-banner', - delDomain: 'test-del-domain' - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]] - } - }, - bidId: 'test-bid-id', - bidderRequestId: 'test-bidder-request-id', - auctionId: 'test-auction-id', - transactionId: 'test-transaction-id-1' - }, { - bidder: 'openx', - mediaTypes: { - video: { - playerSize: [640, 480] - } - }, - params: { - unit: '12345678-video', - delDomain: 'test-del-domain' - }, - 'adUnitCode': 'adunit-code', - - bidId: 'test-bid-id', - bidderRequestId: 'test-bidder-request-id', - auctionId: 'test-auction-id', - transactionId: 'test-transaction-id-2' - }]; - }); - - it('ortb2.site should be merged in the request', function() { - const request = spec.buildRequests(bidRequests, { - ...mockBidderRequest, - 'ortb2': { - site: { - domain: 'page.example.com', - cat: ['IAB2'], - sectioncat: ['IAB2-2'] - } - } - }); - let data = request[0].data; - expect(data.site.domain).to.equal('page.example.com'); - expect(data.site.cat).to.deep.equal(['IAB2']); - expect(data.site.sectioncat).to.deep.equal(['IAB2-2']); - }); - - it('ortb2.user should be merged in the request', function() { - const request = spec.buildRequests(bidRequests, { - ...mockBidderRequest, - 'ortb2': { - user: { - yob: 1985 - } - } - }); - let data = request[0].data; - expect(data.user.yob).to.equal(1985); - }); - - describe('ortb2Imp', function() { - describe('ortb2Imp.ext.data.pbadslot', function() { - beforeEach(function () { - if (bidRequests[0].hasOwnProperty('ortb2Imp')) { - delete bidRequests[0].ortb2Imp; - } - }); - - it('should not send if imp[].ext.data object is invalid', function() { - bidRequests[0].ortb2Imp = { - ext: {} - }; - const request = spec.buildRequests(bidRequests, mockBidderRequest); - let data = request[0].data; - expect(data.imp[0].ext).to.not.have.property('data'); - }); - - it('should not send if imp[].ext.data.pbadslot is undefined', function() { - bidRequests[0].ortb2Imp = { - ext: { - data: { - } - } - }; - const request = spec.buildRequests(bidRequests, mockBidderRequest); - let data = request[0].data; - if (data.imp[0].ext.data) { - expect(data.imp[0].ext.data).to.not.have.property('pbadslot'); - } else { - expect(data.imp[0].ext).to.not.have.property('data'); - } - }); - - it('should send if imp[].ext.data.pbadslot is string', function() { - bidRequests[0].ortb2Imp = { - ext: { - data: { - pbadslot: 'abcd' - } - } - }; - const request = spec.buildRequests(bidRequests, mockBidderRequest); - let data = request[0].data; - expect(data.imp[0].ext.data).to.have.property('pbadslot'); - expect(data.imp[0].ext.data.pbadslot).to.equal('abcd'); - }); - }); - - describe('ortb2Imp.ext.data.adserver', function() { - beforeEach(function () { - if (bidRequests[0].hasOwnProperty('ortb2Imp')) { - delete bidRequests[0].ortb2Imp; - } - }); - - it('should not send if imp[].ext.data object is invalid', function() { - bidRequests[0].ortb2Imp = { - ext: {} - }; - const request = spec.buildRequests(bidRequests, mockBidderRequest); - let data = request[0].data; - expect(data.imp[0].ext).to.not.have.property('data'); - }); - - it('should not send if imp[].ext.data.adserver is undefined', function() { - bidRequests[0].ortb2Imp = { - ext: { - data: { - } - } - }; - const request = spec.buildRequests(bidRequests, mockBidderRequest); - let data = request[0].data; - if (data.imp[0].ext.data) { - expect(data.imp[0].ext.data).to.not.have.property('adserver'); - } else { - expect(data.imp[0].ext).to.not.have.property('data'); - } - }); - - it('should send', function() { - let adSlotValue = 'abc'; - bidRequests[0].ortb2Imp = { - ext: { - data: { - adserver: { - name: 'GAM', - adslot: adSlotValue - } - } - } - }; - const request = spec.buildRequests(bidRequests, mockBidderRequest); - let data = request[0].data; - expect(data.imp[0].ext.data.adserver.name).to.equal('GAM'); - expect(data.imp[0].ext.data.adserver.adslot).to.equal(adSlotValue); - }); - }); - - describe('ortb2Imp.ext.data.other', function() { - beforeEach(function () { - if (bidRequests[0].hasOwnProperty('ortb2Imp')) { - delete bidRequests[0].ortb2Imp; - } - }); - - it('should not send if imp[].ext.data object is invalid', function() { - bidRequests[0].ortb2Imp = { - ext: {} - }; - const request = spec.buildRequests(bidRequests, mockBidderRequest); - let data = request[0].data; - expect(data.imp[0].ext).to.not.have.property('data'); - }); - - it('should not send if imp[].ext.data.other is undefined', function() { - bidRequests[0].ortb2Imp = { - ext: { - data: { - } - } - }; - const request = spec.buildRequests(bidRequests, mockBidderRequest); - let data = request[0].data; - if (data.imp[0].ext.data) { - expect(data.imp[0].ext.data).to.not.have.property('other'); - } else { - expect(data.imp[0].ext).to.not.have.property('data'); - } - }); - - it('ortb2Imp.ext.data.other', function() { - bidRequests[0].ortb2Imp = { - ext: { - data: { - other: 1234 - } - } - }; - const request = spec.buildRequests(bidRequests, mockBidderRequest); - let data = request[0].data; - expect(data.imp[0].ext.data.other).to.equal(1234); - }); - }); - }); - - describe('with user agent client hints', function () { - it('should add device.sua if available', function () { - const bidderRequestWithUserAgentClientHints = { refererInfo: {}, - ortb2: { - device: { - sua: { - source: 2, - platform: { - brand: 'macOS', - version: [ '12', '4', '0' ] - }, - browsers: [ - { - brand: 'Chromium', - version: [ '106', '0', '5249', '119' ] - }, - { - brand: 'Google Chrome', - version: [ '106', '0', '5249', '119' ] - }, - { - brand: 'Not;A=Brand', - version: [ '99', '0', '0', '0' ] - }], - mobile: 0, - model: 'Pro', - bitness: '64', - architecture: 'x86' - } - } - }}; - - let request = spec.buildRequests(bidRequests, bidderRequestWithUserAgentClientHints); - expect(request[0].data.device.sua).to.exist; - expect(request[0].data.device.sua).to.deep.equal(bidderRequestWithUserAgentClientHints.ortb2.device.sua); - const bidderRequestWithoutUserAgentClientHints = {refererInfo: {}, ortb2: {}}; - request = spec.buildRequests(bidRequests, bidderRequestWithoutUserAgentClientHints); - expect(request[0].data.device?.sua).to.not.exist; - }); - }); - }); - - context('when there is a consent management framework', function () { - let bidRequests; - let mockConfig; - let bidderRequest; - - beforeEach(function () { - bidRequests = [{ - bidder: 'openx', - params: { - unit: '12345678-banner', - delDomain: 'test-del-domain' - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]] - } - }, - bidId: 'test-bid-id', - bidderRequestId: 'test-bidder-request-id', - auctionId: 'test-auction-id', - transactionId: 'test-transaction-id-1' - }, { - bidder: 'openx', - mediaTypes: { - video: { - playerSize: [640, 480] - } - }, - params: { - unit: '12345678-video', - delDomain: 'test-del-domain' - }, - 'adUnitCode': 'adunit-code', - - bidId: 'test-bid-id', - bidderRequestId: 'test-bidder-request-id', - auctionId: 'test-auction-id', - transactionId: 'test-transaction-id-2' - }]; - }); - - describe('us_privacy', function () { - beforeEach(function () { - bidderRequest = { - uspConsent: '1YYN', - refererInfo: {} - }; - - sinon.stub(config, 'getConfig').callsFake((key) => { - return utils.deepAccess(mockConfig, key); - }); - }); - - afterEach(function () { - config.getConfig.restore(); - }); - - it('should send a signal to specify that US Privacy applies to this request', function () { - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); - expect(request[0].data.regs.ext.us_privacy).to.equal('1YYN'); - expect(request[1].data.regs.ext.us_privacy).to.equal('1YYN'); - }); - - it('should not send the regs object, when consent string is undefined', function () { - delete bidderRequest.uspConsent; - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); - expect(request[0].data.regs?.us_privacy).to.not.exist; - }); - }); - - describe('GDPR', function () { - beforeEach(function () { - bidderRequest = { - gdprConsent: { - consentString: 'test-gdpr-consent-string', - addtlConsent: 'test-addtl-consent-string', - gdprApplies: true - }, - refererInfo: {} - }; - - mockConfig = { - consentManagement: { - cmpApi: 'iab', - timeout: 1111, - allowAuctionWithoutConsent: 'cancel' - } - }; - - sinon.stub(config, 'getConfig').callsFake((key) => { - return utils.deepAccess(mockConfig, key); - }); - }); - - afterEach(function () { - config.getConfig.restore(); - }); - - it('should send a signal to specify that GDPR applies to this request', function () { - bidderRequest.bids = bidRequests; - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); - expect(request[0].data.regs.ext.gdpr).to.equal(1); - expect(request[1].data.regs.ext.gdpr).to.equal(1); - }); - - it('should send the consent string', function () { - bidderRequest.bids = bidRequests; - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); - expect(request[0].data.user.ext.consent).to.equal(bidderRequest.gdprConsent.consentString); - expect(request[1].data.user.ext.consent).to.equal(bidderRequest.gdprConsent.consentString); - }); - - it('should send the addtlConsent string', function () { - bidderRequest.bids = bidRequests; - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); - expect(request[0].data.user.ext.ConsentedProvidersSettings.consented_providers).to.equal(bidderRequest.gdprConsent.addtlConsent); - expect(request[1].data.user.ext.ConsentedProvidersSettings.consented_providers).to.equal(bidderRequest.gdprConsent.addtlConsent); - }); - - it('should send a signal to specify that GDPR does not apply to this request', function () { - bidderRequest.gdprConsent.gdprApplies = false; - bidderRequest.bids = bidRequests; - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); - expect(request[0].data.regs.ext.gdpr).to.equal(0); - expect(request[1].data.regs.ext.gdpr).to.equal(0); - }); - - it('when GDPR application is undefined, should not send a signal to specify whether GDPR applies to this request, ' + - 'but can send consent data, ', function () { - delete bidderRequest.gdprConsent.gdprApplies; - bidderRequest.bids = bidRequests; - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); - expect(request[0].data.regs?.ext?.gdpr).to.not.be.ok; - expect(request[0].data.user.ext.consent).to.equal(bidderRequest.gdprConsent.consentString); - expect(request[1].data.user.ext.consent).to.equal(bidderRequest.gdprConsent.consentString); - }); - - it('when consent string is undefined, should not send the consent string, ', function () { - delete bidderRequest.gdprConsent.consentString; - bidderRequest.bids = bidRequests; - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); - expect(request[0].data.imp[0].ext.consent).to.equal(undefined); - expect(request[1].data.imp[0].ext.consent).to.equal(undefined); - }); - }); - }); - - context('coppa', function() { - it('when there are no coppa param settings, should not send a coppa flag', function () { - const request = spec.buildRequests(bidRequestsWithMediaTypes, syncAddFPDToBidderRequest(mockBidderRequest)); - expect(request[0].data.regs?.coppa).to.be.not.ok; - }); - - it('should send a coppa flag there is when there is coppa param settings in the bid requests', function () { - let mockConfig = { - coppa: true - }; - - sinon.stub(config, 'getConfig').callsFake((key) => { - return utils.deepAccess(mockConfig, key); - }); - - const request = spec.buildRequests(bidRequestsWithMediaTypes, syncAddFPDToBidderRequest(mockBidderRequest)); - expect(request[0].data.regs.coppa).to.equal(1); - }); - - after(function () { - config.getConfig.restore() - }); - }); - - context('do not track (DNT)', function() { - let doNotTrackStub; - - beforeEach(function () { - doNotTrackStub = sinon.stub(utils, 'getDNT'); - }); - afterEach(function() { - doNotTrackStub.restore(); - }); - - it('when there is a do not track, should send a dnt', function () { - doNotTrackStub.returns(1); - - const request = spec.buildRequests(bidRequestsWithMediaTypes, syncAddFPDToBidderRequest(mockBidderRequest)); - expect(request[0].data.device.dnt).to.equal(1); - }); - - it('when there is not do not track, don\'t send dnt', function () { - doNotTrackStub.returns(0); - - const request = spec.buildRequests(bidRequestsWithMediaTypes, syncAddFPDToBidderRequest(mockBidderRequest)); - expect(request[0].data.device.dnt).to.equal(0); - }); - - it('when there is no defined do not track, don\'t send dnt', function () { - doNotTrackStub.returns(null); - - const request = spec.buildRequests(bidRequestsWithMediaTypes, syncAddFPDToBidderRequest(mockBidderRequest)); - expect(request[0].data.device.dnt).to.equal(0); - }); - }); - - context('supply chain (schain)', function () { - let bidRequests; - let schainConfig; - const supplyChainNodePropertyOrder = ['asi', 'sid', 'hp', 'rid', 'name', 'domain']; - - beforeEach(function () { - schainConfig = { - ver: '1.0', - complete: 1, - nodes: [ - { - asi: 'exchange1.com', - sid: '1234', - hp: 1, - rid: 'bid-request-1', - name: 'publisher', - domain: 'publisher.com' - // omitted ext - }, - { - asi: 'exchange2.com', - sid: 'abcd', - hp: 1, - rid: 'bid-request-2', - // name field missing - domain: 'intermediary.com' - }, - { - asi: 'exchange3.com', - sid: '4321', - hp: 1, - // request id - // name field missing - domain: 'intermediary-2.com' - } - ] - }; - - bidRequests = [{ - bidder: 'openx', - params: { - unit: '11', - delDomain: 'test-del-domain' - }, - adUnitCode: '/adunit-code/test-path', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]] - } - }, - bidId: 'test-bid-id-1', - bidderRequestId: 'test-bid-request-1', - auctionId: 'test-auction-1', - schain: schainConfig - }]; - }); - - it('should send a supply chain object', function () { - const request = spec.buildRequests(bidRequests, mockBidderRequest); - expect(request[0].data.source.ext.schain).to.equal(schainConfig); - }); - - it('should send the supply chain object with the right version', function () { - const request = spec.buildRequests(bidRequests, mockBidderRequest); - expect(request[0].data.source.ext.schain.ver).to.equal(schainConfig.ver); - }); - - it('should send the supply chain object with the right complete value', function () { - const request = spec.buildRequests(bidRequests, mockBidderRequest); - expect(request[0].data.source.ext.schain.complete).to.equal(schainConfig.complete); - }); - }); - - context('when there are userid providers', function () { - const userIdAsEids = [ - { - source: 'adserver.org', - uids: [{ - id: 'some-random-id-value', - atype: 1, - ext: { - rtiPartner: 'TDID' - } - }] - }, - { - source: 'id5-sync.com', - uids: [{ - id: 'some-random-id-value', - atype: 1 - }] - }, - { - source: 'sharedid.org', - uids: [{ - id: 'some-random-id-value', - atype: 1, - ext: { - third: 'some-random-id-value' - } - }] - } - ]; - - it(`should send the user id under the extended ids`, function () { - const bidRequestsWithUserId = [{ - bidder: 'openx', - params: { - unit: '11', - delDomain: 'test-del-domain' - }, - userId: { - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]] - } - }, - bidId: 'test-bid-id-1', - bidderRequestId: 'test-bid-request-1', - auctionId: 'test-auction-1', - userIdAsEids: userIdAsEids - }]; - // enrich bid request with userId key/value - - const request = spec.buildRequests(bidRequestsWithUserId, mockBidderRequest); - expect(request[0].data.user.ext.eids).to.equal(userIdAsEids); - }); - - it(`when no user ids are available, it should not send any extended ids`, function () { - const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); - expect(request[0].data).to.not.have.any.keys('user'); - }); - }); - - context('FLEDGE', function() { - it('when FLEDGE is disabled, should not send imp.ext.ae', function () { - const request = spec.buildRequests( - bidRequestsWithMediaTypes, - { - ...mockBidderRequest, - fledgeEnabled: false - } - ); - expect(request[0].data.imp[0].ext).to.not.have.property('ae'); - }); - - it('when FLEDGE is enabled, should send whatever is set in ortb2imp.ext.ae in all bid requests', function () { - const request = spec.buildRequests(bidRequestsWithMediaTypes, { - ...mockBidderRequest, - fledgeEnabled: true - }); - expect(request[0].data.imp[0].ext.ae).to.equal(2); - }); - }); - }); - - context('banner', function () { - it('should send bid request with a mediaTypes specified with banner type', function () { - const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); - expect(request[0].data.imp[0]).to.have.any.keys(BANNER); - }); - }); - - context('video', function () { - it('should send bid request with a mediaTypes specified with video type', function () { - const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); - expect(request[1].data.imp[0]).to.have.any.keys(VIDEO); - }); - }); - - it.skip('should send ad unit ids when any are defined', function () { - const bidRequestsWithUnitIds = [{ - bidder: 'openx', - params: { - delDomain: 'test-del-domain' - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]] - } - }, - bidId: 'test-bid-id-1', - bidderRequestId: 'test-bid-request-1', - auctionId: 'test-auction-1', - transactionId: 'test-transaction-id-1' - }, { - bidder: 'openx', - params: { - unit: '22', - delDomain: 'test-del-domain' - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[728, 90]] - } - }, - bidId: 'test-bid-id-2', - bidderRequestId: 'test-bid-request-2', - auctionId: 'test-auction-2', - transactionId: 'test-transaction-id-2' - }]; - mockBidderRequest.bids = bidRequestsWithUnitIds; - const request = spec.buildRequests(bidRequestsWithUnitIds, mockBidderRequest); - expect(request[0].data.imp[1].tagid).to.equal(bidRequestsWithUnitIds[1].params.unit); - expect(request[0].data.imp[1].ext.divid).to.equal(bidRequestsWithUnitIds[1].params.adUnitCode); - }); - }); - - describe('interpretResponse()', function () { - let bidRequestConfigs; - let bidRequest; - let bidResponse; - let bid; - - context('when there is an nbr response', function () { - let bids; - beforeEach(function () { - bidRequestConfigs = [{ - bidder: 'openx', - params: { - unit: '12345678', - delDomain: 'test-del-domain' - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]], - }, - }, - bidId: 'test-bid-id', - bidderRequestId: 'test-bidder-request-id', - auctionId: 'test-auction-id' - }]; - - bidRequest = spec.buildRequests(bidRequestConfigs, {refererInfo: {}})[0]; - - bidResponse = {nbr: 0}; // Unknown error - bids = spec.interpretResponse({body: bidResponse}, bidRequest); - }); - - it('should not return any bids', function () { - expect(bids.length).to.equal(0); - }); - }); - - context('when no seatbid in response', function () { - let bids; - beforeEach(function () { - bidRequestConfigs = [{ - bidder: 'openx', - params: { - unit: '12345678', - delDomain: 'test-del-domain' - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]], - }, - }, - bidId: 'test-bid-id', - bidderRequestId: 'test-bidder-request-id', - auctionId: 'test-auction-id' - }]; - - bidRequest = spec.buildRequests(bidRequestConfigs, {refererInfo: {}})[0]; - - bidResponse = {ext: {}, id: 'test-bid-id'}; - bids = spec.interpretResponse({body: bidResponse}, bidRequest); - }); - - it('should not return any bids', function () { - expect(bids.length).to.equal(0); - }); - }); - - context('when there is no response', function () { - let bids; - beforeEach(function () { - bidRequestConfigs = [{ - bidder: 'openx', - params: { - unit: '12345678', - delDomain: 'test-del-domain' - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]], - }, - }, - bidId: 'test-bid-id', - bidderRequestId: 'test-bidder-request-id', - auctionId: 'test-auction-id' - }]; - - bidRequest = spec.buildRequests(bidRequestConfigs, {refererInfo: {}})[0]; - - bidResponse = ''; // Unknown error - bids = spec.interpretResponse({body: bidResponse}, bidRequest); - }); - - it('should not return any bids', function () { - expect(bids.length).to.equal(0); - }); - }); - - const SAMPLE_BID_REQUESTS = [{ - bidder: 'openx', - params: { - unit: '12345678', - delDomain: 'test-del-domain' - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]], - }, - }, - bidId: 'test-bid-id', - bidderRequestId: 'test-bidder-request-id', - auctionId: 'test-auction-id' - }]; - - const SAMPLE_BID_RESPONSE = { - seatbid: [{ - bid: [{ - impid: 'test-bid-id', - price: 2, - w: 300, - h: 250, - crid: 'test-creative-id', - dealid: 'test-deal-id', - adm: 'test-ad-markup', - adomain: ['brand.com'], - ext: { - dsp_id: '123', - buyer_id: '456', - brand_id: '789', - paf: { - content_id: 'paf_content_id' - } - } - }] - }], - cur: 'AUS', - ext: { - paf: { - transmission: {version: '12'} - } - } - }; - - context('when there is a response, the common response properties', function () { - beforeEach(function () { - bidRequestConfigs = deepClone(SAMPLE_BID_REQUESTS); - bidRequest = spec.buildRequests(bidRequestConfigs, {refererInfo: {}})[0]; - bidResponse = deepClone(SAMPLE_BID_RESPONSE); - - bid = spec.interpretResponse({body: bidResponse}, bidRequest)[0]; - }); - - it('should return a price', function () { - expect(bid.cpm).to.equal(bidResponse.seatbid[0].bid[0].price); - }); - - it('should return a request id', function () { - expect(bid.requestId).to.equal(bidResponse.seatbid[0].bid[0].impid); - }); - - it('should return width and height for the creative', function () { - expect(bid.width).to.equal(bidResponse.seatbid[0].bid[0].w); - expect(bid.height).to.equal(bidResponse.seatbid[0].bid[0].h); - }); - - it('should return a creativeId', function () { - expect(bid.creativeId).to.equal(bidResponse.seatbid[0].bid[0].crid); - }); - - it('should return an ad', function () { - expect(bid.ad).to.equal(bidResponse.seatbid[0].bid[0].adm); - }); - - it('should return a deal id if it exists', function () { - expect(bid.dealId).to.equal(bidResponse.seatbid[0].bid[0].dealid); - }); - - it('should have a time-to-live of 5 minutes', function () { - expect(bid.ttl).to.equal(300); - }); - - it('should always return net revenue', function () { - expect(bid.netRevenue).to.equal(true); - }); - - it('should return a currency', function () { - expect(bid.currency).to.equal(bidResponse.cur); - }); - - it('should return a brand ID', function () { - expect(bid.meta.brandId).to.equal(bidResponse.seatbid[0].bid[0].ext.brand_id); - }); - - it('should return a dsp ID', function () { - expect(bid.meta.networkId).to.equal(bidResponse.seatbid[0].bid[0].ext.dsp_id); - }); - - it('should return a buyer ID', function () { - expect(bid.meta.advertiserId).to.equal(bidResponse.seatbid[0].bid[0].ext.buyer_id); - }); - - it('should return adomain', function () { - expect(bid.meta.advertiserDomains).to.equal(bidResponse.seatbid[0].bid[0].adomain); - }); - - it('should return paf fields', function () { - const paf = { - transmission: {version: '12'}, - content_id: 'paf_content_id' - } - expect(bid.meta.paf).to.deep.equal(paf); - }); - }); - - context('when there is more than one response', () => { - let bids; - beforeEach(function () { - bidRequestConfigs = deepClone(SAMPLE_BID_REQUESTS); - bidRequest = spec.buildRequests(bidRequestConfigs, {refererInfo: {}})[0]; - bidResponse = deepClone(SAMPLE_BID_RESPONSE); - bidResponse.seatbid[0].bid.push(deepClone(bidResponse.seatbid[0].bid[0])); - bidResponse.seatbid[0].bid[1].ext.paf.content_id = 'second_paf' - - bids = spec.interpretResponse({body: bidResponse}, bidRequest); - }); - - it('should not confuse paf content_id', () => { - expect(bids.map(b => b.meta.paf.content_id)).to.eql(['paf_content_id', 'second_paf']); - }); - }) - - context('when the response is a banner', function() { - beforeEach(function () { - bidRequestConfigs = [{ - bidder: 'openx', - params: { - unit: '12345678', - delDomain: 'test-del-domain' - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]], - }, - }, - bidId: 'test-bid-id', - bidderRequestId: 'test-bidder-request-id', - auctionId: 'test-auction-id' - }]; - - bidRequest = spec.buildRequests(bidRequestConfigs, {refererInfo: {}})[0]; - - bidResponse = { - seatbid: [{ - bid: [{ - impid: 'test-bid-id', - price: 2, - w: 300, - h: 250, - crid: 'test-creative-id', - dealid: 'test-deal-id', - adm: 'test-ad-markup' - }] - }], - cur: 'AUS' - }; - - bid = spec.interpretResponse({body: bidResponse}, bidRequest)[0]; - }); - - it('should return the proper mediaType', function () { - it('should return a creativeId', function () { - expect(bid.mediaType).to.equal(Object.keys(bidRequestConfigs[0].mediaTypes)[0]); - }); - }); - }); - - context('when the response is a video', function() { - beforeEach(function () { - bidRequestConfigs = [{ - bidder: 'openx', - params: { - unit: '12345678', - delDomain: 'test-del-domain' - }, - adUnitCode: 'adunit-code', - mediaTypes: { - video: { - playerSize: [[640, 360], [854, 480]], - }, - }, - bidId: 'test-bid-id', - bidderRequestId: 'test-bidder-request-id', - auctionId: 'test-auction-id' - }]; - - bidRequest = spec.buildRequests(bidRequestConfigs, {refererInfo: {}})[0]; - - bidResponse = { - seatbid: [{ - bid: [{ - impid: 'test-bid-id', - price: 2, - w: 854, - h: 480, - crid: 'test-creative-id', - dealid: 'test-deal-id', - adm: 'test-ad-markup', - }] - }], - cur: 'AUS' - }; - }); - - it('should return the proper mediaType', function () { - bid = spec.interpretResponse({body: bidResponse}, bidRequest)[0]; - expect(bid.mediaType).to.equal(Object.keys(bidRequestConfigs[0].mediaTypes)[0]); - }); - - it('should return the proper mediaType', function () { - const winUrl = 'https//my.win.url'; - bidResponse.seatbid[0].bid[0].nurl = winUrl - bid = spec.interpretResponse({body: bidResponse}, bidRequest)[0]; - - expect(bid.vastUrl).to.equal(winUrl); - }); - }); - - context('when the response contains FLEDGE interest groups config', function() { - let response; - - beforeEach(function () { - sinon.stub(config, 'getConfig') - .withArgs('fledgeEnabled') - .returns(true); - - bidRequestConfigs = [{ - bidder: 'openx', - params: { - unit: '12345678', - delDomain: 'test-del-domain' - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]], - }, - }, - bidId: 'test-bid-id', - bidderRequestId: 'test-bidder-request-id', - auctionId: 'test-auction-id' - }]; - - bidRequest = spec.buildRequests(bidRequestConfigs, {refererInfo: {}})[0]; - - bidResponse = { - seatbid: [{ - bid: [{ - impid: 'test-bid-id', - price: 2, - w: 300, - h: 250, - crid: 'test-creative-id', - dealid: 'test-deal-id', - adm: 'test-ad-markup' - }] - }], - cur: 'AUS', - ext: { - fledge_auction_configs: { - 'test-bid-id': { - seller: 'codinginadtech.com', - interestGroupBuyers: ['somedomain.com'], - sellerTimeout: 0, - perBuyerSignals: { - 'somedomain.com': { - base_bid_micros: 0.1, - disallowed_advertiser_ids: [ - '1234', - '2345' - ], - multiplier: 1.3, - use_bid_multiplier: true, - win_reporting_id: '1234567asdf' - } - } - } - } - } - }; - - response = spec.interpretResponse({body: bidResponse}, bidRequest); - }); - - afterEach(function () { - config.getConfig.restore(); - }); - - it('should return FLEDGE auction_configs alongside bids', function () { - expect(response).to.have.property('bids'); - expect(response).to.have.property('fledgeAuctionConfigs'); - expect(response.fledgeAuctionConfigs.length).to.equal(1); - expect(response.fledgeAuctionConfigs[0].bidId).to.equal('test-bid-id'); - }); - }); - }); - - describe('user sync', function () { - it('should register the default image pixel if no pixels available', function () { - let syncs = spec.getUserSyncs( - {pixelEnabled: true}, - [] - ); - expect(syncs).to.deep.equal([{type: 'image', url: DEFAULT_SYNC}]); - }); - - it('should register custom syncUrl when exists', function () { - let syncs = spec.getUserSyncs( - {pixelEnabled: true}, - [{body: {ext: {delDomain: 'www.url.com'}}}] - ); - expect(syncs).to.deep.equal([{type: 'image', url: 'https://www.url.com/w/1.0/pd'}]); - }); - - it('should register custom syncUrl when exists', function () { - let syncs = spec.getUserSyncs( - {pixelEnabled: true}, - [{body: {ext: {platform: 'abc'}}}] - ); - expect(syncs).to.deep.equal([{type: 'image', url: SYNC_URL + '?ph=abc'}]); - }); - - it('when iframe sync is allowed, it should register an iframe sync', function () { - let syncs = spec.getUserSyncs( - {iframeEnabled: true}, - [] - ); - expect(syncs).to.deep.equal([{type: 'iframe', url: DEFAULT_SYNC}]); - }); - - it('should prioritize iframe over image for user sync', function () { - let syncs = spec.getUserSyncs( - {iframeEnabled: true, pixelEnabled: true}, - [] - ); - expect(syncs).to.deep.equal([{type: 'iframe', url: DEFAULT_SYNC}]); - }); - - describe('when gdpr applies', function () { - let gdprConsent; - let gdprPixelUrl; - const consentString = 'gdpr-pixel-consent'; - const gdprApplies = '1'; - beforeEach(() => { - gdprConsent = { - consentString, - gdprApplies: true - }; - - gdprPixelUrl = `${SYNC_URL}&gdpr=${gdprApplies}&gdpr_consent=${consentString}`; - }); - - it('when there is a response, it should have the gdpr query params', () => { - let [{url}] = spec.getUserSyncs( - {iframeEnabled: true, pixelEnabled: true}, - [], - gdprConsent - ); - - expect(url).to.have.string(`gdpr_consent=${consentString}`); - expect(url).to.have.string(`gdpr=${gdprApplies}`); - }); - - it('should not send signals if no consent object is available', function () { - let [{url}] = spec.getUserSyncs( - {iframeEnabled: true, pixelEnabled: true}, - [], - ); - expect(url).to.not.have.string('gdpr_consent='); - expect(url).to.not.have.string('gdpr='); - }); - }); - - describe('when ccpa applies', function () { - let usPrivacyConsent; - let uspPixelUrl; - const privacyString = 'TEST'; - beforeEach(() => { - usPrivacyConsent = 'TEST'; - uspPixelUrl = `${DEFAULT_SYNC}&us_privacy=${privacyString}` - }); - it('should send the us privacy string, ', () => { - let [{url}] = spec.getUserSyncs( - {iframeEnabled: true, pixelEnabled: true}, - [], - undefined, - usPrivacyConsent - ); - expect(url).to.have.string(`us_privacy=${privacyString}`); - }); - - it('should not send signals if no consent string is available', function () { - let [{url}] = spec.getUserSyncs( - {iframeEnabled: true, pixelEnabled: true}, - [], - ); - expect(url).to.not.have.string('us_privacy='); - }); - }); - }); -}); diff --git a/test/spec/modules/operaadsBidAdapter_spec.js b/test/spec/modules/operaadsBidAdapter_spec.js index 45bc8995a5c..37d4a2c7bc0 100644 --- a/test/spec/modules/operaadsBidAdapter_spec.js +++ b/test/spec/modules/operaadsBidAdapter_spec.js @@ -1,7 +1,7 @@ -import { expect } from 'chai'; -import { spec } from 'modules/operaadsBidAdapter.js'; -import { newBidder } from 'src/adapters/bidderFactory.js'; -import { BANNER, NATIVE, VIDEO } from 'src/mediaTypes.js'; +import {expect} from 'chai'; +import {spec} from 'modules/operaadsBidAdapter.js'; +import {newBidder} from 'src/adapters/bidderFactory.js'; +import {BANNER, NATIVE, VIDEO} from 'src/mediaTypes.js'; describe('Opera Ads Bid Adapter', function () { describe('Test isBidRequestValid', function () { @@ -234,7 +234,7 @@ describe('Opera Ads Bid Adapter', function () { requestData = JSON.parse(req.data); }).to.not.throw(); - expect(requestData.id).to.equal(bidderRequest.auctionId); + expect(requestData.id).to.exist; expect(requestData.tmax).to.equal(bidderRequest.timeout); expect(requestData.test).to.equal(0); expect(requestData.imp).to.be.an('array').that.have.lengthOf(1); diff --git a/test/spec/modules/optidigitalBidAdapter_spec.js b/test/spec/modules/optidigitalBidAdapter_spec.js index 8c222650f7e..caa12483ea9 100755 --- a/test/spec/modules/optidigitalBidAdapter_spec.js +++ b/test/spec/modules/optidigitalBidAdapter_spec.js @@ -557,7 +557,6 @@ describe('optidigitalAdapterTests', function () { let expectedResponse = [ { 'placementId': 'Billboard_Top', - 'transactionId': 'cf5faec3-fcee-4f26-80ae-fc8b6cf23b7d', 'requestId': '83fb53a5e67f49', 'ttl': 150, 'creativeId': 'mobile_pos_2', @@ -572,7 +571,6 @@ describe('optidigitalAdapterTests', function () { } }, { 'placementId': 'Billboard_Bottom', - 'transactionId': 'df5faec3-fcee-4f26-80ae-fc8b6cf23b7d', 'requestId': '93fb53a5e67f49', 'ttl': 150, 'creativeId': 'mobile_pos_2', diff --git a/test/spec/modules/orbidderBidAdapter_spec.js b/test/spec/modules/orbidderBidAdapter_spec.js index 0d1396866e7..acb779b436d 100644 --- a/test/spec/modules/orbidderBidAdapter_spec.js +++ b/test/spec/modules/orbidderBidAdapter_spec.js @@ -9,7 +9,11 @@ describe('orbidderBidAdapter', () => { const defaultBidRequestBanner = { bidId: 'd66fa86787e0b0ca900a96eacfd5f0bb', auctionId: 'ccc4c7cdfe11cfbd74065e6dd28413d8', - transactionId: 'd58851660c0c4461e4aa06344fc9c0c6', + ortb2Imp: { + ext: { + tid: 'd58851660c0c4461e4aa06344fc9c0c6', + } + }, bidRequestCount: 1, adUnitCode: 'adunit-code', sizes: [[300, 250], [300, 600]], @@ -27,7 +31,11 @@ describe('orbidderBidAdapter', () => { const defaultBidRequestNative = { bidId: 'd66fa86787e0b0ca900a96eacfd5f0bc', auctionId: 'ccc4c7cdfe11cfbd74065e6dd28413d9', - transactionId: 'd58851660c0c4461e4aa06344fc9c0c7', + ortb2Imp: { + ext: { + tid: 'd58851660c0c4461e4aa06344fc9c0c7', + } + }, bidRequestCount: 1, adUnitCode: 'adunit-code-native', sizes: [], @@ -151,8 +159,11 @@ describe('orbidderBidAdapter', () => { }); describe('buildRequests', () => { - const request = buildRequest(defaultBidRequestBanner); - const nativeRequest = buildRequest(defaultBidRequestNative); + let request, nativeRequest; + before(() => { + request = buildRequest(defaultBidRequestBanner); + nativeRequest = buildRequest(defaultBidRequestNative); + }) it('sends bid request to endpoint via https using post', () => { expect(request.method).to.equal('POST'); @@ -170,7 +181,7 @@ describe('orbidderBidAdapter', () => { expect(request.data.bidId).to.equal(defaultBidRequestBanner.bidId); expect(request.data.auctionId).to.equal(defaultBidRequestBanner.auctionId); - expect(request.data.transactionId).to.equal(defaultBidRequestBanner.transactionId); + expect(request.data.transactionId).to.equal(defaultBidRequestBanner.ortb2Imp.ext.tid); expect(request.data.bidRequestCount).to.equal(defaultBidRequestBanner.bidRequestCount); expect(request.data.adUnitCode).to.equal(defaultBidRequestBanner.adUnitCode); expect(request.data.pageUrl).to.equal('https://localhost:9876/'); @@ -187,7 +198,7 @@ describe('orbidderBidAdapter', () => { expect(nativeRequest.data.bidId).to.equal(defaultBidRequestNative.bidId); expect(nativeRequest.data.auctionId).to.equal(defaultBidRequestNative.auctionId); - expect(nativeRequest.data.transactionId).to.equal(defaultBidRequestNative.transactionId); + expect(nativeRequest.data.transactionId).to.equal(defaultBidRequestNative.ortb2Imp.ext.tid); expect(nativeRequest.data.bidRequestCount).to.equal(defaultBidRequestNative.bidRequestCount); expect(nativeRequest.data.adUnitCode).to.equal(defaultBidRequestNative.adUnitCode); expect(nativeRequest.data.pageUrl).to.equal('https://localhost:9876/'); @@ -283,7 +294,7 @@ describe('orbidderBidAdapter', () => { }); }); - describe('buildRequests with price floor module', () => { + it('buildRequests with price floor module', () => { const bidRequest = deepClone(defaultBidRequestBanner); bidRequest.params.bidfloor = 1; bidRequest.getFloor = (floorObj) => { diff --git a/test/spec/modules/outbrainBidAdapter_spec.js b/test/spec/modules/outbrainBidAdapter_spec.js index f5ce00ed8df..ab72ff85ab0 100644 --- a/test/spec/modules/outbrainBidAdapter_spec.js +++ b/test/spec/modules/outbrainBidAdapter_spec.js @@ -3,6 +3,7 @@ import { spec } from 'modules/outbrainBidAdapter.js'; import { config } from 'src/config.js'; import { server } from 'test/mocks/xhr'; import { createEidsArray } from 'modules/userId/eids.js'; +import * as utils from 'src/utils.js'; describe('Outbrain Adapter', function () { describe('Bid request and response', function () { @@ -227,6 +228,7 @@ describe('Outbrain Adapter', function () { }) const commonBidderRequest = { + bidderRequestId: 'mock-uuid', refererInfo: { page: 'https://example.com/' } @@ -263,6 +265,7 @@ describe('Outbrain Adapter', function () { ] } const expectedData = { + id: 'mock-uuid', site: { page: 'https://example.com/', publisher: { @@ -305,6 +308,7 @@ describe('Outbrain Adapter', function () { ...displayBidRequestParams, } const expectedData = { + id: 'mock-uuid', site: { page: 'https://example.com/', publisher: { @@ -352,6 +356,7 @@ describe('Outbrain Adapter', function () { ...videoBidRequestParams, } const expectedData = { + id: 'mock-uuid', site: { page: 'https://example.com/', publisher: { diff --git a/test/spec/modules/oxxionAnalyticsAdapter_spec.js b/test/spec/modules/oxxionAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..5516fb83320 --- /dev/null +++ b/test/spec/modules/oxxionAnalyticsAdapter_spec.js @@ -0,0 +1,343 @@ +import oxxionAnalytics from 'modules/oxxionAnalyticsAdapter.js'; +import {dereferenceWithoutRenderer} from 'modules/oxxionAnalyticsAdapter.js'; +import { expect } from 'chai'; +import { server } from 'test/mocks/xhr.js'; +let adapterManager = require('src/adapterManager').default; +let events = require('src/events'); +let constants = require('src/constants.json'); + +describe('Oxxion Analytics', function () { + let timestamp = new Date() - 256; + let auctionId = '5018eb39-f900-4370-b71e-3bb5b48d324f'; + let timeout = 1500; + + let bidTimeout = [ + { + 'bidId': '5fe418f2d70364', + 'bidder': 'appnexusAst', + 'adUnitCode': 'tag_200124_banner', + 'auctionId': '1e8b993d-8f0a-4232-83eb-3639ddf3a44b' + } + ]; + + const auctionEnd = { + 'auctionId': '1e8b993d-8f0a-4232-83eb-3639ddf3a44b', + 'timestamp': 1647424261187, + 'auctionEnd': 1647424261714, + 'auctionStatus': 'completed', + 'adUnits': [ + { + 'code': 'tag_200124_banner', + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 600 + ] + ] + } + }, + 'bids': [ + { + 'bidder': 'appnexus', + 'params': { + 'placementId': 123456 + } + }, + { + 'bidder': 'appnexusAst', + 'params': { + 'placementId': 234567 + } + } + ], + 'sizes': [ + [ + 300, + 600 + ] + ], + 'transactionId': 'de664ccb-e18b-4436-aeb0-362382eb1b40' + } + ], + 'adUnitCodes': [ + 'tag_200124_banner' + ], + 'bidderRequests': [ + { + 'bidderCode': 'appnexus', + 'auctionId': '1e8b993d-8f0a-4232-83eb-3639ddf3a44b', + 'bidderRequestId': '11dc6ff6378de7', + 'bids': [ + { + 'bidder': 'appnexus', + 'params': { + 'placementId': 123456 + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 600 + ] + ] + } + }, + 'adUnitCode': 'tag_200124_banner', + 'transactionId': 'de664ccb-e18b-4436-aeb0-362382eb1b40', + 'sizes': [ + [ + 300, + 600 + ] + ], + 'bidId': '34a63e5d5378a3', + 'bidderRequestId': '11dc6ff6378de7', + 'auctionId': '1e8b993d-8f0a-4232-83eb-3639ddf3a44b', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 + } + ], + 'auctionStart': 1647424261187, + 'timeout': 1000, + 'gdprConsent': { + 'consentString': 'CONSENT', + 'gdprApplies': true, + 'apiVersion': 2, + 'vendorData': 'a lot of borring stuff', + }, + 'start': 1647424261189 + }, + ], + 'noBids': [ + { + 'bidder': 'appnexusAst', + 'params': { + 'placementId': 10471298 + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 600 + ] + ] + } + }, + 'adUnitCode': 'tag_200124_banner', + 'transactionId': 'de664ccb-e18b-4436-aeb0-362382eb1b40', + 'sizes': [ + [ + 300, + 600 + ] + ], + 'bidId': '5fe418f2d70364', + 'bidderRequestId': '4229a45ab8ea87', + 'auctionId': '1e8b993d-8f0a-4232-83eb-3639ddf3a44b', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 + } + ], + 'bidsReceived': [ + { + 'bidderCode': 'appnexus', + 'width': 300, + 'height': 600, + 'statusMessage': 'Bid available', + 'adId': '7a4ced80f33d33', + 'requestId': '34a63e5d5378a3', + 'transactionId': 'de664ccb-e18b-4436-aeb0-362382eb1b40', + 'auctionId': '1e8b993d-8f0a-4232-83eb-3639ddf3a44b', + 'mediaType': 'video', + 'source': 'client', + 'cpm': 27.4276, + 'creativeId': '158534630', + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 2000, + 'ad': 'some html', + 'meta': { + 'advertiserDomains': [ + 'example.com' + ] + }, + 'renderer': 'something', + 'originalCpm': 25.02521, + 'originalCurrency': 'EUR', + 'responseTimestamp': 1647424261559, + 'requestTimestamp': 1647424261189, + 'bidder': 'appnexus', + 'adUnitCode': 'tag_200124_banner', + 'timeToRespond': 370, + 'pbLg': '5.00', + 'pbMg': '20.00', + 'pbHg': '20.00', + 'pbAg': '20.00', + 'pbDg': '20.00', + 'pbCg': '20.000000', + 'size': '300x600', + 'adserverTargeting': { + 'hb_bidder': 'appnexus', + 'hb_adid': '7a4ced80f33d33', + 'hb_pb': '20.000000', + 'hb_size': '300x600', + 'hb_source': 'client', + 'hb_format': 'banner', + 'hb_adomain': 'example.com' + } + } + ], + 'winningBids': [ + + ], + 'timeout': 1000 + }; + + let bidWon = { + 'bidderCode': 'appnexus', + 'width': 970, + 'height': 250, + 'statusMessage': 'Bid available', + 'adId': '65d16ef039a97a', + 'requestId': '2bd3e8ff8a113f', + 'transactionId': '8b2a8629-d1ea-4bb1-aff0-e335b96dd002', + 'auctionId': '1e8b993d-8f0a-4232-83eb-3639ddf3a44b', + 'mediaType': 'banner', + 'source': 'client', + 'cpm': 27.4276, + 'creativeId': '158533702', + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 2000, + 'ad': 'some html', + 'meta': { + 'advertiserDomains': [ + 'example.com' + ] + }, + 'renderer': 'something', + 'originalCpm': 25.02521, + 'originalCurrency': 'EUR', + 'responseTimestamp': 1647424261558, + 'requestTimestamp': 1647424261189, + 'bidder': 'appnexus', + 'adUnitCode': 'tag_200123_banner', + 'timeToRespond': 369, + 'originalBidder': 'appnexus', + 'pbLg': '5.00', + 'pbMg': '20.00', + 'pbHg': '20.00', + 'pbAg': '20.00', + 'pbDg': '20.00', + 'pbCg': '20.000000', + 'size': '970x250', + 'adserverTargeting': { + 'hb_bidder': 'appnexus', + 'hb_adid': '65d16ef039a97a', + 'hb_pb': '20.000000', + 'hb_size': '970x250', + 'hb_source': 'client', + 'hb_format': 'banner', + 'hb_adomain': 'example.com' + }, + 'status': 'rendered', + 'params': [ + { + 'placementId': 123456 + } + ] + }; + + after(function () { + oxxionAnalytics.disableAnalytics(); + }); + + describe('main test flow', function () { + beforeEach(function () { + sinon.stub(events, 'getEvents').returns([]); + sinon.spy(oxxionAnalytics, 'track'); + }); + afterEach(function () { + events.getEvents.restore(); + oxxionAnalytics.disableAnalytics(); + oxxionAnalytics.track.restore(); + }); + it('test dereferenceWithoutRenderer', function () { + adapterManager.registerAnalyticsAdapter({ + code: 'oxxion', + adapter: oxxionAnalytics + }); + + adapterManager.enableAnalytics({ + provider: 'oxxion', + options: { + domain: 'test' + } + }); + let resultBidWon = JSON.parse(dereferenceWithoutRenderer(bidWon)); + expect(resultBidWon).not.to.have.property('renderer'); + let resultBid = JSON.parse(dereferenceWithoutRenderer(auctionEnd)); + expect(resultBid).to.have.property('bidsReceived').and.to.have.lengthOf(1); + expect(resultBid.bidsReceived[0]).not.to.have.property('renderer'); + }); + it('test auctionEnd', function () { + adapterManager.registerAnalyticsAdapter({ + code: 'oxxion', + adapter: oxxionAnalytics + }); + + adapterManager.enableAnalytics({ + provider: 'oxxion', + options: { + domain: 'test' + } + }); + + events.emit(constants.EVENTS.BID_REQUESTED, auctionEnd['bidderRequests'][0]); + events.emit(constants.EVENTS.BID_RESPONSE, auctionEnd['bidsReceived'][0]); + events.emit(constants.EVENTS.BID_TIMEOUT, bidTimeout); + events.emit(constants.EVENTS.AUCTION_END, auctionEnd); + expect(server.requests.length).to.equal(1); + let message = JSON.parse(server.requests[0].requestBody); + expect(message).to.have.property('auctionEnd').exist; + expect(message.auctionEnd).to.have.lengthOf(1); + expect(message.auctionEnd[0]).to.have.property('bidsReceived').and.to.have.lengthOf(1); + expect(message.auctionEnd[0].bidsReceived[0]).not.to.have.property('ad'); + expect(message.auctionEnd[0].bidsReceived[0]).to.have.property('meta'); + expect(message.auctionEnd[0].bidsReceived[0].meta).to.have.property('advertiserDomains'); + expect(message.auctionEnd[0].bidsReceived[0]).to.have.property('adId'); + expect(message.auctionEnd[0]).to.have.property('bidderRequests').and.to.have.lengthOf(1); + expect(message.auctionEnd[0].bidderRequests[0]).to.have.property('gdprConsent'); + expect(message.auctionEnd[0].bidderRequests[0].gdprConsent).not.to.have.property('vendorData'); + }); + + it('test bidWon', function() { + adapterManager.registerAnalyticsAdapter({ + code: 'oxxion', + adapter: oxxionAnalytics + }); + + adapterManager.enableAnalytics({ + provider: 'oxxion', + options: { + domain: 'test' + } + }); + events.emit(constants.EVENTS.BID_WON, bidWon); + expect(server.requests.length).to.equal(1); + let message = JSON.parse(server.requests[0].requestBody); + expect(message).not.to.have.property('ad'); + expect(message).to.have.property('adId') + expect(message).to.have.property('cpmIncrement').and.to.equal(27.4276); + // sinon.assert.callCount(oxxionAnalytics.track, 1); + }); + }); +}); diff --git a/test/spec/modules/oxxionRtdProvider_spec.js b/test/spec/modules/oxxionRtdProvider_spec.js index 776076cc215..7bccf2319a4 100644 --- a/test/spec/modules/oxxionRtdProvider_spec.js +++ b/test/spec/modules/oxxionRtdProvider_spec.js @@ -6,7 +6,10 @@ const utils = require('src/utils.js'); const moduleConfig = { params: { domain: 'test.endpoint', - contexts: ['instream', 'outstream'] + contexts: ['instream', 'outstream'], + samplingRate: 10, + threshold: false, + bidders: ['appnexus', 'mediasquare'], } }; @@ -31,6 +34,14 @@ let request = { ], 'transactionId': 'de664ccb-e18b-4436-aeb0-362382eb1b41' }, + { + 'code': 'msq_tag_200125_banner', + 'mediaTypes': { 'banner': { 'sizes': [[300, 250]] } }, + 'bids': [ + {'bidder': 'appnexusAst', 'params': {'placementId': 345678}}, + ], + 'transactionId': 'de664ccb-e18b-4436-aeb0-362382eb1b41' + } ] }; @@ -102,6 +113,93 @@ let bids = [{ }, ]; +let originalBidderRequests = [{ + 'bidderCode': 'rubicon', + 'auctionId': 'dd42b870-2072-4b71-8ab7-e7789b14c5ce', + 'bidderRequestId': '16c2bceb2e891a', + 'bids': [ + { + 'bidder': 'rubicon', + 'params': { + 'accountId': 1234, + 'siteId': 2345, + 'zoneId': 3456 + }, + 'auctionId': 'dd42b870-2072-4b71-8ab7-e7789b14c5ce', + 'mediaTypes': {'banner': {'sizes': [[970, 250]]}}, + 'adUnitCode': 'adunit1', + 'transactionId': '8f20b49c-5e47-4bb5-a7d5-0b816cf527f3', + 'bidId': '2d9920072ab028', + 'bidderRequestId': '16c2bceb2e891a', + }, + { + 'bidder': 'rubicon', + 'params': { + 'accountId': 1234, + 'siteId': 2345, + 'zoneId': 4567 + }, + 'auctionId': 'dd42b870-2072-4b71-8ab7-e7789b14c5ce', + 'mediaTypes': {'banner': {'sizes': [[300, 250]]}}, + 'adUnitCode': 'adunit2', + 'transactionId': '4161f09e-7870-4486-b2a6-b4158a327bc4', + 'bidId': '331c3d708f4864', + 'bidderRequestId': '16c2bceb2e891a', + 'src': 'client', + } + ], + 'auctionStart': 1683383333809, + 'timeout': 3000, + 'gdprConsent': { + 'consentString': 'consent_hash', + 'gdprApplies': true, + 'apiVersion': 2 + } +}, +{ + 'bidderCode': 'appnexusAst', + 'auctionId': 'dd42b870-2072-4b71-8ab7-e7789b14c5ce', + 'bidderRequestId': '4d83b8c60d45e7', + 'bids': [ + { + 'bidder': 'appnexusAst', + 'params': { + 'placementId': 10471298 + }, + 'auctionId': 'dd42b870-2072-4b71-8ab7-e7789b14c5ce', + 'mediaTypes': {'banner': {'sizes': [[300, 250]]}}, + 'adUnitCode': 'adunit2', + 'transactionId': '4161f09e-7870-4486-b2a6-b4158a327bc4', + 'bidId': '5b7cd5abc6aea3', + 'bidderRequestId': '4d83b8c60d45e7', + } + ], + 'auctionStart': 1683383333809, + 'timeout': 3000, + 'gdprConsent': { + 'consentString': 'consent_hash', + 'gdprApplies': true, + 'apiVersion': 2 + } +} +]; + +let bidInterests = [ + {'id': 0, 'rate': 50.0, 'suggestion': true}, + {'id': 1, 'rate': 12.0, 'suggestion': false}, + {'id': 2, 'rate': 0.0, 'suggestion': true}, + {'id': 3, 'rate': 0.0, 'suggestion': false}, +]; + +const userConsent = { + 'gdpr': { + 'consentString': 'consent_hash' + }, + 'usp': null, + 'gpp': null, + 'coppa': false +}; + describe('oxxionRtdProvider', () => { describe('Oxxion RTD sub module', () => { it('should init, return true, and set the params', () => { @@ -114,7 +212,22 @@ describe('oxxionRtdProvider', () => { auctionEnd.bidsReceived = bids; it('call everything', function() { oxxionSubmodule.getBidRequestData(request, null, moduleConfig); - oxxionSubmodule.onAuctionEndEvent(auctionEnd, moduleConfig); + oxxionSubmodule.onBidResponseEvent(auctionEnd.bidsReceived[0], moduleConfig); + oxxionSubmodule.onBidResponseEvent(auctionEnd.bidsReceived[1], moduleConfig); + }); + it('check bid filtering', function() { + let requestsList = oxxionSubmodule.getRequestsList(request); + expect(requestsList.length).to.equal(4); + expect(requestsList[0]).to.have.property('id'); + expect(request.adUnits[0].bids[0]).to.have.property('_id'); + expect(requestsList[0].id).to.equal(request.adUnits[0].bids[0]._id); + const [filteredBiddderRequests, filteredBids] = oxxionSubmodule.getFilteredAdUnitsOnBidRates(bidInterests, request.adUnits, moduleConfig.params, false); + expect(filteredBids.length).to.equal(1); + expect(filteredBiddderRequests.length).to.equal(3); + expect(filteredBiddderRequests[0]).to.have.property('bids'); + expect(filteredBiddderRequests[0].bids.length).to.equal(1); + expect(filteredBiddderRequests[1]).to.have.property('bids'); + expect(filteredBiddderRequests[1].bids.length).to.equal(1); }); it('check vastImpUrl', function() { expect(auctionEnd.bidsReceived[0]).to.have.property('vastImpUrl'); @@ -136,7 +249,7 @@ describe('oxxionRtdProvider', () => { expect(inlineImpressions).to.have.lengthOf.above(0); }); it('check cpmIncrement', function() { - expect(auctionEnd.bidsReceived[1].vastImpUrl).to.contain(encodeURI('cpmIncrement=1')); + expect(auctionEnd.bidsReceived[1].vastImpUrl).to.contain(encodeURI('cpmIncrement=0')); }); }); }); diff --git a/test/spec/modules/pairIdSystem_spec.js b/test/spec/modules/pairIdSystem_spec.js new file mode 100644 index 00000000000..0bb42e56c25 --- /dev/null +++ b/test/spec/modules/pairIdSystem_spec.js @@ -0,0 +1,81 @@ +import { storage, pairIdSubmodule } from 'modules/pairIdSystem.js'; +import * as utils from 'src/utils.js'; + +describe('pairId', function () { + let sandbox; + let logInfoStub; + + beforeEach(() => { + sandbox = sinon.sandbox.create(); + logInfoStub = sandbox.stub(utils, 'logInfo'); + }); + afterEach(() => { + sandbox.restore(); + }); + + it('should log an error if no ID is found when getId', function() { + pairIdSubmodule.getId({ params: {} }); + expect(logInfoStub.calledOnce).to.be.true; + }); + + it('should read pairId from local storage if exists', function() { + let pairIds = ['test-pair-id1', 'test-pair-id2', 'test-pair-id3']; + sandbox.stub(storage, 'getDataFromLocalStorage').withArgs('pairId').returns(btoa(JSON.stringify(pairIds))); + + let id = pairIdSubmodule.getId({ params: {} }); + expect(id).to.be.deep.equal({id: pairIds}); + }); + + it('should read pairId from cookie if exists', function() { + let pairIds = ['test-pair-id4', 'test-pair-id5', 'test-pair-id6']; + sandbox.stub(storage, 'getCookie').withArgs('pairId').returns(btoa(JSON.stringify(pairIds))); + + let id = pairIdSubmodule.getId({ params: {} }); + expect(id).to.be.deep.equal({id: pairIds}); + }); + + it('should read pairId from default liveramp envelope local storage key if configured', function() { + let pairIds = ['test-pair-id1', 'test-pair-id2', 'test-pair-id3']; + sandbox.stub(storage, 'getDataFromLocalStorage').withArgs('_lr_pairId').returns(btoa(JSON.stringify({'envelope': pairIds}))); + let id = pairIdSubmodule.getId({ + params: { + liveramp: {} + }}) + expect(id).to.be.deep.equal({id: pairIds}) + }) + + it('should read pairId from default liveramp envelope cookie entry if configured', function() { + let pairIds = ['test-pair-id4', 'test-pair-id5', 'test-pair-id6']; + sandbox.stub(storage, 'getDataFromLocalStorage').withArgs('_lr_pairId').returns(btoa(JSON.stringify({'envelope': pairIds}))); + let id = pairIdSubmodule.getId({ + params: { + liveramp: {} + }}) + expect(id).to.be.deep.equal({id: pairIds}) + }) + + it('should read pairId from specified liveramp envelope cookie entry if configured with storageKey', function() { + let pairIds = ['test-pair-id7', 'test-pair-id8', 'test-pair-id9']; + sandbox.stub(storage, 'getDataFromLocalStorage').withArgs('lr_pairId_custom').returns(btoa(JSON.stringify({'envelope': pairIds}))); + let id = pairIdSubmodule.getId({ + params: { + liveramp: { + storageKey: 'lr_pairId_custom' + } + }}) + expect(id).to.be.deep.equal({id: pairIds}) + }) + + it('should not get data from storage if local storage and cookies are disabled', function () { + sandbox.stub(storage, 'localStorageIsEnabled').returns(false); + sandbox.stub(storage, 'cookiesAreEnabled').returns(false); + let id = pairIdSubmodule.getId({ + params: { + liveramp: { + storageKey: 'lr_pairId_custom' + } + } + }) + expect(id).to.equal(undefined) + }) +}); diff --git a/test/spec/modules/permutiveRtdProvider_spec.js b/test/spec/modules/permutiveRtdProvider_spec.js index 5030e662ea9..942ec2eaa46 100644 --- a/test/spec/modules/permutiveRtdProvider_spec.js +++ b/test/spec/modules/permutiveRtdProvider_spec.js @@ -2,12 +2,15 @@ import { permutiveSubmodule, storage, getSegments, - initSegments, isAcEnabled, isPermutiveOnPage, setBidderRtb, getModuleConfig, PERMUTIVE_SUBMODULE_CONFIG_KEY, + readAndSetCohorts, + PERMUTIVE_STANDARD_KEYWORD, + PERMUTIVE_STANDARD_AUD_KEYWORD, + PERMUTIVE_CUSTOM_COHORTS_KEYWORD, } from 'modules/permutiveRtdProvider.js' import { deepAccess, deepSetValue, mergeDeep } from '../../../src/utils.js' import { config } from 'src/config.js' @@ -188,24 +191,81 @@ describe('permutiveRtdProvider', function () { const moduleConfig = getConfig() const bidderConfig = {}; const acBidders = moduleConfig.params.acBidders - const expectedTargetingData = transformedTargeting().ac.map(seg => { + const segmentsData = transformedTargeting() + const expectedTargetingData = segmentsData.ac.map(seg => { return { id: seg } }) - setBidderRtb(bidderConfig, moduleConfig) + setBidderRtb(bidderConfig, moduleConfig, segmentsData) acBidders.forEach(bidder => { - expect(bidderConfig[bidder].user.data).to.deep.include.members([{ - name: 'permutive.com', - segment: expectedTargetingData - }]) + const customCohorts = segmentsData[bidder] || [] + expect(bidderConfig[bidder].user.data).to.deep.include.members([ + { + name: 'permutive.com', + segment: expectedTargetingData, + }, + // Should have custom cohorts specific for that bidder + { + name: 'permutive', + segment: customCohorts.map(seg => { + return { id: seg } + }), + }, + ]) }) }) + + it('should override existing ortb2.user.data reserved by permutive RTD', function () { + const reservedPermutiveStandardName = 'permutive.com' + const reservedPermutiveCustomCohortName = 'permutive' + + const moduleConfig = getConfig() + const acBidders = moduleConfig.params.acBidders + const segmentsData = transformedTargeting() + + const sampleOrtbConfig = { + user: { + data: [ + { + name: reservedPermutiveCustomCohortName, + segment: [{ id: 'remove-me' }, { id: 'remove-me-also' }] + }, + { + name: reservedPermutiveStandardName, + segment: [{ id: 'remove-me-also-also' }, { id: 'remove-me-also-also-also' }] + } + ] + } + } + + const bidderConfig = Object.fromEntries(acBidders.map(bidder => [bidder, sampleOrtbConfig])) + + setBidderRtb(bidderConfig, moduleConfig, segmentsData) + + acBidders.forEach(bidder => { + const customCohorts = segmentsData[bidder] || [] + + expect(bidderConfig[bidder].user.data).to.not.deep.include.members([...sampleOrtbConfig.user.data]) + expect(bidderConfig[bidder].user.data).to.deep.include.members([ + { + name: reservedPermutiveCustomCohortName, + segment: customCohorts.map(id => ({ id })), + }, + { + name: reservedPermutiveStandardName, + segment: segmentsData.ac.map(id => ({ id })), + }, + ]) + }) + }) + it('should include ortb2 user data transformation for IAB audience taxonomy', function() { const moduleConfig = getConfig() const bidderConfig = {} const acBidders = moduleConfig.params.acBidders - const expectedTargetingData = transformedTargeting().ac.map(seg => { + const segmentsData = transformedTargeting() + const expectedTargetingData = segmentsData.ac.map(seg => { return { id: seg } }) @@ -225,7 +285,7 @@ describe('permutiveRtdProvider', function () { } ) - setBidderRtb(bidderConfig, moduleConfig) + setBidderRtb(bidderConfig, moduleConfig, segmentsData) acBidders.forEach(bidder => { expect(bidderConfig[bidder].user.data).to.deep.include.members([ @@ -244,6 +304,8 @@ describe('permutiveRtdProvider', function () { it('should not overwrite ortb2 config', function () { const moduleConfig = getConfig() const acBidders = moduleConfig.params.acBidders + const segmentsData = transformedTargeting() + const sampleOrtbConfig = { site: { name: 'example' @@ -267,10 +329,7 @@ describe('permutiveRtdProvider', function () { segment: [1, 2, 3] } - setBidderRtb(bidderConfig, moduleConfig, { - // TODO: this argument is unused, is the test still valid / needed? - testTransformation: userData => transformedUserData - }) + setBidderRtb(bidderConfig, moduleConfig, segmentsData) acBidders.forEach(bidder => { expect(bidderConfig[bidder].site.name).to.equal(sampleOrtbConfig.site.name) @@ -280,6 +339,8 @@ describe('permutiveRtdProvider', function () { it('should update user.keywords and not override existing values', function () { const moduleConfig = getConfig() const acBidders = moduleConfig.params.acBidders + const segmentsData = transformedTargeting() + const sampleOrtbConfig = { site: { name: 'example' @@ -304,19 +365,31 @@ describe('permutiveRtdProvider', function () { segment: [1, 2, 3] } - setBidderRtb(bidderConfig, moduleConfig, { - // TODO: this argument is unused, is the test still valid / needed? - testTransformation: userData => transformedUserData - }) + setBidderRtb(bidderConfig, moduleConfig, segmentsData) acBidders.forEach(bidder => { + const customCohortsData = segmentsData[bidder] || [] + const keywordGroups = { + [PERMUTIVE_STANDARD_KEYWORD]: segmentsData.ac, + [PERMUTIVE_STANDARD_AUD_KEYWORD]: segmentsData.ssp.cohorts, + [PERMUTIVE_CUSTOM_COHORTS_KEYWORD]: customCohortsData + } + + // Transform groups of key-values into a single array of strings + // i.e { permutive: ['1', '2'], p_standard: ['3', '4'] } => ['permutive=1', 'permutive=2', 'p_standard=3',' p_standard=4'] + const transformedKeywordGroups = Object.entries(keywordGroups) + .flatMap(([keyword, ids]) => ids.map(id => `${keyword}=${id}`)) + + const keywords = `${sampleOrtbConfig.user.keywords},${transformedKeywordGroups.join(',')}` + expect(bidderConfig[bidder].site.name).to.equal(sampleOrtbConfig.site.name) expect(bidderConfig[bidder].user.data).to.deep.include.members([sampleOrtbConfig.user.data[0]]) - expect(bidderConfig[bidder].user.keywords).to.deep.equal('a,b,p_standard_aud=123,p_standard_aud=abc') + expect(bidderConfig[bidder].user.keywords).to.deep.equal(keywords) }) }) it('should merge ortb2 correctly for ac and ssps', function () { - setLocalStorage({ + const customTargetingData = { + ...getTargetingData(), '_ppam': [], '_psegs': [], '_pcrprs': ['abc', 'def', 'xyz'], @@ -324,7 +397,10 @@ describe('permutiveRtdProvider', function () { ssps: ['foo', 'bar'], cohorts: ['xyz', 'uvw'], } - }) + } + const segmentsData = transformedTargeting(customTargetingData) + setLocalStorage(customTargetingData) + const moduleConfig = { name: 'permutive', waitForIt: true, @@ -335,7 +411,7 @@ describe('permutiveRtdProvider', function () { } const bidderConfig = {}; - setBidderRtb(bidderConfig, moduleConfig) + setBidderRtb(bidderConfig, moduleConfig, segmentsData) // include both ac and ssp cohorts, as foo is both in ac bidders and ssps const expectedFooTargetingData = [ @@ -370,153 +446,117 @@ describe('permutiveRtdProvider', function () { segment: expectedOtherTargetingData }]) }) - }) - describe('Getting segments', function () { - it('should retrieve segments in the expected structure', function () { - const data = transformedTargeting() - expect(getSegments(250)).to.deep.equal(data) - }) - it('should enforce max segments', function () { - const max = 1 - const segments = getSegments(max) - - for (const key in segments) { - if (key === 'ssp') { - expect(segments[key].cohorts).to.have.length(max) - } else { - expect(segments[key]).to.have.length(max) - } - } - }) - }) + describe('ortb2.user.ext tests', function () { + it('should add nothing if there are no cohorts data', function () { + // Empty module config means we default + const moduleConfig = getConfig() - describe('Default segment targeting', function () { - it('sets segment targeting for Xandr', function () { - const data = transformedTargeting() - const adUnits = getAdUnits() - const config = getConfig() + const bidderConfig = {} - initSegments({ adUnits }, callback, config) + // Passing empty values means there is no segment data + const segmentsData = transformedTargeting({ + _pdfps: [], + _prubicons: [], + _papns: [], + _psegs: [], + _ppam: [], + _pcrprs: [], + _pssps: { ssps: [], cohorts: [] } + }) - function callback () { - adUnits.forEach(adUnit => { - adUnit.bids.forEach(bid => { - const { bidder, params } = bid + setBidderRtb(bidderConfig, moduleConfig, segmentsData) - if (bidder === 'appnexus') { - expect(deepAccess(params, 'keywords.permutive')).to.eql(data.appnexus) - expect(deepAccess(params, 'keywords.p_standard')).to.eql(data.ac.concat(data.ssp.cohorts)) - } - }) + moduleConfig.params.acBidders.forEach(bidder => { + expect(bidderConfig[bidder].user).to.not.have.property('ext') }) - } - }) + }) - it('sets segment targeting for Magnite', function () { - const data = transformedTargeting() - const adUnits = getAdUnits() - const config = getConfig() + it('should add standard and custom cohorts', function () { + const moduleConfig = getConfig() - initSegments({ adUnits }, callback, config) + const bidderConfig = {} - function callback () { - adUnits.forEach(adUnit => { - adUnit.bids.forEach(bid => { - const { bidder, params } = bid + const segmentsData = transformedTargeting() - if (bidder === 'rubicon') { - expect(deepAccess(params, 'visitor.permutive')).to.eql(data.rubicon) - expect(deepAccess(params, 'visitor.p_standard')).to.eql(data.ac.concat(data.ssp.cohorts)) - } - }) - }) - } - }) + setBidderRtb(bidderConfig, moduleConfig, segmentsData) - it('sets segment targeting for Magnite video', function () { - const targetingData = getTargetingData() - targetingData._prubicons.push(321) + moduleConfig.params.acBidders.forEach(bidder => { + const userExtData = { + // Default targeting + p_standard: segmentsData.ac, + } - setLocalStorage(targetingData) + const customCohorts = segmentsData[bidder] || [] + if (customCohorts.length > 0) { + deepSetValue(userExtData, 'permutive', customCohorts) + } - const data = transformedTargeting(targetingData) - const config = getConfig() + expect(bidderConfig[bidder].user.ext.data).to.deep + .eq(userExtData) + }) + }) - const adUnits = getAdUnits().filter(adUnit => adUnit.mediaTypes.video) - expect(adUnits).to.have.lengthOf(1) + it('should add ac cohorts ONLY', function () { + const moduleConfig = getConfig() - initSegments({ adUnits }, callback, config) + const bidderConfig = {} - function callback() { - adUnits.forEach(adUnit => { - adUnit.bids.forEach(bid => { - const { bidder, params } = bid + const segmentsData = transformedTargeting() + moduleConfig.params.acBidders.forEach((bidder) => { + // Remove custom cohorts + delete segmentsData[bidder] + }) - if (bidder === 'rubicon') { - expect( - deepAccess(params, 'visitor.permutive'), - 'Should map all targeting values to a string', - ).to.eql(data.rubicon.map(String)) - expect(deepAccess(params, 'visitor.p_standard')).to.eql(data.ac.concat(data.ssp.cohorts)) - } + setBidderRtb(bidderConfig, moduleConfig, segmentsData) + + moduleConfig.params.acBidders.forEach((bidder) => { + expect(bidderConfig[bidder].user.ext.data).to.deep.equal({ + p_standard: segmentsData.ac }) }) - } - }) + }) - it('sets segment targeting for Ozone', function () { - const data = transformedTargeting() - const adUnits = getAdUnits() - const config = getConfig() + it('should add custom cohorts ONLY', function () { + const moduleConfig = getConfig() - initSegments({ adUnits }, callback, config) + const bidderConfig = {} - function callback () { - adUnits.forEach(adUnit => { - adUnit.bids.forEach(bid => { - const { bidder, params } = bid + const segmentsData = transformedTargeting() + // Empty the AC cohorts + segmentsData['ac'] = [] - if (bidder === 'ozone') { - expect(deepAccess(params, 'customData.0.targeting.p_standard')).to.eql(data.ac.concat(data.ssp.cohorts)) - } - }) + setBidderRtb(bidderConfig, moduleConfig, segmentsData) + + moduleConfig.params.acBidders.forEach(bidder => { + const customCohorts = segmentsData[bidder] || [] + if (customCohorts.length > 0) { + expect(bidderConfig[bidder].user.ext.data).to.deep + .eq({ permutive: customCohorts }) + } else { + expect(bidderConfig[bidder].user).to.not.have.property('ext') + } }) - } + }) }) }) - describe('Custom segment targeting', function () { - it('sets custom segment targeting for Magnite', function () { + describe('Getting segments', function () { + it('should retrieve segments in the expected structure', function () { const data = transformedTargeting() - const adUnits = getAdUnits() - const config = getConfig() + expect(getSegments(250)).to.deep.equal(data) + }) + it('should enforce max segments', function () { + const max = 1 + const segments = getSegments(max) - config.params.overwrites = { - rubicon: function (bid, data, acEnabled, utils, defaultFn) { - if (defaultFn) { - bid = defaultFn(bid, data, acEnabled) - } - if (data.gam && data.gam.length) { - utils.deepSetValue(bid, 'params.visitor.permutive', data.gam) - } + for (const key in segments) { + if (key === 'ssp') { + expect(segments[key].cohorts).to.have.length(max) + } else { + expect(segments[key]).to.have.length(max) } } - - initSegments({ adUnits }, callback, config) - - function callback () { - adUnits.forEach(adUnit => { - adUnit.bids.forEach(bid => { - const { bidder, params } = bid - - if (bidder === 'rubicon') { - expect(deepAccess(params, 'visitor.permutive')).to.eql(data.gam) - expect(deepAccess(params, 'visitor.p_standard')).to.eql(data.ac.concat(data.ssp.cohorts)) - } - }) - }) - } }) }) @@ -525,73 +565,65 @@ describe('permutiveRtdProvider', function () { const adUnits = getAdUnits() const config = getConfig() - initSegments({ adUnits }, callback, config) + readAndSetCohorts({ adUnits }, config) - function callback () { - adUnits.forEach(adUnit => { - adUnit.bids.forEach(bid => { - const { bidder, params } = bid + adUnits.forEach(adUnit => { + adUnit.bids.forEach(bid => { + const { bidder, params } = bid - if (bidder === 'appnexus') { - expect(deepAccess(params, 'keywords.test_kv')).to.eql(['true']) - } - }) + if (bidder === 'appnexus') { + expect(deepAccess(params, 'keywords.test_kv')).to.eql(['true']) + } }) - } + }) }) it('doesn\'t overwrite existing key-values for Magnite', function () { const adUnits = getAdUnits() const config = getConfig() - initSegments({ adUnits }, callback, config) + readAndSetCohorts({ adUnits }, config) - function callback () { - adUnits.forEach(adUnit => { - adUnit.bids.forEach(bid => { - const { bidder, params } = bid + adUnits.forEach(adUnit => { + adUnit.bids.forEach(bid => { + const { bidder, params } = bid - if (bidder === 'rubicon') { - expect(deepAccess(params, 'visitor.test_kv')).to.eql(['true']) - } - }) + if (bidder === 'rubicon') { + expect(deepAccess(params, 'visitor.test_kv')).to.eql(['true']) + } }) - } + }) }) it('doesn\'t overwrite existing key-values for Ozone', function () { const adUnits = getAdUnits() const config = getConfig() - initSegments({ adUnits }, callback, config) + readAndSetCohorts({ adUnits }, config) - function callback () { - adUnits.forEach(adUnit => { - adUnit.bids.forEach(bid => { - const { bidder, params } = bid + adUnits.forEach(adUnit => { + adUnit.bids.forEach(bid => { + const { bidder, params } = bid - if (bidder === 'ozone') { - expect(deepAccess(params, 'customData.0.targeting.test_kv')).to.eql(['true']) - } - }) + if (bidder === 'ozone') { + expect(deepAccess(params, 'customData.0.targeting.test_kv')).to.eql(['true']) + } }) - } + }) }) it('doesn\'t overwrite existing key-values for TrustX', function () { const adUnits = getAdUnits() const config = getConfig() - initSegments({ adUnits }, callback, config) + readAndSetCohorts({ adUnits }, config) - function callback () { - adUnits.forEach(adUnit => { - adUnit.bids.forEach(bid => { - const { bidder, params } = bid + adUnits.forEach(adUnit => { + adUnit.bids.forEach(bid => { + const { bidder, params } = bid - if (bidder === 'trustx') { - expect(deepAccess(params, 'keywords.test_kv')).to.eql(['true']) - } - }) + if (bidder === 'trustx') { + expect(deepAccess(params, 'keywords.test_kv')).to.eql(['true']) + } }) - } + }) }) }) @@ -648,6 +680,7 @@ function transformedTargeting (data = getTargetingData()) { return { ac: [...data._pcrprs, ...data._ppam, ...data._psegs.filter(seg => seg >= 1000000)], appnexus: data._papns, + ix: data._pindexs, rubicon: data._prubicons, gam: data._pdfps, ssp: data._pssps, @@ -661,6 +694,7 @@ function getTargetingData () { _papns: ['appnexus1', 'appnexus2'], _psegs: ['1234', '1000001', '1000002'], _ppam: ['ppam1', 'ppam2'], + _pindexs: ['pindex1', 'pindex2'], _pcrprs: ['pcrprs1', 'pcrprs2', 'dup'], _pssps: { ssps: ['xyz', 'abc', 'dup'], cohorts: ['123', 'abc'] } } diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index 0ce060b9904..895c3754876 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -8,7 +8,7 @@ import { } from 'modules/prebidServerBidAdapter/index.js'; import adapterManager from 'src/adapterManager.js'; import * as utils from 'src/utils.js'; -import {deepAccess, deepClone} from 'src/utils.js'; +import {deepAccess, deepClone, mergeDeep} from 'src/utils.js'; import {ajax} from 'src/ajax.js'; import {config} from 'src/config.js'; import * as events from 'src/events.js'; @@ -25,13 +25,16 @@ import 'modules/priceFloors.js'; import 'modules/consentManagement.js'; import 'modules/consentManagementUsp.js'; import 'modules/schain.js'; +import 'modules/fledgeForGpt.js'; +import * as redactor from 'src/activities/redactor.js'; import {hook} from '../../../src/hook.js'; import {decorateAdUnitsWithNativeParams} from '../../../src/native.js'; import {auctionManager} from '../../../src/auctionManager.js'; import {stubAuctionIndex} from '../../helpers/indexStub.js'; -import {registerBidder} from 'src/adapters/bidderFactory.js'; +import {addComponentAuction, registerBidder} from 'src/adapters/bidderFactory.js'; import {getGlobal} from '../../../src/prebidGlobal.js'; import {syncAddFPDEnrichments, syncAddFPDToBidderRequest} from '../../helpers/fpd.js'; +import {deepSetValue} from '../../../src/utils.js'; let CONFIG = { accountId: '1', @@ -634,16 +637,103 @@ describe('S2S Adapter', function () { resetSyncedStatus(); }); - it('should set id and source.tid to auction ID', function () { - config.setConfig({ s2sConfig: CONFIG }); + describe('FPD redaction', () => { + let sandbox, ortb2Fragments, redactorMocks, s2sReq; - adapter.callBids(OUTSTREAM_VIDEO_REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + beforeEach(() => { + sandbox = sinon.sandbox.create(); + redactorMocks = {}; + sandbox.stub(redactor, 'redactor').callsFake((params) => { + if (!redactorMocks.hasOwnProperty(params.component)) { + redactorMocks[params.component] = { + ortb2: sinon.stub().callsFake(o => o), + bidRequest: sinon.stub().callsFake(o => o) + } + } + return redactorMocks[params.component]; + }) + ortb2Fragments = { + global: { + mock: 'value' + }, + bidder: { + appnexus: { + mock: 'A' + } + } + } + const s2sConfig = { + ...CONFIG, + }; + config.setConfig({s2sConfig}); + s2sReq = { + ...REQUEST, + s2sConfig + }; + }); - const requestBid = JSON.parse(server.requests[0].requestBody); - expect(requestBid.id).to.equal(BID_REQUESTS[0].auctionId); - expect(requestBid.source.tid).to.equal(BID_REQUESTS[0].auctionId); + afterEach(() => { + sandbox.restore(); + }) + + function callBids() { + adapter.callBids({ + ...s2sReq, + ortb2Fragments + }, BID_REQUESTS, addBidResponse, done, ajax); + } + + it('should be applied to ortb2Fragments', () => { + callBids(); + sinon.assert.calledWithMatch(redactorMocks['prebid.pbsBidAdapter'].ortb2, ortb2Fragments.global); + Object.entries(ortb2Fragments.bidder).forEach(([bidder, ortb2]) => { + sinon.assert.calledWith(redactorMocks[`bidder.${bidder}`].ortb2, ortb2); + }); + }); + + it('should be applied to ad units', () => { + callBids(); + s2sReq.ad_units.forEach(au => { + sinon.assert.calledWith(redactorMocks['prebid.pbsBidAdapter'].bidRequest, au); + au.bids.forEach((bid) => { + sinon.assert.calledWith(redactorMocks[`bidder.${bid.bidder}`].bidRequest, bid); + }) + }) + }) }); + describe('transaction IDs', () => { + let s2sReq; + beforeEach(() => { + s2sReq = { + ...REQUEST, + ortb2Fragments: {global: {source: {tid: 'mock-tid'}}}, + ad_units: REQUEST.ad_units.map(au => ({...au, ortb2Imp: {ext: {tid: 'mock-tid'}}})) + }; + BID_REQUESTS[0].bids[0].ortb2Imp = {ext: {tid: 'mock-tid'}}; + }); + + function makeRequest() { + adapter.callBids(s2sReq, BID_REQUESTS, addBidResponse, done, ajax); + return JSON.parse(server.requests[0].requestBody); + } + + it('should not be set when transmitTid is not allowed, with ext.prebid.createtids: false', () => { + config.setConfig({ s2sConfig: CONFIG, enableTIDs: false }); + const req = makeRequest(); + expect(req.source.tid).to.not.exist; + expect(req.imp[0].ext.tid).to.not.exist; + expect(req.ext.prebid.createtids).to.equal(false); + }); + + it('should be picked from FPD otherwise', () => { + config.setConfig({s2sConfig: CONFIG, enableTIDs: true}); + const req = makeRequest(); + expect(req.source.tid).to.eql('mock-tid'); + expect(req.imp[0].ext.tid).to.eql('mock-tid'); + }) + }) + it('should set tmax to s2sConfig.timeout', () => { const cfg = {...CONFIG, timeout: 123}; config.setConfig({s2sConfig: cfg}); @@ -706,51 +796,53 @@ describe('S2S Adapter', function () { expect(server.requests.length).to.equal(0); }); - it('should add outstream bc renderer exists on mediatype', function () { - config.setConfig({ s2sConfig: CONFIG }); + if (FEATURES.VIDEO) { + it('should add outstream bc renderer exists on mediatype', function () { + config.setConfig({ s2sConfig: CONFIG }); - adapter.callBids(OUTSTREAM_VIDEO_REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + adapter.callBids(OUTSTREAM_VIDEO_REQUEST, BID_REQUESTS, addBidResponse, done, ajax); - const requestBid = JSON.parse(server.requests[0].requestBody); - expect(requestBid.imp[0].banner).to.exist; - expect(requestBid.imp[0].video).to.exist; - }); + const requestBid = JSON.parse(server.requests[0].requestBody); + expect(requestBid.imp[0].banner).to.exist; + expect(requestBid.imp[0].video).to.exist; + }); - it('should default video placement if not defined and instream', function () { - let ortb2Config = utils.deepClone(CONFIG); - ortb2Config.endpoint.p1Consent = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; + it('should default video placement if not defined and instream', function () { + let ortb2Config = utils.deepClone(CONFIG); + ortb2Config.endpoint.p1Consent = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; - config.setConfig({ s2sConfig: ortb2Config }); + config.setConfig({ s2sConfig: ortb2Config }); - let videoBid = utils.deepClone(VIDEO_REQUEST); - videoBid.ad_units[0].mediaTypes.video.context = 'instream'; - adapter.callBids(videoBid, BID_REQUESTS, addBidResponse, done, ajax); + let videoBid = utils.deepClone(VIDEO_REQUEST); + videoBid.ad_units[0].mediaTypes.video.context = 'instream'; + adapter.callBids(videoBid, BID_REQUESTS, addBidResponse, done, ajax); - const requestBid = JSON.parse(server.requests[0].requestBody); - expect(requestBid.imp[0].banner).to.not.exist; - expect(requestBid.imp[0].video).to.exist; - expect(requestBid.imp[0].video.placement).to.equal(1); - }); + const requestBid = JSON.parse(server.requests[0].requestBody); + expect(requestBid.imp[0].banner).to.not.exist; + expect(requestBid.imp[0].video).to.exist; + expect(requestBid.imp[0].video.placement).to.equal(1); + }); - it('converts video mediaType properties into openRTB format', function () { - let ortb2Config = utils.deepClone(CONFIG); - ortb2Config.endpoint.p1Consent = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; + it('converts video mediaType properties into openRTB format', function () { + let ortb2Config = utils.deepClone(CONFIG); + ortb2Config.endpoint.p1Consent = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; - config.setConfig({ s2sConfig: ortb2Config }); + config.setConfig({ s2sConfig: ortb2Config }); - let videoBid = utils.deepClone(VIDEO_REQUEST); - videoBid.ad_units[0].mediaTypes.video.context = 'instream'; - adapter.callBids(videoBid, BID_REQUESTS, addBidResponse, done, ajax); + let videoBid = utils.deepClone(VIDEO_REQUEST); + videoBid.ad_units[0].mediaTypes.video.context = 'instream'; + adapter.callBids(videoBid, BID_REQUESTS, addBidResponse, done, ajax); - const requestBid = JSON.parse(server.requests[0].requestBody); - expect(requestBid.imp[0].banner).to.not.exist; - expect(requestBid.imp[0].video).to.exist; - expect(requestBid.imp[0].video.placement).to.equal(1); - expect(requestBid.imp[0].video.w).to.equal(640); - expect(requestBid.imp[0].video.h).to.equal(480); - expect(requestBid.imp[0].video.playerSize).to.be.undefined; - expect(requestBid.imp[0].video.context).to.be.undefined; - }); + const requestBid = JSON.parse(server.requests[0].requestBody); + expect(requestBid.imp[0].banner).to.not.exist; + expect(requestBid.imp[0].video).to.exist; + expect(requestBid.imp[0].video.placement).to.equal(1); + expect(requestBid.imp[0].video.w).to.equal(640); + expect(requestBid.imp[0].video.h).to.equal(480); + expect(requestBid.imp[0].video.playerSize).to.be.undefined; + expect(requestBid.imp[0].video.context).to.be.undefined; + }); + } it('exists and is a function', function () { expect(adapter.callBids).to.exist.and.to.be.a('function'); @@ -994,7 +1086,7 @@ describe('S2S Adapter', function () { w: window.innerWidth, h: window.innerHeight }) - expect(requestBid.app).to.deep.equal({ + sinon.assert.match(requestBid.app, { bundle: 'com.test.app', publisher: { 'id': '1' } }); @@ -1021,7 +1113,7 @@ describe('S2S Adapter', function () { w: window.innerWidth, h: window.innerHeight }) - expect(requestBid.app).to.deep.equal({ + sinon.assert.match(requestBid.app, { bundle: 'com.test.app', publisher: { 'id': '1' } }); @@ -1383,7 +1475,7 @@ describe('S2S Adapter', function () { w: window.innerWidth, h: window.innerHeight }) - expect(requestBid.app).to.deep.equal({ + sinon.assert.match(requestBid.app, { bundle: 'com.test.app', publisher: { 'id': '1' } }); @@ -1401,11 +1493,9 @@ describe('S2S Adapter', function () { const ortbReq = JSON.parse(requestBid.imp[0].native.request); expect(ortbReq).to.deep.equal({ ...ORTB_NATIVE_REQ, - 'context': 1, - 'plcmttype': 1, 'eventtrackers': [{ event: 1, - methods: [1] + methods: [1, 2] }], }); expect(requestBid.imp[0].native.ver).to.equal('1.2'); @@ -1522,7 +1612,7 @@ describe('S2S Adapter', function () { }; config.setConfig(_config); - adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + adapter.callBids(addFpdEnrichmentsToS2SRequest(REQUEST, BID_REQUESTS), BID_REQUESTS, addBidResponse, done, ajax); const requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.site).to.not.exist; expect(requestBid.app).to.exist.and.to.be.a('object'); @@ -1639,19 +1729,17 @@ describe('S2S Adapter', function () { const requestBid = JSON.parse(server.requests[0].requestBody); - expect(requestBid.ext).to.deep.equal({ - prebid: { - auctiontimestamp: 1510852447530, - targeting: { - includebidderkeys: false, - includewinners: true - }, - channel: { - name: 'pbjs', - version: 'v$prebid.version$' - } + sinon.assert.match(requestBid.ext.prebid, { + auctiontimestamp: 1510852447530, + targeting: { + includebidderkeys: false, + includewinners: true + }, + channel: { + name: 'pbjs', + version: 'v$prebid.version$' } - }); + }) }); it('skips dynamic aliases to request when skipPbsAliasing enabled', function () { @@ -1678,17 +1766,15 @@ describe('S2S Adapter', function () { const requestBid = JSON.parse(server.requests[0].requestBody); - expect(requestBid.ext).to.deep.equal({ - prebid: { - auctiontimestamp: 1510852447530, - targeting: { - includebidderkeys: false, - includewinners: true - }, - channel: { - name: 'pbjs', - version: 'v$prebid.version$' - } + sinon.assert.match(requestBid.ext.prebid, { + auctiontimestamp: 1510852447530, + targeting: { + includebidderkeys: false, + includewinners: true + }, + channel: { + name: 'pbjs', + version: 'v$prebid.version$' } }); }); @@ -1732,9 +1818,9 @@ describe('S2S Adapter', function () { const s2sBidRequest = utils.deepClone(REQUEST); cookieSyncConfig.syncEndpoint = { p1Consent: 'https://prebid.adnxs.com/pbs/v1/cookie_sync' }; s2sBidRequest.s2sConfig = cookieSyncConfig; - + config.setConfig({ userSync, s2sConfig: cookieSyncConfig }); - + let bidRequest = utils.deepClone(BID_REQUESTS); adapter.callBids(s2sBidRequest, bidRequest, addBidResponse, done, ajax); return JSON.parse(server.requests[0].requestBody); @@ -1762,7 +1848,7 @@ describe('S2S Adapter', function () { } }); }); - + it('correctly adds filterSettings to the cookie_sync request if userSync.filterSettings is present in the config and only the iframe key is present in userSync.filterSettings', function () { const userSync = { filterSettings: { @@ -1944,7 +2030,7 @@ describe('S2S Adapter', function () { device: device }); - adapter.callBids(s2sBidRequest, BID_REQUESTS, addBidResponse, done, ajax); + adapter.callBids(addFpdEnrichmentsToS2SRequest(s2sBidRequest, BID_REQUESTS), BID_REQUESTS, addBidResponse, done, ajax); const requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.site).to.exist.and.to.be.a('object'); @@ -2459,7 +2545,7 @@ describe('S2S Adapter', function () { }; const bcat = ['IAB25', 'IAB7-39']; const badv = ['blockedAdv-1.com', 'blockedAdv-2.com']; - const allowedBidders = ['rubicon', 'appnexus']; + const allowedBidders = ['appnexus']; const expected = allowedBidders.map(bidder => ({ bidders: [bidder], @@ -2512,6 +2598,101 @@ describe('S2S Adapter', function () { expect(parsedRequestBody.bcat).to.deep.equal(bcat); }); + it('passes first party data in request for unknown when allowUnknownBidderCodes is true', () => { + const cfg = { ...CONFIG, allowUnknownBidderCodes: true }; + config.setConfig({ s2sConfig: cfg }); + + const clonedReq = {...REQUEST, s2sConfig: cfg} + const s2sBidRequest = utils.deepClone(clonedReq); + const bidRequests = utils.deepClone(BID_REQUESTS); + + const commonSite = { + keywords: ['power tools'], + search: 'drill' + }; + const commonUser = { + keywords: ['a', 'b'], + gender: 'M' + }; + + const site = { + content: {userrating: 4}, + ext: { + data: { + pageType: 'article', + category: 'tools' + } + } + }; + const user = { + yob: '1984', + geo: { country: 'ca' }, + ext: { + data: { + registered: true, + interests: ['cars'] + } + } + }; + const bcat = ['IAB25', 'IAB7-39']; + const badv = ['blockedAdv-1.com', 'blockedAdv-2.com']; + const allowedBidders = ['appnexus', 'unknown']; + + const expected = allowedBidders.map(bidder => ({ + bidders: [bidder], + config: { + ortb2: { + site: { + content: { userrating: 4 }, + ext: { + data: { + pageType: 'article', + category: 'tools' + } + } + }, + user: { + yob: '1984', + geo: { country: 'ca' }, + ext: { + data: { + registered: true, + interests: ['cars'] + } + } + }, + bcat: ['IAB25', 'IAB7-39'], + badv: ['blockedAdv-1.com', 'blockedAdv-2.com'] + } + } + })); + const commonContextExpected = utils.mergeDeep({ + 'page': 'http://mytestpage.com', + 'domain': 'mytestpage.com', + 'publisher': { + 'id': '1', + 'domain': 'mytestpage.com' + } + }, commonSite); + + const ortb2Fragments = { + global: {site: commonSite, user: commonUser, badv, bcat}, + bidder: Object.fromEntries(allowedBidders.map(bidder => [bidder, {site, user, bcat, badv}])) + }; + + // adapter.callBids({ ...REQUEST, s2sConfig: cfg }, BID_REQUESTS, addBidResponse, done, ajax); + + adapter.callBids(addFpdEnrichmentsToS2SRequest({...s2sBidRequest, ortb2Fragments}, bidRequests, cfg), bidRequests, addBidResponse, done, ajax); + const parsedRequestBody = JSON.parse(server.requests[0].requestBody); + // eslint-disable-next-line no-console + console.log(parsedRequestBody); + expect(parsedRequestBody.ext.prebid.bidderconfig).to.deep.equal(expected); + expect(parsedRequestBody.site).to.deep.equal(commonContextExpected); + expect(parsedRequestBody.user).to.deep.equal(commonUser); + expect(parsedRequestBody.badv).to.deep.equal(badv); + expect(parsedRequestBody.bcat).to.deep.equal(bcat); + }); + describe('pbAdSlot config', function () { it('should not send \"imp.ext.data.pbadslot\" if \"ortb2Imp.ext\" is undefined', function () { const consentConfig = { s2sConfig: CONFIG }; @@ -2809,28 +2990,30 @@ describe('S2S Adapter', function () { expect(response).to.have.property('dealId', 'test-dealid'); }); - it('should pass through default adserverTargeting if present in bidObject for video request', function () { - config.setConfig({ s2sConfig: CONFIG }); - const cacheResponse = utils.deepClone(RESPONSE_OPENRTB); - const targetingTestData = { - hb_cache_path: '/cache', - hb_cache_host: 'prebid-cache.testurl.com' - }; + if (FEATURES.VIDEO) { + it('should pass through default adserverTargeting if present in bidObject for video request', function () { + config.setConfig({ s2sConfig: CONFIG }); + const cacheResponse = utils.deepClone(RESPONSE_OPENRTB); + const targetingTestData = { + hb_cache_path: '/cache', + hb_cache_host: 'prebid-cache.testurl.com' + }; - cacheResponse.seatbid.forEach(item => { - item.bid[0].ext.prebid.targeting = targetingTestData - }); - adapter.callBids(VIDEO_REQUEST, BID_REQUESTS, addBidResponse, done, ajax); - server.requests[0].respond(200, {}, JSON.stringify(cacheResponse)); + cacheResponse.seatbid.forEach(item => { + item.bid[0].ext.prebid.targeting = targetingTestData + }); + adapter.callBids(VIDEO_REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + server.requests[0].respond(200, {}, JSON.stringify(cacheResponse)); - sinon.assert.calledOnce(addBidResponse); - const response = addBidResponse.firstCall.args[1]; - expect(response).to.have.property('adserverTargeting'); - expect(response.adserverTargeting).to.deep.equal({ - 'hb_cache_path': '/cache', - 'hb_cache_host': 'prebid-cache.testurl.com' + sinon.assert.calledOnce(addBidResponse); + const response = addBidResponse.firstCall.args[1]; + expect(response).to.have.property('adserverTargeting'); + expect(response.adserverTargeting).to.deep.equal({ + 'hb_cache_path': '/cache', + 'hb_cache_host': 'prebid-cache.testurl.com' + }); }); - }); + } it('should set the bidResponse currency to whats in the PBS response', function () { adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); @@ -2928,6 +3111,23 @@ describe('S2S Adapter', function () { expect(response).to.have.property('ttl', 60); }); + it('handles seatnonbid responses and emits SEAT_NON_BID', function () { + const original = CONFIG; + CONFIG.extPrebid = { returnallbidstatus: true }; + const nonbidResponse = {...RESPONSE_OPENRTB, ext: {seatnonbid: [{}]}}; + config.setConfig({ CONFIG }); + CONFIG = original; + adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + const responding = deepClone(nonbidResponse); + Object.assign(responding.ext.seatnonbid, [{auctionId: 2}]) + server.requests[0].respond(200, {}, JSON.stringify(responding)); + const event = events.emit.secondCall.args; + expect(event[0]).to.equal(CONSTANTS.EVENTS.SEAT_NON_BID); + expect(event[1].seatnonbid[0]).to.have.property('auctionId', 2); + expect(event[1].requestedBidders).to.deep.equal(['appnexus']); + expect(event[1].response).to.deep.equal(responding); + }); + it('respects defaultTtl', function () { const s2sConfig = Object.assign({}, CONFIG, { defaultTtl: 30 @@ -2946,60 +3146,62 @@ describe('S2S Adapter', function () { expect(response).to.have.property('ttl', 30); }); - it('handles OpenRTB video responses', function () { - const s2sConfig = Object.assign({}, CONFIG, { - endpoint: { - p1Consent: 'https://prebidserverurl/openrtb2/auction?querystring=param' - } - }); - config.setConfig({ s2sConfig }); + if (FEATURES.VIDEO) { + it('handles OpenRTB video responses', function () { + const s2sConfig = Object.assign({}, CONFIG, { + endpoint: { + p1Consent: 'https://prebidserverurl/openrtb2/auction?querystring=param' + } + }); + config.setConfig({ s2sConfig }); - const s2sVidRequest = utils.deepClone(VIDEO_REQUEST); - s2sVidRequest.s2sConfig = s2sConfig; + const s2sVidRequest = utils.deepClone(VIDEO_REQUEST); + s2sVidRequest.s2sConfig = s2sConfig; - adapter.callBids(s2sVidRequest, BID_REQUESTS, addBidResponse, done, ajax); - server.requests[0].respond(200, {}, JSON.stringify(RESPONSE_OPENRTB_VIDEO)); + adapter.callBids(s2sVidRequest, BID_REQUESTS, addBidResponse, done, ajax); + server.requests[0].respond(200, {}, JSON.stringify(RESPONSE_OPENRTB_VIDEO)); - sinon.assert.calledOnce(addBidResponse); - const response = addBidResponse.firstCall.args[1]; - expect(response).to.have.property('statusMessage', 'Bid available'); - expect(response).to.have.property('vastXml', RESPONSE_OPENRTB_VIDEO.seatbid[0].bid[0].adm); - expect(response).to.have.property('mediaType', 'video'); - expect(response).to.have.property('bidderCode', 'appnexus'); - expect(response).to.have.property('requestId', '123'); - expect(response).to.have.property('cpm', 10); - }); - - it('handles response cache from ext.prebid.cache.vastXml', function () { - const s2sConfig = Object.assign({}, CONFIG, { - endpoint: { - p1Consent: 'https://prebidserverurl/openrtb2/auction?querystring=param' - } + sinon.assert.calledOnce(addBidResponse); + const response = addBidResponse.firstCall.args[1]; + expect(response).to.have.property('statusMessage', 'Bid available'); + expect(response).to.have.property('vastXml', RESPONSE_OPENRTB_VIDEO.seatbid[0].bid[0].adm); + expect(response).to.have.property('mediaType', 'video'); + expect(response).to.have.property('bidderCode', 'appnexus'); + expect(response).to.have.property('requestId', '123'); + expect(response).to.have.property('cpm', 10); }); - config.setConfig({ s2sConfig }); - const cacheResponse = utils.deepClone(RESPONSE_OPENRTB_VIDEO); - cacheResponse.seatbid.forEach(item => { - item.bid[0].ext.prebid.cache = { - vastXml: { - cacheId: 'abcd1234', - url: 'https://prebid-cache.net/cache?uuid=abcd1234' + + it('handles response cache from ext.prebid.cache.vastXml', function () { + const s2sConfig = Object.assign({}, CONFIG, { + endpoint: { + p1Consent: 'https://prebidserverurl/openrtb2/auction?querystring=param' } - } - }); + }); + config.setConfig({ s2sConfig }); + const cacheResponse = utils.deepClone(RESPONSE_OPENRTB_VIDEO); + cacheResponse.seatbid.forEach(item => { + item.bid[0].ext.prebid.cache = { + vastXml: { + cacheId: 'abcd1234', + url: 'https://prebid-cache.net/cache?uuid=abcd1234' + } + } + }); - const s2sVidRequest = utils.deepClone(VIDEO_REQUEST); - s2sVidRequest.s2sConfig = s2sConfig; + const s2sVidRequest = utils.deepClone(VIDEO_REQUEST); + s2sVidRequest.s2sConfig = s2sConfig; - adapter.callBids(s2sVidRequest, BID_REQUESTS, addBidResponse, done, ajax); - server.requests[0].respond(200, {}, JSON.stringify(cacheResponse)); + adapter.callBids(s2sVidRequest, BID_REQUESTS, addBidResponse, done, ajax); + server.requests[0].respond(200, {}, JSON.stringify(cacheResponse)); - sinon.assert.calledOnce(addBidResponse); - const response = addBidResponse.firstCall.args[1]; + sinon.assert.calledOnce(addBidResponse); + const response = addBidResponse.firstCall.args[1]; - expect(response).to.have.property('statusMessage', 'Bid available'); - expect(response).to.have.property('videoCacheKey', 'abcd1234'); - expect(response).to.have.property('vastUrl', 'https://prebid-cache.net/cache?uuid=abcd1234'); - }); + expect(response).to.have.property('statusMessage', 'Bid available'); + expect(response).to.have.property('videoCacheKey', 'abcd1234'); + expect(response).to.have.property('vastUrl', 'https://prebid-cache.net/cache?uuid=abcd1234'); + }); + } it('add adserverTargeting object to bids when ext.prebid.targeting is defined', function () { const s2sConfig = Object.assign({}, CONFIG, { @@ -3018,20 +3220,22 @@ describe('S2S Adapter', function () { item.bid[0].ext.prebid.targeting = targetingTestData }); - const s2sVidRequest = utils.deepClone(VIDEO_REQUEST); - s2sVidRequest.s2sConfig = s2sConfig; + if (FEATURES.VIDEO) { + const s2sVidRequest = utils.deepClone(VIDEO_REQUEST); + s2sVidRequest.s2sConfig = s2sConfig; - adapter.callBids(s2sVidRequest, BID_REQUESTS, addBidResponse, done, ajax); - server.requests[0].respond(200, {}, JSON.stringify(cacheResponse)); + adapter.callBids(s2sVidRequest, BID_REQUESTS, addBidResponse, done, ajax); + server.requests[0].respond(200, {}, JSON.stringify(cacheResponse)); - sinon.assert.calledOnce(addBidResponse); - const response = addBidResponse.firstCall.args[1]; + sinon.assert.calledOnce(addBidResponse); + const response = addBidResponse.firstCall.args[1]; - expect(response).to.have.property('adserverTargeting'); - expect(response.adserverTargeting).to.deep.equal({ - 'hb_cache_path': '/cache', - 'hb_cache_host': 'prebid-cache.testurl.com' - }); + expect(response).to.have.property('adserverTargeting'); + expect(response.adserverTargeting).to.deep.equal({ + 'hb_cache_path': '/cache', + 'hb_cache_host': 'prebid-cache.testurl.com' + }); + } }); it('handles response cache from ext.prebid.targeting', function () { @@ -3050,18 +3254,20 @@ describe('S2S Adapter', function () { } }); - const s2sVidRequest = utils.deepClone(VIDEO_REQUEST); - s2sVidRequest.s2sConfig = s2sConfig; + if (FEATURES.VIDEO) { + const s2sVidRequest = utils.deepClone(VIDEO_REQUEST); + s2sVidRequest.s2sConfig = s2sConfig; - adapter.callBids(s2sVidRequest, BID_REQUESTS, addBidResponse, done, ajax); - server.requests[0].respond(200, {}, JSON.stringify(cacheResponse)); + adapter.callBids(s2sVidRequest, BID_REQUESTS, addBidResponse, done, ajax); + server.requests[0].respond(200, {}, JSON.stringify(cacheResponse)); - sinon.assert.calledOnce(addBidResponse); - const response = addBidResponse.firstCall.args[1]; + sinon.assert.calledOnce(addBidResponse); + const response = addBidResponse.firstCall.args[1]; - expect(response).to.have.property('statusMessage', 'Bid available'); - expect(response).to.have.property('videoCacheKey', 'a5ad3993'); - expect(response).to.have.property('vastUrl', 'https://prebid-cache.net/cache?uuid=a5ad3993'); + expect(response).to.have.property('statusMessage', 'Bid available'); + expect(response).to.have.property('videoCacheKey', 'a5ad3993'); + expect(response).to.have.property('vastUrl', 'https://prebid-cache.net/cache?uuid=a5ad3993'); + } }); it('handles response cache from ext.prebid.targeting with wurl', function () { @@ -3082,15 +3288,18 @@ describe('S2S Adapter', function () { hb_cache_path: '/cache' } }); - const s2sVidRequest = utils.deepClone(VIDEO_REQUEST); - s2sVidRequest.s2sConfig = s2sConfig; - adapter.callBids(s2sVidRequest, BID_REQUESTS, addBidResponse, done, ajax); - server.requests[0].respond(200, {}, JSON.stringify(cacheResponse)); + if (FEATURES.VIDEO) { + const s2sVidRequest = utils.deepClone(VIDEO_REQUEST); + s2sVidRequest.s2sConfig = s2sConfig; - sinon.assert.calledOnce(addBidResponse); - const response = addBidResponse.firstCall.args[1]; - expect(response).to.have.property('pbsBidId', '654321'); + adapter.callBids(s2sVidRequest, BID_REQUESTS, addBidResponse, done, ajax); + server.requests[0].respond(200, {}, JSON.stringify(cacheResponse)); + + sinon.assert.calledOnce(addBidResponse); + const response = addBidResponse.firstCall.args[1]; + expect(response).to.have.property('pbsBidId', '654321'); + } }); it('add request property pbsBidId with ext.prebid.bidid value', function () { @@ -3102,16 +3311,18 @@ describe('S2S Adapter', function () { config.setConfig({ s2sConfig }); const cacheResponse = utils.deepClone(RESPONSE_OPENRTB_VIDEO); - const s2sVidRequest = utils.deepClone(VIDEO_REQUEST); - s2sVidRequest.s2sConfig = s2sConfig; + if (FEATURES.VIDEO) { + const s2sVidRequest = utils.deepClone(VIDEO_REQUEST); + s2sVidRequest.s2sConfig = s2sConfig; - adapter.callBids(s2sVidRequest, BID_REQUESTS, addBidResponse, done, ajax); - server.requests[0].respond(200, {}, JSON.stringify(cacheResponse)); + adapter.callBids(s2sVidRequest, BID_REQUESTS, addBidResponse, done, ajax); + server.requests[0].respond(200, {}, JSON.stringify(cacheResponse)); - sinon.assert.calledOnce(addBidResponse); - const response = addBidResponse.firstCall.args[1]; + sinon.assert.calledOnce(addBidResponse); + const response = addBidResponse.firstCall.args[1]; - expect(response).to.have.property('pbsBidId', '654321'); + expect(response).to.have.property('pbsBidId', '654321'); + } }); if (FEATURES.NATIVE) { @@ -3266,6 +3477,70 @@ describe('S2S Adapter', function () { }); }); }); + describe('when the response contains ext.prebid.fledge', () => { + let fledgeStub, request, bidderRequests; + + function fledgeHook(next, ...args) { + fledgeStub(...args); + } + + before(() => { + addComponentAuction.before(fledgeHook); + }); + + after(() => { + addComponentAuction.getHooks({hook: fledgeHook}).remove(); + }) + + beforeEach(function () { + fledgeStub = sinon.stub(); + config.setConfig({CONFIG}); + request = deepClone(REQUEST); + request.ad_units.forEach(au => deepSetValue(au, 'ortb2Imp.ext.ae', 1)); + bidderRequests = deepClone(BID_REQUESTS); + bidderRequests.forEach(req => req.fledgeEnabled = true); + }); + + const AU = 'div-gpt-ad-1460505748561-0'; + const FLEDGE_RESP = { + ext: { + prebid: { + fledge: { + auctionconfigs: [ + { + impid: AU, + config: { + id: 1 + } + }, + { + impid: AU, + config: { + id: 2 + } + } + ] + } + } + } + } + + it('calls addComponentAuction alongside addBidResponse', function () { + adapter.callBids(request, bidderRequests, addBidResponse, done, ajax); + server.requests[0].respond(200, {}, JSON.stringify(mergeDeep({}, RESPONSE_OPENRTB, FLEDGE_RESP))); + expect(addBidResponse.called).to.be.true; + sinon.assert.calledWith(fledgeStub, AU, {id: 1}); + sinon.assert.calledWith(fledgeStub, AU, {id: 2}); + }); + + it('calls addComponentAuction when there is no bid in the response', () => { + adapter.callBids(request, bidderRequests, addBidResponse, done, ajax); + server.requests[0].respond(200, {}, JSON.stringify(FLEDGE_RESP)); + expect(addBidResponse.called).to.be.false; + sinon.assert.calledWith(fledgeStub, AU, {id: 1}); + sinon.assert.calledWith(fledgeStub, AU, {id: 2}); + }) + }); }); describe('bid won events', function () { diff --git a/test/spec/modules/prebidmanagerAnalyticsAdapter_spec.js b/test/spec/modules/prebidmanagerAnalyticsAdapter_spec.js index 522a78627d7..25834e8574d 100644 --- a/test/spec/modules/prebidmanagerAnalyticsAdapter_spec.js +++ b/test/spec/modules/prebidmanagerAnalyticsAdapter_spec.js @@ -70,7 +70,7 @@ describe('Prebid Manager Analytics Adapter', function () { prebidmanagerAnalytics.flush(); expect(server.requests.length).to.equal(1); - expect(server.requests[0].url).to.equal('https://endpoint.prebidmanager.com/endpoint'); + expect(server.requests[0].url).to.equal('https://endpt.prebidmanager.com/endpoint'); expect(server.requests[0].requestBody.substring(0, 2)).to.equal('1:'); const pmEvents = JSON.parse(server.requests[0].requestBody.substring(2)); diff --git a/test/spec/modules/priceFloors_spec.js b/test/spec/modules/priceFloors_spec.js index f232631d73d..e9ce4c8ccd3 100644 --- a/test/spec/modules/priceFloors_spec.js +++ b/test/spec/modules/priceFloors_spec.js @@ -286,7 +286,7 @@ describe('the price floors module', function () { let myBidRequest = { ...basicBidRequest }; // should take adunit floormin first even if lower - utils.deepSetValue(myBidRequest, 'ortb2Imp.ext.prebid.floorMin', 2.2); + utils.deepSetValue(myBidRequest, 'ortb2Imp.ext.prebid.floors.floorMin', 2.2); expect(getFirstMatchingFloor(inputFloorData, myBidRequest, { mediaType: 'banner', size: '*' })).to.deep.equal({ floorMin: 2.2, floorRuleValue: 1.0, @@ -297,7 +297,7 @@ describe('the price floors module', function () { delete inputFloorData.matchingInputs; // should take adunit floormin if higher - utils.deepSetValue(myBidRequest, 'ortb2Imp.ext.prebid.floorMin', 3.0); + utils.deepSetValue(myBidRequest, 'ortb2Imp.ext.prebid.floors.floorMin', 3.0); expect(getFirstMatchingFloor(inputFloorData, myBidRequest, { mediaType: 'banner', size: '*' })).to.deep.equal({ floorMin: 3.0, floorRuleValue: 1.0, @@ -582,7 +582,7 @@ describe('the price floors module', function () { const validateBidRequests = (getFloorExpected, FloorDataExpected) => { exposedAdUnits.forEach(adUnit => adUnit.bids.forEach(bid => { expect(bid.hasOwnProperty('getFloor')).to.equal(getFloorExpected); - expect(bid.floorData).to.deep.equal(FloorDataExpected); + sinon.assert.match(bid.floorData, FloorDataExpected); })); }; const runStandardAuction = (adUnits = [getAdUnitMock('test_div_1')]) => { @@ -918,16 +918,8 @@ describe('the price floors module', function () { floorProvider: 'floorprovider' }); }); - it('should not overwrite previous data object if the new one is bad', function () { + it('should ignore and reset floor data when provided with invalid data', function () { handleSetFloorsConfig({...basicFloorConfig}); - handleSetFloorsConfig({ - ...basicFloorConfig, - data: undefined - }); - handleSetFloorsConfig({ - ...basicFloorConfig, - data: 5 - }); handleSetFloorsConfig({ ...basicFloorConfig, data: { @@ -937,17 +929,7 @@ describe('the price floors module', function () { } }); runStandardAuction(); - validateBidRequests(true, { - skipped: false, - floorMin: undefined, - modelVersion: 'basic model', - modelWeight: 10, - modelTimestamp: 1606772895, - location: 'setConfig', - skipRate: 0, - fetchStatus: undefined, - floorProvider: undefined - }); + validateBidRequests(false, sinon.match({location: 'noData', skipped: true})); }); it('should dynamically add new schema fileds and functions if added via setConfig', function () { let deviceSpoof; @@ -1835,7 +1817,7 @@ describe('the price floors module', function () { transactionId: 'au', }; beforeEach(function () { - returnedBidResponse = {}; + returnedBidResponse = null; reject = sinon.stub().returns({status: 'rejected'}); indexStub = sinon.stub(auctionManager, 'index'); indexStub.get(() => stubAuctionIndex({adUnits: [adUnit]})); @@ -1867,7 +1849,7 @@ describe('the price floors module', function () { _floorDataForAuction[AUCTION_ID].data.values = { 'banner': 1.0 }; runBidResponse(); expect(reject.calledOnce).to.be.true; - expect(returnedBidResponse.status).to.equal('rejected'); + expect(returnedBidResponse).to.not.exist; }); it('if it finds a rule and does not floor should update the bid accordingly', function () { _floorDataForAuction[AUCTION_ID] = utils.deepClone(basicFloorConfig); diff --git a/test/spec/modules/pubCommonId_spec.js b/test/spec/modules/pubCommonId_spec.js deleted file mode 100644 index a46ff26c4b8..00000000000 --- a/test/spec/modules/pubCommonId_spec.js +++ /dev/null @@ -1,370 +0,0 @@ -import { - requestBidHook, - getCookie, - setCookie, - setConfig, - isPubcidEnabled, - getExpInterval, - initPubcid, - setStorageItem, - getStorageItem, - removeStorageItem, - getPubcidConfig } from 'modules/pubCommonId.js'; -import { getAdUnits } from 'test/fixtures/fixtures.js'; -import * as auctionModule from 'src/auction.js'; -import { registerBidder } from 'src/adapters/bidderFactory.js'; -import * as utils from 'src/utils.js'; - -let events = require('src/events'); -let constants = require('src/constants.json'); - -var assert = require('chai').assert; -var expect = require('chai').expect; - -const ID_NAME = '_pubcid'; -const EXP = '_exp'; -const TIMEOUT = 2000; - -const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89a-f][0-9a-f]{3}-[0-9a-f]{12}$/; - -function cleanUp() { - window.document.cookie = ID_NAME + '=; expires=Thu, 01 Jan 1970 00:00:01 GMT;'; - localStorage.removeItem(ID_NAME); - localStorage.removeItem(ID_NAME + EXP); -} - -describe('Publisher Common ID', function () { - afterEach(function () { - $$PREBID_GLOBAL$$.requestBids.removeAll(); - }); - describe('Decorate adUnits', function () { - beforeEach(function() { - cleanUp(); - }); - afterEach(function() { - cleanUp(); - }); - - it('Check same cookie', function () { - let adUnits1 = getAdUnits(); - let adUnits2 = getAdUnits(); - let innerAdUnits1; - let innerAdUnits2; - let pubcid; - - expect(getCookie(ID_NAME)).to.be.null; // there should be no cookie initially - expect(localStorage.getItem(ID_NAME)).to.be.null; // there should be no local storage item either - - requestBidHook((config) => { innerAdUnits1 = config.adUnits }, {adUnits: adUnits1}); - pubcid = localStorage.getItem(ID_NAME); // local storage item is created after requestbidHook - - innerAdUnits1.forEach((unit) => { - unit.bids.forEach((bid) => { - expect(bid).to.have.deep.nested.property('crumbs.pubcid'); - expect(bid.crumbs.pubcid).to.equal(pubcid); - }); - }); - - // verify cookie is null - expect(getCookie(ID_NAME)).to.be.null; - - // verify same pubcid is preserved - requestBidHook((config) => { innerAdUnits2 = config.adUnits }, {adUnits: adUnits2}); - assert.deepEqual(innerAdUnits1, innerAdUnits2); - }); - - it('Check different cookies', function () { - let adUnits1 = getAdUnits(); - let adUnits2 = getAdUnits(); - let innerAdUnits1; - let innerAdUnits2; - let pubcid1; - let pubcid2; - - requestBidHook((config) => { innerAdUnits1 = config.adUnits }, {adUnits: adUnits1}); - pubcid1 = localStorage.getItem(ID_NAME); // get first pubcid - removeStorageItem(ID_NAME); // remove storage - - expect(pubcid1).to.not.be.null; - - innerAdUnits1.forEach((unit) => { - unit.bids.forEach((bid) => { - expect(bid).to.have.deep.nested.property('crumbs.pubcid'); - expect(bid.crumbs.pubcid).to.equal(pubcid1); - }); - }); - - requestBidHook((config) => { innerAdUnits2 = config.adUnits }, {adUnits: adUnits2}); - pubcid2 = localStorage.getItem(ID_NAME); // get second pubcid - - innerAdUnits2.forEach((unit) => { - unit.bids.forEach((bid) => { - expect(bid).to.have.deep.nested.property('crumbs.pubcid'); - expect(bid.crumbs.pubcid).to.equal(pubcid2); - }); - }); - - expect(pubcid2).to.not.be.null; - expect(pubcid1).to.not.equal(pubcid2); - }); - - it('Check new cookie', function () { - let adUnits = getAdUnits(); - let innerAdUnits; - let pubcid = utils.generateUUID(); - - setCookie(ID_NAME, pubcid, 600); - requestBidHook((config) => { innerAdUnits = config.adUnits }, {adUnits}); - innerAdUnits.forEach((unit) => { - unit.bids.forEach((bid) => { - expect(bid).to.have.deep.nested.property('crumbs.pubcid'); - expect(bid.crumbs.pubcid).to.equal(pubcid); - }); - }); - }); - - it('Replicate cookie to storage', function() { - let adUnits = getAdUnits(); - let innerAdUnits; - let pubcid = utils.generateUUID(); - - setCookie(ID_NAME, pubcid, 600); - requestBidHook((config) => { innerAdUnits = config.adUnits }, {adUnits}); - - expect(getStorageItem(ID_NAME)).to.equal(pubcid); - }); - - it('Does not replicate storage to cookie', function() { - let adUnits = getAdUnits(); - let innerAdUnits; - let pubcid = utils.generateUUID(); - - setStorageItem(ID_NAME, pubcid, 600); - requestBidHook((config) => { innerAdUnits = config.adUnits }, {adUnits}); - - expect(getCookie(ID_NAME)).to.be.null; - }); - - it('Cookie only', function() { - setConfig({type: 'cookie'}); - let adUnits = getAdUnits(); - let innerAdUnits; - - requestBidHook((config) => { innerAdUnits = config.adUnits }, {adUnits}); - - expect(getCookie(ID_NAME)).to.match(uuidPattern); - expect(getStorageItem(ID_NAME)).to.be.null; - }); - - it('Storage only', function() { - setConfig({type: 'html5'}); - let adUnits = getAdUnits(); - let innerAdUnits; - - requestBidHook((config) => { innerAdUnits = config.adUnits }, {adUnits}); - - expect(getCookie(ID_NAME)).to.be.null; - expect(getStorageItem(ID_NAME)).to.match(uuidPattern); - }); - - it('Bad id recovery', function() { - let adUnits = getAdUnits(); - let innerAdUnits; - - setStorageItem(ID_NAME, 'undefined', 600); - requestBidHook((config) => { innerAdUnits = config.adUnits }, {adUnits}); - - expect(getStorageItem(ID_NAME)).to.match(uuidPattern); - }); - }); - - describe('Configuration', function () { - beforeEach(() => { - setConfig(); - cleanUp(); - }); - afterEach(() => { - setConfig(); - cleanUp(); - }); - - it('empty config', function () { - // this should work as usual - setConfig({}); - let adUnits = getAdUnits(); - let innerAdUnits; - requestBidHook((config) => { innerAdUnits = config.adUnits }, {adUnits}); - let pubcid = localStorage.getItem(ID_NAME); - innerAdUnits.forEach((unit) => { - unit.bids.forEach((bid) => { - expect(bid).to.have.deep.nested.property('crumbs.pubcid'); - expect(bid.crumbs.pubcid).to.equal(pubcid); - }); - }); - }); - - it('disable', function () { - setConfig({enable: false}); - let adUnits = getAdUnits(); - let unmodified = getAdUnits(); - let innerAdUnits; - expect(isPubcidEnabled()).to.be.false; - requestBidHook((config) => { innerAdUnits = config.adUnits }, {adUnits}); - expect(getCookie(ID_NAME)).to.be.null; - assert.deepEqual(innerAdUnits, unmodified); - setConfig({enable: true}); // reset - requestBidHook((config) => { innerAdUnits = config.adUnits }, {adUnits}); - innerAdUnits.forEach((unit) => { - unit.bids.forEach((bid) => { - expect(bid).to.have.deep.nested.property('crumbs.pubcid'); - }); - }); - }); - - it('change expiration time', function () { - setConfig({expInterval: 100}); - expect(getExpInterval()).to.equal(100); - let adUnits = getAdUnits(); - let innerAdUnits; - requestBidHook((config) => { innerAdUnits = config.adUnits }, {adUnits}); - innerAdUnits.every((unit) => { - unit.bids.forEach((bid) => { - expect(bid).to.have.deep.nested.property('crumbs.pubcid'); - }); - }); - }); - - it.skip('disable auto create', function() { - setConfig({ - create: false - }); - - const config = getPubcidConfig(); - expect(config.create).to.be.false; - expect(config.typeEnabled).to.equal('html5'); - - let adUnits = getAdUnits(); - let innerAdUnits; - requestBidHook((config) => { innerAdUnits = config.adUnits }, {adUnits}); - - const pubcid = localStorage.getItem(ID_NAME); - expect(pubcid).to.be.null; - }); - }); - - describe('Invoking requestBid', function () { - let createAuctionStub; - let adUnits; - let adUnitCodes; - let capturedReqs; - let sampleSpec = { - code: 'sampleBidder', - isBidRequestValid: () => {}, - buildRequest: (reqs) => {}, - interpretResponse: () => {}, - getUserSyncs: () => {} - }; - - beforeEach(function () { - adUnits = [{ - code: 'adUnit-code', - mediaTypes: { - banner: {}, - native: {}, - }, - sizes: [[300, 200], [300, 600]], - bids: [ - {bidder: 'sampleBidder', params: {placementId: 'banner-only-bidder'}} - ] - }]; - adUnitCodes = ['adUnit-code']; - let auction = auctionModule.newAuction({adUnits, adUnitCodes, callback: function() {}, cbTimeout: TIMEOUT}); - createAuctionStub = sinon.stub(auctionModule, 'newAuction'); - createAuctionStub.returns(auction); - initPubcid(); - registerBidder(sampleSpec); - }); - - afterEach(function () { - auctionModule.newAuction.restore(); - }); - - it('test hook', function() { - $$PREBID_GLOBAL$$.requestBids({adUnits}); - adUnits.forEach((unit) => { - unit.bids.forEach((bid) => { - expect(bid).to.have.deep.nested.property('crumbs.pubcid'); - }); - }); - }); - }); - - describe('Storage item functions', () => { - beforeEach(() => { cleanUp(); }); - afterEach(() => { cleanUp(); }); - - it('Test set', () => { - const key = ID_NAME; - const val = 'test-set-value'; - // Set item in localStorage - const now = Date.now(); - setStorageItem(key, val, 100); - // Check both item and expiry time are stored - const expVal = localStorage.getItem(key + EXP); - const storedVal = localStorage.getItem(key); - // Verify expiry - expect(expVal).to.not.be.null; - const expDate = new Date(expVal); - expect((expDate.getTime() - now) / 1000).to.be.closeTo(100 * 60, 5); - // Verify value - expect(storedVal).to.equal(val); - }); - - it('Test get and remove', () => { - const key = ID_NAME; - const val = 'test-get-remove'; - setStorageItem(key, val, 10); - expect(getStorageItem(key)).to.equal(val); - removeStorageItem(key); - expect(getStorageItem(key)).to.be.null; - }); - - it('Test expiry', () => { - const key = ID_NAME; - const val = 'test-expiry'; - setStorageItem(key, val, -1); - expect(localStorage.getItem(key)).to.equal(val); - expect(getStorageItem(key)).to.be.null; - expect(localStorage.getItem(key)).to.be.null; - }); - }); - - describe('event callback', () => { - beforeEach(() => { - setConfig(); - cleanUp(); - sinon.stub(events, 'getEvents').returns([]); - sinon.stub(utils, 'triggerPixel'); - }); - afterEach(() => { - setConfig(); - cleanUp(); - events.getEvents.restore(); - utils.triggerPixel.restore(); - }); - it('auction end trigger', () => { - setConfig({ - pixelUrl: '/any/url' - }); - - let adUnits = getAdUnits(); - let innerAdUnits; - requestBidHook((config) => { innerAdUnits = config.adUnits }, {adUnits}); - - expect(utils.triggerPixel.called).to.be.false; - events.emit(constants.EVENTS.AUCTION_END, {}); - expect(utils.triggerPixel.called).to.be.true; - expect(utils.triggerPixel.getCall(0).args[0]).to.include('/any/url'); - }); - }); -}); diff --git a/test/spec/modules/pubgeniusBidAdapter_spec.js b/test/spec/modules/pubgeniusBidAdapter_spec.js index 1d1b0e65ec8..86c8794dc4c 100644 --- a/test/spec/modules/pubgeniusBidAdapter_spec.js +++ b/test/spec/modules/pubgeniusBidAdapter_spec.js @@ -5,6 +5,7 @@ import { config } from 'src/config.js'; import { VIDEO } from 'src/mediaTypes.js'; import { deepClone, parseQueryStringParameters } from 'src/utils.js'; import { server } from 'test/mocks/xhr.js'; +import * as utils from 'src/utils.js'; const { code, @@ -175,7 +176,7 @@ describe('pubGENIUS adapter', () => { method: 'POST', url: 'https://auction.adpearl.io/prebid/auction', data: { - id: 'fake-auction-id', + id: 'fakebidderrequestid', imp: [ { id: 'fakebidid', diff --git a/test/spec/modules/publinkIdSystem_spec.js b/test/spec/modules/publinkIdSystem_spec.js index 4656afe1585..7d98b724bd8 100644 --- a/test/spec/modules/publinkIdSystem_spec.js +++ b/test/spec/modules/publinkIdSystem_spec.js @@ -1,11 +1,12 @@ import {publinkIdSubmodule} from 'modules/publinkIdSystem.js'; -import {getStorageManager} from '../../../src/storageManager'; +import {getCoreStorageManager, getStorageManager} from '../../../src/storageManager'; import {server} from 'test/mocks/xhr.js'; import sinon from 'sinon'; import {uspDataHandler} from '../../../src/adapterManager'; import {parseUrl} from '../../../src/utils'; -export const storage = getStorageManager({gvlid: 24}); +const storage = getCoreStorageManager(); + const TEST_COOKIE_VALUE = 'cookievalue'; describe('PublinkIdSystem', () => { describe('decode', () => { diff --git a/test/spec/modules/pubmaticAnalyticsAdapter_spec.js b/test/spec/modules/pubmaticAnalyticsAdapter_spec.js index 4ad048fef9a..c56ed565c43 100755 --- a/test/spec/modules/pubmaticAnalyticsAdapter_spec.js +++ b/test/spec/modules/pubmaticAnalyticsAdapter_spec.js @@ -1,4 +1,4 @@ -import pubmaticAnalyticsAdapter from 'modules/pubmaticAnalyticsAdapter.js'; +import pubmaticAnalyticsAdapter, { getMetadata } from 'modules/pubmaticAnalyticsAdapter.js'; import adapterManager from 'src/adapterManager.js'; import CONSTANTS from 'src/constants.json'; import { config } from 'src/config.js'; @@ -371,7 +371,8 @@ describe('pubmatic analytics adapter', function () { expect(data.s[0].fskp).to.equal(0); expect(data.s[0].sz).to.deep.equal(['640x480']); expect(data.s[0].ps).to.be.an('array'); - expect(data.s[0].ps.length).to.equal(1); + expect(data.s[0].au).to.equal('/19968336/header-bid-tag-0'); + expect(data.s[0].ps.length).to.equal(1); expect(data.s[0].ps[0].pn).to.equal('pubmatic'); expect(data.s[0].ps[0].bc).to.equal('pubmatic'); expect(data.s[0].ps[0].bidid).to.equal('2ecff0db240757'); @@ -442,6 +443,13 @@ describe('pubmatic analytics adapter', function () { expect(data.eg).to.equal('1.23'); expect(data.en).to.equal('1.23'); expect(data.piid).to.equal('partnerImpressionID-1'); + expect(data.plt).to.equal('1'); + expect(data.psz).to.equal('640x480'); + expect(data.tgid).to.equal('15'); + expect(data.orig).to.equal('www.test.com'); + expect(data.ss).to.equal('1'); + expect(data.fskp).to.equal('0'); + expect(data.af).to.equal('video'); }); it('bidCpmAdjustment: USD: Logger: best case + win tracker', function() { @@ -481,6 +489,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[0].fskp).to.equal(0); expect(data.s[0].sz).to.deep.equal(['640x480']); expect(data.s[0].ps).to.be.an('array'); + expect(data.s[0].au).to.equal('/19968336/header-bid-tag-0'); expect(data.s[0].ps.length).to.equal(1); expect(data.s[0].ps[0].pn).to.equal('pubmatic'); expect(data.s[0].ps[0].bc).to.equal('pubmatic'); @@ -553,6 +562,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[0].sz).to.deep.equal(['640x480']); expect(data.s[0].ps).to.be.an('array'); expect(data.s[0].ps.length).to.equal(1); + expect(data.s[0].au).to.equal('/19968336/header-bid-tag-0'); expect(data.s[0].ps[0].pn).to.equal('pubmatic'); expect(data.s[0].ps[0].bc).to.equal('pubmatic'); expect(data.s[0].ps[0].bidid).to.equal('2ecff0db240757'); @@ -1029,6 +1039,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[0].fskp).to.equal(0); expect(data.s[0].sz).to.deep.equal(['640x480']); expect(data.s[0].ps).to.be.an('array'); + expect(data.s[0].au).to.equal('/19968336/header-bid-tag-0'); expect(data.s[0].ps.length).to.equal(1); expect(data.s[0].ps[0].pn).to.equal('pubmatic'); expect(data.s[0].ps[0].bc).to.equal('pubmatic_alias'); @@ -1147,6 +1158,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[0].fskp).to.equal(0); expect(data.s[0].sz).to.deep.equal(['640x480']); expect(data.s[0].ps).to.be.an('array'); + expect(data.s[0].au).to.equal('/19968336/header-bid-tag-0'); expect(data.s[0].ps.length).to.equal(1); expect(data.s[0].ps[0].pn).to.equal('pubmatic'); expect(data.s[0].ps[0].bc).to.equal('groupm'); @@ -1219,4 +1231,59 @@ describe('pubmatic analytics adapter', function () { expect(data.piid).to.equal('partnerImpressionID-1'); }); }); + + describe('Get Metadata function', function () { + it('should get the metadata object', function () { + const meta = { + networkId: 'nwid', + advertiserId: 'adid', + networkName: 'nwnm', + primaryCatId: 'pcid', + advertiserName: 'adnm', + agencyId: 'agid', + agencyName: 'agnm', + brandId: 'brid', + brandName: 'brnm', + dchain: 'dc', + demandSource: 'ds', + secondaryCatIds: ['secondaryCatIds'] + }; + const metadataObj = getMetadata(meta); + + expect(metadataObj.nwid).to.equal('nwid'); + expect(metadataObj.adid).to.equal('adid'); + expect(metadataObj.nwnm).to.equal('nwnm'); + expect(metadataObj.pcid).to.equal('pcid'); + expect(metadataObj.adnm).to.equal('adnm'); + expect(metadataObj.agid).to.equal('agid'); + expect(metadataObj.agnm).to.equal('agnm'); + expect(metadataObj.brid).to.equal('brid'); + expect(metadataObj.brnm).to.equal('brnm'); + expect(metadataObj.dc).to.equal('dc'); + expect(metadataObj.ds).to.equal('ds'); + expect(metadataObj.scids).to.be.an('array').with.length.above(0); + expect(metadataObj.scids[0]).to.equal('secondaryCatIds'); + }); + + it('should return undefined if meta is null', function () { + const meta = null; + const metadataObj = getMetadata(meta); + expect(metadataObj).to.equal(undefined); + }); + + it('should return undefined if meta is a empty object', function () { + const meta = {}; + const metadataObj = getMetadata(meta); + expect(metadataObj).to.equal(undefined); + }); + + it('should return undefined if meta object has different properties', function () { + const meta = { + a: 123, + b: 456 + }; + const metadataObj = getMetadata(meta); + expect(metadataObj).to.equal(undefined); + }); + }); }); diff --git a/test/spec/modules/pubmaticBidAdapter_spec.js b/test/spec/modules/pubmaticBidAdapter_spec.js index b6f719ae147..53f5e12aa6d 100644 --- a/test/spec/modules/pubmaticBidAdapter_spec.js +++ b/test/spec/modules/pubmaticBidAdapter_spec.js @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { spec, checkVideoPlacement, _getDomainFromURL, assignDealTier } from 'modules/pubmaticBidAdapter.js'; +import { spec, checkVideoPlacement, _getDomainFromURL, assignDealTier, prepareMetaObject } from 'modules/pubmaticBidAdapter.js'; import * as utils from 'src/utils.js'; import { config } from 'src/config.js'; import { createEidsArray } from 'modules/userId/eids.js'; @@ -79,7 +79,11 @@ describe('PubMatic adapter', function () { bidId: '23acc48ad47af5', requestId: '0fb4905b-9456-4152-86be-c6f6d259ba99', bidderRequestId: '1c56ad30b9b8ca8', - transactionId: '92489f71-1bf2-49a0-adf9-000cea934729', + ortb2Imp: { + ext: { + tid: '92489f71-1bf2-49a0-adf9-000cea934729', + } + }, schain: schainConfig } ]; @@ -111,6 +115,7 @@ describe('PubMatic adapter', function () { battr: [13, 14], linearity: 1, placement: 2, + plcmt: 1, minbitrate: 10, maxbitrate: 10 } @@ -162,6 +167,7 @@ describe('PubMatic adapter', function () { battr: [13, 14], linearity: 1, placement: 2, + plcmt: 1, minbitrate: 100, maxbitrate: 4096 } @@ -194,6 +200,14 @@ describe('PubMatic adapter', function () { image: { required: true, sizes: [300, 250] }, sponsoredBy: { required: true } }, + nativeOrtbRequest: { + ver: '1.2', + assets: [ + { id: 0, required: 1, title: {len: 140} }, + { id: 1, required: 1, img: { type: 3, w: 300, h: 250 } }, + { id: 2, required: 1, data: { type: 1 } } + ] + }, bidder: 'pubmatic', params: { publisherId: '5670', @@ -243,6 +257,22 @@ describe('PubMatic adapter', function () { desc2: {required: true, len: 10, ext: {'desc21': 'desc22'}}, displayurl: {required: true, len: 10, ext: {'displayurl1': 'displayurl2'}} }, + nativeOrtbRequest: { + 'ver': '1.2', + 'assets': [ + {'id': 0, 'required': 1, 'title': {'len': 80}}, + {'id': 1, 'required': 1, 'img': {'type': 1, 'w': 50, 'h': 50}}, + {'id': 2, 'required': 1, 'img': {'type': 3, 'w': 728, 'h': 90}}, + {'id': 3, 'required': 1, 'data': {'type': 1, 'len': 10}}, + {'id': 4, 'required': 1, 'data': {'type': 2, 'len': 10}}, + {'id': 5, 'required': 1, 'data': {'type': 3, 'len': 10}}, + {'id': 6, 'required': 1, 'data': {'type': 4, 'len': 10}}, + {'id': 7, 'required': 1, 'data': {'type': 5, 'len': 10}}, + {'id': 8, 'required': 1, 'data': {'type': 6, 'len': 10}}, + {'id': 9, 'required': 1, 'data': {'type': 8, 'len': 10}}, + {'id': 10, 'required': 1, 'data': {'type': 9, 'len': 10}} + ] + }, bidder: 'pubmatic', params: { publisherId: '5670', @@ -301,6 +331,14 @@ describe('PubMatic adapter', function () { image: { required: false, sizes: [300, 250] }, sponsoredBy: { required: true } }, + nativeOrtbRequest: { + ver: '1.2', + assets: [ + { id: 0, required: 0, title: {len: 140} }, + { id: 1, required: 0, img: {type: 3, w: 300, h: 250} }, + { id: 2, required: 1, data: {type: 1} } + ] + }, bidder: 'pubmatic', params: { publisherId: '5670', @@ -350,6 +388,7 @@ describe('PubMatic adapter', function () { battr: [13, 14], linearity: 1, placement: 2, + plcmt: 1, minbitrate: 100, maxbitrate: 4096 } @@ -389,6 +428,14 @@ describe('PubMatic adapter', function () { image: { required: true, sizes: [300, 250] }, sponsoredBy: { required: true } }, + nativeOrtbRequest: { + ver: '1.2', + assets: [ + {id: 0, required: 1, title: {len: 140}}, + {id: 1, required: 1, img: {type: 3, w: 300, h: 250}}, + {id: 2, required: 1, data: {type: 1}} + ] + }, bidder: 'pubmatic', params: { publisherId: '301', @@ -442,6 +489,14 @@ describe('PubMatic adapter', function () { image: { required: true, sizes: [300, 250] }, sponsoredBy: { required: true } }, + nativeOrtbRequest: { + ver: '1.2', + assets: [ + { id: 0, required: 1, title: {len: 140} }, + { id: 1, required: 1, img: { type: 3, w: 300, h: 250 } }, + { id: 2, required: 1, data: { type: 1 } } + ] + }, bidder: 'pubmatic', params: { publisherId: '301', @@ -460,6 +515,7 @@ describe('PubMatic adapter', function () { battr: [13, 14], linearity: 1, placement: 2, + plcmt: 1, minbitrate: 100, maxbitrate: 4096 } @@ -503,6 +559,14 @@ describe('PubMatic adapter', function () { image: { required: true, sizes: [300, 250] }, sponsoredBy: { required: true } }, + nativeOrtbRequest: { + ver: '1.2', + assets: [ + { id: 0, required: 1, title: {len: 80} }, + { id: 1, required: 1, img: { type: 3, w: 300, h: 250 } }, + { id: 2, required: 1, data: { type: 1 } } + ] + }, bidder: 'pubmatic', params: { publisherId: '301', @@ -521,6 +585,7 @@ describe('PubMatic adapter', function () { battr: [13, 14], linearity: 1, placement: 2, + plcmt: 1, minbitrate: 100, maxbitrate: 4096 } @@ -553,7 +618,8 @@ describe('PubMatic adapter', function () { 'ext': { 'deal_channel': 6, 'advid': 976, - 'dspid': 123 + 'dspid': 123, + 'dchain': 'dchain' } }] }, { @@ -602,7 +668,7 @@ describe('PubMatic adapter', function () { validnativeBidImpression = { 'native': { - 'request': '{"assets":[{"id":1,"required":1,"title":{"len":80}},{"id":2,"required":1,"img":{"type":3,"w":300,"h":250}},{"id":4,"required":1,"data":{"type":1}}]}' + 'request': '{"ver":"1.2","assets":[{"id":0,"required":1,"title":{"len":80}},{"id":1,"required":1,"img":{"type":3,"w":300,"h":250}},{"id":2,"required":1,"data":{"type":1}}]}' } } @@ -614,13 +680,13 @@ describe('PubMatic adapter', function () { validnativeBidImpressionWithRequiredParam = { 'native': { - 'request': '{"assets":[{"id":1,"required":0,"title":{"len":80}},{"id":2,"required":0,"img":{"type":3,"w":300,"h":250}},{"id":4,"required":1,"data":{"type":1}}]}' + 'request': '{"ver":"1.2","assets":[{"id":0,"required":0,"title":{"len":80}},{"id":1,"required":0,"img":{"type":3,"w":300,"h":250}},{"id":2,"required":1,"data":{"type":1}}]}' } } validnativeBidImpressionWithAllParams = { native: { - 'request': '{"assets":[{"id":1,"required":1,"title":{"len":80,"ext":{"title1":"title2"}}},{"id":3,"required":1,"img":{"type":1,"w":50,"h":50}},{"id":2,"required":1,"img":{"type":3,"w":728,"h":90,"mimes":["image/png","image/gif"],"ext":{"image1":"image2"}}},{"id":4,"required":1,"data":{"type":1,"len":10,"ext":{"sponsor1":"sponsor2"}}},{"id":5,"required":1,"data":{"type":2,"len":10,"ext":{"body1":"body2"}}},{"id":13,"required":1,"data":{"type":3,"len":10,"ext":{"rating1":"rating2"}}},{"id":14,"required":1,"data":{"type":4,"len":10,"ext":{"likes1":"likes2"}}},{"id":15,"required":1,"data":{"type":5,"len":10,"ext":{"downloads1":"downloads2"}}},{"id":16,"required":1,"data":{"type":6,"len":10,"ext":{"price1":"price2"}}},{"id":17,"required":1,"data":{"type":7,"len":10,"ext":{"saleprice1":"saleprice2"}}},{"id":18,"required":1,"data":{"type":8,"len":10,"ext":{"phone1":"phone2"}}},{"id":19,"required":1,"data":{"type":9,"len":10,"ext":{"address1":"address2"}}},{"id":20,"required":1,"data":{"type":10,"len":10,"ext":{"desc21":"desc22"}}},{"id":21,"required":1,"data":{"type":11,"len":10,"ext":{"displayurl1":"displayurl2"}}}]}' + 'request': '{"ver":"1.2","assets":[{"id":0,"required":1,"title":{"len":80,"ext":{"title1":"title2"}}},{"id":1,"required":1,"img":{"type":1,"w":50,"h":50,"ext":{"icon1":"icon2"}}},{"id":2,"required":1,"img":{"type":3,"w":728,"h":90,"ext":{"image1":"image2"},"mimes":["image/png","image/gif"]}},{"id":3,"required":1,"data":{"type":1,"len":10,"ext":{"sponsor1":"sponsor2"}}},{"id":4,"required":1,"data":{"type":2,"len":10,"ext":{"body1":"body2"}}},{"id":5,"required":1,"data":{"type":3,"len":10,"ext":{"rating1":"rating2"}}},{"id":6,"required":1,"data":{"type":4,"len":10,"ext":{"likes1":"likes2"}}},{"id":7,"required":1,"data":{"type":5,"len":10,"ext":{"downloads1":"downloads2"}}},{"id":8,"required":1,"data":{"type":6,"len":10,"ext":{"price1":"price2"}}},{"id":9,"required":1,"data":{"type":7,"len":10,"ext":{"saleprice1":"saleprice2"}}},{"id":10,"required":1,"data":{"type":8,"len":10,"ext":{"phone1":"phone2"}}},{"id":11,"required":1,"data":{"type":9,"len":10,"ext":{"address1":"address2"}}},{"id":12,"required":1,"data":{"type":10,"len":10,"ext":{"desc21":"desc22"}}},{"id":13,"required":1,"data":{"type":11,"len":10,"ext":{"displayurl1":"displayurl2"}}}]}' } } @@ -793,12 +859,86 @@ describe('PubMatic adapter', function () { expect(isValid).to.equal(true); }); - it('should check for context if video is present', function() { - let bid = { + if (FEATURES.VIDEO) { + it('should check for context if video is present', function() { + let bid = { + 'bidder': 'pubmatic', + 'params': { + 'adSlot': 'SLOT_NHB1@728x90', + 'publisherId': '5890' + }, + 'mediaTypes': { + 'video': { + 'playerSize': [ + [640, 480] + ], + 'protocols': [1, 2, 5], + 'context': 'instream', + 'mimes': ['video/flv'], + 'skippable': false, + 'skip': 1, + 'linearity': 2 + } + }, + 'adUnitCode': 'video1', + 'transactionId': '803e3750-0bbe-4ffe-a548-b6eca15087bf', + 'sizes': [ + [640, 480] + ], + 'bidId': '2c95df014cfe97', + 'bidderRequestId': '1fe59391566442', + 'auctionId': '3a4118ef-fb96-4416-b0b0-3cfc1cebc142', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 + }, + isValid = spec.isBidRequestValid(bid); + expect(isValid).to.equal(true); + }) + + it('should return false if context is not present in video', function() { + let bid = { + 'bidder': 'pubmatic', + 'params': { + 'adSlot': 'SLOT_NHB1@728x90', + 'publisherId': '5890' + }, + 'mediaTypes': { + 'video': { + 'w': 640, + 'h': 480, + 'protocols': [1, 2, 5], + 'mimes': ['video/flv'], + 'skippable': false, + 'skip': 1, + 'linearity': 2 + } + }, + 'adUnitCode': 'video1', + 'transactionId': '803e3750-0bbe-4ffe-a548-b6eca15087bf', + 'sizes': [ + [640, 480] + ], + 'bidId': '2c95df014cfe97', + 'bidderRequestId': '1fe59391566442', + 'auctionId': '3a4118ef-fb96-4416-b0b0-3cfc1cebc142', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 + }, + isValid = spec.isBidRequestValid(bid); + expect(isValid).to.equal(false); + }) + + it('bid.mediaTypes.video.mimes OR bid.params.video.mimes should be present and must be a non-empty array', function() { + let bid = { 'bidder': 'pubmatic', 'params': { 'adSlot': 'SLOT_NHB1@728x90', - 'publisherId': '5890' + 'publisherId': '5890', + 'video': {} }, 'mediaTypes': { 'video': { @@ -807,42 +947,6 @@ describe('PubMatic adapter', function () { ], 'protocols': [1, 2, 5], 'context': 'instream', - 'mimes': ['video/flv'], - 'skippable': false, - 'skip': 1, - 'linearity': 2 - } - }, - 'adUnitCode': 'video1', - 'transactionId': '803e3750-0bbe-4ffe-a548-b6eca15087bf', - 'sizes': [ - [640, 480] - ], - 'bidId': '2c95df014cfe97', - 'bidderRequestId': '1fe59391566442', - 'auctionId': '3a4118ef-fb96-4416-b0b0-3cfc1cebc142', - 'src': 'client', - 'bidRequestsCount': 1, - 'bidderRequestsCount': 1, - 'bidderWinsCount': 0 - }, - isValid = spec.isBidRequestValid(bid); - expect(isValid).to.equal(true); - }) - - it('should return false if context is not present in video', function() { - let bid = { - 'bidder': 'pubmatic', - 'params': { - 'adSlot': 'SLOT_NHB1@728x90', - 'publisherId': '5890' - }, - 'mediaTypes': { - 'video': { - 'w': 640, - 'h': 480, - 'protocols': [1, 2, 5], - 'mimes': ['video/flv'], 'skippable': false, 'skip': 1, 'linearity': 2 @@ -860,160 +964,124 @@ describe('PubMatic adapter', function () { 'bidRequestsCount': 1, 'bidderRequestsCount': 1, 'bidderWinsCount': 0 - }, - isValid = spec.isBidRequestValid(bid); - expect(isValid).to.equal(false); - }) - - it('bid.mediaTypes.video.mimes OR bid.params.video.mimes should be present and must be a non-empty array', function() { - let bid = { - 'bidder': 'pubmatic', - 'params': { - 'adSlot': 'SLOT_NHB1@728x90', - 'publisherId': '5890', - 'video': {} - }, - 'mediaTypes': { - 'video': { - 'playerSize': [ - [640, 480] - ], - 'protocols': [1, 2, 5], - 'context': 'instream', - 'skippable': false, - 'skip': 1, - 'linearity': 2 - } - }, - 'adUnitCode': 'video1', - 'transactionId': '803e3750-0bbe-4ffe-a548-b6eca15087bf', - 'sizes': [ - [640, 480] - ], - 'bidId': '2c95df014cfe97', - 'bidderRequestId': '1fe59391566442', - 'auctionId': '3a4118ef-fb96-4416-b0b0-3cfc1cebc142', - 'src': 'client', - 'bidRequestsCount': 1, - 'bidderRequestsCount': 1, - 'bidderWinsCount': 0 - }; + }; - delete bid.params.video.mimes; // Undefined - bid.mediaTypes.video.mimes = 'string'; // NOT array - expect(spec.isBidRequestValid(bid)).to.equal(false); + delete bid.params.video.mimes; // Undefined + bid.mediaTypes.video.mimes = 'string'; // NOT array + expect(spec.isBidRequestValid(bid)).to.equal(false); - delete bid.params.video.mimes; // Undefined - delete bid.mediaTypes.video.mimes; // Undefined - expect(spec.isBidRequestValid(bid)).to.equal(false); + delete bid.params.video.mimes; // Undefined + delete bid.mediaTypes.video.mimes; // Undefined + expect(spec.isBidRequestValid(bid)).to.equal(false); - delete bid.params.video.mimes; // Undefined - bid.mediaTypes.video.mimes = ['video/flv']; // Valid - expect(spec.isBidRequestValid(bid)).to.equal(true); + delete bid.params.video.mimes; // Undefined + bid.mediaTypes.video.mimes = ['video/flv']; // Valid + expect(spec.isBidRequestValid(bid)).to.equal(true); - delete bid.mediaTypes.video.mimes; // mediaTypes.video.mimes undefined - bid.params.video = {mimes: 'string'}; // NOT array - expect(spec.isBidRequestValid(bid)).to.equal(false); + delete bid.mediaTypes.video.mimes; // mediaTypes.video.mimes undefined + bid.params.video = {mimes: 'string'}; // NOT array + expect(spec.isBidRequestValid(bid)).to.equal(false); - delete bid.mediaTypes.video.mimes; // mediaTypes.video.mimes undefined - delete bid.params.video.mimes; // Undefined - expect(spec.isBidRequestValid(bid)).to.equal(false); + delete bid.mediaTypes.video.mimes; // mediaTypes.video.mimes undefined + delete bid.params.video.mimes; // Undefined + expect(spec.isBidRequestValid(bid)).to.equal(false); - delete bid.mediaTypes.video.mimes; // mediaTypes.video.mimes undefined - bid.params.video.mimes = ['video/flv']; // Valid - expect(spec.isBidRequestValid(bid)).to.equal(true); + delete bid.mediaTypes.video.mimes; // mediaTypes.video.mimes undefined + bid.params.video.mimes = ['video/flv']; // Valid + expect(spec.isBidRequestValid(bid)).to.equal(true); - delete bid.mediaTypes.video.mimes; // Undefined - bid.params.video.mimes = ['video/flv']; // Valid - expect(spec.isBidRequestValid(bid)).to.equal(true); + delete bid.mediaTypes.video.mimes; // Undefined + bid.params.video.mimes = ['video/flv']; // Valid + expect(spec.isBidRequestValid(bid)).to.equal(true); - delete bid.mediaTypes.video.mimes; // Undefined - delete bid.params.video.mimes; // Undefined - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); + delete bid.mediaTypes.video.mimes; // Undefined + delete bid.params.video.mimes; // Undefined + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); - it('checks on bid.params.outstreamAU & bid.renderer & bid.mediaTypes.video.renderer', function() { - const getThebid = function() { - let bid = utils.deepClone(validOutstreamBidRequest.bids[0]); - bid.params.outstreamAU = 'pubmatic-test'; - bid.renderer = ' '; // we are only checking if this key is set or not - bid.mediaTypes.video.renderer = ' '; // we are only checking if this key is set or not - return bid; - } + it('checks on bid.params.outstreamAU & bid.renderer & bid.mediaTypes.video.renderer', function() { + const getThebid = function() { + let bid = utils.deepClone(validOutstreamBidRequest.bids[0]); + bid.params.outstreamAU = 'pubmatic-test'; + bid.renderer = ' '; // we are only checking if this key is set or not + bid.mediaTypes.video.renderer = ' '; // we are only checking if this key is set or not + return bid; + } - // true: when all are present - // mdiatype: outstream - // bid.params.outstreamAU : Y - // bid.renderer : Y - // bid.mediaTypes.video.renderer : Y - let bid = getThebid(); - expect(spec.isBidRequestValid(bid)).to.equal(true); - - // true: atleast one is present; 3 cases - // mdiatype: outstream - // bid.params.outstreamAU : Y - // bid.renderer : N - // bid.mediaTypes.video.renderer : N - bid = getThebid(); - delete bid.renderer; - delete bid.mediaTypes.video.renderer; - expect(spec.isBidRequestValid(bid)).to.equal(true); - - // true: atleast one is present; 3 cases - // mdiatype: outstream - // bid.params.outstreamAU : N - // bid.renderer : Y - // bid.mediaTypes.video.renderer : N - bid = getThebid(); - delete bid.params.outstreamAU; - delete bid.mediaTypes.video.renderer; - expect(spec.isBidRequestValid(bid)).to.equal(true); - - // true: atleast one is present; 3 cases - // mdiatype: outstream - // bid.params.outstreamAU : N - // bid.renderer : N - // bid.mediaTypes.video.renderer : Y - bid = getThebid(); - delete bid.params.outstreamAU; - delete bid.renderer; - expect(spec.isBidRequestValid(bid)).to.equal(true); - - // false: none present; only outstream - // mdiatype: outstream - // bid.params.outstreamAU : N - // bid.renderer : N - // bid.mediaTypes.video.renderer : N - bid = getThebid(); - delete bid.params.outstreamAU; - delete bid.renderer; - delete bid.mediaTypes.video.renderer; - expect(spec.isBidRequestValid(bid)).to.equal(false); - - // true: none present; outstream + Banner - // mdiatype: outstream, banner - // bid.params.outstreamAU : N - // bid.renderer : N - // bid.mediaTypes.video.renderer : N - bid = getThebid(); - delete bid.params.outstreamAU; - delete bid.renderer; - delete bid.mediaTypes.video.renderer; - bid.mediaTypes.banner = {sizes: [ [300, 250], [300, 600] ]}; - expect(spec.isBidRequestValid(bid)).to.equal(true); - - // true: none present; outstream + Native - // mdiatype: outstream, native - // bid.params.outstreamAU : N - // bid.renderer : N - // bid.mediaTypes.video.renderer : N - bid = getThebid(); - delete bid.params.outstreamAU; - delete bid.renderer; - delete bid.mediaTypes.video.renderer; - bid.mediaTypes.native = {} - expect(spec.isBidRequestValid(bid)).to.equal(true); - }); + // true: when all are present + // mdiatype: outstream + // bid.params.outstreamAU : Y + // bid.renderer : Y + // bid.mediaTypes.video.renderer : Y + let bid = getThebid(); + expect(spec.isBidRequestValid(bid)).to.equal(true); + + // true: atleast one is present; 3 cases + // mdiatype: outstream + // bid.params.outstreamAU : Y + // bid.renderer : N + // bid.mediaTypes.video.renderer : N + bid = getThebid(); + delete bid.renderer; + delete bid.mediaTypes.video.renderer; + expect(spec.isBidRequestValid(bid)).to.equal(true); + + // true: atleast one is present; 3 cases + // mdiatype: outstream + // bid.params.outstreamAU : N + // bid.renderer : Y + // bid.mediaTypes.video.renderer : N + bid = getThebid(); + delete bid.params.outstreamAU; + delete bid.mediaTypes.video.renderer; + expect(spec.isBidRequestValid(bid)).to.equal(true); + + // true: atleast one is present; 3 cases + // mdiatype: outstream + // bid.params.outstreamAU : N + // bid.renderer : N + // bid.mediaTypes.video.renderer : Y + bid = getThebid(); + delete bid.params.outstreamAU; + delete bid.renderer; + expect(spec.isBidRequestValid(bid)).to.equal(true); + + // false: none present; only outstream + // mdiatype: outstream + // bid.params.outstreamAU : N + // bid.renderer : N + // bid.mediaTypes.video.renderer : N + bid = getThebid(); + delete bid.params.outstreamAU; + delete bid.renderer; + delete bid.mediaTypes.video.renderer; + expect(spec.isBidRequestValid(bid)).to.equal(false); + + // true: none present; outstream + Banner + // mdiatype: outstream, banner + // bid.params.outstreamAU : N + // bid.renderer : N + // bid.mediaTypes.video.renderer : N + bid = getThebid(); + delete bid.params.outstreamAU; + delete bid.renderer; + delete bid.mediaTypes.video.renderer; + bid.mediaTypes.banner = {sizes: [ [300, 250], [300, 600] ]}; + expect(spec.isBidRequestValid(bid)).to.equal(true); + + // true: none present; outstream + Native + // mdiatype: outstream, native + // bid.params.outstreamAU : N + // bid.renderer : N + // bid.mediaTypes.video.renderer : N + bid = getThebid(); + delete bid.params.outstreamAU; + delete bid.renderer; + delete bid.mediaTypes.video.renderer; + bid.mediaTypes.native = {} + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + } }); describe('Request formation', function () { @@ -1059,21 +1127,14 @@ describe('PubMatic adapter', function () { expect(data.test).to.equal(undefined); }); - // disabled this test case as it refreshes the whole suite when in karma watch mode - // todo: needs a fix - xit('test flag set to 1 when pubmaticTest=true is present in page url', function() { - window.location.href += '#pubmaticTest=true'; - // now all the test cases below will have window.location.href with #pubmaticTest=true - let request = spec.buildRequests(bidRequests, { - auctionId: 'new-auction-id' - }); - let data = JSON.parse(request.data); - expect(data.test).to.equal(1); - }); - it('Request params check', function () { let request = spec.buildRequests(bidRequests, { - auctionId: 'new-auction-id' + auctionId: 'new-auction-id', + ortb2: { + source: { + tid: 'source-tid' + } + } }); let data = JSON.parse(request.data); expect(data.at).to.equal(1); // auction type @@ -1083,13 +1144,13 @@ describe('PubMatic adapter', function () { expect(data.site.publisher.id).to.equal(bidRequests[0].params.publisherId); // publisher Id expect(data.user.yob).to.equal(parseInt(bidRequests[0].params.yob)); // YOB expect(data.user.gender).to.equal(bidRequests[0].params.gender); // Gender - expect(data.device.geo.lat).to.equal(parseFloat(bidRequests[0].params.lat)); // Latitude - expect(data.device.geo.lon).to.equal(parseFloat(bidRequests[0].params.lon)); // Lognitude - expect(data.user.geo.lat).to.equal(parseFloat(bidRequests[0].params.lat)); // Latitude - expect(data.user.geo.lon).to.equal(parseFloat(bidRequests[0].params.lon)); // Lognitude + expect(data.device.geo.lat).to.not.equal(parseFloat(bidRequests[0].params.lat)); // Latitude + expect(data.device.geo.lon).to.not.equal(parseFloat(bidRequests[0].params.lon)); // Lognitude + expect(data.user.geo.lat).to.not.equal(parseFloat(bidRequests[0].params.lat)); // Latitude + expect(data.user.geo.lon).to.not.equal(parseFloat(bidRequests[0].params.lon)); // Lognitude expect(data.ext.wrapper.wv).to.equal($$REPO_AND_VERSION$$); // Wrapper Version - expect(data.ext.wrapper.transactionId).to.equal(bidRequests[0].transactionId); // Prebid TransactionId - expect(data.source.tid).to.equal(bidRequests[0].transactionId); // Prebid TransactionId + expect(data.ext.wrapper.transactionId).to.equal(bidRequests[0].ortb2Imp.ext.tid); // Prebid TransactionId + expect(data.source.tid).to.equal('source-tid'); // Prebid TransactionId expect(data.ext.wrapper.wiid).to.equal(bidRequests[0].params.wiid); // OpenWrap: Wrapper Impression ID expect(data.ext.wrapper.profile).to.equal(parseInt(bidRequests[0].params.profId)); // OpenWrap: Wrapper Profile ID expect(data.ext.wrapper.version).to.equal(parseInt(bidRequests[0].params.verId)); // OpenWrap: Wrapper Profile Version ID @@ -1336,12 +1397,12 @@ describe('PubMatic adapter', function () { expect(data.site.publisher.id).to.equal(bidRequests[0].params.publisherId); // publisher Id expect(data.user.yob).to.equal(parseInt(bidRequests[0].params.yob)); // YOB expect(data.user.gender).to.equal(bidRequests[0].params.gender); // Gender - expect(data.device.geo.lat).to.equal(parseFloat(bidRequests[0].params.lat)); // Latitude - expect(data.device.geo.lon).to.equal(parseFloat(bidRequests[0].params.lon)); // Lognitude - expect(data.user.geo.lat).to.equal(parseFloat(bidRequests[0].params.lat)); // Latitude - expect(data.user.geo.lon).to.equal(parseFloat(bidRequests[0].params.lon)); // Lognitude + expect(data.device.geo.lat).to.not.equal(parseFloat(bidRequests[0].params.lat)); // Latitude + expect(data.device.geo.lon).to.not.equal(parseFloat(bidRequests[0].params.lon)); // Lognitude + expect(data.user.geo.lat).to.not.equal(parseFloat(bidRequests[0].params.lat)); // Latitude + expect(data.user.geo.lon).to.not.equal(parseFloat(bidRequests[0].params.lon)); // Lognitude expect(data.ext.wrapper.wv).to.equal($$REPO_AND_VERSION$$); // Wrapper Version - expect(data.ext.wrapper.transactionId).to.equal(bidRequests[0].transactionId); // Prebid TransactionId + expect(data.ext.wrapper.transactionId).to.equal(bidRequests[0].ortb2Imp.ext.tid); // Prebid TransactionId expect(data.ext.wrapper.wiid).to.equal(bidRequests[0].params.wiid); // OpenWrap: Wrapper Impression ID expect(data.ext.wrapper.profile).to.equal(parseInt(bidRequests[0].params.profId)); // OpenWrap: Wrapper Profile ID expect(data.ext.wrapper.version).to.equal(parseInt(bidRequests[0].params.verId)); // OpenWrap: Wrapper Profile Version ID @@ -1547,12 +1608,12 @@ describe('PubMatic adapter', function () { expect(data.site.publisher.id).to.equal(bidRequests[0].params.publisherId); // publisher Id expect(data.user.yob).to.equal(parseInt(bidRequests[0].params.yob)); // YOB expect(data.user.gender).to.equal(bidRequests[0].params.gender); // Gender - expect(data.device.geo.lat).to.equal(parseFloat(bidRequests[0].params.lat)); // Latitude - expect(data.device.geo.lon).to.equal(parseFloat(bidRequests[0].params.lon)); // Lognitude - expect(data.user.geo.lat).to.equal(parseFloat(bidRequests[0].params.lat)); // Latitude - expect(data.user.geo.lon).to.equal(parseFloat(bidRequests[0].params.lon)); // Lognitude + expect(data.device.geo.lat).to.not.equal(parseFloat(bidRequests[0].params.lat)); // Latitude + expect(data.device.geo.lon).to.not.equal(parseFloat(bidRequests[0].params.lon)); // Longitude + expect(data.user.geo.lat).to.not.equal(parseFloat(bidRequests[0].params.lat)); // Latitude + expect(data.user.geo.lon).to.not.equal(parseFloat(bidRequests[0].params.lon)); // Longitude expect(data.ext.wrapper.wv).to.equal($$REPO_AND_VERSION$$); // Wrapper Version - expect(data.ext.wrapper.transactionId).to.equal(bidRequests[0].transactionId); // Prebid TransactionId + expect(data.ext.wrapper.transactionId).to.equal(bidRequests[0].ortb2Imp.ext.tid); // Prebid TransactionId expect(data.ext.wrapper.wiid).to.equal('new-auction-id'); // OpenWrap: Wrapper Impression ID expect(data.ext.wrapper.profile).to.equal(parseInt(bidRequests[0].params.profId)); // OpenWrap: Wrapper Profile ID expect(data.ext.wrapper.version).to.equal(parseInt(bidRequests[0].params.verId)); // OpenWrap: Wrapper Profile Version ID @@ -1583,12 +1644,12 @@ describe('PubMatic adapter', function () { expect(data.site.publisher.id).to.equal(bidRequests[0].params.publisherId); // publisher Id expect(data.user.yob).to.equal(parseInt(bidRequests[0].params.yob)); // YOB expect(data.user.gender).to.equal(bidRequests[0].params.gender); // Gender - expect(data.device.geo.lat).to.equal(parseFloat(bidRequests[0].params.lat)); // Latitude - expect(data.device.geo.lon).to.equal(parseFloat(bidRequests[0].params.lon)); // Lognitude - expect(data.user.geo.lat).to.equal(parseFloat(bidRequests[0].params.lat)); // Latitude - expect(data.user.geo.lon).to.equal(parseFloat(bidRequests[0].params.lon)); // Lognitude + expect(data.device.geo.lat).to.not.equal(parseFloat(bidRequests[0].params.lat)); // Latitude + expect(data.device.geo.lon).to.not.equal(parseFloat(bidRequests[0].params.lon)); // Lognitude + expect(data.user.geo.lat).to.not.equal(parseFloat(bidRequests[0].params.lat)); // Latitude + expect(data.user.geo.lon).to.not.equal(parseFloat(bidRequests[0].params.lon)); // Lognitude expect(data.ext.wrapper.wv).to.equal($$REPO_AND_VERSION$$); // Wrapper Version - expect(data.ext.wrapper.transactionId).to.equal(bidRequests[0].transactionId); // Prebid TransactionId + expect(data.ext.wrapper.transactionId).to.equal(bidRequests[0].ortb2Imp.ext.tid); // Prebid TransactionId expect(data.ext.wrapper.wiid).to.equal(bidRequests[0].params.wiid); // OpenWrap: Wrapper Impression ID expect(data.ext.wrapper.profile).to.equal(parseInt(bidRequests[0].params.profId)); // OpenWrap: Wrapper Profile ID expect(data.ext.wrapper.version).to.equal(parseInt(bidRequests[0].params.verId)); // OpenWrap: Wrapper Profile Version ID @@ -1615,12 +1676,12 @@ describe('PubMatic adapter', function () { expect(data.site.publisher.id).to.equal(bidRequests[0].params.publisherId); // publisher Id expect(data.user.yob).to.equal(parseInt(bidRequests[0].params.yob)); // YOB expect(data.user.gender).to.equal(bidRequests[0].params.gender); // Gender - expect(data.device.geo.lat).to.equal(parseFloat(bidRequests[0].params.lat)); // Latitude - expect(data.device.geo.lon).to.equal(parseFloat(bidRequests[0].params.lon)); // Lognitude - expect(data.user.geo.lat).to.equal(parseFloat(bidRequests[0].params.lat)); // Latitude - expect(data.user.geo.lon).to.equal(parseFloat(bidRequests[0].params.lon)); // Lognitude + expect(data.device.geo.lat).to.not.equal(parseFloat(bidRequests[0].params.lat)); // Latitude + expect(data.device.geo.lon).to.not.equal(parseFloat(bidRequests[0].params.lon)); // Lognitude + expect(data.user.geo.lat).to.not.equal(parseFloat(bidRequests[0].params.lat)); // Latitude + expect(data.user.geo.lon).to.not.equal(parseFloat(bidRequests[0].params.lon)); // Lognitude expect(data.ext.wrapper.wv).to.equal($$REPO_AND_VERSION$$); // Wrapper Version - expect(data.ext.wrapper.transactionId).to.equal(bidRequests[0].transactionId); // Prebid TransactionId + expect(data.ext.wrapper.transactionId).to.equal(bidRequests[0].ortb2Imp.ext.tid); // Prebid TransactionId expect(data.ext.wrapper.wiid).to.equal(bidRequests[0].params.wiid); // OpenWrap: Wrapper Impression ID expect(data.ext.wrapper.profile).to.equal(parseInt(bidRequests[0].params.profId)); // OpenWrap: Wrapper Profile ID expect(data.ext.wrapper.version).to.equal(parseInt(bidRequests[0].params.verId)); // OpenWrap: Wrapper Profile Version ID @@ -1998,29 +2059,31 @@ describe('PubMatic adapter', function () { expect(data.bidfloor).to.equal(undefined); }); - it('ignore floormodule o/p if floor is not number', function() { - floorModuleTestData.banner['300x250'].floor = 'Not-a-Number'; - floorModuleTestData.banner['300x600'].floor = 'Not-a-Number'; - newRequest[0].params.kadfloor = undefined; - let request = spec.buildRequests(newRequest, { - auctionId: 'new-auction-id' + if (FEATURES.VIDEO) { + it('ignore floormodule o/p if floor is not number', function() { + floorModuleTestData.banner['300x250'].floor = 'Not-a-Number'; + floorModuleTestData.banner['300x600'].floor = 'Not-a-Number'; + newRequest[0].params.kadfloor = undefined; + let request = spec.buildRequests(newRequest, { + auctionId: 'new-auction-id' + }); + let data = JSON.parse(request.data); + data = data.imp[0]; + expect(data.bidfloor).to.equal(2.5); // video will be lowest now }); - let data = JSON.parse(request.data); - data = data.imp[0]; - expect(data.bidfloor).to.equal(2.5); // video will be lowest now - }); - it('ignore floormodule o/p if currency is not matched', function() { - floorModuleTestData.banner['300x250'].currency = 'INR'; - floorModuleTestData.banner['300x600'].currency = 'INR'; - newRequest[0].params.kadfloor = undefined; - let request = spec.buildRequests(newRequest, { - auctionId: 'new-auction-id' + it('ignore floormodule o/p if currency is not matched', function() { + floorModuleTestData.banner['300x250'].currency = 'INR'; + floorModuleTestData.banner['300x600'].currency = 'INR'; + newRequest[0].params.kadfloor = undefined; + let request = spec.buildRequests(newRequest, { + auctionId: 'new-auction-id' + }); + let data = JSON.parse(request.data); + data = data.imp[0]; + expect(data.bidfloor).to.equal(2.5); // video will be lowest now }); - let data = JSON.parse(request.data); - data = data.imp[0]; - expect(data.bidfloor).to.equal(2.5); // video will be lowest now - }); + } it('kadfloor is not passed, use minimum from floorModule', function() { newRequest[0].params.kadfloor = undefined; @@ -2499,114 +2562,6 @@ describe('PubMatic adapter', function () { }); }); - it('Request params check for video ad', function () { - let request = spec.buildRequests(videoBidRequests, { - auctionId: 'new-auction-id' - }); - let data = JSON.parse(request.data); - expect(data.imp[0].video).to.exist; - expect(data.imp[0].tagid).to.equal('Div1'); - expect(data.imp[0]['video']['mimes']).to.exist.and.to.be.an('array'); - expect(data.imp[0]['video']['mimes'][0]).to.equal(videoBidRequests[0].params.video['mimes'][0]); - expect(data.imp[0]['video']['mimes'][1]).to.equal(videoBidRequests[0].params.video['mimes'][1]); - expect(data.imp[0]['video']['minduration']).to.equal(videoBidRequests[0].params.video['minduration']); - expect(data.imp[0]['video']['maxduration']).to.equal(videoBidRequests[0].params.video['maxduration']); - expect(data.imp[0]['video']['startdelay']).to.equal(videoBidRequests[0].params.video['startdelay']); - - expect(data.imp[0]['video']['playbackmethod']).to.exist.and.to.be.an('array'); - expect(data.imp[0]['video']['playbackmethod'][0]).to.equal(videoBidRequests[0].params.video['playbackmethod'][0]); - expect(data.imp[0]['video']['playbackmethod'][1]).to.equal(videoBidRequests[0].params.video['playbackmethod'][1]); - - expect(data.imp[0]['video']['api']).to.exist.and.to.be.an('array'); - expect(data.imp[0]['video']['api'][0]).to.equal(videoBidRequests[0].params.video['api'][0]); - expect(data.imp[0]['video']['api'][1]).to.equal(videoBidRequests[0].params.video['api'][1]); - - expect(data.imp[0]['video']['protocols']).to.exist.and.to.be.an('array'); - expect(data.imp[0]['video']['protocols'][0]).to.equal(videoBidRequests[0].params.video['protocols'][0]); - expect(data.imp[0]['video']['protocols'][1]).to.equal(videoBidRequests[0].params.video['protocols'][1]); - - expect(data.imp[0]['video']['battr']).to.exist.and.to.be.an('array'); - expect(data.imp[0]['video']['battr'][0]).to.equal(videoBidRequests[0].params.video['battr'][0]); - expect(data.imp[0]['video']['battr'][1]).to.equal(videoBidRequests[0].params.video['battr'][1]); - - expect(data.imp[0]['video']['linearity']).to.equal(videoBidRequests[0].params.video['linearity']); - expect(data.imp[0]['video']['placement']).to.equal(videoBidRequests[0].params.video['placement']); - expect(data.imp[0]['video']['minbitrate']).to.equal(videoBidRequests[0].params.video['minbitrate']); - expect(data.imp[0]['video']['maxbitrate']).to.equal(videoBidRequests[0].params.video['maxbitrate']); - - expect(data.imp[0]['video']['w']).to.equal(videoBidRequests[0].mediaTypes.video.playerSize[0]); - expect(data.imp[0]['video']['h']).to.equal(videoBidRequests[0].mediaTypes.video.playerSize[1]); - }); - - it('Request params check for 1 banner and 1 video ad', function () { - let request = spec.buildRequests(multipleMediaRequests, { - auctionId: 'new-auction-id' - }); - let data = JSON.parse(request.data); - - expect(data.imp).to.be.an('array') - expect(data.imp).with.length.above(1); - - expect(data.at).to.equal(1); // auction type - expect(data.cur[0]).to.equal('USD'); // currency - expect(data.site.domain).to.be.a('string'); // domain should be set - expect(data.site.page).to.equal(multipleMediaRequests[0].params.kadpageurl); // forced pageURL - expect(data.site.publisher.id).to.equal(multipleMediaRequests[0].params.publisherId); // publisher Id - expect(data.user.yob).to.equal(parseInt(multipleMediaRequests[0].params.yob)); // YOB - expect(data.user.gender).to.equal(multipleMediaRequests[0].params.gender); // Gender - expect(data.device.geo.lat).to.equal(parseFloat(multipleMediaRequests[0].params.lat)); // Latitude - expect(data.device.geo.lon).to.equal(parseFloat(multipleMediaRequests[0].params.lon)); // Lognitude - expect(data.user.geo.lat).to.equal(parseFloat(multipleMediaRequests[0].params.lat)); // Latitude - expect(data.user.geo.lon).to.equal(parseFloat(multipleMediaRequests[0].params.lon)); // Lognitude - expect(data.ext.wrapper.wv).to.equal($$REPO_AND_VERSION$$); // Wrapper Version - expect(data.ext.wrapper.transactionId).to.equal(multipleMediaRequests[0].transactionId); // Prebid TransactionId - expect(data.ext.wrapper.wiid).to.equal(multipleMediaRequests[0].params.wiid); // OpenWrap: Wrapper Impression ID - expect(data.ext.wrapper.profile).to.equal(parseInt(multipleMediaRequests[0].params.profId)); // OpenWrap: Wrapper Profile ID - expect(data.ext.wrapper.version).to.equal(parseInt(multipleMediaRequests[0].params.verId)); // OpenWrap: Wrapper Profile Version ID - - // banner imp object check - expect(data.imp[0].id).to.equal(multipleMediaRequests[0].bidId); // Prebid bid id is passed as id - expect(data.imp[0].bidfloor).to.equal(parseFloat(multipleMediaRequests[0].params.kadfloor)); // kadfloor - expect(data.imp[0].tagid).to.equal('/15671365/DMDemo'); // tagid - expect(data.imp[0].banner.w).to.equal(300); // width - expect(data.imp[0].banner.h).to.equal(250); // height - expect(data.imp[0].ext.pmZoneId).to.equal(multipleMediaRequests[0].params.pmzoneid.split(',').slice(0, 50).map(id => id.trim()).join()); // pmzoneid - - // video imp object check - expect(data.imp[1].video).to.exist; - expect(data.imp[1].tagid).to.equal('Div1'); - expect(data.imp[1]['video']['mimes']).to.exist.and.to.be.an('array'); - expect(data.imp[1]['video']['mimes'][0]).to.equal(multipleMediaRequests[1].params.video['mimes'][0]); - expect(data.imp[1]['video']['mimes'][1]).to.equal(multipleMediaRequests[1].params.video['mimes'][1]); - expect(data.imp[1]['video']['minduration']).to.equal(multipleMediaRequests[1].params.video['minduration']); - expect(data.imp[1]['video']['maxduration']).to.equal(multipleMediaRequests[1].params.video['maxduration']); - expect(data.imp[1]['video']['startdelay']).to.equal(multipleMediaRequests[1].params.video['startdelay']); - - expect(data.imp[1]['video']['playbackmethod']).to.exist.and.to.be.an('array'); - expect(data.imp[1]['video']['playbackmethod'][0]).to.equal(multipleMediaRequests[1].params.video['playbackmethod'][0]); - expect(data.imp[1]['video']['playbackmethod'][1]).to.equal(multipleMediaRequests[1].params.video['playbackmethod'][1]); - - expect(data.imp[1]['video']['api']).to.exist.and.to.be.an('array'); - expect(data.imp[1]['video']['api'][0]).to.equal(multipleMediaRequests[1].params.video['api'][0]); - expect(data.imp[1]['video']['api'][1]).to.equal(multipleMediaRequests[1].params.video['api'][1]); - - expect(data.imp[1]['video']['protocols']).to.exist.and.to.be.an('array'); - expect(data.imp[1]['video']['protocols'][0]).to.equal(multipleMediaRequests[1].params.video['protocols'][0]); - expect(data.imp[1]['video']['protocols'][1]).to.equal(multipleMediaRequests[1].params.video['protocols'][1]); - - expect(data.imp[1]['video']['battr']).to.exist.and.to.be.an('array'); - expect(data.imp[1]['video']['battr'][0]).to.equal(multipleMediaRequests[1].params.video['battr'][0]); - expect(data.imp[1]['video']['battr'][1]).to.equal(multipleMediaRequests[1].params.video['battr'][1]); - - expect(data.imp[1]['video']['linearity']).to.equal(multipleMediaRequests[1].params.video['linearity']); - expect(data.imp[1]['video']['placement']).to.equal(multipleMediaRequests[1].params.video['placement']); - expect(data.imp[1]['video']['minbitrate']).to.equal(multipleMediaRequests[1].params.video['minbitrate']); - expect(data.imp[1]['video']['maxbitrate']).to.equal(multipleMediaRequests[1].params.video['maxbitrate']); - - expect(data.imp[1]['video']['w']).to.equal(multipleMediaRequests[1].mediaTypes.video.playerSize[0]); - expect(data.imp[1]['video']['h']).to.equal(multipleMediaRequests[1].mediaTypes.video.playerSize[1]); - }); - it('should pass device.sua if present in bidderRequest fpd ortb2 object', function () { const suaObject = {'source': 2, 'platform': {'brand': 'macOS', 'version': ['12', '4', '0']}, 'browsers': [{'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']}], 'mobile': 0, 'model': '', 'bitness': '64', 'architecture': 'x86'}; let request = spec.buildRequests(multipleMediaRequests, { @@ -2666,127 +2621,6 @@ describe('PubMatic adapter', function () { expect(data.imp[0]['native']['request']).to.exist.and.to.equal(validnativeBidImpressionWithAllParams.native.request); }); - it('Request params - should handle banner and video format in single adunit', function() { - let request = spec.buildRequests(bannerAndVideoBidRequests, { - auctionId: 'new-auction-id' - }); - let data = JSON.parse(request.data); - data = data.imp[0]; - expect(data.banner).to.exist; - expect(data.banner.w).to.equal(300); - expect(data.banner.h).to.equal(250); - expect(data.banner.format).to.exist; - expect(data.banner.format.length).to.equal(bannerAndVideoBidRequests[0].mediaTypes.banner.sizes.length); - - // Case: when size is not present in adslo - bannerAndVideoBidRequests[0].params.adSlot = '/15671365/DMDemo'; - request = spec.buildRequests(bannerAndVideoBidRequests, { - auctionId: 'new-auction-id' - }); - data = JSON.parse(request.data); - data = data.imp[0]; - expect(data.banner).to.exist; - expect(data.banner.w).to.equal(bannerAndVideoBidRequests[0].mediaTypes.banner.sizes[0][0]); - expect(data.banner.h).to.equal(bannerAndVideoBidRequests[0].mediaTypes.banner.sizes[0][1]); - expect(data.banner.format).to.exist; - expect(data.banner.format.length).to.equal(bannerAndVideoBidRequests[0].mediaTypes.banner.sizes.length - 1); - - expect(data.video).to.exist; - expect(data.video.w).to.equal(bannerAndVideoBidRequests[0].mediaTypes.video.playerSize[0]); - expect(data.video.h).to.equal(bannerAndVideoBidRequests[0].mediaTypes.video.playerSize[1]); - }); - - it('Request params - banner and video req in single adslot - should ignore banner imp if banner size is set to fluid and send video imp object', function () { - /* Adslot configured for banner and video. - banner size is set to [['fluid'], [300, 250]] - adslot specifies a size as 300x250 - => banner imp object should have primary w and h set to 300 and 250. fluid is ignored - */ - bannerAndVideoBidRequests[0].mediaTypes.banner.sizes = [['fluid'], [160, 600]]; - - let request = spec.buildRequests(bannerAndVideoBidRequests, { - auctionId: 'new-auction-id' - }); - let data = JSON.parse(request.data); - data = data.imp[0]; - - expect(data.banner).to.exist; - expect(data.banner.w).to.equal(300); - expect(data.banner.h).to.equal(250); - expect(data.banner.format).to.exist; - expect(data.banner.format[0].w).to.equal(160); - expect(data.banner.format[0].h).to.equal(600); - - /* Adslot configured for banner and video. - banner size is set to [['fluid'], [300, 250]] - adslot does not specify any size - => banner imp object should have primary w and h set to 300 and 250. fluid is ignored - */ - bannerAndVideoBidRequests[0].mediaTypes.banner.sizes = [['fluid'], [160, 600]]; - bannerAndVideoBidRequests[0].params.adSlot = '/15671365/DMDemo'; - - request = spec.buildRequests(bannerAndVideoBidRequests, { - auctionId: 'new-auction-id' - }); - data = JSON.parse(request.data); - data = data.imp[0]; - - expect(data.banner).to.exist; - expect(data.banner.w).to.equal(160); - expect(data.banner.h).to.equal(600); - expect(data.banner.format).to.not.exist; - - /* Adslot configured for banner and video. - banner size is set to [[728 90], ['fluid'], [300, 250]] - adslot does not specify any size - => banner imp object should have primary w and h set to 728 and 90. - banner.format should have 300, 250 set in it - fluid is ignore - */ - - bannerAndVideoBidRequests[0].mediaTypes.banner.sizes = [[728, 90], ['fluid'], [300, 250]]; - request = spec.buildRequests(bannerAndVideoBidRequests, { - auctionId: 'new-auction-id' - }); - data = JSON.parse(request.data); - data = data.imp[0]; - - expect(data.banner).to.exist; - expect(data.banner.w).to.equal(728); - expect(data.banner.h).to.equal(90); - expect(data.banner.format).to.exist; - expect(data.banner.format[0].w).to.equal(300); - expect(data.banner.format[0].h).to.equal(250); - - /* Adslot configured for banner and video. - banner size is set to [['fluid']] - adslot does not specify any size - => banner object should not be sent in the request. only video should be sent. - */ - - bannerAndVideoBidRequests[0].mediaTypes.banner.sizes = [['fluid']]; - request = spec.buildRequests(bannerAndVideoBidRequests, { - auctionId: 'new-auction-id' - }); - data = JSON.parse(request.data); - data = data.imp[0]; - - expect(data.banner).to.not.exist; - expect(data.video).to.exist; - }); - - it('Request params - should not contain banner imp if mediaTypes.banner is not present and sizes is specified in bid.sizes', function() { - delete bannerAndVideoBidRequests[0].mediaTypes.banner; - bannerAndVideoBidRequests[0].params.sizes = [300, 250]; - - let request = spec.buildRequests(bannerAndVideoBidRequests, { - auctionId: 'new-auction-id' - }); - let data = JSON.parse(request.data); - data = data.imp[0]; - expect(data.banner).to.not.exist; - }); - it('Request params - should handle banner and native format in single adunit', function() { let request = spec.buildRequests(bannerAndNativeBidRequests, { auctionId: 'new-auction-id' @@ -2804,40 +2638,27 @@ describe('PubMatic adapter', function () { expect(data.native.request).to.exist; }); - it('Request params - should handle video and native format in single adunit', function() { - let request = spec.buildRequests(videoAndNativeBidRequests, { - auctionId: 'new-auction-id' - }); - let data = JSON.parse(request.data); - data = data.imp[0]; - - expect(data.video).to.exist; - expect(data.video.w).to.equal(bannerAndVideoBidRequests[0].mediaTypes.video.playerSize[0]); - expect(data.video.h).to.equal(bannerAndVideoBidRequests[0].mediaTypes.video.playerSize[1]); - - expect(data.native).to.exist; - expect(data.native.request).to.exist; - }); - - it('Request params - should handle banner, video and native format in single adunit', function() { - let request = spec.buildRequests(bannerVideoAndNativeBidRequests, { + it('Request params - banner and native multiformat request - should have native object incase of invalid config present', function() { + bannerAndNativeBidRequests[0].mediaTypes.native = { + title: { required: true }, + image: { required: true }, + sponsoredBy: { required: true }, + clickUrl: { required: true } + }; + bannerAndNativeBidRequests[0].nativeParams = { + title: { required: true }, + image: { required: true }, + sponsoredBy: { required: true }, + clickUrl: { required: true } + } + let request = spec.buildRequests(bannerAndNativeBidRequests, { auctionId: 'new-auction-id' }); let data = JSON.parse(request.data); data = data.imp[0]; expect(data.banner).to.exist; - expect(data.banner.w).to.equal(300); - expect(data.banner.h).to.equal(250); - expect(data.banner.format).to.exist; - expect(data.banner.format.length).to.equal(bannerAndNativeBidRequests[0].mediaTypes.banner.sizes.length); - - expect(data.video).to.exist; - expect(data.video.w).to.equal(bannerAndVideoBidRequests[0].mediaTypes.video.playerSize[0]); - expect(data.video.h).to.equal(bannerAndVideoBidRequests[0].mediaTypes.video.playerSize[1]); - expect(data.native).to.exist; - expect(data.native.request).to.exist; }); it('Request params - should not add banner object if mediaTypes.banner is missing, but adunits.sizes is present', function() { @@ -2856,137 +2677,479 @@ describe('PubMatic adapter', function () { expect(data.native.request).to.exist; }); - it('Request params - banner and native multiformat request - should have native object incase of invalid config present', function() { - bannerAndNativeBidRequests[0].mediaTypes.native = { - title: { required: true }, - image: { required: true }, - sponsoredBy: { required: true }, - clickUrl: { required: true } - }; - bannerAndNativeBidRequests[0].nativeParams = { - title: { required: true }, - image: { required: true }, - sponsoredBy: { required: true }, - clickUrl: { required: true } - } - let request = spec.buildRequests(bannerAndNativeBidRequests, { - auctionId: 'new-auction-id' + if (FEATURES.VIDEO) { + it('Request params - should not contain banner imp if mediaTypes.banner is not present and sizes is specified in bid.sizes', function() { + delete bannerAndVideoBidRequests[0].mediaTypes.banner; + bannerAndVideoBidRequests[0].params.sizes = [300, 250]; + + let request = spec.buildRequests(bannerAndVideoBidRequests, { + auctionId: 'new-auction-id' + }); + let data = JSON.parse(request.data); + data = data.imp[0]; + expect(data.banner).to.not.exist; }); - let data = JSON.parse(request.data); - data = data.imp[0]; - expect(data.banner).to.exist; - expect(data.native).to.exist; - }); + it('Request params check for 1 banner and 1 video ad', function () { + let request = spec.buildRequests(multipleMediaRequests, { + auctionId: 'new-auction-id' + }); + let data = JSON.parse(request.data); - it('Request params - video and native multiformat request - should have native object incase of invalid config present', function() { - videoAndNativeBidRequests[0].mediaTypes.native = { - title: { required: true }, - image: { required: true }, - sponsoredBy: { required: true }, - clickUrl: { required: true } - }; - videoAndNativeBidRequests[0].nativeParams = { - title: { required: true }, - image: { required: true }, - sponsoredBy: { required: true }, - clickUrl: { required: true } - } - let request = spec.buildRequests(videoAndNativeBidRequests, { - auctionId: 'new-auction-id' + expect(data.imp).to.be.an('array') + expect(data.imp).with.length.above(1); + + expect(data.at).to.equal(1); // auction type + expect(data.cur[0]).to.equal('USD'); // currency + expect(data.site.domain).to.be.a('string'); // domain should be set + expect(data.site.page).to.equal(multipleMediaRequests[0].params.kadpageurl); // forced pageURL + expect(data.site.publisher.id).to.equal(multipleMediaRequests[0].params.publisherId); // publisher Id + expect(data.user.yob).to.equal(parseInt(multipleMediaRequests[0].params.yob)); // YOB + expect(data.user.gender).to.equal(multipleMediaRequests[0].params.gender); // Gender + expect(data.device.geo.lat).to.not.equal(parseFloat(multipleMediaRequests[0].params.lat)); // Latitude + expect(data.device.geo.lon).to.not.equal(parseFloat(multipleMediaRequests[0].params.lon)); // Lognitude + expect(data.user.geo.lat).to.not.equal(parseFloat(multipleMediaRequests[0].params.lat)); // Latitude + expect(data.user.geo.lon).to.not.equal(parseFloat(multipleMediaRequests[0].params.lon)); // Lognitude + expect(data.ext.wrapper.wv).to.equal($$REPO_AND_VERSION$$); // Wrapper Version + expect(data.ext.wrapper.transactionId).to.equal(multipleMediaRequests[0].transactionId); // Prebid TransactionId + expect(data.ext.wrapper.wiid).to.equal(multipleMediaRequests[0].params.wiid); // OpenWrap: Wrapper Impression ID + expect(data.ext.wrapper.profile).to.equal(parseInt(multipleMediaRequests[0].params.profId)); // OpenWrap: Wrapper Profile ID + expect(data.ext.wrapper.version).to.equal(parseInt(multipleMediaRequests[0].params.verId)); // OpenWrap: Wrapper Profile Version ID + + // banner imp object check + expect(data.imp[0].id).to.equal(multipleMediaRequests[0].bidId); // Prebid bid id is passed as id + expect(data.imp[0].bidfloor).to.equal(parseFloat(multipleMediaRequests[0].params.kadfloor)); // kadfloor + expect(data.imp[0].tagid).to.equal('/15671365/DMDemo'); // tagid + expect(data.imp[0].banner.w).to.equal(300); // width + expect(data.imp[0].banner.h).to.equal(250); // height + expect(data.imp[0].ext.pmZoneId).to.equal(multipleMediaRequests[0].params.pmzoneid.split(',').slice(0, 50).map(id => id.trim()).join()); // pmzoneid + + // video imp object check + expect(data.imp[1].video).to.exist; + expect(data.imp[1].tagid).to.equal('Div1'); + expect(data.imp[1]['video']['mimes']).to.exist.and.to.be.an('array'); + expect(data.imp[1]['video']['mimes'][0]).to.equal(multipleMediaRequests[1].params.video['mimes'][0]); + expect(data.imp[1]['video']['mimes'][1]).to.equal(multipleMediaRequests[1].params.video['mimes'][1]); + expect(data.imp[1]['video']['minduration']).to.equal(multipleMediaRequests[1].params.video['minduration']); + expect(data.imp[1]['video']['maxduration']).to.equal(multipleMediaRequests[1].params.video['maxduration']); + expect(data.imp[1]['video']['startdelay']).to.equal(multipleMediaRequests[1].params.video['startdelay']); + + expect(data.imp[1]['video']['playbackmethod']).to.exist.and.to.be.an('array'); + expect(data.imp[1]['video']['playbackmethod'][0]).to.equal(multipleMediaRequests[1].params.video['playbackmethod'][0]); + expect(data.imp[1]['video']['playbackmethod'][1]).to.equal(multipleMediaRequests[1].params.video['playbackmethod'][1]); + + expect(data.imp[1]['video']['api']).to.exist.and.to.be.an('array'); + expect(data.imp[1]['video']['api'][0]).to.equal(multipleMediaRequests[1].params.video['api'][0]); + expect(data.imp[1]['video']['api'][1]).to.equal(multipleMediaRequests[1].params.video['api'][1]); + + expect(data.imp[1]['video']['protocols']).to.exist.and.to.be.an('array'); + expect(data.imp[1]['video']['protocols'][0]).to.equal(multipleMediaRequests[1].params.video['protocols'][0]); + expect(data.imp[1]['video']['protocols'][1]).to.equal(multipleMediaRequests[1].params.video['protocols'][1]); + + expect(data.imp[1]['video']['battr']).to.exist.and.to.be.an('array'); + expect(data.imp[1]['video']['battr'][0]).to.equal(multipleMediaRequests[1].params.video['battr'][0]); + expect(data.imp[1]['video']['battr'][1]).to.equal(multipleMediaRequests[1].params.video['battr'][1]); + + expect(data.imp[1]['video']['linearity']).to.equal(multipleMediaRequests[1].params.video['linearity']); + expect(data.imp[1]['video']['placement']).to.equal(multipleMediaRequests[1].params.video['placement']); + expect(data.imp[1]['video']['plcmt']).to.equal(multipleMediaRequests[1].params.video['plcmt']); + expect(data.imp[1]['video']['minbitrate']).to.equal(multipleMediaRequests[1].params.video['minbitrate']); + expect(data.imp[1]['video']['maxbitrate']).to.equal(multipleMediaRequests[1].params.video['maxbitrate']); + + expect(data.imp[1]['video']['w']).to.equal(multipleMediaRequests[1].mediaTypes.video.playerSize[0]); + expect(data.imp[1]['video']['h']).to.equal(multipleMediaRequests[1].mediaTypes.video.playerSize[1]); }); - let data = JSON.parse(request.data); - data = data.imp[0]; - expect(data.video).to.exist; - expect(data.native).to.exist; - }); + // ================================================ + it('Request params - should handle banner and video format in single adunit', function() { + let request = spec.buildRequests(bannerAndVideoBidRequests, { + auctionId: 'new-auction-id' + }); + let data = JSON.parse(request.data); + data = data.imp[0]; + expect(data.banner).to.exist; + expect(data.banner.w).to.equal(300); + expect(data.banner.h).to.equal(250); + expect(data.banner.format).to.exist; + expect(data.banner.format.length).to.equal(bannerAndVideoBidRequests[0].mediaTypes.banner.sizes.length); + + // Case: when size is not present in adslo + bannerAndVideoBidRequests[0].params.adSlot = '/15671365/DMDemo'; + request = spec.buildRequests(bannerAndVideoBidRequests, { + auctionId: 'new-auction-id' + }); + data = JSON.parse(request.data); + data = data.imp[0]; + expect(data.banner).to.exist; + expect(data.banner.w).to.equal(bannerAndVideoBidRequests[0].mediaTypes.banner.sizes[0][0]); + expect(data.banner.h).to.equal(bannerAndVideoBidRequests[0].mediaTypes.banner.sizes[0][1]); + expect(data.banner.format).to.exist; + expect(data.banner.format.length).to.equal(bannerAndVideoBidRequests[0].mediaTypes.banner.sizes.length - 1); + + expect(data.video).to.exist; + expect(data.video.w).to.equal(bannerAndVideoBidRequests[0].mediaTypes.video.playerSize[0]); + expect(data.video.h).to.equal(bannerAndVideoBidRequests[0].mediaTypes.video.playerSize[1]); + }); - it('should build video impression if video params are present in adunit.mediaTypes instead of bid.params', function() { - let videoReq = [{ - 'bidder': 'pubmatic', - 'params': { - 'adSlot': 'SLOT_NHB1@728x90', - 'publisherId': '5890', - }, - 'mediaTypes': { - 'video': { - 'playerSize': [ - [640, 480] + it('Request params - should handle banner, video and native format in single adunit', function() { + let request = spec.buildRequests(bannerVideoAndNativeBidRequests, { + auctionId: 'new-auction-id' + }); + let data = JSON.parse(request.data); + data = data.imp[0]; + + expect(data.banner).to.exist; + expect(data.banner.w).to.equal(300); + expect(data.banner.h).to.equal(250); + expect(data.banner.format).to.exist; + expect(data.banner.format.length).to.equal(bannerAndNativeBidRequests[0].mediaTypes.banner.sizes.length); + + expect(data.video).to.exist; + expect(data.video.w).to.equal(bannerAndVideoBidRequests[0].mediaTypes.video.playerSize[0]); + expect(data.video.h).to.equal(bannerAndVideoBidRequests[0].mediaTypes.video.playerSize[1]); + + expect(data.native).to.exist; + expect(data.native.request).to.exist; + }); + + it('Request params - should handle video and native format in single adunit', function() { + let request = spec.buildRequests(videoAndNativeBidRequests, { + auctionId: 'new-auction-id' + }); + let data = JSON.parse(request.data); + data = data.imp[0]; + + expect(data.video).to.exist; + expect(data.video.w).to.equal(bannerAndVideoBidRequests[0].mediaTypes.video.playerSize[0]); + expect(data.video.h).to.equal(bannerAndVideoBidRequests[0].mediaTypes.video.playerSize[1]); + + expect(data.native).to.exist; + expect(data.native.request).to.exist; + }); + + it('Request params - banner and video req in single adslot - should ignore banner imp if banner size is set to fluid and send video imp object', function () { + /* Adslot configured for banner and video. + banner size is set to [['fluid'], [300, 250]] + adslot specifies a size as 300x250 + => banner imp object should have primary w and h set to 300 and 250. fluid is ignored + */ + bannerAndVideoBidRequests[0].mediaTypes.banner.sizes = [['fluid'], [160, 600]]; + + let request = spec.buildRequests(bannerAndVideoBidRequests, { + auctionId: 'new-auction-id' + }); + let data = JSON.parse(request.data); + data = data.imp[0]; + + expect(data.banner).to.exist; + expect(data.banner.w).to.equal(300); + expect(data.banner.h).to.equal(250); + expect(data.banner.format).to.exist; + expect(data.banner.format[0].w).to.equal(160); + expect(data.banner.format[0].h).to.equal(600); + + /* Adslot configured for banner and video. + banner size is set to [['fluid'], [300, 250]] + adslot does not specify any size + => banner imp object should have primary w and h set to 300 and 250. fluid is ignored + */ + bannerAndVideoBidRequests[0].mediaTypes.banner.sizes = [['fluid'], [160, 600]]; + bannerAndVideoBidRequests[0].params.adSlot = '/15671365/DMDemo'; + + request = spec.buildRequests(bannerAndVideoBidRequests, { + auctionId: 'new-auction-id' + }); + data = JSON.parse(request.data); + data = data.imp[0]; + + expect(data.banner).to.exist; + expect(data.banner.w).to.equal(160); + expect(data.banner.h).to.equal(600); + expect(data.banner.format).to.not.exist; + + /* Adslot configured for banner and video. + banner size is set to [[728 90], ['fluid'], [300, 250]] + adslot does not specify any size + => banner imp object should have primary w and h set to 728 and 90. + banner.format should have 300, 250 set in it + fluid is ignore + */ + + bannerAndVideoBidRequests[0].mediaTypes.banner.sizes = [[728, 90], ['fluid'], [300, 250]]; + request = spec.buildRequests(bannerAndVideoBidRequests, { + auctionId: 'new-auction-id' + }); + data = JSON.parse(request.data); + data = data.imp[0]; + + expect(data.banner).to.exist; + expect(data.banner.w).to.equal(728); + expect(data.banner.h).to.equal(90); + expect(data.banner.format).to.exist; + expect(data.banner.format[0].w).to.equal(300); + expect(data.banner.format[0].h).to.equal(250); + + /* Adslot configured for banner and video. + banner size is set to [['fluid']] + adslot does not specify any size + => banner object should not be sent in the request. only video should be sent. + */ + + bannerAndVideoBidRequests[0].mediaTypes.banner.sizes = [['fluid']]; + request = spec.buildRequests(bannerAndVideoBidRequests, { + auctionId: 'new-auction-id' + }); + data = JSON.parse(request.data); + data = data.imp[0]; + + expect(data.banner).to.not.exist; + expect(data.video).to.exist; + }); + + it('Request params - video and native multiformat request - should have native object incase of invalid config present', function() { + videoAndNativeBidRequests[0].mediaTypes.native = { + title: { required: true }, + image: { required: true }, + sponsoredBy: { required: true }, + clickUrl: { required: true } + }; + videoAndNativeBidRequests[0].nativeParams = { + title: { required: true }, + image: { required: true }, + sponsoredBy: { required: true }, + clickUrl: { required: true } + } + let request = spec.buildRequests(videoAndNativeBidRequests, { + auctionId: 'new-auction-id' + }); + let data = JSON.parse(request.data); + data = data.imp[0]; + + expect(data.video).to.exist; + expect(data.native).to.exist; + }); + + it('should build video impression if video params are present in adunit.mediaTypes instead of bid.params', function() { + let videoReq = [{ + 'bidder': 'pubmatic', + 'params': { + 'adSlot': 'SLOT_NHB1@728x90', + 'publisherId': '5890', + }, + 'mediaTypes': { + 'video': { + 'playerSize': [ + [640, 480] + ], + 'protocols': [1, 2, 5], + 'context': 'instream', + 'mimes': ['video/flv'], + 'skip': 1, + 'linearity': 2 + } + }, + 'adUnitCode': 'video1', + 'transactionId': 'adc36682-887c-41e9-9848-8b72c08332c0', + 'sizes': [ + [640, 480] + ], + 'bidId': '21b59b1353ba82', + 'bidderRequestId': '1a08245305e6dd', + 'auctionId': 'bad3a743-7491-4d19-9a96-b0a69dd24a67', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 + }] + let request = spec.buildRequests(videoReq, { + auctionId: 'new-auction-id' + }); + let data = JSON.parse(request.data); + data = data.imp[0]; + expect(data.video).to.exist; + }); + + it('should build video impression with overwriting video params present in adunit.mediaTypes with bid.params', function() { + let videoReq = [{ + 'bidder': 'pubmatic', + 'params': { + 'adSlot': 'SLOT_NHB1@728x90', + 'publisherId': '5890', + 'video': { + 'mimes': ['video/mp4'], + 'protocols': [1, 2, 5], + 'linearity': 1 + } + }, + 'mediaTypes': { + 'video': { + 'playerSize': [ + [640, 480] + ], + 'protocols': [1, 2, 5], + 'context': 'instream', + 'mimes': ['video/flv'], + 'skip': 1, + 'linearity': 2 + } + }, + 'adUnitCode': 'video1', + 'transactionId': 'adc36682-887c-41e9-9848-8b72c08332c0', + 'sizes': [ + [640, 480] + ], + 'bidId': '21b59b1353ba82', + 'bidderRequestId': '1a08245305e6dd', + 'auctionId': 'bad3a743-7491-4d19-9a96-b0a69dd24a67', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 + }] + let request = spec.buildRequests(videoReq, { + auctionId: 'new-auction-id' + }); + let data = JSON.parse(request.data); + data = data.imp[0]; + + expect(data.video).to.exist; + expect(data.video.linearity).to.equal(1); + }); + + it('Request params check for video ad', function () { + let request = spec.buildRequests(videoBidRequests, { + auctionId: 'new-auction-id' + }); + let data = JSON.parse(request.data); + expect(data.imp[0].video).to.exist; + expect(data.imp[0].tagid).to.equal('Div1'); + expect(data.imp[0]['video']['mimes']).to.exist.and.to.be.an('array'); + expect(data.imp[0]['video']['mimes'][0]).to.equal(videoBidRequests[0].params.video['mimes'][0]); + expect(data.imp[0]['video']['mimes'][1]).to.equal(videoBidRequests[0].params.video['mimes'][1]); + expect(data.imp[0]['video']['minduration']).to.equal(videoBidRequests[0].params.video['minduration']); + expect(data.imp[0]['video']['maxduration']).to.equal(videoBidRequests[0].params.video['maxduration']); + expect(data.imp[0]['video']['startdelay']).to.equal(videoBidRequests[0].params.video['startdelay']); + + expect(data.imp[0]['video']['playbackmethod']).to.exist.and.to.be.an('array'); + expect(data.imp[0]['video']['playbackmethod'][0]).to.equal(videoBidRequests[0].params.video['playbackmethod'][0]); + expect(data.imp[0]['video']['playbackmethod'][1]).to.equal(videoBidRequests[0].params.video['playbackmethod'][1]); + + expect(data.imp[0]['video']['api']).to.exist.and.to.be.an('array'); + expect(data.imp[0]['video']['api'][0]).to.equal(videoBidRequests[0].params.video['api'][0]); + expect(data.imp[0]['video']['api'][1]).to.equal(videoBidRequests[0].params.video['api'][1]); + + expect(data.imp[0]['video']['protocols']).to.exist.and.to.be.an('array'); + expect(data.imp[0]['video']['protocols'][0]).to.equal(videoBidRequests[0].params.video['protocols'][0]); + expect(data.imp[0]['video']['protocols'][1]).to.equal(videoBidRequests[0].params.video['protocols'][1]); + + expect(data.imp[0]['video']['battr']).to.exist.and.to.be.an('array'); + expect(data.imp[0]['video']['battr'][0]).to.equal(videoBidRequests[0].params.video['battr'][0]); + expect(data.imp[0]['video']['battr'][1]).to.equal(videoBidRequests[0].params.video['battr'][1]); + + expect(data.imp[0]['video']['linearity']).to.equal(videoBidRequests[0].params.video['linearity']); + expect(data.imp[0]['video']['placement']).to.equal(videoBidRequests[0].params.video['placement']); + expect(data.imp[0]['video']['plcmt']).to.equal(videoBidRequests[0].params.video['plcmt']); + expect(data.imp[0]['video']['minbitrate']).to.equal(videoBidRequests[0].params.video['minbitrate']); + expect(data.imp[0]['video']['maxbitrate']).to.equal(videoBidRequests[0].params.video['maxbitrate']); + + expect(data.imp[0]['video']['w']).to.equal(videoBidRequests[0].mediaTypes.video.playerSize[0]); + expect(data.imp[0]['video']['h']).to.equal(videoBidRequests[0].mediaTypes.video.playerSize[1]); + }); + } + + describe('GPP', function() { + it('Request params check with GPP Consent', function () { + let bidRequest = { + gppConsent: { + 'gppString': 'DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN', + 'fullGppData': { + 'sectionId': 3, + 'gppVersion': 1, + 'sectionList': [ + 5, + 7 + ], + 'applicableSections': [ + 5 + ], + 'gppString': 'DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN', + 'pingData': { + 'cmpStatus': 'loaded', + 'gppVersion': '1.0', + 'cmpDisplayStatus': 'visible', + 'supportedAPIs': [ + 'tcfca', + 'usnat', + 'usca', + 'usva', + 'usco', + 'usut', + 'usct' + ], + 'cmpId': 31 + }, + 'eventName': 'sectionChange' + }, + 'applicableSections': [ + 5 ], - 'protocols': [1, 2, 5], - 'context': 'instream', - 'mimes': ['video/flv'], - 'skip': 1, - 'linearity': 2 + 'apiVersion': 1 } - }, - 'adUnitCode': 'video1', - 'transactionId': 'adc36682-887c-41e9-9848-8b72c08332c0', - 'sizes': [ - [640, 480] - ], - 'bidId': '21b59b1353ba82', - 'bidderRequestId': '1a08245305e6dd', - 'auctionId': 'bad3a743-7491-4d19-9a96-b0a69dd24a67', - 'src': 'client', - 'bidRequestsCount': 1, - 'bidderRequestsCount': 1, - 'bidderWinsCount': 0 - }] - let request = spec.buildRequests(videoReq, { - auctionId: 'new-auction-id' + }; + let request = spec.buildRequests(bidRequests, bidRequest); + let data = JSON.parse(request.data); + expect(data.regs.gpp).to.equal('DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN'); + expect(data.regs.gpp_sid[0]).to.equal(5); }); - let data = JSON.parse(request.data); - data = data.imp[0]; - expect(data.video).to.exist; - }); - it('should build video impression with overwriting video params present in adunit.mediaTypes with bid.params', function() { - let videoReq = [{ - 'bidder': 'pubmatic', - 'params': { - 'adSlot': 'SLOT_NHB1@728x90', - 'publisherId': '5890', - 'video': { - 'mimes': ['video/mp4'], - 'protocols': [1, 2, 5], - 'linearity': 1 - } - }, - 'mediaTypes': { - 'video': { - 'playerSize': [ - [640, 480] - ], - 'protocols': [1, 2, 5], - 'context': 'instream', - 'mimes': ['video/flv'], - 'skip': 1, - 'linearity': 2 + it('Request params check without GPP Consent', function () { + let bidRequest = {}; + let request = spec.buildRequests(bidRequests, bidRequest); + let data = JSON.parse(request.data); + expect(data.regs).to.equal(undefined); + }); + + it('Request params check with GPP Consent read from ortb2', function () { + let bidRequest = { + ortb2: { + regs: { + 'gpp': 'DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN', + 'gpp_sid': [ + 5 + ] + } } - }, - 'adUnitCode': 'video1', - 'transactionId': 'adc36682-887c-41e9-9848-8b72c08332c0', - 'sizes': [ - [640, 480] - ], - 'bidId': '21b59b1353ba82', - 'bidderRequestId': '1a08245305e6dd', - 'auctionId': 'bad3a743-7491-4d19-9a96-b0a69dd24a67', - 'src': 'client', - 'bidRequestsCount': 1, - 'bidderRequestsCount': 1, - 'bidderWinsCount': 0 - }] - let request = spec.buildRequests(videoReq, { - auctionId: 'new-auction-id' + }; + let request = spec.buildRequests(bidRequests, bidRequest); + let data = JSON.parse(request.data); + expect(data.regs.gpp).to.equal('DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN'); + expect(data.regs.gpp_sid[0]).to.equal(5); + }); + }); + + describe('Fledge', function() { + it('should not send imp.ext.ae when FLEDGE is disabled, ', function () { + let bidRequest = Object.assign([], bidRequests); + bidRequest[0].ortb2Imp = { + ext: { ae: 1 } + }; + const req = spec.buildRequests(bidRequest, { ...bidRequest, fledgeEnabled: false }); + let data = JSON.parse(req.data); + if (data.imp[0].ext) { + expect(data.imp[0].ext).to.not.have.property('ae'); + } }); - let data = JSON.parse(request.data); - data = data.imp[0]; - expect(data.video).to.exist; - expect(data.video.linearity).to.equal(1); + it('when FLEDGE is enabled, should send whatever is set in ortb2imp.ext.ae in all bid requests', function () { + let bidRequest = Object.assign([], bidRequests); + delete bidRequest[0].params.test; + bidRequest[0].ortb2Imp = { + ext: { ae: 1 } + }; + const req = spec.buildRequests(bidRequest, { ...bidRequest, fledgeEnabled: true }); + let data = JSON.parse(req.data); + expect(data.imp[0].ext.ae).to.equal(1); + }); }); }); @@ -3428,7 +3591,8 @@ describe('PubMatic adapter', function () { expect(response[0].ttl).to.equal(300); expect(response[0].meta.networkId).to.equal(123); expect(response[0].adserverTargeting.hb_buyid_pubmatic).to.equal('BUYER-ID-987'); - expect(response[0].meta.buyerId).to.equal(976); + expect(response[0].meta.buyerId).to.equal('seat-id'); + expect(response[0].meta.dchain).to.equal('dchain'); expect(response[0].meta.clickUrl).to.equal('blackrock.com'); expect(response[0].meta.advertiserDomains[0]).to.equal('blackrock.com'); expect(response[0].referrer).to.include(data.site.ref); @@ -3493,16 +3657,17 @@ describe('PubMatic adapter', function () { data.imp[0].id = '2a5571261281d4'; request.data = JSON.stringify(data); let response = spec.interpretResponse(nativeBidResponse, request); + let assets = response[0].native.ortb.assets; expect(response).to.be.an('array').with.length.above(0); expect(response[0].native).to.exist.and.to.be.an('object'); expect(response[0].mediaType).to.exist.and.to.equal('native'); - expect(response[0].native.title).to.exist.and.to.be.an('string'); - expect(response[0].native.image).to.exist.and.to.be.an('object'); - expect(response[0].native.image.url).to.exist.and.to.be.an('string'); - expect(response[0].native.image.height).to.exist; - expect(response[0].native.image.width).to.exist; - expect(response[0].native.sponsoredBy).to.exist.and.to.be.an('string'); - expect(response[0].native.clickUrl).to.exist.and.to.be.an('string'); + expect(assets).to.be.an('array').with.length.above(0); + expect(assets[0].title).to.exist.and.to.be.an('object'); + expect(assets[1].img).to.exist.and.to.be.an('object'); + expect(assets[1].img.url).to.exist.and.to.be.an('string'); + expect(assets[1].img.h).to.exist; + expect(assets[1].img.w).to.exist; + expect(assets[2].data).to.exist.and.to.be.an('object'); }); it('should check for valid banner mediaType in case of multiformat request', function() { @@ -3514,14 +3679,6 @@ describe('PubMatic adapter', function () { expect(response[0].mediaType).to.equal('banner'); }); - it('should check for valid video mediaType in case of multiformat request', function() { - let request = spec.buildRequests(videoBidRequests, { - auctionId: 'new-auction-id' - }); - let response = spec.interpretResponse(videoBidResponse, request); - expect(response[0].mediaType).to.equal('video'); - }); - it('should check for valid native mediaType in case of multiformat request', function() { let request = spec.buildRequests(nativeBidRequests, { auctionId: 'new-auction-id' @@ -3531,28 +3688,6 @@ describe('PubMatic adapter', function () { expect(response[0].mediaType).to.equal('native'); }); - it('should assign renderer if bid is video and request is for outstream', function() { - let request = spec.buildRequests(outstreamBidRequest, validOutstreamBidRequest); - let response = spec.interpretResponse(outstreamVideoBidResponse, request); - expect(response[0].renderer).to.exist; - }); - - it('should not assign renderer if bidderRequest is not present', function() { - let request = spec.buildRequests(outstreamBidRequest, { - auctionId: 'new-auction-id' - }); - let response = spec.interpretResponse(outstreamVideoBidResponse, request); - expect(response[0].renderer).to.not.exist; - }); - - it('should not assign renderer if bid is video and request is for instream', function() { - let request = spec.buildRequests(videoBidRequests, { - auctionId: 'new-auction-id' - }); - let response = spec.interpretResponse(videoBidResponse, request); - expect(response[0].renderer).to.not.exist; - }); - it('should not assign renderer if bid is native', function() { let request = spec.buildRequests(nativeBidRequests, { auctionId: 'new-auction-id' @@ -3569,154 +3704,266 @@ describe('PubMatic adapter', function () { expect(response[0].renderer).to.not.exist; }); - it('should assign mediaType by reading bid.ext.mediaType', function() { - let newvideoRequests = [{ - 'bidder': 'pubmatic', - 'params': { - 'adSlot': 'SLOT_NHB1@728x90', - 'publisherId': '5670', - 'video': { - 'mimes': ['video/mp4'], - 'skippable': true, - 'protocols': [1, 2, 5], - 'linearity': 1 - } - }, - 'mediaTypes': { - 'video': { - 'playerSize': [ - [640, 480] - ], - 'protocols': [1, 2, 5], - 'context': 'instream', - 'mimes': ['video/flv'], - 'skippable': false, - 'skip': 1, - 'linearity': 2 - } - }, - 'adUnitCode': 'video1', - 'transactionId': '803e3750-0bbe-4ffe-a548-b6eca15087bf', - 'sizes': [ - [640, 480] - ], - 'bidId': '2c95df014cfe97', - 'bidderRequestId': '1fe59391566442', - 'auctionId': '3a4118ef-fb96-4416-b0b0-3cfc1cebc142', - 'src': 'client', - 'bidRequestsCount': 1, - 'bidderRequestsCount': 1, - 'bidderWinsCount': 0 - }]; - let newvideoBidResponses = { - 'body': { - 'id': '1621441141473', - 'cur': 'USD', - 'customdata': 'openrtb1', - 'ext': { - 'buyid': 'myBuyId' + if (FEATURES.VIDEO) { + it('should check for valid video mediaType in case of multiformat request', function() { + let request = spec.buildRequests(videoBidRequests, { + auctionId: 'new-auction-id' + }); + let response = spec.interpretResponse(videoBidResponse, request); + expect(response[0].mediaType).to.equal('video'); + }); + + it('should assign renderer if bid is video and request is for outstream', function() { + let request = spec.buildRequests(outstreamBidRequest, validOutstreamBidRequest); + let response = spec.interpretResponse(outstreamVideoBidResponse, request); + expect(response[0].renderer).to.exist; + }); + + it('should not assign renderer if bidderRequest is not present', function() { + let request = spec.buildRequests(outstreamBidRequest, { + auctionId: 'new-auction-id' + }); + let response = spec.interpretResponse(outstreamVideoBidResponse, request); + expect(response[0].renderer).to.not.exist; + }); + + it('should not assign renderer if bid is video and request is for instream', function() { + let request = spec.buildRequests(videoBidRequests, { + auctionId: 'new-auction-id' + }); + let response = spec.interpretResponse(videoBidResponse, request); + expect(response[0].renderer).to.not.exist; + }); + + it('should assign mediaType by reading bid.ext.mediaType', function() { + let newvideoRequests = [{ + 'bidder': 'pubmatic', + 'params': { + 'adSlot': 'SLOT_NHB1@728x90', + 'publisherId': '5670', + 'video': { + 'mimes': ['video/mp4'], + 'skippable': true, + 'protocols': [1, 2, 5], + 'linearity': 1 + } }, - 'seatbid': [{ - 'bid': [{ - 'id': '2c95df014cfe97', - 'impid': '2c95df014cfe97', - 'price': 4.2, - 'cid': 'test1', - 'crid': 'test2', - 'adm': "Acudeo CompatibleVAST 2.0 Instream Test 1VAST 2.0 Instream Test 1", - 'w': 0, - 'h': 0, - 'dealId': 'ASEA-MS-KLY-TTD-DESKTOP-ID-VID-6S-030420', - 'ext': { - 'bidtype': 1 - } - }], + 'mediaTypes': { + 'video': { + 'playerSize': [ + [640, 480] + ], + 'protocols': [1, 2, 5], + 'context': 'instream', + 'mimes': ['video/flv'], + 'skippable': false, + 'skip': 1, + 'linearity': 2 + } + }, + 'adUnitCode': 'video1', + 'transactionId': '803e3750-0bbe-4ffe-a548-b6eca15087bf', + 'sizes': [ + [640, 480] + ], + 'bidId': '2c95df014cfe97', + 'bidderRequestId': '1fe59391566442', + 'auctionId': '3a4118ef-fb96-4416-b0b0-3cfc1cebc142', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 + }]; + let newvideoBidResponses = { + 'body': { + 'id': '1621441141473', + 'cur': 'USD', + 'customdata': 'openrtb1', 'ext': { 'buyid': 'myBuyId' - } - }] - }, - 'headers': {} - } - let newrequest = spec.buildRequests(newvideoRequests, { - auctionId: 'new-auction-id' - }); - let newresponse = spec.interpretResponse(newvideoBidResponses, newrequest); - expect(newresponse[0].mediaType).to.equal('video') - }) + }, + 'seatbid': [{ + 'bid': [{ + 'id': '2c95df014cfe97', + 'impid': '2c95df014cfe97', + 'price': 4.2, + 'cid': 'test1', + 'crid': 'test2', + 'adm': "Acudeo CompatibleVAST 2.0 Instream Test 1VAST 2.0 Instream Test 1", + 'w': 0, + 'h': 0, + 'dealId': 'ASEA-MS-KLY-TTD-DESKTOP-ID-VID-6S-030420', + 'ext': { + 'bidtype': 1 + } + }], + 'ext': { + 'buyid': 'myBuyId' + } + }] + }, + 'headers': {} + } + let newrequest = spec.buildRequests(newvideoRequests, { + auctionId: 'new-auction-id' + }); + let newresponse = spec.interpretResponse(newvideoBidResponses, newrequest); + expect(newresponse[0].mediaType).to.equal('video') + }) - it('should assign mediaType even if bid.ext.mediaType does not exists', function() { - let newvideoRequests = [{ - 'bidder': 'pubmatic', - 'params': { - 'adSlot': 'SLOT_NHB1@728x90', - 'publisherId': '5670', - 'video': { - 'mimes': ['video/mp4'], - 'skippable': true, - 'protocols': [1, 2, 5], - 'linearity': 1 - } - }, - 'mediaTypes': { - 'video': { - 'playerSize': [ - [640, 480] - ], - 'protocols': [1, 2, 5], - 'context': 'instream', - 'mimes': ['video/flv'], - 'skippable': false, - 'skip': 1, - 'linearity': 2 - } - }, - 'adUnitCode': 'video1', - 'transactionId': '803e3750-0bbe-4ffe-a548-b6eca15087bf', - 'sizes': [ - [640, 480] - ], - 'bidId': '2c95df014cfe97', - 'bidderRequestId': '1fe59391566442', - 'auctionId': '3a4118ef-fb96-4416-b0b0-3cfc1cebc142', - 'src': 'client', - 'bidRequestsCount': 1, - 'bidderRequestsCount': 1, - 'bidderWinsCount': 0 - }]; - let newvideoBidResponses = { - 'body': { - 'id': '1621441141473', - 'cur': 'USD', - 'customdata': 'openrtb1', - 'ext': { - 'buyid': 'myBuyId' + it('should assign mediaType even if bid.ext.mediaType does not exists', function() { + let newvideoRequests = [{ + 'bidder': 'pubmatic', + 'params': { + 'adSlot': 'SLOT_NHB1@728x90', + 'publisherId': '5670', + 'video': { + 'mimes': ['video/mp4'], + 'skippable': true, + 'protocols': [1, 2, 5], + 'linearity': 1 + } + }, + 'mediaTypes': { + 'video': { + 'playerSize': [ + [640, 480] + ], + 'protocols': [1, 2, 5], + 'context': 'instream', + 'mimes': ['video/flv'], + 'skippable': false, + 'skip': 1, + 'linearity': 2 + } }, - 'seatbid': [{ - 'bid': [{ - 'id': '2c95df014cfe97', - 'impid': '2c95df014cfe97', - 'price': 4.2, - 'cid': 'test1', - 'crid': 'test2', - 'adm': "Acudeo CompatibleVAST 2.0 Instream Test 1VAST 2.0 Instream Test 1", - 'w': 0, - 'h': 0, - 'dealId': 'ASEA-MS-KLY-TTD-DESKTOP-ID-VID-6S-030420' - }], + 'adUnitCode': 'video1', + 'transactionId': '803e3750-0bbe-4ffe-a548-b6eca15087bf', + 'sizes': [ + [640, 480] + ], + 'bidId': '2c95df014cfe97', + 'bidderRequestId': '1fe59391566442', + 'auctionId': '3a4118ef-fb96-4416-b0b0-3cfc1cebc142', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 + }]; + let newvideoBidResponses = { + 'body': { + 'id': '1621441141473', + 'cur': 'USD', + 'customdata': 'openrtb1', 'ext': { 'buyid': 'myBuyId' - } - }] - }, - 'headers': {} - } - let newrequest = spec.buildRequests(newvideoRequests, { - auctionId: 'new-auction-id' + }, + 'seatbid': [{ + 'bid': [{ + 'id': '2c95df014cfe97', + 'impid': '2c95df014cfe97', + 'price': 4.2, + 'cid': 'test1', + 'crid': 'test2', + 'adm': "Acudeo CompatibleVAST 2.0 Instream Test 1VAST 2.0 Instream Test 1", + 'w': 0, + 'h': 0, + 'dealId': 'ASEA-MS-KLY-TTD-DESKTOP-ID-VID-6S-030420' + }], + 'ext': { + 'buyid': 'myBuyId' + } + }] + }, + 'headers': {} + } + let newrequest = spec.buildRequests(newvideoRequests, { + auctionId: 'new-auction-id' + }); + let newresponse = spec.interpretResponse(newvideoBidResponses, newrequest); + expect(newresponse[0].mediaType).to.equal('video') }); - let newresponse = spec.interpretResponse(newvideoBidResponses, newrequest); - expect(newresponse[0].mediaType).to.equal('video') - }) + } + }); + + describe('Preapare metadata', function () { + it('Should copy all fields from ext to meta', function () { + const bid = { + 'adomain': [ + 'mystartab.com' + ], + cat: ['IAB_CATEGORY'], + ext: { + advid: '12', + 'dspid': 6, + 'deal_channel': 1, + 'bidtype': 0, + advertiserId: 'adid', + // networkName: 'nwnm', + // primaryCatId: 'pcid', + // advertiserName: 'adnm', + // agencyId: 'agid', + // agencyName: 'agnm', + // brandId: 'brid', + // brandName: 'brnm', + // dchain: 'dc', + // demandSource: 'ds', + // secondaryCatIds: ['secondaryCatIds'] + } + }; + + const br = {}; + prepareMetaObject(br, bid, null); + expect(br.meta.networkId).to.equal(6); // dspid + expect(br.meta.buyerId).to.equal('12'); // adid + expect(br.meta.advertiserId).to.equal('12'); + // expect(br.meta.networkName).to.equal('nwnm'); + expect(br.meta.primaryCatId).to.equal('IAB_CATEGORY'); + // expect(br.meta.advertiserName).to.equal('adnm'); + expect(br.meta.agencyId).to.equal('12'); + // expect(br.meta.agencyName).to.equal('agnm'); + expect(br.meta.brandId).to.equal('mystartab.com'); + // expect(br.meta.brandName).to.equal('brnm'); + // expect(br.meta.dchain).to.equal('dc'); + expect(br.meta.demandSource).to.equal(6); + expect(br.meta.secondaryCatIds).to.be.an('array').with.length.above(0); + expect(br.meta.secondaryCatIds[0]).to.equal('IAB_CATEGORY'); + expect(br.meta.advertiserDomains).to.be.an('array').with.length.above(0); // adomain + expect(br.meta.clickUrl).to.equal('mystartab.com'); // adomain + }); + + it('Should be empty, when ext and adomain is absent in bid object', function () { + const bid = {}; + const br = {}; + prepareMetaObject(br, bid, null); + expect(Object.keys(br.meta).length).to.equal(0); + }); + + it('Should be empty, when ext and adomain will not have properties', function () { + const bid = { + 'adomain': [], + ext: {} + }; + const br = {}; + prepareMetaObject(br, bid, null); + expect(Object.keys(br.meta).length).to.equal(0); + expect(br.meta.advertiserDomains).to.equal(undefined); // adomain + expect(br.meta.clickUrl).to.equal(undefined); // adomain + }); + + it('Should have buyerId,advertiserId, agencyId value of site ', function () { + const bid = { + 'adomain': [], + ext: { + advid: '12', + } + }; + const br = {}; + prepareMetaObject(br, bid, '5100'); + expect(br.meta.buyerId).to.equal('5100'); // adid + expect(br.meta.advertiserId).to.equal('5100'); + expect(br.meta.agencyId).to.equal('5100'); + }); }); describe('getUserSyncs', function() { @@ -3814,206 +4061,106 @@ describe('PubMatic adapter', function () { type: 'image', url: `${syncurl_image}&gdpr=1&gdpr_consent=foo&us_privacy=1NYN&coppa=1` }]); }); - }); - describe('JW player segment data for S2S', function() { - let sandbox = sinon.sandbox.create(); - beforeEach(function () { - sandbox = sinon.sandbox.create(); - }); - afterEach(function() { - sandbox.restore(); - }); - it('Should append JW player segment data to dctr values in auction endpoint', function() { - var videoAdUnit = { - 'bidderCode': 'pubmatic', - 'bids': [ - { - 'bidder': 'pubmatic', - 'params': { - 'publisherId': '156276', - 'adSlot': 'pubmatic_video2', - 'dctr': 'key1=123|key2=345', - 'pmzoneid': '1243', - 'video': { - 'mimes': ['video/mp4', 'video/x-flv'], - 'skippable': true, - 'minduration': 5, - 'maxduration': 30, - 'startdelay': 5, - 'playbackmethod': [1, 3], - 'api': [1, 2], - 'protocols': [2, 3], - 'battr': [13, 14], - 'linearity': 1, - 'placement': 2, - 'minbitrate': 10, - 'maxbitrate': 10 - } - }, - 'rtd': { - 'jwplayer': { - 'targeting': { - 'segments': ['80011026', '80011035'], - 'content': { - 'id': 'jw_d9J2zcaA' - } - } - } - }, - 'bid_id': '17a6771be26cc4', - 'ortb2Imp': { - 'ext': { - 'data': { - 'pbadslot': 'abcd', - 'jwTargeting': { - 'playerID': 'myElement1', - 'mediaID': 'd9J2zcaA' - } - } - } - } - } - ], - 'auctionStart': 1630923178417, - 'timeout': 1000, - 'src': 's2s' - } + describe('GPP', function() { + it('should return userSync url without Gpp consent if gppConsent is undefined', () => { + const result = spec.getUserSyncs({iframeEnabled: true}, undefined, undefined, undefined, undefined); + expect(result).to.deep.equal([{ + type: 'iframe', url: `${syncurl_iframe}` + }]); + }); - spec.transformBidParams(bidRequests[0].params, true, videoAdUnit); - expect(bidRequests[0].params.dctr).to.equal('key1:val1,val2|key2:val1|jw-id=jw_d9J2zcaA|jw-80011026=1|jw-80011035=1'); - }); - it('Should send only JW player segment data in auction endpoint, if dctr is missing', function() { - var videoAdUnit = { - 'bidderCode': 'pubmatic', - 'bids': [ - { - 'bidder': 'pubmatic', - 'params': { - 'publisherId': '156276', - 'adSlot': 'pubmatic_video2', - 'dctr': 'key1=123|key2=345', - 'pmzoneid': '1243', - 'video': { - 'mimes': ['video/mp4', 'video/x-flv'], - 'skippable': true, - 'minduration': 5, - 'maxduration': 30, - 'startdelay': 5, - 'playbackmethod': [1, 3], - 'api': [1, 2], - 'protocols': [2, 3], - 'battr': [13, 14], - 'linearity': 1, - 'placement': 2, - 'minbitrate': 10, - 'maxbitrate': 10 - } - }, - 'rtd': { - 'jwplayer': { - 'targeting': { - 'segments': ['80011026', '80011035'], - 'content': { - 'id': 'jw_d9J2zcaA' - } - } - } - }, - 'bid_id': '17a6771be26cc4', - 'ortb2Imp': { - 'ext': { - 'data': { - 'pbadslot': 'abcd', - 'jwTargeting': { - 'playerID': 'myElement1', - 'mediaID': 'd9J2zcaA' - } - } - } - } - } - ], - 'auctionStart': 1630923178417, - 'timeout': 1000, - 'src': 's2s' - } + it('should return userSync url without Gpp consent if gppConsent.gppString is undefined', () => { + const gppConsent = { applicableSections: ['5'] }; + const result = spec.getUserSyncs({iframeEnabled: true}, undefined, undefined, undefined, gppConsent); + expect(result).to.deep.equal([{ + type: 'iframe', url: `${syncurl_iframe}` + }]); + }); - delete bidRequests[0].params.dctr; - spec.transformBidParams(bidRequests[0].params, true, videoAdUnit); - expect(bidRequests[0].params.dctr).to.equal('jw-id=jw_d9J2zcaA|jw-80011026=1|jw-80011035=1'); + it('should return userSync url without Gpp consent if gppConsent.applicableSections is undefined', () => { + const gppConsent = { gppString: 'DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN' }; + const result = spec.getUserSyncs({iframeEnabled: true}, undefined, undefined, undefined, gppConsent); + expect(result).to.deep.equal([{ + type: 'iframe', url: `${syncurl_iframe}` + }]); + }); + + it('should return userSync url without Gpp consent if gppConsent.applicableSections is an empty array', () => { + const gppConsent = { gppString: 'DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN', applicableSections: [] }; + const result = spec.getUserSyncs({iframeEnabled: true}, undefined, undefined, undefined, gppConsent); + expect(result).to.deep.equal([{ + type: 'iframe', url: `${syncurl_iframe}` + }]); + }); + + it('should concatenate gppString and applicableSections values in the returned userSync iframe url', () => { + const gppConsent = { gppString: 'DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN', applicableSections: [5] }; + const result = spec.getUserSyncs({iframeEnabled: true}, undefined, undefined, undefined, gppConsent); + expect(result).to.deep.equal([{ + type: 'iframe', url: `${syncurl_iframe}&gpp=${encodeURIComponent(gppConsent.gppString)}&gpp_sid=${encodeURIComponent(gppConsent.applicableSections)}` + }]); + }); + + it('should concatenate gppString and applicableSections values in the returned userSync image url', () => { + const gppConsent = { gppString: 'DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN', applicableSections: [5] }; + const result = spec.getUserSyncs({iframeEnabled: false}, undefined, undefined, undefined, gppConsent); + expect(result).to.deep.equal([{ + type: 'image', url: `${syncurl_image}&gpp=${encodeURIComponent(gppConsent.gppString)}&gpp_sid=${encodeURIComponent(gppConsent.applicableSections)}` + }]); + }); }); + }); - it('Should not send any JW player segment data in auction endpoint, if it is not available', function() { - var videoAdUnit = { - 'bidderCode': 'pubmatic', - 'bids': [ - { - 'bidder': 'pubmatic', - 'params': { - 'publisherId': '156276', - 'adSlot': 'pubmatic_video2', - 'dctr': 'key1=123|key2=345', - 'pmzoneid': '1243', - 'video': { - 'mimes': ['video/mp4', 'video/x-flv'], - 'skippable': true, - 'minduration': 5, - 'maxduration': 30, - 'startdelay': 5, - 'playbackmethod': [1, 3], - 'api': [1, 2], - 'protocols': [2, 3], - 'battr': [13, 14], - 'linearity': 1, - 'placement': 2, - 'minbitrate': 10, - 'maxbitrate': 10 - } - }, - 'bid_id': '17a6771be26cc4', - 'ortb2Imp': { - 'ext': { - 'data': { - 'pbadslot': 'abcd', - 'jwTargeting': { - 'playerID': 'myElement1', - 'mediaID': 'd9J2zcaA' - } - } - } - } - } - ], - 'auctionStart': 1630923178417, - 'timeout': 1000, - 'src': 's2s' + if (FEATURES.VIDEO) { + describe('Checking for Video.Placement property', function() { + let sandbox, utilsMock; + const adUnit = 'Div1'; + const msg_placement_missing = 'Video.Placement param missing for Div1'; + let videoData = { + battr: [6, 7], + skipafter: 15, + maxduration: 50, + context: 'instream', + playerSize: [640, 480], + skip: 0, + connectiontype: [1, 2, 6], + skipmin: 10, + minduration: 10, + mimes: ['video/mp4', 'video/x-flv'], } - spec.transformBidParams(bidRequests[0].params, true, videoAdUnit); - expect(bidRequests[0].params.dctr).to.equal('key1:val1,val2|key2:val1'); + beforeEach(() => { + utilsMock = sinon.mock(utils); + sandbox = sinon.sandbox.create(); + sandbox.spy(utils, 'logWarn'); + }); + + afterEach(() => { + utilsMock.restore(); + sandbox.restore(); + }) + + it('should log Video.Placement param missing', function() { + checkVideoPlacement(videoData, adUnit); + sinon.assert.calledWith(utils.logWarn, msg_placement_missing); + }) + it('shoud not log Video.Placement param missing', function() { + videoData['placement'] = 1; + checkVideoPlacement(videoData, adUnit); + sinon.assert.neverCalledWith(utils.logWarn, msg_placement_missing); + }) }); - }) + } + }); - describe('Checking for Video.Placement property', function() { - let sandbox, utilsMock; - const adUnit = 'Div1'; - const msg_placement_missing = 'Video.Placement param missing for Div1'; - let videoData = { - battr: [6, 7], - skipafter: 15, - maxduration: 50, - context: 'instream', - playerSize: [640, 480], - skip: 0, - connectiontype: [1, 2, 6], - skipmin: 10, - minduration: 10, - mimes: ['video/mp4', 'video/x-flv'], - } + if (FEATURES.VIDEO) { + describe('Video request params', function() { + let sandbox, utilsMock, newVideoRequest; beforeEach(() => { utilsMock = sinon.mock(utils); sandbox = sinon.sandbox.create(); sandbox.spy(utils, 'logWarn'); + newVideoRequest = utils.deepClone(videoBidRequests) }); afterEach(() => { @@ -4021,112 +4168,87 @@ describe('PubMatic adapter', function () { sandbox.restore(); }) - it('should log Video.Placement param missing', function() { - checkVideoPlacement(videoData, adUnit); - sinon.assert.calledWith(utils.logWarn, msg_placement_missing); - }) - it('shoud not log Video.Placement param missing', function() { - videoData['placement'] = 1; - checkVideoPlacement(videoData, adUnit); - sinon.assert.neverCalledWith(utils.logWarn, msg_placement_missing); - }) - }); - }); - - describe('Video request params', function() { - let sandbox, utilsMock, newVideoRequest; - beforeEach(() => { - utilsMock = sinon.mock(utils); - sandbox = sinon.sandbox.create(); - sandbox.spy(utils, 'logWarn'); - newVideoRequest = utils.deepClone(videoBidRequests) - }); - - afterEach(() => { - utilsMock.restore(); - sandbox.restore(); - }) - - it('Should log warning if video params from mediaTypes and params obj of bid are not present', function () { - delete newVideoRequest[0].mediaTypes.video; - delete newVideoRequest[0].params.video; - - let request = spec.buildRequests(newVideoRequest, { - auctionId: 'new-auction-id' - }); - - sinon.assert.calledOnce(utils.logWarn); - expect(request).to.equal(undefined); - }); + it('Should log warning if video params from mediaTypes and params obj of bid are not present', function () { + delete newVideoRequest[0].mediaTypes.video; + delete newVideoRequest[0].params.video; - it('Should consider video params from mediaType object of bid', function () { - delete newVideoRequest[0].params.video; + let request = spec.buildRequests(newVideoRequest, { + auctionId: 'new-auction-id' + }); - let request = spec.buildRequests(newVideoRequest, { - auctionId: 'new-auction-id' + sinon.assert.calledOnce(utils.logWarn); + expect(request).to.equal(undefined); }); - let data = JSON.parse(request.data); - expect(data.imp[0].video).to.exist; - expect(data.imp[0]['video']['w']).to.equal(videoBidRequests[0].mediaTypes.video.playerSize[0]); - expect(data.imp[0]['video']['h']).to.equal(videoBidRequests[0].mediaTypes.video.playerSize[1]); - expect(data.imp[0]['video']['battr']).to.equal(undefined); - }); - describe('Assign Deal Tier (i.e. prebidDealPriority)', function () { - let videoSeatBid, request, newBid; - // let data = JSON.parse(request.data); - beforeEach(function () { - videoSeatBid = videoBidResponse.body.seatbid[0].bid[0]; - // const adpodValidOutstreamBidRequest = validOutstreamBidRequest.bids[0].mediaTypes.video.context = 'adpod'; - request = spec.buildRequests(bidRequests, validOutstreamBidRequest); - newBid = { - requestId: '47acc48ad47af5' - }; - videoSeatBid.ext = videoSeatBid.ext || {}; - videoSeatBid.ext.video = videoSeatBid.ext.video || {}; - // videoBidRequests[0].mediaTypes.video = videoBidRequests[0].mediaTypes.video || {}; - }); - - it('should not assign video object if deal priority is missing', function () { - assignDealTier(newBid, videoSeatBid, request); - expect(newBid.video).to.equal(undefined); - expect(newBid.video).to.not.exist; - }); + it('Should consider video params from mediaType object of bid', function () { + delete newVideoRequest[0].params.video; - it('should not assign video object if context is not a adpod', function () { - videoSeatBid.ext.prebiddealpriority = 5; - assignDealTier(newBid, videoSeatBid, request); - expect(newBid.video).to.equal(undefined); - expect(newBid.video).to.not.exist; + let request = spec.buildRequests(newVideoRequest, { + auctionId: 'new-auction-id' + }); + let data = JSON.parse(request.data); + expect(data.imp[0].video).to.exist; + expect(data.imp[0]['video']['w']).to.equal(videoBidRequests[0].mediaTypes.video.playerSize[0]); + expect(data.imp[0]['video']['h']).to.equal(videoBidRequests[0].mediaTypes.video.playerSize[1]); + expect(data.imp[0]['video']['battr']).to.equal(undefined); }); - describe('when video deal tier object is present', function () { + describe('Assign Deal Tier (i.e. prebidDealPriority)', function () { + let videoSeatBid, request, newBid; + // let data = JSON.parse(request.data); beforeEach(function () { - videoSeatBid.ext.prebiddealpriority = 5; - request.bidderRequest.bids[0].mediaTypes.video = { - ...request.bidderRequest.bids[0].mediaTypes.video, - context: 'adpod', - maxduration: 50 + videoSeatBid = videoBidResponse.body.seatbid[0].bid[0]; + // const adpodValidOutstreamBidRequest = validOutstreamBidRequest.bids[0].mediaTypes.video.context = 'adpod'; + request = spec.buildRequests(bidRequests, validOutstreamBidRequest); + newBid = { + requestId: '47acc48ad47af5' }; + videoSeatBid.ext = videoSeatBid.ext || {}; + videoSeatBid.ext.video = videoSeatBid.ext.video || {}; + // videoBidRequests[0].mediaTypes.video = videoBidRequests[0].mediaTypes.video || {}; }); - it('should set video deal tier object, when maxduration is present in ext', function () { + it('should not assign video object if deal priority is missing', function () { assignDealTier(newBid, videoSeatBid, request); - expect(newBid.video.durationSeconds).to.equal(50); - expect(newBid.video.context).to.equal('adpod'); - expect(newBid.video.dealTier).to.equal(5); + expect(newBid.video).to.equal(undefined); + expect(newBid.video).to.not.exist; }); - it('should set video deal tier object, when duration is present in ext', function () { - videoSeatBid.ext.video.duration = 20; + it('should not assign video object if context is not a adpod', function () { + videoSeatBid.ext.prebiddealpriority = 5; assignDealTier(newBid, videoSeatBid, request); - expect(newBid.video.durationSeconds).to.equal(20); - expect(newBid.video.context).to.equal('adpod'); - expect(newBid.video.dealTier).to.equal(5); + expect(newBid.video).to.equal(undefined); + expect(newBid.video).to.not.exist; + }); + + describe('when video deal tier object is present', function () { + beforeEach(function () { + videoSeatBid.ext.prebiddealpriority = 5; + request.bidderRequest.bids[0].mediaTypes.video = { + ...request.bidderRequest.bids[0].mediaTypes.video, + context: 'adpod', + maxduration: 50 + }; + }); + + it('should set video deal tier object, when maxduration is present in ext', function () { + assignDealTier(newBid, videoSeatBid, request); + expect(newBid.video.durationSeconds).to.equal(50); + expect(newBid.video.context).to.equal('adpod'); + expect(newBid.video.dealTier).to.equal(5); + }); + + it('should set video deal tier object, when duration is present in ext', function () { + videoSeatBid.ext.video.duration = 20; + assignDealTier(newBid, videoSeatBid, request); + expect(newBid.video.durationSeconds).to.equal(20); + expect(newBid.video.context).to.equal('adpod'); + expect(newBid.video.dealTier).to.equal(5); + }); }); }); }); - }); + } describe('Marketplace params', function () { let sandbox, utilsMock, newBidRequests, newBidResponses; diff --git a/test/spec/modules/pubwiseBidAdapter_spec.js b/test/spec/modules/pubwiseBidAdapter_spec.js index d7b7a527485..49e36c05d1e 100644 --- a/test/spec/modules/pubwiseBidAdapter_spec.js +++ b/test/spec/modules/pubwiseBidAdapter_spec.js @@ -2,7 +2,7 @@ import {expect} from 'chai'; import {spec} from 'modules/pubwiseBidAdapter.js'; -import {_checkMediaType} from 'modules/pubwiseBidAdapter.js'; // this is exported only for testing so maintaining the JS convention of _ to indicate the intent +import {_checkVideoPlacement, _checkMediaType} from 'modules/pubwiseBidAdapter.js'; // this is exported only for testing so maintaining the JS convention of _ to indicate the intent import {_parseAdSlot} from 'modules/pubwiseBidAdapter.js'; // this is exported only for testing so maintaining the JS convention of _ to indicate the intent import * as utils from 'src/utils.js'; @@ -101,6 +101,7 @@ const sampleValidBannerBidRequest = { }, ortb2Imp: { ext: { + tid: '2001a8b2-3bcf-417d-b64f-92641dae21e0', data: { adserver: { name: 'gam', @@ -125,7 +126,6 @@ const sampleValidBannerBidRequest = { } }, 'adUnitCode': 'div-gpt-ad-1460505748561-0', - 'transactionId': '2001a8b2-3bcf-417d-b64f-92641dae21e0', 'sizes': [ [ 300, @@ -179,6 +179,7 @@ const sampleValidBidRequests = [ }, ortb2Imp: { ext: { + tid: '2c8cd034-f068-4419-8c30-f07292c0d17b', data: { adserver: { name: 'gam', @@ -213,7 +214,6 @@ const sampleValidBidRequests = [ } }, 'adUnitCode': 'div-gpt-ad-1460505748561-1', - 'transactionId': '2c8cd034-f068-4419-8c30-f07292c0d17b', 'sizes': [], 'bidId': '30ab7516a51a7c', 'bidderRequestId': '18a45bff5ff705', @@ -252,6 +252,7 @@ const sampleBidderBannerRequest = { }, ortb2Imp: { ext: { + tid: '2001a8b2-3bcf-417d-b64f-92641dae21e0', data: { adserver: { name: 'gam', @@ -272,7 +273,6 @@ const sampleBidderBannerRequest = { } }, 'adUnitCode': 'div-gpt-ad-1460505748561-0', - 'transactionId': '2001a8b2-3bcf-417d-b64f-92641dae21e0', 'sizes': [ [ 300, @@ -299,7 +299,11 @@ const sampleBidderBannerRequest = { const sampleBidderRequest = { 'bidderCode': 'pubwise', - 'auctionId': '9f20663c-4629-4b5c-bff6-ff3aa8319358', + ortb2: { + source: { + tid: '9f20663c-4629-4b5c-bff6-ff3aa8319358', + } + }, 'bidderRequestId': '18a45bff5ff705', 'bids': [ sampleBidderBannerRequest, @@ -486,6 +490,28 @@ const samplePBBidObjects = [ ]; describe('PubWiseAdapter', function () { + describe('Handles Params Properly', function () { + it('properly sets the default endpoint', function () { + const referenceEndpoint = 'https://bid.pubwise.io/prebid'; + let endpointBidRequest = utils.deepClone(sampleValidBidRequests); + // endpointBidRequest.forEach((bidRequest) => { + // bidRequest.params.endpoint_url = newEndpoint; + // }); + let result = spec.buildRequests(endpointBidRequest, {auctionId: 'placeholder'}); + expect(result.url).to.equal(referenceEndpoint); + }); + + it('allows endpoint to be reset', function () { + const newEndpoint = 'http://www.pubwise.io/endpointtest'; + let endpointBidRequest = utils.deepClone(sampleValidBidRequests); + endpointBidRequest.forEach((bidRequest) => { + bidRequest.params.endpoint_url = newEndpoint; + }); + let result = spec.buildRequests(endpointBidRequest, {auctionId: 'placeholder'}); + expect(result.url).to.equal(newEndpoint); + }); + }); + describe('Properly Validates Bids', function () { it('valid bid', function () { let validBid = { @@ -542,8 +568,8 @@ describe('PubWiseAdapter', function () { it('should handle complex bidRequest', function() { let request = spec.buildRequests(sampleValidBidRequests, sampleBidderRequest); expect(request.bidderRequest).to.equal(sampleBidderRequest, "Bid Request Doesn't Match Sample"); - expect(request.data.source.tid).to.equal(sampleBidderRequest.auctionId, 'AuctionId -> source.tid Mismatch'); - expect(request.data.imp[0].ext.tid).to.equal(sampleBidderRequest.bids[0].transactionId, 'TransactionId -> ext.tid Mismatch'); + expect(request.data.source.tid).to.equal(sampleBidderRequest.ortb2.source.tid, 'source.tid -> source.tid Mismatch'); + expect(request.data.imp[0].ext.tid).to.equal(sampleBidderRequest.bids[0].ortb2Imp.ext.tid, 'ext.tid -> ext.tid Mismatch'); }); it('must conform to API for buildRequests', function() { let request = spec.buildRequests(sampleValidBidRequests); @@ -555,14 +581,14 @@ describe('PubWiseAdapter', function () { it('identifies native adm type', function() { let adm = '{"ver":"1.2","assets":[{"title":{"text":"PubWise Test"}},{"img":{"type":3,"url":"http://www.pubwise.io"}},{"img":{"type":1,"url":"http://www.pubwise.io"}},{"data":{"type":2,"value":"PubWise Test Desc"}},{"data":{"type":1,"value":"PubWise.io"}}],"link":{"url":""}}'; let newBid = {mediaType: 'unknown'}; - _checkMediaType(adm, newBid); + _checkMediaType({adm}, newBid); expect(newBid.mediaType).to.equal('native', adm + ' Is a Native adm'); }); it('identifies banner adm type', function() { let adm = '

PubWise Test Bid

'; let newBid = {mediaType: 'unknown'}; - _checkMediaType(adm, newBid); + _checkMediaType({adm}, newBid); expect(newBid.mediaType).to.equal('banner', adm + ' Is a Banner adm'); }); }); @@ -582,4 +608,292 @@ describe('PubWiseAdapter', function () { expect(pbResponse).to.deep.equal(samplePBBidObjects); }); }); + + describe('Video Testing', function () { + /** + * Video Testing + */ + + const videoBidRequests = + [ + { + code: 'video1', + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'instream' + } + }, + bidder: 'pwbid', + bidId: '22bddb28db77d', + adUnitCode: 'Div1', + params: { + siteId: 'xxxxxx', + video: { + mimes: ['video/mp4', 'video/x-flv'], + skippable: true, + minduration: 5, + maxduration: 30, + startdelay: 5, + playbackmethod: [1, 3], + api: [1, 2], + protocols: [2, 3], + battr: [13, 14], + linearity: 1, + placement: 2, + minbitrate: 10, + maxbitrate: 10 + } + } + } + ]; + + let newvideoRequests = [{ + 'bidder': 'pwbid', + 'params': { + 'siteId': 'xxxxx', + 'video': { + 'mimes': ['video/mp4'], + 'skippable': true, + 'protocols': [1, 2, 5], + 'linearity': 1 + } + }, + 'mediaTypes': { + 'video': { + 'playerSize': [ + [640, 480] + ], + 'protocols': [1, 2, 5], + 'context': 'instream', + 'mimes': ['video/flv'], + 'skippable': false, + 'skip': 1, + 'linearity': 2 + } + }, + 'adUnitCode': 'video1', + 'transactionId': '803e3750-0bbe-4ffe-a548-b6eca15087bf', + 'sizes': [ + [640, 480] + ], + 'bidId': '2c95df014cfe97', + 'bidderRequestId': '1fe59391566442', + 'auctionId': '3a4118ef-fb96-4416-b0b0-3cfc1cebc142', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 + }]; + + let newvideoBidResponses = { + 'body': { + 'id': '1621441141473', + 'cur': 'USD', + 'customdata': 'openrtb1', + 'ext': { + 'buyid': 'myBuyId' + }, + 'seatbid': [{ + 'bid': [{ + 'id': '2c95df014cfe97', + 'impid': '2c95df014cfe97', + 'price': 4.2, + 'cid': 'test1', + 'crid': 'test2', + 'adm': "Acudeo CompatibleVAST 2.0 Instream Test 1VAST 2.0 Instream Test 1", + 'w': 0, + 'h': 0 + }], + 'ext': { + 'buyid': 'myBuyId' + } + }] + }, + 'headers': {} + }; + + let videoBidResponse = { + 'body': { + 'id': '93D3BAD6-E2E2-49FB-9D89-920B1761C865', + 'seatbid': [{ + 'bid': [{ + 'id': '74858439-49D7-4169-BA5D-44A046315B2F', + 'impid': '22bddb28db77d', + 'price': 1.3, + 'adm': '', + 'h': 250, + 'w': 300, + 'ext': { + 'deal_channel': 6 + } + }] + }] + } + }; + + it('Request params check for video ad', function () { + let request = spec.buildRequests(videoBidRequests, { + auctionId: 'new-auction-id' + }); + let data = request.data; + expect(data.imp[0].video).to.exist; + expect(data.imp[0].tagid).to.equal('Div1'); + expect(data.imp[0]['video']['mimes']).to.exist.and.to.be.an('array'); + expect(data.imp[0]['video']['mimes'][0]).to.equal(videoBidRequests[0].params.video['mimes'][0]); + expect(data.imp[0]['video']['mimes'][1]).to.equal(videoBidRequests[0].params.video['mimes'][1]); + expect(data.imp[0]['video']['minduration']).to.equal(videoBidRequests[0].params.video['minduration']); + expect(data.imp[0]['video']['maxduration']).to.equal(videoBidRequests[0].params.video['maxduration']); + expect(data.imp[0]['video']['startdelay']).to.equal(videoBidRequests[0].params.video['startdelay']); + + expect(data.imp[0]['video']['playbackmethod']).to.exist.and.to.be.an('array'); + expect(data.imp[0]['video']['playbackmethod'][0]).to.equal(videoBidRequests[0].params.video['playbackmethod'][0]); + expect(data.imp[0]['video']['playbackmethod'][1]).to.equal(videoBidRequests[0].params.video['playbackmethod'][1]); + + expect(data.imp[0]['video']['api']).to.exist.and.to.be.an('array'); + expect(data.imp[0]['video']['api'][0]).to.equal(videoBidRequests[0].params.video['api'][0]); + expect(data.imp[0]['video']['api'][1]).to.equal(videoBidRequests[0].params.video['api'][1]); + + expect(data.imp[0]['video']['protocols']).to.exist.and.to.be.an('array'); + expect(data.imp[0]['video']['protocols'][0]).to.equal(videoBidRequests[0].params.video['protocols'][0]); + expect(data.imp[0]['video']['protocols'][1]).to.equal(videoBidRequests[0].params.video['protocols'][1]); + + expect(data.imp[0]['video']['battr']).to.exist.and.to.be.an('array'); + expect(data.imp[0]['video']['battr'][0]).to.equal(videoBidRequests[0].params.video['battr'][0]); + expect(data.imp[0]['video']['battr'][1]).to.equal(videoBidRequests[0].params.video['battr'][1]); + + expect(data.imp[0]['video']['linearity']).to.equal(videoBidRequests[0].params.video['linearity']); + expect(data.imp[0]['video']['placement']).to.equal(videoBidRequests[0].params.video['placement']); + expect(data.imp[0]['video']['minbitrate']).to.equal(videoBidRequests[0].params.video['minbitrate']); + expect(data.imp[0]['video']['maxbitrate']).to.equal(videoBidRequests[0].params.video['maxbitrate']); + + expect(data.imp[0]['video']['w']).to.equal(videoBidRequests[0].mediaTypes.video.playerSize[0]); + expect(data.imp[0]['video']['h']).to.equal(videoBidRequests[0].mediaTypes.video.playerSize[1]); + }); + + it('should assign mediaType even if bid.ext.mediaType does not exists', function() { + let newrequest = spec.buildRequests(newvideoRequests, { + auctionId: 'new-auction-id' + }); + let newresponse = spec.interpretResponse(newvideoBidResponses, newrequest); + expect(newresponse[0].mediaType).to.equal('video'); + }); + + it('should not assign renderer if bid is video and request is for instream', function() { + let request = spec.buildRequests(videoBidRequests, { + auctionId: 'new-auction-id' + }); + let response = spec.interpretResponse(videoBidResponse, request); + expect(response[0].renderer).to.not.exist; + }); + + it('should process instream and outstream', function() { + let validOutstreamRequest = + { + code: 'video1', + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'outstream' + } + }, + bidder: 'pwbid', + bidId: '47acc48ad47af5', + requestId: '0fb4905b-1234-4152-86be-c6f6d259ba99', + bidderRequestId: '1c56ad30b9b8ca8', + transactionId: '92489f71-1bf2-49a0-adf9-000cea934729', + params: { + siteId: 'xxxxx', + adSlot: 'Div1', // ad_id or tagid + video: { + mimes: ['video/mp4', 'video/x-flv'], + skippable: true, + minduration: 5, + maxduration: 30 + } + } + }; + + let outstreamBidRequest = + [ + validOutstreamRequest + ]; + + let validInstreamRequest = { + code: 'video1', + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'instream' + } + }, + bidder: 'pwbid', + bidId: '47acc48ad47af5', + requestId: '0fb4905b-1234-4152-86be-c6f6d259ba99', + bidderRequestId: '1c56ad30b9b8ca8', + transactionId: '92489f71-1bf2-49a0-adf9-000cea934729', + params: { + siteId: 'xxxxx', + adSlot: 'Div1', // ad_id or tagid + video: { + mimes: ['video/mp4', 'video/x-flv'], + skippable: true, + minduration: 5, + maxduration: 30 + } + } + }; + + let instreamBidRequest = + [ + validInstreamRequest + ]; + + let outstreamRequest = spec.isBidRequestValid(validOutstreamRequest); + expect(outstreamRequest).to.equal(false); + + let instreamRequest = spec.isBidRequestValid(validInstreamRequest); + expect(instreamRequest).to.equal(true); + }); + + describe('Checking for Video.Placement property', function() { + let sandbox, utilsMock; + const adUnit = 'DivCheckPlacement'; + const msg_placement_missing = 'PubWise: Video.Placement param missing for DivCheckPlacement'; + let videoData = { + battr: [6, 7], + skipafter: 15, + maxduration: 50, + context: 'instream', + playerSize: [640, 480], + skip: 0, + connectiontype: [1, 2, 6], + skipmin: 10, + minduration: 10, + mimes: ['video/mp4', 'video/x-flv'], + } + beforeEach(() => { + utilsMock = sinon.mock(utils); + sandbox = sinon.sandbox.create(); + sandbox.spy(utils, 'logWarn'); + }); + + afterEach(() => { + utilsMock.restore(); + sandbox.restore(); + }) + + it('should log Video.Placement param missing', function() { + _checkVideoPlacement(videoData, adUnit); + // when failing this gives an odd message about "AssertError: expected logWarn to be called with arguments" it means the specific message expected + sinon.assert.calledWith(utils.logWarn, msg_placement_missing); + }) + it('shoud not log Video.Placement param missing', function() { + videoData['placement'] = 1; + _checkVideoPlacement(videoData, adUnit); + sinon.assert.neverCalledWith(utils.logWarn, msg_placement_missing); + }) + }); + // end video testing + }); }); diff --git a/test/spec/modules/pubxBidAdapter_spec.js b/test/spec/modules/pubxBidAdapter_spec.js index 06bb5b5f638..b387264bf91 100644 --- a/test/spec/modules/pubxBidAdapter_spec.js +++ b/test/spec/modules/pubxBidAdapter_spec.js @@ -39,14 +39,22 @@ describe('pubxAdapter', function () { id: '26c1ee0038ac11', params: { sid: '12345abc' + }, + ortb2: { + site: { + page: `${location.href}?test=1` + } } } ]; const data = { banner: { - sid: '12345abc' - } + sid: '12345abc', + pu: encodeURIComponent( + utils.deepAccess(bidRequests[0], 'ortb2.site.page').replace(/\?.*$/, '') + ), + }, }; it('sends bid request to ENDPOINT via GET', function () { diff --git a/test/spec/modules/pulsepointBidAdapter_spec.js b/test/spec/modules/pulsepointBidAdapter_spec.js index 60dca9e6da0..8db7e909771 100644 --- a/test/spec/modules/pulsepointBidAdapter_spec.js +++ b/test/spec/modules/pulsepointBidAdapter_spec.js @@ -1,7 +1,8 @@ /* eslint dot-notation:0, quote-props:0 */ import {expect} from 'chai'; import {spec} from 'modules/pulsepointBidAdapter.js'; -import {deepClone} from 'src/utils.js'; +import {syncAddFPDToBidderRequest} from '../../helpers/fpd.js'; +import {deepClone} from '../../../src/utils'; describe('PulsePoint Adapter Tests', function () { const slotConfigs = [{ @@ -31,39 +32,52 @@ describe('PulsePoint Adapter Tests', function () { cf: '728x90' } }]; + const nativeOrtbRequest = { + assets: [{ + id: 1, + required: 1, + img: { + type: 3, + w: 150, + h: 50, + } + }, + { + id: 2, + required: 1, + title: { + len: 80 + } + }, + { + id: 3, + required: 0, + data: { + type: 1 + } + }] + }; const nativeSlotConfig = [{ placementCode: '/DfpAccount1/slot3', bidId: 'bid12345', - nativeParams: { - title: { required: true, len: 200 }, - image: { wmin: 100 }, - sponsoredBy: { } + mediaTypes: { + native: { + sendTargetingKeys: false, + ortb: nativeOrtbRequest + } }, + nativeOrtbRequest, params: { cp: 'p10000', ct: 't10000' } }]; - const appSlotConfig = [{ - placementCode: '/DfpAccount1/slot3', - bidId: 'bid12345', - params: { - cp: 'p10000', - ct: 't10000', - app: { - bundle: 'com.pulsepoint.apps', - storeUrl: 'https://pulsepoint.com/apps', - domain: 'pulsepoint.com', - } - } - }]; const videoSlotConfig = [{ placementCode: '/DfpAccount1/slotVideo', bidId: 'bid12345', - params: { - cp: 'p10000', - ct: 't10000', + mediaTypes: { video: { + playerSize: [400, 300], w: 400, h: 300, minduration: 5, @@ -73,6 +87,10 @@ describe('PulsePoint Adapter Tests', function () { minbitrate: 200, protocols: [1, 2, 4] } + }, + params: { + cp: 'p10000', + ct: 't10000' } }]; const additionalParamsConfig = [{ @@ -97,68 +115,6 @@ describe('PulsePoint Adapter Tests', function () { } }]; - const ortbParamsSlotConfig = [{ - placementCode: '/DfpAccount1/slot1', - mediaTypes: { - banner: { - sizes: [[1, 1]] - } - }, - bidId: 'bid12345', - params: { - cp: 'p10000', - ct: 't10000', - cf: '1x1', - bcat: ['IAB-1', 'IAB-20'], - battr: [1, 2, 3], - bidfloor: 1.5, - badv: ['cocacola.com', 'lays.com'] - } - }, { - placementCode: '/DfpAccount1/slotVideo', - bidId: 'bid12345', - params: { - cp: 'p10000', - ct: 't10000', - video: { - w: 400, - h: 300, - minduration: 5, - maxduration: 10, - }, - battr: [2, 3, 4], - bidfloor: 2.5, - } - }]; - - const outstreamSlotConfig = [{ - placementCode: '/DfpAccount1/slot1', - mediaTypes: { - video: { - playerSize: [640, 480], - context: 'outstream' - } - }, - bidId: 'bid12345', - params: { - cp: 'p10000', - ct: 't10000', - cf: '1x1', - video: { - h: 300, - w: 400, - minduration: 1, - maxduration: 210, - linearity: 1, - } - }, - renderer: { - options: { - text: 'PulsePoint Outstream' - } - } - }]; - const schainParamsSlotConfig = [{ placementCode: '/DfpAccount1/slot1', mediaTypes: { @@ -200,7 +156,7 @@ describe('PulsePoint Adapter Tests', function () { }; it('Verify build request', function () { - const request = spec.buildRequests(slotConfigs, bidderRequest); + const request = spec.buildRequests(slotConfigs, syncAddFPDToBidderRequest(bidderRequest)); expect(request.url).to.equal('https://bid.contextweb.com/header/ortb?src=prebid'); expect(request.method).to.equal('POST'); const ortbRequest = request.data; @@ -208,7 +164,6 @@ describe('PulsePoint Adapter Tests', function () { expect(ortbRequest.site).to.not.equal(null); expect(ortbRequest.site.publisher).to.not.equal(null); expect(ortbRequest.site.publisher.id).to.equal('p10000'); - expect(ortbRequest.site.ref).to.equal(bidderRequest.refererInfo.ref); expect(ortbRequest.site.page).to.equal('https://publisher.com/home'); expect(ortbRequest.imp).to.have.lengthOf(2); // device object @@ -217,19 +172,15 @@ describe('PulsePoint Adapter Tests', function () { // slot 1 expect(ortbRequest.imp[0].tagid).to.equal('t10000'); expect(ortbRequest.imp[0].banner).to.not.equal(null); - expect(ortbRequest.imp[0].banner.w).to.equal(300); - expect(ortbRequest.imp[0].banner.h).to.equal(250); + expect(ortbRequest.imp[0].banner.format).to.deep.eq([{'w': 728, 'h': 90}, {'w': 160, 'h': 600}]); // slot 2 expect(ortbRequest.imp[1].tagid).to.equal('t20000'); expect(ortbRequest.imp[1].banner).to.not.equal(null); - expect(ortbRequest.imp[1].banner.w).to.equal(728); - expect(ortbRequest.imp[1].banner.h).to.equal(90); - // tmax - expect(ortbRequest.tmax).to.equal(500); + expect(ortbRequest.imp[1].banner.format).to.deep.eq([{'w': 728, 'h': 90}]); }); it('Verify parse response', function () { - const request = spec.buildRequests(slotConfigs, bidderRequest); + const request = spec.buildRequests(slotConfigs, syncAddFPDToBidderRequest(bidderRequest)); const ortbRequest = request.data; const ortbResponse = { seatbid: [{ @@ -237,11 +188,16 @@ describe('PulsePoint Adapter Tests', function () { impid: ortbRequest.imp[0].id, price: 1.25, adm: 'This is an Ad', - crid: 'Creative#123' + crid: 'Creative#123', + mtype: 1, + w: 300, + h: 250, + exp: 20, + adomain: ['advertiser.com'] }] }] }; - const bids = spec.interpretResponse({ body: ortbResponse }, request); + const bids = spec.interpretResponse({body: ortbResponse}, request); expect(bids).to.have.lengthOf(1); // verify first bid const bid = bids[0]; @@ -249,140 +205,112 @@ describe('PulsePoint Adapter Tests', function () { expect(bid.ad).to.equal('This is an Ad'); expect(bid.width).to.equal(300); expect(bid.height).to.equal(250); - expect(bid.adId).to.equal('bid12345'); expect(bid.creative_id).to.equal('Creative#123'); expect(bid.creativeId).to.equal('Creative#123'); expect(bid.netRevenue).to.equal(true); expect(bid.currency).to.equal('USD'); expect(bid.ttl).to.equal(20); - }); - - it('Verify ttl/currency/adomain applied to bid', function () { - const request = spec.buildRequests(slotConfigs, bidderRequest); - const ortbRequest = request.data; - const ortbResponse = { - seatbid: [{ - bid: [{ - impid: ortbRequest.imp[0].id, - price: 1.25, - adm: 'This is an Ad#1', - crid: 'Creative#123', - exp: 50, - adomain: ['advertiser.com'] - }, { - impid: ortbRequest.imp[1].id, - price: 1.25, - adm: 'This is an Ad#2', - crid: 'Creative#123' - }] - }], - cur: 'GBP' - }; - const bids = spec.interpretResponse({ body: ortbResponse }, request); - expect(bids).to.have.lengthOf(2); - // verify first bid - const bid = bids[0]; - expect(bid.cpm).to.equal(1.25); - expect(bid.ad).to.equal('This is an Ad#1'); - expect(bid.ttl).to.equal(50); - expect(bid.currency).to.equal('GBP'); expect(bid.meta).to.not.be.null; expect(bid.meta.advertiserDomains).to.eql(['advertiser.com']); - const secondBid = bids[1]; - expect(secondBid.cpm).to.equal(1.25); - expect(secondBid.ad).to.equal('This is an Ad#2'); - expect(secondBid.ttl).to.equal(20); - expect(secondBid.currency).to.equal('GBP'); - expect(secondBid.meta).to.not.be.null; - expect(secondBid.meta.advertiserDomains).to.eql([]); }); it('Verify full passback', function () { const request = spec.buildRequests(slotConfigs, bidderRequest); - const bids = spec.interpretResponse({ body: null }, request) + const bids = spec.interpretResponse({body: null}, request) expect(bids).to.have.lengthOf(0); }); - it('Verify Native request', function () { - const request = spec.buildRequests(nativeSlotConfig, bidderRequest); - expect(request.url).to.equal('https://bid.contextweb.com/header/ortb?src=prebid'); - expect(request.method).to.equal('POST'); - const ortbRequest = request.data; - // native impression - expect(ortbRequest.imp[0].tagid).to.equal('t10000'); - expect(ortbRequest.imp[0].banner).to.equal(null); - const nativePart = ortbRequest.imp[0]['native']; - expect(nativePart).to.not.equal(null); - expect(nativePart.ver).to.equal('1.1'); - expect(nativePart.request).to.not.equal(null); - // native request assets - const nativeRequest = JSON.parse(ortbRequest.imp[0]['native'].request); - expect(nativeRequest).to.not.equal(null); - expect(nativeRequest.assets).to.have.lengthOf(3); - // title asset - expect(nativeRequest.assets[0].id).to.equal(1); - expect(nativeRequest.assets[0].required).to.equal(1); - expect(nativeRequest.assets[0].title).to.not.equal(null); - expect(nativeRequest.assets[0].title.len).to.equal(200); - // data asset - expect(nativeRequest.assets[1].id).to.equal(2); - expect(nativeRequest.assets[1].required).to.equal(0); - expect(nativeRequest.assets[1].title).to.be.undefined; - expect(nativeRequest.assets[1].data).to.not.equal(null); - expect(nativeRequest.assets[1].data.type).to.equal(1); - expect(nativeRequest.assets[1].data.len).to.equal(50); - // image asset - expect(nativeRequest.assets[2].id).to.equal(3); - expect(nativeRequest.assets[2].required).to.equal(0); - expect(nativeRequest.assets[2].title).to.be.undefined; - expect(nativeRequest.assets[2].img).to.not.equal(null); - expect(nativeRequest.assets[2].img.wmin).to.equal(100); - expect(nativeRequest.assets[2].img.hmin).to.equal(150); - expect(nativeRequest.assets[2].img.type).to.equal(3); - }); + if (FEATURES.NATIVE) { + it('Verify Native request', function () { + const request = spec.buildRequests(nativeSlotConfig, syncAddFPDToBidderRequest(bidderRequest)); + expect(request.url).to.equal('https://bid.contextweb.com/header/ortb?src=prebid'); + expect(request.method).to.equal('POST'); + const ortbRequest = request.data; + // native impression + expect(ortbRequest.imp[0].tagid).to.equal('t10000'); + expect(ortbRequest.imp[0].banner).to.be.undefined; + const nativePart = ortbRequest.imp[0]['native']; + expect(nativePart).to.not.equal(null); + expect(nativePart.request).to.not.equal(null); + // native request assets + const nativeRequest = JSON.parse(ortbRequest.imp[0]['native'].request); + expect(nativeRequest).to.not.equal(null); + expect(nativeRequest.assets).to.have.lengthOf(3); + // image asset + expect(nativeRequest.assets[0].id).to.equal(1); + expect(nativeRequest.assets[0].required).to.equal(1); + expect(nativeRequest.assets[0].title).to.be.undefined; + expect(nativeRequest.assets[0].img).to.not.equal(null); + expect(nativeRequest.assets[0].img.w).to.equal(150); + expect(nativeRequest.assets[0].img.h).to.equal(50); + expect(nativeRequest.assets[0].img.type).to.equal(3); + // title asset + expect(nativeRequest.assets[1].id).to.equal(2); + expect(nativeRequest.assets[1].required).to.equal(1); + expect(nativeRequest.assets[1].title).to.not.equal(null); + expect(nativeRequest.assets[1].title.len).to.equal(80); + // data asset + expect(nativeRequest.assets[2].id).to.equal(3); + expect(nativeRequest.assets[2].required).to.equal(0); + expect(nativeRequest.assets[2].title).to.be.undefined; + expect(nativeRequest.assets[2].data).to.not.equal(null); + expect(nativeRequest.assets[2].data.type).to.equal(1); + }); - it('Verify Native response', function () { - const request = spec.buildRequests(nativeSlotConfig, bidderRequest); - expect(request.url).to.equal('https://bid.contextweb.com/header/ortb?src=prebid'); - expect(request.method).to.equal('POST'); - const ortbRequest = request.data; - const nativeResponse = { - 'native': { + it('Verify Native response', function () { + const request = spec.buildRequests(nativeSlotConfig, syncAddFPDToBidderRequest(bidderRequest)); + expect(request.url).to.equal('https://bid.contextweb.com/header/ortb?src=prebid'); + expect(request.method).to.equal('POST'); + const ortbRequest = request.data; + const nativeResponse = { assets: [ - { title: { text: 'Ad Title' } }, - { data: { type: 1, value: 'Sponsored By: Brand' } }, - { img: { type: 3, url: 'https://images.cdn.brand.com/123' } } + {id: 1, img: {type: 3, url: 'https://images.cdn.brand.com/123'}}, + {id: 2, title: {text: 'Ad Title'}}, + {id: 3, data: {type: 1, value: 'Sponsored By: Brand'}} ], - link: { url: 'https://brand.clickme.com/' }, + link: {url: 'https://brand.clickme.com/'}, imptrackers: ['https://imp1.trackme.com/', 'https://imp1.contextweb.com/'] - } - }; - const ortbResponse = { - seatbid: [{ - bid: [{ - impid: ortbRequest.imp[0].id, - price: 1.25, - adm: JSON.stringify(nativeResponse) + + }; + const ortbResponse = { + seatbid: [{ + bid: [{ + impid: ortbRequest.imp[0].id, + price: 1.25, + adm: JSON.stringify(nativeResponse), + mtype: 4 + }] }] - }] - }; - const bids = spec.interpretResponse({ body: ortbResponse }, request); - // verify bid - const bid = bids[0]; - expect(bid.cpm).to.equal(1.25); - expect(bid.adId).to.equal('bid12345'); - expect(bid.ad).to.be.undefined; - expect(bid.mediaType).to.equal('native'); - const nativeBid = bid['native']; - expect(nativeBid).to.not.equal(null); - expect(nativeBid.title).to.equal('Ad Title'); - expect(nativeBid.sponsoredBy).to.equal('Sponsored By: Brand'); - expect(nativeBid.image).to.equal('https://images.cdn.brand.com/123'); - expect(nativeBid.clickUrl).to.equal(encodeURIComponent('https://brand.clickme.com/')); - expect(nativeBid.impressionTrackers).to.have.lengthOf(2); - expect(nativeBid.impressionTrackers[0]).to.equal('https://imp1.trackme.com/'); - expect(nativeBid.impressionTrackers[1]).to.equal('https://imp1.contextweb.com/'); - }); + }; + const bids = spec.interpretResponse({body: ortbResponse}, request); + // verify bid + const bid = bids[0]; + expect(bid.cpm).to.equal(1.25); + expect(bid.requestId).to.equal('bid12345'); + expect(bid.ad).to.be.undefined; + expect(bid.mediaType).to.equal('native'); + expect(bid['native']).to.not.be.null; + expect(bid['native'].ortb).to.not.be.null; + const nativeBid = bid['native'].ortb; + expect(nativeBid.assets).to.have.lengthOf(3); + expect(nativeBid.assets[0].id).to.equal(1); + expect(nativeBid.assets[0].img).to.not.be.null; + expect(nativeBid.assets[0].img.type).to.equal(3); + expect(nativeBid.assets[0].img.url).to.equal('https://images.cdn.brand.com/123'); + expect(nativeBid.assets[1].id).to.equal(2); + expect(nativeBid.assets[1].title).to.not.be.null; + expect(nativeBid.assets[1].title.text).to.equal('Ad Title'); + expect(nativeBid.assets[2].id).to.equal(3); + expect(nativeBid.assets[2].data).to.not.be.null; + expect(nativeBid.assets[2].data.type).to.equal(1); + expect(nativeBid.assets[2].data.value).to.equal('Sponsored By: Brand'); + expect(nativeBid.link).to.not.be.null; + expect(nativeBid.link.url).to.equal('https://brand.clickme.com/'); + expect(nativeBid.imptrackers).to.have.lengthOf(2); + expect(nativeBid.imptrackers[0]).to.equal('https://imp1.trackme.com/'); + expect(nativeBid.imptrackers[1]).to.equal('https://imp1.contextweb.com/'); + }); + } it('Verifies bidder code', function () { expect(spec.code).to.equal('pulsepoint'); @@ -430,19 +358,6 @@ describe('PulsePoint Adapter Tests', function () { expect(options[0].url).to.equal('https://bh.contextweb.com/visitormatch/prebid'); }); - it('Verify app requests', function () { - const request = spec.buildRequests(appSlotConfig, bidderRequest); - const ortbRequest = request.data; - // site object - expect(ortbRequest.site).to.equal(null); - expect(ortbRequest.app).to.not.be.null; - expect(ortbRequest.app.publisher).to.not.equal(null); - expect(ortbRequest.app.publisher.id).to.equal('p10000'); - expect(ortbRequest.app.bundle).to.equal('com.pulsepoint.apps'); - expect(ortbRequest.app.storeurl).to.equal('https://pulsepoint.com/apps'); - expect(ortbRequest.app.domain).to.equal('pulsepoint.com'); - }); - it('Verify GDPR', function () { const bidderRequestGdpr = { gdprConsent: { @@ -450,7 +365,7 @@ describe('PulsePoint Adapter Tests', function () { consentString: 'serialized_gpdr_data' } }; - const request = spec.buildRequests(slotConfigs, Object.assign({}, bidderRequest, bidderRequestGdpr)); + const request = spec.buildRequests(slotConfigs, syncAddFPDToBidderRequest(Object.assign({}, bidderRequest, bidderRequestGdpr))); expect(request.url).to.equal('https://bid.contextweb.com/header/ortb?src=prebid'); expect(request.method).to.equal('POST'); const ortbRequest = request.data; @@ -468,7 +383,8 @@ describe('PulsePoint Adapter Tests', function () { const bidderRequestUSPrivacy = { uspConsent: '1YYY' }; - const request = spec.buildRequests(slotConfigs, Object.assign({}, bidderRequest, bidderRequestUSPrivacy)); + const request = spec.buildRequests(slotConfigs, + syncAddFPDToBidderRequest(Object.assign({}, bidderRequest, bidderRequestUSPrivacy))); expect(request.url).to.equal('https://bid.contextweb.com/header/ortb?src=prebid'); expect(request.method).to.equal('POST'); const ortbRequest = request.data; @@ -478,52 +394,54 @@ describe('PulsePoint Adapter Tests', function () { expect(ortbRequest.regs.ext.us_privacy).to.equal('1YYY'); }); - it('Verify Video request', function () { - const request = spec.buildRequests(videoSlotConfig, bidderRequest); - expect(request.url).to.equal('https://bid.contextweb.com/header/ortb?src=prebid'); - expect(request.method).to.equal('POST'); - const ortbRequest = request.data; - expect(ortbRequest).to.not.equal(null); - expect(ortbRequest.imp).to.have.lengthOf(1); - expect(ortbRequest.imp[0].video).to.not.be.null; - expect(ortbRequest.imp[0].native).to.be.null; - expect(ortbRequest.imp[0].banner).to.be.null; - expect(ortbRequest.imp[0].video.w).to.equal(400); - expect(ortbRequest.imp[0].video.h).to.equal(300); - expect(ortbRequest.imp[0].video.minduration).to.equal(5); - expect(ortbRequest.imp[0].video.maxduration).to.equal(10); - expect(ortbRequest.imp[0].video.startdelay).to.equal(0); - expect(ortbRequest.imp[0].video.skip).to.equal(1); - expect(ortbRequest.imp[0].video.minbitrate).to.equal(200); - expect(ortbRequest.imp[0].video.protocols).to.eql([1, 2, 4]); - }); + if (FEATURES.VIDEO) { + it('Verify Video request', function () { + const request = spec.buildRequests(videoSlotConfig, syncAddFPDToBidderRequest(bidderRequest)); + expect(request.url).to.equal('https://bid.contextweb.com/header/ortb?src=prebid'); + expect(request.method).to.equal('POST'); + const ortbRequest = request.data; + expect(ortbRequest).to.not.equal(null); + expect(ortbRequest.imp).to.have.lengthOf(1); + expect(ortbRequest.imp[0].video).to.not.be.null; + expect(ortbRequest.imp[0].native).to.be.undefined; + expect(ortbRequest.imp[0].banner).to.be.undefined; + expect(ortbRequest.imp[0].video.w).to.equal(400); + expect(ortbRequest.imp[0].video.h).to.equal(300); + expect(ortbRequest.imp[0].video.minduration).to.equal(5); + expect(ortbRequest.imp[0].video.maxduration).to.equal(10); + expect(ortbRequest.imp[0].video.startdelay).to.equal(0); + expect(ortbRequest.imp[0].video.skip).to.equal(1); + expect(ortbRequest.imp[0].video.minbitrate).to.equal(200); + expect(ortbRequest.imp[0].video.protocols).to.eql([1, 2, 4]); + }); - it('Verify Video response', function () { - const request = spec.buildRequests(videoSlotConfig, bidderRequest); - expect(request.url).to.equal('https://bid.contextweb.com/header/ortb?src=prebid'); - expect(request.method).to.equal('POST'); - const ortbRequest = request.data; - const ortbResponse = { - seatbid: [{ - bid: [{ - impid: ortbRequest.imp[0].id, - price: 1.25, - adm: 'https//pulsepoint.video.mp4' + it('Verify Video response', function () { + const request = spec.buildRequests(videoSlotConfig, bidderRequest); + expect(request.url).to.equal('https://bid.contextweb.com/header/ortb?src=prebid'); + expect(request.method).to.equal('POST'); + const ortbRequest = request.data; + const ortbResponse = { + seatbid: [{ + bid: [{ + impid: ortbRequest.imp[0].id, + price: 1.25, + adm: 'https//pulsepoint.video.mp4', + mtype: 2 + }] }] - }] - }; - const bids = spec.interpretResponse({ body: ortbResponse }, request); - const bid = bids[0]; - expect(bid.cpm).to.equal(1.25); - expect(bid.adId).to.equal('bid12345'); - expect(bid.ad).to.be.undefined; - expect(bid['native']).to.be.undefined; - expect(bid.mediaType).to.equal('video'); - expect(bid.vastXml).to.equal(ortbResponse.seatbid[0].bid[0].adm); - }); + }; + const bids = spec.interpretResponse({ body: ortbResponse }, request); + const bid = bids[0]; + expect(bid.cpm).to.equal(1.25); + expect(bid.ad).to.be.undefined; + expect(bid['native']).to.be.undefined; + expect(bid.mediaType).to.equal('video'); + expect(bid.vastXml).to.equal(ortbResponse.seatbid[0].bid[0].adm); + }); + } it('Verify extra parameters', function () { - let request = spec.buildRequests(additionalParamsConfig, bidderRequest); + let request = spec.buildRequests(additionalParamsConfig, syncAddFPDToBidderRequest(bidderRequest)); let ortbRequest = request.data; expect(ortbRequest).to.not.equal(null); expect(ortbRequest.imp).to.have.lengthOf(1); @@ -538,31 +456,15 @@ describe('PulsePoint Adapter Tests', function () { expect(ortbRequest.imp[0].ext.prebid.extra_key4).to.eql([1, 2, 3]); expect(Object.keys(ortbRequest.imp[0].ext.prebid)).to.eql(['extra_key1', 'extra_key2', 'extra_key3', 'extra_key4']); // attempting with a configuration with no unknown params. - request = spec.buildRequests(outstreamSlotConfig, bidderRequest); + request = spec.buildRequests(videoSlotConfig, bidderRequest); ortbRequest = request.data; expect(ortbRequest).to.not.equal(null); expect(ortbRequest.imp).to.have.lengthOf(1); - expect(ortbRequest.imp[0].ext).to.equal(null); - }); - - it('Verify ortb parameters', function () { - const request = spec.buildRequests(ortbParamsSlotConfig, bidderRequest); - const ortbRequest = request.data; - expect(ortbRequest).to.not.equal(null); - expect(ortbRequest.bcat).to.eql(['IAB-1', 'IAB-20']); - expect(ortbRequest.badv).to.eql(['cocacola.com', 'lays.com']); - expect(ortbRequest.imp).to.have.lengthOf(2); - expect(ortbRequest.imp[0].bidfloor).to.equal(1.5); - expect(ortbRequest.imp[0].banner.battr).to.eql([1, 2, 3]); - expect(ortbRequest.imp[0].ext).to.be.null; - // slot 2 - expect(ortbRequest.imp[1].bidfloor).to.equal(2.5); - expect(ortbRequest.imp[1].video.battr).to.eql([2, 3, 4]); - expect(ortbRequest.imp[1].ext).to.be.null; + expect(ortbRequest.imp[0].ext).to.be.undefined; }); it('Verify schain parameters', function () { - const request = spec.buildRequests(schainParamsSlotConfig, bidderRequest); + const request = spec.buildRequests(schainParamsSlotConfig, syncAddFPDToBidderRequest(bidderRequest)); const ortbRequest = request.data; expect(ortbRequest).to.not.equal(null); expect(ortbRequest.source).to.not.equal(null); @@ -580,42 +482,6 @@ describe('PulsePoint Adapter Tests', function () { expect(ortbRequest.source.ext.schain.nodes[0].domain).to.equal('publisher.com'); }); - it('Verify outstream renderer', function () { - const bidderRequestOutstream = Object.assign({}, bidderRequest, {bids: [outstreamSlotConfig[0]]}); - const request = spec.buildRequests(outstreamSlotConfig, bidderRequestOutstream); - const ortbRequest = request.data; - expect(ortbRequest).to.not.be.null; - expect(ortbRequest.imp[0]).to.not.be.null; - expect(ortbRequest.imp[0].video).to.not.be.null; - const ortbResponse = { - seatbid: [{ - bid: [{ - impid: ortbRequest.imp[0].id, - price: 1.25, - adm: 'https//pulsepoint.video.mp4', - ext: { - outstream: { - type: 'Inline', - config: { - text: 'ADVERTISEMENT', - skipaftersec: 5 - }, - rendererUrl: 'https://tag.contextweb.com/hb-outstr-renderer.js' - } - } - }] - }] - }; - const bids = spec.interpretResponse({ body: ortbResponse }, request); - const bid = bids[0]; - expect(bid.cpm).to.equal(1.25); - expect(bid.renderer).to.not.be.null; - expect(bid.renderer.url).to.equal('https://tag.contextweb.com/hb-outstr-renderer.js'); - expect(bid.renderer.getConfig()).to.not.be.null; - expect(bid.renderer.getConfig().defaultOptions).to.eql(ortbResponse.seatbid[0].bid[0].ext.outstream.config); - expect(bid.renderer.getConfig().rendererOptions).to.eql(outstreamSlotConfig[0].renderer.options); - expect(bid.renderer.getConfig().type).to.equal('Inline'); - }); it('Verify common id parameters', function () { const bidRequests = deepClone(slotConfigs); bidRequests[0].userIdAsEids = [{ @@ -633,169 +499,17 @@ describe('PulsePoint Adapter Tests', function () { }] } ]; - const request = spec.buildRequests(bidRequests, bidderRequest); + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); expect(request).to.be.not.null; - const ortbRequest = request.data; expect(request.data).to.be.not.null; + const ortbRequest = request.data; // user object expect(ortbRequest.user).to.not.be.undefined; expect(ortbRequest.user.ext).to.not.be.undefined; expect(ortbRequest.user.ext.eids).to.not.be.undefined; expect(ortbRequest.user.ext.eids).to.deep.equal(bidRequests[0].userIdAsEids); }); - it('Verify multiple adsizes', function () { - const bidRequests = deepClone(slotConfigs); - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request).to.be.not.null; - expect(request.data).to.be.not.null; - const ortbRequest = request.data; - expect(ortbRequest.imp).to.have.lengthOf(2); - // first impression has multi sizes - expect(ortbRequest.imp[0].banner).to.not.be.null; - expect(ortbRequest.imp[0].banner.w).to.equal(300); - expect(ortbRequest.imp[0].banner.h).to.equal(250); - expect(ortbRequest.imp[0].banner.format).to.not.be.null; - expect(ortbRequest.imp[0].banner.format).to.have.lengthOf(2); - expect(ortbRequest.imp[0].banner.format[0].w).to.equal(728); - expect(ortbRequest.imp[0].banner.format[0].h).to.equal(90); - expect(ortbRequest.imp[0].banner.format[1].w).to.equal(160); - expect(ortbRequest.imp[0].banner.format[1].h).to.equal(600); - // slot 2 - expect(ortbRequest.imp[1].banner).to.not.be.null; - expect(ortbRequest.imp[1].banner.w).to.equal(728); - expect(ortbRequest.imp[1].banner.h).to.equal(90); - expect(ortbRequest.imp[1].banner.format).to.not.be.null; - expect(ortbRequest.imp[1].banner.format).to.have.lengthOf(1); - expect(ortbRequest.imp[1].banner.format[0].w).to.equal(728); - expect(ortbRequest.imp[1].banner.format[0].h).to.equal(90); - // adsize on response - const ortbResponse = { - seatbid: [{ - bid: [{ - impid: ortbRequest.imp[0].id, - price: 1.25, - adm: 'This is an Ad', - crid: 'Creative#123', - w: 728, - h: 90 - }] - }] - }; - const bids = spec.interpretResponse({ body: ortbResponse }, request); - expect(bids).to.have.lengthOf(1); - const bid = bids[0]; - expect(bid.width).to.equal(728); - expect(bid.height).to.equal(90); - }); - it('Verify multi-format response', function () { - const bidRequests = deepClone(slotConfigs); - bidRequests[0].mediaTypes['native'] = { - title: { - required: true - }, - image: { - required: true - }, - sponsoredBy: { - required: true - } - }; - bidRequests[1].params.video = { - w: 400, - h: 300, - minduration: 5, - maxduration: 10, - }; - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request).to.be.not.null; - expect(request.data).to.be.not.null; - const ortbRequest = request.data; - expect(ortbRequest.imp).to.have.lengthOf(2); - // adsize on response - const ortbResponse = { - seatbid: [{ - bid: [{ - impid: ortbRequest.imp[0].id, - price: 1.25, - adm: 'This is an Ad', - crid: 'Creative#123', - w: 728, - h: 90 - }, { - impid: ortbRequest.imp[1].id, - price: 2.5, - adm: '', - crid: 'Creative#234', - w: 728, - h: 90 - }] - }] - }; - // request has both types - banner and native, response is parsed as banner. - // for impression#2, response is parsed as video - const bids = spec.interpretResponse({ body: ortbResponse }, request); - expect(bids).to.have.lengthOf(2); - const bid = bids[0]; - expect(bid.width).to.equal(728); - expect(bid.height).to.equal(90); - const secondBid = bids[1]; - expect(secondBid.vastXml).to.equal(''); - }); - it('Verify bid floor', function () { - const bidRequests = deepClone(slotConfigs); - bidRequests[0].params.bidfloor = 1.05; - let request = spec.buildRequests(bidRequests, bidderRequest); - let ortbRequest = request.data; - expect(ortbRequest).to.not.equal(null); - expect(ortbRequest.imp[0].bidfloor).to.equal(1.05); - expect(ortbRequest.imp[1].bidfloor).to.be.undefined; - let floorArg = null; - // publisher uses the floor module - bidRequests[0].getFloor = (arg) => { - floorArg = arg; - return { floor: 1.25 }; - }; - bidRequests[1].getFloor = () => { - return { floor: 2.05 }; - }; - request = spec.buildRequests(bidRequests, bidderRequest); - ortbRequest = request.data; - expect(ortbRequest).to.not.equal(null); - expect(ortbRequest.imp[0].bidfloor).to.equal(1.25); - expect(ortbRequest.imp[1].bidfloor).to.equal(2.05); - expect(floorArg).to.not.be.null; - expect(floorArg.mediaType).to.equal('banner'); - expect(floorArg.currency).to.equal('USD'); - expect(floorArg.size).to.equal('*'); - }); - it('Verify Video params on mediaTypes.video', function () { - const bidRequests = deepClone(videoSlotConfig); - bidRequests[0].mediaTypes = { - video: { - w: 600, - h: 400, - minduration: 15, - maxduration: 20, - startdelay: 10, - skip: 0, - } - }; - const request = spec.buildRequests(bidRequests, bidderRequest); - const ortbRequest = request.data; - expect(ortbRequest).to.not.equal(null); - expect(ortbRequest.imp).to.have.lengthOf(1); - expect(ortbRequest.imp[0].video).to.not.be.null; - expect(ortbRequest.imp[0].native).to.be.null; - expect(ortbRequest.imp[0].banner).to.be.null; - expect(ortbRequest.imp[0].video.w).to.equal(600); - expect(ortbRequest.imp[0].video.h).to.equal(400); - expect(ortbRequest.imp[0].video.minduration).to.equal(15); - expect(ortbRequest.imp[0].video.maxduration).to.equal(20); - expect(ortbRequest.imp[0].video.startdelay).to.equal(10); - expect(ortbRequest.imp[0].video.skip).to.equal(0); - expect(ortbRequest.imp[0].video.minbitrate).to.equal(200); - expect(ortbRequest.imp[0].video.protocols).to.eql([1, 2, 4]); - }); + it('Verify user level first party data', function () { const bidderRequest = { refererInfo: { @@ -819,7 +533,7 @@ describe('PulsePoint Adapter Tests', function () { } } }; - let request = spec.buildRequests(slotConfigs, bidderRequest); + let request = spec.buildRequests(slotConfigs, syncAddFPDToBidderRequest(bidderRequest)); let ortbRequest = request.data; expect(ortbRequest).to.not.equal(null); expect(ortbRequest.user).to.not.equal(null); @@ -835,12 +549,9 @@ describe('PulsePoint Adapter Tests', function () { } }); }); + it('Verify site level first party data', function () { const bidderRequest = { - refererInfo: { - page: 'https://publisher.com/home', - ref: 'https://referrer' - }, ortb2: { site: { content: { @@ -853,11 +564,14 @@ describe('PulsePoint Adapter Tests', function () { }] }, page: 'http://pub.com/news', - ref: 'http://google.com' + ref: 'http://google.com', + publisher: { + domain: 'pub.com' + } } } }; - let request = spec.buildRequests(slotConfigs, bidderRequest); + let request = spec.buildRequests(slotConfigs, syncAddFPDToBidderRequest(bidderRequest)); let ortbRequest = request.data; expect(ortbRequest).to.not.equal(null); expect(ortbRequest.site).to.not.equal(null); @@ -871,13 +585,15 @@ describe('PulsePoint Adapter Tests', function () { } }] }, - page: 'https://publisher.com/home', - ref: 'https://referrer', + page: 'http://pub.com/news', + ref: 'http://google.com', publisher: { - id: 'p10000' + id: 'p10000', + domain: 'pub.com' } }); }); + it('Verify impression/slot level first party data', function () { const bidderRequests = [{ placementCode: '/DfpAccount1/slot1', @@ -928,4 +644,27 @@ describe('PulsePoint Adapter Tests', function () { // assert bidderRequest value is used when available expect(mkRequest(Object.assign({}, { timeout: 6000 }, bidderRequest)).tmax).to.equal(6000) }); + + it('Verify deals', function () { + const bidRequests = deepClone(slotConfigs); + const deals = [{ + id: 'DEAL_ONE', + bidfloor: 1.1 + }, { + id: 'DEAL_TWO', + bidfloor: 2.2 + }]; + bidRequests[0].params.deals = deals; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + expect(request.url).to.equal('https://bid.contextweb.com/header/ortb?src=prebid'); + expect(request.method).to.equal('POST'); + const ortbRequest = request.data; + // slot 1 + expect(ortbRequest.imp[0].tagid).to.equal('t10000'); + expect(ortbRequest.imp[0].pmp).to.not.be.undefined; + expect(ortbRequest.imp[0].pmp).to.deep.equal({ + private_auction: 0, + deals + }); + }) }); diff --git a/test/spec/modules/realTimeDataModule_spec.js b/test/spec/modules/realTimeDataModule_spec.js index ced2f697649..938e2e2f3c1 100644 --- a/test/spec/modules/realTimeDataModule_spec.js +++ b/test/spec/modules/realTimeDataModule_spec.js @@ -5,6 +5,8 @@ import {default as CONSTANTS} from '../../../src/constants.json'; import * as events from '../../../src/events.js'; import 'src/prebid.js'; import {attachRealTimeDataProvider, onDataDeletionRequest} from 'modules/rtdModule/index.js'; +import {GDPR_GVLIDS} from '../../../src/consentHandler.js'; +import {MODULE_TYPE_RTD} from '../../../src/activities/modules.js'; const getBidRequestDataSpy = sinon.spy(); @@ -84,6 +86,26 @@ describe('Real time module', function () { sandbox.restore(); }); + describe('GVL IDs', () => { + beforeEach(() => { + sinon.stub(GDPR_GVLIDS, 'register'); + }); + + afterEach(() => { + GDPR_GVLIDS.register.restore(); + }); + + it('are registered when RTD module is registered', () => { + let mod; + try { + mod = attachRealTimeDataProvider({name: 'mockRtd', gvlid: 123}); + sinon.assert.calledWith(GDPR_GVLIDS.register, MODULE_TYPE_RTD, 'mockRtd', 123); + } finally { + mod && mod(); + } + }) + }) + describe('', () => { const PROVIDERS = [validSM, invalidSM, failureSM, nonConfSM, validSMWait]; let _detachers; @@ -106,7 +128,7 @@ describe('Real time module', function () { it('should be able to modify bid request', function (done) { rtdModule.setBidRequestsData(() => { assert(getBidRequestDataSpy.calledTwice); - assert(getBidRequestDataSpy.calledWith({bidRequest: {}})); + assert(getBidRequestDataSpy.calledWith(sinon.match({bidRequest: {}}))); done(); }, {bidRequest: {}}) }); diff --git a/test/spec/modules/realvuAnalyticsAdapter_spec.js b/test/spec/modules/realvuAnalyticsAdapter_spec.js deleted file mode 100644 index e51a4e2e3a2..00000000000 --- a/test/spec/modules/realvuAnalyticsAdapter_spec.js +++ /dev/null @@ -1,194 +0,0 @@ -import { expect } from 'chai'; -import realvuAnalyticsAdapter, { lib } from 'modules/realvuAnalyticsAdapter.js'; -import CONSTANTS from 'src/constants.json'; - -function addDiv(id) { - let dv = document.createElement('div'); - dv.id = id; - dv.style.width = '728px'; - dv.style.height = '90px'; - dv.style.display = 'block'; - document.body.appendChild(dv); - let f = document.createElement('iframe'); - f.width = 728; - f.height = 90; - dv.appendChild(f); - let d = null; - if (f.contentDocument) d = f.contentDocument; // DOM - else if (f.contentWindow) d = f.contentWindow.document; // IE - d.open() - d.write(''); - d.close(); - return dv; -} - -describe('RealVu', function() { - let sandbox; - beforeEach(function () { - sandbox = sinon.sandbox.create(); - addDiv('ad1'); - addDiv('ad2'); - sandbox.stub(lib, 'scr'); - }); - - afterEach(function () { - let a1 = document.getElementById('ad1'); - document.body.removeChild(a1); - let a2 = document.getElementById('ad2'); - document.body.removeChild(a2); - sandbox.restore(); - realvuAnalyticsAdapter.disableAnalytics(); - }); - - after(function () { - delete window.top1; - delete window.realvu_aa_fifo; - delete window.realvu_aa; - clearInterval(window.boost_poll); - delete window.boost_poll; - }); - - describe('Analytics Adapter.', function () { - it('enableAnalytics', function () { - const config = { - options: { - partnerId: '1Y', - regAllUnits: true - // unitIds: ['ad1', 'ad2'] - } - }; - let p = realvuAnalyticsAdapter.enableAnalytics(config); - expect(p).to.equal('1Y'); - }); - - it('checkIn', function () { - const bid = { - adUnitCode: 'ad1', - sizes: [ - [728, 90], - [970, 250], - [970, 90] - ] - }; - let result = realvuAnalyticsAdapter.checkIn(bid, '1Y'); - const b = Object.assign({}, window.top1.realvu_aa); - let a = b.ads[0]; - // console.log('a: ' + a.x + ', ' + a.y + ', ' + a.w + ', ' + a.h); - // console.log('b: ' + b.x1 + ', ' + b.y1 + ', ' + b.x2 + ', ' + b.y2); - expect(result).to.equal('yes'); - - result = realvuAnalyticsAdapter.checkIn(bid); // test invalid partnerId 'undefined' - result = realvuAnalyticsAdapter.checkIn(bid, ''); // test invalid partnerId '' - }); - - it.skip('isInView returns "yes"', () => { - let inview = realvuAnalyticsAdapter.isInView('ad1'); - expect(inview).to.equal('yes'); - }); - - it('isInView return "NA"', function () { - const adUnitCode = '1234'; - let result = realvuAnalyticsAdapter.isInView(adUnitCode); - expect(result).to.equal('NA'); - }); - - it('bid response event', function () { - const config = { - options: { - partnerId: '1Y', - regAllUnits: true - // unitIds: ['ad1', 'ad2'] - } - }; - realvuAnalyticsAdapter.enableAnalytics(config); - const args = { - 'biddercode': 'realvu', - 'adUnitCode': 'ad1', - 'width': 300, - 'height': 250, - 'statusMessage': 'Bid available', - 'adId': '7ba299eba818c1', - 'mediaType': 'banner', - 'creative_id': 85792851, - 'cpm': 0.4308 - }; - realvuAnalyticsAdapter.track({ - eventType: CONSTANTS.EVENTS.BID_RESPONSE, - args: args - }); - const boost = Object.assign({}, window.top1.realvu_aa); - expect(boost.ads[boost.len - 1].bids.length).to.equal(1); - - realvuAnalyticsAdapter.track({ - eventType: CONSTANTS.EVENTS.BID_WON, - args: args - }); - expect(boost.ads[boost.len - 1].bids[0].winner).to.equal(1); - }); - }); - - describe('Boost.', function () { - // const boost = window.top1.realvu_aa; - let boost; - beforeEach(function() { - boost = Object.assign({}, window.top1.realvu_aa); - }); - it('brd', function () { - let a1 = document.getElementById('ad1'); - let p = boost.brd(a1, 'Left'); - expect(typeof p).to.not.equal('undefined'); - }); - - it('addUnitById', function () { - let a1 = document.getElementById('ad1'); - let p = boost.addUnitById('1Y', 'ad1'); - expect(typeof p).to.not.equal('undefined'); - }); - - it('questA', function () { - const dv = document.getElementById('ad1'); - let q = boost.questA(dv); - expect(q).to.not.equal(null); - }); - - it('render', function () { - let dv = document.getElementById('ad1'); - // dv.style.width = '728px'; - // dv.style.height = '90px'; - // dv.style.display = 'block'; - dv.getBoundingClientRect = false; - // document.body.appendChild(dv); - let q = boost.findPosG(dv); - expect(q).to.not.equal(null); - }); - - it('readPos', function () { - const a = boost.ads[boost.len - 1]; - let r = boost.readPos(a); - expect(r).to.equal(true); - }); - - it('send_track', function () { - const a = boost.ads[boost.len - 1]; - boost.track(a, 'show', ''); - boost.sr = 'a'; - boost.send_track(); - expect(boost.beacons.length).to.equal(0); - }); - - it('questA text', function () { - let p = document.createElement('p'); - p.innerHTML = 'ABC'; - document.body.appendChild(p); - let r = boost.questA(p.firstChild); - document.body.removeChild(p); - expect(r).to.not.equal(null); - }); - - it('_f=conf', function () { - const a = boost.ads[boost.len - 1]; - let r = boost.tru(a, 'conf'); - expect(r).to.not.include('_ps='); - }); - }); -}); diff --git a/test/spec/modules/relaidoBidAdapter_spec.js b/test/spec/modules/relaidoBidAdapter_spec.js index 6a6e79c633d..7778e9cbf80 100644 --- a/test/spec/modules/relaidoBidAdapter_spec.js +++ b/test/spec/modules/relaidoBidAdapter_spec.js @@ -1,15 +1,12 @@ -import { expect } from 'chai'; -import { spec } from 'modules/relaidoBidAdapter.js'; +import {expect} from 'chai'; +import {spec} from 'modules/relaidoBidAdapter.js'; import * as utils from 'src/utils.js'; -import { BANNER, VIDEO } from 'src/mediaTypes.js'; -import { getStorageManager } from '../../../src/storageManager.js'; +import {VIDEO} from 'src/mediaTypes.js'; +import {getCoreStorageManager} from '../../../src/storageManager.js'; const UUID_KEY = 'relaido_uuid'; const relaido_uuid = 'hogehoge'; -const storage = getStorageManager(); -storage.setCookie(UUID_KEY, relaido_uuid); - describe('RelaidoAdapter', function () { let bidRequest; let bidderRequest; @@ -18,6 +15,10 @@ describe('RelaidoAdapter', function () { let serverRequest; let generateUUIDStub; let triggerPixelStub; + before(() => { + const storage = getCoreStorageManager(); + storage.setCookie(UUID_KEY, relaido_uuid); + }); beforeEach(function () { generateUUIDStub = sinon.stub(utils, 'generateUUID').returns(relaido_uuid); @@ -39,7 +40,11 @@ describe('RelaidoAdapter', function () { bidId: '2ed93003f7bb99', bidderRequestId: '1c50443387a1f2', auctionId: '413ed000-8c7a-4ba1-a1fa-9732e006f8c3', - transactionId: '5c2d064c-7b76-42e8-a383-983603afdc45', + ortb2Imp: { + ext: { + tid: '5c2d064c-7b76-42e8-a383-983603afdc45', + } + }, bidRequestsCount: 1, bidderRequestsCount: 1, bidderWinsCount: 0 @@ -243,7 +248,7 @@ describe('RelaidoAdapter', function () { expect(request.bidder_request_id).to.equal(bidRequest.bidderRequestId); expect(data.bid_requests_count).to.equal(bidRequest.bidRequestsCount); expect(request.bid_id).to.equal(bidRequest.bidId); - expect(request.transaction_id).to.equal(bidRequest.transactionId); + expect(request.transaction_id).to.equal(bidRequest.ortb2Imp.ext.tid); expect(request.media_type).to.equal('video'); expect(data.uuid).to.equal(relaido_uuid); expect(data.pv).to.equal('$prebid.version$'); diff --git a/test/spec/modules/relevadRtdProvider_spec.js b/test/spec/modules/relevadRtdProvider_spec.js new file mode 100644 index 00000000000..678ea26eed6 --- /dev/null +++ b/test/spec/modules/relevadRtdProvider_spec.js @@ -0,0 +1,412 @@ +import { addRtdData, getBidRequestData, relevadSubmodule, serverData } from 'modules/relevadRtdProvider.js'; +import { server } from 'test/mocks/xhr.js'; +import {config} from 'src/config.js'; +import { deepClone, deepAccess, deepSetValue } from '../../../src/utils.js'; + +const responseHeader = {'Content-Type': 'application/json'}; + +const moduleConfigCommon = { + 'dryrun': true, + params: { + setgpt: true, + minscore: 50, + partnerid: 12345, + bidders: [{ bidder: 'appnexus' }, + { bidder: 'rubicon', }, + { bidder: 'smart', }, + { bidder: 'ix', }, + { bidder: 'proxistore', }, + { bidder: 'other' }] + } +}; + +const reqBidsCommon = { + 'timeout': 10000, + 'adUnitCodes': ['/19968336/header-bid-tag-0'], + 'ortb2Fragments': { + 'global': { + 'site': { + 'page': 'http://www.localhost.localdomain:8888/integrationExamples/gpt/relevadRtdProvider_example.html?pbjs_debug=true', + 'domain': 'localhost.localdomain:8888', + 'publisher': { + 'domain': 'localhost.localdomain:8888' + } + }, + 'device': { + 'w': 355, + 'h': 682, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36', + 'language': 'en' + } + }, + 'bidder': {} + } +}; + +const adUnitsCommon = [ + { + 'code': '/19968336/header-bid-tag-0', + 'mediaTypes': { + 'banner': { 'sizes': [[728, 90]] } + }, + 'bids': [ + { + 'bidder': 'appnexus', + 'params': { 'placementId': '13144370' } + }, + { bidder: 'other' }, + { bidder: 'rubicon', 'params': { id: 1 } }, + { bidder: 'smart', }, + { bidder: 'ix', }, + { bidder: 'proxistore', } + ] + } +]; + +describe('relevadRtdProvider', function() { + describe('relevadSubmodule', function() { + it('successfully instantiates', function () { + expect(relevadSubmodule.init()).to.equal(true); + }); + }); + + describe('Add segments and categories test 1', function() { + it('adds contextual categories and segments', function() { + let moduleConfig = { ...deepClone(moduleConfigCommon) }; + let reqBids = { + ...deepClone(reqBidsCommon), + 'adUnits': deepClone(adUnitsCommon), + }; + + let data = { + segments: ['segment1', 'segment2'], + cats: { 'category3': 100 }, + }; + + (config.getConfig('ix') || {}).firstPartyData = null; + addRtdData(reqBids, data, moduleConfig, () => {}); + expect(reqBids.adUnits[0].bids[0].params.keywords).to.have.deep.property('relevad_rtd', ['segment1', 'segment2', 'category3']); + expect(reqBids.adUnits[0].bids[1].ortb2.site.ext.data).to.have.deep.property('relevad_rtd', ['category3']); + expect(reqBids.adUnits[0].bids[1].ortb2.user.ext.data).to.have.deep.property('relevad_rtd', ['segment1', 'segment2']); + expect(reqBids.adUnits[0].bids[3].params).to.have.deep.property('target', 'relevad_rtd=segment1;relevad_rtd=segment2;relevad_rtd=category3'); + expect(reqBids.adUnits[0].bids[5].ortb2.user.ext.data).to.have.deep.property('segments', ['segment1', 'segment2']); + expect(reqBids.adUnits[0].bids[5].ortb2.user.ext.data).to.have.deep.property('contextual_categories', ['category3']); + expect(reqBids.ortb2Fragments.bidder.rubicon.user.ext.data).to.have.deep.property('relevad_rtd', ['segment1', 'segment2']); + expect(config.getConfig('ix.firstPartyData')).to.have.deep.property('relevad_rtd', ['segment1', 'segment2', 'category3']); + }); + }); + + describe('Add segments and categories test 2 to one bidder out of many', function() { + it('adds contextual categories and segments', function() { + let moduleConfig = { ...deepClone(moduleConfigCommon) }; + let reqBids = { + ...deepClone(reqBidsCommon), + 'adUnits': deepClone(adUnitsCommon), + }; + + let data = { + segments: ['segment1', 'segment2'], + cats: { 'category3': 100 }, + wl: { 'appnexus': { 'placementId': '13144370' } }, + }; + + (config.getConfig('ix') || {}).firstPartyData = null; + addRtdData(reqBids, data, moduleConfig, () => { }); + expect(reqBids.adUnits[0].bids[0].params.keywords).to.have.deep.property('relevad_rtd', ['segment1', 'segment2', 'category3']); + expect(reqBids.adUnits[0].bids[1].ortb2?.site?.ext?.data || {}).to.not.have.property('relevad_rtd'); + expect(reqBids.adUnits[0].bids[1].ortb2?.user?.ext?.data || {}).to.not.have.property('relevad_rtd'); + expect(reqBids.adUnits[0].bids[3].params || {}).to.not.have.deep.property('target', 'relevad_rtd=segment1;relevad_rtd=segment2;relevad_rtd=category3'); + expect(reqBids.adUnits[0].bids[5].ortb2?.user?.ext?.data || {}).to.not.have.deep.property('segments', ['segment1', 'segment2']); + expect(reqBids.adUnits[0].bids[5].ortb2?.user?.ext?.data || {}).to.not.have.deep.property('contextual_categories', ['category3']); + expect(reqBids.adUnits[0].bids[5].ortb2?.user?.ext?.data || {}).to.not.have.deep.property('contextual_categories', {'0': 'category3'}); + expect(reqBids.ortb2Fragments?.bidder?.rubicon?.user?.ext?.data || {}).to.not.have.deep.property('relevad_rtd', ['segment1', 'segment2']); + expect(config.getConfig('ix.firstPartyData') || {}).to.not.have.deep.property('relevad_rtd', ['segment1', 'segment2', 'category3']); + }); + }); + + describe('Add segments and categories test 4', function() { + it('adds contextual categories and segments', function() { + let moduleConfig = { + 'dryrun': true, + params: { + setgpt: true, + minscore: 50, + partnerid: 12345, + } + }; + + let reqBids = { + 'timeout': 10000, + 'adUnits': deepClone(adUnitsCommon), + 'adUnitCodes': [ '/19968336/header-bid-tag-0' ], + 'ortb2Fragments': { + 'global': { + 'site': { + 'page': 'http://www.localhost.localdomain:8888/integrationExamples/gpt/relevadRtdProvider_example.html?pbjs_debug=true', + 'domain': 'localhost.localdomain:8888', + 'publisher': { + 'domain': 'localhost.localdomain:8888' + } + }, + 'device': { + 'w': 355, + 'h': 682, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36', + 'language': 'en' + } + }, + 'bidder': {} + }, + 'metrics': {}, + 'defer': { 'promise': {} } + } + + let data = { + segments: ['segment1', 'segment2'], + cats: {'category3': 100} + }; + (config.getConfig('ix') || {}).firstPartyData = null; + addRtdData(reqBids, data, moduleConfig, () => {}); + expect(reqBids.adUnits[0].bids[0].params.keywords).to.have.deep.property('relevad_rtd', ['segment1', 'segment2', 'category3']); + }); + }); + + describe('Get Segments And Categories', function() { + it('gets data from async request and adds contextual categories and segments', function() { + const moduleConfig = { + params: { + 'dryrun': true, + sdtgpt: false, + minscore: 50, + bidders: [{ bidder: 'appnexus' }, + { bidder: 'other' }] + } + }; + + let reqBidsConfigObj = { + adUnits: [{ + bids: [{ + bidder: 'appnexus', + params: { + placementId: 13144370 + } + }, { + bidder: 'other' + }] + }] + }; + + let data = { + segments: ['segment1', 'segment2'], + cats: {'category3': 100} + }; + + getBidRequestData(reqBidsConfigObj, () => {}, moduleConfig, {}); + + let request = server.requests[0]; + request.respond(200, responseHeader, JSON.stringify(data)); + + expect(reqBidsConfigObj.adUnits[0].bids[0].params.keywords).to.have.deep.property('relevad_rtd', ['segment1', 'segment2', 'category3']); + expect(reqBidsConfigObj.adUnits[0].bids[1].ortb2.site.ext.data).to.have.deep.property('relevad_rtd', ['category3']); + expect(reqBidsConfigObj.adUnits[0].bids[1].ortb2.user.ext.data).to.have.deep.property('relevad_rtd', ['segment1', 'segment2']); + }); + }); +}); + +describe('Process auction end data', function() { + it('Collects bid data on auction end event', function() { + const auctionEndData = { + 'auctionDetails': { + 'auctionId': 'f7ec9895-5809-475e-8fef-49cbc221921a', + 'auctionStatus': 'completed', + 'adUnits': [ + { + 'code': '/19968336/header-bid-tag-0', + 'mediaTypes': { + 'banner': { 'sizes': [ [ 728, 90 ] ] } + }, + 'bids': [ + { + 'bidder': 'appnexus', + 'params': { + 'placementId': '13144370', + 'keywords': { + 'relevad_rtd': [ 'IAB410-391', 'IAB63-53' ] + } + } + } + ], + 'ortb2Imp': { 'ext': { 'data': { 'relevad_rtd': [ 'IAB410-391', 'IAB63-53' ] }, } }, + 'sizes': [ [ 728, 90 ] ], + } + ], + 'adUnitCodes': [ '/19968336/header-bid-tag-0' ], + 'bidderRequests': [ + { + 'bidderCode': 'appnexus', + 'auctionId': 'f7ec9895-5809-475e-8fef-49cbc221921a', + 'bidderRequestId': '1d917281b2bf6c', + 'bids': [ + { + 'bidder': 'appnexus', + 'params': { + 'placementId': '13144370', + 'keywords': { + 'relevad_rtd': [ + 'IAB410-391', + 'IAB63-53' + ] + } + }, + 'ortb2Imp': { + 'ext': { 'data': { 'relevad_rtd': [ 'IAB410-391', 'IAB63-53' ] }, } + }, + 'mediaTypes': { 'banner': { 'sizes': [ [ 728, 90 ] ] } }, + 'adUnitCode': '/19968336/header-bid-tag-0', + 'sizes': [ [ 728, 90 ] ], + 'bidId': '20f0b347b07f94', + 'bidderRequestId': '1d917281b2bf6c', + 'auctionId': 'f7ec9895-5809-475e-8fef-49cbc221921a', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0, + 'ortb2': { + 'site': { + 'page': 'http://www.localhost.localdomain:8888/integrationExamples/gpt/relevadRtdProvider_example.html', + 'domain': 'localhost.localdomain:8888', + 'publisher': { 'domain': 'localhost.localdomain:8888' }, + 'cat': [ 'IAB410-391', 'IAB63-53' ], + 'pagecat': [ 'IAB410-391', 'IAB63-53' ], + 'sectioncat': [ 'IAB410-391', 'IAB63-53' ] + }, + 'device': { + 'w': 326, + 'h': 649, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36', + 'language': 'en' + } + } + } + ], + 'timeout': 10000, + 'refererInfo': { + 'reachedTop': true, + 'isAmp': false, + 'numIframes': 0, + 'stack': [ + 'http://www.localhost.localdomain:8888/integrationExamples/gpt/relevadRtdProvider_example.html' + ], + 'topmostLocation': 'http://www.localhost.localdomain:8888/integrationExamples/gpt/relevadRtdProvider_example.html', + 'location': 'http://www.localhost.localdomain:8888/integrationExamples/gpt/relevadRtdProvider_example.html', + 'canonicalUrl': null, + 'page': 'http://www.localhost.localdomain:8888/integrationExamples/gpt/relevadRtdProvider_example.html', + 'domain': 'www.localhost.localdomain:8888', + 'ref': null, + 'legacy': { + 'reachedTop': true, + 'isAmp': false, + 'numIframes': 0, + 'stack': [ 'http://www.localhost.localdomain:8888/integrationExamples/gpt/relevadRtdProvider_example.html' ], + 'referer': 'http://www.localhost.localdomain:8888/integrationExamples/gpt/relevadRtdProvider_example.html', + 'canonicalUrl': null + } + }, + 'ortb2': { + 'site': { + 'page': 'http://www.localhost.localdomain:8888/integrationExamples/gpt/relevadRtdProvider_example.html', + 'domain': 'localhost.localdomain:8888', + 'publisher': { 'domain': 'localhost.localdomain:8888' }, + 'cat': [ 'IAB410-391', 'IAB63-53' ], + 'pagecat': [ 'IAB410-391', 'IAB63-53' ], + 'sectioncat': [ 'IAB410-391', 'IAB63-53' ] + }, + 'device': { + 'w': 326, + 'h': 649, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36', + 'language': 'en' + } + }, + 'start': 1674132848498 + } + ], + 'noBids': [], + 'bidsReceived': [ + { + 'bidderCode': 'appnexus', + 'width': 728, + 'height': 90, + 'statusMessage': 'Bid available', + 'adId': '3222e6ead116f3', + 'requestId': '20f0b347b07f94', + 'transactionId': 'df8586ac-6476-4fbf-a727-eda99996dc39', + 'auctionId': 'f7ec9895-5809-475e-8fef-49cbc221921a', + 'mediaType': 'banner', + 'source': 'client', + 'cpm': 1.5, + 'creativeId': 98493734, + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 300, + 'adUnitCode': '/19968336/header-bid-tag-0', + 'adapterCode': 'appnexus', + 'originalCpm': 1.5, + 'originalCurrency': 'USD', + 'responseTimestamp': 1674132848649, + 'requestTimestamp': 1674132848498, + 'bidder': 'appnexus', + 'size': '728x90', + } + ], + }, + 'config': { + 'name': 'RelevadRTDModule', + 'waitForIt': true, + 'dryrun': true, + 'params': { + 'partnerid': 12345, + 'setgpt': true + } + }, + 'userConsent': { 'gdpr': null, 'usp': null, 'gpp': null, 'coppa': false } + }; + + let auctionDetails = auctionEndData['auctionDetails']; + let userConsent = auctionEndData['userConsent']; + let moduleConfig = auctionEndData['config']; + + relevadSubmodule.onAuctionEndEvent(auctionDetails, moduleConfig, userConsent); + expect(serverData.clientdata).to.deep.equal( + { + 'event': 'bids', + 'adunits': [ + { + 'code': '/19968336/header-bid-tag-0', + 'bids': [ + { + 'bidder': 'appnexus', + 'cpm': 1.5, + 'currency': 'USD', + 'type': 'banner', + 'ttr': undefined, + 'dealId': undefined, + 'size': '728x90' + } + ] + } + ], + 'reledata': { segments: ['segment1', 'segment2'], cats: { 'category3': 100 }, }, + 'gdpra': '', + 'gdprc': '', + 'aid': '', + 'cid': '12345', + 'pid': '', + } + ); + }); +}); diff --git a/test/spec/modules/relevantdigitalBidAdapter_spec.js b/test/spec/modules/relevantdigitalBidAdapter_spec.js new file mode 100644 index 00000000000..b2a5495b3cb --- /dev/null +++ b/test/spec/modules/relevantdigitalBidAdapter_spec.js @@ -0,0 +1,295 @@ +import {spec, resetBidderConfigs} from 'modules/relevantdigitalBidAdapter.js'; +import { parseUrl, deepClone } from 'src/utils.js'; + +const expect = require('chai').expect; + +const PBS_HOST = 'dev-api.relevant-digital.com'; +const PLACEMENT_ID = 'example_placement_id'; +const ACCOUNT_ID = 'example_account_id'; +const TEST_DOMAIN = 'example.com'; +const TEST_PAGE = `https://${TEST_DOMAIN}/page.html`; + +const BID_REQUEST = +{ + 'bidder': 'relevantdigital', + 'params': { + 'placementId': PLACEMENT_ID, + 'accountId': ACCOUNT_ID, + 'pbsHost': PBS_HOST, + }, + 'ortb2Imp': { + 'ext': { + 'tid': 'e13391ea-00f3-495d-99a6-d937990d73a9' + } + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 250 + ], + ] + } + }, + 'adUnitCode': '/19968336/header-bid-tag-0', + 'transactionId': 'e13391ea-00f3-495d-99a6-d937990d73a9', + 'sizes': [ + [ + 300, + 250 + ], + ], + 'bidId': '2d69406037a662', + 'bidderRequestId': '1decd098c76ed2', + 'auctionId': '251a6a36-a5c5-4b82-b2b3-538c148a29dd', + 'src': 'client', + 'metrics': { + 'requestBids.validate': 0.7, + 'requestBids.makeRequests': 2.9, + 'adapter.client.validate': 0.4, + 'adapters.client.relevantdigital.validate': 0.4 + }, + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0, + 'ortb2': { + 'site': { + 'page': TEST_PAGE, + 'domain': TEST_DOMAIN, + 'publisher': { + 'domain': 'relevant-digital.com' + } + }, + 'device': { + 'w': 1848, + 'h': 1007, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36', + 'language': 'en', + 'sua': { + 'source': 2, + 'platform': { + 'brand': 'Linux', + 'version': [ + '5', + '4', + '0' + ] + }, + 'browsers': [ + { + 'brand': 'Google Chrome', + 'version': [ + '111', + '0', + '5563', + '146' + ] + }, + ], + 'mobile': 0, + 'model': '', + 'bitness': '64', + 'architecture': 'x86' + } + } + } +}; + +const BIDDER_REQUEST = { + 'bidderCode': BID_REQUEST.bidder, + 'auctionId': BID_REQUEST.auctionId, + 'bidderRequestId': BID_REQUEST.bidderRequestId, + 'bids': [BID_REQUEST], + 'metrics': BID_REQUEST.metrics, + 'ortb2': BID_REQUEST.ortb2, + 'auctionStart': 1681224591370, + 'timeout': 1000, + 'refererInfo': { + 'reachedTop': true, + 'isAmp': false, + 'numIframes': 0, + 'stack': [ + TEST_PAGE + ], + 'topmostLocation': TEST_PAGE, + 'location': TEST_PAGE, + 'canonicalUrl': null, + 'page': TEST_PAGE, + 'domain': TEST_DOMAIN, + 'ref': null, + 'legacy': { + 'reachedTop': true, + 'isAmp': false, + 'numIframes': 0, + 'stack': [ + TEST_PAGE + ], + 'referer': TEST_PAGE, + 'canonicalUrl': null + } + }, + 'start': 1681224591375 +}; + +const BID_RESPONSE = { + 'seatbid': [ + { + 'bid': [ + { + 'id': '613673EF-A07C-4486-8EE9-3FC71A7DC73D', + 'impid': BID_REQUEST.bidId, + 'price': 10.76091063668997, + 'adm': '', + 'adomain': [ + 'www.addomain.com' + ], + 'iurl': 'http://localhost11', + 'crid': 'creative111', + 'w': 300, + 'h': 250, + 'ext': { + 'bidtype': 0, + 'dspid': 6, + 'origbidcpm': 1, + 'origbidcur': 'USD', + 'prebid': { + 'meta': { + 'adaptercode': 'pubmatic' + }, + 'targeting': { + 'hb_bidder': 'pubmatic', + 'hb_cache_host': PBS_HOST, + 'hb_cache_path': '/analytics_cache/read', + 'hb_format': 'banner', + 'hb_pb': '10.70', + 'hb_size': '300x250' + }, + 'type': 'banner', + 'video': { + 'duration': 0, + 'primary_category': '' + }, + 'events': { + 'win': `https://${PBS_HOST}/event?t=win&b=fed970f7-4295-456d-a251-38013faab795&a=620523ae7f4bbe1691bbb815&bidder=pubmatic&ts=1678646619765`, + 'imp': `https://${PBS_HOST}/event?t=imp&b=fed970f7-4295-456d-a251-38013faab795&a=620523ae7f4bbe1691bbb815&bidder=pubmatic&ts=1678646619765` + }, + 'bidid': 'fed970f7-4295-456d-a251-38013faab795' + } + } + } + ], + 'seat': 'pubmatic' + } + ], + 'cur': 'SEK', + 'ext': { + 'responsetimemillis': { + 'appnexus': 305, + 'pubmatic': 156 + }, + 'tmaxrequest': 750, + 'relevant': { + 'sync': [ + { 'type': 'redirect', 'url': 'https://example1.com/sync' }, + { 'type': 'redirect', 'url': 'https://example2.com/sync' }, + ], + }, + 'prebid': { + 'auctiontimestamp': 1678646619765, + 'passthrough': { + 'relevant': { + 'bidder': spec.code + } + } + } + } +}; + +const S2S_RESPONSE_BIDDER = BID_RESPONSE.seatbid[0].seat; + +const resetAndBuildRequest = (params) => { + resetBidderConfigs(); + const bidRequest = { + ...BID_REQUEST, + params: { + ...BID_REQUEST.params, + ...params, + }, + }; + return spec.buildRequests([bidRequest], BIDDER_REQUEST); +}; + +describe('Relevant Digital Bid Adaper', function () { + describe('buildRequests', () => { + const [request] = resetAndBuildRequest(); + const {data, url} = request + it('should give the correct URL', () => { + expect(url).equal(`https://${PBS_HOST}/openrtb2/auction`); + }); + it('should set the correct stored request ids', () => { + expect(data.ext.prebid.storedrequest.id).equal(ACCOUNT_ID); + expect(data.imp[0].ext.prebid.storedrequest.id).equal(PLACEMENT_ID); + }); + it('should include bidder code in passthrough object', () => { + expect(data.ext.prebid.passthrough.relevant.bidder).equal(spec.code); + }); + it('should set tmax to something below the timeout', () => { + expect(data.tmax).be.greaterThan(0); + expect(data.tmax).be.lessThan(BIDDER_REQUEST.timeout) + }); + }); + describe('interpreteResponse', () => { + const [request] = resetAndBuildRequest(); + const [bid] = spec.interpretResponse({ body: BID_RESPONSE }, request); + it('should not have S2S bidder\'s bidder code', () => { + expect(bid.bidderCode).not.equal(S2S_RESPONSE_BIDDER); + }); + it('should return the right creative content', () => { + const respBid = BID_RESPONSE.seatbid[0].bid[0]; + expect(bid.cpm).equal(respBid.price); + expect(bid.ad).equal(respBid.adm); + expect(bid.width).equal(respBid.w); + expect(bid.height).equal(respBid.h); + }); + }); + describe('interpreteResponse with useSourceBidderCode', () => { + const [request] = resetAndBuildRequest({ useSourceBidderCode: true }); + const [bid] = spec.interpretResponse({ body: BID_RESPONSE }, request); + it('should have S2S bidder\'s code', () => { + expect(bid.bidderCode).equal(S2S_RESPONSE_BIDDER); + }); + }); + describe('getUserSyncs with iframeEnabled', () => { + resetAndBuildRequest() + const allSyncs = spec.getUserSyncs({ iframeEnabled: true }, [{ body: BID_RESPONSE }], null, null); + const [{ url, type }] = allSyncs; + const { bidders, endpoint } = parseUrl(url).search; + it('should return a single sync object', () => { + expect(allSyncs.length).equal(1); + }); + it('should use iframe sync when available', () => { + expect(type).equal('iframe'); + }); + it('should sync to all s2s bidders', () => { + expect(bidders.split(',').sort()).to.deep.equal(['appnexus', 'pubmatic']); + }); + it('should sync to the right endpoint', () => { + expect(endpoint).equal(`https://${PBS_HOST}/cookie_sync`); + }); + it('should not sync to the same s2s bidders when called again', () => { + const newSyncs = spec.getUserSyncs({ iframeEnabled: true }, [{ body: BID_RESPONSE }], null, null); + expect(newSyncs).to.deep.equal([]); + }); + }); + describe('getUserSyncs with pixelEnabled', () => { + resetAndBuildRequest() + const responseSyncs = BID_RESPONSE.ext.relevant.sync; + const allSyncs = spec.getUserSyncs({ pixelEnabled: true }, [{ body: BID_RESPONSE }], null, null); + it('should return one sync object per pixel', () => { + const expectedResult = responseSyncs.map(({ url }) => ({url, type: 'image'})); + expect(allSyncs).to.deep.equal(expectedResult) + }); + }); +}); diff --git a/test/spec/modules/retailspotBidAdapter_spec.js b/test/spec/modules/retailspotBidAdapter_spec.js new file mode 100644 index 00000000000..39cddb323b8 --- /dev/null +++ b/test/spec/modules/retailspotBidAdapter_spec.js @@ -0,0 +1,426 @@ +import { expect } from 'chai'; + +import { spec } from 'modules/retailspotBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; + +describe('RetailSpot Adapter', function () { + const canonicalUrl = 'https://canonical.url/?t=%26'; + const referrerUrl = 'http://referrer.url/?param=value'; + const pageUrl = 'http://page.url/?param=value'; + const domain = 'domain:123'; + const env = 'preprod'; + const consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; + const bidderRequest = { + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'gdprConsent': { + consentString: consentString, + gdprApplies: true + }, + refererInfo: {location: referrerUrl, canonicalUrl, domain, topmostLocation: 'fakePageURL'}, + ortb2: {site: {page: pageUrl, ref: referrerUrl}} + }; + + const bidRequestWithSinglePlacement = [ + { + 'bidId': 'bid_id_0', + 'bidder': 'retailspot', + 'placementCode': 'adunit/hb-0', + 'params': { + 'placement': 'placement_0' + }, + 'sizes': '300x250', + 'mediaTypes': { + 'banner': { + 'sizes': ['300x250'] + }, + }, + 'transactionId': 'bid_id_0_transaction_id' + } + ]; + + const bidRequestWithMultipleMediatype = [ + { + 'bidId': 'bid_id_0', + 'bidder': 'retailspot', + 'placementCode': 'adunit/hb-0', + 'params': { + 'placement': 'placement_0' + }, + 'sizes': '300x250', + 'mediaTypes': { + 'banner': { + 'sizes': ['640x480'] + }, + 'video': { + 'playerSize': [640, 480], + 'context': 'outstream' + } + }, + 'transactionId': 'bid_id_0_transaction_id' + } + ]; + + const sentBidVideo = [ + { + 'bidId': 'bid_id_0', + 'placement': 'test-1234', + 'video': { + 'playerSize': [640, 480] + } + } + ]; + + const bidRequestWithDevPlacement = [ + { + 'bidId': 'bid_id_0', + 'bidder': 'retailspot', + 'placementCode': 'adunit/hb-0', + 'params': { + 'placement': 'placement_0', + 'env': 'dev' + }, + 'sizes': '300x250', + 'mediaTypes': + { 'banner': + {'sizes': ['300x250'] + } + }, + 'transactionId': 'bid_id_0_transaction_id' + } + ]; + + const bidRequestMultiPlacements = [ + { + 'bidId': 'bid_id_0', + 'bidder': 'retailspot', + 'placementCode': 'adunit/hb-0', + 'params': { + 'placement': 'placement_0' + }, + 'sizes': '300x250', + 'mediaTypes': + { 'banner': + {'sizes': ['300x250'] + } + }, + 'transactionId': 'bid_id_0_transaction_id' + }, + { + 'bidId': 'bid_id_1', + 'bidder': 'retailspot', + 'placementCode': 'adunit/hb-1', + 'params': { + 'placement': 'placement_1' + }, + 'sizes': [[300, 600]], + 'mediaTypes': + { 'banner': + {'sizes': ['300x600'] + } + }, + 'transactionId': 'bid_id_1_transaction_id' + }, + { + 'bidId': 'bid_id_2', + 'bidder': 'retailspot', + 'placementCode': 'adunit/hb-2', + 'params': {}, + 'sizes': '300x400', + 'transactionId': 'bid_id_2_transaction_id' + }, + { + 'bidId': 'bid_id_3', + 'bidder': 'retailspot', + 'placementCode': 'adunit/hb-3', + 'params': { + 'placement': 'placement_3' + }, + 'transactionId': 'bid_id_3_transaction_id' + } + ]; + + const requestDataOnePlacement = [ + { + 'bidId': 'bid_id_0', + 'placement': 'e622af275681965d3095808561a1e510', + 'width': 300, + 'height': 600 + } + ] + + const requestDataMultiPlacement = [ + { + 'bidId': 'bid_id_0', + 'placement': 'e622af275681965d3095808561a1e510', + 'width': 300, + 'height': 600 + }, + { + 'bidId': 'bid_id_1', + 'placement': 'e622af275681965d3095808561a1e510', + 'width': 400, + 'height': 250 + } + ] + + const testMetaObject = { + 'networkId': 123, + 'advertiserId': '3', + 'advertiserName': 'foobar', + 'advertiserDomains': ['foobar.com'], + 'brandId': '345', + 'brandName': 'Foo', + 'primaryCatId': '34', + 'secondaryCatIds': ['IAB-222', 'IAB-333'], + 'mediaType': 'banner' + }; + const admSample = "\u003cscript id=\"ayl-prebid-a11a121205932e75e622af275681965d\"\u003e\n(function(){\n\twindow.isPrebid = true\n\tvar prebidResults = /*PREBID*/{\"OnEvents\": {\"CLICK\": [{\"Kind\": \"PIXEL_URL\",\"Url\": \"https://testPixelCLICK.com/fake\"}],\"IMPRESSION\": [{\"Kind\": \"PIXEL_URL\",\"Url\": \"https://testPixelIMP.com/fake\"}, {\"Kind\": \"JAVASCRIPT_URL\",\"Url\": \"https://testJsIMP.com/fake.js\"}]},\"Disabled\": false,\"Attempt\": \"a11a121205932e75e622af275681965d\",\"ApiPrefix\": \"https://fo-api.omnitagjs.com/fo-api\",\"TrackingPrefix\": \"https://tracking.omnitagjs.com/tracking\",\"DynamicPrefix\": \"https://tag-dyn.omnitagjs.com/fo-dyn\",\"StaticPrefix\": \"https://fo-static.omnitagjs.com/fo-static\",\"BlobPrefix\": \"https://fo-api.omnitagjs.com/fo-api/blobs\",\"SspPrefix\": \"https://fo-ssp.omnitagjs.com/fo-ssp\",\"VisitorPrefix\": \"https://visitor.omnitagjs.com/visitor\",\"Trusted\": true,\"Placement\": \"e622af275681965d3095808561a1e510\",\"PlacementAccess\": \"ALL\",\"Site\": \"6e2df7a92203c3c7a25561ed63f25a27\",\"Lang\": \"EN\",\"SiteLogo\": null,\"HasSponsorImage\": true,\"ResizeIframe\": true,\"IntegrationConfig\": {\"Kind\": \"WIDGET\",\"Widget\": {\"ExtraStyleSheet\": \"\",\"Placeholders\": {\"Body\": {\"Color\": {\"R\": 77,\"G\": 21,\"B\": 82,\"A\": 100},\"BackgroundColor\": {\"R\": 255,\"G\": 255,\"B\": 255,\"A\": 100},\"FontFamily\": \"Lato\",\"Width\": \"100%\",\"Align\": \"\",\"BoxShadow\": true},\"CallToAction\": {\"Color\": {\"R\": 26,\"G\": 157,\"B\": 212,\"A\": 100}},\"Description\": {\"Length\": 130},\"Image\": {\"Width\": 600,\"Height\": 600,\"Lowres\": false,\"Raw\": false},\"Size\": {\"Height\": \"250px\",\"Width\": \"300px\"},\"Title\": {\"Color\": {\"R\": 219,\"G\": 181,\"B\": 255,\"A\": 100}}},\"Selector\": {\"Kind\": \"CSS\",\"Css\": \"#ayl-prebid-a11a121205932e75e622af275681965d\"},\"Insertion\": \"AFTER\",\"ClickFormat\": true,\"Creative20\": true,\"WidgetKind\": \"CREATIVE_TEMPLATE_4\"}},\"Legal\": \"Sponsored\",\"ForcedCampaign\": \"f1c80d4bb5643c222ae8de75e9b2f991\",\"ForcedTrack\": \"\",\"ForcedCreative\": \"\",\"ForcedSource\": \"\",\"DisplayMode\": \"DEFAULT\",\"Campaign\": \"f1c80d4bb5643c222ae8de75e9b2f991\",\"CampaignAccess\": \"ALL\",\"CampaignKind\": \"AD_TRAFFIC\",\"DataSource\": \"LOCAL\",\"DataSourceUrl\": \"\",\"DataSourceOnEventsIsolated\": false,\"DataSourceWithoutCookie\": false,\"Content\": {\"Preview\": {\"Thumbnail\": {\"Image\": {\"Kind\": \"EXTERNAL\",\"Data\": {\"External\": {\"Url\": \"https://tag-dyn.omnitagjs.com/fo-dyn/native/preview/image?key=fd4362d35bb174d6f1c80d4bb5643c22\\u0026kind=INTERNAL\\u0026ztop=0.000000\\u0026zleft=0.000000\\u0026zwidth=0.333333\\u0026zheight=1.000000\\u0026width=[width]\\u0026height=[height]\"}},\"ZoneTop\": 0,\"ZoneLeft\": 0,\"ZoneWidth\": 1,\"ZoneHeight\": 1,\"Smart\": false,\"NoTransform\": false,\"Quality\": \"NORMAL\"}},\"Text\": {\"CALLTOACTION\": \"Click here to learn more\",\"DESCRIPTION\": \"Considérant l'extrémité conjoncturelle, il serait bon d'anticiper toutes les voies de bon sens.\",\"SPONSOR\": \"Tested by\",\"TITLE\": \"Adserver Traffic Redirect Internal\"},\"Sponsor\": {\"Name\": \"QA Team\",\"Logo\": {\"Resource\": {\"Kind\": \"EXTERNAL\",\"Data\": {\"External\": {\"Url\": \"https://fo-static.omnitagjs.com/fo-static/native/images/info-ayl.svg\"}},\"ZoneTop\": 0,\"ZoneLeft\": 0,\"ZoneWidth\": 1,\"ZoneHeight\": 1,\"Smart\": false,\"NoTransform\": false,\"Quality\": \"NORMAL\"}}},\"Credit\": {\"Logo\": {\"Resource\": {\"Kind\": \"EXTERNAL\",\"Data\": {\"External\": {\"Url\": \"https://fo-static.omnitagjs.com/fo-static/native/images/info-ayl.png\"}},\"ZoneTop\": 0,\"ZoneLeft\": 0,\"ZoneWidth\": 1,\"ZoneHeight\": 1,\"Smart\": false,\"NoTransform\": false,\"Quality\": \"NORMAL\"}},\"Url\": \"https://blobs.omnitagjs.com/adchoice/\"}},\"Landing\": {\"Url\": \"https://www.w3.org/People/mimasa/test/xhtml/entities/entities-11.xhtml#lat1\",\"LegacyTracking\": false},\"ViewButtons\": {\"Close\": {\"Skip\": 6000}},\"InternalContentFields\": {\"AnimatedImage\": false}},\"AdDomain\": \"retailspot.com\",\"Opener\": \"REDIRECT\",\"PerformUITriggers\": [\"CLICK\"],\"RedirectionTarget\": \"TAB\"}/*PREBID*/;\n\tvar insertAds = function insertAds() {\insertAds();\n\t}\n})();\n\u003c/script\u003e"; + const responseWithSinglePlacement = [ + { + 'requestId': 'bid_id_0', + 'placement': 'placement_0', + 'ad': admSample, + 'cpm': 0.5, + 'height': 250, + 'width': 300, + 'meta': testMetaObject, + 'mediaType': 'banner' + } + ]; + + const responseWithSingleVideo = [{ + 'requestId': 'bid_id_0', + 'placement': 'placement_0', + 'vastXml': 'PFZBU1Q+RW1wdHkgc2FtcGxlPC92YXN0Pg==', + 'cpm': 0.5, + 'height': 300, + 'width': 530, + 'mediaType': 'video', + 'creativeId': 'testvideo123', + 'netRevenue': true, + 'currency': 'USD', + 'adId': 'fakeAdID', + 'dealId': 'fakeDealId' + }]; + + const videoResult = [{ + bidderCode: 'retailspot', + cpm: 0.5, + creativeId: 'testvideo123', + currency: 'USD', + height: 300, + netRevenue: true, + requestId: 'bid_id_0', + ttl: 3600, + mediaType: 'video', + meta: { + advertiserDomains: ['retail-spot.io'] + }, + vastXml: 'Empty sample', + width: 530, + adId: 'fakeAdID', + dealId: 'fakeDealId' + }]; + + const responseWithMultiplePlacements = [ + { + 'requestId': 'bid_id_0', + 'mediaType': 'banner', + 'placement': 'placement_0', + 'ad': 'placement_0', + 'cpm': 0.5, + 'height': 0, // test with wrong value + 'width': 300, + }, + { + 'requestId': 'bid_id_1', + 'mediaType': 'banner', + 'placement': 'placement_1', + 'ad': 'placement_1', + 'cpm': 0.6, + 'height': 250 + // 'width' test with missing value + } + ]; + const adapter = newBidder(spec); + + const DEV_URL = 'http://localhost:8090/'; + + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function () { + let bid = { + 'bidId': 'bid_id_1', + 'bidder': 'retailspot', + 'placementCode': 'adunit/hb-1', + 'params': { + 'placement': 'placement_1' + }, + 'sizes': [[300, 600]], + 'transactionId': 'bid_id_1_transaction_id' + }; + + let bidWSize = { + 'bidId': 'bid_id_1', + 'bidder': 'retailspot', + 'placementCode': 'adunit/hb-1', + 'params': { + 'placement': 'placement_1', + 'size': [250, 300], + }, + 'transactionId': 'bid_id_1_transaction_id' + }; + + it('should return true when required params found', function () { + expect(!!spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return true when required params found with size in bid params', function () { + expect(!!spec.isBidRequestValid(bidWSize)).to.equal(true); + }); + + it('should return false when required params are not passed', function () { + let bid = Object.assign({}, bid); + delete bid.size; + + expect(!!spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when required params are not passed', function () { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'placement': 0 + }; + expect(!!spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + it('should add gdpr/usp consent information to the request', function () { + let consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; + let uspConsentData = '1YCC'; + let bidderRequest = { + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'gdprConsent': { + consentString: consentString, + gdprApplies: true + }, + 'uspConsent': uspConsentData + }; + + bidderRequest.Bids = bidRequestWithSinglePlacement; + + const request = spec.buildRequests(bidRequestWithSinglePlacement, bidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.gdprConsent).to.exist; + expect(payload.gdprConsent.consentString).to.exist.and.to.equal(consentString); + expect(payload.uspConsent).to.exist.and.to.equal(uspConsentData); + }); + + it('sends bid request to endpoint with single placement', function () { + bidderRequest.Bids = bidRequestWithSinglePlacement; + + const request = spec.buildRequests(bidRequestWithSinglePlacement, bidderRequest); + const payload = JSON.parse(request.data); + + expect(request.url).to.contain('https://ssp.retail-spot.io/prebid'); + expect(request.method).to.equal('POST'); + + expect(payload).to.deep.equal(bidderRequest); + }); + + it('sends bid request to endpoint with single placement multiple mediatype', function () { + bidderRequest.Bids = bidRequestWithMultipleMediatype; + const request = spec.buildRequests(bidRequestWithSinglePlacement, bidderRequest); + const payload = JSON.parse(request.data); + + expect(request.url).to.contain('https://ssp.retail-spot.io/prebid'); + expect(request.method).to.equal('POST'); + + expect(payload).to.deep.equal(bidderRequest); + }); + + it('sends bid request to endpoint with multiple placements', function () { + bidderRequest.Bids = bidRequestMultiPlacements; + const request = spec.buildRequests(bidRequestMultiPlacements, bidderRequest); + const payload = JSON.parse(request.data); + + expect(request.url).to.contain('https://ssp.retail-spot.io/prebid'); + expect(request.method).to.equal('POST'); + + expect(payload).to.deep.equal(bidderRequest); + }); + + it('sends bid request to endpoint setted by parameters', function () { + const request = spec.buildRequests(bidRequestWithDevPlacement, bidderRequest); + expect(request.url).to.contain(DEV_URL); + }); + }); + // + describe('interpretResponse', function () { + let serverResponse; + + beforeEach(function () { + serverResponse = { + body: {} + } + }); + + it('handles nobid responses', function () { + let response = [{ + requestId: '123dfsdf', + placement: '12df1' + }]; + serverResponse.body = response; + let result = spec.interpretResponse(serverResponse, []); + expect(result).deep.equal([]); + }); + + it('receive reponse with single placement', function () { + serverResponse.body = responseWithSinglePlacement; + let result = spec.interpretResponse(serverResponse, {data: '{"bids":' + JSON.stringify(requestDataOnePlacement) + '}'}); + + expect(result.length).to.equal(1); + expect(result[0].cpm).to.equal(0.5); + expect(result[0].ad).to.equal(admSample); + expect(result[0].width).to.equal(300); + expect(result[0].height).to.equal(250); + expect(result[0].meta).to.deep.equal(testMetaObject); + }); + + it('receive reponse with multiple placement', function () { + serverResponse.body = responseWithMultiplePlacements; + let result = spec.interpretResponse(serverResponse, {data: '{"bids":' + JSON.stringify(requestDataMultiPlacement) + '}'}); + + expect(result.length).to.equal(2); + + expect(result[0].cpm).to.equal(0.5); + expect(result[0].ad).to.equal('placement_0'); + expect(result[0].width).to.equal(300); + expect(result[0].height).to.equal(600); + + expect(result[1].cpm).to.equal(0.6); + expect(result[1].ad).to.equal('placement_1'); + expect(result[1].width).to.equal(400); + expect(result[1].height).to.equal(250); + }); + + it('receive Vast reponse with Video ad', function () { + serverResponse.body = responseWithSingleVideo; + let result = spec.interpretResponse(serverResponse, {data: '{"bids":' + JSON.stringify(sentBidVideo) + '}'}); + + expect(result.length).to.equal(1); + expect(result).to.deep.equal(videoResult); + }); + }); +}); diff --git a/test/spec/modules/revcontentBidAdapter_spec.js b/test/spec/modules/revcontentBidAdapter_spec.js index e6ef4adec87..ca4e7bc4e4b 100644 --- a/test/spec/modules/revcontentBidAdapter_spec.js +++ b/test/spec/modules/revcontentBidAdapter_spec.js @@ -266,8 +266,6 @@ describe('revcontent adapter', function () { it('should set correct native params', function () { const result = spec.interpretResponse(serverResponse, bidRequest)[0]; - assert.equal(result.bidder, 'revcontent'); - assert.equal(result.bidderCode, 'revcontent'); assert.equal(result.mediaType, 'native'); assert.equal(result.requestId, '294a7f446202848'); assert.equal(result.cpm, '0.1'); @@ -279,8 +277,6 @@ describe('revcontent adapter', function () { bidRequest.bid[0].params.size.height = 90; const result = spec.interpretResponse(serverResponse, bidRequest)[0]; - assert.equal(result.bidder, 'revcontent'); - assert.equal(result.bidderCode, 'revcontent'); assert.equal(result.mediaType, 'native'); assert.equal(result.requestId, '294a7f446202848'); assert.equal(result.cpm, '0.1'); @@ -292,8 +288,6 @@ describe('revcontent adapter', function () { bidRequest.bid[0].params.size.height = 600; const result = spec.interpretResponse(serverResponse, bidRequest)[0]; - assert.equal(result.bidder, 'revcontent'); - assert.equal(result.bidderCode, 'revcontent'); assert.equal(result.mediaType, 'native'); assert.equal(result.requestId, '294a7f446202848'); assert.equal(result.cpm, '0.1'); @@ -304,8 +298,6 @@ describe('revcontent adapter', function () { bidRequest.bid[0].params.template = '

{title}

SEE MORE
'; const result = spec.interpretResponse(serverResponse, bidRequest)[0]; - assert.equal(result.bidder, 'revcontent'); - assert.equal(result.bidderCode, 'revcontent'); assert.equal(result.mediaType, 'native'); assert.equal(result.requestId, '294a7f446202848'); assert.equal(result.cpm, '0.1'); @@ -317,8 +309,6 @@ describe('revcontent adapter', function () { bidRequest.bid[0].params.size.height = 200; const result = spec.interpretResponse(serverResponse, bidRequest)[0]; - assert.equal(result.bidder, 'revcontent'); - assert.equal(result.bidderCode, 'revcontent'); assert.equal(result.mediaType, 'native'); assert.equal(result.requestId, '294a7f446202848'); assert.equal(result.cpm, '0.1'); diff --git a/test/spec/modules/richaudienceBidAdapter_spec.js b/test/spec/modules/richaudienceBidAdapter_spec.js index 86d3df8be59..ea45ff7e0b0 100644 --- a/test/spec/modules/richaudienceBidAdapter_spec.js +++ b/test/spec/modules/richaudienceBidAdapter_spec.js @@ -25,6 +25,41 @@ describe('Richaudience adapter tests', function () { auctionId: '0cb3144c-d084-4686-b0d6-f5dbe917c563', bidRequestsCount: 1, bidderRequestId: '1858b7382993ca', + ortb2Imp: { + ext: { + tid: '29df2112-348b-4961-8863-1b33684d95e6', + } + }, + user: {} + }]; + + var DEFAULT_PARAMS_NEW_SIZES_GPID = [{ + adUnitCode: 'test-div', + bidId: '2c7c8e9c900244', + ortb2Imp: { + ext: { + gpid: '/19968336/header-bid-tag-1#example-2', + data: { + pbadslot: '/19968336/header-bid-tag-1#example-2' + } + } + }, + mediaTypes: { + banner: { + sizes: [ + [300, 250], [300, 600], [728, 90], [970, 250]] + } + }, + bidder: 'richaudience', + params: { + bidfloor: 0.5, + pid: 'ADb1f40rmi', + supplyType: 'site', + keywords: 'key1=value1;key2=value2' + }, + auctionId: '0cb3144c-d084-4686-b0d6-f5dbe917c563', + bidRequestsCount: 1, + bidderRequestId: '1858b7382993ca', transactionId: '29df2112-348b-4961-8863-1b33684d95e6', user: {} }]; @@ -236,6 +271,7 @@ describe('Richaudience adapter tests', function () { expect(requestContent).to.have.property('numIframes').and.to.equal(0); expect(typeof requestContent.scr_rsl === 'string') expect(typeof requestContent.cpuc === 'number') + expect(typeof requestContent.gpid === 'string') expect(requestContent).to.have.property('kws').and.to.equal('key1=value1;key2=value2'); }) @@ -831,6 +867,18 @@ describe('Richaudience adapter tests', function () { expect(requestContent).to.have.property('schain').to.deep.equal(schain); }) + it('should pass gpid', function() { + const request = spec.buildRequests(DEFAULT_PARAMS_NEW_SIZES_GPID, { + gdprConsent: { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }, + refererInfo: {} + }) + const requestContent = JSON.parse(request[0].data); + expect(requestContent).to.have.property('gpid').and.to.equal('/19968336/header-bid-tag-1#example-2'); + }) + describe('userSync', function () { it('Verifies user syncs iframe include', function () { config.setConfig({ diff --git a/test/spec/modules/riseBidAdapter_spec.js b/test/spec/modules/riseBidAdapter_spec.js index 4fa4ff354ec..eed8d74f271 100644 --- a/test/spec/modules/riseBidAdapter_spec.js +++ b/test/spec/modules/riseBidAdapter_spec.js @@ -62,7 +62,8 @@ describe('riseAdapter', function () { 'mediaTypes': { 'video': { 'playerSize': [[640, 480]], - 'context': 'instream' + 'context': 'instream', + 'plcmt': 1 } }, 'vastXml': '"..."' @@ -116,6 +117,11 @@ describe('riseAdapter', function () { expect(request.data.bids[0].placementId).to.equal(placementId); }); + it('sends the plcmt to ENDPOINT via POST', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.bids[0].plcmt).to.equal(1); + }); + it('sends the is_wrapper parameter to ENDPOINT via POST', function() { const request = spec.buildRequests(bidRequests, bidderRequest); expect(request.data.params).to.be.an('object'); @@ -341,6 +347,86 @@ describe('riseAdapter', function () { expect(request.data.bids[0]).to.be.an('object'); expect(request.data.bids[0]).to.have.property('floorPrice', 1.5); }); + + it('should check sua param in bid request', function() { + const sua = { + 'platform': { + 'brand': 'macOS', + 'version': ['12', '4', '0'] + }, + 'browsers': [ + { + 'brand': 'Chromium', + 'version': [ '106', '0', '5249', '119' ] + }, + { + 'brand': 'Google Chrome', + 'version': [ '106', '0', '5249', '119' ] + }, + { + 'brand': 'Not;A=Brand', + 'version': [ '99', '0', '0', '0' ] + } + ], + 'mobile': 0, + 'model': '', + 'bitness': '64', + 'architecture': 'x86' + } + const bid = utils.deepClone(bidRequests[0]); + bid.ortb2 = { + 'device': { + 'sua': { + 'platform': { + 'brand': 'macOS', + 'version': [ '12', '4', '0' ] + }, + 'browsers': [ + { + 'brand': 'Chromium', + 'version': [ '106', '0', '5249', '119' ] + }, + { + 'brand': 'Google Chrome', + 'version': [ '106', '0', '5249', '119' ] + }, + { + 'brand': 'Not;A=Brand', + 'version': [ '99', '0', '0', '0' ] + } + ], + 'mobile': 0, + 'model': '', + 'bitness': '64', + 'architecture': 'x86' + } + } + } + const requestWithSua = spec.buildRequests([bid], bidderRequest); + const data = requestWithSua.data; + expect(data.bids[0].sua).to.exist; + expect(data.bids[0].sua).to.deep.equal(sua); + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.bids[0].sua).to.not.exist; + }); + + describe('COPPA Param', function() { + it('should set coppa equal 0 in bid request if coppa is set to false', function() { + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.bids[0].coppa).to.be.equal(0); + }); + + it('should set coppa equal 1 in bid request if coppa is set to true', function() { + const bid = utils.deepClone(bidRequests[0]); + bid.ortb2 = { + 'regs': { + 'coppa': true, + } + }; + const request = spec.buildRequests([bid], bidderRequest); + expect(request.data.bids[0].coppa).to.be.equal(1); + }); + }); }); describe('interpretResponse', function () { diff --git a/test/spec/modules/rtbhouseBidAdapter_spec.js b/test/spec/modules/rtbhouseBidAdapter_spec.js index 6d41df7605b..0b944dcb077 100644 --- a/test/spec/modules/rtbhouseBidAdapter_spec.js +++ b/test/spec/modules/rtbhouseBidAdapter_spec.js @@ -53,16 +53,18 @@ describe('RTBHouseAdapter', () => { describe('buildRequests', function () { let bidRequests; - const bidderRequest = { - 'refererInfo': { - 'numIframes': 0, - 'reachedTop': true, - 'referer': 'https://example.com', - 'stack': ['https://example.com'] - } - }; + let bidderRequest; beforeEach(() => { + bidderRequest = { + 'auctionId': 'bidderrequest-auction-id', + 'refererInfo': { + 'numIframes': 0, + 'reachedTop': true, + 'referer': 'https://example.com', + 'stack': ['https://example.com'] + } + }; bidRequests = [ { 'bidder': 'rtbhouse', @@ -82,6 +84,11 @@ describe('RTBHouseAdapter', () => { 'bidderRequestId': '22edbae2733bf6', 'auctionId': '1d1a030790a475', 'transactionId': 'example-transaction-id', + 'ortb2Imp': { + 'ext': { + 'tid': 'ortb2Imp-transaction-id-1' + } + }, 'schain': { 'ver': '1.0', 'complete': 1, @@ -203,7 +210,7 @@ describe('RTBHouseAdapter', () => { const bidRequest = Object.assign([], bidRequests); const request = spec.buildRequests(bidRequest, bidderRequest); const data = JSON.parse(request.data); - expect(data.source.tid).to.equal('example-transaction-id'); + expect(data.source.tid).to.equal('bidderrequest-auction-id'); }); it('should include bidfloor from floor module if avaiable', () => { @@ -256,6 +263,13 @@ describe('RTBHouseAdapter', () => { expect(data.source).to.have.deep.property('tid'); }); + it('should include impression level transaction id when provided', () => { + const bidRequest = Object.assign([], bidRequests); + const request = spec.buildRequests(bidRequest, bidderRequest); + const data = JSON.parse(request.data); + expect(data.imp[0].ext.tid).to.equal('ortb2Imp-transaction-id-1'); + }); + it('should not include invalid schain', () => { const bidRequest = Object.assign([], bidRequests); bidRequest[0].schain = { @@ -268,6 +282,28 @@ describe('RTBHouseAdapter', () => { expect(data.source).to.not.have.property('ext'); }); + it('should include first party data', function () { + const bidRequest = Object.assign([], bidRequests); + const localBidderRequest = { + ...bidderRequest, + ortb2: { + bcat: ['IAB1', 'IAB2-1'], + badv: ['domain1.com', 'domain2.com'], + site: { ext: { data: 'some site data' } }, + device: { ext: { data: 'some device data' } }, + user: { ext: { data: 'some user data' } } + } + }; + + const request = spec.buildRequests(bidRequest, localBidderRequest); + const data = JSON.parse(request.data); + expect(data.bcat).to.deep.equal(localBidderRequest.ortb2.bcat); + expect(data.badv).to.deep.equal(localBidderRequest.ortb2.badv); + expect(data.site).to.nested.include({'ext.data': 'some site data'}); + expect(data.device).to.nested.include({'ext.data': 'some device data'}); + expect(data.user).to.nested.include({'ext.data': 'some user data'}); + }); + context('FLEDGE', function() { afterEach(function () { config.resetConfig(); @@ -282,6 +318,41 @@ describe('RTBHouseAdapter', () => { expect(request.method).to.equal('POST'); }); + it('sets default fledgeConfig object values when none available from config', function () { + let bidRequest = Object.assign([], bidRequests); + delete bidRequest[0].params.test; + + config.setConfig({ fledgeConfig: false }); + const request = spec.buildRequests(bidRequest, { ...bidderRequest, fledgeEnabled: true }); + const data = JSON.parse(request.data); + expect(data.ext).to.exist.and.to.be.a('object'); + expect(data.ext.fledge_config).to.exist.and.to.be.a('object'); + expect(data.ext.fledge_config).to.contain.keys('seller', 'decisionLogicUrl', 'sellerTimeout'); + expect(data.ext.fledge_config.seller).to.equal('https://fledge-ssp.creativecdn.com'); + expect(data.ext.fledge_config.decisionLogicUrl).to.equal('https://fledge-ssp.creativecdn.com/component-seller-prebid.js'); + expect(data.ext.fledge_config.sellerTimeout).to.equal(500); + }); + + it('sets a fledgeConfig object values when available from config', function () { + let bidRequest = Object.assign([], bidRequests); + delete bidRequest[0].params.test; + + config.setConfig({ + fledgeConfig: { + seller: 'https://sellers.domain', + decisionLogicUrl: 'https://sellers.domain/decision.url' + } + }); + const request = spec.buildRequests(bidRequest, { ...bidderRequest, fledgeEnabled: true }); + const data = JSON.parse(request.data); + expect(data.ext).to.exist.and.to.be.a('object'); + expect(data.ext.fledge_config).to.exist.and.to.be.a('object'); + expect(data.ext.fledge_config).to.contain.keys('seller', 'decisionLogicUrl'); + expect(data.ext.fledge_config.seller).to.equal('https://sellers.domain'); + expect(data.ext.fledge_config.decisionLogicUrl).to.equal('https://sellers.domain/decision.url'); + expect(data.ext.fledge_config.sellerTimeout).to.not.exist; + }); + it('when FLEDGE is disabled, should not send imp.ext.ae', function () { let bidRequest = Object.assign([], bidRequests); delete bidRequest[0].params.test; @@ -617,10 +688,10 @@ describe('RTBHouseAdapter', () => { expect(bids[0].meta.advertiserDomains).to.deep.equal(['rtbhouse.com']); expect(bids[0].native).to.deep.equal({ title: 'Title text', - clickUrl: encodeURIComponent('https://example.com'), + clickUrl: encodeURI('https://example.com'), impressionTrackers: ['https://example.com/imptracker'], image: { - url: encodeURIComponent('https://example.com/image.jpg'), + url: encodeURI('https://example.com/image.jpg'), width: 150, height: 50 }, diff --git a/test/spec/modules/rubiconAnalyticsAdapter_spec.js b/test/spec/modules/rubiconAnalyticsAdapter_spec.js deleted file mode 100644 index 05b6cc6b6f4..00000000000 --- a/test/spec/modules/rubiconAnalyticsAdapter_spec.js +++ /dev/null @@ -1,2540 +0,0 @@ -import rubiconAnalyticsAdapter, { - SEND_TIMEOUT, - parseBidResponse, - getHostNameFromReferer, - storage, - rubiConf, - resetRubiConf -} from 'modules/rubiconAnalyticsAdapter.js'; -import CONSTANTS from 'src/constants.json'; -import { config } from 'src/config.js'; -import { server } from 'test/mocks/xhr.js'; -import * as mockGpt from '../integration/faker/googletag.js'; -import { - setConfig, - addBidResponseHook, -} from 'modules/currency.js'; - -let Ajv = require('ajv'); -let schema = require('./rubiconAnalyticsSchema.json'); -let ajv = new Ajv({ - allErrors: true -}); - -let validator = ajv.compile(schema); - -function validate(message) { - validator(message); - expect(validator.errors).to.deep.equal(null); -} - -let events = require('src/events.js'); -let utils = require('src/utils.js'); - -const { - EVENTS: { - AUCTION_INIT, - AUCTION_END, - BID_REQUESTED, - BID_RESPONSE, - BIDDER_DONE, - BID_WON, - BID_TIMEOUT, - SET_TARGETING, - BILLABLE_EVENT - } -} = CONSTANTS; - -const BID = { - 'bidder': 'rubicon', - 'width': 640, - 'height': 480, - 'mediaType': 'video', - 'statusMessage': 'Bid available', - 'bidId': '2ecff0db240757', - 'adId': 'fake_ad_id', - 'source': 'client', - 'requestId': '2ecff0db240757', - 'currency': 'USD', - 'creativeId': '3571560', - 'cpm': 1.22752, - 'ttl': 300, - 'netRevenue': false, - 'ad': '', - 'rubiconTargeting': { - 'rpfl_elemid': '/19968336/header-bid-tag-0', - 'rpfl_14062': '2_tier0100' - }, - 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa', - 'responseTimestamp': 1519149629415, - 'requestTimestamp': 1519149628471, - 'adUnitCode': '/19968336/header-bid-tag-0', - 'timeToRespond': 944, - 'pbLg': '1.00', - 'pbMg': '1.20', - 'pbHg': '1.22', - 'pbAg': '1.20', - 'pbDg': '1.22', - 'pbCg': '', - 'size': '640x480', - 'adserverTargeting': { - 'hb_bidder': 'rubicon', - 'hb_adid': '2ecff0db240757', - 'hb_pb': 1.20, - 'hb_size': '640x480', - 'hb_source': 'client' - }, - getStatusCode() { - return 1; - } -}; - -const BID2 = Object.assign({}, BID, { - adUnitCode: '/19968336/header-bid-tag1', - bidId: '3bd4ebb1c900e2', - adId: 'fake_ad_id', - requestId: '3bd4ebb1c900e2', - width: 728, - height: 90, - mediaType: 'banner', - cpm: 1.52, - source: 'server', - seatBidId: 'aaaa-bbbb-cccc-dddd', - rubiconTargeting: { - 'rpfl_elemid': '/19968336/header-bid-tag1', - 'rpfl_14062': '2_tier0100' - }, - adserverTargeting: { - 'hb_bidder': 'rubicon', - 'hb_adid': '3bd4ebb1c900e2', - 'hb_pb': '1.500', - 'hb_size': '728x90', - 'hb_source': 'server' - } -}); - -const BID3 = Object.assign({}, BID, { - adUnitCode: '/19968336/siderail-tag1', - bidId: '5fg6hyy4r879f0', - adId: 'fake_ad_id', - requestId: '5fg6hyy4r879f0', - width: 300, - height: 250, - mediaType: 'banner', - cpm: 2.01, - source: 'server', - seatBidId: 'aaaa-bbbb-cccc-dddd', - rubiconTargeting: { - 'rpfl_elemid': '/19968336/siderail-tag1', - 'rpfl_14062': '15_tier0200' - }, - adserverTargeting: { - 'hb_bidder': 'rubicon', - 'hb_adid': '5fg6hyy4r879f0', - 'hb_pb': '2.00', - 'hb_size': '300x250', - 'hb_source': 'server' - } -}); - -const BID4 = Object.assign({}, BID, { - adUnitCode: '/19968336/header-bid-tag1', - bidId: '3bd4ebb1c900e2', - adId: 'fake_ad_id', - requestId: '3bd4ebb1c900e2', - width: 728, - height: 90, - mediaType: 'banner', - cpm: 1.52, - source: 'server', - pbsBidId: 'zzzz-yyyy-xxxx-wwww', - seatBidId: 'aaaa-bbbb-cccc-dddd', - rubiconTargeting: { - 'rpfl_elemid': '/19968336/header-bid-tag1', - 'rpfl_14062': '2_tier0100' - }, - adserverTargeting: { - 'hb_bidder': 'rubicon', - 'hb_adid': '3bd4ebb1c900e2', - 'hb_pb': '1.500', - 'hb_size': '728x90', - 'hb_source': 'server' - } -}); - -const floorMinRequest = { - 'bidder': 'rubicon', - 'params': { - 'accountId': '14062', - 'siteId': '70608', - 'zoneId': '335918', - 'userId': '12346', - 'keywords': ['a', 'b', 'c'], - 'inventory': { 'rating': '4-star', 'prodtype': 'tech' }, - 'visitor': { 'ucat': 'new', 'lastsearch': 'iphone' }, - 'position': 'atf' - }, - 'mediaTypes': { - 'banner': { - 'sizes': [[300, 250]] - } - }, - 'adUnitCode': '/19968336/siderail-tag1', - 'transactionId': 'c435626g-9e3f-401a-bee1-d56aec29a1d4', - 'sizes': [[300, 250]], - 'bidId': '5fg6hyy4r879f0', - 'bidderRequestId': '1be65d7958826a', - 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa' -}; - -const MOCK = { - SET_TARGETING: { - [BID.adUnitCode]: BID.adserverTargeting, - [BID2.adUnitCode]: BID2.adserverTargeting, - [BID3.adUnitCode]: BID3.adserverTargeting - }, - AUCTION_INIT: { - 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa', - 'timestamp': 1519767010567, - 'auctionStatus': 'inProgress', - 'adUnits': [{ - 'code': '/19968336/header-bid-tag1', - 'sizes': [[640, 480]], - 'bids': [{ - 'bidder': 'rubicon', - 'params': { - 'accountId': 1001, 'siteId': 113932, 'zoneId': 535512 - } - }], - 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014' - } - ], - 'adUnitCodes': ['/19968336/header-bid-tag1'], - 'bidderRequests': [{ - 'bidderCode': 'rubicon', - 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa', - 'bidderRequestId': '1be65d7958826a', - 'bids': [{ - 'bidder': 'rubicon', - 'params': { - 'accountId': 1001, 'siteId': 113932, 'zoneId': 535512 - }, - 'mediaTypes': { - 'banner': { - 'sizes': [[640, 480]] - } - }, - 'adUnitCode': '/19968336/header-bid-tag1', - 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014', - 'sizes': [[640, 480]], - 'bidId': '2ecff0db240757', - 'bidderRequestId': '1be65d7958826a', - 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa', - 'src': 'client', - 'bidRequestsCount': 1 - } - ], - 'timeout': 3000, - 'refererInfo': { - 'page': 'http://www.test.com/page.html', 'reachedTop': true, 'numIframes': 0, 'stack': ['http://www.test.com/page.html'] - } - } - ], - 'bidsReceived': [], - 'winningBids': [], - 'timeout': 3000, - 'config': { - 'accountId': 1001, 'endpoint': '//localhost:9999/event' - } - }, - BID_REQUESTED: { - 'bidderCode': 'rubicon', - 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa', - 'bidderRequestId': '1be65d7958826a', - 'bids': [ - { - 'bidder': 'rubicon', - 'params': { - 'accountId': '1001', - 'siteId': '70608', - 'zoneId': '335918', - 'userId': '12346', - 'keywords': ['a', 'b', 'c'], - 'inventory': 'test', - 'visitor': { 'ucat': 'new', 'lastsearch': 'iphone' }, - 'position': 'btf', - 'video': { - 'language': 'en', - 'playerHeight': 480, - 'playerWidth': 640, - 'size_id': 203, - 'skip': 1, - 'skipdelay': 15, - 'aeParams': { - 'p_aso.video.ext.skip': '1', - 'p_aso.video.ext.skipdelay': '15' - } - } - }, - 'mediaTypes': { - 'video': { - 'context': 'instream', - 'playerSize': [640, 480] - } - }, - 'adUnitCode': '/19968336/header-bid-tag-0', - 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014', - 'sizes': [[640, 480]], - 'bidId': '2ecff0db240757', - 'bidderRequestId': '1be65d7958826a', - 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa', - 'src': 'client' - }, - { - 'bidder': 'rubicon', - 'params': { - 'accountId': '14062', - 'siteId': '70608', - 'zoneId': '335918', - 'userId': '12346', - 'keywords': ['a', 'b', 'c'], - 'inventory': { 'rating': '4-star', 'prodtype': 'tech' }, - 'visitor': { 'ucat': 'new', 'lastsearch': 'iphone' }, - 'position': 'atf' - }, - 'mediaTypes': { - 'banner': { - 'sizes': [[1000, 300], [970, 250], [728, 90]] - } - }, - 'adUnitCode': '/19968336/header-bid-tag1', - 'transactionId': 'c116413c-9e3f-401a-bee1-d56aec29a1d4', - 'sizes': [[1000, 300], [970, 250], [728, 90]], - 'bidId': '3bd4ebb1c900e2', - 'bidderRequestId': '1be65d7958826a', - 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa', - 'src': 's2s' - } - ], - 'auctionStart': 1519149536560, - 'timeout': 5000, - 'start': 1519149562216, - 'refererInfo': { - 'page': 'http://www.test.com/page.html', 'reachedTop': true, 'numIframes': 0, 'stack': ['http://www.test.com/page.html'] - } - }, - BID_RESPONSE: [ - BID, - BID2, - BID3 - ], - AUCTION_END: { - 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa' - }, - BID_WON: [ - Object.assign({}, BID, { - 'status': 'rendered' - }), - Object.assign({}, BID2, { - 'status': 'rendered' - }), - Object.assign({}, BID3, { - 'status': 'rendered' - }) - ], - BIDDER_DONE: { - 'bidderCode': 'rubicon', - 'serverResponseTimeMs': 42, - 'bids': [ - BID, - Object.assign({}, BID2, { - 'serverResponseTimeMs': 42, - }), - Object.assign({}, BID3, { - 'serverResponseTimeMs': 55, - }) - ] - }, - BID_TIMEOUT: [ - { - 'bidId': '2ecff0db240757', - 'bidder': 'rubicon', - 'adUnitCode': '/19968336/header-bid-tag-0', - 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa' - } - ] -}; - -const STUBBED_UUID = '12345678-1234-1234-1234-123456789abc'; - -const ANALYTICS_MESSAGE = { - 'channel': 'web', - 'integration': 'pbjs', - 'version': '$prebid.version$', - 'referrerUri': 'http://www.test.com/page.html', - 'session': { - 'expires': 1519788613781, - 'id': STUBBED_UUID, - 'start': 1519767013781 - }, - 'timestamps': { - 'auctionEnded': 1519767013781, - 'eventTime': 1519767013781, - 'prebidLoaded': rubiconAnalyticsAdapter.MODULE_INITIALIZED_TIME - }, - 'trigger': 'allBidWons', - 'referrerHostname': 'www.test.com', - 'auctions': [ - { - - 'auctionEnd': 1519767013781, - 'auctionStart': 1519767010567, - 'bidderOrder': ['rubicon'], - 'requestId': '25c6d7f5-699a-4bfc-87c9-996f915341fa', - 'clientTimeoutMillis': 3000, - 'serverTimeoutMillis': 1000, - 'accountId': 1001, - 'samplingFactor': 1, - 'adUnits': [ - { - 'adUnitCode': '/19968336/header-bid-tag-0', - 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014', - 'videoAdFormat': 'outstream', - 'mediaTypes': [ - 'video' - ], - 'dimensions': [ - { - 'width': 640, - 'height': 480 - } - ], - 'status': 'success', - 'accountId': 1001, - 'siteId': 70608, - 'zoneId': 335918, - 'adserverTargeting': { - 'hb_bidder': 'rubicon', - 'hb_adid': '2ecff0db240757', - 'hb_pb': '1.200', - 'hb_size': '640x480', - 'hb_source': 'client' - }, - 'bids': [ - { - 'bidder': 'rubicon', - 'bidId': '2ecff0db240757', - 'status': 'success', - 'source': 'client', - 'clientLatencyMillis': 3214, - 'params': { - 'accountId': '1001', - 'siteId': '70608', - 'zoneId': '335918' - }, - 'bidResponse': { - 'bidPriceUSD': 1.22752, - 'dimensions': { - 'width': 640, - 'height': 480 - }, - 'mediaType': 'video' - } - } - ] - }, - { - 'adUnitCode': '/19968336/header-bid-tag1', - 'transactionId': 'c116413c-9e3f-401a-bee1-d56aec29a1d4', - 'mediaTypes': [ - 'banner' - ], - 'dimensions': [ - { - 'width': 1000, - 'height': 300 - }, - { - 'width': 970, - 'height': 250 - }, - { - 'width': 728, - 'height': 90 - } - ], - 'status': 'success', - 'adserverTargeting': { - 'hb_bidder': 'rubicon', - 'hb_adid': '3bd4ebb1c900e2', - 'hb_pb': '1.500', - 'hb_size': '728x90', - 'hb_source': 'server' - }, - 'bids': [ - { - 'bidder': 'rubicon', - 'bidId': 'aaaa-bbbb-cccc-dddd', - 'status': 'success', - 'source': 'server', - 'clientLatencyMillis': 3214, - 'serverLatencyMillis': 42, - 'params': { - 'accountId': '14062', - 'siteId': '70608', - 'zoneId': '335918' - }, - 'bidResponse': { - 'bidPriceUSD': 1.52, - 'dimensions': { - 'width': 728, - 'height': 90 - }, - 'mediaType': 'banner' - } - } - ] - } - ] - } - ], - 'bidsWon': [ - { - 'bidder': 'rubicon', - 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014', - 'adUnitCode': '/19968336/header-bid-tag-0', - 'bidId': '2ecff0db240757', - 'status': 'success', - 'source': 'client', - 'clientLatencyMillis': 3214, - 'samplingFactor': 1, - 'accountId': 1001, - 'siteId': 70608, - 'zoneId': 335918, - 'params': { - 'accountId': '1001', - 'siteId': '70608', - 'zoneId': '335918' - }, - 'videoAdFormat': 'outstream', - 'mediaTypes': [ - 'video' - ], - 'adserverTargeting': { - 'hb_bidder': 'rubicon', - 'hb_adid': '2ecff0db240757', - 'hb_pb': '1.200', - 'hb_size': '640x480', - 'hb_source': 'client' - }, - 'bidResponse': { - 'bidPriceUSD': 1.22752, - 'dimensions': { - 'width': 640, - 'height': 480 - }, - 'mediaType': 'video' - }, - 'bidwonStatus': 'success' - }, - { - 'bidder': 'rubicon', - 'transactionId': 'c116413c-9e3f-401a-bee1-d56aec29a1d4', - 'adUnitCode': '/19968336/header-bid-tag1', - 'bidId': 'aaaa-bbbb-cccc-dddd', - 'status': 'success', - 'source': 'server', - 'clientLatencyMillis': 3214, - 'serverLatencyMillis': 42, - 'samplingFactor': 1, - 'accountId': 1001, - 'params': { - 'accountId': '14062', - 'siteId': '70608', - 'zoneId': '335918' - }, - 'mediaTypes': [ - 'banner' - ], - 'adserverTargeting': { - 'hb_bidder': 'rubicon', - 'hb_adid': '3bd4ebb1c900e2', - 'hb_pb': '1.500', - 'hb_size': '728x90', - 'hb_source': 'server' - }, - 'bidResponse': { - 'bidPriceUSD': 1.52, - 'dimensions': { - 'width': 728, - 'height': 90 - }, - 'mediaType': 'banner' - }, - 'bidwonStatus': 'success' - } - ], - 'wrapper': { - 'name': '10000_fakewrapper_test' - } -}; - -function performStandardAuction(gptEvents, auctionId = MOCK.AUCTION_INIT.auctionId) { - events.emit(AUCTION_INIT, { ...MOCK.AUCTION_INIT, auctionId }); - events.emit(BID_REQUESTED, { ...MOCK.BID_REQUESTED, auctionId }); - events.emit(BID_RESPONSE, { ...MOCK.BID_RESPONSE[0], auctionId }); - events.emit(BID_RESPONSE, { ...MOCK.BID_RESPONSE[1], auctionId }); - events.emit(BIDDER_DONE, { ...MOCK.BIDDER_DONE, auctionId }); - events.emit(AUCTION_END, { ...MOCK.AUCTION_END, auctionId }); - - if (gptEvents && gptEvents.length) { - gptEvents.forEach(gptEvent => mockGpt.emitEvent(gptEvent.eventName, gptEvent.params)); - } - - events.emit(SET_TARGETING, { ...MOCK.SET_TARGETING, auctionId }); - events.emit(BID_WON, { ...MOCK.BID_WON[0], auctionId }); - events.emit(BID_WON, { ...MOCK.BID_WON[1], auctionId }); -} - -describe('rubicon analytics adapter', function () { - let sandbox; - let clock; - let getDataFromLocalStorageStub, setDataInLocalStorageStub, localStorageIsEnabledStub; - beforeEach(function () { - getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage'); - setDataInLocalStorageStub = sinon.stub(storage, 'setDataInLocalStorage'); - localStorageIsEnabledStub = sinon.stub(storage, 'localStorageIsEnabled'); - mockGpt.disable(); - sandbox = sinon.sandbox.create(); - - localStorageIsEnabledStub.returns(true); - - sandbox.stub(events, 'getEvents').returns([]); - - sandbox.stub(utils, 'generateUUID').returns(STUBBED_UUID); - - clock = sandbox.useFakeTimers(1519767013781); - - rubiconAnalyticsAdapter.referrerHostname = ''; - - config.setConfig({ - s2sConfig: { - timeout: 1000, - accountId: 10000, - }, - rubicon: { - wrapperName: '10000_fakewrapper_test' - } - }) - }); - - afterEach(function () { - sandbox.restore(); - config.resetConfig(); - mockGpt.enable(); - getDataFromLocalStorageStub.restore(); - setDataInLocalStorageStub.restore(); - localStorageIsEnabledStub.restore(); - }); - - it('should require accountId', function () { - sandbox.stub(utils, 'logError'); - - rubiconAnalyticsAdapter.enableAnalytics({ - options: { - endpoint: '//localhost:9999/event' - } - }); - - expect(utils.logError.called).to.equal(true); - }); - - it('should require endpoint', function () { - sandbox.stub(utils, 'logError'); - - rubiconAnalyticsAdapter.enableAnalytics({ - options: { - accountId: 1001 - } - }); - - expect(utils.logError.called).to.equal(true); - }); - - describe('config subscribe', function () { - it('should update the pvid if user asks', function () { - expect(utils.generateUUID.called).to.equal(false); - config.setConfig({ rubicon: { updatePageView: true } }); - expect(utils.generateUUID.called).to.equal(true); - }); - it('should merge in and preserve older set configs', function () { - resetRubiConf(); - config.setConfig({ - rubicon: { - wrapperName: '1001_general', - int_type: 'dmpbjs', - fpkvs: { - source: 'fb' - } - } - }); - expect(rubiConf).to.deep.equal({ - analyticsEventDelay: 0, - dmBilling: { - enabled: false, - vendors: [], - waitForAuction: true - }, - pvid: '12345678', - wrapperName: '1001_general', - int_type: 'dmpbjs', - fpkvs: { - source: 'fb' - }, - }); - - // update it with stuff - config.setConfig({ - rubicon: { - fpkvs: { - link: 'email' - } - } - }); - expect(rubiConf).to.deep.equal({ - analyticsEventDelay: 0, - dmBilling: { - enabled: false, - vendors: [], - waitForAuction: true - }, - pvid: '12345678', - wrapperName: '1001_general', - int_type: 'dmpbjs', - fpkvs: { - source: 'fb', - link: 'email' - }, - }); - - // overwriting specific edge keys should update them - config.setConfig({ - rubicon: { - fpkvs: { - link: 'iMessage', - source: 'twitter' - } - } - }); - expect(rubiConf).to.deep.equal({ - analyticsEventDelay: 0, - dmBilling: { - enabled: false, - vendors: [], - waitForAuction: true - }, - pvid: '12345678', - wrapperName: '1001_general', - int_type: 'dmpbjs', - fpkvs: { - link: 'iMessage', - source: 'twitter' - }, - }); - }); - }); - - describe('sampling', function () { - beforeEach(function () { - sandbox.stub(Math, 'random').returns(0.08); - sandbox.stub(utils, 'logError'); - }); - - afterEach(function () { - rubiconAnalyticsAdapter.disableAnalytics(); - }); - - describe('with options.samplingFactor', function () { - it('should sample', function () { - rubiconAnalyticsAdapter.enableAnalytics({ - options: { - endpoint: '//localhost:9999/event', - accountId: 1001, - samplingFactor: 10 - } - }); - - performStandardAuction(); - - expect(server.requests.length).to.equal(1); - }); - - it('should unsample', function () { - rubiconAnalyticsAdapter.enableAnalytics({ - options: { - endpoint: '//localhost:9999/event', - accountId: 1001, - samplingFactor: 20 - } - }); - - performStandardAuction(); - - expect(server.requests.length).to.equal(0); - }); - - it('should throw errors for invalid samplingFactor', function () { - rubiconAnalyticsAdapter.enableAnalytics({ - options: { - endpoint: '//localhost:9999/event', - accountId: 1001, - samplingFactor: 30 - } - }); - - performStandardAuction(); - - expect(server.requests.length).to.equal(0); - expect(utils.logError.called).to.equal(true); - }); - }); - describe('with options.sampling', function () { - it('should sample', function () { - rubiconAnalyticsAdapter.enableAnalytics({ - options: { - endpoint: '//localhost:9999/event', - accountId: 1001, - sampling: 0.1 - } - }); - - performStandardAuction(); - - expect(server.requests.length).to.equal(1); - }); - - it('should unsample', function () { - rubiconAnalyticsAdapter.enableAnalytics({ - options: { - endpoint: '//localhost:9999/event', - accountId: 1001, - sampling: 0.05 - } - }); - - performStandardAuction(); - - expect(server.requests.length).to.equal(0); - }); - - it('should throw errors for invalid samplingFactor', function () { - rubiconAnalyticsAdapter.enableAnalytics({ - options: { - endpoint: '//localhost:9999/event', - accountId: 1001, - sampling: 1 / 30 - } - }); - - performStandardAuction(); - - expect(server.requests.length).to.equal(0); - expect(utils.logError.called).to.equal(true); - }); - }); - }); - - describe('when handling events', function () { - beforeEach(function () { - rubiconAnalyticsAdapter.enableAnalytics({ - options: { - endpoint: '//localhost:9999/event', - accountId: 1001 - } - }); - }); - - afterEach(function () { - rubiconAnalyticsAdapter.disableAnalytics(); - }); - - it('should build a batched message from prebid events', function () { - performStandardAuction(); - - expect(server.requests.length).to.equal(1); - let request = server.requests[0]; - - expect(request.url).to.equal('//localhost:9999/event'); - - let message = JSON.parse(request.requestBody); - validate(message); - - expect(message).to.deep.equal(ANALYTICS_MESSAGE); - }); - - it('should pass along bidderOrder correctly', function () { - const appnexusBid = utils.deepClone(MOCK.BID_REQUESTED); - appnexusBid.bidderCode = 'appnexus'; - const pubmaticBid = utils.deepClone(MOCK.BID_REQUESTED); - pubmaticBid.bidderCode = 'pubmatic'; - const indexBid = utils.deepClone(MOCK.BID_REQUESTED); - indexBid.bidderCode = 'ix'; - events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); - events.emit(BID_REQUESTED, pubmaticBid); - events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); - events.emit(BID_REQUESTED, indexBid); - events.emit(BID_REQUESTED, appnexusBid); - events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); - events.emit(AUCTION_END, MOCK.AUCTION_END); - events.emit(SET_TARGETING, MOCK.SET_TARGETING); - clock.tick(SEND_TIMEOUT + 1000); - - let message = JSON.parse(server.requests[0].requestBody); - expect(message.auctions[0].bidderOrder).to.deep.equal([ - 'pubmatic', - 'rubicon', - 'ix', - 'appnexus' - ]); - }); - - it('should pass along user ids', function () { - let auctionInit = utils.deepClone(MOCK.AUCTION_INIT); - auctionInit.bidderRequests[0].bids[0].userId = { - criteoId: 'sadfe4334', - lotamePanoramaId: 'asdf3gf4eg', - pubcid: 'dsfa4545-svgdfs5', - sharedId: { id1: 'asdf', id2: 'sadf4344' } - }; - - events.emit(AUCTION_INIT, auctionInit); - events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); - events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[0]); - events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[1]); - events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); - events.emit(AUCTION_END, MOCK.AUCTION_END); - - events.emit(SET_TARGETING, MOCK.SET_TARGETING); - events.emit(BID_WON, MOCK.BID_WON[0]); - events.emit(BID_WON, MOCK.BID_WON[1]); - - expect(server.requests.length).to.equal(1); - let request = server.requests[0]; - - let message = JSON.parse(request.requestBody); - validate(message); - - expect(message.auctions[0].user).to.deep.equal({ - ids: [ - { provider: 'criteoId', 'hasId': true }, - { provider: 'lotamePanoramaId', 'hasId': true }, - { provider: 'pubcid', 'hasId': true }, - { provider: 'sharedId', 'hasId': true }, - ] - }); - }); - - it('should handle bidResponse dimensions correctly', function () { - events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); - events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); - - // mock bid response with playerWidth and playerHeight (NO width and height) - let bidResponse1 = utils.deepClone(MOCK.BID_RESPONSE[0]); - delete bidResponse1.width; - delete bidResponse1.height; - bidResponse1.playerWidth = 640; - bidResponse1.playerHeight = 480; - - // mock bid response with no width height or playerwidth playerheight - let bidResponse2 = utils.deepClone(MOCK.BID_RESPONSE[1]); - delete bidResponse2.width; - delete bidResponse2.height; - delete bidResponse2.playerWidth; - delete bidResponse2.playerHeight; - - events.emit(BID_RESPONSE, bidResponse1); - events.emit(BID_RESPONSE, bidResponse2); - events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); - events.emit(AUCTION_END, MOCK.AUCTION_END); - events.emit(SET_TARGETING, MOCK.SET_TARGETING); - events.emit(BID_WON, MOCK.BID_WON[0]); - events.emit(BID_WON, MOCK.BID_WON[1]); - - let message = JSON.parse(server.requests[0].requestBody); - validate(message); - expect(message.auctions[0].adUnits[0].bids[0].bidResponse.dimensions).to.deep.equal({ - width: 640, - height: 480 - }); - expect(message.auctions[0].adUnits[1].bids[0].bidResponse.dimensions).to.equal(undefined); - }); - - it('should pass along adomians correctly', function () { - events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); - events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); - - // 1 adomains - let bidResponse1 = utils.deepClone(MOCK.BID_RESPONSE[0]); - bidResponse1.meta = { - advertiserDomains: ['magnite.com'] - } - - // two adomains - let bidResponse2 = utils.deepClone(MOCK.BID_RESPONSE[1]); - bidResponse2.meta = { - advertiserDomains: ['prebid.org', 'magnite.com'] - } - - // make sure we only pass max 10 adomains - bidResponse2.meta.advertiserDomains = [...bidResponse2.meta.advertiserDomains, ...bidResponse2.meta.advertiserDomains, ...bidResponse2.meta.advertiserDomains, ...bidResponse2.meta.advertiserDomains, ...bidResponse2.meta.advertiserDomains, ...bidResponse2.meta.advertiserDomains, ...bidResponse2.meta.advertiserDomains] - - events.emit(BID_RESPONSE, bidResponse1); - events.emit(BID_RESPONSE, bidResponse2); - events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); - events.emit(AUCTION_END, MOCK.AUCTION_END); - events.emit(SET_TARGETING, MOCK.SET_TARGETING); - events.emit(BID_WON, MOCK.BID_WON[0]); - events.emit(BID_WON, MOCK.BID_WON[1]); - - let message = JSON.parse(server.requests[0].requestBody); - validate(message); - expect(message.auctions[0].adUnits[0].bids[0].bidResponse.adomains).to.deep.equal(['magnite.com']); - expect(message.auctions[0].adUnits[1].bids[0].bidResponse.adomains).to.deep.equal(['prebid.org', 'magnite.com', 'prebid.org', 'magnite.com', 'prebid.org', 'magnite.com', 'prebid.org', 'magnite.com', 'prebid.org', 'magnite.com']); - }); - - it('should NOT pass along adomians correctly when edge cases', function () { - events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); - events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); - - // empty => nothing - let bidResponse1 = utils.deepClone(MOCK.BID_RESPONSE[0]); - bidResponse1.meta = { - advertiserDomains: [] - } - - // not array => nothing - let bidResponse2 = utils.deepClone(MOCK.BID_RESPONSE[1]); - bidResponse2.meta = { - advertiserDomains: 'prebid.org' - } - - events.emit(BID_RESPONSE, bidResponse1); - events.emit(BID_RESPONSE, bidResponse2); - events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); - events.emit(AUCTION_END, MOCK.AUCTION_END); - events.emit(SET_TARGETING, MOCK.SET_TARGETING); - events.emit(BID_WON, MOCK.BID_WON[0]); - events.emit(BID_WON, MOCK.BID_WON[1]); - - let message = JSON.parse(server.requests[0].requestBody); - validate(message); - expect(message.auctions[0].adUnits[0].bids[0].bidResponse.adomains).to.be.undefined; - expect(message.auctions[0].adUnits[1].bids[0].bidResponse.adomains).to.be.undefined; - }); - - it('should NOT pass along adomians with other edge cases', function () { - events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); - events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); - - // should filter out non string values and pass valid ones - let bidResponse1 = utils.deepClone(MOCK.BID_RESPONSE[0]); - bidResponse1.meta = { - advertiserDomains: [123, 'prebid.org', false, true, [], 'magnite.com', {}] - } - - // array of arrays (as seen when passed by kargo bid adapter) - let bidResponse2 = utils.deepClone(MOCK.BID_RESPONSE[1]); - bidResponse2.meta = { - advertiserDomains: [['prebid.org']] - } - - events.emit(BID_RESPONSE, bidResponse1); - events.emit(BID_RESPONSE, bidResponse2); - events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); - events.emit(AUCTION_END, MOCK.AUCTION_END); - events.emit(SET_TARGETING, MOCK.SET_TARGETING); - events.emit(BID_WON, MOCK.BID_WON[0]); - events.emit(BID_WON, MOCK.BID_WON[1]); - - let message = JSON.parse(server.requests[0].requestBody); - validate(message); - expect(message.auctions[0].adUnits[0].bids[0].bidResponse.adomains).to.deep.equal(['prebid.org', 'magnite.com']); - expect(message.auctions[0].adUnits[1].bids[0].bidResponse.adomains).to.be.undefined; - }); - - it('should not pass empty adServerTargeting values', function () { - events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); - events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); - events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[0]); - events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[1]); - events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); - events.emit(AUCTION_END, MOCK.AUCTION_END); - - const mockTargeting = utils.deepClone(MOCK.SET_TARGETING); - mockTargeting['/19968336/header-bid-tag-0'].hb_test = ''; - mockTargeting['/19968336/header-bid-tag1'].hb_test = 'NOT_EMPTY'; - events.emit(SET_TARGETING, mockTargeting); - - events.emit(BID_WON, MOCK.BID_WON[0]); - events.emit(BID_WON, MOCK.BID_WON[1]); - - let message = JSON.parse(server.requests[0].requestBody); - validate(message); - expect(message.auctions[0].adUnits[0].adserverTargeting.hb_test).to.be.undefined; - expect(message.auctions[0].adUnits[1].adserverTargeting.hb_test).to.equal('NOT_EMPTY'); - }); - - function performFloorAuction(provider) { - let auctionInit = utils.deepClone(MOCK.AUCTION_INIT); - auctionInit.bidderRequests[0].bids[0].floorData = { - skipped: false, - modelVersion: 'someModelName', - modelWeight: 10, - modelTimestamp: 1606772895, - location: 'setConfig', - skipRate: 15, - fetchStatus: 'error', - floorProvider: provider - }; - let flooredResponse = { - ...BID, - floorData: { - floorValue: 4, - floorRule: '12345/sports|video', - floorCurrency: 'USD', - cpmAfterAdjustments: 2.1, - enforcements: { - enforceJS: true, - enforcePBS: false, - floorDeals: false, - bidAdjustment: true - }, - matchedFields: { - gptSlot: '12345/sports', - mediaType: 'video' - } - }, - status: 'bidRejected', - cpm: 0, - getStatusCode() { - return 2; - } - }; - - let notFlooredResponse = { - ...BID2, - floorData: { - floorValue: 1, - floorRule: '12345/news|banner', - floorCurrency: 'USD', - cpmAfterAdjustments: 1.55, - enforcements: { - enforceJS: true, - enforcePBS: false, - floorDeals: false, - bidAdjustment: true - }, - matchedFields: { - gptSlot: '12345/news', - mediaType: 'banner' - } - } - }; - - let floorMinResponse = { - ...BID3, - floorData: { - floorValue: 1.5, - floorRuleValue: 1, - floorRule: '12345/entertainment|banner', - floorCurrency: 'USD', - cpmAfterAdjustments: 2.00, - enforcements: { - enforceJS: true, - enforcePBS: false, - floorDeals: false, - bidAdjustment: true - }, - matchedFields: { - gptSlot: '12345/entertainment', - mediaType: 'banner' - } - } - }; - - let bidRequest = utils.deepClone(MOCK.BID_REQUESTED); - bidRequest.bids.push(floorMinRequest) - - // spoof the auction with just our duplicates - events.emit(AUCTION_INIT, auctionInit); - events.emit(BID_REQUESTED, bidRequest); - events.emit(BID_RESPONSE, flooredResponse); - events.emit(BID_RESPONSE, notFlooredResponse); - events.emit(BID_RESPONSE, floorMinResponse); - events.emit(AUCTION_END, MOCK.AUCTION_END); - events.emit(SET_TARGETING, MOCK.SET_TARGETING); - events.emit(BID_WON, MOCK.BID_WON[1]); - events.emit(BID_WON, MOCK.BID_WON[2]); - clock.tick(SEND_TIMEOUT + 1000); - - expect(server.requests.length).to.equal(1); - - let message = JSON.parse(server.requests[0].requestBody); - validate(message); - return message; - } - - it('should capture price floor information correctly', function () { - let message = performFloorAuction('rubicon'); - - // verify our floor stuff is passed - // top level floor info - expect(message.auctions[0].floors).to.deep.equal({ - location: 'setConfig', - modelName: 'someModelName', - modelWeight: 10, - modelTimestamp: 1606772895, - skipped: false, - enforcement: true, - dealsEnforced: false, - skipRate: 15, - fetchStatus: 'error', - provider: 'rubicon' - }); - // first adUnit's adSlot - expect(message.auctions[0].adUnits[0].gam.adSlot).to.equal('12345/sports'); - // since no other bids, we set adUnit status to no-bid - expect(message.auctions[0].adUnits[0].status).to.equal('no-bid'); - // first adUnits bid is rejected - expect(message.auctions[0].adUnits[0].bids[0].status).to.equal('rejected-ipf'); - expect(message.auctions[0].adUnits[0].bids[0].bidResponse.floorValue).to.equal(4); - // if bid rejected should take cpmAfterAdjustments val - expect(message.auctions[0].adUnits[0].bids[0].bidResponse.bidPriceUSD).to.equal(2.1); - - // second adUnit's adSlot - expect(message.auctions[0].adUnits[1].gam.adSlot).to.equal('12345/news'); - // top level adUnit status is success - expect(message.auctions[0].adUnits[1].status).to.equal('success'); - // second adUnits bid is success - expect(message.auctions[0].adUnits[1].bids[0].status).to.equal('success'); - expect(message.auctions[0].adUnits[1].bids[0].bidResponse.floorValue).to.equal(1); - expect(message.auctions[0].adUnits[1].bids[0].bidResponse.bidPriceUSD).to.equal(1.52); - - // second adUnit's adSlot - expect(message.auctions[0].adUnits[2].gam.adSlot).to.equal('12345/entertainment'); - // top level adUnit status is success - expect(message.auctions[0].adUnits[2].status).to.equal('success'); - // second adUnits bid is success - expect(message.auctions[0].adUnits[2].bids[0].status).to.equal('success'); - expect(message.auctions[0].adUnits[2].bids[0].bidResponse.floorValue).to.equal(1.5); - expect(message.auctions[0].adUnits[2].bids[0].bidResponse.floorRuleValue).to.equal(1); - expect(message.auctions[0].adUnits[2].bids[0].bidResponse.bidPriceUSD).to.equal(2.01); - }); - - it('should still send floor info if provider is not rubicon', function () { - let message = performFloorAuction('randomProvider'); - - // verify our floor stuff is passed - // top level floor info - expect(message.auctions[0].floors).to.deep.equal({ - location: 'setConfig', - modelName: 'someModelName', - modelWeight: 10, - modelTimestamp: 1606772895, - skipped: false, - enforcement: true, - dealsEnforced: false, - skipRate: 15, - fetchStatus: 'error', - provider: 'randomProvider' - }); - // first adUnit's adSlot - expect(message.auctions[0].adUnits[0].gam.adSlot).to.equal('12345/sports'); - // since no other bids, we set adUnit status to no-bid - expect(message.auctions[0].adUnits[0].status).to.equal('no-bid'); - // first adUnits bid is rejected - expect(message.auctions[0].adUnits[0].bids[0].status).to.equal('rejected-ipf'); - expect(message.auctions[0].adUnits[0].bids[0].bidResponse.floorValue).to.equal(4); - // if bid rejected should take cpmAfterAdjustments val - expect(message.auctions[0].adUnits[0].bids[0].bidResponse.bidPriceUSD).to.equal(2.1); - - // second adUnit's adSlot - expect(message.auctions[0].adUnits[1].gam.adSlot).to.equal('12345/news'); - // top level adUnit status is success - expect(message.auctions[0].adUnits[1].status).to.equal('success'); - // second adUnits bid is success - expect(message.auctions[0].adUnits[1].bids[0].status).to.equal('success'); - expect(message.auctions[0].adUnits[1].bids[0].bidResponse.floorValue).to.equal(1); - expect(message.auctions[0].adUnits[1].bids[0].bidResponse.bidPriceUSD).to.equal(1.52); - - // second adUnit's adSlot - expect(message.auctions[0].adUnits[2].gam.adSlot).to.equal('12345/entertainment'); - // top level adUnit status is success - expect(message.auctions[0].adUnits[2].status).to.equal('success'); - // second adUnits bid is success - expect(message.auctions[0].adUnits[2].bids[0].status).to.equal('success'); - expect(message.auctions[0].adUnits[2].bids[0].bidResponse.floorValue).to.equal(1.5); - expect(message.auctions[0].adUnits[2].bids[0].bidResponse.floorRuleValue).to.equal(1); - expect(message.auctions[0].adUnits[2].bids[0].bidResponse.bidPriceUSD).to.equal(2.01); - }); - - describe('with session handling', function () { - const expectedPvid = STUBBED_UUID.slice(0, 8); - beforeEach(function () { - config.setConfig({ rubicon: { updatePageView: true } }); - }); - - it('should not log any session data if local storage is not enabled', function () { - localStorageIsEnabledStub.returns(false); - - let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); - delete expectedMessage.session; - delete expectedMessage.fpkvs; - - performStandardAuction(); - - expect(server.requests.length).to.equal(1); - let request = server.requests[0]; - - expect(request.url).to.equal('//localhost:9999/event'); - - let message = JSON.parse(request.requestBody); - validate(message); - - expect(message).to.deep.equal(expectedMessage); - }); - - it('should should pass along custom rubicon kv and pvid when defined', function () { - config.setConfig({ - rubicon: { - fpkvs: { - source: 'fb', - link: 'email' - } - } - }); - performStandardAuction(); - expect(server.requests.length).to.equal(1); - let request = server.requests[0]; - let message = JSON.parse(request.requestBody); - validate(message); - - let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); - expectedMessage.session.pvid = STUBBED_UUID.slice(0, 8); - expectedMessage.fpkvs = [ - { key: 'source', value: 'fb' }, - { key: 'link', value: 'email' } - ] - expect(message).to.deep.equal(expectedMessage); - }); - - it('should convert kvs to strings before sending', function () { - config.setConfig({ - rubicon: { - fpkvs: { - number: 24, - boolean: false, - string: 'hello', - array: ['one', 2, 'three'], - object: { one: 'two' } - } - } - }); - performStandardAuction(); - expect(server.requests.length).to.equal(1); - let request = server.requests[0]; - let message = JSON.parse(request.requestBody); - validate(message); - - let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); - expectedMessage.session.pvid = STUBBED_UUID.slice(0, 8); - expectedMessage.fpkvs = [ - { key: 'number', value: '24' }, - { key: 'boolean', value: 'false' }, - { key: 'string', value: 'hello' }, - { key: 'array', value: 'one,2,three' }, - { key: 'object', value: '[object Object]' } - ] - expect(message).to.deep.equal(expectedMessage); - }); - - it('should use the query utm param rubicon kv value and pass updated kv and pvid when defined', function () { - sandbox.stub(utils, 'getWindowLocation').returns({ 'search': '?utm_source=other', 'pbjs_debug': 'true' }); - - config.setConfig({ - rubicon: { - fpkvs: { - source: 'fb', - link: 'email' - } - } - }); - performStandardAuction(); - expect(server.requests.length).to.equal(1); - let request = server.requests[0]; - let message = JSON.parse(request.requestBody); - validate(message); - - let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); - expectedMessage.session.pvid = STUBBED_UUID.slice(0, 8); - expectedMessage.fpkvs = [ - { key: 'source', value: 'other' }, - { key: 'link', value: 'email' } - ] - - message.fpkvs.sort((left, right) => left.key < right.key); - expectedMessage.fpkvs.sort((left, right) => left.key < right.key); - - expect(message).to.deep.equal(expectedMessage); - }); - - it('should pick up existing localStorage and use its values', function () { - // set some localStorage - let inputlocalStorage = { - id: '987654', - start: 1519766113781, // 15 mins before "now" - expires: 1519787713781, // six hours later - lastSeen: 1519766113781, - fpkvs: { source: 'tw' } - }; - getDataFromLocalStorageStub.withArgs('rpaSession').returns(btoa(JSON.stringify(inputlocalStorage))); - - config.setConfig({ - rubicon: { - fpkvs: { - link: 'email' // should merge this with what is in the localStorage! - } - } - }); - performStandardAuction(); - expect(server.requests.length).to.equal(1); - let request = server.requests[0]; - let message = JSON.parse(request.requestBody); - validate(message); - - let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); - expectedMessage.session = { - id: '987654', - start: 1519766113781, - expires: 1519787713781, - pvid: expectedPvid - } - expectedMessage.fpkvs = [ - { key: 'source', value: 'tw' }, - { key: 'link', value: 'email' } - ] - expect(message).to.deep.equal(expectedMessage); - - let calledWith; - try { - calledWith = JSON.parse(atob(setDataInLocalStorageStub.getCall(0).args[1])); - } catch (e) { - calledWith = {}; - } - - expect(calledWith).to.deep.equal({ - id: '987654', // should have stayed same - start: 1519766113781, // should have stayed same - expires: 1519787713781, // should have stayed same - lastSeen: 1519767013781, // lastSeen updated to our "now" - fpkvs: { source: 'tw', link: 'email' }, // link merged in - pvid: expectedPvid // new pvid stored - }); - }); - - it('should overwrite matching localstorge value and use its remaining values', function () { - sandbox.stub(utils, 'getWindowLocation').returns({ 'search': '?utm_source=fb&utm_click=dog' }); - - // set some localStorage - let inputlocalStorage = { - id: '987654', - start: 1519766113781, // 15 mins before "now" - expires: 1519787713781, // six hours later - lastSeen: 1519766113781, - fpkvs: { source: 'tw', link: 'email' } - }; - getDataFromLocalStorageStub.withArgs('rpaSession').returns(btoa(JSON.stringify(inputlocalStorage))); - - config.setConfig({ - rubicon: { - fpkvs: { - link: 'email' // should merge this with what is in the localStorage! - } - } - }); - performStandardAuction(); - expect(server.requests.length).to.equal(1); - let request = server.requests[0]; - let message = JSON.parse(request.requestBody); - validate(message); - - let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); - expectedMessage.session = { - id: '987654', - start: 1519766113781, - expires: 1519787713781, - pvid: expectedPvid - } - expectedMessage.fpkvs = [ - { key: 'source', value: 'fb' }, - { key: 'link', value: 'email' }, - { key: 'click', value: 'dog' } - ] - - message.fpkvs.sort((left, right) => left.key < right.key); - expectedMessage.fpkvs.sort((left, right) => left.key < right.key); - - expect(message).to.deep.equal(expectedMessage); - - let calledWith; - try { - calledWith = JSON.parse(atob(setDataInLocalStorageStub.getCall(0).args[1])); - } catch (e) { - calledWith = {}; - } - - expect(calledWith).to.deep.equal({ - id: '987654', // should have stayed same - start: 1519766113781, // should have stayed same - expires: 1519787713781, // should have stayed same - lastSeen: 1519767013781, // lastSeen updated to our "now" - fpkvs: { source: 'fb', link: 'email', click: 'dog' }, // link merged in - pvid: expectedPvid // new pvid stored - }); - }); - - it('should throw out session if lastSeen > 30 mins ago and create new one', function () { - // set some localStorage - let inputlocalStorage = { - id: '987654', - start: 1519764313781, // 45 mins before "now" - expires: 1519785913781, // six hours later - lastSeen: 1519764313781, // 45 mins before "now" - fpkvs: { source: 'tw' } - }; - getDataFromLocalStorageStub.withArgs('rpaSession').returns(btoa(JSON.stringify(inputlocalStorage))); - - config.setConfig({ - rubicon: { - fpkvs: { - link: 'email' // should merge this with what is in the localStorage! - } - } - }); - - performStandardAuction(); - expect(server.requests.length).to.equal(1); - let request = server.requests[0]; - let message = JSON.parse(request.requestBody); - validate(message); - - let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); - // session should match what is already in ANALYTICS_MESSAGE, just need to add pvid - expectedMessage.session.pvid = expectedPvid; - - // the saved fpkvs should have been thrown out since session expired - expectedMessage.fpkvs = [ - { key: 'link', value: 'email' } - ] - expect(message).to.deep.equal(expectedMessage); - - let calledWith; - try { - calledWith = JSON.parse(atob(setDataInLocalStorageStub.getCall(0).args[1])); - } catch (e) { - calledWith = {}; - } - - expect(calledWith).to.deep.equal({ - id: STUBBED_UUID, // should have stayed same - start: 1519767013781, // should have stayed same - expires: 1519788613781, // should have stayed same - lastSeen: 1519767013781, // lastSeen updated to our "now" - fpkvs: { link: 'email' }, // link merged in - pvid: expectedPvid // new pvid stored - }); - }); - - it('should throw out session if past expires time and create new one', function () { - // set some localStorage - let inputlocalStorage = { - id: '987654', - start: 1519745353781, // 6 hours before "expires" - expires: 1519766953781, // little more than six hours ago - lastSeen: 1519767008781, // 5 seconds ago - fpkvs: { source: 'tw' } - }; - getDataFromLocalStorageStub.withArgs('rpaSession').returns(btoa(JSON.stringify(inputlocalStorage))); - - config.setConfig({ - rubicon: { - fpkvs: { - link: 'email' // should merge this with what is in the localStorage! - } - } - }); - - performStandardAuction(); - expect(server.requests.length).to.equal(1); - let request = server.requests[0]; - let message = JSON.parse(request.requestBody); - validate(message); - - let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); - // session should match what is already in ANALYTICS_MESSAGE, just need to add pvid - expectedMessage.session.pvid = expectedPvid; - - // the saved fpkvs should have been thrown out since session expired - expectedMessage.fpkvs = [ - { key: 'link', value: 'email' } - ] - expect(message).to.deep.equal(expectedMessage); - - let calledWith; - try { - calledWith = JSON.parse(atob(setDataInLocalStorageStub.getCall(0).args[1])); - } catch (e) { - calledWith = {}; - } - - expect(calledWith).to.deep.equal({ - id: STUBBED_UUID, // should have stayed same - start: 1519767013781, // should have stayed same - expires: 1519788613781, // should have stayed same - lastSeen: 1519767013781, // lastSeen updated to our "now" - fpkvs: { link: 'email' }, // link merged in - pvid: expectedPvid // new pvid stored - }); - }); - }); - describe('with googletag enabled', function () { - let gptSlot0, gptSlot1; - let gptSlotRenderEnded0, gptSlotRenderEnded1; - beforeEach(function () { - mockGpt.enable(); - gptSlot0 = mockGpt.makeSlot({ code: '/19968336/header-bid-tag-0' }); - gptSlotRenderEnded0 = { - eventName: 'slotRenderEnded', - params: { - slot: gptSlot0, - isEmpty: false, - advertiserId: 1111, - sourceAgnosticCreativeId: 2222, - sourceAgnosticLineItemId: 3333 - } - }; - - gptSlot1 = mockGpt.makeSlot({ code: '/19968336/header-bid-tag1' }); - gptSlotRenderEnded1 = { - eventName: 'slotRenderEnded', - params: { - slot: gptSlot1, - isEmpty: false, - advertiserId: 4444, - sourceAgnosticCreativeId: 5555, - sourceAgnosticLineItemId: 6666 - } - }; - }); - - afterEach(function () { - mockGpt.disable(); - }); - - it('should add necessary gam information if gpt is enabled and slotRender event emmited', function () { - performStandardAuction([gptSlotRenderEnded0, gptSlotRenderEnded1]); - expect(server.requests.length).to.equal(1); - let request = server.requests[0]; - let message = JSON.parse(request.requestBody); - validate(message); - - let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); - expectedMessage.auctions[0].adUnits[0].gam = { - advertiserId: 1111, - creativeId: 2222, - lineItemId: 3333, - adSlot: '/19968336/header-bid-tag-0' - }; - expectedMessage.auctions[0].adUnits[1].gam = { - advertiserId: 4444, - creativeId: 5555, - lineItemId: 6666, - adSlot: '/19968336/header-bid-tag1' - }; - expect(message).to.deep.equal(expectedMessage); - }); - - it('should handle empty gam renders', function () { - performStandardAuction([gptSlotRenderEnded0, { - eventName: 'slotRenderEnded', - params: { - slot: gptSlot1, - isEmpty: true - } - }]); - expect(server.requests.length).to.equal(1); - let request = server.requests[0]; - let message = JSON.parse(request.requestBody); - validate(message); - - let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); - expectedMessage.auctions[0].adUnits[0].gam = { - advertiserId: 1111, - creativeId: 2222, - lineItemId: 3333, - adSlot: '/19968336/header-bid-tag-0' - }; - expectedMessage.auctions[0].adUnits[1].gam = { - isSlotEmpty: true, - adSlot: '/19968336/header-bid-tag1' - }; - expect(message).to.deep.equal(expectedMessage); - }); - - it('should still add gam ids if falsy', function () { - performStandardAuction([gptSlotRenderEnded0, { - eventName: 'slotRenderEnded', - params: { - slot: gptSlot1, - isEmpty: false, - advertiserId: 0, - sourceAgnosticCreativeId: 0, - sourceAgnosticLineItemId: 0 - } - }]); - expect(server.requests.length).to.equal(1); - let request = server.requests[0]; - let message = JSON.parse(request.requestBody); - validate(message); - - let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); - expectedMessage.auctions[0].adUnits[0].gam = { - advertiserId: 1111, - creativeId: 2222, - lineItemId: 3333, - adSlot: '/19968336/header-bid-tag-0' - }; - expectedMessage.auctions[0].adUnits[1].gam = { - advertiserId: 0, - creativeId: 0, - lineItemId: 0, - adSlot: '/19968336/header-bid-tag1' - }; - expect(message).to.deep.equal(expectedMessage); - }); - - it('should pick backup Ids if no sourceAgnostic available first', function () { - performStandardAuction([gptSlotRenderEnded0, { - eventName: 'slotRenderEnded', - params: { - slot: gptSlot1, - isEmpty: false, - advertiserId: 0, - lineItemId: 1234, - creativeId: 5678 - } - }]); - expect(server.requests.length).to.equal(1); - let request = server.requests[0]; - let message = JSON.parse(request.requestBody); - validate(message); - - let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); - expectedMessage.auctions[0].adUnits[0].gam = { - advertiserId: 1111, - creativeId: 2222, - lineItemId: 3333, - adSlot: '/19968336/header-bid-tag-0' - }; - expectedMessage.auctions[0].adUnits[1].gam = { - advertiserId: 0, - creativeId: 5678, - lineItemId: 1234, - adSlot: '/19968336/header-bid-tag1' - }; - expect(message).to.deep.equal(expectedMessage); - }); - - it('should correctly set adUnit for associated slots', function () { - performStandardAuction([gptSlotRenderEnded0, gptSlotRenderEnded1]); - expect(server.requests.length).to.equal(1); - let request = server.requests[0]; - let message = JSON.parse(request.requestBody); - validate(message); - - let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); - expectedMessage.auctions[0].adUnits[0].gam = { - advertiserId: 1111, - creativeId: 2222, - lineItemId: 3333, - adSlot: '/19968336/header-bid-tag-0' - }; - expectedMessage.auctions[0].adUnits[1].gam = { - advertiserId: 4444, - creativeId: 5555, - lineItemId: 6666, - adSlot: '/19968336/header-bid-tag1' - }; - expect(message).to.deep.equal(expectedMessage); - }); - - it('should only mark the first gam data not all matches', function () { - config.setConfig({ - rubicon: { - waitForGamSlots: true - } - }); - performStandardAuction(); - performStandardAuction([gptSlotRenderEnded0, gptSlotRenderEnded1], '32d332de-123a-32dg-2345-cefef3423324'); - - // tick the clock and both should fire - clock.tick(3000); - - expect(server.requests.length).to.equal(2); - - // first one should have GAM data - let request = server.requests[0]; - let message = JSON.parse(request.requestBody); - - // trigger should be gam since all adunits had associated gam render - expect(message.trigger).to.be.equal('gam'); - expect(message.auctions[0].adUnits[0].gam).to.deep.equal({ - advertiserId: 1111, - creativeId: 2222, - lineItemId: 3333, - adSlot: '/19968336/header-bid-tag-0' - }); - expect(message.auctions[0].adUnits[1].gam).to.deep.equal({ - advertiserId: 4444, - creativeId: 5555, - lineItemId: 6666, - adSlot: '/19968336/header-bid-tag1' - }); - - // second one should NOT have gam data - request = server.requests[1]; - message = JSON.parse(request.requestBody); - validate(message); - - // trigger should be auctionEnd - expect(message.trigger).to.be.equal('auctionEnd'); - expect(message.auctions[0].adUnits[0].gam).to.be.undefined; - expect(message.auctions[0].adUnits[1].gam).to.be.undefined; - }); - - it('should send request when waitForGamSlots is present but no bidWons are sent', function () { - config.setConfig({ - rubicon: { - waitForGamSlots: true, - } - }); - events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); - events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); - events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[0]); - events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[1]); - events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); - events.emit(AUCTION_END, MOCK.AUCTION_END); - events.emit(SET_TARGETING, MOCK.SET_TARGETING); - - // should send if just slotRenderEnded is emmitted for both - mockGpt.emitEvent(gptSlotRenderEnded0.eventName, gptSlotRenderEnded0.params); - mockGpt.emitEvent(gptSlotRenderEnded1.eventName, gptSlotRenderEnded1.params); - - expect(server.requests.length).to.equal(1); - let request = server.requests[0]; - let message = JSON.parse(request.requestBody); - validate(message); - - let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); - delete expectedMessage.bidsWon; // should not be any of these - expectedMessage.auctions[0].adUnits[0].gam = { - advertiserId: 1111, - creativeId: 2222, - lineItemId: 3333, - adSlot: '/19968336/header-bid-tag-0' - }; - expectedMessage.auctions[0].adUnits[1].gam = { - advertiserId: 4444, - creativeId: 5555, - lineItemId: 6666, - adSlot: '/19968336/header-bid-tag1' - }; - expectedMessage.trigger = 'gam'; - expect(message).to.deep.equal(expectedMessage); - }); - - it('should delay the event call depending on analyticsEventDelay config', function () { - config.setConfig({ - rubicon: { - waitForGamSlots: true, - analyticsEventDelay: 2000 - } - }); - events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); - events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); - events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[0]); - events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[1]); - events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); - events.emit(AUCTION_END, MOCK.AUCTION_END); - events.emit(SET_TARGETING, MOCK.SET_TARGETING); - - // should send if just slotRenderEnded is emmitted for both - mockGpt.emitEvent(gptSlotRenderEnded0.eventName, gptSlotRenderEnded0.params); - mockGpt.emitEvent(gptSlotRenderEnded1.eventName, gptSlotRenderEnded1.params); - - // Should not be sent until delay - expect(server.requests.length).to.equal(0); - - // tick the clock and it should fire - clock.tick(2000); - - expect(server.requests.length).to.equal(1); - let request = server.requests[0]; - let message = JSON.parse(request.requestBody); - validate(message); - let expectedGam0 = { - advertiserId: 1111, - creativeId: 2222, - lineItemId: 3333, - adSlot: '/19968336/header-bid-tag-0' - }; - let expectedGam1 = { - advertiserId: 4444, - creativeId: 5555, - lineItemId: 6666, - adSlot: '/19968336/header-bid-tag1' - }; - expect(expectedGam0).to.deep.equal(message.auctions[0].adUnits[0].gam); - expect(expectedGam1).to.deep.equal(message.auctions[0].adUnits[1].gam); - }); - }); - - it('should correctly overwrite bidId if seatBidId is on the bidResponse', function () { - // Only want one bid request in our mock auction - let bidRequested = utils.deepClone(MOCK.BID_REQUESTED); - bidRequested.bids.shift(); - let auctionInit = utils.deepClone(MOCK.AUCTION_INIT); - auctionInit.adUnits.shift(); - - // clone the mock bidResponse and duplicate - let seatBidResponse = utils.deepClone(BID2); - seatBidResponse.seatBidId = 'abc-123-do-re-me'; - - const setTargeting = { - [seatBidResponse.adUnitCode]: seatBidResponse.adserverTargeting - }; - - const bidWon = Object.assign({}, seatBidResponse, { - 'status': 'rendered' - }); - - // spoof the auction with just our duplicates - events.emit(AUCTION_INIT, auctionInit); - events.emit(BID_REQUESTED, bidRequested); - events.emit(BID_RESPONSE, seatBidResponse); - events.emit(AUCTION_END, MOCK.AUCTION_END); - events.emit(SET_TARGETING, setTargeting); - events.emit(BID_WON, bidWon); - - let message = JSON.parse(server.requests[0].requestBody); - - validate(message); - expect(message.auctions[0].adUnits[0].bids[0].bidId).to.equal('abc-123-do-re-me'); - expect(message.bidsWon[0].bidId).to.equal('abc-123-do-re-me'); - }); - - it('should correctly overwrite bidId if pbsBidId is on the bidResponse', function () { - // Only want one bid request in our mock auction - let bidRequested = utils.deepClone(MOCK.BID_REQUESTED); - bidRequested.bids.shift(); - let auctionInit = utils.deepClone(MOCK.AUCTION_INIT); - auctionInit.adUnits.shift(); - - // clone the mock bidResponse and duplicate - let seatBidResponse = utils.deepClone(BID4); - - const setTargeting = { - [seatBidResponse.adUnitCode]: seatBidResponse.adserverTargeting - }; - - const bidWon = Object.assign({}, seatBidResponse, { - 'status': 'rendered' - }); - - // spoof the auction with just our duplicates - events.emit(AUCTION_INIT, auctionInit); - events.emit(BID_REQUESTED, bidRequested); - events.emit(BID_RESPONSE, seatBidResponse); - events.emit(AUCTION_END, MOCK.AUCTION_END); - events.emit(SET_TARGETING, setTargeting); - events.emit(BID_WON, bidWon); - - let message = JSON.parse(server.requests[0].requestBody); - - validate(message); - expect(message.auctions[0].adUnits[0].bids[0].bidId).to.equal('zzzz-yyyy-xxxx-wwww'); - expect(message.bidsWon[0].bidId).to.equal('zzzz-yyyy-xxxx-wwww'); - }); - - it('should correctly generate new bidId if it is 0', function () { - // Only want one bid request in our mock auction - let bidRequested = utils.deepClone(MOCK.BID_REQUESTED); - bidRequested.bids.shift(); - let auctionInit = utils.deepClone(MOCK.AUCTION_INIT); - auctionInit.adUnits.shift(); - - // clone the mock bidResponse and duplicate - let seatBidResponse = utils.deepClone(BID4); - seatBidResponse.pbsBidId = '0'; - - const setTargeting = { - [seatBidResponse.adUnitCode]: seatBidResponse.adserverTargeting - }; - - const bidWon = Object.assign({}, seatBidResponse, { - 'status': 'rendered' - }); - - // spoof the auction with just our duplicates - events.emit(AUCTION_INIT, auctionInit); - events.emit(BID_REQUESTED, bidRequested); - events.emit(BID_RESPONSE, seatBidResponse); - events.emit(AUCTION_END, MOCK.AUCTION_END); - events.emit(SET_TARGETING, setTargeting); - events.emit(BID_WON, bidWon); - - let message = JSON.parse(server.requests[0].requestBody); - - validate(message); - expect(message.auctions[0].adUnits[0].bids[0].bidId).to.equal(STUBBED_UUID); - expect(message.bidsWon[0].bidId).to.equal(STUBBED_UUID); - }); - - it('should pick the highest cpm bid if more than one bid per bidRequestId', function () { - // Only want one bid request in our mock auction - let bidRequested = utils.deepClone(MOCK.BID_REQUESTED); - bidRequested.bids.shift(); - let auctionInit = utils.deepClone(MOCK.AUCTION_INIT); - auctionInit.adUnits.shift(); - - // clone the mock bidResponse and duplicate - let duplicateResponse1 = utils.deepClone(BID2); - duplicateResponse1.cpm = 1.0; - duplicateResponse1.adserverTargeting.hb_pb = '1.0'; - duplicateResponse1.adserverTargeting.hb_adid = '1111'; - let duplicateResponse2 = utils.deepClone(BID2); - duplicateResponse2.cpm = 5.5; - duplicateResponse2.adserverTargeting.hb_pb = '5.5'; - duplicateResponse2.adserverTargeting.hb_adid = '5555'; - let duplicateResponse3 = utils.deepClone(BID2); - duplicateResponse3.cpm = 0.1; - duplicateResponse3.adserverTargeting.hb_pb = '0.1'; - duplicateResponse3.adserverTargeting.hb_adid = '3333'; - - const setTargeting = { - [duplicateResponse2.adUnitCode]: duplicateResponse2.adserverTargeting - }; - - const bidWon = Object.assign({}, duplicateResponse2, { - 'status': 'rendered' - }); - - // spoof the auction with just our duplicates - events.emit(AUCTION_INIT, auctionInit); - events.emit(BID_REQUESTED, bidRequested); - events.emit(BID_RESPONSE, duplicateResponse1); - events.emit(BID_RESPONSE, duplicateResponse2); - events.emit(BID_RESPONSE, duplicateResponse3); - events.emit(AUCTION_END, MOCK.AUCTION_END); - events.emit(SET_TARGETING, setTargeting); - events.emit(BID_WON, bidWon); - - let message = JSON.parse(server.requests[0].requestBody); - validate(message); - expect(message.auctions[0].adUnits[0].bids[0].bidResponse.bidPriceUSD).to.equal(5.5); - expect(message.auctions[0].adUnits[0].adserverTargeting.hb_pb).to.equal('5.5'); - expect(message.auctions[0].adUnits[0].adserverTargeting.hb_adid).to.equal('5555'); - expect(message.bidsWon.length).to.equal(1); - expect(message.bidsWon[0].bidResponse.bidPriceUSD).to.equal(5.5); - expect(message.bidsWon[0].adserverTargeting.hb_pb).to.equal('5.5'); - expect(message.bidsWon[0].adserverTargeting.hb_adid).to.equal('5555'); - }); - - it('should send batched message without BID_WON if necessary and further BID_WON events individually', function () { - events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); - events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); - events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[0]); - events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[1]); - events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); - events.emit(AUCTION_END, MOCK.AUCTION_END); - events.emit(SET_TARGETING, MOCK.SET_TARGETING); - events.emit(BID_WON, MOCK.BID_WON[0]); - - clock.tick(SEND_TIMEOUT + 1000); - - events.emit(BID_WON, MOCK.BID_WON[1]); - - expect(server.requests.length).to.equal(2); - - let message = JSON.parse(server.requests[0].requestBody); - validate(message); - expect(message.bidsWon.length).to.equal(1); - expect(message.auctions).to.deep.equal(ANALYTICS_MESSAGE.auctions); - expect(message.bidsWon[0]).to.deep.equal(ANALYTICS_MESSAGE.bidsWon[0]); - - message = JSON.parse(server.requests[1].requestBody); - validate(message); - expect(message.bidsWon.length).to.equal(1); - expect(message).to.not.have.property('auctions'); - expect(message.bidsWon[0]).to.deep.equal(ANALYTICS_MESSAGE.bidsWon[1]); - }); - - it('should properly mark bids as timed out', function () { - events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); - events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); - events.emit(BID_TIMEOUT, MOCK.BID_TIMEOUT); - events.emit(AUCTION_END, MOCK.AUCTION_END); - - clock.tick(SEND_TIMEOUT + 1000); - - expect(server.requests.length).to.equal(1); - - let message = JSON.parse(server.requests[0].requestBody); - validate(message); - let timedOutBid = message.auctions[0].adUnits[0].bids[0]; - expect(timedOutBid.status).to.equal('error'); - expect(timedOutBid.error.code).to.equal('timeout-error'); - expect(timedOutBid.error.description).to.equal('prebid.js timeout'); - expect(timedOutBid).to.not.have.property('bidResponse'); - }); - - it('should pass aupName as pattern', function () { - let bidRequest = utils.deepClone(MOCK.BID_REQUESTED); - bidRequest.bids[0].ortb2Imp = { - ext: { - data: { - aupname: '1234/mycoolsite/*&gpt_leaderboard&deviceType=mobile' - } - } - }; - bidRequest.bids[1].ortb2Imp = { - ext: { - data: { - aupname: '1234/mycoolsite/*&gpt_skyscraper&deviceType=mobile' - } - } - }; - events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); - events.emit(BID_REQUESTED, bidRequest); - events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[0]); - events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); - events.emit(AUCTION_END, MOCK.AUCTION_END); - events.emit(SET_TARGETING, MOCK.SET_TARGETING); - - clock.tick(SEND_TIMEOUT + 1000); - - expect(server.requests.length).to.equal(1); - - let message = JSON.parse(server.requests[0].requestBody); - validate(message); - expect(message.auctions[0].adUnits[0].pattern).to.equal('1234/mycoolsite/*&gpt_leaderboard&deviceType=mobile'); - expect(message.auctions[0].adUnits[1].pattern).to.equal('1234/mycoolsite/*&gpt_skyscraper&deviceType=mobile'); - }); - - it('should pass gpid if defined', function () { - let bidRequest = utils.deepClone(MOCK.BID_REQUESTED); - bidRequest.bids[0].ortb2Imp = { - ext: { - gpid: '1234/mycoolsite/lowerbox' - } - }; - bidRequest.bids[1].ortb2Imp = { - ext: { - gpid: '1234/mycoolsite/leaderboard' - } - }; - events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); - events.emit(BID_REQUESTED, bidRequest); - events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[0]); - events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); - events.emit(AUCTION_END, MOCK.AUCTION_END); - events.emit(SET_TARGETING, MOCK.SET_TARGETING); - - clock.tick(SEND_TIMEOUT + 1000); - - expect(server.requests.length).to.equal(1); - - let message = JSON.parse(server.requests[0].requestBody); - validate(message); - expect(message.auctions[0].adUnits[0].gpid).to.equal('1234/mycoolsite/lowerbox'); - expect(message.auctions[0].adUnits[1].gpid).to.equal('1234/mycoolsite/leaderboard'); - }); - - it('should pass bidderDetail for multibid auctions', function () { - let bidResponse = utils.deepClone(MOCK.BID_RESPONSE[1]); - bidResponse.targetingBidder = 'rubi2'; - bidResponse.originalRequestId = bidResponse.requestId; - bidResponse.requestId = '1a2b3c4d5e6f7g8h9'; - - events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); - events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); - events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[0]); - events.emit(BID_RESPONSE, bidResponse); - events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); - events.emit(AUCTION_END, MOCK.AUCTION_END); - events.emit(SET_TARGETING, MOCK.SET_TARGETING); - events.emit(BID_WON, MOCK.BID_WON[0]); - - clock.tick(SEND_TIMEOUT + 1000); - - expect(server.requests.length).to.equal(1); - - let message = JSON.parse(server.requests[0].requestBody); - validate(message); - - expect(message.auctions[0].adUnits[1].bids[1].bidder).to.equal('rubicon'); - expect(message.auctions[0].adUnits[1].bids[1].bidderDetail).to.equal('rubi2'); - }); - - it('should successfully convert bid price to USD in parseBidResponse', function () { - // Set the rates - setConfig({ - adServerCurrency: 'JPY', - rates: { - USD: { - JPY: 100 - } - } - }); - - // set our bid response to JPY - const bidCopy = utils.deepClone(BID2); - bidCopy.currency = 'JPY'; - bidCopy.cpm = 100; - - // Now add the bidResponse hook which hooks on the currenct conversion function onto the bid response - let innerBid; - addBidResponseHook(function (adCodeId, bid) { - innerBid = bid; - }, 'elementId', bidCopy); - - // Use the rubi analytics parseBidResponse Function to get the resulting cpm from the bid response! - const bidResponseObj = parseBidResponse(innerBid); - expect(bidResponseObj).to.have.property('bidPriceUSD'); - expect(bidResponseObj.bidPriceUSD).to.equal(1.0); - }); - }); - - describe('config with integration type', () => { - it('should use the integration type provided in the config instead of the default', () => { - config.setConfig({ - rubicon: { - int_type: 'testType' - } - }) - - rubiconAnalyticsAdapter.enableAnalytics({ - options: { - endpoint: '//localhost:9999/event', - accountId: 1001 - } - }); - - performStandardAuction(); - - expect(server.requests.length).to.equal(1); - const request = server.requests[0]; - const message = JSON.parse(request.requestBody); - expect(message.integration).to.equal('testType'); - - rubiconAnalyticsAdapter.disableAnalytics(); - }); - }); - - describe('billing events integration', () => { - beforeEach(function () { - rubiconAnalyticsAdapter.enableAnalytics({ - options: { - endpoint: '//localhost:9999/event', - accountId: 1001 - } - }); - // default dmBilling - config.setConfig({ - rubicon: { - dmBilling: { - enabled: false, - vendors: [], - waitForAuction: true - } - } - }) - }); - afterEach(function () { - rubiconAnalyticsAdapter.disableAnalytics(); - }); - const basicBillingAuction = (billingEvents = []) => { - events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); - events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); - - // emit billing events - billingEvents.forEach(ev => events.emit(BILLABLE_EVENT, ev)); - events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[0]); - events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[1]); - events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); - events.emit(AUCTION_END, MOCK.AUCTION_END); - events.emit(SET_TARGETING, MOCK.SET_TARGETING); - events.emit(BID_WON, MOCK.BID_WON[0]); - events.emit(BID_WON, MOCK.BID_WON[1]); - } - it('should ignore billing events when not enabled', () => { - basicBillingAuction([{ - vendor: 'vendorName', - type: 'auction', - billingId: 'f8558d41-62de-4349-bc7b-2dbee1e69965' - }]); - expect(server.requests.length).to.equal(1); - const request = server.requests[0]; - const message = JSON.parse(request.requestBody); - expect(message.billableEvents).to.be.undefined; - }); - it('should ignore billing events when enabled but vendor is not whitelisted', () => { - // off by default - config.setConfig({ - rubicon: { - dmBilling: { - enabled: true - } - } - }); - basicBillingAuction([{ - vendor: 'vendorName', - type: 'auction', - billingId: 'f8558d41-62de-4349-bc7b-2dbee1e69965' - }]); - expect(server.requests.length).to.equal(1); - const request = server.requests[0]; - const message = JSON.parse(request.requestBody); - expect(message.billableEvents).to.be.undefined; - }); - it('should ignore billing events if billingId is not defined or billingId is not a string', () => { - // off by default - config.setConfig({ - rubicon: { - dmBilling: { - enabled: true, - vendors: ['vendorName'] - } - } - }); - basicBillingAuction([ - { - vendor: 'vendorName', - type: 'auction', - }, - { - vendor: 'vendorName', - type: 'auction', - billingId: true - }, - { - vendor: 'vendorName', - type: 'auction', - billingId: 1233434 - }, - { - vendor: 'vendorName', - type: 'auction', - billingId: null - } - ]); - expect(server.requests.length).to.equal(1); - const request = server.requests[0]; - const message = JSON.parse(request.requestBody); - expect(message.billableEvents).to.be.undefined; - }); - it('should pass along billing event in same payload', () => { - // off by default - config.setConfig({ - rubicon: { - dmBilling: { - enabled: true, - vendors: ['vendorName'] - } - } - }); - basicBillingAuction([{ - vendor: 'vendorName', - type: 'pageView', - billingId: 'f8558d41-62de-4349-bc7b-2dbee1e69965' - }]); - expect(server.requests.length).to.equal(1); - const request = server.requests[0]; - const message = JSON.parse(request.requestBody); - expect(message).to.haveOwnProperty('auctions'); - expect(message.billableEvents).to.deep.equal([{ - accountId: 1001, - vendor: 'vendorName', - type: 'pageView', - billingId: 'f8558d41-62de-4349-bc7b-2dbee1e69965' - }]); - }); - it('should pass along multiple billing events but filter out duplicates', () => { - // off by default - config.setConfig({ - rubicon: { - dmBilling: { - enabled: true, - vendors: ['vendorName'] - } - } - }); - basicBillingAuction([ - { - vendor: 'vendorName', - type: 'auction', - billingId: 'f8558d41-62de-4349-bc7b-2dbee1e69965' - }, - { - vendor: 'vendorName', - type: 'impression', - billingId: '743db6e3-21f2-44d4-917f-cb3488c6076f' - }, - { - vendor: 'vendorName', - type: 'auction', - billingId: 'f8558d41-62de-4349-bc7b-2dbee1e69965' - } - ]); - expect(server.requests.length).to.equal(1); - const request = server.requests[0]; - const message = JSON.parse(request.requestBody); - expect(message).to.haveOwnProperty('auctions'); - expect(message.billableEvents).to.deep.equal([ - { - accountId: 1001, - vendor: 'vendorName', - type: 'auction', - billingId: 'f8558d41-62de-4349-bc7b-2dbee1e69965' - }, - { - accountId: 1001, - vendor: 'vendorName', - type: 'impression', - billingId: '743db6e3-21f2-44d4-917f-cb3488c6076f' - } - ]); - }); - it('should pass along event right away if no pending auction', () => { - // off by default - config.setConfig({ - rubicon: { - dmBilling: { - enabled: true, - vendors: ['vendorName'] - } - } - }); - - events.emit(BILLABLE_EVENT, { - vendor: 'vendorName', - type: 'auction', - billingId: 'f8558d41-62de-4349-bc7b-2dbee1e69965' - }); - expect(server.requests.length).to.equal(1); - const request = server.requests[0]; - const message = JSON.parse(request.requestBody); - expect(message).to.not.haveOwnProperty('auctions'); - expect(message.billableEvents).to.deep.equal([ - { - accountId: 1001, - vendor: 'vendorName', - type: 'auction', - billingId: 'f8558d41-62de-4349-bc7b-2dbee1e69965' - } - ]); - }); - it('should pass along event right away if pending auction but not waiting', () => { - // off by default - config.setConfig({ - rubicon: { - dmBilling: { - enabled: true, - vendors: ['vendorName'], - waitForAuction: false - } - } - }); - // should fire right away, and then auction later - basicBillingAuction([{ - vendor: 'vendorName', - type: 'auction', - billingId: 'f8558d41-62de-4349-bc7b-2dbee1e69965' - }]); - expect(server.requests.length).to.equal(2); - const billingRequest = server.requests[0]; - const billingMessage = JSON.parse(billingRequest.requestBody); - expect(billingMessage).to.not.haveOwnProperty('auctions'); - expect(billingMessage.billableEvents).to.deep.equal([ - { - accountId: 1001, - vendor: 'vendorName', - type: 'auction', - billingId: 'f8558d41-62de-4349-bc7b-2dbee1e69965' - } - ]); - // auction event after - const auctionRequest = server.requests[1]; - const auctionMessage = JSON.parse(auctionRequest.requestBody); - // should not double pass events! - expect(auctionMessage).to.not.haveOwnProperty('billableEvents'); - }); - }); - - describe('wrapper details passed in', () => { - it('should correctly pass in the wrapper details if provided', () => { - config.setConfig({ - rubicon: { - wrapperName: '1001_wrapperName_exp.4', - wrapperFamily: '1001_wrapperName', - rule_name: 'na-mobile' - } - }); - - rubiconAnalyticsAdapter.enableAnalytics({ - options: { - endpoint: '//localhost:9999/event', - accountId: 1001 - } - }); - - performStandardAuction(); - - expect(server.requests.length).to.equal(1); - const request = server.requests[0]; - const message = JSON.parse(request.requestBody); - expect(message.wrapper).to.deep.equal({ - name: '1001_wrapperName_exp.4', - family: '1001_wrapperName', - rule: 'na-mobile' - }); - - rubiconAnalyticsAdapter.disableAnalytics(); - }); - }); - - it('getHostNameFromReferer correctly grabs hostname from an input URL', function () { - let inputUrl = 'https://www.prebid.org/some/path?pbjs_debug=true'; - expect(getHostNameFromReferer(inputUrl)).to.equal('www.prebid.org'); - inputUrl = 'https://www.prebid.com/some/path?pbjs_debug=true'; - expect(getHostNameFromReferer(inputUrl)).to.equal('www.prebid.com'); - inputUrl = 'https://prebid.org/some/path?pbjs_debug=true'; - expect(getHostNameFromReferer(inputUrl)).to.equal('prebid.org'); - inputUrl = 'http://xn--p8j9a0d9c9a.xn--q9jyb4c/'; - expect(typeof getHostNameFromReferer(inputUrl)).to.equal('string'); - - // not non-UTF char's in query / path which break if noDecodeWholeURL not set - inputUrl = 'https://prebid.org/search_results/%95x%8Em%92%CA/?category=000'; - expect(getHostNameFromReferer(inputUrl)).to.equal('prebid.org'); - }); -}); diff --git a/test/spec/modules/rubiconAnalyticsSchema.json b/test/spec/modules/rubiconAnalyticsSchema.json deleted file mode 100644 index 2d0dca42d23..00000000000 --- a/test/spec/modules/rubiconAnalyticsSchema.json +++ /dev/null @@ -1,494 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Prebid Auctions", - "description": "A batched data object describing the lifecycle of an auction or multiple auction across a single page view.", - "type": "object", - "required": [ - "integration", - "version" - ], - "anyOf": [ - { - "required": [ - "auctions" - ] - }, - { - "required": [ - "bidsWon" - ] - }, - { - "required": [ - "billableEvents" - ] - } - ], - "properties": { - "integration": { - "type": "string", - "description": "Integration type that generated this event.", - "default": "pbjs" - }, - "version": { - "type": "string", - "description": "Version of Prebid.js responsible for the auctions contained within." - }, - "fpkvs": { - "type": "array", - "description": "List of any dynamic key value pairs set by publisher.", - "minItems": 1, - "items": { - "type": "object", - "required": [ - "key", - "value" - ], - "properties": { - "key": { - "type": "string" - }, - "value": { - "type": "string" - } - } - } - }, - "session": { - "type": "object", - "description": "The session information for a given event", - "required": [ - "id", - "start", - "expires" - ], - "properties": { - "id": { - "type": "string", - "description": "UUID of session." - }, - "start": { - "type": "integer", - "description": "Unix timestamp of time of creation for this session in milliseconds." - }, - "expires": { - "type": "integer", - "description": "Unix timestamp of the maximum allowed time in milliseconds of the session." - }, - "pvid": { - "type": "string", - "description": "id to track page view." - } - } - }, - "auctions": { - "type": "array", - "minItems": 1, - "items": { - "type": "object", - "required": [ - "adUnits", - "samplingFactor" - ], - "properties": { - "clientTimeoutMillis": { - "type": "integer", - "description": "Timeout given in client for given auction in milliseconds (if applicable)." - }, - "serverTimeoutMillis": { - "type": "integer", - "description": "Timeout configured for server adapter request in milliseconds (if applicable)." - }, - "accountId": { - "type": "number", - "description": "The account id for prebid server (if applicable)." - }, - "samplingFactor": { - "$ref": "#/definitions/samplingFactor" - }, - "adUnits": { - "type": "array", - "minItems": 1, - "items": { - "type": "object", - "description": "An array of adUnits involved in this auction.", - "required": [ - "status", - "adUnitCode", - "transactionId", - "mediaTypes", - "dimensions", - "bids" - ], - "properties": { - "status": { - "type": "string", - "description": "The status of the adUnit" - }, - "adUnitCode": { - "type": "string", - "description": "The adUnit.code identifier" - }, - "transactionId": { - "type": "string", - "description": "The UUID generated id to represent this adunit in this auction." - }, - "adSlot": { - "type": "string" - }, - "mediaTypes": { - "$ref": "#/definitions/mediaTypes" - }, - "videoAdFormat": { - "$ref": "#/definitions/videoAdFormat" - }, - "dimensions": { - "type": "array", - "description": "All valid sizes included in this auction (note: may be sizeConfig filtered).", - "minItems": 1, - "items": { - "$ref": "#/definitions/dimensions" - } - }, - "adserverTargeting": { - "$ref": "#/definitions/adserverTargeting" - }, - "bids": { - "type": "array", - "description": "An array that contains a combination of the bids from the adUnit combined with their responses.", - "minItems": 1, - "items": { - "$ref": "#/definitions/bid" - } - }, - "accountId": { - "type": "number", - "description": "The Rubicon AccountId associated with this adUnit - Removed if null" - }, - "siteId": { - "type": "number", - "description": "The Rubicon siteId associated with this adUnit - Removed if null" - }, - "zoneId": { - "type": "number", - "description": "The Rubicon zoneId associated with this adUnit - Removed if null" - }, - "gam": { - "$ref": "#/definitions/gam" - } - } - } - } - } - } - }, - "bidsWon": { - "type": "array", - "minItems": 1, - "items": { - "allOf": [ - { - "$ref": "#/definitions/bid" - }, - { - "required": [ - "transactionId", - "accountId", - "samplingFactor", - "mediaTypes", - "adUnitCode", - "bidwonStatus" - ], - "properties": { - "transactionId": { - "type": "string" - }, - "accountId": { - "type": "number" - }, - "samplingFactor": { - "$ref": "#/definitions/samplingFactor" - }, - "adUnitCode": { - "type": "string" - }, - "videoAdFormat": { - "$ref": "#/definitions/videoAdFormat" - }, - "mediaTypes": { - "$ref": "#/definitions/mediaTypes" - }, - "adserverTargeting": { - "$ref": "#/definitions/adserverTargeting" - }, - "bidwonStatus": { - "description": "Whether the bid was successfully rendered or not", - "type": "string", - "enum": [ - "success", - "error" - ] - }, - "siteId": { - "type": "number", - "description": "The Rubicon siteId associated with this adUnit - Removed if null" - }, - "zoneId": { - "type": "number", - "description": "The Rubicon zoneId associated with this adUnit - Removed if null" - } - } - } - ] - } - }, - "billableEvents":{ - "type":"array", - "minItems":1, - "items":{ - "type":"object", - "required":[ - "accountId", - "vendor", - "type", - "billingId" - ], - "properties":{ - "vendor":{ - "type":"string", - "description":"The name of the vendor who emitted the billable event" - }, - "type":{ - "type":"string", - "description":"The type of billable event", - "enum":[ - "impression", - "pageLoad", - "auction", - "request", - "general" - ] - }, - "billingId":{ - "type":"string", - "description":"A UUID which is responsible more mapping this event to" - }, - "accountId": { - "type": "number", - "description": "The account id for the rubicon publisher" - } - } - } - } - }, - "definitions": { - "gam": { - "type": "object", - "description": "The gam information for a given ad unit", - "required": [ - "adSlot" - ], - "properties": { - "adSlot": { - "type": "string" - }, - "advertiserId": { - "type": "integer" - }, - "creativeId": { - "type": "integer" - }, - "LineItemId": { - "type": "integer" - }, - "isSlotEmpty": { - "type": "boolean", - "enum": [ - true - ] - } - } - }, - "adserverTargeting": { - "type": "object", - "description": "The adserverTargeting key/value pairs", - "patternProperties": { - ".+": { - "type": "string" - } - } - }, - "samplingFactor": { - "type": "integer", - "description": "An integer value representing the factor to multiply event count by to receive unsampled count.", - "enum": [ - 1, - 10, - 20, - 40, - 100 - ] - }, - "videoAdFormat": { - "type": "string", - "description": "This value only provided for video specifies the ad format", - "enum": [ - "pre-roll", - "interstitial", - "outstream", - "mid-roll", - "post-roll", - "vertical" - ] - }, - "mediaTypes": { - "type": "array", - "uniqueItems": true, - "minItems": 1, - "items": { - "type": "string", - "enum": [ - "native", - "video", - "banner" - ] - } - }, - "dimensions": { - "type": "object", - "description": "Size object representing the dimensions of creative in pixels.", - "required": [ - "width", - "height" - ], - "properties": { - "width": { - "type": "integer", - "minimum": 1 - }, - "height": { - "type": "integer", - "minimum": 1 - } - } - }, - "bid": { - "type": "object", - "required": [ - "bidder", - "bidId", - "status", - "source" - ], - "properties": { - "bidder": { - "type": "string" - }, - "bidId": { - "type": "string", - "description": "UUID representing this individual bid request in this auction." - }, - "params": { - "description": "A copy of the bid.params from the adUnit.bids", - "anyOf": [ - { - "type": "object" - }, - { - "$ref": "#/definitions/params/rubicon" - } - ] - }, - "status": { - "type": "string", - "enum": [ - "success", - "no-bid", - "error", - "rejected-gdpr", - "rejected-ipf" - ] - }, - "error": { - "type": "object", - "additionalProperties": false, - "required": [ - "code" - ], - "properties": { - "code": { - "type": "string", - "enum": [ - "request-error", - "connect-error", - "timeout-error" - ] - }, - "description": { - "type": "string" - } - } - }, - "source": { - "type": "string", - "enum": [ - "client", - "server" - ] - }, - "clientLatencyMillis": { - "type": "integer", - "description": "Latency from auction start to bid response recieved in milliseconds." - }, - "serverLatencyMillis": { - "type": "integer", - "description": "Latency returned by prebid server (response_time_ms)." - }, - "bidResponse": { - "type": "object", - "required": [ - "mediaType", - "bidPriceUSD" - ], - "properties": { - "dimensions": { - "$ref": "#/definitions/dimensions" - }, - "mediaType": { - "type": "string", - "enum": [ - "native", - "video", - "banner" - ] - }, - "bidPriceUSD": { - "type": "number", - "description": "The bid value denoted in USD" - }, - "dealId": { - "type": "integer", - "description": "The id associated with any potential deals" - } - } - } - } - }, - "params": { - "rubicon": { - "type": "object", - "properties": { - "accountId": { - "type": "number" - }, - "siteId": { - "type": "number" - }, - "zoneId": { - "type": "number" - } - } - } - } - } -} diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index e81ef1c805f..1410e481d4e 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -5,13 +5,22 @@ import { masSizeOrdering, resetUserSync, classifiedAsVideo, - resetRubiConf + resetRubiConf, + converter } from 'modules/rubiconBidAdapter.js'; import {parse as parseQuery} from 'querystring'; import {config} from 'src/config.js'; import * as utils from 'src/utils.js'; import {find} from 'src/polyfill.js'; import {createEidsArray} from 'modules/userId/eids.js'; +import 'modules/schain.js'; +import 'modules/consentManagement.js'; +import 'modules/consentManagementUsp.js'; +import 'modules/userId/index.js'; +import 'modules/priceFloors.js'; +import 'modules/multibid/index.js'; +import adapterManager from 'src/adapterManager.js'; +import {syncAddFPDToBidderRequest} from '../../helpers/fpd.js'; const INTEGRATION = `pbjs_lite_v$prebid.version$`; // $prebid.version$ will be substituted in by gulp in built prebid const PBS_INTEGRATION = 'pbjs'; @@ -78,6 +87,11 @@ describe('the rubicon adapter', function () { function getBidderRequest() { return { bidderCode: 'rubicon', + ortb2: { + source: { + tid: 'c45dd708-a418-42ec-b8a7-b70a6c6fab0a', + } + }, auctionId: 'c45dd708-a418-42ec-b8a7-b70a6c6fab0a', bidderRequestId: '178e34bad3658f', bids: [ @@ -102,13 +116,20 @@ describe('the rubicon adapter', function () { referrer: 'localhost', latLong: [40.7607823, '111.8910325'] }, + mediaTypes: { + banner: [[300, 250]] + }, adUnitCode: '/19968336/header-bid-tag-0', code: 'div-1', sizes: [[300, 250], [320, 50]], bidId: '2ffb201a808da7', bidderRequestId: '178e34bad3658f', auctionId: 'c45dd708-a418-42ec-b8a7-b70a6c6fab0a', - transactionId: 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b' + ortb2Imp: { + ext: { + tid: 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b', + } + }, } ], start: 1472239426002, @@ -180,6 +201,7 @@ describe('the rubicon adapter', function () { * @param {boolean} [gdprApplies] */ function createGdprBidderRequest(gdprApplies) { + const bidderRequest = getBidderRequest(); if (typeof gdprApplies === 'boolean') { bidderRequest.gdprConsent = { 'consentString': 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==', @@ -190,15 +212,16 @@ describe('the rubicon adapter', function () { 'consentString': 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==' }; } + return bidderRequest; } - function createUspBidderRequest() { + function addUspToBidderRequest(bidderRequest) { bidderRequest.uspConsent = '1NYN'; } function createVideoBidderRequest() { - createGdprBidderRequest(true); - createUspBidderRequest(); + const bidderRequest = createGdprBidderRequest(true); + addUspToBidderRequest(bidderRequest); let bid = bidderRequest.bids[0]; bid.mediaTypes = { @@ -248,9 +271,10 @@ describe('the rubicon adapter', function () { criteoId: '1111', }; bid.userIdAsEids = createEidsArray(bid.userId); + return bidderRequest; } - function createVideoBidderRequestNoVideo() { + function removeVideoParamFromBidderRequest(bidderRequest) { let bid = bidderRequest.bids[0]; bid.mediaTypes = { video: { @@ -261,7 +285,9 @@ describe('the rubicon adapter', function () { } function createVideoBidderRequestOutstream() { + const bidderRequest = createGdprBidderRequest(false); let bid = bidderRequest.bids[0]; + delete bid.sizes; bid.mediaTypes = { video: { context: 'outstream', @@ -279,17 +305,20 @@ describe('the rubicon adapter', function () { protocols: [1, 2, 3, 4, 5, 6] }, }; - bid.params.accountId = 14062; - bid.params.siteId = 70608; - bid.params.zoneId = 335918; - bid.params.video = { - 'language': 'en', - 'skip': 1, - 'skipafter': 15, - 'playerHeight': 320, - 'playerWidth': 640, - 'size_id': 203 - }; + bid.params = { + accountId: 14062, + siteId: 70608, + zoneId: 335918, + video: { + 'language': 'en', + 'skip': 1, + 'skipafter': 15, + 'playerHeight': 320, + 'playerWidth': 640, + 'size_id': 203 + } + } + return bidderRequest; } beforeEach(function () { @@ -299,6 +328,11 @@ describe('the rubicon adapter', function () { bidderRequest = { bidderCode: 'rubicon', auctionId: 'c45dd708-a418-42ec-b8a7-b70a6c6fab0a', + ortb2: { + source: { + tid: 'c45dd708-a418-42ec-b8a7-b70a6c6fab0a', + } + }, bidderRequestId: '178e34bad3658f', bids: [ { @@ -307,7 +341,6 @@ describe('the rubicon adapter', function () { accountId: '14062', siteId: '70608', zoneId: '335918', - pchain: 'GAM:11111-reseller1:22222', userId: '12346', keywords: ['a', 'b', 'c'], inventory: { @@ -323,13 +356,20 @@ describe('the rubicon adapter', function () { referrer: 'localhost', latLong: [40.7607823, '111.8910325'] }, + mediaTypes: { + banner: [[300, 250]] + }, adUnitCode: '/19968336/header-bid-tag-0', code: 'div-1', sizes: [[300, 250], [320, 50]], bidId: '2ffb201a808da7', bidderRequestId: '178e34bad3658f', auctionId: 'c45dd708-a418-42ec-b8a7-b70a6c6fab0a', - transactionId: 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b' + ortb2Imp: { + ext: { + tid: 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b', + } + }, } ], start: 1472239426002, @@ -414,8 +454,7 @@ describe('the rubicon adapter', function () { 'rp_secure': /[01]/, 'rand': '0.1', 'tk_flint': INTEGRATION, - 'x_source.tid': 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b', - 'x_source.pchain': 'GAM:11111-reseller1:22222', + 'x_source.tid': 'c45dd708-a418-42ec-b8a7-b70a6c6fab0a', 'p_screen_res': /\d+x\d+/, 'tk_user_key': '12346', 'kw': 'a,b,c', @@ -583,20 +622,11 @@ describe('the rubicon adapter', function () { expect(data['p_pos']).to.equal('atf;;btf;;'); }); - it('should not send x_source.pchain to AE if params.pchain is not specified', function () { - var noPchainRequest = utils.deepClone(bidderRequest); - delete noPchainRequest.bids[0].params.pchain; - - let [request] = spec.buildRequests(noPchainRequest.bids, noPchainRequest); - expect(request.data).to.contain('&site_id=70608&'); - expect(request.data).to.not.contain('x_source.pchain'); - }); - it('ad engine query params should be ordered correctly', function () { sandbox.stub(Math, 'random').callsFake(() => 0.1); let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - const referenceOrdering = ['account_id', 'site_id', 'zone_id', 'size_id', 'alt_size_ids', 'p_pos', 'rf', 'p_geo.latitude', 'p_geo.longitude', 'kw', 'tg_v.ucat', 'tg_v.lastsearch', 'tg_v.likes', 'tg_i.rating', 'tg_i.prodtype', 'tk_flint', 'x_source.tid', 'l_pb_bid_id', 'x_source.pchain', 'p_screen_res', 'rp_secure', 'tk_user_key', 'tg_fl.eid', 'rp_maxbids', 'slots', 'rand']; + const referenceOrdering = ['account_id', 'site_id', 'zone_id', 'size_id', 'alt_size_ids', 'p_pos', 'rf', 'p_geo.latitude', 'p_geo.longitude', 'kw', 'tg_v.ucat', 'tg_v.lastsearch', 'tg_v.likes', 'tg_i.rating', 'tg_i.prodtype', 'tk_flint', 'x_source.tid', 'l_pb_bid_id', 'p_screen_res', 'rp_secure', 'tk_user_key', 'x_imp.ext.tid', 'tg_fl.eid', 'rp_maxbids', 'slots', 'rand']; request.data.split('&').forEach((item, i) => { expect(item.split('=')[0]).to.equal(referenceOrdering[i]); @@ -614,7 +644,8 @@ describe('the rubicon adapter', function () { 'rp_secure': /[01]/, 'rand': '0.1', 'tk_flint': INTEGRATION, - 'x_source.tid': 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b', + 'x_source.tid': 'c45dd708-a418-42ec-b8a7-b70a6c6fab0a', + 'x_imp.ext.tid': 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b', 'p_screen_res': /\d+x\d+/, 'tk_user_key': '12346', 'kw': 'a,b,c', @@ -739,7 +770,7 @@ describe('the rubicon adapter', function () { describe('GDPR consent config', function () { it('should send "gdpr" and "gdpr_consent", when gdprConsent defines consentString and gdprApplies', function () { - createGdprBidderRequest(true); + const bidderRequest = createGdprBidderRequest(true); let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); let data = parseQuery(request.data); @@ -748,7 +779,7 @@ describe('the rubicon adapter', function () { }); it('should send only "gdpr_consent", when gdprConsent defines only consentString', function () { - createGdprBidderRequest(); + const bidderRequest = createGdprBidderRequest(); let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); let data = parseQuery(request.data); @@ -765,12 +796,12 @@ describe('the rubicon adapter', function () { }); it('should set "gdpr" value as 1 or 0, using "gdprApplies" value of either true/false', function () { - createGdprBidderRequest(true); + let bidderRequest = createGdprBidderRequest(true); let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); let data = parseQuery(request.data); expect(data['gdpr']).to.equal('1'); - createGdprBidderRequest(false); + bidderRequest = createGdprBidderRequest(false); [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); data = parseQuery(request.data); expect(data['gdpr']).to.equal('0'); @@ -779,7 +810,7 @@ describe('the rubicon adapter', function () { describe('USP Consent', function () { it('should send us_privacy if bidderRequest has a value for uspConsent', function () { - createUspBidderRequest(); + addUspToBidderRequest(bidderRequest); let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); let data = parseQuery(request.data); @@ -794,6 +825,29 @@ describe('the rubicon adapter', function () { }); }); + describe('GPP Consent', function () { + it('should send gpp information if bidderRequest has a value for gppConsent', function () { + bidderRequest.gppConsent = { + gppString: 'consent', + applicableSections: 2 + }; + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + let data = parseQuery(request.data); + delete bidderRequest.gppConsent; + + expect(data['gpp']).to.equal('consent'); + expect(data['gpp_sid']).to.equal('2'); + }); + + it('should not send gpp information if bidderRequest does not have a value for gppConsent', function () { + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + let data = parseQuery(request.data); + + expect(data['gpp']).to.equal(undefined); + expect(data['gpp_sid']).to.equal(undefined); + }); + }); + describe('first party data', function () { it('should not have any tg_v or tg_i params if all are undefined', function () { let params = { @@ -965,7 +1019,7 @@ describe('the rubicon adapter', function () { 'rp_secure': /[01]/, 'rand': '0.1', 'tk_flint': INTEGRATION, - 'x_source.tid': 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b', + 'x_source.tid': 'c45dd708-a418-42ec-b8a7-b70a6c6fab0a', 'p_screen_res': /\d+x\d+/, 'tk_user_key': '12346', 'kw': 'a,b,c', @@ -1534,717 +1588,711 @@ describe('the rubicon adapter', function () { }); }); - describe('for video requests', function () { - it('should make a well-formed video request', function () { - createVideoBidderRequest(); + if (FEATURES.VIDEO) { + describe('for video requests', function () { + it('should make a well-formed video request', function () { + const bidderRequest = createVideoBidderRequest(); - sandbox.stub(Date, 'now').callsFake(() => - bidderRequest.auctionStart + 100 - ); + sandbox.stub(Date, 'now').callsFake(() => + bidderRequest.auctionStart + 100 + ); - let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - let post = request.data; - - expect(post).to.have.property('imp'); - // .with.length.of(1); - let imp = post.imp[0]; - expect(imp.id).to.equal(bidderRequest.bids[0].adUnitCode); - expect(imp.exp).to.equal(undefined); // now undefined - expect(imp.video.w).to.equal(640); - expect(imp.video.h).to.equal(480); - expect(imp.video.pos).to.equal(1); - expect(imp.video.context).to.equal('instream'); - expect(imp.video.minduration).to.equal(15); - expect(imp.video.maxduration).to.equal(30); - expect(imp.video.startdelay).to.equal(0); - expect(imp.video.skip).to.equal(1); - expect(imp.video.skipafter).to.equal(15); - expect(imp.ext.rubicon.video.playerWidth).to.equal(640); - expect(imp.ext.rubicon.video.playerHeight).to.equal(480); - expect(imp.ext.rubicon.video.size_id).to.equal(201); - expect(imp.ext.rubicon.video.language).to.equal('en'); - // Also want it to be in post.site.content.language - expect(post.site.content.language).to.equal('en'); - expect(imp.ext.rubicon.video.skip).to.equal(1); - expect(imp.ext.rubicon.video.skipafter).to.equal(15); - expect(imp.ext.prebid.auctiontimestamp).to.equal(1472239426000); - // should contain version - expect(post.ext.prebid.channel).to.deep.equal({name: 'pbjs', version: 'v$prebid.version$'}); - expect(post.user.ext.consent).to.equal('BOJ/P2HOJ/P2HABABMAAAAAZ+A=='); - // EIDs should exist - expect(post.user.ext).to.have.property('eids').that.is.an('array'); - // LiveIntent should exist - expect(post.user.ext.eids[0].source).to.equal('liveintent.com'); - expect(post.user.ext.eids[0].uids[0].id).to.equal('0000-1111-2222-3333'); - expect(post.user.ext.eids[0].uids[0].atype).to.equal(3); - expect(post.user.ext.eids[0]).to.have.property('ext').that.is.an('object'); - expect(post.user.ext.eids[0].ext).to.have.property('segments').that.is.an('array'); - expect(post.user.ext.eids[0].ext.segments[0]).to.equal('segA'); - expect(post.user.ext.eids[0].ext.segments[1]).to.equal('segB'); - // LiveRamp should exist - expect(post.user.ext.eids[1].source).to.equal('liveramp.com'); - expect(post.user.ext.eids[1].uids[0].id).to.equal('1111-2222-3333-4444'); - expect(post.user.ext.eids[1].uids[0].atype).to.equal(3); - // UnifiedId should exist - expect(post.user.ext.eids[2].source).to.equal('adserver.org'); - expect(post.user.ext.eids[2].uids[0].atype).to.equal(1); - expect(post.user.ext.eids[2].uids[0].id).to.equal('3000'); - // PubCommonId should exist - expect(post.user.ext.eids[3].source).to.equal('pubcid.org'); - expect(post.user.ext.eids[3].uids[0].atype).to.equal(1); - expect(post.user.ext.eids[3].uids[0].id).to.equal('4000'); - // example should exist - expect(post.user.ext.eids[4].source).to.equal('example.com'); - expect(post.user.ext.eids[4].uids[0].id).to.equal('333333'); - // id-partner.com - expect(post.user.ext.eids[5].source).to.equal('id-partner.com'); - expect(post.user.ext.eids[5].uids[0].id).to.equal('4444444'); - // CriteoId should exist - expect(post.user.ext.eids[6].source).to.equal('criteo.com'); - expect(post.user.ext.eids[6].uids[0].id).to.equal('1111'); - expect(post.user.ext.eids[6].uids[0].atype).to.equal(1); - - expect(post.regs.ext.gdpr).to.equal(1); - expect(post.regs.ext.us_privacy).to.equal('1NYN'); - expect(post).to.have.property('ext').that.is.an('object'); - expect(post.ext.prebid.targeting.includewinners).to.equal(true); - expect(post.ext.prebid).to.have.property('cache').that.is.an('object'); - expect(post.ext.prebid.cache).to.have.property('vastxml').that.is.an('object'); - expect(post.ext.prebid.cache.vastxml).to.have.property('returnCreative').that.is.an('boolean'); - expect(post.ext.prebid.cache.vastxml.returnCreative).to.equal(false); - expect(post.ext.prebid.bidders.rubicon.integration).to.equal(PBS_INTEGRATION); - }); + let [request] = spec.buildRequests(bidderRequest.bids, syncAddFPDToBidderRequest(bidderRequest)); + let post = request.data; - describe('ortb2imp sent to video bids', function () { - beforeEach(function () { + expect(post).to.have.property('imp'); + // .with.length.of(1); + let imp = post.imp[0]; + expect(imp.id).to.equal(bidderRequest.bids[0].adUnitCode); + expect(imp.exp).to.equal(undefined); // now undefined + expect(imp.video.w).to.equal(640); + expect(imp.video.h).to.equal(480); + expect(imp.video.pos).to.equal(1); + expect(imp.video.minduration).to.equal(15); + expect(imp.video.maxduration).to.equal(30); + expect(imp.video.startdelay).to.equal(0); + expect(imp.video.skip).to.equal(1); + expect(imp.video.skipafter).to.equal(15); + expect(imp.ext.prebid.bidder.rubicon.video.playerWidth).to.equal(640); + expect(imp.ext.prebid.bidder.rubicon.video.playerHeight).to.equal(480); + expect(imp.ext.prebid.bidder.rubicon.video.size_id).to.equal(201); + expect(imp.ext.prebid.bidder.rubicon.video.language).to.equal('en'); + // Also want it to be in post.site.content.language + expect(imp.ext.prebid.bidder.rubicon.video.skip).to.equal(1); + expect(imp.ext.prebid.bidder.rubicon.video.skipafter).to.equal(15); + expect(post.ext.prebid.auctiontimestamp).to.equal(1472239426000); + // should contain version + expect(post.ext.prebid.channel).to.deep.equal({name: 'pbjs', version: $$PREBID_GLOBAL$$.version}); + expect(post.user.ext.consent).to.equal('BOJ/P2HOJ/P2HABABMAAAAAZ+A=='); + // EIDs should exist + expect(post.user.ext).to.have.property('eids').that.is.an('array'); + // LiveIntent should exist + expect(post.user.ext.eids[0].source).to.equal('liveintent.com'); + expect(post.user.ext.eids[0].uids[0].id).to.equal('0000-1111-2222-3333'); + expect(post.user.ext.eids[0].uids[0].atype).to.equal(3); + expect(post.user.ext.eids[0]).to.have.property('ext').that.is.an('object'); + expect(post.user.ext.eids[0].ext).to.have.property('segments').that.is.an('array'); + expect(post.user.ext.eids[0].ext.segments[0]).to.equal('segA'); + expect(post.user.ext.eids[0].ext.segments[1]).to.equal('segB'); + // LiveRamp should exist + expect(post.user.ext.eids[1].source).to.equal('liveramp.com'); + expect(post.user.ext.eids[1].uids[0].id).to.equal('1111-2222-3333-4444'); + expect(post.user.ext.eids[1].uids[0].atype).to.equal(3); + // UnifiedId should exist + expect(post.user.ext.eids[2].source).to.equal('adserver.org'); + expect(post.user.ext.eids[2].uids[0].atype).to.equal(1); + expect(post.user.ext.eids[2].uids[0].id).to.equal('3000'); + // PubCommonId should exist + expect(post.user.ext.eids[3].source).to.equal('pubcid.org'); + expect(post.user.ext.eids[3].uids[0].atype).to.equal(1); + expect(post.user.ext.eids[3].uids[0].id).to.equal('4000'); + // example should exist + expect(post.user.ext.eids[4].source).to.equal('example.com'); + expect(post.user.ext.eids[4].uids[0].id).to.equal('333333'); + // id-partner.com + expect(post.user.ext.eids[5].source).to.equal('id-partner.com'); + expect(post.user.ext.eids[5].uids[0].id).to.equal('4444444'); + // CriteoId should exist + expect(post.user.ext.eids[6].source).to.equal('criteo.com'); + expect(post.user.ext.eids[6].uids[0].id).to.equal('1111'); + expect(post.user.ext.eids[6].uids[0].atype).to.equal(1); + + expect(post.regs.ext.gdpr).to.equal(1); + expect(post.regs.ext.us_privacy).to.equal('1NYN'); + expect(post).to.have.property('ext').that.is.an('object'); + expect(post.ext.prebid.targeting.includewinners).to.equal(true); + expect(post.ext.prebid).to.have.property('cache').that.is.an('object'); + expect(post.ext.prebid.cache).to.have.property('vastxml').that.is.an('object'); + expect(post.ext.prebid.cache.vastxml).to.have.property('returnCreative').that.is.an('boolean'); + expect(post.ext.prebid.cache.vastxml.returnCreative).to.equal(false); + expect(post.ext.prebid.bidders.rubicon.integration).to.equal(PBS_INTEGRATION); + }); + + describe('ortb2imp sent to video bids', function () { + beforeEach(function () { // initialize - if (bidderRequest.bids[0].hasOwnProperty('ortb2Imp')) { - delete bidderRequest.bids[0].ortb2Imp; - } - }); + if (bidderRequest.bids[0].hasOwnProperty('ortb2Imp')) { + delete bidderRequest.bids[0].ortb2Imp; + } + }); - it('should add ortb values to video requests', function () { - createVideoBidderRequest(); + it('should add ortb values to video requests', function () { + const bidderRequest = createVideoBidderRequest(); - sandbox.stub(Date, 'now').callsFake(() => - bidderRequest.auctionStart + 100 - ); + sandbox.stub(Date, 'now').callsFake(() => + bidderRequest.auctionStart + 100 + ); - bidderRequest.bids[0].ortb2Imp = { - ext: { - gpid: '/test/gpid', - data: { - pbadslot: '/test/pbadslot' - }, - prebid: { - storedauctionresponse: { - id: 'sample_video_response' + bidderRequest.bids[0].ortb2Imp = { + ext: { + gpid: '/test/gpid', + data: { + pbadslot: '/test/pbadslot' + }, + prebid: { + storedauctionresponse: { + id: 'sample_video_response' + } } } } - } - let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - let post = request.data; + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + let post = request.data; - expect(post).to.have.property('imp'); - // .with.length.of(1); - let imp = post.imp[0]; - expect(imp.ext.gpid).to.equal('/test/gpid'); - expect(imp.ext.data.pbadslot).to.equal('/test/pbadslot'); - expect(imp.ext.prebid.storedauctionresponse.id).to.equal('sample_video_response'); + expect(post).to.have.property('imp'); + // .with.length.of(1); + let imp = post.imp[0]; + expect(imp.ext.gpid).to.equal('/test/gpid'); + expect(imp.ext.data.pbadslot).to.equal('/test/pbadslot'); + expect(imp.ext.prebid.storedauctionresponse.id).to.equal('sample_video_response'); + }); }); - }); - it('should correctly set bidfloor on imp when getfloor in scope', function () { - createVideoBidderRequest(); - // default getFloor response is empty object so should not break and not send hard_floor - bidderRequest.bids[0].getFloor = () => getFloorResponse; - sinon.spy(bidderRequest.bids[0], 'getFloor'); + it('should correctly set bidfloor on imp when getfloor in scope', function () { + const bidderRequest = createVideoBidderRequest(); + // default getFloor response is empty object so should not break and not send hard_floor + bidderRequest.bids[0].getFloor = () => getFloorResponse; + sinon.spy(bidderRequest.bids[0], 'getFloor'); - sandbox.stub(Date, 'now').callsFake(() => - bidderRequest.auctionStart + 100 - ); + sandbox.stub(Date, 'now').callsFake(() => + bidderRequest.auctionStart + 100 + ); - let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - // make sure banner bid called with right stuff - expect( - bidderRequest.bids[0].getFloor.calledWith({ - currency: 'USD', - mediaType: 'video', - size: [640, 480] - }) - ).to.be.true; + // make sure banner bid called with right stuff + expect( + bidderRequest.bids[0].getFloor.calledWith({ + currency: 'USD', + mediaType: '*', + size: '*' + }) + ).to.be.true; - // not an object should work and not send - expect(request.data.imp[0].bidfloor).to.be.undefined; + // not an object should work and not send + expect(request.data.imp[0].bidfloor).to.be.undefined; - // make it respond with a non USD floor should not send it - getFloorResponse = {currency: 'EUR', floor: 1.0}; - [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(request.data.imp[0].bidfloor).to.be.undefined; + // make it respond with a non USD floor should not send it + getFloorResponse = {currency: 'EUR', floor: 1.0}; + [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(request.data.imp[0].bidfloor).to.be.undefined; - // make it respond with a non USD floor should not send it - getFloorResponse = {currency: 'EUR'}; - [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(request.data.imp[0].bidfloor).to.be.undefined; + // make it respond with a non USD floor should not send it + getFloorResponse = {currency: 'EUR'}; + [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(request.data.imp[0].bidfloor).to.be.undefined; - // make it respond with USD floor and string floor - getFloorResponse = {currency: 'USD', floor: '1.23'}; - [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(request.data.imp[0].bidfloor).to.equal(1.23); + // make it respond with USD floor and string floor + getFloorResponse = {currency: 'USD', floor: '1.23'}; + [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(request.data.imp[0].bidfloor).to.equal(1.23); - // make it respond with USD floor and num floor - getFloorResponse = {currency: 'USD', floor: 1.23}; - [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(request.data.imp[0].bidfloor).to.equal(1.23); - }); + // make it respond with USD floor and num floor + getFloorResponse = {currency: 'USD', floor: 1.23}; + [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(request.data.imp[0].bidfloor).to.equal(1.23); + }); - it('should continue with auction and log error if getFloor throws one', function () { - createVideoBidderRequest(); - // default getFloor response is empty object so should not break and not send hard_floor - bidderRequest.bids[0].getFloor = () => { - throw new Error('An exception!'); - }; - sandbox.stub(Date, 'now').callsFake(() => - bidderRequest.auctionStart + 100 - ); + it('should continue with auction if getFloor throws error', function () { + const bidderRequest = createVideoBidderRequest(); + // default getFloor response is empty object so should not break and not send hard_floor + bidderRequest.bids[0].getFloor = () => { + throw new Error('An exception!'); + }; + sandbox.stub(Date, 'now').callsFake(() => + bidderRequest.auctionStart + 100 + ); - let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - // log error called - expect(logErrorSpy.calledOnce).to.equal(true); + // should have an imp + expect(request.data.imp).to.exist.and.to.be.a('array'); + expect(request.data.imp).to.have.lengthOf(1); - // should have an imp - expect(request.data.imp).to.exist.and.to.be.a('array'); - expect(request.data.imp).to.have.lengthOf(1); + // should be NO bidFloor + expect(request.data.imp[0].bidfloor).to.be.undefined; + expect(request.data.imp[0].bidfloorcur).to.be.undefined; + }); - // should be NO bidFloor - expect(request.data.imp[0].bidfloor).to.be.undefined; - }); + it('should add alias name to PBS Request', function () { + const bidderRequest = createVideoBidderRequest(); + adapterManager.aliasRegistry['superRubicon'] = 'rubicon'; + bidderRequest.bidderCode = 'superRubicon'; + bidderRequest.bids[0].bidder = 'superRubicon'; + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - it('should add alias name to PBS Request', function () { - createVideoBidderRequest(); + // should have the aliases object sent to PBS + expect(request.data.ext.prebid).to.haveOwnProperty('aliases'); + expect(request.data.ext.prebid.aliases).to.deep.equal({superRubicon: 'rubicon'}); - bidderRequest.bidderCode = 'superRubicon'; - bidderRequest.bids[0].bidder = 'superRubicon'; - let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + // should have the imp ext bidder params be under the alias name not rubicon superRubicon + expect(request.data.imp[0].ext.prebid.bidder).to.have.property('superRubicon').that.is.an('object'); + expect(request.data.imp[0].ext.prebid.bidder).to.not.haveOwnProperty('rubicon'); + }); - // should have the aliases object sent to PBS - expect(request.data.ext.prebid).to.haveOwnProperty('aliases'); - expect(request.data.ext.prebid.aliases).to.deep.equal({superRubicon: 'rubicon'}); + it('should add floors flag correctly to PBS Request', function () { + const bidderRequest = createVideoBidderRequest(); + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - // should have the imp ext bidder params be under the alias name not rubicon superRubicon - expect(request.data.imp[0].ext).to.have.property('superRubicon').that.is.an('object'); - expect(request.data.imp[0].ext).to.not.haveOwnProperty('rubicon'); - }); + // should not pass if undefined + expect(request.data.ext.prebid.floors).to.be.undefined; - it('should add floors flag correctly to PBS Request', function () { - createVideoBidderRequest(); - let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + // should pass it as false + bidderRequest.bids[0].floorData = { + skipped: false, + location: 'fetch', + } + let [newRequest] = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(newRequest.data.ext.prebid.floors).to.deep.equal({ enabled: false }); + }); + + it('should add multibid configuration to PBS Request', function () { + const bidderRequest = createVideoBidderRequest(); + + const multibid = [{ + bidder: 'bidderA', + maxBids: 2 + }, { + bidder: 'bidderB', + maxBids: 2 + }]; + const expected = [{ + bidder: 'bidderA', + maxbids: 2 + }, { + bidder: 'bidderB', + maxbids: 2 + }]; + + config.setConfig({multibid: multibid}); - // should not pass if undefined - expect(request.data.ext.prebid.floors).to.be.undefined; + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - // should pass it as false - bidderRequest.bids[0].floorData = { - skipped: false, - location: 'fetch', - } - let [newRequest] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(newRequest.data.ext.prebid.floors).to.deep.equal({ enabled: false }); - }); + // should have the aliases object sent to PBS + expect(request.data.ext.prebid).to.haveOwnProperty('multibid'); + expect(request.data.ext.prebid.multibid).to.deep.equal(expected); + }); - it('should add multibid configuration to PBS Request', function () { - createVideoBidderRequest(); + it('should pass client analytics to PBS endpoint if all modules included', function () { + const bidderRequest = createVideoBidderRequest(); + $$PREBID_GLOBAL$$.installedModules = []; + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + let payload = request.data; - const multibid = [{ - bidder: 'bidderA', - maxBids: 2 - }, { - bidder: 'bidderB', - maxBids: 2 - }]; - const expected = [{ - bidder: 'bidderA', - maxbids: 2 - }, { - bidder: 'bidderB', - maxbids: 2 - }]; + expect(payload.ext.prebid.analytics).to.not.be.undefined; + expect(payload.ext.prebid.analytics).to.deep.equal({'rubicon': {'client-analytics': true}}); + }); - config.setConfig({multibid: multibid}); + it('should pass client analytics to PBS endpoint if rubicon analytics adapter is included', function () { + const bidderRequest = createVideoBidderRequest(); + $$PREBID_GLOBAL$$.installedModules = ['rubiconBidAdapter', 'rubiconAnalyticsAdapter']; + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + let payload = request.data; - let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(payload.ext.prebid.analytics).to.not.be.undefined; + expect(payload.ext.prebid.analytics).to.deep.equal({'rubicon': {'client-analytics': true}}); + }); - // should have the aliases object sent to PBS - expect(request.data.ext.prebid).to.haveOwnProperty('multibid'); - expect(request.data.ext.prebid.multibid).to.deep.equal(expected); - }); + it('should not pass client analytics to PBS endpoint if rubicon analytics adapter is not included', function () { + const bidderRequest = createVideoBidderRequest(); + $$PREBID_GLOBAL$$.installedModules = ['rubiconBidAdapter']; + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + let payload = request.data; - it('should pass client analytics to PBS endpoint if all modules included', function () { - createVideoBidderRequest(); - $$PREBID_GLOBAL$$.installedModules = []; - let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - let payload = request.data; + expect(payload.ext.prebid.analytics).to.be.undefined; + }); - expect(payload.ext.prebid.analytics).to.not.be.undefined; - expect(payload.ext.prebid.analytics).to.deep.equal({'rubicon': {'client-analytics': true}}); - }); + it('should send video exp param correctly when set', function () { + const bidderRequest = createVideoBidderRequest(); + config.setConfig({s2sConfig: {defaultTtl: 600}}); + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + let post = request.data; - it('should pass client analytics to PBS endpoint if rubicon analytics adapter is included', function () { - createVideoBidderRequest(); - $$PREBID_GLOBAL$$.installedModules = ['rubiconBidAdapter', 'rubiconAnalyticsAdapter']; - let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - let payload = request.data; + // should exp set to the right value according to config + let imp = post.imp[0]; + expect(imp.exp).to.equal(600); + }); - expect(payload.ext.prebid.analytics).to.not.be.undefined; - expect(payload.ext.prebid.analytics).to.deep.equal({'rubicon': {'client-analytics': true}}); - }); + it('should not send video exp at all if not set in s2sConfig config', function () { + const bidderRequest = createVideoBidderRequest(); + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + let post = request.data; - it('should not pass client analytics to PBS endpoint if rubicon analytics adapter is not included', function () { - createVideoBidderRequest(); - $$PREBID_GLOBAL$$.installedModules = ['rubiconBidAdapter']; - let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - let payload = request.data; + // should exp set to the right value according to config + let imp = post.imp[0]; + // bidderFactory stringifies request body before sending so removes undefined attributes: + expect(imp.exp).to.equal(undefined); + }); - expect(payload.ext.prebid.analytics).to.be.undefined; - }); + it('should send tmax as the bidderRequest timeout value', function () { + const bidderRequest = createVideoBidderRequest(); + bidderRequest.timeout = 3333; + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + let post = request.data; + expect(post.tmax).to.equal(3333); + }); - it('should send video exp param correctly when set', function () { - createVideoBidderRequest(); - config.setConfig({s2sConfig: {defaultTtl: 600}}); - let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - let post = request.data; + it('should send correct bidfloor to PBS', function () { + const bidderRequest = createVideoBidderRequest(); - // should exp set to the right value according to config - let imp = post.imp[0]; - expect(imp.exp).to.equal(600); - }); + bidderRequest.bids[0].params.floor = 0.1; + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(request.data.imp[0].bidfloor).to.equal(0.1); - it('should not send video exp at all if not set in s2sConfig config', function () { - createVideoBidderRequest(); - let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - let post = request.data; + bidderRequest.bids[0].params.floor = 5.5; + [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(request.data.imp[0].bidfloor).to.equal(5.5); - // should exp set to the right value according to config - let imp = post.imp[0]; - // bidderFactory stringifies request body before sending so removes undefined attributes: - expect(imp.exp).to.equal(undefined); - }); + bidderRequest.bids[0].params.floor = '1.7'; + [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(request.data.imp[0].bidfloor).to.equal(1.7); - it('should send tmax as the bidderRequest timeout value', function () { - createVideoBidderRequest(); - bidderRequest.timeout = 3333; - let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - let post = request.data; - expect(post.tmax).to.equal(3333); - }); + bidderRequest.bids[0].params.floor = 0; + [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(request.data.imp[0].bidfloor).to.equal(0); - it('should send correct bidfloor to PBS', function () { - createVideoBidderRequest(); + bidderRequest.bids[0].params.floor = undefined; + [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(request.data.imp[0]).to.not.haveOwnProperty('bidfloor'); - bidderRequest.bids[0].params.floor = 0.1; - let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(request.data.imp[0].bidfloor).to.equal(0.1); + bidderRequest.bids[0].params.floor = null; + [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(request.data.imp[0]).to.not.haveOwnProperty('bidfloor'); + }); + + it('should send request with proper ad position', function () { + const bidderRequest = createVideoBidderRequest(); + let positionBidderRequest = utils.deepClone(bidderRequest); + positionBidderRequest.bids[0].mediaTypes.video.pos = 1; + let [request] = spec.buildRequests(positionBidderRequest.bids, positionBidderRequest); + expect(request.data.imp[0].video.pos).to.equal(1); + + positionBidderRequest = utils.deepClone(bidderRequest); + positionBidderRequest.bids[0].params.position = undefined; + positionBidderRequest.bids[0].mediaTypes.video.pos = undefined; + [request] = spec.buildRequests(positionBidderRequest.bids, positionBidderRequest); + expect(request.data.imp[0].video.pos).to.equal(undefined); + + positionBidderRequest = utils.deepClone(bidderRequest); + positionBidderRequest.bids[0].params.position = 'atf' + positionBidderRequest.bids[0].mediaTypes.video.pos = undefined; + [request] = spec.buildRequests(positionBidderRequest.bids, positionBidderRequest); + expect(request.data.imp[0].video.pos).to.equal(1); + + positionBidderRequest = utils.deepClone(bidderRequest); + positionBidderRequest.bids[0].params.position = 'btf'; + positionBidderRequest.bids[0].mediaTypes.video.pos = undefined; + [request] = spec.buildRequests(positionBidderRequest.bids, positionBidderRequest); + expect(request.data.imp[0].video.pos).to.equal(3); + + positionBidderRequest = utils.deepClone(bidderRequest); + positionBidderRequest.bids[0].params.position = 'foobar'; + positionBidderRequest.bids[0].mediaTypes.video.pos = undefined; + [request] = spec.buildRequests(positionBidderRequest.bids, positionBidderRequest); + expect(request.data.imp[0].video.pos).to.equal(undefined); + }); + + it('should properly enforce video.context to be either instream or outstream', function () { + let bid = bidderRequest.bids[0]; + bid.mediaTypes = { + video: { + context: 'instream', + mimes: ['video/mp4', 'video/x-ms-wmv'], + protocols: [2, 5], + maxduration: 30, + linearity: 1, + api: [2] + } + } + bid.params.video = {}; - bidderRequest.bids[0].params.floor = 5.5; - [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(request.data.imp[0].bidfloor).to.equal(5.5); + sandbox.stub(Date, 'now').callsFake(() => + bidderRequest.auctionStart + 100 + ); - bidderRequest.bids[0].params.floor = '1.7'; - [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(request.data.imp[0].bidfloor).to.equal(1.7); + const bidRequestCopy = utils.deepClone(bidderRequest.bids[0]); + expect(spec.isBidRequestValid(bidRequestCopy)).to.equal(true); - bidderRequest.bids[0].params.floor = 0; - [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(request.data.imp[0].bidfloor).to.equal(0); + // change context to outstream, still true + bidRequestCopy.mediaTypes.video.context = 'outstream'; + expect(spec.isBidRequestValid(bidRequestCopy)).to.equal(true); - bidderRequest.bids[0].params.floor = undefined; - [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(request.data.imp[0]).to.not.haveOwnProperty('bidfloor'); + // change context to random, false now + bidRequestCopy.mediaTypes.video.context = 'random'; + expect(spec.isBidRequestValid(bidRequestCopy)).to.equal(false); - bidderRequest.bids[0].params.floor = null; - [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(request.data.imp[0]).to.not.haveOwnProperty('bidfloor'); - }); + // change context to undefined, still false + bidRequestCopy.mediaTypes.video.context = undefined; + expect(spec.isBidRequestValid(bidRequestCopy)).to.equal(false); - it('should send request with proper ad position', function () { - createVideoBidderRequest(); - let positionBidderRequest = utils.deepClone(bidderRequest); - positionBidderRequest.bids[0].mediaTypes.video.pos = 1; - let [request] = spec.buildRequests(positionBidderRequest.bids, positionBidderRequest); - expect(request.data.imp[0].video.pos).to.equal(1); - - positionBidderRequest = utils.deepClone(bidderRequest); - positionBidderRequest.bids[0].params.position = undefined; - positionBidderRequest.bids[0].mediaTypes.video.pos = undefined; - [request] = spec.buildRequests(positionBidderRequest.bids, positionBidderRequest); - expect(request.data.imp[0].video.pos).to.equal(undefined); - - positionBidderRequest = utils.deepClone(bidderRequest); - positionBidderRequest.bids[0].params.position = 'atf' - positionBidderRequest.bids[0].mediaTypes.video.pos = undefined; - [request] = spec.buildRequests(positionBidderRequest.bids, positionBidderRequest); - expect(request.data.imp[0].video.pos).to.equal(1); - - positionBidderRequest = utils.deepClone(bidderRequest); - positionBidderRequest.bids[0].params.position = 'btf'; - positionBidderRequest.bids[0].mediaTypes.video.pos = undefined; - [request] = spec.buildRequests(positionBidderRequest.bids, positionBidderRequest); - expect(request.data.imp[0].video.pos).to.equal(3); - - positionBidderRequest = utils.deepClone(bidderRequest); - positionBidderRequest.bids[0].params.position = 'foobar'; - positionBidderRequest.bids[0].mediaTypes.video.pos = undefined; - [request] = spec.buildRequests(positionBidderRequest.bids, positionBidderRequest); - expect(request.data.imp[0].video.pos).to.equal(undefined); - }); + // remove context, still false + delete bidRequestCopy.mediaTypes.video.context; + expect(spec.isBidRequestValid(bidRequestCopy)).to.equal(false); + }); - it('should properly enforce video.context to be either instream or outstream', function () { - let bid = bidderRequest.bids[0]; - bid.mediaTypes = { - video: { - context: 'instream', - mimes: ['video/mp4', 'video/x-ms-wmv'], - protocols: [2, 5], - maxduration: 30, - linearity: 1, - api: [2] - } - } - bid.params.video = {}; + it('should enforce the new required mediaTypes.video params', function () { + let bidderRequest = createVideoBidderRequest(); - sandbox.stub(Date, 'now').callsFake(() => - bidderRequest.auctionStart + 100 - ); + sandbox.stub(Date, 'now').callsFake(() => + bidderRequest.auctionStart + 100 + ); - const bidRequestCopy = utils.deepClone(bidderRequest.bids[0]); - expect(spec.isBidRequestValid(bidRequestCopy)).to.equal(true); + expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(true); - // change context to outstream, still true - bidRequestCopy.mediaTypes.video.context = 'outstream'; - expect(spec.isBidRequestValid(bidRequestCopy)).to.equal(true); + // change mimes to a non array, no good + bidderRequest = createVideoBidderRequest(); + bidderRequest.bids[0].mediaTypes.video.mimes = 'video/mp4'; + expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(false); - // change context to random, false now - bidRequestCopy.mediaTypes.video.context = 'random'; - expect(spec.isBidRequestValid(bidRequestCopy)).to.equal(false); + // delete mimes, no good + bidderRequest = createVideoBidderRequest(); + delete bidderRequest.bids[0].mediaTypes.video.mimes; + expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(false); - // change context to undefined, still false - bidRequestCopy.mediaTypes.video.context = undefined; - expect(spec.isBidRequestValid(bidRequestCopy)).to.equal(false); + // change protocols to an int not array of ints, no good + bidderRequest = createVideoBidderRequest(); + bidderRequest.bids[0].mediaTypes.video.protocols = 1; + expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(false); - // remove context, still false - delete bidRequestCopy.mediaTypes.video.context; - expect(spec.isBidRequestValid(bidRequestCopy)).to.equal(false); - }); + // delete protocols, no good + bidderRequest = createVideoBidderRequest(); + delete bidderRequest.bids[0].mediaTypes.video.protocols; + expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(false); - it('should enforce the new required mediaTypes.video params', function () { - createVideoBidderRequest(); - - sandbox.stub(Date, 'now').callsFake(() => - bidderRequest.auctionStart + 100 - ); - - expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(true); - - // change mimes to a non array, no good - createVideoBidderRequest(); - bidderRequest.bids[0].mediaTypes.video.mimes = 'video/mp4'; - expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(false); - - // delete mimes, no good - createVideoBidderRequest(); - delete bidderRequest.bids[0].mediaTypes.video.mimes; - expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(false); - - // change protocols to an int not array of ints, no good - createVideoBidderRequest(); - bidderRequest.bids[0].mediaTypes.video.protocols = 1; - expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(false); - - // delete protocols, no good - createVideoBidderRequest(); - delete bidderRequest.bids[0].mediaTypes.video.protocols; - expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(false); - - // change linearity to an string, no good - createVideoBidderRequest(); - bidderRequest.bids[0].mediaTypes.video.linearity = 'string'; - expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(false); - - // delete linearity, no good - createVideoBidderRequest(); - delete bidderRequest.bids[0].mediaTypes.video.linearity; - expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(false); - - // change api to an string, no good - createVideoBidderRequest(); - bidderRequest.bids[0].mediaTypes.video.api = 'string'; - expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(false); - - // delete api, no good - createVideoBidderRequest(); - delete bidderRequest.bids[0].mediaTypes.video.api; - expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(false); - }); + // change linearity to an string, no good + bidderRequest = createVideoBidderRequest(); + bidderRequest.bids[0].mediaTypes.video.linearity = 'string'; + expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(false); - it('bid request is valid when video context is outstream', function () { - createVideoBidderRequestOutstream(); - sandbox.stub(Date, 'now').callsFake(() => - bidderRequest.auctionStart + 100 - ); + // delete linearity, no good + bidderRequest = createVideoBidderRequest(); + delete bidderRequest.bids[0].mediaTypes.video.linearity; + expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(false); - const bidRequestCopy = utils.deepClone(bidderRequest); + // change api to an string, no good + bidderRequest = createVideoBidderRequest(); + bidderRequest.bids[0].mediaTypes.video.api = 'string'; + expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(false); - let [request] = spec.buildRequests(bidRequestCopy.bids, bidRequestCopy); - expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(true); - expect(request.data.imp[0].ext.rubicon.video.size_id).to.equal(203); - }); + // delete api, no good + bidderRequest = createVideoBidderRequest(); + delete bidderRequest.bids[0].mediaTypes.video.api; + expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(false); + }); - it('should send banner request when outstream or instream video included but no rubicon video obect is present', function () { - // add banner and video mediaTypes - bidderRequest.mediaTypes = { - banner: { - sizes: [[300, 250]] - }, - video: { - context: 'outstream' - } - }; - // no video object in rubicon params, so we should see one call made for banner + it('bid request is valid when video context is outstream', function () { + const bidderRequest = createVideoBidderRequestOutstream(); + sandbox.stub(Date, 'now').callsFake(() => + bidderRequest.auctionStart + 100 + ); - sandbox.stub(Date, 'now').callsFake(() => - bidderRequest.auctionStart + 100 - ); + const bidRequestCopy = utils.deepClone(bidderRequest); - let requests = spec.buildRequests(bidderRequest.bids, bidderRequest); + let [request] = spec.buildRequests(bidRequestCopy.bids, bidRequestCopy); + expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(true); + expect(request.data.imp[0].ext.prebid.bidder.rubicon.video.size_id).to.equal(203); + }); - expect(requests.length).to.equal(1); - expect(requests[0].url).to.equal('https://fastlane.rubiconproject.com/a/api/fastlane.json'); + it('should send banner request when outstream or instream video included but no rubicon video obect is present', function () { + // add banner and video mediaTypes + bidderRequest.mediaTypes = { + banner: { + sizes: [[300, 250]] + }, + video: { + context: 'outstream' + } + }; + // no video object in rubicon params, so we should see one call made for banner - bidderRequest.mediaTypes.video.context = 'instream'; + sandbox.stub(Date, 'now').callsFake(() => + bidderRequest.auctionStart + 100 + ); - requests = spec.buildRequests(bidderRequest.bids, bidderRequest); + let requests = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(requests.length).to.equal(1); - expect(requests[0].url).to.equal('https://fastlane.rubiconproject.com/a/api/fastlane.json'); - }); + expect(requests.length).to.equal(1); + expect(requests[0].url).to.equal('https://fastlane.rubiconproject.com/a/api/fastlane.json'); - it('should send request as banner when invalid video bid in multiple mediaType bidRequest', function () { - createVideoBidderRequestNoVideo(); + bidderRequest.mediaTypes.video.context = 'instream'; - let bid = bidderRequest.bids[0]; - bid.mediaTypes.banner = { - sizes: [[300, 250]] - }; + requests = spec.buildRequests(bidderRequest.bids, bidderRequest); - sandbox.stub(Date, 'now').callsFake(() => - bidderRequest.auctionStart + 100 - ); + expect(requests.length).to.equal(1); + expect(requests[0].url).to.equal('https://fastlane.rubiconproject.com/a/api/fastlane.json'); + }); - const bidRequestCopy = utils.deepClone(bidderRequest); + it('should send request as banner when invalid video bid in multiple mediaType bidRequest', function () { + removeVideoParamFromBidderRequest(bidderRequest); - let requests = spec.buildRequests(bidRequestCopy.bids, bidRequestCopy); - expect(requests.length).to.equal(1); - expect(requests[0].url).to.equal('https://fastlane.rubiconproject.com/a/api/fastlane.json'); - }); + let bid = bidderRequest.bids[0]; + bid.mediaTypes.banner = { + sizes: [[300, 250]] + }; - it('should include coppa flag in video bid request', () => { - createVideoBidderRequest(); + sandbox.stub(Date, 'now').callsFake(() => + bidderRequest.auctionStart + 100 + ); - sandbox.stub(Date, 'now').callsFake(() => - bidderRequest.auctionStart + 100 - ); + const bidRequestCopy = utils.deepClone(bidderRequest); - sandbox.stub(config, 'getConfig').callsFake(key => { - const config = { - 'coppa': true - }; - return config[key]; + let requests = spec.buildRequests(bidRequestCopy.bids, bidRequestCopy); + expect(requests.length).to.equal(1); + expect(requests[0].url).to.equal('https://fastlane.rubiconproject.com/a/api/fastlane.json'); }); - const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(request.data.regs.coppa).to.equal(1); - }); + it('should include coppa flag in video bid request', () => { + const bidderRequest = createVideoBidderRequest(); + + sandbox.stub(Date, 'now').callsFake(() => + bidderRequest.auctionStart + 100 + ); - it('should include first party data', () => { - createVideoBidderRequest(); + sandbox.stub(config, 'getConfig').callsFake(key => { + const config = { + 'coppa': true + }; + return config[key]; + }); + const [request] = spec.buildRequests(bidderRequest.bids, syncAddFPDToBidderRequest(bidderRequest)); + expect(request.data.regs.coppa).to.equal(1); + }); - const site = { - ext: { - data: { - page: 'home' - } - }, - content: { + it('should include first party data', () => { + const bidderRequest = createVideoBidderRequest(); + + const site = { + ext: { + data: { + page: 'home' + } + }, + content: { + data: [{foo: 'bar'}] + }, + keywords: 'e,f', + rating: '4-star', data: [{foo: 'bar'}] - }, - keywords: 'e,f', - rating: '4-star', - data: [{foo: 'bar'}] - }; - const user = { - ext: { - data: { - age: 31 - } - }, - keywords: 'd', - gender: 'M', - yob: '1984', - geo: {country: 'ca'}, - data: [{foo: 'bar'}] - }; + }; + const user = { + ext: { + data: { + age: 31 + } + }, + keywords: 'd', + gender: 'M', + yob: '1984', + geo: {country: 'ca'}, + data: [{foo: 'bar'}] + }; - const ortb2 = { - site, - user - }; + const ortb2 = { + site, + user + }; - const [request] = spec.buildRequests(bidderRequest.bids.map((b) => ({...b, ortb2})), bidderRequest); + const [request] = spec.buildRequests(bidderRequest.bids.map((b) => ({...b, ortb2})), bidderRequest); - const expected = { - site: Object.assign({}, site, {keywords: bidderRequest.bids[0].params.keywords.join(',')}), - user: Object.assign({}, user), - siteData: Object.assign({}, site.ext.data, bidderRequest.bids[0].params.inventory), - userData: Object.assign({}, user.ext.data, bidderRequest.bids[0].params.visitor), - }; + const expected = { + site: Object.assign({}, site, {keywords: bidderRequest.bids[0].params.keywords.join(',')}), + user: Object.assign({}, user), + siteData: Object.assign({}, site.ext.data, bidderRequest.bids[0].params.inventory), + userData: Object.assign({}, user.ext.data, bidderRequest.bids[0].params.visitor), + }; - delete request.data.site.page; - delete request.data.site.content.language; + delete request.data.site.page; + delete request.data.site.content.language; - expect(request.data.site.keywords).to.deep.equal('a,b,c'); - expect(request.data.user.keywords).to.deep.equal('d'); - expect(request.data.site.ext.data).to.deep.equal(expected.siteData); - expect(request.data.user.ext.data).to.deep.equal(expected.userData); - }); + expect(request.data.site.keywords).to.deep.equal('a,b,c'); + expect(request.data.user.keywords).to.deep.equal('d'); + expect(request.data.site.ext.data).to.deep.equal(expected.siteData); + expect(request.data.user.ext.data).to.deep.equal(expected.userData); + }); - it('should include pbadslot in bid request', function () { - createVideoBidderRequest(); - bidderRequest.bids[0].ortb2Imp = { - ext: { - data: { - pbadslot: '1234567890' + it('should include pbadslot in bid request', function () { + const bidderRequest = createVideoBidderRequest(); + bidderRequest.bids[0].ortb2Imp = { + ext: { + data: { + pbadslot: '1234567890' + } } } - } - sandbox.stub(Date, 'now').callsFake(() => - bidderRequest.auctionStart + 100 - ); + sandbox.stub(Date, 'now').callsFake(() => + bidderRequest.auctionStart + 100 + ); - const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(request.data.imp[0].ext.data.pbadslot).to.equal('1234567890'); - }); + const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(request.data.imp[0].ext.data.pbadslot).to.equal('1234567890'); + }); - it('should NOT include storedrequests in pbs payload', function () { - createVideoBidderRequest(); - bidderRequest.bids[0].ortb2 = { - ext: { - prebid: { - storedrequest: 'no-send-top-level-sr' + it('should NOT include storedrequests in pbs payload', function () { + const bidderRequest = createVideoBidderRequest(); + bidderRequest.bids[0].ortb2 = { + ext: { + prebid: { + storedrequest: 'no-send-top-level-sr' + } } } - } - bidderRequest.bids[0].ortb2Imp = { - ext: { - prebid: { - storedrequest: 'no-send-imp-sr' + bidderRequest.bids[0].ortb2Imp = { + ext: { + prebid: { + storedrequest: 'no-send-imp-sr' + } } } - } - sandbox.stub(Date, 'now').callsFake(() => - bidderRequest.auctionStart + 100 - ); + sandbox.stub(Date, 'now').callsFake(() => + bidderRequest.auctionStart + 100 + ); - const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(request.data.ext.prebid.storedrequest).to.be.undefined; - expect(request.data.imp[0].ext.prebid.storedrequest).to.be.undefined; - }); + const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(request.data.ext.prebid.storedrequest).to.be.undefined; + expect(request.data.imp[0].ext.prebid.storedrequest).to.be.undefined; + }); - it('should include GAM ad unit in bid request', function () { - createVideoBidderRequest(); - bidderRequest.bids[0].ortb2Imp = { - ext: { - data: { - adserver: { - adslot: '1234567890', - name: 'adServerName1' + it('should include GAM ad unit in bid request', function () { + const bidderRequest = createVideoBidderRequest(); + bidderRequest.bids[0].ortb2Imp = { + ext: { + data: { + adserver: { + adslot: '1234567890', + name: 'adServerName1' + } } } - } - }; + }; + + sandbox.stub(Date, 'now').callsFake(() => + bidderRequest.auctionStart + 100 + ); - sandbox.stub(Date, 'now').callsFake(() => - bidderRequest.auctionStart + 100 - ); + const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(request.data.imp[0].ext.data.adserver.adslot).to.equal('1234567890'); + expect(request.data.imp[0].ext.data.adserver.name).to.equal('adServerName1'); + }); - const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(request.data.imp[0].ext.data.adserver.adslot).to.equal('1234567890'); - expect(request.data.imp[0].ext.data.adserver.name).to.equal('adServerName1'); - }); + it('should use the integration type provided in the config instead of the default', () => { + const bidderRequest = createVideoBidderRequest(); + config.setConfig({rubicon: {int_type: 'testType'}}); + const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(request.data.ext.prebid.bidders.rubicon.integration).to.equal('testType'); + }); - it('should use the integration type provided in the config instead of the default', () => { - createVideoBidderRequest(); - config.setConfig({rubicon: {int_type: 'testType'}}); - const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(request.data.ext.prebid.bidders.rubicon.integration).to.equal('testType'); - }); + it('should pass the user.id provided in the config', function () { + config.setConfig({user: {id: '123'}}); + const bidderRequest = createVideoBidderRequest(); - it('should pass the user.id provided in the config', function () { - config.setConfig({user: {id: '123'}}); - createVideoBidderRequest(); + sandbox.stub(Date, 'now').callsFake(() => + bidderRequest.auctionStart + 100 + ); - sandbox.stub(Date, 'now').callsFake(() => - bidderRequest.auctionStart + 100 - ); + let [request] = spec.buildRequests(bidderRequest.bids, syncAddFPDToBidderRequest(bidderRequest)); + let post = request.data; - let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - let post = request.data; - - expect(post).to.have.property('imp') - // .with.length.of(1); - let imp = post.imp[0]; - expect(imp.id).to.equal(bidderRequest.bids[0].adUnitCode); - expect(imp.exp).to.equal(undefined); - expect(imp.video.w).to.equal(640); - expect(imp.video.h).to.equal(480); - expect(imp.video.pos).to.equal(1); - expect(imp.video.context).to.equal('instream'); - expect(imp.video.minduration).to.equal(15); - expect(imp.video.maxduration).to.equal(30); - expect(imp.video.startdelay).to.equal(0); - expect(imp.video.skip).to.equal(1); - expect(imp.video.skipafter).to.equal(15); - expect(imp.ext.rubicon.video.playerWidth).to.equal(640); - expect(imp.ext.rubicon.video.playerHeight).to.equal(480); - expect(imp.ext.rubicon.video.size_id).to.equal(201); - expect(imp.ext.rubicon.video.language).to.equal('en'); - // Also want it to be in post.site.content.language - expect(post.site.content.language).to.equal('en'); - expect(imp.ext.rubicon.video.skip).to.equal(1); - expect(imp.ext.rubicon.video.skipafter).to.equal(15); - expect(imp.ext.prebid.auctiontimestamp).to.equal(1472239426000); - expect(post.user.ext.consent).to.equal('BOJ/P2HOJ/P2HABABMAAAAAZ+A=='); - - // Config user.id - expect(post.user.id).to.equal('123'); - - expect(post.regs.ext.gdpr).to.equal(1); - expect(post.regs.ext.us_privacy).to.equal('1NYN'); - expect(post).to.have.property('ext').that.is.an('object'); - expect(post.ext.prebid.targeting.includewinners).to.equal(true); - expect(post.ext.prebid).to.have.property('cache').that.is.an('object'); - expect(post.ext.prebid.cache).to.have.property('vastxml').that.is.an('object'); - expect(post.ext.prebid.cache.vastxml).to.have.property('returnCreative').that.is.an('boolean'); - expect(post.ext.prebid.cache.vastxml.returnCreative).to.equal(false); - expect(post.ext.prebid.bidders.rubicon.integration).to.equal(PBS_INTEGRATION); - }) - }); + expect(post).to.have.property('imp') + // .with.length.of(1); + let imp = post.imp[0]; + expect(imp.id).to.equal(bidderRequest.bids[0].adUnitCode); + expect(imp.exp).to.equal(undefined); + expect(imp.video.w).to.equal(640); + expect(imp.video.h).to.equal(480); + expect(imp.video.pos).to.equal(1); + expect(imp.video.minduration).to.equal(15); + expect(imp.video.maxduration).to.equal(30); + expect(imp.video.startdelay).to.equal(0); + expect(imp.video.skip).to.equal(1); + expect(imp.video.skipafter).to.equal(15); + expect(imp.ext.prebid.bidder.rubicon.video.playerWidth).to.equal(640); + expect(imp.ext.prebid.bidder.rubicon.video.playerHeight).to.equal(480); + expect(imp.ext.prebid.bidder.rubicon.video.language).to.equal('en'); + + // Also want it to be in post.site.content.language + expect(post.site.content.language).to.equal('en'); + expect(post.ext.prebid.auctiontimestamp).to.equal(1472239426000); + expect(post.user.ext.consent).to.equal('BOJ/P2HOJ/P2HABABMAAAAAZ+A=='); + + // Config user.id + expect(post.user.id).to.equal('123'); + + expect(post.regs.ext.gdpr).to.equal(1); + expect(post.regs.ext.us_privacy).to.equal('1NYN'); + expect(post).to.have.property('ext').that.is.an('object'); + expect(post.ext.prebid.targeting.includewinners).to.equal(true); + expect(post.ext.prebid).to.have.property('cache').that.is.an('object'); + expect(post.ext.prebid.cache).to.have.property('vastxml').that.is.an('object'); + expect(post.ext.prebid.cache.vastxml).to.have.property('returnCreative').that.is.an('boolean'); + expect(post.ext.prebid.cache.vastxml.returnCreative).to.equal(false); + expect(post.ext.prebid.bidders.rubicon.integration).to.equal(PBS_INTEGRATION); + }) + }); + } describe('combineSlotUrlParams', function () { it('should combine an array of slot url params', function () { @@ -2278,6 +2326,7 @@ describe('the rubicon adapter', function () { describe('createSlotParams', function () { it('should return a valid slot params object', function () { + const localBidderRequest = Object.assign({}, bidderRequest); let expectedQuery = { 'account_id': '14062', 'site_id': '70608', @@ -2287,7 +2336,7 @@ describe('the rubicon adapter', function () { 'p_pos': 'atf', 'rp_secure': /[01]/, 'tk_flint': INTEGRATION, - 'x_source.tid': 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b', + 'x_source.tid': 'c45dd708-a418-42ec-b8a7-b70a6c6fab0a', 'p_screen_res': /\d+x\d+/, 'tk_user_key': '12346', 'kw': 'a,b,c', @@ -2300,7 +2349,7 @@ describe('the rubicon adapter', function () { 'rf': 'localhost' }; - const slotParams = spec.createSlotParams(bidderRequest.bids[0], bidderRequest); + const slotParams = spec.createSlotParams(bidderRequest.bids[0], localBidderRequest); // test that all values above are both present and correct Object.keys(expectedQuery).forEach(key => { @@ -2322,13 +2371,13 @@ describe('the rubicon adapter', function () { describe('classifiedAsVideo', function () { it('should return true if mediaTypes is video', function () { - createVideoBidderRequest(); + const bidderRequest = createVideoBidderRequest(); const bidClassifiedAsVideo = classifiedAsVideo(bidderRequest.bids[0]); expect(bidClassifiedAsVideo).is.equal(true); }); it('should return false if trying to use legacy mediaType with video', function () { - createVideoBidderRequest(); + const bidderRequest = createVideoBidderRequest(); delete bidderRequest.bids[0].mediaTypes; bidderRequest.bids[0].mediaType = 'video'; const legacyVideoTypeBidRequest = classifiedAsVideo(bidderRequest.bids[0]); @@ -2346,13 +2395,13 @@ describe('the rubicon adapter', function () { }); it('Should return false if both banner and video mediaTypes are set and params.video is not an object', function () { - createVideoBidderRequestNoVideo(); + removeVideoParamFromBidderRequest(bidderRequest); let bid = bidderRequest.bids[0]; bid.mediaTypes.banner = {flag: true}; expect(classifiedAsVideo(bid)).to.equal(false); }); it('Should return true if both banner and video mediaTypes are set and params.video is an object', function () { - createVideoBidderRequestNoVideo(); + removeVideoParamFromBidderRequest(bidderRequest); let bid = bidderRequest.bids[0]; bid.mediaTypes.banner = {flag: true}; bid.params.video = {}; @@ -2360,12 +2409,116 @@ describe('the rubicon adapter', function () { }); it('Should return true and create a params.video object if one is not already present', function () { - createVideoBidderRequestNoVideo(); + removeVideoParamFromBidderRequest(bidderRequest); let bid = bidderRequest.bids[0] expect(classifiedAsVideo(bid)).to.equal(true); expect(bid.params.video).to.not.be.undefined; }); }); + + if (FEATURES.NATIVE) { + describe('when there is a native request', function () { + describe('and bidonmultiformat = undefined (false)', () => { + it('should send only one native bid to PBS endpoint', function () { + const bidReq = addNativeToBidRequest(bidderRequest); + bidReq.bids[0].params = { + video: {} + } + let [request] = spec.buildRequests(bidReq.bids, bidReq); + expect(request.method).to.equal('POST'); + expect(request.url).to.equal('https://prebid-server.rubiconproject.com/openrtb2/auction'); + expect(request.data.imp).to.have.nested.property('[0].native'); + }); + + describe('that contains also a banner mediaType', function () { + it('should send the banner to fastlane BUT NOT the native bid because missing params.video', function() { + const bidReq = addNativeToBidRequest(bidderRequest); + bidReq.bids[0].mediaTypes.banner = { + sizes: [[300, 250]] + } + let [request] = spec.buildRequests(bidReq.bids, bidReq); + expect(request.method).to.equal('GET'); + expect(request.url).to.include('https://fastlane.rubiconproject.com/a/api/fastlane.json'); + }); + }); + describe('with another banner request', () => { + it('should send the native bid to PBS and the banner to fastlane', function() { + const bidReq = addNativeToBidRequest(bidderRequest); + bidReq.bids[0].params = { video: {} }; + // add second bidRqeuest + bidReq.bids.push({ + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + params: bidReq.bids[0].params + }) + let [request1, request2] = spec.buildRequests(bidReq.bids, bidReq); + expect(request1.method).to.equal('POST'); + expect(request1.url).to.equal('https://prebid-server.rubiconproject.com/openrtb2/auction'); + expect(request1.data.imp).to.have.nested.property('[0].native'); + expect(request2.method).to.equal('GET'); + expect(request2.url).to.include('https://fastlane.rubiconproject.com/a/api/fastlane.json'); + }); + }); + }); + + describe('with bidonmultiformat === true', () => { + it('should send two requests, to PBS with 2 imps', () => { + const bidReq = addNativeToBidRequest(bidderRequest); + // add second mediaType + bidReq.bids[0].mediaTypes = { + ...bidReq.bids[0].mediaTypes, + banner: { + sizes: [[300, 250]] + } + }; + bidReq.bids[0].params.bidonmultiformat = true; + let [pbsRequest, fastlanteRequest] = spec.buildRequests(bidReq.bids, bidReq); + expect(pbsRequest.method).to.equal('POST'); + expect(pbsRequest.url).to.equal('https://prebid-server.rubiconproject.com/openrtb2/auction'); + expect(pbsRequest.data.imp).to.have.nested.property('[0].native'); + expect(fastlanteRequest.url).to.equal('https://fastlane.rubiconproject.com/a/api/fastlane.json'); + }); + }); + describe('with bidonmultiformat === false', () => { + it('should send only banner request because there\'s no params.video', () => { + const bidReq = addNativeToBidRequest(bidderRequest); + // add second mediaType + bidReq.bids[0].mediaTypes = { + ...bidReq.bids[0].mediaTypes, + banner: { + sizes: [[300, 250]] + } + }; + + let [fastlanteRequest, ...others] = spec.buildRequests(bidReq.bids, bidReq); + expect(fastlanteRequest.url).to.equal('https://fastlane.rubiconproject.com/a/api/fastlane.json'); + expect(others).to.be.empty; + }); + + it('should not send native to PBS even if there\'s param.video', () => { + const bidReq = addNativeToBidRequest(bidderRequest); + // add second mediaType + bidReq.bids[0].mediaTypes = { + ...bidReq.bids[0].mediaTypes, + banner: { + sizes: [[300, 250]] + } + }; + // by adding this, when bidonmultiformat is false, the native request will be sent to pbs + bidReq.bids[0].params = { + video: {} + } + let [fastlaneRequest, ...other] = spec.buildRequests(bidReq.bids, bidReq); + expect(fastlaneRequest.method).to.equal('GET'); + expect(fastlaneRequest.url).to.equal('https://fastlane.rubiconproject.com/a/api/fastlane.json'); + expect(other).to.be.empty; + }); + }); + }); + } }); describe('interpretResponse', function () { @@ -3108,211 +3261,294 @@ describe('the rubicon adapter', function () { }); }); - describe('for video', function () { - beforeEach(function () { - createVideoBidderRequest(); - }); - - it('should register a successful bid', function () { - let response = { - cur: 'USD', - seatbid: [{ - bid: [{ - id: '0', - impid: 'instream_video1', - adomain: ['test.com'], - price: 2, - crid: '4259970', - ext: { - bidder: { - rp: { - mime: 'application/javascript', - size_id: 201, - advid: 12345 - } - }, - prebid: { - targeting: { - hb_uuid: '0c498f63-5111-4bed-98e2-9be7cb932a64' + if (FEATURES.VIDEO) { + describe('for video', function () { + it('should register a successful bid', function () { + const bidderRequest = createVideoBidderRequest(); + let response = { + cur: 'USD', + seatbid: [{ + bid: [{ + id: '0', + impid: '/19968336/header-bid-tag-0', + adomain: ['test.com'], + price: 2, + crid: '4259970', + ext: { + bidder: { + rp: { + mime: 'application/javascript', + size_id: 201, + advid: 12345 + } }, - type: 'video' + prebid: { + targeting: { + hb_uuid: '0c498f63-5111-4bed-98e2-9be7cb932a64' + }, + type: 'video' + } } - } + }], + group: 0, + seat: 'rubicon' }], - group: 0, - seat: 'rubicon' - }], - }; + }; - let bids = spec.interpretResponse({body: response}, { - bidRequest: bidderRequest.bids[0] - }); + const request = converter.toORTB({bidderRequest, bidRequests: bidderRequest.bids}); - expect(bids).to.be.lengthOf(1); + let bids = spec.interpretResponse({body: response}, {data: request}); - expect(bids[0].seatBidId).to.equal('0'); - expect(bids[0].creativeId).to.equal('4259970'); - expect(bids[0].cpm).to.equal(2); - expect(bids[0].ttl).to.equal(300); - expect(bids[0].netRevenue).to.equal(true); - expect(bids[0].adserverTargeting).to.deep.equal({hb_uuid: '0c498f63-5111-4bed-98e2-9be7cb932a64'}); - expect(bids[0].mediaType).to.equal('video'); - expect(bids[0].meta.mediaType).to.equal('video'); - expect(String(bids[0].meta.advertiserDomains)).to.equal('test.com'); - expect(bids[0].meta.advertiserId).to.equal(12345); - expect(bids[0].bidderCode).to.equal('rubicon'); - expect(bids[0].currency).to.equal('USD'); - expect(bids[0].width).to.equal(640); - expect(bids[0].height).to.equal(480); - }); - }); + expect(bids).to.be.lengthOf(1); - describe('for outstream video', function () { - const sandbox = sinon.createSandbox(); - beforeEach(function () { - createVideoBidderRequestOutstream(); - config.setConfig({rubicon: { - rendererConfig: { - align: 'left', - closeButton: true - }, - rendererUrl: 'https://example.test/renderer.js' - }}); - window.MagniteApex = { - renderAd: function() { - return null; - } - } + expect(bids[0].seatBidId).to.equal('0'); + expect(bids[0].creativeId).to.equal('4259970'); + expect(bids[0].cpm).to.equal(2); + expect(bids[0].ttl).to.equal(300); + expect(bids[0].netRevenue).to.equal(true); + expect(bids[0].adserverTargeting).to.deep.equal({hb_uuid: '0c498f63-5111-4bed-98e2-9be7cb932a64'}); + expect(bids[0].mediaType).to.equal('video'); + expect(bids[0].meta.mediaType).to.equal('video'); + expect(String(bids[0].meta.advertiserDomains)).to.equal('test.com'); + expect(bids[0].meta.advertiserId).to.equal(12345); + expect(bids[0].bidderCode).to.equal('rubicon'); + expect(bids[0].currency).to.equal('USD'); + expect(bids[0].width).to.equal(640); + expect(bids[0].height).to.equal(480); + }); }); + } - afterEach(function () { - sandbox.restore(); - delete window.MagniteApex; + if (FEATURES.NATIVE) { + describe('for native', () => { + it('should get a native bid', () => { + const nativeBidderRequest = addNativeToBidRequest(bidderRequest); + const request = converter.toORTB({bidderRequest: nativeBidderRequest, bidRequests: nativeBidderRequest.bids}); + let response = getNativeResponse({impid: request.imp[0].id}); + let bids = spec.interpretResponse({body: response}, {data: request}); + expect(bids).to.have.nested.property('[0].native'); + }); }); + } - it('should register a successful bid', function () { - let response = { - cur: 'USD', - seatbid: [{ - bid: [{ - id: '0', - impid: 'outstream_video1', - adomain: ['test.com'], - price: 2, - crid: '4259970', - ext: { - bidder: { - rp: { - mime: 'application/javascript', - size_id: 201, - advid: 12345 - } - }, - prebid: { - targeting: { - hb_uuid: '0c498f63-5111-4bed-98e2-9be7cb932a64' - }, - type: 'video' - } - } - }], - group: 0, - seat: 'rubicon' - }], - }; - - let bids = spec.interpretResponse({body: response}, { - bidRequest: bidderRequest.bids[0] + if (FEATURES.VIDEO) { + describe('for outstream video', function () { + const sandbox = sinon.createSandbox(); + beforeEach(function () { + config.setConfig({rubicon: { + rendererConfig: { + align: 'left', + closeButton: true + }, + rendererUrl: 'https://example.test/renderer.js' + }}); + window.MagniteApex = { + renderAd: function() { + return null; + } + } }); - expect(bids).to.be.lengthOf(1); + afterEach(function () { + sandbox.restore(); + delete window.MagniteApex; + }); - expect(bids[0].seatBidId).to.equal('0'); - expect(bids[0].creativeId).to.equal('4259970'); - expect(bids[0].cpm).to.equal(2); - expect(bids[0].ttl).to.equal(300); - expect(bids[0].netRevenue).to.equal(true); - expect(bids[0].adserverTargeting).to.deep.equal({hb_uuid: '0c498f63-5111-4bed-98e2-9be7cb932a64'}); - expect(bids[0].mediaType).to.equal('video'); - expect(bids[0].meta.mediaType).to.equal('video'); - expect(String(bids[0].meta.advertiserDomains)).to.equal('test.com'); - expect(bids[0].meta.advertiserId).to.equal(12345); - expect(bids[0].bidderCode).to.equal('rubicon'); - expect(bids[0].currency).to.equal('USD'); - expect(bids[0].width).to.equal(640); - expect(bids[0].height).to.equal(320); - // check custom renderer - expect(typeof bids[0].renderer).to.equal('object'); - expect(bids[0].renderer.getConfig()).to.deep.equal({ - align: 'left', - closeButton: true - }); - expect(bids[0].renderer.url).to.equal('https://example.test/renderer.js'); - }); + it('should register a successful bid', function () { + const bidderRequest = createVideoBidderRequestOutstream(); + let response = { + cur: 'USD', + seatbid: [{ + bid: [{ + id: '0', + impid: '/19968336/header-bid-tag-0', + adomain: ['test.com'], + price: 2, + crid: '4259970', + ext: { + bidder: { + rp: { + mime: 'application/javascript', + size_id: 201, + advid: 12345 + } + }, + prebid: { + targeting: { + hb_uuid: '0c498f63-5111-4bed-98e2-9be7cb932a64' + }, + type: 'video' + } + } + }], + group: 0, + seat: 'rubicon' + }], + }; - it('should render ad with Magnite renderer', function () { - let response = { - cur: 'USD', - seatbid: [{ - bid: [{ - id: '0', - impid: 'outstream_video1', - adomain: ['test.com'], - price: 2, - crid: '4259970', - ext: { - bidder: { - rp: { - mime: 'application/javascript', - size_id: 201, - advid: 12345 + const request = converter.toORTB({bidderRequest, bidRequests: bidderRequest.bids}); + + let bids = spec.interpretResponse({body: response}, { data: request }); + + expect(bids).to.be.lengthOf(1); + + expect(bids[0].seatBidId).to.equal('0'); + expect(bids[0].creativeId).to.equal('4259970'); + expect(bids[0].cpm).to.equal(2); + expect(bids[0].ttl).to.equal(300); + expect(bids[0].netRevenue).to.equal(true); + expect(bids[0].adserverTargeting).to.deep.equal({hb_uuid: '0c498f63-5111-4bed-98e2-9be7cb932a64'}); + expect(bids[0].mediaType).to.equal('video'); + expect(bids[0].meta.mediaType).to.equal('video'); + expect(String(bids[0].meta.advertiserDomains)).to.equal('test.com'); + expect(bids[0].meta.advertiserId).to.equal(12345); + expect(bids[0].bidderCode).to.equal('rubicon'); + expect(bids[0].currency).to.equal('USD'); + expect(bids[0].width).to.equal(640); + expect(bids[0].height).to.equal(320); + // check custom renderer + expect(typeof bids[0].renderer).to.equal('object'); + expect(bids[0].renderer.getConfig()).to.deep.equal({ + align: 'left', + closeButton: true + }); + expect(bids[0].renderer.url).to.equal('https://example.test/renderer.js'); + }); + + it('should render ad with Magnite renderer', function () { + const bidderRequest = createVideoBidderRequestOutstream(); + let response = { + cur: 'USD', + seatbid: [{ + bid: [{ + id: '0', + impid: '/19968336/header-bid-tag-0', + adomain: ['test.com'], + price: 2, + crid: '4259970', + ext: { + bidder: { + rp: { + mime: 'application/javascript', + size_id: 201, + advid: 12345 + } + }, + prebid: { + targeting: { + hb_uuid: '0c498f63-5111-4bed-98e2-9be7cb932a64' + }, + type: 'video' } }, - prebid: { - targeting: { - hb_uuid: '0c498f63-5111-4bed-98e2-9be7cb932a64' - }, - type: 'video' - } - }, - nurl: 'https://test.com/vast.xml' + nurl: 'https://test.com/vast.xml' + }], + group: 0, + seat: 'rubicon' }], - group: 0, - seat: 'rubicon' - }], - }; + }; - sinon.spy(window.MagniteApex, 'renderAd'); + const request = converter.toORTB({bidderRequest, bidRequests: bidderRequest.bids}); + + sinon.spy(window.MagniteApex, 'renderAd'); + + let bids = spec.interpretResponse({body: response}, {data: request}); + const bid = bids[0]; + bid.adUnitCode = 'outstream_video1_placement'; + const adUnit = document.createElement('div'); + adUnit.id = bid.adUnitCode; + document.body.appendChild(adUnit); + + bid.renderer.render(bid); + + const renderCall = window.MagniteApex.renderAd.getCall(0); + expect(renderCall.args[0]).to.deep.equal({ + closeButton: true, + collapse: true, + height: 320, + label: undefined, + placement: { + align: 'left', + attachTo: adUnit, + position: 'append', + }, + vastUrl: 'https://test.com/vast.xml', + width: 640 + }); + // cleanup + adUnit.parentNode.removeChild(adUnit); + }); + + it('should render ad with Magnite renderer without video object', function () { + const bidderRequest = createVideoBidderRequestOutstream(); + delete bidderRequest.bids[0].params.video; + bidderRequest.bids[0].params.bidonmultiformat = true; + bidderRequest.bids[0].mediaTypes.video.placement = 3; + bidderRequest.bids[0].mediaTypes.video.playerSize = [640, 480]; + + let response = { + cur: 'USD', + seatbid: [{ + bid: [{ + id: '0', + impid: '/19968336/header-bid-tag-0', + adomain: ['test.com'], + price: 2, + crid: '4259970', + ext: { + bidder: { + rp: { + mime: 'application/javascript', + size_id: 201, + advid: 12345 + } + }, + prebid: { + targeting: { + hb_uuid: '0c498f63-5111-4bed-98e2-9be7cb932a64' + }, + type: 'video' + } + }, + nurl: 'https://test.com/vast.xml' + }], + group: 0, + seat: 'rubicon' + }], + }; - let bids = spec.interpretResponse({body: response}, { - bidRequest: bidderRequest.bids[0] - }); - const bid = bids[0]; - bid.adUnitCode = 'outstream_video1_placement'; - const adUnit = document.createElement('div'); - adUnit.id = bid.adUnitCode; - document.body.appendChild(adUnit); - - bid.renderer.render(bid); - - const renderCall = window.MagniteApex.renderAd.getCall(0); - expect(renderCall.args[0]).to.deep.equal({ - closeButton: true, - collapse: true, - height: 320, - label: undefined, - placement: { - align: 'left', - attachTo: adUnit, - position: 'append', - }, - vastUrl: 'https://test.com/vast.xml', - width: 640 + const request = converter.toORTB({bidderRequest, bidRequests: bidderRequest.bids}); + + sinon.spy(window.MagniteApex, 'renderAd'); + + let bids = spec.interpretResponse({body: response}, {data: request}); + const bid = bids[0]; + bid.adUnitCode = 'outstream_video1_placement'; + const adUnit = document.createElement('div'); + adUnit.id = bid.adUnitCode; + document.body.appendChild(adUnit); + + bid.renderer.render(bid); + + const renderCall = window.MagniteApex.renderAd.getCall(0); + expect(renderCall.args[0]).to.deep.equal({ + closeButton: true, + collapse: true, + height: 480, + label: undefined, + placement: { + align: 'left', + attachTo: adUnit, + position: 'append', + }, + vastUrl: 'https://test.com/vast.xml', + width: 640 + }); + // cleanup + adUnit.parentNode.removeChild(adUnit); }); - // cleanup - adUnit.parentNode.removeChild(adUnit); }); - }); + } describe('config with integration type', () => { it('should use the integration type provided in the config instead of the default', () => { @@ -3440,6 +3676,24 @@ describe('the rubicon adapter', function () { type: 'iframe', url: `${emilyUrl}?gdpr=1&gdpr_consent=foo&us_privacy=1NYN` }); }); + + it('should pass gpp params when gppConsent is present', function () { + expect(spec.getUserSyncs({iframeEnabled: true}, {}, {}, undefined, { + gppString: 'foo', + applicableSections: [2] + })).to.deep.equal({ + type: 'iframe', url: `${emilyUrl}?gpp=foo&gpp_sid=2` + }); + }); + + it('should pass multiple sid\'s when multiple are present', function () { + expect(spec.getUserSyncs({iframeEnabled: true}, {}, {}, undefined, { + gppString: 'foo', + applicableSections: [2, 5] + })).to.deep.equal({ + type: 'iframe', url: `${emilyUrl}?gpp=foo&gpp_sid=2,5` + }); + }); }); describe('get price granularity', function () { @@ -3562,7 +3816,7 @@ describe('the rubicon adapter', function () { }); it('should copy the schain JSON to to bid.source.ext.schain', () => { - createVideoBidderRequest(); + const bidderRequest = createVideoBidderRequest(); const schain = getSupplyChainConfig(); bidderRequest.bids[0].schain = schain; const request = spec.buildRequests(bidderRequest.bids, bidderRequest); @@ -3598,12 +3852,13 @@ describe('the rubicon adapter', function () { }); // banner - let [bannerRequest] = spec.buildRequests(bidderRequest.bids, bidderRequest); + const bannerBidderRequest = createGdprBidderRequest(false); + let [bannerRequest] = spec.buildRequests(bannerBidderRequest.bids, bannerBidderRequest); expect(bannerRequest.url).to.equal('https://fastlane-qa.rubiconproject.com/a/api/fastlane.json'); // video and returnVast - createVideoBidderRequest(); - let [videoRequest] = spec.buildRequests(bidderRequest.bids, bidderRequest); + const videoBidderRequest = createVideoBidderRequest(); + let [videoRequest] = spec.buildRequests(videoBidderRequest.bids, videoBidderRequest); let post = videoRequest.data; expect(videoRequest.url).to.equal('https://prebid-server-qa.rubiconproject.com/openrtb2/auction'); expect(post.ext.prebid.cache.vastxml).to.have.property('returnCreative').that.is.an('boolean'); @@ -3617,3 +3872,164 @@ describe('the rubicon adapter', function () { }); }); }); + +function addNativeToBidRequest(bidderRequest) { + const nativeOrtbRequest = { + assets: [{ + id: 0, + required: 1, + title: { + len: 140 + } + }, + { + id: 1, + required: 1, + img: { + type: 3, + w: 300, + h: 600 + } + }, + { + id: 2, + required: 1, + data: { + type: 1 + } + }] + }; + bidderRequest.refererInfo = { + page: 'localhost' + } + bidderRequest.bids[0] = { + bidder: 'rubicon', + params: { + accountId: '14062', + siteId: '70608', + zoneId: '335918', + }, + adUnitCode: '/19968336/header-bid-tag-0', + code: 'div-1', + bidId: '2ffb201a808da7', + bidderRequestId: '178e34bad3658f', + auctionId: 'c45dd708-a418-42ec-b8a7-b70a6c6fab0a', + transactionId: 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b', + mediaTypes: { + native: { + ortb: { + ...nativeOrtbRequest + } + } + }, + nativeOrtbRequest + } + return bidderRequest; +} + +function getNativeResponse(options = {impid: 1234}) { + return { + 'id': 'd7786a80-bfb4-4541-859f-225a934e81d4', + 'seatbid': [ + { + 'bid': [ + { + 'id': '971650', + 'impid': options.impid, + 'price': 20, + 'adm': { + 'ver': '1.2', + 'assets': [ + { + 'id': 0, + 'title': { + 'text': 'This is a title' + }, + 'link': { + 'clicktrackers': [ + 'http://localhost:5500/event?type=click1&component=card&asset=0' + ] + } + }, + { + 'id': 1, + 'img': { + 'url': 'https:\\\\/\\\\/vcdn.adnxs.com\\\\/p\\\\/creative-image\\\\/94\\\\/22\\\\/cd\\\\/0f\\\\/9422cd0f-f400-45d3-80f5-2b92629d9257.jpg', + 'h': 2250, + 'w': 3000 + }, + 'link': { + 'clicktrackers': [ + 'http://localhost:5500/event?type=click1&component=card&asset=1' + ] + } + }, + { + 'id': 2, + 'data': { + 'value': 'this is asset data 1 that corresponds to sponsoredBy' + } + } + ], + 'link': { + 'url': 'https://magnite.com', + 'clicktrackers': [ + 'http://localhost:5500/event?type=click1&component=card', + 'http://localhost:5500/event?type=click2&component=card' + ] + }, + 'jstracker': '', + 'eventtrackers': [ + { + 'event': 1, + 'method': 2, + 'url': 'http://localhost:5500/event?type=1&method=2' + }, + { + 'event': 2, + 'method': 1, + 'url': 'http://localhost:5500/event?type=v50&component=card' + } + ] + }, + 'adid': '392180', + 'adomain': [ + 'http://prebid.org' + ], + 'iurl': 'https://lax1-ib.adnxs.com/cr?id=97494403', + 'cid': '9325', + 'crid': '97494403', + 'cat': [ + 'IAB3-1' + ], + 'w': 300, + 'h': 600, + 'ext': { + 'prebid': { + 'targeting': { + 'hb_bidder': 'rubicon', + 'hb_cache_host': 'prebid.lax1.adnxs-simple.com', + 'hb_cache_path': '/pbc/v1/cache', + 'hb_pb': '20.00' + }, + 'type': 'native', + 'video': { + 'duration': 0, + 'primary_category': '' + } + }, + 'rubicon': { + 'auction_id': 642778043863823100, + 'bid_ad_type': 3, + 'bidder_id': 2, + 'brand_id': 555545 + } + } + } + ], + 'seat': 'rubicon' + } + ], + 'cur': 'USD' + }; +} diff --git a/test/spec/modules/scatteredBidAdapter_spec.js b/test/spec/modules/scatteredBidAdapter_spec.js index 7f0b13bc07b..1db2d98d326 100644 --- a/test/spec/modules/scatteredBidAdapter_spec.js +++ b/test/spec/modules/scatteredBidAdapter_spec.js @@ -97,9 +97,7 @@ describe('Scattered adapter', function () { it('has the right fields filled', function () { let request = spec.buildRequests(arrayOfValidBidRequests, validBidderRequest); const bidderRequest = request.data; - assert.equal(bidderRequest.id, validBidderRequest.auctionId); assert.ok(bidderRequest.site); - assert.ok(bidderRequest.source); assert.lengthOf(bidderRequest.imp, 1); }); diff --git a/test/spec/modules/schain_spec.js b/test/spec/modules/schain_spec.js index 34d0cff9a60..eb8e35749db 100644 --- a/test/spec/modules/schain_spec.js +++ b/test/spec/modules/schain_spec.js @@ -469,6 +469,15 @@ describe('#makeBidRequestsHook', function() { makeBidRequestsHook(testCallback, testBidderRequests); }); + it('should not share the same schain object between different bid requests', (done) => { + config.setBidderConfig(goodStrictBidderConfig); + makeBidRequestsHook((requests) => { + requests[0].bids[0].schain.field = 'value'; + expect(requests[1].bids[0].schain.field).to.not.exist; + done(); + }, deepClone(bidderRequests)) + }); + it('should reject bad strict config but allow a bad relaxed config for bidders trying to override it', function () { function testCallback(bidderRequests) { expect(bidderRequests[0].bids[0].schain).to.exist; diff --git a/test/spec/modules/seedingAllianceAdapter_spec.js b/test/spec/modules/seedingAllianceAdapter_spec.js index 6086db01de4..03548cf923a 100755 --- a/test/spec/modules/seedingAllianceAdapter_spec.js +++ b/test/spec/modules/seedingAllianceAdapter_spec.js @@ -49,17 +49,6 @@ describe('SeedingAlliance adapter', function () { assert.deepEqual(keys, data); }); - it('Verify the auction ID', function () { - let validBidRequests = [{ - bidId: 'bidId', - params: {}, - auctionId: 'auctionId' - }]; - let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' }, auctionId: validBidRequests[0].auctionId }).data); - - assert.equal(request.id, validBidRequests[0].auctionId); - }); - it('Verify the site url', function () { let siteUrl = 'https://www.yourdomain.tld/your-directory/'; let validBidRequests = [{ @@ -189,7 +178,6 @@ describe('SeedingAlliance adapter', function () { const resultNative = spec.interpretResponse(goodNativeResponse, bidNativeRequest); const bidNative = goodNativeResponse.body.seatbid[0].bid[0]; - assert.deepEqual(resultNative[0].bidderCode, 'seedingAlliance'); assert.deepEqual(resultNative[0].currency, goodNativeResponse.body.cur); assert.deepEqual(resultNative[0].requestId, bidNativeRequest.bidRequests[0].bidId); assert.deepEqual(resultNative[0].cpm, bidNative.price); @@ -198,7 +186,6 @@ describe('SeedingAlliance adapter', function () { const resultBanner = spec.interpretResponse(goodBannerResponse, bidBannerRequest); - assert.deepEqual(resultBanner[0].bidderCode, 'seedingAlliance'); assert.deepEqual(resultBanner[0].mediaType, 'banner'); assert.deepEqual(resultBanner[0].width, bidBannerRequest.bidRequests[0].sizes[0]); assert.deepEqual(resultBanner[0].height, bidBannerRequest.bidRequests[0].sizes[1]); diff --git a/test/spec/modules/seedtagBidAdapter_spec.js b/test/spec/modules/seedtagBidAdapter_spec.js index 3627296975b..fb666e89f73 100644 --- a/test/spec/modules/seedtagBidAdapter_spec.js +++ b/test/spec/modules/seedtagBidAdapter_spec.js @@ -20,7 +20,11 @@ function getSlotConfigs(mediaTypes, params) { bidder: 'seedtag', mediaTypes: mediaTypes, src: 'client', - transactionId: 'd704d006-0d6e-4a09-ad6c-179e7e758096', + ortb2Imp: { + ext: { + tid: 'd704d006-0d6e-4a09-ad6c-179e7e758096', + } + }, adUnitCode: 'adunit-code', }; } diff --git a/test/spec/modules/sharedIdSystem_spec.js b/test/spec/modules/sharedIdSystem_spec.js index 8ef34a1599e..fcfbe5f7c3f 100644 --- a/test/spec/modules/sharedIdSystem_spec.js +++ b/test/spec/modules/sharedIdSystem_spec.js @@ -91,50 +91,4 @@ describe('SharedId System', function () { expect(result).to.be.undefined; }); }); - - describe('SharedID System domainOverride', () => { - let sandbox, domain, cookies, rejectCookiesFor; - - beforeEach(() => { - sandbox = sinon.createSandbox(); - sandbox.stub(document, 'domain').get(() => domain); - cookies = {}; - sandbox.stub(storage, 'getCookie').callsFake((key) => cookies[key]); - rejectCookiesFor = null; - sandbox.stub(storage, 'setCookie').callsFake((key, value, expires, sameSite, domain) => { - if (domain !== rejectCookiesFor) { - if (expires != null) { - expires = new Date(expires); - } - if (expires == null || expires > Date.now()) { - cookies[key] = value; - } else { - delete cookies[key]; - } - } - }); - }); - - afterEach(() => { - sandbox.restore(); - }); - - it('should return TLD if cookies can be set there', () => { - domain = 'sub.domain.com'; - rejectCookiesFor = 'com'; - expect(sharedIdSystemSubmodule.domainOverride()).to.equal('domain.com'); - }); - - it('should return undefined when cookies cannot be set', () => { - domain = 'sub.domain.com'; - rejectCookiesFor = 'sub.domain.com'; - expect(sharedIdSystemSubmodule.domainOverride()).to.be.undefined; - }); - - it('should return half-way domain if parent domain rejects cookies', () => { - domain = 'inner.outer.domain.com'; - rejectCookiesFor = 'domain.com'; - expect(sharedIdSystemSubmodule.domainOverride()).to.equal('outer.domain.com'); - }); - }); }); diff --git a/test/spec/modules/sharethroughBidAdapter_spec.js b/test/spec/modules/sharethroughBidAdapter_spec.js index 56e10a74240..4989dcb1098 100644 --- a/test/spec/modules/sharethroughBidAdapter_spec.js +++ b/test/spec/modules/sharethroughBidAdapter_spec.js @@ -93,31 +93,107 @@ describe('sharethrough adapter spec', function () { }, }, }, - userId: { - tdid: 'fake-tdid', - pubcid: 'fake-pubcid', - idl_env: 'fake-identity-link', - id5id: { - uid: 'fake-id5id', - ext: { - linkType: 2, - }, + userIdAsEids: [ + { + 'source': 'pubcid.org', + 'uids': [ + { + 'atype': 1, + 'id': 'fake-pubcid' + }, + ] }, - lipb: { - lipbid: 'fake-lipbid', + { + 'source': 'liveramp.com', + 'uids': [ + { + 'atype': 1, + 'id': 'fake-identity-link' + } + ] }, - criteoId: 'fake-criteo', - britepoolid: 'fake-britepool', - intentIqId: 'fake-intentiq', - lotamePanoramaId: 'fake-lotame', - parrableId: { - eid: 'fake-parrable', + { + 'source': 'id5-sync.com', + 'uids': [ + { + 'atype': 1, + 'id': 'fake-id5id' + } + ] }, - netId: 'fake-netid', - sharedid: { - id: 'fake-sharedid', + { + 'source': 'adserver.org', + 'uids': [ + { + 'atype': 1, + 'id': 'fake-tdid' + } + ] }, - }, + { + 'source': 'criteo.com', + 'uids': [ + { + 'atype': 1, + 'id': 'fake-criteo' + } + ] + }, + { + 'source': 'britepool.com', + 'uids': [ + { + 'atype': 1, + 'id': 'fake-britepool' + } + ] + }, + { + 'source': 'liveintent.com', + 'uids': [ + { + 'atype': 1, + 'id': 'fake-lipbid' + } + ] + }, + { + 'source': 'intentiq.com', + 'uids': [ + { + 'atype': 1, + 'id': 'fake-intentiq' + } + ] + }, + { + 'source': 'crwdcntrl.net', + 'uids': [ + { + 'atype': 1, + 'id': 'fake-lotame' + } + ] + }, + { + 'source': 'parrable.com', + 'uids': [ + { + 'atype': 1, + 'id': 'fake-parrable' + } + ] + }, + { + 'source': 'netid.de', + 'uids': [ + { + 'atype': 1, + 'id': 'fake-netid' + } + ] + } + ], crumbs: { pubcid: 'fake-pubcid-in-crumbs-obj', }, @@ -154,7 +230,7 @@ describe('sharethrough adapter spec', function () { api: [3], mimes: ['video/3gpp'], protocols: [2, 3], - playerSize: [640, 480], + playerSize: [[640, 480]], startdelay: 42, skipmin: 10, skipafter: 20, @@ -172,7 +248,12 @@ describe('sharethrough adapter spec', function () { refererInfo: { ref: 'https://referer.com', }, - auctionId: 'auction-id' + ortb2: { + source: { + tid: 'auction-id' + } + }, + timeout: 242 }; }); @@ -234,7 +315,7 @@ describe('sharethrough adapter spec', function () { expect(openRtbReq.device.ua).to.equal(navigator.userAgent); expect(openRtbReq.regs.coppa).to.equal(1); - expect(openRtbReq.source.tid).to.equal(bidderRequest.auctionId); + expect(openRtbReq.source.tid).to.equal(bidderRequest.ortb2.source.tid); expect(openRtbReq.source.ext.version).not.to.be.undefined; expect(openRtbReq.source.ext.str).not.to.be.undefined; expect(openRtbReq.source.ext.schain).to.deep.equal(bidRequests[0].schain); @@ -625,6 +706,81 @@ describe('sharethrough adapter spec', function () { expect(bannerBid.vastXml).to.equal('vastTag'); }); }); + + describe('meta object', () => { + beforeEach(() => { + request = spec.buildRequests(bidRequests, bidderRequest)[0]; + response = { + body: { + seatbid: [{ + bid: [{ + id: '123', + impid: 'bidId1', + w: 300, + h: 250, + price: 42, + crid: 'creative', + dealid: 'deal', + adomain: ['domain.com'], + adm: 'markup', + }], + }], + }, + }; + }); + + it('should have null optional fields when the response\'s optional seatbid[].bid[].ext field is empty', () => { + const bid = spec.interpretResponse(response, request)[0]; + + expect(bid.meta.networkId).to.be.null; + expect(bid.meta.networkName).to.be.null; + expect(bid.meta.agencyId).to.be.null; + expect(bid.meta.agencyName).to.be.null; + expect(bid.meta.advertiserId).to.be.null; + expect(bid.meta.advertiserName).to.be.null; + expect(bid.meta.brandId).to.be.null; + expect(bid.meta.brandName).to.be.null; + expect(bid.meta.demandSource).to.be.null; + expect(bid.meta.dchain).to.be.null; + expect(bid.meta.primaryCatId).to.be.null; + expect(bid.meta.secondaryCatIds).to.be.null; + expect(bid.meta.mediaType).to.be.null; + }); + + it('should have populated fields when the response\'s optional seatbid[].bid[].ext fields are filled', () => { + response.body.seatbid[0].bid[0].ext = { + networkId: 'my network id', + networkName: 'my network name', + agencyId: 'my agency id', + agencyName: 'my agency name', + advertiserId: 'my advertiser id', + advertiserName: 'my advertiser name', + brandId: 'my brand id', + brandName: 'my brand name', + demandSource: 'my demand source', + dchain: { 'my key': 'my value' }, + primaryCatId: 'my primary cat id', + secondaryCatIds: ['my', 'secondary', 'cat', 'ids'], + mediaType: 'my media type', + }; + + const bid = spec.interpretResponse(response, request)[0]; + + expect(bid.meta.networkId).to.equal('my network id'); + expect(bid.meta.networkName).to.equal('my network name'); + expect(bid.meta.agencyId).to.equal('my agency id'); + expect(bid.meta.agencyName).to.equal('my agency name'); + expect(bid.meta.advertiserId).to.equal('my advertiser id'); + expect(bid.meta.advertiserName).to.equal('my advertiser name'); + expect(bid.meta.brandId).to.equal('my brand id'); + expect(bid.meta.brandName).to.equal('my brand name'); + expect(bid.meta.demandSource).to.equal('my demand source'); + expect(bid.meta.dchain).to.deep.equal({ 'my key': 'my value' }); + expect(bid.meta.primaryCatId).to.equal('my primary cat id'); + expect(bid.meta.secondaryCatIds).to.deep.equal(['my', 'secondary', 'cat', 'ids']); + expect(bid.meta.mediaType).to.equal('my media type'); + }); + }); }); describe('getUserSyncs', function () { diff --git a/test/spec/modules/showheroes-bsBidAdapter_spec.js b/test/spec/modules/showheroes-bsBidAdapter_spec.js index 1aa7b132221..30e95b04ccf 100644 --- a/test/spec/modules/showheroes-bsBidAdapter_spec.js +++ b/test/spec/modules/showheroes-bsBidAdapter_spec.js @@ -5,7 +5,7 @@ import {VIDEO, BANNER} from 'src/mediaTypes.js' const bidderRequest = { refererInfo: { - referer: 'https://example.com' + canonicalUrl: 'https://example.com' } } @@ -19,6 +19,8 @@ const gdpr = { } } +const uspConsent = '1---'; + const schain = { 'schain': { 'validation': 'strict', @@ -338,18 +340,28 @@ describe('shBidAdapter', function () { }); }) - it('passes gdpr if present', function () { - const request = spec.buildRequests([bidRequestVideo], {...bidderRequest, ...gdpr}) + it('passes gdpr & uspConsent if present', function () { + const request = spec.buildRequests([bidRequestVideo], { + ...bidderRequest, + ...gdpr, + uspConsent, + }) const payload = request.data.requests[0]; expect(payload).to.be.an('object'); expect(payload.gdprConsent).to.eql(gdpr.gdprConsent) + expect(payload.uspConsent).to.eql(uspConsent) }) - it('passes gdpr if present (V2)', function () { - const request = spec.buildRequests([bidRequestVideoV2], {...bidderRequest, ...gdpr}) + it('passes gdpr & usp if present (V2)', function () { + const request = spec.buildRequests([bidRequestVideoV2], { + ...bidderRequest, + ...gdpr, + uspConsent, + }) const context = request.data.context; expect(context).to.be.an('object'); expect(context.gdprConsent).to.eql(gdpr.gdprConsent) + expect(context.uspConsent).to.eql(uspConsent) }) it('passes schain object if present', function() { @@ -379,7 +391,7 @@ describe('shBidAdapter', function () { expect(spec.interpretResponse({body: []}, {data: {meta: {}}}).length).to.equal(0) }) - const vastTag = 'https://video-library.stage.showheroes.com/commercial/wrapper?player_id=47427aa0-f11a-4d24-abca-1295a46a46cd&ad_bidder=showheroes-bs&master_shadt=1&description_url=https%3A%2F%2Fbid-service.stage.showheroes.com%2Fvast%2Fad%2Fcache%2F4840b920-40e1-4e09-9231-60bbf088c8d6' + const vastTag = 'https://test.com/commercial/wrapper?player_id=47427aa0-f11a-4d24-abca-1295a46a46cd&ad_bidder=showheroes-bs&master_shadt=1&description_url=https%3A%2F%2Fbid-service.stage.showheroes.com%2Fvast%2Fad%2Fcache%2F4840b920-40e1-4e09-9231-60bbf088c8d6' const vastXml = '' const basicResponse = { @@ -389,7 +401,7 @@ describe('shBidAdapter', function () { 'context': 'instream', 'bidId': '38b373e1e31c18', 'size': {'width': 640, 'height': 480}, - 'vastTag': 'https:\/\/video-library.stage.showheroes.com\/commercial\/wrapper?player_id=47427aa0-f11a-4d24-abca-1295a46a46cd&ad_bidder=showheroes-bs&master_shadt=1&description_url=https%3A%2F%2Fbid-service.stage.showheroes.com%2Fvast%2Fad%2Fcache%2F4840b920-40e1-4e09-9231-60bbf088c8d6', + 'vastTag': 'https:\/\/test.com\/commercial\/wrapper?player_id=47427aa0-f11a-4d24-abca-1295a46a46cd&ad_bidder=showheroes-bs&master_shadt=1&description_url=https%3A%2F%2Fbid-service.stage.showheroes.com%2Fvast%2Fad%2Fcache%2F4840b920-40e1-4e09-9231-60bbf088c8d6', 'vastXml': vastXml, 'adomain': adomain, }; @@ -423,13 +435,13 @@ describe('shBidAdapter', function () { 'height': 480, 'advertiserDomain': [], 'callbacks': { - 'won': ['https://api-n729.qa.viralize.com/track/?ver=15&session_id=01ecd03ce381505ccdeb88e555b05001&category=request_session&type=event&request_session_id=01ecd03ce381505ccdeb88e555b05001&label=prebid_won&reason=ok'] + 'won': ['https://test.com/track/?ver=15&session_id=01ecd03ce381505ccdeb88e555b05001&category=request_session&type=event&request_session_id=01ecd03ce381505ccdeb88e555b05001&label=prebid_won&reason=ok'] }, 'mediaType': 'video', 'adomain': adomain, }; - const vastUrl = 'https://api-n729.qa.viralize.com/vast/?zid=AACBWAcof-611K4U&u=https://example.org/?foo=bar&gdpr=0&cs=XXXXXXXXXXXXXXXXXXXX&sid=01ecd03ce381505ccdeb88e555b05001&width=300&height=200&prebidmode=1' + const vastUrl = 'https://test.com/vast/?zid=AACBWAcof-611K4U&u=https://example.org/?foo=bar&gdpr=0&cs=XXXXXXXXXXXXXXXXXXXX&sid=01ecd03ce381505ccdeb88e555b05001&width=300&height=200&prebidmode=1' const responseVideoV2 = { 'bidResponses': [{ @@ -443,7 +455,7 @@ describe('shBidAdapter', function () { 'bidResponses': [{ ...basicResponseV2, 'context': 'outstream', - 'ad': '', + 'ad': '', }], }; @@ -533,10 +545,6 @@ describe('shBidAdapter', function () { expect(renderer.config.vastUrl).to.equal(vastTag) renderer.render(bid) - // TODO: fix these. our tests should not be reliant on third-party scripts. wtf - // const scripts = document.querySelectorAll('script[src="https://static.showheroes.com/publishertag.js"]') - // expect(scripts.length).to.equal(1) - const spots = document.querySelectorAll('.showheroes-spot') expect(spots.length).to.equal(1) }) @@ -590,8 +598,6 @@ describe('shBidAdapter', function () { renderer.render(bid) const iframeDocument = iframe.contentDocument || (iframe.contentWindow && iframe.contentWindow.document) - // const scripts = iframeDocument.querySelectorAll('script[src="https://static.showheroes.com/publishertag.js"]') - // expect(scripts.length).to.equal(1) const spots = iframeDocument.querySelectorAll('.showheroes-spot') expect(spots.length).to.equal(1) }) @@ -602,8 +608,6 @@ describe('shBidAdapter', function () { customRender: function (bid, embedCode) { const container = document.createElement('div') container.appendChild(embedCode) - // const scripts = container.querySelectorAll('script[src="https://static.showheroes.com/publishertag.js"]') - // expect(scripts.length).to.equal(1) const spots = container.querySelectorAll('.showheroes-spot') expect(spots.length).to.equal(1) diff --git a/test/spec/modules/silverpushBidAdapter_spec.js b/test/spec/modules/silverpushBidAdapter_spec.js new file mode 100644 index 00000000000..de31135eabe --- /dev/null +++ b/test/spec/modules/silverpushBidAdapter_spec.js @@ -0,0 +1,394 @@ +import { expect } from 'chai'; +import * as utils from 'src/utils'; +import { newBidder } from 'src/adapters/bidderFactory.js'; +import { REQUEST_URL, SP_OUTSTREAM_PLAYER_URL, CONVERTER, spec } from '../../../modules/silverpushBidAdapter.js'; + +const bannerBid = { + bidder: 'silverpush', + params: { + publisherId: '012345', + bidFloor: 1.5 + }, + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 50], + ], + }, + }, + adUnitCode: 'div-gpt-ad-928572628472-0', + bidId: 'dl38fjf9d', + bidderRequestId: 'brid00000000', + auctionId: 'aucid0000000', +}; + +const videoBid = { + bidder: 'silverpush', + params: { + publisherId: '012345', + bidFloor: 0.1 + }, + mediaTypes: { + video: { + api: [1, 2, 4, 6], + mimes: ['video/mp4'], + playbackmethod: [2, 4, 6], + playerSize: [[1024, 768]], + protocols: [3, 4, 7, 8, 10], + placement: 1, + minduration: 0, + maxduration: 60, + startdelay: 0 + }, + }, + adUnitCode: 'div-gpt-ad-928572628472-1', + bidId: '281141d3541362', + bidderRequestId: 'brid00000000', + auctionId: 'aucid0000000', +}; + +const bidderRequest = { + auctionId: 'aucid0000000', + bidderRequestId: 'brid00000000', + timeout: 200, + refererInfo: { + page: 'https://hello-world-page.com/', + domain: 'hello-world-page.com', + ref: 'http://example-domain.com/foo', + } +}; + +const bannerReponse = { + 'id': 'brid00000000', + 'seatbid': [ + { + 'bid': [ + { + 'id': 'ARUYoUZx', + 'impid': 'dl38fjf9d', + 'price': 1.64, + 'adid': 'aaaaadddddddd', + 'burl': 'http://0.0.0.0:8181/burl', + 'adm': '
', + 'adomain': [ + 'https://www.exampleabc.com' + ], + 'iurl': 'https://example.example.com/2.png', + 'cid': 'aaaaadddddddd', + 'crid': 'aaaaadddddddd', + 'h': 250, + 'w': 300 + } + ] + } + ], + 'bidid': 'ARUYoUZx', + 'cur': 'USD' +} + +const videoResponse = { + 'id': 'brid00000000', + 'seatbid': [ + { + 'bid': [ + { + 'id': 'soCWeklh', + 'impid': '281141d3541362', + 'price': 1.09, + 'adid': 'outstream_video', + 'burl': 'http://0.0.0.0:8181/burl', + 'adm': '\n', + 'adomain': [ + 'https://www.exampleabc.com' + ], + 'cid': '229369649', + 'crid': 'aaaaadddddddd', + 'h': 768, + 'w': 1024 + } + ] + } + ], + 'bidid': 'soCWeklh', + 'cur': 'USD' +} + +describe('Silverpush Adapter', function () { + describe('isBidRequestValid()', () => { + it('should return false when publisherId is not defined', () => { + const bid = utils.deepClone(bannerBid); + delete bid.params.publisherId; + + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + + it('should return false when publisherId is empty string', () => { + const bid = utils.deepClone(bannerBid); + bid.params.publisherId = ''; + + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + + it('should return false when publisherId is a number', () => { + const bid = utils.deepClone(bannerBid); + bid.params.publisherId = 12345; + + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + + it('should return false when there is no banner in mediaTypes', () => { + const bid = utils.deepClone(bannerBid); + delete bid.mediaTypes.banner; + + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when sizes for banner are not specified', () => { + const bid = utils.deepClone(bannerBid); + delete bid.mediaTypes.banner.sizes; + + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when there is no video in mediaTypes', () => { + const bid = utils.deepClone(videoBid); + delete bid.mediaTypes.video; + + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should reutrn false if player size is not set', () => { + const bid = utils.deepClone(videoBid); + delete bid.mediaTypes.video.playerSize; + + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return true when required params found', () => { + expect(spec.isBidRequestValid(bannerBid)).to.equal(true); + expect(spec.isBidRequestValid(videoBid)).to.equal(true); + }); + }); + + describe('buildRequests()', () => { + it('should build correct request for banner bid with both w, h', () => { + const bid = utils.deepClone(bannerBid); + + const [request] = spec.buildRequests([bid], bidderRequest); + const requestData = request.data; + + expect(requestData.imp[0].banner.w).to.equal(300); + expect(requestData.imp[0].banner.h).to.equal(250); + }); + + it('should return default bidfloor when bidFloor is not defined', () => { + const bid = utils.deepClone(bannerBid); + delete bid.params.bidFloor; + + const [request] = spec.buildRequests([bid], bidderRequest); + const requestData = request.data; + + expect(requestData.imp[0].bidfloor).to.equal(0.05); + }); + + it('should contain deals in request if deal is specified in params', () => { + const bid = utils.deepClone(bannerBid); + bid.params.deals = [{ id: 'test' }]; + + const [request] = spec.buildRequests([bid], bidderRequest); + const requestData = request.data; + + expect(requestData.imp[0].pmp.deals).to.equal(bid.params.deals); + }); + + it('should return bidfloor when bidFloor is defined', () => { + const bid = utils.deepClone(bannerBid); + + const [request] = spec.buildRequests([bid], bidderRequest); + const requestData = request.data; + + expect(requestData.imp[0].bidfloor).to.equal(bannerBid.params.bidFloor); + }); + + it('should build correct request for video bid with playerSize', () => { + const bid = utils.deepClone(videoBid); + + const [request] = spec.buildRequests([bid], bidderRequest); + const requestData = request.data; + + expect(requestData.imp[0].video.w).to.equal(1024); + expect(requestData.imp[0].video.h).to.equal(768); + }); + + it('should use bidder video params if they are set', () => { + const videoBidWithParams = utils.deepClone(videoBid); + const bidderVideoParams = { + api: [1, 2], + mimes: ['video/mp4', 'video/x-flv'], + playbackmethod: [3, 4], + protocols: [5, 6], + placement: 1, + minduration: 0, + maxduration: 60, + w: 1024, + h: 768, + startdelay: 0 + }; + + videoBidWithParams.params.video = bidderVideoParams; + + const requests = spec.buildRequests([videoBidWithParams], bidderRequest); + const request = requests[0].data; + + expect(request.imp[0]).to.deep.include({ + video: { + ...bidderVideoParams, + w: videoBidWithParams.mediaTypes.video.playerSize[0][0], + h: videoBidWithParams.mediaTypes.video.playerSize[0][1], + }, + }); + }); + }); + + describe('getOS()', () => { + it('shold return correct os name for Windows', () => { + let userAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.246'; + let osName = spec.getOS(userAgent); + + expect(osName).to.equal('Windows'); + }); + + it('shold return correct os name for Mac OS', () => { + let userAgent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_2) AppleWebKit/601.3.9 (KHTML, like Gecko) Version/9.0.2 Safari/601.3.9'; + let osName = spec.getOS(userAgent); + + expect(osName).to.equal('macOS'); + }); + + it('shold return correct os name for Android', () => { + let userAgent = 'Mozilla/5.0 (Linux; Android 10; SM-G996U Build/QP1A.190711.020; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Mobile Safari/537.36'; + let osName = spec.getOS(userAgent); + + expect(osName).to.equal('Android'); + }); + + it('shold return correct os name for ios', () => { + let userAgent = 'Mozilla/5.0 (iPhone14,3; U; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/602.1.50 (KHTML, like Gecko) Version/10.0 Mobile/19A346 Safari/602.1'; + let osName = spec.getOS(userAgent); + + expect(osName).to.equal('iOS'); + }); + + it('shold return correct os name for Linux', () => { + let userAgent = 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:15.0) Gecko/20100101 Firefox/15.0.1'; + let osName = spec.getOS(userAgent); + + expect(osName).to.equal('Linux'); + }); + }); + + describe('interpretResponse()', () => { + it('should return nbr to 0 when response not received', () => { + const requests = spec.buildRequests([bannerBid], bidderRequest); + const bids = spec.interpretResponse({ body: null }, requests[0]); + + expect(bids[0]).to.equal(undefined); + }); + + it('should correctly interpret valid banner response', () => { + const response = utils.deepClone(bannerReponse); + const requests = spec.buildRequests([bannerBid], bidderRequest); + const bids = spec.interpretResponse({ body: response }, requests[0]); + + expect(bids[0].ad).to.equal('
'); + expect(bids[0].burl).to.equal('http://0.0.0.0:8181/burl'); + expect(bids[0].cpm).to.equal(1.64); + expect(bids[0].creativeId).to.equal('aaaaadddddddd'); + expect(bids[0].creative_id).to.equal('aaaaadddddddd'); + expect(bids[0].currency).to.equal('USD'); + expect(bids[0].height).to.equal(250); + expect(bids[0].mediaType).to.equal('banner'); + expect(bids[0].meta.advertiserDomains[0]).to.equal('https://www.exampleabc.com'); + expect(bids[0].netRevenue).to.equal(true); + expect(bids[0].requestId).to.equal('dl38fjf9d'); + expect(bids[0].seatBidId).to.equal('ARUYoUZx'); + expect(bids[0].ttl).to.equal(300); + expect(bids[0].width).to.equal(300); + }); + + if (FEATURES.VIDEO) { + it('should correctly interpret valid instream video response', () => { + const response = utils.deepClone(videoResponse); + videoBid.mediaTypes.video.context = 'outstream'; + const requests = spec.buildRequests([videoBid], bidderRequest); + const bids = spec.interpretResponse({ body: response }, requests[0]); + + expect(bids[0].vastXml).to.equal('\n'); + expect(bids[0].burl).to.equal('http://0.0.0.0:8181/burl'); + expect(bids[0].cpm).to.equal(1.09); + expect(bids[0].creativeId).to.equal('aaaaadddddddd'); + expect(bids[0].currency).to.equal('USD'); + expect(bids[0].mediaType).to.equal('video'); + expect(bids[0].meta.advertiserDomains[0]).to.equal('https://www.exampleabc.com'); + expect(bids[0].requestId).to.equal('281141d3541362'); + expect(bids[0].seatBidId).to.equal('soCWeklh'); + expect(bids[0].width).to.equal(1024); + expect(bids[0].height).to.equal(768); + }); + + it('should correctly interpret valid outstream video response', () => { + const response = utils.deepClone(videoResponse); + videoBid.mediaTypes.video.context = 'outstream'; + + const requests = spec.buildRequests([videoBid], bidderRequest); + const bids = spec.interpretResponse({ body: response }, requests[0]); + + expect(bids[0].vastXml).to.equal('\n'); + expect(bids[0].rendererUrl).to.equal(SP_OUTSTREAM_PLAYER_URL); + expect(bids[0].renderer.url).to.equal(SP_OUTSTREAM_PLAYER_URL); + expect(bids[0].burl).to.equal('http://0.0.0.0:8181/burl'); + expect(bids[0].cpm).to.equal(1.09); + expect(bids[0].creativeId).to.equal('aaaaadddddddd'); + expect(bids[0].currency).to.equal('USD'); + expect(bids[0].mediaType).to.equal('video'); + expect(bids[0].meta.advertiserDomains[0]).to.equal('https://www.exampleabc.com'); + expect(bids[0].requestId).to.equal('281141d3541362'); + expect(bids[0].seatBidId).to.equal('soCWeklh'); + expect(bids[0].width).to.equal(1024); + expect(bids[0].height).to.equal(768); + }); + } + }); + + describe('onBidWon', function() { + let ajaxStub; + + beforeEach(() => { + ajaxStub = sinon.stub(spec, 'getRequest') + }) + + afterEach(() => { + ajaxStub.restore() + }) + + it('Should not trigger pixel if bid does not contain burl', function() { + const result = spec.onBidWon({}); + + expect(ajaxStub.calledOnce).to.equal(false); + }) + + it('Should trigger pixel with correct macros if bid burl is present', function() { + const result = spec.onBidWon({ + cpm: 1.5, + auctionId: 'auc123', + requestId: 'req123', + adId: 'ad1234', + seatBidId: 'sea123', + burl: 'http://won.foo.bar/trk?ap=${AUCTION_PRICE}&aid=${AUCTION_ID}&imp=${AUCTION_IMP_ID}&adid=${AUCTION_AD_ID}&sid=${AUCTION_SEAT_ID}' + }); + + expect(ajaxStub.calledOnceWith('http://won.foo.bar/trk?ap=1.5&aid=auc123&imp=req123&adid=ad1234&sid=sea123')).to.equal(true); + }) + }) +}); diff --git a/test/spec/modules/sirdataRtdProvider_spec.js b/test/spec/modules/sirdataRtdProvider_spec.js index eccc4777906..fbb5967bc20 100644 --- a/test/spec/modules/sirdataRtdProvider_spec.js +++ b/test/spec/modules/sirdataRtdProvider_spec.js @@ -1,26 +1,31 @@ -import {addSegmentData, getSegmentsAndCategories, sirdataSubmodule} from 'modules/sirdataRtdProvider.js'; +import {addSegmentData, getSegmentsAndCategories, sirdataSubmodule, setOrtb2} from 'modules/sirdataRtdProvider.js'; import {server} from 'test/mocks/xhr.js'; const responseHeader = {'Content-Type': 'application/json'}; describe('sirdataRtdProvider', function () { describe('sirdataSubmodule', function () { + it('exists', function () { + expect(sirdataSubmodule.init).to.be.a('function'); + }); it('successfully instantiates', function () { expect(sirdataSubmodule.init()).to.equal(true); }); + it('has the correct module name', function () { + expect(sirdataSubmodule.name).to.equal('SirdataRTDModule'); + }); }); describe('Add Segment Data', function () { it('adds segment data', function () { - const config = { + const firstConfig = { params: { - setGptKeyValues: false, + partnerId: 1, + key: 1, + setGptKeyValues: true, + gptCurationId: 27449, contextualMinRelevancyScore: 50, - bidders: [{ - bidder: 'appnexus' - }, { - bidder: 'other' - }] + bidders: [] } }; @@ -37,21 +42,47 @@ describe('sirdataRtdProvider', function () { } ]; - let data = { + let firstReqBidsConfigObj = { + adUnits: adUnits, + ortb2Fragments: { + global: {} + } + }; + + let firstData = { segments: [111111, 222222], - contextual_categories: {'333333': 100} + contextual_categories: {'333333': 100}, + 'segtaxid': null, + 'cattaxid': null, + 'shared_taxonomy': { + '27449': { + 'segments': [444444, 555555], + 'segtaxid': null, + 'cattaxid': null, + 'contextual_categories': {'666666': 100} + } + }, + 'global_taxonomy': { + '9998': { + 'segments': [123, 234], + 'segtaxid': 4, + 'cattaxid': 7, + 'contextual_categories': {'345': 100, '456': 100} + } + } }; - addSegmentData({adUnits}, data, config, () => { + addSegmentData(firstReqBidsConfigObj, firstData, firstConfig, () => { }); - expect(adUnits[0].bids[0].params.keywords).to.have.deep.property('sd_rtd', ['111111', '222222', '333333']); + + expect(firstReqBidsConfigObj.ortb2Fragments.global.user.data[0].ext.segtax).to.equal(4); }); }); describe('Get Segments And Categories', function () { it('gets data from async request and adds segment data', function () { const overrideAppnexus = function (adUnit, list, data, bid) { - deepSetValue(bid, 'params.keywords.custom', list); + deepSetValue(bid, 'params.keywords.custom', list.segments.concat(list.categories)); } const config = { @@ -60,16 +91,21 @@ describe('sirdataRtdProvider', function () { contextualMinRelevancyScore: 50, bidders: [{ bidder: 'appnexus', - customFunction: overrideAppnexus + customFunction: overrideAppnexus, + curationId: 27446 }, { - bidder: 'smartadserver' + bidder: 'smartadserver', + curationId: 27440 }, { bidder: 'ix', sizeLimit: 1200, + curationId: 27248 }, { bidder: 'rubicon', + curationId: 27452 }, { bidder: 'proxistore', + curationId: 27484 }] } }; @@ -146,6 +182,12 @@ describe('sirdataRtdProvider', function () { 'segtaxid': 552, 'cattaxid': 553, 'contextual_categories': {'666666': 100} + }, + '27446': { + 'segments': [777777, 888888], + 'segtaxid': 552, + 'cattaxid': 553, + 'contextual_categories': {'999999': 100} } }, 'global_taxonomy': { @@ -164,8 +206,6 @@ describe('sirdataRtdProvider', function () { let request = server.requests[0]; request.respond(200, responseHeader, JSON.stringify(data)); - expect(reqBidsConfigObj.adUnits[0].bids[1].params).to.have.deep.property('target', 'sd_rtd=111111;sd_rtd=222222;sd_rtd=333333;sd_rtd=444444;sd_rtd=555555;sd_rtd=666666'); - expect(reqBidsConfigObj.ortb2Fragments.global.site.content.data[0].name).to.equal( 'sirdata.com' ); @@ -185,4 +225,57 @@ describe('sirdataRtdProvider', function () { expect(reqBidsConfigObj.ortb2Fragments.global.user.data[0].ext.segtax).to.equal(4); }); }); + + describe('Set ortb2 for bidder', function () { + it('set ortb2 for a givent bidder', function () { + const config = { + params: { + setGptKeyValues: false, + contextualMinRelevancyScore: 50, + bidders: [{ + bidder: 'appnexus', + }] + } + }; + + let reqBidsConfigObj = { + adUnits: [{ + bids: [{ + bidder: 'appnexus', + params: { + placementId: 13144370 + } + }] + }], + ortb2Fragments: { + global: {} + } + }; + + let data = { + 'segments': [111111, 222222], + 'segtaxid': null, + 'cattaxid': null, + 'contextual_categories': {'333333': 100}, + 'shared_taxonomy': { + '27440': { + 'segments': [444444, 555555], + 'segtaxid': null, + 'cattaxid': null, + 'contextual_categories': {'666666': 100} + } + }, + 'global_taxonomy': {} + }; + + window.googletag = window.googletag || {}; + window.googletag.cmd = window.googletag.cmd || []; + + let test = setOrtb2(reqBidsConfigObj.ortb2Fragments, 'appnexus', 'user', []); + expect(test).to.be.false; + + test = setOrtb2(reqBidsConfigObj.ortb2Fragments, 'appnexus', 'user', ['1']); + expect(test).to.be.true; + }); + }); }); diff --git a/test/spec/modules/sizeMappingV2_spec.js b/test/spec/modules/sizeMappingV2_spec.js index 27fd7a33c14..16c1527a3ad 100644 --- a/test/spec/modules/sizeMappingV2_spec.js +++ b/test/spec/modules/sizeMappingV2_spec.js @@ -445,6 +445,10 @@ describe('sizeMappingV2', function () { }); describe('video mediaTypes checks', function () { + if (!FEATURES.VIDEO) { + return; + } + beforeEach(function () { sinon.spy(adUnitSetupChecks, 'validateVideoMediaType'); }); diff --git a/test/spec/modules/sizeMapping_spec.js b/test/spec/modules/sizeMapping_spec.js new file mode 100644 index 00000000000..55b536868e6 --- /dev/null +++ b/test/spec/modules/sizeMapping_spec.js @@ -0,0 +1,342 @@ +import {expect} from 'chai'; +import {resolveStatus, setSizeConfig, sizeSupported} from 'modules/sizeMapping.js'; +import {includes} from 'src/polyfill.js'; + +let utils = require('src/utils.js'); +let deepClone = utils.deepClone; + +describe('sizeMapping', function () { + var sizeConfig = [{ + 'mediaQuery': '(min-width: 1200px)', + 'sizesSupported': [ + [970, 90], + [728, 90], + [300, 250] + ] + }, { + 'mediaQuery': '(min-width: 768px) and (max-width: 1199px)', + 'sizesSupported': [ + [728, 90], + [300, 250], + [300, 100] + ] + }, { + 'mediaQuery': '(min-width: 0px) and (max-width: 767px)', + 'sizesSupported': [] + }]; + + var sizeConfigWithLabels = [{ + 'mediaQuery': '(min-width: 1200px)', + 'labels': ['desktop'] + }, { + 'mediaQuery': '(min-width: 768px) and (max-width: 1199px)', + 'sizesSupported': [ + [728, 90], + [300, 250] + ], + 'labels': ['tablet', 'phone'] + }, { + 'mediaQuery': '(min-width: 0px) and (max-width: 767px)', + 'sizesSupported': [ + [300, 250], + [300, 100] + ], + 'labels': ['phone'] + }]; + + let sandbox, + matchMediaOverride; + + beforeEach(function () { + setSizeConfig(sizeConfig); + + sandbox = sinon.sandbox.create(); + + matchMediaOverride = {matches: false}; + + sandbox.stub(utils.getWindowTop(), 'matchMedia').callsFake((...args) => { + if (typeof matchMediaOverride === 'function') { + return matchMediaOverride.apply(utils.getWindowTop(), args); + } + return matchMediaOverride; + }); + }); + + afterEach(function () { + setSizeConfig([]); + + sandbox.restore(); + }); + + describe('sizeConfig', () => { + it('should allow us to validate a single size', function () { + matchMediaOverride = (str) => str === '(min-width: 1200px)' ? {matches: true} : {matches: false}; + + expect(sizeSupported([300, 250])).to.equal(true); + expect(sizeSupported([80, 80])).to.equal(false); + }); + + it('should log a warning when mediaQuery property missing from sizeConfig', function () { + let errorConfig = deepClone(sizeConfig); + + delete errorConfig[0].mediaQuery; + + sandbox.stub(utils, 'logWarn'); + + resolveStatus(undefined, {}, errorConfig); + expect(utils.logWarn.firstCall.args[0]).to.match(/missing.+?mediaQuery/); + }); + + it('should log a warning message when mediaQuery property is declared as an empty string', function () { + const errorConfig = deepClone(sizeConfig); + errorConfig[0].mediaQuery = ''; + + sandbox.stub(utils, 'logWarn'); + resolveStatus(undefined, {}, errorConfig); + expect(utils.logWarn.firstCall.args[0]).to.match(/missing.+?mediaQuery/); + }); + }); + + const TEST_SIZES = [[970, 90], [728, 90], [300, 250], [300, 100], [80, 80]]; + + const suites = { + banner: { + mediaTypes: { + banner: { + sizes: TEST_SIZES + } + }, + getSizes(mediaTypes) { + return mediaTypes.banner.sizes; + }, + } + } + if (FEATURES.VIDEO) { + suites.video = { + mediaTypes: { + video: { + playerSize: TEST_SIZES + } + }, + getSizes(mediaTypes) { + return mediaTypes.video.playerSize; + } + } + } + Object.entries(suites).forEach(([mediaType, {mediaTypes, getSizes}]) => { + describe(`for ${mediaType}`, () => { + describe('when handling sizes', function () { + it('when one mediaQuery block matches, it should filter the adUnit.sizes passed in', function () { + matchMediaOverride = (str) => str === '(min-width: 1200px)' ? {matches: true} : {matches: false}; + + let status = resolveStatus(undefined, mediaTypes, sizeConfig); + + expect(status.active).to.equal(true); + expect(getSizes(status.mediaTypes)).to.deep.equal( + [[970, 90], [728, 90], [300, 250]] + ); + }); + + it('when multiple mediaQuery block matches, it should filter a union of the matched sizesSupported', function () { + matchMediaOverride = (str) => includes([ + '(min-width: 1200px)', + '(min-width: 768px) and (max-width: 1199px)' + ], str) ? {matches: true} : {matches: false}; + + let status = resolveStatus(undefined, mediaTypes, sizeConfig); + expect(status.active).to.equal(true); + expect(getSizes(status.mediaTypes)).to.deep.equal( + [[970, 90], [728, 90], [300, 250], [300, 100]] + ); + }); + + it('if no mediaQueries match, it should allow all sizes specified', function () { + matchMediaOverride = () => ({matches: false}); + + let status = resolveStatus(undefined, mediaTypes, sizeConfig); + expect(status.active).to.equal(true); + expect(status.mediaTypes).to.deep.equal(mediaTypes); + }); + + it('if a mediaQuery matches and has sizesSupported: [], it should filter all sizes', function () { + matchMediaOverride = (str) => str === '(min-width: 0px) and (max-width: 767px)' ? {matches: true} : {matches: false}; + + let status = resolveStatus(undefined, mediaTypes, sizeConfig); + expect(status.active).to.equal(false); + expect(getSizes(status.mediaTypes)).to.deep.equal([]); + }); + + it('should filter all banner sizes and should disable the adUnit even if other mediaTypes are present', function () { + matchMediaOverride = (str) => str === '(min-width: 0px) and (max-width: 767px)' ? {matches: true} : {matches: false}; + let status = resolveStatus(undefined, Object.assign({}, mediaTypes, { + native: { + type: 'image' + } + }), sizeConfig); + expect(status.active).to.equal(false); + expect(getSizes(status.mediaTypes)).to.deep.equal([]); + expect(status.mediaTypes.native).to.deep.equal({ + type: 'image' + }); + }); + + it('if a mediaQuery matches and no sizesSupported specified, it should not affect adUnit.sizes', function () { + matchMediaOverride = (str) => str === '(min-width: 1200px)' ? {matches: true} : {matches: false}; + + let status = resolveStatus(undefined, mediaTypes, sizeConfigWithLabels); + expect(status.active).to.equal(true); + expect(status.mediaTypes).to.deep.equal(mediaTypes); + }); + }); + + describe('when handling labels', function () { + it('should activate/deactivate adUnits/bidders based on sizeConfig.labels', function () { + matchMediaOverride = (str) => str === '(min-width: 1200px)' ? {matches: true} : {matches: false}; + + let status = resolveStatus({ + labels: ['desktop'] + }, mediaTypes, sizeConfigWithLabels); + + expect(status).to.deep.equal({ + active: true, + mediaTypes: mediaTypes + }); + + status = resolveStatus({ + labels: ['tablet'] + }, mediaTypes, sizeConfigWithLabels); + + expect(status.active).to.equal(false); + expect(status.mediaTypes).to.deep.equal(mediaTypes); + }); + + it('should active/deactivate adUnits/bidders based on requestBids labels', function () { + let activeLabels = ['us-visitor', 'desktop', 'smart']; + + let status = resolveStatus({ + labels: ['uk-visitor'], // from adunit + activeLabels // from requestBids.labels + }, mediaTypes, sizeConfigWithLabels); + + expect(status.active).to.equal(false); + expect(status.mediaTypes).to.deep.equal(mediaTypes); + + status = resolveStatus({ + labels: ['us-visitor'], + activeLabels + }, mediaTypes, sizeConfigWithLabels); + + expect(status.active).to.equal(true); + expect(status.mediaTypes).to.deep.equal(mediaTypes); + + status = resolveStatus({ + labels: ['us-visitor', 'tablet'], + labelAll: true, + activeLabels + }, mediaTypes, sizeConfigWithLabels); + + expect(status.active).to.equal(false); + expect(status.mediaTypes).to.deep.equal(mediaTypes); + + status = resolveStatus({ + labels: ['us-visitor', 'desktop'], + labelAll: true, + activeLabels + }, mediaTypes, undefined, sizeConfigWithLabels); + + expect(status.active).to.equal(true); + expect(status.mediaTypes).to.deep.equal(mediaTypes); + }); + }); + }); + }); + + if (FEATURES.VIDEO) { + it('should activate/decactivate adUnits/bidders based on labels with multiformat ads', function () { + matchMediaOverride = (str) => str === '(min-width: 768px) and (max-width: 1199px)' ? {matches: true} : {matches: false}; + + let multiFormatSizes = { + banner: { + sizes: [[728, 90], [300, 300]] + }, + native: { + type: 'image' + }, + video: { + context: 'outstream', + playerSize: [[728, 90], [300, 300]] + } + }; + + let status = resolveStatus({ + labels: ['tablet', 'test'], + labelAll: true + }, multiFormatSizes, sizeConfigWithLabels); + + expect(status.active).to.equal(false); + expect(status.mediaTypes).to.deep.equal({ + banner: { + sizes: [[728, 90]] + }, + native: { + type: 'image' + }, + video: { + context: 'outstream', + playerSize: [[728, 90]] + } + }); + + status = resolveStatus({ + labels: ['tablet'] + }, multiFormatSizes, sizeConfigWithLabels); + + expect(status.active).to.equal(true); + expect(status.mediaTypes).to.deep.equal({ + banner: { + sizes: [[728, 90]] + }, + native: { + type: 'image' + }, + video: { + context: 'outstream', + playerSize: [[728, 90]] + } + }); + + [multiFormatSizes.banner.sizes, multiFormatSizes.video.playerSize].forEach(sz => sz.splice(0, 1, [728, 80])) + status = resolveStatus({ + labels: ['tablet'] + }, multiFormatSizes, sizeConfigWithLabels); + + expect(status.active).to.equal(false); + expect(status.mediaTypes).to.deep.equal({ + banner: { + sizes: [] + }, + native: { + type: 'image' + }, + video: { + context: 'outstream', + playerSize: [] + } + }); + + delete multiFormatSizes.banner; + delete multiFormatSizes.video; + + status = resolveStatus({ + labels: ['tablet'] + }, multiFormatSizes, sizeConfigWithLabels); + + expect(status.active).to.equal(true); + expect(status.mediaTypes).to.deep.equal({ + native: { + type: 'image' + } + }); + }); + } +}); diff --git a/test/spec/modules/slimcutBidAdapter_spec.js b/test/spec/modules/slimcutBidAdapter_spec.js index d821627c24b..da0fee48936 100644 --- a/test/spec/modules/slimcutBidAdapter_spec.js +++ b/test/spec/modules/slimcutBidAdapter_spec.js @@ -178,7 +178,6 @@ describe('slimcutBidAdapter', function() { 'ad': AD_SCRIPT, 'requestId': '3ede2a3fa0db94', 'creativeId': 'er2ee', - 'transactionId': 'deadb33f', 'winUrl': 'https://sb.freeskreen.com/win', 'meta': { 'advertiserDomains': [] diff --git a/test/spec/modules/smaatoBidAdapter_spec.js b/test/spec/modules/smaatoBidAdapter_spec.js index c7dbf1363e7..61a877f329d 100644 --- a/test/spec/modules/smaatoBidAdapter_spec.js +++ b/test/spec/modules/smaatoBidAdapter_spec.js @@ -13,6 +13,7 @@ const CONSENT_STRING = 'HFIDUYFIUYIUYWIPOI87392DSU' const AUCTION_ID = '6653'; const defaultBidderRequest = { + bidderRequestId: 'mock-uuid', gdprConsent: { consentString: CONSENT_STRING, gdprApplies: true @@ -297,6 +298,21 @@ describe('smaatoBidAdapterTest', () => { expect(req.regs.ext.us_privacy).to.equal('uspConsentString'); }); + it('sends gpp', () => { + const ortb2 = { + regs: { + gpp: 'gppString', + gpp_sid: [7] + } + }; + + const reqs = spec.buildRequests([singleBannerBidRequest], {...defaultBidderRequest, ortb2}); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.regs.ext.gpp).to.eql('gppString'); + expect(req.regs.ext.gpp_sid).to.eql([7]); + }); + it('sends no schain if no schain exists', () => { const reqs = spec.buildRequests([singleBannerBidRequest], defaultBidderRequest); @@ -341,6 +357,13 @@ describe('smaatoBidAdapterTest', () => { keywords: 'a,b', gender: 'M', yob: 1984 + }, + device: { + ifa: 'ifa', + geo: { + lat: 53.5488, + lon: 9.9872 + } } }; @@ -353,6 +376,9 @@ describe('smaatoBidAdapterTest', () => { expect(req.user.ext.consent).to.equal(CONSENT_STRING); expect(req.site.keywords).to.eql('power tools,drills'); expect(req.site.publisher.id).to.equal('publisherId'); + expect(req.device.ifa).to.equal('ifa'); + expect(req.device.geo.lat).to.equal(53.5488); + expect(req.device.geo.lon).to.equal(9.9872); }); it('has no user ids', () => { @@ -529,7 +555,7 @@ describe('smaatoBidAdapterTest', () => { const reqs = spec.buildRequests([longFormVideoBidRequest], defaultBidderRequest); const req = extractPayloadOfFirstAndOnlyRequest(reqs); - expect(req.id).to.be.equal(AUCTION_ID); + expect(req.id).to.exist; expect(req.imp.length).to.be.equal(ADPOD_DURATION / DURATION_RANGE[0]); expect(req.imp[0].id).to.be.equal(BID_ID); expect(req.imp[0].tagid).to.be.equal(ADBREAK_ID); @@ -633,7 +659,7 @@ describe('smaatoBidAdapterTest', () => { const reqs = spec.buildRequests([longFormVideoBidRequest], defaultBidderRequest); const req = extractPayloadOfFirstAndOnlyRequest(reqs); - expect(req.id).to.be.equal(AUCTION_ID); + expect(req.id).to.exist; expect(req.imp.length).to.be.equal(DURATION_RANGE.length); expect(req.imp[0].id).to.be.equal(BID_ID); expect(req.imp[0].tagid).to.be.equal(ADBREAK_ID); @@ -992,6 +1018,28 @@ describe('smaatoBidAdapterTest', () => { expect(req.device.ifa).to.equal(DEVICE_ID); }); + it('when geo and ifa info present and fpd present, then prefer fpd', () => { + const ortb2 = { + device: { + ifa: 'ifa', + geo: { + lat: 53.5488, + lon: 9.9872 + } + } + }; + + const inAppBidRequest = utils.deepClone(inAppBidRequestWithoutAppParams); + inAppBidRequest.params.app = {ifa: DEVICE_ID, geo: LOCATION}; + + const reqs = spec.buildRequests([inAppBidRequest], {...defaultBidderRequest, ortb2}); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.device.geo.lat).to.equal(53.5488); + expect(req.device.geo.lon).to.equal(9.9872); + expect(req.device.ifa).to.equal('ifa'); + }); + it('when ifa is present but geo is missing, then add only ifa to device object', () => { const inAppBidRequest = utils.deepClone(inAppBidRequestWithoutAppParams); inAppBidRequest.params.app = {ifa: DEVICE_ID}; diff --git a/test/spec/modules/smartadserverBidAdapter_spec.js b/test/spec/modules/smartadserverBidAdapter_spec.js index 97250ad0ebc..9daa6a87826 100644 --- a/test/spec/modules/smartadserverBidAdapter_spec.js +++ b/test/spec/modules/smartadserverBidAdapter_spec.js @@ -1,6 +1,7 @@ import { expect } from 'chai'; import { BANNER, VIDEO } from 'src/mediaTypes.js'; import { config } from 'src/config.js'; +import { deepClone } from 'src/utils.js'; import { spec } from 'modules/smartadserverBidAdapter.js'; // Default params with optional ones @@ -29,7 +30,11 @@ describe('Smart bid adapter tests', function () { ckId: 42 }, requestId: 'efgh5678', - transactionId: 'zsfgzzg' + ortb2Imp: { + ext: { + tid: 'zsfgzzg' + } + }, }]; var DEFAULT_PARAMS_WITH_EIDS = [{ @@ -57,18 +62,80 @@ describe('Smart bid adapter tests', function () { }, requestId: 'efgh5678', transactionId: 'zsfgzzg', - userId: { - britepoolid: '1111', - criteoId: '1111', - digitrustid: { data: { id: 'DTID', keyv: 4, privacy: { optout: false }, producer: 'ABC', version: 2 } }, - id5id: { uid: '1111' }, - idl_env: '1111', - lipbid: '1111', - parrableid: 'eidVersion.encryptionKeyReference.encryptedValue', - pubcid: '1111', - tdid: '1111', - netId: 'fH5A3n2O8_CZZyPoJVD-eabc6ECb7jhxCicsds7qSg', - } + userIdAsEids: [ + { + 'source': 'pubcid.org', + 'uids': [ + { + 'atype': 1, + 'id': '1111' + } + ] + }, + { + 'source': 'britepoolid', + 'uids': [ + { + 'atype': 1, + 'id': '1111' + } + ] + }, + { + 'source': 'id5id', + 'uids': [ + { + 'atype': 1, + 'id': '1111' + } + ] + }, + { + 'source': 'idl_env', + 'uids': [ + { + 'atype': 1, + 'id': '1111' + } + ] + }, + { + 'source': 'lipbid', + 'uids': [ + { + 'atype': 1, + 'id': '1111' + } + ] + }, + { + 'source': 'parrableid', + 'uids': [ + { + 'atype': 1, + 'id': 'eidVersion.encryptionKeyReference.encryptedValue' + } + ] + }, + { + 'source': 'tdid', + 'uids': [ + { + 'atype': 1, + 'id': '1111' + } + ] + }, + { + 'source': 'netId', + 'uids': [ + { + 'atype': 1, + 'id': 'fH5A3n2O8_CZZyPoJVD-eabc6ECb7jhxCicsds7qSg' + } + ] + } + ] }]; // Default params without optional ones @@ -524,7 +591,11 @@ describe('Smart bid adapter tests', function () { } }, requestId: 'efgh5678', - transactionId: 'zsfgzzg' + ortb2Imp: { + ext: { + tid: 'zsfgzzg', + } + }, }]; var INSTREAM_BID_RESPONSE = { @@ -793,7 +864,11 @@ describe('Smart bid adapter tests', function () { protocol: 7 } }, - requestId: 'efgh5679', + ortb2Imp: { + ext: { + tid: 'efgh5679', + } + }, transactionId: 'zsfgzzga' }]; @@ -1277,4 +1352,45 @@ describe('Smart bid adapter tests', function () { expect(bannerRequest).to.have.property('formatid').and.to.equal('90'); }); }); + + describe('Global Placement ID (GPID)', function () { + it('should not include gpid by default', () => { + const request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL); + const requestContent = JSON.parse(request[0].data); + + expect(requestContent).to.not.have.property('gdid'); + }); + + it('should include gpid if pbadslot in ortb2Imp', () => { + const gpid = '/19968336/header-bid-tag-1'; + const bidRequests = deepClone(DEFAULT_PARAMS_WO_OPTIONAL); + + bidRequests[0].ortb2Imp = { + ext: { + data: { + pbadslot: gpid + } + } + }; + + const request = spec.buildRequests(bidRequests); + const requestContent = JSON.parse(request[0].data); + + expect(requestContent).to.have.property('gpid').and.to.equal(gpid); + }); + + it('should include gpid if imp[].ext.gpid exists', () => { + const gpid = '/1111/homepage#div-leftnav'; + const bidRequests = deepClone(DEFAULT_PARAMS_WO_OPTIONAL); + + bidRequests[0].ortb2Imp = { + ext: { gpid } + }; + + const request = spec.buildRequests(bidRequests); + const requestContent = JSON.parse(request[0].data); + + expect(requestContent).to.have.property('gpid').and.to.equal(gpid); + }); + }); }); diff --git a/test/spec/modules/smarthubBidAdapter_spec.js b/test/spec/modules/smarthubBidAdapter_spec.js index e1787dfe880..e01d0c72f6b 100644 --- a/test/spec/modules/smarthubBidAdapter_spec.js +++ b/test/spec/modules/smarthubBidAdapter_spec.js @@ -91,7 +91,8 @@ describe('SmartHubBidAdapter', function () { gdprConsent: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', refererInfo: { page: 'https://test.com' - } + }, + timeout: 500 }; describe('isBidRequestValid', function () { diff --git a/test/spec/modules/smartyadsBidAdapter_spec.js b/test/spec/modules/smartyadsBidAdapter_spec.js index 3474753c838..cbc4a6405e8 100644 --- a/test/spec/modules/smartyadsBidAdapter_spec.js +++ b/test/spec/modules/smartyadsBidAdapter_spec.js @@ -243,7 +243,7 @@ describe('SmartyadsAdapter', function () { }); }); describe('getUserSyncs', function () { - const syncUrl = 'https://as.ck-ie.com/prebidjs?p=7c47322e527cf8bdeb7facc1bb03387a&gdpr=0&gdpr_consent=&type=iframe&us_privacy='; + const syncUrl = 'https://as.ck-ie.com/prebidjs?p=7c47322e527cf8bdeb7facc1bb03387a&gdpr=0&gdpr_consent=&type=iframe&us_privacy=&gpp='; const syncOptions = { iframeEnabled: true }; diff --git a/test/spec/modules/smartytechBidAdapter_spec.js b/test/spec/modules/smartytechBidAdapter_spec.js index b41b0280235..6b3147859bf 100644 --- a/test/spec/modules/smartytechBidAdapter_spec.js +++ b/test/spec/modules/smartytechBidAdapter_spec.js @@ -138,10 +138,13 @@ function mockRandomSizeArray(len) { }); } -function mockBidRequestListData(mediaType, size) { +function mockBidRequestListData(mediaType, size, customSizes) { return Array.apply(null, {length: size}).map((i, index) => { const id = Math.floor(Math.random() * 800) * (index + 1); let mediaTypes; + let params = { + endpointId: id + } if (mediaType == 'video') { mediaTypes = { @@ -158,13 +161,15 @@ function mockBidRequestListData(mediaType, size) { } } + if (customSizes === undefined || customSizes.length > 0) { + params.sizes = customSizes + } + return { adUnitCode: `adUnitCode-${id}`, mediaTypes: mediaTypes, bidId: `bidId-${id}`, - params: { - endpointId: id - } + params: params } }); } @@ -211,7 +216,7 @@ describe('SmartyTechDSPAdapter: buildRequests', () => { let mockBidRequest; let mockReferer; beforeEach(() => { - mockBidRequest = mockBidRequestListData('banner', 8); + mockBidRequest = mockBidRequestListData('banner', 8, []); mockReferer = mockRefererData(); }); it('has return data', () => { @@ -238,13 +243,45 @@ describe('SmartyTechDSPAdapter: buildRequests', () => { }); }); +describe('SmartyTechDSPAdapter: buildRequests banner custom size', () => { + let mockBidRequest; + let mockReferer; + beforeEach(() => { + mockBidRequest = mockBidRequestListData('banner', 8, [[300, 600]]); + mockReferer = mockRefererData(); + }); + + it('correct request data', () => { + const data = spec.buildRequests(mockBidRequest, mockReferer).data; + data.forEach((request, index) => { + expect(request.banner.sizes).to.be.equal(mockBidRequest[index].params.sizes); + }) + }); +}); + +describe('SmartyTechDSPAdapter: buildRequests video custom size', () => { + let mockBidRequest; + let mockReferer; + beforeEach(() => { + mockBidRequest = mockBidRequestListData('video', 8, [[300, 300], [250, 250]]); + mockReferer = mockRefererData(); + }); + + it('correct request data', () => { + const data = spec.buildRequests(mockBidRequest, mockReferer).data; + data.forEach((request, index) => { + expect(request.video.sizes).to.be.equal(mockBidRequest[index].params.sizes); + }) + }); +}); + describe('SmartyTechDSPAdapter: interpretResponse', () => { let mockBidRequest; let mockReferer; let request; let mockResponse; beforeEach(() => { - const brData = mockBidRequestListData('banner', 2); + const brData = mockBidRequestListData('banner', 2, []); mockReferer = mockRefererData(); request = spec.buildRequests(brData, mockReferer); mockBidRequest = { @@ -290,7 +327,7 @@ describe('SmartyTechDSPAdapter: interpretResponse video', () => { let request; let mockResponse; beforeEach(() => { - const brData = mockBidRequestListData('video', 2); + const brData = mockBidRequestListData('video', 2, []); mockReferer = mockRefererData(); request = spec.buildRequests(brData, mockReferer); mockBidRequest = { diff --git a/test/spec/modules/smilewantedBidAdapter_spec.js b/test/spec/modules/smilewantedBidAdapter_spec.js index 44d2c7c6507..22221dbe1ef 100644 --- a/test/spec/modules/smilewantedBidAdapter_spec.js +++ b/test/spec/modules/smilewantedBidAdapter_spec.js @@ -14,7 +14,11 @@ const DISPLAY_REQUEST = [{ zoneId: 1 }, requestId: 'request_abcd1234', - transactionId: 'trans_abcd1234' + ortb2Imp: { + ext: { + tid: 'trans_abcd1234', + } + }, }]; const DISPLAY_REQUEST_WITH_EIDS = [{ @@ -29,7 +33,11 @@ const DISPLAY_REQUEST_WITH_EIDS = [{ zoneId: 1 }, requestId: 'request_abcd1234', - transactionId: 'trans_abcd1234', + ortb2Imp: { + ext: { + tid: 'trans_abcd1234', + } + }, userIdAsEids: [{ source: 'pubcid.org', uids: [{ @@ -61,7 +69,11 @@ const DISPLAY_REQUEST_WITH_POSITION_TYPE = [{ positionType: 'infeed' }, requestId: 'request_abcd1234', - transactionId: 'trans_abcd1234' + ortb2Imp: { + ext: { + tid: 'trans_abcd1234', + } + }, }]; const BID_RESPONSE_DISPLAY = { @@ -92,7 +104,11 @@ const VIDEO_INSTREAM_REQUEST = [{ bidfloor: 2.50 }, requestId: 'request_abcd1234', - transactionId: 'trans_abcd1234' + ortb2Imp: { + ext: { + tid: 'trans_abcd1234', + } + } }]; const BID_RESPONSE_VIDEO_INSTREAM = { @@ -124,7 +140,11 @@ const VIDEO_OUTSTREAM_REQUEST = [{ bidfloor: 2.50 }, requestId: 'request_abcd1234', - transactionId: 'trans_abcd1234' + ortb2Imp: { + ext: { + tid: 'trans_abcd1234', + } + } }]; const BID_RESPONSE_VIDEO_OUTSTREAM = { @@ -224,7 +244,6 @@ describe('smilewantedBidAdapterTests', function () { describe('gdpr tests', function () { afterEach(function () { config.resetConfig(); - $$PREBID_GLOBAL$$.requestBids.removeAll(); }); it('SmileWanted - Verify build request with GDPR', function () { diff --git a/test/spec/modules/sonobiBidAdapter_spec.js b/test/spec/modules/sonobiBidAdapter_spec.js index 56ed4d5196e..de8d0a5bda7 100644 --- a/test/spec/modules/sonobiBidAdapter_spec.js +++ b/test/spec/modules/sonobiBidAdapter_spec.js @@ -193,7 +193,7 @@ describe('SonobiBidAdapter', function () { }); describe('outstream', () => { - it('should return false if there is no param sizes', () => { + it('should return false if there is no playerSize', () => { const bid = { 'bidder': 'sonobi', 'adUnitCode': 'adunit-code', @@ -203,7 +203,6 @@ describe('SonobiBidAdapter', function () { 'mediaTypes': { video: { context: 'outstream', - playerSize: [300, 250] } }, 'bidId': '30b31c1838de1e', @@ -213,7 +212,7 @@ describe('SonobiBidAdapter', function () { expect(spec.isBidRequestValid(bid)).to.equal(false); }); - it('should return true if there is param sizes', () => { + it('should return true if there is playerSize', () => { const bid = { 'bidder': 'sonobi', 'adUnitCode': 'adunit-code', @@ -224,7 +223,8 @@ describe('SonobiBidAdapter', function () { }, 'mediaTypes': { video: { - context: 'outstream' + context: 'outstream', + playerSize: [640, 480] } }, 'bidId': '30b31c1838de1e', @@ -238,6 +238,13 @@ describe('SonobiBidAdapter', function () { }); describe('.buildRequests', function () { + before(function () { + $$PREBID_GLOBAL$$.bidderSettings = { + sonobi: { + storageAllowed: true + } + }; + }); let sandbox; beforeEach(function () { sinon.stub(userSync, 'canBidderRegisterSync'); @@ -287,7 +294,7 @@ describe('SonobiBidAdapter', function () { }, mediaTypes: { video: { - sizes: [[300, 250], [300, 600]], + playerSize: [640, 480], context: 'outstream' } } @@ -332,7 +339,7 @@ describe('SonobiBidAdapter', function () { }]; let keyMakerData = { - '30b31c1838de1f': '1a2b3c4d5e6f1a2b3c4d|300x250,300x600|f=1.25,gpid=/123123/gpt_publisher/adunit-code-1,c=v,', + '30b31c1838de1f': '1a2b3c4d5e6f1a2b3c4d|640x480|f=1.25,gpid=/123123/gpt_publisher/adunit-code-1,c=v,', '30b31c1838de1d': '1a2b3c4d5e6f1a2b3c4e|300x250,300x600|f=0.42,gpid=/123123/gpt_publisher/adunit-code-3,c=d,', '/7780971/sparks_prebid_LB|30b31c1838de1e': '300x250,300x600|gpid=/7780971/sparks_prebid_LB,c=d,', }; @@ -389,6 +396,10 @@ describe('SonobiBidAdapter', function () { expect(bidRequests.data.coppa).to.equal(0); }); + it('should have storageAllowed set to true', function () { + expect($$PREBID_GLOBAL$$.bidderSettings.sonobi.storageAllowed).to.be.true; + }); + it('should return a properly formatted request', function () { const bidRequests = spec.buildRequests(bidRequest, bidderRequests) const bidRequestsPageViewID = spec.buildRequests(bidRequest, bidderRequests) @@ -398,6 +409,8 @@ describe('SonobiBidAdapter', function () { expect(bidRequests.data.ref).not.to.be.empty expect(bidRequests.data.s).not.to.be.empty expect(bidRequests.data.pv).to.equal(bidRequestsPageViewID.data.pv) + expect(JSON.parse(bidRequests.data.iqid).pcid).to.match(/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/) + expect(JSON.parse(bidRequests.data.iqid).pcidDate).to.match(/^[0-9]{13}$/) expect(bidRequests.data.hfa).to.not.exist expect(bidRequests.bidderRequests).to.eql(bidRequest); expect(bidRequests.data.ref).to.equal('overrides_top_window_location'); @@ -622,12 +635,12 @@ describe('SonobiBidAdapter', function () { bidder: 'sonobi', mediaTypes: { video: { - context: 'outstream' + context: 'outstream', + playerSize: [640, 480] } }, params: { - placement_id: '92e95368e86639dbd86d', - sizes: [[640, 480]] + placement_id: '92e95368e86639dbd86d' } } ] @@ -645,7 +658,7 @@ describe('SonobiBidAdapter', function () { 'sbi_adomain': 'sonobi.com' }, '30b31c1838de1e': { - 'sbi_size': '300x250', + 'sbi_size': '640x480', 'sbi_apoc': 'remnant', 'sbi_aid': '30292e432662bd5f86d90774b944b038', 'sbi_mouse': 1.25, @@ -655,7 +668,7 @@ describe('SonobiBidAdapter', function () { }, '/7780971/sparks_prebid_LB_OUTSTREAM|30b31c1838de1g': { - 'sbi_size': '300x600', + 'sbi_size': '640x480', 'sbi_apoc': 'remnant', 'sbi_crid': '1234abcd', 'sbi_aid': '30292e432662bd5f86d90774b944b038', @@ -707,8 +720,8 @@ describe('SonobiBidAdapter', function () { { 'requestId': '30b31c1838de1e', 'cpm': 1.25, - 'width': 300, - 'height': 250, + 'width': 640, + 'height': 480, 'vastUrl': 'https://mco-1-apex.go.sonobi.com/vast.xml?vid=30292e432662bd5f86d90774b944b038&ref=https%3A%2F%2Flocalhost%2F', 'ttl': 500, 'creativeId': '30292e432662bd5f86d90774b944b038', @@ -724,8 +737,8 @@ describe('SonobiBidAdapter', function () { { 'requestId': '30b31c1838de1g', 'cpm': 1.07, - 'width': 300, - 'height': 600, + 'width': 640, + 'height': 480, 'ad': ``, 'ttl': 500, 'creativeId': '1234abcd', diff --git a/test/spec/modules/sovrnBidAdapter_spec.js b/test/spec/modules/sovrnBidAdapter_spec.js index 02a3d86b29a..90913c6f130 100644 --- a/test/spec/modules/sovrnBidAdapter_spec.js +++ b/test/spec/modules/sovrnBidAdapter_spec.js @@ -170,6 +170,53 @@ describe('sovrnBidAdapter', function() { expect(payload.site.domain).to.equal('example.com') }) + it('sets correct timeout', function() { + const bidderRequest = { + ...baseBidderRequest, + bidderCode: 'sovrn', + auctionId: '1d1a030790a475', + bidderRequestId: '22edbae2733bf6', + timeout: 3000, + bids: [baseBidRequest] + } + const payload = JSON.parse(spec.buildRequests([baseBidRequest], bidderRequest).data) + expect(payload.tmax).to.equal(3000) + }) + + it('forwards auction level tid', function() { + const bidderRequest = { + ...baseBidderRequest, + ortb2: { + source: { + tid: '1d1a030790a475' + } + }, + bids: [baseBidRequest] + } + + const payload = JSON.parse(spec.buildRequests([baseBidRequest], bidderRequest).data) + expect(payload.source?.tid).to.equal('1d1a030790a475') + }) + + it('forwards impression level tid', function() { + const bidRequest = { + ...baseBidRequest, + ortb2Imp: { + ext: { + tid: '1a2c032473f4983' + } + }, + } + + const bidderRequest = { + ...baseBidderRequest, + bids: [bidRequest] + } + + const payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequest).data) + expect(payload.imp[0]?.ext?.tid).to.equal('1a2c032473f4983') + }) + it('includes the ad unit code in the request', function() { const impression = payload.imp[0] expect(impression.adunitcode).to.equal('adunit-code') @@ -248,6 +295,71 @@ describe('sovrnBidAdapter', function() { expect(data.regs.ext['us_privacy']).to.equal(bidderRequest.uspConsent) }) + it('should send gpp info in OpenRTB 2.6 location when gppConsent defined', function () { + const bidderRequest = { + ...baseBidderRequest, + bidderCode: 'sovrn', + auctionId: '1d1a030790a475', + bidderRequestId: '22edbae2733bf6', + timeout: 3000, + gppConsent: { + gppString: 'gppstring', + applicableSections: [8] + }, + bids: [baseBidRequest] + } + const { regs } = JSON.parse(spec.buildRequests([baseBidRequest], bidderRequest).data) + expect(regs.gpp).to.equal('gppstring') + expect(regs.gpp_sid).to.be.an('array') + expect(regs.gpp_sid).to.include(8) + }) + + it('should not send gpp info when gppConsent is not defined', function () { + const bidderRequest = { + ...baseBidderRequest, + bidderCode: 'sovrn', + auctionId: '1d1a030790a475', + bidderRequestId: '22edbae2733bf6', + timeout: 3000, + bids: [baseBidRequest], + gdprConsent: { + consentString: 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A==', + gdprApplies: true + }, + } + const { regs } = JSON.parse(spec.buildRequests([baseBidRequest], bidderRequest).data) + expect(regs.gpp).to.be.undefined + }) + + it('should send gdpr info even when gppConsent defined', function () { + const bidderRequest = { + ...baseBidderRequest, + bidderCode: 'sovrn', + auctionId: '1d1a030790a475', + bidderRequestId: '22edbae2733bf6', + timeout: 3000, + gdprConsent: { + consentString: 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A==', + gdprApplies: true + }, + gppConsent: { + gppString: 'gppstring', + applicableSections: [8] + }, + bids: [baseBidRequest] + } + + const { regs, user } = JSON.parse(spec.buildRequests([baseBidRequest], bidderRequest).data) + + expect(regs.ext.gdpr).to.exist.and.to.be.a('number') + expect(regs.ext.gdpr).to.equal(1) + expect(user.ext.consent).to.exist.and.to.be.a('string') + expect(user.ext.consent).to.equal(bidderRequest.gdprConsent.consentString) + expect(regs.gpp).to.equal('gppstring') + expect(regs.gpp_sid).to.be.an('array') + expect(regs.gpp_sid).to.include(8) + }) + it('should add schain if present', function() { const schainRequest = { ...baseBidRequest, @@ -270,14 +382,33 @@ describe('sovrnBidAdapter', function() { expect(data.source.ext.schain.nodes.length).to.equal(1) }) - it('should add eds to the bid request', function() { + it('should add eids to the bid request', function() { const criteoIdRequest = { ...baseBidRequest, - userId: { - criteoId: 'A_CRITEO_ID', - tdid: 'SOMESORTOFID', - } - } + userIdAsEids: [ + { + source: 'criteo.com', + uids: [ + { + atype: 1, + id: 'A_CRITEO_ID' + } + ] + }, + { + source: 'adserver.org', + uids: [ + { + atype: 1, + ext: { + rtiPartner: 'TDID' + }, + id: 'SOMESORTOFID' + } + ] + } + ] + }; const criteoIdRequests = [criteoIdRequest, baseBidRequest] const ext = JSON.parse(spec.buildRequests(criteoIdRequests, baseBidderRequest).data).user.ext const firstEID = ext.eids[0] @@ -720,7 +851,7 @@ describe('sovrnBidAdapter', function() { url: `https://ap.lijit.com/beacon?gdpr_consent=${gdprConsent.consentString}&informer=13487408`, } - const returnStatement = spec.getUserSyncs(syncOptions, serverResponse, gdprConsent, '') + const returnStatement = spec.getUserSyncs(syncOptions, serverResponse, gdprConsent, '', null) expect(returnStatement[0]).to.deep.equal(expectedReturnStatement) }) @@ -732,7 +863,22 @@ describe('sovrnBidAdapter', function() { url: `https://ap.lijit.com/beacon?us_privacy=${uspString}&informer=13487408`, } - const returnStatement = spec.getUserSyncs(syncOptions, serverResponse, null, uspString) + const returnStatement = spec.getUserSyncs(syncOptions, serverResponse, null, uspString, null) + + expect(returnStatement[0]).to.deep.equal(expectedReturnStatement) + }) + + it('should include gpp consent string if present', function() { + const gppConsent = { + applicableSections: [1, 2], + gppString: 'DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN' + } + const expectedReturnStatement = { + type: 'iframe', + url: `https://ap.lijit.com/beacon?gpp=${gppConsent.gppString}&gpp_sid=${gppConsent.applicableSections}&informer=13487408`, + } + + const returnStatement = spec.getUserSyncs(syncOptions, serverResponse, null, '', gppConsent) expect(returnStatement[0]).to.deep.equal(expectedReturnStatement) }) @@ -743,12 +889,17 @@ describe('sovrnBidAdapter', function() { consentString: 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A==' } const uspString = '1NYN' + const gppConsent = { + applicableSections: [1, 2], + gppString: 'DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN' + } + const expectedReturnStatement = { type: 'iframe', - url: `https://ap.lijit.com/beacon?gdpr_consent=${gdprConsent.consentString}&us_privacy=${uspString}&informer=13487408`, + url: `https://ap.lijit.com/beacon?gdpr_consent=${gdprConsent.consentString}&us_privacy=${uspString}&gpp=${gppConsent.gppString}&gpp_sid=${gppConsent.applicableSections}&informer=13487408`, } - const returnStatement = spec.getUserSyncs(syncOptions, serverResponse, gdprConsent, uspString) + const returnStatement = spec.getUserSyncs(syncOptions, serverResponse, gdprConsent, uspString, gppConsent) expect(returnStatement[0]).to.deep.equal(expectedReturnStatement) }) diff --git a/test/spec/modules/sspBCBidAdapter_spec.js b/test/spec/modules/sspBCBidAdapter_spec.js index 8c9bbe3b336..a95f08314b5 100644 --- a/test/spec/modules/sspBCBidAdapter_spec.js +++ b/test/spec/modules/sspBCBidAdapter_spec.js @@ -611,14 +611,14 @@ describe('SSPBC adapter', function () { expect(result.length).to.equal(bids.length); expect(resultSingle.length).to.equal(1); - expect(resultSingle[0]).to.have.keys('ad', 'cpm', 'width', 'height', 'bidderCode', 'mediaType', 'meta', 'requestId', 'creativeId', 'currency', 'netRevenue', 'ttl', 'vurls'); + expect(resultSingle[0]).to.have.keys('ad', 'cpm', 'width', 'height', 'mediaType', 'meta', 'requestId', 'creativeId', 'currency', 'netRevenue', 'ttl', 'vurls'); }); it('should create bid from OneCode (parameter-less) request, if response contains siteId', function () { let resultOneCode = spec.interpretResponse(serverResponseOneCode, requestOneCode); expect(resultOneCode.length).to.equal(1); - expect(resultOneCode[0]).to.have.keys('ad', 'cpm', 'width', 'height', 'bidderCode', 'mediaType', 'meta', 'requestId', 'creativeId', 'currency', 'netRevenue', 'ttl', 'vurls'); + expect(resultOneCode[0]).to.have.keys('ad', 'cpm', 'width', 'height', 'mediaType', 'meta', 'requestId', 'creativeId', 'currency', 'netRevenue', 'ttl', 'vurls'); }); it('should not create bid from OneCode (parameter-less) request, if response does not contain siteId', function () { @@ -649,7 +649,7 @@ describe('SSPBC adapter', function () { expect(resultVideo.length).to.equal(1); let videoBid = resultVideo[0]; - expect(videoBid).to.have.keys('adType', 'bidderCode', 'cpm', 'creativeId', 'currency', 'width', 'height', 'meta', 'mediaType', 'netRevenue', 'requestId', 'ttl', 'vastContent', 'vastXml', 'vastUrl', 'vurls'); + expect(videoBid).to.have.keys('adType', 'cpm', 'creativeId', 'currency', 'width', 'height', 'meta', 'mediaType', 'netRevenue', 'requestId', 'ttl', 'vastContent', 'vastXml', 'vastUrl', 'vurls'); expect(videoBid.adType).to.equal('instream'); expect(videoBid.mediaType).to.equal('video'); expect(videoBid.vastXml).to.match(/^<\?xml.*<\/VAST>$/); @@ -663,7 +663,7 @@ describe('SSPBC adapter', function () { expect(resultNative.length).to.equal(1); let nativeBid = resultNative[0]; - expect(nativeBid).to.have.keys('bidderCode', 'cpm', 'creativeId', 'currency', 'width', 'height', 'meta', 'mediaType', 'netRevenue', 'requestId', 'ttl', 'native', 'vurls'); + expect(nativeBid).to.have.keys('cpm', 'creativeId', 'currency', 'width', 'height', 'meta', 'mediaType', 'netRevenue', 'requestId', 'ttl', 'native', 'vurls'); expect(nativeBid.native).to.have.keys('image', 'icon', 'title', 'sponsoredBy', 'body', 'clickUrl', 'impressionTrackers', 'javascriptTrackers', 'clickTrackers'); }); }); diff --git a/test/spec/modules/stroeerCoreBidAdapter_spec.js b/test/spec/modules/stroeerCoreBidAdapter_spec.js index 6489d8ece7e..2ed5f80f152 100644 --- a/test/spec/modules/stroeerCoreBidAdapter_spec.js +++ b/test/spec/modules/stroeerCoreBidAdapter_spec.js @@ -3,6 +3,7 @@ import {spec} from 'modules/stroeerCoreBidAdapter.js'; import * as utils from 'src/utils.js'; import {BANNER, VIDEO} from '../../../src/mediaTypes.js'; import {find} from 'src/polyfill.js'; +import sinon from 'sinon'; describe('stroeerCore bid adapter', function () { let sandbox; @@ -51,8 +52,6 @@ describe('stroeerCore bid adapter', function () { assert.notProperty(bidObject, 'ad'); } - const AUCTION_ID = utils.getUniqueIdentifierStr(); - // Vendor user ids and associated data const userIds = Object.freeze({ criteoId: 'criteo-user-id', @@ -72,7 +71,6 @@ describe('stroeerCore bid adapter', function () { }); const buildBidderRequest = () => ({ - auctionId: AUCTION_ID, bidderRequestId: 'bidder-request-id-123', bidderCode: 'stroeerCore', timeout: 5000, @@ -394,6 +392,10 @@ describe('stroeerCore bid adapter', function () { clock.tick(13500); const bidReq = buildBidderRequest(); + const UUID = 'fb6a39e3-083f-424c-9046-f1095e15f3d5'; + + const generateUUIDStub = sinon.stub(utils, 'generateUUID').returns(UUID); + const serverRequestInfo = spec.buildRequests(bidReq.bids, bidReq); const expectedTimeout = bidderRequest.timeout - (13500 - bidderRequest.auctionStart); @@ -401,7 +403,7 @@ describe('stroeerCore bid adapter', function () { assert.equal(expectedTimeout, 1500); const expectedJsonPayload = { - 'id': AUCTION_ID, + 'id': UUID, 'timeout': expectedTimeout, 'ref': 'https://www.example.com/?search=monkey', 'mpa': true, @@ -429,8 +431,9 @@ describe('stroeerCore bid adapter', function () { // trim away fields with undefined const actualJsonPayload = JSON.parse(JSON.stringify(serverRequestInfo.data)); - assert.deepEqual(actualJsonPayload, expectedJsonPayload); + + generateUUIDStub.restore(); }); describe('video bids', () => { @@ -527,14 +530,16 @@ describe('stroeerCore bid adapter', function () { 'bid': 'bid8', 'viz': true, 'ban': { - 'siz': [[300, 600], [160, 60]] - } + 'siz': [[300, 600], [160, 60]], + 'fp': undefined + }, }, { 'sid': 'ABC=', 'bid': 'bid12', 'ban': { - 'siz': [[100, 200], [300, 500]] + 'siz': [[100, 200], [300, 500]], + 'fp': undefined }, 'viz': undefined } @@ -548,7 +553,8 @@ describe('stroeerCore bid adapter', function () { 'vid': { 'ctx': 'instream', 'siz': [640, 480], - 'mim': ['video/mp4', 'video/quicktime'] + 'mim': ['video/mp4', 'video/quicktime'], + 'fp': undefined } } ]; @@ -589,7 +595,8 @@ describe('stroeerCore bid adapter', function () { 'bid': 'bid3', 'viz': true, 'ban': { - 'siz': [[100, 200], [300, 500]] + 'siz': [[100, 200], [300, 500]], + 'fp': undefined } } ]; @@ -602,7 +609,8 @@ describe('stroeerCore bid adapter', function () { 'vid': { 'ctx': 'instream', 'siz': [640, 480], - 'mim': ['video/mp4', 'video/quicktime'] + 'mim': ['video/mp4', 'video/quicktime'], + 'fp': undefined } } ]; @@ -636,10 +644,13 @@ describe('stroeerCore bid adapter', function () { } }); - const gdprSamples = [{consentString: 'RG9ua2V5IEtvbmc=', gdprApplies: true}, { - consentString: 'UGluZyBQb25n', - gdprApplies: false - }]; + const gdprSamples = [ + {consentString: 'RG9ua2V5IEtvbmc=', gdprApplies: true}, + {consentString: 'UGluZyBQb25n', gdprApplies: false}, + {consentString: undefined, gdprApplies: true}, + {consentString: undefined, gdprApplies: false}, + {consentString: undefined, gdprApplies: undefined}, + ]; gdprSamples.forEach((sample) => { it(`should add GDPR info ${JSON.stringify(sample)} when provided`, () => { const bidReq = buildBidderRequest(); @@ -653,22 +664,14 @@ describe('stroeerCore bid adapter', function () { }); }); - const skippableGdprSamples = [{consentString: null, gdprApplies: true}, // - {consentString: 'UGluZyBQb25n', gdprApplies: null}, // - {consentString: null, gdprApplies: null}, // - {consentString: undefined, gdprApplies: true}, // - {consentString: 'UGluZyBQb25n', gdprApplies: undefined}, // - {consentString: undefined, gdprApplies: undefined}]; - skippableGdprSamples.forEach((sample) => { - it(`should not add GDPR info ${JSON.stringify(sample)} when one or more values are missing`, () => { - const bidReq = buildBidderRequest(); - bidReq.gdprConsent = sample; + it(`should not add GDPR info when not provided`, () => { + const bidReq = buildBidderRequest(); - const serverRequestInfo = spec.buildRequests(bidReq.bids, bidReq); + delete bidReq.gdprConsent; - const actualGdpr = serverRequestInfo.data.gdpr; - assert.isUndefined(actualGdpr); - }); + const serverRequestInfo = spec.buildRequests(bidReq.bids, bidReq); + + assert.notProperty(serverRequestInfo.data, 'gdpr'); }); it('should be able to build without third party user id data', () => { @@ -701,6 +704,146 @@ describe('stroeerCore bid adapter', function () { const serverRequestInfo = spec.buildRequests(bidReq.bids, bidReq); assert.deepEqual(serverRequestInfo.data.schain, schain); }); + + it('should add floor info to banner bid request if floor is available', () => { + const bidReq = buildBidderRequest(); + + const getFloorStub1 = sinon.stub(); + const getFloorStub2 = sinon.stub(); + + getFloorStub1 + .returns({}) + .withArgs({currency: 'EUR', mediaType: BANNER, size: '*'}) + .returns({currency: 'TRY', floor: 0.7}) + .withArgs({currency: 'EUR', mediaType: 'banner', size: [300, 600]}) + .returns({currency: 'TRY', floor: 1.3}) + .withArgs({currency: 'EUR', mediaType: 'banner', size: [160, 60]}) + .returns({currency: 'TRY', floor: 2.5}) + + getFloorStub2 + .returns({}) + .withArgs({currency: 'EUR', mediaType: 'banner', size: '*'}) + .returns({currency: 'USD', floor: 1.2}) + .withArgs({currency: 'EUR', mediaType: 'banner', size: [728, 90]}) + .returns({currency: 'USD', floor: 1.85}) + + bidReq.bids[0].getFloor = getFloorStub1; + bidReq.bids[1].getFloor = getFloorStub2; + + const serverRequestInfo = spec.buildRequests(bidReq.bids, bidReq); + + const serverRequestBids = serverRequestInfo.data.bids; + const firstBid = serverRequestBids[0]; + const secondBid = serverRequestBids[1]; + + assert.nestedPropertyVal(firstBid, 'ban.fp.def', 0.7); + assert.nestedPropertyVal(firstBid, 'ban.fp.cur', 'TRY'); + assert.deepNestedPropertyVal(firstBid, 'ban.fp.siz', [{w: 300, h: 600, p: 1.3}, {w: 160, h: 60, p: 2.5}]); + + assert.isTrue(getFloorStub1.calledThrice); + + assert.nestedPropertyVal(secondBid, 'ban.fp.def', 1.2); + assert.nestedPropertyVal(secondBid, 'ban.fp.cur', 'USD'); + assert.deepNestedPropertyVal(secondBid, 'ban.fp.siz', [{w: 728, h: 90, p: 1.85}]); + + assert.isTrue(getFloorStub2.calledTwice); + }); + + it('should add floor info to video bid request if floor is available', () => { + const bidReq = buildBidderRequest(); + + const getFloorStub1 = sinon.stub(); + const getFloorStub2 = sinon.stub(); + + getFloorStub1 + .returns({}) + .withArgs({currency: 'EUR', mediaType: 'video', size: '*'}) + .returns({currency: 'NZD', floor: 3.25}) + .withArgs({currency: 'EUR', mediaType: 'video', size: [640, 480]}) + .returns({currency: 'NZD', floor: 4.10}); + + getFloorStub2 + .returns({}) + .withArgs({currency: 'EUR', mediaType: 'video', size: '*'}) + .returns({currency: 'GBP', floor: 4.75}) + .withArgs({currency: 'EUR', mediaType: 'video', size: [1280, 720]}) + .returns({currency: 'GBP', floor: 6.50}) + + delete bidReq.bids[0].mediaTypes.banner; + bidReq.bids[0].mediaTypes.video = { + playerSize: [640, 480], + context: 'instream' + }; + + delete bidReq.bids[1].mediaTypes.banner; + bidReq.bids[1].mediaTypes.video = { + playerSize: [1280, 720], + context: 'outstream' + }; + + bidReq.bids[0].getFloor = getFloorStub1; + bidReq.bids[1].getFloor = getFloorStub2; + + const serverRequestInfo = spec.buildRequests(bidReq.bids, bidReq); + + const serverRequestBids = serverRequestInfo.data.bids; + const firstBid = serverRequestBids[0]; + const secondBid = serverRequestBids[1]; + + assert.nestedPropertyVal(firstBid, 'vid.fp.def', 3.25); + assert.nestedPropertyVal(firstBid, 'vid.fp.cur', 'NZD'); + assert.deepNestedPropertyVal(firstBid, 'vid.fp.siz', [{w: 640, h: 480, p: 4.10}]); + + assert.isTrue(getFloorStub1.calledTwice); + + assert.nestedPropertyVal(secondBid, 'vid.fp.def', 4.75); + assert.nestedPropertyVal(secondBid, 'vid.fp.cur', 'GBP'); + assert.deepNestedPropertyVal(secondBid, 'vid.fp.siz', [{w: 1280, h: 720, p: 6.50}]); + + assert.isTrue(getFloorStub2.calledTwice); + }); + + it('should not add floor info to bid request if floor is unavailable', () => { + const bidReq = buildBidderRequest(); + const getFloorSpy = sinon.spy(() => ({})); + + delete bidReq.bids[0].getFloor; + bidReq.bids[1].getFloor = getFloorSpy; + + const serverRequestInfo = spec.buildRequests(bidReq.bids, bidReq); + + const serverRequestBids = serverRequestInfo.data.bids; + const firstBid = serverRequestBids[0]; + const secondBid = serverRequestBids[1]; + + assert.nestedPropertyVal(firstBid, 'ban.fp', undefined); + assert.nestedPropertyVal(secondBid, 'ban.fp', undefined); + + assert.isTrue(getFloorSpy.calledWith({currency: 'EUR', mediaType: 'banner', size: '*'})); + assert.isTrue(getFloorSpy.calledWith({currency: 'EUR', mediaType: 'banner', size: [728, 90]})); + assert.isTrue(getFloorSpy.calledTwice); + }); + + it('should not add floor info for a size when it is the same as the default', () => { + const bidReq = buildBidderRequest(); + const getFloorStub = sinon.stub(); + + getFloorStub + .returns({currency: 'EUR', floor: 1.9}) + .withArgs({currency: 'EUR', mediaType: BANNER, size: [160, 60]}) + .returns({currency: 'EUR', floor: 2.7}); + + bidReq.bids[0].getFloor = getFloorStub; + + const serverRequestInfo = spec.buildRequests(bidReq.bids, bidReq); + + const serverRequestBids = serverRequestInfo.data.bids; + const bid = serverRequestBids[0]; + + assert.nestedPropertyVal(bid, 'ban.fp.def', 1.9); + assert.nestedPropertyVal(bid, 'ban.fp.cur', 'EUR'); + assert.deepNestedPropertyVal(bid, 'ban.fp.siz', [{w: 160, h: 60, p: 2.7}]); + }); }); }); }); diff --git a/test/spec/modules/stvBidAdapter_spec.js b/test/spec/modules/stvBidAdapter_spec.js new file mode 100644 index 00000000000..41f29cced34 --- /dev/null +++ b/test/spec/modules/stvBidAdapter_spec.js @@ -0,0 +1,414 @@ +import { expect } from 'chai'; +import { spec } from 'modules/stvBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; + +const ENDPOINT_URL = 'https://ads.smartstream.tv/r/'; +const ENDPOINT_URL_DEV = 'https://ads.smartstream.tv/r/'; + +describe('stvAdapter', function() { + const adapter = newBidder(spec); + + describe('isBidRequestValid', function() { + let bid = { + 'bidder': 'stv', + 'params': { + 'placement': '6682', + 'floorprice': 1000000, + 'bcat': 'IAB2,IAB4', + 'dvt': 'desktop' + }, + 'sizes': [ + [300, 250] + ], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475' + }; + + it('should return true when required params found', function() { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not passed', function() { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'someIncorrectParam': 0 + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function() { + let bidRequests = [ + // banner + { + 'bidder': 'stv', + 'params': { + 'placement': '6682', + 'floorprice': 1000000, + 'geo': { + 'country': 'DE' + }, + 'bcat': 'IAB2,IAB4', + 'dvt': 'desktop' + }, + 'sizes': [ + [300, 250] + ], + 'bidId': '30b31c1838de1e1', + 'bidderRequestId': '22edbae2733bf61', + 'auctionId': '1d1a030790a475', + 'adUnitCode': 'testDiv1', + 'schain': { + 'ver': '1.0', + 'complete': 0, + 'nodes': [ + { + 'asi': 'reseller.com', + 'sid': 'aaaaa', + 'rid': 'BidRequest4', + 'hp': 1 + } + ] + } + }, + { + 'bidder': 'stv', + 'params': { + 'placement': '101', + 'devMode': true + }, + 'sizes': [ + [300, 250] + ], + 'bidId': '30b31c1838de1e2', + 'bidderRequestId': '22edbae2733bf62', + 'auctionId': '1d1a030790a476' + }, { + 'bidder': 'stv', + 'params': { + 'placement': '6682', + 'floorprice': 1000000, + 'geo': { + 'country': 'DE' + }, + 'bcat': 'IAB2,IAB4', + 'dvt': 'desktop' + }, + 'sizes': [ + [300, 250] + ], + 'bidId': '30b31c1838de1e3', + 'bidderRequestId': '22edbae2733bf69', + 'auctionId': '1d1a030790a477', + 'adUnitCode': 'testDiv2' + }, + // video + { + 'bidder': 'stv', + 'params': { + 'placement': '101', + 'devMode': true, + 'max_duration': 20, + }, + 'mediaTypes': { + 'video': { + 'playerSize': [640, 480], + 'context': 'instream' + }, + }, + + 'bidId': '30b31c1838de1e4', + 'bidderRequestId': '22edbae2733bf67', + 'auctionId': '1d1a030790a478', + 'adUnitCode': 'testDiv3' + }, + { + 'bidder': 'stv', + 'params': { + 'placement': '101', + 'devMode': true, + }, + 'mediaTypes': { + 'video': { + 'playerSize': [640, 480], + 'context': 'instream', + 'maxduration': 40, + } + }, + 'bidId': '30b31c1838de1e41', + 'bidderRequestId': '22edbae2733bf67', + 'auctionId': '1d1a030790a478', + 'adUnitCode': 'testDiv4' + }, + { + 'bidder': 'stv', + 'params': { + 'placement': '101', + 'devMode': true, + 'max_duration': 20, + }, + 'mediaTypes': { + 'video': { + 'playerSize': [640, 480], + 'context': 'instream', + 'maxduration': 40, + } + }, + 'bidId': '30b31c1838de1e41', + 'bidderRequestId': '22edbae2733bf67', + 'auctionId': '1d1a030790a478', + 'adUnitCode': 'testDiv4' + } + + ]; + + // With gdprConsent + var bidderRequest = { + refererInfo: { + referer: 'some_referrer.net' + }, + gdprConsent: { + consentString: 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==', + vendorData: { someData: 'value' }, + gdprApplies: true + } + }; + + var request1 = spec.buildRequests([bidRequests[0]], bidderRequest)[0]; + it('sends bid request 1 to our endpoint via GET', function() { + expect(request1.method).to.equal('GET'); + expect(request1.url).to.equal(ENDPOINT_URL); + let data = request1.data.replace(/rnd=\d+\&/g, '').replace(/ref=.*\&bid/g, 'bid').replace(/pbver=.*?&/g, 'pbver=test&'); + expect(data).to.equal('_f=html&alternative=prebid_js&_ps=6682&srw=300&srh=250&idt=100&bid_id=30b31c1838de1e1&pbver=test&schain=1.0,0!reseller.com,aaaaa,1,BidRequest4,,,&pfilter%5Bfloorprice%5D=1000000&pfilter%5Bgeo%5D%5Bcountry%5D=DE&gdpr_consent=BOJ%2FP2HOJ%2FP2HABABMAAAAAZ%2BA%3D%3D&gdpr=true&bcat=IAB2%2CIAB4&dvt=desktop&pbcode=testDiv1&media_types%5Bbanner%5D=300x250'); + }); + + var request2 = spec.buildRequests([bidRequests[1]], bidderRequest)[0]; + it('sends bid request 2 endpoint via GET', function() { + expect(request2.method).to.equal('GET'); + expect(request2.url).to.equal(ENDPOINT_URL); + let data = request2.data.replace(/rnd=\d+\&/g, '').replace(/ref=.*\&bid/g, 'bid').replace(/pbver=.*?&/g, 'pbver=test&'); + expect(data).to.equal('_f=html&alternative=prebid_js&_ps=101&srw=300&srh=250&idt=100&bid_id=30b31c1838de1e2&pbver=test&gdpr_consent=BOJ%2FP2HOJ%2FP2HABABMAAAAAZ%2BA%3D%3D&gdpr=true&prebidDevMode=1&media_types%5Bbanner%5D=300x250'); + }); + + // Without gdprConsent + var bidderRequestWithoutGdpr = { + refererInfo: { + referer: 'some_referrer.net' + } + }; + var request3 = spec.buildRequests([bidRequests[2]], bidderRequestWithoutGdpr)[0]; + it('sends bid request 3 without gdprConsent to our endpoint via GET', function() { + expect(request3.method).to.equal('GET'); + expect(request3.url).to.equal(ENDPOINT_URL); + let data = request3.data.replace(/rnd=\d+\&/g, '').replace(/ref=.*\&bid/g, 'bid').replace(/pbver=.*?&/g, 'pbver=test&'); + expect(data).to.equal('_f=html&alternative=prebid_js&_ps=6682&srw=300&srh=250&idt=100&bid_id=30b31c1838de1e3&pbver=test&pfilter%5Bfloorprice%5D=1000000&pfilter%5Bgeo%5D%5Bcountry%5D=DE&bcat=IAB2%2CIAB4&dvt=desktop&pbcode=testDiv2&media_types%5Bbanner%5D=300x250'); + }); + + var request4 = spec.buildRequests([bidRequests[3]], bidderRequestWithoutGdpr)[0]; + it('sends bid request 4 (video) without gdprConsent endpoint via GET', function() { + expect(request4.method).to.equal('GET'); + expect(request4.url).to.equal(ENDPOINT_URL); + let data = request4.data.replace(/rnd=\d+\&/g, '').replace(/ref=.*\&bid/g, 'bid').replace(/pbver=.*?&/g, 'pbver=test&'); + expect(data).to.equal('_f=vast2&alternative=prebid_js&_ps=101&srw=640&srh=480&idt=100&bid_id=30b31c1838de1e4&pbver=test&pfilter%5Bmax_duration%5D=20&prebidDevMode=1&pbcode=testDiv3&media_types%5Bvideo%5D=640x480'); + }); + + var request5 = spec.buildRequests([bidRequests[4]], bidderRequestWithoutGdpr)[0]; + it('sends bid request 5 (video) to our endpoint via GET', function() { + expect(request5.method).to.equal('GET'); + expect(request5.url).to.equal(ENDPOINT_URL); + let data = request5.data.replace(/rnd=\d+\&/g, '').replace(/ref=.*\&bid/g, 'bid').replace(/pbver=.*?&/g, 'pbver=test&'); + expect(data).to.equal('_f=vast2&alternative=prebid_js&_ps=101&srw=640&srh=480&idt=100&bid_id=30b31c1838de1e41&pbver=test&pfilter%5Bmax_duration%5D=40&prebidDevMode=1&pbcode=testDiv4&media_types%5Bvideo%5D=640x480'); + }); + + var request6 = spec.buildRequests([bidRequests[5]], bidderRequestWithoutGdpr)[0]; + it('sends bid request 6 (video) to our endpoint via GET', function() { + expect(request6.method).to.equal('GET'); + expect(request6.url).to.equal(ENDPOINT_URL); + let data = request6.data.replace(/rnd=\d+\&/g, '').replace(/ref=.*\&bid/g, 'bid').replace(/pbver=.*?&/g, 'pbver=test&'); + expect(data).to.equal('_f=vast2&alternative=prebid_js&_ps=101&srw=640&srh=480&idt=100&bid_id=30b31c1838de1e41&pbver=test&pfilter%5Bmax_duration%5D=20&prebidDevMode=1&pbcode=testDiv4&media_types%5Bvideo%5D=640x480'); + }); + }); + + describe('interpretResponse', function() { + let serverResponse = { + 'body': { + 'cpm': 5000000, + 'crid': 100500, + 'width': '300', + 'height': '250', + 'type': 'sspHTML', + 'tag': '', + 'requestId': '220ed41385952a', + 'currency': 'EUR', + 'ttl': 60, + 'netRevenue': true, + 'zone': '6682', + 'adomain': ['bdomain'] + } + }; + let serverVideoResponse = { + 'body': { + 'cpm': 5000000, + 'crid': 100500, + 'width': '300', + 'height': '250', + 'vastXml': '{"reason":7001,"status":"accepted"}', + 'requestId': '220ed41385952a', + 'type': 'vast2', + 'currency': 'EUR', + 'ttl': 60, + 'netRevenue': true, + 'zone': '6682' + } + }; + + let expectedResponse = [{ + requestId: '23beaa6af6cdde', + cpm: 0.5, + width: 0, + height: 0, + creativeId: 100500, + dealId: '', + currency: 'EUR', + netRevenue: true, + ttl: 300, + meta: { advertiserDomains: ['bdomain'] }, + ad: '', + }, { + requestId: '23beaa6af6cdde', + cpm: 0.5, + width: 0, + height: 0, + creativeId: 100500, + dealId: '', + currency: 'EUR', + netRevenue: true, + ttl: 300, + meta: { advertiserDomains: [] }, + vastXml: '{"reason":7001,"status":"accepted"}', + mediaType: 'video', + }]; + + it('should get the correct bid response by display ad', function() { + let bidRequest = [{ + 'method': 'GET', + 'url': ENDPOINT_URL, + 'data': { + 'bid_id': '30b31c1838de1e' + } + }]; + let result = spec.interpretResponse(serverResponse, bidRequest[0]); + expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); + expect(result[0].meta.advertiserDomains.length).to.equal(1); + expect(result[0].meta.advertiserDomains[0]).to.equal(expectedResponse[0].meta.advertiserDomains[0]); + }); + + it('should get the correct smartstream video bid response by display ad', function() { + let bidRequest = [{ + 'method': 'GET', + 'url': ENDPOINT_URL, + 'mediaTypes': { + 'video': { + 'playerSize': [640, 480], + 'context': 'instream' + } + }, + 'data': { + 'bid_id': '30b31c1838de1e' + } + }]; + let result = spec.interpretResponse(serverVideoResponse, bidRequest[0]); + expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[1])); + expect(result[0].meta.advertiserDomains.length).to.equal(0); + }); + + it('handles empty bid response', function() { + let response = { + body: {} + }; + let result = spec.interpretResponse(response); + expect(result.length).to.equal(0); + }); + }); + + describe(`getUserSyncs test usage`, function() { + let serverResponses; + + beforeEach(function() { + serverResponses = [{ + body: { + requestId: '23beaa6af6cdde', + cpm: 0.5, + width: 0, + height: 0, + creativeId: 100500, + dealId: '', + currency: 'EUR', + netRevenue: true, + ttl: 300, + type: 'sspHTML', + ad: '', + userSync: { + iframeUrl: ['anyIframeUrl?a=1'], + imageUrl: ['anyImageUrl', 'anyImageUrl2'] + } + } + }]; + }); + + it(`return value should be an array`, function() { + expect(spec.getUserSyncs({ iframeEnabled: true })).to.be.an('array'); + }); + it(`array should have only one object and it should have a property type = 'iframe'`, function() { + expect(spec.getUserSyncs({ iframeEnabled: true }, serverResponses).length).to.be.equal(1); + let [userSync] = spec.getUserSyncs({ iframeEnabled: true }, serverResponses); + expect(userSync).to.have.property('type'); + expect(userSync.type).to.be.equal('iframe'); + }); + it(`we have valid sync url for iframe`, function() { + let [userSync] = spec.getUserSyncs({ iframeEnabled: true }, serverResponses, { consentString: 'anyString' }); + expect(userSync.url).to.be.equal('anyIframeUrl?a=1&gdpr_consent=anyString') + expect(userSync.type).to.be.equal('iframe'); + }); + it(`we have valid sync url for image`, function() { + let [userSync] = spec.getUserSyncs({ pixelEnabled: true }, serverResponses, { gdprApplies: true, consentString: 'anyString' }); + expect(userSync.url).to.be.equal('anyImageUrl?gdpr=1&gdpr_consent=anyString') + expect(userSync.type).to.be.equal('image'); + }); + it(`we have valid sync url for image and iframe`, function() { + let userSync = spec.getUserSyncs({ iframeEnabled: true, pixelEnabled: true }, serverResponses, { gdprApplies: true, consentString: 'anyString' }); + expect(userSync.length).to.be.equal(3); + expect(userSync[0].url).to.be.equal('anyIframeUrl?a=1&gdpr=1&gdpr_consent=anyString') + expect(userSync[0].type).to.be.equal('iframe'); + expect(userSync[1].url).to.be.equal('anyImageUrl?gdpr=1&gdpr_consent=anyString') + expect(userSync[1].type).to.be.equal('image'); + expect(userSync[2].url).to.be.equal('anyImageUrl2?gdpr=1&gdpr_consent=anyString') + expect(userSync[2].type).to.be.equal('image'); + }); + }); + + describe(`getUserSyncs test usage in passback response`, function() { + let serverResponses; + + beforeEach(function() { + serverResponses = [{ + body: { + reason: 8002, + status: 'error', + msg: 'passback', + } + }]; + }); + + it(`check for zero array when iframeEnabled`, function() { + expect(spec.getUserSyncs({ iframeEnabled: true })).to.be.an('array'); + expect(spec.getUserSyncs({ iframeEnabled: true }, serverResponses).length).to.be.equal(0); + }); + it(`check for zero array when iframeEnabled`, function() { + expect(spec.getUserSyncs({ pixelEnabled: true })).to.be.an('array'); + expect(spec.getUserSyncs({ pixelEnabled: true }, serverResponses).length).to.be.equal(0); + }); + }); +}); diff --git a/test/spec/modules/taboolaBidAdapter_spec.js b/test/spec/modules/taboolaBidAdapter_spec.js index 0c01244033e..7d31e291667 100644 --- a/test/spec/modules/taboolaBidAdapter_spec.js +++ b/test/spec/modules/taboolaBidAdapter_spec.js @@ -5,14 +5,15 @@ import * as utils from '../../../src/utils' import {server} from '../../mocks/xhr' describe('Taboola Adapter', function () { - let hasLocalStorage, cookiesAreEnabled, getDataFromLocalStorage, localStorageIsEnabled, getCookie, commonBidRequest; + let sandbox, hasLocalStorage, cookiesAreEnabled, getDataFromLocalStorage, localStorageIsEnabled, getCookie, commonBidRequest; beforeEach(() => { - hasLocalStorage = sinon.stub(userData.storageManager, 'hasLocalStorage'); - cookiesAreEnabled = sinon.stub(userData.storageManager, 'cookiesAreEnabled'); - getCookie = sinon.stub(userData.storageManager, 'getCookie'); - getDataFromLocalStorage = sinon.stub(userData.storageManager, 'getDataFromLocalStorage'); - localStorageIsEnabled = sinon.stub(userData.storageManager, 'localStorageIsEnabled'); + sandbox = sinon.sandbox.create(); + hasLocalStorage = sandbox.stub(userData.storageManager, 'hasLocalStorage'); + cookiesAreEnabled = sandbox.stub(userData.storageManager, 'cookiesAreEnabled'); + getCookie = sandbox.stub(userData.storageManager, 'getCookie'); + getDataFromLocalStorage = sandbox.stub(userData.storageManager, 'getDataFromLocalStorage'); + localStorageIsEnabled = sandbox.stub(userData.storageManager, 'localStorageIsEnabled'); commonBidRequest = createBidRequest(); $$PREBID_GLOBAL$$.bidderSettings = { taboola: { @@ -22,12 +23,7 @@ describe('Taboola Adapter', function () { }); afterEach(() => { - hasLocalStorage.restore(); - cookiesAreEnabled.restore(); - getCookie.restore(); - getDataFromLocalStorage.restore(); - localStorageIsEnabled.restore(); - + sandbox.restore(); $$PREBID_GLOBAL$$.bidderSettings = {}; }) @@ -124,6 +120,7 @@ describe('Taboola Adapter', function () { } const commonBidderRequest = { + bidderRequestId: 'mock-uuid', refererInfo: { page: 'https://example.com/ref', ref: 'https://ref', @@ -133,6 +130,7 @@ describe('Taboola Adapter', function () { it('should build display request', function () { const expectedData = { + id: 'mock-uuid', 'imp': [{ 'id': 1, 'banner': { @@ -668,6 +666,107 @@ describe('Taboola Adapter', function () { const res = spec.interpretResponse(serverResponse, request); expect(res).to.deep.equal(expectedRes); }); + + it('should replace AUCTION_PRICE macro in adm', function () { + const multiRequest = { + bids: [ + { + ...createBidRequest(), + ...displayBidRequestParams + }, + { + ...createBidRequest(), + ...displayBidRequestParams + } + ] + } + const multiServerResponseWithMacro = { + body: { + 'id': '49ffg4d58ef9a163a69fhgfghd4fad03621b9e036f24f7_15', + 'seatbid': [ + { + 'bid': [ + { + 'id': '0b3dd94348-134b-435f-8db5-6bf5afgfc39e86c', + 'impid': '2', + 'price': 0.34, + 'adid': '2785119545551083381', + 'adm': 'ADM2,\\nwp:\'${AUCTION_PRICE}\'', + 'adomain': [ + 'example.xyz' + ], + 'cid': '15744349', + 'crid': '278195503434041083381', + 'w': 300, + 'h': 250, + 'exp': 60, + 'lurl': 'http://us-trc.taboola.com/sample', + 'nurl': 'http://win.example.com/' + }, + { + 'id': '0b3dd94348-134b-435f-8db5-6bf5afgfc39e86c', + 'impid': '1', + 'price': 0.35, + 'adid': '2785119545551083381', + 'adm': 'ADM2,\\nwp:\'${AUCTION_PRICE}\'', + 'adomain': [ + 'example.xyz' + ], + 'cid': '15744349', + 'crid': '278195503434041083381', + 'w': 300, + 'h': 250, + 'exp': 60, + 'lurl': 'http://us-trc.taboola.com/sample', + 'nurl': 'http://win.example.com/' + + } + ], + 'seat': '14204545260' + } + ], + 'bidid': 'da43860a-4644-442a-b5e0-93f268cf8d19', + 'cur': 'USD' + } + }; + const [bid] = multiServerResponseWithMacro.body.seatbid[0].bid; + const expectedRes = [ + { + requestId: multiRequest.bids[1].bidId, + cpm: multiServerResponseWithMacro.body.seatbid[0].bid[0].price, + creativeId: bid.crid, + ttl: 60, + netRevenue: true, + currency: multiServerResponseWithMacro.body.cur, + mediaType: 'banner', + ad: 'ADM2,\\nwp:\'0.34\'', + width: bid.w, + height: bid.h, + nurl: 'http://win.example.com/', + meta: { + 'advertiserDomains': bid.adomain + }, + }, + { + requestId: multiRequest.bids[0].bidId, + cpm: multiServerResponseWithMacro.body.seatbid[0].bid[1].price, + creativeId: bid.crid, + ttl: 60, + netRevenue: true, + currency: multiServerResponseWithMacro.body.cur, + mediaType: 'banner', + ad: 'ADM2,\\nwp:\'0.35\'', + width: bid.w, + height: bid.h, + nurl: 'http://win.example.com/', + meta: { + 'advertiserDomains': bid.adomain + }, + } + ]; + const res = spec.interpretResponse(multiServerResponseWithMacro, multiRequest); + expect(res).to.deep.equal(expectedRes); + }); }) describe('getUserSyncs', function () { diff --git a/test/spec/modules/teadsBidAdapter_spec.js b/test/spec/modules/teadsBidAdapter_spec.js index e981d80c8ae..a4b94135bad 100644 --- a/test/spec/modules/teadsBidAdapter_spec.js +++ b/test/spec/modules/teadsBidAdapter_spec.js @@ -121,6 +121,13 @@ describe('teadsBidAdapter', () => { expect(request.method).to.equal('POST'); }); + it('should not send auctionId in bid request ', function() { + const request = spec.buildRequests(bidRequests, bidderResquestDefault); + const payload = JSON.parse(request.data); + + expect(payload.data[0].auctionId).to.not.exist + }); + it('should send US Privacy to endpoint', function() { let usPrivacy = 'OHHHFCP1' let bidderRequest = { diff --git a/test/spec/modules/terceptAnalyticsAdapter_spec.js b/test/spec/modules/terceptAnalyticsAdapter_spec.js index 594e1e5f5b4..a1384bfd919 100644 --- a/test/spec/modules/terceptAnalyticsAdapter_spec.js +++ b/test/spec/modules/terceptAnalyticsAdapter_spec.js @@ -709,7 +709,6 @@ describe('tercept analytics adapter', function () { 'bidsReceived': [], 'winningBids': [], 'timeout': 1000, - 'config': initOptions }, 'initOptions': initOptions }; diff --git a/test/spec/modules/topicsFpdModule_spec.js b/test/spec/modules/topicsFpdModule_spec.js index 958489f2728..22d7a98d45d 100644 --- a/test/spec/modules/topicsFpdModule_spec.js +++ b/test/spec/modules/topicsFpdModule_spec.js @@ -372,11 +372,16 @@ describe('getCachedTopics()', () => { } ]; - const evt = { + const evt_pm = { data: '{"segment":{"domain":"ads.pubmatic.com","topics":[{"configVersion":"chrome.1","modelVersion":"2206021246","taxonomyVersion":"1","topic":165,"version":"chrome.1:1:2206021246"}],"bidder":"pubmatic"},"date":1669743901858}', origin: 'https://ads.pubmatic.com' }; + const evt_rh = { + data: '{"segment":{"domain":"topics.authorizedvault.com","topics":[{"configVersion":"chrome.1","modelVersion":"2206021246","taxonomyVersion":"1","topic":165,"version":"chrome.1:1:2206021246"}],"bidder":"rtbhouse"},"date":1669743901858}', + origin: 'https://topics.authorizedvault.com' + }; + let gdprDataHdlrStub; beforeEach(() => { gdprDataHdlrStub = sinon.stub(gdprDataHandler, 'getConsentData'); @@ -413,9 +418,10 @@ describe('getCachedTopics()', () => { it('should stored segments if receiveMessage event is triggerred with segment data', () => { return processFpd({}, {global: {}}, {data: Promise.resolve(mockData)}) .then(({global}) => { - receiveMessage(evt); + receiveMessage(evt_pm); + receiveMessage(evt_rh); let segments = new Map(safeJSONParse(storage.getDataFromLocalStorage(topicStorageName))); - expect(segments.has('pubmatic')).to.equal(true); + expect(segments.has('pubmatic') || segments.has('rtbhouse')).to.equal(true); }); }); @@ -424,7 +430,7 @@ describe('getCachedTopics()', () => { storage.setDataInLocalStorage(topicStorageName, storedSegments); return processFpd({}, {global: {}}, {data: Promise.resolve(mockData)}) .then(({global}) => { - receiveMessage(evt); + receiveMessage(evt_pm); let segments = new Map(safeJSONParse(storage.getDataFromLocalStorage(topicStorageName))); expect(segments.get('pubmatic')[2206021246].segment.length).to.equal(1); }); diff --git a/test/spec/modules/trafficgateBidAdapter_spec.js b/test/spec/modules/trafficgateBidAdapter_spec.js index ecea0e69ca1..11ff547cc78 100644 --- a/test/spec/modules/trafficgateBidAdapter_spec.js +++ b/test/spec/modules/trafficgateBidAdapter_spec.js @@ -1,223 +1,1373 @@ -import { expect } from 'chai' -import { spec } from '../../../modules/trafficgateBidAdapter' -import { deepStrictEqual, notStrictEqual, ok, strictEqual } from 'assert' - -describe('TrafficGateAdapter', () => { - const bid = { - bidId: '9ec5b177515ee2e5', - bidder: 'trafficgate', +import {expect} from 'chai'; +import {spec} from 'modules/trafficgateBidAdapter'; +import {newBidder} from 'src/adapters/bidderFactory.js'; +import {BANNER, VIDEO} from 'src/mediaTypes.js'; +import {config} from 'src/config.js'; +import * as utils from 'src/utils.js'; +import 'src/prebid.js' +import 'modules/currency.js'; +import 'modules/userId/index.js'; +import 'modules/multibid/index.js'; +import 'modules/priceFloors.js'; +import 'modules/consentManagement.js'; +import 'modules/consentManagementUsp.js'; +import 'modules/schain.js'; +import {deepClone} from 'src/utils.js'; +import {syncAddFPDToBidderRequest} from '../../helpers/fpd.js'; +import {hook} from '../../../src/hook.js'; + +const BidRequestBuilder = function BidRequestBuilder(options) { + const defaults = { + request: { + auctionId: '4fd1ca2d-846c-4211-b9e5-321dfe1709c9', + adUnitCode: 'adunit-code', + bidder: 'trafficgate' + }, params: { - placementId: 1, + placementId: '98765', host: 'example' }, - mediaTypes: { - banner: { - sizes: [[300, 250]] - } + sizes: [[300, 250], [300, 600]], + }; + + const request = { + ...defaults.request, + ...options + }; + + this.withParams = (options) => { + request.params = { + ...defaults.params, + ...options + }; + return this; + }; + + this.build = () => request; +}; + +const BidderRequestBuilder = function BidderRequestBuilder(options) { + const defaults = { + bidderCode: 'trafficgate', + auctionId: '4fd1ca2d-846c-4211-b9e5-321dfe1709c9', + bidderRequestId: '7g36s867Tr4xF90X', + timeout: 3000, + refererInfo: { + numIframes: 0, + reachedTop: true, + referer: 'http://test.io/index.html?pbjs_debug=true' } - } - - describe('isBidRequestValid', () => { - it('Should return true if there are bidId, params and placementId parameters present', () => { - strictEqual(true, spec.isBidRequestValid(bid)) - }) - - it('Should return false if at least one of parameters is not present', () => { - const b = { ...bid } - delete b.params.placementId - strictEqual(false, spec.isBidRequestValid(b)) - }) - - it('Should return false if at least one of parameters is not present', () => { - const b = { ...bid } - delete b.params.host - strictEqual(false, spec.isBidRequestValid(b)) - }) - }) - - describe('buildRequests', () => { - const serverRequest = spec.buildRequests([bid]) - - it('Creates a ServerRequest object with method, URL and data', () => { - ok(serverRequest) - ok(serverRequest.method) - ok(serverRequest.url) - ok(serverRequest.data) - }) - - it('Returns POST method', () => { - strictEqual('POST', serverRequest.method) - }) - - it('Returns valid URL', () => { - strictEqual('https://example.bc-plugin.com/?c=o&m=multi', serverRequest.url) - }) - - it('Returns valid data if array of bids is valid', () => { - const { data } = serverRequest - strictEqual('object', typeof data) - deepStrictEqual(['language', 'secure', 'host', 'page', 'placements'], Object.keys(data)) - strictEqual('string', typeof data.language) - strictEqual('string', typeof data.host) - strictEqual('string', typeof data.page) - notStrictEqual(-1, [0, 1].indexOf(data.secure)) - - const placement = data.placements[0] - deepStrictEqual(['placementId', 'bidId', 'traffic'], Object.keys(placement)) - strictEqual(1, placement.placementId) - strictEqual('9ec5b177515ee2e5', placement.bidId) - strictEqual('banner', placement.traffic) - }) - - it('Returns empty data if no valid requests are passed', () => { - deepStrictEqual([], spec.buildRequests([])) - }) - }) - - describe('interpretResponse', () => { - const validData = [ - { - body: [{ - mediaType: 'banner', - width: 300, - height: 250, - cpm: 0.4, - ad: 'Test', - requestId: '9ec5b177515ee2e5', - ttl: 120, - creativeId: '2', - netRevenue: true, - currency: 'USD', - dealId: '1', - meta: { - advertiserDomains: ['test.com'] + }; + + const request = { + ...defaults, + ...options + }; + + this.build = () => request; +}; + +describe('TrafficgateOpenxRtbAdapter', function () { + before(() => { + hook.ready(); + }); + + const adapter = newBidder(spec); + + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid()', function () { + describe('when request is for a banner ad', function () { + let bannerBid; + beforeEach(function () { + bannerBid = { + bidder: 'trafficgate', + params: {}, + adUnitCode: 'adunit-code', + mediaTypes: {banner: {}}, + sizes: [[300, 250], [300, 600]], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475' + }; + }); + + it('should return false when there is placementId only', function () { + bannerBid.params = {'placementId': '98765'}; + expect(spec.isBidRequestValid(bannerBid)).to.equal(false); + }); + + describe('should return false when there is a host only', function () { + beforeEach(function () { + bannerBid.params = {host: 'test-delivery-domain'} + }); + + it('should return false when there is no placementId and size', function () { + expect(spec.isBidRequestValid(bannerBid)).to.equal(false); + }); + + it('should return false if there is an placementId without sizes', function () { + bannerBid.params.placementId = '98765'; + expect(spec.isBidRequestValid(bannerBid)).to.equal(false); + }); + + it('should return false if there is no placementId and sizes are defined', function () { + bannerBid.mediaTypes.banner.sizes = [720, 90]; + expect(spec.isBidRequestValid(bannerBid)).to.equal(false); + }); + + it('should return false if no sizes are defined ', function () { + expect(spec.isBidRequestValid(bannerBid)).to.equal(false); + }); + + it('should return false if sizes empty ', function () { + bannerBid.mediaTypes.banner.sizes = []; + expect(spec.isBidRequestValid(bannerBid)).to.equal(false); + }); + + it('should return true if there is placementId and sizes are defined', function () { + bannerBid.params.placementId = '98765'; + bannerBid.mediaTypes.banner.sizes = [720, 90]; + expect(spec.isBidRequestValid(bannerBid)).to.equal(true); + }); + }); + }); + + describe('when request is for a multiformat ad', function () { + describe('and request config uses mediaTypes video and banner', () => { + const multiformatBid = { + bidder: 'trafficgate', + params: { + placementId: '98765', + host: 'example' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250]] + }, + video: { + playerSize: [300, 250] + } + }, + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + transactionId: '4008d88a-8137-410b-aa35-fbfdabcb478e' + }; + it('should return true multisize when required params found', function () { + expect(spec.isBidRequestValid(multiformatBid)).to.equal(true); + }); + }); + }); + + describe('when request is for a video ad', function () { + describe('and request config uses mediaTypes', () => { + const videoBidWithMediaTypes = { + bidder: 'trafficgate', + params: { + placementId: '98765', + host: 'example' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + video: { + playerSize: [640, 480] + } + }, + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + transactionId: '4008d88a-8137-410b-aa35-fbfdabcb478e' + }; + it('should return false when isBannerBid', function () { + expect(spec.isBannerBid(videoBidWithMediaTypes)).to.equal(false); + }); + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(videoBidWithMediaTypes)).to.equal(true); + }); + + it('should return false when required params are not passed', function () { + let videoBidWithMediaTypes = Object.assign({}, videoBidWithMediaTypes); + videoBidWithMediaTypes.params = {}; + expect(spec.isBidRequestValid(videoBidWithMediaTypes)).to.equal(false); + }); + }); + describe('and request config uses both host and platform', () => { + const videoBidWithHostAndPlacement = { + bidder: 'trafficgate', + params: { + placementId: '98765', + host: 'example' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + video: { + playerSize: [640, 480] + } + }, + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + transactionId: '4008d88a-8137-410b-aa35-fbfdabcb478e' + }; + it('should return false when isBannerBid', function () { + expect(spec.isBannerBid(videoBidWithHostAndPlacement)).to.equal(false); + }); + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(videoBidWithHostAndPlacement)).to.equal(true); + }); + + it('should return false when required params are not passed', function () { + let videoBidWithMediaTypes = Object.assign({}, videoBidWithHostAndPlacement); + videoBidWithMediaTypes.params = {}; + expect(spec.isBidRequestValid(videoBidWithMediaTypes)).to.equal(false); + }); + }); + describe('and request config uses mediaType', () => { + const videoBidWithMediaType = { + 'bidder': 'trafficgate', + 'params': { + 'placementId': '98765', + 'host': 'example' + }, + 'adUnitCode': 'adunit-code', + 'mediaType': 'video', + 'sizes': [640, 480], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + 'transactionId': '4008d88a-8137-410b-aa35-fbfdabcb478e' + }; + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(videoBidWithMediaType)).to.equal(true); + }); + + it('should return false when required params are not passed', function () { + let videoBidWithMediaType = Object.assign({}, videoBidWithMediaType); + delete videoBidWithMediaType.params; + videoBidWithMediaType.params = {}; + expect(spec.isBidRequestValid(videoBidWithMediaType)).to.equal(false); + }); + }); + }); + }); + + describe('buildRequests()', function () { + let bidRequestsWithMediaTypes; + let bidRequestsWithPlatform; + let mockBidderRequest; + + beforeEach(function () { + mockBidderRequest = {refererInfo: {}}; + + bidRequestsWithMediaTypes = [{ + bidder: 'trafficgate', + params: { + placementId: '11', + host: 'example', + }, + adUnitCode: '/adunit-code/test-path', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] } - }] - }, - { - body: [{ - vastUrl: 'example.com', - mediaType: 'video', - cpm: 0.5, - requestId: '9ec5b177515ee2e5', - ttl: 120, - creativeId: '2', - netRevenue: true, - currency: 'USD', - dealId: '1', - meta: { - advertiserDomains: ['test.com'] + }, + bidId: 'test-bid-id-1', + bidderRequestId: 'test-bid-request-1', + auctionId: 'test-auction-1', + transactionId: 'test-transactionId-1', + ortb2Imp: { + ext: { + ae: 2 } - }] - }, - { - body: [{ - mediaType: 'native', - clickUrl: 'example.com', - title: 'Test', - image: 'example.com', - creativeId: '2', - impressionTrackers: ['example.com'], - ttl: 120, - cpm: 0.4, - requestId: '9ec5b177515ee2e5', - netRevenue: true, - currency: 'USD', - meta: { - advertiserDomains: ['test.com'] + } + }, { + bidder: 'trafficgate', + params: { + placementId: '22', + host: 'example', + }, + adUnitCode: 'adunit-code', + mediaTypes: { + video: { + playerSize: [640, 480] } - }] - } - ] + }, + bidId: 'test-bid-id-2', + bidderRequestId: 'test-bid-request-2', + auctionId: 'test-auction-2', + transactionId: 'test-transactionId-2' + }]; + }); - for (const obj of validData) { - const { mediaType } = obj.body[0] + context('common requests checks', function() { + it('should be able to handle multiformat requests', () => { + const multiformat = utils.deepClone(bidRequestsWithMediaTypes[0]); + multiformat.mediaTypes.video = { + context: 'outstream', + playerSize: [640, 480] + } + const requests = spec.buildRequests([multiformat], mockBidderRequest); + const outgoingFormats = requests.flatMap(rq => rq.data.imp.flatMap(imp => ['banner', 'video'].filter(k => imp[k] != null))); + const expected = FEATURES.VIDEO ? ['banner', 'video'] : ['banner'] + expect(outgoingFormats).to.have.members(expected); + }) - it(`Should interpret ${mediaType} response`, () => { - const response = spec.interpretResponse(obj) + it('should send bid request to trafficgate url via POST', function () { + const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); + expect(request[0].url).to.equal('https://example.bc-plugin.com/prebidjs'); + expect(request[0].method).to.equal('POST'); + }); - expect(response).to.be.an('array') - strictEqual(1, response.length) + it('should send delivery domain, if available', function () { + const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); + expect(request[0].data.imp[0].ext.bidder.host).to.equal(bidRequestsWithMediaTypes[0].params.host); + expect(request[1].data.imp[0].ext.bidder.host).to.equal(bidRequestsWithMediaTypes[1].params.host); + }); - const copy = { ...obj.body[0] } - deepStrictEqual(copy, response[0]) - }) - } + it('should send placementId', function () { + const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); + expect(request[0].data.imp[0].ext.bidder.placementId).to.equal(bidRequestsWithMediaTypes[0].params.placementId); + expect(request[1].data.imp[0].ext.bidder.placementId).to.equal(bidRequestsWithMediaTypes[1].params.placementId); + }); + + describe('floors', function () { + it('should send out custom floors on bids that have customFloors, no currency as account currency is used', function () { + const bidRequest = Object.assign({}, + bidRequestsWithMediaTypes[0], + { + params: { + placementId: '98765', + host: 'example', + customFloor: 1.500 + } + } + ); - for (const obj of validData) { - it(`Should interpret response has meta.advertiserDomains`, () => { - const response = spec.interpretResponse(obj) + const request = spec.buildRequests([bidRequest], mockBidderRequest); + expect(request[0].data.imp[0].bidfloor).to.equal(bidRequest.params.customFloor); + expect(request[0].data.imp[0].bidfloorcur).to.equal(undefined); + }); - expect(response[0]['meta']['advertiserDomains']).to.be.an('array') - expect(response[0]['meta']['advertiserDomains'][0]).to.be.an('string') + context('with floors module', function () { + let adServerCurrencyStub; + + beforeEach(function () { + adServerCurrencyStub = sinon + .stub(config, 'getConfig') + .withArgs('currency.adServerCurrency') + }); + + afterEach(function () { + config.getConfig.restore(); + }); + + it('should send out floors on bids in USD', function () { + const bidRequest = Object.assign({}, + bidRequestsWithMediaTypes[0], + { + getFloor: () => { + return { + currency: 'USD', + floor: 9.99 + } + } + } + ); + + const request = spec.buildRequests([bidRequest], mockBidderRequest); + expect(request[0].data.imp[0].bidfloor).to.equal(9.99); + expect(request[0].data.imp[0].bidfloorcur).to.equal('USD'); + }); + + it('should send not send floors', function () { + adServerCurrencyStub.returns('EUR'); + const bidRequest = Object.assign({}, + bidRequestsWithMediaTypes[0], + { + getFloor: () => { + return { + currency: 'BTC', + floor: 9.99 + } + } + } + ); + + const request = spec.buildRequests([bidRequest], mockBidderRequest); + expect(request[0].data.imp[0].bidfloor).to.equal(undefined) + expect(request[0].data.imp[0].bidfloorcur).to.equal(undefined) + }); + }) }) + + describe('FPD', function() { + let bidRequests; + const mockBidderRequest = {refererInfo: {}}; + + beforeEach(function () { + bidRequests = [{ + bidder: 'trafficgate', + params: { + placementId: '98765-banner', + host: 'example' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + bidId: 'test-bid-id', + bidderRequestId: 'test-bidder-request-id', + auctionId: 'test-auction-id', + transactionId: 'test-transaction-id-1' + }, { + bidder: 'trafficgate', + mediaTypes: { + video: { + playerSize: [640, 480] + } + }, + params: { + placementId: '98765-video', + host: 'example' + }, + 'adUnitCode': 'adunit-code', + + bidId: 'test-bid-id', + bidderRequestId: 'test-bidder-request-id', + auctionId: 'test-auction-id', + transactionId: 'test-transaction-id-2' + }]; + }); + + it('ortb2.site should be merged in the request', function() { + const request = spec.buildRequests(bidRequests, { + ...mockBidderRequest, + 'ortb2': { + site: { + domain: 'page.example.com', + cat: ['IAB2'], + sectioncat: ['IAB2-2'] + } + } + }); + let data = request[0].data; + expect(data.site.domain).to.equal('page.example.com'); + expect(data.site.cat).to.deep.equal(['IAB2']); + expect(data.site.sectioncat).to.deep.equal(['IAB2-2']); + }); + + it('ortb2.user should be merged in the request', function() { + const request = spec.buildRequests(bidRequests, { + ...mockBidderRequest, + 'ortb2': { + user: { + yob: 1985 + } + } + }); + let data = request[0].data; + expect(data.user.yob).to.equal(1985); + }); + + describe('ortb2Imp', function() { + describe('ortb2Imp.ext.data.pbadslot', function() { + beforeEach(function () { + if (bidRequests[0].hasOwnProperty('ortb2Imp')) { + delete bidRequests[0].ortb2Imp; + } + }); + + it('should not send if imp[].ext.data object is invalid', function() { + bidRequests[0].ortb2Imp = { + ext: {} + }; + const request = spec.buildRequests(bidRequests, mockBidderRequest); + let data = request[0].data; + expect(data.imp[0].ext).to.not.have.property('data'); + }); + + it('should not send if imp[].ext.data.pbadslot is undefined', function() { + bidRequests[0].ortb2Imp = { + ext: { + data: { + } + } + }; + const request = spec.buildRequests(bidRequests, mockBidderRequest); + let data = request[0].data; + if (data.imp[0].ext.data) { + expect(data.imp[0].ext.data).to.not.have.property('pbadslot'); + } else { + expect(data.imp[0].ext).to.not.have.property('data'); + } + }); + + it('should send if imp[].ext.data.pbadslot is string', function() { + bidRequests[0].ortb2Imp = { + ext: { + data: { + pbadslot: 'abcd' + } + } + }; + const request = spec.buildRequests(bidRequests, mockBidderRequest); + let data = request[0].data; + expect(data.imp[0].ext.data).to.have.property('pbadslot'); + expect(data.imp[0].ext.data.pbadslot).to.equal('abcd'); + }); + }); + + describe('ortb2Imp.ext.data.adserver', function() { + beforeEach(function () { + if (bidRequests[0].hasOwnProperty('ortb2Imp')) { + delete bidRequests[0].ortb2Imp; + } + }); + + it('should not send if imp[].ext.data object is invalid', function() { + bidRequests[0].ortb2Imp = { + ext: {} + }; + const request = spec.buildRequests(bidRequests, mockBidderRequest); + let data = request[0].data; + expect(data.imp[0].ext).to.not.have.property('data'); + }); + + it('should not send if imp[].ext.data.adserver is undefined', function() { + bidRequests[0].ortb2Imp = { + ext: { + data: { + } + } + }; + const request = spec.buildRequests(bidRequests, mockBidderRequest); + let data = request[0].data; + if (data.imp[0].ext.data) { + expect(data.imp[0].ext.data).to.not.have.property('adserver'); + } else { + expect(data.imp[0].ext).to.not.have.property('data'); + } + }); + + it('should send', function() { + let adSlotValue = 'abc'; + bidRequests[0].ortb2Imp = { + ext: { + data: { + adserver: { + name: 'GAM', + adslot: adSlotValue + } + } + } + }; + const request = spec.buildRequests(bidRequests, mockBidderRequest); + let data = request[0].data; + expect(data.imp[0].ext.data.adserver.name).to.equal('GAM'); + expect(data.imp[0].ext.data.adserver.adslot).to.equal(adSlotValue); + }); + }); + + describe('ortb2Imp.ext.data.other', function() { + beforeEach(function () { + if (bidRequests[0].hasOwnProperty('ortb2Imp')) { + delete bidRequests[0].ortb2Imp; + } + }); + + it('should not send if imp[].ext.data object is invalid', function() { + bidRequests[0].ortb2Imp = { + ext: {} + }; + const request = spec.buildRequests(bidRequests, mockBidderRequest); + let data = request[0].data; + expect(data.imp[0].ext).to.not.have.property('data'); + }); + + it('should not send if imp[].ext.data.other is undefined', function() { + bidRequests[0].ortb2Imp = { + ext: { + data: { + } + } + }; + const request = spec.buildRequests(bidRequests, mockBidderRequest); + let data = request[0].data; + if (data.imp[0].ext.data) { + expect(data.imp[0].ext.data).to.not.have.property('other'); + } else { + expect(data.imp[0].ext).to.not.have.property('data'); + } + }); + + it('ortb2Imp.ext.data.other', function() { + bidRequests[0].ortb2Imp = { + ext: { + data: { + other: 1234 + } + } + }; + const request = spec.buildRequests(bidRequests, mockBidderRequest); + let data = request[0].data; + expect(data.imp[0].ext.data.other).to.equal(1234); + }); + }); + }); + + describe('with user agent client hints', function () { + it('should add device.sua if available', function () { + const bidderRequestWithUserAgentClientHints = { refererInfo: {}, + ortb2: { + device: { + sua: { + source: 2, + platform: { + brand: 'macOS', + version: [ '12', '4', '0' ] + }, + browsers: [ + { + brand: 'Chromium', + version: [ '106', '0', '5249', '119' ] + }, + { + brand: 'Google Chrome', + version: [ '106', '0', '5249', '119' ] + }, + { + brand: 'Not;A=Brand', + version: [ '99', '0', '0', '0' ] + }], + mobile: 0, + model: 'Pro', + bitness: '64', + architecture: 'x86' + } + } + }}; + + let request = spec.buildRequests(bidRequests, bidderRequestWithUserAgentClientHints); + expect(request[0].data.device.sua).to.exist; + expect(request[0].data.device.sua).to.deep.equal(bidderRequestWithUserAgentClientHints.ortb2.device.sua); + const bidderRequestWithoutUserAgentClientHints = {refererInfo: {}, ortb2: {}}; + request = spec.buildRequests(bidRequests, bidderRequestWithoutUserAgentClientHints); + expect(request[0].data.device?.sua).to.not.exist; + }); + }); + }); + + context('when there is a consent management framework', function () { + let bidRequests; + let mockConfig; + let bidderRequest; + + beforeEach(function () { + bidRequests = [{ + bidder: 'trafficgate', + params: { + placementId: '98765-banner', + host: 'example' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + bidId: 'test-bid-id', + bidderRequestId: 'test-bidder-request-id', + auctionId: 'test-auction-id', + transactionId: 'test-transaction-id-1' + }, { + bidder: 'trafficgate', + mediaTypes: { + video: { + playerSize: [640, 480] + } + }, + params: { + placementId: '98765-video', + host: 'example' + }, + 'adUnitCode': 'adunit-code', + + bidId: 'test-bid-id', + bidderRequestId: 'test-bidder-request-id', + auctionId: 'test-auction-id', + transactionId: 'test-transaction-id-2' + }]; + }); + + describe('us_privacy', function () { + beforeEach(function () { + bidderRequest = { + uspConsent: '1YYN', + refererInfo: {} + }; + + sinon.stub(config, 'getConfig').callsFake((key) => { + return utils.deepAccess(mockConfig, key); + }); + }); + + afterEach(function () { + config.getConfig.restore(); + }); + + it('should send a signal to specify that US Privacy applies to this request', function () { + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + expect(request[0].data.regs.ext.us_privacy).to.equal('1YYN'); + expect(request[1].data.regs.ext.us_privacy).to.equal('1YYN'); + }); + + it('should not send the regs object, when consent string is undefined', function () { + delete bidderRequest.uspConsent; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + expect(request[0].data.regs?.us_privacy).to.not.exist; + }); + }); + + describe('GDPR', function () { + beforeEach(function () { + bidderRequest = { + gdprConsent: { + consentString: 'test-gdpr-consent-string', + addtlConsent: 'test-addtl-consent-string', + gdprApplies: true + }, + refererInfo: {} + }; + + mockConfig = { + consentManagement: { + cmpApi: 'iab', + timeout: 1111, + allowAuctionWithoutConsent: 'cancel' + } + }; + + sinon.stub(config, 'getConfig').callsFake((key) => { + return utils.deepAccess(mockConfig, key); + }); + }); + + afterEach(function () { + config.getConfig.restore(); + }); + + it('should send a signal to specify that GDPR applies to this request', function () { + bidderRequest.bids = bidRequests; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + expect(request[0].data.regs.ext.gdpr).to.equal(1); + expect(request[1].data.regs.ext.gdpr).to.equal(1); + }); + + it('should send the consent string', function () { + bidderRequest.bids = bidRequests; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + expect(request[0].data.user.ext.consent).to.equal(bidderRequest.gdprConsent.consentString); + expect(request[1].data.user.ext.consent).to.equal(bidderRequest.gdprConsent.consentString); + }); + + it('should send the addtlConsent string', function () { + bidderRequest.bids = bidRequests; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + expect(request[0].data.user.ext.ConsentedProvidersSettings.consented_providers).to.equal(bidderRequest.gdprConsent.addtlConsent); + expect(request[1].data.user.ext.ConsentedProvidersSettings.consented_providers).to.equal(bidderRequest.gdprConsent.addtlConsent); + }); + + it('should send a signal to specify that GDPR does not apply to this request', function () { + bidderRequest.gdprConsent.gdprApplies = false; + bidderRequest.bids = bidRequests; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + expect(request[0].data.regs.ext.gdpr).to.equal(0); + expect(request[1].data.regs.ext.gdpr).to.equal(0); + }); + + it('when GDPR application is undefined, should not send a signal to specify whether GDPR applies to this request, ' + + 'but can send consent data, ', function () { + delete bidderRequest.gdprConsent.gdprApplies; + bidderRequest.bids = bidRequests; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + expect(request[0].data.regs?.ext?.gdpr).to.not.be.ok; + expect(request[0].data.user.ext.consent).to.equal(bidderRequest.gdprConsent.consentString); + expect(request[1].data.user.ext.consent).to.equal(bidderRequest.gdprConsent.consentString); + }); + + it('when consent string is undefined, should not send the consent string, ', function () { + delete bidderRequest.gdprConsent.consentString; + bidderRequest.bids = bidRequests; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + expect(request[0].data.imp[0].ext.consent).to.equal(undefined); + expect(request[1].data.imp[0].ext.consent).to.equal(undefined); + }); + }); + }); + + context('coppa', function() { + it('when there are no coppa param settings, should not send a coppa flag', function () { + const request = spec.buildRequests(bidRequestsWithMediaTypes, syncAddFPDToBidderRequest(mockBidderRequest)); + expect(request[0].data.regs?.coppa).to.be.not.ok; + }); + + it('should send a coppa flag there is when there is coppa param settings in the bid requests', function () { + let mockConfig = { + coppa: true + }; + + sinon.stub(config, 'getConfig').callsFake((key) => { + return utils.deepAccess(mockConfig, key); + }); + + const request = spec.buildRequests(bidRequestsWithMediaTypes, syncAddFPDToBidderRequest(mockBidderRequest)); + expect(request[0].data.regs.coppa).to.equal(1); + }); + + it('should send a coppa flag there is when there is coppa param settings in the bid params', function () { + const request = spec.buildRequests(bidRequestsWithMediaTypes, syncAddFPDToBidderRequest(mockBidderRequest)); + request.params = {coppa: true}; + expect(request[0].data.regs.coppa).to.equal(1); + }); + + after(function () { + config.getConfig.restore() + }); + }); + + context('do not track (DNT)', function() { + let doNotTrackStub; + + beforeEach(function () { + doNotTrackStub = sinon.stub(utils, 'getDNT'); + }); + afterEach(function() { + doNotTrackStub.restore(); + }); + + it('when there is a do not track, should send a dnt', function () { + doNotTrackStub.returns(1); + + const request = spec.buildRequests(bidRequestsWithMediaTypes, syncAddFPDToBidderRequest(mockBidderRequest)); + expect(request[0].data.device.dnt).to.equal(1); + }); + + it('when there is not do not track, don\'t send dnt', function () { + doNotTrackStub.returns(0); + + const request = spec.buildRequests(bidRequestsWithMediaTypes, syncAddFPDToBidderRequest(mockBidderRequest)); + expect(request[0].data.device.dnt).to.equal(0); + }); + + it('when there is no defined do not track, don\'t send dnt', function () { + doNotTrackStub.returns(null); + + const request = spec.buildRequests(bidRequestsWithMediaTypes, syncAddFPDToBidderRequest(mockBidderRequest)); + expect(request[0].data.device.dnt).to.equal(0); + }); + }); + + context('supply chain (schain)', function () { + let bidRequests; + let schainConfig; + const supplyChainNodePropertyOrder = ['asi', 'sid', 'hp', 'rid', 'name', 'domain']; + + beforeEach(function () { + schainConfig = { + ver: '1.0', + complete: 1, + nodes: [ + { + asi: 'exchange1.com', + sid: '1234', + hp: 1, + rid: 'bid-request-1', + name: 'publisher', + domain: 'publisher.com' + // omitted ext + }, + { + asi: 'exchange2.com', + sid: 'abcd', + hp: 1, + rid: 'bid-request-2', + // name field missing + domain: 'intermediary.com' + }, + { + asi: 'exchange3.com', + sid: '4321', + hp: 1, + // request id + // name field missing + domain: 'intermediary-2.com' + } + ] + }; + + bidRequests = [{ + bidder: 'trafficgate', + params: { + placementId: '11', + host: 'example' + }, + adUnitCode: '/adunit-code/test-path', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + bidId: 'test-bid-id-1', + bidderRequestId: 'test-bid-request-1', + auctionId: 'test-auction-1', + schain: schainConfig + }]; + }); + + it('should send a supply chain object', function () { + const request = spec.buildRequests(bidRequests, mockBidderRequest); + expect(request[0].data.source.ext.schain).to.equal(schainConfig); + }); + + it('should send the supply chain object with the right version', function () { + const request = spec.buildRequests(bidRequests, mockBidderRequest); + expect(request[0].data.source.ext.schain.ver).to.equal(schainConfig.ver); + }); + + it('should send the supply chain object with the right complete value', function () { + const request = spec.buildRequests(bidRequests, mockBidderRequest); + expect(request[0].data.source.ext.schain.complete).to.equal(schainConfig.complete); + }); + }); + + context('when there are userid providers', function () { + const userIdAsEids = [ + { + source: 'adserver.org', + uids: [{ + id: 'some-random-id-value', + atype: 1, + ext: { + rtiPartner: 'TDID' + } + }] + }, + { + source: 'id5-sync.com', + uids: [{ + id: 'some-random-id-value', + atype: 1 + }] + }, + { + source: 'sharedid.org', + uids: [{ + id: 'some-random-id-value', + atype: 1, + ext: { + third: 'some-random-id-value' + } + }] + } + ]; + + it(`should send the user id under the extended ids`, function () { + const bidRequestsWithUserId = [{ + bidder: 'trafficgate', + params: { + placementId: '11', + host: 'example' + }, + userId: { + }, + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + bidId: 'test-bid-id-1', + bidderRequestId: 'test-bid-request-1', + auctionId: 'test-auction-1', + userIdAsEids: userIdAsEids + }]; + // enrich bid request with userId key/value + + const request = spec.buildRequests(bidRequestsWithUserId, mockBidderRequest); + expect(request[0].data.user.ext.eids).to.equal(userIdAsEids); + }); + + it(`when no user ids are available, it should not send any extended ids`, function () { + const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); + expect(request[0].data).to.not.have.any.keys('user'); + }); + }); + + context('FLEDGE', function() { + it('when FLEDGE is enabled, should send whatever is set in ortb2imp.ext.ae in all bid requests', function () { + const request = spec.buildRequests(bidRequestsWithMediaTypes, { + ...mockBidderRequest, + fledgeEnabled: true + }); + expect(request[0].data.imp[0].ext.ae).to.equal(2); + }); + }); + }); + + context('banner', function () { + it('should send bid request with a mediaTypes specified with banner type', function () { + const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); + expect(request[0].data.imp[0]).to.have.any.keys(BANNER); + }); + }); + + if (FEATURES.VIDEO) { + context('video', function () { + it('should send bid request with a mediaTypes specified with video type', function () { + const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); + expect(request[1].data.imp[0]).to.have.any.keys(VIDEO); + }); + + it('Update imp.video with OpenRTB options from mimeTypes and params', function() { + const bid01 = new BidRequestBuilder({ + adUnitCode: 'adunit-code-01', + mediaTypes: { + banner: { sizes: [[300, 250]] }, + video: { + context: 'outstream', + playerSize: [[300, 250]], + mimes: ['video/mp4'], + protocols: [8] + } + }, + }).withParams({ + // options in video, will merge + video: { + skip: 1, + skipafter: 4, + minduration: 10, + maxduration: 30 + } + }).build(); + + const bidderRequest = new BidderRequestBuilder().build(); + const expected = { + mimes: ['video/mp4'], + skip: 1, + skipafter: 4, + minduration: 10, + maxduration: 30, + placement: 4, + protocols: [8], + w: 300, + h: 250 + }; + const requests = spec.buildRequests([bid01], bidderRequest); + expect(requests).to.have.lengthOf(2); + expect(requests[1].data.imp[0].video).to.deep.equal(expected); + }); + }); } + }); - const invalidData = [ - { - body: [{ - width: 300, - cpm: 0.4, - ad: 'Test', - requestId: '9ec5b177515ee2e5', - ttl: 120, - creativeId: '2', - netRevenue: true, - currency: 'USD', - dealId: '1' - }] + describe('interpretResponse()', function () { + let bidRequestConfigs; + let bidRequest; + let bidResponse; + let bid; + + context('when there is an nbr response', function () { + let bids; + beforeEach(function () { + bidRequestConfigs = [{ + bidder: 'trafficgate', + params: { + placementId: '98765', + host: 'example' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]], + }, + }, + bidId: 'test-bid-id', + bidderRequestId: 'test-bidder-request-id', + auctionId: 'test-auction-id' + }]; + + bidRequest = spec.buildRequests(bidRequestConfigs, {refererInfo: {}})[0]; + + bidResponse = {nbr: 0}; // Unknown error + bids = spec.interpretResponse({body: bidResponse}, bidRequest); + }); + + it('should not return any bids', function () { + expect(bids.length).to.equal(0); + }); + }); + + context('when no seatbid in response', function () { + let bids; + beforeEach(function () { + bidRequestConfigs = [{ + bidder: 'trafficgate', + params: { + placementId: '98765', + host: 'example' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]], + }, + }, + bidId: 'test-bid-id', + bidderRequestId: 'test-bidder-request-id', + auctionId: 'test-auction-id' + }]; + + bidRequest = spec.buildRequests(bidRequestConfigs, {refererInfo: {}})[0]; + + bidResponse = {ext: {}, id: 'test-bid-id'}; + bids = spec.interpretResponse({body: bidResponse}, bidRequest); + }); + + it('should not return any bids', function () { + expect(bids.length).to.equal(0); + }); + }); + + context('when there is no response', function () { + let bids; + beforeEach(function () { + bidRequestConfigs = [{ + bidder: 'trafficgate', + params: { + placementId: '98765', + host: 'example' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]], + }, + }, + bidId: 'test-bid-id', + bidderRequestId: 'test-bidder-request-id', + auctionId: 'test-auction-id' + }]; + + bidRequest = spec.buildRequests(bidRequestConfigs, {refererInfo: {}})[0]; + + bidResponse = ''; // Unknown error + bids = spec.interpretResponse({body: bidResponse}, bidRequest); + }); + + it('should not return any bids', function () { + expect(bids.length).to.equal(0); + }); + }); + + const SAMPLE_BID_REQUESTS = [{ + bidder: 'trafficgate', + params: { + placementId: '98765', + host: 'example' }, - { - body: [{ - mediaType: 'video', - cpm: 0.5, - requestId: '9ec5b177515ee2e5', - ttl: 120, - creativeId: '2', - netRevenue: true, - currency: 'USD', - dealId: '1' - }] + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]], + }, }, - { - body: [{ - mediaType: 'native', - clickUrl: 'example.com', - title: 'Test', - impressionTrackers: ['example.com'], - ttl: 120, - requestId: '9ec5b177515ee2e5', - creativeId: '2', - netRevenue: true, - currency: 'USD', + bidId: 'test-bid-id', + bidderRequestId: 'test-bidder-request-id', + auctionId: 'test-auction-id' + }]; + + const SAMPLE_BID_RESPONSE = { + seatbid: [{ + bid: [{ + impid: 'test-bid-id', + price: 3.5, + w: 300, + h: 250, + crid: 'test-creative-id', + dealid: 'test-deal-id', + adm: 'test-ad-markup', + adomain: ['brand.com'], + ext: { + networkId: 123, + advertiserDomains: ['domain.com'], + } }] - } - ] + }], + cur: 'USD' + }; - for (const obj of invalidData) { - const { mediaType } = obj.body[0] + context('when there is a response, the common response properties', function () { + beforeEach(function () { + bidRequestConfigs = deepClone(SAMPLE_BID_REQUESTS); + bidRequest = spec.buildRequests(bidRequestConfigs, {refererInfo: {}})[0]; + bidResponse = deepClone(SAMPLE_BID_RESPONSE); - it(`Should return an empty array if invalid ${mediaType} response is passed `, () => { - const response = spec.interpretResponse(obj) + bid = spec.interpretResponse({body: bidResponse}, bidRequest)[0]; + }); - expect(response).to.be.an('array') - strictEqual(0, response.length) - }) - } + it('should return a price', function () { + expect(bid.cpm).to.equal(bidResponse.seatbid[0].bid[0].price); + }); - it('Should return an empty array if invalid response is passed', () => { - const response = spec.interpretResponse({ - body: [{ - ttl: 120, - creativeId: '2', - netRevenue: true, - currency: 'USD', - dealId: '1' - }] - }) + it('should return a request id', function () { + expect(bid.requestId).to.equal(bidResponse.seatbid[0].bid[0].impid); + }); + + it('should return width and height for the creative', function () { + expect(bid.width).to.equal(bidResponse.seatbid[0].bid[0].w); + expect(bid.height).to.equal(bidResponse.seatbid[0].bid[0].h); + }); - expect(response).to.be.an('array') - strictEqual(0, response.length) - }) - }) -}) + it('should return a creativeId', function () { + expect(bid.creativeId).to.equal(bidResponse.seatbid[0].bid[0].crid); + }); + + it('should return an ad', function () { + expect(bid.ad).to.equal(bidResponse.seatbid[0].bid[0].adm); + }); + + it('should return a deal id if it exists', function () { + expect(bid.dealId).to.equal(bidResponse.seatbid[0].bid[0].dealid); + }); + + it('should have a time-to-live of 5 minutes', function () { + expect(bid.ttl).to.equal(300); + }); + + it('should always return net revenue', function () { + expect(bid.netRevenue).to.equal(true); + }); + + it('should return a currency', function () { + expect(bid.currency).to.equal(bidResponse.cur); + }); + + it('should return a networkId', function () { + expect(bid.meta.networkId).to.equal(bidResponse.seatbid[0].bid[0].ext.networkId); + }); + + it('should return adomain', function () { + expect(bid.meta.advertiserDomains).to.equal(bidResponse.seatbid[0].bid[0].ext.advertiserDomains); + }); + }); + + context('when the response is a banner', function() { + beforeEach(function () { + bidRequestConfigs = [{ + bidder: 'trafficgate', + params: { + placementId: '98765', + host: 'example' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]], + }, + }, + bidId: 'test-bid-id', + bidderRequestId: 'test-bidder-request-id', + auctionId: 'test-auction-id' + }]; + + bidRequest = spec.buildRequests(bidRequestConfigs, {refererInfo: {}})[0]; + + bidResponse = { + seatbid: [{ + bid: [{ + impid: 'test-bid-id', + price: 2, + w: 300, + h: 250, + crid: 'test-creative-id', + dealid: 'test-deal-id', + adm: 'test-ad-markup' + }] + }], + cur: 'AUS' + }; + + bid = spec.interpretResponse({body: bidResponse}, bidRequest)[0]; + }); + + it('should return the proper mediaType', function () { + it('should return a creativeId', function () { + expect(bid.mediaType).to.equal(Object.keys(bidRequestConfigs[0].mediaTypes)[0]); + }); + }); + }); + + if (FEATURES.VIDEO) { + context('when the response is a video', function() { + beforeEach(function () { + bidRequestConfigs = [{ + bidder: 'trafficgate', + params: { + placementId: '98765', + host: 'example' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + video: { + playerSize: [[640, 360], [854, 480]], + }, + }, + bidId: 'test-bid-id', + bidderRequestId: 'test-bidder-request-id', + auctionId: 'test-auction-id' + }]; + + bidRequest = spec.buildRequests(bidRequestConfigs, {refererInfo: {}})[0]; + + bidResponse = { + seatbid: [{ + bid: [{ + impid: 'test-bid-id', + price: 2, + w: 854, + h: 480, + crid: 'test-creative-id', + dealid: 'test-deal-id', + adm: 'test-ad-markup', + }] + }], + cur: 'AUS' + }; + }); + + it('should return the proper mediaType', function () { + bid = spec.interpretResponse({body: bidResponse}, bidRequest)[0]; + expect(bid.mediaType).to.equal(Object.keys(bidRequestConfigs[0].mediaTypes)[0]); + }); + + it('should return the proper mediaType', function () { + const winUrl = 'https//my.win.url'; + bidResponse.seatbid[0].bid[0].nurl = winUrl + bid = spec.interpretResponse({body: bidResponse}, bidRequest)[0]; + + expect(bid.vastUrl).to.equal(winUrl); + }); + }); + } + }); +}); diff --git a/test/spec/modules/tripleliftBidAdapter_spec.js b/test/spec/modules/tripleliftBidAdapter_spec.js index 221ffb8371f..70f6b43eef6 100644 --- a/test/spec/modules/tripleliftBidAdapter_spec.js +++ b/test/spec/modules/tripleliftBidAdapter_spec.js @@ -8,6 +8,7 @@ import * as utils from 'src/utils.js'; const ENDPOINT = 'https://tlx.3lift.com/header/auction?'; const GDPR_CONSENT_STR = 'BOONm0NOONm0NABABAENAa-AAAARh7______b9_3__7_9uz_Kv_K7Vf7nnG072lPVA9LTOQ6gEaY'; +const GPP_CONSENT_STR = 'DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN' describe('triplelift adapter', function () { const adapter = newBidder(tripleliftAdapterSpec); @@ -139,15 +140,13 @@ describe('triplelift adapter', function () { sizes: [[300, 250], [300, 600], [1, 1, 1], ['flex']], bidId: '30b31c1838de1e', bidderRequestId: '22edbae2733bf6', + transactionId: '173f49a8-7549-4218-a23c-e7ba59b47229', auctionId: '1d1a030790a475', userId: {}, schain, ortb2Imp: { ext: { - data: { - pbAdSlot: 'homepage-top-rect', - adUnitSpecificAttribute: 123 - } + tid: '173f49a8-7549-4218-a23c-e7ba59b47229' } } }, @@ -167,7 +166,8 @@ describe('triplelift adapter', function () { mediaTypes: { video: { context: 'instream', - playerSize: [640, 480] + playerSize: [640, 480], + playbackmethod: 5 } }, adUnitCode: 'adunit-code-instream', @@ -177,6 +177,15 @@ describe('triplelift adapter', function () { auctionId: '1d1a030790a475', userId: {}, schain, + ortb2Imp: { + ext: { + data: { + pbAdSlot: 'homepage-top-rect', + adUnitSpecificAttribute: 123 + }, + tid: '173f49a8-7549-4218-a23c-e7ba59b47229' + } + } }, // banner and outstream video { @@ -244,6 +253,11 @@ describe('triplelift adapter', function () { auctionId: '1d1a030790a475', userId: {}, schain, + ortb2Imp: { + misc: { + test: 1 + } + } }, // incomplete banner and incomplete video { @@ -292,7 +306,8 @@ describe('triplelift adapter', function () { mediaTypes: { video: { context: 'instream', - playerSize: [640, 480] + playerSize: [640, 480], + playbackmethod: [1, 2, 3] }, banner: { sizes: [ @@ -687,6 +702,39 @@ describe('triplelift adapter', function () { expect(payload.imp[13].video.placement).to.equal(3); }); + it('should add tid to imp.ext if transactionId exists', function() { + const request = tripleliftAdapterSpec.buildRequests(bidRequests, bidderRequest); + expect(request.data.imp[0].ext.tid).to.exist.and.be.a('string'); + expect(request.data.imp[0].ext.tid).to.equal('173f49a8-7549-4218-a23c-e7ba59b47229'); + }); + + it('should not add impression ext object if ortb2Imp does not exist', function() { + const request = tripleliftAdapterSpec.buildRequests(bidRequests, bidderRequest); + expect(request.data.imp[2].ext).to.not.exist; + }); + + it('should not add impression ext object if ortb2Imp.ext does not exist', function() { + const request = tripleliftAdapterSpec.buildRequests(bidRequests, bidderRequest); + expect(request.data.imp[3].ext).to.not.exist; + }); + + it('should copy entire impression ext object', function() { + const request = tripleliftAdapterSpec.buildRequests(bidRequests, bidderRequest); + expect(request.data.imp[1].ext).to.haveOwnProperty('tid'); + expect(request.data.imp[1].ext).to.haveOwnProperty('data'); + expect(request.data.imp[1].ext.data).to.haveOwnProperty('adUnitSpecificAttribute'); + expect(request.data.imp[1].ext.data).to.haveOwnProperty('pbAdSlot'); + expect(request.data.imp[1].ext).to.deep.equal( + { + data: { + pbAdSlot: 'homepage-top-rect', + adUnitSpecificAttribute: 123 + }, + tid: '173f49a8-7549-4218-a23c-e7ba59b47229' + } + ); + }); + it('should add tdid to the payload if included', function () { const id = '6bca7f6b-a98a-46c0-be05-6020f7604598'; bidRequests[0].userId.tdid = id; @@ -992,6 +1040,12 @@ describe('triplelift adapter', function () { const url = request.url; expect(url).to.match(/(\?|&)us_privacy=1YYY/); }); + it('should pass fledge signal when Triplelift is eligible for fledge', function() { + bidderRequest.fledgeEnabled = true; + const request = tripleliftAdapterSpec.buildRequests(bidRequests, bidderRequest); + const url = request.url; + expect(url).to.match(/(\?|&)fledge=true/); + }); it('should return coppa param when COPPA config is set to true', function() { sinon.stub(config, 'getConfig').withArgs('coppa').returns(true); const request = tripleliftAdapterSpec.buildRequests(bidRequests, bidderRequest); @@ -1101,10 +1155,10 @@ describe('triplelift adapter', function () { }); it('should send ad unit fpd if kvps are available', function() { const request = tripleliftAdapterSpec.buildRequests(bidRequests, bidderRequest); - expect(request.data.imp[0].fpd.context).to.haveOwnProperty('data'); - expect(request.data.imp[0].fpd.context.data).to.haveOwnProperty('pbAdSlot'); - expect(request.data.imp[0].fpd.context.data).to.haveOwnProperty('adUnitSpecificAttribute'); - expect(request.data.imp[1].fpd).to.not.exist; + expect(request.data.imp[1].fpd.context).to.haveOwnProperty('data'); + expect(request.data.imp[1].fpd.context.data).to.haveOwnProperty('pbAdSlot'); + expect(request.data.imp[1].fpd.context.data).to.haveOwnProperty('adUnitSpecificAttribute'); + expect(request.data.imp[2].fpd).to.not.exist; }); it('should send 1PlusX data as fpd if localStorage is available and no other fpd is defined', function() { sandbox.stub(storage, 'getDataFromLocalStorage').callsFake(() => '{"kid":1,"s":"ySRdArquXuBolr/cVv0UNqrJhTO4QZsbNH/t+2kR3gXjbA==","t":"/yVtBrquXuBolr/cVv0UNtx1mssdLYeKFhWFI3Dq1dJnug=="}'); @@ -1181,6 +1235,16 @@ describe('triplelift adapter', function () { 'gpp_sid': [7] }) }); + it('should cast playbackmethod as an array if it is an integer and it exists', function() { + const request = tripleliftAdapterSpec.buildRequests(bidRequests, bidderRequest); + expect(request.data.imp[1].video.playbackmethod).to.be.a('array'); + expect(request.data.imp[1].video.playbackmethod).to.deep.equal([5]); + }); + it('should set playbackmethod as an array if it exists as an array', function() { + const request = tripleliftAdapterSpec.buildRequests(bidRequests, bidderRequest); + expect(request.data.imp[5].video.playbackmethod).to.be.a('array'); + expect(request.data.imp[5].video.playbackmethod).to.deep.equal([1, 2, 3]); + }); }); describe('interpretResponse', function () { @@ -1449,6 +1513,7 @@ describe('triplelift adapter', function () { describe('getUserSyncs', function() { let expectedIframeSyncUrl = 'https://eb2.3lift.com/sync?gdpr=true&cmp_cs=' + GDPR_CONSENT_STR + '&'; let expectedImageSyncUrl = 'https://eb2.3lift.com/sync?px=1&src=prebid&gdpr=true&cmp_cs=' + GDPR_CONSENT_STR + '&'; + let expectedGppSyncUrl = 'https://eb2.3lift.com/sync?gdpr=true&cmp_cs=' + GDPR_CONSENT_STR + '&gpp=' + GPP_CONSENT_STR + '&gpp_sid=2%2C8' + '&'; it('returns undefined when syncing is not enabled', function() { expect(tripleliftAdapterSpec.getUserSyncs({})).to.equal(undefined); @@ -1486,8 +1551,19 @@ describe('triplelift adapter', function () { let syncOptions = { iframeEnabled: true }; - let result = tripleliftAdapterSpec.getUserSyncs(syncOptions, null, null, '1YYY'); + let result = tripleliftAdapterSpec.getUserSyncs(syncOptions, null, null, '1YYY', null); expect(result[0].url).to.match(/(\?|&)us_privacy=1YYY/); }); + it('returns a user sync pixel with GPP signals when available', function() { + let syncOptions = { + iframeEnabled: true + }; + let gppConsent = { + 'applicableSections': [2, 8], + 'gppString': 'DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN' + } + let result = tripleliftAdapterSpec.getUserSyncs(syncOptions, null, null, null, gppConsent); + expect(result[0].url).to.equal(expectedGppSyncUrl); + }); }); }); diff --git a/test/spec/modules/trustpidSystem_spec.js b/test/spec/modules/trustpidSystem_spec.js deleted file mode 100644 index 38e861d977f..00000000000 --- a/test/spec/modules/trustpidSystem_spec.js +++ /dev/null @@ -1,237 +0,0 @@ -import { expect } from 'chai'; -import { trustpidSubmodule } from 'modules/trustpidSystem.js'; -import { storage } from 'modules/trustpidSystem.js'; - -describe('trustpid System', () => { - const connectDataKey = 'fcIdConnectData'; - const connectDomainKey = 'fcIdConnectDomain'; - - const getStorageData = (idGraph) => { - if (!idGraph) { - idGraph = {id: 501, domain: ''}; - } - return { - 'connectId': { - 'idGraph': [idGraph], - } - } - }; - - it('should have the correct module name declared', () => { - expect(trustpidSubmodule.name).to.equal('trustpid'); - }); - - describe('trustpid getId()', () => { - afterEach(() => { - storage.removeDataFromLocalStorage(connectDataKey); - storage.removeDataFromLocalStorage(connectDomainKey); - }); - - after(() => { - window.FC_CONF = {}; - }) - - it('it should return object with key callback', () => { - expect(trustpidSubmodule.getId()).to.have.property('callback'); - }); - - it('should return object with key callback with value type - function', () => { - storage.setDataInLocalStorage(connectDataKey, JSON.stringify(getStorageData())); - expect(trustpidSubmodule.getId()).to.have.property('callback'); - expect(typeof trustpidSubmodule.getId().callback).to.be.equal('function'); - }); - - it('tests if localstorage & JSON works properly ', () => { - const idGraph = { - 'domain': 'domainValue', - 'atid': 'atidValue', - }; - storage.setDataInLocalStorage(connectDataKey, JSON.stringify(getStorageData(idGraph))); - expect(JSON.parse(storage.getDataFromLocalStorage(connectDataKey))).to.have.property('connectId'); - }); - - it('returns {callback: func} if domains don\'t match', () => { - const idGraph = { - 'domain': 'domainValue', - 'atid': 'atidValue', - }; - storage.setDataInLocalStorage(connectDomainKey, JSON.stringify('differentDomainValue')); - storage.setDataInLocalStorage(connectDataKey, JSON.stringify(getStorageData(idGraph))); - expect(trustpidSubmodule.getId()).to.have.property('callback'); - }); - - it('returns {id: {trustpid: data.trustpid}} if we have the right data stored in the localstorage ', () => { - const idGraph = { - 'domain': 'test.domain', - 'atid': 'atidValue', - }; - storage.setDataInLocalStorage(connectDomainKey, JSON.stringify('test.domain')); - storage.setDataInLocalStorage(connectDataKey, JSON.stringify(getStorageData(idGraph))); - const response = trustpidSubmodule.getId(); - expect(response).to.have.property('id'); - expect(response.id).to.have.property('trustpid'); - expect(response.id.trustpid).to.be.equal('atidValue'); - }); - - it('returns {trustpid: data.trustpid} if we have the right data stored in the localstorage right after the callback is called', (done) => { - const idGraph = { - 'domain': 'test.domain', - 'atid': 'atidValue', - }; - const response = trustpidSubmodule.getId(); - expect(response).to.have.property('callback'); - expect(response.callback.toString()).contain('result(callback)'); - - if (typeof response.callback === 'function') { - storage.setDataInLocalStorage(connectDomainKey, JSON.stringify('test.domain')); - storage.setDataInLocalStorage(connectDataKey, JSON.stringify(getStorageData(idGraph))); - response.callback(function (result) { - expect(result).to.not.be.null; - expect(result).to.have.property('trustpid'); - expect(result.trustpid).to.be.equal('atidValue'); - done() - }) - } - }); - - it('returns null if domains don\'t match', (done) => { - const idGraph = { - 'domain': 'test.domain', - 'atid': 'atidValue', - }; - storage.setDataInLocalStorage(connectDomainKey, JSON.stringify('differentDomainValue')); - storage.setDataInLocalStorage(connectDataKey, JSON.stringify(getStorageData(idGraph))); - - const response = trustpidSubmodule.getId(); - expect(response).to.have.property('callback'); - expect(response.callback.toString()).contain('result(callback)'); - - if (typeof response.callback === 'function') { - setTimeout(() => { - expect(JSON.parse(storage.getDataFromLocalStorage(connectDomainKey))).to.be.equal('differentDomainValue'); - }, 100) - response.callback(function (result) { - expect(result).to.be.null; - done() - }) - } - }); - - it('returns {trustpid: data.trustpid} if we have the right data stored in the localstorage right after 500ms delay', (done) => { - const idGraph = { - 'domain': 'test.domain', - 'atid': 'atidValue', - }; - - const response = trustpidSubmodule.getId(); - expect(response).to.have.property('callback'); - expect(response.callback.toString()).contain('result(callback)'); - - if (typeof response.callback === 'function') { - setTimeout(() => { - storage.setDataInLocalStorage(connectDomainKey, JSON.stringify('test.domain')); - storage.setDataInLocalStorage(connectDataKey, JSON.stringify(getStorageData(idGraph))); - }, 500); - response.callback(function (result) { - expect(result).to.not.be.null; - expect(result).to.have.property('trustpid'); - expect(result.trustpid).to.be.equal('atidValue'); - done() - }) - } - }); - - it('returns null if we have the data stored in the localstorage after 500ms delay and the max (waiting) delay is only 200ms ', (done) => { - const idGraph = { - 'domain': 'test.domain', - 'atid': 'atidValue', - }; - - const response = trustpidSubmodule.getId({params: {maxDelayTime: 200}}); - expect(response).to.have.property('callback'); - expect(response.callback.toString()).contain('result(callback)'); - - if (typeof response.callback === 'function') { - setTimeout(() => { - storage.setDataInLocalStorage(connectDomainKey, JSON.stringify('test.domain')); - storage.setDataInLocalStorage(connectDataKey, JSON.stringify(getStorageData(idGraph))); - }, 500); - response.callback(function (result) { - expect(result).to.be.null; - done() - }) - } - }); - }); - - describe('trustpid decode()', () => { - const VALID_API_RESPONSES = [ - { - expected: '32a97f612', - payload: { - trustpid: '32a97f612' - } - }, - { - expected: '32a97f61', - payload: { - trustpid: '32a97f61', - } - }, - ]; - VALID_API_RESPONSES.forEach(responseData => { - it('should return a newly constructed object with the trustpid for a payload with {trustpid: value}', () => { - expect(trustpidSubmodule.decode(responseData.payload)).to.deep.equal( - {trustpid: responseData.expected} - ); - }); - }); - - [{}, '', {foo: 'bar'}].forEach((response) => { - it(`should return null for an invalid response "${JSON.stringify(response)}"`, () => { - expect(trustpidSubmodule.decode(response)).to.be.null; - }); - }); - }); - - describe('trustpid messageHandler', () => { - afterEach(() => { - storage.removeDataFromLocalStorage(connectDataKey); - storage.removeDataFromLocalStorage(connectDomainKey); - }); - - after(() => { - window.FC_CONF = {}; - }) - - const domains = [ - 'domain1', - 'domain2', - 'domain3', - ]; - - domains.forEach(domain => { - it(`correctly sets trustpid value for domain name ${domain}`, (done) => { - const idGraph = { - 'domain': domain, - 'atid': 'atidValue', - }; - - storage.setDataInLocalStorage(connectDomainKey, JSON.stringify(domain)); - storage.setDataInLocalStorage(connectDataKey, JSON.stringify(getStorageData(idGraph))); - - const eventData = { - data: `{\"msgType\":\"MNOSELECTOR\",\"body\":{\"url\":\"https://${domain}/some/path\"}}` - }; - - window.dispatchEvent(new MessageEvent('message', eventData)); - - const response = trustpidSubmodule.getId(); - expect(response).to.have.property('id'); - expect(response.id).to.have.property('trustpid'); - expect(response.id.trustpid).to.be.equal('atidValue'); - done(); - }); - }); - }); -}); diff --git a/test/spec/modules/ttdBidAdapter_spec.js b/test/spec/modules/ttdBidAdapter_spec.js index 9869d072657..56c506dea6b 100644 --- a/test/spec/modules/ttdBidAdapter_spec.js +++ b/test/spec/modules/ttdBidAdapter_spec.js @@ -81,6 +81,18 @@ describe('ttdBidAdapter', function () { delete bid.mediaTypes expect(spec.isBidRequestValid(bid)).to.equal(false); }); + + it('should return false if bidfloor is passed incorrectly', function () { + let bid = makeBid(); + bid.params.bidfloor = 'invalid bidfloor'; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return true if bidfloor is passed correctly as a float', function () { + let bid = makeBid(); + bid.params.bidfloor = 3.01; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); }); describe('banner', function () { @@ -92,6 +104,10 @@ describe('ttdBidAdapter', function () { }); describe('video', function () { + if (!FEATURES.VIDEO) { + return; + } + function makeBid() { return { 'bidder': 'ttd', @@ -207,7 +223,11 @@ describe('ttdBidAdapter', function () { const baseBidderRequestReferer = detectReferer(testWindow)(); const baseBidderRequest = { 'bidderCode': 'ttd', - 'auctionId': 'e7b34fa3-8654-424e-8c49-03e509e53d8c', + ortb2: { + source: { + tid: 'e7b34fa3-8654-424e-8c49-03e509e53d8c', + } + }, 'bidderRequestId': '18084284054531', 'auctionStart': 1540945362095, 'timeout': 3000, @@ -216,6 +236,25 @@ describe('ttdBidAdapter', function () { 'doneCbCallCount': 0 }; + const extFirstPartyDataValues = ['value', 'value2']; + const extFirstPartyData = { + data: { + firstPartyKey: 'firstPartyValue', + firstPartyKey2: extFirstPartyDataValues + }, + custom: 'custom_data', + custom_kvp: { + customKey: 'customValue' + } + } + + function validateExtFirstPartyData(ext) { + expect(ext.data.firstPartyKey).to.equal('firstPartyValue'); + expect(ext.data.firstPartyKey2).to.eql(extFirstPartyDataValues); + expect(ext.custom).to.equal('custom_data'); + expect(ext.custom_kvp.customKey).to.equal('customValue'); + } + it('sends bid request to our endpoint that makes sense', function () { const request = testBuildRequests(baseBannerBidRequests, baseBidderRequest); expect(request.method).to.equal('POST'); @@ -270,10 +309,25 @@ describe('ttdBidAdapter', function () { expect(requestBody.imp[0].ext.gpid).to.equal(gpid); }); - it('sends auction id in source.tid', function () { + it('sends rwdd in imp.rwdd if present', function () { + let clonedBannerRequests = deepClone(baseBannerBidRequests); + const gpid = '/1111/home#header'; + const rwdd = 1; + clonedBannerRequests[0].ortb2Imp = { + rwdd: rwdd, + ext: { + gpid: gpid + } + }; + const requestBody = testBuildRequests(clonedBannerRequests, baseBidderRequest).data; + expect(requestBody.imp[0].rwdd).to.be.not.null; + expect(requestBody.imp[0].rwdd).to.equal(1); + }); + + it('sends source.tid', function () { const requestBody = testBuildRequests(baseBannerBidRequests, baseBidderRequest).data; expect(requestBody.source).to.be.not.null; - expect(requestBody.source.tid).to.equal(baseBidderRequest.auctionId); + expect(requestBody.source.tid).to.equal(baseBidderRequest.ortb2.source.tid); }); it('includes the ad size in the bid request', function () { @@ -477,13 +531,7 @@ describe('ttdBidAdapter', function () { const TDID = '00000000-0000-0000-0000-000000000000'; const UID2 = '99999999-9999-9999-9999-999999999999'; let clonedBannerRequests = deepClone(baseBannerBidRequests); - clonedBannerRequests[0].userId = { - tdid: TDID, - uid2: { - id: UID2 - } - }; - const expectedEids = [ + clonedBannerRequests[0].userIdAsEids = [ { source: 'adserver.org', uids: [ @@ -506,6 +554,7 @@ describe('ttdBidAdapter', function () { ] } ]; + const expectedEids = clonedBannerRequests[0].userIdAsEids; const requestBody = testBuildRequests(clonedBannerRequests, baseBidderRequest).data; expect(requestBody.user.ext.eids).to.deep.equal(expectedEids); @@ -535,6 +584,126 @@ describe('ttdBidAdapter', function () { expect(requestBody.site.ref).to.equal('https://ref.example.com'); expect(requestBody.site.keywords).to.equal('power tools, drills'); }); + + it('should fallback to floor module if no bidfloor is sent ', function () { + let clonedBannerRequests = deepClone(baseBannerBidRequests); + const bidfloor = 5.00; + clonedBannerRequests[0].getFloor = () => { + return { currency: 'USD', floor: bidfloor }; + }; + const requestBody = testBuildRequests(clonedBannerRequests, baseBidderRequest).data; + config.resetConfig(); + expect(requestBody.imp[0].bidfloor).to.equal(bidfloor); + }); + + it('adds default value for secure if not set to request', function () { + const requestBody = testBuildRequests(baseBannerBidRequests, baseBidderRequest).data; + expect(requestBody.imp[0].secure).to.equal(1); + }); + + it('adds secure to request', function () { + let clonedBannerRequests = deepClone(baseBannerBidRequests); + clonedBannerRequests[0].ortb2Imp.secure = 0; + + let requestBody = testBuildRequests(clonedBannerRequests, baseBidderRequest).data; + expect(0).to.equal(requestBody.imp[0].secure); + + clonedBannerRequests[0].ortb2Imp.secure = 1; + requestBody = testBuildRequests(clonedBannerRequests, baseBidderRequest).data; + expect(1).to.equal(requestBody.imp[0].secure); + }); + + it('adds all of site first party data to request', function() { + const ortb2 = { + site: { + ext: extFirstPartyData, + search: 'test search' + } + }; + + let clonedBidderRequest = {...deepClone(baseBidderRequest), ortb2}; + const requestBody = testBuildRequests(baseBannerBidRequests, clonedBidderRequest).data; + + validateExtFirstPartyData(requestBody.site.ext) + expect(requestBody.site.search).to.equal('test search') + }); + + it('adds all of user first party data to request', function() { + const ortb2 = { + user: { + ext: extFirstPartyData, + yob: 1998 + } + }; + + let clonedBidderRequest = {...deepClone(baseBidderRequest), ortb2}; + const requestBody = testBuildRequests(baseBannerBidRequests, clonedBidderRequest).data; + + validateExtFirstPartyData(requestBody.user.ext) + expect(requestBody.user.yob).to.equal(1998) + }); + + it('adds all of imp first party data to request', function() { + const metric = { type: 'viewability', value: 0.8 }; + let clonedBannerRequests = deepClone(baseBannerBidRequests); + clonedBannerRequests[0].ortb2Imp = { + ext: extFirstPartyData, + metric: [metric], + clickbrowser: 1 + }; + + const requestBody = testBuildRequests(clonedBannerRequests, baseBidderRequest).data; + + validateExtFirstPartyData(requestBody.imp[0].ext) + expect(requestBody.imp[0].tagid).to.equal('1gaa015'); + expect(requestBody.imp[0].metric[0]).to.deep.equal(metric); + expect(requestBody.imp[0].clickbrowser).to.equal(1) + }); + + it('adds all of app first party data to request', function() { + const ortb2 = { + app: { + ext: extFirstPartyData, + ver: 'v1.0' + } + }; + + let clonedBidderRequest = {...deepClone(baseBidderRequest), ortb2}; + const requestBody = testBuildRequests(baseBannerBidRequests, clonedBidderRequest).data; + + validateExtFirstPartyData(requestBody.app.ext) + expect(requestBody.app.ver).to.equal('v1.0') + }); + + it('adds all of device first party data to request', function() { + const ortb2 = { + device: { + ext: extFirstPartyData, + os: 'iPhone' + } + }; + + let clonedBidderRequest = {...deepClone(baseBidderRequest), ortb2}; + const requestBody = testBuildRequests(baseBannerBidRequests, clonedBidderRequest).data; + + validateExtFirstPartyData(requestBody.device.ext) + expect(requestBody.device.os).to.equal('iPhone') + }); + + it('adds all of pmp first party data to request', function() { + const ortb2 = { + pmp: { + ext: extFirstPartyData, + private_auction: 1 + } + }; + + let clonedBidderRequest = {...deepClone(baseBidderRequest), ortb2}; + const requestBody = testBuildRequests(baseBannerBidRequests, clonedBidderRequest).data; + + validateExtFirstPartyData(requestBody.pmp.ext) + expect(requestBody.pmp.private_auction).to.equal(1) + }); }); describe('buildRequests-banner-multiple', function () { @@ -591,7 +760,11 @@ describe('ttdBidAdapter', function () { const baseBidderRequest = { 'bidderCode': 'ttd', - 'auctionId': 'e7b34fa3-8654-424e-8c49-03e509e53d8c', + ortb2: { + source: { + tid: 'e7b34fa3-8654-424e-8c49-03e509e53d8c', + } + }, 'bidderRequestId': '18084284054531', 'auctionStart': 1540945362095, 'timeout': 3000, @@ -611,7 +784,7 @@ describe('ttdBidAdapter', function () { const requestBody = testBuildRequests(baseBannerMultipleBidRequests, baseBidderRequest).data; expect(requestBody.imp.length).to.equal(2); expect(requestBody.source).to.be.not.null; - expect(requestBody.source.tid).to.equal(baseBidderRequest.auctionId); + expect(requestBody.source.tid).to.equal(baseBidderRequest.ortb2.source.tid); expect(requestBody.imp[0].ext).to.be.not.null; expect(requestBody.imp[0].ext.tid).to.equal('8651474f-58b1-4368-b812-84f8c937a099'); expect(requestBody.imp[1].ext).to.be.not.null; @@ -650,6 +823,10 @@ describe('ttdBidAdapter', function () { }); describe('buildRequests-display-video-multiformat', function () { + if (!FEATURES.VIDEO) { + return; + } + const baseMultiformatBidRequests = [{ 'bidder': 'ttd', 'params': { @@ -718,6 +895,10 @@ describe('ttdBidAdapter', function () { }); describe('buildRequests-video', function () { + if (!FEATURES.VIDEO) { + return; + } + const baseVideoBidRequests = [{ 'bidder': 'ttd', 'params': { @@ -859,6 +1040,14 @@ describe('ttdBidAdapter', function () { const requestBody = testBuildRequests(clonedVideoRequests, baseBidderRequest).data; expect(requestBody.imp[0].video.placement).to.equal(3); }); + + it('sets plcmt correctly if sent', function () { + let clonedVideoRequests = deepClone(baseVideoBidRequests); + clonedVideoRequests[0].mediaTypes.video.plcmt = 3; + + const requestBody = testBuildRequests(clonedVideoRequests, baseBidderRequest).data; + expect(requestBody.imp[0].video.plcmt).to.equal(3); + }); }); describe('interpretResponse-empty', function () { @@ -1145,6 +1334,10 @@ describe('ttdBidAdapter', function () { }); describe('interpretResponse-simple-video', function () { + if (!FEATURES.VIDEO) { + return; + } + const incoming = { 'body': { 'cur': 'USD', @@ -1238,8 +1431,7 @@ describe('ttdBidAdapter', function () { } }; - const expectedBid = - { + const expectedBid = { 'requestId': '2eabb87dfbcae4', 'cpm': 13.6, 'creativeId': 'mokivv6m', @@ -1277,6 +1469,10 @@ describe('ttdBidAdapter', function () { }); describe('interpretResponse-display-and-video', function () { + if (!FEATURES.VIDEO) { + return; + } + const incoming = { 'body': { 'id': 'e7b34fa3-8654-424e-8c49-03e509e53d8c', diff --git a/test/spec/modules/ucfunnelBidAdapter_spec.js b/test/spec/modules/ucfunnelBidAdapter_spec.js index 992708bd2ea..9bec7229450 100644 --- a/test/spec/modules/ucfunnelBidAdapter_spec.js +++ b/test/spec/modules/ucfunnelBidAdapter_spec.js @@ -1,6 +1,7 @@ import { expect } from 'chai'; import { spec } from 'modules/ucfunnelBidAdapter.js'; import {BANNER, VIDEO, NATIVE} from 'src/mediaTypes.js'; +import {deepClone} from '../../../src/utils.js'; const URL = 'https://hb.aralego.com/header'; const BIDDER_CODE = 'ucfunnel'; @@ -150,7 +151,10 @@ describe('ucfunnel Adapter', function () { }); }); describe('build request', function () { - const request = spec.buildRequests([validBannerBidReq], bidderRequest); + let request; + before(() => { + request = spec.buildRequests([validBannerBidReq], bidderRequest); + }) it('should create a POST request for every bid', function () { expect(request[0].method).to.equal('GET'); expect(request[0].url).to.equal(spec.ENDPOINT); @@ -179,15 +183,16 @@ describe('ucfunnel Adapter', function () { it('must parse bid size from a nested array', function () { const width = 640; const height = 480; - validBannerBidReq.sizes = [[ width, height ]]; - const requests = spec.buildRequests([ validBannerBidReq ], bidderRequest); + const bid = deepClone(validBannerBidReq); + bid.sizes = [[ width, height ]]; + const requests = spec.buildRequests([ bid ], bidderRequest); const data = requests[0].data; expect(data.w).to.equal(width); expect(data.h).to.equal(height); }); it('should set bidfloor if configured', function() { - let bid = Object.assign({}, validBannerBidReq); + let bid = deepClone(validBannerBidReq); bid.getFloor = function() { return { currency: 'USD', @@ -200,7 +205,7 @@ describe('ucfunnel Adapter', function () { }); it('should set bidfloor if configured', function() { - let bid = Object.assign({}, validBannerBidReq); + let bid = deepClone(validBannerBidReq); bid.params.bidfloor = 2.01; const requests = spec.buildRequests([ bid ], bidderRequest); const data = requests[0].data; @@ -208,7 +213,7 @@ describe('ucfunnel Adapter', function () { }); it('should set bidfloor if configured', function() { - let bid = Object.assign({}, validBannerBidReq); + let bid = deepClone(validBannerBidReq); bid.getFloor = function() { return { currency: 'USD', @@ -224,8 +229,12 @@ describe('ucfunnel Adapter', function () { describe('interpretResponse', function () { describe('should support banner', function () { - const request = spec.buildRequests([ validBannerBidReq ], bidderRequest); - const result = spec.interpretResponse({body: validBannerBidRes}, request[0]); + let request, result; + before(() => { + request = spec.buildRequests([ validBannerBidReq ], bidderRequest); + result = spec.interpretResponse({body: validBannerBidRes}, request[0]); + }); + it('should build bid array for banner', function () { expect(result.length).to.equal(1); }); @@ -243,8 +252,11 @@ describe('ucfunnel Adapter', function () { }); describe('handle banner no ad', function () { - const request = spec.buildRequests([ validBannerBidReq ], bidderRequest); - const result = spec.interpretResponse({body: invalidBannerBidRes}, request[0]); + let request, result; + before(() => { + request = spec.buildRequests([ validBannerBidReq ], bidderRequest); + result = spec.interpretResponse({body: invalidBannerBidRes}, request[0]); + }) it('should build bid array for banner', function () { expect(result.length).to.equal(1); }); @@ -261,8 +273,11 @@ describe('ucfunnel Adapter', function () { }); describe('handle banner cpm under bidfloor', function () { - const request = spec.buildRequests([ validBannerBidReq ], bidderRequest); - const result = spec.interpretResponse({body: invalidBannerBidRes}, request[0]); + let request, result; + before(() => { + request = spec.buildRequests([ validBannerBidReq ], bidderRequest); + result = spec.interpretResponse({body: invalidBannerBidRes}, request[0]); + }) it('should build bid array for banner', function () { expect(result.length).to.equal(1); }); @@ -279,8 +294,11 @@ describe('ucfunnel Adapter', function () { }); describe('should support video', function () { - const request = spec.buildRequests([ validVideoBidReq ], bidderRequest); - const result = spec.interpretResponse({body: validVideoBidRes}, request[0]); + let request, result; + before(() => { + request = spec.buildRequests([ validVideoBidReq ], bidderRequest); + result = spec.interpretResponse({body: validVideoBidRes}, request[0]); + }) it('should build bid array', function () { expect(result.length).to.equal(1); }); @@ -299,8 +317,11 @@ describe('ucfunnel Adapter', function () { }); describe('should support native', function () { - const request = spec.buildRequests([ validNativeBidReq ], bidderRequest); - const result = spec.interpretResponse({body: validNativeBidRes}, request[0]); + let request, result; + before(() => { + request = spec.buildRequests([ validNativeBidReq ], bidderRequest); + result = spec.interpretResponse({body: validNativeBidRes}, request[0]); + }) it('should build bid array', function () { expect(result.length).to.equal(1); }); diff --git a/test/spec/modules/uid2IdSystem_helpers.js b/test/spec/modules/uid2IdSystem_helpers.js new file mode 100644 index 00000000000..65d52c1d7c3 --- /dev/null +++ b/test/spec/modules/uid2IdSystem_helpers.js @@ -0,0 +1,70 @@ +import { setConsentConfig } from 'modules/consentManagement.js'; +import { server } from 'test/mocks/xhr.js'; +import {coreStorage, init, setSubmoduleRegistry, requestBidsHook} from 'modules/userId/index.js'; + +const msIn12Hours = 60 * 60 * 12 * 1000; +const expireCookieDate = 'Thu, 01 Jan 1970 00:00:01 GMT'; + +export const cookieHelpers = { + getFutureCookieExpiry: () => new Date(Date.now() + msIn12Hours).toUTCString(), + setPublisherCookie: (cookieName, token) => coreStorage.setCookie(cookieName, JSON.stringify(token), cookieHelpers.getFutureCookieExpiry()), + clearCookies: (...cookieNames) => cookieNames.forEach(cookieName => coreStorage.setCookie(cookieName, '', expireCookieDate)), +} + +export const runAuction = async () => { + const adUnits = [{ + code: 'adUnit-code', + mediaTypes: {banner: {}, native: {}}, + sizes: [[300, 200], [300, 600]], + bids: [{bidder: 'sampleBidder', params: {placementId: 'banner-only-bidder'}}] + }]; + return new Promise(function(resolve) { + requestBidsHook(function() { + resolve(adUnits[0].bids[0]); + }, {adUnits}); + }); +} + +export const apiHelpers = { + makeTokenResponse: (token, shouldRefresh = false, expired = false) => ({ + advertising_token: token, + refresh_token: 'fake-refresh-token', + identity_expires: expired ? Date.now() - 1000 : Date.now() + 60 * 60 * 1000, + refresh_from: shouldRefresh ? Date.now() - 1000 : Date.now() + 60 * 1000, + refresh_expires: Date.now() + 24 * 60 * 60 * 1000, // 24 hours + refresh_response_key: 'wR5t6HKMfJ2r4J7fEGX9Gw==', // Fake data + }), + respondAfterDelay: (delay) => new Promise((resolve) => setTimeout(() => { + server.respond(); + setTimeout(() => resolve()); + }, delay)), +} + +export const setGdprApplies = (consent = false) => { + const consentDetails = consent ? { + tcString: 'CPhJRpMPhJRpMABAMBFRACBoALAAAEJAAIYgAKwAQAKgArABAAqAAA', + purpose: { + consents: { + '1': true, + }, + }, + vendor: { + consents: { + '21': true, + }, + } + + } : { + tcString: 'CPhJRpMPhJRpMABAMBFRACBoALAAAEJAAIYgAKwAQAKgArABAAqAAA' + }; + const staticConfig = { + cmpApi: 'static', + timeout: 7500, + consentData: { + gdprApplies: true, + ...consentDetails + } + } + setConsentConfig(staticConfig); + return staticConfig; +} diff --git a/test/spec/modules/uid2IdSystem_spec.js b/test/spec/modules/uid2IdSystem_spec.js index b33e8cda501..20a38a292bb 100644 --- a/test/spec/modules/uid2IdSystem_spec.js +++ b/test/spec/modules/uid2IdSystem_spec.js @@ -1,11 +1,15 @@ +/* eslint-disable no-console */ + import {coreStorage, init, setSubmoduleRegistry, requestBidsHook} from 'modules/userId/index.js'; import {config} from 'src/config.js'; import * as utils from 'src/utils.js'; import { uid2IdSubmodule } from 'modules/uid2IdSystem.js'; import 'src/prebid.js'; +import 'modules/consentManagement.js'; import { getGlobal } from 'src/prebidGlobal.js'; import { server } from 'test/mocks/xhr.js'; import { configureTimerInterceptors } from 'test/mocks/timers.js'; +import { cookieHelpers, runAuction, apiHelpers, setGdprApplies } from './uid2IdSystem_helpers.js'; import {hook} from 'src/hook.js'; import {uninstall as uninstallGdprEnforcement} from 'modules/gdprEnforcement.js'; @@ -14,72 +18,48 @@ let expect = require('chai').expect; const clearTimersAfterEachTest = true; const debugOutput = () => {}; -const expireCookieDate = 'Thu, 01 Jan 1970 00:00:01 GMT'; -const msIn12Hours = 60 * 60 * 12 * 1000; const moduleCookieName = '__uid2_advertising_token'; const publisherCookieName = '__UID2_SERVER_COOKIE'; const auctionDelayMs = 10; -const legacyConfigParams = null; -const serverCookieConfigParams = { uid2ServerCookie: publisherCookieName } -const getFutureCookieExpiry = () => new Date(Date.now() + msIn12Hours).toUTCString(); -const setPublisherCookie = (token) => coreStorage.setCookie(publisherCookieName, JSON.stringify(token), getFutureCookieExpiry()); - -const makePrebidIdentityContainer = (token) => ({uid2: {id: token}}); -const makePrebidConfig = (params = null, extraSettings = {}, debug = false) => ({ - userSync: { auctionDelay: auctionDelayMs, userIds: [{name: 'uid2', params}] }, debug, ...extraSettings -}); - const initialToken = `initial-advertising-token`; const legacyToken = 'legacy-advertising-token'; const refreshedToken = 'refreshed-advertising-token'; -const makeUid2Token = (token = initialToken, shouldRefresh = false, expired = false) => ({ - advertising_token: token, - refresh_token: 'fake-refresh-token', - identity_expires: expired ? Date.now() - 1000 : Date.now() + 60 * 60 * 1000, - refresh_from: shouldRefresh ? Date.now() - 1000 : Date.now() + 60 * 1000, - refresh_expires: Date.now() + 24 * 60 * 60 * 1000, // 24 hours - refresh_response_key: 'wR5t6HKMfJ2r4J7fEGX9Gw==', + +const legacyConfigParams = {storage: null}; +const serverCookieConfigParams = { uid2ServerCookie: publisherCookieName }; +const newServerCookieConfigParams = { uid2Cookie: publisherCookieName }; + +const makeUid2IdentityContainer = (token) => ({uid2: {id: token}}); +let useLocalStorage = false; +const makePrebidConfig = (params = null, extraSettings = {}, debug = false) => ({ + userSync: { auctionDelay: auctionDelayMs, userIds: [{name: 'uid2', params: {storage: useLocalStorage ? 'localStorage' : 'cookie', ...params}}] }, debug, ...extraSettings }); -const expectInitialToken = (bid) => expect(bid?.userId ?? {}).to.deep.include(makePrebidIdentityContainer(initialToken)); -const expectRefreshedToken = (bid) => expect(bid?.userId ?? {}).to.deep.include(makePrebidIdentityContainer(refreshedToken)); + +const getFromAppropriateStorage = () => { + if (useLocalStorage) return coreStorage.getDataFromLocalStorage(moduleCookieName); + else return coreStorage.getCookie(moduleCookieName); +} + +const expectToken = (bid, token) => expect(bid?.userId ?? {}).to.deep.include(makeUid2IdentityContainer(token)); +const expectLegacyToken = (bid) => expect(bid.userId).to.deep.include(makeUid2IdentityContainer(legacyToken)); const expectNoIdentity = (bid) => expect(bid).to.not.haveOwnProperty('userId'); -const expectGlobalToHaveRefreshedIdentity = () => expect(getGlobal().getUserIds()).to.deep.include(makePrebidIdentityContainer(refreshedToken)); +const expectGlobalToHaveToken = (token) => expect(getGlobal().getUserIds()).to.deep.include(makeUid2IdentityContainer(token)); const expectGlobalToHaveNoUid2 = () => expect(getGlobal().getUserIds()).to.not.haveOwnProperty('uid2'); -const expectLegacyToken = (bid) => expect(bid.userId).to.deep.include(makePrebidIdentityContainer(legacyToken)); -const expectNoLegacyToken = (bid) => expect(bid.userId).to.not.deep.include(makePrebidIdentityContainer(legacyToken)); -const expectModuleCookieEmptyOrMissing = () => expect(coreStorage.getCookie(moduleCookieName)).to.be.null; -const expectModuleCookieToContain = (initialIdentity, latestIdentity) => { - const cookie = JSON.parse(coreStorage.getCookie(moduleCookieName)); +const expectNoLegacyToken = (bid) => expect(bid.userId).to.not.deep.include(makeUid2IdentityContainer(legacyToken)); +const expectModuleStorageEmptyOrMissing = () => expect(getFromAppropriateStorage()).to.be.null; +const expectModuleStorageToContain = (initialIdentity, latestIdentity) => { + const cookie = JSON.parse(getFromAppropriateStorage()); if (initialIdentity) expect(cookie.originalToken.advertising_token).to.equal(initialIdentity); if (latestIdentity) expect(cookie.latestToken.advertising_token).to.equal(latestIdentity); } const apiUrl = 'https://prod.uidapi.com/v2/token/refresh'; const headers = { 'Content-Type': 'application/json' }; -const makeSuccessResponseBody = () => btoa(JSON.stringify({ status: 'success', body: { ...makeUid2Token(), advertising_token: refreshedToken } })); +const makeSuccessResponseBody = () => btoa(JSON.stringify({ status: 'success', body: { ...apiHelpers.makeTokenResponse(initialToken), advertising_token: refreshedToken } })); const configureUid2Response = (httpStatus, response) => server.respondWith('POST', apiUrl, (xhr) => xhr.respond(httpStatus, headers, response)); const configureUid2ApiSuccessResponse = () => configureUid2Response(200, makeSuccessResponseBody()); const configureUid2ApiFailResponse = () => configureUid2Response(500, 'Error'); -const respondAfterDelay = (delay) => new Promise((resolve) => setTimeout(() => { - server.respond(); - setTimeout(() => resolve()); -}, delay)); - -const runAuction = async () => { - const adUnits = [{ - code: 'adUnit-code', - mediaTypes: {banner: {}, native: {}}, - sizes: [[300, 200], [300, 600]], - bids: [{bidder: 'sampleBidder', params: {placementId: 'banner-only-bidder'}}] - }]; - return new Promise(function(resolve) { - requestBidsHook(function() { - resolve(adUnits[0].bids[0]); - }, {adUnits}); - }); -} - // Runs the provided test twice - once with a successful API mock, once with one which returns a server error const testApiSuccessAndFailure = (act, testDescription, failTestDescription, only = false) => { const testFn = only ? it.only : it; @@ -92,6 +72,26 @@ const testApiSuccessAndFailure = (act, testDescription, failTestDescription, onl await act(false); }); } + +const testCookieAndLocalStorage = (description, test, only = false) => { + const describeFn = only ? describe.only : describe; + describeFn(`Using cookies: ${description}`, async function() { + before(function() { + useLocalStorage = false; + }); + await test(); + }); + describeFn(`Using local storage: ${description}`, async function() { + before(function() { + useLocalStorage = true; + }); + after(function() { + useLocalStorage = false; + }); + await test(); + }); +}; + describe(`UID2 module`, function () { let suiteSandbox, testSandbox, timerSpy, fullTestTitle, restoreSubtleToUndefined = false; before(function () { @@ -141,21 +141,21 @@ describe(`UID2 module`, function () { await timerSpy.waitAllActiveTimers(); } } - coreStorage.setCookie(moduleCookieName, '', expireCookieDate); - coreStorage.setCookie(publisherCookieName, '', expireCookieDate); + cookieHelpers.clearCookies(moduleCookieName, publisherCookieName); + coreStorage.removeDataFromLocalStorage(moduleCookieName); debugOutput('----------------- END TEST ------------------'); }); describe('Configuration', function() { it('When no baseUrl is provided in config, the module calls the production endpoint', function() { - const uid2Token = makeUid2Token(initialToken, true, true); + const uid2Token = apiHelpers.makeTokenResponse(initialToken, true, true); config.setConfig(makePrebidConfig({uid2Token})); expect(server.requests[0]?.url).to.have.string('https://prod.uidapi.com/'); }); it('When a baseUrl is provided in config, the module calls the provided endpoint', function() { - const uid2Token = makeUid2Token(initialToken, true, true); + const uid2Token = apiHelpers.makeTokenResponse(initialToken, true, true); config.setConfig(makePrebidConfig({uid2Token, uid2ApiBase: 'https://operator-integ.uidapi.com'})); expect(server.requests[0]?.url).to.have.string('https://operator-integ.uidapi.com/'); }); @@ -175,9 +175,10 @@ describe(`UID2 module`, function () { // This should cover older integrations where the server is setting this legacy cookie and expecting the module to pass it on. describe('When a legacy cookie exists', function () { // Creates a test which sets the legacy cookie, configures the UID2 module with provided params, runs an - const createLegacyTest = function(params, bidAssertions) { + const createLegacyTest = function(params, bidAssertions, addConsent = false) { return async function() { - coreStorage.setCookie(moduleCookieName, legacyToken, getFutureCookieExpiry()); + coreStorage.setCookie(moduleCookieName, legacyToken, cookieHelpers.getFutureCookieExpiry()); + if (addConsent) setGdprApplies(); config.setConfig(makePrebidConfig(params)); const bid = await runAuction(); @@ -189,120 +190,154 @@ describe(`UID2 module`, function () { createLegacyTest(legacyConfigParams, [expectLegacyToken])); it('and a server cookie config is used without a valid server cookie, it should provide the legacy cookie', createLegacyTest(serverCookieConfigParams, [expectLegacyToken])); + it('and a server cookie is used with a valid server cookie configured using the new param name, it should provide the server cookie', + async function() { cookieHelpers.setPublisherCookie(publisherCookieName, apiHelpers.makeTokenResponse(initialToken)); await createLegacyTest(serverCookieConfigParams, [(bid) => expectToken(bid, initialToken), expectNoLegacyToken])(); }); it('and a server cookie is used with a valid server cookie, it should provide the server cookie', - async function() { setPublisherCookie(makeUid2Token()); await createLegacyTest(serverCookieConfigParams, [expectInitialToken, expectNoLegacyToken])(); }); + async function() { cookieHelpers.setPublisherCookie(publisherCookieName, apiHelpers.makeTokenResponse(initialToken)); await createLegacyTest(newServerCookieConfigParams, [(bid) => expectToken(bid, initialToken), expectNoLegacyToken])(); }); it('and a token is provided in config, it should provide the config token', - createLegacyTest({uid2Token: makeUid2Token()}, [expectInitialToken, expectNoLegacyToken])); + createLegacyTest({uid2Token: apiHelpers.makeTokenResponse(initialToken)}, [(bid) => expectToken(bid, initialToken), expectNoLegacyToken])); + it('and GDPR applies, no identity should be provided to the auction', + createLegacyTest(legacyConfigParams, [expectNoIdentity], true)); + it('and GDPR applies, when getId is called directly it provides no identity', () => { + coreStorage.setCookie(moduleCookieName, legacyToken, cookieHelpers.getFutureCookieExpiry()); + const consentConfig = setGdprApplies(); + let configObj = makePrebidConfig(legacyConfigParams); + const result = uid2IdSubmodule.getId(configObj.userSync.userIds[0], consentConfig.consentData); + expect(result?.id).to.not.exist; + }); + + it('multiple runs do not change the value', async function() { + coreStorage.setCookie(moduleCookieName, legacyToken, cookieHelpers.getFutureCookieExpiry()); + config.setConfig(makePrebidConfig(legacyConfigParams)); + + const bid = await runAuction(); + + console.log('Storage', coreStorage.getDataFromLocalStorage(moduleCookieName)); + init(config); + setSubmoduleRegistry([uid2IdSubmodule]); + config.setConfig(makePrebidConfig(legacyConfigParams)); + const bid2 = await runAuction(); + + expect(bid.userId.uid2.id).to.equal(bid2.userId.uid2.id); + }); }); // This setup runs all of the functional tests with both types of config - the full token response in params, or a server cookie with the cookie name provided let scenarios = [ { name: 'Token provided in config call', - setConfig: (token, extraConfig = {}) => config.setConfig(makePrebidConfig({uid2Token: token}, extraConfig)), + setConfig: (token, extraConfig = {}) => { + const gen = makePrebidConfig({uid2Token: token}, extraConfig); + console.log('GENERATED CONFIG', gen.userSync.userIds[0].params); + return config.setConfig(gen); + }, }, { name: 'Token provided in server-set cookie', setConfig: (token, extraConfig) => { - setPublisherCookie(token); + cookieHelpers.setPublisherCookie(publisherCookieName, token); config.setConfig(makePrebidConfig(serverCookieConfigParams, extraConfig)); }, } ] scenarios.forEach(function(scenario) { - describe(scenario.name, function() { + testCookieAndLocalStorage(scenario.name, function() { describe(`When an expired token which can be refreshed is provided`, function() { describe('When the refresh is available in time', function() { testApiSuccessAndFailure(async function(apiSucceeds) { - scenario.setConfig(makeUid2Token(initialToken, true, true)); - respondAfterDelay(auctionDelayMs / 10); + scenario.setConfig(apiHelpers.makeTokenResponse(initialToken, true, true)); + apiHelpers.respondAfterDelay(auctionDelayMs / 10); const bid = await runAuction(); - if (apiSucceeds) expectRefreshedToken(bid); + if (apiSucceeds) expectToken(bid, refreshedToken); else expectNoIdentity(bid); }, 'it should be used in the auction', 'the auction should have no uid2'); testApiSuccessAndFailure(async function(apiSucceeds) { - scenario.setConfig(makeUid2Token(initialToken, true, true)); - respondAfterDelay(auctionDelayMs / 10); + scenario.setConfig(apiHelpers.makeTokenResponse(initialToken, true, true)); + apiHelpers.respondAfterDelay(auctionDelayMs / 10); await runAuction(); if (apiSucceeds) { - expectModuleCookieToContain(initialToken, refreshedToken); + expectModuleStorageToContain(initialToken, refreshedToken); } else { - expectModuleCookieEmptyOrMissing(); + expectModuleStorageEmptyOrMissing(); } - }, 'the refreshed token should be stored in the module cookie', 'the module cookie should not be set'); + }, 'the refreshed token should be stored in the module storage', 'the module storage should not be set'); }); describe(`when the response doesn't arrive before the auction timer`, function() { testApiSuccessAndFailure(async function() { - scenario.setConfig(makeUid2Token(initialToken, true, true)); + scenario.setConfig(apiHelpers.makeTokenResponse(initialToken, true, true)); const bid = await runAuction(); expectNoIdentity(bid); }, 'it should run the auction'); testApiSuccessAndFailure(async function(apiSucceeds) { - scenario.setConfig(makeUid2Token(initialToken, true, true)); - const promise = respondAfterDelay(auctionDelayMs * 2); + scenario.setConfig(apiHelpers.makeTokenResponse(initialToken, true, true)); + const promise = apiHelpers.respondAfterDelay(auctionDelayMs * 2); const bid = await runAuction(); expectNoIdentity(bid); expectGlobalToHaveNoUid2(); await promise; - if (apiSucceeds) expectGlobalToHaveRefreshedIdentity(); + if (apiSucceeds) expectGlobalToHaveToken(refreshedToken); else expectGlobalToHaveNoUid2(); }, 'it should update the userId after the auction', 'there should be no global identity'); }) describe('and there is a refreshed token in the module cookie', function() { it('the refreshed value from the cookie is used', async function() { - const initialIdentity = makeUid2Token(initialToken, true, true); - const refreshedIdentity = makeUid2Token(refreshedToken); + const initialIdentity = apiHelpers.makeTokenResponse(initialToken, true, true); + const refreshedIdentity = apiHelpers.makeTokenResponse(refreshedToken); const moduleCookie = {originalToken: initialIdentity, latestToken: refreshedIdentity}; - coreStorage.setCookie(moduleCookieName, JSON.stringify(moduleCookie), getFutureCookieExpiry()); + coreStorage.setCookie(moduleCookieName, JSON.stringify(moduleCookie), cookieHelpers.getFutureCookieExpiry()); scenario.setConfig(initialIdentity); const bid = await runAuction(); - expectRefreshedToken(bid); + expectToken(bid, refreshedToken); }); }) }); describe(`When a current token is provided`, function() { - beforeEach(function() { - scenario.setConfig(makeUid2Token()); - }); - it('it should use the token in the auction', async function() { + scenario.setConfig(apiHelpers.makeTokenResponse(initialToken)); const bid = await runAuction(); - expectInitialToken(bid); + expectToken(bid, initialToken); }); + + it('and GDPR applies, the token should not be used', async function() { + setGdprApplies(); + scenario.setConfig(apiHelpers.makeTokenResponse(initialToken)); + const bid = await runAuction(); + expectNoIdentity(bid); + }) }); describe(`When a current token which should be refreshed is provided, and the auction is set to run immediately`, function() { beforeEach(function() { - scenario.setConfig(makeUid2Token(initialToken, true), {auctionDelay: 0, syncDelay: 1}); + scenario.setConfig(apiHelpers.makeTokenResponse(initialToken, true), {auctionDelay: 0, syncDelay: 1}); }); testApiSuccessAndFailure(async function() { - respondAfterDelay(10); + apiHelpers.respondAfterDelay(10); const bid = await runAuction(); - expectInitialToken(bid); + expectToken(bid, initialToken); }, 'it should not be refreshed before the auction runs'); testApiSuccessAndFailure(async function(success) { - const promise = respondAfterDelay(1); + const promise = apiHelpers.respondAfterDelay(1); await runAuction(); await promise; if (success) { - expectModuleCookieToContain(initialToken, refreshedToken); + expectModuleStorageToContain(initialToken, refreshedToken); } else { - expectModuleCookieToContain(initialToken, initialToken); + expectModuleStorageToContain(initialToken, initialToken); } }, 'the refreshed token should be stored in the module cookie after the auction runs', 'the module cookie should only have the original token'); it('it should use the current token in the auction', async function() { const bid = await runAuction(); - expectInitialToken(bid); + expectToken(bid, initialToken); }); }); }); diff --git a/test/spec/modules/underdogmediaBidAdapter_spec.js b/test/spec/modules/underdogmediaBidAdapter_spec.js index 70d09513f27..2d7c1f11178 100644 --- a/test/spec/modules/underdogmediaBidAdapter_spec.js +++ b/test/spec/modules/underdogmediaBidAdapter_spec.js @@ -1,29 +1,38 @@ -import { expect } from 'chai'; -import { spec, resetUserSync } from 'modules/underdogmediaBidAdapter.js'; +import { + expect +} from 'chai'; +import { + spec, + resetUserSync +} from 'modules/underdogmediaBidAdapter.js'; describe('UnderdogMedia adapter', function () { let bidRequests; let bidderRequest; beforeEach(function () { - bidRequests = [ - { - bidder: 'underdogmedia', - params: { - siteId: 12143 - }, - adUnitCode: '/19968336/header-bid-tag-1', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600], [728, 90], [160, 600], [320, 50]], - } - }, - bidId: '23acc48ad47af5', - auctionId: '0fb4905b-9456-4152-86be-c6f6d259ba99', - bidderRequestId: '1c56ad30b9b8ca8', - transactionId: '92489f71-1bf2-49a0-adf9-000cea934729' - } - ]; + bidRequests = [{ + bidder: 'underdogmedia', + params: { + siteId: 12143 + }, + adUnitCode: '/19968336/header-bid-tag-1', + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 600], + [728, 90], + [160, 600], + [320, 50] + ], + } + }, + bidId: '23acc48ad47af5', + auctionId: '0fb4905b-9456-4152-86be-c6f6d259ba99', + bidderRequestId: '1c56ad30b9b8ca8', + transactionId: '92489f71-1bf2-49a0-adf9-000cea934729' + }]; bidderRequest = { timeout: 3000, @@ -49,7 +58,10 @@ describe('UnderdogMedia adapter', function () { }, mediaTypes: { banner: { - sizes: [[300, 250], [300, 600]] + sizes: [ + [300, 250], + [300, 600] + ] } } }; @@ -76,7 +88,10 @@ describe('UnderdogMedia adapter', function () { params: {}, mediaTypes: { banner: { - sizes: [[300, 250], [300, 600]] + sizes: [ + [300, 250], + [300, 600] + ] } } }; @@ -86,90 +101,94 @@ describe('UnderdogMedia adapter', function () { }); it('request data should contain sid', function () { - let bidRequests = [ - { - bidId: '3c9408cdbf2f68', - bidder: 'underdogmedia', - mediaTypes: { - banner: { - sizes: [[300, 250]] - } - }, - params: { - siteId: '12143' - }, - auctionId: '10b327aa396609', - adUnitCode: '/123456/header-bid-tag-1' - } - ]; + let bidRequests = [{ + bidId: '3c9408cdbf2f68', + bidder: 'underdogmedia', + mediaTypes: { + banner: { + sizes: [ + [300, 250] + ] + } + }, + params: { + siteId: '12143' + }, + auctionId: '10b327aa396609', + adUnitCode: '/123456/header-bid-tag-1' + }]; const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.sid).to.equal('12143'); + expect(request.data.sid).to.equal(12143); }); it('request data should contain sizes', function () { - let bidRequests = [ - { - bidId: '3c9408cdbf2f68', - mediaTypes: { - banner: { - sizes: [[300, 250], [728, 90]] - } - }, - bidder: 'underdogmedia', - params: { - siteId: '12143' - }, - auctionId: '10b327aa396609', - adUnitCode: '/123456/header-bid-tag-1' - } - ]; + let bidRequests = [{ + bidId: '3c9408cdbf2f68', + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [728, 90] + ] + } + }, + bidder: 'underdogmedia', + params: { + siteId: '12143' + }, + auctionId: '10b327aa396609', + adUnitCode: '/123456/header-bid-tag-1' + }]; const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.sizes).to.equal('300x250,728x90'); + expect(request.data.placements[0].sizes[0]).to.equal('300x250'); + expect(request.data.placements[0].sizes[1]).to.equal('728x90'); }); it('request data should contain gdpr info', function () { - let bidRequests = [ - { - bidId: '3c9408cdbf2f68', - mediaTypes: { - banner: { - sizes: [[300, 250], [728, 90]] - } - }, - bidder: 'underdogmedia', - params: { - siteId: '12143' - }, - auctionId: '10b327aa396609', - adUnitCode: '/123456/header-bid-tag-1' - } - ]; + let bidRequests = [{ + bidId: '3c9408cdbf2f68', + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [728, 90] + ] + } + }, + bidder: 'underdogmedia', + params: { + siteId: '12143' + }, + auctionId: '10b327aa396609', + adUnitCode: '/123456/header-bid-tag-1' + }]; const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.gdprApplies).to.equal(true); - expect(request.data.consentGiven).to.equal(true); - expect(request.data.consentData).to.equal('consentDataString'); + expect(request.data.gdpr.gdprApplies).to.equal(true); + expect(request.data.gdpr.consentGiven).to.equal(true); + expect(request.data.gdpr.consentData).to.equal('consentDataString'); }); it('should not build a request if no vendorConsent', function () { - let bidRequests = [ - { - bidId: '3c9408cdbf2f68', - mediaTypes: { - banner: { - sizes: [[300, 250], [728, 90]] - } - }, - bidder: 'underdogmedia', - params: { - siteId: '12143' - }, - auctionId: '10b327aa396609', - adUnitCode: '/123456/header-bid-tag-1' - } - ]; + let bidRequests = [{ + bidId: '3c9408cdbf2f68', + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [728, 90] + ] + } + }, + bidder: 'underdogmedia', + params: { + siteId: '12143' + }, + auctionId: '10b327aa396609', + adUnitCode: '/123456/header-bid-tag-1' + }]; let bidderRequest = { timeout: 3000, @@ -189,22 +208,23 @@ describe('UnderdogMedia adapter', function () { }); it('should properly build a request if no vendorConsent but no gdprApplies', function () { - let bidRequests = [ - { - bidId: '3c9408cdbf2f68', - mediaTypes: { - banner: { - sizes: [[300, 250], [728, 90]] - } - }, - bidder: 'underdogmedia', - params: { - siteId: '12143' - }, - auctionId: '10b327aa396609', - adUnitCode: '/123456/header-bid-tag-1' - } - ]; + let bidRequests = [{ + bidId: '3c9408cdbf2f68', + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [728, 90] + ] + } + }, + bidder: 'underdogmedia', + params: { + siteId: '12143' + }, + auctionId: '10b327aa396609', + adUnitCode: '/123456/header-bid-tag-1' + }]; let bidderRequest = { timeout: 3000, @@ -220,30 +240,32 @@ describe('UnderdogMedia adapter', function () { } const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.sizes).to.equal('300x250,728x90'); - expect(request.data.sid).to.equal('12143'); - expect(request.data.gdprApplies).to.equal(false); - expect(request.data.consentGiven).to.equal(false); - expect(request.data.consentData).to.equal('consentDataString'); + expect(request.data.placements[0].sizes[0]).to.equal('300x250'); + expect(request.data.placements[0].sizes[1]).to.equal('728x90'); + expect(request.data.sid).to.equal(12143); + expect(request.data.gdpr.gdprApplies).to.equal(false); + expect(request.data.gdpr.consentGiven).to.equal(false); + expect(request.data.gdpr.consentData).to.equal('consentDataString'); }); it('should properly build a request if gdprConsent empty', function () { - let bidRequests = [ - { - bidId: '3c9408cdbf2f68', - mediaTypes: { - banner: { - sizes: [[300, 250], [728, 90]] - } - }, - bidder: 'underdogmedia', - params: { - siteId: '12143' - }, - auctionId: '10b327aa396609', - adUnitCode: '/123456/header-bid-tag-1' - } - ]; + let bidRequests = [{ + bidId: '3c9408cdbf2f68', + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [728, 90] + ] + } + }, + bidder: 'underdogmedia', + params: { + siteId: '12143' + }, + auctionId: '10b327aa396609', + adUnitCode: '/123456/header-bid-tag-1' + }]; let bidderRequest = { timeout: 3000, @@ -251,21 +273,663 @@ describe('UnderdogMedia adapter', function () { } const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.sizes).to.equal('300x250,728x90'); - expect(request.data.sid).to.equal('12143'); + expect(request.data.placements[0].sizes[0]).to.equal('300x250'); + expect(request.data.placements[0].sizes[1]).to.equal('728x90'); + expect(request.data.sid).to.equal(12143); }); it('should have uspConsent if defined', function () { const uspConsent = '1YYN' bidderRequest.uspConsent = uspConsent const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.uspConsent).to.equal(uspConsent); + expect(request.data.usp.uspConsent).to.equal(uspConsent); + }); + + it('should have correct number of placements', function () { + let bidRequests = [{ + adUnitCode: 'div-gpt-ad-1460505748561-0', + auctionId: 'dfa93f1f-6ecc-4d75-8725-f5cb92307658', + bidId: '2dbc995ad299c', + bidder: 'underdogmedia', + crumbs: { + pubcid: 'ba6cbf43-abc0-4d61-b14f-e10f605b74d7' + }, + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [160, 600] + ] + } + }, + ortb2Imp: { + ext: { + gpid: '/19968336/header-bid-tag-0' + } + }, + params: { + siteId: '12143' + }, + userId: { + tdid: '7a9fc5a2-346d-4502-826e-017a9badf5f3' + } + }, + { + adUnitCode: 'div-gpt-ad-2460505748561-0', + auctionId: 'dfa93f1f-6ecc-4d75-8725-f5cb92307658', + bidId: '3a378b833cdef4', + bidder: 'underdogmedia', + crumbs: { + pubcid: 'ba6cbf43-abc0-4d61-b14f-e10f605b74d7' + }, + mediaTypes: { + banner: { + sizes: [ + [300, 250] + ] + } + }, + ortb2Imp: { + ext: { + gpid: '/19968336/header-bid-tag-1' + } + }, + params: { + siteId: '12143' + }, + userId: { + tdid: '7a9fc5a2-346d-4502-826e-017a9badf5f3' + } + }, + { + adUnitCode: 'div-gpt-ad-3460505748561-0', + auctionId: 'dfa93f1f-6ecc-4d75-8725-f5cb92307658', + bidId: '4088f04e07c2a1', + bidder: 'underdogmedia', + crumbs: { + pubcid: 'ba6cbf43-abc0-4d61-b14f-e10f605b74d7' + }, + mediaTypes: { + banner: { + sizes: [ + [160, 600] + ] + } + }, + ortb2Imp: { + ext: { + gpid: '/19968336/header-bid-tag-2' + } + }, + params: { + siteId: '12143' + }, + userId: { + tdid: '7a9fc5a2-346d-4502-826e-017a9badf5f3' + } + } + ]; + + const request = spec.buildRequests(bidRequests, bidderRequest); + + expect(request.data.usp.uspConsent).to.be.undefined; + expect(request.data.placements.length).to.equal(3); + }); + + it('should have correct adUnitCode for each placement', function () { + let bidRequests = [{ + adUnitCode: 'div-gpt-ad-1460505748561-0', + auctionId: 'dfa93f1f-6ecc-4d75-8725-f5cb92307658', + bidId: '2dbc995ad299c', + bidder: 'underdogmedia', + crumbs: { + pubcid: 'ba6cbf43-abc0-4d61-b14f-e10f605b74d7' + }, + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [160, 600] + ] + } + }, + ortb2Imp: { + ext: { + gpid: '/19968336/header-bid-tag-0' + } + }, + params: { + siteId: '12143' + }, + userId: { + tdid: '7a9fc5a2-346d-4502-826e-017a9badf5f3' + } + }, + { + adUnitCode: 'div-gpt-ad-2460505748561-0', + auctionId: 'dfa93f1f-6ecc-4d75-8725-f5cb92307658', + bidId: '3a378b833cdef4', + bidder: 'underdogmedia', + crumbs: { + pubcid: 'ba6cbf43-abc0-4d61-b14f-e10f605b74d7' + }, + mediaTypes: { + banner: { + sizes: [ + [300, 250] + ] + } + }, + ortb2Imp: { + ext: { + gpid: '/19968336/header-bid-tag-1' + } + }, + params: { + siteId: '12143' + }, + userId: { + tdid: '7a9fc5a2-346d-4502-826e-017a9badf5f3' + } + }, + { + adUnitCode: 'div-gpt-ad-3460505748561-0', + auctionId: 'dfa93f1f-6ecc-4d75-8725-f5cb92307658', + bidId: '4088f04e07c2a1', + bidder: 'underdogmedia', + crumbs: { + pubcid: 'ba6cbf43-abc0-4d61-b14f-e10f605b74d7' + }, + mediaTypes: { + banner: { + sizes: [ + [160, 600] + ] + } + }, + ortb2Imp: { + ext: { + gpid: '/19968336/header-bid-tag-2' + } + }, + params: { + siteId: '12143' + }, + userId: { + tdid: '7a9fc5a2-346d-4502-826e-017a9badf5f3' + } + } + ]; + + const request = spec.buildRequests(bidRequests, bidderRequest); + + expect(request.data.usp.uspConsent).to.be.undefined; + expect(request.data.placements[0].adUnitCode).to.equal('div-gpt-ad-1460505748561-0'); + expect(request.data.placements[1].adUnitCode).to.equal('div-gpt-ad-2460505748561-0'); + expect(request.data.placements[2].adUnitCode).to.equal('div-gpt-ad-3460505748561-0'); + }); + + it('should have gpid if it exists', function () { + let bidRequests = [{ + adUnitCode: 'div-gpt-ad-1460505748561-0', + auctionId: 'dfa93f1f-6ecc-4d75-8725-f5cb92307658', + bidId: '2dbc995ad299c', + bidder: 'underdogmedia', + crumbs: { + pubcid: 'ba6cbf43-abc0-4d61-b14f-e10f605b74d7' + }, + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [160, 600] + ] + } + }, + ortb2Imp: { + ext: { + gpid: '/19968336/header-bid-tag-0' + } + }, + params: { + siteId: '12143' + }, + userId: { + tdid: '7a9fc5a2-346d-4502-826e-017a9badf5f3' + } + }]; + + const request = spec.buildRequests(bidRequests, bidderRequest); + + expect(request.data.placements[0].gpid).to.equal('/19968336/header-bid-tag-0'); + }); + + it('gpid should be undefined if it does not exists', function () { + let bidRequests = [{ + adUnitCode: 'div-gpt-ad-1460505748561-0', + auctionId: 'dfa93f1f-6ecc-4d75-8725-f5cb92307658', + bidId: '2dbc995ad299c', + bidder: 'underdogmedia', + crumbs: { + pubcid: 'ba6cbf43-abc0-4d61-b14f-e10f605b74d7' + }, + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [160, 600] + ] + } + }, + params: { + siteId: '12143' + }, + userId: { + tdid: '7a9fc5a2-346d-4502-826e-017a9badf5f3' + } + }]; + + const request = spec.buildRequests(bidRequests, bidderRequest); + + expect(request.data.placements[0].gpid).to.equal(undefined); + }); + + it('should have productId equal to 1 if the productId is standard', function () { + let bidRequests = [{ + adUnitCode: 'div-gpt-ad-1460505748561-0', + auctionId: 'dfa93f1f-6ecc-4d75-8725-f5cb92307658', + bidId: '2dbc995ad299c', + bidder: 'underdogmedia', + crumbs: { + pubcid: 'ba6cbf43-abc0-4d61-b14f-e10f605b74d7' + }, + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [160, 600] + ] + } + }, + params: { + siteId: '12143', + productId: 'standard' + }, + userId: { + tdid: '7a9fc5a2-346d-4502-826e-017a9badf5f3' + } + }]; + + const request = spec.buildRequests(bidRequests, bidderRequest); + + expect(request.data.placements[0].productId).to.equal(1); + }); + + it('should have productId equal to 2 if the productId is adhesion', function () { + let bidRequests = [{ + adUnitCode: 'div-gpt-ad-1460505748561-0', + auctionId: 'dfa93f1f-6ecc-4d75-8725-f5cb92307658', + bidId: '2dbc995ad299c', + bidder: 'underdogmedia', + crumbs: { + pubcid: 'ba6cbf43-abc0-4d61-b14f-e10f605b74d7' + }, + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [160, 600] + ] + } + }, + params: { + siteId: '12143', + productId: 'adhesion' + }, + userId: { + tdid: '7a9fc5a2-346d-4502-826e-017a9badf5f3' + } + }]; + + const request = spec.buildRequests(bidRequests, bidderRequest); + + expect(request.data.placements[0].productId).to.equal(2); + }); + + it('productId should default to 1 if it is not defined', function () { + let bidRequests = [{ + adUnitCode: 'div-gpt-ad-1460505748561-0', + auctionId: 'dfa93f1f-6ecc-4d75-8725-f5cb92307658', + bidId: '2dbc995ad299c', + bidder: 'underdogmedia', + crumbs: { + pubcid: 'ba6cbf43-abc0-4d61-b14f-e10f605b74d7' + }, + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [160, 600] + ] + } + }, + params: { + siteId: '12143' + }, + userId: { + tdid: '7a9fc5a2-346d-4502-826e-017a9badf5f3' + } + }]; + + const request = spec.buildRequests(bidRequests, bidderRequest); + + expect(request.data.placements[0].productId).to.equal(1); + }); + + it('should have correct sizes for multiple placements', function () { + let bidRequests = [{ + adUnitCode: 'div-gpt-ad-1460505748561-0', + auctionId: 'dfa93f1f-6ecc-4d75-8725-f5cb92307658', + bidId: '2dbc995ad299c', + bidder: 'underdogmedia', + crumbs: { + pubcid: 'ba6cbf43-abc0-4d61-b14f-e10f605b74d7' + }, + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [160, 600] + ] + } + }, + ortb2Imp: { + ext: { + gpid: '/19968336/header-bid-tag-0' + } + }, + params: { + siteId: '12143' + }, + userId: { + tdid: '7a9fc5a2-346d-4502-826e-017a9badf5f3' + } + }, + { + adUnitCode: 'div-gpt-ad-2460505748561-0', + auctionId: 'dfa93f1f-6ecc-4d75-8725-f5cb92307658', + bidId: '3a378b833cdef4', + bidder: 'underdogmedia', + crumbs: { + pubcid: 'ba6cbf43-abc0-4d61-b14f-e10f605b74d7' + }, + mediaTypes: { + banner: { + sizes: [ + [300, 250] + ] + } + }, + ortb2Imp: { + ext: { + gpid: '/19968336/header-bid-tag-1' + } + }, + params: { + siteId: '12143' + }, + userId: { + tdid: '7a9fc5a2-346d-4502-826e-017a9badf5f3' + } + }, + { + adUnitCode: 'div-gpt-ad-3460505748561-0', + auctionId: 'dfa93f1f-6ecc-4d75-8725-f5cb92307658', + bidId: '4088f04e07c2a1', + bidder: 'underdogmedia', + crumbs: { + pubcid: 'ba6cbf43-abc0-4d61-b14f-e10f605b74d7' + }, + mediaTypes: { + banner: { + sizes: [ + [160, 600] + ] + } + }, + ortb2Imp: { + ext: { + gpid: '/19968336/header-bid-tag-2' + } + }, + params: { + siteId: '12143' + }, + userId: { + tdid: '7a9fc5a2-346d-4502-826e-017a9badf5f3' + } + } + ]; + + const request = spec.buildRequests(bidRequests, bidderRequest); + + expect(request.data.placements[0].sizes.length).to.equal(2); + expect(request.data.placements[0].sizes[0]).to.equal('300x250'); + expect(request.data.placements[0].sizes[1]).to.equal('160x600'); + expect(request.data.placements[1].sizes.length).to.equal(1); + expect(request.data.placements[1].sizes[0]).to.equal('300x250'); + expect(request.data.placements[2].sizes.length).to.equal(1); + expect(request.data.placements[2].sizes[0]).to.equal('160x600'); + }); + + it('should have ref if it exists', function () { + let bidderRequest = { + timeout: 3000, + gdprConsent: { + gdprApplies: 1, + consentString: 'consentDataString', + vendorData: { + vendorConsents: { + '159': 1 + }, + }, + }, + refererInfo: { + page: 'www.example.com' + } + } + + const request = spec.buildRequests(bidRequests, bidderRequest); + + expect(request.data.ref).to.equal('www.example.com'); + }); + + it('ref should be undefined if it does not exist', function () { + let bidderRequest = { + timeout: 3000, + gdprConsent: { + gdprApplies: 1, + consentString: 'consentDataString', + vendorData: { + vendorConsents: { + '159': 1 + }, + }, + } + } + + const request = spec.buildRequests(bidRequests, bidderRequest); + + expect(request.data.ref).to.equal(undefined); + }); + + it('should have pubcid if it exists', function () { + let bidRequests = [{ + adUnitCode: 'div-gpt-ad-1460505748561-0', + auctionId: 'dfa93f1f-6ecc-4d75-8725-f5cb92307658', + bidId: '2dbc995ad299c', + bidder: 'underdogmedia', + crumbs: { + pubcid: 'ba6cbf43-abc0-4d61-b14f-e10f605b74d7' + }, + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [160, 600] + ] + } + }, + ortb2Imp: { + ext: { + gpid: '/19968336/header-bid-tag-0' + } + }, + params: { + siteId: '12143' + }, + userId: { + tdid: '7a9fc5a2-346d-4502-826e-017a9badf5f3' + } + }]; + + const request = spec.buildRequests(bidRequests, bidderRequest); + + expect(request.data.userIds.pubcid).to.equal('ba6cbf43-abc0-4d61-b14f-e10f605b74d7'); + }); + + it('pubcid should be undefined if it does not exist', function () { + let bidRequests = [{ + adUnitCode: 'div-gpt-ad-1460505748561-0', + auctionId: 'dfa93f1f-6ecc-4d75-8725-f5cb92307658', + bidId: '2dbc995ad299c', + bidder: 'underdogmedia', + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [160, 600] + ] + } + }, + ortb2Imp: { + ext: { + gpid: '/19968336/header-bid-tag-0' + } + }, + params: { + siteId: '12143' + }, + userId: { + tdid: '7a9fc5a2-346d-4502-826e-017a9badf5f3' + } + }]; + + const request = spec.buildRequests(bidRequests, bidderRequest); + + expect(request.data.userIds.pubcid).to.equal(undefined); + }); + + it('should have unifiedId if tdid if it exists', function () { + let bidRequests = [{ + adUnitCode: 'div-gpt-ad-1460505748561-0', + auctionId: 'dfa93f1f-6ecc-4d75-8725-f5cb92307658', + bidId: '2dbc995ad299c', + bidder: 'underdogmedia', + crumbs: { + pubcid: 'ba6cbf43-abc0-4d61-b14f-e10f605b74d7' + }, + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [160, 600] + ] + } + }, + ortb2Imp: { + ext: { + gpid: '/19968336/header-bid-tag-0' + } + }, + params: { + siteId: '12143' + }, + userId: { + tdid: '7a9fc5a2-346d-4502-826e-017a9badf5f3' + } + }]; + + const request = spec.buildRequests(bidRequests, bidderRequest); + + expect(request.data.userIds.unifiedId).to.equal('7a9fc5a2-346d-4502-826e-017a9badf5f3'); + }); + + it('unifiedId should be undefined if tdid does not exist', function () { + let bidRequests = [{ + adUnitCode: 'div-gpt-ad-1460505748561-0', + auctionId: 'dfa93f1f-6ecc-4d75-8725-f5cb92307658', + bidId: '2dbc995ad299c', + bidder: 'underdogmedia', + crumbs: { + pubcid: 'ba6cbf43-abc0-4d61-b14f-e10f605b74d7' + }, + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [160, 600] + ] + } + }, + ortb2Imp: { + ext: { + gpid: '/19968336/header-bid-tag-0' + } + }, + params: { + siteId: '12143' + } + }]; + + const request = spec.buildRequests(bidRequests, bidderRequest); + + expect(request.data.userIds.unifiedId).to.equal(undefined); }); - it('should not have uspConsent if not defined', function () { - bidderRequest.uspConsent = undefined + it('should have correct viewability information', function () { + let bidRequests = [{ + adUnitCode: 'div-gpt-ad-1460505748561-0', + auctionId: 'dfa93f1f-6ecc-4d75-8725-f5cb92307658', + bidId: '2dbc995ad299c', + bidder: 'underdogmedia', + crumbs: { + pubcid: 'ba6cbf43-abc0-4d61-b14f-e10f605b74d7' + }, + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [160, 600] + ] + } + }, + ortb2Imp: { + ext: { + gpid: '/19968336/header-bid-tag-0' + } + }, + params: { + siteId: '12143' + }, + userId: { + tdid: '7a9fc5a2-346d-4502-826e-017a9badf5f3' + } + }]; + const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.uspConsent).to.be.undefined; + + expect(request.data.placements[0].viewability).to.equal(-1) }); }); @@ -273,26 +937,27 @@ describe('UnderdogMedia adapter', function () { it('should return complete bid response', function () { let serverResponse = { body: { - mids: [ - { - ad_code_html: 'ad_code_html', - advertiser_domains: ['domain1'], - cpm: 2.5, - height: '600', - mid: '32634', - notification_url: 'notification_url', - tid: '4', - width: '160' - }, - { - ad_code_html: 'ad_code_html', - cpm: 2.5, - height: '250', - mid: '32633', - notification_url: 'notification_url', - tid: '2', - width: '300' - }, + mids: [{ + ad_code_html: 'ad_code_html', + ad_unit_code: '/19968336/header-bid-tag-1', + advertiser_domains: ['domain1'], + cpm: 2.5, + height: '600', + mid: '32634', + notification_url: 'notification_url', + tid: '4', + width: '160', + }, + { + ad_code_html: 'ad_code_html', + ad_unit_code: '/19968336/header-bid-tag-1', + cpm: 3.0, + height: '250', + mid: '32633', + notification_url: 'notification_url', + tid: '2', + width: '300' + }, ] } }; @@ -302,7 +967,6 @@ describe('UnderdogMedia adapter', function () { expect(bids).to.be.lengthOf(2); expect(bids[0].meta.advertiserDomains).to.deep.equal(['domain1']) - expect(bids[0].bidderCode).to.equal('underdogmedia'); expect(bids[0].cpm).to.equal(2.5); expect(bids[0].width).to.equal('160'); expect(bids[0].height).to.equal('600'); @@ -326,17 +990,15 @@ describe('UnderdogMedia adapter', function () { it('should return empty bid response on incorrect size', function () { let serverResponse = { body: { - mids: [ - { - ad_code_html: 'ad_code_html', - cpm: 2.5, - height: '123', - mid: '32634', - notification_url: 'notification_url', - tid: '4', - width: '160' - } - ] + mids: [{ + ad_code_html: 'ad_code_html', + cpm: 2.5, + height: '123', + mid: '32634', + notification_url: 'notification_url', + tid: '4', + width: '160' + }] } }; const request = spec.buildRequests(bidRequests, bidderRequest); @@ -348,17 +1010,15 @@ describe('UnderdogMedia adapter', function () { it('should return empty bid response on 0 cpm', function () { let serverResponse = { body: { - mids: [ - { - ad_code_html: 'ad_code_html', - cpm: 0, - height: '600', - mid: '32634', - notification_url: 'notification_url', - tid: '4', - width: '160' - } - ] + mids: [{ + ad_code_html: 'ad_code_html', + cpm: 0, + height: '600', + mid: '32634', + notification_url: 'notification_url', + tid: '4', + width: '160' + }] } }; const request = spec.buildRequests(bidRequests, bidderRequest); @@ -370,17 +1030,15 @@ describe('UnderdogMedia adapter', function () { it('should return empty bid response if no ad in response', function () { let serverResponse = { body: { - mids: [ - { - ad_code_html: '', - cpm: 2.5, - height: '600', - mid: '32634', - notification_url: 'notification_url', - tid: '4', - width: '160' - } - ] + mids: [{ + ad_code_html: '', + cpm: 2.5, + height: '600', + mid: '32634', + notification_url: 'notification_url', + tid: '4', + width: '160' + }] } }; const request = spec.buildRequests(bidRequests, bidderRequest); @@ -392,17 +1050,16 @@ describe('UnderdogMedia adapter', function () { it('ad html string should contain the notification urls', function () { let serverResponse = { body: { - mids: [ - { - ad_code_html: 'ad_cod_html', - cpm: 2.5, - height: '600', - mid: '32634', - notification_url: 'notification_url', - tid: '4', - width: '160' - } - ] + mids: [{ + ad_code_html: 'ad_cod_html', + ad_unit_code: '/19968336/header-bid-tag-1', + cpm: 2.5, + height: '600', + mid: '32634', + notification_url: 'notification_url', + tid: '4', + width: '160' + }] } }; const request = spec.buildRequests(bidRequests, bidderRequest); @@ -430,15 +1087,14 @@ describe('UnderdogMedia adapter', function () { const responseWithUserSyncs = [{ body: { - userSyncs: [ - { - type: 'image', - url: 'https://test.url.com' - }, - { - type: 'iframe', - url: 'https://test.url.com' - } + userSyncs: [{ + type: 'image', + url: 'https://test.url.com' + }, + { + type: 'iframe', + url: 'https://test.url.com' + } ] } }]; diff --git a/test/spec/modules/undertoneBidAdapter_spec.js b/test/spec/modules/undertoneBidAdapter_spec.js index 8766307b3bd..7f9c2e7b3d5 100644 --- a/test/spec/modules/undertoneBidAdapter_spec.js +++ b/test/spec/modules/undertoneBidAdapter_spec.js @@ -41,7 +41,9 @@ const videoBidReq = [{ }, mediaTypes: {video: { context: 'outstream', - playerSize: [640, 480] + playerSize: [640, 480], + placement: 1, + plcmt: 1 }}, sizes: [[300, 250], [300, 600]], bidId: '263be71e91dd9d', @@ -184,6 +186,31 @@ const bidderReqCcpaAndGdpr = { uspConsent: 'NY12' }; +const bidderReqGpp = { + refererInfo: { + topmostLocation: 'http://prebid.org/dev-docs/bidder-adaptor.html' + }, + gppConsent: { + gppString: 'DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN', + applicableSections: [7] + } +}; + +const bidderReqFullGppCcpaGdpr = { + refererInfo: { + topmostLocation: 'http://prebid.org/dev-docs/bidder-adaptor.html' + }, + gppConsent: { + gppString: 'DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN', + applicableSections: [7] + }, + gdprConsent: { + gdprApplies: true, + consentString: 'gdprConsent' + }, + uspConsent: '1YNN' +}; + const validBidRes = { ad: '
Hello
', publisherId: 12345, @@ -381,6 +408,31 @@ describe('Undertone Adapter', () => { expect(request.url).to.equal(REQ_URL); expect(request.method).to.equal('POST'); }); + it(`should have gppConsent fields`, function () { + const request = spec.buildRequests(bidReq, bidderReqGpp); + const domainStart = bidderReq.refererInfo.topmostLocation.indexOf('//'); + const domainEnd = bidderReq.refererInfo.topmostLocation.indexOf('/', domainStart + 2); + const domain = bidderReq.refererInfo.topmostLocation.substring(domainStart + 2, domainEnd); + const gppStr = bidderReqGpp.gppConsent.gppString; + const gppSid = bidderReqGpp.gppConsent.applicableSections; + const REQ_URL = `${URL}?pid=${bidReq[0].params.publisherId}&domain=${domain}&gpp=${gppStr}&gpp_sid=${gppSid}`; + expect(request.url).to.equal(REQ_URL); + expect(request.method).to.equal('POST'); + }); + it(`should have gpp, ccpa and gdpr fields`, function () { + const request = spec.buildRequests(bidReq, bidderReqFullGppCcpaGdpr); + const domainStart = bidderReq.refererInfo.topmostLocation.indexOf('//'); + const domainEnd = bidderReq.refererInfo.topmostLocation.indexOf('/', domainStart + 2); + const domain = bidderReq.refererInfo.topmostLocation.substring(domainStart + 2, domainEnd); + const gppStr = bidderReqFullGppCcpaGdpr.gppConsent.gppString; + const gppSid = bidderReqFullGppCcpaGdpr.gppConsent.applicableSections; + const ccpa = bidderReqFullGppCcpaGdpr.uspConsent; + const gdpr = bidderReqFullGppCcpaGdpr.gdprConsent.gdprApplies ? 1 : 0; + const gdprStr = bidderReqFullGppCcpaGdpr.gdprConsent.consentString; + const REQ_URL = `${URL}?pid=${bidReq[0].params.publisherId}&domain=${domain}&gdpr=${gdpr}&gdprstr=${gdprStr}&ccpa=${ccpa}&gpp=${gppStr}&gpp_sid=${gppSid}`; + expect(request.url).to.equal(REQ_URL); + expect(request.method).to.equal('POST'); + }); it('should have all relevant fields', function () { const request = spec.buildRequests(bidReq, bidderReq); const bid1 = JSON.parse(request.data)['x-ut-hb-params'][0]; @@ -409,10 +461,14 @@ describe('Undertone Adapter', () => { expect(bidVideo.video.playbackMethod).to.equal(2); expect(bidVideo.video.maxDuration).to.equal(30); expect(bidVideo.video.skippable).to.equal(true); + expect(bidVideo.video.placement).to.equal(1); + expect(bidVideo.video.plcmt).to.equal(1); expect(bidVideo2.video.skippable).to.equal(null); expect(bidVideo2.video.maxDuration).to.equal(null); expect(bidVideo2.video.playbackMethod).to.equal(null); + expect(bidVideo2.video.placement).to.equal(null); + expect(bidVideo2.video.plcmt).to.equal(null); }); it('should send all userIds data to server', function () { const request = spec.buildRequests(bidReqUserIds, bidderReq); diff --git a/test/spec/modules/userId_spec.js b/test/spec/modules/userId_spec.js index bf27ef0ff81..b1908bdf085 100644 --- a/test/spec/modules/userId_spec.js +++ b/test/spec/modules/userId_spec.js @@ -1,25 +1,25 @@ import { attachIdSystem, auctionDelay, - coreStorage, + coreStorage, dep, + findRootDomain, init, + PBJS_USER_ID_OPTOUT_NAME, requestBidsHook, + requestDataDeletion, setStoredConsentData, setStoredValue, setSubmoduleRegistry, syncDelay, - PBJS_USER_ID_OPTOUT_NAME, - findRootDomain, requestDataDeletion, } from 'modules/userId/index.js'; import {createEidsArray} from 'modules/userId/eids.js'; import {config} from 'src/config.js'; import * as utils from 'src/utils.js'; +import {getPrebidInternal} from 'src/utils.js'; import * as events from 'src/events.js'; import CONSTANTS from 'src/constants.json'; import {getGlobal} from 'src/prebidGlobal.js'; -import { - resetConsentData, -} from 'modules/consentManagement.js'; +import {resetConsentData, } from 'modules/consentManagement.js'; import {server} from 'test/mocks/xhr.js'; import {find} from 'src/polyfill.js'; import {unifiedIdSubmodule} from 'modules/unifiedIdSystem.js'; @@ -27,7 +27,10 @@ import {britepoolIdSubmodule} from 'modules/britepoolIdSystem.js'; import {id5IdSubmodule} from 'modules/id5IdSystem.js'; import {identityLinkSubmodule} from 'modules/identityLinkIdSystem.js'; import {dmdIdSubmodule} from 'modules/dmdIdSystem.js'; -import {liveIntentIdSubmodule} from 'modules/liveIntentIdSystem.js'; +import { + liveIntentIdSubmodule, + setEventFiredFlag as liveIntentIdSubmoduleDoNotFireEvent +} from 'modules/liveIntentIdSystem.js'; import {merkleIdSubmodule} from 'modules/merkleIdSystem.js'; import {netIdSubmodule} from 'modules/netIdSystem.js'; import {intentIqIdSubmodule} from 'modules/intentIqIdSystem.js'; @@ -39,8 +42,8 @@ import {criteoIdSubmodule} from 'modules/criteoIdSystem.js'; import {mwOpenLinkIdSubModule} from 'modules/mwOpenLinkIdSystem.js'; import {tapadIdSubmodule} from 'modules/tapadIdSystem.js'; import {tncidSubModule} from 'modules/tncIdSystem.js'; -import {getPrebidInternal} from 'src/utils.js'; import {uid2IdSubmodule} from 'modules/uid2IdSystem.js'; +import {euidIdSubmodule} from 'modules/euidIdSystem.js'; import {admixerIdSubmodule} from 'modules/admixerIdSystem.js'; import {deepintentDpesSubmodule} from 'modules/deepintentDpesIdSystem.js'; import {amxIdSubmodule} from '../../../modules/amxIdSystem.js'; @@ -53,6 +56,10 @@ import {hook} from '../../../src/hook.js'; import {mockGdprConsent} from '../../helpers/consentData.js'; import {getPPID} from '../../../src/adserver.js'; import {uninstall as uninstallGdprEnforcement} from 'modules/gdprEnforcement.js'; +import {GDPR_GVLIDS} from '../../../src/consentHandler.js'; +import {MODULE_TYPE_UID} from '../../../src/activities/modules.js'; +import {ACTIVITY_ENRICH_EIDS} from '../../../src/activities/activities.js'; +import {ACTIVITY_PARAM_COMPONENT_NAME, ACTIVITY_PARAM_COMPONENT_TYPE} from '../../../src/activities/params.js'; let assert = require('chai').assert; let expect = require('chai').expect; @@ -79,6 +86,19 @@ describe('User ID', function () { } } + function createMockIdSubmodule(name, value, aliasName) { + return { + name, + getId() { + return value; + }, + decode(v) { + return v; + }, + aliasName + } + } + function getAdUnitMock(code = 'adUnit-code') { return { code, @@ -102,12 +122,6 @@ describe('User ID', function () { return cfg; } - function findEid(eids, source) { - return find(eids, (eid) => { - if (eid.source === source) { return true; } - }); - } - let sandbox, consentData, startDelay, callbackDelay; function clearStack() { @@ -147,6 +161,7 @@ describe('User ID', function () { hook.ready(); uninstallGdprEnforcement(); localStorage.removeItem(PBJS_USER_ID_OPTOUT_NAME); + liveIntentIdSubmoduleDoNotFireEvent(); }); beforeEach(function () { @@ -165,14 +180,27 @@ describe('User ID', function () { sandbox.restore(); }); + describe('GVL IDs', () => { + beforeEach(() => { + sinon.stub(GDPR_GVLIDS, 'register'); + }); + afterEach(() => { + GDPR_GVLIDS.register.restore(); + }); + + it('are registered when ID submodule is registered', () => { + attachIdSystem({name: 'gvlidMock', gvlid: 123}); + sinon.assert.calledWith(GDPR_GVLIDS.register, MODULE_TYPE_UID, 'gvlidMock', 123); + }) + }) + describe('Decorate Ad Units', function () { beforeEach(function () { // reset mockGpt so nothing else interferes mockGpt.disable(); mockGpt.enable(); coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('pubcid_alt', 'altpubcid200000', (new Date(Date.now() + 5000).toUTCString())); - let origSK = coreStorage.setCookie.bind(coreStorage); + coreStorage.setCookie('pubcid_alt', 'altpubcid200000', (new Date(Date.now() + 20000).toUTCString())); sinon.spy(coreStorage, 'setCookie'); sinon.stub(utils, 'logWarn'); }); @@ -303,10 +331,6 @@ describe('User ID', function () { }); }); }); - // Because the consent cookie doesn't exist yet, we'll have 2 setCookie calls: - // 1) for the consent cookie - // 2) from the getId() call that results in a new call to store the results - expect(coreStorage.setCookie.callCount).to.equal(2); }); }); @@ -333,7 +357,6 @@ describe('User ID', function () { }); }); }); - expect(coreStorage.setCookie.callCount).to.equal(2); }); }); @@ -381,6 +404,139 @@ describe('User ID', function () { }) }); + it('pbjs.getUserIds(Async) should prioritize user ids according to config available to core', () => { + init(config); + + setSubmoduleRegistry([ + createMockIdSubmodule('mockId1Module', {id: {mockId1: 'mockId1_value'}}), + createMockIdSubmodule('mockId2Module', {id: {mockId2: 'mockId2_value', mockId3: 'mockId3_value_from_mockId2Module'}}), + createMockIdSubmodule('mockId3Module', {id: {mockId1: 'mockId1_value_from_mockId3Module', mockId2: 'mockId2_value_from_mockId3Module', mockId3: 'mockId3_value', mockId4: 'mockId4_value_from_mockId3Module'}}), + createMockIdSubmodule('mockId4Module', {id: {mockId4: 'mockId4_value'}}) + ]); + + config.setConfig({ + userSync: { + idPriority: { + mockId1: ['mockId3Module', 'mockId1Module'], + mockId4: ['mockId4Module', 'mockId3Module'] + }, + auctionDelay: 10, // with auctionDelay > 0, no auction is needed to complete init + userIds: [ + { name: 'mockId1Module' }, + { name: 'mockId2Module' }, + { name: 'mockId3Module' }, + { name: 'mockId4Module' } + ] + } + }); + + return getGlobal().getUserIdsAsync().then((uids) => { + expect(uids['mockId1']).to.deep.equal('mockId1_value_from_mockId3Module'); + expect(uids['mockId2']).to.deep.equal('mockId2_value'); + expect(uids['mockId3']).to.deep.equal('mockId3_value_from_mockId2Module'); + expect(uids['mockId4']).to.deep.equal('mockId4_value'); + }); + }); + + it('pbjs.getUserIds(Async) should prioritize user ids according to config available to core when config uses aliases', () => { + init(config); + + setSubmoduleRegistry([ + createMockIdSubmodule('mockId1Module', {id: {mockId1: 'mockId1_value'}}), + createMockIdSubmodule('mockId2Module', {id: {mockId2: 'mockId2_value', mockId3: 'mockId3_value_from_mockId2Module'}}, 'mockId2Module_alias'), + createMockIdSubmodule('mockId3Module', {id: {mockId1: 'mockId1_value_from_mockId3Module', mockId2: 'mockId2_value_from_mockId3Module', mockId3: 'mockId3_value', mockId4: 'mockId4_value_from_mockId3Module'}}, 'mockId3Module_alias'), + createMockIdSubmodule('mockId4Module', {id: {mockId4: 'mockId4_value'}}) + ]); + + config.setConfig({ + userSync: { + idPriority: { + mockId1: ['mockId3Module_alias', 'mockId1Module'], + mockId4: ['mockId4Module', 'mockId3Module'] + }, + auctionDelay: 10, // with auctionDelay > 0, no auction is needed to complete init + userIds: [ + { name: 'mockId1Module' }, + { name: 'mockId2Module' }, + { name: 'mockId3Module' }, + { name: 'mockId4Module' } + ] + } + }); + + return getGlobal().getUserIdsAsync().then((uids) => { + expect(uids['mockId1']).to.deep.equal('mockId1_value_from_mockId3Module'); + expect(uids['mockId2']).to.deep.equal('mockId2_value'); + expect(uids['mockId3']).to.deep.equal('mockId3_value_from_mockId2Module'); + expect(uids['mockId4']).to.deep.equal('mockId4_value'); + }); + }); + + it('pbjs.getUserIds(Async) should prioritize user ids according to config available to core when called multiple times', () => { + init(config); + + setSubmoduleRegistry([ + createMockIdSubmodule('mockId1Module', {id: {mockId1: 'mockId1_value', mockId2: 'mockId2_value_from_mockId1Module'}}), + createMockIdSubmodule('mockId2Module', {id: {mockId1: 'mockId1_value_from_mockId2Module', mockId2: 'mockId2_value'}}), + ]); + + config.setConfig({ + userSync: { + idPriority: { + mockId1: ['mockId2Module', 'mockId1Module'], + mockId2: ['mockId1Module', 'mockId2Module'] + }, + auctionDelay: 10, // with auctionDelay > 0, no auction is needed to complete init + userIds: [ + { name: 'mockId1Module' }, + { name: 'mockId2Module' } + ] + } + }); + + return getGlobal().getUserIdsAsync().then((uidsFirstRequest) => { + getGlobal().getUserIdsAsync().then((uidsSecondRequest) => { + expect(uidsFirstRequest['mockId1']).to.deep.equal('mockId1_value_from_mockId2Module'); + expect(uidsFirstRequest['mockId2']).to.deep.equal('mockId2_value_from_mockId1Module'); + expect(uidsFirstRequest).to.deep.equal(uidsSecondRequest); + }) + }); + }); + + it('pbjs.getUserIds(Async) with priority config but no collision', () => { + init(config); + + setSubmoduleRegistry([ + createMockIdSubmodule('mockId1Module', {id: {mockId1: 'mockId1_value'}}), + createMockIdSubmodule('mockId2Module', {id: {mockId2: 'mockId2_value'}}), + createMockIdSubmodule('mockId3Module', {id: undefined}), + createMockIdSubmodule('mockId4Module', {id: {mockId4: 'mockId4_value'}}) + ]); + + config.setConfig({ + userSync: { + idPriority: { + mockId1: ['mockId3Module', 'mockId1Module'], + mockId4: ['mockId2Module', 'mockId4Module'] + }, + auctionDelay: 10, // with auctionDelay > 0, no auction is needed to complete init + userIds: [ + { name: 'mockId1Module' }, + { name: 'mockId2Module' }, + { name: 'mockId3Module' }, + { name: 'mockId4Module' } + ] + } + }); + + return getGlobal().getUserIdsAsync().then((uids) => { + expect(uids['mockId1']).to.deep.equal('mockId1_value'); + expect(uids['mockId2']).to.deep.equal('mockId2_value'); + expect(uids['mockId3']).to.be.undefined; + expect(uids['mockId4']).to.deep.equal('mockId4_value'); + }); + }); + it('pbjs.getUserIdsAsEids', function (done) { init(config); setSubmoduleRegistry([sharedIdSystemSubmodule]); @@ -400,6 +556,44 @@ describe('User ID', function () { }); }); + it('pbjs.getUserIdsAsEids should prioritize user ids according to config available to core', () => { + init(config); + setSubmoduleRegistry([ + createMockIdSubmodule('mockId1Module', {id: {uid2: {id: 'uid2_value'}}}), + createMockIdSubmodule('mockId2Module', {id: {pubcid: 'pubcid_value', lipb: {lipbid: 'lipbid_value_from_mockId2Module'}}}), + createMockIdSubmodule('mockId3Module', {id: {uid2: {id: 'uid2_value_from_mockId3Module'}, pubcid: 'pubcid_value_from_mockId3Module', lipb: {lipbid: 'lipbid_value'}, merkleId: {id: 'merkleId_value_from_mockId3Module'}}}), + createMockIdSubmodule('mockId4Module', {id: {merkleId: {id: 'merkleId_value'}}}) + ]); + config.setConfig({ + userSync: { + idPriority: { + uid2: ['mockId3Module', 'mockId1Module'], + merkleId: ['mockId4Module', 'mockId3Module'] + }, + auctionDelay: 10, // with auctionDelay > 0, no auction is needed to complete init + userIds: [ + { name: 'mockId1Module' }, + { name: 'mockId2Module' }, + { name: 'mockId3Module' }, + { name: 'mockId4Module' } + ] + } + }); + + const ids = { + 'uid2': { id: 'uid2_value_from_mockId3Module' }, + 'pubcid': 'pubcid_value', + 'lipb': { lipbid: 'lipbid_value_from_mockId2Module' }, + 'merkleId': { id: 'merkleId_value' } + }; + + return getGlobal().getUserIdsAsync().then(() => { + const eids = getGlobal().getUserIdsAsEids(); + const expected = createEidsArray(ids); + expect(eids).to.deep.equal(expected); + }); + }); + it('should set googletag ppid correctly', function () { let adUnits = [getAdUnitMock()]; init(config); @@ -425,6 +619,93 @@ describe('User ID', function () { }); }); + it('should set googletag ppid correctly when prioritized according to config available to core', () => { + let adUnits = [getAdUnitMock()]; + init(config); + setSubmoduleRegistry([ + // some of the ids are padded to have length >= 32 characters + createMockIdSubmodule('mockId1Module', {id: {uid2: {id: 'uid2_value_7ac66c0f148de9519b8bd264312c4d64'}}}), + createMockIdSubmodule('mockId2Module', {id: {pubcid: 'pubcid_value_7ac66c0f148de9519b8bd264312c4d64', lipb: {lipbid: 'lipbid_value_from_mockId2Module_7ac66c0f148de9519b8bd264312c4d64'}}}), + createMockIdSubmodule('mockId3Module', {id: {uid2: {id: 'uid2_value_from_mockId3Module_7ac66c0f148de9519b8bd264312c4d64'}, pubcid: 'pubcid_value_from_mockId3Module_7ac66c0f148de9519b8bd264312c4d64', lipb: {lipbid: 'lipbid_value_7ac66c0f148de9519b8bd264312c4d64'}, merkleId: {id: 'merkleId_value_from_mockId3Module_7ac66c0f148de9519b8bd264312c4d64'}}}), + createMockIdSubmodule('mockId4Module', {id: {merkleId: {id: 'merkleId_value_7ac66c0f148de9519b8bd264312c4d64'}}}) + ]); + + // before ppid should not be set + expect(window.googletag._ppid).to.equal(undefined); + + config.setConfig({ + userSync: { + ppid: 'uidapi.com', + idPriority: { + uid2: ['mockId3Module', 'mockId1Module'], + merkleId: ['mockId4Module', 'mockId3Module'] + }, + userIds: [ + { name: 'mockId1Module' }, + { name: 'mockId2Module' }, + { name: 'mockId3Module' }, + { name: 'mockId4Module' } + ] + } + }); + + return expectImmediateBidHook(() => {}, {adUnits}).then(() => { + expect(window.googletag._ppid).to.equal('uid2valuefrommockId3Module7ac66c0f148de9519b8bd264312c4d64'); + }); + }); + + describe('submodule callback', () => { + const TEST_KEY = 'testKey'; + + function setVal(val) { + if (val) { + coreStorage.setDataInLocalStorage(TEST_KEY, val); + coreStorage.setDataInLocalStorage(TEST_KEY + '_exp', ''); + } else { + coreStorage.removeDataFromLocalStorage(TEST_KEY); + coreStorage.removeDataFromLocalStorage(TEST_KEY + '_exp'); + } + } + afterEach(() => { + setVal(null); + }) + + it('should be able to re-read ID changes', (done) => { + setVal(null); + init(config); + setSubmoduleRegistry([{ + name: 'mockId', + getId: function (_1, _2, storedId) { + expect(storedId).to.not.exist; + setVal('laterValue'); + return { + callback(_, readId) { + expect(readId()).to.eql('laterValue'); + done(); + } + } + }, + decode(d) { + return d + } + }]); + config.setConfig({ + userSync: { + auctionDelay: 10, + userIds: [ + { + name: 'mockId', + storage: { + type: 'html5', + name: TEST_KEY + } + } + ] + } + }); + }); + }); + it('should set PPID when the source needs to call out to the network', () => { let adUnits = [getAdUnitMock()]; init(config); @@ -521,6 +802,40 @@ describe('User ID', function () { }) }); + it('should make PPID available to core and respect priority', () => { + init(config); + setSubmoduleRegistry([ + // some of the ids are padded to have length >= 32 characters + createMockIdSubmodule('mockId1Module', {id: {uid2: {id: 'uid2_value_7ac66c0f148de9519b8bd264312c4d64'}}}), + createMockIdSubmodule('mockId2Module', {id: {pubcid: 'pubcid_value_7ac66c0f148de9519b8bd264312c4d64', lipb: {lipbid: 'lipbid_value_from_mockId2Module_7ac66c0f148de9519b8bd264312c4d64'}}}), + createMockIdSubmodule('mockId3Module', {id: {uid2: {id: 'uid2_value_from_mockId3Module_7ac66c0f148de9519b8bd264312c4d64'}, pubcid: 'pubcid_value_from_mockId3Module_7ac66c0f148de9519b8bd264312c4d64', lipb: {lipbid: 'lipbid_value_7ac66c0f148de9519b8bd264312c4d64'}, merkleId: {id: 'merkleId_value_from_mockId3Module_7ac66c0f148de9519b8bd264312c4d64'}}}), + createMockIdSubmodule('mockId4Module', {id: {merkleId: {id: 'merkleId_value_7ac66c0f148de9519b8bd264312c4d64'}}}) + ]); + + // before ppid should not be set + expect(window.googletag._ppid).to.equal(undefined); + + config.setConfig({ + userSync: { + ppid: 'uidapi.com', + idPriority: { + uid2: ['mockId3Module', 'mockId1Module'], + merkleId: ['mockId4Module', 'mockId3Module'] + }, + userIds: [ + { name: 'mockId1Module' }, + { name: 'mockId2Module' }, + { name: 'mockId3Module' }, + { name: 'mockId4Module' } + ] + } + }); + + return getGlobal().refreshUserIds().then(() => { + expect(getPPID()).to.eql('uid2valuefrommockId3Module7ac66c0f148de9519b8bd264312c4d64'); + }) + }); + describe('refreshing before init is complete', () => { const MOCK_ID = {'MOCKID': '1111'}; let mockIdCallback; @@ -695,6 +1010,63 @@ describe('User ID', function () { }); }); + it('pbjs.refreshUserIds updates priority config', () => { + init(config); + + setSubmoduleRegistry([ + createMockIdSubmodule('mockId1Module', {id: {mockId1: 'mockId1_value'}}), + createMockIdSubmodule('mockId2Module', {id: {mockId2: 'mockId2_value', mockId3: 'mockId3_value_from_mockId2Module'}}), + createMockIdSubmodule('mockId3Module', {id: {mockId1: 'mockId1_value_from_mockId3Module', mockId2: 'mockId2_value_from_mockId3Module', mockId3: 'mockId3_value', mockId4: 'mockId4_value_from_mockId3Module'}}), + createMockIdSubmodule('mockId4Module', {id: {mockId4: 'mockId4_value'}}) + ]); + + config.setConfig({ + userSync: { + idPriority: { + mockId1: ['mockId3Module', 'mockId1Module'], + mockId4: ['mockId4Module', 'mockId3Module'] + }, + auctionDelay: 10, // with auctionDelay > 0, no auction is needed to complete init + userIds: [ + { name: 'mockId1Module' }, + { name: 'mockId2Module' }, + { name: 'mockId3Module' }, + { name: 'mockId4Module' } + ] + } + }); + + return getGlobal().getUserIdsAsync().then((uids) => { + expect(uids['mockId1']).to.deep.equal('mockId1_value_from_mockId3Module'); + expect(uids['mockId2']).to.deep.equal('mockId2_value'); + expect(uids['mockId3']).to.deep.equal('mockId3_value_from_mockId2Module'); + expect(uids['mockId4']).to.deep.equal('mockId4_value'); + + config.setConfig({ + userSync: { + idPriority: { + mockId1: ['mockId1Module', 'mockId3Module'], + mockId4: ['mockId3Module', 'mockId4Module'] + }, + auctionDelay: 10, // with auctionDelay > 0, no auction is needed to complete init + userIds: [ + { name: 'mockId1Module' }, + { name: 'mockId2Module' }, + { name: 'mockId3Module' }, + { name: 'mockId4Module' } + ] + } + }); + + return getGlobal().getUserIdsAsync().then((uids) => { + expect(uids['mockId1']).to.deep.equal('mockId1_value'); + expect(uids['mockId2']).to.deep.equal('mockId2_value'); + expect(uids['mockId3']).to.deep.equal('mockId3_value_from_mockId2Module'); + expect(uids['mockId4']).to.deep.equal('mockId4_value_from_mockId3Module'); + }); + }); + }); + it('pbjs.refreshUserIds refreshes single', function() { coreStorage.setCookie('MOCKID', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('REFRESH', '', EXPIRED_COOKIE_DATE); @@ -801,7 +1173,7 @@ describe('User ID', function () { it('handles config with no usersync object', function () { init(config); - setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, amxIdSubmodule, kinessoIdSubmodule, adqueryIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, euidIdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, amxIdSubmodule, kinessoIdSubmodule, adqueryIdSubmodule]); config.setConfig({}); // usersync is undefined, and no logInfo message for 'User ID - usersync config updated' expect(typeof utils.logInfo.args[0]).to.equal('undefined'); @@ -809,14 +1181,14 @@ describe('User ID', function () { it('handles config with empty usersync object', function () { init(config); - setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, amxIdSubmodule, kinessoIdSubmodule, adqueryIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, euidIdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, amxIdSubmodule, kinessoIdSubmodule, adqueryIdSubmodule]); config.setConfig({userSync: {}}); expect(typeof utils.logInfo.args[0]).to.equal('undefined'); }); it('handles config with usersync and userIds that are empty objs', function () { init(config); - setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, amxIdSubmodule, kinessoIdSubmodule, adqueryIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, euidIdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, amxIdSubmodule, kinessoIdSubmodule, adqueryIdSubmodule]); config.setConfig({ userSync: { userIds: [{}] @@ -827,7 +1199,7 @@ describe('User ID', function () { it('handles config with usersync and userIds with empty names or that dont match a submodule.name', function () { init(config); - setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, amxIdSubmodule, kinessoIdSubmodule, adqueryIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, euidIdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, amxIdSubmodule, kinessoIdSubmodule, adqueryIdSubmodule]); config.setConfig({ userSync: { userIds: [{ @@ -844,7 +1216,7 @@ describe('User ID', function () { it('config with 1 configurations should create 1 submodules', function () { init(config); - setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, amxIdSubmodule, kinessoIdSubmodule, adqueryIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, euidIdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, amxIdSubmodule, kinessoIdSubmodule, adqueryIdSubmodule]); config.setConfig(getConfigMock(['unifiedId', 'unifiedid', 'cookie'])); expect(utils.logInfo.args[0][0]).to.exist.and.to.contain('User ID - usersync config updated for 1 submodules'); @@ -864,9 +1236,9 @@ describe('User ID', function () { expect(utils.logInfo.args[0][0]).to.exist.and.to.contain('User ID - usersync config updated for 1 submodules'); }); - it('config with 22 configurations should result in 22 submodules add', function () { + it('config with 23 configurations should result in 23 submodules add', function () { init(config); - setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, liveIntentIdSubmodule, britepoolIdSubmodule, netIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, hadronIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, amxIdSubmodule, kinessoIdSubmodule, adqueryIdSubmodule, tncidSubModule]); + setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, liveIntentIdSubmodule, britepoolIdSubmodule, netIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, hadronIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, euidIdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, amxIdSubmodule, kinessoIdSubmodule, adqueryIdSubmodule, tncidSubModule]); config.setConfig({ userSync: { syncDelay: 0, @@ -909,6 +1281,8 @@ describe('User ID', function () { storage: {name: 'tapad_id', type: 'cookie'} }, { name: 'uid2' + }, { + name: 'euid' }, { name: 'admixerId', storage: {name: 'admixerId', type: 'cookie'} @@ -932,12 +1306,12 @@ describe('User ID', function () { }] } }); - expect(utils.logInfo.args[0][0]).to.exist.and.to.contain('User ID - usersync config updated for 22 submodules'); + expect(utils.logInfo.args[0][0]).to.exist.and.to.contain('User ID - usersync config updated for 23 submodules'); }); it('config syncDelay updates module correctly', function () { init(config); - setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, hadronIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, amxIdSubmodule, kinessoIdSubmodule, adqueryIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, hadronIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, euidIdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, amxIdSubmodule, kinessoIdSubmodule, adqueryIdSubmodule]); config.setConfig({ userSync: { syncDelay: 99, @@ -952,7 +1326,7 @@ describe('User ID', function () { it('config auctionDelay updates module correctly', function () { init(config); - setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, hadronIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, amxIdSubmodule, kinessoIdSubmodule, adqueryIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, hadronIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, euidIdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, amxIdSubmodule, kinessoIdSubmodule, adqueryIdSubmodule]); config.setConfig({ userSync: { auctionDelay: 100, @@ -967,7 +1341,7 @@ describe('User ID', function () { it('config auctionDelay defaults to 0 if not a number', function () { init(config); - setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, hadronIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, amxIdSubmodule, kinessoIdSubmodule, adqueryIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, hadronIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, euidIdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, amxIdSubmodule, kinessoIdSubmodule, adqueryIdSubmodule]); config.setConfig({ userSync: { auctionDelay: '', @@ -2300,7 +2674,7 @@ describe('User ID', function () { localStorage.setItem('qid_exp', new Date(Date.now() + 5000).toUTCString()) init(config); - setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, britepoolIdSubmodule, netIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, hadronIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, amxIdSubmodule, kinessoIdSubmodule, adqueryIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, britepoolIdSubmodule, netIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, hadronIdSubmodule, uid2IdSubmodule, euidIdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, amxIdSubmodule, kinessoIdSubmodule, adqueryIdSubmodule]); config.setConfig({ userSync: { @@ -2434,9 +2808,57 @@ describe('User ID', function () { localStorage.removeItem('amxId'); localStorage.removeItem('amxId_exp'); coreStorage.setCookie('kpuid', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('__uid2_advertising_token', '', EXPIRED_COOKIE_DATE); done(); }, {adUnits}); }); + + describe('activity controls', () => { + let isAllowed; + const MOCK_IDS = ['mockId1', 'mockId2'] + beforeEach(() => { + isAllowed = sinon.stub(dep, 'isAllowed'); + init(config); + setSubmoduleRegistry([]); + const mods = MOCK_IDS.map((name) => ({ + name, + decode: function (value) { + return { + [name]: value + }; + }, + getId: function () { + return {id: `${name}Value`}; + } + })); + mods.forEach(attachIdSystem); + }); + afterEach(() => { + isAllowed.restore(); + }); + + it('should check for enrichEids activity permissions', (done) => { + isAllowed.callsFake((activity, params) => { + return !(activity === ACTIVITY_ENRICH_EIDS && + params[ACTIVITY_PARAM_COMPONENT_TYPE] === MODULE_TYPE_UID && + params[ACTIVITY_PARAM_COMPONENT_NAME] === MOCK_IDS[0]) + }) + + config.setConfig({ + userSync: { + syncDelay: 0, + userIds: MOCK_IDS.map(name => ({ + name, storage: {name, type: 'cookie'} + })) + } + }); + requestBidsHook((req) => { + const activeIds = req.adUnits.flatMap(au => au.bids).flatMap(bid => Object.keys(bid.userId)); + expect(Array.from(new Set(activeIds))).to.have.members([MOCK_IDS[1]]); + done(); + }, {adUnits}) + }); + }) }); describe('callbacks at the end of auction', function () { @@ -2522,15 +2944,21 @@ describe('User ID', function () { }); describe('Set cookie behavior', function () { - let coreStorageSpy; + let cookie, cookieStub; + beforeEach(function () { - coreStorageSpy = sinon.spy(coreStorage, 'setCookie'); setSubmoduleRegistry([sharedIdSystemSubmodule]); init(config); + cookie = document.cookie; + cookieStub = sinon.stub(document, 'cookie'); + cookieStub.get(() => cookie); + cookieStub.set((val) => cookie = val); }); + afterEach(function () { - coreStorageSpy.restore(); + cookieStub.restore(); }); + it('should allow submodules to override the domain', function () { const submodule = { submodule: { @@ -2539,26 +2967,34 @@ describe('User ID', function () { } }, config: { + name: 'mockId', storage: { type: 'cookie' } + }, + storageMgr: { + setCookie: sinon.stub() } } setStoredValue(submodule, 'bar'); - expect(coreStorage.setCookie.getCall(0).args[4]).to.equal('foo.com'); + expect(submodule.storageMgr.setCookie.getCall(0).args[4]).to.equal('foo.com'); }); - it('should pass null for domain if submodule does not override the domain', function () { + it('should pass no domain if submodule does not override the domain', function () { const submodule = { submodule: {}, config: { + name: 'mockId', storage: { type: 'cookie' } + }, + storageMgr: { + setCookie: sinon.stub() } } setStoredValue(submodule, 'bar'); - expect(coreStorage.setCookie.getCall(0).args[4]).to.equal(null); + expect(submodule.storageMgr.setCookie.getCall(0).args[4]).to.equal(null); }); }); @@ -2818,6 +3254,50 @@ describe('User ID', function () { }).catch(done); }); + it('pbjs.getEncryptedEidsForSource should return prioritized id as non-encrypted string', (done) => { + init(config); + setSubmoduleRegistry([ + createMockIdSubmodule('mockId1Module', {id: {uid2: {id: 'uid2_value'}}}), + createMockIdSubmodule('mockId2Module', {id: {pubcid: 'pubcid_value', lipb: {lipbid: 'lipbid_value_from_mockId2Module'}}}), + createMockIdSubmodule('mockId3Module', {id: {uid2: {id: 'uid2_value_from_mockId3Module'}, pubcid: 'pubcid_value_from_mockId3Module', lipb: {lipbid: 'lipbid_value'}, merkleId: {id: 'merkleId_value_from_mockId3Module'}}}), + createMockIdSubmodule('mockId4Module', {id: {merkleId: {id: 'merkleId_value'}}}) + ]); + config.setConfig({ + userSync: { + idPriority: { + uid2: ['mockId3Module', 'mockId1Module'], + merkleId: ['mockId4Module', 'mockId3Module'] + }, + auctionDelay: 10, // with auctionDelay > 0, no auction is needed to complete init + userIds: [ + { name: 'mockId1Module' }, + { name: 'mockId2Module' }, + { name: 'mockId3Module' }, + { name: 'mockId4Module' } + ] + } + }); + + const expctedIds = [ + 'pubcid_value', + 'uid2_value_from_mockId3Module', + 'merkleId_value', + 'lipbid_value_from_mockId2Module' + ]; + + const encrypt = false; + + Promise.all([ + getGlobal().getEncryptedEidsForSource('pubcid.org', encrypt), + getGlobal().getEncryptedEidsForSource('uidapi.com', encrypt), + getGlobal().getEncryptedEidsForSource('merkleinc.com', encrypt), + getGlobal().getEncryptedEidsForSource('liveintent.com', encrypt) + ]).then((result) => { + expect(result).to.deep.equal(expctedIds); + done(); + }) + }); + describe('pbjs.getEncryptedEidsForSource', () => { beforeEach(() => { init(config); @@ -2881,6 +3361,45 @@ describe('User ID', function () { done(); }); }); + + it('pbjs.getUserIdsAsEidBySource with priority config available to core', () => { + init(config); + setSubmoduleRegistry([ + createMockIdSubmodule('mockId1Module', {id: {uid2: {id: 'uid2_value'}}}), + createMockIdSubmodule('mockId2Module', {id: {pubcid: 'pubcid_value', lipb: {lipbid: 'lipbid_value_from_mockId2Module'}}}), + createMockIdSubmodule('mockId3Module', {id: {uid2: {id: 'uid2_value_from_mockId3Module'}, pubcid: 'pubcid_value_from_mockId3Module', lipb: {lipbid: 'lipbid_value'}, merkleId: {id: 'merkleId_value_from_mockId3Module'}}}), + createMockIdSubmodule('mockId4Module', {id: {merkleId: {id: 'merkleId_value'}}}) + ]); + config.setConfig({ + userSync: { + idPriority: { + uid2: ['mockId3Module', 'mockId1Module'], + merkleId: ['mockId4Module', 'mockId3Module'] + }, + auctionDelay: 10, // with auctionDelay > 0, no auction is needed to complete init + userIds: [ + { name: 'mockId1Module' }, + { name: 'mockId2Module' }, + { name: 'mockId3Module' }, + { name: 'mockId4Module' } + ] + } + }); + + const ids = { + 'uidapi.com': {'uid2': {id: 'uid2_value_from_mockId3Module'}}, + 'pubcid.org': {'pubcid': 'pubcid_value'}, + 'liveintent.com': {'lipb': {lipbid: 'lipbid_value_from_mockId2Module'}}, + 'merkleinc.com': {'merkleId': {id: 'merkleId_value'}} + }; + + return getGlobal().getUserIdsAsync().then(() => { + expect(getGlobal().getUserIdsAsEidBySource('pubcid.org')).to.deep.equal(createEidsArray(ids['pubcid.org'])[0]); + expect(getGlobal().getUserIdsAsEidBySource('uidapi.com')).to.deep.equal(createEidsArray(ids['uidapi.com'])[0]); + expect(getGlobal().getUserIdsAsEidBySource('merkleinc.com')).to.deep.equal(createEidsArray(ids['merkleinc.com'])[0]); + expect(getGlobal().getUserIdsAsEidBySource('liveintent.com')).to.deep.equal(createEidsArray(ids['liveintent.com'])[0]); + }); + }); }) }); }); diff --git a/test/spec/modules/utiqSystem_spec.js b/test/spec/modules/utiqSystem_spec.js new file mode 100644 index 00000000000..afeeea7c3ea --- /dev/null +++ b/test/spec/modules/utiqSystem_spec.js @@ -0,0 +1,188 @@ +import { expect } from 'chai'; +import { utiqSubmodule } from 'modules/utiqSystem.js'; +import { storage } from 'modules/utiqSystem.js'; + +describe('utiqSystem', () => { + const utiqPassKey = 'utiqPass'; + + const getStorageData = (idGraph) => { + if (!idGraph) { + idGraph = {id: 501, domain: ''}; + } + return { + 'connectId': { + 'idGraph': [idGraph], + } + } + }; + + it('should have the correct module name declared', () => { + expect(utiqSubmodule.name).to.equal('utiq'); + }); + + describe('utiq getId()', () => { + afterEach(() => { + storage.removeDataFromLocalStorage(utiqPassKey); + }); + + it('it should return object with key callback', () => { + expect(utiqSubmodule.getId()).to.have.property('callback'); + }); + + it('should return object with key callback with value type - function', () => { + storage.setDataInLocalStorage(utiqPassKey, JSON.stringify(getStorageData())); + expect(utiqSubmodule.getId()).to.have.property('callback'); + expect(typeof utiqSubmodule.getId().callback).to.be.equal('function'); + }); + + it('tests if localstorage & JSON works properly ', () => { + const idGraph = { + 'domain': 'domainValue', + 'atid': 'atidValue', + }; + storage.setDataInLocalStorage(utiqPassKey, JSON.stringify(getStorageData(idGraph))); + expect(JSON.parse(storage.getDataFromLocalStorage(utiqPassKey))).to.have.property('connectId'); + }); + + it('returns {id: {utiq: data.utiq}} if we have the right data stored in the localstorage ', () => { + const idGraph = { + 'domain': 'test.domain', + 'atid': 'atidValue', + }; + storage.setDataInLocalStorage(utiqPassKey, JSON.stringify(getStorageData(idGraph))); + const response = utiqSubmodule.getId(); + expect(response).to.have.property('id'); + expect(response.id).to.have.property('utiq'); + expect(response.id.utiq).to.be.equal('atidValue'); + }); + + it('returns {utiq: data.utiq} if we have the right data stored in the localstorage right after the callback is called', (done) => { + const idGraph = { + 'domain': 'test.domain', + 'atid': 'atidValue', + }; + const response = utiqSubmodule.getId(); + expect(response).to.have.property('callback'); + expect(response.callback.toString()).contain('result(callback)'); + + if (typeof response.callback === 'function') { + storage.setDataInLocalStorage(utiqPassKey, JSON.stringify(getStorageData(idGraph))); + response.callback(function (result) { + expect(result).to.not.be.null; + expect(result).to.have.property('utiq'); + expect(result.utiq).to.be.equal('atidValue'); + done() + }) + } + }); + + it('returns {utiq: data.utiq} if we have the right data stored in the localstorage right after 500ms delay', (done) => { + const idGraph = { + 'domain': 'test.domain', + 'atid': 'atidValue', + }; + + const response = utiqSubmodule.getId(); + expect(response).to.have.property('callback'); + expect(response.callback.toString()).contain('result(callback)'); + + if (typeof response.callback === 'function') { + setTimeout(() => { + storage.setDataInLocalStorage(utiqPassKey, JSON.stringify(getStorageData(idGraph))); + }, 500); + response.callback(function (result) { + expect(result).to.not.be.null; + expect(result).to.have.property('utiq'); + expect(result.utiq).to.be.equal('atidValue'); + done() + }) + } + }); + + it('returns null if we have the data stored in the localstorage after 500ms delay and the max (waiting) delay is only 200ms ', (done) => { + const idGraph = { + 'domain': 'test.domain', + 'atid': 'atidValue', + }; + + const response = utiqSubmodule.getId({params: {maxDelayTime: 200}}); + expect(response).to.have.property('callback'); + expect(response.callback.toString()).contain('result(callback)'); + + if (typeof response.callback === 'function') { + setTimeout(() => { + storage.setDataInLocalStorage(utiqPassKey, JSON.stringify(getStorageData(idGraph))); + }, 500); + response.callback(function (result) { + expect(result).to.be.null; + done() + }) + } + }); + }); + + describe('utiq decode()', () => { + const VALID_API_RESPONSES = [ + { + expected: '32a97f612', + payload: { + utiq: '32a97f612' + } + }, + { + expected: '32a97f61', + payload: { + utiq: '32a97f61', + } + }, + ]; + VALID_API_RESPONSES.forEach(responseData => { + it('should return a newly constructed object with the utiq for a payload with {utiq: value}', () => { + expect(utiqSubmodule.decode(responseData.payload)).to.deep.equal( + {utiq: responseData.expected} + ); + }); + }); + + [{}, '', {foo: 'bar'}].forEach((response) => { + it(`should return null for an invalid response "${JSON.stringify(response)}"`, () => { + expect(utiqSubmodule.decode(response)).to.be.null; + }); + }); + }); + + describe('utiq messageHandler', () => { + afterEach(() => { + storage.removeDataFromLocalStorage(utiqPassKey); + }); + + const domains = [ + 'domain1', + 'domain2', + 'domain3', + ]; + + domains.forEach(domain => { + it(`correctly sets utiq value for domain name ${domain}`, (done) => { + const idGraph = { + 'domain': domain, + 'atid': 'atidValue', + }; + + storage.setDataInLocalStorage(utiqPassKey, JSON.stringify(getStorageData(idGraph))); + + const eventData = { + data: `{\"msgType\":\"MNOSELECTOR\",\"body\":{\"url\":\"https://${domain}/some/path\"}}` + }; + + window.dispatchEvent(new MessageEvent('message', eventData)); + + const response = utiqSubmodule.getId(); + expect(response).to.have.property('id'); + expect(response.id).to.have.property('utiq'); + expect(response.id.utiq).to.be.equal('atidValue'); + done(); + }); + }); + }); +}); diff --git a/test/spec/modules/vidazooBidAdapter_spec.js b/test/spec/modules/vidazooBidAdapter_spec.js index 24d565805d3..157bbd0fd94 100644 --- a/test/spec/modules/vidazooBidAdapter_spec.js +++ b/test/spec/modules/vidazooBidAdapter_spec.js @@ -1,7 +1,6 @@ import {expect} from 'chai'; import { spec as adapter, - SUPPORTED_ID_SYSTEMS, createDomain, hashCode, extractPID, @@ -21,6 +20,8 @@ import {useFakeTimers} from 'sinon'; import {BANNER, VIDEO} from '../../../src/mediaTypes'; import {config} from '../../../src/config'; +export const TEST_ID_SYSTEMS = ['britepoolid', 'criteoId', 'id5id', 'idl_env', 'lipb', 'netId', 'parrableId', 'pubcid', 'tdid', 'pubProvidedId']; + const SUB_DOMAIN = 'openrtb'; const BID = { @@ -37,7 +38,6 @@ const BID = { } }, 'placementCode': 'div-gpt-ad-1460505748561-0', - 'transactionId': 'c881914b-a3b5-4ecf-ad9c-1c2f37c6aabf', 'sizes': [[300, 250], [300, 600]], 'bidderRequestId': '1fdb5ff1b6eaa7', 'auctionId': 'auction_id', @@ -49,6 +49,7 @@ const BID = { 'mediaTypes': [BANNER], 'ortb2Imp': { 'ext': { + tid: 'c881914b-a3b5-4ecf-ad9c-1c2f37c6aabf', 'gpid': '1234567890' } } @@ -58,7 +59,11 @@ const VIDEO_BID = { 'bidId': '2d52001cabd527', 'adUnitCode': '63550ad1ff6642d368cba59dh5884270560', 'bidderRequestId': '12a8ae9ada9c13', - 'transactionId': '56e184c6-bde9-497b-b9b9-cf47a61381ee', + ortb2Imp: { + ext: { + tid: '56e184c6-bde9-497b-b9b9-cf47a61381ee', + } + }, 'auctionId': 'auction_id', 'bidRequestsCount': 4, 'bidderRequestsCount': 3, @@ -110,6 +115,24 @@ const BIDDER_REQUEST = { 'regs': { 'gpp': 'gpp_string', 'gpp_sid': [7] + }, + 'device': { + 'sua': { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, + {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, + {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + } } }, }; @@ -119,6 +142,7 @@ const SERVER_RESPONSE = { cid: 'testcid123', results: [{ 'ad': '', + 'bidId': '2d52001cabd527-response', 'price': 0.8, 'creativeId': '12610997325162499419', 'exp': 30, @@ -281,6 +305,22 @@ describe('VidazooBidAdapter', function () { schain: VIDEO_BID.schain, sessionId: '', sizes: ['545x307'], + sua: { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, + {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, + {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + }, uniqueDealId: `${hashUrl}_${Date.now().toString()}`, uqs: getTopWindowQueryParams(), isStorageAllowed: true, @@ -330,6 +370,22 @@ describe('VidazooBidAdapter', function () { transactionId: 'c881914b-a3b5-4ecf-ad9c-1c2f37c6aabf', bidderRequestId: '1fdb5ff1b6eaa7', sizes: ['300x250', '300x600'], + sua: { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, + {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, + {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + }, url: 'https%3A%2F%2Fwww.greatsite.com', referrer: 'https://www.somereferrer.com', cb: 1000, @@ -358,8 +414,123 @@ describe('VidazooBidAdapter', function () { }); }); + it('should build single banner request for multiple bids', function () { + config.setConfig({ + bidderTimeout: 3000, + vidazoo: { + singleRequest: true, + chunkSize: 2 + } + }); + + const hashUrl = hashCode(BIDDER_REQUEST.refererInfo.page); + + const BID2 = utils.deepClone(BID); + BID2.bidId = '2d52001cabd528'; + BID2.adUnitCode = 'div-gpt-ad-12345-1'; + BID2.sizes = [[300, 250]]; + + const REQUEST_DATA = { + gdprConsent: 'consent_string', + gdpr: 1, + usPrivacy: 'consent_string', + gppString: 'gpp_string', + gppSid: [7], + auctionId: 'auction_id', + bidRequestsCount: 4, + bidderRequestsCount: 3, + bidderWinsCount: 1, + bidderTimeout: 3000, + transactionId: 'c881914b-a3b5-4ecf-ad9c-1c2f37c6aabf', + bidderRequestId: '1fdb5ff1b6eaa7', + sizes: ['300x250', '300x600'], + sua: { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, + {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, + {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + }, + url: 'https%3A%2F%2Fwww.greatsite.com', + referrer: 'https://www.somereferrer.com', + cb: 1000, + bidFloor: 0.1, + bidId: '2d52001cabd527', + adUnitCode: 'div-gpt-ad-12345-0', + publisherId: '59ac17c192832d0011283fe3', + dealId: 3, + sessionId: '', + uniqueDealId: `${hashUrl}_${Date.now().toString()}`, + bidderVersion: adapter.version, + prebidVersion: version, + schain: BID.schain, + ptrace: '1000', + res: `${window.top.screen.width}x${window.top.screen.height}`, + mediaTypes: [BANNER], + uqs: getTopWindowQueryParams(), + 'ext.param1': 'loremipsum', + 'ext.param2': 'dolorsitamet', + isStorageAllowed: true, + gpid: '1234567890', + cat: ['IAB2'], + pagecat: ['IAB2-2'], + webSessionId: webSessionId + }; + + const REQUEST_DATA2 = utils.deepClone(REQUEST_DATA); + REQUEST_DATA2.bidId = '2d52001cabd528'; + REQUEST_DATA2.adUnitCode = 'div-gpt-ad-12345-1'; + REQUEST_DATA2.sizes = ['300x250']; + REQUEST_DATA2.dealId = 4; + + const requests = adapter.buildRequests([BID, BID2], BIDDER_REQUEST); + expect(requests).to.have.length(1); + + expect(requests[0]).to.deep.equal({ + method: 'POST', + url: `${createDomain(SUB_DOMAIN)}/prebid/multi/59db6b3b4ffaa70004f45cdc`, + data: {bids: [REQUEST_DATA, REQUEST_DATA2]} + }); + }); + + it('should return seperated requests for video and banner if singleRequest is true', function () { + config.setConfig({ + bidderTimeout: 3000, + vidazoo: { + singleRequest: true, + chunkSize: 2 + } + }); + + const requests = adapter.buildRequests([BID, VIDEO_BID], BIDDER_REQUEST); + expect(requests).to.have.length(2); + }); + + it('should chunk requests if requests exceed chunkSize and singleRequest is true', function () { + config.setConfig({ + bidderTimeout: 3000, + vidazoo: { + singleRequest: true, + chunkSize: 2 + } + }); + + const requests = adapter.buildRequests([BID, BID, BID, BID], BIDDER_REQUEST); + expect(requests).to.have.length(2); + }); + after(function () { $$PREBID_GLOBAL$$.bidderSettings = {}; + config.resetConfig(); sandbox.restore(); }); }); @@ -427,6 +598,19 @@ describe('VidazooBidAdapter', function () { }); }); + it('should get meta from response metaData', function () { + const serverResponse = utils.deepClone(SERVER_RESPONSE); + serverResponse.body.results[0].metaData = { + advertiserDomains: ['vidazoo.com'], + agencyName: 'Agency Name', + }; + const responses = adapter.interpretResponse(serverResponse, REQUEST); + expect(responses[0].meta).to.deep.equal({ + advertiserDomains: ['vidazoo.com'], + agencyName: 'Agency Name' + }); + }); + it('should return an array of interpreted video responses', function () { const responses = adapter.interpretResponse(VIDEO_SERVER_RESPONSE, REQUEST); expect(responses).to.have.length(1); @@ -447,6 +631,21 @@ describe('VidazooBidAdapter', function () { }); }); + it('should populate requestId from response in case of singleRequest true', function () { + config.setConfig({ + vidazoo: { + singleRequest: true, + chunkSize: 2 + } + }); + + const responses = adapter.interpretResponse(SERVER_RESPONSE, REQUEST); + expect(responses).to.have.length(1); + expect(responses[0].requestId).to.equal('2d52001cabd527-response'); + + config.resetConfig(); + }); + it('should take default TTL', function () { const serverResponse = utils.deepClone(SERVER_RESPONSE); delete serverResponse.body.results[0].exp; @@ -457,7 +656,7 @@ describe('VidazooBidAdapter', function () { }); describe('user id system', function () { - Object.keys(SUPPORTED_ID_SYSTEMS).forEach((idSystemProvider) => { + TEST_ID_SYSTEMS.forEach((idSystemProvider) => { const id = Date.now().toString(); const bid = utils.deepClone(BID); diff --git a/test/spec/modules/videoModule/pbVideo_spec.js b/test/spec/modules/videoModule/pbVideo_spec.js index 41780a007dd..2e26737da40 100644 --- a/test/spec/modules/videoModule/pbVideo_spec.js +++ b/test/spec/modules/videoModule/pbVideo_spec.js @@ -174,6 +174,37 @@ describe('Prebid Video', function () { expect(nextFn.calledOnce).to.be.true; expect(nextFn.getCall(0).args[0].ortb2).to.be.deep.equal({ site: { content: { test: 'contentTestValue' } } }); }); + + it('allows publishers to override video param', function () { + const getOrtbVideoSpy = videoCoreMock.getOrtbVideo = sinon.spy(() => ({ + test: 'videoTestValue', + test2: 'videoModuleValue' + })); + + let beforeBidRequestCallback; + const requestBids = { + before: callback_ => beforeBidRequestCallback = callback_ + }; + + pbVideoFactory(null, null, Object.assign({}, pbGlobalMock, { requestBids })); + expect(beforeBidRequestCallback).to.not.be.undefined; + const nextFn = sinon.spy(); + const adUnits = [{ + code: 'ad1', + mediaTypes: { + video: { + test2: 'publisherValue' + } + }, + video: { divId: 'divId' } + }]; + beforeBidRequestCallback(nextFn, { adUnits }); + expect(getOrtbVideoSpy.calledOnce).to.be.true; + const adUnit = adUnits[0]; + expect(adUnit.mediaTypes.video).to.have.property('test', 'videoTestValue'); + expect(adUnit.mediaTypes.video).to.have.property('test2', 'publisherValue'); + expect(nextFn.calledOnce).to.be.true; + }); }); describe('Ad tag injection', function () { diff --git a/test/spec/modules/videoModule/shared/helpers_spec.js b/test/spec/modules/videoModule/shared/helpers_spec.js new file mode 100644 index 00000000000..e59988172e6 --- /dev/null +++ b/test/spec/modules/videoModule/shared/helpers_spec.js @@ -0,0 +1,22 @@ +import { getExternalVideoEventName, getExternalVideoEventPayload } from 'libraries/video/shared/helpers.js'; +import { expect } from 'chai'; + +describe('Helpers', function () { + describe('getExternalVideoEventName', function () { + it('should append video prefix and stay camelcase', function () { + expect(getExternalVideoEventName('eventName')).to.equal('videoEventName'); + expect(getExternalVideoEventName(null)).to.equal(''); + }); + }); + + describe('getExternalVideoEventPayload', function () { + it('should include type in payload when absent', function () { + const testType = 'testType'; + const payloadWithType = { datum: 'datum', type: 'existingType' }; + expect(getExternalVideoEventPayload(testType, payloadWithType).type).to.equal('existingType'); + + const payloadWithoutType = { datum: 'datum' }; + expect(getExternalVideoEventPayload(testType, payloadWithoutType).type).to.equal(testType); + }); + }); +}); diff --git a/test/spec/modules/videoModule/submodules/jwplayerVideoProvider_spec.js b/test/spec/modules/videoModule/submodules/jwplayerVideoProvider_spec.js index e08353f5b8d..3cede6c8eda 100644 --- a/test/spec/modules/videoModule/submodules/jwplayerVideoProvider_spec.js +++ b/test/spec/modules/videoModule/submodules/jwplayerVideoProvider_spec.js @@ -19,7 +19,7 @@ import { PLAYBACK_MODE } from 'libraries/video/constants/constants.js'; function getPlayerMock() { return makePlayerFactoryMock({ getState: function () {}, - setup: function () {}, + setup: function () { return this; }, getViewable: function () {}, getPercentViewable: function () {}, getMute: function () {}, @@ -30,8 +30,8 @@ function getPlayerMock() { getFullscreen: function () {}, getPlaylistItem: function () {}, playAd: function () {}, - on: function () {}, - off: function () {}, + on: function () { return this; }, + off: function () { return this; }, remove: function () {}, getAudioTracks: function () {}, getCurrentAudioTrack: function () {}, @@ -142,7 +142,7 @@ describe('JWPlayerProvider', function () { it('should instantiate the player when uninstantiated', function () { const player = getPlayerMock(); config.playerConfig = {}; - const setupSpy = player.setup = sinon.spy(); + const setupSpy = player.setup = sinon.spy(player.setup); const provider = JWPlayerProvider(config, makePlayerFactoryMock(player), adState, timeState, callbackStorage, utilsMock, sharedUtils); provider.init(); expect(setupSpy.calledOnce).to.be.true; @@ -158,6 +158,19 @@ describe('JWPlayerProvider', function () { expect(setupComplete.calledOnce).to.be.true; }); + it('should support multiple setup complete event handlers', function () { + const player = getPlayerMock(); + player.getState = () => 'idle'; + const provider = JWPlayerProvider(config, makePlayerFactoryMock(player), adState, timeState, callbackStorage, utilsMock, sharedUtils); + const setupComplete = sinon.spy(); + const setupComplete2 = sinon.spy(); + provider.onEvent(SETUP_COMPLETE, setupComplete, {}); + provider.onEvent(SETUP_COMPLETE, setupComplete2, {}); + provider.init(); + expect(setupComplete.calledOnce).to.be.true; + expect(setupComplete2.calledOnce).to.be.true; + }); + it('should not reinstantiate player', function () { const player = getPlayerMock(); player.getState = () => 'idle'; diff --git a/test/spec/modules/videobyteBidAdapter_spec.js b/test/spec/modules/videobyteBidAdapter_spec.js index f7ea0698956..7844e2bd1be 100644 --- a/test/spec/modules/videobyteBidAdapter_spec.js +++ b/test/spec/modules/videobyteBidAdapter_spec.js @@ -463,7 +463,6 @@ describe('VideoByteBidAdapter', function () { }); let o = { requestId: serverResponse.id, - bidderCode: spec.code, cpm: serverResponse.seatbid[0].bid[0].price, creativeId: serverResponse.seatbid[0].bid[0].crid, vastXml: serverResponse.seatbid[0].bid[0].adm, diff --git a/test/spec/modules/vidoomyBidAdapter_spec.js b/test/spec/modules/vidoomyBidAdapter_spec.js index 61b7f2fad7d..38fa872e6b8 100644 --- a/test/spec/modules/vidoomyBidAdapter_spec.js +++ b/test/spec/modules/vidoomyBidAdapter_spec.js @@ -74,6 +74,33 @@ describe('vidoomyBidAdapter', function() { 'sizes': [[300, 250], [200, 100]] } }, + 'schain': { + ver: '1.0', + complete: 1, + nodes: [ + { + 'asi': 'exchange1.com', + 'sid': '1234!abcd', + 'hp': 1, + 'rid': 'bid-request-1', + 'name': 'publisher, Inc.', + 'domain': 'publisher.com' + }, + { + 'asi': 'exchange2.com', + 'sid': 'abcd', + 'hp': 1 + }, + { + 'asi': 'exchange2.com', + 'sid': 'abcd', + 'hp': 1, + 'rid': 'bid-request-2', + 'name': 'intermediary', + 'domain': 'intermediary.com' + } + ] + } }, { 'bidder': 'vidoomy', @@ -128,6 +155,119 @@ describe('vidoomyBidAdapter', function() { expect('' + request[1].data.id).to.equal('456456'); expect('' + request[1].data.pid).to.equal('456456'); }); + + it('should send schain parameter in serialized form', function () { + const serializedForm = '1.0,1!exchange1.com,1234%21abcd,1,bid-request-1,publisher%2C%20Inc.,publisher.com!exchange2.com,abcd,1,,,!exchange2.com,abcd,1,bid-request-2,intermediary,intermediary.com' + expect(request[0].data).to.include.any.keys('schain'); + expect(request[0].data.schain).to.eq(serializedForm); + }); + + it('should return standard json formated eids', function () { + const eids = [{ + source: 'pubcid.org', + uids: [ + { + id: 'some-random-id-value-1', + atype: 1 + } + ] + }, + { + source: 'adserver.org', + uids: [{ + id: 'some-random-id-value-2', + atype: 1 + }] + }] + bidRequests[0].userIdAsEids = eids + const bidRequest = spec.buildRequests(bidRequests, bidderRequest); + expect(bidRequest[0].data).to.include.any.keys('eids'); + expect(JSON.parse(bidRequest[0].data.eids)).to.eql(eids); + }); + + it('should set the bidfloor if getFloor module is undefined but static bidfloor is present', function () { + const request = { ...bidRequests[0], params: { bidfloor: 2.5 } } + const req = spec.buildRequests([request], bidderRequest)[0]; + expect(req.data).to.include.any.keys('bidfloor'); + expect(req.data.bidfloor).to.equal(2.5); + }); + + describe('floorModule', function () { + const getFloordata = { + 'currency': 'USD', + 'floor': 1.60 + }; + bidRequests[0].getFloor = _ => { + return getFloordata; + }; + it('should return getFloor.floor if present', function () { + const request = spec.buildRequests(bidRequests, bidderRequest)[0]; + expect(request.data.bidfloor).to.equal(getFloordata.floor); + }); + it('should return the getFloor.floor if it is greater than static bidfloor', function () { + const bidfloor = 1.40; + const request = { ...bidRequests[0] }; + request.params.bidfloor = bidfloor; + const bidRequest = spec.buildRequests([request], bidderRequest)[0]; + expect(bidRequest.data.bidfloor).to.equal(getFloordata.floor); + }); + it('should return the static bidfloor if it is greater than getFloor.floor', function () { + const bidfloor = 1.90; + const request = { ...bidRequests[0] }; + request.params.bidfloor = bidfloor; + const bidRequest = spec.buildRequests([request], bidderRequest)[0]; + expect(bidRequest.data.bidfloor).to.equal(bidfloor); + }); + }); + + describe('badv, bcat, bapp, btype, battr', function () { + const bidderRequestNew = { + ...bidderRequest, + bcat: ['EX1', 'EX2', 'EX3'], + badv: ['site.com'], + bapp: ['app.com'], + btype: [1, 2, 3], + battr: [1, 2, 3] + } + const request = spec.buildRequests(bidRequests, bidderRequestNew); + it('should have badv, bcat, bapp, btype, battr in request', function () { + expect(request[0].data).to.include.any.keys('badv'); + expect(request[0].data).to.include.any.keys('bcat'); + expect(request[0].data).to.include.any.keys('bapp'); + expect(request[0].data).to.include.any.keys('btype'); + expect(request[0].data).to.include.any.keys('battr'); + }) + + it('should have equal badv, bcat, bapp, btype, battr in request', function () { + expect(request[0].badv).to.deep.equal(bidderRequest.refererInfo.badv); + expect(request[0].bcat).to.deep.equal(bidderRequest.refererInfo.bcat); + expect(request[0].bapp).to.deep.equal(bidderRequest.refererInfo.bapp); + expect(request[0].btype).to.deep.equal(bidderRequest.refererInfo.btype); + expect(request[0].battr).to.deep.equal(bidderRequest.refererInfo.battr); + }) + }) + + describe('first party data', function () { + const bidderRequest2 = { + ...bidderRequest, + ortb2: { + bcat: ['EX1', 'EX2', 'EX3'], + badv: ['site.com'], + bapp: ['app.com'], + btype: [1, 2, 3], + battr: [1, 2, 3] + } + } + const request = spec.buildRequests(bidRequests, bidderRequest2); + + it('should have badv, bcat, bapp, btype, battr in request and equal to bidderRequest.ortb2', function () { + expect(request[0].data.bcat).to.deep.equal(bidderRequest2.ortb2.bcat) + expect(request[0].data.badv).to.deep.equal(bidderRequest2.ortb2.badv) + expect(request[0].data.bapp).to.deep.equal(bidderRequest2.ortb2.bapp); + expect(request[0].data.btype).to.deep.equal(bidderRequest2.ortb2.btype); + expect(request[0].data.battr).to.deep.equal(bidderRequest2.ortb2.battr); + }); + }); }); describe('interpretResponse', function () { diff --git a/test/spec/modules/visiblemeasuresBidAdapter_spec.js b/test/spec/modules/visiblemeasuresBidAdapter_spec.js new file mode 100644 index 00000000000..ad75e17699f --- /dev/null +++ b/test/spec/modules/visiblemeasuresBidAdapter_spec.js @@ -0,0 +1,401 @@ +import { expect } from 'chai'; +import { spec } from '../../../modules/visiblemeasuresBidAdapter.js'; +import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; +import { getUniqueIdentifierStr } from '../../../src/utils.js'; + +const bidder = 'visiblemeasures' +const adUrl = 'https://us-e.visiblemeasures.com/pbjs'; +const syncUrl = 'https://cs.visiblemeasures.com'; + +describe('VisibleMeasuresBidAdapter', function () { + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + placementId: 'testBanner', + } + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [VIDEO]: { + playerSize: [[300, 300]], + minduration: 5, + maxduration: 60 + } + }, + params: { + placementId: 'testVideo', + } + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [NATIVE]: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + } + }, + params: { + placementId: 'testNative', + } + } + ]; + + const invalidBid = { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + + } + } + + const bidderRequest = { + uspConsent: '1---', + gdprConsent: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + refererInfo: { + referer: 'https://test.com' + }, + timeout: 500 + }; + + describe('isBidRequestValid', function () { + it('Should return true if there are bidId, params and key parameters present', function () { + expect(spec.isBidRequestValid(bids[0])).to.be.true; + }); + it('Should return false if at least one of parameters is not present', function () { + expect(spec.isBidRequestValid(invalidBid)).to.be.false; + }); + }); + + describe('buildRequests', function () { + let serverRequest = spec.buildRequests(bids, bidderRequest); + + it('Creates a ServerRequest object with method, URL and data', function () { + expect(serverRequest).to.exist; + expect(serverRequest.method).to.exist; + expect(serverRequest.url).to.exist; + expect(serverRequest.data).to.exist; + }); + + it('Returns POST method', function () { + expect(serverRequest.method).to.equal('POST'); + }); + + it('Returns valid URL', function () { + expect(serverRequest.url).to.equal(adUrl); + }); + + it('Returns general data valid', function () { + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.all.keys('deviceWidth', + 'deviceHeight', + 'language', + 'secure', + 'host', + 'page', + 'placements', + 'coppa', + 'ccpa', + 'gdpr', + 'tmax' + ); + expect(data.deviceWidth).to.be.a('number'); + expect(data.deviceHeight).to.be.a('number'); + expect(data.language).to.be.a('string'); + expect(data.secure).to.be.within(0, 1); + expect(data.host).to.be.a('string'); + expect(data.page).to.be.a('string'); + expect(data.coppa).to.be.a('number'); + expect(data.gdpr).to.be.a('string'); + expect(data.ccpa).to.be.a('string'); + expect(data.tmax).to.be.a('number'); + expect(data.placements).to.have.lengthOf(3); + }); + + it('Returns valid placements', function () { + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.placementId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('publisher'); + + if (placement.adFormat === BANNER) { + expect(placement.sizes).to.be.an('array'); + } + switch (placement.adFormat) { + case BANNER: + expect(placement.sizes).to.be.an('array'); + break; + case VIDEO: + expect(placement.playerSize).to.be.an('array'); + expect(placement.minduration).to.be.an('number'); + expect(placement.maxduration).to.be.an('number'); + break; + case NATIVE: + expect(placement.native).to.be.an('object'); + break; + } + } + }); + + it('Returns data with gdprConsent and without uspConsent', function () { + delete bidderRequest.uspConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data.gdpr).to.exist; + expect(data.gdpr).to.be.a('string'); + expect(data.gdpr).to.equal(bidderRequest.gdprConsent); + expect(data.ccpa).to.not.exist; + delete bidderRequest.gdprConsent; + }); + + it('Returns data with uspConsent and without gdprConsent', function () { + bidderRequest.uspConsent = '1---'; + delete bidderRequest.gdprConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data.ccpa).to.exist; + expect(data.ccpa).to.be.a('string'); + expect(data.ccpa).to.equal(bidderRequest.uspConsent); + expect(data.gdpr).to.not.exist; + }); + + it('Returns empty data if no valid requests are passed', function () { + serverRequest = spec.buildRequests([], bidderRequest); + let data = serverRequest.data; + expect(data.placements).to.be.an('array').that.is.empty; + }); + }); + + describe('interpretResponse', function () { + it('Should interpret banner response', function () { + const banner = { + body: [{ + mediaType: 'banner', + width: 300, + height: 250, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + let bannerResponses = spec.interpretResponse(banner); + expect(bannerResponses).to.be.an('array').that.is.not.empty; + let dataItem = bannerResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); + expect(dataItem.requestId).to.equal(banner.body[0].requestId); + expect(dataItem.cpm).to.equal(banner.body[0].cpm); + expect(dataItem.width).to.equal(banner.body[0].width); + expect(dataItem.height).to.equal(banner.body[0].height); + expect(dataItem.ad).to.equal(banner.body[0].ad); + expect(dataItem.ttl).to.equal(banner.body[0].ttl); + expect(dataItem.creativeId).to.equal(banner.body[0].creativeId); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal(banner.body[0].currency); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + it('Should interpret video response', function () { + const video = { + body: [{ + vastUrl: 'test.com', + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + let videoResponses = spec.interpretResponse(video); + expect(videoResponses).to.be.an('array').that.is.not.empty; + + let dataItem = videoResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'vastUrl', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.5); + expect(dataItem.vastUrl).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + it('Should interpret native response', function () { + const native = { + body: [{ + mediaType: 'native', + native: { + clickUrl: 'test.com', + title: 'Test', + image: 'test.com', + impressionTrackers: ['test.com'], + }, + ttl: 120, + cpm: 0.4, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + let nativeResponses = spec.interpretResponse(native); + expect(nativeResponses).to.be.an('array').that.is.not.empty; + + let dataItem = nativeResponses[0]; + expect(dataItem).to.have.keys('requestId', 'cpm', 'ttl', 'creativeId', 'netRevenue', 'currency', 'mediaType', 'native', 'meta'); + expect(dataItem.native).to.have.keys('clickUrl', 'impressionTrackers', 'title', 'image') + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.4); + expect(dataItem.native.clickUrl).to.equal('test.com'); + expect(dataItem.native.title).to.equal('Test'); + expect(dataItem.native.image).to.equal('test.com'); + expect(dataItem.native.impressionTrackers).to.be.an('array').that.is.not.empty; + expect(dataItem.native.impressionTrackers[0]).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + it('Should return an empty array if invalid banner response is passed', function () { + const invBanner = { + body: [{ + width: 300, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + + let serverResponses = spec.interpretResponse(invBanner); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid video response is passed', function () { + const invVideo = { + body: [{ + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let serverResponses = spec.interpretResponse(invVideo); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid native response is passed', function () { + const invNative = { + body: [{ + mediaType: 'native', + clickUrl: 'test.com', + title: 'Test', + impressionTrackers: ['test.com'], + ttl: 120, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + }] + }; + let serverResponses = spec.interpretResponse(invNative); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid response is passed', function () { + const invalid = { + body: [{ + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let serverResponses = spec.interpretResponse(invalid); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + }); + + describe('getUserSyncs', function() { + it('Should return array of objects with proper sync config , include GDPR', function() { + const syncData = spec.getUserSyncs({}, {}, { + consentString: 'ALL', + gdprApplies: true, + }, {}); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object') + expect(syncData[0].type).to.be.a('string') + expect(syncData[0].type).to.equal('image') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal(`${syncUrl}/image?pbjs=1&gdpr=1&gdpr_consent=ALL&coppa=0`) + }); + it('Should return array of objects with proper sync config , include CCPA', function() { + const syncData = spec.getUserSyncs({}, {}, {}, { + consentString: '1---' + }); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object') + expect(syncData[0].type).to.be.a('string') + expect(syncData[0].type).to.equal('image') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal(`${syncUrl}/image?pbjs=1&ccpa_consent=1---&coppa=0`) + }); + }); +}); diff --git a/test/spec/modules/visxBidAdapter_spec.js b/test/spec/modules/visxBidAdapter_spec.js index b8a66e7c3b9..9a486cd6c34 100755 --- a/test/spec/modules/visxBidAdapter_spec.js +++ b/test/spec/modules/visxBidAdapter_spec.js @@ -1273,7 +1273,7 @@ describe('VisxAdapter', function () { it('onTimeout', function () { const data = [{ timeout: 3000, adUnitCode: 'adunit-code-1', auctionId: '1cbd2feafe5e8b', bidder: 'visx', bidId: '23423', params: [{ uid: '1' }] }]; - const expectedData = [{ ...data[0], params: [{ uid: 1 }] }]; + const expectedData = [{ timeout: 3000, params: [{ uid: 1 }] }]; spec.onTimeout(data); expect(utils.triggerPixel.calledOnceWith('https://t.visx.net/track/bid_timeout//' + JSON.stringify(expectedData))).to.equal(true); }); diff --git a/test/spec/modules/voxBidAdapter_spec.js b/test/spec/modules/voxBidAdapter_spec.js index 923b0465e6c..5f4ada06c65 100644 --- a/test/spec/modules/voxBidAdapter_spec.js +++ b/test/spec/modules/voxBidAdapter_spec.js @@ -1,5 +1,6 @@ import { expect } from 'chai' import { spec } from 'modules/voxBidAdapter.js' +import {config} from 'src/config.js' function getSlotConfigs(mediaTypes, params) { return { @@ -175,6 +176,98 @@ describe('VOX Adapter', function() { expect(bid.transactionId).to.equal('31a58515-3634-4e90-9c96-f86196db1459') }) }) + it('should not set userid if not specified', function () { + const request = spec.buildRequests(validBidRequests, bidderRequest) + const data = JSON.parse(request.data) + data.bidRequests.forEach(bid => { + expect(bid.userId).to.be.undefined + }) + }) + + it('should set userid if specified', function () { + const requests = validBidRequests.map(bid => ({ + ...bid, + userId: { + tdid: 'TDID_USER_ID', + pubcid: 'PUBID_USER_ID' + } + })) + const request = spec.buildRequests(requests, bidderRequest) + const data = JSON.parse(request.data) + data.bidRequests.forEach(bid => { + expect(bid.userId.tdid).to.equal('TDID_USER_ID') + expect(bid.userId.pubcid).to.equal('PUBID_USER_ID') + }) + }) + + it('should not set schain if not specified', function () { + const request = spec.buildRequests(validBidRequests, bidderRequest) + const data = JSON.parse(request.data) + data.bidRequests.forEach(bid => { + expect(bid.schain).to.be.undefined + }) + }) + + it('should set schain if not specified', function () { + const requests = validBidRequests.map(bid => ({ + ...bid, + schain: { + validation: 'strict', + config: { + ver: '1.0' + } + } + })) + const request = spec.buildRequests(requests, bidderRequest) + const data = JSON.parse(request.data) + data.bidRequests.forEach(bid => { + expect(bid.schain.validation).to.equal('strict') + expect(bid.schain.config.ver).to.equal('1.0') + }) + }) + + describe('price floors', function () { + it('should be empty if floors module not configured', function () { + const request = spec.buildRequests(validBidRequests, bidderRequest) + const data = JSON.parse(request.data) + data.bidRequests.forEach(bid => { + expect(bid.floorInfo).to.be.empty + }) + }) + + it('should add correct floor values', function () { + const expectedFloors = [ 2, 2.7, 1.4 ] + const validBidRequests = expectedFloors.map(getBidWithFloor) + const request = spec.buildRequests(validBidRequests, bidderRequest) + const data = JSON.parse(request.data) + expectedFloors.forEach((floor, index) => { + expect(data.bidRequests[index].floorInfo.floor).to.equal(floor) + expect(data.bidRequests[index].floorInfo.currency).to.equal('USD') + }) + }) + + it('should request floor price in adserver currency', function () { + const configCurrency = 'DKK' + config.setConfig({ currency: { adServerCurrency: configCurrency } }) + const request = spec.buildRequests([ getBidWithFloor() ], bidderRequest) + const data = JSON.parse(request.data) + data.bidRequests.forEach(bid => { + expect(bid.floorInfo.currency).to.equal(configCurrency) + }) + }) + + function getBidWithFloor(floor) { + return { + ...validBidRequests[0], + getFloor: ({ currency }) => { + return { + currency: currency, + floor + } + } + } + } + }) describe('GDPR params', function() { describe('when there are not consent management platform', function() { diff --git a/test/spec/modules/vrtcalBidAdapter_spec.js b/test/spec/modules/vrtcalBidAdapter_spec.js index d911745d378..764db3fa4f0 100644 --- a/test/spec/modules/vrtcalBidAdapter_spec.js +++ b/test/spec/modules/vrtcalBidAdapter_spec.js @@ -28,7 +28,8 @@ describe('vrtcalBidAdapter', function () { 'bidId': 'bidID0001', 'bidderRequestId': 'br0001', 'auctionId': 'auction0001', - 'userIdAsEids': {} + 'userIdAsEids': {}, + timeout: 435 } ]; @@ -54,14 +55,21 @@ describe('vrtcalBidAdapter', function () { expect(request[0].data).to.match(/"bidfloor":0.55/); }); - it('pass GDPR,CCPA, and COPPA indicators/consent strings with the request when present', function () { + it('pass GDPR,CCPA,COPPA, and GPP indicators/consent strings with the request when present', function () { bidRequests[0].gdprConsent = {consentString: 'gdpr-consent-string', gdprApplies: true}; bidRequests[0].uspConsent = 'ccpa-consent-string'; config.setConfig({ coppa: false }); + bidRequests[0].ortb2 = { + regs: { + gpp: 'testGpp', + gpp_sid: [1, 2, 3] + } + } + request = spec.buildRequests(bidRequests); expect(request[0].data).to.match(/"user":{"ext":{"consent":"gdpr-consent-string"/); - expect(request[0].data).to.match(/"regs":{"coppa":0,"ext":{"gdpr":1,"us_privacy":"ccpa-consent-string"}}/); + expect(request[0].data).to.match(/"regs":{"coppa":0,"ext":{"gdpr":1,"us_privacy":"ccpa-consent-string","gpp":"testGpp","gpp_sid":\[1,2,3\]}}/); }); it('pass bidder timeout/tmax with the request', function () { @@ -95,7 +103,7 @@ describe('vrtcalBidAdapter', function () { w: 300, h: 250, crid: 'v2_1064_vrt_vrtcaltestdisplay2_300_250', - adomain: ['vrtcal.com'] + adomain: ['vrtcal.com'], }], seat: '16' }], diff --git a/test/spec/modules/weboramaRtdProvider_spec.js b/test/spec/modules/weboramaRtdProvider_spec.js index bbc1c6d6f02..7de8474d7c9 100644 --- a/test/spec/modules/weboramaRtdProvider_spec.js +++ b/test/spec/modules/weboramaRtdProvider_spec.js @@ -130,19 +130,19 @@ describe('weboramaRtdProvider', function() { }); expect(reqBidsConfigObj.adUnits[0].bids.length).to.equal(5); - expect(reqBidsConfigObj.adUnits[0].bids[0].params.target).to.equal('webo_ctx=foo;webo_ctx=bar;webo_ds=baz'); - expect(reqBidsConfigObj.adUnits[0].bids[1].params.dctr).to.equal('webo_ctx=foo,bar|webo_ds=baz'); + expect(reqBidsConfigObj.adUnits[0].bids[0].params).to.be.undefined; + expect(reqBidsConfigObj.adUnits[0].bids[1].params).to.be.undefined; expect(reqBidsConfigObj.adUnits[0].bids[2].params.keywords).to.deep.equal(data); - expect(reqBidsConfigObj.adUnits[0].bids[3].params).to.deep.equal({ - inventory: data - }); - expect(reqBidsConfigObj.ortb2Fragments.bidder.other).to.deep.equal({ - site: { - ext: { - data: data - }, - } - }); + expect(reqBidsConfigObj.adUnits[0].bids[3].params).to.be.undefined; + ['smartadserver', 'pubmatic', 'appnexus', 'rubicon', 'other'].forEach((v) => { + expect(reqBidsConfigObj.ortb2Fragments.bidder[v]).to.deep.equal({ + site: { + ext: { + data: data + }, + } + }); + }) expect(onDataResponse).to.deep.equal({ data: data, meta: { @@ -217,19 +217,19 @@ describe('weboramaRtdProvider', function() { }); expect(reqBidsConfigObj.adUnits[0].bids.length).to.equal(5); - expect(reqBidsConfigObj.adUnits[0].bids[0].params.target).to.equal('webo_vctx=foo;webo_vctx=bar'); - expect(reqBidsConfigObj.adUnits[0].bids[1].params.dctr).to.equal('webo_vctx=foo,bar'); + expect(reqBidsConfigObj.adUnits[0].bids[0].params).to.be.undefined; + expect(reqBidsConfigObj.adUnits[0].bids[1].params).to.be.undefined; expect(reqBidsConfigObj.adUnits[0].bids[2].params.keywords).to.deep.equal(data); - expect(reqBidsConfigObj.adUnits[0].bids[3].params).to.deep.equal({ - inventory: data - }); - expect(reqBidsConfigObj.ortb2Fragments.bidder.other).to.deep.equal({ - site: { - ext: { - data: data - }, - } - }); + expect(reqBidsConfigObj.adUnits[0].bids[3].params).to.be.undefined; + ['smartadserver', 'pubmatic', 'appnexus', 'rubicon', 'other'].forEach((v) => { + expect(reqBidsConfigObj.ortb2Fragments.bidder[v]).to.deep.equal({ + site: { + ext: { + data: data + }, + } + }); + }) expect(onDataResponse).to.deep.equal({ data: data, meta: { @@ -304,19 +304,19 @@ describe('weboramaRtdProvider', function() { }); expect(reqBidsConfigObj.adUnits[0].bids.length).to.equal(5); - expect(reqBidsConfigObj.adUnits[0].bids[0].params.target).to.equal('webo_vctx=foo;webo_vctx=bar'); - expect(reqBidsConfigObj.adUnits[0].bids[1].params.dctr).to.equal('webo_vctx=foo,bar'); + expect(reqBidsConfigObj.adUnits[0].bids[0].params).to.be.undefined; + expect(reqBidsConfigObj.adUnits[0].bids[1].params).to.be.undefined; expect(reqBidsConfigObj.adUnits[0].bids[2].params.keywords).to.deep.equal(data); - expect(reqBidsConfigObj.adUnits[0].bids[3].params).to.deep.equal({ - inventory: data - }); - expect(reqBidsConfigObj.ortb2Fragments.bidder.other).to.deep.equal({ - site: { - ext: { - data: data - }, - } - }); + expect(reqBidsConfigObj.adUnits[0].bids[3].params).to.be.undefined; + ['smartadserver', 'pubmatic', 'appnexus', 'rubicon', 'other'].forEach((v) => { + expect(reqBidsConfigObj.ortb2Fragments.bidder[v]).to.deep.equal({ + site: { + ext: { + data: data + }, + } + }); + }) expect(onDataResponse).to.deep.equal({ data: data, meta: { @@ -543,9 +543,22 @@ describe('weboramaRtdProvider', function() { expect(adUnit.bids[1].params).to.be.undefined; expect(adUnit.bids[2].params.keywords).to.deep.equal(data); expect(adUnit.bids[3].params).to.be.undefined; - expect(reqBidsConfigObj.ortb2Fragments.bidder.other).to.be.undefined; }); + ['smartadserver', 'pubmatic', 'appnexus', 'rubicon', 'other'].forEach((v) => { + if (v == 'appnexus') { + expect(reqBidsConfigObj.ortb2Fragments.bidder[v]).to.deep.equal({ + site: { + ext: { + data: data + }, + } + }); + + return; + } + expect(reqBidsConfigObj.ortb2Fragments.bidder[v]).to.be.undefined; + }) expect(onDataResponse).to.deep.equal({ data: data, meta: { @@ -809,9 +822,10 @@ describe('weboramaRtdProvider', function() { baz: 'bam', } }); - - expect(reqBidsConfigObj.ortb2Fragments.bidder.other).to.be.undefined; }); + ['smartadserver', 'pubmatic', 'appnexus', 'rubicon', 'other'].forEach((v) => { + expect(reqBidsConfigObj.ortb2Fragments.bidder[v]).to.be.undefined; + }) }); }); }); @@ -951,9 +965,10 @@ describe('weboramaRtdProvider', function() { baz: 'bam', } }); - - expect(reqBidsConfigObj.ortb2Fragments.bidder.other).to.be.undefined; }); + ['smartadserver', 'pubmatic', 'appnexus', 'rubicon', 'other'].forEach((v) => { + expect(reqBidsConfigObj.ortb2Fragments.bidder[v]).to.be.undefined; + }) }); }); }); @@ -1105,8 +1120,8 @@ describe('weboramaRtdProvider', function() { }); expect(reqBidsConfigObj.adUnits[0].bids.length).to.equal(5); - expect(reqBidsConfigObj.adUnits[0].bids[0].params.target).to.equal('foo=bar;webo_ctx=foo;webo_ctx=bar;webo_ds=baz'); - expect(reqBidsConfigObj.adUnits[0].bids[1].params.dctr).to.equal('foo=bar|webo_ctx=foo,bar|webo_ds=baz'); + expect(reqBidsConfigObj.adUnits[0].bids[0].params.target).to.equal('foo=bar'); + expect(reqBidsConfigObj.adUnits[0].bids[1].params.dctr).to.equal('foo=bar'); expect(reqBidsConfigObj.adUnits[0].bids[2].params.keywords).to.deep.equal({ foo: ['bar'], webo_ctx: ['foo', 'bar'], @@ -1115,20 +1130,20 @@ describe('weboramaRtdProvider', function() { expect(reqBidsConfigObj.adUnits[0].bids[3].params).to.deep.equal({ inventory: { foo: 'bar', - webo_ctx: ['foo', 'bar'], - webo_ds: ['baz'], }, visitor: { baz: 'bam', } }); - expect(reqBidsConfigObj.ortb2Fragments.bidder.other).to.deep.equal({ - site: { - ext: { - data: data - }, - } - }); + ['smartadserver', 'pubmatic', 'appnexus', 'rubicon', 'other'].forEach((v) => { + expect(reqBidsConfigObj.ortb2Fragments.bidder[v]).to.deep.equal({ + site: { + ext: { + data: data + }, + } + }); + }) }); it('should use default profile in case of api error', function() { @@ -1196,19 +1211,19 @@ describe('weboramaRtdProvider', function() { }); expect(reqBidsConfigObj.adUnits[0].bids.length).to.equal(5); - expect(reqBidsConfigObj.adUnits[0].bids[0].params.target).to.equal('webo_ctx=baz'); - expect(reqBidsConfigObj.adUnits[0].bids[1].params.dctr).to.equal('webo_ctx=baz'); + expect(reqBidsConfigObj.adUnits[0].bids[0].params).to.be.undefined; + expect(reqBidsConfigObj.adUnits[0].bids[1].params).to.be.undefined; expect(reqBidsConfigObj.adUnits[0].bids[2].params.keywords).to.deep.equal(defaultProfile); - expect(reqBidsConfigObj.adUnits[0].bids[3].params).to.deep.equal({ - inventory: defaultProfile - }); - expect(reqBidsConfigObj.ortb2Fragments.bidder.other).to.deep.equal({ - site: { - ext: { - data: defaultProfile - }, - } - }); + expect(reqBidsConfigObj.adUnits[0].bids[3].params).to.be.undefined; + ['smartadserver', 'pubmatic', 'appnexus', 'rubicon', 'other'].forEach((v) => { + expect(reqBidsConfigObj.ortb2Fragments.bidder[v]).to.deep.equal({ + site: { + ext: { + data: defaultProfile + }, + } + }); + }) expect(onDataResponse).to.deep.equal({ data: defaultProfile, meta: { @@ -1316,20 +1331,35 @@ describe('weboramaRtdProvider', function() { reqBidsConfigObj.adUnits.forEach(adUnit => { expect(adUnit.bids.length).to.equal(5); - expect(adUnit.bids[0].params.target).to.equal('webo_ctx=foo;webo_ctx=bar;webo_ds=baz'); - expect(adUnit.bids[1].params.dctr).to.equal('webo_ctx=foo,bar|webo_ds=baz'); - expect(adUnit.bids[3].params).to.deep.equal({ - inventory: data - }); - expect(reqBidsConfigObj.ortb2Fragments.bidder.other).to.deep.equal({ + expect(adUnit.bids[0].params).to.be.undefined; + expect(adUnit.bids[1].params).to.be.undefined; + expect(adUnit.bids[3].params).to.be.undefined; + }); + ['smartadserver', 'pubmatic', 'appnexus', 'rubicon', 'other'].forEach((v) => { + if (v == 'appnexus') { + expect(reqBidsConfigObj.ortb2Fragments.bidder[v]).to.deep.equal({ + site: { + ext: { + data: { + webo_ctx: ['foo', 'bar'], + webo_ds: ['baz'], + webo_bar: ['baz'], + } + }, + } + }); + + return + } + + expect(reqBidsConfigObj.ortb2Fragments.bidder[v]).to.deep.equal({ site: { ext: { data: data }, } }); - }); - + }) expect(reqBidsConfigObj.adUnits[0].bids[2].params.keywords).to.deep.equal({ webo_ctx: ['foo', 'bar'], webo_ds: ['baz'], @@ -1414,19 +1444,19 @@ describe('weboramaRtdProvider', function() { }); expect(reqBidsConfigObj.adUnits[0].bids.length).to.equal(5); - expect(reqBidsConfigObj.adUnits[0].bids[0].params.target).to.equal('webo_cs=foo;webo_cs=bar;webo_audiences=baz'); - expect(reqBidsConfigObj.adUnits[0].bids[1].params.dctr).to.equal('webo_cs=foo,bar|webo_audiences=baz'); + expect(reqBidsConfigObj.adUnits[0].bids[0].params).to.be.undefined; + expect(reqBidsConfigObj.adUnits[0].bids[1].params).to.be.undefined; expect(reqBidsConfigObj.adUnits[0].bids[2].params.keywords).to.deep.equal(data); - expect(reqBidsConfigObj.adUnits[0].bids[3].params).to.deep.equal({ - visitor: data - }); - expect(reqBidsConfigObj.ortb2Fragments.bidder.other).to.deep.equal({ - user: { - ext: { - data: data - }, - } - }); + expect(reqBidsConfigObj.adUnits[0].bids[3].params).to.be.undefined; + ['smartadserver', 'pubmatic', 'appnexus', 'rubicon', 'other'].forEach((v) => { + expect(reqBidsConfigObj.ortb2Fragments.bidder[v]).to.deep.equal({ + user: { + ext: { + data: data + }, + } + }); + }) expect(onDataResponse).to.deep.equal({ data: data, meta: { @@ -1545,8 +1575,21 @@ describe('weboramaRtdProvider', function() { expect(adUnit.bids[2].params.keywords).to.deep.equal(data); expect(adUnit.bids[3].params).to.be.undefined; }); - expect(reqBidsConfigObj.ortb2Fragments.bidder.other).to.be.undefined; + ['smartadserver', 'pubmatic', 'appnexus', 'rubicon', 'other'].forEach((v) => { + if (v == 'appnexus') { + expect(reqBidsConfigObj.ortb2Fragments.bidder[v]).to.deep.equal({ + user: { + ext: { + data: data + }, + } + }); + + return + } + expect(reqBidsConfigObj.ortb2Fragments.bidder[v]).to.be.undefined; + }) expect(onDataResponse).to.deep.equal({ data: data, meta: { @@ -1657,7 +1700,21 @@ describe('weboramaRtdProvider', function() { expect(adUnit.bids[1].params).to.be.undefined; expect(adUnit.bids[3].params).to.be.undefined; }); - expect(reqBidsConfigObj.ortb2Fragments.bidder.other).to.be.undefined; + ['smartadserver', 'pubmatic', 'appnexus', 'rubicon', 'other'].forEach((v) => { + if (v == 'appnexus') { + expect(reqBidsConfigObj.ortb2Fragments.bidder[v]).to.deep.equal({ + user: { + ext: { + data: data + }, + } + }); + + return + } + + expect(reqBidsConfigObj.ortb2Fragments.bidder[v]).to.be.undefined; + }) expect(reqBidsConfigObj.adUnits[0].bids[2].params.keywords).to.deep.equal(data); expect(reqBidsConfigObj.adUnits[1].bids[2].params).to.be.undefined; @@ -1813,7 +1870,9 @@ describe('weboramaRtdProvider', function() { } }); }); - expect(reqBidsConfigObj.ortb2Fragments.bidder.other).to.be.undefined; + ['smartadserver', 'pubmatic', 'appnexus', 'rubicon', 'other'].forEach((v) => { + expect(reqBidsConfigObj.ortb2Fragments.bidder[v]).to.be.undefined; + }) }); }); }); @@ -1956,7 +2015,9 @@ describe('weboramaRtdProvider', function() { } }); }); - expect(reqBidsConfigObj.ortb2Fragments.bidder.other).to.be.undefined; + ['smartadserver', 'pubmatic', 'appnexus', 'rubicon', 'other'].forEach((v) => { + expect(reqBidsConfigObj.ortb2Fragments.bidder[v]).to.be.undefined; + }) }); }); }); @@ -2109,8 +2170,8 @@ describe('weboramaRtdProvider', function() { }); expect(reqBidsConfigObj.adUnits[0].bids.length).to.equal(5); - expect(reqBidsConfigObj.adUnits[0].bids[0].params.target).to.equal('foo=bar;webo_cs=foo;webo_cs=bar;webo_audiences=baz'); - expect(reqBidsConfigObj.adUnits[0].bids[1].params.dctr).to.equal('foo=bar|webo_cs=foo,bar|webo_audiences=baz'); + expect(reqBidsConfigObj.adUnits[0].bids[0].params.target).to.equal('foo=bar'); + expect(reqBidsConfigObj.adUnits[0].bids[1].params.dctr).to.equal('foo=bar'); expect(reqBidsConfigObj.adUnits[0].bids[2].params.keywords).to.deep.equal({ foo: ['bar'], webo_cs: ['foo', 'bar'], @@ -2122,17 +2183,17 @@ describe('weboramaRtdProvider', function() { }, visitor: { baz: 'bam', - webo_cs: ['foo', 'bar'], - webo_audiences: ['baz'], - } - }); - expect(reqBidsConfigObj.ortb2Fragments.bidder.other).to.deep.equal({ - user: { - ext: { - data: data - }, } }); + ['smartadserver', 'pubmatic', 'appnexus', 'rubicon', 'other'].forEach((v) => { + expect(reqBidsConfigObj.ortb2Fragments.bidder[v]).to.deep.equal({ + user: { + ext: { + data: data + }, + } + }); + }) }); it('should use default profile in case of nothing on local storage', function() { @@ -2187,19 +2248,19 @@ describe('weboramaRtdProvider', function() { }); expect(reqBidsConfigObj.adUnits[0].bids.length).to.equal(5); - expect(reqBidsConfigObj.adUnits[0].bids[0].params.target).to.equal('webo_audiences=baz'); - expect(reqBidsConfigObj.adUnits[0].bids[1].params.dctr).to.equal('webo_audiences=baz'); + expect(reqBidsConfigObj.adUnits[0].bids[0].params).to.be.undefined; + expect(reqBidsConfigObj.adUnits[0].bids[1].params).to.be.undefined; expect(reqBidsConfigObj.adUnits[0].bids[2].params.keywords).to.deep.equal(defaultProfile); - expect(reqBidsConfigObj.adUnits[0].bids[3].params).to.deep.equal({ - visitor: defaultProfile - }); - expect(reqBidsConfigObj.ortb2Fragments.bidder.other).to.deep.equal({ - user: { - ext: { - data: defaultProfile - }, - } - }); + expect(reqBidsConfigObj.adUnits[0].bids[3].params).to.be.undefined; + ['smartadserver', 'pubmatic', 'appnexus', 'rubicon', 'other'].forEach((v) => { + expect(reqBidsConfigObj.ortb2Fragments.bidder[v]).to.deep.equal({ + user: { + ext: { + data: defaultProfile + }, + } + }); + }) }); it('should use default profile if cant read from local storage', function() { @@ -2261,19 +2322,19 @@ describe('weboramaRtdProvider', function() { }); expect(reqBidsConfigObj.adUnits[0].bids.length).to.equal(5); - expect(reqBidsConfigObj.adUnits[0].bids[0].params.target).to.equal('webo_audiences=baz'); - expect(reqBidsConfigObj.adUnits[0].bids[1].params.dctr).to.equal('webo_audiences=baz'); + expect(reqBidsConfigObj.adUnits[0].bids[0].params).to.be.undefined; + expect(reqBidsConfigObj.adUnits[0].bids[1].params).to.be.undefined; expect(reqBidsConfigObj.adUnits[0].bids[2].params.keywords).to.deep.equal(defaultProfile); - expect(reqBidsConfigObj.adUnits[0].bids[3].params).to.deep.equal({ - visitor: defaultProfile - }); - expect(reqBidsConfigObj.ortb2Fragments.bidder.other).to.deep.equal({ - user: { - ext: { - data: defaultProfile - }, - } - }); + expect(reqBidsConfigObj.adUnits[0].bids[3].params).to.be.undefined; + ['smartadserver', 'pubmatic', 'appnexus', 'rubicon', 'other'].forEach((v) => { + expect(reqBidsConfigObj.ortb2Fragments.bidder[v]).to.deep.equal({ + user: { + ext: { + data: defaultProfile + }, + } + }); + }) expect(onDataResponse).to.deep.equal({ data: defaultProfile, meta: { @@ -2383,26 +2444,35 @@ describe('weboramaRtdProvider', function() { reqBidsConfigObj.adUnits.forEach(adUnit => { expect(adUnit.bids.length).to.equal(5); - expect(adUnit.bids[0].params.target).to.equal('webo_cs=foo;webo_cs=bar;webo_audiences=baz'); - expect(adUnit.bids[1].params.dctr).to.equal('webo_cs=foo,bar|webo_audiences=baz'); - expect(adUnit.bids[3].params).to.deep.equal({ - visitor: data - }); - expect(reqBidsConfigObj.ortb2Fragments.bidder.other).to.deep.equal({ + expect(adUnit.bids[0].params).to.be.undefined; + expect(adUnit.bids[1].params).to.be.undefined; + expect(adUnit.bids[3].params).to.be.undefined; + }); + ['smartadserver', 'pubmatic', 'appnexus', 'rubicon', 'other'].forEach((v) => { + if (v == 'appnexus') { + expect(reqBidsConfigObj.ortb2Fragments.bidder[v]).to.deep.equal({ + user: { + ext: { + data: { + webo_cs: ['foo', 'bar'], + webo_audiences: ['baz'], + webo_bar: ['baz'], + } + }, + } + }); + + return + } + + expect(reqBidsConfigObj.ortb2Fragments.bidder[v]).to.deep.equal({ user: { ext: { data: data }, } }); - }); - - expect(reqBidsConfigObj.adUnits[0].bids[2].params.keywords).to.deep.equal({ - webo_cs: ['foo', 'bar'], - webo_audiences: ['baz'], - webo_bar: ['baz'], - }); - expect(reqBidsConfigObj.adUnits[1].bids[2].params.keywords).to.deep.equal(data); + }) expect(onDataResponse).to.deep.equal({ data: data, @@ -2480,19 +2550,19 @@ describe('weboramaRtdProvider', function() { }); expect(reqBidsConfigObj.adUnits[0].bids.length).to.equal(5); - expect(reqBidsConfigObj.adUnits[0].bids[0].params.target).to.equal('lite_occupation=gérant;lite_occupation=bénévole;lite_hobbies=sport;lite_hobbies=cinéma'); - expect(reqBidsConfigObj.adUnits[0].bids[1].params.dctr).to.equal('lite_occupation=gérant,bénévole|lite_hobbies=sport,cinéma'); + expect(reqBidsConfigObj.adUnits[0].bids[0].params).to.be.undefined; + expect(reqBidsConfigObj.adUnits[0].bids[1].params).to.be.undefined; expect(reqBidsConfigObj.adUnits[0].bids[2].params.keywords).to.deep.equal(data); - expect(reqBidsConfigObj.adUnits[0].bids[3].params).to.deep.equal({ - inventory: data, - }); - expect(reqBidsConfigObj.ortb2Fragments.bidder.other).to.deep.equal({ - site: { - ext: { - data: data, - }, - }, - }); + expect(reqBidsConfigObj.adUnits[0].bids[3].params).to.be.undefined; + ['smartadserver', 'pubmatic', 'appnexus', 'rubicon', 'other'].forEach((v) => { + expect(reqBidsConfigObj.ortb2Fragments.bidder[v]).to.deep.equal({ + site: { + ext: { + data: data + }, + } + }); + }) expect(onDataResponse).to.deep.equal({ data: data, meta: { @@ -2610,7 +2680,21 @@ describe('weboramaRtdProvider', function() { expect(adUnit.bids[2].params.keywords).to.deep.equal(data); expect(adUnit.bids[3].params).to.be.undefined; }); - expect(reqBidsConfigObj.ortb2Fragments.bidder.other).to.be.undefined; + ['smartadserver', 'pubmatic', 'appnexus', 'rubicon', 'other'].forEach((v) => { + if (v == 'appnexus') { + expect(reqBidsConfigObj.ortb2Fragments.bidder[v]).to.deep.equal({ + site: { + ext: { + data: data + }, + } + }); + + return + } + + expect(reqBidsConfigObj.ortb2Fragments.bidder[v]).to.be.undefined; + }) expect(onDataResponse).to.deep.equal({ data: data, @@ -2721,11 +2805,26 @@ describe('weboramaRtdProvider', function() { expect(adUnit.bids[1].params).to.be.undefined; expect(adUnit.bids[3].params).to.be.undefined; }); - expect(reqBidsConfigObj.ortb2Fragments.bidder.other).to.be.undefined; expect(reqBidsConfigObj.adUnits[0].bids[2].params.keywords).to.deep.equal(data); expect(reqBidsConfigObj.adUnits[1].bids[2].params).to.be.undefined; + ['smartadserver', 'pubmatic', 'appnexus', 'rubicon', 'other'].forEach((v) => { + if (v == 'appnexus') { + expect(reqBidsConfigObj.ortb2Fragments.bidder[v]).to.deep.equal({ + site: { + ext: { + data: data + }, + } + }); + + return + } + + expect(reqBidsConfigObj.ortb2Fragments.bidder[v]).to.be.undefined; + }) + expect(onDataResponse).to.deep.equal({ data: data, meta: { @@ -2876,7 +2975,9 @@ describe('weboramaRtdProvider', function() { } }); }); - expect(reqBidsConfigObj.ortb2Fragments.bidder.other).to.be.undefined; + ['smartadserver', 'pubmatic', 'appnexus', 'rubicon', 'other'].forEach((v) => { + expect(reqBidsConfigObj.ortb2Fragments.bidder[v]).to.be.undefined; + }) }); }); }); @@ -3018,7 +3119,9 @@ describe('weboramaRtdProvider', function() { } }); }); - expect(reqBidsConfigObj.ortb2Fragments.bidder.other).to.be.undefined; + ['smartadserver', 'pubmatic', 'appnexus', 'rubicon', 'other'].forEach((v) => { + expect(reqBidsConfigObj.ortb2Fragments.bidder[v]).to.be.undefined; + }) }); }); }); @@ -3173,8 +3276,8 @@ describe('weboramaRtdProvider', function() { }); expect(reqBidsConfigObj.adUnits[0].bids.length).to.equal(5); - expect(reqBidsConfigObj.adUnits[0].bids[0].params.target).to.equal('foo=bar;lite_occupation=gérant;lite_occupation=bénévole;lite_hobbies=sport;lite_hobbies=cinéma'); - expect(reqBidsConfigObj.adUnits[0].bids[1].params.dctr).to.equal('foo=bar|lite_occupation=gérant,bénévole|lite_hobbies=sport,cinéma'); + expect(reqBidsConfigObj.adUnits[0].bids[0].params.target).to.equal('foo=bar'); + expect(reqBidsConfigObj.adUnits[0].bids[1].params.dctr).to.equal('foo=bar'); expect(reqBidsConfigObj.adUnits[0].bids[2].params.keywords).to.deep.equal({ foo: ['bar'], lite_occupation: ['gérant', 'bénévole'], @@ -3183,20 +3286,20 @@ describe('weboramaRtdProvider', function() { expect(reqBidsConfigObj.adUnits[0].bids[3].params).to.deep.equal({ inventory: { foo: 'bar', - lite_occupation: ['gérant', 'bénévole'], - lite_hobbies: ['sport', 'cinéma'], }, visitor: { baz: 'bam', } }); - expect(reqBidsConfigObj.ortb2Fragments.bidder.other).to.deep.equal({ - site: { - ext: { - data: data, - }, - }, - }); + ['smartadserver', 'pubmatic', 'appnexus', 'rubicon', 'other'].forEach((v) => { + expect(reqBidsConfigObj.ortb2Fragments.bidder[v]).to.deep.equal({ + site: { + ext: { + data: data + }, + } + }); + }) }); it('should use default profile in case of nothing on local storage', function() { @@ -3250,19 +3353,19 @@ describe('weboramaRtdProvider', function() { }); expect(reqBidsConfigObj.adUnits[0].bids.length).to.equal(5); - expect(reqBidsConfigObj.adUnits[0].bids[0].params.target).to.equal('lite_hobbies=sport;lite_hobbies=cinéma'); - expect(reqBidsConfigObj.adUnits[0].bids[1].params.dctr).to.equal('lite_hobbies=sport,cinéma'); + expect(reqBidsConfigObj.adUnits[0].bids[0].params).to.be.undefined; + expect(reqBidsConfigObj.adUnits[0].bids[1].params).to.be.undefined; expect(reqBidsConfigObj.adUnits[0].bids[2].params.keywords).to.deep.equal(defaultProfile); - expect(reqBidsConfigObj.adUnits[0].bids[3].params).to.deep.equal({ - inventory: defaultProfile, - }); - expect(reqBidsConfigObj.ortb2Fragments.bidder.other).to.deep.equal({ - site: { - ext: { - data: defaultProfile, - }, - }, - }); + expect(reqBidsConfigObj.adUnits[0].bids[3].params).to.be.undefined; + ['smartadserver', 'pubmatic', 'appnexus', 'rubicon', 'other'].forEach((v) => { + expect(reqBidsConfigObj.ortb2Fragments.bidder[v]).to.deep.equal({ + site: { + ext: { + data: defaultProfile + }, + } + }); + }) }); it('should use default profile if cant read from local storage', function() { @@ -3323,12 +3426,10 @@ describe('weboramaRtdProvider', function() { }); expect(reqBidsConfigObj.adUnits[0].bids.length).to.equal(5); - expect(reqBidsConfigObj.adUnits[0].bids[0].params.target).to.equal('lite_hobbies=sport;lite_hobbies=cinéma'); - expect(reqBidsConfigObj.adUnits[0].bids[1].params.dctr).to.equal('lite_hobbies=sport,cinéma'); + expect(reqBidsConfigObj.adUnits[0].bids[0].params).to.be.undefined; + expect(reqBidsConfigObj.adUnits[0].bids[1].params).to.be.undefined; expect(reqBidsConfigObj.adUnits[0].bids[2].params.keywords).to.deep.equal(defaultProfile); - expect(reqBidsConfigObj.adUnits[0].bids[3].params).to.deep.equal({ - inventory: defaultProfile, - }); + expect(reqBidsConfigObj.adUnits[0].bids[3].params).to.be.undefined; expect(reqBidsConfigObj.ortb2Fragments.bidder.other).to.deep.equal({ site: { ext: { @@ -3336,6 +3437,15 @@ describe('weboramaRtdProvider', function() { }, }, }); + ['smartadserver', 'pubmatic', 'appnexus', 'rubicon', 'other'].forEach((v) => { + expect(reqBidsConfigObj.ortb2Fragments.bidder[v]).to.deep.equal({ + site: { + ext: { + data: defaultProfile, + }, + } + }); + }) expect(onDataResponse).to.deep.equal({ data: defaultProfile, meta: { @@ -3403,19 +3513,19 @@ describe('weboramaRtdProvider', function() { }); expect(reqBidsConfigObj.adUnits[0].bids.length).to.equal(5); - expect(reqBidsConfigObj.adUnits[0].bids[0].params.target).to.equal('lite_hobbies=sport;lite_hobbies=cinéma'); - expect(reqBidsConfigObj.adUnits[0].bids[1].params.dctr).to.equal('lite_hobbies=sport,cinéma'); + expect(reqBidsConfigObj.adUnits[0].bids[0].params).to.be.undefined; + expect(reqBidsConfigObj.adUnits[0].bids[1].params).to.be.undefined; expect(reqBidsConfigObj.adUnits[0].bids[2].params.keywords).to.deep.equal(defaultProfile); - expect(reqBidsConfigObj.adUnits[0].bids[3].params).to.deep.equal({ - inventory: defaultProfile, - }); - expect(reqBidsConfigObj.ortb2Fragments.bidder.other).to.deep.equal({ - site: { - ext: { - data: defaultProfile, - }, - }, - }); + expect(reqBidsConfigObj.adUnits[0].bids[3].params).to.be.undefined; + ['smartadserver', 'pubmatic', 'appnexus', 'rubicon', 'other'].forEach((v) => { + expect(reqBidsConfigObj.ortb2Fragments.bidder[v]).to.deep.equal({ + site: { + ext: { + data: defaultProfile + }, + } + }); + }) expect(onDataResponse).to.deep.equal({ data: defaultProfile, meta: { @@ -3523,19 +3633,35 @@ describe('weboramaRtdProvider', function() { reqBidsConfigObj.adUnits.forEach(adUnit => { expect(adUnit.bids.length).to.equal(5); - expect(adUnit.bids[0].params.target).to.equal('lite_occupation=gérant;lite_occupation=bénévole;lite_hobbies=sport;lite_hobbies=cinéma'); - expect(adUnit.bids[1].params.dctr).to.equal('lite_occupation=gérant,bénévole|lite_hobbies=sport,cinéma'); - expect(adUnit.bids[3].params).to.deep.equal({ - inventory: data, + expect(adUnit.bids[0].params).to.be.undefined; + expect(adUnit.bids[1].params).to.be.undefined; + expect(adUnit.bids[3].params).to.be.undefined; + }); + ['smartadserver', 'pubmatic', 'appnexus', 'rubicon', 'other'].forEach((v) => { + if (v == 'appnexus') { + expect(reqBidsConfigObj.ortb2Fragments.bidder[v]).to.deep.equal({ + site: { + ext: { + data: { + lite_occupation: ['gérant', 'bénévole'], + lite_hobbies: ['sport', 'cinéma'], + lito_bar: ['baz'], + }, + }, + } + }); + + return + } + + expect(reqBidsConfigObj.ortb2Fragments.bidder[v]).to.deep.equal({ + site: { + ext: { + data: data, + }, + } }); - }); - expect(reqBidsConfigObj.ortb2Fragments.bidder.other).to.deep.equal({ - site: { - ext: { - data: data, - }, - }, - }); + }) expect(reqBidsConfigObj.adUnits[0].bids[2].params.keywords).to.deep.equal({ lite_occupation: ['gérant', 'bénévole'], diff --git a/test/spec/modules/winrBidAdapter_spec.js b/test/spec/modules/winrBidAdapter_spec.js index 174f600fa06..95d1473d1cb 100644 --- a/test/spec/modules/winrBidAdapter_spec.js +++ b/test/spec/modules/winrBidAdapter_spec.js @@ -599,15 +599,6 @@ describe('WinrAdapter', function () { }); describe('interpretResponse', function () { - let bfStub; - before(function() { - bfStub = sinon.stub(bidderFactory, 'getIabSubCategory'); - }); - - after(function() { - bfStub.restore(); - }); - let response = { 'version': '3.0.0', 'tags': [ diff --git a/test/spec/modules/xeBidAdapter_spec.js b/test/spec/modules/xeBidAdapter_spec.js index dcd82683221..38b334c32c5 100644 --- a/test/spec/modules/xeBidAdapter_spec.js +++ b/test/spec/modules/xeBidAdapter_spec.js @@ -11,7 +11,11 @@ const defaultRequest = { bidId: '1', requestId: 'qwerty', auctionId: 'auctionId', - transactionId: 'tr1', + ortb2Imp: { + ext: { + tid: 'tr1', + } + }, mediaTypes: { banner: { sizes: [ @@ -85,7 +89,7 @@ describe('buildRequests', function () { const request = JSON.parse(spec.buildRequests([defaultRequest], {}).data)[0]; expect(request).to.have.property('bidId').and.to.equal(defaultRequest.bidId); expect(request).to.have.property('auctionId').and.to.equal(defaultRequest.auctionId); - expect(request).to.have.property('transactionId').and.to.equal(defaultRequest.transactionId); + expect(request).to.have.property('transactionId').and.to.equal(defaultRequest.ortb2Imp.ext.tid); expect(request).to.have.property('tz').and.to.equal(new Date().getTimezoneOffset()); expect(request).to.have.property('bc').and.to.equal(1); expect(request).to.have.property('floor').and.to.equal(null); diff --git a/test/spec/modules/yahoosspBidAdapter_spec.js b/test/spec/modules/yahoosspBidAdapter_spec.js index a326b22ecb4..c0584173082 100644 --- a/test/spec/modules/yahoosspBidAdapter_spec.js +++ b/test/spec/modules/yahoosspBidAdapter_spec.js @@ -3,7 +3,6 @@ import { config } from 'src/config.js'; import { BANNER, VIDEO } from 'src/mediaTypes.js'; import { spec } from 'modules/yahoosspBidAdapter.js'; import {createEidsArray} from '../../../modules/userId/eids'; -import {deepClone} from '../../../src/utils'; const DEFAULT_BID_ID = '84ab500420319d'; const DEFAULT_BID_DCN = '2093845709823475'; @@ -13,18 +12,20 @@ const DEFAULT_AD_UNIT_CODE = '/19968336/header-bid-tag-1'; const DEFAULT_AD_UNIT_TYPE = 'banner'; const DEFAULT_PARAMS_BID_OVERRIDE = {}; const DEFAULT_VIDEO_CONTEXT = 'instream'; -const ADAPTER_VERSION = '1.0.2'; +const ADAPTER_VERSION = '1.1.0'; +const DEFAULT_BIDDER_CODE = 'yahooAds'; +const VALID_BIDDER_CODES = [DEFAULT_BIDDER_CODE, 'yahoossp', 'yahooAdvertising']; const PREBID_VERSION = '$prebid.version$'; const INTEGRATION_METHOD = 'prebid.js'; // Utility functions -const generateBidRequest = ({bidId, pos, adUnitCode, adUnitType, bidOverrideObject, videoContext, pubIdMode, ortb2}) => { +const generateBidRequest = ({bidderCode, bidId, pos, adUnitCode, adUnitType, bidOverrideObject, videoContext, pubIdMode, ortb2}) => { const bidRequest = { adUnitCode, auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', bidId, bidderRequestsCount: 1, - bidder: 'yahoossp', + bidder: bidderCode, bidderRequestId: '7101db09af0db2', bidderWinsCount: 0, mediaTypes: {}, @@ -79,7 +80,7 @@ let generateBidderRequest = (bidRequestArray, adUnitCode, ortb2 = {}) => { adUnitCode: adUnitCode || 'default-adUnitCode', auctionId: 'd4c83a3b-18e4-4208-b98b-63848449c7aa', auctionStart: new Date().getTime(), - bidderCode: 'yahoossp', + bidderCode: bidRequestArray[0].bidder, bidderRequestId: '112f1c7c5d399a', bids: bidRequestArray, refererInfo: { @@ -89,11 +90,16 @@ let generateBidderRequest = (bidRequestArray, adUnitCode, ortb2 = {}) => { numIframes: 0, stack: ['https://publisher-test.com'], }, + uspConsent: '1-Y-', gdprConsent: { consentString: 'BOtmiBKOtmiBKABABAENAFAAAAACeAAA', vendorData: {}, gdprApplies: true }, + gppConsent: { + gppString: 'DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN', + applicableSections: [1, 2, 3] + }, start: new Date().getTime(), timeout: 1000, ortb2 @@ -102,8 +108,9 @@ let generateBidderRequest = (bidRequestArray, adUnitCode, ortb2 = {}) => { return bidderRequest; }; -const generateBuildRequestMock = ({bidId, pos, adUnitCode, adUnitType, bidOverrideObject, videoContext, pubIdMode, ortb2}) => { +const generateBuildRequestMock = ({bidderCode, bidId, pos, adUnitCode, adUnitType, bidOverrideObject, videoContext, pubIdMode, ortb2}) => { const bidRequestConfig = { + bidderCode: bidderCode || DEFAULT_BIDDER_CODE, bidId: bidId || DEFAULT_BID_ID, pos: pos || DEFAULT_BID_POS, adUnitCode: adUnitCode || DEFAULT_AD_UNIT_CODE, @@ -169,64 +176,64 @@ const generateResponseMock = (admPayloadType, vastVersion, videoContext) => { seatbid: [{ bid: [ bidResponse ], seat: 13107 }] } }; - const { validBidRequests, bidderRequest } = generateBuildRequestMock({adUnitType: admPayloadType, videoContext: videoContext}); + const { validBidRequests, bidderRequest } = generateBuildRequestMock({adUnitType: admPayloadType, videoContext}); const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; - return {serverResponse, data, bidderRequest}; } // Unit tests -describe('YahooSSP Bid Adapter:', () => { - it('PLACEHOLDER TO PASS GULP', () => { - const obj = {}; - expect(obj).to.be.an('object'); +describe('Yahoo Advertising Bid Adapter:', () => { + beforeEach(() => { + config.resetConfig(); }); describe('Validate basic properties', () => { it('should define the correct bidder code', () => { - expect(spec.code).to.equal('yahoossp') + expect(spec.code).to.equal('yahooAds'); + }); + + it('should define the correct bidder aliases', () => { + expect(spec.aliases).to.deep.equal(['yahoossp', 'yahooAdvertising']); }); it('should define the correct vendor ID', () => { - expect(spec.gvlid).to.equal(25) + expect(spec.gvlid).to.equal(25); }); }); describe('getUserSyncs()', () => { - const IMAGE_PIXEL_URL = 'http://image-pixel.com/foo/bar?1234&baz=true'; - const IFRAME_ONE_URL = 'http://image-iframe.com/foo/bar?1234&baz=true'; + const IMAGE_PIXEL_URL = 'http://image-pixel.com/foo/bar?1234&baz=true&gdpr=foo&gdpr_consent=bar'; + const IFRAME_ONE_URL = 'http://image-iframe.com/foo/bar?1234&baz=true&us_privacy=hello&gpp=goodbye'; const IFRAME_TWO_URL = 'http://image-iframe-two.com/foo/bar?1234&baz=true'; - - let serverResponses = []; - beforeEach(() => { - serverResponses[0] = { - body: { - ext: { - pixels: `` - } + const SERVER_RESPONSES = [{ + body: { + ext: { + pixels: `` } } - }); - - after(() => { - serverResponses = undefined; - }); + }]; + const bidderRequest = generateBuildRequestMock({}).bidderRequest; it('for only iframe enabled syncs', () => { let syncOptions = { iframeEnabled: true, pixelEnabled: false }; - let pixelsObjects = spec.getUserSyncs(syncOptions, serverResponses); - expect(pixelsObjects.length).to.equal(2); - expect(pixelsObjects).to.deep.equal( - [ - {type: 'iframe', 'url': IFRAME_ONE_URL}, - {type: 'iframe', 'url': IFRAME_TWO_URL} - ] - ) + let pixelObjects = spec.getUserSyncs( + syncOptions, + SERVER_RESPONSES, + bidderRequest.gdprConsent, + bidderRequest.uspConsent, + bidderRequest.gppConsent + ); + expect(pixelObjects.length).to.equal(2); + + pixelObjects.forEach(pixelObject => { + expect(pixelObject).to.have.all.keys('type', 'url'); + expect(pixelObject.type).to.equal('iframe'); + }); }); it('for only pixel enabled syncs', () => { @@ -234,13 +241,16 @@ describe('YahooSSP Bid Adapter:', () => { iframeEnabled: false, pixelEnabled: true }; - let pixelsObjects = spec.getUserSyncs(syncOptions, serverResponses); - expect(pixelsObjects.length).to.equal(1); - expect(pixelsObjects).to.deep.equal( - [ - {type: 'image', 'url': IMAGE_PIXEL_URL} - ] - ) + let pixelObjects = spec.getUserSyncs( + syncOptions, + SERVER_RESPONSES, + bidderRequest.gdprConsent, + bidderRequest.uspConsent, + bidderRequest.gppConsent + ); + expect(pixelObjects.length).to.equal(1); + expect(pixelObjects[0]).to.have.all.keys('type', 'url'); + expect(pixelObjects[0].type).to.equal('image'); }); it('for both pixel and iframe enabled syncs', () => { @@ -248,15 +258,85 @@ describe('YahooSSP Bid Adapter:', () => { iframeEnabled: true, pixelEnabled: true }; - let pixelsObjects = spec.getUserSyncs(syncOptions, serverResponses); - expect(pixelsObjects.length).to.equal(3); - expect(pixelsObjects).to.deep.equal( - [ - {type: 'iframe', 'url': IFRAME_ONE_URL}, - {type: 'image', 'url': IMAGE_PIXEL_URL}, - {type: 'iframe', 'url': IFRAME_TWO_URL} - ] - ) + let pixelObjects = spec.getUserSyncs( + syncOptions, + SERVER_RESPONSES, + bidderRequest.gdprConsent, + bidderRequest.uspConsent, + bidderRequest.gppConsent + ); + expect(pixelObjects.length).to.equal(3); + let iframeCount = 0; + let imageCount = 0; + pixelObjects.forEach(pixelObject => { + if (pixelObject.type == 'iframe') { + iframeCount++; + } else if (pixelObject.type == 'image') { + imageCount++; + } + }); + expect(iframeCount).to.equal(2); + expect(imageCount).to.equal(1); + }); + + describe('user consent parameters are updated', () => { + const syncOptions = { + iframeEnabled: true, + pixelEnabled: true + }; + describe('when all consent data is set', () => { + const pixelObjects = spec.getUserSyncs( + syncOptions, + SERVER_RESPONSES, + bidderRequest.gdprConsent, + bidderRequest.uspConsent, + bidderRequest.gppConsent + ); + pixelObjects.forEach(pixelObject => { + let url = pixelObject.url; + let urlParams = new URL(url).searchParams; + const expectedParams = { + 'baz': 'true', + 'gdpr_consent': bidderRequest.gdprConsent.consentString, + 'gdpr': bidderRequest.gdprConsent.gdprApplies ? '1' : '0', + 'us_privacy': bidderRequest.uspConsent, + 'gpp': bidderRequest.gppConsent.gppString, + 'gpp_sid': Array.isArray(bidderRequest.gppConsent.applicableSections) ? bidderRequest.gppConsent.applicableSections.join(',') : '' + } + for (const [key, value] of Object.entries(expectedParams)) { + it(`Updates the ${key} consent param in user sync URL ${url}`, () => { + expect(urlParams.get(key)).to.equal(value); + }); + }; + }); + }); + + describe('when no consent data is set', () => { + const pixelObjects = spec.getUserSyncs( + syncOptions, + SERVER_RESPONSES, + undefined, + undefined, + undefined + ); + pixelObjects.forEach(pixelObject => { + let url = pixelObject.url; + let urlParams = new URL(url).searchParams; + const expectedParams = { + 'baz': 'true', + 'gdpr_consent': '', + 'gdpr': '0', + 'us_privacy': '', + 'gpp': '', + 'gpp_sid': '' + } + for (const [key, value] of Object.entries(expectedParams)) { + it(`Updates the ${key} consent param in user sync URL ${url}`, () => { + expect(urlParams.get(key)).to.equal(value); + }); + }; + }); + }); }); }); @@ -481,6 +561,23 @@ describe('YahooSSP Bid Adapter:', () => { }); }); + const VALID_PUBLISHER_OBJECTS = ['ext']; + VALID_PUBLISHER_OBJECTS.forEach(param => { + it(`should determine that the ortb2.site.publisher Object key is valid and append to the bid-request: ${JSON.stringify(param)}`, () => { + const ortb2 = { + site: { + publisher: { + [param]: {a: '123', b: '456'} + } + } + }; + const { validBidRequests, bidderRequest } = generateBuildRequestMock({ortb2}); + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + expect(data.site.publisher[param]).to.be.a('object'); + expect(data.site.publisher[param]).to.be.equal(ortb2.site.publisher[param]); + }); + }); + const VALID_CONTENT_ARRAYS = ['cat']; VALID_CONTENT_ARRAYS.forEach(param => { it(`should determine that the ortb2.site Array key is valid and append to the bid-request: ${JSON.stringify(param)}`, () => { @@ -512,7 +609,6 @@ describe('YahooSSP Bid Adapter:', () => { const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; expect(data.site.content[param]).to.be.a('object'); expect(data.site.content[param]).to.be.equal(ortb2.site.content[param]); - config.setConfig({ortb2: {}}); }); }); }); @@ -589,9 +685,9 @@ describe('YahooSSP Bid Adapter:', () => { }; const { validBidRequests, bidderRequest } = generateBuildRequestMock({ortb2}); const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; - expect(data.user[param]).to.be.a('object'); - expect(data.user[param]).to.be.deep.include({[param]: {a: '123', b: '456'}}); - config.setConfig({ortb2: {}}); + const user = data.user; + expect(user[param]).to.be.a('object'); + expect(user[param]).to.be.deep.include({[param]: {a: '123', b: '456'}}); }); }); @@ -611,12 +707,14 @@ describe('YahooSSP Bid Adapter:', () => { }; const { validBidRequests, bidderRequest } = generateBuildRequestMock({ortb2}); const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; - expect(data.user.data[0][param]).to.exist; - expect(data.user.data[0][param]).to.be.a('string'); - expect(data.user.data[0][param]).to.be.equal(ortb2.user.data[0][param]); - expect(data.site.content.data[0][param]).to.exist; - expect(data.site.content.data[0][param]).to.be.a('string'); - expect(data.site.content.data[0][param]).to.be.equal(ortb2.site.content.data[0][param]); + const user = data.user; + const site = data.site; + expect(user.data[0][param]).to.exist; + expect(user.data[0][param]).to.be.a('string'); + expect(user.data[0][param]).to.be.equal(ortb2.user.data[0][param]); + expect(site.content.data[0][param]).to.exist; + expect(site.content.data[0][param]).to.be.a('string'); + expect(site.content.data[0][param]).to.be.equal(ortb2.site.content.data[0][param]); }); }); @@ -630,9 +728,10 @@ describe('YahooSSP Bid Adapter:', () => { }; const { validBidRequests, bidderRequest } = generateBuildRequestMock({ortb2}); const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; - expect(data.user.data[0][param]).to.exist; - expect(data.user.data[0][param]).to.be.a('array'); - expect(data.user.data[0][param]).to.be.equal(ortb2.user.data[0][param]); + const user = data.user; + expect(user.data[0][param]).to.exist; + expect(user.data[0][param]).to.be.a('array'); + expect(user.data[0][param]).to.be.equal(ortb2.user.data[0][param]); }); }); @@ -646,10 +745,10 @@ describe('YahooSSP Bid Adapter:', () => { }; const { validBidRequests, bidderRequest } = generateBuildRequestMock({ortb2}); const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; - expect(data.user.data[0][param]).to.exist; - expect(data.user.data[0][param]).to.be.a('object'); - expect(data.user.data[0][param]).to.be.equal(ortb2.user.data[0][param]); - config.setConfig({ortb2: {}}); + const user = data.user; + expect(user.data[0][param]).to.exist; + expect(user.data[0][param]).to.be.a('object'); + expect(user.data[0][param]).to.be.equal(ortb2.user.data[0][param]); }); }); @@ -733,7 +832,15 @@ describe('YahooSSP Bid Adapter:', () => { expect(options.withCredentials).to.be.false; }); - it('adds the ortb2 gpp consent info to the request', function () { + it('set the GPP consent data from the data within the bid request', function () { + const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); + let clonedBidderRequest = {...bidderRequest}; + const data = spec.buildRequests(validBidRequests, clonedBidderRequest)[0].data; + expect(data.regs.ext.gpp).to.equal(bidderRequest.gppConsent.gppString); + expect(data.regs.ext.gpp_sid).to.eql(bidderRequest.gppConsent.applicableSections); + }); + + it('overrides the GPP consent data using data from the ortb2 config object', function () { const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); const ortb2 = { regs: { @@ -743,32 +850,39 @@ describe('YahooSSP Bid Adapter:', () => { }; let clonedBidderRequest = {...bidderRequest, ortb2}; const data = spec.buildRequests(validBidRequests, clonedBidderRequest)[0].data; - expect(data.regs.ext.gpp).to.equal('somegppstring'); - expect(data.regs.ext.gpp_sid).to.eql([6, 7]); + expect(data.regs.ext.gpp).to.equal(ortb2.regs.gpp); + expect(data.regs.ext.gpp_sid).to.eql(ortb2.regs.gpp_sid); }); }); describe('Endpoint & Impression Request Mode:', () => { - it('should route request to config override endpoint', () => { - const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); - const testOverrideEndpoint = 'http://foo.bar.baz.com/bidderRequest'; + afterEach(() => { config.setConfig({ - yahoossp: { - endpoint: testOverrideEndpoint + yahooAds: { + singleRequestMode: undefined } }); - const response = spec.buildRequests(validBidRequests, bidderRequest)[0]; - expect(response).to.deep.include( - { - method: 'POST', - url: testOverrideEndpoint - }); }); - it('should route request to /bidRequest endpoint when dcn & pos present', () => { - config.setConfig({ - yahoossp: {} + VALID_BIDDER_CODES.forEach(bidderCode => { + it(`should route request to config override endpoint for ${bidderCode} override config`, () => { + const { validBidRequests, bidderRequest } = generateBuildRequestMock({bidderCode}); + const testOverrideEndpoint = 'http://foo.bar.baz.com/bidderRequest'; + const cfg = {}; + cfg[bidderCode] = { + endpoint: testOverrideEndpoint + }; + config.setConfig(cfg); + const response = spec.buildRequests(validBidRequests, bidderRequest)[0]; + expect(response).to.deep.include( + { + method: 'POST', + url: testOverrideEndpoint + }); }); + }); + + it('should route request to /bidRequest endpoint when dcn & pos present', () => { const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); const response = spec.buildRequests(validBidRequests, bidderRequest); expect(response[0]).to.deep.include({ @@ -796,15 +910,15 @@ describe('YahooSSP Bid Adapter:', () => { bidderRequest.bids = validBidRequests; config.setConfig({ - yahoossp: { + yahooAds: { singleRequestMode: true } }); - const data = spec.buildRequests(validBidRequests, bidderRequest).data; - expect(data.imp).to.be.an('array').with.lengthOf(2); + const responsePayload = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + expect(responsePayload.imp).to.be.an('array').with.lengthOf(2); - expect(data.imp[0]).to.deep.include({ + expect(responsePayload.imp[0]).to.deep.include({ id: DEFAULT_BID_ID, ext: { pos: DEFAULT_BID_POS, @@ -812,7 +926,7 @@ describe('YahooSSP Bid Adapter:', () => { } }); - expect(data.imp[1]).to.deep.include({ + expect(responsePayload.imp[1]).to.deep.include({ id: BID_ID_2, ext: { pos: BID_POS_2, @@ -830,8 +944,9 @@ describe('YahooSSP Bid Adapter:', () => { it('buildRequests(): should return an array with the correct amount of request objects', () => { const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); - const response = spec.buildRequests(validBidRequests, bidderRequest).bidderRequest; - expect(response.bids).to.be.an('array').to.have.lengthOf(1); + const reqs = spec.buildRequests(validBidRequests, bidderRequest); + expect(reqs).to.be.an('array').to.have.lengthOf(1); + expect(reqs[0]).to.be.an('object').that.has.keys('method', 'url', 'data', 'options', 'bidderRequest'); }); }); @@ -839,7 +954,7 @@ describe('YahooSSP Bid Adapter:', () => { it('should return request objects with the relevant custom headers and content type declaration', () => { const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); bidderRequest.gdprConsent.gdprApplies = false; - const options = spec.buildRequests(validBidRequests, bidderRequest).options; + const options = spec.buildRequests(validBidRequests, bidderRequest)[0].options; expect(options).to.deep.equal( { contentType: 'application/json', @@ -855,6 +970,7 @@ describe('YahooSSP Bid Adapter:', () => { it('should set the allowed sources user eids', () => { const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); validBidRequests[0].userIdAsEids = createEidsArray({ + connectId: 'connectId_FROM_USER_ID_MODULE', admixerId: 'admixerId_FROM_USER_ID_MODULE', adtelligentId: 'adtelligentId_FROM_USER_ID_MODULE', amxId: 'amxId_FROM_USER_ID_MODULE', @@ -867,9 +983,10 @@ describe('YahooSSP Bid Adapter:', () => { criteoId: 'criteoId_FROM_USER_ID_MODULE', fabrickId: 'fabrickId_FROM_USER_ID_MODULE', }); - const data = spec.buildRequests(validBidRequests, bidderRequest).data; + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; expect(data.user.ext.eids).to.deep.equal([ + {source: 'yahoo.com', uids: [{id: 'connectId_FROM_USER_ID_MODULE', atype: 3}]}, {source: 'admixer.net', uids: [{id: 'admixerId_FROM_USER_ID_MODULE', atype: 3}]}, {source: 'adtelligent.com', uids: [{id: 'adtelligentId_FROM_USER_ID_MODULE', atype: 3}]}, {source: 'amxdt.net', uids: [{id: 'amxId_FROM_USER_ID_MODULE', atype: 1}]}, @@ -889,7 +1006,7 @@ describe('YahooSSP Bid Adapter:', () => { validBidRequests[0].userIdAsEids = createEidsArray({ justId: 'justId_FROM_USER_ID_MODULE' }); - const data = spec.buildRequests(validBidRequests, bidderRequest).data; + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; expect(data.user.ext.eids).to.deep.equal([]); }); @@ -898,7 +1015,7 @@ describe('YahooSSP Bid Adapter:', () => { describe('Request Payload oRTB bid validation:', () => { it('should generate a valid openRTB bid-request object in the data field', () => { const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); - const data = spec.buildRequests(validBidRequests, bidderRequest).data; + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; expect(data.site).to.deep.equal({ id: bidderRequest.bids[0].params.dcn, page: bidderRequest.refererInfo.page @@ -914,8 +1031,10 @@ describe('YahooSSP Bid Adapter:', () => { expect(data.regs).to.deep.equal({ ext: { - 'us_privacy': '', - gdpr: 1 + 'us_privacy': bidderRequest.uspConsent, + gdpr: 1, + gpp: bidderRequest.gppConsent.gppString, + gpp_sid: bidderRequest.gppConsent.applicableSections } }); @@ -945,7 +1064,7 @@ describe('YahooSSP Bid Adapter:', () => { it('should generate a valid openRTB imp.ext object in the bid-request', () => { const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); const bid = validBidRequests[0]; - const data = spec.buildRequests(validBidRequests, bidderRequest).data; + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; expect(data.imp[0].ext).to.deep.equal({ pos: bid.params.pos, dfp_ad_unit_code: DEFAULT_AD_UNIT_CODE @@ -955,24 +1074,63 @@ describe('YahooSSP Bid Adapter:', () => { it('should use siteId value as site.id in the outbound bid-request when using "pubId" integration mode', () => { let { validBidRequests, bidderRequest } = generateBuildRequestMock({pubIdMode: true}); validBidRequests[0].params.siteId = '1234567'; - const data = spec.buildRequests(validBidRequests, bidderRequest).data; + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; expect(data.site.id).to.equal('1234567'); }); + it('should use site publisher ortb2 config in default integration mode', () => { + const ortb2 = { + site: { + publisher: { + ext: { + publisherblob: 'pblob', + bucket: 'bucket' + } + } + } + } + let { validBidRequests, bidderRequest } = generateBuildRequestMock({ortb2}); + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + expect(data.site.publisher).to.deep.equal({ + ext: { + publisherblob: 'pblob', + bucket: 'bucket' + } + }); + }); + + it('should use site publisher ortb2 config when using "pubId" integration mode', () => { + const ortb2 = { + site: { + publisher: { + ext: { + publisherblob: 'pblob', + bucket: 'bucket' + } + } + } + } + let { validBidRequests, bidderRequest } = generateBuildRequestMock({pubIdMode: true, ortb2}); + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + expect(data.site.publisher).to.deep.equal({ + id: DEFAULT_PUBID, + ext: { + publisherblob: 'pblob', + bucket: 'bucket' + } + }); + }); + it('should use placementId value as imp.tagid in the outbound bid-request when using "pubId" integration mode', () => { let { validBidRequests, bidderRequest } = generateBuildRequestMock({pubIdMode: true}); validBidRequests[0].params.placementId = 'header-300x250'; - const data = spec.buildRequests(validBidRequests, bidderRequest).data; + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; expect(data.imp[0].tagid).to.deep.equal('header-300x250'); }); }); describe('Request Payload oRTB bid.imp validation:', () => { - // Validate Banner imp imp when yahoossp.mode=undefined - it('should generate a valid "Banner" imp object', () => { - config.setConfig({ - yahoossp: {} - }); + it('should generate a valid "Banner" imp object when mode config override is undefined', () => { const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; expect(data.imp[0].video).to.not.exist; @@ -982,74 +1140,82 @@ describe('YahooSSP Bid Adapter:', () => { }); }); - // Validate Banner imp when yahoossp.mode="banner" - it('should generate a valid "Banner" imp object', () => { - config.setConfig({ - yahoossp: { mode: 'banner' } - }); - const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); - const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; - expect(data.imp[0].video).to.not.exist; - expect(data.imp[0].banner).to.deep.equal({ - mimes: ['text/html', 'text/javascript', 'application/javascript', 'image/jpg'], - format: [{w: 300, h: 250}, {w: 300, h: 600}] + // Validate Banner imp when config value for mode="banner" + VALID_BIDDER_CODES.forEach(bidderCode => { + it(`should generate a valid "Banner" imp object for ${bidderCode} config override`, () => { + const cfg = {}; + cfg[bidderCode] = { + mode: BANNER + }; + config.setConfig(cfg); + const { validBidRequests, bidderRequest } = generateBuildRequestMock({bidderCode}); + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + expect(data.imp[0].video).to.not.exist; + expect(data.imp[0].banner).to.deep.equal({ + mimes: ['text/html', 'text/javascript', 'application/javascript', 'image/jpg'], + format: [{w: 300, h: 250}, {w: 300, h: 600}] + }); }); - }); - // Validate Video imp - it('should generate a valid "Video" only imp object', () => { - config.setConfig({ - yahoossp: { mode: 'video' } - }); - const { validBidRequests, bidderRequest } = generateBuildRequestMock({adUnitType: 'video'}); - const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; - expect(data.imp[0].banner).to.not.exist; - expect(data.imp[0].video).to.deep.equal({ - mimes: ['video/mp4', 'application/javascript'], - w: 300, - h: 250, - api: [2], - protocols: [2, 5], - startdelay: 0, - linearity: 1, - maxbitrate: undefined, - maxduration: undefined, - minduration: undefined, - delivery: undefined, - pos: undefined, - playbackmethod: undefined, - rewarded: undefined, - placement: undefined + // Validate Video imp + it(`should generate a valid "Video" only imp object for ${bidderCode} config override`, () => { + const cfg = {}; + cfg[bidderCode] = { + mode: VIDEO + }; + config.setConfig(cfg); + const { validBidRequests, bidderRequest } = generateBuildRequestMock({bidderCode, adUnitType: 'video'}); + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + expect(data.imp[0].banner).to.not.exist; + expect(data.imp[0].video).to.deep.equal({ + mimes: ['video/mp4', 'application/javascript'], + w: 300, + h: 250, + api: [2], + protocols: [2, 5], + startdelay: 0, + linearity: 1, + maxbitrate: undefined, + maxduration: undefined, + minduration: undefined, + delivery: undefined, + pos: undefined, + playbackmethod: undefined, + rewarded: undefined, + placement: undefined + }); }); - }); - // Validate multi-format Video+banner imp - it('should generate a valid multi-format "Video + Banner" imp object', () => { - config.setConfig({ - yahoossp: { mode: 'all' } - }); - const { validBidRequests, bidderRequest } = generateBuildRequestMock({adUnitType: 'multi-format'}); - const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; - expect(data.imp[0].banner).to.deep.equal({ - mimes: ['text/html', 'text/javascript', 'application/javascript', 'image/jpg'], - format: [{w: 300, h: 250}, {w: 300, h: 600}] - }); - expect(data.imp[0].video).to.deep.equal({ - mimes: ['video/mp4', 'application/javascript'], - w: 300, - h: 250, - api: [2], - protocols: [2, 5], - startdelay: 0, - linearity: 1, - maxbitrate: undefined, - maxduration: undefined, - minduration: undefined, - delivery: undefined, - pos: undefined, - playbackmethod: undefined, - rewarded: undefined, - placement: undefined + // Validate multi-format Video+banner imp + it(`should generate a valid multi-format "Video + Banner" imp object for ${bidderCode} config override`, () => { + const cfg = {}; + cfg[bidderCode] = { + mode: 'all' + }; + config.setConfig(cfg); + const { validBidRequests, bidderRequest } = generateBuildRequestMock({bidderCode, adUnitType: 'multi-format'}); + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + expect(data.imp[0].banner).to.deep.equal({ + mimes: ['text/html', 'text/javascript', 'application/javascript', 'image/jpg'], + format: [{w: 300, h: 250}, {w: 300, h: 600}] + }); + expect(data.imp[0].video).to.deep.equal({ + mimes: ['video/mp4', 'application/javascript'], + w: 300, + h: 250, + api: [2], + protocols: [2, 5], + startdelay: 0, + linearity: 1, + maxbitrate: undefined, + maxduration: undefined, + minduration: undefined, + delivery: undefined, + pos: undefined, + playbackmethod: undefined, + rewarded: undefined, + placement: undefined + }); }); }); @@ -1068,7 +1234,6 @@ describe('YahooSSP Bid Adapter:', () => { invalidKey5: undefined }; const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; - expect(data.imp[0].ext.kvs).to.deep.equal({ key1: 'String', key2: 123456, @@ -1081,10 +1246,6 @@ describe('YahooSSP Bid Adapter:', () => { describe('Multiple adUnit validations:', () => { // Multiple banner adUnits it('should generate multiple bid-requests for each adUnit - 2 banner only', () => { - config.setConfig({ - yahoossp: { mode: 'banner' } - }); - const BID_ID_2 = '84ab50xxxxx'; const BID_POS_2 = 'footer'; const AD_UNIT_CODE_2 = 'test-ad-unit-code-123'; @@ -1098,12 +1259,12 @@ describe('YahooSSP Bid Adapter:', () => { validBidRequests = [bidRequest, bidRequest2, bidRequest3]; bidderRequest.bids = validBidRequests; - const response = spec.buildRequests(validBidRequests, bidderRequest) - expect(response).to.be.a('array'); - expect(response.length).to.equal(2); - response.forEach((obj) => { - expect(obj.data.imp[0].video).to.not.exist - expect(obj.data.imp[0].banner).to.deep.equal({ + const reqs = spec.buildRequests(validBidRequests, bidderRequest) + expect(reqs).to.be.a('array'); + expect(reqs.length).to.equal(2); + reqs.forEach(req => { + expect(req.data.imp[0].video).to.not.exist + expect(req.data.imp[0].banner).to.deep.equal({ mimes: ['text/html', 'text/javascript', 'application/javascript', 'image/jpg'], format: [{w: 300, h: 250}, {w: 300, h: 600}] }); @@ -1112,9 +1273,11 @@ describe('YahooSSP Bid Adapter:', () => { // Multiple video adUnits it('should generate multiple bid-requests for each adUnit - 2 video only', () => { - config.setConfig({ - yahoossp: { mode: 'video' } - }); + const cfg = {}; + cfg[DEFAULT_BIDDER_CODE] = { + mode: VIDEO + }; + config.setConfig(cfg); const BID_ID_2 = '84ab50xxxxx'; const BID_POS_2 = 'footer'; const AD_UNIT_CODE_2 = 'test-ad-unit-code-123'; @@ -1122,18 +1285,18 @@ describe('YahooSSP Bid Adapter:', () => { const BID_POS_3 = 'hero'; const AD_UNIT_CODE_3 = 'video-ad-unit'; - let { bidRequest, validBidRequests, bidderRequest } = generateBuildRequestMock({adUnitType: 'video'}); // video - const { bidRequest: bidRequest2 } = generateBuildRequestMock({bidId: BID_ID_2, pos: BID_POS_2, adUnitCode: AD_UNIT_CODE_2, adUnitType: 'video'}); // video - const { bidRequest: bidRequest3 } = generateBuildRequestMock({bidId: BID_ID_3, pos: BID_POS_3, adUnitCode: AD_UNIT_CODE_3}); // banner (should be filtered) + let {bidRequest, validBidRequests, bidderRequest} = generateBuildRequestMock({adUnitType: 'video'}); // video + const {bidRequest: bidRequest2} = generateBuildRequestMock({bidId: BID_ID_2, pos: BID_POS_2, adUnitCode: AD_UNIT_CODE_2, adUnitType: 'video'}); // video + const {bidRequest: bidRequest3} = generateBuildRequestMock({bidId: BID_ID_3, pos: BID_POS_3, adUnitCode: AD_UNIT_CODE_3}); // banner (should be filtered) validBidRequests = [bidRequest, bidRequest2, bidRequest3]; bidderRequest.bids = validBidRequests; - const response = spec.buildRequests(validBidRequests, bidderRequest) - expect(response).to.be.a('array'); - expect(response.length).to.equal(2); - response.forEach((obj) => { - expect(obj.data.imp[0].banner).to.not.exist - expect(obj.data.imp[0].video).to.deep.equal({ + const reqs = spec.buildRequests(validBidRequests, bidderRequest) + expect(reqs).to.be.a('array'); + expect(reqs.length).to.equal(2); + reqs.forEach(req => { + expect(req.data.imp[0].banner).to.not.exist + expect(req.data.imp[0].video).to.deep.equal({ mimes: ['video/mp4', 'application/javascript'], w: 300, h: 250, @@ -1154,9 +1317,9 @@ describe('YahooSSP Bid Adapter:', () => { }); // Mixed adUnits 1-banner, 1-video, 1-native (should filter out native) it('should generate multiple bid-requests for both "video & banner" adUnits', () => { - config.setConfig({ - yahoossp: { mode: 'all' } - }); + const cfg = {}; + cfg[DEFAULT_BIDDER_CODE] = { mode: 'all' }; + config.setConfig(cfg); const BID_ID_2 = '84ab50xxxxx'; const BID_POS_2 = 'footer'; const AD_UNIT_CODE_2 = 'video-ad-unit'; @@ -1170,21 +1333,21 @@ describe('YahooSSP Bid Adapter:', () => { validBidRequests = [bidRequest, bidRequest2, bidRequest3]; bidderRequest.bids = validBidRequests; - const response = spec.buildRequests(validBidRequests, bidderRequest); - expect(response).to.be.a('array'); - expect(response.length).to.equal(2); - response.forEach((obj) => { - expect(obj.data.imp[0].native).to.not.exist; + const reqs = spec.buildRequests(validBidRequests, bidderRequest); + expect(reqs).to.be.a('array'); + expect(reqs.length).to.equal(2); + reqs.forEach(req => { + expect(req.data.imp[0].native).to.not.exist; }); - const data1 = response[0].data; + const data1 = reqs[0].data; expect(data1.imp[0].video).to.not.exist; expect(data1.imp[0].banner).to.deep.equal({ mimes: ['text/html', 'text/javascript', 'application/javascript', 'image/jpg'], format: [{w: 300, h: 250}, {w: 300, h: 600}] }); - const data2 = response[1].data; + const data2 = reqs[1].data; expect(data2.imp[0].banner).to.not.exist; expect(data2.imp[0].video).to.deep.equal({ mimes: ['video/mp4', 'application/javascript'], @@ -1207,90 +1370,98 @@ describe('YahooSSP Bid Adapter:', () => { }); describe('Video params firstlook & bidOverride validations:', () => { - it('should first look at params.bidOverride for video placement data', () => { - config.setConfig({ - yahoossp: { mode: 'video' } - }); - const bidOverride = { - imp: { - video: { - mimes: ['video/mp4'], - w: 400, - h: 350, - api: [1], - protocols: [1, 3], - startdelay: 0, - linearity: 1, - maxbitrate: 400000, - maxduration: 3600, - minduration: 1500, - delivery: 1, - pos: 123456, - playbackmethod: 1, - rewarded: 1, - placement: 1 + VALID_BIDDER_CODES.forEach(bidderCode => { + it(`should first look at params.bidOverride for video placement data for ${bidderCode} config override`, () => { + const cfg = {}; + cfg[bidderCode] = { + mode: VIDEO + }; + config.setConfig(cfg); + const bidOverride = { + imp: { + video: { + mimes: ['video/mp4'], + w: 400, + h: 350, + api: [1], + protocols: [1, 3], + startdelay: 0, + linearity: 1, + maxbitrate: 400000, + maxduration: 3600, + minduration: 1500, + delivery: 1, + pos: 123456, + playbackmethod: 1, + rewarded: 1, + placement: 1 + } } } - } - const { validBidRequests, bidderRequest } = generateBuildRequestMock({adUnitType: 'video', bidOverrideObject: bidOverride}); - const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; - expect(data.imp[0].video).to.deep.equal(bidOverride.imp.video); - }); - - it('should second look at bid.mediaTypes.video for video placement data', () => { - config.setConfig({ - yahoossp: { mode: 'video' } - }); - let { bidRequest, bidderRequest } = generateBuildRequestMock({adUnitType: 'video'}); - bidRequest.mediaTypes.video = { - mimes: ['video/mp4'], - playerSize: [400, 350], - api: [1], - protocols: [1, 3], - startdelay: 0, - linearity: 1, - maxbitrate: 400000, - maxduration: 3600, - minduration: 1500, - delivery: 1, - pos: 123456, - playbackmethod: 1, - placement: 1 - } - const validBidRequests = [bidRequest]; - bidderRequest.bids = validBidRequests; - const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; - expect(data.imp[0].video).to.deep.equal({ - mimes: ['video/mp4'], - w: 400, - h: 350, - api: [1], - protocols: [1, 3], - startdelay: 0, - linearity: 1, - maxbitrate: 400000, - maxduration: 3600, - minduration: 1500, - delivery: 1, - pos: 123456, - playbackmethod: 1, - placement: 1, - rewarded: undefined + const { validBidRequests, bidderRequest } = generateBuildRequestMock({bidderCode, adUnitType: 'video', bidOverrideObject: bidOverride}); + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + expect(data.imp[0].video).to.deep.equal(bidOverride.imp.video); }); - }); - it('should use params.bidOverride.device.ip override', () => { - config.setConfig({ - yahoossp: { mode: 'all' } + it(`should second look at bid.mediaTypes.video for video placement data for ${bidderCode} config override`, () => { + const cfg = {}; + cfg[bidderCode] = { + mode: VIDEO + }; + config.setConfig(cfg); + let { bidRequest, bidderRequest } = generateBuildRequestMock({bidderCode, adUnitType: 'video'}); + bidRequest.mediaTypes.video = { + mimes: ['video/mp4'], + playerSize: [400, 350], + api: [1], + protocols: [1, 3], + startdelay: 0, + linearity: 1, + maxbitrate: 400000, + maxduration: 3600, + minduration: 1500, + delivery: 1, + pos: 123456, + playbackmethod: 1, + placement: 1 + } + const validBidRequests = [bidRequest]; + bidderRequest.bids = validBidRequests; + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + expect(data.imp[0].video).to.deep.equal({ + mimes: ['video/mp4'], + w: 400, + h: 350, + api: [1], + protocols: [1, 3], + startdelay: 0, + linearity: 1, + maxbitrate: 400000, + maxduration: 3600, + minduration: 1500, + delivery: 1, + pos: 123456, + playbackmethod: 1, + placement: 1, + rewarded: undefined + }); }); - const bidOverride = { - device: { - ip: '1.2.3.4' + + it(`should use params.bidOverride.device.ip override for ${bidderCode} config override`, () => { + const cfg = {}; + cfg[bidderCode] = { + mode: 'all' + }; + config.setConfig(cfg); + const bidOverride = { + device: { + ip: '1.2.3.4' + } } - } - const { validBidRequests, bidderRequest } = generateBuildRequestMock({adUnitType: 'video', bidOverrideObject: bidOverride}); - const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; - expect(data.device.ip).to.deep.equal(bidOverride.device.ip); + const { validBidRequests, bidderRequest } = generateBuildRequestMock({bidderCode, adUnitType: 'video', bidOverrideObject: bidOverride}); + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + expect(data.device.ip).to.deep.equal(bidOverride.device.ip); + }); }); }); // #endregion buildRequests(): @@ -1306,6 +1477,22 @@ describe('YahooSSP Bid Adapter:', () => { }); describe('for mediaTypes: "video"', () => { + beforeEach(() => { + config.setConfig({ + yahooAds: { + mode: VIDEO + } + }); + }); + + afterEach(() => { + config.setConfig({ + yahooAds: { + mode: undefined + } + }); + }); + it('should insert video VPAID payload into vastXml', () => { const { serverResponse, bidderRequest } = generateResponseMock('video'); const response = spec.interpretResponse(serverResponse, {bidderRequest}); @@ -1323,28 +1510,38 @@ describe('YahooSSP Bid Adapter:', () => { expect(response[0].mediaType).to.equal('video'); }) - it('should insert video DAP O2 Player into ad', () => { - const { serverResponse, bidderRequest } = generateResponseMock('dap-o2', 'vpaid'); - const response = spec.interpretResponse(serverResponse, {bidderRequest}); - expect(response[0].ad).to.equal(''); - expect(response[0].vastUrl).to.be.undefined; - expect(response[0].vastXml).to.be.undefined; - expect(response[0].mediaType).to.equal('banner'); - }); + describe('wrapped in video players for display inventory', () => { + beforeEach(() => { + config.setConfig({ + yahooAds: { + mode: undefined + } + }); + }); - it('should insert video DAP Unified Player into ad', () => { - const { serverResponse, bidderRequest } = generateResponseMock('dap-up', 'vpaid'); - const response = spec.interpretResponse(serverResponse, {bidderRequest}); - expect(response[0].ad).to.equal(''); - expect(response[0].vastUrl).to.be.undefined; - expect(response[0].vastXml).to.be.undefined; - expect(response[0].mediaType).to.equal('banner'); - }) + it('should insert video DAP O2 Player into ad', () => { + const { serverResponse, bidderRequest } = generateResponseMock('dap-o2', 'vpaid'); + const response = spec.interpretResponse(serverResponse, {bidderRequest}); + expect(response[0].ad).to.equal(''); + expect(response[0].vastUrl).to.be.undefined; + expect(response[0].vastXml).to.be.undefined; + expect(response[0].mediaType).to.equal('banner'); + }); + + it('should insert video DAP Unified Player into ad', () => { + const { serverResponse, bidderRequest } = generateResponseMock('dap-up', 'vpaid'); + const response = spec.interpretResponse(serverResponse, {bidderRequest}); + expect(response[0].ad).to.equal(''); + expect(response[0].vastUrl).to.be.undefined; + expect(response[0].vastXml).to.be.undefined; + expect(response[0].mediaType).to.equal('banner'); + }) + }); }); describe('Support Advertiser domains', () => { it('should append bid-response adomain to meta.advertiserDomains', () => { - const { serverResponse, bidderRequest } = generateResponseMock('video', 'vpaid'); + const { serverResponse, bidderRequest } = generateResponseMock('banner'); const response = spec.interpretResponse(serverResponse, {bidderRequest}); expect(response[0].meta.advertiserDomains).to.be.a('array'); expect(response[0].meta.advertiserDomains[0]).to.equal('advertiser-domain.com'); @@ -1379,52 +1576,54 @@ describe('YahooSSP Bid Adapter:', () => { }); describe('Time To Live (ttl)', () => { - const UNSUPPORTED_TTL_FORMATS = ['string', [1, 2, 3], true, false, null, undefined]; - UNSUPPORTED_TTL_FORMATS.forEach(param => { - it('should not allow unsupported global yahoossp.ttl formats and default to 300', () => { - const { serverResponse, bidderRequest } = generateResponseMock('banner'); - config.setConfig({ - yahoossp: { ttl: param } + VALID_BIDDER_CODES.forEach(bidderCode => { + const UNSUPPORTED_TTL_FORMATS = ['string', [1, 2, 3], true, false, null, undefined]; + UNSUPPORTED_TTL_FORMATS.forEach(param => { + it(`should not allow unsupported global ${bidderCode}.ttl formats and default to 300`, () => { + const { serverResponse, bidderRequest } = generateResponseMock('banner'); + const cfg = {}; + cfg['yahooAds'] = { ttl: param }; + config.setConfig(cfg); + const response = spec.interpretResponse(serverResponse, {bidderRequest}); + expect(response[0].ttl).to.equal(300); }); - const response = spec.interpretResponse(serverResponse, {bidderRequest}); - expect(response[0].ttl).to.equal(300); - }); - it('should not allow unsupported params.ttl formats and default to 300', () => { - const { serverResponse, bidderRequest } = generateResponseMock('banner'); - bidderRequest.bids[0].params.ttl = param; - const response = spec.interpretResponse(serverResponse, {bidderRequest}); - expect(response[0].ttl).to.equal(300); + it('should not allow unsupported params.ttl formats and default to 300', () => { + const { serverResponse, bidderRequest } = generateResponseMock('banner'); + bidderRequest.bids[0].params.ttl = param; + const response = spec.interpretResponse(serverResponse, {bidderRequest}); + expect(response[0].ttl).to.equal(300); + }); }); - }); - const UNSUPPORTED_TTL_VALUES = [-1, 3601]; - UNSUPPORTED_TTL_VALUES.forEach(param => { - it('should not allow invalid global yahoossp.ttl values 3600 < ttl < 0 and default to 300', () => { - const { serverResponse, bidderRequest } = generateResponseMock('banner'); - config.setConfig({ - yahoossp: { ttl: param } + const UNSUPPORTED_TTL_VALUES = [-1, 3601]; + UNSUPPORTED_TTL_VALUES.forEach(param => { + it('should not allow invalid global config ttl values 3600 < ttl < 0 and default to 300', () => { + const { serverResponse, bidderRequest } = generateResponseMock('banner'); + config.setConfig({ + yahooAds: { ttl: param } + }); + const response = spec.interpretResponse(serverResponse, {bidderRequest}); + expect(response[0].ttl).to.equal(300); + }); + + it('should not allow invalid params.ttl values 3600 < ttl < 0 and default to 300', () => { + const { serverResponse, bidderRequest } = generateResponseMock('banner'); + bidderRequest.bids[0].params.ttl = param; + const response = spec.interpretResponse(serverResponse, {bidderRequest}); + expect(response[0].ttl).to.equal(300); }); - const response = spec.interpretResponse(serverResponse, {bidderRequest}); - expect(response[0].ttl).to.equal(300); }); - it('should not allow invalid params.ttl values 3600 < ttl < 0 and default to 300', () => { + it('should give presedence to Gloabl ttl over params.ttl ', () => { const { serverResponse, bidderRequest } = generateResponseMock('banner'); - bidderRequest.bids[0].params.ttl = param; + config.setConfig({ + yahooAds: { ttl: 500 } + }); + bidderRequest.bids[0].params.ttl = 400; const response = spec.interpretResponse(serverResponse, {bidderRequest}); - expect(response[0].ttl).to.equal(300); - }); - }); - - it('should give presedence to Gloabl ttl over params.ttl ', () => { - const { serverResponse, bidderRequest } = generateResponseMock('banner'); - config.setConfig({ - yahoossp: { ttl: 500 } + expect(response[0].ttl).to.equal(500); }); - bidderRequest.bids[0].params.ttl = 400; - const response = spec.interpretResponse(serverResponse, {bidderRequest}); - expect(response[0].ttl).to.equal(500); }); }); diff --git a/test/spec/modules/yandexBidAdapter_spec.js b/test/spec/modules/yandexBidAdapter_spec.js index df91100b966..12d413a9c93 100644 --- a/test/spec/modules/yandexBidAdapter_spec.js +++ b/test/spec/modules/yandexBidAdapter_spec.js @@ -1,34 +1,10 @@ import { assert, expect } from 'chai'; -import { spec } from 'modules/yandexBidAdapter.js'; +import { spec, NATIVE_ASSETS } from 'modules/yandexBidAdapter.js'; import { parseUrl } from 'src/utils.js'; -import { BANNER } from '../../../src/mediaTypes'; +import { BANNER, NATIVE } from '../../../src/mediaTypes'; +import {OPENRTB} from '../../../modules/rtbhouseBidAdapter'; describe('Yandex adapter', function () { - function getBidConfig() { - return { - bidder: 'yandex', - params: { - placementId: '123-1', - }, - }; - } - - function getBidRequest() { - return { - ...getBidConfig(), - bidId: 'bidid-1', - adUnitCode: 'adUnit-123', - mediaTypes: { - banner: { - sizes: [ - [300, 250], - [300, 600] - ], - }, - }, - }; - } - describe('isBidRequestValid', function () { it('should return true when required params found', function () { const bid = getBidRequest(); @@ -65,19 +41,17 @@ describe('Yandex adapter', function () { }); describe('buildRequests', function () { - const gdprConsent = { - gdprApplies: 1, - consentString: 'concent-string', - apiVersion: 1, - }; - const bidderRequest = { refererInfo: { domain: 'ya.ru', ref: 'https://ya.ru/', page: 'https://ya.ru/', }, - gdprConsent + gdprConsent: { + gdprApplies: 1, + consentString: 'concent-string', + apiVersion: 1, + }, }; it('creates a valid banner request', function () { @@ -101,7 +75,7 @@ describe('Yandex adapter', function () { const { search: query } = parsedRequestUrl expect(parsedRequestUrl.hostname).to.equal('bs.yandex.ru'); - expect(parsedRequestUrl.pathname).to.equal('/metadsp/123'); + expect(parsedRequestUrl.pathname).to.equal('/prebid/123'); expect(query['imp-id']).to.equal('1'); expect(query['target-ref']).to.equal('ya.ru'); @@ -112,21 +86,197 @@ describe('Yandex adapter', function () { expect(request.data).to.exist; expect(data.site).to.not.equal(null); - expect(data.site.page_url).to.equal('https://ya.ru/'); - expect(data.site.ref_url).to.equal('https://ya.ru/'); + expect(data.site.page).to.equal('https://ya.ru/'); + expect(data.site.ref).to.equal('https://ya.ru/'); + }); + + describe('banner', () => { + it('should create valid banner object', () => { + const bannerRequest = getBidRequest({ + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 600] + ], + }, + } + }); - // expect(data.device).to.not.equal(null); - // expect(data.device.w).to.equal(window.innerWidth); - // expect(data.device.h).to.equal(window.innerHeight); + const requests = spec.buildRequests([bannerRequest], bidderRequest); + expect(requests[0].data.imp).to.have.lengthOf(1); - expect(data.imp).to.have.lengthOf(1); - expect(data.imp[0].banner).to.not.equal(null); - expect(data.imp[0].banner.w).to.equal(300); - expect(data.imp[0].banner.h).to.equal(250); + const imp = requests[0].data.imp[0]; + expect(imp.banner).to.not.equal(null); + expect(imp.banner.w).to.equal(300); + expect(imp.banner.h).to.equal(250); + + expect(imp.banner.format).to.deep.equal([ + { w: 300, h: 250 }, + { w: 300, h: 600 }, + ]); + }); }); + + describe('native', () => { + function buildRequestAndGetNativeParams(extra) { + const bannerRequest = getBidRequest(extra); + const requests = spec.buildRequests([bannerRequest], bidderRequest); + + return JSON.parse(requests[0].data.imp[0].native.request); + } + + it('should extract native params', () => { + const nativeParams = buildRequestAndGetNativeParams({ + mediaTypes: { + native: { + title: { + required: true, + len: 100, + }, + body: { + len: 90 + }, + body2: { + len: 90 + }, + sponsoredBy: { + len: 25, + }, + icon: { + sizes: [32, 32], + }, + image: { + required: true, + sizes: [300, 250], + }, + }, + }, + }); + const sortedAssetsList = nativeParams.assets.sort((a, b) => a.id - b.id); + + expect(sortedAssetsList).to.deep.equal([ + { + id: NATIVE_ASSETS.title[0], + required: 1, + title: { + len: 100, + } + }, + { + id: NATIVE_ASSETS.body[0], + data: { + type: NATIVE_ASSETS.body[1], + len: 90, + }, + }, + { + id: NATIVE_ASSETS.body2[0], + data: { + type: NATIVE_ASSETS.body2[1], + len: 90, + }, + }, + { + id: NATIVE_ASSETS.sponsoredBy[0], + data: { + type: NATIVE_ASSETS.sponsoredBy[1], + len: 25, + }, + }, + { + id: NATIVE_ASSETS.icon[0], + img: { + type: NATIVE_ASSETS.icon[1], + w: 32, + h: 32, + }, + }, + { + id: NATIVE_ASSETS.image[0], + required: 1, + img: { + type: NATIVE_ASSETS.image[1], + w: 300, + h: 250, + }, + }, + ]); + }); + + it('should parse multiple image sizes', () => { + const nativeParams = buildRequestAndGetNativeParams({ + mediaTypes: { + native: { + image: { + sizes: [[300, 250], [100, 100]], + }, + }, + }, + }); + + expect(nativeParams.assets[0]).to.deep.equal({ + id: NATIVE_ASSETS.image[0], + img: { + type: NATIVE_ASSETS.image[1], + w: 300, + h: 250, + }, + }); + }); + + it('should parse aspect ratios with min_width', () => { + const nativeParams = buildRequestAndGetNativeParams({ + mediaTypes: { + native: { + image: { + aspect_ratios: [{ + min_width: 320, + ratio_width: 4, + ratio_height: 3, + }], + }, + }, + }, + }); + + expect(nativeParams.assets[0]).to.deep.equal({ + id: NATIVE_ASSETS.image[0], + img: { + type: NATIVE_ASSETS.image[1], + wmin: 320, + hmin: 240, + }, + }); + }); + + it('should parse aspect ratios without min_width', () => { + const nativeParams = buildRequestAndGetNativeParams({ + mediaTypes: { + native: { + image: { + aspect_ratios: [{ + ratio_width: 4, + ratio_height: 3, + }], + }, + }, + }, + }); + + expect(nativeParams.assets[0]).to.deep.equal({ + id: NATIVE_ASSETS.image[0], + img: { + type: NATIVE_ASSETS.image[1], + wmin: 100, + hmin: 75, + }, + }); + }); + }) }); - describe('response handler', function () { + describe('interpretResponse', function () { const bannerRequest = getBidRequest(); const bannerResponse = { @@ -172,5 +322,125 @@ describe('Yandex adapter', function () { expect(rtbBid.meta.advertiserDomains).to.deep.equal(['example.com']); }); + + describe('native', () => { + function getNativeAdmResponse() { + return { + native: { + link: { + url: 'https://example.com' + }, + imptrackers: [ + 'https://example.com/imptracker' + ], + assets: [ + { + title: { + text: 'title text', + }, + id: NATIVE_ASSETS.title[0], + }, + { + data: { + value: 'body text' + }, + id: NATIVE_ASSETS.body[0], + }, + { + data: { + value: 'sponsoredBy text' + }, + id: NATIVE_ASSETS.sponsoredBy[0], + }, + { + img: { + url: 'https://example.com/image', + w: 200, + h: 150, + }, + id: NATIVE_ASSETS.image[0], + }, + { + img: { + url: 'https://example.com/icon', + h: 32, + w: 32 + }, + id: NATIVE_ASSETS.icon[0], + }, + ] + } + }; + } + + it('handles native responses', function() { + bannerRequest.bidRequest = { + mediaType: NATIVE, + bidId: 'bidid-1', + }; + + const nativeAdmResponce = getNativeAdmResponse(); + const bannerResponse = { + body: { + seatbid: [{ + bid: [ + { + impid: 1, + price: 0.3, + adomain: [ + 'example.com' + ], + adid: 'yabs.123=', + adm: JSON.stringify(nativeAdmResponce), + }, + ], + }], + }, + }; + + const result = spec.interpretResponse(bannerResponse, bannerRequest); + + expect(result).to.have.lengthOf(1); + expect(result[0]).to.exist; + + const bid = result[0]; + expect(bid.meta.advertiserDomains).to.deep.equal(['example.com']); + expect(bid.native).to.deep.equal({ + clickUrl: 'https://example.com', + impressionTrackers: ['https://example.com/imptracker'], + title: 'title text', + body: 'body text', + sponsoredBy: 'sponsoredBy text', + image: { + url: 'https://example.com/image', + width: 200, + height: 150, + }, + icon: { + url: 'https://example.com/icon', + width: 32, + height: 32, + }, + }); + }); + }); }); }); + +function getBidConfig() { + return { + bidder: 'yandex', + params: { + placementId: '123-1', + }, + }; +} + +function getBidRequest(extra = {}) { + return { + ...getBidConfig(), + bidId: 'bidid-1', + adUnitCode: 'adUnit-123', + ...extra, + }; +} diff --git a/test/spec/modules/yieldlabBidAdapter_spec.js b/test/spec/modules/yieldlabBidAdapter_spec.js index e5151cf789c..93c231c816b 100644 --- a/test/spec/modules/yieldlabBidAdapter_spec.js +++ b/test/spec/modules/yieldlabBidAdapter_spec.js @@ -191,6 +191,7 @@ const NATIVE_RESPONSE = Object.assign({}, RESPONSE, { url: 'https://localhost:8080/yl-logo100x100.jpg', w: 100, h: 100, + type: 3, }, }, { @@ -199,6 +200,15 @@ const NATIVE_RESPONSE = Object.assign({}, RESPONSE, { value: 'Native body value', }, }, + { + id: 4, + img: { + url: 'https://localhost:8080/assets/favicon/favicon-16x16.png', + w: 16, + h: 16, + type: 1, + }, + }, ], imptrackers: [ 'http://localhost:8080/ve?d=ODE9ZSY2MTI1MjAzNjMzMzYxPXN0JjA0NWUwZDk0NTY5Yi05M2FiLWUwZTQtOWFjNy1hYWY0MzFiZj1kaXQmMj12', @@ -557,7 +567,6 @@ describe('yieldlabBidAdapter', () => { it('should add adUrl and native assets when type is Native', () => { const result = spec.interpretResponse({body: [NATIVE_RESPONSE]}, {validBidRequests: [NATIVE_REQUEST()], queryParams: REQPARAMS}); - expect(result[0].requestId).to.equal('2d925f27f5079f'); expect(result[0].cpm).to.equal(0.01); expect(result[0].mediaType).to.equal('native'); @@ -567,8 +576,24 @@ describe('yieldlabBidAdapter', () => { expect(result[0].native.image.url).to.equal('https://localhost:8080/yl-logo100x100.jpg'); expect(result[0].native.image.width).to.equal(100); expect(result[0].native.image.height).to.equal(100); + expect(result[0].native.icon.url).to.equal('https://localhost:8080/assets/favicon/favicon-16x16.png'); + expect(result[0].native.icon.width).to.equal(16); + expect(result[0].native.icon.height).to.equal(16); expect(result[0].native.clickUrl).to.equal('https://www.yieldlab.de'); expect(result[0].native.impressionTrackers.length).to.equal(3); + expect(result[0].native.assets.length).to.equal(4); + const titleAsset = result[0].native.assets.find(asset => 'title' in asset); + const imageAsset = result[0].native.assets.find((asset) => { + return asset?.img?.type === 3; + }); + const iconAsset = result[0].native.assets.find((asset) => { + return asset?.img?.type === 1; + }); + const bodyAsset = result[0].native.assets.find(asset => 'data' in asset); + expect(titleAsset).to.exist.and.to.have.nested.property('id', 1) + expect(imageAsset).to.exist.and.to.have.nested.property('id', 2) + expect(bodyAsset).to.exist.and.to.have.nested.property('id', 3) + expect(iconAsset).to.exist.and.to.have.nested.property('id', 4) }); it('should add adUrl and default native assets when type is Native', () => { @@ -594,6 +619,28 @@ describe('yieldlabBidAdapter', () => { expect(result[0].native.image.height).to.equal(0); }); + it('should not add icon if not present in the native response', () => { + const NATIVE_RESPONSE_WITHOUT_ICON = Object.assign({}, NATIVE_RESPONSE, { + native: { + link: { + url: 'https://www.yieldlab.de', + }, + assets: [ + { + id: 1, + title: { + text: 'This is a great headline', + } + } + ], + imptrackers: [], + }, + }); + const result = spec.interpretResponse({body: [NATIVE_RESPONSE_WITHOUT_ICON]}, {validBidRequests: [NATIVE_REQUEST()], queryParams: REQPARAMS}); + expect(result[0].native.hasOwnProperty('icon')).to.be.false; + expect(result[0].native.title).to.equal('This is a great headline'); + }); + it('should append gdpr parameters to vastUrl', () => { const result = spec.interpretResponse({body: [VIDEO_RESPONSE]}, {validBidRequests: [VIDEO_REQUEST()], queryParams: REQPARAMS_GDPR}); diff --git a/test/spec/modules/yieldmoBidAdapter_spec.js b/test/spec/modules/yieldmoBidAdapter_spec.js index 4123422ce6e..3706f770da8 100644 --- a/test/spec/modules/yieldmoBidAdapter_spec.js +++ b/test/spec/modules/yieldmoBidAdapter_spec.js @@ -212,7 +212,7 @@ describe('YieldmoAdapter', function () { expect(data.hasOwnProperty('h')).to.be.true; expect(data.hasOwnProperty('w')).to.be.true; expect(data.hasOwnProperty('pubcid')).to.be.true; - expect(data.userConsent).to.equal('{"gdprApplies":"","cmp":""}'); + expect(data.userConsent).to.equal('{"gdprApplies":"","cmp":"","gpp":"","gpp_sid":[]}'); expect(data.us_privacy).to.equal(''); }); @@ -262,6 +262,24 @@ describe('YieldmoAdapter', function () { JSON.stringify({ gdprApplies: true, cmp: 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==', + gpp: '', + gpp_sid: [], + }) + ); + }); + + it('should add gpp information to request if available', () => { + const gppConsent = { + 'gppString': 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==', + 'applicableSections': [8] + }; + const data = buildAndGetData([mockBannerBid()], 0, mockBidderRequest({gppConsent})); + expect(data.userConsent).equal( + JSON.stringify({ + gdprApplies: '', + cmp: '', + gpp: 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==', + gpp_sid: [8], }) ); }); @@ -371,7 +389,15 @@ describe('YieldmoAdapter', function () { it('should add eids to the banner bid request', function () { const params = { - userId: {pubcid: 'fake_pubcid'}, + userIdAsEids: [{ + source: 'pubcid.org', + uids: [ + { + id: 'fake_pubcid', + atype: 1, + } + ] + }], fakeUserIdAsEids: [{ source: 'pubcid.org', uids: [{ @@ -532,7 +558,15 @@ describe('YieldmoAdapter', function () { it('should add eids to the video bid request', function () { const params = { - userId: {pubcid: 'fake_pubcid'}, + userIdAsEids: [{ + source: 'pubcid.org', + uids: [ + { + id: 'fake_pubcid', + atype: 1, + } + ] + }], fakeUserIdAsEids: [{ source: 'pubcid.org', uids: [{ diff --git a/test/spec/modules/zeotapIdPlusIdSystem_spec.js b/test/spec/modules/zeotapIdPlusIdSystem_spec.js index 6494a7cbfef..54483f0c00e 100644 --- a/test/spec/modules/zeotapIdPlusIdSystem_spec.js +++ b/test/spec/modules/zeotapIdPlusIdSystem_spec.js @@ -4,6 +4,7 @@ import { config } from 'src/config.js'; import { init, requestBidsHook, setSubmoduleRegistry } from 'modules/userId/index.js'; import { storage, getStorage, zeotapIdPlusSubmodule } from 'modules/zeotapIdPlusIdSystem.js'; import * as storageManager from 'src/storageManager.js'; +import {MODULE_TYPE_UID} from '../../../src/activities/modules.js'; const ZEOTAP_COOKIE_NAME = 'IDP'; const ZEOTAP_COOKIE = 'THIS-IS-A-DUMMY-COOKIE'; @@ -52,9 +53,9 @@ describe('Zeotap ID System', function() { }); it('when a stored Zeotap ID exists it is added to bids', function() { - let store = getStorage(); + getStorage(); expect(getStorageManagerSpy.calledOnce).to.be.true; - sinon.assert.calledWith(getStorageManagerSpy, {gvlid: 301, moduleName: 'zeotapIdPlus'}); + sinon.assert.calledWith(getStorageManagerSpy, {moduleType: MODULE_TYPE_UID, moduleName: 'zeotapIdPlus'}); }); }); diff --git a/test/spec/modules/zeta_global_sspBidAdapter_spec.js b/test/spec/modules/zeta_global_sspBidAdapter_spec.js index f616403cfcd..601f4546a29 100644 --- a/test/spec/modules/zeta_global_sspBidAdapter_spec.js +++ b/test/spec/modules/zeta_global_sspBidAdapter_spec.js @@ -25,6 +25,22 @@ describe('Zeta Ssp Bid Adapter', function () { } ]; + const schain = { + complete: 1, + nodes: [ + { + asi: 'asi1', + sid: 'sid1', + rid: 'rid1' + }, + { + asi: 'asi2', + sid: 'sid2', + rid: 'rid2' + } + ] + }; + const params = { user: { uid: 222, @@ -35,6 +51,7 @@ describe('Zeta Ssp Bid Adapter', function () { }, sid: 'publisherId', shortname: 'test_shortname', + tagid: 'test_tag_id', site: { page: 'testPage' }, @@ -103,6 +120,30 @@ describe('Zeta Ssp Bid Adapter', function () { gdprApplies: 1, consentString: 'consentString' }, + schain: schain, + uspConsent: 'someCCPAString', + params: params, + userIdAsEids: eids, + timeout: 500 + }]; + + const bannerWithFewSizesRequest = [{ + bidId: 12345, + auctionId: 67890, + mediaTypes: { + banner: { + sizes: [[300, 250], [200, 240], [100, 150]], + } + }, + refererInfo: { + page: 'http://www.zetaglobal.com/page?param=value', + domain: 'www.zetaglobal.com', + }, + gdprConsent: { + gdprApplies: 1, + consentString: 'consentString' + }, + schain: schain, uspConsent: 'someCCPAString', params: params, userIdAsEids: eids, @@ -119,6 +160,8 @@ describe('Zeta Ssp Bid Adapter', function () { mimes: ['video/mp4'], minduration: 5, maxduration: 30, + placement: 2, + plcmt: 1, protocols: [2, 3] } }, @@ -128,6 +171,87 @@ describe('Zeta Ssp Bid Adapter', function () { params: params }]; + const zetaResponse = { + body: { + id: '12345', + seatbid: [ + { + bid: [ + { + id: 'auctionId', + impid: 'impId', + price: 0.0, + adm: 'adMarkup', + crid: 'creativeId', + adomain: [ + 'https://example.com' + ], + h: 250, + w: 300 + } + ] + } + ], + cur: 'USD' + } + } + + const responseBannerPayload = { + data: { + id: '123', + site: { + id: 'SITE_ID', + page: 'page.com', + domain: 'domain.com' + }, + user: { + id: '45asdf9tydhrty789adfad4678rew656789', + buyeruid: '1234567890' + }, + cur: [ + 'USD' + ], + imp: [ + { + id: '1', + banner: { + h: 600, + w: 160 + } + } + ], + at: 1 + } + }; + + const responseVideoPayload = { + data: { + id: '123', + site: { + id: 'SITE_ID', + page: 'page.com', + domain: 'domain.com' + }, + user: { + id: '45asdf9tydhrty789adfad4678rew656789', + buyeruid: '1234567890' + }, + cur: [ + 'USD' + ], + imp: [ + { + id: '1', + video: { + h: 600, + w: 160 + } + } + ], + at: 1 + } + }; + it('Test the bid validation function', function () { const validBid = spec.isBidRequestValid(bannerRequest[0]); const invalidBid = spec.isBidRequestValid(null); @@ -181,7 +305,12 @@ describe('Zeta Ssp Bid Adapter', function () { 'https://example.com' ], h: 250, - w: 300 + w: 300, + ext: { + prebid: { + type: 'banner' + } + } }, { id: 'auctionId2', @@ -195,7 +324,9 @@ describe('Zeta Ssp Bid Adapter', function () { h: 150, w: 200, ext: { - bidtype: 'video' + prebid: { + type: 'video' + } } }, { @@ -208,7 +339,12 @@ describe('Zeta Ssp Bid Adapter', function () { 'https://example3.com' ], h: 400, - w: 300 + w: 300, + ext: { + prebid: { + type: 'video' + } + } } ] } @@ -217,7 +353,7 @@ describe('Zeta Ssp Bid Adapter', function () { } }; - const bidResponse = spec.interpretResponse(response, null); + const bidResponse = spec.interpretResponse(response, responseBannerPayload); expect(bidResponse).to.not.be.empty; const bid1 = bidResponse[0]; @@ -308,6 +444,8 @@ describe('Zeta Ssp Bid Adapter', function () { expect(payload.imp[0].video.mimes).to.eql(videoRequest[0].mediaTypes.video.mimes); expect(payload.imp[0].video.w).to.eql(720); expect(payload.imp[0].video.h).to.eql(340); + expect(payload.imp[0].video.placement).to.eql(videoRequest[0].mediaTypes.video.placement); + expect(payload.imp[0].video.plcmt).to.eql(videoRequest[0].mediaTypes.video.plcmt); expect(payload.imp[0].banner).to.be.undefined; }); @@ -367,4 +505,103 @@ describe('Zeta Ssp Bid Adapter', function () { expect(payload.imp[0].bidfloor).to.eql(params.bidfloor); }); + + it('Timeout should exists and be a function', function () { + expect(spec.onTimeout).to.exist.and.to.be.a('function'); + expect(spec.onTimeout({ timeout: 1000 })).to.be.undefined; + }); + + it('Test schain provided', function () { + const request = spec.buildRequests(bannerRequest, bannerRequest[0]); + const payload = JSON.parse(request.data); + + expect(payload.source.ext.schain).to.eql(schain); + }); + + it('Test tagid provided', function () { + const request = spec.buildRequests(bannerRequest, bannerRequest[0]); + const payload = JSON.parse(request.data); + + expect(payload.imp[0].tagid).to.eql(params.tagid); + }); + + it('Test if only one size', function () { + const request = spec.buildRequests(bannerRequest, bannerRequest[0]); + const payload = JSON.parse(request.data); + + // banner + expect(payload.imp[0].banner.w).to.eql(300); + expect(payload.imp[0].banner.h).to.eql(250); + + expect(payload.imp[0].banner.format).to.be.undefined; + }); + + it('Test few sizes provided in format', function () { + const request = spec.buildRequests(bannerWithFewSizesRequest, bannerWithFewSizesRequest[0]); + const payload = JSON.parse(request.data); + + // banner + expect(payload.imp[0].banner.w).to.eql(300); + expect(payload.imp[0].banner.h).to.eql(250); + + expect(payload.imp[0].banner.format.length).to.eql(3); + + // format[0] + expect(payload.imp[0].banner.format[0].w).to.eql(300); + expect(payload.imp[0].banner.format[0].h).to.eql(250); + + // format[1] + expect(payload.imp[0].banner.format[1].w).to.eql(200); + expect(payload.imp[0].banner.format[1].h).to.eql(240); + + // format[2] + expect(payload.imp[0].banner.format[2].w).to.eql(100); + expect(payload.imp[0].banner.format[2].h).to.eql(150); + }); + + it('Test the response default mediaType:banner', function () { + const bidResponse = spec.interpretResponse(zetaResponse, responseBannerPayload); + expect(bidResponse).to.not.be.empty; + expect(bidResponse.length).to.eql(1); + expect(bidResponse[0].mediaType).to.eql(BANNER); + expect(bidResponse[0].ad).to.eql(zetaResponse.body.seatbid[0].bid[0].adm); + expect(bidResponse[0].vastXml).to.be.undefined; + }); + + it('Test the response default mediaType:video', function () { + const bidResponse = spec.interpretResponse(zetaResponse, responseVideoPayload); + expect(bidResponse).to.not.be.empty; + expect(bidResponse.length).to.eql(1); + expect(bidResponse[0].mediaType).to.eql(VIDEO); + expect(bidResponse[0].ad).to.eql(zetaResponse.body.seatbid[0].bid[0].adm); + expect(bidResponse[0].vastXml).to.eql(zetaResponse.body.seatbid[0].bid[0].adm); + }); + + it('Test the response mediaType:video from ext param', function () { + zetaResponse.body.seatbid[0].bid[0].ext = { + prebid: { + type: 'video' + } + } + const bidResponse = spec.interpretResponse(zetaResponse, responseBannerPayload); + expect(bidResponse).to.not.be.empty; + expect(bidResponse.length).to.eql(1); + expect(bidResponse[0].mediaType).to.eql(VIDEO); + expect(bidResponse[0].ad).to.eql(zetaResponse.body.seatbid[0].bid[0].adm); + expect(bidResponse[0].vastXml).to.eql(zetaResponse.body.seatbid[0].bid[0].adm); + }); + + it('Test the response mediaType:banner from ext param', function () { + zetaResponse.body.seatbid[0].bid[0].ext = { + prebid: { + type: 'banner' + } + } + const bidResponse = spec.interpretResponse(zetaResponse, responseVideoPayload); + expect(bidResponse).to.not.be.empty; + expect(bidResponse.length).to.eql(1); + expect(bidResponse[0].mediaType).to.eql(BANNER); + expect(bidResponse[0].ad).to.eql(zetaResponse.body.seatbid[0].bid[0].adm); + expect(bidResponse[0].vastXml).to.be.undefined; + }); }); diff --git a/test/spec/native_spec.js b/test/spec/native_spec.js index 2b7c2b88449..dee177d4b9b 100644 --- a/test/spec/native_spec.js +++ b/test/spec/native_spec.js @@ -14,6 +14,7 @@ import { import CONSTANTS from 'src/constants.json'; import { stubAuctionIndex } from '../helpers/indexStub.js'; import { convertOrtbRequestToProprietaryNative, fromOrtbNativeRequest } from '../../src/native.js'; +import {auctionManager} from '../../src/auctionManager.js'; const utils = require('src/utils'); const bid = { @@ -430,158 +431,180 @@ describe('native.js', function () { sinon.assert.calledWith(triggerPixelStub, bid.native.clickTrackers[0]); }); - it('creates native asset message', function () { - const messageRequest = { - message: 'Prebid Native', - action: 'assetRequest', - adId: '123', - assets: ['hb_native_body', 'hb_native_image', 'hb_native_linkurl'], - }; + describe('native postMessages', () => { + let adUnit; + beforeEach(() => { + adUnit = {}; + sinon.stub(auctionManager, 'index').get(() => ({ + getAdUnit: () => adUnit + })) + }); + + it('creates native asset message', function () { + const messageRequest = { + message: 'Prebid Native', + action: 'assetRequest', + adId: '123', + assets: ['hb_native_body', 'hb_native_image', 'hb_native_linkurl'], + }; - const message = getAssetMessage(messageRequest, bid); + const message = getAssetMessage(messageRequest, bid); - expect(message.assets.length).to.equal(3); - expect(message.assets).to.deep.include({ - key: 'body', - value: bid.native.body, - }); - expect(message.assets).to.deep.include({ - key: 'image', - value: bid.native.image.url, - }); - expect(message.assets).to.deep.include({ - key: 'clickUrl', - value: bid.native.clickUrl, + expect(message.assets.length).to.equal(3); + expect(message.assets).to.deep.include({ + key: 'body', + value: bid.native.body, + }); + expect(message.assets).to.deep.include({ + key: 'image', + value: bid.native.image.url, + }); + expect(message.assets).to.deep.include({ + key: 'clickUrl', + value: bid.native.clickUrl, + }); }); - }); - it('creates native all asset message', function () { - const messageRequest = { - message: 'Prebid Native', - action: 'allAssetRequest', - adId: '123', - }; + it('creates native all asset message', function () { + const messageRequest = { + message: 'Prebid Native', + action: 'allAssetRequest', + adId: '123', + }; - const message = getAllAssetsMessage(messageRequest, bid); + const message = getAllAssetsMessage(messageRequest, bid); - expect(message.assets.length).to.equal(10); - expect(message.assets).to.deep.include({ - key: 'body', - value: bid.native.body, - }); - expect(message.assets).to.deep.include({ - key: 'image', - value: bid.native.image.url, - }); - expect(message.assets).to.deep.include({ - key: 'clickUrl', - value: bid.native.clickUrl, - }); - expect(message.assets).to.deep.include({ - key: 'title', - value: bid.native.title, - }); - expect(message.assets).to.deep.include({ - key: 'icon', - value: bid.native.icon.url, - }); - expect(message.assets).to.deep.include({ - key: 'cta', - value: bid.native.cta, - }); - expect(message.assets).to.deep.include({ - key: 'sponsoredBy', - value: bid.native.sponsoredBy, - }); - expect(message.assets).to.deep.include({ - key: 'foo', - value: bid.native.ext.foo, - }); - expect(message.assets).to.deep.include({ - key: 'baz', - value: bid.native.ext.baz, + expect(message.assets.length).to.equal(10); + expect(message.assets).to.deep.include({ + key: 'body', + value: bid.native.body, + }); + expect(message.assets).to.deep.include({ + key: 'image', + value: bid.native.image.url, + }); + expect(message.assets).to.deep.include({ + key: 'clickUrl', + value: bid.native.clickUrl, + }); + expect(message.assets).to.deep.include({ + key: 'title', + value: bid.native.title, + }); + expect(message.assets).to.deep.include({ + key: 'icon', + value: bid.native.icon.url, + }); + expect(message.assets).to.deep.include({ + key: 'cta', + value: bid.native.cta, + }); + expect(message.assets).to.deep.include({ + key: 'sponsoredBy', + value: bid.native.sponsoredBy, + }); + expect(message.assets).to.deep.include({ + key: 'foo', + value: bid.native.ext.foo, + }); + expect(message.assets).to.deep.include({ + key: 'baz', + value: bid.native.ext.baz, + }); }); - }); - it('creates native all asset message with only defined fields', function () { - const messageRequest = { - message: 'Prebid Native', - action: 'allAssetRequest', - adId: '123', - }; + it('creates native all asset message with only defined fields', function () { + const messageRequest = { + message: 'Prebid Native', + action: 'allAssetRequest', + adId: '123', + }; - const message = getAllAssetsMessage(messageRequest, bidWithUndefinedFields); + const message = getAllAssetsMessage(messageRequest, bidWithUndefinedFields); - expect(message.assets.length).to.equal(4); - expect(message.assets).to.deep.include({ - key: 'clickUrl', - value: bid.native.clickUrl, - }); - expect(message.assets).to.deep.include({ - key: 'title', - value: bid.native.title, - }); - expect(message.assets).to.deep.include({ - key: 'sponsoredBy', - value: bid.native.sponsoredBy, - }); - expect(message.assets).to.deep.include({ - key: 'foo', - value: bid.native.ext.foo, + expect(message.assets.length).to.equal(4); + expect(message.assets).to.deep.include({ + key: 'clickUrl', + value: bid.native.clickUrl, + }); + expect(message.assets).to.deep.include({ + key: 'title', + value: bid.native.title, + }); + expect(message.assets).to.deep.include({ + key: 'sponsoredBy', + value: bid.native.sponsoredBy, + }); + expect(message.assets).to.deep.include({ + key: 'foo', + value: bid.native.ext.foo, + }); }); - }); - it('creates native all asset message with complete format', function () { - const messageRequest = { - message: 'Prebid Native', - action: 'allAssetRequest', - adId: '123', - }; + it('creates native all asset message with complete format', function () { + const messageRequest = { + message: 'Prebid Native', + action: 'allAssetRequest', + adId: '123', + }; - const message = getAllAssetsMessage(messageRequest, completeNativeBid); + const message = getAllAssetsMessage(messageRequest, completeNativeBid); - expect(message.assets.length).to.equal(10); - expect(message.assets).to.deep.include({ - key: 'body', - value: bid.native.body, - }); - expect(message.assets).to.deep.include({ - key: 'image', - value: bid.native.image.url, - }); - expect(message.assets).to.deep.include({ - key: 'clickUrl', - value: bid.native.clickUrl, - }); - expect(message.assets).to.deep.include({ - key: 'title', - value: bid.native.title, - }); - expect(message.assets).to.deep.include({ - key: 'icon', - value: bid.native.icon.url, - }); - expect(message.assets).to.deep.include({ - key: 'cta', - value: bid.native.cta, - }); - expect(message.assets).to.deep.include({ - key: 'sponsoredBy', - value: bid.native.sponsoredBy, - }); - expect(message.assets).to.deep.include({ - key: 'privacyLink', - value: ortbBid.native.ortb.privacy, - }); - expect(message.assets).to.deep.include({ - key: 'foo', - value: bid.native.ext.foo, - }); - expect(message.assets).to.deep.include({ - key: 'baz', - value: bid.native.ext.baz, + expect(message.assets.length).to.equal(10); + expect(message.assets).to.deep.include({ + key: 'body', + value: bid.native.body, + }); + expect(message.assets).to.deep.include({ + key: 'image', + value: bid.native.image.url, + }); + expect(message.assets).to.deep.include({ + key: 'clickUrl', + value: bid.native.clickUrl, + }); + expect(message.assets).to.deep.include({ + key: 'title', + value: bid.native.title, + }); + expect(message.assets).to.deep.include({ + key: 'icon', + value: bid.native.icon.url, + }); + expect(message.assets).to.deep.include({ + key: 'cta', + value: bid.native.cta, + }); + expect(message.assets).to.deep.include({ + key: 'sponsoredBy', + value: bid.native.sponsoredBy, + }); + expect(message.assets).to.deep.include({ + key: 'privacyLink', + value: ortbBid.native.ortb.privacy, + }); + expect(message.assets).to.deep.include({ + key: 'foo', + value: bid.native.ext.foo, + }); + expect(message.assets).to.deep.include({ + key: 'baz', + value: bid.native.ext.baz, + }); }); - }); + + it('if necessary, adds ortb response when the request was in ortb', () => { + const messageRequest = { + message: 'Prebid Native', + action: 'allAssetRequest', + adId: '123', + }; + adUnit = {mediaTypes: {native: {ortb: ortbRequest}}, nativeOrtbRequest: ortbRequest} + const message = getAllAssetsMessage(messageRequest, bid); + const expected = toOrtbNativeResponse(bid.native, ortbRequest) + expect(message.ortb).to.eql(expected); + }) + }) const SAMPLE_ORTB_REQUEST = toOrtbNativeRequest({ title: 'vtitle', @@ -979,22 +1002,22 @@ describe('validate native', function () { expect(oldNativeRequest.image).to.deep.include({ required: false, - aspect_ratios: { + aspect_ratios: [{ min_width: 836, min_height: 627, - ratio_width: 836, - ratio_height: 627 - } + ratio_width: 4, + ratio_height: 3 + }] }); expect(oldNativeRequest.icon).to.deep.include({ required: true, - aspect_ratios: { + aspect_ratios: [{ min_width: 50, min_height: 50, - ratio_width: 50, - ratio_height: 50 - } + ratio_width: 1, + ratio_height: 1 + }] }); expect(oldNativeRequest.sponsoredBy).to.include({ required: true, @@ -1096,12 +1119,12 @@ describe('validate native', function () { }, icon: { required: true, - aspect_ratios: { + aspect_ratios: [{ min_width: 50, min_height: 50, - ratio_width: 50, - ratio_height: 50 - } + ratio_width: 1, + ratio_height: 1 + }] }, sponsoredBy: { required: true, @@ -1303,5 +1326,24 @@ describe('toOrtbNativeResponse', () => { text: 'vtitle' } }) + }); + + it('should accept objects as legacy assets', () => { + const legacyResponse = { + icon: { + url: 'image-url' + } + } + const request = toOrtbNativeRequest({ + icon: { + required: true + } + }); + const response = toOrtbNativeResponse(legacyResponse, request); + sinon.assert.match(response.assets[0], { + img: { + url: 'image-url' + } + }) }) }) diff --git a/test/spec/ortbConverter/userId_spec.js b/test/spec/ortbConverter/userId_spec.js index 0b1c8878edf..04a4d39ee48 100644 --- a/test/spec/ortbConverter/userId_spec.js +++ b/test/spec/ortbConverter/userId_spec.js @@ -18,4 +18,16 @@ describe('pbjs - ortb user eids', () => { setOrtbUserExtEids(req, {}, [{}]); expect(req).to.eql({}); }) + + it('has no effect if user.ext.eids is an empty array', () => { + const req = {}; + setOrtbUserExtEids(req, {}, { + bidRequests: [ + { + userIdAsEids: [] + } + ] + }); + expect(req).to.eql({}); + }); }) diff --git a/test/spec/refererDetection_spec.js b/test/spec/refererDetection_spec.js index a0ffc3eddbe..800222892e6 100644 --- a/test/spec/refererDetection_spec.js +++ b/test/spec/refererDetection_spec.js @@ -1,4 +1,4 @@ -import {detectReferer, ensureProtocol, parseDomain} from 'src/refererDetection.js'; +import {cacheWithLocation, detectReferer, ensureProtocol, parseDomain} from 'src/refererDetection.js'; import {config} from 'src/config.js'; import {expect} from 'chai'; @@ -493,3 +493,58 @@ describe('parseDomain', () => { }) }) }); + +describe('cacheWithLocation', () => { + let fn, win, cached; + const RESULT = 'result'; + beforeEach(() => { + fn = sinon.stub().callsFake(() => RESULT); + win = { + location: { + }, + document: { + querySelector: sinon.stub() + } + } + }); + + describe('when window is not on top', () => { + beforeEach(() => { + win.top = {}; + cached = cacheWithLocation(fn, win); + }) + + it('should not cache', () => { + win.top = {}; + cached(); + expect(cached()).to.eql(RESULT); + expect(fn.callCount).to.eql(2); + }); + }) + + describe('when window is on top', () => { + beforeEach(() => { + win.top = win; + cached = cacheWithLocation(fn, win); + }) + + it('should not cache when canonical URL changes', () => { + let canonical = 'foo'; + win.document.querySelector.callsFake(() => ({href: canonical})); + cached(); + expect(cached()).to.eql(RESULT); + canonical = 'bar'; + expect(cached()).to.eql(RESULT); + expect(fn.callCount).to.eql(2); + }); + + it('should not cache when location changes', () => { + win.location.href = 'foo'; + cached(); + expect(cached()).to.eql(RESULT); + win.location.href = 'bar'; + expect(cached()).to.eql(RESULT); + expect(fn.callCount).to.eql(2); + }) + }); +}) diff --git a/test/spec/renderer_spec.js b/test/spec/renderer_spec.js index c41334f916a..fb1e25d6009 100644 --- a/test/spec/renderer_spec.js +++ b/test/spec/renderer_spec.js @@ -52,7 +52,7 @@ describe('Renderer', function () { expect(testRenderer2.getConfig()).to.deep.equal({ test: 'config2' }); }); - it('sets a render function with setRender method', function () { + it('sets a render function with the setRender method', function () { testRenderer1.setRender(spyRenderFn); expect(typeof testRenderer1.render).to.equal('function'); testRenderer1.render(); @@ -110,7 +110,6 @@ describe('Renderer', function () { it('renders immediately when requested', function () { const testRenderer3 = Renderer.install({ - url: 'https://httpbin.org/post', config: { test: 'config2' }, id: 2, renderNow: true diff --git a/test/spec/sizeMapping_spec.js b/test/spec/sizeMapping_spec.js deleted file mode 100644 index c4efbddad6d..00000000000 --- a/test/spec/sizeMapping_spec.js +++ /dev/null @@ -1,341 +0,0 @@ -import { expect } from 'chai'; -import { resolveStatus, setSizeConfig, sizeSupported } from 'src/sizeMapping.js'; -import {includes} from 'src/polyfill.js' - -let utils = require('src/utils'); -let deepClone = utils.deepClone; - -describe('sizeMapping', function () { - var testSizes = { - banner: { - sizes: [[970, 90], [728, 90], [300, 250], [300, 100], [80, 80]] - } - }; - - var sizeConfig = [{ - 'mediaQuery': '(min-width: 1200px)', - 'sizesSupported': [ - [970, 90], - [728, 90], - [300, 250] - ] - }, { - 'mediaQuery': '(min-width: 768px) and (max-width: 1199px)', - 'sizesSupported': [ - [728, 90], - [300, 250], - [300, 100] - ] - }, { - 'mediaQuery': '(min-width: 0px) and (max-width: 767px)', - 'sizesSupported': [] - }]; - - var sizeConfigWithLabels = [{ - 'mediaQuery': '(min-width: 1200px)', - 'labels': ['desktop'] - }, { - 'mediaQuery': '(min-width: 768px) and (max-width: 1199px)', - 'sizesSupported': [ - [728, 90], - [300, 250] - ], - 'labels': ['tablet', 'phone'] - }, { - 'mediaQuery': '(min-width: 0px) and (max-width: 767px)', - 'sizesSupported': [ - [300, 250], - [300, 100] - ], - 'labels': ['phone'] - }]; - - let sandbox, - matchMediaOverride; - - beforeEach(function () { - setSizeConfig(sizeConfig); - - sandbox = sinon.sandbox.create(); - - matchMediaOverride = {matches: false}; - - sandbox.stub(utils.getWindowTop(), 'matchMedia').callsFake((...args) => { - if (typeof matchMediaOverride === 'function') { - return matchMediaOverride.apply(utils.getWindowTop(), args); - } - return matchMediaOverride; - }); - }); - - afterEach(function () { - setSizeConfig([]); - - sandbox.restore(); - }); - - describe('when handling sizes', function () { - it('should allow us to validate a single size', function() { - matchMediaOverride = (str) => str === '(min-width: 1200px)' ? {matches: true} : {matches: false}; - - expect(sizeSupported([300, 250])).to.equal(true); - expect(sizeSupported([80, 80])).to.equal(false); - }); - - it('should log a warning when mediaQuery property missing from sizeConfig', function () { - let errorConfig = deepClone(sizeConfig); - - delete errorConfig[0].mediaQuery; - - sandbox.stub(utils, 'logWarn'); - - resolveStatus(undefined, testSizes, undefined, errorConfig); - expect(utils.logWarn.firstCall.args[0]).to.match(/missing.+?mediaQuery/); - }); - - it('should log a warning message when mediaQuery property is declared as an empty string', function() { - const errorConfig = deepClone(sizeConfig); - errorConfig[0].mediaQuery = ''; - - sandbox.stub(utils, 'logWarn'); - resolveStatus(undefined, testSizes, undefined, errorConfig); - expect(utils.logWarn.firstCall.args[0]).to.match(/missing.+?mediaQuery/); - }); - - it('should allow deprecated adUnit.sizes', function() { - matchMediaOverride = (str) => str === '(min-width: 1200px)' ? {matches: true} : {matches: false}; - - let status = resolveStatus(undefined, undefined, testSizes.banner.sizes, sizeConfig); - - expect(status.active).to.equal(true); - expect(status.mediaTypes).to.deep.equal({ - banner: { - sizes: [[970, 90], [728, 90], [300, 250]] - } - }); - }); - - it('when one mediaQuery block matches, it should filter the adUnit.sizes passed in', function () { - matchMediaOverride = (str) => str === '(min-width: 1200px)' ? {matches: true} : {matches: false}; - - let status = resolveStatus(undefined, testSizes, undefined, sizeConfig); - - expect(status.active).to.equal(true); - expect(status.mediaTypes).to.deep.equal({ - banner: { - sizes: [[970, 90], [728, 90], [300, 250]] - } - }); - }); - - it('when multiple mediaQuery block matches, it should filter a union of the matched sizesSupported', function () { - matchMediaOverride = (str) => includes([ - '(min-width: 1200px)', - '(min-width: 768px) and (max-width: 1199px)' - ], str) ? {matches: true} : {matches: false}; - - let status = resolveStatus(undefined, testSizes, undefined, sizeConfig); - expect(status.active).to.equal(true); - expect(status.mediaTypes).to.deep.equal({ - banner: { - sizes: [[970, 90], [728, 90], [300, 250], [300, 100]] - } - }); - }); - - it('if no mediaQueries match, it should allow all sizes specified', function () { - matchMediaOverride = () => ({matches: false}); - - let status = resolveStatus(undefined, testSizes, undefined, sizeConfig); - expect(status.active).to.equal(true); - expect(status.mediaTypes).to.deep.equal(testSizes); - }); - - it('if a mediaQuery matches and has sizesSupported: [], it should filter all sizes', function () { - matchMediaOverride = (str) => str === '(min-width: 0px) and (max-width: 767px)' ? {matches: true} : {matches: false}; - - let status = resolveStatus(undefined, testSizes, undefined, sizeConfig); - expect(status.active).to.equal(false); - expect(status.mediaTypes).to.deep.equal({ - banner: { - sizes: [] - } - }); - }); - - it('should filter all banner sizes and should disable the adUnit even if other mediaTypes are present', function () { - matchMediaOverride = (str) => str === '(min-width: 0px) and (max-width: 767px)' ? {matches: true} : {matches: false}; - let status = resolveStatus(undefined, Object.assign({}, testSizes, { - native: { - type: 'image' - } - }), undefined, sizeConfig); - expect(status.active).to.equal(false); - expect(status.mediaTypes).to.deep.equal({ - banner: { - sizes: [] - }, - native: { - type: 'image' - } - }); - }); - - it('if a mediaQuery matches and no sizesSupported specified, it should not affect adUnit.sizes', function () { - matchMediaOverride = (str) => str === '(min-width: 1200px)' ? {matches: true} : {matches: false}; - - let status = resolveStatus(undefined, testSizes, undefined, sizeConfigWithLabels); - expect(status.active).to.equal(true); - expect(status.mediaTypes).to.deep.equal(testSizes); - }); - }); - - describe('when handling labels', function () { - it('should activate/deactivate adUnits/bidders based on sizeConfig.labels', function () { - matchMediaOverride = (str) => str === '(min-width: 1200px)' ? {matches: true} : {matches: false}; - - let status = resolveStatus({ - labels: ['desktop'] - }, testSizes, undefined, sizeConfigWithLabels); - - expect(status).to.deep.equal({ - active: true, - mediaTypes: testSizes - }); - - status = resolveStatus({ - labels: ['tablet'] - }, testSizes, undefined, sizeConfigWithLabels); - - expect(status.active).to.equal(false); - expect(status.mediaTypes).to.deep.equal(testSizes); - }); - - it('should activate/decactivate adUnits/bidders based on labels with multiformat ads', function () { - matchMediaOverride = (str) => str === '(min-width: 768px) and (max-width: 1199px)' ? {matches: true} : {matches: false}; - - let multiFormatSizes = { - banner: { - sizes: [[728, 90], [300, 300]] - }, - native: { - type: 'image' - }, - video: { - context: 'outstream', - playerSize: [300, 300] - } - }; - - let status = resolveStatus({ - labels: ['tablet', 'test'], - labelAll: true - }, multiFormatSizes, undefined, sizeConfigWithLabels); - - expect(status.active).to.equal(false); - expect(status.mediaTypes).to.deep.equal({ - banner: { - sizes: [[728, 90]] - }, - native: { - type: 'image' - }, - video: { - context: 'outstream', - playerSize: [300, 300] - } - }); - - status = resolveStatus({ - labels: ['tablet'] - }, multiFormatSizes, undefined, sizeConfigWithLabels); - - expect(status.active).to.equal(true); - expect(status.mediaTypes).to.deep.equal({ - banner: { - sizes: [[728, 90]] - }, - native: { - type: 'image' - }, - video: { - context: 'outstream', - playerSize: [300, 300] - } - }); - - multiFormatSizes.banner.sizes.splice(0, 1, [728, 80]); - status = resolveStatus({ - labels: ['tablet'] - }, multiFormatSizes, undefined, sizeConfigWithLabels); - - expect(status.active).to.equal(false); - expect(status.mediaTypes).to.deep.equal({ - banner: { - sizes: [] - }, - native: { - type: 'image' - }, - video: { - context: 'outstream', - playerSize: [300, 300] - } - }); - - delete multiFormatSizes.banner; - status = resolveStatus({ - labels: ['tablet'] - }, multiFormatSizes, undefined, sizeConfigWithLabels); - - expect(status.active).to.equal(true); - expect(status.mediaTypes).to.deep.equal({ - native: { - type: 'image' - }, - video: { - context: 'outstream', - playerSize: [300, 300] - } - }); - }); - - it('should active/deactivate adUnits/bidders based on requestBids labels', function () { - let activeLabels = ['us-visitor', 'desktop', 'smart']; - - let status = resolveStatus({ - labels: ['uk-visitor'], // from adunit - activeLabels // from requestBids.labels - }, testSizes, undefined, sizeConfigWithLabels); - - expect(status.active).to.equal(false); - expect(status.mediaTypes).to.deep.equal(testSizes); - - status = resolveStatus({ - labels: ['us-visitor'], - activeLabels - }, testSizes, undefined, sizeConfigWithLabels); - - expect(status.active).to.equal(true); - expect(status.mediaTypes).to.deep.equal(testSizes); - - status = resolveStatus({ - labels: ['us-visitor', 'tablet'], - labelAll: true, - activeLabels - }, testSizes, undefined, sizeConfigWithLabels); - - expect(status.active).to.equal(false); - expect(status.mediaTypes).to.deep.equal(testSizes); - - status = resolveStatus({ - labels: ['us-visitor', 'desktop'], - labelAll: true, - activeLabels - }, testSizes, undefined, sizeConfigWithLabels); - - expect(status.active).to.equal(true); - expect(status.mediaTypes).to.deep.equal(testSizes); - }); - }); -}); diff --git a/test/spec/unit/core/adapterManager_spec.js b/test/spec/unit/core/adapterManager_spec.js index 06366e8cc5c..cff26df2e4d 100644 --- a/test/spec/unit/core/adapterManager_spec.js +++ b/test/spec/unit/core/adapterManager_spec.js @@ -4,7 +4,7 @@ import adapterManager, { coppaDataHandler, _partitionBidders, PARTITIONS, - getS2SBidderSet, _filterBidsForAdUnit + getS2SBidderSet, _filterBidsForAdUnit, dep } from 'src/adapterManager.js'; import { getAdUnits, @@ -16,11 +16,14 @@ import CONSTANTS from 'src/constants.json'; import * as utils from 'src/utils.js'; import { config } from 'src/config.js'; import { registerBidder } from 'src/adapters/bidderFactory.js'; -import { setSizeConfig } from 'src/sizeMapping.js'; +import { setSizeConfig } from 'modules/sizeMapping.js'; import {find, includes} from 'src/polyfill.js'; import s2sTesting from 'modules/s2sTesting.js'; import {hook} from '../../../../src/hook.js'; import {auctionManager} from '../../../../src/auctionManager.js'; +import {GDPR_GVLIDS} from '../../../../src/consentHandler.js'; +import {MODULE_TYPE_ANALYTICS, MODULE_TYPE_BIDDER} from '../../../../src/activities/modules.js'; +import {ACTIVITY_FETCH_BIDS, ACTIVITY_REPORT_ANALYTICS} from '../../../../src/activities/activities.js'; var events = require('../../../../src/events'); const CONFIG = { @@ -351,103 +354,79 @@ describe('adapterManager tests', function () { }); }); // end callTimedOutBidders - describe('onBidWon', function () { - var criteoSpec = { onBidWon: sinon.stub() } - var criteoAdapter = { - bidder: 'criteo', - getSpec: function() { return criteoSpec; } - } + describe('bidder spec methods', () => { + let adUnits, bids, criteoSpec; before(function () { config.setConfig({s2sConfig: { enabled: false }}); }); - beforeEach(function () { - adapterManager.bidderRegistry['criteo'] = criteoAdapter; - }); - - afterEach(function () { - delete adapterManager.bidderRegistry['criteo']; - }); - - it('should call spec\'s onBidWon callback when a bid is won', function () { - const bids = [ + beforeEach(() => { + criteoSpec = {} + adapterManager.bidderRegistry['criteo'] = { + bidder: 'criteo', + getSpec: function() { return criteoSpec; }, + } + bids = [ {bidder: 'criteo', params: {placementId: 'id'}}, ]; - const adUnits = [{ + adUnits = [{ code: 'adUnit-code', sizes: [[728, 90]], bids }]; - - adapterManager.callBidWonBidder(bids[0].bidder, bids[0], adUnits); - sinon.assert.called(criteoSpec.onBidWon); - }); - }); // end onBidWon - - describe('onSetTargeting', function () { - var criteoSpec = { onSetTargeting: sinon.stub() } - var criteoAdapter = { - bidder: 'criteo', - getSpec: function() { return criteoSpec; } - } - before(function () { - config.setConfig({s2sConfig: { enabled: false }}); - }); - - beforeEach(function () { - adapterManager.bidderRegistry['criteo'] = criteoAdapter; }); afterEach(function () { delete adapterManager.bidderRegistry['criteo']; }); - it('should call spec\'s onSetTargeting callback when setTargeting is called', function () { - const bids = [ - {bidder: 'criteo', params: {placementId: 'id'}}, - ]; - const adUnits = [{ - code: 'adUnit-code', - sizes: [[728, 90]], - bids - }]; - adapterManager.callSetTargetingBidder(bids[0].bidder, bids[0], adUnits); - sinon.assert.called(criteoSpec.onSetTargeting); - }); - }); // end onSetTargeting + describe('onBidWon', function () { + beforeEach(() => { + criteoSpec.onBidWon = sinon.stub() + }); + it('should call spec\'s onBidWon callback when a bid is won', function () { + adapterManager.callBidWonBidder(bids[0].bidder, bids[0], adUnits); + sinon.assert.called(criteoSpec.onBidWon); + }); - describe('onBidViewable', function () { - var criteoSpec = { onBidViewable: sinon.stub() } - var criteoAdapter = { - bidder: 'criteo', - getSpec: function() { return criteoSpec; } - } - before(function () { - config.setConfig({s2sConfig: { enabled: false }}); + it('should NOT call onBidWon when the bid is S2S', () => { + bids[0].src = CONSTANTS.S2S.SRC + adapterManager.callBidWonBidder(bids[0].bidder, bids[0], adUnits); + sinon.assert.notCalled(criteoSpec.onBidWon); + }) }); - beforeEach(function () { - adapterManager.bidderRegistry['criteo'] = criteoAdapter; - }); + describe('onSetTargeting', function () { + beforeEach(() => { + criteoSpec.onSetTargeting = sinon.stub() + }) - afterEach(function () { - delete adapterManager.bidderRegistry['criteo']; - }); + it('should call spec\'s onSetTargeting callback when setTargeting is called', function () { + adapterManager.callSetTargetingBidder(bids[0].bidder, bids[0], adUnits); + sinon.assert.called(criteoSpec.onSetTargeting); + }); - it('should call spec\'s onBidViewable callback when callBidViewableBidder is called', function () { - const bids = [ - {bidder: 'criteo', params: {placementId: 'id'}}, - ]; - const adUnits = [{ - code: 'adUnit-code', - sizes: [[728, 90]], - bids - }]; - adapterManager.callBidViewableBidder(bids[0].bidder, bids[0]); - sinon.assert.called(criteoSpec.onBidViewable); + it('should NOT call onSetTargeting when bid is S2S', () => { + bids[0].src = CONSTANTS.S2S.SRC; + adapterManager.callSetTargetingBidder(bids[0].bidder, bids[0], adUnits); + sinon.assert.notCalled(criteoSpec.onSetTargeting); + }) + }); // end onSetTargeting + describe('onBidViewable', function () { + beforeEach(() => { + criteoSpec.onBidViewable = sinon.stub(); + }) + it('should call spec\'s onBidViewable callback when callBidViewableBidder is called', function () { + adapterManager.callBidViewableBidder(bids[0].bidder, bids[0]); + sinon.assert.called(criteoSpec.onBidViewable); + }); + it('should NOT call onBidViewable when bid is S2S', () => { + bids[0].src = CONSTANTS.S2S.SRC; + adapterManager.callBidViewableBidder(bids[0].bidder, bids[0]); + sinon.assert.notCalled(criteoSpec.onBidViewable); + }) }); - }); // end onBidViewable - + }) describe('onBidderError', function () { const bidder = 'appnexus'; const appnexusSpec = { onBidderError: sinon.stub() }; @@ -1602,12 +1581,15 @@ describe('adapterManager tests', function () { }); it('should add alias to registry when original adapter is using bidderFactory', function() { - let thisSpec = Object.assign(spec, { supportedMediaTypes: ['video'] }); + const mediaType = FEATURES.VIDEO ? 'video' : 'banner' + let thisSpec = Object.assign(spec, { supportedMediaTypes: [mediaType] }); registerBidder(thisSpec); const alias = 'aliasBidder'; adapterManager.aliasBidAdapter(CODE, alias); expect(adapterManager.bidderRegistry).to.have.property(alias); - expect(adapterManager.videoAdapters).to.include(alias); + if (FEATURES.VIDEO) { + expect(adapterManager.videoAdapters).to.include(alias); + } }); }); @@ -1716,6 +1698,129 @@ describe('adapterManager tests', function () { expect(sizes1).not.to.deep.equal(sizes2); }); + describe('and activity controls', () => { + let redactOrtb2; + let redactBidRequest; + const MOCK_BIDDERS = ['1', '2', '3', '4', '5'].map((n) => `mockBidder${n}`); + + beforeEach(() => { + sinon.stub(dep, 'isAllowed'); + redactOrtb2 = sinon.stub().callsFake(ob => ob); + redactBidRequest = sinon.stub().callsFake(ob => ob); + sinon.stub(dep, 'redact').callsFake(() => ({ + ortb2: redactOrtb2, + bidRequest: redactBidRequest + })) + MOCK_BIDDERS.forEach((bidder) => adapterManager.bidderRegistry[bidder] = {}); + }); + afterEach(() => { + dep.isAllowed.restore(); + dep.redact.restore(); + MOCK_BIDDERS.forEach(bidder => { delete adapterManager.bidderRegistry[bidder] }); + config.resetConfig(); + }) + it('should not generate requests for bidders that cannot fetchBids', () => { + adUnits = [ + {code: 'one', bids: ['mockBidder1', 'mockBidder2', 'mockBidder3'].map((bidder) => ({bidder}))}, + {code: 'two', bids: ['mockBidder4', 'mockBidder5', 'mockBidder4'].map((bidder) => ({bidder}))} + ]; + const allowed = ['mockBidder2', 'mockBidder5']; + dep.isAllowed.callsFake((activity, {componentType, componentName}) => { + return activity === ACTIVITY_FETCH_BIDS && + componentType === MODULE_TYPE_BIDDER && + allowed.includes(componentName); + }); + let reqs = adapterManager.makeBidRequests( + adUnits, + Date.now(), + utils.getUniqueIdentifierStr(), + function callback() {}, + [] + ); + const bidders = Array.from(new Set(reqs.flatMap(br => br.bids).map(bid => bid.bidder)).keys()); + expect(bidders).to.have.members(allowed); + }); + + it('should redact ortb2 and bid request objects', () => { + dep.isAllowed.callsFake(() => true); + adUnits = [ + {code: 'one', bids: [{bidder: 'mockBidder1'}]} + ]; + let reqs = adapterManager.makeBidRequests( + adUnits, + Date.now(), + utils.getUniqueIdentifierStr(), + function callback() {}, + [] + ); + sinon.assert.calledWith(redactBidRequest, reqs[0].bids[0]); + sinon.assert.calledWith(redactOrtb2, reqs[0].ortb2); + }) + + describe('with multiple s2s configs', () => { + beforeEach(() => { + config.setConfig({ + s2sConfig: [ + { + enabled: true, + adapter: 'mockS2SDefault', + bidders: ['mockBidder1'] + }, + { + enabled: true, + adapter: 'mockS2S1', + configName: 'mock1', + }, + { + enabled: true, + adapter: 'mockS2S2', + configName: 'mock2', + } + ] + }); + }); + it('should keep stored impressions, even if everything else is denied', () => { + adUnits = [ + {code: 'one', bids: [{bidder: null}]}, + {code: 'two', bids: [{module: 'pbsBidAdapter', params: {configName: 'mock1'}}, {module: 'pbsBidAdapter', params: {configName: 'mock2'}}]} + ] + dep.isAllowed.callsFake(({componentType}) => componentType !== 'bidder'); + let bidRequests = adapterManager.makeBidRequests( + adUnits, + Date.now(), + utils.getUniqueIdentifierStr(), + function callback() {}, + [] + ); + expect(new Set(bidRequests.map(br => br.uniquePbsTid)).size).to.equal(3); + }); + + it('should check if the s2s adapter itself is allowed to fetch bids', () => { + adUnits = [ + { + code: 'au', + bids: [ + {bidder: null}, + {module: 'pbsBidAdapter', params: {configName: 'mock1'}}, + {module: 'pbsBidAdapter', params: {configName: 'mock2'}}, + {bidder: 'mockBidder1'} + ] + } + ]; + dep.isAllowed.callsFake((_, {configName, componentName}) => !(componentName === 'pbsBidAdapter' && configName === 'mock1')); + let bidRequests = adapterManager.makeBidRequests( + adUnits, + Date.now(), + utils.getUniqueIdentifierStr(), + function callback() { + }, + [] + ); + expect(new Set(bidRequests.map(br => br.uniquePbsTid)).size).to.eql(2) + }); + }); + }); + it('should make FPD available under `ortb2`', () => { const global = { k1: 'v1', @@ -1757,6 +1862,23 @@ describe('adapterManager tests', function () { requests.appnexus.bids.forEach((bid) => expect(bid.ortb2).to.eql(requests.appnexus.ortb2)); }); + describe('source.tid', () => { + beforeEach(() => { + sinon.stub(dep, 'redact').returns({ + ortb2: (o) => o, + bidRequest: (b) => b, + }); + }); + afterEach(() => { + dep.redact.restore(); + }); + + it('should be populated with auctionId', () => { + const reqs = adapterManager.makeBidRequests(adUnits, 0, 'mockAuctionId', 1000, [], {global: {}}); + expect(reqs[0].ortb2.source.tid).to.equal('mockAuctionId'); + }) + }); + it('should merge in bid-level ortb2Imp with adUnit-level ortb2Imp', () => { const adUnit = { ...adUnits[1], @@ -1963,64 +2085,6 @@ describe('adapterManager tests', function () { }); }); - describe('fledgeEnabled', function () { - const origRunAdAuction = navigator?.runAdAuction; - before(function () { - // navigator.runAdAuction doesn't exist, so we can't stub it normally with - // sinon.stub(navigator, 'runAdAuction') or something - navigator.runAdAuction = sinon.stub(); - }); - - after(function() { - navigator.runAdAuction = origRunAdAuction; - }) - - afterEach(function () { - config.resetConfig(); - }); - - it('should set fledgeEnabled correctly per bidder', function () { - config.setConfig({bidderSequence: 'fixed'}) - config.setBidderConfig({ - bidders: ['appnexus'], - config: { - fledgeEnabled: true, - } - }); - - const adUnits = [{ - 'code': '/19968336/header-bid-tag1', - 'mediaTypes': { - 'banner': { - 'sizes': [[728, 90]] - }, - }, - 'bids': [ - { - 'bidder': 'appnexus', - }, - { - 'bidder': 'rubicon', - }, - ] - }]; - - const bidRequests = adapterManager.makeBidRequests( - adUnits, - Date.now(), - utils.getUniqueIdentifierStr(), - function callback() {}, - [] - ); - - expect(bidRequests[0].bids[0].bidder).equals('appnexus'); - expect(bidRequests[0].fledgeEnabled).to.be.true; - - expect(bidRequests[1].bids[0].bidder).equals('rubicon'); - expect(bidRequests[1].fledgeEnabled).to.be.undefined; - }); - }); - describe('sizeMapping', function () { let sandbox; beforeEach(function () { @@ -2055,45 +2119,6 @@ describe('adapterManager tests', function () { expect(appnexusBidRequests.bids[1].mediaTypes).to.deep.equal(find(adUnits, adUnit => adUnit.code === appnexusBidRequests.bids[1].adUnitCode).mediaTypes); }); - it('should not filter video bids', function () { - setSizeConfig([{ - 'mediaQuery': '(min-width: 768px) and (max-width: 1199px)', - 'sizesSupported': [ - [728, 90], - [300, 250] - ], - 'labels': ['tablet', 'phone'] - }]); - - let videoAdUnits = [{ - code: 'test_video', - mediaTypes: { - video: { - playerSize: [300, 300], - context: 'outstream' - } - }, - bids: [{ - bidder: 'appnexus', - params: { - placementId: 13232385, - video: { - skippable: true, - playback_method: ['auto_play_sound_off'] - } - } - }] - }]; - let bidRequests = adapterManager.makeBidRequests( - videoAdUnits, - Date.now(), - utils.getUniqueIdentifierStr(), - function callback() {}, - [] - ); - expect(bidRequests[0].bids[0].sizes).to.deep.equal([300, 300]); - }); - it('should not filter native bids', function () { setSizeConfig([{ 'mediaQuery': '(min-width: 768px) and (max-width: 1199px)', @@ -2745,12 +2770,29 @@ describe('adapterManager tests', function () { beforeEach(() => { bidderRequests = []; + ['mockBidder', 'mockBidder1', 'mockBidder2'].forEach(bidder => { + adapterManager.registerBidAdapter({callBids: sinon.stub(), getSpec: () => ({code: bidder})}, bidder); + }) sinon.stub(auctionManager, 'getBidsRequested').callsFake(() => bidderRequests); }) afterEach(() => { auctionManager.getBidsRequested.restore(); }) + it('can resolve aliases', () => { + adapterManager.aliasBidAdapter('mockBidder', 'mockBidderAlias'); + expect(adapterManager.resolveAlias('mockBidderAlias')).to.eql('mockBidder'); + }); + it('does not stuck in alias cycles', () => { + adapterManager.aliasRegistry['alias1'] = 'alias2'; + adapterManager.aliasRegistry['alias2'] = 'alias2'; + expect(adapterManager.resolveAlias('alias2')).to.eql('alias2'); + }) + it('returns self when not an alias', () => { + delete adapterManager.aliasRegistry['missing']; + expect(adapterManager.resolveAlias('missing')).to.eql('missing'); + }) + it('does not invoke onDataDeletionRequest on aliases', () => { const del = delMethodForBidder('mockBidder'); adapterManager.aliasBidAdapter('mockBidder', 'mockBidderAlias'); @@ -2792,4 +2834,62 @@ describe('adapterManager tests', function () { }) }) }); + + describe('reportAnalytics check', () => { + beforeEach(() => { + sinon.stub(dep, 'isAllowed'); + }); + afterEach(() => { + dep.isAllowed.restore(); + }); + + it('should check for reportAnalytics before registering analytics adapter', () => { + const enabled = {}; + ['mockAnalytics1', 'mockAnalytics2'].forEach((code) => { + adapterManager.registerAnalyticsAdapter({ + code, + adapter: { + enableAnalytics: sinon.stub().callsFake(() => { enabled[code] = true }) + } + }) + }) + + const anlCfg = [ + { + provider: 'mockAnalytics1', + random: 'values' + }, + { + provider: 'mockAnalytics2' + } + ] + dep.isAllowed.callsFake((activity, {component, _config}) => { + return activity === ACTIVITY_REPORT_ANALYTICS && + component === `${MODULE_TYPE_ANALYTICS}.${anlCfg[0].provider}` && + _config === anlCfg[0] + }) + + adapterManager.enableAnalytics(anlCfg); + expect(enabled).to.eql({mockAnalytics1: true}); + }); + }); + + describe('registers GVL IDs', () => { + beforeEach(() => { + sinon.stub(GDPR_GVLIDS, 'register'); + }); + afterEach(() => { + GDPR_GVLIDS.register.restore(); + }); + + it('for bid adapters', () => { + adapterManager.registerBidAdapter({getSpec: () => ({gvlid: 123}), callBids: sinon.stub()}, 'mock'); + sinon.assert.calledWith(GDPR_GVLIDS.register, MODULE_TYPE_BIDDER, 'mock', 123); + }); + + it('for analytics adapters', () => { + adapterManager.registerAnalyticsAdapter({adapter: {enableAnalytics: sinon.stub()}, code: 'mock', gvlid: 123}); + sinon.assert.calledWith(GDPR_GVLIDS.register, MODULE_TYPE_ANALYTICS, 'mock', 123); + }); + }); }); diff --git a/test/spec/unit/core/bidderFactory_spec.js b/test/spec/unit/core/bidderFactory_spec.js index 1eecfac47a7..751c90af50f 100644 --- a/test/spec/unit/core/bidderFactory_spec.js +++ b/test/spec/unit/core/bidderFactory_spec.js @@ -1,18 +1,21 @@ -import {newBidder, registerBidder, preloadBidderMappingFile, storage, isValid} from 'src/adapters/bidderFactory.js'; +import {addComponentAuction, isValid, newBidder, registerBidder} from 'src/adapters/bidderFactory.js'; import adapterManager from 'src/adapterManager.js'; import * as ajax from 'src/ajax.js'; -import { expect } from 'chai'; -import { userSync } from 'src/userSync.js' +import {expect} from 'chai'; +import {userSync} from 'src/userSync.js'; import * as utils from 'src/utils.js'; -import { config } from 'src/config.js'; -import { server } from 'test/mocks/xhr.js'; +import {config} from 'src/config.js'; import CONSTANTS from 'src/constants.json'; import * as events from 'src/events.js'; import {hook} from '../../../../src/hook.js'; import {auctionManager} from '../../../../src/auctionManager.js'; import {stubAuctionIndex} from '../../../helpers/indexStub.js'; -import { bidderSettings } from '../../../../src/bidderSettings.js'; +import {bidderSettings} from '../../../../src/bidderSettings.js'; import {decorateAdUnitsWithNativeParams} from '../../../../src/native.js'; +import * as activityRules from 'src/activities/rules.js'; +import {sandbox} from 'sinon'; +import {MODULE_TYPE_BIDDER} from '../../../../src/activities/modules.js'; +import {ACTIVITY_TRANSMIT_TID} from '../../../../src/activities/activities.js'; const CODE = 'sampleBidder'; const MOCK_BIDS_REQUEST = { @@ -67,24 +70,25 @@ describe('bidders created by newBidder', function () { }); describe('when the ajax response is irrelevant', function () { + let sandbox; let ajaxStub; let getConfigSpy; let aliasRegistryStub, aliasRegistry; beforeEach(function () { - ajaxStub = sinon.stub(ajax, 'ajax'); + sandbox = sinon.sandbox.create(); + sandbox.stub(activityRules, 'isActivityAllowed').callsFake(() => true); + ajaxStub = sandbox.stub(ajax, 'ajax'); addBidResponseStub.reset(); - getConfigSpy = sinon.spy(config, 'getConfig'); + getConfigSpy = sandbox.spy(config, 'getConfig'); doneStub.reset(); aliasRegistry = {}; - aliasRegistryStub = sinon.stub(adapterManager, 'aliasRegistry'); + aliasRegistryStub = sandbox.stub(adapterManager, 'aliasRegistry'); aliasRegistryStub.get(() => aliasRegistry); }); afterEach(function () { - ajaxStub.restore(); - getConfigSpy.restore(); - aliasRegistryStub.restore(); + sandbox.restore(); }); it('should let registerSyncs run with invalid alias and aliasSync enabled', function () { @@ -136,6 +140,74 @@ describe('bidders created by newBidder', function () { expect(getConfigSpy.withArgs('userSync.filterSettings').calledOnce).to.equal(false); }); + describe('transaction IDs', () => { + Object.entries({ + 'be hidden': false, + 'not be hidden': true, + }).forEach(([t, allowed]) => { + const expectation = allowed ? (val) => expect(val).to.exist : (val) => expect(val).to.not.exist; + + function checkBidRequest(br) { + ['auctionId', 'transactionId'].forEach((prop) => expectation(br[prop])); + } + + function checkBidderRequest(br) { + expectation(br.auctionId); + br.bids.forEach(checkBidRequest); + } + + it(`should ${t} from the spec logic when the transmitTid activity is${allowed ? '' : ' not'} allowed`, () => { + spec.isBidRequestValid.callsFake(br => { + checkBidRequest(br); + return true; + }); + spec.buildRequests.callsFake((bidReqs, bidderReq) => { + checkBidderRequest(bidderReq); + bidReqs.forEach(checkBidRequest); + return {method: 'POST'}; + }); + spec.interpretResponse.callsFake(() => [ + { + requestId: 'bid', + cpm: 123, + ttl: 300, + creativeId: 'crid', + netRevenue: true, + currency: 'USD' + } + ]) + activityRules.isActivityAllowed.reset(); + activityRules.isActivityAllowed.callsFake(() => allowed); + + ajaxStub.callsFake((_, callback) => callback.success(null, {getResponseHeader: sinon.stub()})); + + const bidder = newBidder(spec); + + bidder.callBids({ + bidderCode: 'mockBidder', + auctionId: 'aid', + bids: [ + { + adUnitCode: 'mockAU', + bidId: 'bid', + transactionId: 'tid', + auctionId: 'aid' + } + ] + }, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + + sinon.assert.calledWithMatch(activityRules.isActivityAllowed, ACTIVITY_TRANSMIT_TID, { + componentType: MODULE_TYPE_BIDDER, + componentName: 'mockBidder' + }); + sinon.assert.calledWithMatch(addBidResponseStub, sinon.match.any, { + transactionId: 'tid', + auctionId: 'aid' + }) + }); + }); + }); + it('should handle bad bid requests gracefully', function () { const bidder = newBidder(spec); @@ -1212,16 +1284,27 @@ describe('validate bid response: ', function () { }; const fledgeAuctionConfig = { bidId: '1', + config: { + foo: 'bar' + } } describe('when response has FLEDGE auction config', function() { - let logInfoSpy; + let fledgeStub; - beforeEach(function () { - logInfoSpy = sinon.spy(utils, 'logInfo'); + function fledgeHook(next, ...args) { + fledgeStub(...args); + } + + before(() => { + addComponentAuction.before(fledgeHook); }); - afterEach(function () { - logInfoSpy.restore(); + after(() => { + addComponentAuction.getHooks({hook: fledgeHook}).remove(); + }) + + beforeEach(function () { + fledgeStub = sinon.stub(); }); it('should unwrap bids', function() { @@ -1243,8 +1326,8 @@ describe('validate bid response: ', function () { }); bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - expect(logInfoSpy.calledOnce).to.equal(true); - expect(logInfoSpy.firstCall.args[1]).to.equal(fledgeAuctionConfig); + expect(fledgeStub.calledOnce).to.equal(true); + sinon.assert.calledWith(fledgeStub, 'mock/placement', fledgeAuctionConfig.config); expect(addBidResponseStub.calledOnce).to.equal(true); expect(addBidResponseStub.firstCall.args[0]).to.equal('mock/placement'); }) @@ -1257,147 +1340,14 @@ describe('validate bid response: ', function () { }); bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - expect(logInfoSpy.calledOnce).to.equal(true); - expect(logInfoSpy.firstCall.args[1]).to.equal(fledgeAuctionConfig); + expect(fledgeStub.calledOnce).to.be.true; + sinon.assert.calledWith(fledgeStub, 'mock/placement', fledgeAuctionConfig.config); expect(addBidResponseStub.calledOnce).to.equal(false); }) }) }) }); -describe('preload mapping url hook', function() { - let fakeTranslationServer; - let getLocalStorageStub; - let adapterManagerStub; - let adUnits = [{ - code: 'midroll_1', - mediaTypes: { - video: { - context: 'adpod' - } - }, - bids: [ - { - bidder: 'sampleBidder1', - params: { - placementId: 14542875, - } - } - ] - }]; - - beforeEach(function () { - fakeTranslationServer = server; - getLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage'); - adapterManagerStub = sinon.stub(adapterManager, 'getBidAdapter'); - config.setConfig({ - 'adpod': { - 'brandCategoryExclusion': true - } - }); - adapterManagerStub.withArgs('sampleBidder1').returns({ - getSpec: function() { - return { - 'getMappingFileInfo': function() { - return { - url: 'http://sample.com', - refreshInDays: 7, - key: `sampleBidder1MappingFile` - } - } - } - } - }); - }); - - afterEach(function() { - getLocalStorageStub.restore(); - adapterManagerStub.restore(); - config.resetConfig(); - }); - - it('should preload mapping url file', function() { - getLocalStorageStub.returns(null); - preloadBidderMappingFile(sinon.spy(), adUnits); - expect(fakeTranslationServer.requests.length).to.equal(1); - }); - - it('should preload mapping url file for all bidders', function() { - let adUnits = [{ - code: 'midroll_1', - mediaTypes: { - video: { - context: 'adpod' - } - }, - bids: [ - { - bidder: 'sampleBidder1', - params: { - placementId: 14542875, - } - }, - { - bidder: 'sampleBidder2', - params: { - placementId: 123456, - } - } - ] - }]; - getLocalStorageStub.returns(null); - adapterManagerStub.withArgs('sampleBidder2').returns({ - getSpec: function() { - return { - 'getMappingFileInfo': function() { - return { - url: 'http://sample.com', - refreshInDays: 7, - key: `sampleBidder2MappingFile` - } - } - } - } - }); - preloadBidderMappingFile(sinon.spy(), adUnits); - expect(fakeTranslationServer.requests.length).to.equal(2); - - config.setConfig({ - 'adpod': { - 'brandCategoryExclusion': false - } - }); - preloadBidderMappingFile(sinon.spy(), adUnits); - expect(fakeTranslationServer.requests.length).to.equal(2); - }); - - it('should make ajax call to update mapping file if data found in localstorage is expired', function() { - let clock = sinon.useFakeTimers(utils.timestamp()); - getLocalStorageStub.returns(JSON.stringify({ - lastUpdated: utils.timestamp() - 8 * 24 * 60 * 60 * 1000, - mapping: { - 'iab-1': '1' - } - })); - preloadBidderMappingFile(sinon.spy(), adUnits); - expect(fakeTranslationServer.requests.length).to.equal(1); - clock.restore(); - }); - - it('should not make ajax call to update mapping file if data found in localstorage and is not expired', function () { - let clock = sinon.useFakeTimers(utils.timestamp()); - getLocalStorageStub.returns(JSON.stringify({ - lastUpdated: utils.timestamp(), - mapping: { - 'iab-1': '1' - } - })); - preloadBidderMappingFile(sinon.spy(), adUnits); - expect(fakeTranslationServer.requests.length).to.equal(0); - clock.restore(); - }); -}); - describe('bid response isValid', () => { describe('size check', () => { let req, index; diff --git a/test/spec/unit/core/consentHandler_spec.js b/test/spec/unit/core/consentHandler_spec.js index 082ff34f90c..98b317e0d36 100644 --- a/test/spec/unit/core/consentHandler_spec.js +++ b/test/spec/unit/core/consentHandler_spec.js @@ -1,4 +1,4 @@ -import {ConsentHandler} from '../../../../src/consentHandler.js'; +import {ConsentHandler, gvlidRegistry} from '../../../../src/consentHandler.js'; describe('Consent data handler', () => { let handler; @@ -57,3 +57,31 @@ describe('Consent data handler', () => { }) }); }) + +describe('gvlidRegistry', () => { + let registry; + beforeEach(() => { + registry = gvlidRegistry(); + }); + + it('returns undef when id cannoot be found', () => { + expect(registry.get('name')).to.eql({modules: {}}) + }); + + it('does not register null ids', () => { + registry.register('type', 'name', null); + expect(registry.get('type', 'name')).to.eql({modules: {}}); + }) + + it('can retrieve registered GVL IDs', () => { + registry.register('type', 'name', 123); + registry.register('otherType', 'name', 123); + expect(registry.get('name')).to.eql({gvlid: 123, modules: {type: 123, otherType: 123}}); + }); + + it('does not return `gvlid` if there is more than one', () => { + registry.register('type', 'name', 123); + registry.register('otherType', 'name', 321); + expect(registry.get('name')).to.eql({modules: {type: 123, otherType: 321}}) + }); +}) diff --git a/test/spec/unit/core/storageManager_spec.js b/test/spec/unit/core/storageManager_spec.js index 58bf8c9eb25..edead126c2c 100644 --- a/test/spec/unit/core/storageManager_spec.js +++ b/test/spec/unit/core/storageManager_spec.js @@ -1,14 +1,25 @@ import { - resetData, + deviceAccessRule, getCoreStorageManager, + newStorageManager, + resetData, + STORAGE_TYPE_COOKIES, + STORAGE_TYPE_LOCALSTORAGE, + storageAllowedRule, storageCallbacks, - getStorageManager, - newStorageManager, validateStorageEnforcement } from 'src/storageManager.js'; -import { config } from 'src/config.js'; +import adapterManager from 'src/adapterManager.js'; +import {config} from 'src/config.js'; import * as utils from 'src/utils.js'; import {hook} from '../../../../src/hook.js'; -import {VENDORLESS_GVLID} from '../../../../src/consentHandler.js'; +import {MODULE_TYPE_BIDDER, MODULE_TYPE_PREBID} from '../../../../src/activities/modules.js'; +import {ACTIVITY_ACCESS_DEVICE} from '../../../../src/activities/activities.js'; +import { + ACTIVITY_PARAM_COMPONENT_NAME, + ACTIVITY_PARAM_COMPONENT_TYPE, + ACTIVITY_PARAM_STORAGE_TYPE +} from '../../../../src/activities/params.js'; +import {activityParams} from '../../../../src/activities/activityParams.js'; describe('storage manager', function() { before(() => { @@ -34,7 +45,7 @@ describe('storage manager', function() { it('should add done callbacks to storageCallbacks array', function() { let noop = sinon.spy(); - const coreStorage = getStorageManager(); + const coreStorage = newStorageManager(); coreStorage.setCookie('foo', 'bar', null, null, null, noop); coreStorage.getCookie('foo', noop); @@ -50,36 +61,47 @@ describe('storage manager', function() { it('should allow bidder to access device if gdpr enforcement module is not included', function() { let deviceAccessSpy = sinon.spy(utils, 'hasDeviceAccess'); - const storage = getStorageManager(); + const storage = newStorageManager(); storage.setCookie('foo1', 'baz1'); expect(deviceAccessSpy.calledOnce).to.equal(true); deviceAccessSpy.restore(); }); - describe(`core storage`, () => { - let storage, validateHook; + describe(`accessDevice activity check`, () => { + let isAllowed; + + function mkManager(moduleType, moduleName) { + return newStorageManager({moduleType, moduleName}, {isAllowed}); + } beforeEach(() => { - storage = getCoreStorageManager(); - validateHook = sinon.stub().callsFake(function (next, ...args) { - next.apply(this, args); - }); - validateStorageEnforcement.before(validateHook, 99); + isAllowed = sinon.stub(); }); - afterEach(() => { - validateStorageEnforcement.getHooks({hook: validateHook}).remove(); - config.resetConfig(); - }) + it('should pass module type and name as activity params', () => { + mkManager(MODULE_TYPE_PREBID, 'mockMod').localStorageIsEnabled(); + sinon.assert.calledWith(isAllowed, ACTIVITY_ACCESS_DEVICE, sinon.match({ + [ACTIVITY_PARAM_COMPONENT_TYPE]: MODULE_TYPE_PREBID, + [ACTIVITY_PARAM_COMPONENT_NAME]: 'mockMod', + [ACTIVITY_PARAM_STORAGE_TYPE]: STORAGE_TYPE_LOCALSTORAGE + })); + }); - it('should respect (vendorless) consent enforcement', () => { - storage.localStorageIsEnabled(); - expect(validateHook.args[0][1]).to.equal(VENDORLESS_GVLID); // gvlid should be set to VENDORLESS_GVLID + it('should deny access if activity is denied', () => { + isAllowed.returns(false); + const mgr = mkManager(MODULE_TYPE_PREBID, 'mockMod'); + mgr.setDataInLocalStorage('testKey', 'val'); + expect(mgr.getDataFromLocalStorage('testKey')).to.not.exist; }); - it('should respect the deviceAccess flag', () => { - config.setConfig({deviceAccess: false}); - expect(storage.localStorageIsEnabled()).to.be.false + it('should use bidder aliases when possible', () => { + adapterManager.registerBidAdapter({callBids: sinon.stub(), getSpec: () => ({})}, 'mockBidder'); + adapterManager.aliasBidAdapter('mockBidder', 'mockAlias'); + const mgr = mkManager(MODULE_TYPE_BIDDER, 'mockBidder'); + config.runWithBidder('mockAlias', () => mgr.cookiesAreEnabled()); + sinon.assert.calledWith(isAllowed, ACTIVITY_ACCESS_DEVICE, sinon.match({ + [ACTIVITY_PARAM_COMPONENT_NAME]: 'mockAlias' + })) }) }) @@ -100,7 +122,7 @@ describe('storage manager', function() { }) it('should not throw if the localstorage is not accessible when setting/getting/removing from localstorage', function() { - const coreStorage = getStorageManager(); + const coreStorage = newStorageManager(); coreStorage.setDataInLocalStorage('key', 'value'); const val = coreStorage.getDataFromLocalStorage('key'); @@ -124,7 +146,7 @@ describe('storage manager', function() { }) it('should remove side-effect after checking', function () { - const storage = getStorageManager(); + const storage = newStorageManager(); localStorage.setItem('unrelated', 'dummy'); const val = storage.localStorageIsEnabled(); @@ -135,18 +157,31 @@ describe('storage manager', function() { }); }); - describe('when bidderSettings.allowStorage is defined', () => { + describe('deviceAccess control', () => { + afterEach(() => { + config.resetConfig() + }); + + it('should allow by default', () => { + config.resetConfig(); + expect(deviceAccessRule()).to.not.exist; + }); + + it('should deny access when set', () => { + config.setConfig({deviceAccess: false}); + sinon.assert.match(deviceAccessRule(), {allow: false}); + }) + }); + + describe('allowStorage access control rule', () => { const ALLOWED_BIDDER = 'allowed-bidder'; const ALLOW_KEY = 'storageAllowed'; - const COOKIE = 'test-cookie'; - const LS_KEY = 'test-localstorage'; - - function mockBidderSettings() { + function mockBidderSettings(val) { return { get(bidder, key) { if (bidder === ALLOWED_BIDDER && key === ALLOW_KEY) { - return true; + return val; } else { return undefined; } @@ -157,39 +192,72 @@ describe('storage manager', function() { Object.entries({ disallowed: ['denied_bidder', false], allowed: [ALLOWED_BIDDER, true] - }).forEach(([test, [bidderCode, shouldWork]]) => { - describe(`for ${test} bidders`, () => { - let mgr; - - beforeEach(() => { - mgr = newStorageManager({bidderCode: bidderCode}, {bidderSettings: mockBidderSettings()}); - }) - - afterEach(() => { - mgr.setCookie(COOKIE, 'delete', new Date().toUTCString()); - mgr.removeDataFromLocalStorage(LS_KEY); - }) - - const testDesc = (desc) => `should ${shouldWork ? '' : 'not'} ${desc}`; - - it(testDesc('allow cookies'), () => { - mgr.setCookie(COOKIE, 'value'); - expect(mgr.getCookie(COOKIE)).to.equal(shouldWork ? 'value' : null); - }); - - it(testDesc('allow localStorage'), () => { - mgr.setDataInLocalStorage(LS_KEY, 'value'); - expect(mgr.getDataFromLocalStorage(LS_KEY)).to.equal(shouldWork ? 'value' : null); - }); - - it(testDesc('report localStorage as available'), () => { - expect(mgr.hasLocalStorage()).to.equal(shouldWork); - }); - - it(testDesc('report cookies as available'), () => { - expect(mgr.cookiesAreEnabled()).to.equal(shouldWork); + }).forEach(([t, [bidderCode, isBidderAllowed]]) => { + describe(`for ${t} bidders`, () => { + Object.entries({ + 'all': { + configValues: [ + true, + ['html5', 'cookie'] + ], + shouldWork: { + html5: true, + cookie: true + } + }, + 'none': { + configValues: [ + false, + [] + ], + shouldWork: { + html5: false, + cookie: false + } + }, + 'localStorage': { + configValues: [ + 'html5', + ['html5'] + ], + shouldWork: { + html5: true, + cookie: false + } + }, + 'cookies': { + configValues: [ + 'cookie', + ['cookie'] + ], + shouldWork: { + html5: false, + cookie: true + } + } + }).forEach(([t, {configValues, shouldWork: {cookie, html5}}]) => { + describe(`when ${t} is allowed`, () => { + configValues.forEach(configValue => describe(`storageAllowed = ${configValue}`, () => { + Object.entries({ + [STORAGE_TYPE_LOCALSTORAGE]: 'allow localStorage', + [STORAGE_TYPE_COOKIES]: 'allow cookies' + }).forEach(([type, desc]) => { + const shouldWork = isBidderAllowed && ({html5, cookie})[type]; + it(`${shouldWork ? '' : 'NOT'} ${desc}`, () => { + const res = storageAllowedRule(activityParams(MODULE_TYPE_BIDDER, bidderCode, { + [ACTIVITY_PARAM_STORAGE_TYPE]: type + }), mockBidderSettings(configValue)); + if (shouldWork) { + expect(res).to.not.exist; + } else { + sinon.assert.match(res, {allow: false}); + } + }); + }) + })); + }); }); }); }); - }) + }); }); diff --git a/test/spec/unit/core/targeting_spec.js b/test/spec/unit/core/targeting_spec.js index f9fe2e398b0..f16d6208087 100644 --- a/test/spec/unit/core/targeting_spec.js +++ b/test/spec/unit/core/targeting_spec.js @@ -334,12 +334,6 @@ describe('targeting tests', function () { bidExpiryStub.restore(); }); - it('should filter out NO_BID bids', () => { - bidsReceived = [mkBid(sampleBid, CONSTANTS.STATUS.NO_BID)]; - const tg = targetingInstance.getAllTargeting(); - expect(tg[bidsReceived[0].adUnitCode]).to.eql({}); - }); - describe('when handling different adunit targeting value types', function () { const adUnitCode = '/123456/header-bid-tag-0'; const adServerTargeting = {}; diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index 0867cb0e399..5c361d186c0 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -25,6 +25,7 @@ import {stubAuctionIndex} from '../../helpers/indexStub.js'; import {createBid} from '../../../src/bidfactory.js'; import {enrichFPD} from '../../../src/fpd/enrichment.js'; import {mockFpdEnrichments} from '../../helpers/fpd.js'; + var assert = require('chai').assert; var expect = require('chai').expect; @@ -888,16 +889,32 @@ describe('Unit: Prebid Module', function () { it('should only apply price granularity if bid media type matches', function () { initTestConfig({ - adUnits: [ createAdUnit('div-gpt-ad-1460505748561-0', 'video') ], + adUnits: [createAdUnit('div-gpt-ad-1460505748561-0')], adUnitCodes: ['div-gpt-ad-1460505748561-0'] }); - response = videoResponse; + response = bannerResponse; response.tags[0].ads[0].cpm = 3.4288; auction.callBids(cbTimeout); let bidTargeting = targeting.getAllTargeting(); - expect(bidTargeting['div-gpt-ad-1460505748561-0'][CONSTANTS.TARGETING_KEYS.PRICE_BUCKET]).to.equal('3.00'); + expect(bidTargeting['div-gpt-ad-1460505748561-0'][CONSTANTS.TARGETING_KEYS.PRICE_BUCKET]).to.equal('3.25'); + + if (FEATURES.VIDEO) { + ajaxStub.restore(); + + initTestConfig({ + adUnits: [createAdUnit('div-gpt-ad-1460505748561-0', 'video')], + adUnitCodes: ['div-gpt-ad-1460505748561-0'] + }); + + response = videoResponse; + response.tags[0].ads[0].cpm = 3.4288; + + auction.callBids(cbTimeout); + let bidTargeting = targeting.getAllTargeting(); + expect(bidTargeting['div-gpt-ad-1460505748561-0'][CONSTANTS.TARGETING_KEYS.PRICE_BUCKET]).to.equal('3.00'); + } }); }); @@ -2109,7 +2126,9 @@ describe('Unit: Prebid Module', function () { $$PREBID_GLOBAL$$.requestBids({ adUnits: fullAdUnit }); - expect(auctionArgs.adUnits[0].sizes).to.deep.equal([[640, 480]]); + expect(auctionArgs.adUnits[0].sizes).to.deep.equal( + FEATURES.VIDEO ? [[640, 480]] : [[300, 250]] + ); expect(auctionArgs.adUnits[0].mediaTypes.video.playerSize).to.deep.equal([[640, 480]]); expect(auctionArgs.adUnits[0].mediaTypes.native.image.sizes).to.deep.equal([150, 150]); expect(auctionArgs.adUnits[0].mediaTypes.native.icon.sizes).to.deep.equal([75, 75]); @@ -2142,45 +2161,47 @@ describe('Unit: Prebid Module', function () { expect(auctionArgs.adUnits[0].sizes).to.deep.equal([[300, 250]]); expect(auctionArgs.adUnits[0].mediaTypes.video).to.exist; - let mixedAdUnit = [{ - code: 'test3', - bids: [], - sizes: [[300, 250], [300, 600]], - mediaTypes: { - video: { - context: 'outstream', - playerSize: [[400, 350]] - }, - native: { - image: { - aspect_ratios: [200, 150], - required: true + if (FEATURES.VIDEO) { + let mixedAdUnit = [{ + code: 'test3', + bids: [], + sizes: [[300, 250], [300, 600]], + mediaTypes: { + video: { + context: 'outstream', + playerSize: [[400, 350]] + }, + native: { + image: { + aspect_ratios: [200, 150], + required: true + } } } - } - }]; - $$PREBID_GLOBAL$$.requestBids({ - adUnits: mixedAdUnit - }); - expect(auctionArgs.adUnits[0].sizes).to.deep.equal([[400, 350]]); - expect(auctionArgs.adUnits[0].mediaTypes.video).to.exist; + }]; + $$PREBID_GLOBAL$$.requestBids({ + adUnits: mixedAdUnit + }); + expect(auctionArgs.adUnits[0].sizes).to.deep.equal([[400, 350]]); + expect(auctionArgs.adUnits[0].mediaTypes.video).to.exist; - let altVideoPlayerSize = [{ - code: 'test4', - bids: [], - sizes: [[600, 600]], - mediaTypes: { - video: { - playerSize: [640, 480] + let altVideoPlayerSize = [{ + code: 'test4', + bids: [], + sizes: [[600, 600]], + mediaTypes: { + video: { + playerSize: [640, 480] + } } - } - }]; - $$PREBID_GLOBAL$$.requestBids({ - adUnits: altVideoPlayerSize - }); - expect(auctionArgs.adUnits[0].sizes).to.deep.equal([[640, 480]]); - expect(auctionArgs.adUnits[0].mediaTypes.video.playerSize).to.deep.equal([[640, 480]]); - expect(auctionArgs.adUnits[0].mediaTypes.video).to.exist; + }]; + $$PREBID_GLOBAL$$.requestBids({ + adUnits: altVideoPlayerSize + }); + expect(auctionArgs.adUnits[0].sizes).to.deep.equal([[640, 480]]); + expect(auctionArgs.adUnits[0].mediaTypes.video.playerSize).to.deep.equal([[640, 480]]); + expect(auctionArgs.adUnits[0].mediaTypes.video).to.exist; + } }); it('should normalize adUnit.sizes and adUnit.mediaTypes.banner.sizes', function () { @@ -2285,41 +2306,43 @@ describe('Unit: Prebid Module', function () { expect(auctionArgs.adUnits[0].mediaTypes.banner).to.be.undefined; assert.ok(logErrorSpy.calledWith('Detected a mediaTypes.banner object without a proper sizes field. Please ensure the sizes are listed like: [[300, 250], ...]. Removing invalid mediaTypes.banner object from request.')); - let badVideo1 = [{ - code: 'testb2', - bids: [], - sizes: [[600, 600]], - mediaTypes: { - video: { - playerSize: ['600x400'] + if (FEATURES.VIDEO) { + let badVideo1 = [{ + code: 'testb2', + bids: [], + sizes: [[600, 600]], + mediaTypes: { + video: { + playerSize: ['600x400'] + } } - } - }]; - $$PREBID_GLOBAL$$.requestBids({ - adUnits: badVideo1 - }); - expect(auctionArgs.adUnits[0].sizes).to.deep.equal([[600, 600]]); - expect(auctionArgs.adUnits[0].mediaTypes.video.playerSize).to.be.undefined; - expect(auctionArgs.adUnits[0].mediaTypes.video).to.exist; - assert.ok(logErrorSpy.calledWith('Detected incorrect configuration of mediaTypes.video.playerSize. Please specify only one set of dimensions in a format like: [[640, 480]]. Removing invalid mediaTypes.video.playerSize property from request.')); + }]; + $$PREBID_GLOBAL$$.requestBids({ + adUnits: badVideo1 + }); + expect(auctionArgs.adUnits[0].sizes).to.deep.equal([[600, 600]]); + expect(auctionArgs.adUnits[0].mediaTypes.video.playerSize).to.be.undefined; + expect(auctionArgs.adUnits[0].mediaTypes.video).to.exist; + assert.ok(logErrorSpy.calledWith('Detected incorrect configuration of mediaTypes.video.playerSize. Please specify only one set of dimensions in a format like: [[640, 480]]. Removing invalid mediaTypes.video.playerSize property from request.')); - let badVideo2 = [{ - code: 'testb3', - bids: [], - sizes: [[600, 600]], - mediaTypes: { - video: { - playerSize: [['300', '200']] + let badVideo2 = [{ + code: 'testb3', + bids: [], + sizes: [[600, 600]], + mediaTypes: { + video: { + playerSize: [['300', '200']] + } } - } - }]; - $$PREBID_GLOBAL$$.requestBids({ - adUnits: badVideo2 - }); - expect(auctionArgs.adUnits[0].sizes).to.deep.equal([[600, 600]]); - expect(auctionArgs.adUnits[0].mediaTypes.video.playerSize).to.be.undefined; - expect(auctionArgs.adUnits[0].mediaTypes.video).to.exist; - assert.ok(logErrorSpy.calledWith('Detected incorrect configuration of mediaTypes.video.playerSize. Please specify only one set of dimensions in a format like: [[640, 480]]. Removing invalid mediaTypes.video.playerSize property from request.')); + }]; + $$PREBID_GLOBAL$$.requestBids({ + adUnits: badVideo2 + }); + expect(auctionArgs.adUnits[0].sizes).to.deep.equal([[600, 600]]); + expect(auctionArgs.adUnits[0].mediaTypes.video.playerSize).to.be.undefined; + expect(auctionArgs.adUnits[0].mediaTypes.video).to.exist; + assert.ok(logErrorSpy.calledWith('Detected incorrect configuration of mediaTypes.video.playerSize. Please specify only one set of dimensions in a format like: [[640, 480]]. Removing invalid mediaTypes.video.playerSize property from request.')); + } if (FEATURES.NATIVE) { let badNativeImgSize = [{ @@ -2479,7 +2502,6 @@ describe('Unit: Prebid Module', function () { }]; let adUnitCodes = ['adUnit-code']; let auction = auctionModule.newAuction({adUnits, adUnitCodes, callback: function() {}, cbTimeout: timeout}); - adUnits[0]['mediaTypes'] = { native: {} }; adUnitCodes = ['adUnit-code']; let auction1 = auctionModule.newAuction({adUnits, adUnitCodes, callback: function() {}, cbTimeout: timeout}); @@ -3326,69 +3348,60 @@ describe('Unit: Prebid Module', function () { }); }); - describe('markWinningBidAsUsed', function () { - it('marks the bid object as used for the given adUnitCode/adId combination', function () { - // make sure the auction has "state" and does not reload the fixtures + if (FEATURES.VIDEO) { + describe('markWinningBidAsUsed', function () { const adUnitCode = '/19968336/header-bid-tag-0'; - const bidsReceived = $$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode(adUnitCode); - auction.getBidsReceived = function() { return bidsReceived.bids }; + let winningBid; - // mark the bid and verify the state has changed to RENDERED - const winningBid = targeting.getWinningBids(adUnitCode)[0]; - $$PREBID_GLOBAL$$.markWinningBidAsUsed({ adUnitCode, adId: winningBid.adId }); - const markedBid = find($$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode(adUnitCode).bids, - bid => bid.adId === winningBid.adId); + beforeEach(() => { + const bidsReceived = $$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode(adUnitCode); + auction.getBidsReceived = function() { return bidsReceived.bids }; - expect(markedBid.status).to.equal(CONSTANTS.BID_STATUS.RENDERED); - resetAuction(); - }); + // mark the bid and verify the state has changed to RENDERED + winningBid = targeting.getWinningBids(adUnitCode)[0]; + auction.getAuctionId = function() { return winningBid.auctionId }; + }) - it('try and mark the bid object, but fail because we supplied the wrong adId', function () { - const adUnitCode = '/19968336/header-bid-tag-0'; - const bidsReceived = $$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode(adUnitCode); - auction.getBidsReceived = function() { return bidsReceived.bids }; + afterEach(() => { + resetAuction(); + }) - const winningBid = targeting.getWinningBids(adUnitCode)[0]; - $$PREBID_GLOBAL$$.markWinningBidAsUsed({ adUnitCode, adId: 'miss' }); - const markedBid = find($$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode(adUnitCode).bids, - bid => bid.adId === winningBid.adId); + it('marks the bid object as used for the given adUnitCode/adId combination', function () { + // make sure the auction has "state" and does not reload the fixtures + $$PREBID_GLOBAL$$.markWinningBidAsUsed({ adUnitCode, adId: winningBid.adId }); + const markedBid = find($$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode(adUnitCode).bids, + bid => bid.adId === winningBid.adId); - expect(markedBid.status).to.not.equal(CONSTANTS.BID_STATUS.RENDERED); - resetAuction(); - }); + expect(markedBid.status).to.equal(CONSTANTS.BID_STATUS.RENDERED); + }); - it('marks the winning bid object as used for the given adUnitCode', function () { - // make sure the auction has "state" and does not reload the fixtures - const adUnitCode = '/19968336/header-bid-tag-0'; - const bidsReceived = $$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode(adUnitCode); - auction.getBidsReceived = function() { return bidsReceived.bids }; + it('try and mark the bid object, but fail because we supplied the wrong adId', function () { + $$PREBID_GLOBAL$$.markWinningBidAsUsed({ adUnitCode, adId: 'miss' }); + const markedBid = find($$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode(adUnitCode).bids, + bid => bid.adId === winningBid.adId); - // mark the bid and verify the state has changed to RENDERED - const winningBid = targeting.getWinningBids(adUnitCode)[0]; - $$PREBID_GLOBAL$$.markWinningBidAsUsed({ adUnitCode }); - const markedBid = find($$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode(adUnitCode).bids, - bid => bid.adId === winningBid.adId); + expect(markedBid.status).to.not.equal(CONSTANTS.BID_STATUS.RENDERED); + }); - expect(markedBid.status).to.equal(CONSTANTS.BID_STATUS.RENDERED); - resetAuction(); - }); + it('marks the winning bid object as used for the given adUnitCode', function () { + // make sure the auction has "state" and does not reload the fixtures + $$PREBID_GLOBAL$$.markWinningBidAsUsed({ adUnitCode }); + const markedBid = find($$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode(adUnitCode).bids, + bid => bid.adId === winningBid.adId); - it('marks a bid object as used for the given adId', function () { - // make sure the auction has "state" and does not reload the fixtures - const adUnitCode = '/19968336/header-bid-tag-0'; - const bidsReceived = $$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode(adUnitCode); - auction.getBidsReceived = function() { return bidsReceived.bids }; + expect(markedBid.status).to.equal(CONSTANTS.BID_STATUS.RENDERED); + }); - // mark the bid and verify the state has changed to RENDERED - const winningBid = targeting.getWinningBids(adUnitCode)[0]; - $$PREBID_GLOBAL$$.markWinningBidAsUsed({ adId: winningBid.adId }); - const markedBid = find($$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode(adUnitCode).bids, - bid => bid.adId === winningBid.adId); + it('marks a bid object as used for the given adId', function () { + // make sure the auction has "state" and does not reload the fixtures + $$PREBID_GLOBAL$$.markWinningBidAsUsed({ adId: winningBid.adId }); + const markedBid = find($$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode(adUnitCode).bids, + bid => bid.adId === winningBid.adId); - expect(markedBid.status).to.equal(CONSTANTS.BID_STATUS.RENDERED); - resetAuction(); + expect(markedBid.status).to.equal(CONSTANTS.BID_STATUS.RENDERED); + }); }); - }); + } describe('setTargetingForAst', function () { let targeting; @@ -3522,4 +3535,63 @@ describe('Unit: Prebid Module', function () { expect(bids[0].adId).to.equal('adid-1'); }); }); + + describe('deferred billing', function () { + const sandbox = sinon.createSandbox(); + + let adUnits = [ + { + code: 'adUnit-code-1', + mediaTypes: { banner: { sizes: [[300, 250], [300, 600]] } }, + transactionId: '1234567890', + bids: [ + { bidder: 'pubmatic', params: {placementId: '10433394'}, adUnitCode: 'adUnit-code-1' } + ] + }, + { + code: 'adUnit-code-2', + deferBilling: true, + mediaTypes: { banner: { sizes: [[300, 250], [300, 600]] } }, + transactionId: '0987654321', + bids: [ + { bidder: 'pubmatic', params: {placementId: '10433394'}, adUnitCode: 'adUnit-code-2' } + ] + } + ]; + + let winningBid1 = { adapterCode: 'pubmatic', bidder: 'pubmatic', params: {placementId: '10433394'}, adUnitCode: 'adUnit-code-1', transactionId: '1234567890', adId: 'abcdefg' } + let winningBid2 = { adapterCode: 'pubmatic', bidder: 'pubmatic', params: {placementId: '10433394'}, adUnitCode: 'adUnit-code-2', transactionId: '0987654321' } + let adUnitCodes = ['adUnit-code-1', 'adUnit-code-2']; + let auction = auctionModule.newAuction({adUnits, adUnitCodes, callback: function() {}, cbTimeout: 2000}); + + beforeEach(function () { + sandbox.spy(adapterManager, 'callBidWonBidder'); + sandbox.spy(adapterManager, 'callBidBillableBidder'); + sandbox.stub(auctionManager, 'getBidsReceived').returns([winningBid1]); + }); + + afterEach(function () { + sandbox.resetHistory(); + sandbox.restore(); + }); + + it('should by default invoke callBidWonBidder and callBidBillableBidder', function () { + auction.addWinningBid(winningBid1); + sinon.assert.calledOnce(adapterManager.callBidWonBidder); + sinon.assert.calledOnce(adapterManager.callBidBillableBidder); + }); + + it('should only invoke callBidWonBidder and NOT callBidBillableBidder if deferBilling is present and true within the winning adUnit object', function () { + auction.addWinningBid(winningBid2); + sinon.assert.calledOnce(adapterManager.callBidWonBidder); + sinon.assert.notCalled(adapterManager.callBidBillableBidder); + }); + + it('should invoke callBidBillableBidder when pbjs.triggerBilling is invoked', function () { + $$PREBID_GLOBAL$$.triggerBilling(winningBid1); + sinon.assert.calledOnce(auctionManager.getBidsReceived); + sinon.assert.notCalled(adapterManager.callBidWonBidder); + sinon.assert.calledOnce(adapterManager.callBidBillableBidder); + }); + }); }); diff --git a/test/spec/unit/utils/cpm_spec.js b/test/spec/unit/utils/cpm_spec.js index 9d104b04d09..7d63c53525e 100644 --- a/test/spec/unit/utils/cpm_spec.js +++ b/test/spec/unit/utils/cpm_spec.js @@ -1,11 +1,14 @@ import {adjustCpm} from '../../../../src/utils/cpm.js'; +import {ScopedSettings} from '../../../../src/bidderSettings.js'; +import {expect} from 'chai/index.js'; describe('adjustCpm', () => { const bidderCode = 'mockBidder'; let adjustmentFn, bs, index; beforeEach(() => { bs = { - get: sinon.stub() + get: sinon.stub(), + getOwn: sinon.stub() } index = { getBidRequest: sinon.stub() @@ -62,3 +65,55 @@ describe('adjustCpm', () => { }); }) }); + +describe('adjustAlternateBids', () => { + let bs; + afterEach(() => { + bs = null; + }); + + function runAdjustment(cpm, bidderCode, adapterCode) { + return adjustCpm(cpm, {bidderCode, adapterCode}, null, {bs: new ScopedSettings(() => bs)}); + } + + it('should fall back to the adapter adjustment fn when adjustAlternateBids is true', () => { + bs = { + adapter: { + adjustAlternateBids: true, + bidCpmAdjustment: function (cpm) { + return cpm * 2; + } + }, + bidder: {} + }; + expect(runAdjustment(1, 'bidder', 'adapter')).to.eql(2); + }); + + it('should NOT fall back to the adapter adjustment fn when adjustAlternateBids is not true', () => { + bs = { + adapter: { + bidCpmAdjustment(cpm) { + return cpm * 2 + } + } + } + expect(runAdjustment(1, 'bidder', 'adapter')).to.eql(1); + }); + + it('should prioritize bidder adjustment fn', () => { + bs = { + adapter: { + adjustAlternateBids: true, + bidCpmAdjustment(cpm) { + return cpm * 2 + } + }, + bidder: { + bidCpmAdjustment(cpm) { + return cpm * 3 + } + } + } + expect(runAdjustment(1, 'bidder', 'adapter')).to.eql(3); + }); +}); diff --git a/test/spec/unit/utils/promise_spec.js b/test/spec/unit/utils/promise_spec.js index a931b8bc9c4..bd8b0390b2e 100644 --- a/test/spec/unit/utils/promise_spec.js +++ b/test/spec/unit/utils/promise_spec.js @@ -19,94 +19,6 @@ describe('GreedyPromise', () => { }) }); - describe('unhandled rejections', () => { - let unhandled, done, stop; - - function reset(expectUnhandled) { - let pending = expectUnhandled; - let resolver; - unhandled.reset(); - unhandled.callsFake(() => { - pending--; - if (pending === 0) { - resolver(); - } - }) - done = new Promise((resolve) => { - resolver = resolve; - stop = function () { - if (expectUnhandled === 0) { - resolve() - } else { - resolver = resolve; - } - } - }) - } - - before(() => { - unhandled = sinon.stub(); - window.addEventListener('unhandledrejection', unhandled); - }); - - after(() => { - window.removeEventListener('unhandledrejection', unhandled); - }); - - function getUnhandledErrors() { - return unhandled.args.map((args) => args[0].reason); - } - - Object.entries({ - 'simple reject': [1, (P) => { P.reject('err'); stop() }], - 'caught reject': [0, (P) => P.reject('err').catch((e) => { stop(); return e })], - 'unhandled reject with finally': [1, (P) => P.reject('err').finally(() => 'finally')], - 'error handler that throws': [1, (P) => P.reject('err').catch((e) => { stop(); throw e })], - 'rejection handled later in the chain': [0, (P) => P.reject('err').then((v) => v).catch((e) => { stop(); return e })], - 'multiple errors in one chain': [1, (P) => P.reject('err').then((v) => v).catch((e) => e).then((v) => { stop(); return P.reject(v) })], - 'multiple errors in one chain, all handled': [0, (P) => P.reject('err').then((v) => v).catch((e) => e).then((v) => P.reject(v)).catch((e) => { stop(); return e })], - 'separate chains for rejection and handling': [1, (P) => { - const p = P.reject('err'); - p.catch((e) => { stop(); return e; }) - p.then((v) => v); - }], - 'separate rejections merged without handling': [2, (P) => { - const p1 = P.reject('err1'); - const p2 = P.reject('err2'); - p1.then(() => p2).finally(stop); - }], - 'separate rejections merged for handling': [0, (P) => { - const p1 = P.reject('err1'); - const p2 = P.reject('err2'); - P.all([p1, p2]).catch((e) => { stop(); return e }); - }], - // eslint-disable-next-line no-throw-literal - 'exception in resolver': [1, (P) => new P(() => { stop(); throw 'err'; })], - // eslint-disable-next-line no-throw-literal - 'exception in resolver, caught': [0, (P) => new P(() => { throw 'err' }).catch((e) => { stop(); return e })], - 'errors from nested promises': [1, (P) => new P((resolve) => setTimeout(() => { resolve(P.reject('err')); stop(); }))], - 'errors from nested promises, caught': [0, (P) => new P((resolve) => setTimeout(() => resolve(P.reject('err')))).catch((e) => { stop(); return e })], - }).forEach(([t, [expectUnhandled, op]]) => { - describe(`on ${t}`, () => { - it('should match vanilla Promises', () => { - let vanillaUnhandled; - reset(expectUnhandled); - op(Promise); - return done.then(() => { - vanillaUnhandled = getUnhandledErrors(); - reset(expectUnhandled); - op(GreedyPromise); - return done; - }).then(() => { - const actualUnhandled = getUnhandledErrors(); - expect(actualUnhandled.length).to.eql(expectUnhandled); - expect(actualUnhandled).to.eql(vanillaUnhandled); - }) - }) - }) - }); - }); - describe('idioms', () => { let makePromise, pendingFailure, pendingSuccess; @@ -172,9 +84,11 @@ describe('GreedyPromise', () => { 'chained Promise.reject': (P) => P.reject(pendingSuccess), 'chained Promise.reject on failure': (P) => P.reject(pendingFailure), 'simple Promise.all': (P) => P.all([makePromise(P, 'one'), makePromise(P, 'two')]), + 'empty Promise.all': (P) => P.all([]), 'Promise.all with scalars': (P) => P.all([makePromise(P, 'one'), 'two']), 'Promise.all with errors': (P) => P.all([makePromise(P, 'one'), makePromise(P, 'two'), makePromise(P, 'err', true)]), 'Promise.allSettled': (P) => P.allSettled([makePromise(P, 'one', true), makePromise(P, 'two'), makePromise(P, 'three', true)]), + 'empty Promise.allSettled': (P) => P.allSettled([]), 'Promise.allSettled with scalars': (P) => P.allSettled([makePromise(P, 'value'), 'scalar']), 'Promise.race that succeeds': (P) => P.race([makePromise(P, 'error', true, 10), makePromise(P, 'success')]), 'Promise.race that fails': (P) => P.race([makePromise(P, 'success', false, 10), makePromise(P, 'error', true)]), diff --git a/test/spec/userSync_spec.js b/test/spec/userSync_spec.js index 910ffe7b2d6..c403014fcd6 100644 --- a/test/spec/userSync_spec.js +++ b/test/spec/userSync_spec.js @@ -1,5 +1,13 @@ import { expect } from 'chai'; import { config } from 'src/config.js'; +import {ruleRegistry} from '../../src/activities/rules.js'; +import {ACTIVITY_SYNC_USER} from '../../src/activities/activities.js'; +import { + ACTIVITY_PARAM_COMPONENT, + ACTIVITY_PARAM_SYNC_TYPE, + ACTIVITY_PARAM_SYNC_URL +} from '../../src/activities/params.js'; +import {MODULE_TYPE_BIDDER} from '../../src/activities/modules.js'; // Use require since we need to be able to write to these vars const utils = require('../../src/utils'); let { newUserSync, USERSYNC_DEFAULT_CONFIG } = require('../../src/userSync'); @@ -14,12 +22,18 @@ describe('user sync', function () { let idPrefix = 'test-generated-id-'; let lastId = 0; let defaultUserSyncConfig = config.getConfig('userSync'); - function getUserSyncConfig(userSyncConfig) { - return Object.assign({}, defaultUserSyncConfig, userSyncConfig); + let regRule, isAllowed; + + function mkUserSync(deps) { + [regRule, isAllowed] = ruleRegistry(); + return newUserSync(Object.assign({ + regRule, isAllowed + }, deps)) } + function newTestUserSync(configOverrides, disableBrowserCookies) { const thisConfig = Object.assign({}, defaultUserSyncConfig, configOverrides); - return newUserSync({ + return mkUserSync({ config: thisConfig, browserSupportsCookies: !disableBrowserCookies, }) @@ -59,6 +73,22 @@ describe('user sync', function () { expect(triggerPixelStub.getCall(0).args[0]).to.exist.and.to.equal('http://example.com'); }); + it('should NOT fire a sync if a rule blocks syncUser', () => { + const userSync = newTestUserSync() + regRule(ACTIVITY_SYNC_USER, 'testRule', (params) => { + if ( + params[ACTIVITY_PARAM_COMPONENT] === `${MODULE_TYPE_BIDDER}.testBidder` && + params[ACTIVITY_PARAM_SYNC_TYPE] === 'image' && + params[ACTIVITY_PARAM_SYNC_URL] === 'http://example.com' + ) { + return {allow: false} + } + }) + userSync.registerSync('image', 'testBidder', 'http://example.com'); + userSync.syncUsers(); + expect(triggerPixelStub.called).to.be.false; + }) + it('should clear queue after sync', function () { const userSync = newTestUserSync(); userSync.syncUsers(); @@ -110,9 +140,10 @@ describe('user sync', function () { expect(insertUserSyncIframeStub.getCall(0).args[0]).to.equal('http://example.com/iframe'); }); - it('should only trigger syncs once per page per bidder', function () { + it('should stop triggering user syncs after bidderDone', function () { const userSync = newTestUserSync({ pixelEnabled: true }); userSync.registerSync('image', 'testBidder', 'http://example.com/1'); + userSync.bidderDone('testBidder'); userSync.syncUsers(); userSync.registerSync('image', 'testBidder', 'http://example.com/2'); userSync.registerSync('image', 'testBidder2', 'http://example.com/3'); @@ -370,14 +401,13 @@ describe('user sync', function () { userSync.registerSync('image', 'atestBidder', 'http://example.com/1'); userSync.registerSync('iframe', 'testBidder', 'http://example.com/iframe'); userSync.syncUsers(); - expect(logWarnStub.getCall(0).args[0]).to.exist; expect(triggerPixelStub.getCall(0)).to.not.be.null; expect(triggerPixelStub.getCall(0).args[0]).to.exist.and.to.equal('http://example.com/1'); expect(insertUserSyncIframeStub.getCall(0)).to.be.null; }); it('should still allow default image syncs if setConfig only defined iframe', function () { - const userSync = newUserSync({ + const userSync = mkUserSync({ config: config.getConfig('userSync'), browserSupportsCookies: true }); @@ -402,7 +432,7 @@ describe('user sync', function () { }); it('should not fire image pixel for a bidder if iframe pixel is fired for same bidder', function() { - const userSync = newUserSync({ + const userSync = mkUserSync({ config: config.getConfig('userSync'), browserSupportsCookies: true }); @@ -429,7 +459,7 @@ describe('user sync', function () { }); it('should override default image syncs if setConfig used image filter', function () { - const userSync = newUserSync({ + const userSync = mkUserSync({ config: config.getConfig('userSync'), browserSupportsCookies: true }); @@ -454,7 +484,7 @@ describe('user sync', function () { }); it('should override default image syncs if setConfig used all filter', function() { - const userSync = newUserSync({ + const userSync = mkUserSync({ config: config.getConfig('userSync'), browserSupportsCookies: true }); @@ -487,7 +517,7 @@ describe('user sync', function () { describe('canBidderRegisterSync', function () { describe('with filterSettings', function () { it('should return false if filter settings does not allow it', function () { - const userSync = newUserSync({ + const userSync = mkUserSync({ config: { filterSettings: { image: { @@ -504,7 +534,7 @@ describe('user sync', function () { expect(userSync.canBidderRegisterSync('iframe', 'otherTestBidder')).to.equal(false); }); it('should return false for iframe if there is no iframe filterSettings', function () { - const userSync = newUserSync({ + const userSync = mkUserSync({ config: { syncEnabled: true, filterSettings: { @@ -522,7 +552,7 @@ describe('user sync', function () { expect(userSync.canBidderRegisterSync('iframe', 'otherTestBidder')).to.equal(false); }); it('should return true if filter settings does allow it', function () { - const userSync = newUserSync({ + const userSync = mkUserSync({ config: { filterSettings: { image: { @@ -542,7 +572,7 @@ describe('user sync', function () { describe('almost deprecated - without filterSettings', function () { describe('enabledBidders contains testBidder', function () { it('should return false if type is iframe and iframeEnabled is false', function () { - const userSync = newUserSync({ + const userSync = mkUserSync({ config: { filterSettings: { iframe: { @@ -556,7 +586,7 @@ describe('user sync', function () { }); it('should return true if type is iframe and iframeEnabled is true', function () { - const userSync = newUserSync({ + const userSync = mkUserSync({ config: { pixelEnabled: true, iframeEnabled: true, @@ -567,7 +597,7 @@ describe('user sync', function () { }); it('should return false if type is image and pixelEnabled is false', function () { - const userSync = newUserSync({ + const userSync = mkUserSync({ config: { filterSettings: { image: { @@ -581,7 +611,7 @@ describe('user sync', function () { }); it('should return true if type is image and pixelEnabled is true', function () { - const userSync = newUserSync({ + const userSync = mkUserSync({ config: { pixelEnabled: true, iframeEnabled: true, @@ -594,7 +624,7 @@ describe('user sync', function () { describe('enabledBidders does not container testBidder', function () { it('should return false since testBidder is not in enabledBidders', function () { - const userSync = newUserSync({ + const userSync = mkUserSync({ config: { filterSettings: { image: { diff --git a/test/spec/utils_spec.js b/test/spec/utils_spec.js index 9199b259ad6..e26683074c8 100644 --- a/test/spec/utils_spec.js +++ b/test/spec/utils_spec.js @@ -1,8 +1,8 @@ -import { getAdServerTargeting } from 'test/fixtures/fixtures.js'; -import { expect } from 'chai'; +import {getAdServerTargeting} from 'test/fixtures/fixtures.js'; +import {expect} from 'chai'; import CONSTANTS from 'src/constants.json'; import * as utils from 'src/utils.js'; -import {memoize, deepEqual, waitForElementToLoad} from 'src/utils.js'; +import {deepEqual, memoize, waitForElementToLoad} from 'src/utils.js'; var assert = require('assert'); @@ -894,93 +894,24 @@ describe('Utils', function () { }); }); - describe('transformBidderParamKeywords', function () { - it('returns an array of objects when keyvalue is an array', function () { - let keywords = { - genre: ['rock', 'pop'] - }; - let result = utils.transformBidderParamKeywords(keywords); - expect(result).to.deep.equal([{ - key: 'genre', - value: ['rock', 'pop'] - }]); - }); - - it('returns an array of objects when keyvalue is a string', function () { - let keywords = { - genre: 'opera' - }; - let result = utils.transformBidderParamKeywords(keywords); - expect(result).to.deep.equal([{ - key: 'genre', - value: ['opera'] - }]); - }); - - it('returns an array of objects when keyvalue is a number', function () { - let keywords = { - age: 15 - }; - let result = utils.transformBidderParamKeywords(keywords); - expect(result).to.deep.equal([{ - key: 'age', - value: ['15'] - }]); - }); - - it('returns an array of objects when using multiple keys with values of differing types', function () { - let keywords = { - genre: 'classical', - mix: ['1', 2, '3', 4], - age: 10 - }; - let result = utils.transformBidderParamKeywords(keywords); - expect(result).to.deep.equal([{ - key: 'genre', - value: ['classical'] - }, { - key: 'mix', - value: ['1', '2', '3', '4'] - }, { - key: 'age', - value: ['10'] - }]); - }); - - it('returns an array of objects when the keyvalue uses an empty string', function() { - let keywords = { - test: [''], - test2: '' - }; - let result = utils.transformBidderParamKeywords(keywords); - expect(result).to.deep.equal([{ - key: 'test', - value: [''] - }, { - key: 'test2', - value: [''] - }]); - }); - - describe('insertElement', function () { - it('returns a node at the top of the target by default', function () { - const toInsert = document.createElement('div'); - const target = document.getElementsByTagName('body')[0]; - const inserted = utils.insertElement(toInsert, document, 'body'); - expect(inserted).to.equal(target.firstChild); - }); - it('returns a node at bottom of target if 4th argument is true', function () { - const toInsert = document.createElement('div'); - const target = document.getElementsByTagName('html')[0]; - const inserted = utils.insertElement(toInsert, document, 'html', true); - expect(inserted).to.equal(target.lastChild); - }); - it('returns a node at top of the head if no target is given', function () { - const toInsert = document.createElement('div'); - const target = document.getElementsByTagName('head')[0]; - const inserted = utils.insertElement(toInsert); - expect(inserted).to.equal(target.firstChild); - }); + describe('insertElement', function () { + it('returns a node at the top of the target by default', function () { + const toInsert = document.createElement('div'); + const target = document.getElementsByTagName('body')[0]; + const inserted = utils.insertElement(toInsert, document, 'body'); + expect(inserted).to.equal(target.firstChild); + }); + it('returns a node at bottom of target if 4th argument is true', function () { + const toInsert = document.createElement('div'); + const target = document.getElementsByTagName('html')[0]; + const inserted = utils.insertElement(toInsert, document, 'html', true); + expect(inserted).to.equal(target.lastChild); + }); + it('returns a node at top of the head if no target is given', function () { + const toInsert = document.createElement('div'); + const target = document.getElementsByTagName('head')[0]; + const inserted = utils.insertElement(toInsert); + expect(inserted).to.equal(target.firstChild); }); }); diff --git a/test/spec/videoCache_spec.js b/test/spec/videoCache_spec.js index a13028c966a..c7c0b2eb329 100644 --- a/test/spec/videoCache_spec.js +++ b/test/spec/videoCache_spec.js @@ -72,6 +72,29 @@ describe('The video cache', function () { config.resetConfig(); }); + describe('cache.timeout', () => { + let getAjax, cb; + beforeEach(() => { + getAjax = sinon.stub().callsFake(() => sinon.stub()); + cb = sinon.stub(); + }); + + it('should be respected', () => { + config.setConfig({ + cache: { + timeout: 1 + } + }); + store([{ vastUrl: 'my-mock-url.com' }], cb, getAjax); + sinon.assert.calledWith(getAjax, 1); + }); + + it('should use default when not specified', () => { + store([], cb, getAjax); + sinon.assert.calledWith(getAjax, undefined); + }) + }); + it('should execute the callback with a successful result when store() is called', function () { const uuid = 'c488b101-af3e-4a99-b538-00423e5a3371'; const callback = fakeServerCall( diff --git a/test/test_index.js b/test/test_index.js index 883f4d0590c..ce9b671be89 100644 --- a/test/test_index.js +++ b/test/test_index.js @@ -1,3 +1,25 @@ +[it, describe].forEach((ob) => { + ob.only = function () { + [ + 'describe.only and it.only are disabled unless you provide a single spec --file,', + 'because they can silently break the pipeline tests', + // eslint-disable-next-line no-console + ].forEach(l => console.error(l)) + throw new Error('do not use .only()') + }; +}); + +[it, describe].forEach((ob) => { + ob.skip = function () { + [ + 'describe.skip and it.skip are disabled,', + 'because they pollute the pipeline test output', + // eslint-disable-next-line no-console + ].forEach(l => console.error(l)) + throw new Error('do not use .skip()') + }; +}); + require('./test_deps.js'); var testsContext = require.context('.', true, /_spec$/);