diff --git a/creative/renderers/native/renderer.js b/creative/renderers/native/renderer.js index 5cc8f100108..f7c124b41eb 100644 --- a/creative/renderers/native/renderer.js +++ b/creative/renderers/native/renderer.js @@ -45,6 +45,16 @@ function loadScript(url, doc) { }); } +function getRenderFrames(node) { + return Array.from(node.querySelectorAll('iframe[srcdoc*="render"]')) +} + +function getInnerHTML(node) { + const clone = node.cloneNode(true); + getRenderFrames(clone).forEach(node => node.parentNode.removeChild(node)); + return clone.innerHTML; +} + export function getAdMarkup(adId, nativeData, replacer, win, load = loadScript) { const {rendererUrl, assets, ortb, adTemplate} = nativeData; const doc = win.document; @@ -58,21 +68,32 @@ export function getAdMarkup(adId, nativeData, replacer, win, load = loadScript) return win.renderAd(payload); }); } else { - return Promise.resolve(replacer(adTemplate ?? doc.body.innerHTML)); + return Promise.resolve(replacer(adTemplate ?? getInnerHTML(doc.body))); } } export function render({adId, native}, {sendMessage}, win, getMarkup = getAdMarkup) { const {head, body} = win.document; - const resize = () => sendMessage(MESSAGE_NATIVE, { - action: ACTION_RESIZE, - height: body.offsetHeight, - width: body.offsetWidth - }); + const resize = () => { + // force redraw - for some reason this is needed to get the right dimensions + body.style.display = 'none'; + body.style.display = 'block'; + sendMessage(MESSAGE_NATIVE, { + action: ACTION_RESIZE, + height: body.offsetHeight, + width: body.offsetWidth + }); + } + function replaceMarkup(target, markup) { + // do not remove the rendering logic if it's embedded in this window; things will break otherwise + const renderFrames = getRenderFrames(target); + Array.from(target.childNodes).filter(node => !renderFrames.includes(node)).forEach(node => target.removeChild(node)); + target.insertAdjacentHTML('afterbegin', markup); + } const replacer = getReplacer(adId, native); - head && (head.innerHTML = replacer(head.innerHTML)); + replaceMarkup(head, replacer(getInnerHTML(head))); return getMarkup(adId, native, replacer, win).then(markup => { - body.innerHTML = markup; + replaceMarkup(body, markup); if (typeof win.postRenderAd === 'function') { win.postRenderAd({adId, ...native}); } diff --git a/integrationExamples/gpt/liveIntentRtdProviderExample.html b/integrationExamples/gpt/liveIntentRtdProviderExample.html new file mode 100644 index 00000000000..489df86f7a7 --- /dev/null +++ b/integrationExamples/gpt/liveIntentRtdProviderExample.html @@ -0,0 +1,164 @@ + + + + + + + + +

Basic Prebid.js Example

+
Div-1
+
+ +
+
+
Div-2
+
+ +
+ + diff --git a/integrationExamples/gpt/x-domain/creative.html b/integrationExamples/gpt/x-domain/creative.html index 63842b00882..f523c87b326 100644 --- a/integrationExamples/gpt/x-domain/creative.html +++ b/integrationExamples/gpt/x-domain/creative.html @@ -6,8 +6,8 @@ diff --git a/libraries/creative-renderer-native/renderer.js b/libraries/creative-renderer-native/renderer.js index d7d85cdd7ba..5651cc3f0ca 100644 --- a/libraries/creative-renderer-native/renderer.js +++ b/libraries/creative-renderer-native/renderer.js @@ -1,2 +1,2 @@ // this file is autogenerated, see creative/README.md -export const RENDERER = "(()=>{\"use strict\";const e=\"Prebid Native\",t={title:\"text\",data:\"value\",img:\"url\",video:\"vasttag\"};function n(e,t){return new Promise(((n,r)=>{const i=t.createElement(\"script\");i.onload=n,i.onerror=r,i.src=e,t.body.appendChild(i)}))}function r(e,t,r,i,o=n){const{rendererUrl:s,assets:a,ortb:d,adTemplate:c}=t,l=i.document;return s?o(s,l).then((()=>{if(\"function\"!=typeof i.renderAd)throw new Error(`Renderer from '${s}' does not define renderAd()`);const e=a||[];return e.ortb=d,i.renderAd(e)})):Promise.resolve(r(c??l.body.innerHTML))}window.render=function({adId:n,native:i},{sendMessage:o},s,a=r){const{head:d,body:c}=s.document,l=()=>o(e,{action:\"resizeNativeHeight\",height:c.offsetHeight,width:c.offsetWidth}),u=function(e,{assets:n=[],ortb:r,nativeKeys:i={}}){const o=Object.fromEntries(n.map((({key:e,value:t})=>[e,t])));let s=Object.fromEntries(Object.entries(i).flatMap((([t,n])=>{const r=o.hasOwnProperty(t)?o[t]:void 0;return[[`##${n}##`,r],[`${n}:${e}`,r]]})));return r&&Object.assign(s,{\"##hb_native_linkurl##\":r.link?.url,\"##hb_native_privacy##\":r.privacy},Object.fromEntries((r.assets||[]).flatMap((e=>{const n=Object.keys(t).find((t=>e[t]));return[n&&[`##hb_native_asset_id_${e.id}##`,e[n][t[n]]],e.link?.url&&[`##hb_native_asset_link_id_${e.id}##`,e.link.url]].filter((e=>e))})))),s=Object.entries(s).concat([[/##hb_native_asset_(link_)?id_\\d+##/g]]),function(e){return s.reduce(((e,[t,n])=>e.replaceAll(t,n||\"\")),e)}}(n,i);return d&&(d.innerHTML=u(d.innerHTML)),a(n,i,u,s).then((t=>{c.innerHTML=t,\"function\"==typeof s.postRenderAd&&s.postRenderAd({adId:n,...i}),s.document.querySelectorAll(\".pb-click\").forEach((t=>{const n=t.getAttribute(\"hb_native_asset_id\");t.addEventListener(\"click\",(()=>o(e,{action:\"click\",assetId:n})))})),o(e,{action:\"fireNativeImpressionTrackers\"}),\"complete\"===s.document.readyState?l():s.onload=l}))}})();" \ No newline at end of file +export const RENDERER = "(()=>{\"use strict\";const e=\"Prebid Native\",t={title:\"text\",data:\"value\",img:\"url\",video:\"vasttag\"};function n(e,t){return new Promise(((n,r)=>{const i=t.createElement(\"script\");i.onload=n,i.onerror=r,i.src=e,t.body.appendChild(i)}))}function r(e){return Array.from(e.querySelectorAll('iframe[srcdoc*=\"render\"]'))}function i(e){const t=e.cloneNode(!0);return r(t).forEach((e=>e.parentNode.removeChild(e))),t.innerHTML}function o(e,t,r,o,s=n){const{rendererUrl:c,assets:d,ortb:a,adTemplate:l}=t,u=o.document;return c?s(c,u).then((()=>{if(\"function\"!=typeof o.renderAd)throw new Error(`Renderer from '${c}' does not define renderAd()`);const e=d||[];return e.ortb=a,o.renderAd(e)})):Promise.resolve(r(l??i(u.body)))}window.render=function({adId:n,native:s},{sendMessage:c},d,a=o){const{head:l,body:u}=d.document,f=()=>{u.style.display=\"none\",u.style.display=\"block\",c(e,{action:\"resizeNativeHeight\",height:u.offsetHeight,width:u.offsetWidth})};function b(e,t){const n=r(e);Array.from(e.childNodes).filter((e=>!n.includes(e))).forEach((t=>e.removeChild(t))),e.insertAdjacentHTML(\"afterbegin\",t)}const h=function(e,{assets:n=[],ortb:r,nativeKeys:i={}}){const o=Object.fromEntries(n.map((({key:e,value:t})=>[e,t])));let s=Object.fromEntries(Object.entries(i).flatMap((([t,n])=>{const r=o.hasOwnProperty(t)?o[t]:void 0;return[[`##${n}##`,r],[`${n}:${e}`,r]]})));return r&&Object.assign(s,{\"##hb_native_linkurl##\":r.link?.url,\"##hb_native_privacy##\":r.privacy},Object.fromEntries((r.assets||[]).flatMap((e=>{const n=Object.keys(t).find((t=>e[t]));return[n&&[`##hb_native_asset_id_${e.id}##`,e[n][t[n]]],e.link?.url&&[`##hb_native_asset_link_id_${e.id}##`,e.link.url]].filter((e=>e))})))),s=Object.entries(s).concat([[/##hb_native_asset_(link_)?id_\\d+##/g]]),function(e){return s.reduce(((e,[t,n])=>e.replaceAll(t,n||\"\")),e)}}(n,s);return b(l,h(i(l))),a(n,s,h,d).then((t=>{b(u,t),\"function\"==typeof d.postRenderAd&&d.postRenderAd({adId:n,...s}),d.document.querySelectorAll(\".pb-click\").forEach((t=>{const n=t.getAttribute(\"hb_native_asset_id\");t.addEventListener(\"click\",(()=>c(e,{action:\"click\",assetId:n})))})),c(e,{action:\"fireNativeImpressionTrackers\"}),\"complete\"===d.document.readyState?f():d.onload=f}))}})();" \ No newline at end of file diff --git a/libraries/intentIqConstants/intentIqConstants.js b/libraries/intentIqConstants/intentIqConstants.js index ed9856fc213..2cc9acc1844 100644 --- a/libraries/intentIqConstants/intentIqConstants.js +++ b/libraries/intentIqConstants/intentIqConstants.js @@ -7,4 +7,4 @@ export const OPT_OUT = 'O'; export const BLACK_LIST = 'L'; export const CLIENT_HINTS_KEY = '_iiq_ch'; export const EMPTY = 'EMPTY' -export const VERSION = 0.24 +export const VERSION = 0.25 diff --git a/libraries/liveIntentId/shared.js b/libraries/liveIntentId/shared.js index 509f91e44d9..ab00417ccc3 100644 --- a/libraries/liveIntentId/shared.js +++ b/libraries/liveIntentId/shared.js @@ -59,11 +59,8 @@ export function composeIdObject(value) { // old versions stored lipbid in unifiedId. Ensure that we can still read the data. const lipbid = value.nonId || value.unifiedId - if (lipbid) { - const lipb = { ...value, lipbid }; - delete lipb.unifiedId; - result.lipb = lipb; - } + result.lipb = lipbid ? { ...value, lipbid } : value + delete result.lipb?.unifiedId // Lift usage of uid2 by exposing uid2 if we were asked to resolve it. // As adapters are applied in lexicographical order, we will always @@ -76,6 +73,10 @@ export function composeIdObject(value) { result.bidswitch = { 'id': value.bidswitch, ext: { provider: LI_PROVIDER_DOMAIN } } } + if (value.triplelift) { + result.triplelift = { 'id': value.triplelift, ext: { provider: LI_PROVIDER_DOMAIN } } + } + if (value.medianet) { result.medianet = { 'id': value.medianet, ext: { provider: LI_PROVIDER_DOMAIN } } } @@ -261,6 +262,18 @@ export const eids = { } } }, + 'triplelift': { + source: 'liveintent.triplelift.com', + atype: 3, + getValue: function(data) { + return data.id; + }, + getUidExt: function(data) { + if (data.ext) { + return data.ext; + } + } + }, 'vidazoo': { source: 'liveintent.vidazoo.com', atype: 3, diff --git a/libraries/mspa/activityControls.js b/libraries/mspa/activityControls.js index eb68259d585..c93748f73c7 100644 --- a/libraries/mspa/activityControls.js +++ b/libraries/mspa/activityControls.js @@ -24,6 +24,8 @@ export function isBasicConsentDenied(cd) { cd.PersonalDataConsents === 2 || // minors 13+ who have not given consent cd.KnownChildSensitiveDataConsents[0] === 1 || + // minors 16+ who have not given consent (added in usnat version 2) + cd.KnownChildSensitiveDataConsents[2] === 1 || // minors under 13 cannot consent isApplicable(cd.KnownChildSensitiveDataConsents[1]) || // covered cannot be zero @@ -53,14 +55,31 @@ export function isConsentDenied(cd) { } export const isTransmitUfpdConsentDenied = (() => { - // deny anything that smells like: genetic, biometric, state/national ID, financial, union membership, - // or personal communication data - const cannotBeInScope = [6, 7, 9, 10, 12].map(el => --el); - // require consent for everything else (except geo, which is treated separately) - const allExceptGeo = Array.from(Array(12).keys()).filter((el) => el !== SENSITIVE_DATA_GEO) - const mustHaveConsent = allExceptGeo.filter(el => !cannotBeInScope.includes(el)); + const sensitiveFlags = (() => { + // deny anything that smells like: genetic, biometric, state/national ID, financial, union membership, + // personal communication data, status as victim of crime (version 2), status as transgender/nonbinary (version 2) + const cannotBeInScope = [6, 7, 9, 10, 12, 14, 16].map(el => --el); + // require consent for everything else (except geo, which is treated separately) + const allExceptGeo = Array.from(Array(16).keys()).filter((el) => el !== SENSITIVE_DATA_GEO) + const mustHaveConsent = allExceptGeo.filter(el => !cannotBeInScope.includes(el)); + + return Object.fromEntries( + Object.entries({ + 1: 12, + 2: 16 + }).map(([version, cardinality]) => { + const isInVersion = (el) => el < cardinality + return [version, { + cannotBeInScope: cannotBeInScope.filter(isInVersion), + allExceptGeo: allExceptGeo.filter(isInVersion), + mustHaveConsent: mustHaveConsent.filter(isInVersion) + }] + }) + ) + })() return function (cd) { + const {cannotBeInScope, mustHaveConsent, allExceptGeo} = sensitiveFlags[cd.Version]; return isConsentDenied(cd) || // no notice about sensitive data was given sensitiveNoticeIs(cd, 2) || @@ -97,7 +116,7 @@ export function mspaRule(sids, getConsent, denies, applicableSids = () => gppDat if (consent == null) { return {allow: false, reason: 'consent data not available'}; } - if (consent.Version !== 1) { + if (![1, 2].includes(consent.Version)) { return {allow: false, reason: `unsupported consent specification version "${consent.Version}"`} } if (denies(consent)) { diff --git a/libraries/ortbConverter/README.md b/libraries/ortbConverter/README.md index 751971eebdc..92843c0241e 100644 --- a/libraries/ortbConverter/README.md +++ b/libraries/ortbConverter/README.md @@ -1,7 +1,7 @@ # Prebid.js - ORTB conversion library -This library provides methods to convert Prebid.js bid request objects to ORTB requests, -and ORTB responses to Prebid.js bid response objects. +This library provides methods to convert Prebid.js bid request objects to ORTB requests, +and ORTB responses to Prebid.js bid response objects. ## Usage @@ -37,13 +37,25 @@ registerBidder({ }) ``` -Without any customization, the library will generate complete ORTB requests, but ignores your [bid params](#params). +Without any customization, the library will generate complete ORTB requests, but ignores your [bid params](#params). If your endpoint sets `response.seatbid[].bid[].mtype` (part of the ORTB 2.6 spec), it will also parse the response into complete bidResponse objects. See [setting response mediaTypes](#response-mediaTypes) if that is not the case. ### Module-specific conversions Prebid.js features that require a module also require it for their corresponding ORTB conversion logic. For example, `imp.bidfloor` is only populated if the `priceFloors` module is active; `request.cur` needs the `currency` module, and so on. Notably, this means that to get those fields populated from your unit tests, you must import those modules first; see [this suite](https://github.com/prebid/Prebid.js/blob/master/test/spec/modules/openxOrtbBidAdapter_spec.js) for an example. +#### priceFloors extensions + +In addition to `imp.bidfloor` and `imp.bidfloorcur`, the `priceFloors` module also populates media type and `format` objects, if their floors differ: + +| Path | `getFloor` invocation | +|-----------------------------------------------------|----------------------------------------------------------------| +| `imp.bidfloor` & `.bidfloorcur` | `.getFloor()` | +| `imp.banner.ext.bidfloor` & `.bidfloorcur` | `.getFloor({mediaType: 'banner', size: '*'})` | +| `imp.banner.format[].ext.bidfloor` & `.bidfloorcur` | `.getFloor({mediaType: 'banner', size: [format.w, format.h]})` | +| `imp.native.ext.bidfloor` & `.bidfloorcur` | `.getFloor({mediaType: 'native', size: '*'})` | +| `imp.video.ext.bidfloor` & `.bidfloorcur` | `.getFloor({mediaType: 'video', size: '*'})` | + ## Customization ### Modifying return values directly @@ -57,35 +69,35 @@ deepSetValue(data.imp[0], 'ext.myCustomParam', bidRequests[0].params.myCustomPar However, there are two restrictions (to avoid them, use the [other customization options](#fine-customization)): - - you may not change the `imp[].id` returned by `toORTB`; they ared used internally to match responses to their requests. - ```javascript - const data = converter.toORTB({bidRequests, bidderRequest}); - data.imp[0].id = 'custom-imp-id' // do not do this - it will cause an error later in `fromORTB` - ``` - See also [overriding `imp.id`](#imp-id). - - the `request` argument passed to `fromORTB` must be the same object returned by `toORTB`. +- you may not change the `imp[].id` returned by `toORTB`; they ared used internally to match responses to their requests. ```javascript - let data = converter.toORTB({bidRequests, bidderRequest}); - - data = mergeDeep( // the original object is lost - {ext: {myCustomParam: bidRequests[0].params.myCustomParam}}, // `fromORTB` will later throw an error - data - ); - - // do this instead: - mergeDeep( - data, - {ext: {myCustomParam: bidRequests[0].params.myCustomParam}}, - data - ) + const data = converter.toORTB({bidRequests, bidderRequest}); + data.imp[0].id = 'custom-imp-id' // do not do this - it will cause an error later in `fromORTB` ``` + See also [overriding `imp.id`](#imp-id). +- the `request` argument passed to `fromORTB` must be the same object returned by `toORTB`. + ```javascript + let data = converter.toORTB({bidRequests, bidderRequest}); + + data = mergeDeep( // the original object is lost + {ext: {myCustomParam: bidRequests[0].params.myCustomParam}}, // `fromORTB` will later throw an error + data + ); + + // do this instead: + mergeDeep( + data, + {ext: {myCustomParam: bidRequests[0].params.myCustomParam}}, + data + ) + ``` ### Fine grained customization - imp, request, bidResponse, response When invoked, `toORTB({bidRequests, bidderRequest})` first loops through each request in `bidRequests`, converting them into ORTB `imp` objects. It then packages them into a single ORTB request, adding other parameters that are not imp-specific (such as for example `request.tmax`). -Likewise, `fromORTB({request, response})` first loops through each `response.seatbid[].bid[]`, converting them into Prebid bidResponses; it then packages them into +Likewise, `fromORTB({request, response})` first loops through each `response.seatbid[].bid[]`, converting them into Prebid bidResponses; it then packages them into a single return value. You can customize each of these steps using the `ortbConverter` arguments `imp`, `request`, `bidResponse` and `response`: @@ -98,8 +110,8 @@ The arguments are: - `buildImp`: a function taking `(bidRequest, context)` and returning an ORTB `imp` object; - `bidRequest`: the bid request object to convert; - `context`: a [context object](#context) that contains at least: - - `bidderRequest`: the `bidderRequest` argument passed to `toORTB`. - + - `bidderRequest`: the `bidderRequest` argument passed to `toORTB`. + #### Example: attaching custom bid params ```javascript @@ -194,7 +206,7 @@ const converter = ortbConverter({ }) ``` -If you know that a particular ORTB request/response pair deals with exclusively one mediaType, you may also pass it directly in the [context parameter](#context). +If you know that a particular ORTB request/response pair deals with exclusively one mediaType, you may also pass it directly in the [context parameter](#context). Note that - compared to the above - this has additional effects, because `context.mediaType` is also considered during `imp` generation - see [special context properties](#special-context). ```javascript @@ -223,7 +235,7 @@ const converter = ortbConverter({ ### Customizing the response: `response(buildResponse, bidResponses, ortbResponse, context)` -Invoked once, after all `seatbid[].bid[]` objects have been converted to corresponding bid responses. The value returned +Invoked once, after all `seatbid[].bid[]` objects have been converted to corresponding bid responses. The value returned by this function is also the value returned by `fromORTB`. The arguments are: @@ -249,7 +261,7 @@ const converter = ortbConverter({ ### Even finer grained customization - processor overrides Each of the four conversion steps described above - imp, request, bidResponse and response - is further broken down into -smaller units of work (called _processors_). For example, when the currency module is included, it adds a _request processor_ +smaller units of work (called _processors_). For example, when the currency module is included, it adds a _request processor_ that sets `request.cur`; the priceFloors module adds an _imp processor_ that sets `imp.bidfloor` and `imp.bidfloorcur`, and so on. Each processor can be overridden or disabled through the `overrides` argument: @@ -310,21 +322,21 @@ const converter = ortbConverter({ Processor overrides are similar to the override options described above, except that they take the object to process as argument: - `imp` processor overrides take `(orig, imp, bidRequest, context)`, where: - - `orig` is the processor function being overridden, which itself takes `(imp, bidRequest, context)`; - - `imp` is the (partial) imp object to modify; - - `bidRequest` and `context` are the same arguments passed to [imp](#imp). + - `orig` is the processor function being overridden, which itself takes `(imp, bidRequest, context)`; + - `imp` is the (partial) imp object to modify; + - `bidRequest` and `context` are the same arguments passed to [imp](#imp). - `request` processor overrides take `(orig, ortbRequest, bidderRequest, context)`, where: - - `orig` is the processor function being overridden, and takes `(ortbRequest, bidderRequest, context)`; - - `ortbRequest` is the partial request to modify; - - `bidderRequest` and `context` are the same arguments passed to [request](#reuqest). -- `bidResponse` processor overrides take `(orig, bidResponse, bid, context)`, where: - - `orig` is the processor function being overridden, and takes `(bidResponse, bid, context)`; - - `bidResponse` is the partial bid response to modify; - - `bid` and `context` are the same arguments passed to [bidResponse](#bidResponse) + - `orig` is the processor function being overridden, and takes `(ortbRequest, bidderRequest, context)`; + - `ortbRequest` is the partial request to modify; + - `bidderRequest` and `context` are the same arguments passed to [request](#reuqest). +- `bidResponse` processor overrides take `(orig, bidResponse, bid, context)`, where: + - `orig` is the processor function being overridden, and takes `(bidResponse, bid, context)`; + - `bidResponse` is the partial bid response to modify; + - `bid` and `context` are the same arguments passed to [bidResponse](#bidResponse) - `response` processor overrides take `(orig, response, ortbResponse, context)`, where: - - `orig` is the processor function being overriden, and takes `(response, ortbResponse, context)`; - - `response` is the partial response to modify; - - `ortbRespones` and `context` are the same arguments passed to [response](#response). + - `orig` is the processor function being overriden, and takes `(response, ortbResponse, context)`; + - `response` is the partial response to modify; + - `ortbRespones` and `context` are the same arguments passed to [response](#response). ### The `context` argument @@ -354,19 +366,19 @@ const converter = ortbConverter({ For ease of use, the conversion logic gives special meaning to some context properties: - - `currency`: a currency string (e.g. `'EUR'`). If specified, overrides the currency to use for computing price floors and `request.cur`. If omitted, both default to `getConfig('currency.adServerCurrency')`. - - `mediaType`: a bid mediaType (`'banner'`, `'video'`, or `'native'`). If specified: +- `currency`: a currency string (e.g. `'EUR'`). If specified, overrides the currency to use for computing price floors and `request.cur`. If omitted, both default to `getConfig('currency.adServerCurrency')`. +- `mediaType`: a bid mediaType (`'banner'`, `'video'`, or `'native'`). If specified: - disables `imp` generation for other media types (i.e., if `context.mediaType === 'banner'`, only `imp.banner` will be populated; `imp.video` and `imp.native` will not, even if the bid request specifies them); - is passed as the `mediaType` option to `bidRequest.getFloor` when computing price floors; - sets `bidResponse.mediaType`. - - `nativeRequest`: a plain object that serves as the base value for `imp.native.request` (and is relevant only for native bid requests). - If not specified, the only property that is guaranteed to be populated is `assets`, since Prebid does not require anything else to define a native adUnit. You can use `context.nativeRequest` to provide other properties; for example, you may want to signal support for native impression trackers by setting it to `{eventtrackers: [{event: 1, methods: [1, 2]}]}` (see also the [ORTB Native spec](https://www.iab.com/wp-content/uploads/2018/03/OpenRTB-Native-Ads-Specification-Final-1.2.pdf)). - - `netRevenue`: the value to set as `bidResponse.netRevenue`. This is a required property of bid responses that does not have a clear ORTB counterpart. - - `ttl`: the default value to use for `bidResponse.ttl` (if the ORTB response does not provide one in `seatbid[].bid[].exp`). - +- `nativeRequest`: a plain object that serves as the base value for `imp.native.request` (and is relevant only for native bid requests). + If not specified, the only property that is guaranteed to be populated is `assets`, since Prebid does not require anything else to define a native adUnit. You can use `context.nativeRequest` to provide other properties; for example, you may want to signal support for native impression trackers by setting it to `{eventtrackers: [{event: 1, methods: [1, 2]}]}` (see also the [ORTB Native spec](https://www.iab.com/wp-content/uploads/2018/03/OpenRTB-Native-Ads-Specification-Final-1.2.pdf)). +- `netRevenue`: the value to set as `bidResponse.netRevenue`. This is a required property of bid responses that does not have a clear ORTB counterpart. +- `ttl`: the default value to use for `bidResponse.ttl` (if the ORTB response does not provide one in `seatbid[].bid[].exp`). + ## Prebid Server extensions -If your endpoint is a Prebid Server instance, you may take advantage of the `pbsExtension` companion library, which adds a number of processors that can populate and parse PBS-specific extensions (typically prefixed `ext.prebid`); these include bidder params (with `transformBidParams`), bidder aliases, targeting keys, and others. +If your endpoint is a Prebid Server instance, you may take advantage of the `pbsExtension` companion library, which adds a number of processors that can populate and parse PBS-specific extensions (typically prefixed `ext.prebid`); these include bidder params (with `transformBidParams`), bidder aliases, targeting keys, and others. ```javascript import {pbsExtensions} from '../../libraries/pbsExtensions/pbsExtensions.js' diff --git a/libraries/riseUtils/constants.js b/libraries/riseUtils/constants.js new file mode 100644 index 00000000000..4acb9920291 --- /dev/null +++ b/libraries/riseUtils/constants.js @@ -0,0 +1,20 @@ +import {BANNER, NATIVE, VIDEO} from '../../src/mediaTypes.js'; + +const OW_GVLID = 280 +export const SUPPORTED_AD_TYPES = [BANNER, VIDEO, NATIVE]; +export const ADAPTER_VERSION = '7.0.0'; +export const DEFAULT_TTL = 360; +export const DEFAULT_CURRENCY = 'USD'; +export const BASE_URL = 'https://hb.yellowblue.io/'; +export const BIDDER_CODE = 'rise'; +export const DEFAULT_GVLID = 1043; + +export const ALIASES = [ + { code: 'risexchange', gvlid: DEFAULT_GVLID }, + { code: 'openwebxchange', gvlid: OW_GVLID } +] + +export const MODES = { + PRODUCTION: 'hb-multi', + TEST: 'hb-multi-test' +}; diff --git a/libraries/riseUtils/index.js b/libraries/riseUtils/index.js index 60f31ef2603..3046e6dcf4a 100644 --- a/libraries/riseUtils/index.js +++ b/libraries/riseUtils/index.js @@ -1,34 +1,135 @@ import { - isArray, - isFn, + contains, deepAccess, + getBidIdParameter, + isArray, isEmpty, - contains, + isFn, isInteger, - getBidIdParameter, - isPlainObject + isPlainObject, + logInfo, + triggerPixel } from '../../src/utils.js'; -import { BANNER, VIDEO } from '../../src/mediaTypes.js'; +import {BANNER, NATIVE, VIDEO} from '../../src/mediaTypes.js'; import {config} from '../../src/config.js'; +import {ADAPTER_VERSION, DEFAULT_CURRENCY, DEFAULT_TTL, SUPPORTED_AD_TYPES} from './constants.js'; + +export const makeBaseSpec = (baseUrl, modes) => { + return { + version: ADAPTER_VERSION, + supportedMediaTypes: SUPPORTED_AD_TYPES, + buildRequests: function (validBidRequests, bidderRequest) { + const combinedRequestsObject = {}; + + // use data from the first bid, to create the general params for all bids + const generalObject = validBidRequests[0]; + const testMode = generalObject.params.testMode; + const rtbDomain = generalObject.params.rtbDomain || baseUrl; + + combinedRequestsObject.params = generateGeneralParams(generalObject, bidderRequest); + combinedRequestsObject.bids = generateBidsParams(validBidRequests, bidderRequest); + + return { + method: 'POST', + url: getEndpoint(testMode, rtbDomain, modes), + data: combinedRequestsObject + } + }, + interpretResponse: function ({ body }) { + const bidResponses = []; + + if (body.bids) { + body.bids.forEach(adUnit => { + const bidResponse = buildBidResponse(adUnit); + bidResponses.push(bidResponse); + }); + } + + return bidResponses; + }, + getUserSyncs: function (syncOptions, serverResponses) { + const syncs = []; + for (const response of serverResponses) { + if (syncOptions.iframeEnabled && deepAccess(response, 'body.params.userSyncURL')) { + syncs.push({ + type: 'iframe', + url: deepAccess(response, 'body.params.userSyncURL') + }); + } + if (syncOptions.pixelEnabled && isArray(deepAccess(response, 'body.params.userSyncPixels'))) { + const pixels = response.body.params.userSyncPixels.map(pixel => { + return { + type: 'image', + url: pixel + } + }); + syncs.push(...pixels); + } + } + return syncs; + }, + onBidWon: function (bid) { + if (bid == null) { + return; + } + + logInfo('onBidWon:', bid); + if (bid.hasOwnProperty('nurl') && bid.nurl.length > 0) { + triggerPixel(bid.nurl); + } + } + } +} + +export function getBidRequestMediaTypes(bidRequest) { + const mediaTypes = deepAccess(bidRequest, 'mediaTypes'); + if (isPlainObject(mediaTypes)) { + return Object.keys(mediaTypes); + } + return []; +} + +export function getPos(bidRequest) { + const mediaTypes = getBidRequestMediaTypes(bidRequest); + const firstMediaType = mediaTypes[0]; + if (mediaTypes.length === 1) { + return deepAccess(bidRequest, `mediaTypes.${firstMediaType}.pos`); + } +} + +export function getName(bidRequest) { + const mediaTypes = getBidRequestMediaTypes(bidRequest); + const firstMediaType = mediaTypes[0]; + if (mediaTypes.length === 1) { + return deepAccess(bidRequest, `mediaTypes.${firstMediaType}.name`); + } +} -export function getFloor(bid, mediaType) { +export function getFloor(bid) { if (!isFn(bid.getFloor)) { return 0; } + + const mediaTypes = getBidRequestMediaTypes(bid) + const firstMediaType = mediaTypes[0]; + let floorResult = bid.getFloor({ currency: 'USD', - mediaType: mediaType, + mediaType: mediaTypes.length === 1 ? firstMediaType : '*', size: '*' }); return isPlainObject(floorResult) && floorResult.currency === 'USD' && floorResult.floor ? floorResult.floor : 0; } -export function getSizesArray(bid, mediaType) { +export function getSizesArray(bid) { let sizesArray = []; - if (deepAccess(bid, `mediaTypes.${mediaType}.sizes`)) { - sizesArray = bid.mediaTypes[mediaType].sizes; - } else if (Array.isArray(bid.sizes) && bid.sizes.length > 0) { + const mediaTypes = getBidRequestMediaTypes(bid); + const firstMediaType = mediaTypes[0]; + + if (mediaTypes.length === 1 && deepAccess(bid, `mediaTypes.${firstMediaType}.sizes`)) { + sizesArray = bid.mediaTypes[firstMediaType].sizes; + } else if (isArray(bid.sizes) && bid.sizes.length > 0) { sizesArray = bid.sizes; } @@ -111,18 +212,17 @@ export function generateBidsParams(validBidRequests, bidderRequest) { export function generateBidParameters(bid, bidderRequest) { const { params } = bid; - const mediaType = isBanner(bid) ? BANNER : VIDEO; - const sizesArray = getSizesArray(bid, mediaType); + const mediaTypes = getBidRequestMediaTypes(bid); if (isNaN(params.floorPrice)) { params.floorPrice = 0; } const bidObject = { - mediaType, + mediaType: mediaTypes.join(','), adUnitCode: getBidIdParameter('adUnitCode', bid), - sizes: sizesArray, - floorPrice: Math.max(getFloor(bid, mediaType), params.floorPrice), + sizes: getSizesArray(bid), + floorPrice: Math.max(getFloor(bid), params.floorPrice), bidId: getBidIdParameter('bidId', bid), loop: bid.bidderRequestsCount || 0, bidderRequestId: getBidIdParameter('bidderRequestId', bid), @@ -130,8 +230,8 @@ export function generateBidParameters(bid, bidderRequest) { coppa: 0, }; - const pos = deepAccess(bid, `mediaTypes.${mediaType}.pos`); - if (pos) { + const pos = getPos(bid); + if (isInteger(pos)) { bidObject.pos = pos; } @@ -140,21 +240,11 @@ export function generateBidParameters(bid, bidderRequest) { bidObject.gpid = gpid; } - const placementId = params.placementId || deepAccess(bid, `mediaTypes.${mediaType}.name`); + const placementId = params.placementId || getName(bid); if (placementId) { bidObject.placementId = placementId; } - const mimes = deepAccess(bid, `mediaTypes.${mediaType}.mimes`); - if (mimes) { - bidObject.mimes = mimes; - } - - const api = deepAccess(bid, `mediaTypes.${mediaType}.api`); - if (api) { - bidObject.api = api; - } - const sua = deepAccess(bid, `ortb2.device.sua`); if (sua) { bidObject.sua = sua; @@ -165,11 +255,11 @@ export function generateBidParameters(bid, bidderRequest) { bidObject.coppa = 1; } - if (mediaType === VIDEO) { + if (mediaTypes.includes(VIDEO)) { const playbackMethod = deepAccess(bid, `mediaTypes.video.playbackmethod`); let playbackMethodValue; - if (Array.isArray(playbackMethod) && isInteger(playbackMethod[0])) { + if (isArray(playbackMethod) && isInteger(playbackMethod[0])) { playbackMethodValue = playbackMethod[0]; } else if (isInteger(playbackMethod)) { playbackMethodValue = playbackMethod; @@ -213,19 +303,36 @@ export function generateBidParameters(bid, bidderRequest) { if (plcmt) { bidObject.plcmt = plcmt; } + + const mimes = deepAccess(bid, `mediaTypes.video.mimes`); + if (mimes) { + bidObject.mimes = mimes; + } + + const api = deepAccess(bid, `mediaTypes.video.api`); + if (api) { + bidObject.api = api; + } + } + + if (mediaTypes.includes(NATIVE)) { + const nativeOrtbRequest = deepAccess(bid, `nativeOrtbRequest`); + if (nativeOrtbRequest) { + bidObject.nativeOrtbRequest = nativeOrtbRequest; + } } return bidObject; } -export function buildBidResponse(adUnit, DEFAULT_CURRENCY, TTL, VIDEO, BANNER) { +export function buildBidResponse(adUnit) { const bidResponse = { requestId: adUnit.requestId, cpm: adUnit.cpm, currency: adUnit.currency || DEFAULT_CURRENCY, width: adUnit.width, height: adUnit.height, - ttl: adUnit.ttl || TTL, + ttl: adUnit.ttl || DEFAULT_TTL, creativeId: adUnit.creativeId, netRevenue: adUnit.netRevenue || true, nurl: adUnit.nurl, @@ -239,6 +346,8 @@ export function buildBidResponse(adUnit, DEFAULT_CURRENCY, TTL, VIDEO, BANNER) { bidResponse.vastXml = adUnit.vastXml; } else if (adUnit.mediaType === BANNER) { bidResponse.ad = adUnit.ad; + } else if (adUnit.mediaType === NATIVE) { + bidResponse.native = {ortb: adUnit.native}; } if (adUnit.adomain && adUnit.adomain.length) { @@ -248,10 +357,6 @@ export function buildBidResponse(adUnit, DEFAULT_CURRENCY, TTL, VIDEO, BANNER) { return bidResponse; } -function isBanner(bid) { - return bid.mediaTypes && bid.mediaTypes.banner; -} - export function generateGeneralParams(generalObject, bidderRequest, adapterVersion) { const domain = window.location.hostname; const { syncEnabled, filterSettings } = config.getConfig('userSync') || {}; diff --git a/libraries/sizeUtils/sizeUtils.js b/libraries/sizeUtils/sizeUtils.js index 41cdd71df89..c0fe8510d7e 100644 --- a/libraries/sizeUtils/sizeUtils.js +++ b/libraries/sizeUtils/sizeUtils.js @@ -27,3 +27,28 @@ export function getAdUnitSizes(adUnit) { } return sizes; } + +/** + * Normalize adUnit.mediaTypes.banner.sizes to Array.> + * + * @param {Array. | Array.>} bidSizes - value of adUnit.mediaTypes.banner.sizes. + * @returns {Array.>} - Normalized value. + */ + +export function normalizeBannerSizes(bidSizes) { + let sizes = []; + if (Array.isArray(bidSizes) && bidSizes.length === 2 && !Array.isArray(bidSizes[0])) { + sizes.push({ + width: parseInt(bidSizes[0], 10), + height: parseInt(bidSizes[1], 10), + }); + } else if (Array.isArray(bidSizes) && Array.isArray(bidSizes[0])) { + bidSizes.forEach((size) => { + sizes.push({ + width: parseInt(size[0], 10), + height: parseInt(size[1], 10), + }); + }); + } + return sizes; +} diff --git a/libraries/teqblazeUtils/bidderUtils.js b/libraries/teqblazeUtils/bidderUtils.js index f9484ebe5d1..84010adbbd6 100644 --- a/libraries/teqblazeUtils/bidderUtils.js +++ b/libraries/teqblazeUtils/bidderUtils.js @@ -148,7 +148,11 @@ export const buildRequestsBase = (config) => { page, placements, coppa: deepAccess(bidderRequest, 'ortb2.regs.coppa') ? 1 : 0, - tmax: bidderRequest.timeout + tmax: bidderRequest.timeout, + bcat: deepAccess(bidderRequest, 'ortb2.bcat'), + badv: deepAccess(bidderRequest, 'ortb2.badv'), + bapp: deepAccess(bidderRequest, 'ortb2.bapp'), + battr: deepAccess(bidderRequest, 'ortb2.battr') }; if (bidderRequest.uspConsent) { diff --git a/modules/33acrossIdSystem.js b/modules/33acrossIdSystem.js index fb5a7712f1f..277cb8b2f6d 100644 --- a/modules/33acrossIdSystem.js +++ b/modules/33acrossIdSystem.js @@ -60,7 +60,7 @@ function calculateResponseObj(response) { }; } -function calculateQueryStringParams({ pid, hem }, gdprConsentData, enabledStorageTypes) { +function calculateQueryStringParams({ pid, pubProvidedHem }, gdprConsentData, enabledStorageTypes) { const uspString = uspDataHandler.getConsentData(); const coppaValue = coppaDataHandler.getCoppa(); const gppConsent = gppDataHandler.getConsentData(); @@ -98,9 +98,9 @@ function calculateQueryStringParams({ pid, hem }, gdprConsentData, enabledStorag params.tp = encodeURIComponent(tp); } - const hemParam = hem || getStoredValue(STORAGE_HEM_KEY, enabledStorageTypes); - if (hemParam) { - params.sha256 = encodeURIComponent(hemParam); + const hem = pubProvidedHem || getStoredValue(STORAGE_HEM_KEY, enabledStorageTypes); + if (hem) { + params.sha256 = encodeURIComponent(hem); } return params; @@ -145,10 +145,51 @@ function getStoredValue(key, enabledStorageTypes) { return storedValue; } -function handleSupplementalId(key, id, storageConfig) { - id - ? storeValue(key, id, storageConfig) - : deleteFromStorage(key); +function filterEnabledSupplementalIds({ tp, fp, hem }, { storeFpid, storeTpid, envelopeAvailable }) { + const ids = []; + + if (storeFpid) { + ids.push( + /** + * [ + * , + * < ID value to store or remove >, + * < clear flag: indicates if existing storage item should be removed or not based on certain condition> + * ] + */ + [STORAGE_FPID_KEY, fp, !fp], + [STORAGE_HEM_KEY, hem, !envelopeAvailable] // Clear hashed email if envelope is not available + ); + } + + if (storeTpid) { + ids.push([STORAGE_TPID_KEY, tp, !tp]); + } + + return ids; +} + +function updateSupplementalIdStorage(supplementalId, storageConfig) { + const [ key, id, clear ] = supplementalId; + + if (clear) { + deleteFromStorage(key); + + return; + } + + if (id) { + storeValue(key, id, storageConfig); + } +} + +function handleSupplementalIds(ids, { enabledStorageTypes, expires, ...options }) { + filterEnabledSupplementalIds(ids, options).forEach((supplementalId) => { + updateSupplementalIdStorage(supplementalId, { + enabledStorageTypes, + expires + }) + }); } /** @type {Submodule} */ @@ -197,8 +238,10 @@ export const thirtyThreeAcrossIdSubmodule = { const { storeFpid = DEFAULT_1PID_SUPPORT, storeTpid = DEFAULT_TPID_SUPPORT, apiUrl = API_URL, - ...options + pid, + hem } = params; + const pubProvidedHem = hem || window._33across?.hem?.sha256; return { callback(cb) { @@ -218,19 +261,17 @@ export const thirtyThreeAcrossIdSubmodule = { }); } - if (storeFpid) { - handleSupplementalId(STORAGE_FPID_KEY, responseObj.fp, { - enabledStorageTypes, - expires: storageConfig.expires - }); - } - - if (storeTpid) { - handleSupplementalId(STORAGE_TPID_KEY, responseObj.tp, { - enabledStorageTypes, - expires: storageConfig.expires - }); - } + handleSupplementalIds({ + fp: responseObj.fp, + tp: responseObj.tp, + hem: pubProvidedHem + }, { + storeFpid, + storeTpid, + envelopeAvailable: !!responseObj.envelope, + enabledStorageTypes, + expires: storageConfig.expires + }); cb(responseObj.envelope); }, @@ -239,7 +280,7 @@ export const thirtyThreeAcrossIdSubmodule = { cb(); } - }, calculateQueryStringParams(options, gdprConsentData, enabledStorageTypes), { + }, calculateQueryStringParams({ pid, pubProvidedHem }, gdprConsentData, enabledStorageTypes), { method: 'GET', withCredentials: true }); diff --git a/modules/33acrossIdSystem.md b/modules/33acrossIdSystem.md index b6b68622344..7dabb08eebd 100644 --- a/modules/33acrossIdSystem.md +++ b/modules/33acrossIdSystem.md @@ -57,4 +57,5 @@ The following settings are available in the `params` property in `userSync.userI ### HEM Collection -33Across ID System supports user's hashed email, if available in storage. +33Across ID System supports user's hashed emails (HEMs). HEMs could be collected from 3 different sources in following +priority order: `hem` configuration parameter, global `_33across.hem.sha256` field or from storage (cookie or local storage). diff --git a/modules/acuityadsBidAdapter.js b/modules/acuityadsBidAdapter.js index 3caaacb46a4..b94234c2c26 100644 --- a/modules/acuityadsBidAdapter.js +++ b/modules/acuityadsBidAdapter.js @@ -12,7 +12,7 @@ export const spec = { gvlid: GVLID, supportedMediaTypes: [BANNER, VIDEO, NATIVE], - isBidRequestValid: isBidRequestValid(['placementId']), + isBidRequestValid: isBidRequestValid(), buildRequests: buildRequests(AD_URL), interpretResponse, getUserSyncs: getUserSyncs(SYNC_URL) diff --git a/modules/adlooxAnalyticsAdapter.js b/modules/adlooxAnalyticsAdapter.js index 0a953584e26..c7321799f3c 100644 --- a/modules/adlooxAnalyticsAdapter.js +++ b/modules/adlooxAnalyticsAdapter.js @@ -81,8 +81,6 @@ const PARAMS_DEFAULT = { 'id11': '$ADLOOX_WEBSITE' }; -const NOOP = function() {}; - let analyticsAdapter = Object.assign(adapter({ analyticsType: 'endpoint' }), { track({ eventType, args }) { if (!analyticsAdapter[`handle_${eventType}`]) return; @@ -109,6 +107,10 @@ analyticsAdapter.enableAnalytics = function(config) { logError(MODULE, 'invalid js options value'); return; } + if (isStr(config.options.js) && !/\.adlooxtracking\.(com|ru)$/.test(parseUrl(config.options.js, { 'noDecodeWholeURL': true }).host)) { + logError(MODULE, "invalid js options value, must be a sub-domain of 'adlooxtracking.com'"); + return; + } if (!(config.options.toselector === undefined || isFn(config.options.toselector))) { logError(MODULE, 'invalid toselector options value'); return; @@ -221,20 +223,24 @@ analyticsAdapter.url = function(url, args, bid) { return url + a2qs(args); } +const preloaded = {}; analyticsAdapter[`handle_${EVENTS.AUCTION_END}`] = function(auctionDetails) { if (!(auctionDetails.auctionStatus == AUCTION_COMPLETED && auctionDetails.bidsReceived.length > 0)) return; - analyticsAdapter[`handle_${EVENTS.AUCTION_END}`] = NOOP; - - logMessage(MODULE, 'preloading verification JS'); const uri = parseUrl(analyticsAdapter.url(`${analyticsAdapter.context.js}#`)); + const href = `${uri.protocol}://${uri.host}${uri.pathname}`; + if (preloaded[href]) return; + + logMessage(MODULE, 'preloading verification JS'); const link = document.createElement('link'); - link.setAttribute('href', `${uri.protocol}://${uri.host}${uri.pathname}`); + link.setAttribute('href', href); link.setAttribute('rel', 'preload'); link.setAttribute('as', 'script'); // TODO fix rules violation insertElement(link); + + preloaded[href] = true; } analyticsAdapter[`handle_${EVENTS.BID_WON}`] = function(bid) { diff --git a/modules/adnuntiusBidAdapter.js b/modules/adnuntiusBidAdapter.js index ffd02e43a99..f8bf37af42a 100644 --- a/modules/adnuntiusBidAdapter.js +++ b/modules/adnuntiusBidAdapter.js @@ -1,5 +1,5 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; -import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {BANNER, VIDEO, NATIVE} from '../src/mediaTypes.js'; import {isStr, isEmpty, deepAccess, getUnixTimestampFromNow, convertObjectToArray, getWindowTop} from '../src/utils.js'; import { config } from '../src/config.js'; import { getStorageManager } from '../src/storageManager.js'; @@ -12,7 +12,7 @@ const BIDDER_CODE_DEAL_ALIASES = [1, 2, 3, 4, 5].map(num => { const ENDPOINT_URL = 'https://ads.adnuntius.delivery/i'; const ENDPOINT_URL_EUROPE = 'https://europe.delivery.adnuntius.com/i'; const GVLID = 855; -const SUPPORTED_MEDIA_TYPES = [BANNER, VIDEO]; +const SUPPORTED_MEDIA_TYPES = [BANNER, VIDEO, NATIVE]; const MAXIMUM_DEALS_LIMIT = 5; const VALID_BID_TYPES = ['netBid', 'grossBid']; const METADATA_KEY = 'adn.metaData'; @@ -319,6 +319,9 @@ export const spec = { const adUnit = {...bidTargeting, auId: bid.params.auId, targetId: targetId}; if (mediaType === VIDEO) { adUnit.adType = 'VAST'; + } else if (mediaType === NATIVE) { + adUnit.adType = 'NATIVE'; + adUnit.nativeRequest = mediaTypeData.ortb; } const maxDeals = Math.max(0, Math.min(bid.params.maxDeals || 0, MAXIMUM_DEALS_LIMIT)); if (maxDeals > 0) { @@ -391,10 +394,13 @@ export const spec = { const isDeal = dealCount > 0; const renderSource = isDeal ? ad : adUnit; if (renderSource.vastXml) { - adResponse.vastXml = renderSource.vastXml - adResponse.mediaType = VIDEO + adResponse.vastXml = renderSource.vastXml; + adResponse.mediaType = VIDEO; + } else if (renderSource.nativeJson) { + adResponse.mediaType = NATIVE; + adResponse.native = renderSource.nativeJson; } else { - adResponse.ad = renderSource.html + adResponse.ad = renderSource.html; } return adResponse; } diff --git a/modules/airgridRtdProvider.js b/modules/airgridRtdProvider.js index 300744d62fe..c547528a57e 100644 --- a/modules/airgridRtdProvider.js +++ b/modules/airgridRtdProvider.js @@ -17,7 +17,7 @@ import {MODULE_TYPE_RTD} from '../src/activities/modules.js'; const MODULE_NAME = 'realTimeData'; const SUBMODULE_NAME = 'airgrid'; -const AG_TCF_ID = 782; +const MIQ_TCF_ID = 101; export const AG_AUDIENCE_IDS_KEY = 'edkt_matched_audience_ids'; export const storage = getStorageManager({ @@ -76,7 +76,7 @@ export function setAudiencesAsBidderOrtb2(bidConfig, rtdConfig, audiences) { const agUserData = [ { - id: String(AG_TCF_ID), + id: String(MIQ_TCF_ID), ext: { segtax: 540, }, @@ -129,7 +129,7 @@ export const airgridSubmodule = { name: SUBMODULE_NAME, init: init, getBidRequestData: passAudiencesToBidders, - gvlid: AG_TCF_ID + gvlid: MIQ_TCF_ID }; submodule(MODULE_NAME, airgridSubmodule); diff --git a/modules/appnexusBidAdapter.js b/modules/appnexusBidAdapter.js index 4270b47d91e..099dd989a1d 100644 --- a/modules/appnexusBidAdapter.js +++ b/modules/appnexusBidAdapter.js @@ -354,18 +354,23 @@ export const spec = { if (bidRequests[0].userId) { let eids = []; - bidRequests[0].userIdAsEids.forEach(eid => { - if (!eid || !eid.uids || eid.uids.length < 1) { return; } - eid.uids.forEach(uid => { - let tmp = {'source': eid.source, 'id': uid.id}; - if (eid.source == 'adserver.org') { - tmp.rti_partner = 'TDID'; - } else if (eid.source == 'uidapi.com') { - tmp.rti_partner = 'UID2'; - } - eids.push(tmp); + const processEids = (uids) => { + uids.forEach(eid => { + if (!eid || !eid.uids || eid.uids.length < 1) { return; } + eid.uids.forEach(uid => { + let tmp = {'source': eid.source, 'id': uid.id}; + if (eid.source == 'adserver.org') { + tmp.rti_partner = 'TDID'; + } else if (eid.source == 'uidapi.com') { + tmp.rti_partner = 'UID2'; + } + eids.push(tmp); + }); }); - }); + } + if (bidRequests[0].userIdAsEids) { + processEids(bidRequests[0].userIdAsEids); + } if (eids.length) { payload.eids = eids; } diff --git a/modules/asoBidAdapter.js b/modules/asoBidAdapter.js index 08612757de1..388eee86fe2 100644 --- a/modules/asoBidAdapter.js +++ b/modules/asoBidAdapter.js @@ -18,7 +18,8 @@ export const spec = { aliases: [ {code: 'bcmint'}, {code: 'bidgency'}, - {code: 'kuantyx'} + {code: 'kuantyx'}, + {code: 'cordless'} ], isBidRequestValid: bid => { diff --git a/modules/blueBidAdapter.js b/modules/blueBidAdapter.js new file mode 100644 index 00000000000..ba8d0a9e8fe --- /dev/null +++ b/modules/blueBidAdapter.js @@ -0,0 +1,122 @@ +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER } from '../src/mediaTypes.js'; +import { getStorageManager } from '../src/storageManager.js'; +import { deepSetValue, isFn, isPlainObject } from '../src/utils.js'; + +const BIDDER_CODE = 'blue'; +const ENDPOINT_URL = 'https://bidder-us-east-1.getblue.io/engine/?src=prebid'; +const GVLID = 620; // GVLID for your bidder +const COOKIE_NAME = 'ckid'; // Cookie name for identifying users +const CURRENCY = 'USD'; // Currency used in bid floors + +export const storage = getStorageManager({ bidderCode: BIDDER_CODE }); + +const converter = ortbConverter({ + context: { + netRevenue: true, // Default netRevenue setting + ttl: 100, // Default time-to-live for bid responses + }, + imp, + request, +}); + +function request(buildRequest, imps, bidderRequest, context) { + let request = buildRequest(imps, bidderRequest, context); + deepSetValue(request, 'site.publisher.id', context.publisherId); + return request; +} + +function imp(buildImp, bidRequest, context) { + const imp = buildImp(bidRequest, context); + const floor = getBidFloor(bidRequest); + if (floor) { + imp.bidfloor = floor; + imp.bidfloorcur = CURRENCY; + } + return imp; +} + +function getBidFloor(bid) { + if (isFn(bid.getFloor)) { + let floor = bid.getFloor({ + currency: CURRENCY, + mediaType: BANNER, + size: '*', + }); + if ( + isPlainObject(floor) && + !isNaN(floor.floor) && + floor.currency === CURRENCY + ) { + return floor.floor; + } + } + return null; +} + +export const spec = { + code: BIDDER_CODE, + gvlid: GVLID, + supportedMediaTypes: [BANNER], // Supported ad types + + // Validate bid request + isBidRequestValid: function (bid) { + return !!bid.params.placementId && !!bid.params.publisherId; + }, + + // Build OpenRTB requests using `ortbConverter` + buildRequests: function (validBidRequests, bidderRequest) { + const context = { + publisherId: validBidRequests.find( + (bidRequest) => bidRequest.params?.publisherId + )?.params.publisherId, + }; + + const ortbRequest = converter.toORTB({ + bidRequests: validBidRequests, + bidderRequest, + context, + }); + + // Add GVLID and cookie ID to the request + ortbRequest.ext = ortbRequest.ext || {}; + deepSetValue(ortbRequest, 'ext.gvlid', GVLID); + + // Include user cookie if available + const ckid = storage.getDataFromLocalStorage('blueID') || storage.getCookie(COOKIE_NAME) || null; + if (ckid) { + deepSetValue(ortbRequest, 'user.ext.buyerid', ckid); + } + + return { + method: 'POST', + url: ENDPOINT_URL, + data: JSON.stringify(ortbRequest), + options: { + contentType: 'text/plain', + }, + }; + }, + + // Interpret OpenRTB responses using `ortbConverter` + interpretResponse: function (serverResponse, request) { + const ortbResponse = serverResponse.body; + + // Parse the OpenRTB response into Prebid bid responses + const prebidResponses = converter.fromORTB({ + response: ortbResponse, + request: request.data, + }).bids; + + // Example: Modify bid responses if needed + prebidResponses.forEach((bid) => { + bid.meta = bid.meta || {}; + bid.meta.adapterVersion = '1.0.0'; + }); + + return prebidResponses; + }, +}; + +registerBidder(spec); diff --git a/modules/blueBidAdapter.md b/modules/blueBidAdapter.md new file mode 100644 index 00000000000..4f446b1ff4b --- /dev/null +++ b/modules/blueBidAdapter.md @@ -0,0 +1,28 @@ +# Overview + +Module Name: Blue Bidder Adapter +Module Type: Bidder Adapter +Maintainer: celsooliveira@getblue.io + +# Description + +Module that connects to Blue's demand sources. + +# Test Parameters +``` + var adUnits = [ + { + code: 'banner-ad-div', + sizes: [[300, 250], [728, 90]], + bids: [ + { + bidder: 'blue', + params: { + publisherId: "xpto", + placementId: "xpto", + } + } + ] + } + ]; +``` diff --git a/modules/conceptxBidAdapter.js b/modules/conceptxBidAdapter.js index 47c50a4c0ad..67ebd88e4e4 100644 --- a/modules/conceptxBidAdapter.js +++ b/modules/conceptxBidAdapter.js @@ -57,6 +57,9 @@ export const spec = { return bidResponses } const firstSeat = firstBid.ads[0] + if (!firstSeat) { + return bidResponses + } const bidResponse = { requestId: firstSeat.requestId, cpm: firstSeat.cpm, diff --git a/modules/consumableBidAdapter.js b/modules/consumableBidAdapter.js index cb802508de9..e01078890f9 100644 --- a/modules/consumableBidAdapter.js +++ b/modules/consumableBidAdapter.js @@ -33,7 +33,8 @@ export const spec = { /** * Make a server request from the list of BidRequests. * - * @param {validBidRequests[]} - an array of bids + * @param {validBidRequests[]} validBidRequests An array of bids + * @param {Object} bidderRequest The bidder's request info. * @return ServerRequest Info describing the request to the server. */ @@ -300,6 +301,7 @@ function retrieveAd(decision, unitId, unitName) { function handleEids(data, validBidRequests) { let bidUserIdAsEids = deepAccess(validBidRequests, '0.userIdAsEids'); if (isArray(bidUserIdAsEids) && bidUserIdAsEids.length > 0) { + bidUserIdAsEids = bidUserIdAsEids.filter(e => typeof e === 'object'); deepSetValue(data, 'user.eids', bidUserIdAsEids); } else { deepSetValue(data, 'user.eids', undefined); diff --git a/modules/contxtfulBidAdapter.js b/modules/contxtfulBidAdapter.js index 4905c499998..f7d263ae74f 100644 --- a/modules/contxtfulBidAdapter.js +++ b/modules/contxtfulBidAdapter.js @@ -8,7 +8,7 @@ import { interpretResponse, getUserSyncs as getUserSyncsLib, } from '../libraries/teqblazeUtils/bidderUtils.js'; -import {ortbConverter} from '../libraries/ortbConverter/converter.js'; +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; // Constants const BIDDER_CODE = 'contxtful'; @@ -75,7 +75,7 @@ const extractParameters = (config) => { // Construct the Payload towards the Bidding endpoint const buildRequests = (validBidRequests = [], bidderRequest = {}) => { - const ortb2 = converter.toORTB({bidderRequest: bidderRequest, bidRequests: validBidRequests}); + const ortb2 = converter.toORTB({ bidderRequest: bidderRequest, bidRequests: validBidRequests }); const bidRequests = []; _each(validBidRequests, bidRequest => { @@ -88,7 +88,7 @@ const buildRequests = (validBidRequests = [], bidderRequest = {}) => { }); const config = pbjsConfig.getConfig(); config.pbjsVersion = PREBID_VERSION; - const {version, customer} = extractParameters(config) + const { version, customer } = extractParameters(config) const adapterUrl = buildUrl({ protocol: 'https', host: BIDDER_ENDPOINT, @@ -151,6 +151,17 @@ const getSamplingRate = (bidderConfig, eventType) => { return entry ? entry[1] : DEFAULT_SAMPLING_RATE; }; +const logBidderError = ({ error, bidderRequest }) => { + if (error) { + let jsonReason = { + message: error.reason?.message, + stack: error.reason?.stack, + }; + error.reason = jsonReason; + } + logEvent('onBidderError', { error, bidderRequest }); +}; + // Handles the logging of events const logEvent = (eventType, data) => { try { @@ -159,7 +170,7 @@ const logEvent = (eventType, data) => { // Get Config const bidderConfig = pbjsConfig.getConfig(); - const {version, customer} = extractParameters(bidderConfig); + const { version, customer } = extractParameters(bidderConfig); // Sampled monitoring if (['onBidBillable', 'onAdRenderSucceeded'].includes(eventType)) { @@ -206,12 +217,12 @@ export const spec = { buildRequests, interpretResponse, getUserSyncs, - onBidWon: function(bid) { logEvent('onBidWon', bid); }, - onBidBillable: function(bid) { logEvent('onBidBillable', bid); }, - onAdRenderSucceeded: function(bid) { logEvent('onAdRenderSucceeded', bid); }, - onSetTargeting: function(bid) { }, - onTimeout: function(timeoutData) { logEvent('onTimeout', timeoutData); }, - onBidderError: function({ error, bidderRequest }) { logEvent('onBidderError', { error, bidderRequest }); }, + onBidWon: function (bid) { logEvent('onBidWon', bid); }, + onBidBillable: function (bid) { logEvent('onBidBillable', bid); }, + onAdRenderSucceeded: function (bid) { logEvent('onAdRenderSucceeded', bid); }, + onSetTargeting: function (bid) { }, + onTimeout: function (timeoutData) { logEvent('onTimeout', timeoutData); }, + onBidderError: logBidderError, }; registerBidder(spec); diff --git a/modules/contxtfulRtdProvider.js b/modules/contxtfulRtdProvider.js index 55623c00591..1bd977afed1 100644 --- a/modules/contxtfulRtdProvider.js +++ b/modules/contxtfulRtdProvider.js @@ -15,6 +15,7 @@ import { isEmpty, buildUrl, isArray, + generateUUID, } from '../src/utils.js'; import { loadExternalScript } from '../src/adloader.js'; import { getStorageManager } from '../src/storageManager.js'; @@ -26,13 +27,17 @@ const MODULE = `${MODULE_NAME}RtdProvider`; const CONTXTFUL_HOSTNAME_DEFAULT = 'api.receptivity.io'; const CONTXTFUL_DEFER_DEFAULT = 0; +let _sm; +function sm() { + return _sm ??= generateUUID(); +} + const storageManager = getStorageManager({ moduleType: MODULE_TYPE_RTD, moduleName: MODULE_NAME, }); let rxApi = null; -let isFirstBidRequestCall = true; /** * Return current receptivity value for the requester. @@ -150,7 +155,7 @@ function initCustomer(config) { addConnectorEventListener(customer, config); - const loadScript = () => loadExternalScript(CONNECTOR_URL, MODULE_TYPE_RTD, MODULE_NAME); + const loadScript = () => loadExternalScript(CONNECTOR_URL, MODULE_TYPE_RTD, MODULE_NAME, undefined, undefined, { 'data-sm': sm() }); // Optionally defer the loading of the script if (Number.isFinite(defer) && defer > 0) { setTimeout(loadScript, defer); @@ -228,9 +233,6 @@ function getTargetingData(adUnits, config, _userConsent) { */ function getBidRequestData(reqBidsConfigObj, onDone, config, userConsent) { function onReturn() { - if (isFirstBidRequestCall) { - isFirstBidRequestCall = false; - } onDone(); } @@ -245,16 +247,10 @@ function getBidRequestData(reqBidsConfigObj, onDone, config, userConsent) { let fromStorage = prepareBatch(bidders, (bidder) => loadSessionReceptivity(`${config?.params?.customer}_${bidder}`)); let sources = [fromStorage, fromApi]; - if (isFirstBidRequestCall) { - sources.reverse(); - } let rxBatch = Object.assign(...sources); - let singlePointEvents; - if (isEmpty(rxBatch)) { - singlePointEvents = btoa(JSON.stringify({ ui: getUiEvents() })); - } + let singlePointEvents = btoa(JSON.stringify({ ui: getUiEvents() })); bidders .forEach(bidderCode => { @@ -266,6 +262,7 @@ function getBidRequestData(reqBidsConfigObj, onDone, config, userConsent) { ext: { rx: rxBatch[bidderCode], events: singlePointEvents, + sm: sm(), params: { ev: config.params?.version, ci: config.params?.customer, diff --git a/modules/eplanningBidAdapter.js b/modules/eplanningBidAdapter.js index 1fe5cb09c10..5bd1eab8863 100644 --- a/modules/eplanningBidAdapter.js +++ b/modules/eplanningBidAdapter.js @@ -4,6 +4,7 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {getStorageManager} from '../src/storageManager.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {isSlotMatchingAdUnitCode} from '../libraries/gptUtils/gptUtils.js'; +import {serializeSupplyChain} from '../libraries/schainSerializer/schainSerializer.js'; const BIDDER_CODE = 'eplanning'; export const storage = getStorageManager({bidderCode: BIDDER_CODE}); @@ -39,6 +40,7 @@ export const spec = { const method = 'GET'; const dfpClientId = '1'; const sec = 'ROS'; + const schain = bidRequests[0].schain; let url; let params; const urlConfig = getUrlConfig(bidRequests); @@ -70,7 +72,9 @@ export const spec = { if (pcrs) { params.crs = pcrs; } - + if (schain && schain.nodes.length <= 2) { + params.sch = serializeSupplyChain(schain, ['asi', 'sid', 'hp', 'rid', 'name', 'domain']); + } if (referrerUrl) { params.fr = cutUrl(referrerUrl); } diff --git a/modules/escalaxBidAdapter.js b/modules/escalaxBidAdapter.js index 70a10d748bc..027e41d7c56 100644 --- a/modules/escalaxBidAdapter.js +++ b/modules/escalaxBidAdapter.js @@ -84,8 +84,8 @@ export const spec = { const subdomain = getSubdomain(); const endpointURL = ESCALAX_URL .replace(ESCALAX_SUBDOMAIN_MACRO, subdomain || ESCALAX_DEFAULT_SUBDOMAIN) - .replace(ESCALAX_ACCOUNT_ID_MACRO, sourceId) - .replace(ESCALAX_SOURCE_ID_MACRO, accountId); + .replace(ESCALAX_SOURCE_ID_MACRO, sourceId) + .replace(ESCALAX_ACCOUNT_ID_MACRO, accountId); const request = converter.toORTB({ bidRequests: validBidRequests, bidderRequest }); return { method: 'POST', diff --git a/modules/improvedigitalBidAdapter.js b/modules/improvedigitalBidAdapter.js index 33ba08e8fd2..a0347382dd1 100644 --- a/modules/improvedigitalBidAdapter.js +++ b/modules/improvedigitalBidAdapter.js @@ -6,14 +6,6 @@ import {Renderer} from '../src/Renderer.js'; import {hasPurpose1Consent} from '../src/utils/gdpr.js'; import {ortbConverter} from '../libraries/ortbConverter/converter.js'; import {convertCurrency} from '../libraries/currencyUtils/currency.js'; -/** - * See https://github.com/prebid/Prebid.js/pull/8827 for details on linting exception - * ImproveDigital only imports after winning a bid and only if the creative cannot reach top - * Also see https://github.com/prebid/Prebid.js/issues/11656 - */ -// eslint-disable-next-line no-restricted-imports -import {loadExternalScript} from '../src/adloader.js'; -import { MODULE_TYPE_BIDDER } from '../src/activities/modules.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -225,7 +217,6 @@ export const CONVERTER = ortbConverter({ renderer: ID_OUTSTREAM.createRenderer(bidRequest) }) } - ID_RAZR.forwardBid({bidRequest, bid: bidResponse}); return bidResponse; }, overrides: { @@ -367,61 +358,3 @@ const ID_OUTSTREAM = { bid.renderer.handleVideoEvent({ id, eventName }); }, }; - -const ID_RAZR = { - RENDERER_URL: 'https://cdn.360yield.com/razr/tag.js', - - forwardBid({bidRequest, bid}) { - if (bid.mediaType !== BANNER) { - return; - } - - const cfg = { - prebid: { - bidRequest, - bid - } - }; - - const cfgStr = JSON.stringify(cfg).replace(/<\/script>/ig, '\\x3C/script>'); - const s = ``; - // prepend RAZR config to ad markup: - bid.ad = s + bid.ad; - - this.installListener(); - }, - - installListener() { - if (this._listenerInstalled) { - return; - } - - window.addEventListener('message', function(e) { - const data = e.data?.razr?.load; - if (!data) { - return; - } - - if (e.source) { - data.source = e.source; - if (data.id) { - e.source.postMessage({ - razr: { - id: data.id - } - }, '*'); - } - } - - const ns = window.razr = window.razr || {}; - ns.q = ns.q || []; - ns.q.push(data); - - if (!ns.loaded) { - loadExternalScript(ID_RAZR.RENDERER_URL, MODULE_TYPE_BIDDER, BIDDER_CODE); - } - }); - - this._listenerInstalled = true; - } -}; diff --git a/modules/intentIqAnalyticsAdapter.js b/modules/intentIqAnalyticsAdapter.js index 1cf270117b7..e3b5625dc7b 100644 --- a/modules/intentIqAnalyticsAdapter.js +++ b/modules/intentIqAnalyticsAdapter.js @@ -304,7 +304,7 @@ function constructFullUrl(data) { '&jsver=' + VERSION + '&source=pbjs' + '&payload=' + JSON.stringify(report) + - '&uh=' + iiqAnalyticsAnalyticsAdapter.initOptions.clientsHints + + '&uh=' + encodeURIComponent(iiqAnalyticsAnalyticsAdapter.initOptions.clientsHints) + (gppData.gppString ? '&gpp=' + encodeURIComponent(gppData.gppString) : ''); url = appendVrrefAndFui(url, iiqAnalyticsAnalyticsAdapter.initOptions.domainName); diff --git a/modules/ixBidAdapter.js b/modules/ixBidAdapter.js index e2a712e0afc..a96b0da132b 100644 --- a/modules/ixBidAdapter.js +++ b/modules/ixBidAdapter.js @@ -496,6 +496,11 @@ function parseBid(rawBid, currency, bidRequest) { if (rawBid.ext?.dsa) { bid.meta.dsa = rawBid.ext.dsa } + + if (rawBid.ext?.ibv) { + bid.ext = bid.ext || {} + bid.ext.ibv = rawBid.ext.ibv + } return bid; } diff --git a/modules/liveIntentRtdProvider.js b/modules/liveIntentRtdProvider.js new file mode 100644 index 00000000000..92cd09ae346 --- /dev/null +++ b/modules/liveIntentRtdProvider.js @@ -0,0 +1,52 @@ +/** + * This module adds the LiveIntent provider to the Real Time Data module (rtdModule). + */ +import { submodule } from '../src/hook.js'; +import {deepAccess, deepSetValue} from '../src/utils.js' + +/** + * @typedef {import('../modules/rtdModule/index.js').RtdSubmodule} RtdSubmodule + * @typedef {import('../modules/rtdModule/index.js').SubmoduleConfig} SubmoduleConfig + * @typedef {import('../modules/rtdModule/index.js').UserConsentData} UserConsentData + */ + +const SUBMODULE_NAME = 'liveintent'; +const GVLID = 148; + +/** + * Init + * @param {Object} config Module configuration + * @param {UserConsentData} userConsent User consent + * @returns true + */ +const init = (config, userConsent) => { + return true; +} + +/** + * onBidRequest is called for each bidder during an auction and contains the bids for that bidder. + * + * @param {Object} bidRequest + * @param {SubmoduleConfig} config + * @param {UserConsentData} userConsent + */ + +function onBidRequest(bidRequest, config, userConsent) { + bidRequest.bids.forEach(bid => { + const providedSegmentsFromUserId = deepAccess(bid, 'userId.lipb.segments', []) + if (providedSegmentsFromUserId.length > 0) { + const providedSegments = { name: 'liveintent.com', segment: providedSegmentsFromUserId.map(id => ({ id })) } + const existingData = deepAccess(bid, 'ortb2.user.data', []) + deepSetValue(bid, 'ortb2.user.data', existingData.concat(providedSegments)) + } + }) +} + +export const liveIntentRtdSubmodule = { + name: SUBMODULE_NAME, + gvlid: GVLID, + init: init, + onBidRequestEvent: onBidRequest +}; + +submodule('realTimeData', liveIntentRtdSubmodule); diff --git a/modules/liveIntentRtdProvider.md b/modules/liveIntentRtdProvider.md new file mode 100644 index 00000000000..e742fb74bee --- /dev/null +++ b/modules/liveIntentRtdProvider.md @@ -0,0 +1,45 @@ +# Overview + +Module Name: LiveIntent Provider +Module Type: Rtd Provider +Maintainer: product@liveIntent.com + +# Description + +This module extracts segments from `bidRequest.userId.lipb.segments` enriched by the userID module and +injects them in `ortb2.user.data` array entry. + +Please visit [LiveIntent](https://www.liveIntent.com/) for more information. + +# Testing + +To run the example and test the Rtd provider: + +```sh +gulp serve --modules=appnexusBidAdapter,rtdModule,liveIntentRtdProvider,userId,liveIntentIdSystem +``` + +Open chrome with this URL: +`http://localhost:9999/integrationExamples/gpt/liveIntentRtdProviderExample.html` + +To run the unit test: +```sh +gulp test --file "test/spec/modules/liveIntentRtdProvider_spec.js" +``` + +# Integration + +```bash +gulp build --modules=userId,liveIntentIdSystem,rtdModule,liveIntentRtdProvider +``` + +```javascript +pbjs.setConfig({ + realTimeData: { + dataProviders:[{ + name: 'liveintent', + waitForIt: true + }] + } +}); +``` diff --git a/modules/magniteAnalyticsAdapter.js b/modules/magniteAnalyticsAdapter.js index 7eb20439f5f..25adf2b8a3c 100644 --- a/modules/magniteAnalyticsAdapter.js +++ b/modules/magniteAnalyticsAdapter.js @@ -264,7 +264,8 @@ export const parseBidResponse = (bid, previousBidResponse) => { return pick(bid, [ 'bidPriceUSD', () => responsePrice, 'dealId', dealId => dealId || undefined, - 'mediaType', + 'mediaType', () => bid?.meta?.mediaType ?? bid.mediaType, + 'ogMediaType', () => bid?.meta?.mediaType && bid.mediaType !== bid?.meta?.mediaType ? bid.mediaType : undefined, 'dimensions', () => { const width = bid.width || bid.playerWidth; const height = bid.height || bid.playerHeight; diff --git a/modules/medianetAnalyticsAdapter.js b/modules/medianetAnalyticsAdapter.js index 1b4ccc45c88..526399014c1 100644 --- a/modules/medianetAnalyticsAdapter.js +++ b/modules/medianetAnalyticsAdapter.js @@ -20,6 +20,8 @@ import {AUCTION_COMPLETED, AUCTION_IN_PROGRESS, getPriceGranularity} from '../sr import {includes} from '../src/polyfill.js'; import {getGlobal} from '../src/prebidGlobal.js'; import {convertCurrency} from '../libraries/currencyUtils/currency.js'; +import {INSTREAM, OUTSTREAM} from '../src/video.js'; +import {ADPOD} from '../src/mediaTypes.js'; const analyticsType = 'endpoint'; const ENDPOINT = 'https://pb-logs.media.net/log?logid=kfk&evtid=prebid_analytics_events_client'; @@ -266,11 +268,23 @@ class AdSlot { tmax: this.tmax, targ: JSON.stringify(this.targeting), ismn: this.medianetPresent, - vplcmtt: this.context, + vplcmtt: this.getVideoPlacement(), }, this.adext && {'adext': JSON.stringify(this.adext)}, ); } + getVideoPlacement() { + switch (this.context) { + case INSTREAM: + return 1 + case OUTSTREAM: + return 6 + case ADPOD: + return 7 + default: + return 0 + } + } } class BidWrapper { diff --git a/modules/minutemediaBidAdapter.js b/modules/minutemediaBidAdapter.js index 4e83c5c6db4..d5aae8a84d0 100644 --- a/modules/minutemediaBidAdapter.js +++ b/modules/minutemediaBidAdapter.js @@ -1,35 +1,19 @@ -import { - logWarn, - logInfo, - isArray, - deepAccess, - triggerPixel, -} from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER, VIDEO } from '../src/mediaTypes.js'; -import { - getEndpoint, - generateBidsParams, - generateGeneralParams, - buildBidResponse, -} from '../libraries/riseUtils/index.js'; +import {logWarn} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {makeBaseSpec} from '../libraries/riseUtils/index.js'; -const SUPPORTED_AD_TYPES = [BANNER, VIDEO]; const BIDDER_CODE = 'minutemedia'; -const ADAPTER_VERSION = '6.0.0'; -const TTL = 360; -const DEFAULT_CURRENCY = 'USD'; const BASE_URL = 'https://hb.minutemedia-prebid.com/'; +const GVLID = 918; const MODES = { PRODUCTION: 'hb-mm-multi', TEST: 'hb-multi-mm-test' }; export const spec = { + ...makeBaseSpec(BASE_URL, MODES), code: BIDDER_CODE, - gvlid: 918, - version: ADAPTER_VERSION, - supportedMediaTypes: SUPPORTED_AD_TYPES, + gvlid: GVLID, isBidRequestValid: function (bidRequest) { if (!bidRequest.params) { logWarn('no params have been set to MinuteMedia adapter'); @@ -42,64 +26,6 @@ export const spec = { } return true; - }, - buildRequests: function (validBidRequests, bidderRequest) { - const combinedRequestsObject = {}; - - const generalObject = validBidRequests[0]; - const testMode = generalObject.params.testMode; - - combinedRequestsObject.params = generateGeneralParams(generalObject, bidderRequest, ADAPTER_VERSION); - combinedRequestsObject.bids = generateBidsParams(validBidRequests, bidderRequest); - - return { - method: 'POST', - url: getEndpoint(testMode, BASE_URL, MODES), - data: combinedRequestsObject - }; - }, - interpretResponse: function ({ body }) { - const bidResponses = []; - - if (body.bids) { - body.bids.forEach(adUnit => { - const bidResponse = buildBidResponse(adUnit, DEFAULT_CURRENCY, TTL, VIDEO, BANNER); - bidResponses.push(bidResponse); - }); - } - - return bidResponses; - }, - getUserSyncs: function (syncOptions, serverResponses) { - const syncs = []; - for (const response of serverResponses) { - if (syncOptions.iframeEnabled && deepAccess(response, 'body.params.userSyncURL')) { - syncs.push({ - type: 'iframe', - url: deepAccess(response, 'body.params.userSyncURL') - }); - } - if (syncOptions.pixelEnabled && isArray(deepAccess(response, 'body.params.userSyncPixels'))) { - const pixels = response.body.params.userSyncPixels.map(pixel => { - return { - type: 'image', - url: pixel - }; - }); - syncs.push(...pixels); - } - } - return syncs; - }, - onBidWon: function (bid) { - if (bid == null) { - return; - } - - logInfo('onBidWon:', bid); - if (bid.hasOwnProperty('nurl') && bid.nurl.length > 0) { - triggerPixel(bid.nurl); - } } }; diff --git a/modules/minutemediaBidAdapter.md b/modules/minutemediaBidAdapter.md index 66b54adaf0e..b22cf856364 100644 --- a/modules/minutemediaBidAdapter.md +++ b/modules/minutemediaBidAdapter.md @@ -13,7 +13,7 @@ Module that connects to MinuteMedia's demand sources. The MinuteMedia adapter requires setup and approval from the MinuteMedia. Please reach out to hb@minutemedia.com to create an MinuteMedia account. -The adapter supports Video(instream) & Banner. +The adapter supports Video(instream), Banner, Native and multi-format bid requests. # Bid Parameters ## Video diff --git a/modules/missenaBidAdapter.js b/modules/missenaBidAdapter.js index cd5650e73c2..c5c8678e0b1 100644 --- a/modules/missenaBidAdapter.js +++ b/modules/missenaBidAdapter.js @@ -12,6 +12,7 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { getStorageManager } from '../src/storageManager.js'; import { getCurrencyFromBidderRequest } from '../libraries/ortb2Utils/currency.js'; import { isAutoplayEnabled } from '../libraries/autoplayDetection/autoplay.js'; +import { normalizeBannerSizes } from '../libraries/sizeUtils/sizeUtils.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -87,6 +88,7 @@ function toPayload(bidRequest, bidderRequest) { payload.coppa = bidderRequest?.ortb2?.regs?.coppa ? 1 : 0; payload.autoplay = isAutoplayEnabled() === true ? 1 : 0; payload.screen = { height: screen.height, width: screen.width }; + payload.sizes = normalizeBannerSizes(bidRequest.mediaTypes.banner.sizes); return { method: 'POST', diff --git a/modules/nativeRendering.js b/modules/nativeRendering.js index 8e6b6baab55..a6a404a0253 100644 --- a/modules/nativeRendering.js +++ b/modules/nativeRendering.js @@ -7,7 +7,8 @@ import {getCreativeRendererSource} from '../src/creativeRenderers.js'; function getRenderingDataHook(next, bidResponse, options) { if (isNativeResponse(bidResponse)) { next.bail({ - native: getNativeRenderingData(bidResponse, auctionManager.index.getAdUnit(bidResponse)) + native: getNativeRenderingData(bidResponse, auctionManager.index.getAdUnit(bidResponse)), + rendererVersion: 2 // 9.28 fixed a rendering bug; this signals to PUC that the native renderer is safe to use }) } else { next(bidResponse, options) diff --git a/modules/oguryBidAdapter.js b/modules/oguryBidAdapter.js index ecf512f11d3..d4e5fea1cf0 100644 --- a/modules/oguryBidAdapter.js +++ b/modules/oguryBidAdapter.js @@ -1,10 +1,11 @@ 'use strict'; -import {BANNER} from '../src/mediaTypes.js'; -import {getWindowSelf, getWindowTop, isFn, logWarn, deepAccess, isPlainObject} from '../src/utils.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {ajax} from '../src/ajax.js'; -import {getAdUnitSizes} from '../libraries/sizeUtils/sizeUtils.js'; +import { BANNER } from '../src/mediaTypes.js'; +import { getWindowSelf, getWindowTop, isFn, deepAccess, isPlainObject, deepSetValue, mergeDeep } from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { ajax } from '../src/ajax.js'; +import { getAdUnitSizes } from '../libraries/sizeUtils/sizeUtils.js'; +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; const BIDDER_CODE = 'ogury'; const GVLID = 31; @@ -12,38 +13,68 @@ 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.7.0'; +const ADAPTER_VERSION = '2.0.0'; + +export const converter = ortbConverter({ + context: { + netRevenue: true, + ttl: 60, + mediaType: 'banner' + }, + + request(buildRequest, imps, bidderRequest, context) { + const req = buildRequest(imps, bidderRequest, context); + req.tmax = DEFAULT_TIMEOUT; + deepSetValue(req, 'device.pxratio', window.devicePixelRatio); + deepSetValue(req, 'site.page', getWindowContext().location.href); + + req.ext = mergeDeep({}, req.ext, { + adapterversion: ADAPTER_VERSION, + prebidversion: '$prebid.version$' + }); -function getClientWidth() { - const documentElementClientWidth = window.top.document.documentElement.clientWidth - ? window.top.document.documentElement.clientWidth - : 0 - const innerWidth = window.top.innerWidth ? window.top.innerWidth : 0 - const outerWidth = window.top.outerWidth ? window.top.outerWidth : 0 - const screenWidth = window.top.screen.width ? window.top.screen.width : 0 + const bidWithAssetKey = bidderRequest.bids.find(bid => Boolean(deepAccess(bid, 'params.assetKey', false))); + if (bidWithAssetKey) deepSetValue(req, 'site.id', bidWithAssetKey.params.assetKey); - return documentElementClientWidth || innerWidth || outerWidth || screenWidth -} + const bidWithUserIds = bidderRequest.bids.find(bid => Boolean(bid.userId)); + if (bidWithUserIds) deepSetValue(req, 'user.ext.uids', bidWithUserIds.userId); -function getClientHeight() { - const documentElementClientHeight = window.top.document.documentElement.clientHeight - ? window.top.document.documentElement.clientHeight - : 0 - const innerHeight = window.top.innerHeight ? window.top.innerHeight : 0 - const outerHeight = window.top.outerHeight ? window.top.outerHeight : 0 - const screenHeight = window.top.screen.height ? window.top.screen.height : 0 + return req; + }, - return documentElementClientHeight || innerHeight || outerHeight || screenHeight -} + imp(buildImp, bidRequest, context) { + const imp = buildImp(bidRequest, context); + const timeSpentOnPage = document.timeline && document.timeline.currentTime ? document.timeline.currentTime : 0 + const gpid = bidRequest.adUnitCode; + imp.tagid = bidRequest.adUnitCode; + imp.ext = mergeDeep({}, bidRequest.params, { timeSpentOnPage, gpid }, imp.ext); + + const bidfloor = getFloor(bidRequest); + + if (!bidfloor) { + delete imp.bidfloor; + } else { + imp.bidfloor = bidfloor; + } + + return imp; + }, + + bidResponse(buildBidResponse, bid, context) { + const bidResponse = buildBidResponse(bid, context); + bidResponse.currency = 'USD'; + return bidResponse; + } +}); function isBidRequestValid(bid) { const adUnitSizes = getAdUnitSizes(bid); - const isValidSizes = Boolean(adUnitSizes) && adUnitSizes.length > 0; - const isValidAdUnitId = !!bid.params.adUnitId; - const isValidAssetKey = !!bid.params.assetKey; + const isValidSize = (Boolean(adUnitSizes) && adUnitSizes.length > 0); + const hasAssetKeyAndAdUnitId = !!deepAccess(bid, 'params.adUnitId') && !!deepAccess(bid, 'params.assetKey'); + const hasPublisherIdAndAdUnitCode = !!deepAccess(bid, 'ortb2.site.publisher.id') && !!bid.adUnitCode; - return (isValidSizes && isValidAdUnitId && isValidAssetKey); + return isValidSize && (hasAssetKeyAndAdUnitId || hasPublisherIdAndAdUnitCode); } function getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent, gppConsent) { @@ -80,126 +111,19 @@ function getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent, gpp return []; } -function buildRequests(validBidRequests, bidderRequest) { - const openRtbBidRequestBanner = { - id: bidderRequest.bidderRequestId, - tmax: Math.min(DEFAULT_TIMEOUT, bidderRequest.timeout), - at: 1, - regs: { - ext: { - gdpr: bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies ? 1 : 0, - }, - }, - site: { - domain: location.hostname, - page: location.href - }, - user: { - ext: { - consent: '' - } - }, - imp: [], - ext: { - adapterversion: ADAPTER_VERSION, - prebidversion: '$prebid.version$' - }, - device: { - w: getClientWidth(), - h: getClientHeight(), - pxratio: window.devicePixelRatio - } - }; - - if (bidderRequest.gdprConsent && bidderRequest.gdprConsent.consentString) { - openRtbBidRequestBanner.user.ext.consent = bidderRequest.gdprConsent.consentString - } - if (bidderRequest.gppConsent && bidderRequest.gppConsent.gppString) { - openRtbBidRequestBanner.regs.ext.gpp = bidderRequest.gppConsent.gppString - } - if (bidderRequest.gppConsent && bidderRequest.gppConsent.applicableSections) { - openRtbBidRequestBanner.regs.ext.gpp_sid = bidderRequest.gppConsent.applicableSections - } - - validBidRequests.forEach((bidRequest) => { - const sizes = getAdUnitSizes(bidRequest) - .map(size => ({ w: size[0], h: size[1] })); - - if (bidRequest.mediaTypes && - bidRequest.mediaTypes.hasOwnProperty('banner')) { - openRtbBidRequestBanner.site.id = bidRequest.params.assetKey; - const floor = getFloor(bidRequest); - - if (bidRequest.userId) { - openRtbBidRequestBanner.user.ext.uids = bidRequest.userId - } - if (bidRequest.userIdAsEids) { - openRtbBidRequestBanner.user.ext.eids = bidRequest.userIdAsEids - } - - const gpid = deepAccess(bidRequest, 'ortb2Imp.ext.gpid'); - - openRtbBidRequestBanner.imp.push({ - id: bidRequest.bidId, - tagid: bidRequest.params.adUnitId, - ...(floor && {bidfloor: floor}), - banner: { - format: sizes - }, - ext: { - ...bidRequest.params, - ...(gpid && {gpid}), - timeSpentOnPage: document.timeline && document.timeline.currentTime ? document.timeline.currentTime : 0 - } - }); - } - }); +function buildRequests(bidRequests, bidderRequest) { + const data = converter.toORTB({bidRequests, bidderRequest}); return { method: 'POST', url: BID_HOST, - data: openRtbBidRequestBanner, + data, options: {contentType: 'application/json'}, }; } -function interpretResponse(openRtbBidResponse) { - if (!openRtbBidResponse || - !openRtbBidResponse.body || - typeof openRtbBidResponse.body != 'object' || - Object.keys(openRtbBidResponse.body).length === 0) { - logWarn('no response or body is malformed'); - return []; - } - - const bidResponses = []; - - openRtbBidResponse.body.seatbid.forEach((seatbid) => { - seatbid.bid.forEach((bid) => { - let bidResponse = { - requestId: bid.impid, - cpm: bid.price, - currency: 'USD', - width: bid.w, - height: bid.h, - creativeId: bid.id, - netRevenue: true, - ttl: 60, - ext: bid.ext, - meta: { - advertiserDomains: bid.adomain - }, - nurl: bid.nurl, - adapterVersion: ADAPTER_VERSION, - prebidVersion: '$prebid.version$' - }; - - bidResponse.ad = bid.adm; - - bidResponses.push(bidResponse); - }); - }); - return bidResponses; +function interpretResponse(response, request) { + return converter.fromORTB({response: response.body, request: request.data}).bids; } function getFloor(bid) { @@ -211,6 +135,7 @@ function getFloor(bid) { mediaType: 'banner', size: '*' }); + return (isPlainObject(floorResult) && floorResult.currency === 'USD') ? floorResult.floor : 0; } diff --git a/modules/omsBidAdapter.js b/modules/omsBidAdapter.js index 901b9a138d5..de03f65781e 100644 --- a/modules/omsBidAdapter.js +++ b/modules/omsBidAdapter.js @@ -13,7 +13,7 @@ import { formatQS, } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {BANNER} from '../src/mediaTypes.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; import {ajax} from '../src/ajax.js'; import {percentInView} from '../libraries/percentInView/percentInView.js'; import {getUserSyncParams} from '../libraries/userSyncUtils/userSyncUtils.js'; @@ -27,7 +27,7 @@ export const spec = { code: BIDDER_CODE, aliases: ['brightcom', 'bcmssp'], gvlid: 883, - supportedMediaTypes: [BANNER], + supportedMediaTypes: [BANNER, VIDEO], isBidRequestValid, buildRequests, interpretResponse, @@ -39,7 +39,7 @@ export const spec = { function buildRequests(bidReqs, bidderRequest) { try { const impressions = bidReqs.map(bid => { - let bidSizes = bid?.mediaTypes?.banner?.sizes || bid.sizes; + let bidSizes = bid?.mediaTypes?.banner?.sizes || bid?.mediaTypes?.video?.playerSize || bid.sizes; bidSizes = ((isArray(bidSizes) && isArray(bidSizes[0])) ? bidSizes : [bidSizes]); bidSizes = bidSizes.filter(size => isArray(size)); const processedSizes = bidSizes.map(size => ({w: parseInt(size[0], 10), h: parseInt(size[1], 10)})); @@ -52,18 +52,25 @@ function buildRequests(bidReqs, bidderRequest) { const imp = { id: bid.bidId, - banner: { - format: processedSizes, - ext: { - viewability: viewabilityAmountRounded, - } - }, ext: { ...gpidData }, tagid: String(bid.adUnitCode) }; + if (bid?.mediaTypes?.video) { + imp.video = { + ...bid.mediaTypes.video, + } + } else { + imp.banner = { + format: processedSizes, + ext: { + viewability: viewabilityAmountRounded, + } + } + } + const bidFloor = _getBidFloor(bid); if (bidFloor) { @@ -158,7 +165,7 @@ function interpretResponse(serverResponse) { try { if (id && seatbid && seatbid.length > 0 && seatbid[0].bid && seatbid[0].bid.length > 0) { response = seatbid[0].bid.map(bid => { - return { + const bidResponse = { requestId: bid.impid, cpm: parseFloat(bid.price), width: parseInt(bid.w), @@ -166,13 +173,20 @@ function interpretResponse(serverResponse) { creativeId: bid.crid || bid.id, currency: 'USD', netRevenue: true, - mediaType: BANNER, ad: _getAdMarkup(bid), ttl: 300, meta: { advertiserDomains: bid?.adomain || [] } }; + + if (bid.mtype === 2) { + bidResponse.mediaType = VIDEO; + } else { + bidResponse.mediaType = BANNER; + } + + return bidResponse; }); } } catch (e) { diff --git a/modules/omsBidAdapter.md b/modules/omsBidAdapter.md index 0a6b9cac82c..506ba5fdbd5 100644 --- a/modules/omsBidAdapter.md +++ b/modules/omsBidAdapter.md @@ -41,6 +41,21 @@ var adUnits = [ publisherId: 2141020 } }] + }, + { + code: 'video-instream', + mediaTypes: { + video: { + context: 'instream', + playerSize: [640, 480] + } + }, + bids: [{ + bidder: 'oms', + params: { + publisherId: 2141020 + } + }] } ] ``` diff --git a/modules/openwebBidAdapter.js b/modules/openwebBidAdapter.js index 60364f41d3c..0dff314ce17 100644 --- a/modules/openwebBidAdapter.js +++ b/modules/openwebBidAdapter.js @@ -1,35 +1,19 @@ -import { - logWarn, - logInfo, - isArray, - deepAccess, - triggerPixel -} from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER, VIDEO } from '../src/mediaTypes.js'; -import { - getEndpoint, - generateBidsParams, - generateGeneralParams, - buildBidResponse, -} from '../libraries/riseUtils/index.js'; +import {logWarn} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {makeBaseSpec} from '../libraries/riseUtils/index.js'; -const SUPPORTED_AD_TYPES = [BANNER, VIDEO]; const BIDDER_CODE = 'openweb'; -const ADAPTER_VERSION = '6.0.0'; -const TTL = 360; -const DEFAULT_CURRENCY = 'USD'; const BASE_URL = 'https://hb.openwebmp.com/'; +const GVLID = 280; const MODES = { PRODUCTION: 'hb-multi', TEST: 'hb-multi-test' }; export const spec = { + ...makeBaseSpec(BASE_URL, MODES), code: BIDDER_CODE, - gvlid: 280, - version: ADAPTER_VERSION, - supportedMediaTypes: SUPPORTED_AD_TYPES, + gvlid: GVLID, isBidRequestValid: function (bidRequest) { if (!bidRequest.params) { logWarn('no params have been set to OpenWeb adapter'); @@ -47,65 +31,6 @@ export const spec = { } return true; - }, - buildRequests: function (validBidRequests, bidderRequest) { - const combinedRequestsObject = {}; - - // use data from the first bid, to create the general params for all bids - const generalObject = validBidRequests[0]; - const testMode = generalObject.params.testMode; - - combinedRequestsObject.params = generateGeneralParams(generalObject, bidderRequest); - combinedRequestsObject.bids = generateBidsParams(validBidRequests, bidderRequest); - - return { - method: 'POST', - url: getEndpoint(testMode, BASE_URL, MODES), - data: combinedRequestsObject - } - }, - interpretResponse: function ({body}) { - const bidResponses = []; - - if (body && body.bids && body.bids.length) { - body.bids.forEach(adUnit => { - const bidResponse = buildBidResponse(adUnit, DEFAULT_CURRENCY, TTL, VIDEO, BANNER); - bidResponses.push(bidResponse); - }); - } - - return bidResponses; - }, - getUserSyncs: function (syncOptions, serverResponses) { - const syncs = []; - for (const response of serverResponses) { - if (syncOptions.iframeEnabled && deepAccess(response, 'body.params.userSyncURL')) { - syncs.push({ - type: 'iframe', - url: deepAccess(response, 'body.params.userSyncURL') - }); - } - if (syncOptions.pixelEnabled && isArray(deepAccess(response, 'body.params.userSyncPixels'))) { - const pixels = response.body.params.userSyncPixels.map(pixel => { - return { - type: 'image', - url: pixel - } - }); - syncs.push(...pixels); - } - } - return syncs; - }, - onBidWon: function (bid) { - if (bid == null) { - return; - } - - logInfo('onBidWon:', bid); - if (bid.hasOwnProperty('nurl') && bid.nurl.length > 0) { - triggerPixel(bid.nurl); - } } }; diff --git a/modules/openwebBidAdapter.md b/modules/openwebBidAdapter.md index 5450182265c..c5bc10c3c12 100644 --- a/modules/openwebBidAdapter.md +++ b/modules/openwebBidAdapter.md @@ -13,7 +13,7 @@ Module that connects to OpenWeb's demand sources. The OpenWeb adapter requires setup and approval from OpenWeb. Please reach out to monetization@openweb.com to create an OpenWeb account. -The adapter supports Video and Display demand. +The adapter supports Video(instream), Banner, Native and multi-format bid requests. # Bid Parameters ## Video diff --git a/modules/openxBidAdapter.js b/modules/openxBidAdapter.js index 19da19e661f..292df1d4152 100644 --- a/modules/openxBidAdapter.js +++ b/modules/openxBidAdapter.js @@ -2,8 +2,9 @@ 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 {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; import {ortbConverter} from '../libraries/ortbConverter/converter.js'; +import {ORTB_MTYPES} from '../libraries/ortbConverter/processors/mediaType.js'; const bidderConfig = 'hb_pb_ortb'; const bidderVersion = '2.0'; @@ -13,7 +14,7 @@ export const DEFAULT_PH = '2d1251ae-7f3a-47cf-bd2a-2f288854a0ba'; export const spec = { code: 'openx', gvlid: 69, - supportedMediaTypes: [BANNER, VIDEO], + supportedMediaTypes: [BANNER, VIDEO, NATIVE], isBidRequestValid, buildRequests, interpretResponse, @@ -25,7 +26,12 @@ registerBidder(spec); const converter = ortbConverter({ context: { netRevenue: true, - ttl: 300 + ttl: 300, + nativeRequest: { + eventtrackers: [ + {event: 1, methods: [1, 2]}, + ] + } }, imp(buildImp, bidRequest, context) { const imp = buildImp(bidRequest, context); @@ -74,6 +80,18 @@ const converter = ortbConverter({ return req; }, bidResponse(buildBidResponse, bid, context) { + if (!context.mediaType && !bid.mtype) { + let mediaType = BANNER; // default media type + const vastKeywords = ['VAST ', 'vast ', 'videoad', 'VAST_VERSION', 'dc_vast', 'video ']; + if (bid.adm && bid.adm.startsWith('{') && bid.adm.includes('"assets"')) { + mediaType = NATIVE; + } else if (bid.vastXml || bid.vastUrl || (bid.adm && vastKeywords.some(v => bid.adm.includes(v)))) { + mediaType = VIDEO; + } + bid.mediaType = mediaType; + bid.mtype = Object.keys(ORTB_MTYPES).find(key => ORTB_MTYPES[key] === mediaType); + } + const bidResponse = buildBidResponse(bid, context); if (bid.ext) { bidResponse.meta.networkId = bid.ext.dsp_id; @@ -158,8 +176,11 @@ function isBidRequestValid(bidRequest) { 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)] : []; + let bannerAndNativeBids = bids.filter(bid => isBannerBid(bid) || isNativeBid(bid)) + // In case of multi-format bids remove `video` from mediaTypes as for video a separate bid request is built + .map(bid => ({...bid, mediaTypes: {...bid.mediaTypes, video: undefined}})); + + let requests = bannerAndNativeBids.length ? [createRequest(bannerAndNativeBids, bidderRequest, null)] : []; videoBids.forEach(bid => { requests.push(createRequest([bid], bidderRequest, VIDEO)); }); @@ -178,8 +199,13 @@ function isVideoBid(bid) { return utils.deepAccess(bid, 'mediaTypes.video'); } +function isNativeBid(bid) { + return utils.deepAccess(bid, 'mediaTypes.native'); +} + function isBannerBid(bid) { - return utils.deepAccess(bid, 'mediaTypes.banner') || !isVideoBid(bid); + const isNotVideoOrNativeBid = !isVideoBid(bid) && !isNativeBid(bid) + return utils.deepAccess(bid, 'mediaTypes.banner') || isNotVideoOrNativeBid; } function interpretResponse(resp, req) { diff --git a/modules/openxBidAdapter.md b/modules/openxBidAdapter.md index a39aa1580cd..61b6426d113 100644 --- a/modules/openxBidAdapter.md +++ b/modules/openxBidAdapter.md @@ -34,6 +34,16 @@ Please note you should only include either openxBidAdapter or openxOrtbBidAdapte | `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']}` +## Native + +| 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 # Example ```javascript @@ -84,7 +94,42 @@ var adUnits = [ mimes: ['video/x-ms-wmv, video/mp4'] // mediaTypes.video preferred } } - }]p + }] + }, + { + code: 'native1', + mediaTypes: { + native: { + ortb: { + ver: '1.2', + assets: [ + { + required: 1, + img: { + type: 1, + hmin: 50 + }, + }, { + required: 1, + title: { + len: 80 + } + } + ] + } + } + }, + bids: [{ + bidder: 'openx', + params: { + unit: '1611023124', + delDomain: 'PUBLISHER-d.openx.net', + customParams: { + key1: 'v1', + key2: ['v2', 'v3'] + } + } + }] } ]; ``` diff --git a/modules/permutiveRtdProvider.md b/modules/permutiveRtdProvider.md index 9399dffab93..3cf3ed2b367 100644 --- a/modules/permutiveRtdProvider.md +++ b/modules/permutiveRtdProvider.md @@ -50,14 +50,15 @@ as well as enabling settings for specific use cases mentioned above (e.g. acbidd #### Context -Permutive is not listed as a TCF vendor as all data collection is on behalf of the publisher and based on consent the publisher has received from the user. -Rather than through the TCF framework, this consent is provided to Permutive when the user gives the relevant permissions on the publisher website which allow the Permutive SDK to run. -This means that if GDPR enforcement is configured _and_ the user consent isn’t given for Permutive to fire, no cohorts will populate. -As Prebid utilizes TCF vendor consent, for the Permutive RTD module to load, Permutive needs to be labeled within the Vendor Exceptions +While Permutive is listed as a TCF vendor (ID: 361), Permutive does not obtain consent directly from the TCF. As we act as a processor on behalf of our publishers consent is given to the Permutive SDK by the publisher, not by the [GDPR Consent Management Module](https://prebid-docs.atre.net/dev-docs/modules/consentManagement.html). + +This means that if GDPR enforcement is configured within the Permutive SDK _and_ the user consent isn’t given for Permutive to fire, no cohorts will populate. + +If you are also using the [TCF Control Module](https://docs.prebid.org/dev-docs/modules/tcfControl.html), in order to prevent Permutive from being blocked, it needs to be labeled within the Vendor Exceptions. #### Instructions -1. Publisher enables rules within Prebid GDPR module +1. Publisher enables rules within Prebid.js configuration. 2. Label Permutive as an exception, as shown below. ```javascript [ diff --git a/modules/prebidServerBidAdapter/ortbConverter.js b/modules/prebidServerBidAdapter/ortbConverter.js index 51a01e04fdd..99fbcf3d2bb 100644 --- a/modules/prebidServerBidAdapter/ortbConverter.js +++ b/modules/prebidServerBidAdapter/ortbConverter.js @@ -1,5 +1,5 @@ import {ortbConverter} from '../../libraries/ortbConverter/converter.js'; -import {deepSetValue, getBidRequest, logError, logWarn, mergeDeep, timestamp} from '../../src/utils.js'; +import {deepClone, deepSetValue, getBidRequest, logError, logWarn, mergeDeep, timestamp} from '../../src/utils.js'; import {config} from '../../src/config.js'; import {S2S, STATUS} from '../../src/constants.js'; import {createBid} from '../../src/bidfactory.js'; @@ -17,12 +17,25 @@ import {currencyCompare} from '../../libraries/currencyUtils/currency.js'; import {minimum} from '../../src/utils/reducers.js'; import {s2sDefaultConfig} from './index.js'; import {premergeFpd} from './bidderConfig.js'; +import {ALL_MEDIATYPES, BANNER} from '../../src/mediaTypes.js'; const DEFAULT_S2S_TTL = 60; const DEFAULT_S2S_CURRENCY = 'USD'; const DEFAULT_S2S_NETREVENUE = true; const BIDDER_SPECIFIC_REQUEST_PROPS = new Set(['bidderCode', 'bidderRequestId', 'uniquePbsTid', 'bids', 'timeout']); +const getMinimumFloor = (() => { + const getMin = minimum(currencyCompare(floor => [floor.bidfloor, floor.bidfloorcur])); + return function(candidates) { + let min; + for (const candidate of candidates) { + if (candidate?.bidfloorcur == null || candidate?.bidfloor == null) return null; + min = min == null ? candidate : getMin(min, candidate); + } + return min; + } +})(); + const PBS_CONVERTER = ortbConverter({ processors: pbsExtensions, context: { @@ -126,24 +139,39 @@ const PBS_CONVERTER = ortbConverter({ } } }, + // for bid floors, we pass each bidRequest associated with this imp through normal bidfloor/extBidfloor processing, + // and aggregate all of them into a single, minimum floor to put in the request bidfloor(orig, imp, proxyBidRequest, context) { - // for bid floors, we pass each bidRequest associated with this imp through normal bidfloor processing, - // and aggregate all of them into a single, minimum floor to put in the request - const getMin = minimum(currencyCompare(floor => [floor.bidfloor, floor.bidfloorcur])); - let min; - for (const req of context.actualBidRequests.values()) { - const floor = {}; - orig(floor, req, context); - // if any bid does not have a valid floor, do not attempt to send any to PBS - if (floor.bidfloorcur == null || floor.bidfloor == null) { - min = null; - break; + const min = getMinimumFloor((function * () { + for (const req of context.actualBidRequests.values()) { + const floor = {}; + orig(floor, req, context); + yield floor; } - min = min == null ? floor : getMin(min, floor); - } + })()) if (min != null) { Object.assign(imp, min); } + }, + extBidfloor(orig, imp, proxyBidRequest, context) { + function setExtFloor(target, minFloor) { + if (minFloor != null) { + deepSetValue(target, 'ext.bidfloor', minFloor.bidfloor); + deepSetValue(target, 'ext.bidfloorcur', minFloor.bidfloorcur); + } + } + const imps = Array.from(context.actualBidRequests.values()) + .map(request => { + const requestImp = deepClone(imp); + orig(requestImp, request, context); + return requestImp; + }); + Object.values(ALL_MEDIATYPES).forEach(mediaType => { + setExtFloor(imp[mediaType], getMinimumFloor(imps.map(imp => imp[mediaType]?.ext))) + }); + (imp[BANNER]?.format || []).forEach((format, i) => { + setExtFloor(format, getMinimumFloor(imps.map(imp => imp[BANNER].format[i]?.ext))) + }) } }, [REQUEST]: { diff --git a/modules/priceFloors.js b/modules/priceFloors.js index d14a82af360..ab499ce7698 100644 --- a/modules/priceFloors.js +++ b/modules/priceFloors.js @@ -31,6 +31,7 @@ import {adjustCpm} from '../src/utils/cpm.js'; import {getGptSlotInfoForAdUnitCode} from '../libraries/gptUtils/gptUtils.js'; import {convertCurrency} from '../libraries/currencyUtils/currency.js'; import { timeoutQueue } from '../libraries/timeoutQueue/timeoutQueue.js'; +import {ALL_MEDIATYPES, BANNER} from '../src/mediaTypes.js'; export const FLOOR_SKIPPED_REASON = { NOT_FOUND: 'not_found', @@ -264,7 +265,10 @@ export function getFloor(requestParams = {currency: 'USD', mediaType: '*', size: // 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); + const definedParams = Object.fromEntries( + Object.entries(requestParams).filter(([key, val]) => val !== '*' && ['mediaType', 'size'].includes(key)) + ); + floorInfo.matchingFloor = inverseFunction(floorInfo.matchingFloor, bidRequest, definedParams); } else { let cpmAdjustment = getBiddersCpmAdjustment(floorInfo.matchingFloor, null, bidRequest); floorInfo.matchingFloor = cpmAdjustment ? calculateAdjustedFloor(floorInfo.matchingFloor, cpmAdjustment) : floorInfo.matchingFloor; @@ -801,30 +805,70 @@ export const addBidResponseHook = timedBidResponseHook('priceFloors', function a config.getConfig('floors', config => handleSetFloorsConfig(config.floors)); -/** - * Sets bidfloor and bidfloorcur for ORTB imp objects - */ -export function setOrtbImpBidFloor(imp, bidRequest, context) { +function tryGetFloor(bidRequest, {currency = config.getConfig('currency.adServerCurrency') || 'USD', mediaType = '*', size = '*'}, fn) { if (typeof bidRequest.getFloor === 'function') { - let currency, floor; + let floor; try { - ({currency, floor} = bidRequest.getFloor({ - currency: context.currency || config.getConfig('currency.adServerCurrency') || 'USD', - mediaType: context.mediaType || '*', - size: '*' - }) || {}); + floor = bidRequest.getFloor({ + currency, + mediaType, + size + }) || {}; } catch (e) { logWarn('Cannot compute floor for bid', bidRequest); return; } - floor = parseFloat(floor); - if (currency != null && floor != null && !isNaN(floor)) { - Object.assign(imp, { - bidfloor: floor, - bidfloorcur: currency - }); + floor.floor = parseFloat(floor.floor); + if (floor.currency != null && floor.floor && !isNaN(floor.floor)) { + fn(floor.floor, floor.currency); + } + } +} + +/** + * Sets bidfloor and bidfloorcur for ORTB imp objects + */ +export function setOrtbImpBidFloor(imp, bidRequest, context) { + tryGetFloor(bidRequest, { + currency: context.currency, + mediaType: context.mediaType || '*', + size: '*' + }, (bidfloor, bidfloorcur) => { + Object.assign(imp, { + bidfloor, + bidfloorcur + }); + }) +} + +/** + * Set per-mediatype and per-format bidfloor + */ +export function setGranularBidfloors(imp, bidRequest, context) { + function setIfDifferent(bidfloor, bidfloorcur) { + if (bidfloor !== imp.bidfloor || bidfloorcur !== imp.bidfloorcur) { + deepSetValue(this, 'ext.bidfloor', bidfloor); + deepSetValue(this, 'ext.bidfloorcur', bidfloorcur); } } + + Object.values(ALL_MEDIATYPES) + .filter(mediaType => imp[mediaType] != null) + .forEach(mediaType => { + tryGetFloor(bidRequest, { + currency: imp.bidfloorcur || context?.currency, + mediaType + }, setIfDifferent.bind(imp[mediaType])) + }); + (imp[BANNER]?.format || []) + .filter(({w, h}) => w != null && h != null) + .forEach(format => { + tryGetFloor(bidRequest, { + currency: imp.bidfloorcur || context?.currency, + mediaType: BANNER, + size: [format.w, format.h] + }, setIfDifferent.bind(format)) + }) } export function setImpExtPrebidFloors(imp, bidRequest, context) { @@ -867,5 +911,7 @@ export function setOrtbExtPrebidFloors(ortbRequest, bidderRequest, context) { } registerOrtbProcessor({type: IMP, name: 'bidfloor', fn: setOrtbImpBidFloor}); +// granular floors should be set after both "normal" bidfloors and mediaypes +registerOrtbProcessor({type: IMP, name: 'extBidfloor', fn: setGranularBidfloors, priority: -10}) registerOrtbProcessor({type: IMP, name: 'extPrebidFloors', fn: setImpExtPrebidFloors, dialects: [PBS], priority: -1}); registerOrtbProcessor({type: REQUEST, name: 'extPrebidFloors', fn: setOrtbExtPrebidFloors, dialects: [PBS]}); diff --git a/modules/pubmaticAnalyticsAdapter.js b/modules/pubmaticAnalyticsAdapter.js index 2fec213a612..8f9dead2cba 100755 --- a/modules/pubmaticAnalyticsAdapter.js +++ b/modules/pubmaticAnalyticsAdapter.js @@ -281,6 +281,31 @@ function isOWPubmaticBid(adapterName) { }) } +function getFloorsCommonField (floorData) { + if (!floorData) return; + const { location, fetchStatus, floorProvider, modelVersion } = floorData; + return { + ffs: { + [FLOOR_VALUES.SUCCESS]: 1, + [FLOOR_VALUES.ERROR]: 2, + [FLOOR_VALUES.TIMEOUT]: 4, + undefined: 0 + }[fetchStatus], + fsrc: { + [FLOOR_VALUES.FETCH]: 2, + [FLOOR_VALUES.NO_DATA]: 0, + [FLOOR_VALUES.AD_UNIT]: 1, + [FLOOR_VALUES.SET_CONFIG]: 1 + }[location], + fp: floorProvider, + mv: modelVersion + } +} + +function getFloorType(floorResponseData) { + return floorResponseData ? (floorResponseData.enforcements.enforceJS == false ? 0 : 1) : undefined; +} + function gatherPartnerBidsForAdUnitForLogger(adUnit, adUnitId, highestBid, e) { highestBid = (highestBid && highestBid.length > 0) ? highestBid[0] : null; return Object.keys(adUnit.bids).reduce(function(partnerBids, bidId) { @@ -325,6 +350,7 @@ function gatherPartnerBidsForAdUnitForLogger(adUnit, adUnitId, highestBid, e) { 'ocpm': bid.bidResponse ? (bid.bidResponse.originalCpm || 0) : 0, 'ocry': bid.bidResponse ? (bid.bidResponse.originalCurrency || CURRENCY_USD) : CURRENCY_USD, 'frv': bid.bidResponse ? bid.bidResponse.floorData?.floorRuleValue : undefined, + 'fv': bid.bidResponse ? bid.bidResponse.floorData?.floorValue : undefined, 'md': bid.bidResponse ? getMetadata(bid.bidResponse.meta) : undefined, 'pb': pg || undefined }); @@ -403,9 +429,22 @@ function executeBidsLoggerCall(e, highestCpmBids) { outputObj['tgid'] = getTgId(); outputObj['pbv'] = '$prebid.version$' || '-1'; - if (floorData && floorFetchStatus) { - outputObj['fmv'] = floorData.floorRequestData ? floorData.floorRequestData.modelVersion || undefined : undefined; - outputObj['ft'] = floorData.floorResponseData ? (floorData.floorResponseData.enforcements.enforceJS == false ? 0 : 1) : undefined; + if (floorData) { + const floorRootValues = getFloorsCommonField(floorData?.floorRequestData); + if (floorRootValues) { + const { ffs, fsrc, fp, mv } = floorRootValues; + if (floorData?.floorRequestData) { + outputObj['ffs'] = ffs; + outputObj['fsrc'] = fsrc; + outputObj['fp'] = fp; + } + if (floorFetchStatus) { + outputObj['fmv'] = mv || undefined; + } + } + if (floorFetchStatus) { + outputObj['ft'] = getFloorType(floorData?.floorResponseData); + } } outputObj.s = Object.keys(auctionCache.adUnitCodes).reduce(function(slotsArray, adUnitId) { @@ -421,22 +460,6 @@ function executeBidsLoggerCall(e, highestCpmBids) { 'fskp': floorData && floorFetchStatus ? (floorData.floorRequestData ? (floorData.floorRequestData.skipped == false ? 0 : 1) : undefined) : undefined, 'sid': generateUUID() }; - if (floorData?.floorRequestData) { - const { location, fetchStatus, floorProvider } = floorData?.floorRequestData; - slotObject.ffs = { - [FLOOR_VALUES.SUCCESS]: 1, - [FLOOR_VALUES.ERROR]: 2, - [FLOOR_VALUES.TIMEOUT]: 4, - undefined: 0 - }[fetchStatus]; - slotObject.fsrc = { - [FLOOR_VALUES.FETCH]: 2, - [FLOOR_VALUES.NO_DATA]: 2, - [FLOOR_VALUES.AD_UNIT]: 1, - [FLOOR_VALUES.SET_CONFIG]: 1 - }[location]; - slotObject.fp = floorProvider; - } slotsArray.push(slotObject); return slotsArray; }, []); @@ -509,6 +532,25 @@ function executeBidWonLoggerCall(auctionId, adUnitId) { pixelURL += '&orig=' + enc(getDomainFromUrl(referrer)); pixelURL += '&ss=' + enc(isS2SBidder(winningBid.bidder)); (fskp != undefined) && (pixelURL += '&fskp=' + enc(fskp)); + if (floorData) { + const floorRootValues = getFloorsCommonField(floorData.floorRequestData); + const { fsrc, fp, mv } = floorRootValues || {}; + const params = { fsrc, fp, fmv: mv }; + Object.entries(params).forEach(([key, value]) => { + if (value !== undefined) { + pixelURL += `&${key}=${enc(value)}`; + } + }); + const floorType = getFloorType(floorData.floorResponseData); + if (floorType !== undefined) { + pixelURL += '&ft=' + enc(floorType); + } + const floorRuleValue = winningBid?.bidResponse?.floorData?.floorRuleValue; + (floorRuleValue !== undefined) && (pixelURL += '&frv=' + enc(floorRuleValue)); + + const floorValue = winningBid?.bidResponse?.floorData?.floorValue; + (floorValue !== undefined) && (pixelURL += '&fv=' + enc(floorValue)); + } pixelURL += '&af=' + enc(winningBid.bidResponse ? (winningBid.bidResponse.mediaType || undefined) : undefined); ajax( diff --git a/modules/richaudienceBidAdapter.js b/modules/richaudienceBidAdapter.js index 135d2e94b65..5156fb052ef 100644 --- a/modules/richaudienceBidAdapter.js +++ b/modules/richaudienceBidAdapter.js @@ -1,4 +1,4 @@ -import {deepAccess, isStr, triggerPixel} from '../src/utils.js'; +import {deepAccess, triggerPixel} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {config} from '../src/config.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; @@ -48,7 +48,7 @@ export const spec = { numIframes: (typeof bidderRequest.refererInfo.numIframes != 'undefined' ? bidderRequest.refererInfo.numIframes : null), transactionId: bid.ortb2Imp?.ext?.tid, timeout: bidderRequest.timeout || 600, - user: raiSetEids(bid), + eids: deepAccess(bid, 'userIdAsEids') ? bid.userIdAsEids : [], demand: raiGetDemandType(bid), videoData: raiGetVideoInfo(bid), scr_rsl: raiGetResolution(), @@ -266,30 +266,6 @@ function raiGetVideoInfo(bid) { return videoData; } -function raiSetEids(bid) { - let eids = []; - - if (bid && bid.userId) { - raiSetUserId(bid, eids, 'id5-sync.com', deepAccess(bid, `userId.id5id.uid`)); - raiSetUserId(bid, eids, 'pubcommon', deepAccess(bid, `userId.pubcid`)); - raiSetUserId(bid, eids, 'criteo.com', deepAccess(bid, `userId.criteoId`)); - raiSetUserId(bid, eids, 'liveramp.com', deepAccess(bid, `userId.idl_env`)); - raiSetUserId(bid, eids, 'liveintent.com', deepAccess(bid, `userId.lipb.lipbid`)); - raiSetUserId(bid, eids, 'adserver.org', deepAccess(bid, `userId.tdid`)); - } - - return eids; -} - -function raiSetUserId(bid, eids, source, value) { - if (isStr(value)) { - eids.push({ - userId: value, - source: source - }); - } -} - function renderer(bid) { bid.renderer.push(() => { renderAd(bid) diff --git a/modules/riseBidAdapter.js b/modules/riseBidAdapter.js index 236c048982a..a6970e959ce 100644 --- a/modules/riseBidAdapter.js +++ b/modules/riseBidAdapter.js @@ -1,40 +1,19 @@ +import {logWarn} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {makeBaseSpec} from '../libraries/riseUtils/index.js'; import { - logWarn, - logInfo, - isArray, - deepAccess, - triggerPixel, -} from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER, VIDEO } from '../src/mediaTypes.js'; -import { - getEndpoint, - generateBidsParams, - generateGeneralParams, - buildBidResponse, -} from '../libraries/riseUtils/index.js'; - -const SUPPORTED_AD_TYPES = [BANNER, VIDEO]; -const BIDDER_CODE = 'rise'; -const ADAPTER_VERSION = '6.0.0'; -const TTL = 360; -const DEFAULT_CURRENCY = 'USD'; -const DEFAULT_GVLID = 1043; -const BASE_URL = 'https://hb.yellowblue.io/'; -const MODES = { - PRODUCTION: 'hb-multi', - TEST: 'hb-multi-test' -}; + ALIASES, + BASE_URL, + BIDDER_CODE, + DEFAULT_GVLID, + MODES, +} from '../libraries/riseUtils/constants.js'; export const spec = { + ...makeBaseSpec(BASE_URL, MODES), code: BIDDER_CODE, - aliases: [ - { code: 'risexchange', gvlid: DEFAULT_GVLID }, - { code: 'openwebxchange', gvlid: 280 } - ], + aliases: ALIASES, gvlid: DEFAULT_GVLID, - version: ADAPTER_VERSION, - supportedMediaTypes: SUPPORTED_AD_TYPES, isBidRequestValid: function (bidRequest) { if (!bidRequest.params) { logWarn('no params have been set to Rise adapter'); @@ -47,66 +26,6 @@ export const spec = { } return true; - }, - buildRequests: function (validBidRequests, bidderRequest) { - const combinedRequestsObject = {}; - - // use data from the first bid, to create the general params for all bids - const generalObject = validBidRequests[0]; - const testMode = generalObject.params.testMode; - const rtbDomain = generalObject.params.rtbDomain || BASE_URL; - - combinedRequestsObject.params = generateGeneralParams(generalObject, bidderRequest); - combinedRequestsObject.bids = generateBidsParams(validBidRequests, bidderRequest); - - return { - method: 'POST', - url: getEndpoint(testMode, rtbDomain, MODES), - data: combinedRequestsObject - } - }, - interpretResponse: function ({ body }) { - const bidResponses = []; - - if (body.bids) { - body.bids.forEach(adUnit => { - const bidResponse = buildBidResponse(adUnit, DEFAULT_CURRENCY, TTL, VIDEO, BANNER); - bidResponses.push(bidResponse); - }); - } - - return bidResponses; - }, - getUserSyncs: function (syncOptions, serverResponses) { - const syncs = []; - for (const response of serverResponses) { - if (syncOptions.iframeEnabled && deepAccess(response, 'body.params.userSyncURL')) { - syncs.push({ - type: 'iframe', - url: deepAccess(response, 'body.params.userSyncURL') - }); - } - if (syncOptions.pixelEnabled && isArray(deepAccess(response, 'body.params.userSyncPixels'))) { - const pixels = response.body.params.userSyncPixels.map(pixel => { - return { - type: 'image', - url: pixel - } - }); - syncs.push(...pixels); - } - } - return syncs; - }, - onBidWon: function (bid) { - if (bid == null) { - return; - } - - logInfo('onBidWon:', bid); - if (bid.hasOwnProperty('nurl') && bid.nurl.length > 0) { - triggerPixel(bid.nurl); - } } }; diff --git a/modules/riseBidAdapter.md b/modules/riseBidAdapter.md index 94d36a08510..2d355cd0cb6 100644 --- a/modules/riseBidAdapter.md +++ b/modules/riseBidAdapter.md @@ -13,7 +13,7 @@ Module that connects to Rise's demand sources. The Rise adapter requires setup and approval from the Rise. Please reach out to prebid-rise-engage@risecodes.com to create an Rise account. -The adapter supports Video(instream). +The adapter supports Video(instream), Banner, Native and multi-format bid requests. # Bid Parameters ## Video diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index 411e194b1ee..037e19c5d51 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -566,7 +566,7 @@ export const spec = { inserter || '', matcher || '', mm || '', - uidData?.ext?.rtipartner || '' + uidData?.ext?.rtiPartner || uidData?.ext?.rtipartner || '' ].join('^'); // Return a single string formatted with '^' delimiter const eidValue = buildEidValue(uidData); // Build the EID value string diff --git a/modules/sharedIdSystem.js b/modules/sharedIdSystem.js index fa8b5e3bfdb..a96681f749c 100644 --- a/modules/sharedIdSystem.js +++ b/modules/sharedIdSystem.js @@ -183,9 +183,15 @@ export const sharedIdSystemSubmodule = { domainOverride: domainOverrideToRootDomain(storage, 'sharedId'), eids: { - 'pubcid': { - source: 'pubcid.org', - atype: 1 + 'pubcid'(values, config) { + const eid = { + source: 'pubcid.org', + uids: values.map(id => ({id, atype: 1})) + } + if (config?.params?.inserter != null) { + eid.inserter = config.params.inserter; + } + return eid; }, } }; diff --git a/modules/shinezBidAdapter.js b/modules/shinezBidAdapter.js index 89f39284bed..f88360c4c38 100644 --- a/modules/shinezBidAdapter.js +++ b/modules/shinezBidAdapter.js @@ -1,24 +1,8 @@ -import { - logWarn, - logInfo, - isArray, - deepAccess, - triggerPixel, -} from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER, VIDEO } from '../src/mediaTypes.js'; -import { - getEndpoint, - generateBidsParams, - generateGeneralParams, - buildBidResponse, -} from '../libraries/riseUtils/index.js'; +import {logWarn} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {makeBaseSpec} from '../libraries/riseUtils/index.js'; -const SUPPORTED_AD_TYPES = [BANNER, VIDEO]; const BIDDER_CODE = 'shinez'; -const ADAPTER_VERSION = '1.0.0'; -const TTL = 360; -const CURRENCY = 'USD'; const BASE_URL = 'https://hb.sweetgum.io/'; const MODES = { PRODUCTION: 'hb-sz-multi', @@ -26,9 +10,8 @@ const MODES = { }; export const spec = { + ...makeBaseSpec(BASE_URL, MODES), code: BIDDER_CODE, - version: ADAPTER_VERSION, - supportedMediaTypes: SUPPORTED_AD_TYPES, isBidRequestValid: function (bidRequest) { if (!bidRequest.params) { logWarn('no params have been set to Shinez adapter'); @@ -41,65 +24,6 @@ export const spec = { } return true; - }, - buildRequests: function (validBidRequests, bidderRequest) { - const combinedRequestsObject = {}; - - // use data from the first bid, to create the general params for all bids - const generalObject = validBidRequests[0]; - const testMode = generalObject.params.testMode; - - combinedRequestsObject.params = generateGeneralParams(generalObject, bidderRequest); - combinedRequestsObject.bids = generateBidsParams(validBidRequests, bidderRequest); - - return { - method: 'POST', - url: getEndpoint(testMode, BASE_URL, MODES), - data: combinedRequestsObject - } - }, - interpretResponse: function ({body}) { - const bidResponses = []; - - if (body.bids) { - body.bids.forEach(adUnit => { - const bidResponse = buildBidResponse(adUnit, CURRENCY, TTL, VIDEO, BANNER); - bidResponses.push(bidResponse); - }); - } - - return bidResponses; - }, - getUserSyncs: function (syncOptions, serverResponses) { - const syncs = []; - for (const response of serverResponses) { - if (syncOptions.iframeEnabled && deepAccess(response, 'body.params.userSyncURL')) { - syncs.push({ - type: 'iframe', - url: deepAccess(response, 'body.params.userSyncURL') - }); - } - if (syncOptions.pixelEnabled && isArray(deepAccess(response, 'body.params.userSyncPixels'))) { - const pixels = response.body.params.userSyncPixels.map(pixel => { - return { - type: 'image', - url: pixel - } - }); - syncs.push(...pixels); - } - } - return syncs; - }, - onBidWon: function (bid) { - if (bid == null) { - return; - } - - logInfo('onBidWon:', bid); - if (bid.hasOwnProperty('nurl') && bid.nurl.length > 0) { - triggerPixel(bid.nurl); - } } }; diff --git a/modules/shinezBidAdapter.md b/modules/shinezBidAdapter.md index f0ef7a6c218..203b6ece4ff 100644 --- a/modules/shinezBidAdapter.md +++ b/modules/shinezBidAdapter.md @@ -13,7 +13,7 @@ Module that connects to Shinez's demand sources. The Shinez adapter requires setup and approval from the Shinez. Please reach out to tech-team@shinez.io to create an Shinez account. -The adapter supports Video(instream) & Banner. +The adapter supports Video(instream), Banner, Native and multi-format bid requests. # Bid Parameters ## Video @@ -73,4 +73,4 @@ var adUnits = [{ }] } ]; -``` \ No newline at end of file +``` diff --git a/modules/smarthubBidAdapter.js b/modules/smarthubBidAdapter.js index d6600b4afd5..ddf76d64903 100644 --- a/modules/smarthubBidAdapter.js +++ b/modules/smarthubBidAdapter.js @@ -16,6 +16,8 @@ const ALIASES = [ {code: 'felixads'}, {code: 'vimayx'}, {code: 'artechnology'}, + {code: 'adinify'}, + {code: 'addigi'}, ]; const BASE_URLS = { attekmi: 'https://prebid.attekmi.com/pbjs', @@ -26,6 +28,8 @@ const BASE_URLS = { felixads: 'https://felixads-prebid.attekmi.com/pbjs', vimayx: 'https://vimayx-prebid.attekmi.com/pbjs', artechnology: 'https://artechnology-prebid.attekmi.com/pbjs', + adinify: 'https://adinify-prebid.attekmi.com/pbjs', + addigi: 'https://addigi-prebid.attekmi.com/pbjs', }; const _getUrl = (partnerName) => { diff --git a/modules/stnBidAdapter.js b/modules/stnBidAdapter.js index ba922c0fd57..62d82b8d4b2 100644 --- a/modules/stnBidAdapter.js +++ b/modules/stnBidAdapter.js @@ -1,38 +1,17 @@ -import { - logWarn, - logInfo, - isArray, - deepAccess, - triggerPixel, -} from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER, VIDEO } from '../src/mediaTypes.js'; -import { - getEndpoint, - generateBidsParams, - generateGeneralParams, - buildBidResponse, -} from '../libraries/riseUtils/index.js'; +import {logWarn} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {makeBaseSpec} from '../libraries/riseUtils/index.js'; -export const SUPPORTED_AD_TYPES = [BANNER, VIDEO]; -export const BIDDER_CODE = 'stn'; -export const ADAPTER_VERSION = '6.1.0'; -export const TTL = 360; -export const DEFAULT_CURRENCY = 'USD'; -export const SELLER_ENDPOINT = 'https://hb.stngo.com/'; -export const MODES = { +const BIDDER_CODE = 'stn'; +const BASE_URL = 'https://hb.stngo.com/'; +const MODES = { PRODUCTION: 'hb-multi', TEST: 'hb-multi-test' }; -export const SUPPORTED_SYNC_METHODS = { - IFRAME: 'iframe', - PIXEL: 'pixel' -}; export const spec = { + ...makeBaseSpec(BASE_URL, MODES), code: BIDDER_CODE, - version: ADAPTER_VERSION, - supportedMediaTypes: SUPPORTED_AD_TYPES, isBidRequestValid: function (bidRequest) { if (!bidRequest.params) { logWarn('no params have been set to STN adapter'); @@ -45,64 +24,6 @@ export const spec = { } return true; - }, - buildRequests: function (validBidRequests, bidderRequest) { - const combinedRequestsObject = {}; - - const generalObject = validBidRequests[0]; - const testMode = generalObject.params.testMode; - - combinedRequestsObject.params = generateGeneralParams(generalObject, bidderRequest); - combinedRequestsObject.bids = generateBidsParams(validBidRequests, bidderRequest); - - return { - method: 'POST', - url: getEndpoint(testMode, SELLER_ENDPOINT, MODES), - data: combinedRequestsObject - } - }, - interpretResponse: function ({ body }) { - const bidResponses = []; - - if (body.bids) { - body.bids.forEach(adUnit => { - const bidResponse = buildBidResponse(adUnit, DEFAULT_CURRENCY, TTL, VIDEO, BANNER); - bidResponses.push(bidResponse); - }); - } - - return bidResponses; - }, - getUserSyncs: function (syncOptions, serverResponses) { - const syncs = []; - for (const response of serverResponses) { - if (syncOptions.iframeEnabled && deepAccess(response, 'body.params.userSyncURL')) { - syncs.push({ - type: 'iframe', - url: deepAccess(response, 'body.params.userSyncURL') - }); - } - if (syncOptions.pixelEnabled && isArray(deepAccess(response, 'body.params.userSyncPixels'))) { - const pixels = response.body.params.userSyncPixels.map(pixel => { - return { - type: 'image', - url: pixel - } - }) - syncs.push(...pixels) - } - } - return syncs; - }, - onBidWon: function (bid) { - if (bid == null) { - return; - } - - logInfo('onBidWon:', bid); - if (bid.hasOwnProperty('nurl') && bid.nurl.length > 0) { - triggerPixel(bid.nurl); - } } }; diff --git a/modules/stnBidAdapter.md b/modules/stnBidAdapter.md index 90b0b58e34b..d5f5f4f3e1d 100644 --- a/modules/stnBidAdapter.md +++ b/modules/stnBidAdapter.md @@ -13,7 +13,7 @@ Module that connects to STN's demand sources. The STN adapter requires setup and approval from the STN. Please reach out to hb@stnvideo.com to create an STN account. -The adapter supports Video(instream) & Banner. +The adapter supports Video(instream), Banner, Native and multi-format bid requests. # Bid Parameters ## Video diff --git a/modules/symitriDapRtdProvider.js b/modules/symitriDapRtdProvider.js index 7bf523170fe..7befe826382 100644 --- a/modules/symitriDapRtdProvider.js +++ b/modules/symitriDapRtdProvider.js @@ -663,6 +663,7 @@ export function createRtdProvider(moduleName, moduleCode, headerPrefix) { switch (config.api_version) { case 'x1': case 'x1-dev': + case 'x2': method = 'POST'; path = '/data-activation/' + config.api_version + '/domain/' + config.domain + '/identity/tokenize'; body = JSON.stringify(apiParams); @@ -685,6 +686,7 @@ export function createRtdProvider(moduleName, moduleCode, headerPrefix) { switch (config.api_version) { case 'x1': case 'x1-dev': + case 'x2': token = request.getResponseHeader(headerPrefix + '-DAP-Token'); break; } @@ -744,8 +746,7 @@ export function createRtdProvider(moduleName, moduleCode, headerPrefix) { return; } - let path = '/data-activation/' + - config.api_version + + let path = '/data-activation/x1' + '/token/' + token + '/membership'; @@ -812,8 +813,7 @@ export function createRtdProvider(moduleName, moduleCode, headerPrefix) { error: (error, request) => { onError(request, request.status, error, onDone); } }; - let path = '/data-activation/' + - config.api_version + + let path = '/data-activation/x1' + '/token/' + token + '/membership/encrypt'; diff --git a/modules/symitriDapRtdProvider.md b/modules/symitriDapRtdProvider.md index e3429e24144..31dd506b791 100644 --- a/modules/symitriDapRtdProvider.md +++ b/modules/symitriDapRtdProvider.md @@ -42,7 +42,7 @@ pbjs.setConfig({ waitForIt: true, params: { apiHostname: '', - apiVersion: "x1", + apiVersion: 'x1'|'x2', apiAuthToken: '', domain: 'your-domain.com', identityType: 'simpleid'|'compositeid'|'hashedid'|'dap-signature:1.0.0', @@ -68,7 +68,7 @@ Please reach out to your Symitri account representative() to | name | String | Symitri Dap Rtd module name | 'symitriDap' always| | waitForIt | Boolean | Required to ensure that the auction is delayed until prefetch is complete | Optional. Defaults to false | | apiHostname | String | Hostname provided by Symitri | Please reach out to your Symitri account representative() for this value| -| apiVersion | String | This holds the API version | It should be "x1" always | +| apiVersion | String | This holds the API version | Please reach out to your Symitri account representative() for this value | | apiAuthToken | String | Symitri API AuthToken | Please reach out to your Symitri account representative() for this value | | domain | String | The domain name of your webpage | | | identityType | String | 'simpleid' or 'compositeid' or 'hashedid' or 'dap-signature:1.0.0' | Use 'simpleid' to pass email or other plain text ids and SymitriRTD Module will hash it. diff --git a/modules/tealBidAdapter.js b/modules/tealBidAdapter.js new file mode 100644 index 00000000000..03b983d25d9 --- /dev/null +++ b/modules/tealBidAdapter.js @@ -0,0 +1,145 @@ +import {deepSetValue, deepAccess, triggerPixel, deepClone, isEmpty, logError, shuffle} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {ortbConverter} from '../libraries/ortbConverter/converter.js' +import {BANNER} from '../src/mediaTypes.js'; +import {pbsExtensions} from '../libraries/pbsExtensions/pbsExtensions.js' +const BIDDER_CODE = 'teal'; +const GVLID = 1378; +const DEFAULT_ENDPOINT = 'https://a.bids.ws/openrtb2/auction'; +const COOKIE_SYNC_ENDPOINT = 'https://a.bids.ws/cookie_sync'; +const COOKIE_SYNC_IFRAME = 'https://bids.ws/load-cookie.html'; +const MAX_SYNC_COUNT = 10; + +const converter = ortbConverter({ + processors: pbsExtensions, + context: { + netRevenue: true, + ttl: 30 + }, + imp(buildImp, bidRequest, context) { + const imp = buildImp(bidRequest, context); + const { placement, testMode } = bidRequest.params; + if (placement) { + deepSetValue(imp, 'ext.prebid.storedrequest.id', placement); + } + if (testMode) { + deepSetValue(imp, 'ext.prebid.storedauctionresponse.id', placement); + } + delete imp.ext.prebid.bidder; + return imp; + }, + overrides: { + bidResponse: { + bidderCode(orig, bidResponse, bid, { bidRequest }) { + let useSourceBidderCode = deepAccess(bidRequest, 'params.useSourceBidderCode', false); + if (useSourceBidderCode) { + orig.apply(this, [...arguments].slice(1)); + } + }, + }, + } +}); + +export const spec = { + code: BIDDER_CODE, + gvlid: GVLID, + supportedMediaTypes: [BANNER], + aliases: [], + + isBidRequestValid: function(bid) { + return Boolean(bid.params?.account); + }, + + buildRequests: function(bidRequests, bidderRequest) { + const { bidder } = bidRequests[0]; + const data = converter.toORTB({bidRequests, bidderRequest}); + const account = deepAccess(bidRequests[0], 'params.account', null); + const subAccount = deepAccess(bidRequests[0], 'params.subAccount', null); + deepSetValue(data, 'site.publisher.id', account); + deepSetValue(data, 'ext.prebid.storedrequest.id', subAccount || account); + data.ext.prebid.passthrough = { + ...data.ext.prebid.passthrough, + teal: { bidder }, + }; + data.tmax = (bidderRequest.timeout || 1500) - 100; + return { + method: 'POST', + url: deepAccess(bidRequests[0], 'params.endpoint', DEFAULT_ENDPOINT), + data + }; + }, + + interpretResponse: function(response, request) { + const resp = deepClone(response.body); + const { bidder } = request.data.ext.prebid.passthrough.teal; + 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; + }, + + getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent) { + if (!syncOptions.iframeEnabled) { + return []; + } + const syncs = []; + const { gdprApplies, consentString } = gdprConsent || {}; + let bidders = []; + serverResponses.forEach(({ body }) => { + const newBidders = Object.keys(body.ext?.responsetimemillis || {}); + newBidders.forEach(s => { + if (bidders.indexOf(s) === -1) { + bidders.push(s); + } + }); + }); + bidders = shuffle(bidders).slice(0, MAX_SYNC_COUNT); + if (!bidders.length) { + return; + } + const params = { + endpoint: COOKIE_SYNC_ENDPOINT, + max_sync_count: MAX_SYNC_COUNT, + gdpr: gdprApplies ? 1 : 0, + gdpr_consent: consentString, + us_privacy: uspConsent, + bidders: bidders.join(','), + coop_sync: 0 + }; + 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: `${COOKIE_SYNC_IFRAME}?${qs}` }); + return syncs; + }, + + onBidWon: function(bid) { + if (bid.pbsWurl) { + triggerPixel(bid.pbsWurl); + } + if (bid.burl) { + triggerPixel(bid.burl); + } + }, + + onBidderError: function({ error, bidderRequest }) { + if (error.responseText && error.status) { + let id = error.responseText.match(/found for id: (.*)/); + if (Array.isArray(id) && id.length > 1 && error.status == 400) { + logError(`Placement: ${id[1]} not found on ${BIDDER_CODE} server. Please contact your account manager or email prebid@teal.works`, error); + return; + } + } + logError(`${BIDDER_CODE} bidder error`, error); + } +} +registerBidder(spec); diff --git a/modules/tealBidAdapter.md b/modules/tealBidAdapter.md new file mode 100644 index 00000000000..18b654c8108 --- /dev/null +++ b/modules/tealBidAdapter.md @@ -0,0 +1,46 @@ +Overview +======== + +``` +Module Name: Teal Adapter +Module Type: Bidder Adapter +Maintainer: prebid@teal.works +``` + +Description +=========== + +This module connects web publishers to Teal's server-side banner demand. + +# Bid Parameters + +| Key | Required | Example | Description | +| --- | -------- | ------- | ----------- | +| `account` | yes | `myaccount` | account name provided by your account manager - set to `test-account` for test mode | +| `placement` | no | `mysite300x250` | placement name provided by your account manager - set to `test-placement300x250` for test mode | +| `testMode` | no | `true` | activate test mode - 100% test bids - placement needs be set to `test-placement300x250` for this option to work | +| `useSourceBidderCode` | no | `true` | use seat bidder code as hb_bidder, instead of teal (or configured alias) | +| `subAccount` | no | `mysubaccount` | subAccount name, if provided by your account manager | + +### Notes + +- Specific ads.txt entries are required for the Teal bid adapter - please contact your account manager or for more details. +- This adapter requires iframe user syncs to be enabled to support uids. +- For full functionality in GDPR territories, please ensure Teal Digital Group Ltd is configured in your CMP. + +# Test Parameters + +``` +var adUnits = [{ + code: 'test-div', + sizes: [[300, 250]], + bids: [{ + bidder: 'teal', + params: { + account: 'test-account', + placement: 'test-placement300x250', + testMode: true + }, + }] +}] +``` diff --git a/modules/ttdBidAdapter.js b/modules/ttdBidAdapter.js index e9d0d3ca9f1..082b9806da5 100644 --- a/modules/ttdBidAdapter.js +++ b/modules/ttdBidAdapter.js @@ -390,12 +390,13 @@ export const spec = { /** * Make a server request from the list of BidRequests. * - * @param {BidRequest[]} an array of validBidRequests - * @param {*} bidderRequest - * @return {ServerRequest} Info describing the request to the server. + * @param {BidRequest[]} validBidRequests - An array of valid bid requests + * @param {*} bidderRequest - The current bidder request object + * @returns {ServerRequest} - Info describing the request to the server */ buildRequests: function (validBidRequests, bidderRequest) { const firstPartyData = bidderRequest.ortb2 || {}; + const firstPartyImpData = bidderRequest.ortb2Imp || {}; let topLevel = { id: bidderRequest.bidderRequestId, imp: validBidRequests.map(bidRequest => getImpression(bidRequest)), @@ -418,11 +419,18 @@ export const spec = { } if (firstPartyData && firstPartyData.app) { - topLevel.app = firstPartyData.app + topLevel.app = firstPartyData.app; } - if (firstPartyData && firstPartyData.pmp) { - topLevel.pmp = firstPartyData.pmp + if ((firstPartyData && firstPartyData.pmp) || (firstPartyImpData && firstPartyImpData.pmp)) { + topLevel.imp.forEach(imp => { + imp.pmp = utils.mergeDeep( + {}, + imp.pmp || {}, + firstPartyData?.pmp || {}, + firstPartyImpData?.pmp || {} + ); + }); } let url = selectEndpoint(bidderRequest.bids[0].params) + bidderRequest.bids[0].params.supplySourceId; @@ -457,7 +465,7 @@ export const spec = { * - vastXml * - dealId * - * @param {ttdResponseObj} bidResponse A successful response from ttd. + * @param {Object} response A successful response from ttd. * @param {ServerRequest} serverRequest The result of buildRequests() that lead to this response. * @return {Bid[]} An array of formatted bids. */ diff --git a/modules/userId/eids.js b/modules/userId/eids.js index 57341773184..8289e43c963 100644 --- a/modules/userId/eids.js +++ b/modules/userId/eids.js @@ -1,4 +1,4 @@ -import {deepClone, isFn, isStr} from '../../src/utils.js'; +import {logError, deepClone, isFn, isStr} from '../../src/utils.js'; /** * @typedef {import('./index.js').SubmodulePriorityMap} SubmodulePriorityMap @@ -38,7 +38,10 @@ function createEidObject(userIdData, subModuleKey, eidConf) { export function createEidsArray(bidRequestUserId, eidConfigs = EID_CONFIG) { const allEids = {}; function collect(eid) { - const key = JSON.stringify([eid.source?.toLowerCase(), eid.ext]); + const key = JSON.stringify([ + eid.source?.toLowerCase(), + ...Object.keys(eid).filter(k => !['uids', 'source'].includes(k)).sort().map(k => eid[k]) + ]); if (allEids.hasOwnProperty(key)) { allEids[key].uids.push(...eid.uids); } else { @@ -48,8 +51,27 @@ export function createEidsArray(bidRequestUserId, eidConfigs = EID_CONFIG) { Object.entries(bidRequestUserId).forEach(([name, values]) => { values = Array.isArray(values) ? values : [values]; - const eids = name === 'pubProvidedId' ? deepClone(values) : values.map(value => createEidObject(value, name, eidConfigs.get(name))); - eids.filter(eid => eid != null).forEach(collect); + const eidConf = eidConfigs.get(name); + let eids; + if (name === 'pubProvidedId') { + eids = deepClone(values); + } else if (typeof eidConf === 'function') { + try { + eids = eidConf(values); + if (!Array.isArray(eids)) { + eids = [eids]; + } + eids.forEach(eid => eid.uids = eid.uids.filter(({id}) => isStr(id))) + eids = eids.filter(({uids}) => uids?.length > 0); + } catch (e) { + logError(`Could not generate EID for "${name}"`, e); + } + } else { + eids = values.map(value => createEidObject(value, name, eidConf)); + } + if (Array.isArray(eids)) { + eids.filter(eid => eid != null).forEach(collect); + } }) return Object.values(allEids); } @@ -64,7 +86,12 @@ export function getEids(priorityMap) { const submodule = submodules.find(mod => mod.idObj?.[key] != null); if (submodule) { idValues[key] = submodule.idObj[key]; - eidConfigs.set(key, submodule.submodule.eids?.[key]) + let eidConf = submodule.submodule.eids?.[key]; + if (typeof eidConf === 'function') { + // if eid config is given as a function, append the active module configuration to its args + eidConf = ((orig) => (...args) => orig(...args, submodule.config))(eidConf); + } + eidConfigs.set(key, eidConf); } }) return createEidsArray(idValues, eidConfigs); diff --git a/modules/wurflRtdProvider.js b/modules/wurflRtdProvider.js index dbf50744d25..7bb785366bc 100644 --- a/modules/wurflRtdProvider.js +++ b/modules/wurflRtdProvider.js @@ -65,7 +65,7 @@ const getBidRequestData = (reqBidsConfigObj, callback, config, userConsent) => { } url.searchParams.set('mode', 'prebid') - logger.logMessage('url', url.toString()); + url.searchParams.set('wurfl_id', 'true') try { loadExternalScript(url.toString(), MODULE_TYPE_RTD, MODULE_NAME, () => { @@ -120,6 +120,9 @@ function enrichBidderRequests(reqBidsConfigObj, bidders, wjsResponse) { */ export const bidderData = (wurflData, caps, filter) => { const data = {}; + if ('wurfl_id' in wurflData) { + data['wurfl_id'] = wurflData.wurfl_id; + } caps.forEach((cap, index) => { if (!filter.includes(index)) { return; @@ -152,6 +155,9 @@ export const lowEntropyData = (wurflData, lowEntropyCaps) => { if ('brand_name' in wurflData) { data['brand_name'] = wurflData.brand_name; } + if ('wurfl_id' in wurflData) { + data['wurfl_id'] = wurflData.wurfl_id; + } return data; } /** diff --git a/modules/wurflRtdProvider.md b/modules/wurflRtdProvider.md index d656add3543..c7993a67364 100644 --- a/modules/wurflRtdProvider.md +++ b/modules/wurflRtdProvider.md @@ -9,7 +9,7 @@ ## Description The WURFL RTD module enriches the OpenRTB 2.0 device data with [WURFL data](https://www.scientiamobile.com/wurfl-js-business-edition-at-the-intersection-of-javascript-and-enterprise/). -The module sets the WURFL data in `device.ext.wurfl` and all the bidder adapters will always receive the low entry capabilites like `is_mobile`, `complete_device_name` and `form_factor`. +The module sets the WURFL data in `device.ext.wurfl` and all the bidder adapters will always receive the low entry capabilities like `is_mobile`, `complete_device_name` and `form_factor`, and the `wurfl_id`. For a more detailed analysis bidders can subscribe to detect iPhone and iPad models and receive additional [WURFL device capabilities](https://www.scientiamobile.com/capabilities/?products%5B%5D=wurfl-js). diff --git a/modules/zeta_global_sspAnalyticsAdapter.js b/modules/zeta_global_sspAnalyticsAdapter.js index 805e2c51c81..03e3f8f556e 100644 --- a/modules/zeta_global_sspAnalyticsAdapter.js +++ b/modules/zeta_global_sspAnalyticsAdapter.js @@ -4,6 +4,8 @@ import adapterManager from '../src/adapterManager.js'; import {EVENTS} from '../src/constants.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; +import {config} from '../src/config.js'; +import {parseDomain} from '../src/refererDetection.js'; const ZETA_GVL_ID = 833; const ADAPTER_CODE = 'zeta_global_ssp'; @@ -27,10 +29,11 @@ function sendEvent(eventType, event) { /// /////////// ADAPTER EVENT HANDLER FUNCTIONS ////////////// function adRenderSucceededHandler(args) { + const page = config.getConfig('pageUrl') || args.doc?.location?.host + args.doc?.location?.pathname; const event = { zetaParams: zetaParams, - domain: args.doc?.location?.host, - page: args.doc?.location?.host + args.doc?.location?.pathname, + domain: parseDomain(page, {noLeadingWww: true}), + page: page, bid: { adId: args.bid?.adId, requestId: args.bid?.requestId, diff --git a/package-lock.json b/package-lock.json index 2001909266e..6ee34d1b924 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "9.27.0-pre", + "version": "9.30.0-pre", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "9.27.0-pre", + "version": "9.30.0-pre", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.25.2", @@ -22,7 +22,7 @@ "fun-hooks": "^0.9.9", "gulp-wrap": "^0.15.0", "klona": "^2.0.6", - "live-connect-js": "^7.1.0" + "live-connect-js": "^7.2.0" }, "devDependencies": { "@babel/eslint-parser": "^7.16.5", @@ -18209,9 +18209,10 @@ } }, "node_modules/live-connect-js": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-7.1.0.tgz", - "integrity": "sha512-fFxvQjOsHkCjulWsbirjxb6Y8xuAoWdgYqZvBLoSVKry48IyvVnLfvWgJg66qENjxig+8RH9bvlE16I6hJ7J7Q==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-7.2.0.tgz", + "integrity": "sha512-oZY4KqwrG1C+CDKApcsdDdMG4j2d44lhmvbNy4ZE6sPFr+W8R3m0+V+JxXB8p6tgSePJ8X/uhzAGos0lDg/MAg==", + "license": "Apache-2.0", "dependencies": { "live-connect-common": "^v4.1.0", "tiny-hashes": "1.0.1" @@ -25554,10 +25555,11 @@ "dev": true }, "node_modules/undici": { - "version": "6.19.8", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.19.8.tgz", - "integrity": "sha512-U8uCCl2x9TK3WANvmBavymRzxbfFYG+tAu+fgx3zxQy3qdagQqBLwJVrdyO1TBfUXvfKveMKJZhpvUYoOjM+4g==", + "version": "6.21.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.1.tgz", + "integrity": "sha512-q/1rj5D0/zayJB2FraXdaWxbhWiNKDvu8naDT2dl1yTlvJp4BLtOcp2a5BvgGNQpYYJzau7tf1WgKv3b+7mqpQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=18.17" } @@ -41730,9 +41732,9 @@ "integrity": "sha512-sRklgbe13377aR+G0qCBiZPayQw5oZZozkuxKEoyipxscLbVzwe9gtA7CPpbmo6UcOdQxdCE6A7J1tI0wTSmqw==" }, "live-connect-js": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-7.1.0.tgz", - "integrity": "sha512-fFxvQjOsHkCjulWsbirjxb6Y8xuAoWdgYqZvBLoSVKry48IyvVnLfvWgJg66qENjxig+8RH9bvlE16I6hJ7J7Q==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-7.2.0.tgz", + "integrity": "sha512-oZY4KqwrG1C+CDKApcsdDdMG4j2d44lhmvbNy4ZE6sPFr+W8R3m0+V+JxXB8p6tgSePJ8X/uhzAGos0lDg/MAg==", "requires": { "live-connect-common": "^v4.1.0", "tiny-hashes": "1.0.1" @@ -47358,9 +47360,9 @@ "dev": true }, "undici": { - "version": "6.19.8", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.19.8.tgz", - "integrity": "sha512-U8uCCl2x9TK3WANvmBavymRzxbfFYG+tAu+fgx3zxQy3qdagQqBLwJVrdyO1TBfUXvfKveMKJZhpvUYoOjM+4g==", + "version": "6.21.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.1.tgz", + "integrity": "sha512-q/1rj5D0/zayJB2FraXdaWxbhWiNKDvu8naDT2dl1yTlvJp4BLtOcp2a5BvgGNQpYYJzau7tf1WgKv3b+7mqpQ==", "dev": true }, "undici-types": { diff --git a/package.json b/package.json index 648cf2e0736..b41e9852f20 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "9.27.0-pre", + "version": "9.30.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.public.js", "exports": { @@ -143,7 +143,7 @@ "fun-hooks": "^0.9.9", "gulp-wrap": "^0.15.0", "klona": "^2.0.6", - "live-connect-js": "^7.1.0" + "live-connect-js": "^7.2.0" }, "optionalDependencies": { "fsevents": "^2.3.2" diff --git a/src/adloader.js b/src/adloader.js index bf695dd627b..8fad053f7f5 100644 --- a/src/adloader.js +++ b/src/adloader.js @@ -11,7 +11,6 @@ const _approvedLoadExternalJSList = [ 'debugging', 'outstream', // Bid Modules - only exception is on rendering edge cases, to clean up in Prebid 10: - 'improvedigital', 'showheroes-bs', // RTD modules: 'aaxBlockmeter', diff --git a/src/mediaTypes.js b/src/mediaTypes.js index 2afa2aefaf9..9b5318e0657 100644 --- a/src/mediaTypes.js +++ b/src/mediaTypes.js @@ -18,3 +18,5 @@ export const VIDEO = 'video'; export const BANNER = 'banner'; /** @type {VideoContext} */ export const ADPOD = 'adpod'; + +export const ALL_MEDIATYPES = [NATIVE, VIDEO, BANNER]; diff --git a/src/native.js b/src/native.js index 19833406451..d1ac4ea17d7 100644 --- a/src/native.js +++ b/src/native.js @@ -436,13 +436,16 @@ function assetsMessage(data, adObject, keys, {index = auctionManager.index} = {} message: 'assetResponse', adId: data.adId, }; - let renderData = getRenderingData(adObject).native; + let {native: renderData, rendererVersion} = getRenderingData(adObject); if (renderData) { // if we have native rendering data (set up by the nativeRendering module) // include it in full ("all assets") together with the renderer. // this is to allow PUC to use dynamic renderers without requiring changes in creative setup - msg.native = Object.assign({}, renderData); - msg.renderer = getCreativeRendererSource(adObject); + Object.assign(msg, { + native: Object.assign({}, renderData), + renderer: getCreativeRendererSource(adObject), + rendererVersion, + }) if (keys != null) { renderData.assets = renderData.assets.filter(({key}) => keys.includes(key)) } diff --git a/test/spec/creative/nativeRenderer_spec.js b/test/spec/creative/nativeRenderer_spec.js index 66e81a517c7..935f3db1ad3 100644 --- a/test/spec/creative/nativeRenderer_spec.js +++ b/test/spec/creative/nativeRenderer_spec.js @@ -34,10 +34,16 @@ describe('Native creative renderer', () => { }); }); describe('otherwise, calls replacer', () => { - let replacer; + let replacer, frame; beforeEach(() => { replacer = sinon.stub().returns('markup'); + frame = document.createElement('iframe'); + document.body.appendChild(frame); + win.document = frame.contentDocument; }); + afterEach(() => { + document.body.removeChild(frame); + }) it('with adTemplate, if present', () => { return getAdMarkup('123', {adTemplate: 'tpl'}, replacer, win).then((result) => { expect(result).to.eql('markup'); @@ -45,7 +51,7 @@ describe('Native creative renderer', () => { }); }); it('with document body otherwise', () => { - win.document = {body: {innerHTML: 'body'}}; + win.document.body.innerHTML = 'body' return getAdMarkup('123', {}, replacer, win).then((result) => { expect(result).to.eql('markup'); sinon.assert.calledWith(replacer, 'body'); @@ -186,26 +192,29 @@ describe('Native creative renderer', () => { }); describe('render', () => { - let getMarkup, sendMessage, adId, nativeData, exc; + let getMarkup, sendMessage, adId, nativeData, exc, frame; beforeEach(() => { adId = '123'; nativeData = {} getMarkup = sinon.stub(); sendMessage = sinon.stub() exc = sinon.stub(); - win.document = { - querySelectorAll() { return [] }, - body: {} - } + frame = document.createElement('iframe'); + document.body.appendChild(frame); + win.document = frame.contentDocument; }); + afterEach(() => { + document.body.removeChild(frame); + }) + function runRender() { return render({adId, native: nativeData}, {sendMessage, exc}, win, getMarkup) } it('replaces placeholders in head, if present', () => { getMarkup.returns(Promise.resolve('')) - win.document.head = {innerHTML: '##hb_native_asset_id_1##'}; + win.document.head.innerHTML = '##hb_native_asset_id_1##'; nativeData.ortb = { assets: [ {id: 1, data: {value: 'repl'}} @@ -216,6 +225,14 @@ describe('Native creative renderer', () => { }) }); + it('does not replace iframes with srcdoc that contain "renderer"', () => { + win.document.head.innerHTML = win.document.body.innerHTML = ''; + getMarkup.returns(Promise.resolve('')) + return runRender().then(() => { + expect(Array.from(win.document.querySelectorAll('iframe[srcdoc="renderer"]')).length).to.eql(2); + }) + }) + it('drops markup on body, and fires imp trackers', () => { getMarkup.returns(Promise.resolve('markup')); return runRender().then(() => { @@ -246,9 +263,27 @@ describe('Native creative renderer', () => { describe('requests resize', () => { beforeEach(() => { + const mkNode = () => { + const node = { + innerHTML: '', + childNodes: [], + insertAdjacentHTML: () => {}, + style: {}, + querySelectorAll: () => [], + cloneNode: () => node + }; + return node; + } + win.document = { + head: mkNode(), + body: Object.assign(mkNode(), { + offsetHeight: 123, + offsetWidth: 321 + }), + querySelectorAll: () => [], + style: {} + }; getMarkup.returns(Promise.resolve('markup')); - win.document.body.offsetHeight = 123; - win.document.body.offsetWidth = 321; }); it('immediately, if document is loaded', () => { diff --git a/test/spec/libraries/mspa/activityControls_spec.js b/test/spec/libraries/mspa/activityControls_spec.js index 80d9fc500b1..dcbebf9974c 100644 --- a/test/spec/libraries/mspa/activityControls_spec.js +++ b/test/spec/libraries/mspa/activityControls_spec.js @@ -45,6 +45,13 @@ describe('Consent interpretation', () => { expect(isBasicConsentDenied(mkConsent({ KnownChildSensitiveDataConsents: [0, null] }))).to.be.false; + }); + + it('should deny when Version = 2 & childconsent[3] is 1', () => { + expect(isBasicConsentDenied(mkConsent({ + Version: 2, + KnownChildSensitiveDataConsents: [null, null, 1] + }))).to.be.true; }) }); @@ -84,13 +91,60 @@ describe('Consent interpretation', () => { expect(result).to.equal(false); }); Object.entries({ - 'health information': 2, - 'biometric data': 6, - }).forEach(([t, flagNo]) => { - it(`'should be true (consent denied to add ufpd) if no consent to process ${t}'`, () => { - const consent = mkConsent(); - consent.SensitiveDataProcessing[flagNo] = 1; - expect(isTransmitUfpdConsentDenied(consent)).to.be.true; + 'health information': { + flagNo: 2, + consents: { + 1: true, + 2: false + }, + versions: [1, 2] + }, + 'biometric data': { + flagNo: 6, + consents: { + 1: true, + 2: true + }, + versions: [1, 2] + }, + 'consumer health data': { + flagNo: 12, + consents: { + 1: true, + 2: false + }, + versions: [2] + }, + 'status as transgender': { + flagNo: 15, + consents: { + 1: true, + 2: true, + }, + versions: [2] + } + + }).forEach(([t, {flagNo, consents, versions}]) => { + describe(t, () => { + Object.entries(consents).forEach(([flagValue, shouldBeDenied]) => { + const flagDescription = ({ + 1: 'denied', + 2: 'given' + })[flagValue] + describe(`consent is ${flagValue} (${flagDescription})`, () => { + let consent; + beforeEach(() => { + consent = mkConsent(); + consent.SensitiveDataProcessing[flagNo] = parseInt(flagValue, 10); + }); + versions.forEach(version => { + it(`should ${shouldBeDenied ? 'deny' : 'allow'} (version ${version})`, () => { + consent.Version = version; + expect(isTransmitUfpdConsentDenied(consent)).to.eql(shouldBeDenied); + }) + }) + }) + }) }) }); @@ -181,8 +235,8 @@ describe('mspaRule', () => { expect(mkRule()().allow).to.equal(false); }); - it('should deny when consent is using version != 1', () => { - consent = {Version: 2}; + it('should deny when consent is using version other than 1/2', () => { + consent = {Version: 3}; expect(mkRule()().allow).to.equal(false); }) diff --git a/test/spec/libraries/teqblazeUtils/bidderUtils_spec.js b/test/spec/libraries/teqblazeUtils/bidderUtils_spec.js index ba8b986163b..4bc74f50de5 100644 --- a/test/spec/libraries/teqblazeUtils/bidderUtils_spec.js +++ b/test/spec/libraries/teqblazeUtils/bidderUtils_spec.js @@ -151,7 +151,11 @@ describe('TeqBlazeBidderUtils', function () { 'coppa', 'ccpa', 'gdpr', - 'tmax' + 'tmax', + 'bcat', + 'badv', + 'bapp', + 'battr' ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); diff --git a/test/spec/modules/33acrossIdSystem_spec.js b/test/spec/modules/33acrossIdSystem_spec.js index 93e6a41d928..99fe2d4921d 100644 --- a/test/spec/modules/33acrossIdSystem_spec.js +++ b/test/spec/modules/33acrossIdSystem_spec.js @@ -21,7 +21,7 @@ describe('33acrossIdSystem', () => { }); describe('getId', () => { - it('should call endpoint and handle valid response', () => { + it('should call endpoint', () => { const completeCallback = sinon.spy(); const { callback } = thirtyThreeAcrossIdSubmodule.getId({ @@ -50,7 +50,71 @@ describe('33acrossIdSystem', () => { const regExp = new RegExp('https://lexicon.33across.com/v1/envelope\\?pid=12345&gdpr=\\d&src=pbjs&ver=$prebid.version$'); expect(request.url).to.match(regExp); - expect(completeCallback.calledOnceWithExactly('foo')).to.be.true; + }); + + context('when there\'s a successful response containing an ID', () => { + it('should execute the callback and pass the ID', () => { + const completeCallback = sinon.spy(); + + const { callback } = thirtyThreeAcrossIdSubmodule.getId({ + params: { + pid: '12345' + } + }); + + callback(completeCallback); + + const [request] = server.requests; + + request.respond(200, { + 'Content-Type': 'application/json' + }, JSON.stringify({ + succeeded: true, + data: { + envelope: 'foo' + }, + expires: 1645667805067 + })); + + expect(completeCallback.calledOnceWithExactly('foo')).to.be.true; + }); + + it('should NOT wipe any stored hashed email', () => { + const completeCallback = () => {}; + + const { callback } = thirtyThreeAcrossIdSubmodule.getId({ + params: { + pid: '12345' + }, + enabledStorageTypes: [ 'html5' ], + storage: {} + }); + + callback(completeCallback); + + const [request] = server.requests; + + const removeDataFromLocalStorage = sinon.stub(storage, 'removeDataFromLocalStorage'); + const setCookie = sinon.stub(storage, 'setCookie'); + sinon.stub(domainUtils, 'domainOverride').returns('foo.com'); + + request.respond(200, { + 'Content-Type': 'application/json' + }, JSON.stringify({ + succeeded: true, + data: { + envelope: 'foo' + }, + expires: 1645667805067 + })); + + expect(removeDataFromLocalStorage.calledWithExactly('33acrossIdHm')).to.be.false; + expect(setCookie.calledWithExactly('33acrossIdHm', '', sinon.match.string, 'Lax', 'foo.com')).to.be.false; + + removeDataFromLocalStorage.restore(); + setCookie.restore(); + domainUtils.domainOverride.restore(); + }); }); const additionalOptions = { @@ -931,9 +995,194 @@ describe('33acrossIdSystem', () => { storage.getDataFromLocalStorage.restore(); }); + + context('and the enabled storage types include "html5"', () => { + it('should store the hashed email in local storage', () => { + const completeCallback = () => {}; + + const { callback } = thirtyThreeAcrossIdSubmodule.getId({ + params: { + pid: '12345', + hem: '33acrossIdHmValue+' + }, + enabledStorageTypes: [ 'html5' ], + storage: {} + }); + + callback(completeCallback); + + const [request] = server.requests; + + const setDataInLocalStorage = sinon.stub(storage, 'setDataInLocalStorage'); + + request.respond(200, { + 'Content-Type': 'application/json' + }, JSON.stringify({ + succeeded: true, + data: { + envelope: 'foo', + fp: 'bar' + }, + expires: 1645667805067 + })); + + expect(setDataInLocalStorage.calledWithExactly('33acrossIdHm', '33acrossIdHmValue+')).to.be.true; + + setDataInLocalStorage.restore(); + }); + }); + + context('and the enabled storage types include "cookie"', () => { + it('should store the hashed email in a cookie', () => { + const completeCallback = () => {}; + + const { callback } = thirtyThreeAcrossIdSubmodule.getId({ + params: { + pid: '12345', + hem: '33acrossIdHmValue+' + }, + enabledStorageTypes: [ 'cookie' ], + storage: { expires: 30 } + }); + + callback(completeCallback); + + const [request] = server.requests; + + const setCookie = sinon.stub(storage, 'setCookie'); + sinon.stub(domainUtils, 'domainOverride').returns('foo.com'); + + request.respond(200, { + 'Content-Type': 'application/json' + }, JSON.stringify({ + succeeded: true, + data: { + envelope: 'foo', + fp: 'bar' + }, + expires: 1645667805067 + })); + + expect(setCookie.calledWithExactly('33acrossIdHm', '33acrossIdHmValue+', sinon.match.string, 'Lax', 'foo.com')).to.be.true; + + setCookie.restore(); + domainUtils.domainOverride.restore(); + }); + }); }); context('when a hashed email is NOT provided via configuration options', () => { + context('but it\'s provided via global 33across object', () => { + beforeEach(() => { + window._33across = { + hem: { + sha256: 'fake-sha256-hashed-email+' + } + } + }); + + afterEach(() => { + delete window._33across; + }); + + it('should call endpoint with the hashed email included', () => { + const completeCallback = () => {}; + const { callback } = thirtyThreeAcrossIdSubmodule.getId({ + params: { + pid: '12345' + // No hashed email via config option. + }, + enabledStorageTypes: [ 'html5' ], + storage: {} + }); + + // Prioritizes the hashed email given via global object over the one stored in LS. + sinon.stub(storage, 'getDataFromLocalStorage') + .withArgs('33acrossIdHm') + .returns('33acrossIdHmValueLS'); + + callback(completeCallback); + + const [request] = server.requests; + + expect(request.url).to.contain('sha256=fake-sha256-hashed-email%2B'); + + storage.getDataFromLocalStorage.restore(); + }); + + context('and the enabled storage types include "html5"', () => { + it('should store the hashed email in local storage', () => { + const completeCallback = () => {}; + + const { callback } = thirtyThreeAcrossIdSubmodule.getId({ + params: { + pid: '12345' + }, + enabledStorageTypes: [ 'html5' ], + storage: {} + }); + + callback(completeCallback); + + const [request] = server.requests; + + const setDataInLocalStorage = sinon.stub(storage, 'setDataInLocalStorage'); + + request.respond(200, { + 'Content-Type': 'application/json' + }, JSON.stringify({ + succeeded: true, + data: { + envelope: 'foo', + fp: 'bar' + }, + expires: 1645667805067 + })); + + expect(setDataInLocalStorage.calledWithExactly('33acrossIdHm', 'fake-sha256-hashed-email+')).to.be.true; + + setDataInLocalStorage.restore(); + }); + }); + + context('and the enabled storage types include "cookie"', () => { + it('should store the hashed email in a cookie', () => { + const completeCallback = () => {}; + + const { callback } = thirtyThreeAcrossIdSubmodule.getId({ + params: { + pid: '12345' + }, + enabledStorageTypes: [ 'cookie' ], + storage: { expires: 30 } + }); + + callback(completeCallback); + + const [request] = server.requests; + + const setCookie = sinon.stub(storage, 'setCookie'); + sinon.stub(domainUtils, 'domainOverride').returns('foo.com'); + + request.respond(200, { + 'Content-Type': 'application/json' + }, JSON.stringify({ + succeeded: true, + data: { + envelope: 'foo', + fp: 'bar' + }, + expires: 1645667805067 + })); + + expect(setCookie.calledWithExactly('33acrossIdHm', 'fake-sha256-hashed-email+', sinon.match.string, 'Lax', 'foo.com')).to.be.true; + + setCookie.restore(); + domainUtils.domainOverride.restore(); + }); + }); + }); + context('but it\'s provided via local storage', () => { it('should call endpoint with the hashed email included', () => { const completeCallback = () => {}; @@ -1205,6 +1454,43 @@ describe('33acrossIdSystem', () => { expect(completeCallback.calledOnceWithExactly(undefined)).to.be.true; }); + + it('should wipe any stored hashed email', () => { + const completeCallback = () => {}; + + const { callback } = thirtyThreeAcrossIdSubmodule.getId({ + params: { + pid: '12345' + }, + enabledStorageTypes: [ 'html5' ], + storage: {} + }); + + callback(completeCallback); + + const [request] = server.requests; + + const removeDataFromLocalStorage = sinon.stub(storage, 'removeDataFromLocalStorage'); + const setCookie = sinon.stub(storage, 'setCookie'); + sinon.stub(domainUtils, 'domainOverride').returns('foo.com'); + + request.respond(200, { + 'Content-Type': 'application/json' + }, JSON.stringify({ + succeeded: true, + data: { + // no envelope field + }, + expires: 1645667805067 + })); + + expect(removeDataFromLocalStorage.calledWithExactly('33acrossIdHm')).to.be.true; + expect(setCookie.calledWithExactly('33acrossIdHm', '', sinon.match.string, 'Lax', 'foo.com')).to.be.true; + + removeDataFromLocalStorage.restore(); + setCookie.restore(); + domainUtils.domainOverride.restore(); + }); }); context('when the server returns an error status code', () => { diff --git a/test/spec/modules/acuityadsBidAdapter_spec.js b/test/spec/modules/acuityadsBidAdapter_spec.js index ecc40025c95..40de7db69c3 100644 --- a/test/spec/modules/acuityadsBidAdapter_spec.js +++ b/test/spec/modules/acuityadsBidAdapter_spec.js @@ -145,7 +145,11 @@ describe('AcuityAdsBidAdapter', function () { 'coppa', 'ccpa', 'gdpr', - 'tmax' + 'tmax', + 'bcat', + 'badv', + 'bapp', + 'battr' ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); @@ -191,6 +195,55 @@ describe('AcuityAdsBidAdapter', function () { } }); + it('Returns valid endpoints', function () { + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + endpointId: 'testBanner', + }, + userIdAsEids + } + ]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.endpointId).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('network'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); + + 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); diff --git a/test/spec/modules/adlooxAdServerVideo_spec.js b/test/spec/modules/adlooxAdServerVideo_spec.js index 58277bc830d..c941b9dc710 100644 --- a/test/spec/modules/adlooxAdServerVideo_spec.js +++ b/test/spec/modules/adlooxAdServerVideo_spec.js @@ -34,7 +34,6 @@ describe('Adloox Ad Server Video', function () { }; const analyticsOptions = { - js: 'https://j.adlooxtracking.com/ads/js/tfav_adl_%%clientid%%.js', client: 'adlooxtest', clientid: 127, platformid: 0, diff --git a/test/spec/modules/adlooxAnalyticsAdapter_spec.js b/test/spec/modules/adlooxAnalyticsAdapter_spec.js index fa8204a9dc5..964eb6650e9 100644 --- a/test/spec/modules/adlooxAnalyticsAdapter_spec.js +++ b/test/spec/modules/adlooxAnalyticsAdapter_spec.js @@ -45,6 +45,11 @@ describe('Adloox Analytics Adapter', function () { adapter: analyticsAdapter }); describe('enableAnalytics', function () { + afterEach(function () { + analyticsAdapter.disableAnalytics(); + expect(analyticsAdapter.context).is.null; + }); + describe('invalid options', function () { it('should require options', function (done) { adapterManager.enableAnalytics({ @@ -68,6 +73,32 @@ describe('Adloox Analytics Adapter', function () { done(); }); + it('should accept subdomains of adlooxtracking.com for options.js', function (done) { + const analyticsOptionsLocal = utils.deepClone(analyticsOptions); + analyticsOptionsLocal.js = 'https://test.adlooxtracking.com/test.js'; + + adapterManager.enableAnalytics({ + provider: analyticsAdapterName, + options: analyticsOptionsLocal + }); + expect(analyticsAdapter.context).is.not.null; + + done(); + }); + + it('should reject non-subdomains of adlooxtracking.com for options.js', function (done) { + const analyticsOptionsLocal = utils.deepClone(analyticsOptions); + analyticsOptionsLocal.js = 'https://example.com/test.js'; + + adapterManager.enableAnalytics({ + provider: analyticsAdapterName, + options: analyticsOptionsLocal + }); + expect(analyticsAdapter.context).is.null; + + done(); + }); + it('should reject non-function options.toselector', function (done) { const analyticsOptionsLocal = utils.deepClone(analyticsOptions); analyticsOptionsLocal.toselector = esplode; diff --git a/test/spec/modules/admanBidAdapter_spec.js b/test/spec/modules/admanBidAdapter_spec.js index ae3b935619e..abae74b9749 100644 --- a/test/spec/modules/admanBidAdapter_spec.js +++ b/test/spec/modules/admanBidAdapter_spec.js @@ -145,7 +145,11 @@ describe('AdmanBidAdapter', function () { 'coppa', 'ccpa', 'gdpr', - 'tmax' + 'tmax', + 'bcat', + 'badv', + 'bapp', + 'battr' ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); diff --git a/test/spec/modules/adnuntiusBidAdapter_spec.js b/test/spec/modules/adnuntiusBidAdapter_spec.js index c82671040d0..d9574b917ff 100644 --- a/test/spec/modules/adnuntiusBidAdapter_spec.js +++ b/test/spec/modules/adnuntiusBidAdapter_spec.js @@ -106,7 +106,33 @@ describe('adnuntiusBidAdapter', function () { } ]; - const multiBidderInResponse = { + const nativeBidderRequest = {bid: [ + { + bidId: 'adn-0000000000000551', + bidder: 'adnuntius', + params: { + auId: '0000000000000551', + network: 'adnuntius', + }, + mediaTypes: { + native: { + ortb: { + assets: [{ + id: 1, + required: 1, + img: { + type: 3, + w: 250, + h: 250, + } + }] + } + } + }, + } + ]}; + + const multiBidAndFormatRequest = { bid: [{ bidder: 'adnuntius', bidId: '3a602680158a85', @@ -127,6 +153,7 @@ describe('adnuntiusBidAdapter', function () { }, { bidder: 'adnuntius', + bidId: 'fewwef', params: { auId: '381535', network: '1287', @@ -140,6 +167,51 @@ describe('adnuntiusBidAdapter', function () { video: { playerSize: [200, 200], context: 'instream' + }, + native: { + ortb: { + assets: [{ + id: 1, + required: 1, + img: { + type: 3, + w: 150, + h: 50, + } + }] + } + } + } + }, + { + bidder: 'adnuntius', + bidId: 'pol', + params: { + auId: '381535', + network: '1287', + bidType: 'netBid', + targetId: 'alt', + }, + mediaTypes: { + banner: { + sizes: [[200, 200]] + }, + video: { + playerSize: [200, 200], + context: 'instream' + }, + native: { + ortb: { + assets: [{ + id: 1, + required: 1, + img: { + type: 3, + w: 150, + h: 50, + } + }] + } } } }] @@ -172,7 +244,7 @@ describe('adnuntiusBidAdapter', function () { bidId: 'adn-0000000000000551', } ] - } + }; const videoBidRequest = { bid: videoBidderRequest, @@ -180,7 +252,7 @@ describe('adnuntiusBidAdapter', function () { params: { bidType: 'justsomestuff-error-handling' } - } + }; const deals = [ { @@ -246,6 +318,72 @@ describe('adnuntiusBidAdapter', function () { } ]; + const nativeResponseBody = [ + { + 'auId': '0000000000000551', + 'targetId': 'adn-0000000000000551', + 'nativeJson': { + 'assets': [ + { + 'id': 1, + 'required': 1, + 'img': { + 'url': 'http://something.com/something.png' + } + }, + { + 'url': 'http://whatever.com' + }] + }, + 'matchedAdCount': 1, + 'responseId': '', + 'ads': [ + { + 'advertiserDomains': ['adnuntius.com'], + 'creativeWidth': 640, + 'creativeHeight': 640, + 'cpm': { + 'amount': 2000, + 'currency': 'NOK' + }, + 'bid': { + 'amount': 2, + 'currency': 'NOK' + }, + 'grossBid': { + 'amount': 2, + 'currency': 'NOK' + }, + 'netBid': { + 'amount': 2, + 'currency': 'NOK' + }, + 'cost': { + 'amount': 2, + 'currency': 'NOK' + }, + 'assets': { + 'img': { + 'cdnId': 'http://localhost:8079/cdn/9urJusYWpjFDLcpOwfejrkWlLP1heM3vWIJjuHk48BQ.mp4', + 'width': '1920', + 'height': '1080' + } + }, + } + ] + } + ]; + + const nativeResponse = { + body: { + 'adUnits': nativeResponseBody + } + }; + + const nativeMultiFormatResponseBody = JSON.parse(JSON.stringify(nativeResponseBody[0])); + nativeMultiFormatResponseBody.auId = '0000000000381535'; + nativeMultiFormatResponseBody.targetId = 'alt-native'; + const multiFormatServerResponse = { body: { 'adUnits': [ @@ -258,23 +396,23 @@ describe('adnuntiusBidAdapter', function () { 'ads': [ { 'cpm': { - 'amount': 1500.0, + 'amount': 12500.0, 'currency': 'NOK' }, 'bid': { - 'amount': 1.5, + 'amount': 5, 'currency': 'NOK' }, 'grossBid': { - 'amount': 1.5, + 'amount': 5, 'currency': 'NOK' }, 'netBid': { - 'amount': 1.5, + 'amount': 5, 'currency': 'NOK' }, 'cost': { - 'amount': 1.5, + 'amount': 5, 'currency': 'NOK' }, creativeWidth: 200, @@ -321,6 +459,7 @@ describe('adnuntiusBidAdapter', function () { } ] }, + nativeMultiFormatResponseBody, { 'auId': '0000000000381535', 'targetId': '3a602680158a85', @@ -364,6 +503,42 @@ describe('adnuntiusBidAdapter', function () { 'html': '\u003Ca \'\u003E\u003C/script\u003E', } ] + }, + { + 'auId': '0000000000381535', + 'renderOption': 'DIV', + 'targetId': 'alt', + 'html': '\u003C!DOCTYPE html\u003E\n\u003C\u003E\n\u003C/html\u003E', + 'matchedAdCount': 1, + 'responseId': 'adn-rsp-1620340740', + 'ads': [ + { + 'destinationUrlEsc': '', + 'cpm': { + 'amount': 1000.0, + 'currency': 'NOK' + }, + creativeWidth: 200, + creativeHeight: 240, + 'bid': { + 'amount': 1.75, + 'currency': 'NOK' + }, + 'grossBid': { + 'amount': 1.75, + 'currency': 'NOK' + }, + 'netBid': { + 'amount': 1.75, + 'currency': 'NOK' + }, + 'cost': { + 'amount': 1.75, + 'currency': 'NOK' + }, + 'html': '\u003Ca \'\u003E\u003C/script\u003E', + } + ] } ], 'network': '1287', @@ -1322,8 +1497,8 @@ describe('adnuntiusBidAdapter', function () { } }); - const interpretedResponse = config.runWithBidder('adnuntius', () => spec.interpretResponse(multiFormatServerResponse, multiBidderInResponse)); - expect(interpretedResponse).to.have.lengthOf(2); + const interpretedResponse = config.runWithBidder('adnuntius', () => spec.interpretResponse(multiFormatServerResponse, multiBidAndFormatRequest)); + expect(interpretedResponse).to.have.lengthOf(3); let ad = multiFormatServerResponse.body.adUnits[0].ads[0]; expect(interpretedResponse[0].bidderCode).to.equal('adnuntius'); @@ -1336,7 +1511,7 @@ describe('adnuntiusBidAdapter', function () { expect(interpretedResponse[0].ad).to.equal(multiFormatServerResponse.body.adUnits[0].html); expect(interpretedResponse[0].ttl).to.equal(360); - ad = multiFormatServerResponse.body.adUnits[3].ads[0]; + ad = multiFormatServerResponse.body.adUnits[4].ads[0]; expect(interpretedResponse[1].bidderCode).to.equal('adnuntius'); expect(interpretedResponse[1].cpm).to.equal(ad.netBid.amount * 1000); expect(interpretedResponse[1].width).to.equal(Number(ad.creativeWidth)); @@ -1344,8 +1519,19 @@ describe('adnuntiusBidAdapter', function () { expect(interpretedResponse[1].creativeId).to.equal(ad.creativeId); expect(interpretedResponse[1].currency).to.equal(ad.bid.currency); expect(interpretedResponse[1].netRevenue).to.equal(false); - expect(interpretedResponse[1].ad).to.equal(multiFormatServerResponse.body.adUnits[3].html); + expect(interpretedResponse[1].ad).to.equal(multiFormatServerResponse.body.adUnits[4].html); expect(interpretedResponse[1].ttl).to.equal(360); + + ad = multiFormatServerResponse.body.adUnits[2].ads[0]; + expect(interpretedResponse[2].native).to.not.be.undefined; + expect(interpretedResponse[2].bidderCode).to.equal('adnuntius'); + expect(interpretedResponse[2].cpm).to.equal(ad.netBid.amount * 1000); + expect(interpretedResponse[2].width).to.equal(Number(ad.creativeWidth)); + expect(interpretedResponse[2].height).to.equal(Number(ad.creativeHeight)); + expect(interpretedResponse[2].creativeId).to.equal(ad.creativeId); + expect(interpretedResponse[2].currency).to.equal(ad.bid.currency); + expect(interpretedResponse[2].netRevenue).to.equal(false); + expect(interpretedResponse[2].ttl).to.equal(360); }); it('should not process valid response when passed alt bidder that is an adndeal', function () { @@ -1439,4 +1625,24 @@ describe('adnuntiusBidAdapter', function () { expect(interpretedResponse[1].dealCount).to.equal(0); }); }); + + describe('interpretNativeResponse', function () { + it('should return valid response when passed valid server response', function () { + const interpretedResponse = spec.interpretResponse(nativeResponse, nativeBidderRequest); + const ad = nativeResponse.body.adUnits[0].ads[0] + expect(interpretedResponse).to.have.lengthOf(1); + + expect(interpretedResponse[0].bidderCode).to.equal('adnuntius'); + expect(interpretedResponse[0].cpm).to.equal(ad.bid.amount * 1000); + 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('adnuntius.com'); + expect(interpretedResponse[0].vastXml).to.equal(ad.vastXml); + }); + }); }); diff --git a/test/spec/modules/adprimeBidAdapter_spec.js b/test/spec/modules/adprimeBidAdapter_spec.js index 4199145e80a..c185ef3c1ad 100644 --- a/test/spec/modules/adprimeBidAdapter_spec.js +++ b/test/spec/modules/adprimeBidAdapter_spec.js @@ -145,7 +145,11 @@ describe('AdprimeBidAdapter', function () { 'coppa', 'ccpa', 'gdpr', - 'tmax' + 'tmax', + 'bcat', + 'badv', + 'bapp', + 'battr' ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); diff --git a/test/spec/modules/ads_interactiveBidAdapter_spec.js b/test/spec/modules/ads_interactiveBidAdapter_spec.js index 273df230287..a0b8c67af93 100644 --- a/test/spec/modules/ads_interactiveBidAdapter_spec.js +++ b/test/spec/modules/ads_interactiveBidAdapter_spec.js @@ -146,7 +146,11 @@ describe('AdsInteractiveBidAdapter', function () { 'coppa', 'ccpa', 'gdpr', - 'tmax' + 'tmax', + 'bcat', + 'badv', + 'bapp', + 'battr' ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); diff --git a/test/spec/modules/axisBidAdapter_spec.js b/test/spec/modules/axisBidAdapter_spec.js index 2db6c907851..65f1b9cbd94 100644 --- a/test/spec/modules/axisBidAdapter_spec.js +++ b/test/spec/modules/axisBidAdapter_spec.js @@ -154,7 +154,11 @@ describe('AxisBidAdapter', function () { 'coppa', 'ccpa', 'gdpr', - 'tmax' + 'tmax', + 'bcat', + 'badv', + 'bapp', + 'battr' ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); diff --git a/test/spec/modules/beyondmediaBidAdapter_spec.js b/test/spec/modules/beyondmediaBidAdapter_spec.js index 9f31294dc54..b117b2c4972 100644 --- a/test/spec/modules/beyondmediaBidAdapter_spec.js +++ b/test/spec/modules/beyondmediaBidAdapter_spec.js @@ -145,7 +145,11 @@ describe('AndBeyondMediaBidAdapter', function () { 'coppa', 'ccpa', 'gdpr', - 'tmax' + 'tmax', + 'bcat', + 'badv', + 'bapp', + 'battr' ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); diff --git a/test/spec/modules/blueBidAdapter_spec.js b/test/spec/modules/blueBidAdapter_spec.js new file mode 100755 index 00000000000..f3f6f435f20 --- /dev/null +++ b/test/spec/modules/blueBidAdapter_spec.js @@ -0,0 +1,91 @@ +import { expect } from 'chai'; +import sinon from 'sinon'; +import { spec, storage } from 'modules/blueBidAdapter.js'; + +const BIDDER_CODE = 'blue'; +const ENDPOINT_URL = 'https://bidder-us-east-1.getblue.io/engine/?src=prebid'; +const GVLID = 620; +const COOKIE_NAME = 'ckid'; +const CURRENCY = 'USD'; + +describe('blueBidAdapter:', function () { + let sandbox; + + beforeEach(function () { + sandbox = sinon.createSandbox(); + }); + + afterEach(function () { + sandbox.restore(); + }); + + describe('isBidRequestValid:', function () { + it('should return true for valid bid requests', function () { + const validBid = { + params: { + placementId: '12345', + publisherId: '67890', + }, + }; + expect(spec.isBidRequestValid(validBid)).to.be.true; + }); + + it('should return false for invalid bid requests', function () { + const invalidBid = { + params: { + placementId: '12345', + }, + }; + expect(spec.isBidRequestValid(invalidBid)).to.be.false; + }); + }); + + describe('buildRequests:', function () { + let validBidRequests; + let bidderRequest; + + beforeEach(function () { + validBidRequests = [ + { + bidId: 'bid1', + params: { + placementId: '12345', + publisherId: '67890', + }, + getFloor: () => ({ currency: CURRENCY, floor: 1.5 }), + }, + ]; + + bidderRequest = { + refererInfo: { + page: 'https://example.com', + }, + }; + + sandbox.stub(storage, 'getDataFromLocalStorage').returns('testBuyerId'); + }); + + it('should build a valid OpenRTB request', function () { + const request = spec.buildRequests(validBidRequests, bidderRequest); + + expect(request.method).to.equal('POST'); + expect(request.url).to.equal(ENDPOINT_URL); + expect(request.options.contentType).to.equal('text/plain'); + + const ortbRequest = JSON.parse(request.data); + expect(ortbRequest.ext.gvlid).to.equal(GVLID); + expect(ortbRequest.user.ext.buyerid).to.equal('testBuyerId'); + expect(ortbRequest.imp[0].bidfloor).to.equal(1.5); + expect(ortbRequest.imp[0].bidfloorcur).to.equal(CURRENCY); + }); + + it('should omit bidfloor if getFloor is not implemented', function () { + validBidRequests[0].getFloor = undefined; + + const request = spec.buildRequests(validBidRequests, bidderRequest); + const ortbRequest = JSON.parse(request.data); + + expect(ortbRequest.imp[0].bidfloor).to.be.undefined; + }); + }); +}); diff --git a/test/spec/modules/boldwinBidAdapter_spec.js b/test/spec/modules/boldwinBidAdapter_spec.js index 820938dcec2..1435ba2c28b 100644 --- a/test/spec/modules/boldwinBidAdapter_spec.js +++ b/test/spec/modules/boldwinBidAdapter_spec.js @@ -145,7 +145,11 @@ describe('BoldwinBidAdapter', function () { 'coppa', 'ccpa', 'gdpr', - 'tmax' + 'tmax', + 'bcat', + 'badv', + 'bapp', + 'battr' ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); diff --git a/test/spec/modules/compassBidAdapter_spec.js b/test/spec/modules/compassBidAdapter_spec.js index d0cecc2272f..48be231d7d4 100644 --- a/test/spec/modules/compassBidAdapter_spec.js +++ b/test/spec/modules/compassBidAdapter_spec.js @@ -145,7 +145,11 @@ describe('CompassBidAdapter', function () { 'coppa', 'ccpa', 'gdpr', - 'tmax' + 'tmax', + 'bcat', + 'badv', + 'bapp', + 'battr' ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); diff --git a/test/spec/modules/consumableBidAdapter_spec.js b/test/spec/modules/consumableBidAdapter_spec.js index 073e889d172..4b0eefd7e8d 100644 --- a/test/spec/modules/consumableBidAdapter_spec.js +++ b/test/spec/modules/consumableBidAdapter_spec.js @@ -755,6 +755,48 @@ describe('Consumable BidAdapter', function () { expect(data.user.eids).to.deep.equal(bidderRequest.bidRequest[0].userIdAsEids); }); + it('Request should remove non-objects for userIdAsEids', function () { + bidderRequest.bidRequest[0].userId = {}; + bidderRequest.bidRequest[0].userId.tdid = 'TTD_ID'; + bidderRequest.bidRequest[0].userIdAsEids = [ + { + source: 'adserver.org', + uids: [ + { + id: 'TTD_ID_FROM_USER_ID_MODULE', + atype: 1, + ext: { + rtiPartner: 'TDID', + }, + }, + ], + }, + 'RANDOM_IDENTIFIER_STRING' + ]; + let scrubbedEids = [ + { + source: 'adserver.org', + uids: [ + { + id: 'TTD_ID_FROM_USER_ID_MODULE', + atype: 1, + ext: { + rtiPartner: 'TDID', + }, + }, + ], + }, + ]; + let request = spec.buildRequests( + bidderRequest.bidRequest, + BIDDER_REQUEST_1 + ); + let data = JSON.parse(request.data); + expect(data.user.eids).to.deep.equal( + scrubbedEids + ); + }); + it('Request should NOT have adsrvrOrgId params if userId is NOT object', function() { let request = spec.buildRequests(bidderRequest.bidRequest, BIDDER_REQUEST_1); let data = JSON.parse(request.data); diff --git a/test/spec/modules/contentexchangeBidAdapter_spec.js b/test/spec/modules/contentexchangeBidAdapter_spec.js index 913c9072dd5..c006aa520e1 100644 --- a/test/spec/modules/contentexchangeBidAdapter_spec.js +++ b/test/spec/modules/contentexchangeBidAdapter_spec.js @@ -145,7 +145,11 @@ describe('ContentexchangeBidAdapter', function () { 'coppa', 'ccpa', 'gdpr', - 'tmax' + 'tmax', + 'bcat', + 'badv', + 'bapp', + 'battr' ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); diff --git a/test/spec/modules/contxtfulRtdProvider_spec.js b/test/spec/modules/contxtfulRtdProvider_spec.js index e31ef554da0..68e38d63364 100644 --- a/test/spec/modules/contxtfulRtdProvider_spec.js +++ b/test/spec/modules/contxtfulRtdProvider_spec.js @@ -4,12 +4,15 @@ import { loadExternalScriptStub } from 'test/mocks/adloaderStub.js'; import { getStorageManager } from '../../../src/storageManager.js'; import { MODULE_TYPE_UID } from '../../../src/activities/modules.js'; import * as events from '../../../src/events'; +import * as utils from 'src/utils.js'; import Sinon from 'sinon'; +import { deepClone } from '../../../src/utils.js'; const MODULE_NAME = 'contxtful'; const VERSION = 'v1'; const CUSTOMER = 'CUSTOMER'; +const SM = 'SM'; const CONTXTFUL_CONNECTOR_ENDPOINT = `https://api.receptivity.io/${VERSION}/prebid/${CUSTOMER}/connector/rxConnector.js`; const RX_FROM_SESSION_STORAGE = { ReceptivityState: 'Receptive', test_info: 'rx_from_session_storage' }; @@ -62,6 +65,8 @@ describe('contxtfulRtdProvider', function () { eventsEmitSpy = sandbox.spy(events, ['emit']); + sandbox.stub(utils, 'generateUUID').returns(SM); + let tagId = CUSTOMER; sessionStorage.clear(); }); @@ -534,6 +539,7 @@ describe('contxtfulRtdProvider', function () { name: 'contxtful', ext: { rx: RX_FROM_API, + sm: SM, params: { ev: config.params?.version, ci: config.params?.customer, @@ -549,11 +555,40 @@ describe('contxtfulRtdProvider', function () { expect(data.name).to.deep.equal(expectedData.name); expect(data.ext.rx).to.deep.equal(expectedData.ext.rx); + expect(data.ext.sm).to.deep.equal(expectedData.ext.sm); expect(data.ext.params).to.deep.equal(expectedData.ext.params); done(); }, TIMEOUT); }); + it('does not change the sm', function (done) { + let config = buildInitConfig(VERSION, CUSTOMER); + contxtfulSubmodule.init(config); + window.dispatchEvent(RX_CONNECTOR_IS_READY_EVENT); + + let firstReqBidsConfigObj = { + ortb2Fragments: { + global: {}, + bidder: {}, + }, + }; + + let secondReqBidsConfigObj = deepClone(firstReqBidsConfigObj); + + setTimeout(() => { + const onDoneSpy = sinon.spy(); + contxtfulSubmodule.getBidRequestData(firstReqBidsConfigObj, onDoneSpy, config); + contxtfulSubmodule.getBidRequestData(secondReqBidsConfigObj, onDoneSpy, config); + + let firstData = firstReqBidsConfigObj.ortb2Fragments.bidder[config.params.bidders[0]].user.data[0]; + let secondData = secondReqBidsConfigObj.ortb2Fragments.bidder[config.params.bidders[0]].user.data[0]; + + expect(firstData.ext.sm).to.equal(secondData.ext.sm); + + done(); + }, TIMEOUT); + }); + describe('before rxApi is loaded', function () { const moveEventTheories = [ [ @@ -628,7 +663,7 @@ describe('contxtfulRtdProvider', function () { }); describe('after rxApi is loaded', function () { - it('does not add event', function (done) { + it('should add event', function (done) { let config = buildInitConfig(VERSION, CUSTOMER); contxtfulSubmodule.init(config); window.dispatchEvent(RX_CONNECTOR_IS_READY_EVENT); @@ -648,7 +683,7 @@ describe('contxtfulRtdProvider', function () { let events = ext.events; - expect(events).to.be.undefined; + expect(events).to.be.not.undefined; done(); }, TIMEOUT); }); diff --git a/test/spec/modules/copper6sspBidAdapter_spec.js b/test/spec/modules/copper6sspBidAdapter_spec.js index a97ef3ecbe5..4e62a416fb8 100644 --- a/test/spec/modules/copper6sspBidAdapter_spec.js +++ b/test/spec/modules/copper6sspBidAdapter_spec.js @@ -146,7 +146,11 @@ describe('Copper6SSPBidAdapter', function () { 'coppa', 'ccpa', 'gdpr', - 'tmax' + 'tmax', + 'bcat', + 'badv', + 'bapp', + 'battr' ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); diff --git a/test/spec/modules/e_volutionBidAdapter_spec.js b/test/spec/modules/e_volutionBidAdapter_spec.js index 4777b73aa3c..4a97988b128 100644 --- a/test/spec/modules/e_volutionBidAdapter_spec.js +++ b/test/spec/modules/e_volutionBidAdapter_spec.js @@ -145,7 +145,11 @@ describe('EvolutionTechBidAdapter', function () { 'coppa', 'ccpa', 'gdpr', - 'tmax' + 'tmax', + 'bcat', + 'badv', + 'bapp', + 'battr' ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); diff --git a/test/spec/modules/edge226BidAdapter_spec.js b/test/spec/modules/edge226BidAdapter_spec.js index e9e1c34b9cd..b1cf07d5bed 100644 --- a/test/spec/modules/edge226BidAdapter_spec.js +++ b/test/spec/modules/edge226BidAdapter_spec.js @@ -146,7 +146,11 @@ describe('Edge226BidAdapter', function () { 'coppa', 'ccpa', 'gdpr', - 'tmax' + 'tmax', + 'bcat', + 'badv', + 'bapp', + 'battr' ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); diff --git a/test/spec/modules/emtvBidAdapter_spec.js b/test/spec/modules/emtvBidAdapter_spec.js index ce81dc15ad4..e68a65a04d6 100644 --- a/test/spec/modules/emtvBidAdapter_spec.js +++ b/test/spec/modules/emtvBidAdapter_spec.js @@ -147,7 +147,11 @@ describe('EMTVBidAdapter', function () { 'coppa', 'ccpa', 'gdpr', - 'tmax' + 'tmax', + 'bcat', + 'badv', + 'bapp', + 'battr' ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); diff --git a/test/spec/modules/eplanningBidAdapter_spec.js b/test/spec/modules/eplanningBidAdapter_spec.js index a381d7644a1..9f46c57e422 100644 --- a/test/spec/modules/eplanningBidAdapter_spec.js +++ b/test/spec/modules/eplanningBidAdapter_spec.js @@ -53,6 +53,68 @@ describe('E-Planning Adapter', function () { 'adUnitCode': ADUNIT_CODE2, 'sizes': [[300, 250], [300, 600]], }; + const validBidWithSchain = { + 'bidder': 'eplanning', + 'bidId': BID_ID2, + 'params': { + 'ci': CI, + }, + 'adUnitCode': ADUNIT_CODE2, + 'sizes': [[300, 250], [300, 600]], + 'schain': { + ver: '1.0', + complete: 1, + nodes: [ + { + asi: 'directseller.com', + sid: '00001', + rid: 'BidRequest1', + hp: 1, + name: 'publisher', + domain: 'publisher.com' + } + ] + } + }; + const validBidWithSchainNodes = { + 'bidder': 'eplanning', + 'bidId': BID_ID2, + 'params': { + 'ci': CI, + }, + 'adUnitCode': ADUNIT_CODE2, + 'sizes': [[300, 250], [300, 600]], + 'schain': { + ver: '1.0', + complete: 1, + nodes: [ + { + asi: 'directseller.com', + sid: '00001', + rid: 'BidRequest1', + hp: 1, + name: 'publisher', + domain: 'publisher.com' + }, + { + asi: 'reseller.com', + sid: 'aaaaa', + rid: 'BidRequest2', + hp: 1, + name: 'publisher2', + domain: 'publisher2.com' + }, + { + asi: 'reseller3.com', + sid: 'aaaaab', + rid: 'BidRequest3', + hp: 1, + name: 'publisher3', + domain: 'publisher3.com' + } + ] + } + }; const ML = '1'; const validBidMappingLinear = { 'bidder': 'eplanning', @@ -727,7 +789,18 @@ describe('E-Planning Adapter', function () { expect(data.vctx).to.equal(2); expect(data.vv).to.equal(3); }); - + it('should return sch parameter', function () { + let bidRequests = [validBidWithSchain], schainExpected, schain; + schain = validBidWithSchain.schain; + schainExpected = schain.ver + ',' + schain.complete + '!' + schain.nodes.map(node => node.asi + ',' + node.sid + ',' + node.hp + ',' + node.rid + ',' + node.name + ',' + node.domain).join('!'); + const data = spec.buildRequests(bidRequests, bidderRequest).data; + expect(data.sch).to.deep.equal(schainExpected); + }); + it('should not return sch parameter', function () { + let bidRequests = [validBidWithSchainNodes]; + const data = spec.buildRequests(bidRequests, bidderRequest).data; + expect(data.sch).to.equal(undefined); + }); it('should return correct e parameter with linear mapping attribute with more than one adunit', function () { let bidRequestsML = [validBidMappingLinear]; const NEW_CODE = ADUNIT_CODE + '2'; diff --git a/test/spec/modules/globalsunBidAdapter_spec.js b/test/spec/modules/globalsunBidAdapter_spec.js index 0651b05894f..f8d6e2b710d 100644 --- a/test/spec/modules/globalsunBidAdapter_spec.js +++ b/test/spec/modules/globalsunBidAdapter_spec.js @@ -145,7 +145,11 @@ describe('GlobalsunBidAdapter', function () { 'coppa', 'ccpa', 'gdpr', - 'tmax' + 'tmax', + 'bcat', + 'badv', + 'bapp', + 'battr' ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); diff --git a/test/spec/modules/improvedigitalBidAdapter_spec.js b/test/spec/modules/improvedigitalBidAdapter_spec.js index 46e07bacbe4..f6266503297 100644 --- a/test/spec/modules/improvedigitalBidAdapter_spec.js +++ b/test/spec/modules/improvedigitalBidAdapter_spec.js @@ -1006,7 +1006,7 @@ describe('Improve Digital Adapter Tests', function () { width: 728, height: 90, ttl: 300, - ad: '\x3Cscript>window.__razr_config = {"prebid":{"bidRequest":{"bidder":"improvedigital","params":{"publisherId":1234,"placementId":1053688,"keyValues":{"testKey":["testValue"]},"size":{"w":800,"h":600}},"adUnitCode":"div-gpt-ad-1499748733608-0","transactionId":"f183e871-fbed-45f0-a427-c8a63c4c01eb","bidId":"33e9500b21129f","bidderRequestId":"2772c1e566670b","auctionId":"192721e36a0239","mediaTypes":{"banner":{"sizes":[[300,250],[160,600]]}},"sizes":[[300,250],[160,600]]},"bid":{"mediaType":"banner","ad":"
\\"\\"","requestId":"33e9500b21129f","seatBidId":"35adfe19-d6e9-46b9-9f7d-20da7026b965","cpm":1.9200543539802946,"currency":"EUR","width":728,"height":90,"creative_id":"510265","creativeId":"510265","ttl":300,"meta":{},"dealId":320896,"netRevenue":false}}};\x3C/script>  ', + ad: '  ', creativeId: '510265', dealId: 320896, netRevenue: false, @@ -1016,7 +1016,7 @@ describe('Improve Digital Adapter Tests', function () { const multiFormatExpectedBid = [ Object.assign({}, expectedBid[0], { - ad: '\x3Cscript>window.__razr_config = {"prebid":{"bidRequest":{"bidder":"improvedigital","params":{"publisherId":1234,"placementId":1053688},"adUnitCode":"div-gpt-ad-1499748733608-0","transactionId":"f183e871-fbed-45f0-a427-c8a63c4c01eb","bidId":"33e9500b21129f","bidderRequestId":"2772c1e566670b","auctionId":"192721e36a0239","mediaTypes":{"banner":{"sizes":[[300,250],[160,600]]},"native":{},"video":{"context":"outstream","playerSize":[640,480]}},"sizes":[[300,250],[160,600]],"nativeParams":{"body":{"required":true}}},"bid":{"mediaType":"banner","ad":"\\"\\"","requestId":"33e9500b21129f","seatBidId":"35adfe19-d6e9-46b9-9f7d-20da7026b965","cpm":1.9200543539802946,"currency":"EUR","width":728,"height":90,"creative_id":"510265","creativeId":"510265","ttl":300,"meta":{},"dealId":320896,"netRevenue":false}}};\x3C/script>  ', + ad: '  ', }) ]; @@ -1029,7 +1029,7 @@ describe('Improve Digital Adapter Tests', function () { width: 300, height: 250, ttl: 300, - ad: '\x3Cscript>window.__razr_config = {"prebid":{"bidRequest":{"bidder":"improvedigital","params":{"publisherId":1234,"placementId":1053688,"keyValues":{"testKey":["testValue"]},"size":{"w":800,"h":600}},"adUnitCode":"div-gpt-ad-1499748733608-0","transactionId":"f183e871-fbed-45f0-a427-c8a63c4c01eb","bidId":"33e9500b21129f","bidderRequestId":"2772c1e566670b","auctionId":"192721e36a0239","mediaTypes":{"banner":{"sizes":[[300,250],[160,600]]}},"sizes":[[300,250],[160,600]]},"bid":{"mediaType":"banner","ad":"\\"\\"","requestId":"33e9500b21129f","seatBidId":"83c8d524-0955-4d0c-b558-4c9f3600e09b","cpm":1.9200543539802946,"currency":"EUR","width":300,"height":250,"creative_id":"479163","creativeId":"479163","ttl":300,"meta":{},"dealId":320896,"netRevenue":false}}};\x3C/script>  ', + ad: '  ', creativeId: '479163', dealId: 320896, netRevenue: false, diff --git a/test/spec/modules/iqzoneBidAdapter_spec.js b/test/spec/modules/iqzoneBidAdapter_spec.js index f642a935f69..210d3a2d60b 100644 --- a/test/spec/modules/iqzoneBidAdapter_spec.js +++ b/test/spec/modules/iqzoneBidAdapter_spec.js @@ -145,7 +145,11 @@ describe('IQZoneBidAdapter', function () { 'coppa', 'ccpa', 'gdpr', - 'tmax' + 'tmax', + 'bcat', + 'badv', + 'bapp', + 'battr' ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); diff --git a/test/spec/modules/ixBidAdapter_spec.js b/test/spec/modules/ixBidAdapter_spec.js index 5f75e224b50..e0fc7d5affd 100644 --- a/test/spec/modules/ixBidAdapter_spec.js +++ b/test/spec/modules/ixBidAdapter_spec.js @@ -616,7 +616,8 @@ describe('IndexexchangeAdapter', function () { dspid: 50, pricelevel: '_100', advbrandid: 303325, - advbrand: 'OECTA' + advbrand: 'OECTA', + ibv: true }, adm: '' } @@ -3608,6 +3609,9 @@ describe('IndexexchangeAdapter', function () { brandId: 303325, brandName: 'OECTA', advertiserDomains: ['www.abc.com'] + }, + ext: { + ibv: true } } ]; @@ -3719,6 +3723,9 @@ describe('IndexexchangeAdapter', function () { brandId: 303325, brandName: 'OECTA', advertiserDomains: ['www.abc.com'] + }, + ext: { + ibv: true } } ]; @@ -3775,6 +3782,9 @@ describe('IndexexchangeAdapter', function () { brandId: 303325, brandName: 'OECTA', advertiserDomains: ['www.abc.com'] + }, + ext: { + ibv: true } } ]; diff --git a/test/spec/modules/kiviadsBidAdapter_spec.js b/test/spec/modules/kiviadsBidAdapter_spec.js index e2d23d06822..bd59a50e3ae 100644 --- a/test/spec/modules/kiviadsBidAdapter_spec.js +++ b/test/spec/modules/kiviadsBidAdapter_spec.js @@ -147,7 +147,11 @@ describe('KiviAdsBidAdapter', function () { 'coppa', 'ccpa', 'gdpr', - 'tmax' + 'tmax', + 'bcat', + 'badv', + 'bapp', + 'battr' ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); diff --git a/test/spec/modules/krushmediaBidAdapter_spec.js b/test/spec/modules/krushmediaBidAdapter_spec.js index 516b587edff..f6fe1b5661b 100644 --- a/test/spec/modules/krushmediaBidAdapter_spec.js +++ b/test/spec/modules/krushmediaBidAdapter_spec.js @@ -145,7 +145,11 @@ describe('KrushmediabBidAdapter', function () { 'coppa', 'ccpa', 'gdpr', - 'tmax' + 'tmax', + 'bcat', + 'badv', + 'bapp', + 'battr' ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); diff --git a/test/spec/modules/liveIntentExternalIdSystem_spec.js b/test/spec/modules/liveIntentExternalIdSystem_spec.js index 3e49eb5fc4b..b751f288b33 100644 --- a/test/spec/modules/liveIntentExternalIdSystem_spec.js +++ b/test/spec/modules/liveIntentExternalIdSystem_spec.js @@ -269,11 +269,6 @@ describe('LiveIntentExternalId', function() { expect(window.liQHub).to.have.length(1) // instead of 2 }); - it('should not return a decoded identifier when the unifiedId is not present in the value', function() { - const result = liveIntentExternalIdSubmodule.decode({ fireEventDelay: 1, additionalData: 'data' }); - expect(result).to.be.eql({}); - }); - it('should decode a unifiedId to lipbId and remove it', function() { const result = liveIntentExternalIdSubmodule.decode({ unifiedId: 'data' }, defaultConfigParams); expect(result).to.eql({'lipb': {'lipbid': 'data'}}); @@ -316,6 +311,11 @@ describe('LiveIntentExternalId', function() { }) }); + it('should decode values with the segments but no nonId', function() { + const result = liveIntentExternalIdSubmodule.decode({segments: ['tak']}, { params: defaultConfigParams }); + expect(result).to.eql({'lipb': {'segments': ['tak']}}); + }); + it('should decode a uid2 to a separate object when present', function() { const result = liveIntentExternalIdSubmodule.decode({ nonId: 'foo', uid2: 'bar' }, defaultConfigParams); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'uid2': 'bar'}, 'uid2': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); @@ -323,7 +323,7 @@ describe('LiveIntentExternalId', function() { it('should decode values with uid2 but no nonId', function() { const result = liveIntentExternalIdSubmodule.decode({ uid2: 'bar' }, defaultConfigParams); - expect(result).to.eql({'uid2': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); + expect(result).to.eql({'lipb': {'uid2': 'bar'}, 'uid2': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); it('should decode a bidswitch id to a separate object when present', function() { @@ -456,8 +456,18 @@ describe('LiveIntentExternalId', function() { expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'sonobi': 'bar'}, 'sonobi': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); + it('should decode a triplelift id to a separate object when present', function() { + const result = liveIntentExternalIdSubmodule.decode({ nonId: 'foo', triplelift: 'bar' }, defaultConfigParams); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'triplelift': 'bar'}, 'triplelift': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); + }); + it('should decode a vidazoo id to a separate object when present', function() { const result = liveIntentExternalIdSubmodule.decode({ nonId: 'foo', vidazoo: 'bar' }, defaultConfigParams); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'vidazoo': 'bar'}, 'vidazoo': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); + + it('should decode the segments as part of lipb', function() { + const result = liveIntentExternalIdSubmodule.decode({ nonId: 'foo', 'segments': ['bar'] }, { params: defaultConfigParams }); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'segments': ['bar']}}); + }); }); diff --git a/test/spec/modules/liveIntentIdMinimalSystem_spec.js b/test/spec/modules/liveIntentIdMinimalSystem_spec.js index a859b3e7995..810b6a23a20 100644 --- a/test/spec/modules/liveIntentIdMinimalSystem_spec.js +++ b/test/spec/modules/liveIntentIdMinimalSystem_spec.js @@ -49,11 +49,6 @@ describe('LiveIntentMinimalId', function() { expect(server.requests[0]).to.eql(undefined) }); - it('should not return a decoded identifier when the unifiedId is not present in the value', function() { - const result = liveIntentIdSubmodule.decode({ additionalData: 'data' }); - expect(result).to.be.eql({}); - }); - it('should initialize LiveConnect and send no data', function() { liveIntentIdSubmodule.getId(defaultConfigParams); liveIntentIdSubmodule.decode({}, defaultConfigParams); @@ -245,6 +240,11 @@ describe('LiveIntentMinimalId', function() { expect(callBackSpy.calledOnce).to.be.true; }); + it('should decode values with the segments but no nonId', function() { + const result = liveIntentIdSubmodule.decode({segments: ['tak']}, { params: defaultConfigParams }); + expect(result).to.eql({'lipb': {'segments': ['tak']}}); + }); + it('should decode a uid2 to a separate object when present', function() { const result = liveIntentIdSubmodule.decode({ nonId: 'foo', uid2: 'bar' }); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'uid2': 'bar'}, 'uid2': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); @@ -252,7 +252,7 @@ describe('LiveIntentMinimalId', function() { it('should decode values with uid2 but no nonId', function() { const result = liveIntentIdSubmodule.decode({ uid2: 'bar' }); - expect(result).to.eql({'uid2': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); + expect(result).to.eql({'lipb': {'uid2': 'bar'}, 'uid2': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); it('should decode a bidswitch id to a separate object when present', function() { @@ -324,8 +324,18 @@ describe('LiveIntentMinimalId', function() { expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'sonobi': 'bar'}, 'sonobi': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); + it('should decode a triplelift id to a separate object when present', function() { + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', triplelift: 'bar' }, defaultConfigParams); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'triplelift': 'bar'}, 'triplelift': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); + }); + it('should decode a vidazoo id to a separate object when present', function() { const result = liveIntentIdSubmodule.decode({ nonId: 'foo', vidazoo: 'bar' }); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'vidazoo': 'bar'}, 'vidazoo': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); + + it('should decode the segments as part of lipb', function() { + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', 'segments': ['bar'] }, { params: defaultConfigParams }); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'segments': ['bar']}}); + }); }); diff --git a/test/spec/modules/liveIntentIdSystem_spec.js b/test/spec/modules/liveIntentIdSystem_spec.js index 50f51bd3dc8..8f7a3465d88 100644 --- a/test/spec/modules/liveIntentIdSystem_spec.js +++ b/test/spec/modules/liveIntentIdSystem_spec.js @@ -188,11 +188,6 @@ describe('LiveIntentId', function() { }, 300); }); - it('should not return a decoded identifier when the unifiedId is not present in the value', function() { - const result = liveIntentIdSubmodule.decode({ params: { fireEventDelay: 1, additionalData: 'data' } }); - expect(result).to.be.eql({}); - }); - it('should fire an event when decode', function(done) { liveIntentIdSubmodule.decode({}, { params: defaultConfigParams }); setTimeout(() => { @@ -422,9 +417,14 @@ describe('LiveIntentId', function() { expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'uid2': 'bar'}, 'uid2': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); + it('should decode values with the segments but no nonId', function() { + const result = liveIntentIdSubmodule.decode({segments: ['tak']}, { params: defaultConfigParams }); + expect(result).to.eql({'lipb': {'segments': ['tak']}}); + }); + it('should decode values with uid2 but no nonId', function() { const result = liveIntentIdSubmodule.decode({ uid2: 'bar' }, { params: defaultConfigParams }); - expect(result).to.eql({'uid2': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); + expect(result).to.eql({'lipb': {'uid2': 'bar'}, 'uid2': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); it('should decode a bidswitch id to a separate object when present', function() { @@ -469,6 +469,11 @@ describe('LiveIntentId', function() { expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'tdid': 'bar'}, 'tdid': {'id': 'bar', 'ext': {'rtiPartner': 'TDID', 'provider': provider}}}); }); + it('should decode the segments as part of lipb', function() { + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', 'segments': ['bar'] }, { params: defaultConfigParams }); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'segments': ['bar']}}); + }); + it('should allow disabling nonId resolution', function() { let callBackSpy = sinon.spy(); let submoduleCallback = liveIntentIdSubmodule.getId({ params: { @@ -536,6 +541,11 @@ describe('LiveIntentId', function() { expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'sonobi': 'bar'}, 'sonobi': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); + it('should decode a triplelift id to a separate object when present', function() { + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', triplelift: 'bar' }, defaultConfigParams); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'triplelift': 'bar'}, 'triplelift': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); + }); + it('should decode a vidazoo id to a separate object when present', function() { const result = liveIntentIdSubmodule.decode({ nonId: 'foo', vidazoo: 'bar' }, { params: defaultConfigParams }); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'vidazoo': 'bar'}, 'vidazoo': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); @@ -882,6 +892,39 @@ describe('LiveIntentId', function() { }); }); + it('triplelift', function () { + const userId = { + triplelift: { 'id': 'sample_id' } + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'liveintent.triplelift.com', + uids: [{ + id: 'sample_id', + atype: 3 + }] + }); + }); + + it('triplelift with ext', function () { + const userId = { + triplelift: { 'id': 'sample_id', 'ext': { 'provider': 'some.provider.com' } } + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'liveintent.triplelift.com', + uids: [{ + id: 'sample_id', + atype: 3, + ext: { + provider: 'some.provider.com' + } + }] + }); + }); + it('vidazoo', function () { const userId = { vidazoo: { 'id': 'sample_id' } diff --git a/test/spec/modules/liveIntentRtdProvider_spec.js b/test/spec/modules/liveIntentRtdProvider_spec.js new file mode 100644 index 00000000000..d3c34830dd0 --- /dev/null +++ b/test/spec/modules/liveIntentRtdProvider_spec.js @@ -0,0 +1,116 @@ +import {liveIntentRtdSubmodule} from 'modules/liveIntentRtdProvider.js'; +import * as utils from 'src/utils.js'; +import { expect } from 'chai'; + +describe('LiveIntent Rtd Provider', function () { + const SUBMODULE_NAME = 'liveintent'; + + describe('submodule `init`', function () { + const config = { + name: SUBMODULE_NAME, + }; + it('init returns true when the subModuleName is defined', function () { + const value = liveIntentRtdSubmodule.init(config); + expect(value).to.equal(true); + }); + }) + + describe('submodule `onBidRequestEvent`', function () { + const bidRequestExample = { + bidderCode: 'appnexus', + auctionId: '8dbd7cb1-7f6d-4f84-946c-d0df4837234a', + bidderRequestId: '2a038c6820142b', + bids: [ + { + bidder: 'appnexus', + userId: { + lipb: { + segments: [ + 'asa_1231', + 'lalo_4311', + 'liurl_99123' + ] + } + } + } + ] + } + + it('exists', function () { + expect(liveIntentRtdSubmodule.onBidRequestEvent).to.be.a('function'); + }); + + it('undefined segments field does not change the ortb2', function() { + const bidRequest = { + bidderCode: 'appnexus', + auctionId: '8dbd7cb1-7f6d-4f84-946c-d0df4837234a', + bidderRequestId: '2a038c6820142b', + bids: [ + { + bidder: 'appnexus', + ortb2: {} + } + ] + } + + liveIntentRtdSubmodule.onBidRequestEvent(bidRequest); + const ortb2 = bidRequest.bids[0].ortb2; + expect(ortb2).to.deep.equal({}); + }); + + it('extracts segments and move them to the bidRequest.ortb2.user.data when the ortb2 is undefined', function() { + const bidRequest = utils.deepClone(bidRequestExample); + + liveIntentRtdSubmodule.onBidRequestEvent(bidRequest); + const ortb2 = bidRequest.bids[0].ortb2; + const expectedOrtb2 = {user: {data: [{name: 'liveintent.com', segment: [{id: 'asa_1231'}, {id: 'lalo_4311'}, {id: 'liurl_99123'}]}]}} + expect(ortb2).to.deep.equal(expectedOrtb2); + }); + + it('extracts segments and move them to the bidRequest.ortb2.user.data when user is undefined', function() { + bidRequestExample.bids[0].ortb2 = { source: {} } + const bidRequest = utils.deepClone(bidRequestExample); + + liveIntentRtdSubmodule.onBidRequestEvent(bidRequest); + const ortb2 = bidRequest.bids[0].ortb2; + const expectedOrtb2 = {source: {}, user: {data: [{name: 'liveintent.com', segment: [{id: 'asa_1231'}, {id: 'lalo_4311'}, {id: 'liurl_99123'}]}]}} + expect(ortb2).to.deep.equal(expectedOrtb2); + }); + + it('extracts segments and move them to the bidRequest.ortb2.user.data when data is undefined', function() { + bidRequestExample.bids[0].ortb2 = { + source: {}, + user: {} + } + const bidRequest = utils.deepClone(bidRequestExample); + + liveIntentRtdSubmodule.onBidRequestEvent(bidRequest); + const ortb2 = bidRequest.bids[0].ortb2; + const expectedOrtb2 = {source: {}, user: {data: [{name: 'liveintent.com', segment: [{id: 'asa_1231'}, {id: 'lalo_4311'}, {id: 'liurl_99123'}]}]}} + expect(ortb2).to.deep.equal(expectedOrtb2); + }); + + it('extracts segments and move them to the bidRequest.ortb2.user.data with the existing data', function() { + bidRequestExample.bids[0].ortb2 = { + source: {}, + user: { + data: [ + { + name: 'example.com', + segment: [ + { id: 'a_1231' }, + { id: 'b_4311' } + ] + } + ] + } + } + const bidRequest = utils.deepClone(bidRequestExample); + + liveIntentRtdSubmodule.onBidRequestEvent(bidRequest); + const ortb2 = bidRequest.bids[0].ortb2; + const expectedOrtb2 = {source: {}, user: {data: [{name: 'example.com', segment: [{id: 'a_1231'}, {id: 'b_4311'}]}, {name: 'liveintent.com', segment: [{id: 'asa_1231'}, {id: 'lalo_4311'}, {id: 'liurl_99123'}]}]}} + expect(ortb2).to.deep.equal(expectedOrtb2); + }); + }); +}); diff --git a/test/spec/modules/loyalBidAdapter_spec.js b/test/spec/modules/loyalBidAdapter_spec.js index fbfcdcd0742..1c9106e3be8 100644 --- a/test/spec/modules/loyalBidAdapter_spec.js +++ b/test/spec/modules/loyalBidAdapter_spec.js @@ -145,7 +145,11 @@ describe('LoyalBidAdapter', function () { 'coppa', 'ccpa', 'gdpr', - 'tmax' + 'tmax', + 'bcat', + 'badv', + 'bapp', + 'battr' ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); diff --git a/test/spec/modules/lunamediahbBidAdapter_spec.js b/test/spec/modules/lunamediahbBidAdapter_spec.js index 8ae3220504d..b715fb0d0c3 100644 --- a/test/spec/modules/lunamediahbBidAdapter_spec.js +++ b/test/spec/modules/lunamediahbBidAdapter_spec.js @@ -145,7 +145,11 @@ describe('LunamediaHBBidAdapter', function () { 'coppa', 'ccpa', 'gdpr', - 'tmax' + 'tmax', + 'bcat', + 'badv', + 'bapp', + 'battr' ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); diff --git a/test/spec/modules/magniteAnalyticsAdapter_spec.js b/test/spec/modules/magniteAnalyticsAdapter_spec.js index a5644ffc6eb..26a8f2ed313 100644 --- a/test/spec/modules/magniteAnalyticsAdapter_spec.js +++ b/test/spec/modules/magniteAnalyticsAdapter_spec.js @@ -759,6 +759,34 @@ describe('magnite analytics adapter', function () { expect(message.auctions[0].adUnits[0].bids[0].bidResponse.networkId).to.equal(test.expected); }); }); + + // meta mediatype handler things + [ + { input: undefined, expected: 'banner', hasOg: false }, + { input: 'banner', expected: 'banner', hasOg: false }, + { input: 'video', expected: 'video', hasOg: true } + ].forEach((test, index) => { + it(`should handle meta mediaType stuff correctly - #${index + 1}`, function () { + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + + let bidResponse = utils.deepClone(MOCK.BID_RESPONSE); + bidResponse.meta = { + mediaType: test.input + }; + + events.emit(BID_RESPONSE, bidResponse); + events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); + events.emit(AUCTION_END, MOCK.AUCTION_END); + events.emit(BID_WON, MOCK.BID_WON); + clock.tick(rubiConf.analyticsBatchTimeout + 1000); + + let message = JSON.parse(server.requests[0].requestBody); + expect(message.auctions[0].adUnits[0].bids[0].bidResponse.mediaType).to.equal(test.expected); + if (test.hasOg) expect(message.auctions[0].adUnits[0].bids[0].bidResponse.ogMediaType).to.equal('banner'); + else expect(message.auctions[0].adUnits[0].bids[0].bidResponse).to.not.haveOwnProperty('ogMediaType'); + }); + }); }); describe('with session handling', function () { diff --git a/test/spec/modules/mathildeadsBidAdapter_spec.js b/test/spec/modules/mathildeadsBidAdapter_spec.js index cdd1ffbc4bd..eb4318199af 100644 --- a/test/spec/modules/mathildeadsBidAdapter_spec.js +++ b/test/spec/modules/mathildeadsBidAdapter_spec.js @@ -145,7 +145,11 @@ describe('MathildeAdsBidAdapter', function () { 'coppa', 'ccpa', 'gdpr', - 'tmax' + 'tmax', + 'bcat', + 'badv', + 'bapp', + 'battr' ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); diff --git a/test/spec/modules/medianetAnalyticsAdapter_spec.js b/test/spec/modules/medianetAnalyticsAdapter_spec.js index 8a298acae80..85080f904e3 100644 --- a/test/spec/modules/medianetAnalyticsAdapter_spec.js +++ b/test/spec/modules/medianetAnalyticsAdapter_spec.js @@ -200,7 +200,7 @@ describe('Media.net Analytics Adapter', function() { medianetAnalytics.clearlogsQueue(); expect(noBidLog.mtype).to.have.ordered.members([encodeURIComponent('banner|native|video'), encodeURIComponent('banner|video|native')]); expect(noBidLog.szs).to.have.ordered.members([encodeURIComponent('300x250|1x1|640x480'), encodeURIComponent('300x250|1x1|640x480')]); - expect(noBidLog.vplcmtt).to.equal('instream'); + expect(noBidLog.vplcmtt).to.equal('1'); }); it('twin ad units should have correct sizes', function() { diff --git a/test/spec/modules/mgidXBidAdapter_spec.js b/test/spec/modules/mgidXBidAdapter_spec.js index 9b39b307dc4..f933a61ee55 100644 --- a/test/spec/modules/mgidXBidAdapter_spec.js +++ b/test/spec/modules/mgidXBidAdapter_spec.js @@ -157,7 +157,11 @@ describe('MGIDXBidAdapter', function () { 'coppa', 'ccpa', 'gdpr', - 'tmax' + 'tmax', + 'bcat', + 'badv', + 'bapp', + 'battr' ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); diff --git a/test/spec/modules/minutemediaBidAdapter_spec.js b/test/spec/modules/minutemediaBidAdapter_spec.js index f2bdd3b6c9d..4e5cd4883d3 100644 --- a/test/spec/modules/minutemediaBidAdapter_spec.js +++ b/test/spec/modules/minutemediaBidAdapter_spec.js @@ -2,8 +2,9 @@ import { expect } from 'chai'; import { spec } from 'modules/minutemediaBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; import { config } from 'src/config.js'; -import { BANNER, VIDEO } from '../../../src/mediaTypes.js'; +import {BANNER, NATIVE, VIDEO} from '../../../src/mediaTypes.js'; import * as utils from 'src/utils.js'; +import {decorateAdUnitsWithNativeParams} from '../../../src/native'; const ENDPOINT = 'https://hb.minutemedia-prebid.com/hb-mm-multi'; const TEST_ENDPOINT = 'https://hb.minutemedia-prebid.com/hb-multi-mm-test'; @@ -63,7 +64,6 @@ describe('minutemediaAdapter', function () { 'plcmt': 1 } }, - 'vastXml': '"..."' }, { 'bidder': spec.code, @@ -80,7 +80,59 @@ describe('minutemediaAdapter', function () { 'banner': { } }, - 'ad': '""' + }, + { + 'bidder': spec.code, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250]], + 'params': { + 'org': 'jdye8weeyirk00000001' + }, + 'bidId': '299ffc8cca0b87', + 'loop': 1, + 'bidderRequestId': '1144f487e563f9', + 'auctionId': 'bfc420c3-8577-4568-9766-a8a935fb620d', + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ 300, 250 ] + ] + }, + 'video': { + 'playerSize': [[640, 480]], + 'context': 'instream', + 'plcmt': 1 + }, + 'native': { + 'ortb': { + 'assets': [ + { + 'id': 1, + 'required': 1, + 'img': { + 'type': 3, + 'w': 300, + 'h': 200, + } + }, + { + 'id': 2, + 'required': 1, + 'title': { + 'len': 80 + } + }, + { + 'id': 3, + 'required': 1, + 'data': { + 'type': 1 + } + } + ] + } + }, + }, } ]; @@ -151,10 +203,10 @@ describe('minutemediaAdapter', function () { }); it('should send the correct mimes array', function () { - bidRequests[1].mediaTypes.banner.mimes = mimes; + bidRequests[0].mediaTypes.video.mimes = mimes; const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.bids[1].mimes).to.be.an('array'); - expect(request.data.bids[1].mimes).to.eql(['application/javascript', 'video/mp4', 'video/quicktime']); + expect(request.data.bids[0].mimes).to.be.an('array'); + expect(request.data.bids[0].mimes).to.eql(['application/javascript', 'video/mp4', 'video/quicktime']); }); it('should send the correct protocols array', function () { @@ -170,12 +222,21 @@ describe('minutemediaAdapter', function () { expect(request.data.bids[0].sizes).to.equal(bidRequests[0].sizes) expect(request.data.bids[1].sizes).to.be.an('array'); expect(request.data.bids[1].sizes).to.equal(bidRequests[1].sizes) + expect(request.data.bids[2].sizes).to.be.an('array'); + expect(request.data.bids[2].sizes).to.eql(bidRequests[2].sizes) + }); + + it('should send nativeOrtbRequest in native bid request', function () { + decorateAdUnitsWithNativeParams(bidRequests) + const request = spec.buildRequests(bidRequests, bidderRequest); + assert.deepEqual(request.data.bids[2].nativeOrtbRequest, bidRequests[2].mediaTypes.native.ortb) }); it('should send the correct media type', function () { const request = spec.buildRequests(bidRequests, bidderRequest); expect(request.data.bids[0].mediaType).to.equal(VIDEO) expect(request.data.bids[1].mediaType).to.equal(BANNER) + expect(request.data.bids[2].mediaType.split(',')).to.include.members([VIDEO, NATIVE, BANNER]) }); it('should respect syncEnabled option', function() { @@ -455,6 +516,28 @@ describe('minutemediaAdapter', function () { nurl: 'http://example.com/win/1234', adomain: ['abc.com'], mediaType: BANNER + }, + { + cpm: 12.5, + width: 300, + height: 200, + requestId: '21e12606d47ba7', + adomain: ['abc.com'], + creativeId: 'creative-id', + nurl: 'http://example.com/win/1234', + mediaType: NATIVE, + native: { + body: 'Advertise with Rise', + clickUrl: 'https://risecodes.com', + cta: 'Start now', + image: { + width: 300, + height: 200, + url: 'https://sdk.streamrail.com/media/rise-image.jpg' + }, + sponsoredBy: 'Rise', + title: 'Rise Ad Tech Solutions' + } }] }; @@ -494,10 +577,42 @@ describe('minutemediaAdapter', function () { ad: '""' }; + const expectedNativeResponse = { + requestId: '21e12606d47ba7', + cpm: 12.5, + currency: 'USD', + width: 300, + height: 200, + ttl: TTL, + creativeId: 'creative-id', + netRevenue: true, + nurl: 'http://example.com/win/1234', + mediaType: NATIVE, + meta: { + mediaType: NATIVE, + advertiserDomains: ['abc.com'] + }, + native: { + ortb: { + body: 'Advertise with Rise', + clickUrl: 'https://risecodes.com', + cta: 'Start now', + image: { + width: 300, + height: 200, + url: 'https://sdk.streamrail.com/media/rise-image.jpg', + }, + sponsoredBy: 'Rise', + title: 'Rise Ad Tech Solutions' + } + }, + }; + it('should get correct bid response', function () { const result = spec.interpretResponse({ body: response }); expect(result[0]).to.deep.equal(expectedVideoResponse); expect(result[1]).to.deep.equal(expectedBannerResponse); + expect(result[2]).to.deep.equal(expectedNativeResponse); }); it('video type should have vastXml key', function () { @@ -509,6 +624,11 @@ describe('minutemediaAdapter', function () { const result = spec.interpretResponse({ body: response }); expect(result[1].ad).to.equal(expectedBannerResponse.ad) }); + + it('native type should have native key', function () { + const result = spec.interpretResponse({ body: response }); + expect(result[2].native).to.eql(expectedNativeResponse.native) + }); }) describe('getUserSyncs', function() { diff --git a/test/spec/modules/missenaBidAdapter_spec.js b/test/spec/modules/missenaBidAdapter_spec.js index afd96091d84..2ca262dcf7f 100644 --- a/test/spec/modules/missenaBidAdapter_spec.js +++ b/test/spec/modules/missenaBidAdapter_spec.js @@ -23,7 +23,6 @@ describe('Missena Adapter', function () { const bid = { bidder: 'missena', bidId: bidId, - sizes: [[1, 1]], mediaTypes: { banner: { sizes: [[1, 1]] } }, ortb2: { device: { @@ -55,14 +54,14 @@ describe('Missena Adapter', function () { const bidWithoutFloor = { bidder: 'missena', bidId: bidId, - sizes: [[1, 1]], - mediaTypes: { banner: { sizes: [[1, 1]] } }, + mediaTypes: { banner: { sizes: [1, 1] } }, params: { apiKey: API_KEY, placement: 'sticky', formats: ['sticky-banner'], }, }; + const consentString = 'AAAAAAAAA=='; const bidderRequest = { @@ -178,6 +177,16 @@ describe('Missena Adapter', function () { expect(payload.screen.height).to.equal(screen.height); }); + it('should send size', function () { + expect(payload.sizes[0].width).to.equal(1); + expect(payload.sizes[0].height).to.equal(1); + }); + + it('should send single size', function () { + expect(payloadNoFloor.sizes[0].width).to.equal(1); + expect(payloadNoFloor.sizes[0].height).to.equal(1); + }); + getDataFromLocalStorageStub.restore(); getDataFromLocalStorageStub = sinon.stub( storage, diff --git a/test/spec/modules/mobfoxpbBidAdapter_spec.js b/test/spec/modules/mobfoxpbBidAdapter_spec.js index 1bf1ec12bc4..c926c2c9bfc 100644 --- a/test/spec/modules/mobfoxpbBidAdapter_spec.js +++ b/test/spec/modules/mobfoxpbBidAdapter_spec.js @@ -145,7 +145,11 @@ describe('MobfoxHBBidAdapter', function () { 'coppa', 'ccpa', 'gdpr', - 'tmax' + 'tmax', + 'bcat', + 'badv', + 'bapp', + 'battr' ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); diff --git a/test/spec/modules/oguryBidAdapter_spec.js b/test/spec/modules/oguryBidAdapter_spec.js index 99c16d0b1af..369540de668 100644 --- a/test/spec/modules/oguryBidAdapter_spec.js +++ b/test/spec/modules/oguryBidAdapter_spec.js @@ -1,14 +1,16 @@ import { expect } from 'chai'; +import sinon from 'sinon'; import { spec } from 'modules/oguryBidAdapter'; import * as utils from 'src/utils.js'; -import {server} from '../../mocks/xhr.js'; +import { server } from '../../mocks/xhr.js'; const BID_URL = 'https://mweb-hb.presage.io/api/header-bidding-request'; const TIMEOUT_URL = 'https://ms-ads-monitoring-events.presage.io/bid_timeout' -describe('OguryBidAdapter', function () { - let bidRequests; - let bidderRequest; +describe('OguryBidAdapter', () => { + let bidRequests, bidderRequestBase, ortb2; + + const currentLocation = 'https://mwtt.ogury.tech/advanced'; bidRequests = [ { @@ -48,20 +50,7 @@ describe('OguryBidAdapter', function () { return floorResult; }, transactionId: 'transactionId', - userId: { - pubcid: '2abb10e5-c4f6-4f70-9f45-2200e4487714' - }, - userIdAsEids: [ - { - source: 'pubcid.org', - uids: [ - { - id: '2abb10e5-c4f6-4f70-9f45-2200e4487714', - atype: 1 - } - ] - } - ] + userId: { pubcid: 'f5debac9-9a8e-4c08-9820-51e96b69f858' } }, { adUnitCode: 'adUnitCode2', @@ -81,57 +70,119 @@ describe('OguryBidAdapter', function () { }, ]; - bidderRequest = { + ortb2 = { + regs: { + gpp_sid: [7], + gpp: 'DBABLA~BAAAAAAAAQA.QA', + ext: { gdpr: 1 } + }, + site: { + domain: 'mwtt.ogury.tech', + publisher: { domain: 'ogury.tech', id: 'ca06d4199b92bf6808e5ce15b28c6d30' }, + page: currentLocation, + ref: 'https://google.com' + }, + user: { + ext: { + consent: 'CQJI3tqQJI3tqFzABBENBJFsAP_gAEPgAAqIg1NX_H__bW9r8Xr3aft0eY1P99j77sQxBhfJE-4FyLvW_JwXx2EwNA26tqIKmRIEu3ZBIQFlHJHURVigaogVryHsYkGcgTNKJ6BkgFMRI2dYCF5vmYtj-QKY5_p_d3fx2D-t_dv83dzzz8VHn3e5fmckcKCdQ58tDfn9bRKb-5IO9-78v4v09l_rk2_eTVn_pcvr7B-uft87_XU-9_fAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEQagCzDQqIA-yJCQi0DCKBACIKwgIoEAAAAJA0QEAJAwKdgYBLrCRACBFAAMEAIAAUZAAgAAEgAQiACQAoEAAEAgEAAAAAAgEADAwADgAtBAIAAQHQMUwoAFAsIEiMiIUwIQoEggJbKBBICgQVwgCLDAigERMFAAgCQAVgAAAsVgMASAlYkECWUG0AABAAgFFKFQik6MAQwJmy1U4om0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAACAA.YAAAAAAAAAAA', + eids: [ + { + source: 'pubcid.org', + uids: [{ 'id': 'f5debac9-9a8e-4c08-9820-51e96b69f858', 'atype': 1 }] + } + ] + } + }, + device: { + w: 412, + h: 915, + dnt: 0, + ua: 'Mozilla/5.0 (Linux; Android 13; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Mobile Safari/537.36', + language: 'en', + ext: { vpw: 412, vph: 915 }, + sua: { + source: 1, + platform: { brand: 'Android' }, + browsers: [{ brand: 'Google Chrome', version: ['131'] }], + mobile: 1 + } + } + }; + + bidderRequestBase = { + bids: bidRequests, bidderRequestId: 'mock-uuid', auctionId: bidRequests[0].auctionId, gdprConsent: {consentString: 'myConsentString', vendorData: {}, gdprApplies: true}, gppConsent: {gppString: 'myGppString', gppData: {}, applicableSections: [7], parsedSections: {}}, - timeout: 1000 + timeout: 1000, + ortb2 }; - describe('isBidRequestValid', function () { + describe('isBidRequestValid', () => { it('should validate correct bid', () => { let validBid = utils.deepClone(bidRequests[0]); let isValid = spec.isBidRequestValid(validBid); - expect(isValid).to.equal(true); + expect(isValid).to.true; }); - it('should not validate incorrect bid', () => { + it('should not validate when sizes is not defined', () => { let invalidBid = utils.deepClone(bidRequests[0]); delete invalidBid.sizes; delete invalidBid.mediaTypes; let isValid = spec.isBidRequestValid(invalidBid); - expect(isValid).to.equal(false); + expect(isValid).to.be.false; }); - it('should not validate bid if adunit is not present', () => { + it('should not validate bid when adunit is not defined', () => { let invalidBid = utils.deepClone(bidRequests[0]); delete invalidBid.params.adUnitId; let isValid = spec.isBidRequestValid(invalidBid); - expect(isValid).to.equal(false); + expect(isValid).to.to.be.false; }); - it('should not validate bid if assetKet is not present', () => { + it('should not validate bid when assetKey is not defined', () => { let invalidBid = utils.deepClone(bidRequests[0]); delete invalidBid.params.assetKey; let isValid = spec.isBidRequestValid(invalidBid); - expect(isValid).to.equal(false); + expect(isValid).to.be.false; }); - it('should validate bid if getFloor is not present', () => { - let invalidBid = utils.deepClone(bidRequests[1]); - delete invalidBid.getFloor; + it('should validate the request when only publisherId and adUnitCode is defined', () => { + const validBid = utils.deepClone(bidRequests[0]) + delete validBid.params.adUnitId + delete validBid.params.assetKey - let isValid = spec.isBidRequestValid(invalidBid); - expect(isValid).to.equal(true); + validBid.ortb2 = { site: { publisher: { id: 'publisherId' } } } + + expect(spec.isBidRequestValid(validBid)).to.be.true + }); + + it('should not validate the request when only publisherId is defined', () => { + const invalidBid = utils.deepClone(bidRequests[0]) + delete invalidBid.params.adUnitId + delete invalidBid.params.assetKey + delete invalidBid.adUnitCode + + invalidBid.ortb2 = { site: { publisher: { id: 'publisherId' } } } + + expect(spec.isBidRequestValid(invalidBid)).to.be.false + }); + + it('should not validate the request when only adUnitCode is defined', () => { + const invalidBid = utils.deepClone(bidRequests[0]) + delete invalidBid.params.adUnitId + delete invalidBid.params.assetKey + + expect(spec.isBidRequestValid(invalidBid)).to.be.false }); }); - describe('getUserSyncs', function() { + describe('getUserSyncs', () => { let syncOptions, gdprConsent, gppConsent; beforeEach(() => { @@ -612,17 +663,10 @@ describe('OguryBidAdapter', function () { }); }); - describe('buildRequests', function () { - const stubbedWidth = 200 - const stubbedHeight = 600 + describe('buildRequests', () => { + let windowTopStub; const stubbedCurrentTime = 1234567890 const stubbedDevicePixelRatio = 1 - const stubbedWidthMethod = sinon.stub(window.top.document.documentElement, 'clientWidth').get(function() { - return stubbedWidth; - }); - const stubbedHeightMethod = sinon.stub(window.top.document.documentElement, 'clientHeight').get(function() { - return stubbedHeight; - }); const stubbedCurrentTimeMethod = sinon.stub(document.timeline, 'currentTime').get(function() { return stubbedCurrentTime; }); @@ -632,496 +676,144 @@ describe('OguryBidAdapter', function () { }); const defaultTimeout = 1000; - const expectedRequestObject = { - id: 'mock-uuid', - at: 1, - tmax: defaultTimeout, - imp: [{ - id: bidRequests[0].bidId, - tagid: bidRequests[0].params.adUnitId, - bidfloor: 4, - banner: { - format: [{ - w: 300, - h: 250 - }] - }, - ext: { - ...bidRequests[0].params, - gpid: bidRequests[0].ortb2Imp.ext.gpid, - timeSpentOnPage: stubbedCurrentTime - } - }, { - id: bidRequests[1].bidId, - tagid: bidRequests[1].params.adUnitId, - banner: { - format: [{ - w: 600, - h: 500 - }] - }, - ext: { - ...bidRequests[1].params, - timeSpentOnPage: stubbedCurrentTime - } - }], - regs: { - ext: { - gdpr: 1, - gpp: 'myGppString', - gpp_sid: [7] - }, - }, - site: { - id: bidRequests[0].params.assetKey, - domain: window.location.hostname, - page: window.location.href - }, - user: { - ext: { - consent: bidderRequest.gdprConsent.consentString, - uids: { - pubcid: '2abb10e5-c4f6-4f70-9f45-2200e4487714' - }, - eids: [ - { - source: 'pubcid.org', - uids: [ - { - id: '2abb10e5-c4f6-4f70-9f45-2200e4487714', - atype: 1 - } - ] - } - ], - }, - }, - ext: { - prebidversion: '$prebid.version$', - adapterversion: '1.7.0' - }, - device: { - w: stubbedWidth, - h: stubbedHeight, - pxratio: stubbedDevicePixelRatio, - } - }; - - after(function() { - stubbedWidthMethod.restore(); - stubbedHeightMethod.restore(); - stubbedCurrentTimeMethod.restore(); - stubbedDevicePixelMethod.restore(); - }); - - it('sends bid request to ENDPOINT via POST', function () { - const validBidRequests = utils.deepClone(bidRequests) - - const request = spec.buildRequests(validBidRequests, bidderRequest); - expect(request.url).to.equal(BID_URL); - expect(request.method).to.equal('POST'); - }); - - it('timeSpentOnpage should be 0 if timeline is undefined', function () { - const stubbedTimelineMethod = sinon.stub(document, 'timeline').get(function() { - return undefined; - }); - const validBidRequests = utils.deepClone(bidRequests) - - const request = spec.buildRequests(validBidRequests, bidderRequest); - expect(request.data.imp[0].ext.timeSpentOnPage).to.equal(0); - 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) - - const request = spec.buildRequests(validBidRequests, bidderRequest); - expect(request.data).to.deep.equal(expectedRequestObject); - expect(request.data.regs.ext.gdpr).to.be.a('number'); - }); - - describe('getClientWidth', () => { - function testGetClientWidth(testGetClientSizeParams) { - const stubbedClientWidth = sinon.stub(window.top.document.documentElement, 'clientWidth').get(function() { - return testGetClientSizeParams.docClientSize - }) - - const stubbedInnerWidth = sinon.stub(window.top, 'innerWidth').get(function() { - return testGetClientSizeParams.innerSize - }) - - const stubbedOuterWidth = sinon.stub(window.top, 'outerWidth').get(function() { - return testGetClientSizeParams.outerSize - }) - - const stubbedWidth = sinon.stub(window.top.screen, 'width').get(function() { - return testGetClientSizeParams.screenSize - }) - - const validBidRequests = utils.deepClone(bidRequests) - - const request = spec.buildRequests(validBidRequests, bidderRequest); - expect(request.data.device.w).to.equal(testGetClientSizeParams.expectedSize); - - stubbedClientWidth.restore(); - stubbedInnerWidth.restore(); - stubbedOuterWidth.restore(); - stubbedWidth.restore(); - } - - it('should get documentElementClientWidth by default', () => { - testGetClientWidth({ - docClientSize: 22, - innerSize: 50, - outerSize: 45, - screenSize: 10, - expectedSize: 22, - }) - }) - - it('should get innerWidth as first fallback', () => { - testGetClientWidth({ - docClientSize: undefined, - innerSize: 700, - outerSize: 650, - screenSize: 10, - expectedSize: 700, - }) - }) - - it('should get outerWidth as second fallback', () => { - testGetClientWidth({ - docClientSize: undefined, - innerSize: undefined, - outerSize: 650, - screenSize: 10, - expectedSize: 650, - }) - }) - - it('should get screenWidth as last fallback', () => { - testGetClientWidth({ - docClientSize: undefined, - innerSize: undefined, - outerSize: undefined, - screenSize: 10, - expectedSize: 10, - }); + function assertImpObject(ortbBidRequest, bidRequest) { + expect(ortbBidRequest.secure).to.equal(1); + expect(ortbBidRequest.id).to.equal(bidRequest.bidId); + expect(ortbBidRequest.tagid).to.equal(bidRequest.adUnitCode); + expect(ortbBidRequest.banner).to.deep.equal({ + topframe: 0, + format: [{ + w: bidRequest.mediaTypes.banner.sizes[0][0], + h: bidRequest.mediaTypes.banner.sizes[0][1], + }] }); - it('should return 0 if all window width values are undefined', () => { - testGetClientWidth({ - docClientSize: undefined, - innerSize: undefined, - outerSize: undefined, - screenSize: undefined, - expectedSize: 0, - }); + expect(ortbBidRequest.ext).to.deep.equal({ + ...bidRequest.params, + gpid: bidRequest.ortb2Imp?.ext.gpid || bidRequest.adUnitCode, + timeSpentOnPage: stubbedCurrentTime }); - }); - - describe('getClientHeight', () => { - function testGetClientHeight(testGetClientSizeParams) { - const stubbedClientHeight = sinon.stub(window.top.document.documentElement, 'clientHeight').get(function() { - return testGetClientSizeParams.docClientSize - }) - - const stubbedInnerHeight = sinon.stub(window.top, 'innerHeight').get(function() { - return testGetClientSizeParams.innerSize - }) - - const stubbedOuterHeight = sinon.stub(window.top, 'outerHeight').get(function() { - return testGetClientSizeParams.outerSize - }) - - const stubbedHeight = sinon.stub(window.top.screen, 'height').get(function() { - return testGetClientSizeParams.screenSize - }) + } - const validBidRequests = utils.deepClone(bidRequests) + function assertRequestObject(dataRequest) { + expect(dataRequest.id).to.be.a('string'); + expect(dataRequest.tmax).to.equal(defaultTimeout); - const request = spec.buildRequests(validBidRequests, bidderRequest); - expect(request.data.device.h).to.equal(testGetClientSizeParams.expectedSize); + assertImpObject(dataRequest.imp[0], bidRequests[0]); + assertImpObject(dataRequest.imp[1], bidRequests[1]); - stubbedClientHeight.restore(); - stubbedInnerHeight.restore(); - stubbedOuterHeight.restore(); - stubbedHeight.restore(); - } - - it('should get documentElementClientHeight by default', () => { - testGetClientHeight({ - docClientSize: 420, - innerSize: 500, - outerSize: 480, - screenSize: 230, - expectedSize: 420, - }); + expect(dataRequest.imp[0].bidfloor).to.equal(4); + expect(dataRequest.regs).to.deep.equal(ortb2.regs); + expect(dataRequest.site).to.deep.equal({ + ...ortb2.site, + page: currentLocation, + id: bidRequests[0].params.assetKey }); - it('should get innerHeight as first fallback', () => { - testGetClientHeight({ - docClientSize: undefined, - innerSize: 500, - outerSize: 480, - screenSize: 230, - expectedSize: 500, - }); + expect(dataRequest.user).to.deep.equal({ + ext: { + ...ortb2.user.ext, + uids: bidRequests[0].userId + } }); - it('should get outerHeight as second fallback', () => { - testGetClientHeight({ - docClientSize: undefined, - innerSize: undefined, - outerSize: 480, - screenSize: 230, - expectedSize: 480, - }); + expect(dataRequest.ext).to.deep.equal({ + prebidversion: '$prebid.version$', + adapterversion: '2.0.0' }); - it('should get screenHeight as last fallback', () => { - testGetClientHeight({ - docClientSize: undefined, - innerSize: undefined, - outerSize: undefined, - screenSize: 230, - expectedSize: 230, - }); + expect(dataRequest.device).to.deep.equal({ + ...ortb2.device, + pxratio: stubbedDevicePixelRatio, }); - it('should return 0 if all window height values are undefined', () => { - testGetClientHeight({ - docClientSize: undefined, - innerSize: undefined, - outerSize: undefined, - screenSize: undefined, - expectedSize: 0, - }); - }); - }); + expect(dataRequest.regs.ext.gdpr).to.be.a('number'); + expect(dataRequest.device.pxratio).to.be.a('number'); + } - it('should not add gdpr infos if not present', () => { - const bidderRequestWithoutGdpr = { - ...bidderRequest, - gdprConsent: {}, - } - const expectedRequestObjectWithoutGdpr = { - ...expectedRequestObject, - regs: { - ext: { - gdpr: 0, - gpp: 'myGppString', - gpp_sid: [7] - }, - }, - user: { - ext: { - consent: '', - uids: expectedRequestObject.user.ext.uids, - eids: expectedRequestObject.user.ext.eids - }, - } - }; - - const validBidRequests = bidRequests - - const request = spec.buildRequests(validBidRequests, bidderRequestWithoutGdpr); - expect(request.data).to.deep.equal(expectedRequestObjectWithoutGdpr); - expect(request.data.regs.ext.gdpr).to.be.a('number'); + beforeEach(() => { + windowTopStub = sinon.stub(utils, 'getWindowTop'); + windowTopStub.returns({ location: { href: currentLocation } }); }); - it('should not add gpp infos if not present', () => { - const bidderRequestWithoutGpp = { - ...bidderRequest, - gppConsent: {}, - } - const expectedRequestObjectWithoutGpp = { - ...expectedRequestObject, - regs: { - ext: { - gdpr: 1 - }, - }, - user: { - ext: { - consent: 'myConsentString', - uids: expectedRequestObject.user.ext.uids, - eids: expectedRequestObject.user.ext.eids - }, - } - }; - - const validBidRequests = bidRequests - - const request = spec.buildRequests(validBidRequests, bidderRequestWithoutGpp); - expect(request.data).to.deep.equal(expectedRequestObjectWithoutGpp); + afterEach(() => { + windowTopStub.restore(); }); - it('should not add gdpr infos if gdprConsent is undefined', () => { - const bidderRequestWithoutGdpr = { - ...bidderRequest, - gdprConsent: undefined, - } - const expectedRequestObjectWithoutGdpr = { - ...expectedRequestObject, - regs: { - ext: { - gdpr: 0, - gpp: 'myGppString', - gpp_sid: [7] - }, - }, - user: { - ext: { - consent: '', - uids: expectedRequestObject.user.ext.uids, - eids: expectedRequestObject.user.ext.eids - }, - } - }; - - const validBidRequests = bidRequests - - const request = spec.buildRequests(validBidRequests, bidderRequestWithoutGdpr); - expect(request.data).to.deep.equal(expectedRequestObjectWithoutGdpr); - expect(request.data.regs.ext.gdpr).to.be.a('number'); + after(() => { + stubbedCurrentTimeMethod.restore(); + stubbedDevicePixelMethod.restore(); }); - it('should not add gpp infos if gppConsent is undefined', () => { - const bidderRequestWithoutGdpr = { - ...bidderRequest, - gppConsent: undefined, - } - const expectedRequestObjectWithoutGdpr = { - ...expectedRequestObject, - regs: { - ext: { - gdpr: 1, - }, - }, - }; - - const validBidRequests = bidRequests - - const request = spec.buildRequests(validBidRequests, bidderRequestWithoutGdpr); - expect(request.data).to.deep.equal(expectedRequestObjectWithoutGdpr); + it('sends bid request to ENDPOINT via POST', function () { + const request = spec.buildRequests(bidRequests, bidderRequestBase); + expect(request.url).to.equal(BID_URL); + expect(request.method).to.equal('POST'); }); - it('should not add tcString and turn off gdpr-applies if consentString and gdprApplies are undefined', () => { - const bidderRequestWithoutGdpr = { - ...bidderRequest, - gdprConsent: { consentString: undefined, gdprApplies: undefined }, - } - const expectedRequestObjectWithoutGdpr = { - ...expectedRequestObject, - regs: { - ext: { - gdpr: 0, - gpp: 'myGppString', - gpp_sid: [7] - }, - }, - user: { - ext: { - consent: '', - uids: expectedRequestObject.user.ext.uids, - eids: expectedRequestObject.user.ext.eids - }, - } - }; - - const validBidRequests = bidRequests + it('timeSpentOnpage should be 0 if timeline is undefined', function () { + const stubbedTimelineMethod = sinon.stub(document, 'timeline').get(function() { + return undefined; + }); - const request = spec.buildRequests(validBidRequests, bidderRequestWithoutGdpr); - expect(request.data).to.deep.equal(expectedRequestObjectWithoutGdpr); - expect(request.data.regs.ext.gdpr).to.be.a('number'); + const request = spec.buildRequests(bidRequests, bidderRequestBase); + expect(request.data.imp[0].ext.timeSpentOnPage).to.equal(0); + stubbedTimelineMethod.restore(); }); - it('should should not add uids infos if userId is undefined', () => { - const expectedRequestWithUndefinedUserId = { - ...expectedRequestObject, - user: { - ext: { - consent: expectedRequestObject.user.ext.consent, - eids: expectedRequestObject.user.ext.eids - } - } - }; + it('bid request object should be conform', function () { + const request = spec.buildRequests(bidRequests, bidderRequestBase); + assertRequestObject(request.data); + }); - const validBidRequests = utils.deepClone(bidRequests); - validBidRequests[0] = { - ...validBidRequests[0], - userId: undefined - }; + it('should not set site.id when assetKey is not present', () => { + const bidderRequest = utils.deepClone(bidderRequestBase); + const validBidRequests = bidderRequest.bids; + delete validBidRequests[0].params.assetKey; + delete validBidRequests[1].params.assetKey; const request = spec.buildRequests(validBidRequests, bidderRequest); - expect(request.data).to.deep.equal(expectedRequestWithUndefinedUserId); + expect(request.data.site.id).to.be.an('undefined'); }); - it('should should not add uids infos if userIdAsEids is undefined', () => { - const expectedRequestWithUndefinedUserIdAsEids = { - ...expectedRequestObject, - user: { - ext: { - consent: expectedRequestObject.user.ext.consent, - uids: expectedRequestObject.user.ext.uids - } - } - }; - - const validBidRequests = utils.deepClone(bidRequests); - validBidRequests[0] = { - ...validBidRequests[0], - userIdAsEids: undefined - }; + it('should not set user.ext.uids when userId is not present', () => { + const bidderRequest = utils.deepClone(bidderRequestBase); + const validBidRequests = bidderRequest.bids; + delete validBidRequests[0].userId; const request = spec.buildRequests(validBidRequests, bidderRequest); - expect(request.data).to.deep.equal(expectedRequestWithUndefinedUserIdAsEids); + expect(request.data.user.ext.uids).to.be.an('undefined'); }); it('should handle bidFloor undefined', () => { - const expectedRequestWithUndefinedFloor = { - ...expectedRequestObject - }; - - const validBidRequests = utils.deepClone(bidRequests); - validBidRequests[1] = { - ...validBidRequests[1], + const bidderRequest = utils.deepClone(bidderRequestBase); + const validBidRequests = bidderRequest.bids; + validBidRequests[0] = { + ...validBidRequests[0], getFloor: undefined }; const request = spec.buildRequests(validBidRequests, bidderRequest); - expect(request.data).to.deep.equal(expectedRequestWithUndefinedFloor); + expect(request.data.imp[0].bidfloor).to.be.an('undefined'); }); it('should handle bidFloor when is not function', () => { - const expectedRequestWithNotAFunctionFloor = { - ...expectedRequestObject - }; - - let validBidRequests = utils.deepClone(bidRequests); - validBidRequests[1] = { - ...validBidRequests[1], + const bidderRequest = utils.deepClone(bidderRequestBase); + const validBidRequests = bidderRequest.bids; + validBidRequests[0] = { + ...validBidRequests[0], getFloor: 'getFloor' }; const request = spec.buildRequests(validBidRequests, bidderRequest); - expect(request.data).to.deep.equal(expectedRequestWithNotAFunctionFloor); + expect(request.data.imp[0].bidfloor).to.be.an('undefined'); }); it('should handle bidFloor when currency is not USD', () => { - const expectedRequestWithUnsupportedFloorCurrency = utils.deepClone(expectedRequestObject) - delete expectedRequestWithUnsupportedFloorCurrency.imp[0].bidfloor; - let validBidRequests = utils.deepClone(bidRequests); + const bidderRequest = utils.deepClone(bidderRequestBase); + const validBidRequests = bidderRequest.bids; + validBidRequests[0] = { ...validBidRequests[0], getFloor: ({ size, currency, mediaType }) => { @@ -1131,30 +823,23 @@ describe('OguryBidAdapter', function () { } } }; + const request = spec.buildRequests(validBidRequests, bidderRequest); - expect(request.data).to.deep.equal(expectedRequestWithUnsupportedFloorCurrency); + expect(request.data.imp[0].bidfloor).to.be.an('undefined'); }); - it('should not add gpid if ortb2 undefined', () => { - const expectedRequestWithUndefinedGpid = utils.deepClone(expectedRequestObject) - - delete expectedRequestWithUndefinedGpid.imp[0].ext.gpid; - delete expectedRequestWithUndefinedGpid.imp[1].ext.gpid; - - const validBidRequests = utils.deepClone(bidRequests); + it('should use adUnitCode when gpid from ortb2 is undefined', () => { + const bidderRequest = utils.deepClone(bidderRequestBase); + const validBidRequests = bidderRequest.bids; delete validBidRequests[0].ortb2Imp.ext.gpid; const request = spec.buildRequests(validBidRequests, bidderRequest); - expect(request.data).to.deep.equal(expectedRequestWithUndefinedGpid); + expect(request.data.imp[0].ext.gpid).to.equal(bidRequests[0].adUnitCode); }); - it('should not add gpid if gpid undefined', () => { - const expectedRequestWithUndefinedGpid = utils.deepClone(expectedRequestObject) - - delete expectedRequestWithUndefinedGpid.imp[0].ext.gpid; - delete expectedRequestWithUndefinedGpid.imp[1].ext.gpid; - - const validBidRequests = utils.deepClone(bidRequests); + it('should use adUnitCode when gpid is not present in ortb2Imp object', () => { + const bidderRequest = utils.deepClone(bidderRequestBase); + const validBidRequests = bidderRequest.bids; validBidRequests[0] = { ...validBidRequests[0], ortb2Imp: { @@ -1163,18 +848,17 @@ describe('OguryBidAdapter', function () { }; const request = spec.buildRequests(validBidRequests, bidderRequest); - expect(request.data).to.deep.equal(expectedRequestWithUndefinedGpid); + expect(request.data.imp[0].ext.gpid).to.equal(bidRequests[0].adUnitCode); }); - it('should send gpid in bid request', function() { - const validBidRequests = utils.deepClone(bidRequests) + it('should set the actual site location in site.page when the ORTB object contains the referrer instead of the current location', () => { + const bidderRequest = utils.deepClone(bidderRequestBase); + bidderRequest.ortb2.site.page = 'https://google.com'; - const request = spec.buildRequests(validBidRequests, bidderRequest); - expect(request.data).to.deep.equal(expectedRequestObject); - expect(request.data.imp[0].ext.gpid).to.be.a('string'); - expect(request.data.imp[1].ext.gpid).to.be.undefined - }) - }) + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.site.page).to.equal(currentLocation); + }); + }); describe('interpretResponse', function () { let openRtbBidResponse = { @@ -1186,7 +870,7 @@ describe('OguryBidAdapter', function () { impid: 'bidId', price: 100, nurl: 'url', - adm: `test creative
cookies
`, + adm: `
cookies
`, adomain: ['renault.fr'], ext: { adcontent: 'sample_creative', @@ -1205,7 +889,7 @@ describe('OguryBidAdapter', function () { impid: 'bidId2', price: 150, nurl: 'url2', - adm: `test creative
cookies
`, + adm: `
cookies
`, adomain: ['peugeot.fr'], ext: { adcontent: 'sample_creative', @@ -1225,57 +909,27 @@ describe('OguryBidAdapter', function () { } }; - it('should correctly interpret bidResponse', () => { - let expectedInterpretedBidResponse = [{ - requestId: openRtbBidResponse.body.seatbid[0].bid[0].impid, - cpm: openRtbBidResponse.body.seatbid[0].bid[0].price, - currency: 'USD', - width: openRtbBidResponse.body.seatbid[0].bid[0].w, - height: openRtbBidResponse.body.seatbid[0].bid[0].h, - ad: openRtbBidResponse.body.seatbid[0].bid[0].adm, - ttl: 60, - ext: openRtbBidResponse.body.seatbid[0].bid[0].ext, - creativeId: openRtbBidResponse.body.seatbid[0].bid[0].id, - netRevenue: true, - meta: { - advertiserDomains: openRtbBidResponse.body.seatbid[0].bid[0].adomain - }, - nurl: openRtbBidResponse.body.seatbid[0].bid[0].nurl, - adapterVersion: '1.7.0', - prebidVersion: '$prebid.version$' - }, { - requestId: openRtbBidResponse.body.seatbid[0].bid[1].impid, - cpm: openRtbBidResponse.body.seatbid[0].bid[1].price, - currency: 'USD', - width: openRtbBidResponse.body.seatbid[0].bid[1].w, - height: openRtbBidResponse.body.seatbid[0].bid[1].h, - ad: openRtbBidResponse.body.seatbid[0].bid[1].adm, - ttl: 60, - ext: openRtbBidResponse.body.seatbid[0].bid[1].ext, - creativeId: openRtbBidResponse.body.seatbid[0].bid[1].id, - netRevenue: true, - meta: { - advertiserDomains: openRtbBidResponse.body.seatbid[0].bid[1].adomain - }, - nurl: openRtbBidResponse.body.seatbid[0].bid[1].nurl, - adapterVersion: '1.7.0', - prebidVersion: '$prebid.version$' - }] + function assertPrebidBidResponse(prebidBidResponse, ortbResponse) { + expect(prebidBidResponse.ttl).to.equal(60); + expect(prebidBidResponse.currency).to.equal('USD'); + expect(prebidBidResponse.netRevenue).to.be.true; + expect(prebidBidResponse.mediaType).to.equal('banner'); + expect(prebidBidResponse.requestId).to.equal(ortbResponse.impid); + expect(prebidBidResponse.cpm).to.equal(ortbResponse.price); + expect(prebidBidResponse.width).to.equal(ortbResponse.w); + expect(prebidBidResponse.height).to.equal(ortbResponse.h); + expect(prebidBidResponse.ad).to.contain(ortbResponse.adm); + expect(prebidBidResponse.meta.advertiserDomains).to.equal(ortbResponse.adomain); + expect(prebidBidResponse.seatBidId).to.equal(ortbResponse.id); + } - let request = spec.buildRequests(bidRequests, bidderRequest); - let result = spec.interpretResponse(openRtbBidResponse, request); + it('should correctly interpret bidResponse', () => { + const request = spec.buildRequests(bidRequests, bidderRequestBase); + const result = spec.interpretResponse(openRtbBidResponse, request); - expect(result).to.deep.equal(expectedInterpretedBidResponse) + assertPrebidBidResponse(result[0], openRtbBidResponse.body.seatbid[0].bid[0]) + assertPrebidBidResponse(result[1], openRtbBidResponse.body.seatbid[0].bid[1]) }); - - it('should return empty array if error during parsing', () => { - const wrongOpenRtbBidReponse = 'wrong data' - let request = spec.buildRequests(bidRequests, bidderRequest); - let result = spec.interpretResponse(wrongOpenRtbBidReponse, request); - - expect(result).to.be.instanceof(Array); - expect(result.length).to.equal(0) - }) }); describe('onBidWon', function() { diff --git a/test/spec/modules/omsBidAdapter_spec.js b/test/spec/modules/omsBidAdapter_spec.js index dc2d8ffebb6..54d744131cd 100644 --- a/test/spec/modules/omsBidAdapter_spec.js +++ b/test/spec/modules/omsBidAdapter_spec.js @@ -130,6 +130,45 @@ describe('omsBidAdapter', function () { expect(payload.imp[0].banner.format).to.deep.equal([{w: 300, h: 250}, {w: 300, h: 600}]); }); + it('sets the proper video object when ad unit media type is video', function () { + const bidRequests = [ + { + 'bidder': 'oms', + 'params': { + 'publisherId': 1234567 + }, + 'adUnitCode': 'adunit-code', + 'mediaTypes': { + 'video': { + 'context': 'instream', + 'playerSize': [640, 480] + } + }, + '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' + } + ] + }, + } + ] + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].video.context).to.equal('instream'); + expect(payload.imp[0].video.playerSize).to.deep.equal([640, 480]); + }); + it('accepts a single array as a size', function () { bidRequests[0].mediaTypes.banner.sizes = [300, 250]; const request = spec.buildRequests(bidRequests); @@ -379,6 +418,45 @@ describe('omsBidAdapter', function () { expect(result[0]).to.deep.equal(expectedResponse[0]); }); + it('should get the correct bid response for video bids', function () { + let expectedResponse = [{ + 'requestId': '283a9f4cd2415d', + 'cpm': 0.35743275, + 'width': 300, + 'height': 250, + 'creativeId': '376874781', + 'currency': 'USD', + 'netRevenue': true, + 'mediaType': 'video', + 'ad': `
`, + 'ttl': 300, + 'meta': { + 'advertiserDomains': ['example.com'] + } + }]; + const response = { + body: { + 'id': '37386aade21a71', + 'seatbid': [{ + 'bid': [{ + 'id': '376874781', + 'impid': '283a9f4cd2415d', + 'price': 0.35743275, + 'nurl': '', + 'adm': '', + 'w': 300, + 'h': 250, + 'adomain': ['example.com'], + 'mtype': 2 + }] + }] + } + }; + + 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', diff --git a/test/spec/modules/openwebBidAdapter_spec.js b/test/spec/modules/openwebBidAdapter_spec.js index f6f6ad22476..9ab8f608598 100644 --- a/test/spec/modules/openwebBidAdapter_spec.js +++ b/test/spec/modules/openwebBidAdapter_spec.js @@ -2,8 +2,9 @@ import { expect } from 'chai'; import { spec } from 'modules/openwebBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; import { config } from 'src/config.js'; -import { BANNER, VIDEO } from '../../../src/mediaTypes.js'; +import {BANNER, NATIVE, VIDEO} from '../../../src/mediaTypes.js'; import * as utils from 'src/utils.js'; +import {decorateAdUnitsWithNativeParams} from '../../../src/native'; const ENDPOINT = 'https://hb.openwebmp.com/hb-multi'; const TEST_ENDPOINT = 'https://hb.openwebmp.com/hb-multi-test'; @@ -74,7 +75,6 @@ describe('openwebAdapter', function () { 'plcmt': 1 } }, - 'vastXml': '"..."' }, { 'bidder': spec.code, @@ -91,7 +91,59 @@ describe('openwebAdapter', function () { 'banner': { } }, - 'ad': '""' + }, + { + 'bidder': spec.code, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250]], + 'params': { + 'org': 'jdye8weeyirk00000001' + }, + 'bidId': '299ffc8cca0b87', + 'loop': 1, + 'bidderRequestId': '1144f487e563f9', + 'auctionId': 'bfc420c3-8577-4568-9766-a8a935fb620d', + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ 300, 250 ] + ] + }, + 'video': { + 'playerSize': [[640, 480]], + 'context': 'instream', + 'plcmt': 1 + }, + 'native': { + 'ortb': { + 'assets': [ + { + 'id': 1, + 'required': 1, + 'img': { + 'type': 3, + 'w': 300, + 'h': 200, + } + }, + { + 'id': 2, + 'required': 1, + 'title': { + 'len': 80 + } + }, + { + 'id': 3, + 'required': 1, + 'data': { + 'type': 1 + } + } + ] + } + }, + }, } ]; @@ -160,10 +212,10 @@ describe('openwebAdapter', function () { }); it('should send the correct mimes array', function () { - bidRequests[1].mediaTypes.banner.mimes = mimes; + bidRequests[0].mediaTypes.video.mimes = mimes; const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.bids[1].mimes).to.be.an('array'); - expect(request.data.bids[1].mimes).to.eql(['application/javascript', 'video/mp4', 'video/quicktime']); + expect(request.data.bids[0].mimes).to.be.an('array'); + expect(request.data.bids[0].mimes).to.eql(['application/javascript', 'video/mp4', 'video/quicktime']); }); it('should send the correct protocols array', function () { @@ -179,12 +231,21 @@ describe('openwebAdapter', function () { expect(request.data.bids[0].sizes).to.equal(bidRequests[0].sizes) expect(request.data.bids[1].sizes).to.be.an('array'); expect(request.data.bids[1].sizes).to.equal(bidRequests[1].sizes) + expect(request.data.bids[2].sizes).to.be.an('array'); + expect(request.data.bids[2].sizes).to.eql(bidRequests[2].sizes) + }); + + it('should send nativeOrtbRequest in native bid request', function () { + decorateAdUnitsWithNativeParams(bidRequests) + const request = spec.buildRequests(bidRequests, bidderRequest); + assert.deepEqual(request.data.bids[2].nativeOrtbRequest, bidRequests[2].mediaTypes.native.ortb) }); it('should send the correct media type', function () { const request = spec.buildRequests(bidRequests, bidderRequest); expect(request.data.bids[0].mediaType).to.equal(VIDEO) expect(request.data.bids[1].mediaType).to.equal(BANNER) + expect(request.data.bids[2].mediaType.split(',')).to.include.members([VIDEO, NATIVE, BANNER]) }); it('should respect syncEnabled option', function() { @@ -464,6 +525,28 @@ describe('openwebAdapter', function () { nurl: 'http://example.com/win/1234', adomain: ['abc.com'], mediaType: BANNER + }, + { + cpm: 12.5, + width: 300, + height: 200, + requestId: '21e12606d47ba7', + adomain: ['abc.com'], + creativeId: 'creative-id-3', + nurl: 'http://example.com/win/1234', + mediaType: NATIVE, + native: { + body: 'Advertise with Rise', + clickUrl: 'https://risecodes.com', + cta: 'Start now', + image: { + width: 300, + height: 200, + url: 'https://sdk.streamrail.com/media/rise-image.jpg' + }, + sponsoredBy: 'Rise', + title: 'Rise Ad Tech Solutions' + } }] }; @@ -503,10 +586,42 @@ describe('openwebAdapter', function () { ad: '""' }; + const expectedNativeResponse = { + requestId: '21e12606d47ba7', + cpm: 12.5, + currency: 'USD', + width: 300, + height: 200, + ttl: TTL, + creativeId: 'creative-id-3', + netRevenue: true, + nurl: 'http://example.com/win/1234', + mediaType: NATIVE, + meta: { + mediaType: NATIVE, + advertiserDomains: ['abc.com'] + }, + native: { + ortb: { + body: 'Advertise with Rise', + clickUrl: 'https://risecodes.com', + cta: 'Start now', + image: { + width: 300, + height: 200, + url: 'https://sdk.streamrail.com/media/rise-image.jpg', + }, + sponsoredBy: 'Rise', + title: 'Rise Ad Tech Solutions' + } + }, + }; + it('should get correct bid response', function () { const result = spec.interpretResponse({ body: response }); expect(result[0]).to.deep.equal(expectedVideoResponse); expect(result[1]).to.deep.equal(expectedBannerResponse); + expect(result[2]).to.deep.equal(expectedNativeResponse); }); it('video type should have vastXml key', function () { @@ -518,6 +633,11 @@ describe('openwebAdapter', function () { const result = spec.interpretResponse({ body: response }); expect(result[1].ad).to.equal(expectedBannerResponse.ad) }); + + it('native type should have native key', function () { + const result = spec.interpretResponse({ body: response }); + expect(result[2].native).to.eql(expectedNativeResponse.native) + }); }) describe('getUserSyncs', function() { diff --git a/test/spec/modules/openxBidAdapter_spec.js b/test/spec/modules/openxBidAdapter_spec.js index 5dc60b25ab0..4f172fb1195 100644 --- a/test/spec/modules/openxBidAdapter_spec.js +++ b/test/spec/modules/openxBidAdapter_spec.js @@ -1,7 +1,7 @@ import {expect} from 'chai'; import {spec, REQUEST_URL, SYNC_URL, DEFAULT_PH} from 'modules/openxBidAdapter.js'; import {newBidder} from 'src/adapters/bidderFactory.js'; -import {BANNER, VIDEO} from 'src/mediaTypes.js'; +import {BANNER, NATIVE, VIDEO} from 'src/mediaTypes.js'; import {config} from 'src/config.js'; import * as utils from 'src/utils.js'; // load modules that register ORTB processors @@ -249,12 +249,119 @@ describe('OpenxRtbAdapter', function () { }); }); }); + + describe('when request is for a native ad', function () { + const nativeOrtbRequest = { + assets: [{ + id: 1, + required: 1, + title: { + len: 80 + } + }] + } + describe('and request config uses mediaTypes', () => { + const nativeBidWithMediaTypes = { + bidder: 'openx', + params: { + unit: '12345678', + delDomain: 'test-del-domain' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + native: { + ortb: { + ...nativeOrtbRequest + } + } + }, + nativeOrtbRequest, + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + transactionId: '4008d88a-8137-410b-aa35-fbfdabcb478e' + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(nativeBidWithMediaTypes)).to.equal(true); + }); + + it('should return false when required params are not passed', function () { + let invalidNativeBidWithMediaTypes = Object.assign({}, nativeBidWithMediaTypes); + invalidNativeBidWithMediaTypes.params = {}; + expect(spec.isBidRequestValid(invalidNativeBidWithMediaTypes)).to.equal(false); + }); + }); + + describe('and request config uses both delDomain and platform', () => { + const nativeBidWithDelDomainAndPlatform = { + bidder: 'openx', + params: { + unit: '12345678', + delDomain: 'test-del-domain', + platform: '1cabba9e-cafe-3665-beef-f00f00f00f00' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + native: { + ortb: { + ...nativeOrtbRequest + } + } + }, + nativeOrtbRequest, + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + transactionId: '4008d88a-8137-410b-aa35-fbfdabcb478e' + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(nativeBidWithDelDomainAndPlatform)).to.equal(true); + }); + + it('should return false when required params are not passed', function () { + let invalidNativeBidWithMediaTypes = Object.assign({}, nativeBidWithDelDomainAndPlatform); + invalidNativeBidWithMediaTypes.params = {}; + expect(spec.isBidRequestValid(invalidNativeBidWithMediaTypes)).to.equal(false); + }); + }); + }); }); describe('buildRequests()', function () { let bidRequestsWithMediaTypes; - let bidRequestsWithPlatform; let mockBidderRequest; + const nativeOrtbRequest = { + assets: [{ + id: 1, + required: 1, + title: { + len: 80 + } + }] + }; + const nativeBidRequest = { + bidder: 'openx', + params: { + unit: '33', + delDomain: 'test-del-domain', + platform: '1cabba9e-cafe-3665-beef-f00f00f00f00', + }, + adUnitCode: 'adunit-code', + mediaTypes: { + native: { + ortb: { + ...nativeOrtbRequest + } + } + }, + nativeOrtbRequest, + bidId: 'test-bid-id-3', + bidderRequestId: 'test-bid-request-3', + auctionId: 'test-auction-3', + transactionId: 'test-transactionId-3' + }; beforeEach(function () { mockBidderRequest = {refererInfo: {}}; @@ -302,16 +409,64 @@ describe('OpenxRtbAdapter', function () { }); context('common requests checks', function() { - it('should be able to handle multiformat requests', () => { + it('should be able to handle multiformat request - banner + video', () => { const multiformat = utils.deepClone(bidRequestsWithMediaTypes[0]); multiformat.mediaTypes.video = { context: 'outstream', playerSize: [640, 480] + }; + const requests = spec.buildRequests([multiformat], mockBidderRequest); + expect(requests).to.have.length(2); + expect(requests[0].data.imp).to.have.length(1); + expect(requests[0].data.imp[0].banner).to.exist; + expect(requests[0].data.imp[0].video).to.not.exist; + expect(requests[0].data.imp[0].native).to.not.exist; + expect(requests[1].data.imp).to.have.length(1); + expect(requests[1].data.imp[0].banner).to.not.exist; + expect(requests[1].data.imp[0].native).to.not.exist; + if (FEATURES.VIDEO) { + expect(requests[1].data.imp[0].video).to.exist; + } + }) + + it('should be able to handle multiformat request - banner + native', () => { + const multiformat = utils.deepClone(nativeBidRequest); + multiformat.mediaTypes.banner = { + sizes: [[300, 250], [300, 600]] + } + const requests = spec.buildRequests([multiformat], mockBidderRequest); + expect(requests).to.have.length(1); + expect(requests[0].data.imp).to.have.length(1); + expect(requests[0].data.imp[0].banner).to.exist; + expect(requests[0].data.imp[0].video).to.not.exist; + if (FEATURES.NATIVE) { + expect(requests[0].data.imp[0].native).to.exist; + } + }) + + it('should be able to handle multiformat request - banner + video + native', () => { + const multiformat = utils.deepClone(nativeBidRequest); + multiformat.mediaTypes.video = { + context: 'outstream', + playerSize: [640, 480] + }; + multiformat.mediaTypes.banner = { + sizes: [[300, 250]] } 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); + expect(requests).to.have.length(2); + expect(requests[0].data.imp).to.have.length(1); + expect(requests[0].data.imp[0].banner).to.exist; + expect(requests[0].data.imp[0].video).to.not.exist; + if (FEATURES.NATIVE) { + expect(requests[0].data.imp[0].native).to.exist; + } + expect(requests[1].data.imp).to.have.length(1); + expect(requests[1].data.imp[0].banner).to.not.exist; + expect(requests[1].data.imp[0].native).to.not.exist; + if (FEATURES.VIDEO) { + expect(requests[1].data.imp[0].video).to.exist; + } }) it('should send bid request to openx url via POST', function () { @@ -329,11 +484,11 @@ describe('OpenxRtbAdapter', function () { 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'; + bidRequestsWithMediaTypes[1].params.platform = '51ca3159-abc2-4035-8e00-fe26eaa09397'; 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(request[1].data.ext.platform).to.equal(bidRequestsWithMediaTypes[1].params.platform); }); it('should send openx adunit codes', function () { @@ -1300,7 +1455,7 @@ describe('OpenxRtbAdapter', function () { }); }); - context('when the response is a banner', function() { + context('when banner request and the response is a banner', function() { beforeEach(function () { bidRequestConfigs = [{ bidder: 'openx', @@ -1340,14 +1495,12 @@ describe('OpenxRtbAdapter', function () { }); it('should return the proper mediaType', function () { - it('should return a creativeId', function () { - expect(bid.mediaType).to.equal(Object.keys(bidRequestConfigs[0].mediaTypes)[0]); - }); + expect(bid.mediaType).to.equal(Object.keys(bidRequestConfigs[0].mediaTypes)[0]); }); }); if (FEATURES.VIDEO) { - context('when the response is a video', function() { + context('when video request and the response is a video', function() { beforeEach(function () { bidRequestConfigs = [{ bidder: 'openx', @@ -1377,7 +1530,7 @@ describe('OpenxRtbAdapter', function () { h: 480, crid: 'test-creative-id', dealid: 'test-deal-id', - adm: 'test-ad-markup', + adm: '', }] }], cur: 'AUS' @@ -1389,7 +1542,7 @@ describe('OpenxRtbAdapter', function () { expect(bid.mediaType).to.equal(Object.keys(bidRequestConfigs[0].mediaTypes)[0]); }); - it('should return the proper mediaType', function () { + it('should return the proper vastUrl', function () { const winUrl = 'https//my.win.url'; bidResponse.seatbid[0].bid[0].nurl = winUrl bid = spec.interpretResponse({body: bidResponse}, bidRequest).bids[0]; @@ -1397,6 +1550,293 @@ describe('OpenxRtbAdapter', function () { expect(bid.vastUrl).to.equal(winUrl); }); }); + + context('when multi-format request (banner + video) and the response is a video', function() { + beforeEach(function () { + bidRequestConfigs = [{ + bidder: 'openx', + params: { + unit: '12345678', + delDomain: 'test-del-domain' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + size: [[300, 600]] + }, + 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: 5, + adm: '', + }] + }], + cur: 'USD' + }; + }); + + it('should return video mediaType', function () { + bid = spec.interpretResponse({body: bidResponse}, bidRequest).bids[0]; + expect(bid.mediaType).to.equal(VIDEO); + }); + }); + + context('when multiple bid requests (banner + video) and the response is a banner', 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-1', + bidderRequestId: 'test-bidder-request-id-1', + auctionId: 'test-auction-id-1' + }, + { + bidder: 'openx', + params: { + unit: '12345678', + delDomain: 'test-del-domain' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + bidId: 'test-bid-id-2', + bidderRequestId: 'test-bidder-request-id-2', + auctionId: 'test-auction-id-2' + }]; + + bidRequest = spec.buildRequests(bidRequestConfigs, {refererInfo: {}})[0]; + + bidResponse = { + seatbid: [{ + bid: [{ + impid: 'test-bid-id-2', + price: 2, + adm: '', + }] + }], + cur: 'USD' + }; + }); + + it('should return banner mediaType', function () { + bid = spec.interpretResponse({body: bidResponse}, bidRequest).bids[0]; + expect(bid.mediaType).to.equal(BANNER); + }); + }); + } + + if (FEATURES.NATIVE) { + context('when native request and the response is a native', function() { + beforeEach(function () { + const nativeOrtbRequest = { + ver: '1.2', + assets: [{ + id: 1, + required: 1, + title: { + len: 80 + } + }] + }; + bidRequestConfigs = [{ + bidder: 'openx', + params: { + unit: '12345678', + delDomain: 'test-del-domain' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + native: { + ...nativeOrtbRequest + }, + }, + nativeOrtbRequest, + 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, + adm: '{"ver": "1.2", "assets": [{"id": 1, "required": 1,"title": {"text": "OpenX (Title)"}}], "link": {"url": "https://www.openx.com/"}, "eventtrackers":[{"event":1,"method":1,"url":"http://example.com/impression"}]}', + }] + }], + cur: 'AUS' + }; + }); + + it('should return the proper mediaType', function () { + bid = spec.interpretResponse({body: bidResponse}, bidRequest).bids[0]; + expect(bid.mediaType).to.equal(Object.keys(bidRequestConfigs[0].mediaTypes)[0]); + }); + + it('should return parsed adm JSON in native.ortb response field', function () { + bid = spec.interpretResponse({body: bidResponse}, bidRequest).bids[0]; + + expect(bid.native.ortb).to.deep.equal({ + ver: '1.2', + assets: [{ + id: 1, + required: 1, + title: {text: 'OpenX (Title)'} + }], + link: {url: 'https://www.openx.com/'}, + eventtrackers: [{ + event: 1, + method: 1, + url: 'http://example.com/impression' + }] + }); + }); + }); + + context('when multi-format request (banner + native) and the response is a banner', function() { + beforeEach(function () { + const nativeOrtbRequest = { + ver: '1.2', + assets: [{ + id: 1, + required: 1, + title: { + len: 80 + } + }] + }; + bidRequestConfigs = [{ + bidder: 'openx', + params: { + unit: '12345678', + delDomain: 'test-del-domain' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + }, + native: { + ...nativeOrtbRequest + }, + }, + nativeOrtbRequest, + 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, + adm: '', + }] + }], + cur: 'AUS' + }; + }); + + it('should return banner mediaType', function () { + bid = spec.interpretResponse({body: bidResponse}, bidRequest).bids[0]; + expect(bid.mediaType).to.equal(BANNER); + }); + }); + + context('when multiple bid requests (banner + native) and the response is a native', function() { + beforeEach(function () { + const nativeOrtbRequest = { + ver: '1.2', + assets: [{ + id: 1, + required: 1, + title: { + len: 80 + } + }] + }; + bidRequestConfigs = [{ + bidder: 'openx', + params: { + unit: '12345678', + delDomain: 'test-del-domain' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + native: { + ...nativeOrtbRequest + }, + }, + nativeOrtbRequest, + bidId: 'test-bid-id-1', + bidderRequestId: 'test-bidder-request-id-1', + auctionId: 'test-auction-id-1' + }, + { + bidder: 'openx', + params: { + unit: '12345678', + delDomain: 'test-del-domain' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + bidId: 'test-bid-id-2', + bidderRequestId: 'test-bidder-request-id-2', + auctionId: 'test-auction-id-2' + }]; + + bidRequest = spec.buildRequests(bidRequestConfigs, {refererInfo: {}})[0]; + + bidResponse = { + seatbid: [{ + bid: [{ + impid: 'test-bid-id-1', + price: 2, + adm: '{"ver": "1.2", "assets": [{"id": 1, "required": 1,"title": {"text": "OpenX (Title)"}}], "link": {"url": "https://www.openx.com/"}, "eventtrackers":[{"event":1,"method":1,"url":"http://example.com/impression"}]}', + }] + }], + cur: 'USD' + }; + }); + + it('should return native mediaType', function () { + bid = spec.interpretResponse({body: bidResponse}, bidRequest).bids[0]; + expect(bid.mediaType).to.equal(NATIVE); + }); + }); } context('when the response contains FLEDGE interest groups config', function() { diff --git a/test/spec/modules/orakiBidAdapter_spec.js b/test/spec/modules/orakiBidAdapter_spec.js index 9a7c777212b..1a00100cf61 100644 --- a/test/spec/modules/orakiBidAdapter_spec.js +++ b/test/spec/modules/orakiBidAdapter_spec.js @@ -142,7 +142,11 @@ describe('OrakiBidAdapter', function () { 'coppa', 'ccpa', 'gdpr', - 'tmax' + 'tmax', + 'bcat', + 'badv', + 'bapp', + 'battr' ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); diff --git a/test/spec/modules/pgamsspBidAdapter_spec.js b/test/spec/modules/pgamsspBidAdapter_spec.js index bdb837b70a4..ace20539459 100644 --- a/test/spec/modules/pgamsspBidAdapter_spec.js +++ b/test/spec/modules/pgamsspBidAdapter_spec.js @@ -145,7 +145,11 @@ describe('PGAMBidAdapter', function () { 'coppa', 'ccpa', 'gdpr', - 'tmax' + 'tmax', + 'bcat', + 'badv', + 'bapp', + 'battr' ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); diff --git a/test/spec/modules/playdigoBidAdapter_spec.js b/test/spec/modules/playdigoBidAdapter_spec.js index 591892beb8c..107e0ebc7aa 100644 --- a/test/spec/modules/playdigoBidAdapter_spec.js +++ b/test/spec/modules/playdigoBidAdapter_spec.js @@ -145,7 +145,11 @@ describe('PlaydigoBidAdapter', function () { 'coppa', 'ccpa', 'gdpr', - 'tmax' + 'tmax', + 'bcat', + 'badv', + 'bapp', + 'battr' ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index fc0f9ed7b68..fb1b62b9dc6 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -1372,65 +1372,97 @@ describe('S2S Adapter', function () { updateBid(BID_REQUESTS[1].bids[0]); adapter.callBids(s2sReq, BID_REQUESTS, addBidResponse, done, ajax); const pbsReq = JSON.parse(server.requests[server.requests.length - 1].requestBody); - expect(pbsReq.imp[0].bidfloor).to.be.undefined; - expect(pbsReq.imp[0].bidfloorcur).to.be.undefined; + [pbsReq.imp[0], pbsReq.imp[0].banner, pbsReq.imp[0].banner.format[0]].forEach(obj => { + expect(obj.bidfloor).to.be.undefined; + expect(obj.bidfloorcur).to.be.undefined; + }) }); }) Object.entries({ - 'is available': { - expectDesc: 'minimum after conversion', - expectedFloor: 10, - expectedCur: '0.1', - conversionFn: (amount, from, to) => { - from = parseFloat(from); - to = parseFloat(to); - return amount * from / to; - }, + 'imp level floors': { + target: 'imp.0' }, - 'is not available': { - expectDesc: 'absolute minimum', - expectedFloor: 1, - expectedCur: '10', - conversionFn: null + 'mediaType level floors': { + target: 'imp.0.banner.ext', + floorFilter: ({mediaType, size}) => size === '*' && mediaType !== '*' }, - 'is not working': { - expectDesc: 'absolute minimum', - expectedFloor: 1, - expectedCur: '10', - conversionFn: () => { - throw new Error(); - } + 'format level floors': { + target: 'imp.0.banner.format.0.ext', + floorFilter: ({size}) => size !== '*' } - }).forEach(([t, {expectDesc, expectedFloor, expectedCur, conversionFn}]) => { - describe(`and currency conversion ${t}`, () => { - let mockConvertCurrency; - const origConvertCurrency = getGlobal().convertCurrency; + }).forEach(([t, {target, floorFilter}]) => { + describe(t, () => { beforeEach(() => { - if (conversionFn) { - getGlobal().convertCurrency = mockConvertCurrency = sinon.stub().callsFake(conversionFn) - } else { - mockConvertCurrency = null; - delete getGlobal().convertCurrency; + if (floorFilter != null) { + BID_REQUESTS + .flatMap(req => req.bids) + .forEach(req => { + req.getFloor = ((orig) => (params) => { + if (floorFilter(params)) { + return orig(params); + } + })(req.getFloor); + }) } - }); + }) - afterEach(() => { - if (origConvertCurrency != null) { - getGlobal().convertCurrency = origConvertCurrency; - } else { - delete getGlobal().convertCurrency; + Object.entries({ + 'is available': { + expectDesc: 'minimum after conversion', + expectedFloor: 10, + expectedCur: '0.1', + conversionFn: (amount, from, to) => { + from = parseFloat(from); + to = parseFloat(to); + return amount * from / to; + }, + }, + 'is not available': { + expectDesc: 'absolute minimum', + expectedFloor: 1, + expectedCur: '10', + conversionFn: null + }, + 'is not working': { + expectDesc: 'absolute minimum', + expectedFloor: 1, + expectedCur: '10', + conversionFn: () => { + throw new Error(); + } } - }); + }).forEach(([t, {expectDesc, expectedFloor, expectedCur, conversionFn}]) => { + describe(`and currency conversion ${t}`, () => { + let mockConvertCurrency; + const origConvertCurrency = getGlobal().convertCurrency; + beforeEach(() => { + if (conversionFn) { + getGlobal().convertCurrency = mockConvertCurrency = sinon.stub().callsFake(conversionFn) + } else { + mockConvertCurrency = null; + delete getGlobal().convertCurrency; + } + }); - it(`should pick the ${expectDesc}`, () => { - adapter.callBids(s2sReq, BID_REQUESTS, addBidResponse, done, ajax); - const pbsReq = JSON.parse(server.requests[server.requests.length - 1].requestBody); - expect(pbsReq.imp[0].bidfloor).to.eql(expectedFloor); - expect(pbsReq.imp[0].bidfloorcur).to.eql(expectedCur); + afterEach(() => { + if (origConvertCurrency != null) { + getGlobal().convertCurrency = origConvertCurrency; + } else { + delete getGlobal().convertCurrency; + } + }); + + it(`should pick the ${expectDesc}`, () => { + adapter.callBids(s2sReq, BID_REQUESTS, addBidResponse, done, ajax); + const pbsReq = JSON.parse(server.requests[server.requests.length - 1].requestBody); + expect(deepAccess(pbsReq, `${target}.bidfloor`)).to.eql(expectedFloor); + expect(deepAccess(pbsReq, `${target}.bidfloorcur`)).to.eql(expectedCur) + }); + }); }); - }); - }); + }) + }) }); }); diff --git a/test/spec/modules/priceFloors_spec.js b/test/spec/modules/priceFloors_spec.js index 41f1609f74f..8cfa6aabb12 100644 --- a/test/spec/modules/priceFloors_spec.js +++ b/test/spec/modules/priceFloors_spec.js @@ -1865,159 +1865,194 @@ 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; - }, - } - }; + describe('inverse adjustment', () => { + beforeEach(() => { + _floorDataForAuction[bidRequest.auctionId] = utils.deepClone(basicFloorConfig); + _floorDataForAuction[bidRequest.auctionId].data.values = { '*': 1.0 }; + }); - _floorDataForAuction[bidRequest.auctionId] = utils.deepClone(basicFloorConfig); + 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].data.values = { '*': 1.0 }; + // start with banner as only mediaType + bidRequest.mediaTypes = { banner: { sizes: [[300, 250]] } }; + let appnexusBid = { + ...bidRequest, + bidder: 'appnexus', + }; - // 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 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'); - // 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 + }); - // appnexus just using banner should be same - expect(appnexusBid.getFloor()).to.deep.equal({ - currency: 'USD', - floor: 1.3334 - }); + expect(functionUsed).to.equal('Appnexus Inverse'); - 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 + }); - // 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'); - 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 + }); - // 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'); }); - 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; - }, + 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 } - }; - - _floorDataForAuction[bidRequest.auctionId] = utils.deepClone(basicFloorConfig); + 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].data.values = { '*': 1.0 }; + // banner only should be 2 + bidRequest.mediaTypes = { banner: {} }; + expect(bidRequest.getFloor()).to.deep.equal({ + currency: 'USD', + floor: 2.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 + }); - // 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 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 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 + }); - // 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 + }); - // 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 + }); }); - // 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 - }); + Object.entries({ + 'both unspecified': { + getFloorParams: undefined, + inverseParams: {} + }, + 'only mediaType': { + getFloorParams: {mediaType: 'video'}, + inverseParams: {mediaType: 'video'} + }, + 'only size': { + getFloorParams: {mediaType: '*', size: [1, 2]}, + inverseParams: {size: [1, 2]} + }, + 'both': { + getFloorParams: {mediaType: 'banner', size: [1, 2]}, + inverseParams: {mediaType: 'banner', size: [1, 2]} + } + }).forEach(([t, {getFloorParams, inverseParams}]) => { + it(`should pass inverseFloorAdjustment mediatype and size (${t})`, () => { + getGlobal().bidderSettings = { + standard: { + inverseBidAdjustment: sinon.stub() + } + } + bidRequest.mediaTypes = { + video: {}, + native: {}, + banner: { + sizes: [[100, 200], [200, 300]] + } + } + bidRequest.getFloor(getFloorParams); + sinon.assert.calledWith(getGlobal().bidderSettings.standard.inverseBidAdjustment, 1, bidRequest, inverseParams); + }); + }) }); it('should use standard cpmAdjustment if no bidder cpmAdjustment', function () { diff --git a/test/spec/modules/pubCircleBidAdapter_spec.js b/test/spec/modules/pubCircleBidAdapter_spec.js index 083031101f1..f02aab9d4d6 100644 --- a/test/spec/modules/pubCircleBidAdapter_spec.js +++ b/test/spec/modules/pubCircleBidAdapter_spec.js @@ -145,7 +145,11 @@ describe('PubCircleBidAdapter', function () { 'coppa', 'ccpa', 'gdpr', - 'tmax' + 'tmax', + 'bcat', + 'badv', + 'bapp', + 'battr' ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); diff --git a/test/spec/modules/pubmaticAnalyticsAdapter_spec.js b/test/spec/modules/pubmaticAnalyticsAdapter_spec.js index f77b167a3e9..263d0416897 100755 --- a/test/spec/modules/pubmaticAnalyticsAdapter_spec.js +++ b/test/spec/modules/pubmaticAnalyticsAdapter_spec.js @@ -571,13 +571,13 @@ describe('pubmatic analytics adapter', function () { expect(data.pbv).to.equal('$prebid.version$' || '-1'); expect(data.s).to.be.an('array'); expect(data.s.length).to.equal(2); + expect(data.ffs).to.equal(1); + expect(data.fsrc).to.equal(2); + expect(data.fp).to.equal('pubmatic'); // slot 1 expect(data.s[0].sn).to.equal('/19968336/header-bid-tag-0'); expect(data.s[0].fskp).to.equal(0); expect(data.s[0].sid).not.to.be.undefined; - expect(data.s[0].ffs).to.equal(1); - expect(data.s[0].fsrc).to.equal(2); - expect(data.s[0].fp).to.equal('pubmatic'); expect(data.s[0].sz).to.deep.equal(['640x480']); expect(data.s[0].ps).to.be.an('array'); expect(data.s[0].au).to.equal('/19968336/header-bid-tag-0'); @@ -609,9 +609,6 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].sn).to.equal('/19968336/header-bid-tag-1'); expect(data.s[1].fskp).to.equal(0); expect(data.s[1].sid).not.to.be.undefined; - expect(data.s[1].ffs).to.equal(1); - expect(data.s[1].fsrc).to.equal(2); - expect(data.s[1].fp).to.equal('pubmatic'); expect(data.s[1].sz).to.deep.equal(['1000x300', '970x250', '728x90']); expect(data.s[1].ps).to.be.an('array'); expect(data.s[1].ps.length).to.equal(1); @@ -789,14 +786,14 @@ describe('pubmatic analytics adapter', function () { expect(data.pbv).to.equal('$prebid.version$' || '-1'); expect(data.s).to.be.an('array'); expect(data.s.length).to.equal(2); + expect(data.ffs).to.equal(1); + expect(data.fsrc).to.equal(2); + expect(data.fp).to.equal('pubmatic'); expect(data.tgid).to.equal(0); // slot 1 expect(data.s[0].sn).to.equal('/19968336/header-bid-tag-0'); expect(data.s[0].fskp).to.equal(0); expect(data.s[0].sid).not.to.be.undefined; - expect(data.s[0].ffs).to.equal(1); - expect(data.s[0].fsrc).to.equal(2); - expect(data.s[0].fp).to.equal('pubmatic'); expect(data.s[0].sz).to.deep.equal(['640x480']); expect(data.s[0].ps).to.be.an('array'); expect(data.s[0].au).to.equal('/19968336/header-bid-tag-0'); @@ -915,15 +912,13 @@ describe('pubmatic analytics adapter', function () { let request = requests[1]; // logger is executed late, trackers execute first let data = getLoggerJsonFromRequest(request.requestBody); expect(data.tgid).to.equal(0);// test group id should be an INT between 0-15 else set to 0 + expect(data.ffs).to.equal(1); + expect(data.fsrc).to.equal(2); + expect(data.fp).to.equal('pubmatic'); + expect(data.s[1].sn).to.equal('/19968336/header-bid-tag-1'); expect(data.s[1].fskp).to.equal(0); - expect(data.s[1].sid).not.to.be.undefined; - - expect(data.s[1].ffs).to.equal(1); - expect(data.s[1].fsrc).to.equal(2); - expect(data.s[1].fp).to.equal('pubmatic'); - expect(data.s[1].sz).to.deep.equal(['1000x300', '970x250', '728x90']); expect(data.s[1].ps).to.be.an('array'); expect(data.s[1].ps.length).to.equal(1); @@ -1004,12 +999,13 @@ describe('pubmatic analytics adapter', function () { expect(requests.length).to.equal(1); // 1 logger and 0 win-tracker let request = requests[0]; let data = getLoggerJsonFromRequest(request.requestBody); + expect(data.ffs).to.equal(1); + expect(data.fsrc).to.equal(2); + expect(data.fp).to.equal('pubmatic'); + expect(data.s[1].sn).to.equal('/19968336/header-bid-tag-1'); expect(data.s[1].fskp).to.equal(0); expect(data.s[1].sid).not.to.be.undefined; - expect(data.s[1].ffs).to.equal(1); - expect(data.s[1].fsrc).to.equal(2); - expect(data.s[1].fp).to.equal('pubmatic'); expect(data.s[1].sz).to.deep.equal(['1000x300', '970x250', '728x90']); expect(data.s[1].ps).to.be.an('array'); expect(data.s[1].ps.length).to.equal(1); @@ -1119,12 +1115,12 @@ describe('pubmatic analytics adapter', function () { let request = requests[2]; // logger is executed late, trackers execute first expect(request.url).to.equal('https://t.pubmatic.com/wl?pubid=9999'); let data = getLoggerJsonFromRequest(request.requestBody); + expect(data.ffs).to.equal(1); + expect(data.fsrc).to.equal(2); + expect(data.fp).to.equal('pubmatic'); expect(data.s[1].sn).to.equal('/19968336/header-bid-tag-1'); expect(data.s[1].fskp).to.equal(0); expect(data.s[1].sid).not.to.be.undefined; - expect(data.s[1].ffs).to.equal(1); - expect(data.s[1].fsrc).to.equal(2); - expect(data.s[1].fp).to.equal('pubmatic'); expect(data.s[1].sz).to.deep.equal(['1000x300', '970x250', '728x90']); expect(data.s[1].ps).to.be.an('array'); expect(data.s[1].ps.length).to.equal(1); @@ -1239,12 +1235,12 @@ describe('pubmatic analytics adapter', function () { let request = requests[2]; // logger is executed late, trackers execute first expect(request.url).to.equal('https://t.pubmatic.com/wl?pubid=9999'); let data = getLoggerJsonFromRequest(request.requestBody); + expect(data.ffs).to.equal(1); + expect(data.fsrc).to.equal(2); + expect(data.fp).to.equal('pubmatic'); expect(data.s[1].sn).to.equal('/19968336/header-bid-tag-1'); expect(data.s[1].fskp).to.equal(0); expect(data.s[1].sid).not.to.be.undefined; - expect(data.s[1].ffs).to.equal(1); - expect(data.s[1].fsrc).to.equal(2); - expect(data.s[1].fp).to.equal('pubmatic'); expect(data.s[1].sz).to.deep.equal(['1000x300', '970x250', '728x90']); expect(data.s[1].ps).to.be.an('array'); expect(data.s[1].ps.length).to.equal(1); @@ -1357,14 +1353,14 @@ describe('pubmatic analytics adapter', function () { let request = requests[1]; // logger is executed late, trackers execute first expect(request.url).to.equal('https://t.pubmatic.com/wl?pubid=9999'); let data = getLoggerJsonFromRequest(request.requestBody); + expect(data.ffs).to.equal(1); + expect(data.fsrc).to.equal(2); + expect(data.fp).to.equal('pubmatic'); // slot 2 // Testing only for rejected bid as other scenarios will be covered under other TCs expect(data.s[1].sn).to.equal('/19968336/header-bid-tag-1'); expect(data.s[1].fskp).to.equal(0); - expect(data.s[1].ffs).to.equal(1); - expect(data.s[1].fsrc).to.equal(2); - expect(data.s[1].fp).to.equal('pubmatic'); expect(data.s[1].sz).to.deep.equal(['1000x300', '970x250', '728x90']); expect(data.s[1].sid).not.to.be.undefined; expect(data.s[1].ps).to.be.an('array'); @@ -1438,13 +1434,13 @@ describe('pubmatic analytics adapter', function () { expect(data.ft).to.equal(1); expect(data.s).to.be.an('array'); expect(data.s.length).to.equal(2); + expect(data.ffs).to.equal(1); + expect(data.fsrc).to.equal(2); + expect(data.fp).to.equal('pubmatic'); // slot 1 expect(data.s[0].sn).to.equal('/19968336/header-bid-tag-0'); expect(data.s[0].fskp).to.equal(0); - expect(data.s[0].ffs).to.equal(1); - expect(data.s[0].fsrc).to.equal(2); - expect(data.s[0].fp).to.equal('pubmatic'); expect(data.s[0].sz).to.deep.equal(['640x480']); expect(data.s[0].sid).not.to.be.undefined; expect(data.s[0].ps).to.be.an('array'); @@ -1477,9 +1473,6 @@ describe('pubmatic analytics adapter', function () { // slot 2 expect(data.s[1].sn).to.equal('/19968336/header-bid-tag-1'); expect(data.s[1].fskp).to.equal(0); - expect(data.s[1].ffs).to.equal(1); - expect(data.s[1].fsrc).to.equal(2); - expect(data.s[1].fp).to.equal('pubmatic'); expect(data.s[1].sz).to.deep.equal(['1000x300', '970x250', '728x90']); expect(data.s[1].sid).not.to.be.undefined; expect(data.s[1].ps).to.be.an('array'); @@ -1570,13 +1563,13 @@ describe('pubmatic analytics adapter', function () { expect(data.ft).to.equal(1); expect(data.s).to.be.an('array'); expect(data.s.length).to.equal(2); + expect(data.ffs).to.equal(1); + expect(data.fsrc).to.equal(2); + expect(data.fp).to.equal('pubmatic'); // slot 1 expect(data.s[0].sn).to.equal('/19968336/header-bid-tag-0'); expect(data.s[0].fskp).to.equal(0); - expect(data.s[0].ffs).to.equal(1); - expect(data.s[0].fsrc).to.equal(2); - expect(data.s[0].fp).to.equal('pubmatic'); expect(data.s[0].sz).to.deep.equal(['640x480']); expect(data.s[0].sid).not.to.be.undefined; expect(data.s[0].ps).to.be.an('array'); diff --git a/test/spec/modules/pubriseBidAdapter_spec.js b/test/spec/modules/pubriseBidAdapter_spec.js index e6dc710382c..37f1c742c65 100644 --- a/test/spec/modules/pubriseBidAdapter_spec.js +++ b/test/spec/modules/pubriseBidAdapter_spec.js @@ -146,7 +146,11 @@ describe('PubriseBidAdapter', function () { 'coppa', 'ccpa', 'gdpr', - 'tmax' + 'tmax', + 'bcat', + 'badv', + 'bapp', + 'battr' ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); diff --git a/test/spec/modules/qtBidAdapter_spec.js b/test/spec/modules/qtBidAdapter_spec.js index f1c1ca61664..9319df0f660 100644 --- a/test/spec/modules/qtBidAdapter_spec.js +++ b/test/spec/modules/qtBidAdapter_spec.js @@ -145,7 +145,11 @@ describe('QTBidAdapter', function () { 'coppa', 'ccpa', 'gdpr', - 'tmax' + 'tmax', + 'bcat', + 'badv', + 'bapp', + 'battr' ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); diff --git a/test/spec/modules/richaudienceBidAdapter_spec.js b/test/spec/modules/richaudienceBidAdapter_spec.js index 26d226b65f3..e4fd11d8604 100644 --- a/test/spec/modules/richaudienceBidAdapter_spec.js +++ b/test/spec/modules/richaudienceBidAdapter_spec.js @@ -246,6 +246,66 @@ describe('Richaudience adapter tests', function () { transactionId: '29df2112-348b-4961-8863-1b33684d95e6' }]; + var BID_PARAMS_EIDS = [{ + 'bidder': 'richaudience', + 'params': { + 'pid': 'IHOhChZNuI', + 'supplyType': 'site' + }, + 'userIdAsEids': [], + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + }] + + var id5 = { + 'source': 'id5-sync.com', + 'uids': [ + { + 'id': 'id5-string-cookie', + 'atype': 1, + 'ext': { + 'linkType': 2, + 'pba': 'id5-pba', + 'abTestingControlGroup': false + } + } + ] + } + + var first_id = { + 'source': 'first-id.fr', + 'uids': [ + { + 'id': 'value read from cookie or local storage', + 'atype': 1, + 'ext': { + 'stype': 'ppuid' + } + } + ] + } + + var three_party_provided = { + 'source': '3rdpartyprovided.com', + 'uids': [ + { + 'id': 'value read from cookie or local storage', + 'atype': 3, + 'ext': { + 'stype': 'dmp' + } + } + ] + } + var BID_RESPONSE = { body: { cpm: 1.50, @@ -289,7 +349,7 @@ describe('Richaudience adapter tests', function () { } } - it('Referer undefined', function() { + it('Referer undefined', function () { config.setConfig({ 'currency': {'adServerCurrency': 'USD'} }) @@ -306,7 +366,7 @@ describe('Richaudience adapter tests', function () { expect(requestContent).to.have.property('referer').and.to.equal(null); }) - it('Verify build request to prebid 3.0 display test', function() { + it('Verify build request to prebid 3.0 display test', function () { const request = spec.buildRequests(DEFAULT_PARAMS_NEW_SIZES, { gdprConsent: { consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', @@ -348,7 +408,7 @@ describe('Richaudience adapter tests', function () { expect(requestContent).to.have.property('kws').and.to.equal('key1=value1;key2=value2'); }) - it('Verify build request to prebid video inestream', function() { + it('Verify build request to prebid video inestream', function () { const request = spec.buildRequests(DEFAULT_PARAMS_VIDEO_IN, { gdprConsent: { consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', @@ -367,7 +427,7 @@ describe('Richaudience adapter tests', function () { expect(requestContent.videoData).to.have.property('format').and.to.equal('instream'); }) - it('Verify build request to prebid video outstream', function() { + it('Verify build request to prebid video outstream', function () { const request = spec.buildRequests(DEFAULT_PARAMS_VIDEO_OUT, { gdprConsent: { consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', @@ -467,8 +527,8 @@ describe('Richaudience adapter tests', function () { pd: 'MT1iNTBjY...' // optional, see table below for a link to how to generate this }, storage: { - type: 'html5', // "html5" is the required storage type - name: 'id5id', // "id5id" is the required storage name + type: 'html5', // 'html5' is the required storage type + name: 'id5id', // 'id5id' is the required storage name expires: 90, // storage lasts for 90 days refreshInSeconds: 8 * 3600 // refresh ID every 8 hours to ensure it's fresh } @@ -476,213 +536,55 @@ describe('Richaudience adapter tests', function () { auctionDelay: 50 // 50ms maximum auction delay, applies to all userId modules } }); - it('Verify build id5', function () { - var request; - DEFAULT_PARAMS_WO_OPTIONAL[0].userId = {}; - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.id5id = { uid: 1 }; - request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - var requestContent = JSON.parse(request[0].data); - - expect(requestContent.user.eids).to.equal(undefined); - - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.id5id = { uid: [] }; - request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - requestContent = JSON.parse(request[0].data); - expect(requestContent.user.eids).to.equal(undefined); - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.id5id = { uid: null }; - request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - requestContent = JSON.parse(request[0].data); - expect(requestContent.user.eids).to.equal(undefined); - - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.id5id = { uid: {} }; - request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - requestContent = JSON.parse(request[0].data); - expect(requestContent.user.eids).to.equal(undefined); - - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.id5id = null; - request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - requestContent = JSON.parse(request[0].data); - expect(requestContent.user.eids).to.equal(undefined); - - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.id5id = {}; - request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - requestContent = JSON.parse(request[0].data); - expect(requestContent.user.eids).to.equal(undefined); - }); - - it('Verify build pubCommonId', function () { - DEFAULT_PARAMS_WO_OPTIONAL[0].userId = {}; - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.pubcid = 'pub_common_user_id'; - - var request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + it('Verify build return empty users', function () { + var request = spec.buildRequests(BID_PARAMS_EIDS, DEFAULT_PARAMS_GDPR); var requestContent = JSON.parse(request[0].data); - expect(requestContent.user).to.deep.equal([{ - 'userId': 'pub_common_user_id', - 'source': 'pubcommon' - }]); + expect(requestContent.eids).to.deep.equal([]); + }) - var request; - DEFAULT_PARAMS_WO_OPTIONAL[0].userId = {}; - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.pubcid = 1; - request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + it('Verify build return all users', function () { + BID_PARAMS_EIDS[0].userIdAsEids = [id5, three_party_provided, first_id] + var request = spec.buildRequests(BID_PARAMS_EIDS, DEFAULT_PARAMS_GDPR); var requestContent = JSON.parse(request[0].data); - expect(requestContent.user.eids).to.equal(undefined); - - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.pubcid = []; - request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - requestContent = JSON.parse(request[0].data); - expect(requestContent.user.eids).to.equal(undefined); - - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.pubcid = null; - request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - requestContent = JSON.parse(request[0].data); - expect(requestContent.user.eids).to.equal(undefined); - - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.pubcid = {}; - request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - requestContent = JSON.parse(request[0].data); - expect(requestContent.user.eids).to.equal(undefined); - }); - - it('Verify build criteoId', function () { - DEFAULT_PARAMS_WO_OPTIONAL[0].userId = {}; - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.criteoId = 'criteo-user-id'; + expect(requestContent.eids).to.deep.equal([id5, three_party_provided, first_id]); + }) - var request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + it('Verify build return first-id.fr users', function () { + BID_PARAMS_EIDS[0].userIdAsEids = [first_id] + var request = spec.buildRequests(BID_PARAMS_EIDS, DEFAULT_PARAMS_GDPR); var requestContent = JSON.parse(request[0].data); + expect(requestContent.eids).to.deep.equal([first_id]); + }) - expect(requestContent.user).to.deep.equal([{ - 'userId': 'criteo-user-id', - 'source': 'criteo.com' - }]); - - var request; - DEFAULT_PARAMS_WO_OPTIONAL[0].userId = {}; - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.criteoId = 1; - request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + it('Verify build return first-id.fr users', function () { + BID_PARAMS_EIDS[0].userIdAsEids = [first_id] + var request = spec.buildRequests(BID_PARAMS_EIDS, DEFAULT_PARAMS_GDPR); var requestContent = JSON.parse(request[0].data); - expect(requestContent.user.eids).to.equal(undefined); - - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.criteoId = []; - request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - requestContent = JSON.parse(request[0].data); - expect(requestContent.user.eids).to.equal(undefined); - - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.criteoId = null; - request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - requestContent = JSON.parse(request[0].data); - expect(requestContent.user.eids).to.equal(undefined); - - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.criteoId = {}; - request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - requestContent = JSON.parse(request[0].data); - expect(requestContent.user.eids).to.equal(undefined); - }); - - it('Verify build identityLink', function () { - DEFAULT_PARAMS_WO_OPTIONAL[0].userId = {}; - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.idl_env = 'identity-link-user-id'; + expect(requestContent.eids).to.deep.equal([first_id]); + }) - var request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + it('Verify build return users []', function () { + BID_PARAMS_EIDS[0].userIdAsEids = [] + var request = spec.buildRequests(BID_PARAMS_EIDS, DEFAULT_PARAMS_GDPR); var requestContent = JSON.parse(request[0].data); + expect(requestContent.eids).to.deep.equal([]); + }) - expect(requestContent.user).to.deep.equal([{ - 'userId': 'identity-link-user-id', - 'source': 'liveramp.com' - }]); - - var request; - DEFAULT_PARAMS_WO_OPTIONAL[0].userId = {}; - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.idl_env = 1; - request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + it('Verify build return users null', function () { + BID_PARAMS_EIDS[0].userIdAsEids = null + var request = spec.buildRequests(BID_PARAMS_EIDS, DEFAULT_PARAMS_GDPR); var requestContent = JSON.parse(request[0].data); - expect(requestContent.user.eids).to.equal(undefined); - - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.criteoId = []; - request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - requestContent = JSON.parse(request[0].data); - expect(requestContent.user.eids).to.equal(undefined); - - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.idl_env = null; - request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - requestContent = JSON.parse(request[0].data); - expect(requestContent.user.eids).to.equal(undefined); - - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.idl_env = {}; - request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - requestContent = JSON.parse(request[0].data); - expect(requestContent.user.eids).to.equal(undefined); - }); - it('Verify build liveIntentId', function () { - DEFAULT_PARAMS_WO_OPTIONAL[0].userId = {}; - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.idl_env = 'identity-link-user-id'; - - var request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - var requestContent = JSON.parse(request[0].data) - - expect(requestContent.user).to.deep.equal([{ - 'userId': 'identity-link-user-id', - 'source': 'liveramp.com' - }]); + expect(requestContent.eids).to.deep.equal([]); + }) - var request; - DEFAULT_PARAMS_WO_OPTIONAL[0].userId = {}; - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.idl_env = 1; - request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + it('Verify build return users {}', function () { + BID_PARAMS_EIDS[0].userIdAsEids = null + var request = spec.buildRequests(BID_PARAMS_EIDS, DEFAULT_PARAMS_GDPR); var requestContent = JSON.parse(request[0].data); - expect(requestContent.user.eids).to.equal(undefined); - - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.criteoId = []; - request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - requestContent = JSON.parse(request[0].data); - expect(requestContent.user.eids).to.equal(undefined); - - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.idl_env = null; - request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - requestContent = JSON.parse(request[0].data); - expect(requestContent.user.eids).to.equal(undefined); - - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.idl_env = {}; - request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - requestContent = JSON.parse(request[0].data); - expect(requestContent.user.eids).to.equal(undefined); - }); - it('Verify build TradeDesk', function () { - DEFAULT_PARAMS_WO_OPTIONAL[0].userId = {}; - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.tdid = 'tdid-user-id'; - - var request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - var requestContent = JSON.parse(request[0].data) - - expect(requestContent.user).to.deep.equal([{ - 'userId': 'tdid-user-id', - 'source': 'adserver.org' - }]); - - request; - DEFAULT_PARAMS_WO_OPTIONAL[0].userId = {}; - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.idl_env = 1; - request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - requestContent = JSON.parse(request[0].data); - expect(requestContent.user.eids).to.equal(undefined); - - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.criteoId = []; - request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - requestContent = JSON.parse(request[0].data); - expect(requestContent.user.eids).to.equal(undefined); - - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.idl_env = null; - request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - requestContent = JSON.parse(request[0].data); - expect(requestContent.user.eids).to.equal(undefined); - - DEFAULT_PARAMS_WO_OPTIONAL[0].userId.idl_env = {}; - request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); - requestContent = JSON.parse(request[0].data); - expect(requestContent.user.eids).to.equal(undefined); - }); + expect(requestContent.eids).to.deep.equal([]); + }) }); it('Verify interprete response', function () { @@ -900,7 +802,7 @@ describe('Richaudience adapter tests', function () { })).to.equal(true); }); - it('should pass schain', function() { + it('should pass schain', function () { let schain = { 'ver': '1.0', 'complete': 1, @@ -940,7 +842,7 @@ describe('Richaudience adapter tests', function () { expect(requestContent).to.have.property('schain').to.deep.equal(schain); }) - it('should pass DSA', function() { + it('should pass DSA', function () { const request = spec.buildRequests(DEFAULT_PARAMS_NEW_DSA, { gdprConsent: { consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', @@ -955,7 +857,7 @@ describe('Richaudience adapter tests', function () { expect(requestContent.dsa.transparency[0]).to.have.property('domain').and.to.equal('richaudience.com'); }) - it('should pass gpid', function() { + it('should pass gpid', function () { const request = spec.buildRequests(DEFAULT_PARAMS_NEW_SIZES_GPID, { gdprConsent: { consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', @@ -968,11 +870,11 @@ describe('Richaudience adapter tests', function () { }) describe('onTimeout', function () { - beforeEach(function() { + beforeEach(function () { sinon.stub(utils, 'triggerPixel'); }); - afterEach(function() { + afterEach(function () { utils.triggerPixel.restore(); }); it('onTimeout exist as a function', () => { @@ -990,7 +892,7 @@ describe('Richaudience adapter tests', function () { beforeEach(function () { sandbox = sinon.sandbox.create(); }); - afterEach(function() { + afterEach(function () { sandbox.restore(); }); it('Verifies user syncs iframe include', function () { @@ -1002,7 +904,8 @@ describe('Richaudience adapter tests', function () { iframeEnabled: true }, [BID_RESPONSE], { consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', - gdprApplies: true}, + gdprApplies: true + }, ); expect(syncs).to.have.lengthOf(1); expect(syncs[0].type).to.equal('iframe'); @@ -1042,7 +945,8 @@ describe('Richaudience adapter tests', function () { iframeEnabled: true }, [BID_RESPONSE], { consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', - gdprApplies: true}, + gdprApplies: true + }, ); expect(syncs).to.have.lengthOf(0); @@ -1150,7 +1054,12 @@ describe('Richaudience adapter tests', function () { it('Verifies user syncs iframe/image include', function () { config.setConfig({ - 'userSync': {filterSettings: {iframe: {bidders: '*', filter: 'include'}, image: {bidders: '*', filter: 'include'}}} + 'userSync': { + filterSettings: { + iframe: {bidders: '*', filter: 'include'}, + image: {bidders: '*', filter: 'include'} + } + } }) var syncs = spec.getUserSyncs({ @@ -1158,7 +1067,8 @@ describe('Richaudience adapter tests', function () { pixelEnabled: true }, [BID_RESPONSE], { consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', - gdprApplies: true}, + gdprApplies: true + }, ); expect(syncs).to.have.lengthOf(1); expect(syncs[0].type).to.equal('iframe'); @@ -1196,7 +1106,12 @@ describe('Richaudience adapter tests', function () { it('Verifies user syncs iframe/image exclude', function () { config.setConfig({ - 'userSync': {filterSettings: {iframe: {bidders: '*', filter: 'exclude'}, image: {bidders: '*', filter: 'exclude'}}} + 'userSync': { + filterSettings: { + iframe: {bidders: '*', filter: 'exclude'}, + image: {bidders: '*', filter: 'exclude'} + } + } }) var syncs = spec.getUserSyncs({ @@ -1204,7 +1119,8 @@ describe('Richaudience adapter tests', function () { pixelEnabled: true }, [BID_RESPONSE], { consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', - gdprApplies: true}, + gdprApplies: true + }, ); expect(syncs).to.have.lengthOf(0); @@ -1241,7 +1157,12 @@ describe('Richaudience adapter tests', function () { it('Verifies user syncs iframe exclude / image include', function () { config.setConfig({ - 'userSync': {filterSettings: {iframe: {bidders: '*', filter: 'exclude'}, image: {bidders: '*', filter: 'include'}}} + 'userSync': { + filterSettings: { + iframe: {bidders: '*', filter: 'exclude'}, + image: {bidders: '*', filter: 'include'} + } + } }) var syncs = spec.getUserSyncs({ @@ -1249,7 +1170,8 @@ describe('Richaudience adapter tests', function () { pixelEnabled: true }, [BID_RESPONSE], { consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', - gdprApplies: true}, + gdprApplies: true + }, ); expect(syncs).to.have.lengthOf(1); expect(syncs[0].type).to.equal('image'); @@ -1287,7 +1209,12 @@ describe('Richaudience adapter tests', function () { it('Verifies user syncs iframe include / image exclude', function () { config.setConfig({ - 'userSync': {filterSettings: {iframe: {bidders: '*', filter: 'include'}, image: {bidders: '*', filter: 'exclude'}}} + 'userSync': { + filterSettings: { + iframe: {bidders: '*', filter: 'include'}, + image: {bidders: '*', filter: 'exclude'} + } + } }) var syncs = spec.getUserSyncs({ @@ -1295,7 +1222,8 @@ describe('Richaudience adapter tests', function () { pixelEnabled: true }, [BID_RESPONSE], { consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', - gdprApplies: true}, + gdprApplies: true + }, ); expect(syncs).to.have.lengthOf(1); expect(syncs[0].type).to.equal('iframe'); @@ -1338,7 +1266,8 @@ describe('Richaudience adapter tests', function () { var syncs = spec.getUserSyncs({iframeEnabled: true}, [BID_RESPONSE], { gppString: 'DBABL~BVVqAAEABgA.QA', - applicableSections: [7]}, + applicableSections: [7] + }, ); expect(syncs).to.have.lengthOf(1); expect(syncs[0].type).to.equal('iframe'); @@ -1349,17 +1278,22 @@ describe('Richaudience adapter tests', function () { var syncs = spec.getUserSyncs({pixelEnabled: true}, [BID_RESPONSE], { gppString: 'DBABL~BVVqAAEABgA.QA', - applicableSections: [7, 5]}, + applicableSections: [7, 5] + }, ); expect(syncs).to.have.lengthOf(1); expect(syncs[0].type).to.equal('image'); }); it('Verifies user syncs URL image include with GPP', function () { - const gppConsent = { gppString: 'DBACMYA~CP5P4cAP5P4cAPoABAESAlEAAAAAAAAAAAAAA2QAQA2ADZABADYAAAAA.QA2QAQA2AAAA.IA2QAQA2AAAA~BP5P4cAP5P4cAPoABABGBACAAAAAAAAAAAAAAAAAAA.YAAAAAAAAAA', applicableSections: [0] }; + const gppConsent = { + gppString: 'DBACMYA~CP5P4cAP5P4cAPoABAESAlEAAAAAAAAAAAAAA2QAQA2ADZABADYAAAAA.QA2QAQA2AAAA.IA2QAQA2AAAA~BP5P4cAP5P4cAPoABABGBACAAAAAAAAAAAAAAAAAAA.YAAAAAAAAAA', + applicableSections: [0] + }; const result = spec.getUserSyncs({pixelEnabled: true}, undefined, undefined, undefined, gppConsent); expect(result).to.deep.equal([{ - type: 'image', url: `https://sync.richaudience.com/bf7c142f4339da0278e83698a02b0854/?referrer=http%3A%2F%2Fdomain.com&gpp=DBACMYA~CP5P4cAP5P4cAPoABAESAlEAAAAAAAAAAAAAA2QAQA2ADZABADYAAAAA.QA2QAQA2AAAA.IA2QAQA2AAAA~BP5P4cAP5P4cAPoABABGBACAAAAAAAAAAAAAAAAAAA.YAAAAAAAAAA&gpp_sid=0` + type: 'image', + url: `https://sync.richaudience.com/bf7c142f4339da0278e83698a02b0854/?referrer=http%3A%2F%2Fdomain.com&gpp=DBACMYA~CP5P4cAP5P4cAPoABAESAlEAAAAAAAAAAAAAA2QAQA2ADZABADYAAAAA.QA2QAQA2AAAA.IA2QAQA2AAAA~BP5P4cAP5P4cAPoABABGBACAAAAAAAAAAAAAAAAAAA.YAAAAAAAAAA&gpp_sid=0` }]); }); }) diff --git a/test/spec/modules/riseBidAdapter_spec.js b/test/spec/modules/riseBidAdapter_spec.js index 2d11ae16cb7..a3fef50f825 100644 --- a/test/spec/modules/riseBidAdapter_spec.js +++ b/test/spec/modules/riseBidAdapter_spec.js @@ -2,8 +2,9 @@ import { expect } from 'chai'; import { spec } from 'modules/riseBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; import { config } from 'src/config.js'; -import { BANNER, VIDEO } from '../../../src/mediaTypes.js'; +import {BANNER, NATIVE, VIDEO} from '../../../src/mediaTypes.js'; import * as utils from 'src/utils.js'; +import {decorateAdUnitsWithNativeParams} from '../../../src/native'; const ENDPOINT = 'https://hb.yellowblue.io/hb-multi'; const TEST_ENDPOINT = 'https://hb.yellowblue.io/hb-multi-test'; @@ -72,7 +73,6 @@ describe('riseAdapter', function () { 'plcmt': 1 } }, - 'vastXml': '"..."' }, { 'bidder': spec.code, @@ -89,7 +89,59 @@ describe('riseAdapter', function () { 'banner': { } }, - 'ad': '""' + }, + { + 'bidder': spec.code, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250]], + 'params': { + 'org': 'jdye8weeyirk00000001' + }, + 'bidId': '299ffc8cca0b87', + 'loop': 1, + 'bidderRequestId': '1144f487e563f9', + 'auctionId': 'bfc420c3-8577-4568-9766-a8a935fb620d', + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ 300, 250 ] + ] + }, + 'video': { + 'playerSize': [[640, 480]], + 'context': 'instream', + 'plcmt': 1 + }, + 'native': { + 'ortb': { + 'assets': [ + { + 'id': 1, + 'required': 1, + 'img': { + 'type': 3, + 'w': 300, + 'h': 200, + } + }, + { + 'id': 2, + 'required': 1, + 'title': { + 'len': 80 + } + }, + { + 'id': 3, + 'required': 1, + 'data': { + 'type': 1 + } + } + ] + } + }, + }, } ]; @@ -175,10 +227,10 @@ describe('riseAdapter', function () { }); it('should send the correct mimes array', function () { - bidRequests[1].mediaTypes.banner.mimes = mimes; + bidRequests[0].mediaTypes.video.mimes = mimes; const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.bids[1].mimes).to.be.an('array'); - expect(request.data.bids[1].mimes).to.eql(['application/javascript', 'video/mp4', 'video/quicktime']); + expect(request.data.bids[0].mimes).to.be.an('array'); + expect(request.data.bids[0].mimes).to.eql(['application/javascript', 'video/mp4', 'video/quicktime']); }); it('should send the correct protocols array', function () { @@ -194,12 +246,21 @@ describe('riseAdapter', function () { expect(request.data.bids[0].sizes).to.equal(bidRequests[0].sizes) expect(request.data.bids[1].sizes).to.be.an('array'); expect(request.data.bids[1].sizes).to.equal(bidRequests[1].sizes) + expect(request.data.bids[2].sizes).to.be.an('array'); + expect(request.data.bids[2].sizes).to.eql(bidRequests[2].sizes) + }); + + it('should send nativeOrtbRequest in native bid request', function () { + decorateAdUnitsWithNativeParams(bidRequests) + const request = spec.buildRequests(bidRequests, bidderRequest); + assert.deepEqual(request.data.bids[2].nativeOrtbRequest, bidRequests[2].mediaTypes.native.ortb) }); it('should send the correct media type', function () { const request = spec.buildRequests(bidRequests, bidderRequest); expect(request.data.bids[0].mediaType).to.equal(VIDEO) expect(request.data.bids[1].mediaType).to.equal(BANNER) + expect(request.data.bids[2].mediaType.split(',')).to.include.members([VIDEO, NATIVE, BANNER]) }); it('should respect syncEnabled option', function() { @@ -506,6 +567,28 @@ describe('riseAdapter', function () { creativeId: 'creative-id', nurl: 'http://example.com/win/1234', mediaType: BANNER + }, + { + cpm: 12.5, + width: 300, + height: 200, + requestId: '21e12606d47ba7', + adomain: ['abc.com'], + creativeId: 'creative-id', + nurl: 'http://example.com/win/1234', + mediaType: NATIVE, + native: { + body: 'Advertise with Rise', + clickUrl: 'https://risecodes.com', + cta: 'Start now', + image: { + width: 300, + height: 200, + url: 'https://sdk.streamrail.com/media/rise-image.jpg' + }, + sponsoredBy: 'Rise', + title: 'Rise Ad Tech Solutions' + } }] }; @@ -545,10 +628,42 @@ describe('riseAdapter', function () { ad: '""' }; + const expectedNativeResponse = { + requestId: '21e12606d47ba7', + cpm: 12.5, + currency: 'USD', + width: 300, + height: 200, + ttl: TTL, + creativeId: 'creative-id', + netRevenue: true, + nurl: 'http://example.com/win/1234', + mediaType: NATIVE, + meta: { + mediaType: NATIVE, + advertiserDomains: ['abc.com'] + }, + native: { + ortb: { + body: 'Advertise with Rise', + clickUrl: 'https://risecodes.com', + cta: 'Start now', + image: { + width: 300, + height: 200, + url: 'https://sdk.streamrail.com/media/rise-image.jpg', + }, + sponsoredBy: 'Rise', + title: 'Rise Ad Tech Solutions' + } + }, + }; + it('should get correct bid response', function () { const result = spec.interpretResponse({ body: response }); expect(result[0]).to.deep.equal(expectedVideoResponse); expect(result[1]).to.deep.equal(expectedBannerResponse); + expect(result[2]).to.deep.equal(expectedNativeResponse); }); it('video type should have vastXml key', function () { @@ -560,6 +675,11 @@ describe('riseAdapter', function () { const result = spec.interpretResponse({ body: response }); expect(result[1].ad).to.equal(expectedBannerResponse.ad) }); + + it('native type should have native key', function () { + const result = spec.interpretResponse({ body: response }); + expect(result[2].native).to.eql(expectedNativeResponse.native) + }); }) describe('getUserSyncs', function() { diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index aff6c6df171..9821fc45126 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -1562,6 +1562,39 @@ describe('the rubicon adapter', function () { // Expected format: uid^atype^third^inserter^matcher^mm^rtipartner const expectedEidValue = '11111^2^^inserter123^matcher123^mm123^rtipartner123'; + // Check if the generated EID value matches the expected format + expect(data.get('eid_example.com')).to.equal(expectedEidValue); + }); + it('should generate eidValue with all attributes including rtiPartner', function () { + const clonedBid = utils.deepClone(bidderRequest.bids[0]); + // Simulating a full EID object with multiple fields + clonedBid.ortb2 = { + user: { + ext: { + eids: [{ + source: 'example.com', + uids: [{ + id: '11111', // UID + atype: 2, // atype + ext: { + rtiPartner: 'rtipartner123', // rtiPartner (note the different capitalization) + stype: 'ppuid' // stype + } + }], + inserter: 'inserter123', // inserter + matcher: 'matcher123', // matcher + mm: 'mm123' // mm + }] + } + } + }; + + let [request] = spec.buildRequests([clonedBid], bidderRequest); + let data = new URLSearchParams(request.data); + + // Expected format: uid^atype^third^inserter^matcher^mm^rtipartner + const expectedEidValue = '11111^2^^inserter123^matcher123^mm123^rtipartner123'; + // Check if the generated EID value matches the expected format expect(data.get('eid_example.com')).to.equal(expectedEidValue); }); diff --git a/test/spec/modules/sharedIdSystem_spec.js b/test/spec/modules/sharedIdSystem_spec.js index 359cbeb4651..71a531aa6c5 100644 --- a/test/spec/modules/sharedIdSystem_spec.js +++ b/test/spec/modules/sharedIdSystem_spec.js @@ -1,10 +1,12 @@ import {sharedIdSystemSubmodule, storage} from 'modules/sharedIdSystem.js'; import {coppaDataHandler} from 'src/adapterManager'; +import {config} from 'src/config.js'; import sinon from 'sinon'; import * as utils from 'src/utils.js'; import {createEidsArray} from '../../../modules/userId/eids.js'; -import {attachIdSystem} from '../../../modules/userId/index.js'; +import {attachIdSystem, init} from '../../../modules/userId/index.js'; +import {getGlobal} from '../../../src/prebidGlobal.js'; let expect = require('chai').expect; @@ -97,6 +99,9 @@ describe('SharedId System', function () { before(() => { attachIdSystem(sharedIdSystemSubmodule); }); + afterEach(() => { + config.resetConfig(); + }); it('pubCommonId', function() { const userId = { pubcid: 'some-random-id-value' @@ -108,5 +113,24 @@ describe('SharedId System', function () { uids: [{id: 'some-random-id-value', atype: 1}] }); }); + + it('should set inserter, if provided in config', async () => { + config.setConfig({ + userSync: { + userIds: [{ + name: 'sharedId', + params: { + inserter: 'mock-inserter' + }, + value: {pubcid: 'mock-id'} + }] + } + }); + const eids = getGlobal().getUserIdsAsEids(); + sinon.assert.match(eids[0], { + source: 'pubcid.org', + inserter: 'mock-inserter' + }) + }) }) }); diff --git a/test/spec/modules/shinezBidAdapter_spec.js b/test/spec/modules/shinezBidAdapter_spec.js index 4e6c2d3420e..d4ad99359bb 100644 --- a/test/spec/modules/shinezBidAdapter_spec.js +++ b/test/spec/modules/shinezBidAdapter_spec.js @@ -2,8 +2,9 @@ import { expect } from 'chai'; import { spec } from 'modules/shinezBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; import { config } from 'src/config.js'; -import { BANNER, VIDEO } from '../../../src/mediaTypes.js'; +import {BANNER, NATIVE, VIDEO} from '../../../src/mediaTypes.js'; import * as utils from 'src/utils.js'; +import {decorateAdUnitsWithNativeParams} from '../../../src/native'; const ENDPOINT = 'https://hb.sweetgum.io/hb-sz-multi'; const TEST_ENDPOINT = 'https://hb.sweetgum.io/hb-multi-sz-test'; @@ -61,7 +62,6 @@ describe('shinezAdapter', function () { 'context': 'instream' } }, - 'vastXml': '"..."' }, { 'bidder': spec.code, @@ -77,7 +77,59 @@ describe('shinezAdapter', function () { 'banner': { } }, - 'ad': '""' + }, + { + 'bidder': spec.code, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250]], + 'params': { + 'org': 'jdye8weeyirk00000001' + }, + 'bidId': '299ffc8cca0b87', + 'loop': 1, + 'bidderRequestId': '1144f487e563f9', + 'auctionId': 'bfc420c3-8577-4568-9766-a8a935fb620d', + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ 300, 250 ] + ] + }, + 'video': { + 'playerSize': [[640, 480]], + 'context': 'instream', + 'plcmt': 1 + }, + 'native': { + 'ortb': { + 'assets': [ + { + 'id': 1, + 'required': 1, + 'img': { + 'type': 3, + 'w': 300, + 'h': 200, + } + }, + { + 'id': 2, + 'required': 1, + 'title': { + 'len': 80 + } + }, + { + 'id': 3, + 'required': 1, + 'data': { + 'type': 1 + } + } + ] + } + }, + }, } ]; @@ -130,12 +182,21 @@ describe('shinezAdapter', function () { expect(request.data.bids[0].sizes).to.equal(bidRequests[0].sizes) expect(request.data.bids[1].sizes).to.be.an('array'); expect(request.data.bids[1].sizes).to.equal(bidRequests[1].sizes) + expect(request.data.bids[2].sizes).to.be.an('array'); + expect(request.data.bids[2].sizes).to.eql(bidRequests[2].sizes) + }); + + it('should send nativeOrtbRequest in native bid request', function () { + decorateAdUnitsWithNativeParams(bidRequests) + const request = spec.buildRequests(bidRequests, bidderRequest); + assert.deepEqual(request.data.bids[2].nativeOrtbRequest, bidRequests[2].mediaTypes.native.ortb) }); it('should send the correct media type', function () { const request = spec.buildRequests(bidRequests, bidderRequest); expect(request.data.bids[0].mediaType).to.equal(VIDEO) expect(request.data.bids[1].mediaType).to.equal(BANNER) + expect(request.data.bids[2].mediaType.split(',')).to.include.members([VIDEO, NATIVE, BANNER]) }); it('should respect syncEnabled option', function() { @@ -305,6 +366,8 @@ describe('shinezAdapter', function () { height: 480, requestId: '21e12606d47ba7', adomain: ['abc.com'], + creativeId: 'creative-id', + nurl: 'http://example.com/win/1234', mediaType: VIDEO }, { @@ -314,7 +377,31 @@ describe('shinezAdapter', function () { height: 250, requestId: '21e12606d47ba7', adomain: ['abc.com'], + creativeId: 'creative-id', + nurl: 'http://example.com/win/1234', mediaType: BANNER + }, + { + cpm: 12.5, + width: 300, + height: 200, + requestId: '21e12606d47ba7', + adomain: ['abc.com'], + creativeId: 'creative-id', + nurl: 'http://example.com/win/1234', + mediaType: NATIVE, + native: { + body: 'Advertise with Rise', + clickUrl: 'https://risecodes.com', + cta: 'Start now', + image: { + width: 300, + height: 200, + url: 'https://sdk.streamrail.com/media/rise-image.jpg' + }, + sponsoredBy: 'Rise', + title: 'Rise Ad Tech Solutions' + } }] }; @@ -325,7 +412,7 @@ describe('shinezAdapter', function () { width: 640, height: 480, ttl: TTL, - creativeId: '21e12606d47ba7', + creativeId: 'creative-id', netRevenue: true, nurl: 'http://example.com/win/1234', mediaType: VIDEO, @@ -340,10 +427,10 @@ describe('shinezAdapter', function () { requestId: '21e12606d47ba7', cpm: 12.5, currency: 'USD', - width: 640, - height: 480, + width: 300, + height: 250, ttl: TTL, - creativeId: '21e12606d47ba7', + creativeId: 'creative-id', netRevenue: true, nurl: 'http://example.com/win/1234', mediaType: BANNER, @@ -354,10 +441,42 @@ describe('shinezAdapter', function () { ad: '""' }; + const expectedNativeResponse = { + requestId: '21e12606d47ba7', + cpm: 12.5, + currency: 'USD', + width: 300, + height: 200, + ttl: TTL, + creativeId: 'creative-id', + netRevenue: true, + nurl: 'http://example.com/win/1234', + mediaType: NATIVE, + meta: { + mediaType: NATIVE, + advertiserDomains: ['abc.com'] + }, + native: { + ortb: { + body: 'Advertise with Rise', + clickUrl: 'https://risecodes.com', + cta: 'Start now', + image: { + width: 300, + height: 200, + url: 'https://sdk.streamrail.com/media/rise-image.jpg', + }, + sponsoredBy: 'Rise', + title: 'Rise Ad Tech Solutions' + } + }, + }; + it('should get correct bid response', function () { const result = spec.interpretResponse({ body: response }); - expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedVideoResponse)); - expect(Object.keys(result[1])).to.deep.equal(Object.keys(expectedBannerResponse)); + expect(result[0]).to.deep.equal(expectedVideoResponse); + expect(result[1]).to.deep.equal(expectedBannerResponse); + expect(result[2]).to.deep.equal(expectedNativeResponse); }); it('video type should have vastXml key', function () { @@ -369,6 +488,11 @@ describe('shinezAdapter', function () { const result = spec.interpretResponse({ body: response }); expect(result[1].ad).to.equal(expectedBannerResponse.ad) }); + + it('native type should have native key', function () { + const result = spec.interpretResponse({ body: response }); + expect(result[2].native).to.eql(expectedNativeResponse.native) + }); }) describe('getUserSyncs', function() { diff --git a/test/spec/modules/smarthubBidAdapter_spec.js b/test/spec/modules/smarthubBidAdapter_spec.js index 302195ea944..e810acf5271 100644 --- a/test/spec/modules/smarthubBidAdapter_spec.js +++ b/test/spec/modules/smarthubBidAdapter_spec.js @@ -159,7 +159,11 @@ describe('SmartHubBidAdapter', function () { 'coppa', 'ccpa', 'gdpr', - 'tmax' + 'tmax', + 'bcat', + 'badv', + 'bapp', + 'battr' ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); diff --git a/test/spec/modules/smootBidAdapter_spec.js b/test/spec/modules/smootBidAdapter_spec.js index cf72b41b348..f51c054f883 100644 --- a/test/spec/modules/smootBidAdapter_spec.js +++ b/test/spec/modules/smootBidAdapter_spec.js @@ -136,7 +136,11 @@ describe('SmootBidAdapter', function () { 'coppa', 'ccpa', 'gdpr', - 'tmax' + 'tmax', + 'bcat', + 'badv', + 'bapp', + 'battr' ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); diff --git a/test/spec/modules/stnBidAdapter_spec.js b/test/spec/modules/stnBidAdapter_spec.js index de851158ed0..98859385828 100644 --- a/test/spec/modules/stnBidAdapter_spec.js +++ b/test/spec/modules/stnBidAdapter_spec.js @@ -2,8 +2,9 @@ import { expect } from 'chai'; import { spec } from 'modules/stnBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; import { config } from 'src/config.js'; -import { BANNER, VIDEO } from '../../../src/mediaTypes.js'; +import {BANNER, NATIVE, VIDEO} from '../../../src/mediaTypes.js'; import * as utils from 'src/utils.js'; +import {decorateAdUnitsWithNativeParams} from '../../../src/native'; const ENDPOINT = 'https://hb.stngo.com/hb-multi'; const TEST_ENDPOINT = 'https://hb.stngo.com/hb-multi-test'; @@ -63,7 +64,6 @@ describe('stnAdapter', function () { 'plcmt': 1 } }, - 'vastXml': '"..."' }, { 'bidder': spec.code, @@ -80,6 +80,59 @@ describe('stnAdapter', function () { 'banner': { } }, + }, + { + 'bidder': spec.code, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250]], + 'params': { + 'org': 'jdye8weeyirk00000001' + }, + 'bidId': '299ffc8cca0b87', + 'loop': 1, + 'bidderRequestId': '1144f487e563f9', + 'auctionId': 'bfc420c3-8577-4568-9766-a8a935fb620d', + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ 300, 250 ] + ] + }, + 'video': { + 'playerSize': [[640, 480]], + 'context': 'instream', + 'plcmt': 1 + }, + 'native': { + 'ortb': { + 'assets': [ + { + 'id': 1, + 'required': 1, + 'img': { + 'type': 3, + 'w': 300, + 'h': 200, + } + }, + { + 'id': 2, + 'required': 1, + 'title': { + 'len': 80 + } + }, + { + 'id': 3, + 'required': 1, + 'data': { + 'type': 1 + } + } + ] + } + }, + }, 'ad': '""' } ]; @@ -151,10 +204,10 @@ describe('stnAdapter', function () { }); it('should send the correct mimes array', function () { - bidRequests[1].mediaTypes.banner.mimes = mimes; + bidRequests[0].mediaTypes.video.mimes = mimes; const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.bids[1].mimes).to.be.an('array'); - expect(request.data.bids[1].mimes).to.eql(['application/javascript', 'video/mp4', 'video/quicktime']); + expect(request.data.bids[0].mimes).to.be.an('array'); + expect(request.data.bids[0].mimes).to.eql(['application/javascript', 'video/mp4', 'video/quicktime']); }); it('should send the correct protocols array', function () { @@ -170,12 +223,21 @@ describe('stnAdapter', function () { expect(request.data.bids[0].sizes).to.equal(bidRequests[0].sizes) expect(request.data.bids[1].sizes).to.be.an('array'); expect(request.data.bids[1].sizes).to.equal(bidRequests[1].sizes) + expect(request.data.bids[2].sizes).to.be.an('array'); + expect(request.data.bids[2].sizes).to.eql(bidRequests[2].sizes) + }); + + it('should send nativeOrtbRequest in native bid request', function () { + decorateAdUnitsWithNativeParams(bidRequests) + const request = spec.buildRequests(bidRequests, bidderRequest); + assert.deepEqual(request.data.bids[2].nativeOrtbRequest, bidRequests[2].mediaTypes.native.ortb) }); it('should send the correct media type', function () { const request = spec.buildRequests(bidRequests, bidderRequest); expect(request.data.bids[0].mediaType).to.equal(VIDEO) expect(request.data.bids[1].mediaType).to.equal(BANNER) + expect(request.data.bids[2].mediaType.split(',')).to.include.members([VIDEO, NATIVE, BANNER]) }); it('should respect syncEnabled option', function() { @@ -455,6 +517,28 @@ describe('stnAdapter', function () { adomain: ['abc.com'], mediaType: BANNER, nurl: 'http://example.com/win/1234', + }, + { + cpm: 12.5, + width: 300, + height: 200, + requestId: '21e12606d47ba7', + adomain: ['abc.com'], + creativeId: 'creative-id-3', + nurl: 'http://example.com/win/1234', + mediaType: NATIVE, + native: { + body: 'Advertise with Rise', + clickUrl: 'https://risecodes.com', + cta: 'Start now', + image: { + width: 300, + height: 200, + url: 'https://sdk.streamrail.com/media/rise-image.jpg' + }, + sponsoredBy: 'Rise', + title: 'Rise Ad Tech Solutions' + } }] }; @@ -494,10 +578,42 @@ describe('stnAdapter', function () { ad: '""' }; + const expectedNativeResponse = { + requestId: '21e12606d47ba7', + cpm: 12.5, + currency: 'USD', + width: 300, + height: 200, + ttl: TTL, + creativeId: 'creative-id-3', + netRevenue: true, + nurl: 'http://example.com/win/1234', + mediaType: NATIVE, + meta: { + mediaType: NATIVE, + advertiserDomains: ['abc.com'] + }, + native: { + ortb: { + body: 'Advertise with Rise', + clickUrl: 'https://risecodes.com', + cta: 'Start now', + image: { + width: 300, + height: 200, + url: 'https://sdk.streamrail.com/media/rise-image.jpg', + }, + sponsoredBy: 'Rise', + title: 'Rise Ad Tech Solutions' + } + }, + }; + it('should get correct bid response', function () { const result = spec.interpretResponse({ body: response }); expect(result[0]).to.deep.equal(expectedVideoResponse); expect(result[1]).to.deep.equal(expectedBannerResponse); + expect(result[2]).to.deep.equal(expectedNativeResponse); }); it('video type should have vastXml key', function () { @@ -509,6 +625,11 @@ describe('stnAdapter', function () { const result = spec.interpretResponse({ body: response }); expect(result[1].ad).to.equal(expectedBannerResponse.ad) }); + + it('native type should have native key', function () { + const result = spec.interpretResponse({ body: response }); + expect(result[2].native).to.eql(expectedNativeResponse.native) + }); }) describe('getUserSyncs', function() { diff --git a/test/spec/modules/symitriDapRtdProvider_spec.js b/test/spec/modules/symitriDapRtdProvider_spec.js index ec3ba4fdbed..440949aeb52 100644 --- a/test/spec/modules/symitriDapRtdProvider_spec.js +++ b/test/spec/modules/symitriDapRtdProvider_spec.js @@ -74,6 +74,14 @@ describe('symitriDapRtdProvider', function() { 'identity': sampleIdentity } + const sampleX2Config = { + 'api_hostname': 'prebid.dap.akadns.net', + 'api_version': 'x2', + 'domain': 'prebid.org', + 'segtax': 708, + 'identity': sampleIdentity + } + const esampleConfig = { 'api_hostname': 'prebid.dap.akadns.net', 'api_version': 'x1', @@ -260,6 +268,34 @@ describe('symitriDapRtdProvider', function() { }); }); + describe('dapX2Tokenize', function () { + it('dapX2Tokenize error callback', function () { + let configAsync = JSON.parse(JSON.stringify(sampleX2Config)); + let submoduleCallback = dapUtils.dapTokenize(configAsync, sampleIdentity, onDone, + function(token, status, xhr, onDone) { + }, + function(xhr, status, error, onDone) { + } + ); + let request = server.requests[0]; + request.respond(400, responseHeader, JSON.stringify('error')); + expect(submoduleCallback).to.equal(undefined); + }); + + it('dapX2Tokenize success callback', function () { + let configAsync = JSON.parse(JSON.stringify(sampleX2Config)); + let submoduleCallback = dapUtils.dapTokenize(configAsync, sampleIdentity, onDone, + function(token, status, xhr, onDone) { + }, + function(xhr, status, error, onDone) { + } + ); + let request = server.requests[0]; + request.respond(200, responseHeader, JSON.stringify('success')); + expect(submoduleCallback).to.equal(undefined); + }); + }); + describe('dapTokenize and dapMembership incorrect params', function () { it('Onerror and config are null', function () { expect(dapUtils.dapTokenize(null, 'identity', onDone, null, null)).to.be.equal(undefined); diff --git a/test/spec/modules/tealBidAdapter_spec.js b/test/spec/modules/tealBidAdapter_spec.js new file mode 100644 index 00000000000..189e7f90e10 --- /dev/null +++ b/test/spec/modules/tealBidAdapter_spec.js @@ -0,0 +1,268 @@ +import { spec } from 'modules/tealBidAdapter.js'; +import { parseUrl } from 'src/utils.js'; + +const expect = require('chai').expect; + +const PBS_HOST = 'a.bids.ws'; +const PLACEMENT = 'test-placement300x250'; +const ACCOUNT = 'test-account'; +const SUB_ACCOUNT = 'test-sub-account'; +const TEST_DOMAIN = 'example.com'; +const TEST_PAGE = `https://${TEST_DOMAIN}/page.html`; +const ADUNIT_CODE = '/1234/header-bid-tag-0'; + +const BID_PARAMS = { + params: { + placement: PLACEMENT, + account: ACCOUNT, + testMode: true + } +}; + +const BID_REQUEST = { + bidder: 'teal', + ...BID_PARAMS, + ortb2Imp: { + ext: { + tid: 'e13391ea-00f3-495d-99a6-d937990d73a9' + } + }, + mediaTypes: { + banner: { + sizes: [ + [ + 300, + 250 + ], + ] + } + }, + adUnitCode: ADUNIT_CODE, + transactionId: 'e13391ea-00f3-495d-99a6-d937990d73a9', + sizes: [ + [ + 300, + 250 + ], + ], + bidId: '123456789', + bidderRequestId: '1decd098c76ed2', + auctionId: '251a6a36-a5c5-4b82-b2b3-538c148a29dd', + src: 'client', + bidRequestsCount: 1, + bidderRequestsCount: 1, + bidderWinsCount: 0, + ortb2: { + site: { + page: TEST_PAGE, + domain: TEST_DOMAIN, + publisher: { + domain: 'example.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: '123456789', + impid: BID_REQUEST.bidId, + price: 0.286000000000000004, + adm: '', + adomain: [ + 'teal.works' + ], + crid: '684f9b94-b8b9-4c32-83da-b075ca753f65', + w: 300, + h: 250, + exp: 300, + mtype: 1, + ext: { + ct: 0, + prebid: { + type: 'banner', + targeting: { + tl_size: '300x250', + tl_bidder: 'teal', + tl_pb: '0.20' + }, + meta: { + advertiserDomains: [ + 'teal.works' + ] + } + }, + origbidcpm: 0.286000000000000004 + } + } + ], + seat: 'appnexus', + group: 0 + } + ], + cur: 'USD', + ext: { + responsetimemillis: { + appnexus: 0 + }, + tmaxrequest: 750, + prebid: { + auctiontimestamp: 1678646619765, + passthrough: { + teal: { + bidder: spec.code + } + } + } + } +}; + +const S2S_RESPONSE_BIDDER = BID_RESPONSE.seatbid[0].seat; + +const buildRequest = (params) => { + const bidRequest = { + ...BID_REQUEST, + params: { + ...BID_REQUEST.params, + ...params, + }, + }; + var response = spec.buildRequests([bidRequest], BIDDER_REQUEST); + return response; +}; + +describe('Teal Bid Adaper', function () { + describe('buildRequests', () => { + const {data, url} = buildRequest(); + 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); + expect(data.imp[0].ext.prebid.storedrequest.id).equal(PLACEMENT); + }); + it('should include bidder code in passthrough object', () => { + expect(data.ext.prebid.passthrough.teal.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('buildRequests with subAccount', () => { + const {data} = buildRequest({ subAccount: SUB_ACCOUNT }); + it('should set the correct stored request ids', () => { + expect(data.ext.prebid.storedrequest.id).equal(SUB_ACCOUNT); + }); + }); + describe('interpreteResponse', () => { + const request = buildRequest(); + 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 = buildRequest({ 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', () => { + 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 the right endpoint', () => { + expect(endpoint).equal(`https://${PBS_HOST}/cookie_sync`); + }); + it('should sync to at least one bidders', () => { + expect(bidders.split(',').length).be.greaterThan(0); + }); + }); +}); diff --git a/test/spec/modules/ttdBidAdapter_spec.js b/test/spec/modules/ttdBidAdapter_spec.js index 9f98a734d1c..4580c514609 100644 --- a/test/spec/modules/ttdBidAdapter_spec.js +++ b/test/spec/modules/ttdBidAdapter_spec.js @@ -715,8 +715,8 @@ describe('ttdBidAdapter', function () { let clonedBidderRequest = {...deepClone(baseBidderRequest), ortb2}; const requestBody = testBuildRequests(baseBannerBidRequests, clonedBidderRequest).data; - validateExtFirstPartyData(requestBody.pmp.ext) - expect(requestBody.pmp.private_auction).to.equal(1) + validateExtFirstPartyData(requestBody.imp[0].pmp.ext) + expect(requestBody.imp[0].pmp.private_auction).to.equal(1) }); }); diff --git a/test/spec/modules/userId_spec.js b/test/spec/modules/userId_spec.js index c4f333e56ac..066f00feb8f 100644 --- a/test/spec/modules/userId_spec.js +++ b/test/spec/modules/userId_spec.js @@ -456,14 +456,54 @@ describe('User ID', function () { {'mockId2v1': {source: 'mock2source', atype: 2, getEidExt: () => ({v: 1})}}), createMockIdSubmodule('mockId2v2', null, null, {'mockId2v2': {source: 'mock2source', atype: 2, getEidExt: () => ({v: 2})}}), + createMockIdSubmodule('mockId2v3', null, null, { + 'mockId2v3'(ids) { + return { + source: 'mock2source', + inserter: 'ins', + ext: {v: 2}, + uids: ids.map(id => ({id, atype: 2})) + } + } + }), + createMockIdSubmodule('mockId2v4', null, null, { + 'mockId2v4'(ids) { + return ids.map(id => ({ + uids: [{id, atype: 0}], + source: 'mock2source', + inserter: 'ins', + ext: {v: 2} + })) + } + }) + ]); + }); + + it('should filter out non-string uid returned by generator functions', () => { + const eids = createEidsArray({ + mockId2v3: [null, 'id1', 123], + }); + expect(eids[0].uids).to.eql([ + { + atype: 2, + id: 'id1' + } ]); }); - it('should group UIDs by source and ext', () => { + it('should filter out entire EID if none of the uids are strings', () => { + const eids = createEidsArray({ + mockId2v3: [null], + }); + expect(eids).to.eql([]); + }) + + it('should group UIDs by everything except uid', () => { const eids = createEidsArray({ mockId1: ['mock-1-1', 'mock-1-2'], mockId2v1: ['mock-2-1', 'mock-2-2'], - mockId2v2: ['mock-2-1', 'mock-2-2'] + mockId2v2: ['mock-2-1', 'mock-2-2'], + mockId2v3: ['mock-2-1', 'mock-2-2'] }); expect(eids).to.eql([ { @@ -510,10 +550,50 @@ describe('User ID', function () { atype: 2, } ] + }, + { + source: 'mock2source', + inserter: 'ins', + ext: {v: 2}, + uids: [ + { + id: 'mock-2-1', + atype: 2, + }, + { + id: 'mock-2-2', + atype: 2, + } + ] } ]) }); + it('should group matching EIDs regardless of entry order', () => { + const eids = createEidsArray({ + mockId2v3: ['id1', 'id2'], + mockId2v4: ['id3'] + }); + expect(eids).to.eql([{ + source: 'mock2source', + inserter: 'ins', + uids: [ + { + id: 'id1', + atype: 2, + }, + { + id: 'id2', + atype: 2 + }, + { + id: 'id3', + atype: 0 + } + ], + ext: {v: 2} + }]) + }) it('when merging with pubCommonId, should not alter its eids', () => { const uid = { pubProvidedId: [ @@ -705,6 +785,27 @@ describe('User ID', function () { }); }); + it('pbjs.getUserIdsAsEids should pass config to eid function', async function () { + const eidFn = sinon.stub(); + init(config); + setSubmoduleRegistry([createMockIdSubmodule('mockId', null, null, { + mockId: eidFn + })]); + const moduleConfig = { + name: 'mockId', + value: {mockId: 'mockIdValue'}, + some: 'config' + }; + config.setConfig({ + userSync: { + auctionDelay: 10, + userIds: [moduleConfig] + } + }); + await getGlobal().getUserIdsAsync(); + sinon.assert.calledWith(eidFn, ['mockIdValue'], moduleConfig); + }) + it('pbjs.getUserIdsAsEids should prioritize user ids according to config available to core', () => { init(config); setSubmoduleRegistry([ diff --git a/test/spec/modules/visiblemeasuresBidAdapter_spec.js b/test/spec/modules/visiblemeasuresBidAdapter_spec.js index 55f2a74ce77..a6efeca73e2 100644 --- a/test/spec/modules/visiblemeasuresBidAdapter_spec.js +++ b/test/spec/modules/visiblemeasuresBidAdapter_spec.js @@ -147,7 +147,11 @@ describe('VisibleMeasuresBidAdapter', function () { 'coppa', 'ccpa', 'gdpr', - 'tmax' + 'tmax', + 'bcat', + 'badv', + 'bapp', + 'battr' ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); diff --git a/test/spec/modules/wurflRtdProvider_spec.js b/test/spec/modules/wurflRtdProvider_spec.js index 0ac324ef8b2..a683be5304c 100644 --- a/test/spec/modules/wurflRtdProvider_spec.js +++ b/test/spec/modules/wurflRtdProvider_spec.js @@ -52,7 +52,8 @@ describe('wurflRtdProvider', function () { pixel_density: 443, pointing_method: 'touchscreen', resolution_height: 1920, - resolution_width: 1080 + resolution_width: 1080, + wurfl_id: 'lg_nexus5_ver1', }; // expected analytics values @@ -100,6 +101,7 @@ describe('wurflRtdProvider', function () { const expectedURL = new URL(altHost); expectedURL.searchParams.set('debug', true); expectedURL.searchParams.set('mode', 'prebid'); + expectedURL.searchParams.set('wurfl_id', true); const callback = () => { const v = { @@ -147,7 +149,8 @@ describe('wurflRtdProvider', function () { pixel_density: 443, pointing_method: 'touchscreen', resolution_height: 1920, - resolution_width: 1080 + resolution_width: 1080, + wurfl_id: 'lg_nexus5_ver1', }, }, }, @@ -187,7 +190,8 @@ describe('wurflRtdProvider', function () { model_name: 'Nexus 5', pixel_density: 443, resolution_height: 1920, - resolution_width: 1080 + resolution_width: 1080, + wurfl_id: 'lg_nexus5_ver1', }, }, }, @@ -203,6 +207,7 @@ describe('wurflRtdProvider', function () { is_mobile: !0, model_name: 'Nexus 5', brand_name: 'Google', + wurfl_id: 'lg_nexus5_ver1', }, }, }, diff --git a/test/spec/modules/zeta_global_sspAnalyticsAdapter_spec.js b/test/spec/modules/zeta_global_sspAnalyticsAdapter_spec.js index a308eb44987..04554df560e 100644 --- a/test/spec/modules/zeta_global_sspAnalyticsAdapter_spec.js +++ b/test/spec/modules/zeta_global_sspAnalyticsAdapter_spec.js @@ -529,6 +529,7 @@ describe('Zeta Global SSP Analytics Adapter', function () { sandbox = sinon.sandbox.create(); requests = server.requests; sandbox.stub(events, 'getEvents').returns([]); + config.setConfig({ pageUrl: 'https://www.config.domain.com/index.html' }) }); afterEach(function () { @@ -624,8 +625,8 @@ describe('Zeta Global SSP Analytics Adapter', function () { shortname: 'name' } }); - expect(auctionSucceeded.domain).to.eql('test-zeta-ssp.net'); - expect(auctionSucceeded.page).to.eql('test-zeta-ssp.net/zeta-ssp/ssp/_dev/examples/page_banner.html'); + expect(auctionSucceeded.domain).to.eql('config.domain.com'); + expect(auctionSucceeded.page).to.eql('https://www.config.domain.com/index.html'); expect(auctionSucceeded.bid).to.be.deep.equal({ adUnitCode: '/19968336/header-bid-tag-0', adId: '5759bb3ef7be1e8', diff --git a/test/spec/native_spec.js b/test/spec/native_spec.js index 01214cdb3ae..7ea9b5949fe 100644 --- a/test/spec/native_spec.js +++ b/test/spec/native_spec.js @@ -402,7 +402,8 @@ describe('native.js', function () { 'returns native data': { renderDataHook(next, bidResponse) { next.bail({ - native: getNativeRenderingData(bidResponse, adUnit) + native: getNativeRenderingData(bidResponse, adUnit), + rendererVersion: 'native-render-version' }); }, renderSourceHook(next) { @@ -433,8 +434,9 @@ describe('native.js', function () { function checkRenderer(message) { if (withRenderer) { expect(message.renderer).to.eql('mock-native-renderer') + expect(message.rendererVersion).to.eql('native-render-version'); Object.entries(message).forEach(([key, val]) => { - if (!['native', 'adId', 'message', 'assets', 'renderer'].includes(key)) { + if (!['native', 'adId', 'message', 'assets', 'renderer', 'rendererVersion'].includes(key)) { expect(message.native[key]).to.eql(val); } }) diff --git a/test/spec/ortbConverter/priceFloors_spec.js b/test/spec/ortbConverter/priceFloors_spec.js index f6d37992711..73e94b6eb65 100644 --- a/test/spec/ortbConverter/priceFloors_spec.js +++ b/test/spec/ortbConverter/priceFloors_spec.js @@ -1,6 +1,7 @@ import {config} from 'src/config.js'; -import {setOrtbExtPrebidFloors, setOrtbImpBidFloor} from '../../../modules/priceFloors.js'; +import {setOrtbExtPrebidFloors, setOrtbImpBidFloor, setGranularBidfloors} from '../../../modules/priceFloors.js'; import 'src/prebid.js'; +import {ORTB_MTYPES} from '../../../libraries/ortbConverter/processors/mediaType.js'; describe('pbjs - ortb imp floor params', () => { before(() => { @@ -113,6 +114,87 @@ describe('pbjs - ortb imp floor params', () => { }) }); +describe('setOrtbImpExtBidFloor', () => { + let bidRequest, floor, currency, imp; + beforeEach(() => { + bidRequest = { + getFloor: sinon.stub().callsFake(() => ({floor, currency})) + } + imp = { + bidfloor: 1.23, + bidfloorcur: 'EUR' + }; + }); + + Object.values(ORTB_MTYPES).forEach(mediaType => { + describe(`${mediaType}.ext.bidfloor`, () => { + it(`should NOT be set if imp has no ${mediaType}`, () => { + setGranularBidfloors(imp, bidRequest); + expect(imp[mediaType]).to.not.exist; + }); + it('should NOT be set if floor is same as imp.bidfloor', () => { + floor = 1.23 + currency = 'EUR' + imp[mediaType] = {}; + setGranularBidfloors(imp, bidRequest); + expect(imp[mediaType].ext).to.not.exist; + sinon.assert.calledWith(bidRequest.getFloor, sinon.match({mediaType})) + }); + it('should be set otherwise', () => { + floor = 3.21; + currency = 'JPY'; + imp[mediaType] = {}; + setGranularBidfloors(imp, bidRequest); + expect(imp[mediaType].ext).to.eql({ + bidfloor: 3.21, + bidfloorcur: 'JPY' + }); + sinon.assert.calledWith(bidRequest.getFloor, sinon.match({mediaType})) + }); + }); + }); + describe('per-format floors', () => { + beforeEach(() => { + imp = { + banner: { + format: [ + {w: 1, h: 2}, + {w: 3, h: 4} + ] + } + } + }); + + it('should NOT be set if same as imp.bidfloor', () => { + floor = 1.23 + currency = 'EUR'; + setGranularBidfloors(imp, bidRequest); + expect(imp.banner.format.filter(fmt => fmt.bidfloor)).to.eql([]); + [[1, 2], [3, 4]].forEach(size => { + sinon.assert.calledWith(bidRequest.getFloor, sinon.match({size})); + }); + }); + + it('should be set otherwise', () => { + floor = 3.21; + currency = 'JPY'; + setGranularBidfloors(imp, bidRequest); + imp.banner.format.forEach((fmt) => { + expect(fmt.ext).to.eql({bidfloor: 3.21, bidfloorcur: 'JPY'}); + sinon.assert.calledWith(bidRequest.getFloor, sinon.match({mediaType: 'banner', size: [fmt.w, fmt.h]})); + }) + }); + + it('should not be set if format is missing w/h', () => { + floor = 3.21; + currency = 'JPY'; + delete imp.banner.format[0].w; + setGranularBidfloors(imp, bidRequest); + expect(imp.banner.format[0].ext).to.not.exist; + }) + }) +}) + describe('setOrtbExtPrebidFloors', () => { before(() => { config.setConfig({floors: {}});