diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index cfb29ebdfa9..69e13850258 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,6 +1,8 @@ ARG VARIANT="12" FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:${VARIANT} +RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | gpg --dearmor > /usr/share/keyrings/yarn-archive-keyring.gpg + # [Optional] Uncomment this section to install additional OS packages. # RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ # && apt-get -y install --no-install-recommends diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 0176b8317b3..104d9a38132 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -16,7 +16,8 @@ // Add the IDs of extensions you want installed when the container is created. "extensions": [ - "nickdodd79.gulptasks" + "nickdodd79.gulptasks", + "dbaeumer.vscode-eslint" ], // 9999 is web server, 9876 is karma 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/issue_tracker.yml b/.github/workflows/issue_tracker.yml index 4a9502e38c1..69cf4c5fc7f 100644 --- a/.github/workflows/issue_tracker.yml +++ b/.github/workflows/issue_tracker.yml @@ -14,7 +14,7 @@ jobs: steps: - name: Generate token id: generate_token - uses: tibdex/github-app-token@021a2405c7f990db57f5eae5397423dcc554159c + uses: tibdex/github-app-token@b62528385c34dbc9f38e5f4225ac829252d1ea92 with: app_id: ${{ secrets.ISSUE_APP_ID }} private_key: ${{ secrets.ISSUE_APP_PEM }} @@ -29,21 +29,30 @@ jobs: gh api graphql -f query=' query($org: String!, $number: Int!) { organization(login: $org){ - projectNext(number: $number) { + projectV2(number: $number) { id fields(first:100) { nodes { - id - name - settings + ... on ProjectV2Field { + id + name + } + ... on ProjectV2SingleSelectField { + id + name + options { + id + name + } + } } } } } }' -f org=$ORGANIZATION -F number=$PROJECT_NUMBER > project_data.json - echo 'PROJECT_ID='$(jq '.data.organization.projectNext.id' project_data.json) >> $GITHUB_ENV - echo 'DATE_FIELD_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name== "'"$DATE_FIELD"'") | .id' project_data.json) >> $GITHUB_ENV + echo 'PROJECT_ID='$(jq '.data.organization.projectV2.id' project_data.json) >> $GITHUB_ENV + echo 'DATE_FIELD_ID='$(jq '.data.organization.projectV2.fields.nodes[] | select(.name=="'"$DATE_FIELD"'") | .id' project_data.json) >> $GITHUB_ENV - name: Add issue to project env: @@ -52,9 +61,9 @@ jobs: run: | gh api graphql -f query=' mutation($project:ID!, $issue:ID!) { - addProjectNextItem(input: {projectId: $project, contentId: $issue}) { - projectNextItem { - id, + addProjectV2ItemById(input: {projectId: $project, contentId: $issue}) { + item { + id content { ... on Issue { createdAt @@ -67,8 +76,8 @@ jobs: } }' -f project=$PROJECT_ID -f issue=$ISSUE_ID > issue_data.json - echo 'ITEM_ID='$(jq '.data.addProjectNextItem.projectNextItem.id' issue_data.json) >> $GITHUB_ENV - echo 'ITEM_CREATION_DATE='$(jq '.data.addProjectNextItem.projectNextItem.content.createdAt' issue_data.json) >> $GITHUB_ENV + echo 'ITEM_ID='$(jq '.data.addProjectV2ItemById.item.id' issue_data.json) >> $GITHUB_ENV + echo 'ITEM_CREATION_DATE='$(jq '.data.addProjectV2ItemById.item.content.createdAt' issue_data.json | cut -c 2-11) >> $GITHUB_ENV - name: Set fields env: @@ -79,15 +88,17 @@ jobs: $project: ID! $item: ID! $date_field: ID! - $date_value: String! + $date_value: Date! ) { - set_creation_date: updateProjectNextItemField(input: { + set_creation_date: updateProjectV2ItemFieldValue(input: { projectId: $project itemId: $item fieldId: $date_field - value: $date_value + value: { + date: $date_value + } }) { - projectNextItem { + projectV2Item { id } } diff --git a/README.md b/README.md index bbc0d79ab41..fdbb5482ebf 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 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/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/hello_world.html b/integrationExamples/gpt/hello_world.html old mode 100755 new mode 100644 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 new file mode 100644 index 00000000000..142a7c39613 --- /dev/null +++ b/integrationExamples/gpt/neuwoRtdProvider_example.html @@ -0,0 +1,201 @@ + + + + + + + + + + +

Basic Prebid.js Example using neuwoRtdProvider

+
+ Looks like you're not following the testing environment setup, try accessing http://localhost:9999/integrationExamples/gpt/neuwoRtdProvider_example.html + after running commands in the prebid.js source folder that includes libraries/modules/neuwoRtdProvider.js + + npm ci + npm i -g gulp-cli + gulp serve --modules=rtdModule,neuwoRtdProvider,appnexusBidAdapter + +
+
+

Add token and url to use for Neuwo extension configuration

+ + + + +
+ +
Div-1
+
+ Ad spot div-1: This content will be replaced by prebid.js and/or related components once you click "Update" +
+ +
+ +
Div-2
+
+ Ad spot div-2: Replaces this text as well, if everything goes to plan + + +
+ + + + \ 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/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 @@ `; + // native.javascriptTrackers = ``; // break; } }); @@ -484,8 +499,8 @@ export const spec = { */ onBidWon: function (bid) { if (bid['nurl']) { - utils.triggerPixel(bid['nurl']) + utils.triggerPixel(bid['nurl']); } - } + }, }; registerBidder(spec); diff --git a/modules/displayioBidAdapter.js b/modules/displayioBidAdapter.js index c3c6597dd1b..d46cc8ee309 100644 --- a/modules/displayioBidAdapter.js +++ b/modules/displayioBidAdapter.js @@ -2,12 +2,14 @@ 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'; 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 +69,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'; @@ -110,7 +120,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..74a93ce086e 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,34 @@ 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 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 +61,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 +89,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 +142,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 +162,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 +182,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 +298,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 +387,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/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/emx_digitalBidAdapter.js b/modules/emx_digitalBidAdapter.js index eb69e76a837..99f313b9484 100644 --- a/modules/emx_digitalBidAdapter.js +++ b/modules/emx_digitalBidAdapter.js @@ -354,16 +354,23 @@ export const spec = { }, getUserSyncs: function (syncOptions, responses, gdprConsent, uspConsent) { const syncs = []; + const consentParams = []; if (syncOptions.iframeEnabled) { let url = 'https://biddr.brealtime.com/check.html'; if (gdprConsent && typeof gdprConsent.consentString === 'string') { // add 'gdpr' only if 'gdprApplies' is defined if (typeof gdprConsent.gdprApplies === 'boolean') { - url += `?gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; + consentParams.push(`gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`); } else { - url += `?gdpr_consent=${gdprConsent.consentString}`; + consentParams.push(`?gdpr_consent=${gdprConsent.consentString}`); } } + if (uspConsent && typeof uspConsent.consentString === 'string') { + consentParams.push(`usp=${uspConsent.consentString}`); + } + if (consentParams.length > 0) { + url = url + '?' + consentParams.join('&'); + } syncs.push({ type: 'iframe', url: url 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..4a00b97614b --- /dev/null +++ b/modules/eskimiBidAdapter.js @@ -0,0 +1,72 @@ +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER} from '../src/mediaTypes.js'; +import * as utils from '../src/utils.js'; +import {ortbConverter} from '../libraries/ortbConverter/converter.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; + +export const spec = { + code: BIDDER_CODE, + gvlid: GVLID, + supportedMediaTypes: [BANNER], + + isBidRequestValid: function (bid) { + return !!bid.params.placementId; + }, + + buildRequests(bidRequests, bidderRequest) { + const data = converter.toORTB({bidRequests, bidderRequest}) + + let 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; + } + + return [{ + method: 'POST', + url: ENDPOINT, + data, + options: {contentType: 'application/json;charset=UTF-8', withCredentials: false} + }] + }, + + interpretResponse(response, request) { + return converter.fromORTB({response: response.body, request: request.data}).bids; + }, + + /** + * 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); + } + } +} + +const converter = ortbConverter({ + context: { + netRevenue: DEFAULT_NET_REVENUE, + ttl: DEFAULT_BID_TTL, + currency: DEFAULT_CURRENCY, + mediaType: BANNER // TODO: support more types, we should set mtype on the winning bid + } +}); + +registerBidder(spec); diff --git a/modules/eskimiBidAdapter.md b/modules/eskimiBidAdapter.md new file mode 100644 index 00000000000..83ae87fd01b --- /dev/null +++ b/modules/eskimiBidAdapter.md @@ -0,0 +1,34 @@ +# Overview + +Module Name: ESKIMI Bidder Adapter +Module Type: Bidder Adapter +Maintainer: tech@eskimi.com + +# Description + +An adapter to get a bid from Eskimi DSP. + +# Test Parameters +```javascript + var adUnits = [{ + code: 'div-gpt-ad-1460505748561-0', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + + bids: [{ + bidder: 'eskimi', + params: { + placementId: 612 + } + }] + + }]; +``` + +Where: + +* placementId - Placement ID of the ad unit (required) + 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 7b684efab3c..34e14cd674a 100644 --- a/modules/feedadBidAdapter.js +++ b/modules/feedadBidAdapter.js @@ -7,7 +7,23 @@ import {ajax} from '../src/ajax.js'; * Version of the FeedAd bid adapter * @type {string} */ -const VERSION = '1.0.5'; +const VERSION = '1.0.6'; + +/** + * @typedef {object} FeedAdUserSync + * @inner + * + * @property {string} type + * @property {string} url + */ + +/** + * @typedef {object} FeedAdBidExtension + * @inner + * + * @property {FeedAdUserSync[]} pixels + * @property {FeedAdUserSync[]} iframes + */ /** * @typedef {object} FeedAdApiBidRequest @@ -41,7 +57,7 @@ const VERSION = '1.0.5'; * @property {string} requestId - bids[].bidId * @property {number} ttl - Time to live for this ad * @property {number} width - Width of creative returned in [].ad - * @property {object} [ext] - an extension object + * @property {FeedAdBidExtension} [ext] - an extension object */ /** @@ -63,6 +79,14 @@ const VERSION = '1.0.5'; * @property [device_platform] {1|2|3} 1 - Android | 2 - iOS | 3 - Windows */ +/** + * @typedef {object} FeedAdServerResponse + * @extends ServerResponse + * @inner + * + * @property {FeedAdApiBidResponse[]} body - the body of a FeedAd server response + */ + /** * The IAB TCF 2.0 vendor ID for the FeedAd GmbH */ @@ -228,7 +252,7 @@ function buildRequests(validBidRequests, bidderRequest) { /** * Adapts the FeedAd server response to Prebid format - * @param {ServerResponse} serverResponse - the FeedAd server response + * @param {FeedAdServerResponse} serverResponse - the FeedAd server response * @param {BidRequest} request - the initial bid request * @returns {Bid[]} the FeedAd bids */ @@ -296,24 +320,20 @@ function trackingHandlerFactory(klass) { /** * Reads the user syncs off the server responses and converts them into Prebid.JS format * @param {SyncOptions} syncOptions - * @param {ServerResponse[]} serverResponses + * @param {FeedAdServerResponse[]} serverResponses * @param gdprConsent * @param uspConsent */ function getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent) { - return serverResponses.map(response => { - // validate response format - const ext = deepAccess(response, 'body.ext', []); - if (ext == null) { - return null; - } - return ext; + return serverResponses.flatMap(response => { + // merge all response bodies into one + const body = response.body; + return isArray(body) ? body : []; }) - .filter(ext => ext != null) - .flatMap(extension => { + .flatMap(/** @param {FeedAdApiBidResponse} bidResponse */ bidResponse => { // extract user syncs from extension - const pixels = syncOptions.pixelEnabled && extension.pixels ? extension.pixels : []; - const iframes = syncOptions.iframeEnabled && extension.iframes ? extension.iframes : []; + const pixels = (syncOptions.pixelEnabled && bidResponse?.ext?.pixels) ? bidResponse.ext.pixels : []; + const iframes = (syncOptions.iframeEnabled && bidResponse?.ext?.iframes) ? bidResponse.ext.iframes : []; return pixels.concat(...iframes); }) .reduce((syncs, sync) => { 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..97b9847eae4 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,13 +40,12 @@ 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; @@ -53,6 +53,21 @@ export const spec = { 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({ @@ -141,8 +156,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/fpdModule/index.md b/modules/fpdModule/index.md index 638c966883a..238881db96b 100644 --- a/modules/fpdModule/index.md +++ b/modules/fpdModule/index.md @@ -16,9 +16,11 @@ Validation Submodule: - verify that certain OpenRTB attributes are not specified - optionally suppress user FPD based on the existence of _pubcid_optout +Topic Submodule: +- populate first party/third party topics data onto user.data in bid stream. 1. Module initializes on first load and set bidRequestHook -2. When hook runs, corresponding submodule init functions are run to perform enrichments/validations dependant on submodule +2. When hook runs, corresponding submodule init functions are run to perform enrichments/validations/topics dependant on submodule 3. After hook complete, it is disabled - meaning module only runs on first auction 4. To reinitiate the module, run pbjs.refreshFPD(), which allows module to rerun as if initial load @@ -43,4 +45,5 @@ pbjs.setConfig({ At least one of the submodules must be included in order to successfully run the corresponding above operations. enrichmentFpdModule -validationFpdModule \ No newline at end of file +validationFpdModule +topicsFpdModule \ No newline at end of file diff --git a/modules/freewheel-sspBidAdapter.js b/modules/freewheel-sspBidAdapter.js index 764e3d6e6c0..c0524ca3cd6 100644 --- a/modules/freewheel-sspBidAdapter.js +++ b/modules/freewheel-sspBidAdapter.js @@ -1,6 +1,7 @@ -import { logWarn, isArray } 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'; const BIDDER_CODE = 'freewheel-ssp'; @@ -213,6 +214,27 @@ function getAPIName(componentId) { return componentId.replace('-', ''); } +function getBidFloor(bid, config) { + if (!isFn(bid.getFloor)) { + return deepAccess(bid, 'params.bidfloor', 0); + } + + try { + const bidFloor = bid.getFloor({ + currency: getFloorCurrency(config), + mediaType: typeof bid.mediaTypes['banner'] == 'object' ? 'banner' : 'video', + size: '*', + }); + return bidFloor.floor; + } catch (e) { + return -1; + } +} + +function getFloorCurrency(config) { + return config.getConfig('floors.data.currency') != null ? config.getConfig('floors.data.currency') : 'USD'; +} + function formatAdHTML(bid, size) { var integrationType = bid.params.format; @@ -317,13 +339,18 @@ export const spec = { var zone = currentBidRequest.params.zoneId; var timeInMillis = new Date().getTime(); var keyCode = hashcode(zone + '' + timeInMillis); + var bidfloor = getBidFloor(currentBidRequest, config); + var requestParams = { reqType: 'AdsSetup', - protocolVersion: '2.0', + protocolVersion: '4.2', zoneId: zone, componentId: 'prebid', componentSubId: getComponentId(currentBidRequest.params.format), timestamp: timeInMillis, + _fw_bidfloor: (bidfloor > 0) ? bidfloor : 0, + _fw_bidfloorcur: (bidfloor > 0) ? getFloorCurrency(config) : '', + pbjs_version: '$prebid.version$', pKey: keyCode }; @@ -345,10 +372,31 @@ 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) { - requestParams.schain = schain; + try { + requestParams.schain = JSON.stringify(schain); + } catch (error) { + logWarn('PREBID - ' + BIDDER_CODE + ': Unable to stringify the schain: ' + error); + } + } + + 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; @@ -385,6 +433,14 @@ 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 : 'instream'; + var videoPlacement = currentBidRequest.mediaTypes.video.placement ? currentBidRequest.mediaTypes.video.placement : 1; + requestParams.video_context = videoContext; + requestParams.video_placement = videoPlacement; + } + return { method: 'GET', url: FREEWHEEL_ADSSETUP, @@ -479,26 +535,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/gdprEnforcement.js b/modules/gdprEnforcement.js index 9553ad0586a..798dfc848da 100644 --- a/modules/gdprEnforcement.js +++ b/modules/gdprEnforcement.js @@ -11,7 +11,13 @@ import {getHook} from '../src/hook.js'; import {validateStorageEnforcement} from '../src/storageManager.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_CORE, MODULE_TYPE_RTD, + MODULE_TYPE_UID +} from '../src/activities/modules.js'; export const STRICT_STORAGE_ENFORCEMENT = 'strictStorageEnforcement'; @@ -49,77 +55,56 @@ const analyticsBlocked = []; 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 +]; /** - * 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_CORE) { + 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 && !fallbackFn) { + 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; -} - -/** - * 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 - */ -function getGvlidForUserIdModule(userIdModule) { - return (typeof userIdModule === 'object' ? userIdModule.gvlid : null); + return 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 + * Retrieve GVL IDs that are dynamically set on analytics adapters. */ -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); @@ -185,30 +170,33 @@ 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} moduleType type of the module * @param {string=} moduleName name of the module * @param result + * @param validate */ -export function deviceAccessHook(fn, gvlid, moduleName, result, {validate = validateRules} = {}) { +export function deviceAccessHook(fn, moduleType, 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) { + } else if (moduleType === MODULE_TYPE_CORE && !strictStorageEnforcement) { // for vendorless (core) storage, do not enforce rules unless strictStorageEnforcement is set result.valid = true; } else { const consentData = gdprDataHandler.getConsentData(); + let gvlid; 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); + gvlid = getGvlid(moduleType, curBidder); } else { - gvlid = getGvlid(moduleName) || gvlid; + gvlid = getGvlid(moduleType, moduleName) } const curModule = moduleName || curBidder; let isAllowed = validate(purpose1Rule, consentData, curModule, gvlid,); @@ -223,7 +211,7 @@ export function deviceAccessHook(fn, gvlid, moduleName, result, {validate = vali result.valid = true; } } - fn.call(this, gvlid, moduleName, result); + fn.call(this, moduleType, moduleName, result); } /** @@ -235,7 +223,7 @@ export function userSyncHook(fn, ...args) { const consentData = gdprDataHandler.getConsentData(); const curBidder = config.getCurrentBidder(); if (shouldEnforce(consentData, 1, curBidder)) { - const gvlid = getGvlid(curBidder); + const gvlid = getGvlid(MODULE_TYPE_BIDDER, curBidder); let isAllowed = validateRules(purpose1Rule, consentData, curBidder, gvlid); if (isAllowed) { fn.call(this, ...args); @@ -257,8 +245,8 @@ export function userSyncHook(fn, ...args) { 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; + const gvlid = getGvlid(MODULE_TYPE_UID, moduleName); let isAllowed = validateRules(purpose1Rule, consentData, moduleName, gvlid); if (isAllowed) { return submodule; @@ -286,7 +274,7 @@ export function makeBidRequestsHook(fn, adUnits, ...args) { adUnits.forEach(adUnit => { adUnit.bids = adUnit.bids.filter(bid => { const currBidder = bid.bidder; - const gvlId = getGvlid(currBidder); + const gvlId = getGvlid(MODULE_TYPE_BIDDER, currBidder); if (includes(biddersBlocked, currBidder)) return false; const isAllowed = !!validateRules(purpose2Rule, consentData, currBidder, gvlId); if (!isAllowed) { @@ -316,7 +304,7 @@ export function enableAnalyticsHook(fn, config) { } config = config.filter(conf => { const analyticsAdapterCode = conf.provider; - const gvlid = getGvlid(analyticsAdapterCode, conf); + const gvlid = getGvlid(MODULE_TYPE_ANALYTICS, analyticsAdapterCode, () => getGvlidFromAnalyticsAdapter(analyticsAdapterCode, conf)); const isAllowed = !!validateRules(purpose7Rule, consentData, analyticsAdapterCode, gvlid); if (!isAllowed) { analyticsBlocked.push(analyticsAdapterCode); diff --git a/modules/glimpseBidAdapter.js b/modules/glimpseBidAdapter.js index bbb4dbb30cd..53dc0bd3e1a 100644 --- a/modules/glimpseBidAdapter.js +++ b/modules/glimpseBidAdapter.js @@ -1,5 +1,4 @@ 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 { @@ -12,10 +11,7 @@ import { const GVLID = 1012; const BIDDER_CODE = 'glimpse'; -const storageManager = getStorageManager({ - gvlid: GVLID, - bidderCode: BIDDER_CODE, -}); +const storageManager = getStorageManager({bidderCode: BIDDER_CODE}); const ENDPOINT = 'https://market.glimpsevault.io/public/v1/prebid'; const LOCAL_STORAGE_KEY = { vault: { @@ -102,7 +98,7 @@ function getReferer(bidderRequest) { function buildQuery(bidderRequest) { let url = appendQueryParam(ENDPOINT, 'ver', '$prebid.version$'); - const timeout = config.getConfig('bidderTimeout'); + const timeout = bidderRequest.timeout; url = appendQueryParam(url, 'tmax', timeout); if (gdprApplies(bidderRequest)) { 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/gnetBidAdapter.js b/modules/gnetBidAdapter.js index 8bab043d0db..0b02a29c0d4 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, 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..38ed14dd295 --- /dev/null +++ b/modules/greenbidsAnalyticsAdapter.js @@ -0,0 +1,181 @@ +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://europe-west2-greenbids-357713.cloudfunctions.net/publisher-analytics-endpoint'; + +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.sampled = true; + if (typeof config.options.sampling === 'number') { + analyticsOptions.sampled = Math.random() < parseFloat(config.options.sampling); + } + + 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}) { + if (analyticsOptions.sampled) { + 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 aa6c0ab668f..a043483d9b0 100644 --- a/modules/gridBidAdapter.js +++ b/modules/gridBidAdapter.js @@ -6,7 +6,10 @@ import { generateUUID, mergeDeep, logWarn, - parseUrl, isArray, isNumber + parseUrl, + isArray, + isNumber, + isStr } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { Renderer } from '../src/Renderer.js'; @@ -31,7 +34,7 @@ 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 - ', @@ -52,7 +55,12 @@ const ALIAS_CONFIG = { bidResponseExternal: { netRevenue: false } - } + }, + 'gridNM': { + defaultParams: { + multiRequest: true + } + }, }; let hasSynced = false; @@ -60,7 +68,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 +91,6 @@ export const spec = { return null; } let pageKeywords = null; - let jwpseg = null; let content = null; let schain = null; let userIdAsEids = null; @@ -91,10 +98,10 @@ export const spec = { let userExt = null; let endpoint = null; let forceBidderName = false; - let {bidderRequestId, auctionId, gdprConsent, uspConsent, timeout, refererInfo} = bidderRequest || {}; + let {bidderRequestId, auctionId, 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 = []; @@ -127,17 +134,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 +154,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 +187,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; } @@ -210,23 +219,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); @@ -274,26 +272,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; } + user = null; + const ortb2UserData = deepAccess(bidderRequest, 'ortb2.user.data'); if (ortb2UserData && ortb2UserData.length) { - user = request.user || { data: [] }; - user = mergeDeep(user, { - data: [...ortb2UserData] - }); + user = { data: [...ortb2UserData] }; } if (gdprConsent && gdprConsent.consentString) { @@ -367,11 +355,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; } @@ -598,27 +597,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) { @@ -648,22 +661,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) => { 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/gumgumBidAdapter.js b/modules/gumgumBidAdapter.js index 93306984ab1..8a6c4efa7fc 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 => { @@ -387,6 +378,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; } @@ -403,7 +405,7 @@ function buildRequests(validBidRequests, bidderRequest) { sizes, url: BID_ENDPOINT, method: 'GET', - data: Object.assign(data, _getBrowserParams(topWindowUrl), _getDigiTrustQueryParams(userId)) + data: Object.assign(data, _getBrowserParams(topWindowUrl)) }); }); return bids; @@ -583,6 +585,7 @@ function getUserSyncs(syncOptions, serverResponses) { export const spec = { code: BIDDER_CODE, + gvlid: 61, aliases: ALIAS_BIDDER_CODE, isBidRequestValid, buildRequests, 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 2f10245cd59..596bf9611e6 100644 --- a/modules/hadronIdSystem.js +++ b/modules/hadronIdSystem.js @@ -8,18 +8,21 @@ 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 {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. - * @param {String} param + * @param {String|function} param * @param {String} defaultVal + * @param arg */ function paramOrDefault(param, defaultVal, arg) { if (isFn(param)) { @@ -46,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 @@ -53,11 +57,11 @@ export const hadronIdSubmodule = { * @returns {Object} */ decode(value) { - let hadronId = storage.getDataFromLocalStorage('auHadronId'); + const hadronId = storage.getDataFromLocalStorage(HADRONID_LOCAL_NAME); if (isStr(hadronId)) { return {hadronId: hadronId}; } - return (value && typeof value['hadronId'] === 'string') ? { 'hadronId': value['hadronId'] } : undefined; + return (value && typeof value['hadronId'] === 'string') ? {'hadronId': value['hadronId']} : undefined; }, /** * performs action to obtain id and return a value in the callback's response argument @@ -70,37 +74,40 @@ export const hadronIdSubmodule = { config.params = {}; } const partnerId = config.params.partnerId | 0; - - const url = urlAddParams( - paramOrDefault(config.params.url, DEFAULT_HADRON_URL_ENDPOINT, config.params.urlArg), - `partner_id=${partnerId}&_it=prebid` - ); - + let hadronId = storage.getDataFromLocalStorage(HADRONID_LOCAL_NAME); + if (isStr(hadronId)) { + return {id: {hadronId}}; + } const resp = function (callback) { - let hadronId = storage.getDataFromLocalStorage('auHadronId'); - if (isStr(hadronId)) { - const responseObj = {hadronId: hadronId}; - callback(responseObj); - } else { - const callbacks = { - success: response => { - let responseObj; - if (response) { - try { - responseObj = JSON.parse(response); - } catch (error) { - logError(error); - } + let responseObj = {}; + const callbacks = { + success: response => { + if (response) { + try { + responseObj = JSON.parse(response); + } catch (error) { + logError(error); } - callback(responseObj); - }, - error: error => { - logError(`${MODULE_NAME}: ID fetch encountered an error`, error); - callback(); + logInfo(`Response from backend is ${responseObj}`); + hadronId = responseObj['hadronId']; + storage.setDataInLocalStorage(HADRONID_LOCAL_NAME, hadronId); + responseObj = {id: {hadronId}}; } - }; - ajax(url, callbacks, undefined, {method: 'GET'}); - } + callback(responseObj); + }, + error: error => { + logError(`${MODULE_NAME}: ID fetch encountered an error`, error); + callback(); + } + }; + logInfo('HadronId not found in storage, calling backend...'); + const url = urlAddParams( + // config.params.url and config.params.urlArg are not documented + // since their use is for debugging purposes only + paramOrDefault(config.params.url, DEFAULT_HADRON_URL_ENDPOINT, config.params.urlArg), + `partner_id=${partnerId}&_it=prebid` + ); + ajax(url, callbacks, undefined, {method: 'GET'}); }; return {callback: resp}; } 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 be5a7a044c3..92b0a1c18db 100644 --- a/modules/holidBidAdapter.js +++ b/modules/holidBidAdapter.js @@ -85,11 +85,12 @@ export const spec = { } const syncs = [] + const bidders = getBidders(serverResponse) - if (optionsType.iframeEnabled) { + if (optionsType.iframeEnabled && bidders) { const queryParams = [] - queryParams.push('bidders=' + getBidders(serverResponse)) + queryParams.push('bidders=' + bidders) queryParams.push('gdpr=' + +gdprConsent.gdprApplies) queryParams.push('gdpr_consent=' + gdprConsent.consentString) queryParams.push('usp_consent=' + (uspConsent || '')) @@ -107,6 +108,8 @@ export const spec = { return syncs } + + return [] }, } @@ -136,10 +139,12 @@ function getImp(bid) { function getBidders(serverResponse) { const bidders = serverResponse - .map((res) => Object.keys(res.body.ext.responsetimemillis)) + .map((res) => Object.keys(res.body.ext.responsetimemillis || [])) .flat(1) - return encodeURIComponent(JSON.stringify([...new Set(bidders)])) + if (bidders.length) { + return encodeURIComponent(JSON.stringify([...new Set(bidders)])) + } } function addWurl(auctionId, adId, wurl) { 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 c2c4a97c62e..b7ff836a7e6 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 = { @@ -113,6 +114,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 +146,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); @@ -247,11 +258,14 @@ class IdFetchFlow { 'gdpr': hasGdpr, 'nbPage': nbPage, 'o': 'pbjs', - 'rf': referer.topmostLocation, + 'tml': referer.topmostLocation, + 'ref': referer.ref, + 'cu': referer.canonicalUrl, '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 @@ -374,4 +388,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/impactifyBidAdapter.js b/modules/impactifyBidAdapter.js index 04f34bdc7d9..d35d4498136 100644 --- a/modules/impactifyBidAdapter.js +++ b/modules/impactifyBidAdapter.js @@ -2,7 +2,6 @@ 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'; const BIDDER_CODE = 'impactify'; const BIDDER_ALIAS = ['imp']; @@ -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..437fcf7d5bb 100644 --- a/modules/improvedigitalBidAdapter.js +++ b/modules/improvedigitalBidAdapter.js @@ -378,7 +378,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/incrxBidAdapter.js b/modules/incrxBidAdapter.js index 914ef0b904e..d054309ee40 100644 --- a/modules/incrxBidAdapter.js +++ b/modules/incrxBidAdapter.js @@ -68,6 +68,8 @@ export const spec = { requestId: response.slotBidId, cpm: response.cpm, currency: response.currency || DEFAULT_CURRENCY, + adType: response.adType || '1', + settings: response.settings, width: response.adWidth, height: response.adHeight, ttl: CREATIVE_TTL, diff --git a/modules/insticatorBidAdapter.js b/modules/insticatorBidAdapter.js index 150e9d3c5c2..46ff17d2a5a 100644 --- a/modules/insticatorBidAdapter.js +++ b/modules/insticatorBidAdapter.js @@ -12,7 +12,7 @@ const USER_ID_COOKIE_EXP = 2592000000; // 30 days const BID_TTL = 300; // 5 minutes const GVLID = 910; -export const storage = getStorageManager({gvlid: GVLID, bidderCode: BIDDER_CODE}); +export const storage = getStorageManager({bidderCode: BIDDER_CODE}); config.setDefaults({ insticator: { diff --git a/modules/intentIqIdSystem.js b/modules/intentIqIdSystem.js index 563435dee65..b16624ac368 100644 --- a/modules/intentIqIdSystem.js +++ b/modules/intentIqIdSystem.js @@ -8,7 +8,8 @@ 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 {getStorageManager} from '../src/storageManager.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; const PCID_EXPIRY = 365; @@ -16,7 +17,7 @@ const MODULE_NAME = 'intentIqId'; export const FIRST_PARTY_KEY = '_iiq_fdata'; export var FIRST_PARTY_DATA_KEY = '_iiq_fdata'; -export const storage = getStorageManager({ gvlid: undefined, moduleName: MODULE_NAME }); +export const storage = getStorageManager({ moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME }); const INVALID_ID = 'INVALID_ID'; diff --git a/modules/invibesBidAdapter.js b/modules/invibesBidAdapter.js index c3e5cf6cca8..b2444043c22 100644 --- a/modules/invibesBidAdapter.js +++ b/modules/invibesBidAdapter.js @@ -17,7 +17,7 @@ const CONSTANTS = { DISABLE_USER_SYNC: true }; -const storage = getStorageManager({gvlid: CONSTANTS.INVIBES_VENDOR_ID, bidderCode: CONSTANTS.BIDDER_CODE}); +const storage = getStorageManager({bidderCode: CONSTANTS.BIDDER_CODE}); export const spec = { code: CONSTANTS.BIDDER_CODE, diff --git a/modules/iqmBidAdapter.js b/modules/iqmBidAdapter.js index 1c36bafd3ce..51cd8b3bdca 100644 --- a/modules/iqmBidAdapter.js +++ b/modules/iqmBidAdapter.js @@ -1,6 +1,5 @@ import {_each, 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'; import {INSTREAM} from '../src/video.js'; @@ -155,7 +154,7 @@ export const spec = { auctionId: bidRequest.data.auctionId, mediaType: bidRequest.data.imp.mediatype, - ttl: bid.ttl || config.getConfig('_bidderTimeout') + ttl: bid.ttl || 60 }; if (bidRequest.data.imp.mediatype === VIDEO) { diff --git a/modules/iqzoneBidAdapter.js b/modules/iqzoneBidAdapter.js index f2de447d6a7..52f3be7e4b4 100644 --- a/modules/iqzoneBidAdapter.js +++ b/modules/iqzoneBidAdapter.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/ivsBidAdapter.js b/modules/ivsBidAdapter.js new file mode 100644 index 00000000000..47685fbbe46 --- /dev/null +++ b/modules/ivsBidAdapter.js @@ -0,0 +1,85 @@ +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; +import { deepAccess, deepSetValue, getBidIdParameter, logError } from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { VIDEO } from '../src/mediaTypes.js'; +import { INSTREAM } from '../src/video.js'; + +const BIDDER_CODE = 'ivs'; +const ENDPOINT_URL = 'https://a.ivstracker.net/prod/openrtb/2.5'; + +export const converter = ortbConverter({ + context: { + mediaType: VIDEO, + ttl: 360, + netRevenue: true + } +}); + +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) { + if (bid && typeof bid.params !== 'object') { + logError(BIDDER_CODE + ': params is not defined or is incorrect in the bidder settings.'); + return false; + } + + if (!deepAccess(bid, 'mediaTypes.video')) { + logError(BIDDER_CODE + ': mediaTypes.video is not present in the bidder settings.'); + return false; + } + + if (deepAccess(bid, 'mediaTypes.video.context') !== INSTREAM) { + logError(BIDDER_CODE + ': only instream video context is allowed.'); + return false; + } + + if (!getBidIdParameter('publisherId', bid.params)) { + logError(BIDDER_CODE + ': publisherId is not present in bidder params.'); + return false; + } + + return true; + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param {validBidRequests[]} - an array of bids + * @param {bidderRequest} - master bidRequest object + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function(validBidRequests, bidderRequest) { + const data = converter.toORTB({ bidderRequest, validBidRequests, context: {mediaType: 'video'} }); + deepSetValue(data.site, 'publisher.id', validBidRequests[0].params.publisherId); + + return { + method: 'POST', + url: ENDPOINT_URL, + data: data, + options: { + contentType: 'application/json' + }, + }; + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function(serverResponse, bidRequest) { + if (!serverResponse.body) return; + return converter.fromORTB({ response: serverResponse.body, request: bidRequest.data }).bids; + }, +} + +registerBidder(spec); diff --git a/modules/ivsBidAdapter.md b/modules/ivsBidAdapter.md new file mode 100644 index 00000000000..d50061b640d --- /dev/null +++ b/modules/ivsBidAdapter.md @@ -0,0 +1,34 @@ +# Overview + +``` +Module Name: IVS Bidder Adapter +Module Type: Bidder Adapter +Maintainer: prebid@ivs.tv +``` + +# Description + +Module that connects to IVS's demand sources + +# Test Parameters +``` + var adUnits = [ + { + code: 'test-div', + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'instream' + } + }, + bids: [ + { + bidder: 'ivs', + params: { + publisherId: '3001234' // required + } + } + ] + } + ]; +``` \ No newline at end of file diff --git a/modules/ixBidAdapter.js b/modules/ixBidAdapter.js index 27e6d0aee02..06f96edca30 100644 --- a/modules/ixBidAdapter.js +++ b/modules/ixBidAdapter.js @@ -13,7 +13,6 @@ import { logError, logWarn, mergeDeep, - parseQueryStringParameters, safeJSONParse } from '../src/utils.js'; import { BANNER, VIDEO, NATIVE } from '../src/mediaTypes.js'; @@ -76,7 +75,8 @@ const SOURCE_RTI_MAPPING = { 'epsilon.com': '', // Publisher Link, publinkId 'audigent.com': '', // Hadron ID from Audigent, hadronId 'pubcid.org': '', // SharedID, pubcid - 'trustpid.com': '' // Trustpid + 'trustpid.com': '', // Trustpid + 'intimatemerger.com': '' }; const PROVIDERS = [ 'britepoolid', @@ -101,11 +101,14 @@ const VIDEO_PARAMS_ALLOW_LIST = [ const LOCAL_STORAGE_KEY = 'ixdiag'; export const LOCAL_STORAGE_FEATURE_TOGGLES_KEY = `${BIDDER_CODE}_features`; let hasRegisteredHandler = false; -export const storage = getStorageManager({ gvlid: GLOBAL_VENDOR_ID, bidderCode: BIDDER_CODE }); +export const storage = getStorageManager({ bidderCode: BIDDER_CODE }); export const FEATURE_TOGGLES = { + // Update with list of CFTs to be requested from Exchange + REQUESTED_FEATURE_TOGGLES: [], + featureToggles: {}, isFeatureEnabled: function (ft) { - return deepAccess(this.featureToggles, `features.${ft}.activated`) + return deepAccess(this.featureToggles, `features.${ft}.activated`, false) }, getFeatureToggles: function () { if (storage.localStorageIsEnabled()) { @@ -192,6 +195,9 @@ function bidToVideoImp(bid) { // populate imp level transactionId imp.ext.tid = deepAccess(bid, 'ortb2Imp.ext.tid'); + // AdUnit-Specific First Party Data + addAdUnitFPD(imp, bid) + // copy all video properties to imp object for (const adUnitProperty in videoAdUnitRef) { if (VIDEO_PARAMS_ALLOW_LIST.indexOf(adUnitProperty) !== -1 && !imp.video.hasOwnProperty(adUnitProperty)) { @@ -265,6 +271,9 @@ function bidToNativeImp(bid) { // populate imp level transactionId imp.ext.tid = deepAccess(bid, 'ortb2Imp.ext.tid'); + // AdUnit-Specific First Party Data + addAdUnitFPD(imp, bid) + _applyFloor(bid, imp, NATIVE); return imp; @@ -380,8 +389,8 @@ function parseBid(rawBid, currency, bidRequest) { bid.netRevenue = NET_REVENUE; bid.currency = currency; bid.creativeId = rawBid.hasOwnProperty('crid') ? rawBid.crid : '-'; - - if (rawBid.mtype == MEDIA_TYPES.Video) { + // If mtype = video is passed and vastURl is not set, set vastxml + if (rawBid.mtype == MEDIA_TYPES.Video && ((rawBid.ext && !rawBid.ext.vasturl) || !rawBid.ext)) { bid.vastXml = rawBid.adm; } else if (rawBid.ext && rawBid.ext.vasturl) { bid.vastUrl = rawBid.ext.vasturl; @@ -425,7 +434,6 @@ function parseBid(rawBid, currency, bidRequest) { if (rawBid.adomain && rawBid.adomain.length > 0) { bid.meta.advertiserDomains = rawBid.adomain; } - return bid; } @@ -598,29 +606,11 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { // Get ids from Prebid User ID Modules let eidInfo = getEidInfo(deepAccess(validBidRequests, '0.userIdAsEids')); let userEids = eidInfo.toSend; - const pageUrl = deepAccess(bidderRequest, 'refererInfo.page'); - - let MAX_REQUEST_SIZE = 8000; - // Modify request size limit if its FT is enabeld. - if (FEATURE_TOGGLES.isFeatureEnabled('pbjs_use_32kb_size_limit')) { - MAX_REQUEST_SIZE = 32000 - } // RTI ids will be included in the bid request if the function getIdentityInfo() is loaded // and if the data for the partner exist if (window.headertag && typeof window.headertag.getIdentityInfo === 'function') { - let identityInfo = window.headertag.getIdentityInfo(); - if (identityInfo && typeof identityInfo === 'object') { - for (const partnerName in identityInfo) { - if (identityInfo.hasOwnProperty(partnerName)) { - let response = identityInfo[partnerName]; - if (!response.responsePending && response.data && typeof response.data === 'object' && - Object.keys(response.data).length && !eidInfo.seenSources[response.data.source]) { - userEids.push(response.data); - } - } - } - } + addRTI(userEids, eidInfo); } // If `roundel` alias bidder, only send requests if liveramp ids exist. @@ -628,9 +618,103 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { return []; } - const r = {}; - const tmax = config.getConfig('bidderTimeout'); + const requests = []; + let r = createRequest(validBidRequests); + // Add FTs to be requested from Exchange + r = addRequestedFeatureToggles(r, FEATURE_TOGGLES.REQUESTED_FEATURE_TOGGLES) + + // getting ixdiags for adunits of the video, outstream & multi format (MF) style + let ixdiag = buildIXDiag(validBidRequests); + for (var key in ixdiag) { + r.ext.ixdiag[key] = ixdiag[key]; + } + + r = enrichRequest(r, bidderRequest, impressions, validBidRequests, userEids); + + r = applyRegulations(r, bidderRequest); + + let payload = {}; + createPayload(validBidRequests, bidderRequest, r, baseUrl, requests, payload); + + const transactionIds = Object.keys(impressions); + let isFpdAdded = false; + + for (let adUnitIndex = 0; adUnitIndex < transactionIds.length; adUnitIndex++) { + if (requests.length >= MAX_REQUEST_LIMIT) { + break; + } + + r = addImpressions(impressions, transactionIds, r, adUnitIndex); + + const fpd = deepAccess(bidderRequest, 'ortb2') || {}; + const site = { ...(fpd.site || fpd.context) }; + const user = { ...fpd.user }; + if (!isEmpty(fpd) && !isFpdAdded) { + r = addFPD(bidderRequest, r, fpd, site, user); + + const clonedRObject = deepClone(r); + + clonedRObject.site = mergeDeep({}, clonedRObject.site, site); + clonedRObject.user = mergeDeep({}, clonedRObject.user, user); + + r.site = mergeDeep({}, r.site, site); + r.user = mergeDeep({}, r.user, user); + isFpdAdded = true; + } + + // add identifiers info to ixDiag + r = addIdentifiersInfo(impressions, r, transactionIds, adUnitIndex, payload, baseUrl); + + const isLastAdUnit = adUnitIndex === transactionIds.length - 1; + + if (isLastAdUnit) { + requests.push({ + method: 'POST', + url: baseUrl + '?s=' + siteID, + data: deepClone(r), + option: { + contentType: 'text/plain', + }, + validBidRequests + }); + + r.imp = []; + isFpdAdded = false; + } + } + + return requests; +} + +/** + * addRTI adds RTI info of the partner to retrieved user IDs from prebid ID module. + * + * @param {array} userEids userEids info retrieved from prebid + * @param {array} eidInfo eidInfo info from prebid + */ +function addRTI(userEids, eidInfo) { + let identityInfo = window.headertag.getIdentityInfo(); + if (identityInfo && typeof identityInfo === 'object') { + for (const partnerName in identityInfo) { + if (identityInfo.hasOwnProperty(partnerName)) { + let response = identityInfo[partnerName]; + if (!response.responsePending && response.data && typeof response.data === 'object' && + Object.keys(response.data).length && !eidInfo.seenSources[response.data.source]) { + userEids.push(response.data); + } + } + } + } +} + +/** + * createRequest creates the base request object + * @param {array} validBidRequests A list of valid bid request config objects. + * @return {object} Object describing the request to the server. + */ +function createRequest(validBidRequests) { + const r = {}; // Since bidderRequestId are the same for different bid request, just use the first one. r.id = validBidRequests[0].bidderRequestId.toString(); r.site = {}; @@ -640,13 +724,39 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { r.ext.ixdiag.ls = storage.localStorageIsEnabled(); r.imp = []; r.at = 1; + return r +} - // getting ixdiags for adunits of the video, outstream & multi format (MF) style - let ixdiag = buildIXDiag(validBidRequests); - for (var key in ixdiag) { - r.ext.ixdiag[key] = ixdiag[key]; +/** + * Adds requested feature toggles to the provided request object to be sent to Exchange. + * @param {object} r - The request object to add feature toggles to. + * @param {Array} requestedFeatureToggles - The list of feature toggles to add. + * @returns {object} The updated request object with the added feature toggles. + */ +function addRequestedFeatureToggles(r, requestedFeatureToggles) { + if (requestedFeatureToggles.length > 0) { + r.ext.features = {}; + // Loop through each feature toggle and add it to the features object. + // Add current activation status as well. + requestedFeatureToggles.forEach(toggle => { + r.ext.features[toggle] = { activated: FEATURE_TOGGLES.isFeatureEnabled(toggle) }; + }); } + return r; +} +/** + * enrichRequest adds userSync configs, source, and referer info to request and ixDiag objects. + * + * @param {object} r Base reuqest object. + * @param {object} bidderRequest An object containing other info like gdprConsent. + * @param {array} impressions A list of impressions to be added to the request. + * @param {array} validBidRequests A list of valid bid request config objects. + * @param {array} userEids User ID info retrieved from Prebid ID module. + * @return {object} Enriched object describing the request to the server. + */ +function enrichRequest(r, bidderRequest, impressions, validBidRequests, userEids) { + const tmax = deepAccess(bidderRequest, 'timeout'); if (tmax) { r.ext.ixdiag.tmax = tmax; } @@ -685,6 +795,17 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { r.site.ref = document.referrer; } + return r +} + +/** + * applyRegulations applies regulation info such as GDPR and GPP to the reqeust obejct. + * + * @param {object} r Base reuqest object. + * @param {object} bidderRequest An object containing other info like gdprConsent. + * @return {object} Object enriched with regulation info describing the request to the server. + */ +function applyRegulations(r, bidderRequest) { // Apply GDPR information to the request if GDPR is enabled. if (bidderRequest) { if (bidderRequest.gdprConsent) { @@ -706,7 +827,7 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { if (gdprConsent.hasOwnProperty('addtlConsent') && gdprConsent.addtlConsent) { r.user.ext.consented_providers_settings = { - consented_providers: gdprConsent.addtlConsent + addtl_consent: gdprConsent.addtlConsent }; } } @@ -717,16 +838,35 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { usPrivacy = bidderRequest.uspConsent; } + const pageUrl = deepAccess(bidderRequest, 'refererInfo.page'); if (pageUrl) { r.site.page = pageUrl; } + + if (bidderRequest.gppConsent) { + deepSetValue(r, 'regs.gpp', bidderRequest.gppConsent.gppString); + deepSetValue(r, 'regs.gpp_sid', bidderRequest.gppConsent.applicableSections); + } } if (config.getConfig('coppa')) { deepSetValue(r, 'regs.coppa', 1); } - const payload = {}; + return r +} + +/** + * createPayload creates the payload to be sent with the request. + * + * @param {array} validBidRequests A list of valid bid request config objects. + * @param {object} bidderRequest An object containing other info like gdprConsent. + * @param {object} r Reuqest object. + * @param {string} baseUrl Base exchagne URL. + * @param {array} requests List of request obejcts. + * @param {object} payload Request payload object. + */ +function createPayload(validBidRequests, bidderRequest, r, baseUrl, requests, payload) { // Use the siteId in the first bid request as the main siteId. siteID = validBidRequests[0].params.siteId; payload.s = siteID; @@ -734,19 +874,6 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { // Parse additional runtime configs. const bidderCode = (bidderRequest && bidderRequest.bidderCode) || 'ix'; const otherIxConfig = config.getConfig(bidderCode); - const requests = []; - let requestSequenceNumber = 0; - const transactionIds = Object.keys(impressions); - const baseRequestSize = `${baseUrl}${parseQueryStringParameters({ ...payload, r: JSON.stringify(r) })}`.length; - - if (baseRequestSize > MAX_REQUEST_SIZE) { - logError('IX Bid Adapter: Base request size has exceeded maximum request size.', { bidder: BIDDER_CODE, code: ERROR_CODES.EXCEEDS_MAX_SIZE }); - return requests; - } - - let currentRequestSize = baseRequestSize; - let fpdRequestSize = 0; - let isFpdAdded = false; if (otherIxConfig) { // Append firstPartyData to r.site.page if firstPartyData exists. @@ -760,182 +887,178 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { } firstPartyString = firstPartyString.slice(0, -1); - fpdRequestSize = encodeURIComponent(firstPartyString).length; - - if (fpdRequestSize < MAX_REQUEST_SIZE) { - if ('page' in r.site) { - r.site.page += firstPartyString; - } else { - r.site.page = firstPartyString; - } - currentRequestSize += fpdRequestSize; + if ('page' in r.site) { + r.site.page += firstPartyString; } else { - logError('IX Bid Adapter: IX config FPD request size has exceeded maximum request size.', { bidder: BIDDER_CODE, code: ERROR_CODES.IX_FPD_EXCEEDS_MAX_SIZE }); + r.site.page = firstPartyString; } } } +} - for (let adUnitIndex = 0; adUnitIndex < transactionIds.length; adUnitIndex++) { - if (currentRequestSize >= MAX_REQUEST_SIZE || requests.length >= MAX_REQUEST_LIMIT) { - break; - } - - const adUnitImpressions = impressions[transactionIds[adUnitIndex]]; - const { missingImps: missingBannerImpressions = [], ixImps = [] } = adUnitImpressions; - let wasAdUnitImpressionsTrimmed = false; - let remainingRequestSize = MAX_REQUEST_SIZE - currentRequestSize; - const sourceImpressions = { ixImps, missingBannerImpressions }; - const impressionObjects = Object.keys(sourceImpressions) - .map((key) => sourceImpressions[key]) - .filter(item => Array.isArray(item)) - .reduce((acc, curr) => acc.concat(...curr), []); - - let currentImpressionSize = encodeURIComponent(JSON.stringify({ impressionObjects })).length; - - while (impressionObjects.length && currentImpressionSize > remainingRequestSize) { - wasAdUnitImpressionsTrimmed = true; - impressionObjects.pop(); - currentImpressionSize = encodeURIComponent(JSON.stringify({ impressionObjects })).length; - } - - const gpid = impressions[transactionIds[adUnitIndex]].gpid; - const dfpAdUnitCode = impressions[transactionIds[adUnitIndex]].dfp_ad_unit_code; - const tid = impressions[transactionIds[adUnitIndex]].tid; - const sid = impressions[transactionIds[adUnitIndex]].sid - - if (impressionObjects.length && BANNER in impressionObjects[0]) { - const { id, banner: { topframe } } = impressionObjects[0]; - const _bannerImpression = { - id, - banner: { - topframe, - format: impressionObjects.map(({ banner: { w, h }, ext }) => ({ w, h, ext })) - }, - }; - - for (let i = 0; i < _bannerImpression.banner.format.length; i++) { - // We add sid in imp.ext.sid therefore, remove from banner.format[].ext - if (_bannerImpression.banner.format[i].ext != null && _bannerImpression.banner.format[i].ext.sid != null) { - delete _bannerImpression.banner.format[i].ext.sid; - } - - // add floor per size - if ('bidfloor' in impressionObjects[i]) { - _bannerImpression.banner.format[i].ext.bidfloor = impressionObjects[i].bidfloor - } - } - - const position = impressions[transactionIds[adUnitIndex]].pos; - if (isInteger(position)) { - _bannerImpression.banner.pos = position; - } - - if (dfpAdUnitCode || gpid || tid || sid) { - _bannerImpression.ext = {}; - _bannerImpression.ext.dfp_ad_unit_code = dfpAdUnitCode; - _bannerImpression.ext.gpid = gpid; - _bannerImpression.ext.tid = tid; - _bannerImpression.ext.sid = sid; - } +/** + * addImpressions adds impressions to request object + * + * @param {array} impressions List of impressions to be added to the request. + * @param {array} transactionIds List of transaction Ids. + * @param {object} r Reuqest object. + * @param {int} adUnitIndex Index of the current add unit + * @return {object} Reqyest object with added impressions describing the request to the server. + */ +function addImpressions(impressions, transactionIds, r, adUnitIndex) { + const adUnitImpressions = impressions[transactionIds[adUnitIndex]]; + const { missingImps: missingBannerImpressions = [], ixImps = [] } = adUnitImpressions; + const sourceImpressions = { ixImps, missingBannerImpressions }; + const impressionObjects = Object.keys(sourceImpressions) + .map((key) => sourceImpressions[key]) + .filter(item => Array.isArray(item)) + .reduce((acc, curr) => acc.concat(...curr), []); + + const gpid = impressions[transactionIds[adUnitIndex]].gpid; + const dfpAdUnitCode = impressions[transactionIds[adUnitIndex]].dfp_ad_unit_code; + const tid = impressions[transactionIds[adUnitIndex]].tid; + const sid = impressions[transactionIds[adUnitIndex]].sid + + if (impressionObjects.length && BANNER in impressionObjects[0]) { + const { id, banner: { topframe } } = impressionObjects[0]; + const _bannerImpression = { + id, + banner: { + topframe, + format: impressionObjects.map(({ banner: { w, h }, ext }) => ({ w, h, ext })) + }, + }; - if ('bidfloor' in impressionObjects[0]) { - _bannerImpression.bidfloor = impressionObjects[0].bidfloor; + for (let i = 0; i < _bannerImpression.banner.format.length; i++) { + // We add sid in imp.ext.sid therefore, remove from banner.format[].ext + if (_bannerImpression.banner.format[i].ext != null && _bannerImpression.banner.format[i].ext.sid != null) { + delete _bannerImpression.banner.format[i].ext.sid; } - if ('bidfloorcur' in impressionObjects[0]) { - _bannerImpression.bidfloorcur = impressionObjects[0].bidfloorcur; + // add floor per size + if ('bidfloor' in impressionObjects[i]) { + _bannerImpression.banner.format[i].ext.bidfloor = impressionObjects[i].bidfloor; } - - r.imp.push(_bannerImpression); - } else { - // set imp.ext.gpid to resolved gpid for each imp - impressionObjects.forEach(imp => deepSetValue(imp, 'ext.gpid', gpid)); - r.imp.push(...impressionObjects); } - currentRequestSize += currentImpressionSize; - - const fpd = deepAccess(bidderRequest, 'ortb2') || {}; - - if (!isEmpty(fpd) && !isFpdAdded) { - r.ext.ixdiag.fpd = true; + const position = impressions[transactionIds[adUnitIndex]].pos; + if (isInteger(position)) { + _bannerImpression.banner.pos = position; + } - const site = { ...(fpd.site || fpd.context) }; + if (dfpAdUnitCode || gpid || tid || sid) { + _bannerImpression.ext = {}; + _bannerImpression.ext.dfp_ad_unit_code = dfpAdUnitCode; + _bannerImpression.ext.gpid = gpid; + _bannerImpression.ext.tid = tid; + _bannerImpression.ext.sid = sid; + } - Object.keys(site).forEach(key => { - if (FIRST_PARTY_DATA.SITE.indexOf(key) === -1) { - delete site[key]; - } - }); + if ('bidfloor' in impressionObjects[0]) { + _bannerImpression.bidfloor = impressionObjects[0].bidfloor; + } - const user = { ...fpd.user }; + if ('bidfloorcur' in impressionObjects[0]) { + _bannerImpression.bidfloorcur = impressionObjects[0].bidfloorcur; + } - Object.keys(user).forEach(key => { - if (FIRST_PARTY_DATA.USER.indexOf(key) === -1) { - delete user[key]; - } - }); + const adUnitFPD = impressions[transactionIds[adUnitIndex]].adUnitFPD + if (adUnitFPD) { + _bannerImpression.ext.data = adUnitFPD; + } - const clonedRObject = deepClone(r); + r.imp.push(_bannerImpression); + } else { + // set imp.ext.gpid to resolved gpid for each imp + impressionObjects.forEach(imp => deepSetValue(imp, 'ext.gpid', gpid)); + r.imp.push(...impressionObjects); + } - clonedRObject.site = mergeDeep({}, clonedRObject.site, site); - clonedRObject.user = mergeDeep({}, clonedRObject.user, user); + return r; +} - const requestSize = `${baseUrl}${parseQueryStringParameters({ ...payload, r: JSON.stringify(clonedRObject) })}`.length; +/** + * addFPD adds ortb2 first party data to request object. + * + * @param {object} bidderRequest An object containing other info like gdprConsent. + * @param {object} r Reuqest object. + * @param {object} fpd ortb2 first party data. + * @param {object} site First party site data. + * @param {object} user First party user data. + * @return {object} Reqyest object with added FPD describing the request to the server. + */ +function addFPD(bidderRequest, r, fpd, site, user) { + r.ext.ixdiag.fpd = true; - if (requestSize < MAX_REQUEST_SIZE) { - r.site = mergeDeep({}, r.site, site); - r.user = mergeDeep({}, r.user, user); - isFpdAdded = true; - const fpdRequestSize = encodeURIComponent(JSON.stringify({ ...site, ...user })).length; - currentRequestSize += fpdRequestSize; - } else { - logError('IX Bid Adapter: FPD request size has exceeded maximum request size.', { bidder: BIDDER_CODE, code: ERROR_CODES.PB_FPD_EXCEEDS_MAX_SIZE }); - } + Object.keys(site).forEach(key => { + if (FIRST_PARTY_DATA.SITE.indexOf(key) === -1) { + delete site[key]; } + }); - // add identifiers info to ixDiag - const pbaAdSlot = impressions[transactionIds[adUnitIndex]].pbadslot; - const tagId = impressions[transactionIds[adUnitIndex]].tagId; - const adUnitCode = impressions[transactionIds[adUnitIndex]].adUnitCode; - const divId = impressions[transactionIds[adUnitIndex]].divId; - if (pbaAdSlot || tagId || adUnitCode || divId) { - const clonedRObject = deepClone(r); - const requestSize = `${baseUrl}${parseQueryStringParameters({ ...payload, r: JSON.stringify(clonedRObject) })}`.length; - if (requestSize < MAX_REQUEST_SIZE) { - r.ext.ixdiag.pbadslot = pbaAdSlot; - r.ext.ixdiag.tagid = tagId; - r.ext.ixdiag.adunitcode = adUnitCode; - r.ext.ixdiag.divId = divId; - } + Object.keys(user).forEach(key => { + if (FIRST_PARTY_DATA.USER.indexOf(key) === -1) { + delete user[key]; } + }); - const isLastAdUnit = adUnitIndex === transactionIds.length - 1; + if (fpd.device) { + const sua = {...fpd.device.sua}; + if (!isEmpty(sua)) { + deepSetValue(r, 'device.sua', sua); + } + } - if (wasAdUnitImpressionsTrimmed || isLastAdUnit) { - if (!isLastAdUnit || requestSequenceNumber) { - r.ext.ixdiag.sn = requestSequenceNumber; - } + if (fpd.hasOwnProperty('regs') && !bidderRequest.gppConsent) { + if (fpd.regs.hasOwnProperty('gpp') && typeof fpd.regs.gpp == 'string') { + deepSetValue(r, 'regs.gpp', fpd.regs.gpp) + } - requestSequenceNumber++; + if (fpd.regs.hasOwnProperty('gpp_sid') && Array.isArray(fpd.regs.gpp_sid)) { + deepSetValue(r, 'regs.gpp_sid', fpd.regs.gpp_sid) + } + } - requests.push({ - method: 'POST', - url: baseUrl + '?s=' + siteID, - data: deepClone(r), - option: { - contentType: 'text/plain', - }, - validBidRequests - }); + return r; +} - currentRequestSize = baseRequestSize; - r.imp = []; - isFpdAdded = false; - } +/** + * Adds First-Party Data (FPD) from the bid object to the imp object. + * + * @param {Object} imp - The imp object, representing an impression in the OpenRTB format. + * @param {Object} bid - The bid object, containing information about the bid request. + */ +function addAdUnitFPD(imp, bid) { + const adUnitFPD = deepAccess(bid, 'ortb2Imp.ext.data'); + if (adUnitFPD) { + deepSetValue(imp, 'ext.data', adUnitFPD) } +} - return requests; +/** + * addIdentifiersInfo adds indentifier info to ixDaig. + * + * @param {array} impressions List of impressions to be added to the request. + * @param {object} r Reuqest object. + * @param {array} transactionIds List of transaction Ids. + * @param {int} adUnitIndex Index of the current add unit + * @param {object} payload Request payload object. + * @param {string} baseUrl Base exchagne URL. + * @return {object} Reqyest object with added indentigfier info to ixDiag. + */ +function addIdentifiersInfo(impressions, r, transactionIds, adUnitIndex, payload, baseUrl) { + const pbaAdSlot = impressions[transactionIds[adUnitIndex]].pbadslot; + const tagId = impressions[transactionIds[adUnitIndex]].tagId; + const adUnitCode = impressions[transactionIds[adUnitIndex]].adUnitCode; + const divId = impressions[transactionIds[adUnitIndex]].divId; + if (pbaAdSlot || tagId || adUnitCode || divId) { + r.ext.ixdiag.pbadslot = pbaAdSlot; + r.ext.ixdiag.tagid = tagId; + r.ext.ixdiag.adunitcode = adUnitCode; + r.ext.ixdiag.divId = divId; + } + + return r; } /** @@ -1097,6 +1220,12 @@ function createBannerImps(validBidRequest, missingBannerSizes, bannerImps) { bannerImps[validBidRequest.transactionId].tagId = deepAccess(validBidRequest, 'params.tagId'); bannerImps[validBidRequest.transactionId].pos = deepAccess(validBidRequest, 'mediaTypes.banner.pos'); + // AdUnit-Specific First Party Data + const adUnitFPD = deepAccess(validBidRequest, 'ortb2Imp.ext.data'); + if (adUnitFPD) { + bannerImps[validBidRequest.transactionId].adUnitFPD = adUnitFPD; + } + const sid = deepAccess(validBidRequest, 'params.id'); if (sid && (typeof sid === 'string' || typeof sid === 'number')) { bannerImps[validBidRequest.transactionId].sid = String(sid); diff --git a/modules/jixieBidAdapter.js b/modules/jixieBidAdapter.js index 5eeab197f3a..586fce84804 100644 --- a/modules/jixieBidAdapter.js +++ b/modules/jixieBidAdapter.js @@ -6,7 +6,6 @@ import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {ajax} from '../src/ajax.js'; import {getRefererInfo} from '../src/refererDetection.js'; import {Renderer} from '../src/Renderer.js'; -import {createEidsArray} from './userId/eids.js'; const BIDDER_CODE = 'jixie'; export const storage = getStorageManager({bidderCode: BIDDER_CODE}); @@ -190,12 +189,10 @@ export const spec = { let miscDims = internal.getMiscDims(); let schain = deepAccess(validBidRequests[0], 'schain'); + let eids1 = validBidRequests[0].userIdAsEids // all available user ids are sent to our backend in the standard array layout: - if (validBidRequests[0].userId) { - let eids1 = createEidsArray(validBidRequests[0].userId); - if (eids1.length) { - eids = eids1; - } + if (eids1 && eids1.length) { + eids = eids1; } // we want to send this blob of info to our backend: let pg = config.getConfig('priceGranularity'); diff --git a/modules/jwplayerVideoProvider.js b/modules/jwplayerVideoProvider.js index 6264522ad83..ed6b69d756a 100644 --- a/modules/jwplayerVideoProvider.js +++ b/modules/jwplayerVideoProvider.js @@ -47,31 +47,29 @@ export function JWPlayerProvider(config, jwplayer_, adState_, timeState_, callba function init() { if (!jwplayer) { - triggerSetupFailure(-1); // TODO: come up with error code schema- player is absent + triggerSetupFailure({ code: -1 }); // TODO: come up with error code schema- player is absent return; } playerVersion = jwplayer.version; if (playerVersion < minimumSupportedPlayerVersion) { - triggerSetupFailure(-2); // TODO: come up with error code schema - version not supported + triggerSetupFailure({ code: -2 }); // TODO: come up with error code schema - version not supported return; } if (!document.getElementById(divId)) { - triggerSetupFailure(-3); // TODO: come up with error code schema - missing div id + triggerSetupFailure({ code: -3 }); // TODO: come up with error code schema - missing div id return; } player = jwplayer(divId); if (!player || !player.getState) { - triggerSetupFailure(-4); // TODO: come up with error code schema - factory function failure + triggerSetupFailure({ code: -4 }); // TODO: come up with error code schema - factory function failure } else if (player.getState() === undefined) { setupPlayer(playerConfig); } else { - const payload = getSetupCompletePayload(); - setupCompleteCallbacks.forEach(callback => callback(SETUP_COMPLETE, payload)); - setupCompleteCallbacks = []; + triggerSetupComplete(); } } @@ -192,8 +190,12 @@ export function JWPlayerProvider(config, jwplayer_, adState_, timeState_, callba function onEvent(externalEventName, callback, basePayload) { if (externalEventName === SETUP_COMPLETE) { setupCompleteCallbacks.push(callback); - } else if (externalEventName === SETUP_FAILED) { + return; + } + + if (externalEventName === SETUP_FAILED) { setupFailedCallbacks.push(callback); + return; } if (!player) { @@ -203,25 +205,6 @@ export function JWPlayerProvider(config, jwplayer_, adState_, timeState_, callba let getEventPayload; switch (externalEventName) { - case SETUP_COMPLETE: - getEventPayload = () => { - setupCompleteCallbacks = []; - return getSetupCompletePayload(); - }; - break; - - case SETUP_FAILED: - getEventPayload = e => { - setupFailedCallbacks = []; - return { - playerVersion, - errorCode: e.code, - errorMessage: e.message, - sourceError: e.sourceError - }; - }; - break; - case AD_REQUEST: case AD_PLAY: case AD_PAUSE: @@ -496,7 +479,17 @@ export function JWPlayerProvider(config, jwplayer_, adState_, timeState_, callba if (!config) { return; } - player.setup(utils.getJwConfig(config)); + player.setup(utils.getJwConfig(config)).on('ready', triggerSetupComplete).on('setupError', triggerSetupFailure); + } + + function triggerSetupComplete() { + if (!setupCompleteCallbacks.length) { + return; + } + + const payload = getSetupCompletePayload(); + setupCompleteCallbacks.forEach(callback => callback(SETUP_COMPLETE, payload)); + setupCompleteCallbacks = []; } function getSetupCompletePayload() { @@ -511,7 +504,7 @@ export function JWPlayerProvider(config, jwplayer_, adState_, timeState_, callba }; } - function triggerSetupFailure(errorCode) { + function triggerSetupFailure(e) { if (!setupFailedCallbacks.length) { return; } @@ -520,9 +513,9 @@ export function JWPlayerProvider(config, jwplayer_, adState_, timeState_, callba divId, playerVersion, type: SETUP_FAILED, - errorCode, - errorMessage: '', - sourceError: null + errorCode: e.code, + errorMessage: e.message, + sourceError: e.sourceError }; setupFailedCallbacks.forEach(callback => callback(SETUP_FAILED, payload)); diff --git a/modules/kargoBidAdapter.js b/modules/kargoBidAdapter.js index 2c33c3f61d1..b612c88bb12 100644 --- a/modules/kargoBidAdapter.js +++ b/modules/kargoBidAdapter.js @@ -1,289 +1,522 @@ -import { _each, buildUrl, deepAccess, pick, triggerPixel } from '../src/utils.js'; +import { _each, isEmpty, buildUrl, deepAccess, pick, triggerPixel } from '../src/utils.js'; import { config } from '../src/config.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { getStorageManager } from '../src/storageManager.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; -const BIDDER_CODE = 'kargo'; -const HOST = 'https://krk.kargo.com'; -const SYNC = 'https://crb.kargo.com/api/v1/initsyncrnd/{UUID}?seed={SEED}&idx={INDEX}&gdpr={GDPR}&gdpr_consent={GDPR_CONSENT}&us_privacy={US_PRIVACY}'; -const SYNC_COUNT = 5; -const GVLID = 972; -const SUPPORTED_MEDIA_TYPES = [BANNER, VIDEO]; -const storage = getStorageManager({gvlid: GVLID, bidderCode: BIDDER_CODE}); +const PREBID_VERSION = '$prebid.version$' + +const BIDDER = Object.freeze({ + CODE: 'kargo', + HOST: 'krk2.kargo.com', + REQUEST_METHOD: 'POST', + REQUEST_ENDPOINT: '/api/v1/prebid', + TIMEOUT_ENDPOINT: '/api/v1/event/timeout', + GVLID: 972, + SUPPORTED_MEDIA_TYPES: [BANNER, VIDEO], +}); + +const STORAGE = getStorageManager({bidderCode: BIDDER.CODE}); + +const CURRENCY = Object.freeze({ + KEY: 'currency', + US_DOLLAR: 'USD', +}); + +const REQUEST_KEYS = Object.freeze({ + SOCIAL_CANVAS: 'params.socialCanvas', + SUA: 'ortb2.device.sua', + TDID_ADAPTER: 'userId.tdid', +}); + +const SUA = Object.freeze({ + BROWSERS: 'browsers', + MOBILE: 'mobile', + MODEL: 'model', + PLATFORM: 'platform', + SOURCE: 'source', +}); + +const SUA_ATTRIBUTES = [ + SUA.BROWSERS, + SUA.MOBILE, + SUA.MODEL, + SUA.SOURCE, + SUA.PLATFORM, +]; + +const CERBERUS = Object.freeze({ + KEY: 'krg_crb', + SYNC_URL: 'https://crb.kargo.com/api/v1/initsyncrnd/{UUID}?seed={SEED}&idx={INDEX}&gdpr={GDPR}&gdpr_consent={GDPR_CONSENT}&us_privacy={US_PRIVACY}&gpp={GPP_STRING}&gpp_sid={GPP_SID}', + SYNC_COUNT: 5, + PAGE_VIEW_ID: 'pageViewId', + PAGE_VIEW_TIMESTAMP: 'pageViewTimestamp', + PAGE_VIEW_URL: 'pageViewUrl' +}); let sessionId, lastPageUrl, requestCounter; -export const spec = { - gvlid: GVLID, - code: BIDDER_CODE, - isBidRequestValid: function(bid) { - if (!bid || !bid.params) { - return false; - } +function isBidRequestValid(bid) { + if (!bid || !bid.params) { + return false; + } - return !!bid.params.placementId; - }, - buildRequests: function(validBidRequests, bidderRequest) { - const currencyObj = config.getConfig('currency'); - const currency = (currencyObj && currencyObj.adServerCurrency) || 'USD'; - const bidIDs = {}; - const bidSizes = {}; - - _each(validBidRequests, bid => { - bidIDs[bid.bidId] = bid.params.placementId; - bidSizes[bid.bidId] = bid.sizes; - }); + return !!bid.params.placementId; +} + +function buildRequests(validBidRequests, bidderRequest) { + const currencyObj = config.getConfig(CURRENCY.KEY); + const currency = (currencyObj && currencyObj.adServerCurrency) ? currencyObj.adServerCurrency : null; + const impressions = []; + + _each(validBidRequests, bid => { + impressions.push(getImpression(bid)) + }); + + const firstBidRequest = validBidRequests[0]; + const tdidAdapter = deepAccess(firstBidRequest, REQUEST_KEYS.TDID_ADAPTER); + + const metadata = getAllMetadata(bidderRequest); + + const krakenParams = Object.assign({}, { + pbv: PREBID_VERSION, + aid: firstBidRequest.auctionId, + sid: _getSessionId(), + url: metadata.pageURL, + timeout: bidderRequest.timeout, + ts: new Date().getTime(), + device: { + size: [ + window.screen.width, + window.screen.height + ] + }, + imp: impressions, + user: getUserIds(tdidAdapter, bidderRequest.uspConsent, bidderRequest.gdprConsent, firstBidRequest.userIdAsEids, bidderRequest.gppConsent), + }); + + const reqCount = getRequestCount() + if (reqCount != null) { + krakenParams.requestCount = reqCount; + } - const firstBidRequest = validBidRequests[0]; - - const tdid = deepAccess(firstBidRequest, 'userId.tdid') - - const transformedParams = Object.assign({}, { - sessionId: spec._getSessionId(), - requestCount: spec._getRequestCount(), - timeout: bidderRequest.timeout, - currency, - cpmGranularity: 1, - timestamp: (new Date()).getTime(), - cpmRange: { - floor: 0, - ceil: 20 - }, - bidIDs, - bidSizes, - device: { - width: window.screen.width, - height: window.screen.height - }, - prebidRawBidRequests: validBidRequests - }, spec._getAllMetadata(bidderRequest, tdid)); - - // User Agent Client Hints / SUA - const uaClientHints = deepAccess(firstBidRequest, 'ortb2.device.sua'); - if (uaClientHints) { - transformedParams.device.sua = pick(uaClientHints, ['browsers', 'platform', 'mobile', 'model']); - } + if (currency != null && currency != CURRENCY.US_DOLLAR) { + krakenParams.cur = currency; + } - // Pull Social Canvas segments and embed URL - const socialCanvas = deepAccess(firstBidRequest, 'params.socialCanvas'); - if (socialCanvas) { - transformedParams.socialCanvasSegments = socialCanvas.segments; - transformedParams.socialEmbedURL = socialCanvas.embedURL; - } + if (metadata.rawCRB != null) { + krakenParams.rawCRB = metadata.rawCRB + } - const encodedParams = encodeURIComponent(JSON.stringify(transformedParams)); - return Object.assign({}, bidderRequest, { - method: 'GET', - url: `${HOST}/api/v2/bid`, - data: `json=${encodedParams}`, - currency: currency - }); - }, - interpretResponse: function(response, bidRequest) { - let bids = response.body; - const bidResponses = []; - for (let bidId in bids) { - let adUnit = bids[bidId]; - let meta = { - mediaType: BANNER - }; - - if (adUnit.metadata && adUnit.metadata.landingPageDomain) { - meta.clickUrl = adUnit.metadata.landingPageDomain[0]; - meta.advertiserDomains = adUnit.metadata.landingPageDomain; + if (metadata.rawCRBLocalStorage != null) { + krakenParams.rawCRBLocalStorage = metadata.rawCRBLocalStorage + } + + // Pull Social Canvas segments and embed URL + const socialCanvas = deepAccess(firstBidRequest, REQUEST_KEYS.SOCIAL_CANVAS); + + if (socialCanvas != null) { + krakenParams.socan = socialCanvas; + } + + // User Agent Client Hints / SUA + const uaClientHints = deepAccess(firstBidRequest, REQUEST_KEYS.SUA); + if (uaClientHints) { + const suaValidAttributes = [] + + SUA_ATTRIBUTES.forEach(suaKey => { + const suaValue = uaClientHints[suaKey]; + if (!suaValue) { + return; } - if (adUnit.mediaType && SUPPORTED_MEDIA_TYPES.includes(adUnit.mediaType)) { - meta.mediaType = adUnit.mediaType; + // Do not pass any empty strings + if (typeof suaValue == 'string' && suaValue.trim() === '') { + return; } - const bidResponse = { - ad: adUnit.adm, - requestId: bidId, - cpm: Number(adUnit.cpm), - width: adUnit.width, - height: adUnit.height, - ttl: 300, - creativeId: adUnit.id, - dealId: adUnit.targetingCustom, - netRevenue: true, - currency: adUnit.currency || bidRequest.currency, - mediaType: meta.mediaType, - meta: meta - }; - - if (meta.mediaType == VIDEO) { - bidResponse.vastXml = adUnit.adm; + switch (suaKey) { + case SUA.MOBILE && suaValue < 1: // Do not pass 0 value for mobile + case SUA.SOURCE && suaValue < 1: // Do not pass 0 value for source + break; + default: + suaValidAttributes.push(suaKey); } + }); - bidResponses.push(bidResponse); - } + krakenParams.device.sua = pick(uaClientHints, suaValidAttributes); + } + + const validPageId = getLocalStorageSafely(CERBERUS.PAGE_VIEW_ID) != null + const validPageTimestamp = getLocalStorageSafely(CERBERUS.PAGE_VIEW_TIMESTAMP) != null + const validPageUrl = getLocalStorageSafely(CERBERUS.PAGE_VIEW_URL) != null + + const page = {} + if (validPageId) { + page.id = getLocalStorageSafely(CERBERUS.PAGE_VIEW_ID); + } + if (validPageTimestamp) { + page.timestamp = Number(getLocalStorageSafely(CERBERUS.PAGE_VIEW_TIMESTAMP)); + } + if (validPageUrl) { + page.url = getLocalStorageSafely(CERBERUS.PAGE_VIEW_URL); + } + if (!isEmpty(page)) { + krakenParams.page = page; + } + + return Object.assign({}, bidderRequest, { + method: BIDDER.REQUEST_METHOD, + url: `https://${BIDDER.HOST}${BIDDER.REQUEST_ENDPOINT}`, + data: krakenParams, + currency: currency + }); +} + +function interpretResponse(response, bidRequest) { + let bids = response.body; + const bidResponses = []; + if (isEmpty(bids)) { return bidResponses; - }, - getUserSyncs: function(syncOptions, responses, gdprConsent, usPrivacy) { - const syncs = []; - const seed = spec._generateRandomUuid(); - const clientId = spec._getClientId(); - var gdpr = (gdprConsent && gdprConsent.gdprApplies) ? 1 : 0; - var gdprConsentString = (gdprConsent && gdprConsent.consentString) ? gdprConsent.consentString : ''; - // don't sync if opted out via usPrivacy - if (typeof usPrivacy == 'string' && usPrivacy.length == 4 && usPrivacy[0] == 1 && usPrivacy[2] == 'Y') { - return syncs; + } + + if (typeof bids !== 'object') { + return bidResponses; + } + + Object.entries(bids).forEach((entry) => { + const [bidID, adUnit] = entry; + + let meta = { + mediaType: adUnit.mediaType && BIDDER.SUPPORTED_MEDIA_TYPES.includes(adUnit.mediaType) ? adUnit.mediaType : BANNER + }; + + if (adUnit.metadata && adUnit.metadata.landingPageDomain) { + meta.clickUrl = adUnit.metadata.landingPageDomain[0]; + meta.advertiserDomains = adUnit.metadata.landingPageDomain; } - if (syncOptions.iframeEnabled && seed && clientId) { - for (let i = 0; i < SYNC_COUNT; i++) { - syncs.push({ - type: 'iframe', - url: SYNC.replace('{UUID}', clientId).replace('{SEED}', seed) - .replace('{INDEX}', i) - .replace('{GDPR}', gdpr) - .replace('{GDPR_CONSENT}', gdprConsentString) - .replace('{US_PRIVACY}', usPrivacy || '') - }); + + const bidResponse = { + requestId: bidID, + cpm: Number(adUnit.cpm), + width: adUnit.width, + height: adUnit.height, + ttl: 300, + creativeId: adUnit.id, + dealId: adUnit.targetingCustom, + netRevenue: true, + currency: adUnit.currency || bidRequest.currency, + mediaType: meta.mediaType, + meta: meta + }; + + if (meta.mediaType == VIDEO) { + if (adUnit.admUrl) { + bidResponse.vastUrl = adUnit.admUrl; + } else { + bidResponse.vastXml = adUnit.adm; } + } else { + bidResponse.ad = adUnit.adm; } + + bidResponses.push(bidResponse); + }) + + return bidResponses; +} + +function getUserSyncs(syncOptions, _, gdprConsent, usPrivacy, gppConsent) { + const syncs = []; + const seed = _generateRandomUUID(); + const clientId = getClientId(); + + var gdpr = (gdprConsent && gdprConsent.gdprApplies) ? 1 : 0; + var gdprConsentString = (gdprConsent && gdprConsent.consentString) ? gdprConsent.consentString : ''; + + var gppString = (gppConsent && gppConsent.consentString) ? gppConsent.consentString : ''; + var gppApplicableSections = (gppConsent && gppConsent.applicableSections && Array.isArray(gppConsent.applicableSections)) ? gppConsent.applicableSections.join(',') : ''; + + // don't sync if opted out via usPrivacy + if (typeof usPrivacy == 'string' && usPrivacy.length == 4 && usPrivacy[0] == 1 && usPrivacy[2] == 'Y') { return syncs; - }, - supportedMediaTypes: SUPPORTED_MEDIA_TYPES, - onTimeout: function(timeoutData) { - if (timeoutData == null) { - return; + } + if (syncOptions.iframeEnabled && seed && clientId) { + for (let i = 0; i < CERBERUS.SYNC_COUNT; i++) { + syncs.push({ + type: 'iframe', + url: CERBERUS.SYNC_URL.replace('{UUID}', clientId) + .replace('{SEED}', seed) + .replace('{INDEX}', i) + .replace('{GDPR}', gdpr) + .replace('{GDPR_CONSENT}', gdprConsentString) + .replace('{US_PRIVACY}', usPrivacy || '') + .replace('{GPP_STRING}', gppString) + .replace('{GPP_SID}', gppApplicableSections) + }); } + } + return syncs; +} - timeoutData.forEach((bid) => { - this._sendTimeoutData(bid.auctionId, bid.timeout); - }); - }, - - _getCrbFromCookie() { - try { - const crb = JSON.parse(storage.getCookie('krg_crb')); - if (crb && crb.v) { - let vParsed = JSON.parse(atob(crb.v)); - if (vParsed) { - return vParsed; - } +function onTimeout(timeoutData) { + if (timeoutData == null) { + return; + } + + timeoutData.forEach((bid) => { + sendTimeoutData(bid.auctionId, bid.timeout); + }); +} + +function _generateRandomUUID() { + try { + // crypto.getRandomValues is supported everywhere but Opera Mini for years + var buffer = new Uint8Array(16); + crypto.getRandomValues(buffer); + buffer[6] = (buffer[6] & ~176) | 64; + buffer[8] = (buffer[8] & ~64) | 128; + var hex = Array.prototype.map.call(new Uint8Array(buffer), function(x) { + return ('00' + x.toString(16)).slice(-2); + }).join(''); + return hex.slice(0, 8) + '-' + hex.slice(8, 12) + '-' + hex.slice(12, 16) + '-' + hex.slice(16, 20) + '-' + hex.slice(20); + } catch (e) { + return ''; + } +} + +function _getCrb() { + let localStorageCrb = getCrbFromLocalStorage(); + if (Object.keys(localStorageCrb).length) { + return localStorageCrb; + } + return getCrbFromCookie(); +} + +function _getSessionId() { + if (!sessionId) { + sessionId = _generateRandomUUID(); + } + return sessionId; +} + +function getCrbFromCookie() { + try { + const crb = JSON.parse(STORAGE.getCookie(CERBERUS.KEY)); + if (crb && crb.v) { + let vParsed = JSON.parse(atob(crb.v)); + if (vParsed) { + return vParsed; } - return {}; - } catch (e) { - return {}; } - }, + return {}; + } catch (e) { + return {}; + } +} + +function getCrbFromLocalStorage() { + try { + return JSON.parse(atob(getLocalStorageSafely(CERBERUS.KEY))); + } catch (e) { + return {}; + } +} + +function getLocalStorageSafely(key) { + try { + return STORAGE.getDataFromLocalStorage(key); + } catch (e) { + return null; + } +} + +function getUserIds(tdidAdapter, usp, gdpr, eids, gpp) { + const crb = spec._getCrb(); + const userIds = { + crbIDs: crb.syncIds || {} + }; - _getCrbFromLocalStorage() { - try { - return JSON.parse(atob(spec._getLocalStorageSafely('krg_crb'))); - } catch (e) { - return {}; + // Pull Trade Desk ID from adapter + if (tdidAdapter) { + userIds.tdID = tdidAdapter; + } + + // Pull Trade Desk ID from our storage + if (!tdidAdapter && crb.tdID) { + userIds.tdID = crb.tdID; + } + + if (usp) { + userIds.usp = usp; + } + + try { + if (gdpr) { + userIds['gdpr'] = { + consent: gdpr.consentString || '', + applies: !!gdpr.gdprApplies, + } } - }, + } catch (e) { + } + + if (crb.lexId != null) { + userIds.kargoID = crb.lexId; + } + + if (crb.clientId != null) { + userIds.clientID = crb.clientId; + } + + if (crb.optOut != null) { + userIds.optOut = crb.optOut; + } - _getCrb() { - let localStorageCrb = spec._getCrbFromLocalStorage(); - if (Object.keys(localStorageCrb).length) { - return localStorageCrb; + if (eids != null) { + userIds.sharedIDEids = eids; + } + + if (gpp) { + const parsedGPP = {} + if (gpp && gpp.consentString) { + parsedGPP.gppString = gpp.consentString } - return spec._getCrbFromCookie(); - }, - - _getLocalStorageSafely(key) { - try { - return storage.getDataFromLocalStorage(key); - } catch (e) { - return null; + if (gpp && gpp.applicableSections) { + parsedGPP.applicableSections = gpp.applicableSections } - }, - - _getUserIds(tdid, usp, gdpr) { - const crb = spec._getCrb(); - const userIds = { - kargoID: crb.lexId, - clientID: crb.clientId, - crbIDs: crb.syncIds || {}, - optOut: crb.optOut, - usp: usp - }; + if (!isEmpty(parsedGPP)) { + userIds.gpp = parsedGPP + } + } - try { - if (gdpr) { - userIds['gdpr'] = { - consent: gdpr.consentString || '', - applies: !!gdpr.gdprApplies, - } - } - } catch (e) { + return userIds; +} + +function getClientId() { + const crb = spec._getCrb(); + return crb.clientId; +} + +function getAllMetadata(bidderRequest) { + return { + pageURL: bidderRequest?.refererInfo?.page, + rawCRB: STORAGE.getCookie(CERBERUS.KEY), + rawCRBLocalStorage: getLocalStorageSafely(CERBERUS.KEY) + }; +} + +function getRequestCount() { + if (lastPageUrl === window.location.pathname) { + return ++requestCounter; + } + lastPageUrl = window.location.pathname; + return requestCounter = 0; +} + +function sendTimeoutData(auctionId, auctionTimeout) { + let params = { + aid: auctionId, + ato: auctionTimeout + }; + + try { + let timeoutRequestUrl = buildUrl({ + protocol: 'https', + hostname: BIDDER.HOST, + pathname: BIDDER.TIMEOUT_ENDPOINT, + search: params + }); + + triggerPixel(timeoutRequestUrl); + } catch (e) {} +} + +function getImpression(bid) { + const imp = { + id: bid.bidId, + tid: bid.transactionId, + pid: bid.params.placementId, + code: bid.adUnitCode + }; + + if (bid.floorData != null && bid.floorData.floorMin > 0) { + imp.floor = bid.floorData.floorMin; + } + + if (bid.bidRequestsCount > 0) { + imp.bidRequestCount = bid.bidRequestsCount; + } + + if (bid.bidderRequestsCount > 0) { + imp.bidderRequestCount = bid.bidderRequestsCount; + } + + if (bid.bidderWinsCount > 0) { + imp.bidderWinCount = bid.bidderWinsCount; + } + + const gpid = getGPID(bid) + if (gpid != null && gpid != '') { + imp.fpd = { + gpid: gpid } - if (tdid) { - userIds.tdID = tdid; + } + + if (bid.mediaTypes != null) { + if (bid.mediaTypes.banner != null) { + imp.banner = bid.mediaTypes.banner; } - return userIds; - }, - - _getClientId() { - const crb = spec._getCrb(); - return crb.clientId; - }, - - _getAllMetadata(bidderRequest, tdid) { - return { - userIDs: spec._getUserIds(tdid, bidderRequest.uspConsent, bidderRequest.gdprConsent), - pageURL: bidderRequest?.refererInfo?.page, - rawCRB: storage.getCookie('krg_crb'), - rawCRBLocalStorage: spec._getLocalStorageSafely('krg_crb') - }; - }, - _getSessionId() { - if (!sessionId) { - sessionId = spec._generateRandomUuid(); + if (bid.mediaTypes.video != null) { + imp.video = bid.mediaTypes.video; } - return sessionId; - }, - _getRequestCount() { - if (lastPageUrl === window.location.pathname) { - return ++requestCounter; + if (bid.mediaTypes.native != null) { + imp.native = bid.mediaTypes.native; } - lastPageUrl = window.location.pathname; - return requestCounter = 0; - }, - - _generateRandomUuid() { - try { - // crypto.getRandomValues is supported everywhere but Opera Mini for years - var buffer = new Uint8Array(16); - crypto.getRandomValues(buffer); - buffer[6] = (buffer[6] & ~176) | 64; - buffer[8] = (buffer[8] & ~64) | 128; - var hex = Array.prototype.map.call(new Uint8Array(buffer), function(x) { - return ('00' + x.toString(16)).slice(-2); - }).join(''); - return hex.slice(0, 8) + '-' + hex.slice(8, 12) + '-' + hex.slice(12, 16) + '-' + hex.slice(16, 20) + '-' + hex.slice(20); - } catch (e) { - return ''; + } + + return imp +} + +function getGPID(bid) { + if (bid.ortb2Imp != null) { + if (bid.ortb2Imp.gpid != null && bid.ortb2Imp.gpid != '') { + return bid.ortb2Imp.gpid; } - }, - _sendTimeoutData(auctionId, auctionTimeout) { - let params = { - aid: auctionId, - ato: auctionTimeout, - }; + if (bid.ortb2Imp.ext != null && bid.ortb2Imp.ext.data != null) { + if (bid.ortb2Imp.ext.data.pbAdSlot != null && bid.ortb2Imp.ext.data.pbAdSlot != '') { + return bid.ortb2Imp.ext.data.pbAdSlot; + } - try { - let timeoutRequestUrl = buildUrl({ - protocol: 'https', - hostname: 'krk.kargo.com', - pathname: '/api/v1/event/timeout', - search: params - }); + if (bid.ortb2Imp.ext.data.adServer != null && bid.ortb2Imp.ext.data.adServer.adSlot != null && bid.ortb2Imp.ext.data.adServer.adSlot != '') { + return bid.ortb2Imp.ext.data.adServer.adSlot; + } + } + } - triggerPixel(timeoutRequestUrl); - } catch (e) {} + if (bid.adUnitCode != null && bid.adUnitCode != '') { + return bid.adUnitCode; } + return ''; +} + +export const spec = { + gvlid: BIDDER.GVLID, + code: BIDDER.CODE, + isBidRequestValid, + buildRequests, + interpretResponse, + getUserSyncs, + supportedMediaTypes: BIDDER.SUPPORTED_MEDIA_TYPES, + onTimeout, + _getCrb, + _getSessionId }; + registerBidder(spec); diff --git a/modules/koblerBidAdapter.js b/modules/koblerBidAdapter.js index 1dc22d0099a..4eef99024f9 100644 --- a/modules/koblerBidAdapter.js +++ b/modules/koblerBidAdapter.js @@ -17,7 +17,6 @@ const BIDDER_ENDPOINT = 'https://bid.essrtb.com/bid/prebid_rtb_call'; const DEV_BIDDER_ENDPOINT = 'https://bid-service.dev.essrtb.com/bid/prebid_rtb_call'; const TIMEOUT_NOTIFICATION_ENDPOINT = 'https://bid.essrtb.com/notify/prebid_timeout'; const SUPPORTED_CURRENCY = 'USD'; -const DEFAULT_TIMEOUT = 1000; const TIME_TO_LIVE_IN_SECONDS = 10 * 60; export const isBidRequestValid = function (bid) { @@ -124,7 +123,7 @@ function getPageUrlFromRefererInfo() { function buildOpenRtbBidRequestPayload(validBidRequests, bidderRequest) { const imps = validBidRequests.map(buildOpenRtbImpObject); - const timeout = bidderRequest.timeout || config.getConfig('bidderTimeout') || DEFAULT_TIMEOUT; + const timeout = bidderRequest.timeout; const pageUrl = getPageUrlFromRequest(validBidRequests[0], bidderRequest) const request = { id: bidderRequest.auctionId, diff --git a/modules/kueezBidAdapter.js b/modules/kueezBidAdapter.js index 24d339d1938..6efb67ddec8 100644 --- a/modules/kueezBidAdapter.js +++ b/modules/kueezBidAdapter.js @@ -293,7 +293,7 @@ function generateSharedParams(sharedParams, bidderRequest) { const generalBidParams = getBidIdParameter('params', sharedParams); const userIds = getBidIdParameter('userId', sharedParams); const ortb2Metadata = bidderRequest.ortb2 || {}; - const timeout = config.getConfig('bidderTimeout'); + const timeout = bidderRequest.timeout; const params = { adapter_version: VERSION, diff --git a/modules/kueezRtbBidAdapter.js b/modules/kueezRtbBidAdapter.js index 4b0e5055328..ef53ed9baf4 100644 --- a/modules/kueezRtbBidAdapter.js +++ b/modules/kueezRtbBidAdapter.js @@ -1,7 +1,8 @@ -import { _each, deepAccess, parseSizesInput, parseUrl, uniques, isFn } from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER, VIDEO } from '../src/mediaTypes.js'; -import { getStorageManager } from '../src/storageManager.js'; +import {_each, deepAccess, parseSizesInput, parseUrl, uniques, isFn} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {getStorageManager} from '../src/storageManager.js'; +import { config } from '../src/config.js'; const GVLID = 1165; const DEFAULT_SUB_DOMAIN = 'exchange'; @@ -22,11 +23,11 @@ export const SUPPORTED_ID_SYSTEMS = { 'tdid': 1, 'pubProvidedId': 1 }; -const storage = getStorageManager({ gvlid: GVLID, bidderCode: BIDDER_CODE }); +const storage = getStorageManager({bidderCode: BIDDER_CODE}); function getTopWindowQueryParams() { try { - const parsedUrl = parseUrl(window.top.document.URL, { decodeSearchAsString: true }); + const parsedUrl = parseUrl(window.top.document.URL, {decodeSearchAsString: true}); return parsedUrl.search; } catch (e) { return ''; @@ -54,15 +55,30 @@ function isBidRequestValid(bid) { return !!(extractCID(params) && extractPID(params)); } -function buildRequest(bid, topWindowUrl, sizes, bidderRequest) { - const { params, bidId, userId, adUnitCode, schain, mediaTypes } = bid; - let { bidFloor, ext } = params; +function buildRequest(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout) { + const { + params, + bidId, + userId, + adUnitCode, + schain, + mediaTypes, + auctionId, + transactionId, + bidderRequestId, + bidRequestsCount, + bidderRequestsCount, + bidderWinsCount + } = bid; + let {bidFloor, ext} = params; const hashUrl = hashCode(topWindowUrl); const uniqueDealId = getUniqueDealId(hashUrl); const cId = extractCID(params); const pId = extractPID(params); const subDomain = extractSubDomain(params); + const gpid = deepAccess(bid, 'ortb2Imp.ext.gpid', deepAccess(bid, 'ortb2Imp.ext.data.pbadslot', '')); + if (isFn(bid.getFloor)) { const floorInfo = bid.getFloor({ currency: 'USD', @@ -90,11 +106,25 @@ function buildRequest(bid, topWindowUrl, sizes, bidderRequest) { prebidVersion: '$prebid.version$', res: `${screen.width}x${screen.height}`, schain: schain, - mediaTypes: mediaTypes + mediaTypes: mediaTypes, + gpid: gpid, + auctionId: auctionId, + transactionId: transactionId, + bidderRequestId: bidderRequestId, + bidRequestsCount: bidRequestsCount, + bidderRequestsCount: bidderRequestsCount, + bidderWinsCount: bidderWinsCount, + bidderTimeout: bidderTimeout }; appendUserIdsToRequestPayload(data, userId); + const sua = deepAccess(bidderRequest, 'ortb2.device.sua'); + + if (sua) { + data.sua = sua; + } + if (bidderRequest.gdprConsent) { if (bidderRequest.gdprConsent.consentString) { data.gdprConsent = bidderRequest.gdprConsent.consentString; @@ -107,6 +137,14 @@ function buildRequest(bid, topWindowUrl, sizes, bidderRequest) { data.usPrivacy = bidderRequest.uspConsent; } + if (bidderRequest.gppConsent) { + data.gppString = bidderRequest.gppConsent.gppString; + data.gppSid = bidderRequest.gppConsent.applicableSections; + } else if (bidderRequest.ortb2?.regs?.gpp) { + data.gppString = bidderRequest.ortb2.regs.gpp; + data.gppSid = bidderRequest.ortb2.regs.gpp_sid; + } + const dto = { method: 'POST', url: `${createDomain(subDomain)}/prebid/multi/${cId}`, @@ -148,10 +186,11 @@ function appendUserIdsToRequestPayload(payloadRef, userIds) { function buildRequests(validBidRequests, bidderRequest) { const topWindowUrl = bidderRequest.refererInfo.page || bidderRequest.refererInfo.topmostLocation; + const bidderTimeout = config.getConfig('bidderTimeout'); const requests = []; validBidRequests.forEach(validBidRequest => { const sizes = parseSizesInput(validBidRequest.sizes); - const request = buildRequest(validBidRequest, topWindowUrl, sizes, bidderRequest); + const request = buildRequest(validBidRequest, topWindowUrl, sizes, bidderRequest, bidderTimeout); requests.push(request); }); return requests; @@ -161,14 +200,14 @@ function interpretResponse(serverResponse, request) { if (!serverResponse || !serverResponse.body) { return []; } - const { bidId } = request.data; - const { results } = serverResponse.body; + const {bidId} = request.data; + const {results} = serverResponse.body; let output = []; try { results.forEach(result => { - const { creativeId, ad, price, exp, width, height, currency, advertiserDomains, mediaType = BANNER } = result; + const {creativeId, ad, price, exp, width, height, currency, metaData, advertiserDomains, mediaType = BANNER} = result; if (!ad || !price) { return; } @@ -182,11 +221,20 @@ function interpretResponse(serverResponse, request) { currency: currency || CURRENCY, netRevenue: true, ttl: exp || TTL_SECONDS, - meta: { - advertiserDomains: advertiserDomains || [] - } }; + if (metaData) { + Object.assign(response, { + meta: metaData + }) + } else { + Object.assign(response, { + meta: { + advertiserDomains: advertiserDomains || [] + } + }) + } + if (mediaType === BANNER) { Object.assign(response, { ad: ad, @@ -207,8 +255,8 @@ function interpretResponse(serverResponse, request) { function getUserSyncs(syncOptions, responses, gdprConsent = {}, uspConsent = '') { let syncs = []; - const { iframeEnabled, pixelEnabled } = syncOptions; - const { gdprApplies, consentString = '' } = gdprConsent; + const {iframeEnabled, pixelEnabled} = syncOptions; + const {gdprApplies, consentString = ''} = gdprConsent; const cidArr = responses.filter(resp => deepAccess(resp, 'body.cid')).map(resp => resp.body.cid).filter(uniques); const params = `?cid=${encodeURIComponent(cidArr.join(','))}&gdpr=${gdprApplies ? 1 : 0}&gdpr_consent=${encodeURIComponent(consentString || '')}&us_privacy=${encodeURIComponent(uspConsent || '')}` @@ -232,7 +280,9 @@ export function hashCode(s, prefix = '_') { let h = 0 let i = 0; if (l > 0) { - while (i < l) { h = (h << 5) - h + s.charCodeAt(i++) | 0; } + while (i < l) { + h = (h << 5) - h + s.charCodeAt(i++) | 0; + } } return prefix + h; } @@ -256,7 +306,8 @@ export function getUniqueDealId(key, expiry = UNIQUE_DEAL_ID_EXPIRY) { export function getStorageItem(key) { try { return tryParseJSON(storage.getDataFromLocalStorage(key)); - } catch (e) { } + } catch (e) { + } return null; } @@ -264,9 +315,10 @@ export function getStorageItem(key) { export function setStorageItem(key, value, timestamp) { try { const created = timestamp || Date.now(); - const data = JSON.stringify({ value, created }); + const data = JSON.stringify({value, created}); storage.setDataInLocalStorage(key, data); - } catch (e) { } + } catch (e) { + } } export function tryParseJSON(value) { diff --git a/modules/kulturemediaBidAdapter.js b/modules/kulturemediaBidAdapter.js new file mode 100644 index 00000000000..0acdd6406cb --- /dev/null +++ b/modules/kulturemediaBidAdapter.js @@ -0,0 +1,472 @@ +import { + deepSetValue, + logInfo, + deepAccess, + logError, + isFn, + isPlainObject, + isStr, + isNumber, + isArray, logMessage +} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; + +const BIDDER_CODE = 'kulturemedia'; +const DEFAULT_BID_TTL = 300; +const DEFAULT_CURRENCY = 'USD'; +const DEFAULT_NET_REVENUE = true; +const DEFAULT_NETWORK_ID = 1; +const OPENRTB_VIDEO_PARAMS = [ + 'mimes', + 'minduration', + 'maxduration', + 'placement', + 'protocols', + 'startdelay', + 'skip', + 'skipafter', + 'minbitrate', + 'maxbitrate', + 'delivery', + 'playbackmethod', + 'api', + 'linearity' +]; + +export const spec = { + code: BIDDER_CODE, + VERSION: '1.0.0', + supportedMediaTypes: [BANNER, VIDEO], + ENDPOINT: 'https://ads.kulture.media/pbjs', + + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bidRequest The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function (bid) { + return ( + _validateParams(bid) && + _validateBanner(bid) && + _validateVideo(bid) + ); + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param {BidRequest[]} validBidRequests 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 (validBidRequests, bidderRequest) { + if (!validBidRequests || !bidderRequest) { + return; + } + + // We need to refactor this to support mixed content when there are both + // banner and video bid requests + let openrtbRequest; + if (hasBannerMediaType(validBidRequests[0])) { + openrtbRequest = buildBannerRequestData(validBidRequests, bidderRequest); + } else if (hasVideoMediaType(validBidRequests[0])) { + openrtbRequest = buildVideoRequestData(validBidRequests[0], bidderRequest); + } + + // adding schain object + if (validBidRequests[0].schain) { + deepSetValue(openrtbRequest, 'source.ext.schain', validBidRequests[0].schain); + } + + // Attaching GDPR Consent Params + if (bidderRequest.gdprConsent) { + deepSetValue(openrtbRequest, 'user.ext.consent', bidderRequest.gdprConsent.consentString); + deepSetValue(openrtbRequest, 'regs.ext.gdpr', (bidderRequest.gdprConsent.gdprApplies ? 1 : 0)); + } + + // CCPA + if (bidderRequest.uspConsent) { + deepSetValue(openrtbRequest, 'regs.ext.us_privacy', bidderRequest.uspConsent); + } + + // EIDS + const eids = deepAccess(validBidRequests[0], 'userIdAsEids'); + if (Array.isArray(eids) && eids.length > 0) { + deepSetValue(openrtbRequest, 'user.ext.eids', eids); + } + + let publisherId = validBidRequests[0].params.publisherId; + let placementId = validBidRequests[0].params.placementId; + const networkId = validBidRequests[0].params.networkId || DEFAULT_NETWORK_ID; + + if (validBidRequests[0].params.e2etest) { + logMessage('E2E test mode enabled'); + publisherId = 'e2etest' + } + let baseEndpoint = spec.ENDPOINT + '?pid=' + publisherId; + + if (placementId) { + baseEndpoint += '&placementId=' + placementId + } + if (networkId) { + baseEndpoint += '&nId=' + networkId + } + + const payloadString = JSON.stringify(openrtbRequest); + return { + method: 'POST', + url: baseEndpoint, + data: payloadString, + }; + }, + + interpretResponse: function (serverResponse) { + const bidResponses = []; + const response = (serverResponse || {}).body; + // response is always one seat (exchange) with (optional) bids for each impression + if (response && response.seatbid && response.seatbid.length === 1 && response.seatbid[0].bid && response.seatbid[0].bid.length) { + response.seatbid[0].bid.forEach(bid => { + if (bid.adm && bid.price) { + bidResponses.push(_createBidResponse(bid)); + } + }) + } else { + logInfo('kulturemedia.interpretResponse :: no valid responses to interpret'); + } + return bidResponses; + }, + + getUserSyncs: function (syncOptions, serverResponses) { + logInfo('kulturemedia.getUserSyncs', 'syncOptions', syncOptions, 'serverResponses', serverResponses); + let syncs = []; + + if (!syncOptions.iframeEnabled && !syncOptions.pixelEnabled) { + return syncs; + } + + serverResponses.forEach(resp => { + const userSync = deepAccess(resp, 'body.ext.usersync'); + if (userSync) { + let syncDetails = []; + Object.keys(userSync).forEach(key => { + const value = userSync[key]; + if (value.syncs && value.syncs.length) { + syncDetails = syncDetails.concat(value.syncs); + } + }); + syncDetails.forEach(syncDetails => { + syncs.push({ + type: syncDetails.type === 'iframe' ? 'iframe' : 'image', + url: syncDetails.url + }); + }); + + if (!syncOptions.iframeEnabled) { + syncs = syncs.filter(s => s.type !== 'iframe') + } + if (!syncOptions.pixelEnabled) { + syncs = syncs.filter(s => s.type !== 'image') + } + } + }); + logInfo('kulturemedia.getUserSyncs result=%o', syncs); + return syncs; + }, + +}; + +/* ======================================= + * Util Functions + *======================================= */ + +/** + * @param {BidRequest} bidRequest bid request + */ +function hasBannerMediaType(bidRequest) { + return !!deepAccess(bidRequest, 'mediaTypes.banner'); +} + +/** + * @param {BidRequest} bidRequest bid request + */ +function hasVideoMediaType(bidRequest) { + return !!deepAccess(bidRequest, 'mediaTypes.video'); +} + +function _validateParams(bidRequest) { + if (!bidRequest.params) { + return false; + } + + if (bidRequest.params.e2etest) { + return true; + } + + if (!bidRequest.params.publisherId) { + logError('Validation failed: publisherId not declared'); + return false; + } + + if (!bidRequest.params.placementId) { + logError('Validation failed: placementId not declared'); + return false; + } + + const mediaTypesExists = hasVideoMediaType(bidRequest) || hasBannerMediaType(bidRequest); + if (!mediaTypesExists) { + return false; + } + + return true; +} + +/** + * Validates banner bid request. If it is not banner media type returns true. + * @param {object} bid, bid to validate + * @return boolean, true if valid, otherwise false + */ +function _validateBanner(bidRequest) { + // If there's no banner no need to validate + if (!hasBannerMediaType(bidRequest)) { + return true; + } + const banner = deepAccess(bidRequest, 'mediaTypes.banner'); + if (!Array.isArray(banner.sizes)) { + return false; + } + + return true; +} + +/** + * Validates video bid request. If it is not video media type returns true. + * @param {object} bid, bid to validate + * @return boolean, true if valid, otherwise false + */ +function _validateVideo(bidRequest) { + // If there's no video no need to validate + if (!hasVideoMediaType(bidRequest)) { + return true; + } + + const videoPlacement = deepAccess(bidRequest, 'mediaTypes.video', {}); + const videoBidderParams = deepAccess(bidRequest, 'params.video', {}); + const params = deepAccess(bidRequest, 'params', {}); + + if (params && params.e2etest) { + return true; + } + + const videoParams = { + ...videoPlacement, + ...videoBidderParams // Bidder Specific overrides + }; + + if (!Array.isArray(videoParams.mimes) || videoParams.mimes.length === 0) { + logError('Validation failed: mimes are invalid'); + return false; + } + + if (!Array.isArray(videoParams.protocols) || videoParams.protocols.length === 0) { + logError('Validation failed: protocols are invalid'); + return false; + } + + if (!videoParams.context) { + logError('Validation failed: context id not declared'); + return false; + } + + if (videoParams.context !== 'instream') { + logError('Validation failed: only context instream is supported '); + return false; + } + + if (typeof videoParams.playerSize === 'undefined' || !Array.isArray(videoParams.playerSize) || !Array.isArray(videoParams.playerSize[0])) { + logError('Validation failed: player size not declared or is not in format [[w,h]]'); + return false; + } + + return true; +} + +/** + * Prepares video request data. + * + * @param bidRequest + * @param bidderRequest + * @returns openrtbRequest + */ +function buildVideoRequestData(bidRequest, bidderRequest) { + const {params} = bidRequest; + + const videoAdUnit = deepAccess(bidRequest, 'mediaTypes.video', {}); + const videoBidderParams = deepAccess(bidRequest, 'params.video', {}); + + const videoParams = { + ...videoAdUnit, + ...videoBidderParams // Bidder Specific overrides + }; + + if (bidRequest.params && bidRequest.params.e2etest) { + videoParams.playerSize = [[640, 480]] + videoParams.conext = 'instream' + } + + const video = { + w: parseInt(videoParams.playerSize[0][0], 10), + h: parseInt(videoParams.playerSize[0][1], 10), + } + + // Obtain all ORTB params related video from Ad Unit + OPENRTB_VIDEO_PARAMS.forEach((param) => { + if (videoParams.hasOwnProperty(param)) { + video[param] = videoParams[param]; + } + }); + + // Placement Inference Rules: + // - If no placement is defined then default to 1 (In Stream) + video.placement = video.placement || 2; + + // - If product is instream (for instream context) then override placement to 1 + if (params.context === 'instream') { + video.startdelay = video.startdelay || 0; + video.placement = 1; + } + + // bid floor + const bidFloorRequest = { + currency: bidRequest.params.cur || 'USD', + mediaType: 'video', + size: '*' + }; + let floorData = bidRequest.params + if (isFn(bidRequest.getFloor)) { + floorData = bidRequest.getFloor(bidFloorRequest); + } else { + if (params.bidfloor) { + floorData = {floor: params.bidfloor, currency: params.currency || 'USD'}; + } + } + + const openrtbRequest = { + id: bidRequest.bidId, + imp: [ + { + id: '1', + video: video, + secure: isSecure() ? 1 : 0, + bidfloor: floorData.floor, + bidfloorcur: floorData.currency + } + ], + site: { + domain: bidderRequest.refererInfo.domain, + page: bidderRequest.refererInfo.page, + ref: bidderRequest.refererInfo.ref, + }, + ext: { + hb: 1, + prebidver: '$prebid.version$', + adapterver: spec.VERSION, + }, + }; + + // content + if (videoParams.content && isPlainObject(videoParams.content)) { + openrtbRequest.site.content = {}; + const contentStringKeys = ['id', 'title', 'series', 'season', 'genre', 'contentrating', 'language', 'url']; + const contentNumberkeys = ['episode', 'prodq', 'context', 'livestream', 'len']; + const contentArrayKeys = ['cat']; + const contentObjectKeys = ['ext']; + for (const contentKey in videoBidderParams.content) { + if ( + (contentStringKeys.indexOf(contentKey) > -1 && isStr(videoParams.content[contentKey])) || + (contentNumberkeys.indexOf(contentKey) > -1 && isNumber(videoParams.content[contentKey])) || + (contentObjectKeys.indexOf(contentKey) > -1 && isPlainObject(videoParams.content[contentKey])) || + (contentArrayKeys.indexOf(contentKey) > -1 && isArray(videoParams.content[contentKey]) && + videoParams.content[contentKey].every(catStr => isStr(catStr)))) { + openrtbRequest.site.content[contentKey] = videoParams.content[contentKey]; + } else { + logMessage('KultureMedia bid adapter validation error: ', contentKey, ' is either not supported is OpenRTB V2.5 or value is undefined'); + } + } + } + + return openrtbRequest; +} + +/** + * Prepares video request data. + * + * @param bidRequest + * @param bidderRequest + * @returns openrtbRequest + */ +function buildBannerRequestData(bidRequests, bidderRequest) { + const impr = bidRequests.map(bidRequest => ({ + id: bidRequest.bidId, + banner: { + format: bidRequest.mediaTypes.banner.sizes.map(sizeArr => ({ + w: sizeArr[0], + h: sizeArr[1] + })) + }, + ext: { + exchange: { + placementId: bidRequest.params.placementId + } + } + })); + + const openrtbRequest = { + id: bidderRequest.auctionId, + imp: impr, + site: { + domain: bidderRequest.refererInfo?.domain, + page: bidderRequest.refererInfo?.page, + ref: bidderRequest.refererInfo?.ref, + }, + ext: {} + }; + return openrtbRequest; +} + +function _createBidResponse(bid) { + const isADomainPresent = + bid.adomain && bid.adomain.length; + const bidResponse = { + requestId: bid.impid, + bidderCode: spec.code, + cpm: bid.price, + width: bid.w, + height: bid.h, + ad: bid.adm, + ttl: typeof bid.exp === 'number' ? bid.exp : DEFAULT_BID_TTL, + creativeId: bid.crid, + netRevenue: DEFAULT_NET_REVENUE, + currency: DEFAULT_CURRENCY, + mediaType: deepAccess(bid, 'ext.prebid.type', BANNER) + } + + if (isADomainPresent) { + bidResponse.meta = { + advertiserDomains: bid.adomain + }; + } + + if (bidResponse.mediaType === VIDEO) { + bidResponse.vastXml = bid.adm; + } + + return bidResponse; +} + +function isSecure() { + return document.location.protocol === 'https:'; +} + +registerBidder(spec); diff --git a/modules/kulturemediaBidAdapter.md b/modules/kulturemediaBidAdapter.md new file mode 100644 index 00000000000..0bd17e97982 --- /dev/null +++ b/modules/kulturemediaBidAdapter.md @@ -0,0 +1,140 @@ +# Overview + +``` +Module Name: Kulture Media Bid Adapter +Module Type: Bidder Adapter +Maintainer: devops@kulture.media +``` + +# Description + +Module that connects to Kulture Media's demand sources. +Kulture Media bid adapter supports Banner and Video. + + +# Test Parameters + +## Banner + +``` +var adUnits = [ + { + code: 'banner-ad-div', + mediaTypes: { + banner: { + sizes: [[300, 250], [300,600]] + } + }, + bids: [{ + bidder: 'kulturemedia', + params: { + placementId: 'test', + publisherId: 'test', + } + }] + } +]; +``` + +## Video + +We support the following OpenRTB params that can be specified in `mediaTypes.video` or in `bids[].params.video` +- 'mimes', +- 'minduration', +- 'maxduration', +- 'placement', +- 'protocols', +- 'startdelay', +- 'skip', +- 'skipafter', +- 'minbitrate', +- 'maxbitrate', +- 'delivery', +- 'playbackmethod', +- 'api', +- 'linearity' + + +## Instream Video adUnit using mediaTypes.video +*Note:* By default, the adapter will read the mandatory parameters from mediaTypes.video. +*Note:* The Video SSP ad server will respond with an VAST XML to load into your defined player. +``` + var adUnits = [ + { + code: 'video1', + mediaTypes: { + video: { + context: 'instream', + playerSize: [640, 480], + mimes: ['video/mp4', 'application/javascript'], + protocols: [2,5], + api: [2], + position: 1, + delivery: [2], + minduration: 10, + maxduration: 30, + placement: 1, + playbackmethod: [1,5], + } + }, + bids: [ + { + bidder: 'kulturemedia', + params: { + bidfloor: 0.5, + publisherId: '12345', + placementId: '6789' + } + } + ] + } + ] +``` + +# End To End testing mode +By passing bid.params.e2etest = true you will be able to receive a test creative + +## Banner +``` +var adUnits = [ + { + code: 'banner-ad-div', + mediaTypes: { + banner: { + sizes: [[300, 250], [300,600]] + } + }, + bids: [{ + bidder: 'kulturemedia', + params: { + e2etest: true + } + }] + } +]; +``` + +## Video +``` +var adUnits = [ + { + code: 'video1', + mediaTypes: { + video: { + context: "instream", + playerSize: [[640, 480]], + mimes: ['video/mp4'], + protocols: [2,5], + } + }, + bids: [ + { + bidder: 'kulturemedia', + params: { + e2etest: true + } + } + ] + } +] +``` diff --git a/modules/lemmaDigitalBidAdapter.js b/modules/lemmaDigitalBidAdapter.js new file mode 100644 index 00000000000..9fa3081a47e --- /dev/null +++ b/modules/lemmaDigitalBidAdapter.js @@ -0,0 +1,570 @@ +import * as utils from '../src/utils.js'; +import { config } from '../src/config.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; + +var BIDDER_CODE = 'lemmadigital'; +var LOG_WARN_PREFIX = 'LEMMADIGITAL: '; +var ENDPOINT = 'https://bid.lemmadigital.com/lemma/servad'; +var USER_SYNC = 'https://sync.lemmadigital.com/js/usersync.html?'; +var DEFAULT_CURRENCY = 'USD'; +var AUCTION_TYPE = 2; +var DEFAULT_TMAX = 300; +var DEFAULT_NET_REVENUE = false; +var DEFAULT_SECURE = 1; +var RESPONSE_TTL = 300; +var pubId = 0; +var adunitId = 0; + +export var spec = { + + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, 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: (bid) => { + if (!bid || !bid.params) { + utils.logError(LOG_WARN_PREFIX, 'nil/empty bid object'); + return false; + } + if (!utils.isEmpty(bid.params.pubId) || !utils.isNumber(bid.params.pubId)) { + utils.logWarn(LOG_WARN_PREFIX + 'Error: publisherId is mandatory and cannot be string. Call to OpenBid will not be sent for ad unit: ' + JSON.stringify(bid)); + return false; + } + if (!bid.params.adunitId) { + utils.logWarn(LOG_WARN_PREFIX + 'Error: adUnitId is mandatory. Call to OpenBid will not be sent for ad unit: ' + JSON.stringify(bid)); + return false; + } + // video bid request validation + if (bid.params.hasOwnProperty('video')) { + if (!bid.params.video.hasOwnProperty('mimes') || !utils.isArray(bid.params.video.mimes) || bid.params.video.mimes.length === 0) { + utils.logWarn(LOG_WARN_PREFIX + 'Error: For video ads, mimes is mandatory and must specify atlease 1 mime value. Call to OpenBid will not be sent for ad unit:' + JSON.stringify(bid)); + return false; + } + } + return true; + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param {validBidRequests[]} - an array of bids + * @return ServerRequest Info describing the request to the server. + **/ + buildRequests: (validBidRequests, bidderRequest) => { + if (validBidRequests.length === 0) { + return; + } + var refererInfo; + if (bidderRequest && bidderRequest.refererInfo) { + refererInfo = bidderRequest.refererInfo; + } + var conf = spec._setRefURL(refererInfo); + const request = spec._createoRTBRequest(validBidRequests, conf); + if (request && request.imp.length == 0) { + return; + } + spec._setOtherParams(bidderRequest, request); + const endPoint = spec._endPointURL(validBidRequests); + return { + method: 'POST', + url: endPoint, + data: JSON.stringify(request), + }; + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} response A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + **/ + interpretResponse: (response, request) => { + return spec._parseRTBResponse(request, response.body); + }, + + /** + * 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. + * @return {UserSync[]} The user syncs which should be dropped. + **/ + getUserSyncs: (syncOptions, serverResponses) => { + let syncurl = USER_SYNC + 'pid=' + pubId; + if (syncOptions.iframeEnabled) { + return [{ + type: 'iframe', + url: syncurl + }]; + } else { + utils.logWarn(LOG_WARN_PREFIX + 'Please enable iframe based user sync.'); + } + }, + + /** + * Generate UUID + */ + _createUUID: () => { + return new Date().getTime().toString(); + }, + + /** + * parse object + **/ + _parseJSON: function (rawPayload) { + try { + if (rawPayload) { + return JSON.parse(rawPayload); + } + } catch (ex) { + utils.logError(LOG_WARN_PREFIX, 'Exception: ', ex); + } + return null; + }, + + /** + * + * set referal url + */ + _setRefURL: (refererInfo) => { + var conf = {}; + conf.pageURL = (refererInfo && refererInfo.referer) ? refererInfo.referer : window.location.href; + if (refererInfo && refererInfo.referer) { + conf.refURL = refererInfo.referer; + } else { + conf.refURL = ''; + } + return conf; + }, + + /** + * set other params into oRTB request + */ + _setOtherParams: (request, ortbRequest) => { + var params = request && request.params ? request.params : null; + if (params) { + ortbRequest.tmax = params.tmax; + ortbRequest.bcat = params.bcat; + } + }, + + /** + * create IAB standard OpenRTB bid request + **/ + _createoRTBRequest: (bidRequests, conf) => { + var oRTBObject = {}; + try { + oRTBObject = { + id: spec._createUUID(), + at: AUCTION_TYPE, + tmax: DEFAULT_TMAX, + cur: [DEFAULT_CURRENCY], + imp: spec._getImpressionArray(bidRequests), + user: {}, + ext: {} + }; + var bid = bidRequests[0]; + + var site = spec._getSiteObject(bid, conf); + if (site) { + oRTBObject.site = site; + // add the content object from config in request + if (typeof config.getConfig('content') === 'object') { + oRTBObject.site.content = config.getConfig('content'); + } + } + var app = spec._getAppObject(bid); + if (app) { + oRTBObject.app = app; + if (typeof oRTBObject.app.content !== 'object' && typeof config.getConfig('content') === 'object') { + oRTBObject.app.content = + config.getConfig('content') || undefined; + } + } + var device = spec._getDeviceObject(bid); + if (device) { + oRTBObject.device = device; + } + var source = spec._getSourceObject(bid); + if (source) { + oRTBObject.source = source; + } + return oRTBObject; + } catch (ex) { + utils.logError(LOG_WARN_PREFIX, 'ERROR ', ex); + } + }, + + /** + * create impression array objects + **/ + _getImpressionArray: (request) => { + var impArray = []; + var map = request.map(bid => spec._getImpressionObject(bid)); + if (map) { + map.forEach(o => { + if (o) { + impArray.push(o); + } + }); + } + return impArray; + }, + + /** + * create impression (single) object + **/ + _getImpressionObject: (bid) => { + var impression = {}; + var bObj; + var vObj; + var sizes = bid.hasOwnProperty('sizes') ? bid.sizes : []; + var mediaTypes = ''; + var format = []; + var params = bid && bid.params ? bid.params : null; + impression = { + id: bid.bidId, + tagid: params.adunitId ? params.adunitId.toString() : undefined, + secure: DEFAULT_SECURE, + bidfloorcur: params.currency ? params.currency : DEFAULT_CURRENCY + }; + if (params.bidFloor) { + impression.bidfloor = params.bidFloor; + } + if (bid.hasOwnProperty('mediaTypes')) { + for (mediaTypes in bid.mediaTypes) { + switch (mediaTypes) { + case BANNER: + bObj = spec._getBannerRequest(bid); + if (bObj) { + impression.banner = bObj; + } + break; + case VIDEO: + vObj = spec._getVideoRequest(bid); + if (vObj) { + impression.video = vObj; + } + break; + } + } + } else { + bObj = { + pos: 0, + w: sizes && sizes[0] ? sizes[0][0] : 0, + h: sizes && sizes[0] ? sizes[0][1] : 0, + }; + if (utils.isArray(sizes) && sizes.length > 1) { + sizes = sizes.splice(1, sizes.length - 1); + sizes.forEach(size => { + format.push({ + w: size[0], + h: size[1] + }); + }); + bObj.format = format; + } + impression.banner = bObj; + } + spec._setFloor(impression, bid); + return impression.hasOwnProperty(BANNER) || + impression.hasOwnProperty(VIDEO) ? impression : undefined; + }, + + /** + * set bid floor + **/ + _setFloor: (impObj, bid) => { + let bidFloor = -1; + // get lowest floor from floorModule + if (typeof bid.getFloor === 'function') { + [BANNER, VIDEO].forEach(mediaType => { + if (impObj.hasOwnProperty(mediaType)) { + let floorInfo = bid.getFloor({ currency: impObj.bidfloorcur, mediaType: mediaType, size: '*' }); + if (typeof floorInfo === 'object' && floorInfo.currency === impObj.bidfloorcur && !isNaN(parseInt(floorInfo.floor))) { + let mediaTypeFloor = parseFloat(floorInfo.floor); + bidFloor = (bidFloor == -1 ? mediaTypeFloor : Math.min(mediaTypeFloor, bidFloor)); + } + } + }); + } + // get highest from impObj.bidfllor and floor from floor module + // as we are using Math.max, it is ok if we have not got any floor from floorModule, then value of bidFloor will be -1 + if (impObj.bidfloor) { + bidFloor = Math.max(bidFloor, impObj.bidfloor); + } + + // assign value only if bidFloor is > 0 + impObj.bidfloor = ((!isNaN(bidFloor) && bidFloor > 0) ? bidFloor : undefined); + }, + + /** + * parse Open RTB response + **/ + _parseRTBResponse: (request, response) => { + var bidResponses = []; + try { + if (response.seatbid) { + var currency = response.curr || DEFAULT_CURRENCY; + var seatbid = response.seatbid; + seatbid.forEach(seatbidder => { + var bidder = seatbidder.bid; + bidder.forEach(bid => { + var req = spec._parseJSON(request.data); + var newBid = { + requestId: bid.impid, + cpm: parseFloat(bid.price).toFixed(2), + width: bid.w, + height: bid.h, + creativeId: bid.crid, + currency: currency, + netRevenue: DEFAULT_NET_REVENUE, + ttl: RESPONSE_TTL, + referrer: req.site.ref, + ad: bid.adm + }; + if (bid.dealid) { + newBid.dealId = bid.dealid; + } + if (req.imp && req.imp.length > 0) { + req.imp.forEach(robj => { + if (bid.impid === robj.id) { + spec._checkMediaType(bid.adm, newBid); + switch (newBid.mediaType) { + case BANNER: + break; + case VIDEO: + newBid.width = bid.hasOwnProperty('w') ? bid.w : robj.video.w; + newBid.height = bid.hasOwnProperty('h') ? bid.h : robj.video.h; + newBid.vastXml = bid.adm; + break; + } + } + }); + } + bidResponses.push(newBid); + }); + }); + } + } catch (error) { + utils.logError(LOG_WARN_PREFIX, 'ERROR ', error); + } + return bidResponses; + }, + + /** + * get bid request api end point url + **/ + _endPointURL: (request) => { + var params = request && request[0].params ? request[0].params : null; + if (params) { + pubId = params.pubId ? params.pubId : 0; + adunitId = params.adunitId ? params.adunitId : 0; + return ENDPOINT + '?pid=' + pubId + '&aid=' + adunitId; + } + return null; + }, + + /** + * get domain name from url + **/ + _getDomain: (url) => { + var a = document.createElement('a'); + a.setAttribute('href', url); + return a.hostname; + }, + + /** + * create the site object + **/ + _getSiteObject: (request, conf) => { + var params = request && request.params ? request.params : null; + if (params) { + pubId = params.pubId ? params.pubId : '0'; + var siteId = params.siteId ? params.siteId : '0'; + var appParams = params.app; + if (!appParams) { + return { + publisher: { + id: pubId.toString() + }, + domain: spec._getDomain(conf.pageURL), + id: siteId.toString(), + ref: conf.refURL, + page: conf.pageURL, + cat: params.category, + pagecat: params.page_category + }; + } + } + return null; + }, + + /** + * create the app object + **/ + _getAppObject: (request) => { + var params = request && request.params ? request.params : null; + if (params) { + pubId = params.pubId ? params.pubId : 0; + var appParams = params.app; + if (appParams) { + return { + publisher: { + id: pubId.toString(), + }, + id: appParams.id, + name: appParams.name, + bundle: appParams.bundle, + storeurl: appParams.storeUrl, + domain: appParams.domain, + cat: appParams.cat || params.category, + pagecat: appParams.pagecat || params.page_category + }; + } + } + return null; + }, + + /** + * create the device object + **/ + _getDeviceObject: (request) => { + var params = request && request.params ? request.params : null; + if (params) { + return { + dnt: utils.getDNT() ? 1 : 0, + ua: navigator.userAgent, + language: (navigator.language || navigator.browserLanguage || navigator.userLanguage || navigator.systemLanguage), + w: (window.screen.width || window.innerWidth), + h: (window.screen.height || window.innerHeigh), + geo: { + country: params.country, + lat: params.latitude, + lon: params.longitude, + accuracy: params.accuracy, + region: params.region, + city: params.city, + zip: params.zip + }, + ip: params.ip, + make: params.make, + model: params.model, + os: params.os, + carrier: params.carrier, + devicetype: params.device_type, + ifa: params.ifa, + }; + } + return null; + }, + + /** + * create source object + */ + _getSourceObject: (request) => { + var params = request && request.params ? request.params : null; + if (params) { + return { + pchain: params.pchain, + ext: { + schain: request.schain + }, + }; + } + return null; + }, + + /** + * get request ad sizes + **/ + _getSizes: (request) => { + if (request && request.sizes && utils.isArray(request.sizes[0]) && request.sizes[0].length > 0) { + return request.sizes[0]; + } + return null; + }, + + /** + * create the banner object + **/ + _getBannerRequest: (bid) => { + var bObj; + var adFormat = []; + if (utils.deepAccess(bid, 'mediaTypes.banner')) { + var params = bid ? bid.params : null; + var bannerData = params && params.banner; + var sizes = spec._getSizes(bid) || []; + if (sizes && sizes.length == 0) { + sizes = bid.mediaTypes.banner.sizes[0]; + } + if (sizes && sizes.length > 0) { + bObj = {}; + bObj.w = sizes[0]; + bObj.h = sizes[1]; + bObj.pos = 0; + if (bannerData) { + bObj = utils.deepClone(bannerData); + } + sizes = bid.mediaTypes.banner.sizes; + if (sizes.length > 0) { + adFormat = []; + sizes.forEach(function (size) { + if (size.length > 1) { + adFormat.push({ w: size[0], h: size[1] }); + } + }); + if (adFormat.length > 0) { + bObj.format = adFormat; + } + } + } else { + utils.logWarn(LOG_WARN_PREFIX + 'Error: mediaTypes.banner.sizes missing for adunit: ' + bid.params.adunitId); + } + } + return bObj; + }, + + /** + * create the video object + **/ + _getVideoRequest: (bid) => { + var vObj; + if (utils.deepAccess(bid, 'mediaTypes.video')) { + var params = bid ? bid.params : null; + var videoData = utils.mergeDeep(utils.deepAccess(bid.mediaTypes, 'video'), params.video); + var sizes = bid.mediaTypes.video ? bid.mediaTypes.video.playerSize : [] + if (sizes && sizes.length > 0) { + vObj = {}; + if (videoData) { + vObj = utils.deepClone(videoData); + } + vObj.w = sizes[0]; + vObj.h = sizes[1]; + } else { + utils.logWarn(LOG_WARN_PREFIX + 'Error: mediaTypes.video.sizes missing for adunit: ' + bid.params.adunitId); + } + } + return vObj; + }, + + /** + * check media type + **/ + _checkMediaType: (adm, newBid) => { + // Create a regex here to check the strings + var videoRegex = new RegExp(/VAST.*version/); + if (videoRegex.test(adm)) { + newBid.mediaType = VIDEO; + } else { + newBid.mediaType = BANNER; + } + } +}; + +registerBidder(spec); diff --git a/modules/lemmaDigitalBidAdapter.md b/modules/lemmaDigitalBidAdapter.md new file mode 100644 index 00000000000..5a22a7588da --- /dev/null +++ b/modules/lemmaDigitalBidAdapter.md @@ -0,0 +1,61 @@ +# Overview + +``` +Module Name: Lemmadigital Bid Adapter +Module Type: Bidder Adapter +Maintainer: lemmadev@lemmatechnologies.com +``` + +# Description + +Connects to Lemma exchange for bids. +Lemmadigital bid adapter supports Video, Banner formats. + +# Sample Banner Ad Unit: For Publishers +``` +var adUnits = [{ + code: 'div-lemma-ad-1', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]], // required + } + }, + // Replace this object to test a new Adapter! + bids: [{ + bidder: 'lemmadigital', + params: { + pubId: 1, // required + adunitId: '3768', // required + latitude: 37.3230, + longitude: -122.0322, + device_type: 2 + } + }] +}]; +``` + +# Sample Video Ad Unit: For Publishers +``` +var adUnits = [{ + mediaTypes: { + video: { + playerSize: [640, 480], // required + context: 'instream' + } + }, + // Replace this object to test a new Adapter! + bids: [{ + bidder: 'lemmadigital', + params: { + pubId: 1, // required + adunitId: '3769', // required + latitude: 37.3230, + longitude: -122.0322, + device_type: 4, + video: { + mimes: ['video/mp4','video/x-flv'], // required + } + } + }] +}]; +``` diff --git a/modules/limelightDigitalBidAdapter.js b/modules/limelightDigitalBidAdapter.js index 87cf6e4fe21..601d78578b3 100644 --- a/modules/limelightDigitalBidAdapter.js +++ b/modules/limelightDigitalBidAdapter.js @@ -158,7 +158,12 @@ function buildPlacement(bidRequest) { type: bidRequest.params.adUnitType.toUpperCase(), publisherId: bidRequest.params.publisherId, userIdAsEids: bidRequest.userIdAsEids, - supplyChain: bidRequest.schain + supplyChain: bidRequest.schain, + custom1: bidRequest.params.custom1, + custom2: bidRequest.params.custom2, + custom3: bidRequest.params.custom3, + custom4: bidRequest.params.custom4, + custom5: bidRequest.params.custom5 } } } diff --git a/modules/limelightDigitalBidAdapter.md b/modules/limelightDigitalBidAdapter.md index a4abb6f1411..2c773859a7f 100644 --- a/modules/limelightDigitalBidAdapter.md +++ b/modules/limelightDigitalBidAdapter.md @@ -24,7 +24,12 @@ var adUnits = [{ params: { host: 'exchange-9qao.ortb.net', adUnitId: 0, - adUnitType: 'banner' + adUnitType: 'banner', + custom1: 'custom1', + custom2: 'custom2', + custom3: 'custom3', + custom4: 'custom4', + custom5: 'custom5' } }] }]; @@ -40,7 +45,12 @@ var videoAdUnit = [{ params: { host: 'exchange-9qao.ortb.net', adUnitId: 0, - adUnitType: 'video' + adUnitType: 'video', + custom1: 'custom1', + custom2: 'custom2', + custom3: 'custom3', + custom4: 'custom4', + custom5: 'custom5' } }] }]; diff --git a/modules/liveIntentIdSystem.js b/modules/liveIntentIdSystem.js index de70b0eaccd..3ecd061085c 100644 --- a/modules/liveIntentIdSystem.js +++ b/modules/liveIntentIdSystem.js @@ -7,12 +7,14 @@ import { triggerPixel, logError } from '../src/utils.js'; import { ajaxBuilder } from '../src/ajax.js'; import { submodule } from '../src/hook.js'; -import { LiveConnect } from 'live-connect-js/esm/initializer.js'; +import { LiveConnect } from 'live-connect-js'; // eslint-disable-line prebid/validate-imports import { gdprDataHandler, uspDataHandler } from '../src/adapterManager.js'; -import { getStorageManager } from '../src/storageManager.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; +const EVENTS_TOPIC = 'pre_lips' const MODULE_NAME = 'liveIntentId'; -export const storage = getStorageManager({gvlid: null, moduleName: MODULE_NAME}); +export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); const defaultRequestedAttributes = {'nonId': true} const calls = { ajaxGet: (url, onSuccess, onError, timeout) => { @@ -39,14 +41,22 @@ let liveConnect = null; * This function is used in tests */ export function reset() { - if (window && window.liQ) { - window.liQ = []; + if (window && window.liQ_instances) { + window.liQ_instances.forEach(i => i.eventBus.off(EVENTS_TOPIC, setEventFiredFlag)) + window.liQ_instances = []; } liveIntentIdSubmodule.setModuleMode(null) eventFired = false; liveConnect = null; } +/** + * This function is also used in tests + */ +export function setEventFiredFlag() { + eventFired = true; +} + function parseLiveIntentCollectorConfig(collectConfig) { const config = {}; collectConfig = collectConfig || {} @@ -100,6 +110,7 @@ function initializeLiveConnect(configParams) { liveConnectConfig.wrapperName = 'prebid'; liveConnectConfig.identityResolutionConfig = identityResolutionConfig; liveConnectConfig.identifiersToResolve = configParams.identifiersToResolve || []; + liveConnectConfig.fireEventDelay = configParams.fireEventDelay; const usPrivacyString = uspDataHandler.getConsentData(); if (usPrivacyString) { liveConnectConfig.usPrivacyString = usPrivacyString; @@ -121,8 +132,14 @@ function initializeLiveConnect(configParams) { function tryFireEvent() { if (!eventFired && liveConnect) { - liveConnect.fire(); - eventFired = true; + const eventDelay = liveConnect.config.fireEventDelay || 500 + setTimeout(() => { + const instances = window.liQ_instances + instances.forEach(i => i.eventBus.once(EVENTS_TOPIC, setEventFiredFlag)) + if (!eventFired && liveConnect) { + liveConnect.fire(); + } + }, eventDelay) } } @@ -171,6 +188,14 @@ export const liveIntentIdSubmodule = { result.uid2 = { 'id': value.uid2 } } + if (value.bidswitch) { + result.bidswitch = { 'id': value.bidswitch } + } + + if (value.medianet) { + result.medianet = { 'id': value.medianet } + } + return result } diff --git a/modules/livewrappedBidAdapter.js b/modules/livewrappedBidAdapter.js index 3839eb80b91..af754ae3ff0 100644 --- a/modules/livewrappedBidAdapter.js +++ b/modules/livewrappedBidAdapter.js @@ -1,4 +1,4 @@ -import {deepAccess, getWindowTop, isSafariBrowser, mergeDeep} from '../src/utils.js'; +import {deepAccess, getWindowTop, isSafariBrowser, mergeDeep, isFn, isPlainObject} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {config} from '../src/config.js'; import {find} from '../src/polyfill.js'; @@ -68,7 +68,9 @@ export const spec = { bidUrl = bidUrl ? bidUrl.params.bidUrl : URL; url = url ? url.params.url : (getAppDomain() || getTopWindowLocation(bidderRequest)); test = test ? test.params.test : undefined; - var adRequests = bidRequests.map(bidToAdRequest); + const currency = config.getConfig('currency.adServerCurrency') || 'USD'; + var adRequests = bidRequests.map(b => bidToAdRequest(b, currency)); + const adRequestsContainFloors = adRequests.some(r => r.flr !== undefined); if (eids) { ortb2 = mergeDeep(mergeDeep({}, ortb2 || {}), eids); @@ -96,7 +98,8 @@ export const spec = { rcv: getAdblockerRecovered(), adRequests: [...adRequests], rtbData: ortb2, - schain: schain + schain: schain, + flrCur: adRequestsContainFloors ? currency : undefined }; if (config.getConfig().debug) { @@ -223,13 +226,14 @@ function hasPubcid(bid) { return !!bid.crumbs && !!bid.crumbs.pubcid; } -function bidToAdRequest(bid) { +function bidToAdRequest(bid, currency) { var adRequest = { adUnitId: bid.params.adUnitId, callerAdUnitId: bid.params.adUnitName || bid.adUnitCode || bid.placementCode, bidId: bid.bidId, transactionId: bid.transactionId, formats: getSizes(bid).map(sizeToFormat), + flr: getBidFloor(bid, currency), options: bid.params.options }; @@ -264,6 +268,22 @@ function sizeToFormat(size) { } } +function getBidFloor(bid, currency) { + if (!isFn(bid.getFloor)) { + return undefined; + } + + const floor = bid.getFloor({ + currency: currency, + mediaType: '*', + size: '*' + }); + + return isPlainObject(floor) && !isNaN(floor.floor) && floor.currency == currency + ? floor.floor + : undefined; +} + function getAdblockerRecovered() { try { return getWindowTop().I12C && getWindowTop().I12C.Morph === 1; @@ -302,21 +322,13 @@ function getDeviceIfa() { } function getDeviceWidth() { - let device = config.getConfig('device'); - if (typeof device === 'object' && device.width) { - return device.width; - } - - return window.innerWidth; + const device = config.getConfig('device') || {}; + return device.w || window.innerWidth; } function getDeviceHeight() { - let device = config.getConfig('device'); - if (typeof device === 'object' && device.height) { - return device.height; - } - - return window.innerHeight; + const device = config.getConfig('device') || {}; + return device.h || window.innerHeight; } function getCoppa() { diff --git a/modules/lotamePanoramaIdSystem.js b/modules/lotamePanoramaIdSystem.js index 832d98e4f83..02b01b8bd9d 100644 --- a/modules/lotamePanoramaIdSystem.js +++ b/modules/lotamePanoramaIdSystem.js @@ -16,8 +16,9 @@ import { } 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 { uspDataHandler } from '../src/adapterManager.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; const KEY_ID = 'panoramaId'; const KEY_EXPIRY = `${KEY_ID}_expiry`; @@ -29,8 +30,9 @@ const DAY_MS = 60 * 60 * 24 * 1000; const MISSING_CORE_CONSENT = 111; const GVLID = 95; const ID_HOST = 'id.crwdcntrl.net'; +const ID_HOST_COOKIELESS = 'c.ltmsphrcl.net'; -export const storage = getStorageManager({gvlid: GVLID, moduleName: MODULE_NAME}); +export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); let cookieDomain; /** @@ -252,6 +254,13 @@ export const lotamePanoramaIdSubmodule = { usPrivacy = getFromStorage('us_privacy'); } + const getRequestHost = function() { + if (navigator.userAgent && navigator.userAgent.indexOf('Safari') != -1 && navigator.userAgent.indexOf('Chrome') == -1) { + return ID_HOST_COOKIELESS; + } + return ID_HOST; + } + const resolveIdFunction = function (callback) { let queryParams = {}; if (storedUserId) { @@ -288,7 +297,7 @@ export const lotamePanoramaIdSubmodule = { const url = buildUrl({ protocol: 'https', - host: ID_HOST, + host: getRequestHost(), pathname: '/id', search: isEmpty(queryParams) ? undefined : queryParams, }); diff --git a/modules/luponmediaBidAdapter.js b/modules/luponmediaBidAdapter.js index 835c04ba074..059a07b9999 100755 --- a/modules/luponmediaBidAdapter.js +++ b/modules/luponmediaBidAdapter.js @@ -292,7 +292,7 @@ function newOrtbBidRequest(bidRequest, bidderRequest, currentImps) { source: { tid: bidRequest.transactionId }, - tmax: config.getConfig('timeout') || 1500, + tmax: bidderRequest.timeout, imp: currentImps.concat([{ id: bidRequest.bidId, secure: 1, diff --git a/modules/mabidderBidAdapter.js b/modules/mabidderBidAdapter.js index 12da791d2c4..632403c6643 100644 --- a/modules/mabidderBidAdapter.js +++ b/modules/mabidderBidAdapter.js @@ -1,5 +1,7 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js'; +import {getGlobal} from '../src/prebidGlobal.js'; + const BIDDER_CODE = 'mabidder'; export const baseUrl = 'https://prebid.ecdrsvc.com/bid'; export const spec = { @@ -33,7 +35,7 @@ export const spec = { url: baseUrl, method: 'POST', data: { - v: $$PREBID_GLOBAL$$.version, + v: getGlobal().version, bids: bids, url: bidderRequest.refererInfo.page || '', referer: bidderRequest.refererInfo.ref || '', diff --git a/modules/magniteAnalyticsAdapter.js b/modules/magniteAnalyticsAdapter.js index c6911897e84..7cede6af38d 100644 --- a/modules/magniteAnalyticsAdapter.js +++ b/modules/magniteAnalyticsAdapter.js @@ -1,14 +1,33 @@ -import { generateUUID, mergeDeep, deepAccess, parseUrl, logError, pick, isEmpty, logWarn, debugTurnedOn, parseQS, getWindowLocation, isAdUnitCodeMatchingSlot, isNumber, deepSetValue, deepClone, logInfo, isGptPubadsDefined } from '../src/utils.js'; +import { + debugTurnedOn, + deepAccess, + deepClone, + deepSetValue, + generateUUID, + getWindowLocation, + isAdUnitCodeMatchingSlot, + isEmpty, + isGptPubadsDefined, + isNumber, + logError, + logInfo, + logWarn, + mergeDeep, + parseQS, + parseUrl, + pick +} from '../src/utils.js'; 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 { config } from '../src/config.js'; -import { getGlobal } from '../src/prebidGlobal.js'; -import { getStorageManager } from '../src/storageManager.js'; +import {ajax} from '../src/ajax.js'; +import {config} from '../src/config.js'; +import {getGlobal} from '../src/prebidGlobal.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {MODULE_TYPE_ANALYTICS} from '../src/activities/modules.js'; const RUBICON_GVL_ID = 52; -export const storage = getStorageManager({ gvlid: RUBICON_GVL_ID, moduleName: 'magnite' }); +export const storage = getStorageManager({ moduleType: MODULE_TYPE_ANALYTICS, moduleName: 'magnite' }); const COOKIE_NAME = 'mgniSession'; const LAST_SEEN_EXPIRE_TIME = 1800000; // 30 mins const END_EXPIRE_TIME = 21600000; // 6 hours @@ -37,7 +56,8 @@ const { BIDDER_DONE, BID_TIMEOUT, BID_WON, - BILLABLE_EVENT + BILLABLE_EVENT, + SEAT_NON_BID }, STATUS: { GOOD, @@ -305,6 +325,10 @@ const getTopLevelDetails = () => { } } + if (browser) { + deepSetValue(payload, rubiConf.pbaBrowserLocation || 'client.browser', browser); + } + // Add DM wrapper details if (rubiConf.wrapperName) { payload.wrapper = { @@ -460,7 +484,7 @@ const findMatchingAdUnitFromAuctions = (matchesFunction, returnFirstMatch) => { } } return matches; -} +}; const getRenderingIds = bidWonData => { // if bid caching off -> return the bidWon auction id @@ -595,6 +619,28 @@ const subscribeToGamSlots = () => { }); } +/** + * Lazy parsing of UA to determine browser + * @param {string} userAgent string from prebid ortb ua or navigator + * @returns {string} lazily guessed browser name + */ +export const detectBrowserFromUa = userAgent => { + let normalizedUa = userAgent.toLowerCase(); + + if (normalizedUa.includes('edg')) { + return 'Edge'; + } else if ((/opr|opera|opt/i).test(normalizedUa)) { + return 'Opera'; + } else if ((/chrome|crios/i).test(normalizedUa)) { + return 'Chrome'; + } else if ((/fxios|firefox/i).test(normalizedUa)) { + return 'Firefox'; + } else if (normalizedUa.includes('safari') && !(/chromium|ucbrowser/i).test(normalizedUa)) { + return 'Safari'; + } + return 'OTHER'; +} + let accountId; let endpoint; @@ -656,6 +702,7 @@ magniteAdapter.onDataDeletionRequest = function () { magniteAdapter.MODULE_INITIALIZED_TIME = Date.now(); magniteAdapter.referrerHostname = ''; +let browser; magniteAdapter.track = ({ eventType, args }) => { switch (eventType) { case AUCTION_INIT: @@ -675,6 +722,12 @@ magniteAdapter.track = ({ eventType, args }) => { ]); auctionData.accountId = accountId; + // get browser + if (!browser) { + const userAgent = deepAccess(args, 'bidderRequests.0.ortb2.device.ua', navigator.userAgent) || ''; + browser = detectBrowserFromUa(userAgent); + } + // Order bidders were called auctionData.bidderOrder = args.bidderRequests.map(bidderRequest => bidderRequest.bidderCode); @@ -788,7 +841,16 @@ magniteAdapter.track = ({ eventType, args }) => { auctionEntry.floors.dealsEnforced = args.floorData.enforcements.floorDeals; } - // Log error if no matching bid! + // no-bid from server. report it! + if (!bid && args.seatBidId) { + bid = adUnit.bids[args.seatBidId] = { + bidder: args.bidderCode, + source: 'server', + bidId: args.seatBidId, + unknownBid: true + }; + } + if (!bid) { logError(`${MODULE_NAME}: Could not find associated bid request for bid response with requestId: `, args.requestId); break; @@ -819,6 +881,9 @@ magniteAdapter.track = ({ eventType, args }) => { bid.pbsBidId = pbsBidId; } break; + case SEAT_NON_BID: + handleNonBidEvent(args); + break; case BIDDER_DONE: const serverError = deepAccess(args, 'serverErrors.0'); const serverResponseTimeMs = args.serverResponseTimeMs; @@ -906,6 +971,66 @@ magniteAdapter.track = ({ eventType, args }) => { } }; +const handleNonBidEvent = function(args) { + const {seatnonbid, auctionId} = args; + const auction = deepAccess(cache, `auctions.${auctionId}.auction`); + // if no auction just bail + if (!auction) { + logWarn(`Unable to match nonbid to auction`); + return; + } + const adUnits = auction.adUnits; + seatnonbid.forEach(seatnonbid => { + let {seat} = seatnonbid; + seatnonbid.nonbid.forEach(nonbid => { + try { + const {status, impid} = nonbid; + const matchingTid = Object.keys(adUnits).find(tid => adUnits[tid].adUnitCode === impid); + const adUnit = adUnits[matchingTid]; + const statusInfo = statusMap[status] || { status: 'no-bid' }; + adUnit.bids[generateUUID()] = { + bidder: seat, + source: 'server', + isSeatNonBid: true, + clientLatencyMillis: Date.now() - auction.auctionStart, + ...statusInfo + }; + } catch (error) { + logWarn(`Unable to match nonbid to adUnit`); + } + }); + }); +}; + +const statusMap = { + 0: { + status: 'no-bid' + }, + 100: { + status: 'error', + error: { + code: 'request-error', + description: 'general error' + } + }, + 101: { + status: 'error', + error: { + code: 'timeout-error', + description: 'prebid server timeout' + } + }, + 200: { + status: 'rejected' + }, + 202: { + status: 'rejected' + }, + 301: { + status: 'rejected-ipf' + } +}; + adapterManager.registerAnalyticsAdapter({ adapter: magniteAdapter, code: 'magnite', diff --git a/modules/marsmediaAnalyticsAdapter.js b/modules/marsmediaAnalyticsAdapter.js index c86cc4dfbc2..f1e53a3c20c 100644 --- a/modules/marsmediaAnalyticsAdapter.js +++ b/modules/marsmediaAnalyticsAdapter.js @@ -1,6 +1,7 @@ import {ajax} from '../src/ajax.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; +import {getGlobal} from '../src/prebidGlobal.js'; /**** * Mars Media Analytics @@ -33,7 +34,7 @@ var marsmediaAnalyticsAdapter = Object.assign(adapter( success: function() {}, error: function() {} }, - JSON.stringify({act: 'prebid_analytics', params: events, 'pbjs': $$PREBID_GLOBAL$$.getBidResponses(), ver: MARS_VERSION}), + JSON.stringify({act: 'prebid_analytics', params: events, 'pbjs': getGlobal().getBidResponses(), ver: MARS_VERSION}), { method: 'POST' } diff --git a/modules/mathildeadsBidAdapter.js b/modules/mathildeadsBidAdapter.js index 6b886b72955..929cee8f3c0 100644 --- a/modules/mathildeadsBidAdapter.js +++ b/modules/mathildeadsBidAdapter.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/mediafuseBidAdapter.js b/modules/mediafuseBidAdapter.js index e61c2e65c39..87347ca8d27 100644 --- a/modules/mediafuseBidAdapter.js +++ b/modules/mediafuseBidAdapter.js @@ -62,7 +62,7 @@ const SCRIPT_TAG_START = '> // a random number from -// (a / 4)) -// ) // 8 to 11 -// .toString(16) // in hexadecimal -// : ( // or otherwise a concatenated string: -// [1e7] + // 10000000 + -// 1e3 + // -1000 + -// 4e3 + // -4000 + -// 8e3 + // -80000000 + -// 1e11 -// ) // -100000000000, -// .replace( -// // replacing -// /[018]/g, // zeroes, ones, and eights with -// getRandomId // random hex digits -// ); -// } - /* ----- mguid:start ------ */ const COOKIE_KEY_MGUID = '__mguid_'; @@ -148,12 +116,12 @@ function isMobileAndTablet() { */ // function getBidFloor(bid, mediaType, sizes) { // var floor; -// var size = sizes.length === 1 ? sizes[0] : "*"; -// if (typeof bid.getFloor === "function") { -// const floorInfo = bid.getFloor({ currency: "USD", mediaType, size }); +// var size = sizes.length === 1 ? sizes[0] : '*'; +// if (typeof bid.getFloor === 'function') { +// const floorInfo = bid.getFloor({ currency: 'USD', mediaType, size }); // if ( -// typeof floorInfo === "object" && -// floorInfo.currency === "USD" && +// typeof floorInfo === 'object' && +// floorInfo.currency === 'USD' && // !isNaN(parseFloat(floorInfo.floor)) // ) { // floor = parseFloat(floorInfo.floor); @@ -247,7 +215,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 }; } const bidFloor = getBidFloor(req); @@ -255,7 +225,6 @@ function getItems(validBidRequests, bidderRequest) { // utils.deepAccess(req, 'ortb2Imp.ext.gpid') || // utils.deepAccess(req, 'ortb2Imp.ext.data.pbadslot') || // utils.deepAccess(req, 'params.placementId', 0); - // console.log("wjh getItems:", req, bidFloor, gpid); // if (mediaTypes.native) {} // banner广告类型 @@ -268,9 +237,10 @@ function getItems(validBidRequests, bidderRequest) { h: matchSize.h, w: matchSize.w, pos: 1, + format: sizes, }, ext: { - // gpid: gpid, // 加入后无法返回广告 + // gpid: gpid, // 加入后无法返回广告 }, }; itemMaps[id] = { @@ -296,6 +266,8 @@ function getParam(validBidRequests, bidderRequest) { const sharedid = utils.deepAccess(validBidRequests[0], 'userId.sharedid.id') || utils.deepAccess(validBidRequests[0], 'userId.pubcid'); + const eids = validBidRequests[0].userIdAsEids || validBidRequests[0].userId; + let isMobile = isMobileAndTablet() ? 1 : 0; // input test status by Publisher. more frequently for test true req let isTest = validBidRequests[0].params.test || 0; @@ -309,6 +281,7 @@ function getParam(validBidRequests, bidderRequest) { const referer = utils.deepAccess(bidderRequest, 'refererInfo.ref'); const timeout = bidderRequest.timeout || 2000; + const firstPartyData = bidderRequest.ortb2; if (items && items.length) { let c = { @@ -318,19 +291,24 @@ function getParam(validBidRequests, bidderRequest) { cur: ['USD'], device: { connectiontype: 0, - // ip: '64.188.178.115', + // ip: '98.61.5.0', js: 1, - // language: "en", - // os: "Microsoft Windows", - // ua: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.19043", + // language: 'en', + // os: 'Microsoft Windows', + // ua: 'Mozilla/5.0 (Linux; Android 12; SM-G970U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Mobile Safari/537.36', os: navigator.platform || '', ua: navigator.userAgent, language: /en/.test(navigator.language) ? 'en' : navigator.language, }, - ext: {}, + ext: { + eids, + firstPartyData, + }, user: { - id: sharedid || pubcid || getUserID(), + buyeruid: getUserID(), + id: sharedid || pubcid, }, + eids, site: { name: domain, domain: domain, @@ -420,12 +398,12 @@ export const spec = { nurl: getProperty(bid, 'nurl'), // adserverTargeting: { // granularityMultiplier: 0.1, - // priceGranularity: "pbHg", - // pbMg: "0.01", + // priceGranularity: 'pbHg', + // pbMg: '0.01', // }, - // pbMg: "0.01", + // pbMg: '0.01', // granularityMultiplier: 0.1, - // priceGranularity: "pbHg", + // priceGranularity: 'pbHg', }; bidResponses.push(bidResponse); } diff --git a/modules/mediakeysBidAdapter.js b/modules/mediakeysBidAdapter.js index 60ced650329..de86bfa248f 100644 --- a/modules/mediakeysBidAdapter.js +++ b/modules/mediakeysBidAdapter.js @@ -20,7 +20,6 @@ import { import {registerBidder} from '../src/adapters/bidderFactory.js'; import {config} from '../src/config.js'; import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; -import {createEidsArray} from './userId/eids.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; const AUCTION_TYPE = 1; @@ -638,8 +637,8 @@ export const spec = { deepSetValue(payload, 'regs.coppa', 1); } - if (deepAccess(validBidRequests[0], 'userId')) { - deepSetValue(payload, 'user.ext.eids', createEidsArray(validBidRequests[0].userId)); + if (deepAccess(validBidRequests[0], 'userIdAsEids')) { + deepSetValue(payload, 'user.ext.eids', validBidRequests[0].userIdAsEids); } // Assign payload.site from refererinfo diff --git a/modules/medianetAnalyticsAdapter.js b/modules/medianetAnalyticsAdapter.js index d2d3d2bf888..b902727a730 100644 --- a/modules/medianetAnalyticsAdapter.js +++ b/modules/medianetAnalyticsAdapter.js @@ -8,8 +8,7 @@ import { logError, logInfo, triggerPixel, - uniques, - getHighestCpm + uniques } from '../src/utils.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; @@ -18,6 +17,7 @@ import {ajax} from '../src/ajax.js'; import {getRefererInfo} from '../src/refererDetection.js'; import {AUCTION_COMPLETED, AUCTION_IN_PROGRESS, getPriceGranularity} from '../src/auction.js'; import {includes} from '../src/polyfill.js'; +import {getGlobal} from '../src/prebidGlobal.js'; const analyticsType = 'endpoint'; const ENDPOINT = 'https://pb-logs.media.net/log?logid=kfk&evtid=prebid_analytics_events_client'; @@ -37,10 +37,11 @@ const PRICE_GRANULARITY = { const MEDIANET_BIDDER_CODE = 'medianet'; // eslint-disable-next-line no-undef -const PREBID_VERSION = $$PREBID_GLOBAL$$.version; +const PREBID_VERSION = getGlobal().version; const ERROR_CONFIG_JSON_PARSE = 'analytics_config_parse_fail'; const ERROR_CONFIG_FETCH = 'analytics_config_ajax_fail'; const ERROR_WINNING_BID_ABSENT = 'winning_bid_absent'; +const ERROR_WINNING_AUCTION_MISSING = 'winning_auction_missing'; const BID_SUCCESS = 1; const BID_NOBID = 2; const BID_TIMEOUT = 3; @@ -72,7 +73,7 @@ class ErrorLogger { this.evtid = 'projectevents'; this.project = 'prebidanalytics'; this.dn = pageDetails.domain || ''; - this.requrl = pageDetails.requrl || ''; + this.requrl = pageDetails.topmostLocation || ''; this.pbversion = PREBID_VERSION; this.cid = config.cid || ''; this.rd = additionalData; @@ -270,6 +271,52 @@ class AdSlot { } } +class BidWrapper { + constructor() { + this.bidReqs = []; + this.bidObjs = []; + } + + findReqBid(bidId) { + return this.bidReqs.find(bid => { + return bid['bidId'] === bidId + }); + } + + findBidObj(key, value) { + return this.bidObjs.find(bid => { + return bid[key] === value + }); + } + + addBidReq(bidRequest) { + this.bidReqs.push(bidRequest) + } + + addBidObj(bidObj) { + if (!(bidObj instanceof Bid)) { + bidObj = Bid.getInstance(bidObj); + } + const bidReq = this.findReqBid(bidObj.bidId); + if (bidReq instanceof Bid) { + bidReq.used = true; + } + this.bidObjs.push(bidObj); + } + + getAdSlotBids(adSlot) { + const bidResponses = this.getAdSlotBidObjs(adSlot); + return bidResponses.map((bid) => bid.getLoggingData()); + } + + getAdSlotBidObjs(adSlot) { + const bidResponses = this.bidObjs + .filter((bid) => bid.adUnitCode === adSlot); + const remResponses = this.bidReqs.filter(bid => !bid.used && bid.adUnitCode === adSlot); + return [...bidResponses, ...remResponses]; + } +} + class Bid { constructor(bidId, bidder, src, start, adUnitCode, mediaType, allMediaTypeSizes) { this.bidId = bidId; @@ -299,6 +346,9 @@ class Bid { this.floorPrice = undefined; this.floorRule = undefined; this.serverLatencyMillis = undefined; + this.used = false; + this.originalRequestId = bidId; + this.requestId = undefined; } get size() { @@ -308,8 +358,15 @@ class Bid { return this.width + 'x' + this.height; } + static getInstance(bidProps) { + const bidObj = new Bid(); + return bidProps && Object.assign(bidObj, bidProps); + } + getLoggingData() { return { + reqId: this.requestId || this.bidId, + ogReqId: this.originalRequestId, adid: this.adId, pvnm: this.bidder, src: this.src, @@ -341,7 +398,7 @@ class Auction { constructor(acid) { this.acid = acid; this.status = AUCTION_IN_PROGRESS; - this.bids = []; + this.bidWrapper = new BidWrapper(); this.adSlots = {}; this.auctionInitTime = undefined; this.auctionStartTime = undefined; @@ -378,24 +435,31 @@ class Auction { addSlot({ adUnitCode, supplyAdCode, mediaTypes, allMediaTypeSizes, tmax, adext, context }) { if (adUnitCode && this.adSlots[adUnitCode] === undefined) { this.adSlots[adUnitCode] = new AdSlot(tmax, supplyAdCode, context, adext); - this.addBid(new Bid('-1', DUMMY_BIDDER, 'client', '-1', adUnitCode, mediaTypes, allMediaTypeSizes)); + this.addBidObj(new Bid('-1', DUMMY_BIDDER, 'client', Date.now(), adUnitCode, mediaTypes, allMediaTypeSizes)); } } addBid(bid) { - this.bids.push(bid); + this.bidWrapper.addBidReq(bid); } - findBid(key, value) { - return this.bids.filter(bid => { - return bid[key] === value - })[0]; + addBidObj(bidObj) { + this.bidWrapper.addBidObj(bidObj) } - getAdslotBids(adslot) { - return this.bids - .filter((bid) => bid.adUnitCode === adslot) - .map((bid) => bid.getLoggingData()); + findReqBid(bidId) { + return this.bidWrapper.findReqBid(bidId) + } + + findBidObj(key, value) { + return this.bidWrapper.findBidObj(key, value) + } + + getAdSlotBids(adSlot) { + return this.bidWrapper.getAdSlotBids(adSlot); + } + getAdSlotBidObjs(adSlot) { + return this.bidWrapper.getAdSlotBidObjs(adSlot); } _mergeFieldsToLog(objParams) { @@ -494,41 +558,26 @@ function _getSizes(mediaTypes, sizes) { } } -/* - - The code is used to determine if the current bid is higher than the previous bid. - - If it is, then the code will return true and if not, it will return false. - */ -function canSelectCurrentBid(previousBid, currentBid) { - if (!(previousBid instanceof Bid)) return false; - - // For first bid response the previous bid will be containing bid request obj - // in which the cpm would be undefined so the current bid can directly be selected. - const isFirstBidResponse = previousBid.cpm === undefined && currentBid.cpm !== undefined; - if (isFirstBidResponse) return true; - - // if there are 2 bids, get the highest bid - const selectedBid = getHighestCpm(previousBid, currentBid); - - // Return true if selectedBid is currentBid, - // The timeToRespond field is used as an identifier for distinguishing - // between the current iterating bid and the previous bid. - return selectedBid.timeToRespond === currentBid.timeToRespond; -} - function bidResponseHandler(bid) { - const { width, height, mediaType, cpm, requestId, timeToRespond, auctionId, dealId } = bid; - const {originalCpm, bidderCode, creativeId, adId, currency} = bid; + const { width, height, mediaType, cpm, requestId, timeToRespond, auctionId, dealId, originalRequestId, bidder } = bid; + const {originalCpm, creativeId, adId, currency} = bid; if (!(auctions[auctionId] instanceof Auction)) { return; } - let bidObj = auctions[auctionId].findBid('bidId', requestId); - if (!canSelectCurrentBid(bidObj, bid)) { - return; + const reqId = originalRequestId || requestId; + const bidReq = auctions[auctionId].findReqBid(reqId); + + if (!(bidReq instanceof Bid)) return; + + let bidObj = auctions[auctionId].findBidObj('bidId', requestId); + let isBidOverridden = true; + if (!bidObj || bidObj.status === BID_SUCCESS) { + bidObj = {}; + isBidOverridden = false; } - Object.assign( - bidObj, - { cpm, width, height, mediaType, timeToRespond, dealId, creativeId }, + Object.assign(bidObj, bidReq, + { cpm, width, height, mediaType, timeToRespond, dealId, creativeId, originalRequestId, requestId }, { adId, currency } ); bidObj.floorPrice = deepAccess(bid, 'floorData.floorValue'); @@ -547,7 +596,7 @@ function bidResponseHandler(bid) { bidObj.status = BID_SUCCESS; } - if (bidderCode === MEDIANET_BIDDER_CODE && bid.ext instanceof Object) { + if (bidder === MEDIANET_BIDDER_CODE && bid.ext instanceof Object) { Object.assign( bidObj, { 'ext': bid.ext }, @@ -558,6 +607,7 @@ function bidResponseHandler(bid) { if (typeof bid.serverResponseTimeMs !== 'undefined') { bidObj.serverLatencyMillis = bid.serverResponseTimeMs; } + !isBidOverridden && auctions[auctionId].addBidObj(bidObj); } function noBidResponseHandler({ auctionId, bidId }) { @@ -567,11 +617,13 @@ function noBidResponseHandler({ auctionId, bidId }) { if (auctions[auctionId].hasEnded()) { return; } - let bidObj = auctions[auctionId].findBid('bidId', bidId); - if (!(bidObj instanceof Bid)) { + const bidReq = auctions[auctionId].findReqBid(bidId); + if (!(bidReq instanceof Bid) || bidReq.used) { return; } + const bidObj = {...bidReq}; bidObj.status = BID_NOBID; + auctions[auctionId].addBidObj(bidObj); } function bidTimeoutHandler(timedOutBids) { @@ -579,11 +631,13 @@ function bidTimeoutHandler(timedOutBids) { if (!(auctions[auctionId] instanceof Auction)) { return; } - let bidObj = auctions[auctionId].findBid('bidId', bidId); - if (!(bidObj instanceof Bid)) { + const bidReq = auctions[auctionId].findReqBid('bidId', bidId); + if (!(bidReq instanceof Bid) || bidReq.used) { return; } + const bidObj = {...bidReq}; bidObj.status = BID_TIMEOUT; + auctions[auctionId].addBidObj(bidObj); }) } @@ -614,13 +668,13 @@ function setTargetingHandler(params) { const winnerAdId = params[adunit][CONSTANTS.TARGETING_KEYS.AD_ID]; let winningBid; let bidAdIds = Object.keys(targetingObj).map(k => targetingObj[k]); - auctionObj.bids.filter((bid) => bidAdIds.indexOf(bid.adId) !== -1).map(function(bid) { + auctionObj.bidWrapper.bidObjs.filter((bid) => bidAdIds.indexOf(bid.adId) !== -1).map(function(bid) { bid.iwb = 1; if (bid.adId === winnerAdId) { winningBid = bid; } }); - auctionObj.bids.forEach(bid => { + auctionObj.bidWrapper.bidObjs.forEach(bid => { if (bid.bidder === DUMMY_BIDDER && bid.adUnitCode === adunit) { bid.iwb = bidAdIds.length === 0 ? 0 : 1; bid.width = deepAccess(winningBid, 'width'); @@ -633,16 +687,27 @@ function setTargetingHandler(params) { } function bidWonHandler(bid) { - const { auctionId, adUnitCode, adId } = bid; + const { auctionId, adUnitCode, adId, bidder, requestId, originalRequestId } = bid; if (!(auctions[auctionId] instanceof Auction)) { + new ErrorLogger(ERROR_WINNING_AUCTION_MISSING, { + adId, + auctionId, + adUnitCode, + bidder, + requestId, + originalRequestId + }).send(); return; } - let bidObj = auctions[auctionId].findBid('adId', adId); + let bidObj = auctions[auctionId].findBidObj('adId', adId); if (!(bidObj instanceof Bid)) { new ErrorLogger(ERROR_WINNING_BID_ABSENT, { - adId: adId, - acid: auctionId, + adId, + auctionId, adUnitCode, + bidder, + requestId, + originalRequestId }).send(); return; } @@ -696,13 +761,13 @@ function fireAuctionLog(acid, adtag, logType, adId) { let bidParams; if (logType === LOG_TYPE.RA) { - const winningBidObj = auctions[acid].findBid('adId', adId); + const winningBidObj = auctions[acid].findBidObj('adId', adId); if (!winningBidObj) return; const winLogData = winningBidObj.getLoggingData(); bidParams = [winLogData]; commonParams.lper = 1; } else { - bidParams = auctions[acid].getAdslotBids(adtag).map(({winner, ...restParams}) => restParams); + bidParams = auctions[acid].getAdSlotBids(adtag).map(({winner, ...restParams}) => restParams); delete commonParams.wts; } let mnetPresent = bidParams.filter(b => b.pvnm === MEDIANET_BIDDER_CODE).length > 0; @@ -822,8 +887,8 @@ medianetAnalytics.enableAnalytics = function (configuration) { logError('Media.net Analytics adapter: cid is required.'); return; } - $$PREBID_GLOBAL$$.medianetGlobals = $$PREBID_GLOBAL$$.medianetGlobals || {}; - $$PREBID_GLOBAL$$.medianetGlobals.analyticsEnabled = true; + getGlobal().medianetGlobals = getGlobal().medianetGlobals || {}; + getGlobal().medianetGlobals.analyticsEnabled = true; pageDetails = new PageDetail(); diff --git a/modules/medianetBidAdapter.js b/modules/medianetBidAdapter.js index 01652a3fac0..c398d8fd5db 100644 --- a/modules/medianetBidAdapter.js +++ b/modules/medianetBidAdapter.js @@ -17,6 +17,7 @@ import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; import {getRefererInfo} from '../src/refererDetection.js'; import {Renderer} from '../src/Renderer.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import {getGlobal} from '../src/prebidGlobal.js'; const BIDDER_CODE = 'medianet'; const BID_URL = 'https://prebid.media.net/rtb/prebid'; @@ -51,7 +52,7 @@ const aliases = [ { code: 'aax', gvlid: 720 }, ]; -$$PREBID_GLOBAL$$.medianetGlobals = $$PREBID_GLOBAL$$.medianetGlobals || {}; +getGlobal().medianetGlobals = getGlobal().medianetGlobals || {}; function getTopWindowReferrer() { try { @@ -177,7 +178,7 @@ function extParams(bidRequest, bidderRequests) { const coppaApplies = !!(config.getConfig('coppa')); return Object.assign({}, { customer_id: params.cid }, - { prebid_version: $$PREBID_GLOBAL$$.version }, + { prebid_version: getGlobal().version }, { gdpr_applies: gdprApplies }, (gdprApplies) && { gdpr_consent_string: gdpr.consentString || '' }, { usp_applies: uspApplies }, @@ -185,7 +186,7 @@ function extParams(bidRequest, bidderRequests) { {coppa_applies: coppaApplies}, windowSize.w !== -1 && windowSize.h !== -1 && { screen: windowSize }, userId && { user_id: userId }, - $$PREBID_GLOBAL$$.medianetGlobals.analyticsEnabled && { analytics: true }, + getGlobal().medianetGlobals.analyticsEnabled && { analytics: true }, !isEmpty(sChain) && {schain: sChain} ); } @@ -333,7 +334,7 @@ function generatePayload(bidRequests, bidderRequests) { id: bidRequests[0].auctionId, imp: bidRequests.map(request => slotParams(request)), ortb2: bidderRequests.ortb2, - tmax: bidderRequests.timeout || config.getConfig('bidderTimeout') + tmax: bidderRequests.timeout } } @@ -358,7 +359,7 @@ function getLoggingData(event, data) { params.evtid = 'projectevents'; params.project = 'prebid'; params.acid = deepAccess(data, '0.auctionId') || ''; - params.cid = $$PREBID_GLOBAL$$.medianetGlobals.cid || ''; + params.cid = getGlobal().medianetGlobals.cid || ''; params.crid = data.map((adunit) => deepAccess(adunit, 'params.0.crid') || adunit.adUnitCode).join('|'); params.adunit_count = data.length || 0; params.dn = mnData.urlData.domain || ''; @@ -442,7 +443,7 @@ export const spec = { return false; } - Object.assign($$PREBID_GLOBAL$$.medianetGlobals, !$$PREBID_GLOBAL$$.medianetGlobals.cid && {cid: bid.params.cid}); + Object.assign(getGlobal().medianetGlobals, !getGlobal().medianetGlobals.cid && {cid: bid.params.cid}); return true; }, diff --git a/modules/mediasquareBidAdapter.js b/modules/mediasquareBidAdapter.js index 819ff280e35..4076cd6927a 100644 --- a/modules/mediasquareBidAdapter.js +++ b/modules/mediasquareBidAdapter.js @@ -4,6 +4,7 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; import {Renderer} from '../src/Renderer.js'; +import { getRefererInfo } from '../src/refererDetection.js'; const BIDDER_CODE = 'mediasquare'; const BIDDER_URL_PROD = 'https://pbs-front.mediasquare.fr/' @@ -170,7 +171,7 @@ export const spec = { */ onBidWon: function(bid) { // fires a pixel to confirm a winning bid - let params = {'pbjs': '$prebid.version$'}; + let params = { pbjs: '$prebid.version$', referer: encodeURIComponent(getRefererInfo().page || getRefererInfo().topmostLocation) }; let endpoint = document.location.search.match(/msq_test=true/) ? BIDDER_URL_TEST : BIDDER_URL_PROD; let paramsToSearchFor = ['cpm', 'size', 'mediaType', 'currency', 'creativeId', 'adUnitCode', 'timeToRespond', 'requestId', 'auctionId', 'originalCpm', 'originalCurrency']; if (bid.hasOwnProperty('mediasquare')) { diff --git a/modules/merkleIdSystem.js b/modules/merkleIdSystem.js index 6a10c2a94eb..c522d588970 100644 --- a/modules/merkleIdSystem.js +++ b/modules/merkleIdSystem.js @@ -9,13 +9,14 @@ import { logInfo, logError, logWarn } from '../src/utils.js'; import * as ajaxLib 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'; const MODULE_NAME = 'merkleId'; const ID_URL = 'https://prebid.sv.rkdms.com/identity/'; const DEFAULT_REFRESH = 7 * 3600; const SESSION_COOKIE_NAME = '_svsid'; -export const storage = getStorageManager(); +export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); function getSession(configParams) { let session = null; diff --git a/modules/mgidBidAdapter.js b/modules/mgidBidAdapter.js index 036effecd88..0079936d803 100644 --- a/modules/mgidBidAdapter.js +++ b/modules/mgidBidAdapter.js @@ -1,14 +1,31 @@ -import { _each, deepAccess, isPlainObject, isArray, isStr, logInfo, parseUrl, isEmpty, triggerPixel, logWarn, getBidIdParameter, isFn, isNumber } from '../src/utils.js'; +import { + _each, + deepAccess, + isPlainObject, + isArray, + isStr, + logInfo, + parseUrl, + isEmpty, + triggerPixel, + logWarn, + getBidIdParameter, + isFn, + isNumber, + isBoolean, + isInteger, deepSetValue, +} 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 { getStorageManager } from '../src/storageManager.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import {USERSYNC_DEFAULT_CONFIG} from '../src/userSync.js'; const GVLID = 358; const DEFAULT_CUR = 'USD'; const BIDDER_CODE = 'mgid'; -export const storage = getStorageManager({gvlid: GVLID, bidderCode: BIDDER_CODE}); +export const storage = getStorageManager({bidderCode: BIDDER_CODE}); const ENDPOINT_URL = 'https://prebid.mgid.com/prebid/'; const LOG_WARN_PREFIX = '[MGID warn]: '; const LOG_INFO_PREFIX = '[MGID info]: '; @@ -64,7 +81,7 @@ _each(NATIVE_ASSETS, anAsset => { _NATIVE_ASSET_ID_TO_KEY_MAP[anAsset.ID] = anAs _each(NATIVE_ASSETS, anAsset => { _NATIVE_ASSET_KEY_TO_ASSET_MAP[anAsset.KEY] = anAsset }); export const spec = { - VERSION: '1.5', + VERSION: '1.6', code: BIDDER_CODE, gvlid: GVLID, supportedMediaTypes: [BANNER, NATIVE], @@ -115,22 +132,19 @@ export const spec = { /** * Make a server request from the list of BidRequests. * - * @param {validBidRequests[]} - an array of bids + * @param {BidRequest[]} validBidRequests A non-empty list of bid requests which should be sent to the Server. + * @param bidderRequest * @return ServerRequest Info describing the request to the server. */ buildRequests: (validBidRequests, bidderRequest) => { // convert Native ORTB definition to old-style prebid native definition validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - + const [bidRequest] = validBidRequests; logInfo(LOG_INFO_PREFIX + `buildRequests`); if (validBidRequests.length === 0) { return; } const info = pageInfo(); - // TODO: the fallback seems to never be used here, and probably in the wrong order - const page = info.location || deepAccess(bidderRequest, 'refererInfo.page') - const hostname = parseUrl(page).hostname; - let domain = extractDomainFromHost(hostname) || hostname; const accountId = setOnAny(validBidRequests, 'params.accountId'); const muid = getLocalStorageSafely('mgMuidn'); let url = (setOnAny(validBidRequests, 'params.bidUrl') || ENDPOINT_URL) + accountId; @@ -182,31 +196,108 @@ export const spec = { let request = { id: deepAccess(bidderRequest, 'bidderRequestId'), - site: {domain, page}, + site: ortb2Data?.site || {}, cur: [cur], geo: {utcoffset: info.timeOffset}, - device: { - ua: navigator.userAgent, - js: 1, - dnt: (navigator.doNotTrack === 'yes' || navigator.doNotTrack === '1' || navigator.msDoNotTrack === '1') ? 1 : 0, - h: screen.height, - w: screen.width, - language: getLanguage() - }, + device: ortb2Data?.device || {}, ext: { mgid_ver: spec.VERSION, prebid_ver: '$prebid.version$', - ...ortb2Data }, - imp + imp, + tmax: bidderRequest?.timeout || config.getConfig('bidderTimeout') || 500, }; - if (bidderRequest && bidderRequest.gdprConsent) { - request.user = {ext: {consent: bidderRequest.gdprConsent.consentString}}; - request.regs = {ext: {gdpr: (bidderRequest.gdprConsent.gdprApplies ? 1 : 0)}} + // request level + const bcat = ortb2Data?.bcat || bidRequest?.params?.bcat || []; + const badv = ortb2Data?.badv || bidRequest?.params?.badv || []; + const wlang = ortb2Data?.wlang || bidRequest?.params?.wlang || []; + if (bcat.length > 0) { + request.bcat = bcat; + } + if (badv.length > 0) { + request.badv = badv; + } + if (wlang.length > 0) { + request.wlang = wlang; + } + // site level + const page = deepAccess(bidderRequest, 'refererInfo.page') || info.location + if (!isStr(deepAccess(request.site, 'domain'))) { + const hostname = parseUrl(page).hostname; + request.site.domain = extractDomainFromHost(hostname) || hostname + } + if (!isStr(deepAccess(request.site, 'page'))) { + request.site.page = page + } + if (!isStr(deepAccess(request.site, 'ref'))) { + const ref = deepAccess(bidderRequest, 'refererInfo.ref') || info.referrer; + if (ref) { + request.site.ref = ref + } + } + // device level + if (!isStr(deepAccess(request.device, 'ua'))) { + request.device.ua = navigator.userAgent; + } + request.device.js = 1; + if (!isInteger(deepAccess(request.device, 'dnt'))) { + request.device.dnt = (navigator?.doNotTrack === 'yes' || navigator?.doNotTrack === '1' || navigator?.msDoNotTrack === '1') ? 1 : 0; + } + if (!isInteger(deepAccess(request.device, 'h'))) { + request.device.h = screen.height; } - if (info.referrer) { - request.site.ref = info.referrer + if (!isInteger(deepAccess(request.device, 'w'))) { + request.device.w = screen.width; + } + if (!isStr(deepAccess(request.device, 'language'))) { + request.device.language = getLanguage(); + } + // user & regs & privacy + if (isPlainObject(ortb2Data?.user)) { + request.user = ortb2Data.user; + } + if (isPlainObject(ortb2Data?.regs)) { + request.regs = ortb2Data.regs; + } + if (bidderRequest && isPlainObject(bidderRequest.gdprConsent)) { + if (!isStr(deepAccess(request.user, 'ext.consent'))) { + deepSetValue(request, 'user.ext.consent', bidderRequest.gdprConsent?.consentString); + } + if (!isBoolean(deepAccess(request.regs, 'ext.gdpr'))) { + deepSetValue(request, 'regs.ext.gdpr', bidderRequest.gdprConsent?.gdprApplies ? 1 : 0); + } + } + const userId = deepAccess(bidderRequest, 'userId') + if (isStr(userId)) { + deepSetValue(request, 'user.id', userId); + } + const eids = setOnAny(validBidRequests, 'userIdAsEids') + if (eids && eids.length > 0) { + deepSetValue(request, 'user.ext.eids', eids); + } + if (bidderRequest && isStr(bidderRequest.uspConsent)) { + if (!isBoolean(deepAccess(request.regs, 'ext.us_privacy'))) { + deepSetValue(request, 'regs.ext.us_privacy', bidderRequest.uspConsent); + } } + if (bidderRequest && isPlainObject(bidderRequest.gppConsent)) { + if (!isStr(deepAccess(request.regs, 'gpp'))) { + deepSetValue(request, 'regs.gpp', bidderRequest.gppConsent?.gppString); + } + if (!isArray(deepAccess(request.regs, 'gpp_sid'))) { + deepSetValue(request, 'regs.gpp_sid', bidderRequest.gppConsent?.applicableSections); + } + } + if (config.getConfig('coppa')) { + if (!isInteger(deepAccess(request.regs, 'coppa'))) { + deepSetValue(request, 'regs.coppa', 1); + } + } + const schain = setOnAny(validBidRequests, 'schain'); + if (schain) { + deepSetValue(request, 'source.ext.schain', schain); + } + logInfo(LOG_INFO_PREFIX + `buildRequest:`, request); return { method: 'POST', @@ -218,6 +309,7 @@ export const spec = { * Unpack the response from the server into a list of bids. * * @param {ServerResponse} serverResponse A successful response from the server. + * @param bidRequests * @return {Bid[]} An array of bids which were nested inside the server. */ interpretResponse: (serverResponse, bidRequests) => { @@ -268,8 +360,66 @@ export const spec = { } logInfo(LOG_INFO_PREFIX + `onBidWon`); }, - getUserSyncs: (syncOptions, serverResponses) => { + getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent, gppConsent) => { logInfo(LOG_INFO_PREFIX + `getUserSyncs`); + const spb = isPlainObject(config.getConfig('userSync')) && + isNumber(config.getConfig('userSync').syncsPerBidder) + ? config.getConfig('userSync').syncsPerBidder : USERSYNC_DEFAULT_CONFIG.syncsPerBidder; + + if (spb > 0 && isPlainObject(syncOptions) && (syncOptions.iframeEnabled || syncOptions.pixelEnabled)) { + let pixels = []; + if (serverResponses && + isArray(serverResponses) && + serverResponses.length > 0 && + isPlainObject(serverResponses[0].body) && + isPlainObject(serverResponses[0].body.ext) && + isArray(serverResponses[0].body.ext.cm) && + serverResponses[0].body.ext.cm.length > 0) { + pixels = serverResponses[0].body.ext.cm; + } + + const syncs = []; + const query = []; + query.push('cbuster=' + Math.round(new Date().getTime())); + query.push('consentData=' + encodeURIComponent(isPlainObject(gdprConsent) && isStr(gdprConsent?.consentString) ? gdprConsent.consentString : '')); + if (isPlainObject(gdprConsent) && typeof gdprConsent?.gdprApplies === 'boolean' && gdprConsent.gdprApplies) { + query.push('gdprApplies=1'); + } else { + query.push('gdprApplies=0'); + } + if (isPlainObject(uspConsent) && uspConsent?.consentString) { + query.push(`uspString=${encodeURIComponent(uspConsent?.consentString)}`); + } + if (isPlainObject(gppConsent) && gppConsent?.gppString) { + query.push(`gppString=${encodeURIComponent(gppConsent?.gppString)}`); + } + if (config.getConfig('coppa')) { + query.push('coppa=1') + } + if (syncOptions.iframeEnabled) { + syncs.push({ + type: 'iframe', + url: 'https://cm.mgid.com/i.html?' + query.join('&') + }); + } else if (syncOptions.pixelEnabled) { + if (pixels.length === 0) { + for (let i = 0; i < spb; i++) { + syncs.push({ + type: 'image', + url: 'https://cm.mgid.com/i.gif?' + query.join('&') // randomly selects partner if sync required + }); + } + } else { + for (let i = 0; i < spb && i < pixels.length; i++) { + syncs.push({ + type: 'image', + url: pixels[i] + (pixels[i].indexOf('?') > 0 ? '&' : '?') + query.join('&') + }); + } + } + } + return syncs; + } } }; @@ -287,6 +437,7 @@ function setOnAny(collection, key) { /** * Unpack the Server's Bid into a Prebid-compatible one. * @param serverBid + * @param cur * @return Bid */ function prebidBid(serverBid, cur) { @@ -330,7 +481,7 @@ function setMediaType(bid, newBid) { } function extractDomainFromHost(pageHost) { - if (pageHost == 'localhost') { + if (pageHost === 'localhost') { return 'localhost' } let domain = null; @@ -600,6 +751,7 @@ function pageInfo() { * Get the floor price from bid.params for backward compatibility. * If not found, then check floor module. * @param bid A valid bid object + * @param cur * @returns {*|number} floor price */ function getBidFloor(bid, cur) { diff --git a/modules/mgidRtdProvider.js b/modules/mgidRtdProvider.js index f30f14ea528..fd2c0bbe6fd 100644 --- a/modules/mgidRtdProvider.js +++ b/modules/mgidRtdProvider.js @@ -3,6 +3,7 @@ import {ajax} from '../src/ajax.js'; import {deepAccess, logError, logInfo, mergeDeep} from '../src/utils.js'; import {getStorageManager} from '../src/storageManager.js'; import {getRefererInfo} from '../src/refererDetection.js'; +import {MODULE_TYPE_RTD} from '../src/activities/modules.js'; const MODULE_NAME = 'realTimeData'; const SUBMODULE_NAME = 'mgid'; @@ -13,7 +14,7 @@ const ORTB2_NAME = 'www.mgid.com' const GVLID = 358; /** @type {?Object} */ export const storage = getStorageManager({ - gvlid: GVLID, + moduleType: MODULE_TYPE_RTD, moduleName: SUBMODULE_NAME }); @@ -185,6 +186,7 @@ export const mgidSubmodule = { name: SUBMODULE_NAME, init: init, getBidRequestData: getBidRequestData, + gvlid: GVLID }; submodule(MODULE_NAME, mgidSubmodule); diff --git a/modules/minutemediaBidAdapter.js b/modules/minutemediaBidAdapter.js index d953558bf31..a80a37f5ead 100644 --- a/modules/minutemediaBidAdapter.js +++ b/modules/minutemediaBidAdapter.js @@ -366,7 +366,7 @@ function generateGeneralParams(generalObject, bidderRequest) { const {syncEnabled, filterSettings} = config.getConfig('userSync') || {}; const {bidderCode} = bidderRequest; const generalBidParams = generalObject.params; - const timeout = config.getConfig('bidderTimeout'); + const timeout = bidderRequest.timeout; // these params are snake_case instead of camelCase to allow backwards compatability on the server. // in the future, these will be converted to camelCase to match our convention. diff --git a/modules/minutemediaplusBidAdapter.js b/modules/minutemediaplusBidAdapter.js new file mode 100644 index 00000000000..bd7874c405b --- /dev/null +++ b/modules/minutemediaplusBidAdapter.js @@ -0,0 +1,343 @@ +import {_each, deepAccess, parseSizesInput, parseUrl, uniques, isFn} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {config} from '../src/config.js'; + +const GVLID = 918; +const DEFAULT_SUB_DOMAIN = 'exchange'; +const BIDDER_CODE = 'mmplus'; +const BIDDER_VERSION = '1.0.0'; +const CURRENCY = 'USD'; +const TTL_SECONDS = 60 * 5; +const UNIQUE_DEAL_ID_EXPIRY = 1000 * 60 * 15; +export const SUPPORTED_ID_SYSTEMS = { + 'britepoolid': 1, + 'criteoId': 1, + 'id5id': 1, + 'idl_env': 1, + 'lipb': 1, + 'netId': 1, + 'parrableId': 1, + 'pubcid': 1, + 'tdid': 1, + 'pubProvidedId': 1 +}; +const storage = getStorageManager({bidderCode: BIDDER_CODE}); + +function getTopWindowQueryParams() { + try { + const parsedUrl = parseUrl(window.top.document.URL, {decodeSearchAsString: true}); + return parsedUrl.search; + } catch (e) { + return ''; + } +} + +export function createDomain(subDomain = DEFAULT_SUB_DOMAIN) { + return `https://${subDomain}.minutemedia-prebid.com`; +} + +export function extractCID(params) { + return params.cId || params.CID || params.cID || params.CId || params.cid || params.ciD || params.Cid || params.CiD; +} + +export function extractPID(params) { + return params.pId || params.PID || params.pID || params.PId || params.pid || params.piD || params.Pid || params.PiD; +} + +export function extractSubDomain(params) { + return params.subDomain || params.SubDomain || params.Subdomain || params.subdomain || params.SUBDOMAIN || params.subDOMAIN; +} + +function isBidRequestValid(bid) { + const params = bid.params || {}; + return !!(extractCID(params) && extractPID(params)); +} + +function buildRequest(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout) { + const { + params, + bidId, + userId, + adUnitCode, + schain, + mediaTypes, + auctionId, + transactionId, + bidderRequestId, + bidRequestsCount, + bidderRequestsCount, + bidderWinsCount + } = bid; + let {bidFloor, ext} = params; + const hashUrl = hashCode(topWindowUrl); + const uniqueDealId = getUniqueDealId(hashUrl); + const cId = extractCID(params); + const pId = extractPID(params); + const subDomain = extractSubDomain(params); + + const gpid = deepAccess(bid, 'ortb2Imp.ext.gpid', deepAccess(bid, 'ortb2Imp.ext.data.pbadslot', '')); + + if (isFn(bid.getFloor)) { + const floorInfo = bid.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*' + }); + + if (floorInfo.currency === 'USD') { + bidFloor = floorInfo.floor; + } + } + + let data = { + url: encodeURIComponent(topWindowUrl), + uqs: getTopWindowQueryParams(), + cb: Date.now(), + bidFloor: bidFloor, + bidId: bidId, + referrer: bidderRequest.refererInfo.ref, + adUnitCode: adUnitCode, + publisherId: pId, + sizes: sizes, + uniqueDealId: uniqueDealId, + bidderVersion: BIDDER_VERSION, + prebidVersion: '$prebid.version$', + res: `${screen.width}x${screen.height}`, + schain: schain, + mediaTypes: mediaTypes, + gpid: gpid, + auctionId: auctionId, + transactionId: transactionId, + bidderRequestId: bidderRequestId, + bidRequestsCount: bidRequestsCount, + bidderRequestsCount: bidderRequestsCount, + bidderWinsCount: bidderWinsCount, + bidderTimeout: bidderTimeout + }; + + appendUserIdsToRequestPayload(data, userId); + + const sua = deepAccess(bidderRequest, 'ortb2.device.sua'); + + if (sua) { + data.sua = sua; + } + + if (bidderRequest.gdprConsent) { + if (bidderRequest.gdprConsent.consentString) { + data.gdprConsent = bidderRequest.gdprConsent.consentString; + } + if (bidderRequest.gdprConsent.gdprApplies !== undefined) { + data.gdpr = bidderRequest.gdprConsent.gdprApplies ? 1 : 0; + } + } + if (bidderRequest.uspConsent) { + data.usPrivacy = bidderRequest.uspConsent; + } + + if (bidderRequest.gppConsent) { + data.gppString = bidderRequest.gppConsent.gppString; + data.gppSid = bidderRequest.gppConsent.applicableSections; + } else if (bidderRequest.ortb2?.regs?.gpp) { + data.gppString = bidderRequest.ortb2.regs.gpp; + data.gppSid = bidderRequest.ortb2.regs.gpp_sid; + } + + const dto = { + method: 'POST', + url: `${createDomain(subDomain)}/prebid/multi/${cId}`, + data: data + }; + + _each(ext, (value, key) => { + dto.data['ext.' + key] = value; + }); + + return dto; +} + +function appendUserIdsToRequestPayload(payloadRef, userIds) { + let key; + _each(userIds, (userId, idSystemProviderName) => { + if (SUPPORTED_ID_SYSTEMS[idSystemProviderName]) { + key = `uid.${idSystemProviderName}`; + + switch (idSystemProviderName) { + case 'digitrustid': + payloadRef[key] = deepAccess(userId, 'data.id'); + break; + case 'lipb': + payloadRef[key] = userId.lipbid; + break; + case 'parrableId': + payloadRef[key] = userId.eid; + break; + case 'id5id': + payloadRef[key] = userId.uid; + break; + default: + payloadRef[key] = userId; + } + } + }); +} + +function buildRequests(validBidRequests, bidderRequest) { + const topWindowUrl = bidderRequest.refererInfo.page || bidderRequest.refererInfo.topmostLocation; + const bidderTimeout = config.getConfig('bidderTimeout'); + const requests = []; + validBidRequests.forEach(validBidRequest => { + const sizes = parseSizesInput(validBidRequest.sizes); + const request = buildRequest(validBidRequest, topWindowUrl, sizes, bidderRequest, bidderTimeout); + requests.push(request); + }); + return requests; +} + +function interpretResponse(serverResponse, request) { + if (!serverResponse || !serverResponse.body) { + return []; + } + const {bidId} = request.data; + const {results} = serverResponse.body; + + let output = []; + + try { + results.forEach(result => { + const {creativeId, ad, price, exp, width, height, currency, metaData, advertiserDomains, mediaType = BANNER} = result; + if (!ad || !price) { + return; + } + + const response = { + requestId: bidId, + cpm: price, + width: width, + height: height, + creativeId: creativeId, + currency: currency || CURRENCY, + netRevenue: true, + ttl: exp || TTL_SECONDS, + }; + + if (metaData) { + Object.assign(response, { + meta: metaData + }) + } else { + Object.assign(response, { + meta: { + advertiserDomains: advertiserDomains || [] + } + }) + } + + if (mediaType === BANNER) { + Object.assign(response, { + ad: ad, + }); + } else { + Object.assign(response, { + vastXml: ad, + mediaType: VIDEO + }); + } + output.push(response); + }); + return output; + } catch (e) { + return []; + } +} + +function getUserSyncs(syncOptions, responses, gdprConsent = {}, uspConsent = '') { + let syncs = []; + const {iframeEnabled, pixelEnabled} = syncOptions; + const {gdprApplies, consentString = ''} = gdprConsent; + + const cidArr = responses.filter(resp => deepAccess(resp, 'body.cid')).map(resp => resp.body.cid).filter(uniques); + const params = `?cid=${encodeURIComponent(cidArr.join(','))}&gdpr=${gdprApplies ? 1 : 0}&gdpr_consent=${encodeURIComponent(consentString || '')}&us_privacy=${encodeURIComponent(uspConsent || '')}` + if (iframeEnabled) { + syncs.push({ + type: 'iframe', + url: `https://sync.minutemedia-prebid.com/api/sync/iframe/${params}` + }); + } + if (pixelEnabled) { + syncs.push({ + type: 'image', + url: `https://sync.minutemedia-prebid.com/api/sync/image/${params}` + }); + } + return syncs; +} + +export function hashCode(s, prefix = '_') { + const l = s.length; + let h = 0 + let i = 0; + if (l > 0) { + while (i < l) { + h = (h << 5) - h + s.charCodeAt(i++) | 0; + } + } + return prefix + h; +} + +export function getUniqueDealId(key, expiry = UNIQUE_DEAL_ID_EXPIRY) { + const storageKey = `u_${key}`; + const now = Date.now(); + const data = getStorageItem(storageKey); + let uniqueId; + + if (!data || !data.value || now - data.created > expiry) { + uniqueId = `${key}_${now.toString()}`; + setStorageItem(storageKey, uniqueId); + } else { + uniqueId = data.value; + } + + return uniqueId; +} + +export function getStorageItem(key) { + try { + return tryParseJSON(storage.getDataFromLocalStorage(key)); + } catch (e) { + } + + return null; +} + +export function setStorageItem(key, value, timestamp) { + try { + const created = timestamp || Date.now(); + const data = JSON.stringify({value, created}); + storage.setDataInLocalStorage(key, data); + } catch (e) { + } +} + +export function tryParseJSON(value) { + try { + return JSON.parse(value); + } catch (e) { + return value; + } +} + +export const spec = { + code: BIDDER_CODE, + version: BIDDER_VERSION, + gvlid: GVLID, + supportedMediaTypes: [BANNER, VIDEO], + isBidRequestValid, + buildRequests, + interpretResponse, + getUserSyncs +}; + +registerBidder(spec); diff --git a/modules/minutemediaplusBidAdapter.md b/modules/minutemediaplusBidAdapter.md new file mode 100644 index 00000000000..410c00e7017 --- /dev/null +++ b/modules/minutemediaplusBidAdapter.md @@ -0,0 +1,35 @@ +# Overview + +**Module Name:** MinuteMediaPlus Bidder Adapter + +**Module Type:** Bidder Adapter + +**Maintainer:** hb@minutemedia.com + +# Description + +Module that connects to MinuteMediaPlus's demand sources. + +# Test Parameters +```js +var adUnits = [ + { + code: 'test-ad', + sizes: [[300, 250]], + bids: [ + { + bidder: 'mmplus', + params: { + cId: '562524b21b1c1f08117fc7f9', + pId: '59ac17c192832d0011283fe3', + bidFloor: 0.0001, + ext: { + param1: 'loremipsum', + param2: 'dolorsitamet' + } + } + } + ] + } +]; +``` diff --git a/modules/missenaBidAdapter.js b/modules/missenaBidAdapter.js index 2ec9d39fc5d..33fa6857e85 100644 --- a/modules/missenaBidAdapter.js +++ b/modules/missenaBidAdapter.js @@ -1,12 +1,14 @@ -import { formatQS, logInfo } from '../src/utils.js'; +import { buildUrl, formatQS, logInfo, triggerPixel } from '../src/utils.js'; import { BANNER } from '../src/mediaTypes.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; const BIDDER_CODE = 'missena'; const ENDPOINT_URL = 'https://bid.missena.io/'; +const EVENTS_DOMAIN = 'events.missena.io'; +const EVENTS_DOMAIN_DEV = 'events.staging.missena.xyz'; export const spec = { - aliases: [BIDDER_CODE], + aliases: ['msna'], code: BIDDER_CODE, gvlid: 687, supportedMediaTypes: [BANNER], @@ -30,6 +32,7 @@ export const spec = { buildRequests: function (validBidRequests, bidderRequest) { return validBidRequests.map((bidRequest) => { const payload = { + adunit: bidRequest.adUnitCode, request_id: bidRequest.bidId, timeout: bidderRequest.timeout, }; @@ -48,6 +51,16 @@ export const spec = { if (bidRequest.params.test) { payload.test = bidRequest.params.test; } + if (bidRequest.params.placement) { + payload.placement = bidRequest.params.placement; + } + if (bidRequest.params.formats) { + payload.formats = bidRequest.params.formats; + } + if (bidRequest.params.isInternal) { + payload.is_internal = bidRequest.params.isInternal; + } + payload.userEids = bidRequest.userIdAsEids || []; return { method: 'POST', url: baseUrl + '?' + formatQS({ t: bidRequest.params.apiKey }), @@ -109,6 +122,15 @@ export const spec = { * @param {Bid} The bid that won the auction */ onBidWon: function (bid) { + const hostname = bid.params[0].baseUrl ? EVENTS_DOMAIN_DEV : EVENTS_DOMAIN; + triggerPixel( + buildUrl({ + protocol: 'https', + hostname, + pathname: '/v1/bidsuccess', + search: { t: bid.params[0].apiKey, provider: bid.meta?.networkName, cpm: bid.cpm, currency: bid.currency }, + }) + ); logInfo('Missena - Bid won', bid); }, }; diff --git a/modules/mwOpenLinkIdSystem.js b/modules/mwOpenLinkIdSystem.js index 552223fa73c..9b1035cbf18 100644 --- a/modules/mwOpenLinkIdSystem.js +++ b/modules/mwOpenLinkIdSystem.js @@ -8,14 +8,15 @@ import { timestamp, logError, deepClone, generateUUID, 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 openLinkID = { name: 'mwol', cookie_expiration: (86400 * 1000 * 365 * 1) // 1 year } -const storage = getStorageManager(); +const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: openLinkID.name}); function getExpirationDate() { return (new Date(timestamp() + openLinkID.cookie_expiration)).toGMTString(); diff --git a/modules/nativoBidAdapter.js b/modules/nativoBidAdapter.js index a92168492d0..c62a74e6d6c 100644 --- a/modules/nativoBidAdapter.js +++ b/modules/nativoBidAdapter.js @@ -1,6 +1,7 @@ import { deepAccess, isEmpty } from '../src/utils.js' import { registerBidder } from '../src/adapters/bidderFactory.js' import { BANNER } from '../src/mediaTypes.js' +import { getGlobal } from '../src/prebidGlobal.js' // import { config } from 'src/config' const BIDDER_CODE = 'nativo' @@ -14,6 +15,8 @@ const SUPPORTED_AD_TYPES = [BANNER] const FLOOR_PRICE_CURRENCY = 'USD' const PRICE_FLOOR_WILDCARD = '*' +const localPbjsRef = getGlobal() + /** * Keep track of bid data by keys * @returns {Object} - Map of bid data that can be referenced by multiple keys @@ -133,6 +136,9 @@ export const spec = { * @return ServerRequest Info describing the request to the server. */ buildRequests: function (validBidRequests, bidderRequest) { + const requestData = new RequestData() + requestData.addBidRequestDataSource(new UserEIDs()) + // Parse values from bid requests const placementIds = new Set() const bidDataMap = BidDataMap() @@ -166,6 +172,8 @@ export const spec = { if (bidRequestFloorPriceData) { floorPriceData[bidRequest.adUnitCode] = bidRequestFloorPriceData } + + requestData.processBidRequestData(bidRequest, bidderRequest) }) bidRequestMap[bidderRequest.bidderRequestId] = bidDataMap @@ -174,6 +182,10 @@ export const spec = { // Build basic required QS Params let params = [ + // Prebid version + { + key: 'ntv_pbv', value: localPbjsRef.version + }, // Prebid request id { key: 'ntv_pb_rid', value: bidderRequest.bidderRequestId }, // Ad unit data @@ -255,9 +267,12 @@ export const spec = { params.unshift({ key: 'us_privacy', value: bidderRequest.uspConsent }) } + const qsParamStrings = [requestData.getRequestDataQueryString(), arrayToQS(params)] + const requestUrl = buildRequestUrl(BIDDER_ENDPOINT, qsParamStrings) + let serverRequest = { method: 'GET', - url: BIDDER_ENDPOINT + arrayToQS(params), + url: requestUrl } return serverRequest @@ -404,13 +419,6 @@ export const spec = { return syncs }, - /** - * Will be called when an adpater timed out for an auction. - * Adapter can fire a ajax or pixel call to register a timeout at thier end. - * @param {Object} timeoutData - Timeout specific data - */ - onTimeout: function (timeoutData) {}, - /** * Will be called when a bid from the adapter won the auction. * @param {Object} bid - The bid that won the auction @@ -425,12 +433,6 @@ export const spec = { appendFilterData(campaignsToFilter, ext.campaignsToFilter) }, - /** - * Will be called when the adserver targeting has been set for a bid from the adapter. - * @param {Object} bidder - The bid of which the targeting has been set - */ - onSetTargeting: function (bid) {}, - /** * Maps Prebid's bidId to Nativo's placementId values per unique bidderRequestId * @param {String} bidderRequestId - The unique ID value associated with the bidderRequest @@ -451,6 +453,78 @@ export const spec = { registerBidder(spec) // Utils +export class RequestData { + constructor() { + this.bidRequestDataSources = [] + } + + addBidRequestDataSource(bidRequestDataSource) { + if (!(bidRequestDataSource instanceof BidRequestDataSource)) return + + this.bidRequestDataSources.push(bidRequestDataSource) + } + + processBidRequestData(bidRequest, bidderRequest) { + for (let bidRequestDataSource of this.bidRequestDataSources) { + bidRequestDataSource.processBidRequestData(bidRequest, bidderRequest) + } + } + + getRequestDataQueryString() { + if (this.bidRequestDataSources.length == 0) return + + const queryParams = this.bidRequestDataSources.map(dataSource => dataSource.getRequestQueryString()).filter(queryString => queryString !== '') + return queryParams.join('&') + } +} + +export class BidRequestDataSource { + constructor() { + this.type = 'BidRequestDataSource' + } + processBidRequestData(bidRequest, bidderRequest) { } + getRequestQueryString() { return '' } +} + +export class UserEIDs extends BidRequestDataSource { + constructor() { + super() + this.type = 'UserEIDs' + this.qsParam = new QueryStringParam('ntv_pb_eid') + this.eids = [] + } + + processBidRequestData(bidRequest, bidderRequest) { + if (bidRequest.userIdAsEids === undefined || this.eids.length > 0) return + this.eids = bidRequest.userIdAsEids + } + + getRequestQueryString() { + if (this.eids.length === 0) return '' + + const encodedValueArray = encodeToBase64(this.eids) + this.qsParam.value = encodedValueArray + return this.qsParam.toString() + } +} + +export class QueryStringParam { + constructor(key, value) { + this.key = key + this.value = value + } +} + +QueryStringParam.prototype.toString = function () { + return `${this.key}=${this.value}` +} + +export function encodeToBase64(value) { + try { + return btoa(JSON.stringify(value)) + } catch (err) { } +} + export function parseFloorPriceData(bidRequest) { if (typeof bidRequest.getFloor !== 'function') return @@ -589,12 +663,9 @@ function appendQSParamString(str, key, value) { * @returns */ function arrayToQS(arr) { - return ( - '?' + - arr.reduce((value, obj) => { - return appendQSParamString(value, obj.key, obj.value) - }, '') - ) + return arr.reduce((value, obj) => { + return appendQSParamString(value, obj.key, obj.value) + }, '') } /** @@ -615,6 +686,24 @@ function getLargestSize(sizes, method = area) { }) } +/** + * Build the final request url + */ +export function buildRequestUrl(baseUrl, qsParamStringArray = []) { + if (qsParamStringArray.length === 0 || !Array.isArray(qsParamStringArray)) return baseUrl + + const nonEmptyQSParamStrings = qsParamStringArray.filter(qsParamString => qsParamString.trim() !== '') + + if (nonEmptyQSParamStrings.length === 0) return baseUrl + + let requestUrl = `${baseUrl}?${nonEmptyQSParamStrings[0]}` + for (let i = 1; i < nonEmptyQSParamStrings.length; i++) { + requestUrl += `&${nonEmptyQSParamStrings[i]}` + } + + return requestUrl +} + /** * Calculate the area * @param {Array} size - [width, height] @@ -645,7 +734,7 @@ export function getPageUrlFromBidRequest(bidRequest) { try { const url = new URL(paramPageUrl) return url.href - } catch (err) {} + } catch (err) { } } export function hasProtocol(url) { diff --git a/modules/naveggIdSystem.js b/modules/naveggIdSystem.js index 25f0afe4733..878ae7fadb2 100644 --- a/modules/naveggIdSystem.js +++ b/modules/naveggIdSystem.js @@ -7,7 +7,8 @@ import { isStr, isPlainObject, logError } from '../src/utils.js'; import { submodule } from '../src/hook.js'; import { ajax } from '../src/ajax.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 = 'naveggId'; const OLD_NAVEGG_ID = 'nid'; @@ -16,7 +17,7 @@ const BASE_URL = 'https://id.navegg.com/uid/'; const DEFAULT_EXPIRE = 8 * 24 * 3600 * 1000; const INVALID_EXPIRE = 3600 * 1000; -export const storage = getStorageManager(); +export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); function getNaveggIdFromApi() { const callbacks = { diff --git a/modules/neuwoRtdProvider.js b/modules/neuwoRtdProvider.js new file mode 100644 index 00000000000..00a3c59b4a6 --- /dev/null +++ b/modules/neuwoRtdProvider.js @@ -0,0 +1,174 @@ +import { deepAccess, deepSetValue, generateUUID, logError, logInfo, mergeDeep } from '../src/utils.js'; +import { getRefererInfo } from '../src/refererDetection.js'; +import { ajax } from '../src/ajax.js'; +import { submodule } from '../src/hook.js'; +import * as events from '../src/events.js'; +import CONSTANTS from '../src/constants.json'; + +export const DATA_PROVIDER = 'neuwo.ai'; +const SEGTAX_IAB = 6 // IAB - Content Taxonomy version 2 +const RESPONSE_IAB_TIER_1 = 'marketing_categories.iab_tier_1' +const RESPONSE_IAB_TIER_2 = 'marketing_categories.iab_tier_2' + +function init(config = {}, userConsent) { + config.params = config.params || {} + // ignore module if publicToken is missing (module setup failure) + if (!config.params.publicToken) { + logError('publicToken missing', 'NeuwoRTDModule', 'config.params.publicToken') + return false; + } + if (!config.params.apiUrl) { + logError('apiUrl missing', 'NeuwoRTDModule', 'config.params.apiUrl') + return false; + } + return true; +} + +export function getBidRequestData(reqBidsConfigObj, callback, config, userConsent) { + config.params = config.params || {}; + logInfo('NeuwoRTDModule', 'starting getBidRequestData') + + const wrappedArgUrl = encodeURIComponent(config.params.argUrl || getRefererInfo().page); + /* adjust for pages api.url?prefix=test (to add params with '&') as well as api.url (to add params with '?') */ + const joiner = config.params.apiUrl.indexOf('?') < 0 ? '?' : '&' + const url = config.params.apiUrl + joiner + [ + 'token=' + config.params.publicToken, + 'url=' + wrappedArgUrl + ].join('&') + const billingId = generateUUID(); + + const success = (responseContent) => { + logInfo('NeuwoRTDModule', 'GetAiTopics: response', responseContent) + try { + const jsonContent = JSON.parse(responseContent); + if (jsonContent.marketing_categories) { + events.emit(CONSTANTS.EVENTS.BILLABLE_EVENT, { type: 'request', billingId, vendor: neuwoRtdModule.name }) + } + injectTopics(jsonContent, reqBidsConfigObj, billingId) + } catch (ex) { + logError('NeuwoRTDModule', 'Response to JSON parse error', ex) + } + callback() + } + + const error = (err) => { + logError('xhr error', null, err); + callback() + } + + ajax(url, {success, error}, null, { + // could assume Origin header is set, or + // customHeaders: { 'Origin': 'Origin' } + }) +} + +export function addFragment(base, path, addition) { + const container = {} + deepSetValue(container, path, addition) + mergeDeep(base, container) +} + +/** + * Concatenate a base array and an array within an object + * non-array bases will be arrays, non-arrays at object key will be discarded + * @param {array} base base array to add to + * @param {object} source object to get an array from + * @param {string} key dot-notated path to array within object + * @returns base + source[key] if that's an array + */ +function combineArray(base, source, key) { + if (Array.isArray(base) === false) base = [] + const addition = deepAccess(source, key, []) + if (Array.isArray(addition)) return base.concat(addition) + else return base +} + +export function injectTopics(topics, bidsConfig) { + topics = topics || {} + + // join arrays of IAB category details to single array + const combinedTiers = combineArray( + combineArray([], topics, RESPONSE_IAB_TIER_1), + topics, RESPONSE_IAB_TIER_2) + + const segment = pickSegments(combinedTiers) + // effectively gets topics.marketing_categories.iab_tier_1, topics.marketing_categories.iab_tier_2 + // used as FPD segments content + + const IABSegments = { + name: DATA_PROVIDER, + ext: { segtax: SEGTAX_IAB }, + segment + } + + addFragment(bidsConfig.ortb2Fragments.global, 'site.content.data', [IABSegments]) + + // upgrade category taxonomy to IAB 2.2, inject result to page categories + if (segment.length > 0) { + addFragment(bidsConfig.ortb2Fragments.global, 'site.pagecat', segment.map(s => s.id)) + } + + logInfo('NeuwoRTDModule', 'injectTopics: post-injection bidsConfig', bidsConfig) +} + +/* eslint-disable object-property-newline */ +const D_IAB_ID = { // Content Taxonomy version 2.0 final release November 2017 [sic] (Taxonomy ID Mapping, IAB versions 2.0 - 2.2) + 'IAB19-1': '603', 'IAB6-1': '193', 'IAB5-2': '133', 'IAB20-1': '665', 'IAB20-2': '656', 'IAB23-2': '454', 'IAB3-2': '102', 'IAB20-3': '672', 'IAB8-5': '211', + 'IAB8-18': '211', 'IAB7-4': '288', 'IAB7-5': '233', 'IAB17-12': '484', 'IAB19-3': '608', 'IAB21-1': '442', 'IAB9-2': '248', 'IAB15-1': '456', 'IAB9-17': '265', 'IAB20-4': '658', + 'IAB2-3': '30', 'IAB2-1': '32', 'IAB17-1': '518', 'IAB2-2': '34', 'IAB2': '1', 'IAB8-2': '215', 'IAB17-2': '545', 'IAB17-26': '547', 'IAB9-3': '249', 'IAB18-1': '553', 'IAB20-5': '674', + 'IAB15-2': '465', 'IAB3-3': '119', 'IAB16-2': '423', 'IAB9-4': '259', 'IAB9-5': '270', 'IAB18-2': '574', 'IAB17-4': '549', 'IAB7-33': '312', 'IAB1-1': '42', 'IAB17-5': '485', 'IAB23-3': '458', + 'IAB20-6': '675', 'IAB3': '53', 'IAB20-7': '676', 'IAB19-5': '633', 'IAB20-9': '677', 'IAB9-6': '250', 'IAB17-6': '499', 'IAB2-4': '25', 'IAB9-7': '271', 'IAB4-11': '125', 'IAB4-1': '126', + 'IAB4': '123', 'IAB16-3': '424', 'IAB2-5': '18', 'IAB17-7': '486', 'IAB15-3': '466', 'IAB23-5': '459', 'IAB9-9': '260', 'IAB2-22': '19', 'IAB17-8': '500', 'IAB9-10': '261', 'IAB5-5': '137', + 'IAB9-11': '262', 'IAB2-21': '3', 'IAB19-2': '610', 'IAB19-8': '600', 'IAB19-9': '601', 'IAB3-5': '121', 'IAB9-15': '264', 'IAB2-6': '8', 'IAB2-7': '9', 'IAB22-2': '474', 'IAB17-9': '491', + 'IAB2-8': '10', 'IAB20-12': '678', 'IAB17-3': '492', 'IAB19-12': '611', 'IAB14-1': '188', 'IAB6-3': '194', 'IAB7-17': '316', 'IAB19-13': '612', 'IAB8-8': '217', 'IAB9-1': '205', 'IAB19-22': '613', + 'IAB8-9': '218', 'IAB14-2': '189', 'IAB16-4': '425', 'IAB9-12': '251', 'IAB5': '132', 'IAB6-9': '190', 'IAB19-15': '623', 'IAB17-17': '496', 'IAB20-14': '659', 'IAB6': '186', 'IAB20-26': '666', + 'IAB17-10': '510', 'IAB13-4': '396', 'IAB1-3': '201', 'IAB16-1': '426', 'IAB17-11': '511', 'IAB17-13': '511', 'IAB17-32': '511', 'IAB7-1': '225', 'IAB8': '210', 'IAB8-10': '219', 'IAB9-13': '266', + 'IAB10-4': '275', 'IAB9-14': '273', 'IAB15-8': '469', 'IAB15-4': '470', 'IAB17-15': '512', 'IAB3-7': '77', 'IAB19-16': '614', 'IAB3-8': '78', 'IAB2-10': '22', 'IAB2-12': '22', 'IAB2-11': '11', + 'IAB8-12': '221', 'IAB7-35': '223', 'IAB7-38': '223', 'IAB7-24': '296', 'IAB13-5': '411', 'IAB7-25': '234', 'IAB23-6': '460', 'IAB9': '239', 'IAB7-26': '235', 'IAB10': '274', 'IAB10-1': '278', + 'IAB10-2': '279', 'IAB19-17': '634', 'IAB10-5': '280', 'IAB5-10': '145', 'IAB5-11': '146', 'IAB20-17': '667', 'IAB17-16': '497', 'IAB20-18': '668', 'IAB3-9': '55', 'IAB1-4': '440', 'IAB17-18': '514', + 'IAB17-27': '515', 'IAB10-3': '282', 'IAB19-25': '618', 'IAB17-19': '516', 'IAB13-6': '398', 'IAB10-7': '283', 'IAB12-1': '382', 'IAB19-24': '624', 'IAB6-4': '195', 'IAB23-7': '461', 'IAB9-19': '252', + 'IAB4-4': '128', 'IAB4-5': '127', 'IAB23-8': '462', 'IAB10-8': '284', 'IAB5-8': '147', 'IAB16-5': '427', 'IAB11-2': '383', 'IAB12-3': '384', 'IAB3-10': '57', 'IAB2-13': '23', 'IAB9-20': '241', + 'IAB3-1': '58', 'IAB3-11': '58', 'IAB14-4': '191', 'IAB17-20': '520', 'IAB7-31': '228', 'IAB7-37': '301', 'IAB3-12': '107', 'IAB2-14': '13', 'IAB17-25': '519', 'IAB2-15': '27', 'IAB1-5': '324', + 'IAB1-6': '338', 'IAB9-16': '243', 'IAB13-8': '412', 'IAB12-2': '385', 'IAB9-21': '253', 'IAB8-6': '222', 'IAB7-32': '229', 'IAB2-16': '14', 'IAB17-23': '521', 'IAB13-9': '413', 'IAB17-24': '501', + 'IAB9-22': '254', 'IAB15-5': '244', 'IAB6-2': '196', 'IAB6-5': '197', 'IAB6-6': '198', 'IAB2-17': '24', 'IAB13-2': '405', 'IAB13': '391', 'IAB13-7': '410', 'IAB13-12': '415', 'IAB16': '422', + 'IAB9-23': '255', 'IAB7-36': '236', 'IAB15-6': '471', 'IAB2-18': '15', 'IAB11-4': '386', 'IAB1-2': '432', 'IAB5-9': '139', 'IAB6-7': '305', 'IAB5-12': '149', 'IAB5-13': '134', 'IAB19-4': '631', + 'IAB19-19': '631', 'IAB19-20': '631', 'IAB19-32': '631', 'IAB9-24': '245', 'IAB21': '441', 'IAB21-3': '451', 'IAB23': '453', 'IAB10-9': '276', 'IAB4-9': '130', 'IAB16-6': '429', 'IAB4-6': '129', + 'IAB13-10': '416', 'IAB2-19': '28', 'IAB17-28': '525', 'IAB9-25': '272', 'IAB17-29': '527', 'IAB17-30': '227', 'IAB17-31': '530', 'IAB22-1': '481', 'IAB15': '464', 'IAB9-26': '246', 'IAB9-27': '256', + 'IAB9-28': '267', 'IAB17-33': '502', 'IAB19-35': '627', 'IAB2-20': '4', 'IAB7-39': '307', 'IAB19-30': '605', 'IAB22': '473', 'IAB17-34': '503', 'IAB17-35': '531', 'IAB7-19': '309', 'IAB7-40': '310', + 'IAB19-6': '635', 'IAB7-41': '237', 'IAB17-36': '504', 'IAB17-44': '533', 'IAB20-23': '662', 'IAB15-7': '472', 'IAB20-24': '671', 'IAB5-14': '136', 'IAB6-8': '199', 'IAB17': '483', 'IAB9-29': '263', + 'IAB2-23': '5', 'IAB13-11': '414', 'IAB4-3': '395', 'IAB18': '552', 'IAB7-42': '311', 'IAB17-37': '505', 'IAB17-38': '537', 'IAB17-39': '538', 'IAB19-26': '636', 'IAB19': '596', 'IAB1-7': '640', + 'IAB17-40': '539', 'IAB7-43': '293', 'IAB20': '653', 'IAB8-16': '212', 'IAB8-17': '213', 'IAB16-7': '430', 'IAB9-30': '680', 'IAB17-41': '541', 'IAB17-42': '542', 'IAB17-43': '506', 'IAB15-10': '390', + 'IAB19-23': '607', 'IAB19-34': '629', 'IAB14-7': '165', 'IAB7-44': '231', 'IAB7-45': '238', 'IAB9-31': '257', 'IAB5-1': '135', 'IAB7-2': '301', 'IAB18-6': '580', 'IAB7-3': '297', 'IAB23-1': '453', + 'IAB8-1': '214', 'IAB7-6': '312', 'IAB7-7': '300', 'IAB7-8': '301', 'IAB13-1': '410', 'IAB7-9': '301', 'IAB15-9': '465', 'IAB7-10': '313', 'IAB3-4': '602', 'IAB20-8': '660', 'IAB8-3': '214', + 'IAB20-10': '660', 'IAB7-11': '314', 'IAB20-11': '660', 'IAB23-4': '459', 'IAB9-8': '270', 'IAB8-4': '214', 'IAB7-12': '306', 'IAB7-13': '313', 'IAB7-14': '287', 'IAB18-5': '575', 'IAB7-15': '315', + 'IAB8-7': '214', 'IAB19-11': '616', 'IAB7-16': '289', 'IAB7-18': '301', 'IAB7-20': '290', 'IAB20-13': '659', 'IAB7-21': '313', 'IAB18-3': '579', 'IAB13-3': '52', 'IAB20-15': '659', 'IAB8-11': '214', + 'IAB7-22': '318', 'IAB20-16': '659', 'IAB7-23': '313', 'IAB7': '223', 'IAB10-6': '634', 'IAB7-27': '318', 'IAB11-1': '388', 'IAB7-29': '318', 'IAB7-30': '304', 'IAB19-18': '619', 'IAB8-13': '214', + 'IAB20-19': '659', 'IAB20-20': '657', 'IAB8-14': '214', 'IAB18-4': '565', 'IAB23-9': '459', 'IAB11': '379', 'IAB8-15': '214', 'IAB20-21': '662', 'IAB17-21': '492', 'IAB17-22': '518', 'IAB12': '379', + 'IAB23-10': '453', 'IAB7-34': '301', 'IAB4-8': '395', 'IAB26-3': '608', 'IAB20-25': '151', 'IAB20-27': '659' +} + +export function convertSegment(segment) { + if (!segment) return {} + return { + id: D_IAB_ID[segment.id || segment.ID] + } +} + +/** + * map array of objects to segments + * @param {Array[{ID: string}]} normalizable + * @returns array of IAB "segments" + */ +export function pickSegments(normalizable) { + if (Array.isArray(normalizable) === false) return [] + return normalizable.map(convertSegment) + .filter(t => t.id) +} + +export const neuwoRtdModule = { + name: 'NeuwoRTDModule', + init, + getBidRequestData +} + +submodule('realTimeData', neuwoRtdModule) diff --git a/modules/neuwoRtdProvider.md b/modules/neuwoRtdProvider.md new file mode 100644 index 00000000000..2adead66d4e --- /dev/null +++ b/modules/neuwoRtdProvider.md @@ -0,0 +1,49 @@ +# Overview + +Module Name: Neuwo Rtd Provider +Module Type: Rtd Provider +Maintainer: neuwo.ai + +# Description + +The Neuwo AI RTD module is an advanced AI solution for real-time data processing in the field of contextual targeting and advertising. With its cutting-edge algorithms, it allows advertisers to target their audiences with the highest level of precision based on context, while also delivering a seamless user experience. + +The module provides advertiser with valuable insights and real-time contextual bidding capabilities. Whether you're a seasoned advertising professional or just starting out, Neuwo AI RTD module is the ultimate tool for contextual targeting and advertising. + +The benefit of Neuwo AI RTD module is that it provides an alternative solution for advertisers to target their audiences and deliver relevant advertisements, as the widespread use of cookies for tracking and targeting is becoming increasingly limited. + +The RTD module uses cutting-edge algorithms to process real-time data, allowing advertisers to target their audiences based on contextual information, such as segments, IAB Tiers and brand safety. The RTD module is designed to be flexible and scalable, making it an ideal solution for advertisers looking to stay ahead of the curve in the post-cookie era. + +Generate your token at: [https://neuwo.ai/generatetoken/] + +# Configuration + +```javascript + +const neuwoDataProvider = { + name: 'NeuwoRTDModule', + params: { + publicToken: '', + apiUrl: '' + } +} +pbjs.setConfig({realTimeData: { dataProviders: [ neuwoDataProvider ]}}) + +``` + +# Testing + +## Add development tools if necessary + +- Install node for npm +- run in prebid.js source folder: +`npm ci` +`npm i -g gulp-cli` + +## Serve + +`gulp serve --modules=rtdModule,neuwoRtdProvider,appnexusBidAdapter` + +- in your browser, navigate to: + +`http://localhost:9999/integrationExamples/gpt/neuwoRtdProvider_example.html` diff --git a/modules/nextMillenniumBidAdapter.js b/modules/nextMillenniumBidAdapter.js index b5d0e15d078..89c6e677007 100644 --- a/modules/nextMillenniumBidAdapter.js +++ b/modules/nextMillenniumBidAdapter.js @@ -24,12 +24,14 @@ import { getRefererInfo } from '../src/refererDetection.js'; const BIDDER_CODE = 'nextMillennium'; const ENDPOINT = 'https://pbs.nextmillmedia.com/openrtb2/auction'; const TEST_ENDPOINT = 'https://test.pbs.nextmillmedia.com/openrtb2/auction'; +const SYNC_ENDPOINT = 'https://cookies.nextmillmedia.com/sync?'; const REPORT_ENDPOINT = 'https://report2.hb.brainlyads.com/statistics/metric'; const TIME_TO_LIVE = 360; const VIDEO_PARAMS = [ 'api', 'linearity', 'maxduration', 'mimes', 'minduration', 'placement', 'playbackmethod', 'protocols', 'startdelay' ]; +const GVLID = 1060; const sendingDataStatistic = initSendingDataStatistic(); events.on(CONSTANTS.EVENTS.AUCTION_INIT, auctionInitHandler); @@ -43,6 +45,7 @@ events.on(CONSTANTS.EVENTS.BID_WON, bidWonHandler); export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO], + gvlid: GVLID, isBidRequestValid: function(bid) { return !!( @@ -87,6 +90,7 @@ export const spec = { }; const imp = { + id: bid.adUnitCode, ext: { prebid: { storedrequest: {id} @@ -182,7 +186,7 @@ export const spec = { height: bid.h, creativeId: bid.adid, currency: response.cur, - netRevenue: false, + netRevenue: true, ttl: TIME_TO_LIVE, meta: { advertiserDomains: bid.adomain || [] @@ -231,6 +235,13 @@ export const spec = { }) } + if (!pixels.length) { + let syncUrl = SYNC_ENDPOINT; + if (gdprConsent && gdprConsent.gdprApplies) syncUrl += 'gdpr=1&gdpr_consent=' + gdprConsent.consentString + '&'; + if (uspConsent) syncUrl += 'us_privacy=' + uspConsent + '&'; + if (syncOptions.iframeEnabled) pixels.push({type: 'iframe', url: syncUrl + 'type=iframe'}); + if (syncOptions.pixelEnabled) pixels.push({type: 'image', url: syncUrl + 'type=image'}); + } return pixels; }, @@ -271,15 +282,25 @@ export const spec = { }; function replaceUsersyncMacros(url, gdprConsent, uspConsent) { - const { consentString, gdprApplies } = gdprConsent; - - return url.replace( - '{{.GDPR}}', Number(gdprApplies) - ).replace( - '{{.GDPRConsent}}', consentString - ).replace( - '{{.USPrivacy}}', uspConsent - ); + const { consentString, gdprApplies } = gdprConsent || {}; + + if (gdprApplies) { + const gdpr = Number(gdprApplies); + url = url.replace('{{.GDPR}}', gdpr); + + if (gdpr == 1 && consentString && consentString.length > 0) { + url = url.replace('{{.GDPRConsent}}', consentString); + } + } else { + url = url.replace('{{.GDPR}}', 0); + url = url.replace('{{.GDPRConsent}}', ''); + } + + if (uspConsent) { + url = url.replace('{{.USPrivacy}}', uspConsent); + } + + return url; }; function getAdEl(bid) { diff --git a/modules/nexx360BidAdapter.js b/modules/nexx360BidAdapter.js index 96738f586c1..9a7541fdd96 100644 --- a/modules/nexx360BidAdapter.js +++ b/modules/nexx360BidAdapter.js @@ -1,251 +1,176 @@ import {config} from '../src/config.js'; -import * as utils from '../src/utils.js'; +import { deepAccess, deepSetValue, generateUUID, logError, logInfo } from '../src/utils.js'; +import {Renderer} from '../src/Renderer.js'; +import {getStorageManager} from '../src/storageManager.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; import {getGlobal} from '../src/prebidGlobal.js'; +import {ortbConverter} from '../libraries/ortbConverter/converter.js' +import { INSTREAM, OUTSTREAM } from '../src/video.js'; + +const OUTSTREAM_RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; -const VIDEO_TARGETING = ['startdelay', 'mimes', 'minduration', 'maxduration', 'delivery', - 'startdelay', 'skip', 'playbackmethod', 'api', 'protocol', 'boxingallowed', 'maxextended', - 'linearity', 'delivery', 'protocols', 'placement', 'minbitrate', 'maxbitrate', 'battr', 'ext']; const BIDDER_CODE = 'nexx360'; const REQUEST_URL = 'https://fast.nexx360.io/booster'; - -const PAGE_VIEW_ID = utils.generateUUID(); - -const BIDDER_VERSION = '1.0'; - +const PAGE_VIEW_ID = generateUUID(); +const BIDDER_VERSION = '2.0'; const GVLID = 965; +const NEXXID_KEY = 'nexx360_storage'; -export const spec = { - code: BIDDER_CODE, - gvlid: GVLID, - aliases: [ - { code: 'revenuemaker' }, - { code: 'firstid-ssp' }, - ], - supportedMediaTypes: [BANNER, VIDEO], - isBidRequestValid, - buildRequests, - interpretResponse, - getUserSyncs, - // onBidWon, -}; +const ALIASES = [ + { code: 'revenuemaker' }, + { code: 'first-id', gvlid: 1178 }, + { code: 'adwebone' }, + { code: 'league-m', gvlid: 965 } +]; -registerBidder(spec); - -/** - * 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. - */ -function isBidRequestValid(bid) { - return !!bid.params.tagId || !!bid.params.videoTagId; -}; +export const storage = getStorageManager({ + bidderCode: BIDDER_CODE, +}); /** - * Make a server request from the list of BidRequests. - * - * @param {validBidRequests[]} - an array of bids - * @return ServerRequest Info describing the request to the server. - */ + * Get the NexxId + * @param + * @return {object | false } false if localstorageNotEnabled + */ -function buildRequests(bids, bidderRequest) { - const data = getBaseRequest(bids[0], bidderRequest); - bids.forEach((bid) => { - const impObject = createImpObject(bid); - if (isBannerBid(bid)) impObject.banner = getBannerObject(bid); - if (isVideoBid(bid)) impObject.video = getVideoObject(bid); - data.imp.push(impObject); - }); - return { - method: 'POST', - url: REQUEST_URL, - data, +export function getNexx360LocalStorage() { + if (!storage.localStorageIsEnabled()) { + logInfo(`localstorage not enabled for Nexx360`); + return false; } -} - -function createImpObject(bid) { - const floor = getFloor(bid, BANNER); - const imp = { - id: bid.bidId, - tagid: bid.adUnitCode, - ext: { - divId: bid.adUnitCode, - nexx360: { - videoTagId: bid.params.videoTagId, - tagId: bid.params.tagId, - allBids: bid.params.allBids === true, - } - } - }; - enrichImp(imp, bid, floor); - return imp; -} - -function getBannerObject(bid) { - return { - format: toFormat(bid.mediaTypes.banner.sizes), - topframe: utils.inIframe() ? 0 : 1 - }; -} - -function getVideoObject(bid) { - let width, height; - const videoParams = utils.deepAccess(bid, `mediaTypes.video`); - const playerSize = utils.deepAccess(bid, 'mediaTypes.video.playerSize'); - const context = utils.deepAccess(bid, 'mediaTypes.video.context'); - // normalize config for video size - if (utils.isArray(bid.sizes) && bid.sizes.length === 2 && !utils.isArray(bid.sizes[0])) { - width = parseInt(bid.sizes[0], 10); - height = parseInt(bid.sizes[1], 10); - } else if (utils.isArray(bid.sizes) && utils.isArray(bid.sizes[0]) && bid.sizes[0].length === 2) { - width = parseInt(bid.sizes[0][0], 10); - height = parseInt(bid.sizes[0][1], 10); - } else if (utils.isArray(playerSize) && playerSize.length === 2) { - width = parseInt(playerSize[0], 10); - height = parseInt(playerSize[1], 10); + const output = storage.getDataFromLocalStorage(NEXXID_KEY); + if (output === null) { + const nexx360Storage = { nexx360Id: generateUUID() }; + storage.setDataInLocalStorage(NEXXID_KEY, JSON.stringify(nexx360Storage)); + return nexx360Storage; } - const video = { - playerSize: [height, width], - context, - }; - - Object.keys(videoParams) - .filter(param => VIDEO_TARGETING.includes(param)) - .forEach(param => video[param] = videoParams[param]); - return video; -} - -function isVideoBid(bid) { - return utils.deepAccess(bid, 'mediaTypes.video'); -} - -function isBannerBid(bid) { - return utils.deepAccess(bid, 'mediaTypes.banner') || !isVideoBid(bid); -} - -function toFormat(sizes) { - return sizes.map((s) => { - return { w: s[0], h: s[1] }; - }); -} - -function getFloor(bid, mediaType) { - let floor = 0; - if (typeof bid.getFloor === 'function') { - const floorInfo = bid.getFloor({ - currency: 'USD', - mediaType: mediaType, - size: '*' - }); - - if (typeof floorInfo === 'object' && - floorInfo.currency === 'USD' && - !isNaN(parseFloat(floorInfo.floor))) { - floor = Math.max(floor, parseFloat(floorInfo.floor)); - } + try { + return JSON.parse(output) + } catch (e) { + return false; } - - return floor; } -function enrichImp(imp, bid, floor) { - if (floor > 0) { - imp.bidfloor = floor; - imp.bidfloorcur = 'USD'; - } else if (bid.params.customFloor) { - imp.bidfloor = bid.params.customFloor; - } - if (bid.ortb2Imp && bid.ortb2Imp.ext && bid.ortb2Imp.ext.data) { - imp.ext.data = bid.ortb2Imp.ext.data; +function getAdContainer(container) { + if (document.getElementById(container)) { + return document.getElementById(container); } } -function getBaseRequest(bid, bidderRequest) { - let req = { - id: bidderRequest.auctionId, - imp: [], - cur: [config.getConfig('currency.adServerCurrency') || 'USD'], - at: 1, - tmax: config.getConfig('bidderTimeout'), - site: { - page: bidderRequest.refererInfo.topmostLocation || bidderRequest.refererInfo.page, - domain: bidderRequest.refererInfo.domain, - }, - regs: { - coppa: (config.getConfig('coppa') === true || bid.params.coppa) ? 1 : 0, - }, - device: { - dnt: (utils.getDNT() || bid.params.doNotTrack) ? 1 : 0, - h: screen.height, - w: screen.width, - ua: window.navigator.userAgent, - language: window.navigator.language.split('-').shift() - }, - user: {}, - ext: { - source: 'prebid.js', - version: '$prebid.version$', - pageViewId: PAGE_VIEW_ID, - bidderVersion: BIDDER_VERSION, +const converter = ortbConverter({ + context: { + netRevenue: true, // or false if your adapter should set bidResponse.netRevenue = false + ttl: 90, // default bidResponse.ttl (when not specified in ORTB response.seatbid[].bid[].exp) + }, + imp(buildImp, bidRequest, context) { + // console.log(bidRequest, context); + const imp = buildImp(bidRequest, context); + const tagid = bidRequest.params.adUnitName ? bidRequest.params.adUnitName : bidRequest.adUnitCode; + deepSetValue(imp, 'tagid', tagid); + deepSetValue(imp, 'ext.adUnitCode', bidRequest.adUnitCode); + const divId = bidRequest.params.divId ? bidRequest.params.divId : bidRequest.adUnitCode; + deepSetValue(imp, 'ext.divId', divId); + const slotEl = getAdContainer(divId); + if (slotEl) { + deepSetValue(imp, 'ext.dimensions.slotW', slotEl.offsetWidth); + deepSetValue(imp, 'ext.dimensions.slotH', slotEl.offsetHeight); + deepSetValue(imp, 'ext.dimensions.cssMaxW', slotEl.style?.maxWidth); + deepSetValue(imp, 'ext.dimensions.cssMaxH', slotEl.style?.maxHeight); } - }; - - if (bid.params.platform) { - utils.deepSetValue(req, 'ext.platform', bid.params.platform); - } - if (bid.params.response_template_name) { - utils.deepSetValue(req, 'ext.response_template_name', bid.params.response_template_name); - } - req.test = config.getConfig('debug') ? 1 : 0; - if (bidderRequest.gdprConsent) { - if (bidderRequest.gdprConsent.gdprApplies !== undefined) { - utils.deepSetValue(req, 'regs.ext.gdpr', bidderRequest.gdprConsent.gdprApplies === true ? 1 : 0); - } - if (bidderRequest.gdprConsent.consentString !== undefined) { - utils.deepSetValue(req, 'user.ext.consent', bidderRequest.gdprConsent.consentString); - } - if (bidderRequest.gdprConsent.addtlConsent !== undefined) { - utils.deepSetValue(req, 'user.ext.ConsentedProvidersSettings.consented_providers', bidderRequest.gdprConsent.addtlConsent); + deepSetValue(imp, 'ext.nexx360', bidRequest.params.tagId); + deepSetValue(imp, 'ext.nexx360.tagId', bidRequest.params.tagId); + deepSetValue(imp, 'ext.nexx360.videoTagId', bidRequest.params.videoTagId); + deepSetValue(imp, 'ext.nexx360.allBids', bidRequest.params.allBids); + if (imp.video) { + const playerSize = deepAccess(bidRequest, 'mediaTypes.video.playerSize'); + const videoContext = deepAccess(bidRequest, 'mediaTypes.video.context'); + deepSetValue(imp, 'video.ext.playerSize', playerSize); + deepSetValue(imp, 'video.ext.context', videoContext); } + + if (bidRequest.params.adUnitName) deepSetValue(imp, 'ext.adUnitName', bidRequest.params.adUnitName); + if (bidRequest.params.adUnitPath) deepSetValue(imp, 'ext.adUnitPath', bidRequest.params.adUnitPath); + return imp; + }, + request(buildRequest, imps, bidderRequest, context) { + const request = buildRequest(imps, bidderRequest, context); + const nexx360LocalStorage = getNexx360LocalStorage(); + if (nexx360LocalStorage) deepSetValue(request, 'ext.nexx360Id', nexx360LocalStorage.nexx360Id); + deepSetValue(request, 'ext.version', '$prebid.version$'); + deepSetValue(request, 'ext.source', 'prebid.js'); + deepSetValue(request, 'ext.pageViewId', PAGE_VIEW_ID); + deepSetValue(request, 'ext.bidderVersion', BIDDER_VERSION); + deepSetValue(request, 'cur', [config.getConfig('currency.adServerCurrency') || 'USD']); + if (!request.user) deepSetValue(request, 'user', {}); + return request; + }, +}); + +/** + * 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. + */ +function isBidRequestValid(bid) { + if (bid.params.adUnitName && (typeof bid.params.adUnitName !== 'string' || bid.params.adUnitName === '')) { + logError('bid.params.adUnitName needs to be a string'); + return false; } - if (bidderRequest.uspConsent) { - utils.deepSetValue(req, 'regs.ext.us_privacy', bidderRequest.uspConsent); + if (bid.params.adUnitPath && (typeof bid.params.adUnitPath !== 'string' || bid.params.adUnitPath === '')) { + logError('bid.params.adUnitPath needs to be a string'); + return false; } - if (bid.schain) { - utils.deepSetValue(req, 'source.ext.schain', bid.schain); + if (bid.params.divId && (typeof bid.params.divId !== 'string' || bid.params.divId === '')) { + logError('bid.params.divId needs to be a string'); + return false; } - if (bid.userIdAsEids) { - utils.deepSetValue(req, 'user.ext.eids', bid.userIdAsEids); + if (bid.params.allBids && typeof bid.params.allBids !== 'boolean') { + logError('bid.params.allBids needs to be a boolean'); + return false; } - const commonFpd = bidderRequest.ortb2 || {}; - if (commonFpd.site) { - utils.mergeDeep(req, {site: commonFpd.site}); + if (!bid.params.tagId && !bid.params.videoTagId && !bid.params.nativeTagId) { + logError('bid.params.tagId or bid.params.videoTagId or bid.params.nativeTagId must be defined'); + return false; } - if (commonFpd.user) { - utils.mergeDeep(req, {user: commonFpd.user}); + return true; +}; + +/** + * Make a server request from the list of BidRequests. + * + * @param {validBidRequests[]} - an array of bids + * @return ServerRequest Info describing the request to the server. + */ + +function buildRequests(bidRequests, bidderRequest) { + const data = converter.toORTB({bidRequests, bidderRequest}) + return { + method: 'POST', + url: REQUEST_URL, + data, } - return req; } /** - * Unpack the response from the server into a list of bids. - * - * @param {ServerResponse} serverResponse A successful response from the server. - * @return {Bid[]} An array of bids which were nested inside the server. - */ -function interpretResponse(response, req) { - const { bidderSettings } = getGlobal(); - const allowAlternateBidderCodes = bidderSettings && bidderSettings.standard ? bidderSettings.standard.allowAlternateBidderCodes : false; - const respBody = response.body; + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + +function interpretResponse(serverResponse) { + const respBody = serverResponse.body; if (!respBody || !Array.isArray(respBody.seatbid)) { return []; } + const { bidderSettings } = getGlobal(); + const allowAlternateBidderCodes = bidderSettings && bidderSettings.standard ? bidderSettings.standard.allowAlternateBidderCodes : false; + let bids = []; respBody.seatbid.forEach(seatbid => { - const ssp = seatbid.seat; bids = [...bids, ...seatbid.bid.map(bid => { const response = { requestId: bid.impid, @@ -254,28 +179,30 @@ function interpretResponse(response, req) { height: bid.h, creativeId: bid.crid, dealId: bid.dealid, - currency: respBody.cur || 'USD', + currency: respBody.cur, netRevenue: true, ttl: 120, - bidderCode: allowAlternateBidderCodes ? `n360-${bid.ssp}` : 'nexx360', - mediaType: bid.type === 'banner' ? 'banner' : 'video', + mediaType: [OUTSTREAM, INSTREAM].includes(bid.ext.mediaType) ? 'video' : bid.ext.mediaType, meta: { advertiserDomains: bid.adomain, - demandSource: ssp, + demandSource: bid.ext.ssp, }, }; - // if (bid.dealid) response.dealid = bid.dealid; + if (allowAlternateBidderCodes) response.bidderCode = `n360-${bid.ext.ssp}`; - if (response.mediaType === 'banner') { - response.adUrl = bid.adUrl; - } - - if (['instream', 'outstream'].includes(bid.type)) response.vastXml = bid.vastXml; + if (bid.ext.mediaType === BANNER) response.adUrl = bid.ext.adUrl; + if ([INSTREAM, OUTSTREAM].includes(bid.ext.mediaType)) response.vastXml = bid.ext.vastXml; - if (bid.ext) { - response.meta.networkId = bid.ext.dsp_id; - response.meta.advertiserId = bid.ext.buyer_id; - response.meta.brandId = bid.ext.brand_id; + if (bid.ext.mediaType === OUTSTREAM) { + response.renderer = createRenderer(bid, OUTSTREAM_RENDERER_URL); + response.divId = bid.ext.divId + }; + if (bid.ext.mediaType === NATIVE) { + try { + response.native = { + ortb: JSON.parse(bid.adm) + } + } catch (e) {} } return response; })]; @@ -284,17 +211,65 @@ function interpretResponse(response, req) { } /** - * 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. - * @return {UserSync[]} The user syncs which should be dropped. - */ + * 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. + * @return {UserSync[]} The user syncs which should be dropped. + */ function getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent) { - if (typeof serverResponses === 'object' && serverResponses != null && serverResponses.length > 0 && serverResponses[0].hasOwnProperty('body') && - serverResponses[0].body.hasOwnProperty('cookies') && typeof serverResponses[0].body.cookies === 'object') { - return serverResponses[0].body.cookies.slice(0, 5); + if (typeof serverResponses === 'object' && + serverResponses != null && + serverResponses.length > 0 && + serverResponses[0].hasOwnProperty('body') && + serverResponses[0].body.hasOwnProperty('ext') && + serverResponses[0].body.ext.hasOwnProperty('cookies') && + typeof serverResponses[0].body.ext.cookies === 'object') { + return serverResponses[0].body.ext.cookies.slice(0, 5); } else { return []; } }; + +function outstreamRender(response) { + response.renderer.push(() => { + window.ANOutstreamVideo.renderAd({ + sizes: [response.width, response.height], + targetId: response.divId, + adResponse: response.vastXml, + rendererOptions: { + showBigPlayButton: false, + showProgressBar: 'bar', + showVolume: false, + allowFullscreen: true, + skippable: false, + content: response.vastXml + } + }); + }); +} + +function createRenderer(bid, url) { + const renderer = Renderer.install({ + id: bid.id, + url: url, + loaded: false, + adUnitCode: bid.ext.adUnitCode, + targetId: bid.ext.divId, + }); + renderer.setRender(outstreamRender); + return renderer; +} + +export const spec = { + code: BIDDER_CODE, + gvlid: GVLID, + aliases: ALIASES, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + isBidRequestValid, + buildRequests, + interpretResponse, + getUserSyncs, +}; + +registerBidder(spec); diff --git a/modules/nobidBidAdapter.js b/modules/nobidBidAdapter.js index 20878b545e4..7bf1c4b80db 100644 --- a/modules/nobidBidAdapter.js +++ b/modules/nobidBidAdapter.js @@ -7,8 +7,8 @@ import {hasPurpose1Consent} from '../src/utils/gpdr.js'; const GVLID = 816; const BIDDER_CODE = 'nobid'; -const storage = getStorageManager({gvlid: GVLID, bidderCode: BIDDER_CODE}); -window.nobidVersion = '1.3.2'; +const storage = getStorageManager({bidderCode: BIDDER_CODE}); +window.nobidVersion = '1.3.3'; window.nobid = window.nobid || {}; window.nobid.bidResponses = window.nobid.bidResponses || {}; window.nobid.timeoutTotal = 0; @@ -175,6 +175,9 @@ function nobidBuildRequests(bids, bidderRequest) { if (adunitObject.div) { a.d = adunitObject.div; } + if (adunitObject.floor) { + a.floor = adunitObject.floor; + } if (adunitObject.targeting) { a.g = adunitObject.targeting; } else { @@ -201,6 +204,12 @@ function nobidBuildRequests(bids, bidderRequest) { adunits.push(a); return adunits; } + function getFloor (bid) { + if (bid && typeof bid.getFloor === 'function' && bid.getFloor().floor) { + return bid.getFloor().floor; + } + return null; + } if (typeof window.nobid.refreshLimit !== 'undefined') { if (window.nobid.refreshLimit < window.nobid.refreshCount) return false; } @@ -227,6 +236,7 @@ function nobidBuildRequests(bids, bidderRequest) { if (bid.mediaType === VIDEO || (videoMediaType && (context === 'instream' || context === 'outstream'))) { adType = 'video'; } + const floor = getFloor(bid); if (siteId) { newAdunit({ @@ -235,7 +245,8 @@ function nobidBuildRequests(bids, bidderRequest) { siteId: siteId, placementId: placementId, ad_type: adType, - params: bid.params + params: bid.params, + floor: floor }, adunits); } diff --git a/modules/nobidBidAdapter.md b/modules/nobidBidAdapter.md index 9e47aa5f43f..4449ad5c88b 100644 --- a/modules/nobidBidAdapter.md +++ b/modules/nobidBidAdapter.md @@ -4,7 +4,7 @@ title: Nobid description: Prebid Nobid Bidder Adaptor biddercode: nobid hide: true -media_types: banner +media_types: banner, video gdpr_supported: true usp_supported: true --- @@ -51,4 +51,4 @@ usp_supported: true ] } ]; -``` \ No newline at end of file +``` diff --git a/modules/novatiqIdSystem.js b/modules/novatiqIdSystem.js index 7bd1ee8acd9..7a801a945ae 100644 --- a/modules/novatiqIdSystem.js +++ b/modules/novatiqIdSystem.js @@ -8,7 +8,10 @@ import { logInfo, getWindowLocation } 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 = 'novatiq'; /** @type {Submodule} */ export const novatiqIdSubmodule = { @@ -17,7 +20,7 @@ export const novatiqIdSubmodule = { * used to link submodule with config * @type {string} */ - name: 'novatiq', + name: MODULE_NAME, /** * used to specify vendor id * @type {number} @@ -35,6 +38,16 @@ export const novatiqIdSubmodule = { snowflake: novatiqId } }; + + if (novatiqId.syncResponse !== undefined) { + responseObj.novatiq.ext = {}; + responseObj.novatiq.ext.syncResponse = novatiqId.syncResponse; + } + + if (typeof config != 'undefined' && typeof config.params !== 'undefined' && typeof config.params.removeAdditionalInfo !== 'undefined' && config.params.removeAdditionalInfo === true) { + delete responseObj.novatiq.snowflake.syncResponse; + } + return responseObj; }, @@ -120,7 +133,7 @@ export const novatiqIdSubmodule = { getNovatiqId(urlParams) { // standard uuid format let uuidFormat = [1e7] + -1e3 + -4e3 + -8e3 + -1e11; - if (urlParams.useStandardUuid == false) { + if (urlParams.useStandardUuid === false) { // novatiq standard uuid(like) format uuidFormat = uuidFormat + 1e3; } @@ -207,7 +220,7 @@ export const novatiqIdSubmodule = { let sharedId = null; if (this.useSharedId(configParams)) { let cookieOrStorageID = this.getCookieOrStorageID(configParams); - const storage = getStorageManager({gvlid: this.gvlid, moduleName: 'pubCommonId'}); + const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); // first check local storage if (storage.hasLocalStorage()) { diff --git a/modules/oguryBidAdapter.js b/modules/oguryBidAdapter.js index da62ce5c0a1..d09320c00fe 100644 --- a/modules/oguryBidAdapter.js +++ b/modules/oguryBidAdapter.js @@ -11,7 +11,7 @@ const DEFAULT_TIMEOUT = 1000; const BID_HOST = 'https://mweb-hb.presage.io/api/header-bidding-request'; const TIMEOUT_MONITORING_HOST = 'https://ms-ads-monitoring-events.presage.io'; const MS_COOKIE_SYNC_DOMAIN = 'https://ms-cookie-sync.presage.io'; -const ADAPTER_VERSION = '1.4.0'; +const ADAPTER_VERSION = '1.4.1'; function getClientWidth() { const documentElementClientWidth = window.top.document.documentElement.clientWidth @@ -90,7 +90,8 @@ function buildRequests(validBidRequests, bidderRequest) { }, device: { w: getClientWidth(), - h: getClientHeight() + h: getClientHeight(), + pxratio: window.devicePixelRatio } }; diff --git a/modules/onetagBidAdapter.js b/modules/onetagBidAdapter.js index b5217e77cd6..2ae879cdcbc 100644 --- a/modules/onetagBidAdapter.js +++ b/modules/onetagBidAdapter.js @@ -13,7 +13,7 @@ const USER_SYNC_ENDPOINT = 'https://onetag-sys.com/usync/'; const BIDDER_CODE = 'onetag'; const GVLID = 241; -const storage = getStorageManager({ gvlid: GVLID, bidderCode: BIDDER_CODE }); +const storage = getStorageManager({ bidderCode: BIDDER_CODE }); /** * Determines whether or not the given bid request is valid. @@ -61,6 +61,12 @@ function buildRequests(validBidRequests, bidderRequest) { consentRequired: bidderRequest.gdprConsent.gdprApplies }; } + if (bidderRequest && bidderRequest.gppConsent) { + payload.gppConsent = { + consentString: bidderRequest.gppConsent.gppString, + applicableSections: bidderRequest.gppConsent.applicableSections + } + } if (bidderRequest && bidderRequest.uspConsent) { payload.usPrivacy = bidderRequest.uspConsent; } @@ -340,7 +346,7 @@ function getSizes(sizes) { return ret; } -function getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent) { +function getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent, gppConsent) { let syncs = []; let params = ''; if (gdprConsent) { @@ -351,6 +357,11 @@ function getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent) { params += '&gdpr_consent=' + gdprConsent.consentString; } } + if (gppConsent) { + if (typeof gppConsent.gppString === 'string') { + params += '&gpp_consent=' + gppConsent.gppString; + } + } if (uspConsent && typeof uspConsent === 'string') { params += '&us_privacy=' + uspConsent; } diff --git a/modules/onomagicBidAdapter.js b/modules/onomagicBidAdapter.js index d99455f3f73..edab625e541 100644 --- a/modules/onomagicBidAdapter.js +++ b/modules/onomagicBidAdapter.js @@ -13,7 +13,6 @@ import { } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER} from '../src/mediaTypes.js'; -import {config} from '../src/config.js'; const BIDDER_CODE = 'onomagic'; const URL = 'https://bidder.onomagic.com/hb'; @@ -80,7 +79,7 @@ function buildRequests(bidReqs, bidderRequest) { w: screen.width, h: screen.height }, - tmax: config.getConfig('bidderTimeout') + tmax: bidderRequest?.timeout }; return { diff --git a/modules/openxBidAdapter.js b/modules/openxBidAdapter.js index 14525cd0cfc..03423a028b4 100644 --- a/modules/openxBidAdapter.js +++ b/modules/openxBidAdapter.js @@ -1,604 +1,238 @@ -import { - _each, - _map, - convertTypes, - deepAccess, - deepSetValue, - inIframe, - isArray, - parseSizesInput, - parseUrl -} from '../src/utils.js'; import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; +import * as utils from '../src/utils.js'; +import {mergeDeep} from '../src/utils.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; -import {includes} from '../src/polyfill.js'; +import {ortbConverter} from '../libraries/ortbConverter/converter.js'; -const SUPPORTED_AD_TYPES = [BANNER, VIDEO]; -const VIDEO_TARGETING = ['startdelay', 'mimes', 'minduration', 'maxduration', - 'startdelay', 'skippable', 'playbackmethod', 'api', 'protocols', 'boxingallowed', - 'linearity', 'delivery', 'protocol', 'placement', 'minbitrate', 'maxbitrate']; -const BIDDER_CODE = 'openx'; -const BIDDER_CONFIG = 'hb_pb'; -const BIDDER_VERSION = '3.0.3'; - -const DEFAULT_CURRENCY = 'USD'; - -export const USER_ID_CODE_TO_QUERY_ARG = { - britepoolid: 'britepoolid', // BritePool ID - criteoId: 'criteoid', // CriteoID - fabrickId: 'nuestarid', // Fabrick ID by Nuestar - hadronId: 'audigentid', // Hadron ID from Audigent - id5id: 'id5id', // ID5 ID - idl_env: 'lre', // LiveRamp IdentityLink - IDP: 'zeotapid', // zeotapIdPlus ID+ - idxId: 'idxid', // idIDx, - intentIqId: 'intentiqid', // IntentIQ ID - lipb: 'lipbid', // LiveIntent ID - lotamePanoramaId: 'lotameid', // Lotame Panorama ID - merkleId: 'merkleid', // Merkle ID - netId: 'netid', // netID - parrableId: 'parrableid', // Parrable ID - pubcid: 'pubcid', // PubCommon ID - quantcastId: 'quantcastid', // Quantcast ID - tapadId: 'tapadid', // Tapad Id - tdid: 'ttduuid', // The Trade Desk Unified ID - uid2: 'uid2', // Unified ID 2.0 - admixerId: 'admixerid', // AdMixer ID - deepintentId: 'deepintentid', // DeepIntent ID - dmdId: 'dmdid', // DMD Marketing Corp ID - nextrollId: 'nextrollid', // NextRoll ID - novatiq: 'novatiqid', // Novatiq ID - mwOpenLinkId: 'mwopenlinkid', // MediaWallah OpenLink ID - dapId: 'dapid', // Akamai DAP ID - amxId: 'amxid', // AMX RTB ID - kpuid: 'kpuid', // Kinesso ID - publinkId: 'publinkid', // Publisher Link - naveggId: 'naveggid', // Navegg ID - imuid: 'imuid', // IM-UID by Intimate Merger - adtelligentId: 'adtelligentid' // Adtelligent ID +const bidderConfig = 'hb_pb_ortb'; +const bidderVersion = '2.0'; +export const REQUEST_URL = 'https://rtb.openx.net/openrtbb/prebidjs'; +export const SYNC_URL = 'https://u.openx.net/w/1.0/pd'; +export const DEFAULT_PH = '2d1251ae-7f3a-47cf-bd2a-2f288854a0ba'; +export const spec = { + code: 'openx', + supportedMediaTypes: [BANNER, VIDEO], + isBidRequestValid, + buildRequests, + interpretResponse, + getUserSyncs, + transformBidParams }; -export const spec = { - code: BIDDER_CODE, - gvlid: 69, - supportedMediaTypes: SUPPORTED_AD_TYPES, - isBidRequestValid: function (bidRequest) { - const hasDelDomainOrPlatform = bidRequest.params.delDomain || bidRequest.params.platform; - if (deepAccess(bidRequest, 'mediaTypes.banner') && hasDelDomainOrPlatform) { - return !!bidRequest.params.unit || deepAccess(bidRequest, 'mediaTypes.banner.sizes.length') > 0; - } +registerBidder(spec); - return !!(bidRequest.params.unit && hasDelDomainOrPlatform); +const converter = ortbConverter({ + context: { + netRevenue: true, + ttl: 300 }, - buildRequests: function (bidRequests, bidderRequest) { - if (bidRequests.length === 0) { - return []; - } - - let requests = []; - let [videoBids, bannerBids] = partitionByVideoBids(bidRequests); - - // build banner requests - if (bannerBids.length > 0) { - requests.push(buildOXBannerRequest(bannerBids, bidderRequest)); - } - // build video requests - if (videoBids.length > 0) { - videoBids.forEach(videoBid => { - requests.push(buildOXVideoRequest(videoBid, bidderRequest)) - }); + imp(buildImp, bidRequest, context) { + const imp = buildImp(bidRequest, context); + mergeDeep(imp, { + tagid: bidRequest.params.unit, + ext: { + divid: bidRequest.adUnitCode + } + }); + if (bidRequest.params.customParams) { + utils.deepSetValue(imp, 'ext.customParams', bidRequest.params.customParams); } - - return requests; - }, - interpretResponse: function ({body: oxResponseObj}, serverRequest) { - let mediaType = getMediaTypeFromRequest(serverRequest); - - return mediaType === VIDEO ? createVideoBidResponses(oxResponseObj, serverRequest.payload) - : createBannerBidResponses(oxResponseObj, serverRequest.payload); - }, - getUserSyncs: function (syncOptions, responses, gdprConsent, uspConsent) { - if (syncOptions.iframeEnabled || syncOptions.pixelEnabled) { - let pixelType = syncOptions.iframeEnabled ? 'iframe' : 'image'; - let url = deepAccess(responses, '0.body.ads.pixels') || - deepAccess(responses, '0.body.pixels') || - generateDefaultSyncUrl(gdprConsent, uspConsent); - - return [{ - type: pixelType, - url: url - }]; + if (bidRequest.params.customFloor && !imp.bidfloor) { + imp.bidfloor = bidRequest.params.customFloor; } + return imp; }, - transformBidParams: function(params, isOpenRtb) { - return convertTypes({ - 'unit': 'string', - 'customFloor': 'number' - }, params); - } -}; - -function generateDefaultSyncUrl(gdprConsent, uspConsent) { - let url = 'https://u.openx.net/w/1.0/pd'; - let queryParamStrings = []; - - if (gdprConsent) { - queryParamStrings.push('gdpr=' + (gdprConsent.gdprApplies ? 1 : 0)); - queryParamStrings.push('gdpr_consent=' + encodeURIComponent(gdprConsent.consentString || '')); - } - - // CCPA - if (uspConsent) { - queryParamStrings.push('us_privacy=' + encodeURIComponent(uspConsent)); - } - - return `${url}${queryParamStrings.length > 0 ? '?' + queryParamStrings.join('&') : ''}`; -} - -function isVideoRequest(bidRequest) { - return (deepAccess(bidRequest, 'mediaTypes.video') && !deepAccess(bidRequest, 'mediaTypes.banner')) || bidRequest.mediaType === VIDEO; -} - -function createBannerBidResponses(oxResponseObj, {bids, startTime}) { - let adUnits = oxResponseObj.ads.ad; - let bidResponses = []; - for (let i = 0; i < adUnits.length; i++) { - let adUnit = adUnits[i]; - let adUnitIdx = parseInt(adUnit.idx, 10); - let bidResponse = {}; - - bidResponse.requestId = bids[adUnitIdx].bidId; - - if (adUnit.pub_rev) { - bidResponse.cpm = Number(adUnit.pub_rev) / 1000; - } else { - // No fill, do not add the bidresponse - continue; - } - let creative = adUnit.creative[0]; - if (creative) { - bidResponse.width = creative.width; - bidResponse.height = creative.height; + request(buildRequest, imps, bidderRequest, context) { + const req = buildRequest(imps, bidderRequest, context); + mergeDeep(req, { + at: 1, + ext: { + bc: `${bidderConfig}_${bidderVersion}` + } + }) + const bid = context.bidRequests[0]; + if (bid.params.coppa) { + utils.deepSetValue(req, 'regs.coppa', 1); } - bidResponse.creativeId = creative.id; - bidResponse.ad = adUnit.html; - if (adUnit.deal_id) { - bidResponse.dealId = adUnit.deal_id; + if (bid.params.doNotTrack) { + utils.deepSetValue(req, 'device.dnt', 1); } - // default 5 mins - bidResponse.ttl = 300; - // true is net, false is gross - bidResponse.netRevenue = true; - bidResponse.currency = adUnit.currency; - - // additional fields to add - if (adUnit.tbd) { - bidResponse.tbd = adUnit.tbd; + if (bid.params.platform) { + utils.deepSetValue(req, 'ext.platform', bid.params.platform); } - bidResponse.ts = adUnit.ts; - - bidResponse.meta = {}; - if (adUnit.brand_id) { - bidResponse.meta.brandId = adUnit.brand_id; + if (bid.params.delDomain) { + utils.deepSetValue(req, 'ext.delDomain', bid.params.delDomain); } - - if (adUnit.adomain && length(adUnit.adomain) > 0) { - bidResponse.meta.advertiserDomains = adUnit.adomain; - } else { - bidResponse.meta.advertiserDomains = []; + if (bid.params.response_template_name) { + utils.deepSetValue(req, 'ext.response_template_name', bid.params.response_template_name); } - - if (adUnit.adv_id) { - bidResponse.meta.dspid = adUnit.adv_id; + if (bid.params.test) { + req.test = 1 } - - bidResponses.push(bidResponse); - } - return bidResponses; -} - -function getViewportDimensions(isIfr) { - let width; - let height; - let tWin = window; - let tDoc = document; - let docEl = tDoc.documentElement; - let body; - - if (isIfr) { - try { - tWin = window.top; - tDoc = window.top.document; - } catch (e) { - return; + return req; + }, + bidResponse(buildBidResponse, bid, context) { + const bidResponse = buildBidResponse(bid, context); + if (bid.ext) { + bidResponse.meta.networkId = bid.ext.dsp_id; + bidResponse.meta.advertiserId = bid.ext.buyer_id; + bidResponse.meta.brandId = bid.ext.brand_id; + } + const {ortbResponse} = context; + if (ortbResponse.ext && ortbResponse.ext.paf) { + bidResponse.meta.paf = Object.assign({}, ortbResponse.ext.paf); + bidResponse.meta.paf.content_id = utils.deepAccess(bid, 'ext.paf.content_id'); + } + return bidResponse; + }, + response(buildResponse, bidResponses, ortbResponse, context) { + // pass these from request to the responses for use in userSync + const {ortbRequest} = context; + if (ortbRequest.ext) { + if (ortbRequest.ext.delDomain) { + utils.deepSetValue(ortbResponse, 'ext.delDomain', ortbRequest.ext.delDomain); + } + if (ortbRequest.ext.platform) { + utils.deepSetValue(ortbResponse, 'ext.platform', ortbRequest.ext.platform); + } } - body = tDoc.body; - - width = tWin.innerWidth || docEl.clientWidth || body.clientWidth; - height = tWin.innerHeight || docEl.clientHeight || body.clientHeight; - } else { - width = tWin.innerWidth || docEl.clientWidth; - height = tWin.innerHeight || docEl.clientHeight; - } - - return `${width}x${height}`; -} - -function formatCustomParms(customKey, customParams) { - let value = customParams[customKey]; - if (isArray(value)) { - // if value is an array, join them with commas first - value = value.join(','); - } - // return customKey=customValue format, escaping + to . and / to _ - return (customKey.toLowerCase() + '=' + value.toLowerCase()).replace('+', '.').replace('/', '_') -} - -function partitionByVideoBids(bidRequests) { - return bidRequests.reduce(function (acc, bid) { - // Fallback to banner ads if nothing specified - if (isVideoRequest(bid)) { - acc[0].push(bid); + const response = buildResponse(bidResponses, ortbResponse, context); + // TODO: we may want to standardize this and move fledge logic to ortbConverter + let fledgeAuctionConfigs = utils.deepAccess(ortbResponse, 'ext.fledge_auction_configs'); + if (fledgeAuctionConfigs) { + fledgeAuctionConfigs = Object.entries(fledgeAuctionConfigs).map(([bidId, cfg]) => { + return { + bidId, + config: Object.assign({ + auctionSignals: {}, + }, cfg) + } + }); + return { + bids: response.bids, + fledgeAuctionConfigs, + } } else { - acc[1].push(bid); - } - return acc; - }, [[], []]); -} - -function getMediaTypeFromRequest(serverRequest) { - return /avjp$/.test(serverRequest.url) ? VIDEO : BANNER; -} - -function buildCommonQueryParamsFromBids(bids, bidderRequest) { - const isInIframe = inIframe(); - let defaultParams; - - defaultParams = { - ju: bidderRequest.refererInfo.page, - ch: document.charSet || document.characterSet, - res: `${screen.width}x${screen.height}x${screen.colorDepth}`, - ifr: isInIframe, - tz: new Date().getTimezoneOffset(), - tws: getViewportDimensions(isInIframe), - be: 1, - bc: bids[0].params.bc || `${BIDDER_CONFIG}_${BIDDER_VERSION}`, - dddid: _map(bids, bid => bid.transactionId).join(','), - nocache: new Date().getTime() - }; - - const userDataSegments = buildFpdQueryParams('user.data', bidderRequest.ortb2); - if (userDataSegments.length > 0) { - defaultParams.sm = userDataSegments; - } - - const siteContentDataSegments = buildFpdQueryParams('site.content.data', bidderRequest.ortb2); - if (siteContentDataSegments.length > 0) { - defaultParams.scsm = siteContentDataSegments; - } - - if (bids[0].params.platform) { - defaultParams.ph = bids[0].params.platform; - } - - if (bidderRequest.gdprConsent) { - let gdprConsentConfig = bidderRequest.gdprConsent; - - if (gdprConsentConfig.consentString !== undefined) { - defaultParams.gdpr_consent = gdprConsentConfig.consentString; - } - - if (gdprConsentConfig.gdprApplies !== undefined) { - defaultParams.gdpr = gdprConsentConfig.gdprApplies ? 1 : 0; - } - - if (config.getConfig('consentManagement.cmpApi') === 'iab') { - defaultParams.x_gdpr_f = 1; + return response.bids } - } - - if (bidderRequest && bidderRequest.uspConsent) { - defaultParams.us_privacy = bidderRequest.uspConsent; - } - - // normalize publisher common id - if (deepAccess(bids[0], 'crumbs.pubcid')) { - deepSetValue(bids[0], 'userId.pubcid', deepAccess(bids[0], 'crumbs.pubcid')); - } - defaultParams = appendUserIdsToQueryParams(defaultParams, bids[0].userId); - - // supply chain support - if (bids[0].schain) { - defaultParams.schain = serializeSupplyChain(bids[0].schain); - } - - return defaultParams; -} - -function buildFpdQueryParams(fpdPath, ortb2) { - const firstPartyData = deepAccess(ortb2, fpdPath); - if (!Array.isArray(firstPartyData) || !firstPartyData.length) { - return ''; - } - const fpd = firstPartyData - .filter( - data => (Array.isArray(data.segment) && - data.segment.length > 0 && - data.name !== undefined && - data.name.length > 0) - ) - .reduce((acc, data) => { - const name = typeof data.ext === 'object' && data.ext.segtax ? `${data.name}/${data.ext.segtax}` : data.name; - acc[name] = (acc[name] || []).concat(data.segment.map(seg => seg.id)); - return acc; - }, {}) - return Object.keys(fpd) - .map((name, _) => name + ':' + fpd[name].join('|')) - .join(',') -} - -function appendUserIdsToQueryParams(queryParams, userIds) { - _each(userIds, (userIdObjectOrValue, userIdProviderKey) => { - const key = USER_ID_CODE_TO_QUERY_ARG[userIdProviderKey]; - - if (USER_ID_CODE_TO_QUERY_ARG.hasOwnProperty(userIdProviderKey)) { - switch (userIdProviderKey) { - case 'merkleId': - queryParams[key] = userIdObjectOrValue.id; - break; - case 'uid2': - queryParams[key] = userIdObjectOrValue.id; - break; - case 'lipb': - queryParams[key] = userIdObjectOrValue.lipbid; - if (Array.isArray(userIdObjectOrValue.segments) && userIdObjectOrValue.segments.length > 0) { - const liveIntentSegments = 'liveintent:' + userIdObjectOrValue.segments.join('|'); - queryParams.sm = `${queryParams.sm ? queryParams.sm + ',' : ''}${liveIntentSegments}`; + }, + overrides: { + imp: { + bidfloor(setBidFloor, imp, bidRequest, context) { + // enforce floors should always be in USD + // TODO: does it make sense that request.cur can be any currency, but request.imp[].bidfloorcur must be USD? + const floor = {}; + setBidFloor(floor, bidRequest, {...context, currency: 'USD'}); + if (floor.bidfloorcur === 'USD') { + Object.assign(imp, floor); + } + }, + video(orig, imp, bidRequest, context) { + if (FEATURES.VIDEO) { + // `orig` is the video imp processor, which looks at bidRequest.mediaTypes[VIDEO] + // to populate imp.video + // alter its input `bidRequest` to also pick up parameters from `bidRequest.params` + let videoParams = bidRequest.mediaTypes[VIDEO]; + if (videoParams) { + videoParams = Object.assign({}, videoParams, bidRequest.params.video); + bidRequest = {...bidRequest, mediaTypes: {[VIDEO]: videoParams}} + } + orig(imp, bidRequest, context); + if (imp.video && videoParams?.context === 'outstream') { + imp.video.placement = imp.video.placement || 4; } - break; - case 'parrableId': - queryParams[key] = userIdObjectOrValue.eid; - break; - case 'id5id': - queryParams[key] = userIdObjectOrValue.uid; - break; - case 'novatiq': - queryParams[key] = userIdObjectOrValue.snowflake; - break; - default: - queryParams[key] = userIdObjectOrValue; + } } } - }); - - return queryParams; -} - -function serializeSupplyChain(supplyChain) { - return `${supplyChain.ver},${supplyChain.complete}!${serializeSupplyChainNodes(supplyChain.nodes)}`; -} - -function serializeSupplyChainNodes(supplyChainNodes) { - const supplyChainNodePropertyOrder = ['asi', 'sid', 'hp', 'rid', 'name', 'domain']; + } +}); - return supplyChainNodes.map(supplyChainNode => { - return supplyChainNodePropertyOrder.map(property => supplyChainNode[property] || '') - .join(','); - }).join('!'); +function transformBidParams(params, isOpenRtb) { + return utils.convertTypes({ + 'unit': 'string', + 'customFloor': 'number' + }, params); } -function buildOXBannerRequest(bids, bidderRequest) { - let customParamsForAllBids = []; - let hasCustomParam = false; - let queryParams = buildCommonQueryParamsFromBids(bids, bidderRequest); - let auids = _map(bids, bid => bid.params.unit); +function isBidRequestValid(bidRequest) { + const hasDelDomainOrPlatform = bidRequest.params.delDomain || + bidRequest.params.platform; - queryParams.aus = _map(bids, bid => parseSizesInput(bid.mediaTypes.banner.sizes).join(',')).join('|'); - queryParams.divids = _map(bids, bid => encodeURIComponent(bid.adUnitCode)).join(','); - // gpid - queryParams.aucs = _map(bids, function (bid) { - let gpid = deepAccess(bid, 'ortb2Imp.ext.data.pbadslot'); - return encodeURIComponent(gpid || '') - }).join(','); - - if (auids.some(auid => auid)) { - queryParams.auid = auids.join(','); - } - - if (bids.some(bid => bid.params.doNotTrack)) { - queryParams.ns = 1; + if (utils.deepAccess(bidRequest, 'mediaTypes.banner') && + hasDelDomainOrPlatform) { + return !!bidRequest.params.unit || + utils.deepAccess(bidRequest, 'mediaTypes.banner.sizes.length') > 0; } - if (config.getConfig('coppa') === true || bids.some(bid => bid.params.coppa)) { - queryParams.tfcd = 1; - } + return !!(bidRequest.params.unit && hasDelDomainOrPlatform); +} - bids.forEach(function (bid) { - if (bid.params.customParams) { - let customParamsForBid = _map(Object.keys(bid.params.customParams), customKey => formatCustomParms(customKey, bid.params.customParams)); - let formattedCustomParams = window.btoa(customParamsForBid.join('&')); - hasCustomParam = true; - customParamsForAllBids.push(formattedCustomParams); - } else { - customParamsForAllBids.push(''); - } +function buildRequests(bids, bidderRequest) { + let videoBids = bids.filter(bid => isVideoBid(bid)); + let bannerBids = bids.filter(bid => isBannerBid(bid)); + let requests = bannerBids.length ? [createRequest(bannerBids, bidderRequest, BANNER)] : []; + videoBids.forEach(bid => { + requests.push(createRequest([bid], bidderRequest, VIDEO)); }); - if (hasCustomParam) { - queryParams.tps = customParamsForAllBids.join(','); - } - - enrichQueryWithFloors(queryParams, BANNER, bids); - - let url = queryParams.ph - ? `https://u.openx.net/w/1.0/arj` - : `https://${bids[0].params.delDomain}/w/1.0/arj`; - - return { - method: 'GET', - url: url, - data: queryParams, - payload: {'bids': bids, 'startTime': new Date()} - }; + return requests; } -function buildOXVideoRequest(bid, bidderRequest) { - let oxVideoParams = generateVideoParameters(bid, bidderRequest); - let url = oxVideoParams.ph - ? `https://u.openx.net/v/1.0/avjp` - : `https://${bid.params.delDomain}/v/1.0/avjp`; +function createRequest(bidRequests, bidderRequest, mediaType) { return { - method: 'GET', - url: url, - data: oxVideoParams, - payload: {'bid': bid, 'startTime': new Date()} - }; -} - -function generateVideoParameters(bid, bidderRequest) { - const videoMediaType = deepAccess(bid, `mediaTypes.video`); - let queryParams = buildCommonQueryParamsFromBids([bid], bidderRequest); - let oxVideoConfig = deepAccess(bid, 'params.video') || {}; - let context = deepAccess(bid, 'mediaTypes.video.context'); - let playerSize = deepAccess(bid, 'mediaTypes.video.playerSize'); - let width; - let height; - - // normalize config for video size - if (isArray(bid.sizes) && bid.sizes.length === 2 && !isArray(bid.sizes[0])) { - width = parseInt(bid.sizes[0], 10); - height = parseInt(bid.sizes[1], 10); - } else if (isArray(bid.sizes) && isArray(bid.sizes[0]) && bid.sizes[0].length === 2) { - width = parseInt(bid.sizes[0][0], 10); - height = parseInt(bid.sizes[0][1], 10); - } else if (isArray(playerSize) && playerSize.length === 2) { - width = parseInt(playerSize[0], 10); - height = parseInt(playerSize[1], 10); - } - - let openRtbParams = {w: width, h: height}; - - // legacy openrtb params could be in video, openrtb, or video.openrtb - let legacyParams = bid.params.video || bid.params.openrtb || {}; - if (legacyParams.openrtb) { - legacyParams = legacyParams.openrtb; - } - // support for video object or full openrtb object - if (isArray(legacyParams.imp)) { - legacyParams = legacyParams.imp[0].video; + method: 'POST', + url: config.getConfig('openxOrtbUrl') || REQUEST_URL, + data: converter.toORTB({bidRequests, bidderRequest, context: {mediaType}}) } - Object.keys(legacyParams) - .filter(param => includes(VIDEO_TARGETING, param)) - .forEach(param => openRtbParams[param] = legacyParams[param]); - - // 5.0 openrtb video params - Object.keys(videoMediaType) - .filter(param => includes(VIDEO_TARGETING, param)) - .forEach(param => openRtbParams[param] = videoMediaType[param]); - - let openRtbReq = { - imp: [ - { - video: openRtbParams - } - ] - }; - - queryParams['openrtb'] = JSON.stringify(openRtbReq); - - queryParams.auid = bid.params.unit; - // override prebid config with openx config if available - queryParams.vwd = width || oxVideoConfig.vwd; - queryParams.vht = height || oxVideoConfig.vht; - - if (context === 'outstream') { - queryParams.vos = '101'; - } - - if (oxVideoConfig.mimes) { - queryParams.vmimes = oxVideoConfig.mimes; - } - - if (bid.params.test) { - queryParams.vtest = 1; - } - - let gpid = deepAccess(bid, 'ortb2Imp.ext.data.pbadslot'); - if (gpid) { - queryParams.aucs = encodeURIComponent(gpid); - } - - // each video bid makes a separate request - enrichQueryWithFloors(queryParams, VIDEO, [bid]); - - return queryParams; } -function createVideoBidResponses(response, {bid, startTime}) { - let bidResponses = []; - - if (response !== undefined && response.vastUrl !== '' && response.pub_rev > 0) { - let vastQueryParams = parseUrl(response.vastUrl).search || {}; - let bidResponse = {}; - bidResponse.requestId = bid.bidId; - if (response.deal_id) { - bidResponse.dealId = response.deal_id; - } - // default 5 mins - bidResponse.ttl = 300; - // true is net, false is gross - bidResponse.netRevenue = true; - bidResponse.currency = response.currency; - bidResponse.cpm = parseInt(response.pub_rev, 10) / 1000; - bidResponse.width = parseInt(response.width, 10); - bidResponse.height = parseInt(response.height, 10); - bidResponse.creativeId = response.adid; - bidResponse.vastUrl = response.vastUrl; - bidResponse.mediaType = VIDEO; +function isVideoBid(bid) { + return utils.deepAccess(bid, 'mediaTypes.video'); +} - // enrich adunit with vast parameters - response.ph = vastQueryParams.ph; - response.colo = vastQueryParams.colo; - response.ts = vastQueryParams.ts; +function isBannerBid(bid) { + return utils.deepAccess(bid, 'mediaTypes.banner') || !isVideoBid(bid); +} - bidResponses.push(bidResponse); +function interpretResponse(resp, req) { + if (!resp.body) { + resp.body = {nbr: 0}; } - - return bidResponses; + return converter.fromORTB({request: req.data, response: resp.body}); } -function enrichQueryWithFloors(queryParams, mediaType, bids) { - let customFloorsForAllBids = []; - let hasCustomFloor = false; - bids.forEach(function (bid) { - let floor = getBidFloor(bid, mediaType); - - if (floor) { - customFloorsForAllBids.push(floor); - hasCustomFloor = true; +/** + * @param syncOptions + * @param responses + * @param gdprConsent + * @param uspConsent + * @return {{type: (string), url: (*|string)}[]} + */ +function getUserSyncs(syncOptions, responses, gdprConsent, uspConsent) { + if (syncOptions.iframeEnabled || syncOptions.pixelEnabled) { + let pixelType = syncOptions.iframeEnabled ? 'iframe' : 'image'; + let queryParamStrings = []; + let syncUrl = SYNC_URL; + if (gdprConsent) { + queryParamStrings.push('gdpr=' + (gdprConsent.gdprApplies ? 1 : 0)); + queryParamStrings.push('gdpr_consent=' + encodeURIComponent(gdprConsent.consentString || '')); + } + if (uspConsent) { + queryParamStrings.push('us_privacy=' + encodeURIComponent(uspConsent)); + } + if (responses.length > 0 && responses[0].body && responses[0].body.ext) { + const ext = responses[0].body.ext; + if (ext.delDomain) { + syncUrl = `https://${ext.delDomain}/w/1.0/pd` + } else if (ext.platform) { + queryParamStrings.push('ph=' + ext.platform) + } } else { - customFloorsForAllBids.push(0); + queryParamStrings.push('ph=' + DEFAULT_PH) } - }); - if (hasCustomFloor) { - queryParams.aumfs = customFloorsForAllBids.join(','); + return [{ + type: pixelType, + url: `${syncUrl}${queryParamStrings.length > 0 ? '?' + queryParamStrings.join('&') : ''}` + }]; } } - -function getBidFloor(bidRequest, mediaType) { - let floorInfo = {}; - const currency = config.getConfig('currency.adServerCurrency') || DEFAULT_CURRENCY; - - if (typeof bidRequest.getFloor === 'function') { - floorInfo = bidRequest.getFloor({ - currency: currency, - mediaType: mediaType, - size: '*' - }); - } - let floor = floorInfo.floor || bidRequest.params.customFloor || 0; - - return Math.round(floor * 1000); // normalize to micro currency -} - -registerBidder(spec); diff --git a/modules/openxBidAdapter.md b/modules/openxBidAdapter.md index 0690bf6b4fc..a39aa1580cd 100644 --- a/modules/openxBidAdapter.md +++ b/modules/openxBidAdapter.md @@ -9,30 +9,31 @@ Maintainer: team-openx@openx.com # Description Module that connects to OpenX's demand sources. -Note there is an updated version of the OpenX bid adapter called openxOrtbBidAdapter. -Publishers are welcome to test the other adapter and give feedback. Please note you should only include either openxBidAdapter or openxOrtbBidAdapter in your build. +Note that this adapter mirrors openxOrtbBidAdapter and any updates must be +completed in both adapters. +openxOrtbBidAdapter will be removed in a future release and should not be used. +Please note you should only include either openxBidAdapter or openxOrtbBidAdapter in your build. # Bid Parameters ## Banner -| Name | Scope | Type | Description | Example | -|---------------------------------|----------|---------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------| -| `delDomain` ~~or `platform`~~** | required | String | OpenX delivery domain provided by your OpenX representative. | "PUBLISHER-d.openx.net" | -| `unit` | required | String | OpenX ad unit ID provided by your OpenX representative. | "1611023122" | -| `customParams` | optional | Object | User-defined targeting key-value pairs. customParams applies to a specific unit. | `{key1: "v1", key2: ["v2","v3"]}` | -| `customFloor` | optional | Number | Minimum price in USD. customFloor applies to a specific unit. For example, use the following value to set a $1.50 floor: 1.50

**WARNING:**
Misuse of this parameter can impact revenue

Note: OpenX suggests using the [Price Floor Module](https://docs.prebid.org/dev-docs/modules/floors.html) instead of customFloor. The Price Floor Module is prioritized over customFloor if both are present. | 1.50 | -| `doNotTrack` | optional | Boolean | Prevents advertiser from using data for this user.

**WARNING:**
Request-level setting. May impact revenue. | true | -| `coppa` | optional | Boolean | Enables Child's Online Privacy Protection Act (COPPA) regulations. | true | - -** platform is deprecated. Please use delDomain instead. If you have any questions please contact your representative. +| Name | Scope | Type | Description | Example +| ---- | ----- | ---- | ----------- | ------- +| `delDomain` or `platform` | required | String | OpenX delivery domain or platform id provided by your OpenX representative. | "PUBLISHER-d.openx.net" or "555not5a-real-plat-form-id0123456789" +| `unit` | required | String | OpenX ad unit ID provided by your OpenX representative. | "1611023122" +| `customParams` | optional | Object | User-defined targeting key-value pairs. customParams applies to a specific unit. | `{key1: "v1", key2: ["v2","v3"]}` +| `customFloor` | optional | Number | Minimum price in USD. customFloor applies to a specific unit. For example, use the following value to set a $1.50 floor: 1.50

**WARNING:**
Misuse of this parameter can impact revenue | 1.50 +| `doNotTrack` | optional | Boolean | Prevents advertiser from using data for this user.

**WARNING:**
Request-level setting. May impact revenue. | true +| `coppa` | optional | Boolean | Enables Child's Online Privacy Protection Act (COPPA) regulations. Use of `pbjs.setConfig({coppa: true});` is now preferred. | true ## Video -| Name | Scope | Type | Description | Example | -|-------------|----------|--------------------|--------------------------------------------------------------|----------------------------------------------------------------| -| `unit` | required | String | OpenX ad unit ID provided by your OpenX representative. | "1611023122" | -| `delDomain` | required | String | OpenX delivery domain provided by your OpenX representative. | "PUBLISHER-d.openx.net" | -| `openrtb` | optional | OpenRTB Impression | An OpenRtb Impression with Video subtype properties | `{ imp: [{ video: {mimes: ['video/x-ms-wmv, video/mp4']} }] }` | +| Name | Scope | Type | Description | Example +| ---- | ----- | ---- | ----------- | ------- +| `unit` | required | String | OpenX ad unit ID provided by your OpenX representative. | "1611023122" +| `delDomain` | required | String | OpenX delivery domain provided by your OpenX representative. | "PUBLISHER-d.openx.net" +| `video` | optional | OpenRTB video subtypes | Use of adUnit.mediaTypes.video is now preferred. | `{ video: {mimes: ['video/mp4']}` + # Example ```javascript @@ -70,7 +71,8 @@ var adUnits = [ mediaTypes: { video: { playerSize: [640, 480], - context: 'instream' + context: 'instream', + mimes: ['video/x-ms-wmv, video/mp4'] } }, bids: [{ @@ -79,10 +81,10 @@ var adUnits = [ unit: '1611023124', delDomain: 'PUBLISHER-d.openx.net', video: { - mimes: ['video/x-ms-wmv, video/mp4'] + mimes: ['video/x-ms-wmv, video/mp4'] // mediaTypes.video preferred } } - }] + }]p } ]; ``` diff --git a/modules/openxOrtbBidAdapter.js b/modules/openxOrtbBidAdapter.js index 200d2cf0fed..5afee034d5f 100644 --- a/modules/openxOrtbBidAdapter.js +++ b/modules/openxOrtbBidAdapter.js @@ -29,13 +29,6 @@ const converter = ortbConverter({ }, imp(buildImp, bidRequest, context) { const imp = buildImp(bidRequest, context); - if (bidRequest.mediaTypes[VIDEO]?.context === 'outstream') { - imp.video.placement = imp.video.placement || 4; - } - if (imp.ext?.ae && !context.bidderRequest.fledgeEnabled) { - // TODO: we may want to standardize this and move fledge logic to ortbConverter - delete imp.ext.ae; - } mergeDeep(imp, { tagid: bidRequest.params.unit, ext: { @@ -59,6 +52,9 @@ const converter = ortbConverter({ } }) const bid = context.bidRequests[0]; + if (bid.params.coppa) { + utils.deepSetValue(req, 'regs.coppa', 1); + } if (bid.params.doNotTrack) { utils.deepSetValue(req, 'device.dnt', 1); } @@ -106,10 +102,12 @@ const converter = ortbConverter({ let fledgeAuctionConfigs = utils.deepAccess(ortbResponse, 'ext.fledge_auction_configs'); if (fledgeAuctionConfigs) { fledgeAuctionConfigs = Object.entries(fledgeAuctionConfigs).map(([bidId, cfg]) => { - return Object.assign({ + return { bidId, - auctionSignals: {} - }, cfg); + config: Object.assign({ + auctionSignals: {}, + }, cfg) + } }); return { bids: response.bids, @@ -129,6 +127,22 @@ const converter = ortbConverter({ if (floor.bidfloorcur === 'USD') { Object.assign(imp, floor); } + }, + video(orig, imp, bidRequest, context) { + if (FEATURES.VIDEO) { + // `orig` is the video imp processor, which looks at bidRequest.mediaTypes[VIDEO] + // to populate imp.video + // alter its input `bidRequest` to also pick up parameters from `bidRequest.params` + let videoParams = bidRequest.mediaTypes[VIDEO]; + if (videoParams) { + videoParams = Object.assign({}, videoParams, bidRequest.params.video); + bidRequest = {...bidRequest, mediaTypes: {[VIDEO]: videoParams}} + } + orig(imp, bidRequest, context); + if (imp.video && videoParams?.context === 'outstream') { + imp.video.placement = imp.video.placement || 4; + } + } } } } diff --git a/modules/openxOrtbBidAdapter.md b/modules/openxOrtbBidAdapter.md index fd926b27b9f..b5e1820021a 100644 --- a/modules/openxOrtbBidAdapter.md +++ b/modules/openxOrtbBidAdapter.md @@ -1,15 +1,17 @@ # Overview ``` -Module Name: OpenX OpenRTB Bidder Adapter +Module Name: OpenX Bidder Adapter Module Type: Bidder Adapter Maintainer: team-openx@openx.com ``` # Description +DEPRECATED. Use openxBidAdapter. -This is an updated version of the OpenX bid adapter which calls our new serving architecture. -Publishers are welcome to test this adapter and give feedback. Please note you should only include either openxBidAdapter or openxOrtbBidAdapter in your build. +This adapter was originally an adapter used to test OpenX serving architecture changes. +This adapter now mirrors openxBidAdapter and this adapter will be removed in a future release. Please use openxBidAdapter. +Please note you should only include either openxBidAdapter or openxOrtbBidAdapter in your build. # Bid Parameters ## Banner @@ -21,7 +23,7 @@ Publishers are welcome to test this adapter and give feedback. Please note you s | `customParams` | optional | Object | User-defined targeting key-value pairs. customParams applies to a specific unit. | `{key1: "v1", key2: ["v2","v3"]}` | `customFloor` | optional | Number | Minimum price in USD. customFloor applies to a specific unit. For example, use the following value to set a $1.50 floor: 1.50

**WARNING:**
Misuse of this parameter can impact revenue | 1.50 | `doNotTrack` | optional | Boolean | Prevents advertiser from using data for this user.

**WARNING:**
Request-level setting. May impact revenue. | true -| `coppa` | optional | Boolean | Enables Child's Online Privacy Protection Act (COPPA) regulations. | true +| `coppa` | optional | Boolean | Enables Child's Online Privacy Protection Act (COPPA) regulations. Use of `pbjs.setConfig({coppa: true});` is now preferred. | true ## Video @@ -29,7 +31,7 @@ Publishers are welcome to test this adapter and give feedback. Please note you s | ---- | ----- | ---- | ----------- | ------- | `unit` | required | String | OpenX ad unit ID provided by your OpenX representative. | "1611023122" | `delDomain` | required | String | OpenX delivery domain provided by your OpenX representative. | "PUBLISHER-d.openx.net" -| `video` | optional | OpenRTB video subtypes | Alternatively can be added under adUnit.mediaTypes.video | `{ video: {mimes: ['video/mp4']}` +| `video` | optional | OpenRTB video subtypes | Use of adUnit.mediaTypes.video is now preferred. | `{ video: {mimes: ['video/mp4']}` # Example @@ -68,7 +70,8 @@ var adUnits = [ mediaTypes: { video: { playerSize: [640, 480], - context: 'instream' + context: 'instream', + mimes: ['video/x-ms-wmv, video/mp4'] } }, bids: [{ @@ -77,10 +80,10 @@ var adUnits = [ unit: '1611023124', delDomain: 'PUBLISHER-d.openx.net', video: { - mimes: ['video/x-ms-wmv, video/mp4'] + mimes: ['video/x-ms-wmv, video/mp4'] // mediaTypes.video preferred } } - }] + }]p } ]; ``` diff --git a/modules/operaadsBidAdapter.js b/modules/operaadsBidAdapter.js index aa548debf32..48c6246ce6b 100644 --- a/modules/operaadsBidAdapter.js +++ b/modules/operaadsBidAdapter.js @@ -228,7 +228,7 @@ function buildOpenRtbBidRequest(bidRequest, bidderRequest) { // build OpenRTB request body const payload = { id: bidderRequest.auctionId, - tmax: bidderRequest.timeout || config.getConfig('bidderTimeout'), + tmax: bidderRequest.timeout, test: config.getConfig('debug') ? 1 : 0, imp: createImp(bidRequest), device: getDevice(), diff --git a/modules/optidigitalBidAdapter.js b/modules/optidigitalBidAdapter.js new file mode 100755 index 00000000000..4372aa830e6 --- /dev/null +++ b/modules/optidigitalBidAdapter.js @@ -0,0 +1,221 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER } from '../src/mediaTypes.js'; +import { deepAccess, parseSizesInput, getAdUnitSizes } from '../src/utils.js'; + +const BIDDER_CODE = 'optidigital'; +const GVL_ID = 915; +const ENDPOINT_URL = 'https://pbs.optidigital.com/bidder'; +const USER_SYNC_URL_IFRAME = 'https://scripts.opti-digital.com/js/presync.html?endpoint=optidigital'; +let CUR = 'USD'; + +export const spec = { + code: BIDDER_CODE, + gvlid: GVL_ID, + supportedMediaTypes: [BANNER], + /** + * 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 isValid = false; + if (typeof bid.params !== 'undefined' && bid.params.placementId && bid.params.publisherId) { + isValid = true; + } + + return isValid; + }, + /** + * Make a server request from the list of BidRequests. + * + * @param {validBidRequests[]} - an array of bids + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function(validBidRequests, bidderRequest) { + if (!validBidRequests || validBidRequests.length === 0 || !bidderRequest || !bidderRequest.bids) { + return []; + } + + const ortb2 = bidderRequest.ortb2 || { + bcat: [], + badv: [] + }; + + const payload = { + referrer: (bidderRequest.refererInfo && bidderRequest.refererInfo.page) ? bidderRequest.refererInfo.page : '', + hb_version: '$prebid.version$', + deviceWidth: document.documentElement.clientWidth, + auctionId: deepAccess(validBidRequests[0], 'auctionId'), + bidderRequestId: deepAccess(validBidRequests[0], 'bidderRequestId'), + publisherId: deepAccess(validBidRequests[0], 'params.publisherId'), + imp: validBidRequests.map(bidRequest => buildImp(bidRequest, ortb2)), + badv: ortb2.badv || deepAccess(validBidRequests[0], 'params.badv') || [], + bcat: ortb2.bcat || deepAccess(validBidRequests[0], 'params.bcat') || [], + bapp: deepAccess(validBidRequests[0], 'params.bapp') || [] + } + + if (validBidRequests[0].params.pageTemplate && validBidRequests[0].params.pageTemplate !== '') { + payload.pageTemplate = validBidRequests[0].params.pageTemplate; + } + + if (validBidRequests[0].schain) { + payload.schain = validBidRequests[0].schain; + } + + const gdpr = deepAccess(bidderRequest, 'gdprConsent'); + if (bidderRequest && gdpr) { + const isConsentString = typeof gdpr.consentString === 'string'; + payload.gdpr = { + consent: isConsentString ? gdpr.consentString : '', + required: true + }; + } + if (bidderRequest && !gdpr) { + payload.gdpr = { + consent: '', + required: false + } + } + + if (window.location.href.indexOf('optidigitalTestMode=true') !== -1) { + payload.testMode = true; + } + + if (bidderRequest && bidderRequest.uspConsent) { + payload.uspConsent = bidderRequest.uspConsent; + } + + const payloadObject = JSON.stringify(payload); + return { + method: 'POST', + url: ENDPOINT_URL, + data: payloadObject + }; + }, + /** + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function(serverResponse, bidRequest) { + const bidResponses = []; + serverResponse = serverResponse.body; + + if (serverResponse.bids) { + serverResponse.bids.forEach((bid) => { + const bidResponse = { + placementId: bid.placementId, + transactionId: bid.transactionId, + requestId: bid.bidId, + ttl: bid.ttl, + creativeId: bid.creativeId, + currency: bid.cur, + cpm: bid.cpm, + width: bid.w, + height: bid.h, + ad: bid.adm, + netRevenue: true, + meta: { + advertiserDomains: bid.adomain && bid.adomain.length > 0 ? bid.adomain : [] + } + }; + bidResponses.push(bidResponse); + }); + } + 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. + * @return {UserSync[]} The user syncs which should be dropped. + */ + getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent) { + let syncurl = ''; + + // Attaching GDPR Consent Params in UserSync url + if (gdprConsent) { + syncurl += '&gdpr=' + (gdprConsent.gdprApplies ? 1 : 0); + syncurl += '&gdpr_consent=' + encodeURIComponent(gdprConsent.consentString || ''); + } + if (uspConsent && uspConsent.consentString) { + syncurl += `&ccpa_consent=${uspConsent.consentString}`; + } + + if (syncOptions.iframeEnabled) { + return [{ + type: 'iframe', + url: USER_SYNC_URL_IFRAME + syncurl + }]; + } + }, +}; + +function buildImp(bidRequest, ortb2) { + let imp = {}; + imp = { + sizes: parseSizesInput(deepAccess(bidRequest, 'mediaTypes.banner.sizes')), + bidId: deepAccess(bidRequest, 'bidId'), + adUnitCode: deepAccess(bidRequest, 'adUnitCode'), + transactionId: deepAccess(bidRequest, 'transactionId'), + placementId: deepAccess(bidRequest, 'params.placementId') + }; + + if (bidRequest.params.divId && bidRequest.params.divId !== '') { + if (getAdContainer(bidRequest.params.divId)) { + imp.adContainerWidth = getAdContainer(bidRequest.params.divId).offsetWidth; + imp.adContainerHeight = getAdContainer(bidRequest.params.divId).offsetHeight; + } + } + + let floorSizes = []; + if (deepAccess(bidRequest, 'mediaTypes.banner')) { + floorSizes = getAdUnitSizes(bidRequest); + } + + if (bidRequest.params.currency && bidRequest.params.currency !== '') { + CUR = bidRequest.params.currency; + } + + let bidFloor = _getFloor(bidRequest, floorSizes, CUR); + if (bidFloor) { + imp.bidFloor = bidFloor; + } + + let battr = ortb2.battr || deepAccess(bidRequest, 'params.battr'); + if (battr && Array.isArray(battr) && battr.length) { + imp.battr = battr; + } + + return imp; +} + +function getAdContainer(container) { + if (document.getElementById(container)) { + return document.getElementById(container); + } +} + +function _getFloor (bid, sizes, currency) { + let floor = null; + let size = sizes.length === 1 ? sizes[0] : '*'; + if (typeof bid.getFloor === 'function') { + try { + const floorInfo = bid.getFloor({ + currency: currency, + mediaType: 'banner', + size: size + }); + if (typeof floorInfo === 'object' && floorInfo.currency === CUR && !isNaN(parseFloat(floorInfo.floor))) { + floor = parseFloat(floorInfo.floor); + } + } catch (err) {} + } + return floor !== null ? floor : bid.params.floor; +} + +registerBidder(spec); diff --git a/modules/optidigitalBidAdapter.md b/modules/optidigitalBidAdapter.md new file mode 100755 index 00000000000..466dfb3bef2 --- /dev/null +++ b/modules/optidigitalBidAdapter.md @@ -0,0 +1,46 @@ +# Overview + +**Module Name**: OptiDigital Bidder Adapter +**Module Type**: Bidder Adapter +**Maintainer**: prebid@optidigital.com + +# Description + +Bidder Adapter for Prebid.js. + +## AdUnits configuration example +``` + var adUnits = [{ + code: 'your-slot_1-div', //use exactly the same code as your slot div id. + mediaTypes: { + banner: { + sizes: [[300,600]] + } + }, + bids: [{ + bidder: 'optidigital', + params: { + publisherId: 'test', + placementId: 'Billboard_Top', + divId: 'Billboard_Top_3c5425', // optional parameter + pageTemplate: 'home', // optional parameter + badv: ['example.com'], // optional parameter + bcat: ['IAB1-1'], // optional parameter + bapp: ['com.blocked'], // optional parameter + battr: [1, 2] // optional parameter + } + }] + }]; +``` + +## UserSync example + +``` +pbjs.setConfig({ + userSync: { + iframeEnabled: true, + syncEnabled: true, + syncDelay: 3000 + } +}); +``` diff --git a/modules/orbidderBidAdapter.js b/modules/orbidderBidAdapter.js index 9979b1fdc3b..b84c67ba6d2 100644 --- a/modules/orbidderBidAdapter.js +++ b/modules/orbidderBidAdapter.js @@ -3,6 +3,7 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import { getStorageManager } from '../src/storageManager.js'; import { BANNER, NATIVE } from '../src/mediaTypes.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import {getGlobal} from '../src/prebidGlobal.js'; const storageManager = getStorageManager({bidderCode: 'orbidder'}); @@ -96,7 +97,7 @@ export const spec = { method: 'POST', options: { withCredentials: true }, data: { - v: $$PREBID_GLOBAL$$.version, + v: getGlobal().version, pageUrl: referer, bidId: bidRequest.bidId, auctionId: bidRequest.auctionId, diff --git a/modules/orbitsoftBidAdapter.js b/modules/orbitsoftBidAdapter.js index d72a8719bd8..4c3f2e38c58 100644 --- a/modules/orbitsoftBidAdapter.js +++ b/modules/orbitsoftBidAdapter.js @@ -1,6 +1,5 @@ import * as utils from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {config} from '../src/config.js'; const BIDDER_CODE = 'orbitsoft'; let styleParamsMap = { @@ -122,7 +121,7 @@ export const spec = { const HEIGHT = serverBody.height; const CREATIVE = serverBody.content_url; const CALLBACK_UID = serverBody.callback_uid; - const TIME_TO_LIVE = config.getConfig('_bidderTimeout'); + const TIME_TO_LIVE = 60; const REFERER = utils.getWindowTop(); let bidRequest = request.bidRequest; if (CPM > 0 && WIDTH > 0 && HEIGHT > 0) { diff --git a/modules/outbrainBidAdapter.js b/modules/outbrainBidAdapter.js index 3db1da0d689..6bcbc6a1cba 100644 --- a/modules/outbrainBidAdapter.js +++ b/modules/outbrainBidAdapter.js @@ -4,11 +4,13 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { NATIVE, BANNER } from '../src/mediaTypes.js'; -import { deepAccess, deepSetValue, replaceAuctionPrice, _map, isArray } from '../src/utils.js'; +import { NATIVE, BANNER, VIDEO } from '../src/mediaTypes.js'; +import { OUTSTREAM } from '../src/video.js'; +import { deepAccess, deepSetValue, replaceAuctionPrice, _map, isArray, logWarn } from '../src/utils.js'; import { ajax } from '../src/ajax.js'; import { config } from '../src/config.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import { Renderer } from '../src/Renderer.js'; const BIDDER_CODE = 'outbrain'; const GVLID = 164; @@ -22,11 +24,12 @@ const NATIVE_PARAMS = { body: { id: 4, name: 'data', type: 2 }, cta: { id: 1, type: 12, name: 'data' } }; +const OUTSTREAM_RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; export const spec = { code: BIDDER_CODE, gvlid: GVLID, - supportedMediaTypes: [ NATIVE, BANNER ], + supportedMediaTypes: [ NATIVE, BANNER, VIDEO ], isBidRequestValid: (bid) => { if (typeof bid.params !== 'object') { return false; @@ -50,7 +53,7 @@ export const spec = { return ( !!config.getConfig('outbrain.bidderUrl') && - !!(bid.nativeParams || bid.sizes) + (!!(bid.nativeParams || bid.sizes) || isValidVideoRequest(bid)) ); }, buildRequests: (validBidRequests, bidderRequest) => { @@ -85,6 +88,8 @@ export const spec = { assets: getNativeAssets(bid) }) } + } else if (isVideoRequest(bid)) { + imp.video = getVideoAsset(bid); } else { imp.banner = { format: transformSizes(bid.sizes) @@ -163,7 +168,12 @@ export const spec = { return bids.map((bid, id) => { const bidResponse = bidResponses[id]; if (bidResponse) { - const type = bid.nativeParams ? NATIVE : BANNER; + let type = BANNER; + if (bid.nativeParams) { + type = NATIVE; + } else if (isVideoRequest(bid)) { + type = VIDEO; + } const bidObject = { requestId: bid.bidId, cpm: bidResponse.price, @@ -176,10 +186,16 @@ export const spec = { }; if (type === NATIVE) { bidObject.native = parseNative(bidResponse); - } else { + } else if (type === BANNER) { bidObject.ad = bidResponse.adm; bidObject.width = bidResponse.w; bidObject.height = bidResponse.h; + } else if (type === VIDEO) { + bidObject.vastXml = bidResponse.adm; + const videoContext = deepAccess(bid, 'mediaTypes.video.context'); + if (videoContext === OUTSTREAM) { + bidObject.renderer = createRenderer(bid); + } } bidObject.meta = {}; if (bidResponse.adomain && bidResponse.adomain.length > 0) { @@ -304,6 +320,27 @@ function getNativeAssets(bid) { }).filter(Boolean); } +function getVideoAsset(bid) { + const sizes = flatten(bid.mediaTypes.video.playerSize); + return { + w: parseInt(sizes[0], 10), + h: parseInt(sizes[1], 10), + protocols: bid.mediaTypes.video.protocols, + playbackmethod: bid.mediaTypes.video.playbackmethod, + mimes: bid.mediaTypes.video.mimes, + skip: bid.mediaTypes.video.skip, + delivery: bid.mediaTypes.video.delivery, + api: bid.mediaTypes.video.api, + minbitrate: bid.mediaTypes.video.minbitrate, + maxbitrate: bid.mediaTypes.video.maxbitrate, + minduration: bid.mediaTypes.video.minduration, + maxduration: bid.mediaTypes.video.maxduration, + startdelay: bid.mediaTypes.video.startdelay, + placement: bid.mediaTypes.video.placement, + linearity: bid.mediaTypes.video.linearity + }; +} + /* Turn bid request sizes into ut-compatible format */ function transformSizes(requestSizes) { if (!isArray(requestSizes)) { @@ -338,3 +375,63 @@ function _getFloor(bid, type) { } return null; } + +function isVideoRequest(bid) { + return bid.mediaType === 'video' || !!deepAccess(bid, 'mediaTypes.video'); +} + +function createRenderer(bid) { + let config = {}; + let playerUrl = OUTSTREAM_RENDERER_URL; + let render = function (bid) { + bid.renderer.push(() => { + window.ANOutstreamVideo.renderAd({ + sizes: bid.sizes, + targetId: bid.adUnitCode, + adResponse: { content: bid.vastXml } + }); + }); + }; + + let externalRenderer = deepAccess(bid, 'mediaTypes.video.renderer'); + if (!externalRenderer) { + externalRenderer = deepAccess(bid, 'renderer'); + } + + if (externalRenderer) { + config = externalRenderer.options; + playerUrl = externalRenderer.url; + render = externalRenderer.render; + } + + const renderer = Renderer.install({ + id: bid.adUnitCode, + url: playerUrl, + config: config, + adUnitCode: bid.adUnitCode, + loaded: false + }); + try { + renderer.setRender(render); + } catch (err) { + logWarn('Prebid Error calling setRender on renderer', err); + } + return renderer; +} + +function isValidVideoRequest(bid) { + const videoAdUnit = deepAccess(bid, 'mediaTypes.video') + if (!videoAdUnit) { + return false; + } + + if (!Array.isArray(videoAdUnit.playerSize)) { + return false; + } + + if (videoAdUnit.context == '') { + return false; + } + + return true; +} diff --git a/modules/pairIdSystem.js b/modules/pairIdSystem.js new file mode 100644 index 00000000000..4d73d4efb7e --- /dev/null +++ b/modules/pairIdSystem.js @@ -0,0 +1,79 @@ +/** + * This module adds PAIR Id to the User ID module + * The {@link module:modules/userId} module is required + * @module modules/pairIdSystem + * @requires module:modules/userId + */ + +import { submodule } from '../src/hook.js'; +import {getStorageManager} from '../src/storageManager.js' +import { logError } from '../src/utils.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; + +const MODULE_NAME = 'pairId'; +const PAIR_ID_KEY = 'pairId'; +const DEFAULT_LIVERAMP_PAIR_ID_KEY = '_lr_pairId'; + +export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); + +function pairIdFromLocalStorage(key) { + return storage.localStorageIsEnabled ? storage.getDataFromLocalStorage(key) : null; +} + +function pairIdFromCookie(key) { + return storage.cookiesAreEnabled ? storage.getCookie(key) : null; +} + +/** @type {Submodule} */ +export const pairIdSubmodule = { + /** + * used to link submodule with config + * @type {string} + */ + name: MODULE_NAME, + /** + * decode the stored id value for passing to bid requests + * @function + * @param { string | undefined } value + * @returns {{pairId:string} | undefined } + */ + decode(value) { + return value && Array.isArray(value) ? {'pairId': value} : undefined + }, + /** + * performs action to obtain id and return a value in the callback's response argument + * @function + * @returns {id: string | undefined } + */ + getId(config) { + const pairIdsString = pairIdFromLocalStorage(PAIR_ID_KEY) || pairIdFromCookie(PAIR_ID_KEY) + let ids = [] + if (pairIdsString && typeof pairIdsString == 'string') { + try { + ids = ids.concat(JSON.parse(atob(pairIdsString))) + } catch (error) { + logError(error) + } + } + + const configParams = (config && config.params) || {}; + if (configParams && configParams.liveramp) { + let LRStorageLocation = configParams.liveramp.storageKey || DEFAULT_LIVERAMP_PAIR_ID_KEY + const liverampValue = pairIdFromLocalStorage(LRStorageLocation) || pairIdFromCookie(LRStorageLocation) + try { + const obj = JSON.parse(atob(liverampValue)); + ids = ids.concat(obj.envelope); + } catch (error) { + logError(error) + } + } + + if (ids.length == 0) { + logError('PairId not found.') + return undefined; + } + return {'id': ids}; + } +}; + +submodule('userId', pairIdSubmodule); diff --git a/modules/parrableIdSystem.js b/modules/parrableIdSystem.js index 4a777097914..01ffec3c249 100644 --- a/modules/parrableIdSystem.js +++ b/modules/parrableIdSystem.js @@ -14,6 +14,7 @@ import {submodule} from '../src/hook.js'; import {getRefererInfo} from '../src/refererDetection.js'; import {uspDataHandler} from '../src/adapterManager.js'; import {getStorageManager} from '../src/storageManager.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; const PARRABLE_URL = 'https://h.parrable.com/prebid'; const PARRABLE_COOKIE_NAME = '_parrable_id'; @@ -22,8 +23,9 @@ const LEGACY_ID_COOKIE_NAME = '_parrable_eid'; const LEGACY_OPTOUT_COOKIE_NAME = '_parrable_optout'; const ONE_YEAR_MS = 364 * 24 * 60 * 60 * 1000; const EXPIRE_COOKIE_DATE = 'Thu, 01 Jan 1970 00:00:00 GMT'; +const MODULE_NAME = 'parrableId'; -const storage = getStorageManager({gvlid: PARRABLE_GVLID}); +const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); function getExpirationDate() { const oneYearFromNow = new Date(timestamp() + ONE_YEAR_MS); @@ -336,7 +338,7 @@ export const parrableIdSubmodule = { * used to link submodule with config * @type {string} */ - name: 'parrableId', + name: MODULE_NAME, /** * Global Vendor List ID * @type {number} diff --git a/modules/permutiveRtdProvider.js b/modules/permutiveRtdProvider.js index c64080f3308..ab827f2b6a5 100644 --- a/modules/permutiveRtdProvider.js +++ b/modules/permutiveRtdProvider.js @@ -8,15 +8,20 @@ import {getGlobal} from '../src/prebidGlobal.js'; import {submodule} from '../src/hook.js'; import {getStorageManager} from '../src/storageManager.js'; -import {deepAccess, deepSetValue, isFn, logError, mergeDeep, isPlainObject, safeJSONParse} from '../src/utils.js'; +import {deepAccess, deepSetValue, isFn, logError, mergeDeep, isPlainObject, safeJSONParse, prefixLog} from '../src/utils.js'; import {includes} from '../src/polyfill.js'; +import {MODULE_TYPE_RTD} from '../src/activities/modules.js'; const MODULE_NAME = 'permutive' +const logger = prefixLog('[PermutiveRTD]') + export const PERMUTIVE_SUBMODULE_CONFIG_KEY = 'permutive-prebid-rtd' +export const PERMUTIVE_STANDARD_KEYWORD = 'p_standard' +export const PERMUTIVE_CUSTOM_COHORTS_KEYWORD = 'permutive' export const PERMUTIVE_STANDARD_AUD_KEYWORD = 'p_standard_aud' -export const storage = getStorageManager({gvlid: null, moduleName: MODULE_NAME}) +export const storage = getStorageManager({moduleType: MODULE_TYPE_RTD, moduleName: MODULE_NAME}) function init(moduleConfig, userConsent) { readPermutiveModuleConfigFromCache() @@ -24,30 +29,6 @@ function init(moduleConfig, userConsent) { return true } -/** - * Set segment targeting from cache and then try to wait for Permutive - * to initialise to get realtime segment targeting - * @param {Object} reqBidsConfigObj - * @param {function} callback - Called when submodule is done - * @param {customModuleConfig} reqBidsConfigObj - Publisher config for module - */ -export function initSegments (reqBidsConfigObj, callback, customModuleConfig) { - const permutiveOnPage = isPermutiveOnPage() - const moduleConfig = getModuleConfig(customModuleConfig) - const segmentData = getSegments(moduleConfig.params.maxSegs) - - setSegments(reqBidsConfigObj, moduleConfig, segmentData) - - if (moduleConfig.waitForIt && permutiveOnPage) { - window.permutive.ready(function () { - setSegments(reqBidsConfigObj, moduleConfig, segmentData) - callback() - }, 'realtime') - } else { - callback() - } -} - function liftIntoParams(params) { return isPlainObject(params) ? { params } : {} } @@ -109,15 +90,13 @@ export function getModuleConfig(customModuleConfig) { /** * Sets ortb2 config for ac bidders - * @param {Object} bidderOrtb2 + * @param {Object} bidderOrtb2 - The ortb2 object for the all bidders * @param {Object} customModuleConfig - Publisher config for module */ -export function setBidderRtb (bidderOrtb2, customModuleConfig) { - const moduleConfig = getModuleConfig(customModuleConfig) +export function setBidderRtb (bidderOrtb2, moduleConfig, segmentData) { const acBidders = deepAccess(moduleConfig, 'params.acBidders') const maxSegs = deepAccess(moduleConfig, 'params.maxSegs') const transformationConfigs = deepAccess(moduleConfig, 'params.transformations') || [] - const segmentData = getSegments(maxSegs) const ssps = segmentData?.ssp?.ssps ?? [] const sspCohorts = segmentData?.ssp?.cohorts ?? [] @@ -126,28 +105,39 @@ export function setBidderRtb (bidderOrtb2, customModuleConfig) { bidders.forEach(function (bidder) { const currConfig = { ortb2: bidderOrtb2[bidder] || {} } + let cohorts = [] + const isAcBidder = acBidders.indexOf(bidder) > -1 - const isSspBidder = ssps.indexOf(bidder) > -1 + if (isAcBidder) { + cohorts = segmentData.ac + } - let cohorts = [] - if (isAcBidder) cohorts = segmentData.ac - if (isSspBidder) cohorts = [...new Set([...cohorts, ...sspCohorts])].slice(0, maxSegs) + const isSspBidder = ssps.indexOf(bidder) > -1 + if (isSspBidder) { + cohorts = [...new Set([...cohorts, ...sspCohorts])].slice(0, maxSegs) + } - const nextConfig = updateOrtbConfig(currConfig, cohorts, sspCohorts, transformationConfigs) - bidderOrtb2[bidder] = nextConfig.ortb2; + const nextConfig = updateOrtbConfig(bidder, currConfig, cohorts, sspCohorts, transformationConfigs, segmentData) + bidderOrtb2[bidder] = nextConfig.ortb2 }) } /** * Updates `user.data` object in existing bidder config with Permutive segments + * @param string bidder - The bidder * @param {Object} currConfig - Current bidder config * @param {Object[]} transformationConfigs - array of objects with `id` and `config` properties, used to determine * the transformations on user data to include the ORTB2 object * @param {string[]} segmentIDs - Permutive segment IDs * @param {string[]} sspSegmentIDs - Permutive SSP segment IDs + * @param {Object} segmentData - The segments available for targeting * @return {Object} Merged ortb2 object */ -function updateOrtbConfig (currConfig, segmentIDs, sspSegmentIDs, transformationConfigs) { +function updateOrtbConfig(bidder, currConfig, segmentIDs, sspSegmentIDs, transformationConfigs, segmentData) { + logger.logInfo(`Current ortb2 config`, { bidder, config: currConfig }) + + const customCohortsData = deepAccess(segmentData, bidder) || [] + const name = 'permutive.com' const permutiveUserData = { @@ -159,21 +149,59 @@ function updateOrtbConfig (currConfig, segmentIDs, sspSegmentIDs, transformation .filter(({ id }) => ortb2UserDataTransformations.hasOwnProperty(id)) .map(({ id, config }) => ortb2UserDataTransformations[id](permutiveUserData, config)) + const customCohortsUserData = { + name: PERMUTIVE_CUSTOM_COHORTS_KEYWORD, + segment: customCohortsData.map(cohortID => ({ id: cohortID })), + } + const ortbConfig = mergeDeep({}, currConfig) const currentUserData = deepAccess(ortbConfig, 'ortb2.user.data') || [] const updatedUserData = currentUserData - .filter(el => el.name !== name) - .concat(permutiveUserData, transformedUserData) + .filter(el => el.name !== permutiveUserData.name && el.name !== customCohortsUserData.name) + .concat(permutiveUserData, transformedUserData, customCohortsUserData) + logger.logInfo(`Updating ortb2.user.data`, { bidder, user_data: updatedUserData }) deepSetValue(ortbConfig, 'ortb2.user.data', updatedUserData) - // As of writing this, only used for AppNexus/Xandr in place of appnexusAuctionKeywords in config - const currentUserKeywords = deepAccess(ortbConfig, 'ortb2.user.keywords') || '' - const keywords = sspSegmentIDs.map(segment => `${PERMUTIVE_STANDARD_AUD_KEYWORD}=${segment}`).join(',') - const updatedUserKeywords = (currentUserKeywords === '') ? keywords : `${currentUserKeywords},${keywords}` - deepSetValue(ortbConfig, 'ortb2.user.keywords', updatedUserKeywords) + // Set ortb2.user.keywords + const currentKeywords = deepAccess(ortbConfig, 'ortb2.user.keywords') + const keywordGroups = { + [PERMUTIVE_STANDARD_KEYWORD]: segmentIDs, + [PERMUTIVE_STANDARD_AUD_KEYWORD]: sspSegmentIDs, + [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 = [ + currentKeywords, + ...transformedKeywordGroups, + ] + .filter(Boolean) + .join(',') + + logger.logInfo(`Updating ortb2.user.keywords`, { + bidder, + keywords, + }) + deepSetValue(ortbConfig, 'ortb2.user.keywords', keywords) + + // Set user extensions + if (segmentIDs.length > 0) { + deepSetValue(ortbConfig, `ortb2.user.ext.data.${PERMUTIVE_STANDARD_KEYWORD}`, segmentIDs) + logger.logInfo(`Extending ortb2.user.ext.data with "${PERMUTIVE_STANDARD_KEYWORD}"`, segmentIDs) + } + + if (customCohortsData.length > 0) { + deepSetValue(ortbConfig, `ortb2.user.ext.data.${PERMUTIVE_CUSTOM_COHORTS_KEYWORD}`, customCohortsData.map(String)) + logger.logInfo(`Extending ortb2.user.ext.data with "${PERMUTIVE_CUSTOM_COHORTS_KEYWORD}"`, customCohortsData) + } + + logger.logInfo(`Updated ortb2 config`, { bidder, config: ortbConfig }) return ortbConfig } @@ -251,28 +279,6 @@ function getDefaultBidderFn (bidder) { return [...new Set([...ac, ...ssp])] } const bidderMap = { - appnexus: function (bid, data, acEnabled) { - if (isPStandardTargetingEnabled(data, acEnabled)) { - const segments = pStandardTargeting(data, acEnabled) - deepSetValue(bid, 'params.keywords.p_standard', segments) - } - if (data.appnexus && data.appnexus.length) { - deepSetValue(bid, 'params.keywords.permutive', data.appnexus) - } - - return bid - }, - rubicon: function (bid, data, acEnabled) { - if (isPStandardTargetingEnabled(data, acEnabled)) { - const segments = pStandardTargeting(data, acEnabled) - deepSetValue(bid, 'params.visitor.p_standard', segments) - } - if (data.rubicon && data.rubicon.length) { - deepSetValue(bid, 'params.visitor.permutive', data.rubicon.map(String)) - } - - return bid - }, ozone: function (bid, data, acEnabled) { if (isPStandardTargetingEnabled(data, acEnabled)) { const segments = pStandardTargeting(data, acEnabled) @@ -283,7 +289,12 @@ function getDefaultBidderFn (bidder) { } } - return bidderMap[bidder] + // On no default bidder just return the same bid as passed in + function bidIdentity(bid) { + return bid + } + + return bidderMap[bidder] || bidIdentity } /** @@ -311,16 +322,20 @@ export function isPermutiveOnPage () { * @return {Object} */ export function getSegments (maxSegs) { - const legacySegs = readSegments('_psegs').map(Number).filter(seg => seg >= 1000000).map(String) - const _ppam = readSegments('_ppam') - const _pcrprs = readSegments('_pcrprs') + const legacySegs = readSegments('_psegs', []).map(Number).filter(seg => seg >= 1000000).map(String) + const _ppam = readSegments('_ppam', []) + const _pcrprs = readSegments('_pcrprs', []) const segments = { ac: [..._pcrprs, ..._ppam, ...legacySegs], - rubicon: readSegments('_prubicons'), - appnexus: readSegments('_papns'), - gam: readSegments('_pdfps'), - ssp: readSegments('_pssps'), + ix: readSegments('_pindexs', []), + rubicon: readSegments('_prubicons', []), + appnexus: readSegments('_papns', []), + gam: readSegments('_pdfps', []), + ssp: readSegments('_pssps', { + cohorts: [], + ssps: [] + }), } for (const bidder in segments) { @@ -338,15 +353,17 @@ export function getSegments (maxSegs) { /** * Gets an array of segment IDs from LocalStorage - * or returns an empty array + * or return the default value provided. + * @template A * @param {string} key - * @return {string[]|number[]} + * @param {A} defaultValue + * @return {A} */ -function readSegments (key) { +function readSegments (key, defaultValue) { try { - return JSON.parse(storage.getDataFromLocalStorage(key) || '[]') + return JSON.parse(storage.getDataFromLocalStorage(key)) || defaultValue } catch (e) { - return [] + return defaultValue } } @@ -378,17 +395,55 @@ function iabSegmentId(permutiveSegmentId, iabIds) { return iabIds[permutiveSegmentId] || unknownIabSegmentId } +/** + * Pull the latest configuration and cohort information and update accordingly. + * + * @param reqBidsConfigObj - Bidder provided config for request + * @param customModuleConfig - Publisher provide config + */ +export function readAndSetCohorts(reqBidsConfigObj, moduleConfig) { + const segmentData = getSegments(deepAccess(moduleConfig, 'params.maxSegs')) + + makeSafe(function () { + // Legacy route with custom parameters + // ACK policy violation, in process of removing + setSegments(reqBidsConfigObj, moduleConfig, segmentData) + }); + + makeSafe(function () { + // Route for bidders supporting ORTB2 + setBidderRtb(reqBidsConfigObj.ortb2Fragments?.bidder, moduleConfig, segmentData) + }) +} + +let permutiveSDKInRealTime = false + /** @type {RtdSubmodule} */ export const permutiveSubmodule = { name: MODULE_NAME, getBidRequestData: function (reqBidsConfigObj, callback, customModuleConfig) { + const completeBidRequestData = () => { + logger.logInfo(`Request data updated`) + callback() + } + + const moduleConfig = getModuleConfig(customModuleConfig) + + readAndSetCohorts(reqBidsConfigObj, moduleConfig) + makeSafe(function () { - // Legacy route with custom parameters - initSegments(reqBidsConfigObj, callback, customModuleConfig) - }); - makeSafe(function () { - // Route for bidders supporting ORTB2 - setBidderRtb(reqBidsConfigObj.ortb2Fragments?.bidder, customModuleConfig) + if (permutiveSDKInRealTime || !(moduleConfig.waitForIt && isPermutiveOnPage())) { + return completeBidRequestData() + } + + window.permutive.ready(function () { + logger.logInfo(`SDK is realtime, updating cohorts`) + permutiveSDKInRealTime = true + readAndSetCohorts(reqBidsConfigObj, getModuleConfig(customModuleConfig)) + completeBidRequestData() + }, 'realtime') + + logger.logInfo(`Registered cohort update when SDK is realtime`) }) }, init: init diff --git a/modules/permutiveRtdProvider.md b/modules/permutiveRtdProvider.md index 7d8f073357c..9399dffab93 100644 --- a/modules/permutiveRtdProvider.md +++ b/modules/permutiveRtdProvider.md @@ -13,7 +13,7 @@ gulp build --modules=rtdModule,permutiveRtdProvider > Note that the global RTD module, `rtdModule`, is a prerequisite of the Permutive RTD module. -You then need to enable the Permutive RTD in your Prebid configuration, using the below format: +You then need to enable the Permutive RTD in your Prebid configuration. Below is an example of the format: ```javascript pbjs.setConfig({ @@ -37,59 +37,16 @@ pbjs.setConfig({ The parameters below provide configurability for general behaviours of the RTD submodule, as well as enabling settings for specific use cases mentioned above (e.g. acbidders). -| Name | Type | Description | Default | -|------------------------|----------|-----------------------------------------------------------------------------------------------|---------| -| name | String | This should always be `permutive` | - | -| waitForIt | Boolean | Should be `true` if there's an `auctionDelay` defined (optional) | `false` | -| params | Object | | - | -| params.acBidders | String[] | An array of bidders which should receive AC cohorts. | `[]` | -| params.maxSegs | Integer | Maximum number of cohorts to be included in either the `permutive` or `p_standard` key-value. | `500` | -| params.transformations | Object[] | An array of configurations for ORTB2 user data transformations | | -| params.overwrites | Object | An object specifying functions for custom targeting logic for bidders. | - | - -##### The `transformations` parameter - -This array contains configurations for transformations we'll apply to the Permutive object in the ORTB2 `user.data` array. The results of these transformations will be appended to the `user.data` array that's attached to ORTB2 bid requests. - -###### Supported transformations - -| Name | ID | Config structure | Description | -|----------------|-----|---------------------------------------------------|--------------------------------------------------------------------------------------| -| IAB taxonomies | iab | { segtax: number, iabIds: Object} | Transform segment IDs from Permutive to IAB (note: alpha version, subject to change) | - -##### The `overwrites` parameter - -The keys for this object should match a bidder (e.g. `rubicon`), which then can define a function to overwrite the customer targeting logic. - -```javascript -{ - params: { - overwrites: { - rubicon: function customTargeting(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) - } - } - } - } -} -``` - -###### `customTargeting` function parameters - -| Name | Description | -|--------------|--------------------------------------------------------------------------------| -| `bid` | The bid request object. | -| `data` | Permutive's targeting data read from localStorage. | -| `acEnabled` | Boolean stating whether Audience Connect is enabled via `acBidders`. | -| `utils` | An object of helpful utilities. `(deepSetValue, deepAccess, isFn, mergeDeep)`. | -| `defaultFn` | The default targeting function. | - - +## Parameters +{: .table .table-bordered .table-striped } +| Name | Type | Description | Default | +| ---------------------- | -------------------- | --------------------------------------------------------------------------------------------- | ------------------ | +| name | String | This should always be `permutive` | - | +| waitForIt | Boolean | Should be `true` if there's an `auctionDelay` defined (optional) | `false` | +| params | Object | | - | +| params.acBidders | String[] | An array of bidder codes to share cohorts with in certain versions of Prebid, see below | `[]` | +| params.maxSegs | Integer | Maximum number of cohorts to be included in either the `permutive` or `p_standard` key-value. | `500` | #### Context @@ -100,7 +57,7 @@ As Prebid utilizes TCF vendor consent, for the Permutive RTD module to load, Per #### Instructions -1. Publisher enables GDPR rules within Prebid. +1. Publisher enables rules within Prebid GDPR module 2. Label Permutive as an exception, as shown below. ```javascript [ @@ -123,29 +80,58 @@ Before making any updates to this configuration, please ensure that this approac ## Cohort Activation with Permutive RTD Module -### _Enabling Standard Cohorts_ - **Note**: Publishers must be enabled on the above Permutive RTD Submodule to enable Standard Cohorts. -The acbidders config in the Permutive RTD module allows publishers to determine which demand partners (SSPs) will receive standard cohorts via the user.data ortb2 object. Cohorts will be sent in the `p_standard` key-value. +### _Enabling Publisher Cohorts_ + +#### Standard Cohorts + +The Permutive RTD module sets Standard Cohort IDs as bidder-specific ortb2.user.data first-party data, following the Prebid ortb2 convention. Cohorts will be sent in the `p_standard` key-value. + +For Prebid versions below 7.29.0, populate the acbidders config in the Permutive RTD with an array of bidder codes with whom you wish to share Standard Cohorts with. You also need to permission the bidders by communicating the bidder list to the Permutive team at strategicpartnershipops@permutive.com. -The Permutive RTD module sets standard cohort IDs as bidder-specific ortb2.user.data first-party data, following the Prebid ortb2 convention. +For Prebid versions 7.29.0 and above, do not populate bidder codes in acbidders for the purpose of sharing Standard Cohorts (Note: there may be other business needs that require you to populate acbidders for Prebid versions 7.29.0+, see Advertiser Cohorts below). To share Standard Cohorts with bidders in Prebid versions 7.29.0 and above, communicate the bidder list to the Permutive team at strategicpartnershipops@permutive.com. -There are **two** ways to assign which demand partner bidders (e.g. SSPs) will receive Standard Cohort information via the Audience Connector (acbidders) config: +#### _Bidder Specific Requirements for Standard Cohorts_ +For PubMatic or OpenX: Please ensure you are using Prebid.js 7.13 (or later) +For Xandr: Please ensure you are using Prebid.js 7.29 (or later) +For Equativ: Please ensure you are using Prebid.js 7.26 (or later) + +#### Custom Cohorts + +The Permutive RTD module also supports passing any of the **Custom** Cohorts created in the dashboard to some SSP partners for targeting +e.g. setting up publisher deals. For these activations, cohort IDs are set in bidder-specific locations per ad unit (custom parameters). + +Currently, bidders with known support for custom cohort targeting are: + +- Xandr +- Magnite + +When enabling the respective Activation for a cohort in Permutive, this module will automatically attach that cohort ID to the bid request. +There is no need to enable individual bidders in the module configuration, it will automatically reflect which SSP integrations you have enabled in your Permutive dashboard. +Permutive cohorts will be sent in the permutive key-value. + + +### _Enabling Advertiser Cohorts_ + +If you are connecting to an Advertiser seat within Permutive to share Advertiser Cohorts, populate the acbidders config in the Permutive RTD with an array of bidder codes with whom you wish to share Advertiser Cohorts with. + +### _Managing acbidders_ + +If your business needs require you to populate acbidders with bidder codes based on the criteria above, there are **two** ways to manage it. #### Option 1 - Automated -New demand partner bidders may be added to the acbidders config directly within the Permutive Platform. +If you are using Prebid.js v7.13.0+, bidders may be added to or removed from the acbidders config directly within the Permutive Dashboard. **Permutive can do this on your behalf**. Simply contact your Permutive CSM with strategicpartnershipops@permutive.com on cc, indicating which bidders you would like added. -Or, a publisher may do this themselves within the UI using the below instructions. +Or, a publisher may do this themselves within the Permutive Dashboard using the below instructions. ##### Create Integration -In order to update acbidders via the Permutive dashboard, -it is necessary to first enable the prebid integration in the integrations page (settings). +In order to manage acbidders via the Permutive dashboard, it is necessary to first enable the Prebid integration via the integrations page (settings). **Note on Revenue Insights:** The prebid integration includes a feature for revenue insights, which is not required for the purpose of updating acbidders config. @@ -153,31 +139,15 @@ Please see [this document](https://support.permutive.com/hc/en-us/articles/36001 ##### Update acbidders -The input for the “Data Provider config” is currently a multi-input free text. -A valid “bidder code” needs to be entered in order to enable Standard Cohorts to be passed to the desired partner. -The [prebid Bidders page](https://docs.prebid.org/dev-docs/bidders.html) contains instructions and a link to a list of possible bidder codes. +The input for the “Data Provider config” is a multi-input free text. A valid “bidder code” needs to be entered in order to enable Standard or Advertiser Cohorts to be passed to the desired partner. The [prebid Bidders page](https://docs.prebid.org/dev-docs/bidders.html) contains instructions and a link to a list of possible bidder codes. -Acbidders can be added or removed from the list using this feature, however, this will not impact any acbidders that have been applied using the manual method below. +Bidders can be added or removed from acbidders using this feature, however, this will not impact any bidders that have been applied using the manual method below. #### Option 2 - Manual -As a secondary option, new demand partner bidders may be added manually. +As a secondary option, bidders may be added manually. -To do so, a Publisher may define which bidders should receive Standard Cohorts by +To do so, define which bidders should receive Standard or Advertiser Cohorts by including the _bidder code_ of any bidder in the `acBidders` array. -**Note:** If a Publisher ever needs to remove a manually-added bidder, the bidder will also need to be removed manually. - -### _Enabling Custom Cohort IDs for Targeting_ - -Separately from Standard Cohorts - The Permutive RTD module also supports passing any of the **custom** cohorts created in the dashboard to some SSP partners for targeting -e.g. setting up publisher deals. For these activations, cohort IDs are set in bidder-specific locations per ad unit (custom parameters). - -Currently, bidders with known support for custom cohort targeting are: - -- Xandr -- Magnite - -When enabling the respective Activation for a cohort in Permutive, this module will automatically attach that cohort ID to the bid request. -There is no need to enable individual bidders in the module configuration, it will automatically reflect which SSP integrations you have enabled in your Permutive dashboard. -Permutive cohorts will be sent in the permutive key-value. +**Note:** If you ever need to remove a manually-added bidder, the bidder will also need to be removed manually. diff --git a/modules/pixfutureBidAdapter.js b/modules/pixfutureBidAdapter.js index 41b1151e72a..5019b31b90b 100644 --- a/modules/pixfutureBidAdapter.js +++ b/modules/pixfutureBidAdapter.js @@ -14,6 +14,7 @@ import { transformBidderParamKeywords } from '../src/utils.js'; import { auctionManager } from '../src/auctionManager.js'; +import {getGlobal} from '../src/prebidGlobal.js'; const SOURCE = 'pbjs'; const storageManager = getStorageManager({bidderCode: 'pixfuture'}); @@ -132,7 +133,7 @@ export const spec = { method: 'POST', options: {withCredentials: true}, data: { - v: $$PREBID_GLOBAL$$.version, + v: getGlobal().version, pageUrl: referer, bidId: bidRequest.bidId, auctionId: bidRequest.auctionId, diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index b609d1a54ec..ffb02204ed0 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -20,7 +20,7 @@ import { import CONSTANTS from '../../src/constants.json'; import adapterManager from '../../src/adapterManager.js'; import {config} from '../../src/config.js'; -import {isValid} from '../../src/adapters/bidderFactory.js'; +import {addComponentAuction, isValid} from '../../src/adapters/bidderFactory.js'; import * as events from '../../src/events.js'; import {includes} from '../../src/polyfill.js'; import {S2S_VENDORS} from './config.js'; @@ -222,10 +222,23 @@ function queueSync(bidderCodes, gdprConsent, uspConsent, gppConsent, s2sConfig) } _syncCount++; + let filterSettings = {}; + const userSyncFilterSettings = getConfig('userSync.filterSettings'); + + if (userSyncFilterSettings) { + const { all, iframe, image } = userSyncFilterSettings; + const ifrm = iframe || all; + const img = image || all; + + if (ifrm) filterSettings = Object.assign({ iframe: ifrm }, filterSettings); + if (img) filterSettings = Object.assign({ image: img }, filterSettings); + } + const payload = { uuid: generateUUID(), bidders: bidderCodes, - account: s2sConfig.accountId + account: s2sConfig.accountId, + filterSettings }; let userSyncLimit = s2sConfig.userSyncLimit; @@ -456,10 +469,13 @@ export function PrebidServer() { } processPBSRequest(s2sBidRequest, bidRequests, ajax, { - onResponse: function (isValid, requestedBidders) { + onResponse: function (isValid, requestedBidders, response) { if (isValid) { bidRequests.forEach(bidderRequest => events.emit(CONSTANTS.EVENTS.BIDDER_DONE, bidderRequest)); } + if (shouldEmitNonbids(s2sBidRequest.s2sConfig, response)) { + emitNonBids(response.ext.seatnonbid, bidRequests[0].auctionId); + } done(); doClientSideSyncs(requestedBidders, gdprConsent, uspConsent, gppConsent); }, @@ -480,6 +496,9 @@ export function PrebidServer() { addBidResponse.reject(adUnit, bid, CONSTANTS.REJECTION_REASON.INVALID); } } + }, + onFledge: ({adUnitCode, config}) => { + addComponentAuction(adUnitCode, config); } }) } @@ -505,7 +524,7 @@ export function PrebidServer() { * @param onError {function(String, {})} invoked on HTTP failure - with status message and XHR error * @param onBid {function({})} invoked once for each bid in the response - with the bid as returned by interpretResponse */ -export const processPBSRequest = hook('sync', function (s2sBidRequest, bidRequests, ajax, {onResponse, onError, onBid}) { +export const processPBSRequest = hook('sync', function (s2sBidRequest, bidRequests, ajax, {onResponse, onError, onBid, onFledge}) { let { gdprConsent } = getConsentData(bidRequests); const adUnits = deepClone(s2sBidRequest.ad_units); @@ -529,8 +548,11 @@ export const processPBSRequest = hook('sync', function (s2sBidRequest, bidReques let result; try { result = JSON.parse(response); - const bids = s2sBidRequest.metrics.measureTime('interpretResponse', () => interpretPBSResponse(result, request).bids); + const {bids, fledgeAuctionConfigs} = s2sBidRequest.metrics.measureTime('interpretResponse', () => interpretPBSResponse(result, request)); bids.forEach(onBid); + if (fledgeAuctionConfigs) { + fledgeAuctionConfigs.forEach(onFledge); + } } catch (error) { logError(error); } @@ -538,7 +560,7 @@ export const processPBSRequest = hook('sync', function (s2sBidRequest, bidReques logError('error parsing response: ', result ? result.status : 'not valid JSON'); onResponse(false, requestedBidders); } else { - onResponse(true, requestedBidders); + onResponse(true, requestedBidders, result); } }, error: function () { @@ -554,6 +576,17 @@ export const processPBSRequest = hook('sync', function (s2sBidRequest, bidReques } }, 'processPBSRequest'); +function shouldEmitNonbids(s2sConfig, response) { + return s2sConfig?.extPrebid?.returnallbidstatus && response?.ext?.seatnonbid; +} + +function emitNonBids(seatnonbid, auctionId) { + events.emit(CONSTANTS.EVENTS.SEAT_NON_BID, { + seatnonbid, + auctionId + }); +} + /** * Global setter that sets eids permissions for bidders * This setter is to be used by userId module when included diff --git a/modules/prebidServerBidAdapter/ortbConverter.js b/modules/prebidServerBidAdapter/ortbConverter.js index 83335f81bc2..820c34c66fd 100644 --- a/modules/prebidServerBidAdapter/ortbConverter.js +++ b/modules/prebidServerBidAdapter/ortbConverter.js @@ -32,6 +32,12 @@ const PBS_CONVERTER = ortbConverter({ imp(buildImp, proxyBidRequest, context) { Object.assign(context, proxyBidRequest.pbsData); const imp = buildImp(proxyBidRequest, context); + (proxyBidRequest.bids || []).forEach(bid => { + if (bid.ortb2Imp && Object.keys(bid.ortb2Imp).length > 0) { + // set bidder-level imp attributes; see https://github.com/prebid/prebid-server/issues/2335 + deepSetValue(imp, `ext.prebid.imp.${bid.bidder}`, bid.ortb2Imp); + } + }); if (Object.values(SUPPORTED_MEDIA_TYPES).some(mtype => imp[mtype])) { imp.secure = context.s2sBidRequest.s2sConfig.secure; return imp; @@ -47,7 +53,7 @@ const PBS_CONVERTER = ortbConverter({ request.tmax = s2sBidRequest.s2sConfig.timeout; deepSetValue(request, 'source.tid', proxyBidderRequest.auctionId); - [request.app, request.site].forEach(section => { + [request.app, request.dooh, request.site].forEach(section => { if (section && !section.publisher?.id) { deepSetValue(section, 'publisher.id', s2sBidRequest.s2sConfig.accountId); } @@ -215,6 +221,13 @@ const PBS_CONVERTER = ortbConverter({ serverSideStats(orig, response, ortbResponse, context) { // override to process each request context.actualBidderRequests.forEach(req => orig(response, ortbResponse, {...context, bidderRequest: req, bidRequests: req.bids})); + }, + fledgeAuctionConfigs(orig, response, ortbResponse, context) { + const configs = Object.values(context.impContext) + .flatMap((impCtx) => (impCtx.fledgeConfigs || []).map(cfg => ({adUnitCode: impCtx.adUnit.code, config: cfg.config}))); + if (configs.length > 0) { + response.fledgeAuctionConfigs = configs; + } } } }, @@ -248,11 +261,14 @@ export function buildPBSRequest(s2sBidRequest, bidderRequests, adUnits, requeste ...adUnit, adUnitCode: adUnit.code, ...getDefinedParams(actualBidRequests.values().next().value || {}, ['userId', 'userIdAsEids', 'schain']), - pbsData: {impId, actualBidRequests, adUnit} + pbsData: {impId, actualBidRequests, adUnit}, }); }); - const proxyBidderRequest = Object.fromEntries(Object.entries(bidderRequests[0]).filter(([k]) => !BIDDER_SPECIFIC_REQUEST_PROPS.has(k))) + const proxyBidderRequest = { + ...Object.fromEntries(Object.entries(bidderRequests[0]).filter(([k]) => !BIDDER_SPECIFIC_REQUEST_PROPS.has(k))), + fledgeEnabled: bidderRequests.some(req => req.fledgeEnabled) + } return PBS_CONVERTER.toORTB({ bidderRequest: proxyBidderRequest, diff --git a/modules/prebidmanagerAnalyticsAdapter.js b/modules/prebidmanagerAnalyticsAdapter.js index 1180a74db30..b877918d16d 100644 --- a/modules/prebidmanagerAnalyticsAdapter.js +++ b/modules/prebidmanagerAnalyticsAdapter.js @@ -2,16 +2,17 @@ import { generateUUID, getParameterByName, logError, parseUrl, logInfo } from '. import {ajaxBuilder} 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'; /** * prebidmanagerAnalyticsAdapter.js - analytics adapter for prebidmanager */ -export const storage = getStorageManager({gvlid: undefined, moduleName: 'prebidmanager'}); -const DEFAULT_EVENT_URL = 'https://endpoint.prebidmanager.com/endpoint'; +export const storage = getStorageManager({moduleType: MODULE_TYPE_ANALYTICS, moduleName: 'prebidmanager'}); +const DEFAULT_EVENT_URL = 'https://endpt.prebidmanager.com/endpoint'; const analyticsType = 'endpoint'; -const analyticsName = 'Prebid Manager Analytics: '; +const analyticsName = 'Prebid Manager Analytics'; let ajax = ajaxBuilder(0); @@ -28,8 +29,8 @@ var w = window; var d = document; var e = d.documentElement; var g = d.getElementsByTagName('body')[0]; -var x = w.innerWidth || e.clientWidth || g.clientWidth; -var y = w.innerHeight || e.clientHeight || g.clientHeight; +var x = (w && w.innerWidth) || (e && e.clientWidth) || (g && g.clientWidth); +var y = (w && w.innerHeight) || (e && e.clientHeight) || (g && g.clientHeight); var _pageView = { eventType: 'pageView', @@ -56,13 +57,20 @@ prebidmanagerAnalytics.originEnableAnalytics = prebidmanagerAnalytics.enableAnal prebidmanagerAnalytics.enableAnalytics = function (config) { initOptions = config.options || {}; initOptions.url = initOptions.url || DEFAULT_EVENT_URL; - pmAnalyticsEnabled = true; + initOptions.sampling = initOptions.sampling || 1; + + if (Math.floor(Math.random() * initOptions.sampling) === 0) { + pmAnalyticsEnabled = true; + flushInterval = setInterval(flush, 1000); + } else { + logInfo(`${analyticsName} isn't enabled because of sampling`); + } + prebidmanagerAnalytics.originEnableAnalytics(config); - flushInterval = setInterval(flush, 1000); }; prebidmanagerAnalytics.originDisableAnalytics = prebidmanagerAnalytics.disableAnalytics; -prebidmanagerAnalytics.disableAnalytics = function() { +prebidmanagerAnalytics.disableAnalytics = function () { if (!pmAnalyticsEnabled) { return; } @@ -95,7 +103,7 @@ function collectUtmTagData() { }); } } catch (e) { - logError(`${analyticsName}Error`, e); + logError(`${analyticsName} Error`, e); pmUtmTags['error_utm'] = 1; } return pmUtmTags; @@ -126,6 +134,16 @@ function flush() { pageInfo: collectPageInfo(), }; + if ('version' in initOptions) { + data.version = initOptions.version; + } + if ('tcf_compliant' in initOptions) { + data.tcf_compliant = initOptions.tcf_compliant; + } + if ('sampling' in initOptions) { + data.sampling = initOptions.sampling; + } + ajax( initOptions.url, () => logInfo(`${analyticsName} sent events batch`), @@ -142,65 +160,156 @@ function flush() { } } +function trimAdUnit(adUnit) { + if (!adUnit) return adUnit; + const res = {}; + res.code = adUnit.code; + res.sizes = adUnit.sizes; + return res; +} + +function trimBid(bid) { + if (!bid) return bid; + const res = {}; + res.auctionId = bid.auctionId; + res.bidder = bid.bidder; + res.bidderRequestId = bid.bidderRequestId; + res.bidId = bid.bidId; + res.crumbs = bid.crumbs; + res.cpm = bid.cpm; + res.currency = bid.currency; + res.mediaTypes = bid.mediaTypes; + res.sizes = bid.sizes; + res.transactionId = bid.transactionId; + res.adUnitCode = bid.adUnitCode; + res.bidRequestsCount = bid.bidRequestsCount; + res.serverResponseTimeMs = bid.serverResponseTimeMs; + return res; +} + +function trimBidderRequest(bidderRequest) { + if (!bidderRequest) return bidderRequest; + const res = {}; + res.auctionId = bidderRequest.auctionId; + res.auctionStart = bidderRequest.auctionStart; + res.bidderRequestId = bidderRequest.bidderRequestId; + res.bidderCode = bidderRequest.bidderCode; + res.bids = bidderRequest.bids && bidderRequest.bids.map(trimBid); + return res; +} + function handleEvent(eventType, eventArgs) { - eventArgs = eventArgs ? JSON.parse(JSON.stringify(eventArgs)) : {}; - var pmEvent = {}; + try { + eventArgs = eventArgs ? JSON.parse(JSON.stringify(eventArgs)) : {}; + } catch (e) { + // keep eventArgs as is + } + + const pmEvent = {}; switch (eventType) { case CONSTANTS.EVENTS.AUCTION_INIT: { - pmEvent = eventArgs; + pmEvent.auctionId = eventArgs.auctionId; + pmEvent.timeout = eventArgs.timeout; + pmEvent.eventType = eventArgs.eventType; + pmEvent.adUnits = eventArgs.adUnits && eventArgs.adUnits.map(trimAdUnit) + pmEvent.bidderRequests = eventArgs.bidderRequests && eventArgs.bidderRequests.map(trimBidderRequest) _startAuction = pmEvent.timestamp; _bidRequestTimeout = pmEvent.timeout; break; } case CONSTANTS.EVENTS.AUCTION_END: { - pmEvent = eventArgs; + pmEvent.auctionId = eventArgs.auctionId; + pmEvent.end = eventArgs.end; + pmEvent.start = eventArgs.start; + pmEvent.adUnitCodes = eventArgs.adUnitCodes; + pmEvent.bidsReceived = eventArgs.bidsReceived && eventArgs.bidsReceived.map(trimBid); pmEvent.start = _startAuction; pmEvent.end = Date.now(); break; } case CONSTANTS.EVENTS.BID_ADJUSTMENT: { - pmEvent.bidders = eventArgs; break; } case CONSTANTS.EVENTS.BID_TIMEOUT: { - pmEvent.bidders = eventArgs; + pmEvent.bidders = eventArgs && eventArgs.map ? eventArgs.map(trimBid) : eventArgs; pmEvent.duration = _bidRequestTimeout; break; } case CONSTANTS.EVENTS.BID_REQUESTED: { - pmEvent = eventArgs; + pmEvent.auctionId = eventArgs.auctionId; + pmEvent.bidderCode = eventArgs.bidderCode; + pmEvent.doneCbCallCount = eventArgs.doneCbCallCount; + pmEvent.start = eventArgs.start; + pmEvent.bidderRequestId = eventArgs.bidderRequestId; + pmEvent.bids = eventArgs.bids && eventArgs.bids.map(trimBid); + pmEvent.auctionStart = eventArgs.auctionStart; + pmEvent.timeout = eventArgs.timeout; break; } case CONSTANTS.EVENTS.BID_RESPONSE: { - pmEvent = eventArgs; - delete pmEvent.ad; + pmEvent.bidderCode = eventArgs.bidderCode; + pmEvent.width = eventArgs.width; + pmEvent.height = eventArgs.height; + pmEvent.adId = eventArgs.adId; + pmEvent.mediaType = eventArgs.mediaType; + pmEvent.cpm = eventArgs.cpm; + pmEvent.currency = eventArgs.currency; + pmEvent.requestId = eventArgs.requestId; + pmEvent.adUnitCode = eventArgs.adUnitCode; + pmEvent.auctionId = eventArgs.auctionId; + pmEvent.timeToRespond = eventArgs.timeToRespond; + pmEvent.responseTimestamp = eventArgs.responseTimestamp; + pmEvent.requestTimestamp = eventArgs.requestTimestamp; + pmEvent.netRevenue = eventArgs.netRevenue; + pmEvent.size = eventArgs.size; + pmEvent.adserverTargeting = eventArgs.adserverTargeting; break; } case CONSTANTS.EVENTS.BID_WON: { - pmEvent = eventArgs; - delete pmEvent.ad; - delete pmEvent.adUrl; + pmEvent.auctionId = eventArgs.auctionId; + pmEvent.adId = eventArgs.adId; + pmEvent.adserverTargeting = eventArgs.adserverTargeting; + pmEvent.adUnitCode = eventArgs.adUnitCode; + pmEvent.bidderCode = eventArgs.bidderCode; + pmEvent.height = eventArgs.height; + pmEvent.mediaType = eventArgs.mediaType; + pmEvent.netRevenue = eventArgs.netRevenue; + pmEvent.cpm = eventArgs.cpm; + pmEvent.requestTimestamp = eventArgs.requestTimestamp; + pmEvent.responseTimestamp = eventArgs.responseTimestamp; + pmEvent.size = eventArgs.size; + pmEvent.width = eventArgs.width; + pmEvent.currency = eventArgs.currency; + pmEvent.bidder = eventArgs.bidder; break; } case CONSTANTS.EVENTS.BIDDER_DONE: { - pmEvent = eventArgs; + pmEvent.auctionId = eventArgs.auctionId; + pmEvent.auctionStart = eventArgs.auctionStart; + pmEvent.bidderCode = eventArgs.bidderCode; + pmEvent.bidderRequestId = eventArgs.bidderRequestId; + pmEvent.bids = eventArgs.bids && eventArgs.bids.map(trimBid); + pmEvent.doneCbCallCount = eventArgs.doneCbCallCount; + pmEvent.start = eventArgs.start; + pmEvent.timeout = eventArgs.timeout; + pmEvent.tid = eventArgs.tid; + pmEvent.src = eventArgs.src; break; } case CONSTANTS.EVENTS.SET_TARGETING: { - pmEvent.targetings = eventArgs; break; } case CONSTANTS.EVENTS.REQUEST_BIDS: { - pmEvent = eventArgs; break; } case CONSTANTS.EVENTS.ADD_AD_UNITS: { - pmEvent = eventArgs; break; } case CONSTANTS.EVENTS.AD_RENDER_FAILED: { - pmEvent = eventArgs; + pmEvent.bid = eventArgs.bid; + pmEvent.message = eventArgs.message; + pmEvent.reason = eventArgs.reason; break; } default: @@ -215,7 +324,7 @@ function handleEvent(eventType, eventArgs) { function sendEvent(event) { _eventQueue.push(event); - logInfo(`${analyticsName}Event ${event.eventType}:`, event); + logInfo(`${analyticsName} Event ${event.eventType}:`, event); if (event.eventType === CONSTANTS.EVENTS.AUCTION_END) { flush(); diff --git a/modules/priceFloors.js b/modules/priceFloors.js index 8b5976149d0..92aecb0ca50 100644 --- a/modules/priceFloors.js +++ b/modules/priceFloors.js @@ -28,6 +28,7 @@ import {auctionManager} from '../src/auctionManager.js'; import {IMP, PBS, registerOrtbProcessor, REQUEST} from '../src/pbjsORTB.js'; import {timedAuctionHook, timedBidResponseHook} from '../src/utils/perfMetrics.js'; import {beConvertCurrency} from '../src/utils/currency.js'; +import {adjustCpm} from '../src/utils/cpm.js'; /** * @summary This Module is intended to provide users with the ability to dynamically set and enforce price floors on a per auction basis. @@ -179,12 +180,8 @@ function generatePossibleEnumerations(arrayOfFields, delimiter) { /** * @summary If a the input bidder has a registered cpmadjustment it returns the input CPM after being adjusted */ -export function getBiddersCpmAdjustment(bidderName, inputCpm, bid = {}) { - const adjustmentFunction = bidderSettings.get(bidderName, 'bidCpmAdjustment'); - if (adjustmentFunction) { - return parseFloat(adjustmentFunction(inputCpm, {...bid, cpm: inputCpm})); - } - return parseFloat(inputCpm); +export function getBiddersCpmAdjustment(inputCpm, bid, bidRequest) { + return parseFloat(adjustCpm(inputCpm, {...bid, cpm: inputCpm}, bidRequest)); } /** @@ -249,8 +246,14 @@ export function getFloor(requestParams = {currency: 'USD', mediaType: '*', size: // if cpmAdjustment flag is true and we have a valid floor then run the adjustment on it if (floorData.enforcement.bidAdjustment && floorInfo.matchingFloor) { - let cpmAdjustment = getBiddersCpmAdjustment(bidRequest.bidder, floorInfo.matchingFloor); - floorInfo.matchingFloor = cpmAdjustment ? calculateAdjustedFloor(floorInfo.matchingFloor, cpmAdjustment) : floorInfo.matchingFloor; + // pub provided inverse function takes precedence, otherwise do old adjustment stuff + const inverseFunction = bidderSettings.get(bidRequest.bidder, 'inverseBidAdjustment'); + if (inverseFunction) { + floorInfo.matchingFloor = inverseFunction(floorInfo.matchingFloor, bidRequest); + } else { + let cpmAdjustment = getBiddersCpmAdjustment(floorInfo.matchingFloor, null, bidRequest); + floorInfo.matchingFloor = cpmAdjustment ? calculateAdjustedFloor(floorInfo.matchingFloor, cpmAdjustment) : floorInfo.matchingFloor; + } } if (floorInfo.matchingFloor) { @@ -307,6 +310,8 @@ export function getFloorDataFromAdUnits(adUnits) { // copy over the new rules into our values object Object.assign(accum.values, newRules); } + } else if (adUnit.floors != null) { + logWarn(`adUnit '${adUnit.code}' provides an invalid \`floor\` definition, it will be ignored for floor calculations`, adUnit); } return accum; }, {}); @@ -731,7 +736,7 @@ export const addBidResponseHook = timedBidResponseHook('priceFloors', function a } // ok we got the bid response cpm in our desired currency. Now we need to run the bidders CPMAdjustment function if it exists - adjustedCpm = getBiddersCpmAdjustment(bid.bidderCode, adjustedCpm, bid); + adjustedCpm = getBiddersCpmAdjustment(adjustedCpm, bid, matchingBidRequest); // add necessary data information for analytics adapters / floor providers would possibly need addFloorDataToBid(floorData, floorInfo, bid, adjustedCpm); diff --git a/modules/proxistoreBidAdapter.js b/modules/proxistoreBidAdapter.js index 04d363be7fb..1f113f9c432 100644 --- a/modules/proxistoreBidAdapter.js +++ b/modules/proxistoreBidAdapter.js @@ -3,9 +3,9 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; const BIDDER_CODE = 'proxistore'; const PROXISTORE_VENDOR_ID = 418; -const COOKIE_BASE_URL = 'https://abs.proxistore.com/v3/rtb/prebid/multi'; +const COOKIE_BASE_URL = 'https://api.proxistore.com/v3/rtb/prebid/multi'; const COOKIE_LESS_URL = - 'https://abs.cookieless-proxistore.com/v3/rtb/prebid/multi'; + 'https://api.cookieless-proxistore.com/v3/rtb/prebid/multi'; function _createServerRequest(bidRequests, bidderRequest) { var sizeIds = []; diff --git a/modules/pubCommonId.js b/modules/pubCommonId.js index 1fde8f8db5b..ba24190322e 100644 --- a/modules/pubCommonId.js +++ b/modules/pubCommonId.js @@ -7,11 +7,15 @@ import { logMessage, parseUrl, buildUrl, triggerPixel, generateUUID, isArray } f import { config } from '../src/config.js'; import * as events from '../src/events.js'; import CONSTANTS from '../src/constants.json'; -import { getStorageManager } from '../src/storageManager.js'; +import {getStorageManager} from '../src/storageManager.js'; import {timedAuctionHook} from '../src/utils/perfMetrics.js'; -import {VENDORLESS_GVLID} from '../src/consentHandler.js'; +import {GDPR_GVLIDS, VENDORLESS_GVLID} from '../src/consentHandler.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; +import {getGlobal} from '../src/prebidGlobal.js'; -const storage = getStorageManager({moduleName: 'pubCommonId', gvlid: VENDORLESS_GVLID}); +const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: 'pubCommonId'}); +// register our GVL ID directly, since this is not a "real" user ID module we don't have a spec where to declare it +GDPR_GVLIDS.register(MODULE_TYPE_UID, 'pubCommonId', VENDORLESS_GVLID); const ID_NAME = '_pubcid'; const OPTOUT_NAME = '_pubcid_optout'; @@ -168,7 +172,7 @@ export function getPubcidConfig() { return pubcidConfig; } */ export const requestBidHook = timedAuctionHook('pubCommonId', function requestBidHook(next, config) { - let adUnits = config.adUnits || $$PREBID_GLOBAL$$.adUnits; + let adUnits = config.adUnits || getGlobal().adUnits; let pubcid = null; // Pass control to the next function if not enabled @@ -292,7 +296,7 @@ export function initPubcid() { (storage.hasLocalStorage() && readValue(OPTOUT_NAME, LOCAL_STORAGE)); if (!optout) { - $$PREBID_GLOBAL$$.requestBids.before(requestBidHook); + getGlobal().requestBids.before(requestBidHook); } } diff --git a/modules/publinkIdSystem.js b/modules/publinkIdSystem.js index 9d5645a38cb..6a5504b5ba0 100644 --- a/modules/publinkIdSystem.js +++ b/modules/publinkIdSystem.js @@ -10,13 +10,14 @@ import {getStorageManager} from '../src/storageManager.js'; import {ajax} from '../src/ajax.js'; import { parseUrl, buildUrl, logError } from '../src/utils.js'; import {uspDataHandler} from '../src/adapterManager.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; const MODULE_NAME = 'publinkId'; const GVLID = 24; const PUBLINK_COOKIE = '_publink'; const PUBLINK_S2S_COOKIE = '_publink_srv'; -export const storage = getStorageManager({gvlid: GVLID}); +export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); function isHex(s) { return /^[A-F0-9]+$/i.test(s); diff --git a/modules/pubmaticAnalyticsAdapter.js b/modules/pubmaticAnalyticsAdapter.js index cae94f6fe7b..9e2a5b1cfeb 100755 --- a/modules/pubmaticAnalyticsAdapter.js +++ b/modules/pubmaticAnalyticsAdapter.js @@ -224,6 +224,38 @@ function getAdDomain(bidResponse) { } } +function isObject(object) { + return typeof object === 'object' && object !== null; +}; + +function isEmptyObject(object) { + return isObject(object) && Object.keys(object).length === 0; +}; + +/** + * Prepare meta object to pass in logger call + * @param {*} meta + */ +export function getMetadata(meta) { + if (!meta || isEmptyObject(meta)) return; + const metaObj = {}; + if (meta.networkId) metaObj.nwid = meta.networkId; + if (meta.advertiserId) metaObj.adid = meta.advertiserId; + if (meta.networkName) metaObj.nwnm = meta.networkName; + if (meta.primaryCatId) metaObj.pcid = meta.primaryCatId; + if (meta.advertiserName) metaObj.adnm = meta.advertiserName; + if (meta.agencyId) metaObj.agid = meta.agencyId; + if (meta.agencyName) metaObj.agnm = meta.agencyName; + if (meta.brandId) metaObj.brid = meta.brandId; + if (meta.brandName) metaObj.brnm = meta.brandName; + if (meta.dchain) metaObj.dc = meta.dchain; + if (meta.demandSource) metaObj.ds = meta.demandSource; + if (meta.secondaryCatIds) metaObj.scids = meta.secondaryCatIds; + + if (isEmptyObject(metaObj)) return; + return metaObj; +} + function gatherPartnerBidsForAdUnitForLogger(adUnit, adUnitId, highestBid) { highestBid = (highestBid && highestBid.length > 0) ? highestBid[0] : null; return Object.keys(adUnit.bids).reduce(function(partnerBids, bidId) { @@ -251,13 +283,25 @@ function gatherPartnerBidsForAdUnitForLogger(adUnit, adUnitId, highestBid) { 'ocpm': bid.bidResponse ? (bid.bidResponse.originalCpm || 0) : 0, 'ocry': bid.bidResponse ? (bid.bidResponse.originalCurrency || CURRENCY_USD) : CURRENCY_USD, 'piid': bid.bidResponse ? (bid.bidResponse.partnerImpId || EMPTY_STRING) : EMPTY_STRING, - 'frv': (s2sBidders.indexOf(bid.bidder) > -1) ? undefined : (bid.bidResponse ? (bid.bidResponse.floorData ? bid.bidResponse.floorData.floorRuleValue : undefined) : undefined) + 'frv': (s2sBidders.indexOf(bid.bidder) > -1) ? undefined : (bid.bidResponse ? (bid.bidResponse.floorData ? bid.bidResponse.floorData.floorRuleValue : undefined) : undefined), + 'md': bid.bidResponse ? getMetadata(bid.bidResponse.meta) : undefined }); }); return partnerBids; }, []) } +function getSizesForAdUnit(adUnit) { + var bid = Object.values(adUnit.bids).filter((bid) => !!bid.bidResponse && bid.bidResponse.mediaType === 'native')[0]; + if (!!bid || (bid === undefined && adUnit.dimensions.length === 0)) { + return ['1x1']; + } else { + return adUnit.dimensions.map(function (e) { + return e[0] + 'x' + e[1]; + }) + } +} + function getAdUnitAdFormats(adUnit) { var af = adUnit ? Object.keys(adUnit.mediaTypes || {}).map(format => MEDIATYPE[format.toUpperCase()]) : []; return af; @@ -313,7 +357,7 @@ function executeBidsLoggerCall(e, highestCpmBids) { 'sn': adUnitId, 'au': origAdUnit.adUnitId || adUnitId, 'mt': getAdUnitAdFormats(origAdUnit), - 'sz': adUnit.dimensions.map(e => e[0] + 'x' + e[1]), + 'sz': getSizesForAdUnit(adUnit, adUnitId), 'ps': gatherPartnerBidsForAdUnitForLogger(adUnit, adUnitId, highestCpmBids.filter(bid => bid.adUnitCode === adUnitId)), 'fskp': floorData ? (floorData.floorRequestData ? (floorData.floorRequestData.skipped == false ? 0 : 1) : undefined) : undefined, }; diff --git a/modules/pubmaticBidAdapter.js b/modules/pubmaticBidAdapter.js index 296a4314ac8..edfee7a0b6d 100644 --- a/modules/pubmaticBidAdapter.js +++ b/modules/pubmaticBidAdapter.js @@ -1,10 +1,10 @@ -import { getBidRequest, logWarn, _each, isBoolean, isStr, isArray, inIframe, mergeDeep, deepAccess, isNumber, deepSetValue, logInfo, logError, deepClone, convertTypes, uniques } from '../src/utils.js'; +import { getBidRequest, logWarn, isBoolean, isStr, isArray, inIframe, mergeDeep, deepAccess, isNumber, deepSetValue, logInfo, logError, deepClone, convertTypes, uniques, isPlainObject, isInteger } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO, NATIVE, ADPOD } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; import { Renderer } from '../src/Renderer.js'; import { bidderSettings } from '../src/bidderSettings.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import CONSTANTS from '../src/constants.json'; const BIDDER_CODE = 'pubmatic'; const LOG_WARN_PREFIX = 'PubMatic: '; @@ -20,6 +20,7 @@ const PREBID_NATIVE_HELP_LINK = 'http://prebid.org/dev-docs/show-native-ads.html const PUBLICATION = 'pubmatic'; // Your publication on Blue Billywig, potentially with environment (e.g. publication.bbvms.com or publication.test.bbvms.com) const RENDERER_URL = 'https://pubmatic.bbvms.com/r/'.concat('$RENDERER', '.js'); // URL of the renderer application const MSG_VIDEO_PLACEMENT_MISSING = 'Video.Placement param missing'; + const CUSTOM_PARAMS = { 'kadpageurl': '', // Custom page url 'gender': '', // User gender @@ -55,56 +56,11 @@ const VIDEO_CUSTOM_PARAMS = { 'skip': DATA_TYPES.NUMBER } -const NATIVE_ASSETS = { - 'TITLE': { ID: 1, KEY: 'title', TYPE: 0 }, - 'IMAGE': { ID: 2, KEY: 'image', TYPE: 0 }, - 'ICON': { ID: 3, KEY: 'icon', TYPE: 0 }, - 'SPONSOREDBY': { ID: 4, KEY: 'sponsoredBy', TYPE: 1 }, // please note that type of SPONSORED is also 1 - 'BODY': { ID: 5, KEY: 'body', TYPE: 2 }, // please note that type of DESC is also set to 2 - 'CLICKURL': { ID: 6, KEY: 'clickUrl', TYPE: 0 }, - 'VIDEO': { ID: 7, KEY: 'video', TYPE: 0 }, - 'EXT': { ID: 8, KEY: 'ext', TYPE: 0 }, - 'DATA': { ID: 9, KEY: 'data', TYPE: 0 }, - 'LOGO': { ID: 10, KEY: 'logo', TYPE: 0 }, - 'SPONSORED': { ID: 11, KEY: 'sponsored', TYPE: 1 }, // please note that type of SPONSOREDBY is also set to 1 - 'DESC': { ID: 12, KEY: 'data', TYPE: 2 }, // please note that type of BODY is also set to 2 - 'RATING': { ID: 13, KEY: 'rating', TYPE: 3 }, - 'LIKES': { ID: 14, KEY: 'likes', TYPE: 4 }, - 'DOWNLOADS': { ID: 15, KEY: 'downloads', TYPE: 5 }, - 'PRICE': { ID: 16, KEY: 'price', TYPE: 6 }, - 'SALEPRICE': { ID: 17, KEY: 'saleprice', TYPE: 7 }, - 'PHONE': { ID: 18, KEY: 'phone', TYPE: 8 }, - 'ADDRESS': { ID: 19, KEY: 'address', TYPE: 9 }, - 'DESC2': { ID: 20, KEY: 'desc2', TYPE: 10 }, - 'DISPLAYURL': { ID: 21, KEY: 'displayurl', TYPE: 11 }, - 'CTA': { ID: 22, KEY: 'cta', TYPE: 12 } -}; - const NATIVE_ASSET_IMAGE_TYPE = { 'ICON': 1, - 'LOGO': 2, 'IMAGE': 3 } -// check if title, image can be added with mandatory field default values -const NATIVE_MINIMUM_REQUIRED_IMAGE_ASSETS = [ - { - id: NATIVE_ASSETS.SPONSOREDBY.ID, - required: true, - data: { - type: 1 - } - }, - { - id: NATIVE_ASSETS.TITLE.ID, - required: true, - }, - { - id: NATIVE_ASSETS.IMAGE.ID, - required: true, - } -] - const NET_REVENUE = true; const dealChannelValues = { 1: 'PMP', @@ -175,16 +131,9 @@ const MEDIATYPE = [ let publisherId = 0; let isInvalidNativeRequest = false; -let NATIVE_ASSET_ID_TO_KEY_MAP = {}; -let NATIVE_ASSET_KEY_TO_ASSET_MAP = {}; let biddersList = ['pubmatic']; const allBiddersList = ['all']; -// loading NATIVE_ASSET_ID_TO_KEY_MAP -_each(NATIVE_ASSETS, anAsset => { NATIVE_ASSET_ID_TO_KEY_MAP[anAsset.ID] = anAsset.KEY }); -// loading NATIVE_ASSET_KEY_TO_ASSET_MAP -_each(NATIVE_ASSETS, anAsset => { NATIVE_ASSET_KEY_TO_ASSET_MAP[anAsset.KEY] = anAsset }); - export function _getDomainFromURL(url) { let anchor = document.createElement('a'); anchor.href = url; @@ -355,137 +304,189 @@ function _checkParamDataType(key, value, datatype) { return UNDEFINED; } -function _commonNativeRequestObject(nativeAsset, params) { - var key = nativeAsset.KEY; - return { - id: nativeAsset.ID, - required: params[key].required ? 1 : 0, - data: { - type: nativeAsset.TYPE, - len: params[key].len, - ext: params[key].ext - } - }; -} +// TODO delete this code when removing native 1.1 support +const PREBID_NATIVE_DATA_KEYS_TO_ORTB = { + 'desc': 'desc', + 'desc2': 'desc2', + 'body': 'desc', + 'body2': 'desc2', + 'sponsoredBy': 'sponsored', + 'cta': 'ctatext', + 'rating': 'rating', + 'address': 'address', + 'downloads': 'downloads', + 'likes': 'likes', + 'phone': 'phone', + 'price': 'price', + 'salePrice': 'saleprice', + 'displayUrl': 'displayurl', + 'saleprice': 'saleprice', + 'displayurl': 'displayurl' +}; -function _createNativeRequest(params) { - var nativeRequestObject = { +const { NATIVE_IMAGE_TYPES, NATIVE_KEYS_THAT_ARE_NOT_ASSETS, NATIVE_KEYS, NATIVE_ASSET_TYPES } = CONSTANTS; +const PREBID_NATIVE_DATA_KEY_VALUES = Object.values(PREBID_NATIVE_DATA_KEYS_TO_ORTB); + +// TODO remove this function when the support for 1.1 is removed +/** + * Copy of the function toOrtbNativeRequest from core native.js to handle the title len/length + * and ext and mimes parameters from legacy assets. + * @param {object} legacyNativeAssets + * @returns an OpenRTB format of the same bid request + */ +export function toOrtbNativeRequest(legacyNativeAssets) { + if (!legacyNativeAssets && !isPlainObject(legacyNativeAssets)) { + logWarn(`${LOG_WARN_PREFIX}: Native assets object is empty or not an object: ${legacyNativeAssets}`); + isInvalidNativeRequest = true; + return; + } + const ortb = { + ver: '1.2', assets: [] }; - for (var key in params) { - if (params.hasOwnProperty(key)) { - var assetObj = {}; - if (!(nativeRequestObject.assets && nativeRequestObject.assets.length > 0 && nativeRequestObject.assets.hasOwnProperty(key))) { - switch (key) { - case NATIVE_ASSETS.TITLE.KEY: - if (params[key].len || params[key].length) { - assetObj = { - id: NATIVE_ASSETS.TITLE.ID, - required: params[key].required ? 1 : 0, - title: { - len: params[key].len || params[key].length, - ext: params[key].ext - } - }; - } else { - logWarn(LOG_WARN_PREFIX + 'Error: Title Length is required for native ad: ' + JSON.stringify(params)); + for (let key in legacyNativeAssets) { + // skip conversion for non-asset keys + if (NATIVE_KEYS_THAT_ARE_NOT_ASSETS.includes(key)) continue; + if (!NATIVE_KEYS.hasOwnProperty(key) && !PREBID_NATIVE_DATA_KEY_VALUES.includes(key)) { + logWarn(`${LOG_WARN_PREFIX}: Unrecognized native asset code: ${key}. Asset will be ignored.`); + continue; + } + + const asset = legacyNativeAssets[key]; + let required = 0; + if (asset.required && isBoolean(asset.required)) { + required = Number(asset.required); + } + const ortbAsset = { + id: ortb.assets.length, + required + }; + // data cases + if (key in PREBID_NATIVE_DATA_KEYS_TO_ORTB) { + ortbAsset.data = { + type: NATIVE_ASSET_TYPES[PREBID_NATIVE_DATA_KEYS_TO_ORTB[key]] + } + if (asset.len || asset.length) { + ortbAsset.data.len = asset.len || asset.length; + } + if (asset.ext) { + ortbAsset.data.ext = asset.ext; + } + // icon or image case + } else if (key === 'icon' || key === 'image') { + ortbAsset.img = { + type: key === 'icon' ? NATIVE_IMAGE_TYPES.ICON : NATIVE_IMAGE_TYPES.MAIN, + } + // if min_width and min_height are defined in aspect_ratio, they are preferred + if (asset.aspect_ratios) { + if (!isArray(asset.aspect_ratios)) { + logWarn(`${LOG_WARN_PREFIX}: image.aspect_ratios was passed, but it's not a an array: ${asset.aspect_ratios}`); + } else if (!asset.aspect_ratios.length) { + logWarn(`${LOG_WARN_PREFIX}: image.aspect_ratios was passed, but it's empty: ${asset.aspect_ratios}`); + } else { + const { min_width: minWidth, min_height: minHeight } = asset.aspect_ratios[0]; + if (!isInteger(minWidth) || !isInteger(minHeight)) { + logWarn(`${LOG_WARN_PREFIX}: image.aspect_ratios min_width or min_height are invalid: ${minWidth}, ${minHeight}`); + } else { + ortbAsset.img.wmin = minWidth; + ortbAsset.img.hmin = minHeight; + } + const aspectRatios = asset.aspect_ratios + .filter((ar) => ar.ratio_width && ar.ratio_height) + .map(ratio => `${ratio.ratio_width}:${ratio.ratio_height}`); + if (aspectRatios.length > 0) { + ortbAsset.img.ext = { + aspectratios: aspectRatios } - break; - case NATIVE_ASSETS.IMAGE.KEY: - assetObj = { - id: NATIVE_ASSETS.IMAGE.ID, - required: params[key].required ? 1 : 0, - img: { - type: NATIVE_ASSET_IMAGE_TYPE.IMAGE, - w: params[key].w || params[key].width || (params[key].sizes ? params[key].sizes[0] : UNDEFINED), - h: params[key].h || params[key].height || (params[key].sizes ? params[key].sizes[1] : UNDEFINED), - wmin: params[key].wmin || params[key].minimumWidth || (params[key].minsizes ? params[key].minsizes[0] : UNDEFINED), - hmin: params[key].hmin || params[key].minimumHeight || (params[key].minsizes ? params[key].minsizes[1] : UNDEFINED), - mimes: params[key].mimes, - ext: params[key].ext, - } - }; - break; - case NATIVE_ASSETS.ICON.KEY: - assetObj = { - id: NATIVE_ASSETS.ICON.ID, - required: params[key].required ? 1 : 0, - img: { - type: NATIVE_ASSET_IMAGE_TYPE.ICON, - w: params[key].w || params[key].width || (params[key].sizes ? params[key].sizes[0] : UNDEFINED), - h: params[key].h || params[key].height || (params[key].sizes ? params[key].sizes[1] : UNDEFINED), - } - }; - break; - case NATIVE_ASSETS.VIDEO.KEY: - assetObj = { - id: NATIVE_ASSETS.VIDEO.ID, - required: params[key].required ? 1 : 0, - video: { - minduration: params[key].minduration, - maxduration: params[key].maxduration, - protocols: params[key].protocols, - mimes: params[key].mimes, - ext: params[key].ext - } - }; - break; - case NATIVE_ASSETS.EXT.KEY: - assetObj = { - id: NATIVE_ASSETS.EXT.ID, - required: params[key].required ? 1 : 0, - }; - break; - case NATIVE_ASSETS.LOGO.KEY: - assetObj = { - id: NATIVE_ASSETS.LOGO.ID, - required: params[key].required ? 1 : 0, - img: { - type: NATIVE_ASSET_IMAGE_TYPE.LOGO, - w: params[key].w || params[key].width || (params[key].sizes ? params[key].sizes[0] : UNDEFINED), - h: params[key].h || params[key].height || (params[key].sizes ? params[key].sizes[1] : UNDEFINED) - } - }; - break; - case NATIVE_ASSETS.SPONSOREDBY.KEY: - case NATIVE_ASSETS.BODY.KEY: - case NATIVE_ASSETS.RATING.KEY: - case NATIVE_ASSETS.LIKES.KEY: - case NATIVE_ASSETS.DOWNLOADS.KEY: - case NATIVE_ASSETS.PRICE.KEY: - case NATIVE_ASSETS.SALEPRICE.KEY: - case NATIVE_ASSETS.PHONE.KEY: - case NATIVE_ASSETS.ADDRESS.KEY: - case NATIVE_ASSETS.DESC2.KEY: - case NATIVE_ASSETS.DISPLAYURL.KEY: - case NATIVE_ASSETS.CTA.KEY: - assetObj = _commonNativeRequestObject(NATIVE_ASSET_KEY_TO_ASSET_MAP[key], params); - break; + } } } + + ortbAsset.img.w = asset.w || asset.width; + ortbAsset.img.h = asset.h || asset.height; + ortbAsset.img.wmin = asset.wmin || asset.minimumWidth || (asset.minsizes ? asset.minsizes[0] : UNDEFINED); + ortbAsset.img.hmin = asset.hmin || asset.minimumHeight || (asset.minsizes ? asset.minsizes[1] : UNDEFINED); + + // if asset.sizes exist, by OpenRTB spec we should remove wmin and hmin + if (asset.sizes) { + if (asset.sizes.length !== 2 || !isInteger(asset.sizes[0]) || !isInteger(asset.sizes[1])) { + logWarn(`${LOG_WARN_PREFIX}: image.sizes was passed, but its value is not an array of integers: ${asset.sizes}`); + } else { + logInfo(`${LOG_WARN_PREFIX}: if asset.sizes exist, by OpenRTB spec we should remove wmin and hmin`); + ortbAsset.img.w = asset.sizes[0]; + ortbAsset.img.h = asset.sizes[1]; + delete ortbAsset.img.hmin; + delete ortbAsset.img.wmin; + } + } + asset.ext && (ortbAsset.img.ext = asset.ext); + asset.mimes && (ortbAsset.img.mimes = asset.mimes); + // title case + } else if (key === 'title') { + ortbAsset.title = { + // in openRTB, len is required for titles, while in legacy prebid was not. + // for this reason, if len is missing in legacy prebid, we're adding a default value of 140. + len: asset.len || asset.length || 140 + } + asset.ext && (ortbAsset.title.ext = asset.ext); + // all extensions to the native bid request are passed as is + } else if (key === 'ext') { + ortbAsset.ext = asset; + // in `ext` case, required field is not needed + delete ortbAsset.required; } - if (assetObj && assetObj.id) { - nativeRequestObject.assets[nativeRequestObject.assets.length] = assetObj; + ortb.assets.push(ortbAsset); + } + + if (ortb.assets.length < 1) { + logWarn(`${LOG_WARN_PREFIX}: Could not find any valid asset`); + isInvalidNativeRequest = true; + return; + } + + return ortb; +} +// TODO delete this code when removing native 1.1 support + +function _createNativeRequest(params) { + var nativeRequestObject; + + // TODO delete this code when removing native 1.1 support + if (!params.ortb) { // legacy assets definition found + nativeRequestObject = toOrtbNativeRequest(params); + } else { // ortb assets definition found + params = params.ortb; + // TODO delete this code when removing native 1.1 support + nativeRequestObject = { ver: '1.2', ...params, assets: [] }; + const { assets } = params; + + const isValidAsset = (asset) => asset.title || asset.img || asset.data || asset.video; + + if (assets.length < 1 || !assets.some(asset => isValidAsset(asset))) { + logWarn(`${LOG_WARN_PREFIX}: Native assets object is empty or contains some invalid object`); + isInvalidNativeRequest = true; + return nativeRequestObject; } - }; - // for native image adtype prebid has to have few required assests i.e. title,sponsoredBy, image - // if any of these are missing from the request then request will not be sent - var requiredAssetCount = NATIVE_MINIMUM_REQUIRED_IMAGE_ASSETS.length; - var presentrequiredAssetCount = 0; - NATIVE_MINIMUM_REQUIRED_IMAGE_ASSETS.forEach(ele => { - var lengthOfExistingAssets = nativeRequestObject.assets.length; - for (var i = 0; i < lengthOfExistingAssets; i++) { - if (ele.id == nativeRequestObject.assets[i].id) { - presentrequiredAssetCount++; - break; + assets.forEach(asset => { + var assetObj = asset; + if (assetObj.img) { + if (assetObj.img.type == NATIVE_ASSET_IMAGE_TYPE.IMAGE) { + assetObj.w = assetObj.w || assetObj.width || (assetObj.sizes ? assetObj.sizes[0] : UNDEFINED); + assetObj.h = assetObj.h || assetObj.height || (assetObj.sizes ? assetObj.sizes[1] : UNDEFINED); + assetObj.wmin = assetObj.wmin || assetObj.minimumWidth || (assetObj.minsizes ? assetObj.minsizes[0] : UNDEFINED); + assetObj.hmin = assetObj.hmin || assetObj.minimumHeight || (assetObj.minsizes ? assetObj.minsizes[1] : UNDEFINED); + } else if (assetObj.img.type == NATIVE_ASSET_IMAGE_TYPE.ICON) { + assetObj.w = assetObj.w || assetObj.width || (assetObj.sizes ? assetObj.sizes[0] : UNDEFINED); + assetObj.h = assetObj.h || assetObj.height || (assetObj.sizes ? assetObj.sizes[1] : UNDEFINED); + } + } + + if (assetObj && assetObj.id !== undefined && isValidAsset(assetObj)) { + nativeRequestObject.assets.push(assetObj); } } - }); - if (requiredAssetCount == presentrequiredAssetCount) { - isInvalidNativeRequest = false; - } else { - isInvalidNativeRequest = true; + ); } return nativeRequestObject; } @@ -533,7 +534,7 @@ function _createBannerRequest(bid) { export function checkVideoPlacement(videoData, adUnitCode) { // Check for video.placement property. If property is missing display log message. - if (!deepAccess(videoData, 'placement')) { + if (FEATURES.VIDEO && !deepAccess(videoData, 'placement')) { logWarn(MSG_VIDEO_PLACEMENT_MISSING + ' for ' + adUnitCode); }; } @@ -542,7 +543,7 @@ function _createVideoRequest(bid) { var videoData = mergeDeep(deepAccess(bid.mediaTypes, 'video'), bid.params.video); var videoObj; - if (videoData !== UNDEFINED) { + if (FEATURES.VIDEO && videoData !== UNDEFINED) { videoObj = {}; checkVideoPlacement(videoData, bid.adUnitCode); for (var key in VIDEO_CUSTOM_PARAMS) { @@ -607,7 +608,7 @@ function _addDealCustomTargetings(imp, bid) { } } -function _addJWPlayerSegmentData(imp, bid, isS2S) { +function _addJWPlayerSegmentData(imp, bid) { var jwSegData = (bid.rtd && bid.rtd.jwplayer && bid.rtd.jwplayer.targeting) || undefined; var jwPlayerData = ''; const jwMark = 'jw-'; @@ -624,15 +625,11 @@ function _addJWPlayerSegmentData(imp, bid, isS2S) { var ext; - if (isS2S) { - (imp.dctr === undefined || imp.dctr.length == 0) ? imp.dctr = jwPlayerData : imp.dctr += '|' + jwPlayerData; - } else { - ext = imp.ext; - ext && ext.key_val === undefined ? ext.key_val = jwPlayerData : ext.key_val += '|' + jwPlayerData; - } + ext = imp.ext; + ext && ext.key_val === undefined ? ext.key_val = jwPlayerData : ext.key_val += '|' + jwPlayerData; } -function _createImpressionObject(bid, conf) { +function _createImpressionObject(bid) { var impObj = {}; var bannerObj; var videoObj; @@ -665,14 +662,18 @@ function _createImpressionObject(bid, conf) { } break; case NATIVE: + // TODO uncomment below line when removing native 1.1 support + // nativeObj['request'] = JSON.stringify(_createNativeRequest(bid.nativeOrtbRequest)); + // TODO delete below line when removing native 1.1 support nativeObj['request'] = JSON.stringify(_createNativeRequest(bid.nativeParams)); if (!isInvalidNativeRequest) { impObj.native = nativeObj; } else { logWarn(LOG_WARN_PREFIX + 'Error: Error in Native adunit ' + bid.params.adUnit + '. Ignoring the adunit. Refer to ' + PREBID_NATIVE_HELP_LINK + ' for more details.'); + isInvalidNativeRequest = false; } break; - case VIDEO: + case FEATURES.VIDEO && VIDEO: videoObj = _createVideoRequest(bid); if (videoObj !== UNDEFINED) { impObj.video = videoObj; @@ -708,7 +709,7 @@ function _createImpressionObject(bid, conf) { return impObj.hasOwnProperty(BANNER) || impObj.hasOwnProperty(NATIVE) || - impObj.hasOwnProperty(VIDEO) ? impObj : UNDEFINED; + (FEATURES.VIDEO && impObj.hasOwnProperty(VIDEO)) ? impObj : UNDEFINED; } function _addImpressionFPD(imp, bid) { @@ -809,7 +810,7 @@ function _checkMediaType(bid, newBid) { var videoRegex = new RegExp(/VAST\s+version/); if (adm.indexOf('span class="PubAPIAd"') >= 0) { newBid.mediaType = BANNER; - } else if (videoRegex.test(adm)) { + } else if (FEATURES.VIDEO && videoRegex.test(adm)) { newBid.mediaType = VIDEO; } else { try { @@ -825,7 +826,6 @@ function _checkMediaType(bid, newBid) { } function _parseNativeResponse(bid, newBid) { - newBid.native = {}; if (bid.hasOwnProperty('adm')) { var adm = ''; try { @@ -834,53 +834,15 @@ function _parseNativeResponse(bid, newBid) { logWarn(LOG_WARN_PREFIX + 'Error: Cannot parse native reponse for ad response: ' + newBid.adm); return; } - if (adm && adm.native && adm.native.assets && adm.native.assets.length > 0) { - newBid.mediaType = NATIVE; - for (let i = 0, len = adm.native.assets.length; i < len; i++) { - switch (adm.native.assets[i].id) { - case NATIVE_ASSETS.TITLE.ID: - newBid.native.title = adm.native.assets[i].title && adm.native.assets[i].title.text; - break; - case NATIVE_ASSETS.IMAGE.ID: - newBid.native.image = { - url: adm.native.assets[i].img && adm.native.assets[i].img.url, - height: adm.native.assets[i].img && adm.native.assets[i].img.h, - width: adm.native.assets[i].img && adm.native.assets[i].img.w, - }; - break; - case NATIVE_ASSETS.ICON.ID: - newBid.native.icon = { - url: adm.native.assets[i].img && adm.native.assets[i].img.url, - height: adm.native.assets[i].img && adm.native.assets[i].img.h, - width: adm.native.assets[i].img && adm.native.assets[i].img.w, - }; - break; - case NATIVE_ASSETS.SPONSOREDBY.ID: - case NATIVE_ASSETS.BODY.ID: - case NATIVE_ASSETS.LIKES.ID: - case NATIVE_ASSETS.DOWNLOADS.ID: - case NATIVE_ASSETS.PRICE: - case NATIVE_ASSETS.SALEPRICE.ID: - case NATIVE_ASSETS.PHONE.ID: - case NATIVE_ASSETS.ADDRESS.ID: - case NATIVE_ASSETS.DESC2.ID: - case NATIVE_ASSETS.CTA.ID: - case NATIVE_ASSETS.RATING.ID: - case NATIVE_ASSETS.DISPLAYURL.ID: - newBid.native[NATIVE_ASSET_ID_TO_KEY_MAP[adm.native.assets[i].id]] = adm.native.assets[i].data && adm.native.assets[i].data.value; - break; - } - } - newBid.native.clickUrl = adm.native.link && adm.native.link.url; - newBid.native.clickTrackers = (adm.native.link && adm.native.link.clicktrackers) || []; - newBid.native.impressionTrackers = adm.native.imptrackers || []; - newBid.native.jstracker = adm.native.jstracker || []; - if (!newBid.width) { - newBid.width = DEFAULT_WIDTH; - } - if (!newBid.height) { - newBid.height = DEFAULT_HEIGHT; - } + newBid.native = { + ortb: { ...adm.native } + }; + newBid.mediaType = NATIVE; + if (!newBid.width) { + newBid.width = DEFAULT_WIDTH; + } + if (!newBid.height) { + newBid.height = DEFAULT_HEIGHT; } } } @@ -934,7 +896,10 @@ function _assignRenderer(newBid, request) { for (let bidderRequestBidsIndex = 0; bidderRequestBidsIndex < request.bidderRequest.bids.length; bidderRequestBidsIndex++) { if (request.bidderRequest.bids[bidderRequestBidsIndex].bidId === newBid.requestId) { bidParams = request.bidderRequest.bids[bidderRequestBidsIndex].params; - context = request.bidderRequest.bids[bidderRequestBidsIndex].mediaTypes[VIDEO].context; + + if (FEATURES.VIDEO) { + context = request.bidderRequest.bids[bidderRequestBidsIndex].mediaTypes[VIDEO].context; + } adUnitCode = request.bidderRequest.bids[bidderRequestBidsIndex].adUnitCode; } } @@ -954,7 +919,7 @@ function _assignRenderer(newBid, request) { * @returns */ export function assignDealTier(newBid, bid, request) { - if (!bid?.ext?.prebiddealpriority) return; + if (!bid?.ext?.prebiddealpriority || !FEATURES.VIDEO) return; const bidRequest = getBidRequest(newBid.requestId, [request.bidderRequest]); const videoObj = deepAccess(bidRequest, 'mediaTypes.video'); if (videoObj?.context != ADPOD) return; @@ -977,6 +942,49 @@ function isNonEmptyArray(test) { return false; } +/** + * Prepare meta object to pass as params + * @param {*} br : bidResponse + * @param {*} bid : bids + */ +export function prepareMetaObject(br, bid, seat) { + br.meta = {}; + + if (bid.ext && bid.ext.dspid) { + br.meta.networkId = bid.ext.dspid; + br.meta.demandSource = bid.ext.dspid; + } + + // NOTE: We will not recieve below fields from the translator response also not sure on what will be the key names for these in the response, + // when we needed we can add it back. + // New fields added, assignee fields name may change + // if (bid.ext.networkName) br.meta.networkName = bid.ext.networkName; + // if (bid.ext.advertiserName) br.meta.advertiserName = bid.ext.advertiserName; + // if (bid.ext.agencyName) br.meta.agencyName = bid.ext.agencyName; + // if (bid.ext.brandName) br.meta.brandName = bid.ext.brandName; + if (bid.ext && bid.ext.dchain) { + br.meta.dchain = bid.ext.dchain; + } + + const advid = seat || (bid.ext && bid.ext.advid); + if (advid) { + br.meta.advertiserId = advid; + br.meta.agencyId = advid; + br.meta.buyerId = advid; + } + + if (bid.adomain && isNonEmptyArray(bid.adomain)) { + br.meta.advertiserDomains = bid.adomain; + br.meta.clickUrl = bid.adomain[0]; + br.meta.brandId = bid.adomain[0]; + } + + if (bid.cat && isNonEmptyArray(bid.cat)) { + br.meta.secondaryCatIds = bid.cat; + br.meta.primaryCatId = bid.cat[0]; + } +} + export const spec = { code: BIDDER_CODE, gvlid: 76, @@ -994,7 +1002,7 @@ export const spec = { return false; } // video ad validation - if (bid.hasOwnProperty('mediaTypes') && bid.mediaTypes.hasOwnProperty(VIDEO)) { + if (FEATURES.VIDEO && bid.hasOwnProperty('mediaTypes') && bid.mediaTypes.hasOwnProperty(VIDEO)) { // bid.mediaTypes.video.mimes OR bid.params.video.mimes should be present and must be a non-empty array let mediaTypesVideoMimes = deepAccess(bid.mediaTypes, 'video.mimes'); let paramsVideoMimes = deepAccess(bid, 'params.video.mimes'); @@ -1040,7 +1048,7 @@ export const spec = { */ buildRequests: (validBidRequests, bidderRequest) => { // convert Native ORTB definition to old-style prebid native definition - validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + // validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); var refererInfo; if (bidderRequest && bidderRequest.refererInfo) { refererInfo = bidderRequest.refererInfo; @@ -1086,7 +1094,7 @@ export const spec = { if (bid.params.hasOwnProperty('acat') && isArray(bid.params.acat)) { allowedIabCategories = allowedIabCategories.concat(bid.params.acat); } - var impObj = _createImpressionObject(bid, conf); + var impObj = _createImpressionObject(bid); if (impObj) { payload.imp.push(impObj); } @@ -1189,6 +1197,10 @@ export const spec = { if (commonFpd.bcat) { blockedIabCategories = blockedIabCategories.concat(commonFpd.bcat); } + // check if fpd ortb2 contains device property with sua object + if (commonFpd.device?.sua) { + payload.device.sua = commonFpd.device?.sua; + } if (commonFpd.ext?.prebid?.bidderparams?.[bidderRequest.bidderCode]?.acat) { const acatParams = commonFpd.ext.prebid.bidderparams[bidderRequest.bidderCode].acat; @@ -1202,7 +1214,7 @@ export const spec = { // bidderRequest has timeout property if publisher sets during calling requestBids function from page // if not bidderRequest contains global value set by Prebid if (bidderRequest?.timeout) { - payload.tmax = bidderRequest.timeout || config.getConfig('bidderTimeout'); + payload.tmax = bidderRequest.timeout; } else { payload.tmax = window?.PWT?.versionDetails?.timeout; } @@ -1275,7 +1287,7 @@ export const spec = { switch (newBid.mediaType) { case BANNER: break; - case VIDEO: + case FEATURES.VIDEO && VIDEO: newBid.width = bid.hasOwnProperty('w') ? bid.w : req.video.w; newBid.height = bid.hasOwnProperty('h') ? bid.h : req.video.h; newBid.vastXml = bid.adm; @@ -1293,17 +1305,7 @@ export const spec = { newBid['dealChannel'] = dealChannelValues[bid.ext.deal_channel] || null; } - newBid.meta = {}; - if (bid.ext && bid.ext.dspid) { - newBid.meta.networkId = bid.ext.dspid; - } - if (bid.ext && bid.ext.advid) { - newBid.meta.buyerId = bid.ext.advid; - } - if (bid.adomain && bid.adomain.length > 0) { - newBid.meta.advertiserDomains = bid.adomain; - newBid.meta.clickUrl = bid.adomain[0]; - } + prepareMetaObject(newBid, bid, seatbidder.seat); // adserverTargeting if (seatbidder.ext && seatbidder.ext.buyid) { @@ -1371,7 +1373,6 @@ export const spec = { */ transformBidParams: function (params, isOpenRtb, adUnit, bidRequests) { - _addJWPlayerSegmentData(params, adUnit.bids[0], true); return convertTypes({ 'publisherId': 'string', 'adSlot': 'string' diff --git a/modules/pubwiseAnalyticsAdapter.js b/modules/pubwiseAnalyticsAdapter.js index 19a74c7c245..6aed462f2d5 100644 --- a/modules/pubwiseAnalyticsAdapter.js +++ b/modules/pubwiseAnalyticsAdapter.js @@ -3,8 +3,10 @@ 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 { getStorageManager } from '../src/storageManager.js'; -const storage = getStorageManager(); +import {getStorageManager} from '../src/storageManager.js'; +import {MODULE_TYPE_ANALYTICS} from '../src/activities/modules.js'; +const MODULE_CODE = 'pubwise'; +const storage = getStorageManager({moduleType: MODULE_TYPE_ANALYTICS, moduleName: MODULE_CODE}); /**** * PubWise.io Analytics @@ -304,11 +306,14 @@ pubwiseAnalytics.storeSessionID = function (userSessID) { // ensure a session exists, if not make one, always store it pubwiseAnalytics.ensureSession = function () { - if (sessionExpired() === true || userSessionID() === null || userSessionID() === '') { + let sessionId = userSessionID(); + if (sessionExpired() === true || sessionId === null || sessionId === '') { let generatedId = generateUUID(); expireUtmData(); this.storeSessionID(generatedId); sessionData.sessionId = generatedId; + } else if (sessionId != null) { + sessionData.sessionId = sessionId; } // eslint-disable-next-line // console.log('ensured session'); @@ -331,7 +336,7 @@ pubwiseAnalytics.enableAnalytics = function (config) { adapterManager.registerAnalyticsAdapter({ adapter: pubwiseAnalytics, - code: 'pubwise', + code: MODULE_CODE, gvlid: 842 }); diff --git a/modules/pubwiseBidAdapter.js b/modules/pubwiseBidAdapter.js index 5e381e74a18..a7381bb2884 100644 --- a/modules/pubwiseBidAdapter.js +++ b/modules/pubwiseBidAdapter.js @@ -1,19 +1,32 @@ -import { _each, isStr, deepClone, isArray, deepSetValue, inIframe, logMessage, logInfo, logWarn, logError } from '../src/utils.js'; -import { config } from '../src/config.js'; + +import { _each, isBoolean, isEmptyStr, isNumber, isStr, deepClone, isArray, deepSetValue, inIframe, mergeDeep, deepAccess, logMessage, logInfo, logWarn, logError } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER, NATIVE } from '../src/mediaTypes.js'; -const VERSION = '0.2.0'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { config } from '../src/config.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import { OUTSTREAM, INSTREAM } from '../src/video.js'; + +const VERSION = '0.3.0'; const GVLID = 842; const NET_REVENUE = true; const UNDEFINED = undefined; const DEFAULT_CURRENCY = 'USD'; const AUCTION_TYPE = 1; const BIDDER_CODE = 'pwbid'; +const LOG_PREFIX = 'PubWise: '; const ENDPOINT_URL = 'https://bid.pubwise.io/prebid'; +// const ENDPOINT_URL = 'https://bid.pubwise.io/prebid'; // testing observable endpoint const DEFAULT_WIDTH = 0; const DEFAULT_HEIGHT = 0; const PREBID_NATIVE_HELP_LINK = 'https://prebid.org/dev-docs/show-native-ads.html'; // const USERSYNC_URL = '//127.0.0.1:8080/usersync' +const MSG_VIDEO_PLACEMENT_MISSING = 'Video.Placement param missing'; + +const MEDIATYPE = [ + BANNER, + VIDEO, + NATIVE +] const CUSTOM_PARAMS = { 'gender': '', // User gender @@ -22,6 +35,32 @@ const CUSTOM_PARAMS = { 'lon': '', // User Location - Longitude }; +const DATA_TYPES = { + 'NUMBER': 'number', + 'STRING': 'string', + 'BOOLEAN': 'boolean', + 'ARRAY': 'array', + 'OBJECT': 'object' +}; + +const VIDEO_CUSTOM_PARAMS = { + 'mimes': DATA_TYPES.ARRAY, + 'minduration': DATA_TYPES.NUMBER, + 'maxduration': DATA_TYPES.NUMBER, + 'startdelay': DATA_TYPES.NUMBER, + 'playbackmethod': DATA_TYPES.ARRAY, + 'api': DATA_TYPES.ARRAY, + 'protocols': DATA_TYPES.ARRAY, + 'w': DATA_TYPES.NUMBER, + 'h': DATA_TYPES.NUMBER, + 'battr': DATA_TYPES.ARRAY, + 'linearity': DATA_TYPES.NUMBER, + 'placement': DATA_TYPES.NUMBER, + 'minbitrate': DATA_TYPES.NUMBER, + 'maxbitrate': DATA_TYPES.NUMBER, + 'skip': DATA_TYPES.NUMBER +} + // rtb native types are meant to be dynamic and extendable // the extendable data asset types are nicely aligned // in practice we set an ID that is distinct for each real type of return @@ -88,7 +127,7 @@ _each(NATIVE_ASSETS, anAsset => { NATIVE_ASSET_KEY_TO_ASSET_MAP[anAsset.KEY] = a export const spec = { code: BIDDER_CODE, gvlid: GVLID, - supportedMediaTypes: [BANNER, NATIVE], + supportedMediaTypes: [BANNER, VIDEO, NATIVE], /** * Determines whether or not the given bid request is valid. * @@ -96,18 +135,40 @@ export const spec = { * @return boolean True if this is a valid bid, and false otherwise. */ isBidRequestValid: function (bid) { - // siteId is required + // siteId is required for any type if (bid.params && bid.params.siteId) { // it must be a string if (!isStr(bid.params.siteId)) { _logWarn('siteId is required for bid', bid); return false; } - } else { - return false; + + // video ad validation + if (bid.hasOwnProperty('mediaTypes') && bid.mediaTypes.hasOwnProperty(VIDEO)) { + // bid.mediaTypes.video.mimes OR bid.params.video.mimes should be present and must be a non-empty array + let mediaTypesVideoMimes = deepAccess(bid.mediaTypes, 'video.mimes'); + let paramsVideoMimes = deepAccess(bid, 'params.video.mimes'); + if (_isNonEmptyArray(mediaTypesVideoMimes) === false && _isNonEmptyArray(paramsVideoMimes) === false) { + _logWarn('Error: For video ads, bid.mediaTypes.video.mimes OR bid.params.video.mimes should be present and must be a non-empty array. Call suppressed:', JSON.stringify(bid)); + return false; + } + + if (!bid.mediaTypes[VIDEO].hasOwnProperty('context')) { + _logError(`no context specified in bid. Rejecting bid: `, JSON.stringify(bid)); + return false; + } + + if (bid.mediaTypes[VIDEO].context === 'outstream') { + delete bid.mediaTypes[VIDEO]; + _logWarn(`outstream not currently supported `, JSON.stringify(bid)); + return false; + } + } + + return true; } - return true; + return false; }, /** * Make a server request from the list of BidRequests. @@ -116,6 +177,7 @@ export const spec = { * @return ServerRequest Info describing the request to the server. */ buildRequests: function (validBidRequests, bidderRequest) { + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); var refererInfo; if (bidderRequest && bidderRequest.refererInfo) { refererInfo = bidderRequest.refererInfo; @@ -210,7 +272,7 @@ export const spec = { return { method: 'POST', - url: ENDPOINT_URL, + url: _getEndpointURL(bid), data: payload, options: options, bidderRequest: bidderRequest, @@ -231,6 +293,7 @@ export const spec = { // try { if (response.body && response.body.seatbid && isArray(response.body.seatbid)) { + _logInfo('interpretResponse response body', response.body); // Supporting multiple bid responses for same adSize respCur = response.body.cur || respCur; response.body.seatbid.forEach(seatbidder => { @@ -254,10 +317,24 @@ export const spec = { if (parsedRequest.imp && parsedRequest.imp.length > 0) { parsedRequest.imp.forEach(req => { if (bid.impid === req.id) { - _checkMediaType(bid.adm, newBid); + _checkMediaType(bid, newBid); switch (newBid.mediaType) { case BANNER: break; + case VIDEO: + const videoContext = deepAccess(request, 'mediaTypes.video.context'); + switch (videoContext) { + case OUTSTREAM: + // not currently supported + break; + case INSTREAM: + break; + } + newBid.width = bid.hasOwnProperty('w') ? bid.w : req.video.w; + newBid.height = bid.hasOwnProperty('h') ? bid.h : req.video.h; + newBid.vastXml = bid.adm; + newBid.vastUrl = bid.vastUrl; + break; case NATIVE: _parseNativeResponse(bid, newBid); break; @@ -289,20 +366,31 @@ export const spec = { } } -function _checkMediaType(adm, newBid) { - // Create a regex here to check the strings - var admJSON = ''; - if (adm.indexOf('"ver":') >= 0) { - try { - admJSON = JSON.parse(adm.replace(/\\/g, '')); - if (admJSON && admJSON.assets) { - newBid.mediaType = NATIVE; +function _checkMediaType(bid, newBid) { + // Check Various ADM Aspects to Determine Media Type + if (bid.ext && bid.ext['bidtype'] != undefined) { + // this is the most explicity check + newBid.mediaType = MEDIATYPE[bid.ext.bidtype]; + } else { + _logInfo('bid.ext.bidtype does not exist, checking alternatively for mediaType'); + var adm = bid.adm; + var videoRegex = new RegExp(/VAST\s+version/); + + if (adm.indexOf('"ver":') >= 0) { + try { + var admJSON = ''; + admJSON = JSON.parse(adm.replace(/\\/g, '')); + if (admJSON && admJSON.assets) { + newBid.mediaType = NATIVE; + } + } catch (e) { + _logWarn('Error: Cannot parse native reponse for ad response: ', adm); } - } catch (e) { - _logWarn('Error: Cannot parse native reponse for ad response: ' + adm); + } else if (videoRegex.test(adm)) { + newBid.mediaType = VIDEO; + } else { + newBid.mediaType = BANNER; } - } else { - newBid.mediaType = BANNER; } } @@ -416,7 +504,8 @@ function _createOrtbTemplate(conf) { dnt: (navigator.doNotTrack == 'yes' || navigator.doNotTrack == '1' || navigator.msDoNotTrack == '1') ? 1 : 0, h: screen.height, w: screen.width, - language: navigator.language + language: navigator.language, + devicetype: _getDeviceType() }, user: {}, ext: { @@ -428,6 +517,7 @@ function _createOrtbTemplate(conf) { function _createImpressionObject(bid, conf) { var impObj = {}; var bannerObj; + var videoObj; var nativeObj = {}; var mediaTypes = ''; @@ -459,6 +549,12 @@ function _createImpressionObject(bid, conf) { _logWarn('Error: Error in Native adunit ' + bid.params.adUnit + '. Ignoring the adunit. Refer to ' + PREBID_NATIVE_HELP_LINK + ' for more details.'); } break; + case VIDEO: + videoObj = _createVideoRequest(bid); + if (videoObj !== UNDEFINED) { + impObj.video = videoObj; + } + break; } } } else { @@ -468,7 +564,8 @@ function _createImpressionObject(bid, conf) { _addFloorFromFloorModule(impObj, bid); return impObj.hasOwnProperty(BANNER) || - impObj.hasOwnProperty(NATIVE) ? impObj : UNDEFINED; + impObj.hasOwnProperty(NATIVE) || + impObj.hasOwnProperty(VIDEO) ? impObj : UNDEFINED; } function _parseSlotParam(paramName, paramValue) { @@ -492,7 +589,7 @@ function _parseSlotParam(paramName, paramValue) { } function _parseAdSlot(bid) { - _logInfo('parseAdSlot bid', bid) + _logInfo('parseAdSlot bid', bid); if (bid.adUnitCode) { bid.params.adUnit = bid.adUnitCode; } else { @@ -504,7 +601,7 @@ function _parseAdSlot(bid) { if (bid.hasOwnProperty('mediaTypes')) { if (bid.mediaTypes.hasOwnProperty(BANNER) && - bid.mediaTypes.banner.hasOwnProperty('sizes')) { // if its a banner, has mediaTypes and sizes + bid.mediaTypes.banner.hasOwnProperty('sizes')) { // if its a banner, has mediaTypes and sizes var i = 0; var sizeArray = []; for (;i < bid.mediaTypes.banner.sizes.length; i++) { @@ -522,7 +619,7 @@ function _parseAdSlot(bid) { } } } else { - _logWarn('MediaTypes are Required for all Adunit Configs', bid) + _logWarn('MediaTypes are Required for all Adunit Configs', bid); } } @@ -558,7 +655,7 @@ function _addFloorFromFloorModule(impObj, bid) { // get lowest floor from floorModule if (typeof bid.getFloor === 'function' && !config.getConfig('pubwise.disableFloors')) { - [BANNER, NATIVE].forEach(mediaType => { + [BANNER, VIDEO, NATIVE].forEach(mediaType => { if (impObj.hasOwnProperty(mediaType)) { let floorInfo = bid.getFloor({ currency: impObj.bidFloorCur, mediaType: mediaType, size: '*' }); if (typeof floorInfo === 'object' && floorInfo.currency === impObj.bidFloorCur && !isNaN(parseInt(floorInfo.floor))) { @@ -746,28 +843,162 @@ function _createBannerRequest(bid) { _logWarn('Error: mediaTypes.banner.size missing for adunit: ' + bid.params.adUnit + '. Ignoring the banner impression in the adunit.'); bannerObj = UNDEFINED; } + return bannerObj; } // various error levels are not always used // eslint-disable-next-line no-unused-vars function _logMessage(textValue, objectValue) { - logMessage('PubWise: ' + textValue, objectValue); + objectValue = objectValue || ''; + logMessage(LOG_PREFIX + textValue, objectValue); } // eslint-disable-next-line no-unused-vars function _logInfo(textValue, objectValue) { - logInfo('PubWise: ' + textValue, objectValue); + objectValue = objectValue || ''; + logInfo(LOG_PREFIX + textValue, objectValue); } // eslint-disable-next-line no-unused-vars function _logWarn(textValue, objectValue) { - logWarn('PubWise: ' + textValue, objectValue); + objectValue = objectValue || ''; + logWarn(LOG_PREFIX + textValue, objectValue); } // eslint-disable-next-line no-unused-vars function _logError(textValue, objectValue) { - logError('PubWise: ' + textValue, objectValue); + objectValue = objectValue || ''; + logError(LOG_PREFIX + textValue, objectValue); +} + +function _checkVideoPlacement(videoData, adUnitCode) { + // Check for video.placement property. If property is missing display log message. + if (!deepAccess(videoData, 'placement')) { + _logWarn(`${MSG_VIDEO_PLACEMENT_MISSING} for ${adUnitCode}`, adUnitCode); + }; +} + +function _createVideoRequest(bid) { + var videoData = mergeDeep(deepAccess(bid.mediaTypes, 'video'), bid.params.video); + var videoObj; + + if (videoData !== UNDEFINED) { + videoObj = {}; + _checkVideoPlacement(videoData, bid.adUnitCode); + for (var key in VIDEO_CUSTOM_PARAMS) { + if (videoData.hasOwnProperty(key)) { + videoObj[key] = _checkParamDataType(key, videoData[key], VIDEO_CUSTOM_PARAMS[key]); + } + } + // read playersize and assign to h and w. + if (isArray(bid.mediaTypes.video.playerSize[0])) { + videoObj.w = parseInt(bid.mediaTypes.video.playerSize[0][0], 10); + videoObj.h = parseInt(bid.mediaTypes.video.playerSize[0][1], 10); + } else if (isNumber(bid.mediaTypes.video.playerSize[0])) { + videoObj.w = parseInt(bid.mediaTypes.video.playerSize[0], 10); + videoObj.h = parseInt(bid.mediaTypes.video.playerSize[1], 10); + } + } else { + videoObj = UNDEFINED; + _logWarn('Error: Video config params missing for adunit: ' + bid.params.adUnit + ' with mediaType set as video. Ignoring video impression in the adunit.', bid.params); + } + return videoObj; +} + +/** + * Determines if the array has values + * + * @param {object} test + * @returns {boolean} + */ +function _isNonEmptyArray(test) { + if (isArray(test) === true) { + if (test.length > 0) { + return true; + } + } + return false; +} + +/** + * Returns the overridden bid endpoint_url if it is set, primarily used for testing + * + * @param {object} bid the current bid + * @returns + */ +function _getEndpointURL(bid) { + if (!isEmptyStr(bid?.params?.endpoint_url) && bid?.params?.endpoint_url != UNDEFINED) { + return bid.params.endpoint_url; + } + + return ENDPOINT_URL; +} + +/** + * + * @param {object} key + * @param {object}} value + * @param {object} datatype + * @returns + */ +function _checkParamDataType(key, value, datatype) { + var errMsg = 'Ignoring param key: ' + key + ', expects ' + datatype + ', found ' + typeof value; + var functionToExecute; + switch (datatype) { + case DATA_TYPES.BOOLEAN: + functionToExecute = isBoolean; + break; + case DATA_TYPES.NUMBER: + functionToExecute = isNumber; + break; + case DATA_TYPES.STRING: + functionToExecute = isStr; + break; + case DATA_TYPES.ARRAY: + functionToExecute = isArray; + break; + } + if (functionToExecute(value)) { + return value; + } + _logWarn(errMsg, key); + return UNDEFINED; +} + +function _isMobile() { + if (navigator.userAgentData && navigator.userAgentData.mobile) { + return true; + } else { + return (/(mobi)/i).test(navigator.userAgent); + } +} + +function _isConnectedTV() { + return (/(smart[-]?tv|hbbtv|appletv|googletv|hdmi|netcast\.tv|viera|nettv|roku|\bdtv\b|sonydtv|inettvbrowser|\btv\b)/i).test(navigator.userAgent); +} + +function _isTablet() { + return (/ipad|android 3.0|xoom|sch-i800|playbook|tablet|kindle/i.test(navigator.userAgent.toLowerCase())); +} + +/** + * Very high level device detection, order matters + */ +function _getDeviceType() { + if (_isTablet()) { + return 5; + } + + if (_isMobile()) { + return 4; + } + + if (_isConnectedTV()) { + return 3; + } + + return 2; } // function _decorateLog() { @@ -777,6 +1008,7 @@ function _logError(textValue, objectValue) { // these are exported only for testing so maintaining the JS convention of _ to indicate the intent export { + _checkVideoPlacement, _checkMediaType, _parseAdSlot } diff --git a/modules/pubxBidAdapter.js b/modules/pubxBidAdapter.js index 17c4ba3848e..ee28d549475 100644 --- a/modules/pubxBidAdapter.js +++ b/modules/pubxBidAdapter.js @@ -1,4 +1,4 @@ -import { deepSetValue } from '../src/utils.js'; +import { deepSetValue, deepAccess } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; const BIDDER_CODE = 'pubx'; @@ -16,8 +16,11 @@ export const spec = { const bidId = bidRequest.bidId; const params = bidRequest.params; const sid = params.sid; + const pageUrl = deepAccess(bidRequest, 'ortb2.site.page').replace(/\?.*$/, ''); + const pageEnc = encodeURIComponent(pageUrl); const payload = { - sid: sid + sid: sid, + pu: pageEnc, }; return { id: bidId, diff --git a/modules/pubxaiAnalyticsAdapter.js b/modules/pubxaiAnalyticsAdapter.js index f7e73dd5fdd..19a3c236942 100644 --- a/modules/pubxaiAnalyticsAdapter.js +++ b/modules/pubxaiAnalyticsAdapter.js @@ -3,10 +3,11 @@ 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'; const emptyUrl = ''; const analyticsType = 'endpoint'; -const pubxaiAnalyticsVersion = 'v1.1.0'; +const pubxaiAnalyticsVersion = 'v1.2.0'; const defaultHost = 'api.pbxai.com'; const auctionPath = '/analytics/auction'; const winningBidPath = '/analytics/bidwon'; @@ -154,7 +155,7 @@ function send(data, status) { search: location.search }); if (typeof data !== 'undefined' && typeof data.auctionInit !== 'undefined') { - data.pageDetail.adUnitCount = data.auctionInit.adUnitCodes ? data.auctionInit.adUnitCodes.length : null; + data.pageDetail.adUnits = data.auctionInit.adUnitCodes; data.initOptions.auctionId = data.auctionInit.auctionId; delete data.auctionInit; @@ -180,7 +181,7 @@ function send(data, status) { search: { auctionTimestamp: auctionTimestamp, pubxaiAnalyticsVersion: pubxaiAnalyticsVersion, - prebidVersion: $$PREBID_GLOBAL$$.version + prebidVersion: getGlobal().version } }); if (status == 'bidwon') { diff --git a/modules/pulsepointBidAdapter.js b/modules/pulsepointBidAdapter.js index 25f82fb60d9..015e80d5692 100644 --- a/modules/pulsepointBidAdapter.js +++ b/modules/pulsepointBidAdapter.js @@ -16,6 +16,7 @@ const DEFAULT_BID_TTL = 20; const DEFAULT_CURRENCY = 'USD'; const DEFAULT_NET_REVENUE = true; const KNOWN_PARAMS = ['cp', 'ct', 'cf', 'video', 'battr', 'bcat', 'badv', 'bidfloor']; +const DEFAULT_TMAX = 500; /** * PulsePoint Bid Adapter. @@ -54,6 +55,7 @@ export const spec = { user: user(bidRequests[0], bidderRequest), regs: regs(bidderRequest), source: source(bidRequests[0].schain), + tmax: bidderRequest.timeout || DEFAULT_TMAX, }; return { method: 'POST', diff --git a/modules/quantcastBidAdapter.js b/modules/quantcastBidAdapter.js index 19a559edfda..2c721a61616 100644 --- a/modules/quantcastBidAdapter.js +++ b/modules/quantcastBidAdapter.js @@ -22,7 +22,7 @@ export const QUANTCAST_PROTOCOL = 'https'; export const QUANTCAST_PORT = '8443'; export const QUANTCAST_FPA = '__qca'; -export const storage = getStorageManager({gvlid: QUANTCAST_VENDOR_ID, bidderCode: BIDDER_CODE}); +export const storage = getStorageManager({bidderCode: BIDDER_CODE}); function makeVideoImp(bid) { const videoInMediaType = deepAccess(bid, 'mediaTypes.video') || {}; diff --git a/modules/quantcastIdSystem.js b/modules/quantcastIdSystem.js index 97cd01f98da..6a07082b61c 100644 --- a/modules/quantcastIdSystem.js +++ b/modules/quantcastIdSystem.js @@ -6,9 +6,10 @@ */ import {submodule} from '../src/hook.js' -import { getStorageManager } from '../src/storageManager.js'; +import {getStorageManager} from '../src/storageManager.js'; import { triggerPixel, logInfo } from '../src/utils.js'; import { uspDataHandler, coppaDataHandler, gdprDataHandler } from '../src/adapterManager.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; const QUANTCAST_FPA = '__qca'; const DEFAULT_COOKIE_EXP_DAYS = 392; // (13 months - 2 days) @@ -23,8 +24,9 @@ const QC_TCF_CONSENT_FIRST_PURPOSES = [PURPOSE_DATA_COLLECT]; const QC_TCF_CONSENT_ONLY_PUPROSES = [PURPOSE_DATA_COLLECT]; const GDPR_PRIVACY_STRING = gdprDataHandler.getConsentData(); const US_PRIVACY_STRING = uspDataHandler.getConsentData(); +const MODULE_NAME = 'quantcastId'; -export const storage = getStorageManager(); +export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); export function firePixel(clientId, cookieExpDays = DEFAULT_COOKIE_EXP_DAYS) { // check for presence of Quantcast Measure tag _qevent obj and publisher provided clientID @@ -160,7 +162,7 @@ export const quantcastIdSubmodule = { * used to link submodule with config * @type {string} */ - name: 'quantcastId', + name: MODULE_NAME, /** * Vendor id of Quantcast diff --git a/modules/radsBidAdapter.js b/modules/radsBidAdapter.js index fcb8c3a8c2a..ae16bcf9d83 100644 --- a/modules/radsBidAdapter.js +++ b/modules/radsBidAdapter.js @@ -1,7 +1,6 @@ -import { deepAccess } from '../src/utils.js'; -import {config} from '../src/config.js'; +import {deepAccess} 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'; const BIDDER_CODE = 'rads'; const ENDPOINT_URL = 'https://rads.recognified.net/md.request.php'; @@ -86,7 +85,7 @@ export const spec = { dealId: dealId, currency: currency, netRevenue: netRevenue, - ttl: config.getConfig('_bidderTimeout'), + ttl: 60, meta: { advertiserDomains: response.adomain || [] } diff --git a/modules/realvuAnalyticsAdapter.js b/modules/realvuAnalyticsAdapter.js index 0811c70a2f7..e4bcf1b474a 100644 --- a/modules/realvuAnalyticsAdapter.js +++ b/modules/realvuAnalyticsAdapter.js @@ -2,9 +2,12 @@ import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import CONSTANTS from '../src/constants.json'; -import { getStorageManager } from '../src/storageManager.js'; +import {getStorageManager} from '../src/storageManager.js'; import { logMessage, logError } from '../src/utils.js'; -const storage = getStorageManager(); +import {MODULE_TYPE_ANALYTICS} from '../src/activities/modules.js'; +const MODULE_CODE = 'realvuAnalytics'; + +const storage = getStorageManager({moduleType: MODULE_TYPE_ANALYTICS, moduleName: MODULE_CODE}); let realvuAnalyticsAdapter = adapter({ global: 'realvuAnalytics', @@ -967,7 +970,7 @@ realvuAnalyticsAdapter.disableAnalytics = function () { adapterManager.registerAnalyticsAdapter({ adapter: realvuAnalyticsAdapter, - code: 'realvuAnalytics' + code: MODULE_CODE, }); export default realvuAnalyticsAdapter; diff --git a/modules/relevadRtdProvider.js b/modules/relevadRtdProvider.js new file mode 100644 index 00000000000..a7d0305da62 --- /dev/null +++ b/modules/relevadRtdProvider.js @@ -0,0 +1,365 @@ +/** + * This module adds Relevad provider to the real time data module + * The {@link module:modules/realTimeData} module is required + * The module will fetch categories and segments from Relevad server and pass them to the bidders + * @module modules/relevadRtdProvider + * @requires module:modules/realTimeData + */ + +import {deepSetValue, isEmpty, logError, mergeDeep} from '../src/utils.js'; +import {submodule} from '../src/hook.js'; +import {ajax} from '../src/ajax.js'; +import {findIndex} from '../src/polyfill.js'; +import {getRefererInfo} from '../src/refererDetection.js'; +import {config} from '../src/config.js'; + +const MODULE_NAME = 'realTimeData'; +const SUBMODULE_NAME = 'RelevadRTDModule'; + +const SEGTAX_IAB = 6; // IAB Content Taxonomy v2 +const CATTAX_IAB = 6; // IAB Contextual Taxonomy v2.2 +const RELEVAD_API_DOMAIN = 'https://prebid.relestar.com'; +const entries = Object.entries; +const AJAX_OPTIONS = { + withCredentials: true, + referrerPolicy: 'unsafe-url', + crossOrigin: true, +}; + +export let serverData = {}; // Tracks data returned from Relevad RTD server + +/** + * Provides contextual IAB categories and segments to the bidders. + * + * @param {} reqBidsConfigObj Bids request configuration + * @param {Function} onDone Ajax callbacek + * @param {} moduleConfig Rtd module configuration + * @param {} userConsent user GDPR consent + */ +export function getBidRequestData(reqBidsConfigObj, onDone, moduleConfig, userConsent) { + moduleConfig.params = moduleConfig.params || {}; + moduleConfig.params.partnerid = moduleConfig.params.partnerid ? moduleConfig.params.partnerid : 1; + + let adunitInfo = reqBidsConfigObj.adUnits.map(adunit => { return [adunit.code, adunit.bids.map(bid => { return [bid.bidder, bid.params] })]; }); + serverData.page = moduleConfig.params.actualUrl || getRefererInfo().page || ''; + const url = (RELEVAD_API_DOMAIN + '/apis/rweb2/' + + '?url=' + encodeURIComponent(serverData.page) + + '&au=' + encodeURIComponent(JSON.stringify(adunitInfo)) + + '&pid=' + encodeURIComponent(moduleConfig.params?.publisherid || '') + + '&aid=' + encodeURIComponent(moduleConfig.params?.apikey || '') + + '&cid=' + encodeURIComponent(moduleConfig.params?.partnerid || '') + + '&gdpra=' + encodeURIComponent(userConsent?.gdpr?.gdprApplies || '') + + '&gdprc=' + encodeURIComponent(userConsent?.gdpr?.consentString || '') + ); + + ajax(url, + { + success: function (response, req) { + if (req.status === 200) { + try { + const data = JSON.parse(response); + serverData.rawdata = data; + if (data) { + addRtdData(reqBidsConfigObj, data, moduleConfig); + } + } catch (e) { + logError(SUBMODULE_NAME, 'unable to parse data: ' + e); + } + onDone(); + } + }, + error: function () { + logError(SUBMODULE_NAME, 'unable to receive data'); + onDone(); + } + }, + null, + { method: 'GET', ...AJAX_OPTIONS, }, + ); +} + +/** + * Sets global ORTB user and site data + * + * @param {dictionary} ortb2 The gloabl ORTB structure + * @param {dictionary} rtdData Rtd segments and categories + */ +export function setGlobalOrtb2(ortb2, rtdData) { + try { + let addOrtb2 = composeOrtb2Data(rtdData, 'site'); + !isEmpty(addOrtb2) && mergeDeep(ortb2, addOrtb2); + } catch (e) { + logError(e) + } +} + +/** + * Compose ORTB2 data fragment from RTD data + * + * @param {dictionary} rtdData RTD segments and categories + * @param {string} prefix Site path prefix + * @return {dictionary} ORTB2 fragment ready to be merged into global or bidder ORTB + */ +function composeOrtb2Data(rtdData, prefix) { + const segments = rtdData.segments; + const categories = rtdData.categories; + const content = rtdData.content; + let addOrtb2 = {}; + + !isEmpty(segments) && deepSetValue(addOrtb2, 'user.ext.data.relevad_rtd', segments); + !isEmpty(categories.cat) && deepSetValue(addOrtb2, prefix + '.cat', categories.cat); + !isEmpty(categories.pagecat) && deepSetValue(addOrtb2, prefix + '.pagecat', categories.pagecat); + !isEmpty(categories.sectioncat) && deepSetValue(addOrtb2, prefix + '.sectioncat', categories.sectioncat); + !isEmpty(categories.cattax) && deepSetValue(addOrtb2, prefix + '.cattax', categories.cattax); + + if (!isEmpty(content) && !isEmpty(content.segs) && content.segtax) { + const contentSegments = { + name: 'relevad', + ext: { segtax: content.segtax }, + segment: content.segs.map(x => { return {id: x}; }) + }; + deepSetValue(addOrtb2, prefix + '.content.data', [contentSegments]); + } + return addOrtb2; +} + +/** + * Sets ORTB user and site data for a given bidder + * + * @param {dictionary} bidderOrtbFragment The bidder ORTB fragment + * @param {object} bidder The bidder name + * @param {object} rtdData RTD categories and segments + */ +function setBidderSiteAndContent(bidderOrtbFragment, bidder, rtdData) { + try { + let addOrtb2 = composeOrtb2Data(rtdData, 'site'); + !isEmpty(rtdData.segments) && deepSetValue(addOrtb2, 'user.ext.data.relevad_rtd', rtdData.segments); + !isEmpty(rtdData.categories?.sectioncat) && deepSetValue(addOrtb2, 'site.ext.data.relevad_rtd', rtdData.categories.sectioncat); + if (isEmpty(addOrtb2)) { + return; + } + bidderOrtbFragment[bidder] = bidderOrtbFragment[bidder] || {}; + mergeDeep(bidderOrtbFragment[bidder], addOrtb2); + } catch (e) { + logError(e) + } +} + +/** + * Filters dictionary entries + * + * @param {array of {key:value}} dict A dictionary with numeric values + * @param {string} minscore The minimum value + * @return {array[names]} Array of category names with scores greater or equal to minscore + */ +function filterByScore(dict, minscore) { + if (dict && !isEmpty(dict)) { + minscore = minscore && typeof minscore == 'number' ? minscore : 30; + try { + const filteredCategories = Object.keys(Object.fromEntries(Object.entries(dict).filter(([k, v]) => v > minscore))); + return isEmpty(filteredCategories) ? null : filteredCategories; + } catch (e) { + logError(e); + } + } + return null; +} + +/** + * Filters RTD by relevancy score + * + * @param {object} data The Input RTD + * @param {string} minscore The minimum relevancy score + * @return {object} Filtered RTD + */ +function getFiltered(data, minscore) { + let relevadData = {'segments': []}; + + minscore = minscore && typeof minscore == 'number' ? minscore : 30; + + const cats = filterByScore(data.cats, minscore); + const pcats = filterByScore(data.pcats, minscore) || cats; + const scats = filterByScore(data.scats, minscore) || pcats; + const cattax = data.cattax ? data.cattax : CATTAX_IAB; + relevadData.categories = {cat: cats, pagecat: pcats, sectioncat: scats, cattax: cattax}; + + const contsegs = filterByScore(data.contsegs, minscore); + const segtax = data.segtax ? data.segtax : SEGTAX_IAB; + relevadData.content = {segs: contsegs, segtax: segtax}; + + try { + if (data && data.segments) { + for (let segId in data.segments) { + if (data.segments.hasOwnProperty(segId)) { + relevadData.segments.push(data.segments[segId].toString()); + } + } + } + } catch (e) { + logError(e); + } + return relevadData; +} + +/** + * Adds Rtd data to global ORTB structure and bidder requests + * + * @param {} reqBids The bid requests list + * @param {} data The Rtd data + * @param {} moduleConfig The Rtd module configuration + */ +export function addRtdData(reqBids, data, moduleConfig) { + moduleConfig = moduleConfig || {}; + moduleConfig.params = moduleConfig.params || {}; + const globalMinScore = moduleConfig.params.hasOwnProperty('minscore') ? moduleConfig.params.minscore : 30; + const relevadData = getFiltered(data, globalMinScore); + const relevadList = relevadData.segments.concat(relevadData.categories.pagecat); + // Publisher side bidder whitelist + const biddersParamsExist = !!(moduleConfig?.params?.bidders); + // RTD Server-side bidder whitelist + const wl = data.wl || null; + const noWhitelists = !biddersParamsExist && isEmpty(wl); + + // Add RTD data to the global ORTB fragments when no whitelists present + noWhitelists && setGlobalOrtb2(reqBids.ortb2Fragments?.global, relevadData); + + // Target GAM/GPT + let setgpt = moduleConfig.params.setgpt || !moduleConfig.params.hasOwnProperty('setgpt'); + if (moduleConfig.dryrun || (typeof window.googletag !== 'undefined' && setgpt)) { + try { + if (window.googletag && window.googletag.pubads && (typeof window.googletag.pubads === 'function')) { + window.googletag.pubads().getSlots().forEach(function (n) { + if (typeof n.setTargeting !== 'undefined' && relevadList && relevadList.length > 0) { + n.setTargeting('relevad_rtd', relevadList); + } + }); + } + } catch (e) { + logError(e); + } + } + + // Set per-bidder RTD + const adUnits = reqBids.adUnits; + adUnits.forEach(adUnit => { + noWhitelists && deepSetValue(adUnit, 'ortb2Imp.ext.data.relevad_rtd', relevadList); + + adUnit.hasOwnProperty('bids') && adUnit.bids.forEach(bid => { + let bidderIndex = (moduleConfig.params.hasOwnProperty('bidders') ? findIndex(moduleConfig.params.bidders, function (i) { + return i.bidder === bid.bidder; + }) : false); + const indexFound = !!(typeof bidderIndex == 'number' && bidderIndex >= 0); + try { + if ( + !biddersParamsExist || + (indexFound && + (!moduleConfig.params.bidders[bidderIndex].hasOwnProperty('adUnitCodes') || + moduleConfig.params.bidders[bidderIndex].adUnitCodes.indexOf(adUnit.code) !== -1 + ) + ) + ) { + let wb = isEmpty(wl) || wl[bid.bidder] === true; + if (!wb && !isEmpty(wl[bid.bidder])) { + wb = true; + for (const [key, value] of entries(wl[bid.bidder])) { + let params = bid?.params || {}; + wb = wb && (key in params) && params[key] == value; + } + } + if (wb && !isEmpty(relevadList)) { + setBidderSiteAndContent(reqBids.ortb2Fragments?.bidder, bid.bidder, relevadData); + deepSetValue(bid, 'params.keywords.relevad_rtd', relevadList); + deepSetValue(bid, 'params.target', [].concat(bid.params?.target ? [bid.params.target] : []).concat(relevadList.map(entry => { return 'relevad_rtd=' + entry; })).join(';')); + let firstPartyData = {}; + firstPartyData[bid.bidder] = { firstPartyData: { relevad_rtd: relevadList } }; + config.setConfig(firstPartyData); + !isEmpty(relevadData.segments) && deepSetValue(bid, 'ortb2.user.ext.data.segments', relevadData.segments); + !isEmpty(relevadData.categories) && deepSetValue(bid, 'ortb2.user.ext.data.contextual_categories', relevadData.categories.pagecat); + !isEmpty(relevadData.categories) && deepSetValue(bid, 'ortb2.site.ext.data.relevad_rtd', relevadData.categories.pagecat); + !isEmpty(relevadData.segments) && deepSetValue(bid, 'ortb2.user.ext.data.relevad_rtd', relevadData.segments); + } + } + } catch (e) { + logError(e); + } + }); + }); + + serverData = {...serverData, ...relevadData}; + return adUnits; +} + +/** + * Sends bid info to the RTD server + * + * @param {JSON} data Bids information + * @param {object} config Configuraion + */ +function sendBids(data, config) { + let dataJson = JSON.stringify(data); + + if (!config.dryrun) { + ajax(RELEVAD_API_DOMAIN + '/apis/bids/', () => {}, dataJson, AJAX_OPTIONS); + } + serverData = { clientdata: data }; +}; + +/** + * Processes AUCTION_END event + * + * @param {object} auctionDetails Auction details + * @param {object} config Module configuration + * @param {object} userConsent User GDPR consent object + */ +function onAuctionEnd(auctionDetails, config, userConsent) { + let adunitObj = {}; + let adunits = []; + + // Add Bids Received + auctionDetails.bidsReceived.forEach((bidObj) => { + if (!adunitObj[bidObj.adUnitCode]) { adunitObj[bidObj.adUnitCode] = []; } + + adunitObj[bidObj.adUnitCode].push({ + bidder: bidObj.bidderCode || bidObj.bidder, + cpm: bidObj.cpm, + currency: bidObj.currency, + dealId: bidObj.dealId, + type: bidObj.mediaType, + ttr: bidObj.timeToRespond, + size: bidObj.size + }); + }); + + entries(adunitObj).forEach(([adunitCode, bidsReceived]) => { + adunits.push({code: adunitCode, bids: bidsReceived}); + }); + + let data = { + event: 'bids', + adunits: adunits, + reledata: serverData.rawdata, + pid: encodeURIComponent(config.params?.publisherid || ''), + aid: encodeURIComponent(config.params?.apikey || ''), + cid: encodeURIComponent(config.params?.partnerid || ''), + gdpra: encodeURIComponent(userConsent?.gdpr?.gdprApplies || ''), + gdprc: encodeURIComponent(userConsent?.gdpr?.consentString || ''), + } + if (!config.dryrun) { + data.page = serverData?.page || config?.params?.actualUrl || getRefererInfo().page || ''; + } + + sendBids(data, config); +} + +export function init(config) { + return true; +} + +export const relevadSubmodule = { + name: SUBMODULE_NAME, + init: init, + onAuctionEndEvent: onAuctionEnd, + getBidRequestData: getBidRequestData +}; + +submodule(MODULE_NAME, relevadSubmodule); diff --git a/modules/relevadRtdProvider.md b/modules/relevadRtdProvider.md new file mode 100644 index 00000000000..fcbc7a7fb36 --- /dev/null +++ b/modules/relevadRtdProvider.md @@ -0,0 +1,108 @@ +# Relevad Real-Time Data Submodule + +Module Name: Relevad Rtd Provider +Module Type: Rtd Provider +Maintainer: anna@relevad.com + +# Description + +Relevad is a contextual semantic analytics company. Our privacy-first, cookieless contextual categorization, segmentation, and keyword generation platform is designed to help publishers and advertisers optimize targeting and increase ad inventory yield. + +Our real-time data processing module provides quality contextual IAB categories and segments along with their relevancy scores to the publisher’s web page. It places them into auction bid requests as global and/or bidder-specific: + +| Attrubute Type | ORTB2 Attribute | +| -------------- | ------------------------------------------------------------ | +| Contextual | “site.cat”: [IAB category codes]
“site.pagecat”: [IAB category codes],
“site.sectioncat”: [IAB category codes]
“site.cattax”: 6 | +| Content | “site.content.data”: {“name”: “relevad”, “ext”: …, “segment”: …} | +| User Data | “user.ext.data.relevad_rtd”: {segments} | + +Publisher may configre minimum relevancy score to restrict the categories and segments we pass to the bidders. +Relevad service does not use browser cookies and is fully GDPR compliant. + +### Publisher Integration + +Compile the Relevad RTD module into the Prebid.js package with + +`gulp build --modules=rtdModule,relevadRtdProvider` + +Add Relevad RTD provider to your Prebid config. Here is an example: + +``` +pbjs.setConfig( + ... + realTimeData: { + auctionDelay: 1000, + dataProviders: [ + { + name: "RelevadRTDModule", + waitForIt: true, + params: { + partnerId: your_partner_id, // Your Relevad partner id. + setgpt: true, // Target or not google GAM/GPT on your page. + minscore: 30, // Minimum relevancy score (0-100). If absent, defaults to 30. + + // The list of bidders to target with Relevad categories and segments. If absent or empty, target all bidders. + bidders: [ + { bidder: "appnexus", // Bidder name + adUnitCodes: ['adUnit-1','adUnit-2'], // List of adUnit codes to target. If absent or empty, target all ad units. + minscore: 70, // Minimum relevancy score for this bidder (0-100). If absent, defaults to the global minscore. + }, + ... + ] + } + } + ] + } + ... +} +``` + +### Relevad Real Time Submodule Configuration Parameters + + + +{: .table .table-bordered .table-striped } +| Name |Type | Description | Notes | +| :------------ | :------------ | :------------ |:------------ | +| name | String | Relevad RTD module name | Mandatory, must be **RelevadRTDModule** | +| waitForIt | Boolean | Whether to delay auction for the RTD module response | Optional. Defaults to false.We recommend setting it to true. Relevad RTD service is very fast. | +| params | Object | | Relevad RTD module configuration | +| params.partnerid | String | Relevad Partner ID, required to enable the service | Mandatory | +| params.publisherid | String | Relevad publisher id | Mandatory | +| params.apikey | String | Relevad API key | Mandatory | +| param.actualUrl | String | Your page URL. When present, will be categorized instead of the browser-provided URL | Optional, defaults to the browser-providedURL | +| params.setgpt | Boolean | Target or not Google GPT/GAM when it is configured on your page | Optional, defaults to true. | +| params.minscore | Integer | Minimum categorization relevancy score in the range of 0-100. Our categories and segments come with their relevancy scores. We’ll send to the bidders only categories and segments with the scores higher than the minscore. |Optional, defaults to 30| +| params.bidders | Dictionary | Bidders with which to share category and segment information | Optional. If empty or absent, target all bidders. | + + + +#### Bidder-specific configuration. Every bidder may have these configuration parameters + +| Name |Type | Description | Notes | +| :------------ | :------------ | :------------ |:------------ | +| bidder | String | Bidder name | Mandatory. Example: “appnexus” | +| adUnitCodes | Array of Strings | List of specific AdUnit codes you with to target | Optional. If empty or absent, all ad units are targeted. | +| minscore | Integer | Bidder-specific minimum categorization relevancy score (0, 100) | Optional, defaults to global minscore above. | + +If you do not have your own `partnerid, publisherid, apikey` please reach out to [info@relevad.com](mailto:info@relevad.com). + +### Testing + +To view an example of the on page setup required: + +```bash +gulp serve-fast --modules=rtdModule,relevadRtdProvider +``` + +Then in your browser access: + +``` +http://localhost:9999/integrationExamples/gpt/relevadRtdProvider_example.html +``` + +Run the unit tests for Relevad RTD module: + +```bash +gulp test --file "test/spec/modules/relevadRtdProvider_spec.js" +``` \ No newline at end of file diff --git a/modules/relevantdigitalBidAdapter.js b/modules/relevantdigitalBidAdapter.js new file mode 100644 index 00000000000..ad9ee5e1e14 --- /dev/null +++ b/modules/relevantdigitalBidAdapter.js @@ -0,0 +1,198 @@ +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {ortbConverter} from '../libraries/ortbConverter/converter.js' +import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; +import {config} from '../src/config.js'; +import {pbsExtensions} from '../libraries/pbsExtensions/pbsExtensions.js' +import {deepSetValue, isEmpty, deepClone, shuffle, triggerPixel, deepAccess} from '../src/utils.js'; + +const BIDDER_CODE = 'relevantdigital'; + +/** Global settings per bidder-code for this adapter (which might be > 1 if using aliasing) */ +let configByBidder = {}; + +/** Used by the tests */ +export const resetBidderConfigs = () => { + configByBidder = {}; +}; + +/** Settings ber bidder-code. checkParams === true means that it can optionally be set in bid-params */ +const FIELDS = [ + { name: 'pbsHost', checkParams: true, required: true }, + { name: 'accountId', checkParams: true, required: true }, + { name: 'pbsBufferMs', checkParams: false, required: false, default: 250 }, + { name: 'useSourceBidderCode', checkParams: false, required: false, default: false }, +]; + +const SYNC_HTML = 'https://cdn.relevant-digital.com/resources/load-cookie.html'; +const MAX_SYNC_COUNT = 10; // Max server-side bidder to sync at once via the iframe + +/** Get settings for a bidder-code via config and, if needed, bid parameters */ +const getBidderConfig = (bids) => { + const { bidder } = bids[0]; + const cfg = configByBidder[bidder] || { + ...Object.fromEntries(FIELDS.filter((f) => 'default' in f).map((f) => [f.name, f.default])), + syncedBidders: {}, // To keep track of S2S-bidders we already (started to) synced + }; + if (cfg.complete) { + return cfg; // Most common case, we already have the settings we need (and we won't re-read them) + } + configByBidder[bidder] = cfg; + const bidderConfiguration = config.getConfig(bidder) || {}; + + // Read settings set by setConfig({ [bidder]: { ... }}) and if not available - from bid params + FIELDS.forEach(({ name, checkParams }) => { + cfg[name] = bidderConfiguration[name] || cfg[name]; + if (!cfg[name] && checkParams) { + bids.forEach((bid) => { + cfg[name] = cfg[name] || bid.params?.[name]; + }); + } + }); + cfg.complete = FIELDS.every((field) => !field.required || cfg[field.name]); + if (cfg.complete) { + cfg.pbsHost = cfg.pbsHost.trim().replace('http://', 'https://'); + if (cfg.pbsHost.indexOf('https://') < 0) { + cfg.pbsHost = `https://${cfg.pbsHost}`; + } + } + return cfg; +} + +const converter = ortbConverter({ + context: { + netRevenue: true, + ttl: 300 + }, + processors: pbsExtensions, + imp(buildImp, bidRequest, context) { + // Set stored request id from placementId + const imp = buildImp(bidRequest, context); + const { placementId } = bidRequest.params; + deepSetValue(imp, 'ext.prebid.storedrequest.id', placementId); + delete imp.ext.prebid.bidder; + return imp; + }, + overrides: { + bidResponse: { + bidderCode(orig, bidResponse, bid, { bidRequest }) { + const { bidder, params = {} } = bidRequest || {}; + let useSourceBidderCode = configByBidder[bidder]?.useSourceBidderCode; + if ('useSourceBidderCode' in params) { + useSourceBidderCode = params.useSourceBidderCode; + } + // Only use the orignal function when useSourceBidderCode is true, else our own bidder code will be used + if (useSourceBidderCode) { + orig.apply(this, [...arguments].slice(1)); + } + }, + }, + } +}); + +export const spec = { + code: BIDDER_CODE, + gvlid: 1100, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + + /** We need both params.placementId + a complete configuration (pbsHost + accountId) to continue **/ + isBidRequestValid: (bid) => bid.params?.placementId && getBidderConfig([bid]).complete, + + /** Trigger impression-pixel */ + onBidWon: ({pbsWurl}) => pbsWurl && triggerPixel(pbsWurl), + + /** Build BidRequest for PBS */ + buildRequests(bidRequests, bidderRequest) { + const { bidder } = bidRequests[0]; + const cfg = getBidderConfig(bidRequests); + const data = converter.toORTB({bidRequests, bidderRequest}); + + /** Set tmax, in general this will be timeout - pbsBufferMs */ + const pbjsTimeout = bidderRequest.timeout || 1000; + data.tmax = Math.min(Math.max(pbjsTimeout - cfg.pbsBufferMs, cfg.pbsBufferMs), pbjsTimeout); + + delete data.ext?.prebid?.aliases; // We don't need/want to send aliases to PBS + deepSetValue(data, 'ext.relevant', { + ...data.ext?.relevant, + adapter: true, // For internal analytics + }); + deepSetValue(data, 'ext.prebid.storedrequest.id', cfg.accountId); + data.ext.prebid.passthrough = { + ...data.ext.prebid.passthrough, + relevant: { bidder }, // to find config for the right bidder-code in interpretResponse / getUserSyncs + }; + return [{ + method: 'POST', + url: `${cfg.pbsHost}/openrtb2/auction`, + data + }]; + }, + + /** Read BidResponse from PBS and make necessary adjustments to not make it appear to come from unknown bidders */ + interpretResponse(response, request) { + const resp = deepClone(response.body); + const { bidder } = request.data.ext.prebid.passthrough.relevant; + + // Modify response times / errors for actual PBS bidders into a single value + const MODIFIERS = { + responsetimemillis: (values) => Math.max(...values), + errors: (values) => [].concat(...values), + }; + Object.entries(MODIFIERS).forEach(([field, combineFn]) => { + const obj = resp.ext?.[field]; + if (!isEmpty(obj)) { + resp.ext[field] = {[bidder]: combineFn(Object.values(obj))}; + } + }); + + const bids = converter.fromORTB({response: resp, request: request.data}).bids; + return bids; + }, + + /** Do syncing, but avoid running the sync > 1 time for S2S bidders */ + getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent) { + if (!syncOptions.iframeEnabled && !syncOptions.pixelEnabled) { + return []; + } + const syncs = []; + serverResponses.forEach(({ body }) => { + const { pbsHost, syncedBidders } = configByBidder[body.ext.prebid.passthrough.relevant.bidder] || {}; + if (!pbsHost) { + return; + } + const { gdprApplies, consentString } = gdprConsent || {}; + let bidders = Object.keys(body.ext?.responsetimemillis || {}); + bidders = bidders.reduce((acc, curr) => { + if (!syncedBidders[curr]) { + acc.push(curr); + syncedBidders[curr] = true; + } + return acc; + }, []); + bidders = shuffle(bidders).slice(0, MAX_SYNC_COUNT); // Shuffle to not always leave out the same bidders + if (!bidders.length) { + return; // All bidders already synced + } + if (syncOptions.iframeEnabled) { + const params = { + endpoint: `${pbsHost}/cookie_sync`, + max_sync_count: bidders.length, + gdpr: gdprApplies ? 1 : 0, + gdpr_consent: consentString, + us_privacy: uspConsent, + bidders: bidders.join(','), + }; + const qs = Object.entries(params) + .filter(([k, v]) => ![null, undefined, ''].includes(v)) + .map(([k, v]) => `${k}=${encodeURIComponent(v.toString())}`) + .join('&'); + syncs.push({ type: 'iframe', url: `${SYNC_HTML}?${qs}` }); + } else { // Else, try to pixel-sync (for future-compatibility) + const pixels = deepAccess(body, `ext.relevant.sync`, []).filter(({ type }) => type === 'redirect'); + syncs.push(...pixels.map(({ url }) => ({ type: 'image', url }))); + } + }); + return syncs; + }, +}; + +registerBidder(spec); diff --git a/modules/relevantdigitalBidAdapter.md b/modules/relevantdigitalBidAdapter.md new file mode 100644 index 00000000000..d54e3e95137 --- /dev/null +++ b/modules/relevantdigitalBidAdapter.md @@ -0,0 +1,117 @@ +# Overview + +``` +Module Name: Relevant Digital Bid Adapter +Module Type: Bidder Adapter +Maintainer: support@relevant-digital.com +``` + +# Description + +This adapter is used for integration with providers using the **[Relevant Yield](https://www.relevant-digital.com/relevantyield)** platform. The provider will supply the necessary **pbsHost** and **accountId** settings along with the **placementId** bid parameters per ad unit. + +# Example setup using pbjs.setConfig() +This is the recommended method to set the global configuration parameters. +```javascript +pbjs.setConfig({ + relevantdigital: { + pbsHost: 'pbs-example.relevant-digital.com', + accountId: '6204e5fa70e3ad10821b84ff', + }, +}); + +var adUnits = [ + { + code: 'test-div', + mediaTypes: { banner: { sizes: [[300, 250], [320, 320]] }}, + bids: [ + { + bidder: 'relevantdigital', + params: { + placementId: '6204e83a077c5825441b8508_620f9e8e4fe67c1f87cd30ed', + } + } + ], + } +]; +``` +# Example setup using only bid params +This method to set the global configuration parameters (like **pbsHost**) in **params** could simplify integration of a provider for some publishers. Setting different global config-parameters on different bids is not supported in general*, as the first settings found will be used and any subsequent global settings will be ignored. + +  * _The exception is `useSourceBidderCode` which can be overriden individually per ad unit._ +```javascript +var adUnits = [ + { + code: 'test-div', + mediaTypes: { banner: { sizes: [[300, 250], [320, 320]] }}, + bids: [ + { + bidder: 'relevantdigital', + params: { + placementId: '6204e83a077c5825441b8508_620f9e8e4fe67c1f87cd30ed', + pbsHost: 'pbs-example.relevant-digital.com', + accountId: '6204e5fa70e3ad10821b84ff', + } + } + ], + } +]; +``` + +# Example setup with multiple providers +**Notice:** Placements below are _not_ live test placements +```javascript + +pbjs.aliasBidder('relevantdigital', 'providerA'); +pbjs.aliasBidder('relevantdigital', 'providerB'); + +pbjs.setConfig({ + providerA: { + pbsHost: 'pbs-example-a.relevant-digital.com', + accountId: '620533ae7f5bbe1691bbb815', + }, + providerB: { + pbsHost: 'pbs-example-b.relevant-digital.com', + accountId: '990533ae7f5bbe1691bbb815', + }, +}); + +var adUnits = [ + { + code: 'test-div', + mediaTypes: { banner: { sizes: [[300, 250], [320, 320]] }}, + bids: [ + { + bidder: 'providerA', + params: { + placementId: '610525862d7517bfd4bbb81e_620523b7d1dbed6b0fbbb817', + } + }, + { + bidder: 'providerB', + params: { + placementId: '990525862d7517bfd4bbb81e_770523b7d1dbed6b0fbbb817', + } + }, + ], + } +]; +``` + +# Bid Parameters + +| Name | Scope | Description | Example | Type | +|---------------|----------|---------------------------------------------------------|----------------------------|--------------| +| `placementId` | required | The placement id. | `'6204e83a077c5825441b8508_620f9e8e4fe67c1f87cd30ed'` | `String` | +| `pbsHost` | required if not set in config | Host name of the server. | `'pbs-example.relevant-digital.com'` | `String` | +| `accountId` | required if not set in config | The account id. | `'6204e5fa70e3ad10821b84ff'` | `String` | +| `useSourceBidderCode` | optional | Set to `true` in order to use the bidder code of the actual server-side bidder in bid responses. You **MUST** also use `allowAlternateBidderCodes: true` in `bidderSettings` if you enabled this - as otherwise the bids will be rejected.| `true` | `Boolean` | + +# Config Parameters + +| Name | Scope | Description | Example | Type | +|---------------|----------|---------------------------------------------------------|----------------------------|--------------| +| `pbsHost` | required if not set in bid parameters | Host name of the server. | `'pbs-example.relevant-digital.com'` | `String` | +| `accountId` | required if not set in bid parameters | The account id. | `'6204e5fa70e3ad10821b84ff'` | `String` | +| `pbsBufferMs` | optional | How much less in *milliseconds* the server's internal timeout should be compared to the normal Prebid timeout. Default is *250*. To be increased in cases of frequent timeouts. | `250` | `Integer` | +| `useSourceBidderCode` | optional | Set to `true` in order to use the bidder code of the actual server-side bidder in bid responses. You **MUST** also use `allowAlternateBidderCodes: true` in `bidderSettings` if you enabled this - as otherwise the bids will be rejected.| `true` | `Boolean` | diff --git a/modules/resetdigitalBidAdapter.js b/modules/resetdigitalBidAdapter.js index a606f0c0b7d..93846e402e9 100644 --- a/modules/resetdigitalBidAdapter.js +++ b/modules/resetdigitalBidAdapter.js @@ -1,9 +1,11 @@ - -import { timestamp, deepAccess } from '../src/utils.js'; +import { timestamp, deepAccess, isStr, deepClone } from '../src/utils.js'; import { getOrigin } from '../libraries/getOrigin/index.js'; import { config } from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER} from '../src/mediaTypes.js'; + const BIDDER_CODE = 'resetdigital'; +const CURRENCY = 'USD'; export const spec = { code: BIDDER_CODE, @@ -43,16 +45,70 @@ export const spec = { }; } + if (bidderRequest && bidderRequest.uspConsent) { + payload.ccpa = bidderRequest.uspConsent; + } + + function getOrtb2Keywords(ortb2Obj) { + const fields = ['site.keywords', 'site.content.keywords', 'user.keywords', 'app.keywords', 'app.content.keywords']; + let result = []; + + fields.forEach(path => { + let keyStr = deepAccess(ortb2Obj, path); + if (isStr(keyStr)) result.push(keyStr); + }); + return result; + } + + // get the ortb2 keywords data (if it exists) + let ortb2 = deepClone(bidderRequest && bidderRequest.ortb2); + let ortb2KeywordsList = getOrtb2Keywords(ortb2); + // get meta keywords data (if it exists) + let metaKeywords = document.getElementsByTagName('meta')['keywords']; + if (metaKeywords && metaKeywords.content) { + metaKeywords = metaKeywords.content.split(','); + } + for (let x = 0; x < validBidRequests.length; x++) { - let req = validBidRequests[x] + let req = validBidRequests[x]; + + let bidFloor = req.params.bidFloor ? req.params.bidFloor : null; + let bidFloorCur = req.params.bidFloor ? req.params.bidFloorCur : null; + + if (typeof req.getFloor === 'function') { + const floorInfo = req.getFloor({ + currency: CURRENCY, + mediaType: BANNER, + size: '*' + }); + if (typeof floorInfo === 'object' && floorInfo.currency === CURRENCY && !isNaN(parseFloat(floorInfo.floor))) { + bidFloor = parseFloat(floorInfo.floor); + bidFloorCur = CURRENCY; + } + } + + // get param kewords (if it exists) + let paramsKeywords = req.params.keywords ? req.params.keywords.split(',') : []; + // merge all keywords + let keywords = ortb2KeywordsList.concat(paramsKeywords).concat(metaKeywords); payload.imps.push({ pub_id: req.params.pubId, + site_id: req.params.siteID ? req.params.siteID : null, + placement_id: req.params.placement ? req.params.placement : null, + position: req.params.position ? req.params.position : null, + bid_floor: bidFloor, + bid_floor_cur: bidFloorCur, + lat_long: req.params.latLong ? req.params.latLong : null, + inventory: req.params.inventory ? req.params.inventory : null, + visitor: req.params.visitor ? req.params.visitor : null, + keywords: keywords.join(','), zone_id: req.params.zoneId, bid_id: req.bidId, imp_id: req.transactionId, sizes: req.sizes, force_bid: req.params.forceBid, + coppa: config.getConfig('coppa') === true ? 1 : 0, media_types: deepAccess(req, 'mediaTypes') }); } @@ -62,7 +118,8 @@ export const spec = { return { method: 'POST', url: url, - data: JSON.stringify(payload) + data: JSON.stringify(payload), + bids: validBidRequests }; }, interpretResponse: function(serverResponse, bidRequest) { diff --git a/modules/resetdigitalBidAdapter.md b/modules/resetdigitalBidAdapter.md index 2f9f69b5e84..a368c7f5633 100644 --- a/modules/resetdigitalBidAdapter.md +++ b/modules/resetdigitalBidAdapter.md @@ -21,17 +21,43 @@ Video is supported but requires a publisher supplied renderer at this time. mediaTypes: { banner: { sizes: [[300,250]] + }, + + }, + bids: [ + { + bidder: "resetdigital", + params: { + pubId: "your-pub-id", + site_id: "your-site-id", + forceBid: true, + } } + ] + } + ]; + + + var videoAdUnits = [ + { + code: 'your-div', + mediaTypes: { + video: { + playerSize: [640, 480] + }, + }, bids: [ { bidder: "resetdigital", params: { pubId: "your-pub-id", - forceBid: true + site_id: "your-site-id", + forceBid: true, } } ] } ]; + ``` diff --git a/modules/riseBidAdapter.js b/modules/riseBidAdapter.js index 0c4d6148f2b..89e4e85c627 100644 --- a/modules/riseBidAdapter.js +++ b/modules/riseBidAdapter.js @@ -384,7 +384,7 @@ function generateGeneralParams(generalObject, bidderRequest) { const {syncEnabled, filterSettings} = config.getConfig('userSync') || {}; const {bidderCode} = bidderRequest; const generalBidParams = generalObject.params; - const timeout = config.getConfig('bidderTimeout'); + const timeout = bidderRequest.timeout; // these params are snake_case instead of camelCase to allow backwards compatability on the server. // in the future, these will be converted to camelCase to match our convention. diff --git a/modules/rivrAnalyticsAdapter.js b/modules/rivrAnalyticsAdapter.js index cf14ff44571..c74ce519ab9 100644 --- a/modules/rivrAnalyticsAdapter.js +++ b/modules/rivrAnalyticsAdapter.js @@ -2,6 +2,7 @@ import {ajax} from '../src/ajax.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import * as utils from '../src/utils.js'; +import {getGlobal} from '../src/prebidGlobal.js'; const analyticsType = 'endpoint'; @@ -20,7 +21,7 @@ rivrAnalytics.originEnableAnalytics = rivrAnalytics.enableAnalytics; // override enableAnalytics so we can get access to the config passed in from the page rivrAnalytics.enableAnalytics = (config) => { if (window.rivraddon && window.rivraddon.analytics) { - window.rivraddon.analytics.enableAnalytics(config, {utils, ajax, pbjsGlobalVariable: $$PREBID_GLOBAL$$}); + window.rivraddon.analytics.enableAnalytics(config, {utils, ajax, pbjsGlobalVariable: getGlobal()}); rivrAnalytics.originEnableAnalytics(config); } }; diff --git a/modules/roxotAnalyticsAdapter.js b/modules/roxotAnalyticsAdapter.js index 1ded17f3a5b..2c3be3e1757 100644 --- a/modules/roxotAnalyticsAdapter.js +++ b/modules/roxotAnalyticsAdapter.js @@ -5,8 +5,11 @@ import adapterManager from '../src/adapterManager.js'; import {includes} from '../src/polyfill.js'; import {ajaxBuilder} from '../src/ajax.js'; import {getStorageManager} from '../src/storageManager.js'; +import {MODULE_TYPE_ANALYTICS} from '../src/activities/modules.js'; -const storage = getStorageManager(); +const MODULE_CODE = 'roxot'; + +const storage = getStorageManager({moduleType: MODULE_TYPE_ANALYTICS, moduleName: MODULE_CODE}); let ajax = ajaxBuilder(0); @@ -504,7 +507,7 @@ function buildLogMessage(message) { adapterManager.registerAnalyticsAdapter({ adapter: roxotAdapter, - code: 'roxot' + code: MODULE_CODE, }); export default roxotAdapter; diff --git a/modules/rtbhouseBidAdapter.js b/modules/rtbhouseBidAdapter.js index 9f498014a8e..5ebf51b6e0d 100644 --- a/modules/rtbhouseBidAdapter.js +++ b/modules/rtbhouseBidAdapter.js @@ -56,7 +56,7 @@ export const spec = { site: mapSite(validBidRequests, bidderRequest), cur: DEFAULT_CURRENCY_ARR, test: validBidRequests[0].params.test || 0, - source: mapSource(validBidRequests[0]), + source: mapSource(validBidRequests[0], bidderRequest), }; if (bidderRequest && bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies) { @@ -85,15 +85,12 @@ export const spec = { } const ortb2Params = bidderRequest?.ortb2 || {}; - if (ortb2Params.site) { - mergeDeep(request, { site: ortb2Params.site }); - } - if (ortb2Params.user) { - mergeDeep(request, { user: ortb2Params.user }); - } - if (ortb2Params.device) { - mergeDeep(request, { device: ortb2Params.device }); - } + ['site', 'user', 'device', 'bcat', 'badv'].forEach(entry => { + const ortb2Param = ortb2Params[entry]; + if (ortb2Param) { + mergeDeep(request, { [entry]: ortb2Param }); + } + }); let computedEndpointUrl = ENDPOINT_URL; @@ -171,10 +168,12 @@ export const spec = { if (fledgeAuctionConfigs) { fledgeAuctionConfigs = Object.entries(fledgeAuctionConfigs).map(([bidId, cfg]) => { - return Object.assign({ + return { bidId, - auctionSignals: {} - }, cfg); + config: Object.assign({ + auctionSignals: {} + }, cfg) + } }); logInfo('Response with FLEDGE:', { bids, fledgeAuctionConfigs }); return { @@ -228,6 +227,13 @@ function mapImpression(slot, bidderRequest) { delete imp.ext.ae; } } + + const tid = deepAccess(slot, 'ortb2Imp.ext.tid'); + if (tid) { + imp.ext = imp.ext || {}; + imp.ext.tid = tid; + } + return imp; } @@ -283,9 +289,9 @@ function mapSite(slot, bidderRequest) { * @param {object} slot Ad Unit Params by Prebid * @returns {object} Source by OpenRTB 2.5 §3.2.2 */ -function mapSource(slot) { +function mapSource(slot, bidderRequest) { const source = { - tid: slot.transactionId, + tid: bidderRequest?.auctionId || '', }; return source; @@ -470,7 +476,7 @@ function interpretNativeBid(serverBid) { function interpretNativeAd(adm) { const native = JSON.parse(adm).native; const result = { - clickUrl: encodeURIComponent(native.link.url), + clickUrl: encodeURI(native.link.url), impressionTrackers: native.imptrackers }; native.assets.forEach(asset => { @@ -480,14 +486,14 @@ function interpretNativeAd(adm) { break; case OPENRTB.NATIVE.ASSET_ID.IMAGE: result.image = { - url: encodeURIComponent(asset.img.url), + url: encodeURI(asset.img.url), width: asset.img.w, height: asset.img.h }; break; case OPENRTB.NATIVE.ASSET_ID.ICON: result.icon = { - url: encodeURIComponent(asset.img.url), + url: encodeURI(asset.img.url), width: asset.img.w, height: asset.img.h }; diff --git a/modules/rtdModule/index.js b/modules/rtdModule/index.js index 05594c63132..29e2ce3de43 100644 --- a/modules/rtdModule/index.js +++ b/modules/rtdModule/index.js @@ -166,6 +166,8 @@ import CONSTANTS from '../../src/constants.json'; import adapterManager, {gdprDataHandler, uspDataHandler, gppDataHandler} from '../../src/adapterManager.js'; import {find} from '../../src/polyfill.js'; import {timedAuctionHook} from '../../src/utils/perfMetrics.js'; +import {GDPR_GVLIDS} from '../../src/consentHandler.js'; +import {MODULE_TYPE_RTD} from '../../src/activities/modules.js'; /** @type {string} */ const MODULE_NAME = 'realTimeData'; @@ -188,6 +190,7 @@ let _userConsent; */ export function attachRealTimeDataProvider(submodule) { registeredSubModules.push(submodule); + GDPR_GVLIDS.register(MODULE_TYPE_RTD, submodule.name, submodule.gvlid) return function detach() { const idx = registeredSubModules.indexOf(submodule) if (idx >= 0) { diff --git a/modules/rubiconAnalyticsAdapter.js b/modules/rubiconAnalyticsAdapter.js index 76043b71c64..7bbc435cb27 100644 --- a/modules/rubiconAnalyticsAdapter.js +++ b/modules/rubiconAnalyticsAdapter.js @@ -5,10 +5,11 @@ import CONSTANTS from '../src/constants.json'; import { ajax } from '../src/ajax.js'; import { config } from '../src/config.js'; import { getGlobal } from '../src/prebidGlobal.js'; -import { getStorageManager } from '../src/storageManager.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {MODULE_TYPE_ANALYTICS} from '../src/activities/modules.js'; const RUBICON_GVL_ID = 52; -export const storage = getStorageManager({gvlid: RUBICON_GVL_ID, moduleName: 'rubicon'}); +export const storage = getStorageManager({moduleType: MODULE_TYPE_ANALYTICS, moduleName: 'rubicon'}); const COOKIE_NAME = 'rpaSession'; const LAST_SEEN_EXPIRE_TIME = 1800000; // 30 mins const END_EXPIRE_TIME = 21600000; // 6 hours diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index bd53e9d5104..36e077aeab8 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -1,5 +1,12 @@ +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; +import { pbsExtensions } from '../libraries/pbsExtensions/pbsExtensions.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { config } from '../src/config.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { find } from '../src/polyfill.js'; +import { getGlobal } from '../src/prebidGlobal.js'; +import { Renderer } from '../src/Renderer.js'; import { - _each, convertTypes, deepAccess, deepSetValue, @@ -11,14 +18,8 @@ import { logMessage, logWarn, mergeDeep, - parseSizesInput + parseSizesInput, _each } from '../src/utils.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {config} from '../src/config.js'; -import {BANNER, VIDEO} from '../src/mediaTypes.js'; -import {find} from '../src/polyfill.js'; -import {Renderer} from '../src/Renderer.js'; -import {getGlobal} from '../src/prebidGlobal.js'; const DEFAULT_INTEGRATION = 'pbjs_lite'; const DEFAULT_PBS_INTEGRATION = 'pbjs'; @@ -115,12 +116,14 @@ var sizeMap = { 257: '400x600', 258: '500x200', 259: '998x200', + 261: '480x480', 264: '970x1000', 265: '1920x1080', 274: '1800x200', 278: '320x500', 282: '320x400', 288: '640x380', + 524: '1x2', 548: '500x1000', 550: '980x480', 552: '300x200', @@ -137,17 +140,101 @@ var sizeMap = { 580: '505x656', 622: '192x160' }; + _each(sizeMap, (item, key) => sizeMap[item] = key); +export const converter = ortbConverter({ + request(buildRequest, imps, bidderRequest, context) { + const {bidRequests} = context; + const data = buildRequest(imps, bidderRequest, context); + data.cur = ['USD']; + data.test = config.getConfig('debug') ? 1 : 0; + deepSetValue(data, 'ext.prebid.cache', { + vastxml: { + returnCreative: rubiConf.returnVast === true + } + }); + + deepSetValue(data, 'ext.prebid.bidders', { + rubicon: { + integration: rubiConf.int_type || DEFAULT_PBS_INTEGRATION, + } + }); + + deepSetValue(data, 'ext.prebid.targeting.pricegranularity', getPriceGranularity(config)); + + let modules = (getGlobal()).installedModules; + if (modules && (!modules.length || modules.indexOf('rubiconAnalyticsAdapter') !== -1)) { + deepSetValue(data, 'ext.prebid.analytics', {'rubicon': {'client-analytics': true}}); + } + + addOrtbFirstPartyData(data, bidRequests); + + delete data?.ext?.prebid?.storedrequest; + + // floors + if (rubiConf.disableFloors === true) { + delete data.ext.prebid.floors; + } + + // If the price floors module is active, then we need to signal to PBS! If floorData obj is present is best way to check + const haveFloorDataBidRequests = bidRequests.filter(bidRequest => typeof bidRequest.floorData === 'object'); + if (haveFloorDataBidRequests.length > 0) { + data.ext.prebid.floors = { enabled: false }; + } + return data; + }, + imp(buildImp, bidRequest, context) { + // skip banner-only requests + const bidRequestType = bidType(bidRequest); + if (bidRequestType.includes(BANNER) && bidRequestType.length == 1) return; + + const imp = buildImp(bidRequest, context); + imp.id = bidRequest.adUnitCode; + delete imp.banner; + if (config.getConfig('s2sConfig.defaultTtl')) { + imp.exp = config.getConfig('s2sConfig.defaultTtl'); + }; + bidRequest.params.position === 'atf' && (imp.video.pos = 1); + bidRequest.params.position === 'btf' && (imp.video.pos = 3); + delete imp.ext?.prebid?.storedrequest; + + setBidFloors(bidRequest, imp); + + return imp; + }, + bidResponse(buildBidResponse, bid, context) { + const bidResponse = buildBidResponse(bid, context); + bidResponse.meta.mediaType = deepAccess(bid, 'ext.prebid.type'); + const {bidRequest} = context; + if (bidResponse.mediaType === VIDEO && bidRequest.mediaTypes.video.context === 'outstream') { + bidResponse.renderer = outstreamRenderer(bidResponse); + } + bidResponse.width = bid.w || deepAccess(bidRequest, 'mediaTypes.video.w') || deepAccess(bidRequest, 'params.video.playerWidth'); + bidResponse.height = bid.h || deepAccess(bidRequest, 'mediaTypes.video.h') || deepAccess(bidRequest, 'params.video.playerHeight'); + + if (deepAccess(bid, 'ext.bidder.rp.advid')) { + deepSetValue(bidResponse, 'meta.advertiserId', bid.ext.bidder.rp.advid); + } + return bidResponse; + }, + context: { + netRevenue: rubiConf.netRevenue !== false, // If anything other than false, netRev is true + ttl: 300, + }, + processors: pbsExtensions +}); + export const spec = { code: 'rubicon', gvlid: GVLID, - supportedMediaTypes: [BANNER, VIDEO], + supportedMediaTypes: [BANNER, VIDEO, NATIVE], /** * @param {object} bid * @return boolean */ isBidRequestValid: function (bid) { + let valid = true; if (typeof bid.params !== 'object') { return false; } @@ -159,15 +246,16 @@ export const spec = { return false } } - let bidFormat = bidType(bid, true); + let bidFormats = bidType(bid, true); // bidType is undefined? Return false - if (!bidFormat) { + if (!bidFormats.length) { return false; - } else if (bidFormat === 'video') { // bidType is video, make sure it has required params - return hasValidVideoParams(bid); + } else if (bidFormats.includes(VIDEO)) { // bidType is video, make sure it has required params + valid = hasValidVideoParams(bid); } - // bidType is banner? return true - return true; + const hasBannerOrNativeMediaType = [BANNER, NATIVE].filter(mediaType => bidFormats.includes(mediaType)).length > 0; + if (!hasBannerOrNativeMediaType) return valid; + return valid && hasBannerOrNativeMediaType; }, /** * @param {BidRequest[]} bidRequests @@ -177,166 +265,57 @@ export const spec = { buildRequests: function (bidRequests, bidderRequest) { // separate video bids because the requests are structured differently let requests = []; - const videoRequests = bidRequests.filter(bidRequest => bidType(bidRequest) === 'video').map(bidRequest => { - bidRequest.startTime = new Date().getTime(); - - const data = { - id: bidRequest.transactionId, - test: config.getConfig('debug') ? 1 : 0, - cur: ['USD'], - source: { - tid: bidRequest.transactionId - }, - tmax: bidderRequest.timeout, - imp: [{ - exp: config.getConfig('s2sConfig.defaultTtl'), - id: bidRequest.adUnitCode, - secure: 1, - ext: { - [bidRequest.bidder]: bidRequest.params - }, - video: deepAccess(bidRequest, 'mediaTypes.video') || {} - }], - ext: { - prebid: { - channel: { - name: 'pbjs', - version: $$PREBID_GLOBAL$$.version - }, - cache: { - vastxml: { - returnCreative: rubiConf.returnVast === true - } - }, - targeting: { - includewinners: true, - // includebidderkeys always false for openrtb - includebidderkeys: false, - pricegranularity: getPriceGranularity(config) - }, - bidders: { - rubicon: { - integration: rubiConf.int_type || DEFAULT_PBS_INTEGRATION - } - } - } - } - } - - // Add alias if it is there - if (bidRequest.bidder !== 'rubicon') { - data.ext.prebid.aliases = { - [bidRequest.bidder]: 'rubicon' - } - } - - let modules = (getGlobal()).installedModules; - if (modules && (!modules.length || modules.indexOf('rubiconAnalyticsAdapter') !== -1)) { - deepSetValue(data, 'ext.prebid.analytics', {'rubicon': {'client-analytics': true}}); - } - - let bidFloor; - if (typeof bidRequest.getFloor === 'function' && !rubiConf.disableFloors) { - let floorInfo; - try { - floorInfo = bidRequest.getFloor({ - currency: 'USD', - mediaType: 'video', - size: parseSizes(bidRequest, 'video') - }); - } catch (e) { - logError('Rubicon: getFloor threw an error: ', e); - } - bidFloor = typeof floorInfo === 'object' && floorInfo.currency === 'USD' && !isNaN(parseInt(floorInfo.floor)) ? parseFloat(floorInfo.floor) : undefined; - } else { - bidFloor = parseFloat(deepAccess(bidRequest, 'params.floor')); - } - if (!isNaN(bidFloor)) { - data.imp[0].bidfloor = bidFloor; - } - - // If the price floors module is active, then we need to signal to PBS! If floorData obj is present is best way to check - if (typeof bidRequest.floorData === 'object') { - data.ext.prebid.floors = { enabled: false }; - } - - // if value is set, will overwrite with same value - data.imp[0].ext[bidRequest.bidder].video.size_id = determineRubiconVideoSizeId(bidRequest) - - appendSiteAppDevice(data, bidRequest, bidderRequest); - - addVideoParameters(data, bidRequest); - - if (bidderRequest.gdprConsent) { - // note - gdprApplies & consentString may be undefined in certain use-cases for consentManagement module - let gdprApplies; - if (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') { - gdprApplies = bidderRequest.gdprConsent.gdprApplies ? 1 : 0; - } - - deepSetValue(data, 'regs.ext.gdpr', gdprApplies); - deepSetValue(data, 'user.ext.consent', bidderRequest.gdprConsent.consentString); - } - - if (bidderRequest.uspConsent) { - deepSetValue(data, 'regs.ext.us_privacy', bidderRequest.uspConsent); - } - - const eids = deepAccess(bidderRequest, 'bids.0.userIdAsEids'); - if (eids && eids.length) { - deepSetValue(data, 'user.ext.eids', eids); - } - - // set user.id value from config value - const configUserId = config.getConfig('user.id'); - if (configUserId) { - deepSetValue(data, 'user.id', configUserId); - } - - if (config.getConfig('coppa') === true) { - deepSetValue(data, 'regs.coppa', 1); - } - - if (bidRequest.schain && hasValidSupplyChainParams(bidRequest.schain)) { - deepSetValue(data, 'source.ext.schain', bidRequest.schain); - } - - const multibid = config.getConfig('multibid'); - if (multibid) { - deepSetValue(data, 'ext.prebid.multibid', multibid.reduce((result, i) => { - let obj = {}; - - Object.keys(i).forEach(key => { - obj[key.toLowerCase()] = i[key]; - }); - - result.push(obj); - - return result; - }, [])); - } - - applyFPD(bidRequest, VIDEO, data); - - // set ext.prebid.auctiontimestamp using auction time - deepSetValue(data.imp[0], 'ext.prebid.auctiontimestamp', bidderRequest.auctionStart); + let filteredHttpRequest = []; + let filteredRequests; + + filteredRequests = bidRequests.filter(req => { + const mediaTypes = bidType(req) || []; + const { length } = mediaTypes; + const { bidonmultiformat, video } = req.params || {}; + + return ( + // if there's just one mediaType and it's video or native, just send it! + (length === 1 && (mediaTypes.includes(VIDEO) || mediaTypes.includes(NATIVE))) || + // if it's two mediaTypes, and they don't contain banner, send to PBS both native & video + (length === 2 && !mediaTypes.includes(BANNER)) || + // if it contains the video param and the Video mediaType, send Video to PBS (not native!) + (video && mediaTypes.includes(VIDEO)) || + // if bidonmultiformat is on, send everything to PBS + (bidonmultiformat && (mediaTypes.includes(VIDEO) || mediaTypes.includes(NATIVE))) + ) + }); - // set storedrequests to undefined so not sent to PBS - // top level and imp level both 'ext.prebid' objects are set above so no exception thrown here - data.ext.prebid.storedrequest = undefined; - data.imp[0].ext.prebid.storedrequest = undefined; + if (filteredRequests && filteredRequests.length) { + const data = converter.toORTB({bidRequests: filteredRequests, bidderRequest}); - return { + filteredHttpRequest.push({ method: 'POST', url: `https://${rubiConf.videoHost || 'prebid-server'}.rubiconproject.com/openrtb2/auction`, data, - bidRequest - } - }); + bidRequest: filteredRequests + }); + } + const bannerBidRequests = bidRequests.filter((req) => { + const mediaTypes = bidType(req) || []; + const {bidonmultiformat, video} = req.params || {}; + return ( + // Send to fastlane if: it must include BANNER and... + mediaTypes.includes(BANNER) && ( + // if it's just banner + (mediaTypes.length === 1) || + // if bidonmultiformat is true + bidonmultiformat || + // if bidonmultiformat is false and there's no video parameter + (!bidonmultiformat && !video) || + // if there's video parameter, but there's no video mediatype + (!bidonmultiformat && video && !mediaTypes.includes(VIDEO)) + ) + ); + }); if (config.getConfig('rubicon.singleRequest') !== true) { // bids are not grouped if single request mode is not enabled - requests = videoRequests.concat(bidRequests.filter(bidRequest => bidType(bidRequest) === 'banner').map(bidRequest => { + requests = filteredHttpRequest.concat(bannerBidRequests.map(bidRequest => { const bidParams = spec.createSlotParams(bidRequest, bidderRequest); return { method: 'GET', @@ -351,8 +330,7 @@ export const spec = { } else { // single request requires bids to be grouped by site id into a single request // note: groupBy wasn't used because deep property access was needed - const nonVideoRequests = bidRequests.filter(bidRequest => bidType(bidRequest) === 'banner'); - const groupedBidRequests = nonVideoRequests.reduce((groupedBids, bid) => { + const groupedBidRequests = bannerBidRequests.reduce((groupedBids, bid) => { (groupedBids[bid.params['siteId']] = groupedBids[bid.params['siteId']] || []).push(bid); return groupedBids; }, {}); @@ -361,7 +339,7 @@ export const spec = { const SRA_BID_LIMIT = 10; // multiple requests are used if bids groups have more than 10 bids - requests = videoRequests.concat(Object.keys(groupedBidRequests).reduce((aggregate, bidGroupKey) => { + requests = filteredHttpRequest.concat(Object.keys(groupedBidRequests).reduce((aggregate, bidGroupKey) => { // for each partioned bidGroup, append a bidRequest to requests list partitionArray(groupedBidRequests[bidGroupKey], SRA_BID_LIMIT).forEach(bidsInGroup => { const combinedSlotParams = spec.combineSlotUrlParams(bidsInGroup.map(bidRequest => { @@ -415,7 +393,6 @@ export const spec = { 'tk_flint', 'x_source.tid', 'l_pb_bid_id', - 'x_source.pchain', 'p_screen_res', 'rp_floor', 'rp_secure', @@ -488,8 +465,8 @@ export const spec = { 'rp_secure': '1', 'tk_flint': `${rubiConf.int_type || DEFAULT_INTEGRATION}_v$prebid.version$`, 'x_source.tid': bidRequest.transactionId, + 'x_imp.ext.tid': bidRequest.transactionId, 'l_pb_bid_id': bidRequest.bidId, - 'x_source.pchain': params.pchain, 'p_screen_res': _getScreenResolution(), 'tk_user_key': params.userId, 'p_geo.latitude': isNaN(parseFloat(latitude)) ? undefined : parseFloat(latitude).toFixed(4), @@ -614,106 +591,36 @@ export const spec = { /** * @param {*} responseObj - * @param {BidRequest|Object.} bidRequest - if request was SRA the bidRequest argument will be a keyed BidRequest array object, + * @param {BidRequest|Object.} request - if request was SRA the bidRequest argument will be a keyed BidRequest array object, * non-SRA responses return a plain BidRequest object * @return {Bid[]} An array of bids which */ - interpretResponse: function (responseObj, {bidRequest}) { + interpretResponse: function (responseObj, request) { responseObj = responseObj.body; + const {data} = request; // check overall response if (!responseObj || typeof responseObj !== 'object') { return []; } - // video response from PBS Java openRTB + // Response from PBS Java openRTB if (responseObj.seatbid) { const responseErrors = deepAccess(responseObj, 'ext.errors.rubicon'); if (Array.isArray(responseErrors) && responseErrors.length > 0) { logWarn('Rubicon: Error in video response'); } - const bids = []; - responseObj.seatbid.forEach(seatbid => { - (seatbid.bid || []).forEach(bid => { - let bidObject = { - requestId: bidRequest.bidId, - currency: responseObj.cur || 'USD', - creativeId: bid.crid, - cpm: bid.price || 0, - bidderCode: seatbid.seat, - ttl: 300, - netRevenue: rubiConf.netRevenue !== false, // If anything other than false, netRev is true - width: bid.w || deepAccess(bidRequest, 'mediaTypes.video.w') || deepAccess(bidRequest, 'params.video.playerWidth'), - height: bid.h || deepAccess(bidRequest, 'mediaTypes.video.h') || deepAccess(bidRequest, 'params.video.playerHeight'), - }; - - if (bid.id) { - bidObject.seatBidId = bid.id; - } - - if (bid.dealid) { - bidObject.dealId = bid.dealid; - } - - if (bid.adomain) { - deepSetValue(bidObject, 'meta.advertiserDomains', Array.isArray(bid.adomain) ? bid.adomain : [bid.adomain]); - } - - if (deepAccess(bid, 'ext.bidder.rp.advid')) { - deepSetValue(bidObject, 'meta.advertiserId', bid.ext.bidder.rp.advid); - } - - let serverResponseTimeMs = deepAccess(responseObj, 'ext.responsetimemillis.rubicon'); - if (bidRequest && serverResponseTimeMs) { - bidRequest.serverResponseTimeMs = serverResponseTimeMs; - } - - if (deepAccess(bid, 'ext.prebid.type') === VIDEO) { - bidObject.mediaType = VIDEO; - deepSetValue(bidObject, 'meta.mediaType', VIDEO); - const extPrebidTargeting = deepAccess(bid, 'ext.prebid.targeting'); - - // If ext.prebid.targeting exists, add it as a property value named 'adserverTargeting' - if (extPrebidTargeting && typeof extPrebidTargeting === 'object') { - bidObject.adserverTargeting = extPrebidTargeting; - } - - // try to get cache values from 'response.ext.prebid.cache.js' - // else try 'bid.ext.prebid.targeting' as fallback - if (bid.ext.prebid.cache && typeof bid.ext.prebid.cache.vastXml === 'object' && bid.ext.prebid.cache.vastXml.cacheId && bid.ext.prebid.cache.vastXml.url) { - bidObject.videoCacheKey = bid.ext.prebid.cache.vastXml.cacheId; - bidObject.vastUrl = bid.ext.prebid.cache.vastXml.url; - } else if (extPrebidTargeting && extPrebidTargeting.hb_uuid && extPrebidTargeting.hb_cache_host && extPrebidTargeting.hb_cache_path) { - bidObject.videoCacheKey = extPrebidTargeting.hb_uuid; - // build url using key and cache host - bidObject.vastUrl = `https://${extPrebidTargeting.hb_cache_host}${extPrebidTargeting.hb_cache_path}?uuid=${extPrebidTargeting.hb_uuid}`; - } - - if (bid.adm) { bidObject.vastXml = bid.adm; } - if (bid.nurl) { bidObject.vastUrl = bid.nurl; } - if (!bidObject.vastUrl && bid.nurl) { bidObject.vastUrl = bid.nurl; } - - const videoContext = deepAccess(bidRequest, 'mediaTypes.video.context'); - if (videoContext.toLowerCase() === 'outstream') { - bidObject.renderer = outstreamRenderer(bidObject); - } - } else { - logWarn('Rubicon: video response received non-video media type'); - } - - bids.push(bidObject); - }); - }); - + const bids = converter.fromORTB({request: data, response: responseObj}).bids; return bids; } let ads = responseObj.ads; let lastImpId; let multibid = 0; + const {bidRequest} = request; // video ads array is wrapped in an object - if (typeof bidRequest === 'object' && !Array.isArray(bidRequest) && bidType(bidRequest) === 'video' && typeof ads === 'object') { + if (typeof bidRequest === 'object' && !Array.isArray(bidRequest) && bidType(bidRequest).includes(VIDEO) && typeof ads === 'object') { ads = ads[bidRequest.adUnitCode]; } @@ -779,7 +686,6 @@ export const spec = { } else { logError(`Rubicon: bidRequest undefined at index position:${i}`, bidRequest, responseObj); } - return bids; }, []).sort((adA, adB) => { return (adB.cpm || 0.0) - (adA.cpm || 0.0); @@ -918,7 +824,7 @@ function outstreamRenderer(rtbBid) { function parseSizes(bid, mediaType) { let params = bid.params; - if (mediaType === 'video') { + if (mediaType === VIDEO) { let size = []; if (params.video && params.video.playerWidth && params.video.playerHeight) { size = [ @@ -948,65 +854,6 @@ function parseSizes(bid, mediaType) { return masSizeOrdering(sizes); } -/** - * @param {Object} data - * @param bidRequest - * @param bidderRequest - */ -function appendSiteAppDevice(data, bidRequest, bidderRequest) { - if (!data) return; - - // ORTB specifies app OR site - if (typeof config.getConfig('app') === 'object') { - data.app = config.getConfig('app'); - } else { - data.site = { - page: _getPageUrl(bidRequest, bidderRequest) - } - } - if (typeof config.getConfig('device') === 'object') { - data.device = config.getConfig('device'); - } - // Add language to site and device objects if there - if (bidRequest.params.video.language) { - ['site', 'device'].forEach(function(param) { - if (data[param]) { - if (param === 'site') { - data[param].content = Object.assign({language: bidRequest.params.video.language}, data[param].content) - } else { - data[param] = Object.assign({language: bidRequest.params.video.language}, data[param]) - } - } - }); - } -} - -/** - * @param {Object} data - * @param {BidRequest} bidRequest - */ -function addVideoParameters(data, bidRequest) { - if (typeof data.imp[0].video === 'object' && data.imp[0].video.skip === undefined) { - data.imp[0].video.skip = bidRequest.params.video.skip; - } - if (typeof data.imp[0].video === 'object' && data.imp[0].video.skipafter === undefined) { - data.imp[0].video.skipafter = bidRequest.params.video.skipdelay; - } - // video.pos can already be specified by adunit.mediatypes.video.pos. - // but if not, it might be specified in the params - if (typeof data.imp[0].video === 'object' && data.imp[0].video.pos === undefined) { - if (bidRequest.params.position === 'atf') { - data.imp[0].video.pos = 1; - } else if (bidRequest.params.position === 'btf') { - data.imp[0].video.pos = 3; - } - } - - const size = parseSizes(bidRequest, 'video') - data.imp[0].video.w = size[0] - data.imp[0].video.h = size[1] -} - function applyFPD(bidRequest, mediaType, data) { const BID_FPD = { user: {ext: {data: {...bidRequest.params.visitor}}}, @@ -1117,11 +964,15 @@ function mapSizes(sizes) { export function classifiedAsVideo(bidRequest) { let isVideo = typeof deepAccess(bidRequest, `mediaTypes.${VIDEO}`) !== 'undefined'; let isBanner = typeof deepAccess(bidRequest, `mediaTypes.${BANNER}`) !== 'undefined'; + let isBidOnMultiformat = typeof deepAccess(bidRequest, `params.bidonmultiformat`) !== 'undefined'; let isMissingVideoParams = typeof deepAccess(bidRequest, 'params.video') !== 'object'; // If an ad has both video and banner types, a legacy implementation allows choosing video over banner // based on whether or not there is a video object defined in the params // Given this legacy implementation, other code depends on params.video being defined + // if it's bidonmultiformat, we don't care of the video object + if (isVideo && isBidOnMultiformat) return true; + if (isBanner && isMissingVideoParams) { isVideo = false; } @@ -1132,13 +983,14 @@ export function classifiedAsVideo(bidRequest) { } /** - * Determine bidRequest mediaType + * Determine bidRequest mediaTypes. All mediaTypes must be correct. If one fails, all the others will fail too. * @param bid the bid to test - * @param log whether we should log errors/warnings for invalid bids - * @returns {string|undefined} Returns 'video' or 'banner' if resolves to a type, or undefined otherwise (invalid). + * @param log boolean. whether we should log errors/warnings for invalid bids + * @returns {string|undefined} Returns an array containing one of 'video' or 'banner' or 'native' if resolves to a type. */ function bidType(bid, log = false) { // Is it considered video ad unit by rubicon + let bidTypes = []; if (classifiedAsVideo(bid)) { // Removed legacy mediaType support. new way using mediaTypes.video object is now required // We require either context as instream or outstream @@ -1146,37 +998,43 @@ function bidType(bid, log = false) { if (log) { logError('Rubicon: mediaTypes.video.context must be outstream or instream'); } - return; + return bidTypes; } // we require playerWidth and playerHeight to come from one of params.playerWidth/playerHeight or mediaTypes.video.playerSize or adUnit.sizes - if (parseSizes(bid, 'video').length < 2) { + if (parseSizes(bid, VIDEO).length < 2) { if (log) { logError('Rubicon: could not determine the playerSize of the video'); } - return; + return bidTypes; } if (log) { logMessage('Rubicon: making video request for adUnit', bid.adUnitCode); } - return 'video'; - } else { + bidTypes.push(VIDEO); + } + if (typeof deepAccess(bid, `mediaTypes.${NATIVE}`) !== 'undefined') { + bidTypes.push(NATIVE); + } + + if (typeof deepAccess(bid, `mediaTypes.${BANNER}`) !== 'undefined') { // we require banner sizes to come from one of params.sizes or mediaTypes.banner.sizes or adUnit.sizes, in that order // if we cannot determine them, we reject it! - if (parseSizes(bid, 'banner').length === 0) { + if (parseSizes(bid, BANNER).length === 0) { if (log) { logError('Rubicon: could not determine the sizes for banner request'); } - return; + return bidTypes; } // everything looks good for banner so lets do it if (log) { logMessage('Rubicon: making banner request for adUnit', bid.adUnitCode); } - return 'banner'; + bidTypes.push(BANNER); } + return bidTypes; } export const resetRubiConf = () => rubiConf = {}; @@ -1306,4 +1164,64 @@ export function resetUserSync() { hasSynced = false; } +/** + * Sets the floor on the bidRequest. imp.bidfloor and imp.bidfloorcur + * should be already set by the conversion library. if they're not, + * or invalid, try to read from params.floor. + * @param {*} bidRequest + * @param {*} imp + */ +function setBidFloors(bidRequest, imp) { + if (imp.bidfloorcur != 'USD') { + delete imp.bidfloor; + delete imp.bidfloorcur; + } + + if (!imp.bidfloor) { + let bidFloor = parseFloat(deepAccess(bidRequest, 'params.floor')); + + if (!isNaN(bidFloor)) { + imp.bidfloor = bidFloor; + imp.bidfloorcur = 'USD'; + } + } +} + +function addOrtbFirstPartyData(data, nonBannerRequests) { + let fpd = {}; + const keywords = new Set(); + nonBannerRequests.forEach(bidRequest => { + const bidFirstPartyData = { + user: {ext: {data: {...bidRequest.params.visitor}}}, + site: {ext: {data: {...bidRequest.params.inventory}}} + }; + + // add site.content.language + const impThatHasVideoLanguage = data.imp.find(imp => imp.ext?.prebid?.bidder?.rubicon?.video?.language); + if (impThatHasVideoLanguage) { + bidFirstPartyData.site.content = { + language: impThatHasVideoLanguage.ext?.prebid?.bidder?.rubicon?.video?.language + } + } + + if (bidRequest.params.keywords) { + const keywordsArray = (!Array.isArray(bidRequest.params.keywords) ? bidRequest.params.keywords.split(',') : bidRequest.params.keywords); + keywordsArray.forEach(keyword => keywords.add(keyword)); + } + fpd = mergeDeep(fpd, bidRequest.ortb2 || {}, bidFirstPartyData); + + // add user.id from config. + // NOTE: This is DEPRECATED. user.id should come from setConfig({ortb2}). + const configUserId = config.getConfig('user.id'); + fpd.user.id = fpd.user.id || configUserId; + }); + + mergeDeep(data, fpd); + + if (keywords && keywords.size) { + deepSetValue(data, 'site.keywords', Array.from(keywords.values()).join(',')); + } + delete data?.ext?.prebid?.storedrequest; +} + registerBidder(spec); diff --git a/modules/scatteredBidAdapter.js b/modules/scatteredBidAdapter.js new file mode 100644 index 00000000000..47dc09cd1b2 --- /dev/null +++ b/modules/scatteredBidAdapter.js @@ -0,0 +1,72 @@ +// jshint esversion: 6, es3: false, node: true +'use strict'; + +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER } from '../src/mediaTypes.js'; +import { deepAccess, logInfo } from '../src/utils.js'; +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; + +const BIDDER_CODE = 'scattered'; +const GVLID = 1179; +export const converter = ortbConverter({ + context: { + mediaType: BANNER, + ttl: 360, + netRevenue: true + } +}) + +export const spec = { + code: BIDDER_CODE, + gvlid: GVLID, + supportedMediaTypes: [BANNER], + + // 1. + isBidRequestValid: function (bid) { + const bidderDomain = deepAccess(bid, 'params.bidderDomain') + if (bidderDomain === undefined || bidderDomain === '') { + return false + } + + const sizes = deepAccess(bid, 'mediaTypes.banner.sizes') + if (sizes === undefined || sizes.length < 1) { + return false + } + + return true + }, + + // 2. + buildRequests: function (bidRequests, bidderRequest) { + return { + method: 'POST', + url: 'https://' + getKeyOnAny(bidRequests, 'params.bidderDomain'), + data: converter.toORTB({ bidderRequest, bidRequests }), + options: { + contentType: 'application/json' + }, + }; + }, + + // 3. + interpretResponse: function (response, request) { + if (!response.body) return; + return converter.fromORTB({ response: response.body, request: request.data }).bids; + }, + + // 4 + onBidWon: function (bid) { + logInfo('onBidWon', bid) + } +} + +function getKeyOnAny(collection, key) { + for (let i = 0; i < collection.length; i++) { + const result = deepAccess(collection[i], key); + if (result) { + return result; + } + } +} + +registerBidder(spec); diff --git a/modules/scatteredBidAdapter.md b/modules/scatteredBidAdapter.md new file mode 100644 index 00000000000..031d953e32b --- /dev/null +++ b/modules/scatteredBidAdapter.md @@ -0,0 +1,36 @@ +# Overview + +``` +Module Name: Scattered Adapter +Module Type: Bidder Adapter +Maintainer: office@scattered.pl +``` + +# Description + +Module that connects to Scattered's demand sources. +It uses OpenRTB standard to communicate between the adapter and bidding servers. + +# Test Parameters + +```javascript + var adUnits = [ + { + code: 'test-div', + mediaTypes: { + banner: { + sizes: [[300, 250]], // a display size + } + }, + bids: [ + { + bidder: "scattered", + params: { + bidderDomain: "prebid-test.scattered.eu/bid", + test: 0 + } + } + ] + } + ]; +``` \ No newline at end of file diff --git a/modules/seedingAllianceBidAdapter.js b/modules/seedingAllianceBidAdapter.js index 64b9cd5d4aa..29953da7ffa 100755 --- a/modules/seedingAllianceBidAdapter.js +++ b/modules/seedingAllianceBidAdapter.js @@ -2,120 +2,111 @@ '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 { NATIVE, BANNER } from '../src/mediaTypes.js'; +import { _map, isArray, isEmpty, deepSetValue, replaceAuctionPrice } from '../src/utils.js'; import { config } from '../src/config.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; -const BIDDER_CODE = 'seedingAlliance'; const GVL_ID = 371; +const BIDDER_CODE = 'seedingAlliance'; const DEFAULT_CUR = 'EUR'; const ENDPOINT_URL = 'https://b.nativendo.de/cds/rtb/bid?format=openrtb2.5&ssp=pb'; -const NATIVE_ASSET_IDS = {0: 'title', 1: 'body', 2: 'sponsoredBy', 3: 'image', 4: 'cta', 5: 'icon'}; +const NATIVE_ASSET_IDS = { 0: 'title', 1: 'body', 2: 'sponsoredBy', 3: 'image', 4: 'cta', 5: 'icon' }; const NATIVE_PARAMS = { - title: { - id: 0, - name: 'title' - }, - - body: { - id: 1, - name: 'data', - type: 2 - }, - - sponsoredBy: { - id: 2, - name: 'data', - type: 1 - }, - - image: { - id: 3, - type: 3, - name: 'img' - }, - - cta: { - id: 4, - type: 12, - name: 'data' - }, - - icon: { - id: 5, - type: 1, - name: 'img' - } + title: { id: 0, name: 'title' }, + body: { id: 1, name: 'data', type: 2 }, + sponsoredBy: { id: 2, name: 'data', type: 1 }, + image: { id: 3, type: 3, name: 'img' }, + cta: { id: 4, type: 12, name: 'data' }, + icon: { id: 5, type: 1, name: 'img' } }; export const spec = { code: BIDDER_CODE, - gvlid: GVL_ID, + supportedMediaTypes: [NATIVE, BANNER], - supportedMediaTypes: [NATIVE], - - isBidRequestValid: function(bid) { + isBidRequestValid: function (bid) { return !!bid.params.adUnitId; }, - buildRequests: (validBidRequests, bidderRequest) => { + buildRequests: (validBidRequests = [], bidderRequest) => { // 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 cur = [config.getConfig('currency.adServerCurrency') || DEFAULT_CUR]; let url = bidderRequest.refererInfo.page; - const imp = validBidRequests.map((bid, id) => { - const assets = _map(bid.nativeParams, (bidParams, key) => { - const props = NATIVE_PARAMS[key]; - - const asset = { - required: bidParams.required & 1 - }; + const imps = validBidRequests.map((bidRequest, id) => { + const imp = { + id: String(id + 1), + tagid: bidRequest.params.adUnitId + }; - if (props) { - asset.id = props.id; + /** + * Native Ad + */ + if (bidRequest.nativeParams) { + const assets = _map(bidRequest.nativeParams, (nativeAsset, key) => { + const props = NATIVE_PARAMS[key]; + + if (props) { + let wmin, hmin, w, h; + let aRatios = nativeAsset.aspect_ratios; + + if (aRatios && aRatios[0]) { + aRatios = aRatios[0]; + wmin = aRatios.min_width || 0; + hmin = aRatios.ratio_height * wmin / aRatios.ratio_width | 0; + } - let w, h; + if (nativeAsset.sizes) { + const sizes = flatten(nativeAsset.sizes); + w = parseInt(sizes[0], 10); + h = parseInt(sizes[1], 10); + } - if (bidParams.sizes) { - w = bidParams.sizes[0]; - h = bidParams.sizes[1]; + const asset = { + id: props.id, + required: nativeAsset.required & 1 + }; + + asset[props.name] = { + len: nativeAsset.len, + type: props.type, + wmin, + hmin, + w, + h + }; + + return asset; + } else { + // TODO Filter impressions with required assets we don't support } + }).filter(Boolean); - asset[props.name] = { - len: bidParams.len, - type: props.type, - w, - h - }; + imp.native = { + request: { + assets + } + }; + } else { + let sizes = transformSizes(bidRequest.sizes); - return asset; + imp.banner = { + format: sizes, + w: sizes[0] ? sizes[0].w : 0, + h: sizes[0] ? sizes[0].h : 0 } - }) - .filter(Boolean); + } - if (bid.params.url) { - url = bid.params.url; + if (bidRequest.params.url) { + url = bidRequest.params.url; } - return { - id: String(id + 1), - tagid: bid.params.adUnitId, - tid: tid, - pt: pt, - native: { - request: { - assets - } - } - }; + return imp; }); const request = { @@ -123,12 +114,9 @@ export const spec = { site: { page: url }, - device: { - ua: navigator.userAgent - }, - cur, - imp, - user: {}, + cur: [config.getConfig('currency.adServerCurrency') || DEFAULT_CUR], + imp: imps, + tmax: bidderRequest.timeout, regs: { ext: { gdpr: 0, @@ -137,23 +125,22 @@ export const spec = { } }; - if (bidderRequest && bidderRequest.gdprConsent) { + if (bidderRequest.gdprConsent) { + request.user = {}; + deepSetValue(request, 'user.ext.consent', bidderRequest.gdprConsent.consentString); deepSetValue(request, 'regs.ext.gdpr', (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean' && bidderRequest.gdprConsent.gdprApplies) ? 1 : 0); } return { method: 'POST', - url: ENDPOINT_URL, + url: config.getConfig('seedingAlliance.endpoint') || ENDPOINT_URL, data: JSON.stringify(request), - options: { - contentType: 'application/json' - }, - bids: validBidRequests + bidRequests: validBidRequests }; }, - interpretResponse: function(serverResponse, { bids }) { + interpretResponse: function (serverResponse, { bidRequests }) { if (isEmpty(serverResponse.body)) { return []; } @@ -165,35 +152,72 @@ export const spec = { return result; }, []) : []; - return bids - .map((bid, id) => { + return bidRequests + .map((bidRequest, id) => { const bidResponse = bidResponses[id]; + const type = bidRequest.nativeParams ? NATIVE : BANNER; + if (bidResponse) { - return { - requestId: bid.bidId, + const bidObject = { + requestId: bidRequest.bidId, // TODO get this value from response? cpm: bidResponse.price, creativeId: bidResponse.crid, - ttl: 1000, - netRevenue: (!bid.netRevenue || bid.netRevenue === 'net'), + ttl: 600, + netRevenue: true, currency: cur, - mediaType: NATIVE, + mediaType: type, bidderCode: BIDDER_CODE, - native: parseNative(bidResponse), meta: { advertiserDomains: bidResponse.adomain && bidResponse.adomain.length > 0 ? bidResponse.adomain : [] } }; + + if (type === NATIVE) { + bidObject.native = parseNative(bidResponse); + bidObject.mediaType = NATIVE; + } + + if (type === BANNER) { + bidObject.ad = replaceAuctionPrice(bidResponse.adm, bidResponse.price); + bidObject.width = bidResponse.w; + bidObject.height = bidResponse.h; + bidObject.mediaType = BANNER; + } + + return bidObject; } }) .filter(Boolean); } }; -registerBidder(spec); +function transformSizes(requestSizes) { + if (!isArray(requestSizes)) { + return []; + } + + if (requestSizes.length === 2 && !isArray(requestSizes[0])) { + return [{ + w: parseInt(requestSizes[0], 10), + h: parseInt(requestSizes[1], 10) + }]; + } else if (isArray(requestSizes[0])) { + return requestSizes.map(item => ({ + w: parseInt(item[0], 10), + h: parseInt(item[1], 10) + })); + } + + return []; +} + +function flatten(arr) { + return [].concat(...arr); +} function parseNative(bid) { - const {assets, link, imptrackers} = bid.adm.native; + const { assets, link, imptrackers } = bid.adm.native; let clickUrl = link.url.replace(/\$\{AUCTION_PRICE\}/g, bid.price); @@ -228,15 +252,4 @@ function parseNative(bid) { return result; } -function setOnAny(collection, key) { - for (let i = 0, result; i < collection.length; i++) { - result = deepAccess(collection[i], key); - if (result) { - return result; - } - } -} - -function flatten(arr) { - return [].concat(...arr); -} +registerBidder(spec); diff --git a/modules/seedtagBidAdapter.js b/modules/seedtagBidAdapter.js index d5abb89437b..f54245a41ab 100644 --- a/modules/seedtagBidAdapter.js +++ b/modules/seedtagBidAdapter.js @@ -60,6 +60,10 @@ function hasVideoMediaType(bid) { return !!bid.mediaTypes && !!bid.mediaTypes.video; } +function hasBannerMediaType(bid) { + return !!bid.mediaTypes && !!bid.mediaTypes.banner; +} + function hasMandatoryDisplayParams(bid) { const p = bid.params; return ( @@ -72,17 +76,27 @@ function hasMandatoryDisplayParams(bid) { function hasMandatoryVideoParams(bid) { const videoParams = getVideoParams(bid); - return ( + let isValid = !!bid.params.publisherId && !!bid.params.adUnitId && hasVideoMediaType(bid) && !!videoParams.playerSize && isArray(videoParams.playerSize) && - videoParams.playerSize.length > 0 && - // only instream is supported for video - videoParams.context === 'instream' && - bid.params.placement === 'inStream' - ); + videoParams.playerSize.length > 0; + + switch (bid.params.placement) { + // instream accept only video format + case 'inStream': + return isValid && videoParams.context === 'instream'; + // outstream accept banner/native/video format + default: + return ( + isValid && + videoParams.context === 'outstream' && + hasBannerMediaType(bid) && + hasMandatoryDisplayParams(bid) + ); + } } function buildBidRequest(validBidRequest) { diff --git a/modules/sharedIdSystem.js b/modules/sharedIdSystem.js index 7562b472047..ef56e10870b 100644 --- a/modules/sharedIdSystem.js +++ b/modules/sharedIdSystem.js @@ -5,13 +5,15 @@ * @requires module:modules/userId */ -import { parseUrl, buildUrl, triggerPixel, logInfo, hasDeviceAccess, generateUUID } from '../src/utils.js'; +import {parseUrl, buildUrl, triggerPixel, logInfo, hasDeviceAccess, generateUUID} from '../src/utils.js'; import {submodule} from '../src/hook.js'; -import { coppaDataHandler } from '../src/adapterManager.js'; +import {coppaDataHandler} from '../src/adapterManager.js'; import {getStorageManager} from '../src/storageManager.js'; import {VENDORLESS_GVLID} from '../src/consentHandler.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; +import {domainOverrideToRootDomain} from '../libraries/domainOverrideToRootDomain/index.js'; -export const storage = getStorageManager({moduleName: 'pubCommonId', gvlid: VENDORLESS_GVLID}); +export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: 'pubCommonId'}); const COOKIE = 'cookie'; const LOCAL_STORAGE = 'html5'; const OPTOUT_NAME = '_pubcid_optout'; @@ -38,12 +40,15 @@ function readValue(name, type) { } } -function getIdCallback(pubcid, pixelCallback) { - return function (callback) { - if (typeof pixelCallback === 'function') { - pixelCallback(); +function getIdCallback(pubcid, pixelUrl) { + return function (callback, getStoredId) { + if (pixelUrl) { + queuePixelCallback(pixelUrl, pubcid, () => { + callback(getStoredId() || pubcid); + })(); + } else { + callback(pubcid); } - callback(pubcid); } } @@ -58,7 +63,7 @@ function queuePixelCallback(pixelUrl, id = '', callback) { const targetUrl = buildUrl(urlInfo); return function () { - triggerPixel(targetUrl); + triggerPixel(targetUrl, callback); }; } @@ -125,8 +130,7 @@ export const sharedIdSystemSubmodule = { if (!newId) newId = (create && hasDeviceAccess()) ? generateUUID() : undefined; } - const pixelCallback = queuePixelCallback(pixelUrl, newId); - return {id: newId, callback: getIdCallback(newId, pixelCallback)}; + return {id: newId, callback: getIdCallback(newId, pixelUrl)}; }, /** * performs action to extend an id. There are generally two ways to extend the expiration time @@ -169,31 +173,7 @@ export const sharedIdSystemSubmodule = { } }, - domainOverride: function () { - const domainElements = document.domain.split('.'); - const cookieName = `_gd${Date.now()}`; - 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; - } - } - } - + domainOverride: domainOverrideToRootDomain(storage, 'sharedId'), }; submodule('userId', sharedIdSystemSubmodule); diff --git a/modules/sharethroughBidAdapter.js b/modules/sharethroughBidAdapter.js index 0c8b84fd0c5..9c91af8b130 100644 --- a/modules/sharethroughBidAdapter.js +++ b/modules/sharethroughBidAdapter.js @@ -2,7 +2,6 @@ import { deepAccess, generateUUID, inIframe } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; -import { createEidsArray } from './userId/eids.js'; const VERSION = '4.3.0'; const BIDDER_CODE = 'sharethrough'; @@ -22,7 +21,7 @@ export const sharethroughAdapterSpec = { isBidRequestValid: bid => !!bid.params.pkey && bid.bidder === BIDDER_CODE, buildRequests: (bidRequests, bidderRequest) => { - const timeout = config.getConfig('bidderTimeout'); + const timeout = bidderRequest.timeout; const firstPartyData = bidderRequest.ortb2 || {}; const nonHttp = sharethroughInternal.getProtocol().indexOf('http') < 0; @@ -66,7 +65,7 @@ export const sharethroughAdapterSpec = { req.user = nullish(firstPartyData.user, {}); if (!req.user.ext) req.user.ext = {}; - req.user.ext.eids = createEidsArray(deepAccess(bidRequests[0], 'userId')) || []; + req.user.ext.eids = bidRequests[0].userIdAsEids || []; if (bidderRequest.gdprConsent) { const gdprApplies = bidderRequest.gdprConsent.gdprApplies === true; diff --git a/modules/shinezBidAdapter.js b/modules/shinezBidAdapter.js index 0ce2eed6479..f93736894f5 100644 --- a/modules/shinezBidAdapter.js +++ b/modules/shinezBidAdapter.js @@ -364,7 +364,7 @@ function generateGeneralParams(generalObject, bidderRequest) { const {syncEnabled, filterSettings} = config.getConfig('userSync') || {}; const {bidderCode} = bidderRequest; const generalBidParams = generalObject.params; - const timeout = config.getConfig('bidderTimeout'); + const timeout = bidderRequest.timeout; // these params are snake_case instead of camelCase to allow backwards compatability on the server. // in the future, these will be converted to camelCase to match our convention. diff --git a/modules/showheroes-bsBidAdapter.js b/modules/showheroes-bsBidAdapter.js index f9ce3a450cf..4fa95e4ba51 100644 --- a/modules/showheroes-bsBidAdapter.js +++ b/modules/showheroes-bsBidAdapter.js @@ -38,7 +38,9 @@ export const spec = { }, buildRequests: function(validBidRequests, bidderRequest) { let adUnits = []; - const pageURL = validBidRequests[0].params.contentPageUrl || bidderRequest.refererInfo.referer; + const pageURL = validBidRequests[0].params.contentPageUrl || + bidderRequest.refererInfo.canonicalUrl || + deepAccess(window, 'location.href'); const isStage = !!validBidRequests[0].params.stage; const isViralize = !!validBidRequests[0].params.unitId; const isOutstream = deepAccess(validBidRequests[0], 'mediaTypes.video.context') === 'outstream'; @@ -50,6 +52,7 @@ export const spec = { const defaultSchain = validBidRequests[0].schain || {}; const consentData = bidderRequest.gdprConsent || {}; + const uspConsent = bidderRequest.uspConsent || ''; const gdprConsent = { apiVersion: consentData.apiVersion || 2, gdprApplies: consentData.gdprApplies || 0, @@ -104,6 +107,7 @@ export const spec = { height: size[1] }; rBid.gdprConsent = gdprConsent; + rBid.uspConsent = uspConsent; } return rBid; @@ -138,6 +142,7 @@ export const spec = { 'bidRequests': adUnits, 'context': { 'gdprConsent': gdprConsent, + 'uspConsent': uspConsent, 'schain': defaultSchain, 'pageURL': QA.pageURL || encodeURIComponent(pageURL) } diff --git a/modules/sigmoidAnalyticsAdapter.js b/modules/sigmoidAnalyticsAdapter.js index dc386813978..18e1e20e3e3 100644 --- a/modules/sigmoidAnalyticsAdapter.js +++ b/modules/sigmoidAnalyticsAdapter.js @@ -6,8 +6,10 @@ import CONSTANTS from '../src/constants.json'; import adapterManager from '../src/adapterManager.js'; import {getStorageManager} from '../src/storageManager.js'; import {generateUUID, logError, logInfo} from '../src/utils.js'; +import {MODULE_TYPE_ANALYTICS} from '../src/activities/modules.js'; -const storage = getStorageManager(); +const MODULE_CODE = 'sigmoid'; +const storage = getStorageManager({moduleType: MODULE_TYPE_ANALYTICS, moduleName: MODULE_CODE}); const url = 'https://kinesis.us-east-1.amazonaws.com/'; const analyticsType = 'endpoint'; @@ -285,7 +287,7 @@ function pushEvent(eventType, args) { adapterManager.registerAnalyticsAdapter({ adapter: sigmoidAdapter, - code: 'sigmoid' + code: MODULE_CODE, }); export default sigmoidAdapter; diff --git a/modules/sirdataRtdProvider.js b/modules/sirdataRtdProvider.js index 369276e7638..40ee3d8b973 100644 --- a/modules/sirdataRtdProvider.js +++ b/modules/sirdataRtdProvider.js @@ -1,12 +1,13 @@ /** * This module adds Sirdata provider to the real time data module + * and now supports Seller Defined Audience * The {@link module:modules/realTimeData} module is required * The module will fetch segments (user-centric) and categories (page-centric) from Sirdata server * The module will automatically handle user's privacy and choice in California (IAB TL CCPA Framework) and in Europe (IAB EU TCF FOR GDPR) * @module modules/sirdataRtdProvider * @requires module:modules/realTimeData */ -import {deepAccess, deepEqual, deepSetValue, isEmpty, logError, mergeDeep} from '../src/utils.js'; +import {deepAccess, deepSetValue, isEmpty, logError, mergeDeep} from '../src/utils.js'; import {submodule} from '../src/hook.js'; import {ajax} from '../src/ajax.js'; import {findIndex} from '../src/polyfill.js'; @@ -16,6 +17,52 @@ import {config} from '../src/config.js'; /** @type {string} */ const MODULE_NAME = 'realTimeData'; const SUBMODULE_NAME = 'SirdataRTDModule'; +const ORTB2_NAME = 'sirdata.com'; + +const partnerIds = { + 'criteo': 27443, + 'openx': 30342, + 'pubmatic': 30345, + 'smaato': 27520, + 'triplelift': 27518, + 'yahoossp': 30339, + 'rubicon': 27452, + 'appnexus': 27446, + 'appnexusAst': 27446, + 'brealtime': 27446, + 'emxdigital': 27446, + 'pagescience': 27446, + 'gourmetads': 33394, + 'matomy': 27446, + 'featureforward': 27446, + 'oftmedia': 27446, + 'districtm': 27446, + 'adasta': 27446, + 'beintoo': 27446, + 'gravity': 27446, + 'msq_classic': 27878, + 'msq_max': 27878, + '366_apx': 27878, + 'mediasquare': 27878, + 'smartadserver': 27440, + 'smart': 27440, + 'proxistore': 27484, + 'ix': 27248, + 'sdRtdForGpt': 27449, + 'smilewanted': 28690, + 'taboola': 33379, + 'ttd': 33382, + 'zeta_global': 33385, + 'teads': 33388, + 'conversant': 33391, + 'improvedigital': 33397, + 'invibes': 33400, + 'sublime': 33403, + 'rtbhouse': 33406, + 'zeta_global_ssp': 33385, +}; + +let CONTEXT_ONLY = true; export function getSegmentsAndCategories(reqBidsConfigObj, onDone, moduleConfig, userConsent) { moduleConfig.params = moduleConfig.params || {}; @@ -46,9 +93,10 @@ export function getSegmentsAndCategories(reqBidsConfigObj, onDone, moduleConfig, if (!sirdataDomain || !gdprApplies || (deepAccess(userConsent, 'gdpr.vendorData.vendor.consents') && userConsent.gdpr.vendorData.vendor.consents[53] && userConsent.gdpr.vendorData.purpose.consents[1] && userConsent.gdpr.vendorData.purpose.consents[4])) { sirdataDomain = 'sddan.com'; sendWithCredentials = true; + CONTEXT_ONLY = false; } - // TODO: is 'page' the right value here? - var actualUrl = moduleConfig.params.actualUrl || getRefererInfo().page; + + var actualUrl = moduleConfig.params.actualUrl || getRefererInfo().stack.pop() || getRefererInfo().page; const url = 'https://kvt.' + sirdataDomain + '/api/v1/public/p/' + moduleConfig.params.partnerId + '/d/' + moduleConfig.params.key + '/s?callback=&gdpr=' + gdprApplies + '&gdpr_consent=' + tcString + (actualUrl ? '&url=' + encodeURIComponent(actualUrl) : ''); @@ -86,39 +134,74 @@ export function getSegmentsAndCategories(reqBidsConfigObj, onDone, moduleConfig, }); } -export function setGlobalOrtb2(ortb2, segments, categories) { +export function setGlobalOrtb2Sda(ortb2Fragments, data, segtaxid, cattaxid) { try { - let addOrtb2 = {}; - let testGlobal = ortb2 || {} - if (!deepAccess(testGlobal, 'user.ext.data.sd_rtd') || !deepEqual(testGlobal.user.ext.data.sd_rtd, segments)) { - deepSetValue(addOrtb2, 'user.ext.data.sd_rtd', segments || {}); + if (!isEmpty(data.segments)) { + applyGlobalOrtb2Sda(ortb2Fragments, 'user', data.segments, segtaxid); } - if (!deepAccess(testGlobal, 'site.ext.data.sd_rtd') || !deepEqual(testGlobal.site.ext.data.sd_rtd, categories)) { - deepSetValue(addOrtb2, 'site.ext.data.sd_rtd', categories || {}); - } - if (!isEmpty(addOrtb2)) { - mergeDeep(ortb2, addOrtb2); + if (!isEmpty(data.categories)) { + applyGlobalOrtb2Sda(ortb2Fragments, 'site', data.categories, cattaxid); } } catch (e) { logError(e) } + return true; +} +export function applyGlobalOrtb2Sda(ortb2Fragments, type, segments, segtaxValue) { + try { + let ortb2Data = [{ + name: ORTB2_NAME, + segment: segments.map((segmentId) => ({ id: segmentId })), + }]; + if (segtaxValue) { + ortb2Data[0].ext = { segtax: segtaxValue }; + } + let ortb2Conf = (type == 'site' ? {site: {content: {data: ortb2Data}}} : {user: {data: ortb2Data}}); + mergeDeep(ortb2Fragments, ortb2Conf); + } catch (e) { + logError(e) + } return true; } -export function setBidderOrtb2(bidderOrtb2, bidder, segments, categories) { +export function setBidderOrtb2Sda(ortb2Fragments, bidder, data, segtaxid, cattaxid) { try { - let addOrtb2 = {}; - let testBidder = bidderOrtb2[bidder]; - if (!deepAccess(testBidder, 'user.ext.data.sd_rtd') || !deepEqual(testBidder.user.ext.data.sd_rtd, segments)) { - deepSetValue(addOrtb2, 'user.ext.data.sd_rtd', segments || {}); + if (!isEmpty(data.segments)) { + applyBidderOrtb2Sda(ortb2Fragments, bidder, 'user', data.segments, segtaxid); } - if (!deepAccess(testBidder, 'site.ext.data.sd_rtd') || !deepEqual(testBidder.site.ext.data.sd_rtd, categories)) { - deepSetValue(addOrtb2, 'site.ext.data.sd_rtd', categories || {}); + if (!isEmpty(data.categories)) { + applyBidderOrtb2Sda(ortb2Fragments, bidder, 'site', data.categories, cattaxid); } - if (!isEmpty(addOrtb2)) { - mergeDeep(bidderOrtb2[bidder], addOrtb2) + } catch (e) { + logError(e) + } + return true; +} + +export function applyBidderOrtb2Sda(ortb2Fragments, bidder, type, segments, segtaxValue) { + try { + let ortb2Data = [{ + name: ORTB2_NAME, + segment: segments.map((segmentId) => ({ id: segmentId })), + }]; + if (segtaxValue) { + ortb2Data[0].ext = { segtax: segtaxValue }; } + let ortb2Conf = (type == 'site' ? {site: {content: {data: ortb2Data}}} : {user: {data: ortb2Data}}); + mergeDeep(ortb2Fragments, {[bidder]: ortb2Conf}); + } catch (e) { + logError(e) + } + return true; +} + +export function setBidderOrtb2(bidderOrtb2Fragments, bidder, path, segments) { + try { + if (isEmpty(segments)) { return; } + let ortb2Conf = {}; + deepSetValue(ortb2Conf, path, segments || {}); + mergeDeep(bidderOrtb2Fragments, {[bidder]: ortb2Conf}); } catch (e) { logError(e) } @@ -137,16 +220,16 @@ export function loadCustomFunction(todo, adUnit, list, data, bid) { return true; } -export function getSegAndCatsArray(data, minScore) { - var sirdataData = {'segments': [], 'categories': []}; +export function getSegAndCatsArray(data, minScore, pid) { + let sirdataData = {'segments': [], 'categories': []}; minScore = minScore && typeof minScore == 'number' ? minScore : 30; try { if (data && data.contextual_categories) { for (let catId in data.contextual_categories) { - if (data.contextual_categories.hasOwnProperty(catId)) { + if (data.contextual_categories.hasOwnProperty(catId) && data.contextual_categories[catId]) { let value = data.contextual_categories[catId]; if (value >= minScore && sirdataData.categories.indexOf(catId) === -1) { - sirdataData.categories.push(catId.toString()); + sirdataData.categories.push((pid ? pid.toString() + 'cc' : '') + catId.toString()); } } } @@ -157,8 +240,11 @@ export function getSegAndCatsArray(data, minScore) { try { if (data && data.segments) { for (let segId in data.segments) { - if (data.segments.hasOwnProperty(segId)) { - sirdataData.segments.push(data.segments[segId].toString()); + if (data.segments.hasOwnProperty(segId) && data.segments[segId]) { + sirdataData.segments.push((pid ? pid.toString() + 'us' : '') + data.segments[segId].toString()); + if (pid && CONTEXT_ONLY) { + sirdataData.categories.push(pid.toString() + 'uc' + data.segments[segId].toString()); + } } } } @@ -168,34 +254,77 @@ export function getSegAndCatsArray(data, minScore) { return sirdataData; } +export function applySdaGetSpecificData(data, sirdataList, biddersParamsExist, minScore, reqBids, bid, moduleConfig, indexFound, bidderIndex, adUnit) { + // only share SDA data if whitelisted + if (!biddersParamsExist || indexFound) { + // SDA Publisher + let sirdataDataForSDA = getSegAndCatsArray(data, minScore, moduleConfig.params.partnerId); + setBidderOrtb2Sda(reqBids.ortb2Fragments?.bidder, bid.bidder, sirdataDataForSDA, data.segtaxid, data.cattaxid); + } + + // always share SDA for curation + let curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : (partnerIds[bid.bidder] ? partnerIds[bid.bidder] : null)); + if (curationId) { + // seller defined audience & bidder specific data + if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { + // Get Bidder Specific Data + let curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore, null); + sirdataList = sirdataList.concat(curationData.segments).concat(curationData.categories); + + // SDA Partners + let curationDataForSDA = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore, curationId); + setBidderOrtb2Sda(reqBids.ortb2Fragments?.bidder, bid.bidder, curationDataForSDA, data.shared_taxonomy[curationId].segtaxid, data.shared_taxonomy[curationId].cattaxid); + } + } + + // Apply custom function or return Bidder Specific Data if publisher is ok + if (sirdataList && sirdataList.length > 0 && (!biddersParamsExist || indexFound)) { + if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { + return loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataList, data, bid); + } else { + return sirdataList; + } + } +} + +export function applySdaAndDefaultSpecificData(data, sirdataList, biddersParamsExist, minScore, reqBids, bid, moduleConfig, indexFound, bidderIndex, adUnit) { + let specificData = applySdaGetSpecificData(data, sirdataList, biddersParamsExist, minScore, reqBids, bid, moduleConfig, indexFound, bidderIndex, adUnit); + if (specificData && specificData.length > 0) { + setBidderOrtb2(reqBids.ortb2Fragments?.bidder, bid.bidder, 'user.ext.data', {sd_rtd: specificData}); + } +} + export function addSegmentData(reqBids, data, moduleConfig, onDone) { const adUnits = reqBids.adUnits; moduleConfig = moduleConfig || {}; moduleConfig.params = moduleConfig.params || {}; const globalMinScore = moduleConfig.params.hasOwnProperty('contextualMinRelevancyScore') ? moduleConfig.params.contextualMinRelevancyScore : 30; - var sirdataData = getSegAndCatsArray(data, globalMinScore); + var sirdataData = getSegAndCatsArray(data, globalMinScore, null); const sirdataList = sirdataData.segments.concat(sirdataData.categories); - var sirdataMergedList = []; - var curationData = {'segments': [], 'categories': []}; - var curationId = '1'; const biddersParamsExist = (!!(moduleConfig.params && moduleConfig.params.bidders)); - // Global ortb2 - if (!biddersParamsExist) { - setGlobalOrtb2(reqBids.ortb2Fragments?.global, sirdataData.segments, sirdataData.categories); + // Global ortb2 SDA + if (data.global_taxonomy && !isEmpty(data.global_taxonomy)) { + let globalData = {'segments': [], 'categories': []}; + for (let i in data.global_taxonomy) { + if (!isEmpty(data.global_taxonomy[i])) { + globalData = getSegAndCatsArray(data.global_taxonomy[i], globalMinScore, null); + setGlobalOrtb2Sda(reqBids.ortb2Fragments?.global, globalData, data.global_taxonomy[i].segtaxid, data.global_taxonomy[i].cattaxid); + } + } } // Google targeting if (typeof window.googletag !== 'undefined' && (moduleConfig.params.setGptKeyValues || !moduleConfig.params.hasOwnProperty('setGptKeyValues'))) { try { - // For curation Google is pid 27449 - curationId = (moduleConfig.params.gptCurationId ? moduleConfig.params.gptCurationId : '27449'); - if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { - curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], globalMinScore); + let gptCurationId = (moduleConfig.params.gptCurationId ? moduleConfig.params.gptCurationId : (partnerIds['sdRtdForGpt'] ? partnerIds['sdRtdForGpt'] : null)); + let sirdataMergedList = sirdataList; + if (gptCurationId && data.shared_taxonomy && data.shared_taxonomy[gptCurationId]) { + let gamCurationData = getSegAndCatsArray(data.shared_taxonomy[gptCurationId], globalMinScore, null); + sirdataMergedList = sirdataMergedList.concat(gamCurationData.segments).concat(gamCurationData.categories); } - sirdataMergedList = sirdataList.concat(curationData.segments).concat(curationData.categories); window.googletag.pubads().getSlots().forEach(function (n) { if (typeof n.setTargeting !== 'undefined' && sirdataMergedList && sirdataMergedList.length > 0) { n.setTargeting('sd_rtd', sirdataMergedList); @@ -221,259 +350,108 @@ export function addSegmentData(reqBids, data, moduleConfig, onDone) { }) : false); indexFound = (!!(typeof bidderIndex == 'number' && bidderIndex >= 0)); try { - curationData = {'segments': [], 'categories': []}; - sirdataMergedList = []; - let minScore = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('contextualMinRelevancyScore') ? moduleConfig.params.bidders[bidderIndex].contextualMinRelevancyScore : globalMinScore); + let specificData = null; + + switch (bid.bidder) { + case 'appnexus': + case 'appnexusAst': + case 'brealtime': + case 'emxdigital': + case 'pagescience': + case 'gourmetads': + case 'matomy': + case 'featureforward': + case 'oftmedia': + case 'districtm': + case 'adasta': + case 'beintoo': + case 'gravity': + case 'msq_classic': + case 'msq_max': + case '366_apx': + specificData = applySdaGetSpecificData(data, sirdataList, biddersParamsExist, minScore, reqBids, bid, moduleConfig, indexFound, bidderIndex, adUnit); + if (specificData && specificData.length > 0) { + deepSetValue(bid, 'params.keywords.sd_rtd', specificData); + } + break; - if (!biddersParamsExist || (indexFound && (!moduleConfig.params.bidders[bidderIndex].hasOwnProperty('adUnitCodes') || moduleConfig.params.bidders[bidderIndex].adUnitCodes.indexOf(adUnit.code) !== -1))) { - switch (bid.bidder) { - case 'appnexus': - case 'appnexusAst': - case 'brealtime': - case 'emxdigital': - case 'pagescience': - case 'gourmetads': - case 'matomy': - case 'featureforward': - case 'oftmedia': - case 'districtm': - case 'adasta': - case 'beintoo': - case 'gravity': - case 'msq_classic': - case 'msq_max': - case '366_apx': - // For curation Xandr is pid 27446 - curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : '27446'); - if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { - curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore); - } - sirdataMergedList = sirdataList.concat(curationData.segments).concat(curationData.categories); - if (sirdataMergedList && sirdataMergedList.length > 0) { - if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { - loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataMergedList, data, bid); - } else { - deepSetValue(bid, 'params.keywords.sd_rtd', sirdataMergedList); - } - } - break; - - case 'smartadserver': - case 'smart': + case 'smartadserver': + case 'smart': + specificData = applySdaGetSpecificData(data, sirdataList, biddersParamsExist, minScore, reqBids, bid, moduleConfig, indexFound, bidderIndex, adUnit); + if (specificData && specificData.length > 0) { var target = []; if (bid.hasOwnProperty('params') && bid.params.hasOwnProperty('target')) { target.push(bid.params.target); } - // For curation Smart is pid 27440 - curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : '27440'); - if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { - curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore); - } - sirdataMergedList = sirdataList.concat(curationData.segments).concat(curationData.categories); - if (sirdataMergedList && sirdataMergedList.length > 0) { - if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { - loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataMergedList, data, bid); - } else { - sirdataMergedList.forEach(function (entry) { - if (target.indexOf('sd_rtd=' + entry) === -1) { - target.push('sd_rtd=' + entry); - } - }); - deepSetValue(bid, 'params.target', target.join(';')); - } - } - break; - - case 'rubicon': - // For curation Magnite is pid 27518 - curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : '27452'); - if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { - curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore); - } - sirdataMergedList = sirdataList.concat(curationData.segments).concat(curationData.categories); - if (sirdataMergedList && sirdataMergedList.length > 0) { - if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { - loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataMergedList, data, bid); - } else { - setBidderOrtb2(reqBids.ortb2Fragments?.bidder, bid.bidder, data.segments.concat(curationData.segments), sirdataMergedList); - } - } - break; - - case 'ix': - var ixConfig = config.getConfig('ix.firstPartyData.sd_rtd'); - if (!ixConfig) { - // For curation index is pid 27248 - curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : '27248'); - if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { - curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore); - } - sirdataMergedList = sirdataList.concat(curationData.segments).concat(curationData.categories); - if (sirdataMergedList && sirdataMergedList.length > 0) { - if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { - loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataMergedList, data, bid); - } else { - var cappIxCategories = []; - var ixLength = 0; - var ixLimit = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('sizeLimit') ? moduleConfig.params.bidders[bidderIndex].sizeLimit : 1000); - // Push ids For publisher use and for curation if exists but limit size because the bidder uses GET parameters - sirdataMergedList.forEach(function (entry) { - if (ixLength < ixLimit) { - cappIxCategories.push(entry); - ixLength += entry.toString().length; - } - }); - config.setConfig({ix: {firstPartyData: {sd_rtd: cappIxCategories}}}); - } - } - } - break; - - case 'proxistore': - // For curation Proxistore is pid 27484 - curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : '27484'); - if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { - curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore); - } else { - data.shared_taxonomy[curationId] = {contextual_categories: {}}; - } - sirdataMergedList = sirdataList.concat(curationData.segments).concat(curationData.categories); - if (sirdataMergedList && sirdataMergedList.length > 0) { - if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { - loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataMergedList, data, bid); - } else { - deepSetValue(bid, 'ortb2.user.ext.data', { - segments: sirdataData.segments.concat(curationData.segments), - contextual_categories: {...data.contextual_categories, ...data.shared_taxonomy[curationId].contextual_categories} - }); - } - } - break; - - case 'criteo': - // For curation Smart is pid 27443 - curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : '27443'); - if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { - curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore); - } - sirdataMergedList = sirdataList.concat(curationData.segments).concat(curationData.categories); - if (sirdataMergedList && sirdataMergedList.length > 0) { - if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { - loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataMergedList, data, bid); - } else { - setBidderOrtb2(reqBids.ortb2Fragments?.bidder, bid.bidder, data.segments.concat(curationData.segments), sirdataMergedList); - } - } - break; - - case 'triplelift': - // For curation Triplelift is pid 27518 - curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : '27518'); - if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { - curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore); - } - sirdataMergedList = sirdataList.concat(curationData.segments).concat(curationData.categories); - if (sirdataMergedList && sirdataMergedList.length > 0) { - if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { - loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataMergedList, data, bid); - } else { - setBidderOrtb2(reqBids.ortb2Fragments?.bidder, bid.bidder, data.segments.concat(curationData.segments), sirdataMergedList); - } - } - break; - - case 'avct': - case 'avocet': - // For curation Avocet is pid 27522 - curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : '27522'); - if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { - curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore); - } - sirdataMergedList = sirdataList.concat(curationData.segments).concat(curationData.categories); - if (sirdataMergedList && sirdataMergedList.length > 0) { - if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { - loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataMergedList, data, bid); - } else { - setBidderOrtb2(reqBids.ortb2Fragments?.bidder, bid.bidder, data.segments.concat(curationData.segments), sirdataMergedList); - } - } - break; - - case 'smaato': - // For curation Smaato is pid 27520 - curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : '27520'); - if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { - curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore); - } - sirdataMergedList = sirdataList.concat(curationData.segments).concat(curationData.categories); - if (sirdataMergedList && sirdataMergedList.length > 0) { - if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { - loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataMergedList, data, bid); - } else { - setBidderOrtb2(reqBids.ortb2Fragments?.bidder, bid.bidder, data.segments.concat(curationData.segments), sirdataMergedList); - } - } - break; - - case 'yahoossp': - // For curation Yahoo is pid 30339 - curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : '30339'); - if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { - curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore); - } - sirdataMergedList = sirdataList.concat(curationData.segments).concat(curationData.categories); - if (sirdataMergedList && sirdataMergedList.length > 0) { - if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { - loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataMergedList, data, bid); - } else { - setBidderOrtb2(reqBids.ortb2Fragments?.bidder, bid.bidder, data.segments.concat(curationData.segments), sirdataMergedList); - } - } - break; - - case 'openx': - // For curation OpenX is pid 30342 - curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : '30342'); - if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { - curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore); - } - sirdataMergedList = sirdataList.concat(curationData.segments).concat(curationData.categories); - if (sirdataMergedList && sirdataMergedList.length > 0) { - if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { - loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataMergedList, data, bid); - } else { - setBidderOrtb2(reqBids.ortb2Fragments?.bidder, bid.bidder, data.segments.concat(curationData.segments), sirdataMergedList); + specificData.forEach(function (entry) { + if (target.indexOf('sd_rtd=' + entry) === -1) { + target.push('sd_rtd=' + entry); } - } - break; - - case 'pubmatic': - // For curation Pubmatic is pid 30345 - curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : '30345'); - if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { - curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore); - } - sirdataMergedList = sirdataList.concat(curationData.segments).concat(curationData.categories); - if (sirdataMergedList && sirdataMergedList.length > 0) { - if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { - loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataMergedList, data, bid); - } else { - setBidderOrtb2(reqBids.ortb2Fragments?.bidder, bid.bidder, data.segments.concat(curationData.segments), sirdataMergedList); - } - } - break; - - default: - if (!biddersParamsExist || indexFound) { - if (!deepAccess(bid, 'ortb2.site.ext.data.sd_rtd')) { - deepSetValue(bid, 'ortb2.site.ext.data.sd_rtd', sirdataData.categories); - } - if (!deepAccess(bid, 'ortb2.user.ext.data.sd_rtd')) { - deepSetValue(bid, 'ortb2.user.ext.data.sd_rtd', sirdataData.segments); + }); + deepSetValue(bid, 'params.target', target.join(';')); + } + break; + + case 'ix': + specificData = applySdaGetSpecificData(data, sirdataList, biddersParamsExist, minScore, reqBids, bid, moduleConfig, indexFound, bidderIndex, adUnit); + let ixConfig = config.getConfig('ix.firstPartyData.sd_rtd'); + if (!ixConfig && specificData && specificData.length > 0) { + let cappIxCategories = []; + let ixLength = 0; + let ixLimit = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('sizeLimit') ? moduleConfig.params.bidders[bidderIndex].sizeLimit : 1000); + // Push ids For publisher use and for curation if exists but limit size because the bidder uses GET parameters + specificData.forEach(function (entry) { + if (ixLength < ixLimit) { + cappIxCategories.push(entry); + ixLength += entry.toString().length; } + }); + config.setConfig({ix: {firstPartyData: {sd_rtd: cappIxCategories}}}); + } + break; + + case 'proxistore': + specificData = applySdaGetSpecificData(data, sirdataList, biddersParamsExist, minScore, reqBids, bid, moduleConfig, indexFound, bidderIndex, adUnit); + if (specificData && specificData.length > 0) { + let psCurationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : (partnerIds[bid.bidder] ? partnerIds[bid.bidder] : null)); + if (!data.shared_taxonomy || !data.shared_taxonomy[psCurationId]) { + data.shared_taxonomy[psCurationId] = {segments: [], contextual_categories: {}, segtaxid: null, cattaxid: null}; } - } + let psCurationData = getSegAndCatsArray(data.shared_taxonomy[psCurationId], minScore, null); + setBidderOrtb2(reqBids.ortb2Fragments?.bidder, bid.bidder, 'user.ext.data', { + segments: sirdataData.segments.concat(psCurationData.segments), + contextual_categories: {...data.contextual_categories, ...data.shared_taxonomy[psCurationId].contextual_categories} + }); + } + break; + + case 'rubicon': + case 'criteo': + case 'triplelift': + case 'smaato': + case 'yahoossp': + case 'openx': + case 'pubmatic': + case 'smilewanted': + case 'taboola': + case 'ttd': + case 'zeta_global': + case 'zeta_global_ssp': + case 'teads': + case 'conversant': + case 'improvedigital': + case 'invibes': + case 'sublime': + case 'rtbhouse': + case 'mediasquare': + applySdaAndDefaultSpecificData(data, sirdataList, biddersParamsExist, minScore, reqBids, bid, moduleConfig, indexFound, bidderIndex, adUnit); + break; + + default: + if (!biddersParamsExist || (indexFound && (!moduleConfig.params.bidders[bidderIndex].hasOwnProperty('adUnitCodes') || moduleConfig.params.bidders[bidderIndex].adUnitCodes.indexOf(adUnit.code) !== -1))) { + applySdaAndDefaultSpecificData(data, sirdataList, biddersParamsExist, minScore, reqBids, bid, moduleConfig, indexFound, bidderIndex, adUnit); + } } } catch (e) { logError(e); diff --git a/modules/sizeMappingV2.js b/modules/sizeMappingV2.js index 05475b3e143..d212d98f50b 100644 --- a/modules/sizeMappingV2.js +++ b/modules/sizeMappingV2.js @@ -182,7 +182,7 @@ export function checkAdUnitSetupHook(adUnits) { } } - if (mediaTypes.video) { + if (FEATURES.VIDEO && mediaTypes.video) { if (mediaTypes.video.playerSize) { // Ad unit is using 'mediaTypes.video.playerSize' instead of the new property 'sizeConfig'. Apply the old checks! validatedVideo = validatedBanner ? adUnitSetupChecks.validateVideoMediaType(validatedBanner) : adUnitSetupChecks.validateVideoMediaType(adUnit); diff --git a/modules/smaatoBidAdapter.js b/modules/smaatoBidAdapter.js index 18ce56c7a80..c1eb1bb8489 100644 --- a/modules/smaatoBidAdapter.js +++ b/modules/smaatoBidAdapter.js @@ -8,7 +8,7 @@ import CONSTANTS from '../src/constants.json'; const { NATIVE_IMAGE_TYPES } = CONSTANTS; const BIDDER_CODE = 'smaato'; const SMAATO_ENDPOINT = 'https://prebid.ad.smaato.net/oapi/prebid'; -const SMAATO_CLIENT = 'prebid_js_$prebid.version$_1.7' +const SMAATO_CLIENT = 'prebid_js_$prebid.version$_1.8' const CURRENCY = 'USD'; const buildOpenRtbBidRequest = (bidRequest, bidderRequest) => { @@ -63,11 +63,28 @@ const buildOpenRtbBidRequest = (bidRequest, bidderRequest) => { deepSetValue(requestTemplate, 'regs.ext.us_privacy', bidderRequest.uspConsent); } + if (ortb2.regs?.gpp !== undefined) { + deepSetValue(requestTemplate, 'regs.ext.gpp', ortb2.regs.gpp); + deepSetValue(requestTemplate, 'regs.ext.gpp_sid', ortb2.regs.gpp_sid); + } + + if (ortb2.device?.ifa !== undefined) { + deepSetValue(requestTemplate, 'device.ifa', ortb2.device.ifa); + } + + if (ortb2.device?.geo !== undefined) { + deepSetValue(requestTemplate, 'device.geo', ortb2.device.geo); + } + if (deepAccess(bidRequest, 'params.app')) { - const geo = deepAccess(bidRequest, 'params.app.geo'); - deepSetValue(requestTemplate, 'device.geo', geo); - const ifa = deepAccess(bidRequest, 'params.app.ifa'); - deepSetValue(requestTemplate, 'device.ifa', ifa); + if (!deepAccess(requestTemplate, 'device.geo')) { + const geo = deepAccess(bidRequest, 'params.app.geo'); + deepSetValue(requestTemplate, 'device.geo', geo); + } + if (!deepAccess(requestTemplate, 'device.ifa')) { + const ifa = deepAccess(bidRequest, 'params.app.ifa'); + deepSetValue(requestTemplate, 'device.ifa', ifa); + } } const eids = deepAccess(bidRequest, 'userIdAsEids'); diff --git a/modules/smartadserverBidAdapter.js b/modules/smartadserverBidAdapter.js index 6ff0e592542..fd2d6e16463 100644 --- a/modules/smartadserverBidAdapter.js +++ b/modules/smartadserverBidAdapter.js @@ -1,7 +1,6 @@ import { deepAccess, deepClone, logError, isFn, isPlainObject } from '../src/utils.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; -import { createEidsArray } from './userId/eids.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; const BIDDER_CODE = 'smartadserver'; @@ -13,6 +12,7 @@ export const spec = { gvlid: GVL_ID, aliases: ['smart'], // short code supportedMediaTypes: [BANNER, VIDEO], + /** * Determines whether or not the given bid request is valid. * @@ -131,7 +131,6 @@ export const spec = { */ buildRequests: function (validBidRequests, bidderRequest) { // use bidderRequest.bids[] to get bidder-dependent request info - const adServerCurrency = config.getConfig('currency.adServerCurrency'); const sellerDefinedAudience = deepAccess(bidderRequest, 'ortb2.user.data', config.getAnyConfig('ortb2.user.data')); const sellerDefinedContext = deepAccess(bidderRequest, 'ortb2.site.content.data', config.getAnyConfig('ortb2.site.content.data')); @@ -144,7 +143,6 @@ export const spec = { pageid: bid.params.pageId, formatid: bid.params.formatId, currencyCode: adServerCurrency, - bidfloor: bid.params.bidfloor || spec.getBidFloor(bid, adServerCurrency), targeting: bid.params.target && bid.params.target !== '' ? bid.params.target : undefined, buid: bid.params.buId && bid.params.buId !== '' ? bid.params.buId : undefined, appname: bid.params.appName && bid.params.appName !== '' ? bid.params.appName : undefined, @@ -161,38 +159,53 @@ export const spec = { sdc: sellerDefinedContext }; - if (bidderRequest && bidderRequest.gdprConsent) { - payload.addtl_consent = bidderRequest.gdprConsent.addtlConsent; - payload.gdpr_consent = bidderRequest.gdprConsent.consentString; - payload.gdpr = bidderRequest.gdprConsent.gdprApplies; // we're handling the undefined case server side + if (bidderRequest) { + if (bidderRequest.gdprConsent) { + payload.addtl_consent = bidderRequest.gdprConsent.addtlConsent; + payload.gdpr_consent = bidderRequest.gdprConsent.consentString; + payload.gdpr = bidderRequest.gdprConsent.gdprApplies; // we're handling the undefined case server side + } + + if (bidderRequest.gppConsent) { + payload.gpp = bidderRequest.gppConsent.gppString; + payload.gpp_sid = bidderRequest.gppConsent.applicableSections; + } + + if (bidderRequest.uspConsent) { + payload.us_privacy = bidderRequest.uspConsent; + } } - if (bid && bid.userId) { - payload.eids = createEidsArray(bid.userId); + if (bid && bid.userIdAsEids) { + payload.eids = bid.userIdAsEids; } if (bidderRequest && bidderRequest.uspConsent) { payload.us_privacy = bidderRequest.uspConsent; } - const videoMediaType = deepAccess(bid, 'mediaTypes.video'); const bannerMediaType = deepAccess(bid, 'mediaTypes.banner'); - const isAdUnitContainingVideo = videoMediaType && (videoMediaType.context === 'instream' || videoMediaType.context === 'outstream'); - if (!isAdUnitContainingVideo && bannerMediaType) { - payload.sizes = spec.adaptBannerSizes(bannerMediaType.sizes); - bidRequests.push(spec.createServerRequest(payload, bid.params.domain)); - } else if (isAdUnitContainingVideo && !bannerMediaType) { - spec.fillPayloadForVideoBidRequest(payload, videoMediaType, bid.params.video); - bidRequests.push(spec.createServerRequest(payload, bid.params.domain)); - } else if (isAdUnitContainingVideo && bannerMediaType) { - // If there are video and banner media types in the ad unit, we clone the payload - // to create a specific one for video. - let videoPayload = deepClone(payload); + const videoMediaType = deepAccess(bid, 'mediaTypes.video'); + const isSupportedVideoContext = videoMediaType && (videoMediaType.context === 'instream' || videoMediaType.context === 'outstream'); + + if (bannerMediaType || isSupportedVideoContext) { + let type; + if (bannerMediaType) { + type = BANNER; + payload.sizes = spec.adaptBannerSizes(bannerMediaType.sizes); - spec.fillPayloadForVideoBidRequest(videoPayload, videoMediaType, bid.params.video); - bidRequests.push(spec.createServerRequest(videoPayload, bid.params.domain)); + if (isSupportedVideoContext) { + let videoPayload = deepClone(payload); + spec.fillPayloadForVideoBidRequest(videoPayload, videoMediaType, bid.params.video); + videoPayload.bidfloor = bid.params.bidfloor || spec.getBidFloor(bid, adServerCurrency, VIDEO); + bidRequests.push(spec.createServerRequest(videoPayload, bid.params.domain)); + } + } else { + type = VIDEO; + spec.fillPayloadForVideoBidRequest(payload, videoMediaType, bid.params.video); + } - payload.sizes = spec.adaptBannerSizes(bannerMediaType.sizes); + payload.bidfloor = bid.params.bidfloor || spec.getBidFloor(bid, adServerCurrency, type); bidRequests.push(spec.createServerRequest(payload, bid.params.domain)); } else { bidRequests.push({}); @@ -253,24 +266,21 @@ export const spec = { * * @param {object} bid Bid request object * @param {string} currency Ad server currency + * @param {string} mediaType Bid media type * @return {number} Floor price */ - getBidFloor: function (bid, currency) { + getBidFloor: function (bid, currency, mediaType) { if (!isFn(bid.getFloor)) { return DEFAULT_FLOOR; } const floor = bid.getFloor({ currency: currency || 'USD', - mediaType: '*', + mediaType, size: '*' }); - if (isPlainObject(floor) && !isNaN(floor.floor)) { - return floor.floor; - } - - return DEFAULT_FLOOR; + return isPlainObject(floor) && !isNaN(floor.floor) ? floor.floor : DEFAULT_FLOOR; }, /** diff --git a/modules/smarthubBidAdapter.js b/modules/smarthubBidAdapter.js index 6aab6a8b57e..2889bd5358b 100644 --- a/modules/smarthubBidAdapter.js +++ b/modules/smarthubBidAdapter.js @@ -125,7 +125,7 @@ function buildRequestParams(bidderRequest = {}, placements = []) { coppa: config.getConfig('coppa') === true ? 1 : 0, ccpa: bidderRequest.uspConsent || undefined, gdpr: bidderRequest.gdprConsent || undefined, - tmax: config.getConfig('bidderTimeout') + tmax: bidderRequest.timeout }; } diff --git a/modules/smartxBidAdapter.js b/modules/smartxBidAdapter.js index bfc664180a3..d91b62729bc 100644 --- a/modules/smartxBidAdapter.js +++ b/modules/smartxBidAdapter.js @@ -10,8 +10,10 @@ import { } from '../src/mediaTypes.js'; const BIDDER_CODE = 'smartx'; const URL = 'https://bid.sxp.smartclip.net/bid/1000'; +const GVLID = 115; export const spec = { code: BIDDER_CODE, + gvlid: GVLID, supportedMediaTypes: [VIDEO], /** * Determines whether or not the given bid request is valid. diff --git a/modules/smartyadsBidAdapter.js b/modules/smartyadsBidAdapter.js index e5800e7cad0..89749aed433 100644 --- a/modules/smartyadsBidAdapter.js +++ b/modules/smartyadsBidAdapter.js @@ -17,7 +17,7 @@ function isBidResponseValid(bid) { case BANNER: return Boolean(bid.width && bid.height && bid.ad); case VIDEO: - return Boolean(bid.vastUrl); + return Boolean(bid.vastUrl) || Boolean(bid.vastXml); case NATIVE: return Boolean(bid.native && bid.native.title && bid.native.image && bid.native.impressionTrackers); default: diff --git a/modules/smartyadsBidAdapter.md b/modules/smartyadsBidAdapter.md index f078d905e62..e0d6023a794 100644 --- a/modules/smartyadsBidAdapter.md +++ b/modules/smartyadsBidAdapter.md @@ -10,6 +10,15 @@ Maintainer: supply@smartyads.com Module that connects to SmartyAds' demand sources +# Parameters + +| Name | Scope | Description | Example | +| :------------ | :------- | :------------------------ | :------------------- | +| `sourceid` | required (for prebid.js) | placement ID | "0" | +| `host` | required (for prebid-server) | const value, set to "prebid" | "prebid" | +| `accountid` | required (for prebid-server) | partner ID | "1901" | +| `traffic` | optional (for prebid.js) | Configures the mediaType that should be used. Values can be banner, native or video | "banner" | + # Test Parameters ``` var adUnits = [ diff --git a/modules/smartytechBidAdapter.js b/modules/smartytechBidAdapter.js index 231ca315de8..34cf9285909 100644 --- a/modules/smartytechBidAdapter.js +++ b/modules/smartytechBidAdapter.js @@ -1,29 +1,86 @@ +import {buildUrl, deepAccess} from '../src/utils.js' +import { BANNER, VIDEO } from '../src/mediaTypes.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {buildUrl} from '../src/utils.js' const BIDDER_CODE = 'smartytech'; export const ENDPOINT_PROTOCOL = 'https'; export const ENDPOINT_DOMAIN = 'server.smartytech.io'; -export const ENDPOINT_PATH = '/hb/bidder'; +export const ENDPOINT_PATH = '/hb/v2/bidder'; export const spec = { + supportedMediaTypes: [ BANNER, VIDEO ], code: BIDDER_CODE, isBidRequestValid: function (bidRequest) { - return !!parseInt(bidRequest.params.endpointId); + return ( + !!parseInt(bidRequest.params.endpointId) && + spec._validateBanner(bidRequest) && + spec._validateVideo(bidRequest) + ); + }, + + _validateBanner: function(bidRequest) { + const bannerAdUnit = deepAccess(bidRequest, 'mediaTypes.banner'); + + if (bannerAdUnit === undefined) { + return true; + } + + if (!Array.isArray(bannerAdUnit.sizes)) { + return false; + } + + return true; + }, + + _validateVideo: function(bidRequest) { + const videoAdUnit = deepAccess(bidRequest, 'mediaTypes.video'); + + if (videoAdUnit === undefined) { + return true; + } + + if (!Array.isArray(videoAdUnit.playerSize)) { + return false; + } + + if (!videoAdUnit.context) { + return false; + } + + return true; }, buildRequests: function (validBidRequests, bidderRequest) { const referer = bidderRequest?.refererInfo?.page || window.location.href; const bidRequests = validBidRequests.map((validBidRequest) => { - return { + let video = deepAccess(validBidRequest, 'mediaTypes.video', false); + let banner = deepAccess(validBidRequest, 'mediaTypes.banner', false); + let sizes = validBidRequest.params.sizes; + + let oneRequest = { endpointId: validBidRequest.params.endpointId, adUnitCode: validBidRequest.adUnitCode, - sizes: validBidRequest.sizes, - bidId: validBidRequest.bidId, - referer: referer + referer: referer, + bidId: validBidRequest.bidId }; + + if (video) { + oneRequest.video = video; + + if (sizes) { + oneRequest.video.sizes = sizes; + } + } else if (banner) { + oneRequest.banner = banner; + + if (sizes) { + oneRequest.banner.sizes = sizes; + } + } + + return oneRequest }); let adPartnerRequestUrl = buildUrl({ @@ -55,12 +112,14 @@ export const spec = { bid: validBids.find(b => b.adUnitCode === key), response: responseBody[key] } - }).map(item => spec.adResponse(item.bid.bidId, item.response)); + }).map(item => spec._adResponse(item.bid, item.response)); }, - adResponse: function (requestId, response) { + _adResponse: function (request, response) { const bidObject = { - requestId, + requestId: request.bidId, + adUnitCode: request.adUnitCode, + bidderCode: BIDDER_CODE, ad: response.ad, cpm: response.cpm, width: response.width, @@ -69,7 +128,14 @@ export const spec = { creativeId: response.creativeId, netRevenue: true, currency: response.currency, + mediaType: BANNER } + + if (response.mediaType === VIDEO) { + bidObject.vastXml = response.ad; + bidObject.mediaType = VIDEO; + } + return bidObject; }, diff --git a/modules/smartytechBidAdapter.md b/modules/smartytechBidAdapter.md index dbfc2833c78..9df57ddbde7 100644 --- a/modules/smartytechBidAdapter.md +++ b/modules/smartytechBidAdapter.md @@ -1,44 +1,55 @@ # Overview -Module Name: SmartyTech Bidder Adapter - -Module Type: Bidder Adapter - +``` +Module Name: SmartyTech Bid Adapter +Module Type: Bidder Adapter Maintainer: info@adpartner.pro +``` # Description -You can use this adapter to get a bid from smartytech.io. +Connects to SmartyTech's exchange for bids. -About us : https://smartytech.io +SmartyTech bid adapter supports Banner and Video -# Test Parameters +# Sample Ad Unit: For Publishers +## Sample Banner Ad Unit +``` +var adUnits = [{ + code: '/123123123/prebidjs-banner', + mediaTypes: { + banner: { + sizes: [ + [300, 301], + [300, 250] + ] + } + }, + bids: [{ + bidder: 'smartytech', + params: { + endpointId: 12 + } + }] +}]; +``` -```javascript - var adUnits = [ - { - code: 'div-smartytech-example', - sizes: [[300, 250]], - bids: [ - { - bidder: "smartytech", - params: { - endpointId: 14 - } - } - ] +## Sample Video Ad Unit +``` +var videoAdUnit = { + code: '/123123123/video-vast-banner', + mediaTypes: { + video: { + context: 'instream', + playerSize: [640, 480], + mimes: ['video/mp4'], + } }, - { - code: 'div-smartytech-example-2', - sizes: [[300, 250]], - bids: [ - { - bidder: "smartytech", - params: { - endpointId: 14 - } - } - ] - } -]; + bids: [{ + bidder: 'smartytech', + params: { + endpointId: 12 + } + }] +}; ``` diff --git a/modules/smilewantedBidAdapter.js b/modules/smilewantedBidAdapter.js index f82e7c9258f..fe14c57d641 100644 --- a/modules/smilewantedBidAdapter.js +++ b/modules/smilewantedBidAdapter.js @@ -63,6 +63,11 @@ export const spec = { payload.gdpr_consent = bidderRequest.gdprConsent.consentString; payload.gdpr = bidderRequest.gdprConsent.gdprApplies; // we're handling the undefined case server side } + + if (bid && bid.userIdAsEids) { + payload.eids = bid.userIdAsEids; + } + var payloadString = JSON.stringify(payload); return { method: 'POST', diff --git a/modules/snigelBidAdapter.js b/modules/snigelBidAdapter.js index e0ec10c6ed6..f41fb98d436 100644 --- a/modules/snigelBidAdapter.js +++ b/modules/snigelBidAdapter.js @@ -3,6 +3,7 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER} from '../src/mediaTypes.js'; import {deepAccess, isArray, isFn, isPlainObject} from '../src/utils.js'; import {hasPurpose1Consent} from '../src/utils/gpdr.js'; +import {getGlobal} from '../src/prebidGlobal.js'; const BIDDER_CODE = 'snigel'; const GVLID = 1076; @@ -33,7 +34,7 @@ export const spec = { test: getTestFlag(), devw: window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth, devh: window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight, - version: $$PREBID_GLOBAL$$.version, + version: getGlobal().version, gdprApplies: gdprApplies, gdprConsentString: gdprApplies === true ? deepAccess(bidderRequest, 'gdprConsent.consentString') : undefined, gdprConsentProv: gdprApplies === true ? deepAccess(bidderRequest, 'gdprConsent.addtlConsent') : undefined, diff --git a/modules/sonobiBidAdapter.js b/modules/sonobiBidAdapter.js index 3c841cc4d8a..6760a3c18ab 100644 --- a/modules/sonobiBidAdapter.js +++ b/modules/sonobiBidAdapter.js @@ -1,9 +1,10 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { parseSizesInput, logError, generateUUID, isEmpty, deepAccess, logWarn, logMessage, deepClone, getGptSlotInfoForAdUnitCode, isFn, isPlainObject } from '../src/utils.js'; +import { parseSizesInput, logError, generateUUID, isEmpty, deepAccess, logWarn, logMessage, getGptSlotInfoForAdUnitCode, isFn, isPlainObject } from '../src/utils.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; import { Renderer } from '../src/Renderer.js'; import { userSync } from '../src/userSync.js'; +import { bidderSettings } from '../src/bidderSettings.js'; const BIDDER_CODE = 'sonobi'; const STR_ENDPOINT = 'https://apex.go.sonobi.com/trinity.json'; const PAGEVIEW_ID = generateUUID(); @@ -49,6 +50,7 @@ export const spec = { return true; }, + /** * Make a server request from the list of BidRequests. * @@ -93,7 +95,7 @@ export const spec = { 'lib_name': 'prebid', 'lib_v': '$prebid.version$', 'us': 0, - + 'iqid': bidderSettings.get(BIDDER_CODE, 'storageAllowed') ? JSON.stringify(loadOrCreateFirstPartyData()) : null, }; const fpd = bidderRequest.ortb2; @@ -132,15 +134,6 @@ export const spec = { if (validBidRequests[0].schain) { payload.schain = JSON.stringify(validBidRequests[0].schain); } - if (deepAccess(validBidRequests[0], 'userId') && Object.keys(validBidRequests[0].userId).length > 0) { - const userIds = deepClone(validBidRequests[0].userId); - - if (userIds.id5id) { - userIds.id5id = deepAccess(userIds, 'id5id.uid'); - } - - payload.userid = JSON.stringify(userIds); - } const eids = deepAccess(validBidRequests[0], 'userIdAsEids'); if (Array.isArray(eids) && eids.length > 0) { @@ -295,22 +288,29 @@ function _findBidderRequest(bidderRequests, bidId) { } } +// This function takes all the possible sizes. +// returns string csv. function _validateSize(bid) { - if (deepAccess(bid, 'mediaTypes.video')) { - return ''; // Video bids arent allowed to override sizes via the trinity request + let size = []; + if (deepAccess(bid, 'mediaTypes.video.playerSize')) { + size.push(deepAccess(bid, 'mediaTypes.video.playerSize')) } - - if (bid.params.sizes) { - return parseSizesInput(bid.params.sizes).join(','); + if (deepAccess(bid, 'mediaTypes.video.sizes')) { + size.push(deepAccess(bid, 'mediaTypes.video.sizes')) + } + if (deepAccess(bid, 'params.sizes')) { + size.push(deepAccess(bid, 'params.sizes')); } if (deepAccess(bid, 'mediaTypes.banner.sizes')) { - return parseSizesInput(deepAccess(bid, 'mediaTypes.banner.sizes')).join(','); + size.push(deepAccess(bid, 'mediaTypes.banner.sizes')) } - - // Handle deprecated sizes definition - if (bid.sizes) { - return parseSizesInput(bid.sizes).join(','); + if (deepAccess(bid, 'sizes')) { + size.push(deepAccess(bid, 'sizes')) } + // Pass the 2d sizes array into parseSizeInput to flatten it into an array of x separated sizes. + // Then throw it into Set to uniquify it. + // Then spread it to an array again. Then join it into a csv of sizes. + return [...new Set(parseSizesInput(...size))].join(','); } function _validateSlot(bid) { @@ -390,6 +390,67 @@ export function _getPlatform(context = window) { } return 'desktop'; } +/** + * Check for local storage + * Generate a UUID for the user if one does not exist in local storage + * Store the UUID in local storage for future use + * @return {object} firstPartyData - Data object containing first party information + */ +function loadOrCreateFirstPartyData() { + var localStorageEnabled; + + var FIRST_PARTY_KEY = '_iiq_fdata'; + var tryParse = function (data) { + try { + return JSON.parse(data); + } catch (err) { + return null; + } + }; + var readData = function (key) { + if (hasLocalStorage()) { + return window.localStorage.getItem(key); + } + return null; + }; + var hasLocalStorage = function () { + if (typeof localStorageEnabled != 'undefined') { return localStorageEnabled; } else { + try { + localStorageEnabled = !!window.localStorage; + return localStorageEnabled; + } catch (e) { + localStorageEnabled = false; + } + } + return false; + }; + var generateGUID = function () { + var d = new Date().getTime(); + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { + var r = (d + Math.random() * 16) % 16 | 0; + d = Math.floor(d / 16); + return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16); + }); + }; + var storeData = function (key, value) { + try { + if (hasLocalStorage()) { + window.localStorage.setItem(key, value); + } + } catch (error) { + return null; + } + }; + var firstPartyData = tryParse(readData(FIRST_PARTY_KEY)); + if (!firstPartyData || !firstPartyData.pcid) { + var firstPartyId = generateGUID(); + firstPartyData = { pcid: firstPartyId, pcidDate: Date.now() }; + } else if (firstPartyData && !firstPartyData.pcidDate) { + firstPartyData.pcidDate = Date.now(); + } + storeData(FIRST_PARTY_KEY, JSON.stringify(firstPartyData)); + return firstPartyData; +}; function newRenderer(adUnitCode, bid, rendererOptions = {}) { const renderer = Renderer.install({ diff --git a/modules/sovrnBidAdapter.js b/modules/sovrnBidAdapter.js index ab8abb7e2b4..b825a554e4d 100644 --- a/modules/sovrnBidAdapter.js +++ b/modules/sovrnBidAdapter.js @@ -15,7 +15,6 @@ import { BANNER, VIDEO } from '../src/mediaTypes.js' -import {createEidsArray} from './userId/eids.js'; const ORTB_VIDEO_PARAMS = { 'mimes': (value) => Array.isArray(value) && value.length > 0 && value.every(v => typeof v === 'string'), @@ -90,8 +89,8 @@ export const spec = { let criteoId; _each(bidReqs, function (bid) { - if (!eids && bid.userId) { - eids = createEidsArray(bid.userId) + if (!eids && bid.userIdAsEids) { + eids = bid.userIdAsEids; eids.forEach(function (id) { if (id.uids && id.uids[0]) { if (id.source === 'criteo.com') { @@ -174,6 +173,10 @@ export const spec = { if (bidderRequest.uspConsent) { deepSetValue(sovrnBidReq, 'regs.ext.us_privacy', bidderRequest.uspConsent); } + if (bidderRequest.gppConsent) { + deepSetValue(sovrnBidReq, 'regs.gpp', bidderRequest.gppConsent.gppString); + deepSetValue(sovrnBidReq, 'regs.gpp_sid', bidderRequest.gppConsent.applicableSections); + } if (eids) { deepSetValue(sovrnBidReq, 'user.ext.eids', eids) diff --git a/modules/sspBCBidAdapter.js b/modules/sspBCBidAdapter.js index ffe2bef054c..acfa9fe7945 100644 --- a/modules/sspBCBidAdapter.js +++ b/modules/sspBCBidAdapter.js @@ -10,10 +10,9 @@ const BIDDER_CODE = 'sspBC'; const BIDDER_URL = 'https://ssp.wp.pl/bidder/'; const SYNC_URL = 'https://ssp.wp.pl/bidder/usersync'; const NOTIFY_URL = 'https://ssp.wp.pl/bidder/notify'; -const TRACKER_URL = 'https://bdr.wpcdn.pl/tag/jstracker.js'; const GVLID = 676; const TMAX = 450; -const BIDDER_VERSION = '5.7'; +const BIDDER_VERSION = '5.8'; const DEFAULT_CURRENCY = 'PLN'; const W = window; const { navigator } = W; @@ -23,6 +22,39 @@ const adSizesCalled = {}; const pageView = {}; var consentApiVersion; +/** + * Native asset mapping - we use constant id per type + * id > 10 indicates additional images + */ +var nativeAssetMap = { + title: 0, + cta: 1, + icon: 2, + image: 3, + body: 4, + sponsoredBy: 5 +}; + +/** + * return native asset type, based on asset id + * @param {int} id - native asset id + * @returns {string} asset type + */ +const getNativeAssetType = id => { + // id>10 will always be an image... + if (id > 10) { + return 'image'; + } + + // ...others should be decoded from nativeAssetMap + for (let assetName in nativeAssetMap) { + const assetId = nativeAssetMap[assetName]; + if (assetId === id) { + return assetName; + } + } +} + /** * Get preferred language of browser (i.e. user) * @returns {string} languageCode - ISO language code @@ -42,6 +74,16 @@ const getContentLanguage = () => { } }; +/** + * Get Bid parameters - returns bid params from Object, or 1el array + * @param {*} bidData - bid (bidWon), or array of bids (timeout) + * @returns {object} params object + */ +const unpackParams = (bidParams) => { + const result = isArray(bidParams) ? bidParams[0] : bidParams; + return result || {}; +} + /** * Get bid parameters for notification * @param {*} bidData - bid (bidWon), or array of bids (timeout) @@ -58,8 +100,7 @@ const getNotificationPayload = bidData => { } bids.forEach(bid => { const { adUnitCode, auctionId, cpm, creativeId, meta, params: bidParams, requestId, timeout } = bid; - let params = isArray(bidParams) ? bidParams[0] : bidParams; - params = params || {}; + const params = unpackParams(bidParams); // basic notification data const bidBasicData = { @@ -178,6 +219,46 @@ const applyGdpr = (bidderRequest, ortbRequest) => { } } +/** + * Get highest floorprice for a given adslot + * (sspBC adapter accepts one floor per imp) + * returns floor = 0 if getFloor() is not defined + * + * @param {object} slot bid request adslot + * @returns {float} floorprice + */ +const getHighestFloor = (slot) => { + const currency = getCurrency(); + let result = { floor: 0, currency }; + + if (typeof slot.getFloor === 'function') { + let bannerFloor = 0; + + if (slot.sizes.length) { + bannerFloor = slot.sizes.reduce(function (prev, next) { + const { floor: currentFloor = 0 } = slot.getFloor({ + mediaType: 'banner', + size: next, + currency + }); + return prev > currentFloor ? prev : currentFloor; + }, 0); + } + + const { floor: nativeFloor = 0 } = slot.getFloor({ + mediaType: 'native', currency + }); + + const { floor: videoFloor = 0 } = slot.getFloor({ + mediaType: 'video', currency + }); + + result.floor = Math.max(bannerFloor, nativeFloor, videoFloor); + } + + return result; +}; + /** * Get currency (either default or adserver) * @returns {string} currency name @@ -226,71 +307,111 @@ const mapBanner = slot => { * @param {object} paramValue Native parameter value * @returns {object} native asset object that conforms to ortb native ads spec */ -const mapAsset = (paramName, paramValue) => { - let asset; - switch (paramName) { - case 'title': - asset = { - id: 0, - required: paramValue.required, - title: { len: paramValue.len } - } - break; - case 'cta': - asset = { - id: 1, - required: paramValue.required, - data: { type: 12 } - } - break; - case 'icon': - asset = { - id: 2, - required: paramValue.required, - img: { type: 1, w: paramValue.sizes[0], h: paramValue.sizes[1] } - } - break; - case 'image': - asset = { - id: 3, - required: paramValue.required, - img: { type: 3, w: paramValue.sizes[0], h: paramValue.sizes[1] } - } - break; - case 'body': - asset = { - id: 4, - required: paramValue.required, - data: { type: 2 } - } - break; - case 'sponsoredBy': - asset = { - id: 5, - required: paramValue.required, - data: { type: 1 } - } - break; +var mapAsset = function mapAsset(paramName, paramValue) { + const { required, sizes, wmin, hmin, len } = paramValue; + var id = nativeAssetMap[paramName]; + var assets = []; + + if (id !== undefined) { + switch (paramName) { + case 'title': + assets.push({ + id: id, + required: required, + title: { + len: len + } + }); + break; + + case 'cta': + assets.push({ + id: id, + required: required, + data: { + type: 12 + } + }); + break; + + case 'icon': + assets.push({ + id: id, + required: required, + img: { + type: 1, + w: sizes && sizes[0], + h: sizes && sizes[1] + } + }); + break; + + case 'image': + var hasMultipleImages = sizes && Array.isArray(sizes[0]); + var imageSizes = hasMultipleImages ? sizes : [sizes]; + + for (var i = 0; i < imageSizes.length; i++) { + assets.push({ + id: i > 0 ? 10 + i : id, + required: required, + img: { + type: 3, + w: imageSizes[i][0], + h: imageSizes[i][1], + wmin: wmin, + hmin: hmin + } + }); + } + + break; + + case 'body': + assets.push({ + id: id, + required: required, + data: { + type: 2 + } + }); + break; + + case 'sponsoredBy': + assets.push({ + id: id, + required: required, + data: { + type: 1 + } + }); + break; + } } - return asset; -} + + return assets; +}; /** * @param {object} slot Ad Unit Params by Prebid * @returns {object} native object that conforms to ortb native ads spec */ -const mapNative = slot => { +const mapNative = (slot) => { const native = deepAccess(slot, 'mediaTypes.native'); - let assets; if (native) { - const nativeParams = Object.keys(native); - assets = []; - nativeParams.forEach(par => { - const newAsset = mapAsset(par, native[par]); - if (newAsset) { assets.push(newAsset) }; + var nativeParams = Object.keys(native); + var assets = []; + nativeParams.forEach(function (par) { + var newAssets = mapAsset(par, native[par]); + assets = assets.concat(newAssets); }); + return { + request: JSON.stringify({ + native: { + assets: assets + } + }) + }; } - return assets ? { request: JSON.stringify({ native: { assets } }) } : undefined; } var mapVideo = (slot, videoFromBid) => { @@ -346,41 +467,18 @@ const mapImpression = slot => { const imp = { id: id && siteId ? id.padStart(3, '0') : 'bidid-' + bidId, banner: mapBanner(slot), - native: mapNative(slot), + native: mapNative(slot, bidId), video: mapVideo(slot, video), tagid: adUnitCode, ext, }; // Check floorprices for this imp - const currency = getCurrency(); - if (typeof slot.getFloor === 'function') { - var bannerFloor = 0; - var nativeFloor = 0; - var videoFloor = 0; // sspBC adapter accepts only floor per imp - check for maximum value for requested ad types and sizes - - if (slot.sizes.length) { - bannerFloor = slot.sizes.reduce(function (prev, next) { - var currentFloor = slot.getFloor({ - mediaType: 'banner', - size: next, - currency - }).floor; - return prev > currentFloor ? prev : currentFloor; - }, 0); - } + const { floor, currency } = getHighestFloor(slot); - nativeFloor = slot.getFloor({ - mediaType: 'native', currency - }); - videoFloor = slot.getFloor({ - mediaType: 'video', currency - }); - imp.bidfloor = Math.max(bannerFloor, nativeFloor, videoFloor); - } else { - imp.bidfloor = 0; - } + imp.bidfloor = floor; imp.bidfloorcur = currency; + return imp; } @@ -395,50 +493,51 @@ const isNativeAd = bid => { return bid.admNative || (bid.adm && bid.adm.match(xmlTester)); } -const parseNative = nativeData => { - const result = {}; - nativeData.assets.forEach(asset => { - const id = parseInt(asset.id); - switch (id) { - case 0: - result.title = asset.title.text; - break; - case 1: - result.cta = asset.data.value; - break; - case 2: - result.icon = { - url: asset.img.url, - width: asset.img.w, - height: asset.img.h, - }; - break; - case 3: - result.image = { - url: asset.img.url, - width: asset.img.w, - height: asset.img.h, - }; - break; - case 4: - result.body = asset.data.value; - break; - case 5: - result.sponsoredBy = asset.data.value; - break; +const parseNative = (nativeData) => { + const { link = {}, imptrackers: impressionTrackers, jstracker } = nativeData; + const { url: clickUrl, clicktrackers: clickTrackers = [] } = link; - default: - logWarn('Unrecognized native asset', asset); + const result = { + clickUrl, + clickTrackers, + impressionTrackers, + javascriptTrackers: isArray(jstracker) ? jstracker : jstracker && [jstracker], + }; + + nativeData.assets.forEach(asset => { + const { id, img = {}, title = {}, data = {} } = asset; + const { w: imgWidth, h: imgHeight, url: imgUrl, type: imgType } = img; + const { type: dataType, value: dataValue } = data; + const { text: titleText } = title; + const detectedType = getNativeAssetType(id); + if (titleText) { + result.title = titleText; + } + if (imgUrl) { + // image or icon + const thisImage = { + url: imgUrl, + width: imgWidth, + height: imgHeight, + }; + if (imgType === 3 || detectedType === 'image') { + result.image = thisImage; + } else if (imgType === 1 || detectedType === 'icon') { + result.icon = thisImage; + } + } + if (dataValue) { + // call-to-action, sponsored-by or body + if (dataType === 1 || detectedType === 'sponsoredBy') { + result.sponsoredBy = dataValue; + } else if (dataType === 2 || detectedType === 'body') { + result.body = dataValue; + } else if (dataType === 12 || detectedType === 'cta') { + result.cta = dataValue; + } } }); - result.clickUrl = nativeData.link.url; - result.impressionTrackers = nativeData.imptrackers; - if (isArray(nativeData.jstracker)) { - result.javascriptTrackers = nativeData.jstracker; - } else if (nativeData.jstracker) { - result.javascriptTrackers = [nativeData.jstracker]; - } return result; } @@ -505,10 +604,6 @@ const renderCreative = (site, auctionId, bid, seat, request) => { window.requestPVID = "${pageView.id}"; `; - if (gam) { - adcode += `window.gam = ${JSON.stringify(gam)};`; - } - adcode += ` @@ -550,7 +645,7 @@ const spec = { const payload = { id: bidderRequest.auctionId, site: { - id: siteId, + id: siteId ? `${siteId}` : undefined, publisher: publisherId ? { id: publisherId } : undefined, page, domain, @@ -586,7 +681,7 @@ const spec = { const { bidderRequest } = request; const response = serverResponse.body; const bids = []; - const site = JSON.parse(request.data).site; // get page and referer data from request + let site = JSON.parse(request.data).site; // get page and referer data from request site.sn = response.sn || 'mc_adapter'; // WPM site name (wp_sn) pageView.sn = site.sn; // store site_name (for syncing and notifications) let seat; @@ -597,39 +692,35 @@ const spec = { 'bidid-' prefix indicates oneCode (parameterless) request and response */ response.seatbid.forEach(seatbid => { - let creativeCache; seat = seatbid.seat; seatbid.bid.forEach(serverBid => { // get data from bid response - const { adomain, crid = `mcad_${bidderRequest.auctionId}_${site.slot}`, impid, exp = 300, ext, price, w, h } = serverBid; + const { adomain, crid = `mcad_${bidderRequest.auctionId}_${site.slot}`, impid, exp = 300, ext = {}, price, w, h } = serverBid; const bidRequest = bidderRequest.bids.filter(b => { - const { bidId, params = {} } = b; + const { bidId, params: requestParams = {} } = b; + const params = unpackParams(requestParams); const { id, siteId } = params; const currentBidId = id && siteId ? id : 'bidid-' + bidId; return currentBidId === impid; })[0]; - // get data from linked bidRequest - const { bidId, params } = bidRequest || {}; - - // get slot id for current bid - site.slot = params && params.id; - - if (ext) { - /* - bid response might include ext object containing siteId / slotId, as detected by OneCode - update site / slot data in this case - - ext also might contain publisherId and custom ad label - */ - const { siteid, slotid, pubid, adlabel, cache } = ext; - site.id = siteid || site.id; - site.slot = slotid || site.slot; - site.publisherId = pubid; - site.adLabel = adlabel; - creativeCache = cache; - } + // get bidid from linked bidRequest + const { bidId } = bidRequest || {}; + + // get ext data from bid + const { siteid = site.id, slotid = site.slot, pubid, adlabel, cache: creativeCache, vurls = [] } = ext; + + // update site data + site = { + ...site, + ...{ + id: siteid, + slot: slotid, + publisherId: pubid, + adLabel: adlabel + } + }; if (bidRequest && site.id && !strIncludes(site.id, 'bidid')) { // found a matching request; add this bid @@ -652,6 +743,7 @@ const spec = { pricepl: ext && ext.pricepl, }, netRevenue: true, + vurls, }; // mediaType and ad data for instream / native / banner @@ -671,24 +763,6 @@ const spec = { bid.native = parseNative(nativeData); bid.width = 1; bid.height = 1; - - // append viewability tracker - const jsData = { - rid: bidRequest.auctionId, - crid: bid.creativeId, - adunit: bidRequest.adUnitCode, - url: bid.native.clickUrl, - vendor: seat, - site: site.id, - slot: site.slot, - cpm: bid.cpm.toPrecision(4), - }; - const jsTracker = '') }); @@ -1385,63 +1412,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 +1572,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(); 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/fpd/oneClient.js b/test/spec/fpd/oneClient.js new file mode 100644 index 00000000000..4ecde8d8a38 --- /dev/null +++ b/test/spec/fpd/oneClient.js @@ -0,0 +1,24 @@ +import {clientSectionChecker} from '../../../src/fpd/oneClient.js'; + +describe('onlyOneClientSection', () => { + const oneClient = clientSectionChecker(); + [ + [['app'], 'app'], + [['site'], 'site'], + [['dooh'], 'dooh'], + [['app', 'site'], 'app'], + [['dooh', 'app', 'site'], 'dooh'], + [['dooh', 'site'], 'dooh'] + ].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'}])); + oneClient(req); + expect(Object.keys(req)).to.eql([winner]); + }) + }); + it('should not choke if none of the sections are in the request', () => { + const 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/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/modules/1plusXRtdProvider_spec.js b/test/spec/modules/1plusXRtdProvider_spec.js index 496c005f470..8657c37d7d8 100644 --- a/test/spec/modules/1plusXRtdProvider_spec.js +++ b/test/spec/modules/1plusXRtdProvider_spec.js @@ -332,15 +332,16 @@ 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', () => { diff --git a/test/spec/modules/33acrossBidAdapter_spec.js b/test/spec/modules/33acrossBidAdapter_spec.js index 2680544d00b..3b3c05660df 100644 --- a/test/spec/modules/33acrossBidAdapter_spec.js +++ b/test/spec/modules/33acrossBidAdapter_spec.js @@ -218,6 +218,14 @@ describe('33acrossBidAdapter:', function () { return this; }; + this.withReferer = referer => { + Object.assign(ttxRequest.site, { + ref: referer + }); + + return this; + }; + this.withSchain = schain => { Object.assign(ttxRequest, { source: { @@ -1187,26 +1195,51 @@ describe('33acrossBidAdapter:', function () { }); }); - context('when referer value is available', function() { - it('returns corresponding server requests with site.page set', function() { - const bidderRequest = { - refererInfo: { - page: 'http://foo.com/bar' - } - }; + 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 = { + refererInfo: { + page: 'http://foo.com/bar' + } + }; - const ttxRequest = new TtxRequestBuilder() - .withBanner() - .withProduct() - .withPageUrl('http://foo.com/bar') - .build(); - const serverRequest = new ServerRequestBuilder() - .withData(ttxRequest) - .build(); + const ttxRequest = new TtxRequestBuilder() + .withBanner() + .withProduct() + .withPageUrl('http://foo.com/bar') + .build(); + const serverRequest = new ServerRequestBuilder() + .withData(ttxRequest) + .build(); - const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); - validateBuiltServerRequest(builtServerRequest, serverRequest); + validateBuiltServerRequest(builtServerRequest, serverRequest); + }); + }); + + context('when refererInfo.ref is defined', function() { + it('returns corresponding server requests with site.ref set', function() { + const bidderRequest = { + refererInfo: { + ref: 'google.com' + } + }; + + const ttxRequest = new TtxRequestBuilder() + .withBanner() + .withProduct() + .withReferer('google.com') + .build(); + const serverRequest = new ServerRequestBuilder() + .withData(ttxRequest) + .build(); + + const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); + + validateBuiltServerRequest(builtServerRequest, serverRequest); + }); }); }); @@ -1246,7 +1279,7 @@ describe('33acrossBidAdapter:', function () { }); context('when referer value is not available', function() { - it('returns corresponding server requests without site.page set', function() { + it('returns corresponding server requests without site.page and site.ref set', function() { const bidderRequest = { refererInfo: {} }; 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 8abdc621922..adba79ddc96 100644 --- a/test/spec/modules/adagioBidAdapter_spec.js +++ b/test/spec/modules/adagioBidAdapter_spec.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 = { @@ -266,7 +267,8 @@ describe('Adagio bid adapter', () => { 'schain', 'prebidVersion', 'featuresVersion', - 'data' + 'data', + 'usIfr' ]; it('groups requests by organizationId', function() { @@ -345,6 +347,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() { @@ -381,8 +453,8 @@ describe('Adagio bid adapter', () => { context: 'outstream', playerSize: [[300, 250]], mimes: ['video/mp4'], - api: 5, // will be removed because invalid - playbackmethod: [7], // will be removed because invalid + api: 'val', // will be removed because invalid + playbackmethod: ['val'], // will be removed because invalid } }, }).withParams({ @@ -561,15 +633,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 +684,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 +800,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() { @@ -1377,31 +1481,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/adfBidAdapter_spec.js b/test/spec/modules/adfBidAdapter_spec.js index eb560bb1bae..574f559e994 100644 --- a/test/spec/modules/adfBidAdapter_spec.js +++ b/test/spec/modules/adfBidAdapter_spec.js @@ -481,6 +481,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 +558,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 +617,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 +666,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 +694,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 +719,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 +738,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 +768,14 @@ describe('Adf adapter', function () { icon: { aspect_ratios: [] } + }, + nativeOrtbRequest: { + request: { + assets: [ + { img: {} }, + { img: {} } + ] + } } }]; @@ -689,13 +794,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 +827,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 +839,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 +870,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 +884,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 +972,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 +1026,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 +1079,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/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/admaruBidAdapter_spec.js b/test/spec/modules/admaruBidAdapter_spec.js index a45ddae108f..813a4ed8b29 100644 --- a/test/spec/modules/admaruBidAdapter_spec.js +++ b/test/spec/modules/admaruBidAdapter_spec.js @@ -121,4 +121,52 @@ describe('Admaru Adapter', function () { expect(result.length).to.equal(0); }); }); + + describe('getUserSyncs()', () => { + it('should return iframe user sync if iframe sync is enabled', () => { + const syncs = spec.getUserSyncs( + { + pixelEnabled: true, + iframeEnabled: true, + }, + null + ); + + expect(syncs).to.deep.equal([ + { + type: 'iframe', + url: 'https://p2.admaru.net/UserSync/sync', + }, + ]); + }); + + it('should return image syncs if they are enabled and iframe is disabled', () => { + const syncs = spec.getUserSyncs( + { + pixelEnabled: true, + iframeEnabled: false, + }, + null + ); + + expect(syncs).to.deep.equal([ + { + type: 'image', + url: 'https://p2.admaru.net/UserSync/sync', + }, + ]); + }); + + it('should not return user syncs if syncs are disabled', () => { + const syncs = spec.getUserSyncs( + { + pixelEnabled: false, + iframeEnabled: false, + }, + null + ); + + expect(syncs).to.deep.equal([]); + }); + }); }); 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/admediaBidAdapter_spec.js b/test/spec/modules/admediaBidAdapter_spec.js new file mode 100644 index 00000000000..a04e288311a --- /dev/null +++ b/test/spec/modules/admediaBidAdapter_spec.js @@ -0,0 +1,130 @@ +import {assert, expect} from 'chai'; +import {spec} from 'modules/admediaBidAdapter.js'; +import {newBidder} from 'src/adapters/bidderFactory.js'; +import * as utils from 'src/utils.js'; + +const ENDPOINT_URL = 'https://prebid.admedia.com/bidder/'; + +describe('admediaBidAdapter', function () { + const adapter = newBidder(spec); + describe('isBidRequestValid', function () { + let bid = { + adUnitCode: 'adunit-code', + bidder: 'admedia', + bidId: 'g7ghhs78', + mediaTypes: {banner: {sizes: [[300, 250]]}}, + params: { + placementId: '782332', + aid: '86858', + }, + refererInfo: { + page: 'https://test.com' + } + }; + it('should return true where required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + }); + describe('buildRequests', function () { + let bidRequests = [ + { + adUnitCode: 'adunit-code', + bidder: 'admedia', + bidId: 'g7ghhs78', + mediaTypes: {banner: {sizes: [[300, 250]]}}, + params: { + placementId: '782332', + aid: '86858' + }, + refererInfo: { + page: 'https://test.com' + } + } + ]; + + let bidderRequests = { + refererInfo: { + page: 'https://test.com', + } + }; + + const request = spec.buildRequests(bidRequests, bidderRequests); + it('sends bid request via POST', function () { + expect(request[0].method).to.equal('POST'); + }); + }); + + describe('interpretResponse', function () { + let bidRequest = { + method: 'POST', + url: ENDPOINT_URL, + data: { + 'id': '782332', + 'aid': '86858', + 'tags': [ + { + 'sizes': [ + '300x250' + ], + 'id': '782332', + 'aid': '86858' + } + ], + 'bidId': '2556388472b168', + 'referer': 'https%3A%2F%test.com' + } + }; + let serverResponse = { + body: + { + 'tags': [ + { + 'requestId': '2b8bf2ac497ae', + 'ad': "", + 'width': 300, + 'height': 250, + 'cpm': 0.71, + 'currency': 'USD', + 'ttl': 200, + 'creativeId': 128, + 'netRevenue': true, + 'meta': { + 'advertiserDomains': [ + 'https://www.test.com' + ] + } + } + ] + } + + }; + it('should get the correct bid response', function () { + let expectedResponse = + { + 'tags': [ + { + 'requestId': '2b8bf2ac497ae', + 'ad': "", + 'width': 300, + 'height': 250, + 'cpm': 0.71, + 'currency': 'USD', + 'ttl': 200, + 'creativeId': 128, + 'netRevenue': true, + 'meta': { + 'advertiserDomains': [ + 'https://www.test.com' + ] + } + } + ] + } + let result = spec.interpretResponse(serverResponse, bidRequest); + expect(result).to.be.an('array').that.is.not.empty; + expect(Object.keys(result[0])).to.have.members( + Object.keys(expectedResponse.tags[0]) + ); + }); + }); +}); diff --git a/test/spec/modules/admixerBidAdapter_spec.js b/test/spec/modules/admixerBidAdapter_spec.js index 228b87ae4d5..26caaf5b70f 100644 --- a/test/spec/modules/admixerBidAdapter_spec.js +++ b/test/spec/modules/admixerBidAdapter_spec.js @@ -4,8 +4,10 @@ import {newBidder} from 'src/adapters/bidderFactory.js'; import {config} from '../../../src/config.js'; const BIDDER_CODE = 'admixer'; +const BIDDER_CODE_ADX = 'admixeradx'; const ENDPOINT_URL = 'https://inv-nets.admixer.net/prebid.1.2.aspx'; const ENDPOINT_URL_CUSTOM = 'https://custom.admixer.net/prebid.aspx'; +const ENDPOINT_URL_ADX = 'https://inv-nets.admixer.net/adxprebid.1.2.aspx'; const ZONE_ID = '2eb6bd58-865c-47ce-af7f-a918108c3fd2'; describe('AdmixerAdapter', function () { @@ -16,18 +18,22 @@ describe('AdmixerAdapter', function () { expect(adapter.callBids).to.be.exist.and.to.be.a('function'); }); }); + // inv-nets.admixer.net/adxprebid.1.2.aspx describe('isBidRequestValid', function () { let bid = { - 'bidder': BIDDER_CODE, - 'params': { - 'zone': ZONE_ID + bidder: BIDDER_CODE, + 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', }; it('should return true when required params found', function () { @@ -38,7 +44,7 @@ describe('AdmixerAdapter', function () { let bid = Object.assign({}, bid); delete bid.params; bid.params = { - 'placementId': 0 + placementId: 0, }; expect(spec.isBidRequestValid(bid)).to.equal(false); }); @@ -47,22 +53,25 @@ describe('AdmixerAdapter', function () { describe('buildRequests', function () { let validRequest = [ { - 'bidder': BIDDER_CODE, - 'params': { - 'zone': ZONE_ID + bidder: BIDDER_CODE, + 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, refererInfo: { - page: 'https://example.com' - } + page: 'https://example.com', + }, }; it('should add referrer and imp to be equal bidRequest', function () { @@ -81,49 +90,122 @@ describe('AdmixerAdapter', function () { it('sends bid request to CUSTOM_ENDPOINT via GET', function () { config.setBidderConfig({ bidders: [BIDDER_CODE], // one or more bidders - config: {[BIDDER_CODE]: {endpoint_url: ENDPOINT_URL_CUSTOM}} + config: { [BIDDER_CODE]: { endpoint_url: ENDPOINT_URL_CUSTOM } }, }); - const request = config.runWithBidder(BIDDER_CODE, () => spec.buildRequests(validRequest, bidderRequest)); + const request = config.runWithBidder(BIDDER_CODE, () => + spec.buildRequests(validRequest, bidderRequest) + ); expect(request.url).to.equal(ENDPOINT_URL_CUSTOM); expect(request.method).to.equal('POST'); }); }); + describe('buildRequestsAdmixerADX', function () { + let validRequest = [ + { + bidder: BIDDER_CODE_ADX, + params: { + zone: ZONE_ID, + }, + 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', + }, + }; + + it('sends bid request to ADX ENDPOINT', function () { + const request = spec.buildRequests(validRequest, bidderRequest); + expect(request.url).to.equal(ENDPOINT_URL_ADX); + expect(request.method).to.equal('POST'); + }); + }); + + describe('checkFloorGetting', function () { + let validRequest = [ + { + bidder: BIDDER_CODE, + params: { + zone: ZONE_ID, + }, + adUnitCode: 'adunit-code', + sizes: [[300, 250]], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + }, + ]; + let bidderRequest = { + bidderCode: BIDDER_CODE, + refererInfo: { + page: 'https://example.com', + }, + }; + it('gets floor', function () { + bidderRequest.getFloor = () => { + return { floor: 0.6 }; + }; + const request = spec.buildRequests(validRequest, bidderRequest); + const payload = request.data; + expect(payload.bidFloor).to.deep.equal(0.6); + }); + }); + describe('interpretResponse', function () { let response = { body: { - ads: [{ - 'currency': 'USD', - 'cpm': 6.210000, - 'ad': '
ad
', - 'width': 300, - 'height': 600, - 'creativeId': 'ccca3e5e-0c54-4761-9667-771322fbdffc', - 'ttl': 360, - 'netRevenue': false, - 'requestId': '5e4e763b6bc60b', - 'dealId': 'asd123', - 'meta': {'advertiserId': 123, 'networkId': 123, 'advertiserDomains': ['test.com']} - }] - } + ads: [ + { + currency: 'USD', + cpm: 6.21, + ad: '
ad
', + width: 300, + height: 600, + creativeId: 'ccca3e5e-0c54-4761-9667-771322fbdffc', + ttl: 360, + netRevenue: false, + requestId: '5e4e763b6bc60b', + dealId: 'asd123', + meta: { + advertiserId: 123, + networkId: 123, + advertiserDomains: ['test.com'], + }, + }, + ], + }, }; it('should get correct bid response', function () { const ads = response.body.ads; let expectedResponse = [ { - 'requestId': ads[0].requestId, - 'cpm': ads[0].cpm, - 'creativeId': ads[0].creativeId, - 'width': ads[0].width, - 'height': ads[0].height, - 'ad': ads[0].ad, - 'currency': ads[0].currency, - 'netRevenue': ads[0].netRevenue, - 'ttl': ads[0].ttl, - 'dealId': ads[0].dealId, - 'meta': {'advertiserId': 123, 'networkId': 123, 'advertiserDomains': ['test.com']} - } + requestId: ads[0].requestId, + cpm: ads[0].cpm, + creativeId: ads[0].creativeId, + width: ads[0].width, + height: ads[0].height, + ad: ads[0].ad, + currency: ads[0].currency, + netRevenue: ads[0].netRevenue, + ttl: ads[0].ttl, + dealId: ads[0].dealId, + meta: { + advertiserId: 123, + networkId: 123, + advertiserDomains: ['test.com'], + }, + }, ]; let result = spec.interpretResponse(response); @@ -141,18 +223,16 @@ describe('AdmixerAdapter', function () { describe('getUserSyncs', function () { let imgUrl = 'https://example.com/img1'; let frmUrl = 'https://example.com/frm2'; - let responses = [{ - body: { - cm: { - pixels: [ - imgUrl - ], - iframes: [ - frmUrl - ], - } - } - }]; + let responses = [ + { + body: { + cm: { + pixels: [imgUrl], + iframes: [frmUrl], + }, + }, + }, + ]; it('Returns valid values', function () { let userSyncAll = spec.getUserSyncs({pixelEnabled: true, iframeEnabled: true}, responses); 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 ee585862800..153565eb3ca 100644 --- a/test/spec/modules/adnuntiusBidAdapter_spec.js +++ b/test/spec/modules/adnuntiusBidAdapter_spec.js @@ -8,26 +8,27 @@ import { getStorageManager } from 'src/storageManager.js'; 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 = { adnuntius: { storageAllowed: true } }; + const storage = getStorageManager({ bidderCode: 'adnuntius' }) + storage.setDataInLocalStorage('adn.metaData', JSON.stringify(meta)) + }); + + after(() => { + $$PREBID_GLOBAL$$.bidderSettings = {}; }); afterEach(function () { config.resetConfig(); - $$PREBID_GLOBAL$$.bidderSettings = {}; }); const tzo = new Date().getTimezoneOffset(); @@ -35,7 +36,7 @@ describe('adnuntiusBidAdapter', function () { 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_CONSENT = `${EURO_URL}${tzo}&format=json&consentString=consentString&userId=${usi}`; const adapter = newBidder(spec); const bidderRequests = [ @@ -71,29 +72,29 @@ 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 nativeBidderRequest = [ + // { + // bidId: '123', + // bidder: 'adnuntius', + // params: { + // auId: '8b6bc', + // network: 'adnuntius', + // }, + // mediaTypes: { + // native: { + // title: { + // required: true + // }, + // image: { + // required: true + // }, + // body: { + // required: true + // } + // } + // }, + // } + // ] const singleBidRequest = { bid: [ @@ -107,9 +108,9 @@ describe('adnuntiusBidAdapter', function () { bid: videoBidderRequest } - const nativeBidRequest = { - bid: nativeBidderRequest - } + // const nativeBidRequest = { + // bid: nativeBidderRequest + // } const serverResponse = { body: { @@ -237,83 +238,83 @@ describe('adnuntiusBidAdapter', function () { ] } } - 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', - } - ] - } - } + // 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 () { @@ -531,21 +532,21 @@ describe('adnuntiusBidAdapter', function () { expect(interpretedResponse[0].vastXml).to.equal(serverVideoResponse.body.adUnits[0].vastXml); }); }); - 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); - }); - }); + // 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/adrinoBidAdapter_spec.js b/test/spec/modules/adrinoBidAdapter_spec.js index 78cea8da9ac..72f006d4e4a 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,46 @@ 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].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 +110,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 +134,16 @@ 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].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 +153,16 @@ 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].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 +172,22 @@ 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].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 +215,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/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..c57e51c44fc 100644 --- a/test/spec/modules/adtelligentBidAdapter_spec.js +++ b/test/spec/modules/adtelligentBidAdapter_spec.js @@ -11,16 +11,17 @@ 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/', }; const DEFAULT_ADATPER_REQ = { bidderCode: 'adtelligent' }; diff --git a/test/spec/modules/adyoulikeBidAdapter_spec.js b/test/spec/modules/adyoulikeBidAdapter_spec.js index e6a153d501a..c69d31d1bbd 100644 --- a/test/spec/modules/adyoulikeBidAdapter_spec.js +++ b/test/spec/modules/adyoulikeBidAdapter_spec.js @@ -672,10 +672,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 +692,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 () { @@ -722,6 +724,7 @@ describe('Adyoulike Adapter', function () { expect(payload.Version).to.equal('1.0'); expect(payload.Bids['bid_id_0'].PlacementID).to.be.equal('placement_0'); expect(payload.PageRefreshed).to.equal(false); + expect(payload.pbjs_version).to.equal('$prebid.version$'); expect(payload.Bids['bid_id_0'].TransactionID).to.be.equal('bid_id_0_transaction_id'); }); @@ -736,6 +739,7 @@ describe('Adyoulike Adapter', function () { expect(payload.Version).to.equal('1.0'); expect(payload.Bids['bid_id_0'].PlacementID).to.be.equal('placement_0'); expect(payload.PageRefreshed).to.equal(false); + expect(payload.pbjs_version).to.equal('$prebid.version$'); expect(payload.Bids['bid_id_0'].TransactionID).to.be.equal('bid_id_0_transaction_id'); }); @@ -758,6 +762,7 @@ describe('Adyoulike Adapter', function () { expect(payload.Bids['bid_id_1'].TransactionID).to.be.equal('bid_id_1_transaction_id'); expect(payload.Bids['bid_id_3'].TransactionID).to.be.equal('bid_id_3_transaction_id'); expect(payload.PageRefreshed).to.equal(false); + expect(payload.pbjs_version).to.equal('$prebid.version$'); }); it('sends bid request to endpoint setted by parameters', function () { diff --git a/test/spec/modules/aidemBidAdapter_spec.js b/test/spec/modules/aidemBidAdapter_spec.js index 71edfcf82fb..6a875feb2a9 100644 --- a/test/spec/modules/aidemBidAdapter_spec.js +++ b/test/spec/modules/aidemBidAdapter_spec.js @@ -13,7 +13,7 @@ const VALID_BIDS = [ params: { siteId: '301491', publisherId: '3021491', - placementId: 13144370, + placementId: '13144370', }, mediaTypes: { banner: { @@ -26,7 +26,7 @@ const VALID_BIDS = [ params: { siteId: '301491', publisherId: '3021491', - placementId: 13144370, + placementId: '13144370', }, mediaTypes: { video: { @@ -110,7 +110,7 @@ const INVALID_BIDS = [ }, params: { siteId: '301491', - placementId: 13144370, + placementId: '13144370', }, }, { @@ -126,7 +126,7 @@ const INVALID_BIDS = [ }, params: { siteId: '301491', - placementId: 13144370, + placementId: '13144370', }, }, { @@ -143,7 +143,7 @@ const INVALID_BIDS = [ }, params: { siteId: '301491', - placementId: 13144370, + placementId: '13144370', video: { size: [480, 40] } @@ -167,8 +167,8 @@ const DEFAULT_VALID_BANNER_REQUESTS = [ } }, params: { - siteId: 1, - placementId: 13144370 + siteId: '1', + placementId: '13144370' }, src: 'client', transactionId: '54a58774-7a41-494e-9aaf-fa7b79164f0c' @@ -192,8 +192,8 @@ const DEFAULT_VALID_VIDEO_REQUESTS = [ } }, params: { - siteId: 1, - placementId: 13144370 + siteId: '1', + placementId: '13144370' }, src: 'client', transactionId: '54a58774-7a41-494e-9aaf-fa7b79164f0c' @@ -276,7 +276,7 @@ const SERVER_RESPONSE_VIDEO = { }, } -const WIN_NOTICE = { +const WIN_NOTICE_WEB = { 'adId': '3a20ee5dc78c1e', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'creativeId': '24277955', @@ -289,14 +289,87 @@ const WIN_NOTICE = { 'hb_size': '300x250', 'hb_source': 'client', 'hb_format': 'banner', - 'hb_adomain': 'tenutabene.it' + 'hb_adomain': 'example.com' }, + + 'auctionId': '85864730-6cbc-4e56-bc3c-a4a6596dca5b', + 'currency': [ + 'USD' + ], + 'mediaType': 'banner', + '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}}' + } + } + }, 'size': '300x250', + 'params': [ + { + 'placementId': '13144370', + 'siteId': '23434', + 'publisherId': '7689670753' + } + ], 'width': 300, 'height': 250, 'status': 'rendered', @@ -365,6 +438,62 @@ describe('Aidem adapter', () => { deepSetValue(validVideoRequest.params, 'video.size', [640, 480]) expect(spec.isBidRequestValid(validVideoRequest)).to.be.true }); + + it('BANNER: should return true if rateLimit is 1', function () { + // spec.isBidRequestValid() + const validBannerRequest = utils.deepClone(VALID_BIDS[0]) + deepSetValue(validBannerRequest.params, 'rateLimit', 1) + expect(spec.isBidRequestValid(validBannerRequest)).to.be.true + }); + + it('BANNER: should return false if rateLimit is 0', function () { + // spec.isBidRequestValid() + const validBannerRequest = utils.deepClone(VALID_BIDS[0]) + deepSetValue(validBannerRequest.params, 'rateLimit', 0) + expect(spec.isBidRequestValid(validBannerRequest)).to.be.false + }); + + it('BANNER: should return false if rateLimit is not between 0 and 1', function () { + // spec.isBidRequestValid() + const validBannerRequest = utils.deepClone(VALID_BIDS[0]) + deepSetValue(validBannerRequest.params, 'rateLimit', 1.2) + expect(spec.isBidRequestValid(validBannerRequest)).to.be.false + }); + + it('BANNER: should return false if rateLimit is not a number', function () { + // spec.isBidRequestValid() + const validBannerRequest = utils.deepClone(VALID_BIDS[0]) + deepSetValue(validBannerRequest.params, 'rateLimit', '0.5') + expect(spec.isBidRequestValid(validBannerRequest)).to.be.false + }); + + it('VIDEO: should return true if rateLimit is 1', function () { + // spec.isBidRequestValid() + const validVideoRequest = utils.deepClone(VALID_BIDS[1]) + deepSetValue(validVideoRequest.params, 'rateLimit', 1) + expect(spec.isBidRequestValid(validVideoRequest)).to.be.true + }); + + it('VIDEO: should return false if rateLimit is 0', function () { + // spec.isBidRequestValid() + const validVideoRequest = utils.deepClone(VALID_BIDS[1]) + deepSetValue(validVideoRequest.params, 'rateLimit', 0) + expect(spec.isBidRequestValid(validVideoRequest)).to.be.false + }); + + it('VIDEO: should return false if rateLimit is not between 0 and 1', function () { + // spec.isBidRequestValid() + const validBannerRequest = utils.deepClone(VALID_BIDS[1]) + deepSetValue(validBannerRequest.params, 'rateLimit', 1.2) + expect(spec.isBidRequestValid(validBannerRequest)).to.be.false + }); + + it('VIDEO: should return false if rateLimit is not a number', function () { + // spec.isBidRequestValid() + const validBannerRequest = utils.deepClone(VALID_BIDS[1]) + deepSetValue(validBannerRequest.params, 'rateLimit', '0.5') + expect(spec.isBidRequestValid(validBannerRequest)).to.be.false + }); }); describe('buildRequests', () => { @@ -482,11 +611,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..c587ef1a133 100644 --- a/test/spec/modules/airgridRtdProvider_spec.js +++ b/test/spec/modules/airgridRtdProvider_spec.js @@ -110,12 +110,18 @@ describe('airgrid RTD Submodule', function () { .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; + agRTD.setAudiencesAsBidderOrtb2(RTD_CONFIG.dataProviders[0], audiences); + + const bidderConfig = config.getBidderConfig() + const bidders = RTD_CONFIG.dataProviders[0].params.bidders + const bidderOrtb2 = bidderConfig + Object.keys(bidderOrtb2).forEach((bidder) => { if (bidders.indexOf(bidder) === -1) return; - expect(deepAccess(bidderOrtb2[bidder], 'ortb2.user.ext.data.airgrid')).to.eql(MATCHED_AUDIENCES); + MATCHED_AUDIENCES.forEach((audience) => { + expect(deepAccess(bidderOrtb2[bidder], 'ortb2.site.keywords')).to.contain(audience); + }) }); }); diff --git a/test/spec/modules/ajaBidAdapter_spec.js b/test/spec/modules/ajaBidAdapter_spec.js index a2095d52857..7cf5698f7d4 100644 --- a/test/spec/modules/ajaBidAdapter_spec.js +++ b/test/spec/modules/ajaBidAdapter_spec.js @@ -45,6 +45,26 @@ describe('AjaAdapter', function () { bidId: '30b31c1838de1e', bidderRequestId: '22edbae2733bf6', auctionId: '1d1a030790a475', + ortb2: { + 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: '' + } + } + } } ]; @@ -58,7 +78,7 @@ describe('AjaAdapter', function () { const requests = spec.buildRequests(bidRequests, bidderRequest); expect(requests[0].url).to.equal(ENDPOINT); expect(requests[0].method).to.equal('GET'); - expect(requests[0].data).to.equal('asi=123456&skt=5&prebid_id=30b31c1838de1e&prebid_ver=$prebid.version$&page_url=https%3A%2F%2Fhoge.com&'); + expect(requests[0].data).to.equal('asi=123456&skt=5&prebid_id=30b31c1838de1e&prebid_ver=$prebid.version$&page_url=https%3A%2F%2Fhoge.com&sua=%7B%22source%22%3A2%2C%22platform%22%3A%7B%22brand%22%3A%22Android%22%2C%22version%22%3A%5B%228%22%2C%220%22%2C%220%22%5D%7D%2C%22browsers%22%3A%5B%7B%22brand%22%3A%22Not_A%20Brand%22%2C%22version%22%3A%5B%2299%22%2C%220%22%2C%220%22%2C%220%22%5D%7D%2C%7B%22brand%22%3A%22Google%20Chrome%22%2C%22version%22%3A%5B%22109%22%2C%220%22%2C%225414%22%2C%22119%22%5D%7D%2C%7B%22brand%22%3A%22Chromium%22%2C%22version%22%3A%5B%22109%22%2C%220%22%2C%225414%22%2C%22119%22%5D%7D%5D%2C%22mobile%22%3A1%2C%22model%22%3A%22SM-G955U%22%2C%22bitness%22%3A%2264%22%2C%22architecture%22%3A%22%22%7D&'); }); }); diff --git a/test/spec/modules/alkimiBidAdapter_spec.js b/test/spec/modules/alkimiBidAdapter_spec.js index 92e5d28f42a..a396e5b8139 100644 --- a/test/spec/modules/alkimiBidAdapter_spec.js +++ b/test/spec/modules/alkimiBidAdapter_spec.js @@ -140,7 +140,7 @@ describe('alkimiBidAdapter', function () { expect(bidderRequest.data.requestId).to.equal('123') expect(bidderRequest.data.referer).to.equal('http://test.com/path.html') expect(bidderRequest.data.schain).to.deep.contains({ ver: '1.0', complete: 1, nodes: [{ asi: 'alkimi-onboarding.com', sid: '00001', hp: 1 }] }) - expect(bidderRequest.data.signRequest.bids).to.deep.contains({ token: 'e64782a4-8e68-4c38-965b-80ccf115d46f', pos: 7, bidFloor: 0.1, width: 300, height: 250, impMediaType: 'Banner', adUnitCode: 'bannerAdUnitCode' }) + expect(bidderRequest.data.signRequest.bids).to.deep.contains({ token: 'e64782a4-8e68-4c38-965b-80ccf115d46f', pos: 7, bidFloor: 0.1, sizes: [{width: 300, height: 250}], playerSizes: [], impMediaTypes: ['Banner'], adUnitCode: 'bannerAdUnitCode' }) expect(bidderRequest.data.signRequest.randomUUID).to.equal(undefined) expect(bidderRequest.data.bidIds).to.deep.contains('456') expect(bidderRequest.data.signature).to.equal(undefined) diff --git a/test/spec/modules/amxBidAdapter_spec.js b/test/spec/modules/amxBidAdapter_spec.js index d71e9ab5cba..6f69e57d8bc 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,17 @@ const sampleBidderRequest = { consentString: utils.getUniqueIdentifierStr(), vendorData: {}, }, + gppConsent: { + gppString: 'example', + applicableSections: 'example' + }, auctionId: utils.getUniqueIdentifierStr(), 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', @@ -195,9 +204,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 +272,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 +307,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 +316,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 +338,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], @@ -509,7 +598,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 index 471c76f55cf..92246f76c7a 100644 --- a/test/spec/modules/aolBidAdapter_spec.js +++ b/test/spec/modules/aolBidAdapter_spec.js @@ -84,7 +84,7 @@ describe('AolAdapter', function () { 'admixer.net': '100', 'adserver.org': '200', 'adtelligent.com': '300', - 'amxrtb.com': '500', + 'amxdt.net': '500', 'audigent.com': '600', 'britepool.com': '700', 'criteo.com': '800', @@ -110,7 +110,7 @@ describe('AolAdapter', function () { const USER_ID_DATA = { admixerId: SUPPORTED_USER_ID_SOURCES['admixer.net'], adtelligentId: SUPPORTED_USER_ID_SOURCES['adtelligent.com'], - amxId: SUPPORTED_USER_ID_SOURCES['amxrtb.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'], diff --git a/test/spec/modules/appnexusBidAdapter_spec.js b/test/spec/modules/appnexusBidAdapter_spec.js index a5bd9a3592e..1603c6e9397 100644 --- a/test/spec/modules/appnexusBidAdapter_spec.js +++ b/test/spec/modules/appnexusBidAdapter_spec.js @@ -36,14 +36,31 @@ describe('AppNexusAdapter', function () { }); it('should return true when required params found', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let bid1 = deepClone(bid); + bid1.params = { + 'placement_id': 123423 + } + expect(spec.isBidRequestValid(bid1)).to.equal(true); + }); + + it('should return true when required params found', function () { + let bid1 = deepClone(bid); + bid1.params = { 'member': '1234', 'invCode': 'ABCD' }; - expect(spec.isBidRequestValid(bid)).to.equal(true); + expect(spec.isBidRequestValid(bid1)).to.equal(true); + }); + + it('should return true when required params found', function () { + let bid1 = deepClone(bid); + bid1.params = { + 'member': '1234', + 'inv_code': 'ABCD' + }; + + expect(spec.isBidRequestValid(bid1)).to.equal(true); }); it('should return false when required params are not passed', function () { @@ -54,6 +71,15 @@ describe('AppNexusAdapter', function () { }; 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_id': 0 + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); }); describe('buildRequests', function () { @@ -101,6 +127,24 @@ describe('AppNexusAdapter', function () { expect(payload.tags[0].private_sizes).to.deep.equal([{ width: 300, height: 250 }]); }); + it('should parse out private sizes', function () { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { + placementId: '10433394', + private_sizes: [300, 250] + } + } + ); + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + expect(payload.tags[0].private_sizes).to.exist; + expect(payload.tags[0].private_sizes).to.deep.equal([{ width: 300, height: 250 }]); + }); + it('should add position in request', function () { // set from bid.params let bidRequest = deepClone(bidRequests[0]); @@ -168,6 +212,24 @@ describe('AppNexusAdapter', function () { expect(payload.publisher_id).to.deep.equal(1231234); }); + it('should add publisher_id in request', function () { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { + placement_id: '10433394', + publisher_id: '1231234' + } + }); + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + expect(payload.tags[0].publisher_id).to.exist; + expect(payload.tags[0].publisher_id).to.deep.equal(1231234); + expect(payload.publisher_id).to.exist; + expect(payload.publisher_id).to.deep.equal(1231234); + }); + it('should add source and verison to the tag', function () { const request = spec.buildRequests(bidRequests); const payload = JSON.parse(request.data); @@ -189,17 +251,21 @@ describe('AppNexusAdapter', function () { bids: [{ bidder: 'appnexus', params: { - placementId: '10433394' + placement_id: '10433394' } }], 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; @@ -228,122 +294,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], + } + } + } + ); - expect(payload.tags[0].video).to.deep.equal({ - minduration: 5, - playback_method: 2, - skippable: true, - context: 4 + 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); }); - 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); + }); + + 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([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); }); - let bidRequest2 = deepClone(bidRequests[0]); - bidRequest2.adUnitCode = 'adUnit_code_2'; - bidRequest2 = Object.assign({}, bidRequest2, videoData); + 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([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 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 () { @@ -351,7 +598,7 @@ describe('AppNexusAdapter', function () { bidRequests[0], { params: { - placementId: '10433394', + placement_id: '10433394', user: { externalUid: '123', segments: [123, { id: 987, value: 876 }], @@ -407,7 +654,7 @@ describe('AppNexusAdapter', function () { // 2 -> reserve is defined, getFloor not defined > reserve is used bidRequest.params = { - 'placementId': '10433394', + 'placement_id': '10433394', 'reserve': 0.5 }; request = spec.buildRequests([bidRequest]); @@ -424,185 +671,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: { 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(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: { placementId: '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: { placementId: '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: { placementId: '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: { 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); - }); - - 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); - }); - it('should contain hb_source value for other media', function () { let bidRequest = Object.assign({}, bidRequests[0], @@ -610,7 +678,7 @@ describe('AppNexusAdapter', function () { mediaType: 'banner', params: { sizes: [[300, 250], [300, 600]], - placementId: 13144370 + placement_id: 13144370 } } ); @@ -786,7 +854,7 @@ describe('AppNexusAdapter', function () { bidRequests[0], { params: { - placementId: '10433394', + placement_id: '10433394', keywords: { single: 'val', singleArr: ['val'], @@ -931,6 +999,23 @@ describe('AppNexusAdapter', function () { expect(payload.tags[0].use_pmt_rule).to.equal(true); }); + it('should add payment rules to the request', function () { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { + placement_id: '10433394', + use_payment_rule: true + } + } + ); + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + expect(payload.tags[0].use_pmt_rule).to.equal(true); + }); + it('should add gpid to the request', function () { let testGpid = '/12345/my-gpt-tag-0'; let bidRequest = deepClone(bidRequests[0]); @@ -1239,9 +1324,33 @@ describe('AppNexusAdapter', function () { source: 'puburl2.com', uids: [{ id: 'pubid2' + }, { + id: 'pubid2-123' }] }] - } + }, + userIdAsEids: [{ + source: 'adserver.org', + uids: [{ id: 'sample-userid' }] + }, { + source: 'criteo.com', + uids: [{ id: 'sample-criteo-userid' }] + }, { + source: 'netid.de', + uids: [{ id: 'sample-netId-userid' }] + }, { + source: 'liveramp.com', + uids: [{ id: 'sample-idl-userid' }] + }, { + source: 'uidapi.com', + uids: [{ id: 'sample-uid2-value' }] + }, { + source: 'puburl.com', + uids: [{ id: 'pubid1' }] + }, { + source: 'puburl2.com', + uids: [{ id: 'pubid2' }, { id: 'pubid2-123' }] + }] }); const request = spec.buildRequests([bidRequest]); @@ -1282,30 +1391,38 @@ describe('AppNexusAdapter', function () { source: 'puburl2.com', id: 'pubid2' }); + expect(payload.eids).to.deep.include({ + source: 'puburl2.com', + id: 'pubid2-123' + }); }); 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]); @@ -1315,19 +1432,21 @@ 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; + } }); }) @@ -1484,116 +1603,118 @@ 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'); + }] + }; + 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 () { @@ -1651,59 +1772,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/arcspanRtdProvider_spec.js b/test/spec/modules/arcspanRtdProvider_spec.js new file mode 100644 index 00000000000..c75075d8e05 --- /dev/null +++ b/test/spec/modules/arcspanRtdProvider_spec.js @@ -0,0 +1,187 @@ +import { arcspanSubmodule } from 'modules/arcspanRtdProvider.js'; +import { expect } from 'chai'; +import { loadExternalScript } from 'src/adloader.js'; + +describe('arcspanRtdProvider', function () { + describe('init', function () { + afterEach(function () { + window.arcobj1 = undefined; + window.arcobj2 = undefined; + }); + + it('successfully initializes with a valid silo ID', function () { + expect(arcspanSubmodule.init(getGoodConfig())).to.equal(true); + expect(loadExternalScript.called).to.be.ok; + expect(loadExternalScript.args[0][0]).to.deep.equal('https://silo13.p7cloud.net/as.js'); + loadExternalScript.resetHistory(); + }); + + it('fails to initialize with a missing silo ID', function () { + expect(arcspanSubmodule.init(getBadConfig())).to.equal(false); + expect(loadExternalScript.called).to.be.not.ok; + loadExternalScript.resetHistory(); + }); + + it('drops localhost script for test silo', function () { + expect(arcspanSubmodule.init(getTestConfig())).to.equal(true); + expect(loadExternalScript.called).to.be.ok; + expect(loadExternalScript.args[0][0]).to.deep.equal('https://localhost:8080/as.js'); + loadExternalScript.resetHistory(); + }); + }); + + describe('alterBidRequests', function () { + afterEach(function () { + window.arcobj1 = undefined; + window.arcobj2 = undefined; + }); + + it('alters the bid request 1', function () { + setIAB({ + raw: { + images: [ + 'Religion & Spirituality', + 'Medical Health>Substance Abuse', + 'Religion & Spirituality>Astrology', + 'Medical Health', + 'Events & Attractions', + ], + }, + codes: { + images: ['IAB23-10', 'IAB7', 'IAB7-42', 'IAB15-1'], + }, + newcodes: { + images: ['150', '453', '311', '456', '286'], + }, + }); + + var reqBidsConfigObj = {}; + reqBidsConfigObj.ortb2Fragments = {}; + reqBidsConfigObj.ortb2Fragments.global = {}; + arcspanSubmodule.getBidRequestData(reqBidsConfigObj, function () { + expect(reqBidsConfigObj.ortb2Fragments.global.site.name).to.equal( + 'arcspan' + ); + expect(reqBidsConfigObj.ortb2Fragments.global.site.keywords).to.equal( + 'Religion & Spirituality,Medical Health>Substance Abuse,Religion & Spirituality>Astrology,Medical Health,Events & Attractions' + ); + expect(reqBidsConfigObj.ortb2Fragments.global.site.content.data[0].ext.segtax).to.equal(6); + expect(reqBidsConfigObj.ortb2Fragments.global.site.cat).to.eql([ + 'IAB23_10', + 'IAB7', + 'IAB7_42', + 'IAB15_1', + ]); + expect(reqBidsConfigObj.ortb2Fragments.global.site.sectioncat).to.eql([ + 'IAB23_10', + 'IAB7', + 'IAB7_42', + 'IAB15_1' + ]); + expect(reqBidsConfigObj.ortb2Fragments.global.site.pagecat).to.eql([ + 'IAB23_10', + 'IAB7', + 'IAB7_42', + 'IAB15_1', + ]); + expect(reqBidsConfigObj.ortb2Fragments.global.site.content.data[0].segment).to.eql([ + { id: '150' }, + { id: '453' }, + { id: '311' }, + { id: '456' }, + { id: '286' } + ]); + }); + }); + + it('alters the bid request 2', function () { + setIAB({ + raw: { text: ['Sports', 'Sports>Soccer'] }, + codes: { text: ['IAB17', 'IAB17-44'] }, + newcodes: { text: ['483', '533'] }, + }); + + var reqBidsConfigObj = {}; + reqBidsConfigObj.ortb2Fragments = {}; + reqBidsConfigObj.ortb2Fragments.global = {}; + arcspanSubmodule.getBidRequestData(reqBidsConfigObj, function () { + expect(reqBidsConfigObj.ortb2Fragments.global.site.name).to.equal( + 'arcspan' + ); + expect(reqBidsConfigObj.ortb2Fragments.global.site.keywords).to.equal( + 'Sports,Sports>Soccer' + ); + expect(reqBidsConfigObj.ortb2Fragments.global.site.content.data[0].ext.segtax).to.equal(6); + expect(reqBidsConfigObj.ortb2Fragments.global.site.cat).to.eql([ + 'IAB17', + 'IAB17_44', + ]); + expect(reqBidsConfigObj.ortb2Fragments.global.site.sectioncat).to.eql([ + 'IAB17', + 'IAB17_44' + ]); + expect(reqBidsConfigObj.ortb2Fragments.global.site.pagecat).to.eql([ + 'IAB17', + 'IAB17_44', + ]); + expect(reqBidsConfigObj.ortb2Fragments.global.site.content.data[0].segment).to.eql([ + { id: '483' }, + { id: '533' } + ]); + }); + }); + }); +}); + +function getGoodConfig() { + return { + name: 'arcspan', + waitForIt: true, + params: { + silo: 13, + }, + }; +} + +function getBadConfig() { + return { + name: 'arcspan', + waitForIt: true, + params: { + notasilo: 1, + }, + }; +} + +function getTestConfig() { + return { + name: 'arcspan', + waitForIt: true, + params: { + silo: 'test', + }, + }; +} + +function setIAB(vjson) { + window.arcobj2 = {}; + window.arcobj2.cat = 0; + if (typeof vjson.codes != 'undefined') { + window.arcobj2.cat = 1; + if (typeof vjson.codes.images != 'undefined') { + vjson.codes.images.forEach(function f(e, i) { + vjson.codes.images[i] = e.replace('-', '_'); + }); + } + if (typeof vjson.codes.text != 'undefined') { + vjson.codes.text.forEach(function f(e, i) { + vjson.codes.text[i] = e.replace('-', '_'); + }); + } + window.arcobj2.sampled = 1; + window.arcobj1 = {}; + window.arcobj1.page_iab_codes = vjson.codes; + window.arcobj1.page_iab = vjson.raw; + window.arcobj1.page_iab_newcodes = vjson.newcodes; + } +} 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/bedigitechBidAdapter_spec.js b/test/spec/modules/bedigitechBidAdapter_spec.js new file mode 100644 index 00000000000..4bd335b7bbd --- /dev/null +++ b/test/spec/modules/bedigitechBidAdapter_spec.js @@ -0,0 +1,145 @@ +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: 1234, + }, + }; + + 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: 1234, + }, + }]; + + 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://bedigitalhb.s3.amazonaws.com/hb.js'); + }); + + it('should attach pid to url', function () { + const request = spec.buildRequests(bidRequests)[0]; + expect(request.data.pid).to.equal(1234); + }); + }); + + describe('interpretResponse', function () { + const response = { + 'body': [ + { + 'id': 'bedigitechMyidfdfdf', + 'requestId': 'gshgfshdfgdfsd', + 'cpm': '5.0', + 'ad': '%3C!--%20Creative%20--%3E', + 'width': 300, + 'height': 250, + 'currency': 'USD', + 'ttl': 300, + 'creativeId': '0af345b42983cc4bc0' + } + ], + 'headers': { + 'get': function() {} + } + }; + + const bidRequest = { + bidder: 'bedigitech', + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + params: { + placementId: 1234, + }, + }; + + it('should get correct bid response', function () { + const expectedResponse = [ + { + 'id': 'bedigitechMyidfdfdf', + 'requestId': 'gshgfshdfgdfsd', + 'cpm': '5.0', + 'ad': '%3C!--%20Creative%20--%3E', + 'width': 300, + 'height': 250, + 'currency': 'USD', + 'ttl': 300, + 'creativeId': '0af345b42983cc4bc0', + 'meta': { + 'mediaType': BANNER, + }, + } + ]; + + const result = spec.interpretResponse(response, bidRequest); + 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/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/big-richmediaBidAdapter_spec.js b/test/spec/modules/big-richmediaBidAdapter_spec.js index b2ee0725d49..f01e261ef9f 100644 --- a/test/spec/modules/big-richmediaBidAdapter_spec.js +++ b/test/spec/modules/big-richmediaBidAdapter_spec.js @@ -92,40 +92,42 @@ 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 () { @@ -227,42 +229,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/bliinkBidAdapter_spec.js b/test/spec/modules/bliinkBidAdapter_spec.js index b8994b86847..8e96bd76940 100644 --- a/test/spec/modules/bliinkBidAdapter_spec.js +++ b/test/spec/modules/bliinkBidAdapter_spec.js @@ -130,7 +130,7 @@ const getConfigCreative = () => { creativeId: '34567erty', width: 300, height: 250, - ttl: 3600, + ttl: 300, netRevenue: true, } } @@ -145,7 +145,7 @@ const getConfigCreativeVideo = (isNoVast) => { requestId: '6a204ce130280d', width: 300, height: 250, - ttl: 3600, + ttl: 300, netRevenue: true, } } @@ -395,7 +395,7 @@ const testsInterpretResponse = [ mediaType: 'video', netRevenue: true, requestId: '2def0c5b2a7f6e', - ttl: 3600, + ttl: 300, vastXml, vastUrl: 'data:text/xml;charset=utf-8;base64,' + btoa(vastXml.replace(/\\"/g, '"')) }] @@ -421,7 +421,7 @@ const testsInterpretResponse = [ mediaType: 'banner', netRevenue: true, requestId: '2def0c5b2a7f6e', - ttl: 3600, + ttl: 300, width: 300 }] }, @@ -505,7 +505,7 @@ const testsBuildBid = [ netRevenue: true, vastXml: getConfigCreativeVideo().vastXml, vastUrl: 'data:text/xml;charset=utf-8;base64,' + btoa(getConfigCreativeVideo().vastXml.replace(/\\"/g, '"')), - ttl: 3600, + ttl: 300, } }, { @@ -535,7 +535,7 @@ const testsBuildBid = [ netRevenue: true, vastXml: getConfigCreativeVideo().vastXml, vastUrl: 'data:text/xml;charset=utf-8;base64,' + btoa(getConfigCreativeVideo().vastXml.replace(/\\"/g, '"')), - ttl: 3600, + ttl: 300, } }, { @@ -552,7 +552,7 @@ const testsBuildBid = [ height: 250, creativeId: getConfigCreative().creativeId, ad: getConfigBannerBid().creative.banner.adm, - ttl: 3600, + ttl: 300, netRevenue: true, } } diff --git a/test/spec/modules/bluebillywigBidAdapter_spec.js b/test/spec/modules/bluebillywigBidAdapter_spec.js index 0a9c6b30c94..0826acc7f29 100644 --- a/test/spec/modules/bluebillywigBidAdapter_spec.js +++ b/test/spec/modules/bluebillywigBidAdapter_spec.js @@ -535,10 +535,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/brightcomSSPBidAdapter_spec.js b/test/spec/modules/brightcomSSPBidAdapter_spec.js new file mode 100644 index 00000000000..6f35a7a290b --- /dev/null +++ b/test/spec/modules/brightcomSSPBidAdapter_spec.js @@ -0,0 +1,411 @@ +import { expect } from 'chai'; +import * as utils from 'src/utils.js'; +import { spec } from 'modules/brightcomSSPBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory.js'; +import {config} from '../../../src/config'; + +const URL = 'https://rt.marphezis.com/hb'; + +describe('brightcomSSPBidAdapter', function() { + const adapter = newBidder(spec); + let element, win; + let bidRequests; + let sandbox; + + beforeEach(function() { + element = { + x: 0, + y: 0, + + width: 0, + height: 0, + + getBoundingClientRect: () => { + return { + width: element.width, + height: element.height, + + left: element.x, + top: element.y, + right: element.x + element.width, + bottom: element.y + element.height + }; + } + }; + win = { + document: { + visibilityState: 'visible' + }, + + innerWidth: 800, + innerHeight: 600 + }; + bidRequests = [{ + 'bidder': 'bcmssp', + 'params': { + 'publisherId': 1234567 + }, + 'adUnitCode': 'adunit-code', + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250], [300, 600]] + } + }, + 'bidId': '5fb26ac22bde4', + 'bidderRequestId': '4bf93aeb730cb9', + 'auctionId': 'ffe9a1f7-7b67-4bda-a8e0-9ee5dc9f442e', + 'schain': { + 'ver': '1.0', + 'complete': 1, + 'nodes': [ + { + 'asi': 'exchange1.com', + 'sid': '1234', + 'hp': 1, + 'rid': 'bid-request-1', + 'name': 'publisher', + 'domain': 'publisher.com' + } + ] + }, + }]; + + sandbox = sinon.sandbox.create(); + sandbox.stub(document, 'getElementById').withArgs('adunit-code').returns(element); + sandbox.stub(utils, 'getWindowTop').returns(win); + sandbox.stub(utils, 'getWindowSelf').returns(win); + }); + + afterEach(function() { + sandbox.restore(); + }); + + describe('isBidRequestValid', function () { + let bid = { + 'bidder': 'bcmssp', + 'params': { + 'publisherId': 1234567 + }, + 'adUnitCode': 'adunit-code', + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250], [300, 600]] + } + }, + 'bidId': '5fb26ac22bde4', + 'bidderRequestId': '4bf93aeb730cb9', + 'auctionId': 'ffe9a1f7-7b67-4bda-a8e0-9ee5dc9f442e', + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when publisherId not passed correctly', function () { + bid.params.publisherId = undefined; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when require params are not passed', function () { + let bid = Object.assign({}, bid); + bid.params = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + it('sends bid request to our endpoint via POST', function () { + const request = spec.buildRequests(bidRequests); + expect(request.method).to.equal('POST'); + }); + + it('request url should match our endpoint url', function () { + const request = spec.buildRequests(bidRequests); + expect(request.url).to.equal(URL); + }); + + it('sets the proper banner object', function() { + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.format).to.deep.equal([{w: 300, h: 250}, {w: 300, h: 600}]); + }); + + it('accepts a single array as a size', function() { + bidRequests[0].mediaTypes.banner.sizes = [300, 250]; + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.format).to.deep.equal([{w: 300, h: 250}]); + }); + + it('sends bidfloor param if present', function () { + bidRequests[0].params.bidFloor = 0.05; + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].bidfloor).to.equal(0.05); + }); + + it('sends tagid', function () { + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].tagid).to.equal('adunit-code'); + }); + + it('sends publisher id', function () { + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.site.publisher.id).to.equal(1234567); + }); + + it('sends gdpr info if exists', function () { + const consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; + const bidderRequest = { + 'bidderCode': 'bcmssp', + 'auctionId': '1d1a030790a437', + 'bidderRequestId': '22edbae2744bf5', + 'timeout': 3000, + gdprConsent: { + consentString: consentString, + gdprApplies: true + }, + refererInfo: { + page: 'http://example.com/page.html', + domain: 'example.com', + } + }; + bidderRequest.bids = bidRequests; + + const data = JSON.parse(spec.buildRequests(bidRequests, bidderRequest).data); + + expect(data.regs.ext.gdpr).to.exist.and.to.be.a('number'); + expect(data.regs.ext.gdpr).to.equal(1); + expect(data.user.ext.consent).to.exist.and.to.be.a('string'); + expect(data.user.ext.consent).to.equal(consentString); + }); + + it('sends us_privacy', function () { + const bidderRequest = { + uspConsent: '1YYY' + }; + const data = JSON.parse(spec.buildRequests(bidRequests, bidderRequest).data) + + expect(data.regs).to.not.equal(null); + expect(data.regs.ext).to.not.equal(null); + expect(data.regs.ext.us_privacy).to.equal('1YYY'); + }); + + it('sends coppa', function () { + sandbox.stub(config, 'getConfig').withArgs('coppa').returns(true); + + const data = JSON.parse(spec.buildRequests(bidRequests).data) + expect(data.regs).to.not.be.undefined; + expect(data.regs.coppa).to.equal(1); + }); + + it('sends schain', function () { + const data = JSON.parse(spec.buildRequests(bidRequests).data); + expect(data).to.not.be.undefined; + expect(data.source).to.not.be.undefined; + expect(data.source.ext).to.not.be.undefined; + expect(data.source.ext.schain).to.not.be.undefined; + expect(data.source.ext.schain.complete).to.equal(1); + expect(data.source.ext.schain.ver).to.equal('1.0'); + expect(data.source.ext.schain.nodes).to.not.be.undefined; + expect(data.source.ext.schain.nodes).to.lengthOf(1); + expect(data.source.ext.schain.nodes[0].asi).to.equal('exchange1.com'); + expect(data.source.ext.schain.nodes[0].sid).to.equal('1234'); + expect(data.source.ext.schain.nodes[0].hp).to.equal(1); + expect(data.source.ext.schain.nodes[0].rid).to.equal('bid-request-1'); + expect(data.source.ext.schain.nodes[0].name).to.equal('publisher'); + expect(data.source.ext.schain.nodes[0].domain).to.equal('publisher.com'); + }); + + it('sends user eid parameters', function () { + bidRequests[0].userIdAsEids = [{ + source: 'pubcid.org', + uids: [{ + id: 'userid_pubcid' + }] + }, { + source: 'adserver.org', + uids: [{ + id: 'userid_ttd', + ext: { + rtiPartner: 'TDID' + } + }] + } + ]; + + const data = JSON.parse(spec.buildRequests(bidRequests).data); + + expect(data.user).to.not.be.undefined; + expect(data.user.ext).to.not.be.undefined; + expect(data.user.ext.eids).to.not.be.undefined; + expect(data.user.ext.eids).to.deep.equal(bidRequests[0].userIdAsEids); + }); + + it('sends user id parameters', function () { + const userId = { + sharedid: { + id: '01*******', + third: '01E*******' + } + }; + + bidRequests[0].userId = userId; + + const data = JSON.parse(spec.buildRequests(bidRequests).data); + expect(data.user).to.not.be.undefined; + expect(data.user.ext).to.not.be.undefined; + expect(data.user.ext.ids).is.deep.equal(userId); + }); + + context('when element is fully in view', function() { + it('returns 100', function() { + Object.assign(element, { width: 600, height: 400 }); + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.ext.viewability).to.equal(100); + }); + }); + + context('when element is out of view', function() { + it('returns 0', function() { + Object.assign(element, { x: -300, y: 0, width: 207, height: 320 }); + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.ext.viewability).to.equal(0); + }); + }); + + context('when element is partially in view', function() { + it('returns percentage', function() { + Object.assign(element, { width: 800, height: 800 }); + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.ext.viewability).to.equal(75); + }); + }); + + context('when width or height of the element is zero', function() { + it('try to use alternative values', function() { + Object.assign(element, { width: 0, height: 0 }); + bidRequests[0].mediaTypes.banner.sizes = [[800, 2400]]; + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.ext.viewability).to.equal(25); + }); + }); + + context('when nested iframes', function() { + it('returns \'na\'', function() { + Object.assign(element, { width: 600, height: 400 }); + + utils.getWindowTop.restore(); + utils.getWindowSelf.restore(); + sandbox.stub(utils, 'getWindowTop').returns(win); + sandbox.stub(utils, 'getWindowSelf').returns({}); + + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.ext.viewability).to.equal('na'); + }); + }); + + context('when tab is inactive', function() { + it('returns 0', function() { + Object.assign(element, { width: 600, height: 400 }); + + utils.getWindowTop.restore(); + win.document.visibilityState = 'hidden'; + sandbox.stub(utils, 'getWindowTop').returns(win); + + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.ext.viewability).to.equal(0); + }); + }); + }); + + describe('interpretResponse', function () { + let response; + beforeEach(function () { + response = { + body: { + 'id': '37386aade21a71', + 'seatbid': [{ + 'bid': [{ + 'id': '376874781', + 'impid': '283a9f4cd2415d', + 'price': 0.35743275, + 'nurl': '', + 'adm': '', + 'w': 300, + 'h': 250, + 'adomain': ['example.com'] + }] + }] + } + }; + }); + + it('should get the correct bid response', function () { + let expectedResponse = [{ + 'requestId': '283a9f4cd2415d', + 'cpm': 0.35743275, + 'width': 300, + 'height': 250, + 'creativeId': '376874781', + 'currency': 'USD', + 'netRevenue': true, + 'mediaType': 'banner', + 'ad': `
`, + 'ttl': 60, + 'meta': { + 'advertiserDomains': ['example.com'] + } + }]; + + let result = spec.interpretResponse(response); + expect(result[0]).to.deep.equal(expectedResponse[0]); + }); + + it('crid should default to the bid id if not on the response', function () { + let expectedResponse = [{ + 'requestId': '283a9f4cd2415d', + 'cpm': 0.35743275, + 'width': 300, + 'height': 250, + 'creativeId': response.body.seatbid[0].bid[0].id, + 'currency': 'USD', + 'netRevenue': true, + 'mediaType': 'banner', + 'ad': `
`, + 'ttl': 60, + 'meta': { + 'advertiserDomains': ['example.com'] + } + }]; + + let result = spec.interpretResponse(response); + expect(result[0]).to.deep.equal(expectedResponse[0]); + }); + + it('handles empty bid response', function () { + let response = { + body: '' + }; + let result = spec.interpretResponse(response); + expect(result.length).to.equal(0); + }); + }); + + describe('getUserSyncs ', () => { + let syncOptions = {iframeEnabled: true, pixelEnabled: true}; + + it('should not return', () => { + let returnStatement = spec.getUserSyncs(syncOptions, []); + expect(returnStatement).to.be.empty; + }); + }); +}); diff --git a/test/spec/modules/browsiBidAdapter_spec.js b/test/spec/modules/browsiBidAdapter_spec.js new file mode 100644 index 00000000000..8892396adac --- /dev/null +++ b/test/spec/modules/browsiBidAdapter_spec.js @@ -0,0 +1,209 @@ +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', + 'transactionId': '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.transactionId, + 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, + bidderCode: 'browsi', + 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/cleanmedianetBidAdapter_spec.js b/test/spec/modules/cleanmedianetBidAdapter_spec.js index 8c2ac34350b..a19cdf7fd35 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.auctionId); + 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].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/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/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/concertBidAdapter_spec.js b/test/spec/modules/concertBidAdapter_spec.js index 00626472ecb..f5c807b4703 100644 --- a/test/spec/modules/concertBidAdapter_spec.js +++ b/test/spec/modules/concertBidAdapter_spec.js @@ -57,8 +57,9 @@ describe('ConcertAdapter', function () { refererInfo: { page: 'https://www.google.com' }, - uspConsent: '1YYY', - gdprConsent: {} + uspConsent: '1YN-', + gdprConsent: {}, + gppConsent: {} }; bidResponse = { @@ -111,7 +112,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 +139,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 +217,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/connectIdSystem_spec.js b/test/spec/modules/connectIdSystem_spec.js index 52639c39baf..405e7c5e8b9 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} from 'src/adapterManager.js'; + +const TEST_SERVER_URL = 'http://localhost:9876/'; describe('Yahoo ConnectID Submodule', () => { const HASHED_EMAIL = '6bda6f2fa268bf0438b5423a9861a2cedaa5dec163c03f743cfe05c08a8397b2'; @@ -8,6 +12,8 @@ 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'; it('should have the correct module name declared', () => { expect(connectIdSubmodule.name).to.equal('connectId'); @@ -20,271 +26,440 @@ 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 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'); + + cookiesEnabledStub.returns(true); + localStorageEnabledStub.returns(true); + uspConsentDataStub.returns(USP_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(); }); 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); + 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 + } + ] - expect(ajaxStub.firstCall.args[1]).to.be.an('object').that.has.all.keys(['success', 'error']); - }); + storageTestCases.forEach(testCase => it(`getId() should return ${testCase.detail}`, function () { + getCookieStub.withArgs(STORAGE_KEY).returns(testCase.cookie); + getLocalStorageStub.withArgs(STORAGE_KEY).returns(testCase.localStorage); - it('sets GDPR consent data flag correctly when call is under GDPR jurisdiction.', () => { - invokeGetIdAPI({ - he: HASHED_EMAIL, - pixelId: PIXEL_ID, - }, consentData); + const result = invokeGetIdAPI({ + he: HASHED_EMAIL, + endpoint: OVERRIDE_ENDPOINT + }, consentData); - const requestQueryParams = parseQS(ajaxStub.firstCall.args[0].split('?')[1]); - expect(requestQueryParams.gdpr).to.equal('1'); - expect(requestQueryParams.gdpr_consent).to.equal(consentData.gdpr.consentString); - }); + expect(result.id).to.be.deep.equal(testCase.expected ? testCase.expected : undefined); + })); + }) - it('sets GDPR consent data flag correctly when call is NOT under GDPR jurisdiction.', () => { - consentData.gdpr.gdprApplies = false; + 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); + }); - invokeGetIdAPI({ - he: HASHED_EMAIL, - pixelId: PIXEL_ID, - }, consentData); + 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); + }); - 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 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); + }); }); - [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', () => { + 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'); + expect(result.id).to.deep.equal(cookieData); + }); + + it('returns an object with the stored ID from localStorage for valid module configuration', () => { + 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'); + expect(result.id).to.deep.equal(localStorageData); + }); + + 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 + }; + 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 + }; + 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 + }; + 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 + }; + 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 cookie storage', () => { + const dateNowStub = sinon.stub(Date, 'now'); + dateNowStub.returns(0); + getAjaxFnStub.restore(); + const upsResponse = {connectid: 'foobarbaz'}; + const expiryDelta = new Date(60 * 60 * 24 * 14 * 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) + ); + expect(setCookieStub.calledOnce).to.be.true; + expect(setCookieStub.firstCall.args[0]).to.equal(STORAGE_KEY); + expect(setCookieStub.firstCall.args[1]).to.equal(JSON.stringify(upsResponse)); + 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}`); + dateNowStub.restore(); + }); + + 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 = JSON.stringify({ + connectid: 'barfoo', + __expires: 60 * 60 * 24 * 14 * 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) + ); + expect(setLocalStorageStub.calledOnce).to.be.true; + expect(setLocalStorageStub.firstCall.args[0]).to.equal(STORAGE_KEY); + expect(setLocalStorageStub.firstCall.args[1]).to.deep.equal(expectedStoredData); + dateNowStub.restore(); + }); }); }); }); @@ -306,6 +481,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 +530,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/consentManagementUsp_spec.js b/test/spec/modules/consentManagementUsp_spec.js index f9c3cd5890e..e98486754ab 100644 --- a/test/spec/modules/consentManagementUsp_spec.js +++ b/test/spec/modules/consentManagementUsp_spec.js @@ -8,7 +8,7 @@ import { } from 'modules/consentManagementUsp.js'; import * as utils from 'src/utils.js'; import { config } from 'src/config.js'; -import adapterManager, {uspDataHandler} from 'src/adapterManager.js'; +import adapterManager, {gdprDataHandler, uspDataHandler} from 'src/adapterManager.js'; import 'src/prebid.js'; import {defer} from '../../../src/utils/promise.js'; @@ -508,7 +508,20 @@ describe('consentManagement', function () { sinon.assert.notCalled(adapterManager.callDataDeletionRequest); listener(); sinon.assert.calledOnce(adapterManager.callDataDeletionRequest); - }) + }); + + it('does not fail if CMP does not support registerDeletion', () => { + sandbox.stub(window, '__uspapi').callsFake((cmd, _, cb) => { + if (cmd === 'registerDeletion') { + throw new Error('CMP not compliant'); + } else if (cmd === 'getUSPData') { + // eslint-disable-next-line standard/no-callback-literal + cb({uspString: 'string'}, true); + } + }); + setConsentConfig(goodConfig); + expect(uspDataHandler.getConsentData()).to.eql('string'); + }); }); }); }); diff --git a/test/spec/modules/consentManagement_spec.js b/test/spec/modules/consentManagement_spec.js index 47a2e2ab3d9..2cd3d011e1d 100644 --- a/test/spec/modules/consentManagement_spec.js +++ b/test/spec/modules/consentManagement_spec.js @@ -1,4 +1,15 @@ -import { setConsentConfig, requestBidsHook, resetConsentData, userCMP, consentTimeout, staticConsentData, gdprScope } from 'modules/consentManagement.js'; +import { + setConsentConfig, + requestBidsHook, + resetConsentData, + userCMP, + consentTimeout, + actionTimeout, + staticConsentData, + gdprScope, + loadConsentData, + setActionTimeout +} from 'modules/consentManagement.js'; import { gdprDataHandler } from 'src/adapterManager.js'; import * as utils from 'src/utils.js'; import { config } from 'src/config.js'; @@ -135,16 +146,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, @@ -207,18 +219,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); + }); + }); }); }); }); @@ -232,9 +248,7 @@ describe('consentManagement', function () { const staticConfig = { cmpApi: 'static', timeout: 7500, - consentData: { - getTCData: {} - } + consentData: {} } let didHookReturn; @@ -674,6 +688,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': '', diff --git a/test/spec/modules/consumableBidAdapter_spec.js b/test/spec/modules/consumableBidAdapter_spec.js index d1b310624a6..556dce447b9 100644 --- a/test/spec/modules/consumableBidAdapter_spec.js +++ b/test/spec/modules/consumableBidAdapter_spec.js @@ -625,6 +625,52 @@ describe('Consumable BidAdapter', function () { expect(opts.length).to.equal(1); }); + it('should return a sync url if iframe syncs are enabled and GDPR applies', function () { + let gdprConsent = { + consentString: 'GDPR_CONSENT_STRING', + gdprApplies: true, + } + let opts = spec.getUserSyncs(syncOptions, [AD_SERVER_RESPONSE], gdprConsent); + + expect(opts.length).to.equal(1); + expect(opts[0].url).to.equal('https://sync.serverbid.com/ss/730181.html?gdpr=1&gdpr_consent=GDPR_CONSENT_STRING'); + }) + + it('should return a sync url if iframe syncs are enabled and GDPR is undefined', function () { + let gdprConsent = { + consentString: 'GDPR_CONSENT_STRING', + gdprApplies: undefined, + } + let opts = spec.getUserSyncs(syncOptions, [AD_SERVER_RESPONSE], gdprConsent); + + expect(opts.length).to.equal(1); + expect(opts[0].url).to.equal('https://sync.serverbid.com/ss/730181.html?gdpr=0&gdpr_consent=GDPR_CONSENT_STRING'); + }) + + it('should return a sync url if iframe syncs are enabled and USP applies', function () { + let uspConsent = { + consentString: 'USP_CONSENT_STRING', + } + let opts = spec.getUserSyncs(syncOptions, [AD_SERVER_RESPONSE], {}, uspConsent); + + expect(opts.length).to.equal(1); + expect(opts[0].url).to.equal('https://sync.serverbid.com/ss/730181.html?us_privacy=USP_CONSENT_STRING'); + }) + + it('should return a sync url if iframe syncs are enabled, GDPR and USP applies', function () { + let gdprConsent = { + consentString: 'GDPR_CONSENT_STRING', + gdprApplies: true, + } + let uspConsent = { + consentString: 'USP_CONSENT_STRING', + } + let opts = spec.getUserSyncs(syncOptions, [AD_SERVER_RESPONSE], gdprConsent, uspConsent); + + expect(opts.length).to.equal(1); + expect(opts[0].url).to.equal('https://sync.serverbid.com/ss/730181.html?gdpr=1&gdpr_consent=GDPR_CONSENT_STRING&us_privacy=USP_CONSENT_STRING'); + }) + it('should return a sync url if pixel syncs are enabled and some are returned from the server', function () { let syncOptions = {'pixelEnabled': true}; let opts = spec.getUserSyncs(syncOptions, [AD_SERVER_RESPONSE]); 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..bda5d8daca7 100644 --- a/test/spec/modules/conversantBidAdapter_spec.js +++ b/test/spec/modules/conversantBidAdapter_spec.js @@ -263,6 +263,7 @@ describe('Conversant adapter tests', function() { const payload = request.data; expect(payload).to.have.property('id', 'req000'); + expect(payload.source).to.have.property('tid', 'req000'); 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 +390,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 c54453bd7fc..a1d738c3657 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'] } }; @@ -743,6 +728,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: { @@ -898,6 +939,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' @@ -1156,6 +1258,18 @@ describe('The Criteo bidding adapter', function () { it('should properly build a request with first party data', function () { const siteData = { keywords: ['power tools'], + content: { + data: [{ + name: 'some_provider', + ext: { + segtax: 3 + }, + segment: [ + { 'id': '1001' }, + { 'id': '1002' } + ] + }] + }, ext: { data: { pageType: 'article' @@ -1164,6 +1278,16 @@ describe('The Criteo bidding adapter', function () { }; const userData = { gender: 'M', + data: [{ + name: 'some_provider', + ext: { + segtax: 3 + }, + segment: [ + { 'id': '1001' }, + { 'id': '1002' } + ] + }], ext: { data: { registered: true @@ -1203,7 +1327,8 @@ describe('The Criteo bidding adapter', function () { const request = spec.buildRequests(bidRequests, { ...bidderRequest, ortb2 }); expect(request.data.publisher.ext).to.deep.equal({ data: { pageType: 'article' } }); - expect(request.data.user.ext).to.deep.equal({ data: { registered: true } }); + expect(request.data.user).to.deep.equal(userData); + expect(request.data.site).to.deep.equal(siteData); expect(request.data.slots[0].ext).to.deep.equal({ bidfloor: 0.75, data: { @@ -1438,7 +1563,7 @@ describe('The Criteo bidding adapter', function () { creativecode: 'test-crId', width: 728, height: 90, - dealCode: 'myDealCode', + deal: 'myDealCode', adomain: ['criteo.com'], }], }, 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/cwireBidAdapter_spec.js b/test/spec/modules/cwireBidAdapter_spec.js index f116b184b8c..88c54212aff 100644 --- a/test/spec/modules/cwireBidAdapter_spec.js +++ b/test/spec/modules/cwireBidAdapter_spec.js @@ -1,382 +1,296 @@ -import { expect } from 'chai'; -import * as utils from '../../../src/utils.js'; -import { config } from '../../../src/config.js'; -import { - spec, - CW_PAGE_VIEW_ID, - ENDPOINT_URL, - RENDERER_URL, -} from '../../../modules/cwireBidAdapter.js'; -import * as prebidGlobal from 'src/prebidGlobal.js'; - -// ------------------------------------ -// Bid Request Builder -// ------------------------------------ - -const BID_DEFAULTS = { - request: { - bidder: 'cwire', - auctionId: 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee', - transactionId: 'txaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee', - bidId: 'bid123445', - bidderRequestId: 'brid12345', - code: 'original-div', - }, - params: { - placementId: 123456, - pageId: 777, - }, - sizes: [[300, 250], [1, 1]], -}; - -const BidderRequestBuilder = function BidderRequestBuilder(options) { - const defaults = { - bidderCode: 'cwire', - auctionId: BID_DEFAULTS.request.auctionId, - bidderRequestId: BID_DEFAULTS.request.bidderRequestId, - transactionId: BID_DEFAULTS.request.transactionId, - timeout: 3000, - }; - - const request = { - ...defaults, - ...options - }; - - this.build = () => request; -}; - -const BidRequestBuilder = function BidRequestBuilder(options, deleteKeys) { - const defaults = JSON.parse(JSON.stringify(BID_DEFAULTS)); - - const request = { - ...defaults.request, - ...options - }; - - if (request && utils.isArray(deleteKeys)) { - deleteKeys.forEach((k) => { - delete request[k]; - }) - } - - this.withParams = (options, deleteKeys) => { - request.params = { - ...defaults.params, - ...options - }; - if (request && utils.isArray(deleteKeys)) { - deleteKeys.forEach((k) => { - delete request.params[k]; - }) - } - return this; - }; - - this.build = () => request; -}; +import {expect} from 'chai'; +import {newBidder} from '../../../src/adapters/bidderFactory'; +import {BID_ENDPOINT, spec, storage} from '../../../modules/cwireBidAdapter'; +import {deepClone, logInfo} from '../../../src/utils'; +import * as utils from 'src/utils.js'; +import {sandbox, stub} from 'sinon'; +import {config} from '../../../src/config'; describe('C-WIRE bid adapter', () => { - let sandbox; - - beforeEach(() => { - sandbox = sinon.createSandbox(); + config.setConfig({debug: true}); + const adapter = newBidder(spec); + let bidRequests = [ + { + 'bidder': 'cwire', + 'params': { + 'pageId': '4057', + 'placementId': 'ad-slot-bla' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + 'transactionId': '04f2659e-c005-4eb1-a57c-fa93145e3843' + } + ]; + const response = { + body: { + 'cwid': '2ef90743-7936-4a82-8acf-e73382a64e94', + 'hash': '17112D98BBF55D3A', + 'bids': [{ + 'html': '

Hello world

', + 'cpm': 100, + 'currency': 'CHF', + 'dimensions': [1, 1], + 'netRevenue': true, + 'creativeId': '3454', + 'requestId': '2c634d4ca5ccfb', + 'placementId': 177, + 'transactionId': 'b4b32618-1350-4828-b6f0-fbb5c329e9a4', + 'ttl': 360 + }] + } + } + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + expect(spec.isBidRequestValid).to.exist.and.to.be.a('function'); + expect(spec.buildRequests).to.exist.and.to.be.a('function'); + expect(spec.interpretResponse).to.exist.and.to.be.a('function'); + }); }); - - afterEach(() => { - sandbox.restore(); - config.resetConfig(); + describe('buildRequests', function () { + it('sends bid request to ENDPOINT via POST', function () { + const request = spec.buildRequests(bidRequests); + expect(request.url).to.equal(BID_ENDPOINT); + expect(request.method).to.equal('POST'); + }); }); + describe('buildRequests with given creative', function () { + let utilsStub; - // START TESTING - describe('C-WIRE - isBidRequestValid', function () { - it('should return true when required params found', function () { - const bid01 = new BidRequestBuilder().withParams().build(); - expect(spec.isBidRequestValid(bid01)).to.equal(true); + before(function () { + utilsStub = stub(utils, 'getParameterByName').callsFake(function () { + return 'str-str' + }); }); - it('should fail if there is no placementId', function () { - const bid01 = new BidRequestBuilder().withParams().build(); - delete bid01.params.placementId - expect(spec.isBidRequestValid(bid01)).to.equal(false); + after(function () { + utilsStub.restore(); }); - it('should fail if invalid placementId type', function () { - const bid01 = new BidRequestBuilder().withParams().build(); - delete bid01.params.placementId; - bid01.placementId = '322'; - expect(spec.isBidRequestValid(bid01)).to.equal(false); + it('should add creativeId if url parameter given', function () { + // set from bid.params + let bidRequest = deepClone(bidRequests[0]); + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + expect(payload.cwcreative).to.exist; + expect(payload.cwcreative).to.deep.equal('str-str'); + }); + }) + + describe('buildRequests reads adUnit offsetWidth and offsetHeight', function () { + before(function () { + const documentStub = sandbox.stub(document, 'getElementById'); + documentStub.withArgs(`${bidRequests[0].adUnitCode}`).returns({ + offsetWidth: 200, + offsetHeight: 250 + }); }); + it('width and height should be set', function () { + let bidRequest = deepClone(bidRequests[0]); - it('should fail if there is no pageId', function () { - const bid01 = new BidRequestBuilder().withParams().build(); - delete bid01.params.pageId - expect(spec.isBidRequestValid(bid01)).to.equal(false); + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + const el = document.getElementById(`${bidRequest.adUnitCode}`) + + logInfo(JSON.stringify(payload)) + + expect(el).to.exist; + expect(payload.slots[0].cwExt.dimensions.width).to.equal(200); + expect(payload.slots[0].cwExt.dimensions.height).to.equal(250); + expect(payload.slots[0].cwExt.style.maxHeight).to.not.exist; + expect(payload.slots[0].cwExt.style.maxWidth).to.not.exist; + }); + after(function () { + sandbox.restore() + }); + }); + describe('buildRequests reads style attributes', function () { + before(function () { + const documentStub = sandbox.stub(document, 'getElementById'); + documentStub.withArgs(`${bidRequests[0].adUnitCode}`).returns({ + style: { + maxWidth: '400px', + maxHeight: '350px', + } + }); }); + it('css maxWidth should be set', function () { + let bidRequest = deepClone(bidRequests[0]); + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + const el = document.getElementById(`${bidRequest.adUnitCode}`) + + logInfo(JSON.stringify(payload)) - it('should fail if invalid pageId type', function () { - const bid01 = new BidRequestBuilder().withParams().build(); - delete bid01.params.pageId; - bid01.params.pageId = '3320'; - expect(spec.isBidRequestValid(bid01)).to.equal(false); + expect(el).to.exist; + expect(payload.slots[0].cwExt.style.maxWidth).to.eq('400px'); + !expect(payload.slots[0].cwExt.style.maxHeight).to.eq('350px'); }); + after(function () { + sandbox.restore() + }); + }); - it('should fail if cwcreative of type number', function () { - const bid01 = new BidRequestBuilder().withParams().build(); - delete bid01.params.cwcreative; - bid01.params.cwcreative = 3320; - expect(spec.isBidRequestValid(bid01)).to.equal(false); + describe('buildRequests reads feature flags', function () { + before(function () { + sandbox.stub(utils, 'getParameterByName').callsFake(function () { + return 'feature1,feature2' + }); }); - it('should pass with valid cwcreative of type string', function () { - const bid01 = new BidRequestBuilder().withParams().build(); - bid01.params.cwcreative = 'i-am-a-string'; - expect(spec.isBidRequestValid(bid01)).to.equal(true); + it('read from url parameter', function () { + let bidRequest = deepClone(bidRequests[0]); + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + logInfo(JSON.stringify(payload)) + + expect(payload.featureFlags).to.exist; + expect(payload.featureFlags).to.include.members(['feature1', 'feature2']); + }); + after(function () { + sandbox.restore() }); }); - describe('C-WIRE - buildRequests()', function () { - it('creates a valid request', function () { - const bid01 = new BidRequestBuilder({ - mediaTypes: { - banner: { - sizes: [[1, 1]], - } - } - }).withParams({ - cwcreative: '54321', - cwapikey: 'xxx-xxx-yyy-zzz-uuid', - refgroups: 'group_1', - }).build(); - - const bidderRequest01 = new BidderRequestBuilder().build(); - - const requests = spec.buildRequests([bid01], bidderRequest01); - - expect(requests.data.slots.length).to.equal(1); - expect(requests.data.cwid).to.be.null; - expect(requests.data.cwid).to.be.null; - expect(requests.data.slots[0].sizes[0]).to.equal('1x1'); - expect(requests.data.cwcreative).to.equal('54321'); - expect(requests.data.cwapikey).to.equal('xxx-xxx-yyy-zzz-uuid'); - expect(requests.data.refgroups[0]).to.equal('group_1'); + describe('buildRequests reads cwgroups flag', function () { + before(function () { + sandbox.stub(utils, 'getParameterByName').callsFake(function () { + return 'group1,group2' + }); }); - it('creates a valid request - read debug params from second bid', function () { - const bid01 = new BidRequestBuilder().withParams().build(); + it('read from url parameter', function () { + let bidRequest = deepClone(bidRequests[0]); - const bid02 = new BidRequestBuilder({ - mediaTypes: { - banner: { - sizes: [[1, 1]], - } - } - }).withParams({ - cwcreative: '1234', - cwapikey: 'api_key_5', - refgroups: 'group_5', - }).build(); - - const bidderRequest01 = new BidderRequestBuilder().build(); - - const requests = spec.buildRequests([bid01, bid02], bidderRequest01); - - expect(requests.data.slots.length).to.equal(2); - expect(requests.data.cwid).to.be.null; - expect(requests.data.cwid).to.be.null; - expect(requests.data.cwcreative).to.equal('1234'); - expect(requests.data.cwapikey).to.equal('api_key_5'); - expect(requests.data.refgroups[0]).to.equal('group_5'); + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + logInfo(JSON.stringify(payload)) + + expect(payload.refgroups).to.exist; + expect(payload.refgroups).to.include.members(['group1', 'group2']); + }); + after(function () { + sandbox.restore() }); + }) - it('creates a valid request - read debug params from first bid, ignore second', function () { - const bid01 = new BidRequestBuilder() - .withParams({ - cwcreative: '33', - cwapikey: 'api_key_33', - refgroups: 'group_33', - }).build(); - - const bid02 = new BidRequestBuilder() - .withParams({ - cwcreative: '1234', - cwapikey: 'api_key_5', - refgroups: 'group_5', - }).build(); - - const bidderRequest01 = new BidderRequestBuilder().build(); - - const requests = spec.buildRequests([bid01, bid02], bidderRequest01); - - expect(requests.data.slots.length).to.equal(2); - expect(requests.data.cwid).to.be.null; - expect(requests.data.cwid).to.be.null; - expect(requests.data.cwcreative).to.equal('33'); - expect(requests.data.cwapikey).to.equal('api_key_33'); - expect(requests.data.refgroups[0]).to.equal('group_33'); + describe('buildRequests reads debug flag', function () { + before(function () { + sandbox.stub(utils, 'getParameterByName').callsFake(function () { + return 'true' + }); }); - it('creates a valid request - read debug params from 3 different slots', function () { - const bid01 = new BidRequestBuilder() - .withParams({ - cwcreative: '33', - }).build(); - - const bid02 = new BidRequestBuilder() - .withParams({ - cwapikey: 'api_key_5', - }).build(); - - const bid03 = new BidRequestBuilder() - .withParams({ - refgroups: 'group_5', - }).build(); - const bidderRequest01 = new BidderRequestBuilder().build(); - - const requests = spec.buildRequests([bid01, bid02, bid03], bidderRequest01); - - expect(requests.data.slots.length).to.equal(3); - expect(requests.data.cwid).to.be.null; - expect(requests.data.cwid).to.be.null; - expect(requests.data.cwcreative).to.equal('33'); - expect(requests.data.cwapikey).to.equal('api_key_5'); - expect(requests.data.refgroups[0]).to.equal('group_5'); + it('read from url parameter', function () { + let bidRequest = deepClone(bidRequests[0]); + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + logInfo(JSON.stringify(payload)) + + expect(payload.debug).to.exist; + expect(payload.debug).to.equal(true); + }); + after(function () { + sandbox.restore() }); + }) - it('creates a valid request - config is overriden by URL params', function () { - // for whatever reason stub for getWindowLocation does not work - // so this was the closest way to test for get params - const params = sandbox.stub(utils, 'getParameterByName'); - params.withArgs('cwgroups').returns('group_2'); - params.withArgs('cwcreative').returns('654321'); - - const bid01 = new BidRequestBuilder({ - mediaTypes: { - banner: { - sizes: [[1, 1]], - } - } - }).withParams({ - cwcreative: '54321', - cwapikey: 'xxx-xxx-yyy-zzz', - refgroups: 'group_1', - }).build(); - - const bidderRequest01 = new BidderRequestBuilder().build(); - - const requests = spec.buildRequests([bid01], bidderRequest01); - - expect(requests.data.slots.length).to.equal(1); - expect(requests.data.cwid).to.be.null; - expect(requests.data.cwid).to.be.null; - expect(requests.data.slots[0].sizes[0]).to.equal('1x1'); - expect(requests.data.cwcreative).to.equal('654321'); - expect(requests.data.cwapikey).to.equal('xxx-xxx-yyy-zzz'); - expect(requests.data.refgroups[0]).to.equal('group_2'); + describe('buildRequests reads cw_id from Localstorage', function () { + before(function () { + sandbox.stub(storage, 'localStorageIsEnabled').callsFake(() => true); + sandbox.stub(storage, 'setDataInLocalStorage'); + sandbox.stub(storage, 'getDataFromLocalStorage').callsFake((key) => 'taerfagerg'); }); - it('creates a valid request - if params are not set, null or empty array are sent to the API', function () { - const bid01 = new BidRequestBuilder({ - mediaTypes: { - banner: { - sizes: [[1, 1]], - } - } - }).withParams().build(); + it('cw_id is set', function () { + let bidRequest = deepClone(bidRequests[0]); - const bidderRequest01 = new BidderRequestBuilder().build(); + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); - const requests = spec.buildRequests([bid01], bidderRequest01); + logInfo(JSON.stringify(payload)) - expect(requests.data.slots.length).to.equal(1); - expect(requests.data.cwid).to.be.null; - expect(requests.data.cwid).to.be.null; - expect(requests.data.slots[0].sizes[0]).to.equal('1x1'); - expect(requests.data.cwcreative).to.equal(null); - expect(requests.data.cwapikey).to.equal(null); - expect(requests.data.refgroups.length).to.equal(0); + expect(payload.cwid).to.exist; + expect(payload.cwid).to.equal('taerfagerg'); }); - }); + after(function () { + sandbox.restore() + }); + }) - describe('C-WIRE - interpretResponse()', function () { - const serverResponse = { - body: { - bids: [{ - html: '

AD CONTENT

', - currency: 'CHF', - cpm: 43.37, - dimensions: [1, 1], - netRevenue: true, - creativeId: '1337', - requestId: BID_DEFAULTS.request.bidId, - ttl: 3500, - }], - } - }; - - const expectedResponse = [{ - ad: JSON.parse(JSON.stringify(serverResponse.body.bids[0].html)), - bidderCode: BID_DEFAULTS.request.bidder, - cpm: JSON.parse(JSON.stringify(serverResponse.body.bids[0].cpm)), - creativeId: JSON.parse(JSON.stringify(serverResponse.body.bids[0].creativeId)), - currency: JSON.parse(JSON.stringify(serverResponse.body.bids[0].currency)), - height: JSON.parse(JSON.stringify(serverResponse.body.bids[0].dimensions[0])), - width: JSON.parse(JSON.stringify(serverResponse.body.bids[0].dimensions[1])), - netRevenue: JSON.parse(JSON.stringify(serverResponse.body.bids[0].netRevenue)), - requestId: JSON.parse(JSON.stringify(serverResponse.body.bids[0].requestId)), - ttl: JSON.parse(JSON.stringify(serverResponse.body.bids[0].ttl)), - meta: { - advertiserDomains: [], - }, - mediaType: 'banner', - }] - - it('correctly parses response', function () { - const bid01 = new BidRequestBuilder({ - mediaTypes: { - banner: { - sizes: [[1, 1]], - } - } - }).withParams().build(); + describe('buildRequests maps flattens params for legacy compat', function () { + before(function () { + const documentStub = sandbox.stub(document, 'getElementById'); + documentStub.withArgs(`${bidRequests[0].adUnitCode}`).returns({}); + }); + it('pageId flattened', function () { + let bidRequest = deepClone(bidRequests[0]); - const bidderRequest01 = new BidderRequestBuilder().build(); - const requests = spec.buildRequests([bid01], bidderRequest01); + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); - const response = spec.interpretResponse(serverResponse, requests); - expect(response).to.deep.equal(expectedResponse); + logInfo(JSON.stringify(payload)) + + expect(payload.slots[0].pageId).to.exist; + }); + after(function () { + sandbox.restore() }); + }) - it('attaches renderer', function () { - const bid01 = new BidRequestBuilder({ - mediaTypes: { - video: { - playerSize: [[640, 480]], - context: 'outstream', - } - } - }).withParams().build(); - const bidderRequest01 = new BidderRequestBuilder().build(); + describe('pageId and placementId are required params', function () { + it('invalid request', function () { + let bidRequest = deepClone(bidRequests[0]); + delete bidRequest.params + + const valid = spec.isBidRequestValid(bidRequest); + expect(valid).to.be.false; + }) - const _serverResponse = utils.deepClone(serverResponse); - _serverResponse.body.bids[0].vastXml = ''; + it('valid request', function () { + let bidRequest = deepClone(bidRequests[0]); + bidRequest.params.pageId = 42 + bidRequest.params.placementId = 42 - const _expectedResponse = utils.deepClone(expectedResponse); - _expectedResponse[0].mediaType = 'video'; - _expectedResponse[0].videoScript = JSON.parse(JSON.stringify(_serverResponse.body.bids[0].html)); - _expectedResponse[0].vastXml = JSON.parse(JSON.stringify(_serverResponse.body.bids[0].vastXml)); - delete _expectedResponse[0].ad; + const valid = spec.isBidRequestValid(bidRequest); + expect(valid).to.be.true; + }) - const requests = spec.buildRequests([bid01], bidderRequest01); - expect(requests.data.slots[0].sizes).to.deep.equal(['640x480']); + it('cwcreative must be of type string', function () { + let bidRequest = deepClone(bidRequests[0]); + bidRequest.params.pageId = 42 + bidRequest.params.placementId = 42 - const response = spec.interpretResponse(_serverResponse, requests); - expect(response[0].renderer).to.exist; - expect(response[0].renderer.url).to.equals(RENDERER_URL); - expect(response[0].renderer.loaded).to.equals(false); + const valid = spec.isBidRequestValid(bidRequest); + expect(valid).to.be.true; + }) - delete response[0].renderer; - expect(response).to.deep.equal(_expectedResponse); - }); + it('build request adds pageId', function () { + let bidRequest = deepClone(bidRequests[0]); + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + expect(payload.slots[0].pageId).to.exist; + }) + }); + + describe('process serverResponse', function () { + it('html to ad mapping', function () { + let bidResponse = deepClone(response); + const bids = spec.interpretResponse(bidResponse, {}); + + expect(bids[0].ad).to.exist; + }) }); }); diff --git a/test/spec/modules/datablocksBidAdapter_spec.js b/test/spec/modules/datablocksBidAdapter_spec.js index 8e203510b10..537b82b0e2e 100644 --- a/test/spec/modules/datablocksBidAdapter_spec.js +++ b/test/spec/modules/datablocksBidAdapter_spec.js @@ -2,7 +2,6 @@ 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(); const bid = { bidId: '2dd581a2b6281d', 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/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 78c8f2b3148..e5e779479cc 100644 --- a/test/spec/modules/eids_spec.js +++ b/test/spec/modules/eids_spec.js @@ -172,6 +172,36 @@ 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('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('liveIntentId; getValue call and NO ext', function() { const userId = { lipb: { @@ -392,7 +422,7 @@ describe('eids array generation for known sub-modules', function() { const [eid] = createEidsArray(userId); expect(eid).to.deep.equal({ - source: 'amxrtb.com', + source: 'amxdt.net', uids: [{ atype: 1, id, @@ -432,9 +462,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/emx_digitalBidAdapter_spec.js b/test/spec/modules/emx_digitalBidAdapter_spec.js index d99318b5ddc..d80d0f3e875 100644 --- a/test/spec/modules/emx_digitalBidAdapter_spec.js +++ b/test/spec/modules/emx_digitalBidAdapter_spec.js @@ -707,7 +707,7 @@ describe('emx_digital Adapter', function () { })); }); - it('returns valid advertiser domain', function () { + it('returns valid advertiser domains', function () { const bidResponse = utils.deepClone(serverResponse); let result = spec.interpretResponse({body: bidResponse}); expect(result[0].meta.advertiserDomains).to.deep.equal(expectedResponse[0].meta.advertiserDomains); @@ -724,6 +724,7 @@ describe('emx_digital Adapter', function () { expect(syncs).to.not.be.an('undefined'); expect(syncs).to.have.lengthOf(1); expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.equal('https://biddr.brealtime.com/check.html') }); it('should pass gdpr params', function () { @@ -734,6 +735,34 @@ describe('emx_digital Adapter', function () { expect(syncs).to.have.lengthOf(1); expect(syncs[0].type).to.equal('iframe'); expect(syncs[0].url).to.contains('gdpr=0'); + expect(syncs[0].url).to.equal('https://biddr.brealtime.com/check.html?gdpr=0&gdpr_consent=test') + }); + + it('should pass us_privacy string', function () { + let syncs = spec.getUserSyncs({ iframeEnabled: true }, {}, {}, { + consentString: 'test', + }); + expect(syncs).to.not.be.an('undefined'); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.contains('usp=test'); + }); + + it('should pass us_privacy and gdpr strings', function () { + let syncs = spec.getUserSyncs({ iframeEnabled: true }, {}, + { + gdprApplies: true, + consentString: 'test' + }, + { + consentString: 'test' + }); + expect(syncs).to.not.be.an('undefined'); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.contains('gdpr=1'); + expect(syncs[0].url).to.contains('usp=test'); + expect(syncs[0].url).to.equal('https://biddr.brealtime.com/check.html?gdpr=1&gdpr_consent=test&usp=test') }); }); }); 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..4622b374de5 --- /dev/null +++ b/test/spec/modules/eskimiBidAdapter_spec.js @@ -0,0 +1,155 @@ +import {expect} from 'chai'; +import {spec} from 'modules/eskimiBidAdapter.js'; + +const REQUEST = { + 'bidderCode': 'eskimi', + 'auctionId': 'auctionId-56a2-4f71-9098-720a68f2f708', + 'bidderRequestId': 'requestId', + 'bidRequest': [{ + 'bidder': 'eskimi', + 'params': { + 'placementId': 1003000, + 'accId': 123 + }, + 'sizes': [ + [300, 250] + ], + 'bidId': 'bidId1', + 'adUnitCode': 'adUnitCode1', + 'bidderRequestId': 'bidderRequestId', + 'auctionId': 'auctionId-56a2-4f71-9098-720a68f2f708' + }, + { + 'bidder': 'eskimi', + 'params': { + 'placementId': 1003001, + 'accId': 123 + }, + 'sizes': [ + [300, 250] + ], + 'bidId': 'bidId2', + 'adUnitCode': 'adUnitCode2', + 'bidderRequestId': 'bidderRequestId', + 'auctionId': 'auctionId-56a2-4f71-9098-720a68f2f708' + }], + 'start': 1487883186070, + 'auctionStart': 1487883186069, + 'timeout': 3000 +}; + +const RESPONSE = { + 'headers': null, + 'body': { + 'id': 'requestId', + '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': { + 'eskimi': { + 'brand_id': 334553, + 'auction_id': 514667951122925701, + 'bidder_id': 2, + 'bid_ad_type': 0 + } + } + } + } + ], + 'seat': 'seat' + } + ] + } +}; + +describe('Eskimi bid adapter', function () { + describe('isBidRequestValid', function () { + it('should accept request if placementId is passed', function () { + let bid = { + bidder: 'eskimi', + params: { + placementId: 123 + } + }; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + it('reject requests without params', function () { + let bid = { + bidder: 'eskimi', + params: {} + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + it('creates request data', function () { + let request = spec.buildRequests(REQUEST.bidRequest, REQUEST)[0]; + expect(request).to.exist.and.to.be.a('object'); + const payload = request.data; + expect(payload.imp[0]).to.have.property('id', REQUEST.bidRequest[0].bidId); + expect(payload.imp[1]).to.have.property('id', REQUEST.bidRequest[1].bidId); + }); + + it('has gdpr data if applicable', function () { + const req = Object.assign({}, REQUEST, { + gdprConsent: { + consentString: 'consentString', + gdprApplies: true, + } + }); + let request = spec.buildRequests(REQUEST.bidRequest, 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); + }); + }); + + describe('interpretResponse', function () { + it('has bids', function () { + let request = spec.buildRequests(REQUEST.bidRequest, REQUEST)[0]; + let bids = spec.interpretResponse(RESPONSE, request); + expect(bids).to.be.an('array').that.is.not.empty; + validateBidOnIndex(0); + + function validateBidOnIndex(index) { + expect(bids[index]).to.have.property('currency', 'USD'); + expect(bids[index]).to.have.property('requestId', RESPONSE.body.seatbid[0].bid[index].id); + 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]).to.have.property('ttl', 30); + expect(bids[index]).to.have.property('netRevenue', true); + } + }); + + it('handles empty response', function () { + let request = spec.buildRequests(REQUEST.bidRequest, REQUEST)[0]; + const EMPTY_RESP = Object.assign({}, RESPONSE, {'body': {}}); + const bids = spec.interpretResponse(EMPTY_RESP, request); + expect(bids).to.be.empty; + }); + }); +}); diff --git a/test/spec/modules/feedadBidAdapter_spec.js b/test/spec/modules/feedadBidAdapter_spec.js index 8cbd6907890..5789361d2a1 100644 --- a/test/spec/modules/feedadBidAdapter_spec.js +++ b/test/spec/modules/feedadBidAdapter_spec.js @@ -4,7 +4,7 @@ import {BANNER, NATIVE, VIDEO} from '../../../src/mediaTypes.js'; import {server} from 'test/mocks/xhr.js'; const CODE = 'feedad'; -const EXPECTED_ADAPTER_VERSION = '1.0.5'; +const EXPECTED_ADAPTER_VERSION = '1.0.6'; describe('FeedAdAdapter', function () { describe('Public API', function () { @@ -373,81 +373,67 @@ describe('FeedAdAdapter', function () { const pixelSync2 = {type: 'image', url: 'the pixel url 2'}; const iFrameSync1 = {type: 'iframe', url: 'the iFrame url 1'}; const iFrameSync2 = {type: 'iframe', url: 'the iFrame url 2'}; - const mockServerResponse = (content) => { - if (!(content instanceof Array)) { - content = [content]; - } - return content.map(it => ({body: it})); - }; - - it('should pass through the syncs out of the extension fields of the server response', function () { - const serverResponse = mockServerResponse([{ + const response1 = { + body: [{ ext: { pixels: [pixelSync1, pixelSync2], - iframes: [iFrameSync1, iFrameSync2], + iframes: [iFrameSync1] + }, + }] + }; + const response2 = { + body: [{ + ext: { + pixels: [pixelSync1], + iframes: [iFrameSync1], } - }]); - const result = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, serverResponse) + }, { + ext: { + pixels: [pixelSync2], + iframes: [iFrameSync2], + } + }] + }; + it('should pass through the syncs out of the extension fields of the server response', function () { + const result = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, [response1]) expect(result).to.deep.equal([ pixelSync1, pixelSync2, iFrameSync1, - iFrameSync2, ]); }); it('should concat the syncs of all responses', function () { - const serverResponse = mockServerResponse([{ - ext: { - pixels: [pixelSync1], - iframes: [iFrameSync2], - }, - ad: 'ad html', - cpm: 100 - }, { - ext: { - iframes: [iFrameSync1], - } - }, { - ext: { - pixels: [pixelSync2], - } - }]); - const result = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, serverResponse); + const result = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, [response1, response2]); expect(result).to.deep.equal([ pixelSync1, + pixelSync2, + iFrameSync1, iFrameSync2, + ]); + }); + + it('should concat the syncs of all bids', function () { + const result = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, [response2]); + expect(result).to.deep.equal([ + pixelSync1, iFrameSync1, pixelSync2, + iFrameSync2, ]); }); it('should filter out duplicates', function () { - const serverResponse = mockServerResponse([{ - ext: { - pixels: [pixelSync1, pixelSync1], - iframes: [iFrameSync2, iFrameSync2], - } - }, { - ext: { - iframes: [iFrameSync2, iFrameSync2], - } - }]); - const result = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, serverResponse); + const result = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, [response1, response1]); expect(result).to.deep.equal([ pixelSync1, - iFrameSync2, + pixelSync2, + iFrameSync1, ]); }); it('should not include iFrame syncs if the option is disabled', function () { - const serverResponse = mockServerResponse([{ - ext: { - pixels: [pixelSync1, pixelSync2], - iframes: [iFrameSync1, iFrameSync2], - } - }]); - const result = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: true}, serverResponse); + const result = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: true}, [response1]); expect(result).to.deep.equal([ pixelSync1, pixelSync2, @@ -455,52 +441,36 @@ describe('FeedAdAdapter', function () { }); it('should not include pixel syncs if the option is disabled', function () { - const serverResponse = mockServerResponse([{ - ext: { - pixels: [pixelSync1, pixelSync2], - iframes: [iFrameSync1, iFrameSync2], - } - }]); - const result = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: false}, serverResponse); + const result = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: false}, [response1]); expect(result).to.deep.equal([ iFrameSync1, - iFrameSync2, ]); }); it('should not include any syncs if the sync options are disabled or missing', function () { - const serverResponse = mockServerResponse([{ - ext: { - pixels: [pixelSync1, pixelSync2], - iframes: [iFrameSync1, iFrameSync2], - } - }]); - const result = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: false}, serverResponse); + const result = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: false}, [response1]); expect(result).to.deep.equal([]); }); it('should handle empty responses', function () { - const serverResponse = mockServerResponse([]); - const result = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, serverResponse) + const result = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, []) expect(result).to.deep.equal([]); }); it('should not throw if the server response is weird', function () { const responses = [ - mockServerResponse(null), - mockServerResponse('null'), - mockServerResponse(1234), - mockServerResponse({}), - mockServerResponse([{}, 123]), + {body: null}, + {body: 'null'}, + {body: 1234}, + {body: {}}, + {body: [{}, 123]}, ]; - responses.forEach(it => { - expect(() => spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, it)).not.to.throw; - }); + expect(() => spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, responses)).to.not.throw(); }); it('should return empty array if the body extension is null', function () { - const response = mockServerResponse({ext: null}); - const result = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, response); + const response = {body: [{ext: null}]}; + const result = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, [response]); expect(result).to.deep.equal([]); }); }); 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..e9681c05314 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,9 +305,15 @@ 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', + }, + ], } }; @@ -338,4 +402,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/freewheel-sspBidAdapter_spec.js b/test/spec/modules/freewheel-sspBidAdapter_spec.js index 7ef576fc7ec..d8dc6b18b8f 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); @@ -84,7 +86,8 @@ describe('freewheelSSP BidAdapter Test', () => { { 'bidder': 'freewheel-ssp', 'params': { - 'zoneId': '277225' + 'zoneId': '277225', + 'bidfloor': 2.00, }, 'adUnitCode': 'adunit-code', 'mediaTypes': { @@ -114,21 +117,63 @@ describe('freewheelSSP BidAdapter Test', () => { } ]; + it('should get bidfloor value from params if no getFloor method', () => { + const request = spec.buildRequests(bidRequests); + const payload = request[0].data; + expect(payload._fw_bidfloor).to.equal(2.00); + expect(payload._fw_bidfloorcur).to.deep.equal('USD'); + }); + + it('should get bidfloor value from getFloor method if available', () => { + const bidRequest = bidRequests[0]; + bidRequest.getFloor = () => ({ currency: 'USD', floor: 1.16 }); + const request = spec.buildRequests(bidRequests); + const payload = request[0].data; + expect(payload._fw_bidfloor).to.equal(1.16); + 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 }); + const request = spec.buildRequests(bidRequests); + const payload = request[0].data; + expect(payload._fw_bidfloor).to.equal(0); + expect(payload._fw_bidfloorcur).to.deep.equal(''); + }); + it('should add parameters to the tag', () => { const request = spec.buildRequests(bidRequests); const payload = request[0].data; expect(payload.reqType).to.equal('AdsSetup'); - expect(payload.protocolVersion).to.equal('2.0'); + expect(payload.protocolVersion).to.equal('4.2'); expect(payload.zoneId).to.equal('277225'); 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 () { const request = spec.buildRequests(bidRequests); const payload = request[0].data; - expect(payload.schain).to.deep.equal(bidRequests[0].schain) + expect(payload.schain).to.deep.equal('{\"ver\":\"1.0\",\"complete\":1,\"nodes\":[{\"asi\":\"example.com\",\"sid\":\"0\",\"hp\":1,\"rid\":\"bidrequestid\",\"domain\":\"example.com\"}]}'); }); it('sends bid request to ENDPOINT via GET', () => { @@ -144,7 +189,7 @@ describe('freewheelSSP BidAdapter Test', () => { const request = spec.buildRequests(bidRequests, bidderRequest); const payload = request[0].data; expect(payload.reqType).to.equal('AdsSetup'); - expect(payload.protocolVersion).to.equal('2.0'); + expect(payload.protocolVersion).to.equal('4.2'); expect(payload.zoneId).to.equal('277225'); expect(payload.componentId).to.equal('prebid'); expect(payload.componentSubId).to.equal('mustang'); @@ -164,7 +209,7 @@ describe('freewheelSSP BidAdapter Test', () => { const request = spec.buildRequests(bidRequests, bidderRequest); const payload = request[0].data; expect(payload.reqType).to.equal('AdsSetup'); - expect(payload.protocolVersion).to.equal('2.0'); + expect(payload.protocolVersion).to.equal('4.2'); expect(payload.zoneId).to.equal('277225'); expect(payload.componentId).to.equal('prebid'); expect(payload.componentSubId).to.equal('mustang'); @@ -179,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', () => { @@ -207,11 +281,18 @@ 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('instream'); ; + expect(payload.video_placement).to.equal(1); + }); + it('should add parameters to the tag', () => { const request = spec.buildRequests(bidRequests); const payload = request[0].data; expect(payload.reqType).to.equal('AdsSetup'); - expect(payload.protocolVersion).to.equal('2.0'); + expect(payload.protocolVersion).to.equal('4.2'); expect(payload.zoneId).to.equal('277225'); expect(payload.componentId).to.equal('prebid'); expect(payload.componentSubId).to.equal('mustang'); @@ -231,7 +312,7 @@ describe('freewheelSSP BidAdapter Test', () => { const request = spec.buildRequests(bidRequests, bidderRequest); const payload = request[0].data; expect(payload.reqType).to.equal('AdsSetup'); - expect(payload.protocolVersion).to.equal('2.0'); + expect(payload.protocolVersion).to.equal('4.2'); expect(payload.zoneId).to.equal('277225'); expect(payload.componentId).to.equal('prebid'); expect(payload.componentSubId).to.equal('mustang'); @@ -251,7 +332,7 @@ describe('freewheelSSP BidAdapter Test', () => { const request = spec.buildRequests(bidRequests, bidderRequest); const payload = request[0].data; expect(payload.reqType).to.equal('AdsSetup'); - expect(payload.protocolVersion).to.equal('2.0'); + expect(payload.protocolVersion).to.equal('4.2'); expect(payload.zoneId).to.equal('277225'); expect(payload.componentId).to.equal('prebid'); expect(payload.componentSubId).to.equal('mustang'); @@ -266,7 +347,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-' @@ -274,6 +355,36 @@ describe('freewheelSSP BidAdapter Test', () => { }); }) + describe('buildRequestsForVideoWithContextAndPlacement', () => { + let bidRequests = [ + { + 'bidder': 'freewheel-ssp', + 'params': { + 'zoneId': '277225' + }, + 'adUnitCode': 'adunit-code', + 'mediaTypes': { + 'video': { + 'context': 'outstream', + 'placement': 2, + '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); + }); + }) + describe('interpretResponseForBanner', () => { let bidRequests = [ { @@ -337,7 +448,7 @@ describe('freewheelSSP BidAdapter Test', () => { } ]; - let response = '' + + let response = '' + '' + ' ' + ' Adswizz' + @@ -422,7 +533,7 @@ describe('freewheelSSP BidAdapter Test', () => { it('handles nobid responses', () => { var request = spec.buildRequests(formattedBidRequests); - let response = ''; + let response = ''; let result = spec.interpretResponse(response, request[0]); expect(result.length).to.equal(0); @@ -503,7 +614,7 @@ describe('freewheelSSP BidAdapter Test', () => { } ]; - let response = '' + + let response = '' + '' + ' ' + ' Adswizz' + @@ -599,7 +710,7 @@ describe('freewheelSSP BidAdapter Test', () => { it('handles nobid responses', () => { var request = spec.buildRequests(formattedBidRequests); - let response = ''; + let response = ''; let result = spec.interpretResponse(response, request[0]); expect(result.length).to.equal(0); diff --git a/test/spec/modules/gdprEnforcement_spec.js b/test/spec/modules/gdprEnforcement_spec.js index 8d58990bb66..941f2b3c8df 100644 --- a/test/spec/modules/gdprEnforcement_spec.js +++ b/test/spec/modules/gdprEnforcement_spec.js @@ -1,26 +1,33 @@ import { deviceAccessHook, - setEnforcementConfig, - userSyncHook, - userIdHook, - makeBidRequestsHook, - validateRules, + enableAnalyticsHook, enforcementRules, + getGvlid, + getGvlidFromAnalyticsAdapter, + makeBidRequestsHook, purpose1Rule, purpose2Rule, - enableAnalyticsHook, - getGvlid, - internal, STRICT_STORAGE_ENFORCEMENT + setEnforcementConfig, + STRICT_STORAGE_ENFORCEMENT, + userIdHook, + userSyncHook, + 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_CORE, + 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'; describe('gdpr enforcement', function () { let nextFnSpy; @@ -100,6 +107,7 @@ describe('gdpr enforcement', function () { } } }; + let gvlids; before(() => { hook.ready(); @@ -111,31 +119,28 @@ describe('gdpr enforcement', function () { adapterManager.makeBidRequests.getHooks({ hook: makeBidRequestsHook }).remove(); }) - describe('deviceAccessHook', function () { - let adapterManagerStub; + beforeEach(() => { + gvlids = {}; + sinon.stub(GDPR_GVLIDS, 'get').callsFake((name) => ({gvlid: gvlids[name], modules: {}})); + }); - function getBidderSpec(gvlid) { - return { - getSpec: () => { - return { - gvlid - } - } - } - } + afterEach(() => { + GDPR_GVLIDS.get.restore(); + }); + describe('deviceAccessHook', function () { 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, @@ -161,8 +166,10 @@ describe('gdpr enforcement', function () { }); 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)); + Object.assign(gvlids, { + appnexus: 1, + rubicon: 5 + }); setEnforcementConfig({ gdpr: { rules: [{ @@ -179,14 +186,16 @@ describe('gdpr enforcement', function () { consentData.apiVersion = 2; gdprDataHandlerStub.returns(consentData); - deviceAccessHook(nextFnSpy, 1, 'appnexus'); - deviceAccessHook(nextFnSpy, 5, 'rubicon'); + deviceAccessHook(nextFnSpy, MODULE_TYPE_BIDDER, 'appnexus'); + deviceAccessHook(nextFnSpy, MODULE_TYPE_BIDDER, 'rubicon'); expect(logWarnSpy.callCount).to.equal(0); }); 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: [{ @@ -202,13 +211,13 @@ describe('gdpr enforcement', function () { consentData.apiVersion = 2; gdprDataHandlerStub.returns(consentData); - deviceAccessHook(nextFnSpy, 1, 'appnexus'); - deviceAccessHook(nextFnSpy, 3, 'rubicon'); + deviceAccessHook(nextFnSpy, MODULE_TYPE_BIDDER, 'appnexus'); + deviceAccessHook(nextFnSpy, MODULE_TYPE_BIDDER, 'rubicon'); expect(logWarnSpy.callCount).to.equal(1); }); 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: [{ @@ -225,13 +234,13 @@ describe('gdpr enforcement', function () { consentData.apiVersion = 2; gdprDataHandlerStub.returns(consentData); - deviceAccessHook(nextFnSpy, 1, 'appnexus'); + deviceAccessHook(nextFnSpy, MODULE_TYPE_BIDDER, 'appnexus'); expect(nextFnSpy.calledOnce).to.equal(true); let result = { hasEnforcementHook: true, valid: true } - sinon.assert.calledWith(nextFnSpy, 1, 'appnexus', result); + sinon.assert.calledWith(nextFnSpy, MODULE_TYPE_BIDDER, 'appnexus', result); }); it('should use gvlMapping set by publisher', function() { @@ -256,13 +265,13 @@ describe('gdpr enforcement', function () { consentData.apiVersion = 2; gdprDataHandlerStub.returns(consentData); - deviceAccessHook(nextFnSpy, 1, 'appnexus'); + deviceAccessHook(nextFnSpy, MODULE_TYPE_BIDDER, 'appnexus'); expect(nextFnSpy.calledOnce).to.equal(true); let result = { hasEnforcementHook: true, valid: true } - sinon.assert.calledWith(nextFnSpy, 4, 'appnexus', result); + sinon.assert.calledWith(nextFnSpy, MODULE_TYPE_BIDDER, 'appnexus', result); config.resetConfig(); }); @@ -291,13 +300,13 @@ describe('gdpr enforcement', function () { consentData.apiVersion = 2; gdprDataHandlerStub.returns(consentData); - deviceAccessHook(nextFnSpy, 1, 'appnexus'); + deviceAccessHook(nextFnSpy, MODULE_TYPE_BIDDER, 'appnexus'); expect(nextFnSpy.calledOnce).to.equal(true); let result = { hasEnforcementHook: true, valid: true } - sinon.assert.calledWith(nextFnSpy, 4, 'appnexus', result); + sinon.assert.calledWith(nextFnSpy, MODULE_TYPE_BIDDER, 'appnexus', result); config.resetConfig(); curBidderStub.restore(); }); @@ -310,9 +319,9 @@ describe('gdpr enforcement', function () { } gdprDataHandlerStub.returns(consentData); const validate = sinon.stub().callsFake(() => false); - deviceAccessHook(nextFnSpy, VENDORLESS_GVLID, 'mockModule', undefined, {validate}); + deviceAccessHook(nextFnSpy, MODULE_TYPE_CORE, 'mockModule', undefined, {validate}); sinon.assert.callCount(validate, 0); - sinon.assert.calledWith(nextFnSpy, VENDORLESS_GVLID, 'mockModule', {hasEnforcementHook: true, valid: true}); + sinon.assert.calledWith(nextFnSpy, MODULE_TYPE_CORE, 'mockModule', {hasEnforcementHook: true, valid: true}); }) }); @@ -354,23 +363,11 @@ describe('gdpr enforcement', function () { gdprDataHandlerStub.returns(consentData); curBidderStub.returns('sampleBidder1'); - adapterManagerStub.withArgs('sampleBidder1').returns({ - getSpec: function () { - return { - 'gvlid': 1 - } - } - }); + gvlids.sampleBidder1 = 1; userSyncHook(nextFnSpy); curBidderStub.returns('sampleBidder2'); - adapterManagerStub.withArgs('sampleBidder2').returns({ - getSpec: function () { - return { - 'gvlid': 3 - } - } - }); + gvlids.sampleBidder2 = 3; userSyncHook(nextFnSpy); expect(nextFnSpy.calledTwice).to.equal(true); }); @@ -393,23 +390,11 @@ describe('gdpr enforcement', function () { gdprDataHandlerStub.returns(consentData); curBidderStub.returns('sampleBidder1'); - adapterManagerStub.withArgs('sampleBidder1').returns({ - getSpec: function () { - return { - 'gvlid': 1 - } - } - }); + gvlids.sampleBidder1 = 1; userSyncHook(nextFnSpy); curBidderStub.returns('sampleBidder2'); - adapterManagerStub.withArgs('sampleBidder2').returns({ - getSpec: function () { - return { - 'gvlid': 3 - } - } - }); + gvlids.sampleBidder2 = 3; userSyncHook(nextFnSpy); expect(nextFnSpy.calledOnce).to.equal(true); expect(logWarnSpy.callCount).to.equal(1); @@ -433,23 +418,11 @@ describe('gdpr enforcement', function () { gdprDataHandlerStub.returns(consentData); curBidderStub.returns('sampleBidder1'); - adapterManagerStub.withArgs('sampleBidder1').returns({ - getSpec: function () { - return { - 'gvlid': 1 - } - } - }); + gvlids.sampleBidder1 = 1; userSyncHook(nextFnSpy); curBidderStub.returns('sampleBidder2'); - adapterManagerStub.withArgs('sampleBidder2').returns({ - getSpec: function () { - return { - 'gvlid': 3 - } - } - }); + gvlids.sampleBidder2 = 3; userSyncHook(nextFnSpy); expect(nextFnSpy.calledTwice).to.equal(true); expect(logWarnSpy.callCount).to.equal(0); @@ -486,6 +459,7 @@ describe('gdpr enforcement', function () { name: 'sampleUserId' } }] + gvlids.sampleUserId = 1; userIdHook(nextFnSpy, submodules, consentData); // Should pass back hasValidated flag since version 2 const args = nextFnSpy.getCalls()[0].args; @@ -501,6 +475,7 @@ describe('gdpr enforcement', function () { name: 'sampleUserId' } }]; + gvlids.sampleUserId = 1; let consentData = null; userIdHook(nextFnSpy, submodules, consentData); // Should not pass back hasValidated flag since version 2 @@ -537,6 +512,10 @@ describe('gdpr enforcement', function () { name: 'sampleUserId1' } }] + Object.assign(gvlids, { + sampleUserId: 1, + sampleUserId1: 3 + }); userIdHook(nextFnSpy, submodules, consentData); expect(logWarnSpy.callCount).to.equal(1); let expectedSubmodules = [{ @@ -602,20 +581,9 @@ describe('gdpr enforcement', function () { 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 } - } + Object.assign(gvlids, { + bidder_1: 4, + bidder_2: 5, }); makeBidRequestsHook(nextFnSpy, MOCK_AD_UNITS, []); @@ -660,21 +628,10 @@ describe('gdpr enforcement', function () { 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 } - } - }); + Object.assign(gvlids, { + bidder_1: 4, + bidder_2: 5, + }) makeBidRequestsHook(nextFnSpy, MOCK_AD_UNITS, []); @@ -771,9 +728,11 @@ describe('gdpr enforcement', function () { consentData.gdprApplies = true; gdprDataHandlerStub.returns(consentData); - adapterManagerStub.withArgs('analyticsAdapter_A').returns({ gvlid: 3 }); - adapterManagerStub.withArgs('analyticsAdapter_B').returns({ gvlid: 5 }); - adapterManagerStub.withArgs('analyticsAdapter_C').returns({ gvlid: 1 }); + Object.assign(gvlids, { + analyticsAdapter_A: 3, + analyticsAdapter_B: 5, + analyticsAdapter_C: 1 + }); enableAnalyticsHook(nextFnSpy, MOCK_ANALYTICS_ADAPTER_CONFIG); @@ -1142,13 +1101,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 +1115,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_CORE, 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 +1183,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/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/greenbidsAnalyticsAdapter_spec.js b/test/spec/modules/greenbidsAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..b3cbf4c47c2 --- /dev/null +++ b/test/spec/modules/greenbidsAnalyticsAdapter_spec.js @@ -0,0 +1,418 @@ +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); + expect(greenbidsAnalyticsAdapter.getAnalyticsOptions().sampled).to.equal(false); + }); + + it('should not enable Analytics when pbuid is missing', function () { + const configOptions = { + options: { + } + }; + const validConfig = greenbidsAnalyticsAdapter.initConfig(configOptions); + expect(validConfig).to.equal(false); + }); + it('should fall back to default value when sampling factor is not number', function () { + const configOptions = { + options: { + pbuid: pbuid, + sampling: 'string', + } + }; + greenbidsAnalyticsAdapter.enableAnalytics({ + provider: 'greenbidsAnalytics', + options: configOptions + }); + + expect(greenbidsAnalyticsAdapter.getAnalyticsOptions().sampled).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..2ef604dd097 100644 --- a/test/spec/modules/gridBidAdapter_spec.js +++ b/test/spec/modules/gridBidAdapter_spec.js @@ -480,6 +480,14 @@ describe('TheMediaGrid Adapter', function () { 'h': 600, 'protocols': [1, 2, 3], 'mimes': ['video/mp4', 'video/webm', 'application/javascript', 'video/ogg'], + 'context': 'instream', + 'maxduration': 30, + 'minduration': 0, + 'api': [1, 2], + 'skip': 1, + 'placement': 1, + 'playbackmethod': 1, + 'startdelay': 0 } }, { 'id': bidRequests[3].bidId, @@ -575,6 +583,30 @@ 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 = Object.assign({ortb2: {regs: {gpp: consentString, gpp_sid: [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('if userId is present payload must have user.ext param with right keys', function () { const eids = [ { @@ -667,14 +699,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 +817,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 +847,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,14 +881,17 @@ 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); @@ -886,10 +907,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 }); }); 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/gumgumBidAdapter_spec.js b/test/spec/modules/gumgumBidAdapter_spec.js index a2dacb16b73..71f356a83ae 100644 --- a/test/spec/modules/gumgumBidAdapter_spec.js +++ b/test/spec/modules/gumgumBidAdapter_spec.js @@ -485,6 +485,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/hadronIdSystem_spec.js b/test/spec/modules/hadronIdSystem_spec.js index ca9eadc7fd4..c998ef2cf14 100644 --- a/test/spec/modules/hadronIdSystem_spec.js +++ b/test/spec/modules/hadronIdSystem_spec.js @@ -24,7 +24,7 @@ describe('HadronIdSystem', function () { const request = server.requests[0]; expect(request.url).to.eq(`https://id.hadron.ad.gt/api/v1/pbhid?partner_id=0&_it=prebid`); request.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify({ hadronId: 'testHadronId1' })); - expect(callbackSpy.lastCall.lastArg).to.deep.equal({hadronId: 'testHadronId1'}); + expect(callbackSpy.lastCall.lastArg).to.deep.equal({ id: { hadronId: 'testHadronId1' } }); }); it('gets a cached hadronid', function() { @@ -33,10 +33,8 @@ describe('HadronIdSystem', function () { }; getDataFromLocalStorageStub.withArgs('auHadronId').returns('tstCachedHadronId1'); - const callbackSpy = sinon.spy(); - const callback = hadronIdSubmodule.getId(config).callback; - callback(callbackSpy); - expect(callbackSpy.lastCall.lastArg).to.deep.equal({hadronId: 'tstCachedHadronId1'}); + const result = hadronIdSubmodule.getId(config); + expect(result).to.deep.equal({ id: { hadronId: 'tstCachedHadronId1' } }); }); it('allows configurable id url', function() { @@ -51,7 +49,7 @@ describe('HadronIdSystem', function () { const request = server.requests[0]; expect(request.url).to.eq('https://hadronid.publync.com?partner_id=0&_it=prebid'); request.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify({ hadronId: 'testHadronId1' })); - expect(callbackSpy.lastCall.lastArg).to.deep.equal({hadronId: 'testHadronId1'}); + expect(callbackSpy.lastCall.lastArg).to.deep.equal({ id: { hadronId: 'testHadronId1' } }); }); }); }); diff --git a/test/spec/modules/holidBidAdapter_spec.js b/test/spec/modules/holidBidAdapter_spec.js index e18a5ac58f4..e55befd213a 100644 --- a/test/spec/modules/holidBidAdapter_spec.js +++ b/test/spec/modules/holidBidAdapter_spec.js @@ -161,5 +161,34 @@ describe('holidBidAdapterTests', () => { expect(userSyncs).to.deep.equal(expectedUserSyncs) }) + + it('should return empty user syncs when responsetimemillis is not defined', () => { + const optionsType = { + iframeEnabled: true, + pixelEnabled: true, + } + const serverResponse = [ + { + body: { + ext: {}, + }, + }, + ] + const gdprConsent = { + gdprApplies: 1, + consentString: 'dkj49Sjmfjuj34as:12jaf90123hufabidfy9u23brfpoig', + } + const uspConsent = 'mkjvbiniwot4827obfoy8sdg8203gb' + const expectedUserSyncs = [] + + const userSyncs = spec.getUserSyncs( + optionsType, + serverResponse, + gdprConsent, + uspConsent + ) + + expect(userSyncs).to.deep.equal(expectedUserSyncs) + }) }) }) diff --git a/test/spec/modules/id5IdSystem_spec.js b/test/spec/modules/id5IdSystem_spec.js index 8c0f8ad9cf3..51954f76356 100644 --- a/test/spec/modules/id5IdSystem_spec.js +++ b/test/spec/modules/id5IdSystem_spec.js @@ -52,6 +52,22 @@ describe('ID5 ID System', function () { 'signature': ID5_RESPONSE_SIGNATURE, 'link_type': 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 +152,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 +221,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 +295,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 +345,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 +723,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 +880,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() { 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/improvedigitalBidAdapter_spec.js b/test/spec/modules/improvedigitalBidAdapter_spec.js index 400b9145e0b..1c3bd3197d0 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'; @@ -278,12 +277,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 +469,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 +578,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 +595,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 +620,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 +634,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 +681,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 +720,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 +1249,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 = '

Ad from IncrementX

', slotBidId: 'bid-id-123456', + adType: '1', + settings: '1,2', nurl: 'htt://nurl.com', statusText: 'Success' } @@ -80,6 +82,8 @@ describe('IncrementX', function () { requestId: 'bid-id-123456', cpm: '0.7', currency: 'USD', + adType: '1', + settings: '1,2', netRevenue: false, width: '300', height: '250', 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..79b3d6811f4 --- /dev/null +++ b/test/spec/modules/ivsBidAdapter_spec.js @@ -0,0 +1,197 @@ +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.equal(bidderRequest.id, validBidderRequest.auctionId); + assert.ok(bidderRequest.site); + assert.ok(bidderRequest.source); + 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 0de300e4a32..149c4c44c21 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 } 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', @@ -658,7 +658,7 @@ describe('IndexexchangeAdapter', function () { } }; - const DEFAULT_VIDEO_BID_RESPONSE_WITH_MTYPE_SET = { + const DEFAULT_VIDEO_BID_RESPONSE_WITH_XML_ADM = { cur: 'USD', id: '1aa2bb3cc4de', seatbid: [ @@ -675,7 +675,6 @@ describe('IndexexchangeAdapter', function () { mtype: 2, adm: ' Test In-Stream Video { - sandbox.stub(storage, 'localStorageIsEnabled').returns(true); - serverResponse.body.ext.features.pbjs_use_32kb_size_limit = { - activated: true - }; - FEATURE_TOGGLES.setFeatureToggles(serverResponse); - const bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); - bid.bidderRequestId = Array(10000).join('#'); - - expect(spec.isBidRequestValid(bid)).to.be.true; - spec.buildRequests([bid], {}); - const lsData = JSON.parse(storage.getDataFromLocalStorage(LOCAL_STORAGE_FEATURE_TOGGLES_KEY)); - expect(lsData.features.pbjs_use_32kb_size_limit.activated).to.be.true; - }); - - it('6 ad units should generate only 2 requests if 32kb size limit FT is enabled', function () { + it('6 ad units should generate only 1 request if buildRequestV2 FT is enabled', function () { sandbox.stub(storage, 'localStorageIsEnabled').returns(true); - serverResponse.body.ext.features.pbjs_use_32kb_size_limit = { - activated: true - }; - serverResponse.body.ext.features.pbjs_enable_post = { + serverResponse.body.ext.features.pbjs_use_buildRequestV2 = { activated: true }; FEATURE_TOGGLES.setFeatureToggles(serverResponse); @@ -3549,47 +3683,106 @@ describe('IndexexchangeAdapter', function () { const requests = spec.buildRequests([bid1, bid2, bid3, bid4, bid5, bid6], DEFAULT_OPTION); expect(requests).to.be.an('array'); - // 32KB size limit causes only 2 requests to get generated. - expect(requests).to.have.lengthOf(2); + // buildRequestv2 enabled causes only 1 requests to get generated. + expect(requests).to.have.lengthOf(1); for (let request of requests) { expect(request.method).to.equal('POST'); } }); - it('4 ad units should generate only 1 requests if 32kb size limit FT is enabled', function () { + it('1 request with 2 ad units, buildRequestV2 enabled', function () { sandbox.stub(storage, 'localStorageIsEnabled').returns(true); - serverResponse.body.ext.features.pbjs_use_32kb_size_limit = { - activated: true - }; - serverResponse.body.ext.features.pbjs_enable_post = { + serverResponse.body.ext.features.pbjs_use_buildRequestV2 = { activated: true }; FEATURE_TOGGLES.setFeatureToggles(serverResponse); - const bid1 = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); - bid1.mediaTypes.banner.sizes = LARGE_SET_OF_SIZES; - bid1.params.siteId = '121'; - bid1.adUnitCode = 'div-gpt-1' - bid1.transactionId = 'tr1'; - bid1.bidId = '2f6g5s5e'; + const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); + bid.mediaTypes.banner.sizes = LARGE_SET_OF_SIZES; + bid.params.siteId = '124'; + bid.adUnitCode = 'div-gpt-1' + bid.transactionId = '152e36d1-1241-4242-t35e-y1dv34d12315'; + bid.bidId = '2f6g5s5e'; - const bid2 = utils.deepClone(bid1); - bid2.transactionId = 'tr2'; + const requests = spec.buildRequests([bid, DEFAULT_BANNER_VALID_BID[0]], DEFAULT_OPTION); + expect(requests).to.be.an('array'); + expect(requests).to.have.lengthOf(1); + }); - const bid3 = utils.deepClone(bid1); - bid3.transactionId = 'tr3'; + 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); + }); - const bid4 = utils.deepClone(bid1); - bid4.transactionId = 'tr4'; + 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('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; + }); - const requests = spec.buildRequests([bid1, bid2, bid3, bid4], DEFAULT_OPTION); + 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; + }); - expect(requests).to.be.an('array'); - // 32KB size limit causes only 1 requests to get generated. - expect(requests).to.have.lengthOf(1); - for (let request of requests) { - expect(request.method).to.equal('POST'); + 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('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 } + }); + + 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 } + }); }); }); @@ -3670,48 +3863,6 @@ describe('IndexexchangeAdapter', function () { expect(JSON.parse(localStorageValues[key])).to.deep.equal({ [TODAY]: { [ERROR_CODES.BID_FLOOR_INVALID_FORMAT]: 1 } }); }); - 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]); - - config.setConfig({ - ix: { - firstPartyData: { - cd: Array(1700).join('#') - } - } - }); - - 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 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(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('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(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 } }); - }); - it('should log ERROR_CODES.VIDEO_DURATION_INVALID in LocalStorage when there is logError called.', () => { const bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); bid.params.video.minduration = 1; 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 565b83704fa..d692cc67e26 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,45 @@ 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', + transactionId: '10101', + 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 +127,76 @@ describe('kargo adapter tests', function () { version: [ '99', '0', '0', '0' ] } ], - mobile: 0, - model: '' + mobile: 1, + model: 'model', + source: 1, } } + }, + ortb2Imp: { + ext: { + 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', + transactionId: '20202', + sizes: [[320, 50], [300, 250], [300, 600]], + mediaTypes: { + video: { + sizes: [[320, 50], [300, 50]] + } + }, + bidRequestsCount: 0, + bidderRequestsCount: 0, + bidderWinsCount: 0, + ortb2Imp: { + ext: { + 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', + transactionId: '30303', + sizes: [[320, 50], [300, 250], [300, 600]], + mediaTypes: { + native: { + sizes: [[320, 50], [300, 50]] + } + }, + ortb2Imp: { + ext: { + 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 }, }, - userIDs: { + 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' + }, + 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)); }); }); @@ -556,6 +711,17 @@ describe('kargo adapter tests', function () { mediaType: 'video', metadata: {}, currency: 'EUR' + }, + 6: { + id: 'bar', + cpm: 2.5, + adm: '', + admUrl: 'https://foobar.com/vast_adm', + width: 300, + height: 250, + mediaType: 'video', + metadata: {}, + currency: 'EUR' } }}, { currency: 'USD', @@ -584,14 +750,19 @@ describe('kargo adapter tests', function () { params: { placementId: 'bar' } + }, { + bidId: 6, + params: { + placementId: 'bar' + } }] }); var expectation = [{ + ad: '
', requestId: '1', cpm: 3, width: 320, height: 50, - ad: '
', ttl: 300, creativeId: 'foo', dealId: undefined, @@ -603,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', @@ -620,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, @@ -635,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, @@ -653,7 +824,6 @@ describe('kargo adapter tests', function () { cpm: 2.5, width: 300, height: 250, - ad: '', vastXml: '', ttl: 300, creativeId: 'bar', @@ -664,6 +834,21 @@ describe('kargo adapter tests', function () { meta: { mediaType: 'video' } + }, { + requestId: '6', + cpm: 2.5, + width: 300, + height: 250, + vastUrl: 'https://foobar.com/vast_adm', + ttl: 300, + creativeId: 'bar', + dealId: undefined, + netRevenue: true, + currency: 'EUR', + mediaType: 'video', + meta: { + mediaType: 'video' + } }]; expect(resp).to.deep.equal(expectation); }); @@ -707,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() { @@ -723,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; } @@ -770,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(); @@ -798,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/koblerBidAdapter_spec.js b/test/spec/modules/koblerBidAdapter_spec.js index 5bcf62ff95d..5fb0bef726e 100644 --- a/test/spec/modules/koblerBidAdapter_spec.js +++ b/test/spec/modules/koblerBidAdapter_spec.js @@ -366,26 +366,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; diff --git a/test/spec/modules/kueezRtbBidAdapter_spec.js b/test/spec/modules/kueezRtbBidAdapter_spec.js index 30b2a46cefb..fc7219d2ee7 100644 --- a/test/spec/modules/kueezRtbBidAdapter_spec.js +++ b/test/spec/modules/kueezRtbBidAdapter_spec.js @@ -1,4 +1,4 @@ -import { expect } from 'chai'; +import {expect} from 'chai'; import { spec as adapter, SUPPORTED_ID_SYSTEMS, @@ -13,9 +13,10 @@ import { getUniqueDealId, } from 'modules/kueezRtbBidAdapter.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'; const SUB_DOMAIN = 'exchange'; @@ -36,9 +37,18 @@ const BID = { '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': '0123456789' + } + } }; const VIDEO_BID = { @@ -46,6 +56,10 @@ const VIDEO_BID = { 'adUnitCode': '63550ad1ff6642d368cba59dh5884270560', 'bidderRequestId': '12a8ae9ada9c13', 'transactionId': '56e184c6-bde9-497b-b9b9-cf47a61381ee', + 'auctionId': 'auction_id', + 'bidRequestsCount': 4, + 'bidderRequestsCount': 3, + 'bidderWinsCount': 1, 'schain': 'a0819c69-005b-41ed-af06-1be1e0aefefc', 'params': { 'subDomain': SUB_DOMAIN, @@ -78,10 +92,36 @@ 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': '' + } + } } }; @@ -134,7 +174,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 +253,9 @@ describe('KueezRtbBidAdapter', 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 +266,42 @@ describe('KueezRtbBidAdapter', 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 +320,17 @@ describe('KueezRtbBidAdapter', 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 +339,33 @@ describe('KueezRtbBidAdapter', 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 +379,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', @@ -296,7 +394,7 @@ describe('KueezRtbBidAdapter', 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 +403,7 @@ describe('KueezRtbBidAdapter', 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.kueezrtb.com/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=' @@ -313,7 +411,7 @@ describe('KueezRtbBidAdapter', 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.kueezrtb.com/api/sync/image/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=', @@ -329,12 +427,12 @@ describe('KueezRtbBidAdapter', 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 +455,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); @@ -394,11 +505,11 @@ describe('KueezRtbBidAdapter', function () { 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 +528,18 @@ describe('KueezRtbBidAdapter', 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 +599,7 @@ describe('KueezRtbBidAdapter', 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 +615,8 @@ describe('KueezRtbBidAdapter', 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/kulturemediaBidAdapter_spec.js b/test/spec/modules/kulturemediaBidAdapter_spec.js new file mode 100644 index 00000000000..1872f6c171a --- /dev/null +++ b/test/spec/modules/kulturemediaBidAdapter_spec.js @@ -0,0 +1,613 @@ +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: '', + bidderCode: spec.code, + 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/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 4efb4f4e84d..5a92110abb4 100644 --- a/test/spec/modules/limelightDigitalBidAdapter_spec.js +++ b/test/spec/modules/limelightDigitalBidAdapter_spec.js @@ -10,7 +10,12 @@ describe('limelightDigitalAdapter', function () { host: 'exchange.ortb.net', adUnitId: 123, adUnitType: 'banner', - publisherId: 'perfectPublisher' + publisherId: 'perfectPublisher', + custom1: 'custom1', + custom2: 'custom2', + custom3: 'custom3', + custom4: 'custom4', + custom5: 'custom5' }, placementCode: 'placement_0', auctionId: '74f78609-a92d-4cf1-869f-1b244bbfb5d2', @@ -49,7 +54,12 @@ describe('limelightDigitalAdapter', function () { params: { host: 'ads.project-limelight.com', adUnitId: 456, - adUnitType: 'banner' + adUnitType: 'banner', + custom1: 'custom1', + custom2: 'custom2', + custom3: 'custom3', + custom4: 'custom4', + custom5: 'custom5' }, placementCode: 'placement_1', auctionId: '482f88de-29ab-45c8-981a-d25e39454a34', @@ -90,7 +100,12 @@ describe('limelightDigitalAdapter', function () { host: 'exchange.ortb.net', adUnitId: 789, adUnitType: 'video', - publisherId: 'secondPerfectPublisher' + publisherId: 'secondPerfectPublisher', + custom1: 'custom1', + custom2: 'custom2', + custom3: 'custom3', + custom4: 'custom4', + custom5: 'custom5' }, placementCode: 'placement_2', auctionId: 'e4771143-6aa7-41ec-8824-ced4342c96c8', @@ -128,7 +143,12 @@ describe('limelightDigitalAdapter', function () { params: { host: 'exchange.ortb.net', adUnitId: 789, - adUnitType: 'video' + adUnitType: 'video', + custom1: 'custom1', + custom2: 'custom2', + custom3: 'custom3', + custom4: 'custom4', + custom5: 'custom5' }, placementCode: 'placement_2', auctionId: 'e4771143-6aa7-41ec-8824-ced4342c96c8', @@ -196,7 +216,12 @@ describe('limelightDigitalAdapter', function () { 'transactionId', 'publisherId', 'userIdAsEids', - 'supplyChain' + 'supplyChain', + 'custom1', + 'custom2', + 'custom3', + 'custom4', + 'custom5' ); expect(adUnit.id).to.be.a('number'); expect(adUnit.bidId).to.be.a('string'); @@ -205,6 +230,11 @@ describe('limelightDigitalAdapter', function () { expect(adUnit.sizes).to.be.an('array'); expect(adUnit.userIdAsEids).to.be.an('array'); expect(adUnit.supplyChain).to.be.an('object'); + expect(adUnit.custom1).to.be.a('string'); + expect(adUnit.custom2).to.be.a('string'); + expect(adUnit.custom3).to.be.a('string'); + expect(adUnit.custom4).to.be.a('string'); + expect(adUnit.custom5).to.be.a('string'); }) }) }) diff --git a/test/spec/modules/liveIntentIdSystem_spec.js b/test/spec/modules/liveIntentIdSystem_spec.js index 3c22cda1154..afbd1566438 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,34 @@ 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 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 +129,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, @@ -152,7 +173,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 +188,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 +203,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 +220,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 +243,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 +265,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 +298,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, @@ -292,6 +313,16 @@ describe('LiveIntentId', function() { expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'uid2': 'bar'}, 'uid2': {'id': 'bar'}}); }); + 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'}}); + }); + + 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'}}); + }); + it('should decode values with uid2 but no nonId', function() { const result = liveIntentIdSubmodule.decode({ uid2: 'bar' }); expect(result).to.eql({'uid2': {'id': 'bar'}}); @@ -304,7 +335,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/livewrappedBidAdapter_spec.js b/test/spec/modules/livewrappedBidAdapter_spec.js index a4fee5e3b26..6bbdf6e1705 100644 --- a/test/spec/modules/livewrappedBidAdapter_spec.js +++ b/test/spec/modules/livewrappedBidAdapter_spec.js @@ -13,6 +13,10 @@ describe('Livewrapped adapter tests', function () { window.livewrapped = undefined; + config.setConfig({ + device: { w: 100, h: 100 } + }); + bidderRequest = { bidderCode: 'livewrapped', auctionId: 'c45dd708-a418-42ec-b8a7-b70a6c6fab0a', @@ -43,6 +47,7 @@ describe('Livewrapped adapter tests', function () { afterEach(function () { sandbox.restore(); + config.resetConfig(); }); describe('isBidRequestValid', function() { @@ -351,7 +356,7 @@ describe('Livewrapped adapter tests', function () { expect(data).to.deep.equal(expectedQuery); }); - it('should make a well-formed single request object with ad blocker revovered parameter', function() { + it('should make a well-formed single request object with ad blocker recovered parameter', function() { sandbox.stub(utils, 'getWindowTop').returns({ I12C: { Morph: 1 } }); sandbox.stub(utils, 'isSafariBrowser').callsFake(() => false); sandbox.stub(storage, 'cookiesAreEnabled').callsFake(() => true); @@ -489,7 +494,7 @@ describe('Livewrapped adapter tests', function () { return {bundle: 'bundle', domain: 'https://appdomain.com'}; } if (key === 'device') { - return {ifa: 'ifa', width: 300, height: 200}; + return {ifa: 'ifa', w: 300, h: 200}; } return origGetConfig.apply(config, arguments); }); @@ -839,6 +844,266 @@ describe('Livewrapped adapter tests', function () { expect(data).to.deep.equal(expectedQuery); }); + + it('width and height should default to values from window when not set in config', function() { + sandbox.stub(utils, 'isSafariBrowser').callsFake(() => false); + sandbox.stub(storage, 'cookiesAreEnabled').callsFake(() => true); + + config.resetConfig(); + + let testbidRequest = clone(bidderRequest); + let result = spec.buildRequests(testbidRequest.bids, testbidRequest); + let data = JSON.parse(result.data); + + expect(result.url).to.equal('https://lwadm.com/ad'); + + let expectedQuery = { + auctionId: 'F7557995-65F5-4682-8782-7D5D34D82A8C', + publisherId: '26947112-2289-405D-88C1-A7340C57E63E', + userId: 'user id', + url: 'https://www.domain.com', + seats: {'dsp': ['seat 1']}, + version: '1.4', + width: window.innerWidth, + height: window.innerHeight, + cookieSupport: true, + adRequests: [{ + adUnitId: '9E153CED-61BC-479E-98DF-24DC0D01BA37', + callerAdUnitId: 'panorama_d_1', + bidId: '2ffb201a808da7', + transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D', + formats: [{width: 980, height: 240}, {width: 980, height: 120}] + }] + }; + + expect(data).to.deep.equal(expectedQuery); + }); + }); + + describe('price floors module', function() { + it('price floors module disabled', function() { + sandbox.stub(utils, 'isSafariBrowser').callsFake(() => false); + sandbox.stub(storage, 'cookiesAreEnabled').callsFake(() => true); + + let testbidRequest = clone(bidderRequest); + let result = spec.buildRequests(testbidRequest.bids, testbidRequest); + let data = JSON.parse(result.data); + + expect(result.url).to.equal('https://lwadm.com/ad'); + + let expectedQuery = { + auctionId: 'F7557995-65F5-4682-8782-7D5D34D82A8C', + publisherId: '26947112-2289-405D-88C1-A7340C57E63E', + userId: 'user id', + url: 'https://www.domain.com', + seats: {'dsp': ['seat 1']}, + version: '1.4', + width: 100, + height: 100, + cookieSupport: true, + adRequests: [{ + adUnitId: '9E153CED-61BC-479E-98DF-24DC0D01BA37', + callerAdUnitId: 'panorama_d_1', + bidId: '2ffb201a808da7', + transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D', + formats: [{width: 980, height: 240}, {width: 980, height: 120}] + }] + }; + + expect(data).to.deep.equal(expectedQuery); + }); + + it('getFloor does not return an object', function() { + sandbox.stub(utils, 'isSafariBrowser').callsFake(() => false); + sandbox.stub(storage, 'cookiesAreEnabled').callsFake(() => true); + + let testbidRequest = clone(bidderRequest); + let bids = testbidRequest.bids.map(b => { + b.getFloor = function () { return undefined; } + return b; + }); + let result = spec.buildRequests(bids, testbidRequest); + let data = JSON.parse(result.data); + + expect(result.url).to.equal('https://lwadm.com/ad'); + + let expectedQuery = { + auctionId: 'F7557995-65F5-4682-8782-7D5D34D82A8C', + publisherId: '26947112-2289-405D-88C1-A7340C57E63E', + userId: 'user id', + url: 'https://www.domain.com', + seats: {'dsp': ['seat 1']}, + version: '1.4', + width: 100, + height: 100, + cookieSupport: true, + adRequests: [{ + adUnitId: '9E153CED-61BC-479E-98DF-24DC0D01BA37', + callerAdUnitId: 'panorama_d_1', + bidId: '2ffb201a808da7', + transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D', + formats: [{width: 980, height: 240}, {width: 980, height: 120}] + }] + }; + + expect(data).to.deep.equal(expectedQuery); + }); + + it('getFloor returns a NaN floor', function() { + sandbox.stub(utils, 'isSafariBrowser').callsFake(() => false); + sandbox.stub(storage, 'cookiesAreEnabled').callsFake(() => true); + + let testbidRequest = clone(bidderRequest); + let bids = testbidRequest.bids.map(b => { + b.getFloor = function () { return { floor: undefined }; } + return b; + }); + let result = spec.buildRequests(bids, testbidRequest); + let data = JSON.parse(result.data); + + expect(result.url).to.equal('https://lwadm.com/ad'); + + let expectedQuery = { + auctionId: 'F7557995-65F5-4682-8782-7D5D34D82A8C', + publisherId: '26947112-2289-405D-88C1-A7340C57E63E', + userId: 'user id', + url: 'https://www.domain.com', + seats: {'dsp': ['seat 1']}, + version: '1.4', + width: 100, + height: 100, + cookieSupport: true, + adRequests: [{ + adUnitId: '9E153CED-61BC-479E-98DF-24DC0D01BA37', + callerAdUnitId: 'panorama_d_1', + bidId: '2ffb201a808da7', + transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D', + formats: [{width: 980, height: 240}, {width: 980, height: 120}] + }] + }; + + expect(data).to.deep.equal(expectedQuery); + }); + + it('getFloor returns unexpected currency', function() { + sandbox.stub(utils, 'isSafariBrowser').callsFake(() => false); + sandbox.stub(storage, 'cookiesAreEnabled').callsFake(() => true); + + let testbidRequest = clone(bidderRequest); + let bids = testbidRequest.bids.map(b => { + b.getFloor = function () { return { floor: 10, currency: 'EUR' }; } + return b; + }); + let result = spec.buildRequests(bids, testbidRequest); + let data = JSON.parse(result.data); + + expect(result.url).to.equal('https://lwadm.com/ad'); + + let expectedQuery = { + auctionId: 'F7557995-65F5-4682-8782-7D5D34D82A8C', + publisherId: '26947112-2289-405D-88C1-A7340C57E63E', + userId: 'user id', + url: 'https://www.domain.com', + seats: {'dsp': ['seat 1']}, + version: '1.4', + width: 100, + height: 100, + cookieSupport: true, + adRequests: [{ + adUnitId: '9E153CED-61BC-479E-98DF-24DC0D01BA37', + callerAdUnitId: 'panorama_d_1', + bidId: '2ffb201a808da7', + transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D', + formats: [{width: 980, height: 240}, {width: 980, height: 120}] + }] + }; + + expect(data).to.deep.equal(expectedQuery); + }); + + it('getFloor returns valid floor - ad server currency', function() { + sandbox.stub(utils, 'isSafariBrowser').callsFake(() => false); + sandbox.stub(storage, 'cookiesAreEnabled').callsFake(() => true); + + let origGetConfig = config.getConfig; + sandbox.stub(config, 'getConfig').callsFake(function (key) { + if (key === 'currency.adServerCurrency') { + return 'EUR'; + } + return origGetConfig.apply(config, arguments); + }); + + let testbidRequest = clone(bidderRequest); + let bids = testbidRequest.bids.map(b => { + b.getFloor = function () { return { floor: 10, currency: 'EUR' }; } + return b; + }); + let result = spec.buildRequests(bids, testbidRequest); + let data = JSON.parse(result.data); + + expect(result.url).to.equal('https://lwadm.com/ad'); + + let expectedQuery = { + auctionId: 'F7557995-65F5-4682-8782-7D5D34D82A8C', + publisherId: '26947112-2289-405D-88C1-A7340C57E63E', + userId: 'user id', + url: 'https://www.domain.com', + seats: {'dsp': ['seat 1']}, + version: '1.4', + width: 100, + height: 100, + cookieSupport: true, + flrCur: 'EUR', + adRequests: [{ + adUnitId: '9E153CED-61BC-479E-98DF-24DC0D01BA37', + callerAdUnitId: 'panorama_d_1', + bidId: '2ffb201a808da7', + transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D', + formats: [{width: 980, height: 240}, {width: 980, height: 120}], + flr: 10 + }] + }; + + expect(data).to.deep.equal(expectedQuery); + }); + + it('getFloor returns valid floor - default currency', function() { + sandbox.stub(utils, 'isSafariBrowser').callsFake(() => false); + sandbox.stub(storage, 'cookiesAreEnabled').callsFake(() => true); + + let testbidRequest = clone(bidderRequest); + let bids = testbidRequest.bids.map(b => { + b.getFloor = function () { return { floor: 10, currency: 'USD' }; } + return b; + }); + let result = spec.buildRequests(bids, testbidRequest); + let data = JSON.parse(result.data); + + expect(result.url).to.equal('https://lwadm.com/ad'); + + let expectedQuery = { + auctionId: 'F7557995-65F5-4682-8782-7D5D34D82A8C', + publisherId: '26947112-2289-405D-88C1-A7340C57E63E', + userId: 'user id', + url: 'https://www.domain.com', + seats: {'dsp': ['seat 1']}, + version: '1.4', + width: 100, + height: 100, + cookieSupport: true, + flrCur: 'USD', + adRequests: [{ + adUnitId: '9E153CED-61BC-479E-98DF-24DC0D01BA37', + callerAdUnitId: 'panorama_d_1', + bidId: '2ffb201a808da7', + transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D', + formats: [{width: 980, height: 240}, {width: 980, height: 120}], + flr: 10 + }] + }; + + expect(data).to.deep.equal(expectedQuery); + }); }); it('should make use of user ids if available', function() { diff --git a/test/spec/modules/lotamePanoramaIdSystem_spec.js b/test/spec/modules/lotamePanoramaIdSystem_spec.js index 5dc055ac080..ea538db08e1 100644 --- a/test/spec/modules/lotamePanoramaIdSystem_spec.js +++ b/test/spec/modules/lotamePanoramaIdSystem_spec.js @@ -18,6 +18,7 @@ describe('LotameId', function() { let removeFromLocalStorageStub; let timeStampStub; let uspConsentDataStub; + let requestHost; const nowTimestamp = new Date().getTime(); @@ -33,6 +34,11 @@ describe('LotameId', function() { ); timeStampStub = sinon.stub(utils, 'timestamp').returns(nowTimestamp); uspConsentDataStub = sinon.stub(uspDataHandler, 'getConsentData'); + if (navigator.userAgent && navigator.userAgent.indexOf('Safari') != -1 && navigator.userAgent.indexOf('Chrome') == -1) { + requestHost = 'https://c.ltmsphrcl.net/id'; + } else { + requestHost = 'https://id.crwdcntrl.net/id'; + } }); afterEach(function () { @@ -69,7 +75,7 @@ describe('LotameId', function() { }); it('should call the remote server when getId is called', function () { - expect(request.url).to.be.eq('https://id.crwdcntrl.net/id'); + expect(request.url).to.be.eq(`${requestHost}`); expect(callBackSpy.calledOnce).to.be.true; }); @@ -439,7 +445,7 @@ describe('LotameId', function() { it('should pass the gdpr consent string back', function() { expect(request.url).to.be.eq( - 'https://id.crwdcntrl.net/id?gdpr_applies=true&gdpr_consent=consentGiven' + `${requestHost}?gdpr_applies=true&gdpr_consent=consentGiven` ); }); }); @@ -471,7 +477,7 @@ describe('LotameId', function() { it('should pass the gdpr consent string back', function() { expect(request.url).to.be.eq( - 'https://id.crwdcntrl.net/id?gdpr_applies=true&gdpr_consent=consentGiven' + `${requestHost}?gdpr_applies=true&gdpr_consent=consentGiven` ); }); }); @@ -503,7 +509,7 @@ describe('LotameId', function() { it('should pass the gdpr consent string back', function() { expect(request.url).to.be.eq( - 'https://id.crwdcntrl.net/id?gdpr_applies=true&gdpr_consent=consentGiven' + `${requestHost}?gdpr_applies=true&gdpr_consent=consentGiven` ); }); }); @@ -531,7 +537,7 @@ describe('LotameId', function() { it('should not include the gdpr consent string on the url', function() { expect(request.url).to.be.eq( - 'https://id.crwdcntrl.net/id?gdpr_applies=true' + `${requestHost}?gdpr_applies=true` ); }); }); @@ -560,7 +566,7 @@ describe('LotameId', function() { it('should pass the gdpr consent string back', function() { expect(request.url).to.be.eq( - 'https://id.crwdcntrl.net/id?gdpr_consent=consentGiven' + `${requestHost}?gdpr_consent=consentGiven` ); }); }); @@ -589,7 +595,7 @@ describe('LotameId', function() { it('should pass the gdpr consent string back', function() { expect(request.url).to.be.eq( - 'https://id.crwdcntrl.net/id?gdpr_consent=consentGiven' + `${requestHost}?gdpr_consent=consentGiven` ); }); }); @@ -613,7 +619,7 @@ describe('LotameId', function() { }); it('should pass the gdpr consent string back', function() { - expect(request.url).to.be.eq('https://id.crwdcntrl.net/id'); + expect(request.url).to.be.eq(`${requestHost}`); }); }); @@ -835,7 +841,7 @@ describe('LotameId', function() { it('should pass the usp consent string and client id back', function () { expect(request.url).to.be.eq( - 'https://id.crwdcntrl.net/id?gdpr_applies=false&us_privacy=1NNN&c=1234' + `${requestHost}?gdpr_applies=false&us_privacy=1NNN&c=1234` ); }); @@ -923,7 +929,7 @@ describe('LotameId', function() { it('should pass client id back', function () { expect(request.url).to.be.eq( - 'https://id.crwdcntrl.net/id?gdpr_applies=false&c=1234' + `${requestHost}?gdpr_applies=false&c=1234` ); }); diff --git a/test/spec/modules/luponmediaBidAdapter_spec.js b/test/spec/modules/luponmediaBidAdapter_spec.js index 9f109ff1892..c8d4c18c407 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, diff --git a/test/spec/modules/magniteAnalyticsAdapter_spec.js b/test/spec/modules/magniteAnalyticsAdapter_spec.js index 3606b4b4550..bf9c3050bf6 100644 --- a/test/spec/modules/magniteAnalyticsAdapter_spec.js +++ b/test/spec/modules/magniteAnalyticsAdapter_spec.js @@ -3,6 +3,7 @@ import magniteAdapter, { getHostNameFromReferer, storage, rubiConf, + detectBrowserFromUa } from '../../../modules/magniteAnalyticsAdapter.js'; import CONSTANTS from 'src/constants.json'; import { config } from 'src/config.js'; @@ -27,7 +28,8 @@ const { BIDDER_DONE, BID_WON, BID_TIMEOUT, - BILLABLE_EVENT + BILLABLE_EVENT, + SEAT_NON_BID } } = CONSTANTS; @@ -101,6 +103,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', }, @@ -154,6 +161,16 @@ const MOCK = { 'status': 'rendered', getStatusCode: () => 1, }, + 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', 'auctionEnd': 1658868384019, @@ -209,6 +226,9 @@ const ANALYTICS_MESSAGE = { 'start': 1519767013781, 'expires': 1519788613781 }, + 'client': { + 'browser': 'Chrome' + }, 'auctions': [ { 'auctionId': '99785e47-a7c8-4c8a-ae05-ef1c717a4b4d', @@ -550,6 +570,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); @@ -2007,4 +2050,121 @@ 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 + } + ); + }); + }); + + 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); + }); + }); + }); }); 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/mediakeysBidAdapter_spec.js b/test/spec/modules/mediakeysBidAdapter_spec.js index 393b6ac6764..75c7f42cf58 100644 --- a/test/spec/modules/mediakeysBidAdapter_spec.js +++ b/test/spec/modules/mediakeysBidAdapter_spec.js @@ -601,33 +601,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/mgidBidAdapter_spec.js b/test/spec/modules/mgidBidAdapter_spec.js index c79cad6245d..28530c4c4b4 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+&consentData=&gdprApplies=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+&consentData=consent&gdprApplies=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+&consentData=consent1&gdprApplies=1&uspString=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+&consentData=consent1&gdprApplies=1&uspString=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+&consentData=consent1&gdprApplies=1&uspString=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+&consentData=consent1&gdprApplies=1&uspString=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+&consentData=consent1&gdprApplies=1&uspString=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+&consentData=consent1&gdprApplies=1&uspString=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+&consentData=consent1&gdprApplies=1&uspString=consent2&gppString=gpp&coppa=1/) }); }); diff --git a/test/spec/modules/minutemediaplusBidAdapter_spec.js b/test/spec/modules/minutemediaplusBidAdapter_spec.js new file mode 100644 index 00000000000..e19323c794f --- /dev/null +++ b/test/spec/modules/minutemediaplusBidAdapter_spec.js @@ -0,0 +1,630 @@ +import {expect} from 'chai'; +import { + spec as adapter, + SUPPORTED_ID_SYSTEMS, + createDomain, + hashCode, + extractPID, + extractCID, + extractSubDomain, + getStorageItem, + setStorageItem, + tryParseJSON, + 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 {config} from '../../../src/config'; + +const SUB_DOMAIN = 'exchange'; + +const BID = { + 'bidId': '2d52001cabd527', + 'adUnitCode': 'div-gpt-ad-12345-0', + 'params': { + 'subDomain': SUB_DOMAIN, + 'cId': '59db6b3b4ffaa70004f45cdc', + 'pId': '59ac17c192832d0011283fe3', + 'bidFloor': 0.1, + 'ext': { + 'param1': 'loremipsum', + 'param2': 'dolorsitamet' + } + }, + '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], + 'ortb2Imp': { + 'ext': { + 'gpid': '1234567890' + } + } +}; + +const VIDEO_BID = { + 'bidId': '2d52001cabd527', + 'adUnitCode': '63550ad1ff6642d368cba59dh5884270560', + 'bidderRequestId': '12a8ae9ada9c13', + 'auctionId': 'auction_id', + 'bidRequestsCount': 4, + 'bidderRequestsCount': 3, + 'bidderWinsCount': 1, + 'transactionId': '56e184c6-bde9-497b-b9b9-cf47a61381ee', + 'schain': 'a0819c69-005b-41ed-af06-1be1e0aefefc', + 'params': { + 'subDomain': SUB_DOMAIN, + 'cId': '635509f7ff6642d368cb9837', + 'pId': '59ac17c192832d0011283fe3', + 'bidFloor': 0.1 + }, + 'sizes': [[545, 307]], + 'mediaTypes': { + 'video': { + 'playerSize': [[545, 307]], + 'context': 'instream', + 'mimes': [ + 'video/mp4', + 'application/javascript' + ], + 'protocols': [2, 3, 5, 6], + 'maxduration': 60, + 'minduration': 0, + 'startdelay': 0, + 'linearity': 1, + 'api': [2], + 'placement': 1 + } + } +} + +const BIDDER_REQUEST = { + 'gdprConsent': { + '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 = { + body: { + cid: 'testcid123', + results: [{ + 'ad': '', + 'price': 0.8, + 'creativeId': '12610997325162499419', + 'exp': 30, + 'width': 300, + 'height': 250, + 'advertiserDomains': ['securepubads.g.doubleclick.net'], + 'cookies': [{ + 'src': 'https://sync.com', + 'type': 'iframe' + }, { + 'src': 'https://sync.com', + 'type': 'img' + }] + }] + } +}; + +const VIDEO_SERVER_RESPONSE = { + body: { + 'cid': '635509f7ff6642d368cb9837', + 'results': [{ + 'ad': '', + 'advertiserDomains': ['minutemedia-prebid.com'], + 'exp': 60, + 'width': 545, + 'height': 307, + 'mediaType': 'video', + 'creativeId': '12610997325162499419', + 'price': 2, + 'cookies': [] + }] + } +}; + +const REQUEST = { + data: { + width: 300, + height: 250, + bidId: '2d52001cabd527' + } +}; + +function getTopWindowQueryParams() { + try { + const parsedUrl = utils.parseUrl(window.top.document.URL, {decodeSearchAsString: true}); + return parsedUrl.search; + } catch (e) { + return ''; + } +} + +describe('MinuteMediaPlus Bid Adapter', function () { + describe('validtae spec', function () { + it('exists and is a function', function () { + expect(adapter.isBidRequestValid).to.exist.and.to.be.a('function'); + }); + + it('exists and is a function', function () { + expect(adapter.buildRequests).to.exist.and.to.be.a('function'); + }); + + it('exists and is a function', function () { + expect(adapter.interpretResponse).to.exist.and.to.be.a('function'); + }); + + it('exists and is a function', function () { + expect(adapter.getUserSyncs).to.exist.and.to.be.a('function'); + }); + + it('exists and is a string', function () { + expect(adapter.code).to.exist.and.to.be.a('string'); + }); + + it('exists and contains media types', function () { + expect(adapter.supportedMediaTypes).to.exist.and.to.be.an('array').with.length(2); + expect(adapter.supportedMediaTypes).to.contain.members([BANNER, VIDEO]); + }); + }); + + describe('validate bid requests', function () { + it('should require cId', function () { + const isValid = adapter.isBidRequestValid({ + params: { + pId: 'pid' + } + }); + expect(isValid).to.be.false; + }); + + it('should require pId', function () { + const isValid = adapter.isBidRequestValid({ + params: { + cId: 'cid' + } + }); + expect(isValid).to.be.false; + }); + + it('should validate correctly', function () { + const isValid = adapter.isBidRequestValid({ + params: { + cId: 'cid', + pId: 'pid' + } + }); + expect(isValid).to.be.true; + }); + }); + + describe('build requests', function () { + let sandbox; + before(function () { + $$PREBID_GLOBAL$$.bidderSettings = { + mmplus: { + storageAllowed: true + } + }; + sandbox = sinon.sandbox.create(); + sandbox.stub(Date, 'now').returns(1000); + }); + + 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({ + method: 'POST', + url: `${createDomain(SUB_DOMAIN)}/prebid/multi/635509f7ff6642d368cb9837`, + data: { + adUnitCode: '63550ad1ff6642d368cba59dh5884270560', + 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: { + video: { + api: [2], + context: 'instream', + linearity: 1, + maxduration: 60, + mimes: [ + 'video/mp4', + 'application/javascript' + ], + minduration: 0, + placement: 1, + playerSize: [[545, 307]], + 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({ + method: 'POST', + url: `${createDomain(SUB_DOMAIN)}/prebid/multi/59db6b3b4ffaa70004f45cdc`, + 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, + bidFloor: 0.1, + bidId: '2d52001cabd527', + adUnitCode: 'div-gpt-ad-12345-0', + publisherId: '59ac17c192832d0011283fe3', + uniqueDealId: `${hashUrl}_${Date.now().toString()}`, + bidderVersion: adapter.version, + prebidVersion: version, + 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', + } + }); + }); + + after(function () { + $$PREBID_GLOBAL$$.bidderSettings = {}; + sandbox.restore(); + }); + }); + describe('getUserSyncs', function () { + it('should have valid user sync with iframeEnabled', function () { + 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=' + }]); + }); + + it('should have valid user sync with cid on response', function () { + 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=' + }]); + }); + + it('should have valid user sync with pixelEnabled', function () { + 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=', + 'type': 'image' + }]); + }) + }); + + describe('interpret response', function () { + it('should return empty array when there is no response', function () { + const responses = adapter.interpretResponse(null); + expect(responses).to.be.empty; + }); + + it('should return empty array when there is no ad', function () { + 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'}); + expect(responses).to.be.empty; + }); + + it('should return an array of interpreted banner responses', function () { + const responses = adapter.interpretResponse(SERVER_RESPONSE, REQUEST); + expect(responses).to.have.length(1); + expect(responses[0]).to.deep.equal({ + requestId: '2d52001cabd527', + cpm: 0.8, + width: 300, + height: 250, + creativeId: '12610997325162499419', + currency: 'USD', + netRevenue: true, + ttl: 30, + ad: '', + meta: { + advertiserDomains: ['securepubads.g.doubleclick.net'] + } + }); + }); + + 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); + expect(responses[0]).to.deep.equal({ + requestId: '2d52001cabd527', + cpm: 2, + width: 545, + height: 307, + mediaType: 'video', + creativeId: '12610997325162499419', + currency: 'USD', + netRevenue: true, + ttl: 60, + vastXml: '', + meta: { + advertiserDomains: ['minutemedia-prebid.com'] + } + }); + }); + + it('should take default TTL', function () { + const serverResponse = utils.deepClone(SERVER_RESPONSE); + delete serverResponse.body.results[0].exp; + const responses = adapter.interpretResponse(serverResponse, REQUEST); + expect(responses).to.have.length(1); + expect(responses[0].ttl).to.equal(300); + }); + }); + + describe('user id system', function () { + Object.keys(SUPPORTED_ID_SYSTEMS).forEach((idSystemProvider) => { + const id = Date.now().toString(); + const bid = utils.deepClone(BID); + + const userId = (function () { + switch (idSystemProvider) { + case 'lipb': + return {lipbid: id}; + case 'parrableId': + return {eid: id}; + case 'id5id': + return {uid: id}; + default: + return id; + } + })(); + + bid.userId = { + [idSystemProvider]: userId + }; + + it(`should include 'uid.${idSystemProvider}' in request params`, function () { + const requests = adapter.buildRequests([bid], BIDDER_REQUEST); + expect(requests[0].data[`uid.${idSystemProvider}`]).to.equal(id); + }); + }); + }); + + 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'}); + 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'}); + expect(cid).to.be.equal('1'); + expect(pid).to.be.equal('2'); + expect(subDomain).to.be.equal('prebid'); + }); + }); + + describe('unique deal id', function () { + before(function () { + $$PREBID_GLOBAL$$.bidderSettings = { + mmplus: { + storageAllowed: true + } + }; + }); + after(function () { + $$PREBID_GLOBAL$$.bidderSettings = {}; + }); + const key = 'myKey'; + let uniqueDealId; + beforeEach(() => { + uniqueDealId = getUniqueDealId(key, 0); + }) + + it('should get current unique deal id', function (done) { + // waiting some time so `now` will become past + setTimeout(() => { + const current = getUniqueDealId(key); + expect(current).to.be.equal(uniqueDealId); + done(); + }, 200); + }); + + it('should get new unique deal id on expiration', function (done) { + setTimeout(() => { + const current = getUniqueDealId(key, 100); + expect(current).to.not.be.equal(uniqueDealId); + done(); + }, 200) + }); + }); + + describe('storage utils', function () { + before(function () { + $$PREBID_GLOBAL$$.bidderSettings = { + mmplus: { + storageAllowed: true + } + }; + }); + after(function () { + $$PREBID_GLOBAL$$.bidderSettings = {}; + }); + it('should get value from storage with create param', function () { + const now = Date.now(); + const clock = useFakeTimers({ + shouldAdvanceTime: true, + now + }); + setStorageItem('myKey', 2020); + const {value, created} = getStorageItem('myKey'); + expect(created).to.be.equal(now); + expect(value).to.be.equal(2020); + expect(typeof value).to.be.equal('number'); + expect(typeof created).to.be.equal('number'); + clock.restore(); + }); + + it('should get external stored value', function () { + const value = 'superman' + window.localStorage.setItem('myExternalKey', value); + const item = getStorageItem('myExternalKey'); + expect(item).to.be.equal(value); + }); + + it('should parse JSON value', function () { + const data = JSON.stringify({event: 'send'}); + const {event} = tryParseJSON(data); + expect(event).to.be.equal('send'); + }); + + it('should get original value on parse fail', function () { + const value = 21; + const parsed = tryParseJSON(value); + expect(typeof parsed).to.be.equal('number'); + expect(parsed).to.be.equal(value); + }); + }); +}); 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 new file mode 100644 index 00000000000..0ad3d7c1f74 --- /dev/null +++ b/test/spec/modules/neuwoRtdProvider_spec.js @@ -0,0 +1,123 @@ +import { server } from 'test/mocks/xhr.js'; +import * as neuwo from 'modules/neuwoRtdProvider'; + +const PUBLIC_TOKEN = 'public_key_0000'; +const config = () => ({ + params: { + publicToken: PUBLIC_TOKEN, + apiUrl: 'https://testing-requirement.neuwo.api' + } +}) + +const apiReturns = () => ({ + somethingExtra: { object: true }, + marketing_categories: { + iab_tier_1: [ + { ID: 'IAB21', label: 'Real Estate', relevance: '0.45699' } + ] + } +}) + +const TAX_ID = '441' + +/** + * Object generator, like above, written using alternative techniques + * @returns object with predefined (expected) bidsConfig fields + */ +function bidsConfiglike() { + return Object.assign({}, { + ortb2Fragments: { global: {} } + }) +} + +describe('neuwoRtdProvider', function () { + describe('neuwoRtdModule', function () { + it('initializes', function () { + expect(neuwo.neuwoRtdModule.init(config())).to.be.true; + }) + it('init needs that public token', function () { + expect(neuwo.neuwoRtdModule.init()).to.be.false; + }) + + describe('segment picking', function () { + it('handles bad inputs', function () { + expect(neuwo.pickSegments()).to.be.an('array').that.is.empty; + expect(neuwo.pickSegments('technically also an array')).to.be.an('array').that.is.empty; + expect(neuwo.pickSegments({ bad_object: 'bad' })).to.be.an('array').that.is.empty; + }) + it('handles malformations', function () { + let result = neuwo.pickSegments([{something_wrong: true}, null, { ID: 'IAB19-20' }, { id: 'IAB3-1', ID: 'IAB9-20' }]) + expect(result[0].id).to.equal('631') + expect(result[1].id).to.equal('58') + expect(result.length).to.equal(2) + }) + }) + + describe('topic injection', function () { + it('mutates bidsConfig', function () { + let topics = apiReturns() + let bidsConfig = bidsConfiglike() + 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.pagecat[0], 'category taxonomy code for pagecat').to.equal(TAX_ID) + }) + + it('handles malformed responses', function () { + let topics = { message: 'Forbidden' } + let bidsConfig = bidsConfiglike() + 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, 'length of segment(s) in content.data').to.be.an('array').that.is.empty; + + topics = '404 wouldn\'t really even show up for injection' + let bdsConfig = bidsConfiglike() + neuwo.injectTopics(topics, bdsConfig, () => { }) + expect(bdsConfig.ortb2Fragments.global.site.content.data[0].name, 'name of first content data object').to.equal(neuwo.DATA_PROVIDER) + expect(bdsConfig.ortb2Fragments.global.site.content.data[0].segment, 'length of segment(s) in content.data').to.be.an('array').that.is.empty; + + topics = undefined + let bdsConfigE = bidsConfiglike() + neuwo.injectTopics(topics, bdsConfigE, () => { }) + expect(bdsConfigE.ortb2Fragments.global.site.content.data[0].name, 'name of first content data object').to.equal(neuwo.DATA_PROVIDER) + expect(bdsConfigE.ortb2Fragments.global.site.content.data[0].segment, 'length of segment(s) in content.data').to.be.an('array').that.is.empty; + }) + }) + + describe('fragment addition', function () { + it('mutates input objects', function () { + let alphabet = { a: { b: { c: {} } } } + neuwo.addFragment(alphabet.a.b.c, 'd.e.f', { g: 'h' }) + expect(alphabet.a.b.c.d.e.f.g).to.equal('h') + }) + }) + + describe('getBidRequestData', function () { + it('forms requests properly and mutates input bidsConfig', function () { + let bids = bidsConfiglike() + let conf = config() + // control xhr api request target for testing + conf.params.argUrl = 'https://publisher.works/article.php?get=horrible_url_for_testing&id=5' + + neuwo.getBidRequestData(bids, () => { }, conf, 'any consent data works, clearly') + + let request = server.requests[0]; + expect(request.url).to.be.a('string').that.includes(conf.params.publicToken) + expect(request.url).to.include(encodeURIComponent(conf.params.argUrl)) + request.respond(200, { 'Content-Type': 'application/json; encoding=UTF-8' }, JSON.stringify(apiReturns())); + + expect(bids.ortb2Fragments.global.site.content.data[0].name, 'name of first content data object').to.equal(neuwo.DATA_PROVIDER) + expect(bids.ortb2Fragments.global.site.content.data[0].segment[0].id, 'id of first segment in content.data').to.equal(TAX_ID) + }) + + it('accepts detail not available result', function () { + let bidsConfig = bidsConfiglike() + let comparison = bidsConfiglike() + neuwo.getBidRequestData(bidsConfig, () => { }, config(), 'consensually') + let request = server.requests[0]; + request.respond(404, { 'Content-Type': 'application/json; encoding=UTF-8' }, JSON.stringify({ detail: 'Basically first time seeing this' })); + expect(bidsConfig).to.deep.equal(comparison) + }) + }) + }) +}) diff --git a/test/spec/modules/nextMillenniumBidAdapter_spec.js b/test/spec/modules/nextMillenniumBidAdapter_spec.js index 90f0d8ae15c..7f40e7d89f8 100644 --- a/test/spec/modules/nextMillenniumBidAdapter_spec.js +++ b/test/spec/modules/nextMillenniumBidAdapter_spec.js @@ -49,7 +49,7 @@ describe('nextMillenniumBidAdapterTests', function() { cur: 'USD', ext: { sync: { - image: ['urlA'], + image: ['urlA?gdpr={{.GDPR}}'], iframe: ['urlB'], } } @@ -132,7 +132,7 @@ describe('nextMillenniumBidAdapterTests', function() { let userSync = spec.getUserSyncs(syncOptions, [serverResponse], bidRequestData[0].gdprConsent, bidRequestData[0].uspConsent); expect(userSync).to.be.an('array').with.lengthOf(1); expect(userSync[0].type).to.equal('image'); - expect(userSync[0].url).to.equal('urlA'); + expect(userSync[0].url).to.equal('urlA?gdpr=1'); syncOptions.iframeEnabled = true; syncOptions.pixelEnabled = false; @@ -142,6 +142,29 @@ describe('nextMillenniumBidAdapterTests', function() { expect(userSync[0].url).to.equal('urlB'); }); + it('Test getUserSyncs with no response', function () { + const syncOptions = { + 'iframeEnabled': true, + 'pixelEnabled': false + } + let userSync = spec.getUserSyncs(syncOptions, [], bidRequestData[0].gdprConsent, bidRequestData[0].uspConsent); + expect(userSync).to.be.an('array') + expect(userSync[0].type).to.equal('iframe') + expect(userSync[0].url).to.equal('https://cookies.nextmillmedia.com/sync?gdpr=1&gdpr_consent=kjfdniwjnifwenrif3&us_privacy=1---&type=iframe') + }) + + it('Test getUserSyncs function if GDPR is undefined', function () { + const syncOptions = { + 'iframeEnabled': false, + 'pixelEnabled': true + } + + let userSync = spec.getUserSyncs(syncOptions, [serverResponse], undefined, bidRequestData[0].uspConsent); + expect(userSync).to.be.an('array').with.lengthOf(1); + expect(userSync[0].type).to.equal('image'); + expect(userSync[0].url).to.equal('urlA?gdpr=0'); + }); + it('Request params check without GDPR Consent', function () { delete bidRequestData[0].gdprConsent const request = spec.buildRequests(bidRequestData, bidRequestData[0]); diff --git a/test/spec/modules/nexx360BidAdapter_spec.js b/test/spec/modules/nexx360BidAdapter_spec.js index a5da8b29fbc..9ba3ac0e5a4 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', @@ -276,63 +358,83 @@ describe('Nexx360 bid adapter tests', function () { 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 +447,6 @@ describe('Nexx360 bid adapter tests', function () { 'id': '4427551302944024629', 'impid': '226175918ebeda', 'price': 1.5, - 'type': 'banner', 'adomain': [ 'http://prebid.org' ], @@ -356,145 +457,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].bidderCode).to.be.eql('nexx360'); - 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].bidderCode).to.be.eql('nexx360'); - 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].bidderCode).to.be.eql('nexx360'); - 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].bidderCode).to.be.eql('nexx360'); - 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'); }); }); @@ -505,7 +614,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..2d48dca5ebb 100644 --- a/test/spec/modules/oguryBidAdapter_spec.js +++ b/test/spec/modules/oguryBidAdapter_spec.js @@ -245,6 +245,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,6 +256,10 @@ describe('OguryBidAdapter', function () { return stubbedCurrentTime; }); + const stubbedDevicePixelMethod = sinon.stub(window, 'devicePixelRatio').get(function() { + return stubbedDevicePixelRatio; + }); + const defaultTimeout = 1000; const expectedRequestObject = { id: bidRequests[0].auctionId, @@ -305,11 +310,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 +323,7 @@ describe('OguryBidAdapter', function () { stubbedWidthMethod.restore(); stubbedHeightMethod.restore(); stubbedCurrentTimeMethod.restore(); + stubbedDevicePixelMethod.restore(); }); it('sends bid request to ENDPOINT via POST', function () { @@ -338,6 +345,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 +712,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 +729,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 71e897c7f9e..5bd65cf0fd5 100644 --- a/test/spec/modules/onetagBidAdapter_spec.js +++ b/test/spec/modules/onetagBidAdapter_spec.js @@ -266,6 +266,27 @@ describe('onetag', function () { expect(payload.gdprConsent.consentString).to.exist.and.to.equal(consentString); expect(payload.gdprConsent.consentRequired).to.exist.and.to.be.true; }); + it('Should send GPP consent data', function () { + let consentString = 'consentString'; + let applicableSections = [1, 2, 3]; + let bidderRequest = { + 'bidderCode': 'onetag', + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'gppConsent': { + gppString: consentString, + applicableSections: applicableSections + } + }; + let serverRequest = spec.buildRequests([bannerBid], bidderRequest); + const payload = JSON.parse(serverRequest.data); + + expect(payload).to.exist; + expect(payload.gppConsent).to.exist; + expect(payload.gppConsent.consentString).to.exist.and.to.equal(consentString); + expect(payload.gppConsent.applicableSections).to.have.same.members(applicableSections); + }); it('Should send us privacy string', function () { let consentString = 'us_foo'; let bidderRequest = { @@ -375,6 +396,28 @@ describe('onetag', function () { expect(syncs[0].url).to.include(sync_endpoint); expect(syncs[0].url).to.not.match(/(?:[?&](?:gdpr_consent=([^&]*)|gdpr=([^&]*)))+$/); }); + it('Must pass gpp consent string when gppConsent object is available', function () { + const syncs = spec.getUserSyncs({ iframeEnabled: true }, {}, {}, {}, { + gppString: 'foo' + }); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.include(sync_endpoint); + expect(syncs[0].url).to.match(/(?:[?&](?:gpp_consent=foo([^&]*)))+$/); + }); + it('Must pass no gpp params when consentString is null', function () { + const syncs = spec.getUserSyncs({ iframeEnabled: true }, {}, {}, {}, { + gppString: null + }); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.include(sync_endpoint); + expect(syncs[0].url).to.not.match(/(?:[?&](?:gpp_consent=([^&]*)))+$/); + }); + it('Must pass no gpp params when consentString is empty', function () { + const syncs = spec.getUserSyncs({ iframeEnabled: true }, {}, {}, {}, {}); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.include(sync_endpoint); + expect(syncs[0].url).to.not.match(/(?:[?&](?:gpp_consent=([^&]*)))+$/); + }); it('Should send us privacy string', function () { let usConsentString = 'us_foo'; const syncs = spec.getUserSyncs({ iframeEnabled: true }, {}, {}, usConsentString); 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/openxBidAdapter_spec.js b/test/spec/modules/openxBidAdapter_spec.js index cc1c2d1e607..d8ea79ac698 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/openxOrtbBidAdapter.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,1468 +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'); - }); + context('when there is a consent management framework', function () { + let bidRequests; + let mockConfig; + let bidderRequest; - 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'); - }); - - 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); }); + }); + + context('supply chain (schain)', function () { + let bidRequests; + let schainConfig; + const supplyChainNodePropertyOrder = ['asi', 'sid', 'hp', 'rid', 'name', 'domain']; - it('should omit filtered values for legacy', function () { - let myOpenRTBObject = {mimes: ['application/javascript'], dont: 'use'}; - videoBidRequest.params.video = { - openrtb: myOpenRTBObject + 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: {}}; - - 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('interpretResponse()', function () { + let bidRequestConfigs; + let bidRequest; + let bidResponse; + let bid; - 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'}, + context('when there is an nbr response', function () { + let bids; + beforeEach(function () { + bidRequestConfigs = [{ + bidder: 'openx', + params: { + unit: '12345678', + delDomain: 'test-del-domain' }, - { - 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'}]}, - ] - } - } - } + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]], }, - 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'}]}, - {}, - ] - } - } - } }, - { - 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' - }, + 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 just liveintent segment from request if no first party config', - config: {}, - request: { - userId: { - lipb: { - lipbid: 'aaa', - segments: ['l1', 'l2'] - }, - }, + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]], }, - expect: {sm: 'liveintent:l1|l2'}, }, - { - name: 'should send nothing if lipb section does not contain segments', - config: {}, - request: { - userId: { - lipb: { - lipbid: 'aaa', - }, - }, + 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]], }, }, - ]; - 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 = ''; // Unknown error + bids = spec.interpretResponse({body: bidResponse}, bidRequest); }); - }); - }) - describe('interpretResponse for banner ads', function () { - beforeEach(function () { - sinon.spy(userSync, 'registerSync'); + it('should not return any bids', function () { + expect(bids.length).to.equal(0); + }); }); - afterEach(function () { - userSync.registerSync.restore(); - }); + 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' + }]; - 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_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 () { @@ -1822,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 () { @@ -2246,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 () { @@ -2279,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 index be6427f607b..d8ea79ac698 100644 --- a/test/spec/modules/openxOrtbBidAdapter_spec.js +++ b/test/spec/modules/openxOrtbBidAdapter_spec.js @@ -19,6 +19,57 @@ 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 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: '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' + } + }; + + const request = { + ...defaults, + ...options + }; + + this.build = () => request; +}; + describe('OpenxRtbAdapter', function () { before(() => { hook.ready(); @@ -249,6 +300,18 @@ describe('OpenxRtbAdapter', function () { }); 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 send bid request to openx url via POST', function () { const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); expect(request[0].url).to.equal(REQUEST_URL); @@ -579,6 +642,47 @@ describe('OpenxRtbAdapter', function () { }); }); }); + + 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 () { @@ -748,6 +852,12 @@ describe('OpenxRtbAdapter', function () { 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() }); @@ -922,17 +1032,6 @@ describe('OpenxRtbAdapter', function () { }); 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, @@ -950,51 +1049,53 @@ describe('OpenxRtbAdapter', function () { }); }); - 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); - }); - }); + 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.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); - }); + 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('interpretResponse()', function () { @@ -1276,56 +1377,58 @@ describe('OpenxRtbAdapter', function () { }); }); - 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]], + if (FEATURES.VIDEO) { + context('when the response is a video', function() { + beforeEach(function () { + bidRequestConfigs = [{ + bidder: 'openx', + params: { + unit: '12345678', + delDomain: 'test-del-domain' }, - }, - bidId: 'test-bid-id', - bidderRequestId: 'test-bidder-request-id', - auctionId: 'test-auction-id' - }]; - - bidRequest = spec.buildRequests(bidRequestConfigs, {refererInfo: {}})[0]; + adUnitCode: 'adunit-code', + mediaTypes: { + video: { + playerSize: [[640, 360], [854, 480]], + }, + }, + bidId: 'test-bid-id', + bidderRequestId: 'test-bidder-request-id', + auctionId: 'test-auction-id' + }]; - 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' - }; - }); + 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 () { + 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]; + 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); + expect(bid.vastUrl).to.equal(winUrl); + }); }); - }); + } context('when the response contains FLEDGE interest groups config', function() { let response; diff --git a/test/spec/modules/optidigitalBidAdapter_spec.js b/test/spec/modules/optidigitalBidAdapter_spec.js new file mode 100755 index 00000000000..8c222650f7e --- /dev/null +++ b/test/spec/modules/optidigitalBidAdapter_spec.js @@ -0,0 +1,604 @@ +import { expect } from 'chai'; +import { spec } from 'modules/optidigitalBidAdapter.js'; +import * as utils from 'src/utils.js'; + +const ENDPOINT = 'https://pbs.optidigital.com/bidder'; + +describe('optidigitalAdapterTests', function () { + describe('isBidRequestValid', function () { + it('bidRequest with publisherId and placementId', function () { + expect(spec.isBidRequestValid({ + bidder: 'optidigital', + params: { + publisherId: 's123', + placementId: 'Billboard_Top' + } + })).to.equal(true); + }); + it('bidRequest without publisherId', function () { + expect(spec.isBidRequestValid({ + bidder: 'optidigital', + params: { + placementId: 'Billboard_Top' + } + })).to.equal(false); + }); + it('bidRequest without placementId', function () { + expect(spec.isBidRequestValid({ + bidder: 'optidigital', + params: { + publisherId: 's123' + } + })).to.equal(false); + }); + it('bidRequest without required parameters', function () { + expect(spec.isBidRequestValid({ + bidder: 'optidigital', + params: {} + })).to.equal(false); + }); + }); + + describe('buildRequests', function () { + const bidderRequest = { + bids: [ + { + 'bidder': 'optidigital', + 'params': { + 'publisherId': 's123', + 'placementId': 'Billboard_Top', + 'divId': 'Billboard_Top_3c5425', + 'badv': ['example.com'], + 'bcat': ['IAB1-1'], + 'bapp': ['com.blocked'], + 'battr': [1, 2] + }, + 'crumbs': { + 'pubcid': '7769fd03-574c-48fe-b512-8147f7c4023a' + }, + 'ortb2Imp': { + 'ext': { + 'tid': '0cb56262-9637-474d-a572-86fa860fd8b7', + 'data': { + 'adserver': { + 'name': 'gam', + 'adslot': '/19968336/header-bid-tag-0' + }, + 'pbadslot': '/19968336/header-bid-tag-0' + }, + 'gpid': '/19968336/header-bid-tag-0' + } + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ [ 300, 250 ], [ 300, 600 ] ] + } + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': '0cb56262-9637-474d-a572-86fa860fd8b7', + 'sizes': [ [ 300, 250 ], [ 300, 600 ] ], + 'bidId': '245d89f17f289f', + 'bidderRequestId': '199d7ffafa1e91', + 'auctionId': 'b66f01cd-3441-4403-99fa-d8062e795933', + 'src': 'client', + 'metrics': { + 'requestBids.usp': 0.5, + 'requestBids.pubCommonId': 0.29999999701976776, + 'requestBids.fpd': 3.1000000089406967, + 'requestBids.validate': 0.5, + 'requestBids.makeRequests': 2.2000000029802322, + 'requestBids.total': 570, + 'requestBids.callBids': 320.5, + 'adapter.client.net': [ + 317.30000001192093 + ], + 'adapters.client.optidigital.net': [ + 317.30000001192093 + ], + 'adapter.client.interpretResponse': [ + 0 + ], + 'adapters.client.optidigital.interpretResponse': [ + 0 + ], + 'adapter.client.validate': 0, + 'adapters.client.optidigital.validate': 0, + 'adapter.client.buildRequests': 1, + 'adapters.client.optidigital.buildRequests': 1, + 'adapter.client.total': 318.59999999403954, + 'adapters.client.optidigital.total': 318.59999999403954 + }, + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0, + 'ortb2': { + 'site': { + 'page': 'https://example.com', + 'ref': 'https://example.com', + 'domain': 'example.com', + 'publisher': { + 'domain': 'example.com' + } + }, + 'device': { + 'w': 1605, + 'h': 1329, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36', + 'language': 'pl', + 'sua': { + 'source': 2, + 'platform': { + 'brand': 'Windows', + 'version': [ + '10', + '0', + '0' + ] + }, + 'browsers': [ + { + 'brand': 'Not_A Brand', + 'version': [ + '99', + '0', + '0', + '0' + ] + }, + { + 'brand': 'Google Chrome', + 'version': [ + '109', + '0', + '5414', + '75' + ] + }, + { + 'brand': 'Chromium', + 'version': [ + '109', + '0', + '5414', + '75' + ] + } + ], + 'mobile': 0, + 'model': '', + 'bitness': '64', + 'architecture': 'x86' + } + } + } + } + ], + 'refererInfo': { + 'canonicalUrl': 'https://www.prebid.org/the/link/to/the/page' + } + }; + + let validBidRequests = [ + { + 'bidder': 'optidigital', + 'bidId': '51ef8751f9aead', + 'params': { + 'publisherId': 's123', + 'placementId': 'Billboard_Top', + 'badv': ['example.com'], + 'bcat': ['IAB1-1'], + 'bapp': ['com.blocked'], + 'battr': [1, 2] + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + 'sizes': [[320, 50], [300, 250], [300, 600]], + 'bidderRequestId': '418b37f85e772c', + 'auctionId': '18fd8b8b0bd757' + } + ] + + it('should return an empty array if there are no bid requests', () => { + const emptyBidRequests = []; + const request = spec.buildRequests(emptyBidRequests, emptyBidRequests); + expect(request).to.be.an('array').that.is.empty; + }); + + it('should send bid request via POST', function() { + const request = spec.buildRequests(validBidRequests, bidderRequest); + expect(request.method).to.equal('POST'); + }); + + it('should send bid request to given endpoint', function() { + const request = spec.buildRequests(validBidRequests, bidderRequest); + expect(request.url).to.equal(ENDPOINT); + }); + + it('should be bidRequest data', function () { + const request = spec.buildRequests(validBidRequests, bidderRequest); + expect(request.data).to.exist; + }); + + it('should add schain object to payload if exists', function () { + const bidRequest = Object.assign({}, validBidRequests[0], { + schain: { + ver: '1.0', + complete: 1, + nodes: [{ + asi: 'examplewebsite.com', + sid: '00001', + hp: 1 + }] + } + }); + const request = spec.buildRequests([bidRequest], bidderRequest); + const payload = JSON.parse(request.data) + expect(payload.schain).to.exist; + expect(payload.schain).to.deep.equal({ + ver: '1.0', + complete: 1, + nodes: [{ + asi: 'examplewebsite.com', + sid: '00001', + hp: 1 + }] + }); + }); + + it('should add adContainerWidth and adContainerHeight to payload if divId exsists in parameter', function () { + let validBidRequestsWithDivId = [ + { + 'bidder': 'optidigital', + 'bidId': '51ef8751f9aead', + 'params': { + 'publisherId': 's123', + 'placementId': 'Billboard_Top', + 'divId': 'div-gpt-ad-1460505748561-0' + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 50], [300, 250], [300, 600]] + } + }, + 'sizes': [[320, 50], [300, 250], [300, 600]], + 'bidderRequestId': '418b37f85e772c', + 'auctionId': '18fd8b8b0bd757' + } + ] + const request = spec.buildRequests(validBidRequestsWithDivId, bidderRequest); + const payload = JSON.parse(request.data) + payload.imp[0].adContainerWidth = 1920 + payload.imp[0].adContainerHeight = 1080 + expect(payload.imp[0].adContainerWidth).to.exist; + expect(payload.imp[0].adContainerHeight).to.exist; + }); + + it('should add pageTemplate to payload if pageTemplate exsists in parameter', function () { + let validBidRequestsWithDivId = [ + { + 'bidder': 'optidigital', + 'bidId': '51ef8751f9aead', + 'params': { + 'publisherId': 's123', + 'placementId': 'Billboard_Top', + 'pageTemplate': 'home' + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 50], [300, 250], [300, 600]] + } + }, + 'sizes': [[320, 50], [300, 250], [300, 600]], + 'bidderRequestId': '418b37f85e772c', + 'auctionId': '18fd8b8b0bd757' + } + ] + const request = spec.buildRequests(validBidRequestsWithDivId, bidderRequest); + const payload = JSON.parse(request.data) + payload.imp[0].pageTemplate = 'home' + expect(payload.imp[0].pageTemplate).to.exist; + }); + + it('should add referrer to payload if it exsists in bidderRequest', function () { + bidderRequest.refererInfo.page = 'https://www.prebid.org'; + const request = spec.buildRequests(validBidRequests, bidderRequest); + const payload = JSON.parse(request.data) + expect(payload.referrer).to.equal('https://www.prebid.org'); + }); + + it('should use value for badv, bcat, bapp from params', function () { + bidderRequest.ortb2 = { + 'site': { + 'page': 'https://example.com', + 'ref': 'https://example.com', + 'domain': 'example.com', + 'publisher': { + 'domain': 'example.com' + } + }, + 'device': { + 'w': 1507, + 'h': 1329, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36', + 'language': 'pl', + 'sua': { + 'source': 2, + 'platform': { + 'brand': 'Windows', + 'version': [ + '10', + '0', + '0' + ] + }, + 'browsers': [ + { + 'brand': 'Not_A Brand', + 'version': [ + '99', + '0', + '0', + '0' + ] + }, + { + 'brand': 'Google Chrome', + 'version': [ + '109', + '0', + '5414', + '120' + ] + }, + { + 'brand': 'Chromium', + 'version': [ + '109', + '0', + '5414', + '120' + ] + } + ], + 'mobile': 0, + 'model': '', + 'bitness': '64', + 'architecture': 'x86' + } + } + }; + const request = spec.buildRequests(validBidRequests, bidderRequest); + const payload = JSON.parse(request.data); + expect(payload.badv).to.deep.equal(validBidRequests[0].params.badv); + expect(payload.bcat).to.deep.equal(validBidRequests[0].params.bcat); + expect(payload.bapp).to.deep.equal(validBidRequests[0].params.bapp); + }); + + it('should send empty GDPR consent and required set to false', function() { + const request = spec.buildRequests(validBidRequests, bidderRequest); + const payload = JSON.parse(request.data); + expect(payload.gdpr.consent).to.equal(''); + expect(payload.gdpr.required).to.equal(false); + }); + + it('should send GDPR to given endpoint', function() { + let consentString = 'DFR8KRePoQNsRREZCADBG+A=='; + bidderRequest.gdprConsent = { + 'consentString': consentString, + 'gdprApplies': true, + 'vendorData': { + 'hasGlobalConsent': false + }, + 'apiVersion': 1 + } + const request = spec.buildRequests(validBidRequests, bidderRequest); + const payload = JSON.parse(request.data); + expect(payload.gdpr).to.exist; + expect(payload.gdpr.consent).to.equal(consentString); + expect(payload.gdpr.required).to.exist.and.to.be.true; + }); + + it('should send empty GDPR consent to endpoint', function() { + let consentString = false; + bidderRequest.gdprConsent = { + 'consentString': consentString, + 'gdprApplies': true, + 'vendorData': { + 'hasGlobalConsent': false + }, + 'apiVersion': 1 + } + const request = spec.buildRequests(validBidRequests, bidderRequest); + const payload = JSON.parse(request.data); + expect(payload.gdpr.consent).to.equal(''); + }); + + it('should send uspConsent to given endpoint', function() { + bidderRequest.uspConsent = '1YYY'; + const request = spec.buildRequests(validBidRequests, bidderRequest); + const payload = JSON.parse(request.data); + expect(payload.uspConsent).to.exist; + }); + + it('should use appropriate mediaTypes banner sizes', function() { + const mediaTypesBannerSize = { + 'mediaTypes': { + 'banner': { + 'sizes': [300, 600] + } + } + }; + returnBannerSizes(mediaTypesBannerSize, '300x600'); + }); + + it('should use appropriate mediaTypes banner sizes as array', function() { + const mediaTypesBannerSize = { + 'mediaTypes': { + 'banner': { + 'sizes': [300, 600] + } + } + }; + returnBannerSizes(mediaTypesBannerSize, ['300x600']); + }); + + it('should fetch floor from floor module if it is available', function() { + let validBidRequestsWithCurrency = [ + { + 'bidder': 'optidigital', + 'bidId': '51ef8751f9aead', + 'params': { + 'publisherId': 's123', + 'placementId': 'Billboard_Top', + 'pageTemplate': 'home', + 'currency': 'USD' + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 50], [300, 250], [300, 600]] + } + }, + 'sizes': [[320, 50], [300, 250], [300, 600]], + 'bidderRequestId': '418b37f85e772c', + 'auctionId': '18fd8b8b0bd757' + } + ] + let floorInfo; + validBidRequestsWithCurrency[0].getFloor = () => floorInfo; + floorInfo = { currency: 'USD', floor: 1.99 }; + let request = spec.buildRequests(validBidRequestsWithCurrency, bidderRequest); + const payload = JSON.parse(request.data); + expect(payload.imp[0].bidFloor).to.exist; + }); + + function returnBannerSizes(mediaTypes, expectedSizes) { + const bidRequest = Object.assign(validBidRequests[0], mediaTypes); + const request = spec.buildRequests([bidRequest], bidderRequest); + const payload = JSON.parse(request.data); + return payload.imp.forEach(bid => { + if (Array.isArray(expectedSizes)) { + expect(JSON.stringify(bid.sizes)).to.equal(JSON.stringify(expectedSizes)); + } else { + expect(bid.sizes[0]).to.equal(expectedSizes); + } + }); + } + }); + describe('getUserSyncs', function() { + const syncurlIframe = 'https://scripts.opti-digital.com/js/presync.html?endpoint=optidigital'; + let test; + beforeEach(function () { + test = sinon.sandbox.create(); + }); + afterEach(function() { + test.restore(); + }); + + it('should be executed as in config', function() { + expect(spec.getUserSyncs({ iframeEnabled: true }, {}, undefined, undefined)).to.deep.equal([{ + type: 'iframe', url: syncurlIframe + }]); + }); + + it('should return appropriate URL', function() { + expect(spec.getUserSyncs({ iframeEnabled: true }, {}, {gdprApplies: true, consentString: 'foo'}, undefined)).to.deep.equal([{ + type: 'iframe', url: `${syncurlIframe}&gdpr=1&gdpr_consent=foo` + }]); + expect(spec.getUserSyncs({ iframeEnabled: true }, {}, {gdprApplies: false, consentString: 'foo'}, undefined)).to.deep.equal([{ + type: 'iframe', url: `${syncurlIframe}&gdpr=0&gdpr_consent=foo` + }]); + expect(spec.getUserSyncs({ iframeEnabled: true }, {}, {gdprApplies: true, consentString: undefined}, undefined)).to.deep.equal([{ + type: 'iframe', url: `${syncurlIframe}&gdpr=1&gdpr_consent=` + }]); + expect(spec.getUserSyncs({ iframeEnabled: true }, {}, {gdprApplies: true, consentString: 'foo'}, {consentString: 'fooUsp'})).to.deep.equal([{ + type: 'iframe', url: `${syncurlIframe}&gdpr=1&gdpr_consent=foo&ccpa_consent=fooUsp` + }]); + }); + }); + describe('interpretResponse', function () { + it('should get bids', function() { + let bids = { + 'body': { + 'bids': [{ + 'transactionId': 'cf5faec3-fcee-4f26-80ae-fc8b6cf23b7d', + 'placementId': 'Billboard_Top', + 'bidId': '83fb53a5e67f49', + 'ttl': 150, + 'creativeId': 'mobile_pos_2', + 'cur': 'USD', + 'cpm': 0.445455, + 'w': '300', + 'h': '600', + 'adm': '', + 'adomain': [] + }] + } + }; + let expectedResponse = [ + { + 'placementId': 'Billboard_Top', + 'transactionId': 'cf5faec3-fcee-4f26-80ae-fc8b6cf23b7d', + 'requestId': '83fb53a5e67f49', + 'ttl': 150, + 'creativeId': 'mobile_pos_2', + 'currency': 'USD', + 'cpm': 0.445455, + 'width': '300', + 'height': '600', + 'ad': '', + 'netRevenue': true, + 'meta': { + 'advertiserDomains': [] + } + } + ]; + let result = spec.interpretResponse(bids); + expect(result).to.eql(expectedResponse); + }); + + it('should handle empty array bid response', function() { + let bids = { + 'body': { + 'bids': [] + } + }; + let result = spec.interpretResponse(bids); + expect(result.length).to.equal(0); + }); + }); +}); diff --git a/test/spec/modules/outbrainBidAdapter_spec.js b/test/spec/modules/outbrainBidAdapter_spec.js index 5d7bebc1de1..f5ce00ed8df 100644 --- a/test/spec/modules/outbrainBidAdapter_spec.js +++ b/test/spec/modules/outbrainBidAdapter_spec.js @@ -1,7 +1,7 @@ -import {expect} from 'chai'; -import {spec} from 'modules/outbrainBidAdapter.js'; -import {config} from 'src/config.js'; -import {server} from 'test/mocks/xhr'; +import { expect } from 'chai'; +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'; describe('Outbrain Adapter', function () { @@ -45,6 +45,26 @@ describe('Outbrain Adapter', function () { ] } + const videoBidRequestParams = { + mediaTypes: { + video: { + playerSize: [[640, 480]], + mimes: ['video/mp4'], + protocols: [1, 2, 3, 4, 5, 6, 7, 8], + playbackmethod: [1], + skip: 1, + api: [2], + minbitrate: 1000, + maxbitrate: 3000, + minduration: 3, + maxduration: 10, + startdelay: 2, + placement: 4, + linearity: 1 + } + } + } + describe('isBidRequestValid', function () { before(() => { config.setConfig({ @@ -93,6 +113,34 @@ describe('Outbrain Adapter', function () { } expect(spec.isBidRequestValid(bid)).to.equal(true) }) + it('should succeed when bid contains video', function () { + const bid = { + bidder: 'outbrain', + params: { + publisher: { + id: 'publisher-id', + } + }, + ...videoBidRequestParams, + } + expect(spec.isBidRequestValid(bid)).to.equal(true) + }) + it('should fail when bid contains insufficient video information', function () { + const bid = { + bidder: 'outbrain', + params: { + publisher: { + id: 'publisher-id', + } + }, + mediaTypes: { + video: { + context: 'outstream' + } + }, + } + expect(spec.isBidRequestValid(bid)).to.equal(false) + }) it('should fail if publisher id is not set', function () { const bid = { bidder: 'outbrain', @@ -298,6 +346,61 @@ describe('Outbrain Adapter', function () { expect(res.data).to.deep.equal(JSON.stringify(expectedData)) }) + it('should build video request', function () { + const bidRequest = { + ...commonBidRequest, + ...videoBidRequestParams, + } + const expectedData = { + site: { + page: 'https://example.com/', + publisher: { + id: 'publisher-id' + } + }, + device: { + ua: navigator.userAgent + }, + source: { + fd: 1 + }, + cur: [ + 'USD' + ], + imp: [ + { + id: '1', + video: { + w: 640, + h: 480, + protocols: [1, 2, 3, 4, 5, 6, 7, 8], + playbackmethod: [1], + mimes: ['video/mp4'], + skip: 1, + api: [2], + minbitrate: 1000, + maxbitrate: 3000, + minduration: 3, + maxduration: 10, + startdelay: 2, + placement: 4, + linearity: 1 + } + } + ], + ext: { + prebid: { + channel: { + name: 'pbjs', version: '$prebid.version$' + } + } + } + } + const res = spec.buildRequests([bidRequest], commonBidderRequest) + expect(res.url).to.equal('https://bidder-url.com') + expect(res.data).to.deep.equal(JSON.stringify(expectedData)) + }) + it('should pass optional parameters in request', function () { const bidRequest = { ...commonBidRequest, @@ -390,7 +493,7 @@ describe('Outbrain Adapter', function () { ...commonBidRequest, ...nativeBidRequestParams, } - config.setConfig({coppa: true}) + config.setConfig({ coppa: true }) const res = spec.buildRequests([bidRequest], commonBidderRequest) const resData = JSON.parse(res.data) @@ -412,7 +515,7 @@ describe('Outbrain Adapter', function () { let res = spec.buildRequests([bidRequest], commonBidderRequest); const resData = JSON.parse(res.data) expect(resData.user.ext.eids).to.deep.equal([ - {source: 'liveramp.com', uids: [{id: 'id-value', atype: 3}]} + { source: 'liveramp.com', uids: [{ id: 'id-value', atype: 3 }] } ]); }); @@ -421,7 +524,7 @@ describe('Outbrain Adapter', function () { ...commonBidRequest, ...nativeBidRequestParams, } - bidRequest.getFloor = function() { + bidRequest.getFloor = function () { return { currency: 'USD', floor: 1.23, @@ -619,6 +722,67 @@ describe('Outbrain Adapter', function () { const res = spec.interpretResponse(serverResponse, request) expect(res).to.deep.equal(expectedRes) }); + + it('should interpret video response', function () { + const serverResponse = { + body: { + id: '123', + seatbid: [ + { + bid: [ + { + id: '111', + impid: '1', + price: 1.1, + adm: '\u003cVAST version="3.0"\u003e\u003cAd\u003e\u003cInLine\u003e\u003cAdSystem\u003ezemanta\u003c/AdSystem\u003e\u003cAdTitle\u003e1\u003c/AdTitle\u003e\u003cImpression\u003ehttp://win.com\u003c/Impression\u003e\u003cImpression\u003ehttp://example.com/imptracker\u003c/Impression\u003e\u003cCreatives\u003e\u003cCreative\u003e\u003cLinear\u003e\u003cDuration\u003e00:00:25\u003c/Duration\u003e\u003cTrackingEvents\u003e\u003cTracking event="start"\u003ehttp://example.com/start\u003c/Tracking\u003e\u003cTracking event="progress" offset="00:00:03"\u003ehttp://example.com/p3s\u003c/Tracking\u003e\u003c/TrackingEvents\u003e\u003cVideoClicks\u003e\u003cClickThrough\u003ehttp://link.com\u003c/ClickThrough\u003e\u003c/VideoClicks\u003e\u003cMediaFiles\u003e\u003cMediaFile delivery="progressive" type="video/mp4" bitrate="700" width="640" height="360"\u003ehttps://example.com/123_360p.mp4\u003c/MediaFile\u003e\u003c/MediaFiles\u003e\u003c/Linear\u003e\u003c/Creative\u003e\u003c/Creatives\u003e\u003c/InLine\u003e\u003c/Ad\u003e\u003c/VAST\u003e', + adid: '100', + cid: '5', + crid: '29998660', + cat: ['cat-1'], + adomain: [ + 'example.com' + ], + nurl: 'http://example.com/win/${AUCTION_PRICE}' + } + ], + seat: '100', + group: 1 + } + ], + bidid: '456', + cur: 'USD' + } + } + const request = { + bids: [ + { + ...commonBidRequest, + ...videoBidRequestParams + } + ] + } + const expectedRes = [ + { + requestId: request.bids[0].bidId, + cpm: 1.1, + creativeId: '29998660', + ttl: 360, + netRevenue: false, + currency: 'USD', + mediaType: 'video', + nurl: 'http://example.com/win/${AUCTION_PRICE}', + vastXml: 'zemanta1http://win.comhttp://example.com/imptracker00:00:25http://example.com/starthttp://example.com/p3shttp://link.comhttps://example.com/123_360p.mp4', + meta: { + 'advertiserDomains': [ + 'example.com' + ] + }, + } + ] + + const res = spec.interpretResponse(serverResponse, request) + expect(res).to.deep.equal(expectedRes) + }); }) }) @@ -637,41 +801,41 @@ describe('Outbrain Adapter', function () { }) it('should return user sync if pixel enabled with outbrain config', function () { - const ret = spec.getUserSyncs({pixelEnabled: true}) - expect(ret).to.deep.equal([{type: 'image', url: usersyncUrl}]) + const ret = spec.getUserSyncs({ pixelEnabled: true }) + expect(ret).to.deep.equal([{ type: 'image', url: usersyncUrl }]) }) it('should not return user sync if pixel disabled', function () { - const ret = spec.getUserSyncs({pixelEnabled: false}) + const ret = spec.getUserSyncs({ pixelEnabled: false }) expect(ret).to.be.an('array').that.is.empty }) it('should not return user sync if url is not set', function () { config.resetConfig() - const ret = spec.getUserSyncs({pixelEnabled: true}) + const ret = spec.getUserSyncs({ pixelEnabled: true }) expect(ret).to.be.an('array').that.is.empty }) - it('should pass GDPR consent', function() { - expect(spec.getUserSyncs({ pixelEnabled: true }, {}, {gdprApplies: true, consentString: 'foo'}, undefined)).to.deep.equal([{ + it('should pass GDPR consent', function () { + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, { gdprApplies: true, consentString: 'foo' }, undefined)).to.deep.equal([{ type: 'image', url: `${usersyncUrl}?gdpr=1&gdpr_consent=foo` }]); - expect(spec.getUserSyncs({ pixelEnabled: true }, {}, {gdprApplies: false, consentString: 'foo'}, undefined)).to.deep.equal([{ + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, { gdprApplies: false, consentString: 'foo' }, undefined)).to.deep.equal([{ type: 'image', url: `${usersyncUrl}?gdpr=0&gdpr_consent=foo` }]); - expect(spec.getUserSyncs({ pixelEnabled: true }, {}, {gdprApplies: true, consentString: undefined}, undefined)).to.deep.equal([{ + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, { gdprApplies: true, consentString: undefined }, undefined)).to.deep.equal([{ type: 'image', url: `${usersyncUrl}?gdpr=1&gdpr_consent=` }]); }); - it('should pass US consent', function() { + it('should pass US consent', function () { expect(spec.getUserSyncs({ pixelEnabled: true }, {}, undefined, '1NYN')).to.deep.equal([{ type: 'image', url: `${usersyncUrl}?us_privacy=1NYN` }]); }); - it('should pass GDPR and US consent', function() { - expect(spec.getUserSyncs({ pixelEnabled: true }, {}, {gdprApplies: true, consentString: 'foo'}, '1NYN')).to.deep.equal([{ + it('should pass GDPR and US consent', function () { + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, { gdprApplies: true, consentString: 'foo' }, '1NYN')).to.deep.equal([{ type: 'image', url: `${usersyncUrl}?gdpr=1&gdpr_consent=foo&us_privacy=1NYN` }]); }); diff --git a/test/spec/modules/pairIdSystem_spec.js b/test/spec/modules/pairIdSystem_spec.js new file mode 100644 index 00000000000..4f75666affe --- /dev/null +++ b/test/spec/modules/pairIdSystem_spec.js @@ -0,0 +1,68 @@ +import { storage, pairIdSubmodule } from 'modules/pairIdSystem.js'; +import * as utils from 'src/utils.js'; + +describe('pairId', function () { + let sandbox; + let logErrorStub; + + beforeEach(() => { + sandbox = sinon.sandbox.create(); + logErrorStub = sandbox.stub(utils, 'logError'); + }); + afterEach(() => { + sandbox.restore(); + }); + + it('should log an error if no ID is found when getId', function() { + pairIdSubmodule.getId({ params: {} }); + expect(logErrorStub.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}) + }) +}); diff --git a/test/spec/modules/permutiveRtdProvider_spec.js b/test/spec/modules/permutiveRtdProvider_spec.js index 5030e662ea9..1f39d1a2cda 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,137 @@ 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) + 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() - for (const key in segments) { - if (key === 'ssp') { - expect(segments[key].cohorts).to.have.length(max) - } else { - expect(segments[key]).to.have.length(max) - } - } - }) - }) + const bidderConfig = {} - describe('Default segment targeting', function () { - it('sets segment targeting for Xandr', function () { - const data = transformedTargeting() - const adUnits = getAdUnits() - const config = getConfig() - - 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) + describe('Default segment targeting', function () { + it('sets segment targeting for Ozone', function () { + const data = transformedTargeting() + const adUnits = getAdUnits() + const config = getConfig() - function callback () { - adUnits.forEach(adUnit => { - adUnit.bids.forEach(bid => { - const { bidder, params } = bid + readAndSetCohorts({ adUnits }, config) - 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)) - } - }) + adUnits.forEach(adUnit => { + adUnit.bids.forEach(bid => { + const { bidder, params } = bid + + if (bidder === 'ozone') { + expect(deepAccess(params, 'customData.0.targeting.p_standard')).to.eql(data.ac.concat(data.ssp.cohorts)) + } }) - } + }) }) }) @@ -525,73 +585,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 +700,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 +714,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 9da242381be..9516f0402c1 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -1,3 +1,4 @@ +/* eslint-disable no-trailing-spaces */ import {expect} from 'chai'; import { PrebidServer as Adapter, @@ -7,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'; @@ -24,13 +25,15 @@ import 'modules/priceFloors.js'; import 'modules/consentManagement.js'; import 'modules/consentManagementUsp.js'; import 'modules/schain.js'; +import 'modules/fledgeForGpt.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', @@ -705,51 +708,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'); @@ -993,7 +998,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' } }); @@ -1020,7 +1025,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' } }); @@ -1382,7 +1387,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' } }); @@ -1505,6 +1510,28 @@ describe('S2S Adapter', function () { }); }); + it('site should not be present when app is present', function () { + const _config = { + s2sConfig: CONFIG, + app: { bundle: 'com.test.app' }, + site: { + publisher: { + id: '1234', + domain: 'test.com' + }, + content: { + language: 'en' + } + } + }; + + config.setConfig(_config); + 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'); + }); + it('adds appnexus aliases to request', function () { config.setConfig({ s2sConfig: CONFIG }); @@ -1703,6 +1730,121 @@ describe('S2S Adapter', function () { }]); }); + describe('filterSettings', function () { + const getRequestBid = userSync => { + let cookieSyncConfig = utils.deepClone(CONFIG); + 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); + } + + it('correctly adds filterSettings to the cookie_sync request if userSync.filterSettings is present in the config and only the all key is present in userSync.filterSettings', function () { + const userSync = { + filterSettings: { + all: { + bidders: ['appnexus', 'rubicon', 'pubmatic'], + filter: 'exclude' + } + } + }; + const requestBid = getRequestBid(userSync); + + expect(requestBid.filterSettings).to.deep.equal({ + 'image': { + 'bidders': ['appnexus', 'rubicon', 'pubmatic'], + 'filter': 'exclude' + }, + 'iframe': { + 'bidders': ['appnexus', 'rubicon', 'pubmatic'], + 'filter': 'exclude' + } + }); + }); + + 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: { + iframe: { + bidders: ['rubicon', 'pubmatic'], + filter: 'include' + } + } + }; + const requestBid = getRequestBid(userSync); + + expect(requestBid.filterSettings).to.deep.equal({ + 'image': { + 'bidders': '*', + 'filter': 'include' + }, + 'iframe': { + 'bidders': ['rubicon', 'pubmatic'], + 'filter': 'include' + } + }); + }); + + it('correctly adds filterSettings to the cookie_sync request if userSync.filterSettings is present in the config and the image and iframe keys are both present in userSync.filterSettings', function () { + const userSync = { + filterSettings: { + image: { + bidders: ['triplelift', 'appnexus'], + filter: 'include' + }, + iframe: { + bidders: ['pulsepoint', 'triplelift', 'appnexus', 'rubicon'], + filter: 'exclude' + } + } + }; + const requestBid = getRequestBid(userSync); + + expect(requestBid.filterSettings).to.deep.equal({ + 'image': { + 'bidders': ['triplelift', 'appnexus'], + 'filter': 'include' + }, + 'iframe': { + 'bidders': ['pulsepoint', 'triplelift', 'appnexus', 'rubicon'], + 'filter': 'exclude' + } + }); + }); + + it('correctly adds filterSettings to the cookie_sync request if userSync.filterSettings is present in the config and the all and iframe keys are both present in userSync.filterSettings', function () { + const userSync = { + filterSettings: { + all: { + bidders: ['triplelift', 'appnexus'], + filter: 'include' + }, + iframe: { + bidders: ['pulsepoint', 'triplelift', 'appnexus', 'rubicon'], + filter: 'exclude' + } + } + }; + const requestBid = getRequestBid(userSync); + + expect(requestBid.filterSettings).to.deep.equal({ + 'image': { + 'bidders': ['triplelift', 'appnexus'], + 'filter': 'include' + }, + 'iframe': { + 'bidders': ['pulsepoint', 'triplelift', 'appnexus', 'rubicon'], + 'filter': 'exclude' + } + }); + }); + }); + it('adds limit to the cookie_sync request if userSyncLimit is greater than 0', function () { let cookieSyncConfig = utils.deepClone(CONFIG); cookieSyncConfig.syncEndpoint = { p1Consent: 'https://prebid.adnxs.com/pbs/v1/cookie_sync' }; @@ -1806,7 +1948,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'); @@ -2522,6 +2664,62 @@ describe('S2S Adapter', function () { }); }); + describe('Bidder-level ortb2Imp', () => { + beforeEach(() => { + config.setConfig({ + s2sConfig: { + ...CONFIG, + bidders: ['A', 'B'] + } + }) + }) + it('should be set on imp.ext.prebid.imp', () => { + const s2sReq = utils.deepClone(REQUEST); + s2sReq.ad_units[0].ortb2Imp = {l0: 'adUnit'}; + s2sReq.ad_units[0].bids = [ + { + bidder: 'A', + bid_id: 1, + ortb2Imp: { + l2: 'A' + } + }, + { + bidder: 'B', + bid_id: 2, + ortb2Imp: { + l2: 'B' + } + } + ]; + const bidderReqs = [ + { + ...BID_REQUESTS[0], + bidderCode: 'A', + bids: [{ + bidId: 1, + bidder: 'A' + }] + }, + { + ...BID_REQUESTS[0], + bidderCode: 'B', + bids: [{ + bidId: 2, + bidder: 'B' + }] + } + ] + adapter.callBids(s2sReq, bidderReqs, addBidResponse, done, ajax); + const req = JSON.parse(server.requests[0].requestBody); + expect(req.imp[0].l0).to.eql('adUnit'); + expect(req.imp[0].ext.prebid.imp).to.eql({ + A: {l2: 'A'}, + B: {l2: 'B'} + }); + }); + }); + describe('ext.prebid config', function () { it('should send \"imp.ext.prebid.storedrequest.id\" if \"ortb2Imp.ext.prebid.storedrequest.id\" is set', function () { const consentConfig = { s2sConfig: CONFIG }; @@ -2615,28 +2813,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); @@ -2734,6 +2934,21 @@ describe('S2S Adapter', function () { expect(response).to.have.property('ttl', 60); }); + it('handles seatnonbid responses and calls 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); + }); + it('respects defaultTtl', function () { const s2sConfig = Object.assign({}, CONFIG, { defaultTtl: 30 @@ -2752,60 +2967,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, { @@ -2824,20 +3041,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 () { @@ -2856,18 +3075,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 () { @@ -2888,15 +3109,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 () { @@ -2908,16 +3132,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) { @@ -3072,6 +3298,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 b7d771814d0..f232631d73d 100644 --- a/test/spec/modules/priceFloors_spec.js +++ b/test/spec/modules/priceFloors_spec.js @@ -1495,6 +1495,161 @@ describe('the price floors module', function () { }); }); + it('should use inverseFloorAdjustment function before bidder cpm adjustment', function () { + let functionUsed; + getGlobal().bidderSettings = { + rubicon: { + bidCpmAdjustment: function (bidCpm, bidResponse) { + functionUsed = 'Rubicon Adjustment'; + bidCpm *= 0.5; + if (bidResponse.mediaType === 'video') bidCpm -= 0.18; + return bidCpm; + }, + inverseBidAdjustment: function (bidCpm, bidRequest) { + functionUsed = 'Rubicon Inverse'; + // if video is the only mediaType on Bid Request => add 0.18 + if (bidRequest.mediaTypes.video && Object.keys(bidRequest.mediaTypes).length === 1) bidCpm += 0.18; + return bidCpm / 0.5; + }, + }, + appnexus: { + bidCpmAdjustment: function (bidCpm, bidResponse) { + functionUsed = 'Appnexus Adjustment'; + bidCpm *= 0.75; + if (bidResponse.mediaType === 'video') bidCpm -= 0.18; + return bidCpm; + }, + inverseBidAdjustment: function (bidCpm, bidRequest) { + functionUsed = 'Appnexus Inverse'; + // if video is the only mediaType on Bid Request => add 0.18 + if (bidRequest.mediaTypes.video && Object.keys(bidRequest.mediaTypes).length === 1) bidCpm += 0.18; + return bidCpm / 0.75; + }, + } + }; + + _floorDataForAuction[bidRequest.auctionId] = utils.deepClone(basicFloorConfig); + + _floorDataForAuction[bidRequest.auctionId].data.values = { '*': 1.0 }; + + // start with banner as only mediaType + bidRequest.mediaTypes = { banner: { sizes: [[300, 250]] } }; + let appnexusBid = { + ...bidRequest, + bidder: 'appnexus', + }; + + // should be same as the adjusted calculated inverses above test (banner) + expect(bidRequest.getFloor()).to.deep.equal({ + currency: 'USD', + floor: 2.0 + }); + + // should use rubicon inverse + expect(functionUsed).to.equal('Rubicon Inverse'); + + // appnexus just using banner should be same + expect(appnexusBid.getFloor()).to.deep.equal({ + currency: 'USD', + floor: 1.3334 + }); + + expect(functionUsed).to.equal('Appnexus Inverse'); + + // now since asking for 'video' only mediaType inverse function should include the .18 + bidRequest.mediaTypes = { video: { context: 'instream' } }; + expect(bidRequest.getFloor({ mediaType: 'video' })).to.deep.equal({ + currency: 'USD', + floor: 2.36 + }); + + expect(functionUsed).to.equal('Rubicon Inverse'); + + // now since asking for 'video' inverse function should include the .18 + appnexusBid.mediaTypes = { video: { context: 'instream' } }; + expect(appnexusBid.getFloor({ mediaType: 'video' })).to.deep.equal({ + currency: 'USD', + floor: 1.5734 + }); + + expect(functionUsed).to.equal('Appnexus Inverse'); + }); + + it('should pass inverseFloorAdjustment the bidRequest object so it can be used', function () { + // Adjustment factors based on Bid Media Type + const mediaTypeFactors = { + banner: 0.5, + native: 0.7, + video: 0.9 + } + getGlobal().bidderSettings = { + rubicon: { + bidCpmAdjustment: function (bidCpm, bidResponse) { + return bidCpm * mediaTypeFactors[bidResponse.mediaType]; + }, + inverseBidAdjustment: function (bidCpm, bidRequest) { + // For the inverse we add up each mediaType in the request and divide by number of Mt's to get the inverse number + let factor = Object.keys(bidRequest.mediaTypes).reduce((sum, mediaType) => sum += mediaTypeFactors[mediaType], 0); + factor = factor / Object.keys(bidRequest.mediaTypes).length; + return bidCpm / factor; + }, + } + }; + + _floorDataForAuction[bidRequest.auctionId] = utils.deepClone(basicFloorConfig); + + _floorDataForAuction[bidRequest.auctionId].data.values = { '*': 1.0 }; + + // banner only should be 2 + bidRequest.mediaTypes = { banner: {} }; + expect(bidRequest.getFloor()).to.deep.equal({ + currency: 'USD', + floor: 2.0 + }); + + // native only should be 1.4286 + bidRequest.mediaTypes = { native: {} }; + expect(bidRequest.getFloor()).to.deep.equal({ + currency: 'USD', + floor: 1.4286 + }); + + // video only should be 1.1112 + bidRequest.mediaTypes = { video: {} }; + expect(bidRequest.getFloor()).to.deep.equal({ + currency: 'USD', + floor: 1.1112 + }); + + // video and banner should even out to 0.7 factor so 1.4286 + bidRequest.mediaTypes = { video: {}, banner: {} }; + expect(bidRequest.getFloor()).to.deep.equal({ + currency: 'USD', + floor: 1.4286 + }); + + // video and native should even out to 0.8 factor so -- 1.25 + bidRequest.mediaTypes = { video: {}, native: {} }; + expect(bidRequest.getFloor()).to.deep.equal({ + currency: 'USD', + floor: 1.25 + }); + + // banner and native should even out to 0.6 factor so -- 1.6667 + bidRequest.mediaTypes = { banner: {}, native: {} }; + expect(bidRequest.getFloor()).to.deep.equal({ + currency: 'USD', + floor: 1.6667 + }); + + // all 3 banner video and native should even out to 0.7 factor so -- 1.4286 + bidRequest.mediaTypes = { banner: {}, native: {}, video: {} }; + expect(bidRequest.getFloor()).to.deep.equal({ + currency: 'USD', + floor: 1.4286 + }); + }); + it('should use standard cpmAdjustment if no bidder cpmAdjustment', function () { getGlobal().bidderSettings = { rubicon: { diff --git a/test/spec/modules/proxistoreBidAdapter_spec.js b/test/spec/modules/proxistoreBidAdapter_spec.js index 5b9f35c5bcf..bcddb9e8b04 100644 --- a/test/spec/modules/proxistoreBidAdapter_spec.js +++ b/test/spec/modules/proxistoreBidAdapter_spec.js @@ -55,9 +55,9 @@ describe('ProxistoreBidAdapter', function () { }); describe('buildRequests', function () { const url = { - cookieBase: 'https://abs.proxistore.com/v3/rtb/prebid/multi', + cookieBase: 'https://api.proxistore.com/v3/rtb/prebid/multi', cookieLess: - 'https://abs.cookieless-proxistore.com/v3/rtb/prebid/multi', + 'https://api.cookieless-proxistore.com/v3/rtb/prebid/multi', }; let request = spec.buildRequests([bid], bidderRequest); diff --git a/test/spec/modules/pubCommonId_spec.js b/test/spec/modules/pubCommonId_spec.js index a46ff26c4b8..7c539014cc5 100644 --- a/test/spec/modules/pubCommonId_spec.js +++ b/test/spec/modules/pubCommonId_spec.js @@ -233,23 +233,6 @@ describe('Publisher Common ID', function () { }); }); }); - - 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 () { 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..bd35297b027 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'; @@ -1219,4 +1219,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 6b240cb2d06..3198fe406e7 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'; @@ -194,6 +194,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 +251,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 +325,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', @@ -389,6 +421,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 +482,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', @@ -503,6 +551,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', @@ -553,7 +609,8 @@ describe('PubMatic adapter', function () { 'ext': { 'deal_channel': 6, 'advid': 976, - 'dspid': 123 + 'dspid': 123, + 'dchain': 'dchain' } }] }, { @@ -602,7 +659,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 +671,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 +850,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 +938,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 +955,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,18 +1118,6 @@ 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' @@ -1998,29 +2045,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,112 +2548,19 @@ 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 () { + 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, { - auctionId: 'new-auction-id' + auctionId: 'new-auction-id', + ortb2: { + device: { + sua: suaObject + } + } }); 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]); + expect(data.device.sua).to.exist.and.to.be.an('object'); + expect(data.device.sua).to.deep.equal(suaObject); }); it('Request params should have valid native bid request for all valid params', function () { @@ -2639,13 +2595,6 @@ describe('PubMatic adapter', function () { expect(data.imp[0]['native']['request']).to.exist.and.to.equal(validnativeBidImpressionWithRequiredParam.native.request); }); - it('should not have valid native request if assets are not defined with minimum required params and only native is the slot', function () { - let request = spec.buildRequests(nativeBidRequestsWithoutAsset, { - auctionId: 'new-auction-id' - }); - expect(request).to.deep.equal(undefined); - }); - it('Request params should have valid native bid request for all native params', function () { let request = spec.buildRequests(nativeBidRequestsWithAllParams, { auctionId: 'new-auction-id' @@ -2658,127 +2607,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' @@ -2796,40 +2624,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() { @@ -2848,138 +2663,383 @@ describe('PubMatic adapter', function () { expect(data.native.request).to.exist; }); - it('Request params - banner and native multiformat request - should not 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.not.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 not 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.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]); }); - let data = JSON.parse(request.data); - data = data.imp[0]; - expect(data.video).to.exist; - expect(data.native).to.not.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] - ], - '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' + 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; }); - 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' + 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; }); - 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 - 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']['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 dctr check', function () { @@ -3420,7 +3480,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); @@ -3485,16 +3546,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() { @@ -3506,14 +3568,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' @@ -3523,28 +3577,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' @@ -3561,154 +3593,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 + } }, - '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' - }], + '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' + }, + '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() { @@ -3808,204 +3952,55 @@ describe('PubMatic adapter', function () { }); }); - 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' - } - - 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' + 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'], } + beforeEach(() => { + utilsMock = sinon.mock(utils); + sandbox = sinon.sandbox.create(); + sandbox.spy(utils, 'logWarn'); + }); - 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 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' - } - spec.transformBidParams(bidRequests[0].params, true, videoAdUnit); - expect(bidRequests[0].params.dctr).to.equal('key1:val1,val2|key2:val1'); + 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(() => { @@ -4013,112 +4008,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) - }); + 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; - 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 consider video params from mediaType object of bid', function () { - delete newVideoRequest[0].params.video; - - 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); - }); + let request = spec.buildRequests(newVideoRequest, { + auctionId: 'new-auction-id' + }); - 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 || {}; + sinon.assert.calledOnce(utils.logWarn); + expect(request).to.equal(undefined); }); - 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..780cc8b8fdb 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'; @@ -486,6 +486,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 = { @@ -555,14 +577,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 +604,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/pubxaiAnalyticsAdapter_spec.js b/test/spec/modules/pubxaiAnalyticsAdapter_spec.js index 1ad23b9a41e..1dce87e4b8e 100644 --- a/test/spec/modules/pubxaiAnalyticsAdapter_spec.js +++ b/test/spec/modules/pubxaiAnalyticsAdapter_spec.js @@ -567,7 +567,9 @@ describe('pubxai analytics adapter', function() { 'host': location.host, 'path': location.pathname, 'search': location.search, - 'adUnitCount': 1 + 'adUnits': [ + '/19968336/header-bid-tag-1' + ] }, 'floorDetail': { 'fetchStatus': 'success', diff --git a/test/spec/modules/pulsepointBidAdapter_spec.js b/test/spec/modules/pulsepointBidAdapter_spec.js index 825b3abf432..60dca9e6da0 100644 --- a/test/spec/modules/pulsepointBidAdapter_spec.js +++ b/test/spec/modules/pulsepointBidAdapter_spec.js @@ -2,7 +2,6 @@ import {expect} from 'chai'; import {spec} from 'modules/pulsepointBidAdapter.js'; import {deepClone} from 'src/utils.js'; -import { config } from 'src/config.js'; describe('PulsePoint Adapter Tests', function () { const slotConfigs = [{ @@ -225,6 +224,8 @@ describe('PulsePoint Adapter Tests', function () { 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); }); it('Verify parse response', function () { @@ -918,4 +919,13 @@ describe('PulsePoint Adapter Tests', function () { } }); }); + + it('Verify bid request timeouts', function () { + const mkRequest = (bidderRequest) => spec.buildRequests(slotConfigs, bidderRequest).data; + // assert default is used when no bidderRequest.timeout value is available + expect(mkRequest(bidderRequest).tmax).to.equal(500) + + // assert bidderRequest value is used when available + expect(mkRequest(Object.assign({}, { timeout: 6000 }, bidderRequest)).tmax).to.equal(6000) + }); }); diff --git a/test/spec/modules/realTimeDataModule_spec.js b/test/spec/modules/realTimeDataModule_spec.js index ced2f697649..f9c41b2fda0 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; 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..0f2f9abd583 100644 --- a/test/spec/modules/relaidoBidAdapter_spec.js +++ b/test/spec/modules/relaidoBidAdapter_spec.js @@ -1,13 +1,13 @@ -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(); +const storage = getCoreStorageManager(); storage.setCookie(UUID_KEY, relaido_uuid); describe('RelaidoAdapter', function () { 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/rtbhouseBidAdapter_spec.js b/test/spec/modules/rtbhouseBidAdapter_spec.js index 6d41df7605b..78c700f3804 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(); @@ -617,10 +653,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/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index e81ef1c805f..e3ebf15619d 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'; @@ -102,6 +111,9 @@ 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]], @@ -307,7 +319,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,6 +334,9 @@ 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]], @@ -415,7 +429,6 @@ describe('the rubicon adapter', function () { 'rand': '0.1', 'tk_flint': INTEGRATION, 'x_source.tid': 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b', - 'x_source.pchain': 'GAM:11111-reseller1:22222', 'p_screen_res': /\d+x\d+/, 'tk_user_key': '12346', 'kw': 'a,b,c', @@ -583,20 +596,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]); @@ -615,6 +619,7 @@ describe('the rubicon adapter', function () { 'rand': '0.1', 'tk_flint': INTEGRATION, 'x_source.tid': 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b', + 'x_imp.ext.tid': 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b', 'p_screen_res': /\d+x\d+/, 'tk_user_key': '12346', 'kw': 'a,b,c', @@ -1534,717 +1539,711 @@ describe('the rubicon adapter', function () { }); }); - describe('for video requests', function () { - it('should make a well-formed video request', function () { - createVideoBidderRequest(); - - 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); - }); - - describe('ortb2imp sent to video bids', function () { - beforeEach(function () { - // initialize - if (bidderRequest.bids[0].hasOwnProperty('ortb2Imp')) { - delete bidderRequest.bids[0].ortb2Imp; - } - }); - - it('should add ortb values to video requests', function () { + if (FEATURES.VIDEO) { + describe('for video requests', function () { + it('should make a well-formed video request', function () { createVideoBidderRequest(); 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' - } - } - } - } - - let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + let [request] = spec.buildRequests(bidderRequest.bids, syncAddFPDToBidderRequest(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(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; + } + }); - 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 add ortb values to video requests', function () { + createVideoBidderRequest(); - sandbox.stub(Date, 'now').callsFake(() => - bidderRequest.auctionStart + 100 - ); + sandbox.stub(Date, 'now').callsFake(() => + bidderRequest.auctionStart + 100 + ); - let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + bidderRequest.bids[0].ortb2Imp = { + ext: { + gpid: '/test/gpid', + data: { + pbadslot: '/test/pbadslot' + }, + prebid: { + storedauctionresponse: { + id: 'sample_video_response' + } + } + } + } - // make sure banner bid called with right stuff - expect( - bidderRequest.bids[0].getFloor.calledWith({ - currency: 'USD', - mediaType: 'video', - size: [640, 480] - }) - ).to.be.true; + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + let post = request.data; - // not an object should work and not send - expect(request.data.imp[0].bidfloor).to.be.undefined; + 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'); + }); + }); - // 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; + 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'); - // 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; + sandbox.stub(Date, 'now').callsFake(() => + bidderRequest.auctionStart + 100 + ); - // 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); + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - // 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 sure banner bid called with right stuff + expect( + bidderRequest.bids[0].getFloor.calledWith({ + currency: 'USD', + mediaType: '*', + size: '*' + }) + ).to.be.true; - 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 - ); + // not an object should work and not send + expect(request.data.imp[0].bidfloor).to.be.undefined; - let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + // 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; - // log error called - expect(logErrorSpy.calledOnce).to.equal(true); + // 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; - // should have an imp - expect(request.data.imp).to.exist.and.to.be.a('array'); - expect(request.data.imp).to.have.lengthOf(1); + // 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); - // should be NO bidFloor - expect(request.data.imp[0].bidfloor).to.be.undefined; - }); + // 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 add alias name to PBS Request', function () { - createVideoBidderRequest(); + it('should continue with auction if getFloor throws error', 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 + ); - bidderRequest.bidderCode = 'superRubicon'; - bidderRequest.bids[0].bidder = 'superRubicon'; - let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - // 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'}); + // 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 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 be NO bidFloor + expect(request.data.imp[0].bidfloor).to.be.undefined; + expect(request.data.imp[0].bidfloorcur).to.be.undefined; + }); - it('should add floors flag correctly to PBS Request', function () { - createVideoBidderRequest(); - let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + it('should add alias name to PBS Request', function () { + createVideoBidderRequest(); + adapterManager.aliasRegistry['superRubicon'] = 'rubicon'; + bidderRequest.bidderCode = 'superRubicon'; + bidderRequest.bids[0].bidder = 'superRubicon'; + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - // should not pass if undefined - expect(request.data.ext.prebid.floors).to.be.undefined; + // 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'}); - // 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 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'); + }); - it('should add multibid configuration to PBS Request', function () { - createVideoBidderRequest(); + it('should add floors flag correctly to PBS Request', function () { + createVideoBidderRequest(); + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - const multibid = [{ - bidder: 'bidderA', - maxBids: 2 - }, { - bidder: 'bidderB', - maxBids: 2 - }]; - const expected = [{ - bidder: 'bidderA', - maxbids: 2 - }, { - bidder: 'bidderB', - maxbids: 2 - }]; + // should not pass if undefined + expect(request.data.ext.prebid.floors).to.be.undefined; - config.setConfig({multibid: multibid}); + // 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 }); + }); - let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + it('should add multibid configuration to PBS Request', function () { + createVideoBidderRequest(); - // 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); - }); + const multibid = [{ + bidder: 'bidderA', + maxBids: 2 + }, { + bidder: 'bidderB', + maxBids: 2 + }]; + const expected = [{ + bidder: 'bidderA', + maxbids: 2 + }, { + bidder: 'bidderB', + maxbids: 2 + }]; + + config.setConfig({multibid: multibid}); - 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; + 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 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; + 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.not.be.undefined; - expect(payload.ext.prebid.analytics).to.deep.equal({'rubicon': {'client-analytics': true}}); - }); + expect(payload.ext.prebid.analytics).to.not.be.undefined; + expect(payload.ext.prebid.analytics).to.deep.equal({'rubicon': {'client-analytics': true}}); + }); - 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; + 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; - 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 () { - createVideoBidderRequest(); - config.setConfig({s2sConfig: {defaultTtl: 600}}); - 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]; - expect(imp.exp).to.equal(600); - }); + expect(payload.ext.prebid.analytics).to.be.undefined; + }); - 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; + 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; - // 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); - }); + // should exp set to the right value according to config + let imp = post.imp[0]; + expect(imp.exp).to.equal(600); + }); - 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); - }); + 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; - it('should send correct bidfloor to PBS', function () { - createVideoBidderRequest(); + // 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 = 0.1; - let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(request.data.imp[0].bidfloor).to.equal(0.1); + 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 = 5.5; - [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(request.data.imp[0].bidfloor).to.equal(5.5); + it('should send correct bidfloor to PBS', function () { + createVideoBidderRequest(); - bidderRequest.bids[0].params.floor = '1.7'; - [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(request.data.imp[0].bidfloor).to.equal(1.7); + 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 = 0; - [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(request.data.imp[0].bidfloor).to.equal(0); + bidderRequest.bids[0].params.floor = 5.5; + [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(request.data.imp[0].bidfloor).to.equal(5.5); - 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 = '1.7'; + [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(request.data.imp[0].bidfloor).to.equal(1.7); - bidderRequest.bids[0].params.floor = null; - [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(request.data.imp[0]).to.not.haveOwnProperty('bidfloor'); - }); + bidderRequest.bids[0].params.floor = 0; + [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(request.data.imp[0].bidfloor).to.equal(0); - 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); - }); + bidderRequest.bids[0].params.floor = undefined; + [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(request.data.imp[0]).to.not.haveOwnProperty('bidfloor'); - 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] + 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 () { + 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 = {}; + bid.params.video = {}; - 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); + const bidRequestCopy = utils.deepClone(bidderRequest.bids[0]); + expect(spec.isBidRequestValid(bidRequestCopy)).to.equal(true); - // change context to outstream, still true - bidRequestCopy.mediaTypes.video.context = 'outstream'; - expect(spec.isBidRequestValid(bidRequestCopy)).to.equal(true); + // change context to outstream, still true + bidRequestCopy.mediaTypes.video.context = 'outstream'; + expect(spec.isBidRequestValid(bidRequestCopy)).to.equal(true); - // change context to random, false now - bidRequestCopy.mediaTypes.video.context = 'random'; - expect(spec.isBidRequestValid(bidRequestCopy)).to.equal(false); + // change context to random, false now + bidRequestCopy.mediaTypes.video.context = 'random'; + expect(spec.isBidRequestValid(bidRequestCopy)).to.equal(false); - // change context to undefined, still false - bidRequestCopy.mediaTypes.video.context = undefined; - expect(spec.isBidRequestValid(bidRequestCopy)).to.equal(false); + // change context to undefined, still false + bidRequestCopy.mediaTypes.video.context = undefined; + expect(spec.isBidRequestValid(bidRequestCopy)).to.equal(false); - // remove context, still false - delete bidRequestCopy.mediaTypes.video.context; - expect(spec.isBidRequestValid(bidRequestCopy)).to.equal(false); - }); + // remove context, still false + delete bidRequestCopy.mediaTypes.video.context; + expect(spec.isBidRequestValid(bidRequestCopy)).to.equal(false); + }); - it('should enforce the new required mediaTypes.video params', function () { - createVideoBidderRequest(); + it('should enforce the new required mediaTypes.video params', function () { + createVideoBidderRequest(); - sandbox.stub(Date, 'now').callsFake(() => - bidderRequest.auctionStart + 100 - ); + sandbox.stub(Date, 'now').callsFake(() => + bidderRequest.auctionStart + 100 + ); - expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(true); + 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); + // 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); + // 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); + // 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); + // 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); + // 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); + // 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); + // 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); - }); + // delete api, no good + createVideoBidderRequest(); + delete bidderRequest.bids[0].mediaTypes.video.api; + 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 - ); + it('bid request is valid when video context is outstream', function () { + createVideoBidderRequestOutstream(); + sandbox.stub(Date, 'now').callsFake(() => + bidderRequest.auctionStart + 100 + ); - const bidRequestCopy = utils.deepClone(bidderRequest); + const bidRequestCopy = utils.deepClone(bidderRequest); - 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); - }); + 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); + }); - it('should send banner request when outstream or instream video included but no rubicon video obect is present', function () { + 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 = { + banner: { + sizes: [[300, 250]] + }, + video: { + context: 'outstream' + } + }; + // no video object in rubicon params, so we should see one call made for banner - sandbox.stub(Date, 'now').callsFake(() => - bidderRequest.auctionStart + 100 - ); + sandbox.stub(Date, 'now').callsFake(() => + bidderRequest.auctionStart + 100 + ); - let 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'); - bidderRequest.mediaTypes.video.context = 'instream'; + bidderRequest.mediaTypes.video.context = 'instream'; - requests = spec.buildRequests(bidderRequest.bids, bidderRequest); + 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(); + it('should send request as banner when invalid video bid in multiple mediaType bidRequest', function () { + createVideoBidderRequestNoVideo(); - let bid = bidderRequest.bids[0]; - bid.mediaTypes.banner = { - sizes: [[300, 250]] - }; + let bid = bidderRequest.bids[0]; + bid.mediaTypes.banner = { + sizes: [[300, 250]] + }; - sandbox.stub(Date, 'now').callsFake(() => - bidderRequest.auctionStart + 100 - ); + sandbox.stub(Date, 'now').callsFake(() => + bidderRequest.auctionStart + 100 + ); - const bidRequestCopy = utils.deepClone(bidderRequest); + const bidRequestCopy = utils.deepClone(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 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'); + }); - it('should include coppa flag in video bid request', () => { - createVideoBidderRequest(); + 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 + ); - sandbox.stub(config, 'getConfig').callsFake(key => { - const config = { - 'coppa': true - }; - return config[key]; + 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 [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(request.data.regs.coppa).to.equal(1); - }); - - it('should include first party data', () => { - createVideoBidderRequest(); + it('should include first party data', () => { + createVideoBidderRequest(); - const site = { - ext: { - data: { - page: 'home' - } - }, - content: { + 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 () { + 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 () { + 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 () { + 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', () => { - 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'}}); - 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 () { @@ -2366,6 +2365,110 @@ describe('the rubicon adapter', function () { 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 +3211,227 @@ describe('the rubicon adapter', function () { }); }); - describe('for video', function () { - beforeEach(function () { - createVideoBidderRequest(); - }); + if (FEATURES.VIDEO) { + 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' + it('should register a successful bid', function () { + 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 () { + createVideoBidderRequestOutstream(); + 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); - - 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'); - }); + afterEach(function () { + sandbox.restore(); + delete window.MagniteApex; + }); - 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 - } - }, - prebid: { - targeting: { - hb_uuid: '0c498f63-5111-4bed-98e2-9be7cb932a64' + it('should register a successful bid', function () { + 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' + } } - }, - nurl: 'https://test.com/vast.xml' + }], + group: 0, + seat: 'rubicon' }], - group: 0, - seat: 'rubicon' - }], - }; - - sinon.spy(window.MagniteApex, 'renderAd'); + }; - 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: { + 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', - attachTo: adUnit, - position: 'append', - }, - vastUrl: 'https://test.com/vast.xml', - width: 640 + closeButton: true + }); + expect(bids[0].renderer.url).to.equal('https://example.test/renderer.js'); + }); + + it('should render ad with Magnite renderer', function () { + 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' + }], + }; + + 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); }); - // cleanup - adUnit.parentNode.removeChild(adUnit); }); - }); + } describe('config with integration type', () => { it('should use the integration type provided in the config instead of the default', () => { @@ -3617,3 +3736,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 new file mode 100644 index 00000000000..7f0b13bc07b --- /dev/null +++ b/test/spec/modules/scatteredBidAdapter_spec.js @@ -0,0 +1,210 @@ +import { spec, converter } from 'modules/scatteredBidAdapter.js'; +import { assert } from 'chai'; +import { config } from 'src/config.js'; +import { deepClone, mergeDeep } from '../../../src/utils'; +describe('Scattered adapter', function () { + describe('isBidRequestValid', function () { + // A valid bid + let validBid = { + bidder: 'scattered', + mediaTypes: { + banner: { + sizes: [[300, 250], [760, 400]] + } + }, + params: { + bidderDomain: 'https://prebid.scattered.eu', + test: 0 + } + }; + + // Because this valid bid is modified to create invalid bids in following tests we first check it. + // We must be sure it is a valid one, not to get false negatives. + it('should accept a valid bid', function () { + assert.isTrue(spec.isBidRequestValid(validBid)); + }); + + it('should skip if bidderDomain info is missing', function () { + let bid = deepClone(validBid); + + delete bid.params.bidderDomain; + assert.isFalse(spec.isBidRequestValid(bid)); + }); + + it('should expect at least one banner size', function () { + let bid = deepClone(validBid); + + delete bid.mediaTypes.banner; + assert.isFalse(spec.isBidRequestValid(bid)); + + // empty sizes array + bid.mediaTypes = { + banner: { + sizes: [] + } + }; + assert.isFalse(spec.isBidRequestValid(bid)); + }); + }); + + describe('buildRequests', function () { + let arrayOfValidBidRequests, validBidderRequest; + + beforeEach(function () { + arrayOfValidBidRequests = [{ + bidder: 'scattered', + params: { + bidderDomain: 'https://prebid.scattered.eu', + test: 0 + }, + mediaTypes: { + banner: { + sizes: [[300, 250], [760, 400]] + }, + adUnitCode: 'test-div', + transactionId: '32d09c47-c6b8-40b0-9605-2e251a472ea4', + bidId: '21adc5d8765aa1', + bidderRequestId: '130728f7662afc', + auctionId: 'b4a45a23-8371-4d87-9308-39146b29ca32', + }, + }]; + + validBidderRequest = { + bidderCode: 'scattered', + auctionId: 'b4a45a23-8371-4d87-9308-39146b29ca32', + gdprConsent: { consentString: 'BOtmiBKOtmiBKABABAENAFAAAAACeAAA', gdprApplies: true }, + refererInfo: { + domain: 'localhost', + page: 'http://localhost:9999/integrationExamples/gpt/hello_world.html?pbjs_debug=true', + }, + ortb2: { + site: { + publisher: { + name: 'publisher1 INC.' + } + } + } + }; + }); + + it('should validate request format', function () { + let request = spec.buildRequests(arrayOfValidBidRequests, validBidderRequest); + assert.equal(request.method, 'POST'); + assert.deepEqual(request.options, { contentType: 'application/json' }); + assert.ok(request.data); + }); + + 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); + }); + + it('should configure the site object', function () { + let request = spec.buildRequests(arrayOfValidBidRequests, validBidderRequest); + const site = request.data.site; + assert.equal(site.publisher.name, validBidderRequest.ortb2.site.publisher.name) + }); + + it('should configure site with ortb2', function () { + const req = mergeDeep({}, validBidderRequest, { + ortb2: { + site: { + id: '876', + publisher: { + domain: 'publisher1.eu' + } + } + } + }); + + let request = spec.buildRequests(arrayOfValidBidRequests, req); + const site = request.data.site; + assert.deepEqual(site, { + id: '876', + publisher: { + domain: 'publisher1.eu', + name: 'publisher1 INC.' + } + }); + }); + + it('should send device info', function () { + it('should send info about device', function () { + config.setConfig({ + device: { w: 375, h: 273 } + }); + + let request = spec.buildRequests(arrayOfValidBidRequests, validBidderRequest); + + assert.equal(request.device.ua, navigator.userAgent); + assert.equal(request.device.w, 375); + assert.equal(request.device.h, 273); + }); + }) + }) +}) + +describe('interpretResponse', function () { + const serverResponse = { + body: { + id: 'b4a45a23-8371-4d87-9308-39146b29ca32', + bidid: '11111111-2222-2222-2222-333333333333', + cur: 'PLN', + seatbid: [{ + bid: [ + { + id: '234234-234234-234234', // bidder generated + impid: '123', + price: '34.2', + nurl: 'https://scattered.eu/nurl', + adm: '
', + cpm: '34.2', + creativeId: '2345-2345-23', + currency: 'PLN', + height: 456, + width: 345, + mediaType: 'banner', + requestId: '123', + ttl: 360, + }; + expect(results.length).to.equal(1); + sinon.assert.match(results[0], expected); + }); +}); diff --git a/test/spec/modules/seedingAllianceAdapter_spec.js b/test/spec/modules/seedingAllianceAdapter_spec.js index 81af9546ff0..6086db01de4 100755 --- a/test/spec/modules/seedingAllianceAdapter_spec.js +++ b/test/spec/modules/seedingAllianceAdapter_spec.js @@ -38,7 +38,7 @@ describe('SeedingAlliance adapter', function () { }); it('should have default request structure', function () { - let keys = 'site,device,cur,imp,user,regs'.split(','); + let keys = 'site,cur,imp,regs'.split(','); let validBidRequests = [{ bidId: 'bidId', params: {} @@ -60,14 +60,17 @@ describe('SeedingAlliance adapter', function () { assert.equal(request.id, validBidRequests[0].auctionId); }); - it('Verify the device', function () { + it('Verify the site url', function () { + let siteUrl = 'https://www.yourdomain.tld/your-directory/'; let validBidRequests = [{ bidId: 'bidId', - params: {} + params: { + url: siteUrl + } }]; let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }).data); - assert.equal(request.device.ua, navigator.userAgent); + assert.equal(request.site.page, siteUrl); }); it('Verify native asset ids', function () { @@ -109,7 +112,7 @@ describe('SeedingAlliance adapter', function () { }); describe('interpretResponse', function () { - const goodResponse = { + const goodNativeResponse = { body: { cur: 'EUR', id: '4b516b80-886e-4ec0-82ae-9209e6d625fb', @@ -136,51 +139,91 @@ describe('SeedingAlliance adapter', function () { ] } }; + + const goodBannerResponse = { + body: { + cur: 'EUR', + id: 'b4516b80-886e-4ec0-82ae-9209e6d625fb', + seatbid: [ + { + seat: 'seedingAlliance', + bid: [{ + adm: '', + impid: 1, + price: 0.90, + h: 250, + w: 300 + }] + } + ] + } + }; + const badResponse = { body: { cur: 'EUR', id: '4b516b80-886e-4ec0-82ae-9209e6d625fb', seatbid: [] }}; - const bidRequest = { + const bidNativeRequest = { data: {}, - bids: [{ bidId: 'bidId1' }] + bidRequests: [{bidId: 'bidId1', nativeParams: {title: {required: true, len: 800}}}] + }; + + const bidBannerRequest = { + data: {}, + bidRequests: [{bidId: 'bidId1', sizes: [300, 250]}] }; it('should return null if body is missing or empty', function () { - const result = spec.interpretResponse(badResponse, bidRequest); + const result = spec.interpretResponse(badResponse, bidNativeRequest); assert.equal(result.length, 0); delete badResponse.body - const result1 = spec.interpretResponse(badResponse, bidRequest); + const result1 = spec.interpretResponse(badResponse, bidNativeRequest); assert.equal(result.length, 0); }); it('should return the correct params', function () { - const result = spec.interpretResponse(goodResponse, bidRequest); - const bid = goodResponse.body.seatbid[0].bid[0]; - - assert.deepEqual(result[0].currency, goodResponse.body.cur); - assert.deepEqual(result[0].requestId, bidRequest.bids[0].bidId); - 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, 'seedingAlliance'); + 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); + assert.deepEqual(resultNative[0].creativeId, bidNative.crid); + assert.deepEqual(resultNative[0].mediaType, 'native'); + + 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]); }); - it('should return the correct tracking links', function () { - const result = spec.interpretResponse(goodResponse, bidRequest); - const bid = goodResponse.body.seatbid[0].bid[0]; + it('should return the correct native tracking links', function () { + const result = spec.interpretResponse(goodNativeResponse, bidNativeRequest); + const bid = goodNativeResponse.body.seatbid[0].bid[0]; const regExpPrice = new RegExp('price=' + bid.price); result[0].native.clickTrackers.forEach(function (clickTracker) { - assert.ok(clickTracker.search(regExpPrice) > -1); + assert.ok(clickTracker.search(regExpPrice) > -1); }); result[0].native.impressionTrackers.forEach(function (impTracker) { - assert.ok(impTracker.search(regExpPrice) > -1); + assert.ok(impTracker.search(regExpPrice) > -1); }); }); + + it('should return the correct banner content', function () { + const result = spec.interpretResponse(goodBannerResponse, bidBannerRequest); + const bid = goodBannerResponse.body.seatbid[0].bid[0]; + const regExpContent = new RegExp(''); + + assert.ok(result[0].ad.search(regExpContent) > -1); + }); }); }); diff --git a/test/spec/modules/seedtagBidAdapter_spec.js b/test/spec/modules/seedtagBidAdapter_spec.js index cfdd3365269..3627296975b 100644 --- a/test/spec/modules/seedtagBidAdapter_spec.js +++ b/test/spec/modules/seedtagBidAdapter_spec.js @@ -33,30 +33,60 @@ function createInStreamSlotConfig(mediaType) { }); } +const createBannerSlotConfig = (placement, mediatypes) => { + return getSlotConfigs(mediatypes || { banner: {} }, { + publisherId: PUBLISHER_ID, + adUnitId: ADUNIT_ID, + placement, + }); +}; + describe('Seedtag Adapter', function () { describe('isBidRequestValid method', function () { describe('returns true', function () { describe('when banner slot config has all mandatory params', () => { - describe('and placement has the correct value', function () { - const createBannerSlotConfig = (placement) => { - return getSlotConfigs( - { banner: {} }, - { - publisherId: PUBLISHER_ID, - adUnitId: ADUNIT_ID, - placement, - } + const placements = ['inBanner', 'inImage', 'inScreen', 'inArticle']; + placements.forEach((placement) => { + it(placement + 'should be valid', function () { + const isBidRequestValid = spec.isBidRequestValid( + createBannerSlotConfig(placement) ); - }; - const placements = ['inBanner', 'inImage', 'inScreen', 'inArticle']; - placements.forEach((placement) => { - it('should be ' + placement, function () { + expect(isBidRequestValid).to.equal(true); + }); + + it( + placement + + ' should be valid when has display and video mediatypes, and video context is outstream', + function () { const isBidRequestValid = spec.isBidRequestValid( - createBannerSlotConfig(placement) + createBannerSlotConfig(placement, { + banner: {}, + video: { + context: 'outstream', + playerSize: [[600, 200]], + }, + }) ); expect(isBidRequestValid).to.equal(true); - }); - }); + } + ); + + it( + placement + + " shouldn't be valid when has display and video mediatypes, and video context is instream", + function () { + const isBidRequestValid = spec.isBidRequestValid( + createBannerSlotConfig(placement, { + banner: {}, + video: { + context: 'instream', + playerSize: [[600, 200]], + }, + }) + ); + expect(isBidRequestValid).to.equal(false); + } + ); }); }); describe('when video slot has all mandatory params', function () { @@ -70,7 +100,18 @@ describe('Seedtag Adapter', function () { const isBidRequestValid = spec.isBidRequestValid(slotConfig); expect(isBidRequestValid).to.equal(true); }); - it('should return true, when video context is instream, but placement is not inStream', function () { + it('should return true, when video context is instream and mediatype is video and banner', function () { + const slotConfig = createInStreamSlotConfig({ + video: { + context: 'instream', + playerSize: [[600, 200]], + }, + banner: {}, + }); + const isBidRequestValid = spec.isBidRequestValid(slotConfig); + expect(isBidRequestValid).to.equal(true); + }); + it('should return false, when video context is instream, but placement is not inStream', function () { const slotConfig = getSlotConfigs( { video: { 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..fc4fbc86018 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' + }, + ] + }, + { + 'source': 'liveramp.com', + 'uids': [ + { + 'atype': 1, + 'id': 'fake-identity-link' + } + ] + }, + { + 'source': 'id5-sync.com', + 'uids': [ + { + 'atype': 1, + 'id': 'fake-id5id' + } + ] }, - lipb: { - lipbid: 'fake-lipbid', + { + 'source': 'adserver.org', + 'uids': [ + { + 'atype': 1, + 'id': 'fake-tdid' + } + ] }, - criteoId: 'fake-criteo', - britepoolid: 'fake-britepool', - intentIqId: 'fake-intentiq', - lotamePanoramaId: 'fake-lotame', - parrableId: { - eid: 'fake-parrable', + { + 'source': 'criteo.com', + 'uids': [ + { + 'atype': 1, + 'id': 'fake-criteo' + } + ] }, - netId: 'fake-netid', - sharedid: { - id: 'fake-sharedid', + { + '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', }, @@ -172,7 +248,8 @@ describe('sharethrough adapter spec', function () { refererInfo: { ref: 'https://referer.com', }, - auctionId: 'auction-id' + auctionId: 'auction-id', + timeout: 242 }; }); 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/sirdataRtdProvider_spec.js b/test/spec/modules/sirdataRtdProvider_spec.js index 9cf392ebd62..eccc4777906 100644 --- a/test/spec/modules/sirdataRtdProvider_spec.js +++ b/test/spec/modules/sirdataRtdProvider_spec.js @@ -1,17 +1,17 @@ -import { addSegmentData, getSegmentsAndCategories, sirdataSubmodule } from 'modules/sirdataRtdProvider.js'; -import { server } from 'test/mocks/xhr.js'; +import {addSegmentData, getSegmentsAndCategories, sirdataSubmodule} from 'modules/sirdataRtdProvider.js'; +import {server} from 'test/mocks/xhr.js'; const responseHeader = {'Content-Type': 'application/json'}; -describe('sirdataRtdProvider', function() { - describe('sirdataSubmodule', function() { +describe('sirdataRtdProvider', function () { + describe('sirdataSubmodule', function () { it('successfully instantiates', function () { - expect(sirdataSubmodule.init()).to.equal(true); + expect(sirdataSubmodule.init()).to.equal(true); }); }); - describe('Add Segment Data', function() { - it('adds segment data', function() { + describe('Add Segment Data', function () { + it('adds segment data', function () { const config = { params: { setGptKeyValues: false, @@ -42,23 +42,34 @@ describe('sirdataRtdProvider', function() { contextual_categories: {'333333': 100} }; - addSegmentData({adUnits}, data, config, () => {}); + addSegmentData({adUnits}, data, config, () => { + }); expect(adUnits[0].bids[0].params.keywords).to.have.deep.property('sd_rtd', ['111111', '222222', '333333']); - expect(adUnits[0].bids[1].ortb2.site.ext.data).to.have.deep.property('sd_rtd', ['333333']); - expect(adUnits[0].bids[1].ortb2.user.ext.data).to.have.deep.property('sd_rtd', ['111111', '222222']); }); }); - describe('Get Segments And Categories', function() { - it('gets data from async request and adds segment data', function() { + 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); + } + const config = { params: { setGptKeyValues: false, contextualMinRelevancyScore: 50, bidders: [{ - bidder: 'appnexus' + bidder: 'appnexus', + customFunction: overrideAppnexus }, { - bidder: 'other' + bidder: 'smartadserver' + }, { + bidder: 'ix', + sizeLimit: 1200, + }, { + bidder: 'rubicon', + }, { + bidder: 'proxistore', }] } }; @@ -71,24 +82,107 @@ describe('sirdataRtdProvider', function() { placementId: 13144370 } }, { - bidder: 'other' + bidder: 'smartadserver', + params: { + siteId: 207435, + pageId: 896536, + formatId: 62913 + } + }, { + bidder: 'proxistore', + params: {website: 'demo.sirdata.com', language: 'fr'}, + adUnitCode: 'HALFPAGE_CENTER_LOADER', + transactionId: '92ac333a-a569-4827-abf1-01fc9d19278a', + sizes: [[300, 600]], + mediaTypes: { + banner: { + filteredSizeConfig: [ + {minViewPort: [1600, 0], sizes: [[300, 600]]}, + ], + sizeConfig: [ + {minViewPort: [0, 0], sizes: [[300, 600]]}, + {minViewPort: [768, 0], sizes: [[300, 600]]}, + {minViewPort: [1200, 0], sizes: [[300, 600]]}, + {minViewPort: [1600, 0], sizes: [[300, 600]]}, + ], + sizes: [[300, 600]], + }, + }, + bidId: '190bab495bc5f6e', + bidderRequestId: '18c0b0f0c91cd88', + auctionId: '9bdd917b-908d-4d9f-8f2f-d443277a62fc', + src: 'client', + bidRequestsCount: 1, + bidderRequestsCount: 1, + bidderWinsCount: 0, + }, { + bidder: 'ix', + params: { + siteId: '12345', + size: [300, 600] + } + }, { + bidder: 'rubicon', + params: { + accountId: 14062, + siteId: 70608, + zoneId: 498816 + } }] - }] + }], + ortb2Fragments: { + global: {} + } }; let data = { - segments: [111111, 222222], - contextual_categories: {'333333': 100} + 'segments': [111111, 222222], + 'segtaxid': null, + 'cattaxid': null, + 'contextual_categories': {'333333': 100}, + 'shared_taxonomy': { + '27440': { + 'segments': [444444, 555555], + 'segtaxid': 552, + 'cattaxid': 553, + 'contextual_categories': {'666666': 100} + } + }, + 'global_taxonomy': { + '9998': { + 'segments': [123, 234], + 'segtaxid': 4, + 'cattaxid': 7, + 'contextual_categories': {'345': 100, '456': 100} + } + } }; - getSegmentsAndCategories(reqBidsConfigObj, () => {}, config, {}); + getSegmentsAndCategories(reqBidsConfigObj, () => { + }, config, {}); let request = server.requests[0]; request.respond(200, responseHeader, JSON.stringify(data)); - expect(reqBidsConfigObj.adUnits[0].bids[0].params.keywords).to.have.deep.property('sd_rtd', ['111111', '222222', '333333']); - expect(reqBidsConfigObj.adUnits[0].bids[1].ortb2.site.ext.data).to.have.deep.property('sd_rtd', ['333333']); - expect(reqBidsConfigObj.adUnits[0].bids[1].ortb2.user.ext.data).to.have.deep.property('sd_rtd', ['111111', '222222']); + 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' + ); + expect(reqBidsConfigObj.ortb2Fragments.global.site.content.data[0].segment).to.eql([ + {id: '345'}, + {id: '456'} + ]); + expect(reqBidsConfigObj.ortb2Fragments.global.site.content.data[0].ext.segtax).to.equal(7); + + expect(reqBidsConfigObj.ortb2Fragments.global.user.data[0].name).to.equal( + 'sirdata.com' + ); + expect(reqBidsConfigObj.ortb2Fragments.global.user.data[0].segment).to.eql([ + {id: '123'}, + {id: '234'} + ]); + expect(reqBidsConfigObj.ortb2Fragments.global.user.data[0].ext.segtax).to.equal(4); }); }); }); 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/smaatoBidAdapter_spec.js b/test/spec/modules/smaatoBidAdapter_spec.js index c7dbf1363e7..7f727d5d9e3 100644 --- a/test/spec/modules/smaatoBidAdapter_spec.js +++ b/test/spec/modules/smaatoBidAdapter_spec.js @@ -297,6 +297,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 +356,13 @@ describe('smaatoBidAdapterTest', () => { keywords: 'a,b', gender: 'M', yob: 1984 + }, + device: { + ifa: 'ifa', + geo: { + lat: 53.5488, + lon: 9.9872 + } } }; @@ -353,6 +375,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', () => { @@ -992,6 +1017,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 db61983c9c9..504ff978e9e 100644 --- a/test/spec/modules/smartadserverBidAdapter_spec.js +++ b/test/spec/modules/smartadserverBidAdapter_spec.js @@ -1,4 +1,5 @@ import { expect } from 'chai'; +import { BANNER, VIDEO } from 'src/mediaTypes.js'; import { config } from 'src/config.js'; import { spec } from 'modules/smartadserverBidAdapter.js'; @@ -56,18 +57,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 @@ -394,7 +457,6 @@ describe('Smart bid adapter tests', function () { afterEach(function () { config.setConfig({ ortb2: undefined }); config.resetConfig(); - $$PREBID_GLOBAL$$.requestBids.removeAll(); }); it('Verify build request with GDPR', function () { @@ -443,10 +505,33 @@ describe('Smart bid adapter tests', function () { }); }); + describe('GPP', function () { + it('should be added to payload when gppConsent available in bidder request', function () { + const options = { + gppConsent: { + gppString: 'some-gpp-string', + applicableSections: [3, 5] + } + }; + const request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, options); + const payload = JSON.parse(request[0].data); + + expect(payload).to.have.property('gpp').and.to.equal(options.gppConsent.gppString); + expect(payload).to.have.property('gpp_sid').and.to.be.an('array'); + expect(payload.gpp_sid).to.have.lengthOf(2).and.to.deep.equal(options.gppConsent.applicableSections); + }); + + it('should be undefined on payload when gppConsent unavailable in bidder request', function () { + const request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, {}); + const payload = JSON.parse(request[0].data); + + expect(payload.gpp).to.be.undefined; + }); + }); + describe('ccpa/us privacy tests', function () { afterEach(function () { config.resetConfig(); - $$PREBID_GLOBAL$$.requestBids.removeAll(); }); it('Verify build request with us privacy', function () { @@ -475,7 +560,6 @@ describe('Smart bid adapter tests', function () { describe('Instream video tests', function () { afterEach(function () { config.resetConfig(); - $$PREBID_GLOBAL$$.requestBids.removeAll(); }); const INSTREAM_DEFAULT_PARAMS = [{ @@ -746,7 +830,6 @@ describe('Smart bid adapter tests', function () { describe('Outstream video tests', function () { afterEach(function () { config.resetConfig(); - $$PREBID_GLOBAL$$.requestBids.removeAll(); }); const OUTSTREAM_DEFAULT_PARAMS = [{ @@ -1055,6 +1138,17 @@ describe('Smart bid adapter tests', function () { }); describe('Floors module', function () { + const getFloor = (bid) => { + switch (bid.mediaType) { + case BANNER: + return { currency: 'USD', floor: 1.93 }; + case VIDEO: + return { currency: 'USD', floor: 2.72 }; + default: + return {}; + } + }; + it('should include floor from bid params', function() { const bidRequest = JSON.parse((spec.buildRequests(DEFAULT_PARAMS))[0].data); expect(bidRequest.bidfloor).to.deep.equal(DEFAULT_PARAMS[0].params.bidfloor); @@ -1094,12 +1188,91 @@ describe('Smart bid adapter tests', function () { const floor = spec.getBidFloor(bidRequest, null); expect(floor).to.deep.equal(0); }); + + it('should take floor from bidder params over ad unit', function() { + const bidRequest = [{ + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + getFloor, + params: { siteId: 1234, pageId: 678, formatId: 73, bidfloor: 1.25 } + }]; + + const request = spec.buildRequests(bidRequest); + const requestContent = JSON.parse(request[0].data); + + expect(requestContent).to.have.property('bidfloor').and.to.equal(1.25); + }); + + it('should take floor from banner ad unit', function() { + const bidRequest = [{ + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + getFloor, + params: { siteId: 1234, pageId: 678, formatId: 73 } + }]; + + const request = spec.buildRequests(bidRequest); + const requestContent = JSON.parse(request[0].data); + + expect(requestContent).to.have.property('bidfloor').and.to.equal(1.93); + }); + + it('should take floor from video ad unit', function() { + const bidRequest = [{ + mediaTypes: { + video: { + context: 'outstream', + playerSize: [[640, 480]] + } + }, + getFloor, + params: { siteId: 1234, pageId: 678, formatId: 73 } + }]; + + const request = spec.buildRequests(bidRequest); + const requestContent = JSON.parse(request[0].data); + + expect(requestContent).to.have.property('bidfloor').and.to.equal(2.72); + }); + + it('should take floor from multiple media type ad unit', function() { + const bidRequest = [{ + mediaTypes: { + banner: { + sizes: [[300, 600]] + }, + video: { + context: 'outstream', + playerSize: [[640, 480]] + } + }, + getFloor, + params: { siteId: 1234, pageId: 678, formatId: 73 } + }]; + + const requests = spec.buildRequests(bidRequest); + expect(requests).to.have.lengthOf(2); + + const requestContents = requests.map(r => JSON.parse(r.data)); + const videoRequest = requestContents.filter(r => r.videoData)[0]; + expect(videoRequest).to.not.equal(null).and.to.not.be.undefined; + expect(videoRequest).to.have.property('bidfloor').and.to.equal(2.72); + + const bannerRequest = requestContents.filter(r => !r.videoData)[0]; + expect(bannerRequest).to.not.equal(null).and.to.not.be.undefined; + expect(bannerRequest).to.have.property('bidfloor').and.to.equal(1.93); + }); }); describe('Verify bid requests with multiple mediaTypes', function () { afterEach(function () { config.resetConfig(); - $$PREBID_GLOBAL$$.requestBids.removeAll(); }); var DEFAULT_PARAMS_MULTIPLE_MEDIA_TYPES = [{ 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/smartytechBidAdapter_spec.js b/test/spec/modules/smartytechBidAdapter_spec.js index 78503d7a6f0..6b3147859bf 100644 --- a/test/spec/modules/smartytechBidAdapter_spec.js +++ b/test/spec/modules/smartytechBidAdapter_spec.js @@ -19,20 +19,116 @@ describe('SmartyTechDSPAdapter: inherited functions', function () { describe('SmartyTechDSPAdapter: isBidRequestValid', function () { it('Invalid bid request. Should return false', function () { - const invalidBidFixture = { + const bidFixture = { params: { use_id: 13144375 } } - expect(spec.isBidRequestValid(invalidBidFixture)).to.be.false + + expect(spec.isBidRequestValid(bidFixture)).to.be.false }); it('Valid bid request. Should return true', function () { - const validBidFixture = { + const bidFixture = { params: { endpointId: 13144375 } } - expect(spec.isBidRequestValid(validBidFixture)).to.be.true + expect(spec.isBidRequestValid(bidFixture)).to.be.true + }); + + it('Invalid bid request. Check video block', function () { + const bidFixture = { + params: { + endpointId: 1 + }, + mediaTypes: { + video: {} + } + } + expect(spec.isBidRequestValid(bidFixture)).to.be.false + }); + + it('Invalid bid request. Check playerSize', function () { + const bidFixture = { + params: { + endpointId: 1 + }, + mediaTypes: { + video: { + playerSize: '300x250' + } + } + } + expect(spec.isBidRequestValid(bidFixture)).to.be.false + }); + + it('Invalid bid request. Check context', function () { + const bidFixture = { + params: { + endpointId: 1 + }, + mediaTypes: { + video: { + playerSize: [300, 250] + } + } + } + expect(spec.isBidRequestValid(bidFixture)).to.be.false + }); + + it('Valid bid request. valid video bid', function () { + const bidFixture = { + params: { + endpointId: 1 + }, + mediaTypes: { + video: { + playerSize: [300, 250], + context: 'instream' + } + } + } + expect(spec.isBidRequestValid(bidFixture)).to.be.true + }); + + it('Invalid bid request. Check banner block', function () { + const bidFixture = { + params: { + endpointId: 1 + }, + mediaTypes: { + banner: {} + } + } + expect(spec.isBidRequestValid(bidFixture)).to.be.false + }); + + it('Invalid bid request. Check banner sizes', function () { + const bidFixture = { + params: { + endpointId: 1 + }, + mediaTypes: { + banner: { + sizes: '300x250' + } + } + } + expect(spec.isBidRequestValid(bidFixture)).to.be.false + }); + + it('Valid bid request. valid banner bid', function () { + const bidFixture = { + params: { + endpointId: 1 + }, + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + } + } + expect(spec.isBidRequestValid(bidFixture)).to.be.true }); }); @@ -42,16 +138,38 @@ function mockRandomSizeArray(len) { }); } -function mockBidRequestListData(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 = { + video: { + playerSize: mockRandomSizeArray(1), + context: 'instream' + }, + } + } else { + mediaTypes = { + banner: { + sizes: mockRandomSizeArray(index + 1) + } + } + } + + if (customSizes === undefined || customSizes.length > 0) { + params.sizes = customSizes + } + return { adUnitCode: `adUnitCode-${id}`, - sizes: mockRandomSizeArray(index + 1), + mediaTypes: mediaTypes, bidId: `bidId-${id}`, - params: { - endpointId: id - } + params: params } }); } @@ -66,18 +184,27 @@ function mockRefererData() { function mockResponseData(requestData) { let data = {} - requestData.data.forEach((request, index) => { - const sizeArrayIndex = Math.floor(Math.random() * (request.sizes.length - 1)); const rndIndex = Math.floor(Math.random() * 800); + let width, height, mediaType; + if (request.video !== undefined) { + width = request.video.playerSize[0][0]; + height = request.video.playerSize[0][1]; + mediaType = 'video'; + } else { + width = request.banner.sizes[0][0]; + height = request.banner.sizes[0][1]; + mediaType = 'banner'; + } data[request.adUnitCode] = { ad: `ad-${rndIndex}`, - width: request.sizes[sizeArrayIndex][0], - height: request.sizes[sizeArrayIndex][1], + width: width, + height: height, creativeId: `creative-id-${index}`, cpm: Math.floor(Math.random() * 100), - currency: `UAH-${rndIndex}` + currency: `UAH-${rndIndex}`, + mediaType: mediaType }; }); return { @@ -89,7 +216,7 @@ describe('SmartyTechDSPAdapter: buildRequests', () => { let mockBidRequest; let mockReferer; beforeEach(() => { - mockBidRequest = mockBidRequestListData(8); + mockBidRequest = mockBidRequestListData('banner', 8, []); mockReferer = mockRefererData(); }); it('has return data', () => { @@ -108,7 +235,7 @@ describe('SmartyTechDSPAdapter: buildRequests', () => { const data = spec.buildRequests(mockBidRequest, mockReferer).data; data.forEach((request, index) => { expect(request.adUnitCode).to.be.equal(mockBidRequest[index].adUnitCode); - expect(request.sizes).to.be.equal(mockBidRequest[index].sizes); + expect(request.banner).to.be.equal(mockBidRequest[index].mediaTypes.banner); expect(request.bidId).to.be.equal(mockBidRequest[index].bidId); expect(request.endpointId).to.be.equal(mockBidRequest[index].params.endpointId); expect(request.referer).to.be.equal(mockReferer.refererInfo.page); @@ -116,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(2); + const brData = mockBidRequestListData('banner', 2, []); mockReferer = mockRefererData(); request = spec.buildRequests(brData, mockReferer); mockBidRequest = { @@ -157,6 +316,42 @@ describe('SmartyTechDSPAdapter: interpretResponse', () => { expect(responseItem.requestId).to.be.equal(mockBidRequest.data[index].bidId); expect(responseItem.width).to.be.equal(mockResponse.body[keys[index]].width); expect(responseItem.height).to.be.equal(mockResponse.body[keys[index]].height); + expect(responseItem.mediaType).to.be.equal(mockResponse.body[keys[index]].mediaType); + }); + }); +}); + +describe('SmartyTechDSPAdapter: interpretResponse video', () => { + let mockBidRequest; + let mockReferer; + let request; + let mockResponse; + beforeEach(() => { + const brData = mockBidRequestListData('video', 2, []); + mockReferer = mockRefererData(); + request = spec.buildRequests(brData, mockReferer); + mockBidRequest = { + data: brData + } + mockResponse = mockResponseData(request); + }); + + it('interpretResponse: convert to correct data', () => { + const keys = Object.keys(mockResponse.body); + const data = spec.interpretResponse(mockResponse, mockBidRequest); + + data.forEach((responseItem, index) => { + expect(responseItem.ad).to.be.equal(mockResponse.body[keys[index]].ad); + expect(responseItem.cpm).to.be.equal(mockResponse.body[keys[index]].cpm); + expect(responseItem.creativeId).to.be.equal(mockResponse.body[keys[index]].creativeId); + expect(responseItem.currency).to.be.equal(mockResponse.body[keys[index]].currency); + expect(responseItem.netRevenue).to.be.true; + expect(responseItem.ttl).to.be.equal(60); + expect(responseItem.requestId).to.be.equal(mockBidRequest.data[index].bidId); + expect(responseItem.width).to.be.equal(mockResponse.body[keys[index]].width); + expect(responseItem.height).to.be.equal(mockResponse.body[keys[index]].height); + expect(responseItem.mediaType).to.be.equal(mockResponse.body[keys[index]].mediaType); + expect(responseItem.vastXml).to.be.equal(mockResponse.body[keys[index]].ad); }); }); }); diff --git a/test/spec/modules/smilewantedBidAdapter_spec.js b/test/spec/modules/smilewantedBidAdapter_spec.js index b9a816cf3d5..44d2c7c6507 100644 --- a/test/spec/modules/smilewantedBidAdapter_spec.js +++ b/test/spec/modules/smilewantedBidAdapter_spec.js @@ -1,9 +1,6 @@ import { expect } from 'chai'; import { spec } from 'modules/smilewantedBidAdapter.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'; const DISPLAY_REQUEST = [{ adUnitCode: 'sw_300x250', @@ -20,6 +17,37 @@ const DISPLAY_REQUEST = [{ transactionId: 'trans_abcd1234' }]; +const DISPLAY_REQUEST_WITH_EIDS = [{ + adUnitCode: 'sw_300x250', + bidId: '12345', + sizes: [ + [300, 250], + [300, 200] + ], + bidder: 'smilewanted', + params: { + zoneId: 1 + }, + requestId: 'request_abcd1234', + transactionId: 'trans_abcd1234', + userIdAsEids: [{ + source: 'pubcid.org', + uids: [{ + id: 'some-random-id-value', + atype: 1 + }] + }, { + source: 'adserver.org', + uids: [{ + id: 'some-random-id-value', + atype: 1, + ext: { + rtiPartner: 'TDID' + } + }] + }] +}]; + const DISPLAY_REQUEST_WITH_POSITION_TYPE = [{ adUnitCode: 'sw_300x250', bidId: '12345', @@ -170,6 +198,29 @@ describe('smilewantedBidAdapterTests', function () { expect(requestContent).to.have.property('pageDomain').and.to.equal('https://localhost/Prebid.js/integrationExamples/gpt/hello_world.html'); }); + it('Verify external ids in request and ids found', function () { + config.setConfig({ + 'currency': { + 'adServerCurrency': 'EUR' + } + }); + const request = spec.buildRequests(DISPLAY_REQUEST_WITH_EIDS, {}); + const requestContent = JSON.parse(request[0].data); + + expect(requestContent).to.have.property('eids'); + expect(requestContent.eids).to.not.equal(null).and.to.not.be.undefined; + expect(requestContent.eids.length).to.greaterThan(0); + for (let index in requestContent.eids) { + let eid = requestContent.eids[index]; + expect(eid.source).to.not.equal(null).and.to.not.be.undefined; + expect(eid.uids).to.not.equal(null).and.to.not.be.undefined; + for (let uidsIndex in eid.uids) { + let uid = eid.uids[uidsIndex]; + expect(uid.id).to.not.equal(null).and.to.not.be.undefined; + } + } + }); + describe('gdpr tests', function () { afterEach(function () { config.resetConfig(); diff --git a/test/spec/modules/sonobiBidAdapter_spec.js b/test/spec/modules/sonobiBidAdapter_spec.js index 7803cfff394..b9bd0dc4d9f 100644 --- a/test/spec/modules/sonobiBidAdapter_spec.js +++ b/test/spec/modules/sonobiBidAdapter_spec.js @@ -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,6 +294,7 @@ describe('SonobiBidAdapter', function () { }, mediaTypes: { video: { + sizes: [[300, 250], [300, 600]], context: 'outstream' } } @@ -331,7 +339,7 @@ describe('SonobiBidAdapter', function () { }]; let keyMakerData = { - '30b31c1838de1f': '1a2b3c4d5e6f1a2b3c4d||f=1.25,gpid=/123123/gpt_publisher/adunit-code-1,c=v,', + '30b31c1838de1f': '1a2b3c4d5e6f1a2b3c4d|300x250,300x600|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,', }; @@ -370,7 +378,7 @@ describe('SonobiBidAdapter', function () { } } }; - const bidRequests = spec.buildRequests(bidRequest, {...bidderRequests, ortb2}); + const bidRequests = spec.buildRequests(bidRequest, { ...bidderRequests, ortb2 }); expect(bidRequests.data.fpd).to.equal(JSON.stringify(ortb2)); }); @@ -388,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) @@ -397,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'); @@ -539,7 +553,7 @@ describe('SonobiBidAdapter', function () { ]); }); - it('should return a properly formatted request with userid as a JSON-encoded set of User ID results', function () { + it('should return a properly formatted request with the userid value omitted when the userId object is present on the bidRequest. ', function () { bidRequest[0].userId = { 'pubcid': 'abcd-efg-0101', 'tdid': 'td-abcd-efg-0101', 'id5id': { 'uid': 'ID5-ZHMOrVeUVTUKgrZ-a2YGxeh5eS_pLzHCQGYOEAiTBQ', 'ext': { 'linkType': 2 } } }; bidRequest[1].userId = { 'pubcid': 'abcd-efg-0101', 'tdid': 'td-abcd-efg-0101', 'id5id': { 'uid': 'ID5-ZHMOrVeUVTUKgrZ-a2YGxeh5eS_pLzHCQGYOEAiTBQ', 'ext': { 'linkType': 2 } } }; const bidRequests = spec.buildRequests(bidRequest, bidderRequests); @@ -547,29 +561,7 @@ describe('SonobiBidAdapter', function () { expect(bidRequests.method).to.equal('GET'); expect(bidRequests.data.ref).not.to.be.empty; expect(bidRequests.data.s).not.to.be.empty; - expect(JSON.parse(bidRequests.data.userid)).to.eql({ 'pubcid': 'abcd-efg-0101', 'tdid': 'td-abcd-efg-0101', 'id5id': 'ID5-ZHMOrVeUVTUKgrZ-a2YGxeh5eS_pLzHCQGYOEAiTBQ' }); - }); - - it('should return a properly formatted request with userid omitted if there are no userIds', function () { - bidRequest[0].userId = {}; - bidRequest[1].userId = {}; - const bidRequests = spec.buildRequests(bidRequest, bidderRequests); - expect(bidRequests.url).to.equal('https://apex.go.sonobi.com/trinity.json'); - expect(bidRequests.method).to.equal('GET'); - expect(bidRequests.data.ref).not.to.be.empty; - expect(bidRequests.data.s).not.to.be.empty; - expect(bidRequests.data.userid).to.equal(undefined); - }); - - it('should return a properly formatted request with userid omitted', function () { - bidRequest[0].userId = undefined; - bidRequest[1].userId = undefined; - const bidRequests = spec.buildRequests(bidRequest, bidderRequests); - expect(bidRequests.url).to.equal('https://apex.go.sonobi.com/trinity.json'); - expect(bidRequests.method).to.equal('GET'); - expect(bidRequests.data.ref).not.to.be.empty; - expect(bidRequests.data.s).not.to.be.empty; - expect(bidRequests.data.userid).to.equal(undefined); + expect(bidRequests.data.userid).to.be.undefined; }); it('should return a properly formatted request with keywrods included as a csv of strings', function () { diff --git a/test/spec/modules/sovrnBidAdapter_spec.js b/test/spec/modules/sovrnBidAdapter_spec.js index 02a3d86b29a..831d3ae0315 100644 --- a/test/spec/modules/sovrnBidAdapter_spec.js +++ b/test/spec/modules/sovrnBidAdapter_spec.js @@ -248,6 +248,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 +335,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] diff --git a/test/spec/modules/sspBCBidAdapter_spec.js b/test/spec/modules/sspBCBidAdapter_spec.js index d182b9db24c..8c9bbe3b336 100644 --- a/test/spec/modules/sspBCBidAdapter_spec.js +++ b/test/spec/modules/sspBCBidAdapter_spec.js @@ -557,6 +557,7 @@ describe('SSPBC adapter', function () { const nativeAssets = payloadNative.imp && payloadNative.imp[0].native.request; expect(payloadNative.imp.length).to.equal(1); + expect(nativeAssets).to.contain('{"id":0,"required":true,"title":{"len":80}}'); expect(nativeAssets).to.contain('{"id":2,"required":true,"img":{"type":1,"w":50,"h":50}}'); expect(nativeAssets).to.contain('{"id":3,"required":true,"img":{"type":3,"w":150,"h":50}}'); @@ -610,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'); + expect(resultSingle[0]).to.have.keys('ad', 'cpm', 'width', 'height', 'bidderCode', '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'); + expect(resultOneCode[0]).to.have.keys('ad', 'cpm', 'width', 'height', 'bidderCode', '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 () { @@ -648,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'); + expect(videoBid).to.have.keys('adType', 'bidderCode', '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>$/); @@ -662,8 +663,8 @@ 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'); - expect(nativeBid.native).to.have.keys('image', 'icon', 'title', 'sponsoredBy', 'body', 'clickUrl', 'impressionTrackers', 'javascriptTrackers'); + expect(nativeBid).to.have.keys('bidderCode', '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..c2806317ee6 100644 --- a/test/spec/modules/stroeerCoreBidAdapter_spec.js +++ b/test/spec/modules/stroeerCoreBidAdapter_spec.js @@ -636,10 +636,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 +656,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', () => { diff --git a/test/spec/modules/stvBidAdapter_spec.js b/test/spec/modules/stvBidAdapter_spec.js new file mode 100644 index 00000000000..d095fd3cf55 --- /dev/null +++ b/test/spec/modules/stvBidAdapter_spec.js @@ -0,0 +1,402 @@ +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', + }, + { + '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&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/synacormediaBidAdapter_spec.js b/test/spec/modules/synacormediaBidAdapter_spec.js index 0773d29789d..747dc4edc63 100644 --- a/test/spec/modules/synacormediaBidAdapter_spec.js +++ b/test/spec/modules/synacormediaBidAdapter_spec.js @@ -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', diff --git a/test/spec/modules/taboolaBidAdapter_spec.js b/test/spec/modules/taboolaBidAdapter_spec.js index 17cc5fc0213..206a0142043 100644 --- a/test/spec/modules/taboolaBidAdapter_spec.js +++ b/test/spec/modules/taboolaBidAdapter_spec.js @@ -460,7 +460,9 @@ describe('Taboola Adapter', function () { 'w': 300, 'h': 250, 'exp': 60, - 'lurl': 'http://us-trc.taboola.com/sample' + 'lurl': 'http://us-trc.taboola.com/sample', + 'nurl': 'http://win.example.com/', + } ], 'seat': '14204545260' @@ -546,7 +548,8 @@ describe('Taboola Adapter', function () { 'w': 300, 'h': 250, 'exp': 60, - 'lurl': 'http://us-trc.taboola.com/sample' + 'lurl': 'http://us-trc.taboola.com/sample', + 'nurl': 'http://win.example.com/' }, { 'id': '0b3dd94348-134b-435f-8db5-6bf5afgfc39e86c', @@ -562,7 +565,9 @@ describe('Taboola Adapter', function () { 'w': 300, 'h': 250, 'exp': 60, - 'lurl': 'http://us-trc.taboola.com/sample' + 'lurl': 'http://us-trc.taboola.com/sample', + 'nurl': 'http://win.example.com/' + } ], 'seat': '14204545260' @@ -586,6 +591,7 @@ describe('Taboola Adapter', function () { ad: multiServerResponse.body.seatbid[0].bid[0].adm, width: bid.w, height: bid.h, + nurl: 'http://win.example.com/', meta: { 'advertiserDomains': bid.adomain }, @@ -601,6 +607,7 @@ describe('Taboola Adapter', function () { ad: multiServerResponse.body.seatbid[0].bid[1].adm, width: bid.w, height: bid.h, + nurl: 'http://win.example.com/', meta: { 'advertiserDomains': bid.adomain }, @@ -625,6 +632,7 @@ describe('Taboola Adapter', function () { ad: bid.adm, width: bid.w, height: bid.h, + nurl: 'http://win.example.com/', meta: { 'advertiserDomains': bid.adomain }, @@ -651,6 +659,7 @@ describe('Taboola Adapter', function () { ad: bid.adm, width: bid.w, height: bid.h, + nurl: 'http://win.example.com/', meta: { 'advertiserDomains': bid.adomain }, @@ -659,6 +668,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/targetVideoBidAdapter_spec.js b/test/spec/modules/targetVideoBidAdapter_spec.js index adbc982e509..8180183e6d7 100644 --- a/test/spec/modules/targetVideoBidAdapter_spec.js +++ b/test/spec/modules/targetVideoBidAdapter_spec.js @@ -58,7 +58,7 @@ describe('TargetVideo Bid Adapter', function() { 'uuid': '84ab500420319d', 'ads': [{ 'ad_type': 'video', - 'cpm': 0.500000, + 'cpm': 0.675000, 'notify_url': 'https://www.target-video.com/', 'rtb': { 'video': { @@ -87,7 +87,7 @@ describe('TargetVideo Bid Adapter', function() { const bid = bidResponse[0]; expect(bid).to.not.be.empty; - expect(bid.cpm).to.equal(0.675); + expect(bid.cpm).to.equal(0.5); expect(bid.width).to.equal(300); expect(bid.height).to.equal(250); expect(bid.ad).to.include('') diff --git a/test/spec/modules/tripleliftBidAdapter_spec.js b/test/spec/modules/tripleliftBidAdapter_spec.js index 5cfa64184f9..718d030be91 100644 --- a/test/spec/modules/tripleliftBidAdapter_spec.js +++ b/test/spec/modules/tripleliftBidAdapter_spec.js @@ -139,15 +139,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 +165,8 @@ describe('triplelift adapter', function () { mediaTypes: { video: { context: 'instream', - playerSize: [640, 480] + playerSize: [640, 480], + playbackmethod: 5 } }, adUnitCode: 'adunit-code-instream', @@ -177,6 +176,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 +252,11 @@ describe('triplelift adapter', function () { auctionId: '1d1a030790a475', userId: {}, schain, + ortb2Imp: { + misc: { + test: 1 + } + } }, // incomplete banner and incomplete video { @@ -292,7 +305,8 @@ describe('triplelift adapter', function () { mediaTypes: { video: { context: 'instream', - playerSize: [640, 480] + playerSize: [640, 480], + playbackmethod: [1, 2, 3] }, banner: { sizes: [ @@ -687,6 +701,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; @@ -725,11 +772,11 @@ describe('triplelift adapter', function () { it('should add amxRtbId to the payload if included', function () { const id = 'Ok9JQkBM-UFlAXEZQ-UUNBQlZOQzgrUFhW-UUNBQkRQTUBPQVpVWVxNXlZUUF9AUFhAUF9PXFY/'; - bidRequests[0].userIdAsEids = [{ source: 'amxrtb.com', uids: [{ id }] }]; + bidRequests[0].userIdAsEids = [{ source: 'amxdt.net', uids: [{ id }] }]; const request = tripleliftAdapterSpec.buildRequests(bidRequests, bidderRequest); const payload = request.data; expect(payload).to.exist; - expect(payload.user).to.deep.equal({ext: {eids: [{source: 'amxrtb.com', uids: [{id, ext: {rtiPartner: 'amxrtb.com'}}]}]}}); + expect(payload.user).to.deep.equal({ext: {eids: [{source: 'amxdt.net', uids: [{id, ext: {rtiPartner: 'amxdt.net'}}]}]}}); }); it('should add tdid, idl_env and criteoId to the payload if both are included', function () { @@ -1101,10 +1148,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=="}'); @@ -1168,6 +1215,29 @@ describe('triplelift adapter', function () { } }) }); + it('should add gpp consent data to bid request object if gpp data exists', function() { + bidderRequest.ortb2 = { + regs: { + 'gpp': 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==', + 'gpp_sid': [7] + } + } + const request = tripleliftAdapterSpec.buildRequests(bidRequests, bidderRequest); + expect(request.data.regs).to.deep.equal({ + 'gpp': 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==', + '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 () { @@ -1424,6 +1494,13 @@ describe('triplelift adapter', function () { expect(result[0].meta.advertiserDomains[1]).to.equal('internetalerts.org'); expect(result[1].meta).to.not.have.key('advertiserDomains'); }); + + it('should include networkId in the meta field if available', function () { + let result = tripleliftAdapterSpec.interpretResponse(response, {bidderRequest}); + expect(result[1].meta.networkId).to.equal('10092'); + expect(result[2].meta.networkId).to.equal('5989'); + expect(result[3].meta.networkId).to.equal('5989'); + }); }); describe('getUserSyncs', function() { diff --git a/test/spec/modules/ttdBidAdapter_spec.js b/test/spec/modules/ttdBidAdapter_spec.js index 9869d072657..37d1bac40ee 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', @@ -270,6 +286,21 @@ describe('ttdBidAdapter', function () { expect(requestBody.imp[0].ext.gpid).to.equal(gpid); }); + 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 auction id in source.tid', function () { const requestBody = testBuildRequests(baseBannerBidRequests, baseBidderRequest).data; expect(requestBody.source).to.be.not.null; @@ -477,13 +508,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 +531,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 +561,17 @@ 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); + }); }); describe('buildRequests-banner-multiple', function () { @@ -650,6 +687,10 @@ describe('ttdBidAdapter', function () { }); describe('buildRequests-display-video-multiformat', function () { + if (!FEATURES.VIDEO) { + return; + } + const baseMultiformatBidRequests = [{ 'bidder': 'ttd', 'params': { @@ -718,6 +759,10 @@ describe('ttdBidAdapter', function () { }); describe('buildRequests-video', function () { + if (!FEATURES.VIDEO) { + return; + } + const baseVideoBidRequests = [{ 'bidder': 'ttd', 'params': { @@ -859,6 +904,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 +1198,10 @@ describe('ttdBidAdapter', function () { }); describe('interpretResponse-simple-video', function () { + if (!FEATURES.VIDEO) { + return; + } + const incoming = { 'body': { 'cur': 'USD', @@ -1277,6 +1334,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/underdogmediaBidAdapter_spec.js b/test/spec/modules/underdogmediaBidAdapter_spec.js index 70d09513f27..4201ecc2329 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: { + ref: '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' + }, ] } }; @@ -326,17 +991,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 +1011,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 +1031,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 +1051,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 +1088,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/userId_spec.js b/test/spec/modules/userId_spec.js index 99e0681e547..a2f2bfd8713 100644 --- a/test/spec/modules/userId_spec.js +++ b/test/spec/modules/userId_spec.js @@ -27,7 +27,7 @@ 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'; @@ -53,6 +53,8 @@ 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'; let assert = require('chai').assert; let expect = require('chai').expect; @@ -147,6 +149,7 @@ describe('User ID', function () { hook.ready(); uninstallGdprEnforcement(); localStorage.removeItem(PBJS_USER_ID_OPTOUT_NAME); + liveIntentIdSubmoduleDoNotFireEvent(); }); beforeEach(function () { @@ -165,6 +168,20 @@ 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 @@ -425,6 +442,58 @@ describe('User ID', function () { }); }); + 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); @@ -897,7 +966,7 @@ describe('User ID', function () { storage: {name: 'intentIqId', type: 'cookie'} }, { name: 'hadronId', - storage: {name: 'hadronId', type: 'cookie'} + storage: {name: 'hadronId', type: 'html5'} }, { name: 'zeotapIdPlus' }, { @@ -1298,7 +1367,7 @@ describe('User ID', function () { expect(bid).to.have.deep.nested.property('userId.amxId'); expect(bid.userId.amxId).to.equal('test_amxid_id'); expect(bid.userIdAsEids[0]).to.deep.equal({ - source: 'amxrtb.com', + source: 'amxdt.net', uids: [{ id: 'test_amxid_id', atype: 1, @@ -1872,8 +1941,8 @@ describe('User ID', function () { it('test hook from hadronId html5', function (done) { // simulate existing browser local storage values - localStorage.setItem('hadronId', JSON.stringify({'hadronId': 'random-ls-identifier'})); - localStorage.setItem('hadronId_exp', ''); + localStorage.setItem('hadronId', JSON.stringify({'hadronId': 'testHadronId1'})); + localStorage.setItem('hadronId_exp', (new Date(Date.now() + 5000)).toUTCString()); init(config); setSubmoduleRegistry([hadronIdSubmodule]); @@ -1883,15 +1952,15 @@ describe('User ID', function () { adUnits.forEach(unit => { unit.bids.forEach(bid => { expect(bid).to.have.deep.nested.property('userId.hadronId'); - expect(bid.userId.hadronId).to.equal('random-ls-identifier'); + expect(bid.userId.hadronId).to.equal('testHadronId1'); expect(bid.userIdAsEids[0]).to.deep.equal({ source: 'audigent.com', - uids: [{id: 'random-ls-identifier', atype: 1}] + uids: [{id: 'testHadronId1', atype: 1}] }); }); }); localStorage.removeItem('hadronId'); - localStorage.removeItem('hadronId_exp', ''); + localStorage.removeItem('hadronId_exp'); done(); }, {adUnits}); }); @@ -2125,7 +2194,9 @@ describe('User ID', function () { coreStorage.setCookie('netId', JSON.stringify({'netId': 'testnetId'}), (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('intentIqId', 'testintentIqId', (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('IDP', btoa(JSON.stringify('zeotapId')), (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('hadronId', JSON.stringify({'hadronId': 'testHadronId'}), (new Date(Date.now() + 5000).toUTCString())); + // hadronId only supports localStorage + localStorage.setItem('hadronId', JSON.stringify({'hadronId': 'testHadronId1'})); + localStorage.setItem('hadronId_exp', (new Date(Date.now() + 5000)).toUTCString()); coreStorage.setCookie('storage_criteo', JSON.stringify({'criteoId': 'test_bidid'}), (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('mwol', JSON.stringify({eid: 'XX-YY-ZZ-123'}), (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('uid2id', 'Sample_AD_Token', (new Date(Date.now() + 5000).toUTCString())); @@ -2149,7 +2220,7 @@ describe('User ID', function () { ['netId', 'netId', 'cookie'], ['intentIqId', 'intentIqId', 'cookie'], ['zeotapIdPlus', 'IDP', 'cookie'], - ['hadronId', 'hadronId', 'cookie'], + ['hadronId', 'hadronId', 'html5'], ['criteo', 'storage_criteo', 'cookie'], ['mwOpenLinkId', 'mwol', 'cookie'], ['tapadId', 'tapad_id', 'cookie'], @@ -2192,7 +2263,7 @@ describe('User ID', function () { expect(bid.userId.IDP).to.equal('zeotapId'); // also check that hadronId id was copied to bid expect(bid).to.have.deep.nested.property('userId.hadronId'); - expect(bid.userId.hadronId).to.equal('testHadronId'); + expect(bid.userId.hadronId).to.equal('testHadronId1'); // also check that criteo id was copied to bid expect(bid).to.have.deep.nested.property('userId.criteoId'); expect(bid.userId.criteoId).to.equal('test_bidid'); @@ -2231,7 +2302,8 @@ describe('User ID', function () { coreStorage.setCookie('netId', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('intentIqId', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('IDP', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('hadronId', '', EXPIRED_COOKIE_DATE); + localStorage.removeItem('hadronId'); + localStorage.removeItem('hadronId_exp'); coreStorage.setCookie('storage_criteo', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('mwol', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('uid2id', '', EXPIRED_COOKIE_DATE); @@ -2284,7 +2356,8 @@ describe('User ID', function () { coreStorage.setCookie('netId', JSON.stringify({'netId': 'testnetId'}), new Date(Date.now() + 5000).toUTCString()); coreStorage.setCookie('intentIqId', 'testintentIqId', (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('IDP', btoa(JSON.stringify('zeotapId')), (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('hadronId', JSON.stringify({'hadronId': 'testHadronId'}), (new Date(Date.now() + 5000).toUTCString())); + localStorage.setItem('hadronId', JSON.stringify({'hadronId': 'testHadronId1'})); + localStorage.setItem('hadronId_exp', (new Date(Date.now() + 5000)).toUTCString()); coreStorage.setCookie('admixerId', 'testadmixerId', new Date(Date.now() + 5000).toUTCString()); coreStorage.setCookie('deepintentId', 'testdeepintentId', new Date(Date.now() + 5000).toUTCString()); coreStorage.setCookie('MOCKID', JSON.stringify({'MOCKID': '123456778'}), new Date(Date.now() + 5000).toUTCString()); @@ -2320,7 +2393,7 @@ describe('User ID', function () { }, { name: 'zeotapIdPlus' }, { - name: 'hadronId', storage: {name: 'hadronId', type: 'cookie'} + name: 'hadronId', storage: {name: 'hadronId', type: 'html5'} }, { name: 'admixerId', storage: {name: 'admixerId', type: 'cookie'} }, { @@ -2388,7 +2461,7 @@ describe('User ID', function () { expect(bid.userId.IDP).to.equal('zeotapId'); // also check that hadronId id data was copied to bid expect(bid).to.have.deep.nested.property('userId.hadronId'); - expect(bid.userId.hadronId).to.equal('testHadronId'); + expect(bid.userId.hadronId).to.equal('testHadronId1'); expect(bid.userId.uid2).to.deep.equal({ id: 'Sample_AD_Token' }); @@ -2420,7 +2493,8 @@ describe('User ID', function () { coreStorage.setCookie('netId', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('intentIqId', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('IDP', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('hadronId', '', EXPIRED_COOKIE_DATE); + localStorage.removeItem('hadronId'); + localStorage.removeItem('hadronId_exp'); coreStorage.setCookie('dmdId', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('admixerId', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('deepintentId', '', EXPIRED_COOKIE_DATE); diff --git a/test/spec/modules/vidazooBidAdapter_spec.js b/test/spec/modules/vidazooBidAdapter_spec.js index 2ddb65469af..0429a2a51bf 100644 --- a/test/spec/modules/vidazooBidAdapter_spec.js +++ b/test/spec/modules/vidazooBidAdapter_spec.js @@ -1,4 +1,4 @@ -import { expect } from 'chai'; +import {expect} from 'chai'; import { spec as adapter, SUPPORTED_ID_SYSTEMS, @@ -13,11 +13,13 @@ import { getUniqueDealId, getNextDealId, getVidazooSessionId, + webSessionId } from 'modules/vidazooBidAdapter.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'; const SUB_DOMAIN = 'openrtb'; @@ -38,6 +40,10 @@ const BID = { '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], @@ -53,6 +59,10 @@ const VIDEO_BID = { 'adUnitCode': '63550ad1ff6642d368cba59dh5884270560', 'bidderRequestId': '12a8ae9ada9c13', 'transactionId': '56e184c6-bde9-497b-b9b9-cf47a61381ee', + 'auctionId': 'auction_id', + 'bidRequestsCount': 4, + 'bidderRequestsCount': 3, + 'bidderWinsCount': 1, 'schain': 'a0819c69-005b-41ed-af06-1be1e0aefefc', 'params': { 'subDomain': SUB_DOMAIN, @@ -85,6 +95,8 @@ const BIDDER_REQUEST = { 'consentString': 'consent_string', 'gdprApplies': true }, + 'gppString': 'gpp_string', + 'gppSid': [7], 'uspConsent': 'consent_string', 'refererInfo': { 'page': 'https://www.greatsite.com', @@ -94,6 +106,28 @@ const BIDDER_REQUEST = { 'site': { 'cat': ['IAB2'], 'pagecat': ['IAB2-2'] + }, + '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': '' + } } }, }; @@ -147,7 +181,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 ''; @@ -226,6 +260,9 @@ describe('VidazooBidAdapter', 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({ @@ -243,6 +280,15 @@ describe('VidazooBidAdapter', function () { gdpr: 1, gdprConsent: 'consent_string', usPrivacy: 'consent_string', + gppString: 'gpp_string', + gppSid: [7], + auctionId: 'auction_id', + bidRequestsCount: 4, + bidderRequestsCount: 3, + bidderWinsCount: 1, + bidderTimeout: 3000, + transactionId: '56e184c6-bde9-497b-b9b9-cf47a61381ee', + bidderRequestId: '12a8ae9ada9c13', gpid: '', prebidVersion: version, ptrace: '1000', @@ -253,9 +299,26 @@ 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, + webSessionId: webSessionId, mediaTypes: { video: { api: [2], @@ -278,6 +341,9 @@ describe('VidazooBidAdapter', function () { }); it('should build banner request for each size', function () { + config.setConfig({ + bidderTimeout: 3000 + }); const hashUrl = hashCode(BIDDER_REQUEST.refererInfo.page); const requests = adapter.buildRequests([BID], BIDDER_REQUEST); expect(requests).to.have.length(1); @@ -288,7 +354,32 @@ describe('VidazooBidAdapter', function () { 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, @@ -311,7 +402,8 @@ describe('VidazooBidAdapter', function () { isStorageAllowed: true, gpid: '1234567890', cat: ['IAB2'], - pagecat: ['IAB2-2'] + pagecat: ['IAB2-2'], + webSessionId: webSessionId } }); }); @@ -324,7 +416,7 @@ describe('VidazooBidAdapter', 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', @@ -333,7 +425,7 @@ describe('VidazooBidAdapter', 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.cootlogix.com/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=' @@ -341,7 +433,7 @@ describe('VidazooBidAdapter', 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.cootlogix.com/api/sync/image/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=', @@ -357,12 +449,12 @@ describe('VidazooBidAdapter', 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; }); @@ -385,6 +477,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); @@ -422,11 +527,11 @@ describe('VidazooBidAdapter', function () { 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; } @@ -445,18 +550,18 @@ describe('VidazooBidAdapter', 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'); @@ -569,7 +674,7 @@ describe('VidazooBidAdapter', 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'); @@ -585,8 +690,8 @@ describe('VidazooBidAdapter', 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/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/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/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/vrtcalBidAdapter_spec.js b/test/spec/modules/vrtcalBidAdapter_spec.js index d911745d378..491c96df5e2 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 } ]; @@ -95,7 +96,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/yahoosspBidAdapter_spec.js b/test/spec/modules/yahoosspBidAdapter_spec.js index 01583ac30dc..a326b22ecb4 100644 --- a/test/spec/modules/yahoosspBidAdapter_spec.js +++ b/test/spec/modules/yahoosspBidAdapter_spec.js @@ -872,7 +872,7 @@ describe('YahooSSP Bid Adapter:', () => { expect(data.user.ext.eids).to.deep.equal([ {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: 'amxrtb.com', uids: [{id: 'amxId_FROM_USER_ID_MODULE', atype: 1}]}, + {source: 'amxdt.net', uids: [{id: 'amxId_FROM_USER_ID_MODULE', atype: 1}]}, {source: 'britepool.com', uids: [{id: 'britepoolid_FROM_USER_ID_MODULE', atype: 3}]}, {source: 'deepintent.com', uids: [{id: 'deepintentId_FROM_USER_ID_MODULE', atype: 3}]}, {source: 'epsilon.com', uids: [{id: 'publinkId_FROM_USER_ID_MODULE', atype: 3}]}, 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/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 d6befa0fc78..c3678427a9a 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,12 +51,14 @@ describe('Zeta Ssp Bid Adapter', function () { }, sid: 'publisherId', shortname: 'test_shortname', + tagid: 'test_tag_id', site: { page: 'testPage' }, app: { bundle: 'testBundle' }, + bidfloor: 0.2, test: 1 }; @@ -102,6 +120,7 @@ describe('Zeta Ssp Bid Adapter', function () { gdprApplies: 1, consentString: 'consentString' }, + schain: schain, uspConsent: 'someCCPAString', params: params, userIdAsEids: eids, @@ -359,4 +378,30 @@ describe('Zeta Ssp Bid Adapter', function () { expect(payload.tmax).to.be.undefined; }); + + it('Test provide bidfloor', function () { + const request = spec.buildRequests(bannerRequest, bannerRequest[0]); + const payload = JSON.parse(request.data); + + 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); + }); }); diff --git a/test/spec/modules/zeusPrimeRtdProvider_spec.js b/test/spec/modules/zeusPrimeRtdProvider_spec.js deleted file mode 100644 index 294d98df377..00000000000 --- a/test/spec/modules/zeusPrimeRtdProvider_spec.js +++ /dev/null @@ -1,410 +0,0 @@ -import { zeusPrimeSubmodule } from 'modules/zeusPrimeRtdProvider'; -import { server } from 'test/mocks/xhr.js'; -import * as utils from 'src/utils'; - -async function waitForStatus(statusVar) { - const MAX_COUNT = 20; - let count = 0; - while ( - count <= MAX_COUNT && - window.zeusPrime.status && - window.zeusPrime.status[statusVar] !== true - ) { - count += 1; - await new Promise((resolve) => setTimeout(resolve, 10)); - } - - if (count === MAX_COUNT) { - throw new Error('Timeout waiting for zeusPrimeRtdProvider to complete'); - } -} - -/** - * Execute all the commands in the googletag.cmd queue. - */ -function executeGoogletagTargeting() { - window.googletag.cmd.forEach((cmd) => cmd()); -} - -describe('Zeus Prime RTD submodule', () => { - let logErrorSpy; - let logMessageSpy; - let setTargetingStub; - let originalGtag; - - beforeEach(() => { - logErrorSpy = sinon.spy(utils, 'logError'); - logMessageSpy = sinon.spy(utils, 'logMessage'); - setTargetingStub = sinon.stub(); - window.zeusPrime = { cmd: [] }; - originalGtag = window.googletag; - window.googletag = { - cmd: [], - pubads: () => ({ - setTargeting: setTargetingStub, - }), - }; - - // Mock subtle since this doesnt exists in some test environments due to security in newer browsers. - if (typeof window.crypto.subtle === 'undefined') { - Object.defineProperty(crypto, 'subtle', { value: { digest: () => 'mockHash' } }) - } - }); - - afterEach(() => { - logErrorSpy.restore(); - logMessageSpy.restore(); - window.googletag = originalGtag; - }); - - it('should init and set key-value for zeus_', async () => { - zeusPrimeSubmodule.init({ - params: { - gamId: '1234', - hostname: 'www.example.com', - pathname: '/', - }, - }); - - // wait for the script to finish - await waitForStatus('scriptComplete'); - - // execute any googletag commands added to the queue during execution - executeGoogletagTargeting(); - - expect(window.googletag.cmd).to.have.length(1); - sinon.assert.callCount(setTargetingStub, 1); - sinon.assert.calledWith(setTargetingStub, 'zeus_1234', 'www.example.com'); - }); - - it('should init and set key-value for zeus_ from command queue', async () => { - window.zeusPrime.cmd.push((prime) => (prime.gamId = '9876')); - zeusPrimeSubmodule.init({ - params: { - hostname: 'www.example.com', - pathname: '/', - }, - }); - - // wait for the script to finish - await waitForStatus('scriptComplete'); - - // execute any googletag commands added to the queue during execution - executeGoogletagTargeting(); - - expect(window.googletag.cmd).to.have.length(1); - sinon.assert.callCount(setTargetingStub, 1); - sinon.assert.calledWith(setTargetingStub, 'zeus_9876', 'www.example.com'); - }); - - it('should init with values from location and set key-value for zeus_', async () => { - zeusPrimeSubmodule.init({ params: { gamId: '1234' } }); - - // wait for the script to finish - await waitForStatus('scriptComplete'); - - // execute any googletag commands added to the queue during execution - executeGoogletagTargeting(); - - expect(window.googletag.cmd).to.have.length(1); - sinon.assert.callCount(setTargetingStub, 1); - sinon.assert.calledWith(setTargetingStub, 'zeus_1234', 'localhost'); - expect(window.zeusPrime.pathname).to.equal('/context.html'); - }); - - it('should emit error when gamId is not set', async () => { - zeusPrimeSubmodule.init({}); - - // wait for the script to finish - await waitForStatus('scriptComplete'); - - // execute any googletag commands added to the queue during execution - executeGoogletagTargeting(); - - expect(window.googletag.cmd).to.have.length(0); - sinon.assert.callCount(setTargetingStub, 0); - sinon.assert.calledWith( - logErrorSpy, - 'zeusPrimeRtdProvider: ', - 'Failed to run.', - 'window.zeusPrime.gamId must be a string. Received: undefined' - ); - }); - - it('should not make a call to the server when url is a homepage', async () => { - zeusPrimeSubmodule.init({ - params: { - gamId: '1234', - hostname: 'www.example.com', - pathname: '/', - }, - }); - - // wait for the script to finish - await waitForStatus('scriptComplete'); - - expect(server.requests).to.have.length(0); - }); - - it('should make a call to the server and set key-vlaue when url is an article page and returns topics', async () => { - zeusPrimeSubmodule.init({ - params: { - gamId: '1234', - hostname: 'www.example.com', - pathname: '/article/some-article', - }, - }); - - // Wait for request to be sent - await waitForStatus('insightsReqReceived'); - - // Response - server.requests[0].respond( - 200, - { 'Content-Type': 'application/json' }, - '{"topics": ["bs0"]}' - ); - - // Wait for the script to process the response - await waitForStatus('scriptComplete'); - - // execute any googletag commands added to the queue during execution - executeGoogletagTargeting(); - - expect(server.requestCount).to.be.equal(1); - expect(window.googletag.cmd).to.have.length(2); - sinon.assert.calledTwice(setTargetingStub); - sinon.assert.calledWith( - setTargetingStub.firstCall, - 'zeus_1234', - 'www.example.com' - ); - sinon.assert.calledWith(setTargetingStub.secondCall, 'zeus_insights', [ - 'bs0', - ]); - sinon.assert.notCalled(logErrorSpy); - }); - - it('should not set insights keyvalue when server returns 204', async () => { - zeusPrimeSubmodule.init({ - params: { - gamId: '1234', - hostname: 'www.example.com', - pathname: '/article/some-article', - }, - }); - - // Wait for request to be sent - await waitForStatus('insightsReqReceived'); - - // Response - server.requests[0].respond(204, { 'Content-Type': 'application/json' }); - - // Wait for the script to process the response - await waitForStatus('scriptComplete'); - - // execute any googletag commands added to the queue during execution - executeGoogletagTargeting(); - - expect(server.requestCount).to.be.equal(1); - expect(window.googletag.cmd).to.have.length(1); - sinon.assert.calledOnce(setTargetingStub); - sinon.assert.calledWith( - setTargetingStub.firstCall, - 'zeus_1234', - 'www.example.com' - ); - sinon.assert.notCalled(logErrorSpy); - }); - - it('should not set insights keyvalue when server returns empty topics array', async () => { - zeusPrimeSubmodule.init({ - params: { - gamId: '1234', - hostname: 'www.example.com', - pathname: '/article/some-article', - }, - }); - - // Wait for request to be sent - await waitForStatus('insightsReqReceived'); - - // Respond - server.requests[0].respond( - 200, - { 'Content-Type': 'application/json' }, - '{ "topics": [] }' - ); - - // Wait for the script to process the response - await waitForStatus('scriptComplete'); - - // execute any googletag commands added to the queue during execution - executeGoogletagTargeting(); - - expect(server.requestCount).to.be.equal(1); - expect(window.googletag.cmd).to.have.length(1); - sinon.assert.calledOnce(setTargetingStub); - sinon.assert.calledWith( - setTargetingStub.firstCall, - 'zeus_1234', - 'www.example.com' - ); - sinon.assert.notCalled(logErrorSpy); - }); - - it('should not set insights keyvalue and emit error when server returns error status (400)', async () => { - zeusPrimeSubmodule.init({ - params: { - gamId: '1234', - hostname: 'www.example.com', - pathname: '/article/some-article', - }, - }); - - // Wait for request to be sent - await waitForStatus('insightsReqReceived'); - - // Response - server.requests[0].respond( - 404, - { 'Content-Type': 'application/json' }, - '{"message": "Not found"}' - ); - - // Wait for the script to process the response - await waitForStatus('scriptComplete'); - - // execute any googletag commands added to the queue during execution - executeGoogletagTargeting(); - - expect(server.requestCount).to.be.equal(1); - expect(window.googletag.cmd).to.have.length(1); - sinon.assert.calledOnce(setTargetingStub); - sinon.assert.calledWith( - setTargetingStub.firstCall, - 'zeus_1234', - 'www.example.com' - ); - sinon.assert.calledWith( - logErrorSpy, - 'zeusPrimeRtdProvider: ', - 'Topics request returned error: 404' - ); - }); - - it('should not set insights keyvalue and emit error when response is not received (network request)', async () => { - zeusPrimeSubmodule.init({ - params: { - gamId: '1234', - hostname: 'www.example.com', - pathname: '/article/some-article', - }, - }); - - // Wait for request to be sent - await waitForStatus('insightsReqReceived'); - - // Response - server.requests[0].error(); - - // Wait for the script to process the response - await waitForStatus('scriptComplete'); - - // execute any googletag commands added to the queue during execution - executeGoogletagTargeting(); - - expect(server.requestCount).to.be.equal(1); - expect(window.googletag.cmd).to.have.length(1); - sinon.assert.calledOnce(setTargetingStub); - sinon.assert.calledWith( - setTargetingStub.firstCall, - 'zeus_1234', - 'www.example.com' - ); - sinon.assert.calledWith( - logErrorSpy, - 'zeusPrimeRtdProvider: ', - 'failed to request topics' - ); - }); - - it('fails gracefully when crypto fails', async () => { - const digestStub = sinon.stub(window.crypto.subtle, 'digest'); - digestStub.throwsException('Failed to generate digest.'); - - zeusPrimeSubmodule.init({ - params: { - gamId: '1234', - hostname: 'www.example.com', - pathname: '/article/some-article', - }, - }); - - // Wait for request to be sent - await waitForStatus('scriptComplete'); - - sinon.assert.calledWith( - logErrorSpy, - 'zeusPrimeRtdProvider: ', - 'Failed to load hash' - ); - - digestStub.restore(); - }); - - it('script should add zeus_prime key and not send request when disabled is set', async () => { - zeusPrimeSubmodule.init({ - params: { - gamId: '1234', - disabled: true, - hostname: 'www.example.com', - pathname: '/article/some-article', - }, - }); - - // Wait for request to be sent - await waitForStatus('scriptComplete'); - - // execute any googletag commands added to the queue during execution - executeGoogletagTargeting(); - - expect(server.requestCount).to.be.equal(0); - expect(window.googletag.cmd).to.have.length(1); - sinon.assert.calledWith(setTargetingStub, 'zeus_prime', 'false'); - }); - - it('debug true enables debug logging', async () => { - zeusPrimeSubmodule.init({ - params: { - gamId: '1234', - debug: true, - hostname: 'www.example.com', - pathname: '/article/some-article', - }, - }); - - // Wait for request to be sent - await waitForStatus('scriptComplete'); - - sinon.assert.called(logMessageSpy); - sinon.assert.notCalled(logErrorSpy); - }); - - it('debug false disables debug logging', async () => { - window.zeusPrime.disabled = false; - zeusPrimeSubmodule.init({ - params: { - gamId: '1234', - hostname: 'www.example.com', - pathname: '/article/some-article', - }, - }); - - // Wait for request to be sent - await waitForStatus('scriptComplete'); - - sinon.assert.notCalled(logMessageSpy); - sinon.assert.notCalled(logErrorSpy); - }); -}); diff --git a/test/spec/native_spec.js b/test/spec/native_spec.js index 2b7c2b88449..9150329ff60 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', @@ -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/unit/core/adapterManager_spec.js b/test/spec/unit/core/adapterManager_spec.js index 3fea8988a95..8d887474180 100644 --- a/test/spec/unit/core/adapterManager_spec.js +++ b/test/spec/unit/core/adapterManager_spec.js @@ -21,6 +21,8 @@ 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'; var events = require('../../../../src/events'); const CONFIG = { @@ -1602,12 +1604,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); + } }); }); @@ -1757,6 +1762,153 @@ describe('adapterManager tests', function () { requests.appnexus.bids.forEach((bid) => expect(bid.ortb2).to.eql(requests.appnexus.ortb2)); }); + it('should merge in bid-level ortb2Imp with adUnit-level ortb2Imp', () => { + const adUnit = { + ...adUnits[1], + ortb2Imp: {oneone: {twoone: 'val'}, onetwo: 'val'} + }; + adUnit.bids[0].ortb2Imp = {oneone: {twotwo: 'val'}, onethree: 'val', onetwo: 'val2'}; + const reqs = Object.fromEntries( + adapterManager.makeBidRequests([adUnit], 123, 'auction-id', 123, [], {}) + .map((req) => [req.bidderCode, req]) + ); + sinon.assert.match(reqs[adUnit.bids[0].bidder].bids[0].ortb2Imp, { + oneone: { + twoone: 'val', + twotwo: 'val', + }, + onetwo: 'val2', + onethree: 'val' + }) + sinon.assert.match(reqs[adUnit.bids[1].bidder].bids[0].ortb2Imp, adUnit.ortb2Imp) + }) + + it('picks ortb2Imp from "module" when only one s2sConfig is set', () => { + config.setConfig({ + s2sConfig: [ + { + enabled: true, + adapter: 'mockS2S1', + } + ] + }); + const adUnit = { + code: 'mockau', + ortb2Imp: { + p1: 'adUnit' + }, + bids: [ + { + module: 'pbsBidAdapter', + ortb2Imp: { + p2: 'module' + } + } + ] + }; + const req = adapterManager.makeBidRequests([adUnit], 123, 'auction-id', 123, [], {})[0]; + [req.adUnitsS2SCopy[0].ortb2Imp, req.bids[0].ortb2Imp].forEach(imp => { + sinon.assert.match(imp, { + p1: 'adUnit', + p2: 'module' + }); + }); + }); + + describe('with named s2s configs', () => { + beforeEach(() => { + config.setConfig({ + s2sConfig: [ + { + enabled: true, + adapter: 'mockS2S1', + configName: 'one', + bidders: ['A'] + }, + { + enabled: true, + adapter: 'mockS2S2', + configName: 'two', + bidders: ['B'] + } + ] + }) + }); + + it('generates requests for "module" bids', () => { + const adUnit = { + code: 'mockau', + ortb2Imp: { + p1: 'adUnit' + }, + bids: [ + { + module: 'pbsBidAdapter', + params: {configName: 'one'}, + ortb2Imp: { + p2: 'one' + } + }, + { + module: 'pbsBidAdapter', + params: {configName: 'two'}, + ortb2Imp: { + p2: 'two' + } + } + ] + }; + const reqs = adapterManager.makeBidRequests([adUnit], 123, 'auction-id', 123, [], {}); + [reqs[0].adUnitsS2SCopy[0].ortb2Imp, reqs[0].bids[0].ortb2Imp].forEach(imp => { + sinon.assert.match(imp, { + p1: 'adUnit', + p2: 'one' + }) + }); + [reqs[1].adUnitsS2SCopy[0].ortb2Imp, reqs[1].bids[0].ortb2Imp].forEach(imp => { + sinon.assert.match(imp, { + p1: 'adUnit', + p2: 'two' + }) + }); + }); + + it('applies module-level ortb2Imp to "normal" s2s requests', () => { + const adUnit = { + code: 'mockau', + ortb2Imp: { + p1: 'adUnit' + }, + bids: [ + { + module: 'pbsBidAdapter', + params: {configName: 'one'}, + ortb2Imp: { + p2: 'one' + } + }, + { + bidder: 'A', + ortb2Imp: { + p3: 'bidderA' + } + } + ] + }; + const reqs = adapterManager.makeBidRequests([adUnit], 123, 'auction-id', 123, [], {}); + expect(reqs.length).to.equal(1); + sinon.assert.match(reqs[0].adUnitsS2SCopy[0].ortb2Imp, { + p1: 'adUnit', + p2: 'one' + }) + sinon.assert.match(reqs[0].bids[0].ortb2Imp, { + p1: 'adUnit', + p2: 'one', + p3: 'bidderA' + }) + }); + }); + describe('when calling the s2s adapter', () => { beforeEach(() => { config.setConfig({ @@ -1816,64 +1968,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 () { @@ -2645,4 +2739,23 @@ describe('adapterManager tests', function () { }) }) }); + + 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..c0e48089b52 100644 --- a/test/spec/unit/core/bidderFactory_spec.js +++ b/test/spec/unit/core/bidderFactory_spec.js @@ -1,4 +1,11 @@ -import {newBidder, registerBidder, preloadBidderMappingFile, storage, isValid} from 'src/adapters/bidderFactory.js'; +import { + newBidder, + registerBidder, + preloadBidderMappingFile, + storage, + isValid, + addComponentAuction +} from 'src/adapters/bidderFactory.js'; import adapterManager from 'src/adapterManager.js'; import * as ajax from 'src/ajax.js'; import { expect } from 'chai'; @@ -1212,16 +1219,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 +1261,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,8 +1275,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.be.true; + sinon.assert.calledWith(fledgeStub, 'mock/placement', fledgeAuctionConfig.config); expect(addBidResponseStub.calledOnce).to.equal(false); }) }) @@ -1266,6 +1284,10 @@ describe('validate bid response: ', function () { }); describe('preload mapping url hook', function() { + if (!FEATURES.VIDEO) { + return + } + let fakeTranslationServer; let getLocalStorageStub; let adapterManagerStub; 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..9e31389d96f 100644 --- a/test/spec/unit/core/storageManager_spec.js +++ b/test/spec/unit/core/storageManager_spec.js @@ -1,14 +1,14 @@ import { + getCoreStorageManager, getStorageManager, + newStorageManager, resetData, - getCoreStorageManager, storageCallbacks, - getStorageManager, - newStorageManager, validateStorageEnforcement + validateStorageEnforcement } from 'src/storageManager.js'; -import { config } from 'src/config.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} from '../../../../src/activities/modules.js'; describe('storage manager', function() { before(() => { @@ -34,7 +34,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,17 +50,16 @@ 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(`enforcement`, () => { + let validateHook; beforeEach(() => { - storage = getCoreStorageManager(); validateHook = sinon.stub().callsFake(function (next, ...args) { next.apply(this, args); }); @@ -72,15 +71,26 @@ describe('storage manager', function() { config.resetConfig(); }) - it('should respect (vendorless) consent enforcement', () => { - storage.localStorageIsEnabled(); - expect(validateHook.args[0][1]).to.equal(VENDORLESS_GVLID); // gvlid should be set to VENDORLESS_GVLID - }); + Object.entries({ + 'core': () => getCoreStorageManager('mock'), + 'other': () => getStorageManager({moduleType: 'other', moduleName: 'mock'}) + }).forEach(([moduleType, getMgr]) => { + describe(`for ${moduleType} modules`, () => { + let storage; + beforeEach(() => { + storage = getMgr(); + }); + it(`should pass '${moduleType}' module type to consent enforcement`, () => { + storage.localStorageIsEnabled(); + expect(validateHook.args[0][1]).to.equal(moduleType); + }); - it('should respect the deviceAccess flag', () => { - config.setConfig({deviceAccess: false}); - expect(storage.localStorageIsEnabled()).to.be.false - }) + it('should respect the deviceAccess flag', () => { + config.setConfig({deviceAccess: false}); + expect(storage.localStorageIsEnabled()).to.be.false + }); + }); + }); }) describe('localstorage forbidden access in 3rd-party context', function() { @@ -100,7 +110,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 +134,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(); @@ -142,11 +152,11 @@ describe('storage manager', function() { 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 +167,89 @@ 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}`, () => { + let mgr; + + beforeEach(() => { + mgr = newStorageManager({moduleType: MODULE_TYPE_BIDDER, moduleName: bidderCode}, {bidderSettings: mockBidderSettings(configValue)}); + }) + + afterEach(() => { + mgr.setCookie(COOKIE, 'delete', new Date().toUTCString()); + mgr.removeDataFromLocalStorage(LS_KEY); + }) + + function scenario(type, desc, fn) { + const shouldWork = isBidderAllowed && ({html5, cookie})[type]; + it(`${shouldWork ? '' : 'NOT'} ${desc}`, () => fn(shouldWork)); + } + + scenario('cookie', 'allow cookies', (shouldWork) => { + mgr.setCookie(COOKIE, 'value'); + expect(mgr.getCookie(COOKIE)).to.equal(shouldWork ? 'value' : null); + }); + + scenario('html5', 'allow localStorage', (shouldWork) => { + mgr.setDataInLocalStorage(LS_KEY, 'value'); + expect(mgr.getDataFromLocalStorage(LS_KEY)).to.equal(shouldWork ? 'value' : null); + }); + + scenario('html5', 'report localStorage as available', (shouldWork) => { + expect(mgr.hasLocalStorage()).to.equal(shouldWork); + }); + + scenario('cookie', 'report cookies as available', (shouldWork) => { + expect(mgr.cookiesAreEnabled()).to.equal(shouldWork); + }); + })); + }); }); }); }); - }) + }); }); diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index ae9ea09908a..820d87ef49c 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -888,16 +888,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 +2125,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 +2160,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 +2305,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 = [{ @@ -3017,6 +3039,20 @@ describe('Unit: Prebid Module', function () { }); }); + describe('aliasRegistry', function () { + it('should return the same value as adapterManager.aliasRegistry by default', function () { + const adapterManagerAliasRegistry = adapterManager.aliasRegistry; + const pbjsAliasRegistry = $$PREBID_GLOBAL$$.aliasRegistry; + assert.equal(adapterManagerAliasRegistry, pbjsAliasRegistry); + }); + + it('should return undefined if the aliasRegistry config option is set to private', function () { + configObj.setConfig({ aliasRegistry: 'private' }); + const pbjsAliasRegistry = $$PREBID_GLOBAL$$.aliasRegistry; + assert.equal(pbjsAliasRegistry, undefined); + }); + }); + describe('setPriceGranularity', function () { it('should log error when not passed granularity', function () { const logErrorSpy = sinon.spy(utils, 'logError'); @@ -3312,69 +3348,71 @@ 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 - const adUnitCode = '/19968336/header-bid-tag-0'; - const bidsReceived = $$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode(adUnitCode); - auction.getBidsReceived = function() { return bidsReceived.bids }; - - // 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); - - expect(markedBid.status).to.equal(CONSTANTS.BID_STATUS.RENDERED); - resetAuction(); - }); + if (FEATURES.VIDEO) { + 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 + const adUnitCode = '/19968336/header-bid-tag-0'; + const bidsReceived = $$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode(adUnitCode); + auction.getBidsReceived = function() { return bidsReceived.bids }; + + // 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); + + expect(markedBid.status).to.equal(CONSTANTS.BID_STATUS.RENDERED); + resetAuction(); + }); - 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 }; + 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 }; - 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); + 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); - expect(markedBid.status).to.not.equal(CONSTANTS.BID_STATUS.RENDERED); - resetAuction(); - }); + expect(markedBid.status).to.not.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 - const adUnitCode = '/19968336/header-bid-tag-0'; - const bidsReceived = $$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode(adUnitCode); - auction.getBidsReceived = function() { return bidsReceived.bids }; + 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 }; - // 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); + // 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.equal(CONSTANTS.BID_STATUS.RENDERED); - resetAuction(); - }); + expect(markedBid.status).to.equal(CONSTANTS.BID_STATUS.RENDERED); + resetAuction(); + }); - 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 }; + 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 }; - // 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); + // 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); - expect(markedBid.status).to.equal(CONSTANTS.BID_STATUS.RENDERED); - resetAuction(); + expect(markedBid.status).to.equal(CONSTANTS.BID_STATUS.RENDERED); + resetAuction(); + }); }); - }); + } describe('setTargetingForAst', function () { let targeting; diff --git a/test/spec/unit/utils/cpm_spec.js b/test/spec/unit/utils/cpm_spec.js new file mode 100644 index 00000000000..9d104b04d09 --- /dev/null +++ b/test/spec/unit/utils/cpm_spec.js @@ -0,0 +1,64 @@ +import {adjustCpm} from '../../../../src/utils/cpm.js'; + +describe('adjustCpm', () => { + const bidderCode = 'mockBidder'; + let adjustmentFn, bs, index; + beforeEach(() => { + bs = { + get: sinon.stub() + } + index = { + getBidRequest: sinon.stub() + } + adjustmentFn = sinon.stub().callsFake((cpm) => cpm * 2); + }) + + it('throws when neither bidRequest nor bidResponse are provided', () => { + expect(() => adjustCpm(1)).to.throw(); + }) + + it('always provides an object as bidResponse for the adjustment fn', () => { + bs.get.callsFake(() => adjustmentFn); + adjustCpm(1, null, {bidder: bidderCode}, {index, bs}); + sinon.assert.calledWith(adjustmentFn, 1, {}); + }); + + describe('when no bidRequest is provided', () => { + Object.entries({ + 'unavailable': undefined, + 'found': {foo: 'bar'} + }).forEach(([t, req]) => { + describe(`and it is ${t} in the index`, () => { + beforeEach(() => { + bs.get.callsFake(() => adjustmentFn); + index.getBidRequest.callsFake(() => req) + }); + + it('provides it to the adjustment fn', () => { + const bidResponse = {bidderCode}; + adjustCpm(1, bidResponse, undefined, {index, bs}); + sinon.assert.calledWith(index.getBidRequest, bidResponse); + sinon.assert.calledWith(adjustmentFn, 1, bidResponse, req); + }) + }) + }) + }); + + Object.entries({ + 'bidResponse': [{bidderCode}], + 'bidRequest': [null, {bidder: bidderCode}], + }).forEach(([t, [bidResp, bidReq]]) => { + describe(`when passed ${t}`, () => { + beforeEach(() => { + bs.get.callsFake((bidder) => { if (bidder === bidderCode) return adjustmentFn }); + }); + it('retrieves the correct bidder code', () => { + expect(adjustCpm(1, bidResp, bidReq, {bs, index})).to.eql(2); + }); + it('passes them to the adjustment fn', () => { + adjustCpm(1, bidResp, bidReq, {bs, index}); + sinon.assert.calledWith(adjustmentFn, 1, bidResp == null ? sinon.match.any : bidResp, bidReq); + }); + }); + }) +}); 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..6d0953f68ac 100644 --- a/test/spec/userSync_spec.js +++ b/test/spec/userSync_spec.js @@ -110,9 +110,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'); 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$/);