diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml new file mode 100644 index 00000000000..8984252f4c3 --- /dev/null +++ b/.github/release-drafter.yml @@ -0,0 +1,28 @@ + +name-template: 'Prebid $RESOLVED_VERSION Release' +tag-template: '$RESOLVED_VERSION' +categories: + - title: '🚀 New Features' + label: 'feature' + - title: '🛠 Maintenance' + label: 'maintenance' + - title: '🐛 Bug Fixes' + labels: + - 'fix' + - 'bugfix' + - 'bug' +change-template: '- $TITLE (#$NUMBER)' +version-resolver: + major: + labels: + - 'major' + minor: + labels: + - 'minor' + patch: + labels: + - 'patch' + default: patch +template: | + ## In This Release + $CHANGES diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml new file mode 100644 index 00000000000..8152b61275d --- /dev/null +++ b/.github/workflows/release-drafter.yml @@ -0,0 +1,18 @@ +name: Release Drafter + +on: + push: + # branches to consider in the event; optional, defaults to all + branches: + - master + +jobs: + update_release_draft: + runs-on: ubuntu-latest + steps: + # Drafts your next Release notes as Pull Requests are merged into "master" + - uses: release-drafter/release-drafter@v5 + with: + config-name: release-drafter.yml + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/PR_REVIEW.md b/PR_REVIEW.md index 662a1a871c8..0519cbb7b6e 100644 --- a/PR_REVIEW.md +++ b/PR_REVIEW.md @@ -5,6 +5,17 @@ If the PR is for a standard bid adapter or a standard analytics adapter, just th For modules and core platform updates, the initial reviewer should request an additional team member to review as a sanity check. Merge should only happen when the PR has 2 `LGTM` from the core team and a documentation PR if required. +### Running Tests and Verifying Integrations + +General gulp commands include separate commands for serving the codebase on a built in webserver, creating code coverage reports and allowing serving integration examples. The `review-start` gulp command combinese those into one command. + +- Run `gulp review-start`, adding the host parameter `gulp review-start --host=0.0.0.0` will bind to all IPs on the machine + - A page will open which provides a hub for common reviewer tools. + - If you need to manually acceess the tools: + - Navigate to build/coverage/lcov-report/index.html to view coverage + - Navigate to integrationExamples/gpt/hellow_world.html for basic integration testing + - The hello_world.html and other exampls can be edited and used as needed to verify functionality + ### General PR review Process - All required global and bidder-adapter rules defined in the [Module Rules](https://docs.prebid.org/dev-docs/module-rules.html) must be followed. Please review these rules often - we depend on reviewers to enforce them. - Checkout the branch (these instructions are available on the github PR page as well). diff --git a/README.md b/README.md index 40df62ccee4..d87b70710b7 100644 --- a/README.md +++ b/README.md @@ -112,11 +112,11 @@ prebid.requestBids({ $ git clone https://github.com/prebid/Prebid.js.git $ cd Prebid.js - $ npm install + $ npm ci *Note:* You need to have `NodeJS` 12.16.1 or greater installed. -*Note:* In the 1.24.0 release of Prebid.js we have transitioned to using gulp 4.0 from using gulp 3.9.1. To comply with gulp's recommended setup for 4.0, you'll need to have `gulp-cli` installed globally prior to running the general `npm install`. This shouldn't impact any other projects you may work on that use an earlier version of gulp in its setup. +*Note:* In the 1.24.0 release of Prebid.js we have transitioned to using gulp 4.0 from using gulp 3.9.1. To comply with gulp's recommended setup for 4.0, you'll need to have `gulp-cli` installed globally prior to running the general `npm ci`. This shouldn't impact any other projects you may work on that use an earlier version of gulp in its setup. If you have a previous version of `gulp` installed globally, you'll need to remove it before installing `gulp-cli`. You can check if this is installed by running `gulp -v` and seeing the version that's listed in the `CLI` field of the output. If you have the `gulp` package installed globally, it's likely the same version that you'll see in the `Local` field. If you already have `gulp-cli` installed, it should be a lower major version (it's at version `2.0.1` at the time of the transition). @@ -202,6 +202,11 @@ To run the unit tests: gulp test ``` +To run the unit tests for a perticular file (example for pubmaticBidAdapter_spec.js): +```bash +gulp test --file "test/spec/modules/pubmaticBidAdapter_spec.js" +``` + To generate and view the code coverage reports: ```bash @@ -260,7 +265,7 @@ directory you will have sourcemaps available in your browser's developer tools. To run the example file, go to: -+ `http://localhost:9999/integrationExamples/gpt/pbjs_example_gpt.html` ++ `http://localhost:9999/integrationExamples/gpt/hello_world.html` As you make code changes, the bundles will be rebuilt and the page reloaded automatically. diff --git a/RELEASE_SCHEDULE.md b/RELEASE_SCHEDULE.md index 7b2c6244bd7..bfbd0772c3e 100644 --- a/RELEASE_SCHEDULE.md +++ b/RELEASE_SCHEDULE.md @@ -1,6 +1,14 @@ **Table of Contents** - [Release Schedule](#release-schedule) - [Release Process](#release-process) + - [1. Make sure that all PRs have been named and labeled properly per the PR Process](#1-make-sure-that-all-prs-have-been-named-and-labeled-properly-per-the-pr-process) + - [2. Make sure all browserstack tests are passing](#2-make-sure-all-browserstack-tests-are-passing) + - [3. Prepare Prebid Code](#3-prepare-prebid-code) + - [4. Verify the Release](#4-verify-the-release) + - [5. Create a GitHub release](#5-create-a-github-release) + - [6. Update coveralls _(skip for legacy)_](#6-update-coveralls-skip-for-legacy) + - [7. Distribute the code](#7-distribute-the-code) + - [8. Increment Version for Next Release](#8-increment-version-for-next-release) - [Beta Releases](#beta-releases) - [FAQs](#faqs) @@ -9,7 +17,7 @@ We aim to push a new release of Prebid.js every week on Tuesday. While the releases will be available immediately for those using direct Git access, -it will be about a week before the Prebid Org [Download Page](http://prebid.org/download.html) will be updated. +it will be about a week before the Prebid Org [Download Page](http://prebid.org/download.html) will be updated. You can determine what is in a given build using the [releases page](https://github.com/prebid/Prebid.js/releases) @@ -19,14 +27,20 @@ Announcements regarding releases will be made to the #headerbidding-dev channel _Note: If `github.com/prebid/Prebid.js` is not configured as the git origin for your repo, all of the following git commands will have to be modified to reference the proper remote (e.g. `upstream`)_ -1. Make Sure all browserstack tests are passing. On PR merge to master CircleCI will run unit tests on browserstack. Checking the last CircleCI build [here](https://circleci.com/gh/prebid/Prebid.js) for master branch will show you detailed results. - - In case of failure do following, +### 1. Make sure that all PRs have been named and labeled properly per the [PR Process](https://github.com/prebid/Prebid.js/blob/master/PR_REVIEW.md#general-pr-review-process) + * Do this by checking the latest draft release from the [releases page](https://github.com/prebid/Prebid.js/releases) and make sure nothing appears in the first section called "In This Release". If they do, please open the PRs and add the appropriate labels. + * Do a quick check that all the titles/descriptions look ok, and if not, adjust the PR title. + +### 2. Make sure all browserstack tests are passing + + On PR merge to master, CircleCI will run unit tests on browserstack. Checking the last CircleCI build [here](https://circleci.com/gh/prebid/Prebid.js) for master branch will show you detailed results.** + + In case of failure do following, - Try to fix the failing tests. - If you are not able to fix tests in time. Skip the test, create issue and tag contributor. - #### How to run tests in browserstack - + **How to run tests in browserstack** + _Note: the following browserstack information is only relevant for debugging purposes, if you will not be debugging then it can be skipped._ Set the environment variables. You may want to add these to your `~/.bashrc` for convenience. @@ -35,40 +49,40 @@ _Note: If `github.com/prebid/Prebid.js` is not configured as the git origin for export BROWSERSTACK_USERNAME="my browserstack username" export BROWSERSTACK_ACCESS_KEY="my browserstack access key" ``` - + ``` gulp test --browserstack >> prebid_test.log - + vim prebid_test.log // Will show the test results ``` -2. Prepare Prebid Code +### 3. Prepare Prebid Code Update the package.json version to become the current release. Then commit your changes. ``` - git commit -m "Prebid 1.x.x Release" + git commit -m "Prebid 4.x.x Release" git push ``` -3. Verify Release +### 4. Verify the Release Make sure your there are no more merges to master branch. Prebid code is clean and up to date. -4. Create a GitHub release +### 5. Create a GitHub release + + Edit the most recent [release notes](https://github.com/prebid/Prebid.js/releases) draft and make sure the correct version is set and the master branch is selected in the dropdown. Click `Publish release`. GitHub will create release tag. - Edit the most recent [release notes](https://github.com/prebid/Prebid.js/releases) draft and make sure the correct tag is in the dropdown. Click `Publish`. GitHub will create release tag. - - Pull these changes locally by running command + Pull these changes locally by running command ``` git pull git fetch --tags - ``` - + ``` + and verify the tag. -5. Update coveralls _(skip for legacy)_ +### 6. Update coveralls _(skip for legacy)_ We use https://coveralls.io/ to show parts of code covered by unit tests. @@ -80,35 +94,23 @@ _Note: If `github.com/prebid/Prebid.js` is not configured as the git origin for Run `gulp coveralls` to update code coverage history. -6. Distribute the code +### 7. Distribute the code - _Note: do not go to step 7 until step 6 has been verified completed._ + _Note: do not go to step 8 until step 7 has been verified completed._ Reach out to any of the Appnexus folks to trigger the jenkins job. - // TODO + // TODO: Jenkins job is moving files to appnexus cdn, pushing prebid.js to npm, purging cache and sending notification to slack. Move all the files from Appnexus CDN to jsDelivr and create bash script to do above tasks. -7. Post Release Version - - Update the version - Manually edit Prebid's package.json to become "1.x.x-pre" (using the values for the next release). Then commit your changes. +### 8. Increment Version for Next Release + + Update the version by manually editing Prebid's `package.json` to become "4.x.x-pre" (using the values for the next release). Then commit your changes. ``` git commit -m "Increment pre version" git push ``` - -8. Create new release draft - - Go to [github releases](https://github.com/prebid/Prebid.js/releases) and add a new draft for the next version of Prebid.js with the following template: -``` -## 🚀New Features - -## 🛠Maintenance - -## 🐛Bug Fixes -``` ## Beta Releases @@ -129,11 +131,8 @@ Characteristics of a `GA` release: ## FAQs **1. Is there flexibility in the schedule?** - -If a major bug is found in the current release, a maintenance patch will be done as soon as possible. - -It is unlikely that we will put out a maintenance patch at the request of a given bid adapter or module owner. +* If a major bug is found in the current release, a maintenance patch will be done as soon as possible. +* It is unlikely that we will put out a maintenance patch at the request of a given bid adapter or module owner. **2. What Pull Requests make it into a release?** - -Every PR that's merged into master will be part of a release. Here are the [PR review guidelines](https://github.com/prebid/Prebid.js/blob/master/PR_REVIEW.md). +* Every PR that's merged into master will be part of a release. Here are the [PR review guidelines](https://github.com/prebid/Prebid.js/blob/master/PR_REVIEW.md). diff --git a/gulpfile.js b/gulpfile.js index 879e34ae588..ac8b8c2dcd5 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -31,7 +31,7 @@ const execa = require('execa'); var prebid = require('./package.json'); var dateString = 'Updated : ' + (new Date()).toISOString().substring(0, 10); -var banner = '/* <%= prebid.name %> v<%= prebid.version %>\n' + dateString + ' */\n'; +var banner = '/* <%= prebid.name %> v<%= prebid.version %>\n' + dateString + '\nModules: <%= modules %> */\n'; var port = 9999; const FAKE_SERVER_HOST = argv.host ? argv.host : 'localhost'; const FAKE_SERVER_PORT = 4444; @@ -86,7 +86,8 @@ function viewCoverage(done) { connect.server({ port: coveragePort, root: 'build/coverage/lcov-report', - livereload: false + livereload: false, + debug: true }); opens('http://' + mylocalhost + ':' + coveragePort); done(); @@ -94,6 +95,19 @@ function viewCoverage(done) { viewCoverage.displayName = 'view-coverage'; +// View the reviewer tools page +function viewReview(done) { + var mylocalhost = (argv.host) ? argv.host : 'localhost'; + var reviewUrl = 'http://' + mylocalhost + ':' + port + '/integrationExamples/reviewerTools/index.html'; // reuse the main port from 9999 + + // console.log(`stdout: opening` + reviewUrl); + + opens(reviewUrl); + done(); +}; + +viewReview.displayName = 'view-review'; + // Watch Task with Live Reload function watch(done) { var mainWatcher = gulp.watch([ @@ -143,15 +157,20 @@ function makeWebpackPkg() { const analyticsSources = helpers.getAnalyticsSources(); const moduleSources = helpers.getModulePaths(externalModules); + const modulesString = getModulesListToAddInBanner(externalModules); return gulp.src([].concat(moduleSources, analyticsSources, 'src/prebid.js')) .pipe(helpers.nameModules(externalModules)) .pipe(webpackStream(cloned, webpack)) .pipe(uglify()) - .pipe(gulpif(file => file.basename === 'prebid-core.js', header(banner, { prebid: prebid }))) + .pipe(gulpif(file => file.basename === 'prebid-core.js', header(banner, { prebid: prebid, modules: modulesString }))) .pipe(gulp.dest('build/dist')); } +function getModulesListToAddInBanner(modules){ + return (modules.length > 0) ? modules.join(', ') : 'All available modules in current version.'; +} + function gulpBundle(dev) { return bundle(dev).pipe(gulp.dest('build/' + (dev ? 'dev' : 'dist'))); } @@ -201,6 +220,8 @@ function bundle(dev, moduleArr) { return gulp.src( entries ) + // Need to uodate the "Modules: ..." section in comment with the current modules list + .pipe(replace(/(Modules: )(.*?)(\*\/)/, ('$1' + getModulesListToAddInBanner(helpers.getArgModules()) + ' $3'))) .pipe(gulpif(dev, sourcemaps.init({ loadMaps: true }))) .pipe(concat(outputFileName)) .pipe(gulpif(!argv.manualEnable, footer('\n<%= global %>.processQueue();', { @@ -265,7 +286,7 @@ function test(done) { } else { var karmaConf = karmaConfMaker(false, argv.browserstack, argv.watch, argv.file); - var browserOverride = helpers.parseBrowserArgs(argv).map(helpers.toCapitalCase); + var browserOverride = helpers.parseBrowserArgs(argv); if (browserOverride.length > 0) { karmaConf.browsers = browserOverride; } @@ -383,4 +404,8 @@ gulp.task('e2e-test', gulp.series(clean, setupE2e, gulp.parallel('build-bundle-p gulp.task(bundleToStdout); gulp.task('bundle', gulpBundle.bind(null, false)); // used for just concatenating pre-built files with no build step +// build task for reviewers, runs test-coverage, serves, without watching +gulp.task(viewReview); +gulp.task('review-start', gulp.series(clean, lint, gulp.parallel('build-bundle-dev', watch, testCoverage), viewReview)); + module.exports = nodeBundle; diff --git a/integrationExamples/gpt/adUnitFloors.html b/integrationExamples/gpt/adUnitFloors.html new file mode 100644 index 00000000000..bb48a20ef78 --- /dev/null +++ b/integrationExamples/gpt/adUnitFloors.html @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + +

Prebid.js Test

+
Div-1
+
+ +
+ + + + diff --git a/integrationExamples/gpt/adloox.html b/integrationExamples/gpt/adloox.html new file mode 100644 index 00000000000..33f8b9be6a2 --- /dev/null +++ b/integrationExamples/gpt/adloox.html @@ -0,0 +1,211 @@ + + + + Prebid Display/Video Merged Auction with Adloox Integration + + + + + + + + +

Prebid Display/Video Merged Auction with Adloox Integration

+ +

div-1

+
+ +
+ +

div-2

+
+ +
+ +

video-1

+
+ + + + diff --git a/integrationExamples/gpt/creative_rendering.html b/integrationExamples/gpt/creative_rendering.html index aef8b7f1654..04d4736c631 100644 --- a/integrationExamples/gpt/creative_rendering.html +++ b/integrationExamples/gpt/creative_rendering.html @@ -1,9 +1,4 @@ - - - - + + + + + + + + + + + +Prebid + +

ID Import Library Example

+

Steps before logging in:

+ + + + + + + + + + + diff --git a/integrationExamples/gpt/jwplayerRtdProvider_example.html b/integrationExamples/gpt/jwplayerRtdProvider_example.html index 75eb85a2d8c..41c27b70ece 100644 --- a/integrationExamples/gpt/jwplayerRtdProvider_example.html +++ b/integrationExamples/gpt/jwplayerRtdProvider_example.html @@ -10,32 +10,30 @@ var PREBID_TIMEOUT = 1000; var adUnits = [{ - code: 'div-gpt-ad-1460505748561-0', - fpd: { - context: { - data: { - jwTargeting: { - // Note: the following Ids are placeholders and should be replaced with your Ids. - playerID: '123', - mediaID: 'abc' + code: 'div-gpt-ad-1460505748561-0', + ortb2Imp: { + ext: { + data: { + jwTargeting: { + // Note: the following Ids are placeholders and should be replaced with your Ids. + playerID: '123', + mediaID: 'abc' + } + } } - }, - } - }, - - mediaTypes: { - banner: { - sizes: [[300, 250], [300,600]], - } - }, - // Replace this object to test a new Adapter! - bids: [{ - bidder: 'appnexus', - params: { - placementId: 13144370 - } - }] - + }, + mediaTypes: { + banner: { + sizes: [[300, 250], [300,600]], + } + }, + // Replace this object to test a new Adapter! + bids: [{ + bidder: 'appnexus', + params: { + placementId: 13144370 + } + }] }]; var pbjs = pbjs || {}; diff --git a/integrationExamples/gpt/permutiveRtdProvider_example.html b/integrationExamples/gpt/permutiveRtdProvider_example.html new file mode 100644 index 00000000000..a06430bcdfa --- /dev/null +++ b/integrationExamples/gpt/permutiveRtdProvider_example.html @@ -0,0 +1,232 @@ + + + + + + + + + + + +

+

Basic Prebid.js Example

+
Div-1
+
+ +
+ +
+ +
Div-2
+
+ +
+ + + + diff --git a/integrationExamples/gpt/userId_example.html b/integrationExamples/gpt/userId_example.html index 7375293fdf0..973c295325a 100644 --- a/integrationExamples/gpt/userId_example.html +++ b/integrationExamples/gpt/userId_example.html @@ -1,334 +1,343 @@ + - - + + + ] + } + ]; - + - var adUnits = [ - { - code: 'test-div', - mediaTypes: { - banner: { - sizes: [[300,250],[300,600],[728,90]] - } - }, - bids: [ - { - bidder: 'rubicon', - params: { - accountId: '1001', - siteId: '113932', - zoneId: '535510' - } - } - ] - } - ]; + - + pbjs.que.push(function() { + pbjs.setConfig({ + debug: true, + consentManagement: { + cmpApi: 'iab', + timeout: 1000, + defaultGdprScope: true + }, + // consentManagement: { + // cmpApi: 'static', + // consentData: { + // consentString: 'BOEFEAyOEFEAyAHABDENAI4AAAB9vABAASA' + // vendorData: { + // purposeConsents: { + // '1': true + // } + // } + // } + // }, + userSync: { + userIds: [{ + name: "pubProvidedId", + params: { + eids: [{ + source: "domain.com", + uids:[{ + id: "value read from cookie or local storage", + atype: 1, + ext: { + stype: "ppuid" // allowable options are sha256email, DMP, ppuid for now + } + }] + },{ + source: "3rdpartyprovided.com", + uids:[{ + id: "value read from cookie or local storage", + atype: 3, + ext: { + stype: "sha256email" + } + }] + }], + eidsFunction: getHashedEmail // any user defined function that exists in the page + } + },{ + name: "unifiedId", + params: { + partner: "prebid", + url: "http://match.adsrvr.org/track/rid?ttd_pid=prebid&fmt=json" + }, + storage: { + type: "html5", + name: "unifiedid", + expires: 30 + }, + },{ + name: "intentIqId", + params: { + partner: 0, //Set your real IntentIQ partner ID here for production. + }, + storage: { + type: "cookie", + name: "intentIqId", + expires: 30 + }, + }, + { + name: "id5Id", + params: { + partner: 173 //Set your real ID5 partner ID here for production, please ask for one at http://id5.io/prebid + }, + storage: { + type: "cookie", + name: "id5id", + expires: 90, + refreshInSeconds: 8*3600 // Refresh frequency of cookies, defaulting to 'expires' + }, - + function sendAdserverRequest() { + if (pbjs.adserverRequestSent) return; + pbjs.adserverRequestSent = true; + googletag.cmd.push(function() { + pbjs.que.push(function() { + pbjs.setTargetingForGPTAsync(); + googletag.pubads().refresh(); + }); + }); + } - + setTimeout(function() { + sendAdserverRequest(); + }, FAILSAFE_TIMEOUT); + - - + - -

Rubicon Project Prebid

+ + -
- -
- + +

Rubicon Project Prebid

+
+ +
+ diff --git a/integrationExamples/mass/index.html b/integrationExamples/mass/index.html new file mode 100644 index 00000000000..80fe4cfb934 --- /dev/null +++ b/integrationExamples/mass/index.html @@ -0,0 +1,110 @@ + + + + + + + + + + + + + + + +
+

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

+
+ +
+
+ + diff --git a/integrationExamples/postbid/postbid_prebidServer_example.html b/integrationExamples/postbid/postbid_prebidServer_example.html new file mode 100644 index 00000000000..3af7b010872 --- /dev/null +++ b/integrationExamples/postbid/postbid_prebidServer_example.html @@ -0,0 +1,86 @@ + + + + + + + + + + + diff --git a/integrationExamples/reviewerTools/index.html b/integrationExamples/reviewerTools/index.html new file mode 100755 index 00000000000..2732cb4fd88 --- /dev/null +++ b/integrationExamples/reviewerTools/index.html @@ -0,0 +1,40 @@ + + + + + + + Prebid Reviewer Tools + + + + +
+
+
+

Reviewer Tools

+

Below are links to the most common tool used by Prebid reviewers. For more info on PR review processes check out the General PR Review Process page on Github.

+

Common

+ +

Other Tools

+ +

Documentation & Training Material

+ +
+
+
+ + \ No newline at end of file diff --git a/modules/.submodules.json b/modules/.submodules.json index 4e02391129a..f71325d38a9 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -16,10 +16,16 @@ "zeotapIdPlusIdSystem", "haloIdSystem", "quantcastIdSystem", + "nextrollIdSystem", "idxIdSystem", "fabrickIdSystem", "verizonMediaIdSystem", - "pubProvidedIdSystem" + "pubProvidedIdSystem", + "mwOpenLinkIdSystem", + "tapadIdSystem", + "novatiqIdSystem", + "uid2IdSystem", + "admixerIdSystem" ], "adpod": [ "freeWheelAdserverVideo", diff --git a/modules/a4gBidAdapter.js b/modules/a4gBidAdapter.js index b7d8722e9f9..1961dba1f10 100644 --- a/modules/a4gBidAdapter.js +++ b/modules/a4gBidAdapter.js @@ -70,8 +70,7 @@ export const spec = { if (response.cpm > 0) { const bidResponse = { requestId: response.id, - creativeId: response.id, - adId: response.id, + creativeId: response.crid || response.id, cpm: response.cpm, width: response.width, height: response.height, diff --git a/modules/adWMGBidAdapter.js b/modules/adWMGBidAdapter.js index 3a0a8a22274..87c40db51e6 100644 --- a/modules/adWMGBidAdapter.js +++ b/modules/adWMGBidAdapter.js @@ -113,14 +113,18 @@ export const spec = { return bidResponses; }, getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => { - if (gdprConsent) { + if (gdprConsent && SYNC_ENDPOINT.indexOf('gdpr') === -1) { SYNC_ENDPOINT = utils.tryAppendQueryString(SYNC_ENDPOINT, 'gdpr', (gdprConsent.gdprApplies ? 1 : 0)); } - if (gdprConsent && typeof gdprConsent.consentString === 'string') { + if (gdprConsent && typeof gdprConsent.consentString === 'string' && SYNC_ENDPOINT.indexOf('gdpr_consent') === -1) { SYNC_ENDPOINT = utils.tryAppendQueryString(SYNC_ENDPOINT, 'gdpr_consent', gdprConsent.consentString); } + if (SYNC_ENDPOINT.slice(-1) === '&') { + SYNC_ENDPOINT = SYNC_ENDPOINT.slice(0, -1); + } + /* if (uspConsent) { SYNC_ENDPOINT = utils.tryAppendQueryString(SYNC_ENDPOINT, 'us_privacy', uspConsent); } */ diff --git a/modules/adWMGBidAdapter.md b/modules/adWMGBidAdapter.md index 8c277b803db..ec5541e6168 100644 --- a/modules/adWMGBidAdapter.md +++ b/modules/adWMGBidAdapter.md @@ -12,10 +12,11 @@ Module that connects to adWMG demand sources to fetch bids. Supports 'banner' ad # Bid Parameters -| Key | Required | Example | Description | -| --- | -------- | ------- | ----------- | -| `publisherId` | yes | `'5cebea3c9eea646c7b623d5e'` | publisher ID from WMG Dashboard | -| `IABCategories` | no | `['IAB1', 'IAB5']` | IAB ad categories for adUnit | +| Key | Required | Example | Description | +| --------------- | -------- | -----------------------------| ------------------------------- | +| `publisherId` | yes | `'5cebea3c9eea646c7b623d5e'` | publisher ID from WMG Dashboard | +| `IABCategories` | no | `['IAB1', 'IAB5']` | IAB ad categories for adUnit | +| `floorCPM` | no | `0.5` | Floor price for adUnit | # Test Parameters diff --git a/modules/adagioBidAdapter.js b/modules/adagioBidAdapter.js index 6f7feec59c9..134303bf400 100644 --- a/modules/adagioBidAdapter.js +++ b/modules/adagioBidAdapter.js @@ -8,22 +8,22 @@ import sha256 from 'crypto-js/sha256.js'; import { getStorageManager } from '../src/storageManager.js'; import { getRefererInfo } from '../src/refererDetection.js'; import { createEidsArray } from './userId/eids.js'; -import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { Renderer } from '../src/Renderer.js'; import { OUTSTREAM } from '../src/video.js'; export const BIDDER_CODE = 'adagio'; export const LOG_PREFIX = 'Adagio:'; -export const VERSION = '2.6.0'; +export const VERSION = '2.8.0'; export const FEATURES_VERSION = '1'; export const ENDPOINT = 'https://mp.4dex.io/prebid'; -export const SUPPORTED_MEDIA_TYPES = [BANNER, VIDEO]; +export const SUPPORTED_MEDIA_TYPES = [BANNER, NATIVE, VIDEO]; export const ADAGIO_TAG_URL = 'https://script.4dex.io/localstore.js'; export const ADAGIO_LOCALSTORAGE_KEY = 'adagioScript'; export const GVLID = 617; export const storage = getStorageManager(GVLID, 'adagio'); export const RENDERER_URL = 'https://script.4dex.io/outstream-player.js'; - +export const MAX_SESS_DURATION = 30 * 60 * 1000; export const ADAGIO_PUBKEY = `-----BEGIN PUBLIC KEY----- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC9el0+OEn6fvEh1RdVHQu4cnT0 jFSzIbGJJyg3cKqvtE6A0iaz9PkIdJIvSSSNrmJv+lRGKPEyRA/VnzJIieL39Ngl @@ -62,6 +62,8 @@ export const ORTB_VIDEO_PARAMS = { let currentWindow; +const EXT_DATA = {} + export function adagioScriptFromLocalStorageCb(ls) { try { if (!ls) { @@ -108,6 +110,9 @@ export function getAdagioScript() { // It's an antipattern regarding the TCF2 enforcement logic // but it's the only way to respect the user choice update. window.localStorage.removeItem(ADAGIO_LOCALSTORAGE_KEY); + // Extra data from external script. + // This key is removed only if localStorage is not accessible. + window.localStorage.removeItem('adagio'); } }); } @@ -131,6 +136,37 @@ function isSafeFrameWindow() { return !!(ws.$sf && ws.$sf.ext); } +// Get localStorage "adagio" data to be passed to the request +export function prepareExchange(storageValue) { + const adagioStorage = JSON.parse(storageValue, function(name, value) { + if (!name.startsWith('_') || name === '') { + return value; + } + }); + let random = utils.deepAccess(adagioStorage, 'session.rnd'); + let newSession = false; + + if (internal.isNewSession(adagioStorage)) { + newSession = true; + random = Math.random(); + } + + const data = { + session: { + new: newSession, + rnd: random + } + } + + utils.mergeDeep(EXT_DATA, adagioStorage, data); + + internal.enqueue({ + action: 'session', + ts: Date.now(), + data: EXT_DATA + }); +} + function initAdagio() { if (canAccessTopWindow()) { currentWindow = (canAccessTopWindow()) ? utils.getWindowTop() : utils.getWindowSelf(); @@ -146,6 +182,14 @@ function initAdagio() { w.ADAGIO.versions.adagioBidderAdapter = VERSION; w.ADAGIO.isSafeFrameWindow = isSafeFrameWindow(); + storage.getDataFromLocalStorage('adagio', (storageData) => { + try { + internal.prepareExchange(storageData); + } catch (e) { + utils.logError(LOG_PREFIX, e); + } + }); + getAdagioScript(); } @@ -561,6 +605,21 @@ function isRendererPreferredFromPublisher(bidRequest) { ); } +/** + * + * @param {object} adagioStorage + * @returns {boolean} + */ +function isNewSession(adagioStorage) { + const now = Date.now(); + const { lastActivityTime, vwSmplg } = utils.deepAccess(adagioStorage, 'session', {}); + return ( + !utils.isNumber(lastActivityTime) || + !utils.isNumber(vwSmplg) || + (now - lastActivityTime) > MAX_SESS_DURATION + ) +} + export const internal = { enqueue, getOrAddAdagioAdUnit, @@ -577,7 +636,9 @@ export const internal = { getCurrentWindow, supportIObs, canAccessTopWindow, - isRendererPreferredFromPublisher + isRendererPreferredFromPublisher, + isNewSession, + prepareExchange }; function _getGdprConsent(bidderRequest) { @@ -687,6 +748,112 @@ function _renderer(bid) { }); } +function _parseNativeBidResponse(bid) { + if (!bid.admNative || !Array.isArray(bid.admNative.assets)) { + utils.logError(`${LOG_PREFIX} Invalid native response`); + return; + } + + const native = {} + + function addAssetDataValue(data) { + const map = { + 1: 'sponsoredBy', // sponsored + 2: 'body', // desc + 3: 'rating', + 4: 'likes', + 5: 'downloads', + 6: 'price', + 7: 'salePrice', + 8: 'phone', + 9: 'address', + 10: 'body2', // desc2 + 11: 'displayUrl', + 12: 'cta' + } + if (map.hasOwnProperty(data.type) && typeof data.value === 'string') { + native[map[data.type]] = data.value; + } + } + + // assets + bid.admNative.assets.forEach(asset => { + if (asset.title) { + native.title = asset.title.text + } else if (asset.data) { + addAssetDataValue(asset.data) + } else if (asset.img) { + switch (asset.img.type) { + case 1: + native.icon = { + url: asset.img.url, + width: asset.img.w, + height: asset.img.h + }; + break; + default: + native.image = { + url: asset.img.url, + width: asset.img.w, + height: asset.img.h + }; + break; + } + } + }); + + if (bid.admNative.link) { + if (bid.admNative.link.url) { + native.clickUrl = bid.admNative.link.url; + } + if (Array.isArray(bid.admNative.link.clickTrackers)) { + native.clickTrackers = bid.admNative.link.clickTrackers + } + } + + if (Array.isArray(bid.admNative.eventtrackers)) { + native.impressionTrackers = []; + bid.admNative.eventtrackers.forEach(tracker => { + // Only Impression events are supported. Prebid does not support Viewability events yet. + if (tracker.event !== 1) { + return; + } + + // methods: + // 1: image + // 2: js + // note: javascriptTrackers is a string. If there's more than one JS tracker in bid response, the last script will be used. + switch (tracker.method) { + case 1: + native.impressionTrackers.push(tracker.url); + break; + case 2: + native.javascriptTrackers = ``; + break; + } + }); + } else { + native.impressionTrackers = Array.isArray(bid.admNative.imptrackers) ? bid.admNative.imptrackers : []; + if (bid.admNative.jstracker) { + native.javascriptTrackers = bid.admNative.jstracker; + } + } + + if (bid.admNative.privacy) { + native.privacyLink = bid.admNative.privacy; + } + + if (bid.admNative.ext) { + native.ext = {} + + if (bid.admNative.ext.bvw) { + native.ext.adagio_bvw = bid.admNative.ext.bvw; + } + } + + bid.native = native +} + export const spec = { code: BIDDER_CODE, gvlid: GVLID, @@ -812,6 +979,7 @@ export const spec = { site: site, pageviewId: pageviewId, adUnits: groupedAdUnits[organizationId], + data: EXT_DATA, regs: { gdpr: gdprConsent, coppa: coppa, @@ -873,6 +1041,10 @@ export const spec = { } } + if (bidObj.mediaType === NATIVE) { + _parseNativeBidResponse(bidObj); + } + bidObj.site = bidReq.params.site; bidObj.placement = bidReq.params.placement; bidObj.pagetype = bidReq.params.pagetype; diff --git a/modules/adagioBidAdapter.md b/modules/adagioBidAdapter.md index aa79338d79e..46656d88d37 100644 --- a/modules/adagioBidAdapter.md +++ b/modules/adagioBidAdapter.md @@ -38,8 +38,8 @@ Connects to Adagio demand source to fetch bids. category: 'sport', // Recommended. Category of the content displayed in the page. subcategory: 'handball', // Optional. Subcategory of the content displayed in the page. postBid: false, // Optional. Use it in case of Post-bid integration only. - useAdUnitCodeAsAdUnitElementId: false // Optional. Use it by-pass adUnitElementId and use the adUnit code as value - useAdUnitCodeAsPlacement: false // Optional. Use it to by-pass placement and use the adUnit code as value + useAdUnitCodeAsAdUnitElementId: false, // Optional. Use it by-pass adUnitElementId and use the adUnit code as value + useAdUnitCodeAsPlacement: false, // Optional. Use it to by-pass placement and use the adUnit code as value // Optional debug mode, used to get a bid response with expected cpm. debug: { enabled: true, @@ -78,8 +78,8 @@ Connects to Adagio demand source to fetch bids. category: 'sport', // Recommended. Category of the content displayed in the page. subcategory: 'handball', // Optional. Subcategory of the content displayed in the page. postBid: false, // Optional. Use it in case of Post-bid integration only. - useAdUnitCodeAsAdUnitElementId: false // Optional. Use it by-pass adUnitElementId and use the adUnit code as value - useAdUnitCodeAsPlacement: false // Optional. Use it to by-pass placement and use the adUnit code as value + useAdUnitCodeAsAdUnitElementId: false, // Optional. Use it by-pass adUnitElementId and use the adUnit code as value + useAdUnitCodeAsPlacement: false, // Optional. Use it to by-pass placement and use the adUnit code as value video: { skip: 0 // OpenRTB 2.5 video options defined here override ones defined in mediaTypes. @@ -91,6 +91,58 @@ Connects to Adagio demand source to fetch bids. } } }] + }, + { + code: 'article_native', + mediaTypes: { + native: { + // generic Prebid options + title: { + required: true, + len: 80 + }, + // … + // Custom Adagio data assets + ext: { + adagio_bvw: { + required: false + } + } + } + }, + bids: [{ + bidder: 'adagio', // Required + params: { + organizationId: '1002', // Required - Organization ID provided by Adagio. + site: 'adagio-io', // Required - Site Name provided by Adagio. + placement: 'in_article', // Required. Refers to the placement of an adunit in a page. Must not contain any information about the type of device. Other example: `mpu_btf'. + adUnitElementId: 'article_native', // Required - AdUnit element id. Refers to the adunit id in a page. Usually equals to the adunit code above. + + // The following params are limited to 30 characters, + // and can only contain the following characters: + // - alphanumeric (A-Z+a-z+0-9, case-insensitive) + // - dashes `-` + // - underscores `_` + // Also, each param can have at most 50 unique active values (case-insensitive). + pagetype: 'article', // Highly recommended. The pagetype describes what kind of content will be present in the page. + environment: 'mobile', // Recommended. Environment where the page is displayed. + category: 'sport', // Recommended. Category of the content displayed in the page. + subcategory: 'handball', // Optional. Subcategory of the content displayed in the page. + postBid: false, // Optional. Use it in case of Post-bid integration only. + useAdUnitCodeAsAdUnitElementId: false, // Optional. Use it by-pass adUnitElementId and use the adUnit code as value + useAdUnitCodeAsPlacement: false, // Optional. Use it to by-pass placement and use the adUnit code as value + // Optional OpenRTB Native 1.2 request object. Only `context`, `plcmttype` fields are supported. + native: { + context: 1, + plcmttype: 2 + }, + // Optional debug mode, used to get a bid response with expected cpm. + debug: { + enabled: true, + cpm: 3.00 // default to 1.00 + } + } + }] } ]; diff --git a/modules/adblender.md b/modules/adblender.md new file mode 100644 index 00000000000..e70b2a4a8ed --- /dev/null +++ b/modules/adblender.md @@ -0,0 +1,36 @@ +# Overview + +Module Name: AdBlender Bidder Adapter +Module Type: Bidder Adapter +Maintainer: contact@ad-blender.com + +# Description + +Connects to AdBlender demand source to fetch bids. +Banner and Video formats are supported. +Please use ```adblender``` as the bidder code. +#Bidder Config +You can set an alternate endpoint url `pbjs.setBidderConfig` for the bidder `adblender` +``` +pbjs.setBidderConfig({ + bidders: ["adblender"], + config: {"adblender": { "endpoint_url": "https://inv-nets.admixer.net/adblender.1.1.aspx"}} + }) +``` +# Ad Unit Example +``` + var adUnits = [ + { + code: 'desktop-banner-ad-div', + sizes: [[300, 250]], // a display size + bids: [ + { + bidder: "adblender", + params: { + zone: 'fb3d34d0-7a88-4a4a-a5c9-8088cd7845f4' + } + } + ] + } + ]; +``` diff --git a/modules/addefendBidAdapter.js b/modules/addefendBidAdapter.js new file mode 100644 index 00000000000..18cafe829b5 --- /dev/null +++ b/modules/addefendBidAdapter.js @@ -0,0 +1,79 @@ +import {registerBidder} from '../src/adapters/bidderFactory.js'; + +const BIDDER_CODE = 'addefend'; + +export const spec = { + code: BIDDER_CODE, + hostname: 'https://addefend-platform.com', + + getHostname() { + return this.hostname; + }, + isBidRequestValid: function(bid) { + return (bid.sizes !== undefined && bid.bidId !== undefined && bid.params !== undefined && + (bid.params.pageId !== undefined && (typeof bid.params.pageId === 'string')) && + (bid.params.placementId !== undefined && (typeof bid.params.placementId === 'string'))); + }, + buildRequests: function(validBidRequests, bidderRequest) { + let bid = { + v: $$PREBID_GLOBAL$$.version, + auctionId: false, + pageId: false, + gdpr_consent: bidderRequest.gdprConsent && bidderRequest.gdprConsent.consentString ? bidderRequest.gdprConsent.consentString : '', + referer: bidderRequest.refererInfo.referer, + bids: [], + }; + + for (var i = 0; i < validBidRequests.length; i++) { + let vb = validBidRequests[i]; + let o = vb.params; + bid.auctionId = vb.auctionId; + o.bidId = vb.bidId; + o.transactionId = vb.transactionId; + o.sizes = []; + if (o.trafficTypes) { + bid.trafficTypes = o.trafficTypes; + } + delete o.trafficTypes; + + bid.pageId = o.pageId; + delete o.pageId; + + if (vb.sizes && Array.isArray(vb.sizes)) { + for (var j = 0; j < vb.sizes.length; j++) { + let s = vb.sizes[j]; + if (Array.isArray(s) && s.length == 2) { + o.sizes.push(s[0] + 'x' + s[1]); + } + } + } + bid.bids.push(o); + } + return [{ + method: 'POST', + url: this.getHostname() + '/bid', + options: { withCredentials: true }, + data: bid + }]; + }, + interpretResponse: function(serverResponse, request) { + const requiredKeys = ['requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', 'netRevenue', 'currency', 'advertiserDomains']; + const validBidResponses = []; + serverResponse = serverResponse.body; + if (serverResponse && (serverResponse.length > 0)) { + serverResponse.forEach((bid) => { + const bidResponse = {}; + for (const requiredKey of requiredKeys) { + if (!bid.hasOwnProperty(requiredKey)) { + return []; + } + bidResponse[requiredKey] = bid[requiredKey]; + } + validBidResponses.push(bidResponse); + }); + } + return validBidResponses; + } +} + +registerBidder(spec); diff --git a/modules/addefendBidAdapter.md b/modules/addefendBidAdapter.md new file mode 100644 index 00000000000..f1ac3ff2c46 --- /dev/null +++ b/modules/addefendBidAdapter.md @@ -0,0 +1,43 @@ +# Overview + +``` +Module Name: AdDefend Bid Adapter +Module Type: Bidder Adapter +Maintainer: prebid@addefend.com +``` + +# Description + +Module that connects to AdDefend as a demand source. + +## Parameters +| Param | Description | Optional | Default | +| ------------- | ------------- | ----- | ----- | +| pageId | id assigned to the website in the AdDefend system. (ask AdDefend support) | no | - | +| placementId | id of the placement in the AdDefend system. (ask AdDefend support) | no | - | +| trafficTypes | comma seperated list of the following traffic types:
ADBLOCK - user has a activated adblocker
PM - user has firefox private mode activated
NC - user has not given consent
NONE - user traffic is none of the above, this usually means this is a "normal" user.
| yes | ADBLOCK | + + +# Test Parameters +``` + var adUnits = [ + { + code: 'test-div', + mediaTypes: { + banner: { + sizes: [[970, 250]], // a display size + } + }, + bids: [ + { + bidder: "addefend", + params: { + pageId: "887", + placementId: "9398", + trafficTypes: "ADBLOCK" + } + } + ] + } + ]; +``` diff --git a/modules/adformBidAdapter.js b/modules/adformBidAdapter.js index 05e45a428d3..dc30e152861 100644 --- a/modules/adformBidAdapter.js +++ b/modules/adformBidAdapter.js @@ -5,6 +5,7 @@ import { config } from '../src/config.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { Renderer } from '../src/Renderer.js'; import * as utils from '../src/utils.js'; +import includes from 'core-js-pure/features/array/includes.js'; const OUTSTREAM_RENDERER_URL = 'https://s2.adform.net/banners/scripts/video/outstream/render.js'; @@ -24,13 +25,32 @@ export const spec = { var request = []; var globalParams = [ [ 'adxDomain', 'adx.adform.net' ], [ 'fd', 1 ], [ 'url', null ], [ 'tid', null ], [ 'eids', eids ] ]; + const targetingParams = { mkv: [], mkw: [], msw: [] }; + var bids = JSON.parse(JSON.stringify(validBidRequests)); var bidder = (bids[0] && bids[0].bidder) || BIDDER_CODE; + + // set common targeting options as query params + if (bids.length > 1) { + for (let key in targetingParams) { + if (targetingParams.hasOwnProperty(key)) { + const collection = bids.map(bid => ((bid.params[key] && bid.params[key].split(',')) || [])); + targetingParams[key] = collection.reduce(intersects); + if (targetingParams[key].length) { + bids.forEach((bid, index) => { + bid.params[key] = collection[index].filter(item => !includes(targetingParams[key], item)); + }); + } + } + } + } + for (i = 0, l = bids.length; i < l; i++) { bid = bids[i]; if ((bid.params.priceType === 'net') || (bid.params.pt === 'net')) { netRevenue = 'net'; } + for (j = 0, k = globalParams.length; j < k; j++) { _key = globalParams[j][0]; _value = bid[_key] || bid.params[_key]; @@ -65,6 +85,12 @@ export const spec = { request.push('us_privacy=' + bidderRequest.uspConsent); } + for (let key in targetingParams) { + if (targetingParams.hasOwnProperty(key)) { + globalParams.push([ key, targetingParams[key].join(',') ]); + } + } + for (i = 1, l = globalParams.length; i < l; i++) { _key = globalParams[i][0]; _value = globalParams[i][1]; @@ -114,6 +140,10 @@ export const spec = { return result; }, {}); } + + function intersects(col1, col2) { + return col1.filter(item => includes(col2, item)); + } }, interpretResponse: function (serverResponse, bidRequest) { const VALID_RESPONSES = { diff --git a/modules/adhashBidAdapter.js b/modules/adhashBidAdapter.js new file mode 100644 index 00000000000..b30f9cebbc1 --- /dev/null +++ b/modules/adhashBidAdapter.js @@ -0,0 +1,102 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import includes from 'core-js-pure/features/array/includes.js'; +import { BANNER } from '../src/mediaTypes.js'; + +const VERSION = '1.0'; + +export const spec = { + code: 'adhash', + url: 'https://bidder.adhash.org/rtb?version=' + VERSION + '&prebid=true', + supportedMediaTypes: [ BANNER ], + + isBidRequestValid: (bid) => { + try { + const { publisherId, platformURL } = bid.params; + return ( + includes(Object.keys(bid.mediaTypes), BANNER) && + typeof publisherId === 'string' && + publisherId.length === 42 && + typeof platformURL === 'string' && + platformURL.length >= 13 + ); + } catch (error) { + return false; + } + }, + + buildRequests: (validBidRequests, bidderRequest) => { + const { gdprConsent } = bidderRequest; + const { url } = spec; + const bidRequests = []; + let referrer = ''; + if (bidderRequest && bidderRequest.refererInfo) { + referrer = bidderRequest.refererInfo.referer; + } + for (var i = 0; i < validBidRequests.length; i++) { + var index = Math.floor(Math.random() * validBidRequests[i].sizes.length); + var size = validBidRequests[i].sizes[index].join('x'); + bidRequests.push({ + method: 'POST', + url: url, + bidRequest: validBidRequests[i], + data: { + timezone: new Date().getTimezoneOffset() / 60, + location: referrer, + publisherId: validBidRequests[i].params.publisherId, + size: { + screenWidth: window.screen.width, + screenHeight: window.screen.height + }, + navigator: { + platform: window.navigator.platform, + language: window.navigator.language, + userAgent: window.navigator.userAgent + }, + creatives: [{ + size: size, + position: validBidRequests[i].adUnitCode + }], + blockedCreatives: [], + currentTimestamp: new Date().getTime(), + recentAds: [], + GDPR: gdprConsent + }, + options: { + withCredentials: false, + crossOrigin: true + }, + }); + } + return bidRequests; + }, + + interpretResponse: (serverResponse, request) => { + const responseBody = serverResponse ? serverResponse.body : {}; + + if (!responseBody.creatives || responseBody.creatives.length === 0) { + return []; + } + + const publisherURL = JSON.stringify(request.bidRequest.params.platformURL); + const oneTimeId = request.bidRequest.adUnitCode + Math.random().toFixed(16).replace('0.', '.'); + const bidderResponse = JSON.stringify({ responseText: JSON.stringify(responseBody) }); + const requestData = JSON.stringify(request.data); + + return [{ + requestId: request.bidRequest.bidId, + cpm: responseBody.creatives[0].costEUR, + ad: + `
+ + `, + width: request.bidRequest.sizes[0][0], + height: request.bidRequest.sizes[0][1], + creativeId: request.bidRequest.adUnitCode, + netRevenue: true, + currency: 'EUR', + ttl: 60 + }]; + } +}; + +registerBidder(spec); diff --git a/modules/adhashBidAdapter.md b/modules/adhashBidAdapter.md new file mode 100644 index 00000000000..c1093e0ccd7 --- /dev/null +++ b/modules/adhashBidAdapter.md @@ -0,0 +1,43 @@ +# Overview + +``` +Module Name: AdHash Bidder Adapter +Module Type: Bidder Adapter +Maintainer: damyan@adhash.org +``` + +# Description + +Here is what you need for Prebid integration with AdHash: +1. Register with AdHash. +2. Once registered and approved, you will receive a Publisher ID and Platform URL. +3. Use the Publisher ID and Platform URL as parameters in params. + +Please note that a number of AdHash functionalities are not supported in the Prebid.js integration: +* Cookie-less frequency and recency capping; +* Audience segments; +* Price floors and passback tags, as they are not needed in the Prebid.js setup; +* Reservation for direct deals only, as bids are evaluated based on their price. + +# Test Parameters +``` + var adUnits = [ + { + code: '/19968336/header-bid-tag-1', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bids: [ + { + bidder: 'adhash', + params: { + publisherId: '0x1234567890123456789012345678901234567890', + platformURL: 'https://adhash.org/p/struma/' + } + } + ] + } + ]; +``` \ No newline at end of file diff --git a/modules/adheseBidAdapter.js b/modules/adheseBidAdapter.js index 80758668a95..b9dbae529ba 100644 --- a/modules/adheseBidAdapter.js +++ b/modules/adheseBidAdapter.js @@ -22,16 +22,25 @@ export const spec = { } const { gdprConsent, refererInfo } = bidderRequest; - const targets = validBidRequests.map(bid => bid.params.data).reduce(mergeTargets, {}); const gdprParams = (gdprConsent && gdprConsent.consentString) ? { xt: [gdprConsent.consentString] } : {}; const refererParams = (refererInfo && refererInfo.referer) ? { xf: [base64urlEncode(refererInfo.referer)] } : {}; - const id5Params = (getId5Id(validBidRequests)) ? { x5: [getId5Id(validBidRequests)] } : {}; - const slots = validBidRequests.map(bid => ({ slotname: bidToSlotName(bid) })); + const commonParams = { ...gdprParams, ...refererParams }; + + const slots = validBidRequests.map(bid => ({ + slotname: bidToSlotName(bid), + parameters: cleanTargets(bid.params.data) + })); const payload = { slots: slots, - parameters: { ...targets, ...gdprParams, ...refererParams, ...id5Params } - } + parameters: commonParams, + vastContentAsUrl: true, + user: { + ext: { + eids: getEids(validBidRequests), + } + } + }; const account = getAccount(validBidRequests); const uri = 'https://ads-' + account + '.adhese.com/json'; @@ -87,7 +96,7 @@ function adResponse(bid, ad) { const bidResponse = getbaseAdResponse({ requestId: bid.bidId, - mediaType: getMediaType(markup), + mediaType: ad.extension.mediaType, cpm: Number(price.amount), currency: price.currency, width: Number(ad.width), @@ -102,7 +111,11 @@ function adResponse(bid, ad) { }); if (bidResponse.mediaType === VIDEO) { - bidResponse.vastXml = markup; + if (ad.cachedBodyUrl) { + bidResponse.vastUrl = ad.cachedBodyUrl + } else { + bidResponse.vastXml = markup; + } } else { const counter = ad.impressionCounter ? "" : ''; bidResponse.ad = markup + counter; @@ -110,7 +123,8 @@ function adResponse(bid, ad) { return bidResponse; } -function mergeTargets(targets, target) { +function cleanTargets(target) { + const targets = {}; if (target) { Object.keys(target).forEach(function (key) { const val = target[key]; @@ -149,9 +163,9 @@ function getAccount(validBidRequests) { return validBidRequests[0].params.account; } -function getId5Id(validBidRequests) { - if (validBidRequests[0] && validBidRequests[0].userId && validBidRequests[0].userId.id5id && validBidRequests[0].userId.id5id.uid) { - return validBidRequests[0].userId.id5id.uid; +function getEids(validBidRequests) { + if (validBidRequests[0] && validBidRequests[0].userIdAsEids) { + return validBidRequests[0].userIdAsEids; } } @@ -163,11 +177,6 @@ function isAdheseAd(ad) { return !ad.origin || ad.origin === 'JERLICIA'; } -function getMediaType(markup) { - const isVideo = markup.trim().toLowerCase().match(/<\?xml| { adapterManager.registerAnalyticsAdapter({ adapter: analyticsAdapter, - code: 'adkernelAdn' + code: 'adkernelAdn', + gvlid: GVLID }); export default analyticsAdapter; @@ -390,3 +395,19 @@ function getLocationAndReferrer(win) { loc: win.location }; } + +function initPrivacy(template, requests) { + let consent = requests[0].gdprConsent; + if (consent && consent.gdprApplies) { + template.user.gdpr = ~~consent.gdprApplies; + } + if (consent && consent.consentString) { + template.user.gdpr_consent = consent.consentString; + } + if (requests[0].uspConsent) { + template.user.us_privacy = requests[0].uspConsent; + } + if (config.getConfig('coppa')) { + template.user.coppa = 1; + } +} diff --git a/modules/adkernelAdnBidAdapter.js b/modules/adkernelAdnBidAdapter.js index 0a0317e1f59..c838d93b8f1 100644 --- a/modules/adkernelAdnBidAdapter.js +++ b/modules/adkernelAdnBidAdapter.js @@ -1,11 +1,13 @@ import * as utils from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {config} from '../src/config.js'; const DEFAULT_ADKERNEL_DSP_DOMAIN = 'tag.adkernel.com'; const DEFAULT_MIMES = ['video/mp4', 'video/webm', 'application/x-shockwave-flash', 'application/javascript']; const DEFAULT_PROTOCOLS = [2, 3, 5, 6]; const DEFAULT_APIS = [1, 2]; +const GVLID = 14; function isRtbDebugEnabled(refInfo) { return refInfo.referer.indexOf('adk_debug=true') !== -1; @@ -67,6 +69,9 @@ function buildRequestParams(tags, bidderRequest) { if (uspConsent) { utils.deepSetValue(req, 'user.us_privacy', uspConsent); } + if (config.getConfig('coppa')) { + utils.deepSetValue(req, 'user.coppa', 1); + } return req; } @@ -110,6 +115,7 @@ function buildBid(tag) { export const spec = { code: 'adkernelAdn', + gvlid: GVLID, supportedMediaTypes: [BANNER, VIDEO], aliases: ['engagesimply'], diff --git a/modules/adkernelBidAdapter.js b/modules/adkernelBidAdapter.js index c34902eda46..b6d82aec976 100644 --- a/modules/adkernelBidAdapter.js +++ b/modules/adkernelBidAdapter.js @@ -22,6 +22,7 @@ const SYNC_TYPES = Object.freeze({ 1: 'iframe', 2: 'image' }); +const GVLID = 14; const NATIVE_MODEL = [ {name: 'title', assetType: 'title'}, @@ -50,9 +51,9 @@ const NATIVE_INDEX = NATIVE_MODEL.reduce((acc, val, idx) => { * Adapter for requesting bids from AdKernel white-label display platform */ export const spec = { - code: 'adkernel', - aliases: ['headbidding', 'adsolut', 'oftmediahb', 'audiencemedia', 'waardex_ak', 'roqoon', 'andbeyond', 'adbite', 'houseofpubs'], + gvlid: GVLID, + aliases: ['headbidding', 'adsolut', 'oftmediahb', 'audiencemedia', 'waardex_ak', 'roqoon', 'andbeyond', 'adbite', 'houseofpubs', 'torchad', 'stringads', 'bcm'], supportedMediaTypes: [BANNER, VIDEO, NATIVE], /** @@ -320,7 +321,7 @@ function getAllowedSyncMethod(bidderCode) { */ function buildRtbRequest(imps, bidderRequest, schain) { let {bidderCode, gdprConsent, auctionId, refererInfo, timeout, uspConsent} = bidderRequest; - + let coppa = config.getConfig('coppa'); let req = { 'id': auctionId, 'imp': imps, @@ -349,6 +350,9 @@ function buildRtbRequest(imps, bidderRequest, schain) { if (uspConsent) { utils.deepSetValue(req, 'regs.ext.us_privacy', uspConsent); } + if (coppa) { + utils.deepSetValue(req, 'regs.coppa', 1); + } let syncMethod = getAllowedSyncMethod(bidderCode); if (syncMethod) { utils.deepSetValue(req, 'ext.adk_usersync', syncMethod); diff --git a/modules/adlooxAnalyticsAdapter.js b/modules/adlooxAnalyticsAdapter.js new file mode 100644 index 00000000000..3e92ae34004 --- /dev/null +++ b/modules/adlooxAnalyticsAdapter.js @@ -0,0 +1,288 @@ +/** + * This module provides [Adloox]{@link https://www.adloox.com/} Analytics + * The module will inject Adloox's verification JS tag alongside slot at bidWin + * @module modules/adlooxAnalyticsAdapter + */ + +import adapterManager from '../src/adapterManager.js'; +import adapter from '../src/AnalyticsAdapter.js'; +import { loadExternalScript } from '../src/adloader.js'; +import { auctionManager } from '../src/auctionManager.js'; +import { AUCTION_COMPLETED } from '../src/auction.js'; +import { EVENTS } from '../src/constants.json'; +import find from 'core-js-pure/features/array/find.js'; +import * as utils from '../src/utils.js'; + +const MODULE = 'adlooxAnalyticsAdapter'; + +const URL_JS = 'https://j.adlooxtracking.com/ads/js/tfav_adl_%%clientid%%.js'; + +const ADLOOX_VENDOR_ID = 93; + +const ADLOOX_MEDIATYPE = { + DISPLAY: 2, + VIDEO: 6 +}; + +const MACRO = {}; +MACRO['client'] = function(b, c) { + return c.client; +}; +MACRO['clientid'] = function(b, c) { + return c.clientid; +}; +MACRO['tagid'] = function(b, c) { + return c.tagid; +}; +MACRO['platformid'] = function(b, c) { + return c.platformid; +}; +MACRO['targetelt'] = function(b, c) { + return c.toselector(b); +}; +MACRO['creatype'] = function(b, c) { + return b.mediaType == 'video' ? ADLOOX_MEDIATYPE.VIDEO : ADLOOX_MEDIATYPE.DISPLAY; +}; +MACRO['pbAdSlot'] = function(b, c) { + const adUnit = find(auctionManager.getAdUnits(), a => b.adUnitCode === a.code); + return utils.deepAccess(adUnit, 'fpd.context.pbAdSlot') || utils.getGptSlotInfoForAdUnitCode(b.adUnitCode).gptSlot || b.adUnitCode; +}; + +const PARAMS_DEFAULT = { + 'id1': function(b) { return b.adUnitCode }, + 'id2': '%%pbAdSlot%%', + 'id3': function(b) { return b.bidder }, + 'id4': function(b) { return b.adId }, + 'id5': function(b) { return b.dealId }, + 'id6': function(b) { return b.creativeId }, + 'id7': function(b) { return b.size }, + 'id11': '$ADLOOX_WEBSITE' +}; + +const NOOP = function() {}; + +let analyticsAdapter = Object.assign(adapter({ analyticsType: 'endpoint' }), { + track({ eventType, args }) { + if (!analyticsAdapter[`handle_${eventType}`]) return; + + utils.logInfo(MODULE, 'track', eventType, args); + + analyticsAdapter[`handle_${eventType}`](args); + } +}); + +analyticsAdapter.context = null; + +analyticsAdapter.originEnableAnalytics = analyticsAdapter.enableAnalytics; +analyticsAdapter.enableAnalytics = function(config) { + analyticsAdapter.context = null; + + utils.logInfo(MODULE, 'config', config); + + if (!utils.isPlainObject(config.options)) { + utils.logError(MODULE, 'missing options'); + return; + } + if (!(config.options.js === undefined || utils.isStr(config.options.js))) { + utils.logError(MODULE, 'invalid js options value'); + return; + } + if (!(config.options.toselector === undefined || utils.isFn(config.options.toselector))) { + utils.logError(MODULE, 'invalid toselector options value'); + return; + } + if (!utils.isStr(config.options.client)) { + utils.logError(MODULE, 'invalid client options value'); + return; + } + if (!utils.isNumber(config.options.clientid)) { + utils.logError(MODULE, 'invalid clientid options value'); + return; + } + if (!utils.isNumber(config.options.tagid)) { + utils.logError(MODULE, 'invalid tagid options value'); + return; + } + if (!utils.isNumber(config.options.platformid)) { + utils.logError(MODULE, 'invalid platformid options value'); + return; + } + if (!(config.options.params === undefined || utils.isPlainObject(config.options.params))) { + utils.logError(MODULE, 'invalid params options value'); + return; + } + + analyticsAdapter.context = { + js: config.options.js || URL_JS, + toselector: config.options.toselector || function(bid) { + let code = utils.getGptSlotInfoForAdUnitCode(bid.adUnitCode).divId || bid.adUnitCode; + // https://mathiasbynens.be/notes/css-escapes + code = code.replace(/^\d/, '\\3$& '); + return `#${code}` + }, + client: config.options.client, + clientid: config.options.clientid, + tagid: config.options.tagid, + platformid: config.options.platformid, + params: [] + }; + + config.options.params = utils.mergeDeep({}, PARAMS_DEFAULT, config.options.params || {}); + Object + .keys(config.options.params) + .forEach(k => { + if (!Array.isArray(config.options.params[k])) { + config.options.params[k] = [ config.options.params[k] ]; + } + config.options.params[k].forEach(v => analyticsAdapter.context.params.push([ k, v ])); + }); + + Object.keys(COMMAND_QUEUE).forEach(commandProcess); + + analyticsAdapter.originEnableAnalytics(config); +} + +analyticsAdapter.originDisableAnalytics = analyticsAdapter.disableAnalytics; +analyticsAdapter.disableAnalytics = function() { + analyticsAdapter.context = null; + + analyticsAdapter.originDisableAnalytics(); +} + +analyticsAdapter.url = function(url, args, bid) { + // utils.formatQS outputs PHP encoded querystrings... (╯°□°)╯ ┻━┻ + function a2qs(a) { + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent + function fixedEncodeURIComponent(str) { + return encodeURIComponent(str).replace(/[!'()*]/g, function(c) { + return '%' + c.charCodeAt(0).toString(16); + }); + } + + const args = []; + let n = a.length; + while (n-- > 0) { + if (!(a[n][1] === undefined || a[n][1] === null || a[n][1] === false)) { + args.unshift(fixedEncodeURIComponent(a[n][0]) + (a[n][1] !== true ? ('=' + fixedEncodeURIComponent(a[n][1])) : '')); + } + } + + return args.join('&'); + } + + const macros = (str) => { + return str.replace(/%%([a-z]+)%%/gi, (match, p1) => MACRO[p1] ? MACRO[p1](bid, analyticsAdapter.context) : match); + }; + + url = macros(url); + args = args || []; + + let n = args.length; + while (n-- > 0) { + if (utils.isFn(args[n][1])) { + try { + args[n][1] = args[n][1](bid); + } catch (_) { + utils.logError(MODULE, 'macro', args[n][0], _.message); + args[n][1] = `ERROR: ${_.message}`; + } + } + if (utils.isStr(args[n][1])) { + args[n][1] = macros(args[n][1]); + } + } + + return url + a2qs(args); +} + +analyticsAdapter[`handle_${EVENTS.AUCTION_END}`] = function(auctionDetails) { + if (!(auctionDetails.auctionStatus == AUCTION_COMPLETED && auctionDetails.bidsReceived.length > 0)) return; + analyticsAdapter[`handle_${EVENTS.AUCTION_END}`] = NOOP; + + utils.logMessage(MODULE, 'preloading verification JS'); + + const uri = utils.parseUrl(analyticsAdapter.url(`${analyticsAdapter.context.js}#`)); + + const link = document.createElement('link'); + link.setAttribute('href', `${uri.protocol}://${uri.host}${uri.pathname}`); + link.setAttribute('rel', 'preload'); + link.setAttribute('as', 'script'); + utils.insertElement(link); +} + +analyticsAdapter[`handle_${EVENTS.BID_WON}`] = function(bid) { + const sl = analyticsAdapter.context.toselector(bid); + let el; + try { + el = document.querySelector(sl); + } catch (_) { } + if (!el) { + utils.logWarn(MODULE, `unable to find ad unit code '${bid.adUnitCode}' slot using selector '${sl}' (use options.toselector to change), ignoring`); + return; + } + + utils.logMessage(MODULE, `measuring '${bid.mediaType}' unit at '${bid.adUnitCode}'`); + + const params = analyticsAdapter.context.params.concat([ + [ 'tagid', '%%tagid%%' ], + [ 'platform', '%%platformid%%' ], + [ 'fwtype', 4 ], + [ 'targetelt', '%%targetelt%%' ], + [ 'creatype', '%%creatype%%' ] + ]); + + loadExternalScript(analyticsAdapter.url(`${analyticsAdapter.context.js}#`, params, bid), 'adloox'); +} + +adapterManager.registerAnalyticsAdapter({ + adapter: analyticsAdapter, + code: 'adloox', + gvlid: ADLOOX_VENDOR_ID +}); + +export default analyticsAdapter; + +// src/events.js does not support custom events or handle races... (╯°□°)╯ ┻━┻ +const COMMAND_QUEUE = {}; +export const COMMAND = { + CONFIG: 'config', + URL: 'url', + TRACK: 'track' +}; +export function command(cmd, data, callback0) { + const cid = utils.getUniqueIdentifierStr(); + const callback = function() { + delete COMMAND_QUEUE[cid]; + if (callback0) callback0.apply(null, arguments); + }; + COMMAND_QUEUE[cid] = { cmd, data, callback }; + if (analyticsAdapter.context) commandProcess(cid); +} +function commandProcess(cid) { + const { cmd, data, callback } = COMMAND_QUEUE[cid]; + + utils.logInfo(MODULE, 'command', cmd, data); + + switch (cmd) { + case COMMAND.CONFIG: + const response = { + client: analyticsAdapter.context.client, + clientid: analyticsAdapter.context.clientid, + tagid: analyticsAdapter.context.tagid, + platformid: analyticsAdapter.context.platformid + }; + callback(response); + break; + case COMMAND.URL: + if (data.ids) data.args = data.args.concat(analyticsAdapter.context.params.filter(p => /^id([1-9]|10)$/.test(p[0]))); // not >10 + callback(analyticsAdapter.url(data.url, data.args, data.bid)); + break; + case COMMAND.TRACK: + analyticsAdapter.track(data); + callback(); // drain queue + break; + default: + utils.logWarn(MODULE, 'command unknown', cmd); + // do not callback as arguments are unknown and to aid debugging + } +} diff --git a/modules/adlooxAnalyticsAdapter.md b/modules/adlooxAnalyticsAdapter.md new file mode 100644 index 00000000000..0ca67f937f6 --- /dev/null +++ b/modules/adlooxAnalyticsAdapter.md @@ -0,0 +1,146 @@ +# Overview + + Module Name: Adloox Analytics Adapter + Module Type: Analytics Adapter + Maintainer: technique@adloox.com + +# Description + +Analytics adapter for adloox.com. Contact adops@adloox.com for information. + +This module can be used to track: + + * Display + * Native + * Video (see below for further instructions) + +The adapter adds an HTML ` @@ -126,14 +128,9 @@ export const spec = { * @return {Bid[]} An array of bids which were nested inside the server. */ interpretResponse: function(serverResponse) { - const serverBody = serverResponse.body; - if (serverBody && utils.isArray(serverBody)) { - return utils._map(serverBody, function(bid) { - return buildBid(bid); - }); - } else { - return []; - } + const bids = serverResponse.body && serverResponse.body.bids; + + return Array.isArray(bids) ? bids.map(bid => buildBid(bid)) : [] } } diff --git a/modules/astraoneBidAdapter.md b/modules/astraoneBidAdapter.md index a7eaeeef5a4..e090cfe1e54 100644 --- a/modules/astraoneBidAdapter.md +++ b/modules/astraoneBidAdapter.md @@ -18,17 +18,17 @@ About us: https://astraone.io var adUnits = [{ code: 'test-div', mediaTypes: { - banner: { - sizes: [1, 1] - } + banner: { + sizes: [1, 1], + } }, bids: [{ - bidder: "astraone", - params: { - placement: "inImage", - placeId: "5af45ad34d506ee7acad0c26", - imageUrl: "https://creative.astraone.io/files/default_image-1-600x400.jpg" - } + bidder: "astraone", + params: { + placement: "inImage", + placeId: "5f477bf94d506ebe2c4240f3", + imageUrl: "https://creative.astraone.io/files/default_image-1-600x400.jpg" + } }] }]; ``` @@ -39,66 +39,69 @@ var adUnits = [{ - - Prebid.js Banner Example - - + + }, + bids: [{ + bidder: "astraone", + params: { + placement: "inImage", + placeId: "5f477bf94d506ebe2c4240f3", + imageUrl: "https://creative.astraone.io/files/default_image-1-600x400.jpg" + } + }] + }]; + + var pbjs = pbjs || {}; + pbjs.que = pbjs.que || []; + + pbjs.que.push(function() { + pbjs.addAdUnits(adUnits); + pbjs.requestBids({ + bidsBackHandler: function (e) { + if (pbjs.adserverRequestSent) return; + pbjs.adserverRequestSent = true; + var params = pbjs.getAdserverTargetingForAdUnitCode("test-div"); + var iframe = document.getElementById('test-div'); + + if (params && params['hb_adid']) { + iframe.parentElement.style.position = "relative"; + iframe.style.display = "block"; + pbjs.renderAd(iframe.contentDocument, params['hb_adid']); + } + } + }); + }); + -

Prebid.js InImage Banner Test

+

Prebid.js InImage Banner Test

-
- - -
+
+ + +
@@ -109,90 +112,91 @@ var adUnits = [{ - - Prebid.js Banner Example - - - - + + Prebid.js Banner gpt Example + + + + -

Prebid.js Banner Ad Unit Test

+

Prebid.js InImage Banner gpt Test

-
- +
+ - -
+ +
``` diff --git a/modules/atsAnalyticsAdapter.js b/modules/atsAnalyticsAdapter.js index a5bb3797bbf..31e46dead89 100644 --- a/modules/atsAnalyticsAdapter.js +++ b/modules/atsAnalyticsAdapter.js @@ -3,26 +3,235 @@ import CONSTANTS from '../src/constants.json'; import adaptermanager from '../src/adapterManager.js'; import * as utils from '../src/utils.js'; import {ajax} from '../src/ajax.js'; +import {getStorageManager} from '../src/storageManager.js'; + +export const storage = getStorageManager(); + +/** + * Analytics adapter for - https://liveramp.com + * Maintainer - prebid@liveramp.com + */ const analyticsType = 'endpoint'; +// dev endpoints +// const preflightUrl = 'https://analytics-check.publishersite.xyz/check/'; +// export const analyticsUrl = 'https://analyticsv2.publishersite.xyz'; + +const preflightUrl = 'https://check.analytics.rlcdn.com/check/'; +export const analyticsUrl = 'https://analytics.rlcdn.com'; let handlerRequest = []; let handlerResponse = []; -let host = ''; + +let atsAnalyticsAdapterVersion = 1; + +let browsersList = [ + /* Googlebot */ + { + test: /googlebot/i, + name: 'Googlebot' + }, + + /* Opera < 13.0 */ + { + test: /opera/i, + name: 'Opera', + }, + + /* Opera > 13.0 */ + { + test: /opr\/|opios/i, + name: 'Opera' + }, + { + test: /SamsungBrowser/i, + name: 'Samsung Internet for Android', + }, + { + test: /Whale/i, + name: 'NAVER Whale Browser', + }, + { + test: /MZBrowser/i, + name: 'MZ Browser' + }, + { + test: /focus/i, + name: 'Focus', + }, + { + test: /swing/i, + name: 'Swing', + }, + { + test: /coast/i, + name: 'Opera Coast', + }, + { + test: /opt\/\d+(?:.?_?\d+)+/i, + name: 'Opera Touch', + }, + { + test: /yabrowser/i, + name: 'Yandex Browser', + }, + { + test: /ucbrowser/i, + name: 'UC Browser', + }, + { + test: /Maxthon|mxios/i, + name: 'Maxthon', + }, + { + test: /epiphany/i, + name: 'Epiphany', + }, + { + test: /puffin/i, + name: 'Puffin', + }, + { + test: /sleipnir/i, + name: 'Sleipnir', + }, + { + test: /k-meleon/i, + name: 'K-Meleon', + }, + { + test: /micromessenger/i, + name: 'WeChat', + }, + { + test: /qqbrowser/i, + name: (/qqbrowserlite/i).test(window.navigator.userAgent) ? 'QQ Browser Lite' : 'QQ Browser', + }, + { + test: /msie|trident/i, + name: 'Internet Explorer', + }, + { + test: /\sedg\//i, + name: 'Microsoft Edge', + }, + { + test: /edg([ea]|ios)/i, + name: 'Microsoft Edge', + }, + { + test: /vivaldi/i, + name: 'Vivaldi', + }, + { + test: /seamonkey/i, + name: 'SeaMonkey', + }, + { + test: /sailfish/i, + name: 'Sailfish', + }, + { + test: /silk/i, + name: 'Amazon Silk', + }, + { + test: /phantom/i, + name: 'PhantomJS', + }, + { + test: /slimerjs/i, + name: 'SlimerJS', + }, + { + test: /blackberry|\bbb\d+/i, + name: 'BlackBerry', + }, + { + test: /(web|hpw)[o0]s/i, + name: 'WebOS Browser', + }, + { + test: /bada/i, + name: 'Bada', + }, + { + test: /tizen/i, + name: 'Tizen', + }, + { + test: /qupzilla/i, + name: 'QupZilla', + }, + { + test: /firefox|iceweasel|fxios/i, + name: 'Firefox', + }, + { + test: /electron/i, + name: 'Electron', + }, + { + test: /MiuiBrowser/i, + name: 'Miui', + }, + { + test: /chromium/i, + name: 'Chromium', + }, + { + test: /chrome|crios|crmo/i, + name: 'Chrome', + }, + { + test: /GSA/i, + name: 'Google Search', + }, + + /* Android Browser */ + { + test: /android/i, + name: 'Android Browser', + }, + + /* PlayStation 4 */ + { + test: /playstation 4/i, + name: 'PlayStation 4', + }, + + /* Safari */ + { + test: /safari|applewebkit/i, + name: 'Safari', + }, +]; + +function setSamplingCookie(samplRate) { + let now = new Date(); + now.setTime(now.getTime() + 3600000); + storage.setCookie('_lr_sampling_rate', samplRate, now.toUTCString()); +} + +let listOfSupportedBrowsers = ['Safari', 'Chrome', 'Firefox', 'Microsoft Edge']; function bidRequestedHandler(args) { + let envelopeSourceCookieValue = storage.getCookie('_lr_env_src_ats'); + let envelopeSource = envelopeSourceCookieValue === 'true'; let requests; requests = args.bids.map(function(bid) { return { + envelope_source: envelopeSource, has_envelope: bid.userId ? !!bid.userId.idl_env : false, bidder: bid.bidder, bid_id: bid.bidId, auction_id: args.auctionId, - user_browser: checkUserBrowser(), + user_browser: parseBrowser(), user_platform: navigator.platform, auction_start: new Date(args.auctionStart).toJSON(), domain: window.location.hostname, pid: atsAnalyticsAdapter.context.pid, + adapter_version: atsAnalyticsAdapterVersion }; }); return requests; @@ -38,58 +247,44 @@ function bidResponseHandler(args) { }; } -export function checkUserBrowser() { - let firefox = browserIsFirefox(); - let chrome = browserIsChrome(); - let edge = browserIsEdge(); - let safari = browserIsSafari(); - if (firefox) { - return firefox; - } if (chrome) { - return chrome; - } if (edge) { - return edge; - } if (safari) { - return safari; - } else { - return 'Unknown' +export function parseBrowser() { + let ua = atsAnalyticsAdapter.getUserAgent(); + try { + let result = browsersList.filter(function(obj) { + return obj.test.test(ua); + }); + let browserName = result && result.length ? result[0].name : ''; + return (listOfSupportedBrowsers.indexOf(browserName) >= 0) ? browserName : 'Unknown'; + } catch (err) { + utils.logError('ATS Analytics - Error while checking user browser!', err); } } -export function browserIsFirefox() { - if (typeof InstallTrigger !== 'undefined') { - return 'Firefox'; - } else { - return false; +function sendDataToAnalytic () { + // send data to ats analytic endpoint + try { + let dataToSend = {'Data': atsAnalyticsAdapter.context.events}; + let strJSON = JSON.stringify(dataToSend); + utils.logInfo('ATS Analytics - tried to send analytics data!'); + ajax(analyticsUrl, function () { + }, strJSON, {method: 'POST', contentType: 'application/json'}); + } catch (err) { + utils.logError('ATS Analytics - request encounter an error: ', err); } } -export function browserIsIE() { - return !!document.documentMode; -} - -export function browserIsEdge() { - if (!browserIsIE() && !!window.StyleMedia) { - return 'Edge'; - } else { - return false; - } -} - -export function browserIsChrome() { - if ((!!window.chrome && (!!window.chrome.webstore || !!window.chrome.runtime)) || (/Android/i.test(navigator.userAgent) && !!window.chrome)) { - return 'Chrome'; - } else { - return false; - } -} - -export function browserIsSafari() { - if (window.safari !== undefined) { - return 'Safari' - } else { - return false; - } +// preflight request, to check did publisher have permission to send data to analytics endpoint +function preflightRequest (envelopeSourceCookieValue) { + ajax(preflightUrl + atsAnalyticsAdapter.context.pid, function (data) { + let samplingRateObject = JSON.parse(data); + utils.logInfo('ATS Analytics - Sampling Rate: ', samplingRateObject); + let samplingRate = samplingRateObject['samplingRate']; + setSamplingCookie(samplingRate); + let samplingRateNumber = Number(samplingRate); + if (data && samplingRate && atsAnalyticsAdapter.shouldFireRequest(samplingRateNumber) && envelopeSourceCookieValue != null) { + sendDataToAnalytic(); + } + }, undefined, { method: 'GET', crossOrigin: true }); } function callHandler(evtype, args) { @@ -117,7 +312,6 @@ function callHandler(evtype, args) { let atsAnalyticsAdapter = Object.assign(adapter( { - host, analyticsType }), { @@ -126,16 +320,19 @@ let atsAnalyticsAdapter = Object.assign(adapter( callHandler(eventType, args); } if (eventType === CONSTANTS.EVENTS.AUCTION_END) { - if (atsAnalyticsAdapter.shouldFireRequest()) { - // send data to ats analytic endpoint - try { - let dataToSend = {'Data': atsAnalyticsAdapter.context.events}; - let strJSON = JSON.stringify(dataToSend); - utils.logInfo('atsAnalytics tried to send analytics data!'); - ajax(atsAnalyticsAdapter.context.host, function () { - }, strJSON, {method: 'POST', contentType: 'application/json'}); - } catch (err) { + let envelopeSourceCookieValue = storage.getCookie('_lr_env_src_ats'); + try { + utils.logInfo('ATS Analytics - preflight request!'); + let samplingRateCookie = storage.getCookie('_lr_sampling_rate'); + if (!samplingRateCookie) { + preflightRequest(envelopeSourceCookieValue); + } else { + if (atsAnalyticsAdapter.shouldFireRequest(parseInt(samplingRateCookie)) && envelopeSourceCookieValue != null) { + sendDataToAnalytic(); + } } + } catch (err) { + utils.logError('ATS Analytics - preflight request encounter an error: ', err); } } } @@ -145,26 +342,23 @@ let atsAnalyticsAdapter = Object.assign(adapter( atsAnalyticsAdapter.originEnableAnalytics = atsAnalyticsAdapter.enableAnalytics; // add check to not fire request every time, but instead to send 1/10 events -atsAnalyticsAdapter.shouldFireRequest = function () { - return (Math.floor((Math.random() * 11)) === 10); -} +atsAnalyticsAdapter.shouldFireRequest = function (samplingRate) { + let shouldFireRequestValue = (Math.floor((Math.random() * samplingRate + 1)) === samplingRate); + utils.logInfo('ATS Analytics - Should Fire Request: ', shouldFireRequestValue); + return shouldFireRequestValue; +}; +atsAnalyticsAdapter.getUserAgent = function () { + return window.navigator.userAgent; +}; // override enableAnalytics so we can get access to the config passed in from the page atsAnalyticsAdapter.enableAnalytics = function (config) { if (!config.options.pid) { - utils.logError('Publisher ID (pid) option is not defined. Analytics won\'t work'); + utils.logError('ATS Analytics - Publisher ID (pid) option is not defined. Analytics won\'t work'); return; } - - if (!config.options.host) { - utils.logError('Host option is not defined. Analytics won\'t work'); - return; - } - - host = config.options.host; atsAnalyticsAdapter.context = { events: [], - host: config.options.host, pid: config.options.pid }; let initOptions = config.options; diff --git a/modules/atsAnalyticsAdapter.md b/modules/atsAnalyticsAdapter.md index 560ad237aa0..7c634f39ae2 100644 --- a/modules/atsAnalyticsAdapter.md +++ b/modules/atsAnalyticsAdapter.md @@ -17,7 +17,6 @@ Analytics adapter for Authenticated Traffic Solution(ATS), provided by LiveRamp. provider: 'atsAnalytics', options: { pid: '999', // publisher ID - host: 'https://example.com' // host is provided to publisher } } ``` diff --git a/modules/automatadBidAdapter.js b/modules/automatadBidAdapter.js index 6b66044f5e5..415c52ba6d3 100644 --- a/modules/automatadBidAdapter.js +++ b/modules/automatadBidAdapter.js @@ -78,7 +78,9 @@ export const spec = { requestId: bid.impid, cpm: bid.price, ad: bid.adm, - adDomain: bid.adomain[0], + meta: { + advertiserDomains: bid.adomain + }, currency: DEFAULT_CURRENCY, ttl: DEFAULT_BID_TTL, creativeId: bid.crid, diff --git a/modules/avocetBidAdapter.js b/modules/avocetBidAdapter.js index 7a9e5062c0f..1283bb865d4 100644 --- a/modules/avocetBidAdapter.js +++ b/modules/avocetBidAdapter.js @@ -53,7 +53,7 @@ export const spec = { const publisherDomain = config.getConfig('publisherDomain'); // First-party data from config - const fpd = config.getConfig('fpd'); + const fpd = config.getLegacyFpd(config.getConfig('ortb2')); // GDPR status and TCF consent string let tcfConsentString; diff --git a/modules/axonixBidAdapter.js b/modules/axonixBidAdapter.js new file mode 100644 index 00000000000..224486467f3 --- /dev/null +++ b/modules/axonixBidAdapter.js @@ -0,0 +1,186 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { config } from '../src/config.js'; +import * as utils from '../src/utils.js'; +import { ajax } from '../src/ajax.js'; + +const BIDDER_CODE = 'axonix'; +const BIDDER_VERSION = '1.0.1'; + +const CURRENCY = 'USD'; +const DEFAULT_REGION = 'us-east-1'; + +function getBidFloor(bidRequest) { + let floorInfo = {}; + + if (typeof bidRequest.getFloor === 'function') { + floorInfo = bidRequest.getFloor({ + currency: CURRENCY, + mediaType: '*', + size: '*' + }); + } + + return floorInfo.floor || 0; +} + +function getPageUrl(bidRequest, bidderRequest) { + let pageUrl = config.getConfig('pageUrl'); + + if (bidRequest.params.referrer) { + pageUrl = bidRequest.params.referrer; + } else if (!pageUrl) { + pageUrl = bidderRequest.refererInfo.referer; + } + + return bidRequest.params.secure ? pageUrl.replace(/^http:/i, 'https:') : pageUrl; +} + +function isMobile() { + return (/(ios|ipod|ipad|iphone|android)/i).test(navigator.userAgent); +} + +function isConnectedTV() { + return (/(smart[-]?tv|hbbtv|appletv|googletv|hdmi|netcast\.tv|viera|nettv|roku|\bdtv\b|sonydtv|inettvbrowser|\btv\b)/i).test(navigator.userAgent); +} + +function getURL(params, path) { + let { supplyId, region, endpoint } = params; + let url; + + if (endpoint) { + url = endpoint; + } else if (region) { + url = `https://openrtb-${region}.axonix.com/supply/${path}/${supplyId}`; + } else { + url = `https://openrtb-${DEFAULT_REGION}.axonix.com/supply/${path}/${supplyId}` + } + + return url; +} + +export const spec = { + code: BIDDER_CODE, + version: BIDDER_VERSION, + supportedMediaTypes: [BANNER, VIDEO], + + isBidRequestValid: function(bid) { + // video bid request validation + if (bid.hasOwnProperty('mediaTypes') && bid.mediaTypes.hasOwnProperty(VIDEO)) { + if (!bid.mediaTypes[VIDEO].hasOwnProperty('mimes') || + !utils.isArray(bid.mediaTypes[VIDEO].mimes) || + bid.mediaTypes[VIDEO].mimes.length === 0) { + utils.logError('mimes are mandatory for video bid request. Ad Unit: ', JSON.stringify(bid)); + + return false; + } + } + + return !!(bid.params && bid.params.supplyId); + }, + + buildRequests: function(validBidRequests, bidderRequest) { + // device.connectiontype + let connection = window.navigator && (window.navigator.connection || window.navigator.mozConnection || window.navigator.webkitConnection) + let connectionType = 'unknown'; + let effectiveType = ''; + + if (connection) { + if (connection.type) { + connectionType = connection.type; + } + + if (connection.effectiveType) { + effectiveType = connection.effectiveType; + } + } + + const requests = validBidRequests.map(validBidRequest => { + // app/site + let app; + let site; + + if (typeof config.getConfig('app') === 'object') { + app = config.getConfig('app'); + } else { + site = { + page: getPageUrl(validBidRequest, bidderRequest) + } + } + + const data = { + app, + site, + validBidRequest, + connectionType, + effectiveType, + devicetype: isMobile() ? 1 : isConnectedTV() ? 3 : 2, + bidfloor: getBidFloor(validBidRequest), + dnt: (navigator.doNotTrack === 'yes' || navigator.doNotTrack === '1' || navigator.msDoNotTrack === '1') ? 1 : 0, + language: navigator.language, + prebidVersion: '$prebid.version$', + screenHeight: screen.height, + screenWidth: screen.width, + tmax: config.getConfig('bidderTimeout'), + ua: navigator.userAgent, + }; + + return { + method: 'POST', + url: getURL(validBidRequest.params, 'prebid'), + options: { + withCredentials: false, + contentType: 'application/json' + }, + data + }; + }); + + return requests; + }, + + interpretResponse: function(serverResponse) { + if (!utils.isArray(serverResponse)) { + return []; + } + + const responses = []; + + for (const response of serverResponse) { + if (response.requestId) { + responses.push(Object.assign(response, { + ttl: config.getConfig('_bidderTimeout') + })); + } + } + + return responses; + }, + + onTimeout: function(timeoutData) { + const params = utils.deepAccess(timeoutData, '0.params.0'); + + if (!utils.isEmpty(params)) { + ajax(getURL(params, 'prebid/timeout'), null, timeoutData[0], { + method: 'POST', + options: { + withCredentials: false, + contentType: 'application/json' + } + }); + } + }, + + onBidWon: function(bids) { + for (const bid of bids) { + const { nurl } = bid || {}; + + if (bid.nurl) { + utils.replaceAuctionPrice(nurl, bid.cpm) + utils.triggerPixel(nurl); + }; + } + } +} + +registerBidder(spec); diff --git a/modules/axonixBidAdapter.md b/modules/axonixBidAdapter.md new file mode 100644 index 00000000000..1ff59f17828 --- /dev/null +++ b/modules/axonixBidAdapter.md @@ -0,0 +1,140 @@ +# Overview + +``` +Module Name : Axonix Bidder Adapter +Module Type : Bidder Adapter +Maintainer : support-prebid@axonix.com +``` + +# Description + +Module that connects to Axonix's exchange for bids. + +# Parameters + +| Name | Scope | Description | Example | +| :------------ | :------- | :---------------------------------------------- | :------------------------------------- | +| `supplyId` | required | Supply UUID | `"2c426f78-bb18-4a16-abf4-62c6cd0ee8de"` | +| `region` | optional | Cloud region | `"us-east-1"` | +| `endpoint` | optional | Supply custom endpoint | `"https://open-rtb.axonix.com/custom"` | +| `instl` | optional | Set to 1 if using interstitial (default: 0) | `1` | + +# Test Parameters + +## Banner + +```javascript +var bannerAdUnit = { + code: 'test-banner', + mediaTypes: { + banner: { + sizes: [[120, 600], [300, 250], [320, 50], [468, 60], [728, 90]] + } + }, + bids: [{ + bidder: 'axonix', + params: { + supplyId: 'abc', + region: 'def', + endpoint: 'url' + } + }] +}; +``` + +## Video + +```javascript +var videoAdUnit = { + code: 'test-video', + mediaTypes: { + video: { + protocols: [1, 2, 3, 4, 5, 6, 7, 8] + } + }, + bids: [{ + bidder: 'axonix', + params: { + supplyId: 'abc', + region: 'def', + endpoint: 'url' + } + }] +}; +``` + +## Native + +```javascript +var nativeAdUnit = { + code: 'test-native', + mediaTypes: { + native: { + + } + }, + bids: [{ + bidder: 'axonix', + params: { + supplyId: 'abc', + region: 'def', + endpoint: 'url' + } + }] +}; +``` + +## Multiformat + +```javascript +var adUnits = [ +{ + code: 'test-banner', + mediaTypes: { + banner: { + sizes: [[120, 600], [300, 250], [320, 50], [468, 60], [728, 90]] + } + }, + bids: [{ + bidder: 'axonix', + params: { + supplyId: 'abc', + region: 'def', + endpoint: 'url' + } + }] +}, +{ + code: 'test-video', + mediaTypes: { + video: { + protocols: [1, 2, 3, 4, 5, 6, 7, 8] + } + }, + bids: [{ + bidder: 'axonix', + params: { + supplyId: 'abc', + region: 'def', + endpoint: 'url' + } + }] +}, +{ + code: 'test-native', + mediaTypes: { + native: { + + } + }, + bids: [{ + bidder: 'axonix', + params: { + supplyId: 'abc', + region: 'def', + endpoint: 'url' + } + }] +} +]; +``` diff --git a/modules/betweenBidAdapter.js b/modules/betweenBidAdapter.js index 0ed05717391..f445c9dfd5d 100644 --- a/modules/betweenBidAdapter.js +++ b/modules/betweenBidAdapter.js @@ -3,6 +3,7 @@ import { getAdUnitSizes, parseSizesInput } from '../src/utils.js'; import { getRefererInfo } from '../src/refererDetection.js'; const BIDDER_CODE = 'between'; +const ENDPOINT = 'https://ads.betweendigital.com/adjson?t=prebid'; export const spec = { code: BIDDER_CODE, @@ -30,7 +31,7 @@ export const spec = { validBidRequests.forEach(i => { let params = { - sizes: parseSizesInput(getAdUnitSizes(i)).join('%2C'), + sizes: parseSizesInput(getAdUnitSizes(i)), jst: 'hb', ord: Math.random() * 10000000000000000, tz: getTz(), @@ -74,9 +75,14 @@ export const spec = { } } - requests.push({method: 'GET', url: 'https://ads.betweendigital.com/adjson', data: params}) + requests.push({data: params}) }) - return requests; + return { + method: 'POST', + url: ENDPOINT, + data: JSON.stringify(requests) + } + // return requests; }, /** * Unpack the response from the server into a list of bids. diff --git a/modules/bidViewability.js b/modules/bidViewability.js new file mode 100644 index 00000000000..c3b72cda8d4 --- /dev/null +++ b/modules/bidViewability.js @@ -0,0 +1,97 @@ +// This module, when included, will trigger a BID_VIEWABLE event which can be consumed by Bidders and Analytics adapters +// GPT API is used to find when a bid is viewable, https://developers.google.com/publisher-tag/reference#googletag.events.impressionviewableevent +// Does not work with other than GPT integration + +import { config } from '../src/config.js'; +import * as events from '../src/events.js'; +import { EVENTS } from '../src/constants.json'; +import { logWarn, isFn, triggerPixel } from '../src/utils.js'; +import { getGlobal } from '../src/prebidGlobal.js'; +import adapterManager, { gdprDataHandler, uspDataHandler } from '../src/adapterManager.js'; +import find from 'core-js-pure/features/array/find.js'; + +const MODULE_NAME = 'bidViewability'; +const CONFIG_ENABLED = 'enabled'; +const CONFIG_FIRE_PIXELS = 'firePixels'; +const CONFIG_CUSTOM_MATCH = 'customMatchFunction'; +const BID_VURL_ARRAY = 'vurls'; +const GPT_IMPRESSION_VIEWABLE_EVENT = 'impressionViewable'; + +export let isBidAdUnitCodeMatchingSlot = (bid, slot) => { + return (slot.getAdUnitPath() === bid.adUnitCode || slot.getSlotElementId() === bid.adUnitCode); +} + +export let getMatchingWinningBidForGPTSlot = (globalModuleConfig, slot) => { + return find(getGlobal().getAllWinningBids(), + // supports custom match function from config + bid => isFn(globalModuleConfig[CONFIG_CUSTOM_MATCH]) + ? globalModuleConfig[CONFIG_CUSTOM_MATCH](bid, slot) + : isBidAdUnitCodeMatchingSlot(bid, slot) + ) || null; +}; + +export let fireViewabilityPixels = (globalModuleConfig, bid) => { + if (globalModuleConfig[CONFIG_FIRE_PIXELS] === true && bid.hasOwnProperty(BID_VURL_ARRAY)) { + let queryParams = {}; + + const gdprConsent = gdprDataHandler.getConsentData(); + if (gdprConsent) { + if (typeof gdprConsent.gdprApplies === 'boolean') { queryParams.gdpr = Number(gdprConsent.gdprApplies); } + if (gdprConsent.consentString) { queryParams.gdpr_consent = gdprConsent.consentString; } + if (gdprConsent.addtlConsent) { queryParams.addtl_consent = gdprConsent.addtlConsent; } + } + + const uspConsent = uspDataHandler.getConsentData(); + if (uspConsent) { queryParams.us_privacy = uspConsent; } + + bid[BID_VURL_ARRAY].forEach(url => { + // add '?' if not present in URL + if (Object.keys(queryParams).length > 0 && url.indexOf('?') === -1) { + url += '?'; + } + // append all query params, `&key=urlEncoded(value)` + url += Object.keys(queryParams).reduce((prev, key) => prev += `&${key}=${encodeURIComponent(queryParams[key])}`, ''); + triggerPixel(url) + }); + } +}; + +export let logWinningBidNotFound = (slot) => { + logWarn(`bid details could not be found for ${slot.getSlotElementId()}, probable reasons: a non-prebid bid is served OR check the prebid.AdUnit.code to GPT.AdSlot relation.`); +}; + +export let impressionViewableHandler = (globalModuleConfig, slot, event) => { + let respectiveBid = getMatchingWinningBidForGPTSlot(globalModuleConfig, slot); + if (respectiveBid === null) { + logWinningBidNotFound(slot); + } else { + // if config is enabled AND VURL array is present then execute each pixel + fireViewabilityPixels(globalModuleConfig, respectiveBid); + // trigger respective bidder's onBidViewable handler + adapterManager.callBidViewableBidder(respectiveBid.bidder, respectiveBid); + // emit the BID_VIEWABLE event with bid details, this event can be consumed by bidders and analytics pixels + events.emit(EVENTS.BID_VIEWABLE, respectiveBid); + } +}; + +export let init = () => { + events.on(EVENTS.AUCTION_INIT, () => { + // read the config for the module + const globalModuleConfig = config.getConfig(MODULE_NAME) || {}; + // do nothing if module-config.enabled is not set to true + // this way we are adding a way for bidders to know (using pbjs.getConfig('bidViewability').enabled === true) whether this module is added in build and is enabled + if (globalModuleConfig[CONFIG_ENABLED] !== true) { + return; + } + // add the GPT event listener + window.googletag = window.googletag || {}; + window.googletag.cmd = window.googletag.cmd || []; + window.googletag.cmd.push(() => { + window.googletag.pubads().addEventListener(GPT_IMPRESSION_VIEWABLE_EVENT, function(event) { + impressionViewableHandler(globalModuleConfig, event.slot, event); + }); + }); + }); +} + +init() diff --git a/modules/bidViewability.md b/modules/bidViewability.md new file mode 100644 index 00000000000..78a1539fb1a --- /dev/null +++ b/modules/bidViewability.md @@ -0,0 +1,49 @@ +# Overview + +Module Name: bidViewability + +Purpose: Track when a bid is viewable + +Maintainer: harshad.mane@pubmatic.com + +# Description +- This module, when included, will trigger a BID_VIEWABLE event which can be consumed by Analytics adapters, bidders will need to implement `onBidViewable` method to capture this event +- Bidderes can check if this module is part of the final build and whether it is enabled or not by accessing ```pbjs.getConfig('bidViewability')``` +- GPT API is used to find when a bid is viewable, https://developers.google.com/publisher-tag/reference#googletag.events.impressionviewableevent . This event is fired when an impression becomes viewable, according to the Active View criteria. +Refer: https://support.google.com/admanager/answer/4524488 +- The module does not work with adserver other than GAM with GPT integration +- Logic used to find a matching pbjs-bid for a GPT slot is ``` (slot.getAdUnitPath() === bid.adUnitCode || slot.getSlotElementId() === bid.adUnitCode) ``` this logic can be changed by using param ```customMatchFunction``` +- When a rendered PBJS bid is viewable the module will trigger BID_VIEWABLE event, which can be consumed by bidders and analytics adapters +- For the viewable bid if ```bid.vurls type array``` param is and module config ``` firePixels: true ``` is set then the URLs mentioned in bid.vurls will be executed. Please note that GDPR and USP related parameters will be added to the given URLs + +# Params +- enabled [required] [type: boolean, default: false], when set to true, the module will emit BID_VIEWABLE when applicable +- firePixels [optional] [type: boolean], when set to true, will fire the urls mentioned in bid.vurls which should be array of urls +- customMatchFunction [optional] [type: function(bid, slot)], when passed this function will be used to `find` the matching winning bid for the GPT slot. Default value is ` (bid, slot) => (slot.getAdUnitPath() === bid.adUnitCode || slot.getSlotElementId() === bid.adUnitCode) ` + +# Example of consuming BID_VIEWABLE event +``` + pbjs.onEvent('bidViewable', function(bid){ + console.log('got bid details in bidViewable event', bid); + }); + +``` + +# Example of using config +``` + pbjs.setConfig({ + bidViewability: { + enabled: true, + firePixels: true, + customMatchFunction: function(bid, slot){ + console.log('using custom match function....'); + return bid.adUnitCode === slot.getAdUnitPath(); + } + } + }); +``` + +# Please Note: +- Doesn't seems to work with Instream Video, https://docs.prebid.org/dev-docs/examples/instream-banner-mix.html as GPT's impressionViewable event is not triggered for instream-video-creative +- Works with Banner, Outsteam, Native creatives + diff --git a/modules/bidglassBidAdapter.js b/modules/bidglassBidAdapter.js index 6db35f184ca..44f5cdf4384 100644 --- a/modules/bidglassBidAdapter.js +++ b/modules/bidglassBidAdapter.js @@ -46,7 +46,7 @@ export const spec = { return window === window.top ? window.location.href : window.parent === window.top ? document.referrer : null; }; let getOrigins = function() { - var ori = ['https://' + window.location.hostname]; + var ori = [window.location.protocol + '//' + window.location.hostname]; if (window.location.ancestorOrigins) { for (var i = 0; i < window.location.ancestorOrigins.length; i++) { @@ -56,7 +56,7 @@ export const spec = { // Derive the parent origin var parts = document.referrer.split('/'); - ori.push('https://' + parts[2]); + ori.push(parts[0] + '//' + parts[2]); if (window.parent !== window.top) { // Additional unknown origins exist @@ -67,15 +67,30 @@ export const spec = { return ori; }; + let bidglass = window['bidglass']; + utils._each(validBidRequests, function(bid) { bid.sizes = ((utils.isArray(bid.sizes) && utils.isArray(bid.sizes[0])) ? bid.sizes : [bid.sizes]); bid.sizes = bid.sizes.filter(size => utils.isArray(size)); - // Stuff to send: [bid id, sizes, adUnitId] + var adUnitId = utils.getBidIdParameter('adUnitId', bid.params); + var options = utils.deepClone(bid.params); + + delete options.adUnitId; + + // Merge externally set targeting params + if (typeof bidglass === 'object' && bidglass.getTargeting) { + let targeting = bidglass.getTargeting(adUnitId, options.targeting); + + if (targeting && Object.keys(targeting).length > 0) options.targeting = targeting; + } + + // Stuff to send: [bid id, sizes, adUnitId, options] imps.push({ bidId: bid.bidId, sizes: bid.sizes, - adUnitId: utils.getBidIdParameter('adUnitId', bid.params) + adUnitId: adUnitId, + options: options }); }); diff --git a/modules/bizzclickBidAdapter.js b/modules/bizzclickBidAdapter.js index 80d2f6b5395..2af9a7afed2 100644 --- a/modules/bizzclickBidAdapter.js +++ b/modules/bizzclickBidAdapter.js @@ -81,10 +81,12 @@ export const spec = { let data = { id: bidRequest.bidId, test: config.getConfig('debug') ? 1 : 0, + at: 1, cur: ['USD'], device: { w: winTop.screen.width, h: winTop.screen.height, + dnt: utils.getDNT() ? 1 : 0, language: (navigator && navigator.language) ? navigator.language.indexOf('-') != -1 ? navigator.language.split('-')[0] : navigator.language : '', }, site: { @@ -94,9 +96,26 @@ export const spec = { source: { tid: bidRequest.transactionId }, + regs: { + coppa: config.getConfig('coppa') === true ? 1 : 0, + ext: {} + }, + user: { + ext: {} + }, tmax: bidRequest.timeout, imp: [impObject], }; + if (bidRequest) { + if (bidRequest.gdprConsent && bidRequest.gdprConsent.gdprApplies) { + utils.deepSetValue(data, 'regs.ext.gdpr', bidRequest.gdprConsent.gdprApplies ? 1 : 0); + utils.deepSetValue(data, 'user.ext.consent', bidRequest.gdprConsent.consentString); + } + + if (bidRequest.uspConsent !== undefined) { + utils.deepSetValue(data, 'regs.ext.us_privacy', bidRequest.uspConsent); + } + } bids.push(data) } return { diff --git a/modules/britepoolIdSystem.md b/modules/britepoolIdSystem.md index a15f601aee3..72edbe2324b 100644 --- a/modules/britepoolIdSystem.md +++ b/modules/britepoolIdSystem.md @@ -4,7 +4,7 @@ BritePool User ID Module. For assistance setting up your module please contact u ### Prebid Params -Individual params may be set for the BritePool User ID Submodule. At least one identifier must be set in the params. +Individual params may be set for the BritePool User ID Submodule. ``` pbjs.setConfig({ userSync: { diff --git a/modules/browsiRtdProvider.md b/modules/browsiRtdProvider.md index 0dd8c1d7609..9eed4b2b2d4 100644 --- a/modules/browsiRtdProvider.md +++ b/modules/browsiRtdProvider.md @@ -19,9 +19,9 @@ Configuration example for using RTD module with `browsi` provider "name": "browsi", "waitForIt": "true" "params": { - "url": "testUrl.com", - "siteKey": "testKey", - "pubKey": "testPub", + "url": "yield-manager.browsiprod.com", + "siteKey": "browsidemo", + "pubKey": "browsidemo" "keyName":"bv" } }] diff --git a/modules/cleanmedianetBidAdapter.js b/modules/cleanmedianetBidAdapter.js index a8f37450d68..80e4f13e7d4 100644 --- a/modules/cleanmedianetBidAdapter.js +++ b/modules/cleanmedianetBidAdapter.js @@ -3,6 +3,7 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {config} from '../src/config.js'; import {Renderer} from '../src/Renderer.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import includes from 'core-js-pure/features/array/includes.js'; export const helper = { getTopWindowDomain: function (url) { @@ -119,7 +120,7 @@ export const spec = { const hasFavoredMediaType = params.favoredMediaType && - this.supportedMediaTypes.includes(params.favoredMediaType); + includes(this.supportedMediaTypes, params.favoredMediaType); if (!mediaTypes || mediaTypes.banner) { if (!hasFavoredMediaType || params.favoredMediaType === BANNER) { diff --git a/modules/colossussspBidAdapter.js b/modules/colossussspBidAdapter.js index a3beb723528..1afb343566d 100644 --- a/modules/colossussspBidAdapter.js +++ b/modules/colossussspBidAdapter.js @@ -56,15 +56,8 @@ export const spec = { * @return ServerRequest Info describing the request to the server. */ buildRequests: (validBidRequests, bidderRequest) => { - let winTop = window; - let location; - try { - location = new URL(bidderRequest.refererInfo.referer) - winTop = window.top; - } catch (e) { - location = winTop.location; - utils.logMessage(e); - }; + const winTop = utils.getWindowTop(); + const location = winTop.location; let placements = []; let request = { 'deviceWidth': winTop.screen.width, @@ -94,15 +87,29 @@ export const spec = { bidId: bid.bidId, sizes: bid.mediaTypes[traff].sizes, traffic: traff, - eids: [] + eids: [], + floor: {} }; + if (typeof bid.getFloor === 'function') { + let tmpFloor = {}; + for (let size of placement.sizes) { + tmpFloor = bid.getFloor({ + currency: 'USD', + mediaType: traff, + size: size + }); + if (tmpFloor) { + placement.floor[`${size[0]}x${size[1]}`] = tmpFloor.floor; + } + } + } if (bid.schain) { placement.schain = bid.schain; } if (bid.userId) { getUserId(placement.eids, bid.userId.britepoolid, 'britepool.com'); getUserId(placement.eids, bid.userId.idl_env, 'identityLink'); - getUserId(placement.eids, utils.deepAccess(bid, 'userId.id5id.uid'), 'id5-sync.com', utils.deepAccess(bid, 'userId.id5id.ext')); + getUserId(placement.eids, bid.userId.id5id, 'id5-sync.com') getUserId(placement.eids, bid.userId.tdid, 'adserver.org', { rtiPartner: 'TDID' }); diff --git a/modules/connectadBidAdapter.js b/modules/connectadBidAdapter.js index d1811a1b7d1..4fa2a56a004 100644 --- a/modules/connectadBidAdapter.js +++ b/modules/connectadBidAdapter.js @@ -72,7 +72,7 @@ export const spec = { // EIDS Support if (validBidRequests[0].userId) { - data.user.ext.eids = createEidsArray(validBidRequests[0].userId); + utils.deepSetValue(data, 'user.ext.eids', createEidsArray(validBidRequests[0].userId)); } validBidRequests.map(bid => { diff --git a/modules/consentManagementUsp.js b/modules/consentManagementUsp.js index 3edacb41549..cba9c2758d0 100644 --- a/modules/consentManagementUsp.js +++ b/modules/consentManagementUsp.js @@ -44,6 +44,35 @@ function lookupStaticConsentData(cmpSuccess, cmpError, hookConfig) { * @param {object} hookConfig contains module related variables (see comment in requestBidsHook function) */ function lookupUspConsent(uspSuccess, uspError, hookConfig) { + function findUsp() { + let f = window; + let uspapiFrame; + let uspapiFunction; + + while (!uspapiFrame) { + try { + if (typeof f.__uspapi === 'function') { + uspapiFunction = f.__uspapi; + uspapiFrame = f; + break; + } + } catch (e) {} + + try { + if (f.frames['__uspapiLocator']) { + uspapiFrame = f; + break; + } + } catch (e) {} + if (f === window.top) break; + f = f.parent; + } + return { + uspapiFrame, + uspapiFunction, + }; + } + function handleUspApiResponseCallbacks() { const uspResponse = {}; @@ -61,41 +90,43 @@ function lookupUspConsent(uspSuccess, uspError, hookConfig) { uspResponse.usPrivacy = consentResponse.uspString; } afterEach(); - } + }, }; } let callbackHandler = handleUspApiResponseCallbacks(); let uspapiCallbacks = {}; + let { uspapiFrame, uspapiFunction } = findUsp(); + + if (!uspapiFrame) { + return uspError('USP CMP not found.', hookConfig); + } + // to collect the consent information from the user, we perform a call to USPAPI // to collect the user's consent choices represented as a string (via getUSPData) // the following code also determines where the USPAPI is located and uses the proper workflow to communicate with it: - // - use the USPAPI locator code to see if USP's located in the current window or an ancestor window. This works in friendly or cross domain iframes + // - use the USPAPI locator code to see if USP's located in the current window or an ancestor window. + // - else assume prebid is in an iframe, and use the locator to see if the CMP is located in a higher parent window. This works in cross domain iframes. // - if USPAPI is not found, the iframe function will call the uspError exit callback to abort the rest of the USPAPI workflow - // - try to call the __uspapi() function directly, otherwise use the postMessage() api - // find the CMP frame/window - - try { - // try to call __uspapi directly - window.__uspapi('getUSPData', USPAPI_VERSION, callbackHandler.consentDataCallback); - } catch (e) { - // must not have been accessible, try using postMessage() api - let f = window; - let uspapiFrame; - while (!uspapiFrame) { - try { - if (f.frames['__uspapiLocator']) uspapiFrame = f; - } catch (e) { } - if (f === window.top) break; - f = f.parent; - } - if (!uspapiFrame) { - return uspError('USP CMP not found.', hookConfig); - } - callUspApiWhileInIframe('getUSPData', uspapiFrame, callbackHandler.consentDataCallback); + if (utils.isFn(uspapiFunction)) { + utils.logInfo('Detected USP CMP is directly accessible, calling it now...'); + uspapiFunction( + 'getUSPData', + USPAPI_VERSION, + callbackHandler.consentDataCallback + ); + } else { + utils.logInfo( + 'Detected USP CMP is outside the current iframe where Prebid.js is located, calling it now...' + ); + callUspApiWhileInIframe( + 'getUSPData', + uspapiFrame, + callbackHandler.consentDataCallback + ); } function callUspApiWhileInIframe(commandName, uspapiFrame, moduleCallback) { @@ -107,19 +138,19 @@ function lookupUspConsent(uspSuccess, uspError, hookConfig) { __uspapiCall: { command: cmd, version: ver, - callId: callId - } + callId: callId, + }, }; uspapiCallbacks[callId] = callback; uspapiFrame.postMessage(msg, '*'); - } + }; /** when we get the return message, call the stashed callback */ window.addEventListener('message', readPostMessageResponse, false); // call uspapi - window.__uspapi(commandName, USPAPI_VERSION, uspapiCallback); + window.__uspapi(commandName, USPAPI_VERSION, moduleCallback); function readPostMessageResponse(event) { const res = event && event.data && event.data.__uspapiReturn; @@ -130,11 +161,6 @@ function lookupUspConsent(uspSuccess, uspError, hookConfig) { } } } - - function uspapiCallback(consentObject, success) { - window.removeEventListener('message', readPostMessageResponse, false); - moduleCallback(consentObject, success); - } } } diff --git a/modules/conversantBidAdapter.js b/modules/conversantBidAdapter.js index 3b3d04dc498..74cd97ad019 100644 --- a/modules/conversantBidAdapter.js +++ b/modules/conversantBidAdapter.js @@ -205,6 +205,10 @@ export const spec = { ttl: 300, netRevenue: true }; + bid.meta = {}; + if (conversantBid.adomain && conversantBid.adomain.length > 0) { + bid.meta.advertiserDomains = conversantBid.adomain; + } if (request.video) { if (responseAd.charAt(0) === '<') { @@ -331,7 +335,6 @@ function collectEids(bidRequests) { 'criteo.com': 1, 'id5-sync.com': 1, 'parrable.com': 1, - 'digitru.st': 1, 'liveintent.com': 1 }; request.userIdAsEids.forEach(function(eid) { diff --git a/modules/criteoBidAdapter.js b/modules/criteoBidAdapter.js index 3dabe911884..41cbb0670c8 100644 --- a/modules/criteoBidAdapter.js +++ b/modules/criteoBidAdapter.js @@ -8,7 +8,7 @@ import { verify } from 'criteo-direct-rsa-validate/build/verify.js'; import { getStorageManager } from '../src/storageManager.js'; const GVLID = 91; -export const ADAPTER_VERSION = 32; +export const ADAPTER_VERSION = 33; const BIDDER_CODE = 'criteo'; const CDB_ENDPOINT = 'https://bidder.criteo.com/cdb'; const PROFILE_ID_INLINE = 207; @@ -28,7 +28,7 @@ export const spec = { gvlid: GVLID, supportedMediaTypes: [ BANNER, VIDEO, NATIVE ], - /** + /** f * @param {object} bid * @return {boolean} */ @@ -56,10 +56,11 @@ export const spec = { buildRequests: (bidRequests, bidderRequest) => { let url; let data; + let fpd = config.getLegacyFpd(config.getConfig('ortb2')) || {}; Object.assign(bidderRequest, { - publisherExt: config.getConfig('fpd.context'), - userExt: config.getConfig('fpd.user'), + publisherExt: fpd.context, + userExt: fpd.user, ceh: config.getConfig('criteo.ceh') }); @@ -79,10 +80,6 @@ export const spec = { if (publisherTagAvailable()) { // eslint-disable-next-line no-undef const adapter = new Criteo.PubTag.Adapters.Prebid(PROFILE_ID_PUBLISHERTAG, ADAPTER_VERSION, bidRequests, bidderRequest, '$prebid.version$'); - const enableSendAllBids = config.getConfig('enableSendAllBids'); - if (adapter.setEnableSendAllBids && typeof adapter.setEnableSendAllBids === 'function' && typeof enableSendAllBids === 'boolean') { - adapter.setEnableSendAllBids(enableSendAllBids); - } url = adapter.buildCdbUrl(); data = adapter.buildCdbRequest(); } else { @@ -133,8 +130,6 @@ export const spec = { if (slot.native) { if (bidRequest.params.nativeCallback) { bid.ad = createNativeAd(bidId, slot.native, bidRequest.params.nativeCallback); - } else if (config.getConfig('enableSendAllBids') === true) { - return; } else { bid.native = createPrebidNativeAd(slot.native); bid.mediaType = NATIVE; @@ -253,12 +248,14 @@ function buildCdbUrl(context) { function checkNativeSendId(bidRequest) { return !(bidRequest.nativeParams && - ((bidRequest.nativeParams.image && bidRequest.nativeParams.image.sendId !== true) || - (bidRequest.nativeParams.icon && bidRequest.nativeParams.icon.sendId !== true) || - (bidRequest.nativeParams.clickUrl && bidRequest.nativeParams.clickUrl.sendId !== true) || - (bidRequest.nativeParams.displayUrl && bidRequest.nativeParams.displayUrl.sendId !== true) || - (bidRequest.nativeParams.privacyLink && bidRequest.nativeParams.privacyLink.sendId !== true) || - (bidRequest.nativeParams.privacyIcon && bidRequest.nativeParams.privacyIcon.sendId !== true))); + ( + (bidRequest.nativeParams.image && ((bidRequest.nativeParams.image.sendId !== true || bidRequest.nativeParams.image.sendTargetingKeys === true))) || + (bidRequest.nativeParams.icon && ((bidRequest.nativeParams.icon.sendId !== true || bidRequest.nativeParams.icon.sendTargetingKeys === true))) || + (bidRequest.nativeParams.clickUrl && ((bidRequest.nativeParams.clickUrl.sendId !== true || bidRequest.nativeParams.clickUrl.sendTargetingKeys === true))) || + (bidRequest.nativeParams.displayUrl && ((bidRequest.nativeParams.displayUrl.sendId !== true || bidRequest.nativeParams.displayUrl.sendTargetingKeys === true))) || + (bidRequest.nativeParams.privacyLink && ((bidRequest.nativeParams.privacyLink.sendId !== true || bidRequest.nativeParams.privacyLink.sendTargetingKeys === true))) || + (bidRequest.nativeParams.privacyIcon && ((bidRequest.nativeParams.privacyIcon.sendId !== true || bidRequest.nativeParams.privacyIcon.sendTargetingKeys === true))) + )); } /** @@ -284,8 +281,8 @@ function buildCdbRequest(context, bidRequests, bidderRequest) { if (bidRequest.params.zoneId) { slot.zoneid = bidRequest.params.zoneId; } - if (bidRequest.fpd && bidRequest.fpd.context) { - slot.ext = bidRequest.fpd.context; + if (utils.deepAccess(bidRequest, 'ortb2Imp.ext')) { + slot.ext = bidRequest.ortb2Imp.ext; } if (bidRequest.params.ext) { slot.ext = Object.assign({}, slot.ext, bidRequest.params.ext); @@ -416,6 +413,7 @@ function hasValidVideoMediaType(bidRequest) { */ function createPrebidNativeAd(payload) { return { + sendTargetingKeys: false, // no key is added to KV by default title: payload.products[0].title, body: payload.products[0].description, sponsoredBy: payload.advertiser.description, diff --git a/modules/dfpAdServerVideo.js b/modules/dfpAdServerVideo.js index 13677c90bae..22c904b604c 100644 --- a/modules/dfpAdServerVideo.js +++ b/modules/dfpAdServerVideo.js @@ -8,7 +8,7 @@ import { deepAccess, isEmpty, logError, parseSizesInput, formatQS, parseUrl, bui import { config } from '../src/config.js'; import { getHook, submodule } from '../src/hook.js'; import { auctionManager } from '../src/auctionManager.js'; -import { uspDataHandler } from '../src/adapterManager.js'; +import { gdprDataHandler, uspDataHandler } from '../src/adapterManager.js'; import events from '../src/events.js'; import CONSTANTS from '../src/constants.json'; @@ -101,6 +101,13 @@ export function buildDfpVideoUrl(options) { const descriptionUrl = getDescriptionUrl(bid, options, 'params'); if (descriptionUrl) { queryParams.description_url = descriptionUrl; } + const gdprConsent = gdprDataHandler.getConsentData(); + if (gdprConsent) { + if (typeof gdprConsent.gdprApplies === 'boolean') { queryParams.gdpr = Number(gdprConsent.gdprApplies); } + if (gdprConsent.consentString) { queryParams.gdpr_consent = gdprConsent.consentString; } + if (gdprConsent.addtlConsent) { queryParams.addtl_consent = gdprConsent.addtlConsent; } + } + const uspConsent = uspDataHandler.getConsentData(); if (uspConsent) { queryParams.us_privacy = uspConsent; } @@ -187,6 +194,13 @@ export function buildAdpodVideoUrl({code, params, callback} = {}) { { cust_params: encodedCustomParams } ); + const gdprConsent = gdprDataHandler.getConsentData(); + if (gdprConsent) { + if (typeof gdprConsent.gdprApplies === 'boolean') { queryParams.gdpr = Number(gdprConsent.gdprApplies); } + if (gdprConsent.consentString) { queryParams.gdpr_consent = gdprConsent.consentString; } + if (gdprConsent.addtlConsent) { queryParams.addtl_consent = gdprConsent.addtlConsent; } + } + const uspConsent = uspDataHandler.getConsentData(); if (uspConsent) { queryParams.us_privacy = uspConsent; } diff --git a/modules/districtmDMXBidAdapter.js b/modules/districtmDMXBidAdapter.js index 60f6b9b64b1..ec0a0a2f2e6 100644 --- a/modules/districtmDMXBidAdapter.js +++ b/modules/districtmDMXBidAdapter.js @@ -7,6 +7,7 @@ const BIDDER_CODE = 'districtmDMX'; const DMXURI = 'https://dmx.districtm.io/b/v1'; +const GVLID = 144; const VIDEO_MAPPING = { playback_method: { 'auto_play_sound_on': 1, @@ -19,10 +20,12 @@ const VIDEO_MAPPING = { }; export const spec = { code: BIDDER_CODE, + gvlid: GVLID, + aliases: ['dmx'], supportedFormat: [BANNER, VIDEO], supportedMediaTypes: [VIDEO, BANNER], isBidRequestValid(bid) { - return !!(bid.params.dmxid && bid.params.memberid); + return !!(bid.params.memberid); }, interpretResponse(response, bidRequest) { response = response.body || {}; @@ -39,9 +42,9 @@ export const spec = { nBid.requestId = nBid.impid; nBid.width = nBid.w || width; nBid.height = nBid.h || height; - nBid.ttl = 360; + nBid.ttl = 300; nBid.mediaType = bid.mediaTypes && bid.mediaTypes.video ? 'video' : 'banner'; - if (nBid.mediaType) { + if (nBid.mediaType === 'video') { nBid.vastXml = cleanVast(nBid.adm, nBid.nurl); nBid.ttl = 3600; } @@ -152,7 +155,7 @@ export const spec = { let tosendtags = bidRequest.map(dmx => { var obj = {}; obj.id = dmx.bidId; - obj.tagid = String(dmx.params.dmxid); + obj.tagid = String(dmx.params.dmxid || dmx.adUnitCode); obj.secure = 1; obj.bidfloor = getFloor(dmx); if (dmx.mediaTypes && dmx.mediaTypes.video) { diff --git a/modules/docereeBidAdapter.js b/modules/docereeBidAdapter.js new file mode 100644 index 00000000000..f9f3e1bcc70 --- /dev/null +++ b/modules/docereeBidAdapter.js @@ -0,0 +1,65 @@ +import * as utils from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { config } from '../src/config.js'; +import { BANNER } from '../src/mediaTypes.js'; +const BIDDER_CODE = 'doceree'; +const END_POINT = 'https://bidder.doceree.com' + +export const spec = { + code: BIDDER_CODE, + url: '', + supportedMediaTypes: [ BANNER ], + + isBidRequestValid: (bid) => { + const { placementId } = bid.params; + return !!placementId + }, + buildRequests: (validBidRequests) => { + const serverRequests = []; + const { data } = config.getConfig('doceree.user') + const { page, domain, token } = config.getConfig('doceree.context') + const encodedUserInfo = window.btoa(encodeURIComponent(JSON.stringify(data))) + + validBidRequests.forEach(function(validBidRequest) { + const { publisherUrl, placementId } = validBidRequest.params; + const url = publisherUrl || page + let queryString = ''; + queryString = utils.tryAppendQueryString(queryString, 'id', placementId); + queryString = utils.tryAppendQueryString(queryString, 'publisherDomain', domain); + queryString = utils.tryAppendQueryString(queryString, 'pubRequestedURL', encodeURIComponent(url)); + queryString = utils.tryAppendQueryString(queryString, 'loggedInUser', encodedUserInfo); + queryString = utils.tryAppendQueryString(queryString, 'currentUrl', url); + queryString = utils.tryAppendQueryString(queryString, 'prebidjs', true); + queryString = utils.tryAppendQueryString(queryString, 'token', token); + queryString = utils.tryAppendQueryString(queryString, 'requestId', validBidRequest.bidId); + + serverRequests.push({ + method: 'GET', + url: END_POINT + '/v1/adrequest?' + queryString + }) + }) + return serverRequests; + }, + interpretResponse: (serverResponse, request) => { + const responseJson = serverResponse ? serverResponse.body : {}; + const placementId = responseJson.DIVID; + const bidResponse = { + ad: responseJson.sourceHTML, + width: Number(responseJson.width), + height: Number(responseJson.height), + requestId: responseJson.guid, + netRevenue: true, + ttl: 30, + cpm: responseJson.cpmBid, + currency: responseJson.currency, + mediaType: 'banner', + creativeId: placementId, + meta: { + advertiserDomains: [responseJson.advertiserDomain] + } + }; + return [bidResponse]; + } +}; + +registerBidder(spec); diff --git a/modules/docereeBidAdapter.md b/modules/docereeBidAdapter.md index 9e3d4bbe1b8..d977e11f40a 100644 --- a/modules/docereeBidAdapter.md +++ b/modules/docereeBidAdapter.md @@ -1,32 +1,33 @@ # Overview -Module Name: Doceree Bidder Adapter -Module Type: Bidder Adapter - -# Description +``` +Module Name: Doceree Bidder Adapter +Module Type: Bidder Adapter +Maintainer: sourbh.gupta@doceree.com +``` + Connects to Doceree demand source to fetch bids. -Please use ```doceree``` as the bidder code. +Please use ```doceree``` as the bidder code. + # Test Parameters ``` - var adUnits = [ +var adUnits = [ + { + code: 'doceree', + sizes: [ + [300, 250] + ], + bids: [ { - code: 'desktop-banner-ad-div', - sizes: [[300, 250]], - bids: [ - { - bidder: "doceree", - params: { - accountID: '167283', - zoneID: '445501', - domain: 'adbserver.doceree.com', - extra: { - tuid: '1234-abcd' - } - } - } - ] - }, - ]; + bidder: "doceree", + params: { + placementId: 'DOC_7jm9j5eqkl0xvc5w', //required + publisherUrl: document.URL || window.location.href, //optional + } + } + ] + } +]; ``` diff --git a/modules/dspxBidAdapter.js b/modules/dspxBidAdapter.js index d05549601e1..dd49a744225 100644 --- a/modules/dspxBidAdapter.js +++ b/modules/dspxBidAdapter.js @@ -123,7 +123,44 @@ export const spec = { bidResponses.push(bidResponse); } return bidResponses; + }, + getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent) { + if (!serverResponses || serverResponses.length === 0) { + return []; + } + + const syncs = [] + + let gdprParams = ''; + if (gdprConsent) { + if ('gdprApplies' in gdprConsent && typeof gdprConsent.gdprApplies === 'boolean') { + gdprParams = `gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; + } else { + gdprParams = `gdpr_consent=${gdprConsent.consentString}`; + } + } + + if (syncOptions.iframeEnabled) { + serverResponses[0].body.userSync.iframeUrl.forEach((url) => syncs.push({ + type: 'iframe', + url: appendToUrl(url, gdprParams) + })); + } + if (syncOptions.pixelEnabled && serverResponses.length > 0) { + serverResponses[0].body.userSync.imageUrl.forEach((url) => syncs.push({ + type: 'image', + url: appendToUrl(url, gdprParams) + })); + } + return syncs; + } +} + +function appendToUrl(url, what) { + if (!what) { + return url; } + return url + (url.indexOf('?') !== -1 ? '&' : '?') + what; } function objectToQueryString(obj, prefix) { diff --git a/modules/emx_digitalBidAdapter.js b/modules/emx_digitalBidAdapter.js index 72da18d5691..41a5af6d703 100644 --- a/modules/emx_digitalBidAdapter.js +++ b/modules/emx_digitalBidAdapter.js @@ -158,11 +158,11 @@ export const emxAdapter = { return emxData; }, - getSupplyChain: (bidRequests, emxData) => { - if (bidRequests.schain) { + getSupplyChain: (bidderRequest, emxData) => { + if (bidderRequest.bids[0] && bidderRequest.bids[0].schain) { emxData.source = { ext: { - schain: bidRequests.schain + schain: bidderRequest.bids[0].schain } }; } diff --git a/modules/engageyaBidAdapter.js b/modules/engageyaBidAdapter.js new file mode 100644 index 00000000000..321b3287c2b --- /dev/null +++ b/modules/engageyaBidAdapter.js @@ -0,0 +1,133 @@ +import { + BANNER, + NATIVE +} from '../src/mediaTypes.js'; + +const { + registerBidder +} = require('../src/adapters/bidderFactory.js'); +const BIDDER_CODE = 'engageya'; +const ENDPOINT_URL = 'https://recs.engageya.com/rec-api/getrecs.json'; +const ENDPOINT_METHOD = 'GET'; + +function getPageUrl() { + var pUrl = window.location.href; + if (isInIframe()) { + pUrl = document.referrer ? document.referrer : pUrl; + } + pUrl = encodeURIComponent(pUrl); + return pUrl; +} + +function isInIframe() { + try { + var isInIframe = (window.self !== window.top); + } catch (e) { + isInIframe = true; + } + return isInIframe; +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, NATIVE], + isBidRequestValid: function(bid) { + return bid && bid.params && bid.params.hasOwnProperty('widgetId') && bid.params.hasOwnProperty('websiteId') && !isNaN(bid.params.widgetId) && !isNaN(bid.params.websiteId); + }, + + buildRequests: function(validBidRequests, bidderRequest) { + var bidRequests = []; + if (validBidRequests && validBidRequests.length > 0) { + validBidRequests.forEach(function(bidRequest) { + if (bidRequest.params) { + var mediaType = bidRequest.hasOwnProperty('nativeParams') ? 1 : 2; + var imageWidth = -1; + var imageHeight = -1; + if (bidRequest.sizes && bidRequest.sizes.length > 0) { + imageWidth = bidRequest.sizes[0][0]; + imageHeight = bidRequest.sizes[0][1]; + } else if (bidRequest.nativeParams && bidRequest.nativeParams.image && bidRequest.nativeParams.image.sizes) { + imageWidth = bidRequest.nativeParams.image.sizes[0]; + imageHeight = bidRequest.nativeParams.image.sizes[1]; + } + + var widgetId = bidRequest.params.widgetId; + var websiteId = bidRequest.params.websiteId; + var pageUrl = (bidRequest.params.pageUrl && bidRequest.params.pageUrl != '[PAGE_URL]') ? bidRequest.params.pageUrl : ''; + if (!pageUrl) { + pageUrl = (bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.referer) ? bidderRequest.refererInfo.referer : getPageUrl(); + } + var bidId = bidRequest.bidId; + var finalUrl = ENDPOINT_URL + '?pubid=0&webid=' + websiteId + '&wid=' + widgetId + '&url=' + pageUrl + '&ireqid=' + bidId + '&pbtpid=' + mediaType + '&imw=' + imageWidth + '&imh=' + imageHeight; + if (bidderRequest && bidderRequest.gdprConsent && bidderRequest.gdprApplies && bidderRequest.consentString) { + finalUrl += '&is_gdpr=1&gdpr_consent=' + bidderRequest.consentString; + } + bidRequests.push({ + url: finalUrl, + method: ENDPOINT_METHOD, + data: '' + }); + } + }); + } + + return bidRequests; + }, + + interpretResponse: function(serverResponse, bidRequest) { + const bidResponses = []; + if (serverResponse.body && serverResponse.body.recs && serverResponse.body.recs.length > 0) { + var response = serverResponse.body; + var isNative = response.pbtypeId == 1; + response.recs.forEach(function(rec) { + var imageSrc = rec.thumbnail_path.indexOf('http') == -1 ? 'https:' + rec.thumbnail_path : rec.thumbnail_path; + if (isNative) { + bidResponses.push({ + requestId: response.ireqId, + cpm: rec.ecpm, + width: response.imageWidth, + height: response.imageHeight, + creativeId: rec.postId, + currency: 'USD', + netRevenue: false, + ttl: 360, + native: { + title: rec.title, + body: '', + image: { + url: imageSrc, + width: response.imageWidth, + height: response.imageHeight + }, + privacyLink: '', + clickUrl: rec.clickUrl, + displayUrl: rec.url, + cta: '', + sponsoredBy: rec.displayName, + impressionTrackers: [], + }, + }); + } else { + // var htmlTag = ""; + var htmlTag = '
'; + var tag = rec.tag ? rec.tag : htmlTag; + bidResponses.push({ + requestId: response.ireqId, + cpm: rec.ecpm, + width: response.imageWidth, + height: response.imageHeight, + creativeId: rec.postId, + currency: 'USD', + netRevenue: false, + ttl: 360, + ad: tag, + }); + } + }); + } + + return bidResponses; + } +}; + +registerBidder(spec); diff --git a/modules/engageyaBidAdapter.md b/modules/engageyaBidAdapter.md new file mode 100644 index 00000000000..541ba548eeb --- /dev/null +++ b/modules/engageyaBidAdapter.md @@ -0,0 +1,68 @@ +# Overview + +``` +Module Name: Engageya's Bidder Adapter +Module Type: Bidder Adapter +Maintainer: reem@engageya.com +``` + +# Description + +Module that connects to Engageya's demand sources + +# Test Parameters +``` + var adUnits = [ + { + code: 'test-div', + mediaTypes: { + banner: { + sizes: [[300, 250]], // a display size + } + }, + bids: [ + { + bidder: "engageya", + params: { + widgetId: '', + websiteId: '', + pageUrl:'[PAGE_URL]' + } + } + ] + },{ + code: 'test-div', + mediaTypes: { + native: { + image: { + required: true, + sizes: [236, 202] + }, + title: { + required: true, + len: 80 + }, + sponsoredBy: { + required: true + }, + clickUrl: { + required: true + }, + body: { + required: true + } + } + }, + bids: [ + { + bidder: "engageya", + params: { + widgetId: '', + websiteId: '', + pageUrl:'[PAGE_URL]' + } + } + ] + } + ]; +``` \ No newline at end of file diff --git a/modules/eplanningBidAdapter.js b/modules/eplanningBidAdapter.js index ac5ba659ad7..f85423ff05c 100644 --- a/modules/eplanningBidAdapter.js +++ b/modules/eplanningBidAdapter.js @@ -8,7 +8,7 @@ const BIDDER_CODE = 'eplanning'; const rnd = Math.random(); const DEFAULT_SV = 'ads.us.e-planning.net'; const DEFAULT_ISV = 'i.e-planning.net'; -const PARAMS = ['ci', 'sv', 't', 'ml']; +const PARAMS = ['ci', 'sv', 't', 'ml', 'sn']; const DOLLARS = 'USD'; const NET_REVENUE = true; const TTL = 120; @@ -16,6 +16,9 @@ const NULL_SIZE = '1x1'; const FILE = 'file'; const STORAGE_RENDER_PREFIX = 'pbsr_'; const STORAGE_VIEW_PREFIX = 'pbvi_'; +const mobileUserAgent = isMobileUserAgent(); +const PRIORITY_ORDER_FOR_MOBILE_SIZES_ASC = ['1x1', '300x50', '320x50', '300x250']; +const PRIORITY_ORDER_FOR_DESKTOP_SIZES_ASC = ['1x1', '970x90', '970x250', '160x600', '300x600', '728x90', '300x250']; export const spec = { code: BIDDER_CODE, @@ -140,6 +143,18 @@ export const spec = { }, } +function getUserAgent() { + return window.navigator.userAgent; +} +function getInnerWidth() { + return utils.getWindowSelf().innerWidth; +} +function isMobileUserAgent() { + return getUserAgent().match(/(mobile)|(ip(hone|ad))|(android)|(blackberry)|(nokia)|(phone)|(opera\smini)/i); +} +function isMobileDevice() { + return (getInnerWidth() <= 1024) || window.orientation || mobileUserAgent; +} function getUrlConfig(bidRequests) { if (isTestRequest(bidRequests)) { return getTestConfig(bidRequests.filter(br => br.params.t)); @@ -173,8 +188,32 @@ function getTestConfig(bidRequests) { }; } +function compareSizesByPriority(size1, size2) { + var priorityOrderForSizesAsc = isMobileDevice() ? PRIORITY_ORDER_FOR_MOBILE_SIZES_ASC : PRIORITY_ORDER_FOR_DESKTOP_SIZES_ASC; + var index1 = priorityOrderForSizesAsc.indexOf(size1); + var index2 = priorityOrderForSizesAsc.indexOf(size2); + if (index1 > -1) { + if (index2 > -1) { + return (index1 < index2) ? 1 : -1; + } else { + return -1; + } + } else { + return (index2 > -1) ? 1 : 0; + } +} + +function getSizesSortedByPriority(sizes) { + return utils.parseSizesInput(sizes).sort(compareSizesByPriority); +} + function getSize(bid, first) { - return bid.sizes && bid.sizes.length ? utils.parseSizesInput(first ? bid.sizes[0] : bid.sizes).join(',') : NULL_SIZE; + var arraySizes = bid.sizes && bid.sizes.length ? getSizesSortedByPriority(bid.sizes) : []; + if (arraySizes.length) { + return first ? arraySizes[0] : arraySizes.join(','); + } else { + return NULL_SIZE; + } } function getSpacesStruct(bids) { @@ -197,7 +236,14 @@ function getSpaces(bidRequests, ml) { let es = {str: '', vs: '', map: {}}; es.str = Object.keys(spacesStruct).map(size => spacesStruct[size].map((bid, i) => { es.vs += getVs(bid); - let name = ml ? cleanName(bid.adUnitCode) : getSize(bid, true) + '_' + i; + + let name; + if (ml) { + name = cleanName(bid.adUnitCode); + } else { + name = (bid.params && bid.params.sn) || (getSize(bid, true) + '_' + i); + } + es.map[name] = bid.bidId; return name + ':' + getSize(bid); }).join('+')).join('+'); diff --git a/modules/fabrickIdSystem.js b/modules/fabrickIdSystem.js index e61b377eefa..bb838788f07 100644 --- a/modules/fabrickIdSystem.js +++ b/modules/fabrickIdSystem.js @@ -46,7 +46,7 @@ export const fabrickIdSubmodule = { if (window.fabrickMod1) { window.fabrickMod1(configParams, consentData, cacheIdObj); } - if (!configParams || typeof configParams.apiKey !== 'string') { + if (!configParams || !configParams.apiKey || typeof configParams.apiKey !== 'string') { utils.logError('fabrick submodule requires an apiKey.'); return; } @@ -55,28 +55,34 @@ export const fabrickIdSubmodule = { let keysArr = Object.keys(configParams); for (let i in keysArr) { let k = keysArr[i]; - if (k === 'url' || k === 'refererInfo') { + if (k === 'url' || k === 'refererInfo' || (k.length > 3 && k.substring(0, 3) === 'max')) { continue; } let v = configParams[k]; if (Array.isArray(v)) { for (let j in v) { - url += `${k}=${v[j]}&`; + if (typeof v[j] === 'string' || typeof v[j] === 'number') { + url += `${k}=${v[j]}&`; + } } - } else { + } else if (typeof v === 'string' || typeof v === 'number') { url += `${k}=${v}&`; } } // pull off the trailing & url = url.slice(0, -1) const referer = _getRefererInfo(configParams); - const urls = new Set(); - url = truncateAndAppend(urls, url, 'r', referer.referer); + const refs = new Map(); + _setReferrer(refs, referer.referer); if (referer.stack && referer.stack[0]) { - url = truncateAndAppend(urls, url, 'r', referer.stack[0]); + _setReferrer(refs, referer.stack[0]); } - url = truncateAndAppend(urls, url, 'r', referer.canonicalUrl); - url = truncateAndAppend(urls, url, 'r', window.location.href); + _setReferrer(refs, referer.canonicalUrl); + _setReferrer(refs, window.location.href); + + refs.forEach(v => { + url = appendUrl(url, 'r', v, configParams); + }); const resp = function (callback) { const callbacks = { @@ -130,18 +136,48 @@ function _getBaseUrl(configParams) { } } -function truncateAndAppend(urls, url, paramName, s) { - if (s && url.length < 2000) { - if (s.length > 200) { - s = s.substring(0, 200); +function _setReferrer(refs, s) { + if (s) { + // store the longest one for the same URI + const url = s.split('?')[0]; + // OR store the longest one for the same domain + // const url = s.split('?')[0].replace('http://','').replace('https://', '').split('/')[0]; + if (refs.has(url)) { + const prevRef = refs.get(url); + if (s.length > prevRef.length) { + refs.set(url, s); + } + } else { + refs.set(url, s); + } + } +} + +export function appendUrl(url, paramName, s, configParams) { + const maxUrlLen = (configParams && configParams.maxUrlLen) || 2000; + const maxRefLen = (configParams && configParams.maxRefLen) || 1000; + const maxSpaceAvailable = (configParams && configParams.maxSpaceAvailable) || 50; + // make sure we have enough space left to make it worthwhile + if (s && url.length < (maxUrlLen - maxSpaceAvailable)) { + let thisMaxRefLen = maxUrlLen - url.length; + if (thisMaxRefLen > maxRefLen) { + thisMaxRefLen = maxRefLen; } - // Don't send the same url in multiple params - if (!urls.has(s)) { - urls.add(s); - return `${url}&${paramName}=${s}` + + s = `&${paramName}=${encodeURIComponent(s)}`; + + if (s.length >= thisMaxRefLen) { + s = s.substring(0, thisMaxRefLen); + if (s.charAt(s.length - 1) === '%') { + s = s.substring(0, thisMaxRefLen - 1); + } else if (s.charAt(s.length - 2) === '%') { + s = s.substring(0, thisMaxRefLen - 2); + } } + return `${url}${s}` + } else { + return url; } - return url; } submodule('userId', fabrickIdSubmodule); diff --git a/modules/fidelityBidAdapter.js b/modules/fidelityBidAdapter.js index baf5384fbfe..fac273721ff 100644 --- a/modules/fidelityBidAdapter.js +++ b/modules/fidelityBidAdapter.js @@ -6,7 +6,6 @@ const BIDDER_SERVER = 'x.fidelity-media.com'; const FIDELITY_VENDOR_ID = 408; export const spec = { code: BIDDER_CODE, - aliases: ['kubient'], gvlid: 408, isBidRequestValid: function isBidRequestValid(bid) { return !!(bid && bid.params && bid.params.zoneid); diff --git a/modules/freewheel-sspBidAdapter.js b/modules/freewheel-sspBidAdapter.js index 53f490a0a3c..1c9cd75f76f 100644 --- a/modules/freewheel-sspBidAdapter.js +++ b/modules/freewheel-sspBidAdapter.js @@ -102,22 +102,49 @@ function getCreativeId(xmlNode) { return creaId; } -function getDealId(xmlNode) { - var dealId = ''; +function getValueFromKeyInImpressionNode(xmlNode, key) { + var value = ''; var impNodes = xmlNode.querySelectorAll('Impression'); // Nodelist.forEach is not supported in IE and Edge - // Workaround given here https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/10638731/ - + var isRootViewKeyPresent = false; + var isAdsDisplayStartedPresent = false; Array.prototype.forEach.call(impNodes, function (el) { - var queries = el.textContent.substring(el.textContent.indexOf('?') + 1).split('&'); + if (isRootViewKeyPresent && isAdsDisplayStartedPresent) { + return value; + } + isRootViewKeyPresent = false; + isAdsDisplayStartedPresent = false; + var text = el.textContent; + var queries = text.substring(el.textContent.indexOf('?') + 1).split('&'); + var tempValue = ''; Array.prototype.forEach.call(queries, function (item) { var split = item.split('='); - if (split[0] == 'dealId') { - dealId = split[1]; + if (split[0] == key) { + tempValue = split[1]; + } + if (split[0] == 'reqType' && split[1] == 'AdsDisplayStarted') { + isAdsDisplayStartedPresent = true; + } + if (split[0] == 'rootViewKey') { + isRootViewKeyPresent = true; } }); + if (isAdsDisplayStartedPresent) { + value = tempValue; + } }); + return value; +} - return dealId; +function getDealId(xmlNode) { + return getValueFromKeyInImpressionNode(xmlNode, 'dealId'); +} + +function getBannerId(xmlNode) { + return getValueFromKeyInImpressionNode(xmlNode, 'adId'); +} + +function getCampaignId(xmlNode) { + return getValueFromKeyInImpressionNode(xmlNode, 'campaignId'); } /** @@ -373,7 +400,8 @@ export const spec = { const princingData = getPricing(xmlDoc); const creativeId = getCreativeId(xmlDoc); const dealId = getDealId(xmlDoc); - + const campaignId = getCampaignId(xmlDoc); + const bannerId = getBannerId(xmlDoc); const topWin = getTopMostWindow(); if (!topWin.freewheelssp_cache) { topWin.freewheelssp_cache = {}; @@ -392,7 +420,9 @@ export const spec = { currency: princingData.currency, netRevenue: true, ttl: 360, - dealId: dealId + dealId: dealId, + campaignId: campaignId, + bannerId: bannerId }; if (bidrequest.mediaTypes.video) { @@ -426,6 +456,6 @@ export const spec = { return []; } }, +}; -} registerBidder(spec); diff --git a/modules/gamoshiBidAdapter.js b/modules/gamoshiBidAdapter.js index 48a142a66a6..de839219897 100644 --- a/modules/gamoshiBidAdapter.js +++ b/modules/gamoshiBidAdapter.js @@ -3,6 +3,7 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {config} from '../src/config.js'; import {Renderer} from '../src/Renderer.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import includes from 'core-js-pure/features/array/includes.js'; const ENDPOINTS = { 'gamoshi': 'https://rtb.gamoshi.io' @@ -42,7 +43,7 @@ export const helper = { export const spec = { code: 'gamoshi', - aliases: ['gambid', 'cleanmedia', '9MediaOnline', 'MobfoxX'], + aliases: ['gambid', '9MediaOnline'], supportedMediaTypes: ['banner', 'video'], isBidRequestValid: function (bid) { @@ -111,7 +112,7 @@ export const spec = { }; const hasFavoredMediaType = - params.favoredMediaType && this.supportedMediaTypes.includes(params.favoredMediaType); + params.favoredMediaType && includes(this.supportedMediaTypes, params.favoredMediaType); if (!mediaTypes || mediaTypes.banner) { if (!hasFavoredMediaType || params.favoredMediaType === BANNER) { diff --git a/modules/geoedgeRtdProvider.md b/modules/geoedgeRtdProvider.md index e4aa046a97d..5414606612c 100644 --- a/modules/geoedgeRtdProvider.md +++ b/modules/geoedgeRtdProvider.md @@ -4,7 +4,7 @@ Module Name: Geoedge Rtd provider Module Type: Rtd Provider Maintainer: guy.books@geoedge.com -The Geoedge Realtime module let pusblishers to block bad ads such as automatic redirects, malware, offensive creatives and landing pages. +The Geoedge Realtime module lets publishers block bad ads such as automatic redirects, malware, offensive creatives and landing pages. To use this module, you'll need to work with [Geoedge](https://www.geoedge.com/publishers-real-time-protection/) to get an account and cutomer key. ## Integration diff --git a/modules/gjirafaBidAdapter.js b/modules/gjirafaBidAdapter.js index 48496b52c05..77589cd9071 100644 --- a/modules/gjirafaBidAdapter.js +++ b/modules/gjirafaBidAdapter.js @@ -31,6 +31,7 @@ export const spec = { let bidderRequestId = ''; let url = ''; let contents = []; + let data = {}; let placements = validBidRequests.map(bidRequest => { if (!propertyId) { propertyId = bidRequest.params.propertyId; } @@ -38,7 +39,8 @@ export const spec = { if (!storageId && bidRequest.params) { storageId = bidRequest.params.storageId || ''; } if (!bidderRequestId) { bidderRequestId = bidRequest.bidderRequestId; } if (!url && bidderRequest) { url = bidderRequest.refererInfo.referer; } - if (!contents.length && bidRequest.params.contents && bidRequest.params.contents.length) { contents = bidRequest.params.contents } + if (!contents.length && bidRequest.params.contents && bidRequest.params.contents.length) { contents = bidRequest.params.contents; } + if (Object.keys(data).length === 0 && bidRequest.params.data && Object.keys(bidRequest.params.data).length !== 0) { data = bidRequest.params.data; } let adUnitId = bidRequest.adUnitCode; let placementId = bidRequest.params.placementId; @@ -49,6 +51,8 @@ export const spec = { adUnitId: adUnitId, placementId: placementId, bidid: bidRequest.bidId, + count: bidRequest.params.count, + skipTime: bidRequest.params.skipTime }; }); @@ -59,7 +63,8 @@ export const spec = { url: url, requestid: bidderRequestId, placements: placements, - contents: contents + contents: contents, + data: data } return [{ diff --git a/modules/gjirafaBidAdapter.md b/modules/gjirafaBidAdapter.md index deb06e74a27..fb4960d61f6 100644 --- a/modules/gjirafaBidAdapter.md +++ b/modules/gjirafaBidAdapter.md @@ -1,45 +1,67 @@ # Overview -Module Name: Gjirafa Bidder Adapter Module -Type: Bidder Adapter -Maintainer: drilon@gjirafa.com +Module Name: Gjirafa Bidder Adapter Module + +Type: Bidder Adapter + +Maintainer: arditb@gjirafa.com # Description Gjirafa Bidder Adapter for Prebid.js. # Test Parameters +```js var adUnits = [ { code: 'test-div', mediaTypes: { banner: { - sizes: [[728, 90]] + sizes: [ + [728, 90] + ] } }, - bids: [ - { - bidder: 'gjirafa', - params: { - propertyId: '105227', - placementId: '846841' + bids: [{ + bidder: 'gjirafa', + params: { + propertyId: '105227', //Required + placementId: '846841', //Required + data: { //Optional + catalogs: [{ + catalogId: 9, + items: ["193", "4", "1"] + }], + inventory: { + category: ["tech"], + query: ["iphone 12"] + } } } - ] + }] }, { code: 'test-div', mediaTypes: { - video: { + video: { context: 'instream' - } + } }, - bids: [ - { - bidder: 'gjirafa', - params: { - propertyId: '105227', - placementId: '846836' + bids: [{ + bidder: 'gjirafa', + params: { + propertyId: '105227', //Required + placementId: '846836', //Required + data: { //Optional + catalogs: [{ + catalogId: 9, + items: ["193", "4", "1"] + }], + inventory: { + category: ["tech"], + query: ["iphone 12"] + } } } - ] + }] } ]; +``` diff --git a/modules/glomexBidAdapter.js b/modules/glomexBidAdapter.js new file mode 100644 index 00000000000..8de3d3724ce --- /dev/null +++ b/modules/glomexBidAdapter.js @@ -0,0 +1,86 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js' +import find from 'core-js-pure/features/array/find.js' +import { BANNER } from '../src/mediaTypes.js' + +const ENDPOINT = 'https://prebid.mes.glomex.cloud/request-bid' +const BIDDER_CODE = 'glomex' + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + + isBidRequestValid: function (bid) { + if (bid && bid.params && bid.params.integrationId) { + return true + } + return false + }, + + buildRequests: function (validBidRequests, bidderRequest = {}) { + const refererInfo = bidderRequest.refererInfo || {}; + const gdprConsent = bidderRequest.gdprConsent || {}; + + return { + method: 'POST', + url: `${ENDPOINT}`, + data: { + auctionId: bidderRequest.auctionId, + refererInfo: { + isAmp: refererInfo.isAmp, + numIframes: refererInfo.numIframes, + reachedTop: refererInfo.reachedTop, + referer: refererInfo.referer + }, + gdprConsent: { + consentString: gdprConsent.consentString, + gdprApplies: gdprConsent.gdprApplies + }, + bidRequests: validBidRequests.map(({ params, sizes, bidId, adUnitCode }) => ({ + bidId, + adUnitCode, + params, + sizes + })) + }, + options: { + withCredentials: false, + contentType: 'application/json' + }, + validBidRequests: validBidRequests, + } + }, + + interpretResponse: function (serverResponse, originalBidRequest) { + const bidResponses = [] + + originalBidRequest.validBidRequests.forEach(function (bidRequest) { + if (!serverResponse.body) { + return + } + + const matchedBid = find(serverResponse.body.bids, function (bid) { + return String(bidRequest.bidId) === String(bid.id) + }) + + if (matchedBid) { + const bidResponse = { + requestId: bidRequest.bidId, + cpm: matchedBid.cpm, + width: matchedBid.width, + height: matchedBid.height, + creativeId: matchedBid.creativeId, + dealId: matchedBid.dealId, + currency: matchedBid.currency, + netRevenue: matchedBid.netRevenue, + ttl: matchedBid.ttl, + ad: matchedBid.ad + } + + bidResponses.push(bidResponse) + } + }) + return bidResponses + } +}; + +registerBidder(spec) diff --git a/modules/glomexBidAdapter.md b/modules/glomexBidAdapter.md new file mode 100644 index 00000000000..d52ed88ff16 --- /dev/null +++ b/modules/glomexBidAdapter.md @@ -0,0 +1,32 @@ +# Overview + +``` +Module Name: Glomex Bidder Adapter +Module Type: Bidder Adapter +Maintainer: integration-squad@services.glomex.com +``` + +# Description + +Module to use the Glomex Player with prebid.js + +# Test Parameters +``` + var adUnits = [ + { + code: "banner", + mediaTypes: { + banner: { + sizes: [[640, 360]] + } + }, + bids: [{ + bidder: "glomex", + params: { + integrationId: '4059a11hkdzuf65i', + playlistId: 'v-bdui4dz7vjq9' + } + }] + } + ]; +``` diff --git a/modules/gmosspBidAdapter.js b/modules/gmosspBidAdapter.js index d9dc8f7641a..5eac4069b21 100644 --- a/modules/gmosspBidAdapter.js +++ b/modules/gmosspBidAdapter.js @@ -29,7 +29,7 @@ export const spec = { buildRequests: function (validBidRequests, bidderRequest) { const bidRequests = []; - const url = bidderRequest.refererInfo.referer; + const urlInfo = getUrlInfo(bidderRequest.refererInfo); const cur = getCurrencyType(); const dnt = utils.getDNT() ? '1' : '0'; @@ -46,7 +46,8 @@ export const spec = { queryString = utils.tryAppendQueryString(queryString, 'bid', bid); queryString = utils.tryAppendQueryString(queryString, 'ver', ver); queryString = utils.tryAppendQueryString(queryString, 'sid', sid); - queryString = utils.tryAppendQueryString(queryString, 'url', url); + queryString = utils.tryAppendQueryString(queryString, 'url', urlInfo.url); + queryString = utils.tryAppendQueryString(queryString, 'ref', urlInfo.ref); queryString = utils.tryAppendQueryString(queryString, 'cur', cur); queryString = utils.tryAppendQueryString(queryString, 'dnt', dnt); @@ -131,4 +132,31 @@ function getCurrencyType() { return 'JPY'; } +function getUrlInfo(refererInfo) { + return { + url: getUrl(refererInfo), + ref: getReferrer(), + }; +} + +function getUrl(refererInfo) { + if (refererInfo && refererInfo.referer) { + return refererInfo.referer; + } + + try { + return window.top.location.href; + } catch (e) { + return window.location.href; + } +} + +function getReferrer() { + try { + return window.top.document.referrer; + } catch (e) { + return document.referrer; + } +} + registerBidder(spec); diff --git a/modules/gothamadsBidAdapter.js b/modules/gothamadsBidAdapter.js new file mode 100644 index 00000000000..f2d2a9f5261 --- /dev/null +++ b/modules/gothamadsBidAdapter.js @@ -0,0 +1,307 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import * as utils from '../src/utils.js'; +import {config} from '../src/config.js'; + +const BIDDER_CODE = 'gothamads'; +const ACCOUNTID_MACROS = '[account_id]'; +const URL_ENDPOINT = `https://us-e-node1.gothamads.com/bid?pass=${ACCOUNTID_MACROS}&integration=prebidjs`; +const NATIVE_ASSET_IDS = { 0: 'title', 2: 'icon', 3: 'image', 5: 'sponsoredBy', 4: 'body', 1: 'cta' }; +const NATIVE_PARAMS = { + title: { + id: 0, + name: 'title' + }, + icon: { + id: 2, + type: 1, + name: 'img' + }, + image: { + id: 3, + type: 3, + name: 'img' + }, + sponsoredBy: { + id: 5, + name: 'data', + type: 1 + }, + body: { + id: 4, + name: 'data', + type: 2 + }, + cta: { + id: 1, + type: 12, + name: 'data' + } +}; +const NATIVE_VERSION = '1.2'; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + + /** + * Determines whether or not the given bid request is valid. + * + * @param {object} bid The bid to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: (bid) => { + return Boolean(bid.params.accountId) && Boolean(bid.params.placementId) + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param {BidRequest[]} validBidRequests A non-empty list of valid bid requests that should be sent to the Server. + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: (validBidRequests, bidderRequest) => { + if (validBidRequests && validBidRequests.length === 0) return [] + let accuontId = validBidRequests[0].params.accountId; + const endpointURL = URL_ENDPOINT.replace(ACCOUNTID_MACROS, accuontId); + + let winTop = window; + let location; + try { + location = new URL(bidderRequest.refererInfo.referer) + winTop = window.top; + } catch (e) { + location = winTop.location; + utils.logMessage(e); + }; + + let bids = []; + for (let bidRequest of validBidRequests) { + let impObject = prepareImpObject(bidRequest); + let data = { + id: bidRequest.bidId, + test: config.getConfig('debug') ? 1 : 0, + cur: ['USD'], + device: { + w: winTop.screen.width, + h: winTop.screen.height, + language: (navigator && navigator.language) ? navigator.language.indexOf('-') != -1 ? navigator.language.split('-')[0] : navigator.language : '', + }, + site: { + page: location.pathname, + host: location.host + }, + source: { + tid: bidRequest.transactionId + }, + tmax: bidRequest.timeout, + imp: [impObject], + }; + bids.push(data) + } + return { + method: 'POST', + url: endpointURL, + data: bids + }; + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {*} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: (serverResponse) => { + if (!serverResponse || !serverResponse.body) return [] + let GothamAdskResponse = serverResponse.body; + + let bids = []; + for (let response of GothamAdskResponse) { + let mediaType = response.seatbid[0].bid[0].ext && response.seatbid[0].bid[0].ext.mediaType ? response.seatbid[0].bid[0].ext.mediaType : BANNER; + + let bid = { + requestId: response.id, + cpm: response.seatbid[0].bid[0].price, + width: response.seatbid[0].bid[0].w, + height: response.seatbid[0].bid[0].h, + ttl: response.ttl || 1200, + currency: response.cur || 'USD', + netRevenue: true, + creativeId: response.seatbid[0].bid[0].crid, + dealId: response.seatbid[0].bid[0].dealid, + mediaType: mediaType + }; + + switch (mediaType) { + case VIDEO: + bid.vastXml = response.seatbid[0].bid[0].adm + bid.vastUrl = response.seatbid[0].bid[0].ext.vastUrl + break + case NATIVE: + bid.native = parseNative(response.seatbid[0].bid[0].adm) + break + default: + bid.ad = response.seatbid[0].bid[0].adm + } + + bids.push(bid); + } + + return bids; + }, +}; + +/** + * Determine type of request + * + * @param bidRequest + * @param type + * @returns {boolean} + */ +const checkRequestType = (bidRequest, type) => { + return (typeof utils.deepAccess(bidRequest, `mediaTypes.${type}`) !== 'undefined'); +} + +const parseNative = admObject => { + const { assets, link, imptrackers, jstracker } = admObject.native; + const result = { + clickUrl: link.url, + clickTrackers: link.clicktrackers || undefined, + impressionTrackers: imptrackers || undefined, + javascriptTrackers: jstracker ? [ jstracker ] : undefined + }; + assets.forEach(asset => { + const kind = NATIVE_ASSET_IDS[asset.id]; + const content = kind && asset[NATIVE_PARAMS[kind].name]; + if (content) { + result[kind] = content.text || content.value || { url: content.url, width: content.w, height: content.h }; + } + }); + + return result; +} + +const prepareImpObject = (bidRequest) => { + let impObject = { + id: bidRequest.transactionId, + secure: 1, + ext: { + placementId: bidRequest.params.placementId + } + }; + if (checkRequestType(bidRequest, BANNER)) { + impObject.banner = addBannerParameters(bidRequest); + } + if (checkRequestType(bidRequest, VIDEO)) { + impObject.video = addVideoParameters(bidRequest); + } + if (checkRequestType(bidRequest, NATIVE)) { + impObject.native = { + ver: NATIVE_VERSION, + request: addNativeParameters(bidRequest) + }; + } + return impObject +}; + +const addNativeParameters = bidRequest => { + let impObject = { + id: bidRequest.transactionId, + ver: NATIVE_VERSION, + }; + + const assets = utils._map(bidRequest.mediaTypes.native, (bidParams, key) => { + const props = NATIVE_PARAMS[key]; + const asset = { + required: bidParams.required & 1, + }; + if (props) { + asset.id = props.id; + let wmin, hmin; + let aRatios = bidParams.aspect_ratios; + + if (aRatios && aRatios[0]) { + aRatios = aRatios[0]; + wmin = aRatios.min_width || 0; + hmin = aRatios.ratio_height * wmin / aRatios.ratio_width | 0; + } + + if (bidParams.sizes) { + const sizes = flatten(bidParams.sizes); + wmin = sizes[0]; + hmin = sizes[1]; + } + + asset[props.name] = {} + + if (bidParams.len) asset[props.name]['len'] = bidParams.len; + if (props.type) asset[props.name]['type'] = props.type; + if (wmin) asset[props.name]['wmin'] = wmin; + if (hmin) asset[props.name]['hmin'] = hmin; + + return asset; + } + }).filter(Boolean); + + impObject.assets = assets; + return impObject +} + +const addBannerParameters = (bidRequest) => { + let bannerObject = {}; + const size = parseSizes(bidRequest, 'banner'); + bannerObject.w = size[0]; + bannerObject.h = size[1]; + return bannerObject; +}; + +const parseSizes = (bid, mediaType) => { + let mediaTypes = bid.mediaTypes; + if (mediaType === 'video') { + let size = []; + if (mediaTypes.video && mediaTypes.video.w && mediaTypes.video.h) { + size = [ + mediaTypes.video.w, + mediaTypes.video.h + ]; + } else if (Array.isArray(utils.deepAccess(bid, 'mediaTypes.video.playerSize')) && bid.mediaTypes.video.playerSize.length === 1) { + size = bid.mediaTypes.video.playerSize[0]; + } else if (Array.isArray(bid.sizes) && bid.sizes.length > 0 && Array.isArray(bid.sizes[0]) && bid.sizes[0].length > 1) { + size = bid.sizes[0]; + } + return size; + } + let sizes = []; + if (Array.isArray(mediaTypes.banner.sizes)) { + sizes = mediaTypes.banner.sizes[0]; + } else if (Array.isArray(bid.sizes) && bid.sizes.length > 0) { + sizes = bid.sizes + } else { + utils.logWarn('no sizes are setup or found'); + } + + return sizes +} + +const addVideoParameters = (bidRequest) => { + let videoObj = {}; + let supportParamsList = ['mimes', 'minduration', 'maxduration', 'protocols', 'startdelay', 'placement', 'skip', 'skipafter', 'minbitrate', 'maxbitrate', 'delivery', 'playbackmethod', 'api', 'linearity'] + + for (let param of supportParamsList) { + if (bidRequest.mediaTypes.video[param] !== undefined) { + videoObj[param] = bidRequest.mediaTypes.video[param]; + } + } + + const size = parseSizes(bidRequest, 'video'); + videoObj.w = size[0]; + videoObj.h = size[1]; + return videoObj; +} + +const flatten = arr => { + return [].concat(...arr); +} + +registerBidder(spec); diff --git a/modules/gothamadsBidAdapter.md b/modules/gothamadsBidAdapter.md new file mode 100644 index 00000000000..3105dff6c6c --- /dev/null +++ b/modules/gothamadsBidAdapter.md @@ -0,0 +1,104 @@ +# Overview + +``` +Module Name: GothamAds SSP Bidder Adapter +Module Type: Bidder Adapter +Maintainer: support@gothamads.com +``` + +# Description + +Module that connects to GothamAds SSP demand sources + +# Test Parameters +``` + var adUnits = [{ + code: 'placementId', + mediaTypes: { + banner: { + sizes: [[300, 250], [300,600]] + } + }, + bids: [{ + bidder: 'gothamads', + params: { + placementId: 'hash', + accountId: 'accountId' + } + }] + }, + { + code: 'native_example', + // sizes: [[1, 1]], + mediaTypes: { + native: { + title: { + required: true, + len: 800 + }, + image: { + required: true, + len: 80 + }, + sponsoredBy: { + required: true + }, + clickUrl: { + required: true + }, + privacyLink: { + required: false + }, + body: { + required: true + }, + icon: { + required: true, + sizes: [50, 50] + } + } + + }, + bids: [ { + bidder: 'gothamads', + params: { + placementId: 'hash', + accountId: 'accountId' + } + }] + }, + { + code: 'video1', + sizes: [640,480], + mediaTypes: { video: { + minduration:0, + maxduration:999, + boxingallowed:1, + skip:0, + mimes:[ + 'application/javascript', + 'video/mp4' + ], + w:1920, + h:1080, + protocols:[ + 2 + ], + linearity:1, + api:[ + 1, + 2 + ] + } }, + bids: [ + { + bidder: 'gothamads', + params: { + placementId: 'hash', + accountId: 'accountId' + } + } + ] + } + ]; +``` \ No newline at end of file diff --git a/modules/gptPreAuction.js b/modules/gptPreAuction.js index 48b72671d6a..ee2b5406453 100644 --- a/modules/gptPreAuction.js +++ b/modules/gptPreAuction.js @@ -26,46 +26,48 @@ export const appendGptSlots = adUnits => { if (matchingAdUnitCode) { const adUnit = adUnitMap[matchingAdUnitCode]; - adUnit.fpd = adUnit.fpd || {}; - adUnit.fpd.context = adUnit.fpd.context || {}; + adUnit.ortb2Imp = adUnit.ortb2Imp || {}; + adUnit.ortb2Imp.ext = adUnit.ortb2Imp.ext || {}; + adUnit.ortb2Imp.ext.data = adUnit.ortb2Imp.ext.data || {}; - const context = adUnit.fpd.context; - context.adServer = context.adServer || {}; - context.adServer.name = 'gam'; - context.adServer.adSlot = slot.getAdUnitPath(); + const context = adUnit.ortb2Imp.ext.data; + context.adserver = context.adserver || {}; + context.adserver.name = 'gam'; + context.adserver.adslot = slot.getAdUnitPath(); } }); }; export const appendPbAdSlot = adUnit => { - adUnit.fpd = adUnit.fpd || {}; - adUnit.fpd.context = adUnit.fpd.context || {}; - const context = adUnit.fpd.context; + adUnit.ortb2Imp = adUnit.ortb2Imp || {}; + adUnit.ortb2Imp.ext = adUnit.ortb2Imp.ext || {}; + adUnit.ortb2Imp.ext.data = adUnit.ortb2Imp.ext.data || {}; + const context = adUnit.ortb2Imp.ext.data; const { customPbAdSlot } = _currentConfig; if (customPbAdSlot) { - context.pbAdSlot = customPbAdSlot(adUnit.code, utils.deepAccess(context, 'adServer.adSlot')); + context.pbadslot = customPbAdSlot(adUnit.code, utils.deepAccess(context, 'adserver.adslot')); return; } // use context.pbAdSlot if set - if (context.pbAdSlot) { + if (context.pbadslot) { return; } // use data attribute 'data-adslotid' if set try { const adUnitCodeDiv = document.getElementById(adUnit.code); if (adUnitCodeDiv.dataset.adslotid) { - context.pbAdSlot = adUnitCodeDiv.dataset.adslotid; + context.pbadslot = adUnitCodeDiv.dataset.adslotid; return; } } catch (e) {} // banner adUnit, use GPT adunit if defined - if (context.adServer) { - context.pbAdSlot = context.adServer.adSlot; + if (utils.deepAccess(context, 'adserver.adslot')) { + context.pbadslot = context.adserver.adslot; return; } - context.pbAdSlot = adUnit.code; + context.pbadslot = adUnit.code; }; export const makeBidRequestsHook = (fn, adUnits, ...args) => { diff --git a/modules/gridBidAdapter.js b/modules/gridBidAdapter.js index 3f9d4fba31d..964b34dcfa2 100644 --- a/modules/gridBidAdapter.js +++ b/modules/gridBidAdapter.js @@ -51,6 +51,7 @@ export const spec = { let content = null; let schain = null; let userId = null; + let userIdAsEids = null; let user = null; let userExt = null; let {bidderRequestId, auctionId, gdprConsent, uspConsent, timeout, refererInfo} = bidderRequest || {}; @@ -72,6 +73,9 @@ export const spec = { if (!userId) { userId = bid.userId; } + if (!userIdAsEids) { + userIdAsEids = bid.userIdAsEids; + } const {params: {uid, keywords}, mediaTypes, bidId, adUnitCode, rtd} = bid; bidsMap[bidId] = bid; if (!pageKeywords && !utils.isEmpty(keywords)) { @@ -161,66 +165,9 @@ export const spec = { userExt = {consent: gdprConsent.consentString}; } - if (userId) { - if (userId.tdid) { - userExt = userExt || {}; - userExt.eids = userExt.eids || []; - userExt.eids.push({ - source: 'adserver.org', // Unified ID - uids: [{ - id: userId.tdid, - ext: { - rtiPartner: 'TDID' - } - }] - }); - } - if (userId.id5id && userId.id5id.uid) { - userExt = userExt || {}; - userExt.eids = userExt.eids || []; - userExt.eids.push({ - source: 'id5-sync.com', - uids: [{ - id: userId.id5id.uid - }], - ext: userId.id5id.ext - }); - } - if (userId.lipb && userId.lipb.lipbid) { - userExt = userExt || {}; - userExt.eids = userExt.eids || []; - userExt.eids.push({ - source: 'liveintent.com', - uids: [{ - id: userId.lipb.lipbid - }] - }); - } - if (userId.idl_env) { - userExt = userExt || {}; - userExt.eids = userExt.eids || []; - userExt.eids.push({ - source: 'identityLink', - uids: [{ - id: userId.idl_env - }] - }); - } - if (userId.criteoId) { - userExt = userExt || {}; - userExt.eids = userExt.eids || []; - userExt.eids.push({ - source: 'criteo.com', - uids: [{ - id: userId.criteoId - }] - }); - } - - if (userId.digitrustid && userId.digitrustid.data && userId.digitrustid.data.id) { - userExt = userExt || {}; - userExt.digitrust = Object.assign({}, userId.digitrustid.data); - } + if (userIdAsEids && userIdAsEids.length) { + userExt = userExt || {}; + userExt.eids = [...userIdAsEids]; } if (userExt && Object.keys(userExt).length) { @@ -233,8 +180,8 @@ export const spec = { } const configKeywords = utils.transformBidderParamKeywords({ - 'user': utils.deepAccess(config.getConfig('fpd.user'), 'keywords') || null, - 'context': utils.deepAccess(config.getConfig('fpd.context'), 'keywords') || null + 'user': utils.deepAccess(config.getConfig('ortb2.user'), 'keywords') || null, + 'context': utils.deepAccess(config.getConfig('ortb2.site'), 'keywords') || null }); if (configKeywords.length) { @@ -382,7 +329,6 @@ function _addBidResponse(serverBid, bidRequest, bidResponses) { if (bid) { const bidResponse = { requestId: bid.bidId, // bid.bidderRequestId, - bidderCode: spec.code, cpm: serverBid.price, width: serverBid.w, height: serverBid.h, @@ -418,7 +364,7 @@ function _addBidResponse(serverBid, bidRequest, bidResponses) { } function createVideoRequest(bid, mediaType) { - const {playerSize, mimes, durationRangeSec} = mediaType; + const {playerSize, mimes, durationRangeSec, protocols} = mediaType; const size = (playerSize || bid.sizes || [])[0]; if (!size) return; @@ -433,6 +379,10 @@ function createVideoRequest(bid, mediaType) { result.maxduration = durationRangeSec[1]; } + if (protocols && protocols.length) { + result.protocols = protocols; + } + return result; } diff --git a/modules/gridNMBidAdapter.js b/modules/gridNMBidAdapter.js index ffd6c1b250c..af1e9f84f43 100644 --- a/modules/gridNMBidAdapter.js +++ b/modules/gridNMBidAdapter.js @@ -134,7 +134,6 @@ export const spec = { } const bidResponse = { requestId: bid.bidId, - bidderCode: spec.code, cpm: serverBid.price, width: serverBid.w, height: serverBid.h, diff --git a/modules/gumgumBidAdapter.js b/modules/gumgumBidAdapter.js index 2cb5ce61064..606f8335a19 100644 --- a/modules/gumgumBidAdapter.js +++ b/modules/gumgumBidAdapter.js @@ -208,25 +208,24 @@ function _getVidParams (attributes) { * @param {Object} bid * @returns {Number} floor */ -function _getFloor (mediaTypes, bidfloor, bid) { +function _getFloor (mediaTypes, staticBidfloor, bid) { const curMediaType = Object.keys(mediaTypes)[0] || 'banner'; - let floor = bidfloor || 0; + const bidFloor = { floor: 0, currency: 'USD' }; if (typeof bid.getFloor === 'function') { - const floorInfo = bid.getFloor({ - currency: 'USD', + const { currency, floor } = bid.getFloor({ mediaType: curMediaType, size: '*' }); + floor && (bidFloor.floor = floor); + currency && (bidFloor.currency = currency); - if (typeof floorInfo === 'object' && - floorInfo.currency === 'USD' && - !isNaN(parseFloat(floorInfo.floor))) { - floor = Math.max(floor, parseFloat(floorInfo.floor)); + if (staticBidfloor && floor && currency === 'USD') { + bidFloor.floor = Math.max(staticBidfloor, parseFloat(floor)); } } - return floor; + return bidFloor; } /** @@ -250,7 +249,7 @@ function buildRequests (validBidRequests, bidderRequest) { transactionId, userId = {} } = bidRequest; - const bidFloor = _getFloor(mediaTypes, params.bidfloor, bidRequest); + const { currency, floor } = _getFloor(mediaTypes, params.bidfloor, bidRequest); let sizes = [1, 1]; let data = {}; @@ -265,16 +264,22 @@ function buildRequests (validBidRequests, bidderRequest) { data.pv = pageViewId; } - if (bidFloor) { - data.fp = bidFloor; + if (floor) { + data.fp = floor; + data.fpc = currency; } if (params.iriscat && typeof params.iriscat === 'string') { data.iriscat = params.iriscat; } - if (params.zone) { - data.t = params.zone; + if (params.irisid && typeof params.irisid === 'string') { + data.irisid = params.irisid; + } + + if (params.zone || params.pubId) { + params.zone ? (data.t = params.zone) : (data.pubId = params.pubId); + data.pi = 2; // inscreen // override pi if the following is found if (params.slot) { @@ -285,11 +290,8 @@ function buildRequests (validBidRequests, bidderRequest) { data.ni = parseInt(params.native, 10); data.pi = 5; } else if (mediaTypes.video) { - data.pi = mediaTypes.video.linearity === 1 ? 7 : 6; // video : invideo + data.pi = mediaTypes.video.linearity === 2 ? 6 : 7; // invideo : video } - } else if (params.pubId) { - data.pubId = params.pubId - data.pi = mediaTypes.video ? 7 : 2; // video : inscreen } else { // legacy params data = { ...data, ...handleLegacyParams(params, sizes) } } @@ -386,6 +388,10 @@ function interpretResponse (serverResponse, bidRequest) { }, pag: { pvid: 0 + }, + meta: { + adomain: [], + mediaType: '' } } const { @@ -399,7 +405,11 @@ function interpretResponse (serverResponse, bidRequest) { pag: { pvid }, - jcsi + jcsi, + meta: { + adomain: advertiserDomains, + mediaType: type + } } = Object.assign(defaultResponse, serverResponseBody) let data = bidRequest.data || {} let product = data.pi @@ -407,6 +417,10 @@ function interpretResponse (serverResponse, bidRequest) { let isTestUnit = (product === 3 && data.si === 9) let sizes = utils.parseSizesInput(bidRequest.sizes) let [width, height] = sizes[0].split('x') + let metaData = { + advertiserDomains: advertiserDomains || [], + mediaType: type || mediaType + } // return 1x1 when breakout expected if ((product === 2 || product === 5) && includes(sizes, '1x1')) { @@ -435,7 +449,8 @@ function interpretResponse (serverResponse, bidRequest) { netRevenue: true, requestId: bidRequest.id, ttl: TIME_TO_LIVE, - width + width, + meta: metaData }) } return bidResponses diff --git a/modules/gumgumBidAdapter.md b/modules/gumgumBidAdapter.md index 7b4f0c98ea7..57d56235d1c 100644 --- a/modules/gumgumBidAdapter.md +++ b/modules/gumgumBidAdapter.md @@ -10,11 +10,76 @@ Maintainer: engineering@gumgum.com GumGum adapter for Prebid.js Please note that both video and in-video products require a mediaType of video. -All other products (in-screen, slot, native) should have a mediaType of banner. - +In-screen and slot products should have a mediaType of banner. # Test Parameters ``` +var adUnits = [ + { + code: 'slot-placement', + sizes: [[300, 250]], + mediaTypes: { + banner: { + sizes: [[300, 250]], + } + }, + bids: [ + { + bidder: 'gumgum', + params: { + zone: 'dc9d6be1', // GumGum Zone ID given to the client + slot: '15901', // GumGum Slot ID given to the client, + bidfloor: 0.03 // CPM bid floor + } + } + ] + },{ + code: 'inscreen-placement', + sizes: [[300, 50]], + mediaTypes: { + banner: { + sizes: [[1, 1]], + } + }, + bids: [ + { + bidder: 'gumgum', + params: { + zone: 'dc9d6be1', // GumGum Zone ID given to the client + bidfloor: 0.03 // CPM bid floor + } + } + ] + },{ + code: 'video-placement', + sizes: [[300, 50]], + mediaTypes: { + video: { + context: 'instream', + playerSize: [640, 480], + minduration: 1, + maxduration: 2, + linearity: 1, + startdelay: 1, + placement: 1, + protocols: [1, 2] + } + }, + bids: [ + { + bidder: 'gumgum', + params: { + zone: 'ggumtest', // GumGum Zone ID given to the client + bidfloor: 0.03 // CPM bid floor + } + } + ] + } +]; +``` + +# Legacy Test Parameters +``` var adUnits = [ { code: 'test-div', diff --git a/modules/h12mediaBidAdapter.js b/modules/h12mediaBidAdapter.js index 0d2c22a3f68..7b736780226 100644 --- a/modules/h12mediaBidAdapter.js +++ b/modules/h12mediaBidAdapter.js @@ -1,6 +1,5 @@ import * as utils from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; -import find from 'core-js-pure/features/array/find.js'; const BIDDER_CODE = 'h12media'; const DEFAULT_URL = 'https://bidder.h12-media.com/prebid/'; const DEFAULT_CURRENCY = 'USD'; @@ -16,21 +15,31 @@ export const spec = { }, buildRequests: function(validBidRequests, bidderRequest) { - const requestUrl = validBidRequests[0].params.endpointdom || DEFAULT_URL; - const isiframe = !((window.self === window.top) || window.frameElement); + const isiframe = utils.inIframe(); const screenSize = getClientDimensions(); const docSize = getDocumentDimensions(); - const bidrequests = validBidRequests.map((bidRequest) => { + return validBidRequests.map((bidRequest) => { const bidderParams = bidRequest.params; - const adUnitElement = document.getElementById(bidRequest.adUnitCode); + const requestUrl = bidderParams.endpointdom || DEFAULT_URL; + let pubsubid = bidderParams.pubsubid || ''; + if (pubsubid && pubsubid.length > 32) { + utils.logError('Bidder param \'pubsubid\' should be not more than 32 chars.'); + pubsubid = ''; + } + const pubcontainerid = bidderParams.pubcontainerid; + const adUnitElement = document.getElementById(pubcontainerid || bidRequest.adUnitCode); const ishidden = !isVisible(adUnitElement); - const coords = { + const framePos = getFramePos(); + const coords = isiframe ? { + x: framePos[0], + y: framePos[1], + } : { x: adUnitElement && adUnitElement.getBoundingClientRect().x, y: adUnitElement && adUnitElement.getBoundingClientRect().y, }; - return { + const bidrequest = { bidId: bidRequest.bidId, transactionId: bidRequest.transactionId, adunitId: bidRequest.adUnitCode, @@ -40,33 +49,46 @@ export const spec = { adunitSize: bidRequest.mediaTypes.banner.sizes || [], coords, ishidden, + pubsubid, + pubcontainerid, }; - }); - return { - method: 'POST', - url: requestUrl, - options: {withCredentials: false}, - data: { - gdpr: utils.deepAccess(bidderRequest, 'gdprConsent.gdprApplies') ? Boolean(bidderRequest.gdprConsent.gdprApplies & 1) : false, - gdpr_cs: utils.deepAccess(bidderRequest, 'gdprConsent.gdprApplies') ? bidderRequest.gdprConsent.consentString : '', - topLevelUrl: window.top.location.href, - refererUrl: bidderRequest.refererInfo ? bidderRequest.refererInfo.referer : '', - isiframe, - version: '$prebid.version$', - visitorInfo: { - localTime: getLocalDateFormatted(), - dayOfWeek: new Date().getDay(), - screenWidth: screenSize[0], - screenHeight: screenSize[1], - docWidth: docSize[0], - docHeight: docSize[1], - scrollbarx: window.scrollX, - scrollbary: window.scrollY, + let windowTop; + try { + windowTop = window.top; + } catch (e) { + utils.logMessage(e); + windowTop = window; + } + + return { + method: 'POST', + url: requestUrl, + options: {withCredentials: true}, + data: { + gdpr: !!utils.deepAccess(bidderRequest, 'gdprConsent.gdprApplies', false), + gdpr_cs: utils.deepAccess(bidderRequest, 'gdprConsent.consentString', ''), + usp: !!utils.deepAccess(bidderRequest, 'uspConsent', false), + usp_cs: utils.deepAccess(bidderRequest, 'uspConsent', ''), + topLevelUrl: utils.deepAccess(bidderRequest, 'refererInfo.referer', ''), + refererUrl: windowTop.document.referrer, + isiframe, + version: '$prebid.version$', + ExtUserIDs: bidRequest.userId, + visitorInfo: { + localTime: getLocalDateFormatted(), + dayOfWeek: new Date().getDay(), + screenWidth: screenSize[0], + screenHeight: screenSize[1], + docWidth: docSize[0], + docHeight: docSize[1], + scrollbarx: windowTop.scrollX, + scrollbary: windowTop.scrollY, + }, + bidrequest, }, - bidrequests, - }, - }; + }; + }); }, interpretResponse: function(serverResponse, bidRequests) { @@ -74,29 +96,28 @@ export const spec = { try { const serverBody = serverResponse.body; if (serverBody) { - if (serverBody.bids) { - serverBody.bids.forEach(bidBody => { - const bidRequest = find(bidRequests.data.bidrequests, bid => bid.bidId === bidBody.bidId); - const bidResponse = { - currency: serverBody.currency || DEFAULT_CURRENCY, - netRevenue: serverBody.netRevenue || DEFAULT_NET_REVENUE, - ttl: serverBody.ttl || DEFAULT_TTL, - requestId: bidBody.bidId, - cpm: bidBody.cpm, - width: bidBody.width, - height: bidBody.height, - creativeId: bidBody.creativeId, - ad: bidBody.ad, - meta: bidBody.meta, - mediaType: 'banner', - }; - if (bidRequest) { - bidResponse.pubid = bidRequest.pubid; - bidResponse.placementid = bidRequest.placementid; - bidResponse.size = bidRequest.size; - } - bidResponses.push(bidResponse); - }); + if (serverBody.bid) { + const bidBody = serverBody.bid; + const bidRequest = bidRequests.data.bidrequest; + const bidResponse = { + currency: serverBody.currency || DEFAULT_CURRENCY, + netRevenue: serverBody.netRevenue || DEFAULT_NET_REVENUE, + ttl: serverBody.ttl || DEFAULT_TTL, + requestId: bidBody.bidId, + cpm: bidBody.cpm, + width: bidBody.width, + height: bidBody.height, + creativeId: bidBody.creativeId, + ad: bidBody.ad, + meta: bidBody.meta, + mediaType: 'banner', + }; + if (bidRequest) { + bidResponse.pubid = bidRequest.pubid; + bidResponse.placementid = bidRequest.placementid; + bidResponse.size = bidRequest.size; + } + bidResponses.push(bidResponse); } } return bidResponses; @@ -105,47 +126,50 @@ export const spec = { } }, - getUserSyncs: function(syncOptions, serverResponses, gdprConsent) { - const serverBody = serverResponses[0].body; + getUserSyncs: function(syncOptions, serverResponses, gdprConsent, usPrivacy) { const syncs = []; + const uspApplies = !!utils.deepAccess(usPrivacy, 'uspConsent', false); + const uspString = utils.deepAccess(usPrivacy, 'uspConsent', ''); gdprConsent = gdprConsent || { gdprApplies: false, consentString: '', }; - if (serverBody) { - if (serverBody.bids) { - serverBody.bids.forEach(bidBody => { - const userSyncUrls = bidBody.usersync || []; - const userSyncUrlProcess = url => { - return url - .replace('{gdpr}', gdprConsent.gdprApplies) - .replace('{gdpr_cs}', gdprConsent.consentString); - } + const userSyncUrlProcess = url => { + return url + .replace('{gdpr}', gdprConsent.gdprApplies) + .replace('{gdpr_cs}', gdprConsent.consentString) + .replace('{usp}', uspApplies) + .replace('{usp_cs}', uspString); + } - userSyncUrls.forEach(sync => { - if (syncOptions.iframeEnabled && sync.type === 'iframe' && sync.url) { - syncs.push({ - type: 'iframe', - url: userSyncUrlProcess(sync.url), - }); - } - if (syncOptions.pixelEnabled && sync.type === 'image' && sync.url) { - syncs.push({ - type: 'image', - url: userSyncUrlProcess(sync.url), - }); - } + serverResponses.forEach(serverResponse => { + const userSyncUrls = serverResponse.body.usersync || []; + userSyncUrls.forEach(sync => { + if (syncOptions.iframeEnabled && sync.type === 'iframe' && sync.url) { + syncs.push({ + type: 'iframe', + url: userSyncUrlProcess(sync.url), }); - }); - } - } + } + if (syncOptions.pixelEnabled && sync.type === 'image' && sync.url) { + syncs.push({ + type: 'image', + url: userSyncUrlProcess(sync.url), + }); + } + }) + }); return syncs; }, } function getContext(elem) { - return elem && window.document.body.contains(elem) ? window : (window.top.document.body.contains(elem) ? top : undefined); + try { + return elem && window.document.body.contains(elem) ? window : (window.top.document.body.contains(elem) ? top : undefined); + } catch (e) { + return undefined; + } } function isDefined(val) { @@ -206,4 +230,24 @@ function getLocalDateFormatted() { return `${d.getFullYear()}-${two(d.getMonth() + 1)}-${two(d.getDate())} ${two(d.getHours())}:${two(d.getMinutes())}:${two(d.getSeconds())}`; } +function getFramePos() { + let t = window; + let m = 0; + let frmLeft = 0; + let frmTop = 0; + do { + m = m + 1; + try { + if (m > 1) { + t = t.parent + } + frmLeft = frmLeft + t.frameElement.getBoundingClientRect().left; + frmTop = frmTop + t.frameElement.getBoundingClientRect().top; + } catch (o) { /* keep looping */ + } + } while ((m < 100) && (t.parent !== t.self)) + + return [frmLeft, frmTop]; +} + registerBidder(spec); diff --git a/modules/hybridBidAdapter.js b/modules/hybridBidAdapter.js index dd55483ef33..e7281086a92 100644 --- a/modules/hybridBidAdapter.js +++ b/modules/hybridBidAdapter.js @@ -164,7 +164,7 @@ function wrapAd(bid, bidData) { parentDocument.style.width = "100%"; } var _content = "${encodeURIComponent(JSON.stringify(bid.inImageContent))}"; - window._ao_ssp.registerInImage(JSON.parse(decodeURIComponent(_content))); + window._hyb_prebid_ssp.registerInImage(JSON.parse(decodeURIComponent(_content))); `; diff --git a/modules/id5IdSystem.js b/modules/id5IdSystem.js index 7033a71d015..201fa6ce812 100644 --- a/modules/id5IdSystem.js +++ b/modules/id5IdSystem.js @@ -16,7 +16,9 @@ const MODULE_NAME = 'id5Id'; const GVLID = 131; const NB_EXP_DAYS = 30; export const ID5_STORAGE_NAME = 'id5id'; +export const ID5_PRIVACY_STORAGE_NAME = `${ID5_STORAGE_NAME}_privacy`; const LOCAL_STORAGE = 'html5'; +const ABTEST_RESOLUTION = 10000; // order the legacy cookie names in reverse priority order so the last // cookie in the array is the most preferred to use @@ -42,27 +44,50 @@ export const id5IdSubmodule = { * decode the stored id value for passing to bid requests * @function decode * @param {(Object|string)} value + * @param {SubmoduleConfig|undefined} config * @returns {(Object|undefined)} */ - decode(value) { - let uid; + decode(value, config) { + let universalUid; let linkType = 0; if (value && typeof value.universal_uid === 'string') { - uid = value.universal_uid; + universalUid = value.universal_uid; linkType = value.link_type || linkType; } else { return undefined; } - return { - 'id5id': { - 'uid': uid, - 'ext': { - 'linkType': linkType + // check for A/B testing configuration and hide ID if in Control Group + const abConfig = getAbTestingConfig(config); + const controlGroup = isInControlGroup(universalUid, abConfig.controlGroupPct); + if (abConfig.enabled === true && typeof controlGroup === 'undefined') { + // A/B Testing is enabled, but configured improperly, so skip A/B testing + utils.logError('User ID - ID5 submodule: A/B Testing controlGroupPct must be a number >= 0 and <= 1! Skipping A/B Testing'); + } else if (abConfig.enabled === true && controlGroup === true) { + // A/B Testing is enabled and user is in the Control Group, so do not share the ID5 ID + utils.logInfo('User ID - ID5 submodule: A/B Testing Enabled - user is in the Control Group, so the ID5 ID is NOT exposed'); + universalUid = ''; + linkType = 0; + } else if (abConfig.enabled === true) { + // A/B Testing is enabled but user is not in the Control Group, so ID5 ID is shared + utils.logInfo('User ID - ID5 submodule: A/B Testing Enabled - user is NOT in the Control Group, so the ID5 ID is exposed'); + } + + let responseObj = { + id5id: { + uid: universalUid, + ext: { + linkType: linkType } } }; + + if (abConfig.enabled === true) { + utils.deepSetValue(responseObj, 'id5id.ext.abTestingControlGroup', (typeof controlGroup === 'undefined' ? false : controlGroup)); + } + + return responseObj; }, /** @@ -98,6 +123,10 @@ export const id5IdSubmodule = { 'v': '$prebid.version$' }; + if (getAbTestingConfig(config).enabled === true) { + utils.deepSetValue(data, 'features.ab', 1); + } + const resp = function (callback) { const callbacks = { success: response => { @@ -106,6 +135,9 @@ export const id5IdSubmodule = { try { responseObj = JSON.parse(response); resetNb(config.params.partner); + if (responseObj.privacy) { + storeInLocalStorage(ID5_PRIVACY_STORAGE_NAME, JSON.stringify(responseObj.privacy), NB_EXP_DAYS); + } // TODO: remove after requiring publishers to use localstorage and // all publishers have upgraded @@ -135,10 +167,11 @@ export const id5IdSubmodule = { * It's permissible to return neither, one, or both fields. * @function extendId * @param {SubmoduleConfig} config + * @param {ConsentData|undefined} consentData * @param {Object} cacheIdObj - existing id, if any * @return {(IdResponse|function(callback:function))} A response object that contains id and/or callback. */ - extendId(config, cacheIdObj) { + extendId(config, consentData, cacheIdObj) { const partnerId = (config && config.params && config.params.partner) || 0; incrementNb(partnerId); return cacheIdObj; @@ -247,4 +280,41 @@ export function storeInLocalStorage(key, value, expDays) { storage.setDataInLocalStorage(`${key}`, value); } +/** + * gets the existing abTesting config or generates a default config with abTesting off + * + * @param {SubmoduleConfig|undefined} config + * @returns {(Object|undefined)} + */ +function getAbTestingConfig(config) { + return (config && config.params && config.params.abTesting) || { enabled: false }; +} + +/** + * Return a consistant random number between 0 and ABTEST_RESOLUTION-1 for this user + * Falls back to plain random if no user provided + * @param {string} userId + * @returns {number} + */ +function abTestBucket(userId) { + if (userId) { + return ((utils.cyrb53Hash(userId) % ABTEST_RESOLUTION) + ABTEST_RESOLUTION) % ABTEST_RESOLUTION; + } else { + return Math.floor(Math.random() * ABTEST_RESOLUTION); + } +} + +/** + * Return a consistant boolean if this user is within the control group ratio provided + * @param {string} userId + * @param {number} controlGroupPct - Ratio [0,1] of users expected to be in the control group + * @returns {boolean} + */ +export function isInControlGroup(userId, controlGroupPct) { + if (!utils.isNumber(controlGroupPct) || controlGroupPct < 0 || controlGroupPct > 1) { + return undefined; + } + return abTestBucket(userId) < controlGroupPct * ABTEST_RESOLUTION; +} + submodule('userId', id5IdSubmodule); diff --git a/modules/id5IdSystem.md b/modules/id5IdSystem.md index e5e3969c19c..6a662361492 100644 --- a/modules/id5IdSystem.md +++ b/modules/id5IdSystem.md @@ -1,6 +1,6 @@ # ID5 Universal ID -The ID5 Universal ID is a shared, neutral identifier that publishers and ad tech platforms can use to recognise users even in environments where 3rd party cookies are not available. The ID5 Universal ID is designed to respect users' privacy choices and publishers’ preferences throughout the advertising value chain. For more information about the ID5 Universal ID and detailed integration docs, please visit [our documentation](https://console.id5.io/docs/public/prebid). We also recommend that you sign up for our [release notes](https://id5.io/universal-id/release-notes) to stay up-to-date with any changes to the implementation of the ID5 Universal ID in Prebid. +The ID5 Universal ID is a shared, neutral identifier that publishers and ad tech platforms can use to recognise users even in environments where 3rd party cookies are not available. The ID5 Universal ID is designed to respect users' privacy choices and publishers’ preferences throughout the advertising value chain. For more information about the ID5 Universal ID and detailed integration docs, please visit [our documentation](https://wiki.id5.io/x/BIAZ). We also recommend that you sign up for our [release notes](https://id5.io/universal-id/release-notes) to stay up-to-date with any changes to the implementation of the ID5 Universal ID in Prebid. ## ID5 Universal ID Registration @@ -22,14 +22,18 @@ The following configuration parameters are available: pbjs.setConfig({ userSync: { userIds: [{ - name: "id5Id", + name: 'id5Id', params: { partner: 173, // change to the Partner Number you received from ID5 - pd: "MT1iNTBjY..." // optional, see table below for a link to how to generate this + pd: 'MT1iNTBjY...', // optional, see table below for a link to how to generate this + abTesting: { // optional + enabled: true, // false by default + controlGroupPct: 0.1 // valid values are 0.0 - 1.0 (inclusive) + } }, 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 } @@ -46,6 +50,9 @@ pbjs.setConfig({ | params.partner | Required | Number | This is the ID5 Partner Number obtained from registering with ID5. | `173` | | params.pd | Optional | String | Publisher-supplied data used for linking ID5 IDs across domains. See [our documentation](https://wiki.id5.io/x/BIAZ) for details on generating the string. Omit the parameter or leave as an empty string if no data to supply | `"MT1iNTBjY..."` | | params.provider | Optional | String | An identifier provided by ID5 to technology partners who manage Prebid setups on behalf of publishers. Reach out to [ID5](mailto:prebid@id5.io) if you have questions about this parameter | `pubmatic-identity-hub` | +| params.abTesting | Optional | Object | Allows publishers to easily run an A/B Test. If enabled and the user is in the Control Group, the ID5 ID will NOT be exposed to bid adapters for that request | Disabled by default | +| params.abTesting.enabled | Optional | Boolean | Set this to `true` to turn on this feature | `true` or `false` | +| params.abTesting.controlGroupPct | Optional | Number | Must be a number between `0.0` and `1.0` (inclusive) and is used to determine the percentage of requests that fall into the control group (and thus not exposing the ID5 ID). For example, a value of `0.20` will result in 20% of requests without an ID5 ID and 80% with an ID. | `0.1` | | storage | Required | Object | Storage settings for how the User ID module will cache the ID5 ID locally | | | storage.type | Required | String | This is where the results of the user ID will be stored. ID5 **requires** `"html5"`. | `"html5"` | | storage.name | Required | String | The name of the local storage where the user ID will be stored. ID5 **requires** `"id5id"`. | `"id5id"` | @@ -53,3 +60,9 @@ pbjs.setConfig({ | storage.refreshInSeconds | Optional | Integer | How many seconds until the ID5 ID will be refreshed. ID5 strongly recommends 8 hours between refreshes | `8*3600` | **ATTENTION:** As of Prebid.js v4.14.0, ID5 requires `storage.type` to be `"html5"` and `storage.name` to be `"id5id"`. Using other values will display a warning today, but in an upcoming release, it will prevent the ID5 module from loading. This change is to ensure the ID5 module in Prebid.js interoperates properly with the [ID5 API](https://github.com/id5io/id5-api.js) and to reduce the size of publishers' first-party cookies that are sent to their web servers. If you have any questions, please reach out to us at [prebid@id5.io](mailto:prebid@id5.io). + +### A Note on A/B Testing + +Publishers may want to test the value of the ID5 ID with their downstream partners. While there are various ways to do this, A/B testing is a standard approach. Instead of publishers manually enabling or disabling the ID5 User ID Module based on their control group settings (which leads to fewer calls to ID5, reducing our ability to recognize the user), we have baked this in to our module directly. + +To turn on A/B Testing, simply edit the configuration (see above table) to enable it and set what percentage of users you would like to set for the control group. The control group is the set of user where an ID5 ID will not be exposed in to bid adapters or in the various user id functions available on the `pbjs` global. An additional value of `ext.abTestingControlGroup` will be set to `true` or `false` that can be used to inform reporting systems that the user was in the control group or not. It's important to note that the control group is user based, and not request based. In other words, from one page view to another, a user will always be in or out of the control group. diff --git a/modules/idLibrary.js b/modules/idImportLibrary.js similarity index 96% rename from modules/idLibrary.js rename to modules/idImportLibrary.js index ba3cc0b5efb..2a3a86cf270 100644 --- a/modules/idLibrary.js +++ b/modules/idImportLibrary.js @@ -8,7 +8,7 @@ let email; let conf; const LOG_PRE_FIX = 'ID-Library: '; const CONF_DEFAULT_OBSERVER_DEBOUNCE_MS = 250; -const CONF_DEFAULT_FULL_BODY_SCAN = true; +const CONF_DEFAULT_FULL_BODY_SCAN = false; const OBSERVER_CONFIG = { subtree: true, attributes: true, @@ -38,7 +38,7 @@ function getEmail(value) { if (!matched) { return null; } - logInfo('Email found' + matched[0]); + logInfo('Email found: ' + matched[0]); return matched[0]; } @@ -200,10 +200,10 @@ function postData() { logInfo('No user ids'); return; } - logInfo('Users' + JSON.stringify(userIds)); + logInfo('Users' + userIds); const syncPayload = {}; syncPayload.hid = MD5(email).toString(); - syncPayload.uids = JSON.stringify(userIds); + syncPayload.uids = userIds; const payloadString = JSON.stringify(syncPayload); logInfo(payloadString); ajax(conf.url, syncCallback(), payloadString, {method: 'POST', withCredentials: true}); @@ -240,4 +240,4 @@ export function setConfig(config) { associateIds(); } -config.getConfig('idLibrary', config => setConfig(config.idLibrary)); +config.getConfig('idImportLibrary', config => setConfig(config.idImportLibrary)); diff --git a/modules/idImportLibrary.md b/modules/idImportLibrary.md new file mode 100644 index 00000000000..3dd78ee25d8 --- /dev/null +++ b/modules/idImportLibrary.md @@ -0,0 +1,22 @@ +# ID Import Library + +## Configuration Options + +| Parameter | Required | Type | Default | Description | +| :--------- | :------- | :------ | :------ | :---------- | +| `target` | Yes | String | N/A | ID attribute of the element from which the email can be read. | +| `url` | Yes | String | N/A | URL endpoint used to post the hashed email and user IDs. | +| `debounce` | No | Number | 250 | Time in milliseconds before the email and IDs are fetched. | +| `fullscan` | No | Boolean | false | Enable/disable a full page body scan to get email. | + +## Example + +```javascript +pbjs.setConfig({ + idImportLibrary: { + target: 'username', + url: 'https://example.com', + debounce: 250, + fullscan: false, + }, +}); diff --git a/modules/idLibrary.md b/modules/idLibrary.md deleted file mode 100644 index 69b63dc466b..00000000000 --- a/modules/idLibrary.md +++ /dev/null @@ -1,24 +0,0 @@ -## ID Library Configuration Example - - -|Param |Required |Description | -|----------------|-------------------------------|-----------------------------| -|url |Yes | The url endpoint is used to post the hashed email and user ids. | -|target |No |It should contain the element id from which the email can be read. | -|debounce |No | Time in milliseconds before the email and ids are fetched | -|fullscan |No | Option to enable/disable full body scan to get email. By default the full body scan is enabled. | - -### Example -``` - pbjs.setConfig({ - idLibrary:{ - url: , - debounce: 250, - target: 'username', - fullscan: false - }, - }); -``` - - -``` diff --git a/modules/identityLinkIdSystem.js b/modules/identityLinkIdSystem.js index 33dd74380ac..716929db70a 100644 --- a/modules/identityLinkIdSystem.js +++ b/modules/identityLinkIdSystem.js @@ -43,7 +43,7 @@ export const identityLinkSubmodule = { getId(config, consentData) { const configParams = (config && config.params) || {}; if (!configParams || typeof configParams.pid !== 'string') { - utils.logError('identityLink submodule requires partner id to be defined'); + utils.logError('identityLink: requires partner id to be defined'); return; } const hasGdpr = (consentData && typeof consentData.gdprApplies === 'boolean' && consentData.gdprApplies) ? 1 : 0; @@ -51,7 +51,7 @@ export const identityLinkSubmodule = { const tcfPolicyV2 = utils.deepAccess(consentData, 'vendorData.tcfPolicyVersion') === 2; // use protocol relative urls for http or https if (hasGdpr && (!gdprConsentString || gdprConsentString === '')) { - utils.logInfo('Consent string is required to call envelope API.'); + utils.logInfo('identityLink: Consent string is required to call envelope API.'); return; } const url = `https://api.rlcdn.com/api/identity/envelope?pid=${configParams.pid}${hasGdpr ? (tcfPolicyV2 ? '&ct=4&cv=' : '&ct=1&cv=') + gdprConsentString : ''}`; @@ -60,10 +60,11 @@ export const identityLinkSubmodule = { // Check ats during callback so it has a chance to initialise. // If ats library is available, use it to retrieve envelope. If not use standard third party endpoint if (window.ats) { - utils.logInfo('ATS exists!'); + utils.logInfo('identityLink: ATS exists!'); window.ats.retrieveEnvelope(function (envelope) { if (envelope) { - utils.logInfo('An envelope can be retrieved from ATS!'); + utils.logInfo('identityLink: An envelope can be retrieved from ATS!'); + setEnvelopeSource(true); callback(JSON.parse(envelope).envelope); } else { getEnvelope(url, callback); @@ -92,14 +93,15 @@ function getEnvelope(url, callback) { callback((responseObj && responseObj.envelope) ? responseObj.envelope : ''); }, error: error => { - utils.logInfo(`identityLink: ID fetch encountered an error`, error); + utils.logInfo(`identityLink: identityLink: ID fetch encountered an error`, error); callback(); } }; if (!storage.getCookie('_lr_retry_request')) { setRetryCookie(); - utils.logInfo('A 3P retrieval is attempted!'); + utils.logInfo('identityLink: A 3P retrieval is attempted!'); + setEnvelopeSource(false); ajax(url, callbacks, undefined, { method: 'GET', withCredentials: true }); } } @@ -110,4 +112,10 @@ function setRetryCookie() { storage.setCookie('_lr_retry_request', 'true', now.toUTCString()); } +function setEnvelopeSource(src) { + let now = new Date(); + now.setTime(now.getTime() + 2592000000); + storage.setCookie('_lr_env_src_ats', src, now.toUTCString()); +} + submodule('userId', identityLinkSubmodule); diff --git a/modules/impactifyBidAdapter.js b/modules/impactifyBidAdapter.js new file mode 100644 index 00000000000..b649b5a8a73 --- /dev/null +++ b/modules/impactifyBidAdapter.js @@ -0,0 +1,260 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import * as utils from '../src/utils.js'; +import { config } from '../src/config.js'; +import {ajax} from '../src/ajax.js'; + +const BIDDER_CODE = 'impactify'; +const BIDDER_ALIAS = ['imp']; +const DEFAULT_CURRENCY = 'USD'; +const DEFAULT_VIDEO_WIDTH = 640; +const DEFAULT_VIDEO_HEIGHT = 480; +const ORIGIN = 'https://sonic.impactify.media'; +const LOGGER_URI = 'https://logger.impactify.media'; +const AUCTIONURI = '/bidder'; +const COOKIESYNCURI = '/static/cookie_sync.html'; +const GVLID = 606; +const GETCONFIG = config.getConfig; + +const getDeviceType = () => { + // OpenRTB Device type + if ((/ipad|android 3.0|xoom|sch-i800|playbook|tablet|kindle/i.test(navigator.userAgent.toLowerCase()))) { + return 5; + } + if ((/iphone|ipod|android|blackberry|opera|mini|windows\sce|palm|smartphone|iemobile/i.test(navigator.userAgent.toLowerCase()))) { + return 4; + } + return 2; +} + +const createOpenRtbRequest = (validBidRequests, bidderRequest) => { + // Create request and set imp bids inside + let request = { + id: bidderRequest.auctionId, + validBidRequests, + cur: [DEFAULT_CURRENCY], + imp: [] + }; + + // Force impactify debugging parameter + if (window.localStorage.getItem('_im_db_bidder') == 3) { + request.test = 3; + } + + // Set device/user/site + if (!request.device) request.device = {}; + if (!request.site) request.site = {}; + request.device = { + w: window.innerWidth, + h: window.innerHeight, + devicetype: getDeviceType(), + ua: navigator.userAgent, + js: 1, + dnt: (navigator.doNotTrack == 'yes' || navigator.doNotTrack == '1' || navigator.msDoNotTrack == '1') ? 1 : 0, + language: ((navigator.language || navigator.userLanguage || '').split('-'))[0] || 'en', + }; + request.site = {page: bidderRequest.refererInfo.referer}; + + // Handle privacy settings for GDPR/CCPA/COPPA + if (bidderRequest.gdprConsent) { + let gdprApplies = 0; + if (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') gdprApplies = bidderRequest.gdprConsent.gdprApplies ? 1 : 0; + utils.deepSetValue(request, 'regs.ext.gdpr', gdprApplies); + utils.deepSetValue(request, 'user.ext.consent', bidderRequest.gdprConsent.consentString); + } + + if (bidderRequest.uspConsent) { + utils.deepSetValue(request, 'regs.ext.us_privacy', bidderRequest.uspConsent); + this.syncStore.uspConsent = bidderRequest.uspConsent; + } + + if (GETCONFIG('coppa') == true) utils.deepSetValue(request, 'regs.coppa', 1); + + if (bidderRequest.uspConsent) { + utils.deepSetValue(request, 'regs.ext.us_privacy', bidderRequest.uspConsent); + } + + // Set buyer uid + utils.deepSetValue(request, 'user.buyeruid', utils.generateUUID()); + + // Create imps with bids + validBidRequests.forEach((bid) => { + let imp = { + id: bid.bidId, + bidfloor: bid.params.bidfloor ? bid.params.bidfloor : 0, + ext: { + impactify: { + appId: bid.params.appId, + format: bid.params.format, + style: bid.params.style + }, + }, + video: { + playerSize: [DEFAULT_VIDEO_WIDTH, DEFAULT_VIDEO_HEIGHT], + context: 'outstream', + mimes: ['video/mp4'], + }, + }; + if (bid.params.container) { + imp.ext.impactify.container = bid.params.container; + } + request.imp.push(imp); + }); + + return request; +}; + +export const spec = { + code: BIDDER_CODE, + gvlid: GVLID, + supportedMediaTypes: ['video'], + aliases: BIDDER_ALIAS, + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function (bid) { + if (!bid.params.appId || typeof bid.params.appId != 'string' || !bid.params.format || typeof bid.params.format != 'string' || !bid.params.style || typeof bid.params.style != 'string') { + return false; + } + if (bid.params.format != 'screen' && bid.params.format != 'display') { + return false; + } + if (bid.params.style != 'inline' && bid.params.style != 'impact' && bid.params.style != 'static') { + return false; + } + + return true; + }, + /** + * Make a server request from the list of BidRequests. + * + * @param {validBidRequests[]} - an array of bids + * @param {bidderRequest} - the bidding request + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function (validBidRequests, bidderRequest) { + // Create a clean openRTB request + let request = createOpenRtbRequest(validBidRequests, bidderRequest); + + return { + method: 'POST', + url: ORIGIN + AUCTIONURI, + data: JSON.stringify(request), + }; + }, + /** + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function (serverResponse, bidRequest) { + const serverBody = serverResponse.body; + let bidResponses = []; + + if (!serverBody) { + return bidResponses; + } + + if (!serverBody.seatbid || !serverBody.seatbid.length) { + return []; + } + + serverBody.seatbid.forEach((seatbid) => { + if (seatbid.bid.length) { + bidResponses = [ + ...bidResponses, + ...seatbid.bid + .filter((bid) => bid.price > 0) + .map((bid) => ({ + id: bid.id, + requestId: bid.impid, + cpm: bid.price, + currency: serverBody.cur, + netRevenue: true, + ad: bid.adm, + width: bid.w || 0, + height: bid.h || 0, + ttl: 300, + creativeId: bid.crid || 0, + hash: bid.hash, + expiry: bid.expiry + })), + ]; + } + }); + + return bidResponses; + }, + + /** + * Register the user sync pixels which should be dropped after the auction. + * + * @param {SyncOptions} syncOptions Which user syncs are allowed? + * @param {ServerResponse[]} serverResponses List of server's responses. + * @return {UserSync[]} The user syncs which should be dropped. + */ + getUserSyncs: function ( + syncOptions, + serverResponses, + gdprConsent, + uspConsent + ) { + if (!serverResponses || serverResponses.length === 0) { + return []; + } + + if (!syncOptions.iframeEnabled) { + return []; + } + + let params = ''; + if (gdprConsent && typeof gdprConsent.consentString === 'string') { + if (typeof gdprConsent.gdprApplies === 'boolean') { + params += `?gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; + } else { + params += `?gdpr_consent=${gdprConsent.consentString}`; + } + } + + if (uspConsent) { + params += `${params ? '&' : '?'}us_privacy=${encodeURIComponent(uspConsent)}`; + } + + if (document.location.search.match(/pbs_debug=true/)) params += `&pbs_debug=true`; + + return [{ + type: 'iframe', + url: ORIGIN + COOKIESYNCURI + params + }]; + }, + + /** + * Register bidder specific code, which will execute if a bid from this bidder won the auction + * @param {Bid} The bid that won the auction + */ + onBidWon: function(bid) { + ajax(`${LOGGER_URI}/log/bidder/won`, null, JSON.stringify(bid), { + method: 'POST', + contentType: 'application/json' + }); + + return true; + }, + + /** + * Register bidder specific code, which will execute if bidder timed out after an auction + * @param {data} Containing timeout specific data + */ + onTimeout: function(data) { + ajax(`${LOGGER_URI}/log/bidder/timeout`, null, JSON.stringify(data[0]), { + method: 'POST', + contentType: 'application/json' + }); + + return true; + } +}; +registerBidder(spec); diff --git a/modules/impactifyBidAdapter.md b/modules/impactifyBidAdapter.md new file mode 100644 index 00000000000..3de9a8cfb84 --- /dev/null +++ b/modules/impactifyBidAdapter.md @@ -0,0 +1,35 @@ +# Overview + +``` +Module Name: Impactify Bidder Adapter +Module Type: Bidder Adapter +Maintainer: thomas.destefano@impactify.io +``` + +# Description + +Module that connects to the Impactify solution. +The impactify bidder need 3 parameters: + - appId : This is your unique publisher identifier + - format : This is the ad format needed, can be : screen or display + - style : This is the ad style needed, can be : inline, impact or static + +# Test Parameters +``` + var adUnits = [{ + code: 'your-slot-div-id', // This is your slot div id + mediaTypes: { + video: { + context: 'outstream' + } + }, + bids: [{ + bidder: 'impactify', + params: { + appId: 'example.com', + format: 'screen', + style: 'inline' + } + }] + }]; +``` diff --git a/modules/improvedigitalBidAdapter.js b/modules/improvedigitalBidAdapter.js index 0e2d8f6f7dd..dc0911ff5da 100644 --- a/modules/improvedigitalBidAdapter.js +++ b/modules/improvedigitalBidAdapter.js @@ -4,12 +4,14 @@ import { config } from '../src/config.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import {Renderer} from '../src/Renderer.js'; import { createEidsArray } from './userId/eids.js'; +import includes from 'core-js-pure/features/array/includes.js'; const BIDDER_CODE = 'improvedigital'; const RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; +const VIDEO_TARGETING = ['skip', 'skipmin', 'skipafter']; export const spec = { - version: '7.1.0', + version: '7.3.0', code: BIDDER_CODE, gvlid: 253, aliases: ['id'], @@ -124,7 +126,6 @@ export const spec = { } // Common properties - bid.adId = bidObject.id; bid.cpm = parseFloat(bidObject.price); bid.creativeId = bidObject.crid; bid.currency = bidObject.currency ? bidObject.currency.toUpperCase() : 'USD'; @@ -202,6 +203,21 @@ function isOutstreamVideo(bid) { return videoMediaType && context === 'outstream'; } +function getVideoTargetingParams(bid) { + const result = {}; + Object.keys(Object(bid.mediaTypes.video)) + .filter(key => includes(VIDEO_TARGETING, key)) + .forEach(key => { + result[ key ] = bid.mediaTypes.video[ key ]; + }); + Object.keys(Object(bid.params.video)) + .filter(key => includes(VIDEO_TARGETING, key)) + .forEach(key => { + result[ key ] = bid.params.video[ key ]; + }); + return result; +} + function outstreamRender(bid) { bid.renderer.push(() => { window.ANOutstreamVideo.renderAd({ @@ -255,6 +271,9 @@ function getNormalizedBidRequest(bid) { if (isInstreamVideo(bid)) { normalizedBidRequest.adTypes = [ VIDEO ]; } + if (isInstreamVideo(bid) || isOutstreamVideo(bid)) { + normalizedBidRequest.video = getVideoTargetingParams(bid); + } if (placementId) { normalizedBidRequest.placementId = placementId; } else { @@ -400,7 +419,7 @@ export function ImproveDigitalAdServerJSClient(endPoint) { AD_SERVER_BASE_URL: 'ice.360yield.com', END_POINT: endPoint || 'hb', AD_SERVER_URL_PARAM: 'jsonp=', - CLIENT_VERSION: 'JS-6.3.0', + CLIENT_VERSION: 'JS-6.4.0', MAX_URL_LENGTH: 2083, ERROR_CODES: { MISSING_PLACEMENT_PARAMS: 2, @@ -609,6 +628,21 @@ export function ImproveDigitalAdServerJSClient(endPoint) { if (placementObject.transactionId) { impressionObject.tid = placementObject.transactionId; } + if (!utils.isEmpty(placementObject.video)) { + const video = Object.assign({}, placementObject.video); + // skip must be 0 or 1 + if (video.skip !== 1) { + delete video.skipmin; + delete video.skipafter; + if (video.skip !== 0) { + utils.logWarn(`video.skip: invalid value '${video.skip}'. Expected 0 or 1`); + delete video.skip; + } + } + if (!utils.isEmpty(video)) { + impressionObject.video = video; + } + } if (placementObject.keyValues) { for (let key in placementObject.keyValues) { for (let valueCounter = 0; valueCounter < placementObject.keyValues[key].length; valueCounter++) { diff --git a/modules/inmarBidAdapter.js b/modules/inmarBidAdapter.js index b5ab72266fc..e1edd935587 100755 --- a/modules/inmarBidAdapter.js +++ b/modules/inmarBidAdapter.js @@ -17,7 +17,7 @@ export const spec = { * @returns {boolean} True if this is a valid bid, and false otherwise */ isBidRequestValid: function(bid) { - return !!(bid.params && bid.params.partnerId && bid.params.adnetId); + return !!(bid.params && bid.params.partnerId); }, /** @@ -40,7 +40,7 @@ export const spec = { uspConsent: bidderRequest.uspConsent, currencyCode: config.getConfig('currency.adServerCurrency'), coppa: config.getConfig('coppa'), - firstPartyData: config.getConfig('fpd'), + firstPartyData: config.getLegacyFpd(config.getConfig('ortb2')), prebidVersion: '$prebid.version$' }; @@ -49,9 +49,6 @@ export const spec = { return { method: 'POST', url: 'https://prebid.owneriq.net:8443/bidder/pb/bid', - options: { - withCredentials: false - }, data: payloadString, }; }, diff --git a/modules/inmarBidAdapter.md b/modules/inmarBidAdapter.md index 1bacb30f2dd..8ed6b998602 100644 --- a/modules/inmarBidAdapter.md +++ b/modules/inmarBidAdapter.md @@ -25,7 +25,6 @@ Please reach out to your account manager for more information. bidder: 'inmar', params: { partnerId: 12345, - adnetId: 'ADb1f40rmi', position: 1 } }] @@ -37,7 +36,6 @@ Please reach out to your account manager for more information. bidder: 'inmar', params: { partnerId: 12345, - adnetId: 'ADb1f40rmo', position: 0 } }] diff --git a/modules/inskinBidAdapter.js b/modules/inskinBidAdapter.js index 2a55b5280db..29040205818 100644 --- a/modules/inskinBidAdapter.js +++ b/modules/inskinBidAdapter.js @@ -4,9 +4,7 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; const BIDDER_CODE = 'inskin'; const CONFIG = { - 'inskin': { - 'BASE_URI': 'https://mfad.inskinad.com/api/v2' - } + BASE_URI: 'https://mfad.inskinad.com/api/v2' }; export const spec = { @@ -97,8 +95,7 @@ export const spec = { } validBidRequests.map(bid => { - let config = CONFIG[bid.bidder]; - ENDPOINT_URL = config.BASE_URI; + ENDPOINT_URL = CONFIG.BASE_URI; const placement = Object.assign({ divName: bid.bidId, @@ -108,8 +105,12 @@ export const spec = { placement.adTypes.push(5, 9, 163, 2163, 3006); + placement.properties = placement.properties || {}; + + placement.properties.screenWidth = screen.width; + placement.properties.screenHeight = screen.height; + if (restrictions.length) { - placement.properties = placement.properties || {}; placement.properties.restrictions = restrictions; } @@ -188,7 +189,7 @@ export const spec = { const id = 'ism_tag_' + Math.floor((Math.random() * 10e16)); window[id] = { - plr_AdSlot: e.source.frameElement, + plr_AdSlot: e.source && e.source.frameElement, bidId: e.data.bidId, bidPrice: bidsMap[e.data.bidId].price, serverResponse diff --git a/modules/interactiveOffersBidAdapter.js b/modules/interactiveOffersBidAdapter.js new file mode 100644 index 00000000000..ea45c00903a --- /dev/null +++ b/modules/interactiveOffersBidAdapter.js @@ -0,0 +1,149 @@ +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER} from '../src/mediaTypes.js'; +import {config} from '../src/config.js'; +import * as utils from '../src/utils.js'; + +const BIDDER_CODE = 'interactiveOffers'; +const ENDPOINT = 'https://rtb.ioadx.com/bidRequest/?partnerId=4a3bab187a74ac4862920cca864d6eff195ff5e4'; + +const DEFAULT = { + 'OpenRTBBidRequest': {}, + 'OpenRTBBidRequestSite': {}, + 'OpenRTBBidRequestSitePublisher': {}, + 'OpenRTBBidRequestSiteContent': { + language: navigator.language, + }, + 'OpenRTBBidRequestSource': {}, + 'OpenRTBBidRequestDevice': { + ua: navigator.userAgent, + language: navigator.language + }, + 'OpenRTBBidRequestUser': {}, + 'OpenRTBBidRequestImp': {}, + 'OpenRTBBidRequestImpBanner': {}, + 'PrebidBid': { + currency: 'USD', + ttl: 60, + netRevenue: false + } +}; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + + isBidRequestValid: function(bid) { + let ret = true; + if (bid && bid.params) { + if (!utils.isNumber(bid.params.pubid)) { + utils.logWarn('pubid must be a valid numeric ID'); + ret = false; + } + if (bid.params.tmax && !utils.isNumber(bid.params.tmax)) { + utils.logWarn('tmax must be a valid numeric ID'); + ret = false; + } + } else { + utils.logWarn('invalid request'); + ret = false; + } + return ret; + }, + buildRequests: function(validBidRequests, bidderRequest) { + let payload = parseRequestPrebidjsToOpenRTB(bidderRequest); + return { + method: 'POST', + url: ENDPOINT, + data: JSON.stringify(payload), + bidderRequest: bidderRequest + }; + }, + + interpretResponse: function(response, request) { + let bidResponses = []; + if (response.body && response.body.length) { + bidResponses = parseResponseOpenRTBToPrebidjs(response.body); + } + return bidResponses; + } +}; + +function parseRequestPrebidjsToOpenRTB(prebidRequest) { + let pageURL = window.location.href; + let domain = window.location.hostname; + let secure = (window.location.protocol == 'https:' ? 1 : 0); + let openRTBRequest = JSON.parse(JSON.stringify(DEFAULT['OpenRTBBidRequest'])); + openRTBRequest.id = prebidRequest.auctionId; + + openRTBRequest.site = JSON.parse(JSON.stringify(DEFAULT['OpenRTBBidRequestSite'])); + openRTBRequest.site.id = domain; + openRTBRequest.site.name = domain; + openRTBRequest.site.domain = domain; + openRTBRequest.site.page = pageURL; + openRTBRequest.site.ref = prebidRequest.refererInfo.referer; + + openRTBRequest.site.publisher = JSON.parse(JSON.stringify(DEFAULT['OpenRTBBidRequestSitePublisher'])); + openRTBRequest.site.publisher.id = 0; + openRTBRequest.site.publisher.name = config.getConfig('publisherDomain'); + openRTBRequest.site.publisher.domain = domain; + openRTBRequest.site.publisher.domain = domain; + + openRTBRequest.site.content = JSON.parse(JSON.stringify(DEFAULT['OpenRTBBidRequestSiteContent'])); + + openRTBRequest.source = JSON.parse(JSON.stringify(DEFAULT['OpenRTBBidRequestSource'])); + openRTBRequest.source.fd = 0; + openRTBRequest.source.tid = prebidRequest.auctionId; + openRTBRequest.source.pchain = ''; + + openRTBRequest.device = JSON.parse(JSON.stringify(DEFAULT['OpenRTBBidRequestDevice'])); + + openRTBRequest.user = JSON.parse(JSON.stringify(DEFAULT['OpenRTBBidRequestUser'])); + + openRTBRequest.imp = []; + prebidRequest.bids.forEach(function(bid, impId) { + impId++; + let imp = JSON.parse(JSON.stringify(DEFAULT['OpenRTBBidRequestImp'])); + imp.id = impId; + imp.secure = secure; + imp.tagid = bid.bidId; + + openRTBRequest.site.publisher.id = openRTBRequest.site.publisher.id || bid.params.pubid; + openRTBRequest.tmax = openRTBRequest.tmax || bid.params.tmax || 0; + + Object.keys(bid.mediaTypes).forEach(function(mediaType) { + if (mediaType == 'banner') { + imp.banner = JSON.parse(JSON.stringify(DEFAULT['OpenRTBBidRequestImpBanner'])); + imp.banner.w = 0; + imp.banner.h = 0; + imp.banner.format = []; + bid.mediaTypes[mediaType].sizes.forEach(function(adSize) { + if (!imp.banner.w) { + imp.banner.w = adSize[0]; + imp.banner.h = adSize[1]; + } + imp.banner.format.push({w: adSize[0], h: adSize[1]}); + }); + } + }); + openRTBRequest.imp.push(imp); + }); + return openRTBRequest; +} +function parseResponseOpenRTBToPrebidjs(openRTBResponse) { + let prebidResponse = []; + openRTBResponse.forEach(function(response) { + response.seatbid.forEach(function(seatbid) { + seatbid.bid.forEach(function(bid) { + let prebid = JSON.parse(JSON.stringify(DEFAULT['PrebidBid'])); + prebid.requestId = bid.ext.tagid; + prebid.ad = bid.adm; + prebid.creativeId = bid.crid; + prebid.cpm = bid.price; + prebidResponse.push(prebid); + }); + }); + }); + return prebidResponse; +} + +registerBidder(spec); diff --git a/modules/interactiveOffersBidAdapter.md b/modules/interactiveOffersBidAdapter.md index 7eb440c8216..35be59862ef 100644 --- a/modules/interactiveOffersBidAdapter.md +++ b/modules/interactiveOffersBidAdapter.md @@ -1,14 +1,14 @@ # Overview - + ``` Module Name: interactiveOffers Bidder Adapter Module Type: Bidder Adapter -Maintainer: devteam@interactiveoffers.com +Maintainer: faria@interactiveoffers.com ``` # Description -Module that connects to interactiveOffers demand sources. Param pubId is required. +Module that connects to interactiveOffers demand sources. Param pubid is required. # Test Parameters ``` @@ -24,11 +24,11 @@ Module that connects to interactiveOffers demand sources. Param pubId is require { bidder: "interactiveOffers", params: { - pubId: '10', - tmax: 5000 + pubid: 10, + tmax: 250 } } ] } ]; -``` \ No newline at end of file +``` diff --git a/modules/invibesBidAdapter.js b/modules/invibesBidAdapter.js index 50ed7d5f57c..d4d26b1e017 100644 --- a/modules/invibesBidAdapter.js +++ b/modules/invibesBidAdapter.js @@ -1,6 +1,6 @@ import * as utils from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { getStorageManager } from '../src/storageManager.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {getStorageManager} from '../src/storageManager.js'; const CONSTANTS = { BIDDER_CODE: 'invibes', @@ -8,9 +8,10 @@ const CONSTANTS = { SYNC_ENDPOINT: 'https://k.r66net.com/GetUserSync', TIME_TO_LIVE: 300, DEFAULT_CURRENCY: 'EUR', - PREBID_VERSION: 4, + PREBID_VERSION: 5, METHOD: 'GET', - INVIBES_VENDOR_ID: 436 + INVIBES_VENDOR_ID: 436, + USERID_PROVIDERS: ['pubcid', 'pubProvidedId'] }; const storage = getStorageManager(CONSTANTS.INVIBES_VENDOR_ID); @@ -54,6 +55,7 @@ registerBidder(spec); const topWin = getTopMostWindow(); let invibes = topWin.invibes = topWin.invibes || {}; invibes.purposes = invibes.purposes || [false, false, false, false, false, false, false, false, false, false]; +invibes.legitimateInterests = invibes.legitimateInterests || [false, false, false, false, false, false, false, false, false, false]; let _customUserSync; function isBidRequestValid(bid) { @@ -77,7 +79,7 @@ function isBidRequestValid(bid) { function buildRequest(bidRequests, bidderRequest) { bidderRequest = bidderRequest || {}; const _placementIds = []; - let _loginId, _customEndpoint; + let _loginId, _customEndpoint, _userId; let _ivAuctionStart = bidderRequest.auctionStart || Date.now(); bidRequests.forEach(function (bidRequest) { @@ -86,6 +88,7 @@ function buildRequest(bidRequests, bidderRequest) { _loginId = _loginId || bidRequest.params.loginId; _customEndpoint = _customEndpoint || bidRequest.params.customEndpoint; _customUserSync = _customUserSync || bidRequest.params.customUserSync; + _userId = _userId || bidRequest.userId; }); invibes.optIn = invibes.optIn || readGdprConsent(bidderRequest.gdprConsent); @@ -95,19 +98,23 @@ function buildRequest(bidRequests, bidderRequest) { let lid = initDomainId(invibes.domainOptions); const currentQueryStringParams = parseQueryStringParams(); - + let userIdModel = getUserIds(_userId); + let bidParamsJson = { + placementIds: _placementIds, + loginId: _loginId, + auctionStartTime: _ivAuctionStart, + bidVersion: CONSTANTS.PREBID_VERSION + }; + if (userIdModel) { + bidParamsJson.userId = userIdModel; + } let data = { location: getDocumentLocation(topWin), videoAdHtmlId: generateRandomId(), showFallback: currentQueryStringParams['advs'] === '0', ivbsCampIdsLocal: invibes.getCookie('IvbsCampIdsLocal'), - bidParamsJson: JSON.stringify({ - placementIds: _placementIds, - loginId: _loginId, - auctionStartTime: _ivAuctionStart, - bidVersion: CONSTANTS.PREBID_VERSION - }), + bidParamsJson: JSON.stringify(bidParamsJson), capCounts: getCappedCampaignsAsString(), vId: invibes.visitId, @@ -118,6 +125,7 @@ function buildRequest(bidRequests, bidderRequest) { kw: keywords, purposes: invibes.purposes.toString(), + li: invibes.legitimateInterests.toString(), tc: invibes.gdpr_consent }; @@ -140,7 +148,7 @@ function buildRequest(bidRequests, bidderRequest) { method: CONSTANTS.METHOD, url: _customEndpoint || CONSTANTS.BID_ENDPOINT, data: data, - options: { withCredentials: true }, + options: {withCredentials: true}, // for POST: { contentType: 'application/json', withCredentials: true } bidRequests: bidRequests }; @@ -228,9 +236,26 @@ function getDocumentLocation(topWin) { return topWin.location.href.substring(0, 300).split(/[?#]/)[0]; } +function getUserIds(bidUserId) { + let userId; + if (bidUserId) { + CONSTANTS.USERID_PROVIDERS.forEach(provider => { + if (bidUserId[provider]) { + userId = userId || {}; + userId[provider] = bidUserId[provider]; + } + }); + } + + return userId; +} + function parseQueryStringParams() { let params = {}; - try { params = JSON.parse(localStorage.ivbs); } catch (e) { } + try { + params = JSON.parse(localStorage.ivbs); + } catch (e) { + } let re = /[\\?&]([^=]+)=([^\\?&#]+)/g; let m; while ((m = re.exec(window.location.href)) != null) { @@ -257,9 +282,12 @@ function getTopMostWindow() { try { while (top !== res) { - if (res.parent.location.href.length) { res = res.parent; } + if (res.parent.location.href.length) { + res = res.parent; + } } - } catch (e) { } + } catch (e) { + } return res; } @@ -277,7 +305,9 @@ function renderCreative(bidModel) { function getCappedCampaignsAsString() { const key = 'ivvcap'; - if (!invibes.optIn || !invibes.purposes[0]) { return ''; } + if (!invibes.optIn || !invibes.purposes[0]) { + return ''; + } let loadData = function () { try { @@ -311,24 +341,31 @@ function getCappedCampaignsAsString() { clearExpired(); let data = loadData(); return Object.keys(data) - .filter(function (k) { return data.hasOwnProperty(k); }) + .filter(function (k) { + return data.hasOwnProperty(k); + }) .sort() - .map(function (k) { return [k, data[k][0]]; }); + .map(function (k) { + return [k, data[k][0]]; + }); }; return getCappedCampaigns() - .map(function (record) { return record.join('='); }) + .map(function (record) { + return record.join('='); + }) .join(','); } -const noop = function () { }; +const noop = function () { +}; function initLogger() { if (storage.hasLocalStorage() && localStorage.InvibesDEBUG) { return window.console; } - return { info: noop, error: noop, log: noop, warn: noop, debug: noop }; + return {info: noop, error: noop, log: noop, warn: noop, debug: noop}; } function buildSyncUrl() { @@ -358,39 +395,37 @@ function readGdprConsent(gdprConsent) { for (index = 0; index < invibes.purposes; ++index) { invibes.purposes[index] = true; } + + for (index = 0; index < invibes.legitimateInterests.length; ++index) { + invibes.legitimateInterests[index] = true; + } return 2; } let purposeConsents = getPurposeConsents(gdprConsent.vendorData); - if (purposeConsents == null) { return 0; } + if (purposeConsents == null) { + return 0; + } let purposesLength = getPurposeConsentsCounter(gdprConsent.vendorData); - if (purposeConsents instanceof Array) { - for (let i = 0; i < purposesLength && i < purposeConsents.length; i++) { - invibes.purposes[i] = !((purposeConsents[i] === false || purposeConsents[i] === 'false' || purposeConsents[i] == null)); - } - } else if (typeof purposeConsents === 'object' && purposeConsents !== null) { - let i = 0; - for (let prop in purposeConsents) { - if (i === purposesLength) { - break; - } - - if (purposeConsents.hasOwnProperty(prop)) { - invibes.purposes[i] = !((purposeConsents[prop] === false || purposeConsents[prop] === 'false' || purposeConsents[prop] == null)); - i++; - } - } - } else { + if (!tryCopyValueToArray(purposeConsents, invibes.purposes, purposesLength)) { return 0; } + let legitimateInterests = getLegitimateInterests(gdprConsent.vendorData); + tryCopyValueToArray(legitimateInterests, invibes.legitimateInterests, 10); + let invibesVendorId = CONSTANTS.INVIBES_VENDOR_ID.toString(10); let vendorConsents = getVendorConsents(gdprConsent.vendorData); - if (vendorConsents == null || vendorConsents[invibesVendorId] == null) { return 4; } + let vendorHasLegitimateInterest = getVendorLegitimateInterest(gdprConsent.vendorData)[invibesVendorId] === true; + if (vendorConsents == null || vendorConsents[invibesVendorId] == null) { + return 4; + } - if (vendorConsents[invibesVendorId] === false) { return 0; } + if (vendorConsents[invibesVendorId] === false && vendorHasLegitimateInterest === false) { + return 0; + } return 2; } @@ -398,6 +433,31 @@ function readGdprConsent(gdprConsent) { return 0; } +function tryCopyValueToArray(value, target, length) { + if (value instanceof Array) { + for (let i = 0; i < length && i < value.length; i++) { + target[i] = !((value[i] === false || value[i] === 'false' || value[i] == null)); + } + return true; + } + if (typeof value === 'object' && value !== null) { + let i = 0; + for (let prop in value) { + if (i === length) { + break; + } + + if (value.hasOwnProperty(prop)) { + target[i] = !((value[prop] === false || value[prop] === 'false' || value[prop] == null)); + i++; + } + } + return true; + } + + return false; +} + function getPurposeConsentsCounter(vendorData) { if (vendorData.purpose && vendorData.purpose.consents) { return 10; @@ -418,9 +478,19 @@ function getPurposeConsents(vendorData) { return null; } +function getLegitimateInterests(vendorData) { + if (vendorData.purpose && vendorData.purpose.legitimateInterests) { + return vendorData.purpose.legitimateInterests; + } + + return null; +} + function getVendorConsentData(vendorData) { if (vendorData.purpose && vendorData.purpose.consents) { - if (vendorData.tcString != null) { return vendorData.tcString; } + if (vendorData.tcString != null) { + return vendorData.tcString; + } } return vendorData.consentData; }; @@ -437,13 +507,23 @@ function getVendorConsents(vendorData) { return null; } +function getVendorLegitimateInterest(vendorData) { + if (vendorData.vendor && vendorData.vendor.legitimateInterests) { + return vendorData.vendor.legitimateInterests; + } + + return {}; +} + const ivLogger = initLogger(); /// Local domain cookie management ===================== invibes.Uid = { generate: function () { let maxRand = parseInt('zzzzzz', 36) - let mkRand = function () { return Math.floor(Math.random() * maxRand).toString(36); }; + let mkRand = function () { + return Math.floor(Math.random() * maxRand).toString(36); + }; let rand1 = mkRand(); let rand2 = mkRand(); return rand1 + rand2; @@ -451,9 +531,13 @@ invibes.Uid = { }; invibes.getCookie = function (name) { - if (!storage.cookiesAreEnabled()) { return; } + if (!storage.cookiesAreEnabled()) { + return; + } - if (!invibes.optIn || !invibes.purposes[0]) { return; } + if (!invibes.optIn || !invibes.purposes[0]) { + return; + } return storage.getCookie(name); }; @@ -465,7 +549,8 @@ let initDomainId = function (options) { let str = invibes.getCookie(this.cname) || ''; try { return JSON.parse(str); - } catch (e) { } + } catch (e) { + } } }; @@ -546,6 +631,7 @@ let keywords = (function () { } return kw; }()); + // ===================== export function resetInvibes() { diff --git a/modules/ipromBidAdapter.js b/modules/ipromBidAdapter.js new file mode 100644 index 00000000000..e328cd1ec5d --- /dev/null +++ b/modules/ipromBidAdapter.js @@ -0,0 +1,72 @@ +import * as utils from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; + +const BIDDER_CODE = 'iprom'; +const ENDPOINT_URL = 'https://core.iprom.net/programmatic'; +const VERSION = 'v1.0.0'; +const DEFAULT_CURRENCY = 'EUR'; +const DEFAULT_NETREVENUE = true; +const DEFAULT_TTL = 360; + +export const spec = { + code: BIDDER_CODE, + isBidRequestValid: function ({ bidder, params = {} } = {}) { + // id parameter checks + if (!params.id) { + utils.logError(`${bidder}: Parameter 'id' missing`); + return false; + } else if (typeof params.id !== 'string') { + utils.logError(`${bidder}: Parameter 'id' needs to be a string`); + return false; + } + // dimension parameter checks + if (!params.dimension) { + utils.logError(`${bidder}: Required parameter 'dimension' missing`); + return false; + } else if (typeof params.dimension !== 'string') { + utils.logError(`${bidder}: Parameter 'dimension' needs to be a string`); + return false; + } + + return true; + }, + + buildRequests: function (validBidRequests, bidderRequest) { + const payload = { + bids: validBidRequests, + referer: bidderRequest.refererInfo, + version: VERSION + }; + const payloadString = JSON.stringify(payload); + + return { + method: 'POST', + url: ENDPOINT_URL, + data: payloadString + }; + }, + + interpretResponse: function (serverResponse, request) { + let bids = serverResponse.body; + + const bidResponses = []; + + bids.forEach(bid => { + bidResponses.push({ + ad: bid.ad, + requestId: bid.requestId, + cpm: bid.cpm, + width: bid.width, + height: bid.height, + creativeId: bid.creativeId, + currency: bid.currency || DEFAULT_CURRENCY, + netRevenue: bid.netRevenue || DEFAULT_NETREVENUE, + ttl: bid.ttl || DEFAULT_TTL, + }); + }); + + return bidResponses; + }, +} + +registerBidder(spec); diff --git a/modules/ipromBidAdapter.md b/modules/ipromBidAdapter.md new file mode 100644 index 00000000000..f7124e7c89c --- /dev/null +++ b/modules/ipromBidAdapter.md @@ -0,0 +1,34 @@ +# Overview + +``` +Module Name: Iprom PreBid Adapter +Module Type: Bidder Adapter +Maintainer: support@iprom.si +``` + +# Description + +Module that connects to Iprom's demand sources + +# Test Parameters +``` + var adUnits = [ + { + code: 'test-div', + mediaTypes: { + banner: { + sizes: [[300, 250]], // a display size + } + }, + bids: [ + { + bidder: "iprom", + params: { + id: '1234', + dimension: '300x250' + } + } + ] + } + ]; +``` diff --git a/modules/ironsourceBidAdapter.js b/modules/ironsourceBidAdapter.js index e23c0cb857d..5b8531d7a85 100644 --- a/modules/ironsourceBidAdapter.js +++ b/modules/ironsourceBidAdapter.js @@ -211,7 +211,8 @@ function generateParameters(bid, bidderRequest) { bid_id: utils.getBidIdParameter('bidId', bid), bidder_request_id: utils.getBidIdParameter('bidderRequestId', bid), transaction_id: utils.getBidIdParameter('transactionId', bid), - session_id: utils.getBidIdParameter('auctionId', bid), + session_id: params.sessionId || utils.getBidIdParameter('auctionId', bid), + is_wrapper: !!params.isWrapper, publisher_name: domain, site_domain: domain, bidder_version: BIDDER_VERSION @@ -243,7 +244,7 @@ function generateParameters(bid, bidderRequest) { if (bidderRequest && bidderRequest.refererInfo) { requestParams.referrer = utils.deepAccess(bidderRequest, 'refererInfo.referer'); - requestParams.page_url = utils.deepAccess(bidderRequest, 'refererInfo.canonicalUrl') || config.getConfig('pageUrl') || utils.deepAccess(window, 'location.href'); + requestParams.page_url = config.getConfig('pageUrl') || utils.deepAccess(window, 'location.href'); } return requestParams; diff --git a/modules/ixBidAdapter.js b/modules/ixBidAdapter.js index cb37e4735c5..7b972aa37e6 100644 --- a/modules/ixBidAdapter.js +++ b/modules/ixBidAdapter.js @@ -6,6 +6,8 @@ import isInteger from 'core-js-pure/features/number/is-integer.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; const BIDDER_CODE = 'ix'; +const ALIAS_BIDDER_CODE = 'roundel'; +const GLOBAL_VENDOR_ID = 10; const SECURE_BID_URL = 'https://htlb.casalemedia.com/cygnus'; const SUPPORTED_AD_TYPES = [BANNER, VIDEO]; const BANNER_ENDPOINT_VERSION = 7.2; @@ -14,11 +16,14 @@ const CENT_TO_DOLLAR_FACTOR = 100; const BANNER_TIME_TO_LIVE = 300; const VIDEO_TIME_TO_LIVE = 3600; // 1hr const NET_REVENUE = true; + const PRICE_TO_DOLLAR_FACTOR = { JPY: 1 }; const USER_SYNC_URL = 'https://js-sec.indexww.com/um/ixmatch.html'; +const FLOOR_SOURCE = { PBJS: 'p', IX: 'x' }; + /** * Transform valid bid request config object to banner impression object that will be sent to ad server. * @@ -33,6 +38,8 @@ function bidToBannerImp(bid) { imp.banner.h = bid.params.size[1]; imp.banner.topframe = utils.inIframe() ? 0 : 1; + _applyFloor(bid, imp, BANNER); + return imp; } @@ -44,12 +51,20 @@ function bidToBannerImp(bid) { */ function bidToVideoImp(bid) { const imp = bidToImp(bid); + const videoAdUnitRef = utils.deepAccess(bid, 'mediaTypes.video'); + const context = utils.deepAccess(bid, 'mediaTypes.video.context'); + const videoAdUnitAllowlist = [ + 'mimes', 'minduration', 'maxduration', 'protocols', 'protocol', + 'startdelay', 'placement', 'linearity', 'skip', 'skipmin', + 'skipafter', 'sequence', 'battr', 'maxextended', 'minbitrate', + 'maxbitrate', 'boxingallowed', 'playbackmethod', 'playbackend', + 'delivery', 'pos', 'companionad', 'api', 'companiontype', 'ext' + ]; imp.video = utils.deepClone(bid.params.video) imp.video.w = bid.params.size[0]; imp.video.h = bid.params.size[1]; - const context = utils.deepAccess(bid, 'mediaTypes.video.context'); if (context) { if (context === 'instream') { imp.video.placement = 1; @@ -60,6 +75,14 @@ function bidToVideoImp(bid) { } } + for (const adUnitProperty in videoAdUnitRef) { + if (videoAdUnitAllowlist.indexOf(adUnitProperty) !== -1 && !imp.video.hasOwnProperty(adUnitProperty)) { + imp.video[adUnitProperty] = videoAdUnitRef[adUnitProperty]; + } + } + + _applyFloor(bid, imp, VIDEO); + return imp; } @@ -78,12 +101,73 @@ function bidToImp(bid) { imp.ext.sid = `${bid.params.size[0]}x${bid.params.size[1]}`; } - if (bid.params.hasOwnProperty('bidFloor') && bid.params.hasOwnProperty('bidFloorCur')) { - imp.bidfloor = bid.params.bidFloor; - imp.bidfloorcur = bid.params.bidFloorCur; + return imp; +} + +/** + * Gets priceFloors floors and IX adapter floors, + * Validates and sets the higher one on the impression + * @param {object} bid bid object + * @param {object} imp impression object + * @param {string} mediaType the impression ad type, one of the SUPPORTED_AD_TYPES + */ +function _applyFloor(bid, imp, mediaType) { + let adapterFloor = null; + let moduleFloor = null; + + if (bid.params.bidFloor && bid.params.bidFloorCur) { + adapterFloor = { floor: bid.params.bidFloor, currency: bid.params.bidFloorCur }; } - return imp; + if (utils.isFn(bid.getFloor)) { + let _mediaType = '*'; + let _size = '*'; + + if (mediaType && utils.contains(SUPPORTED_AD_TYPES, mediaType)) { + const { w: width, h: height } = imp[mediaType]; + _mediaType = mediaType; + _size = [width, height]; + } + try { + moduleFloor = bid.getFloor({ + mediaType: _mediaType, + size: _size + }); + } catch (err) { + // continue with no module floors + utils.logWarn('priceFloors module call getFloor failed, error : ', err); + } + } + + if (adapterFloor && moduleFloor) { + if (adapterFloor.currency !== moduleFloor.currency) { + utils.logWarn('The bid floor currency mismatch between IX params and priceFloors module config'); + return; + } + + if (adapterFloor.floor > moduleFloor.floor) { + imp.bidfloor = adapterFloor.floor; + imp.bidfloorcur = adapterFloor.currency; + imp.ext.fl = FLOOR_SOURCE.IX; + } else { + imp.bidfloor = moduleFloor.floor; + imp.bidfloorcur = moduleFloor.currency; + imp.ext.fl = FLOOR_SOURCE.PBJS; + } + return; + } + + if (moduleFloor) { + imp.bidfloor = moduleFloor.floor; + imp.bidfloorcur = moduleFloor.currency; + imp.ext.fl = FLOOR_SOURCE.PBJS; + } else if (adapterFloor) { + imp.bidfloor = adapterFloor.floor; + imp.bidfloorcur = adapterFloor.currency; + imp.ext.fl = FLOOR_SOURCE.IX; + } else { + utils.logInfo('IX Bid Adapter: No floors available, no floors applied'); + } } /** @@ -198,36 +282,35 @@ function getBidRequest(id, impressions) { } /** - * Adds a User ID module's response into user Eids array. - * - * @param {array} userEids An array of objects containing user ids, - * will be attached to bid request later. - * @param {object} seenIdPartners An object with Identity partners names already added, - * updated with new partner name. - * @param {*} id The id obtained from User ID module. - * @param {string} source The URL of the User ID module. - * @param {string} ixlPartnerName The name of the Identity Partner in IX Library. - * @param {string} rtiPartner The name of the User ID provider in Prebid. - * @return {boolean} True if successfully added the ID to the userEids, false otherwise. + * From the userIdAsEids array, filter for the ones our adserver can use, and modify them + * for our purposes, e.g. add rtiPartner + * @param {array} allEids userIdAsEids passed in by prebid + * @return {object} contains toSend (eids to send to the adserver) and seenSources (used to filter + * identity info from IX Library) */ -function addUserEids(userEids, seenIdPartners, id, source, ixlPartnerName, rtiPartner) { - if (id) { - // mark the partnername that IX RTI uses - seenIdPartners[ixlPartnerName] = 1; - userEids.push({ - source: source, - uids: [{ - id: id, - ext: { - rtiPartner: rtiPartner - } - }] - }); - return true; +function getEidInfo(allEids) { + // determines which eids we send and the rtiPartner field in ext + var sourceRTIMapping = { + 'liveramp.com': 'idl', + 'netid.de': 'NETID', + 'neustar.biz': 'fabrickId', + 'zeotap.com': 'zeotapIdPlus' + }; + var toSend = []; + var seenSources = {}; + if (utils.isArray(allEids)) { + for (var i = 0; i < allEids.length; i++) { + if (sourceRTIMapping[allEids[i].source] && utils.deepAccess(allEids[i], 'uids.0')) { + seenSources[allEids[i].source] = 1; + allEids[i].uids[0].ext = { + rtiPartner: sourceRTIMapping[allEids[i].source] + }; + delete allEids[i].uids[0].atype; + toSend.push(allEids[i]); + } + } } - - utils.logWarn('Tried to add a user ID from Prebid, the ID received was null'); - return false; + return { toSend: toSend, seenSources: seenSources }; } /** @@ -241,21 +324,12 @@ function addUserEids(userEids, seenIdPartners, id, source, ixlPartnerName, rtiPa * */ function buildRequest(validBidRequests, bidderRequest, impressions, version) { - const userEids = []; - // Always use secure HTTPS protocol. let baseUrl = SECURE_BID_URL; - // Dict for identity partners already populated from prebid - let seenIdPartners = {}; - // Get ids from Prebid User ID Modules - const userId = validBidRequests[0].userId; - if (userId && typeof userId === 'object') { - if (userId.idl_env) { - addUserEids(userEids, seenIdPartners, userId.idl_env, 'liveramp.com', 'LiveRampIp', 'idl'); - } - } + var eidInfo = getEidInfo(utils.deepAccess(validBidRequests, '0.userIdAsEids')); + var userEids = eidInfo.toSend; // RTI ids will be included in the bid request if the function getIdentityInfo() is loaded // and if the data for the partner exist @@ -264,17 +338,21 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { if (identityInfo && typeof identityInfo === 'object') { for (const partnerName in identityInfo) { if (identityInfo.hasOwnProperty(partnerName)) { - // check if not already populated by prebid cache - if (!seenIdPartners.hasOwnProperty(partnerName)) { - let response = identityInfo[partnerName]; - if (!response.responsePending && response.data && typeof response.data === 'object' && Object.keys(response.data).length) { - userEids.push(response.data); - } + let response = identityInfo[partnerName]; + if (!response.responsePending && response.data && typeof response.data === 'object' && + Object.keys(response.data).length && !eidInfo.seenSources[response.data.source]) { + userEids.push(response.data); } } } } } + + // If `roundel` alias bidder, only send requests if liveramp ids exist. + if (bidderRequest && bidderRequest.bidderCode === ALIAS_BIDDER_CODE && !eidInfo.seenSources['liveramp.com']) { + return []; + } + const r = {}; // Since bidderRequestId are the same for different bid request, just use the first one. @@ -285,6 +363,12 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { r.ext.source = 'prebid'; r.ext.ixdiag = {}; + // getting ixdiags for adunits of the video, outstream & multi format (MF) style + let ixdiag = buildIXDiag(validBidRequests); + for (var key in ixdiag) { + r.ext.ixdiag[key] = ixdiag[key]; + } + // if an schain is provided, send it along if (validBidRequests[0].schain) { r.source = { @@ -321,6 +405,12 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { r.user.ext = { consent: gdprConsent.consentString || '' }; + + if (gdprConsent.hasOwnProperty('addtlConsent') && gdprConsent.addtlConsent) { + r.user.ext.consented_providers_settings = { + consented_providers: gdprConsent.addtlConsent + } + } } } @@ -382,7 +472,7 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { data: payload }; - const BASE_REQ_SIZE = new Blob([`${request.url}${utils.parseQueryStringParameters({...request.data, r: JSON.stringify(r)})}`]).size; + const BASE_REQ_SIZE = new Blob([`${request.url}${utils.parseQueryStringParameters({ ...request.data, r: JSON.stringify(r) })}`]).size; let currReqSize = BASE_REQ_SIZE; const MAX_REQ_SIZE = 8000; @@ -466,6 +556,67 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { return requests; } + +/** + * Calculates IX diagnostics values and packages them into an object + * + * @param {array} validBidRequests The valid bid requests from prebid + * @return {Object} IX diag values for ad units + */ +function buildIXDiag(validBidRequests) { + var adUnitMap = validBidRequests + .map(bidRequest => bidRequest.transactionId) + .filter((value, index, arr) => arr.indexOf(value) === index) + + var ixdiag = { + mfu: 0, + bu: 0, + iu: 0, + nu: 0, + ou: 0, + allu: 0, + ren: false, + version: '$prebid.version$' + }; + + // create ad unit map and collect the required diag properties + for (let i = 0; i < adUnitMap.length; i++) { + var bid = validBidRequests.filter(bidRequest => bidRequest.transactionId === adUnitMap[i])[0]; + + if (utils.deepAccess(bid, 'mediaTypes')) { + if (Object.keys(bid.mediaTypes).length > 1) { + ixdiag.mfu++; + } + + if (utils.deepAccess(bid, 'mediaTypes.native')) { + ixdiag.nu++; + } + + if (utils.deepAccess(bid, 'mediaTypes.banner')) { + ixdiag.bu++; + } + + if (utils.deepAccess(bid, 'mediaTypes.video.context') === 'outstream') { + ixdiag.ou++; + // renderer only needed for outstream + + const hasRenderer = typeof (utils.deepAccess(bid, 'renderer') || utils.deepAccess(bid, 'mediaTypes.video.renderer')) === 'object'; + + // if any one ad unit is missing renderer, set ren status to false in diag + ixdiag.ren = ixdiag.ren && hasRenderer ? (utils.deepAccess(ixdiag, 'ren')) : hasRenderer; + } + + if (utils.deepAccess(bid, 'mediaTypes.video.context') === 'instream') { + ixdiag.iu++; + } + + ixdiag.allu++; + } + } + + return ixdiag; +} + /** * * @param {Object} impressions containing ixImps and possibly missingImps @@ -525,7 +676,8 @@ function updateMissingSizes(validBidRequest, missingBannerSizes, imp) { if (utils.deepAccess(validBidRequest, 'mediaTypes.banner.sizes')) { let sizeList = utils.deepClone(validBidRequest.mediaTypes.banner.sizes); removeFromSizes(sizeList, validBidRequest.params.size); - let newAdUnitEntry = { 'missingSizes': sizeList, + let newAdUnitEntry = { + 'missingSizes': sizeList, 'impression': imp }; missingBannerSizes[transactionID] = newAdUnitEntry; @@ -534,23 +686,31 @@ function updateMissingSizes(validBidRequest, missingBannerSizes, imp) { } /** - * + * @param {object} bid ValidBidRequest object, used to adjust floor * @param {object} imp Impression object to be modified * @param {array} newSize The new size to be applied * @return {object} newImp Updated impression object */ -function createMissingBannerImp(imp, newSize) { +function createMissingBannerImp(bid, imp, newSize) { const newImp = utils.deepClone(imp); newImp.ext.sid = `${newSize[0]}x${newSize[1]}`; newImp.banner.w = newSize[0]; newImp.banner.h = newSize[1]; + + _applyFloor(bid, newImp, BANNER); + return newImp; } export const spec = { code: BIDDER_CODE, - gvlid: 10, + gvlid: GLOBAL_VENDOR_ID, + aliases: [{ + code: ALIAS_BIDDER_CODE, + gvlid: GLOBAL_VENDOR_ID, + skipPbsAliasing: false + }], supportedMediaTypes: SUPPORTED_AD_TYPES, /** @@ -560,32 +720,57 @@ export const spec = { * @return {boolean} True if this is a valid bid, and false otherwise. */ isBidRequestValid: function (bid) { + const paramsVideoRef = utils.deepAccess(bid, 'params.video'); + const paramsSize = utils.deepAccess(bid, 'params.size'); + const mediaTypeBannerSizes = utils.deepAccess(bid, 'mediaTypes.banner.sizes'); + const mediaTypeVideoRef = utils.deepAccess(bid, 'mediaTypes.video'); + const mediaTypeVideoPlayerSize = utils.deepAccess(bid, 'mediaTypes.video.playerSize'); + const hasBidFloor = bid.params.hasOwnProperty('bidFloor'); + const hasBidFloorCur = bid.params.hasOwnProperty('bidFloorCur'); + if (!isValidSize(bid.params.size)) { utils.logError('ix bidder params: bid size has invalid format.'); return false; } - if (!includesSize(bid.sizes, bid.params.size)) { - utils.logError('ix bidder params: bid size is not included in ad unit sizes.'); + if (bid.hasOwnProperty('mediaType') && !(utils.contains(SUPPORTED_AD_TYPES, bid.mediaType))) { return false; } - if (bid.hasOwnProperty('mediaType') && !(utils.contains(SUPPORTED_AD_TYPES, bid.mediaType))) { + if (bid.hasOwnProperty('mediaTypes') && !(mediaTypeBannerSizes || mediaTypeVideoPlayerSize)) { return false; } - if (bid.hasOwnProperty('mediaTypes') && !(utils.deepAccess(bid, 'mediaTypes.banner.sizes') || utils.deepAccess(bid, 'mediaTypes.video.playerSize'))) { + if (!includesSize(bid.sizes, paramsSize) && !((mediaTypeVideoPlayerSize && includesSize(mediaTypeVideoPlayerSize, paramsSize)) || + (mediaTypeBannerSizes && includesSize(mediaTypeBannerSizes, paramsSize)))) { + utils.logError('ix bidder params: bid size is not included in ad unit sizes or player size.'); return false; } + if (mediaTypeVideoRef && paramsVideoRef) { + const requiredIXParams = ['mimes', 'minduration', 'maxduration', 'protocols']; + let isParamsLevelValid = true; + for (let property of requiredIXParams) { + if (!mediaTypeVideoRef.hasOwnProperty(property) && !paramsVideoRef.hasOwnProperty(property)) { + const isProtocolsValid = (property === 'protocols' && (mediaTypeVideoRef.hasOwnProperty('protocol') || paramsVideoRef.hasOwnProperty('protocol'))); + if (isProtocolsValid) { + continue; + } + utils.logError('ix bidder params: ' + property + ' is not included in either the adunit or params level'); + isParamsLevelValid = false; + } + } + + if (!isParamsLevelValid) { + return false; + } + } + if (typeof bid.params.siteId !== 'string' && typeof bid.params.siteId !== 'number') { utils.logError('ix bidder params: siteId must be string or number value.'); return false; } - const hasBidFloor = bid.params.hasOwnProperty('bidFloor'); - const hasBidFloorCur = bid.params.hasOwnProperty('bidFloorCur'); - if (hasBidFloor || hasBidFloorCur) { if (!(hasBidFloor && hasBidFloorCur && isValidBidFloorParams(bid.params.bidFloor, bid.params.bidFloorCur))) { utils.logError('ix bidder params: bidFloor / bidFloorCur parameter has invalid format.'); @@ -616,7 +801,7 @@ export const spec = { detectMissingSizes: true, }; - const ixConfig = {...DEFAULT_IX_CONFIG, ...config.getConfig('ix')}; + const ixConfig = { ...DEFAULT_IX_CONFIG, ...config.getConfig('ix') }; for (let i = 0; i < validBidRequests.length; i++) { validBidRequest = validBidRequests[i]; @@ -629,14 +814,12 @@ export const spec = { if (!videoImps[validBidRequest.transactionId].hasOwnProperty('ixImps')) { videoImps[validBidRequest.transactionId].ixImps = []; } - videoImps[validBidRequest.transactionId].ixImps.push(bidToVideoImp(validBidRequest)); - } else { - utils.logError('Bid size is not included in video playerSize') } } - if (validBidRequest.mediaType === BANNER || utils.deepAccess(validBidRequest, 'mediaTypes.banner') || - (!validBidRequest.mediaType && !validBidRequest.mediaTypes)) { + if (validBidRequest.mediaType === BANNER || + (utils.deepAccess(validBidRequest, 'mediaTypes.banner') && includesSize(utils.deepAccess(validBidRequest, 'mediaTypes.banner.sizes'), validBidRequest.params.size)) || + (!validBidRequest.mediaType && !validBidRequest.mediaTypes)) { let imp = bidToBannerImp(validBidRequest); if (!bannerImps.hasOwnProperty(validBidRequest.transactionId)) { @@ -667,7 +850,7 @@ export const spec = { let origImp = missingBannerSizes[transactionId].impression; for (let i = 0; i < missingSizes.length; i++) { - let newImp = createMissingBannerImp(origImp, missingSizes[i]); + let newImp = createMissingBannerImp(validBidRequest, origImp, missingSizes[i]); bannerImps[transactionId].missingImps.push(newImp); bannerImps[transactionId].missingCount++; } @@ -726,7 +909,7 @@ export const spec = { * @param {Boolean} isOpenRtb boolean to check openrtb2 protocol * @return {Object} params bid params */ - transformBidParams: function(params, isOpenRtb) { + transformBidParams: function (params, isOpenRtb) { return utils.convertTypes({ 'siteID': 'number' }, params); diff --git a/modules/ixBidAdapter.md b/modules/ixBidAdapter.md index 5b9903c91d2..c358b19a0a2 100644 --- a/modules/ixBidAdapter.md +++ b/modules/ixBidAdapter.md @@ -87,7 +87,7 @@ object are detailed here. | --- | --- | --- | --- | siteId | Required | String | An IX-specific identifier that is associated with a specific size on this ad unit. This is similar to a placement ID or an ad unit ID that some other modules have. Examples: `'3723'`, `'6482'`, `'3639'` | size | Required | Number[] | The single size associated with the site ID. It should be one of the sizes listed in the ad unit under `adUnits[].sizes` or `adUnits[].mediaTypes.video.playerSize`. Examples: `[300, 250]`, `[300, 600]` -| video | Required | Hash | The video object will serve as the properties of the video ad. You can create any field under the video object that is mentioned in the `OpenRTB Spec v2.5`. Some fields like `mimes, protocols, minduration, maxduration` are required. +| video | Required | Hash | The video object will serve as the properties of the video ad. You can create any field under the video object that is mentioned in the `OpenRTB Spec v2.5`. Some fields like `mimes, protocols, minduration, maxduration` are required. Properties not defined at this level, will be pulled from the Adunit level. | video.mimes | Required | String[] | Array list of content MIME types supported. Popular MIME types include, but are not limited to, `"video/x-ms- wmv"` for Windows Media and `"video/x-flv"` for Flash Video. |video.minduration| Required | Integer | Minimum video ad duration in seconds. |video.maxduration| Required | Integer | Maximum video ad duration in seconds. diff --git a/modules/jwplayerRtdProvider.js b/modules/jwplayerRtdProvider.js index 197c3c192c8..8332e720ae7 100644 --- a/modules/jwplayerRtdProvider.js +++ b/modules/jwplayerRtdProvider.js @@ -144,8 +144,13 @@ function enrichBidRequest(bidReqConfig, onDone) { * @param {function} onDone */ export function enrichAdUnits(adUnits) { - const fpdFallback = config.getConfig('fpd.context.data.jwTargeting'); + const fpdFallback = config.getConfig('ortb2.site.ext.data.jwTargeting'); adUnits.forEach(adUnit => { + const jwTargeting = extractPublisherParams(adUnit, fpdFallback); + if (!jwTargeting || !Object.keys(jwTargeting).length) { + return; + } + const onVatResponse = function (vat) { if (!vat) { return; @@ -153,25 +158,29 @@ export function enrichAdUnits(adUnits) { const targeting = formatTargetingResponse(vat); addTargetingToBids(adUnit.bids, targeting); }; - - const jwTargeting = extractPublisherParams(adUnit, fpdFallback); loadVat(jwTargeting, onVatResponse); }); } +function supportsInstreamVideo(mediaTypes) { + const video = mediaTypes && mediaTypes.video; + return video && video.context === 'instream'; +} + export function extractPublisherParams(adUnit, fallback) { let adUnitTargeting; try { - adUnitTargeting = adUnit.fpd.context.data.jwTargeting; + adUnitTargeting = adUnit.ortb2Imp.ext.data.jwTargeting; } catch (e) {} - return Object.assign({}, fallback, adUnitTargeting); -} -function loadVat(params, onCompletion) { - if (!params || !Object.keys(params).length) { + if (!adUnitTargeting && !supportsInstreamVideo(adUnit.mediaTypes)) { return; } + return Object.assign({}, fallback, adUnitTargeting); +} + +function loadVat(params, onCompletion) { const { playerID, mediaID } = params; if (pendingRequests[mediaID] !== undefined) { loadVatForPendingRequest(playerID, mediaID, onCompletion); diff --git a/modules/jwplayerRtdProvider.md b/modules/jwplayerRtdProvider.md index ae09277979a..0723e8cbb6c 100644 --- a/modules/jwplayerRtdProvider.md +++ b/modules/jwplayerRtdProvider.md @@ -25,14 +25,14 @@ pbjs.setConfig({ } }); ``` -Lastly, include the content's media ID and/or the player's ID in the matching AdUnit's `fpd.context.data`: +Lastly, include the content's media ID and/or the player's ID in the matching AdUnit's `ortb2Imp.ext.data`: ```javascript const adUnit = { code: '/19968336/prebid_native_example_1', ... - fpd: { - context: { + ortb2Imp: { + ext: { data: { jwTargeting: { // Note: the following Ids are placeholders and should be replaced with your Ids. @@ -52,7 +52,7 @@ pbjs.que.push(function() { }); ``` -**Note**: You may also include `jwTargeting` information in the prebid config's `fpd.context.data`. Information provided in the adUnit will always supersede, and information in the config will be used as a fallback. +**Note**: You may also include `jwTargeting` information in the prebid config's `ortb2.site.ext.data`. Information provided in the adUnit will always supersede, and information in the config will be used as a fallback. ##Prefetching In order to prefetch targeting information for certain media, include the media IDs in the `jwplayerDataProvider` var and set `waitForIt` to `true`: @@ -117,3 +117,7 @@ To view an example: `http://localhost:9999/integrationExamples/gpt/jwplayerRtdProvider_example.html` **Note:** the mediaIds in the example are placeholder values; replace them with your existing IDs. + +#Maintainer info + +Maintained by JW Player. For any questions, comments or feedback please contact Karim Mourra, karim@jwplayer.com diff --git a/modules/kargoBidAdapter.js b/modules/kargoBidAdapter.js index 03767efc135..610f4558139 100644 --- a/modules/kargoBidAdapter.js +++ b/modules/kargoBidAdapter.js @@ -3,17 +3,19 @@ import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import { getStorageManager } from '../src/storageManager.js'; -const storage = getStorageManager(); const BIDDER_CODE = 'kargo'; const HOST = 'https://krk.kargo.com'; -const SYNC = 'https://crb.kargo.com/api/v1/initsyncrnd/{UUID}?seed={SEED}&idx={INDEX}'; +const SYNC = 'https://crb.kargo.com/api/v1/initsyncrnd/{UUID}?seed={SEED}&idx={INDEX}&gdpr={GDPR}&gdpr_consent={GDPR_CONSENT}&us_privacy={US_PRIVACY}'; const SYNC_COUNT = 5; +const GVLID = 972; +const storage = getStorageManager(GVLID, BIDDER_CODE); let sessionId, lastPageUrl, requestCounter; export const spec = { + gvlid: GVLID, code: BIDDER_CODE, isBidRequestValid: function(bid) { if (!bid || !bid.params) { @@ -48,7 +50,7 @@ export const spec = { bidIDs: bidIds, bidSizes: bidSizes, prebidRawBidRequests: validBidRequests - }, spec._getAllMetadata(tdid, bidderRequest.uspConsent)); + }, spec._getAllMetadata(tdid, bidderRequest.uspConsent, bidderRequest.gdprConsent)); const encodedParams = encodeURIComponent(JSON.stringify(transformedParams)); return Object.assign({}, bidderRequest, { method: 'GET', @@ -85,15 +87,25 @@ export const spec = { } return bidResponses; }, - getUserSyncs: function(syncOptions) { + getUserSyncs: function(syncOptions, responses, gdprConsent, usPrivacy) { const syncs = []; const seed = spec._generateRandomUuid(); const clientId = spec._getClientId(); + var gdpr = (gdprConsent && gdprConsent.gdprApplies) ? 1 : 0; + var gdprConsentString = (gdprConsent && gdprConsent.consentString) ? gdprConsent.consentString : ''; + // don't sync if opted out via usPrivacy + if (typeof usPrivacy == 'string' && usPrivacy.length == 4 && usPrivacy[0] == 1 && usPrivacy[2] == 'Y') { + return syncs; + } if (syncOptions.iframeEnabled && seed && clientId) { for (let i = 0; i < SYNC_COUNT; i++) { syncs.push({ type: 'iframe', - url: SYNC.replace('{UUID}', clientId).replace('{SEED}', seed).replace('{INDEX}', i) + url: SYNC.replace('{UUID}', clientId).replace('{SEED}', seed) + .replace('{INDEX}', i) + .replace('{GDPR}', gdpr) + .replace('{GDPR_CONSENT}', gdprConsentString) + .replace('{US_PRIVACY}', usPrivacy || '') }); } } @@ -183,7 +195,7 @@ export const spec = { } }, - _getUserIds(tdid, usp) { + _getUserIds(tdid, usp, gdpr) { const crb = spec._getCrb(); const userIds = { kargoID: crb.userId, @@ -192,6 +204,16 @@ export const spec = { optOut: crb.optOut, usp: usp }; + + try { + if (gdpr) { + userIds['gdpr'] = { + consent: gdpr.consentString || '', + applies: !!gdpr.gdprApplies, + } + } + } catch (e) { + } if (tdid) { userIds.tdID = tdid; } @@ -203,9 +225,9 @@ export const spec = { return crb.clientId; }, - _getAllMetadata(tdid, usp) { + _getAllMetadata(tdid, usp, gdpr) { return { - userIDs: spec._getUserIds(tdid, usp), + userIDs: spec._getUserIds(tdid, usp, gdpr), krux: spec._getKrux(), pageURL: window.location.href, rawCRB: spec._readCookie('krg_crb'), diff --git a/modules/kubientBidAdapter.js b/modules/kubientBidAdapter.js new file mode 100644 index 00000000000..8f6ea53ecce --- /dev/null +++ b/modules/kubientBidAdapter.js @@ -0,0 +1,111 @@ +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER} from '../src/mediaTypes.js'; +import * as utils from '../src/utils.js'; + +const BIDDER_CODE = 'kubient'; +const END_POINT = 'https://kssp.kbntx.ch/pbjs'; +const VERSION = '1.0'; +const VENDOR_ID = 794; +export const spec = { + code: BIDDER_CODE, + gvlid: VENDOR_ID, + supportedMediaTypes: [BANNER], + isBidRequestValid: function (bid) { + return !!(bid && bid.params); + }, + buildRequests: function (validBidRequests, bidderRequest) { + if (!validBidRequests || !bidderRequest) { + return; + } + const result = validBidRequests.map(function (bid) { + let data = { + v: VERSION, + requestId: bid.bidderRequestId, + adSlots: [{ + bidId: bid.bidId, + zoneId: bid.params.zoneid || '', + floor: bid.params.floor || 0.0, + sizes: bid.sizes || [], + schain: bid.schain || {}, + mediaTypes: bid.mediaTypes + }], + referer: (bidderRequest.refererInfo && bidderRequest.refererInfo.referer) ? bidderRequest.refererInfo.referer : null, + tmax: bidderRequest.timeout, + gdpr: (bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies) ? 1 : 0, + consent: (bidderRequest.gdprConsent && bidderRequest.gdprConsent.consentString) ? bidderRequest.gdprConsent.consentString : null, + consentGiven: kubientGetConsentGiven(bidderRequest.gdprConsent), + uspConsent: bidderRequest.uspConsent + }; + return { + method: 'POST', + url: END_POINT, + data: JSON.stringify(data) + }; + }); + return result; + }, + interpretResponse: function interpretResponse(serverResponse, request) { + if (!serverResponse || !serverResponse.body || !serverResponse.body.seatbid) { + return []; + } + let bidResponses = []; + serverResponse.body.seatbid.forEach(seatbid => { + let bids = seatbid.bid || []; + bids.forEach(bid => { + bidResponses.push({ + requestId: bid.bidId, + cpm: bid.price, + currency: bid.cur, + width: bid.w, + height: bid.h, + creativeId: bid.creativeId, + netRevenue: bid.netRevenue, + ttl: bid.ttl, + ad: bid.adm + }); + }); + }); + return bidResponses; + }, + getUserSyncs: function (syncOptions, serverResponses, gdprConsent, uspConsent) { + const syncs = []; + let gdprParams = ''; + if (gdprConsent && typeof gdprConsent.consentString === 'string') { + gdprParams = `?consent_str=${gdprConsent.consentString}`; + if (typeof gdprConsent.gdprApplies === 'boolean') { + gdprParams = gdprParams + `&gdpr=${Number(gdprConsent.gdprApplies)}`; + } + gdprParams = gdprParams + `&consent_given=` + kubientGetConsentGiven(gdprConsent); + } + if (syncOptions.iframeEnabled) { + syncs.push({ + type: 'iframe', + url: 'https://kdmp.kbntx.ch/init.html' + gdprParams + }); + } + if (syncOptions.pixelEnabled) { + syncs.push({ + type: 'image', + url: 'https://kdmp.kbntx.ch/init.png' + gdprParams + }); + } + return syncs; + } +}; + +function kubientGetConsentGiven(gdprConsent) { + let consentGiven = 0; + if (typeof gdprConsent !== 'undefined') { + let apiVersion = utils.deepAccess(gdprConsent, `apiVersion`); + switch (apiVersion) { + case 1: + consentGiven = utils.deepAccess(gdprConsent, `vendorData.vendorConsents.${VENDOR_ID}`) ? 1 : 0; + break; + case 2: + consentGiven = utils.deepAccess(gdprConsent, `vendorData.vendor.consents.${VENDOR_ID}`) ? 1 : 0; + break; + } + } + return consentGiven; +} +registerBidder(spec); diff --git a/modules/kubientBidAdapter.md b/modules/kubientBidAdapter.md new file mode 100644 index 00000000000..9f3e1d5f52e --- /dev/null +++ b/modules/kubientBidAdapter.md @@ -0,0 +1,26 @@ +# Overview +​ +**Module Name**: Kubient Bidder Adapter +**Module Type**: Bidder Adapter +**Maintainer**: artem.aleksashkin@kubient.com +​ +# Description +​ +Connects to Kubient KSSP demand source to fetch bids. +​ +# Test Parameters +``` + var adUnits = [{ + code: 'banner-ad-div', + mediaTypes: { + banner: { + sizes: [[300, 250],[728, 90]], + } + }, + bids: [{ + "bidder": "kubient", + "params": { + "zoneid": "5fbb948f1e22b", + } + }] + }]; diff --git a/modules/lemmaBidAdapter.js b/modules/lemmaBidAdapter.js index 5941802f97d..c7440743d2c 100644 --- a/modules/lemmaBidAdapter.js +++ b/modules/lemmaBidAdapter.js @@ -62,18 +62,6 @@ export var spec = { }, getUserSyncs: (syncOptions, responses, gdprConsent, uspConsent) => { let syncurl = USER_SYNC + 'pid=' + pubId; - - // Attaching GDPR Consent Params in UserSync url - if (gdprConsent) { - syncurl += '&gdpr=' + (gdprConsent.gdprApplies ? 1 : 0); - syncurl += '&gdpr_consent=' + encodeURIComponent(gdprConsent.consentString || ''); - } - - // CCPA - if (uspConsent) { - syncurl += '&us_privacy=' + encodeURIComponent(uspConsent); - } - if (syncOptions.iframeEnabled) { return [{ type: 'iframe', @@ -96,6 +84,30 @@ function _initConf(refererInfo) { return conf; } +function _setFloor(impObj, bid) { + let bidFloor = -1; + // get lowest floor from floorModule + if (typeof bid.getFloor === 'function') { + [BANNER, VIDEO].forEach(mediaType => { + if (impObj.hasOwnProperty(mediaType)) { + let floorInfo = bid.getFloor({ currency: impObj.bidfloorcur, mediaType: mediaType, size: '*' }); + if (typeof floorInfo === 'object' && floorInfo.currency === impObj.bidfloorcur && !isNaN(parseInt(floorInfo.floor))) { + let mediaTypeFloor = parseFloat(floorInfo.floor); + bidFloor = (bidFloor == -1 ? mediaTypeFloor : Math.min(mediaTypeFloor, bidFloor)) + } + } + }); + } + // get highest from impObj.bidfllor and floor from floor module + // as we are using Math.max, it is ok if we have not got any floor from floorModule, then value of bidFloor will be -1 + if (impObj.bidfloor) { + bidFloor = Math.max(bidFloor, impObj.bidfloor) + } + + // assign value only if bidFloor is > 0 + impObj.bidfloor = ((!isNaN(bidFloor) && bidFloor > 0) ? bidFloor : undefined); +} + function parseRTBResponse(request, response) { var bidResponses = []; try { @@ -122,9 +134,9 @@ function parseRTBResponse(request, response) { newBid.dealId = bid.dealid; } if (req.imp && req.imp.length > 0) { - newBid.mediaType = req.mediaType; req.imp.forEach(robj => { if (bid.impid === robj.id) { + _checkMediaType(bid.adm, newBid); switch (newBid.mediaType) { case BANNER: break; @@ -277,10 +289,6 @@ function _getDeviceObject(request) { function setOtherParams(request, ortbRequest) { var params = request && request.params ? request.params : null; - if (request && request.gdprConsent) { - ortbRequest.regs = { ext: { gdpr: request.gdprConsent.gdprApplies ? 1 : 0 } }; - ortbRequest.user = { ext: { consent: request.gdprConsent.consentString } }; - } if (params) { ortbRequest.tmax = params.tmax; ortbRequest.bcat = params.bcat; @@ -368,11 +376,9 @@ function _getImpressionObject(bid) { secure: window.location.protocol === 'https:' ? 1 : 0, bidfloorcur: params.currency ? params.currency : DEFAULT_CURRENCY }; - if (params.bidFloor) { impression.bidfloor = params.bidFloor; } - if (bid.hasOwnProperty('mediaTypes')) { for (mediaTypes in bid.mediaTypes) { switch (mediaTypes) { @@ -408,7 +414,7 @@ function _getImpressionObject(bid) { } impression.banner = bObj; } - + _setFloor(impression, bid); return impression.hasOwnProperty(BANNER) || impression.hasOwnProperty(VIDEO) ? impression : undefined; } @@ -424,4 +430,13 @@ function parse(rawResp) { return null; } +function _checkMediaType(adm, newBid) { + // Create a regex here to check the strings + var videoRegex = new RegExp(/VAST.*version/); + if (videoRegex.test(adm)) { + newBid.mediaType = VIDEO; + } else { + newBid.mediaType = BANNER; + } +} registerBidder(spec); diff --git a/modules/liveIntentIdSystem.js b/modules/liveIntentIdSystem.js index 4f18c73ad2a..5a955eefa92 100644 --- a/modules/liveIntentIdSystem.js +++ b/modules/liveIntentIdSystem.js @@ -8,9 +8,10 @@ import * as utils from '../src/utils.js'; import { triggerPixel } from '../src/utils.js'; import { ajaxBuilder } from '../src/ajax.js'; import { submodule } from '../src/hook.js'; -import { LiveConnect } from 'live-connect-js/cjs/live-connect.js'; +import { LiveConnect } from 'live-connect-js/esm/initializer.js'; import { gdprDataHandler, uspDataHandler } from '../src/adapterManager.js'; import { getStorageManager } from '../src/storageManager.js'; +import { MinimalLiveConnect } from 'live-connect-js/esm/minimal-live-connect.js'; const MODULE_NAME = 'liveIntentId'; export const storage = getStorageManager(null, MODULE_NAME); @@ -42,26 +43,18 @@ export function reset() { if (window && window.liQ) { window.liQ = []; } + liveIntentIdSubmodule.setModuleMode(null) eventFired = false; liveConnect = null; } function parseLiveIntentCollectorConfig(collectConfig) { const config = {}; - if (collectConfig) { - if (collectConfig.appId) { - config.appId = collectConfig.appId; - } - if (collectConfig.fpiStorageStrategy) { - config.storageStrategy = collectConfig.fpiStorageStrategy; - } - if (collectConfig.fpiExpirationDays) { - config.expirationDays = collectConfig.fpiExpirationDays; - } - if (collectConfig.collectorUrl) { - config.collectorUrl = collectConfig.collectorUrl; - } - } + collectConfig = collectConfig || {} + collectConfig.appId && (config.appId = collectConfig.appId); + collectConfig.fpiStorageStrategy && (config.storageStrategy = collectConfig.fpiStorageStrategy); + collectConfig.fpiExpirationDays && (config.expirationDays = collectConfig.fpiExpirationDays); + collectConfig.collectorUrl && (config.collectorUrl = collectConfig.collectorUrl); return config; } @@ -90,9 +83,6 @@ function initializeLiveConnect(configParams) { liveConnectConfig.wrapperName = 'prebid'; liveConnectConfig.identityResolutionConfig = identityResolutionConfig; liveConnectConfig.identifiersToResolve = configParams.identifiersToResolve || []; - if (configParams.emailHash) { - liveConnectConfig.eventSource = { hash: configParams.emailHash } - } const usPrivacyString = uspDataHandler.getConsentData(); if (usPrivacyString) { liveConnectConfig.usPrivacyString = usPrivacyString; @@ -105,7 +95,10 @@ function initializeLiveConnect(configParams) { // The second param is the storage object, LS & Cookie manipulation uses PBJS utils. // The third param is the ajax and pixel object, the ajax and pixel use PBJS utils. - liveConnect = LiveConnect(liveConnectConfig, storage, calls); + liveConnect = liveIntentIdSubmodule.getInitializer()(liveConnectConfig, storage, calls); + if (configParams.emailHash) { + liveConnect.push({ hash: configParams.emailHash }) + } return liveConnect; } @@ -118,12 +111,20 @@ function tryFireEvent() { /** @type {Submodule} */ export const liveIntentIdSubmodule = { + moduleMode: process.env.LiveConnectMode, /** * used to link submodule with config * @type {string} */ name: MODULE_NAME, + setModuleMode(mode) { + this.moduleMode = mode + }, + getInitializer() { + return this.moduleMode === 'minimal' ? MinimalLiveConnect : LiveConnect + }, + /** * decode the stored id value for passing to bid requests. Note that lipb object is a wrapper for everything, and * internally it could contain more data other than `lipbid`(e.g. `segments`) depending on the `partner` and @@ -136,7 +137,7 @@ export const liveIntentIdSubmodule = { decode(value, config) { const configParams = (config && config.params) || {}; function composeIdObject(value) { - const base = { 'lipbid': value['unifiedId'] }; + const base = { 'lipbid': value.unifiedId }; delete value.unifiedId; return { 'lipb': { ...base, ...value } }; } @@ -165,15 +166,7 @@ export const liveIntentIdSubmodule = { const result = function(callback) { liveConnect.resolve( response => { - let responseObj = {}; - if (response) { - try { - responseObj = JSON.parse(response); - } catch (error) { - utils.logError(error); - } - } - callback(responseObj); + callback(response); }, error => { utils.logError(`${MODULE_NAME}: ID fetch encountered an error: `, error); diff --git a/modules/livewrappedBidAdapter.js b/modules/livewrappedBidAdapter.js index 512fc775d95..2840da8dda6 100644 --- a/modules/livewrappedBidAdapter.js +++ b/modules/livewrappedBidAdapter.js @@ -14,6 +14,7 @@ const VERSION = '1.4'; export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, NATIVE, VIDEO], + gvlid: 919, /** * Determines whether or not the given bid request is valid. @@ -258,34 +259,10 @@ function getAdblockerRecovered() { } catch (e) {} } -function AddExternalUserId(eids, value, source, atype, rtiPartner) { - if (utils.isStr(value)) { - var eid = { - source, - uids: [{ - id: value, - atype - }] - }; - - if (rtiPartner) { - eid.uids[0] = {ext: {rtiPartner}}; - } - - eids.push(eid); - } -} - function handleEids(bidRequests) { - let eids = []; const bidRequest = bidRequests[0]; - if (bidRequest && bidRequest.userId) { - AddExternalUserId(eids, utils.deepAccess(bidRequest, `userId.pubcid`), 'pubcid.org', 1); // Also add this to eids - AddExternalUserId(eids, utils.deepAccess(bidRequest, `userId.id5id.uid`), 'id5-sync.com', 1); - AddExternalUserId(eids, utils.deepAccess(bidRequest, `userId.criteoId`), 'criteo.com', 1); - } - if (eids.length > 0) { - return {user: {ext: {eids}}}; + if (bidRequest && bidRequest.userIdAsEids) { + return {user: {ext: {eids: bidRequest.userIdAsEids}}}; } return undefined; diff --git a/modules/loganBidAdapter.js b/modules/loganBidAdapter.js new file mode 100644 index 00000000000..e55876de675 --- /dev/null +++ b/modules/loganBidAdapter.js @@ -0,0 +1,118 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import * as utils from '../src/utils.js'; + +const BIDDER_CODE = 'logan'; +const AD_URL = 'https://USeast2.logan.ai/?c=o&m=multi'; +const SYNC_URL = 'https://ssp-cookie.logan.ai/html?src=pbjs' + +function isBidResponseValid(bid) { + if (!bid.requestId || !bid.cpm || !bid.creativeId || + !bid.ttl || !bid.currency) { + return false; + } + switch (bid.mediaType) { + case BANNER: + return Boolean(bid.width && bid.height && bid.ad); + case VIDEO: + return Boolean(bid.vastUrl); + case NATIVE: + return Boolean(bid.native && bid.native.impressionTrackers); + default: + return false; + } +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + + isBidRequestValid: (bid) => { + return Boolean(bid.bidId && bid.params && !isNaN(parseInt(bid.params.placementId))); + }, + + buildRequests: (validBidRequests = [], bidderRequest) => { + const winTop = utils.getWindowTop(); + const location = winTop.location; + const placements = []; + const request = { + 'deviceWidth': winTop.screen.width, + 'deviceHeight': winTop.screen.height, + 'language': (navigator && navigator.language) ? navigator.language.split('-')[0] : '', + 'secure': 1, + 'host': location.host, + 'page': location.pathname, + 'placements': placements + }; + + if (bidderRequest) { + if (bidderRequest.uspConsent) { + request.ccpa = bidderRequest.uspConsent; + } + if (bidderRequest.gdprConsent) { + request.gdpr = bidderRequest.gdprConsent + } + } + + const len = validBidRequests.length; + for (let i = 0; i < len; i++) { + const bid = validBidRequests[i]; + const placement = { + placementId: bid.params.placementId, + bidId: bid.bidId, + schain: bid.schain || {}, + }; + const mediaType = bid.mediaTypes + + if (mediaType && mediaType[BANNER] && mediaType[BANNER].sizes) { + placement.sizes = mediaType[BANNER].sizes; + placement.traffic = BANNER; + } else if (mediaType && mediaType[VIDEO] && mediaType[VIDEO].playerSize) { + placement.wPlayer = mediaType[VIDEO].playerSize[0]; + placement.hPlayer = mediaType[VIDEO].playerSize[1]; + placement.traffic = VIDEO; + } else if (mediaType && mediaType[NATIVE]) { + placement.native = mediaType[NATIVE]; + placement.traffic = NATIVE; + } + placements.push(placement); + } + + return { + method: 'POST', + url: AD_URL, + data: request + }; + }, + + interpretResponse: (serverResponse) => { + let response = []; + for (let i = 0; i < serverResponse.body.length; i++) { + let resItem = serverResponse.body[i]; + if (isBidResponseValid(resItem)) { + response.push(resItem); + } + } + return response; + }, + + getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => { + let syncUrl = SYNC_URL + if (gdprConsent && gdprConsent.consentString) { + if (typeof gdprConsent.gdprApplies === 'boolean') { + syncUrl += `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; + } else { + syncUrl += `&gdpr=0&gdpr_consent=${gdprConsent.consentString}`; + } + } + if (uspConsent && uspConsent.consentString) { + syncUrl += `&ccpa_consent=${uspConsent.consentString}`; + } + return [{ + type: 'iframe', + url: syncUrl + }]; + } +}; + +registerBidder(spec); diff --git a/modules/loganBidAdapter.md b/modules/loganBidAdapter.md new file mode 100644 index 00000000000..3c628cdacc2 --- /dev/null +++ b/modules/loganBidAdapter.md @@ -0,0 +1,72 @@ +# Overview + +``` +Module Name: logan Bidder Adapter +Module Type: logan Bidder Adapter +Maintainer: support@logan.ai +``` + +# Description + +Module that connects to logan demand sources + +# Test Parameters +``` + var adUnits = [ + { + code:'1', + mediaTypes:{ + banner: { + sizes: [[300, 250]], + } + }, + bids:[ + { + bidder: 'logan', + params: { + placementId: 0 + } + } + ] + }, + { + code:'1', + mediaTypes:{ + video: { + playerSize: [640, 480], + context: 'instream' + } + }, + bids:[ + { + bidder: 'logan', + params: { + placementId: 0 + } + } + ] + }, + { + code:'1', + mediaTypes:{ + native: { + title: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + }, + bids:[ + { + bidder: 'logan', + params: { + placementId: 0 + } + } + ] + } + ]; +``` \ No newline at end of file diff --git a/modules/lotamePanoramaIdSystem.js b/modules/lotamePanoramaIdSystem.js index 5c3a9a16b3a..6d36687b4e8 100644 --- a/modules/lotamePanoramaIdSystem.js +++ b/modules/lotamePanoramaIdSystem.js @@ -16,8 +16,11 @@ const MODULE_NAME = 'lotamePanoramaId'; const NINE_MONTHS_MS = 23328000 * 1000; const DAYS_TO_CACHE = 7; const DAY_MS = 60 * 60 * 24 * 1000; +const MISSING_CORE_CONSENT = 111; +const GVLID = 95; -export const storage = getStorageManager(null, MODULE_NAME); +export const storage = getStorageManager(GVLID, MODULE_NAME); +let cookieDomain; /** * Set the Lotame First Party Profile ID in the first party namespace @@ -26,7 +29,14 @@ export const storage = getStorageManager(null, MODULE_NAME); function setProfileId(profileId) { if (storage.cookiesAreEnabled()) { let expirationDate = new Date(utils.timestamp() + NINE_MONTHS_MS).toUTCString(); - storage.setCookie(KEY_PROFILE, profileId, expirationDate, 'Lax', undefined, undefined); + storage.setCookie( + KEY_PROFILE, + profileId, + expirationDate, + 'Lax', + cookieDomain, + undefined + ); } if (storage.hasLocalStorage()) { storage.setDataInLocalStorage(KEY_PROFILE, profileId, undefined); @@ -88,7 +98,7 @@ function saveLotameCache( value, expirationDate, 'Lax', - undefined, + cookieDomain, undefined ); } @@ -115,7 +125,7 @@ function getLotameLocalCache() { try { const rawExpiry = getFromStorage(KEY_EXPIRY); if (utils.isStr(rawExpiry)) { - cache.expiryTimestampMs = parseInt(rawExpiry, 0); + cache.expiryTimestampMs = parseInt(rawExpiry, 10); } } catch (error) { utils.logError(error); @@ -132,7 +142,14 @@ function clearLotameCache(key) { if (key) { if (storage.cookiesAreEnabled()) { let expirationDate = new Date(0).toUTCString(); - storage.setCookie(key, '', expirationDate, 'Lax', undefined, undefined); + storage.setCookie( + key, + '', + expirationDate, + 'Lax', + cookieDomain, + undefined + ); } if (storage.hasLocalStorage()) { storage.removeDataFromLocalStorage(key, undefined); @@ -141,13 +158,18 @@ function clearLotameCache(key) { } /** @type {Submodule} */ export const lotamePanoramaIdSubmodule = { - /** * used to link submodule with config * @type {string} */ name: MODULE_NAME, + /** + * Vendor id of Lotame + * @type {Number} + */ + gvlid: GVLID, + /** * Decode the stored id value for passing to bid requests * @function decode @@ -156,7 +178,7 @@ export const lotamePanoramaIdSubmodule = { * @returns {(Object|undefined)} */ decode(value, config) { - return utils.isStr(value) ? { 'lotamePanoramaId': value } : undefined; + return utils.isStr(value) ? { lotamePanoramaId: value } : undefined; }, /** @@ -168,13 +190,14 @@ export const lotamePanoramaIdSubmodule = { * @returns {IdResponse|undefined} */ getId(config, consentData, cacheIdObj) { + cookieDomain = lotamePanoramaIdSubmodule.findRootDomain(); let localCache = getLotameLocalCache(); let refreshNeeded = Date.now() > localCache.expiryTimestampMs; if (!refreshNeeded) { return { - id: localCache.data + id: localCache.data, }; } @@ -183,14 +206,25 @@ export const lotamePanoramaIdSubmodule = { const resolveIdFunction = function (callback) { let queryParams = {}; if (storedUserId) { - queryParams.fp = storedUserId + queryParams.fp = storedUserId; } - if (consentData && utils.isBoolean(consentData.gdprApplies)) { - queryParams.gdpr_applies = consentData.gdprApplies; - if (consentData.gdprApplies) { - queryParams.gdpr_consent = consentData.consentString; + let consentString; + if (consentData) { + if (utils.isBoolean(consentData.gdprApplies)) { + queryParams.gdpr_applies = consentData.gdprApplies; } + consentString = consentData.consentString; + } + // If no consent string, try to read it from 1st party cookies + if (!consentString) { + consentString = getFromStorage('eupubconsent-v2'); + } + if (!consentString) { + consentString = getFromStorage('euconsent-v2'); + } + if (consentString) { + queryParams.gdpr_consent = consentString; } const url = utils.buildUrl({ protocol: 'https', @@ -205,10 +239,17 @@ export const lotamePanoramaIdSubmodule = { if (response) { try { let responseObj = JSON.parse(response); - saveLotameCache(KEY_EXPIRY, responseObj.expiry_ts); + const shouldUpdateProfileId = !( + utils.isArray(responseObj.errors) && + responseObj.errors.indexOf(MISSING_CORE_CONSENT) !== -1 + ); + + saveLotameCache(KEY_EXPIRY, responseObj.expiry_ts, responseObj.expiry_ts); if (utils.isStr(responseObj.profile_id)) { - setProfileId(responseObj.profile_id); + if (shouldUpdateProfileId) { + setProfileId(responseObj.profile_id); + } if (utils.isStr(responseObj.core_id)) { saveLotameCache( @@ -221,7 +262,9 @@ export const lotamePanoramaIdSubmodule = { clearLotameCache(KEY_ID); } } else { - clearLotameCache(KEY_PROFILE); + if (shouldUpdateProfileId) { + clearLotameCache(KEY_PROFILE); + } clearLotameCache(KEY_ID); } } catch (error) { @@ -233,7 +276,7 @@ export const lotamePanoramaIdSubmodule = { undefined, { method: 'GET', - withCredentials: true + withCredentials: true, } ); }; diff --git a/modules/luponmediaBidAdapter.js b/modules/luponmediaBidAdapter.js index 4f7fd2ae1a0..29b54f77fbb 100644 --- a/modules/luponmediaBidAdapter.js +++ b/modules/luponmediaBidAdapter.js @@ -279,8 +279,9 @@ function newOrtbBidRequest(bidRequest, bidderRequest, currentImps) { utils.deepSetValue(data, 'source.ext.schain', bidRequest.schain); } - const siteData = Object.assign({}, bidRequest.params.inventory, config.getConfig('fpd.context')); - const userData = Object.assign({}, bidRequest.params.visitor, config.getConfig('fpd.user')); + const fpd = config.getLegacyFpd(config.getConfig('ortb2')) || {}; + const siteData = Object.assign({}, bidRequest.params.inventory, fpd.context); + const userData = Object.assign({}, bidRequest.params.visitor, fpd.user); if (!utils.isEmpty(siteData) || !utils.isEmpty(userData)) { const bidderData = { @@ -301,7 +302,7 @@ function newOrtbBidRequest(bidRequest, bidderRequest, currentImps) { utils.deepSetValue(data, 'ext.prebid.bidderconfig.0', bidderData); } - const pbAdSlot = utils.deepAccess(bidRequest, 'fpd.context.pbAdSlot'); + const pbAdSlot = utils.deepAccess(bidRequest, 'ortb2Imp.ext.data.pbadslot'); if (typeof pbAdSlot === 'string' && pbAdSlot) { utils.deepSetValue(data.imp[0].ext, 'context.data.adslot', pbAdSlot); } diff --git a/modules/malltvBidAdapter.js b/modules/malltvBidAdapter.js index 846935a5522..7deffe6c07a 100644 --- a/modules/malltvBidAdapter.js +++ b/modules/malltvBidAdapter.js @@ -31,6 +31,7 @@ export const spec = { let bidderRequestId = ''; let url = ''; let contents = []; + let data = {}; let placements = validBidRequests.map(bidRequest => { if (!propertyId) { propertyId = bidRequest.params.propertyId; } @@ -38,7 +39,8 @@ export const spec = { if (!storageId && bidRequest.params) { storageId = bidRequest.params.storageId || ''; } if (!bidderRequestId) { bidderRequestId = bidRequest.bidderRequestId; } if (!url && bidderRequest) { url = bidderRequest.refererInfo.referer; } - if (!contents.length && bidRequest.params.contents && bidRequest.params.contents.length) { contents = bidRequest.params.contents } + if (!contents.length && bidRequest.params.contents && bidRequest.params.contents.length) { contents = bidRequest.params.contents; } + if (Object.keys(data).length === 0 && bidRequest.params.data && Object.keys(bidRequest.params.data).length !== 0) { data = bidRequest.params.data; } let adUnitId = bidRequest.adUnitCode; let placementId = bidRequest.params.placementId; @@ -49,6 +51,8 @@ export const spec = { adUnitId: adUnitId, placementId: placementId, bidid: bidRequest.bidId, + count: bidRequest.params.count, + skipTime: bidRequest.params.skipTime }; }); @@ -59,7 +63,8 @@ export const spec = { url: url, requestid: bidderRequestId, placements: placements, - contents: contents + contents: contents, + data: data } return [{ diff --git a/modules/malltvBidAdapter.md b/modules/malltvBidAdapter.md index 3d419fa0916..e32eb54f90f 100644 --- a/modules/malltvBidAdapter.md +++ b/modules/malltvBidAdapter.md @@ -1,45 +1,68 @@ # Overview -Module Name: MallTV Bidder Adapter Module -Type: Bidder Adapter -Maintainer: drilon@gjirafa.com +Module Name: MallTV Bidder Adapter Module + +Type: Bidder Adapter + +Maintainer: arditb@gjirafa.com # Description MallTV Bidder Adapter for Prebid.js. # Test Parameters +```js var adUnits = [ { code: 'test-div', mediaTypes: { banner: { - sizes: [[300, 250], [300, 300]] + sizes: [ + [300, 250], + [300, 300] + ] } }, - bids: [ - { - bidder: 'malltv', - params: { - propertyId: '105134', - placementId: '846832' + bids: [{ + bidder: 'malltv', + params: { + propertyId: '105134', //Required + placementId: '846832', //Required + data: { //Optional + catalogs: [{ + catalogId: 9, + items: ["193", "4", "1"] + }], + inventory: { + category: ["tech"], + query: ["iphone 12"] + } } } - ] + }] }, { code: 'test-div', mediaTypes: { - video: { + video: { context: 'instream' - } + } }, - bids: [ - { - bidder: 'malltv', - params: { - propertyId: '105134', - placementId: '846841' + bids: [{ + bidder: 'malltv', + params: { + propertyId: '105134', //Required + placementId: '846832', //Required + data: { //Optional + catalogs: [{ + catalogId: 9, + items: ["193", "4", "1"] + }], + inventory: { + category: ["tech"], + query: ["iphone 12"] + } } } - ] + }] } ]; +``` diff --git a/modules/marsmediaBidAdapter.js b/modules/marsmediaBidAdapter.js index 1ce2558b8de..bb1763ebb2e 100644 --- a/modules/marsmediaBidAdapter.js +++ b/modules/marsmediaBidAdapter.js @@ -3,6 +3,7 @@ import * as utils from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import {config} from '../src/config.js'; function MarsmediaAdapter() { this.code = 'marsmedia'; @@ -16,7 +17,7 @@ function MarsmediaAdapter() { let SUPPORTED_VIDEO_API = [1, 2, 5]; let slotsToBids = {}; let that = this; - let version = '2.3'; + let version = '2.4'; this.isBidRequestValid = function (bid) { return !!(bid.params && bid.params.zoneId); @@ -53,6 +54,7 @@ function MarsmediaAdapter() { if (!(impObj.banner || impObj.video)) { continue; } + impObj.bidfloor = _getFloor(BRs[i]); impObj.ext = frameExt(BRs[i]); impList.push(impObj); } @@ -153,9 +155,31 @@ function MarsmediaAdapter() { } function frameExt(bid) { - return { - bidder: { - zoneId: bid.params['zoneId'] + if ((bid.mediaTypes && bid.mediaTypes.banner && bid.mediaTypes.banner.sizes)) { + let bidSizes = (bid.mediaTypes && bid.mediaTypes.banner && bid.mediaTypes.banner.sizes) || bid.sizes; + bidSizes = ((utils.isArray(bidSizes) && utils.isArray(bidSizes[0])) ? bidSizes : [bidSizes]); + bidSizes = bidSizes.filter(size => utils.isArray(size)); + const processedSizes = bidSizes.map(size => ({w: parseInt(size[0], 10), h: parseInt(size[1], 10)})); + + const element = document.getElementById(bid.adUnitCode); + const minSize = _getMinSize(processedSizes); + const viewabilityAmount = _isViewabilityMeasurable(element) + ? _getViewability(element, utils.getWindowTop(), minSize) + : 'na'; + const viewabilityAmountRounded = isNaN(viewabilityAmount) ? viewabilityAmount : Math.round(viewabilityAmount); + + return { + bidder: { + zoneId: bid.params['zoneId'] + }, + viewability: viewabilityAmountRounded + } + } else { + return { + bidder: { + zoneId: bid.params['zoneId'] + }, + viewability: 'na' } } } @@ -180,12 +204,15 @@ function MarsmediaAdapter() { } }; if (BRs[0].schain) { - bid.source = { - 'ext': { - 'schain': BRs[0].schain - } - } + utils.deepSetValue(bid, 'source.ext.schain', BRs[0].schain); + } + if (bidderRequest.uspConsent) { + utils.deepSetValue(bid, 'regs.ext.us_privacy', bidderRequest.uspConsent) } + if (config.getConfig('coppa') === true) { + utils.deepSetValue(bid, 'regs.coppa', config.getConfig('coppa') & 1) + } + return bid; } @@ -241,12 +268,6 @@ function MarsmediaAdapter() { sendbeacon(bid, 20) }; - function sendbeacon(bid, type) { - const bidString = JSON.stringify(bid); - const encodedBuf = window.btoa(bidString); - utils.triggerPixel('https://ping-hqx-1.go2speed.media/notification/rtb/beacon/?bt=' + type + '&bid=3mhdom&hb_j=' + encodedBuf, null); - } - this.interpretResponse = function (serverResponse) { let responses = serverResponse.body || []; let bids = []; @@ -295,6 +316,126 @@ function MarsmediaAdapter() { return bids; }; + + function sendbeacon(bid, type) { + const bidString = JSON.stringify(bid); + const encodedBuf = window.btoa(bidString); + utils.triggerPixel('https://ping-hqx-1.go2speed.media/notification/rtb/beacon/?bt=' + type + '&bid=3mhdom&hb_j=' + encodedBuf, null); + } + + /** + * Gets bidfloor + * @param {Object} bid + * @returns {Number} floor + */ + function _getFloor (bid) { + const curMediaType = bid.mediaTypes.video ? 'video' : 'banner'; + let floor = 0; + + if (typeof bid.getFloor === 'function') { + const floorInfo = bid.getFloor({ + currency: 'USD', + mediaType: curMediaType, + size: '*' + }); + + if (typeof floorInfo === 'object' && + floorInfo.currency === 'USD' && + !isNaN(parseFloat(floorInfo.floor))) { + floor = floorInfo.floor; + } + } + + return floor; + } + + function _getMinSize(sizes) { + return sizes.reduce((min, size) => size.h * size.w < min.h * min.w ? size : min); + } + + function _isViewabilityMeasurable(element) { + return !_isIframe() && element !== null; + } + + function _isIframe() { + try { + return utils.getWindowSelf() !== utils.getWindowTop(); + } catch (e) { + return true; + } + } + + function _getViewability(element, topWin, { w, h } = {}) { + return topWin.document.visibilityState === 'visible' + ? _getPercentInView(element, topWin, { w, h }) + : 0; + } + + function _getPercentInView(element, topWin, { w, h } = {}) { + const elementBoundingBox = _getBoundingBox(element, { w, h }); + + const elementInViewBoundingBox = _getIntersectionOfRects([ { + left: 0, + top: 0, + right: topWin.innerWidth, + bottom: topWin.innerHeight + }, elementBoundingBox ]); + + let elementInViewArea, elementTotalArea; + + if (elementInViewBoundingBox !== null) { + // Some or all of the element is in view + elementInViewArea = elementInViewBoundingBox.width * elementInViewBoundingBox.height; + elementTotalArea = elementBoundingBox.width * elementBoundingBox.height; + + return ((elementInViewArea / elementTotalArea) * 100); + } + + return 0; + } + + function _getBoundingBox(element, { w, h } = {}) { + let { width, height, left, top, right, bottom } = element.getBoundingClientRect(); + + if ((width === 0 || height === 0) && w && h) { + width = w; + height = h; + right = left + w; + bottom = top + h; + } + + return { width, height, left, top, right, bottom }; + } + + function _getIntersectionOfRects(rects) { + const bbox = { + left: rects[0].left, + right: rects[0].right, + top: rects[0].top, + bottom: rects[0].bottom + }; + + for (let i = 1; i < rects.length; ++i) { + bbox.left = Math.max(bbox.left, rects[i].left); + bbox.right = Math.min(bbox.right, rects[i].right); + + if (bbox.left >= bbox.right) { + return null; + } + + bbox.top = Math.max(bbox.top, rects[i].top); + bbox.bottom = Math.min(bbox.bottom, rects[i].bottom); + + if (bbox.top >= bbox.bottom) { + return null; + } + } + + bbox.width = bbox.right - bbox.left; + bbox.height = bbox.bottom - bbox.top; + + return bbox; + } } export const spec = new MarsmediaAdapter(); diff --git a/modules/mass.js b/modules/mass.js new file mode 100644 index 00000000000..14fe556a466 --- /dev/null +++ b/modules/mass.js @@ -0,0 +1,129 @@ +/** + * This module adds MASS support to Prebid.js. + */ + +import { config } from '../src/config.js'; +import { getHook } from '../src/hook.js'; +import find from 'core-js-pure/features/array/find.js'; + +export let listenerAdded = false; +export let massEnabled = false; + +const defaultCfg = { + dealIdPattern: /^MASS/i +}; +let cfg; + +const massBids = {}; + +init(); +config.getConfig('mass', config => init(config.mass)); + +/** + * Module init. + */ +export function init(customCfg) { + cfg = Object.assign({}, defaultCfg, customCfg); + + if (cfg.enabled === false) { + if (massEnabled) { + massEnabled = false; + getHook('addBidResponse').getHooks({hook: addBidResponseHook}).remove(); + } + } else { + if (!massEnabled) { + getHook('addBidResponse').before(addBidResponseHook); + massEnabled = true; + } + } +} + +/** + * Before hook for 'addBidResponse'. + */ +export function addBidResponseHook(next, adUnitCode, bid) { + if (!isMassBid(bid) || !cfg.renderUrl) { + return next(adUnitCode, bid); + } + + const bidRequest = find(this.bidderRequest.bids, bidRequest => + bidRequest.bidId === bid.requestId + ); + + massBids[bid.requestId] = { + bidRequest, + bid, + adm: bid.ad + }; + + bid.ad = ' + + + +
+ + + `; +} + +function wrapBanner(bid, bidData) { + return ` + + + + + + + + +
+ + + `; +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO], + + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid(bid) { + return ( + !!bid.params.placementId && + !!bid.params.placement && + ( + (getMediaTypeFromBid(bid) === BANNER && bid.params.placement === 'banner') || + (getMediaTypeFromBid(bid) === BANNER && bid.params.placement === 'inImage' && !!bid.params.imageUrl) || + (getMediaTypeFromBid(bid) === VIDEO && bid.params.placement === 'video' && hasVideoMandatoryParams(bid.mediaTypes)) + ) + ); + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param {validBidRequests[]} - an array of bids + * @return ServerRequest Info describing the request to the server. + */ + buildRequests(validBidRequests, bidderRequest) { + const payload = { + url: bidderRequest.refererInfo.referer, + cmp: !!bidderRequest.gdprConsent, + bidRequests: buildBidRequests(validBidRequests) + }; + + if (payload.cmp) { + const gdprApplies = bidderRequest.gdprConsent.gdprApplies; + if (gdprApplies !== undefined) payload['ga'] = gdprApplies; + payload['cs'] = bidderRequest.gdprConsent.consentString; + } + + const payloadString = JSON.stringify(payload); + + return { + method: 'POST', + url: SSP_ENDPOINT, + data: payloadString, + options: { + contentType: 'application/json' + } + } + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function(serverResponse, bidRequest) { + let bidRequests = JSON.parse(bidRequest.data).bidRequests; + const serverBody = serverResponse.body; + + if (serverBody && serverBody.bids && utils.isArray(serverBody.bids)) { + return utils._map(serverBody.bids, function(bid) { + let rawBid = find(bidRequests, function (item) { + return item.bidId === bid.bidId; + }); + bid.placement = rawBid.placement; + bid.transactionId = rawBid.transactionId; + bid.placeId = rawBid.placeId; + return buildBid(bid); + }); + } else { + return []; + } + } + +} +registerBidder(spec); diff --git a/modules/voxBidAdapter.md b/modules/voxBidAdapter.md new file mode 100644 index 00000000000..3fc0383e6f8 --- /dev/null +++ b/modules/voxBidAdapter.md @@ -0,0 +1,237 @@ +# Overview + + +**Module Name**: VOX Bidder Adapter +**Module Type**: Bidder Adapter +**Maintainer**: prebid@hybrid.ai + +# Description + +You can use this adapter to get a bid from partners.hybrid.ai + + +## Sample Banner Ad Unit + +```js +var adUnits = [{ + code: 'banner_ad_unit', + mediaTypes: { + banner: { + sizes: [[160, 600]] + } + }, + bids: [{ + bidder: "vox", + params: { + placement: "banner", // required + placementId: "5fc77bc5a757531e24c89a4c" // required + } + }] +}]; +``` + +## Sample Video Ad Unit + +```js +var adUnits = [{ + code: 'video_ad_unit', + mediaTypes: { + video: { + context: 'outstream', // required + playerSize: [[640, 480]] // required + } + }, + bids: [{ + bidder: 'vox', + params: { + placement: "video", // required + placementId: "5fc77a94a757531e24c89a3d" // required + } + }] +}]; +``` + +# Sample In-Image Ad Unit + +```js +var adUnits = [{ + code: 'test-div', + mediaTypes: { + banner: { + sizes: [0, 0] + } + }, + bids: [{ + bidder: "vox", + params: { + placement: "inImage", + placementId: "5fc77b40a757531e24c89a42", + imageUrl: "https://gallery.voxexchange.io/vox-main.png" + } + }] +}]; +``` + +# Example page with In-Image + +```html + + + + + Prebid.js Banner Example + + + + + +

Prebid.js InImage Banner Test

+
+ + +
+ + +``` + +# Example page with In-Image and GPT + +```html + + + + + Prebid.js Banner Example + + + + + + +

Prebid.js Banner Ad Unit Test

+
+ + +
+ + +``` diff --git a/modules/waardexBidAdapter.js b/modules/waardexBidAdapter.js index b9114d4f1bf..1558ea39cd1 100644 --- a/modules/waardexBidAdapter.js +++ b/modules/waardexBidAdapter.js @@ -1,217 +1,295 @@ -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER } from '../src/mediaTypes.js'; -import { config } from '../src/config.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {config} from '../src/config.js'; +import * as utils from '../src/utils.js'; +import find from 'core-js-pure/features/array/find.js'; -const domain = 'hb.justbidit.xyz'; -const httpsPort = 8843; -const path = '/prebid'; +const ENDPOINT = `https://hb.justbidit.xyz:8843/prebid`; +const BIDDER_CODE = 'waardex'; -const ENDPOINT = `https://${domain}:${httpsPort}${path}`; +const isBidRequestValid = bid => { + if (!bid.bidId) { + utils.logError(BIDDER_CODE + ': bid.bidId should be non-empty'); + return false; + } -const BIDDER_CODE = 'waardex'; + if (!bid.params) { + utils.logError(BIDDER_CODE + ': bid.params should be non-empty'); + return false; + } -/** - * @param {Array} requestSizes - * - * @returns {Array} - * */ -function transformSizes(requestSizes) { - let sizes = []; - if ( - Array.isArray(requestSizes) && - !Array.isArray(requestSizes[0]) - ) { - sizes[0] = { - width: parseInt(requestSizes[0], 10) || 0, - height: parseInt(requestSizes[1], 10) || 0, - }; - } else if ( - Array.isArray(requestSizes) && - Array.isArray(requestSizes[0]) - ) { - sizes = requestSizes.map(item => { - return { - width: parseInt(item[0], 10) || 0, - height: parseInt(item[1], 10) || 0, - } - }); + if (!+bid.params.zoneId) { + utils.logError(BIDDER_CODE + ': bid.params.zoneId should be non-empty Number'); + return false; } - return sizes; -} -/** - * @param {Object} banner - * @param {Array} banner.sizes - * - * @returns {Object} - * */ -function createBannerObject(banner) { - return { - sizes: transformSizes(banner.sizes), - }; -} - -/** - * @param {Array} validBidRequests - * - * @returns {Object} - * */ -function buildBidRequests(validBidRequests) { - return validBidRequests.map((validBidRequest) => { - const params = validBidRequest.params; - - const item = { - bidId: validBidRequest.bidId, - bidfloor: parseFloat(params.bidfloor) || 0, - position: parseInt(params.position) || 1, - instl: parseInt(params.instl) || 0, - }; - if (validBidRequest.mediaTypes[BANNER]) { - item[BANNER] = createBannerObject(validBidRequest.mediaTypes[BANNER]); + if (bid.mediaTypes && bid.mediaTypes.video) { + if (!bid.mediaTypes.video.playerSize) { + utils.logError(BIDDER_CODE + ': bid.mediaTypes.video.playerSize should be non-empty'); + return false; + } + + if (!utils.isArray(bid.mediaTypes.video.playerSize)) { + utils.logError(BIDDER_CODE + ': bid.mediaTypes.video.playerSize should be an Array'); + return false; } - return item; - }); -} - -/** - * @param {Object} bidderRequest - * @param {String} bidderRequest.userAgent - * @param {String} bidderRequest.refererInfo - * @param {String} bidderRequest.uspConsent - * @param {Object} bidderRequest.gdprConsent - * @param {String} bidderRequest.gdprConsent.consentString - * @param {String} bidderRequest.gdprConsent.gdprApplies - * - * @returns {Object} - { - * ua: string, - * language: string, - * [referer]: string, - * [us_privacy]: string, - * [consent_string]: string, - * [consent_required]: string, - * [coppa]: boolean, - * } - * */ -function getCommonBidsData(bidderRequest) { + + if (!bid.mediaTypes.video.playerSize[0]) { + utils.logError(BIDDER_CODE + ': bid.mediaTypes.video.playerSize should be non-empty'); + return false; + } + + if (!utils.isArray(bid.mediaTypes.video.playerSize[0])) { + utils.logError(BIDDER_CODE + ': bid.mediaTypes.video.playerSize should be non-empty Array'); + return false; + } + } + + return true; +}; + +const buildRequests = (validBidRequests, bidderRequest) => { + const dataToSend = { + ...getCommonBidsData(bidderRequest), + bidRequests: getBidRequestsToSend(validBidRequests) + }; + + let zoneId = ''; + if (validBidRequests[0] && validBidRequests[0].params && +validBidRequests[0].params.zoneId) { + zoneId = +validBidRequests[0].params.zoneId; + } + + return {method: 'POST', url: `${ENDPOINT}?pubId=${zoneId}`, data: dataToSend}; +}; + +const getCommonBidsData = bidderRequest => { const payload = { ua: navigator.userAgent || '', language: navigator.language && navigator.language.indexOf('-') !== -1 ? navigator.language.split('-')[0] : '', - }; + if (bidderRequest && bidderRequest.refererInfo) { payload.referer = encodeURIComponent(bidderRequest.refererInfo.referer); } + if (bidderRequest && bidderRequest.uspConsent) { payload.us_privacy = bidderRequest.uspConsent; } + if (bidderRequest && bidderRequest.gdprConsent) { payload.gdpr_consent = { consent_string: bidderRequest.gdprConsent.consentString, consent_required: bidderRequest.gdprConsent.gdprApplies, } } + payload.coppa = !!config.getConfig('coppa'); return payload; -} - -/** - * this function checks either bid response is valid or noе - * - * @param {Object} bid - * @param {string} bid.requestId - * @param {number} bid.cpm - * @param {string} bid.creativeId - * @param {number} bid.ttl - * @param {string} bid.currency - * @param {number} bid.width - * @param {number} bid.height - * @param {string} bid.ad - * - * @returns {boolean} - * */ -function isBidValid(bid) { - if (!bid.requestId || !bid.cpm || !bid.creativeId || !bid.ttl || !bid.currency) { - return false; +}; + +const getBidRequestsToSend = validBidRequests => { + return validBidRequests.map(getBidRequestToSend); +}; + +const getBidRequestToSend = validBidRequest => { + const result = { + bidId: validBidRequest.bidId, + bidfloor: parseFloat(validBidRequest.params.bidfloor) || 0, + position: parseInt(validBidRequest.params.position) || 1, + instl: parseInt(validBidRequest.params.instl) || 0, + }; + + if (validBidRequest.mediaTypes[BANNER]) { + result[BANNER] = createBannerObject(validBidRequest.mediaTypes[BANNER]); } - return Boolean(bid.width && bid.height && bid.ad); -} - -/** - * @param {Object} serverBid - * - * @returns {Object|null} - * */ -function createBid(serverBid) { - const bid = { - requestId: serverBid.id, - cpm: serverBid.price, + if (validBidRequest.mediaTypes[VIDEO]) { + result[VIDEO] = createVideoObject(validBidRequest.mediaTypes[VIDEO], validBidRequest.params); + } + + return result; +}; + +const createBannerObject = banner => { + return { + sizes: transformSizes(banner.sizes), + }; +}; + +const transformSizes = requestSizes => { + let result = []; + + if (Array.isArray(requestSizes) && !Array.isArray(requestSizes[0])) { + result[0] = { + width: parseInt(requestSizes[0], 10) || 0, + height: parseInt(requestSizes[1], 10) || 0, + }; + } else if (Array.isArray(requestSizes) && Array.isArray(requestSizes[0])) { + result = requestSizes.map(item => { + return { + width: parseInt(item[0], 10) || 0, + height: parseInt(item[1], 10) || 0, + } + }); + } + + return result; +}; + +const createVideoObject = (videoMediaTypes, videoParams) => { + return { + w: utils.deepAccess(videoMediaTypes, 'playerSize')[0][0], + h: utils.deepAccess(videoMediaTypes, 'playerSize')[0][1], + mimes: utils.getBidIdParameter('mimes', videoParams) || ['application/javascript', 'video/mp4', 'video/webm'], + minduration: utils.getBidIdParameter('minduration', videoParams) || 0, + maxduration: utils.getBidIdParameter('maxduration', videoParams) || 500, + protocols: utils.getBidIdParameter('protocols', videoParams) || [2, 3, 5, 6], + startdelay: utils.getBidIdParameter('startdelay', videoParams) || 0, + placement: utils.getBidIdParameter('placement', videoParams) || videoMediaTypes.context === 'outstream' ? 3 : 1, + skip: utils.getBidIdParameter('skip', videoParams) || 1, + skipafter: utils.getBidIdParameter('skipafter', videoParams) || 0, + minbitrate: utils.getBidIdParameter('minbitrate', videoParams) || 0, + maxbitrate: utils.getBidIdParameter('maxbitrate', videoParams) || 3500, + delivery: utils.getBidIdParameter('delivery', videoParams) || [2], + playbackmethod: utils.getBidIdParameter('playbackmethod', videoParams) || [1, 2, 3, 4], + api: utils.getBidIdParameter('api', videoParams) || [2], + linearity: utils.getBidIdParameter('linearity', videoParams) || 1 + }; +}; + +const interpretResponse = (serverResponse, bidRequest) => { + try { + const responseBody = serverResponse.body; + + if (!responseBody.seatbid || !responseBody.seatbid[0]) { + return []; + } + + return responseBody.seatbid[0].bid + .map(openRtbBid => { + const hbRequestBid = getHbRequestBid(openRtbBid, bidRequest.data); + if (!hbRequestBid) return; + + const hbRequestMediaType = getHbRequestMediaType(hbRequestBid); + if (!hbRequestMediaType) return; + + return mapOpenRtbToHbBid(openRtbBid, hbRequestMediaType, hbRequestBid); + }) + .filter(x => x); + } catch (e) { + return []; + } +}; + +const getHbRequestBid = (openRtbBid, bidRequest) => { + return find(bidRequest.bidRequests, x => x.bidId === openRtbBid.impid); +}; + +const getHbRequestMediaType = hbRequestBid => { + if (hbRequestBid.banner) return BANNER; + if (hbRequestBid.video) return VIDEO; + return null; +}; + +const mapOpenRtbToHbBid = (openRtbBid, mediaType, hbRequestBid) => { + let bid = null; + + if (mediaType === BANNER) { + bid = mapOpenRtbBannerToHbBid(openRtbBid, hbRequestBid); + } + + if (mediaType === VIDEO) { + bid = mapOpenRtbVideoToHbBid(openRtbBid, hbRequestBid); + } + + return isBidValid(bid) ? bid : null; +}; + +const mapOpenRtbBannerToHbBid = (openRtbBid, hbRequestBid) => { + return { + mediaType: BANNER, + requestId: hbRequestBid.bidId, + cpm: openRtbBid.price, currency: 'USD', - width: serverBid.w, - height: serverBid.h, - creativeId: serverBid.crid, + width: openRtbBid.w, + height: openRtbBid.h, + creativeId: openRtbBid.crid, netRevenue: true, ttl: 3000, - ad: serverBid.adm, - dealId: serverBid.dealid, + ad: openRtbBid.adm, + dealId: openRtbBid.dealid, meta: { - cid: serverBid.cid, - adomain: serverBid.adomain, - mediaType: serverBid.ext.mediaType + cid: openRtbBid.cid, + adomain: openRtbBid.adomain, + mediaType: openRtbBid.ext && openRtbBid.ext.mediaType }, }; +}; - return isBidValid(bid) ? bid : null; -} +const mapOpenRtbVideoToHbBid = (openRtbBid, hbRequestBid) => { + return { + mediaType: VIDEO, + requestId: hbRequestBid.bidId, + cpm: openRtbBid.price, + currency: 'USD', + width: hbRequestBid.video.w, + height: hbRequestBid.video.h, + ad: openRtbBid.adm, + ttl: 3000, + creativeId: openRtbBid.crid, + netRevenue: true, + vastUrl: getVastUrl(openRtbBid), + // An impression tracking URL to serve with video Ad + // Optional; only usable with vastUrl and requires prebid cache to be enabled + // Example: "https://vid.exmpale.com/imp/134" + // For now we don't need this field + // vastImpUrl: null, + vastXml: openRtbBid.adm, + dealId: openRtbBid.dealid, + meta: { + cid: openRtbBid.cid, + adomain: openRtbBid.adomain, + networkId: null, + networkName: null, + agencyId: null, + agencyName: null, + advertiserId: null, + advertiserName: null, + advertiserDomains: null, + brandId: null, + brandName: null, + primaryCatId: null, + secondaryCatIds: null, + mediaType: 'video', + }, + } +}; -export const spec = { - code: BIDDER_CODE, - supportedMediaTypes: [BANNER], - - isBidRequestValid: (bid) => Boolean(bid.bidId && bid.params && +bid.params.zoneId), - - /** - * @param {Object[]} validBidRequests - array of valid bid requests - * @param {Object} bidderRequest - an array of valid bid requests - * - * */ - buildRequests(validBidRequests, bidderRequest) { - const payload = getCommonBidsData(bidderRequest); - payload.bidRequests = buildBidRequests(validBidRequests); - - let zoneId = ''; - if (validBidRequests[0] && validBidRequests[0].params && +validBidRequests[0].params.zoneId) { - zoneId = +validBidRequests[0].params.zoneId; - } +const getVastUrl = openRtbBid => { + const adm = (openRtbBid.adm || '').trim(); - const url = `${ENDPOINT}?pubId=${zoneId}`; + if (adm.startsWith('http')) { + return adm; + } else { + return null + } +}; - return { - method: 'POST', - url, - data: payload - }; - }, - - /** - * Unpack the response from the server into a list of bids. - */ - interpretResponse(serverResponse, bidRequest) { - const bids = []; - serverResponse = serverResponse.body; - - if (serverResponse.seatbid && serverResponse.seatbid[0]) { - const oneSeatBid = serverResponse.seatbid[0]; - oneSeatBid.bid.forEach(serverBid => { - const bid = createBid(serverBid); - if (bid) { - bids.push(bid); - } - }); - } - return bids; - }, -} +const isBidValid = bid => { + if (!bid.requestId || !bid.cpm || !bid.creativeId || !bid.ttl || !bid.currency) { + return false; + } + + return Boolean(bid.width && bid.height && bid.ad); +}; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO], + isBidRequestValid, + buildRequests, + interpretResponse, +}; registerBidder(spec); diff --git a/modules/waardexBidAdapter.md b/modules/waardexBidAdapter.md index 44ee720d31a..8eade8409f4 100644 --- a/modules/waardexBidAdapter.md +++ b/modules/waardexBidAdapter.md @@ -10,51 +10,84 @@ Maintainer: info@prebid.org Connects to Waardex exchange for bids. -Waardex bid adapter supports Banner. +Waardex bid adapter supports Banner and Video. # Test Parameters - -``` - +## Banner +```javascript var sizes = [ [300, 250] ]; var PREBID_TIMEOUT = 5000; var FAILSAFE_TIMEOUT = 5000; -var adUnits = [{ - code: '/19968336/header-bid-tag-0', - mediaTypes: { - banner: { - sizes: sizes, - }, - }, - bids: [{ - bidder: 'waardex', - params: { - placementId: 13144370, - position: 1, // add position openrtb - bidfloor: 0.5, - instl: 0, // 1 - full screen - pubId: 1, +var adUnits = [ + { + code: '/19968336/header-bid-tag-0', + mediaTypes: { + banner: { + sizes: sizes + } + }, + bids: [ + { + bidder: 'waardex', + params: { + bidfloor: 1.5, + position: 1, + instl: 1, + zoneId: 1 + } + } + ] } - }] -},{ - code: '/19968336/header-bid-tag-1', - mediaTypes: { - banner: { - sizes: sizes, +]; +``` + +## Video + +```javascript +const PREBID_TIMEOUT = 1000; +const FAILSAFE_TIMEOUT = 3000; + +const sizes = [ + [640, 480] +]; + +const adUnits = [ + { + code: 'video1', + mediaTypes: { + video: { + context: 'instream', + playerSize: sizes[0] + } }, - }, - bids: [{ - bidder: 'waardex', - params: { - placementId: 333333333333, - position: 1, // add position openrtb - bidfloor: 0.5, - instl: 0, // 1 - full screen - pubId: 1, - } - }] -}]; + bids: [ + { + bidder: 'waardex', + params: { + bidfloor: 1.5, + position: 1, + instl: 1, + zoneId: 1, + mimes: ['video/x-ms-wmv', 'video/mp4'], + minduration: 2, + maxduration: 10, + protocols: ['VAST 1.0', 'VAST 2.0'], + startdelay: -1, + placement: 1, + skip: 1, + skipafter: 2, + minbitrate: 0, + maxbitrate: 0, + delivery: [1, 2, 3], + playbackmethod: [1, 2], + api: [1, 2, 3, 4, 5, 6], + linearity: 1, + } + } + ] + } +] ``` diff --git a/modules/yieldlabBidAdapter.js b/modules/yieldlabBidAdapter.js index 5465a10a884..9c1c54cfb2b 100644 --- a/modules/yieldlabBidAdapter.js +++ b/modules/yieldlabBidAdapter.js @@ -8,7 +8,7 @@ const ENDPOINT = 'https://ad.yieldlab.net' const BIDDER_CODE = 'yieldlab' const BID_RESPONSE_TTL_SEC = 300 const CURRENCY_CODE = 'EUR' -const OUTSTREAMPLAYER_URL = 'https://ad2.movad.net/dynamic.ad?a=o193092&ma_loadEvent=ma-start-event' +const OUTSTREAMPLAYER_URL = 'https://ad.adition.com/dynamic.ad?a=o193092&ma_loadEvent=ma-start-event' export const spec = { code: BIDDER_CODE, @@ -96,8 +96,8 @@ export const spec = { }) if (matchedBid) { - const primarysize = bidRequest.sizes.length === 2 && !utils.isArray(bidRequest.sizes[0]) ? bidRequest.sizes : bidRequest.sizes[0] - const customsize = bidRequest.params.adSize !== undefined ? parseSize(bidRequest.params.adSize) : primarysize + const adUnitSize = bidRequest.sizes.length === 2 && !utils.isArray(bidRequest.sizes[0]) ? bidRequest.sizes : bidRequest.sizes[0] + const adSize = bidRequest.params.adSize !== undefined ? parseSize(bidRequest.params.adSize) : (matchedBid.adsize !== undefined) ? parseSize(matchedBid.adsize) : adUnitSize const extId = bidRequest.params.extId !== undefined ? '&id=' + bidRequest.params.extId : '' const adType = matchedBid.adtype !== undefined ? matchedBid.adtype : '' const gdprApplies = reqParams.gdpr ? '&gdpr=' + reqParams.gdpr : '' @@ -106,15 +106,15 @@ export const spec = { const bidResponse = { requestId: bidRequest.bidId, cpm: matchedBid.price / 100, - width: customsize[0], - height: customsize[1], + width: adSize[0], + height: adSize[1], creativeId: '' + matchedBid.id, dealId: (matchedBid['c.dealid']) ? matchedBid['c.dealid'] : matchedBid.pid, currency: CURRENCY_CODE, netRevenue: false, ttl: BID_RESPONSE_TTL_SEC, referrer: '', - ad: `` + ad: `` } if (isVideo(bidRequest, adType)) { @@ -124,8 +124,7 @@ export const spec = { bidResponse.height = playersize[1] } bidResponse.mediaType = VIDEO - bidResponse.vastUrl = `${ENDPOINT}/d/${matchedBid.id}/${bidRequest.params.supplyId}/${customsize[0]}x${customsize[1]}?ts=${timestamp}${extId}${gdprApplies}${gdprConsent}` - + bidResponse.vastUrl = `${ENDPOINT}/d/${matchedBid.id}/${bidRequest.params.supplyId}/?ts=${timestamp}${extId}${gdprApplies}${gdprConsent}` if (isOutstream(bidRequest)) { const renderer = Renderer.install({ id: bidRequest.bidId, diff --git a/modules/yieldlabBidAdapter.md b/modules/yieldlabBidAdapter.md index 37897b83f12..a7a3f2715dc 100644 --- a/modules/yieldlabBidAdapter.md +++ b/modules/yieldlabBidAdapter.md @@ -21,7 +21,6 @@ Module that connects to Yieldlab's demand sources params: { adslotId: "5220336", supplyId: "1381604", - adSize: "728x90", targeting: { key1: "value1", key2: "value2" @@ -41,8 +40,7 @@ Module that connects to Yieldlab's demand sources bidder: "yieldlab", params: { adslotId: "5220339", - supplyId: "1381604", - adSize: "640x480" + supplyId: "1381604" } }] } diff --git a/modules/yieldmoBidAdapter.js b/modules/yieldmoBidAdapter.js index 05af0bf0d66..a4bd58140b9 100644 --- a/modules/yieldmoBidAdapter.js +++ b/modules/yieldmoBidAdapter.js @@ -2,6 +2,7 @@ import * as utils from '../src/utils.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import includes from 'core-js-pure/features/array/includes'; +import find from 'core-js-pure/features/array/find.js'; const BIDDER_CODE = 'yieldmo'; const CURRENCY = 'USD'; @@ -188,6 +189,10 @@ function createNewBannerBid(response) { netRevenue: NET_REVENUE, ttl: TIME_TO_LIVE, ad: response.ad, + meta: { + advertiserDomains: response.adomain || [], + mediaType: BANNER, + }, }; } @@ -197,7 +202,7 @@ function createNewBannerBid(response) { * @param bidRequest server request */ function createNewVideoBid(response, bidRequest) { - const imp = (utils.deepAccess(bidRequest, 'data.imp') || []).find(imp => imp.id === response.impid); + const imp = find((utils.deepAccess(bidRequest, 'data.imp') || []), imp => imp.id === response.impid); return { requestId: imp.id, cpm: response.price, @@ -208,7 +213,11 @@ function createNewVideoBid(response, bidRequest) { netRevenue: NET_REVENUE, mediaType: VIDEO, ttl: TIME_TO_LIVE, - vastXml: response.adm + vastXml: response.adm, + meta: { + advertiserDomains: response.adomain || [], + mediaType: VIDEO, + }, }; } diff --git a/modules/yuktamediaAnalyticsAdapter.js b/modules/yuktamediaAnalyticsAdapter.js index caca3bf3341..2ef2d251ace 100644 --- a/modules/yuktamediaAnalyticsAdapter.js +++ b/modules/yuktamediaAnalyticsAdapter.js @@ -5,6 +5,7 @@ import CONSTANTS from '../src/constants.json'; import * as utils from '../src/utils.js'; import { getStorageManager } from '../src/storageManager.js'; import { getRefererInfo } from '../src/refererDetection.js'; +import strIncludes from 'core-js-pure/features/string/includes.js'; const storage = getStorageManager(); const yuktamediaAnalyticsVersion = 'v3.1.0'; @@ -152,7 +153,7 @@ var yuktamediaAnalyticsAdapter = Object.assign(adapter({ analyticsType: 'endpoin bidResponse.responseTimestamp = args.responseTimestamp; bidResponse.bidForSize = args.size; for (const [adserverTargetingKey, adserverTargetingValue] of Object.entries(args.adserverTargeting)) { - if (['body', 'icon', 'image', 'linkurl', 'host', 'path'].every((ele) => !adserverTargetingKey.includes(ele))) { + if (['body', 'icon', 'image', 'linkurl', 'host', 'path'].every((ele) => !strIncludes(adserverTargetingKey, ele))) { bidResponse['adserverTargeting-' + adserverTargetingKey] = adserverTargetingValue; } } diff --git a/modules/zemantaBidAdapter.js b/modules/zemantaBidAdapter.js new file mode 100644 index 00000000000..b6dafcaed77 --- /dev/null +++ b/modules/zemantaBidAdapter.js @@ -0,0 +1,277 @@ +// jshint esversion: 6, es3: false, node: true +'use strict'; + +import { + registerBidder +} from '../src/adapters/bidderFactory.js'; +import { NATIVE, BANNER } from '../src/mediaTypes.js'; +import * as utils from '../src/utils.js'; +import { ajax } from '../src/ajax.js'; +import { config } from '../src/config.js'; + +const BIDDER_CODE = 'zemanta'; +const GVLID = 164; +const CURRENCY = 'USD'; +const NATIVE_ASSET_IDS = { 0: 'title', 2: 'icon', 3: 'image', 5: 'sponsoredBy', 4: 'body', 1: 'cta' }; +const NATIVE_PARAMS = { + title: { id: 0, name: 'title' }, + icon: { id: 2, type: 1, name: 'img' }, + image: { id: 3, type: 3, name: 'img' }, + sponsoredBy: { id: 5, name: 'data', type: 1 }, + body: { id: 4, name: 'data', type: 2 }, + cta: { id: 1, type: 12, name: 'data' } +}; + +export const spec = { + code: BIDDER_CODE, + gvlid: GVLID, + aliases: [ + { code: 'outbrain', gvlid: GVLID } + ], + supportedMediaTypes: [ NATIVE, BANNER ], + isBidRequestValid: (bid) => { + return ( + (!!config.getConfig('zemanta.bidderUrl') || !!config.getConfig('outbrain.bidderUrl')) && + !!utils.deepAccess(bid, 'params.publisher.id') && + !!(bid.nativeParams || bid.sizes) + ); + }, + buildRequests: (validBidRequests, bidderRequest) => { + const page = bidderRequest.refererInfo.referer; + const ua = navigator.userAgent; + const test = setOnAny(validBidRequests, 'params.test'); + const publisher = setOnAny(validBidRequests, 'params.publisher'); + const bcat = setOnAny(validBidRequests, 'params.bcat'); + const badv = setOnAny(validBidRequests, 'params.badv'); + const cur = CURRENCY; + const endpointUrl = config.getConfig('zemanta.bidderUrl') || config.getConfig('outbrain.bidderUrl'); + const timeout = bidderRequest.timeout; + + const imps = validBidRequests.map((bid, id) => { + bid.netRevenue = 'net'; + const imp = { + id: id + 1 + '' + } + + if (bid.params.tagid) { + imp.tagid = bid.params.tagid + } + + if (bid.nativeParams) { + imp.native = { + request: JSON.stringify({ + assets: getNativeAssets(bid) + }) + } + } else { + imp.banner = { + format: transformSizes(bid.sizes) + } + } + + return imp; + }); + + const request = { + id: bidderRequest.auctionId, + site: { page, publisher }, + device: { ua }, + source: { fd: 1 }, + cur: [cur], + tmax: timeout, + imp: imps, + bcat: bcat, + badv: badv, + }; + + if (test) { + request.is_debug = !!test; + request.test = 1; + } + + if (utils.deepAccess(bidderRequest, 'gdprConsent.gdprApplies')) { + utils.deepSetValue(request, 'user.ext.consent', bidderRequest.gdprConsent.consentString) + utils.deepSetValue(request, 'regs.ext.gdpr', bidderRequest.gdprConsent.gdprApplies & 1) + } + if (bidderRequest.uspConsent) { + utils.deepSetValue(request, 'regs.ext.us_privacy', bidderRequest.uspConsent) + } + if (config.getConfig('coppa') === true) { + utils.deepSetValue(request, 'regs.coppa', config.getConfig('coppa') & 1) + } + + return { + method: 'POST', + url: endpointUrl, + data: JSON.stringify(request), + bids: validBidRequests + }; + }, + interpretResponse: (serverResponse, { bids }) => { + if (!serverResponse.body) { + return []; + } + const { seatbid, cur } = serverResponse.body; + + const bidResponses = flatten(seatbid.map(seat => seat.bid)).reduce((result, bid) => { + result[bid.impid - 1] = bid; + return result; + }, []); + + return bids.map((bid, id) => { + const bidResponse = bidResponses[id]; + if (bidResponse) { + const type = bid.nativeParams ? NATIVE : BANNER; + const bidObject = { + requestId: bid.bidId, + cpm: bidResponse.price, + creativeId: bidResponse.crid, + ttl: 360, + netRevenue: bid.netRevenue === 'net', + currency: cur, + mediaType: type, + nurl: bidResponse.nurl, + }; + if (type === NATIVE) { + bidObject.native = parseNative(bidResponse); + } else { + bidObject.ad = bidResponse.adm; + bidObject.width = bidResponse.w; + bidObject.height = bidResponse.h; + } + bidObject.meta = {}; + if (bidResponse.adomain && bidResponse.adomain.length > 0) { + bidObject.meta.advertiserDomains = bidResponse.adomain; + } + return bidObject; + } + }).filter(Boolean); + }, + getUserSyncs: (syncOptions, responses, gdprConsent, uspConsent) => { + const syncs = []; + let syncUrl = config.getConfig('zemanta.usersyncUrl') || config.getConfig('outbrain.usersyncUrl'); + if (syncOptions.pixelEnabled && syncUrl) { + if (gdprConsent) { + syncUrl += '&gdpr=' + (gdprConsent.gdprApplies & 1); + syncUrl += '&gdpr_consent=' + encodeURIComponent(gdprConsent.consentString || ''); + } + if (uspConsent) { + syncUrl += '&us_privacy=' + encodeURIComponent(uspConsent); + } + + syncs.push({ + type: 'image', + url: syncUrl + }); + } + return syncs; + }, + onBidWon: (bid) => { + ajax(utils.replaceAuctionPrice(bid.nurl, bid.originalCpm)) + } +}; + +registerBidder(spec); + +function parseNative(bid) { + const { assets, link, eventtrackers } = JSON.parse(bid.adm); + const result = { + clickUrl: link.url, + clickTrackers: link.clicktrackers || undefined + }; + assets.forEach(asset => { + const kind = NATIVE_ASSET_IDS[asset.id]; + const content = kind && asset[NATIVE_PARAMS[kind].name]; + if (content) { + result[kind] = content.text || content.value || { url: content.url, width: content.w, height: content.h }; + } + }); + if (eventtrackers) { + result.impressionTrackers = []; + eventtrackers.forEach(tracker => { + if (tracker.event !== 1) return; + switch (tracker.method) { + case 1: // img + result.impressionTrackers.push(tracker.url); + break; + case 2: // js + result.javascriptTrackers = ``; + break; + } + }); + } + return result; +} + +function setOnAny(collection, key) { + for (let i = 0, result; i < collection.length; i++) { + result = utils.deepAccess(collection[i], key); + if (result) { + return result; + } + } +} + +function flatten(arr) { + return [].concat(...arr); +} + +function getNativeAssets(bid) { + return utils._map(bid.nativeParams, (bidParams, key) => { + const props = NATIVE_PARAMS[key]; + const asset = { + required: bidParams.required & 1, + }; + if (props) { + asset.id = props.id; + let wmin, hmin, w, h; + let aRatios = bidParams.aspect_ratios; + + if (aRatios && aRatios[0]) { + aRatios = aRatios[0]; + wmin = aRatios.min_width || 0; + hmin = aRatios.ratio_height * wmin / aRatios.ratio_width | 0; + } + + if (bidParams.sizes) { + const sizes = flatten(bidParams.sizes); + w = sizes[0]; + h = sizes[1]; + } + + asset[props.name] = { + len: bidParams.len, + type: props.type, + wmin, + hmin, + w, + h + }; + + return asset; + } + }).filter(Boolean); +} + +/* Turn bid request sizes into ut-compatible format */ +function transformSizes(requestSizes) { + if (!utils.isArray(requestSizes)) { + return []; + } + + if (requestSizes.length === 2 && !utils.isArray(requestSizes[0])) { + return [{ + w: parseInt(requestSizes[0], 10), + h: parseInt(requestSizes[1], 10) + }]; + } else if (utils.isArray(requestSizes[0])) { + return requestSizes.map(item => + ({ + w: parseInt(item[0], 10), + h: parseInt(item[1], 10) + }) + ); + } + + return []; +} diff --git a/modules/zemantaBidAdapter.md b/modules/zemantaBidAdapter.md new file mode 100644 index 00000000000..fa933ecd922 --- /dev/null +++ b/modules/zemantaBidAdapter.md @@ -0,0 +1,111 @@ +# Overview + +``` +Module Name: Zemanta Adapter +Module Type: Bidder Adapter +Maintainer: prog-ops-team@outbrain.com +``` + +# Description + +Module that connects to zemanta bidder to fetch bids. +Both native and display formats are supported but not at the same time. Using OpenRTB standard. + +# Configuration + +## Bidder and usersync URLs + +The Zemanta adapter does not work without setting the correct bidder and usersync URLs. +You will receive the URLs when contacting us. + +``` +pbjs.setConfig({ + zemanta: { + bidderUrl: 'https://bidder-url.com', + usersyncUrl: 'https://usersync-url.com' + } +}); +``` + + +# Test Native Parameters +``` + var adUnits = [ + code: '/19968336/prebid_native_example_1', + mediaTypes: { + native: { + image: { + required: false, + sizes: [100, 50] + }, + title: { + required: false, + len: 140 + }, + sponsoredBy: { + required: false + }, + clickUrl: { + required: false + }, + body: { + required: false + }, + icon: { + required: false, + sizes: [50, 50] + } + } + }, + bids: [{ + bidder: 'zemanta', + params: { + publisher: { + id: '2706', // required + name: 'Publishers Name', + domain: 'publisher.com' + }, + tagid: 'tag-id', + bcat: ['IAB1-1'], + badv: ['example.com'] + } + }] + ]; + + pbjs.setConfig({ + zemanta: { + bidderUrl: 'https://prebidtest.zemanta.com/api/bidder/prebidtest/bid/' + } + }); +``` + +# Test Display Parameters +``` + var adUnits = [ + code: '/19968336/prebid_display_example_1', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bids: [{ + bidder: 'zemanta', + params: { + publisher: { + id: '2706', // required + name: 'Publishers Name', + domain: 'publisher.com' + }, + tagid: 'tag-id', + bcat: ['IAB1-1'], + badv: ['example.com'] + }, + }] + ]; + + pbjs.setConfig({ + zemanta: { + bidderUrl: 'https://prebidtest.zemanta.com/api/bidder/prebidtest/bid/' + } + }); +``` diff --git a/modules/zeotapIdPlusIdSystem.js b/modules/zeotapIdPlusIdSystem.js index d800286b00e..8f26cc827d6 100644 --- a/modules/zeotapIdPlusIdSystem.js +++ b/modules/zeotapIdPlusIdSystem.js @@ -9,23 +9,35 @@ import {submodule} from '../src/hook.js'; import { getStorageManager } from '../src/storageManager.js'; const ZEOTAP_COOKIE_NAME = 'IDP'; -export const storage = getStorageManager(); +const ZEOTAP_VENDOR_ID = 301; +const ZEOTAP_MODULE_NAME = 'zeotapIdPlus'; function readCookie() { - return storage.cookiesAreEnabled ? storage.getCookie(ZEOTAP_COOKIE_NAME) : null; + return storage.cookiesAreEnabled() ? storage.getCookie(ZEOTAP_COOKIE_NAME) : null; } function readFromLocalStorage() { - return storage.localStorageIsEnabled ? storage.getDataFromLocalStorage(ZEOTAP_COOKIE_NAME) : null; + return storage.localStorageIsEnabled() ? storage.getDataFromLocalStorage(ZEOTAP_COOKIE_NAME) : null; } +export function getStorage() { + return getStorageManager(ZEOTAP_VENDOR_ID, ZEOTAP_MODULE_NAME); +} + +export const storage = getStorage(); + /** @type {Submodule} */ export const zeotapIdPlusSubmodule = { /** * used to link submodule with config * @type {string} */ - name: 'zeotapIdPlus', + name: ZEOTAP_MODULE_NAME, + /** + * Vendor ID of Zeotap + * @type {Number} + */ + gvlid: ZEOTAP_VENDOR_ID, /** * decode the stored id value for passing to bid requests * @function diff --git a/modules/zetaBidAdapter.js b/modules/zetaBidAdapter.js index f60e8946799..09d631b3d18 100644 --- a/modules/zetaBidAdapter.js +++ b/modules/zetaBidAdapter.js @@ -20,19 +20,37 @@ export const spec = { */ isBidRequestValid: function(bid) { // check for all required bid fields - let isValid = !!( - bid && - bid.bidId && - bid.params && - bid.params.ip && - bid.params.user && - bid.params.user.buyeruid && - bid.params.definerId - ); - if (!isValid) { - utils.logWarn('Invalid bid request'); + if (!(bid && + bid.bidId && + bid.params)) { + utils.logWarn('Invalid bid request - missing required bid data'); + return false; } - return isValid; + + if (!(bid.params.user && + bid.params.user.buyeruid)) { + utils.logWarn('Invalid bid request - missing required user data'); + return false; + } + + if (!(bid.params.device && + bid.params.device.ip)) { + utils.logWarn('Invalid bid request - missing required device data'); + return false; + } + + if (!(bid.params.device.geo && + bid.params.device.geo.country)) { + utils.logWarn('Invalid bid request - missing required geo data'); + return false; + } + + if (!bid.params.definerId) { + utils.logWarn('Invalid bid request - missing required definer data'); + return false; + } + + return true; }, /** @@ -51,27 +69,23 @@ export const spec = { secure: secure, banner: buildBanner(request) }; - let isMobile = /(ios|ipod|ipad|iphone|android)/i.test(navigator.userAgent) ? 1 : 0; let payload = { id: bidderRequest.auctionId, cur: [DEFAULT_CUR], imp: [impData], - site: { - mobile: isMobile, - page: bidderRequest.refererInfo.referer - }, - device: { - ua: navigator.userAgent, - ip: params.ip - }, - user: { - buyeruid: params.user.buyeruid, - uid: params.user.uid - }, + site: params.site ? params.site : {}, + device: params.device ? params.device : {}, + user: params.user ? params.user : {}, + app: params.app ? params.app : {}, ext: { definerId: params.definerId } }; + + payload.device.ua = navigator.userAgent; + payload.site.page = bidderRequest.refererInfo.referer; + payload.site.mobile = /(ios|ipod|ipad|iphone|android)/i.test(navigator.userAgent) ? 1 : 0; + if (params.test) { payload.test = params.test; } diff --git a/modules/zetaBidAdapter.md b/modules/zetaBidAdapter.md index ce19b831d4d..d99846c7822 100644 --- a/modules/zetaBidAdapter.md +++ b/modules/zetaBidAdapter.md @@ -29,7 +29,12 @@ Module that connects to Zeta's demand sources uid: 12345, buyeruid: 12345 }, - ip: '111.222.33.44', + device: { + ip: '111.222.33.44', + geo: { + country: "USA" + } + }, definerId: 1, test: 1 } diff --git a/package-lock.json b/package-lock.json index bf95487ed9d..5131b5f4d23 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "4.14.0-pre", + "version": "4.15.0-pre", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -1920,11 +1920,6 @@ "schema-utils": "^2.7.0" } }, - "@kiosked/ulid": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@kiosked/ulid/-/ulid-3.0.0.tgz", - "integrity": "sha512-ZKt2KIgGHDaGfKt6FjYvCpDvBXZRRoE8b+wDOlAV76aXKpq6ITiSUnPYevR4y55NKDnwCvwOrjWe+aVOCAK8kQ==" - }, "@sindresorhus/is": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-3.0.0.tgz", @@ -2771,7 +2766,8 @@ "abab": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.3.tgz", - "integrity": "sha512-tsFzPpcttalNjFBCFMqsKYQcWxxen1pgJR56by//QwvJc4/OUS3kPOOttx2tSIfjsylB0pYu7f5D3K1RCxUnUg==" + "integrity": "sha512-tsFzPpcttalNjFBCFMqsKYQcWxxen1pgJR56by//QwvJc4/OUS3kPOOttx2tSIfjsylB0pYu7f5D3K1RCxUnUg==", + "dev": true }, "abbrev": { "version": "1.0.9", @@ -5069,11 +5065,6 @@ "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", "dev": true }, - "browser-cookies": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/browser-cookies/-/browser-cookies-1.2.0.tgz", - "integrity": "sha1-/KP/ubamOq3E2MCZnGtX0Pp9KbU=" - }, "browser-process-hrtime": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", @@ -15727,13 +15718,10 @@ "dev": true }, "live-connect-js": { - "version": "1.1.23", - "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-1.1.23.tgz", - "integrity": "sha512-alOXlYyDdMXt8zzCIs3+iCrdi6r/69c7YRN3sMETa3b2cCOxep3i9j2O0iepk2hxT5JxiR1MvqlqdWAL9d2Hcg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-2.0.0.tgz", + "integrity": "sha512-Xhrj1JU5LoLjJuujjTlvDfc/n3Shzk2hPlYmLdCx/lsltFFVuCFa9uM8u5mcHlmOUKP5pu9I54bAITxZBMHoXg==", "requires": { - "@kiosked/ulid": "^3.0.0", - "abab": "^2.0.3", - "browser-cookies": "^1.2.0", "tiny-hashes": "1.0.1" } }, @@ -24625,4 +24613,4 @@ } } } -} +} \ No newline at end of file diff --git a/package.json b/package.json index 04ec495c93d..8b8a4dc5fb4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "4.20.0-pre", + "version": "4.35.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { @@ -112,6 +112,6 @@ "fun-hooks": "^0.9.9", "jsencrypt": "^3.0.0-rc.1", "just-clone": "^1.0.2", - "live-connect-js": "^1.1.23" + "live-connect-js": "2.0.0" } } diff --git a/src/Renderer.js b/src/Renderer.js index 7cedf278537..c997658b30b 100644 --- a/src/Renderer.js +++ b/src/Renderer.js @@ -39,17 +39,22 @@ export function Renderer(options) { // use a function, not an arrow, in order to be able to pass "arguments" through this.render = function () { + const renderArgs = arguments + const runRender = () => { + if (this._render) { + this._render.apply(this, renderArgs) + } else { + utils.logWarn(`No render function was provided, please use .setRender on the renderer`); + } + } + if (!isRendererPreferredFromAdUnit(adUnitCode)) { // we expect to load a renderer url once only so cache the request to load script + this.cmd.unshift(runRender) // should render run first ? loadExternalScript(url, moduleCode, this.callback); } else { utils.logWarn(`External Js not loaded by Renderer since renderer url and callback is already defined on adUnit ${adUnitCode}`); - } - - if (this._render) { - this._render.apply(this, arguments) // _render is expected to use push as appropriate - } else { - utils.logWarn(`No render function was provided, please use .setRender on the renderer`); + runRender() } }.bind(this) // bind the function to this object to avoid 'this' errors } diff --git a/src/adapterManager.js b/src/adapterManager.js index 5124ae99694..f7f5d821932 100644 --- a/src/adapterManager.js +++ b/src/adapterManager.js @@ -22,9 +22,11 @@ let adapterManager = {}; let _bidderRegistry = adapterManager.bidderRegistry = {}; let _aliasRegistry = adapterManager.aliasRegistry = {}; -let _s2sConfig = {}; +let _s2sConfigs = []; config.getConfig('s2sConfig', config => { - _s2sConfig = config.s2sConfig; + if (config && config.s2sConfig) { + _s2sConfigs = Array.isArray(config.s2sConfig) ? config.s2sConfig : [config.s2sConfig]; + } }); var _analyticsRegistry = {}; @@ -66,7 +68,7 @@ function getBids({bidderCode, auctionId, bidderRequestId, adUnits, labels, src}) } bid = Object.assign({}, bid, getDefinedParams(adUnit, [ - 'fpd', + 'ortb2Imp', 'mediaType', 'renderer', 'storedAuctionResponse' @@ -118,15 +120,15 @@ function getBids({bidderCode, auctionId, bidderRequestId, adUnits, labels, src}) const hookedGetBids = hook('sync', getBids, 'getBids'); -function getAdUnitCopyForPrebidServer(adUnits) { - let adaptersServerSide = _s2sConfig.bidders; +function getAdUnitCopyForPrebidServer(adUnits, s2sConfig) { + let adaptersServerSide = s2sConfig.bidders; let adUnitsCopy = utils.deepClone(adUnits); adUnitsCopy.forEach((adUnit) => { // filter out client side bids adUnit.bids = adUnit.bids.filter((bid) => { return includes(adaptersServerSide, bid.bidder) && - (!doingS2STesting() || bid.finalSource !== s2sTestingModule.CLIENT); + (!doingS2STesting(s2sConfig) || bid.finalSource !== s2sTestingModule.CLIENT); }).map((bid) => { bid.bid_id = utils.getUniqueIdentifierStr(); return bid; @@ -145,7 +147,7 @@ function getAdUnitCopyForClientAdapters(adUnits) { // filter out s2s bids adUnitsClientCopy.forEach((adUnit) => { adUnit.bids = adUnit.bids.filter((bid) => { - return !doingS2STesting() || bid.finalSource !== s2sTestingModule.SERVER; + return !clientTestAdapters.length || bid.finalSource !== s2sTestingModule.SERVER; }) }); @@ -177,6 +179,21 @@ export let uspDataHandler = { } }; +// export for testing +export let clientTestAdapters = []; +export const allS2SBidders = []; + +export function getAllS2SBidders() { + adapterManager.s2STestingEnabled = false; + _s2sConfigs.forEach(s2sConfig => { + if (s2sConfig && s2sConfig.enabled) { + if (s2sConfig.bidders && s2sConfig.bidders.length) { + allS2SBidders.push(...s2sConfig.bidders); + } + } + }) +} + adapterManager.makeBidRequests = hook('sync', function (adUnits, auctionStart, auctionId, cbTimeout, labels) { /** * emit and pass adunits for external modification @@ -184,8 +201,6 @@ adapterManager.makeBidRequests = hook('sync', function (adUnits, auctionStart, a */ events.emit(CONSTANTS.EVENTS.BEFORE_REQUEST_BIDS, adUnits); - let bidRequests = []; - let bidderCodes = getBidderCodes(adUnits); if (config.getConfig('bidderSequence') === RANDOM) { bidderCodes = shuffle(bidderCodes); @@ -193,70 +208,86 @@ adapterManager.makeBidRequests = hook('sync', function (adUnits, auctionStart, a const refererInfo = getRefererInfo(); let clientBidderCodes = bidderCodes; - let clientTestAdapters = []; - - if (_s2sConfig.enabled) { - // if s2sConfig.bidderControl testing is turned on - if (doingS2STesting()) { - // get all adapters doing client testing - const bidderMap = s2sTestingModule.getSourceBidderMap(adUnits); - clientTestAdapters = bidderMap[s2sTestingModule.CLIENT]; - } - - // these are called on the s2s adapter - let adaptersServerSide = _s2sConfig.bidders; - // don't call these client side (unless client request is needed for testing) - clientBidderCodes = bidderCodes.filter(elm => - !includes(adaptersServerSide, elm) || includes(clientTestAdapters, elm) - ); + let bidRequests = []; - const adUnitsContainServerRequests = adUnits => Boolean( - find(adUnits, adUnit => find(adUnit.bids, bid => ( - bid.bidSource || - (_s2sConfig.bidderControl && _s2sConfig.bidderControl[bid.bidder]) - ) && bid.finalSource === s2sTestingModule.SERVER)) - ); + if (allS2SBidders.length === 0) { + getAllS2SBidders(); + } - if (isTestingServerOnly() && adUnitsContainServerRequests(adUnits)) { - clientBidderCodes.length = 0; + _s2sConfigs.forEach(s2sConfig => { + if (s2sConfig && s2sConfig.enabled) { + if (doingS2STesting(s2sConfig)) { + s2sTestingModule.calculateBidSources(s2sConfig); + const bidderMap = s2sTestingModule.getSourceBidderMap(adUnits, allS2SBidders); + // get all adapters doing client testing + bidderMap[s2sTestingModule.CLIENT].forEach(bidder => { + if (!includes(clientTestAdapters, bidder)) { + clientTestAdapters.push(bidder); + } + }) + } } + }) - let adUnitsS2SCopy = getAdUnitCopyForPrebidServer(adUnits); - let tid = utils.generateUUID(); - adaptersServerSide.forEach(bidderCode => { - const bidderRequestId = utils.getUniqueIdentifierStr(); - const bidderRequest = { - bidderCode, - auctionId, - bidderRequestId, - tid, - bids: hookedGetBids({bidderCode, auctionId, bidderRequestId, 'adUnits': utils.deepClone(adUnitsS2SCopy), labels, src: CONSTANTS.S2S.SRC}), - auctionStart: auctionStart, - timeout: _s2sConfig.timeout, - src: CONSTANTS.S2S.SRC, - refererInfo - }; - if (bidderRequest.bids.length !== 0) { - bidRequests.push(bidderRequest); + // don't call these client side (unless client request is needed for testing) + clientBidderCodes = bidderCodes.filter(bidderCode => { + return !includes(allS2SBidders, bidderCode) || includes(clientTestAdapters, bidderCode) + }); + + // these are called on the s2s adapter + let adaptersServerSide = allS2SBidders; + + const adUnitsContainServerRequests = (adUnits, s2sConfig) => Boolean( + find(adUnits, adUnit => find(adUnit.bids, bid => ( + bid.bidSource || + (s2sConfig.bidderControl && s2sConfig.bidderControl[bid.bidder]) + ) && bid.finalSource === s2sTestingModule.SERVER)) + ); + + _s2sConfigs.forEach(s2sConfig => { + if (s2sConfig && s2sConfig.enabled) { + if ((isTestingServerOnly(s2sConfig) && adUnitsContainServerRequests(adUnits, s2sConfig))) { + utils.logWarn('testServerOnly: True. All client requests will be suppressed.'); + clientBidderCodes.length = 0; } - }); - // update the s2sAdUnits object and remove all bids that didn't pass sizeConfig/label checks from getBids() - // this is to keep consistency and only allow bids/adunits that passed the checks to go to pbs - adUnitsS2SCopy.forEach((adUnitCopy) => { - let validBids = adUnitCopy.bids.filter((adUnitBid) => { - return find(bidRequests, request => { - return find(request.bids, (reqBid) => reqBid.bidId === adUnitBid.bid_id); - }); + let adUnitsS2SCopy = getAdUnitCopyForPrebidServer(adUnits, s2sConfig); + let tid = utils.generateUUID(); + adaptersServerSide.forEach(bidderCode => { + const bidderRequestId = utils.getUniqueIdentifierStr(); + const bidderRequest = { + bidderCode, + auctionId, + bidderRequestId, + tid, + bids: hookedGetBids({bidderCode, auctionId, bidderRequestId, 'adUnits': utils.deepClone(adUnitsS2SCopy), labels, src: CONSTANTS.S2S.SRC}), + auctionStart: auctionStart, + timeout: s2sConfig.timeout, + src: CONSTANTS.S2S.SRC, + refererInfo + }; + if (bidderRequest.bids.length !== 0) { + bidRequests.push(bidderRequest); + } }); - adUnitCopy.bids = validBids; - }); - bidRequests.forEach(request => { - request.adUnitsS2SCopy = adUnitsS2SCopy.filter(adUnitCopy => adUnitCopy.bids.length > 0); - }); - } + // update the s2sAdUnits object and remove all bids that didn't pass sizeConfig/label checks from getBids() + // this is to keep consistency and only allow bids/adunits that passed the checks to go to pbs + adUnitsS2SCopy.forEach((adUnitCopy) => { + let validBids = adUnitCopy.bids.filter((adUnitBid) => + find(bidRequests, request => + find(request.bids, (reqBid) => reqBid.bidId === adUnitBid.bid_id))); + adUnitCopy.bids = validBids; + }); + + bidRequests.forEach(request => { + if (request.adUnitsS2SCopy === undefined) { + request.adUnitsS2SCopy = adUnitsS2SCopy.filter(adUnitCopy => adUnitCopy.bids.length > 0); + } + }); + } + }) // client adapters let adUnitsClientCopy = getAdUnitCopyForClientAdapters(adUnits); @@ -306,56 +337,74 @@ adapterManager.callBids = (adUnits, bidRequests, addBidResponse, doneCb, request return partitions; }, [[], []]); - if (serverBidRequests.length) { - // s2s should get the same client side timeout as other client side requests. - const s2sAjax = ajaxBuilder(requestBidsTimeout, requestCallbacks ? { - request: requestCallbacks.request.bind(null, 's2s'), - done: requestCallbacks.done - } : undefined); - let adaptersServerSide = _s2sConfig.bidders; - const s2sAdapter = _bidderRegistry[_s2sConfig.adapter]; - let tid = serverBidRequests[0].tid; - let adUnitsS2SCopy = serverBidRequests[0].adUnitsS2SCopy; - - if (s2sAdapter) { - let s2sBidRequest = {tid, 'ad_units': adUnitsS2SCopy}; - if (s2sBidRequest.ad_units.length) { - let doneCbs = serverBidRequests.map(bidRequest => { - bidRequest.start = timestamp(); - return doneCb.bind(bidRequest); - }); - - // only log adapters that actually have adUnit bids - let allBidders = s2sBidRequest.ad_units.reduce((adapters, adUnit) => { - return adapters.concat((adUnit.bids || []).reduce((adapters, bid) => { return adapters.concat(bid.bidder) }, [])); - }, []); - utils.logMessage(`CALLING S2S HEADER BIDDERS ==== ${adaptersServerSide.filter(adapter => { - return includes(allBidders, adapter); - }).join(',')}`); - - // fire BID_REQUESTED event for each s2s bidRequest - serverBidRequests.forEach(bidRequest => { - events.emit(CONSTANTS.EVENTS.BID_REQUESTED, bidRequest); - }); + var uniqueServerBidRequests = []; + serverBidRequests.forEach(serverBidRequest => { + var index = -1; + for (var i = 0; i < uniqueServerBidRequests.length; ++i) { + if (serverBidRequest.tid === uniqueServerBidRequests[i].tid) { + index = i; + break; + } + } + if (index <= -1) { + uniqueServerBidRequests.push(serverBidRequest); + } + }); - // make bid requests - s2sAdapter.callBids( - s2sBidRequest, - serverBidRequests, - function(adUnitCode, bid) { - let bidderRequest = getBidderRequest(serverBidRequests, bid.bidderCode, adUnitCode); - if (bidderRequest) { - addBidResponse.call(bidderRequest, adUnitCode, bid) - } - }, - () => doneCbs.forEach(done => done()), - s2sAjax - ); + let counter = 0 + _s2sConfigs.forEach((s2sConfig) => { + if (s2sConfig && uniqueServerBidRequests[counter] && includes(s2sConfig.bidders, uniqueServerBidRequests[counter].bidderCode)) { + // s2s should get the same client side timeout as other client side requests. + const s2sAjax = ajaxBuilder(requestBidsTimeout, requestCallbacks ? { + request: requestCallbacks.request.bind(null, 's2s'), + done: requestCallbacks.done + } : undefined); + let adaptersServerSide = s2sConfig.bidders; + const s2sAdapter = _bidderRegistry[s2sConfig.adapter]; + let tid = uniqueServerBidRequests[counter].tid; + let adUnitsS2SCopy = uniqueServerBidRequests[counter].adUnitsS2SCopy; + + let uniqueServerRequests = serverBidRequests.filter(serverBidRequest => serverBidRequest.tid === tid) + + if (s2sAdapter) { + let s2sBidRequest = {tid, 'ad_units': adUnitsS2SCopy, s2sConfig}; + if (s2sBidRequest.ad_units.length) { + let doneCbs = uniqueServerRequests.map(bidRequest => { + bidRequest.start = timestamp(); + return doneCb.bind(bidRequest); + }); + + // only log adapters that actually have adUnit bids + let allBidders = s2sBidRequest.ad_units.reduce((adapters, adUnit) => { + return adapters.concat((adUnit.bids || []).reduce((adapters, bid) => adapters.concat(bid.bidder), [])); + }, []); + utils.logMessage(`CALLING S2S HEADER BIDDERS ==== ${adaptersServerSide.filter(adapter => includes(allBidders, adapter)).join(',')}`); + + // fire BID_REQUESTED event for each s2s bidRequest + uniqueServerRequests.forEach(bidRequest => { + events.emit(CONSTANTS.EVENTS.BID_REQUESTED, bidRequest); + }); + + // make bid requests + s2sAdapter.callBids( + s2sBidRequest, + serverBidRequests, + (adUnitCode, bid) => { + let bidderRequest = getBidderRequest(serverBidRequests, bid.bidderCode, adUnitCode); + if (bidderRequest) { + addBidResponse.call(bidderRequest, adUnitCode, bid) + } + }, + () => doneCbs.forEach(done => done()), + s2sAjax + ); + } + } else { + utils.logError('missing ' + s2sConfig.adapter); } - } else { - utils.logError('missing ' + _s2sConfig.adapter); + counter++ } - } + }); // handle client adapter requests clientBidRequests.forEach(bidRequest => { @@ -390,27 +439,27 @@ adapterManager.callBids = (adUnits, bidRequests, addBidResponse, doneCb, request }); }; -function doingS2STesting() { - return _s2sConfig && _s2sConfig.enabled && _s2sConfig.testing && s2sTestingModule; +function doingS2STesting(s2sConfig) { + return s2sConfig && s2sConfig.enabled && s2sConfig.testing && s2sTestingModule; } -function isTestingServerOnly() { - return Boolean(doingS2STesting() && _s2sConfig.testServerOnly); +function isTestingServerOnly(s2sConfig) { + return Boolean(doingS2STesting(s2sConfig) && s2sConfig.testServerOnly); }; function getSupportedMediaTypes(bidderCode) { - let result = []; - if (includes(adapterManager.videoAdapters, bidderCode)) result.push('video'); - if (includes(nativeAdapters, bidderCode)) result.push('native'); - return result; + let supportedMediaTypes = []; + if (includes(adapterManager.videoAdapters, bidderCode)) supportedMediaTypes.push('video'); + if (includes(nativeAdapters, bidderCode)) supportedMediaTypes.push('native'); + return supportedMediaTypes; } adapterManager.videoAdapters = []; // added by adapterLoader for now -adapterManager.registerBidAdapter = function (bidAdaptor, bidderCode, {supportedMediaTypes = []} = {}) { - if (bidAdaptor && bidderCode) { - if (typeof bidAdaptor.callBids === 'function') { - _bidderRegistry[bidderCode] = bidAdaptor; +adapterManager.registerBidAdapter = function (bidAdapter, bidderCode, {supportedMediaTypes = []} = {}) { + if (bidAdapter && bidderCode) { + if (typeof bidAdapter.callBids === 'function') { + _bidderRegistry[bidderCode] = bidAdapter; if (includes(supportedMediaTypes, 'video')) { adapterManager.videoAdapters.push(bidderCode); @@ -422,7 +471,7 @@ adapterManager.registerBidAdapter = function (bidAdaptor, bidderCode, {supported utils.logError('Bidder adaptor error for bidder code: ' + bidderCode + 'bidder must implement a callBids() function'); } } else { - utils.logError('bidAdaptor or bidderCode not specified'); + utils.logError('bidAdapter or bidderCode not specified'); } }; @@ -430,30 +479,37 @@ adapterManager.aliasBidAdapter = function (bidderCode, alias, options) { let existingAlias = _bidderRegistry[alias]; if (typeof existingAlias === 'undefined') { - let bidAdaptor = _bidderRegistry[bidderCode]; - if (typeof bidAdaptor === 'undefined') { + let bidAdapter = _bidderRegistry[bidderCode]; + if (typeof bidAdapter === 'undefined') { // check if alias is part of s2sConfig and allow them to register if so (as base bidder may be s2s-only) - const s2sConfig = config.getConfig('s2sConfig'); - const s2sBidders = s2sConfig && s2sConfig.bidders; - - if (!(s2sBidders && includes(s2sBidders, alias))) { + const nonS2SAlias = []; + _s2sConfigs.forEach(s2sConfig => { + if (s2sConfig.bidders && s2sConfig.bidders.length) { + const s2sBidders = s2sConfig && s2sConfig.bidders; + if (!(s2sConfig && includes(s2sBidders, alias))) { + nonS2SAlias.push(bidderCode); + } else { + _aliasRegistry[alias] = bidderCode; + } + } + }); + nonS2SAlias.forEach(bidderCode => { utils.logError('bidderCode "' + bidderCode + '" is not an existing bidder.', 'adapterManager.aliasBidAdapter'); - } else { - _aliasRegistry[alias] = bidderCode; - } + }) } else { try { let newAdapter; let supportedMediaTypes = getSupportedMediaTypes(bidderCode); // Have kept old code to support backward compatibilitiy. // Remove this if loop when all adapters are supporting bidderFactory. i.e When Prebid.js is 1.0 - if (bidAdaptor.constructor.prototype != Object.prototype) { - newAdapter = new bidAdaptor.constructor(); + if (bidAdapter.constructor.prototype != Object.prototype) { + newAdapter = new bidAdapter.constructor(); newAdapter.setBidderCode(alias); } else { - let spec = bidAdaptor.getSpec(); + let spec = bidAdapter.getSpec(); let gvlid = options && options.gvlid; - newAdapter = newBidder(Object.assign({}, spec, { code: alias, gvlid })); + let skipPbsAliasing = options && options.skipPbsAliasing; + newAdapter = newBidder(Object.assign({}, spec, { code: alias, gvlid, skipPbsAliasing })); _aliasRegistry[alias] = bidderCode; } adapterManager.registerBidAdapter(newAdapter, alias, { @@ -550,4 +606,8 @@ adapterManager.callSetTargetingBidder = function(bidder, bid) { tryCallBidderMethod(bidder, 'onSetTargeting', bid); }; +adapterManager.callBidViewableBidder = function(bidder, bid) { + tryCallBidderMethod(bidder, 'onBidViewable', bid); +}; + export default adapterManager; diff --git a/src/adapters/bidderFactory.js b/src/adapters/bidderFactory.js index 3b6260efc88..27dcc9c0115 100644 --- a/src/adapters/bidderFactory.js +++ b/src/adapters/bidderFactory.js @@ -155,12 +155,14 @@ export function registerBidder(spec) { spec.aliases.forEach(alias => { let aliasCode = alias; let gvlid; + let skipPbsAliasing; if (isPlainObject(alias)) { aliasCode = alias.code; gvlid = alias.gvlid; + skipPbsAliasing = alias.skipPbsAliasing } adapterManager.aliasRegistry[aliasCode] = spec.code; - putBidder(Object.assign({}, spec, { code: aliasCode, gvlid })); + putBidder(Object.assign({}, spec, { code: aliasCode, gvlid, skipPbsAliasing })); }); } } diff --git a/src/adloader.js b/src/adloader.js index 1c18ce82b16..5460cc79410 100644 --- a/src/adloader.js +++ b/src/adloader.js @@ -4,6 +4,7 @@ import * as utils from './utils.js'; const _requestCache = {}; // The below list contains modules or vendors whom Prebid allows to load external JS. const _approvedLoadExternalJSList = [ + 'adloox', 'criteo', 'outstream', 'adagio', diff --git a/src/auction.js b/src/auction.js index c94e3adc9a7..cb7e0f60352 100644 --- a/src/auction.js +++ b/src/auction.js @@ -452,13 +452,13 @@ export function addBidToAuction(auctionInstance, bidResponse) { function tryAddVideoBid(auctionInstance, bidResponse, bidRequests, afterBidAdded) { let addBid = true; - const bidderRequest = getBidRequest(bidResponse.requestId, [bidRequests]); + const bidderRequest = getBidRequest(bidResponse.originalRequestId || bidResponse.requestId, [bidRequests]); const videoMediaType = bidderRequest && deepAccess(bidderRequest, 'mediaTypes.video'); const context = videoMediaType && deepAccess(videoMediaType, 'context'); if (config.getConfig('cache.url') && context !== OUTSTREAM) { - if (!bidResponse.videoCacheKey) { + if (!bidResponse.videoCacheKey || config.getConfig('cache.ignoreBidderCacheKey')) { addBid = false; callPrebidCache(auctionInstance, bidResponse, afterBidAdded, bidderRequest); } else if (!bidResponse.vastUrl) { @@ -519,7 +519,7 @@ function getPreparedBidForAuction({adUnitCode, bid, bidderRequest, auctionId}) { events.emit(CONSTANTS.EVENTS.BID_ADJUSTMENT, bidObject); // a publisher-defined renderer can be used to render bids - const bidReq = bidderRequest.bids && find(bidderRequest.bids, bid => bid.adUnitCode == adUnitCode); + const bidReq = bidderRequest.bids && find(bidderRequest.bids, bid => bid.adUnitCode == adUnitCode && bid.bidId == bidObject.requestId); const adUnitRenderer = bidReq && bidReq.renderer; // a publisher can also define a renderer for a mediaType @@ -533,9 +533,9 @@ function getPreparedBidForAuction({adUnitCode, bid, bidderRequest, auctionId}) { var renderer = null; // the renderer for the mediaType takes precendence - if (mediaTypeRenderer && mediaTypeRenderer.url && !(mediaTypeRenderer.backupOnly === true && mediaTypeRenderer.render)) { + if (mediaTypeRenderer && mediaTypeRenderer.url && mediaTypeRenderer.render && !(mediaTypeRenderer.backupOnly === true && bid.renderer)) { renderer = mediaTypeRenderer; - } else if (adUnitRenderer && adUnitRenderer.url && !(adUnitRenderer.backupOnly === true && bid.renderer)) { + } else if (adUnitRenderer && adUnitRenderer.url && adUnitRenderer.render && !(adUnitRenderer.backupOnly === true && bid.renderer)) { renderer = adUnitRenderer; } @@ -626,6 +626,16 @@ export const getPriceByGranularity = (granularity) => { } } +/** + * This function returns a function to get first advertiser domain from bid response meta + * @returns {function} + */ +export const getAdvertiserDomain = () => { + return (bid) => { + return (bid.meta && bid.meta.advertiserDomains && bid.meta.advertiserDomains.length > 0) ? bid.meta.advertiserDomains[0] : ''; + } +} + /** * @param {string} mediaType * @param {string} bidderCode @@ -662,6 +672,7 @@ export function getStandardBidderSettings(mediaType, bidderCode, bidReq) { createKeyVal(TARGETING_KEYS.DEAL, 'dealId'), createKeyVal(TARGETING_KEYS.SOURCE, 'source'), createKeyVal(TARGETING_KEYS.FORMAT, 'mediaType'), + createKeyVal(TARGETING_KEYS.ADOMAIN, getAdvertiserDomain()), ] } diff --git a/src/config.js b/src/config.js index daaf739bbbd..51184a8014d 100644 --- a/src/config.js +++ b/src/config.js @@ -324,6 +324,119 @@ export function newConfig() { return bidderConfig; } + /** + * Returns backwards compatible FPD data for modules + */ + function getLegacyFpd(obj) { + if (typeof obj !== 'object') return; + + let duplicate = {}; + + Object.keys(obj).forEach((type) => { + let prop = (type === 'site') ? 'context' : type; + duplicate[prop] = (prop === 'context' || prop === 'user') ? Object.keys(obj[type]).filter(key => key !== 'data').reduce((result, key) => { + if (key === 'ext') { + utils.mergeDeep(result, obj[type][key]); + } else { + utils.mergeDeep(result, {[key]: obj[type][key]}); + } + + return result; + }, {}) : obj[type]; + }); + + return duplicate; + } + + /** + * Returns backwards compatible FPD data for modules + */ + function getLegacyImpFpd(obj) { + if (typeof obj !== 'object') return; + + let duplicate = {}; + + if (utils.deepAccess(obj, 'ext.data')) { + Object.keys(obj.ext.data).forEach((key) => { + if (key === 'pbadslot') { + utils.mergeDeep(duplicate, {context: {pbAdSlot: obj.ext.data[key]}}); + } else if (key === 'adserver') { + utils.mergeDeep(duplicate, {context: {adServer: obj.ext.data[key]}}); + } else { + utils.mergeDeep(duplicate, {context: {data: {[key]: obj.ext.data[key]}}}); + } + }); + } + + return duplicate; + } + + /** + * Copy FPD over to OpenRTB standard format in config + */ + function convertFpd(opt) { + let duplicate = {}; + + Object.keys(opt).forEach((type) => { + let prop = (type === 'context') ? 'site' : type; + duplicate[prop] = (prop === 'site' || prop === 'user') ? Object.keys(opt[type]).reduce((result, key) => { + if (key === 'data') { + utils.mergeDeep(result, {ext: {data: opt[type][key]}}); + } else { + utils.mergeDeep(result, {[key]: opt[type][key]}); + } + + return result; + }, {}) : opt[type]; + }); + + return duplicate; + } + + /** + * Copy Impression FPD over to OpenRTB standard format in config + * Only accepts bid level context.data values with pbAdSlot and adServer exceptions + */ + function convertImpFpd(opt) { + let duplicate = {}; + + Object.keys(opt).filter(prop => prop === 'context').forEach((type) => { + Object.keys(opt[type]).forEach((key) => { + if (key === 'data') { + utils.mergeDeep(duplicate, {ext: {data: opt[type][key]}}); + } else { + if (typeof opt[type][key] === 'object' && !Array.isArray(opt[type][key])) { + Object.keys(opt[type][key]).forEach(data => { + utils.mergeDeep(duplicate, {ext: {data: {[key.toLowerCase()]: {[data.toLowerCase()]: opt[type][key][data]}}}}); + }); + } else { + utils.mergeDeep(duplicate, {ext: {data: {[key.toLowerCase()]: opt[type][key]}}}); + } + } + }); + }); + + return duplicate; + } + + /** + * Copy FPD over to OpenRTB standard format in each adunit + */ + function convertAdUnitFpd(arr) { + let convert = []; + + arr.forEach((adunit) => { + if (adunit.fpd) { + (adunit['ortb2Imp']) ? utils.mergeDeep(adunit['ortb2Imp'], convertImpFpd(adunit.fpd)) : adunit['ortb2Imp'] = convertImpFpd(adunit.fpd); + convert.push((({ fpd, ...duplicate }) => duplicate)(adunit)); + } else { + convert.push(adunit); + } + }); + + return convert; + } + /* * Sets configuration given an object containing key-value pairs and calls * listeners that were added by the `subscribe` function @@ -338,13 +451,14 @@ export function newConfig() { let topicalConfig = {}; topics.forEach(topic => { - let option = options[topic]; + let prop = (topic === 'fpd') ? 'ortb2' : topic; + let option = (topic === 'fpd') ? convertFpd(options[topic]) : options[topic]; - if (utils.isPlainObject(defaults[topic]) && utils.isPlainObject(option)) { - option = Object.assign({}, defaults[topic], option); + if (utils.isPlainObject(defaults[prop]) && utils.isPlainObject(option)) { + option = Object.assign({}, defaults[prop], option); } - topicalConfig[topic] = config[topic] = option; + topicalConfig[prop] = config[prop] = option; }); callSubscribers(topicalConfig); @@ -437,11 +551,13 @@ export function newConfig() { bidderConfig[bidder] = {}; } Object.keys(config.config).forEach(topic => { - let option = config.config[topic]; + let prop = (topic === 'fpd') ? 'ortb2' : topic; + let option = (topic === 'fpd') ? convertFpd(config.config[topic]) : config.config[topic]; + if (utils.isPlainObject(option)) { - bidderConfig[bidder][topic] = Object.assign({}, bidderConfig[bidder][topic] || {}, option); + bidderConfig[bidder][prop] = Object.assign({}, bidderConfig[bidder][prop] || {}, option); } else { - bidderConfig[bidder][topic] = option; + bidderConfig[bidder][prop] = option; } }); }); @@ -499,7 +615,10 @@ export function newConfig() { runWithBidder, callbackWithBidder, setBidderConfig, - getBidderConfig + getBidderConfig, + convertAdUnitFpd, + getLegacyFpd, + getLegacyImpFpd }; } diff --git a/src/constants.json b/src/constants.json index 7e82946b65c..e6b9687f911 100644 --- a/src/constants.json +++ b/src/constants.json @@ -38,7 +38,8 @@ "ADD_AD_UNITS": "addAdUnits", "AD_RENDER_FAILED": "adRenderFailed", "TCF2_ENFORCEMENT": "tcf2Enforcement", - "AUCTION_DEBUG": "auctionDebug" + "AUCTION_DEBUG": "auctionDebug", + "BID_VIEWABLE": "bidViewable" }, "AD_RENDER_FAILED_REASON" : { "PREVENT_WRITING_ON_MAIN_DOCUMENT": "preventWritingOnMainDocument", @@ -59,6 +60,19 @@ "CUSTOM": "custom" }, "TARGETING_KEYS": { + "BIDDER": "hb_bidder", + "AD_ID": "hb_adid", + "PRICE_BUCKET": "hb_pb", + "SIZE": "hb_size", + "DEAL": "hb_deal", + "SOURCE": "hb_source", + "FORMAT": "hb_format", + "UUID": "hb_uuid", + "CACHE_ID": "hb_cache_id", + "CACHE_HOST": "hb_cache_host", + "ADOMAIN" : "hb_adomain" + }, + "DEFAULT_TARGETING_KEYS": { "BIDDER": "hb_bidder", "AD_ID": "hb_adid", "PRICE_BUCKET": "hb_pb", @@ -88,7 +102,9 @@ "likes": "hb_native_likes", "phone": "hb_native_phone", "price": "hb_native_price", - "salePrice": "hb_native_saleprice" + "salePrice": "hb_native_saleprice", + "rendererUrl": "hb_renderer_url", + "adTemplate": "hb_adTemplate" }, "S2S" : { "SRC" : "s2s", diff --git a/src/native.js b/src/native.js index e41d7740ffa..b43e582327b 100644 --- a/src/native.js +++ b/src/native.js @@ -154,21 +154,48 @@ export function fireNativeTrackers(message, adObject) { export function getNativeTargeting(bid, bidReq) { let keyValues = {}; - Object.keys(bid['native']).forEach(asset => { - const key = CONSTANTS.NATIVE_KEYS[asset]; - let value = getAssetValue(bid['native'][asset]); + if (deepAccess(bidReq, 'nativeParams.rendererUrl')) { + bid['native']['rendererUrl'] = getAssetValue(bidReq.nativeParams['rendererUrl']); + } else if (deepAccess(bidReq, 'nativeParams.adTemplate')) { + bid['native']['adTemplate'] = getAssetValue(bidReq.nativeParams['adTemplate']); + } + + const globalSendTargetingKeys = deepAccess( + bidReq, + `nativeParams.sendTargetingKeys` + ) !== false; + + const nativeKeys = getNativeKeys(bidReq); + + const flatBidNativeKeys = { ...bid.native, ...bid.native.ext }; + delete flatBidNativeKeys.ext; - const sendPlaceholder = deepAccess( - bidReq, - `mediaTypes.native.${asset}.sendId` - ); + Object.keys(flatBidNativeKeys).forEach(asset => { + const key = nativeKeys[asset]; + let value = getAssetValue(bid.native[asset]) || getAssetValue(deepAccess(bid, `native.ext.${asset}`)); + + if (asset === 'adTemplate' || !key || !value) { + return; + } + + let sendPlaceholder = deepAccess(bidReq, `nativeParams.${asset}.sendId`); + if (typeof sendPlaceholder !== 'boolean') { + sendPlaceholder = deepAccess(bidReq, `nativeParams.ext.${asset}.sendId`); + } if (sendPlaceholder) { const placeholder = `${key}:${bid.adId}`; value = placeholder; } - if (key && value) { + let assetSendTargetingKeys = deepAccess(bidReq, `nativeParams.${asset}.sendTargetingKeys`) + if (typeof assetSendTargetingKeys !== 'boolean') { + assetSendTargetingKeys = deepAccess(bidReq, `nativeParams.ext.${asset}.sendTargetingKeys`); + } + + const sendTargeting = typeof assetSendTargetingKeys === 'boolean' ? assetSendTargetingKeys : globalSendTargetingKeys; + + if (sendTargeting) { keyValues[key] = value; } }); @@ -187,6 +214,12 @@ export function getAssetMessage(data, adObject) { assets: [], }; + if (adObject.native.hasOwnProperty('adTemplate')) { + message.adTemplate = getAssetValue(adObject.native['adTemplate']); + } if (adObject.native.hasOwnProperty('rendererUrl')) { + message.rendererUrl = getAssetValue(adObject.native['rendererUrl']); + } + data.assets.forEach(asset => { const key = getKeyByValue(CONSTANTS.NATIVE_KEYS, asset); const value = getAssetValue(adObject.native[key]); @@ -197,6 +230,35 @@ export function getAssetMessage(data, adObject) { return message; } +export function getAllAssetsMessage(data, adObject) { + const message = { + message: 'assetResponse', + adId: data.adId, + assets: [] + }; + + Object.keys(adObject.native).forEach(function(key, index) { + if (key === 'adTemplate' && adObject.native[key]) { + message.adTemplate = getAssetValue(adObject.native[key]); + } else if (key === 'rendererUrl' && adObject.native[key]) { + message.rendererUrl = getAssetValue(adObject.native[key]); + } else if (key === 'ext') { + Object.keys(adObject.native[key]).forEach(extKey => { + if (adObject.native[key][extKey]) { + const value = getAssetValue(adObject.native[key][extKey]); + message.assets.push({ key: extKey, value }); + } + }) + } else if (adObject.native[key] && CONSTANTS.NATIVE_KEYS.hasOwnProperty(key)) { + const value = getAssetValue(adObject.native[key]); + + message.assets.push({ key, value }); + } + }); + + return message; +} + /** * Native assets can be a string or an object with a url prop. Returns the value * appropriate for sending in adserver targeting or placeholder replacement. @@ -208,3 +270,18 @@ function getAssetValue(value) { return value; } + +function getNativeKeys(bidReq) { + const extraNativeKeys = {} + + if (deepAccess(bidReq, 'nativeParams.ext')) { + Object.keys(bidReq.nativeParams.ext).forEach(extKey => { + extraNativeKeys[extKey] = `hb_native_${extKey}`; + }) + } + + return { + ...CONSTANTS.NATIVE_KEYS, + ...extraNativeKeys + } +} diff --git a/src/prebid.js b/src/prebid.js index 0f72ca878e5..6565c1610d8 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -488,6 +488,18 @@ $$PREBID_GLOBAL$$.requestBids = hook('async', function ({ bidsBackHandler, timeo utils.logInfo('Invoking $$PREBID_GLOBAL$$.requestBids', arguments); + let _s2sConfigs = []; + const s2sBidders = []; + config.getConfig('s2sConfig', config => { + if (config && config.s2sConfig) { + _s2sConfigs = Array.isArray(config.s2sConfig) ? config.s2sConfig : [config.s2sConfig]; + } + }); + + _s2sConfigs.forEach(s2sConfig => { + s2sBidders.push(...s2sConfig.bidders); + }); + adUnits = checkAdUnitSetup(adUnits); if (adUnitCodes && adUnitCodes.length) { @@ -512,11 +524,7 @@ $$PREBID_GLOBAL$$.requestBids = hook('async', function ({ bidsBackHandler, timeo const allBidders = adUnit.bids.map(bid => bid.bidder); const bidderRegistry = adapterManager.bidderRegistry; - const s2sConfig = config.getConfig('s2sConfig'); - const s2sBidders = s2sConfig && s2sConfig.bidders; - const bidders = (s2sBidders) ? allBidders.filter(bidder => { - return !includes(s2sBidders, bidder); - }) : allBidders; + const bidders = (s2sBidders) ? allBidders.filter(bidder => !includes(s2sBidders, bidder)) : allBidders; adUnit.transactionId = utils.generateUUID(); @@ -587,11 +595,7 @@ $$PREBID_GLOBAL$$.requestBids.before(executeCallbacks, 49); */ $$PREBID_GLOBAL$$.addAdUnits = function (adUnitArr) { utils.logInfo('Invoking $$PREBID_GLOBAL$$.addAdUnits', arguments); - if (utils.isArray(adUnitArr)) { - $$PREBID_GLOBAL$$.adUnits.push.apply($$PREBID_GLOBAL$$.adUnits, adUnitArr); - } else if (typeof adUnitArr === 'object') { - $$PREBID_GLOBAL$$.adUnits.push(adUnitArr); - } + $$PREBID_GLOBAL$$.adUnits.push.apply($$PREBID_GLOBAL$$.adUnits, config.convertAdUnitFpd(utils.isArray(adUnitArr) ? adUnitArr : [adUnitArr])); // emit event events.emit(ADD_AD_UNITS); }; diff --git a/src/secureCreatives.js b/src/secureCreatives.js index cb192dd773e..def1a9abdbb 100644 --- a/src/secureCreatives.js +++ b/src/secureCreatives.js @@ -4,7 +4,7 @@ */ import events from './events.js'; -import { fireNativeTrackers, getAssetMessage } from './native.js'; +import { fireNativeTrackers, getAssetMessage, getAllAssetsMessage } from './native.js'; import constants from './constants.json'; import { logWarn, replaceAuctionPrice } from './utils.js'; import { auctionManager } from './auctionManager.js'; @@ -51,6 +51,13 @@ function receiveMessage(ev) { const message = getAssetMessage(data, adObject); ev.source.postMessage(JSON.stringify(message), ev.origin); return; + } else if (data.action === 'allAssetRequest') { + const message = getAllAssetsMessage(data, adObject); + ev.source.postMessage(JSON.stringify(message), ev.origin); + } else if (data.action === 'resizeNativeHeight') { + adObject.height = data.height; + adObject.width = data.width; + resizeRemoteCreative(adObject); } const trackerType = fireNativeTrackers(data, adObject); diff --git a/src/storageManager.js b/src/storageManager.js index 60e5a7706d0..66a0cf68cbf 100644 --- a/src/storageManager.js +++ b/src/storageManager.js @@ -1,4 +1,4 @@ -import { hook } from './hook.js'; +import {hook} from './hook.js'; import * as utils from './utils.js'; import includes from 'core-js-pure/features/array/includes.js'; @@ -110,7 +110,12 @@ export function newStorageManager({gvlid, moduleName, moduleType} = {}) { try { localStorage.setItem('prebid.cookieTest', '1'); return localStorage.getItem('prebid.cookieTest') === '1'; - } catch (error) {} + } catch (error) { + } finally { + try { + localStorage.removeItem('prebid.cookieTest'); + } catch (error) {} + } } return false; } diff --git a/src/targeting.js b/src/targeting.js index b6a38bdbb61..365453e1e8f 100644 --- a/src/targeting.js +++ b/src/targeting.js @@ -4,7 +4,9 @@ import { NATIVE_TARGETING_KEYS } from './native.js'; import { auctionManager } from './auctionManager.js'; import { sizeSupported } from './sizeMapping.js'; import { ADPOD } from './mediaTypes.js'; +import { hook } from './hook.js'; import includes from 'core-js-pure/features/array/includes.js'; +import find from 'core-js-pure/features/array/find.js'; const utils = require('./utils.js'); var CONSTANTS = require('./constants.json'); @@ -19,7 +21,7 @@ export const TARGETING_KEYS = Object.keys(CONSTANTS.TARGETING_KEYS).map( ); // return unexpired bids -const isBidNotExpired = (bid) => (bid.responseTimestamp + bid.ttl * 1000 + TTL_BUFFER) > timestamp(); +const isBidNotExpired = (bid) => (bid.responseTimestamp + bid.ttl * 1000 - TTL_BUFFER) > timestamp(); // return bids whose status is not set. Winning bids can only have a status of `rendered`. const isUnusedBid = (bid) => bid && ((bid.status && !includes([CONSTANTS.BID_STATUS.RENDERED], bid.status)) || !bid.status); @@ -32,26 +34,31 @@ export let filters = { // If two bids are found for same adUnitCode, we will use the highest one to take part in auction // This can happen in case of concurrent auctions // If adUnitBidLimit is set above 0 return top N number of bids -export function getHighestCpmBidsFromBidPool(bidsReceived, highestCpmCallback, adUnitBidLimit = 0) { - const bids = []; - const dealPrioritization = config.getConfig('sendBidsControl.dealPrioritization'); - // bucket by adUnitcode - let buckets = groupBy(bidsReceived, 'adUnitCode'); - // filter top bid for each bucket by bidder - Object.keys(buckets).forEach(bucketKey => { - let bucketBids = []; - let bidsByBidder = groupBy(buckets[bucketKey], 'bidderCode'); - Object.keys(bidsByBidder).forEach(key => bucketBids.push(bidsByBidder[key].reduce(highestCpmCallback))); - // if adUnitBidLimit is set, pass top N number bids - if (adUnitBidLimit > 0) { - bucketBids = dealPrioritization ? bucketBids.sort(sortByDealAndPriceBucketOrCpm(true)) : bucketBids.sort((a, b) => b.cpm - a.cpm); - bids.push(...bucketBids.slice(0, adUnitBidLimit)); - } else { - bids.push(...bucketBids); - } - }); - return bids; -} +export const getHighestCpmBidsFromBidPool = hook('sync', function(bidsReceived, highestCpmCallback, adUnitBidLimit = 0, hasModified = false) { + if (!hasModified) { + const bids = []; + const dealPrioritization = config.getConfig('sendBidsControl.dealPrioritization'); + // bucket by adUnitcode + let buckets = groupBy(bidsReceived, 'adUnitCode'); + // filter top bid for each bucket by bidder + Object.keys(buckets).forEach(bucketKey => { + let bucketBids = []; + let bidsByBidder = groupBy(buckets[bucketKey], 'bidderCode'); + Object.keys(bidsByBidder).forEach(key => bucketBids.push(bidsByBidder[key].reduce(highestCpmCallback))); + // if adUnitBidLimit is set, pass top N number bids + if (adUnitBidLimit > 0) { + bucketBids = dealPrioritization ? bucketBids.sort(sortByDealAndPriceBucketOrCpm(true)) : bucketBids.sort((a, b) => b.cpm - a.cpm); + bids.push(...bucketBids.slice(0, adUnitBidLimit)); + } else { + bids.push(...bucketBids); + } + }); + + return bids; + } + + return bidsReceived; +}) /** * A descending sort function that will sort the list of objects based on the following two dimensions: @@ -200,7 +207,7 @@ export function newTargeting(auctionManager) { // check if key is in default keys, if not, it's custom, we won't remove it. const isCustom = defaultKeys.filter(defaultKey => key.indexOf(defaultKeyring[defaultKey]) === 0).length === 0; // check if key explicitly allowed, if not, we'll remove it. - const found = isCustom || allowedKeys.find(allowedKey => { + const found = isCustom || find(allowedKeys, allowedKey => { const allowedKeyName = defaultKeyring[allowedKey]; // we're looking to see if the key exactly starts with one of our default keys. // (which hopefully means it's not custom) @@ -249,7 +256,8 @@ export function newTargeting(auctionManager) { }); }); - const allowedKeys = config.getConfig('targetingControls.allowTargetingKeys'); + const defaultKeys = Object.keys(Object.assign({}, CONSTANTS.DEFAULT_TARGETING_KEYS, CONSTANTS.NATIVE_KEYS)); + const allowedKeys = config.getConfig('targetingControls.allowTargetingKeys') || defaultKeys; if (Array.isArray(allowedKeys) && allowedKeys.length > 0) { targeting = getAllowedTargetingKeyValues(targeting, allowedKeys); } diff --git a/src/userSync.js b/src/userSync.js index fceeb1d722d..f653880fa29 100644 --- a/src/userSync.js +++ b/src/userSync.js @@ -225,7 +225,7 @@ export function newUserSync(userSyncDependencies) { } return checkForFiltering[filterType](biddersToFilter, bidder); } - return false; + return !permittedPixels[type]; } /** diff --git a/src/utils.js b/src/utils.js index acdf0f101ad..bd3257ab7e7 100644 --- a/src/utils.js +++ b/src/utils.js @@ -43,6 +43,14 @@ export const internal = { deepEqual }; +let prebidInternal = {} +/** + * Returns object that is used as internal prebid namespace + */ +export function getPrebidInternal() { + return prebidInternal; +} + var uniqueRef = {}; export let bind = function(a, b) { return b; }.bind(null, 1, uniqueRef)() === uniqueRef ? Function.prototype.bind diff --git a/test/fixtures/fixtures.js b/test/fixtures/fixtures.js index 2a0a7638fc4..908382f8daa 100644 --- a/test/fixtures/fixtures.js +++ b/test/fixtures/fixtures.js @@ -676,7 +676,13 @@ export function getAdUnits() { 'bidder': 'appnexus', 'params': { 'placementId': '543221', - 'test': 'me' + } + }, + { + 'bidder': 'pubmatic', + 'params': { + 'publisherId': 1234567, + 'adSlot': '1234567@728x90' } } ] diff --git a/test/mocks/adloaderStub.js b/test/mocks/adloaderStub.js index b52ca5e9280..6e7f5756100 100644 --- a/test/mocks/adloaderStub.js +++ b/test/mocks/adloaderStub.js @@ -4,7 +4,7 @@ import * as adloader from 'src/adloader.js'; // this export is for adloader's tests against actual implementation export let loadExternalScript = adloader.loadExternalScript; -let stub = createStub(); +export let loadExternalScriptStub = createStub(); function createStub() { return sinon.stub(adloader, 'loadExternalScript').callsFake((...args) => { @@ -18,6 +18,6 @@ function createStub() { } beforeEach(function() { - stub.restore(); - stub = createStub(); + loadExternalScriptStub.restore(); + loadExternalScriptStub = createStub(); }); diff --git a/test/spec/adUnits_spec.js b/test/spec/adUnits_spec.js index 7dd48a13208..baa5b4ac8c4 100644 --- a/test/spec/adUnits_spec.js +++ b/test/spec/adUnits_spec.js @@ -23,6 +23,16 @@ describe('Publisher API _ AdUnits', function () { } ] }, { + fpd: { + context: { + pbAdSlot: 'adSlotTest', + data: { + inventory: [4], + keywords: 'foo,bar', + visitor: [1, 2, 3], + } + } + }, code: '/1996833/slot-2', sizes: [[468, 60]], bids: [ @@ -85,6 +95,7 @@ describe('Publisher API _ AdUnits', function () { it('the second adUnits value should be same with the adUnits that is added by $$PREBID_GLOBAL$$.addAdUnits();', function () { assert.strictEqual(adUnit2.code, '/1996833/slot-2', 'adUnit2 code'); assert.deepEqual(adUnit2.sizes, [[468, 60]], 'adUnit2 sizes'); + assert.deepEqual(adUnit2['ortb2Imp'], {'ext': {'data': {'pbadslot': 'adSlotTest', 'inventory': [4], 'keywords': 'foo,bar', 'visitor': [1, 2, 3]}}}, 'adUnit2 ortb2Imp'); assert.strictEqual(bids2[0].bidder, 'rubicon', 'adUnit2 bids1 bidder'); assert.strictEqual(bids2[0].params.rp_account, '4934', 'adUnit2 bids1 params.rp_account'); assert.strictEqual(bids2[0].params.rp_zonesize, '23948-15', 'adUnit2 bids1 params.rp_zonesize'); diff --git a/test/spec/adapters/adbutler_spec.js b/test/spec/adapters/adbutler_spec.js deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/test/spec/auctionmanager_spec.js b/test/spec/auctionmanager_spec.js index a50eba5e585..69e34c4a07a 100644 --- a/test/spec/auctionmanager_spec.js +++ b/test/spec/auctionmanager_spec.js @@ -143,6 +143,9 @@ describe('auctionmanager.js', function () { adId: '1adId', source: 'client', mediaType: 'banner', + meta: { + advertiserDomains: ['adomain'] + } }; /* return the expected response for a given bid, filter by keys if given */ @@ -154,6 +157,7 @@ describe('auctionmanager.js', function () { expected[ CONSTANTS.TARGETING_KEYS.SIZE ] = bid.getSize(); expected[ CONSTANTS.TARGETING_KEYS.SOURCE ] = bid.source; expected[ CONSTANTS.TARGETING_KEYS.FORMAT ] = bid.mediaType; + expected[ CONSTANTS.TARGETING_KEYS.ADOMAIN ] = bid.meta.advertiserDomains[0]; if (bid.mediaType === 'video') { expected[ CONSTANTS.TARGETING_KEYS.UUID ] = bid.videoCacheKey; expected[ CONSTANTS.TARGETING_KEYS.CACHE_ID ] = bid.videoCacheKey; @@ -239,6 +243,12 @@ describe('auctionmanager.js', function () { return bidResponse.mediaType; } }, + { + key: CONSTANTS.TARGETING_KEYS.ADOMAIN, + val: function (bidResponse) { + return bidResponse.meta.advertiserDomains[0]; + } + } ] } @@ -309,6 +319,12 @@ describe('auctionmanager.js', function () { val: function (bidResponse) { return bidResponse.videoCacheKey; } + }, + { + key: CONSTANTS.TARGETING_KEYS.ADOMAIN, + val: function (bidResponse) { + return bidResponse.meta.advertiserDomains[0]; + } } ] diff --git a/test/spec/config_spec.js b/test/spec/config_spec.js index 81ce966efb2..0b8dd6978cf 100644 --- a/test/spec/config_spec.js +++ b/test/spec/config_spec.js @@ -6,6 +6,8 @@ const utils = require('src/utils'); let getConfig; let setConfig; +let getBidderConfig; +let setBidderConfig; let setDefaults; describe('config API', function () { @@ -15,6 +17,8 @@ describe('config API', function () { const config = newConfig(); getConfig = config.getConfig; setConfig = config.setConfig; + getBidderConfig = config.getBidderConfig; + setBidderConfig = config.setBidderConfig; setDefaults = config.setDefaults; logErrorSpy = sinon.spy(utils, 'logError'); logWarnSpy = sinon.spy(utils, 'logWarn'); @@ -57,6 +61,17 @@ describe('config API', function () { expect(getConfig('foo')).to.eql({baz: 'qux'}); }); + it('moves fpd config into ortb2 properties', function () { + setConfig({fpd: {context: {keywords: 'foo,bar', data: {inventory: [1]}}}}); + expect(getConfig('ortb2')).to.eql({site: {keywords: 'foo,bar', ext: {data: {inventory: [1]}}}}); + expect(getConfig('fpd')).to.eql(undefined); + }); + + it('moves fpd bidderconfig into ortb2 properties', function () { + setBidderConfig({bidders: ['bidderA'], config: {fpd: {context: {keywords: 'foo,bar', data: {inventory: [1]}}}}}); + expect(getBidderConfig()).to.eql({'bidderA': {ortb2: {site: {keywords: 'foo,bar', ext: {data: {inventory: [1]}}}}}}); + }); + it('sets debugging', function () { setConfig({ debug: true }); expect(getConfig('debug')).to.be.true; diff --git a/test/spec/modules/a4gBidAdapter_spec.js b/test/spec/modules/a4gBidAdapter_spec.js index 3dccbb28426..cb05fa62ab6 100644 --- a/test/spec/modules/a4gBidAdapter_spec.js +++ b/test/spec/modules/a4gBidAdapter_spec.js @@ -1,5 +1,6 @@ import { expect } from 'chai'; import { spec } from 'modules/a4gBidAdapter.js'; +import * as utils from 'src/utils.js'; describe('a4gAdapterTests', function () { describe('bidRequestValidity', function () { @@ -139,15 +140,54 @@ describe('a4gAdapterTests', function () { const bidResponse = { body: [{ - 'id': 'div-gpt-ad-1460505748561-0', + 'id': '51ef8751f9aead', 'ad': 'test ad', 'width': 320, 'height': 250, - 'cpm': 5.2 + 'cpm': 5.2, + 'crid': '111' }], headers: {} }; + it('should get correct bid response for banner ad', function () { + const expectedParse = [ + { + requestId: '51ef8751f9aead', + cpm: 5.2, + creativeId: '111', + width: 320, + height: 250, + ad: 'test ad', + currency: 'USD', + ttl: 120, + netRevenue: true + } + ]; + const result = spec.interpretResponse(bidResponse, bidRequest); + expect(result[0]).to.deep.equal(expectedParse[0]); + }); + + it('should set creativeId to default value if not provided', function () { + const bidResponseWithoutCrid = utils.deepClone(bidResponse); + delete bidResponseWithoutCrid.body[0].crid; + const expectedParse = [ + { + requestId: '51ef8751f9aead', + cpm: 5.2, + creativeId: '51ef8751f9aead', + width: 320, + height: 250, + ad: 'test ad', + currency: 'USD', + ttl: 120, + netRevenue: true + } + ]; + const result = spec.interpretResponse(bidResponseWithoutCrid, bidRequest); + expect(result[0]).to.deep.equal(expectedParse[0]); + }) + it('required keys', function () { const result = spec.interpretResponse(bidResponse, bidRequest); @@ -169,5 +209,11 @@ describe('a4gAdapterTests', function () { expect(requiredKeys.indexOf(key) !== -1).to.equal(true); }); }) + + it('adId should not be equal to requestId', function () { + const result = spec.interpretResponse(bidResponse, bidRequest); + + expect(result[0].requestId).to.not.equal(result[0].adId); + }) }); }); diff --git a/test/spec/modules/adWMGBidAdapter_spec.js b/test/spec/modules/adWMGBidAdapter_spec.js index 5c2364d454c..1f881897fd8 100644 --- a/test/spec/modules/adWMGBidAdapter_spec.js +++ b/test/spec/modules/adWMGBidAdapter_spec.js @@ -288,5 +288,45 @@ describe('adWMGBidAdapter', function () { expect(syncs[0].url).includes('gdpr=1'); expect(syncs[0].url).includes(`gdpr_consent=${gdprConsent.consentString}`); }); + + it('should not add GDPR consent params twice', function() { + const syncOptions = { + 'iframeEnabled': true, + 'pixelEnabled': true + }; + const gdprConsent = { + consentString: 'CO9rhBTO9rhBTAcABBENBCCsAP_AAH_AACiQHItf_X_fb3_j-_59_9t0eY1f9_7_v20zjgeds-8Nyd_X_L8X42M7vB36pq4KuR4Eu3LBIQdlHOHcTUmw6IkVqTPsbk2Mr7NKJ7PEinMbe2dYGH9_n9XTuZKY79_s___z__-__v__7_f_r-3_3_vp9V---3YHIgEmGpfARZiWOBJNGlUKIEIVxIdACACihGFomsICVwU7K4CP0EDABAagIwIgQYgoxZBAAAAAElEQEgB4IBEARAIAAQAqQEIACNAEFgBIGAQACgGhYARQBCBIQZHBUcpgQESLRQTyVgCUXexhhCGUUANAg4AA.YAAAAAAAAAAA', + vendorData: {}, + gdprApplies: true, + apiVersion: 2 + }; + const gdprConsent2 = { + consentString: 'CO9rhBTO9rhBTAcABBENBCCsAP_AAH_AACiQHItf_7_fb3_j-_59_9t0eY1f9_7_v20zjgeds-8Nyd_X_L8X42M7vB36pq4KuR4Eu3LBIQdlHOHcTUmw6IkVqTPsbk2Mr7NKJ7PEinMbe2dYGH9_n9XTuZKY79_s___z__-__v__7_f_r-3_3_vp9V---3YHIgEmGpfARZiWOBJNGlUKIEIVxIdACACihGFomsICVwU7K4CP0EDABAagIwIgQYgoxZBAAAAAElEQEgB4IBEARAIAAQAqQEIACNAEFgBIGAQACgGhYARQBCBIQZHBUcpgQESLRQTyVgCUXexhhCGUUANAg4AA.YAAAAAAAAAAA', + vendorData: {}, + gdprApplies: true, + apiVersion: 2 + }; + const serverResponse = {}; + let syncs = spec.getUserSyncs(syncOptions, serverResponse, gdprConsent); + syncs = spec.getUserSyncs(syncOptions, serverResponse, gdprConsent2); + expect(syncs[0].url.match(/gdpr/g).length).to.equal(2); // gdpr + gdpr_consent + expect(syncs[0].url.match(/gdpr_consent/g).length).to.equal(1); + }); + + it('should delete \'&\' symbol at the end of usersync URL', function() { + const syncOptions = { + 'iframeEnabled': true, + 'pixelEnabled': true + }; + const gdprConsent = { + consentString: 'CO9rhBTO9rhBTAcABBENBCCsAP_AAH_AACiQHItf_X_fb3_j-_59_9t0eY1f9_7_v20zjgeds-8Nyd_X_L8X42M7vB36pq4KuR4Eu3LBIQdlHOHcTUmw6IkVqTPsbk2Mr7NKJ7PEinMbe2dYGH9_n9XTuZKY79_s___z__-__v__7_f_r-3_3_vp9V---3YHIgEmGpfARZiWOBJNGlUKIEIVxIdACACihGFomsICVwU7K4CP0EDABAagIwIgQYgoxZBAAAAAElEQEgB4IBEARAIAAQAqQEIACNAEFgBIGAQACgGhYARQBCBIQZHBUcpgQESLRQTyVgCUXexhhCGUUANAg4AA.YAAAAAAAAAAA', + vendorData: {}, + gdprApplies: true, + apiVersion: 2 + }; + const serverResponse = {}; + let syncs = spec.getUserSyncs(syncOptions, serverResponse, gdprConsent); + expect(syncs[0].url.slice(-1)).to.not.equal('&'); + }); }); }); diff --git a/test/spec/modules/adagioBidAdapter_spec.js b/test/spec/modules/adagioBidAdapter_spec.js index 0a585caaa1a..3707c19e471 100644 --- a/test/spec/modules/adagioBidAdapter_spec.js +++ b/test/spec/modules/adagioBidAdapter_spec.js @@ -1,5 +1,5 @@ import find from 'core-js-pure/features/array/find.js'; -import { expect } from 'chai'; +import { expect, util } from 'chai'; import { _features, internal as adagio, @@ -13,7 +13,8 @@ import { } from '../../../modules/adagioBidAdapter.js'; import { loadExternalScript } from '../../../src/adloader.js'; import * as utils from '../../../src/utils.js'; -import { config } from 'src/config.js'; +import { config } from '../../../src/config.js'; +import { NATIVE } from '../../../src/mediaTypes.js'; const BidRequestBuilder = function BidRequestBuilder(options) { const defaults = { @@ -331,7 +332,8 @@ describe('Adagio bid adapter', () => { 'schain', 'prebidVersion', 'adapterVersion', - 'featuresVersion' + 'featuresVersion', + 'data' ]; it('groups requests by organizationId', function() { @@ -879,6 +881,174 @@ describe('Adagio bid adapter', () => { expect(bidResponse.vastUrl).to.match(/^data:text\/xml;/) }); }); + + describe('Response with native add', () => { + const serverResponseWithNative = utils.deepClone(serverResponse) + serverResponseWithNative.body.bids[0].mediaType = 'native'; + serverResponseWithNative.body.bids[0].admNative = { + ver: '1.2', + link: { + url: 'https://i.am.a.click.url', + clickTrackers: [ + 'https://i.am.a.clicktracker.url' + ] + }, + privacy: 'http://www.myprivacyurl.url', + ext: { + bvw: 'test' + }, + eventtrackers: [ + { + event: 1, + method: 1, + url: 'https://eventrack.local/impression' + }, + { + event: 1, + method: 2, + url: 'https://eventrack.local/impression' + }, + { + event: 2, + method: 1, + url: 'https://eventrack.local/viewable-mrc50' + } + ], + assets: [ + { + required: 1, + title: { + text: 'My title' + } + }, + { + img: { + url: 'https://images.local/image.jpg', + w: 100, + h: 250 + } + }, + { + img: { + type: 1, + url: 'https://images.local/icon.png', + w: 40, + h: 40 + } + }, + { + data: { + type: 1, // sponsored + value: 'Adagio' + } + }, + { + data: { + type: 2, // desc / body + value: 'The super ad text' + } + }, + { + data: { + type: 3, // rating + value: '10 from 10' + } + }, + { + data: { + type: 11, // displayUrl + value: 'https://i.am.a.display.url' + } + } + ] + }; + + const bidRequestNative = utils.deepClone(bidRequest) + bidRequestNative.mediaTypes = { + native: { + sendTargetingKeys: false, + + clickUrl: { + required: true, + }, + title: { + required: true, + }, + body: { + required: true, + }, + sponsoredBy: { + required: false + }, + image: { + required: true + }, + icon: { + required: true + }, + privacyLink: { + required: false + }, + ext: { + adagio_bvw: {} + } + } + }; + + it('Should ignore native parsing due to missing raw admNative property', () => { + const alternateServerResponse = utils.deepClone(serverResponseWithNative); + delete alternateServerResponse.body.bids[0].admNative + const r = spec.interpretResponse(alternateServerResponse, bidRequestNative); + expect(r[0].mediaType).to.equal(NATIVE); + expect(r[0].native).not.ok; + utilsMock.expects('logError').once(); + }); + + it('Should ignore native parsing due to invalid raw admNative.assets property', () => { + const alternateServerResponse = utils.deepClone(serverResponseWithNative); + alternateServerResponse.body.bids[0].admNative.assets = { title: { text: 'test' } }; + const r = spec.interpretResponse(alternateServerResponse, bidRequestNative); + expect(r[0].mediaType).to.equal(NATIVE); + expect(r[0].native).not.ok; + utilsMock.expects('logError').once(); + }); + + it('Should handle and return a formated Native ad', () => { + const r = spec.interpretResponse(serverResponseWithNative, bidRequestNative); + const expected = { + displayUrl: 'https://i.am.a.display.url', + sponsoredBy: 'Adagio', + body: 'The super ad text', + rating: '10 from 10', + clickUrl: 'https://i.am.a.click.url', + title: 'My title', + impressionTrackers: [ + 'https://eventrack.local/impression' + ], + javascriptTrackers: '', + clickTrackers: [ + 'https://i.am.a.clicktracker.url' + ], + image: { + url: 'https://images.local/image.jpg', + width: 100, + height: 250 + }, + icon: { + url: 'https://images.local/icon.png', + width: 40, + height: 40 + }, + ext: { + adagio_bvw: 'test' + }, + privacyLink: 'http://www.myprivacyurl.url' + } + expect(r[0].mediaType).to.equal(NATIVE); + expect(r[0].native).ok; + expect(r[0].native).to.deep.equal(expected); + }); + }); }); describe('getUserSyncs()', function() { diff --git a/test/spec/modules/addefendBidAdapter_spec.js b/test/spec/modules/addefendBidAdapter_spec.js new file mode 100644 index 00000000000..ac01750e98f --- /dev/null +++ b/test/spec/modules/addefendBidAdapter_spec.js @@ -0,0 +1,184 @@ +import {expect} from 'chai'; +import {spec} from 'modules/addefendBidAdapter.js'; + +describe('addefendBidAdapter', () => { + const defaultBidRequest = { + bidId: 'd66fa86787e0b0ca900a96eacfd5f0bb', + auctionId: 'ccc4c7cdfe11cfbd74065e6dd28413d8', + transactionId: 'd58851660c0c4461e4aa06344fc9c0c6', + sizes: [[300, 250], [300, 600]], + params: { + pageId: 'stringid1', + placementId: 'stringid2' + } + }; + + const deepClone = function (val) { + return JSON.parse(JSON.stringify(val)); + }; + + const buildRequest = (buildRequest, bidderRequest) => { + if (!Array.isArray(buildRequest)) { + buildRequest = [buildRequest]; + } + + return spec.buildRequests(buildRequest, { + ...bidderRequest || {}, + refererInfo: { + referer: 'https://referer.example.com' + } + })[0]; + }; + + describe('isBidRequestValid', () => { + it('should return true when required params found', () => { + const bidRequest = deepClone(defaultBidRequest); + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }); + + it('pageId performs type checking', () => { + const bidRequest = deepClone(defaultBidRequest); + bidRequest.params.pageId = 1; // supposed to be a string + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + + it('placementId performs type checking', () => { + const bidRequest = deepClone(defaultBidRequest); + bidRequest.params.placementId = 1; // supposed to be a string + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + + it('should return false when required params are not passed', () => { + const bidRequest = deepClone(defaultBidRequest); + delete bidRequest.params; + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + }); + + describe('buildRequests', () => { + const bidRequest = deepClone(defaultBidRequest); + const request = buildRequest(bidRequest); + + it('sends bid request to endpoint via https using post', () => { + expect(request.method).to.equal('POST'); + expect(request.url.indexOf('https://')).to.equal(0); + expect(request.url).to.equal(`${spec.hostname}/bid`); + }); + + it('contains prebid version parameter', () => { + expect(request.data.v).to.equal($$PREBID_GLOBAL$$.version); + }); + + it('contains correct referer', () => { + expect(request.data.referer).to.equal('https://referer.example.com'); + }); + + it('contains auctionId', () => { + expect(request.data.auctionId).to.equal('ccc4c7cdfe11cfbd74065e6dd28413d8'); + }); + + it('contains pageId', () => { + expect(request.data.pageId).to.equal('stringid1'); + }); + + it('sends correct bid parameters', () => { + const bidRequest = deepClone(defaultBidRequest); + expect(request.data.bids).to.deep.equal([ { + bidId: bidRequest.bidId, + placementId: bidRequest.params.placementId, + sizes: [ '300x250', '300x600' ], + transactionId: 'd58851660c0c4461e4aa06344fc9c0c6' + } ]); + }); + + it('handles empty gdpr object', () => { + const bidRequest = deepClone(defaultBidRequest); + const request = buildRequest(bidRequest, { + gdprConsent: {} + }); + expect(request.data.gdpr_consent).to.be.equal(''); + }); + + it('handles non-existent gdpr object', () => { + const bidRequest = deepClone(defaultBidRequest); + const request = buildRequest(bidRequest, { + gdprConsent: null + }); + expect(request.data.gdpr_consent).to.be.equal(''); + }); + + it('handles properly filled gdpr string', () => { + const bidRequest = deepClone(defaultBidRequest); + const consentString = 'GDPR_CONSENT_STRING'; + const request = buildRequest(bidRequest, { + gdprConsent: { + gdprApplies: true, + consentString: consentString + } + }); + + expect(request.data.gdpr_consent).to.be.equal(consentString); + }); + }); + + describe('interpretResponse', () => { + it('should get correct bid response', () => { + const serverResponse = [ + { + 'width': 300, + 'height': 250, + 'creativeId': '29681110', + 'ad': '', + 'cpm': 0.5, + 'requestId': 'ccc4c7cdfe11cfbd74065e6dd28413d8', + 'ttl': 120, + 'netRevenue': true, + 'currency': 'EUR', + 'advertiserDomains': ['advertiser.example.com'] + } + ]; + + const expectedResponse = [ + { + 'requestId': 'ccc4c7cdfe11cfbd74065e6dd28413d8', + 'cpm': 0.5, + 'creativeId': '29681110', + 'width': 300, + 'height': 250, + 'ttl': 120, + 'currency': 'EUR', + 'ad': '', + 'netRevenue': true, + 'advertiserDomains': ['advertiser.example.com'] + } + ]; + + const result = spec.interpretResponse({body: serverResponse}); + expect(result.length).to.equal(expectedResponse.length); + Object.keys(expectedResponse[0]).forEach((key) => { + expect(result[0][key]).to.deep.equal(expectedResponse[0][key]); + }); + }); + + it('handles incomplete server response', () => { + const serverResponse = [ + { + 'ad': '', + 'cpm': 0.5, + 'requestId': 'ccc4c7cdfe11cfbd74065e6dd28413d8', + 'ttl': 60 + } + ]; + const result = spec.interpretResponse({body: serverResponse}); + + expect(result.length).to.equal(0); + }); + + it('handles nobid responses', () => { + const serverResponse = []; + const result = spec.interpretResponse({body: serverResponse}); + + expect(result.length).to.equal(0); + }); + }); +}); diff --git a/test/spec/modules/adformBidAdapter_spec.js b/test/spec/modules/adformBidAdapter_spec.js index 23db7a8dc97..18b2565d4f8 100644 --- a/test/spec/modules/adformBidAdapter_spec.js +++ b/test/spec/modules/adformBidAdapter_spec.js @@ -157,6 +157,37 @@ describe('Adform adapter', function () { assert.equal(eids, 'some_id_value'); }); + it('should add parameter to global parameters if it exists in all bids', function () { + const _bids = []; + _bids.push(bids[0]); + _bids.push(bids[1]); + _bids.push(bids[2]); + _bids[0].params.mkv = 'key:value,key1:value1'; + _bids[1].params.mkv = 'key:value,key1:value1,keyR:removed'; + _bids[2].params.mkv = 'key:value,key1:value1,keyR:removed,key8:value1'; + _bids[0].params.mkw = 'targeting'; + _bids[1].params.mkw = 'targeting'; + _bids[2].params.mkw = 'targeting,targeting2'; + _bids[0].params.msw = 'search:word,search:word2'; + _bids[1].params.msw = 'search:word'; + _bids[2].params.msw = 'search:word,search:word5'; + let bidList = _bids; + let request = spec.buildRequests(bidList); + let parsedUrl = parseUrl(request.url); + assert.equal(parsedUrl.query.mkv, encodeURIComponent('key:value,key1:value1')); + assert.equal(parsedUrl.query.mkw, 'targeting'); + assert.equal(parsedUrl.query.msw, encodeURIComponent('search:word')); + assert.ok(!parsedUrl.items[0].mkv); + assert.ok(!parsedUrl.items[0].mkw); + assert.equal(parsedUrl.items[0].msw, 'search:word2'); + assert.equal(parsedUrl.items[1].mkv, 'keyR:removed'); + assert.ok(!parsedUrl.items[1].mkw); + assert.ok(!parsedUrl.items[1].msw); + assert.equal(parsedUrl.items[2].mkv, 'keyR:removed,key8:value1'); + assert.equal(parsedUrl.items[2].mkw, 'targeting2'); + assert.equal(parsedUrl.items[2].msw, 'search:word5'); + }); + describe('user privacy', function () { it('should send GDPR Consent data to adform if gdprApplies', function () { let request = spec.buildRequests([bids[0]], {gdprConsent: {gdprApplies: true, consentString: 'concentDataString'}}); diff --git a/test/spec/modules/adhashBidAdapter_spec.js b/test/spec/modules/adhashBidAdapter_spec.js new file mode 100644 index 00000000000..ab4df84c093 --- /dev/null +++ b/test/spec/modules/adhashBidAdapter_spec.js @@ -0,0 +1,155 @@ +import { expect } from 'chai'; +import { spec } from 'modules/adhashBidAdapter.js'; + +describe('adhashBidAdapter', function () { + describe('isBidRequestValid', function () { + const validBid = { + bidder: 'adhash', + params: { + publisherId: '0xc3b09b27e9c6ef73957901aa729b9e69e5bbfbfb', + platformURL: 'https://adhash.org/p/struma/' + }, + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + adUnitCode: 'adunit-code', + sizes: [[300, 250]], + bidId: '12345678901234', + bidderRequestId: '98765432109876', + auctionId: '01234567891234', + }; + + it('should return true when all mandatory parameters are there', function () { + expect(spec.isBidRequestValid(validBid)).to.equal(true); + }); + + it('should return false when there are no params', function () { + const bid = { ...validBid }; + delete bid.params; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when unsupported media type is requested', function () { + const bid = { ...validBid }; + bid.mediaTypes = { native: { sizes: [[300, 250]] } }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when publisherId is not a string', function () { + const bid = { ...validBid }; + bid.params.publisherId = 123; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when publisherId is not valid', function () { + const bid = { ...validBid }; + bid.params.publisherId = 'short string'; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when publisherId is not a string', function () { + const bid = { ...validBid }; + bid.params.platformURL = 123; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when publisherId is not valid', function () { + const bid = { ...validBid }; + bid.params.platformURL = 'https://'; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + const bidRequest = { + params: { + publisherId: '0xc3b09b27e9c6ef73957901aa729b9e69e5bbfbfb' + }, + sizes: [[300, 250]], + adUnitCode: 'adUnitCode' + }; + it('should build the request correctly', function () { + const result = spec.buildRequests( + [ bidRequest ], + { gdprConsent: true, refererInfo: { referer: 'http://example.com/' } } + ); + expect(result.length).to.equal(1); + expect(result[0].method).to.equal('POST'); + expect(result[0].url).to.equal('https://bidder.adhash.org/rtb?version=1.0&prebid=true'); + expect(result[0].bidRequest).to.equal(bidRequest); + expect(result[0].data).to.have.property('timezone'); + expect(result[0].data).to.have.property('location'); + expect(result[0].data).to.have.property('publisherId'); + expect(result[0].data).to.have.property('size'); + expect(result[0].data).to.have.property('navigator'); + expect(result[0].data).to.have.property('creatives'); + expect(result[0].data).to.have.property('blockedCreatives'); + expect(result[0].data).to.have.property('currentTimestamp'); + expect(result[0].data).to.have.property('recentAds'); + }); + it('should build the request correctly without referer', function () { + const result = spec.buildRequests([ bidRequest ], { gdprConsent: true }); + expect(result.length).to.equal(1); + expect(result[0].method).to.equal('POST'); + expect(result[0].url).to.equal('https://bidder.adhash.org/rtb?version=1.0&prebid=true'); + expect(result[0].bidRequest).to.equal(bidRequest); + expect(result[0].data).to.have.property('timezone'); + expect(result[0].data).to.have.property('location'); + expect(result[0].data).to.have.property('publisherId'); + expect(result[0].data).to.have.property('size'); + expect(result[0].data).to.have.property('navigator'); + expect(result[0].data).to.have.property('creatives'); + expect(result[0].data).to.have.property('blockedCreatives'); + expect(result[0].data).to.have.property('currentTimestamp'); + expect(result[0].data).to.have.property('recentAds'); + }); + }); + + describe('interpretResponse', function () { + const request = { + data: { some: 'data' }, + bidRequest: { + bidId: '12345678901234', + adUnitCode: 'adunit-code', + sizes: [[300, 250]], + params: { + platformURL: 'https://adhash.org/p/struma/' + } + } + }; + + it('should interpret the response correctly', function () { + const serverResponse = { + body: { + creatives: [{ + costEUR: 1.234 + }] + } + }; + const result = spec.interpretResponse(serverResponse, request); + expect(result.length).to.equal(1); + expect(result[0].requestId).to.equal('12345678901234'); + expect(result[0].cpm).to.equal(1.234); + expect(result[0].width).to.equal(300); + expect(result[0].height).to.equal(250); + expect(result[0].creativeId).to.equal('adunit-code'); + expect(result[0].netRevenue).to.equal(true); + expect(result[0].currency).to.equal('EUR'); + expect(result[0].ttl).to.equal(60); + }); + + it('should return empty array when there are no creatives returned', function () { + expect(spec.interpretResponse({body: {creatives: []}}, request).length).to.equal(0); + }); + + it('should return empty array when there is no creatives key in the response', function () { + expect(spec.interpretResponse({body: {}}, request).length).to.equal(0); + }); + + it('should return empty array when something is not right', function () { + expect(spec.interpretResponse(null, request).length).to.equal(0); + }); + }); +}); diff --git a/test/spec/modules/adheseBidAdapter_spec.js b/test/spec/modules/adheseBidAdapter_spec.js index 4d888db269d..526102c51fe 100644 --- a/test/spec/modules/adheseBidAdapter_spec.js +++ b/test/spec/modules/adheseBidAdapter_spec.js @@ -17,10 +17,9 @@ let minimalBid = function() { } }; -let bidWithParams = function(data, userId) { +let bidWithParams = function(data) { let bid = minimalBid(); bid.params.data = data; - bid.userId = userId; return bid; }; @@ -74,31 +73,33 @@ describe('AdheseAdapter', function () { it('should include requested slots', function () { let req = spec.buildRequests([ minimalBid() ], bidderRequest); - expect(JSON.parse(req.data).slots).to.deep.include({ 'slotname': '_main_page_-leaderboard' }); + expect(JSON.parse(req.data).slots[0].slotname).to.equal('_main_page_-leaderboard'); }); it('should include all extra bid params', function () { let req = spec.buildRequests([ bidWithParams({ 'ag': '25' }) ], bidderRequest); - expect(JSON.parse(req.data).parameters).to.deep.include({ 'ag': [ '25' ] }); + expect(JSON.parse(req.data).slots[0].parameters).to.deep.include({ 'ag': [ '25' ] }); }); - it('should include duplicate bid params once', function () { + it('should assign bid params per slot', function () { let req = spec.buildRequests([ bidWithParams({ 'ag': '25' }), bidWithParams({ 'ag': '25', 'ci': 'gent' }) ], bidderRequest); - expect(JSON.parse(req.data).parameters).to.deep.include({'ag': ['25']}).and.to.deep.include({ 'ci': [ 'gent' ] }); + expect(JSON.parse(req.data).slots[0].parameters).to.deep.include({ 'ag': [ '25' ] }).and.not.to.deep.include({ 'ci': [ 'gent' ] }); + expect(JSON.parse(req.data).slots[1].parameters).to.deep.include({ 'ag': [ '25' ] }).and.to.deep.include({ 'ci': [ 'gent' ] }); }); it('should split multiple target values', function () { let req = spec.buildRequests([ bidWithParams({ 'ci': 'london' }), bidWithParams({ 'ci': 'gent' }) ], bidderRequest); - expect(JSON.parse(req.data).parameters).to.deep.include({ 'ci': [ 'london', 'gent' ] }); + expect(JSON.parse(req.data).slots[0].parameters).to.deep.include({ 'ci': [ 'london' ] }); + expect(JSON.parse(req.data).slots[1].parameters).to.deep.include({ 'ci': [ 'gent' ] }); }); it('should filter out empty params', function () { let req = spec.buildRequests([ bidWithParams({ 'aa': [], 'bb': null, 'cc': '', 'dd': [ '', '' ], 'ee': [ 0, 1, null ], 'ff': 0, 'gg': [ 'x', 'y', '' ] }) ], bidderRequest); - let params = JSON.parse(req.data).parameters; + let params = JSON.parse(req.data).slots[0].parameters; expect(params).to.not.have.any.keys('aa', 'bb', 'cc', 'dd'); expect(params).to.deep.include({ 'ee': [ 0, 1 ], 'ff': [ 0 ], 'gg': [ 'x', 'y' ] }); }); @@ -115,10 +116,25 @@ describe('AdheseAdapter', function () { expect(JSON.parse(req.data).parameters).to.deep.include({ 'xf': [ 'aHR0cDovL3ByZWJpZC5vcmcvZGV2LWRvY3Mvc3ViamVjdHM_X2Q9MQ' ] }); }); - it('should include id5 id as /x5 param', function () { - let req = spec.buildRequests([ bidWithParams({}, { 'id5id': { 'uid': 'ID5-1234567890' } }) ], bidderRequest); + it('should include eids', function () { + let bid = minimalBid(); + bid.userIdAsEids = [{ source: 'id5-sync.com', uids: [{ id: 'ID5@59sigaS-...' }] }]; + + let req = spec.buildRequests([ bid ], bidderRequest); + + expect(JSON.parse(req.data).user.ext.eids).to.deep.equal(bid.userIdAsEids); + }); + + it('should not include eids field when userid module disabled', function () { + let req = spec.buildRequests([ minimalBid() ], bidderRequest); - expect(JSON.parse(req.data).parameters).to.deep.include({ 'x5': [ 'ID5-1234567890' ] }); + expect(JSON.parse(req.data)).to.not.have.key('eids'); + }); + + it('should request vast content as url', function () { + let req = spec.buildRequests([ minimalBid() ], bidderRequest); + + expect(JSON.parse(req.data).vastContentAsUrl).to.equal(true); }); it('should include bids', function () { @@ -170,7 +186,7 @@ describe('AdheseAdapter', function () { body: '
', tracker: 'https://hosts-demo.adhese.com/rtb_gateway/handlers/client/track/?id=a2f39296-6dd0-4b3c-be85-7baa22e7ff4a', impressionCounter: 'https://hosts-demo.adhese.com/rtb_gateway/handlers/client/track/?id=a2f39296-6dd0-4b3c-be85-7baa22e7ff4a', - extension: {'prebid': {'cpm': {'amount': '1.000000', 'currency': 'USD'}}} + extension: {'prebid': {'cpm': {'amount': '1.000000', 'currency': 'USD'}}, mediaType: 'banner'} } ] }; @@ -217,7 +233,7 @@ describe('AdheseAdapter', function () { width: '640', height: '350', body: '', - extension: {'prebid': {'cpm': {'amount': '2.1', 'currency': 'USD'}}} + extension: {'prebid': {'cpm': {'amount': '2.1', 'currency': 'USD'}}, mediaType: 'video'} } ] }; @@ -243,6 +259,43 @@ describe('AdheseAdapter', function () { expect(spec.interpretResponse(sspVideoResponse, bidRequest)).to.deep.equal(expectedResponse); }); + it('should get correct ssp cache video response', () => { + let sspCachedVideoResponse = { + body: [ + { + origin: 'RUBICON', + ext: 'js', + slotName: '_main_page_-leaderboard', + adType: 'leaderboard', + width: '640', + height: '350', + cachedBodyUrl: 'https://ads-demo.adhese.com/content/38983ccc-4083-4c24-932c-96f798d969b3', + extension: {'prebid': {'cpm': {'amount': '2.1', 'currency': 'USD'}}, mediaType: 'video'} + } + ] + }; + + let expectedResponse = [{ + requestId: BID_ID, + vastUrl: 'https://ads-demo.adhese.com/content/38983ccc-4083-4c24-932c-96f798d969b3', + cpm: 2.1, + currency: 'USD', + creativeId: 'RUBICON', + dealId: '', + width: 640, + height: 350, + mediaType: 'video', + netRevenue: NET_REVENUE, + ttl: TTL, + adhese: { + origin: 'RUBICON', + originInstance: '', + originData: {} + } + }]; + expect(spec.interpretResponse(sspCachedVideoResponse, bidRequest)).to.deep.equal(expectedResponse); + }); + it('should get correct Adhese banner response', () => { const adheseBannerResponse = { body: [ @@ -280,7 +333,8 @@ describe('AdheseAdapter', function () { amount: '5.96', currency: 'USD' } - } + }, + mediaType: 'banner' } } ] @@ -342,7 +396,10 @@ describe('AdheseAdapter', function () { impressionCounter: 'https://hosts-demo.adhese.com/track/742898', origin: 'JERLICIA', originData: {}, - auctionable: true + auctionable: true, + extension: { + mediaType: 'video' + } } ] }; @@ -380,6 +437,70 @@ describe('AdheseAdapter', function () { expect(spec.interpretResponse(adheseVideoResponse, bidRequest)).to.deep.equal(expectedResponse); }); + it('should get correct Adhese cached video response', () => { + const adheseVideoResponse = { + body: [ + { + adType: 'preroll', + adFormat: '', + orderId: '22248', + adspaceId: '164196', + body: '', + height: '360', + width: '640', + extension: { + mediaType: 'video' + }, + cachedBodyUrl: 'https://ads-demo.adhese.com/content/38983ccc-4083-4c24-932c-96f798d969b3', + libId: '89860', + id: '742470', + advertiserId: '2263', + ext: 'advar', + orderName: 'Smartphoto EOY-20181112', + creativeName: 'PREROLL', + slotName: '_main_page_-leaderboard', + slotID: '41711', + impressionCounter: 'https://hosts-demo.adhese.com/track/742898', + origin: 'JERLICIA', + originData: {}, + auctionable: true + } + ] + }; + + let expectedResponse = [{ + requestId: BID_ID, + vastUrl: 'https://ads-demo.adhese.com/content/38983ccc-4083-4c24-932c-96f798d969b3', + adhese: { + origin: '', + originInstance: '', + originData: { + adFormat: '', + adId: '742470', + adType: 'preroll', + adspaceId: '164196', + libId: '89860', + orderProperty: undefined, + priority: undefined, + viewableImpressionCounter: undefined, + slotId: '41711', + slotName: '_main_page_-leaderboard', + advertiserId: '2263', + } + }, + cpm: 0, + currency: 'USD', + creativeId: '742470', + dealId: '22248', + width: 640, + height: 360, + mediaType: 'video', + netRevenue: NET_REVENUE, + ttl: TTL, + }]; + expect(spec.interpretResponse(adheseVideoResponse, bidRequest)).to.deep.equal(expectedResponse); + }); + it('should return no bids for empty adserver response', () => { let adserverResponse = { body: [] }; expect(spec.interpretResponse(adserverResponse, bidRequest)).to.be.empty; diff --git a/test/spec/modules/adkernelAdnAnalytics_spec.js b/test/spec/modules/adkernelAdnAnalytics_spec.js index e7ef831080c..7af96c9321c 100644 --- a/test/spec/modules/adkernelAdnAnalytics_spec.js +++ b/test/spec/modules/adkernelAdnAnalytics_spec.js @@ -1,6 +1,6 @@ -import analyticsAdapter, {ExpiringQueue, getUmtSource, storage} from 'modules/adkernelAdnAnalyticsAdapter.js'; +import analyticsAdapter, {ExpiringQueue, getUmtSource, storage} from 'modules/adkernelAdnAnalyticsAdapter'; import {expect} from 'chai'; -import adapterManager from 'src/adapterManager.js'; +import adapterManager from 'src/adapterManager'; import CONSTANTS from 'src/constants.json'; const events = require('../../../src/events'); @@ -158,11 +158,16 @@ describe('', function () { sizes: [[300, 250]], bidId: '208750227436c1', bidderRequestId: '1a6fc81528d0f6', - auctionId: '5018eb39-f900-4370-b71e-3bb5b48d324f' + auctionId: '5018eb39-f900-4370-b71e-3bb5b48d324f', + gdprConsent: { + consentString: 'CONSENT', + gdprApplies: true, + apiVersion: 2 + }, + uspConsent: '1---' }], auctionStart: 1509369418387, - timeout: 3000, - start: 1509369418389 + timeout: 3000 }; const RESPONSE = { @@ -202,6 +207,10 @@ describe('', function () { events.getEvents.restore(); }); + after(function() { + sandbox.restore(); + }); + it('should be configurable', function () { adapterManager.registerAnalyticsAdapter({ code: 'adkernelAdn', @@ -221,7 +230,7 @@ describe('', function () { }); it('should handle auction init event', function () { - events.emit(CONSTANTS.EVENTS.AUCTION_INIT, {config: {}, timeout: 3000}); + events.emit(CONSTANTS.EVENTS.AUCTION_INIT, {config: {}, bidderRequests: [REQUEST], timeout: 3000}); const ev = analyticsAdapter.context.queue.peekAll(); expect(ev).to.have.length(1); expect(ev[0]).to.be.eql({event: 'auctionInit'}); diff --git a/test/spec/modules/adkernelAdnBidAdapter_spec.js b/test/spec/modules/adkernelAdnBidAdapter_spec.js index 3d3e64aeec9..a3063df84fe 100644 --- a/test/spec/modules/adkernelAdnBidAdapter_spec.js +++ b/test/spec/modules/adkernelAdnBidAdapter_spec.js @@ -1,5 +1,6 @@ import {expect} from 'chai'; -import {spec} from 'modules/adkernelAdnBidAdapter.js'; +import {spec} from 'modules/adkernelAdnBidAdapter'; +import {config} from 'src/config'; describe('AdkernelAdn adapter', function () { const bid1_pub1 = { @@ -263,6 +264,14 @@ describe('AdkernelAdn adapter', function () { expect(bidRequests[0].user).to.have.property('gdpr', 0); expect(bidRequests[0].user).to.not.have.property('consent'); }); + + it('should\'t contain consent string if gdpr isn\'t applied', function () { + config.setConfig({coppa: true}); + let [_, bidRequests] = buildRequest([bid1_pub1]); + config.resetConfig(); + expect(bidRequests[0]).to.have.property('user'); + expect(bidRequests[0].user).to.have.property('coppa', 1); + }); }); describe('video request building', () => { diff --git a/test/spec/modules/adkernelBidAdapter_spec.js b/test/spec/modules/adkernelBidAdapter_spec.js index 4d3dca7f344..9881acc68df 100644 --- a/test/spec/modules/adkernelBidAdapter_spec.js +++ b/test/spec/modules/adkernelBidAdapter_spec.js @@ -1,8 +1,8 @@ import {expect} from 'chai'; -import {spec} from 'modules/adkernelBidAdapter.js'; -import * as utils from 'src/utils.js'; +import {spec} from 'modules/adkernelBidAdapter'; +import * as utils from 'src/utils'; import {NATIVE, BANNER, VIDEO} from 'src/mediaTypes'; -import {config} from 'src/config.js'; +import {config} from 'src/config'; describe('Adkernel adapter', function () { const bid1_zone1 = { @@ -241,6 +241,7 @@ describe('Adkernel adapter', function () { afterEach(function () { sandbox.restore(); + config.resetConfig(); }); function buildBidderRequest(url = 'https://example.com/index.html', params = {}) { @@ -343,6 +344,14 @@ describe('Adkernel adapter', function () { expect(bidRequest.user.ext).to.be.eql({'consent': 'test-consent-string'}); }); + it('should contain coppa if configured', function () { + config.setConfig({coppa: true}); + let [_, bidRequests] = buildRequest([bid1_zone1]); + let bidRequest = bidRequests[0]; + expect(bidRequest).to.have.property('regs'); + expect(bidRequest.regs).to.have.property('coppa', 1); + }); + it('should\'t contain consent string if gdpr isn\'t applied', function () { let [_, bidRequests] = buildRequest([bid1_zone1], buildBidderRequest('https://example.com/index.html', {gdprConsent: {gdprApplies: false}})); let bidRequest = bidRequests[0]; @@ -556,7 +565,7 @@ describe('Adkernel adapter', function () { describe('adapter configuration', () => { it('should have aliases', () => { - expect(spec.aliases).to.have.lengthOf(9); + expect(spec.aliases).to.have.lengthOf(12); }); }); diff --git a/test/spec/modules/adlooxAnalyticsAdapter_spec.js b/test/spec/modules/adlooxAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..9cc9c4ded4d --- /dev/null +++ b/test/spec/modules/adlooxAnalyticsAdapter_spec.js @@ -0,0 +1,229 @@ +import adapterManager from 'src/adapterManager.js'; +import analyticsAdapter, { command as analyticsCommand, COMMAND } from 'modules/adlooxAnalyticsAdapter.js'; +import { AUCTION_COMPLETED } from 'src/auction.js'; +import { expect } from 'chai'; +import events from 'src/events.js'; +import { EVENTS } from 'src/constants.json'; +import * as utils from 'src/utils.js'; +import { loadExternalScriptStub } from 'test/mocks/adloaderStub.js'; + +const analyticsAdapterName = 'adloox'; + +describe('Adloox Analytics Adapter', function () { + let sandbox; + + const esplode = 'esplode'; + + const bid = { + bidder: 'dummy', + adUnitCode: 'ad-slot-1', + mediaType: 'display' + }; + + const auctionDetails = { + auctionStatus: AUCTION_COMPLETED, + bidsReceived: [ + bid + ] + }; + + const analyticsOptions = { + js: 'https://j.adlooxtracking.com/ads/js/tfav_adl_%%clientid%%.js', + client: 'adlooxtest', + clientid: 127, + platformid: 0, + tagid: 0, + params: { + dummy1: '%%client%%', + dummy2: '%%pbAdSlot%%', + dummy3: function(bid) { throw new Error(esplode) } + } + }; + + adapterManager.registerAnalyticsAdapter({ + code: analyticsAdapterName, + adapter: analyticsAdapter + }); + describe('enableAnalytics', function () { + describe('invalid options', function () { + it('should require options', function (done) { + adapterManager.enableAnalytics({ + provider: analyticsAdapterName + }); + expect(analyticsAdapter.context).is.null; + + done(); + }); + + it('should reject non-string options.js', function (done) { + const analyticsOptionsLocal = utils.deepClone(analyticsOptions); + analyticsOptionsLocal.js = function () { }; + + 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; + + adapterManager.enableAnalytics({ + provider: analyticsAdapterName, + options: analyticsOptionsLocal + }); + expect(analyticsAdapter.context).is.null; + + done(); + }); + + [ 'client', 'clientid', 'platformid', 'tagid' ].forEach(function (o) { + it('should require options.' + o, function (done) { + const analyticsOptionsLocal = utils.deepClone(analyticsOptions); + delete analyticsOptionsLocal[o]; + + adapterManager.enableAnalytics({ + provider: analyticsAdapterName, + options: analyticsOptionsLocal + }); + expect(analyticsAdapter.context).is.null; + + done(); + }); + }); + + it('should reject non-object options.params', function (done) { + const analyticsOptionsLocal = utils.deepClone(analyticsOptions); + analyticsOptionsLocal.params = esplode; + + adapterManager.enableAnalytics({ + provider: analyticsAdapterName, + options: analyticsOptionsLocal + }); + expect(analyticsAdapter.context).is.null; + + done(); + }); + }); + }); + + describe('process', function () { + beforeEach(function() { + sandbox = sinon.sandbox.create(); + + sandbox.stub(events, 'getEvents').returns([]); + + adapterManager.enableAnalytics({ + provider: analyticsAdapterName, + options: utils.deepClone(analyticsOptions) + }); + + expect(analyticsAdapter.context).is.not.null; + }); + + afterEach(function () { + analyticsAdapter.disableAnalytics(); + expect(analyticsAdapter.context).is.null; + + sandbox.restore(); + sandbox = undefined; + }); + + describe('event', function () { + it('should preload the script on AUCTION_END only once', function (done) { + const insertElementStub = sandbox.stub(utils, 'insertElement'); + + const uri = utils.parseUrl(analyticsAdapter.url(analyticsOptions.js)); + const isLinkPreloadAsScript = function(arg) { + const href_uri = utils.parseUrl(arg.href); // IE11 requires normalisation (hostname always includes port) + return arg.tagName === 'LINK' && arg.getAttribute('rel') === 'preload' && arg.getAttribute('as') === 'script' && href_uri.href === uri.href; + }; + + events.emit(EVENTS.AUCTION_END, auctionDetails); + expect(insertElementStub.calledWith(sinon.match(isLinkPreloadAsScript))).to.true; + + events.emit(EVENTS.AUCTION_END, auctionDetails); + expect(insertElementStub.callCount).to.equal(1); + + done(); + }); + + it('should inject verification JS on BID_WON', function (done) { + const url = analyticsAdapter.url(`${analyticsOptions.js}#`); + + const parent = document.createElement('div'); + + const slot = document.createElement('div'); + slot.id = bid.adUnitCode; + parent.appendChild(slot); + + const dummy = document.createElement('div'); + parent.appendChild(dummy); + + const querySelectorStub = sandbox.stub(document, 'querySelector'); + querySelectorStub.withArgs(`#${bid.adUnitCode}`).returns(slot); + + events.emit(EVENTS.BID_WON, bid); + + const [urlInserted, moduleCode] = loadExternalScriptStub.getCall(0).args; + + expect(urlInserted.substr(0, url.length)).to.equal(url); + expect(moduleCode).to.equal(analyticsAdapterName); + expect(/[#&]creatype=2(&|$)/.test(urlInserted)).is.true; // prebid 'display' -> adloox '2' + expect(new RegExp('[#&]dummy3=' + encodeURIComponent('ERROR: ' + esplode) + '(&|$)').test(urlInserted)).is.true; + + done(); + }); + }); + + describe('command', function () { + it('should return config', function (done) { + const expected = utils.deepClone(analyticsOptions); + delete expected.js; + delete expected.toselector; + delete expected.params; + + analyticsCommand(COMMAND.CONFIG, null, function (response) { + expect(utils.deepEqual(response, expected)).is.true; + + done(); + }); + }); + + it('should return expanded URL', function (done) { + const data = { + url: 'https://example.com?', + args: [ + [ 'client', '%%client%%' ] + ], + bid: bid, + ids: true + }; + const expected = `${data.url}${data.args[0][0]}=${analyticsOptions.client}`; + + analyticsCommand(COMMAND.URL, data, function (response) { + expect(response.substr(0, expected.length)).is.equal(expected); + + done(); + }); + }); + + it('should inject tracking event', function (done) { + const data = { + eventType: EVENTS.BID_WON, + args: bid + }; + + analyticsCommand(COMMAND.TRACK, data, function (response) { + expect(response).is.undefined; + + done(); + }); + }); + }); + }); +}); diff --git a/test/spec/modules/admixerBidAdapter_spec.js b/test/spec/modules/admixerBidAdapter_spec.js index dfadf1f95d5..1321d81d536 100644 --- a/test/spec/modules/admixerBidAdapter_spec.js +++ b/test/spec/modules/admixerBidAdapter_spec.js @@ -1,9 +1,11 @@ import {expect} from 'chai'; import {spec} from 'modules/admixerBidAdapter.js'; import {newBidder} from 'src/adapters/bidderFactory.js'; +import {config} from '../../../src/config.js'; const BIDDER_CODE = 'admixer'; const ENDPOINT_URL = 'https://inv-nets.admixer.net/prebid.1.1.aspx'; +const ENDPOINT_URL_CUSTOM = 'https://custom.admixer.net/prebid.aspx'; const ZONE_ID = '2eb6bd58-865c-47ce-af7f-a918108c3fd2'; describe('AdmixerAdapter', function () { @@ -57,6 +59,7 @@ describe('AdmixerAdapter', function () { } ]; let bidderRequest = { + bidderCode: BIDDER_CODE, refererInfo: { referer: 'https://example.com' } @@ -74,6 +77,16 @@ describe('AdmixerAdapter', function () { expect(request.url).to.equal(ENDPOINT_URL); expect(request.method).to.equal('GET'); }); + + it('sends bid request to CUSTOM_ENDPOINT via GET', function () { + config.setBidderConfig({ + bidders: [BIDDER_CODE], // one or more bidders + config: {[BIDDER_CODE]: {endpoint_url: ENDPOINT_URL_CUSTOM}} + }); + const request = config.runWithBidder(BIDDER_CODE, () => spec.buildRequests(validRequest, bidderRequest)); + expect(request.url).to.equal(ENDPOINT_URL_CUSTOM); + expect(request.method).to.equal('GET'); + }); }); describe('interpretResponse', function () { diff --git a/test/spec/modules/admixerIdSystem_spec.js b/test/spec/modules/admixerIdSystem_spec.js new file mode 100644 index 00000000000..18107b780db --- /dev/null +++ b/test/spec/modules/admixerIdSystem_spec.js @@ -0,0 +1,81 @@ +import {admixerIdSubmodule} from 'modules/admixerIdSystem.js'; +import * as utils from 'src/utils.js'; +import {server} from 'test/mocks/xhr.js'; +import {getStorageManager} from '../../../src/storageManager.js'; + +export const storage = getStorageManager(); + +const pid = '4D393FAC-B6BB-4E19-8396-0A4813607316'; +const getIdParams = {params: {pid: pid}}; +describe('admixerId tests', function () { + let logErrorStub; + + beforeEach(function () { + logErrorStub = sinon.stub(utils, 'logError'); + }); + + afterEach(function () { + logErrorStub.restore(); + }); + + it('should log an error if pid configParam was not passed when getId', function () { + admixerIdSubmodule.getId(); + expect(logErrorStub.callCount).to.be.equal(1); + + admixerIdSubmodule.getId({}); + expect(logErrorStub.callCount).to.be.equal(2); + + admixerIdSubmodule.getId({params: {}}); + expect(logErrorStub.callCount).to.be.equal(3); + + admixerIdSubmodule.getId({params: {pid: 123}}); + expect(logErrorStub.callCount).to.be.equal(4); + }); + + it('should NOT call the admixer id endpoint if gdpr applies but consent string is missing', function () { + let submoduleCallback = admixerIdSubmodule.getId(getIdParams, { gdprApplies: true }); + expect(submoduleCallback).to.be.undefined; + }); + + it('should call the admixer id endpoint', function () { + let callBackSpy = sinon.spy(); + let submoduleCallback = admixerIdSubmodule.getId(getIdParams).callback; + submoduleCallback(callBackSpy); + let request = server.requests[0]; + expect(request.url).to.be.eq(`https://inv-nets.admixer.net/cntcm.aspx?ssp=${pid}`); + request.respond( + 200, + {}, + JSON.stringify({}) + ); + expect(callBackSpy.calledOnce).to.be.true; + }); + + it('should call callback with user id', function () { + let callBackSpy = sinon.spy(); + let submoduleCallback = admixerIdSubmodule.getId(getIdParams).callback; + submoduleCallback(callBackSpy); + let request = server.requests[0]; + expect(request.url).to.be.eq(`https://inv-nets.admixer.net/cntcm.aspx?ssp=${pid}`); + request.respond( + 200, + {}, + JSON.stringify({setData: {visitorid: '571058d70bce453b80e6d98b4f8a81e3'}}) + ); + expect(callBackSpy.calledOnce).to.be.true; + expect(callBackSpy.args[0][0]).to.be.eq('571058d70bce453b80e6d98b4f8a81e3'); + }); + + it('should continue to callback if ajax response 204', function () { + let callBackSpy = sinon.spy(); + let submoduleCallback = admixerIdSubmodule.getId(getIdParams).callback; + submoduleCallback(callBackSpy); + let request = server.requests[0]; + expect(request.url).to.be.eq(`https://inv-nets.admixer.net/cntcm.aspx?ssp=${pid}`); + request.respond( + 204 + ); + expect(callBackSpy.calledOnce).to.be.true; + expect(callBackSpy.args[0][0]).to.be.undefined; + }); +}); diff --git a/test/spec/modules/adnuntiusBidAdapter_spec.js b/test/spec/modules/adnuntiusBidAdapter_spec.js index 54ff038c083..f4b0306451c 100644 --- a/test/spec/modules/adnuntiusBidAdapter_spec.js +++ b/test/spec/modules/adnuntiusBidAdapter_spec.js @@ -4,7 +4,8 @@ import { spec } from 'modules/adnuntiusBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; describe('adnuntiusBidAdapter', function () { - const ENDPOINT_URL = 'https://delivery.adnuntius.com/i?tzo=-60&format=json'; + const tzo = new Date().getTimezoneOffset(); + const ENDPOINT_URL = `https://delivery.adnuntius.com/i?tzo=${tzo}&format=json`; const adapter = newBidder(spec); const bidRequests = [ { @@ -47,8 +48,8 @@ describe('adnuntiusBidAdapter', function () { 'destination': 'http://google.com' }, 'cpm': { 'amount': 5.0, 'currency': 'NOK' }, - 'bid': { 'amount': 0.005, 'currency': 'NOK' }, - 'cost': { 'amount': 0.005, 'currency': 'NOK' }, + 'bid': { 'amount': 5.0, 'currency': 'NOK' }, + 'cost': { 'amount': 5.0, 'currency': 'NOK' }, 'impressionTrackingUrls': [], 'impressionTrackingUrlsEsc': [], 'adId': 'adn-id-1347343135', @@ -96,7 +97,7 @@ describe('adnuntiusBidAdapter', function () { expect(request[0]).to.have.property('url'); expect(request[0].url).to.equal(ENDPOINT_URL); expect(request[0]).to.have.property('data'); - expect(request[0].data).to.equal('{\"adUnits\":[{\"auId\":\"8b6bc\"}]}'); + expect(request[0].data).to.equal('{\"adUnits\":[{\"auId\":\"8b6bc\",\"targetId\":\"123\"}]}'); }); }); diff --git a/test/spec/modules/adprimeBidAdapter_spec.js b/test/spec/modules/adprimeBidAdapter_spec.js index fe05634baae..8627941dc80 100644 --- a/test/spec/modules/adprimeBidAdapter_spec.js +++ b/test/spec/modules/adprimeBidAdapter_spec.js @@ -284,4 +284,14 @@ describe('AdprimebBidAdapter', function () { expect(serverResponses).to.be.an('array').that.is.empty; }); }); + describe('getUserSyncs', function () { + let userSync = spec.getUserSyncs(); + it('Returns valid URL and type', function () { + expect(userSync).to.be.an('array').with.lengthOf(1); + expect(userSync[0].type).to.exist; + expect(userSync[0].url).to.exist; + expect(userSync[0].type).to.be.equal('image'); + expect(userSync[0].url).to.be.equal('https://delta.adprime.com/?c=rtb&m=sync'); + }); + }); }); diff --git a/test/spec/modules/adrelevantisBidAdapter_spec.js b/test/spec/modules/adrelevantisBidAdapter_spec.js index 11a6a14a353..596f3bade5d 100644 --- a/test/spec/modules/adrelevantisBidAdapter_spec.js +++ b/test/spec/modules/adrelevantisBidAdapter_spec.js @@ -224,12 +224,14 @@ describe('AdrelevantisAdapter', function () { let bidRequest = Object.assign({}, bidRequests[0]); sinon .stub(config, 'getConfig') - .withArgs('fpd') + .withArgs('ortb2') .returns({ - context: { + site: { keywords: 'US Open', - data: { - category: 'sports/tennis' + ext: { + data: { + category: 'sports/tennis' + } } } }); diff --git a/test/spec/modules/adtelligentBidAdapter_spec.js b/test/spec/modules/adtelligentBidAdapter_spec.js index 5c1e4a38d03..67372216e96 100644 --- a/test/spec/modules/adtelligentBidAdapter_spec.js +++ b/test/spec/modules/adtelligentBidAdapter_spec.js @@ -14,7 +14,8 @@ const aliasEP = { appaloosa: 'https://ghb.hb.appaloosa.media/v2/auction/', appaloosa_publisherSuffix: 'https://ghb.hb.appaloosa.media/v2/auction/', onefiftytwomedia: 'https://ghb.ads.152media.com/v2/auction/', - mediafuse: 'https://ghb.hbmp.mediafuse.com/v2/auction/' + mediafuse: 'https://ghb.hbmp.mediafuse.com/v2/auction/', + navelix: 'https://ghb.hb.navelix.com/v2/auction/', }; const DEFAULT_ADATPER_REQ = { bidderCode: 'adtelligent' }; @@ -294,7 +295,8 @@ describe('adtelligentBidAdapter', () => { CallbackId: '84ab500420319d', AdType: 'video', Aid: 12345, - Sizes: '480x360,640x480' + Sizes: '480x360,640x480', + PlacementId: 'adunit-code' }; expect(data.BidRequests[0]).to.deep.equal(eq); }); @@ -306,7 +308,8 @@ describe('adtelligentBidAdapter', () => { CallbackId: '84ab500420319d', AdType: 'display', Aid: 12345, - Sizes: '300x250' + Sizes: '300x250', + PlacementId: 'adunit-code' }; expect(data.BidRequests[0]).to.deep.equal(eq); @@ -318,12 +321,14 @@ describe('adtelligentBidAdapter', () => { CallbackId: '84ab500420319d', AdType: 'display', Aid: 12345, - Sizes: '300x250' + Sizes: '300x250', + PlacementId: 'adunit-code' }, { CallbackId: '84ab500420319d', AdType: 'video', Aid: 12345, - Sizes: '480x360,640x480' + Sizes: '480x360,640x480', + PlacementId: 'adunit-code' }] expect(bidRequests.BidRequests).to.deep.equal(expectedBidReqs); diff --git a/test/spec/modules/adtrueBidAdapter_spec.js b/test/spec/modules/adtrueBidAdapter_spec.js new file mode 100644 index 00000000000..8e1c872d460 --- /dev/null +++ b/test/spec/modules/adtrueBidAdapter_spec.js @@ -0,0 +1,431 @@ +import {expect} from 'chai' +import {spec} from 'modules/adtrueBidAdapter.js' +import {newBidder} from 'src/adapters/bidderFactory.js' +import * as utils from '../../../src/utils.js'; +import {config} from 'src/config.js'; + +describe('AdTrueBidAdapter', function () { + const adapter = newBidder(spec) + let bidRequests; + let bidResponses; + let bidResponses2; + beforeEach(function () { + bidRequests = [ + { + bidder: 'adtrue', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + params: { + publisherId: '1212', + zoneId: '21423', + reserve: 0.2 + }, + placementCode: 'adunit-code-1', + sizes: [[300, 250]], + bidId: '23acc48ad47af5', + requestId: '0fb4905b-9456-4152-86be-c6f6d259ba99', + bidderRequestId: '1c56ad30b9b8ca8', + transactionId: '92489f71-1bf2-49a0-adf9-000cea934729', + schain: { + 'ver': '1.0', + 'complete': 1, + 'nodes': [ + { + 'asi': 'indirectseller.com', + 'sid': '00001', + 'hp': 1 + }, + + { + 'asi': 'indirectseller-2.com', + 'sid': '00002', + 'hp': 2 + } + ] + } + } + ]; + bidResponses = { + body: { + id: '1610681506302', + seatbid: [ + { + bid: [ + { + id: '1', + impid: '201fb513ca24e9', + price: 2.880000114440918, + burl: 'https://hb.adtrue.com/prebid/win-notify?impid=1610681506302&wp=${AUCTION_PRICE}', + adm: '', + adid: '1610681506302', + adomain: [ + 'adtrue.com' + ], + cid: 'f6l0r6n', + crid: 'abc77au4', + attr: [], + w: 300, + h: 250 + } + ], + seat: 'adtrue', + group: 0 + } + ], + bidid: '1610681506302', + cur: 'USD', + ext: { + cookie_sync: [ + { + type: 1, + url: 'https://hb.adtrue.com/prebid/usersync?bidder=adtrue' + } + ] + } + } + }; + bidResponses2 = { + body: { + id: '1610681506302', + seatbid: [ + { + bid: [ + { + id: '1', + impid: '201fb513ca24e9', + price: 2.880000114440918, + burl: 'https://hb.adtrue.com/prebid/win-notify?impid=1610681506302&wp=${AUCTION_PRICE}', + adm: '', + adid: '1610681506302', + adomain: [ + 'adtrue.com' + ], + cid: 'f6l0r6n', + crid: 'abc77au4', + attr: [], + w: 300, + h: 250 + } + ], + seat: 'adtrue', + group: 0 + } + ], + bidid: '1610681506302', + cur: 'USD', + ext: { + cookie_sync: [ + { + type: 2, + url: 'https://hb.adtrue.com/prebid/usersync?bidder=adtrue&type=image' + }, + { + type: 1, + url: 'https://hb.adtrue.com/prebid/usersync?bidder=appnexus' + } + ] + } + } + }; + }); + + describe('.code', function () { + it('should return a bidder code of adtrue', function () { + expect(spec.code).to.equal('adtrue') + }) + }) + + describe('inherited functions', function () { + it('should exist and be a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function') + }) + }) + describe('implementation', function () { + describe('Bid validations', function () { + it('valid bid case', function () { + let validBid = { + bidder: 'adtrue', + params: { + zoneId: '21423', + publisherId: '1212' + } + }, + isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(true); + }); + it('invalid bid case: publisherId not passed', function () { + let validBid = { + bidder: 'adtrue', + params: { + zoneId: '21423' + } + }, + isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(false); + }); + it('valid bid case: zoneId is not passed', function () { + let validBid = { + bidder: 'adtrue', + params: { + publisherId: '1212' + } + }, + isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(false); + }); + it('should return false if there are no params', () => { + const bid = { + 'bidder': 'adtrue', + 'adUnitCode': 'adunit-code', + 'mediaTypes': { + banner: { + sizes: [[300, 250]] + } + }, + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + it('should return false if there is no publisherId param', () => { + const bid = { + 'bidder': 'adtrue', + 'adUnitCode': 'adunit-code', + params: { + zoneId: '21423', + }, + 'mediaTypes': { + banner: { + sizes: [[300, 250]] + } + }, + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + it('should return false if there is no zoneId param', () => { + const bid = { + 'bidder': 'adtrue', + 'adUnitCode': 'adunit-code', + params: { + publisherId: '1212', + }, + 'mediaTypes': { + banner: { + sizes: [[300, 250]] + } + }, + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + it('should return true if the bid is valid', () => { + const bid = { + 'bidder': 'adtrue', + 'adUnitCode': 'adunit-code', + params: { + zoneId: '21423', + publisherId: '1212', + }, + 'mediaTypes': { + banner: { + sizes: [[300, 250]] + } + }, + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + }); + describe('Request formation', function () { + it('buildRequests function should not modify original bidRequests object', function () { + let originalBidRequests = utils.deepClone(bidRequests); + let request = spec.buildRequests(bidRequests, { + auctionId: 'new-auction-id' + }); + expect(bidRequests).to.deep.equal(originalBidRequests); + }); + + it('Endpoint/method checking', function () { + let request = spec.buildRequests(bidRequests, { + auctionId: 'new-auction-id' + }); + expect(request.url).to.equal('https://hb.adtrue.com/prebid/auction'); + expect(request.method).to.equal('POST'); + }); + + it('test flag not sent when adtrueTest=true is absent in page url', function () { + let request = spec.buildRequests(bidRequests, { + auctionId: 'new-auction-id' + }); + let data = JSON.parse(request.data); + expect(data.test).to.equal(undefined); + }); + it('Request params check', function () { + let request = spec.buildRequests(bidRequests, { + auctionId: 'new-auction-id' + }); + let data = JSON.parse(request.data); + expect(data.at).to.equal(1); // auction type + expect(data.cur[0]).to.equal('USD'); // currency + expect(data.site.domain).to.be.a('string'); // domain should be set + expect(data.site.publisher.id).to.equal(bidRequests[0].params.publisherId); // publisher Id + expect(data.ext.wrapper.transactionId).to.equal(bidRequests[0].transactionId); // Prebid TransactionId + expect(data.source.tid).to.equal(bidRequests[0].transactionId); // Prebid TransactionId + expect(data.imp[0].id).to.equal(bidRequests[0].bidId); // Prebid bid id is passed as id + expect(data.imp[0].bidfloor).to.equal(bidRequests[0].params.reserve); // reverse + expect(data.imp[0].tagid).to.equal(bidRequests[0].params.zoneId); // zoneId + expect(data.imp[0].banner.w).to.equal(300); // width + expect(data.imp[0].banner.h).to.equal(250); // height + expect(data.source.ext.schain).to.deep.equal(bidRequests[0].schain); + }); + it('Request params check with GDPR Consent', function () { + let bidRequest = { + gdprConsent: { + consentString: 'kjfdniwjnifwenrif3', + gdprApplies: true + } + }; + let request = spec.buildRequests(bidRequests, bidRequest); + let data = JSON.parse(request.data); + expect(data.user.ext.consent).to.equal('kjfdniwjnifwenrif3'); + expect(data.at).to.equal(1); // auction type + expect(data.cur[0]).to.equal('USD'); // currency + expect(data.site.domain).to.be.a('string'); // domain should be set + expect(data.site.publisher.id).to.equal(bidRequests[0].params.publisherId); // publisher Id + expect(data.ext.wrapper.transactionId).to.equal(bidRequests[0].transactionId); // Prebid TransactionId + expect(data.source.tid).to.equal(bidRequests[0].transactionId); // Prebid TransactionId + expect(data.imp[0].id).to.equal(bidRequests[0].bidId); // Prebid bid id is passed as id + expect(data.imp[0].bidfloor).to.equal(parseFloat(bidRequests[0].params.reserve)); // reverse + expect(data.imp[0].tagid).to.equal(bidRequests[0].params.zoneId); // zoneId + expect(data.imp[0].banner.w).to.equal(300); // width + expect(data.imp[0].banner.h).to.equal(250); // height + expect(data.source.ext.schain).to.deep.equal(bidRequests[0].schain); + }); + it('Request params check with USP/CCPA Consent', function () { + let bidRequest = { + uspConsent: '1NYN' + }; + let request = spec.buildRequests(bidRequests, bidRequest); + let data = JSON.parse(request.data); + expect(data.regs.ext.us_privacy).to.equal('1NYN');// USP/CCPAs + expect(data.at).to.equal(1); // auction type + expect(data.cur[0]).to.equal('USD'); // currency + expect(data.site.domain).to.be.a('string'); // domain should be set + expect(data.site.publisher.id).to.equal(bidRequests[0].params.publisherId); // publisher Id + expect(data.ext.wrapper.transactionId).to.equal(bidRequests[0].transactionId); // Prebid TransactionId + expect(data.source.tid).to.equal(bidRequests[0].transactionId); // Prebid TransactionId + expect(data.imp[0].id).to.equal(bidRequests[0].bidId); // Prebid bid id is passed as id + expect(data.imp[0].bidfloor).to.equal(parseFloat(bidRequests[0].params.reserve)); // reverse + expect(data.imp[0].tagid).to.equal(bidRequests[0].params.zoneId); // zoneId + expect(data.imp[0].banner.w).to.equal(300); // width + expect(data.imp[0].banner.h).to.equal(250); // height + expect(data.source.ext.schain).to.deep.equal(bidRequests[0].schain); + }); + + it('should NOT include coppa flag in bid request if coppa config is not present', () => { + const request = spec.buildRequests(bidRequests, {}); + let data = JSON.parse(request.data); + if (data.regs) { + // in case GDPR is set then data.regs will exist + expect(data.regs.coppa).to.equal(undefined); + } else { + expect(data.regs).to.equal(undefined); + } + }); + it('should include coppa flag in bid request if coppa is set to true', () => { + let sandbox = sinon.sandbox.create(); + sandbox.stub(config, 'getConfig').callsFake(key => { + const config = { + 'coppa': true + }; + return config[key]; + }); + const request = spec.buildRequests(bidRequests, {}); + let data = JSON.parse(request.data); + expect(data.regs.coppa).to.equal(1); + sandbox.restore(); + }); + it('should NOT include coppa flag in bid request if coppa is set to false', () => { + let sandbox = sinon.sandbox.create(); + sandbox.stub(config, 'getConfig').callsFake(key => { + const config = { + 'coppa': false + }; + return config[key]; + }); + const request = spec.buildRequests(bidRequests, {}); + let data = JSON.parse(request.data); + if (data.regs) { + // in case GDPR is set then data.regs will exist + expect(data.regs.coppa).to.equal(undefined); + } else { + expect(data.regs).to.equal(undefined); + } + sandbox.restore(); + }); + }); + }); + describe('Response checking', function () { + it('should check for valid response values', function () { + let request = spec.buildRequests(bidRequests, { + auctionId: 'new-auction-id' + }); + let data = JSON.parse(request.data); + let response = spec.interpretResponse(bidResponses, request); + expect(response).to.be.an('array').with.length.above(0); + expect(response[0].requestId).to.equal(bidResponses.body.seatbid[0].bid[0].impid); + expect(response[0].width).to.equal(bidResponses.body.seatbid[0].bid[0].w); + expect(response[0].height).to.equal(bidResponses.body.seatbid[0].bid[0].h); + if (bidResponses.body.seatbid[0].bid[0].crid) { + expect(response[0].creativeId).to.equal(bidResponses.body.seatbid[0].bid[0].crid); + } else { + expect(response[0].creativeId).to.equal(bidResponses.body.seatbid[0].bid[0].id); + } + expect(response[0].currency).to.equal('USD'); + expect(response[0].ttl).to.equal(300); + expect(response[0].meta.clickUrl).to.equal('adtrue.com'); + expect(response[0].meta.advertiserDomains[0]).to.equal('adtrue.com'); + expect(response[0].ad).to.equal(bidResponses.body.seatbid[0].bid[0].adm); + expect(response[0].partnerImpId).to.equal(bidResponses.body.seatbid[0].bid[0].id); + }); + }); + describe('getUserSyncs', function () { + let sandbox; + beforeEach(function () { + sandbox = sinon.sandbox.create(); + }); + afterEach(function () { + sandbox.restore(); + }); + it('execute as per config', function () { + expect(spec.getUserSyncs({iframeEnabled: true}, [bidResponses], undefined, undefined)).to.deep.equal([{ + type: 'iframe', + url: 'https://hb.adtrue.com/prebid/usersync?bidder=adtrue&publisherId=1212&zoneId=21423&gdpr=0&gdpr_consent=&us_privacy=&coppa=0' + }]); + }); + // Multiple user sync output + it('execute as per config', function () { + expect(spec.getUserSyncs({iframeEnabled: true}, [bidResponses2], undefined, undefined)).to.deep.equal([ + { + type: 'image', + url: 'https://hb.adtrue.com/prebid/usersync?bidder=adtrue&type=image&publisherId=1212&zoneId=21423&gdpr=0&gdpr_consent=&us_privacy=&coppa=0' + }, + { + type: 'iframe', + url: 'https://hb.adtrue.com/prebid/usersync?bidder=appnexus&publisherId=1212&zoneId=21423&gdpr=0&gdpr_consent=&us_privacy=&coppa=0' + } + ]); + }); + }); +}); diff --git a/test/spec/modules/adyoulikeBidAdapter_spec.js b/test/spec/modules/adyoulikeBidAdapter_spec.js index ec8f2f00923..c432ad1b32d 100644 --- a/test/spec/modules/adyoulikeBidAdapter_spec.js +++ b/test/spec/modules/adyoulikeBidAdapter_spec.js @@ -58,12 +58,137 @@ describe('Adyoulike Adapter', function () { 'mediaTypes': { 'banner': {'sizes': ['300x250'] + }, + 'native': + { 'image': { + 'required': true, + }, + 'title': { + 'required': true, + 'len': 80 + }, + 'cta': { + 'required': false + }, + 'sponsoredBy': { + 'required': true + }, + 'clickUrl': { + 'required': true + }, + 'privacyIcon': { + 'required': false + }, + 'privacyLink': { + 'required': false + }, + 'body': { + 'required': true + }, + 'icon': { + 'required': true, + 'sizes': [] } + }, }, 'transactionId': 'bid_id_0_transaction_id' } ]; + const bidRequestWithNativeImageType = [ + { + 'bidId': 'bid_id_0', + 'bidder': 'adyoulike', + 'placementCode': 'adunit/hb-0', + 'params': { + 'placement': 'placement_0' + }, + 'sizes': '300x250', + 'mediaTypes': + { + 'native': { + 'type': 'image', + 'additional': { + 'will': 'be', + 'sent': ['300x250'] + } + }, + }, + 'transactionId': 'bid_id_0_transaction_id' + } + ]; + + const sentBidNative = { + 'bid_id_0': { + 'PlacementID': 'e622af275681965d3095808561a1e510', + 'TransactionID': 'e8355240-d976-4cd5-a493-640656fe08e8', + 'AvailableSizes': '', + 'Native': { + 'image': { + 'required': true, + 'sizes': [] + }, + 'title': { + 'required': true, + 'len': 80 + }, + 'cta': { + 'required': false + }, + 'sponsoredBy': { + 'required': true + }, + 'clickUrl': { + 'required': true + }, + 'privacyIcon': { + 'required': false + }, + 'privacyLink': { + 'required': false + }, + 'body': { + 'required': true + }, + 'icon': { + 'required': true, + 'sizes': [] + } + } + } + }; + + const sentNativeImageType = { + 'additional': { + 'sent': [ + '300x250' + ], + 'will': 'be' + }, + 'body': { + 'required': false + }, + 'clickUrl': { + 'required': true + }, + 'cta': { + 'required': false + }, + 'icon': { + 'required': false + }, + 'image': { + 'required': true + }, + 'sponsoredBy': { + 'required': true + }, + 'title': { + 'required': true + }, + 'type': 'image' + }; + const bidRequestWithDCPlacement = [ { 'bidId': 'bid_id_0', @@ -165,11 +290,12 @@ describe('Adyoulike Adapter', function () { 'Placement': 'placement_0' } ]; + const admSample = "\u003cscript id=\"ayl-prebid-a11a121205932e75e622af275681965d\"\u003e\n(function(){\n\twindow.isPrebid = true\n\tvar prebidResults = /*PREBID*/{\"OnEvents\":{\"CLICK\":[{\"Kind\":\"PIXEL_URL\",\"Url\":\"https://testPixelCLICK.com/fake\"}],\"IMPRESSION\":[{\"Kind\":\"PIXEL_URL\",\"Url\":\"https://testPixelIMP.com/fake\"},{\"Kind\":\"JAVASCRIPT_URL\",\"Url\":\"https://testJsIMP.com/fake.js\"}]},\"Disabled\":false,\"Attempt\":\"a11a121205932e75e622af275681965d\",\"ApiPrefix\":\"https://fo-api.omnitagjs.com/fo-api\",\"TrackingPrefix\":\"https://tracking.omnitagjs.com/tracking\",\"DynamicPrefix\":\"https://tag-dyn.omnitagjs.com/fo-dyn\",\"StaticPrefix\":\"https://fo-static.omnitagjs.com/fo-static\",\"BlobPrefix\":\"https://fo-api.omnitagjs.com/fo-api/blobs\",\"SspPrefix\":\"https://fo-ssp.omnitagjs.com/fo-ssp\",\"VisitorPrefix\":\"https://visitor.omnitagjs.com/visitor\",\"Trusted\":true,\"Placement\":\"e622af275681965d3095808561a1e510\",\"PlacementAccess\":\"ALL\",\"Site\":\"6e2df7a92203c3c7a25561ed63f25a27\",\"Lang\":\"EN\",\"SiteLogo\":null,\"HasSponsorImage\":false,\"ResizeIframe\":true,\"IntegrationConfig\":{\"Kind\":\"WIDGET\",\"Widget\":{\"ExtraStyleSheet\":\"\",\"Placeholders\":{\"Body\":{\"Color\":{\"R\":77,\"G\":21,\"B\":82,\"A\":100},\"BackgroundColor\":{\"R\":255,\"G\":255,\"B\":255,\"A\":100},\"FontFamily\":\"Lato\",\"Width\":\"100%\",\"Align\":\"\",\"BoxShadow\":true},\"CallToAction\":{\"Color\":{\"R\":26,\"G\":157,\"B\":212,\"A\":100}},\"Description\":{\"Length\":130},\"Image\":{\"Width\":600,\"Height\":600,\"Lowres\":false,\"Raw\":false},\"Size\":{\"Height\":\"250px\",\"Width\":\"300px\"},\"Sponsor\":{\"Color\":{\"R\":35,\"G\":35,\"B\":35,\"A\":100},\"Label\":true,\"WithoutLogo\":false},\"Title\":{\"Color\":{\"R\":219,\"G\":181,\"B\":255,\"A\":100}}},\"Selector\":{\"Kind\":\"CSS\",\"Css\":\"#ayl-prebid-a11a121205932e75e622af275681965d\"},\"Insertion\":\"AFTER\",\"ClickFormat\":true,\"Creative20\":true,\"WidgetKind\":\"CREATIVE_TEMPLATE_4\"}},\"Legal\":\"Sponsored\",\"ForcedCampaign\":\"f1c80d4bb5643c222ae8de75e9b2f991\",\"ForcedTrack\":\"\",\"ForcedCreative\":\"\",\"ForcedSource\":\"\",\"DisplayMode\":\"DEFAULT\",\"Campaign\":\"f1c80d4bb5643c222ae8de75e9b2f991\",\"CampaignAccess\":\"ALL\",\"CampaignKind\":\"AD_TRAFFIC\",\"DataSource\":\"LOCAL\",\"DataSourceUrl\":\"\",\"DataSourceOnEventsIsolated\":false,\"DataSourceWithoutCookie\":false,\"Content\":{\"Preview\":{\"Thumbnail\":{\"Image\":{\"Kind\":\"EXTERNAL\",\"Data\":{\"External\":{\"Url\":\"https://tag-dyn.omnitagjs.com/fo-dyn/native/preview/image?key=fd4362d35bb174d6f1c80d4bb5643c22\\u0026kind=INTERNAL\\u0026ztop=0.000000\\u0026zleft=0.000000\\u0026zwidth=0.333333\\u0026zheight=1.000000\\u0026width=[width]\\u0026height=[height]\"}},\"ZoneTop\":0,\"ZoneLeft\":0,\"ZoneWidth\":1,\"ZoneHeight\":1,\"Smart\":false,\"NoTransform\":false,\"Quality\":\"NORMAL\"}},\"Text\":{\"CALLTOACTION\":\"Click here to learn more\",\"DESCRIPTION\":\"Considérant l'extrémité conjoncturelle, il serait bon d'anticiper toutes les voies de bon sens.\",\"SPONSOR\":\"Tested by\",\"TITLE\":\"Adserver Traffic Redirect Internal\"},\"Sponsor\":{\"Name\":\"QA Team\"},\"Credit\":{\"Logo\":{\"Resource\":{\"Kind\":\"EXTERNAL\",\"Data\":{\"External\":{\"Url\":\"https://fo-static.omnitagjs.com/fo-static/native/images/info-ayl.png\"}},\"ZoneTop\":0,\"ZoneLeft\":0,\"ZoneWidth\":1,\"ZoneHeight\":1,\"Smart\":false,\"NoTransform\":false,\"Quality\":\"NORMAL\"}},\"Url\":\"https://blobs.omnitagjs.com/adchoice/\"}},\"Landing\":{\"Url\":\"https://www.w3.org/People/mimasa/test/xhtml/entities/entities-11.xhtml#lat1\",\"LegacyTracking\":false},\"ViewButtons\":{\"Close\":{\"Skip\":6000}},\"InternalContentFields\":{\"AnimatedImage\":false}},\"AdDomain\":\"adyoulike.com\",\"Opener\":\"REDIRECT\",\"PerformUITriggers\":[\"CLICK\"],\"RedirectionTarget\":\"TAB\"}/*PREBID*/;\n\tvar insertAds = function insertAds() {\insertAds();\n\t}\n})();\n\u003c/script\u003e"; const responseWithSinglePlacement = [ { 'BidID': 'bid_id_0', 'Placement': 'placement_0', - 'Ad': 'placement_0', + 'Ad': admSample, 'Price': 0.5, 'Height': 600, } @@ -214,15 +340,34 @@ describe('Adyoulike Adapter', function () { 'transactionId': 'bid_id_1_transaction_id' }; + let nativeBid = { + 'bidId': 'bid_id_1', + 'bidder': 'adyoulike', + 'placementCode': 'adunit/hb-1', + 'params': { + 'placement': 'placement_1' + }, + mediaTypes: { + native: { + + } + }, + 'transactionId': 'bid_id_1_transaction_id' + }; + it('should return true when required params found', function () { - expect(spec.isBidRequestValid(bid)).to.equal(true); + expect(!!spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return true when required params found for native ad', function () { + expect(!!spec.isBidRequestValid(nativeBid)).to.equal(true); }); it('should return false when required params are not passed', function () { let bid = Object.assign({}, bid); delete bid.size; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(!!spec.isBidRequestValid(bid)).to.equal(false); }); it('should return false when required params are not passed', function () { @@ -231,7 +376,7 @@ describe('Adyoulike Adapter', function () { bid.params = { 'placement': 0 }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(!!spec.isBidRequestValid(bid)).to.equal(false); }); }); @@ -250,6 +395,23 @@ describe('Adyoulike Adapter', function () { canonicalQuery.restore(); }); + it('Should expand short native image config type', function() { + const request = spec.buildRequests(bidRequestWithNativeImageType, bidderRequest); + const payload = JSON.parse(request.data); + + expect(request.url).to.contain(getEndpoint()); + expect(request.method).to.equal('POST'); + expect(request.url).to.contains('CanonicalUrl=' + encodeURIComponent(canonicalUrl)); + expect(request.url).to.contains('RefererUrl=' + encodeURIComponent(referrerUrl)); + expect(request.url).to.contains('PublisherDomain=http%3A%2F%2Flocalhost%3A9876'); + + expect(payload.Version).to.equal('1.0'); + expect(payload.Bids['bid_id_0'].PlacementID).to.be.equal('placement_0'); + expect(payload.PageRefreshed).to.equal(false); + expect(payload.Bids['bid_id_0'].TransactionID).to.be.equal('bid_id_0_transaction_id'); + expect(payload.Bids['bid_id_0'].Native).deep.equal(sentNativeImageType); + }); + it('should add gdpr/usp consent information to the request', function () { let consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; let uspConsentData = '1YCC'; @@ -384,7 +546,7 @@ describe('Adyoulike Adapter', function () { expect(result.length).to.equal(1); expect(result[0].cpm).to.equal(0.5); - expect(result[0].ad).to.equal('placement_0'); + expect(result[0].ad).to.equal(admSample); expect(result[0].width).to.equal(300); expect(result[0].height).to.equal(600); }); @@ -405,5 +567,48 @@ describe('Adyoulike Adapter', function () { expect(result[1].width).to.equal(400); expect(result[1].height).to.equal(250); }); + + it('receive reponse with Native ad', function () { + serverResponse.body = responseWithSinglePlacement; + let result = spec.interpretResponse(serverResponse, {data: '{"Bids":' + JSON.stringify(sentBidNative) + '}'}); + + expect(result.length).to.equal(1); + + expect(result).to.deep.equal([{ + cpm: 0.5, + creativeId: undefined, + currency: 'USD', + netRevenue: true, + requestId: 'bid_id_0', + ttl: 3600, + mediaType: 'native', + native: { + body: 'Considérant l\'extrémité conjoncturelle, il serait bon d\'anticiper toutes les voies de bon sens.', + clickTrackers: [ + 'https://testPixelCLICK.com/fake' + ], + clickUrl: 'https://tracking.omnitagjs.com/tracking/ar?event_kind=CLICK&attempt=a11a121205932e75e622af275681965d&campaign=f1c80d4bb5643c222ae8de75e9b2f991&url=https%3A%2F%2Fwww.w3.org%2FPeople%2Fmimasa%2Ftest%2Fxhtml%2Fentities%2Fentities-11.xhtml%23lat1', + cta: 'Click here to learn more', + image: { + height: 600, + url: 'https://blobs.omnitagjs.com/blobs/f1/f1c80d4bb5643c22/fd4362d35bb174d6f1c80d4bb5643c22', + width: 300, + }, + impressionTrackers: [ + 'https://testPixelIMP.com/fake', + 'https://tracking.omnitagjs.com/tracking/pixel?event_kind=IMPRESSION&attempt=a11a121205932e75e622af275681965d&campaign=f1c80d4bb5643c222ae8de75e9b2f991', + 'https://tracking.omnitagjs.com/tracking/pixel?event_kind=INSERTION&attempt=a11a121205932e75e622af275681965d&campaign=f1c80d4bb5643c222ae8de75e9b2f991' + ], + javascriptTrackers: [ + 'https://testJsIMP.com/fake.js' + ], + privacyIcon: 'https://fo-static.omnitagjs.com/fo-static/native/images/info-ayl.png', + privacyLink: 'https://blobs.omnitagjs.com/adchoice/', + sponsoredBy: 'QA Team', + title: 'Adserver Traffic Redirect Internal', + } + + }]); + }); }); }); diff --git a/test/spec/modules/amxBidAdapter_spec.js b/test/spec/modules/amxBidAdapter_spec.js index 766045b0f3e..f502d631c17 100644 --- a/test/spec/modules/amxBidAdapter_spec.js +++ b/test/spec/modules/amxBidAdapter_spec.js @@ -17,10 +17,12 @@ const embeddedTrackingPixel = `https://1x1.a-mo.net/hbx/g_impression?A=sample&B= const sampleNurl = 'https://example.exchange/nurl'; const sampleFPD = { - context: { + site: { keywords: 'sample keywords', - data: { - pageType: 'article' + ext: { + data: { + pageType: 'article' + } } }, user: { @@ -31,7 +33,7 @@ const sampleFPD = { const stubConfig = (withStub) => { const stub = sinon.stub(config, 'getConfig').callsFake( - (arg) => arg === 'fpd' ? sampleFPD : null + (arg) => arg === 'ortb2' ? sampleFPD : null ) withStub(); @@ -58,6 +60,15 @@ const sampleBidRequestBase = { endpoint: 'https://httpbin.org/post', }, sizes: [[320, 50]], + getFloor(params) { + if (params.size == null || params.currency == null || params.mediaType == null) { + throw new Error(`getFloor called with incomplete params: ${JSON.stringify(params)}`) + } + return { + floor: 0.5, + currency: 'USD' + } + }, mediaTypes: { [BANNER]: { sizes: [[300, 250]] @@ -69,13 +80,28 @@ const sampleBidRequestBase = { auctionId: utils.getUniqueIdentifierStr(), }; +const schainConfig = { + ver: '1.0', + nodes: [{ + asi: 'greatnetwork.exchange', + sid: '000001', + hp: 1, + rid: 'bid_request_1', + domain: 'publisher.com' + }] +}; + const sampleBidRequestVideo = { ...sampleBidRequestBase, bidId: sampleRequestId + '_video', sizes: [[300, 150]], + schain: schainConfig, mediaTypes: { [VIDEO]: { - sizes: [[360, 250]] + sizes: [[360, 250]], + context: 'adpod', + adPodDurationSec: 90, + contentMode: 'live' } } }; @@ -104,6 +130,7 @@ const sampleServerResponse = { 'h': 600, 'id': '2014691335735134254', 'impid': '1', + 'exp': 90, 'price': 0.25, 'w': 300 }, @@ -123,6 +150,7 @@ const sampleServerResponse = { 'h': 1, 'id': '7735706981389902829', 'impid': '1', + 'exp': 90, 'price': 0.25, 'w': 1 }, @@ -144,8 +172,11 @@ describe('AmxBidAdapter', () => { expect(spec.isBidRequestValid({params: { tagId: 'test' }})).to.equal(true) }); - it('testMode is an optional boolean', () => { - expect(spec.isBidRequestValid({params: { testMode: 1 }})).to.equal(false) + it('testMode is an optional truthy value', () => { + expect(spec.isBidRequestValid({params: { testMode: 1 }})).to.equal(true) + expect(spec.isBidRequestValid({params: { testMode: 'true' }})).to.equal(true) + // ignore invalid values (falsy) + expect(spec.isBidRequestValid({params: { testMode: 'non-truthy-invalid-value' }})).to.equal(true) expect(spec.isBidRequestValid({params: { testMode: false }})).to.equal(true) }); @@ -179,6 +210,17 @@ describe('AmxBidAdapter', () => { expect(url).to.equal('https://prebid.a-mo.net/a/c') }); + it('will read the prebid version & global', () => { + const { data: { V: prebidVersion, vg: prebidGlobal } } = spec.buildRequests([{ + ...sampleBidRequestBase, + params: { + testMode: true + } + }], sampleBidderRequest); + expect(prebidVersion).to.equal('$prebid.version$') + expect(prebidGlobal).to.equal('$$PREBID_GLOBAL$$') + }); + it('reads test mode from the first bid request', () => { const { data } = spec.buildRequests([{ ...sampleBidRequestBase, @@ -253,7 +295,7 @@ describe('AmxBidAdapter', () => { it('will forward first-party data', () => { stubConfig(() => { const { data } = spec.buildRequests([sampleBidRequestBase], sampleBidderRequest); - expect(data.fpd).to.deep.equal(sampleFPD) + expect(data.fpd2).to.deep.equal(sampleFPD) }); }); @@ -299,20 +341,24 @@ describe('AmxBidAdapter', () => { expect(data.m[sampleRequestId]).to.deep.equal({ av: true, au: 'div-gpt-ad-example', + vd: {}, ms: [ [[320, 50]], [[300, 250]], [] ], aw: 300, + sc: {}, ah: 250, tf: 0, + f: 0.5, vr: false }); expect(data.m[sampleRequestId + '_2']).to.deep.equal({ av: true, aw: 300, au: 'div-gpt-ad-example', + sc: {}, ms: [ [[320, 50]], [[300, 250]], @@ -320,7 +366,9 @@ describe('AmxBidAdapter', () => { ], i: 'example', ah: 250, + vd: {}, tf: 0, + f: 0.5, vr: false, }); }); @@ -338,7 +386,15 @@ describe('AmxBidAdapter', () => { av: true, aw: 360, ah: 250, + sc: schainConfig, + vd: { + sizes: [[360, 250]], + context: 'adpod', + adPodDurationSec: 90, + contentMode: 'live' + }, tf: 0, + f: 0.5, vr: true }); }); @@ -383,9 +439,10 @@ describe('AmxBidAdapter', () => { ...baseBidResponse.meta, mediaType: BANNER, }, + mediaType: BANNER, width: 300, height: 600, // from the bid itself - ttl: 70, + ttl: 90, ad: sampleDisplayAd( `` + `` @@ -396,20 +453,14 @@ describe('AmxBidAdapter', () => { it('can parse a video ad', () => { const parsed = spec.interpretResponse({ body: sampleServerResponse }, baseRequest) expect(parsed.length).to.equal(2) - - // we should have display, video, display - const xml = parsed[1].vastXml - delete parsed[1].vastXml - - expect(xml).to.have.string(``) - expect(xml).to.have.string(``) - expect(parsed[1]).to.deep.equal({ ...baseBidResponse, meta: { ...baseBidResponse.meta, mediaType: VIDEO, }, + mediaType: VIDEO, + vastXml: sampleVideoAd(''), width: 300, height: 250, ttl: 90, diff --git a/test/spec/modules/apacdexBidAdapter_spec.js b/test/spec/modules/apacdexBidAdapter_spec.js index da9a050a8de..3a71833bc3e 100644 --- a/test/spec/modules/apacdexBidAdapter_spec.js +++ b/test/spec/modules/apacdexBidAdapter_spec.js @@ -1,7 +1,8 @@ import { expect } from 'chai' -import { spec } from 'modules/apacdexBidAdapter.js' +import { spec, validateGeoObject, getDomain } from '../../../modules/apacdexBidAdapter.js' import { newBidder } from 'src/adapters/bidderFactory.js' import { userSync } from '../../../src/userSync.js'; +import { config } from 'src/config.js'; describe('ApacdexBidAdapter', function () { const adapter = newBidder(spec) @@ -199,11 +200,34 @@ describe('ApacdexBidAdapter', function () { 'bidder': 'apacdex', 'params': { 'siteId': '1a2b3c4d5e6f1a2b3c4d', + 'geo': {'lat': 123.13123456, 'lon': 54.23467311, 'accuracy': 60} }, 'adUnitCode': 'adunit-code-1', 'sizes': [[300, 250], [300, 600]], 'targetKey': 0, 'bidId': '30b31c1838de1f', + 'userIdAsEids': [{ + 'source': 'criteo.com', + 'uids': [{ + 'id': 'p0cCLF9JazY1ZUFjazJRb3NKbEprVTcwZ0IwRUlGalBjOG9laUZNbFJ0ZGpOSnVFbE9VMjBNMzNBTzladGt4cUVGQzBybDY2Y1FqT1dkUkFsMmJIWDRHNjlvNXJjbiUyQlZDd1dOTmt6VlV2TDhRd0F0RTlBcmpyZU5WRHBPU25GQXpyMnlT', + 'atype': 1 + }] + }, { + 'source': 'pubcid.org', + 'uids': [{ + 'id': '2ae366c2-2576-45e5-bd21-72ed10598f17', + 'atype': 1 + }] + }, { + 'source': 'sharedid.org', + 'uids': [{ + 'id': '01EZXQDVAPER4KE1VBS29XKV4Z', + 'atype': 1, + 'ext': { + 'third': '01EZXQDVAPER4KE1VBS29XKV4Z' + } + }] + }], }, { 'bidder': 'apacdex', @@ -300,10 +324,30 @@ describe('ApacdexBidAdapter', function () { const bidRequests = spec.buildRequests(bidRequest, bidderRequests); expect(bidRequests.data.schain).to.deep.equal(bidRequest[0].schain) }); + it('should return a properly formatted request with eids defined', function () { + const bidRequests = spec.buildRequests(bidRequest, bidderRequests); + expect(bidRequests.data.eids).to.deep.equal(bidRequest[0].userIdAsEids) + }); + it('should return a properly formatted request with geo defined', function () { + const bidRequests = spec.buildRequests(bidRequest, bidderRequests); + expect(bidRequests.data.geo).to.deep.equal(bidRequest[0].params.geo) + }); it('should return a properly formatted request with us_privacy included', function () { const bidRequests = spec.buildRequests(bidRequest, bidderRequests); expect(bidRequests.data.us_privacy).to.equal('someCCPAString'); }); + describe('debug test', function() { + beforeEach(function() { + config.setConfig({debug: true}); + }); + afterEach(function() { + config.setConfig({debug: false}); + }); + it('should return a properly formatted request with pbjs_debug is true', function () { + const bidRequests = spec.buildRequests(bidRequest, bidderRequests); + expect(bidRequests.data.test).to.equal(1); + }); + }); }); describe('.interpretResponse', function () { @@ -601,4 +645,66 @@ describe('ApacdexBidAdapter', function () { expect(spec.getUserSyncs({ pixelEnabled: true }, [])).to.have.length(0); }); }); + + describe('validateGeoObject', function () { + it('should return true if the geo object is valid', () => { + let geoObject = { + lat: 123.5624234, + lon: 23.6712341, + accuracy: 20 + }; + expect(validateGeoObject(geoObject)).to.equal(true); + }); + + it('should return false if the geo object is not plain object', () => { + let geoObject = [{ + lat: 123.5624234, + lon: 23.6712341, + accuracy: 20 + }]; + expect(validateGeoObject(geoObject)).to.equal(false); + }); + + it('should return false if the geo object is missing lat attribute', () => { + let geoObject = { + lon: 23.6712341, + accuracy: 20 + }; + expect(validateGeoObject(geoObject)).to.equal(false); + }); + + it('should return false if the geo object is missing lon attribute', () => { + let geoObject = { + lat: 123.5624234, + accuracy: 20 + }; + expect(validateGeoObject(geoObject)).to.equal(false); + }); + + it('should return false if the geo object is missing accuracy attribute', () => { + let geoObject = { + lat: 123.5624234, + lon: 23.6712341 + }; + expect(validateGeoObject(geoObject)).to.equal(false); + }); + }); + + describe('getDomain', function () { + it('should return valid domain from publisherDomain config', () => { + let pageUrl = 'https://www.example.com/page/prebid/exam.html'; + config.setConfig({publisherDomain: pageUrl}); + expect(getDomain(pageUrl)).to.equal('example.com'); + }); + it('should return valid domain from pageUrl argument', () => { + let pageUrl = 'https://www.example.com/page/prebid/exam.html'; + config.setConfig({publisherDomain: ''}); + expect(getDomain(pageUrl)).to.equal('example.com'); + }); + it('should return undefined if pageUrl and publisherDomain not config', () => { + let pageUrl; + config.setConfig({publisherDomain: ''}); + expect(getDomain(pageUrl)).to.equal(pageUrl); + }); + }); }); diff --git a/test/spec/modules/appnexusBidAdapter_spec.js b/test/spec/modules/appnexusBidAdapter_spec.js index 9b12d892440..3a3f4effcb8 100644 --- a/test/spec/modules/appnexusBidAdapter_spec.js +++ b/test/spec/modules/appnexusBidAdapter_spec.js @@ -784,6 +784,18 @@ describe('AppNexusAdapter', function () { config.getConfig.restore(); }); + it('should set the X-Is-Test customHeader if test flag is enabled', function () { + let bidRequest = Object.assign({}, bidRequests[0]); + sinon.stub(config, 'getConfig') + .withArgs('apn_test') + .returns(true); + + const request = spec.buildRequests([bidRequest]); + expect(request.options.customHeaders).to.deep.equal({'X-Is-Test': 1}); + + config.getConfig.restore(); + }); + it('should set withCredentials to false if purpose 1 consent is not given', function () { let consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; let bidderRequest = { @@ -810,11 +822,13 @@ describe('AppNexusAdapter', function () { expect(request.options).to.deep.equal({withCredentials: false}); }); - it('should populate eids when ttd id and criteo is available', function () { + it('should populate eids when supported userIds are available', function () { const bidRequest = Object.assign({}, bidRequests[0], { userId: { tdid: 'sample-userid', - criteoId: 'sample-criteo-userid' + criteoId: 'sample-criteo-userid', + netId: 'sample-netId-userid', + idl_env: 'sample-idl-userid' } }); @@ -830,6 +844,16 @@ describe('AppNexusAdapter', function () { source: 'criteo.com', id: 'sample-criteo-userid', }); + + expect(payload.eids).to.deep.include({ + source: 'netid.de', + id: 'sample-netId-userid', + }); + + expect(payload.eids).to.deep.include({ + source: 'liveramp.com', + id: 'sample-idl-userid' + }) }); it('should populate iab_support object at the root level if omid support is detected', function () { diff --git a/test/spec/modules/astraoneBidAdapter_spec.js b/test/spec/modules/astraoneBidAdapter_spec.js index e422f64b570..0e545081869 100644 --- a/test/spec/modules/astraoneBidAdapter_spec.js +++ b/test/spec/modules/astraoneBidAdapter_spec.js @@ -14,7 +14,7 @@ function getSlotConfigs(mediaTypes, params) { describe('AstraOne Adapter', function() { describe('isBidRequestValid method', function() { - const PLACE_ID = '5af45ad34d506ee7acad0c26'; + const PLACE_ID = '5f477bf94d506ebe2c4240f3'; const IMAGE_URL = 'https://creative.astraone.io/files/default_image-1-600x400.jpg'; describe('returns true', function() { @@ -176,21 +176,23 @@ describe('AstraOne Adapter', function() { describe('the bid is a banner', function() { it('should return a banner bid', function() { const serverResponse = { - body: [ - { - bidId: '2df8c0733f284e', - price: 0.5, - currency: 'USD', - content: { - content: 'html', - actionUrls: {}, - seanceId: '123123' - }, - width: 100, - height: 100, - ttl: 360 - } - ] + body: { + bids: [ + { + bidId: '2df8c0733f284e', + price: 0.5, + currency: 'USD', + content: { + content: 'html', + actionUrls: {}, + seanceId: '123123' + }, + width: 100, + height: 100, + ttl: 360 + } + ] + } } const bids = spec.interpretResponse(serverResponse) expect(bids.length).to.equal(1) diff --git a/test/spec/modules/atsAnalyticsAdapter_spec.js b/test/spec/modules/atsAnalyticsAdapter_spec.js index 84206337fad..59b9105925a 100644 --- a/test/spec/modules/atsAnalyticsAdapter_spec.js +++ b/test/spec/modules/atsAnalyticsAdapter_spec.js @@ -2,27 +2,38 @@ import atsAnalyticsAdapter from '../../../modules/atsAnalyticsAdapter.js'; import { expect } from 'chai'; import adapterManager from 'src/adapterManager.js'; import {server} from '../../mocks/xhr.js'; -import {checkUserBrowser, browserIsChrome, browserIsEdge, browserIsSafari, browserIsFirefox} from '../../../modules/atsAnalyticsAdapter.js'; +import {parseBrowser} from '../../../modules/atsAnalyticsAdapter.js'; +import {getStorageManager} from '../../../src/storageManager.js'; +import {analyticsUrl} from '../../../modules/atsAnalyticsAdapter.js'; + let events = require('src/events'); let constants = require('src/constants.json'); +export const storage = getStorageManager(); + describe('ats analytics adapter', function () { beforeEach(function () { sinon.stub(events, 'getEvents').returns([]); + storage.setCookie('_lr_env_src_ats', 'true', 'Thu, 01 Jan 1970 00:00:01 GMT'); }); afterEach(function () { events.getEvents.restore(); + atsAnalyticsAdapter.getUserAgent.restore(); atsAnalyticsAdapter.disableAnalytics(); }); describe('track', function () { it('builds and sends request and response data', function () { sinon.stub(atsAnalyticsAdapter, 'shouldFireRequest').returns(true); + sinon.stub(atsAnalyticsAdapter, 'getUserAgent').returns('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_5) AppleWebKit/536.25 (KHTML, like Gecko) Version/6.0 Safari/536.25'); + let now = new Date(); + now.setTime(now.getTime() + 3600000); + storage.setCookie('_lr_env_src_ats', 'true', now.toUTCString()); + storage.setCookie('_lr_sampling_rate', '10', now.toUTCString()); let initOptions = { - pid: '10433394', - host: 'https://example.com/dev', + pid: '10433394' }; let auctionTimestamp = 1496510254326; @@ -74,13 +85,15 @@ describe('ats analytics adapter', function () { let expectedAfterBid = { 'Data': [{ 'has_envelope': true, + 'adapter_version': 1, 'bidder': 'appnexus', 'bid_id': '30c77d079cdf17', 'auction_id': 'a5b849e5-87d7-4205-8300-d063084fcfb7', - 'user_browser': checkUserBrowser(), + 'user_browser': parseBrowser(), 'user_platform': navigator.platform, 'auction_start': '2020-02-03T14:14:25.161Z', 'domain': window.location.hostname, + 'envelope_source': true, 'pid': '10433394', 'response_time_stamp': '2020-02-03T14:23:11.978Z', 'currency': 'USD', @@ -132,76 +145,37 @@ describe('ats analytics adapter', function () { events.emit(constants.EVENTS.AUCTION_END, {}); let requests = server.requests.filter(req => { - return req.url.indexOf(initOptions.host) > -1; + return req.url.indexOf(analyticsUrl) > -1; }); + expect(requests.length).to.equal(1); let realAfterBid = JSON.parse(requests[0].requestBody); - // Step 6: assert real data after bid and expected data expect(realAfterBid['Data']).to.deep.equal(expectedAfterBid['Data']); - // check that the host and publisher ID is configured via options - expect(atsAnalyticsAdapter.context.host).to.equal(initOptions.host); + // check that the publisher ID is configured via options expect(atsAnalyticsAdapter.context.pid).to.equal(initOptions.pid); }) - it('check browser is not safari', function () { - window.safari = undefined; - let browser = browserIsSafari(); - expect(browser).to.equal(false); - }) it('check browser is safari', function () { - window.safari = {}; - let browser = browserIsSafari(); + sinon.stub(atsAnalyticsAdapter, 'getUserAgent').returns('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_5) AppleWebKit/536.25 (KHTML, like Gecko) Version/6.0 Safari/536.25'); + let browser = parseBrowser(); expect(browser).to.equal('Safari'); }) - it('check browser is not chrome', function () { - window.chrome = { - app: undefined, - webstore: undefined, - runtime: undefined - }; - let browser = browserIsChrome(); - expect(browser).to.equal(false); - }) it('check browser is chrome', function () { - window.chrome = { - app: {}, - webstore: {}, - runtime: {} - }; - let browser = browserIsChrome(); + sinon.stub(atsAnalyticsAdapter, 'getUserAgent').returns('Mozilla/5.0 (iPhone; CPU iPhone OS 13_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/80.0.3987.95 Mobile/15E148 Safari/604.1'); + let browser = parseBrowser(); expect(browser).to.equal('Chrome'); }) it('check browser is edge', function () { - Object.defineProperty(window, 'StyleMedia', { - value: {}, - writable: true - }); - Object.defineProperty(document, 'documentMode', { - value: undefined, - writable: true - }); - let browser = browserIsEdge(); - expect(browser).to.equal('Edge'); - }) - it('check browser is not edge', function () { - Object.defineProperty(document, 'documentMode', { - value: {}, - writable: true - }); - let browser = browserIsEdge(); - expect(browser).to.equal(false); + sinon.stub(atsAnalyticsAdapter, 'getUserAgent').returns('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.74 Safari/537.36 Edg/79.0.309.43'); + let browser = parseBrowser(); + expect(browser).to.equal('Microsoft Edge'); }) it('check browser is firefox', function () { - global.InstallTrigger = {}; - let browser = browserIsFirefox(); + sinon.stub(atsAnalyticsAdapter, 'getUserAgent').returns('Mozilla/5.0 (iPhone; CPU OS 13_3_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) FxiOS/23.0 Mobile/15E148 Safari/605.1.15'); + let browser = parseBrowser(); expect(browser).to.equal('Firefox'); }) - it('check browser is not firefox', function () { - global.InstallTrigger = undefined; - let browser = browserIsFirefox(); - expect(browser).to.equal(false); - }) }) }) diff --git a/test/spec/modules/automatadBidAdapter_spec.js b/test/spec/modules/automatadBidAdapter_spec.js index fca1a464ff2..9d828bad4c3 100644 --- a/test/spec/modules/automatadBidAdapter_spec.js +++ b/test/spec/modules/automatadBidAdapter_spec.js @@ -111,6 +111,7 @@ describe('automatadBidAdapter', function () { it('should get the correct bid response', function () { let result = spec.interpretResponse(expectedResponse[0]) expect(result).to.be.an('array').that.is.not.empty + expect(result[0].meta.advertiserDomains[0]).to.equal('someAdDomain'); }) it('should interpret multiple bids in seatbid', function () { diff --git a/test/spec/modules/axonixBidAdapter_spec.js b/test/spec/modules/axonixBidAdapter_spec.js new file mode 100644 index 00000000000..aac1cbe08ff --- /dev/null +++ b/test/spec/modules/axonixBidAdapter_spec.js @@ -0,0 +1,374 @@ +import { expect } from 'chai'; +import { spec } from 'modules/axonixBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; +import * as utils from 'src/utils'; + +describe('AxonixBidAdapter', function () { + const adapter = newBidder(spec); + + const SUPPLY_ID_1 = '91fd110a-5685-11eb-8db6-a7e0eeefbbc7'; + const SUPPLY_ID_2 = '22de2092-568b-11eb-bae3-cfa975dc72aa'; + const REGION_1 = 'us-east-1'; + const REGION_2 = 'eu-west-1'; + + const BANNER_REQUEST = { + adUnitCode: 'ad_code', + bidId: 'abcd1234', + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 200] + ] + } + }, + bidder: 'axonix', + params: { + supplyId: SUPPLY_ID_1, + region: REGION_1 + }, + requestId: 'q4owht8ofqi3ulwsd', + transactionId: 'fvpq3oireansdwo' + }; + + const VIDEO_REQUEST = { + adUnitCode: 'ad_code', + bidId: 'abcd1234', + mediaTypes: { + video: { + context: 'outstream', + mimes: ['video/mp4'], + playerSize: [400, 300], + renderer: { + url: 'https://url.com', + backupOnly: true, + render: () => true + }, + } + }, + bidder: 'axonix', + params: { + supplyId: SUPPLY_ID_1, + region: REGION_1 + }, + requestId: 'q4owht8ofqi3ulwsd', + transactionId: 'fvpq3oireansdwo' + }; + + const BIDDER_REQUEST = { + bidderCode: 'axonix', + auctionId: '18fd8b8b0bd757', + bidderRequestId: '418b37f85e772c', + timeout: 3000, + gdprConsent: { + consentString: 'BOKAVy4OKAVy4ABAB8AAAAAZ+A==', + gdprApplies: true + }, + refererInfo: { + referer: 'https://www.prebid.org', + canonicalUrl: 'https://www.prebid.org/the/link/to/the/page' + } + }; + + const BANNER_RESPONSE = { + requestId: 'f08b3a8dcff747eabada295dcf94eee0', + cpm: 6, + currency: 'USD', + width: 300, + height: 250, + ad: '', + creativeId: 'abc', + netRevenue: false, + meta: { + networkId: 'nid', + advertiserDomains: [ + 'https://the.url' + ], + secondaryCatIds: [ + 'IAB1' + ], + mediaType: 'banner' + }, + nurl: 'https://win.url' + }; + + const VIDEO_RESPONSE = { + requestId: 'f08b3a8dcff747eabada295dcf94eee0', + cpm: 6, + currency: 'USD', + width: 300, + height: 250, + ad: '', + creativeId: 'abc', + netRevenue: false, + meta: { + networkId: 'nid', + advertiserDomains: [ + 'https://the.url' + ], + secondaryCatIds: [ + 'IAB1' + ], + mediaType: 'video' + }, + nurl: 'https://win.url' + }; + + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function () { + let validBids = [ + { + bidder: 'axonix', + params: { + supplyId: SUPPLY_ID_1, + region: REGION_1 + }, + }, + { + bidder: 'axonix', + params: { + supplyId: SUPPLY_ID_2, + region: REGION_2 + }, + future_parameter: { + future: 'ididid' + } + }, + ]; + + let invalidBids = [ + { + bidder: 'axonix', + params: {}, + }, + { + bidder: 'axonix', + }, + ]; + + it('should accept valid bids', function () { + for (let bid of validBids) { + expect(spec.isBidRequestValid(bid)).to.equal(true); + } + }); + + it('should reject invalid bids', function () { + for (let bid of invalidBids) { + expect(spec.isBidRequestValid(bid)).to.equal(false); + } + }); + }); + + describe('buildRequests: can handle banner ad requests', function () { + it('creates ServerRequests with the correct data', function () { + const [request] = spec.buildRequests([BANNER_REQUEST], BIDDER_REQUEST); + + expect(request).to.have.property('url', `https://openrtb-${REGION_1}.axonix.com/supply/prebid/${SUPPLY_ID_1}`); + expect(request).to.have.property('method', 'POST'); + expect(request).to.have.property('data'); + + const { data } = request; + expect(data.app).to.be.undefined; + + expect(data).to.have.property('site'); + expect(data.site).to.have.property('page', 'https://www.prebid.org'); + + expect(data).to.have.property('validBidRequest', BANNER_REQUEST); + expect(data).to.have.property('connectionType').to.exist; + expect(data).to.have.property('effectiveType').to.exist; + expect(data).to.have.property('devicetype', 2); + expect(data).to.have.property('bidfloor', 0); + expect(data).to.have.property('dnt', 0); + expect(data).to.have.property('language').to.be.a('string'); + expect(data).to.have.property('prebidVersion').to.be.a('string'); + expect(data).to.have.property('screenHeight').to.be.a('number'); + expect(data).to.have.property('screenWidth').to.be.a('number'); + expect(data).to.have.property('tmax').to.be.a('number'); + expect(data).to.have.property('ua').to.be.a('string'); + }); + + it('creates ServerRequests pointing to the correct region and endpoint if it changes', function () { + const bannerRequests = [utils.deepClone(BANNER_REQUEST), utils.deepClone(BANNER_REQUEST)]; + bannerRequests[0].params.endpoint = 'https://the.url'; + bannerRequests[1].params.endpoint = 'https://the.other.url'; + + const requests = spec.buildRequests(bannerRequests, BIDDER_REQUEST); + + requests.forEach((request, index) => { + expect(request).to.have.property('url', bannerRequests[index].params.endpoint); + }); + }); + + it('creates ServerRequests pointing to default endpoint if missing', function () { + const bannerRequests = [utils.deepClone(BANNER_REQUEST), utils.deepClone(BANNER_REQUEST)]; + bannerRequests[1].params.supplyId = SUPPLY_ID_2; + bannerRequests[1].params.region = REGION_2; + + const requests = spec.buildRequests(bannerRequests, BIDDER_REQUEST); + expect(requests[0]).to.have.property('url', `https://openrtb-${REGION_1}.axonix.com/supply/prebid/${SUPPLY_ID_1}`); + expect(requests[1]).to.have.property('url', `https://openrtb-${REGION_2}.axonix.com/supply/prebid/${SUPPLY_ID_2}`); + }); + + it('creates ServerRequests pointing to default region if missing', function () { + const bannerRequest = utils.deepClone(BANNER_REQUEST); + delete bannerRequest.params.region; + + const requests = spec.buildRequests([bannerRequest], BIDDER_REQUEST); + expect(requests[0]).to.have.property('url', `https://openrtb-${REGION_1}.axonix.com/supply/prebid/${SUPPLY_ID_1}`); + }); + }); + + describe('buildRequests: can handle video ad requests', function () { + it('creates ServerRequests with the correct data', function () { + const [request] = spec.buildRequests([VIDEO_REQUEST], BIDDER_REQUEST); + + expect(request).to.have.property('url', `https://openrtb-${REGION_1}.axonix.com/supply/prebid/${SUPPLY_ID_1}`); + expect(request).to.have.property('method', 'POST'); + expect(request).to.have.property('data'); + + const { data } = request; + expect(data.app).to.be.undefined; + + expect(data).to.have.property('site'); + expect(data.site).to.have.property('page', 'https://www.prebid.org'); + + expect(data).to.have.property('validBidRequest', VIDEO_REQUEST); + expect(data).to.have.property('connectionType').to.exist; + expect(data).to.have.property('effectiveType').to.exist; + expect(data).to.have.property('devicetype', 2); + expect(data).to.have.property('bidfloor', 0); + expect(data).to.have.property('dnt', 0); + expect(data).to.have.property('language').to.be.a('string'); + expect(data).to.have.property('prebidVersion').to.be.a('string'); + expect(data).to.have.property('screenHeight').to.be.a('number'); + expect(data).to.have.property('screenWidth').to.be.a('number'); + expect(data).to.have.property('tmax').to.be.a('number'); + expect(data).to.have.property('ua').to.be.a('string'); + }); + + it('creates ServerRequests pointing to the correct region and endpoint if it changes', function () { + const videoRequests = [utils.deepClone(VIDEO_REQUEST), utils.deepClone(VIDEO_REQUEST)]; + videoRequests[0].params.endpoint = 'https://the.url'; + videoRequests[1].params.endpoint = 'https://the.other.url'; + + const requests = spec.buildRequests(videoRequests, BIDDER_REQUEST); + + requests.forEach((request, index) => { + expect(request).to.have.property('url', videoRequests[index].params.endpoint); + }); + }); + + it('creates ServerRequests pointing to default endpoint if missing', function () { + const videoRequests = [utils.deepClone(VIDEO_REQUEST), utils.deepClone(VIDEO_REQUEST)]; + videoRequests[1].params.supplyId = SUPPLY_ID_2; + videoRequests[1].params.region = REGION_2; + + const requests = spec.buildRequests(videoRequests, BIDDER_REQUEST); + expect(requests[0]).to.have.property('url', `https://openrtb-${REGION_1}.axonix.com/supply/prebid/${SUPPLY_ID_1}`); + expect(requests[1]).to.have.property('url', `https://openrtb-${REGION_2}.axonix.com/supply/prebid/${SUPPLY_ID_2}`); + }); + + it('creates ServerRequests pointing to default region if missing', function () { + const videoRequest = utils.deepClone(VIDEO_REQUEST); + delete videoRequest.params.region; + + const requests = spec.buildRequests([videoRequest], BIDDER_REQUEST); + expect(requests[0]).to.have.property('url', `https://openrtb-${REGION_1}.axonix.com/supply/prebid/${SUPPLY_ID_1}`); + }); + }); + + describe.skip('buildRequests: can handle native ad requests', function () { + it('creates ServerRequests pointing to the correct region and endpoint if it changes', function () { + // loop: + // set supply id + // set region/endpoint in ssp config + // call buildRequests, validate request (url, method, supply id) + expect.fail('Not implemented'); + }); + + it('creates ServerRequests pointing to default endpoint if missing', function () { + // no endpoint in config means default value openrtb.axonix.com + expect.fail('Not implemented'); + }); + + it('creates ServerRequests pointing to default region if missing', function () { + // no region in config means default value us-east-1 + expect.fail('Not implemented'); + }); + }); + + describe('interpretResponse', function () { + it('considers corner cases', function() { + expect(spec.interpretResponse(null)).to.be.an('array').that.is.empty; + expect(spec.interpretResponse()).to.be.an('array').that.is.empty; + }); + + it('ignores unparseable responses', function() { + expect(spec.interpretResponse('invalid')).to.be.an('array').that.is.empty; + expect(spec.interpretResponse(['invalid'])).to.be.an('array').that.is.empty; + expect(spec.interpretResponse([{ invalid: 'object' }])).to.be.an('array').that.is.empty; + }); + + it('parses banner responses', function () { + const response = spec.interpretResponse([BANNER_RESPONSE]); + + expect(response).to.be.an('array').that.is.not.empty; + expect(response[0]).to.equal(BANNER_RESPONSE); + }); + + it('parses 1 video responses', function () { + const response = spec.interpretResponse([VIDEO_RESPONSE]); + + expect(response).to.be.an('array').that.is.not.empty; + expect(response[0]).to.equal(VIDEO_RESPONSE); + }); + + it.skip('parses 1 native responses', function () { + // passing 1 valid native in a response generates an array with 1 correct prebid response + // examine mediaType:native, native element + // check nativeBidIsValid from {@link file://./../../../src/native.js} + expect.fail('Not implemented'); + }); + }); + + describe('onBidWon', function () { + beforeEach(function () { + sinon.stub(utils, 'triggerPixel'); + }); + + afterEach(function () { + utils.triggerPixel.restore(); + }); + + it('called once', function () { + spec.onBidWon(spec.interpretResponse([BANNER_RESPONSE])); + expect(utils.triggerPixel.calledOnce).to.equal(true); + }); + + it('called false', function () { + spec.onBidWon([{ cpm: '2.21' }]); + expect(utils.triggerPixel.called).to.equal(false); + }); + + it('when there is no notification expected server side, none is called', function () { + var response = spec.onBidWon([]); + expect(utils.triggerPixel.called).to.equal(false); + expect(response).to.be.an('undefined') + }); + }); + + describe('onTimeout', function () { + it('banner response', () => { + spec.onTimeout(spec.interpretResponse([BANNER_RESPONSE])); + }); + + it('video response', () => { + spec.onTimeout(spec.interpretResponse([VIDEO_RESPONSE])); + }); + }); +}); diff --git a/test/spec/modules/betweenBidAdapter_spec.js b/test/spec/modules/betweenBidAdapter_spec.js index 94db41ba014..44a8d2cc733 100644 --- a/test/spec/modules/betweenBidAdapter_spec.js +++ b/test/spec/modules/betweenBidAdapter_spec.js @@ -20,7 +20,7 @@ describe('betweenBidAdapterTests', function () { sizes: [[240, 400]] }] let request = spec.buildRequests(bidRequestData); - let req_data = request[0].data; + let req_data = JSON.parse(request.data)[0].data; expect(req_data.bidid).to.equal('bid1234'); }); it('validate itu param', function() { @@ -37,7 +37,7 @@ describe('betweenBidAdapterTests', function () { }]; let request = spec.buildRequests(bidRequestData); - let req_data = request[0].data; + let req_data = JSON.parse(request.data)[0].data; expect(req_data.itu).to.equal('https://something.url'); }); @@ -55,7 +55,7 @@ describe('betweenBidAdapterTests', function () { }]; let request = spec.buildRequests(bidRequestData); - let req_data = request[0].data; + let req_data = JSON.parse(request.data)[0].data; expect(req_data.cur).to.equal('THX'); }); @@ -73,7 +73,7 @@ describe('betweenBidAdapterTests', function () { }]; let request = spec.buildRequests(bidRequestData); - let req_data = request[0].data; + let req_data = JSON.parse(request.data)[0].data; expect(req_data.subid).to.equal(1138); }); @@ -91,7 +91,7 @@ describe('betweenBidAdapterTests', function () { }]; let request = spec.buildRequests(bidRequestData); - let req_data = request[0].data; + let req_data = JSON.parse(request.data)[0].data; expect(req_data.click3rd).to.equal('https://something.url'); }); @@ -111,7 +111,7 @@ describe('betweenBidAdapterTests', function () { }]; let request = spec.buildRequests(bidRequestData); - let req_data = request[0].data; + let req_data = JSON.parse(request.data)[0].data; expect(req_data['pubside_macro[param]']).to.equal('%26test%3Dtset'); }); @@ -134,7 +134,7 @@ describe('betweenBidAdapterTests', function () { } let request = spec.buildRequests(bidRequestData, bidderRequest); - let req_data = request[0].data; + let req_data = JSON.parse(request.data)[0].data; expect(req_data.gdprApplies).to.equal(bidderRequest.gdprConsent.gdprApplies); expect(req_data.consentString).to.equal(bidderRequest.gdprConsent.consentString); @@ -217,8 +217,8 @@ describe('betweenBidAdapterTests', function () { }]; let request = spec.buildRequests(bidRequestData); - let req_data = request[0].data; + let req_data = JSON.parse(request.data)[0].data; - expect(req_data.sizes).to.deep.equal('970x250%2C240x400%2C728x90'); + expect(req_data.sizes).to.deep.equal(['970x250', '240x400', '728x90']) }); }); diff --git a/test/spec/modules/bidViewability_spec.js b/test/spec/modules/bidViewability_spec.js new file mode 100644 index 00000000000..211dec090a5 --- /dev/null +++ b/test/spec/modules/bidViewability_spec.js @@ -0,0 +1,297 @@ +import * as bidViewability from 'modules/bidViewability.js'; +import { config } from 'src/config.js'; +import * as events from 'src/events.js'; +import * as utils from 'src/utils.js'; +import * as sinon from 'sinon'; +import {expect, spy} from 'chai'; +import * as prebidGlobal from 'src/prebidGlobal.js'; +import { EVENTS } from 'src/constants.json'; +import adapterManager, { gdprDataHandler, uspDataHandler } from 'src/adapterManager.js'; +import parse from 'url-parse'; + +const GPT_SLOT = { + getAdUnitPath() { + return '/harshad/Jan/2021/'; + }, + + getSlotElementId() { + return 'DIV-1'; + } +}; + +const PBJS_WINNING_BID = { + 'adUnitCode': '/harshad/Jan/2021/', + 'bidderCode': 'pubmatic', + 'bidder': 'pubmatic', + 'width': 300, + 'height': 250, + 'statusMessage': 'Bid available', + 'adId': 'id', + 'requestId': 1024, + 'source': 'client', + 'no_bid': false, + 'cpm': '1.1495', + 'ttl': 180, + 'creativeId': 'id', + 'netRevenue': true, + 'currency': 'USD', + 'vurls': [ + 'https://domain-1.com/end-point?a=1', + 'https://domain-2.com/end-point/', + 'https://domain-3.com/end-point?a=1' + ] +}; + +describe('#bidViewability', function() { + let gptSlot; + let pbjsWinningBid; + + beforeEach(function() { + gptSlot = Object.assign({}, GPT_SLOT); + pbjsWinningBid = Object.assign({}, PBJS_WINNING_BID); + }); + + describe('isBidAdUnitCodeMatchingSlot', function() { + it('match found by GPT Slot getAdUnitPath', function() { + expect(bidViewability.isBidAdUnitCodeMatchingSlot(pbjsWinningBid, gptSlot)).to.equal(true); + }); + + it('match found by GPT Slot getSlotElementId', function() { + pbjsWinningBid.adUnitCode = 'DIV-1'; + expect(bidViewability.isBidAdUnitCodeMatchingSlot(pbjsWinningBid, gptSlot)).to.equal(true); + }); + + it('match not found', function() { + pbjsWinningBid.adUnitCode = 'DIV-10'; + expect(bidViewability.isBidAdUnitCodeMatchingSlot(pbjsWinningBid, gptSlot)).to.equal(false); + }); + }); + + describe('getMatchingWinningBidForGPTSlot', function() { + let winningBidsArray; + let sandbox + beforeEach(function() { + sandbox = sinon.sandbox.create(); + // mocking winningBidsArray + winningBidsArray = []; + sandbox.stub(prebidGlobal, 'getGlobal').returns({ + getAllWinningBids: function (number) { + return winningBidsArray; + } + }); + }); + + afterEach(function() { + sandbox.restore(); + }) + + it('should find a match by using customMatchFunction provided in config', function() { + // Needs config to be passed with customMatchFunction + let bidViewabilityConfig = { + customMatchFunction(bid, slot) { + return ('AD-' + slot.getAdUnitPath()) === bid.adUnitCode; + } + }; + let newWinningBid = Object.assign({}, PBJS_WINNING_BID, {adUnitCode: 'AD-' + PBJS_WINNING_BID.adUnitCode}); + // Needs pbjs.getWinningBids to be implemented with match + winningBidsArray.push(newWinningBid); + let wb = bidViewability.getMatchingWinningBidForGPTSlot(bidViewabilityConfig, gptSlot); + expect(wb).to.deep.equal(newWinningBid); + }); + + it('should NOT find a match by using customMatchFunction provided in config', function() { + // Needs config to be passed with customMatchFunction + let bidViewabilityConfig = { + customMatchFunction(bid, slot) { + return ('AD-' + slot.getAdUnitPath()) === bid.adUnitCode; + } + }; + // Needs pbjs.getWinningBids to be implemented without match; winningBidsArray is set to empty in beforeEach + let wb = bidViewability.getMatchingWinningBidForGPTSlot(bidViewabilityConfig, gptSlot); + expect(wb).to.equal(null); + }); + + it('should find a match by using default matching function', function() { + // Needs config to be passed without customMatchFunction + // Needs pbjs.getWinningBids to be implemented with match + winningBidsArray.push(PBJS_WINNING_BID); + let wb = bidViewability.getMatchingWinningBidForGPTSlot({}, gptSlot); + expect(wb).to.deep.equal(PBJS_WINNING_BID); + }); + + it('should NOT find a match by using default matching function', function() { + // Needs config to be passed without customMatchFunction + // Needs pbjs.getWinningBids to be implemented without match; winningBidsArray is set to empty in beforeEach + let wb = bidViewability.getMatchingWinningBidForGPTSlot({}, gptSlot); + expect(wb).to.equal(null); + }); + }); + + describe('fireViewabilityPixels', function() { + let sandbox; + let triggerPixelSpy; + + beforeEach(function() { + sandbox = sinon.sandbox.create(); + triggerPixelSpy = sandbox.spy(utils, ['triggerPixel']); + }); + + afterEach(function() { + sandbox.restore(); + }); + + it('DO NOT fire pixels if NOT mentioned in module config', function() { + let moduleConfig = {}; + bidViewability.fireViewabilityPixels(moduleConfig, PBJS_WINNING_BID); + expect(triggerPixelSpy.callCount).to.equal(0); + }); + + it('fire pixels if mentioned in module config', function() { + let moduleConfig = {firePixels: true}; + bidViewability.fireViewabilityPixels(moduleConfig, PBJS_WINNING_BID); + PBJS_WINNING_BID.vurls.forEach((url, i) => { + let call = triggerPixelSpy.getCall(i); + expect(call.args[0]).to.equal(url); + }); + }); + + it('USP: should include the us_privacy key when USP Consent is available', function () { + let uspDataHandlerStub = sinon.stub(uspDataHandler, 'getConsentData'); + uspDataHandlerStub.returns('1YYY'); + let moduleConfig = {firePixels: true}; + bidViewability.fireViewabilityPixels(moduleConfig, PBJS_WINNING_BID); + PBJS_WINNING_BID.vurls.forEach((url, i) => { + let call = triggerPixelSpy.getCall(i); + expect(call.args[0].indexOf(url)).to.equal(0); + const testurl = parse(call.args[0]); + const queryObject = utils.parseQS(testurl.query); + expect(queryObject.us_privacy).to.equal('1YYY'); + }); + uspDataHandlerStub.restore(); + }); + + it('USP: should not include the us_privacy key when USP Consent is not available', function () { + let moduleConfig = {firePixels: true}; + bidViewability.fireViewabilityPixels(moduleConfig, PBJS_WINNING_BID); + PBJS_WINNING_BID.vurls.forEach((url, i) => { + let call = triggerPixelSpy.getCall(i); + expect(call.args[0].indexOf(url)).to.equal(0); + const testurl = parse(call.args[0]); + const queryObject = utils.parseQS(testurl.query); + expect(queryObject.us_privacy).to.equal(undefined); + }); + }); + + it('GDPR: should include the GDPR keys when GDPR Consent is available', function() { + let gdprDataHandlerStub = sinon.stub(gdprDataHandler, 'getConsentData'); + gdprDataHandlerStub.returns({ + gdprApplies: true, + consentString: 'consent', + addtlConsent: 'moreConsent' + }); + let moduleConfig = {firePixels: true}; + bidViewability.fireViewabilityPixels(moduleConfig, PBJS_WINNING_BID); + PBJS_WINNING_BID.vurls.forEach((url, i) => { + let call = triggerPixelSpy.getCall(i); + expect(call.args[0].indexOf(url)).to.equal(0); + const testurl = parse(call.args[0]); + const queryObject = utils.parseQS(testurl.query); + expect(queryObject.gdpr).to.equal('1'); + expect(queryObject.gdpr_consent).to.equal('consent'); + expect(queryObject.addtl_consent).to.equal('moreConsent'); + }); + gdprDataHandlerStub.restore(); + }); + + it('GDPR: should not include the GDPR keys when GDPR Consent is not available', function () { + let moduleConfig = {firePixels: true}; + bidViewability.fireViewabilityPixels(moduleConfig, PBJS_WINNING_BID); + PBJS_WINNING_BID.vurls.forEach((url, i) => { + let call = triggerPixelSpy.getCall(i); + expect(call.args[0].indexOf(url)).to.equal(0); + const testurl = parse(call.args[0]); + const queryObject = utils.parseQS(testurl.query); + expect(queryObject.gdpr).to.equal(undefined); + expect(queryObject.gdpr_consent).to.equal(undefined); + expect(queryObject.addtl_consent).to.equal(undefined); + }); + }); + + it('GDPR: should only include the GDPR keys for GDPR Consent fields with values', function () { + let gdprDataHandlerStub = sinon.stub(gdprDataHandler, 'getConsentData'); + gdprDataHandlerStub.returns({ + gdprApplies: true, + consentString: 'consent' + }); + let moduleConfig = {firePixels: true}; + bidViewability.fireViewabilityPixels(moduleConfig, PBJS_WINNING_BID); + PBJS_WINNING_BID.vurls.forEach((url, i) => { + let call = triggerPixelSpy.getCall(i); + expect(call.args[0].indexOf(url)).to.equal(0); + const testurl = parse(call.args[0]); + const queryObject = utils.parseQS(testurl.query); + expect(queryObject.gdpr).to.equal('1'); + expect(queryObject.gdpr_consent).to.equal('consent'); + expect(queryObject.addtl_consent).to.equal(undefined); + }); + gdprDataHandlerStub.restore(); + }) + }); + + describe('impressionViewableHandler', function() { + let sandbox; + let triggerPixelSpy; + let eventsEmitSpy; + let logWinningBidNotFoundSpy; + let callBidViewableBidderSpy; + let winningBidsArray; + + beforeEach(function() { + sandbox = sinon.sandbox.create(); + triggerPixelSpy = sandbox.spy(utils, ['triggerPixel']); + eventsEmitSpy = sandbox.spy(events, ['emit']); + callBidViewableBidderSpy = sandbox.spy(adapterManager, ['callBidViewableBidder']); + // mocking winningBidsArray + winningBidsArray = []; + sandbox.stub(prebidGlobal, 'getGlobal').returns({ + getAllWinningBids: function (number) { + return winningBidsArray; + } + }); + }); + + afterEach(function() { + sandbox.restore(); + }) + + it('matching winning bid is found', function() { + let moduleConfig = { + firePixels: true + }; + winningBidsArray.push(PBJS_WINNING_BID); + bidViewability.impressionViewableHandler(moduleConfig, GPT_SLOT, null); + // fire pixels should be called + PBJS_WINNING_BID.vurls.forEach((url, i) => { + let call = triggerPixelSpy.getCall(i); + expect(call.args[0]).to.equal(url); + }); + // adapterManager.callBidViewableBidder is called with required args + let call = callBidViewableBidderSpy.getCall(0); + expect(call.args[0]).to.equal(PBJS_WINNING_BID.bidder); + expect(call.args[1]).to.deep.equal(PBJS_WINNING_BID); + // EVENTS.BID_VIEWABLE is triggered + call = eventsEmitSpy.getCall(0); + expect(call.args[0]).to.equal(EVENTS.BID_VIEWABLE); + expect(call.args[1]).to.deep.equal(PBJS_WINNING_BID); + }); + + it('matching winning bid is NOT found', function() { + // fire pixels should NOT be called + expect(triggerPixelSpy.callCount).to.equal(0); + // adapterManager.callBidViewableBidder is NOT called + expect(callBidViewableBidderSpy.callCount).to.equal(0); + // EVENTS.BID_VIEWABLE is NOT triggered + expect(eventsEmitSpy.callCount).to.equal(0); + }); + }); +}); diff --git a/test/spec/modules/bizzclickBidAdapter_spec.js b/test/spec/modules/bizzclickBidAdapter_spec.js index 39ad4ae39c9..e0698c9eda8 100644 --- a/test/spec/modules/bizzclickBidAdapter_spec.js +++ b/test/spec/modules/bizzclickBidAdapter_spec.js @@ -1,5 +1,6 @@ import { expect } from 'chai'; import { spec } from 'modules/bizzclickBidAdapter.js'; +import {config} from 'src/config.js'; const NATIVE_BID_REQUEST = { code: 'native_example', @@ -53,7 +54,11 @@ const BANNER_BID_REQUEST = { accountId: 'accountId' }, timeout: 1000, - + gdprConsent: { + consentString: 'BOEFEAyOEFEAyAHABDENAI4AAAB9vABAASA', + gdprApplies: 1, + }, + uspConsent: 'uspConsent' } const bidRequest = { @@ -172,6 +177,22 @@ const NATIVE_BID_RESPONSE = { }; describe('BizzclickAdapter', function() { + describe('with COPPA', function() { + beforeEach(function() { + sinon.stub(config, 'getConfig') + .withArgs('coppa') + .returns(true); + }); + afterEach(function() { + config.getConfig.restore(); + }); + + it('should send the Coppa "required" flag set to "1" in the request', function () { + let serverRequest = spec.buildRequests([BANNER_BID_REQUEST]); + expect(serverRequest.data[0].regs.coppa).to.equal(1); + }); + }); + describe('isBidRequestValid', function() { it('should return true when required params found', function () { expect(spec.isBidRequestValid(NATIVE_BID_REQUEST)).to.equal(true); @@ -225,6 +246,12 @@ describe('BizzclickAdapter', function() { expect(request.method).to.equal('POST'); }); + it('check consent and ccpa string is set properly', function() { + expect(request.data[0].regs.ext.gdpr).to.equal(1); + expect(request.data[0].user.ext.consent).to.equal(BANNER_BID_REQUEST.gdprConsent.consentString); + expect(request.data[0].regs.ext.us_privacy).to.equal(BANNER_BID_REQUEST.uspConsent); + }) + it('Returns valid URL', function () { expect(request.url).to.equal('https://us-e-node1.bizzclick.com/bid?rtb_seat_id=prebidjs&secret_key=accountId'); }); diff --git a/test/spec/modules/colossussspBidAdapter_spec.js b/test/spec/modules/colossussspBidAdapter_spec.js index a10a7590677..f6e24d07c63 100644 --- a/test/spec/modules/colossussspBidAdapter_spec.js +++ b/test/spec/modules/colossussspBidAdapter_spec.js @@ -88,12 +88,13 @@ describe('ColossussspAdapter', function () { let placements = data['placements']; for (let i = 0; i < placements.length; i++) { let placement = placements[i]; - expect(placement).to.have.all.keys('placementId', 'eids', 'bidId', 'traffic', 'sizes', 'schain'); + expect(placement).to.have.all.keys('placementId', 'eids', 'bidId', 'traffic', 'sizes', 'schain', 'floor'); expect(placement.schain).to.be.an('object') expect(placement.placementId).to.be.a('number'); expect(placement.bidId).to.be.a('string'); expect(placement.traffic).to.be.a('string'); expect(placement.sizes).to.be.an('array'); + expect(placement.floor).to.be.an('object'); } }); it('Returns empty data if no valid requests are passed', function () { @@ -126,7 +127,6 @@ describe('ColossussspAdapter', function () { expect(v.uids).to.be.an('array'); expect(v.uids.length).to.be.equal(1) expect(v.uids[0]).to.have.property('id') - expect(v.uids[0].id).to.be.oneOf(['britepoolid123', 'idl_env123', 'tdid123', 'id5id123']) } } }); diff --git a/test/spec/modules/conversantBidAdapter_spec.js b/test/spec/modules/conversantBidAdapter_spec.js index d802cd288ef..1c24fb7694a 100644 --- a/test/spec/modules/conversantBidAdapter_spec.js +++ b/test/spec/modules/conversantBidAdapter_spec.js @@ -339,6 +339,7 @@ describe('Conversant adapter tests', function() { expect(bid).to.have.property('creativeId', '1000'); expect(bid).to.have.property('width', 300); expect(bid).to.have.property('height', 250); + expect(bid.meta.advertiserDomains).to.deep.equal(['https://example.com']); expect(bid).to.have.property('ad', 'markup000'); expect(bid).to.have.property('ttl', 300); expect(bid).to.have.property('netRevenue', true); @@ -483,7 +484,7 @@ describe('Conversant adapter tests', function() { const payload = spec.buildRequests(requests).data; expect(payload).to.have.deep.nested.property('user.ext.eids', [ {source: 'adserver.org', uids: [{id: '223344', atype: 1, ext: {rtiPartner: 'TDID'}}]}, - {source: 'liveramp.com', uids: [{id: '334455', atype: 1}]} + {source: 'liveramp.com', uids: [{id: '334455', atype: 3}]} ]); }); }); diff --git a/test/spec/modules/criteoBidAdapter_spec.js b/test/spec/modules/criteoBidAdapter_spec.js index c5068d11d31..cad1e3f8114 100755 --- a/test/spec/modules/criteoBidAdapter_spec.js +++ b/test/spec/modules/criteoBidAdapter_spec.js @@ -820,14 +820,18 @@ describe('The Criteo bidding adapter', function () { it('should properly build a request with first party data', function () { const contextData = { keywords: ['power tools'], - data: { - pageType: 'article' + ext: { + data: { + pageType: 'article' + } } }; const userData = { gender: 'M', - data: { - registered: true + ext: { + data: { + registered: true + } } }; const bidRequests = [ @@ -842,8 +846,8 @@ describe('The Criteo bidding adapter', function () { bidfloor: 0.75 } }, - fpd: { - context: { + ortb2Imp: { + ext: { data: { someContextAttribute: 'abc' } @@ -854,8 +858,8 @@ describe('The Criteo bidding adapter', function () { sandbox.stub(config, 'getConfig').callsFake(key => { const config = { - fpd: { - context: contextData, + ortb2: { + site: contextData, user: userData } }; @@ -863,8 +867,8 @@ describe('The Criteo bidding adapter', function () { }); const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.publisher.ext).to.deep.equal(contextData); - expect(request.data.user.ext).to.deep.equal(userData); + expect(request.data.publisher.ext).to.deep.equal({keywords: ['power tools'], data: {pageType: 'article'}}); + expect(request.data.user.ext).to.deep.equal({gender: 'M', data: {registered: true}}); expect(request.data.slots[0].ext).to.deep.equal({ bidfloor: 0.75, data: { @@ -992,6 +996,7 @@ describe('The Criteo bidding adapter', function () { zoneid: 123, native: { 'products': [{ + 'sendTargetingKeys': false, 'title': 'Product title', 'description': 'Product desc', 'price': '100', @@ -1027,7 +1032,6 @@ describe('The Criteo bidding adapter', function () { native: true, }] }; - config.setConfig({'enableSendAllBids': false}); const bids = spec.interpretResponse(response, request); expect(bids).to.have.lengthOf(1); expect(bids[0].requestId).to.equal('test-bidId'); @@ -1036,61 +1040,87 @@ describe('The Criteo bidding adapter', function () { expect(bids[0].mediaType).to.equal(NATIVE); }); - it('should not parse bid response with native when enableSendAllBids is true', function () { - const response = { - body: { - slots: [{ - impid: 'test-requestId', - bidId: 'abc123', - cpm: 1.23, - width: 728, - height: 90, - zoneid: 123, - native: {} - }], - }, - }; - const request = { - bidRequests: [{ - adUnitCode: 'test-requestId', - bidId: 'test-bidId', + it('should warn only once if sendTargetingKeys set to true on required fields for native bidRequest', () => { + const bidderRequest = { }; + const bidRequests = [ + { + bidder: 'criteo', + adUnitCode: 'bid-123', + transactionId: 'transaction-123', + sizes: [[728, 90]], params: { zoneId: 123, + publisherSubId: '123', + nativeCallback: function() {} }, - native: true, - }] - }; - config.setConfig({'enableSendAllBids': true}); - const bids = spec.interpretResponse(response, request); - expect(bids).to.have.lengthOf(0); - }); - - it('should not parse bid response with native when enableSendAllBids is not set', function () { - const response = { - body: { - slots: [{ - impid: 'test-requestId', - bidId: 'abc123', - cpm: 1.23, - width: 728, - height: 90, - zoneid: 123, - native: {} - }], }, - }; - const request = { - bidRequests: [{ - adUnitCode: 'test-requestId', - bidId: 'test-bidId', + { + bidder: 'criteo', + adUnitCode: 'bid-456', + transactionId: 'transaction-456', + sizes: [[728, 90]], params: { - zoneId: 123, + zoneId: 456, + publisherSubId: '456', + nativeCallback: function() {} }, - native: true, - }] - }; - const bids = spec.interpretResponse(response, request); - expect(bids).to.have.lengthOf(0); + }, + ]; + + const nativeParamsWithSendTargetingKeys = [ + { + nativeParams: { + image: { + sendTargetingKeys: true + }, + } + }, + { + nativeParams: { + icon: { + sendTargetingKeys: true + }, + } + }, + { + nativeParams: { + clickUrl: { + sendTargetingKeys: true + }, + } + }, + { + nativeParams: { + displayUrl: { + sendTargetingKeys: true + }, + } + }, + { + nativeParams: { + privacyLink: { + sendTargetingKeys: true + }, + } + }, + { + nativeParams: { + privacyIcon: { + sendTargetingKeys: true + }, + } + } + ]; + + utilsMock.expects('logWarn') + .withArgs('Criteo: all native assets containing URL should be sent as placeholders with sendId(icon, image, clickUrl, displayUrl, privacyLink, privacyIcon)') + .exactly(nativeParamsWithSendTargetingKeys.length * bidRequests.length); + nativeParamsWithSendTargetingKeys.forEach(nativeParams => { + let transformedBidRequests = {...bidRequests}; + transformedBidRequests = [Object.assign(transformedBidRequests[0], nativeParams), Object.assign(transformedBidRequests[1], nativeParams)]; + spec.buildRequests(transformedBidRequests, bidderRequest); + }); + utilsMock.verify(); }); it('should properly parse a bid response with a zoneId passed as a string', function () { diff --git a/test/spec/modules/dfpAdServerVideo_spec.js b/test/spec/modules/dfpAdServerVideo_spec.js index ed9c968cfa2..eaffca01e06 100644 --- a/test/spec/modules/dfpAdServerVideo_spec.js +++ b/test/spec/modules/dfpAdServerVideo_spec.js @@ -7,7 +7,7 @@ import * as utils from 'src/utils.js'; import { config } from 'src/config.js'; import { targeting } from 'src/targeting.js'; import { auctionManager } from 'src/auctionManager.js'; -import { uspDataHandler } from 'src/adapterManager.js'; +import { gdprDataHandler, uspDataHandler } from 'src/adapterManager.js'; import * as adpod from 'modules/adpod.js'; import { server } from 'test/mocks/xhr.js'; @@ -154,6 +154,78 @@ describe('The DFP video support module', function () { expect(queryObject.us_privacy).to.equal(undefined); }); + it('should include the GDPR keys when GDPR Consent is available', function () { + let gdprDataHandlerStub = sinon.stub(gdprDataHandler, 'getConsentData'); + gdprDataHandlerStub.returns({ + gdprApplies: true, + consentString: 'consent', + addtlConsent: 'moreConsent' + }); + + const bidCopy = utils.deepClone(bid); + bidCopy.adserverTargeting = Object.assign(bidCopy.adserverTargeting, { + hb_adid: 'ad_id', + }); + + const url = parse(buildDfpVideoUrl({ + adUnit: adUnit, + bid: bidCopy, + params: { + 'iu': 'my/adUnit' + } + })); + const queryObject = utils.parseQS(url.query); + expect(queryObject.gdpr).to.equal('1'); + expect(queryObject.gdpr_consent).to.equal('consent'); + expect(queryObject.addtl_consent).to.equal('moreConsent'); + gdprDataHandlerStub.restore(); + }); + + it('should not include the GDPR keys when GDPR Consent is not available', function () { + const bidCopy = utils.deepClone(bid); + bidCopy.adserverTargeting = Object.assign(bidCopy.adserverTargeting, { + hb_adid: 'ad_id', + }); + + const url = parse(buildDfpVideoUrl({ + adUnit: adUnit, + bid: bidCopy, + params: { + 'iu': 'my/adUnit' + } + })); + const queryObject = utils.parseQS(url.query); + expect(queryObject.gdpr).to.equal(undefined); + expect(queryObject.gdpr_consent).to.equal(undefined); + expect(queryObject.addtl_consent).to.equal(undefined); + }); + + it('should only include the GDPR keys for GDPR Consent fields with values', function () { + let gdprDataHandlerStub = sinon.stub(gdprDataHandler, 'getConsentData'); + gdprDataHandlerStub.returns({ + gdprApplies: true, + consentString: 'consent', + }); + + const bidCopy = utils.deepClone(bid); + bidCopy.adserverTargeting = Object.assign(bidCopy.adserverTargeting, { + hb_adid: 'ad_id', + }); + + const url = parse(buildDfpVideoUrl({ + adUnit: adUnit, + bid: bidCopy, + params: { + 'iu': 'my/adUnit' + } + })); + const queryObject = utils.parseQS(url.query); + expect(queryObject.gdpr).to.equal('1'); + expect(queryObject.gdpr_consent).to.equal('consent'); + expect(queryObject.addtl_consent).to.equal(undefined); + gdprDataHandlerStub.restore(); + }); + describe('special targeting unit test', function () { const allTargetingData = { 'hb_format': 'video', @@ -391,6 +463,12 @@ describe('The DFP video support module', function () { amStub.returns(getBidsReceived()); let uspDataHandlerStub = sinon.stub(uspDataHandler, 'getConsentData'); uspDataHandlerStub.returns('1YYY'); + let gdprDataHandlerStub = sinon.stub(gdprDataHandler, 'getConsentData'); + gdprDataHandlerStub.returns({ + gdprApplies: true, + consentString: 'consent', + addtlConsent: 'moreConsent' + }); let url; parse(buildAdpodVideoUrl({ code: 'adUnitCode-1', @@ -422,11 +500,15 @@ describe('The DFP video support module', function () { expect(queryParams).to.have.property('url'); expect(queryParams).to.have.property('cust_params'); expect(queryParams).to.have.property('us_privacy', '1YYY'); + expect(queryParams).to.have.property('gdpr', '1'); + expect(queryParams).to.have.property('gdpr_consent', 'consent'); + expect(queryParams).to.have.property('addtl_consent', 'moreConsent'); const custParams = utils.parseQS(decodeURIComponent(queryParams.cust_params)); expect(custParams).to.have.property('hb_cache_id', '123'); expect(custParams).to.have.property('hb_pb_cat_dur', '15.00_395_15s,15.00_406_30s,10.00_395_15s'); uspDataHandlerStub.restore(); + gdprDataHandlerStub.restore(); } }); diff --git a/test/spec/modules/districtmDmxBidAdapter_spec.js b/test/spec/modules/districtmDmxBidAdapter_spec.js index 5d1f299dad5..90c18c6a84f 100644 --- a/test/spec/modules/districtmDmxBidAdapter_spec.js +++ b/test/spec/modules/districtmDmxBidAdapter_spec.js @@ -687,17 +687,16 @@ describe('DistrictM Adaptor', function () { describe(`isBidRequestValid test response`, function () { let params = { - dmxid: 10001, + dmxid: 10001, // optional memberid: 10003, }; it(`function should return true`, function () { expect(districtm.isBidRequestValid({ params })).to.be.equal(true); }); it(`function should return false`, function () { - expect(districtm.isBidRequestValid({ params: { memberid: 12345 } })).to.be.equal(false); + expect(districtm.isBidRequestValid({ params: {} })).to.be.equal(false); }); - it(`expect to have two property available dmxid and memberid`, function () { - expect(params).to.have.property('dmxid'); + it(`expect to have memberid`, function () { expect(params).to.have.property('memberid'); }); }); diff --git a/test/spec/modules/docereeBidAdapter_spec.js b/test/spec/modules/docereeBidAdapter_spec.js new file mode 100644 index 00000000000..efff2efa319 --- /dev/null +++ b/test/spec/modules/docereeBidAdapter_spec.js @@ -0,0 +1,97 @@ +import {expect} from 'chai'; +import {spec} from '../../../modules/docereeBidAdapter.js'; +import { config } from '../../../src/config.js'; + +describe('BidlabBidAdapter', function () { + config.setConfig({ + doceree: { + context: { + data: { + token: 'testing-token', // required + } + }, + user: { + data: { + gender: '', + email: '', + hashedEmail: '', + firstName: '', + lastName: '', + npi: '', + hashedNPI: '', + city: '', + zipCode: '', + specialization: '', + } + } + } + }); + let bid = { + bidId: 'testing', + bidder: 'doceree', + params: { + placementId: 'DOC_7jm9j5eqkl0xvc5w', + } + }; + + describe('isBidRequestValid', function () { + it('Should return true if placementId is present', function () { + expect(spec.isBidRequestValid(bid)).to.be.true; + }); + it('Should return false if placementId is not present', function () { + delete bid.params.placementId; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + }); + + describe('buildRequests', function () { + let serverRequest = spec.buildRequests([bid]); + serverRequest = serverRequest[0] + it('Creates a ServerRequest object with method, URL and data', function () { + expect(serverRequest).to.exist; + expect(serverRequest.method).to.exist; + expect(serverRequest.url).to.exist; + }); + it('Returns GET method', function () { + expect(serverRequest.method).to.equal('GET'); + }); + it('Returns valid URL', function () { + expect(serverRequest.url).to.equal('https://bidder.doceree.com/v1/adrequest?id=DOC_7jm9j5eqkl0xvc5w&pubRequestedURL=undefined&loggedInUser=JTdCJTIyZ2VuZGVyJTIyJTNBJTIyJTIyJTJDJTIyZW1haWwlMjIlM0ElMjIlMjIlMkMlMjJoYXNoZWRFbWFpbCUyMiUzQSUyMiUyMiUyQyUyMmZpcnN0TmFtZSUyMiUzQSUyMiUyMiUyQyUyMmxhc3ROYW1lJTIyJTNBJTIyJTIyJTJDJTIybnBpJTIyJTNBJTIyJTIyJTJDJTIyaGFzaGVkTlBJJTIyJTNBJTIyJTIyJTJDJTIyY2l0eSUyMiUzQSUyMiUyMiUyQyUyMnppcENvZGUlMjIlM0ElMjIlMjIlMkMlMjJzcGVjaWFsaXphdGlvbiUyMiUzQSUyMiUyMiU3RA%3D%3D&prebidjs=true&requestId=testing&'); + }); + }); + describe('interpretResponse', function () { + it('Should interpret banner response', function () { + const banner = { + body: { + DIVID: 'DOC_7jm9j5eqkl0xvc5w', + creativeType: 'banner', + guid: 'G125fzC5NKl3FHeOT8yvL98ILfQS9TVUgk6Q', + currency: 'USD', + cpmBid: 2, + height: '250', + width: '300', + ctaLink: 'https://doceree.com/', + sourceURL: '', + sourceHTML: '
test
', + advertiserDomain: 'doceree.com', + } + }; + let bannerResponses = spec.interpretResponse(banner); + expect(bannerResponses).to.be.an('array').that.is.not.empty; + let dataItem = bannerResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', + 'netRevenue', 'currency', 'mediaType', 'creativeId', 'meta'); + expect(dataItem.requestId).to.equal('G125fzC5NKl3FHeOT8yvL98ILfQS9TVUgk6Q'); + expect(dataItem.cpm).to.equal(2); + expect(dataItem.width).to.equal(300); + expect(dataItem.height).to.equal(250); + expect(dataItem.ad).to.equal('
test
'); + expect(dataItem.ttl).to.equal(30); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + expect(dataItem.creativeId).to.equal('DOC_7jm9j5eqkl0xvc5w'); + expect(dataItem.meta.advertiserDomains).to.be.an('array').that.is.not.empty; + expect(dataItem.meta.advertiserDomains[0]).to.equal('doceree.com') + }); + }) +}); diff --git a/test/spec/modules/dspxBidAdapter_spec.js b/test/spec/modules/dspxBidAdapter_spec.js index cf36c3f62c4..87752f7747a 100644 --- a/test/spec/modules/dspxBidAdapter_spec.js +++ b/test/spec/modules/dspxBidAdapter_spec.js @@ -283,4 +283,60 @@ describe('dspxAdapter', function () { expect(result.length).to.equal(0); }); }); + + describe(`getUserSyncs test usage`, function () { + let serverResponses; + + beforeEach(function () { + serverResponses = [{ + body: { + requestId: '23beaa6af6cdde', + cpm: 0.5, + width: 0, + height: 0, + creativeId: 100500, + dealId: '', + currency: 'EUR', + netRevenue: true, + ttl: 300, + type: 'sspHTML', + ad: '', + userSync: { + iframeUrl: ['anyIframeUrl?a=1'], + imageUrl: ['anyImageUrl', 'anyImageUrl2'] + } + } + }]; + }); + + it(`return value should be an array`, function () { + expect(spec.getUserSyncs({ iframeEnabled: true })).to.be.an('array'); + }); + it(`array should have only one object and it should have a property type = 'iframe'`, function () { + expect(spec.getUserSyncs({ iframeEnabled: true }, serverResponses).length).to.be.equal(1); + let [userSync] = spec.getUserSyncs({ iframeEnabled: true }, serverResponses); + expect(userSync).to.have.property('type'); + expect(userSync.type).to.be.equal('iframe'); + }); + it(`we have valid sync url for iframe`, function () { + let [userSync] = spec.getUserSyncs({ iframeEnabled: true }, serverResponses, {consentString: 'anyString'}); + expect(userSync.url).to.be.equal('anyIframeUrl?a=1&gdpr_consent=anyString') + expect(userSync.type).to.be.equal('iframe'); + }); + it(`we have valid sync url for image`, function () { + let [userSync] = spec.getUserSyncs({ pixelEnabled: true }, serverResponses, {gdprApplies: true, consentString: 'anyString'}); + expect(userSync.url).to.be.equal('anyImageUrl?gdpr=1&gdpr_consent=anyString') + expect(userSync.type).to.be.equal('image'); + }); + it(`we have valid sync url for image and iframe`, function () { + let userSync = spec.getUserSyncs({ iframeEnabled: true, pixelEnabled: true }, serverResponses, {gdprApplies: true, consentString: 'anyString'}); + expect(userSync.length).to.be.equal(3); + expect(userSync[0].url).to.be.equal('anyIframeUrl?a=1&gdpr=1&gdpr_consent=anyString') + expect(userSync[0].type).to.be.equal('iframe'); + expect(userSync[1].url).to.be.equal('anyImageUrl?gdpr=1&gdpr_consent=anyString') + expect(userSync[1].type).to.be.equal('image'); + expect(userSync[2].url).to.be.equal('anyImageUrl2?gdpr=1&gdpr_consent=anyString') + expect(userSync[2].type).to.be.equal('image'); + }); + }); }); diff --git a/test/spec/modules/eids_spec.js b/test/spec/modules/eids_spec.js index 26dbad2f153..86a6dff2205 100644 --- a/test/spec/modules/eids_spec.js +++ b/test/spec/modules/eids_spec.js @@ -57,10 +57,13 @@ describe('eids array generation for known sub-modules', function() { expect(newEids.length).to.equal(1); expect(newEids[0]).to.deep.equal({ source: 'id5-sync.com', - uids: [{ id: 'some-random-id-value', atype: 1 }], - ext: { - linkType: 0 - } + uids: [{ + id: 'some-random-id-value', + atype: 1, + ext: { + linkType: 0 + } + }] }); }); }); @@ -81,13 +84,18 @@ describe('eids array generation for known sub-modules', function() { it('merkleId', function() { const userId = { - merkleId: 'some-random-id-value' + merkleId: { + id: 'some-random-id-value', keyID: 1 + } }; const newEids = createEidsArray(userId); expect(newEids.length).to.equal(1); expect(newEids[0]).to.deep.equal({ source: 'merkleinc.com', - uids: [{id: 'some-random-id-value', atype: 1}] + uids: [{id: 'some-random-id-value', + atype: 3, + ext: { keyID: 1 + }}] }); }); @@ -99,7 +107,7 @@ describe('eids array generation for known sub-modules', function() { expect(newEids.length).to.equal(1); expect(newEids[0]).to.deep.equal({ source: 'liveramp.com', - uids: [{id: 'some-random-id-value', atype: 1}] + uids: [{id: 'some-random-id-value', atype: 3}] }); }); @@ -114,7 +122,7 @@ describe('eids array generation for known sub-modules', function() { expect(newEids.length).to.equal(1); expect(newEids[0]).to.deep.equal({ source: 'liveintent.com', - uids: [{id: 'some-random-id-value', atype: 1}], + uids: [{id: 'some-random-id-value', atype: 3}], ext: {segments: ['s1', 's2']} }); }); @@ -129,7 +137,7 @@ describe('eids array generation for known sub-modules', function() { expect(newEids.length).to.equal(1); expect(newEids[0]).to.deep.equal({ source: 'liveintent.com', - uids: [{id: 'some-random-id-value', atype: 1}] + uids: [{id: 'some-random-id-value', atype: 3}] }); }); @@ -141,7 +149,7 @@ describe('eids array generation for known sub-modules', function() { expect(newEids.length).to.equal(1); expect(newEids[0]).to.deep.equal({ source: 'britepool.com', - uids: [{id: 'some-random-id-value', atype: 1}] + uids: [{id: 'some-random-id-value', atype: 3}] }); }); @@ -169,6 +177,18 @@ describe('eids array generation for known sub-modules', function() { }); }); + it('tapadId', function() { + const userId = { + tapadId: 'some-random-id-value' + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'tapad.com', + uids: [{id: 'some-random-id-value', atype: 1}] + }); + }); + it('NetId', function() { const userId = { netId: 'some-random-id-value' @@ -180,6 +200,18 @@ describe('eids array generation for known sub-modules', function() { uids: [{id: 'some-random-id-value', atype: 1}] }); }); + + it('NextRollId', function() { + const userId = { + nextrollId: 'some-random-id-value' + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'nextroll.com', + uids: [{id: 'some-random-id-value', atype: 1}] + }); + }); it('Sharedid', function() { const userId = { sharedid: { @@ -261,6 +293,20 @@ describe('eids array generation for known sub-modules', function() { }] }); }); + it('uid2', function() { + const userId = { + uid2: {'id': 'Sample_AD_Token'} + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'uidapi.com', + uids: [{ + id: 'Sample_AD_Token', + atype: 3 + }] + }); + }); it('pubProvidedId', function() { const userId = { pubProvidedId: [{ diff --git a/test/spec/modules/emx_digitalBidAdapter_spec.js b/test/spec/modules/emx_digitalBidAdapter_spec.js index 39e56638ece..855ffa0a0b7 100644 --- a/test/spec/modules/emx_digitalBidAdapter_spec.js +++ b/test/spec/modules/emx_digitalBidAdapter_spec.js @@ -370,7 +370,7 @@ describe('emx_digital Adapter', function () { it('should add schain object to request', function() { const schainBidderRequest = utils.deepClone(bidderRequest); - schainBidderRequest.schain = { + schainBidderRequest.bids[0].schain = { 'complete': 1, 'ver': '1.0', 'nodes': [ @@ -386,7 +386,7 @@ describe('emx_digital Adapter', function () { expect(request.source.ext.schain).to.exist; expect(request.source.ext.schain).to.have.property('complete', 1); expect(request.source.ext.schain).to.have.property('ver', '1.0'); - expect(request.source.ext.schain.nodes[0].asi).to.equal(schainBidderRequest.schain.nodes[0].asi); + expect(request.source.ext.schain.nodes[0].asi).to.equal(schainBidderRequest.bids[0].schain.nodes[0].asi); }); }); diff --git a/test/spec/modules/engageyaBidAdapter_spec.js b/test/spec/modules/engageyaBidAdapter_spec.js new file mode 100644 index 00000000000..ad411fc9350 --- /dev/null +++ b/test/spec/modules/engageyaBidAdapter_spec.js @@ -0,0 +1,161 @@ +import {expect} from 'chai'; +import {spec} from 'modules/engageyaBidAdapter.js'; +import * as utils from 'src/utils.js'; + +const ENDPOINT_URL = 'https://recs.engageya.com/rec-api/getrecs.json'; + +export const _getUrlVars = function(url) { + var hash; + var myJson = {}; + var hashes = url.slice(url.indexOf('?') + 1).split('&'); + for (var i = 0; i < hashes.length; i++) { + hash = hashes[i].split('='); + myJson[hash[0]] = hash[1]; + } + return myJson; +} + +describe('engageya adapter', function() { + let bidRequests; + let nativeBidRequests; + + beforeEach(function() { + bidRequests = [ + { + bidder: 'engageya', + params: { + widgetId: 85610, + websiteId: 91140, + pageUrl: '[PAGE_URL]' + } + } + ] + + nativeBidRequests = [ + { + bidder: 'engageya', + params: { + widgetId: 85610, + websiteId: 91140, + pageUrl: '[PAGE_URL]' + }, + nativeParams: { + title: { + required: true, + len: 80 + }, + image: { + required: true, + sizes: [150, 50] + }, + sponsoredBy: { + required: true + } + } + } + ] + }) + describe('isBidRequestValid', function () { + it('valid bid case', function () { + let validBid = { + bidder: 'engageya', + params: { + widgetId: 85610, + websiteId: 91140, + pageUrl: '[PAGE_URL]' + } + } + let isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(true); + }); + + it('invalid bid case: widgetId and websiteId is not passed', function() { + let validBid = { + bidder: 'engageya', + params: { + } + } + let isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(false); + }) + + it('invalid bid case: widget id must be number', function() { + let invalidBid = { + bidder: 'engageya', + params: { + widgetId: '157746a', + websiteId: 91140, + pageUrl: '[PAGE_URL]' + } + } + let isValid = spec.isBidRequestValid(invalidBid); + expect(isValid).to.equal(false); + }) + }) + + describe('buildRequests', function () { + it('sends bid request to ENDPOINT via GET', function () { + const request = spec.buildRequests(bidRequests)[0]; + expect(request.url).to.include(ENDPOINT_URL); + expect(request.method).to.equal('GET'); + }); + + it('buildRequests function should not modify original bidRequests object', function () { + let originalBidRequests = utils.deepClone(bidRequests); + let request = spec.buildRequests(bidRequests); + expect(bidRequests).to.deep.equal(originalBidRequests); + }); + + it('buildRequests function should not modify original nativeBidRequests object', function () { + let originalBidRequests = utils.deepClone(nativeBidRequests); + let request = spec.buildRequests(nativeBidRequests); + expect(nativeBidRequests).to.deep.equal(originalBidRequests); + }); + + it('Request params check', function() { + let request = spec.buildRequests(bidRequests)[0]; + const data = _getUrlVars(request.url) + expect(parseInt(data.wid)).to.exist.and.to.equal(bidRequests[0].params.widgetId); + expect(parseInt(data.webid)).to.exist.and.to.equal(bidRequests[0].params.websiteId); + }) + }) + + describe('interpretResponse', function () { + let response = {recs: [ + { + 'ecpm': 0.0920, + 'postId': '', + 'ad': '', + 'thumbnail_path': 'https://engageya.live/wp-content/uploads/2019/05/images.png' + } + ], + imageWidth: 300, + imageHeight: 250, + ireqId: '1d236f7890b', + pbtypeId: 2}; + + it('should get correct bid response', function () { + let expectedResponse = [ + { + 'requestId': '1d236f7890b', + 'cpm': 0.0920, + 'width': 300, + 'height': 250, + 'netRevenue': false, + 'currency': 'USD', + 'creativeId': '', + 'ttl': 700, + 'ad': '' + } + ]; + let request = spec.buildRequests(bidRequests)[0]; + let result = spec.interpretResponse({body: response}, request); + expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); + expect(result[0].cpm).to.not.equal(null); + expect(result[0].creativeId).to.not.equal(null); + expect(result[0].ad).to.not.equal(null); + expect(result[0].currency).to.equal('USD'); + expect(result[0].netRevenue).to.equal(false); + }); + }) +}) diff --git a/test/spec/modules/eplanningBidAdapter_spec.js b/test/spec/modules/eplanningBidAdapter_spec.js index f267680b46b..5969777f4da 100644 --- a/test/spec/modules/eplanningBidAdapter_spec.js +++ b/test/spec/modules/eplanningBidAdapter_spec.js @@ -1,6 +1,7 @@ import { expect } from 'chai'; import { spec, storage } from 'modules/eplanningBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; +import * as utils from 'src/utils.js'; describe('E-Planning Adapter', function () { const adapter = newBidder('spec'); @@ -32,6 +33,15 @@ describe('E-Planning Adapter', function () { 'adUnitCode': ADUNIT_CODE, 'sizes': [[300, 250], [300, 600]], }; + const validBid2 = { + 'bidder': 'eplanning', + 'bidId': BID_ID2, + 'params': { + 'ci': CI, + }, + 'adUnitCode': ADUNIT_CODE2, + 'sizes': [[300, 250], [300, 600]], + }; const ML = '1'; const validBidMappingLinear = { 'bidder': 'eplanning', @@ -43,13 +53,14 @@ describe('E-Planning Adapter', function () { 'adUnitCode': ADUNIT_CODE, 'sizes': [[300, 250], [300, 600]], }; - const validBid2 = { + const SN = 'spaceName'; + const validBidSpaceName = { 'bidder': 'eplanning', - 'bidId': BID_ID2, + 'bidId': BID_ID, 'params': { 'ci': CI, + 'sn': SN, }, - 'adUnitCode': ADUNIT_CODE2, 'sizes': [[300, 250], [300, 600]], }; const validBidView = { @@ -95,6 +106,39 @@ describe('E-Planning Adapter', function () { 'adUnitCode': 'adunit-code', 'sizes': [[300, 250], [300, 600]], }; + const validBidExistingSizesInPriorityListForMobile = { + 'bidder': 'eplanning', + 'bidId': BID_ID, + 'params': { + 'ci': CI, + }, + 'adUnitCode': ADUNIT_CODE, + 'sizes': [[970, 250], [320, 50], [300, 50]], + }; + const validBidSizesNotExistingInPriorityListForMobile = { + 'bidder': 'eplanning', + 'bidId': BID_ID, + 'params': { + 'ci': CI, + }, + 'adUnitCode': ADUNIT_CODE, + 'sizes': [[970, 250], [300, 70], [160, 600]], + }; + const validBidExistingSizesInPriorityListForDesktop = { + 'bidder': 'eplanning', + 'bidId': BID_ID, + 'params': { + 'ci': CI, + }, + 'adUnitCode': ADUNIT_CODE, + 'sizes': [[970, 250], [300, 600], [300, 250]], + 'ext': { + 'screen': { + 'w': 1025, + 'h': 1025 + } + } + }; const response = { body: { 'sI': { @@ -247,6 +291,27 @@ describe('E-Planning Adapter', function () { describe('buildRequests', function () { let bidRequests = [validBid]; + let sandbox; + beforeEach(() => { + sandbox = sinon.sandbox.create(); + }); + + afterEach(() => { + sandbox.restore(); + }); + + const createWindow = () => { + const win = {}; + win.self = win; + win.innerWidth = 1025; + return win; + }; + + function setupSingleWindow(sandBox) { + const win = createWindow(); + sandBox.stub(utils, 'getWindowSelf').returns(win); + } + it('should create the url correctly', function () { const url = spec.buildRequests(bidRequests, bidderRequest).url; expect(url).to.equal('https://ads.us.e-planning.net/hb/1/' + CI + '/1/localhost/ROS'); @@ -278,6 +343,12 @@ describe('E-Planning Adapter', function () { expect(e).to.equal(CLEAN_ADUNIT_CODE_ML + ':300x250,300x600'); }); + it('should return e parameter with space name attribute with value according to the adunit sizes', function () { + let bidRequestsSN = [validBidSpaceName]; + const e = spec.buildRequests(bidRequestsSN, bidderRequest).data.e; + expect(e).to.equal(SN + ':300x250,300x600'); + }); + it('should return correct e parameter with more than one adunit', function () { const NEW_CODE = ADUNIT_CODE + '2'; const CLEAN_NEW_CODE = CLEAN_ADUNIT_CODE + '2'; @@ -314,6 +385,23 @@ describe('E-Planning Adapter', function () { expect(e).to.equal(CLEAN_ADUNIT_CODE_ML + ':300x250,300x600+' + CLEAN_NEW_CODE + ':100x100'); }); + it('should return correct e parameter with space name attribute with more than one adunit', function () { + let bidRequestsSN = [validBidSpaceName]; + const NEW_SN = 'anotherNameSpace'; + const anotherBid = { + 'bidder': 'eplanning', + 'params': { + 'ci': CI, + 'sn': NEW_SN, + }, + 'sizes': [[100, 100]], + }; + bidRequestsSN.push(anotherBid); + + const e = spec.buildRequests(bidRequestsSN, bidderRequest).data.e; + expect(e).to.equal(SN + ':300x250,300x600+' + NEW_SN + ':100x100'); + }); + it('should return correct e parameter when the adunit has no size', function () { const noSizeBid = { 'bidder': 'eplanning', @@ -388,6 +476,25 @@ describe('E-Planning Adapter', function () { const dataRequest = request.data; expect(dataRequest.ccpa).to.equal('consentCcpa'); }); + + it('should return the e parameter with a value according to the sizes in order corresponding to the mobile priority list of the ad units', function () { + let bidRequestsPrioritySizes = [validBidExistingSizesInPriorityListForMobile]; + const e = spec.buildRequests(bidRequestsPrioritySizes, bidderRequest).data.e; + expect(e).to.equal('320x50_0:320x50,300x50,970x250'); + }); + + it('should return the e parameter with a value according to the sizes in order corresponding to the desktop priority list of the ad units', function () { + let bidRequestsPrioritySizes = [validBidExistingSizesInPriorityListForDesktop]; + setupSingleWindow(sandbox); + const e = spec.buildRequests(bidRequestsPrioritySizes, bidderRequest).data.e; + expect(e).to.equal('300x250_0:300x250,300x600,970x250'); + }); + + it('should return the e parameter with a value according to the sizes in order as they are sent from the ad units', function () { + let bidRequestsPrioritySizes2 = [validBidSizesNotExistingInPriorityListForMobile]; + const e = spec.buildRequests(bidRequestsPrioritySizes2, bidderRequest).data.e; + expect(e).to.equal('970x250_0:970x250,300x70,160x600'); + }); }); describe('interpretResponse', function () { diff --git a/test/spec/modules/fabrickIdSystem_spec.js b/test/spec/modules/fabrickIdSystem_spec.js index cbd538816ab..c250c8e5e8b 100644 --- a/test/spec/modules/fabrickIdSystem_spec.js +++ b/test/spec/modules/fabrickIdSystem_spec.js @@ -1,7 +1,7 @@ import * as utils from '../../../src/utils.js'; import {server} from '../../mocks/xhr.js'; -import * as fabrickIdSystem from 'modules/fabrickIdSystem.js'; +import {fabrickIdSubmodule, appendUrl} from 'modules/fabrickIdSystem.js'; const defaultConfigParams = { apiKey: '123', @@ -10,26 +10,25 @@ const defaultConfigParams = { url: 'http://localhost:9999/test/mocks/fabrickId.json?' }; const responseHeader = {'Content-Type': 'application/json'} -const fabrickIdSubmodule = fabrickIdSystem.fabrickIdSubmodule; describe('Fabrick ID System', function() { let logErrorStub; beforeEach(function () { - logErrorStub = sinon.stub(utils, 'logError'); }); afterEach(function () { - logErrorStub.restore(); - fabrickIdSubmodule.getRefererInfoOverride = null; }); it('should log an error if no configParams were passed into getId', function () { + logErrorStub = sinon.stub(utils, 'logError'); fabrickIdSubmodule.getId(); expect(logErrorStub.calledOnce).to.be.true; + logErrorStub.restore(); }); it('should error on json parsing', function() { + logErrorStub = sinon.stub(utils, 'logError'); let submoduleCallback = fabrickIdSubmodule.getId({ name: 'fabrickId', params: defaultConfigParams @@ -44,11 +43,12 @@ describe('Fabrick ID System', function() { ); expect(callBackSpy.calledOnce).to.be.true; expect(logErrorStub.calledOnce).to.be.true; + logErrorStub.restore(); }); it('should truncate the params', function() { let r = ''; - for (let i = 0; i < 300; i++) { + for (let i = 0; i < 1500; i++) { r += 'r'; } let configParams = Object.assign({}, defaultConfigParams, { @@ -66,7 +66,7 @@ describe('Fabrick ID System', function() { submoduleCallback(callBackSpy); let request = server.requests[0]; r = ''; - for (let i = 0; i < 200; i++) { + for (let i = 0; i < 1000 - 3; i++) { r += 'r'; } expect(request.url).to.match(new RegExp(`r=${r}&r=`)); @@ -76,7 +76,6 @@ describe('Fabrick ID System', function() { JSON.stringify({}) ); expect(callBackSpy.calledOnce).to.be.true; - expect(logErrorStub.calledOnce).to.be.false; }); it('should complete successfully', function() { @@ -101,6 +100,35 @@ describe('Fabrick ID System', function() { JSON.stringify({}) ); expect(callBackSpy.calledOnce).to.be.true; - expect(logErrorStub.calledOnce).to.be.false; + }); + + it('should truncate 2', function() { + let configParams = { + maxUrlLen: 10, + maxRefLen: 5, + maxSpaceAvailable: 2 + }; + + let url = appendUrl('', 'r', '123', configParams); + expect(url).to.equal('&r=12'); + + url = appendUrl('12345', 'r', '678', configParams); + expect(url).to.equal('12345&r=67'); + + url = appendUrl('12345678', 'r', '9', configParams); + expect(url).to.equal('12345678'); + + configParams.maxRefLen = 8; + url = appendUrl('', 'r', '1234&', configParams); + expect(url).to.equal('&r=1234'); + + url = appendUrl('', 'r', '123&', configParams); + expect(url).to.equal('&r=123'); + + url = appendUrl('', 'r', '12&', configParams); + expect(url).to.equal('&r=12%26'); + + url = appendUrl('', 'r', '1&&', configParams); + expect(url).to.equal('&r=1%26'); }); }); diff --git a/test/spec/modules/freewheel-sspBidAdapter_spec.js b/test/spec/modules/freewheel-sspBidAdapter_spec.js index c44d7908ba8..e416bd1c26b 100644 --- a/test/spec/modules/freewheel-sspBidAdapter_spec.js +++ b/test/spec/modules/freewheel-sspBidAdapter_spec.js @@ -323,7 +323,10 @@ describe('freewheelSSP BidAdapter Test', () => { ' ' + ' Adswizz' + ' ' + - ' https://ads.stickyadstv.com/auto-user-sync?dealId=NRJ-PRO-00008' + + ' https://ads.stickyadstv.com/auto-user-sync?dealId=NRJ-PRO-12008' + + ' ' + + ' ' + + ' ' + ' ' + ' ' + ' ' + @@ -358,6 +361,8 @@ describe('freewheelSSP BidAdapter Test', () => { netRevenue: true, ttl: 360, dealId: 'NRJ-PRO-00008', + campaignId: 'SMF-WOW-55555', + bannerId: '12345', ad: ad } ]; @@ -365,6 +370,8 @@ describe('freewheelSSP BidAdapter Test', () => { let result = spec.interpretResponse(response, request[0]); expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedResponse[0])); expect(result[0].dealId).to.equal('NRJ-PRO-00008'); + expect(result[0].campaignId).to.equal('SMF-WOW-55555'); + expect(result[0].bannerId).to.equal('12345'); }); it('should get correct bid response with formated ad', () => { @@ -381,6 +388,8 @@ describe('freewheelSSP BidAdapter Test', () => { netRevenue: true, ttl: 360, dealId: 'NRJ-PRO-00008', + campaignId: 'SMF-WOW-55555', + bannerId: '12345', ad: formattedAd } ]; @@ -388,6 +397,8 @@ describe('freewheelSSP BidAdapter Test', () => { let result = spec.interpretResponse(response, request[0]); expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedResponse[0])); expect(result[0].dealId).to.equal('NRJ-PRO-00008'); + expect(result[0].campaignId).to.equal('SMF-WOW-55555'); + expect(result[0].bannerId).to.equal('12345'); }); it('handles nobid responses', () => { @@ -398,6 +409,7 @@ describe('freewheelSSP BidAdapter Test', () => { expect(result.length).to.equal(0); }); }); + describe('interpretResponseForVideo', () => { let bidRequests = [ { @@ -461,6 +473,12 @@ describe('freewheelSSP BidAdapter Test', () => { ' Adswizz' + ' ' + ' https://ads.stickyadstv.com/auto-user-sync?dealId=NRJ-PRO-00008' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + ' ' + ' ' + ' ' + @@ -495,6 +513,8 @@ describe('freewheelSSP BidAdapter Test', () => { netRevenue: true, ttl: 360, dealId: 'NRJ-PRO-00008', + campaignId: 'SMF-WOW-55555', + bannerId: '12345', vastXml: response, mediaType: 'video', ad: ad @@ -504,6 +524,8 @@ describe('freewheelSSP BidAdapter Test', () => { let result = spec.interpretResponse(response, request[0]); expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedResponse[0])); expect(result[0].dealId).to.equal('NRJ-PRO-00008'); + expect(result[0].campaignId).to.equal('SMF-WOW-55555'); + expect(result[0].bannerId).to.equal('12345'); }); it('should get correct bid response with formated ad', () => { @@ -520,6 +542,8 @@ describe('freewheelSSP BidAdapter Test', () => { netRevenue: true, ttl: 360, dealId: 'NRJ-PRO-00008', + campaignId: 'SMF-WOW-55555', + bannerId: '12345', vastXml: response, mediaType: 'video', ad: formattedAd @@ -529,6 +553,8 @@ describe('freewheelSSP BidAdapter Test', () => { let result = spec.interpretResponse(response, request[0]); expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedResponse[0])); expect(result[0].dealId).to.equal('NRJ-PRO-00008'); + expect(result[0].campaignId).to.equal('SMF-WOW-55555'); + expect(result[0].bannerId).to.equal('12345'); }); it('handles nobid responses', () => { diff --git a/test/spec/modules/gjirafaBidAdapter_spec.js b/test/spec/modules/gjirafaBidAdapter_spec.js index db9b82e0a10..f0fb01f4398 100644 --- a/test/spec/modules/gjirafaBidAdapter_spec.js +++ b/test/spec/modules/gjirafaBidAdapter_spec.js @@ -34,9 +34,7 @@ describe('gjirafaAdapterTest', () => { it('bidRequest without propertyId or placementId', () => { expect(spec.isBidRequestValid({ bidder: 'gjirafa', - params: { - propertyId: '{propertyId}', - } + params: {} })).to.equal(false); }); }); @@ -46,7 +44,17 @@ describe('gjirafaAdapterTest', () => { 'bidder': 'gjirafa', 'params': { 'propertyId': '{propertyId}', - 'placementId': '{placementId}' + 'placementId': '{placementId}', + 'data': { + 'catalogs': [{ + 'catalogId': 1, + 'items': ['1', '2', '3'] + }], + 'inventory': { + 'category': ['category1', 'category2'], + 'query': ['query'] + } + } }, 'adUnitCode': 'hb-leaderboard', 'transactionId': 'b6b889bb-776c-48fd-bc7b-d11a1cf0425e', @@ -86,6 +94,20 @@ describe('gjirafaAdapterTest', () => { expect(requestItem.data.placements[0].sizes).to.equal('728x90'); }); }); + + it('bidRequest data param', () => { + const requests = spec.buildRequests(bidRequests); + requests.forEach((requestItem) => { + expect(requestItem.data.data).to.exist; + expect(requestItem.data.data.catalogs).to.exist; + expect(requestItem.data.data.inventory).to.exist; + expect(requestItem.data.data.catalogs.length).to.equal(1); + expect(requestItem.data.data.catalogs[0].items.length).to.equal(3); + expect(Object.keys(requestItem.data.data.inventory).length).to.equal(2); + expect(requestItem.data.data.inventory.category.length).to.equal(2); + expect(requestItem.data.data.inventory.query.length).to.equal(1); + }); + }); }); describe('interpretResponse', () => { diff --git a/test/spec/modules/glomexBidAdapter_spec.js b/test/spec/modules/glomexBidAdapter_spec.js new file mode 100644 index 00000000000..b43623928f7 --- /dev/null +++ b/test/spec/modules/glomexBidAdapter_spec.js @@ -0,0 +1,133 @@ +import { expect } from 'chai' +import { spec } from 'modules/glomexBidAdapter.js' +import { newBidder } from 'src/adapters/bidderFactory.js' + +const REQUEST = { + bidder: 'glomex', + params: { + integrationId: 'abcdefg', + playlistId: 'defghjk' + }, + bidderRequestId: '143346cf0f1732', + auctionId: '2e41f65424c87c', + adUnitCode: 'adunit-code', + bidId: '2d925f27f5079f', + sizes: [640, 360] +} + +const BIDDER_REQUEST = { + auctionId: '2d921234f5079f', + refererInfo: { + isAmp: true, + numIframes: 0, + reachedTop: true, + referer: 'https://glomex.com' + }, + gdprConsent: { + gdprApplies: true, + consentString: 'CO5asbJO5asbJE-AAAENAACAAAAAAAAAAAYgAAAAAAAA.IAAA' + } +} + +const RESPONSE = { + bids: [ + { + id: '2d925f27f5079f', + cpm: 3.5, + width: 640, + height: 360, + creativeId: 'creative-1j75x4ln1kk6m1ius', + dealId: 'deal-1j75x4ln1kk6m1iut', + currency: 'EUR', + netRevenue: true, + ttl: 300, + ad: '' + } + ] +} +describe('glomexBidAdapter', function () { + const adapter = newBidder(spec) + + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function') + }) + }) + + describe('isBidRequestValid', function () { + it('should return true when required params found', function () { + const request = { + 'params': { + 'integrationId': 'abcdefg' + } + } + expect(spec.isBidRequestValid(request)).to.equal(true) + }) + + it('should return false when required params are not passed', function () { + expect(spec.isBidRequestValid({})).to.equal(false) + }) + }) + + describe('buildRequests', function () { + const bidRequests = [REQUEST] + const request = spec.buildRequests(bidRequests, BIDDER_REQUEST) + + it('sends bid request to ENDPOINT via POST', function () { + expect(request.method).to.equal('POST') + }) + + it('returns a list of valid requests', function () { + expect(request.validBidRequests).to.eql([REQUEST]) + }) + + it('sends params.integrationId', function () { + expect(request.validBidRequests[0].params.integrationId).to.eql(REQUEST.params.integrationId) + }) + + it('sends params.playlistId', function () { + expect(request.validBidRequests[0].params.playlistId).to.eql(REQUEST.params.playlistId) + }) + + it('sends refererInfo', function () { + expect(request.data.refererInfo).to.eql(BIDDER_REQUEST.refererInfo) + }) + + it('sends gdprConsent', function () { + expect(request.data.gdprConsent).to.eql(BIDDER_REQUEST.gdprConsent) + }) + + it('sends the auctionId', function () { + expect(request.data.auctionId).to.eql(BIDDER_REQUEST.auctionId) + }) + }) + + describe('interpretResponse', function () { + it('handles nobid responses', function () { + expect(spec.interpretResponse({body: {}}, {validBidRequests: []}).length).to.equal(0) + expect(spec.interpretResponse({body: []}, {validBidRequests: []}).length).to.equal(0) + }) + + it('handles the server response', function () { + const result = spec.interpretResponse( + { + body: RESPONSE + }, + { + validBidRequests: [REQUEST] + } + ) + + expect(result[0].requestId).to.equal('2d925f27f5079f') + expect(result[0].cpm).to.equal(3.5) + expect(result[0].width).to.equal(640) + expect(result[0].height).to.equal(360) + expect(result[0].creativeId).to.equal('creative-1j75x4ln1kk6m1ius') + expect(result[0].dealId).to.equal('deal-1j75x4ln1kk6m1iut') + expect(result[0].currency).to.equal('EUR') + expect(result[0].netRevenue).to.equal(true) + expect(result[0].ttl).to.equal(300) + expect(result[0].ad).to.equal('') + }) + }) +}) diff --git a/test/spec/modules/gmosspBidAdapter_spec.js b/test/spec/modules/gmosspBidAdapter_spec.js index 5de3db623c5..52bb8460caa 100644 --- a/test/spec/modules/gmosspBidAdapter_spec.js +++ b/test/spec/modules/gmosspBidAdapter_spec.js @@ -54,18 +54,30 @@ describe('GmosspAdapter', function () { } ]; - const bidderRequest = { - refererInfo: { - referer: 'https://hoge.com' - } - }; - it('sends bid request to ENDPOINT via GET', function () { + const bidderRequest = { + refererInfo: { + referer: 'https://hoge.com' + } + }; + const requests = spec.buildRequests(bidRequests, bidderRequest); expect(requests[0].url).to.equal(ENDPOINT); expect(requests[0].method).to.equal('GET'); expect(requests[0].data).to.equal('tid=791e9d84-af92-4903-94da-24c7426d9d0c&bid=2b84475b5b636e&ver=$prebid.version$&sid=123456&url=https%3A%2F%2Fhoge.com&cur=JPY&dnt=0&'); }); + + it('should use fallback if refererInfo.referer in bid request is empty', function () { + const bidderRequest = { + refererInfo: { + referer: '' + } + }; + + const requests = spec.buildRequests(bidRequests, bidderRequest); + const result = 'tid=791e9d84-af92-4903-94da-24c7426d9d0c&bid=2b84475b5b636e&ver=$prebid.version$&sid=123456&url=' + encodeURIComponent(window.top.location.href) + '&cur=JPY&dnt=0&'; + expect(requests[0].data).to.equal(result); + }); }); describe('interpretResponse', function () { diff --git a/test/spec/modules/gothamadsBidAdapter_spec.js b/test/spec/modules/gothamadsBidAdapter_spec.js new file mode 100644 index 00000000000..c638e7a0e3e --- /dev/null +++ b/test/spec/modules/gothamadsBidAdapter_spec.js @@ -0,0 +1,369 @@ +import { expect } from 'chai'; +import { spec } from 'modules/gothamadsBidAdapter.js'; + +const NATIVE_BID_REQUEST = { + code: 'native_example', + mediaTypes: { + native: { + title: { + required: true, + len: 800 + }, + image: { + required: true, + len: 80 + }, + sponsoredBy: { + required: true + }, + clickUrl: { + required: true + }, + privacyLink: { + required: false + }, + body: { + required: true + }, + icon: { + required: true, + sizes: [50, 50] + } + } + }, + bidder: 'gothamads', + params: { + placementId: 'hash', + accountId: 'accountId' + }, + timeout: 1000 + +}; + +const BANNER_BID_REQUEST = { + code: 'banner_example', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + bidder: 'gothamads', + params: { + placementId: 'hash', + accountId: 'accountId' + }, + timeout: 1000, + +} + +const bidRequest = { + refererInfo: { + referer: 'test.com' + } +} + +const VIDEO_BID_REQUEST = { + code: 'video1', + sizes: [640, 480], + mediaTypes: { video: { + minduration: 0, + maxduration: 999, + boxingallowed: 1, + skip: 0, + mimes: [ + 'application/javascript', + 'video/mp4' + ], + w: 1920, + h: 1080, + protocols: [ + 2 + ], + linearity: 1, + api: [ + 1, + 2 + ] + } + }, + + bidder: 'gothamads', + params: { + placementId: 'hash', + accountId: 'accountId' + }, + timeout: 1000 + +} + +const BANNER_BID_RESPONSE = { + id: 'request_id', + bidid: 'request_imp_id', + seatbid: [{ + bid: [{ + id: 'bid_id', + impid: 'request_imp_id', + price: 5, + adomain: ['example.com'], + adm: 'admcode', + crid: 'crid', + ext: { + mediaType: 'banner' + } + }], + }], +}; + +const VIDEO_BID_RESPONSE = { + id: 'request_id', + bidid: 'request_imp_id', + seatbid: [{ + bid: [{ + id: 'bid_id', + impid: 'request_imp_id', + price: 5, + adomain: ['example.com'], + adm: 'admcode', + crid: 'crid', + ext: { + mediaType: 'video', + vastUrl: 'http://example.vast', + } + }], + }], +}; + +let imgData = { + url: `https://example.com/image`, + w: 1200, + h: 627 +}; + +const NATIVE_BID_RESPONSE = { + id: 'request_id', + bidid: 'request_imp_id', + seatbid: [{ + bid: [{ + id: 'bid_id', + impid: 'request_imp_id', + price: 5, + adomain: ['example.com'], + adm: { native: + { + assets: [ + {id: 0, title: 'dummyText'}, + {id: 3, image: imgData}, + { + id: 5, + data: {value: 'organization.name'} + } + ], + link: {url: 'example.com'}, + imptrackers: ['tracker1.com', 'tracker2.com', 'tracker3.com'], + jstracker: 'tracker1.com' + } + }, + crid: 'crid', + ext: { + mediaType: 'native' + } + }], + }], +}; + +describe('GothamAdsAdapter', function() { + describe('isBidRequestValid', function() { + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(NATIVE_BID_REQUEST)).to.equal(true); + }); + + it('should return false when required params are not passed', function () { + let bid = Object.assign({}, NATIVE_BID_REQUEST); + delete bid.params; + bid.params = { + 'IncorrectParam': 0 + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('build Native Request', function () { + const request = spec.buildRequests([NATIVE_BID_REQUEST], bidRequest); + + it('Creates a ServerRequest object with method, URL and data', function () { + expect(request).to.exist; + expect(request.method).to.exist; + expect(request.url).to.exist; + expect(request.data).to.exist; + }); + + it('sends bid request to our endpoint via POST', function () { + expect(request.method).to.equal('POST'); + }); + + it('Returns valid URL', function () { + expect(request.url).to.equal('https://us-e-node1.gothamads.com/bid?pass=accountId&integration=prebidjs'); + }); + + it('Returns empty data if no valid requests are passed', function () { + let serverRequest = spec.buildRequests([]); + expect(serverRequest).to.be.an('array').that.is.empty; + }); + }); + + describe('build Banner Request', function () { + const request = spec.buildRequests([BANNER_BID_REQUEST]); + + it('Creates a ServerRequest object with method, URL and data', function () { + expect(request).to.exist; + expect(request.method).to.exist; + expect(request.url).to.exist; + expect(request.data).to.exist; + }); + + it('sends bid request to our endpoint via POST', function () { + expect(request.method).to.equal('POST'); + }); + + it('Returns valid URL', function () { + expect(request.url).to.equal('https://us-e-node1.gothamads.com/bid?pass=accountId&integration=prebidjs'); + }); + }); + + describe('build Video Request', function () { + const request = spec.buildRequests([VIDEO_BID_REQUEST]); + + it('Creates a ServerRequest object with method, URL and data', function () { + expect(request).to.exist; + expect(request.method).to.exist; + expect(request.url).to.exist; + expect(request.data).to.exist; + }); + + it('sends bid request to our endpoint via POST', function () { + expect(request.method).to.equal('POST'); + }); + + it('Returns valid URL', function () { + expect(request.url).to.equal('https://us-e-node1.gothamads.com/bid?pass=accountId&integration=prebidjs'); + }); + }); + + describe('interpretResponse', function () { + it('Empty response must return empty array', function() { + const emptyResponse = null; + let response = spec.interpretResponse(emptyResponse); + + expect(response).to.be.an('array').that.is.empty; + }) + + it('Should interpret banner response', function () { + const bannerResponse = { + body: [BANNER_BID_RESPONSE] + } + + const expectedBidResponse = { + requestId: BANNER_BID_RESPONSE.id, + cpm: BANNER_BID_RESPONSE.seatbid[0].bid[0].price, + width: BANNER_BID_RESPONSE.seatbid[0].bid[0].w, + height: BANNER_BID_RESPONSE.seatbid[0].bid[0].h, + ttl: BANNER_BID_RESPONSE.ttl || 1200, + currency: BANNER_BID_RESPONSE.cur || 'USD', + netRevenue: true, + creativeId: BANNER_BID_RESPONSE.seatbid[0].bid[0].crid, + dealId: BANNER_BID_RESPONSE.seatbid[0].bid[0].dealid, + mediaType: 'banner', + ad: BANNER_BID_RESPONSE.seatbid[0].bid[0].adm + } + + let bannerResponses = spec.interpretResponse(bannerResponse); + + expect(bannerResponses).to.be.an('array').that.is.not.empty; + let dataItem = bannerResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType'); + expect(dataItem.requestId).to.equal(expectedBidResponse.requestId); + expect(dataItem.cpm).to.equal(expectedBidResponse.cpm); + expect(dataItem.ad).to.equal(expectedBidResponse.ad); + expect(dataItem.ttl).to.equal(expectedBidResponse.ttl); + expect(dataItem.creativeId).to.equal(expectedBidResponse.creativeId); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal(expectedBidResponse.currency); + expect(dataItem.width).to.equal(expectedBidResponse.width); + expect(dataItem.height).to.equal(expectedBidResponse.height); + }); + + it('Should interpret video response', function () { + const videoResponse = { + body: [VIDEO_BID_RESPONSE] + } + + const expectedBidResponse = { + requestId: VIDEO_BID_RESPONSE.id, + cpm: VIDEO_BID_RESPONSE.seatbid[0].bid[0].price, + width: VIDEO_BID_RESPONSE.seatbid[0].bid[0].w, + height: VIDEO_BID_RESPONSE.seatbid[0].bid[0].h, + ttl: VIDEO_BID_RESPONSE.ttl || 1200, + currency: VIDEO_BID_RESPONSE.cur || 'USD', + netRevenue: true, + creativeId: VIDEO_BID_RESPONSE.seatbid[0].bid[0].crid, + dealId: VIDEO_BID_RESPONSE.seatbid[0].bid[0].dealid, + mediaType: 'video', + vastXml: VIDEO_BID_RESPONSE.seatbid[0].bid[0].adm, + vastUrl: VIDEO_BID_RESPONSE.seatbid[0].bid[0].ext.vastUrl + } + + let videoResponses = spec.interpretResponse(videoResponse); + + expect(videoResponses).to.be.an('array').that.is.not.empty; + let dataItem = videoResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'vastXml', 'vastUrl', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType'); + expect(dataItem.requestId).to.equal(expectedBidResponse.requestId); + expect(dataItem.cpm).to.equal(expectedBidResponse.cpm); + expect(dataItem.vastXml).to.equal(expectedBidResponse.vastXml) + expect(dataItem.ttl).to.equal(expectedBidResponse.ttl); + expect(dataItem.creativeId).to.equal(expectedBidResponse.creativeId); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal(expectedBidResponse.currency); + expect(dataItem.width).to.equal(expectedBidResponse.width); + expect(dataItem.height).to.equal(expectedBidResponse.height); + }); + + it('Should interpret native response', function () { + const nativeResponse = { + body: [NATIVE_BID_RESPONSE] + } + + const expectedBidResponse = { + requestId: NATIVE_BID_RESPONSE.id, + cpm: NATIVE_BID_RESPONSE.seatbid[0].bid[0].price, + width: NATIVE_BID_RESPONSE.seatbid[0].bid[0].w, + height: NATIVE_BID_RESPONSE.seatbid[0].bid[0].h, + ttl: NATIVE_BID_RESPONSE.ttl || 1200, + currency: NATIVE_BID_RESPONSE.cur || 'USD', + netRevenue: true, + creativeId: NATIVE_BID_RESPONSE.seatbid[0].bid[0].crid, + dealId: NATIVE_BID_RESPONSE.seatbid[0].bid[0].dealid, + mediaType: 'native', + native: {clickUrl: NATIVE_BID_RESPONSE.seatbid[0].bid[0].adm.native.link.url} + } + + let nativeResponses = spec.interpretResponse(nativeResponse); + + expect(nativeResponses).to.be.an('array').that.is.not.empty; + let dataItem = nativeResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'native', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType'); + expect(dataItem.requestId).to.equal(expectedBidResponse.requestId); + expect(dataItem.cpm).to.equal(expectedBidResponse.cpm); + expect(dataItem.native.clickUrl).to.equal(expectedBidResponse.native.clickUrl) + expect(dataItem.ttl).to.equal(expectedBidResponse.ttl); + expect(dataItem.creativeId).to.equal(expectedBidResponse.creativeId); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal(expectedBidResponse.currency); + expect(dataItem.width).to.equal(expectedBidResponse.width); + expect(dataItem.height).to.equal(expectedBidResponse.height); + }); + }); +}) diff --git a/test/spec/modules/gptPreAuction_spec.js b/test/spec/modules/gptPreAuction_spec.js index 16b84467af2..c4a81c21d5c 100644 --- a/test/spec/modules/gptPreAuction_spec.js +++ b/test/spec/modules/gptPreAuction_spec.js @@ -30,34 +30,34 @@ describe('GPT pre-auction module', () => { '
test2
'; it('should be unchanged if already defined on adUnit', () => { - const adUnit = { fpd: { context: { pbAdSlot: '12345' } } }; + const adUnit = { ortb2Imp: { ext: { data: { pbadslot: '12345' } } } }; appendPbAdSlot(adUnit); - expect(adUnit.fpd.context.pbAdSlot).to.equal('12345'); + expect(adUnit.ortb2Imp.ext.data.pbadslot).to.equal('12345'); }); it('should use adUnit.code if matching id exists', () => { - const adUnit = { code: 'foo1', fpd: { context: {} } }; + const adUnit = { code: 'foo1', ortb2Imp: { ext: { data: {} } } }; appendPbAdSlot(adUnit); - expect(adUnit.fpd.context.pbAdSlot).to.equal('bar1'); + expect(adUnit.ortb2Imp.ext.data.pbadslot).to.equal('bar1'); }); it('should use the gptSlot.adUnitPath if the adUnit.code matches a div id but does not have a data-adslotid', () => { - const adUnit = { code: 'foo3', mediaTypes: { banner: { sizes: [[250, 250]] } }, fpd: { context: { adServer: { name: 'gam', adSlot: '/baz' } } } }; + const adUnit = { code: 'foo3', mediaTypes: { banner: { sizes: [[250, 250]] } }, ortb2Imp: { ext: { data: { adserver: { name: 'gam', adslot: '/baz' } } } } }; appendPbAdSlot(adUnit); - expect(adUnit.fpd.context.pbAdSlot).to.equal('/baz'); + expect(adUnit.ortb2Imp.ext.data.pbadslot).to.equal('/baz'); }); it('should use the video adUnit.code (which *should* match the configured "adSlotName", but is not being tested) if there is no matching div with "data-adslotid" defined', () => { - const adUnit = { code: 'foo4', mediaTypes: { video: { sizes: [[250, 250]] } }, fpd: { context: {} } }; + const adUnit = { code: 'foo4', mediaTypes: { video: { sizes: [[250, 250]] } }, ortb2Imp: { ext: { data: {} } } }; adUnit.code = 'foo5'; appendPbAdSlot(adUnit, undefined); - expect(adUnit.fpd.context.pbAdSlot).to.equal('foo5'); + expect(adUnit.ortb2Imp.ext.data.pbadslot).to.equal('foo5'); }); it('should use the adUnit.code if all other sources failed', () => { - const adUnit = { code: 'foo4', fpd: { context: {} } }; + const adUnit = { code: 'foo4', ortb2Imp: { ext: { data: {} } } }; appendPbAdSlot(adUnit, undefined); - expect(adUnit.fpd.context.pbAdSlot).to.equal('foo4'); + expect(adUnit.ortb2Imp.ext.data.pbadslot).to.equal('foo4'); }); it('should use the customPbAdSlot function if one is given', () => { @@ -67,32 +67,32 @@ describe('GPT pre-auction module', () => { } }); - const adUnit = { code: 'foo1', fpd: { context: {} } }; + const adUnit = { code: 'foo1', ortb2Imp: { ext: { data: {} } } }; appendPbAdSlot(adUnit); - expect(adUnit.fpd.context.pbAdSlot).to.equal('customPbAdSlotName'); + expect(adUnit.ortb2Imp.ext.data.pbadslot).to.equal('customPbAdSlotName'); }); }); describe('appendGptSlots', () => { it('should not add adServer object to context if no slots defined', () => { - const adUnit = { code: 'adUnitCode', fpd: { context: {} } }; + const adUnit = { code: 'adUnitCode', ortb2Imp: { ext: { data: {} } } }; appendGptSlots([adUnit]); - expect(adUnit.fpd.context.adServer).to.be.undefined; + expect(adUnit.ortb2Imp.ext.data.adserver).to.be.undefined; }); it('should not add adServer object to context if no slot matches', () => { window.googletag.pubads().setSlots(testSlots); - const adUnit = { code: 'adUnitCode', fpd: { context: {} } }; + const adUnit = { code: 'adUnitCode', ortb2Imp: { ext: { data: {} } } }; appendGptSlots([adUnit]); - expect(adUnit.fpd.context.adServer).to.be.undefined; + expect(adUnit.ortb2Imp.ext.data.adserver).to.be.undefined; }); it('should add adServer object to context if matching slot is found', () => { window.googletag.pubads().setSlots(testSlots); - const adUnit = { code: 'slotCode2', fpd: { context: {} } }; + const adUnit = { code: 'slotCode2', ortb2Imp: { ext: { data: {} } } }; appendGptSlots([adUnit]); - expect(adUnit.fpd.context.adServer).to.be.an('object'); - expect(adUnit.fpd.context.adServer).to.deep.equal({ name: 'gam', adSlot: 'slotCode2' }); + expect(adUnit.ortb2Imp.ext.data.adserver).to.be.an('object'); + expect(adUnit.ortb2Imp.ext.data.adserver).to.deep.equal({ name: 'gam', adslot: 'slotCode2' }); }); it('should use the customGptSlotMatching function if one is given', () => { @@ -104,10 +104,10 @@ describe('GPT pre-auction module', () => { }); window.googletag.pubads().setSlots(testSlots); - const adUnit = { code: 'SlOtCoDe1', fpd: { context: {} } }; + const adUnit = { code: 'SlOtCoDe1', ortb2Imp: { ext: { data: {} } } }; appendGptSlots([adUnit]); - expect(adUnit.fpd.context.adServer).to.be.an('object'); - expect(adUnit.fpd.context.adServer).to.deep.equal({ name: 'gam', adSlot: 'slotCode1' }); + expect(adUnit.ortb2Imp.ext.data.adserver).to.be.an('object'); + expect(adUnit.ortb2Imp.ext.data.adserver).to.deep.equal({ name: 'gam', adslot: 'slotCode1' }); }); }); @@ -164,23 +164,23 @@ describe('GPT pre-auction module', () => { it('should append PB Ad Slot and GPT Slot info to first-party data in each ad unit', () => { const testAdUnits = [{ code: 'adUnit1', - fpd: { context: { pbAdSlot: '12345' } } + ortb2Imp: { ext: { data: { pbadslot: '12345' } } } }, { code: 'slotCode1', - fpd: { context: { pbAdSlot: '67890' } } + ortb2Imp: { ext: { data: { pbadslot: '67890' } } } }, { code: 'slotCode3', }]; const expectedAdUnits = [{ code: 'adUnit1', - fpd: { context: { pbAdSlot: '12345' } } + ortb2Imp: { ext: { data: { pbadslot: '12345' } } } }, { code: 'slotCode1', - fpd: { context: { pbAdSlot: '67890', adServer: { name: 'gam', adSlot: 'slotCode1' } } } + ortb2Imp: { ext: { data: { pbadslot: '67890', adserver: { name: 'gam', adslot: 'slotCode1' } } } } }, { code: 'slotCode3', - fpd: { context: { pbAdSlot: 'slotCode3', adServer: { name: 'gam', adSlot: 'slotCode3' } } } + ortb2Imp: { ext: { data: { pbadslot: 'slotCode3', adserver: { name: 'gam', adslot: 'slotCode3' } } } } }]; window.googletag.pubads().setSlots(testSlots); diff --git a/test/spec/modules/gridBidAdapter_spec.js b/test/spec/modules/gridBidAdapter_spec.js index e884df40c5e..2e8601bddf6 100644 --- a/test/spec/modules/gridBidAdapter_spec.js +++ b/test/spec/modules/gridBidAdapter_spec.js @@ -105,7 +105,8 @@ describe('TheMediaGrid Adapter', function () { 'sizes': [[728, 90]], 'mediaTypes': { 'video': { - 'playerSize': [[400, 600]] + 'playerSize': [[400, 600]], + 'protocols': [1, 2, 3] }, 'banner': { 'sizes': [[728, 90]] @@ -281,7 +282,8 @@ describe('TheMediaGrid Adapter', function () { }, 'video': { 'w': 400, - 'h': 600 + 'h': 600, + 'protocols': [1, 2, 3] } }] }); @@ -311,68 +313,36 @@ describe('TheMediaGrid Adapter', function () { }); it('if userId is present payload must have user.ext param with right keys', function () { - const bidRequestsWithUserIds = bidRequests.map((bid) => { - return Object.assign({ - userId: { - id5id: { uid: 'id5id_1', ext: { linkType: 2 } }, - tdid: 'tdid_1', - digitrustid: {data: {id: 'DTID', keyv: 4, privacy: {optout: false}, producer: 'ABC', version: 2}}, - lipb: {lipbid: 'lipb_1'}, - idl_env: 'idl_env_1', - criteoId: 'criteoId_1' - } - }, bid); - }); - const request = spec.buildRequests(bidRequestsWithUserIds, bidderRequest); - expect(request.data).to.be.an('string'); - const payload = parseRequest(request.data); - expect(payload).to.have.property('user'); - expect(payload.user).to.have.property('ext'); - expect(payload.user.ext.digitrust).to.deep.equal({ - id: 'DTID', - keyv: 4, - privacy: { - optout: false + const eids = [ + { + source: 'pubcid.org', + uids: [{ + id: 'some-random-id-value', + atype: 1 + }] }, - producer: 'ABC', - version: 2 - }); - expect(payload.user.ext.eids).to.deep.equal([ { source: 'adserver.org', uids: [{ - id: 'tdid_1', + id: 'some-random-id-value', + atype: 1, ext: { rtiPartner: 'TDID' } }] - }, - { - source: 'id5-sync.com', - uids: [{ - id: 'id5id_1' - }], - ext: { linkType: 2 } - }, - { - source: 'liveintent.com', - uids: [{ - id: 'lipb_1' - }] - }, - { - source: 'identityLink', - uids: [{ - id: 'idl_env_1' - }] - }, - { - source: 'criteo.com', - uids: [{ - id: 'criteoId_1' - }] } - ]); + ]; + const bidRequestsWithUserIds = bidRequests.map((bid) => { + return Object.assign({ + userIdAsEids: eids + }, bid); + }); + const request = spec.buildRequests(bidRequestsWithUserIds, bidderRequest); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload).to.have.property('user'); + expect(payload.user).to.have.property('ext'); + expect(payload.user.ext.eids).to.deep.equal(eids); }); it('if schain is present payload must have source.ext.schain param', function () { @@ -403,7 +373,7 @@ describe('TheMediaGrid Adapter', function () { it('if content and segment is present in jwTargeting, payload must have right params', function () { const jsContent = {id: 'test_jw_content_id'}; const jsSegments = ['test_seg_1', 'test_seg_2']; - const bidRequestsWithUserIds = bidRequests.map((bid) => { + const bidRequestsWithJwTargeting = bidRequests.map((bid) => { return Object.assign({ rtd: { jwplayer: { @@ -415,7 +385,7 @@ describe('TheMediaGrid Adapter', function () { } }, bid); }); - const request = spec.buildRequests(bidRequestsWithUserIds, bidderRequest); + const request = spec.buildRequests(bidRequestsWithJwTargeting, bidderRequest); expect(request.data).to.be.an('string'); const payload = parseRequest(request.data); expect(payload).to.have.property('user'); @@ -430,6 +400,16 @@ describe('TheMediaGrid Adapter', function () { expect(payload.site.content).to.deep.equal(jsContent); }); + it('should contain the keyword values if it present in ortb2.(site/user)', function () { + const getConfigStub = sinon.stub(config, 'getConfig').callsFake( + arg => arg === 'ortb2.user' ? {'keywords': 'foo'} : (arg === 'ortb2.site' ? {'keywords': 'bar'} : null)); + const request = spec.buildRequests([bidRequests[0]], bidderRequest); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload.ext.keywords).to.deep.equal([{'key': 'user', 'value': ['foo']}, {'key': 'context', 'value': ['bar']}]); + getConfigStub.restore(); + }); + it('shold be right tmax when timeout in config is less then timeout in bidderRequest', function() { const getConfigStub = sinon.stub(config, 'getConfig').callsFake( arg => arg === 'bidderTimeout' ? 2000 : null); @@ -521,7 +501,6 @@ describe('TheMediaGrid Adapter', function () { 'width': 300, 'height': 250, 'ad': '
test content 1
', - 'bidderCode': 'grid', 'currency': 'USD', 'mediaType': 'banner', 'netRevenue': false, @@ -579,7 +558,6 @@ describe('TheMediaGrid Adapter', function () { 'width': 300, 'height': 250, 'ad': '
test content 1
', - 'bidderCode': 'grid', 'currency': 'USD', 'mediaType': 'banner', 'netRevenue': false, @@ -593,7 +571,6 @@ describe('TheMediaGrid Adapter', function () { 'width': 300, 'height': 600, 'ad': '
test content 2
', - 'bidderCode': 'grid', 'currency': 'USD', 'mediaType': 'banner', 'netRevenue': false, @@ -607,7 +584,6 @@ describe('TheMediaGrid Adapter', function () { 'width': 728, 'height': 90, 'ad': '
test content 3
', - 'bidderCode': 'grid', 'currency': 'USD', 'mediaType': 'banner', 'netRevenue': false, @@ -667,7 +643,6 @@ describe('TheMediaGrid Adapter', function () { 'dealId': undefined, 'width': 300, 'height': 600, - 'bidderCode': 'grid', 'currency': 'USD', 'mediaType': 'video', 'netRevenue': false, @@ -684,7 +659,6 @@ describe('TheMediaGrid Adapter', function () { 'dealId': undefined, 'width': undefined, 'height': undefined, - 'bidderCode': 'grid', 'currency': 'USD', 'mediaType': 'video', 'netRevenue': false, @@ -816,7 +790,6 @@ describe('TheMediaGrid Adapter', function () { 'width': 300, 'height': 250, 'ad': '
test content 1
', - 'bidderCode': 'grid', 'currency': 'USD', 'mediaType': 'banner', 'netRevenue': false, @@ -830,7 +803,6 @@ describe('TheMediaGrid Adapter', function () { 'width': 300, 'height': 600, 'ad': '
test content 2
', - 'bidderCode': 'grid', 'currency': 'USD', 'mediaType': 'banner', 'netRevenue': false, @@ -844,7 +816,6 @@ describe('TheMediaGrid Adapter', function () { 'width': 728, 'height': 90, 'ad': '
test content 3
', - 'bidderCode': 'grid', 'currency': 'USD', 'mediaType': 'banner', 'netRevenue': false, @@ -858,7 +829,6 @@ describe('TheMediaGrid Adapter', function () { 'width': 300, 'height': 600, 'ad': '
test content 4
', - 'bidderCode': 'grid', 'currency': 'USD', 'mediaType': 'banner', 'netRevenue': false, diff --git a/test/spec/modules/gridNMBidAdapter_spec.js b/test/spec/modules/gridNMBidAdapter_spec.js index 0dbaac0c526..2aec9713000 100644 --- a/test/spec/modules/gridNMBidAdapter_spec.js +++ b/test/spec/modules/gridNMBidAdapter_spec.js @@ -300,7 +300,6 @@ describe('TheMediaGridNM Adapter', function () { 'dealId': 11, 'width': 300, 'height': 250, - 'bidderCode': 'gridNM', 'currency': 'USD', 'mediaType': 'video', 'netRevenue': false, @@ -317,7 +316,6 @@ describe('TheMediaGridNM Adapter', function () { 'dealId': undefined, 'width': 300, 'height': 600, - 'bidderCode': 'gridNM', 'currency': 'USD', 'mediaType': 'video', 'netRevenue': false, diff --git a/test/spec/modules/gumgumBidAdapter_spec.js b/test/spec/modules/gumgumBidAdapter_spec.js index 52a3a21db4e..2365fddd01f 100644 --- a/test/spec/modules/gumgumBidAdapter_spec.js +++ b/test/spec/modules/gumgumBidAdapter_spec.js @@ -1,7 +1,8 @@ +import { BANNER, VIDEO } from 'src/mediaTypes.js'; + import { expect } from 'chai'; import { newBidder } from 'src/adapters/bidderFactory.js'; import { spec } from 'modules/gumgumBidAdapter.js'; -import { BANNER, VIDEO } from 'src/mediaTypes.js'; const ENDPOINT = 'https://g2.gumgum.com/hbid/imp'; const JCSI = { t: 0, rq: 8, pbv: '$prebid.version$' } @@ -35,7 +36,7 @@ describe('gumgumAdapter', function () { it('should return true when required params found', function () { const zoneBid = { ...bid, params: { 'zone': '123' } }; - const pubIdBid = { ...bid, params: { 'pubId': '123' } }; + const pubIdBid = { ...bid, params: { 'pubId': 123 } }; expect(spec.isBidRequestValid(bid)).to.equal(true); expect(spec.isBidRequestValid(zoneBid)).to.equal(true); expect(spec.isBidRequestValid(pubIdBid)).to.equal(true); @@ -143,23 +144,54 @@ describe('gumgumAdapter', function () { protocols: [1, 2] } }; + const zoneParam = { 'zone': '123a' }; + const pubIdParam = { 'pubId': 123 }; - describe('zone param', function () { - const zoneParam = { 'zone': '123a' }; + it('should set pubId param if found', function () { + const request = { ...bidRequests[0], params: pubIdParam }; + const bidRequest = spec.buildRequests([request])[0]; + expect(bidRequest.data.pubId).to.equal(pubIdParam.pubId); + }); - it('should set t and pi param', function () { - const request = { ...bidRequests[0], params: zoneParam }; - const bidRequest = spec.buildRequests([request])[0]; - expect(bidRequest.data.t).to.equal(zoneParam.zone); - expect(bidRequest.data.pi).to.equal(2); - }); - it('should set the correct pi param if slot param is found', function () { - const request = { ...bidRequests[0], params: { ...zoneParam, 'slot': 1 } }; - const bidRequest = spec.buildRequests([request])[0]; - expect(bidRequest.data.pi).to.equal(3); - }); + it('should set t param when zone param is found', function () { + const request = { ...bidRequests[0], params: zoneParam }; + const bidRequest = spec.buildRequests([request])[0]; + expect(bidRequest.data.t).to.equal(zoneParam.zone); + }); + + it('should set the iriscat param when found', function () { + const request = { ...bidRequests[0], params: { iriscat: 'abc123' } } + const bidRequest = spec.buildRequests([request])[0]; + expect(bidRequest.data).to.have.property('iriscat'); + }); + + it('should not set the iriscat param when not found', function () { + const request = { ...bidRequests[0] } + const bidRequest = spec.buildRequests([request])[0]; + expect(bidRequest.data).to.not.have.property('iriscat'); + }); + + it('should set the irisid param when found', function () { + const request = { ...bidRequests[0], params: { irisid: 'abc123' } } + const bidRequest = spec.buildRequests([request])[0]; + expect(bidRequest.data).to.have.property('irisid'); + }); + + it('should not set the irisid param when not found', function () { + const request = { ...bidRequests[0] } + const bidRequest = spec.buildRequests([request])[0]; + expect(bidRequest.data).to.not.have.property('irisid'); + }); + + it('should not set the irisid param when not of type string', function () { + const request = { ...bidRequests[0], params: { irisid: 123456 } } + const bidRequest = spec.buildRequests([request])[0]; + expect(bidRequest.data).to.not.have.property('irisid'); + }); + + describe('product id', function () { it('should set the correct pi param if native param is found', function () { - const request = { ...bidRequests[0], params: { ...zoneParam, 'native': 2 } }; + const request = { ...bidRequests[0], params: { ...zoneParam, native: 2 } }; const bidRequest = spec.buildRequests([request])[0]; expect(bidRequest.data.pi).to.equal(5); }); @@ -174,25 +206,18 @@ describe('gumgumAdapter', function () { const bidRequest = spec.buildRequests([request])[0]; expect(bidRequest.data.pi).to.equal(6); }); - }); - - describe('pubId zone', function () { - const pubIdParam = { 'pubId': 'abc' }; - - it('should set t param', function () { - const request = { ...bidRequests[0], params: pubIdParam }; + it('should set the correct pi param if slot param is found', function () { + const request = { ...bidRequests[0], params: { ...zoneParam, slot: '123s' } }; const bidRequest = spec.buildRequests([request])[0]; - expect(bidRequest.data.pubId).to.equal(pubIdParam.pubId); + expect(bidRequest.data.pi).to.equal(3); }); - - it('should set the correct pi depending on what is found in mediaTypes', function () { - const request = { ...bidRequests[0], params: pubIdParam }; - const bidRequest = spec.buildRequests([request])[0]; - const vidRequest = { ...bidRequests[0], mediaTypes: vidMediaTypes, params: { 'videoPubID': 123 } }; - const vidBidRequest = spec.buildRequests([vidRequest])[0]; - - expect(bidRequest.data.pi).to.equal(2); - expect(vidBidRequest.data.pi).to.equal(7); + it('should default the pi param to 2 if only zone or pubId param is found', function () { + const zoneRequest = { ...bidRequests[0], params: zoneParam }; + const pubIdRequest = { ...bidRequests[0], params: pubIdParam }; + const zoneBidRequest = spec.buildRequests([zoneRequest])[0]; + const pubIdBidRequest = spec.buildRequests([pubIdRequest])[0]; + expect(zoneBidRequest.data.pi).to.equal(2); + expect(pubIdBidRequest.data.pi).to.equal(2); }); }); @@ -233,6 +258,10 @@ describe('gumgumAdapter', function () { const bidRequest = spec.buildRequests([request])[0]; expect(bidRequest.data.fp).to.equal(bidfloor); }); + it('should return a floor currency', function () { + const request = spec.buildRequests(bidRequests)[0]; + expect(request.data.fpc).to.equal(floorTestData.currency); + }) }); it('sends bid request to ENDPOINT via GET', function () { @@ -432,37 +461,40 @@ describe('gumgumAdapter', function () { }) describe('interpretResponse', function () { - let serverResponse = { - 'ad': { - 'id': 29593, - 'width': 300, - 'height': 250, - 'ipd': 2000, - 'markup': '

I am an ad

', - 'ii': true, - 'du': null, - 'price': 0, - 'zi': 0, - 'impurl': 'http://g2.gumgum.com/ad/view', - 'clsurl': 'http://g2.gumgum.com/ad/close' + const metaData = { adomain: ['advertiser.com'], mediaType: BANNER } + const serverResponse = { + ad: { + id: 29593, + width: 300, + height: 250, + ipd: 2000, + markup: '

I am an ad

', + ii: true, + du: null, + price: 0, + zi: 0, + impurl: 'http://g2.gumgum.com/ad/view', + clsurl: 'http://g2.gumgum.com/ad/close' }, - 'pag': { - 't': 'ggumtest', - 'pvid': 'aa8bbb65-427f-4689-8cee-e3eed0b89eec', - 'css': 'html { overflow-y: auto }', - 'js': 'console.log("environment", env);' + pag: { + t: 'ggumtest', + pvid: 'aa8bbb65-427f-4689-8cee-e3eed0b89eec', + css: 'html { overflow-y: auto }', + js: 'console.log("environment", env);' }, - 'jcsi': { t: 0, rq: 8 }, - 'thms': 10000 + jcsi: { t: 0, rq: 8 }, + thms: 10000, + meta: metaData } - let bidRequest = { + const bidRequest = { id: 12345, sizes: [[300, 250], [1, 1]], url: ENDPOINT, method: 'GET', pi: 3 } - let expectedResponse = { + const expectedMetaData = { advertiserDomains: ['advertiser.com'], mediaType: BANNER }; + const expectedResponse = { ad: '

I am an ad

', cpm: 0, creativeId: 29593, @@ -472,19 +504,42 @@ describe('gumgumAdapter', function () { requestId: 12345, width: '300', mediaType: BANNER, - ttl: 60 + ttl: 60, + meta: expectedMetaData }; it('should get correct bid response', function () { expect(spec.interpretResponse({ body: serverResponse }, bidRequest)).to.deep.equal([expectedResponse]); }); + it('should set a default value for advertiserDomains if adomain is not found', function () { + const meta = { ...metaData }; + delete meta.adomain; + + const response = { ...serverResponse, meta }; + const expectedMeta = { ...expectedMetaData, advertiserDomains: [] }; + const expected = { ...expectedResponse, meta: expectedMeta }; + + expect(spec.interpretResponse({ body: response }, bidRequest)).to.deep.equal([expected]); + }); + + it('should set a default value for meta.mediaType if mediaType is not found in the response', function () { + const meta = { ...metaData }; + delete meta.mediaType; + const response = { ...serverResponse, meta }; + const expected = { ...expectedResponse }; + + expect(spec.interpretResponse({ body: response }, bidRequest)).to.deep.equal([expected]); + }); + it('should pass correct currency if found in bid response', function () { const cur = 'EURO'; - let response = Object.assign({}, serverResponse); - let expected = Object.assign({}, expectedResponse); + const response = { ...serverResponse }; response.ad.cur = cur; + + const expected = { ...expectedResponse }; expected.currency = cur; + expect(spec.interpretResponse({ body: response }, bidRequest)).to.deep.equal([expected]); }); diff --git a/test/spec/modules/h12mediaBidAdapter_spec.js b/test/spec/modules/h12mediaBidAdapter_spec.js index 08a83ce981f..9861069f260 100644 --- a/test/spec/modules/h12mediaBidAdapter_spec.js +++ b/test/spec/modules/h12mediaBidAdapter_spec.js @@ -1,6 +1,7 @@ import {expect} from 'chai'; import {spec} from 'modules/h12mediaBidAdapter'; import {newBidder} from 'src/adapters/bidderFactory'; +import * as utils from 'src/utils'; describe('H12 Media Adapter', function () { const DEFAULT_CURRENCY = 'USD'; @@ -21,6 +22,7 @@ describe('H12 Media Adapter', function () { auctionId: '9adc85ed-43ee-4a78-816b-52b7e578f313', params: { pubid: 123321, + pubsubid: 'pubsubtestid', }, }; @@ -72,34 +74,34 @@ describe('H12 Media Adapter', function () { currency: 'EUR', netRevenue: true, ttl: 500, - bids: [{ + bid: { bidId: validBid.bidId, cpm: 0.33, width: 300, height: 600, creativeId: '335566', ad: '
my ad
', - usersync: [ - {url: 'https://cookiesync.3rdpartypartner.com/?3rdparty_partner_user_id={user_id}&partner_id=h12media&gdpr_applies={gdpr}&gdpr_consent_string={gdpr_cs}', type: 'image'}, - {url: 'https://cookiesync.3rdpartypartner.com/?3rdparty_partner_user_id={user_id}&partner_id=h12media&gdpr_applies={gdpr}&gdpr_consent_string={gdpr_cs}', type: 'iframe'} - ], meta: { advertiserId: '54321', advertiserName: 'My advertiser', advertiserDomains: ['test.com'] } - }] + }, + usersync: [ + {url: 'https://cookiesync.3rdpartypartner.com/?3rdparty_partner_user_id={user_id}&partner_id=h12media&gdpr_applies={gdpr}&gdpr_consent_string={gdpr_cs}', type: 'image'}, + {url: 'https://cookiesync.3rdpartypartner.com/?3rdparty_partner_user_id={user_id}&partner_id=h12media&gdpr_applies={gdpr}&gdpr_consent_string={gdpr_cs}', type: 'iframe'} + ], }; const serverResponse2 = { - bids: [{ + bid: { bidId: validBid2.bidId, cpm: 0.33, width: 300, height: 600, creativeId: '335566', ad: '
my ad 2
', - }] + } }; function removeElement(id) { @@ -152,6 +154,10 @@ describe('H12 Media Adapter', function () { beforeEach(function () { sandbox = sinon.sandbox.create(); + sandbox.stub(frameElement, 'getBoundingClientRect').returns({ + left: 10, + top: 10, + }); }); afterEach(function () { @@ -186,36 +192,62 @@ describe('H12 Media Adapter', function () { createElementVisible(validBid.adUnitCode); createElementVisible(validBid2.adUnitCode); const requests = spec.buildRequests([validBid, validBid2], bidderRequest); - const requestsData = requests.data; + const requestsData = requests[0].data.bidrequest; - expect(requestsData.bidrequests[0]).to.include({adunitSize: validBid.mediaTypes.banner.sizes}); + expect(requestsData).to.include({adunitSize: validBid.mediaTypes.banner.sizes}); }); it('should return empty bid size', function () { createElementVisible(validBid.adUnitCode); createElementVisible(validBid2.adUnitCode); const requests = spec.buildRequests([validBid, validBid2], bidderRequest); - const requestsData = requests.data; + const requestsData2 = requests[1].data.bidrequest; + + expect(requestsData2).to.deep.include({adunitSize: []}); + }); + + it('should return pubsubid from params', function () { + createElementVisible(validBid.adUnitCode); + createElementVisible(validBid2.adUnitCode); + const requests = spec.buildRequests([validBid, validBid2], bidderRequest); + const requestsData = requests[0].data.bidrequest; + const requestsData2 = requests[1].data.bidrequest; + + expect(requestsData).to.include({pubsubid: 'pubsubtestid'}); + expect(requestsData2).to.include({pubsubid: ''}); + }); - expect(requestsData.bidrequests[1]).to.deep.include({adunitSize: []}); + it('should return empty for incorrect pubsubid from params', function () { + createElementVisible(validBid.adUnitCode); + createElementVisible(validBid2.adUnitCode); + const bidWithPub = {...validBid}; + bidWithPub.params.pubsubid = 'iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii'; // More than 32 chars + const requests = spec.buildRequests([bidWithPub], bidderRequest); + const requestsData = requests[0].data.bidrequest; + + expect(requestsData).to.include({pubsubid: ''}); }); it('should return bid size from params', function () { createElementVisible(validBid.adUnitCode); createElementVisible(validBid2.adUnitCode); const requests = spec.buildRequests([validBid, validBid2], bidderRequest); - const requestsData = requests.data; + const requestsData = requests[0].data.bidrequest; + const requestsData2 = requests[1].data.bidrequest; - expect(requestsData.bidrequests[1]).to.include({size: validBid2.params.size}); + expect(requestsData).to.include({size: ''}); + expect(requestsData2).to.include({size: validBid2.params.size}); }); it('should return GDPR info', function () { createElementVisible(validBid.adUnitCode); createElementVisible(validBid2.adUnitCode); const requests = spec.buildRequests([validBid, validBid2], bidderRequest); - const requestsData = requests.data; + const requestsData = requests[0].data; + const requestsData2 = requests[1].data; expect(requestsData).to.include({gdpr: true, gdpr_cs: bidderRequest.gdprConsent.consentString}); + expect(requestsData2).to.include({gdpr: true, gdpr_cs: bidderRequest.gdprConsent.consentString}); }); it('should not have error on empty GDPR', function () { @@ -223,9 +255,23 @@ describe('H12 Media Adapter', function () { createElementVisible(validBid2.adUnitCode); const bidderRequestWithoutGDRP = {...bidderRequest, gdprConsent: null}; const requests = spec.buildRequests([validBid, validBid2], bidderRequestWithoutGDRP); - const requestsData = requests.data; + const requestsData = requests[0].data; + const requestsData2 = requests[1].data; expect(requestsData).to.include({gdpr: false}); + expect(requestsData2).to.include({gdpr: false}); + }); + + it('should not have error on empty USP', function () { + createElementVisible(validBid.adUnitCode); + createElementVisible(validBid2.adUnitCode); + const bidderRequestWithoutUSP = {...bidderRequest, uspConsent: null}; + const requests = spec.buildRequests([validBid, validBid2], bidderRequestWithoutUSP); + const requestsData = requests[0].data; + const requestsData2 = requests[1].data; + + expect(requestsData).to.include({usp: false}); + expect(requestsData2).to.include({usp: false}); }); it('should create single POST', function () { @@ -233,7 +279,8 @@ describe('H12 Media Adapter', function () { createElementVisible(validBid2.adUnitCode); const requests = spec.buildRequests([validBid, validBid2], bidderRequest); - expect(requests.method).to.equal('POST'); + expect(requests[0].method).to.equal('POST'); + expect(requests[1].method).to.equal('POST'); }); }); @@ -241,42 +288,44 @@ describe('H12 Media Adapter', function () { it('should return coords', function () { createElementVisible(validBid.adUnitCode); const requests = spec.buildRequests([validBid], bidderRequest); - const requestsData = requests.data; + const requestsData = requests[0].data.bidrequest; - expect(requestsData.bidrequests[0]).to.deep.include({coords: {x: 10, y: 10}}); + expect(requestsData).to.deep.include({coords: {x: 10, y: 10}}); }); - it('should define not iframe', function () { + it('should define iframe', function () { createElementVisible(validBid.adUnitCode); createElementVisible(validBid2.adUnitCode); const requests = spec.buildRequests([validBid, validBid2], bidderRequest); - const requestsData = requests.data; + const requestsData = requests[0].data; + const requestsData2 = requests[1].data; - expect(requestsData).to.include({isiframe: false}); + expect(requestsData).to.include({isiframe: true}); + expect(requestsData2).to.include({isiframe: true}); }); it('should define visible element', function () { createElementVisible(validBid.adUnitCode); const requests = spec.buildRequests([validBid], bidderRequest); - const requestsData = requests.data; + const requestsData = requests[0].data.bidrequest; - expect(requestsData.bidrequests[0]).to.include({ishidden: false}); + expect(requestsData).to.include({ishidden: false}); }); it('should define invisible element', function () { createElementInvisible(validBid.adUnitCode); const requests = spec.buildRequests([validBid], bidderRequest); - const requestsData = requests.data; + const requestsData = requests[0].data.bidrequest; - expect(requestsData.bidrequests[0]).to.include({ishidden: true}); + expect(requestsData).to.include({ishidden: true}); }); it('should define hidden element', function () { createElementHidden(validBid.adUnitCode); const requests = spec.buildRequests([validBid], bidderRequest); - const requestsData = requests.data; + const requestsData = requests[0].data.bidrequest; - expect(requestsData.bidrequests[0]).to.include({ishidden: true}); + expect(requestsData).to.include({ishidden: true}); }); }); @@ -290,27 +339,27 @@ describe('H12 Media Adapter', function () { it('should return no bids if the response is empty', function () { const bidResponse = spec.interpretResponse({ body: [] }, { validBid }); - expect(bidResponse.length).to.equal(0); + expect(bidResponse).to.be.empty; }); it('should return valid bid responses', function () { createElementVisible(validBid.adUnitCode); createElementVisible(validBid2.adUnitCode); const request = spec.buildRequests([validBid, validBid2], bidderRequest); - const bidResponse = spec.interpretResponse({body: serverResponse}, request); + const bidResponse = spec.interpretResponse({body: serverResponse}, request[0]); expect(bidResponse[0]).to.deep.include({ requestId: validBid.bidId, - ad: serverResponse.bids[0].ad, + ad: serverResponse.bid.ad, mediaType: 'banner', - creativeId: serverResponse.bids[0].creativeId, - cpm: serverResponse.bids[0].cpm, - width: serverResponse.bids[0].width, - height: serverResponse.bids[0].height, + creativeId: serverResponse.bid.creativeId, + cpm: serverResponse.bid.cpm, + width: serverResponse.bid.width, + height: serverResponse.bid.height, currency: 'EUR', netRevenue: true, ttl: 500, - meta: serverResponse.bids[0].meta, + meta: serverResponse.bid.meta, pubid: validBid.params.pubid }); }); @@ -319,17 +368,17 @@ describe('H12 Media Adapter', function () { createElementVisible(validBid.adUnitCode); createElementVisible(validBid2.adUnitCode); const request = spec.buildRequests([validBid, validBid2], bidderRequest); - const bidResponse = spec.interpretResponse({body: serverResponse2}, request); + const bidResponse = spec.interpretResponse({body: serverResponse2}, request[0]); expect(bidResponse[0]).to.deep.include({ requestId: validBid2.bidId, - ad: serverResponse2.bids[0].ad, + ad: serverResponse2.bid.ad, mediaType: 'banner', - creativeId: serverResponse2.bids[0].creativeId, - cpm: serverResponse2.bids[0].cpm, - width: serverResponse2.bids[0].width, - height: serverResponse2.bids[0].height, - meta: serverResponse2.bids[0].meta, + creativeId: serverResponse2.bid.creativeId, + cpm: serverResponse2.bid.cpm, + width: serverResponse2.bid.width, + height: serverResponse2.bid.height, + meta: serverResponse2.bid.meta, pubid: validBid2.params.pubid, currency: DEFAULT_CURRENCY, netRevenue: DEFAULT_NET_REVENUE, diff --git a/test/spec/modules/id5IdSystem_spec.js b/test/spec/modules/id5IdSystem_spec.js index 845cf7fa010..aa09454d978 100644 --- a/test/spec/modules/id5IdSystem_spec.js +++ b/test/spec/modules/id5IdSystem_spec.js @@ -1,12 +1,14 @@ import { id5IdSubmodule, ID5_STORAGE_NAME, + ID5_PRIVACY_STORAGE_NAME, getFromLocalStorage, storeInLocalStorage, expDaysStr, nbCacheName, getNbFromCache, - storeNbInCache + storeNbInCache, + isInControlGroup } from 'modules/id5IdSystem.js'; import { init, requestBidsHook, setSubmoduleRegistry, coreStorage } from 'modules/userId/index.js'; import { config } from 'src/config.js'; @@ -26,16 +28,19 @@ describe('ID5 ID System', function() { const ID5_NB_STORAGE_NAME = nbCacheName(ID5_TEST_PARTNER_ID); const ID5_STORED_ID = 'storedid5id'; const ID5_STORED_SIGNATURE = '123456'; + const ID5_STORED_LINK_TYPE = 1; const ID5_STORED_OBJ = { 'universal_uid': ID5_STORED_ID, - 'signature': ID5_STORED_SIGNATURE + 'signature': ID5_STORED_SIGNATURE, + 'link_type': ID5_STORED_LINK_TYPE }; const ID5_RESPONSE_ID = 'newid5id'; const ID5_RESPONSE_SIGNATURE = 'abcdef'; + const ID5_RESPONSE_LINK_TYPE = 2; const ID5_JSON_RESPONSE = { 'universal_uid': ID5_RESPONSE_ID, 'signature': ID5_RESPONSE_SIGNATURE, - 'link_type': 0 + 'link_type': ID5_RESPONSE_LINK_TYPE }; function getId5FetchConfig(storageName = ID5_STORAGE_NAME, storageType = 'html5') { @@ -177,10 +182,10 @@ describe('ID5 ID System', function() { it('should call the ID5 server with pd field when pd config is set', function () { const pubData = 'b50ca08271795a8e7e4012813f23d505193d75c0f2e2bb99baa63aa822f66ed3'; - let config = getId5FetchConfig(); - config.params.pd = pubData; + let id5Config = getId5FetchConfig(); + id5Config.params.pd = pubData; - let submoduleCallback = id5IdSubmodule.getId(config, undefined, ID5_STORED_OBJ).callback; + let submoduleCallback = id5IdSubmodule.getId(id5Config, undefined, ID5_STORED_OBJ).callback; submoduleCallback(callbackSpy); let request = server.requests[0]; @@ -191,10 +196,10 @@ describe('ID5 ID System', function() { }); it('should call the ID5 server with empty pd field when pd config is not set', function () { - let config = getId5FetchConfig(); - config.params.pd = undefined; + let id5Config = getId5FetchConfig(); + id5Config.params.pd = undefined; - let submoduleCallback = id5IdSubmodule.getId(config, undefined, ID5_STORED_OBJ).callback; + let submoduleCallback = id5IdSubmodule.getId(id5Config, undefined, ID5_STORED_OBJ).callback; submoduleCallback(callbackSpy); let request = server.requests[0]; @@ -233,6 +238,74 @@ describe('ID5 ID System', function() { expect(getNbFromCache(ID5_TEST_PARTNER_ID)).to.be.eq(0); }); + + it('should call the ID5 server with ab feature = 1 when abTesting is turned on', function () { + let id5Config = getId5FetchConfig(); + id5Config.params.abTesting = { enabled: true, controlGroupPct: 10 } + + let submoduleCallback = id5IdSubmodule.getId(id5Config, undefined, ID5_STORED_OBJ).callback; + submoduleCallback(callbackSpy); + + let request = server.requests[0]; + let requestBody = JSON.parse(request.requestBody); + expect(requestBody.features.ab).to.eq(1); + + request.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); + }); + + it('should call the ID5 server without ab feature when abTesting is turned off', function () { + let id5Config = getId5FetchConfig(); + id5Config.params.abTesting = { enabled: false, controlGroupPct: 10 } + + let submoduleCallback = id5IdSubmodule.getId(id5Config, undefined, ID5_STORED_OBJ).callback; + submoduleCallback(callbackSpy); + + let request = server.requests[0]; + let requestBody = JSON.parse(request.requestBody); + expect(requestBody.features).to.be.undefined; + + request.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); + }); + + it('should call the ID5 server without ab feature when when abTesting is not set', function () { + let id5Config = getId5FetchConfig(); + + let submoduleCallback = id5IdSubmodule.getId(id5Config, undefined, ID5_STORED_OBJ).callback; + submoduleCallback(callbackSpy); + + let request = server.requests[0]; + let requestBody = JSON.parse(request.requestBody); + expect(requestBody.features).to.be.undefined; + + request.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); + }); + + it('should store the privacy object from the ID5 server response', function () { + let submoduleCallback = id5IdSubmodule.getId(getId5FetchConfig(), undefined, ID5_STORED_OBJ).callback; + submoduleCallback(callbackSpy); + + let request = server.requests[0]; + + let responseObject = utils.deepClone(ID5_JSON_RESPONSE); + responseObject.privacy = { + jurisdiction: 'gdpr', + id5_consent: true + }; + request.respond(200, responseHeader, JSON.stringify(responseObject)); + expect(getFromLocalStorage(ID5_PRIVACY_STORAGE_NAME)).to.be.eq(JSON.stringify(responseObject.privacy)); + coreStorage.removeDataFromLocalStorage(ID5_PRIVACY_STORAGE_NAME); + }); + + it('should not store a privacy object if not part of ID5 server response', function () { + coreStorage.removeDataFromLocalStorage(ID5_PRIVACY_STORAGE_NAME); + let submoduleCallback = id5IdSubmodule.getId(getId5FetchConfig(), undefined, ID5_STORED_OBJ).callback; + submoduleCallback(callbackSpy); + + let request = server.requests[0]; + + request.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); + expect(getFromLocalStorage(ID5_PRIVACY_STORAGE_NAME)).to.be.null; + }); }); describe('Request Bids Hook', function() { @@ -266,10 +339,13 @@ describe('ID5 ID System', function() { expect(bid.userId.id5id.uid).to.equal(ID5_STORED_ID); expect(bid.userIdAsEids[0]).to.deep.equal({ source: ID5_SOURCE, - uids: [{ id: ID5_STORED_ID, atype: 1 }], - ext: { - linkType: 0 - } + uids: [{ + id: ID5_STORED_ID, + atype: 1, + ext: { + linkType: ID5_STORED_LINK_TYPE + } + }] }); }); }); @@ -306,7 +382,7 @@ describe('ID5 ID System', function() { config.setConfig(getFetchLocalStorageConfig()); let innerAdUnits; - requestBidsHook((config) => { innerAdUnits = config.adUnits }, {adUnits}); + requestBidsHook((adUnitConfig) => { innerAdUnits = adUnitConfig.adUnits }, {adUnits}); expect(getNbFromCache(ID5_TEST_PARTNER_ID)).to.be.eq(1); }); @@ -320,7 +396,7 @@ describe('ID5 ID System', function() { config.setConfig(getFetchLocalStorageConfig()); let innerAdUnits; - requestBidsHook((config) => { innerAdUnits = config.adUnits }, {adUnits}); + requestBidsHook((adUnitConfig) => { innerAdUnits = adUnitConfig.adUnits }, {adUnits}); expect(getNbFromCache(ID5_TEST_PARTNER_ID)).to.be.eq(2); }); @@ -338,7 +414,7 @@ describe('ID5 ID System', function() { config.setConfig(id5Config); let innerAdUnits; - requestBidsHook((config) => { innerAdUnits = config.adUnits }, {adUnits}); + requestBidsHook((adUnitConfig) => { innerAdUnits = adUnitConfig.adUnits }, {adUnits}); expect(getNbFromCache(ID5_TEST_PARTNER_ID)).to.be.eq(2); @@ -360,13 +436,199 @@ describe('ID5 ID System', function() { }); describe('Decode stored object', function() { - const expectedDecodedObject = { id5id: { uid: ID5_STORED_ID, ext: { linkType: 0 } } }; + const expectedDecodedObject = { id5id: { uid: ID5_STORED_ID, ext: { linkType: ID5_STORED_LINK_TYPE } } }; it('should properly decode from a stored object', function() { - expect(id5IdSubmodule.decode(ID5_STORED_OBJ)).to.deep.equal(expectedDecodedObject); + expect(id5IdSubmodule.decode(ID5_STORED_OBJ, getId5FetchConfig())).to.deep.equal(expectedDecodedObject); }); it('should return undefined if passed a string', function() { - expect(id5IdSubmodule.decode('somestring')).to.eq(undefined); + expect(id5IdSubmodule.decode('somestring', getId5FetchConfig())).to.eq(undefined); + }); + }); + + describe('A/B Testing', function() { + const expectedDecodedObjectWithIdAbOff = { id5id: { uid: ID5_STORED_ID, ext: { linkType: ID5_STORED_LINK_TYPE } } }; + const expectedDecodedObjectWithIdAbOn = { id5id: { uid: ID5_STORED_ID, ext: { linkType: ID5_STORED_LINK_TYPE, abTestingControlGroup: false } } }; + const expectedDecodedObjectWithoutIdAbOn = { id5id: { uid: '', ext: { linkType: 0, abTestingControlGroup: true } } }; + let testConfig; + + beforeEach(function() { + testConfig = getId5FetchConfig(); + }); + + describe('Configuration Validation', function() { + let logErrorSpy; + let logInfoSpy; + + beforeEach(function() { + logErrorSpy = sinon.spy(utils, 'logError'); + logInfoSpy = sinon.spy(utils, 'logInfo'); + }); + afterEach(function() { + logErrorSpy.restore(); + logInfoSpy.restore(); + }); + + // A/B Testing ON, but invalid config + let testInvalidAbTestingConfigsWithError = [ + { enabled: true }, + { enabled: true, controlGroupPct: 2 }, + { enabled: true, controlGroupPct: -1 }, + { enabled: true, controlGroupPct: 'a' }, + { enabled: true, controlGroupPct: true } + ]; + testInvalidAbTestingConfigsWithError.forEach((testAbTestingConfig) => { + it('should be undefined if ratio is invalid', () => { + expect(isInControlGroup('userId', testAbTestingConfig.controlGroupPct)).to.be.undefined; + }); + it('should error if config is invalid, and always return an ID', function () { + testConfig.params.abTesting = testAbTestingConfig; + let decoded = id5IdSubmodule.decode(ID5_STORED_OBJ, testConfig); + expect(decoded).to.deep.equal(expectedDecodedObjectWithIdAbOn); + sinon.assert.calledOnce(logErrorSpy); + }); + }); + + // A/B Testing OFF, with invalid config (ignore) + let testInvalidAbTestingConfigsWithoutError = [ + { enabled: false, controlGroupPct: -1 }, + { enabled: false, controlGroupPct: 2 }, + { enabled: false, controlGroupPct: 'a' }, + { enabled: false, controlGroupPct: true } + ]; + testInvalidAbTestingConfigsWithoutError.forEach((testAbTestingConfig) => { + it('should be undefined if ratio is invalid', () => { + expect(isInControlGroup('userId', testAbTestingConfig.controlGroupPct)).to.be.undefined; + }); + it('should not error if config is invalid but A/B testing is off, and always return an ID', function () { + testConfig.params.abTesting = testAbTestingConfig; + let decoded = id5IdSubmodule.decode(ID5_STORED_OBJ, testConfig); + expect(decoded).to.deep.equal(expectedDecodedObjectWithIdAbOff); + sinon.assert.notCalled(logErrorSpy); + sinon.assert.notCalled(logInfoSpy); + }); + }); + + // A/B Testing ON, with valid config + let testValidConfigs = [ + { enabled: true, controlGroupPct: 0 }, + { enabled: true, controlGroupPct: 0.5 }, + { enabled: true, controlGroupPct: 1 } + ]; + testValidConfigs.forEach((testAbTestingConfig) => { + it('should not be undefined if ratio is valid', () => { + expect(isInControlGroup('userId', testAbTestingConfig.controlGroupPct)).to.not.be.undefined; + }); + it('should not error if config is valid', function () { + testConfig.params.abTesting = testAbTestingConfig; + id5IdSubmodule.decode(ID5_STORED_OBJ, testConfig); + sinon.assert.notCalled(logErrorSpy); + sinon.assert.calledOnce(logInfoSpy); + }); + }); + }); + + describe('A/B Testing Config is not Set', function() { + let randStub; + + beforeEach(function() { + randStub = sinon.stub(Math, 'random').callsFake(function() { + return 0; + }); + }); + afterEach(function () { + randStub.restore(); + }); + + it('should expose ID when A/B config is not set', function () { + let decoded = id5IdSubmodule.decode(ID5_STORED_OBJ, testConfig); + expect(decoded).to.deep.equal(expectedDecodedObjectWithIdAbOff); + }); + + it('should expose ID when A/B config is empty', function () { + testConfig.params.abTesting = { }; + + let decoded = id5IdSubmodule.decode(ID5_STORED_OBJ, testConfig); + expect(decoded).to.deep.equal(expectedDecodedObjectWithIdAbOff); + }); + }); + + describe('A/B Testing Config is Set', function() { + let randStub; + + beforeEach(function() { + randStub = sinon.stub(Math, 'random').callsFake(function() { + return 0.25; + }); + }); + afterEach(function () { + randStub.restore(); + }); + + describe('IsInControlGroup', function () { + it('Nobody is in a 0% control group', function () { + expect(isInControlGroup('dsdndskhsdks', 0)).to.be.false; + expect(isInControlGroup('3erfghyuijkm', 0)).to.be.false; + expect(isInControlGroup('', 0)).to.be.false; + expect(isInControlGroup(undefined, 0)).to.be.false; + }); + + it('Everybody is in a 100% control group', function () { + expect(isInControlGroup('dsdndskhsdks', 1)).to.be.true; + expect(isInControlGroup('3erfghyuijkm', 1)).to.be.true; + expect(isInControlGroup('', 1)).to.be.true; + expect(isInControlGroup(undefined, 1)).to.be.true; + }); + + it('Being in the control group must be consistant', function () { + const inControlGroup = isInControlGroup('dsdndskhsdks', 0.5); + expect(inControlGroup === isInControlGroup('dsdndskhsdks', 0.5)).to.be.true; + expect(inControlGroup === isInControlGroup('dsdndskhsdks', 0.5)).to.be.true; + expect(inControlGroup === isInControlGroup('dsdndskhsdks', 0.5)).to.be.true; + }); + + it('Control group ratio must be within a 10% error on a large sample', function () { + let nbInControlGroup = 0; + const sampleSize = 100; + for (let i = 0; i < sampleSize; i++) { + nbInControlGroup = nbInControlGroup + (isInControlGroup('R$*df' + i, 0.5) ? 1 : 0); + } + expect(nbInControlGroup).to.be.greaterThan(sampleSize / 2 - sampleSize / 10); + expect(nbInControlGroup).to.be.lessThan(sampleSize / 2 + sampleSize / 10); + }); + }); + + describe('Decode', function() { + it('should expose ID when A/B testing is off', function () { + testConfig.params.abTesting = { + enabled: false, + controlGroupPct: 0.5 + }; + + let decoded = id5IdSubmodule.decode(ID5_STORED_OBJ, testConfig); + expect(decoded).to.deep.equal(expectedDecodedObjectWithIdAbOff); + }); + + it('should expose ID when no one is in control group', function () { + testConfig.params.abTesting = { + enabled: true, + controlGroupPct: 0 + }; + + let decoded = id5IdSubmodule.decode(ID5_STORED_OBJ, testConfig); + expect(decoded).to.deep.equal(expectedDecodedObjectWithIdAbOn); + }); + + it('should not expose ID when everyone is in control group', function () { + testConfig.params.abTesting = { + enabled: true, + controlGroupPct: 1 + }; + + let decoded = id5IdSubmodule.decode(ID5_STORED_OBJ, testConfig); + expect(decoded).to.deep.equal(expectedDecodedObjectWithoutIdAbOn); + }); + }); }); }); }); diff --git a/test/spec/modules/idLibrary_spec.js b/test/spec/modules/idImportLibrary_spec.js similarity index 79% rename from test/spec/modules/idLibrary_spec.js rename to test/spec/modules/idImportLibrary_spec.js index da61850f29b..699c2c43a94 100644 --- a/test/spec/modules/idLibrary_spec.js +++ b/test/spec/modules/idImportLibrary_spec.js @@ -1,5 +1,5 @@ import * as utils from 'src/utils.js'; -import * as idlibrary from 'modules/idLibrary.js'; +import * as idImportlibrary from 'modules/idImportLibrary.js'; var expect = require('chai').expect; @@ -20,7 +20,7 @@ describe('currency', function () { utils.logInfo.restore(); utils.logError.restore(); fakeCurrencyFileServer.restore(); - idlibrary.setConfig({}); + idImportlibrary.setConfig({}); }); describe('setConfig', function () { @@ -35,27 +35,27 @@ describe('currency', function () { }); it('results when no config available', function () { - idlibrary.setConfig({}); + idImportlibrary.setConfig({}); sinon.assert.called(utils.logError); }); it('results with config available', function () { - idlibrary.setConfig({ 'url': 'URL' }); + idImportlibrary.setConfig({ 'url': 'URL' }); sinon.assert.called(utils.logInfo); }); it('results with config default debounce ', function () { let config = { 'url': 'URL' } - idlibrary.setConfig(config); + idImportlibrary.setConfig(config); expect(config.debounce).to.be.equal(250); }); it('results with config default fullscan ', function () { let config = { 'url': 'URL' } - idlibrary.setConfig(config); - expect(config.fullscan).to.be.equal(true); + idImportlibrary.setConfig(config); + expect(config.fullscan).to.be.equal(false); }); it('results with config fullscan ', function () { - let config = { 'url': 'URL', 'fullscan': false } - idlibrary.setConfig(config); - expect(config.fullscan).to.be.equal(false); + let config = { 'url': 'URL', 'fullscan': true } + idImportlibrary.setConfig(config); + expect(config.fullscan).to.be.equal(true); }); }); }); diff --git a/test/spec/modules/impactifyBidAdapter_spec.js b/test/spec/modules/impactifyBidAdapter_spec.js new file mode 100644 index 00000000000..d14cea8cad3 --- /dev/null +++ b/test/spec/modules/impactifyBidAdapter_spec.js @@ -0,0 +1,398 @@ +import { expect } from 'chai'; +import { spec } from 'modules/impactifyBidAdapter.js'; +import * as utils from 'src/utils.js'; + +const BIDDER_CODE = 'impactify'; +const BIDDER_ALIAS = ['imp']; +const DEFAULT_CURRENCY = 'USD'; +const DEFAULT_VIDEO_WIDTH = 640; +const DEFAULT_VIDEO_HEIGHT = 480; +const ORIGIN = 'https://sonic.impactify.media'; +const LOGGER_URI = 'https://logger.impactify.media'; +const AUCTIONURI = '/bidder'; +const COOKIESYNCURI = '/static/cookie_sync.html'; +const GVLID = 606; + +var gdprData = { + 'consentString': 'BOh7mtYOh7mtYAcABBENCU-AAAAncgPIXJiiAoao0PxBFkgCAC8ACIAAQAQQAAIAAAIAAAhBGAAAQAQAEQgAAAAAAABAAAAAAAAAAAAAAACAAAAAAAACgAAAAABAAAAQAAAAAAA', + 'gdprApplies': true +}; + +describe('ImpactifyAdapter', function () { + describe('isBidRequestValid', function () { + let validBid = { + bidder: 'impactify', + params: { + appId: '1', + format: 'screen', + style: 'inline' + } + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(validBid)).to.equal(true); + }); + + it('should return false when required params are not passed', function () { + let bid = Object.assign({}, validBid); + delete bid.params; + bid.params = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when appId is missing', () => { + const bid = utils.deepClone(validBid); + delete bid.params.appId; + + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when appId is not a string', () => { + const bid = utils.deepClone(validBid); + + bid.params.appId = 123; + expect(spec.isBidRequestValid(bid)).to.equal(false); + + bid.params.appId = false; + expect(spec.isBidRequestValid(bid)).to.equal(false); + + bid.params.appId = void (0); + expect(spec.isBidRequestValid(bid)).to.equal(false); + + bid.params.appId = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when format is missing', () => { + const bid = utils.deepClone(validBid); + delete bid.params.format; + + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when format is not a string', () => { + const bid = utils.deepClone(validBid); + + bid.params.format = 123; + expect(spec.isBidRequestValid(bid)).to.equal(false); + + bid.params.format = false; + expect(spec.isBidRequestValid(bid)).to.equal(false); + + bid.params.format = void (0); + expect(spec.isBidRequestValid(bid)).to.equal(false); + + bid.params.format = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when format is not equals to screen or display', () => { + const bid = utils.deepClone(validBid); + if (bid.params.format != 'screen' && bid.params.format != 'display') { + expect(spec.isBidRequestValid(bid)).to.equal(false); + } + }); + + it('should return false when style is missing', () => { + const bid = utils.deepClone(validBid); + delete bid.params.style; + + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when style is not a string', () => { + const bid = utils.deepClone(validBid); + + bid.params.style = 123; + expect(spec.isBidRequestValid(bid)).to.equal(false); + + bid.params.style = false; + expect(spec.isBidRequestValid(bid)).to.equal(false); + + bid.params.style = void (0); + expect(spec.isBidRequestValid(bid)).to.equal(false); + + bid.params.style = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + describe('buildRequests', function () { + let videoBidRequests = [ + { + bidder: 'impactify', + params: { + appId: '1', + format: 'screen', + style: 'inline' + }, + mediaTypes: { + video: { + context: 'instream' + } + }, + adUnitCode: 'adunit-code', + sizes: [[DEFAULT_VIDEO_WIDTH, DEFAULT_VIDEO_HEIGHT]], + bidId: '123456789', + bidderRequestId: '987654321', + auctionId: '19ab94a9-b0d7-4ed7-9f80-ad0c033cf1b1', + transactionId: 'f7b2c372-7a7b-11eb-9439-0242ac130002' + } + ]; + let videoBidderRequest = { + bidderRequestId: '98845765110', + auctionId: '165410516454', + bidderCode: 'impactify', + bids: [ + { + ...videoBidRequests[0] + } + ], + refererInfo: { + referer: 'https://impactify.io' + } + }; + + it('sends video bid request to ENDPOINT via POST', function () { + const request = spec.buildRequests(videoBidRequests, videoBidderRequest); + expect(request.url).to.equal(ORIGIN + AUCTIONURI); + expect(request.method).to.equal('POST'); + }); + }); + describe('interpretResponse', function () { + it('should get correct bid response', function () { + let response = { + id: '19ab94a9-b0d7-4ed7-9f80-ad0c033cf1b1', + seatbid: [ + { + bid: [ + { + id: '65820304700829014', + impid: '462c08f20d428', + price: 3.40, + adm: '', + adid: '97517771', + adomain: [ + '' + ], + iurl: 'https://fra1-ib.adnxs.com/cr?id=97517771', + cid: '9325', + crid: '97517771', + w: 1, + h: 1, + ext: { + prebid: { + 'type': 'video' + }, + bidder: { + prebid: { + type: 'video', + video: { + duration: 30, + primary_category: '' + } + }, + bidder: { + appnexus: { + brand_id: 182979, + auction_id: 8657683934873599656, + bidder_id: 2, + bid_ad_type: 1, + creative_info: { + video: { + duration: 30, + mimes: [ + 'video/x-flv', + 'video/mp4', + 'video/webm' + ] + } + } + } + } + } + } + } + ], + seat: 'impactify' + } + ], + cur: DEFAULT_CURRENCY, + ext: { + responsetimemillis: { + impactify: 114 + }, + prebid: { + auctiontimestamp: 1614587024591 + } + } + }; + let bidderRequest = { + bids: [ + { + bidId: '462c08f20d428', + adUnitCode: '/19968336/header-bid-tag-1', + auctionId: '19ab94a9-b0d7-4ed7-9f80-ad0c033cf1b1', + bidder: 'impactify', + sizes: [[DEFAULT_VIDEO_WIDTH, DEFAULT_VIDEO_HEIGHT]], + mediaTypes: { + video: { + context: 'outstream' + } + } + }, + ] + } + let expectedResponse = [ + { + id: '65820304700829014', + requestId: '462c08f20d428', + cpm: 3.40, + currency: DEFAULT_CURRENCY, + netRevenue: true, + ad: '', + width: 1, + height: 1, + hash: 'test', + expiry: 166192938, + ttl: 300, + creativeId: '97517771' + } + ]; + let result = spec.interpretResponse({ body: response }, bidderRequest); + expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); + }); + }); + describe('getUserSyncs', function () { + let videoBidRequests = [ + { + bidder: 'impactify', + params: { + appId: '1', + format: 'screen', + style: 'inline' + }, + mediaTypes: { + video: { + context: 'instream' + } + }, + adUnitCode: 'adunit-code', + sizes: [[DEFAULT_VIDEO_WIDTH, DEFAULT_VIDEO_HEIGHT]], + bidId: '123456789', + bidderRequestId: '987654321', + auctionId: '19ab94a9-b0d7-4ed7-9f80-ad0c033cf1b1', + transactionId: 'f7b2c372-7a7b-11eb-9439-0242ac130002' + } + ]; + let videoBidderRequest = { + bidderRequestId: '98845765110', + auctionId: '165410516454', + bidderCode: 'impactify', + bids: [ + { + ...videoBidRequests[0] + } + ], + refererInfo: { + referer: 'https://impactify.io' + } + }; + let validResponse = { + id: '19ab94a9-b0d7-4ed7-9f80-ad0c033cf1b1', + seatbid: [ + { + bid: [ + { + id: '65820304700829014', + impid: '462c08f20d428', + price: 3.40, + adm: '', + adid: '97517771', + adomain: [ + '' + ], + iurl: 'https://fra1-ib.adnxs.com/cr?id=97517771', + cid: '9325', + crid: '97517771', + w: 1, + h: 1, + ext: { + prebid: { + 'type': 'video' + }, + bidder: { + prebid: { + type: 'video', + video: { + duration: 30, + primary_category: '' + } + }, + bidder: { + appnexus: { + brand_id: 182979, + auction_id: 8657683934873599656, + bidder_id: 2, + bid_ad_type: 1, + creative_info: { + video: { + duration: 30, + mimes: [ + 'video/x-flv', + 'video/mp4', + 'video/webm' + ] + } + } + } + } + } + } + } + ], + seat: 'impactify' + } + ], + cur: DEFAULT_CURRENCY, + ext: { + responsetimemillis: { + impactify: 114 + }, + prebid: { + auctiontimestamp: 1614587024591 + } + } + }; + it('should return empty response if server response is false', function () { + const result = spec.getUserSyncs('bad', false, gdprData); + expect(result).to.be.empty; + }); + it('should return empty response if server response is empty', function () { + const result = spec.getUserSyncs('bad', [], gdprData); + expect(result).to.be.empty; + }); + it('should append the various values if they exist', function() { + const result = spec.getUserSyncs({iframeEnabled: true}, validResponse, gdprData); + expect(result[0].url).to.include('gdpr=1'); + expect(result[0].url).to.include('gdpr_consent=BOh7mtYOh7mtYAcABBENCU-AAAAncgPIXJiiAoao0PxBFkgCAC8ACIAAQAQQAAIAAAIAAAhBGAAAQAQAEQgAAAAAAABAAAAAAAAAAAAAAACAAAAAAAACgAAAAABAAAAQAAAAAAA'); + }); + }); + + describe('On winning bid', function () { + const bid = { + ad: '', + cpm: '2' + }; + const result = spec.onBidWon(bid); + assert.ok(result); + }); + + describe('On bid Time out', function () { + const bid = { + ad: '', + cpm: '2' + }; + const result = spec.onTimeout(bid); + assert.ok(result); + }); +}) diff --git a/test/spec/modules/improvedigitalBidAdapter_spec.js b/test/spec/modules/improvedigitalBidAdapter_spec.js index 89ec5aed8c3..f34a75ef8f3 100644 --- a/test/spec/modules/improvedigitalBidAdapter_spec.js +++ b/test/spec/modules/improvedigitalBidAdapter_spec.js @@ -28,6 +28,12 @@ describe('Improve Digital Adapter Tests', function () { sizes: [[300, 250], [160, 600], ['blah', 150], [-1, 300], [300, -5]] }; + const videoParams = { + skip: 1, + skipmin: 5, + skipafter: 30 + } + const instreamBidRequest = utils.deepClone(simpleBidRequest); instreamBidRequest.mediaTypes = { video: { @@ -280,14 +286,23 @@ describe('Improve Digital Adapter Tests', function () { expect(params.bid_request.referrer).to.equal('https://blah.com/test.html'); }); + it('should not add video params for banner', function () { + const bidRequest = JSON.parse(JSON.stringify(simpleBidRequest)); + bidRequest.params.video = videoParams; + const request = spec.buildRequests([bidRequest], bidderRequest)[0]; + const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); + expect(params.bid_request.imp[0].video).to.not.exist; + }); + it('should add ad type for instream video', function () { - let bidRequest = Object.assign({}, simpleBidRequest); + let bidRequest = JSON.parse(JSON.stringify(simpleBidRequest)); bidRequest.mediaType = 'video'; let request = spec.buildRequests([bidRequest], bidderRequest)[0]; let params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); expect(params.bid_request.imp[0].ad_types).to.deep.equal(['video']); + expect(params.bid_request.imp[0].video).to.not.exist; - bidRequest = Object.assign({}, simpleBidRequest); + bidRequest = JSON.parse(JSON.stringify(simpleBidRequest)); bidRequest.mediaTypes = { video: { context: 'instream', @@ -297,18 +312,89 @@ describe('Improve Digital Adapter Tests', function () { request = spec.buildRequests([bidRequest], bidderRequest)[0]; params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); expect(params.bid_request.imp[0].ad_types).to.deep.equal(['video']); + expect(params.bid_request.imp[0].video).to.not.exist; }); it('should not set ad type for outstream video', function() { const request = spec.buildRequests([outstreamBidRequest])[0]; const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); expect(params.bid_request.imp[0].ad_types).to.not.exist; + expect(params.bid_request.imp[0].video).to.not.exist; }); it('should not set ad type for multi-format bids', function() { const request = spec.buildRequests([multiFormatBidRequest], bidderRequest)[0]; const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); expect(params.bid_request.imp[0].ad_types).to.not.exist; + expect(params.bid_request.imp[0].video).to.not.exist; + }); + + it('should set video params for instream', function() { + const bidRequest = JSON.parse(JSON.stringify(instreamBidRequest)); + bidRequest.params.video = videoParams; + const request = spec.buildRequests([bidRequest])[0]; + const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); + expect(params.bid_request.imp[0].video).to.deep.equal(videoParams); + }); + + it('should set skip params only if skip=1', function() { + const bidRequest = JSON.parse(JSON.stringify(instreamBidRequest)); + // 1 + const videoTest = { + skip: 1, + skipmin: 5, + skipafter: 30 + } + bidRequest.params.video = videoTest; + let request = spec.buildRequests([bidRequest])[0]; + let params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); + expect(params.bid_request.imp[0].video).to.deep.equal(videoTest); + + // 0 - leave out skipmin and skipafter + videoTest.skip = 0; + bidRequest.params.video = videoTest; + request = spec.buildRequests([bidRequest])[0]; + params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); + expect(params.bid_request.imp[0].video).to.deep.equal({ skip: 0 }); + + // other + videoTest.skip = 'blah'; + bidRequest.params.video = videoTest; + request = spec.buildRequests([bidRequest])[0]; + params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); + expect(params.bid_request.imp[0].video).to.not.exist; + }); + + it('should ignore invalid/unexpected video params', function() { + const bidRequest = JSON.parse(JSON.stringify(instreamBidRequest)); + // 1 + const videoTest = { + skip: 1, + skipmin: 5, + skipafter: 30 + } + const videoTestInvParam = Object.assign({}, videoTest); + videoTestInvParam.blah = 1; + bidRequest.params.video = videoTestInvParam; + let request = spec.buildRequests([bidRequest])[0]; + let params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); + expect(params.bid_request.imp[0].video).to.deep.equal(videoTest); + }); + + it('should set video params for outstream', function() { + const bidRequest = JSON.parse(JSON.stringify(outstreamBidRequest)); + bidRequest.params.video = videoParams; + const request = spec.buildRequests([bidRequest])[0]; + const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); + expect(params.bid_request.imp[0].video).to.deep.equal(videoParams); + }); + + it('should set video params for multi-format', function() { + const bidRequest = JSON.parse(JSON.stringify(multiFormatBidRequest)); + bidRequest.params.video = videoParams; + const request = spec.buildRequests([bidRequest])[0]; + const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); + expect(params.bid_request.imp[0].video).to.deep.equal(videoParams); }); it('should not set Prebid sizes in bid request for instream video', function () { @@ -664,7 +750,6 @@ describe('Improve Digital Adapter Tests', function () { const expectedBid = [ { 'ad': '', - 'adId': '33e9500b21129f', 'creativeId': '422031', 'cpm': 1.45888594164456, 'currency': 'USD', @@ -681,7 +766,6 @@ describe('Improve Digital Adapter Tests', function () { expectedBid[0], { 'ad': '', - 'adId': '1234', 'creativeId': '422033', 'cpm': 1.23, 'currency': 'USD', @@ -697,7 +781,6 @@ describe('Improve Digital Adapter Tests', function () { const expectedBidNative = [ { mediaType: 'native', - adId: '33e9500b21129f', creativeId: '422031', cpm: 1.45888594164456, currency: 'USD', @@ -746,7 +829,6 @@ describe('Improve Digital Adapter Tests', function () { const expectedBidInstreamVideo = [ { 'vastXml': '', - 'adId': '33e9500b21129f', 'creativeId': '422031', 'cpm': 1.45888594164456, 'currency': 'USD', diff --git a/test/spec/modules/inmarBidAdapter_spec.js b/test/spec/modules/inmarBidAdapter_spec.js index 86b7ab3a8af..998fe20d369 100644 --- a/test/spec/modules/inmarBidAdapter_spec.js +++ b/test/spec/modules/inmarBidAdapter_spec.js @@ -17,7 +17,6 @@ describe('Inmar adapter tests', function () { }, bidder: 'inmar', params: { - adnetId: 'ADb1f40rmi', partnerId: 12345 }, auctionId: '0cb3144c-d084-4686-b0d6-f5dbe917c563', @@ -39,7 +38,6 @@ describe('Inmar adapter tests', function () { }, bidder: 'inmar', params: { - adnetId: 'ADb1f40rmi', partnerId: 12345 }, auctionId: '0cb3144c-d084-4686-b0d6-f5dbe917c563', @@ -60,7 +58,6 @@ describe('Inmar adapter tests', function () { ], bidder: 'inmar', params: { - adnetId: 'ADb1f40rmi', partnerId: 12345, }, auctionId: '851adee7-d843-48f9-a7e9-9ff00573fcbf', @@ -118,7 +115,6 @@ describe('Inmar adapter tests', function () { expect(request).to.have.property('method').and.to.equal('POST'); const requestContent = JSON.parse(request.data); - expect(requestContent.bidRequests[0].params).to.have.property('adnetId').and.to.equal('ADb1f40rmi'); expect(requestContent.bidRequests[0].params).to.have.property('partnerId').and.to.equal(12345); expect(requestContent.bidRequests[0]).to.have.property('auctionId').and.to.equal('0cb3144c-d084-4686-b0d6-f5dbe917c563'); expect(requestContent.bidRequests[0]).to.have.property('bidId').and.to.equal('2c7c8e9c900244'); @@ -198,19 +194,12 @@ describe('Inmar adapter tests', function () { })).to.equal(false); expect(spec.isBidRequestValid({ params: { - adnetId: 'ADb1f40rmi' } })).to.equal(false); expect(spec.isBidRequestValid({ params: { partnerId: 12345 } - })).to.equal(false); - expect(spec.isBidRequestValid({ - params: { - adnetId: 'ADb1f40rmi', - partnerId: 12345 - } })).to.equal(true); }); diff --git a/test/spec/modules/inskinBidAdapter_spec.js b/test/spec/modules/inskinBidAdapter_spec.js index e817b3e3b81..cedaff2a0cf 100644 --- a/test/spec/modules/inskinBidAdapter_spec.js +++ b/test/spec/modules/inskinBidAdapter_spec.js @@ -250,7 +250,7 @@ describe('InSkin BidAdapter', function () { const payload = JSON.parse(request.data); expect(payload.keywords).to.be.an('array').that.is.empty; - expect(payload.placements[0].properties).to.be.undefined; + expect(payload.placements[0].properties.restrictions).to.be.undefined; }); it('should add keywords if TCF v2 purposes are not granted', function () { diff --git a/test/spec/modules/instreamTracking_spec.js b/test/spec/modules/instreamTracking_spec.js index 8c49da76ab6..8d795fec88b 100644 --- a/test/spec/modules/instreamTracking_spec.js +++ b/test/spec/modules/instreamTracking_spec.js @@ -1,7 +1,7 @@ import { assert } from 'chai'; import { trackInstreamDeliveredImpressions } from 'modules/instreamTracking.js'; import { config } from 'src/config.js'; -import * as events from 'src/events.js'; +import events from 'src/events.js'; import * as utils from 'src/utils.js'; import * as sinon from 'sinon'; import { INSTREAM, OUTSTREAM } from 'src/video.js'; diff --git a/test/spec/modules/interactiveOffersBidAdapter_spec.js b/test/spec/modules/interactiveOffersBidAdapter_spec.js new file mode 100644 index 00000000000..8826977f34a --- /dev/null +++ b/test/spec/modules/interactiveOffersBidAdapter_spec.js @@ -0,0 +1,42 @@ +import { expect } from 'chai'; +import {spec} from 'modules/interactiveOffersBidAdapter.js'; + +describe('Interactive Offers Prebbid.js Adapter', function() { + describe('isBidRequestValid function', function() { + let bid = {bidder: 'interactiveOffers', params: {pubid: 100, tmax: 300}, mediaTypes: {banner: {sizes: [[300, 250]]}}, adUnitCode: 'pageAd01', transactionId: '16526f30-3be2-43f6-ab37-f1ab1f2ac25d', sizes: [[300, 250]], bidId: '227faa83f86546', bidderRequestId: '1eb79bc9dd44a', auctionId: '1aad860c-e04b-482b-acac-0da55ed491c8', src: 'client', bidRequestsCount: 1, bidderRequestsCount: 1, bidderWinsCount: 0}; + + it('returns true if all the required params are present and properly formatted', function() { + expect(spec.isBidRequestValid(bid)).to.be.true; + }); + + it('returns false if any if the required params is missing', function() { + bid.params = {}; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + + it('returns false if any if the required params is not properly formatted', function() { + bid.params = {pubid: 'abcd123', tmax: 250}; + expect(spec.isBidRequestValid(bid)).to.be.false; + bid.params = {pubid: 100, tmax: '+250'}; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + }); + describe('buildRequests function', function() { + let validBidRequests = [{bidder: 'interactiveOffers', params: {pubid: 100, tmax: 300}, mediaTypes: {banner: {sizes: [[300, 250]]}}, adUnitCode: 'pageAd01', transactionId: '16526f30-3be2-43f6-ab37-f1ab1f2ac25d', sizes: [[300, 250]], bidId: '227faa83f86546', bidderRequestId: '1eb79bc9dd44a', auctionId: '1aad860c-e04b-482b-acac-0da55ed491c8', src: 'client', bidRequestsCount: 1, bidderRequestsCount: 1, bidderWinsCount: 0}]; + let bidderRequest = {bidderCode: 'interactiveOffers', auctionId: '1aad860c-e04b-482b-acac-0da55ed491c8', bidderRequestId: '1eb79bc9dd44a', bids: [{bidder: 'interactiveOffers', params: {pubid: 100, tmax: 300}, mediaTypes: {banner: {sizes: [[300, 250]]}}, adUnitCode: 'pageAd01', transactionId: '16526f30-3be2-43f6-ab37-f1ab1f2ac25d', sizes: [[300, 250]], bidId: '227faa83f86546', bidderRequestId: '1eb79bc9dd44a', auctionId: '1aad860c-e04b-482b-acac-0da55ed491c8', src: 'client', bidRequestsCount: 1, bidderRequestsCount: 1, bidderWinsCount: 0}], timeout: 5000, refererInfo: {referer: 'http://www.google.com', reachedTop: true, isAmp: false, numIframes: 0, stack: ['http://www.google.com'], canonicalUrl: null}}; + + it('returns a Prebid.js request object with a valid json string at the "data" property', function() { + let request = spec.buildRequests(validBidRequests, bidderRequest); + expect(request.data).length !== 0; + }); + }); + describe('interpretResponse function', function() { + let openRTBResponse = {body: [{cur: 'USD', id: '2052afa35febb79baa9893cc3ae8b83b89740df65fe98b1bd358dbae6e912801', seatbid: [{seat: 1493, bid: [{ext: {tagid: '227faa83f86546'}, crid: '24477', adm: '', nurl: '', adid: '1138', adomain: ['url.com'], price: '1.53', w: 300, h: 250, iurl: 'http://url.com', cat: ['IAB13-11'], id: '5507ced7a39c06942d3cb260197112ba712e4180', attr: [], impid: 1, cid: '13280'}]}], 'bidid': '0959b9d58ba71b3db3fa29dce3b117c01fc85de0'}], 'headers': {}}; + let prebidRequest = {method: 'POST', url: 'https://url.com', data: '{"id": "1aad860c-e04b-482b-acac-0da55ed491c8", "site": {"id": "url.com", "name": "url.com", "domain": "url.com", "page": "http://url.com", "ref": "http://url.com", "publisher": {"id": 100, "name": "http://url.com", "domain": "url.com"}, "content": {"language": "pt-PT"}}, "source": {"fd": 0, "tid": "1aad860c-e04b-482b-acac-0da55ed491c8", "pchain": ""}, "device": {"ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.192 Safari/537.36", "language": "pt-PT"}, "user": {}, "imp": [{"id":1, "secure": 0, "tagid": "227faa83f86546", "banner": {"pos": 0, "w": 300, "h": 250, "format": [{"w": 300, "h": 250}]}}], "tmax": 300}', bidderRequest: {bidderCode: 'interactiveOffers', auctionId: '1aad860c-e04b-482b-acac-0da55ed491c8', bidderRequestId: '1eb79bc9dd44a', bids: [{bidder: 'interactiveOffers', params: {pubid: 100, tmax: 300}, mediaTypes: {banner: {sizes: [[300, 250]]}}, adUnitCode: 'pageAd01', transactionId: '16526f30-3be2-43f6-ab37-f1ab1f2ac25d', sizes: [[300, 250]], bidId: '227faa83f86546', bidderRequestId: '1eb79bc9dd44a', auctionId: '1aad860c-e04b-482b-acac-0da55ed491c8', src: 'client', bidRequestsCount: 1, bidderRequestsCount: 1, bidderWinsCount: 0}], timeout: 5000, refererInfo: {referer: 'http://url.com', reachedTop: true, isAmp: false, numIframes: 0, stack: ['http://url.com'], canonicalUrl: null}}}; + + it('returns an array of Prebid.js response objects', function() { + let prebidResponses = spec.interpretResponse(openRTBResponse, prebidRequest); + expect(prebidResponses).to.not.be.empty; + }); + }); +}); diff --git a/test/spec/modules/invibesBidAdapter_spec.js b/test/spec/modules/invibesBidAdapter_spec.js index 442039504f8..ee3624b5b16 100644 --- a/test/spec/modules/invibesBidAdapter_spec.js +++ b/test/spec/modules/invibesBidAdapter_spec.js @@ -22,7 +22,11 @@ describe('invibesBidAdapter:', function () { [400, 300], [125, 125] ], - transactionId: 't1' + transactionId: 't1', + userId: { + pubcid: 'pub-cid-code', + pubProvidedId: 'pub-provided-id' + } }, { bidId: 'b2', bidder: BIDDER_CODE, @@ -284,6 +288,45 @@ describe('invibesBidAdapter:', function () { expect(request.data.purposes.split(',')[9]).to.equal('true'); }); + it('should send legitimateInterests 2 & 7', function () { + let bidderRequest = { + gdprConsent: { + vendorData: { + gdprApplies: true, + hasGlobalConsent: false, + vendor: {consents: {436: true}}, + purpose: { + consents: { + 1: true, + 2: true, + 3: true, + 4: true, + 5: true, + 6: true, + 7: true, + 8: true, + 9: true, + 10: true + }, + legitimateInterests: { + 1: true, + 2: true, + 3: true, + 4: true, + 5: true, + 6: true, + 7: true, + 8: true, + 9: true, + 10: true + } + } + } + } + }; + let request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.li.split(',')[1] && request.data.li.split(',')[6]).to.equal('true'); + }); it('should send oi = 0 when vendorData is null', function () { let bidderRequest = { gdprConsent: { @@ -321,7 +364,6 @@ describe('invibesBidAdapter:', function () { let request = spec.buildRequests(bidRequests, bidderRequest); expect(request.data.oi).to.equal(2); }); - it('should send oi = 0 when vendor consents for invibes are false on tcf v2', function () { let bidderRequest = { gdprConsent: { @@ -349,6 +391,74 @@ describe('invibesBidAdapter:', function () { let request = spec.buildRequests(bidRequests, bidderRequest); expect(request.data.oi).to.equal(0); }); + it('should send oi = 2 when vendor consent for invibes are false and vendor legitimate interest for invibes are true on tcf v2', function () { + let bidderRequest = { + gdprConsent: { + vendorData: { + gdprApplies: true, + hasGlobalConsent: false, + vendor: {consents: {436: false}, legitimateInterests: {436: true}}, + purpose: { + consents: { + 1: true, + 2: true, + 3: true, + 4: true, + 5: true, + 6: true, + 7: true, + 8: true, + 9: true, + 10: true + } + } + } + } + }; + let request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.oi).to.equal(2); + }); + it('should send oi = 0 when vendor consents and legitimate interests for invibes are false on tcf v2', function () { + let bidderRequest = { + gdprConsent: { + vendorData: { + gdprApplies: true, + hasGlobalConsent: false, + vendor: {consents: {436: false}, legitimateInterests: {436: false}}, + purpose: { + consents: { + 1: true, + 2: true, + 3: true, + 4: true, + 5: true, + 6: true, + 7: true, + 8: true, + 9: true, + 10: true + } + } + } + } + }; + let request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.oi).to.equal(0); + }); + it('should send oi = 0 when purpose consents is null', function () { + let bidderRequest = { + gdprConsent: { + vendorData: { + gdprApplies: true, + hasGlobalConsent: false, + vendor: {consents: {436: false}}, + purpose: {} + } + } + }; + let request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.oi).to.equal(0); + }); it('should send oi = 2 when purpose consents weren\'t approved on tcf v2', function () { let bidderRequest = { diff --git a/test/spec/modules/ipromBidAdapter_spec.js b/test/spec/modules/ipromBidAdapter_spec.js new file mode 100644 index 00000000000..535cc332b21 --- /dev/null +++ b/test/spec/modules/ipromBidAdapter_spec.js @@ -0,0 +1,193 @@ +import {expect} from 'chai'; +import {spec} from 'modules/ipromBidAdapter.js'; + +describe('iPROM Adapter', function () { + let bidRequests; + let bidderRequest; + + beforeEach(function () { + bidRequests = [ + { + bidder: 'iprom', + params: { + id: '1234', + dimension: '300x250', + }, + adUnitCode: '/19966331/header-bid-tag-1', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]], + } + }, + bidId: '29a72b151f7bd3', + auctionId: 'e36abb27-g3b1-1ad6-8a4c-701c8919d3hh', + bidderRequestId: '2z76da40m1b3cb8', + transactionId: 'j51lhf58-1ad6-g3b1-3j6s-912c9493g0gu' + } + ]; + + bidderRequest = { + timeout: 3000, + refererInfo: { + referer: 'https://adserver.si/index.html', + reachedTop: true, + numIframes: 1, + stack: [ + 'https://adserver.si/index.html', + 'https://adserver.si/iframe1.html', + ] + } + } + }); + + describe('validating bids', function () { + it('should accept valid bid', function () { + let validBid = { + bidder: 'iprom', + params: { + id: '1234', + dimension: '300x250', + }, + }; + + const isValid = spec.isBidRequestValid(validBid); + + expect(isValid).to.equal(true); + }); + + it('should reject bid if missing dimension and id', function () { + let invalidBid = { + bidder: 'iprom', + params: {} + }; + + const isValid = spec.isBidRequestValid(invalidBid); + + expect(isValid).to.equal(false); + }); + + it('should reject bid if missing dimension', function () { + let invalidBid = { + bidder: 'iprom', + params: { + id: '1234', + } + }; + + const isValid = spec.isBidRequestValid(invalidBid); + + expect(isValid).to.equal(false); + }); + + it('should reject bid if dimension is not a string', function () { + let invalidBid = { + bidder: 'iprom', + params: { + id: '1234', + dimension: 404, + } + }; + + const isValid = spec.isBidRequestValid(invalidBid); + + expect(isValid).to.equal(false); + }); + + it('should reject bid if missing id', function () { + let invalidBid = { + bidder: 'iprom', + params: { + dimension: '300x250', + } + }; + + const isValid = spec.isBidRequestValid(invalidBid); + + expect(isValid).to.equal(false); + }); + + it('should reject bid if id is not a string', function () { + let invalidBid = { + bidder: 'iprom', + params: { + id: 1234, + dimension: '300x250', + } + }; + + const isValid = spec.isBidRequestValid(invalidBid); + + expect(isValid).to.equal(false); + }); + }); + + describe('building requests', function () { + it('should go to correct endpoint', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + + expect(request.method).to.exist; + expect(request.method).to.equal('POST'); + expect(request.url).to.exist; + expect(request.url).to.equal('https://core.iprom.net/programmatic'); + }); + + it('should add referer info', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + const requestparse = JSON.parse(request.data); + + expect(requestparse.referer).to.exist; + expect(requestparse.referer.referer).to.equal('https://adserver.si/index.html'); + }); + + it('should add adapter version', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + const requestparse = JSON.parse(request.data); + + expect(requestparse.version).to.exist; + }); + + it('should contain id and dimension', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + const requestparse = JSON.parse(request.data); + + expect(requestparse.bids[0].params.id).to.equal('1234'); + expect(requestparse.bids[0].params.dimension).to.equal('300x250'); + }); + }); + + describe('handling responses', function () { + it('should return complete bid response', function () { + const serverResponse = { + body: [{ + requestId: '29a72b151f7bd3', + cpm: 0.5, + width: '300', + height: '250', + creativeId: 1234, + ad: 'Iprom Header bidding example' + } + ]}; + + const request = spec.buildRequests(bidRequests, bidderRequest); + const bids = spec.interpretResponse(serverResponse, request); + + expect(bids).to.be.lengthOf(1); + expect(bids[0].requestId).to.equal('29a72b151f7bd3'); + expect(bids[0].cpm).to.equal(0.5); + expect(bids[0].width).to.equal('300'); + expect(bids[0].height).to.equal('250'); + expect(bids[0].ad).to.have.length.above(1); + }); + + it('should return empty bid response', function () { + const emptyServerResponse = { + body: [] + }; + + const request = spec.buildRequests(bidRequests, bidderRequest); + const bids = spec.interpretResponse(emptyServerResponse, request); + + expect(bids).to.be.lengthOf(0); + }); + }); +}); diff --git a/test/spec/modules/ironsourceBidAdapter_spec.js b/test/spec/modules/ironsourceBidAdapter_spec.js index 93c3a6fb7b9..cca928ff28b 100644 --- a/test/spec/modules/ironsourceBidAdapter_spec.js +++ b/test/spec/modules/ironsourceBidAdapter_spec.js @@ -75,6 +75,8 @@ describe('ironsourceAdapter', function () { bidderCode: 'ironsource', } + const customSessionId = '12345678'; + it('sends bid request to ENDPOINT via GET', function () { const requests = spec.buildRequests(bidRequests, bidderRequest); for (const request of requests) { @@ -98,6 +100,22 @@ describe('ironsourceAdapter', function () { } }); + it('sends the is_wrapper query param', function () { + bidRequests[0].params.isWrapper = true; + const requests = spec.buildRequests(bidRequests, bidderRequest); + for (const request of requests) { + expect(request.data.is_wrapper).to.equal(true); + } + }); + + it('sends the custom session id as a query param', function () { + bidRequests[0].params.sessionId = customSessionId; + const requests = spec.buildRequests(bidRequests, bidderRequest); + for (const request of requests) { + expect(request.data.session_id).to.equal(customSessionId); + } + }); + it('should send the correct width and height', function () { const requests = spec.buildRequests(bidRequests, bidderRequest); for (const request of requests) { diff --git a/test/spec/modules/ixBidAdapter_spec.js b/test/spec/modules/ixBidAdapter_spec.js index 7ac4bd94f9d..65ff31c0500 100644 --- a/test/spec/modules/ixBidAdapter_spec.js +++ b/test/spec/modules/ixBidAdapter_spec.js @@ -3,6 +3,7 @@ import { config } from 'src/config.js'; import { expect } from 'chai'; import { newBidder } from 'src/adapters/bidderFactory.js'; import { spec } from 'modules/ixBidAdapter.js'; +import { createEidsArray } from 'modules/userId/eids.js'; describe('IndexexchangeAdapter', function () { const IX_SECURE_ENDPOINT = 'https://htlb.casalemedia.com/cygnus'; @@ -25,7 +26,7 @@ describe('IndexexchangeAdapter', function () { } ] }; - var div_many_sizes = [ + const div_many_sizes = [ [300, 250], [600, 410], [336, 280], @@ -93,6 +94,62 @@ describe('IndexexchangeAdapter', function () { [600, 40], [600, 30] ]; + + const ONE_VIDEO = [ + { + bidder: 'ix', + params: { + siteId: '456', + video: { + skippable: false, + mimes: [ + 'video/mp4', + 'video/webm' + ], + minduration: 0, + maxduration: 60, + protocols: [2] + }, + size: [400, 100] + }, + sizes: [[400, 100]], + mediaTypes: { + video: { + context: 'instream', + playerSize: [[400, 100]] + } + }, + adUnitCode: 'div-gpt-ad-1460505748562-0', + transactionId: '173f49a8-7549-4218-a23c-e7ba59b47230', + bidId: '1a2b3c4e', + bidderRequestId: '11a22b33c44e', + auctionId: '1aa2bb3cc4de', + schain: SAMPLE_SCHAIN + } + ]; + + const ONE_BANNER = [ + { + bidder: 'ix', + params: { + siteId: '123', + size: [300, 250] + }, + sizes: [[300, 250]], + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + adUnitCode: 'div-gpt-ad-1460505748561-0', + transactionId: '173f49a8-7549-4218-a23c-e7ba59b47229', + bidId: '1a2b3c4d', + bidderRequestId: '11a22b33c44d', + auctionId: '1aa2bb3cc4dd', + schain: SAMPLE_SCHAIN + } + ]; + const DEFAULT_BANNER_VALID_BID = [ { bidder: 'ix', @@ -126,7 +183,9 @@ describe('IndexexchangeAdapter', function () { 'video/mp4', 'video/webm' ], - minduration: 0 + minduration: 0, + maxduration: 60, + protocols: [2] }, size: [400, 100] }, @@ -146,6 +205,68 @@ describe('IndexexchangeAdapter', function () { } ]; + const DEFAULT_MULTIFORMAT_BANNER_VALID_BID = [ + { + bidder: 'ix', + params: { + siteId: '123', + size: [300, 250] + }, + sizes: [[300, 250], [300, 600]], + mediaTypes: { + video: { + context: 'outstream', + playerSize: [[400, 100]] + }, + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + adUnitCode: 'div-gpt-ad-1460505748562-0', + transactionId: '173f49a8-7549-4218-a23c-e7ba59b47230', + bidId: '1a2b3c4e', + bidderRequestId: '11a22b33c44e', + auctionId: '1aa2bb3cc4de', + schain: SAMPLE_SCHAIN + } + ]; + + const DEFAULT_MULTIFORMAT_VIDEO_VALID_BID = [ + { + bidder: 'ix', + params: { + siteId: '456', + video: { + skippable: false, + mimes: [ + 'video/mp4', + 'video/webm' + ], + minduration: 0, + maxduration: 60, + protocols: [1] + }, + size: [400, 100] + }, + sizes: [[300, 250], [300, 600]], + mediaTypes: { + video: { + context: 'outstream', + playerSize: [[400, 100]] + }, + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + adUnitCode: 'div-gpt-ad-1460505748562-0', + transactionId: '173f49a8-7549-4218-a23c-e7ba59b47230', + bidId: '1a2b3c4e', + bidderRequestId: '11a22b33c44e', + auctionId: '1aa2bb3cc4de', + schain: SAMPLE_SCHAIN + } + ]; + const DEFAULT_BANNER_BID_RESPONSE = { cur: 'USD', id: '11a22b33c44d', @@ -287,8 +408,13 @@ describe('IndexexchangeAdapter', function () { const DEFAULT_USERID_DATA = { idl_env: '1234-5678-9012-3456', // Liveramp + netId: 'testnetid123', // NetId + IDP: 'userIDP000', // IDP + fabrickId: 'fabrickId9000', // FabrickId }; + const DEFAULT_USERIDASEIDS_DATA = createEidsArray(DEFAULT_USERID_DATA); + const DEFAULT_USERID_PAYLOAD = [ { source: 'liveramp.com', @@ -298,6 +424,30 @@ describe('IndexexchangeAdapter', function () { rtiPartner: 'idl' } }] + }, { + source: 'netid.de', + uids: [{ + id: DEFAULT_USERID_DATA.netId, + ext: { + rtiPartner: 'NETID' + } + }] + }, { + source: 'neustar.biz', + uids: [{ + id: DEFAULT_USERID_DATA.fabrickId, + ext: { + rtiPartner: 'fabrickId' + } + }] + }, { + source: 'zeotap.com', + uids: [{ + id: DEFAULT_USERID_DATA.IDP, + ext: { + rtiPartner: 'zeotapIdPlus' + } + }] } ]; @@ -436,6 +586,16 @@ describe('IndexexchangeAdapter', function () { expect(spec.isBidRequestValid(bid)).to.equal(true); }); + it('should return true for banner bid when there are multiple mediaTypes (banner, outstream)', function () { + const bid = utils.deepClone(DEFAULT_MULTIFORMAT_BANNER_VALID_BID[0]); + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return true for video bid when there are multiple mediaTypes (banner, outstream)', function () { + const bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + it('should return false when there is only bidFloor', function () { const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); bid.params.bidFloor = 50; @@ -461,6 +621,71 @@ describe('IndexexchangeAdapter', function () { bid.params.bidFloorCur = 70; expect(spec.isBidRequestValid(bid)).to.equal(false); }); + + it('should return false when required video properties are missing on both adunit & param levels', function () { + const bid = utils.deepClone(DEFAULT_VIDEO_VALID_BID[0]); + delete bid.params.video.mimes; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return true when required video properties are at the adunit level', function () { + const bid = utils.deepClone(DEFAULT_VIDEO_VALID_BID[0]); + delete bid.params.video.mimes; + bid.mediaTypes.video.mimes = ['video/mp4']; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return true if protocols exists but protocol doesn\'t', function () { + const bid = utils.deepClone(DEFAULT_VIDEO_VALID_BID[0]); + expect(spec.isBidRequestValid(bid)).to.equal(true); + delete bid.params.video.protocols; + bid.mediaTypes.video.protocols = 1; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return true if protocol exists but protocols doesn\'t', function () { + const bid = utils.deepClone(DEFAULT_VIDEO_VALID_BID[0]); + delete bid.params.video.protocols; + bid.params.video.protocol = 1; + expect(spec.isBidRequestValid(bid)).to.equal(true); + delete bid.params.video.protocol; + bid.mediaTypes.video.protocol = 1; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false if both protocol/protocols are missing', function () { + const bid = utils.deepClone(DEFAULT_VIDEO_VALID_BID[0]); + delete bid.params.video.protocols; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('Roundel alias adapter', function () { + const vaildBids = [DEFAULT_BANNER_VALID_BID, DEFAULT_VIDEO_VALID_BID, DEFAULT_MULTIFORMAT_BANNER_VALID_BID, DEFAULT_MULTIFORMAT_VIDEO_VALID_BID]; + const ALIAS_OPTIONS = Object.assign({ + bidderCode: 'roundel' + }, DEFAULT_OPTION); + + it('should not build requests for mediaTypes if liveramp data is unavaliable', function () { + vaildBids.forEach((validBid) => { + const request = spec.buildRequests(validBid, ALIAS_OPTIONS); + expect(request).to.be.an('array'); + expect(request).to.have.lengthOf(0); + }); + }); + + it('should build requests for mediaTypes if liveramp data is avaliable', function () { + vaildBids.forEach((validBid) => { + const cloneValidBid = utils.deepClone(validBid); + cloneValidBid[0].userIdAsEids = utils.deepClone(DEFAULT_USERIDASEIDS_DATA); + const request = spec.buildRequests(cloneValidBid, ALIAS_OPTIONS); + const payload = JSON.parse(request[0].data.r); + expect(request).to.be.an('array'); + expect(request).to.have.lengthOf(1); + expect(payload.user.eids).to.have.lengthOf(4); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[0]); + }); + }); }); describe('buildRequestsIdentity', function () { @@ -650,14 +875,18 @@ describe('IndexexchangeAdapter', function () { delete window.headertag; }); - it('IX adapter reads LiveRamp IDL envelope from Prebid and adds it to Video', function () { + it('IX adapter reads supported user modules from Prebid and adds it to Video', function () { const cloneValidBid = utils.deepClone(DEFAULT_VIDEO_VALID_BID); - cloneValidBid[0].userId = utils.deepClone(DEFAULT_USERID_DATA); + // cloneValidBid[0].userId = utils.deepClone(DEFAULT_USERID_DATA); + cloneValidBid[0].userIdAsEids = utils.deepClone(DEFAULT_USERIDASEIDS_DATA); const request = spec.buildRequests(cloneValidBid, DEFAULT_OPTION)[0]; const payload = JSON.parse(request.data.r); - expect(payload.user.eids).to.have.lengthOf(1); + expect(payload.user.eids).to.have.lengthOf(4); expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[0]); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[1]); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[2]); + expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[3]); }); it('We continue to send in IXL identity info and Prebid takes precedence over IXL', function () { @@ -711,11 +940,45 @@ describe('IndexexchangeAdapter', function () { } } ] + }, + NetIdIp: { + source: 'netid.de', + uids: [ + { + id: 'testnetid', + ext: { + rtiPartner: 'NETID' + } + } + ] + }, + NeustarIp: { + source: 'neustar.biz', + uids: [ + { + id: 'testfabrick', + ext: { + rtiPartner: 'fabrickId' + } + } + ] + }, + ZeotapIp: { + source: 'zeotap.com', + uids: [ + { + id: 'testzeotap', + ext: { + rtiPartner: 'zeotapIdPlus' + } + } + ] } }; const cloneValidBid = utils.deepClone(DEFAULT_BANNER_VALID_BID); - cloneValidBid[0].userId = utils.deepClone(DEFAULT_USERID_DATA) + // cloneValidBid[0].userId = utils.deepClone(DEFAULT_USERID_DATA); + cloneValidBid[0].userIdAsEids = utils.deepClone(DEFAULT_USERIDASEIDS_DATA); const request = spec.buildRequests(cloneValidBid, DEFAULT_OPTION)[0]; const payload = JSON.parse(request.data.r); @@ -756,10 +1019,14 @@ describe('IndexexchangeAdapter', function () { }) expect(payload.user).to.exist; - expect(payload.user.eids).to.have.lengthOf(3); + expect(payload.user.eids).to.have.lengthOf(6); + expect(payload.user.eids).to.deep.include(validUserIdPayload[0]); expect(payload.user.eids).to.deep.include(validUserIdPayload[1]); expect(payload.user.eids).to.deep.include(validUserIdPayload[2]); + expect(payload.user.eids).to.deep.include(validUserIdPayload[3]); + expect(payload.user.eids).to.deep.include(validUserIdPayload[4]); + expect(payload.user.eids).to.deep.include(validUserIdPayload[5]); }); it('IXL and Prebid are mutually exclusive', function () { @@ -781,7 +1048,8 @@ describe('IndexexchangeAdapter', function () { }; const cloneValidBid = utils.deepClone(DEFAULT_VIDEO_VALID_BID); - cloneValidBid[0].userId = utils.deepClone(DEFAULT_USERID_DATA); + // cloneValidBid[0].userId = utils.deepClone(DEFAULT_USERID_DATA); + cloneValidBid[0].userIdAsEids = utils.deepClone(DEFAULT_USERIDASEIDS_DATA); const request = spec.buildRequests(cloneValidBid, DEFAULT_OPTION)[0]; @@ -799,9 +1067,12 @@ describe('IndexexchangeAdapter', function () { }); const payload = JSON.parse(request.data.r); - expect(payload.user.eids).to.have.lengthOf(2); + expect(payload.user.eids).to.have.lengthOf(5); expect(payload.user.eids).to.deep.include(validUserIdPayload[0]); expect(payload.user.eids).to.deep.include(validUserIdPayload[1]); + expect(payload.user.eids).to.deep.include(validUserIdPayload[2]); + expect(payload.user.eids).to.deep.include(validUserIdPayload[3]); + expect(payload.user.eids).to.deep.include(validUserIdPayload[4]); }); }); @@ -870,6 +1141,84 @@ describe('IndexexchangeAdapter', function () { expect(impression.ext.sid).to.equal(sidValue); }); + it('video impression has #priceFloors floors', function () { + const bid = utils.deepClone(ONE_VIDEO[0]); + const flr = 5.5 + const floorInfo = {floor: flr, currency: 'USD'}; + bid.getFloor = function () { + return floorInfo; + } + + // check if floors are in imp + const requestBidFloor = spec.buildRequests([bid])[0]; + const imp1 = JSON.parse(requestBidFloor.data.r).imp[0]; + expect(imp1.bidfloor).to.equal(flr); + expect(imp1.bidfloorcur).to.equal('USD'); + expect(imp1.ext.fl).to.equal('p'); + }); + + it('banner imp has floors from #priceFloors module', function () { + const floor300x250 = 3.25 + const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]) + + const floorInfo = { floor: floor300x250, currency: 'USD' }; + bid.getFloor = function () { + return floorInfo; + }; + + // check if floors are in imp + const requestBidFloor = spec.buildRequests([bid])[0]; + const imp1 = JSON.parse(requestBidFloor.data.r).imp[0]; + + expect(imp1.bidfloorcur).to.equal('USD'); + expect(imp1.bidfloor).to.equal(floor300x250); + expect(imp1.ext.fl).to.equal('p'); + }); + + it('ix adapter floors chosen over #priceFloors ', function () { + const bid = utils.deepClone(ONE_BANNER[0]); + + const floorhi = 4.5 + const floorlow = 3.5 + + bid.params.bidFloor = floorhi + bid.params.bidFloorCur = 'USD' + + const floorInfo = { floor: floorlow, currency: 'USD' }; + bid.getFloor = function () { + return floorInfo; + }; + + // check if floors are in imp + const requestBidFloor = spec.buildRequests([bid])[0]; + const imp1 = JSON.parse(requestBidFloor.data.r).imp[0]; + expect(imp1.bidfloor).to.equal(floorhi); + expect(imp1.bidfloorcur).to.equal(bid.params.bidFloorCur); + expect(imp1.ext.fl).to.equal('x'); + }); + + it(' #priceFloors floors chosen over ix adapter floors', function () { + const bid = utils.deepClone(ONE_BANNER[0]); + + const floorhi = 4.5 + const floorlow = 3.5 + + bid.params.bidFloor = floorlow + bid.params.bidFloorCur = 'USD' + + const floorInfo = { floor: floorhi, currency: 'USD' }; + bid.getFloor = function () { + return floorInfo; + }; + + // check if floors are in imp + const requestBidFloor = spec.buildRequests([bid])[0]; + const imp1 = JSON.parse(requestBidFloor.data.r).imp[0]; + expect(imp1.bidfloor).to.equal(floorhi); + expect(imp1.bidfloorcur).to.equal(bid.params.bidFloorCur); + expect(imp1.ext.fl).to.equal('p'); + }); + it('impression should have bidFloor and bidFloorCur if configured', function () { const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); bid.params.bidFloor = 50; @@ -879,6 +1228,73 @@ describe('IndexexchangeAdapter', function () { expect(impression.bidfloor).to.equal(bid.params.bidFloor); expect(impression.bidfloorcur).to.equal(bid.params.bidFloorCur); + expect(impression.ext.fl).to.equal('x'); + }); + + it('missing sizes #priceFloors ', function () { + const bid = utils.deepClone(ONE_BANNER[0]); + bid.mediaTypes.banner.sizes.push([500, 400]) + + const floorInfo = { floor: 3.25, currency: 'USD' }; + bid.getFloor = function () { + return floorInfo; + }; + + sinon.spy(bid, 'getFloor'); + + const requestBidFloor = spec.buildRequests([bid])[0]; + // called getFloor with 300 x 250 + expect(bid.getFloor.getCall(0).args[0].mediaType).to.equal('banner'); + expect(bid.getFloor.getCall(0).args[0].size[0]).to.equal(300); + expect(bid.getFloor.getCall(0).args[0].size[1]).to.equal(250); + + // called getFloor with 500 x 400 + expect(bid.getFloor.getCall(1).args[0].mediaType).to.equal('banner'); + expect(bid.getFloor.getCall(1).args[0].size[0]).to.equal(500); + expect(bid.getFloor.getCall(1).args[0].size[1]).to.equal(400); + + // both will have same floors due to mock getFloor + const imp1 = JSON.parse(requestBidFloor.data.r).imp[0]; + expect(imp1.bidfloor).to.equal(3.25); + expect(imp1.bidfloorcur).to.equal('USD'); + + const imp2 = JSON.parse(requestBidFloor.data.r).imp[1]; + expect(imp2.bidfloor).to.equal(3.25); + expect(imp2.bidfloorcur).to.equal('USD'); + }); + + it('#pricefloors inAdUnit, banner impressions should have floors', function () { + const bid = utils.deepClone(ONE_BANNER[0]); + + const flr = 4.3 + bid.floors = { + currency: 'USD', + schema: { + delimiter: '|', + fields: ['mediaType', 'size'] + }, + values: { + 'banner|300x250': flr, + 'banner|600x500': 6.5, + 'banner|*': 7.5 + } + }; + const floorInfo = { floor: flr, currency: 'USD' }; + bid.getFloor = function () { + return floorInfo; + }; + + sinon.spy(bid, 'getFloor'); + + const requestBidFloor = spec.buildRequests([bid])[0]; + // called getFloor with 300 x 250 + expect(bid.getFloor.getCall(0).args[0].mediaType).to.equal('banner'); + expect(bid.getFloor.getCall(0).args[0].size[0]).to.equal(300); + expect(bid.getFloor.getCall(0).args[0].size[1]).to.equal(250); + + const imp1 = JSON.parse(requestBidFloor.data.r).imp[0]; + expect(imp1.bidfloor).to.equal(flr); + expect(imp1.bidfloorcur).to.equal('USD'); }); it('payload without mediaType should have correct format and value', function () { @@ -958,7 +1374,6 @@ describe('IndexexchangeAdapter', function () { const requestWithFirstPartyData = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION)[0]; const pageUrl = JSON.parse(requestWithFirstPartyData.data.r).site.page; const expectedPageUrl = DEFAULT_OPTION.refererInfo.referer + '?ab=123&cd=123%23ab&e%2Ff=456&h%3Fg=456%23cd'; - expect(pageUrl).to.equal(expectedPageUrl); }); @@ -1292,6 +1707,102 @@ describe('IndexexchangeAdapter', function () { expect(impression.video.placement).to.exist; expect(impression.video.placement).to.equal(4); }); + + it('should not override video properties if they are already configured at the params video level', function () { + const bid = utils.deepClone(DEFAULT_VIDEO_VALID_BID[0]); + bid.mediaTypes.video.context = 'outstream'; + bid.mediaTypes.video.protocols = [1]; + bid.mediaTypes.video.mimes = ['video/override']; + const request = spec.buildRequests([bid])[0]; + const impression = JSON.parse(request.data.r).imp[0]; + + expect(impression.video.protocols[0]).to.equal(2); + expect(impression.video.mimes[0]).to.not.equal('video/override'); + }); + + it('should not add video adunit level properties in imp object if they are not allowlisted', function () { + const bid = utils.deepClone(DEFAULT_VIDEO_VALID_BID[0]); + bid.mediaTypes.video.context = 'outstream'; + bid.mediaTypes.video.random = true; + const request = spec.buildRequests([bid])[0]; + const impression = JSON.parse(request.data.r).imp[0]; + + expect(impression.video.random).to.not.exist; + }); + + it('should add allowlisted adunit level video properties in imp object if they are not configured at params level', function () { + const bid = utils.deepClone(DEFAULT_VIDEO_VALID_BID[0]); + bid.mediaTypes.video.context = 'outstream'; + delete bid.params.video.protocols; + delete bid.params.video.mimes; + bid.mediaTypes.video.protocols = [6]; + bid.mediaTypes.video.mimes = ['video/mp4']; + bid.mediaTypes.video.api = 2; + const request = spec.buildRequests([bid])[0]; + const impression = JSON.parse(request.data.r).imp[0]; + + expect(impression.video.protocols[0]).to.equal(6); + expect(impression.video.api).to.equal(2); + expect(impression.video.mimes[0]).to.equal('video/mp4'); + }); + }); + + describe('buildRequestMultiFormat', function () { + describe('only banner bidder params set', function () { + const request = spec.buildRequests(DEFAULT_MULTIFORMAT_BANNER_VALID_BID) + + const bannerImp = JSON.parse(request[0].data.r).imp[0]; + expect(JSON.parse(request[0].data.r).imp).to.have.lengthOf(2); + expect(JSON.parse(request[0].data.v)).to.equal(BANNER_ENDPOINT_VERSION); + expect(bannerImp.id).to.equal(DEFAULT_MULTIFORMAT_BANNER_VALID_BID[0].bidId); + expect(bannerImp.banner).to.exist; + expect(bannerImp.banner.w).to.equal(DEFAULT_MULTIFORMAT_BANNER_VALID_BID[0].params.size[0]); + expect(bannerImp.banner.h).to.equal(DEFAULT_MULTIFORMAT_BANNER_VALID_BID[0].params.size[1]); + }); + + describe('only video bidder params set', function () { + const request = spec.buildRequests(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID); + + const videoImp = JSON.parse(request[0].data.r).imp[0]; + expect(JSON.parse(request[0].data.r).imp).to.have.lengthOf(1); + expect(JSON.parse(request[0].data.v)).to.equal(VIDEO_ENDPOINT_VERSION); + expect(videoImp.id).to.equal(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0].bidId); + expect(videoImp.video).to.exist; + expect(videoImp.video.w).to.equal(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0].params.size[0]); + expect(videoImp.video.h).to.equal(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0].params.size[1]); + }); + describe('both banner and video bidder params set', function () { + const request = spec.buildRequests([DEFAULT_MULTIFORMAT_BANNER_VALID_BID[0], DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]]); + + it('should return valid banner and video requests', function () { + const bannerImp = JSON.parse(request[0].data.r).imp[0]; + expect(JSON.parse(request[0].data.r).imp).to.have.lengthOf(2); + expect(JSON.parse(request[0].data.v)).to.equal(BANNER_ENDPOINT_VERSION); + expect(bannerImp.id).to.equal(DEFAULT_MULTIFORMAT_BANNER_VALID_BID[0].bidId); + expect(bannerImp.banner).to.exist; + expect(bannerImp.banner.w).to.equal(DEFAULT_MULTIFORMAT_BANNER_VALID_BID[0].params.size[0]); + expect(bannerImp.banner.h).to.equal(DEFAULT_MULTIFORMAT_BANNER_VALID_BID[0].params.size[1]); + + const videoImp = JSON.parse(request[1].data.r).imp[0]; + expect(JSON.parse(request[1].data.r).imp).to.have.lengthOf(1); + expect(JSON.parse(request[1].data.v)).to.equal(VIDEO_ENDPOINT_VERSION); + expect(videoImp.id).to.equal(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0].bidId); + expect(videoImp.video).to.exist; + expect(videoImp.video.w).to.equal(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0].params.size[0]); + expect(videoImp.video.h).to.equal(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0].params.size[1]); + }); + + it('should contain all correct IXdiag properties', function () { + const diagObj = JSON.parse(request[0].data.r).ext.ixdiag; + expect(diagObj.iu).to.equal(0); + expect(diagObj.nu).to.equal(0); + expect(diagObj.ou).to.equal(1); + expect(diagObj.ren).to.equal(false); + expect(diagObj.mfu).to.equal(1); + expect(diagObj.allu).to.equal(1); + expect(diagObj.version).to.equal('$prebid.version$'); + }); + }); }); describe('interpretResponse', function () { @@ -1566,5 +2077,32 @@ describe('IndexexchangeAdapter', function () { expect(requestWithConsent.regs.ext.gdpr).to.equal(1); expect(requestWithConsent.regs.ext.us_privacy).to.equal('1YYN'); }); + + it('should contain `consented_providers_settings.consented_providers` & consent on user.ext when both are provided', function () { + const options = { + gdprConsent: { + consentString: '3huaa11=qu3198ae', + addtlConsent: '1~1.35.41.101', + } + }; + + const validBidWithConsent = spec.buildRequests(DEFAULT_BANNER_VALID_BID, options); + const requestWithConsent = JSON.parse(validBidWithConsent[0].data.r); + expect(requestWithConsent.user.ext.consented_providers_settings.consented_providers).to.equal('1~1.35.41.101'); + expect(requestWithConsent.user.ext.consent).to.equal('3huaa11=qu3198ae'); + }); + + it('should not contain `consented_providers_settings.consented_providers` on user.ext when consent is not provided', function () { + const options = { + gdprConsent: { + addtlConsent: '1~1.35.41.101', + } + }; + + const validBidWithConsent = spec.buildRequests(DEFAULT_BANNER_VALID_BID, options); + const requestWithConsent = JSON.parse(validBidWithConsent[0].data.r); + expect(utils.deepAccess(requestWithConsent, 'user.ext.consented_providers_settings')).to.not.exist; + expect(utils.deepAccess(requestWithConsent, 'user.ext.consent')).to.not.exist; + }); }); }); diff --git a/test/spec/modules/jwplayerRtdProvider_spec.js b/test/spec/modules/jwplayerRtdProvider_spec.js index 48b432b6bb4..458e45e8ae7 100644 --- a/test/spec/modules/jwplayerRtdProvider_spec.js +++ b/test/spec/modules/jwplayerRtdProvider_spec.js @@ -229,8 +229,8 @@ describe('jwplayerRtdProvider', function() { const bid = {}; const adUnit = { - fpd: { - context: { + ortb2Imp: { + ext: { data: { jwTargeting: { mediaID: mediaIdWithSegment, @@ -298,8 +298,8 @@ describe('jwplayerRtdProvider', function() { } ]; const adUnit = { - fpd: { - context: { + ortb2Imp: { + ext: { data: { jwTargeting: { mediaID: testIdForSuccess @@ -345,8 +345,8 @@ describe('jwplayerRtdProvider', function() { } ]; const adUnit = { - fpd: { - context: { + ortb2Imp: { + ext: { data: { jwTargeting: { mediaID: testIdForSuccess @@ -392,8 +392,8 @@ describe('jwplayerRtdProvider', function() { } ]; const adUnit = { - fpd: { - context: { + ortb2Imp: { + ext: { data: { jwTargeting: { mediaID: testIdForFailure @@ -413,19 +413,43 @@ describe('jwplayerRtdProvider', function() { }); describe(' Extract Publisher Params', function () { - it('should default to config', function () { - const config = { mediaID: 'test' }; + const config = { mediaID: 'test' }; - const adUnit1 = { fpd: { context: {} } }; - const targeting1 = extractPublisherParams(adUnit1, config); - expect(targeting1).to.deep.equal(config); + it('should exclude adUnits that do not support instream video and do not specify jwTargeting', function () { + const oustreamAdUnit = { mediaTypes: { video: { context: 'outstream' } } }; + const oustreamTargeting = extractPublisherParams(oustreamAdUnit, config); + expect(oustreamTargeting).to.be.undefined; - const adUnit2 = { fpd: { context: { data: { jwTargeting: {} } } } }; - const targeting2 = extractPublisherParams(adUnit2, config); - expect(targeting2).to.deep.equal(config); + const bannerAdUnit = { mediaTypes: { banner: {} } }; + const bannerTargeting = extractPublisherParams(bannerAdUnit, config); + expect(bannerTargeting).to.be.undefined; - const targeting3 = extractPublisherParams(null, config); - expect(targeting3).to.deep.equal(config); + const targeting = extractPublisherParams({}, config); + expect(targeting).to.be.undefined; + }); + + it('should include ad unit when media type is video and is instream', function () { + const adUnit = { mediaTypes: { video: { context: 'instream' } } }; + const targeting = extractPublisherParams(adUnit, config); + expect(targeting).to.deep.equal(config); + }); + + it('should include banner ad units that specify jwTargeting', function() { + const adUnit = { mediaTypes: { banner: {} }, ortb2Imp: { ext: { data: { jwTargeting: {} } } } }; + const targeting = extractPublisherParams(adUnit, config); + expect(targeting).to.deep.equal(config); + }); + + it('should include outstream ad units that specify jwTargeting', function() { + const adUnit = { mediaTypes: { video: { context: 'outstream' } }, ortb2Imp: { ext: { data: { jwTargeting: {} } } } }; + const targeting = extractPublisherParams(adUnit, config); + expect(targeting).to.deep.equal(config); + }); + + it('should fallback to config when empty jwTargeting is defined in ad unit', function () { + const adUnit = { ortb2Imp: { ext: { data: { jwTargeting: {} } } } }; + const targeting = extractPublisherParams(adUnit, config); + expect(targeting).to.deep.equal(config); }); it('should prioritize adUnit properties ', function () { @@ -433,7 +457,7 @@ describe('jwplayerRtdProvider', function() { const expectedPlayerID = 'test_player_id'; const config = { playerID: 'bad_id', mediaID: 'bad_id' }; - const adUnit = { fpd: { context: { data: { jwTargeting: { mediaID: expectedMediaID, playerID: expectedPlayerID } } } } }; + const adUnit = { ortb2Imp: { ext: { data: { jwTargeting: { mediaID: expectedMediaID, playerID: expectedPlayerID } } } } }; const targeting = extractPublisherParams(adUnit, config); expect(targeting).to.have.property('mediaID', expectedMediaID); expect(targeting).to.have.property('playerID', expectedPlayerID); @@ -444,15 +468,15 @@ describe('jwplayerRtdProvider', function() { const expectedPlayerID = 'test_player_id'; const config = { playerID: expectedPlayerID, mediaID: 'bad_id' }; - const adUnit = { fpd: { context: { data: { jwTargeting: { mediaID: expectedMediaID } } } } }; + const adUnit = { ortb2Imp: { ext: { data: { jwTargeting: { mediaID: expectedMediaID } } } } }; const targeting = extractPublisherParams(adUnit, config); expect(targeting).to.have.property('mediaID', expectedMediaID); expect(targeting).to.have.property('playerID', expectedPlayerID); }); - it('should return empty object when Publisher Params are absent', function () { - const targeting = extractPublisherParams(null, null); - expect(targeting).to.deep.equal({}); + it('should return undefined when Publisher Params are absent', function () { + const targeting = extractPublisherParams({}, null); + expect(targeting).to.be.undefined; }) }); @@ -553,8 +577,8 @@ describe('jwplayerRtdProvider', function() { bidReqConfig = { adUnits: [ { - fpd: { - context: { + ortb2Imp: { + ext: { data: { jwTargeting: { mediaID: validMediaIDs[0] @@ -567,8 +591,8 @@ describe('jwplayerRtdProvider', function() { ] }, { - fpd: { - context: { + ortb2Imp: { + ext: { data: { jwTargeting: { mediaID: validMediaIDs[1] @@ -634,8 +658,8 @@ describe('jwplayerRtdProvider', function() { it('sets targeting data in proper structure', function () { const bid = {}; const adUnitWithMediaId = { - fpd: { - context: { + ortb2Imp: { + ext: { data: { jwTargeting: { mediaID: testIdForSuccess @@ -666,8 +690,8 @@ describe('jwplayerRtdProvider', function() { const adUnitCode = 'test_ad_unit'; const bid = {}; const adUnit = { - fpd: { - context: { + ortb2Imp: { + ext: { data: { jwTargeting: { mediaID: testIdForFailure @@ -677,7 +701,7 @@ describe('jwplayerRtdProvider', function() { }, bids: [ bid ] }; - const expectedContentId = 'jw_' + adUnit.fpd.context.data.jwTargeting.mediaID; + const expectedContentId = 'jw_' + adUnit.ortb2Imp.ext.data.jwTargeting.mediaID; const expectedTargeting = { content: { id: expectedContentId @@ -708,8 +732,8 @@ describe('jwplayerRtdProvider', function() { const adUnitEmptyfpd = { code: 'test_ad_unit_empty_fpd', - fpd: { - context: { + ortb2Imp: { + ext: { id: 'sthg' } }, diff --git a/test/spec/modules/kargoBidAdapter_spec.js b/test/spec/modules/kargoBidAdapter_spec.js index 9dbcca8e331..43968bbef5a 100644 --- a/test/spec/modules/kargoBidAdapter_spec.js +++ b/test/spec/modules/kargoBidAdapter_spec.js @@ -134,6 +134,21 @@ describe('kargo adapter tests', function () { noAdServerCurrency = true; } + function generateGDPR(applies, haveConsent) { + var data = { + consentString: 'gdprconsentstring', + gdprApplies: applies, + }; + return data; + } + + function generateGDPRExpect(applies, haveConsent) { + return { + consent: 'gdprconsentstring', + applies: applies, + }; + } + function initializeKruxUser() { setLocalStorageItem('kxkar_user', 'rsgr9pnij'); } @@ -221,7 +236,7 @@ describe('kargo adapter tests', function () { return spec._getSessionId(); } - function getExpectedKrakenParams(excludeUserIds, excludeKrux, expectedRawCRB, expectedRawCRBCookie) { + function getExpectedKrakenParams(excludeUserIds, excludeKrux, expectedRawCRB, expectedRawCRBCookie, expectedGDPR) { var base = { timeout: 200, requestCount: requestCount++, @@ -299,6 +314,10 @@ describe('kargo adapter tests', function () { rawCRBLocalStorage: expectedRawCRB }; + if (expectedGDPR) { + base.userIDs['gdpr'] = expectedGDPR; + } + if (excludeUserIds === true) { base.userIDs = { crbIDs: {}, @@ -317,12 +336,16 @@ describe('kargo adapter tests', function () { return base; } - function testBuildRequests(excludeTdid, expected) { + function testBuildRequests(excludeTdid, expected, gdpr) { var clonedBids = JSON.parse(JSON.stringify(bids)); if (excludeTdid) { delete clonedBids[0].userId.tdid; } - var request = spec.buildRequests(clonedBids, {timeout: 200, uspConsent: '1---', foo: 'bar'}); + var payload = { timeout: 200, uspConsent: '1---', foo: 'bar' }; + if (gdpr) { + payload['gdprConsent'] = gdpr + } + var request = spec.buildRequests(clonedBids, payload); expected.sessionId = getSessionId(); sessionIds.push(expected.sessionId); var krakenParams = JSON.parse(decodeURIComponent(request.data.slice(5))); @@ -431,6 +454,15 @@ describe('kargo adapter tests', function () { initializeKrgCrb(); testBuildRequests(false, getExpectedKrakenParams(undefined, undefined, getKrgCrb(), getKrgCrbOldStyle())); }); + + it('sends gdpr consent', function () { + initializeKruxUser(); + initializeKruxSegments(); + initializeKrgCrb(); + testBuildRequests(false, getExpectedKrakenParams(undefined, undefined, getKrgCrb(), getKrgCrbOldStyle(), generateGDPRExpect(true, true)), generateGDPR(true, true)); + testBuildRequests(false, getExpectedKrakenParams(undefined, undefined, getKrgCrb(), getKrgCrbOldStyle(), generateGDPRExpect(false, true)), generateGDPR(false, true)); + testBuildRequests(false, getExpectedKrakenParams(undefined, undefined, getKrgCrb(), getKrgCrbOldStyle(), generateGDPRExpect(false, false)), generateGDPR(false, false)); + }); }); describe('response handler', function() { @@ -558,8 +590,8 @@ describe('kargo adapter tests', function () { }); }); - function getUserSyncsWhenAllowed() { - return spec.getUserSyncs({iframeEnabled: true}); + function getUserSyncsWhenAllowed(gdprConsent, usPrivacy) { + return spec.getUserSyncs({iframeEnabled: true}, null, gdprConsent, usPrivacy); } function getUserSyncsWhenForbidden() { @@ -574,17 +606,17 @@ describe('kargo adapter tests', function () { shouldSimulateOutdatedBrowser = true; } - function getSyncUrl(index) { + function getSyncUrl(index, gdprApplies, gdprConsentString, usPrivacy) { return { type: 'iframe', - url: `https://crb.kargo.com/api/v1/initsyncrnd/${clientId}?seed=3205e885-8d37-4139-b47e-f82cff268000&idx=${index}` + url: `https://crb.kargo.com/api/v1/initsyncrnd/${clientId}?seed=3205e885-8d37-4139-b47e-f82cff268000&idx=${index}&gdpr=${gdprApplies}&gdpr_consent=${gdprConsentString}&us_privacy=${usPrivacy}` }; } - function getSyncUrls() { + function getSyncUrls(gdprApplies, gdprConsentString, usPrivacy) { var syncs = []; for (var i = 0; i < 5; i++) { - syncs[i] = getSyncUrl(i); + syncs[i] = getSyncUrl(i, gdprApplies || 0, gdprConsentString || '', usPrivacy || ''); } return syncs; } @@ -606,6 +638,21 @@ describe('kargo adapter tests', function () { safelyRun(() => expect(getUserSyncsWhenAllowed()).to.be.an('array').that.is.empty); }); + it('no user syncs when there is no us privacy consent', function() { + turnOnClientId(); + safelyRun(() => expect(getUserSyncsWhenAllowed(null, '1YYY')).to.be.an('array').that.is.empty); + }); + + it('pass through us privacy consent', function() { + turnOnClientId(); + safelyRun(() => expect(getUserSyncsWhenAllowed(null, '1YNY')).to.deep.equal(getSyncUrls(0, '', '1YNY'))); + }); + + it('pass through gdpr consent', function() { + turnOnClientId(); + safelyRun(() => expect(getUserSyncsWhenAllowed({ gdprApplies: true, consentString: 'consentstring' })).to.deep.equal(getSyncUrls(1, 'consentstring', ''))); + }); + it('no user syncs when there is outdated browser', function() { turnOnClientId(); simulateOutdatedBrowser(); diff --git a/test/spec/modules/kubientBidAdapter_spec.js b/test/spec/modules/kubientBidAdapter_spec.js new file mode 100644 index 00000000000..1df4370b2ba --- /dev/null +++ b/test/spec/modules/kubientBidAdapter_spec.js @@ -0,0 +1,259 @@ +import { expect, assert } from 'chai'; +import { spec } from 'modules/kubientBidAdapter.js'; + +describe('KubientAdapter', function () { + let bid = { + bidId: '2dd581a2b6281d', + bidder: 'kubient', + bidderRequestId: '145e1d6a7837c9', + params: { + zoneid: '5678', + floor: 0.05, + }, + auctionId: '74f78609-a92d-4cf1-869f-1b244bbfb5d2', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + transactionId: '3bb2f6da-87a6-4029-aeb0-bfe951372e62', + schain: { + ver: '1.0', + complete: 1, + nodes: [ + { + asi: 'example.com', + sid: '0', + hp: 1, + rid: 'bidrequestid', + domain: 'example.com' + } + ] + } + }; + let consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; + let uspConsentData = '1YCC'; + let bidderRequest = { + bidderCode: 'kubient', + auctionId: 'fffffff-ffff-ffff-ffff-ffffffffffff', + bidderRequestId: 'ffffffffffffff', + start: 1472239426002, + auctionStart: 1472239426000, + timeout: 5000, + refererInfo: { + referer: 'http://www.example.com', + reachedTop: true, + }, + gdprConsent: { + consentString: consentString, + gdprApplies: true + }, + uspConsent: uspConsentData, + bids: [bid] + }; + describe('buildRequests', function () { + let serverRequests = spec.buildRequests([bid], bidderRequest); + it('Creates a ServerRequest object with method, URL and data', function () { + expect(serverRequests).to.be.an('array'); + }); + for (let i = 0; i < serverRequests.length; i++) { + let serverRequest = serverRequests[i]; + it('Creates a ServerRequest object with method, URL and data', function () { + expect(serverRequest.method).to.be.a('string'); + expect(serverRequest.url).to.be.a('string'); + expect(serverRequest.data).to.be.a('string'); + }); + it('Returns POST method', function () { + expect(serverRequest.method).to.equal('POST'); + }); + it('Returns valid URL', function () { + expect(serverRequest.url).to.equal('https://kssp.kbntx.ch/pbjs'); + }); + it('Returns valid data if array of bids is valid', function () { + let data = JSON.parse(serverRequest.data); + expect(data).to.be.an('object'); + expect(data).to.have.all.keys('v', 'requestId', 'adSlots', 'gdpr', 'referer', 'tmax', 'consent', 'consentGiven', 'uspConsent'); + expect(data.v).to.exist.and.to.be.a('string'); + expect(data.requestId).to.exist.and.to.be.a('string'); + expect(data.referer).to.be.a('string'); + expect(data.tmax).to.exist.and.to.be.a('number'); + expect(data.gdpr).to.exist.and.to.be.within(0, 1); + expect(data.consent).to.equal(consentString); + expect(data.uspConsent).to.exist.and.to.equal(uspConsentData); + for (let j = 0; j < data['adSlots'].length; j++) { + let adSlot = data['adSlots'][i]; + expect(adSlot).to.have.all.keys('bidId', 'zoneId', 'floor', 'sizes', 'schain', 'mediaTypes'); + expect(adSlot.bidId).to.be.a('string'); + expect(adSlot.zoneId).to.be.a('string'); + expect(adSlot.floor).to.be.a('number'); + expect(adSlot.sizes).to.be.an('array'); + expect(adSlot.schain).to.be.an('object'); + expect(adSlot.mediaTypes).to.be.an('object'); + } + }); + } + }); + + describe('isBidRequestValid', function () { + it('Should return true when required params are found', function () { + expect(spec.isBidRequestValid(bid)).to.be.true; + }); + it('Should return false when required params are not found', function () { + expect(spec.isBidRequestValid(bid)).to.be.true; + }); + it('Should return false when params are not found', function () { + delete bid.params; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + }); + + describe('interpretResponse', function () { + it('Should interpret response', function () { + const serverResponse = { + body: + { + seatbid: [ + { + bid: [ + { + bidId: '000', + price: 1.5, + adm: '
test
', + creativeId: 'creativeId', + w: 300, + h: 250, + cur: 'USD', + netRevenue: false, + ttl: 360 + } + ] + } + ] + } + }; + let bannerResponses = spec.interpretResponse(serverResponse); + expect(bannerResponses).to.be.an('array').that.is.not.empty; + let dataItem = bannerResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'ad', 'creativeId', 'width', 'height', 'currency', 'netRevenue', 'ttl'); + expect(dataItem.requestId).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.seatbid[0].bid[0].bidId); + expect(dataItem.cpm).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].price); + expect(dataItem.ad).to.exist.and.to.be.a('string').and.to.have.string(serverResponse.body.seatbid[0].bid[0].adm); + expect(dataItem.creativeId).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.seatbid[0].bid[0].creativeId); + expect(dataItem.width).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].w); + expect(dataItem.height).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].h); + expect(dataItem.currency).to.exist.and.to.be.a('string').and.to.equal(serverResponse.body.seatbid[0].bid[0].cur); + expect(dataItem.netRevenue).to.exist.and.to.be.a('boolean').and.to.equal(serverResponse.body.seatbid[0].bid[0].netRevenue); + expect(dataItem.ttl).to.exist.and.to.be.a('number').and.to.equal(serverResponse.body.seatbid[0].bid[0].ttl); + }); + + it('Should return no ad when not given a server response', function () { + const ads = spec.interpretResponse(null); + expect(ads).to.be.an('array').and.to.have.length(0); + }); + }); + + describe('getUserSyncs', function () { + it('should register the sync iframe without gdpr', function () { + let syncOptions = { + iframeEnabled: true + }; + let serverResponses = null; + let gdprConsent = { + consentString: consentString + }; + let uspConsent = null; + let syncs = spec.getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent); + expect(syncs).to.be.an('array').and.to.have.length(1); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.equal('https://kdmp.kbntx.ch/init.html?consent_str=' + consentString + '&consent_given=0'); + }); + it('should register the sync iframe with gdpr', function () { + let syncOptions = { + iframeEnabled: true + }; + let serverResponses = null; + let gdprConsent = { + gdprApplies: true, + consentString: consentString + }; + let uspConsent = null; + let syncs = spec.getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent); + expect(syncs).to.be.an('array').and.to.have.length(1); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.equal('https://kdmp.kbntx.ch/init.html?consent_str=' + consentString + '&gdpr=1&consent_given=0'); + }); + it('should register the sync iframe with gdpr vendor', function () { + let syncOptions = { + iframeEnabled: true + }; + let serverResponses = null; + let gdprConsent = { + gdprApplies: true, + consentString: consentString, + apiVersion: 1, + vendorData: { + vendorConsents: { + 794: 1 + } + } + }; + let uspConsent = null; + let syncs = spec.getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent); + expect(syncs).to.be.an('array').and.to.have.length(1); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.equal('https://kdmp.kbntx.ch/init.html?consent_str=' + consentString + '&gdpr=1&consent_given=1'); + }); + it('should register the sync image without gdpr', function () { + let syncOptions = { + pixelEnabled: true + }; + let serverResponses = null; + let gdprConsent = { + consentString: consentString + }; + let uspConsent = null; + let syncs = spec.getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent); + expect(syncs).to.be.an('array').and.to.have.length(1); + expect(syncs[0].type).to.equal('image'); + expect(syncs[0].url).to.equal('https://kdmp.kbntx.ch/init.png?consent_str=' + consentString + '&consent_given=0'); + }); + it('should register the sync image with gdpr', function () { + let syncOptions = { + pixelEnabled: true + }; + let serverResponses = null; + let gdprConsent = { + gdprApplies: true, + consentString: consentString + }; + let uspConsent = null; + let syncs = spec.getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent); + expect(syncs).to.be.an('array').and.to.have.length(1); + expect(syncs[0].type).to.equal('image'); + expect(syncs[0].url).to.equal('https://kdmp.kbntx.ch/init.png?consent_str=' + consentString + '&gdpr=1&consent_given=0'); + }); + it('should register the sync image with gdpr vendor', function () { + let syncOptions = { + pixelEnabled: true + }; + let serverResponses = null; + let gdprConsent = { + gdprApplies: true, + consentString: consentString, + apiVersion: 2, + vendorData: { + vendor: { + consents: { + 794: 1 + } + } + } + }; + let uspConsent = null; + let syncs = spec.getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent); + expect(syncs).to.be.an('array').and.to.have.length(1); + expect(syncs[0].type).to.equal('image'); + expect(syncs[0].url).to.equal('https://kdmp.kbntx.ch/init.png?consent_str=' + consentString + '&gdpr=1&consent_given=1'); + }); + }) +}); diff --git a/test/spec/modules/lemmaBidAdapter_spec.js b/test/spec/modules/lemmaBidAdapter_spec.js index a00c25d126c..a3a70a39731 100644 --- a/test/spec/modules/lemmaBidAdapter_spec.js +++ b/test/spec/modules/lemmaBidAdapter_spec.js @@ -23,6 +23,7 @@ describe('lemmaBidAdapter', function() { pubId: 1001, adunitId: 1, currency: 'AUD', + bidFloor: 1.3, geo: { lat: '12.3', lon: '23.7', @@ -46,6 +47,7 @@ describe('lemmaBidAdapter', function() { params: { pubId: 1001, adunitId: 1, + bidFloor: 1.3, video: { mimes: ['video/mp4', 'video/x-flv'], skippable: true, @@ -161,6 +163,7 @@ describe('lemmaBidAdapter', function() { expect(data.site.publisher.id).to.equal(bidRequests[0].params.pubId.toString()); // publisher Id expect(data.imp[0].tagid).to.equal('1'); // tagid expect(data.imp[0].bidfloorcur).to.equal(bidRequests[0].params.currency); + expect(data.imp[0].bidfloor).to.equal(bidRequests[0].params.bidFloor); }); it('Request params check without mediaTypes object', function() { var bidRequests = [{ @@ -192,6 +195,7 @@ describe('lemmaBidAdapter', function() { expect(data.site.publisher.id).to.equal(bidRequests[0].params.pubId.toString()); // publisher Id expect(data.imp[0].tagid).to.equal(undefined); // tagid expect(data.imp[0].bidfloorcur).to.equal(bidRequests[0].params.currency); + expect(data.imp[0].bidfloor).to.equal(bidRequests[0].params.bidFloor); }); it('Request params multi size format object check', function() { var bidRequests = [{ @@ -309,6 +313,77 @@ describe('lemmaBidAdapter', function() { expect(data.imp[0]['video']['w']).to.equal(videoBidRequests[0].mediaTypes.video.playerSize[0]); expect(data.imp[0]['video']['h']).to.equal(videoBidRequests[0].mediaTypes.video.playerSize[1]); }); + describe('setting imp.floor using floorModule', function() { + /* + Use the minimum value among floor from floorModule per mediaType + If params.bidFloor is set then take max(floor, min(floors from floorModule)) + set imp.bidfloor only if it is more than 0 + */ + + let newRequest; + let floorModuleTestData; + let getFloor = function(req) { + return floorModuleTestData[req.mediaType]; + }; + + beforeEach(() => { + floorModuleTestData = { + 'banner': { + 'currency': 'AUD', + 'floor': 1.50 + }, + 'video': { + 'currency': 'AUD', + 'floor': 2.00 + } + }; + newRequest = utils.deepClone(bidRequests); + newRequest[0].getFloor = getFloor; + }); + + it('bidfloor should be undefined if calculation is <= 0', function() { + floorModuleTestData.banner.floor = 0; // lowest of them all + newRequest[0].params.bidFloor = undefined; + let request = spec.buildRequests(newRequest); + let data = JSON.parse(request.data); + data = data.imp[0]; + expect(data.bidfloor).to.equal(undefined); + }); + + it('ignore floormodule o/p if floor is not number', function() { + floorModuleTestData.banner.floor = 'INR'; + newRequest[0].params.bidFloor = undefined; + let request = spec.buildRequests(newRequest); + let data = JSON.parse(request.data); + data = data.imp[0]; + expect(data.bidfloor).to.equal(undefined); // video will be lowest now + }); + + it('ignore floormodule o/p if currency is not matched', function() { + floorModuleTestData.banner.currency = 'INR'; + newRequest[0].params.bidFloor = undefined; + let request = spec.buildRequests(newRequest); + let data = JSON.parse(request.data); + data = data.imp[0]; + expect(data.bidfloor).to.equal(undefined); // video will be lowest now + }); + + it('bidFloor is not passed, use minimum from floorModule', function() { + newRequest[0].params.bidFloor = undefined; + let request = spec.buildRequests(newRequest); + let data = JSON.parse(request.data); + data = data.imp[0]; + expect(data.bidfloor).to.equal(1.5); + }); + + it('bidFloor is passed as 1, use min of floorModule as it is highest', function() { + newRequest[0].params.bidFloor = '1.0';// yes, we want it as a string + let request = spec.buildRequests(newRequest); + let data = JSON.parse(request.data); + data = data.imp[0]; + expect(data.bidfloor).to.equal(1.5); + }); + }); describe('Response checking', function() { it('should check for valid response values', function() { var request = spec.buildRequests(bidRequests); @@ -346,24 +421,6 @@ describe('lemmaBidAdapter', function() { type: 'iframe', url: syncurl_iframe }]); }); - - it('CCPA/USP', function() { - expect(spec.getUserSyncs({ iframeEnabled: true }, {}, undefined, '1NYN')).to.deep.equal([{ - type: 'iframe', url: `${syncurl_iframe}&us_privacy=1NYN` - }]); - }); - - it('GDPR', function() { - expect(spec.getUserSyncs({ iframeEnabled: true }, {}, { gdprApplies: true, consentString: 'foo' }, undefined)).to.deep.equal([{ - type: 'iframe', url: `${syncurl_iframe}&gdpr=1&gdpr_consent=foo` - }]); - expect(spec.getUserSyncs({ iframeEnabled: true }, {}, { gdprApplies: false, consentString: 'foo' }, undefined)).to.deep.equal([{ - type: 'iframe', url: `${syncurl_iframe}&gdpr=0&gdpr_consent=foo` - }]); - expect(spec.getUserSyncs({ iframeEnabled: true }, {}, { gdprApplies: true, consentString: undefined }, undefined)).to.deep.equal([{ - type: 'iframe', url: `${syncurl_iframe}&gdpr=1&gdpr_consent=` - }]); - }); }); }); }); diff --git a/test/spec/modules/liveIntentIdMinimalSystem_spec.js b/test/spec/modules/liveIntentIdMinimalSystem_spec.js new file mode 100644 index 00000000000..b0f97ae0300 --- /dev/null +++ b/test/spec/modules/liveIntentIdMinimalSystem_spec.js @@ -0,0 +1,188 @@ +import * as utils from 'src/utils.js'; +import { gdprDataHandler, uspDataHandler } from '../../../src/adapterManager.js'; +import { server } from 'test/mocks/xhr.js'; +import { liveIntentIdSubmodule, reset as resetLiveIntentIdSubmodule, storage } from 'modules/liveIntentIdSystem.js'; + +const PUBLISHER_ID = '89899'; +const defaultConfigParams = { params: {publisherId: PUBLISHER_ID} }; +const responseHeader = {'Content-Type': 'application/json'}; + +describe('LiveIntentMinimalId', function() { + let logErrorStub; + let uspConsentDataStub; + let gdprConsentDataStub; + let getCookieStub; + let getDataFromLocalStorageStub; + let imgStub; + + beforeEach(function() { + liveIntentIdSubmodule.setModuleMode('minimal'); + imgStub = sinon.stub(utils, 'triggerPixel'); + getCookieStub = sinon.stub(storage, 'getCookie'); + getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage'); + logErrorStub = sinon.stub(utils, 'logError'); + uspConsentDataStub = sinon.stub(uspDataHandler, 'getConsentData'); + gdprConsentDataStub = sinon.stub(gdprDataHandler, 'getConsentData'); + }); + + afterEach(function() { + imgStub.restore(); + getCookieStub.restore(); + getDataFromLocalStorageStub.restore(); + logErrorStub.restore(); + uspConsentDataStub.restore(); + gdprConsentDataStub.restore(); + liveIntentIdSubmodule.setModuleMode('minimal'); + resetLiveIntentIdSubmodule(); + }); + it('should not fire an event when getId', function() { + uspConsentDataStub.returns('1YNY'); + gdprConsentDataStub.returns({ + gdprApplies: true, + consentString: 'consentDataString' + }) + liveIntentIdSubmodule.getId(defaultConfigParams); + 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.undefined; + }); + + it('should initialize LiveConnect and send no data', function() { + liveIntentIdSubmodule.getId(defaultConfigParams); + liveIntentIdSubmodule.decode({}, defaultConfigParams); + liveIntentIdSubmodule.getId(defaultConfigParams); + liveIntentIdSubmodule.decode({}, defaultConfigParams); + expect(server.requests.length).to.be.eq(0); + }); + + it('should call the Custom URL of the LiveIntent Identity Exchange endpoint', function() { + getCookieStub.returns(null); + let callBackSpy = sinon.spy(); + let submoduleCallback = liveIntentIdSubmodule.getId({ params: {...defaultConfigParams.params, ...{'url': 'https://dummy.liveintent.com/idex'}} }).callback; + submoduleCallback(callBackSpy); + let request = server.requests[0]; + expect(request.url).to.be.eq('https://dummy.liveintent.com/idex/prebid/89899'); + request.respond( + 200, + responseHeader, + JSON.stringify({}) + ); + expect(callBackSpy.calledOnce).to.be.true; + }); + + it('should call the default url of the LiveIntent Identity Exchange endpoint, with a partner', function() { + getCookieStub.returns(null); + let callBackSpy = sinon.spy(); + let submoduleCallback = liveIntentIdSubmodule.getId({ params: { + ...defaultConfigParams.params, + ...{ + 'url': 'https://dummy.liveintent.com/idex', + 'partner': 'rubicon' + } + } }).callback; + submoduleCallback(callBackSpy); + let request = server.requests[0]; + expect(request.url).to.be.eq('https://dummy.liveintent.com/idex/rubicon/89899'); + request.respond( + 200, + responseHeader, + JSON.stringify({}) + ); + expect(callBackSpy.calledOnce).to.be.true; + }); + + it('should call the LiveIntent Identity Exchange endpoint, with no additional query params', function() { + getCookieStub.returns(null); + let callBackSpy = sinon.spy(); + let submoduleCallback = liveIntentIdSubmodule.getId(defaultConfigParams).callback; + submoduleCallback(callBackSpy); + let request = server.requests[0]; + expect(request.url).to.be.eq('https://idx.liadm.com/idex/prebid/89899'); + request.respond( + 200, + responseHeader, + JSON.stringify({}) + ); + expect(callBackSpy.calledOnce).to.be.true; + }); + + it('should log an error and continue to callback if ajax request errors', function() { + getCookieStub.returns(null); + let callBackSpy = sinon.spy(); + let submoduleCallback = liveIntentIdSubmodule.getId(defaultConfigParams).callback; + submoduleCallback(callBackSpy); + let request = server.requests[0]; + expect(request.url).to.be.eq('https://idx.liadm.com/idex/prebid/89899'); + request.respond( + 503, + responseHeader, + 'Unavailable' + ); + expect(logErrorStub.calledOnce).to.be.true; + expect(callBackSpy.calledOnce).to.be.true; + }); + + it('should include the LiveConnect identifier when calling the LiveIntent Identity Exchange endpoint', function() { + const oldCookie = 'a-xxxx--123e4567-e89b-12d3-a456-426655440000' + getDataFromLocalStorageStub.withArgs('_li_duid').returns(oldCookie); + let callBackSpy = sinon.spy(); + let submoduleCallback = liveIntentIdSubmodule.getId(defaultConfigParams).callback; + submoduleCallback(callBackSpy); + let request = server.requests[0]; + expect(request.url).to.be.eq(`https://idx.liadm.com/idex/prebid/89899?duid=${oldCookie}`); + request.respond( + 200, + responseHeader, + JSON.stringify({}) + ); + expect(callBackSpy.calledOnce).to.be.true; + }); + + it('should include the LiveConnect identifier and additional Identifiers to resolve', function() { + const oldCookie = 'a-xxxx--123e4567-e89b-12d3-a456-426655440000' + getDataFromLocalStorageStub.withArgs('_li_duid').returns(oldCookie); + getDataFromLocalStorageStub.withArgs('_thirdPC').returns('third-pc'); + const configParams = { params: { + ...defaultConfigParams.params, + ...{ + 'identifiersToResolve': ['_thirdPC'] + } + }}; + let callBackSpy = sinon.spy(); + let submoduleCallback = liveIntentIdSubmodule.getId(configParams).callback; + submoduleCallback(callBackSpy); + let request = server.requests[0]; + expect(request.url).to.be.eq(`https://idx.liadm.com/idex/prebid/89899?duid=${oldCookie}&_thirdPC=third-pc`); + request.respond( + 200, + responseHeader, + JSON.stringify({}) + ); + expect(callBackSpy.calledOnce).to.be.true; + }); + + it('should include an additional identifier value to resolve even if it is an object', function() { + getCookieStub.returns(null); + getDataFromLocalStorageStub.withArgs('_thirdPC').returns({'key': 'value'}); + const configParams = { params: { + ...defaultConfigParams.params, + ...{ + 'identifiersToResolve': ['_thirdPC'] + } + }}; + let callBackSpy = sinon.spy(); + let submoduleCallback = liveIntentIdSubmodule.getId(configParams).callback; + submoduleCallback(callBackSpy); + let request = server.requests[0]; + expect(request.url).to.be.eq('https://idx.liadm.com/idex/prebid/89899?_thirdPC=%7B%22key%22%3A%22value%22%7D'); + request.respond( + 200, + responseHeader, + JSON.stringify({}) + ); + expect(callBackSpy.calledOnce).to.be.true; + }); +}); diff --git a/test/spec/modules/liveIntentIdSystem_spec.js b/test/spec/modules/liveIntentIdSystem_spec.js index aae60cbcd19..f1de2f3bf93 100644 --- a/test/spec/modules/liveIntentIdSystem_spec.js +++ b/test/spec/modules/liveIntentIdSystem_spec.js @@ -2,7 +2,8 @@ import { liveIntentIdSubmodule, reset as resetLiveIntentIdSubmodule, storage } f import * as utils from 'src/utils.js'; import { gdprDataHandler, uspDataHandler } from '../../../src/adapterManager.js'; import { server } from 'test/mocks/xhr.js'; - +resetLiveIntentIdSubmodule(); +liveIntentIdSubmodule.setModuleMode('standard') const PUBLISHER_ID = '89899'; const defaultConfigParams = { params: {publisherId: PUBLISHER_ID} }; const responseHeader = {'Content-Type': 'application/json'} @@ -16,6 +17,7 @@ describe('LiveIntentId', function() { let imgStub; beforeEach(function() { + liveIntentIdSubmodule.setModuleMode('standard'); imgStub = sinon.stub(utils, 'triggerPixel'); getCookieStub = sinon.stub(storage, 'getCookie'); getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage'); @@ -34,7 +36,7 @@ describe('LiveIntentId', function() { resetLiveIntentIdSubmodule(); }); - it('should initialize LiveConnect with a privacy string when getId, and include it in the resolution request', function() { + it('should initialize LiveConnect with a privacy string when getId, and include it in the resolution request', function () { uspConsentDataStub.returns('1YNY'); gdprConsentDataStub.returns({ gdprApplies: true, @@ -45,12 +47,16 @@ describe('LiveIntentId', function() { submoduleCallback(callBackSpy); let request = server.requests[1]; expect(request.url).to.match(/.*us_privacy=1YNY.*&gdpr=1&gdpr_consent=consentDataString.*/); + const response = { + unifiedId: 'a_unified_id', + segments: [123, 234] + } request.respond( 200, responseHeader, - JSON.stringify({}) + JSON.stringify(response) ); - expect(callBackSpy.calledOnce).to.be.true; + expect(callBackSpy.calledOnceWith(response)).to.be.true; }); it('should fire an event when getId', function() { @@ -129,11 +135,10 @@ describe('LiveIntentId', function() { let request = server.requests[1]; expect(request.url).to.be.eq('https://dummy.liveintent.com/idex/prebid/89899'); request.respond( - 200, - responseHeader, - JSON.stringify({}) + 204, + responseHeader ); - expect(callBackSpy.calledOnce).to.be.true; + expect(callBackSpy.calledOnceWith({})).to.be.true; }); it('should call the default url of the LiveIntent Identity Exchange endpoint, with a partner', function() { diff --git a/test/spec/modules/livewrappedBidAdapter_spec.js b/test/spec/modules/livewrappedBidAdapter_spec.js index 7983e8fbb0b..fa00a359191 100644 --- a/test/spec/modules/livewrappedBidAdapter_spec.js +++ b/test/spec/modules/livewrappedBidAdapter_spec.js @@ -781,61 +781,45 @@ describe('Livewrapped adapter tests', function () { }); }); - it('should make use of Id5-Id if available', function() { + it('should make use of user ids if available', function() { sandbox.stub(utils, 'isSafariBrowser').callsFake(() => false); sandbox.stub(storage, 'cookiesAreEnabled').callsFake(() => true); let testbidRequest = clone(bidderRequest); delete testbidRequest.bids[0].params.userId; - testbidRequest.bids[0].userId = {}; - testbidRequest.bids[0].userId.id5id = { uid: 'id5-user-id' }; - let result = spec.buildRequests(testbidRequest.bids, testbidRequest); - let data = JSON.parse(result.data); - - expect(data.rtbData.user.ext.eids).to.deep.equal([{ - 'source': 'id5-sync.com', - 'uids': [{ - 'id': 'id5-user-id', - 'atype': 1 - }] - }]); - }); - - it('should make use of publisher common Id if available', function() { - sandbox.stub(utils, 'isSafariBrowser').callsFake(() => false); - sandbox.stub(storage, 'cookiesAreEnabled').callsFake(() => true); - let testbidRequest = clone(bidderRequest); - delete testbidRequest.bids[0].params.userId; - testbidRequest.bids[0].userId = {}; - testbidRequest.bids[0].userId.pubcid = 'publisher-common-id'; - let result = spec.buildRequests(testbidRequest.bids, testbidRequest); - let data = JSON.parse(result.data); - - expect(data.rtbData.user.ext.eids).to.deep.equal([{ - 'source': 'pubcid.org', - 'uids': [{ - 'id': 'publisher-common-id', - 'atype': 1 - }] - }]); - }); + testbidRequest.bids[0].userIdAsEids = [ + { + 'source': 'id5-sync.com', + 'uids': [{ + 'id': 'ID5-id', + 'atype': 1, + 'ext': { + 'linkType': 2 + } + }] + }, + { + 'source': 'pubcid.org', + 'uids': [{ + 'id': 'publisher-common-id', + 'atype': 1 + }] + }, + { + 'source': 'sharedid.org', + 'uids': [{ + 'id': 'sharedid', + 'atype': 1, + 'ext': { + 'third': 'sharedid' + } + }] + } + ]; - it('should make use of criteoId if available', function() { - sandbox.stub(utils, 'isSafariBrowser').callsFake(() => false); - sandbox.stub(storage, 'cookiesAreEnabled').callsFake(() => true); - let testbidRequest = clone(bidderRequest); - delete testbidRequest.bids[0].params.userId; - testbidRequest.bids[0].userId = {}; - testbidRequest.bids[0].userId.criteoId = 'criteo-id'; let result = spec.buildRequests(testbidRequest.bids, testbidRequest); let data = JSON.parse(result.data); - expect(data.rtbData.user.ext.eids).to.deep.equal([{ - 'source': 'criteo.com', - 'uids': [{ - 'id': 'criteo-id', - 'atype': 1 - }] - }]); + expect(data.rtbData.user.ext.eids).to.deep.equal(testbidRequest.bids[0].userIdAsEids); }); it('should send schain object if available', function() { diff --git a/test/spec/modules/loganBidAdapter_spec.js b/test/spec/modules/loganBidAdapter_spec.js new file mode 100644 index 00000000000..96029294df0 --- /dev/null +++ b/test/spec/modules/loganBidAdapter_spec.js @@ -0,0 +1,329 @@ +import {expect} from 'chai'; +import {spec} from '../../../modules/loganBidAdapter.js'; +import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; + +describe('LoganBidAdapter', function () { + const bid = { + bidId: '23fhj33i987f', + bidder: 'logan', + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + placementId: 783, + traffic: BANNER + } + }; + + const bidderRequest = { + refererInfo: { + referer: 'test.com' + } + }; + + describe('isBidRequestValid', function () { + it('Should return true if there are bidId, params and key parameters present', function () { + expect(spec.isBidRequestValid(bid)).to.be.true; + }); + it('Should return false if at least one of parameters is not present', function () { + delete bid.params.placementId; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + }); + + describe('buildRequests', function () { + let serverRequest = spec.buildRequests([bid], bidderRequest); + it('Creates a ServerRequest object with method, URL and data', function () { + expect(serverRequest).to.exist; + expect(serverRequest.method).to.exist; + expect(serverRequest.url).to.exist; + expect(serverRequest.data).to.exist; + }); + it('Returns POST method', function () { + expect(serverRequest.method).to.equal('POST'); + }); + it('Returns valid URL', function () { + expect(serverRequest.url).to.equal('https://USeast2.logan.ai/?c=o&m=multi'); + }); + it('Returns valid data if array of bids is valid', function () { + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements'); + expect(data.deviceWidth).to.be.a('number'); + expect(data.deviceHeight).to.be.a('number'); + expect(data.language).to.be.a('string'); + expect(data.secure).to.be.within(0, 1); + expect(data.host).to.be.a('string'); + expect(data.page).to.be.a('string'); + expect(data.gdpr).to.not.exist; + expect(data.ccpa).to.not.exist; + let placement = data['placements'][0]; + expect(placement).to.have.keys('placementId', 'bidId', 'traffic', 'sizes', 'schain'); + expect(placement.placementId).to.equal(783); + expect(placement.bidId).to.equal('23fhj33i987f'); + expect(placement.traffic).to.equal(BANNER); + expect(placement.schain).to.be.an('object'); + expect(placement.sizes).to.be.an('array'); + }); + + it('Returns valid data for mediatype video', function () { + const playerSize = [300, 300]; + bid.mediaTypes = {}; + bid.params.traffic = VIDEO; + bid.mediaTypes[VIDEO] = { + playerSize + }; + serverRequest = spec.buildRequests([bid], bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + let placement = data['placements'][0]; + expect(placement).to.be.an('object'); + expect(placement).to.have.keys('placementId', 'bidId', 'traffic', 'wPlayer', 'hPlayer', 'schain'); + expect(placement.traffic).to.equal(VIDEO); + expect(placement.wPlayer).to.equal(playerSize[0]); + expect(placement.hPlayer).to.equal(playerSize[1]); + }); + + it('Returns valid data for mediatype native', function () { + const native = { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + }; + + bid.mediaTypes = {}; + bid.params.traffic = NATIVE; + bid.mediaTypes[NATIVE] = native; + serverRequest = spec.buildRequests([bid], bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + let placement = data['placements'][0]; + expect(placement).to.be.an('object'); + expect(placement).to.have.keys('placementId', 'bidId', 'traffic', 'native', 'schain'); + expect(placement.traffic).to.equal(NATIVE); + expect(placement.native).to.equal(native); + }); + + it('Returns data with gdprConsent and without uspConsent', function () { + bidderRequest.gdprConsent = 'test'; + serverRequest = spec.buildRequests([bid], bidderRequest); + let data = serverRequest.data; + expect(data.gdpr).to.exist; + expect(data.gdpr).to.be.a('string'); + expect(data.gdpr).to.equal(bidderRequest.gdprConsent); + expect(data.ccpa).to.not.exist; + delete bidderRequest.gdprConsent; + }); + + it('Returns data with uspConsent and without gdprConsent', function () { + bidderRequest.uspConsent = 'test'; + serverRequest = spec.buildRequests([bid], bidderRequest); + let data = serverRequest.data; + expect(data.ccpa).to.exist; + expect(data.ccpa).to.be.a('string'); + expect(data.ccpa).to.equal(bidderRequest.uspConsent); + expect(data.gdpr).to.not.exist; + }); + + it('Returns empty data if no valid requests are passed', function () { + serverRequest = spec.buildRequests([]); + let data = serverRequest.data; + expect(data.placements).to.be.an('array').that.is.empty; + }); + }); + describe('interpretResponse', function () { + it('Should interpret banner response', function () { + const banner = { + body: [{ + mediaType: 'banner', + width: 300, + height: 250, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let bannerResponses = spec.interpretResponse(banner); + expect(bannerResponses).to.be.an('array').that.is.not.empty; + let dataItem = bannerResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType'); + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.4); + expect(dataItem.width).to.equal(300); + expect(dataItem.height).to.equal(250); + expect(dataItem.ad).to.equal('Test'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + }); + it('Should interpret video response', function () { + const video = { + body: [{ + vastUrl: 'test.com', + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let videoResponses = spec.interpretResponse(video); + expect(videoResponses).to.be.an('array').that.is.not.empty; + + let dataItem = videoResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'vastUrl', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType'); + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.5); + expect(dataItem.vastUrl).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + }); + it('Should interpret native response', function () { + const native = { + body: [{ + mediaType: 'native', + native: { + clickUrl: 'test.com', + title: 'Test', + image: 'test.com', + impressionTrackers: ['test.com'], + }, + ttl: 120, + cpm: 0.4, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + }] + }; + let nativeResponses = spec.interpretResponse(native); + expect(nativeResponses).to.be.an('array').that.is.not.empty; + + let dataItem = nativeResponses[0]; + expect(dataItem).to.have.keys('requestId', 'cpm', 'ttl', 'creativeId', 'netRevenue', 'currency', 'mediaType', 'native'); + expect(dataItem.native).to.have.keys('clickUrl', 'impressionTrackers', 'title', 'image') + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.4); + expect(dataItem.native.clickUrl).to.equal('test.com'); + expect(dataItem.native.title).to.equal('Test'); + expect(dataItem.native.image).to.equal('test.com'); + expect(dataItem.native.impressionTrackers).to.be.an('array').that.is.not.empty; + expect(dataItem.native.impressionTrackers[0]).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + }); + it('Should return an empty array if invalid banner response is passed', function () { + const invBanner = { + body: [{ + width: 300, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + + let serverResponses = spec.interpretResponse(invBanner); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid video response is passed', function () { + const invVideo = { + body: [{ + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let serverResponses = spec.interpretResponse(invVideo); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid native response is passed', function () { + const invNative = { + body: [{ + mediaType: 'native', + clickUrl: 'test.com', + title: 'Test', + impressionTrackers: ['test.com'], + ttl: 120, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + }] + }; + let serverResponses = spec.interpretResponse(invNative); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid response is passed', function () { + const invalid = { + body: [{ + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let serverResponses = spec.interpretResponse(invalid); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + }); + describe('getUserSyncs', function() { + it('Should return array of objects with proper sync config , include GDPR', function() { + const syncData = spec.getUserSyncs({}, {}, { + consentString: 'ALL', + gdprApplies: true, + }, {}); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object') + expect(syncData[0].type).to.be.a('string') + expect(syncData[0].type).to.equal('iframe') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal('https://ssp-cookie.logan.ai/html?src=pbjs&gdpr=1&gdpr_consent=ALL') + }); + it('Should return array of objects with proper sync config , include CCPA', function() { + const syncData = spec.getUserSyncs({}, {}, {}, { + consentString: '1NNN' + }); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object') + expect(syncData[0].type).to.be.a('string') + expect(syncData[0].type).to.equal('iframe') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal('https://ssp-cookie.logan.ai/html?src=pbjs&ccpa_consent=1NNN') + }); + }); +}); diff --git a/test/spec/modules/lotamePanoramaIdSystem_spec.js b/test/spec/modules/lotamePanoramaIdSystem_spec.js index c6d3383374a..5bbec1ec26f 100644 --- a/test/spec/modules/lotamePanoramaIdSystem_spec.js +++ b/test/spec/modules/lotamePanoramaIdSystem_spec.js @@ -440,10 +440,289 @@ describe('LotameId', function() { }); }); - it('should retrieve the id when decode is called', function() { - var id = lotamePanoramaIdSubmodule.decode('1234'); - expect(id).to.be.eql({ - 'lotamePanoramaId': '1234' + describe('when gdpr applies and falls back to eupubconsent cookie', function () { + let request; + let callBackSpy = sinon.spy(); + let consentData = { + gdprApplies: true, + consentString: undefined + }; + + beforeEach(function () { + getCookieStub + .withArgs('eupubconsent-v2') + .returns('consentGiven'); + + let submoduleCallback = lotamePanoramaIdSubmodule.getId({}, consentData).callback; + submoduleCallback(callBackSpy); + + // the contents of the response don't matter for this + request = server.requests[0]; + request.respond(200, responseHeader, ''); + }); + + it('should call the remote server when getId is called', function () { + expect(callBackSpy.calledOnce).to.be.true; + }); + + it('should pass the gdpr consent string back', function() { + expect(request.url).to.be.eq( + 'https://id.crwdcntrl.net/id?gdpr_applies=true&gdpr_consent=consentGiven' + ); + }); + }); + + describe('when gdpr applies and falls back to euconsent cookie', function () { + let request; + let callBackSpy = sinon.spy(); + let consentData = { + gdprApplies: true, + consentString: undefined + }; + + beforeEach(function () { + getCookieStub + .withArgs('euconsent-v2') + .returns('consentGiven'); + + let submoduleCallback = lotamePanoramaIdSubmodule.getId({}, consentData).callback; + submoduleCallback(callBackSpy); + + // the contents of the response don't matter for this + request = server.requests[0]; + request.respond(200, responseHeader, ''); + }); + + it('should call the remote server when getId is called', function () { + expect(callBackSpy.calledOnce).to.be.true; + }); + + it('should pass the gdpr consent string back', function() { + expect(request.url).to.be.eq( + 'https://id.crwdcntrl.net/id?gdpr_applies=true&gdpr_consent=consentGiven' + ); + }); + }); + + describe('when gdpr applies but no consent string is available', function () { + let request; + let callBackSpy = sinon.spy(); + let consentData = { + gdprApplies: true, + consentString: undefined + }; + + beforeEach(function () { + let submoduleCallback = lotamePanoramaIdSubmodule.getId({}, consentData).callback; + submoduleCallback(callBackSpy); + + // the contents of the response don't matter for this + request = server.requests[0]; + request.respond(200, responseHeader, ''); + }); + + it('should call the remote server when getId is called', function () { + expect(callBackSpy.calledOnce).to.be.true; + }); + + it('should not include the gdpr consent string on the url', function() { + expect(request.url).to.be.eq( + 'https://id.crwdcntrl.net/id?gdpr_applies=true' + ); + }); + }); + + describe('when no consentData and falls back to eupubconsent cookie', function () { + let request; + let callBackSpy = sinon.spy(); + let consentData; + + beforeEach(function () { + getCookieStub + .withArgs('eupubconsent-v2') + .returns('consentGiven'); + + let submoduleCallback = lotamePanoramaIdSubmodule.getId({}, consentData).callback; + submoduleCallback(callBackSpy); + + // the contents of the response don't matter for this + request = server.requests[0]; + request.respond(200, responseHeader, ''); + }); + + it('should call the remote server when getId is called', function () { + expect(callBackSpy.calledOnce).to.be.true; + }); + + it('should pass the gdpr consent string back', function() { + expect(request.url).to.be.eq( + 'https://id.crwdcntrl.net/id?gdpr_consent=consentGiven' + ); + }); + }); + + describe('when no consentData and falls back to euconsent cookie', function () { + let request; + let callBackSpy = sinon.spy(); + let consentData; + + beforeEach(function () { + getCookieStub + .withArgs('euconsent-v2') + .returns('consentGiven'); + + let submoduleCallback = lotamePanoramaIdSubmodule.getId({}, consentData).callback; + submoduleCallback(callBackSpy); + + // the contents of the response don't matter for this + request = server.requests[0]; + request.respond(200, responseHeader, ''); + }); + + it('should call the remote server when getId is called', function () { + expect(callBackSpy.calledOnce).to.be.true; + }); + + it('should pass the gdpr consent string back', function() { + expect(request.url).to.be.eq( + 'https://id.crwdcntrl.net/id?gdpr_consent=consentGiven' + ); + }); + }); + + describe('when no consentData and no cookies', function () { + let request; + let callBackSpy = sinon.spy(); + let consentData; + + beforeEach(function () { + let submoduleCallback = lotamePanoramaIdSubmodule.getId({}, consentData).callback; + submoduleCallback(callBackSpy); + + // the contents of the response don't matter for this + request = server.requests[0]; + request.respond(200, responseHeader, ''); + }); + + it('should call the remote server when getId is called', function () { + expect(callBackSpy.calledOnce).to.be.true; + }); + + it('should pass the gdpr consent string back', function() { + expect(request.url).to.be.eq('https://id.crwdcntrl.net/id'); + }); + }); + + describe('with an empty cache, ignore profile id for error 111', function () { + let request; + let callBackSpy = sinon.spy(); + + beforeEach(function () { + let submoduleCallback = lotamePanoramaIdSubmodule.getId({}).callback; + submoduleCallback(callBackSpy); + + request = server.requests[0]; + + request.respond( + 200, + responseHeader, + JSON.stringify({ + profile_id: '4ec137245858469eb94a4e248f238694', + expiry_ts: 10, + errors: [111], + core_id: + 'ca22992567e3cd4d116a5899b88a55d0d857a23610db939ae6ac13ba2335d87a', + }) + ); + }); + + it('should not save the first party id', function () { + sinon.assert.neverCalledWith( + setLocalStorageStub, + '_cc_id', + '4ec137245858469eb94a4e248f238694' + ); + sinon.assert.neverCalledWith( + setCookieStub, + '_cc_id', + '4ec137245858469eb94a4e248f238694' + ); + }); + + it('should save the expiry', function () { + sinon.assert.calledWith(setLocalStorageStub, 'panoramaId_expiry', 10); + + sinon.assert.calledWith(setCookieStub, 'panoramaId_expiry', 10); + }); + + it('should save the id', function () { + sinon.assert.calledWith( + setLocalStorageStub, + 'panoramaId', + 'ca22992567e3cd4d116a5899b88a55d0d857a23610db939ae6ac13ba2335d87a' + ); + + sinon.assert.calledWith( + setCookieStub, + 'panoramaId', + 'ca22992567e3cd4d116a5899b88a55d0d857a23610db939ae6ac13ba2335d87a' + ); + }); + }); + + describe('receives an optout request with an error 111', function () { + let request; + let callBackSpy = sinon.spy(); + + beforeEach(function () { + getCookieStub.withArgs('panoramaId_expiry').returns('1000'); + getCookieStub + .withArgs('panoramaId') + .returns( + 'ca22992567e3cd4d116a5899b88a55d0d857a23610db939ae6ac13ba2335d87d' + ); + + let submoduleCallback = lotamePanoramaIdSubmodule.getId({}).callback; + submoduleCallback(callBackSpy); + + request = server.requests[0]; + + request.respond( + 200, + responseHeader, + JSON.stringify({ + errors: [111], + expiry_ts: Date.now() + 30 * 24 * 60 * 60 * 1000, + }) + ); + }); + + it('should call the remote server when getId is called', function () { + expect(callBackSpy.calledOnce).to.be.true; + }); + + it('should clear the panorama id', function () { + sinon.assert.calledWith(removeFromLocalStorageStub, 'panoramaId'); + + sinon.assert.calledWith( + setCookieStub, + 'panoramaId', + '', + 'Thu, 01 Jan 1970 00:00:00 GMT', + 'Lax' + ); + }); + + it('should not clear the profile id', function () { + sinon.assert.neverCalledWith(removeFromLocalStorageStub, '_cc_id'); + + sinon.assert.neverCalledWith( + setCookieStub, + '_cc_id', + '', + 'Thu, 01 Jan 1970 00:00:00 GMT', + 'Lax' + ); }); }); }); diff --git a/test/spec/modules/malltvBidAdapter_spec.js b/test/spec/modules/malltvBidAdapter_spec.js index e1e9ad867e7..ffe08ad1a5e 100644 --- a/test/spec/modules/malltvBidAdapter_spec.js +++ b/test/spec/modules/malltvBidAdapter_spec.js @@ -34,9 +34,7 @@ describe('malltvAdapterTest', () => { it('bidRequest without propertyId or placementId', () => { expect(spec.isBidRequestValid({ bidder: 'malltv', - params: { - propertyId: '{propertyId}', - } + params: {} })).to.equal(false); }); }); @@ -46,7 +44,17 @@ describe('malltvAdapterTest', () => { 'bidder': 'malltv', 'params': { 'propertyId': '{propertyId}', - 'placementId': '{placementId}' + 'placementId': '{placementId}', + 'data': { + 'catalogs': [{ + 'catalogId': 1, + 'items': ['1', '2', '3'] + }], + 'inventory': { + 'category': ['category1', 'category2'], + 'query': ['query'] + } + } }, 'adUnitCode': 'hb-leaderboard', 'transactionId': 'b6b889bb-776c-48fd-bc7b-d11a1cf0425e', @@ -86,6 +94,20 @@ describe('malltvAdapterTest', () => { expect(requestItem.data.placements[0].sizes).to.equal('300x250'); }); }); + + it('bidRequest data param', () => { + const requests = spec.buildRequests(bidRequests); + requests.forEach((requestItem) => { + expect(requestItem.data.data).to.exist; + expect(requestItem.data.data.catalogs).to.exist; + expect(requestItem.data.data.inventory).to.exist; + expect(requestItem.data.data.catalogs.length).to.equal(1); + expect(requestItem.data.data.catalogs[0].items.length).to.equal(3); + expect(Object.keys(requestItem.data.data.inventory).length).to.equal(2); + expect(requestItem.data.data.inventory.category.length).to.equal(2); + expect(requestItem.data.data.inventory.query.length).to.equal(1); + }); + }); }); describe('interpretResponse', () => { diff --git a/test/spec/modules/marsmediaBidAdapter_spec.js b/test/spec/modules/marsmediaBidAdapter_spec.js index b4c2fe68f34..cf074b0f3d6 100644 --- a/test/spec/modules/marsmediaBidAdapter_spec.js +++ b/test/spec/modules/marsmediaBidAdapter_spec.js @@ -1,11 +1,41 @@ -import {spec} from '../../../modules/marsmediaBidAdapter.js'; -import * as utils from '../../../src/utils.js'; -import * as sinon from 'sinon'; +import { spec } from 'modules/marsmediaBidAdapter.js'; +import * as utils from 'src/utils.js'; +import { config } from 'src/config.js'; -var r1adapter = spec; +var marsAdapter = spec; describe('marsmedia adapter tests', function () { + let element, win; + let sandbox; + beforeEach(function() { + element = { + x: 0, + y: 0, + + width: 0, + height: 0, + + getBoundingClientRect: () => { + return { + width: element.width, + height: element.height, + + left: element.x, + top: element.y, + right: element.x + element.width, + bottom: element.y + element.height + }; + } + }; + win = { + document: { + visibilityState: 'visible' + }, + + innerWidth: 800, + innerHeight: 600 + }; this.defaultBidderRequest = { 'refererInfo': { 'referer': 'Reference Page', @@ -15,28 +45,40 @@ describe('marsmedia adapter tests', function () { ] } }; + + this.defaultBidRequestList = [ + { + 'bidder': 'marsmedia', + 'params': { + 'zoneId': 9999 + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250]] + } + }, + 'adUnitCode': 'Unit-Code', + 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + 'bidderRequestId': '418b37f85e772c', + 'auctionId': '18fd8b8b0bd757', + 'bidRequestsCount': 1, + 'bidId': '51ef8751f9aead' + } + ]; + + sandbox = sinon.sandbox.create(); + sandbox.stub(document, 'getElementById').withArgs('Unit-Code').returns(element); + sandbox.stub(utils, 'getWindowTop').returns(win); + sandbox.stub(utils, 'getWindowSelf').returns(win); + }); + + afterEach(function() { + sandbox.restore(); }); describe('Verify 1.0 POST Banner Bid Request', function () { it('buildRequests works', function () { - var bidRequestList = [ - { - 'bidder': 'marsmedia', - 'params': { - 'zoneId': 9999 - }, - 'mediaType': 'banner', - 'adUnitCode': 'div-gpt-ad-1438287399331-0', - 'sizes': [[300, 250]], - 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', - 'bidderRequestId': '418b37f85e772c', - 'auctionId': '18fd8b8b0bd757', - 'bidRequestsCount': 1, - 'bidId': '51ef8751f9aead' - } - ]; - - var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + var bidRequest = marsAdapter.buildRequests(this.defaultBidRequestList, this.defaultBidderRequest); expect(bidRequest.url).to.have.string('https://hb.go2speed.media/bidder/?bid=3mhdom&zoneId=9999&hbv='); expect(bidRequest.method).to.equal('POST'); @@ -52,11 +94,11 @@ describe('marsmedia adapter tests', function () { expect(openrtbRequest.imp[0].ext.bidder.zoneId).to.equal(9999); }); - it('interpretResponse works', function() { + /* it('interpretResponse works', function() { var bidList = { 'body': [ { - 'impid': 'div-gpt-ad-1438287399331-0', + 'impid': 'Unit-Code', 'w': 300, 'h': 250, 'adm': '
My Compelling Ad
', @@ -67,7 +109,7 @@ describe('marsmedia adapter tests', function () { ] }; - var bannerBids = r1adapter.interpretResponse(bidList); + var bannerBids = marsAdapter.interpretResponse(bidList); expect(bannerBids.length).to.equal(1); const bid = bannerBids[0]; @@ -78,7 +120,7 @@ describe('marsmedia adapter tests', function () { expect(bid.netRevenue).to.equal(true); expect(bid.cpm).to.equal(1.0); expect(bid.ttl).to.equal(350); - }); + }); */ }); describe('Verify POST Video Bid Request', function() { @@ -95,7 +137,7 @@ describe('marsmedia adapter tests', function () { 'context': 'instream' } }, - 'adUnitCode': 'div-gpt-ad-1438287399331-1', + 'adUnitCode': 'Unit-Code', 'sizes': [ [300, 250] ], @@ -107,7 +149,7 @@ describe('marsmedia adapter tests', function () { } ]; - var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + var bidRequest = marsAdapter.buildRequests(bidRequestList, this.defaultBidderRequest); expect(bidRequest.url).to.have.string('https://hb.go2speed.media/bidder/?bid=3mhdom&zoneId=9999&hbv='); expect(bidRequest.method).to.equal('POST'); @@ -132,7 +174,7 @@ describe('marsmedia adapter tests', function () { var bidList = { 'body': [ { - 'impid': 'div-gpt-ad-1438287399331-1', + 'impid': 'Unit-Code', 'price': 1, 'adm': 'https://example.com/', 'adomain': [ @@ -147,7 +189,7 @@ describe('marsmedia adapter tests', function () { ] }; - var videoBids = r1adapter.interpretResponse(bidList); + var videoBids = marsAdapter.interpretResponse(bidList); expect(videoBids.length).to.equal(1); const bid = videoBids[0]; @@ -166,7 +208,7 @@ describe('marsmedia adapter tests', function () { var bidList = { 'body': [ { - 'impid': 'div-gpt-ad-1438287399331-1', + 'impid': 'Unit-Code', 'price': 1, 'adm': '', 'adomain': [ @@ -181,7 +223,7 @@ describe('marsmedia adapter tests', function () { ] }; - var videoBids = r1adapter.interpretResponse(bidList); + var videoBids = marsAdapter.interpretResponse(bidList); expect(videoBids.length).to.equal(1); const bid = videoBids[0]; @@ -199,26 +241,6 @@ describe('marsmedia adapter tests', function () { describe('misc buildRequests', function() { it('should send GDPR Consent data to Marsmedia tag', function () { - var bidRequestList = [ - { - 'bidder': 'marsmedia', - 'params': { - 'zoneId': 9999 - }, - 'mediaTypes': { - 'banner': { - 'sizes': [[300, 250]] - } - }, - 'adUnitCode': 'div-gpt-ad-1438287399331-3', - 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', - 'bidderRequestId': '418b37f85e772c', - 'auctionId': '18fd8b8b0bd757', - 'bidRequestsCount': 1, - 'bidId': '51ef8751f9aead' - } - ]; - var consentString = 'testConsentString'; var gdprBidderRequest = this.defaultBidderRequest; gdprBidderRequest.gdprConsent = { @@ -226,13 +248,50 @@ describe('marsmedia adapter tests', function () { 'consentString': consentString }; - var bidRequest = r1adapter.buildRequests(bidRequestList, gdprBidderRequest); + var bidRequest = marsAdapter.buildRequests(this.defaultBidRequestList, gdprBidderRequest); const openrtbRequest = JSON.parse(bidRequest.data); expect(openrtbRequest.user.ext.consent).to.equal(consentString); expect(openrtbRequest.regs.ext.gdpr).to.equal(true); }); + it('should have CCPA Consent if defined', function () { + const ccpaBidderRequest = this.defaultBidderRequest; + ccpaBidderRequest.uspConsent = '1YYN'; + const bidRequest = marsAdapter.buildRequests(this.defaultBidRequestList, ccpaBidderRequest); + const openrtbRequest = JSON.parse(bidRequest.data); + + expect(openrtbRequest.regs.ext.us_privacy).to.equal('1YYN'); + }); + + it('should submit coppa if set in config', function () { + sinon.stub(config, 'getConfig') + .withArgs('coppa') + .returns(true); + const request = marsAdapter.buildRequests(this.defaultBidRequestList, this.defaultBidderRequest); + const requestparse = JSON.parse(request.data); + expect(requestparse.regs.coppa).to.equal(1); + config.getConfig.restore(); + }); + + it('should process floors module if available', function() { + const floorBidderRequest = this.defaultBidRequestList; + const floorInfo = { + currency: 'USD', + floor: 1.20 + }; + floorBidderRequest[0].getFloor = () => floorInfo; + const request = marsAdapter.buildRequests(floorBidderRequest, this.defaultBidderRequest); + const requestparse = JSON.parse(request.data); + expect(requestparse.imp[0].bidfloor).to.equal(1.20); + }); + + it('should have 0 bidfloor value', function() { + const request = marsAdapter.buildRequests(this.defaultBidRequestList, this.defaultBidderRequest); + const requestparse = JSON.parse(request.data); + expect(requestparse.imp[0].bidfloor).to.equal(0); + }); + it('prefer 2.0 sizes', function () { var bidRequestList = [ { @@ -245,7 +304,7 @@ describe('marsmedia adapter tests', function () { 'sizes': [[300, 600]] } }, - 'adUnitCode': 'div-gpt-ad-1438287399331-0', + 'adUnitCode': 'Unit-Code', 'sizes': [[300, 250]], 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', 'bidderRequestId': '418b37f85e772c', @@ -255,7 +314,7 @@ describe('marsmedia adapter tests', function () { } ]; - var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + var bidRequest = marsAdapter.buildRequests(bidRequestList, this.defaultBidderRequest); const openrtbRequest = JSON.parse(bidRequest.data); expect(openrtbRequest.imp[0].banner.format[0].w).to.equal(300); @@ -274,7 +333,7 @@ describe('marsmedia adapter tests', function () { 'sizes': [[300]] } }, - 'adUnitCode': 'div-gpt-ad-1438287399331-0', + 'adUnitCode': 'Unit-Code', 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', 'bidderRequestId': '418b37f85e772c', 'auctionId': '18fd8b8b0bd757', @@ -283,7 +342,7 @@ describe('marsmedia adapter tests', function () { } ]; - var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + var bidRequest = marsAdapter.buildRequests(bidRequestList, this.defaultBidderRequest); expect(bidRequest.method).to.be.undefined; }); @@ -297,7 +356,7 @@ describe('marsmedia adapter tests', function () { 'mediaTypes': { 'banner': {} }, - 'adUnitCode': 'div-gpt-ad-1438287399331-0', + 'adUnitCode': 'Unit-Code', 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', 'bidderRequestId': '418b37f85e772c', 'auctionId': '18fd8b8b0bd757', @@ -306,7 +365,7 @@ describe('marsmedia adapter tests', function () { } ]; - var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + var bidRequest = marsAdapter.buildRequests(bidRequestList, this.defaultBidderRequest); expect(bidRequest.method).to.be.undefined; }); @@ -320,7 +379,7 @@ describe('marsmedia adapter tests', function () { 'mediaTypes': { 'banner': {'sizes': [['400', '500'], ['4n0', '5g0']]} }, - 'adUnitCode': 'div-gpt-ad-1438287399331-0', + 'adUnitCode': 'Unit-Code', 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', 'bidderRequestId': '418b37f85e772c', 'auctionId': '18fd8b8b0bd757', @@ -329,35 +388,15 @@ describe('marsmedia adapter tests', function () { } ]; - var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + var bidRequest = marsAdapter.buildRequests(bidRequestList, this.defaultBidderRequest); const openrtbRequest = JSON.parse(bidRequest.data); expect(openrtbRequest.imp[0].banner.format.length).to.equal(1); }); it('dnt is correctly set to 1', function () { - var bidRequestList = [ - { - 'bidder': 'marsmedia', - 'params': { - 'zoneId': 9999 - }, - 'mediaTypes': { - 'banner': { - 'sizes': [[300, 600]] - } - }, - 'adUnitCode': 'div-gpt-ad-1438287399331-0', - 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', - 'bidderRequestId': '418b37f85e772c', - 'auctionId': '18fd8b8b0bd757', - 'bidRequestsCount': 1, - 'bidId': '51ef8751f9aead' - } - ]; - var dntStub = sinon.stub(utils, 'getDNT').returns(1); - var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + var bidRequest = marsAdapter.buildRequests(this.defaultBidRequestList, this.defaultBidderRequest); dntStub.restore(); @@ -378,7 +417,7 @@ describe('marsmedia adapter tests', function () { 'playerSize': ['600', '300'] } }, - 'adUnitCode': 'div-gpt-ad-1438287399331-1', + 'adUnitCode': 'Unit-Code', 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', 'bidderRequestId': '418b37f85e772c', 'auctionId': '18fd8b8b0bd757', @@ -387,7 +426,7 @@ describe('marsmedia adapter tests', function () { } ]; - var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + var bidRequest = marsAdapter.buildRequests(bidRequestList, this.defaultBidderRequest); const openrtbRequest = JSON.parse(bidRequest.data); expect(openrtbRequest.imp[0].video.w).to.equal(600); @@ -407,7 +446,7 @@ describe('marsmedia adapter tests', function () { 'playerSize': ['badWidth', 'badHeight'] } }, - 'adUnitCode': 'div-gpt-ad-1438287399331-1', + 'adUnitCode': 'Unit-Code', 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', 'bidderRequestId': '418b37f85e772c', 'auctionId': '18fd8b8b0bd757', @@ -416,7 +455,7 @@ describe('marsmedia adapter tests', function () { } ]; - var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + var bidRequest = marsAdapter.buildRequests(bidRequestList, this.defaultBidderRequest); const openrtbRequest = JSON.parse(bidRequest.data); expect(openrtbRequest.imp[0].video.w).to.be.undefined; @@ -435,7 +474,7 @@ describe('marsmedia adapter tests', function () { 'context': 'instream' } }, - 'adUnitCode': 'div-gpt-ad-1438287399331-1', + 'adUnitCode': 'Unit-Code', 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', 'bidderRequestId': '418b37f85e772c', 'auctionId': '18fd8b8b0bd757', @@ -444,7 +483,7 @@ describe('marsmedia adapter tests', function () { } ]; - var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + var bidRequest = marsAdapter.buildRequests(bidRequestList, this.defaultBidderRequest); const openrtbRequest = JSON.parse(bidRequest.data); expect(openrtbRequest.imp[0].video.w).to.be.undefined; @@ -453,52 +492,74 @@ describe('marsmedia adapter tests', function () { it('should return empty site data when refererInfo is missing', function() { delete this.defaultBidderRequest.refererInfo; - var bidRequestList = [ - { - 'bidder': 'marsmedia', - 'params': { - 'zoneId': 9999 - }, - 'mediaType': 'banner', - 'adUnitCode': 'div-gpt-ad-1438287399331-0', - 'sizes': [[300, 250]], - 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', - 'bidderRequestId': '418b37f85e772c', - 'auctionId': '18fd8b8b0bd757', - 'bidRequestsCount': 1, - 'bidId': '51ef8751f9aead' - } - ]; - - var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + var bidRequest = marsAdapter.buildRequests(this.defaultBidRequestList, this.defaultBidderRequest); const openrtbRequest = JSON.parse(bidRequest.data); expect(openrtbRequest.site.domain).to.equal(''); expect(openrtbRequest.site.page).to.equal(''); expect(openrtbRequest.site.ref).to.equal(''); }); + + context('when element is fully in view', function() { + it('returns 100', function() { + Object.assign(element, { width: 600, height: 400 }); + const request = marsAdapter.buildRequests(this.defaultBidRequestList, this.defaultBidderRequest); + const openrtbRequest = JSON.parse(request.data); + expect(openrtbRequest.imp[0].ext.viewability).to.equal(100); + }); + }); + + context('when element is out of view', function() { + it('returns 0', function() { + Object.assign(element, { x: -300, y: 0, width: 207, height: 320 }); + const request = marsAdapter.buildRequests(this.defaultBidRequestList, this.defaultBidderRequest); + const openrtbRequest = JSON.parse(request.data); + expect(openrtbRequest.imp[0].ext.viewability).to.equal(0); + }); + }); + + context('when element is partially in view', function() { + it('returns percentage', function() { + Object.assign(element, { width: 800, height: 800 }); + const request = marsAdapter.buildRequests(this.defaultBidRequestList, this.defaultBidderRequest); + const openrtbRequest = JSON.parse(request.data); + expect(openrtbRequest.imp[0].ext.viewability).to.equal(75); + }); + }); + + context('when nested iframes', function() { + it('returns \'na\'', function() { + Object.assign(element, { width: 600, height: 400 }); + + utils.getWindowTop.restore(); + utils.getWindowSelf.restore(); + sandbox.stub(utils, 'getWindowTop').returns(win); + sandbox.stub(utils, 'getWindowSelf').returns({}); + + const request = marsAdapter.buildRequests(this.defaultBidRequestList, this.defaultBidderRequest); + const openrtbRequest = JSON.parse(request.data); + expect(openrtbRequest.imp[0].ext.viewability).to.equal('na'); + }); + }); + + context('when tab is inactive', function() { + it('returns 0', function() { + Object.assign(element, { width: 600, height: 400 }); + + utils.getWindowTop.restore(); + win.document.visibilityState = 'hidden'; + sandbox.stub(utils, 'getWindowTop').returns(win); + + const request = marsAdapter.buildRequests(this.defaultBidRequestList, this.defaultBidderRequest); + const openrtbRequest = JSON.parse(request.data); + expect(openrtbRequest.imp[0].ext.viewability).to.equal(0); + }); + }); }); it('should return empty site.domain and site.page when refererInfo.stack is empty', function() { this.defaultBidderRequest.refererInfo.stack = []; - var bidRequestList = [ - { - 'bidder': 'marsmedia', - 'params': { - 'zoneId': 9999 - }, - 'mediaType': 'banner', - 'adUnitCode': 'div-gpt-ad-1438287399331-0', - 'sizes': [[300, 250]], - 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', - 'bidderRequestId': '418b37f85e772c', - 'auctionId': '18fd8b8b0bd757', - 'bidRequestsCount': 1, - 'bidId': '51ef8751f9aead' - } - ]; - - var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + var bidRequest = marsAdapter.buildRequests(this.defaultBidRequestList, this.defaultBidderRequest); const openrtbRequest = JSON.parse(bidRequest.data); expect(openrtbRequest.site.domain).to.equal(''); @@ -508,24 +569,7 @@ describe('marsmedia adapter tests', function () { it('should secure correctly', function() { this.defaultBidderRequest.refererInfo.stack[0] = ['https://securesite.dvl']; - var bidRequestList = [ - { - 'bidder': 'marsmedia', - 'params': { - 'zoneId': 9999 - }, - 'mediaType': 'banner', - 'adUnitCode': 'div-gpt-ad-1438287399331-0', - 'sizes': [[300, 250]], - 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', - 'bidderRequestId': '418b37f85e772c', - 'auctionId': '18fd8b8b0bd757', - 'bidRequestsCount': 1, - 'bidId': '51ef8751f9aead' - } - ]; - - var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + var bidRequest = marsAdapter.buildRequests(this.defaultBidRequestList, this.defaultBidderRequest); const openrtbRequest = JSON.parse(bidRequest.data); expect(openrtbRequest.imp[0].secure).to.equal(1); @@ -551,9 +595,12 @@ describe('marsmedia adapter tests', function () { 'params': { 'zoneId': 9999 }, - 'mediaType': 'banner', - 'adUnitCode': 'div-gpt-ad-1438287399331-0', - 'sizes': [[300, 250]], + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250]] + } + }, + 'adUnitCode': 'Unit-Code', 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', 'bidderRequestId': '418b37f85e772c', 'auctionId': '18fd8b8b0bd757', @@ -563,7 +610,7 @@ describe('marsmedia adapter tests', function () { } ]; - var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + var bidRequest = marsAdapter.buildRequests(bidRequestList, this.defaultBidderRequest); const openrtbRequest = JSON.parse(bidRequest.data); expect(openrtbRequest.source.ext.schain).to.deep.equal(schain); @@ -571,7 +618,7 @@ describe('marsmedia adapter tests', function () { describe('misc interpretResponse', function () { it('No bid response', function() { - var noBidResponse = r1adapter.interpretResponse({ + var noBidResponse = marsAdapter.interpretResponse({ 'body': '' }); expect(noBidResponse.length).to.equal(0); @@ -589,22 +636,22 @@ describe('marsmedia adapter tests', function () { 'sizes': [[300, 250]] } }, - 'adUnitCode': 'bannerDiv' + 'adUnitCode': 'Unit-Code' }; it('should return true when required params found', function () { - expect(r1adapter.isBidRequestValid(bid)).to.equal(true); + expect(marsAdapter.isBidRequestValid(bid)).to.equal(true); }); it('should return false when placementId missing', function () { delete bid.params.zoneId; - expect(r1adapter.isBidRequestValid(bid)).to.equal(false); + expect(marsAdapter.isBidRequestValid(bid)).to.equal(false); }); }); describe('getUserSyncs', function () { it('returns an empty string', function () { - expect(r1adapter.getUserSyncs()).to.deep.equal([]); + expect(marsAdapter.getUserSyncs()).to.deep.equal([]); }); }); diff --git a/test/spec/modules/mass_spec.js b/test/spec/modules/mass_spec.js new file mode 100644 index 00000000000..d8d27348bde --- /dev/null +++ b/test/spec/modules/mass_spec.js @@ -0,0 +1,130 @@ +import { expect } from 'chai'; +import { + init, + addBidResponseHook, + addListenerOnce, + getRenderPayload, + render, + listenerAdded, + massEnabled +} from 'modules/mass'; +import { logInfo } from 'src/utils.js'; + +// mock a MASS bid: +const mockedMassBids = [ + { + bidder: 'ix', + bidId: 'mass-bid-1', + requestId: 'mass-bid-1', + bidderRequestId: 'bidder-request-id-1', + dealId: 'MASS1234', + ad: 'mass://provider/product/etc...', + meta: {} + }, + { + bidder: 'ix', + bidId: 'mass-bid-2', + requestId: 'mass-bid-2', + bidderRequestId: 'bidder-request-id-1', + dealId: '1234', + ad: 'mass://provider/product/etc...', + meta: { + mass: true + } + }, +]; + +// mock non-MASS bids: +const mockedNonMassBids = [ + { + bidder: 'ix', + bidId: 'non-mass-bid-1', + requstId: 'non-mass-bid-1', + bidderRequestId: 'bidder-request-id-1', + dealId: 'MASS1234', + ad: '', + meta: { + mass: true + } + }, + { + bidder: 'ix', + bidId: 'non-mass-bid-2', + requestId: 'non-mass-bid-2', + bidderRequestId: 'bidder-request-id-1', + dealId: '1234', + ad: 'mass://provider/product/etc...', + meta: {} + }, +]; + +// mock bidder request: +const mockedBidderRequest = { + bidderCode: 'ix', + bidderRequestId: 'bidder-request-id-1' +}; + +const noop = function() {}; + +describe('MASS Module', function() { + let bidderRequest = Object.assign({}, mockedBidderRequest); + + it('should be enabled by default', function() { + expect(massEnabled).to.equal(true); + }); + + it('can be disabled', function() { + init({enabled: false}); + expect(massEnabled).to.equal(false); + }); + + it('should only affect MASS bids', function() { + init({renderUrl: 'http://...'}); + mockedNonMassBids.forEach(function(mockedBid) { + const originalBid = Object.assign({}, mockedBid); + const bid = Object.assign({}, originalBid); + + bidderRequest.bids = [bid]; + + addBidResponseHook.call({bidderRequest}, noop, 'ad-code-id', bid); + + expect(bid).to.deep.equal(originalBid); + }); + }); + + it('should only update the ad markup field', function() { + init({renderUrl: 'http://...'}); + mockedMassBids.forEach(function(mockedBid) { + const originalBid = Object.assign({}, mockedBid); + const bid = Object.assign({}, originalBid); + + bidderRequest.bids = [bid]; + + addBidResponseHook.call({bidderRequest}, noop, 'ad-code-id', bid); + + expect(bid.ad).to.not.equal(originalBid.ad); + + delete bid.ad; + delete originalBid.ad; + + expect(bid).to.deep.equal(originalBid); + }); + }); + + it('should add a message listener', function() { + addListenerOnce(); + expect(listenerAdded).to.equal(true); + }); + + it('should get correct bid in render payload', function() { + const payload = getRenderPayload({data: {massBidId: 'mass-bid-1'}}); + expect(payload.type).to.equal('prebid'); + expect(payload.bid.bidId).to.equal('mass-bid-1'); + }); + + it('should load the bootloader on rendering', function() { + render({}); + expect(window.mass).to.be.an('object'); + expect(window.mass.bootloader.loaded).to.equal(true); + }); +}); diff --git a/test/spec/modules/mediaforceBidAdapter_spec.js b/test/spec/modules/mediaforceBidAdapter_spec.js index 0b3271da770..24326afe5c0 100644 --- a/test/spec/modules/mediaforceBidAdapter_spec.js +++ b/test/spec/modules/mediaforceBidAdapter_spec.js @@ -142,7 +142,7 @@ describe('mediaforce bid adapter', function () { const requestUrl = `${baseUrl}/header_bid`; const dnt = utils.getDNT() ? 1 : 0; - const secure = window.location.protocol === 'https' ? 1 : 0; + const secure = window.location.protocol === 'https:' ? 1 : 0; const pageUrl = window.location.href; const timeout = 1500; diff --git a/test/spec/modules/mediasquareBidAdapter_spec.js b/test/spec/modules/mediasquareBidAdapter_spec.js index 3c18cfe0be7..796be3420e8 100644 --- a/test/spec/modules/mediasquareBidAdapter_spec.js +++ b/test/spec/modules/mediasquareBidAdapter_spec.js @@ -165,6 +165,16 @@ describe('MediaSquare bid adapter tests', function () { expect(syncs[0]).to.have.property('type').and.to.equal('image'); expect(syncs[0]).to.have.property('url').and.to.equal('http://www.cookie.sync.org/'); }); + it('Verifies user sync with no bid response', function() { + var syncs = spec.getUserSyncs({}, null, DEFAULT_OPTIONS.gdprConsent, DEFAULT_OPTIONS.uspConsent); + expect(syncs).to.have.property('type').and.to.equal('iframe'); + }); + it('Verifies user sync with no bid body response', function() { + var syncs = spec.getUserSyncs({}, [], DEFAULT_OPTIONS.gdprConsent, DEFAULT_OPTIONS.uspConsent); + expect(syncs).to.have.property('type').and.to.equal('iframe'); + var syncs = spec.getUserSyncs({}, [{}], DEFAULT_OPTIONS.gdprConsent, DEFAULT_OPTIONS.uspConsent); + expect(syncs).to.have.property('type').and.to.equal('iframe'); + }); it('Verifies native in bid response', function () { const request = spec.buildRequests(NATIVE_PARAMS, DEFAULT_OPTIONS); BID_RESPONSE.body.responses[0].native = {'title': 'native title'}; diff --git a/test/spec/modules/missenaBidAdapter_spec.js b/test/spec/modules/missenaBidAdapter_spec.js new file mode 100644 index 00000000000..026e79c6d5a --- /dev/null +++ b/test/spec/modules/missenaBidAdapter_spec.js @@ -0,0 +1,131 @@ +import { expect } from 'chai'; +import { spec, _getPlatform } from 'modules/missenaBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; + +describe('Missena Adapter', function () { + const adapter = newBidder(spec); + + const bidId = 'abc'; + + const bid = { + bidder: 'missena', + bidId: bidId, + sizes: [[1, 1]], + params: { + apiKey: 'PA-34745704', + }, + }; + + describe('codes', function () { + it('should return a bidder code of missena', function () { + expect(spec.code).to.equal('missena'); + }); + }); + + describe('isBidRequestValid', function () { + it('should return true if the apiKey param is present', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false if the apiKey is missing', function () { + expect( + spec.isBidRequestValid(Object.assign(bid, { params: {} })) + ).to.equal(false); + }); + + it('should return false if the apiKey is an empty string', function () { + expect( + spec.isBidRequestValid(Object.assign(bid, { params: { apiKey: '' } })) + ).to.equal(false); + }); + }); + + describe('buildRequests', function () { + const consentString = 'AAAAAAAAA=='; + + const bidderRequest = { + gdprConsent: { + consentString: consentString, + gdprApplies: true, + }, + refererInfo: { + referer: 'https://referer', + canonicalUrl: 'https://canonical', + }, + }; + + const requests = spec.buildRequests([bid, bid], bidderRequest); + const request = requests[0]; + const payload = JSON.parse(request.data); + + it('should return as many server requests as bidder requests', function () { + expect(requests.length).to.equal(2); + }); + + it('should have a post method', function () { + expect(request.method).to.equal('POST'); + }); + + it('should send the bidder id', function () { + expect(payload.request_id).to.equal(bidId); + }); + + it('should send referer information to the request', function () { + expect(payload.referer).to.equal('https://referer'); + expect(payload.referer_canonical).to.equal('https://canonical'); + }); + + it('should send gdpr consent information to the request', function () { + expect(payload.consent_string).to.equal(consentString); + expect(payload.consent_required).to.equal(true); + }); + }); + + describe('interpretResponse', function () { + const serverResponse = { + requestId: bidId, + cpm: 0.5, + currency: 'USD', + ad: '', + }; + + const serverTimeoutResponse = { + requestId: bidId, + timeout: true, + ad: '', + }; + + const serverEmptyAdResponse = { + requestId: bidId, + cpm: 0.5, + currency: 'USD', + ad: '', + }; + + it('should return a proper bid response', function () { + const result = spec.interpretResponse({ body: serverResponse }, bid); + + expect(result.length).to.equal(1); + + expect(Object.keys(result[0])).to.have.members( + Object.keys(serverResponse) + ); + }); + + it('should return an empty response when the server answers with a timeout', function () { + const result = spec.interpretResponse( + { body: serverTimeoutResponse }, + bid + ); + expect(result).to.deep.equal([]); + }); + + it('should return an empty response when the server answers with an empty ad', function () { + const result = spec.interpretResponse( + { body: serverEmptyAdResponse }, + bid + ); + expect(result).to.deep.equal([]); + }); + }); +}); diff --git a/test/spec/modules/multibid_spec.js b/test/spec/modules/multibid_spec.js new file mode 100644 index 00000000000..e849392ee4b --- /dev/null +++ b/test/spec/modules/multibid_spec.js @@ -0,0 +1,845 @@ +import {expect} from 'chai'; +import { + validateMultibid, + adjustBidderRequestsHook, + addBidResponseHook, + resetMultibidUnits, + sortByMultibid, + targetBidPoolHook, + resetMultiConfig +} from 'modules/multibid/index.js'; +import {parse as parseQuery} from 'querystring'; +import {config} from 'src/config.js'; +import * as utils from 'src/utils.js'; +import find from 'core-js-pure/features/array/find.js'; + +describe('multibid adapter', function () { + let bidArray = [{ + 'bidderCode': 'bidderA', + 'requestId': '1c5f0a05d3629a', + 'cpm': 75, + 'originalCpm': 75, + 'bidder': 'bidderA', + }, { + 'bidderCode': 'bidderA', + 'cpm': 52, + 'requestId': '2e6j8s05r4363h', + 'originalCpm': 52, + 'bidder': 'bidderA', + }]; + let bidCacheArray = [{ + 'bidderCode': 'bidderA', + 'requestId': '1c5f0a05d3629a', + 'cpm': 66, + 'originalCpm': 66, + 'bidder': 'bidderA', + 'originalBidder': 'bidderA', + 'multibidPrefix': 'bidA' + }, { + 'bidderCode': 'bidderA', + 'cpm': 38, + 'requestId': '2e6j8s05r4363h', + 'originalCpm': 38, + 'bidder': 'bidderA', + 'originalBidder': 'bidderA', + 'multibidPrefix': 'bidA' + }]; + let bidArrayAlt = [{ + 'bidderCode': 'bidderA', + 'requestId': '1c5f0a05d3629a', + 'cpm': 29, + 'originalCpm': 29, + 'bidder': 'bidderA' + }, { + 'bidderCode': 'bidderA', + 'cpm': 52, + 'requestId': '2e6j8s05r4363h', + 'originalCpm': 52, + 'bidder': 'bidderA' + }, { + 'bidderCode': 'bidderB', + 'cpm': 3, + 'requestId': '7g8h5j45l7654i', + 'originalCpm': 3, + 'bidder': 'bidderB' + }, { + 'bidderCode': 'bidderC', + 'cpm': 12, + 'requestId': '9d7f4h56t6483u', + 'originalCpm': 12, + 'bidder': 'bidderC' + }]; + let bidderRequests = [{ + 'bidderCode': 'bidderA', + 'auctionId': 'e6bd4400-28fc-459b-9905-ad64d044daaa', + 'bidderRequestId': '10e78266423c0e', + 'bids': [{ + 'bidder': 'bidderA', + 'params': {'placementId': 1234567}, + 'crumbs': {'pubcid': 'fb4cfc66-ff3d-4fda-bef8-3f2cb6fe9412'}, + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250]] + } + }, + 'adUnitCode': 'test-div', + 'transactionId': 'c153f3da-84f0-4be8-95cb-0647c458bc60', + 'sizes': [[300, 250]], + 'bidId': '2408ef83b84c9d', + 'bidderRequestId': '10e78266423c0e', + 'auctionId': 'e6bd4400-28fc-459b-9905-ad64d044daaa', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 + }] + }, { + 'bidderCode': 'bidderB', + 'auctionId': 'e6bd4400-28fc-459b-9905-ad64d044daaa', + 'bidderRequestId': '10e78266423c0e', + 'bids': [{ + 'bidder': 'bidderB', + 'params': {'placementId': 1234567}, + 'crumbs': {'pubcid': 'fb4cfc66-ff3d-4fda-bef8-3f2cb6fe9412'}, + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250]] + } + }, + 'adUnitCode': 'test-div', + 'transactionId': 'c153f3da-84f0-4be8-95cb-0647c458bc60', + 'sizes': [[300, 250]], + 'bidId': '2408ef83b84c9d', + 'bidderRequestId': '10e78266423c0e', + 'auctionId': 'e6bd4400-28fc-459b-9905-ad64d044daaa', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 + }] + }]; + + afterEach(function () { + config.resetConfig(); + resetMultiConfig(); + resetMultibidUnits(); + }); + + describe('adjustBidderRequestsHook', function () { + let result; + let callbackFn = function (bidderRequests) { + result = bidderRequests; + }; + + beforeEach(function() { + result = null; + }); + + it('does not modify bidderRequest when no multibid config exists', function () { + let bidRequests = [{...bidderRequests[0]}]; + + adjustBidderRequestsHook(callbackFn, bidRequests); + + expect(result).to.not.equal(null); + expect(result).to.deep.equal(bidRequests); + }); + + it('does modify bidderRequest when multibid config exists', function () { + let bidRequests = [{...bidderRequests[0]}]; + + config.setConfig({multibid: [{bidder: 'bidderA', maxBids: 2}]}); + + adjustBidderRequestsHook(callbackFn, [{...bidderRequests[0]}]); + + expect(result).to.not.equal(null); + expect(result).to.not.deep.equal(bidRequests); + expect(result[0].bidLimit).to.equal(2); + }); + + it('does modify bidderRequest when multibid config exists using bidders array', function () { + let bidRequests = [{...bidderRequests[0]}]; + + config.setConfig({multibid: [{bidders: ['bidderA'], maxBids: 2}]}); + + adjustBidderRequestsHook(callbackFn, [{...bidderRequests[0]}]); + + expect(result).to.not.equal(null); + expect(result).to.not.deep.equal(bidRequests); + expect(result[0].bidLimit).to.equal(2); + }); + + it('does only modifies bidderRequest when multibid config exists for bidder', function () { + let bidRequests = [{...bidderRequests[0]}, {...bidderRequests[1]}]; + + config.setConfig({multibid: [{bidder: 'bidderA', maxBids: 2}]}); + + adjustBidderRequestsHook(callbackFn, [{...bidderRequests[0]}, {...bidderRequests[1]}]); + + expect(result).to.not.equal(null); + expect(result[0]).to.not.deep.equal(bidRequests[0]); + expect(result[0].bidLimit).to.equal(2); + expect(result[1]).to.deep.equal(bidRequests[1]); + expect(result[1].bidLimit).to.equal(undefined); + }); + }); + + describe('addBidResponseHook', function () { + let result; + let callbackFn = function (adUnitCode, bid) { + result = { + 'adUnitCode': adUnitCode, + 'bid': bid + }; + }; + + beforeEach(function() { + result = null; + }); + + it('adds original bids and does not modify', function () { + let adUnitCode = 'test-div'; + let bids = [{...bidArray[0]}, {...bidArray[1]}]; + + addBidResponseHook(callbackFn, adUnitCode, {...bids[0]}); + + expect(result).to.not.equal(null); + expect(result.adUnitCode).to.not.equal(null); + expect(result.adUnitCode).to.equal('test-div'); + expect(result.bid).to.not.equal(null); + expect(result.bid).to.deep.equal(bids[0]); + + result = null; + + addBidResponseHook(callbackFn, adUnitCode, {...bids[1]}); + + expect(result).to.not.equal(null); + expect(result.adUnitCode).to.not.equal(null); + expect(result.adUnitCode).to.equal('test-div'); + expect(result.bid).to.not.equal(null); + expect(result.bid).to.deep.equal(bids[1]); + }); + + it('modifies and adds both bids based on multibid configuration', function () { + let adUnitCode = 'test-div'; + let bids = [{...bidArray[0]}, {...bidArray[1]}]; + + config.setConfig({multibid: [{bidder: 'bidderA', maxBids: 2, targetBiddercodePrefix: 'bidA'}]}); + + addBidResponseHook(callbackFn, adUnitCode, {...bids[0]}); + + bids[0].multibidPrefix = 'bidA'; + bids[0].originalBidder = 'bidderA'; + + expect(result).to.not.equal(null); + expect(result.adUnitCode).to.not.equal(null); + expect(result.adUnitCode).to.equal('test-div'); + expect(result.bid).to.not.equal(null); + expect(result.bid).to.deep.equal(bids[0]); + + result = null; + + addBidResponseHook(callbackFn, adUnitCode, {...bids[1]}); + + bids[1].multibidPrefix = 'bidA'; + bids[1].originalBidder = 'bidderA'; + bids[1].targetingBidder = 'bidA2'; + bids[1].originalRequestId = '2e6j8s05r4363h'; + + delete bids[1].requestId; + delete result.bid.requestId; + + expect(result).to.not.equal(null); + expect(result.adUnitCode).to.not.equal(null); + expect(result.adUnitCode).to.equal('test-div'); + expect(result.bid).to.not.equal(null); + expect(result.bid).to.deep.equal(bids[1]); + }); + + it('only modifies bids defined in the multibid configuration', function () { + let adUnitCode = 'test-div'; + let bids = [{...bidArray[0]}, {...bidArray[1]}]; + + bids.push({ + 'bidderCode': 'bidderB', + 'cpm': 33, + 'requestId': '1j8s5f89y2345l', + 'originalCpm': 33, + 'bidder': 'bidderB', + }); + + config.setConfig({multibid: [{bidder: 'bidderA', maxBids: 2, targetBiddercodePrefix: 'bidA'}]}); + + addBidResponseHook(callbackFn, adUnitCode, {...bids[0]}); + + bids[0].multibidPrefix = 'bidA'; + bids[0].originalBidder = 'bidderA'; + + expect(result).to.not.equal(null); + expect(result.adUnitCode).to.not.equal(null); + expect(result.adUnitCode).to.equal('test-div'); + expect(result.bid).to.not.equal(null); + expect(result.bid).to.deep.equal(bids[0]); + + result = null; + + addBidResponseHook(callbackFn, adUnitCode, {...bids[1]}); + + bids[1].multibidPrefix = 'bidA'; + bids[1].originalBidder = 'bidderA'; + bids[1].targetingBidder = 'bidA2'; + bids[1].originalRequestId = '2e6j8s05r4363h'; + bids[1].requestId = result.bid.requestId; + + expect(result).to.not.equal(null); + expect(result.adUnitCode).to.not.equal(null); + expect(result.adUnitCode).to.equal('test-div'); + expect(result.bid).to.not.equal(null); + expect(result.bid).to.deep.equal(bids[1]); + + result = null; + + addBidResponseHook(callbackFn, adUnitCode, {...bids[2]}); + + expect(result).to.not.equal(null); + expect(result.adUnitCode).to.not.equal(null); + expect(result.adUnitCode).to.equal('test-div'); + expect(result.bid).to.not.equal(null); + expect(result.bid).to.deep.equal(bids[2]); + }); + + it('only modifies and returns bids under limit for a specifc bidder in the multibid configuration', function () { + let adUnitCode = 'test-div'; + let bids = [{...bidArray[0]}, {...bidArray[1]}]; + + bids.push({ + 'bidderCode': 'bidderA', + 'cpm': 33, + 'requestId': '1j8s5f89y2345l', + 'originalCpm': 33, + 'bidder': 'bidderA', + }); + + config.setConfig({multibid: [{bidder: 'bidderA', maxBids: 2, targetBiddercodePrefix: 'bidA'}]}); + + addBidResponseHook(callbackFn, adUnitCode, {...bids[0]}); + + bids[0].multibidPrefix = 'bidA'; + bids[0].originalBidder = 'bidderA'; + + expect(result).to.not.equal(null); + expect(result.adUnitCode).to.not.equal(null); + expect(result.adUnitCode).to.equal('test-div'); + expect(result.bid).to.not.equal(null); + expect(result.bid).to.deep.equal(bids[0]); + + result = null; + + addBidResponseHook(callbackFn, adUnitCode, {...bids[1]}); + + bids[1].multibidPrefix = 'bidA'; + bids[1].originalBidder = 'bidderA'; + bids[1].targetingBidder = 'bidA2'; + bids[1].originalRequestId = '2e6j8s05r4363h'; + bids[1].requestId = result.bid.requestId; + + expect(result).to.not.equal(null); + expect(result.adUnitCode).to.not.equal(null); + expect(result.adUnitCode).to.equal('test-div'); + expect(result.bid).to.not.equal(null); + expect(result.bid).to.deep.equal(bids[1]); + + result = null; + + addBidResponseHook(callbackFn, adUnitCode, {...bids[2]}); + + expect(result).to.equal(null); + }); + + it('if no prefix in multibid configuration, modifies and returns bids under limit without preifx property', function () { + let adUnitCode = 'test-div'; + let bids = [{...bidArray[0]}, {...bidArray[1]}]; + + bids.push({ + 'bidderCode': 'bidderA', + 'cpm': 33, + 'requestId': '1j8s5f89y2345l', + 'originalCpm': 33, + 'bidder': 'bidderA', + }); + + config.setConfig({multibid: [{bidder: 'bidderA', maxBids: 2}]}); + + addBidResponseHook(callbackFn, adUnitCode, {...bids[0]}); + + bids[0].originalBidder = 'bidderA'; + + expect(result).to.not.equal(null); + expect(result.adUnitCode).to.not.equal(null); + expect(result.adUnitCode).to.equal('test-div'); + expect(result.bid).to.not.equal(null); + expect(result.bid).to.deep.equal(bids[0]); + + result = null; + + addBidResponseHook(callbackFn, adUnitCode, {...bids[1]}); + + bids[1].originalBidder = 'bidderA'; + bids[1].originalRequestId = '2e6j8s05r4363h'; + bids[1].requestId = result.bid.requestId; + + expect(result).to.not.equal(null); + expect(result.adUnitCode).to.not.equal(null); + expect(result.adUnitCode).to.equal('test-div'); + expect(result.bid).to.not.equal(null); + expect(result.bid).to.deep.equal(bids[1]); + + result = null; + + addBidResponseHook(callbackFn, adUnitCode, {...bids[2]}); + + expect(result).to.equal(null); + }); + + it('does not include extra bids if cpm is less than floor value', function () { + let adUnitCode = 'test-div'; + let bids = [{...bidArrayAlt[1]}, {...bidArrayAlt[0]}, {...bidArrayAlt[2]}, {...bidArrayAlt[3]}]; + + bids.map(bid => { + bid.floorData = { + cpmAfterAdjustments: bid.cpm, + enforcements: { + enforceJS: true, + enforcePBS: false, + floorDeals: false, + bidAdjustment: true + }, + floorCurrency: 'USD', + floorRule: '*|banner', + floorRuleValue: 65, + floorValue: 65, + matchedFields: { + gptSlot: 'test-div', + mediaType: 'banner' + } + } + + return bid; + }); + + config.setConfig({multibid: [{bidder: 'bidderA', maxBids: 2, targetBiddercodePrefix: 'bidA'}]}); + + addBidResponseHook(callbackFn, adUnitCode, {...bids[0]}); + + bids[0].multibidPrefix = 'bidA'; + bids[0].originalBidder = 'bidderA'; + + expect(result).to.not.equal(null); + expect(result.adUnitCode).to.not.equal(null); + expect(result.adUnitCode).to.equal('test-div'); + expect(result.bid).to.not.equal(null); + expect(result.bid.bidder).to.equal('bidderA'); + expect(result.bid.targetingBidder).to.equal(undefined); + + result = null; + + addBidResponseHook(callbackFn, adUnitCode, {...bids[1]}); + + expect(result).to.equal(null); + + result = null; + + addBidResponseHook(callbackFn, adUnitCode, {...bids[2]}); + + expect(result).to.not.equal(null); + expect(result.adUnitCode).to.not.equal(null); + expect(result.adUnitCode).to.equal('test-div'); + expect(result.bid).to.not.equal(null); + expect(result.bid.bidder).to.equal('bidderB'); + expect(result.bid.targetingBidder).to.equal(undefined); + + result = null; + + addBidResponseHook(callbackFn, adUnitCode, {...bids[3]}); + + expect(result).to.not.equal(null); + expect(result.adUnitCode).to.not.equal(null); + expect(result.adUnitCode).to.equal('test-div'); + expect(result.bid).to.not.equal(null); + expect(result.bid.bidder).to.equal('bidderC'); + expect(result.bid.targetingBidder).to.equal(undefined); + }); + + it('does include extra bids if cpm is not less than floor value', function () { + let adUnitCode = 'test-div'; + let bids = [{...bidArrayAlt[1]}, {...bidArrayAlt[0]}]; + + bids.map(bid => { + bid.floorData = { + cpmAfterAdjustments: bid.cpm, + enforcements: { + enforceJS: true, + enforcePBS: false, + floorDeals: false, + bidAdjustment: true + }, + floorCurrency: 'USD', + floorRule: '*|banner', + floorRuleValue: 25, + floorValue: 25, + matchedFields: { + gptSlot: 'test-div', + mediaType: 'banner' + } + } + + return bid; + }); + + config.setConfig({multibid: [{bidder: 'bidderA', maxBids: 2, targetBiddercodePrefix: 'bidA'}]}); + + addBidResponseHook(callbackFn, adUnitCode, {...bids[0]}); + + bids[0].multibidPrefix = 'bidA'; + bids[0].originalBidder = 'bidderA'; + + expect(result).to.not.equal(null); + expect(result.adUnitCode).to.not.equal(null); + expect(result.adUnitCode).to.equal('test-div'); + expect(result.bid).to.not.equal(null); + expect(result.bid.bidder).to.equal('bidderA'); + expect(result.bid.targetingBidder).to.equal(undefined); + + result = null; + + addBidResponseHook(callbackFn, adUnitCode, {...bids[0]}); + + bids[0].multibidPrefix = 'bidA'; + bids[0].originalBidder = 'bidderA'; + + expect(result).to.not.equal(null); + expect(result.adUnitCode).to.not.equal(null); + expect(result.adUnitCode).to.equal('test-div'); + expect(result.bid).to.not.equal(null); + expect(result.bid.bidder).to.equal('bidderA'); + expect(result.bid.targetingBidder).to.equal('bidA2'); + }); + }); + + describe('targetBidPoolHook', function () { + let result; + let bidResult; + let callbackFn = function (bidsReceived, highestCpmCallback, adUnitBidLimit = 0, hasModified = false) { + result = { + 'bidsReceived': bidsReceived, + 'adUnitBidLimit': adUnitBidLimit, + 'hasModified': hasModified + }; + }; + let bidResponseCallback = function (adUnitCode, bid) { + bidResult = bid; + }; + + beforeEach(function() { + result = null; + bidResult = null; + }); + + it('it does not run filter on bidsReceived if no multibid configuration found', function () { + let bids = [{...bidArray[0]}, {...bidArray[1]}]; + targetBidPoolHook(callbackFn, bids, utils.getHighestCpm); + + expect(result).to.not.equal(null); + expect(result.bidsReceived).to.not.equal(null); + expect(result.bidsReceived.length).to.equal(2); + expect(result.bidsReceived).to.deep.equal(bids); + expect(result.adUnitBidLimit).to.not.equal(null); + expect(result.adUnitBidLimit).to.equal(0); + expect(result.hasModified).to.not.equal(null); + expect(result.hasModified).to.equal(false); + }); + + it('it does filter on bidsReceived if multibid configuration found with no prefix', function () { + let bids = [{...bidArray[0]}, {...bidArray[1]}]; + + config.setConfig({multibid: [{bidder: 'bidderA', maxBids: 2}]}); + + targetBidPoolHook(callbackFn, bids, utils.getHighestCpm); + bids.pop(); + + expect(result).to.not.equal(null); + expect(result.bidsReceived).to.not.equal(null); + expect(result.bidsReceived.length).to.equal(1); + expect(result.bidsReceived).to.deep.equal(bids); + expect(result.adUnitBidLimit).to.not.equal(null); + expect(result.adUnitBidLimit).to.equal(0); + expect(result.hasModified).to.not.equal(null); + expect(result.hasModified).to.equal(true); + }); + + it('it sorts and creates dynamic alias on bidsReceived if multibid configuration found with prefix', function () { + let modifiedBids = [{...bidArray[1]}, {...bidArray[0]}].map(bid => { + addBidResponseHook(bidResponseCallback, 'test-div', {...bid}); + + return bidResult; + }); + + config.setConfig({multibid: [{bidder: 'bidderA', maxBids: 2, targetBiddercodePrefix: 'bidA'}]}); + + targetBidPoolHook(callbackFn, modifiedBids, utils.getHighestCpm); + + expect(result).to.not.equal(null); + expect(result.bidsReceived).to.not.equal(null); + expect(result.bidsReceived.length).to.equal(2); + expect(result.bidsReceived).to.deep.equal([modifiedBids[1], modifiedBids[0]]); + expect(result.bidsReceived[0].bidderCode).to.equal('bidderA'); + expect(result.bidsReceived[0].bidder).to.equal('bidderA'); + expect(result.bidsReceived[1].bidderCode).to.equal('bidA2'); + expect(result.bidsReceived[1].bidder).to.equal('bidderA'); + expect(result.adUnitBidLimit).to.not.equal(null); + expect(result.adUnitBidLimit).to.equal(0); + expect(result.hasModified).to.not.equal(null); + expect(result.hasModified).to.equal(true); + }); + + it('it sorts by cpm treating dynamic alias as unique bid when no bid limit defined', function () { + let modifiedBids = [{...bidArrayAlt[0]}, {...bidArrayAlt[2]}, {...bidArrayAlt[3]}, {...bidArrayAlt[1]}].map(bid => { + addBidResponseHook(bidResponseCallback, 'test-div', {...bid}); + + return bidResult; + }); + + config.setConfig({multibid: [{bidder: 'bidderA', maxBids: 2, targetBiddercodePrefix: 'bidA'}]}); + + targetBidPoolHook(callbackFn, modifiedBids, utils.getHighestCpm); + + expect(result).to.not.equal(null); + expect(result.bidsReceived).to.not.equal(null); + expect(result.bidsReceived.length).to.equal(4); + expect(result.bidsReceived).to.deep.equal([modifiedBids[3], modifiedBids[0], modifiedBids[2], modifiedBids[1]]); + expect(result.bidsReceived[0].bidderCode).to.equal('bidderA'); + expect(result.bidsReceived[0].bidder).to.equal('bidderA'); + expect(result.bidsReceived[0].cpm).to.equal(52); + expect(result.bidsReceived[1].bidderCode).to.equal('bidA2'); + expect(result.bidsReceived[1].bidder).to.equal('bidderA'); + expect(result.bidsReceived[1].cpm).to.equal(29); + expect(result.bidsReceived[2].bidderCode).to.equal('bidderC'); + expect(result.bidsReceived[2].bidder).to.equal('bidderC'); + expect(result.bidsReceived[2].cpm).to.equal(12); + expect(result.bidsReceived[3].bidderCode).to.equal('bidderB'); + expect(result.bidsReceived[3].bidder).to.equal('bidderB'); + expect(result.bidsReceived[3].cpm).to.equal(3); + expect(result.adUnitBidLimit).to.not.equal(null); + expect(result.adUnitBidLimit).to.equal(0); + expect(result.hasModified).to.not.equal(null); + expect(result.hasModified).to.equal(true); + }); + + it('it should filter out dynamic bid when bid limit is less than unique bid pool', function () { + let modifiedBids = [{...bidArrayAlt[0]}, {...bidArrayAlt[2]}, {...bidArrayAlt[3]}, {...bidArrayAlt[1]}].map(bid => { + addBidResponseHook(bidResponseCallback, 'test-div', {...bid}); + + return bidResult; + }); + + config.setConfig({ multibid: [{bidder: 'bidderA', maxBids: 2, targetBiddercodePrefix: 'bidA'}] }); + + targetBidPoolHook(callbackFn, modifiedBids, utils.getHighestCpm, 3); + + expect(result).to.not.equal(null); + expect(result.bidsReceived).to.not.equal(null); + expect(result.bidsReceived.length).to.equal(3); + expect(result.bidsReceived).to.deep.equal([modifiedBids[3], modifiedBids[2], modifiedBids[1]]); + expect(result.bidsReceived[0].bidderCode).to.equal('bidderA'); + expect(result.bidsReceived[1].bidderCode).to.equal('bidderC'); + expect(result.bidsReceived[2].bidderCode).to.equal('bidderB'); + expect(result.adUnitBidLimit).to.not.equal(null); + expect(result.adUnitBidLimit).to.equal(3); + expect(result.hasModified).to.not.equal(null); + expect(result.hasModified).to.equal(true); + }); + + it('it should collect all bids from auction and bid cache then sort and filter', function () { + config.setConfig({ multibid: [{bidder: 'bidderA', maxBids: 2, targetBiddercodePrefix: 'bidA'}] }); + + let modifiedBids = [{...bidArrayAlt[0]}, {...bidArrayAlt[2]}, {...bidArrayAlt[3]}, {...bidArrayAlt[1]}].map(bid => { + addBidResponseHook(bidResponseCallback, 'test-div', {...bid}); + + return bidResult; + }); + + let bidPool = [].concat.apply(modifiedBids, [{...bidCacheArray[0]}, {...bidCacheArray[1]}]); + + expect(bidPool.length).to.equal(6); + + targetBidPoolHook(callbackFn, bidPool, utils.getHighestCpm); + + expect(result).to.not.equal(null); + expect(result.bidsReceived).to.not.equal(null); + expect(result.bidsReceived.length).to.equal(4); + expect(result.bidsReceived).to.deep.equal([bidPool[4], bidPool[3], bidPool[2], bidPool[1]]); + expect(result.bidsReceived[0].bidderCode).to.equal('bidderA'); + expect(result.bidsReceived[1].bidderCode).to.equal('bidA2'); + expect(result.bidsReceived[2].bidderCode).to.equal('bidderC'); + expect(result.bidsReceived[3].bidderCode).to.equal('bidderB'); + expect(result.adUnitBidLimit).to.not.equal(null); + expect(result.adUnitBidLimit).to.equal(0); + expect(result.hasModified).to.not.equal(null); + expect(result.hasModified).to.equal(true); + }); + }); + + describe('validate multibid', function () { + it('should fail validation for missing bidder name in entry', function () { + let conf = [{maxBids: 1}]; + let result = validateMultibid(conf); + + expect(result).to.equal(false); + }); + + it('should pass validation on all multibid entries', function () { + let conf = [{bidder: 'bidderA', maxBids: 1}, {bidder: 'bidderB', maxBids: 2}]; + let result = validateMultibid(conf); + + expect(result).to.equal(true); + }); + + it('should fail validation for maxbids less than 1 in entry', function () { + let conf = [{bidder: 'bidderA', maxBids: 0}, {bidder: 'bidderB', maxBids: 2}]; + let result = validateMultibid(conf); + + expect(result).to.equal(false); + }); + + it('should fail validation for maxbids greater than 9 in entry', function () { + let conf = [{bidder: 'bidderA', maxBids: 10}, {bidder: 'bidderB', maxBids: 2}]; + let result = validateMultibid(conf); + + expect(result).to.equal(false); + }); + + it('should add multbid entries to global config', function () { + config.setConfig({multibid: [{bidder: 'bidderA', maxBids: 1}]}); + let conf = config.getConfig('multibid'); + + expect(conf).to.deep.equal([{bidder: 'bidderA', maxBids: 1}]); + }); + + it('should modify multbid entries and add to global config', function () { + config.setConfig({multibid: [{bidder: 'bidderA', maxBids: 0}, {bidder: 'bidderB', maxBids: 15}]}); + let conf = config.getConfig('multibid'); + + expect(conf).to.deep.equal([{bidder: 'bidderA', maxBids: 1}, {bidder: 'bidderB', maxBids: 9}]); + }); + + it('should filter multbid entry and add modified to global config', function () { + config.setConfig({multibid: [{bidder: 'bidderA', maxBids: 0}, {maxBids: 15}]}); + let conf = config.getConfig('multibid'); + + expect(conf.length).to.equal(1); + expect(conf).to.deep.equal([{bidder: 'bidderA', maxBids: 1}]); + }); + }); + + describe('sort multibid', function () { + it('should not alter order', function () { + let bids = [{ + 'bidderCode': 'bidderA', + 'cpm': 75, + 'originalCpm': 75, + 'multibidPrefix': 'bidA', + 'originalBidder': 'bidderA', + 'bidder': 'bidderA', + }, { + 'bidderCode': 'bidA2', + 'cpm': 75, + 'originalCpm': 75, + 'multibidPrefix': 'bidA', + 'originalBidder': 'bidderA', + 'bidder': 'bidderA', + }]; + + let expected = [{ + 'bidderCode': 'bidderA', + 'cpm': 75, + 'originalCpm': 75, + 'multibidPrefix': 'bidA', + 'originalBidder': 'bidderA', + 'bidder': 'bidderA', + }, { + 'bidderCode': 'bidA2', + 'cpm': 75, + 'originalCpm': 75, + 'multibidPrefix': 'bidA', + 'originalBidder': 'bidderA', + 'bidder': 'bidderA', + }]; + let result = bids.sort(sortByMultibid); + + expect(result).to.deep.equal(expected); + }); + + it('should sort dynamic alias bidders to end', function () { + let bids = [{ + 'bidderCode': 'bidA2', + 'cpm': 75, + 'originalCpm': 75, + 'multibidPrefix': 'bidA', + 'originalBidder': 'bidderA', + 'bidder': 'bidderA', + }, { + 'bidderCode': 'bidderA', + 'cpm': 22, + 'originalCpm': 22, + 'multibidPrefix': 'bidA', + 'originalBidder': 'bidderA', + 'bidder': 'bidderA', + }, { + 'bidderCode': 'bidderB', + 'cpm': 4, + 'originalCpm': 4, + 'multibidPrefix': 'bidB', + 'originalBidder': 'bidderB', + 'bidder': 'bidderB', + }, { + 'bidderCode': 'bidB', + 'cpm': 2, + 'originalCpm': 2, + 'multibidPrefix': 'bidB', + 'originalBidder': 'bidderB', + 'bidder': 'bidderB', + }]; + let expected = [{ + 'bidderCode': 'bidderA', + 'cpm': 22, + 'originalCpm': 22, + 'multibidPrefix': 'bidA', + 'originalBidder': 'bidderA', + 'bidder': 'bidderA', + }, { + 'bidderCode': 'bidderB', + 'cpm': 4, + 'originalCpm': 4, + 'multibidPrefix': 'bidB', + 'originalBidder': 'bidderB', + 'bidder': 'bidderB', + }, { + 'bidderCode': 'bidA2', + 'cpm': 75, + 'originalCpm': 75, + 'multibidPrefix': 'bidA', + 'originalBidder': 'bidderA', + 'bidder': 'bidderA', + }, { + 'bidderCode': 'bidB', + 'cpm': 2, + 'originalCpm': 2, + 'multibidPrefix': 'bidB', + 'originalBidder': 'bidderB', + 'bidder': 'bidderB', + }]; + let result = bids.sort(sortByMultibid); + + expect(result).to.deep.equal(expected); + }); + }); +}); diff --git a/test/spec/modules/mwOpenLinkIdSystem_spec.js b/test/spec/modules/mwOpenLinkIdSystem_spec.js new file mode 100644 index 00000000000..fb082b8cd16 --- /dev/null +++ b/test/spec/modules/mwOpenLinkIdSystem_spec.js @@ -0,0 +1,20 @@ +import { writeCookie, mwOpenLinkIdSubModule } from 'modules/mwOpenLinkIdSystem.js'; + +const P_CONFIG_MOCK = { + params: { + accountId: '123', + partnerId: '123' + } +}; + +describe('mwOpenLinkId module', function () { + beforeEach(function() { + writeCookie(''); + }); + + it('getId() should return a MediaWallah openLink Id when the MediaWallah openLink first party cookie exists', function () { + writeCookie({eid: 'XX-YY-ZZ-123'}); + const id = mwOpenLinkIdSubModule.getId(P_CONFIG_MOCK); + expect(id).to.be.deep.equal({id: {eid: 'XX-YY-ZZ-123'}}); + }); +}); diff --git a/test/spec/modules/nextrollIdSystem_spec.js b/test/spec/modules/nextrollIdSystem_spec.js new file mode 100644 index 00000000000..d89c7fe3c98 --- /dev/null +++ b/test/spec/modules/nextrollIdSystem_spec.js @@ -0,0 +1,56 @@ +import { nextrollIdSubmodule, storage } from 'modules/nextrollIdSystem.js'; + +const LS_VALUE = `{ + "AdID":{"id":"adid","key":"AdID"}, + "AdID:1002": {"id":"adid","key":"AdID:1002","value":"id_value"}}`; + +describe('NextrollId module', function () { + let sandbox = sinon.sandbox.create(); + let hasLocalStorageStub; + let getLocalStorageStub; + + beforeEach(function() { + hasLocalStorageStub = sandbox.stub(storage, 'hasLocalStorage'); + getLocalStorageStub = sandbox.stub(storage, 'getDataFromLocalStorage'); + }); + + afterEach(function () { + sandbox.restore(); + }) + + const testCases = [ + { + expect: { + id: {nextrollId: 'id_value'}, + }, + params: {partnerId: '1002'}, + localStorage: LS_VALUE + }, + { + expect: {id: undefined}, + params: {partnerId: '1003'}, + localStorage: LS_VALUE + }, + { + expect: {id: undefined}, + params: {partnerId: ''}, + localStorage: LS_VALUE + }, + { + expect: {id: undefined}, + params: {partnerId: '102'}, + localStorage: undefined + }, + { + expect: {id: undefined}, + params: undefined, + localStorage: undefined + } + ] + testCases.forEach( + (testCase, i) => it(`getId() (TC #${i}) should return the nextroll id if it exists`, function () { + getLocalStorageStub.withArgs('dca0.com').returns(testCase.localStorage); + const id = nextrollIdSubmodule.getId({params: testCase.params}); + expect(id).to.be.deep.equal(testCase.expect); + })) +}); diff --git a/test/spec/modules/nobidBidAdapter_spec.js b/test/spec/modules/nobidBidAdapter_spec.js index e67d3b41f1d..8dc15fbc89e 100644 --- a/test/spec/modules/nobidBidAdapter_spec.js +++ b/test/spec/modules/nobidBidAdapter_spec.js @@ -257,12 +257,12 @@ describe('Nobid Adapter', function () { 'uids': [ { 'id': 'ID5_ID', - 'atype': 1 + 'atype': 1, + 'ext': { + 'linkType': 0 + } } - ], - 'ext': { - 'linkType': 0 - } + ] }, { 'source': 'adserver.org', @@ -284,7 +284,7 @@ describe('Nobid Adapter', function () { refererInfo: {referer: REFERER} } - it('should criteo eid', function () { + it('should get user ids from eids', function () { const request = spec.buildRequests(bidRequests, bidderRequest); const payload = JSON.parse(request.data); expect(payload.sid).to.exist.and.to.equal(2); diff --git a/test/spec/modules/novatiqIdSystem_spec.js b/test/spec/modules/novatiqIdSystem_spec.js new file mode 100644 index 00000000000..60c82626450 --- /dev/null +++ b/test/spec/modules/novatiqIdSystem_spec.js @@ -0,0 +1,70 @@ +import { novatiqIdSubmodule } from 'modules/novatiqIdSystem.js'; +import * as utils from 'src/utils.js'; +import { server } from 'test/mocks/xhr.js'; + +describe('novatiqIdSystem', function () { + describe('getSrcId', function() { + it('getSrcId should set srcId value to 000 due to undefined parameter in config section', function() { + const config = { params: { } }; + const configParams = config.params || {}; + const response = novatiqIdSubmodule.getSrcId(configParams); + expect(response).to.eq('000'); + }); + + it('getSrcId should set srcId value to 000 due to missing value in config section', function() { + const config = { params: { sourceid: '' } }; + const configParams = config.params || {}; + const response = novatiqIdSubmodule.getSrcId(configParams); + expect(response).to.eq('000'); + }); + + it('getSrcId should set value to 000 due to null value in config section', function() { + const config = { params: { sourceid: null } }; + const configParams = config.params || {}; + const response = novatiqIdSubmodule.getSrcId(configParams); + expect(response).to.eq('000'); + }); + + it('getSrcId should set value to 001 due to wrong length in config section max 3 chars', function() { + const config = { params: { sourceid: '1234' } }; + const configParams = config.params || {}; + const response = novatiqIdSubmodule.getSrcId(configParams); + expect(response).to.eq('001'); + }); + + it('getSrcId should set value to 002 due to wrong format in config section', function() { + const config = { params: { sourceid: '1xc' } }; + const configParams = config.params || {}; + const response = novatiqIdSubmodule.getSrcId(configParams); + expect(response).to.eq('002'); + }); + }); + + describe('getId', function() { + it('should log message if novatiqId has wrong format', function() { + const config = { params: { sourceid: '123' } }; + const response = novatiqIdSubmodule.getId(config); + expect(response.id).to.have.length(40); + }); + + it('should log message if novatiqId not provided', function() { + const config = { params: { sourceid: '123' } }; + const response = novatiqIdSubmodule.getId(config); + expect(response.id).should.be.not.empty; + }); + }); + + describe('decode', function() { + it('should log message if novatiqId has wrong format', function() { + const novatiqId = '81b001ec-8914-488c-a96e-8c220d4ee08895ef'; + const response = novatiqIdSubmodule.decode(novatiqId); + expect(response.novatiq.snowflake).to.have.length(40); + }); + + it('should log message if novatiqId has wrong format', function() { + const novatiqId = '81b001ec-8914-488c-a96e-8c220d4ee08895ef'; + const response = novatiqIdSubmodule.decode(novatiqId); + expect(response.novatiq.snowflake).should.be.not.empty; + }); + }); +}) diff --git a/test/spec/modules/oneVideoBidAdapter_spec.js b/test/spec/modules/oneVideoBidAdapter_spec.js index 331ac8976e6..9ee1045a6e8 100644 --- a/test/spec/modules/oneVideoBidAdapter_spec.js +++ b/test/spec/modules/oneVideoBidAdapter_spec.js @@ -1,5 +1,9 @@ -import { expect } from 'chai'; -import { spec } from 'modules/oneVideoBidAdapter.js'; +import { + expect +} from 'chai'; +import { + spec +} from 'modules/oneVideoBidAdapter.js'; import * as utils from 'src/utils.js'; describe('OneVideoBidAdapter', function () { @@ -200,24 +204,24 @@ describe('OneVideoBidAdapter', function () { describe('spec.buildRequests', function () { it('should create a POST request for every bid', function () { - const requests = spec.buildRequests([ bidRequest ], bidderRequest); + const requests = spec.buildRequests([bidRequest], bidderRequest); expect(requests[0].method).to.equal('POST'); expect(requests[0].url).to.equal(spec.ENDPOINT + bidRequest.params.pubId); }); it('should attach the bid request object', function () { - const requests = spec.buildRequests([ bidRequest ], bidderRequest); + const requests = spec.buildRequests([bidRequest], bidderRequest); expect(requests[0].bidRequest).to.equal(bidRequest); }); it('should attach request data', function () { - const requests = spec.buildRequests([ bidRequest ], bidderRequest); + const requests = spec.buildRequests([bidRequest], bidderRequest); const data = requests[0].data; - const [ width, height ] = bidRequest.sizes; + const [width, height] = bidRequest.sizes; const placement = bidRequest.params.video.placement; const rewarded = bidRequest.params.video.rewarded; const inventoryid = bidRequest.params.video.inventoryid; - const VERSION = '3.0.4'; + const VERSION = '3.0.6'; expect(data.imp[0].video.w).to.equal(width); expect(data.imp[0].video.h).to.equal(height); expect(data.imp[0].bidfloor).to.equal(bidRequest.params.bidfloor); @@ -231,8 +235,10 @@ describe('OneVideoBidAdapter', function () { it('must parse bid size from a nested array', function () { const width = 640; const height = 480; - bidRequest.sizes = [[ width, height ]]; - const requests = spec.buildRequests([ bidRequest ], bidderRequest); + bidRequest.sizes = [ + [width, height] + ]; + const requests = spec.buildRequests([bidRequest], bidderRequest); const data = requests[0].data; expect(data.imp[0].video.w).to.equal(width); expect(data.imp[0].video.h).to.equal(height); @@ -240,14 +246,14 @@ describe('OneVideoBidAdapter', function () { it('should set pubId to HBExchange when bid.params.video.e2etest = true', function () { bidRequest.params.video.e2etest = true; - const requests = spec.buildRequests([ bidRequest ], bidderRequest); + const requests = spec.buildRequests([bidRequest], bidderRequest); expect(requests[0].method).to.equal('POST'); expect(requests[0].url).to.equal(spec.E2ETESTENDPOINT + 'HBExchange'); }); it('should attach End 2 End test data', function () { bidRequest.params.video.e2etest = true; - const requests = spec.buildRequests([ bidRequest ], bidderRequest); + const requests = spec.buildRequests([bidRequest], bidderRequest); const data = requests[0].data; expect(data.imp[0].bidfloor).to.not.exist; expect(data.imp[0].video.w).to.equal(300); @@ -260,7 +266,7 @@ describe('OneVideoBidAdapter', function () { }); it('it should create new schain and send it if video.params.sid exists', function () { - const requests = spec.buildRequests([ bidRequest ], bidderRequest); + const requests = spec.buildRequests([bidRequest], bidderRequest); const data = requests[0].data; const schain = data.source.ext.schain; expect(schain.nodes.length).to.equal(1); @@ -281,7 +287,7 @@ describe('OneVideoBidAdapter', function () { }] }; bidRequest.schain = globalSchain; - const requests = spec.buildRequests([ bidRequest ], bidderRequest); + const requests = spec.buildRequests([bidRequest], bidderRequest); const data = requests[0].data; const schain = data.source.ext.schain; expect(schain.nodes.length).to.equal(1); @@ -300,7 +306,7 @@ describe('OneVideoBidAdapter', function () { }] }; bidRequest.schain = globalSchain; - const requests = spec.buildRequests([ bidRequest ], bidderRequest); + const requests = spec.buildRequests([bidRequest], bidderRequest); const data = requests[0].data; const schain = data.source.ext.schain; expect(schain.nodes.length).to.equal(1); @@ -310,44 +316,44 @@ describe('OneVideoBidAdapter', function () { }) it('should append hp to new schain created by sid if video.params.hp is passed', function () { - const requests = spec.buildRequests([ bidRequest ], bidderRequest); + const requests = spec.buildRequests([bidRequest], bidderRequest); const data = requests[0].data; const schain = data.source.ext.schain; expect(schain.nodes[0].hp).to.equal(bidRequest.params.video.hp); }) it('should not accept key values pairs if custom is Undefined ', function () { bidRequest.params.video.custom = null; - const requests = spec.buildRequests([ bidRequest ], bidderRequest); + const requests = spec.buildRequests([bidRequest], bidderRequest); const data = requests[0].data; expect(data.imp[0].ext.custom).to.be.undefined; }); it('should not accept key values pairs if custom is Array ', function () { bidRequest.params.video.custom = []; - const requests = spec.buildRequests([ bidRequest ], bidderRequest); + const requests = spec.buildRequests([bidRequest], bidderRequest); const data = requests[0].data; expect(data.imp[0].ext.custom).to.be.undefined; }); it('should not accept key values pairs if custom is Number ', function () { bidRequest.params.video.custom = 123456; - const requests = spec.buildRequests([ bidRequest ], bidderRequest); + const requests = spec.buildRequests([bidRequest], bidderRequest); const data = requests[0].data; expect(data.imp[0].ext.custom).to.be.undefined; }); it('should not accept key values pairs if custom is String ', function () { bidRequest.params.video.custom = 'keyValuePairs'; - const requests = spec.buildRequests([ bidRequest ], bidderRequest); + const requests = spec.buildRequests([bidRequest], bidderRequest); const data = requests[0].data; expect(data.imp[0].ext.custom).to.be.undefined; }); it('should not accept key values pairs if custom is Boolean ', function () { bidRequest.params.video.custom = true; - const requests = spec.buildRequests([ bidRequest ], bidderRequest); + const requests = spec.buildRequests([bidRequest], bidderRequest); const data = requests[0].data; expect(data.imp[0].ext.custom).to.be.undefined; }); it('should accept key values pairs if custom is Object ', function () { bidRequest.params.video.custom = {}; - const requests = spec.buildRequests([ bidRequest ], bidderRequest); + const requests = spec.buildRequests([bidRequest], bidderRequest); const data = requests[0].data; expect(data.imp[0].ext.custom).to.be.a('object'); }); @@ -357,12 +363,14 @@ describe('OneVideoBidAdapter', function () { key2: 'value2', key3: 4444444, key4: false, - key5: {nested: 'object'}, + key5: { + nested: 'object' + }, key6: ['string', 2, true, null], key7: null, key8: undefined }; - const requests = spec.buildRequests([ bidRequest ], bidderRequest); + const requests = spec.buildRequests([bidRequest], bidderRequest); const custom = requests[0].data.imp[0].ext.custom; expect(custom['key1']).to.be.a('string'); expect(custom['key2']).to.be.a('string'); @@ -373,41 +381,199 @@ describe('OneVideoBidAdapter', function () { expect(custom['key7']).to.not.exist; expect(custom['key8']).to.not.exist; }); + + describe('content object validations', function () { + it('should not accept content object if value is Undefined ', function () { + bidRequest.params.video.content = null; + const requests = spec.buildRequests([bidRequest], bidderRequest); + const data = requests[0].data; + expect(data.imp[0].content).to.be.undefined; + }); + it('should not accept content object if value is is Array ', function () { + bidRequest.params.video.content = []; + const requests = spec.buildRequests([bidRequest], bidderRequest); + const data = requests[0].data; + expect(data.imp[0].content).to.be.undefined; + }); + it('should not accept content object if value is Number ', function () { + bidRequest.params.video.content = 123456; + const requests = spec.buildRequests([bidRequest], bidderRequest); + const data = requests[0].data; + expect(data.imp[0].content).to.be.undefined; + }); + it('should not accept content object if value is String ', function () { + bidRequest.params.video.content = 'keyValuePairs'; + const requests = spec.buildRequests([bidRequest], bidderRequest); + const data = requests[0].data; + expect(data.imp[0].content).to.be.undefined; + }); + it('should not accept content object if value is Boolean ', function () { + bidRequest.params.video.content = true; + const requests = spec.buildRequests([bidRequest], bidderRequest); + const data = requests[0].data; + expect(data.imp[0].content).to.be.undefined; + }); + it('should accept content object if value is Object ', function () { + bidRequest.params.video.content = {}; + const requests = spec.buildRequests([bidRequest], bidderRequest); + const data = requests[0].data; + expect(data.imp[0].content).to.be.a('object'); + }); + + it('should not append unsupported content object keys', function () { + bidRequest.params.video.content = { + fake: 'news', + unreal: 'param', + counterfit: 'data' + }; + const requests = spec.buildRequests([bidRequest], bidderRequest); + const data = requests[0].data; + expect(data.imp[0].content).to.be.empty; + }); + + it('should not append content string parameters if value is not string ', function () { + bidRequest.params.video.content = { + id: 1234, + title: ['Title'], + series: ['Series'], + season: ['Season'], + genre: ['Genre'], + contentrating: {1: 'C-Rating'}, + language: {1: 'EN'} + }; + const requests = spec.buildRequests([bidRequest], bidderRequest); + const data = requests[0].data; + expect(data.imp[0].content).to.be.a('object'); + expect(data.imp[0].content).to.be.empty + }); + it('should not append content Number parameters if value is not Number ', function () { + bidRequest.params.video.content = { + episode: '1', + context: 'context', + livestream: {0: 'stream'}, + len: [360], + prodq: [1], + }; + const requests = spec.buildRequests([bidRequest], bidderRequest); + const data = requests[0].data; + expect(data.imp[0].content).to.be.a('object'); + expect(data.imp[0].content).to.be.empty + }); + it('should not append content Array parameters if value is not Array ', function () { + bidRequest.params.video.content = { + cat: 'categories', + }; + const requests = spec.buildRequests([bidRequest], bidderRequest); + const data = requests[0].data; + expect(data.imp[0].content).to.be.a('object'); + expect(data.imp[0].content).to.be.empty + }); + it('should not append content ext if value is not Object ', function () { + bidRequest.params.video.content = { + ext: 'content.ext', + }; + const requests = spec.buildRequests([bidRequest], bidderRequest); + const data = requests[0].data; + expect(data.imp[0].content).to.be.a('object'); + expect(data.imp[0].content).to.be.empty + }); + it('should append supported parameters if value match validations ', function () { + bidRequest.params.video.content = { + id: '1234', + title: 'Title', + series: 'Series', + season: 'Season', + cat: [ + 'IAB1' + ], + genre: 'Genre', + contentrating: 'C-Rating', + language: 'EN', + episode: 1, + prodq: 1, + context: 1, + livestream: 0, + len: 360, + ext: {} + }; + const requests = spec.buildRequests([bidRequest], bidderRequest); + const data = requests[0].data; + expect(data.imp[0].content).to.deep.equal(bidRequest.params.video.content); + }); + }); }); describe('spec.interpretResponse', function () { it('should return no bids if the response is not valid', function () { - const bidResponse = spec.interpretResponse({ body: null }, { bidRequest }); + const bidResponse = spec.interpretResponse({ + body: null + }, { + bidRequest + }); expect(bidResponse.length).to.equal(0); }); it('should return no bids if the response "nurl" and "adm" are missing', function () { - const serverResponse = {seatbid: [{bid: [{price: 6.01}]}]}; - const bidResponse = spec.interpretResponse({ body: serverResponse }, { bidRequest }); + const serverResponse = { + seatbid: [{ + bid: [{ + price: 6.01 + }] + }] + }; + const bidResponse = spec.interpretResponse({ + body: serverResponse + }, { + bidRequest + }); expect(bidResponse.length).to.equal(0); }); it('should return no bids if the response "price" is missing', function () { - const serverResponse = {seatbid: [{bid: [{adm: ''}]}]}; - const bidResponse = spec.interpretResponse({ body: serverResponse }, { bidRequest }); + const serverResponse = { + seatbid: [{ + bid: [{ + adm: '' + }] + }] + }; + const bidResponse = spec.interpretResponse({ + body: serverResponse + }, { + bidRequest + }); expect(bidResponse.length).to.equal(0); }); it('should return a valid video bid response with just "adm"', function () { - const serverResponse = {seatbid: [{bid: [{id: 1, adid: 123, crid: 2, price: 6.01, adm: ''}]}], cur: 'USD'}; - const bidResponse = spec.interpretResponse({ body: serverResponse }, { bidRequest }); + const serverResponse = { + seatbid: [{ + bid: [{ + id: 1, + adid: 123, + crid: 2, + price: 6.01, + adm: '' + }] + }], + cur: 'USD' + }; + const bidResponse = spec.interpretResponse({ + body: serverResponse + }, { + bidRequest + }); let o = { requestId: bidRequest.bidId, bidderCode: spec.code, cpm: serverResponse.seatbid[0].bid[0].price, - adId: serverResponse.seatbid[0].bid[0].adid, creativeId: serverResponse.seatbid[0].bid[0].crid, vastXml: serverResponse.seatbid[0].bid[0].adm, width: 640, height: 480, mediaType: 'video', currency: 'USD', - ttl: 100, + ttl: 300, netRevenue: true, adUnitCode: bidRequest.adUnitCode, renderer: (bidRequest.mediaTypes.video.context === 'outstream') ? newRenderer(bidRequest, bidResponse) : undefined, @@ -428,12 +594,51 @@ describe('OneVideoBidAdapter', function () { } } } - const serverResponse = {seatbid: [{bid: [{id: 1, adid: 123, crid: 2, price: 6.01, adm: '
DAP UNIT HERE
'}]}], cur: 'USD'}; - const bidResponse = spec.interpretResponse({ body: serverResponse }, { bidRequest }); + const serverResponse = { + seatbid: [{ + bid: [{ + id: 1, + adid: 123, + crid: 2, + price: 6.01, + adm: '
DAP UNIT HERE
' + }] + }], + cur: 'USD' + }; + const bidResponse = spec.interpretResponse({ + body: serverResponse + }, { + bidRequest + }); expect(bidResponse.ad).to.equal('
DAP UNIT HERE
'); expect(bidResponse.mediaType).to.equal('banner'); expect(bidResponse.renderer).to.be.undefined; }); + + it('should default ttl to 300', function () { + const serverResponse = {seatbid: [{bid: [{id: 1, adid: 123, crid: 2, price: 6.01, adm: ''}]}], cur: 'USD'}; + const bidResponse = spec.interpretResponse({ body: serverResponse }, { bidRequest }); + expect(bidResponse.ttl).to.equal(300); + }); + it('should not allow ttl above 3601, default to 300', function () { + bidRequest.params.video.ttl = 3601; + const serverResponse = {seatbid: [{bid: [{id: 1, adid: 123, crid: 2, price: 6.01, adm: ''}]}], cur: 'USD'}; + const bidResponse = spec.interpretResponse({ body: serverResponse }, { bidRequest }); + expect(bidResponse.ttl).to.equal(300); + }); + it('should not allow ttl below 1, default to 300', function () { + bidRequest.params.video.ttl = 0; + const serverResponse = {seatbid: [{bid: [{id: 1, adid: 123, crid: 2, price: 6.01, adm: ''}]}], cur: 'USD'}; + const bidResponse = spec.interpretResponse({ body: serverResponse }, { bidRequest }); + expect(bidResponse.ttl).to.equal(300); + }); + it('should use custom ttl if under 3600', function () { + bidRequest.params.video.ttl = 1000; + const serverResponse = {seatbid: [{bid: [{id: 1, adid: 123, crid: 2, price: 6.01, adm: ''}]}], cur: 'USD'}; + const bidResponse = spec.interpretResponse({ body: serverResponse }, { bidRequest }); + expect(bidResponse.ttl).to.equal(1000); + }); }); describe('when GDPR and uspConsent applies', function () { @@ -476,22 +681,22 @@ describe('OneVideoBidAdapter', function () { }); it('should send a signal to specify that GDPR applies to this request', function () { - const request = spec.buildRequests([ bidRequest ], bidderRequest); + const request = spec.buildRequests([bidRequest], bidderRequest); expect(request[0].data.regs.ext.gdpr).to.equal(1); }); it('should send the consent string', function () { - const request = spec.buildRequests([ bidRequest ], bidderRequest); + const request = spec.buildRequests([bidRequest], bidderRequest); expect(request[0].data.user.ext.consent).to.equal(bidderRequest.gdprConsent.consentString); }); it('should send the uspConsent string', function () { - const request = spec.buildRequests([ bidRequest ], bidderRequest); + const request = spec.buildRequests([bidRequest], bidderRequest); expect(request[0].data.regs.ext.us_privacy).to.equal(bidderRequest.uspConsent); }); it('should send the uspConsent and GDPR ', function () { - const request = spec.buildRequests([ bidRequest ], bidderRequest); + const request = spec.buildRequests([bidRequest], bidderRequest); expect(request[0].data.regs.ext.gdpr).to.equal(1); expect(request[0].data.regs.ext.us_privacy).to.equal(bidderRequest.uspConsent); }); @@ -535,7 +740,7 @@ describe('OneVideoBidAdapter', function () { pubId: 'OneMDisplay' } }; - const requests = spec.buildRequests([ bidRequest ], bidderRequest); + const requests = spec.buildRequests([bidRequest], bidderRequest); const data = requests[0].data; const width = bidRequest.params.video.playerWidth; const height = bidRequest.params.video.playerHeight; @@ -585,7 +790,7 @@ describe('OneVideoBidAdapter', function () { pubId: 'OneMDisplay' } }; - const requests = spec.buildRequests([ bidRequest ], bidderRequest); + const requests = spec.buildRequests([bidRequest], bidderRequest); const data = requests[0].data; const width = bidRequest.params.video.playerWidth; const height = bidRequest.params.video.playerHeight; @@ -631,7 +836,7 @@ describe('OneVideoBidAdapter', function () { pubId: 'OneMDisplay' } }; - const requests = spec.buildRequests([ bidRequest ], bidderRequest); + const requests = spec.buildRequests([bidRequest], bidderRequest); const data = requests[0].data; const width = bidRequest.params.video.playerWidth; const height = bidRequest.params.video.playerHeight; @@ -650,19 +855,28 @@ describe('OneVideoBidAdapter', function () { const GDPR_CONSENT_STRING = 'GDPR_CONSENT_STRING'; it('should get correct user sync when iframeEnabled', function () { - let pixel = spec.getUserSyncs({pixelEnabled: true}, {}, {gdprApplies: true, consentString: GDPR_CONSENT_STRING}) + let pixel = spec.getUserSyncs({ + pixelEnabled: true + }, {}, { + gdprApplies: true, + consentString: GDPR_CONSENT_STRING + }) expect(pixel[1].type).to.equal('image'); expect(pixel[1].url).to.equal('https://sync-tm.everesttech.net/upi/pid/m7y5t93k?gdpr=1&gdpr_consent=' + GDPR_CONSENT_STRING + '&redir=https%3A%2F%2Fpixel.advertising.com%2Fups%2F55986%2Fsync%3Fuid%3D%24%7BUSER_ID%7D%26_origin%3D0&gdpr=1&gdpr_consent=' + encodeURI(GDPR_CONSENT_STRING)); }); it('should default to gdprApplies=0 when consentData is undefined', function () { - let pixel = spec.getUserSyncs({pixelEnabled: true}, {}, undefined); + let pixel = spec.getUserSyncs({ + pixelEnabled: true + }, {}, undefined); expect(pixel[1].url).to.equal('https://sync-tm.everesttech.net/upi/pid/m7y5t93k?gdpr=0&gdpr_consent=&redir=https%3A%2F%2Fpixel.advertising.com%2Fups%2F55986%2Fsync%3Fuid%3D%24%7BUSER_ID%7D%26_origin%3D0&gdpr=0&gdpr_consent='); }); }); describe('verify sync pixels', function () { - let pixel = spec.getUserSyncs({pixelEnabled: true}, {}, undefined); + let pixel = spec.getUserSyncs({ + pixelEnabled: true + }, {}, undefined); it('should be UPS sync pixel for DBM', function () { expect(pixel[0].url).to.equal('https://pixel.advertising.com/ups/57304/sync?gdpr=&gdpr_consent=&_origin=0&redir=true') }); diff --git a/test/spec/modules/openxBidAdapter_spec.js b/test/spec/modules/openxBidAdapter_spec.js index 121f8e76a07..c8c92392ff2 100644 --- a/test/spec/modules/openxBidAdapter_spec.js +++ b/test/spec/modules/openxBidAdapter_spec.js @@ -1,6 +1,7 @@ import {expect} from 'chai'; import {spec, USER_ID_CODE_TO_QUERY_ARG} from 'modules/openxBidAdapter.js'; import {newBidder} from 'src/adapters/bidderFactory.js'; +import {BANNER, VIDEO} from 'src/mediaTypes.js'; import {userSync} from 'src/userSync.js'; import {config} from 'src/config.js'; import * as utils from 'src/utils.js'; @@ -1043,13 +1044,24 @@ describe('OpenxAdapter', function () { britepoolid: '1111-britepoolid', criteoId: '1111-criteoId', digitrustid: {data: {id: 'DTID', keyv: 4, privacy: {optout: false}, producer: 'ABC', version: 2}}, + fabrickId: '1111-fabrickid', + haloId: '1111-haloid', id5id: {uid: '1111-id5id'}, idl_env: '1111-idl_env', + IDP: '1111-zeotap-idplusid', + idxId: '1111-idxid', + intentIqId: '1111-intentiqid', lipb: {lipbid: '1111-lipb'}, + lotamePanoramaId: '1111-lotameid', + merkleId: '1111-merkleid', netId: 'fH5A3n2O8_CZZyPoJVD-eabc6ECb7jhxCicsds7qSg', parrableId: { eid: 'eidVersion.encryptionKeyReference.encryptedValue' }, pubcid: '1111-pubcid', + quantcastId: '1111-quantcastid', + sharedId: '1111-sharedid', + tapadId: '111-tapadid', tdid: '1111-tdid', + verizonMediaId: '1111-verizonmediaid', }; // generates the same set of tests for each id provider @@ -1187,6 +1199,7 @@ describe('OpenxAdapter', function () { let getFloorSpy = sinon.spy(bidRequest1, 'getFloor'); spec.buildRequests([bidRequest1], mockBidderRequest); + expect(getFloorSpy.args[0][0].mediaType).to.equal(BANNER); expect(getFloorSpy.args[0][0].currency).to.equal('USD'); }); @@ -1205,6 +1218,7 @@ describe('OpenxAdapter', function () { let getFloorSpy = sinon.spy(bidRequest1, 'getFloor'); spec.buildRequests([bidRequest1], mockBidderRequest); + expect(getFloorSpy.args[0][0].mediaType).to.equal(BANNER); expect(getFloorSpy.args[0][0].currency).to.equal('bitcoin'); }); }) @@ -1345,6 +1359,87 @@ describe('OpenxAdapter', function () { }); }); }); + + describe('floors', function () { + it('should send out custom floors on bids that have customFloors specified', function () { + const bidRequest = Object.assign({}, + bidRequestsWithMediaTypes[0], + { + params: { + 'unit': '12345678', + 'delDomain': 'test-del-domain', + 'customFloor': 1.500001 + } + } + ); + + const request = spec.buildRequests([bidRequest], mockBidderRequest); + const dataParams = request[0].data; + + expect(dataParams.aumfs).to.exist; + expect(dataParams.aumfs).to.equal('1500'); + }); + + context('with floors module', function () { + let adServerCurrencyStub; + function makeBidWithFloorInfo(floorInfo) { + return Object.assign(utils.deepClone(bidRequestsWithMediaTypes[0]), + { + getFloor: () => { + return floorInfo; + } + }); + } + + beforeEach(function () { + adServerCurrencyStub = sinon + .stub(config, 'getConfig') + .withArgs('currency.adServerCurrency') + }); + + afterEach(function () { + config.getConfig.restore(); + }); + + it('should send out floors on bids', function () { + const floors = [9.99, 18.881]; + const bidRequests = floors.map(floor => { + return makeBidWithFloorInfo({ + currency: 'AUS', + floor: floor + }); + }); + const request = spec.buildRequests(bidRequests, mockBidderRequest); + + expect(request[0].data.aumfs).to.exist; + expect(request[0].data.aumfs).to.equal('9990'); + expect(request[1].data.aumfs).to.exist; + expect(request[1].data.aumfs).to.equal('18881'); + }); + + it('should send out floors on bids in the default currency', function () { + const bidRequest1 = makeBidWithFloorInfo({}); + + let getFloorSpy = sinon.spy(bidRequest1, 'getFloor'); + + spec.buildRequests([bidRequest1], mockBidderRequest); + expect(getFloorSpy.args[0][0].mediaType).to.equal(VIDEO); + expect(getFloorSpy.args[0][0].currency).to.equal('USD'); + }); + + it('should send out floors on bids in the ad server currency if defined', function () { + adServerCurrencyStub.returns('bitcoin'); + + const bidRequest1 = makeBidWithFloorInfo({}); + + let getFloorSpy = sinon.spy(bidRequest1, 'getFloor'); + + spec.buildRequests([bidRequest1], mockBidderRequest); + expect(getFloorSpy.args[0][0].mediaType).to.equal(VIDEO); + expect(getFloorSpy.args[0][0].currency).to.equal('bitcoin'); + }); + }) + }) }); describe('buildRequest for multi-format ad', function () { diff --git a/test/spec/modules/optimonAnalyticsAdapter_spec.js b/test/spec/modules/optimonAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..b5b76ce3fde --- /dev/null +++ b/test/spec/modules/optimonAnalyticsAdapter_spec.js @@ -0,0 +1,40 @@ +import * as utils from 'src/utils.js'; +import { expect } from 'chai'; +import optimonAnalyticsAdapter from '../../../modules/optimonAnalyticsAdapter.js'; +import adapterManager from 'src/adapterManager'; +import events from 'src/events'; +import constants from 'src/constants.json' + +const AD_UNIT_CODE = 'demo-adunit-1'; +const PUBLISHER_CONFIG = { + pubId: 'optimon_test', + pubAdxAccount: 123456789, + pubTimezone: 'Asia/Jerusalem' +}; + +describe('Optimon Analytics Adapter', () => { + const optmn_currentWindow = utils.getWindowSelf(); + let optmn_queue = []; + + beforeEach(() => { + optmn_currentWindow.OptimonAnalyticsAdapter = (...optmn_args) => optmn_queue.push(optmn_args); + adapterManager.enableAnalytics({ + provider: 'optimon' + }); + optmn_queue = [] + }); + + afterEach(() => { + optimonAnalyticsAdapter.disableAnalytics(); + }); + + it('should forward all events to the queue', () => { + const optmn_arguments = [AD_UNIT_CODE, PUBLISHER_CONFIG]; + + events.emit(constants.EVENTS.AUCTION_END, optmn_arguments) + events.emit(constants.EVENTS.BID_TIMEOUT, optmn_arguments) + events.emit(constants.EVENTS.BID_WON, optmn_arguments) + + expect(optmn_queue.length).to.eql(3); + }); +}); diff --git a/test/spec/modules/ozoneBidAdapter_spec.js b/test/spec/modules/ozoneBidAdapter_spec.js index c1022608b4a..10b8ce31d28 100644 --- a/test/spec/modules/ozoneBidAdapter_spec.js +++ b/test/spec/modules/ozoneBidAdapter_spec.js @@ -6,12 +6,13 @@ import {getGranularityKeyName, getGranularityObject} from '../../../modules/ozon import * as utils from '../../../src/utils.js'; const OZONEURI = 'https://elb.the-ozone-project.com/openrtb2/auction'; const BIDDER_CODE = 'ozone'; + /* NOTE - use firefox console to deep copy the objects to use here */ -var originalPropertyBag = {'lotameWasOverridden': 0, 'pageId': null}; +var originalPropertyBag = {'pageId': null}; var validBidRequests = [ { adUnitCode: 'div-gpt-ad-1460505748561-0', @@ -21,7 +22,7 @@ var validBidRequests = [ bidder: 'ozone', bidderRequestId: '1c1586b27a1b5c8', crumbs: {pubcid: '203a0692-f728-4856-87f6-9a25a6b63715'}, - params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], lotameData: {'Profile': {'tpid': 'c8ef27a0d4ba771a81159f0d2e792db4', 'Audiences': {'Audience': [{'id': '99999', 'abbr': 'sports'}, {'id': '88888', 'abbr': 'movie'}, {'id': '77777', 'abbr': 'blogger'}]}}}, placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { id: '2899ec066a91ff8', tagid: 'undefined', secure: 1, banner: { format: [{ w: 300, h: 250 }, { w: 300, h: 600 }], h: 250, topframe: 1, w: 300 } } ] }, + params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { id: '2899ec066a91ff8', tagid: 'undefined', secure: 1, banner: { format: [{ w: 300, h: 250 }, { w: 300, h: 600 }], h: 250, topframe: 1, w: 300 } } ] }, sizes: [[300, 250], [300, 600]], transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87' } @@ -36,7 +37,7 @@ var validBidRequestsMulti = [ bidder: 'ozone', bidderRequestId: '1c1586b27a1b5c8', crumbs: {pubcid: '203a0692-f728-4856-87f6-9a25a6b63715'}, - params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], lotameData: {'Profile': {'tpid': 'c8ef27a0d4ba771a81159f0d2e792db4', 'Audiences': {'Audience': [{'id': '99999', 'abbr': 'sports'}, {'id': '88888', 'abbr': 'movie'}, {'id': '77777', 'abbr': 'blogger'}]}}}, placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { id: '2899ec066a91ff8', tagid: 'undefined', secure: 1, banner: { format: [{ w: 300, h: 250 }, { w: 300, h: 600 }], h: 250, topframe: 1, w: 300 } } ] }, + params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { id: '2899ec066a91ff8', tagid: 'undefined', secure: 1, banner: { format: [{ w: 300, h: 250 }, { w: 300, h: 600 }], h: 250, topframe: 1, w: 300 } } ] }, sizes: [[300, 250], [300, 600]], transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87' }, @@ -49,11 +50,14 @@ var validBidRequestsMulti = [ bidder: 'ozone', bidderRequestId: '1c1586b27a1b5c0', crumbs: {pubcid: '203a0692-f728-4856-87f6-9a25a6b63715'}, - params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], lotameData: {'Profile': {'tpid': 'c8ef27a0d4ba771a81159f0d2e792db4', 'Audiences': {'Audience': [{'id': '99999', 'abbr': 'sports'}, {'id': '88888', 'abbr': 'movie'}, {'id': '77777', 'abbr': 'blogger'}]}}}, placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { id: '2899ec066a91ff8', tagid: 'undefined', secure: 1, banner: { format: [{ w: 300, h: 250 }, { w: 300, h: 600 }], h: 250, topframe: 1, w: 300 } } ] }, + params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { id: '2899ec066a91ff8', tagid: 'undefined', secure: 1, banner: { format: [{ w: 300, h: 250 }, { w: 300, h: 600 }], h: 250, topframe: 1, w: 300 } } ] }, sizes: [[300, 250], [300, 600]], transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87' } ]; +// use 'pubcid', 'tdid', 'id5id', 'parrableId', 'idl_env', 'criteoId', 'criteortus' +// NOTE THAT criteortus is no longer referenced anywhere - should be removed asap +// see http://prebid.org/dev-docs/modules/userId.html var validBidRequestsWithUserIdData = [ { adUnitCode: 'div-gpt-ad-1460505748561-0', @@ -63,10 +67,83 @@ var validBidRequestsWithUserIdData = [ bidder: 'ozone', bidderRequestId: '1c1586b27a1b5c8', crumbs: {pubcid: '203a0692-f728-4856-87f6-9a25a6b63715'}, - params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], lotameData: {'Profile': {'tpid': 'c8ef27a0d4ba771a81159f0d2e792db4', 'Audiences': {'Audience': [{'id': '99999', 'abbr': 'sports'}, {'id': '88888', 'abbr': 'movie'}, {'id': '77777', 'abbr': 'blogger'}]}}}, placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { id: '2899ec066a91ff8', tagid: 'undefined', secure: 1, banner: { format: [{ w: 300, h: 250 }, { w: 300, h: 600 }], h: 250, topframe: 1, w: 300 } } ] }, + params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { id: '2899ec066a91ff8', tagid: 'undefined', secure: 1, banner: { format: [{ w: 300, h: 250 }, { w: 300, h: 600 }], h: 250, topframe: 1, w: 300 } } ] }, sizes: [[300, 250], [300, 600]], transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87', - userId: {'pubcid': '12345678', 'id5id': { 'uid': 'ID5-someId' }, 'criteortus': {'ozone': {'userid': 'critId123'}}, 'idl_env': 'liverampId', 'lipb': {'lipbid': 'lipbidId123'}, 'parrableId': {eid: 'parrableid123'}} + userId: { + 'pubcid': '12345678', + 'tdid': '1111tdid', + 'id5id': 'ID5-someId', + 'criteortus': {'ozone': {'userid': 'critId123'}}, + 'criteoId': '1111criteoId', + 'idl_env': 'liverampId', + 'lipb': {'lipbid': 'lipbidId123'}, + 'parrableId': {'eid': '01.5678.parrableid'} + }, + userIdAsEids: [ + { + 'source': 'pubcid.org', + 'uids': [ + { + 'id': '12345678', + 'atype': 1 + } + ] + }, + { + 'source': 'adserver.org', + 'uids': [{ + 'id': '1111tdid', + 'atype': 1, + 'ext': { + 'rtiPartner': 'TDID' + } + }] + }, + { + 'source': 'id5-sync.com', + 'uids': [{ + 'id': 'ID5-someId', + 'atype': 1, + }] + }, + { + 'source': 'criteortus', + 'uids': [{ + 'id': {'ozone': {'userid': 'critId123'}}, + 'atype': 1, + }] + }, + { + 'source': 'criteoId', + 'uids': [{ + 'id': '1111criteoId', + 'atype': 1, + }] + }, + { + 'source': 'idl_env', + 'uids': [{ + 'id': 'liverampId', + 'atype': 1, + }] + }, + { + 'source': 'lipb', + 'uids': [{ + 'id': {'lipbid': 'lipbidId123'}, + 'atype': 1, + }] + }, + { + 'source': 'parrableId', + 'uids': [{ + 'id': {'eid': '01.5678.parrableid'}, + 'atype': 1, + }] + } + ] + } ]; var validBidRequestsMinimal = [ @@ -91,7 +168,7 @@ var validBidRequestsNoSizes = [ bidder: 'ozone', bidderRequestId: '1c1586b27a1b5c8', crumbs: {pubcid: '203a0692-f728-4856-87f6-9a25a6b63715'}, - params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], lotameData: {'Profile': {'tpid': 'c8ef27a0d4ba771a81159f0d2e792db4', 'Audiences': {'Audience': [{'id': '99999', 'abbr': 'sports'}, {'id': '88888', 'abbr': 'movie'}, {'id': '77777', 'abbr': 'blogger'}]}}}, placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { id: '2899ec066a91ff8', tagid: 'undefined', secure: 1, banner: { format: [{ w: 300, h: 250 }, { w: 300, h: 600 }], h: 250, topframe: 1, w: 300 } } ] }, + params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { id: '2899ec066a91ff8', tagid: 'undefined', secure: 1, banner: { format: [{ w: 300, h: 250 }, { w: 300, h: 600 }], h: 250, topframe: 1, w: 300 } } ] }, transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87' } ]; @@ -105,7 +182,7 @@ var validBidRequestsWithBannerMediaType = [ bidder: 'ozone', bidderRequestId: '1c1586b27a1b5c8', crumbs: {pubcid: '203a0692-f728-4856-87f6-9a25a6b63715'}, - params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], lotameData: {'Profile': {'tpid': 'c8ef27a0d4ba771a81159f0d2e792db4', 'Audiences': {'Audience': [{'id': '99999', 'abbr': 'sports'}, {'id': '88888', 'abbr': 'movie'}, {'id': '77777', 'abbr': 'blogger'}]}}}, placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { id: '2899ec066a91ff8', tagid: 'undefined', secure: 1, banner: { format: [{ w: 300, h: 250 }, { w: 300, h: 600 }], h: 250, topframe: 1, w: 300 } } ] }, + params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { id: '2899ec066a91ff8', tagid: 'undefined', secure: 1, banner: { format: [{ w: 300, h: 250 }, { w: 300, h: 600 }], h: 250, topframe: 1, w: 300 } } ] }, mediaTypes: {banner: {sizes: [[300, 250], [300, 600]]}}, transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87' } @@ -119,7 +196,7 @@ var validBidRequestsWithNonBannerMediaTypesAndValidOutstreamVideo = [ bidder: 'ozone', bidderRequestId: '1c1586b27a1b5c8', crumbs: {pubcid: '203a0692-f728-4856-87f6-9a25a6b63715'}, - params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], lotameData: {'Profile': {'tpid': 'c8ef27a0d4ba771a81159f0d2e792db4', 'Audiences': {'Audience': [{'id': '99999', 'abbr': 'sports'}, {'id': '88888', 'abbr': 'movie'}, {'id': '77777', 'abbr': 'blogger'}]}}}, placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { id: '2899ec066a91ff8', tagid: 'undefined', secure: 1, video: {skippable: true, playback_method: ['auto_play_sound_off'], targetDiv: 'some-different-div-id-to-my-adunitcode'} } ] }, + params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { id: '2899ec066a91ff8', tagid: 'undefined', secure: 1, video: {skippable: true, playback_method: ['auto_play_sound_off'], targetDiv: 'some-different-div-id-to-my-adunitcode'} } ] }, mediaTypes: {video: {mimes: ['video/mp4'], 'context': 'outstream', 'sizes': [640, 480], playerSize: [640, 480]}, native: {info: 'dummy data'}}, transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87' } @@ -161,43 +238,21 @@ var validBidRequests1OutstreamVideo2020 = [ } } ], - 'lotameData': { - 'Profile': { - 'tpid': '4e5c21fc7c181c2b1eb3a73d543a27f6', - 'pid': '3a45fd4872fa01f35c49586d8dcb7c60', - 'Audiences': { - 'Audience': [ - { - 'id': '439847', - 'abbr': 'all' - }, - { - 'id': '446197', - 'abbr': 'Arts, Culture & Literature' - }, - { - 'id': '446198', - 'abbr': 'Business' - } - ] - } + 'userId': { + 'pubcid': '2ada6ae6-aeca-4e07-8922-a99b3aaf8a56' + }, + 'userIdAsEids': [ + { + 'source': 'pubcid.org', + 'uids': [ + { + 'id': '2ada6ae6-aeca-4e07-8922-a99b3aaf8a56', + 'atype': 1 + } + ] } - } - }, - 'userId': { - 'pubcid': '2ada6ae6-aeca-4e07-8922-a99b3aaf8a56' + ] }, - 'userIdAsEids': [ - { - 'source': 'pubcid.org', - 'uids': [ - { - 'id': '2ada6ae6-aeca-4e07-8922-a99b3aaf8a56', - 'atype': 1 - } - ] - } - ], 'mediaTypes': { 'video': { 'playerSize': [ @@ -272,32 +327,10 @@ var validBidderRequest1OutstreamVideo2020 = { 'pt9': '|k0xw2vqzp33kklb3j5w4|||' } } - ], - 'lotameData': { - 'Profile': { - 'tpid': '4e5c21fc7c181c2b1eb3a73d543a27f6', - 'pid': '3a45fd4872fa01f35c49586d8dcb7c60', - 'Audiences': { - 'Audience': [ - { - 'id': '439847', - 'abbr': 'all' - }, - { - 'id': '446197', - 'abbr': 'Arts, Culture & Literature' - }, - { - 'id': '446198', - 'abbr': 'Business' - } - ] - } - } - } + ] }, 'userId': { - 'id5id': { uid: 'ID5-ZHMOpSv9CkZNiNd1oR4zc62AzCgSS73fPjmQ6Od7OA' }, + 'id5id': 'ID5-ZHMOpSv9CkZNiNd1oR4zc62AzCgSS73fPjmQ6Od7OA', 'pubcid': '2ada6ae6-aeca-4e07-8922-a99b3aaf8a56' }, 'userIdAsEids': [ @@ -370,7 +403,7 @@ var validBidderRequest = { bidder: 'ozone', bidderRequestId: '1c1586b27a1b5c8', crumbs: {pubcid: '203a0692-f728-4856-87f6-9a25a6b63715'}, - params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], lotameData: {'Profile': {'tpid': 'c8ef27a0d4ba771a81159f0d2e792db4', 'Audiences': {'Audience': [{'id': '99999', 'abbr': 'sports'}, {'id': '88888', 'abbr': 'movie'}, {'id': '77777', 'abbr': 'blogger'}]}}}, placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { banner: { topframe: 1, w: 300, h: 250, format: [{ w: 300, h: 250 }, { w: 300, h: 600 }] }, id: '2899ec066a91ff8', secure: 1, tagid: 'undefined' } ] }, + params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { banner: { topframe: 1, w: 300, h: 250, format: [{ w: 300, h: 250 }, { w: 300, h: 600 }] }, id: '2899ec066a91ff8', secure: 1, tagid: 'undefined' } ] }, sizes: [[300, 250], [300, 600]], transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87' }], @@ -399,7 +432,7 @@ var bidderRequestWithFullGdpr = { bidder: 'ozone', bidderRequestId: '1c1586b27a1b5c8', crumbs: {pubcid: '203a0692-f728-4856-87f6-9a25a6b63715'}, - params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], lotameData: {'Profile': {'tpid': 'c8ef27a0d4ba771a81159f0d2e792db4', 'Audiences': {'Audience': [{'id': '99999', 'abbr': 'sports'}, {'id': '88888', 'abbr': 'movie'}, {'id': '77777', 'abbr': 'blogger'}]}}}, placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { banner: { topframe: 1, w: 300, h: 250, format: [{ w: 300, h: 250 }, { w: 300, h: 600 }] }, id: '2899ec066a91ff8', secure: 1, tagid: 'undefined' } ] }, + params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { banner: { topframe: 1, w: 300, h: 250, format: [{ w: 300, h: 250 }, { w: 300, h: 600 }] }, id: '2899ec066a91ff8', secure: 1, tagid: 'undefined' } ] }, sizes: [[300, 250], [300, 600]], transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87' }], @@ -491,17 +524,6 @@ var bidderRequestWithPartialGdpr = { params: { publisherId: '9876abcd12-3', customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], - lotameData: { - 'Profile': { - 'tpid': 'c8ef27a0d4ba771a81159f0d2e792db4', - 'Audiences': { - 'Audience': [{'id': '99999', 'abbr': 'sports'}, { - 'id': '88888', - 'abbr': 'movie' - }, {'id': '77777', 'abbr': 'blogger'}] - } - } - }, placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', @@ -982,29 +1004,7 @@ var multiRequest1 = [ 'pt9': '|k0xw2vqzp33kklb3j5w4|||' } } - ], - 'lotameData': { - 'Profile': { - 'tpid': '4e5c21fc7c181c2b1eb3a73d543a27f6', - 'pid': '3a45fd4872fa01f35c49586d8dcb7c60', - 'Audiences': { - 'Audience': [ - { - 'id': '439847', - 'abbr': 'all' - }, - { - 'id': '446197', - 'abbr': 'Arts, Culture & Literature' - }, - { - 'id': '446198', - 'abbr': 'Business' - } - ] - } - } - } + ] }, 'mediaTypes': { 'banner': { @@ -1069,29 +1069,7 @@ var multiRequest1 = [ 'pt9': '|k0xw2vqzp33kklb3j5w4|||' } } - ], - 'lotameData': { - 'Profile': { - 'tpid': '4e5c21fc7c181c2b1eb3a73d543a27f6', - 'pid': '3a45fd4872fa01f35c49586d8dcb7c60', - 'Audiences': { - 'Audience': [ - { - 'id': '439847', - 'abbr': 'all' - }, - { - 'id': '446197', - 'abbr': 'Arts, Culture & Literature' - }, - { - 'id': '446198', - 'abbr': 'Business' - } - ] - } - } - } + ] }, 'mediaTypes': { 'banner': { @@ -1165,29 +1143,7 @@ var multiBidderRequest1 = { 'pt9': '|k0xw2vqzp33kklb3j5w4|||' } } - ], - 'lotameData': { - 'Profile': { - 'tpid': '4e5c21fc7c181c2b1eb3a73d543a27f6', - 'pid': '3a45fd4872fa01f35c49586d8dcb7c60', - 'Audiences': { - 'Audience': [ - { - 'id': '439847', - 'abbr': 'all' - }, - { - 'id': '446197', - 'abbr': 'Arts, Culture & Literature' - }, - { - 'id': '446198', - 'abbr': 'Business' - } - ] - } - } - } + ] }, 'mediaTypes': { 'banner': { @@ -1252,29 +1208,7 @@ var multiBidderRequest1 = { 'pt9': '|k0xw2vqzp33kklb3j5w4|||' } } - ], - 'lotameData': { - 'Profile': { - 'tpid': '4e5c21fc7c181c2b1eb3a73d543a27f6', - 'pid': '3a45fd4872fa01f35c49586d8dcb7c60', - 'Audiences': { - 'Audience': [ - { - 'id': '439847', - 'abbr': 'all' - }, - { - 'id': '446197', - 'abbr': 'Arts, Culture & Literature' - }, - { - 'id': '446198', - 'abbr': 'Business' - } - ] - } - } - } + ] }, 'mediaTypes': { 'banner': { @@ -1588,8 +1522,7 @@ describe('ozone Adapter', function () { placementId: '1310000099', publisherId: '9876abcd12-3', siteId: '1234567890', - customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}], - lotameData: {'Profile': {'tpid': 'c8ef27a0d4ba771a81159f0d2e792db4', 'Audiences': {'Audience': [{'id': '99999', 'abbr': 'sports'}, {'id': '88888', 'abbr': 'movie'}, {'id': '77777', 'abbr': 'blogger'}]}}}, + customData: [{'settings': {}, 'targeting': {'gender': 'bart', 'age': 'low'}}] }, siteId: 1234567890 } @@ -1883,26 +1816,11 @@ describe('ozone Adapter', function () { it('should not validate customParams - this is a renamed key', function () { expect(spec.isBidRequestValid(xBadCustomParams)).to.equal(false); }); - - var xBadLotame = { - bidder: BIDDER_CODE, - params: { - 'placementId': '1234567890', - 'publisherId': '9876abcd12-3', - 'lotameData': 'this should be an object', - siteId: '1234567890' - } - }; - it('should not validate lotameData being sent', function () { - expect(spec.isBidRequestValid(xBadLotame)).to.equal(false); - }); - var xBadVideoContext2 = { bidder: BIDDER_CODE, params: { 'placementId': '1234567890', 'publisherId': '9876abcd12-3', - 'lotameData': {}, siteId: '1234567890' }, mediaTypes: { @@ -1937,35 +1855,6 @@ describe('ozone Adapter', function () { instreamVid.mediaTypes.video.context = 'instream'; expect(spec.isBidRequestValid(instreamVid)).to.equal(true); }); - // validate lotame override parameters - it('should validate lotame override params', function () { - // mock the getGetParametersAsObject function to simulate GET parameters for lotame overrides: - spec.getGetParametersAsObject = function() { - return {'oz_lotameid': '123abc', 'oz_lotamepid': 'pid123', 'oz_lotametpid': 'tpid123'}; - }; - expect(spec.isBidRequestValid(validBidReq)).to.equal(true); - }); - it('should validate missing lotame override params', function () { - // mock the getGetParametersAsObject function to simulate GET parameters for lotame overrides: - spec.getGetParametersAsObject = function() { - return {'oz_lotameid': '123abc', 'oz_lotamepid': 'pid123'}; - }; - expect(spec.isBidRequestValid(validBidReq)).to.equal(false); - }); - it('should validate invalid lotame override params', function () { - // mock the getGetParametersAsObject function to simulate GET parameters for lotame overrides: - spec.getGetParametersAsObject = function() { - return {'oz_lotameid': '123abc', 'oz_lotamepid': 'pid123', 'oz_lotametpid': '123 "this ain\\t right!" eee'}; - }; - expect(spec.isBidRequestValid(validBidReq)).to.equal(false); - }); - it('should validate no lotame override params', function () { - // mock the getGetParametersAsObject function to simulate GET parameters for lotame overrides: - spec.getGetParametersAsObject = function() { - return {}; - }; - expect(spec.isBidRequestValid(validBidReq)).to.equal(true); - }); }); describe('buildRequests', function () { @@ -1989,19 +1878,27 @@ describe('ozone Adapter', function () { const request = spec.buildRequests(validBidRequests, validBidderRequest.bidderRequest); expect(request.data).to.be.a('string'); var data = JSON.parse(request.data); - expect(data.ext.ozone.lotameData).to.be.an('object'); + expect(data.imp[0].ext.ozone.customData).to.be.an('array'); + expect(request).not.to.have.key('lotameData'); + expect(request).not.to.have.key('customData'); + }); + + it('adds all parameters inside the ext object only - lightning', function () { + let localBidReq = JSON.parse(JSON.stringify(validBidRequests)); + const request = spec.buildRequests(localBidReq, validBidderRequest.bidderRequest); + expect(request.data).to.be.a('string'); + var data = JSON.parse(request.data); expect(data.imp[0].ext.ozone.customData).to.be.an('array'); expect(request).not.to.have.key('lotameData'); expect(request).not.to.have.key('customData'); }); it('ignores ozoneData in & after version 2.1.1', function () { - let validBidRequestsWithOzoneData = validBidRequests; + let validBidRequestsWithOzoneData = JSON.parse(JSON.stringify(validBidRequests)); validBidRequestsWithOzoneData[0].params.ozoneData = {'networkID': '3048', 'dfpSiteID': 'd.thesun', 'sectionID': 'homepage', 'path': '/', 'sec_id': 'null', 'sec': 'sec', 'topics': 'null', 'kw': 'null', 'aid': 'null', 'search': 'null', 'article_type': 'null', 'hide_ads': '', 'article_slug': 'null'}; - const request = spec.buildRequests(validBidRequests, validBidderRequest.bidderRequest); + const request = spec.buildRequests(validBidRequestsWithOzoneData, validBidderRequest.bidderRequest); expect(request.data).to.be.a('string'); var data = JSON.parse(request.data); - expect(data.ext.ozone.lotameData).to.be.an('object'); expect(data.imp[0].ext.ozone.customData).to.be.an('array'); expect(data.imp[0].ext.ozone.ozoneData).to.be.undefined; expect(request).not.to.have.key('lotameData'); @@ -2018,7 +1915,7 @@ describe('ozone Adapter', function () { expect(request).to.have.all.keys(['bidderRequest', 'data', 'method', 'url']); }); - it('handles no ozone, lotame or custom data', function () { + it('handles no ozone or custom data', function () { const request = spec.buildRequests(validBidRequestsMinimal, validBidderRequest.bidderRequest); expect(request).to.have.all.keys(['bidderRequest', 'data', 'method', 'url']); }); @@ -2080,6 +1977,7 @@ describe('ozone Adapter', function () { expect(payload.regs.ext.gdpr).to.equal(1); expect(payload.user.ext.consent).to.equal(consentString); }); + it('should set regs.ext.gdpr flag to 0 when gdprApplies is false', function () { let consentString = 'BOcocyaOcocyaAfEYDENCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NphLgA=='; let bidderRequest = validBidderRequest.bidderRequest; @@ -2118,13 +2016,14 @@ describe('ozone Adapter', function () { bidRequests[0]['userId'] = { 'criteortus': '1111', 'digitrustid': {data: {id: 'DTID', keyv: 4, privacy: {optout: false}, producer: 'ABC', version: 2}}, - 'id5id': {'uid': '2222'}, + 'id5id': '2222', 'idl_env': '3333', 'lipb': {'lipbid': '4444'}, - 'parrableId': {eid: 'eidVersion.encryptionKeyReference.encryptedValue'}, + 'parrableid': 'eidVersion.encryptionKeyReference.encryptedValue', 'pubcid': '5555', 'tdid': '6666' }; + bidRequests[0]['userIdAsEids'] = validBidRequestsWithUserIdData[0]['userIdAsEids']; const request = spec.buildRequests(bidRequests, bidderRequest); const payload = JSON.parse(request.data); let firstBid = payload.imp[0].ext.ozone; @@ -2138,62 +2037,121 @@ describe('ozone Adapter', function () { bidRequests[0]['userId'] = { 'criteortus': '1111', 'digitrustid': {data: {id: 'DTID', keyv: 4, privacy: {optout: false}, producer: 'ABC', version: 2}}, - 'id5id': {'uid': '2222'}, + 'id5id': '2222', 'idl_env': '3333', 'lipb': {'lipbid': '4444'}, - 'parrableId': {eid: 'eidVersion.encryptionKeyReference.encryptedValue'}, + 'parrableid': 'eidVersion.encryptionKeyReference.encryptedValue', // 'pubcid': '5555', // remove pubcid from here to emulate the OLD module & cause the failover code to kick in 'tdid': '6666' }; + bidRequests[0]['userIdAsEids'] = validBidRequestsWithUserIdData[0]['userIdAsEids']; const request = spec.buildRequests(bidRequests, validBidderRequest.bidderRequest); const payload = JSON.parse(request.data); expect(payload.ext.ozone.pubcid).to.equal(bidRequests[0]['crumbs']['pubcid']); delete validBidRequests[0].userId; // tidy up now, else it will screw with other tests }); - it('should add a user.ext.eids object to contain user ID data in the new location (Nov 2019)', function() { + it('should add a user.ext.eids object to contain user ID data in the new location (Nov 2019) Updated Aug 2020', function() { const request = spec.buildRequests(validBidRequestsWithUserIdData, validBidderRequest.bidderRequest); + /* + 'pubcid': '12345678', + 'tdid': '1111tdid', + 'id5id': 'ID5-someId', + 'criteortus': {'ozone': {'userid': 'critId123'}}, + 'criteoId': '1111criteoId', + 'idl_env': 'liverampId', + 'lipb': {'lipbid': 'lipbidId123'}, + 'parrableId': {'eid': '01.5678.parrableid'} + */ + const payload = JSON.parse(request.data); expect(payload.user).to.exist; expect(payload.user.ext).to.exist; expect(payload.user.ext.eids).to.exist; - expect(payload.user.ext.eids[0]['source']).to.equal('pubcid'); + expect(payload.user.ext.eids[0]['source']).to.equal('pubcid.org'); expect(payload.user.ext.eids[0]['uids'][0]['id']).to.equal('12345678'); - expect(payload.user.ext.eids[1]['source']).to.equal('pubcommon'); - expect(payload.user.ext.eids[1]['uids'][0]['id']).to.equal('12345678'); + expect(payload.user.ext.eids[1]['source']).to.equal('adserver.org'); + expect(payload.user.ext.eids[1]['uids'][0]['id']).to.equal('1111tdid'); expect(payload.user.ext.eids[2]['source']).to.equal('id5-sync.com'); expect(payload.user.ext.eids[2]['uids'][0]['id']).to.equal('ID5-someId'); - expect(payload.user.ext.eids[3]['source']).to.equal('criteortus'); - expect(payload.user.ext.eids[3]['uids'][0]['id']).to.equal('critId123'); - expect(payload.user.ext.eids[4]['source']).to.equal('liveramp.com'); - expect(payload.user.ext.eids[4]['uids'][0]['id']).to.equal('liverampId'); - expect(payload.user.ext.eids[5]['source']).to.equal('liveintent.com'); - expect(payload.user.ext.eids[5]['uids'][0]['id']).to.equal('lipbidId123'); - expect(payload.user.ext.eids[6]['source']).to.equal('parrable.com'); - expect(payload.user.ext.eids[6]['uids'][0]['id']).to.equal('parrableid123'); + expect(payload.user.ext.eids[3]['source']).to.equal('criteortus'); // this is deprecated + expect(payload.user.ext.eids[3]['uids'][0]['id']['ozone']['userid']).to.equal('critId123'); + expect(payload.user.ext.eids[4]['source']).to.equal('criteoId'); + expect(payload.user.ext.eids[4]['uids'][0]['id']).to.equal('1111criteoId'); + expect(payload.user.ext.eids[5]['source']).to.equal('idl_env'); + expect(payload.user.ext.eids[5]['uids'][0]['id']).to.equal('liverampId'); + expect(payload.user.ext.eids[6]['source']).to.equal('lipb'); + expect(payload.user.ext.eids[6]['uids'][0]['id']['lipbid']).to.equal('lipbidId123'); + expect(payload.user.ext.eids[7]['source']).to.equal('parrableId'); + expect(payload.user.ext.eids[7]['uids'][0]['id']['eid']).to.equal('01.5678.parrableid'); + }); + + it('replaces the auction url for a config override', function () { + spec.propertyBag.whitelabel = null; + let fakeOrigin = 'http://sometestendpoint'; + config.setConfig({'ozone': {'endpointOverride': {'origin': fakeOrigin}}}); + const request = spec.buildRequests(validBidRequests, validBidderRequest.bidderRequest); + expect(request.url).to.equal(fakeOrigin + '/openrtb2/auction'); + expect(request.method).to.equal('POST'); + config.setConfig({'ozone': {'kvpPrefix': null, 'endpointOverride': null}}); + spec.propertyBag.whitelabel = null; }); + it('replaces the renderer url for a config override', function () { + spec.propertyBag.whitelabel = null; + let fakeUrl = 'http://renderer.com'; + config.setConfig({'ozone': {'endpointOverride': {'rendererUrl': fakeUrl}}}); + const request = spec.buildRequests(validBidRequests1OutstreamVideo2020, validBidderRequest1OutstreamVideo2020.bidderRequest); + const result = spec.interpretResponse(getCleanValidVideoResponse(), validBidderRequest1OutstreamVideo2020); + const bid = result[0]; + expect(bid.renderer).to.be.an.instanceOf(Renderer); + expect(bid.renderer.url).to.equal(fakeUrl); + config.setConfig({'ozone': {'kvpPrefix': null, 'endpointOverride': null}}); + spec.propertyBag.whitelabel = null; + }); + + it('replaces the kvp prefix ', function () { + spec.propertyBag.whitelabel = null; + config.setConfig({'ozone': {'kvpPrefix': 'test'}}); + const request = spec.buildRequests(validBidRequests, validBidderRequest.bidderRequest); + const data = JSON.parse(request.data); + expect(data.ext.ozone).to.haveOwnProperty('test_rw'); + config.setConfig({'ozone': {'kvpPrefix': null}}); + spec.propertyBag.whitelabel = null; + }); + + it('handles an alias ', function () { + spec.propertyBag.whitelabel = null; + config.setConfig({'lmc': {'kvpPrefix': 'test'}}); + let br = JSON.parse(JSON.stringify(validBidRequests)); + br[0]['bidder'] = 'lmc'; + const request = spec.buildRequests(br, validBidderRequest.bidderRequest); + const data = JSON.parse(request.data); + expect(data.ext.lmc).to.haveOwnProperty('test_rw'); + config.setConfig({'lmc': {'kvpPrefix': null}}); // I cant remove the key so set the value to null + spec.propertyBag.whitelabel = null; + }); + var specMock = utils.deepClone(spec); it('should use oztestmode GET value if set', function() { // mock the getGetParametersAsObject function to simulate GET parameters for oztestmode: - spec.getGetParametersAsObject = function() { + specMock.getGetParametersAsObject = function() { return {'oztestmode': 'mytestvalue_123'}; }; - const request = spec.buildRequests(validBidRequests, validBidderRequest.bidderRequest); + const request = specMock.buildRequests(validBidRequests, validBidderRequest.bidderRequest); const data = JSON.parse(request.data); expect(data.imp[0].ext.ozone.customData).to.be.an('array'); expect(data.imp[0].ext.ozone.customData[0].targeting.oztestmode).to.equal('mytestvalue_123'); }); it('should use oztestmode GET value if set, even if there is no customdata in config', function() { // mock the getGetParametersAsObject function to simulate GET parameters for oztestmode: - spec.getGetParametersAsObject = function() { + specMock.getGetParametersAsObject = function() { return {'oztestmode': 'mytestvalue_123'}; }; - const request = spec.buildRequests(validBidRequestsMinimal, validBidderRequest.bidderRequest); + const request = specMock.buildRequests(validBidRequestsMinimal, validBidderRequest.bidderRequest); const data = JSON.parse(request.data); expect(data.imp[0].ext.ozone.customData).to.be.an('array'); expect(data.imp[0].ext.ozone.customData[0].targeting.oztestmode).to.equal('mytestvalue_123'); }); - var specMock = utils.deepClone(spec); it('should use a valid ozstoredrequest GET value if set to override the placementId values, and set oz_rw if we find it', function() { // mock the getGetParametersAsObject function to simulate GET parameters for ozstoredrequest: specMock.getGetParametersAsObject = function() { @@ -2215,54 +2173,6 @@ describe('ozone Adapter', function () { expect(data.imp[0].ext.prebid.storedrequest.id).to.equal('1310000099'); }); - it('should pick up the value of valid lotame override parameters when there is a lotame object', function () { - spec.getGetParametersAsObject = function() { - return {'oz_lotameid': '123abc', 'oz_lotamepid': 'pid123', 'oz_lotametpid': '123eee'}; - }; - const request = spec.buildRequests(validBidRequests, validBidderRequest.bidderRequest); - const payload = JSON.parse(request.data); - expect(payload.ext.ozone.lotameData.Profile.Audiences.Audience[0].id).to.equal('123abc'); - expect(payload.ext.ozone.oz_lot_rw).to.equal(1); - }); - it('should pick up the value of valid lotame override parameters when there is an empty lotame object', function () { - let nolotameBidReq = JSON.parse(JSON.stringify(validBidRequests)); - nolotameBidReq[0].params.lotameData = {}; - spec.getGetParametersAsObject = function() { - return {'oz_lotameid': '123abc', 'oz_lotamepid': 'pid123', 'oz_lotametpid': '123eeetpid'}; - }; - const request = spec.buildRequests(nolotameBidReq, validBidderRequest.bidderRequest); - const payload = JSON.parse(request.data); - expect(payload.ext.ozone.lotameData.Profile.Audiences.Audience[0].id).to.equal('123abc'); - expect(payload.ext.ozone.lotameData.Profile.tpid).to.equal('123eeetpid'); - expect(payload.ext.ozone.lotameData.Profile.pid).to.equal('pid123'); - expect(payload.ext.ozone.oz_lot_rw).to.equal(1); - }); - it('should pick up the value of valid lotame override parameters when there is NO "lotame" key at all', function () { - let nolotameBidReq = JSON.parse(JSON.stringify(validBidRequests)); - delete (nolotameBidReq[0].params['lotameData']); - spec.getGetParametersAsObject = function() { - return {'oz_lotameid': '123abc', 'oz_lotamepid': 'pid123', 'oz_lotametpid': '123eeetpid'}; - }; - const request = spec.buildRequests(nolotameBidReq, validBidderRequest.bidderRequest); - const payload = JSON.parse(request.data); - expect(payload.ext.ozone.lotameData.Profile.Audiences.Audience[0].id).to.equal('123abc'); - expect(payload.ext.ozone.lotameData.Profile.tpid).to.equal('123eeetpid'); - expect(payload.ext.ozone.lotameData.Profile.pid).to.equal('pid123'); - expect(payload.ext.ozone.oz_lot_rw).to.equal(1); - spec.propertyBag = originalPropertyBag; // tidy up - }); - // NOTE - only one negative test case; - // you can't send invalid lotame params to buildRequests because 'validate' will have rejected them - it('should not use lotame override parameters if they dont exist', function () { - expect(spec.propertyBag.lotameWasOverridden).to.equal(0); - spec.getGetParametersAsObject = function() { - return {}; // no lotame override params - }; - const request = spec.buildRequests(validBidRequests, validBidderRequest.bidderRequest); - const payload = JSON.parse(request.data); - expect(payload.ext.ozone.oz_lot_rw).to.equal(0); - }); - it('should pick up the config value of coppa & set it in the request', function () { config.setConfig({'coppa': true}); const request = spec.buildRequests(validBidRequestsNoSizes, validBidderRequest.bidderRequest); @@ -2609,6 +2519,14 @@ describe('ozone Adapter', function () { const result = playerSizeIsNestedArray(obj); expect(result).to.be.null; }); + it('should add oz_appnexus_dealid into ads request if dealid exists in the auction response', function () { + const request = spec.buildRequests(validBidRequestsMulti, validBidderRequest.bidderRequest); + let validres = JSON.parse(JSON.stringify(validResponse2Bids)); + validres.body.seatbid[0].bid[0].dealid = '1234'; + const result = spec.interpretResponse(validres, request); + expect(utils.deepAccess(result[0].adserverTargeting, 'oz_appnexus_dealid')).to.equal('1234'); + expect(utils.deepAccess(result[1].adserverTargeting, 'oz_appnexus_dealid', '')).to.equal(''); + }); }); describe('default size', function () { @@ -2660,44 +2578,6 @@ describe('ozone Adapter', function () { config.resetConfig(); }); }); - describe('makeLotameObjectFromOverride', function() { - it('should update an object with valid lotame data', function () { - let objLotameOverride = {'oz_lotametpid': '1234', 'oz_lotameid': '12345', 'oz_lotamepid': '123456'}; - let result = spec.makeLotameObjectFromOverride( - objLotameOverride, - {'Profile': {'pid': 'originalpid', 'tpid': 'originaltpid', 'Audiences': {'Audience': [{'id': 'aud1'}]}}} - ); - expect(result.Profile.Audiences.Audience).to.be.an('array'); - expect(result.Profile.Audiences.Audience[0]).to.be.an('object'); - expect(result.Profile.Audiences.Audience[0]).to.deep.include({'id': '12345', 'abbr': '12345'}); - }); - it('should return the original object if it seems weird', function () { - let objLotameOverride = {'oz_lotametpid': '1234', 'oz_lotameid': '12345', 'oz_lotamepid': '123456'}; - let objLotameOriginal = {'Profile': {'pid': 'originalpid', 'tpid': 'originaltpid', 'somethingstrange': [{'id': 'aud1'}]}}; - let result = spec.makeLotameObjectFromOverride( - objLotameOverride, - objLotameOriginal - ); - expect(result).to.equal(objLotameOriginal); - }); - }); - describe('lotameDataIsValid', function() { - it('should allow a valid minimum lotame object', function() { - let obj = {'Profile': {'pid': '', 'tpid': '', 'Audiences': {'Audience': []}}}; - let result = spec.isLotameDataValid(obj); - expect(result).to.be.true; - }); - it('should allow a valid lotame object', function() { - let obj = {'Profile': {'pid': '12345', 'tpid': '45678', 'Audiences': {'Audience': [{'id': '1234', 'abbr': '567'}, {'id': '9999', 'abbr': '1111'}]}}}; - let result = spec.isLotameDataValid(obj); - expect(result).to.be.true; - }); - it('should disallow a lotame object without an Audience.id', function() { - let obj = {'Profile': {'tpid': '', 'pid': '', 'Audiences': {'Audience': [{'abbr': 'marktest'}]}}}; - let result = spec.isLotameDataValid(obj); - expect(result).to.be.false; - }); - }); describe('getPageId', function() { it('should return the same Page ID for multiple calls', function () { let result = spec.getPageId(); @@ -2720,24 +2600,6 @@ describe('ozone Adapter', function () { expect(result).to.equal('outstream'); }); }); - describe('getLotameOverrideParams', function() { - it('should get 3 valid lotame params that exist in GET params', function () { - // mock the getGetParametersAsObject function to simulate GET parameters for lotame overrides: - spec.getGetParametersAsObject = function() { - return {'oz_lotameid': '123abc', 'oz_lotamepid': 'pid123', 'oz_lotametpid': 'tpid123'}; - }; - let result = spec.getLotameOverrideParams(); - expect(Object.keys(result).length).to.equal(3); - }); - it('should get only 1 valid lotame param that exists in GET params', function () { - // mock the getGetParametersAsObject function to simulate GET parameters for lotame overrides: - spec.getGetParametersAsObject = function() { - return {'oz_lotameid': '123abc', 'xoz_lotamepid': 'pid123', 'xoz_lotametpid': 'tpid123'}; - }; - let result = spec.getLotameOverrideParams(); - expect(Object.keys(result).length).to.equal(1); - }); - }); describe('unpackVideoConfigIntoIABformat', function() { it('should correctly unpack a usual video config', function () { let mediaTypes = { diff --git a/test/spec/modules/parrableIdSystem_spec.js b/test/spec/modules/parrableIdSystem_spec.js index 5e62af9b2fa..3f517a66c68 100644 --- a/test/spec/modules/parrableIdSystem_spec.js +++ b/test/spec/modules/parrableIdSystem_spec.js @@ -18,7 +18,7 @@ const P_XHR_EID = '01.1588030911.test-new-eid' const P_CONFIG_MOCK = { name: 'parrableId', params: { - partner: 'parrable_test_partner_123,parrable_test_partner_456' + partners: 'parrable_test_partner_123,parrable_test_partner_456' } }; @@ -74,6 +74,15 @@ function removeParrableCookie() { storage.setCookie(P_COOKIE_NAME, '', EXPIRED_COOKIE_DATE); } +function decodeBase64UrlSafe(encBase64) { + const DEC = { + '-': '+', + '_': '/', + '.': '=' + }; + return encBase64.replace(/[-_.]/g, (m) => DEC[m]); +} + describe('Parrable ID System', function() { describe('parrableIdSystem.getId()', function() { describe('response callback function', function() { @@ -98,7 +107,7 @@ describe('Parrable ID System', function() { let request = server.requests[0]; let queryParams = utils.parseQS(request.url.split('?')[1]); - let data = JSON.parse(atob(queryParams.data)); + let data = JSON.parse(atob(decodeBase64UrlSafe(queryParams.data))); expect(getIdResult.callback).to.be.a('function'); expect(request.url).to.contain('h.parrable.com'); @@ -106,8 +115,10 @@ describe('Parrable ID System', function() { expect(queryParams).to.not.have.property('us_privacy'); expect(data).to.deep.equal({ eid: P_COOKIE_EID, - trackers: P_CONFIG_MOCK.params.partner.split(','), - url: getRefererInfo().referer + trackers: P_CONFIG_MOCK.params.partners.split(','), + url: getRefererInfo().referer, + prebidVersion: '$prebid.version$', + isIframe: true }); server.requests[0].respond(200, @@ -136,9 +147,22 @@ describe('Parrable ID System', function() { expect(server.requests[0].url).to.contain('us_privacy=' + uspString); }); + it('xhr base64 safely encodes url data object', function() { + const urlSafeBase64EncodedData = '-_.'; + const btoaStub = sinon.stub(window, 'btoa').returns('+/='); + let getIdResult = parrableIdSubmodule.getId(P_CONFIG_MOCK); + + getIdResult.callback(callbackSpy); + + let request = server.requests[0]; + let queryParams = utils.parseQS(request.url.split('?')[1]); + expect(queryParams.data).to.equal(urlSafeBase64EncodedData); + btoaStub.restore(); + }); + it('should log an error and continue to callback if ajax request errors', function () { let callBackSpy = sinon.spy(); - let submoduleCallback = parrableIdSubmodule.getId({ params: {partner: 'prebid'} }).callback; + let submoduleCallback = parrableIdSubmodule.getId({ params: {partners: 'prebid'} }).callback; submoduleCallback(callBackSpy); let request = server.requests[0]; expect(request.url).to.contain('h.parrable.com'); @@ -186,6 +210,38 @@ describe('Parrable ID System', function() { removeParrableCookie(); }); }); + + describe('GDPR consent', () => { + let callbackSpy = sinon.spy(); + + const config = { + params: { + partner: 'partner' + } + }; + + const gdprConsentTestCases = [ + { consentData: { gdprApplies: true, consentString: 'expectedConsentString' }, expected: { gdpr: 1, gdpr_consent: 'expectedConsentString' } }, + { consentData: { gdprApplies: false, consentString: 'expectedConsentString' }, expected: { gdpr: 0 } }, + { consentData: { gdprApplies: true, consentString: undefined }, expected: { gdpr: 1, gdpr_consent: '' } }, + { consentData: { gdprApplies: 'yes', consentString: 'expectedConsentString' }, expected: { gdpr: 0 } }, + { consentData: undefined, expected: { gdpr: 0 } } + ]; + + gdprConsentTestCases.forEach((testCase, index) => { + it(`should call user sync url with the gdprConsent - case ${index}`, () => { + parrableIdSubmodule.getId(config, testCase.consentData).callback(callbackSpy); + + if (testCase.expected.gdpr === 1) { + expect(server.requests[0].url).to.contain('gdpr=' + testCase.expected.gdpr); + expect(server.requests[0].url).to.contain('gdpr_consent=' + testCase.expected.gdpr_consent); + } else { + expect(server.requests[0].url).to.contain('gdpr=' + testCase.expected.gdpr); + expect(server.requests[0].url).to.not.contain('gdpr_consent'); + } + }) + }); + }); }); describe('parrableIdSystem.decode()', function() { @@ -213,7 +269,7 @@ describe('Parrable ID System', function() { it('permits an impression when no timezoneFilter is configured', function() { expect(parrableIdSubmodule.getId({ params: { - partner: 'prebid-test', + partners: 'prebid-test', } })).to.have.property('callback'); }); @@ -225,7 +281,7 @@ describe('Parrable ID System', function() { writeParrableCookie({ eid: P_COOKIE_EID }); expect(parrableIdSubmodule.getId({ params: { - partner: 'prebid-test', + partners: 'prebid-test', timezoneFilter: { blockedZones: [ blockedZone ] } @@ -241,7 +297,7 @@ describe('Parrable ID System', function() { Intl.DateTimeFormat.returns({ resolvedOptions }); expect(parrableIdSubmodule.getId({ params: { - partner: 'prebid-test', + partners: 'prebid-test', timezoneFilter: { allowedZones: [ allowedZone ] } @@ -249,13 +305,27 @@ describe('Parrable ID System', function() { expect(resolvedOptions.called).to.equal(true); }); + it('permits an impression from a lower cased allowed timezone', function() { + const allowedZone = 'America/New_York'; + const resolvedOptions = sinon.stub().returns({ timeZone: allowedZone }); + Intl.DateTimeFormat.returns({ resolvedOptions }); + + expect(parrableIdSubmodule.getId({ params: { + partner: 'prebid-test', + timezoneFilter: { + allowedZones: [ allowedZone.toLowerCase() ] + } + } })).to.have.property('callback'); + expect(resolvedOptions.called).to.equal(true); + }); + it('permits an impression from a timezone that is not blocked', function() { const blockedZone = 'America/New_York'; const resolvedOptions = sinon.stub().returns({ timeZone: 'Iceland' }); Intl.DateTimeFormat.returns({ resolvedOptions }); expect(parrableIdSubmodule.getId({ params: { - partner: 'prebid-test', + partners: 'prebid-test', timezoneFilter: { blockedZones: [ blockedZone ] } @@ -269,7 +339,7 @@ describe('Parrable ID System', function() { Intl.DateTimeFormat.returns({ resolvedOptions }); expect(parrableIdSubmodule.getId({ params: { - partner: 'prebid-test', + partners: 'prebid-test', timezoneFilter: { blockedZones: [ blockedZone ] } @@ -277,13 +347,27 @@ describe('Parrable ID System', function() { expect(resolvedOptions.called).to.equal(true); }); + it('does not permit an impression from a lower cased blocked timezone', function() { + const blockedZone = 'America/New_York'; + const resolvedOptions = sinon.stub().returns({ timeZone: blockedZone }); + Intl.DateTimeFormat.returns({ resolvedOptions }); + + expect(parrableIdSubmodule.getId({ params: { + partner: 'prebid-test', + timezoneFilter: { + blockedZones: [ blockedZone.toLowerCase() ] + } + } })).to.equal(null); + expect(resolvedOptions.called).to.equal(true); + }); + it('does not permit an impression from a blocked timezone even when also allowed', function() { const timezone = 'America/New_York'; const resolvedOptions = sinon.stub().returns({ timeZone: timezone }); Intl.DateTimeFormat.returns({ resolvedOptions }); expect(parrableIdSubmodule.getId({ params: { - partner: 'prebid-test', + partners: 'prebid-test', timezoneFilter: { allowedZones: [ timezone ], blockedZones: [ timezone ] @@ -313,7 +397,7 @@ describe('Parrable ID System', function() { writeParrableCookie({ eid: P_COOKIE_EID }); expect(parrableIdSubmodule.getId({ params: { - partner: 'prebid-test', + partners: 'prebid-test', timezoneFilter: { blockedOffsets: [ blockedOffset ] } @@ -327,7 +411,7 @@ describe('Parrable ID System', function() { Date.prototype.getTimezoneOffset.returns(allowedOffset * 60); expect(parrableIdSubmodule.getId({ params: { - partner: 'prebid-test', + partners: 'prebid-test', timezoneFilter: { allowedOffsets: [ allowedOffset ] } @@ -341,7 +425,7 @@ describe('Parrable ID System', function() { Date.prototype.getTimezoneOffset.returns(allowedOffset * 60); expect(parrableIdSubmodule.getId({ params: { - partner: 'prebid-test', + partners: 'prebid-test', timezoneFilter: { blockedOffsets: [ blockedOffset ] } @@ -354,7 +438,7 @@ describe('Parrable ID System', function() { Date.prototype.getTimezoneOffset.returns(blockedOffset * 60); expect(parrableIdSubmodule.getId({ params: { - partner: 'prebid-test', + partners: 'prebid-test', timezoneFilter: { blockedOffsets: [ blockedOffset ] } @@ -367,7 +451,7 @@ describe('Parrable ID System', function() { Date.prototype.getTimezoneOffset.returns(offset * 60); expect(parrableIdSubmodule.getId({ params: { - partner: 'prebid-test', + partners: 'prebid-test', timezoneFilter: { allowedOffset: [ offset ], blockedOffsets: [ offset ] @@ -444,4 +528,47 @@ describe('Parrable ID System', function() { }, { adUnits }); }); }); + + describe('partners parsing', () => { + let callbackSpy = sinon.spy(); + + const partnersTestCase = [ + { + name: '"partners" as an array', + config: { params: { partners: ['parrable_test_partner_123', 'parrable_test_partner_456'] } }, + expected: ['parrable_test_partner_123', 'parrable_test_partner_456'] + }, + { + name: '"partners" as a string list', + config: { params: { partners: 'parrable_test_partner_123,parrable_test_partner_456' } }, + expected: ['parrable_test_partner_123', 'parrable_test_partner_456'] + }, + { + name: '"partners" as a string', + config: { params: { partners: 'parrable_test_partner_123' } }, + expected: ['parrable_test_partner_123'] + }, + { + name: '"partner" as a string list', + config: { params: { partner: 'parrable_test_partner_123,parrable_test_partner_456' } }, + expected: ['parrable_test_partner_123', 'parrable_test_partner_456'] + }, + { + name: '"partner" as string', + config: { params: { partner: 'parrable_test_partner_123' } }, + expected: ['parrable_test_partner_123'] + }, + ]; + partnersTestCase.forEach(testCase => { + it(`accepts config property ${testCase.name}`, () => { + parrableIdSubmodule.getId(testCase.config).callback(callbackSpy); + + let request = server.requests[0]; + let queryParams = utils.parseQS(request.url.split('?')[1]); + let data = JSON.parse(atob(decodeBase64UrlSafe(queryParams.data))); + + expect(data.trackers).to.deep.equal(testCase.expected); + }); + }); + }); }); diff --git a/test/spec/modules/permutiveRtdProvider_spec.js b/test/spec/modules/permutiveRtdProvider_spec.js new file mode 100644 index 00000000000..cf1f3861eab --- /dev/null +++ b/test/spec/modules/permutiveRtdProvider_spec.js @@ -0,0 +1,397 @@ +import { permutiveSubmodule, storage, getSegments, initSegments, isAcEnabled, isPermutiveOnPage } from 'modules/permutiveRtdProvider.js' +import { deepAccess } from '../../../src/utils.js' + +describe('permutiveRtdProvider', function () { + before(function () { + const data = getTargetingData() + setLocalStorage(data) + }) + + after(function () { + const data = getTargetingData() + removeLocalStorage(data) + }) + + describe('permutiveSubmodule', function () { + it('should initalise and return true', function () { + expect(permutiveSubmodule.init()).to.equal(true) + }) + }) + + describe('Getting segments', function () { + it('should retrieve segments in the expected structure', function () { + const data = transformedTargeting() + expect(getSegments(250)).to.deep.equal(data) + }) + it('should enforce max segments', function () { + const max = 1 + const segments = getSegments(max) + + for (const key in segments) { + expect(segments[key]).to.have.length(max) + } + }) + }) + + describe('Default segment targeting', function () { + it('sets segment targeting for Xandr', function () { + const data = transformedTargeting() + const adUnits = getAdUnits() + const config = getConfig() + + initSegments({ adUnits }, callback, config) + + function callback () { + adUnits.forEach(adUnit => { + adUnit.bids.forEach(bid => { + const { bidder, params } = bid + + if (bidder === 'appnexus') { + expect(deepAccess(params, 'keywords.permutive')).to.eql(data.appnexus) + expect(deepAccess(params, 'keywords.p_standard')).to.eql(data.ac) + } + }) + }) + } + }) + it('sets segment targeting for Magnite', function () { + const data = transformedTargeting() + const adUnits = getAdUnits() + const config = getConfig() + + initSegments({ adUnits }, callback, config) + + function callback () { + adUnits.forEach(adUnit => { + adUnit.bids.forEach(bid => { + const { bidder, params } = bid + + if (bidder === 'rubicon') { + expect(deepAccess(params, 'visitor.permutive')).to.eql(data.rubicon) + expect(deepAccess(params, 'visitor.p_standard')).to.eql(data.ac) + } + }) + }) + } + }) + it('sets segment targeting for Ozone', function () { + const data = transformedTargeting() + const adUnits = getAdUnits() + const config = getConfig() + + initSegments({ adUnits }, callback, config) + + function callback () { + adUnits.forEach(adUnit => { + adUnit.bids.forEach(bid => { + const { bidder, params } = bid + + if (bidder === 'ozone') { + expect(deepAccess(params, 'customData.0.targeting.p_standard')).to.eql(data.ac) + } + }) + }) + } + }) + it('sets segment targeting for TrustX', function () { + const data = transformedTargeting() + const adUnits = getAdUnits() + const config = getConfig() + + initSegments({ adUnits }, callback, config) + + function callback () { + adUnits.forEach(adUnit => { + adUnit.bids.forEach(bid => { + const { bidder, params } = bid + + if (bidder === 'trustx') { + expect(deepAccess(params, 'keywords.p_standard')).to.eql(data.ac) + } + }) + }) + } + }) + }) + + describe('Custom segment targeting', function () { + it('sets custom segment targeting for Magnite', function () { + const data = transformedTargeting() + const adUnits = getAdUnits() + const config = getConfig() + + config.params.overwrites = { + rubicon: function (bid, data, acEnabled, utils, defaultFn) { + if (defaultFn) { + bid = defaultFn(bid, data, acEnabled) + } + if (data.gam && data.gam.length) { + utils.deepSetValue(bid, 'params.visitor.permutive', data.gam) + } + } + } + + initSegments({ adUnits }, callback, config) + + function callback () { + adUnits.forEach(adUnit => { + adUnit.bids.forEach(bid => { + const { bidder, params } = bid + + if (bidder === 'rubicon') { + expect(deepAccess(params, 'visitor.permutive')).to.eql(data.gam) + expect(deepAccess(params, 'visitor.p_standard')).to.eql(data.ac) + } + }) + }) + } + }) + }) + + describe('Existing key-value targeting', function () { + it('doesn\'t overwrite existing key-values for Xandr', function () { + const adUnits = getAdUnits() + const config = getConfig() + + initSegments({ adUnits }, callback, config) + + function callback () { + adUnits.forEach(adUnit => { + adUnit.bids.forEach(bid => { + const { bidder, params } = bid + + if (bidder === 'appnexus') { + expect(deepAccess(params, 'keywords.test_kv')).to.eql(['true']) + } + }) + }) + } + }) + it('doesn\'t overwrite existing key-values for Magnite', function () { + const adUnits = getAdUnits() + const config = getConfig() + + initSegments({ adUnits }, callback, config) + + function callback () { + adUnits.forEach(adUnit => { + adUnit.bids.forEach(bid => { + const { bidder, params } = bid + + if (bidder === 'rubicon') { + expect(deepAccess(params, 'visitor.test_kv')).to.eql(['true']) + } + }) + }) + } + }) + it('doesn\'t overwrite existing key-values for Ozone', function () { + const adUnits = getAdUnits() + const config = getConfig() + + initSegments({ adUnits }, callback, config) + + function callback () { + adUnits.forEach(adUnit => { + adUnit.bids.forEach(bid => { + const { bidder, params } = bid + + if (bidder === 'ozone') { + expect(deepAccess(params, 'customData.0.targeting.test_kv')).to.eql(['true']) + } + }) + }) + } + }) + it('doesn\'t overwrite existing key-values for TrustX', function () { + const adUnits = getAdUnits() + const config = getConfig() + + initSegments({ adUnits }, callback, config) + + function callback () { + adUnits.forEach(adUnit => { + adUnit.bids.forEach(bid => { + const { bidder, params } = bid + + if (bidder === 'trustx') { + expect(deepAccess(params, 'keywords.test_kv')).to.eql(['true']) + } + }) + }) + } + }) + }) + + describe('Permutive on page', function () { + it('checks if Permutive is on page', function () { + expect(isPermutiveOnPage()).to.equal(false) + }) + }) + + describe('AC is enabled', function () { + it('checks if AC is enabled for Xandr', function () { + expect(isAcEnabled({ params: { acBidders: ['appnexus'] } }, 'appnexus')).to.equal(true) + expect(isAcEnabled({ params: { acBidders: ['kjdbfkvb'] } }, 'appnexus')).to.equal(false) + }) + it('checks if AC is enabled for Magnite', function () { + expect(isAcEnabled({ params: { acBidders: ['rubicon'] } }, 'rubicon')).to.equal(true) + expect(isAcEnabled({ params: { acBidders: ['kjdbfkb'] } }, 'rubicon')).to.equal(false) + }) + it('checks if AC is enabled for Ozone', function () { + expect(isAcEnabled({ params: { acBidders: ['ozone'] } }, 'ozone')).to.equal(true) + expect(isAcEnabled({ params: { acBidders: ['kjdvb'] } }, 'ozone')).to.equal(false) + }) + }) +}) + +function setLocalStorage (data) { + for (const key in data) { + storage.setDataInLocalStorage(key, JSON.stringify(data[key])) + } +} + +function removeLocalStorage (data) { + for (const key in data) { + storage.removeDataFromLocalStorage(key) + } +} + +function getConfig () { + return { + name: 'permutive', + waitForIt: true, + params: { + acBidders: ['appnexus', 'rubicon', 'ozone', 'trustx'], + maxSegs: 500 + } + } +} + +function transformedTargeting () { + const data = getTargetingData() + + return { + ac: [...data._pcrprs, ...data._ppam, ...data._psegs.filter(seg => seg >= 1000000)], + appnexus: data._papns, + rubicon: data._prubicons, + gam: data._pdfps + } +} + +function getTargetingData () { + return { + _pdfps: ['gam1', 'gam2'], + _prubicons: ['rubicon1', 'rubicon2'], + _papns: ['appnexus1', 'appnexus2'], + _psegs: ['1234', '1000001', '1000002'], + _ppam: ['ppam1', 'ppam2'], + _pcrprs: ['pcrprs1', 'pcrprs2'] + } +} + +function getAdUnits () { + const div_1_sizes = [ + [300, 250], + [300, 600] + ] + const div_2_sizes = [ + [728, 90], + [970, 250] + ] + return [ + { + code: '/19968336/header-bid-tag-0', + mediaTypes: { + banner: { + sizes: div_1_sizes + } + }, + bids: [ + { + bidder: 'appnexus', + params: { + placementId: 13144370, + keywords: { + test_kv: ['true'] + } + } + }, + { + bidder: 'rubicon', + params: { + accountId: '9840', + siteId: '123564', + zoneId: '583584', + inventory: { + area: ['home'] + }, + visitor: { + test_kv: ['true'] + } + } + }, + { + bidder: 'ozone', + params: { + publisherId: 'OZONEGMG0001', + siteId: '4204204209', + placementId: '0420420500', + customData: [ + { + settings: {}, + targeting: { + test_kv: ['true'] + } + } + ], + ozoneData: {} + } + }, + { + bidder: 'trustx', + params: { + uid: 45, + keywords: { + test_kv: ['true'] + } + } + } + ] + }, + { + code: '/19968336/header-bid-tag-1', + mediaTypes: { + banner: { + sizes: div_2_sizes + } + }, + bids: [ + { + bidder: 'appnexus', + params: { + placementId: 13144370, + keywords: { + test_kv: ['true'] + } + } + }, + { + bidder: 'ozone', + params: { + publisherId: 'OZONEGMG0001', + siteId: '4204204209', + placementId: '0420420500', + customData: [ + { + targeting: { + test_kv: ['true'] + } + } + ] + } + } + ] + } + ] +} diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index d069bb74944..937d67677d9 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -26,6 +26,7 @@ const REQUEST = { 'secure': 0, 'url': '', 'prebid_version': '0.30.0-pre', + 's2sConfig': CONFIG, 'ad_units': [ { 'code': 'div-gpt-ad-1460505748561-0', @@ -80,6 +81,7 @@ const VIDEO_REQUEST = { 'secure': 0, 'url': '', 'prebid_version': '1.4.0-pre', + 's2sConfig': CONFIG, 'ad_units': [ { 'code': 'div-gpt-ad-1460505748561-0', @@ -110,6 +112,7 @@ const OUTSTREAM_VIDEO_REQUEST = { 'secure': 0, 'url': '', 'prebid_version': '1.4.0-pre', + 's2sConfig': CONFIG, 'ad_units': [ { 'code': 'div-gpt-ad-1460505748561-0', @@ -162,7 +165,7 @@ const OUTSTREAM_VIDEO_REQUEST = { } } } - ] + ], }; let BID_REQUESTS; @@ -267,6 +270,7 @@ const RESPONSE_OPENRTB = { 'win': 'http://wurl.org?id=333' } }, + 'dchain': { 'ver': '1.0', 'complete': 0, 'nodes': [ { 'asi': 'magnite.com', 'bsid': '123456789', } ] }, 'bidder': { 'appnexus': { 'brand_id': 1, @@ -497,11 +501,9 @@ describe('S2S Adapter', function () { resetSyncedStatus(); }); - it('should not add outstream without renderer', function () { - let ortb2Config = utils.deepClone(CONFIG); - ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; + it('should not add outstrean without renderer', function () { + config.setConfig({ s2sConfig: CONFIG }); - config.setConfig({ s2sConfig: ortb2Config }); adapter.callBids(OUTSTREAM_VIDEO_REQUEST, BID_REQUESTS, addBidResponse, done, ajax); const requestBid = JSON.parse(server.requests[0].requestBody); @@ -535,10 +537,7 @@ describe('S2S Adapter', function () { }); it('adds gdpr consent information to ortb2 request depending on presence of module', function () { - let ortb2Config = utils.deepClone(CONFIG); - ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; - - let consentConfig = { consentManagement: { cmpApi: 'iab' }, s2sConfig: ortb2Config }; + let consentConfig = { consentManagement: { cmpApi: 'iab' }, s2sConfig: CONFIG }; config.setConfig(consentConfig); let gdprBidRequest = utils.deepClone(BID_REQUESTS); @@ -564,10 +563,7 @@ describe('S2S Adapter', function () { }); it('adds additional consent information to ortb2 request depending on presence of module', function () { - let ortb2Config = utils.deepClone(CONFIG); - ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; - - let consentConfig = { consentManagement: { cmpApi: 'iab' }, s2sConfig: ortb2Config }; + let consentConfig = { consentManagement: { cmpApi: 'iab' }, s2sConfig: CONFIG }; config.setConfig(consentConfig); let gdprBidRequest = utils.deepClone(BID_REQUESTS); @@ -608,7 +604,10 @@ describe('S2S Adapter', function () { gdprApplies: true }; - adapter.callBids(REQUEST, gdprBidRequest, addBidResponse, done, ajax); + const s2sBidRequest = utils.deepClone(REQUEST); + s2sBidRequest.s2sConfig = cookieSyncConfig; + + adapter.callBids(s2sBidRequest, gdprBidRequest, addBidResponse, done, ajax); let requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.gdpr).is.equal(1); @@ -624,20 +623,23 @@ describe('S2S Adapter', function () { let consentConfig = { consentManagement: { cmpApi: 'iab' }, s2sConfig: cookieSyncConfig }; config.setConfig(consentConfig); + const s2sBidRequest = utils.deepClone(REQUEST); + s2sBidRequest.s2sConfig = cookieSyncConfig; + let gdprBidRequest = utils.deepClone(BID_REQUESTS); gdprBidRequest[0].gdprConsent = { consentString: 'xyz789abcc', gdprApplies: false }; - adapter.callBids(REQUEST, gdprBidRequest, addBidResponse, done, ajax); + adapter.callBids(s2sBidRequest, gdprBidRequest, addBidResponse, done, ajax); let requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.gdpr).is.equal(0); expect(requestBid.gdpr_consent).is.undefined; }); - it('checks gdpr info gets added to cookie_sync request: consent data unknown', function () { + it('checks gdpr info gets added to cookie_sync request: applies is false', function () { let cookieSyncConfig = utils.deepClone(CONFIG); cookieSyncConfig.syncEndpoint = 'https://prebid.adnxs.com/pbs/v1/cookie_sync'; @@ -647,13 +649,16 @@ describe('S2S Adapter', function () { let gdprBidRequest = utils.deepClone(BID_REQUESTS); gdprBidRequest[0].gdprConsent = { consentString: undefined, - gdprApplies: undefined + gdprApplies: false }; - adapter.callBids(REQUEST, gdprBidRequest, addBidResponse, done, ajax); + const s2sBidRequest = utils.deepClone(REQUEST); + s2sBidRequest.s2sConfig = cookieSyncConfig; + + adapter.callBids(s2sBidRequest, gdprBidRequest, addBidResponse, done, ajax); let requestBid = JSON.parse(server.requests[0].requestBody); - expect(requestBid.gdpr).is.undefined; + expect(requestBid.gdpr).is.equal(0); expect(requestBid.gdpr_consent).is.undefined; }); }); @@ -664,9 +669,7 @@ describe('S2S Adapter', function () { }); it('is added to ortb2 request when in bidRequest', function () { - let ortb2Config = utils.deepClone(CONFIG); - ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; - config.setConfig({ s2sConfig: ortb2Config }); + config.setConfig({ s2sConfig: CONFIG }); let uspBidRequest = utils.deepClone(BID_REQUESTS); uspBidRequest[0].uspConsent = '1NYN'; @@ -693,7 +696,10 @@ describe('S2S Adapter', function () { let uspBidRequest = utils.deepClone(BID_REQUESTS); uspBidRequest[0].uspConsent = '1YNN'; - adapter.callBids(REQUEST, uspBidRequest, addBidResponse, done, ajax); + const s2sBidRequest = utils.deepClone(REQUEST); + s2sBidRequest.s2sConfig = cookieSyncConfig; + + adapter.callBids(s2sBidRequest, uspBidRequest, addBidResponse, done, ajax); let requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.us_privacy).is.equal('1YNN'); @@ -708,9 +714,7 @@ describe('S2S Adapter', function () { }); it('is added to ortb2 request when in bidRequest', function () { - let ortb2Config = utils.deepClone(CONFIG); - ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; - config.setConfig({ s2sConfig: ortb2Config }); + config.setConfig({ s2sConfig: CONFIG }); let consentBidRequest = utils.deepClone(BID_REQUESTS); consentBidRequest[0].uspConsent = '1NYN'; @@ -748,7 +752,10 @@ describe('S2S Adapter', function () { gdprApplies: true }; - adapter.callBids(REQUEST, consentBidRequest, addBidResponse, done, ajax); + const s2sBidRequest = utils.deepClone(REQUEST); + s2sBidRequest.s2sConfig = cookieSyncConfig + + adapter.callBids(s2sBidRequest, consentBidRequest, addBidResponse, done, ajax); let requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.us_privacy).is.equal('1YNN'); @@ -806,11 +813,8 @@ describe('S2S Adapter', function () { }); it('adds debugging value from storedAuctionResponse to OpenRTB', function () { - const s2sConfig = Object.assign({}, CONFIG, { - endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' - }); const _config = { - s2sConfig: s2sConfig, + s2sConfig: CONFIG, device: { ifa: '6D92078A-8246-4BA4-AE5B-76104861E7DC' }, app: { bundle: 'com.test.app' } }; @@ -826,13 +830,104 @@ describe('S2S Adapter', function () { expect(requestBid.imp[0].ext.prebid.storedauctionresponse.id).to.equal('11111'); }); - it('adds device.w and device.h even if the config lacks a device object', function () { - const s2sConfig = Object.assign({}, CONFIG, { - endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' + describe('price floors module', function () { + function runTest(expectedFloor, expectedCur) { + adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + const requestBid = JSON.parse(server.requests[requestCount].requestBody); + expect(requestBid.imp[0].bidfloor).to.equal(expectedFloor); + expect(requestBid.imp[0].bidfloorcur).to.equal(expectedCur); + requestCount += 1; + } + + let getFloorResponse, requestCount; + beforeEach(function () { + getFloorResponse = {}; + requestCount = 0; + }); + + it('should NOT pass bidfloor and bidfloorcur when getFloor not present or returns invalid response', function () { + const _config = { + s2sConfig: CONFIG, + }; + + config.setConfig(_config); + + // if no get floor + runTest(undefined, undefined); + + // if getFloor returns empty object + BID_REQUESTS[0].bids[0].getFloor = () => getFloorResponse; + sinon.spy(BID_REQUESTS[0].bids[0], 'getFloor'); + + runTest(undefined, undefined); + // make sure getFloor was called + expect( + BID_REQUESTS[0].bids[0].getFloor.calledWith({ + currency: 'USD', + }) + ).to.be.true; + + // if getFloor does not return number + getFloorResponse = {currency: 'EUR', floor: 'not a number'}; + runTest(undefined, undefined); + + // if getFloor does not return currency + getFloorResponse = {floor: 1.1}; + runTest(undefined, undefined); }); + it('should correctly pass bidfloor and bidfloorcur', function () { + const _config = { + s2sConfig: CONFIG, + }; + + config.setConfig(_config); + + BID_REQUESTS[0].bids[0].getFloor = () => getFloorResponse; + sinon.spy(BID_REQUESTS[0].bids[0], 'getFloor'); + + // returns USD and string floor + getFloorResponse = {currency: 'USD', floor: '1.23'}; + runTest(1.23, 'USD'); + // make sure getFloor was called + expect( + BID_REQUESTS[0].bids[0].getFloor.calledWith({ + currency: 'USD', + }) + ).to.be.true; + + // returns non USD and number floor + getFloorResponse = {currency: 'EUR', floor: 0.85}; + runTest(0.85, 'EUR'); + }); + + it('should correctly pass adServerCurrency when set to getFloor not default', function () { + config.setConfig({ + s2sConfig: CONFIG, + currency: { adServerCurrency: 'JPY' }, + }); + + // we have to start requestCount at 1 because a conversion rates fetch occurs when adServerCur is not USD! + requestCount = 1; + + BID_REQUESTS[0].bids[0].getFloor = () => getFloorResponse; + sinon.spy(BID_REQUESTS[0].bids[0], 'getFloor'); + + // returns USD and string floor + getFloorResponse = {currency: 'JPY', floor: 97.2}; + runTest(97.2, 'JPY'); + // make sure getFloor was called with JPY + expect( + BID_REQUESTS[0].bids[0].getFloor.calledWith({ + currency: 'JPY', + }) + ).to.be.true; + }); + }); + + it('adds device.w and device.h even if the config lacks a device object', function () { const _config = { - s2sConfig: s2sConfig, + s2sConfig: CONFIG, app: { bundle: 'com.test.app' }, }; @@ -850,12 +945,8 @@ describe('S2S Adapter', function () { }); it('adds native request for OpenRTB', function () { - const s2sConfig = Object.assign({}, CONFIG, { - endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' - }); - const _config = { - s2sConfig: s2sConfig + s2sConfig: CONFIG }; config.setConfig(_config); @@ -909,12 +1000,8 @@ describe('S2S Adapter', function () { }); it('adds site if app is not present', function () { - const s2sConfig = Object.assign({}, CONFIG, { - endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' - }); - const _config = { - s2sConfig: s2sConfig, + s2sConfig: CONFIG, site: { publisher: { id: '1234', @@ -949,14 +1036,71 @@ describe('S2S Adapter', function () { }); it('adds appnexus aliases to request', function () { + config.setConfig({ s2sConfig: CONFIG }); + + const aliasBidder = { + bidder: 'brealtime', + params: { placementId: '123456' } + }; + + const request = utils.deepClone(REQUEST); + request.ad_units[0].bids = [aliasBidder]; + + adapter.callBids(request, BID_REQUESTS, addBidResponse, done, ajax); + + const requestBid = JSON.parse(server.requests[0].requestBody); + expect(requestBid.ext).to.haveOwnProperty('prebid'); + expect(requestBid.ext.prebid).to.deep.include({ + aliases: { + brealtime: 'appnexus' + }, + auctiontimestamp: 1510852447530, + targeting: { + includebidderkeys: false, + includewinners: true + } + }); + }); + + it('adds dynamic aliases to request', function () { + config.setConfig({ s2sConfig: CONFIG }); + + const alias = 'foobar'; + const aliasBidder = { + bidder: alias, + params: { placementId: '123456' } + }; + + const request = utils.deepClone(REQUEST); + request.ad_units[0].bids = [aliasBidder]; + + // TODO: stub this + $$PREBID_GLOBAL$$.aliasBidder('appnexus', alias); + adapter.callBids(request, BID_REQUESTS, addBidResponse, done, ajax); + + const requestBid = JSON.parse(server.requests[0].requestBody); + expect(requestBid.ext).to.haveOwnProperty('prebid'); + expect(requestBid.ext.prebid).to.deep.include({ + aliases: { + [alias]: 'appnexus' + }, + auctiontimestamp: 1510852447530, + targeting: { + includebidderkeys: false, + includewinners: true + } + }); + }); + + it('skips pbs alias when skipPbsAliasing is enabled in adapter', function() { const s2sConfig = Object.assign({}, CONFIG, { endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' }); config.setConfig({ s2sConfig: s2sConfig }); const aliasBidder = { - bidder: 'brealtime', - params: { placementId: '123456' } + bidder: 'mediafuse', + params: { aid: 123 } }; const request = utils.deepClone(REQUEST); @@ -968,9 +1112,6 @@ describe('S2S Adapter', function () { expect(requestBid.ext).to.deep.equal({ prebid: { - aliases: { - brealtime: 'appnexus' - }, auctiontimestamp: 1510852447530, targeting: { includebidderkeys: false, @@ -980,32 +1121,29 @@ describe('S2S Adapter', function () { }); }); - it('adds dynamic aliases to request', function () { + it('skips dynamic aliases to request when skipPbsAliasing enabled', function () { const s2sConfig = Object.assign({}, CONFIG, { endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' }); config.setConfig({ s2sConfig: s2sConfig }); - const alias = 'foobar'; + const alias = 'foobar_1'; const aliasBidder = { bidder: alias, - params: { placementId: '123456' } + params: { aid: 123456 } }; const request = utils.deepClone(REQUEST); request.ad_units[0].bids = [aliasBidder]; // TODO: stub this - $$PREBID_GLOBAL$$.aliasBidder('appnexus', alias); + $$PREBID_GLOBAL$$.aliasBidder('appnexus', alias, { skipPbsAliasing: true }); adapter.callBids(request, BID_REQUESTS, addBidResponse, done, ajax); const requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.ext).to.deep.equal({ prebid: { - aliases: { - [alias]: 'appnexus' - }, auctiontimestamp: 1510852447530, targeting: { includebidderkeys: false, @@ -1049,10 +1187,13 @@ describe('S2S Adapter', function () { cookieSyncConfig.syncEndpoint = 'https://prebid.adnxs.com/pbs/v1/cookie_sync'; cookieSyncConfig.userSyncLimit = 1; + const s2sBidRequest = utils.deepClone(REQUEST); + s2sBidRequest.s2sConfig = cookieSyncConfig; + config.setConfig({ s2sConfig: cookieSyncConfig }); let bidRequest = utils.deepClone(BID_REQUESTS); - adapter.callBids(REQUEST, bidRequest, addBidResponse, done, ajax); + adapter.callBids(s2sBidRequest, bidRequest, addBidResponse, done, ajax); let requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.bidders).to.contain('appnexus').and.to.have.lengthOf(1); @@ -1065,8 +1206,11 @@ describe('S2S Adapter', function () { cookieSyncConfig.syncEndpoint = 'https://prebid.adnxs.com/pbs/v1/cookie_sync'; config.setConfig({ s2sConfig: cookieSyncConfig }); + const s2sBidRequest = utils.deepClone(REQUEST); + s2sBidRequest.s2sConfig = cookieSyncConfig; + let bidRequest = utils.deepClone(BID_REQUESTS); - adapter.callBids(REQUEST, bidRequest, addBidResponse, done, ajax); + adapter.callBids(s2sBidRequest, bidRequest, addBidResponse, done, ajax); let requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.bidders).to.contain('appnexus').and.to.have.lengthOf(1); @@ -1077,8 +1221,11 @@ describe('S2S Adapter', function () { config.resetConfig(); config.setConfig({ s2sConfig: cookieSyncConfig }); + const s2sBidRequest2 = utils.deepClone(REQUEST); + s2sBidRequest2.s2sConfig = cookieSyncConfig; + bidRequest = utils.deepClone(BID_REQUESTS); - adapter.callBids(REQUEST, bidRequest, addBidResponse, done, ajax); + adapter.callBids(s2sBidRequest2, bidRequest, addBidResponse, done, ajax); requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.bidders).to.contain('appnexus').and.to.have.lengthOf(1); @@ -1088,7 +1235,6 @@ describe('S2S Adapter', function () { it('adds s2sConfig adapterOptions to request for ORTB', function () { const s2sConfig = Object.assign({}, CONFIG, { - endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction', adapterOptions: { appnexus: { key: 'value' @@ -1101,8 +1247,11 @@ describe('S2S Adapter', function () { app: { bundle: 'com.test.app' }, }; + const s2sBidRequest = utils.deepClone(REQUEST); + s2sBidRequest.s2sConfig = s2sConfig; + config.setConfig(_config); - adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + adapter.callBids(s2sBidRequest, BID_REQUESTS, addBidResponse, done, ajax); const requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.imp[0].ext.appnexus).to.haveOwnProperty('key'); expect(requestBid.imp[0].ext.appnexus.key).to.be.equal('value') @@ -1110,7 +1259,6 @@ describe('S2S Adapter', function () { describe('config site value is added to the oRTB request', function () { const s2sConfig = Object.assign({}, CONFIG, { - endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction', adapterOptions: { appnexus: { key: 'value' @@ -1122,6 +1270,9 @@ describe('S2S Adapter', function () { ip: '75.97.0.47' }; + const s2sBidRequest = utils.deepClone(REQUEST); + s2sBidRequest.s2sConfig = s2sConfig; + it('and overrides publisher and page', function () { config.setConfig({ s2sConfig: s2sConfig, @@ -1132,7 +1283,8 @@ describe('S2S Adapter', function () { }, device: device }); - adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + + adapter.callBids(s2sBidRequest, BID_REQUESTS, addBidResponse, done, ajax); const requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.site).to.exist.and.to.be.a('object'); @@ -1150,7 +1302,8 @@ describe('S2S Adapter', function () { }, device: device }); - adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + + adapter.callBids(s2sBidRequest, BID_REQUESTS, addBidResponse, done, ajax); const requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.site).to.exist.and.to.be.a('object'); @@ -1162,10 +1315,7 @@ describe('S2S Adapter', function () { }); it('when userId is defined on bids, it\'s properties should be copied to user.ext.tpid properties', function () { - let ortb2Config = utils.deepClone(CONFIG); - ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; - - let consentConfig = { s2sConfig: ortb2Config }; + let consentConfig = { s2sConfig: CONFIG }; config.setConfig(consentConfig); let userIdBidRequest = utils.deepClone(BID_REQUESTS); @@ -1207,17 +1357,14 @@ describe('S2S Adapter', function () { expect(requestBid.user.ext.eids.filter(eid => eid.source === 'liveintent.com')[0].ext.segments[1]).is.equal('segB'); expect(requestBid.user.ext.eids.filter(eid => eid.source === 'id5-sync.com')).is.not.empty; expect(requestBid.user.ext.eids.filter(eid => eid.source === 'id5-sync.com')[0].uids[0].id).is.equal('11111'); - expect(requestBid.user.ext.eids.filter(eid => eid.source === 'id5-sync.com')[0].ext.linkType).is.equal('some-link-type'); + expect(requestBid.user.ext.eids.filter(eid => eid.source === 'id5-sync.com')[0].uids[0].ext.linkType).is.equal('some-link-type'); // LiveRamp should exist expect(requestBid.user.ext.eids.filter(eid => eid.source === 'liveramp.com')[0].uids[0].id).is.equal('0000-1111-2222-3333'); }); it('when config \'currency.adServerCurrency\' value is an array: ORTB has property \'cur\' value set to a single item array', function () { - let s2sConfig = utils.deepClone(CONFIG); - s2sConfig.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; config.setConfig({ currency: { adServerCurrency: ['USD', 'GB', 'UK', 'AU'] }, - s2sConfig: s2sConfig }); const bidRequests = utils.deepClone(BID_REQUESTS); @@ -1228,11 +1375,8 @@ describe('S2S Adapter', function () { }); it('when config \'currency.adServerCurrency\' value is a string: ORTB has property \'cur\' value set to a single item array', function () { - let s2sConfig = utils.deepClone(CONFIG); - s2sConfig.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; config.setConfig({ currency: { adServerCurrency: 'NZ' }, - s2sConfig: s2sConfig }); const bidRequests = utils.deepClone(BID_REQUESTS); @@ -1243,9 +1387,7 @@ describe('S2S Adapter', function () { }); it('when config \'currency.adServerCurrency\' is unset: ORTB should not define a \'cur\' property', function () { - let s2sConfig = utils.deepClone(CONFIG); - s2sConfig.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; - config.setConfig({ s2sConfig: s2sConfig }); + config.setConfig({ s2sConfig: CONFIG }); const bidRequests = utils.deepClone(BID_REQUESTS); adapter.callBids(REQUEST, bidRequests, addBidResponse, done, ajax); @@ -1256,7 +1398,6 @@ describe('S2S Adapter', function () { it('always add ext.prebid.targeting.includebidderkeys: false for ORTB', function () { const s2sConfig = Object.assign({}, CONFIG, { - endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction', adapterOptions: { appnexus: { key: 'value' @@ -1270,7 +1411,11 @@ describe('S2S Adapter', function () { }; config.setConfig(_config); - adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + + const s2sBidRequest = utils.deepClone(REQUEST); + s2sBidRequest.s2sConfig = s2sConfig; + + adapter.callBids(s2sBidRequest, BID_REQUESTS, addBidResponse, done, ajax); const requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.ext.prebid.targeting).to.haveOwnProperty('includebidderkeys'); @@ -1279,7 +1424,6 @@ describe('S2S Adapter', function () { it('always add ext.prebid.targeting.includewinners: true for ORTB', function () { const s2sConfig = Object.assign({}, CONFIG, { - endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction', adapterOptions: { appnexus: { key: 'value' @@ -1291,9 +1435,12 @@ describe('S2S Adapter', function () { device: { ifa: '6D92078A-8246-4BA4-AE5B-76104861E7DC' }, app: { bundle: 'com.test.app' }, }; - config.setConfig(_config); - adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + + const s2sBidRequest = utils.deepClone(REQUEST); + s2sBidRequest.s2sConfig = s2sConfig; + + adapter.callBids(s2sBidRequest, BID_REQUESTS, addBidResponse, done, ajax); const requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.ext.prebid.targeting).to.haveOwnProperty('includewinners'); @@ -1302,7 +1449,6 @@ describe('S2S Adapter', function () { it('adds s2sConfig video.ext.prebid to request for ORTB', function () { const s2sConfig = Object.assign({}, CONFIG, { - endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction', extPrebid: { foo: 'bar' } @@ -1313,13 +1459,16 @@ describe('S2S Adapter', function () { app: { bundle: 'com.test.app' }, }; + const s2sBidRequest = utils.deepClone(REQUEST); + s2sBidRequest.s2sConfig = s2sConfig; + config.setConfig(_config); - adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + adapter.callBids(s2sBidRequest, BID_REQUESTS, addBidResponse, done, ajax); const requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid).to.haveOwnProperty('ext'); expect(requestBid.ext).to.haveOwnProperty('prebid'); - expect(requestBid.ext.prebid).to.deep.equal({ + expect(requestBid.ext.prebid).to.deep.include({ auctiontimestamp: 1510852447530, foo: 'bar', targeting: { @@ -1331,7 +1480,6 @@ describe('S2S Adapter', function () { it('overrides request.ext.prebid properties using s2sConfig video.ext.prebid values for ORTB', function () { const s2sConfig = Object.assign({}, CONFIG, { - endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction', extPrebid: { targeting: { includewinners: false, @@ -1345,13 +1493,16 @@ describe('S2S Adapter', function () { app: { bundle: 'com.test.app' }, }; + const s2sBidRequest = utils.deepClone(REQUEST); + s2sBidRequest.s2sConfig = s2sConfig; + config.setConfig(_config); - adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + adapter.callBids(s2sBidRequest, BID_REQUESTS, addBidResponse, done, ajax); const requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid).to.haveOwnProperty('ext'); expect(requestBid.ext).to.haveOwnProperty('prebid'); - expect(requestBid.ext.prebid).to.deep.equal({ + expect(requestBid.ext.prebid).to.deep.include({ auctiontimestamp: 1510852447530, targeting: { includewinners: false, @@ -1362,7 +1513,6 @@ describe('S2S Adapter', function () { it('overrides request.ext.prebid properties using s2sConfig video.ext.prebid values for ORTB', function () { const s2sConfig = Object.assign({}, CONFIG, { - endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction', extPrebid: { cache: { vastxml: 'vastxml-set-though-extPrebid.cache.vastXml' @@ -1379,13 +1529,16 @@ describe('S2S Adapter', function () { app: { bundle: 'com.test.app' }, }; + const s2sBidRequest = utils.deepClone(REQUEST); + s2sBidRequest.s2sConfig = s2sConfig; + config.setConfig(_config); - adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + adapter.callBids(s2sBidRequest, BID_REQUESTS, addBidResponse, done, ajax); const requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid).to.haveOwnProperty('ext'); expect(requestBid.ext).to.haveOwnProperty('prebid'); - expect(requestBid.ext.prebid).to.deep.equal({ + expect(requestBid.ext.prebid).to.deep.include({ auctiontimestamp: 1510852447530, cache: { vastxml: 'vastxml-set-though-extPrebid.cache.vastXml' @@ -1422,6 +1575,30 @@ describe('S2S Adapter', function () { expect(parsedRequestBody.source.ext.schain).to.deep.equal(schainObject); }); + it('passes multibid array in request', function () { + const bidRequests = utils.deepClone(BID_REQUESTS); + const multibid = [{ + bidder: 'bidderA', + maxBids: 2 + }, { + bidder: 'bidderB', + maxBids: 2 + }]; + const expected = [{ + bidder: 'bidderA', + maxbids: 2 + }, { + bidder: 'bidderB', + maxbids: 2 + }]; + + config.setConfig({multibid: multibid}); + + adapter.callBids(REQUEST, bidRequests, addBidResponse, done, ajax); + const parsedRequestBody = JSON.parse(server.requests[0].requestBody); + expect(parsedRequestBody.ext.prebid.multibid).to.deep.equal(expected); + }); + it('passes first party data in request', () => { const s2sBidRequest = utils.deepClone(REQUEST); const bidRequests = utils.deepClone(BID_REQUESTS); @@ -1454,23 +1631,44 @@ describe('S2S Adapter', function () { const expected = allowedBidders.map(bidder => ({ bidders: [ bidder ], - config: { fpd: { site: context, user } } + config: { + ortb2: { + site: { + content: { userrating: 4 }, + ext: { + data: { + pageType: 'article', + category: 'tools' + } + } + }, + user: { + yob: '1984', + geo: { country: 'ca' }, + ext: { + data: { + registered: true, + interests: ['cars'] + } + } + } + } + } })); + const commonContextExpected = utils.mergeDeep({'page': 'http://mytestpage.com', 'publisher': {'id': '1'}}, commonContext); config.setConfig({ fpd: { context: commonContext, user: commonUser } }); config.setBidderConfig({ bidders: allowedBidders, config: { fpd: { context, user } } }); adapter.callBids(s2sBidRequest, bidRequests, addBidResponse, done, ajax); const parsedRequestBody = JSON.parse(server.requests[0].requestBody); expect(parsedRequestBody.ext.prebid.bidderconfig).to.deep.equal(expected); - expect(parsedRequestBody.site.ext.data).to.deep.equal(commonContext); - expect(parsedRequestBody.user.ext.data).to.deep.equal(commonUser); + expect(parsedRequestBody.site).to.deep.equal(commonContextExpected); + expect(parsedRequestBody.user).to.deep.equal(commonUser); }); describe('pbAdSlot config', function () { - it('should not send \"imp.ext.context.data.pbadslot\" if \"fpd.context\" is undefined', function () { - const ortb2Config = utils.deepClone(CONFIG); - ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; - const consentConfig = { s2sConfig: ortb2Config }; + it('should not send \"imp.ext.data.pbadslot\" if \"ortb2Imp.ext\" is undefined', function () { + const consentConfig = { s2sConfig: CONFIG }; config.setConfig(consentConfig); const bidRequest = utils.deepClone(REQUEST); @@ -1479,34 +1677,32 @@ describe('S2S Adapter', function () { expect(parsedRequestBody.imp).to.be.a('array'); expect(parsedRequestBody.imp[0]).to.be.a('object'); - expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.context.data.pbadslot'); + expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.data.pbadslot'); }); - it('should not send \"imp.ext.context.data.pbadslot\" if \"fpd.context.pbAdSlot\" is undefined', function () { - const ortb2Config = utils.deepClone(CONFIG); - ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; - const consentConfig = { s2sConfig: ortb2Config }; + it('should not send \"imp.ext.data.pbadslot\" if \"ortb2Imp.ext.data.pbadslot\" is undefined', function () { + const consentConfig = { s2sConfig: CONFIG }; config.setConfig(consentConfig); const bidRequest = utils.deepClone(REQUEST); - bidRequest.ad_units[0].fpd = {}; + bidRequest.ad_units[0].ortb2Imp = {}; adapter.callBids(bidRequest, BID_REQUESTS, addBidResponse, done, ajax); const parsedRequestBody = JSON.parse(server.requests[0].requestBody); expect(parsedRequestBody.imp).to.be.a('array'); expect(parsedRequestBody.imp[0]).to.be.a('object'); - expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.context.data.pbadslot'); + expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.data.pbadslot'); }); - it('should not send \"imp.ext.context.data.pbadslot\" if \"fpd.context.pbAdSlot\" is empty string', function () { - const ortb2Config = utils.deepClone(CONFIG); - ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; - const consentConfig = { s2sConfig: ortb2Config }; + it('should not send \"imp.ext.data.pbadslot\" if \"ortb2Imp.ext.data.pbadslot\" is empty string', function () { + const consentConfig = { s2sConfig: CONFIG }; config.setConfig(consentConfig); const bidRequest = utils.deepClone(REQUEST); - bidRequest.ad_units[0].fpd = { - context: { - pbAdSlot: '' + bidRequest.ad_units[0].ortb2Imp = { + ext: { + data: { + pbadslot: '' + } } }; @@ -1515,18 +1711,18 @@ describe('S2S Adapter', function () { expect(parsedRequestBody.imp).to.be.a('array'); expect(parsedRequestBody.imp[0]).to.be.a('object'); - expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.context.data.pbadslot'); + expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.data.pbadslot'); }); - it('should send \"imp.ext.context.data.pbadslot\" if \"fpd.context.pbAdSlot\" value is a non-empty string', function () { - const ortb2Config = utils.deepClone(CONFIG); - ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; - const consentConfig = { s2sConfig: ortb2Config }; + it('should send \"imp.ext.data.pbadslot\" if \"ortb2Imp.ext.data.pbadslot\" value is a non-empty string', function () { + const consentConfig = { s2sConfig: CONFIG }; config.setConfig(consentConfig); const bidRequest = utils.deepClone(REQUEST); - bidRequest.ad_units[0].fpd = { - context: { - pbAdSlot: '/a/b/c' + bidRequest.ad_units[0].ortb2Imp = { + ext: { + data: { + pbadslot: '/a/b/c' + } } }; @@ -1535,16 +1731,14 @@ describe('S2S Adapter', function () { expect(parsedRequestBody.imp).to.be.a('array'); expect(parsedRequestBody.imp[0]).to.be.a('object'); - expect(parsedRequestBody.imp[0]).to.have.deep.nested.property('ext.context.data.pbadslot'); - expect(parsedRequestBody.imp[0].ext.context.data.pbadslot).to.equal('/a/b/c'); + expect(parsedRequestBody.imp[0]).to.have.deep.nested.property('ext.data.pbadslot'); + expect(parsedRequestBody.imp[0].ext.data.pbadslot).to.equal('/a/b/c'); }); }); describe('GAM ad unit config', function () { - it('should not send \"imp.ext.context.data.adserver.adslot\" if \"fpd.context\" is undefined', function () { - const ortb2Config = utils.deepClone(CONFIG); - ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; - const consentConfig = { s2sConfig: ortb2Config }; + it('should not send \"imp.ext.data.adserver.adslot\" if \"ortb2Imp.ext\" is undefined', function () { + const consentConfig = { s2sConfig: CONFIG }; config.setConfig(consentConfig); const bidRequest = utils.deepClone(REQUEST); @@ -1553,35 +1747,33 @@ describe('S2S Adapter', function () { expect(parsedRequestBody.imp).to.be.a('array'); expect(parsedRequestBody.imp[0]).to.be.a('object'); - expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.context.data.adslot'); + expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.data.adslot'); }); - it('should not send \"imp.ext.context.data.adserver.adslot\" if \"fpd.context.adserver.adSlot\" is undefined', function () { - const ortb2Config = utils.deepClone(CONFIG); - ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; - const consentConfig = { s2sConfig: ortb2Config }; + it('should not send \"imp.ext.data.adserver.adslot\" if \"ortb2Imp.ext.data.adserver.adslot\" is undefined', function () { + const consentConfig = { s2sConfig: CONFIG }; config.setConfig(consentConfig); const bidRequest = utils.deepClone(REQUEST); - bidRequest.ad_units[0].fpd = {}; + bidRequest.ad_units[0].ortb2Imp = {}; adapter.callBids(bidRequest, BID_REQUESTS, addBidResponse, done, ajax); const parsedRequestBody = JSON.parse(server.requests[0].requestBody); expect(parsedRequestBody.imp).to.be.a('array'); expect(parsedRequestBody.imp[0]).to.be.a('object'); - expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.context.data.adslot'); + expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.data.adslot'); }); - it('should not send \"imp.ext.context.data.adserver.adslot\" if \"fpd.context.adserver.adSlot\" is empty string', function () { - const ortb2Config = utils.deepClone(CONFIG); - ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; - const consentConfig = { s2sConfig: ortb2Config }; + it('should not send \"imp.ext.data.adserver.adslot\" if \"ortb2Imp.ext.data.adserver.adslot\" is empty string', function () { + const consentConfig = { s2sConfig: CONFIG }; config.setConfig(consentConfig); const bidRequest = utils.deepClone(REQUEST); - bidRequest.ad_units[0].fpd = { - context: { - adServer: { - adSlot: '' + bidRequest.ad_units[0].ortb2Imp = { + ext: { + data: { + adserver: { + adslot: '' + } } } }; @@ -1591,20 +1783,20 @@ describe('S2S Adapter', function () { expect(parsedRequestBody.imp).to.be.a('array'); expect(parsedRequestBody.imp[0]).to.be.a('object'); - expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.context.data.adslot'); + expect(parsedRequestBody.imp[0]).to.not.have.deep.nested.property('ext.data.adslot'); }); - it('should send both \"adslot\" and \"name\" from \"imp.ext.context.data.adserver\" if \"fpd.context.adserver.adSlot\" and \"fpd.context.adserver.name\" values are non-empty strings', function () { - const ortb2Config = utils.deepClone(CONFIG); - ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; - const consentConfig = { s2sConfig: ortb2Config }; + it('should send both \"adslot\" and \"name\" from \"imp.ext.data.adserver\" if \"ortb2Imp.ext.data.adserver.adslot\" and \"ortb2Imp.ext.data.adserver.name\" values are non-empty strings', function () { + const consentConfig = { s2sConfig: CONFIG }; config.setConfig(consentConfig); const bidRequest = utils.deepClone(REQUEST); - bidRequest.ad_units[0].fpd = { - context: { - adserver: { - adSlot: '/a/b/c', - name: 'adserverName1' + bidRequest.ad_units[0].ortb2Imp = { + ext: { + data: { + adserver: { + adslot: '/a/b/c', + name: 'adserverName1' + } } } }; @@ -1614,10 +1806,10 @@ describe('S2S Adapter', function () { expect(parsedRequestBody.imp).to.be.a('array'); expect(parsedRequestBody.imp[0]).to.be.a('object'); - expect(parsedRequestBody.imp[0]).to.have.deep.nested.property('ext.context.data.adserver.adslot'); - expect(parsedRequestBody.imp[0]).to.have.deep.nested.property('ext.context.data.adserver.name'); - expect(parsedRequestBody.imp[0].ext.context.data.adserver.adslot).to.equal('/a/b/c'); - expect(parsedRequestBody.imp[0].ext.context.data.adserver.name).to.equal('adserverName1'); + expect(parsedRequestBody.imp[0]).to.have.deep.nested.property('ext.data.adserver.adslot'); + expect(parsedRequestBody.imp[0]).to.have.deep.nested.property('ext.data.adserver.name'); + expect(parsedRequestBody.imp[0].ext.data.adserver.adslot).to.equal('/a/b/c'); + expect(parsedRequestBody.imp[0].ext.data.adserver.name).to.equal('adserverName1'); }); }); }); @@ -1770,10 +1962,7 @@ describe('S2S Adapter', function () { }; sinon.stub(adapterManager, 'getBidAdapter').returns(rubiconAdapter); - const s2sConfig = Object.assign({}, CONFIG, { - endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' - }); - config.setConfig({ s2sConfig }); + config.setConfig({ CONFIG }); adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); server.requests[0].respond(200, {}, JSON.stringify(RESPONSE_OPENRTB)); @@ -1784,10 +1973,7 @@ describe('S2S Adapter', function () { }); it('handles OpenRTB responses and call BIDDER_DONE', function () { - const s2sConfig = Object.assign({}, CONFIG, { - endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' - }); - config.setConfig({ s2sConfig }); + config.setConfig({ CONFIG }); adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); server.requests[0].respond(200, {}, JSON.stringify(RESPONSE_OPENRTB)); @@ -1806,6 +1992,9 @@ describe('S2S Adapter', function () { expect(response).to.have.property('meta'); expect(response.meta).to.have.property('advertiserDomains'); expect(response.meta.advertiserDomains[0]).to.equal('appnexus.com'); + expect(response.meta).to.have.property('dchain'); + expect(response.meta.dchain.ver).to.equal('1.0'); + expect(response.meta.dchain.nodes[0].asi).to.equal('magnite.com'); expect(response).to.not.have.property('vastUrl'); expect(response).to.not.have.property('videoCacheKey'); expect(response).to.have.property('ttl', 60); @@ -1813,12 +2002,13 @@ describe('S2S Adapter', function () { it('respects defaultTtl', function () { const s2sConfig = Object.assign({}, CONFIG, { - endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction', defaultTtl: 30 }); - config.setConfig({ s2sConfig }); - adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + const s2sBidRequest = utils.deepClone(REQUEST); + s2sBidRequest.s2sConfig = s2sConfig; + + adapter.callBids(s2sBidRequest, BID_REQUESTS, addBidResponse, done, ajax); server.requests[0].respond(200, {}, JSON.stringify(RESPONSE_OPENRTB)); sinon.assert.calledOnce(events.emit); @@ -1834,7 +2024,10 @@ describe('S2S Adapter', function () { }); config.setConfig({ s2sConfig }); - adapter.callBids(VIDEO_REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + const s2sVidRequest = utils.deepClone(VIDEO_REQUEST); + s2sVidRequest.s2sConfig = s2sConfig; + + adapter.callBids(s2sVidRequest, BID_REQUESTS, addBidResponse, done, ajax); server.requests[0].respond(200, {}, JSON.stringify(RESPONSE_OPENRTB_VIDEO)); sinon.assert.calledOnce(addBidResponse); @@ -1862,7 +2055,10 @@ describe('S2S Adapter', function () { } }); - adapter.callBids(VIDEO_REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + const s2sVidRequest = utils.deepClone(VIDEO_REQUEST); + s2sVidRequest.s2sConfig = s2sConfig; + + adapter.callBids(s2sVidRequest, BID_REQUESTS, addBidResponse, done, ajax); server.requests[0].respond(200, {}, JSON.stringify(cacheResponse)); sinon.assert.calledOnce(addBidResponse); @@ -1887,7 +2083,11 @@ describe('S2S Adapter', function () { cacheResponse.seatbid.forEach(item => { item.bid[0].ext.prebid.targeting = targetingTestData }); - adapter.callBids(VIDEO_REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + + const s2sVidRequest = utils.deepClone(VIDEO_REQUEST); + s2sVidRequest.s2sConfig = s2sConfig; + + adapter.callBids(s2sVidRequest, BID_REQUESTS, addBidResponse, done, ajax); server.requests[0].respond(200, {}, JSON.stringify(cacheResponse)); sinon.assert.calledOnce(addBidResponse); @@ -1913,7 +2113,11 @@ describe('S2S Adapter', function () { hb_cache_path: '/cache' } }); - adapter.callBids(VIDEO_REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + + const s2sVidRequest = utils.deepClone(VIDEO_REQUEST); + s2sVidRequest.s2sConfig = s2sConfig; + + adapter.callBids(s2sVidRequest, BID_REQUESTS, addBidResponse, done, ajax); server.requests[0].respond(200, {}, JSON.stringify(cacheResponse)); sinon.assert.calledOnce(addBidResponse); @@ -1940,7 +2144,10 @@ describe('S2S Adapter', function () { hb_cache_path: '/cache' } }); - adapter.callBids(VIDEO_REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + const s2sVidRequest = utils.deepClone(VIDEO_REQUEST); + s2sVidRequest.s2sConfig = s2sConfig; + + adapter.callBids(s2sVidRequest, BID_REQUESTS, addBidResponse, done, ajax); server.requests[0].respond(200, {}, JSON.stringify(cacheResponse)); sinon.assert.calledOnce(addBidResponse); @@ -1966,7 +2173,11 @@ describe('S2S Adapter', function () { hb_bidid: '1234567890', } }); - adapter.callBids(VIDEO_REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + + const s2sVidRequest = utils.deepClone(VIDEO_REQUEST); + s2sVidRequest.s2sConfig = s2sConfig; + + adapter.callBids(s2sVidRequest, BID_REQUESTS, addBidResponse, done, ajax); server.requests[0].respond(200, {}, JSON.stringify(cacheResponse)); sinon.assert.calledOnce(addBidResponse); @@ -1986,7 +2197,10 @@ describe('S2S Adapter', function () { config.setConfig({ s2sConfig }); const cacheResponse = utils.deepClone(RESPONSE_OPENRTB_VIDEO); - adapter.callBids(VIDEO_REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + const s2sVidRequest = utils.deepClone(VIDEO_REQUEST); + s2sVidRequest.s2sConfig = s2sConfig; + + adapter.callBids(s2sVidRequest, BID_REQUESTS, addBidResponse, done, ajax); server.requests[0].respond(200, {}, JSON.stringify(cacheResponse)); sinon.assert.calledOnce(addBidResponse); @@ -2006,7 +2220,10 @@ describe('S2S Adapter', function () { }); config.setConfig({ s2sConfig }); - adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + const s2sBidRequest = utils.deepClone(REQUEST); + s2sBidRequest.s2sConfig = s2sConfig; + + adapter.callBids(s2sBidRequest, BID_REQUESTS, addBidResponse, done, ajax); server.requests[0].respond(200, {}, JSON.stringify(RESPONSE_OPENRTB_NATIVE)); sinon.assert.calledOnce(addBidResponse); @@ -2307,10 +2524,11 @@ describe('S2S Adapter', function () { s2sConfig.syncEndpoint = 'https://prebid.adnxs.com/pbs/v1/cookie_sync'; s2sConfig.bidders = ['appnexus', 'rubicon-alias']; - const request = utils.deepClone(REQUEST); + const s2sBidRequest = utils.deepClone(REQUEST); + s2sBidRequest.s2sConfig = s2sConfig; // Add another bidder, `rubicon-alias` - request.ad_units[0].bids.push({ + s2sBidRequest.ad_units[0].bids.push({ bidder: 'rubicon-alias', params: { accoundId: 14062, @@ -2322,8 +2540,6 @@ describe('S2S Adapter', function () { // create an alias for the Rubicon Bid Adapter adapterManager.aliasBidAdapter('rubicon', 'rubicon-alias'); - config.setConfig({ s2sConfig }); - const bidRequest = utils.deepClone(BID_REQUESTS); bidRequest.push({ 'bidderCode': 'rubicon-alias', @@ -2367,10 +2583,41 @@ describe('S2S Adapter', function () { 'src': 's2s' }); - adapter.callBids(request, bidRequest, addBidResponse, done, ajax); + adapter.callBids(s2sBidRequest, bidRequest, addBidResponse, done, ajax); const requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.bidders).to.deep.equal(['appnexus', 'rubicon']); }); + + it('should add cooperative sync flag to cookie_sync request if property is present', function () { + let s2sConfig = utils.deepClone(CONFIG); + s2sConfig.coopSync = false; + s2sConfig.syncEndpoint = 'https://prebid.adnxs.com/pbs/v1/cookie_sync'; + + const s2sBidRequest = utils.deepClone(REQUEST); + s2sBidRequest.s2sConfig = s2sConfig; + + let bidRequest = utils.deepClone(BID_REQUESTS); + + adapter.callBids(s2sBidRequest, bidRequest, addBidResponse, done, ajax); + let requestBid = JSON.parse(server.requests[0].requestBody); + + expect(requestBid.coopSync).to.equal(false); + }); + + it('should not add cooperative sync flag to cookie_sync request if property is not present', function () { + let s2sConfig = utils.deepClone(CONFIG); + s2sConfig.syncEndpoint = 'https://prebid.adnxs.com/pbs/v1/cookie_sync'; + + const s2sBidRequest = utils.deepClone(REQUEST); + s2sBidRequest.s2sConfig = s2sConfig; + + let bidRequest = utils.deepClone(BID_REQUESTS); + + adapter.callBids(s2sBidRequest, bidRequest, addBidResponse, done, ajax); + let requestBid = JSON.parse(server.requests[0].requestBody); + + expect(requestBid.coopSync).to.be.undefined; + }); }); }); diff --git a/test/spec/modules/priceFloors_spec.js b/test/spec/modules/priceFloors_spec.js index 1b3ce021068..548d2789d3e 100644 --- a/test/spec/modules/priceFloors_spec.js +++ b/test/spec/modules/priceFloors_spec.js @@ -23,6 +23,7 @@ describe('the price floors module', function () { let clock; const basicFloorData = { modelVersion: 'basic model', + modelWeight: 10, modelTimestamp: 1606772895, currency: 'USD', schema: { @@ -38,6 +39,7 @@ describe('the price floors module', function () { const basicFloorDataHigh = { floorMin: 7.0, modelVersion: 'basic model', + modelWeight: 10, currency: 'USD', schema: { delimiter: '|', @@ -52,6 +54,7 @@ describe('the price floors module', function () { const basicFloorDataLow = { floorMin: 2.3, modelVersion: 'basic model', + modelWeight: 10, currency: 'USD', schema: { delimiter: '|', @@ -185,6 +188,7 @@ describe('the price floors module', function () { let resultingData = getFloorsDataForAuction(inputFloorData, 'test_div_1'); expect(resultingData).to.deep.equal({ modelVersion: 'basic model', + modelWeight: 10, modelTimestamp: 1606772895, currency: 'USD', schema: { @@ -203,6 +207,7 @@ describe('the price floors module', function () { resultingData = getFloorsDataForAuction(inputFloorData, 'this_is_a_div'); expect(resultingData).to.deep.equal({ modelVersion: 'basic model', + modelWeight: 10, modelTimestamp: 1606772895, currency: 'USD', schema: { @@ -432,6 +437,7 @@ describe('the price floors module', function () { skipped: true, floorMin: undefined, modelVersion: undefined, + modelWeight: undefined, modelTimestamp: undefined, location: 'noData', skipRate: 0, @@ -467,6 +473,7 @@ describe('the price floors module', function () { skipped: false, floorMin: undefined, modelVersion: 'adUnit Model Version', + modelWeight: 10, modelTimestamp: 1606772895, location: 'adUnit', skipRate: 0, @@ -501,6 +508,7 @@ describe('the price floors module', function () { validateBidRequests(true, { skipped: false, modelVersion: 'adUnit Model Version', + modelWeight: 10, modelTimestamp: 1606772895, location: 'adUnit', skipRate: 0, @@ -516,6 +524,7 @@ describe('the price floors module', function () { skipped: false, floorMin: undefined, modelVersion: 'basic model', + modelWeight: 10, modelTimestamp: 1606772895, location: 'setConfig', skipRate: 0, @@ -538,6 +547,7 @@ describe('the price floors module', function () { skipped: false, floorMin: undefined, modelVersion: 'basic model', + modelWeight: 10, modelTimestamp: 1606772895, location: 'setConfig', skipRate: 0, @@ -553,6 +563,7 @@ describe('the price floors module', function () { skipped: false, floorMin: undefined, modelVersion: 'basic model', + modelWeight: 10, modelTimestamp: 1606772895, location: 'setConfig', skipRate: 0, @@ -568,6 +579,7 @@ describe('the price floors module', function () { skipped: false, floorMin: undefined, modelVersion: 'basic model', + modelWeight: 10, modelTimestamp: 1606772895, location: 'setConfig', skipRate: 0, @@ -592,6 +604,7 @@ describe('the price floors module', function () { skipped: false, floorMin: undefined, modelVersion: 'basic model', + modelWeight: 10, modelTimestamp: 1606772895, location: 'setConfig', skipRate: 50, @@ -607,6 +620,7 @@ describe('the price floors module', function () { skipped: false, floorMin: undefined, modelVersion: 'basic model', + modelWeight: 10, modelTimestamp: 1606772895, location: 'setConfig', skipRate: 10, @@ -622,6 +636,7 @@ describe('the price floors module', function () { skipped: false, floorMin: undefined, modelVersion: 'basic model', + modelWeight: 10, modelTimestamp: 1606772895, location: 'setConfig', skipRate: 0, @@ -687,6 +702,7 @@ describe('the price floors module', function () { skipped: false, floorMin: undefined, modelVersion: 'model-1', + modelWeight: 10, modelTimestamp: undefined, location: 'setConfig', skipRate: 0, @@ -701,6 +717,7 @@ describe('the price floors module', function () { skipped: false, floorMin: undefined, modelVersion: 'model-2', + modelWeight: 40, modelTimestamp: undefined, location: 'setConfig', skipRate: 0, @@ -715,6 +732,7 @@ describe('the price floors module', function () { skipped: false, floorMin: undefined, modelVersion: 'model-3', + modelWeight: 50, modelTimestamp: undefined, location: 'setConfig', skipRate: 0, @@ -745,6 +763,7 @@ describe('the price floors module', function () { skipped: false, floorMin: undefined, modelVersion: 'basic model', + modelWeight: 10, modelTimestamp: 1606772895, location: 'setConfig', skipRate: 0, @@ -825,6 +844,7 @@ describe('the price floors module', function () { skipped: false, floorMin: undefined, modelVersion: 'basic model', + modelWeight: 10, modelTimestamp: 1606772895, location: 'setConfig', skipRate: 0, @@ -864,6 +884,7 @@ describe('the price floors module', function () { skipped: false, floorMin: undefined, modelVersion: 'fetch model name', + modelWeight: 10, modelTimestamp: 1606772895, location: 'fetch', skipRate: 0, @@ -902,6 +923,7 @@ describe('the price floors module', function () { skipped: false, floorMin: undefined, modelVersion: 'fetch model name', + modelWeight: 10, modelTimestamp: 1606772895, location: 'fetch', skipRate: 0, @@ -943,6 +965,7 @@ describe('the price floors module', function () { skipped: false, floorMin: undefined, modelVersion: 'fetch model name', + modelWeight: 10, modelTimestamp: 1606772895, location: 'fetch', skipRate: 95, @@ -966,6 +989,7 @@ describe('the price floors module', function () { skipped: false, floorMin: undefined, modelVersion: 'basic model', + modelWeight: 10, modelTimestamp: 1606772895, location: 'setConfig', skipRate: 0, @@ -991,6 +1015,7 @@ describe('the price floors module', function () { skipped: false, floorMin: undefined, modelVersion: 'basic model', + modelWeight: 10, modelTimestamp: 1606772895, location: 'setConfig', skipRate: 0, @@ -1291,6 +1316,41 @@ describe('the price floors module', function () { floor: 1.3334 // 1.3334 * 0.75 = 1.000005 which is the floor (we cut off getFloor at 4 decimal points) }); }); + + it('should use standard cpmAdjustment if no bidder cpmAdjustment', function () { + getGlobal().bidderSettings = { + rubicon: { + bidCpmAdjustment: function (bidCpm, bidResponse) { + return bidResponse.cpm * 0.5; + }, + }, + standard: { + bidCpmAdjustment: function (bidCpm, bidResponse) { + return bidResponse.cpm * 0.75; + }, + } + }; + _floorDataForAuction[bidRequest.auctionId] = utils.deepClone(basicFloorConfig); + _floorDataForAuction[bidRequest.auctionId].data.values = { '*': 1.0 }; + let appnexusBid = { + ...bidRequest, + bidder: 'appnexus' + }; + + // the conversion should be what the bidder would need to return in order to match the actual floor + // rubicon + expect(bidRequest.getFloor()).to.deep.equal({ + currency: 'USD', + floor: 2.0 // a 2.0 bid after rubicons cpm adjustment would be 1.0 and thus is the floor after adjust + }); + + // appnexus + expect(appnexusBid.getFloor()).to.deep.equal({ + currency: 'USD', + floor: 1.3334 // 1.3334 * 0.75 = 1.000005 which is the floor (we cut off getFloor at 4 decimal points) + }); + }); + it('should work when cpmAdjust function uses bid object', function () { getGlobal().bidderSettings = { rubicon: { diff --git a/test/spec/modules/proxistoreBidAdapter_spec.js b/test/spec/modules/proxistoreBidAdapter_spec.js index 410c3c59fb6..f98d9633320 100644 --- a/test/spec/modules/proxistoreBidAdapter_spec.js +++ b/test/spec/modules/proxistoreBidAdapter_spec.js @@ -1,55 +1,58 @@ import { expect } from 'chai'; let { spec } = require('modules/proxistoreBidAdapter'); - const BIDDER_CODE = 'proxistore'; describe('ProxistoreBidAdapter', function () { const bidderRequest = { - 'bidderCode': BIDDER_CODE, - 'auctionId': '1025ba77-5463-4877-b0eb-14b205cb9304', - 'bidderRequestId': '10edf38ec1a719', - 'gdprConsent': { - 'gdprApplies': true, - 'consentString': 'CONSENT_STRING', - 'vendorData': { - 'vendorConsents': { - '418': true - } - } - } + bidderCode: BIDDER_CODE, + auctionId: '1025ba77-5463-4877-b0eb-14b205cb9304', + bidderRequestId: '10edf38ec1a719', + gdprConsent: { + gdprApplies: true, + consentString: 'CONSENT_STRING', + vendorData: { + vendorConsents: { + 418: true, + }, + }, + }, }; let bid = { sizes: [[300, 600]], params: { website: 'example.fr', - language: 'fr' + language: 'fr', + }, + ortb2: { + user: { ext: { data: { segments: [], contextual_categories: {} } } }, }, auctionId: 442133079, bidId: 464646969, - transactionId: 511916005 + transactionId: 511916005, }; describe('isBidRequestValid', function () { it('it should be true if required params are presents and there is no info in the local storage', function () { expect(spec.isBidRequestValid(bid)).to.equal(true); }); - - it('it should be false if the value in the localstorage is less than 5minutes of the actual time', function() { + it('it should be false if the value in the localstorage is less than 5minutes of the actual time', function () { const date = new Date(); - date.setMinutes(date.getMinutes() - 1) - localStorage.setItem(`PX_NoAds_${bid.params.website}`, date) - expect(spec.isBidRequestValid(bid)).to.equal(false); + date.setMinutes(date.getMinutes() - 1); + localStorage.setItem(`PX_NoAds_${bid.params.website}`, date); + expect(spec.isBidRequestValid(bid)).to.equal(true); }); - - it('it should be true if the value in the localstorage is more than 5minutes of the actual time', function() { + it('it should be true if the value in the localstorage is more than 5minutes of the actual time', function () { const date = new Date(); - date.setMinutes(date.getMinutes() - 10) - localStorage.setItem(`PX_NoAds_${bid.params.website}`, date) + date.setMinutes(date.getMinutes() - 10); + localStorage.setItem(`PX_NoAds_${bid.params.website}`, date); expect(spec.isBidRequestValid(bid)).to.equal(true); }); }); - describe('buildRequests', function () { - const url = 'https://abs.proxistore.com/fr/v3/rtb/prebid/multi'; - const request = spec.buildRequests([bid], bidderRequest); + const url = { + cookieBase: 'https://abs.proxistore.com/fr/v3/rtb/prebid/multi', + cookieLess: + 'https://abs.proxistore.com/fr/v3/rtb/prebid/multi/cookieless', + }; + let request = spec.buildRequests([bid], bidderRequest); it('should return a valid object', function () { expect(request).to.be.an('object'); expect(request.method).to.exist; @@ -59,16 +62,22 @@ describe('ProxistoreBidAdapter', function () { it('request method should be POST', function () { expect(request.method).to.equal('POST'); }); - it('should contain a valid url', function () { - expect(request.url).equal(url); - }); it('should have the value consentGiven to true bc we have 418 in the vendor list', function () { const data = JSON.parse(request.data); - - expect(data.gdpr.consentString).equal(bidderRequest.gdprConsent.consentString); + expect(data.gdpr.consentString).equal( + bidderRequest.gdprConsent.consentString + ); expect(data.gdpr.applies).to.be.true; expect(data.gdpr.consentGiven).to.be.true; }); + it('should contain a valid url', function () { + // has gdpr consent + expect(request.url).equal(url.cookieBase); + // doens't have gpdr consent + bidderRequest.gdprConsent.vendorData = null; + request = spec.buildRequests([bid], bidderRequest); + expect(request.url).equal(url.cookieLess); + }); it('should have a property a length of bids equal to one if there is only one bid', function () { const data = JSON.parse(request.data); expect(data.hasOwnProperty('bids')).to.be.true; @@ -77,51 +86,51 @@ describe('ProxistoreBidAdapter', function () { expect(data.bids[0].hasOwnProperty('id')).to.be.true; expect(data.bids[0].sizes).to.be.an('array'); }); - }); + it('should correctly set bidfloor on imp when getfloor in scope', function () { + let data = JSON.parse(request.data); + expect(data.bids[0].floor).to.be.null; - describe('interpretResponse', function () { - const responses = { - body: - [{ - cpm: 6.25, - creativeId: '48fd47c9-ce35-4fda-804b-17e16c8c36ac', - currency: 'EUR', - dealId: '2019-10_e3ecad8e-d07a-4c90-ad46-cd0f306c8960', - height: 600, - netRevenue: true, - requestId: '923756713', - ttl: 10, - vastUrl: null, - vastXml: null, - width: 300, - }] - }; - const badResponse = { body: [] }; - const interpretedResponse = spec.interpretResponse(responses, bid)[0]; - it('should send an empty array if body is empty', function () { - expect(spec.interpretResponse(badResponse, bid)).to.be.an('array'); - expect(spec.interpretResponse(badResponse, bid).length).equal(0); - }); - it('should interpret the response correctly if it is valid', function () { - expect(interpretedResponse.cpm).equal(6.25); - expect(interpretedResponse.creativeId).equal('48fd47c9-ce35-4fda-804b-17e16c8c36ac'); - expect(interpretedResponse.currency).equal('EUR'); - expect(interpretedResponse.height).equal(600); - expect(interpretedResponse.width).equal(300); - expect(interpretedResponse.requestId).equal('923756713'); - expect(interpretedResponse.netRevenue).to.be.true; - expect(interpretedResponse.netRevenue).to.be.true; - }); - it('should have a value in the local storage if the response is empty', function() { - spec.interpretResponse(badResponse, bid); - expect(localStorage.getItem(`PX_NoAds_${bid.params.website}`)).to.be.string; + // make it respond with a non USD floor should not send it + bid.getFloor = function () { + return { currency: 'EUR', floor: 1.0 }; + }; + let req = spec.buildRequests([bid], bidderRequest); + data = JSON.parse(req.data); + expect(data.bids[0].floor).equal(1); + bid.getFloor = function () { + return { currency: 'USD', floor: 1.0 }; + }; + req = spec.buildRequests([bid], bidderRequest); + data = JSON.parse(req.data); + expect(data.bids[0].floor).to.be.null; }); }); + describe('interpretResponse', function() { + const emptyResponseParam = {body: []}; + const fakeResponseParam = {body: [ + { ad: '', + cpm: 6.25, + creativeId: '22c3290b-8cd5-4cd6-8e8c-28a2de180ccd', + currency: 'EUR', + dealId: '2021-03_a63ec55e-b9bb-4ca4-b2c9-f456be67e656', + height: 600, + netRevenue: true, + requestId: '3543724f2a033c9', + segments: [], + ttl: 10, + vastUrl: null, + vastXml: null, + width: 300} + ] + }; - describe('interpretResponse', function () { - it('should aways return an empty array', function () { - expect(spec.getUserSyncs()).to.be.an('array'); - expect(spec.getUserSyncs().length).equal(0); + it('should always return an array', function() { + let response = spec.interpretResponse(emptyResponseParam, bid); + expect(response).to.be.an('array'); + expect(response.length).equal(0); + response = spec.interpretResponse(fakeResponseParam, bid); + expect(response).to.be.an('array'); + expect(response.length).equal(1); }); }); }); diff --git a/test/spec/modules/pubmaticBidAdapter_spec.js b/test/spec/modules/pubmaticBidAdapter_spec.js index 37deb0bca9c..4477ee4824f 100644 --- a/test/spec/modules/pubmaticBidAdapter_spec.js +++ b/test/spec/modules/pubmaticBidAdapter_spec.js @@ -1695,7 +1695,7 @@ describe('PubMatic adapter', function () { 'source': 'liveramp.com', 'uids': [{ 'id': 'identity-link-user-id', - 'atype': 1 + 'atype': 3 }] }]); }); @@ -1736,7 +1736,7 @@ describe('PubMatic adapter', function () { 'source': 'liveintent.com', 'uids': [{ 'id': 'live-intent-user-id', - 'atype': 1 + 'atype': 3 }] }]); }); @@ -1818,7 +1818,7 @@ describe('PubMatic adapter', function () { 'source': 'britepool.com', 'uids': [{ 'id': 'britepool-user-id', - 'atype': 1 + 'atype': 3 }] }]); }); @@ -2614,7 +2614,7 @@ describe('PubMatic adapter', function () { } expect(response[0].dealId).to.equal(bidResponses.body.seatbid[0].bid[0].dealid); expect(response[0].currency).to.equal('USD'); - expect(response[0].netRevenue).to.equal(false); + expect(response[0].netRevenue).to.equal(true); expect(response[0].ttl).to.equal(300); expect(response[0].meta.networkId).to.equal(123); expect(response[0].adserverTargeting.hb_buyid_pubmatic).to.equal('BUYER-ID-987'); @@ -2638,7 +2638,7 @@ describe('PubMatic adapter', function () { } expect(response[1].dealId).to.equal(bidResponses.body.seatbid[1].bid[0].dealid); expect(response[1].currency).to.equal('USD'); - expect(response[1].netRevenue).to.equal(false); + expect(response[1].netRevenue).to.equal(true); expect(response[1].ttl).to.equal(300); expect(response[1].meta.networkId).to.equal(422); expect(response[1].adserverTargeting.hb_buyid_pubmatic).to.equal('BUYER-ID-789'); diff --git a/test/spec/modules/pubxBidAdapter_spec.js b/test/spec/modules/pubxBidAdapter_spec.js index d5f1a0f5da3..6cea8787845 100644 --- a/test/spec/modules/pubxBidAdapter_spec.js +++ b/test/spec/modules/pubxBidAdapter_spec.js @@ -60,6 +60,70 @@ describe('pubxAdapter', function () { }); }); + describe('getUserSyncs', function () { + const sandbox = sinon.sandbox.create(); + + const keywordsText = 'meta1,meta2,meta3,meta4,meta5'; + const descriptionText = 'description1description2description3description4description5description'; + + let documentStubMeta; + + beforeEach(function () { + documentStubMeta = sandbox.stub(document, 'getElementsByName'); + const metaElKeywords = document.createElement('meta'); + metaElKeywords.setAttribute('name', 'keywords'); + metaElKeywords.setAttribute('content', keywordsText); + documentStubMeta.withArgs('keywords').returns([metaElKeywords]); + + const metaElDescription = document.createElement('meta'); + metaElDescription.setAttribute('name', 'description'); + metaElDescription.setAttribute('content', descriptionText); + documentStubMeta.withArgs('description').returns([metaElDescription]); + }); + + afterEach(function () { + documentStubMeta.restore(); + }); + + let kwString = ''; + let kwEnc = ''; + let descContent = ''; + let descEnc = ''; + + it('returns empty sync array when iframe is not enabled', function () { + const syncOptions = {}; + expect(spec.getUserSyncs(syncOptions)).to.deep.equal([]); + }); + + it('returns kwEnc when there is kwTag with more than 20 length', function () { + const kwArray = keywordsText.substr(0, 20).split(','); + kwArray.pop(); + kwString = kwArray.join(); + kwEnc = encodeURIComponent(kwString); + const syncs = spec.getUserSyncs({ iframeEnabled: true }); + expect(syncs[0].url).to.include(`pkw=${kwEnc}`); + }); + + it('returns kwEnc when there is kwTag with more than 60 length', function () { + descContent = descContent.substr(0, 60); + descEnc = encodeURIComponent(descContent); + const syncs = spec.getUserSyncs({ iframeEnabled: true }); + expect(syncs[0].url).to.include(`pkw=${descEnc}`); + }); + + it('returns titleEnc when there is titleContent with more than 30 length', function () { + let titleText = 'title1title2title3title4title5title'; + const documentStubTitle = sandbox.stub(document, 'title').value(titleText); + + if (titleText.length > 30) { + titleText = titleText.substr(0, 30); + } + + const syncs = spec.getUserSyncs({ iframeEnabled: true }); + expect(syncs[0].url).to.include(`pt=${encodeURIComponent(titleText)}`); + }); + }); + describe('interpretResponse', function () { const serverResponse = { body: { diff --git a/test/spec/modules/pubxaiAnalyticsAdapter_spec.js b/test/spec/modules/pubxaiAnalyticsAdapter_spec.js index 91c81dcae8d..40f7afeeb6a 100644 --- a/test/spec/modules/pubxaiAnalyticsAdapter_spec.js +++ b/test/spec/modules/pubxaiAnalyticsAdapter_spec.js @@ -1,5 +1,5 @@ import pubxaiAnalyticsAdapter from 'modules/pubxaiAnalyticsAdapter.js'; -import { getDeviceType } from 'modules/pubxaiAnalyticsAdapter.js'; +import { getDeviceType, getBrowser, getOS } from 'modules/pubxaiAnalyticsAdapter.js'; import { expect } from 'chai'; @@ -27,6 +27,8 @@ describe('pubxai analytics adapter', function() { pubxId: '6c415fc0-8b0e-4cf5-be73-01526a4db625' }; + let location = utils.getWindowLocation(); + let prebidEvent = { 'auctionInit': { 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', @@ -53,9 +55,9 @@ describe('pubxai analytics adapter', function() { 'floorData': { 'skipped': false, 'skipRate': 0, - 'modelVersion': 'new model 1.0', + 'modelVersion': 'test model 1.0', 'location': 'fetch', - 'floorProvider': 'PubXFloor', + 'floorProvider': 'PubXFloorProvider', 'fetchStatus': 'success' } }], @@ -83,9 +85,9 @@ describe('pubxai analytics adapter', function() { 'floorData': { 'skipped': false, 'skipRate': 0, - 'modelVersion': 'new model 1.0', + 'modelVersion': 'test model 1.0', 'location': 'fetch', - 'floorProvider': 'PubXFloor', + 'floorProvider': 'PubXFloorProvider', 'fetchStatus': 'success' }, 'mediaTypes': { @@ -149,9 +151,9 @@ describe('pubxai analytics adapter', function() { 'floorData': { 'skipped': false, 'skipRate': 0, - 'modelVersion': 'new model 1.0', + 'modelVersion': 'test model 1.0', 'location': 'fetch', - 'floorProvider': 'PubXFloor', + 'floorProvider': 'PubXFloorProvider', 'fetchStatus': 'success' }, 'mediaTypes': { @@ -219,6 +221,12 @@ describe('pubxai analytics adapter', function() { 'originalCpm': 0.5, 'originalCurrency': 'USD', 'floorData': { + 'fetchStatus': 'success', + 'floorProvider': 'PubXFloorProvider', + 'location': 'fetch', + 'modelVersion': 'test model 1.0', + 'skipRate': 0, + 'skipped': false, 'floorValue': 0.4, 'floorRule': '/19968336/header-bid-tag-1|banner', 'floorCurrency': 'USD', @@ -235,8 +243,8 @@ describe('pubxai analytics adapter', function() { } }, 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', - 'responseTimestamp': 1603865707449, - 'requestTimestamp': 1603865707182, + 'responseTimestamp': 1616654313071, + 'requestTimestamp': 1616654312804, 'bidder': 'appnexus', 'timeToRespond': 267, 'pbLg': '0.50', @@ -257,8 +265,8 @@ describe('pubxai analytics adapter', function() { }, 'auctionEnd': { 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', - 'timestamp': 1603865707180, - 'auctionEnd': 1603865707180, + 'timestamp': 1616654312804, + 'auctionEnd': 1616654313090, 'auctionStatus': 'completed', 'adUnits': [{ 'code': '/19968336/header-bid-tag-1', @@ -281,9 +289,9 @@ describe('pubxai analytics adapter', function() { 'floorData': { 'skipped': false, 'skipRate': 0, - 'modelVersion': 'new model 1.0', + 'modelVersion': 'test model 1.0', 'location': 'fetch', - 'floorProvider': 'PubXFloor', + 'floorProvider': 'PubXFloorProvider', 'fetchStatus': 'success' } }], @@ -311,9 +319,9 @@ describe('pubxai analytics adapter', function() { 'floorData': { 'skipped': false, 'skipRate': 0, - 'modelVersion': 'new model 1.0', + 'modelVersion': 'test model 1.0', 'location': 'fetch', - 'floorProvider': 'PubXFloor', + 'floorProvider': 'PubXFloorProvider', 'fetchStatus': 'success' }, 'mediaTypes': { @@ -381,6 +389,12 @@ describe('pubxai analytics adapter', function() { 'originalCpm': 0.5, 'originalCurrency': 'USD', 'floorData': { + 'fetchStatus': 'success', + 'floorProvider': 'PubXFloorProvider', + 'location': 'fetch', + 'modelVersion': 'test model 1.0', + 'skipRate': 0, + 'skipped': false, 'floorValue': 0.4, 'floorRule': '/19968336/header-bid-tag-1|banner', 'floorCurrency': 'USD', @@ -397,8 +411,8 @@ describe('pubxai analytics adapter', function() { } }, 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', - 'responseTimestamp': 1603865707449, - 'requestTimestamp': 1603865707182, + 'responseTimestamp': 1616654313071, + 'requestTimestamp': 1616654312804, 'bidder': 'appnexus', 'timeToRespond': 267, 'pbLg': '0.50', @@ -449,6 +463,12 @@ describe('pubxai analytics adapter', function() { 'originalCpm': 0.5, 'originalCurrency': 'USD', 'floorData': { + 'fetchStatus': 'success', + 'floorProvider': 'PubXFloorProvider', + 'location': 'fetch', + 'modelVersion': 'test model 1.0', + 'skipRate': 0, + 'skipped': false, 'floorValue': 0.4, 'floorRule': '/19968336/header-bid-tag-1|banner', 'floorCurrency': 'USD', @@ -465,8 +485,8 @@ describe('pubxai analytics adapter', function() { } }, 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', - 'responseTimestamp': 1603865707449, - 'requestTimestamp': 1603865707182, + 'responseTimestamp': 1616654313071, + 'requestTimestamp': 1616654312804, 'bidder': 'appnexus', 'timeToRespond': 267, 'pbLg': '0.50', @@ -488,20 +508,23 @@ describe('pubxai analytics adapter', function() { 'params': [{ 'placementId': 13144370 }] - } + }, + 'pageDetail': { + 'host': location.host, + 'path': location.pathname, + 'search': location.search + }, }; - let location = utils.getWindowLocation(); let expectedAfterBid = { 'bids': [{ 'bidderCode': 'appnexus', 'bidId': '248f9a4489835e', 'adUnitCode': '/19968336/header-bid-tag-1', - 'requestId': '184cbc05bb90ba', 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', 'sizes': '300x250', 'renderStatus': 2, - 'requestTimestamp': 1603865707182, + 'requestTimestamp': 1616654312804, 'creativeId': 96846035, 'currency': 'USD', 'cpm': 0.5, @@ -509,6 +532,12 @@ describe('pubxai analytics adapter', function() { 'mediaType': 'banner', 'statusMessage': 'Bid available', 'floorData': { + 'fetchStatus': 'success', + 'floorProvider': 'PubXFloorProvider', + 'location': 'fetch', + 'modelVersion': 'test model 1.0', + 'skipRate': 0, + 'skipped': false, 'floorValue': 0.4, 'floorRule': '/19968336/header-bid-tag-1|banner', 'floorCurrency': 'USD', @@ -525,141 +554,47 @@ describe('pubxai analytics adapter', function() { } }, 'timeToRespond': 267, - 'responseTimestamp': 1603865707449, - 'platform': navigator.platform, - 'deviceType': getDeviceType() + 'responseTimestamp': 1616654313071 }], - 'auctionInit': { + 'pageDetail': { 'host': location.host, 'path': location.pathname, 'search': location.search, - 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', - 'timestamp': 1603865707180, - 'auctionStatus': 'inProgress', - 'adUnits': [{ - 'code': '/19968336/header-bid-tag-1', - 'mediaTypes': { - 'banner': { - 'sizes': [ - [ - 300, - 250 - ] - ] - } - }, - 'bids': [{ - 'bidder': 'appnexus', - 'params': { - 'placementId': 13144370 - }, - 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', - 'floorData': { - 'skipped': false, - 'skipRate': 0, - 'modelVersion': 'new model 1.0', - 'location': 'fetch', - 'floorProvider': 'PubXFloor', - 'fetchStatus': 'success' - } - }], - 'sizes': [ - [ - 300, - 250 - ] - ], - 'transactionId': '41ec8eaf-3e7c-4a8b-8344-ab796ff6e294' - }], - 'adUnitCodes': [ - '/19968336/header-bid-tag-1' - ], - 'bidderRequests': [{ - 'bidderCode': 'appnexus', - 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', - 'bidderRequestId': '184cbc05bb90ba', - 'bids': [{ - 'bidder': 'appnexus', - 'params': { - 'placementId': 13144370 - }, - 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', - 'floorData': { - 'skipped': false, - 'skipRate': 0, - 'modelVersion': 'new model 1.0', - 'location': 'fetch', - 'floorProvider': 'PubXFloor', - 'fetchStatus': 'success' - }, - 'mediaTypes': { - 'banner': { - 'sizes': [ - [ - 300, - 250 - ] - ] - } - }, - 'adUnitCode': '/19968336/header-bid-tag-1', - 'transactionId': '41ec8eaf-3e7c-4a8b-8344-ab796ff6e294', - 'sizes': [ - [ - 300, - 250 - ] - ], - 'bidId': '248f9a4489835e', - 'bidderRequestId': '184cbc05bb90ba', - 'src': 'client', - 'bidRequestsCount': 1, - 'bidderRequestsCount': 1, - 'bidderWinsCount': 0 - }], - 'auctionStart': 1603865707180, - 'timeout': 1000, - 'refererInfo': { - 'referer': 'http://local-pnh.net:8080/stream/', - 'reachedTop': true, - 'isAmp': false, - 'numIframes': 0, - 'stack': [ - 'http://local-pnh.net:8080/stream/' - ], - 'canonicalUrl': null - }, - 'start': 1603865707182 - }], - 'noBids': [], - 'bidsReceived': [], - 'winningBids': [], - 'timeout': 1000, - 'config': { - 'samplingRate': '1', - 'pubxId': '6c415fc0-8b0e-4cf5-be73-01526a4db625' - } + 'adUnitCount': 1 + }, + 'floorDetail': { + 'fetchStatus': 'success', + 'floorProvider': 'PubXFloorProvider', + 'location': 'fetch', + 'modelVersion': 'test model 1.0', + 'skipRate': 0, + 'skipped': false + }, + 'deviceDetail': { + 'platform': navigator.platform, + 'deviceType': getDeviceType(), + 'deviceOS': getOS(), + 'browser': getBrowser() }, 'initOptions': initOptions }; let expectedAfterBidWon = { 'winningBid': { - 'bidderCode': 'appnexus', - 'bidId': '248f9a4489835e', 'adUnitCode': '/19968336/header-bid-tag-1', 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', - 'renderedSize': '300x250', - 'renderStatus': 4, - 'requestTimestamp': 1603865707182, + 'bidderCode': 'appnexus', + 'bidId': '248f9a4489835e', + 'cpm': 0.5, 'creativeId': 96846035, 'currency': 'USD', - 'cpm': 0.5, - 'netRevenue': true, - 'mediaType': 'banner', - 'status': 'rendered', - 'statusMessage': 'Bid available', 'floorData': { + 'fetchStatus': 'success', + 'floorProvider': 'PubXFloorProvider', + 'location': 'fetch', + 'modelVersion': 'test model 1.0', + 'skipRate': 0, + 'skipped': false, 'floorValue': 0.4, 'floorRule': '/19968336/header-bid-tag-1|banner', 'floorCurrency': 'USD', @@ -675,10 +610,24 @@ describe('pubxai analytics adapter', function() { 'mediaType': 'banner' } }, - 'timeToRespond': 267, - 'responseTimestamp': 1603865707449, + 'floorProvider': 'PubXFloorProvider', + 'isWinningBid': true, + 'mediaType': 'banner', + 'netRevenue': true, + 'placementId': 13144370, + 'renderedSize': '300x250', + 'renderStatus': 4, + 'responseTimestamp': 1616654313071, + 'requestTimestamp': 1616654312804, + 'status': 'rendered', + 'statusMessage': 'Bid available', + 'timeToRespond': 267 + }, + 'deviceDetail': { 'platform': navigator.platform, - 'deviceType': getDeviceType() + 'deviceType': getDeviceType(), + 'deviceOS': getOS(), + 'browser': getBrowser() }, 'initOptions': initOptions } diff --git a/test/spec/modules/qwarryBidAdapter_spec.js b/test/spec/modules/qwarryBidAdapter_spec.js index 91e3cf4bfdf..f15d7b488cc 100644 --- a/test/spec/modules/qwarryBidAdapter_spec.js +++ b/test/spec/modules/qwarryBidAdapter_spec.js @@ -70,11 +70,21 @@ describe('qwarryBidAdapter', function () { describe('buildRequests', function () { let bidRequests = [REQUEST] - const bidderRequest = spec.buildRequests(bidRequests, { bidderRequestId: '123' }) + const bidderRequest = spec.buildRequests(bidRequests, { + bidderRequestId: '123', + gdprConsent: { + gdprApplies: true, + consentString: 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A==' + }, + refererInfo: { + referer: 'http://test.com/path.html' + } + }) it('sends bid request to ENDPOINT via POST', function () { expect(bidderRequest.method).to.equal('POST') expect(bidderRequest.data.requestId).to.equal('123') + expect(bidderRequest.data.referer).to.equal('http://test.com/path.html') expect(bidderRequest.data.bids).to.deep.contains({ bidId: '456', zoneToken: 'e64782a4-8e68-4c38-965b-80ccf115d46f', pos: 7 }) expect(bidderRequest.options.customHeaders).to.deep.equal({ 'Rtb-Direct': true }) expect(bidderRequest.options.contentType).to.equal('application/json') diff --git a/test/spec/modules/radsBidAdapter_spec.js b/test/spec/modules/radsBidAdapter_spec.js index c629daf3da5..c3c3b4b2746 100644 --- a/test/spec/modules/radsBidAdapter_spec.js +++ b/test/spec/modules/radsBidAdapter_spec.js @@ -87,12 +87,25 @@ describe('radsAdapter', function () { 'auctionId': '1d1a030790a475' }]; + // Without gdprConsent let bidderRequest = { refererInfo: { referer: 'some_referrer.net' } } + // With gdprConsent + var bidderRequestGdprConsent = { + refererInfo: { + referer: 'some_referrer.net' + }, + gdprConsent: { + consentString: 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==', + vendorData: {someData: 'value'}, + gdprApplies: true + } + }; + // without gdprConsent const request = spec.buildRequests(bidRequests, bidderRequest); it('sends bid request to our endpoint via GET', function () { expect(request[0].method).to.equal('GET'); @@ -105,6 +118,20 @@ describe('radsAdapter', function () { let data = request[1].data.replace(/rnd=\d+\&/g, '').replace(/ref=.*\&bid/g, 'bid'); expect(data).to.equal('rt=vast2&_f=prebid_js&_ps=6682&srw=640&srh=480&idt=100&p=some_referrer.net&bid_id=30b31c1838de1e&pfilter%5Bfloorprice%5D=1000000&pfilter%5Bgeo%5D%5Bcountry%5D=DE&pfilter%5Bgeo%5D%5Bregion%5D=DE-BE&bcat=IAB2%2CIAB4&dvt=desktop'); }); + + // with gdprConsent + const request2 = spec.buildRequests(bidRequests, bidderRequestGdprConsent); + it('sends bid request to our endpoint via GET', function () { + expect(request2[0].method).to.equal('GET'); + let data = request2[0].data.replace(/rnd=\d+\&/g, '').replace(/ref=.*\&bid/g, 'bid'); + expect(data).to.equal('rt=bid-response&_f=prebid_js&_ps=6682&srw=300&srh=250&idt=100&p=some_referrer.net&bid_id=30b31c1838de1e&pfilter%5Bfloorprice%5D=1000000&pfilter%5Bgeo%5D%5Bcountry%5D=DE&pfilter%5Bgdpr_consent%5D=BOJ%2FP2HOJ%2FP2HABABMAAAAAZ%2BA%3D%3D&pfilter%5Bgdpr%5D=true&bcat=IAB2%2CIAB4&dvt=desktop&i=1.1.1.1'); + }); + + it('sends bid video request to our rads endpoint via GET', function () { + expect(request2[1].method).to.equal('GET'); + let data = request2[1].data.replace(/rnd=\d+\&/g, '').replace(/ref=.*\&bid/g, 'bid'); + expect(data).to.equal('rt=vast2&_f=prebid_js&_ps=6682&srw=640&srh=480&idt=100&p=some_referrer.net&bid_id=30b31c1838de1e&pfilter%5Bfloorprice%5D=1000000&pfilter%5Bgeo%5D%5Bcountry%5D=DE&pfilter%5Bgeo%5D%5Bregion%5D=DE-BE&pfilter%5Bgdpr_consent%5D=BOJ%2FP2HOJ%2FP2HABABMAAAAAZ%2BA%3D%3D&pfilter%5Bgdpr%5D=true&bcat=IAB2%2CIAB4&dvt=desktop'); + }); }); describe('interpretResponse', function () { @@ -203,4 +230,60 @@ describe('radsAdapter', function () { expect(result.length).to.equal(0); }); }); + + describe(`getUserSyncs test usage`, function () { + let serverResponses; + + beforeEach(function () { + serverResponses = [{ + body: { + requestId: '23beaa6af6cdde', + cpm: 0.5, + width: 0, + height: 0, + creativeId: 100500, + dealId: '', + currency: 'EUR', + netRevenue: true, + ttl: 300, + type: 'sspHTML', + ad: '', + userSync: { + iframeUrl: ['anyIframeUrl?a=1'], + imageUrl: ['anyImageUrl', 'anyImageUrl2'] + } + } + }]; + }); + + it(`return value should be an array`, function () { + expect(spec.getUserSyncs({ iframeEnabled: true })).to.be.an('array'); + }); + it(`array should have only one object and it should have a property type = 'iframe'`, function () { + expect(spec.getUserSyncs({ iframeEnabled: true }, serverResponses).length).to.be.equal(1); + let [userSync] = spec.getUserSyncs({ iframeEnabled: true }, serverResponses); + expect(userSync).to.have.property('type'); + expect(userSync.type).to.be.equal('iframe'); + }); + it(`we have valid sync url for iframe`, function () { + let [userSync] = spec.getUserSyncs({ iframeEnabled: true }, serverResponses, {consentString: 'anyString'}); + expect(userSync.url).to.be.equal('anyIframeUrl?a=1&gdpr_consent=anyString') + expect(userSync.type).to.be.equal('iframe'); + }); + it(`we have valid sync url for image`, function () { + let [userSync] = spec.getUserSyncs({ pixelEnabled: true }, serverResponses, {gdprApplies: true, consentString: 'anyString'}); + expect(userSync.url).to.be.equal('anyImageUrl?gdpr=1&gdpr_consent=anyString') + expect(userSync.type).to.be.equal('image'); + }); + it(`we have valid sync url for image and iframe`, function () { + let userSync = spec.getUserSyncs({ iframeEnabled: true, pixelEnabled: true }, serverResponses, {gdprApplies: true, consentString: 'anyString'}); + expect(userSync.length).to.be.equal(3); + expect(userSync[0].url).to.be.equal('anyIframeUrl?a=1&gdpr=1&gdpr_consent=anyString') + expect(userSync[0].type).to.be.equal('iframe'); + expect(userSync[1].url).to.be.equal('anyImageUrl?gdpr=1&gdpr_consent=anyString') + expect(userSync[1].type).to.be.equal('image'); + expect(userSync[2].url).to.be.equal('anyImageUrl2?gdpr=1&gdpr_consent=anyString') + expect(userSync[2].type).to.be.equal('image'); + }); + }); }); diff --git a/test/spec/modules/readpeakBidAdapter_spec.js b/test/spec/modules/readpeakBidAdapter_spec.js index 0c6f942e724..d5a877f6221 100644 --- a/test/spec/modules/readpeakBidAdapter_spec.js +++ b/test/spec/modules/readpeakBidAdapter_spec.js @@ -28,7 +28,8 @@ describe('ReadPeakAdapter', function() { params: { bidfloor: 5.0, publisherId: '11bc5dd5-7421-4dd8-c926-40fa653bec76', - siteId: '11bc5dd5-7421-4dd8-c926-40fa653bec77' + siteId: '11bc5dd5-7421-4dd8-c926-40fa653bec77', + tagId: 'test-tag-1' }, bidId: '2ffb201a808da7', bidderRequestId: '178e34bad3658f', @@ -104,7 +105,8 @@ describe('ReadPeakAdapter', function() { ver: '1.1' }, bidfloor: 5, - bidfloorcur: 'USD' + bidfloorcur: 'USD', + tagId: 'test-tag-1' } ], site: { @@ -177,6 +179,7 @@ describe('ReadPeakAdapter', function() { expect(data.id).to.equal(bidRequest.bidderRequestId); expect(data.imp[0].bidfloor).to.equal(bidRequest.params.bidfloor); expect(data.imp[0].bidfloorcur).to.equal('USD'); + expect(data.imp[0].tagId).to.equal('test-tag-1'); expect(data.site.publisher.id).to.equal(bidRequest.params.publisherId); expect(data.site.id).to.equal(bidRequest.params.siteId); expect(data.site.page).to.equal(bidderRequest.refererInfo.referer); diff --git a/test/spec/modules/relevantAnalyticsAdapter_spec.js b/test/spec/modules/relevantAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..3e31db2d7dc --- /dev/null +++ b/test/spec/modules/relevantAnalyticsAdapter_spec.js @@ -0,0 +1,43 @@ +import relevantAnalytics from '../../../modules/relevantAnalyticsAdapter.js'; +import adapterManager from 'src/adapterManager'; +import events from 'src/events'; +import constants from 'src/constants.json' +import { expect } from 'chai'; + +describe('Relevant Analytics Adapter', () => { + beforeEach(() => { + adapterManager.enableAnalytics({ + provider: 'relevant' + }); + }); + + afterEach(() => { + relevantAnalytics.disableAnalytics(); + }); + + it('should pass all events to the global array', () => { + // Given + const testEvents = [ + { ev: constants.EVENTS.AUCTION_INIT, args: { test: 1 } }, + { ev: constants.EVENTS.BID_REQUESTED, args: { test: 2 } }, + ]; + + // When + testEvents.forEach(({ ev, args }) => ( + events.emit(ev, args) + )); + + // Then + const eventQueue = (window.relevantDigital || {}).pbEventLog; + expect(eventQueue).to.be.an('array'); + expect(eventQueue.length).to.be.at.least(testEvents.length); + + // The last events should be our test events + const myEvents = eventQueue.slice(-testEvents.length); + testEvents.forEach(({ ev, args }, idx) => { + const actualEvent = myEvents[idx]; + expect(actualEvent.ev).to.eql(ev); + expect(actualEvent.args).to.eql(args); + }); + }); +}); diff --git a/test/spec/modules/richaudienceBidAdapter_spec.js b/test/spec/modules/richaudienceBidAdapter_spec.js index 90723fb863f..5deb2463523 100644 --- a/test/spec/modules/richaudienceBidAdapter_spec.js +++ b/test/spec/modules/richaudienceBidAdapter_spec.js @@ -4,7 +4,6 @@ import { spec } from 'modules/richaudienceBidAdapter.js'; import {config} from 'src/config.js'; -import * as utils from 'src/utils.js'; describe('Richaudience adapter tests', function () { var DEFAULT_PARAMS_NEW_SIZES = [{ @@ -20,7 +19,8 @@ describe('Richaudience adapter tests', function () { params: { bidfloor: 0.5, pid: 'ADb1f40rmi', - supplyType: 'site' + supplyType: 'site', + keywords: 'key1=value1;key2=value2' }, auctionId: '0cb3144c-d084-4686-b0d6-f5dbe917c563', bidRequestsCount: 1, @@ -75,26 +75,19 @@ describe('Richaudience adapter tests', function () { user: {} }]; - var DEFAULT_PARAMS_VIDEO_OUT_PARAMS = [{ + var DEFAULT_PARAMS_BANNER_OUTSTREAM = [{ adUnitCode: 'test-div', bidId: '2c7c8e9c900244', mediaTypes: { - video: { - context: 'outstream', - playerSize: [640, 480], - mimes: ['video/mp4'] + banner: { + sizes: [[300, 250], [600, 300]] } }, bidder: 'richaudience', params: { bidfloor: 0.5, pid: 'ADb1f40rmi', - supplyType: 'site', - player: { - init: 'close', - end: 'close', - skin: 'dark' - } + supplyType: 'site' }, auctionId: '0cb3144c-d084-4686-b0d6-f5dbe917c563', bidRequestsCount: 1, @@ -240,6 +233,9 @@ describe('Richaudience adapter tests', function () { expect(requestContent).to.have.property('transactionId').and.to.equal('29df2112-348b-4961-8863-1b33684d95e6'); expect(requestContent).to.have.property('timeout').and.to.equal(3000); expect(requestContent).to.have.property('numIframes').and.to.equal(0); + expect(typeof requestContent.scr_rsl === 'string') + expect(typeof requestContent.cpuc === 'number') + expect(requestContent).to.have.property('kws').and.to.equal('key1=value1;key2=value2'); }) it('Verify build request to prebid video inestream', function() { @@ -259,8 +255,6 @@ describe('Richaudience adapter tests', function () { expect(requestContent).to.have.property('demand').and.to.equal('video'); expect(requestContent.videoData).to.have.property('format').and.to.equal('instream'); - // expect(requestContent.videoData.playerSize[0][0]).to.equal('640'); - // expect(requestContent.videoData.playerSize[0][0]).to.equal('480'); }) it('Verify build request to prebid video outstream', function() { @@ -278,6 +272,7 @@ describe('Richaudience adapter tests', function () { expect(request[0]).to.have.property('method').and.to.equal('POST'); const requestContent = JSON.parse(request[0].data); + expect(requestContent).to.have.property('demand').and.to.equal('video'); expect(requestContent.videoData).to.have.property('format').and.to.equal('outstream'); }) @@ -620,9 +615,19 @@ describe('Richaudience adapter tests', function () { }); const bids = spec.interpretResponse(BID_RESPONSE_VIDEO, request[0]); + expect(bids).to.have.lengthOf(1); const bid = bids[0]; + expect(bid.cpm).to.equal(1.50); expect(bid.mediaType).to.equal('video'); expect(bid.vastXml).to.equal(''); + expect(bid.cpm).to.equal(1.50); + expect(bid.width).to.equal(1); + expect(bid.height).to.equal(1); + expect(bid.creativeId).to.equal('189198063'); + expect(bid.netRevenue).to.equal(true); + expect(bid.currency).to.equal('USD'); + expect(bid.ttl).to.equal(300); + expect(bid.dealId).to.equal('dealId'); }); it('no banner media response outstream', function () { @@ -637,6 +642,35 @@ describe('Richaudience adapter tests', function () { } }); + const bids = spec.interpretResponse(BID_RESPONSE_VIDEO, request[0]); + expect(bids).to.have.lengthOf(1); + const bid = bids[0]; + expect(bid.cpm).to.equal(1.50); + expect(bid.mediaType).to.equal('video'); + expect(bid.vastXml).to.equal(''); + expect(bid.renderer.url).to.equal('https://cdn3.richaudience.com/prebidVideo/player.js'); + expect(bid.cpm).to.equal(1.50); + expect(bid.width).to.equal(1); + expect(bid.height).to.equal(1); + expect(bid.creativeId).to.equal('189198063'); + expect(bid.netRevenue).to.equal(true); + expect(bid.currency).to.equal('USD'); + expect(bid.ttl).to.equal(300); + expect(bid.dealId).to.equal('dealId'); + }); + + it('banner media and response VAST', function () { + const request = spec.buildRequests(DEFAULT_PARAMS_BANNER_OUTSTREAM, { + gdprConsent: { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }, + refererInfo: { + referer: 'https://domain.com', + numIframes: 0 + } + }); + const bids = spec.interpretResponse(BID_RESPONSE_VIDEO, request[0]); const bid = bids[0]; expect(bid.mediaType).to.equal('video'); @@ -653,6 +687,16 @@ describe('Richaudience adapter tests', function () { expect(spec.aliases[0]).to.equal('ra'); }); + it('Verifies bidder gvlid', function () { + expect(spec.gvlid).to.equal(108); + }); + + it('Verifies bidder supportedMediaTypes', function () { + expect(spec.supportedMediaTypes).to.have.lengthOf(2); + expect(spec.supportedMediaTypes[0]).to.equal('banner'); + expect(spec.supportedMediaTypes[1]).to.equal('video'); + }); + it('Verifies if bid request is valid', function () { expect(spec.isBidRequestValid(DEFAULT_PARAMS_NEW_SIZES[0])).to.equal(true); expect(spec.isBidRequestValid(DEFAULT_PARAMS_WO_OPTIONAL[0])).to.equal(true); @@ -714,6 +758,34 @@ describe('Richaudience adapter tests', function () { bidfloor: 0.50, } })).to.equal(true); + expect(spec.isBidRequestValid({ + params: { + pid: ['1gCB5ZC4XL', '1a40xk8qSV'], + bidfloor: 0.50, + } + })).to.equal(false); + expect(spec.isBidRequestValid({ + params: { + pid: ['1gCB5ZC4XL', '1a40xk8qSV'], + supplyType: 'site', + bidfloor: 0.50, + } + })).to.equal(true); + expect(spec.isBidRequestValid({ + params: { + supplyType: 'site', + bidfloor: 0.50, + ifa: 'AAAAAAAAA-BBBB-CCCC-1111-222222220000', + } + })).to.equal(false); + expect(spec.isBidRequestValid({ + params: { + pid: ['1gCB5ZC4XL', '1a40xk8qSV'], + supplyType: 'site', + bidfloor: 0.50, + ifa: 'AAAAAAAAA-BBBB-CCCC-1111-222222220000', + } + })).to.equal(true); }); it('Verifies user syncs iframe', function () { diff --git a/test/spec/modules/riseBidAdapter_spec.js b/test/spec/modules/riseBidAdapter_spec.js new file mode 100644 index 00000000000..b3257cbda9d --- /dev/null +++ b/test/spec/modules/riseBidAdapter_spec.js @@ -0,0 +1,381 @@ +import { expect } from 'chai'; +import { spec } from 'modules/riseBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; +import { config } from 'src/config.js'; +import { VIDEO } from '../../../src/mediaTypes.js'; + +const ENDPOINT = 'https://hb.yellowblue.io/hb'; +const TEST_ENDPOINT = 'https://hb.yellowblue.io/hb-test'; +const TTL = 360; + +describe('riseAdapter', function () { + const adapter = newBidder(spec); + + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function () { + const bid = { + 'bidder': spec.code, + 'adUnitCode': 'adunit-code', + 'sizes': [['640', '480']], + 'params': { + 'org': 'jdye8weeyirk00000001' + } + }; + + it('should return true when required params are passed', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not found', function () { + const newBid = Object.assign({}, bid); + delete newBid.params; + newBid.params = { + 'org': null + }; + expect(spec.isBidRequestValid(newBid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + const bidRequests = [ + { + 'bidder': spec.code, + 'adUnitCode': 'adunit-code', + 'sizes': [[640, 480]], + 'params': { + 'org': 'jdye8weeyirk00000001' + }, + 'bidId': '299ffc8cca0b87', + 'bidderRequestId': '1144f487e563f9', + 'auctionId': 'bfc420c3-8577-4568-9766-a8a935fb620d', + } + ]; + + const testModeBidRequests = [ + { + 'bidder': spec.code, + 'adUnitCode': 'adunit-code', + 'sizes': [[640, 480]], + 'params': { + 'org': 'jdye8weeyirk00000001', + 'testMode': true + }, + 'bidId': '299ffc8cca0b87', + 'bidderRequestId': '1144f487e563f9', + 'auctionId': 'bfc420c3-8577-4568-9766-a8a935fb620d', + } + ]; + + const bidderRequest = { + bidderCode: 'rise', + } + + const customSessionId = '12345678'; + + it('sends bid request to ENDPOINT via GET', function () { + const requests = spec.buildRequests(bidRequests, bidderRequest); + for (const request of requests) { + expect(request.url).to.equal(ENDPOINT); + expect(request.method).to.equal('GET'); + } + }); + + it('sends the is_wrapper query param', function () { + bidRequests[0].params.isWrapper = true; + const requests = spec.buildRequests(bidRequests, bidderRequest); + for (const request of requests) { + expect(request.data.is_wrapper).to.equal(true); + } + }); + + it('sends the custom session id as a query param', function () { + bidRequests[0].params.sessionId = customSessionId; + const requests = spec.buildRequests(bidRequests, bidderRequest); + for (const request of requests) { + expect(request.data.session_id).to.equal(customSessionId); + } + }); + + it('sends bid request to test ENDPOINT via GET', function () { + const requests = spec.buildRequests(testModeBidRequests, bidderRequest); + for (const request of requests) { + expect(request.url).to.equal(TEST_ENDPOINT); + expect(request.method).to.equal('GET'); + } + }); + + it('should send the correct bid Id', function () { + const requests = spec.buildRequests(bidRequests, bidderRequest); + for (const request of requests) { + expect(request.data.bid_id).to.equal('299ffc8cca0b87'); + } + }); + + it('should send the correct width and height', function () { + const requests = spec.buildRequests(bidRequests, bidderRequest); + for (const request of requests) { + expect(request.data).to.be.an('object'); + expect(request.data).to.have.property('width', 640); + expect(request.data).to.have.property('height', 480); + } + }); + + it('should respect syncEnabled option', function() { + config.setConfig({ + userSync: { + syncEnabled: false, + filterSettings: { + all: { + bidders: '*', + filter: 'include' + } + } + } + }); + const requests = spec.buildRequests(bidRequests, bidderRequest); + for (const request of requests) { + expect(request.data).to.be.an('object'); + expect(request.data).to.not.have.property('cs_method'); + } + }); + + it('should respect "iframe" filter settings', function () { + config.setConfig({ + userSync: { + syncEnabled: true, + filterSettings: { + iframe: { + bidders: [spec.code], + filter: 'include' + } + } + } + }); + const requests = spec.buildRequests(bidRequests, bidderRequest); + for (const request of requests) { + expect(request.data).to.be.an('object'); + expect(request.data).to.have.property('cs_method', 'iframe'); + } + }); + + it('should respect "all" filter settings', function () { + config.setConfig({ + userSync: { + syncEnabled: true, + filterSettings: { + all: { + bidders: [spec.code], + filter: 'include' + } + } + } + }); + const requests = spec.buildRequests(bidRequests, bidderRequest); + for (const request of requests) { + expect(request.data).to.be.an('object'); + expect(request.data).to.have.property('cs_method', 'iframe'); + } + }); + + it('should send the pixel user sync param if userSync is enabled and no "iframe" or "all" configs are present', function () { + config.setConfig({ + userSync: { + syncEnabled: true + } + }); + const requests = spec.buildRequests(bidRequests, bidderRequest); + for (const request of requests) { + expect(request.data).to.be.an('object'); + expect(request.data).to.have.property('cs_method', 'pixel'); + } + }); + + it('should respect total exclusion', function() { + config.setConfig({ + userSync: { + syncEnabled: true, + filterSettings: { + image: { + bidders: [spec.code], + filter: 'exclude' + }, + iframe: { + bidders: [spec.code], + filter: 'exclude' + } + } + } + }); + const requests = spec.buildRequests(bidRequests, bidderRequest); + for (const request of requests) { + expect(request.data).to.be.an('object'); + expect(request.data).to.not.have.property('cs_method'); + } + }); + + it('should have us_privacy param if usPrivacy is available in the bidRequest', function () { + const bidderRequestWithUSP = Object.assign({uspConsent: '1YNN'}, bidderRequest); + const requests = spec.buildRequests(bidRequests, bidderRequestWithUSP); + for (const request of requests) { + expect(request.data).to.be.an('object'); + expect(request.data).to.have.property('us_privacy', '1YNN'); + } + }); + + it('should have an empty us_privacy param if usPrivacy is missing in the bidRequest', function () { + const requests = spec.buildRequests(bidRequests, bidderRequest); + for (const request of requests) { + expect(request.data).to.be.an('object'); + expect(request.data).to.not.have.property('us_privacy'); + } + }); + + it('should not send the gdpr param if gdprApplies is false in the bidRequest', function () { + const bidderRequestWithGDPR = Object.assign({gdprConsent: {gdprApplies: false}}, bidderRequest); + const requests = spec.buildRequests(bidRequests, bidderRequestWithGDPR); + for (const request of requests) { + expect(request.data).to.be.an('object'); + expect(request.data).to.not.have.property('gdpr'); + expect(request.data).to.not.have.property('gdpr_consent'); + } + }); + + it('should send the gdpr param if gdprApplies is true in the bidRequest', function () { + const bidderRequestWithGDPR = Object.assign({gdprConsent: {gdprApplies: true, consentString: 'test-consent-string'}}, bidderRequest); + const requests = spec.buildRequests(bidRequests, bidderRequestWithGDPR); + for (const request of requests) { + expect(request.data).to.be.an('object'); + expect(request.data).to.have.property('gdpr', true); + expect(request.data).to.have.property('gdpr_consent', 'test-consent-string'); + } + }); + + it('should have schain param if it is available in the bidRequest', () => { + const schain = { + ver: '1.0', + complete: 1, + nodes: [{ asi: 'indirectseller.com', sid: '00001', hp: 1 }], + }; + bidRequests[0].schain = schain; + const requests = spec.buildRequests(bidRequests, bidderRequest); + for (const request of requests) { + expect(request.data).to.be.an('object'); + expect(request.data).to.have.property('schain', '1.0,1!indirectseller.com,00001,,,,'); + } + }); + }); + + describe('interpretResponse', function () { + const response = { + cpm: 12.5, + vastXml: '', + width: 640, + height: 480, + requestId: '21e12606d47ba7', + netRevenue: true, + currency: 'USD' + }; + + it('should get correct bid response', function () { + let expectedResponse = [ + { + requestId: '21e12606d47ba7', + cpm: 12.5, + width: 640, + height: 480, + creativeId: '21e12606d47ba7', + currency: 'USD', + netRevenue: true, + ttl: TTL, + vastXml: '', + mediaType: VIDEO + } + ]; + const result = spec.interpretResponse({ body: response }); + expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); + }); + }) + + describe('getUserSyncs', function() { + const imageSyncResponse = { + body: { + userSyncPixels: [ + 'https://image-sync-url.test/1', + 'https://image-sync-url.test/2', + 'https://image-sync-url.test/3' + ] + } + }; + + const iframeSyncResponse = { + body: { + userSyncURL: 'https://iframe-sync-url.test' + } + }; + + it('should register all img urls from the response', function() { + const syncs = spec.getUserSyncs({ pixelEnabled: true }, [imageSyncResponse]); + expect(syncs).to.deep.equal([ + { + type: 'image', + url: 'https://image-sync-url.test/1' + }, + { + type: 'image', + url: 'https://image-sync-url.test/2' + }, + { + type: 'image', + url: 'https://image-sync-url.test/3' + } + ]); + }); + + it('should register the iframe url from the response', function() { + const syncs = spec.getUserSyncs({ iframeEnabled: true }, [iframeSyncResponse]); + expect(syncs).to.deep.equal([ + { + type: 'iframe', + url: 'https://iframe-sync-url.test' + } + ]); + }); + + it('should register both image and iframe urls from the responses', function() { + const syncs = spec.getUserSyncs({ pixelEnabled: true, iframeEnabled: true }, [iframeSyncResponse, imageSyncResponse]); + expect(syncs).to.deep.equal([ + { + type: 'iframe', + url: 'https://iframe-sync-url.test' + }, + { + type: 'image', + url: 'https://image-sync-url.test/1' + }, + { + type: 'image', + url: 'https://image-sync-url.test/2' + }, + { + type: 'image', + url: 'https://image-sync-url.test/3' + } + ]); + }); + + it('should handle an empty response', function() { + const syncs = spec.getUserSyncs({ iframeEnabled: true }, []); + expect(syncs).to.deep.equal([]); + }); + + it('should handle when user syncs are disabled', function() { + const syncs = spec.getUserSyncs({ pixelEnabled: false }, [imageSyncResponse]); + expect(syncs).to.deep.equal([]); + }); + }) +}); diff --git a/test/spec/modules/rubiconAnalyticsAdapter_spec.js b/test/spec/modules/rubiconAnalyticsAdapter_spec.js index 0d6cf331e52..0239eff5883 100644 --- a/test/spec/modules/rubiconAnalyticsAdapter_spec.js +++ b/test/spec/modules/rubiconAnalyticsAdapter_spec.js @@ -135,6 +135,31 @@ const BID3 = Object.assign({}, BID, { } }); +const BID4 = Object.assign({}, BID, { + adUnitCode: '/19968336/header-bid-tag1', + bidId: '3bd4ebb1c900e2', + adId: 'fake_ad_id', + requestId: '3bd4ebb1c900e2', + width: 728, + height: 90, + mediaType: 'banner', + cpm: 1.52, + source: 'server', + pbsBidId: 'zzzz-yyyy-xxxx-wwww', + seatBidId: 'aaaa-bbbb-cccc-dddd', + rubiconTargeting: { + 'rpfl_elemid': '/19968336/header-bid-tag1', + 'rpfl_14062': '2_tier0100' + }, + adserverTargeting: { + 'hb_bidder': 'rubicon', + 'hb_adid': '3bd4ebb1c900e2', + 'hb_pb': '1.500', + 'hb_size': '728x90', + 'hb_source': 'server' + } +}); + const floorMinRequest = { 'bidder': 'rubicon', 'params': { @@ -532,7 +557,9 @@ const ANALYTICS_MESSAGE = { 'bidwonStatus': 'success' } ], - 'wrapperName': '10000_fakewrapper_test' + 'wrapper': { + 'name': '10000_fakewrapper_test' + } }; function performStandardAuction(gptEvents) { @@ -634,6 +661,7 @@ describe('rubicon analytics adapter', function () { } }); expect(rubiConf).to.deep.equal({ + analyticsEventDelay: 0, pvid: '12345678', wrapperName: '1001_general', int_type: 'dmpbjs', @@ -652,6 +680,7 @@ describe('rubicon analytics adapter', function () { } }); expect(rubiConf).to.deep.equal({ + analyticsEventDelay: 0, pvid: '12345678', wrapperName: '1001_general', int_type: 'dmpbjs', @@ -672,6 +701,7 @@ describe('rubicon analytics adapter', function () { } }); expect(rubiConf).to.deep.equal({ + analyticsEventDelay: 0, pvid: '12345678', wrapperName: '1001_general', int_type: 'dmpbjs', @@ -847,11 +877,75 @@ describe('rubicon analytics adapter', function () { expect(message.auctions[0].adUnits[1].bids[0].bidResponse.dimensions).to.equal(undefined); }); + it('should pass along adomians correctly', function () { + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + + // 1 adomains + let bidResponse1 = utils.deepClone(MOCK.BID_RESPONSE[0]); + bidResponse1.meta = { + advertiserDomains: ['magnite.com'] + } + + // two adomains + let bidResponse2 = utils.deepClone(MOCK.BID_RESPONSE[1]); + bidResponse2.meta = { + advertiserDomains: ['prebid.org', 'magnite.com'] + } + + // make sure we only pass max 10 adomains + bidResponse2.meta.advertiserDomains = [...bidResponse2.meta.advertiserDomains, ...bidResponse2.meta.advertiserDomains, ...bidResponse2.meta.advertiserDomains, ...bidResponse2.meta.advertiserDomains, ...bidResponse2.meta.advertiserDomains, ...bidResponse2.meta.advertiserDomains, ...bidResponse2.meta.advertiserDomains] + + events.emit(BID_RESPONSE, bidResponse1); + events.emit(BID_RESPONSE, bidResponse2); + events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); + events.emit(AUCTION_END, MOCK.AUCTION_END); + events.emit(SET_TARGETING, MOCK.SET_TARGETING); + events.emit(BID_WON, MOCK.BID_WON[0]); + events.emit(BID_WON, MOCK.BID_WON[1]); + + let message = JSON.parse(server.requests[0].requestBody); + validate(message); + expect(message.auctions[0].adUnits[0].bids[0].bidResponse.adomains).to.deep.equal(['magnite.com']); + expect(message.auctions[0].adUnits[1].bids[0].bidResponse.adomains).to.deep.equal(['prebid.org', 'magnite.com', 'prebid.org', 'magnite.com', 'prebid.org', 'magnite.com', 'prebid.org', 'magnite.com', 'prebid.org', 'magnite.com']); + }); + + it('should NOT pass along adomians correctly when edge cases', function () { + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + + // empty => nothing + let bidResponse1 = utils.deepClone(MOCK.BID_RESPONSE[0]); + bidResponse1.meta = { + advertiserDomains: [] + } + + // not array => nothing + let bidResponse2 = utils.deepClone(MOCK.BID_RESPONSE[1]); + bidResponse2.meta = { + advertiserDomains: 'prebid.org' + } + + events.emit(BID_RESPONSE, bidResponse1); + events.emit(BID_RESPONSE, bidResponse2); + events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); + events.emit(AUCTION_END, MOCK.AUCTION_END); + events.emit(SET_TARGETING, MOCK.SET_TARGETING); + events.emit(BID_WON, MOCK.BID_WON[0]); + events.emit(BID_WON, MOCK.BID_WON[1]); + + let message = JSON.parse(server.requests[0].requestBody); + validate(message); + expect(message.auctions[0].adUnits[0].bids[0].bidResponse.adomains).to.be.undefined; + expect(message.auctions[0].adUnits[1].bids[0].bidResponse.adomains).to.be.undefined; + }); + function performFloorAuction(provider) { let auctionInit = utils.deepClone(MOCK.AUCTION_INIT); auctionInit.bidderRequests[0].bids[0].floorData = { skipped: false, modelVersion: 'someModelName', + modelWeight: 10, modelTimestamp: 1606772895, location: 'setConfig', skipRate: 15, @@ -954,6 +1048,7 @@ describe('rubicon analytics adapter', function () { expect(message.auctions[0].floors).to.deep.equal({ location: 'setConfig', modelName: 'someModelName', + modelWeight: 10, modelTimestamp: 1606772895, skipped: false, enforcement: true, @@ -1000,6 +1095,7 @@ describe('rubicon analytics adapter', function () { expect(message.auctions[0].floors).to.deep.equal({ location: 'setConfig', modelName: 'someModelName', + modelWeight: 10, modelTimestamp: 1606772895, skipped: false, enforcement: true, @@ -1086,6 +1182,34 @@ describe('rubicon analytics adapter', function () { expect(message).to.deep.equal(expectedMessage); }); + it('should convert kvs to strings before sending', function () { + config.setConfig({rubicon: { + fpkvs: { + number: 24, + boolean: false, + string: 'hello', + array: ['one', 2, 'three'], + object: {one: 'two'} + } + }}); + performStandardAuction(); + expect(server.requests.length).to.equal(1); + let request = server.requests[0]; + let message = JSON.parse(request.requestBody); + validate(message); + + let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); + expectedMessage.session.pvid = STUBBED_UUID.slice(0, 8); + expectedMessage.fpkvs = [ + {key: 'number', value: '24'}, + {key: 'boolean', value: 'false'}, + {key: 'string', value: 'hello'}, + {key: 'array', value: 'one,2,three'}, + {key: 'object', value: '[object Object]'} + ] + expect(message).to.deep.equal(expectedMessage); + }); + it('should use the query utm param rubicon kv value and pass updated kv and pvid when defined', function () { sandbox.stub(utils, 'getWindowLocation').returns({'search': '?utm_source=other', 'pbjs_debug': 'true'}); @@ -1326,29 +1450,31 @@ describe('rubicon analytics adapter', function () { }); }); describe('with googletag enabled', function () { - let gptSlot0, gptSlot1, gptEvent0, gptEvent1; + let gptSlot0, gptSlot1; + let gptSlotRenderEnded0, gptSlotRenderEnded1; beforeEach(function () { mockGpt.enable(); gptSlot0 = mockGpt.makeSlot({code: '/19968336/header-bid-tag-0'}); - gptSlot1 = mockGpt.makeSlot({code: '/19968336/header-bid-tag1'}); - gptEvent0 = { + gptSlotRenderEnded0 = { eventName: 'slotRenderEnded', params: { slot: gptSlot0, isEmpty: false, advertiserId: 1111, - creativeId: 2222, - lineItemId: 3333 + sourceAgnosticCreativeId: 2222, + sourceAgnosticLineItemId: 3333 } }; - gptEvent1 = { + + gptSlot1 = mockGpt.makeSlot({code: '/19968336/header-bid-tag1'}); + gptSlotRenderEnded1 = { eventName: 'slotRenderEnded', params: { slot: gptSlot1, isEmpty: false, advertiserId: 4444, - creativeId: 5555, - lineItemId: 6666 + sourceAgnosticCreativeId: 5555, + sourceAgnosticLineItemId: 6666 } }; }); @@ -1358,7 +1484,7 @@ describe('rubicon analytics adapter', function () { }); it('should add necessary gam information if gpt is enabled and slotRender event emmited', function () { - performStandardAuction([gptEvent0, gptEvent1]); + performStandardAuction([gptSlotRenderEnded0, gptSlotRenderEnded1]); expect(server.requests.length).to.equal(1); let request = server.requests[0]; let message = JSON.parse(request.requestBody); @@ -1381,7 +1507,7 @@ describe('rubicon analytics adapter', function () { }); it('should handle empty gam renders', function () { - performStandardAuction([gptEvent0, { + performStandardAuction([gptSlotRenderEnded0, { eventName: 'slotRenderEnded', params: { slot: gptSlot1, @@ -1408,14 +1534,14 @@ describe('rubicon analytics adapter', function () { }); it('should still add gam ids if falsy', function () { - performStandardAuction([gptEvent0, { + performStandardAuction([gptSlotRenderEnded0, { eventName: 'slotRenderEnded', params: { slot: gptSlot1, isEmpty: false, advertiserId: 0, - creativeId: 0, - lineItemId: 0 + sourceAgnosticCreativeId: 0, + sourceAgnosticLineItemId: 0 } }]); expect(server.requests.length).to.equal(1); @@ -1439,14 +1565,86 @@ describe('rubicon analytics adapter', function () { expect(message).to.deep.equal(expectedMessage); }); - it('should handle empty gam renders', function () { - performStandardAuction([gptEvent0, gptEvent1]); + it('should pick backup Ids if no sourceAgnostic available first', function () { + performStandardAuction([gptSlotRenderEnded0, { + eventName: 'slotRenderEnded', + params: { + slot: gptSlot1, + isEmpty: false, + advertiserId: 0, + lineItemId: 1234, + creativeId: 5678 + } + }]); + expect(server.requests.length).to.equal(1); + let request = server.requests[0]; + let message = JSON.parse(request.requestBody); + validate(message); + + let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); + expectedMessage.auctions[0].adUnits[0].gam = { + advertiserId: 1111, + creativeId: 2222, + lineItemId: 3333, + adSlot: '/19968336/header-bid-tag-0' + }; + expectedMessage.auctions[0].adUnits[1].gam = { + advertiserId: 0, + creativeId: 5678, + lineItemId: 1234, + adSlot: '/19968336/header-bid-tag1' + }; + expect(message).to.deep.equal(expectedMessage); + }); + + it('should correctly set adUnit for associated slots', function () { + performStandardAuction([gptSlotRenderEnded0, gptSlotRenderEnded1]); + expect(server.requests.length).to.equal(1); + let request = server.requests[0]; + let message = JSON.parse(request.requestBody); + validate(message); + + let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); + expectedMessage.auctions[0].adUnits[0].gam = { + advertiserId: 1111, + creativeId: 2222, + lineItemId: 3333, + adSlot: '/19968336/header-bid-tag-0' + }; + expectedMessage.auctions[0].adUnits[1].gam = { + advertiserId: 4444, + creativeId: 5555, + lineItemId: 6666, + adSlot: '/19968336/header-bid-tag1' + }; + expect(message).to.deep.equal(expectedMessage); + }); + + it('should send request when waitForGamSlots is present but no bidWons are sent', function () { + config.setConfig({ + rubicon: { + waitForGamSlots: true, + } + }); + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[0]); + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[1]); + events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); + events.emit(AUCTION_END, MOCK.AUCTION_END); + events.emit(SET_TARGETING, MOCK.SET_TARGETING); + + // should send if just slotRenderEnded is emmitted for both + mockGpt.emitEvent(gptSlotRenderEnded0.eventName, gptSlotRenderEnded0.params); + mockGpt.emitEvent(gptSlotRenderEnded1.eventName, gptSlotRenderEnded1.params); + expect(server.requests.length).to.equal(1); let request = server.requests[0]; let message = JSON.parse(request.requestBody); validate(message); let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); + delete expectedMessage.bidsWon; // should not be any of these expectedMessage.auctions[0].adUnits[0].gam = { advertiserId: 1111, creativeId: 2222, @@ -1461,6 +1659,51 @@ describe('rubicon analytics adapter', function () { }; expect(message).to.deep.equal(expectedMessage); }); + + it('should delay the event call depending on analyticsEventDelay config', function () { + config.setConfig({ + rubicon: { + waitForGamSlots: true, + analyticsEventDelay: 2000 + } + }); + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[0]); + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[1]); + events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); + events.emit(AUCTION_END, MOCK.AUCTION_END); + events.emit(SET_TARGETING, MOCK.SET_TARGETING); + + // should send if just slotRenderEnded is emmitted for both + mockGpt.emitEvent(gptSlotRenderEnded0.eventName, gptSlotRenderEnded0.params); + mockGpt.emitEvent(gptSlotRenderEnded1.eventName, gptSlotRenderEnded1.params); + + // Should not be sent until delay + expect(server.requests.length).to.equal(0); + + // tick the clock and it should fire + clock.tick(2000); + + expect(server.requests.length).to.equal(1); + let request = server.requests[0]; + let message = JSON.parse(request.requestBody); + validate(message); + let expectedGam0 = { + advertiserId: 1111, + creativeId: 2222, + lineItemId: 3333, + adSlot: '/19968336/header-bid-tag-0' + }; + let expectedGam1 = { + advertiserId: 4444, + creativeId: 5555, + lineItemId: 6666, + adSlot: '/19968336/header-bid-tag1' + }; + expect(expectedGam0).to.deep.equal(message.auctions[0].adUnits[0].gam); + expect(expectedGam1).to.deep.equal(message.auctions[0].adUnits[1].gam); + }); }); it('should correctly overwrite bidId if seatBidId is on the bidResponse', function () { @@ -1497,6 +1740,39 @@ describe('rubicon analytics adapter', function () { expect(message.bidsWon[0].bidId).to.equal('abc-123-do-re-me'); }); + it('should correctly overwrite bidId if pbsBidId is on the bidResponse', function () { + // Only want one bid request in our mock auction + let bidRequested = utils.deepClone(MOCK.BID_REQUESTED); + bidRequested.bids.shift(); + let auctionInit = utils.deepClone(MOCK.AUCTION_INIT); + auctionInit.adUnits.shift(); + + // clone the mock bidResponse and duplicate + let seatBidResponse = utils.deepClone(BID4); + + const setTargeting = { + [seatBidResponse.adUnitCode]: seatBidResponse.adserverTargeting + }; + + const bidWon = Object.assign({}, seatBidResponse, { + 'status': 'rendered' + }); + + // spoof the auction with just our duplicates + events.emit(AUCTION_INIT, auctionInit); + events.emit(BID_REQUESTED, bidRequested); + events.emit(BID_RESPONSE, seatBidResponse); + events.emit(AUCTION_END, MOCK.AUCTION_END); + events.emit(SET_TARGETING, setTargeting); + events.emit(BID_WON, bidWon); + + let message = JSON.parse(server.requests[0].requestBody); + + validate(message); + expect(message.auctions[0].adUnits[0].bids[0].bidId).to.equal('zzzz-yyyy-xxxx-wwww'); + expect(message.bidsWon[0].bidId).to.equal('zzzz-yyyy-xxxx-wwww'); + }); + it('should pick the highest cpm bid if more than one bid per bidRequestId', function () { // Only want one bid request in our mock auction let bidRequested = utils.deepClone(MOCK.BID_REQUESTED); @@ -1594,6 +1870,61 @@ describe('rubicon analytics adapter', function () { expect(timedOutBid).to.not.have.property('bidResponse'); }); + it('should pass aupName as pattern', function () { + let bidRequest = utils.deepClone(MOCK.BID_REQUESTED); + bidRequest.bids[0].fpd = { + context: { + aupName: '1234/mycoolsite/*&gpt_leaderboard&deviceType=mobile' + } + }; + bidRequest.bids[1].fpd = { + context: { + aupName: '1234/mycoolsite/*&gpt_skyscraper&deviceType=mobile' + } + }; + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, bidRequest); + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[0]); + events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); + events.emit(AUCTION_END, MOCK.AUCTION_END); + events.emit(SET_TARGETING, MOCK.SET_TARGETING); + + clock.tick(SEND_TIMEOUT + 1000); + + expect(server.requests.length).to.equal(1); + + let message = JSON.parse(server.requests[0].requestBody); + validate(message); + expect(message.auctions[0].adUnits[0].pattern).to.equal('1234/mycoolsite/*&gpt_leaderboard&deviceType=mobile'); + expect(message.auctions[0].adUnits[1].pattern).to.equal('1234/mycoolsite/*&gpt_skyscraper&deviceType=mobile'); + }); + + it('should pass bidderDetail for multibid auctions', function () { + let bidResponse = utils.deepClone(MOCK.BID_RESPONSE[1]); + bidResponse.targetingBidder = 'rubi2'; + bidResponse.originalRequestId = bidResponse.requestId; + bidResponse.requestId = '1a2b3c4d5e6f7g8h9'; + + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[0]); + events.emit(BID_RESPONSE, bidResponse); + events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); + events.emit(AUCTION_END, MOCK.AUCTION_END); + events.emit(SET_TARGETING, MOCK.SET_TARGETING); + events.emit(BID_WON, MOCK.BID_WON[0]); + + clock.tick(SEND_TIMEOUT + 1000); + + expect(server.requests.length).to.equal(1); + + let message = JSON.parse(server.requests[0].requestBody); + validate(message); + + expect(message.auctions[0].adUnits[1].bids[1].bidder).to.equal('rubicon'); + expect(message.auctions[0].adUnits[1].bids[1].bidderDetail).to.equal('rubi2'); + }); + it('should successfully convert bid price to USD in parseBidResponse', function () { // Set the rates setConfig({ @@ -1647,6 +1978,36 @@ describe('rubicon analytics adapter', function () { }); }); + describe('wrapper details passed in', () => { + it('should correctly pass in the wrapper details if provided', () => { + config.setConfig({rubicon: { + wrapperName: '1001_wrapperName_exp.4', + wrapperFamily: '1001_wrapperName', + rule_name: 'na-mobile' + }}); + + rubiconAnalyticsAdapter.enableAnalytics({ + options: { + endpoint: '//localhost:9999/event', + accountId: 1001 + } + }); + + performStandardAuction(); + + expect(server.requests.length).to.equal(1); + const request = server.requests[0]; + const message = JSON.parse(request.requestBody); + expect(message.wrapper).to.deep.equal({ + name: '1001_wrapperName_exp.4', + family: '1001_wrapperName', + rule: 'na-mobile' + }); + + rubiconAnalyticsAdapter.disableAnalytics(); + }); + }); + it('getHostNameFromReferer correctly grabs hostname from an input URL', function () { let inputUrl = 'https://www.prebid.org/some/path?pbjs_debug=true'; expect(getHostNameFromReferer(inputUrl)).to.equal('www.prebid.org'); diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index 6944034a787..f53cde3c8ab 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -488,6 +488,17 @@ describe('the rubicon adapter', function () { data = parseQuery(request.data); expect(data.rp_hard_floor).to.equal('1.23'); }); + + it('should send rp_maxbids to AE if rubicon multibid config exists', function () { + var multibidRequest = utils.deepClone(bidderRequest); + multibidRequest.bidLimit = 5; + + let [request] = spec.buildRequests(multibidRequest.bids, multibidRequest); + let data = parseQuery(request.data); + + expect(data['rp_maxbids']).to.equal('5'); + }); + it('should not send p_pos to AE if not params.position specified', function () { var noposRequest = utils.deepClone(bidderRequest); delete noposRequest.bids[0].params.position; @@ -554,7 +565,7 @@ describe('the rubicon adapter', function () { sandbox.stub(Math, 'random').callsFake(() => 0.1); let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - const referenceOrdering = ['account_id', 'site_id', 'zone_id', 'size_id', 'alt_size_ids', 'p_pos', 'rf', 'p_geo.latitude', 'p_geo.longitude', 'kw', 'tg_v.ucat', 'tg_v.lastsearch', 'tg_v.likes', 'tg_i.rating', 'tg_i.prodtype', 'tk_flint', 'x_source.tid', 'x_source.pchain', 'p_screen_res', 'rp_secure', 'tk_user_key', 'tg_fl.eid', 'slots', 'rand']; + const referenceOrdering = ['account_id', 'site_id', 'zone_id', 'size_id', 'alt_size_ids', 'p_pos', 'rf', 'p_geo.latitude', 'p_geo.longitude', 'kw', 'tg_v.ucat', 'tg_v.lastsearch', 'tg_v.likes', 'tg_i.rating', 'tg_i.prodtype', 'tk_flint', 'x_source.tid', 'x_source.pchain', 'p_screen_res', 'rp_secure', 'tk_user_key', 'tg_fl.eid', 'rp_maxbids', 'slots', 'rand']; request.data.split('&').forEach((item, i) => { expect(item.split('=')[0]).to.equal(referenceOrdering[i]); @@ -826,22 +837,40 @@ describe('the rubicon adapter', function () { }); }); - it('should use first party data from getConfig over the bid params, if present', () => { - const context = { - keywords: ['e', 'f'], - rating: '4-star' + it('should merge first party data from getConfig with the bid params, if present', () => { + const site = { + keywords: 'e,f', + rating: '4-star', + ext: { + data: { + page: 'home' + } + } }; const user = { - keywords: ['d'], + data: [{ + 'name': 'www.dataprovider1.com', + 'ext': { 'taxonomyname': 'IAB Audience Taxonomy' }, + 'segment': [ + { 'id': '687' }, + { 'id': '123' } + ] + }], gender: 'M', yob: '1984', - geo: {country: 'ca'} + geo: {country: 'ca'}, + keywords: 'd', + ext: { + data: { + age: 40 + } + } }; sandbox.stub(config, 'getConfig').callsFake(key => { const config = { - fpd: { - context, + ortb2: { + site, user } }; @@ -849,14 +878,16 @@ describe('the rubicon adapter', function () { }); const expectedQuery = { - 'kw': 'a,b,c,d,e,f', + 'kw': 'a,b,c,d', 'tg_v.ucat': 'new', 'tg_v.lastsearch': 'iphone', 'tg_v.likes': 'sports,video games', 'tg_v.gender': 'M', + 'tg_v.age': '40', + 'tg_v.iab': '687,123', 'tg_v.yob': '1984', - 'tg_v.geo': '{"country":"ca"}', - 'tg_i.rating': '4-star', + 'tg_i.rating': '4-star,5-star', + 'tg_i.page': 'home', 'tg_i.prodtype': 'tech,mobile', }; @@ -1280,12 +1311,12 @@ describe('the rubicon adapter', function () { describe('Prebid AdSlot', function () { beforeEach(function () { // enforce that the bid at 0 does not have a 'context' property - if (bidderRequest.bids[0].hasOwnProperty('fpd')) { - delete bidderRequest.bids[0].fpd; + if (bidderRequest.bids[0].hasOwnProperty('ortb2Imp')) { + delete bidderRequest.bids[0].ortb2Imp; } }); - it('should not send \"tg_i.pbadslot’\" if \"fpd.context\" object is not valid', function () { + it('should not send \"tg_i.pbadslot’\" if \"ortb2Imp.ext.data\" object is not valid', function () { const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); const data = parseQuery(request.data); @@ -1293,8 +1324,8 @@ describe('the rubicon adapter', function () { expect(data).to.not.have.property('tg_i.pbadslot’'); }); - it('should not send \"tg_i.pbadslot’\" if \"fpd.context.pbAdSlot\" is undefined', function () { - bidderRequest.bids[0].fpd = {}; + it('should not send \"tg_i.pbadslot’\" if \"ortb2Imp.ext.data.pbadslot\" is undefined', function () { + bidderRequest.bids[0].ortb2Imp = {}; const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); const data = parseQuery(request.data); @@ -1303,10 +1334,12 @@ describe('the rubicon adapter', function () { expect(data).to.not.have.property('tg_i.pbadslot’'); }); - it('should not send \"tg_i.pbadslot’\" if \"fpd.context.pbAdSlot\" value is an empty string', function () { - bidderRequest.bids[0].fpd = { - context: { - pbAdSlot: '' + it('should not send \"tg_i.pbadslot’\" if \"ortb2Imp.ext.data.pbadslot\" value is an empty string', function () { + bidderRequest.bids[0].ortb2Imp = { + ext: { + data: { + pbadslot: '' + } } }; @@ -1317,10 +1350,12 @@ describe('the rubicon adapter', function () { expect(data).to.not.have.property('tg_i.pbadslot'); }); - it('should send \"tg_i.pbadslot\" if \"fpd.context.pbAdSlot\" value is a valid string', function () { - bidderRequest.bids[0].fpd = { - context: { - pbAdSlot: 'abc' + it('should send \"tg_i.pbadslot\" if \"ortb2Imp.ext.data.pbadslot\" value is a valid string', function () { + bidderRequest.bids[0].ortb2Imp = { + ext: { + data: { + pbadslot: 'abc' + } } } @@ -1332,12 +1367,14 @@ describe('the rubicon adapter', function () { expect(data['tg_i.pbadslot']).to.equal('abc'); }); - it('should send \"tg_i.pbadslot\" if \"fpd.context.pbAdSlot\" value is a valid string, but all leading slash characters should be removed', function () { - bidderRequest.bids[0].fpd = { - context: { - pbAdSlot: '/a/b/c' + it('should send \"tg_i.pbadslot\" if \"ortb2Imp.ext.data.pbadslot\" value is a valid string, but all leading slash characters should be removed', function () { + bidderRequest.bids[0].ortb2Imp = { + ext: { + data: { + pbadslot: '/a/b/c' + } } - }; + } const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); const data = parseQuery(request.data); @@ -1351,12 +1388,12 @@ describe('the rubicon adapter', function () { describe('GAM ad unit', function () { beforeEach(function () { // enforce that the bid at 0 does not have a 'context' property - if (bidderRequest.bids[0].hasOwnProperty('fpd')) { - delete bidderRequest.bids[0].fpd; + if (bidderRequest.bids[0].hasOwnProperty('ortb2Imp')) { + delete bidderRequest.bids[0].ortb2Imp; } }); - it('should not send \"tg_i.dfp_ad_unit_code’\" if \"fpd.context\" object is not valid', function () { + it('should not send \"tg_i.dfp_ad_unit_code’\" if \"ortb2Imp.ext.data\" object is not valid', function () { const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); const data = parseQuery(request.data); @@ -1364,8 +1401,8 @@ describe('the rubicon adapter', function () { expect(data).to.not.have.property('tg_i.dfp_ad_unit_code’'); }); - it('should not send \"tg_i.dfp_ad_unit_code’\" if \"fpd.context.adServer.adSlot\" is undefined', function () { - bidderRequest.bids[0].fpd = {}; + it('should not send \"tg_i.dfp_ad_unit_code’\" if \"ortb2Imp.ext.data.adServer.adslot\" is undefined', function () { + bidderRequest.bids[0].ortb2Imp = {}; const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); const data = parseQuery(request.data); @@ -1374,11 +1411,13 @@ describe('the rubicon adapter', function () { expect(data).to.not.have.property('tg_i.dfp_ad_unit_code’'); }); - it('should not send \"tg_i.dfp_ad_unit_code’\" if \"fpd.context.adServer.adSlot\" value is an empty string', function () { - bidderRequest.bids[0].fpd = { - context: { - adServer: { - adSlot: '' + it('should not send \"tg_i.dfp_ad_unit_code’\" if \"ortb2Imp.ext.data.adServer.adslot\" value is an empty string', function () { + bidderRequest.bids[0].ortb2Imp = { + ext: { + data: { + adserver: { + adslot: '' + } } } }; @@ -1390,11 +1429,13 @@ describe('the rubicon adapter', function () { expect(data).to.not.have.property('tg_i.dfp_ad_unit_code'); }); - it('should send \"tg_i.dfp_ad_unit_code\" if \"fpd.context.adServer.adSlot\" value is a valid string', function () { - bidderRequest.bids[0].fpd = { - context: { - adServer: { - adSlot: 'abc' + it('should send \"tg_i.dfp_ad_unit_code\" if \"ortb2Imp.ext.data.adServer.adslot\" value is a valid string', function () { + bidderRequest.bids[0].ortb2Imp = { + ext: { + data: { + adserver: { + adslot: 'abc' + } } } } @@ -1407,11 +1448,13 @@ describe('the rubicon adapter', function () { expect(data['tg_i.dfp_ad_unit_code']).to.equal('abc'); }); - it('should send \"tg_i.dfp_ad_unit_code\" if \"fpd.context.adServer.adSlot\" value is a valid string, but all leading slash characters should be removed', function () { - bidderRequest.bids[0].fpd = { - context: { - adServer: { - adSlot: 'a/b/c' + it('should send \"tg_i.dfp_ad_unit_code\" if \"ortb2Imp.ext.data.adServer.adslot\" value is a valid string, but all leading slash characters should be removed', function () { + bidderRequest.bids[0].ortb2Imp = { + ext: { + data: { + adserver: { + adslot: 'a/b/c' + } } } }; @@ -1466,7 +1509,7 @@ describe('the rubicon adapter', function () { // LiveIntent should exist expect(post.user.ext.eids[0].source).to.equal('liveintent.com'); expect(post.user.ext.eids[0].uids[0].id).to.equal('0000-1111-2222-3333'); - expect(post.user.ext.eids[0].uids[0].atype).to.equal(1); + expect(post.user.ext.eids[0].uids[0].atype).to.equal(3); expect(post.user.ext.eids[0]).to.have.property('ext').that.is.an('object'); expect(post.user.ext.eids[0].ext).to.have.property('segments').that.is.an('array'); expect(post.user.ext.eids[0].ext.segments[0]).to.equal('segA'); @@ -1474,7 +1517,7 @@ describe('the rubicon adapter', function () { // LiveRamp should exist expect(post.user.ext.eids[1].source).to.equal('liveramp.com'); expect(post.user.ext.eids[1].uids[0].id).to.equal('1111-2222-3333-4444'); - expect(post.user.ext.eids[1].uids[0].atype).to.equal(1); + expect(post.user.ext.eids[1].uids[0].atype).to.equal(3); // SharedId should exist expect(post.user.ext.eids[2].source).to.equal('sharedid.org'); expect(post.user.ext.eids[2].uids[0].id).to.equal('1111'); @@ -1594,6 +1637,33 @@ describe('the rubicon adapter', function () { expect(request.data.imp[0].ext).to.not.haveOwnProperty('rubicon'); }); + it('should add multibid configuration to PBS Request', function () { + createVideoBidderRequest(); + + const multibid = [{ + bidder: 'bidderA', + maxBids: 2 + }, { + bidder: 'bidderB', + maxBids: 2 + }]; + const expected = [{ + bidder: 'bidderA', + maxbids: 2 + }, { + bidder: 'bidderB', + maxbids: 2 + }]; + + config.setConfig({multibid: multibid}); + + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + + // should have the aliases object sent to PBS + expect(request.data.ext.prebid).to.haveOwnProperty('multibid'); + expect(request.data.ext.prebid.multibid).to.deep.equal(expected); + }); + it('should send video exp param correctly when set', function () { createVideoBidderRequest(); config.setConfig({s2sConfig: {defaultTtl: 600}}); @@ -1751,16 +1821,6 @@ describe('the rubicon adapter', function () { delete bidderRequest.bids[0].mediaTypes.video.protocols; expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(false); - // change maxduration to an string, no good - createVideoBidderRequest(); - bidderRequest.bids[0].mediaTypes.video.maxduration = 'string'; - expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(false); - - // delete maxduration, no good - createVideoBidderRequest(); - delete bidderRequest.bids[0].mediaTypes.video.maxduration; - expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(false); - // change linearity to an string, no good createVideoBidderRequest(); bidderRequest.bids[0].mediaTypes.video.linearity = 'string'; @@ -1864,39 +1924,58 @@ describe('the rubicon adapter', function () { it('should include first party data', () => { createVideoBidderRequest(); - const context = { - keywords: ['e', 'f'], - rating: '4-star' + const site = { + ext: { + data: { + page: 'home' + } + }, + content: { + data: [{foo: 'bar'}] + }, + keywords: 'e,f', + rating: '4-star', + data: [{foo: 'bar'}] }; const user = { - keywords: ['d'], + ext: { + data: { + age: 31 + } + }, + keywords: 'd', gender: 'M', yob: '1984', - geo: {country: 'ca'} + geo: {country: 'ca'}, + data: [{foo: 'bar'}] }; sandbox.stub(config, 'getConfig').callsFake(key => { const config = { - fpd: { - context, + ortb2: { + site, user } }; return utils.deepAccess(config, key); }); - const expected = [{ - bidders: ['rubicon'], - config: { - fpd: { - site: Object.assign({}, bidderRequest.bids[0].params.inventory, context), - user: Object.assign({}, bidderRequest.bids[0].params.visitor, user) - } - } - }]; - const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(request.data.ext.prebid.bidderconfig).to.deep.equal(expected); + + const expected = { + site: Object.assign({}, site, {keywords: bidderRequest.bids[0].params.keywords.join(',')}), + user: Object.assign({}, user), + siteData: Object.assign({}, site.ext.data, bidderRequest.bids[0].params.inventory), + userData: Object.assign({}, user.ext.data, bidderRequest.bids[0].params.visitor), + }; + + delete request.data.site.page; + delete request.data.site.content.language; + + expect(request.data.site.keywords).to.deep.equal('a,b,c'); + expect(request.data.user.keywords).to.deep.equal('d'); + expect(request.data.site.ext.data).to.deep.equal(expected.siteData); + expect(request.data.user.ext.data).to.deep.equal(expected.userData); }); it('should include storedAuctionResponse in video bid request', function () { @@ -1915,29 +1994,33 @@ describe('the rubicon adapter', function () { expect(request.data.imp[0].ext.prebid.storedauctionresponse.id).to.equal('11111'); }); - it('should include pbAdSlot in bid request', function () { + it('should include pbadslot in bid request', function () { createVideoBidderRequest(); - bidderRequest.bids[0].fpd = { - context: { - pbAdSlot: '1234567890' + bidderRequest.bids[0].ortb2Imp = { + ext: { + data: { + pbadslot: '1234567890' + } } - }; + } sandbox.stub(Date, 'now').callsFake(() => bidderRequest.auctionStart + 100 ); const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(request.data.imp[0].ext.context.data.pbadslot).to.equal('1234567890'); + expect(request.data.imp[0].ext.data.pbadslot).to.equal('1234567890'); }); it('should include GAM ad unit in bid request', function () { createVideoBidderRequest(); - bidderRequest.bids[0].fpd = { - context: { - adserver: { - adSlot: '1234567890', - name: 'adServerName1' + bidderRequest.bids[0].ortb2Imp = { + ext: { + data: { + adserver: { + adslot: '1234567890', + name: 'adServerName1' + } } } }; @@ -1947,8 +2030,8 @@ describe('the rubicon adapter', function () { ); const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(request.data.imp[0].ext.context.data.adserver.adslot).to.equal('1234567890'); - expect(request.data.imp[0].ext.context.data.adserver.name).to.equal('adServerName1'); + expect(request.data.imp[0].ext.data.adserver.adslot).to.equal('1234567890'); + expect(request.data.imp[0].ext.data.adserver.name).to.equal('adServerName1'); }); it('should use the integration type provided in the config instead of the default', () => { @@ -2079,7 +2162,7 @@ describe('the rubicon adapter', function () { it('should not fail if keywords param is not an array', function () { bidderRequest.bids[0].params.keywords = 'a,b,c'; const slotParams = spec.createSlotParams(bidderRequest.bids[0], bidderRequest); - expect(slotParams.kw).to.equal(''); + expect(slotParams.kw).to.equal('a,b,c'); }); }); @@ -2501,6 +2584,146 @@ describe('the rubicon adapter', function () { expect(bids[0].cpm).to.be.equal(0); }); + it('should create bids with matching requestIds if imp id matches', function () { + let bidRequests = [{ + 'bidder': 'rubicon', + 'params': { + 'accountId': 1001, + 'siteId': 12345, + 'zoneId': 67890, + 'floor': null + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250]] + } + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': '404a7b28-f276-41cc-a5cf-c1d3dc5671f9', + 'sizes': [[300, 250]], + 'bidId': '557ba307cef098', + 'bidderRequestId': '46a00704ffeb7', + 'auctionId': '3fdc6494-da94-44a0-a292-b55a90b08b2c', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0, + 'startTime': 1615412098213 + }, { + 'bidder': 'rubicon', + 'params': { + 'accountId': 1001, + 'siteId': 12345, + 'zoneId': 67890, + 'floor': null + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250]] + } + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-1', + 'transactionId': '404a7b28-f276-41cc-a5cf-c1d3dc5671f9', + 'sizes': [[300, 250]], + 'bidId': '456gt123jkl098', + 'bidderRequestId': '46a00704ffeb7', + 'auctionId': '3fdc6494-da94-44a0-a292-b55a90b08b2c', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0, + 'startTime': 1615412098213 + }]; + + let response = { + 'status': 'ok', + 'account_id': 14062, + 'site_id': 70608, + 'zone_id': 530022, + 'size_id': 15, + 'alt_size_ids': [ + 43 + ], + 'tracking': '', + 'inventory': {}, + 'ads': [ + { + 'status': 'ok', + 'impression_id': '153dc240-8229-4604-b8f5-256933b9374c', + 'size_id': '15', + 'ad_id': '6', + 'advertiser': 7, + 'network': 8, + 'creative_id': 'crid-9', + 'type': 'script', + 'script': 'alert(\'foo\')', + 'campaign_id': 10, + 'cpm': 0.811, + 'targeting': [ + { + 'key': 'rpfl_14062', + 'values': [ + '15_tier_all_test' + ] + } + ] + }, + { + 'status': 'ok', + 'impression_id': '153dc240-8229-4604-b8f5-256933b9374c', + 'size_id': '15', + 'ad_id': '7', + 'advertiser': 7, + 'network': 8, + 'creative_id': 'crid-9', + 'type': 'script', + 'script': 'alert(\'foo\')', + 'campaign_id': 10, + 'cpm': 0.911, + 'targeting': [ + { + 'key': 'rpfl_14062', + 'values': [ + '43_tier_all_test' + ] + } + ] + }, + { + 'status': 'ok', + 'impression_id': '153dc240-8229-4604-b8f5-256933b9374d', + 'size_id': '43', + 'ad_id': '7', + 'advertiser': 7, + 'network': 8, + 'creative_id': 'crid-9', + 'type': 'script', + 'script': 'alert(\'foo\')', + 'campaign_id': 10, + 'cpm': 1.911, + 'targeting': [ + { + 'key': 'rpfl_14062', + 'values': [ + '43_tier_all_test' + ] + } + ] + } + ] + }; + + config.setConfig({ multibid: [{bidder: 'rubicon', maxbids: 2, targetbiddercodeprefix: 'rubi'}] }); + + let bids = spec.interpretResponse({body: response}, { + bidRequest: bidRequests + }); + + expect(bids).to.be.lengthOf(3); + expect(bids[0].requestId).to.not.equal(bids[1].requestId); + expect(bids[1].requestId).to.equal(bids[2].requestId); + }); + it('should handle an error with no ads returned', function () { let response = { 'status': 'ok', diff --git a/test/spec/modules/s2sTesting_spec.js b/test/spec/modules/s2sTesting_spec.js index 5c7f3004dee..273f6747e52 100644 --- a/test/spec/modules/s2sTesting_spec.js +++ b/test/spec/modules/s2sTesting_spec.js @@ -1,5 +1,4 @@ import s2sTesting from 'modules/s2sTesting.js'; -import { config } from 'src/config.js'; var expect = require('chai').expect; @@ -78,26 +77,16 @@ describe('s2sTesting', function () { beforeEach(function () { // set random number for testing s2sTesting.globalRand = 0.7; - }); - - it('does not work if testing is "false"', function () { - config.setConfig({s2sConfig: { - bidders: ['rubicon'], - testing: false, - bidderControl: {rubicon: {bidSource: {server: 1, client: 1}}} - }}); - expect(s2sTesting.getSourceBidderMap()).to.eql({ - server: [], - client: [] - }); + s2sTesting.bidSource = {}; }); it('sets one client bidder', function () { - config.setConfig({s2sConfig: { + const s2sConfig = { bidders: ['rubicon'], - testing: true, bidderControl: {rubicon: {bidSource: {server: 1, client: 1}}} - }}); + }; + + s2sTesting.calculateBidSources(s2sConfig); expect(s2sTesting.getSourceBidderMap()).to.eql({ server: [], client: ['rubicon'] @@ -105,11 +94,11 @@ describe('s2sTesting', function () { }); it('sets one server bidder', function () { - config.setConfig({s2sConfig: { + const s2sConfig = { bidders: ['rubicon'], - testing: true, bidderControl: {rubicon: {bidSource: {server: 4, client: 1}}} - }}); + } + s2sTesting.calculateBidSources(s2sConfig); expect(s2sTesting.getSourceBidderMap()).to.eql({ server: ['rubicon'], client: [] @@ -117,10 +106,10 @@ describe('s2sTesting', function () { }); it('defaults to server', function () { - config.setConfig({s2sConfig: { + const s2sConfig = { bidders: ['rubicon'], - testing: true - }}); + } + s2sTesting.calculateBidSources(s2sConfig); expect(s2sTesting.getSourceBidderMap()).to.eql({ server: ['rubicon'], client: [] @@ -128,13 +117,14 @@ describe('s2sTesting', function () { }); it('sets two bidders', function () { - config.setConfig({s2sConfig: { + const s2sConfig = { bidders: ['rubicon', 'appnexus'], - testing: true, bidderControl: { rubicon: {bidSource: {server: 3, client: 1}}, appnexus: {bidSource: {server: 1, client: 1}} - }}}); + } + } + s2sTesting.calculateBidSources(s2sConfig); var serverClientBidders = s2sTesting.getSourceBidderMap(); expect(serverClientBidders.server).to.eql(['rubicon']); expect(serverClientBidders.client).to.have.members(['appnexus']); @@ -143,33 +133,37 @@ describe('s2sTesting', function () { it('sends both bidders to same source when weights are the same', function () { s2sTesting.globalRand = 0.01; - config.setConfig({s2sConfig: { + const s2sConfig = { bidders: ['rubicon', 'appnexus'], - testing: true, bidderControl: { rubicon: {bidSource: {server: 1, client: 99}}, appnexus: {bidSource: {server: 1, client: 99}} - }}}); + } + } + s2sTesting.calculateBidSources(s2sConfig); expect(s2sTesting.getSourceBidderMap()).to.eql({ client: ['rubicon', 'appnexus'], server: [] }); + s2sTesting.calculateBidSources(s2sConfig); expect(s2sTesting.getSourceBidderMap()).to.eql({ client: ['rubicon', 'appnexus'], server: [] }); + s2sTesting.calculateBidSources(s2sConfig); expect(s2sTesting.getSourceBidderMap()).to.eql({ client: ['rubicon', 'appnexus'], server: [] }); - config.setConfig({s2sConfig: { + const s2sConfig2 = { bidders: ['rubicon', 'appnexus'], - testing: true, bidderControl: { rubicon: {bidSource: {server: 99, client: 1}}, appnexus: {bidSource: {server: 99, client: 1}} - }}}); + } + } + s2sTesting.calculateBidSources(s2sConfig2); expect(s2sTesting.getSourceBidderMap()).to.eql({ server: ['rubicon', 'appnexus'], client: [] @@ -186,11 +180,12 @@ describe('s2sTesting', function () { }); describe('setting source through adUnits', function () { + const s2sConfig3 = {testing: true}; + beforeEach(function () { - // reset s2sconfig bid sources - config.setConfig({s2sConfig: {testing: true}}); // set random number for testing s2sTesting.globalRand = 0.7; + s2sTesting.bidSource = {}; }); it('sets one bidder source from one adUnit', function () { @@ -199,7 +194,8 @@ describe('s2sTesting', function () { {bidder: 'rubicon', bidSource: {server: 4, client: 1}} ]} ]; - expect(s2sTesting.getSourceBidderMap(adUnits)).to.eql({ + + expect(s2sTesting.getSourceBidderMap(adUnits, [])).to.eql({ server: ['rubicon'], client: [] }); @@ -212,7 +208,7 @@ describe('s2sTesting', function () { {bidder: 'rubicon', bidSource: {server: 1, client: 1}} ]} ]; - expect(s2sTesting.getSourceBidderMap(adUnits)).to.eql({ + expect(s2sTesting.getSourceBidderMap(adUnits, [])).to.eql({ server: [], client: ['rubicon'] }); @@ -227,7 +223,7 @@ describe('s2sTesting', function () { {bidder: 'rubicon', bidSource: {}} ]} ]; - expect(s2sTesting.getSourceBidderMap(adUnits)).to.eql({ + expect(s2sTesting.getSourceBidderMap(adUnits, [])).to.eql({ server: [], client: ['rubicon'] }); @@ -243,7 +239,7 @@ describe('s2sTesting', function () { {bidder: 'appnexus', bidSource: {server: 3, client: 1}} ]} ]; - var serverClientBidders = s2sTesting.getSourceBidderMap(adUnits); + var serverClientBidders = s2sTesting.getSourceBidderMap(adUnits, []); expect(serverClientBidders.server).to.eql(['appnexus']); expect(serverClientBidders.client).to.have.members(['rubicon']); // should have saved the source on the bid @@ -264,7 +260,7 @@ describe('s2sTesting', function () { {bidder: 'bidder3', bidSource: {client: 1}} ]} ]; - var serverClientBidders = s2sTesting.getSourceBidderMap(adUnits); + var serverClientBidders = s2sTesting.getSourceBidderMap(adUnits, []); expect(serverClientBidders.server).to.have.members(['rubicon']); expect(serverClientBidders.server).to.not.have.members(['appnexus', 'bidder3']); expect(serverClientBidders.client).to.have.members(['rubicon', 'appnexus', 'bidder3']); @@ -287,7 +283,7 @@ describe('s2sTesting', function () { {bidder: 'bidder3', calcSource: 'server', bidSource: {client: 1}} ]} ]; - var serverClientBidders = s2sTesting.getSourceBidderMap(adUnits); + var serverClientBidders = s2sTesting.getSourceBidderMap(adUnits, []); expect(serverClientBidders.server).to.have.members(['appnexus', 'bidder3']); expect(serverClientBidders.server).to.not.have.members(['rubicon']); @@ -305,8 +301,6 @@ describe('s2sTesting', function () { describe('setting source through s2sconfig and adUnits', function () { beforeEach(function () { - // reset s2sconfig bid sources - config.setConfig({s2sConfig: {testing: true}}); // set random number for testing s2sTesting.globalRand = 0.7; }); @@ -321,15 +315,15 @@ describe('s2sTesting', function () { ]; // set rubicon: client and appnexus: server - config.setConfig({s2sConfig: { + const s2sConfig = { bidders: ['rubicon', 'appnexus'], testing: true, bidderControl: { rubicon: {bidSource: {server: 2, client: 1}}, appnexus: {bidSource: {server: 1}} } - }}); - + } + s2sTesting.calculateBidSources(s2sConfig); var serverClientBidders = s2sTesting.getSourceBidderMap(adUnits); expect(serverClientBidders.server).to.have.members(['rubicon', 'appnexus']); expect(serverClientBidders.client).to.have.members(['rubicon', 'appnexus']); diff --git a/test/spec/modules/seedtagBidAdapter_spec.js b/test/spec/modules/seedtagBidAdapter_spec.js index 8957bde6bd9..c82c8300d2e 100644 --- a/test/spec/modules/seedtagBidAdapter_spec.js +++ b/test/spec/modules/seedtagBidAdapter_spec.js @@ -1,5 +1,6 @@ import { expect } from 'chai' import { spec, getTimeoutUrl } from 'modules/seedtagBidAdapter.js' +import * as utils from 'src/utils.js' const PUBLISHER_ID = '0000-0000-01' const ADUNIT_ID = '000000' @@ -42,7 +43,7 @@ describe('Seedtag Adapter', function() { } ) } - const placements = ['banner', 'video', 'inImage', 'inScreen'] + const placements = ['banner', 'video', 'inImage', 'inScreen', 'inArticle'] placements.forEach(placement => { it('should be ' + placement, function() { const isBidRequestValid = spec.isBidRequestValid( @@ -53,7 +54,7 @@ describe('Seedtag Adapter', function() { }) }) }) - describe('when video slot has all mandatory params.', function() { + describe('when video slot has all mandatory params', function() { it('should return true, when video mediatype object are correct.', function() { const slotConfig = getSlotConfigs( { @@ -116,7 +117,7 @@ describe('Seedtag Adapter', function() { expect(isBidRequestValid).to.equal(false) }) }) - describe('when video mediaType object is not correct.', function() { + describe('when video mediaType object is not correct', function() { function createVideoSlotconfig(mediaType) { return getSlotConfigs(mediaType, { publisherId: PUBLISHER_ID, @@ -199,6 +200,7 @@ describe('Seedtag Adapter', function() { expect(data.url).to.equal('referer') expect(data.publisherToken).to.equal('0000-0000-01') expect(typeof data.version).to.equal('string') + expect(['fixed', 'mobile', 'unknown'].indexOf(data.connectionType)).to.be.above(-1) }) describe('adPosition param', function() { @@ -301,7 +303,7 @@ describe('Seedtag Adapter', function() { expect(typeof bids).to.equal('object') expect(bids.length).to.equal(0) }) - it('should return a void array, when the server response have not got bids.', function() { + it('should return a void array, when the server response have no bids.', function() { const request = { data: JSON.stringify({}) } const serverResponse = { body: { bids: [] } } const bids = spec.interpretResponse(serverResponse, request) @@ -323,7 +325,8 @@ describe('Seedtag Adapter', function() { width: 728, height: 90, mediaType: 'display', - ttl: 360 + ttl: 360, + nurl: 'testurl.com/nurl' } ], cookieSync: { url: '' } @@ -338,6 +341,7 @@ describe('Seedtag Adapter', function() { expect(bids[0].currency).to.equal('USD') expect(bids[0].netRevenue).to.equal(true) expect(bids[0].ad).to.equal('content') + expect(bids[0].nurl).to.equal('testurl.com/nurl') }) }) describe('the bid is a video', function() { @@ -354,7 +358,8 @@ describe('Seedtag Adapter', function() { width: 728, height: 90, mediaType: 'video', - ttl: 360 + ttl: 360, + nurl: undefined } ], cookieSync: { url: '' } @@ -404,6 +409,14 @@ describe('Seedtag Adapter', function() { }) describe('onTimeout', function () { + beforeEach(function() { + sinon.stub(utils, 'triggerPixel') + }) + + afterEach(function() { + utils.triggerPixel.restore() + }) + it('should return the correct endpoint', function () { const params = { publisherId: '0000', adUnitId: '11111' } const timeoutData = [{ params: [ params ] }]; @@ -415,5 +428,44 @@ describe('Seedtag Adapter', function() { params.adUnitId ) }) + + it('should set the timeout pixel', function() { + const params = { publisherId: '0000', adUnitId: '11111' } + const timeoutData = [{ params: [ params ] }]; + spec.onTimeout(timeoutData) + expect(utils.triggerPixel.calledWith('https://s.seedtag.com/se/hb/timeout?publisherToken=' + + params.publisherId + + '&adUnitId=' + + params.adUnitId)).to.equal(true); + }) + }) + + describe('onBidWon', function () { + beforeEach(function() { + sinon.stub(utils, 'triggerPixel') + }) + + afterEach(function() { + utils.triggerPixel.restore() + }) + + describe('without nurl', function() { + const bid = {} + + it('does not create pixel ', function() { + spec.onBidWon(bid) + expect(utils.triggerPixel.called).to.equal(false); + }) + }) + + describe('with nurl', function () { + const nurl = 'http://seedtag_domain/won' + const bid = { nurl } + + it('creates nurl pixel if bid nurl', function() { + spec.onBidWon({ nurl }) + expect(utils.triggerPixel.calledWith(nurl)).to.equal(true); + }) + }) }) }) diff --git a/test/spec/modules/sharedIdSystem_spec.js b/test/spec/modules/sharedIdSystem_spec.js new file mode 100644 index 00000000000..904d6fe1c78 --- /dev/null +++ b/test/spec/modules/sharedIdSystem_spec.js @@ -0,0 +1,55 @@ +import { + sharedIdSubmodule, +} from 'modules/sharedIdSystem.js'; +import { server } from 'test/mocks/xhr.js'; + +let expect = require('chai').expect; + +describe('SharedId System', function() { + const SHAREDID_RESPONSE = {sharedId: 'testsharedid'}; + + describe('Xhr Requests from getId()', function() { + let callbackSpy = sinon.spy(); + + beforeEach(function() { + callbackSpy.resetHistory(); + }); + + afterEach(function () { + + }); + + it('should call shared id endpoint without consent data and handle a valid response', function () { + let submoduleCallback = sharedIdSubmodule.getId(undefined, undefined).callback; + submoduleCallback(callbackSpy); + + let request = server.requests[0]; + expect(request.url).to.equal('https://id.sharedid.org/id'); + expect(request.withCredentials).to.be.true; + + request.respond(200, {}, JSON.stringify(SHAREDID_RESPONSE)); + + expect(callbackSpy.calledOnce).to.be.true; + expect(callbackSpy.lastCall.lastArg.id).to.equal(SHAREDID_RESPONSE.sharedId); + }); + + it('should call shared id endpoint with consent data and handle a valid response', function () { + let consentData = { + gdprApplies: true, + consentString: 'abc12345234', + }; + + let submoduleCallback = sharedIdSubmodule.getId(undefined, consentData).callback; + submoduleCallback(callbackSpy); + + let request = server.requests[0]; + expect(request.url).to.equal('https://id.sharedid.org/id?gdpr=1&gdpr_consent=abc12345234'); + expect(request.withCredentials).to.be.true; + + request.respond(200, {}, JSON.stringify(SHAREDID_RESPONSE)); + + expect(callbackSpy.calledOnce).to.be.true; + expect(callbackSpy.lastCall.lastArg.id).to.equal(SHAREDID_RESPONSE.sharedId); + }); + }); +}); diff --git a/test/spec/modules/sharethroughBidAdapter_spec.js b/test/spec/modules/sharethroughBidAdapter_spec.js index d45d1e977e6..b3451a09dde 100644 --- a/test/spec/modules/sharethroughBidAdapter_spec.js +++ b/test/spec/modules/sharethroughBidAdapter_spec.js @@ -1,6 +1,7 @@ import { expect } from 'chai'; import { sharethroughAdapterSpec, sharethroughInternal } from 'modules/sharethroughBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; +import * as utils from '../../../src/utils.js'; const spec = newBidder(sharethroughAdapterSpec).getSpec(); const bidRequests = [ @@ -14,7 +15,21 @@ const bidRequests = [ }, userId: { tdid: 'fake-tdid', - pubcid: 'fake-pubcid' + pubcid: 'fake-pubcid', + idl_env: 'fake-identity-link', + id5id: { + uid: 'fake-id5id', + ext: { + linkType: 2 + } + }, + sharedid: { + id: 'fake-sharedid', + third: 'fake-sharedthird' + }, + lipb: { + lipbid: 'fake-lipbid' + } }, crumbs: { pubcid: 'fake-pubcid-in-crumbs-obj' @@ -40,12 +55,32 @@ const bidRequests = [ iframe: true, iframeSize: [500, 500] } - } + }, + { + bidder: 'sharethrough', + bidId: 'bidId4', + sizes: [[700, 400]], + placementCode: 'bar', + params: { + pkey: 'dddd4444', + badv: ['domain1.com', 'domain2.com'] + } + }, + { + bidder: 'sharethrough', + bidId: 'bidId5', + sizes: [[700, 400]], + placementCode: 'bar', + params: { + pkey: 'eeee5555', + bcat: ['IAB1-1', 'IAB1-2'] + } + }, ]; const prebidRequests = [ { - method: 'GET', + method: 'POST', url: 'https://btlr.sharethrough.com/WYu2BXv1/v1', data: { bidId: 'bidId', @@ -57,7 +92,7 @@ const prebidRequests = [ } }, { - method: 'GET', + method: 'POST', url: 'https://btlr.sharethrough.com/WYu2BXv1/v1', data: { bidId: 'bidId', @@ -69,7 +104,7 @@ const prebidRequests = [ } }, { - method: 'GET', + method: 'POST', url: 'https://btlr.sharethrough.com/WYu2BXv1/v1', data: { bidId: 'bidId', @@ -82,7 +117,7 @@ const prebidRequests = [ } }, { - method: 'GET', + method: 'POST', url: 'https://btlr.sharethrough.com/WYu2BXv1/v1', data: { bidId: 'bidId', @@ -94,7 +129,7 @@ const prebidRequests = [ } }, { - method: 'GET', + method: 'POST', url: 'https://btlr.sharethrough.com/WYu2BXv1/v1', data: { bidId: 'bidId', @@ -225,7 +260,7 @@ describe('sharethrough adapter spec', function() { expect(builtBidRequests[0].url).to.eq('https://btlr.sharethrough.com/WYu2BXv1/v1'); expect(builtBidRequests[1].url).to.eq('https://btlr.sharethrough.com/WYu2BXv1/v1'); - expect(builtBidRequests[0].method).to.eq('GET'); + expect(builtBidRequests[0].method).to.eq('POST'); }); it('should set the instant_play_capable parameter correctly based on browser userAgent string', function() { @@ -308,6 +343,15 @@ describe('sharethrough adapter spec', function() { expect(bidRequest.data.pubcid).to.eq('fake-pubcid'); }); + it('should add the pubcid parameter if a bid request contains a value for the Publisher Common ID Module in the' + + ' crumbs object of the bidrequest', function() { + const bidData = utils.deepClone(bidRequests); + delete bidData[0].userId.pubcid; + + const bidRequest = spec.buildRequests(bidData)[0]; + expect(bidRequest.data.pubcid).to.eq('fake-pubcid-in-crumbs-obj'); + }); + it('should add the pubcid parameter if a bid request contains a value for the Publisher Common ID Module in the' + ' crumbs object of the bidrequest', function() { const bidRequest = spec.buildRequests(bidRequests)[0]; @@ -315,6 +359,28 @@ describe('sharethrough adapter spec', function() { expect(bidRequest.data.pubcid).to.eq('fake-pubcid'); }); + it('should add the idluid parameter if a bid request contains a value for Identity Link from Live Ramp', function() { + const bidRequest = spec.buildRequests(bidRequests)[0]; + expect(bidRequest.data.idluid).to.eq('fake-identity-link'); + }); + + it('should add the id5uid parameter if a bid request contains a value for ID5', function() { + const bidRequest = spec.buildRequests(bidRequests)[0]; + expect(bidRequest.data.id5uid.id).to.eq('fake-id5id'); + expect(bidRequest.data.id5uid.linkType).to.eq(2); + }); + + it('should add the shduid parameter if a bid request contains a value for Shared ID', function() { + const bidRequest = spec.buildRequests(bidRequests)[0]; + expect(bidRequest.data.shduid.id).to.eq('fake-sharedid'); + expect(bidRequest.data.shduid.third).to.eq('fake-sharedthird'); + }); + + it('should add the liuid parameter if a bid request contains a value for LiveIntent ID', function() { + const bidRequest = spec.buildRequests(bidRequests)[0]; + expect(bidRequest.data.liuid).to.eq('fake-lipbid'); + }); + it('should add Sharethrough specific parameters', function() { const builtBidRequests = spec.buildRequests(bidRequests); expect(builtBidRequests[0]).to.deep.include({ @@ -346,6 +412,18 @@ describe('sharethrough adapter spec', function() { expect(builtBidRequest.data.schain).to.eq(JSON.stringify(bidRequest.schain)); }); + it('should add badv if provided', () => { + const builtBidRequest = spec.buildRequests([bidRequests[3]])[0]; + + expect(builtBidRequest.data.badv).to.have.members(['domain1.com', 'domain2.com']) + }); + + it('should add bcat if provided', () => { + const builtBidRequest = spec.buildRequests([bidRequests[4]])[0]; + + expect(builtBidRequest.data.bcat).to.have.members(['IAB1-1', 'IAB1-2']) + }); + it('should not add a supply chain parameter if schain is missing', function() { const bidRequest = spec.buildRequests(bidRequests)[0]; expect(bidRequest.data).to.not.include.any.keys('schain'); diff --git a/test/spec/modules/smaatoBidAdapter_spec.js b/test/spec/modules/smaatoBidAdapter_spec.js index 6af0a855800..1bc77fc9572 100644 --- a/test/spec/modules/smaatoBidAdapter_spec.js +++ b/test/spec/modules/smaatoBidAdapter_spec.js @@ -42,7 +42,7 @@ const ADTYPE_IMG = 'Img'; const ADTYPE_RICHMEDIA = 'Richmedia'; const ADTYPE_VIDEO = 'Video'; -const context = { +const site = { keywords: 'power tools,drills' }; @@ -439,8 +439,8 @@ describe('smaatoBidAdapterTest', () => { it('sends fp data', () => { this.sandbox.stub(config, 'getConfig').callsFake(key => { const config = { - fpd: { - context, + ortb2: { + site, user } }; diff --git a/test/spec/modules/smartadserverBidAdapter_spec.js b/test/spec/modules/smartadserverBidAdapter_spec.js index e3bca240a47..749de43b9af 100644 --- a/test/spec/modules/smartadserverBidAdapter_spec.js +++ b/test/spec/modules/smartadserverBidAdapter_spec.js @@ -115,7 +115,41 @@ describe('Smart bid adapter tests', function () { ttl: 300, adUrl: 'http://awesome.fake.url', ad: '< --- awesome script --- >', - cSyncUrl: 'http://awesome.fake.csync.url' + cSyncUrl: 'http://awesome.fake.csync.url', + isNoAd: false + } + }; + + var BID_RESPONSE_IS_NO_AD = { + body: { + cpm: 12, + width: 300, + height: 250, + creativeId: 'zioeufg', + currency: 'GBP', + isNetCpm: true, + ttl: 300, + adUrl: 'http://awesome.fake.url', + ad: '< --- awesome script --- >', + cSyncUrl: 'http://awesome.fake.csync.url', + isNoAd: true + } + }; + + var BID_RESPONSE_IMAGE_SYNC = { + body: { + cpm: 12, + width: 300, + height: 250, + creativeId: 'zioeufg', + currency: 'GBP', + isNetCpm: true, + ttl: 300, + adUrl: 'http://awesome.fake.url', + ad: '< --- awesome script --- >', + cSyncUrl: 'http://awesome.fake.csync.url', + isNoAd: false, + dspPixels: ['pixelOne', 'pixelTwo', 'pixelThree'] } }; @@ -149,6 +183,18 @@ describe('Smart bid adapter tests', function () { expect(requestContent).to.have.property('ckid').and.to.equal(42); }); + it('Verify parse response with no ad', function () { + const request = spec.buildRequests(DEFAULT_PARAMS); + const bids = spec.interpretResponse(BID_RESPONSE_IS_NO_AD, request[0]); + expect(bids).to.have.lengthOf(0); + + expect(function () { + spec.interpretResponse(BID_RESPONSE_IS_NO_AD, { + data: 'invalid Json' + }) + }).to.not.throw(); + }); + it('Verify parse response', function () { const request = spec.buildRequests(DEFAULT_PARAMS); const bids = spec.interpretResponse(BID_RESPONSE, request[0]); @@ -258,6 +304,27 @@ describe('Smart bid adapter tests', function () { expect(syncs).to.have.lengthOf(0); }); + it('Verifies user sync using dspPixels', function () { + var syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: true + }, [BID_RESPONSE_IMAGE_SYNC]); + expect(syncs).to.have.lengthOf(3); + expect(syncs[0].type).to.equal('image'); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: false + }, [BID_RESPONSE_IMAGE_SYNC]); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: true + }, []); + expect(syncs).to.have.lengthOf(0); + }); + describe('gdpr tests', function () { afterEach(function () { config.resetConfig(); diff --git a/test/spec/modules/smartrtbBidAdapter_spec.js b/test/spec/modules/smartrtbBidAdapter_spec.js index cb5ceee0870..a7f30bdec6e 100644 --- a/test/spec/modules/smartrtbBidAdapter_spec.js +++ b/test/spec/modules/smartrtbBidAdapter_spec.js @@ -69,9 +69,6 @@ describe('SmartRTBBidAdapter', function () { it('should return a bidder code of smartrtb', function () { expect(spec.code).to.equal('smartrtb') }) - it('should alias smrtb', function () { - expect(spec.aliases.length > 0 && spec.aliases[0] === 'smrtb').to.be.true - }) }) describe('isBidRequestValid', function () { @@ -79,8 +76,8 @@ describe('SmartRTBBidAdapter', function () { expect(spec.isBidRequestValid(bannerRequest)).to.be.true }) - it('should return false if any zone id missing', function () { - expect(spec.isBidRequestValid(Object.assign(bannerRequest, { params: { zoneId: null } }))).to.be.false + it('should return false if any zone id and pub id missing', function () { + expect(spec.isBidRequestValid(Object.assign(bannerRequest, { params: { pubId: null, zoneId: null } }))).to.be.false }) }) diff --git a/test/spec/modules/smartxBidAdapter_spec.js b/test/spec/modules/smartxBidAdapter_spec.js index efc6abcc5fa..3c871c6f88b 100644 --- a/test/spec/modules/smartxBidAdapter_spec.js +++ b/test/spec/modules/smartxBidAdapter_spec.js @@ -421,6 +421,9 @@ describe('The smartx adapter', function () { it('should return an array of bid responses', function () { var responses = spec.interpretResponse(serverResponse, bidderRequestObj); expect(responses).to.be.an('array').with.length(2); + expect(bidderRequestObj).to.be.an('Object'); + expect(bidderRequestObj.bidRequest.bids).to.be.an('array').with.length(2); + expect(responses[0].meta.advertiserDomains[0]).to.equal('abc.com'); expect(responses[0].requestId).to.equal(123); expect(responses[0].currency).to.equal('USD'); expect(responses[0].cpm).to.equal(12); @@ -430,6 +433,7 @@ describe('The smartx adapter', function () { expect(responses[0].mediaType).to.equal('video'); expect(responses[0].width).to.equal(400); expect(responses[0].height).to.equal(300); + expect(responses[1].meta.advertiserDomains[0]).to.equal('def.com'); expect(responses[1].requestId).to.equal(124); expect(responses[1].currency).to.equal('USD'); expect(responses[1].cpm).to.equal(13); @@ -493,7 +497,7 @@ describe('The smartx adapter', function () { }; }); - it('should attempt to insert the EASI script', function () { + it('should attempt to insert the script', function () { var scriptTag; sinon.stub(window.document, 'getElementById').returns({ appendChild: sinon.stub().callsFake(function (script) { @@ -505,7 +509,7 @@ describe('The smartx adapter', function () { responses[0].renderer.render(responses[0]); expect(scriptTag.getAttribute('type')).to.equal('text/javascript'); - expect(scriptTag.getAttribute('src')).to.equal('https://dco.smartclip.net/?plc=7777777'); + expect(scriptTag.getAttribute('src')).to.equal('https://dco.smartclip.net/?plc=7777778'); window.document.getElementById.restore(); }); diff --git a/test/spec/modules/smartyadsBidAdapter_spec.js b/test/spec/modules/smartyadsBidAdapter_spec.js index 2780e88255d..8804050134a 100644 --- a/test/spec/modules/smartyadsBidAdapter_spec.js +++ b/test/spec/modules/smartyadsBidAdapter_spec.js @@ -1,5 +1,6 @@ import {expect} from 'chai'; import {spec} from '../../../modules/smartyadsBidAdapter.js'; +import { config } from '../../../src/config.js'; describe('SmartyadsAdapter', function () { let bid = { @@ -38,9 +39,10 @@ describe('SmartyadsAdapter', function () { it('Returns valid data if array of bids is valid', function () { let data = serverRequest.data; expect(data).to.be.an('object'); - expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements'); + expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements', 'coppa'); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); + expect(data.coppa).to.be.a('number'); expect(data.language).to.be.a('string'); expect(data.secure).to.be.within(0, 1); expect(data.host).to.be.a('string'); @@ -57,6 +59,23 @@ describe('SmartyadsAdapter', function () { expect(data.placements).to.be.an('array').that.is.empty; }); }); + + describe('with COPPA', function() { + beforeEach(function() { + sinon.stub(config, 'getConfig') + .withArgs('coppa') + .returns(true); + }); + afterEach(function() { + config.getConfig.restore(); + }); + + it('should send the Coppa "required" flag set to "1" in the request', function () { + let serverRequest = spec.buildRequests([bid]); + expect(serverRequest.data.coppa).to.equal(1); + }); + }); + describe('interpretResponse', function () { it('Should interpret banner response', function () { const banner = { diff --git a/test/spec/modules/sonobiBidAdapter_spec.js b/test/spec/modules/sonobiBidAdapter_spec.js index 52821072a21..d1ac200394c 100644 --- a/test/spec/modules/sonobiBidAdapter_spec.js +++ b/test/spec/modules/sonobiBidAdapter_spec.js @@ -414,15 +414,92 @@ describe('SonobiBidAdapter', function () { expect(JSON.parse(bidRequests.data.schain)).to.deep.equal(bidRequest[0].schain) }); + it('should return a properly formatted request with eids as a JSON-encoded set of eids', function () { + bidRequest[0].userIdAsEids = [ + { + 'source': 'pubcid.org', + 'uids': [ + { + 'id': '97b1ff9b-6bf1-41fc-95de-acfd33dbb95a', + 'atype': 1 + } + ] + }, + { + 'source': 'sharedid.org', + 'uids': [ + { + 'id': '01ERJ6W40EXJZNQJVJZWASEG7J', + 'atype': 1, + 'ext': { + 'third': '01ERJ6W40EXJZNQJVJZWASEG7J' + } + } + ] + } + ]; + bidRequest[1].userIdAsEids = [ + { + 'source': 'pubcid.org', + 'uids': [ + { + 'id': '97b1ff9b-6bf1-41fc-95de-acfd33dbb95a', + 'atype': 1 + } + ] + }, + { + 'source': 'sharedid.org', + 'uids': [ + { + 'id': '01ERJ6W40EXJZNQJVJZWASEG7J', + 'atype': 1, + 'ext': { + 'third': '01ERJ6W40EXJZNQJVJZWASEG7J' + } + } + ] + } + ]; + const bidRequests = spec.buildRequests(bidRequest, bidderRequests); + expect(bidRequests.url).to.equal('https://apex.go.sonobi.com/trinity.json'); + expect(bidRequests.method).to.equal('GET'); + expect(bidRequests.data.ref).not.to.be.empty; + expect(bidRequests.data.s).not.to.be.empty; + expect(JSON.parse(bidRequests.data.eids)).to.eql([ + { + 'source': 'pubcid.org', + 'uids': [ + { + 'id': '97b1ff9b-6bf1-41fc-95de-acfd33dbb95a', + 'atype': 1 + } + ] + }, + { + 'source': 'sharedid.org', + 'uids': [ + { + 'id': '01ERJ6W40EXJZNQJVJZWASEG7J', + 'atype': 1, + 'ext': { + 'third': '01ERJ6W40EXJZNQJVJZWASEG7J' + } + } + ] + } + ]); + }); + it('should return a properly formatted request with userid as a JSON-encoded set of User ID results', function () { - bidRequest[0].userId = {'pubcid': 'abcd-efg-0101', 'tdid': 'td-abcd-efg-0101'}; - bidRequest[1].userId = {'pubcid': 'abcd-efg-0101', 'tdid': 'td-abcd-efg-0101'}; + bidRequest[0].userId = {'pubcid': 'abcd-efg-0101', 'tdid': 'td-abcd-efg-0101', 'id5id': {'uid': 'ID5-ZHMOrVeUVTUKgrZ-a2YGxeh5eS_pLzHCQGYOEAiTBQ', 'ext': {'linkType': 2}}}; + bidRequest[1].userId = {'pubcid': 'abcd-efg-0101', 'tdid': 'td-abcd-efg-0101', 'id5id': {'uid': 'ID5-ZHMOrVeUVTUKgrZ-a2YGxeh5eS_pLzHCQGYOEAiTBQ', 'ext': {'linkType': 2}}}; const bidRequests = spec.buildRequests(bidRequest, bidderRequests); expect(bidRequests.url).to.equal('https://apex.go.sonobi.com/trinity.json'); expect(bidRequests.method).to.equal('GET'); expect(bidRequests.data.ref).not.to.be.empty; expect(bidRequests.data.s).not.to.be.empty; - expect(JSON.parse(bidRequests.data.userid)).to.eql({'pubcid': 'abcd-efg-0101', 'tdid': 'td-abcd-efg-0101'}); + expect(JSON.parse(bidRequests.data.userid)).to.eql({'pubcid': 'abcd-efg-0101', 'tdid': 'td-abcd-efg-0101', 'id5id': 'ID5-ZHMOrVeUVTUKgrZ-a2YGxeh5eS_pLzHCQGYOEAiTBQ'}); }); it('should return a properly formatted request with userid omitted if there are no userIds', function () { @@ -469,7 +546,7 @@ describe('SonobiBidAdapter', function () { ]; const bidRequests = spec.buildRequests(bRequest, bidderRequests); expect(bidRequests.url).to.equal('https://iad-2-apex.go.sonobi.com/trinity.json'); - }) + }); }); describe('.interpretResponse', function () { diff --git a/test/spec/modules/sovrnBidAdapter_spec.js b/test/spec/modules/sovrnBidAdapter_spec.js index 983ade4dd14..84a0069c0b4 100644 --- a/test/spec/modules/sovrnBidAdapter_spec.js +++ b/test/spec/modules/sovrnBidAdapter_spec.js @@ -4,11 +4,26 @@ import {newBidder} from 'src/adapters/bidderFactory.js'; const ENDPOINT = `https://ap.lijit.com/rtb/bid?src=$$REPO_AND_VERSION$$`; +const adUnitBidRequest = { + 'bidder': 'sovrn', + 'params': { + 'tagid': '403370' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [ + [300, 250], + [300, 600] + ], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', +} + describe('sovrnBidAdapter', function() { const adapter = newBidder(spec); describe('isBidRequestValid', function () { - let bid = { + const bid = { 'bidder': 'sovrn', 'params': { 'tagid': '403370' @@ -276,6 +291,52 @@ describe('sovrnBidAdapter', function() { expect(data.user.ext.tpid[0].uid).to.equal('A_CRITEO_ID') expect(data.user.ext.prebid_criteoid).to.equal('A_CRITEO_ID') }); + + it('should ignore empty segments', function() { + const payload = JSON.parse(request.data) + expect(payload.imp[0].ext).to.be.undefined + }) + + it('should pass the segments param value as trimmed deal ids array', function() { + const segmentsRequests = [{ + 'bidder': 'sovrn', + 'params': { + 'segments': ' test1,test2 ' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [ + [300, 250], + [300, 600] + ], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475' + }] + const request = spec.buildRequests(segmentsRequests, bidderRequest) + const payload = JSON.parse(request.data) + expect(payload.imp[0].ext.deals[0]).to.equal('test1') + expect(payload.imp[0].ext.deals[1]).to.equal('test2') + }) + it('should use the floor provided from the floor module if present', function() { + const floorBid = {...adUnitBidRequest, getFloor: () => ({currency: 'USD', floor: 1.10})} + floorBid.params = { + tagid: 1234, + bidfloor: 2.00 + } + const request = spec.buildRequests([floorBid], bidderRequest) + const payload = JSON.parse(request.data) + expect(payload.imp[0].bidfloor).to.equal(1.10) + }) + it('should use the floor from the param if there is no floor from the floor module', function() { + const floorBid = {...adUnitBidRequest, getFloor: () => ({})} + floorBid.params = { + tagid: 1234, + bidfloor: 2.00 + } + const request = spec.buildRequests([floorBid], bidderRequest) + const payload = JSON.parse(request.data) + expect(payload.imp[0].bidfloor).to.equal(2.00) + }) }); describe('interpretResponse', function () { @@ -361,7 +422,7 @@ describe('sovrnBidAdapter', function() { }); it('should get correct bid response when ttl is set', function () { - response.body.seatbid[0].bid[0].ttl = 480; + response.body.seatbid[0].bid[0].ext = { 'ttl': 480 }; let expectedResponse = [{ 'requestId': '263c448586f5a1', diff --git a/test/spec/modules/spotxBidAdapter_spec.js b/test/spec/modules/spotxBidAdapter_spec.js index 798fb3eec10..873914441aa 100644 --- a/test/spec/modules/spotxBidAdapter_spec.js +++ b/test/spec/modules/spotxBidAdapter_spec.js @@ -204,9 +204,9 @@ describe('the spotx adapter', function () { eids: [{ source: 'id5-sync.com', uids: [{ - id: 'id5id_1' - }], - ext: {} + id: 'id5id_1', + ext: {} + }] }, { source: 'adserver.org', @@ -379,6 +379,32 @@ describe('the spotx adapter', function () { expect(request.data.site.page).to.equal('prebid.js'); }); + + it('should set ext.wrap_response to 0 when cache url is set and ignoreBidderCacheKey is true', function() { + var request; + + var origGetConfig = config.getConfig; + sinon.stub(config, 'getConfig').callsFake(function (key) { + if (key === 'cache') { + return { + url: 'prebidCacheLocation', + ignoreBidderCacheKey: true + }; + } + if (key === 'cache.url') { + return 'prebidCacheLocation'; + } + if (key === 'cache.ignoreBidderCacheKey') { + return true; + } + return origGetConfig.apply(config, arguments); + }); + + request = spec.buildRequests([bid], bidRequestObj)[0]; + + expect(request.data.ext.wrap_response).to.equal(0); + config.getConfig.restore(); + }); }); describe('interpretResponse', function() { @@ -469,7 +495,6 @@ describe('the spotx adapter', function () { expect(responses[0].requestId).to.equal(123); expect(responses[0].ttl).to.equal(360); expect(responses[0].vastUrl).to.equal('https://search.spotxchange.com/ad/vast.html?key=cache123'); - expect(responses[0].videoCacheKey).to.equal('cache123'); expect(responses[0].width).to.equal(400); expect(responses[1].cache_key).to.equal('cache124'); expect(responses[1].channel_id).to.equal(12345); @@ -483,12 +508,11 @@ describe('the spotx adapter', function () { expect(responses[1].requestId).to.equal(124); expect(responses[1].ttl).to.equal(360); expect(responses[1].vastUrl).to.equal('https://search.spotxchange.com/ad/vast.html?key=cache124'); - expect(responses[1].videoCacheKey).to.equal('cache124'); expect(responses[1].width).to.equal(200); }); }); - describe('oustreamRender', function() { + describe('outstreamRender', function() { var serverResponse, bidderRequestObj; beforeEach(function() { @@ -545,7 +569,7 @@ describe('the spotx adapter', function () { it('should attempt to insert the EASI script', function() { var scriptTag; sinon.stub(window.document, 'getElementById').returns({ - appendChild: sinon.stub().callsFake(function(script) { scriptTag = script }) + appendChild: sinon.stub().callsFake(function(script) { scriptTag = script; }) }); var responses = spec.interpretResponse(serverResponse, bidderRequestObj); @@ -573,7 +597,7 @@ describe('the spotx adapter', function () { nodeName: 'IFRAME', contentDocument: { body: { - appendChild: sinon.stub().callsFake(function(script) { scriptTag = script }) + appendChild: sinon.stub().callsFake(function(script) { scriptTag = script; }) } } }); @@ -598,5 +622,42 @@ describe('the spotx adapter', function () { expect(scriptTag.getAttribute('data-spotx_content_height')).to.equal('300'); window.document.getElementById.restore(); }); + + it('should adjust width and height to match slot clientWidth if playersize_auto_adapt is used', function() { + var scriptTag; + sinon.stub(window.document, 'getElementById').returns({ + clientWidth: 200, + appendChild: sinon.stub().callsFake(function(script) { scriptTag = script; }) + }); + var responses = spec.interpretResponse(serverResponse, bidderRequestObj); + + responses[0].renderer.render(responses[0]); + + expect(scriptTag.getAttribute('type')).to.equal('text/javascript'); + expect(scriptTag.getAttribute('src')).to.equal('https://js.spotx.tv/easi/v1/12345.js'); + expect(scriptTag.getAttribute('data-spotx_content_width')).to.equal('200'); + expect(scriptTag.getAttribute('data-spotx_content_height')).to.equal('150'); + window.document.getElementById.restore(); + }); + + it('should use a default 4/3 ratio if playersize_auto_adapt is used and response does not contain width or height', function() { + delete serverResponse.body.seatbid[0].bid[0].w; + delete serverResponse.body.seatbid[0].bid[0].h; + + var scriptTag; + sinon.stub(window.document, 'getElementById').returns({ + clientWidth: 200, + appendChild: sinon.stub().callsFake(function(script) { scriptTag = script; }) + }); + var responses = spec.interpretResponse(serverResponse, bidderRequestObj); + + responses[0].renderer.render(responses[0]); + + expect(scriptTag.getAttribute('type')).to.equal('text/javascript'); + expect(scriptTag.getAttribute('src')).to.equal('https://js.spotx.tv/easi/v1/12345.js'); + expect(scriptTag.getAttribute('data-spotx_content_width')).to.equal('200'); + expect(scriptTag.getAttribute('data-spotx_content_height')).to.equal('150'); + window.document.getElementById.restore(); + }); }); }); diff --git a/test/spec/modules/sspBCAdapter_spec.js b/test/spec/modules/sspBCBidAdapter_spec.js similarity index 78% rename from test/spec/modules/sspBCAdapter_spec.js rename to test/spec/modules/sspBCBidAdapter_spec.js index 29718deb031..efbfa37f6ca 100644 --- a/test/spec/modules/sspBCAdapter_spec.js +++ b/test/spec/modules/sspBCBidAdapter_spec.js @@ -1,8 +1,6 @@ import { assert, expect } from 'chai'; -import { spec } from 'modules/sspBCAdapter.js'; +import { spec } from 'modules/sspBCBidAdapter.js'; import * as utils from 'src/utils.js'; -import * as sinon from 'sinon'; -import * as ajax from 'src/ajax.js'; const BIDDER_CODE = 'sspBC'; const BIDDER_URL = 'https://ssp.wp.pl/bidder/'; @@ -66,6 +64,28 @@ describe('SSPBC adapter', function () { transactionId, } ]; + const bid_OneCode = { + adUnitCode: 'test_wideboard', + bidder: BIDDER_CODE, + mediaTypes: { + banner: { + sizes: [ + [728, 90], + [750, 100], + [750, 200] + ] + } + }, + sizes: [ + [728, 90], + [750, 100], + [750, 200] + ], + auctionId, + bidderRequestId, + bidId: auctionId + '1', + transactionId, + }; const bids_timeouted = [{ adUnitCode: 'test_wideboard', bidder: BIDDER_CODE, @@ -144,6 +164,18 @@ describe('SSPBC adapter', function () { stack: ['https://test.site.pl/'], } }; + const bidRequestOneCode = { + auctionId, + bidderCode: BIDDER_CODE, + bidderRequestId, + bids: [bid_OneCode], + gdprConsent, + refererInfo: { + reachedTop: true, + referer: 'https://test.site.pl/', + stack: ['https://test.site.pl/'], + } + }; const bidRequestTest = { auctionId, bidderCode: BIDDER_CODE, @@ -174,6 +206,8 @@ describe('SSPBC adapter', function () { 'bid': [{ 'id': '3347324c-6889-46d2-a800-ae78a5214c06', 'impid': '003', + 'siteid': '8816', + 'slotid': '003', 'price': 1, 'adid': 'lxHWkB7OnZeso3QiN1N4', 'nurl': '', @@ -190,6 +224,8 @@ describe('SSPBC adapter', function () { 'bid': [{ 'id': '2d766853-ea07-4529-8299-5f0ebadc546a', 'impid': '005', + 'siteid': '8816', + 'slotid': '005', 'price': 2, 'adm': 'AD CODE 2', 'cid': '57744', @@ -210,6 +246,8 @@ describe('SSPBC adapter', function () { 'bid': [{ 'id': '3347324c-6889-46d2-a800-ae78a5214c06', 'impid': '003', + 'siteid': '8816', + 'slotid': '003', 'price': 1, 'adid': 'lxHWkB7OnZeso3QiN1N4', 'nurl': '', @@ -226,20 +264,50 @@ describe('SSPBC adapter', function () { 'cur': 'PLN' } }; + const serverResponseOneCode = { + 'body': { + 'id': auctionId, + 'seatbid': [{ + 'bid': [{ + 'id': '3347324c-6889-46d2-a800-ae78a5214c06', + 'impid': 'bidid-' + auctionId + '1', + 'price': 1, + 'adid': 'lxHWkB7OnZeso3QiN1N4', + 'nurl': '', + 'adm': 'AD CODE 1', + 'adomain': ['adomain.pl'], + 'cid': 'BZ4gAg21T5nNtxlUCDSW', + 'crid': 'lxHWkB7OnZeso3QiN1N4', + 'w': 728, + 'h': 90, + 'ext': { + 'siteid': '8816', + 'slotid': '003', + }, + }], + 'seat': 'dsp1', + 'group': 0 + }], + 'cur': 'PLN' + } + }; const emptyResponse = { 'body': { 'id': auctionId, } } return { + bid_OneCode, bids, bids_test, bids_timeouted, bidRequest, + bidRequestOneCode, bidRequestSingle, bidRequestTest, bidRequestTestNoGDPR, serverResponse, + serverResponseOneCode, serverResponseSingle, emptyResponse }; @@ -257,13 +325,10 @@ describe('SSPBC adapter', function () { const { bids } = prepareTestData(); let bid = bids[0]; - it('should return true when required params found', function () { + it('should always return true whether bid has params (standard) or not (OneCode)', function () { assert(spec.isBidRequestValid(bid)); - }); - - it('should return false when required params are missing', function () { bid.params.id = undefined; - assert.isFalse(spec.isBidRequestValid(bid)); + assert(spec.isBidRequestValid(bid)); }); }); @@ -308,9 +373,10 @@ describe('SSPBC adapter', function () { }); describe('interpretResponse', function () { - const { bids, emptyResponse, serverResponse, serverResponseSingle, bidRequest, bidRequestSingle } = prepareTestData(); + const { bid_OneCode, bids, emptyResponse, serverResponse, serverResponseOneCode, serverResponseSingle, bidRequest, bidRequestOneCode, bidRequestSingle } = prepareTestData(); const request = spec.buildRequests(bids, bidRequest); const requestSingle = spec.buildRequests([bids[0]], bidRequestSingle); + const requestOneCode = spec.buildRequests([bid_OneCode], bidRequestOneCode); it('should handle nobid responses', function () { let result = spec.interpretResponse(emptyResponse, request); @@ -326,6 +392,19 @@ describe('SSPBC adapter', function () { expect(resultSingle[0]).to.have.keys('ad', 'cpm', 'width', 'height', 'bidderCode', 'mediaType', 'meta', 'requestId', 'creativeId', 'currency', 'netRevenue', 'ttl'); }); + it('should create bid from OneCode (parameter-less) request, if response contains siteId', function () { + let resultOneCode = spec.interpretResponse(serverResponseOneCode, requestOneCode); + + expect(resultOneCode.length).to.equal(1); + expect(resultOneCode[0]).to.have.keys('ad', 'cpm', 'width', 'height', 'bidderCode', 'mediaType', 'meta', 'requestId', 'creativeId', 'currency', 'netRevenue', 'ttl'); + }); + + it('should not create bid from OneCode (parameter-less) request, if response does not contain siteId', function () { + let resultOneCodeNoMatch = spec.interpretResponse(serverResponse, requestOneCode); + + expect(resultOneCodeNoMatch.length).to.equal(0); + }); + it('should handle a partial response', function () { let resultPartial = spec.interpretResponse(serverResponseSingle, request); expect(resultPartial.length).to.equal(1); @@ -364,16 +443,16 @@ describe('SSPBC adapter', function () { expect(notificationPayload).to.be.undefined; }); - it('should generate notification with event name and request/site/slot data, if correct bid is provided', function () { + it('should generate notification with event name and request/adUnit data, if correct bid is provided. Should also contain site/slot data as arrays.', function () { const { bids } = prepareTestData(); let bid = bids[0]; - bid.params = [bid.params]; let notificationPayload = spec.onBidWon(bid); expect(notificationPayload).to.have.property('event').that.equals('bidWon'); expect(notificationPayload).to.have.property('requestId').that.equals(bid.auctionId); - expect(notificationPayload).to.have.property('siteId').that.deep.equals([bid.params[0].siteId]); - expect(notificationPayload).to.have.property('slotId').that.deep.equals([bid.params[0].id]); + expect(notificationPayload).to.have.property('adUnit').that.deep.equals([bid.adUnitCode]); + expect(notificationPayload).to.have.property('siteId').that.is.an('array'); + expect(notificationPayload).to.have.property('slotId').that.is.an('array'); }); }); @@ -388,13 +467,11 @@ describe('SSPBC adapter', function () { it('should generate single notification for any number of timeouted bids', function () { const { bids_timeouted } = prepareTestData(); - let notificationPayload = spec.onTimeout(bids_timeouted); expect(notificationPayload).to.have.property('event').that.equals('timeout'); expect(notificationPayload).to.have.property('requestId').that.equals(bids_timeouted[0].auctionId); - expect(notificationPayload).to.have.property('siteId').that.deep.equals([bids_timeouted[0].params[0].siteId]); - expect(notificationPayload).to.have.property('slotId').that.deep.equals([bids_timeouted[0].params[0].id, bids_timeouted[1].params[0].id]); + expect(notificationPayload).to.have.property('adUnit').that.deep.equals([bids_timeouted[0].adUnitCode, bids_timeouted[1].adUnitCode]); }); }); }); diff --git a/test/spec/modules/stroeerCoreBidAdapter_spec.js b/test/spec/modules/stroeerCoreBidAdapter_spec.js index dd02ebb7c8e..4b028d563d3 100644 --- a/test/spec/modules/stroeerCoreBidAdapter_spec.js +++ b/test/spec/modules/stroeerCoreBidAdapter_spec.js @@ -2,6 +2,7 @@ import {assert} from 'chai'; import {spec} from 'modules/stroeerCoreBidAdapter.js'; import * as utils from 'src/utils.js'; import {BANNER, VIDEO} from '../../../src/mediaTypes.js'; +import find from 'core-js-pure/features/array/find.js'; describe('stroeerCore bid adapter', function () { let sandbox; @@ -123,7 +124,7 @@ describe('stroeerCore bid adapter', function () { } }, referrer, - getElementById: id => placementElements.find(el => el.id === id) + getElementById: id => find(placementElements, el => el.id === id) } }; diff --git a/test/spec/modules/sublimeBidAdapter_spec.js b/test/spec/modules/sublimeBidAdapter_spec.js index 008f24730bc..2e1d65f0533 100644 --- a/test/spec/modules/sublimeBidAdapter_spec.js +++ b/test/spec/modules/sublimeBidAdapter_spec.js @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { spec, sendEvent, log, setState, state } from 'modules/sublimeBidAdapter.js'; +import { spec } from 'modules/sublimeBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; let utils = require('src/utils'); @@ -9,6 +9,19 @@ describe('Sublime Adapter', function() { describe('sendEvent', function() { let sandbox; + const triggeredPixelProperties = [ + 't', + 'tse', + 'z', + 'e', + 'src', + 'puid', + 'trId', + 'pbav', + 'pubpbv', + 'device', + 'pubtimeout', + ]; beforeEach(function () { sandbox = sinon.sandbox.create(); @@ -16,8 +29,10 @@ describe('Sublime Adapter', function() { it('should trigger pixel', function () { sandbox.spy(utils, 'triggerPixel'); - sendEvent('test', true); + spec.sendEvent('test'); expect(utils.triggerPixel.called).to.equal(true); + const params = utils.parseUrl(utils.triggerPixel.args[0][0]).search; + expect(Object.keys(params)).to.have.members(triggeredPixelProperties); }); afterEach(function () { @@ -94,8 +109,9 @@ describe('Sublime Adapter', function() { }); it('should contains a request id equals to the bid id', function() { - expect(request[0].data.requestId).to.equal(bidRequests[0].bidId); - expect(request[1].data.requestId).to.equal(bidRequests[1].bidId); + for (let i = 0; i < request.length; i = i + 1) { + expect(JSON.parse(request[i].data).requestId).to.equal(bidRequests[i].bidId); + } }); it('should have an url that contains bid keyword', function() { @@ -126,6 +142,7 @@ describe('Sublime Adapter', function() { describe('interpretResponse', function() { let serverResponse = { 'request_id': '3db3773286ee59', + 'sspname': 'foo', 'cpm': 0.5, 'ad': '', }; @@ -147,9 +164,10 @@ describe('Sublime Adapter', function() { creativeId: 1, dealId: 1, currency: 'USD', + sspname: 'foo', netRevenue: true, ttl: 600, - pbav: '0.6.0', + pbav: '0.7.1', ad: '', }, ]; @@ -160,6 +178,7 @@ describe('Sublime Adapter', function() { it('should get correct default size for 1x1', function() { let serverResponse = { 'requestId': 'xyz654_2', + 'sspname': 'sublime', 'cpm': 0.5, 'ad': '', }; @@ -191,7 +210,8 @@ describe('Sublime Adapter', function() { netRevenue: true, ttl: 600, ad: '', - pbav: '0.6.0', + pbav: '0.7.1', + sspname: 'sublime' }; expect(result[0]).to.deep.equal(expectedResponse); @@ -211,6 +231,7 @@ describe('Sublime Adapter', function() { it('should return bid with default value in response', function () { let serverResponse = { 'requestId': 'xyz654_2', + 'sspname': 'sublime', 'ad': '', }; @@ -238,10 +259,11 @@ describe('Sublime Adapter', function() { creativeId: 1, dealId: 1, currency: 'EUR', + sspname: 'sublime', netRevenue: true, ttl: 600, ad: '', - pbav: '0.6.0', + pbav: '0.7.1', }; expect(result[0]).to.deep.equal(expectedResponse); @@ -279,4 +301,24 @@ describe('Sublime Adapter', function() { }); }); }); + + describe('onBidWon', function() { + let sandbox; + let bid = { foo: 'bar' }; + + beforeEach(function () { + sandbox = sinon.sandbox.create(); + }); + + it('should trigger "bidwon" pixel', function () { + sandbox.spy(utils, 'triggerPixel'); + spec.onBidWon(bid); + const params = utils.parseUrl(utils.triggerPixel.args[0][0]).search; + expect(params.e).to.equal('bidwon'); + }); + + afterEach(function () { + sandbox.restore(); + }); + }) }); diff --git a/test/spec/modules/synacormediaBidAdapter_spec.js b/test/spec/modules/synacormediaBidAdapter_spec.js index dd40e634723..2ead2fb689e 100644 --- a/test/spec/modules/synacormediaBidAdapter_spec.js +++ b/test/spec/modules/synacormediaBidAdapter_spec.js @@ -1,6 +1,6 @@ import { assert, expect } from 'chai'; import { BANNER } from 'src/mediaTypes.js'; -import {config} from 'src/config.js'; +import { config } from 'src/config.js'; import { spec } from 'modules/synacormediaBidAdapter.js'; describe('synacormediaBidAdapter ', function () { @@ -11,12 +11,19 @@ describe('synacormediaBidAdapter ', function () { sizes: [300, 250], params: { seatId: 'prebid', - placementId: '1234' + tagId: '1234' } }; }); it('should return true when params placementId and seatId are truthy', function () { + bid.params.placementId = bid.params.tagId; + delete bid.params.tagId; + assert(spec.isBidRequestValid(bid)); + }); + + it('should return true when params tagId and seatId are truthy', function () { + delete bid.params.placementId; assert(spec.isBidRequestValid(bid)); }); @@ -35,8 +42,9 @@ describe('synacormediaBidAdapter ', function () { assert.isFalse(spec.isBidRequestValid(bid)); }); - it('should return false when placementId param is missing', function () { + it('should return false when both placementId param and tagId param are missing', function () { delete bid.params.placementId; + delete bid.params.tagId; assert.isFalse(spec.isBidRequestValid(bid)); }); @@ -47,13 +55,13 @@ describe('synacormediaBidAdapter ', function () { }); }); - describe('impression type', function() { + describe('impression type', function () { let nonVideoReq = { bidId: '9876abcd', sizes: [[300, 250], [300, 600]], params: { seatId: 'prebid', - placementId: '1234', + tagId: '1234', bidfloor: '0.50' } }; @@ -63,7 +71,7 @@ describe('synacormediaBidAdapter ', function () { sizes: [[300, 250], [300, 600]], params: { seatId: 'prebid', - placementId: '1234', + tagId: '1234', bidfloor: '0.50' }, mediaTypes: { @@ -84,7 +92,7 @@ describe('synacormediaBidAdapter ', function () { sizes: [[640, 480]], params: { seatId: 'prebid', - placementId: '1234', + tagId: '1234', bidfloor: '0.50' }, mediaTypes: { @@ -99,7 +107,7 @@ describe('synacormediaBidAdapter ', function () { } }, }; - it('should return correct impression type video/banner', function() { + it('should return correct impression type video/banner', function () { assert.isFalse(spec.isVideoBid(nonVideoReq)); assert.isFalse(spec.isVideoBid(bannerReq)); assert.isTrue(spec.isVideoBid(videoReq)); @@ -110,7 +118,7 @@ describe('synacormediaBidAdapter ', function () { bidder: 'synacormedia', params: { seatId: 'prebid', - placementId: '1234', + tagId: '1234', video: { minduration: 30 } @@ -118,12 +126,12 @@ describe('synacormediaBidAdapter ', function () { mediaTypes: { video: { context: 'instream', - playerSize: [[ 640, 480 ]] + playerSize: [[640, 480]] } }, adUnitCode: 'video1', transactionId: '93e5def8-29aa-4fe8-bd3a-0298c39f189a', - sizes: [[ 640, 480 ]], + sizes: [[640, 480]], bidId: '2624fabbb078e8', bidderRequestId: '117954d20d7c9c', auctionId: 'defd525f-4f1e-4416-a4cb-ae53be90e706', @@ -141,7 +149,7 @@ describe('synacormediaBidAdapter ', function () { referer: 'https://localhost:9999/test/pages/video.html?pbjs_debug=true', reachedTop: true, numIframes: 0, - stack: [ 'https://localhost:9999/test/pages/video.html?pbjs_debug=true' ] + stack: ['https://localhost:9999/test/pages/video.html?pbjs_debug=true'] }, start: 1553624929700 }; @@ -163,7 +171,7 @@ describe('synacormediaBidAdapter ', function () { sizes: [[300, 250], [300, 600]], params: { seatId: 'prebid', - placementId: '1234', + tagId: '1234', bidfloor: '0.50' } }; @@ -230,7 +238,7 @@ describe('synacormediaBidAdapter ', function () { sizes: [[300, 600]], params: { seatId: validBidRequest.params.seatId, - placementId: '5678', + tagId: '5678', bidfloor: '0.50' } }; @@ -262,7 +270,7 @@ describe('synacormediaBidAdapter ', function () { sizes: [[300, 250]], params: { seatId: 'somethingelse', - placementId: '5678', + tagId: '5678', bidfloor: '0.50' } }; @@ -295,7 +303,7 @@ describe('synacormediaBidAdapter ', function () { sizes: [[300, 250]], params: { seatId: 'prebid', - placementId: '1234', + tagId: '1234', bidfloor: 'abcd' } }; @@ -327,7 +335,7 @@ describe('synacormediaBidAdapter ', function () { sizes: [[300, 250]], params: { seatId: 'prebid', - placementId: '1234' + tagId: '1234' } }; let req = spec.buildRequests([badFloorBidRequest], bidderRequest); @@ -358,7 +366,7 @@ describe('synacormediaBidAdapter ', function () { sizes: [[300, 250]], params: { seatId: 'prebid', - placementId: '1234', + tagId: '1234', pos: 1 } }; @@ -390,7 +398,7 @@ describe('synacormediaBidAdapter ', function () { sizes: [[300, 250]], params: { seatId: 'prebid', - placementId: '1234', + tagId: '1234', } }; let req = spec.buildRequests([newPosBidRequest], bidderRequest); @@ -419,13 +427,13 @@ describe('synacormediaBidAdapter ', function () { expect(spec.buildRequests([validBidRequest], null)).to.be.undefined; }); - it('should return empty impression when there is no valid sizes in bidrequest', function() { + it('should return empty impression when there is no valid sizes in bidrequest', function () { let validBidReqWithoutSize = { bidId: '9876abcd', sizes: [], params: { seatId: 'prebid', - placementId: '1234', + tagId: '1234', bidfloor: '0.50' } }; @@ -435,7 +443,7 @@ describe('synacormediaBidAdapter ', function () { sizes: [[300]], params: { seatId: 'prebid', - placementId: '1234', + tagId: '1234', bidfloor: '0.50' } }; @@ -457,7 +465,7 @@ describe('synacormediaBidAdapter ', function () { bidder: 'synacormedia', params: { seatId: 'prebid', - placementId: '1234', + tagId: '1234', video: { minduration: 30, maxduration: 45, @@ -472,12 +480,12 @@ describe('synacormediaBidAdapter ', function () { mediaTypes: { video: { context: 'instream', - playerSize: [[ 640, 480 ]] + playerSize: [[640, 480]] } }, adUnitCode: 'video1', transactionId: '93e5def8-29aa-4fe8-bd3a-0298c39f189a', - sizes: [[ 640, 480 ]], + sizes: [[640, 480]], bidId: '2624fabbb078e8', bidderRequestId: '117954d20d7c9c', auctionId: 'defd525f-4f1e-4416-a4cb-ae53be90e706', @@ -515,7 +523,7 @@ describe('synacormediaBidAdapter ', function () { bidder: 'synacormedia', params: { seatId: 'prebid', - placementId: '1234', + tagId: '1234', video: { minduration: 30, maxduration: 45, @@ -526,7 +534,7 @@ describe('synacormediaBidAdapter ', function () { mediaTypes: { video: { context: 'instream', - playerSize: [[ 640, 480 ]], + playerSize: [[640, 480]], startdelay: 1, linearity: 1, placement: 1, @@ -535,7 +543,7 @@ describe('synacormediaBidAdapter ', function () { }, adUnitCode: 'video1', transactionId: '93e5def8-29aa-4fe8-bd3a-0298c39f189a', - sizes: [[ 640, 480 ]], + sizes: [[640, 480]], bidId: '2624fabbb078e8', bidderRequestId: '117954d20d7c9c', auctionId: 'defd525f-4f1e-4416-a4cb-ae53be90e706', @@ -568,7 +576,7 @@ describe('synacormediaBidAdapter ', function () { } ]); }); - it('should contain the CCPA privacy string when UspConsent is in bidder request', function() { + it('should contain the CCPA privacy string when UspConsent is in bidder request', function () { // banner test let req = spec.buildRequests([validBidRequest], bidderRequestWithCCPA); expect(req).be.an('object'); @@ -582,8 +590,8 @@ describe('synacormediaBidAdapter ', function () { }) }); - describe('Bid Requests with schain object ', function() { - let validBidReq = { + describe('Bid Requests with placementId should be backward compatible ', function () { + let validVideoBidReq = { bidder: 'synacormedia', params: { seatId: 'prebid', @@ -609,6 +617,68 @@ describe('synacormediaBidAdapter ', function () { src: 'client', bidRequestsCount: 1, bidderRequestsCount: 1, + bidderWinsCount: 0 + }; + + let validBannerBidRequest = { + bidId: '9876abcd', + sizes: [[300, 250]], + params: { + seatId: 'prebid', + placementId: '1234', + } + }; + + let bidderRequest = { + refererInfo: { + referer: 'http://localhost:9999/' + }, + bidderCode: 'synacormedia', + auctionId: 'f8a75621-d672-4cbb-9275-3db7d74fb110' + }; + + it('should return valid bid request for banner impression', function () { + let req = spec.buildRequests([validBannerBidRequest], bidderRequest); + expect(req).to.have.property('method', 'POST'); + expect(req).to.have.property('url'); + expect(req.url).to.contain('//prebid.technoratimedia.com/openrtb/bids/prebid?src=$$REPO_AND_VERSION$$'); + }); + + it('should return valid bid request for video impression', function () { + let req = spec.buildRequests([validVideoBidReq], bidderRequest); + expect(req).to.have.property('method', 'POST'); + expect(req).to.have.property('url'); + expect(req.url).to.contain('//prebid.technoratimedia.com/openrtb/bids/prebid?src=$$REPO_AND_VERSION$$'); + }); + }); + + describe('Bid Requests with schain object ', function () { + let validBidReq = { + bidder: 'synacormedia', + params: { + seatId: 'prebid', + tagId: 'demo1', + pos: 1, + video: {} + }, + renderer: { + url: '../syncOutstreamPlayer.js' + }, + mediaTypes: { + video: { + playerSize: [[300, 250]], + context: 'outstream' + } + }, + adUnitCode: 'div-1', + transactionId: '0869f34e-090b-4b20-84ee-46ff41405a39', + sizes: [[300, 250]], + bidId: '22b3a2268d9f0e', + bidderRequestId: '1d195910597e13', + auctionId: '3375d336-2aea-4ee7-804c-6d26b621ad20', + src: 'client', + bidRequestsCount: 1, + bidderRequestsCount: 1, bidderWinsCount: 0, schain: { ver: '1.0', @@ -634,7 +704,7 @@ describe('synacormediaBidAdapter ', function () { bidder: 'synacormedia', params: { seatId: 'prebid', - placementId: 'demo1', + tagId: 'demo1', pos: 1, video: {} }, @@ -733,7 +803,7 @@ describe('synacormediaBidAdapter ', function () { url: 'https://prebid.technoratimedia.com/openrtb/bids/prebid?src=prebid_prebid_3.27.0-pre' }; let serverResponse; - beforeEach(function() { + beforeEach(function () { serverResponse = { body: { id: 'abc123', @@ -778,7 +848,7 @@ describe('synacormediaBidAdapter ', function () { price: 0.45, nurl: 'https://uat-net.technoratimedia.com/openrtb/tags?ID=QVVDVElPTl9JRD1lOTBhYWU1My1hZDkwLTRkNDEtYTQxMC1lZDY1MjIxMDc0ZGMmQVVDVElPTl9CSURfSUQ9MTEzMzkxMjgwMDE2OTIzMzd-OTk5OX4wJkFVQ1RJT05fU0VBVF9JRD05OTk5JkFVQ1RJT05fSU1QX0lEPXYyZGE3MzIyYjJkZjYxZi02NDB4NDgwJkFDVE9SX1JFRj1ha2thLnRjcDovL2F3cy1lYXN0MUBhZHMxMy5jYXAtdXNlMS5zeW5hY29yLmNvbToyNTUxL3VzZXIvJGNMYmZiIy0xOTk4NTIzNTk3JlNFQVRfSUQ9cHJlYmlk&AUCTION_PRICE=${AUCTION_PRICE}', adm: '\n\n\n\nSynacor Media Ad Server - 9999\nhttps://uat-net.technoratimedia.com/openrtb/tags?ID=QVVDVElPTl9JRD1lOTBhYWU1My1hZDkwLTRkNDEtYTQxMC1lZDY1MjIxMDc0ZGMmQVVDVElPTl9CSURfSUQ9MTEzMzkxMjgwMDE2OTIzMzd-OTk5OX4wJkFVQ1RJT05fU0VBVF9JRD05OTk5JkFVQ1RJT05fSU1QX0lEPXYyZGE3MzIyYjJkZjYxZi02NDB4NDgwJkFDVE9SX1JFRj1ha2thLnRjcDovL2F3cy1lYXN0MUBhZHMxMy5jYXAtdXNlMS5zeW5hY29yLmNvbToyNTUxL3VzZXIvJGNMYmZiIy0xOTk4NTIzNTk3JlNFQVRfSUQ9cHJlYmlk&AUCTION_PRICE=${AUCTION_PRICE}\n\n\n', - adomain: [ 'psacentral.org' ], + adomain: ['psacentral.org'], cid: 'bidder-crid', crid: 'bidder-cid', cat: [], @@ -797,7 +867,6 @@ describe('synacormediaBidAdapter ', function () { expect(resp).to.be.an('array').to.have.lengthOf(1); expect(resp[0]).to.eql({ requestId: '2da7322b2df61f', - adId: '11339128001692337-9999-0', cpm: 0.45, width: 640, height: 480, @@ -807,6 +876,7 @@ describe('synacormediaBidAdapter ', function () { mediaType: 'video', ad: '\n\n\n\nSynacor Media Ad Server - 9999\nhttps://uat-net.technoratimedia.com/openrtb/tags?ID=QVVDVElPTl9JRD1lOTBhYWU1My1hZDkwLTRkNDEtYTQxMC1lZDY1MjIxMDc0ZGMmQVVDVElPTl9CSURfSUQ9MTEzMzkxMjgwMDE2OTIzMzd-OTk5OX4wJkFVQ1RJT05fU0VBVF9JRD05OTk5JkFVQ1RJT05fSU1QX0lEPXYyZGE3MzIyYjJkZjYxZi02NDB4NDgwJkFDVE9SX1JFRj1ha2thLnRjcDovL2F3cy1lYXN0MUBhZHMxMy5jYXAtdXNlMS5zeW5hY29yLmNvbToyNTUxL3VzZXIvJGNMYmZiIy0xOTk4NTIzNTk3JlNFQVRfSUQ9cHJlYmlk&AUCTION_PRICE=0.45\n\n\n', ttl: 60, + meta: { advertiserDomains: ['psacentral.org'] }, videoCacheKey: 'QVVDVElPTl9JRD1lOTBhYWU1My1hZDkwLTRkNDEtYTQxMC1lZDY1MjIxMDc0ZGMmQVVDVElPTl9CSURfSUQ9MTEzMzkxMjgwMDE2OTIzMzd-OTk5OX4wJkFVQ1RJT05fU0VBVF9JRD05OTk5JkFVQ1RJT05fSU1QX0lEPXYyZGE3MzIyYjJkZjYxZi02NDB4NDgwJkFDVE9SX1JFRj1ha2thLnRjcDovL2F3cy1lYXN0MUBhZHMxMy5jYXAtdXNlMS5zeW5hY29yLmNvbToyNTUxL3VzZXIvJGNMYmZiIy0xOTk4NTIzNTk3JlNFQVRfSUQ9cHJlYmlk', vastUrl: 'https://uat-net.technoratimedia.com/openrtb/tags?ID=QVVDVElPTl9JRD1lOTBhYWU1My1hZDkwLTRkNDEtYTQxMC1lZDY1MjIxMDc0ZGMmQVVDVElPTl9CSURfSUQ9MTEzMzkxMjgwMDE2OTIzMzd-OTk5OX4wJkFVQ1RJT05fU0VBVF9JRD05OTk5JkFVQ1RJT05fSU1QX0lEPXYyZGE3MzIyYjJkZjYxZi02NDB4NDgwJkFDVE9SX1JFRj1ha2thLnRjcDovL2F3cy1lYXN0MUBhZHMxMy5jYXAtdXNlMS5zeW5hY29yLmNvbToyNTUxL3VzZXIvJGNMYmZiIy0xOTk4NTIzNTk3JlNFQVRfSUQ9cHJlYmlk&AUCTION_PRICE=0.45' }); @@ -818,7 +888,6 @@ describe('synacormediaBidAdapter ', function () { expect(resp).to.be.an('array').to.have.lengthOf(1); expect(resp[0]).to.eql({ requestId: '9876abcd', - adId: '10865933907263896-9998-0', cpm: 0.13, width: 300, height: 250, @@ -841,7 +910,6 @@ describe('synacormediaBidAdapter ', function () { expect(resp).to.be.an('array').to.have.lengthOf(2); expect(resp[0]).to.eql({ requestId: '9876abcd', - adId: '10865933907263896-9998-0', cpm: 0.13, width: 300, height: 250, @@ -855,7 +923,6 @@ describe('synacormediaBidAdapter ', function () { expect(resp[1]).to.eql({ requestId: '9876abcd', - adId: '10865933907263800-9999-0', cpm: 1.99, width: 300, height: 600, @@ -892,7 +959,7 @@ describe('synacormediaBidAdapter ', function () { price: 0.45, nurl: 'https://uat-net.technoratimedia.com/openrtb/tags?ID=QVVDVElPTl9JRD1lOTBhYWU1My1hZDkwLTRkNDEtYTQxMC1lZDY1MjIxMDc0ZGMmQVVDVElPTl9CSURfSUQ9MTEzMzkxMjgwMDE2OTIzMzd-OTk5OX4wJkFVQ1RJT05fU0VBVF9JRD05OTk5JkFVQ1RJT05fSU1QX0lEPXYyZGE3MzIyYjJkZjYxZi02NDB4NDgwJkFDVE9SX1JFRj1ha2thLnRjcDovL2F3cy1lYXN0MUBhZHMxMy5jYXAtdXNlMS5zeW5hY29yLmNvbToyNTUxL3VzZXIvJGNMYmZiIy0xOTk4NTIzNTk3JlNFQVRfSUQ9cHJlYmlk&AUCTION_PRICE=${AUCTION_PRICE}', adm: '\n\n\n\nSynacor Media Ad Server - 9999\nhttps://uat-net.technoratimedia.com/openrtb/tags?ID=QVVDVElPTl9JRD1lOTBhYWU1My1hZDkwLTRkNDEtYTQxMC1lZDY1MjIxMDc0ZGMmQVVDVElPTl9CSURfSUQ9MTEzMzkxMjgwMDE2OTIzMzd-OTk5OX4wJkFVQ1RJT05fU0VBVF9JRD05OTk5JkFVQ1RJT05fSU1QX0lEPXYyZGE3MzIyYjJkZjYxZi02NDB4NDgwJkFDVE9SX1JFRj1ha2thLnRjcDovL2F3cy1lYXN0MUBhZHMxMy5jYXAtdXNlMS5zeW5hY29yLmNvbToyNTUxL3VzZXIvJGNMYmZiIy0xOTk4NTIzNTk3JlNFQVRfSUQ9cHJlYmlk&AUCTION_PRICE=${AUCTION_PRICE}\n\n\n', - adomain: [ 'psacentral.org' ], + adomain: ['psacentral.org'], cid: 'bidder-crid', crid: 'bidder-cid', cat: [], @@ -914,8 +981,8 @@ describe('synacormediaBidAdapter ', function () { }); let resp = spec.interpretResponse(serverRespVideo, bidRequest); - sandbox.restore(); - expect(resp[0].videoCacheKey).to.not.exist; + sandbox.restore(); + expect(resp[0].videoCacheKey).to.not.exist; }); it('should use video bid request height and width if not present in response', function () { @@ -952,7 +1019,7 @@ describe('synacormediaBidAdapter ', function () { price: 0.45, nurl: 'https://uat-net.technoratimedia.com/openrtb/tags?ID=QVVDVElPTl9JRD1lOTBhYWU1My1hZDkwLTRkNDEtYTQxMC1lZDY1MjIxMDc0ZGMmQVVDVElPTl9CSURfSUQ9MTEzMzkxMjgwMDE2OTIzMzd-OTk5OX4wJkFVQ1RJT05fU0VBVF9JRD05OTk5JkFVQ1RJT05fSU1QX0lEPXYyZGE3MzIyYjJkZjYxZi02NDB4NDgwJkFDVE9SX1JFRj1ha2thLnRjcDovL2F3cy1lYXN0MUBhZHMxMy5jYXAtdXNlMS5zeW5hY29yLmNvbToyNTUxL3VzZXIvJGNMYmZiIy0xOTk4NTIzNTk3JlNFQVRfSUQ9cHJlYmlk&AUCTION_PRICE=${AUCTION_PRICE}', adm: '\n\n\n\nSynacor Media Ad Server - 9999\nhttps://uat-net.technoratimedia.com/openrtb/tags?ID=QVVDVElPTl9JRD1lOTBhYWU1My1hZDkwLTRkNDEtYTQxMC1lZDY1MjIxMDc0ZGMmQVVDVElPTl9CSURfSUQ9MTEzMzkxMjgwMDE2OTIzMzd-OTk5OX4wJkFVQ1RJT05fU0VBVF9JRD05OTk5JkFVQ1RJT05fSU1QX0lEPXYyZGE3MzIyYjJkZjYxZi02NDB4NDgwJkFDVE9SX1JFRj1ha2thLnRjcDovL2F3cy1lYXN0MUBhZHMxMy5jYXAtdXNlMS5zeW5hY29yLmNvbToyNTUxL3VzZXIvJGNMYmZiIy0xOTk4NTIzNTk3JlNFQVRfSUQ9cHJlYmlk&AUCTION_PRICE=${AUCTION_PRICE}\n\n\n', - adomain: [ 'psacentral.org' ], + adomain: ['psacentral.org'], cid: 'bidder-crid', crid: 'bidder-cid', cat: [] @@ -967,7 +1034,6 @@ describe('synacormediaBidAdapter ', function () { expect(resp).to.be.an('array').to.have.lengthOf(1); expect(resp[0]).to.eql({ requestId: '2da7322b2df61f', - adId: '11339128001692337-9999-0', cpm: 0.45, width: 300, height: 250, @@ -977,6 +1043,7 @@ describe('synacormediaBidAdapter ', function () { mediaType: 'video', ad: '\n\n\n\nSynacor Media Ad Server - 9999\nhttps://uat-net.technoratimedia.com/openrtb/tags?ID=QVVDVElPTl9JRD1lOTBhYWU1My1hZDkwLTRkNDEtYTQxMC1lZDY1MjIxMDc0ZGMmQVVDVElPTl9CSURfSUQ9MTEzMzkxMjgwMDE2OTIzMzd-OTk5OX4wJkFVQ1RJT05fU0VBVF9JRD05OTk5JkFVQ1RJT05fSU1QX0lEPXYyZGE3MzIyYjJkZjYxZi02NDB4NDgwJkFDVE9SX1JFRj1ha2thLnRjcDovL2F3cy1lYXN0MUBhZHMxMy5jYXAtdXNlMS5zeW5hY29yLmNvbToyNTUxL3VzZXIvJGNMYmZiIy0xOTk4NTIzNTk3JlNFQVRfSUQ9cHJlYmlk&AUCTION_PRICE=0.45\n\n\n', ttl: 60, + meta: { advertiserDomains: ['psacentral.org'] }, videoCacheKey: 'QVVDVElPTl9JRD1lOTBhYWU1My1hZDkwLTRkNDEtYTQxMC1lZDY1MjIxMDc0ZGMmQVVDVElPTl9CSURfSUQ9MTEzMzkxMjgwMDE2OTIzMzd-OTk5OX4wJkFVQ1RJT05fU0VBVF9JRD05OTk5JkFVQ1RJT05fSU1QX0lEPXYyZGE3MzIyYjJkZjYxZi02NDB4NDgwJkFDVE9SX1JFRj1ha2thLnRjcDovL2F3cy1lYXN0MUBhZHMxMy5jYXAtdXNlMS5zeW5hY29yLmNvbToyNTUxL3VzZXIvJGNMYmZiIy0xOTk4NTIzNTk3JlNFQVRfSUQ9cHJlYmlk', vastUrl: 'https://uat-net.technoratimedia.com/openrtb/tags?ID=QVVDVElPTl9JRD1lOTBhYWU1My1hZDkwLTRkNDEtYTQxMC1lZDY1MjIxMDc0ZGMmQVVDVElPTl9CSURfSUQ9MTEzMzkxMjgwMDE2OTIzMzd-OTk5OX4wJkFVQ1RJT05fU0VBVF9JRD05OTk5JkFVQ1RJT05fSU1QX0lEPXYyZGE3MzIyYjJkZjYxZi02NDB4NDgwJkFDVE9SX1JFRj1ha2thLnRjcDovL2F3cy1lYXN0MUBhZHMxMy5jYXAtdXNlMS5zeW5hY29yLmNvbToyNTUxL3VzZXIvJGNMYmZiIy0xOTk4NTIzNTk3JlNFQVRfSUQ9cHJlYmlk&AUCTION_PRICE=0.45' }); @@ -1020,7 +1087,6 @@ describe('synacormediaBidAdapter ', function () { expect(resp).to.be.an('array').to.have.lengthOf(1); expect(resp[0]).to.eql({ requestId: 'abc123', - adId: '10865933907263896-9998-0', cpm: 0.13, width: 400, height: 350, diff --git a/test/spec/modules/tapadIdSystem_spec.js b/test/spec/modules/tapadIdSystem_spec.js new file mode 100644 index 00000000000..bc31f1d37ba --- /dev/null +++ b/test/spec/modules/tapadIdSystem_spec.js @@ -0,0 +1,65 @@ +import { tapadIdSubmodule, graphUrl } from 'modules/tapadIdSystem.js'; +import * as utils from 'src/utils.js'; + +import { server } from 'test/mocks/xhr.js'; + +describe('TapadIdSystem', function () { + describe('getId', function() { + const config = { params: { companyId: 12345 } }; + it('should call to real time graph endpoint and handle valid response', function() { + const callbackSpy = sinon.spy(); + const callback = tapadIdSubmodule.getId(config).callback; + callback(callbackSpy); + + const request = server.requests[0]; + expect(request.url).to.eq(`${graphUrl}?company_id=12345&tapad_id_type=TAPAD_ID`); + + request.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify({ tapadId: 'your-tapad-id' })); + expect(callbackSpy.lastCall.lastArg).to.eq('your-tapad-id'); + }); + + it('should remove stored tapadId if not found', function() { + const callbackSpy = sinon.spy(); + const callback = tapadIdSubmodule.getId(config).callback; + callback(callbackSpy); + + const request = server.requests[0]; + + request.respond(404); + expect(callbackSpy.lastCall.lastArg).to.be.undefined; + }); + + it('should log message with invalid company id', function() { + const logMessageSpy = sinon.spy(utils, 'logMessage'); + const callbackSpy = sinon.spy(); + const callback = tapadIdSubmodule.getId(config).callback; + callback(callbackSpy); + + const request = server.requests[0]; + + request.respond(403); + expect(logMessageSpy.lastCall.lastArg).to.eq('Invalid Company Id. Contact prebid@tapad.com for assistance.'); + logMessageSpy.restore(); + }); + + it('should log message if company id not given', function() { + const logMessageSpy = sinon.spy(utils, 'logMessage'); + const callbackSpy = sinon.spy(); + const callback = tapadIdSubmodule.getId({}).callback; + callback(callbackSpy); + + expect(logMessageSpy.lastCall.lastArg).to.eq('Please provide a valid Company Id. Contact prebid@tapad.com for assistance.'); + logMessageSpy.restore(); + }); + + it('should log message if company id is incorrect format', function() { + const logMessageSpy = sinon.spy(utils, 'logMessage'); + const callbackSpy = sinon.spy(); + const callback = tapadIdSubmodule.getId({ params: { companyId: 'notANumber' } }).callback; + callback(callbackSpy); + + expect(logMessageSpy.lastCall.lastArg).to.eq('Please provide a valid Company Id. Contact prebid@tapad.com for assistance.'); + logMessageSpy.restore(); + }); + }); +}) diff --git a/test/spec/modules/tappxBidAdapter_spec.js b/test/spec/modules/tappxBidAdapter_spec.js new file mode 100644 index 00000000000..1d3f9676d09 --- /dev/null +++ b/test/spec/modules/tappxBidAdapter_spec.js @@ -0,0 +1,181 @@ +import { assert } from 'chai'; +import { spec } from 'modules/tappxBidAdapter'; + +const c_BIDREQUEST = { + data: { + }, + bids: [ + { + bidder: 'tappx', + params: { + host: 'testing.ssp.tappx.com\/rtb\/v2\/', + tappxkey: 'pub-1234-android-1234', + endpoint: 'ZZ1234PBJS', + bidfloor: 0.05 + }, + crumbs: { + pubcid: 'df2144f7-673f-4440-83f5-cd4a73642d99' + }, + fpd: { + context: { + adServer: { + name: 'gam', + adSlot: '/19968336/header-bid-tag-0' + }, + pbAdSlot: '/19968336/header-bid-tag-0', + }, + }, + mediaTypes: { + banner: { + sizes: [ + [ + 320, + 480 + ] + ] + } + }, + adUnitCode: 'div-1', + transactionId: '47dd44e8-e7db-417c-a8f1-621a2e1a117d', + sizes: [ + [ + 320, + 480 + ] + ], + bidId: '2170932097e505', + bidderRequestId: '140ba7a1ab7aeb', + auctionId: '1c54b4f1-645f-44e6-b8ae-5d43c923ef1c', + src: 'client', + bidRequestsCount: 1, + bidderRequestsCount: 1, + bidderWinsCount: 0, + } + ] +}; +const c_SERVERRESPONSE = { + body: { + id: '1c54b4f1-645f-44e6-b8ae-5d43c923ef1c', + bidid: 'bid3811165568213389257', + seatbid: [ + { + seat: '1', + group: 0, + bid: [ + { + id: '3811165568213389257', + impid: 1, + price: 0.05, + adm: "\t", + w: 320, + h: 480, + lurl: 'http://testing.ssp.tappx.com/rtb/RTBv2Loss?id=3811165568213389257&ep=ZZ1234PBJS&au=test&bu=localhost&sz=320x480&pu=0.005&pt=0.01&cid=&crid=&adv=&aid=${AUCTION_ID}&bidid=${AUCTION_BID_ID}&impid=${AUCTION_IMP_ID}&sid=${AUCTION_SEAT_ID}&adid=${AUCTION_AD_ID}&ap=${AUCTION_PRICE}&cur=${AUCTION_CURRENCY}&mbr=${AUCTION_MBR}&l=${AUCTION_LOSS}', + cid: '01744fbb521e9fb10ffea926190effea', + crid: 'a13cf884e66e7c660afec059c89d98b6', + adomain: [ + ], + }, + ], + }, + ], + cur: 'USD', + }, + headers: {} +}; +const c_CONSENTSTRING = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; +const c_VALIDBIDREQUESTS = [{'bidder': 'tappx', 'params': {'host': 'testing.ssp.tappx.com\/rtb\/v2\/', 'tappxkey': 'pub-1234-android-1234', 'endpoint': 'ZZ1234PBJS', 'bidfloor': 0.005, 'test': 1}, 'userId': {'haloId': '0000x179MZAzMqUWsFonu7Drm3eDDBMYtj5SPoWQnl89Upk3WTlCvEnKI9SshX0p6eFJ7otPYix179MZAzMqUWsFonu7Drm3eDDBMYtj5SPoWQnl89Upk3WTlCvEnKI9SshX0p6e', 'id5id': {'uid': 'ID5@iu-PJX_OQ0d6FJjKS8kYfUpHriD_rEXbz6UYtYEJelYrDaZOLkh8WcF9J0ZHmEHFKZEBlLXsgP6xqXU3BCj4Ay0Z6fw_jSOaHxMHwd-voRHqFA4Q9NwAxFcVLyPWnNGZ9VbcSAPos1wupq7Xu3MIm-Bw_0vxjhZdWNy4chM9x3i', 'ext': {'linkType': 0}}, 'intentIqId': 'GIF89a\u0000\u0000\u0000\u0000�\u0000\u0000���\u0000\u0000\u0000?�\u0000\u0000\u0000\u0000\u0000\u0000,\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000A\u0000\u0000;', 'lotamePanoramaId': 'xTtLUY7GwqX2MMqSHo9RQ2YUOIBFhlASOR43I9KjvgtcrxIys3RxME96M02LTjWR', 'parrableId': {'eid': '02.YoqC9lWZh8.C8QTSiJTNgI6Pp0KCM5zZgEgwVMSsVP5W51X8cmiUHQESq9WRKB4nreqZJwsWIcNKlORhG4u25Wm6lmDOBmQ0B8hv0KP6uVQ97aouuH52zaz2ctVQTORUKkErPRPcaCJ7dKFcrNoF2i6WOR0S5Nk'}, 'pubcid': 'b1254-152f-12F5-5698-dI1eljK6C7WA', 'pubProvidedId': [{'source': 'domain.com', 'uids': [{'id': 'value read from cookie or local storage', 'atype': 1, 'ext': {'stype': 'ppuid'}}]}, {'source': '3rdpartyprovided.com', 'uids': [{'id': 'value read from cookie or local storage', 'atype': 3, 'ext': {'stype': 'sha256email'}}]}]}, 'userIdAsEids': [{'source': 'audigent.com', 'uids': [{'id': '0000fgclxw05ycn0608xiyi90bwpa0c0evvlif0hv1x0i0ku88il0ntek0o0qskvir0trr70u0wqxiix0zq3u1012pa5j315ogh1618nmsj91bmt41c1elzfjf1hl5r1i1kkc2jl', 'atype': 1}]}, {'source': 'id5-sync.com', 'uids': [{'id': 'ID5@iu-PJX_OQ0d6FJjKS8kYfUpHriD_qpoXJUngedfpNva812If1fHEqHHkamLC89txVxk1i9WGqeQrTX97HFCgv9QDa1M_bkHUBsAWFm-D5r1rYrsfMFFiyqwCAEzqNbvsUZXOYCAQSjPcLxR4of22w-U9_JDRThCGRDV3Fmvc38E', 'atype': 1, 'ext': {'linkType': 0}}]}], 'ortb2Imp': {'ext': {'data': {'adserver': {'name': 'gam', 'adslot': '/19968336/header-bid-tag-0'}, 'pbadslot': '/19968336/header-bid-tag-0'}}}, 'mediaTypes': {'banner': {'sizes': [[320, 480], [320, 50]]}}, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'transactionId': '71c0d86b-4b47-4aff-a6da-1af0b1712439', 'sizes': [[320, 480], [320, 50]], 'bidId': '264d7969b125a5', 'bidderRequestId': '1c674c14a3889c', 'auctionId': '13a8a3a9-ed3a-4101-9435-4699ee77bb62', 'src': 'client', 'bidRequestsCount': 1, 'bidderRequestsCount': 1, 'bidderWinsCount': 0}]; +const c_VALIDBIDAPPREQUESTS = [{'bidder': 'tappx', 'params': {'host': 'testing.ssp.tappx.com\/rtb\/v2\/', 'tappxkey': 'pub-1234-android-1234', 'endpoint': 'ZZ1234PBJS', 'bidfloor': 0.005, 'test': 1, 'app': {'name': 'Tappx Test', 'bundle': 'com.test.tappx', 'domain': 'tappx.com', 'publisher': { 'name': 'Tappx', 'domain': 'tappx.com' }}}, 'userId': {'haloId': '0000fgclxw05ycn0608xiyi90bwpa0c0evvlif0hv1x0i0ku88il0ntek0o0qskvir0trr70u0wqxiix0zq3u1012pa5j315ogh1618nmsj91bmt41c1elzfjf1hl5r1i1kkc2jl', 'id5id': {'uid': 'ID5@iu-PJX_OQ0d6FJjKS8kYfUpHriD_qpoXJUngedfpNva812If1fHEqHHkamLC89txVxk1i9WGqeQrTX97HFCgv9QDa1M_bkHUBsAWFm-D5r1rYrsfMFFiyqwCAEzqNbvsUZXOYCAQSjPcLxR4of22w-U9_JDRThCGRDV3Fmvc38E', 'ext': {'linkType': 0}}, 'intentIqId': 'GIF89a\u0001\u0000\u0001\u0000�\u0000\u0000���\u0000\u0000\u0000!�\u0004\u0001\u0000\u0000\u0000\u0000,\u0000\u0000\u0000\u0000\u0001\u0000\u0001\u0000\u0000\u0002\u0002D\u0001\u0000;', 'lotamePanoramaId': '8003916b61a95b185690ec103bdf4945a70213e01818a5e5d8690b542730755a', 'parrableId': {'eid': '01.1617088921.7faa68d9570a50ea8e4f359e9b99ca4b7509e948a6175b3e5b0b8cbaf5b62424104ccfb0191ca79366de8368ed267b89a68e236df5f41f96f238e4301659e9023fec05e46399fb1ad0a0'}, 'pubcid': 'b7143795-852f-42f0-8864-5ecbea1ade4e', 'pubProvidedId': [{'source': 'domain.com', 'uids': [{'id': 'value read from cookie or local storage', 'atype': 1, 'ext': {'stype': 'ppuid'}}]}, {'source': '3rdpartyprovided.com', 'uids': [{'id': 'value read from cookie or local storage', 'atype': 3, 'ext': {'stype': 'sha256email'}}]}]}, 'userIdAsEids': [{'source': 'audigent.com', 'uids': [{'id': '0000fgclxw05ycn0608xiyi90bwpa0c0evvlif0hv1x0i0ku88il0ntek0o0qskvir0trr70u0wqxiix0zq3u1012pa5j315ogh1618nmsj91bmt41c1elzfjf1hl5r1i1kkc2jl', 'atype': 1}]}, {'source': 'id5-sync.com', 'uids': [{'id': 'ID5@iu-PJX_OQ0d6FJjKS8kYfUpHriD_qpoXJUngedfpNva812If1fHEqHHkamLC89txVxk1i9WGqeQrTX97HFCgv9QDa1M_bkHUBsAWFm-D5r1rYrsfMFFiyqwCAEzqNbvsUZXOYCAQSjPcLxR4of22w-U9_JDRThCGRDV3Fmvc38E', 'atype': 1, 'ext': {'linkType': 0}}]}, {'source': 'intentiq.com', 'uids': [{'id': 'GIF89a\u0001\u0000\u0001\u0000�\u0000\u0000���\u0000\u0000\u0000!�\u0004\u0001\u0000\u0000\u0000\u0000,\u0000\u0000\u0000\u0000\u0001\u0000\u0001\u0000\u0000\u0002\u0002D\u0001\u0000;', 'atype': 1}]}, {'source': 'crwdcntrl.net', 'uids': [{'id': '8003916b61a95b185690ec103bdf4945a70213e01818a5e5d8690b542730755a', 'atype': 1}]}, {'source': 'parrable.com', 'uids': [{'id': '01.1617088921.7faa68d9570a50ea8e4f359e9b99ca4b7509e948a6175b3e5b0b8cbaf5b62424104ccfb0191ca79366de8368ed267b89a68e236df5f41f96f238e4301659e9023fec05e46399fb1ad0a0', 'atype': 1}]}, {'source': 'pubcid.org', 'uids': [{'id': 'b7143795-852f-42f0-8864-5ecbea1ade4e', 'atype': 1}]}, {'source': 'domain.com', 'uids': [{'id': 'value read from cookie or local storage', 'atype': 1, 'ext': {'stype': 'ppuid'}}]}, {'source': '3rdpartyprovided.com', 'uids': [{'id': 'value read from cookie or local storage', 'atype': 3, 'ext': {'stype': 'sha256email'}}]}], 'ortb2Imp': {'ext': {'data': {'adserver': {'name': 'gam', 'adslot': '/19968336/header-bid-tag-0'}, 'pbadslot': '/19968336/header-bid-tag-0'}}}, 'mediaTypes': {'banner': {'sizes': [[320, 480], [320, 50]]}}, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'transactionId': '71c0d86b-4b47-4aff-a6da-1af0b1712439', 'sizes': [[320, 480], [320, 50]], 'bidId': '264d7969b125a5', 'bidderRequestId': '1c674c14a3889c', 'auctionId': '13a8a3a9-ed3a-4101-9435-4699ee77bb62', 'src': 'client', 'bidRequestsCount': 1, 'bidderRequestsCount': 1, 'bidderWinsCount': 0}]; +const c_BIDDERREQUEST = {'bidderCode': 'tappx', 'auctionId': '13a8a3a9-ed3a-4101-9435-4699ee77bb62', 'bidderRequestId': '1c674c14a3889c', 'bids': [{'bidder': 'tappx', 'params': {'host': 'testing.ssp.tappx.com\/rtb\/v2\/', 'tappxkey': 'pub-1234-android-1234', 'endpoint': 'ZZ1234PBJS', 'bidfloor': 0.005, 'test': 1}, 'userId': {'haloId': '0000fgclxw05ycn0608xiyi90bwpa0c0evvlif0hv1x0i0ku88il0ntek0o0qskvir0trr70u0wqxiix0zq3u1012pa5j315ogh1618nmsj91bmt41c1elzfjf1hl5r1i1kkc2jl', 'id5id': {'uid': 'ID5@iu-PJX_OQ0d6FJjKS8kYfUpHriD_qpoXJUngedfpNva812If1fHEqHHkamLC89txVxk1i9WGqeQrTX97HFCgv9QDa1M_bkHUBsAWFm-D5r1rYrsfMFFiyqwCAEzqNbvsUZXOYCAQSjPcLxR4of22w-U9_JDRThCGRDV3Fmvc38E', 'ext': {'linkType': 0}}, 'intentIqId': 'GIF89a\u0000\u0000\u0000\u0000�\u0000\u0000���\u0000\u0000\u0000?�\u0000\u0000\u0000\u0000\u0000\u0000,\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000A\u0000\u0000;', 'lotamePanoramaId': '8003916b61a95b185690ec103bdf4945a70213e01818a5e5d8690b542730755a', 'parrableId': {'eid': '01.1617088921.7faa68d9570a50ea8e4f359e9b99ca4b7509e948a6175b3e5b0b8cbaf5b62424104ccfb0191ca79366de8368ed267b89a68e236df5f41f96f238e4301659e9023fec05e46399fb1ad0a0'}, 'pubcid': 'b7143795-852f-42f0-8864-5ecbea1ade4e', 'pubProvidedId': [{'source': 'domain.com', 'uids': [{'id': 'value read from cookie or local storage', 'atype': 1, 'ext': {'stype': 'ppuid'}}]}, {'source': '3rdpartyprovided.com', 'uids': [{'id': 'value read from cookie or local storage', 'atype': 3, 'ext': {'stype': 'sha256email'}}]}]}, 'userIdAsEids': [{'source': 'audigent.com', 'uids': [{'id': '0000fgclxw05ycn0608xiyi90bwpa0c0evvlif0hv1x0i0ku88il0ntek0o0qskvir0trr70u0wqxiix0zq3u1012pa5j315ogh1618nmsj91bmt41c1elzfjf1hl5r1i1kkc2jl', 'atype': 1}]}, {'source': 'id5-sync.com', 'uids': [{'id': 'ID5@iu-PJX_OQ0d6FJjKS8kYfUpHriD_qpoXJUngedfpNva812If1fHEqHHkamLC89txVxk1i9WGqeQrTX97HFCgv9QDa1M_bkHUBsAWFm-D5r1rYrsfMFFiyqwCAEzqNbvsUZXOYCAQSjPcLxR4of22w-U9_JDRThCGRDV3Fmvc38E', 'atype': 1, 'ext': {'linkType': 0}}]}], 'ortb2Imp': {'ext': {'data': {'adserver': {'name': 'gam', 'adslot': '/19968336/header-bid-tag-0'}, 'pbadslot': '/19968336/header-bid-tag-0'}}}, 'mediaTypes': {'banner': {'sizes': [[320, 480], [320, 50]]}}, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'transactionId': '71c0d86b-4b47-4aff-a6da-1af0b1712439', 'sizes': [[320, 480], [320, 50]], 'bidId': '264d7969b125a5', 'bidderRequestId': '1c674c14a3889c', 'auctionId': '13a8a3a9-ed3a-4101-9435-4699ee77bb62', 'src': 'client', 'bidRequestsCount': 1, 'bidderRequestsCount': 1, 'bidderWinsCount': 0}], 'auctionStart': 1617088922120, 'timeout': 700, 'refererInfo': {'referer': 'http://localhost:9999/integrationExamples/gpt/gdpr_hello_world.html?pbjs_debug=true', 'reachedTop': true, 'isAmp': false, 'numIframes': 0, 'stack': ['http://localhost:9999/integrationExamples/gpt/gdpr_hello_world.html?pbjs_debug=true'], 'canonicalUrl': null}, 'gdprConsent': {'consentString': c_CONSENTSTRING, 'vendorData': {'metadata': 'BO-JeiTPABAOkAAABAENABA', 'gdprApplies': true, 'hasGlobalScope': false, 'cookieVersion': 1, 'created': '2020-12-09T09:22:09.900Z', 'lastUpdated': '2021-01-14T15:44:03.600Z', 'cmpId': 0, 'cmpVersion': 1, 'consentScreen': 0, 'consentLanguage': 'EN', 'vendorListVersion': 1, 'maxVendorId': 0, 'purposeConsents': {}, 'vendorConsents': {}}, 'gdprApplies': true, 'apiVersion': 1}, 'uspConsent': '1YCC', 'start': 1611308859099}; + +describe('Tappx bid adapter', function () { + /** + * IS REQUEST VALID + */ + describe('isBidRequestValid', function () { + it('should return true when required params found', function () { + assert.isTrue(spec.isBidRequestValid(c_BIDREQUEST.bids[0]), JSON.stringify(c_BIDREQUEST)); + }); + + it('should return false when required params are missing', function () { + let badBidRequest = c_BIDREQUEST; + delete badBidRequest.bids[0].params.tappxkey; + delete badBidRequest.bids[0].params.endpoint; + assert.isFalse(spec.isBidRequestValid(badBidRequest.bids[0])); + }); + }); + + /** + * BUILD REQUEST TEST + */ + describe('buildRequest', function () { + // Web Test + let validBidRequests = c_VALIDBIDREQUESTS; + // App Test + let validAppBidRequests = c_VALIDBIDAPPREQUESTS; + + let bidderRequest = c_BIDDERREQUEST; + + it('should add gdpr/usp consent information to the request', function () { + const request = spec.buildRequests(validBidRequests, bidderRequest); + const payload = JSON.parse(request[0].data); + + expect(payload.regs.gdpr).to.exist.and.to.be.true; + expect(payload.regs.consent).to.exist.and.to.equal(c_CONSENTSTRING); + expect(payload.regs.ext.us_privacy).to.exist; + }); + + it('should properly build a banner request', function () { + const request = spec.buildRequests(validBidRequests, bidderRequest); + expect(request[0].url).to.match(/^(http|https):\/\/(.*)\.tappx\.com\/.+/); + expect(request[0].method).to.equal('POST'); + + const data = JSON.parse(request[0].data); + expect(data.site).to.not.equal(null); + expect(data.imp).to.have.lengthOf(1); + expect(data.imp[0].bidfloor, data).to.not.be.null; + expect(data.imp[0].banner).to.not.equal(null); + expect(data.imp[0].banner.w).to.be.oneOf([320, 50, 250, 480]); + expect(data.imp[0].banner.h).to.be.oneOf([320, 50, 250, 480]); + }); + + it('should set user eids array', function () { + const request = spec.buildRequests(validBidRequests, bidderRequest); + + const data = JSON.parse(request[0].data); + expect(data.user.ext.eids, data).to.not.be.null; + expect(data.user.ext.eids[0]).to.have.keys(['source', 'uids']); + }); + + it('should properly build a banner request with app params', function () { + const request = spec.buildRequests(validAppBidRequests, bidderRequest); + expect(request[0].url).to.match(/^(http|https):\/\/(.*)\.tappx\.com\/.+/); + expect(request[0].method).to.equal('POST'); + + const data = JSON.parse(request[0].data); + expect(data.site).to.not.equal(null); + expect(data.imp).to.have.lengthOf(1); + expect(data.imp[0].bidfloor, data).to.not.be.null; + expect(data.imp[0].banner).to.not.equal(null); + expect(data.imp[0].banner.w).to.be.oneOf([320, 50, 250, 480]); + expect(data.imp[0].banner.h).to.be.oneOf([320, 50, 250, 480]); + }); + }); + + /** + * INTERPRET RESPONSE TESTS + */ + describe('interpretResponse', function () { + it('receive reponse with single placement', function () { + const bids = spec.interpretResponse(c_SERVERRESPONSE, c_BIDREQUEST); + const bid = bids[0]; + expect(bid.cpm).to.exist; + expect(bid.ad).to.match(/^', + 'ttl': 700, + 'ad': '' + } + ]; + let request = spec.buildRequests(bidRequests)[0]; + let result = spec.interpretResponse({body: response}, request); + expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); + expect(result[0].cpm).to.not.equal(null); + expect(result[0].creativeId).to.not.equal(null); + expect(result[0].ad).to.not.equal(null); + expect(result[0].currency).to.equal('TRY'); + expect(result[0].netRevenue).to.equal(false); + }); + }) +}) diff --git a/test/spec/modules/trionBidAdapter_spec.js b/test/spec/modules/trionBidAdapter_spec.js index 596e8a3e2d9..ae329b4a028 100644 --- a/test/spec/modules/trionBidAdapter_spec.js +++ b/test/spec/modules/trionBidAdapter_spec.js @@ -146,47 +146,47 @@ describe('Trion adapter tests', function () { expect(bidUrlParams).to.include(getPublisherUrl()); }); - describe('webdriver', function () { - let originalWD; - - beforeEach(function () { - originalWD = window.navigator.webdriver; - }); - - afterEach(function () { - window.navigator['__defineGetter__']('webdriver', function () { - return originalWD; - }); - }); - - describe('is present', function () { - beforeEach(function () { - window.navigator['__defineGetter__']('webdriver', function () { - return 1; - }); - }); - - it('when there is non human traffic', function () { - let bidRequests = spec.buildRequests(TRION_BID_REQUEST); - let bidUrlParams = bidRequests[0].data; - expect(bidUrlParams).to.include('tr_wd=1'); - }); - }); - - describe('is not present', function () { - beforeEach(function () { - window.navigator['__defineGetter__']('webdriver', function () { - return 0; - }); - }); - - it('when there is not non human traffic', function () { - let bidRequests = spec.buildRequests(TRION_BID_REQUEST); - let bidUrlParams = bidRequests[0].data; - expect(bidUrlParams).to.include('tr_wd=0'); - }); - }); - }); + // describe('webdriver', function () { + // let originalWD; + + // beforeEach(function () { + // originalWD = window.navigator.webdriver; + // }); + + // afterEach(function () { + // window.navigator['__defineGetter__']('webdriver', function () { + // return originalWD; + // }); + // }); + + // describe('is present', function () { + // beforeEach(function () { + // window.navigator['__defineGetter__']('webdriver', function () { + // return 1; + // }); + // }); + + // it('when there is non human traffic', function () { + // let bidRequests = spec.buildRequests(TRION_BID_REQUEST); + // let bidUrlParams = bidRequests[0].data; + // expect(bidUrlParams).to.include('tr_wd=1'); + // }); + // }); + + // describe('is not present', function () { + // beforeEach(function () { + // window.navigator['__defineGetter__']('webdriver', function () { + // return 0; + // }); + // }); + + // it('when there is not non human traffic', function () { + // let bidRequests = spec.buildRequests(TRION_BID_REQUEST); + // let bidUrlParams = bidRequests[0].data; + // expect(bidUrlParams).to.include('tr_wd=0'); + // }); + // }); + // }); describe('document', function () { let originalHD; diff --git a/test/spec/modules/tripleliftBidAdapter_spec.js b/test/spec/modules/tripleliftBidAdapter_spec.js index 82578424027..30377ec0a5d 100644 --- a/test/spec/modules/tripleliftBidAdapter_spec.js +++ b/test/spec/modules/tripleliftBidAdapter_spec.js @@ -143,10 +143,10 @@ describe('triplelift adapter', function () { auctionId: '1d1a030790a475', userId: {}, schain, - fpd: { - context: { - pbAdSlot: 'homepage-top-rect', + ortb2Imp: { + ext: { data: { + pbAdSlot: 'homepage-top-rect', adUnitSpecificAttribute: 123 } } @@ -506,15 +506,16 @@ describe('triplelift adapter', function () { }); }); - it('should add user ids from multiple bid requests', function () { + it('should consolidate user ids from multiple bid requests', function () { const tdidId = '6bca7f6b-a98a-46c0-be05-6020f7604598'; const idlEnvId = 'XY6104gr0njcH9UDIR7ysFFJcm2XNpqeJTYslleJ_cMlsFOfZI'; const criteoId = '53e30ea700424f7bbdd793b02abc5d7'; + const pubcid = '3261d8ad-435d-481d-abd1-9f1a9ec99f0e'; const bidRequestsMultiple = [ - { ...bidRequests[0], userId: { tdid: tdidId } }, - { ...bidRequests[0], userId: { idl_env: idlEnvId } }, - { ...bidRequests[0], userId: { criteoId: criteoId } } + { ...bidRequests[0], userId: { tdid: tdidId, idl_env: idlEnvId, criteoId, pubcid } }, + { ...bidRequests[0], userId: { tdid: tdidId, idl_env: idlEnvId, criteoId, pubcid } }, + { ...bidRequests[0], userId: { tdid: tdidId, idl_env: idlEnvId, criteoId, pubcid } } ]; const request = tripleliftAdapterSpec.buildRequests(bidRequestsMultiple, bidderRequest); @@ -549,10 +550,22 @@ describe('triplelift adapter', function () { ext: { rtiPartner: 'criteoId' } } ] + }, + { + source: 'pubcid.org', + uids: [ + { + id: '3261d8ad-435d-481d-abd1-9f1a9ec99f0e', + ext: { rtiPartner: 'pubcid' } + } + ] } ] } }); + + expect(payload.user.ext.eids).to.be.an('array'); + expect(payload.user.ext.eids).to.have.lengthOf(4); }); it('should return a query string for TL call', function () { @@ -597,23 +610,66 @@ describe('triplelift adapter', function () { expect(payload.ext).to.deep.equal(undefined); }); it('should get floor from floors module if available', function() { - const floorInfo = { - currency: 'USD', - floor: 1.99 - }; + let floorInfo; bidRequests[0].getFloor = () => floorInfo; - const request = tripleliftAdapterSpec.buildRequests(bidRequests, bidderRequest); + + // standard float response; expected functionality of floors module + floorInfo = { currency: 'USD', floor: 1.99 }; + let request = tripleliftAdapterSpec.buildRequests(bidRequests, bidderRequest); + expect(request.data.imp[0].floor).to.equal(1.99); + + // if string response, convert to float + floorInfo = { currency: 'USD', floor: '1.99' }; + request = tripleliftAdapterSpec.buildRequests(bidRequests, bidderRequest); expect(request.data.imp[0].floor).to.equal(1.99); }); + it('should call getFloor with the correct parameters based on mediaType', function() { + bidRequests.forEach(request => { + request.getFloor = () => {}; + sinon.spy(request, 'getFloor') + }); + + tripleliftAdapterSpec.buildRequests(bidRequests, bidderRequest); + + // banner + expect(bidRequests[0].getFloor.calledWith({ + currency: 'USD', + mediaType: 'banner', + size: '*' + })).to.be.true; + + // instream + expect(bidRequests[1].getFloor.calledWith({ + currency: 'USD', + mediaType: 'video', + size: '*' + })).to.be.true; + + // banner and incomplete video (POST will only include banner) + expect(bidRequests[3].getFloor.calledWith({ + currency: 'USD', + mediaType: 'banner', + size: '*' + })).to.be.true; + + // banner and instream (POST will only include video) + expect(bidRequests[5].getFloor.calledWith({ + currency: 'USD', + mediaType: 'video', + size: '*' + })).to.be.true; + }); it('should send global config fpd if kvps are available', function() { const sens = null; const category = ['news', 'weather', 'hurricane']; const pmp_elig = 'true'; - const fpd = { - context: { + const ortb2 = { + site: { pmp_elig: pmp_elig, - data: { - category: category + ext: { + data: { + category: category + } } }, user: { @@ -622,7 +678,7 @@ describe('triplelift adapter', function () { } sandbox.stub(config, 'getConfig').callsFake(key => { const config = { - fpd + ortb2 }; return utils.deepAccess(config, key); }); @@ -634,8 +690,8 @@ describe('triplelift adapter', function () { }); it('should send ad unit fpd if kvps are available', function() { const request = tripleliftAdapterSpec.buildRequests(bidRequests, bidderRequest); - expect(request.data.imp[0].fpd.context).to.haveOwnProperty('pbAdSlot'); expect(request.data.imp[0].fpd.context).to.haveOwnProperty('data'); + expect(request.data.imp[0].fpd.context.data).to.haveOwnProperty('pbAdSlot'); expect(request.data.imp[0].fpd.context.data).to.haveOwnProperty('adUnitSpecificAttribute'); expect(request.data.imp[1].fpd).to.not.exist; }); diff --git a/test/spec/modules/trustxBidAdapter_spec.js b/test/spec/modules/trustxBidAdapter_spec.js index 9e0aad9b36f..e9daaa83b5d 100644 --- a/test/spec/modules/trustxBidAdapter_spec.js +++ b/test/spec/modules/trustxBidAdapter_spec.js @@ -262,7 +262,6 @@ describe('TrustXAdapter', function () { 'width': 300, 'height': 250, 'ad': '
test content 1
', - 'bidderCode': 'trustx', 'currency': 'USD', 'mediaType': 'banner', 'netRevenue': true, @@ -320,7 +319,6 @@ describe('TrustXAdapter', function () { 'width': 300, 'height': 250, 'ad': '
test content 1
', - 'bidderCode': 'trustx', 'currency': 'USD', 'mediaType': 'banner', 'netRevenue': true, @@ -334,7 +332,6 @@ describe('TrustXAdapter', function () { 'width': 300, 'height': 600, 'ad': '
test content 2
', - 'bidderCode': 'trustx', 'currency': 'USD', 'mediaType': 'banner', 'netRevenue': true, @@ -348,7 +345,6 @@ describe('TrustXAdapter', function () { 'width': 728, 'height': 90, 'ad': '
test content 3
', - 'bidderCode': 'trustx', 'currency': 'USD', 'mediaType': 'banner', 'netRevenue': true, @@ -476,7 +472,6 @@ describe('TrustXAdapter', function () { 'width': 300, 'height': 250, 'ad': '
test content 1
', - 'bidderCode': 'trustx', 'currency': 'USD', 'mediaType': 'banner', 'netRevenue': true, @@ -490,7 +485,6 @@ describe('TrustXAdapter', function () { 'width': 300, 'height': 600, 'ad': '
test content 2
', - 'bidderCode': 'trustx', 'currency': 'USD', 'mediaType': 'banner', 'netRevenue': true, @@ -504,7 +498,6 @@ describe('TrustXAdapter', function () { 'width': 728, 'height': 90, 'ad': '
test content 3
', - 'bidderCode': 'trustx', 'currency': 'USD', 'mediaType': 'banner', 'netRevenue': true, @@ -518,7 +511,6 @@ describe('TrustXAdapter', function () { 'width': 300, 'height': 600, 'ad': '
test content 4
', - 'bidderCode': 'trustx', 'currency': 'USD', 'mediaType': 'banner', 'netRevenue': true, @@ -580,7 +572,6 @@ describe('TrustXAdapter', function () { 'width': 300, 'height': 250, 'ad': '
test content 1
', - 'bidderCode': 'trustx', 'currency': 'USD', 'mediaType': 'banner', 'netRevenue': true, @@ -594,7 +585,6 @@ describe('TrustXAdapter', function () { 'width': 300, 'height': 250, 'ad': '
test content 2
', - 'bidderCode': 'trustx', 'currency': 'USD', 'mediaType': 'banner', 'netRevenue': true, @@ -655,7 +645,6 @@ describe('TrustXAdapter', function () { 'dealId': undefined, 'width': 300, 'height': 600, - 'bidderCode': 'trustx', 'currency': 'USD', 'mediaType': 'video', 'netRevenue': true, @@ -735,7 +724,6 @@ describe('TrustXAdapter', function () { 'dealId': undefined, 'width': 300, 'height': 600, - 'bidderCode': 'trustx', 'currency': 'USD', 'mediaType': 'video', 'netRevenue': true, @@ -753,7 +741,6 @@ describe('TrustXAdapter', function () { 'dealId': undefined, 'width': 300, 'height': 250, - 'bidderCode': 'trustx', 'currency': 'USD', 'mediaType': 'video', 'netRevenue': true, @@ -771,7 +758,6 @@ describe('TrustXAdapter', function () { 'dealId': undefined, 'width': 300, 'height': 250, - 'bidderCode': 'trustx', 'currency': 'USD', 'mediaType': 'video', 'netRevenue': true, diff --git a/test/spec/modules/ucfunnelBidAdapter_spec.js b/test/spec/modules/ucfunnelBidAdapter_spec.js index b5f29287f81..a2939eb95c3 100644 --- a/test/spec/modules/ucfunnelBidAdapter_spec.js +++ b/test/spec/modules/ucfunnelBidAdapter_spec.js @@ -1,7 +1,6 @@ import { expect } from 'chai'; import { spec } from 'modules/ucfunnelBidAdapter.js'; import {BANNER, VIDEO, NATIVE} from 'src/mediaTypes.js'; - const URL = 'https://hb.aralego.com/header'; const BIDDER_CODE = 'ucfunnel'; @@ -9,6 +8,17 @@ const bidderRequest = { uspConsent: '1YNN' }; +const userId = { + 'criteoId': 'vYlICF9oREZlTHBGRVdrJTJCUUJnc3U2ckNVaXhrV1JWVUZVSUxzZmJlcnJZR0ZxbVhFRnU5bDAlMkJaUWwxWTlNcmdEeHFrJTJGajBWVlV4T3lFQ0FyRVcxNyUyQlIxa0lLSlFhcWJpTm9PSkdPVkx0JTJCbzlQRTQlM0Q', + 'id5id': {'uid': 'ID5-8ekgswyBTQqnkEKy0ErmeQ1GN5wV4pSmA-RE4eRedA'}, + 'netId': 'fH5A3n2O8_CZZyPoJVD-eabc6ECb7jhxCicsds7qSg', + 'parrableId': {'eid': '01.1608624401.fe44bca9b96873084a0d4e9d0ac5729f13790ba8f8e58fa4707b6b3c096df91c6b5f254992bdad4ab1dd4a89919081e9b877d7a039ac3183709277665bac124f28e277d109f0ff965058'}, + 'pubcid': 'd8aa10fa-d86c-451d-aad8-5f16162a9e64', + 'sharedid': {'id': '01ESHXW4HD29KMF387T63JQ9H5', 'third': '01ESHXW4HD29KMF387T63JQ9H5'}, + 'tdid': 'D6885E90-2A7A-4E0F-87CB-7734ED1B99A3', + 'haloId': {} +} + const validBannerBidReq = { bidder: BIDDER_CODE, params: { @@ -18,6 +28,7 @@ const validBannerBidReq = { sizes: [[300, 250]], bidId: '263be71e91dd9d', auctionId: '9ad1fa8d-2297-4660-a018-b39945054746', + userId: userId, 'schain': { 'ver': '1.0', 'complete': 1, @@ -50,7 +61,8 @@ const validBannerBidRes = { adm: '
', cpm: 1.01, height: 250, - width: 300 + width: 300, + crid: 'test-crid' }; const invalidBannerBidRes = ''; @@ -112,6 +124,13 @@ const validNativeBidRes = { width: 1 }; +const gdprConsent = { + consentString: 'CO9rhBTO9rhBTAcABBENBCCsAP_AAH_AACiQHItf_X_fb3_j-_59_9t0eY1f9_7_v20zjgeds-8Nyd_X_L8X42M7vB36pq4KuR4Eu3LBIQdlHOHcTUmw6IkVqTPsbk2Mr7NKJ7PEinMbe2dYGH9_n9XTuZKY79_s___z__-__v__7_f_r-3_3_vp9V---3YHIgEmGpfARZiWOBJNGlUKIEIVxIdACACihGFomsICVwU7K4CP0EDABAagIwIgQYgoxZBAAAAAElEQEgB4IBEARAIAAQAqQEIACNAEFgBIGAQACgGhYARQBCBIQZHBUcpgQESLRQTyVgCUXexhhCGUUANAg4AA.YAAAAAAAAAAA', + vendorData: {}, + gdprApplies: true, + apiVersion: 2 +}; + describe('ucfunnel Adapter', function () { describe('request', function () { it('should validate bid request', function () { @@ -253,18 +272,18 @@ describe('ucfunnel Adapter', function () { describe('cookie sync', function () { describe('cookie sync iframe', function () { - const result = spec.getUserSyncs({'iframeEnabled': true}); + const result = spec.getUserSyncs({'iframeEnabled': true}, null, gdprConsent); it('should return cookie sync iframe info', function () { expect(result[0].type).to.equal('iframe'); - expect(result[0].url).to.equal('https://cdn.aralego.net/ucfad/cookie/sync.html'); + expect(result[0].url).to.equal('https://cdn.aralego.net/ucfad/cookie/sync.html?gdpr=1&euconsent-v2=CO9rhBTO9rhBTAcABBENBCCsAP_AAH_AACiQHItf_X_fb3_j-_59_9t0eY1f9_7_v20zjgeds-8Nyd_X_L8X42M7vB36pq4KuR4Eu3LBIQdlHOHcTUmw6IkVqTPsbk2Mr7NKJ7PEinMbe2dYGH9_n9XTuZKY79_s___z__-__v__7_f_r-3_3_vp9V---3YHIgEmGpfARZiWOBJNGlUKIEIVxIdACACihGFomsICVwU7K4CP0EDABAagIwIgQYgoxZBAAAAAElEQEgB4IBEARAIAAQAqQEIACNAEFgBIGAQACgGhYARQBCBIQZHBUcpgQESLRQTyVgCUXexhhCGUUANAg4AA.YAAAAAAAAAAA&'); }); }); describe('cookie sync image', function () { - const result = spec.getUserSyncs({'pixelEnabled': true}); + const result = spec.getUserSyncs({'pixelEnabled': true}, null, gdprConsent); it('should return cookie sync image info', function () { expect(result[0].type).to.equal('image'); - expect(result[0].url).to.equal('https://sync.aralego.com/idSync'); + expect(result[0].url).to.equal('https://sync.aralego.com/idSync?gdpr=1&euconsent-v2=CO9rhBTO9rhBTAcABBENBCCsAP_AAH_AACiQHItf_X_fb3_j-_59_9t0eY1f9_7_v20zjgeds-8Nyd_X_L8X42M7vB36pq4KuR4Eu3LBIQdlHOHcTUmw6IkVqTPsbk2Mr7NKJ7PEinMbe2dYGH9_n9XTuZKY79_s___z__-__v__7_f_r-3_3_vp9V---3YHIgEmGpfARZiWOBJNGlUKIEIVxIdACACihGFomsICVwU7K4CP0EDABAagIwIgQYgoxZBAAAAAElEQEgB4IBEARAIAAQAqQEIACNAEFgBIGAQACgGhYARQBCBIQZHBUcpgQESLRQTyVgCUXexhhCGUUANAg4AA.YAAAAAAAAAAA&'); }); }); }); diff --git a/test/spec/modules/unicornBidAdapter_spec.js b/test/spec/modules/unicornBidAdapter_spec.js index 4c56c37700b..dcd446b2bb0 100644 --- a/test/spec/modules/unicornBidAdapter_spec.js +++ b/test/spec/modules/unicornBidAdapter_spec.js @@ -11,12 +11,12 @@ const bidRequests = [ }, mediaTypes: { banner: { - sizes: [[300, 250]] + sizes: [[300, 250], [336, 280]] } }, adUnitCode: '/19968336/header-bid-tag-0', transactionId: 'ea0aa332-a6e1-4474-8180-83720e6b87bc', - sizes: [[300, 250]], + sizes: [[300, 250], [336, 280]], bidId: '226416e6e6bf41', bidderRequestId: '1f41cbdcbe58d5', auctionId: '77987c3a-9be9-4e43-985a-26fc91d84724', @@ -81,12 +81,12 @@ const validBidRequests = [ }, mediaTypes: { banner: { - sizes: [[300, 250]] + sizes: [[300, 250], [336, 280]] } }, adUnitCode: '/19968336/header-bid-tag-0', transactionId: 'fbf94ccf-f377-4201-a662-32c2feb8ab6d', - sizes: [[300, 250]], + sizes: [[300, 250], [336, 280]], bidId: '2fb90842443e24', bidderRequestId: '123ae4cc3eeb7e', auctionId: 'c594a888-6744-46c6-8b0e-d188e40e83ef', @@ -156,12 +156,12 @@ const bidderRequest = { }, mediaTypes: { banner: { - sizes: [[300, 250]] + sizes: [[300, 250], [336, 280]] } }, adUnitCode: '/19968336/header-bid-tag-0', transactionId: 'fbf94ccf-f377-4201-a662-32c2feb8ab6d', - sizes: [[300, 250]], + sizes: [[300, 250], [336, 280]], bidId: '2fb90842443e24', bidderRequestId: '123ae4cc3eeb7e', auctionId: 'c594a888-6744-46c6-8b0e-d188e40e83ef', @@ -234,8 +234,18 @@ const openRTBRequest = { { id: '216255f234b602', banner: { - w: '300', - h: '250' + w: 300, + h: 250, + format: [ + { + w: 300, + h: 250 + }, + { + w: 336, + h: 280 + } + ] }, secure: 1, bidfloor: 0, @@ -244,8 +254,14 @@ const openRTBRequest = { { id: '31e2b28ced2475', banner: { - w: '300', - h: '250' + w: 300, + h: 250, + format: [ + { + w: 300, + h: 250 + } + ] }, secure: 1, bidfloor: 0, @@ -254,8 +270,14 @@ const openRTBRequest = { { id: '40a333e047a9bd', banner: { - w: '300', - h: '250' + w: 300, + h: 250, + format: [ + { + w: 300, + h: 250 + } + ] }, secure: 1, bidfloor: 0, @@ -287,7 +309,8 @@ const openRTBRequest = { source: { ext: { stype: 'prebid_uncn', - bidder: 'unicorn' + bidder: 'unicorn', + prebid_version: '1.0' } } }; @@ -378,7 +401,7 @@ const request = { method: 'POST', url: 'https://ds.uncn.jp/pb/0/bid.json', data: - '{"id":"5ebea288-f13a-4754-be6d-4ade66c68877","at":1,"imp":[{"id":"216255f234b602","banner":{"w":"300","h":"250"},"secure":1,"bidfloor":0,"tagid":"/19968336/header-bid-tag-0"},{"id":"31e2b28ced2475","banner":{"w":"300","h":"250"},"secure":1,"bidfloor":0"tagid":"/19968336/header-bid-tag-1"},{"id":"40a333e047a9bd","banner":{"w":"300","h":"250"},"secure":1,"bidfloor":0,"tagid":"/19968336/header-bid-tag-2"}],"cur":"JPY","site":{"id":"uni-corn.net","publisher":{"id":12345},"domain":"uni-corn.net","page":"https://uni-corn.net/","ref":"https://uni-corn.net/"},"device":{"language":"ja","ua":"Mozilla/5.0 (Linux; Android 8.0.0; ONEPLUS A5000) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.93 Mobile Safari/537.36"},"user":{"id":"69d9e1c2-801e-4901-a665-fad467550fec"},"bcat":[],"source":{"ext":{"stype":"prebid_uncn","bidder":"unicorn"}}}' + '{"id":"5ebea288-f13a-4754-be6d-4ade66c68877","at":1,"imp":[{"id":"216255f234b602","banner":{"w":300,"h":250},"format":[{"w":300,"h":250},{"w":336,"h":280}],"secure":1,"bidfloor":0,"tagid":"/19968336/header-bid-tag-0"},{"id":"31e2b28ced2475","banner":{"w":"300","h":"250"},"format":[{"w":"300","h":"250"}],"secure":1,"bidfloor":0"tagid":"/19968336/header-bid-tag-1"},{"id":"40a333e047a9bd","banner":{"w":300,"h":250},"format":[{"w":300,"h":250}],"secure":1,"bidfloor":0,"tagid":"/19968336/header-bid-tag-2"}],"cur":"JPY","site":{"id":"uni-corn.net","publisher":{"id":12345},"domain":"uni-corn.net","page":"https://uni-corn.net/","ref":"https://uni-corn.net/"},"device":{"language":"ja","ua":"Mozilla/5.0 (Linux; Android 8.0.0; ONEPLUS A5000) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.93 Mobile Safari/537.36"},"user":{"id":"69d9e1c2-801e-4901-a665-fad467550fec"},"bcat":[],"source":{"ext":{"stype":"prebid_uncn","bidder":"unicorn","prebid_version":"1.0"}}}' }; const interpretedBids = [ diff --git a/test/spec/modules/userId_spec.js b/test/spec/modules/userId_spec.js index 981ebb5f50e..3c852f3af5c 100644 --- a/test/spec/modules/userId_spec.js +++ b/test/spec/modules/userId_spec.js @@ -8,7 +8,8 @@ import { setStoredValue, setSubmoduleRegistry, syncDelay, - PBJS_USER_ID_OPTOUT_NAME + PBJS_USER_ID_OPTOUT_NAME, + findRootDomain, } from 'modules/userId/index.js'; import {createEidsArray} from 'modules/userId/eids.js'; import {config} from 'src/config.js'; @@ -22,6 +23,7 @@ import { setConsentConfig } from 'modules/consentManagement.js'; import {server} from 'test/mocks/xhr.js'; +import find from 'core-js-pure/features/array/find.js'; import {unifiedIdSubmodule} from 'modules/unifiedIdSystem.js'; import {pubCommonIdSubmodule} from 'modules/pubCommonIdSystem.js'; import {britepoolIdSubmodule} from 'modules/britepoolIdSystem.js'; @@ -30,12 +32,18 @@ import {identityLinkSubmodule} from 'modules/identityLinkIdSystem.js'; import {liveIntentIdSubmodule} from 'modules/liveIntentIdSystem.js'; import {merkleIdSubmodule} from 'modules/merkleIdSystem.js'; import {netIdSubmodule} from 'modules/netIdSystem.js'; +import {nextrollIdSubmodule} from 'modules/nextrollIdSystem.js'; import {intentIqIdSubmodule} from 'modules/intentIqIdSystem.js'; import {zeotapIdPlusSubmodule} from 'modules/zeotapIdPlusIdSystem.js'; import {sharedIdSubmodule} from 'modules/sharedIdSystem.js'; import {haloIdSubmodule} from 'modules/haloIdSystem.js'; import {pubProvidedIdSubmodule} from 'modules/pubProvidedIdSystem.js'; import {criteoIdSubmodule} from 'modules/criteoIdSystem.js'; +import {mwOpenLinkIdSubModule} from 'modules/mwOpenLinkIdSystem.js'; +import {tapadIdSubmodule} from 'modules/tapadIdSystem.js'; +import {getPrebidInternal} from 'src/utils.js'; +import {uid2IdSubmodule} from 'modules/uid2IdSystem.js'; +import {admixerIdSubmodule} from 'modules/admixerIdSystem.js'; let assert = require('chai').assert; let expect = require('chai').expect; @@ -67,7 +75,7 @@ describe('User ID', function () { code, mediaTypes: {banner: {}, native: {}}, sizes: [[300, 200], [300, 600]], - bids: [{bidder: 'sampleBidder', params: {placementId: 'banner-only-bidder'}}] + bids: [{bidder: 'sampleBidder', params: {placementId: 'banner-only-bidder'}}, {bidder: 'anotherSampleBidder', params: {placementId: 'banner-only-bidder'}}] }; } @@ -86,7 +94,7 @@ describe('User ID', function () { } function findEid(eids, source) { - return eids.find((eid) => { + return find(eids, (eid) => { if (eid.source === source) { return true; } }); } @@ -454,7 +462,7 @@ describe('User ID', function () { }); it('handles config with no usersync object', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule]); init(config); config.setConfig({}); // usersync is undefined, and no logInfo message for 'User ID - usersync config updated' @@ -462,14 +470,14 @@ describe('User ID', function () { }); it('handles config with empty usersync object', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule]); init(config); config.setConfig({userSync: {}}); expect(typeof utils.logInfo.args[0]).to.equal('undefined'); }); it('handles config with usersync and userIds that are empty objs', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, nextrollIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule]); init(config); config.setConfig({ userSync: { @@ -480,7 +488,7 @@ describe('User ID', function () { }); it('handles config with usersync and userIds with empty names or that dont match a submodule.name', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, nextrollIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule]); init(config); config.setConfig({ userSync: { @@ -497,15 +505,15 @@ describe('User ID', function () { }); it('config with 1 configurations should create 1 submodules', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, nextrollIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule]); init(config); config.setConfig(getConfigMock(['unifiedId', 'unifiedid', 'cookie'])); expect(utils.logInfo.args[0][0]).to.exist.and.to.contain('User ID - usersync config updated for 1 submodules'); }); - it('config with 13 configurations should result in 13 submodules add', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, liveIntentIdSubmodule, britepoolIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule]); + it('config with 17 configurations should result in 18 submodules add', function () { + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, liveIntentIdSubmodule, britepoolIdSubmodule, netIdSubmodule, nextrollIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule]); init(config); config.setConfig({ userSync: { @@ -532,6 +540,8 @@ describe('User ID', function () { }, { name: 'netId', storage: {name: 'netId', type: 'cookie'} + }, { + name: 'nextrollId' }, { name: 'sharedId', storage: {name: 'sharedid', type: 'cookie'} @@ -545,14 +555,24 @@ describe('User ID', function () { name: 'zeotapIdPlus' }, { name: 'criteo' + }, { + name: 'mwOpenLinkId' + }, { + name: 'tapadId', + storage: {name: 'tapad_id', type: 'cookie'} + }, { + name: 'uid2' + }, { + name: 'admixerId', + storage: {name: 'admixerId', type: 'cookie'} }] } }); - expect(utils.logInfo.args[0][0]).to.exist.and.to.contain('User ID - usersync config updated for 13 submodules'); + expect(utils.logInfo.args[0][0]).to.exist.and.to.contain('User ID - usersync config updated for 18 submodules'); }); it('config syncDelay updates module correctly', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, nextrollIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule]); init(config); config.setConfig({ userSync: { @@ -567,7 +587,7 @@ describe('User ID', function () { }); it('config auctionDelay updates module correctly', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, nextrollIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule]); init(config); config.setConfig({ userSync: { @@ -582,7 +602,7 @@ describe('User ID', function () { }); it('config auctionDelay defaults to 0 if not a number', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, nextrollIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule]); init(config); config.setConfig({ userSync: { @@ -928,7 +948,7 @@ describe('User ID', function () { expect(bid.userId.idl_env).to.equal('AiGNC8Z5ONyZKSpIPf'); expect(bid.userIdAsEids[0]).to.deep.equal({ source: 'liveramp.com', - uids: [{id: 'AiGNC8Z5ONyZKSpIPf', atype: 1}] + uids: [{id: 'AiGNC8Z5ONyZKSpIPf', atype: 3}] }); }); }); @@ -952,7 +972,7 @@ describe('User ID', function () { expect(bid.userId.idl_env).to.equal('AiGNC8Z5ONyZKSpIPf'); expect(bid.userIdAsEids[0]).to.deep.equal({ source: 'liveramp.com', - uids: [{id: 'AiGNC8Z5ONyZKSpIPf', atype: 1}] + uids: [{id: 'AiGNC8Z5ONyZKSpIPf', atype: 3}] }); }); }); @@ -984,6 +1004,29 @@ describe('User ID', function () { }, {adUnits}); }); + it('test hook from tapadIdModule cookie', function (done) { + coreStorage.setCookie('tapad_id', 'test-tapad-id', (new Date(Date.now() + 100000).toUTCString())); + + setSubmoduleRegistry([tapadIdSubmodule]); + init(config); + config.setConfig(getConfigMock(['tapadId', 'tapad_id', 'cookie'])); + + requestBidsHook(function () { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.tapadId'); + expect(bid.userId.tapadId).to.equal('test-tapad-id'); + expect(bid.userIdAsEids[0]).to.deep.equal({ + source: 'tapad.com', + uids: [{id: 'test-tapad-id', atype: 1}] + }); + }); + }) + coreStorage.setCookie('tapad_id', '', EXPIRED_COOKIE_DATE); + done(); + }, {adUnits}); + }); + it('test hook from liveIntentId html5', function (done) { // simulate existing browser local storage values localStorage.setItem('_li_pbid', JSON.stringify({'unifiedId': 'random-ls-identifier'})); @@ -999,7 +1042,7 @@ describe('User ID', function () { expect(bid.userId.lipb.lipbid).to.equal('random-ls-identifier'); expect(bid.userIdAsEids[0]).to.deep.equal({ source: 'liveintent.com', - uids: [{id: 'random-ls-identifier', atype: 1}] + uids: [{id: 'random-ls-identifier', atype: 3}] }); }); }); @@ -1023,7 +1066,7 @@ describe('User ID', function () { expect(bid.userId.lipb.lipbid).to.equal('random-cookie-identifier'); expect(bid.userIdAsEids[0]).to.deep.equal({ source: 'liveintent.com', - uids: [{id: 'random-cookie-identifier', atype: 1}] + uids: [{id: 'random-cookie-identifier', atype: 3}] }); }); }); @@ -1167,6 +1210,133 @@ describe('User ID', function () { }, {adUnits}); }); + it('eidPermissions fun with bidders', function (done) { + coreStorage.setCookie('sharedid', JSON.stringify({ + 'id': 'test222', + 'ts': 1590525289611 + }), (new Date(Date.now() + 5000).toUTCString())); + + setSubmoduleRegistry([sharedIdSubmodule]); + let eidPermissions; + getPrebidInternal().setEidPermissions = function (newEidPermissions) { + eidPermissions = newEidPermissions; + } + init(config); + config.setConfig({ + userSync: { + syncDelay: 0, + userIds: [ + { + name: 'sharedId', + bidders: [ + 'sampleBidder' + ], + storage: { + type: 'cookie', + name: 'sharedid', + expires: 28 + } + } + ] + } + }); + + requestBidsHook(function () { + expect(eidPermissions).to.deep.equal( + [ + { + bidders: [ + 'sampleBidder' + ], + source: 'sharedid.org' + } + ] + ); + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + if (bid.bidder === 'sampleBidder') { + expect(bid).to.have.deep.nested.property('userId.sharedid'); + expect(bid.userId.sharedid.id).to.equal('test222'); + expect(bid.userIdAsEids[0]).to.deep.equal({ + source: 'sharedid.org', + uids: [ + { + id: 'test222', + atype: 1, + ext: { + third: 'test222' + } + } + ] + }); + } + if (bid.bidder === 'anotherSampleBidder') { + expect(bid).to.not.have.deep.nested.property('userId.sharedid'); + expect(bid).to.not.have.property('userIdAsEids'); + } + }); + }); + coreStorage.setCookie('sharedid', '', EXPIRED_COOKIE_DATE); + getPrebidInternal().setEidPermissions = undefined; + done(); + }, {adUnits}); + }); + + it('eidPermissions fun without bidders', function (done) { + coreStorage.setCookie('sharedid', JSON.stringify({ + 'id': 'test222', + 'ts': 1590525289611 + }), (new Date(Date.now() + 5000).toUTCString())); + + setSubmoduleRegistry([sharedIdSubmodule]); + let eidPermissions; + getPrebidInternal().setEidPermissions = function (newEidPermissions) { + eidPermissions = newEidPermissions; + } + init(config); + config.setConfig({ + userSync: { + syncDelay: 0, + userIds: [ + { + name: 'sharedId', + storage: { + type: 'cookie', + name: 'sharedid', + expires: 28 + } + } + ] + } + }); + + requestBidsHook(function () { + expect(eidPermissions).to.deep.equal( + [] + ); + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.sharedid'); + expect(bid.userId.sharedid.id).to.equal('test222'); + expect(bid.userIdAsEids[0]).to.deep.equal({ + source: 'sharedid.org', + uids: [ + { + id: 'test222', + atype: 1, + ext: { + third: 'test222' + } + }] + }); + }); + }); + getPrebidInternal().setEidPermissions = undefined; + coreStorage.setCookie('sharedid', '', EXPIRED_COOKIE_DATE); + done(); + }, {adUnits}); + }); + it('test hook from pubProvidedId config params', function (done) { setSubmoduleRegistry([pubProvidedIdSubmodule]); init(config); @@ -1280,7 +1450,7 @@ describe('User ID', function () { expect(bid.userId.lipb.segments).to.include('123'); expect(bid.userIdAsEids[0]).to.deep.equal({ source: 'liveintent.com', - uids: [{id: 'random-ls-identifier', atype: 1}], + uids: [{id: 'random-ls-identifier', atype: 3}], ext: {segments: ['123']} }); }); @@ -1309,7 +1479,7 @@ describe('User ID', function () { expect(bid.userId.lipb.segments).to.include('123'); expect(bid.userIdAsEids[0]).to.deep.equal({ source: 'liveintent.com', - uids: [{id: 'random-cookie-identifier', atype: 1}], + uids: [{id: 'random-cookie-identifier', atype: 3}], ext: {segments: ['123']} }); }); @@ -1334,7 +1504,7 @@ describe('User ID', function () { expect(bid.userId.britepoolid).to.equal('279c0161-5152-487f-809e-05d7f7e653fd'); expect(bid.userIdAsEids[0]).to.deep.equal({ source: 'britepool.com', - uids: [{id: '279c0161-5152-487f-809e-05d7f7e653fd', atype: 1}] + uids: [{id: '279c0161-5152-487f-809e-05d7f7e653fd', atype: 3}] }); }); }); @@ -1419,7 +1589,7 @@ describe('User ID', function () { it('test hook from merkleId cookies', function (done) { // simulate existing browser local storage values - coreStorage.setCookie('merkleId', JSON.stringify({'ppid': {'id': 'testmerkleId'}}), (new Date(Date.now() + 5000).toUTCString())); + coreStorage.setCookie('merkleId', JSON.stringify({'pam_id': {'id': 'testmerkleId', 'keyID': 1}}), (new Date(Date.now() + 5000).toUTCString())); setSubmoduleRegistry([merkleIdSubmodule]); init(config); @@ -1429,10 +1599,10 @@ describe('User ID', function () { adUnits.forEach(unit => { unit.bids.forEach(bid => { expect(bid).to.have.deep.nested.property('userId.merkleId'); - expect(bid.userId.merkleId).to.equal('testmerkleId'); + expect(bid.userId.merkleId).to.deep.equal({'id': 'testmerkleId', 'keyID': 1}); expect(bid.userIdAsEids[0]).to.deep.equal({ source: 'merkleinc.com', - uids: [{id: 'testmerkleId', atype: 1}] + uids: [{id: 'testmerkleId', atype: 3, ext: {keyID: 1}}] }); }); }); @@ -1465,7 +1635,74 @@ describe('User ID', function () { }, {adUnits}); }); - it('test hook when pubCommonId, unifiedId, id5Id, identityLink, britepoolId, intentIqId, zeotapIdPlus, sharedId, netId, haloId and Criteo have data to pass', function (done) { + it('test hook from mwOpenLinkId cookies', function (done) { + // simulate existing browser local storage values + coreStorage.setCookie('mwol', JSON.stringify({eid: 'XX-YY-ZZ-123'}), (new Date(Date.now() + 5000).toUTCString())); + + setSubmoduleRegistry([mwOpenLinkIdSubModule]); + init(config); + config.setConfig(getConfigMock(['mwOpenLinkId', 'mwol', 'cookie'])); + + requestBidsHook(function () { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.mwOpenLinkId'); + expect(bid.userId.mwOpenLinkId).to.equal('XX-YY-ZZ-123'); + }); + }); + coreStorage.setCookie('mwol', '', EXPIRED_COOKIE_DATE); + done(); + }, {adUnits}); + }); + + it('test hook from admixerId html5', function (done) { + // simulate existing browser local storage values + localStorage.setItem('admixerId', 'testadmixerId'); + localStorage.setItem('admixerId_exp', ''); + + setSubmoduleRegistry([admixerIdSubmodule]); + init(config); + config.setConfig(getConfigMock(['admixerId', 'admixerId', 'html5'])); + requestBidsHook(function () { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.admixerId'); + expect(bid.userId.admixerId).to.equal('testadmixerId'); + expect(bid.userIdAsEids[0]).to.deep.equal({ + source: 'admixer.net', + uids: [{id: 'testadmixerId', atype: 3}] + }); + }); + }); + localStorage.removeItem('admixerId'); + done(); + }, {adUnits}); + }); + + it('test hook from admixerId cookie', function (done) { + coreStorage.setCookie('admixerId', 'testadmixerId', (new Date(Date.now() + 100000).toUTCString())); + + setSubmoduleRegistry([admixerIdSubmodule]); + init(config); + config.setConfig(getConfigMock(['admixerId', 'admixerId', 'cookie'])); + + requestBidsHook(function () { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.admixerId'); + expect(bid.userId.admixerId).to.equal('testadmixerId'); + expect(bid.userIdAsEids[0]).to.deep.equal({ + source: 'admixer.net', + uids: [{id: 'testadmixerId', atype: 3}] + }); + }); + }); + coreStorage.setCookie('admixerId', '', EXPIRED_COOKIE_DATE); + done(); + }, {adUnits}); + }); + + it('test hook when pubCommonId, unifiedId, id5Id, identityLink, britepoolId, intentIqId, zeotapIdPlus, sharedId, netId, haloId, Criteo, UID 2.0, admixerId and mwOpenLinkId have data to pass', function (done) { coreStorage.setCookie('pubcid', 'testpubcid', (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('unifiedid', JSON.stringify({'TDID': 'testunifiedid'}), (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('id5id', JSON.stringify({'universal_uid': 'testid5id'}), (new Date(Date.now() + 5000).toUTCString())); @@ -1480,8 +1717,11 @@ describe('User ID', function () { }), (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('haloId', JSON.stringify({'haloId': 'testHaloId'}), (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('storage_criteo', JSON.stringify({'criteoId': 'test_bidid'}), (new Date(Date.now() + 5000).toUTCString())); + coreStorage.setCookie('mwol', JSON.stringify({eid: 'XX-YY-ZZ-123'}), (new Date(Date.now() + 5000).toUTCString())); + coreStorage.setCookie('uid2id', 'Sample_AD_Token', (new Date(Date.now() + 5000).toUTCString())); + coreStorage.setCookie('admixerId', 'testadmixerId', (new Date(Date.now() + 5000).toUTCString())); - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, britepoolIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, criteoIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, britepoolIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule]); init(config); config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'cookie'], ['unifiedId', 'unifiedid', 'cookie'], @@ -1493,7 +1733,11 @@ describe('User ID', function () { ['intentIqId', 'intentIqId', 'cookie'], ['zeotapIdPlus', 'IDP', 'cookie'], ['haloId', 'haloId', 'cookie'], - ['criteo', 'storage_criteo', 'cookie'])); + ['criteo', 'storage_criteo', 'cookie'], + ['mwOpenLinkId', 'mwol', 'cookie'], + ['tapadId', 'tapad_id', 'cookie'], + ['uid2', 'uid2id', 'cookie'], + ['admixerId', 'admixerId', 'cookie'])); requestBidsHook(function () { adUnits.forEach(unit => { @@ -1533,8 +1777,17 @@ describe('User ID', function () { // also check that criteo id was copied to bid expect(bid).to.have.deep.nested.property('userId.criteoId'); expect(bid.userId.criteoId).to.equal('test_bidid'); + // also check that mwOpenLink id was copied to bid + expect(bid).to.have.deep.nested.property('userId.mwOpenLinkId'); + expect(bid.userId.mwOpenLinkId).to.equal('XX-YY-ZZ-123'); + expect(bid.userId.uid2).to.deep.equal({ + id: 'Sample_AD_Token' + }); + // also check that criteo id was copied to bid + expect(bid).to.have.deep.nested.property('userId.admixerId'); + expect(bid.userId.admixerId).to.equal('testadmixerId'); - expect(bid.userIdAsEids.length).to.equal(11); + expect(bid.userIdAsEids.length).to.equal(14); }); }); coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); @@ -1548,11 +1801,14 @@ describe('User ID', function () { coreStorage.setCookie('IDP', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('haloId', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('storage_criteo', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('mwol', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('uid2id', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('admixerId', '', EXPIRED_COOKIE_DATE); done(); }, {adUnits}); }); - it('test hook when pubCommonId, unifiedId, id5Id, britepoolId, intentIqId, zeotapIdPlus, sharedId, criteo, netId and haloId have their modules added before and after init', function (done) { + it('test hook when pubCommonId, unifiedId, id5Id, britepoolId, intentIqId, zeotapIdPlus, sharedId, criteo, netId, haloId, UID 2.0, admixerId and mwOpenLinkId have their modules added before and after init', function (done) { coreStorage.setCookie('pubcid', 'testpubcid', (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('unifiedid', JSON.stringify({'TDID': 'cookie-value-add-module-variations'}), new Date(Date.now() + 5000).toUTCString()); coreStorage.setCookie('id5id', JSON.stringify({'universal_uid': 'testid5id'}), (new Date(Date.now() + 5000).toUTCString())); @@ -1567,6 +1823,9 @@ describe('User ID', function () { coreStorage.setCookie('IDP', btoa(JSON.stringify('zeotapId')), (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('haloId', JSON.stringify({'haloId': 'testHaloId'}), (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('storage_criteo', JSON.stringify({'criteoId': 'test_bidid'}), (new Date(Date.now() + 5000).toUTCString())); + coreStorage.setCookie('mwol', JSON.stringify({eid: 'XX-YY-ZZ-123'}), (new Date(Date.now() + 5000).toUTCString())); + coreStorage.setCookie('uid2id', 'Sample_AD_Token', (new Date(Date.now() + 5000).toUTCString())); + coreStorage.setCookie('admixerId', 'testadmixerId', (new Date(Date.now() + 5000).toUTCString())); setSubmoduleRegistry([]); @@ -1586,6 +1845,10 @@ describe('User ID', function () { attachIdSystem(zeotapIdPlusSubmodule); attachIdSystem(haloIdSubmodule); attachIdSystem(criteoIdSubmodule); + attachIdSystem(mwOpenLinkIdSubModule); + attachIdSystem(tapadIdSubmodule); + attachIdSystem(uid2IdSubmodule); + attachIdSystem(admixerIdSubmodule); config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'cookie'], ['unifiedId', 'unifiedid', 'cookie'], @@ -1597,7 +1860,11 @@ describe('User ID', function () { ['intentIqId', 'intentIqId', 'cookie'], ['zeotapIdPlus', 'IDP', 'cookie'], ['haloId', 'haloId', 'cookie'], - ['criteo', 'storage_criteo', 'cookie'])); + ['criteo', 'storage_criteo', 'cookie'], + ['mwOpenLinkId', 'mwol', 'cookie'], + ['tapadId', 'tapad_id', 'cookie'], + ['uid2', 'uid2id', 'cookie'], + ['admixerId', 'admixerId', 'cookie'])); requestBidsHook(function () { adUnits.forEach(unit => { @@ -1640,7 +1907,18 @@ describe('User ID', function () { expect(bid).to.have.deep.nested.property('userId.criteoId'); expect(bid.userId.criteoId).to.equal('test_bidid'); - expect(bid.userIdAsEids.length).to.equal(11); + // also check that mwOpenLink id data was copied to bid + expect(bid).to.have.deep.nested.property('userId.mwOpenLinkId'); + expect(bid.userId.mwOpenLinkId).to.equal('XX-YY-ZZ-123') + expect(bid.userId.uid2).to.deep.equal({ + id: 'Sample_AD_Token' + }); + + // also check that admixerId id data was copied to bid + expect(bid).to.have.deep.nested.property('userId.admixerId'); + expect(bid.userId.admixerId).to.equal('testadmixerId'); + + expect(bid.userIdAsEids.length).to.equal(14); }); }); coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); @@ -1654,6 +1932,9 @@ describe('User ID', function () { coreStorage.setCookie('IDP', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('haloId', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('storage_criteo', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('mwol', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('uid2id', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('admixerId', '', EXPIRED_COOKIE_DATE); done(); }, {adUnits}); }); @@ -1868,6 +2149,34 @@ describe('User ID', function () { }, {adUnits}); }); + it('test hook from UID2 cookie', function (done) { + coreStorage.setCookie('uid2id', 'Sample_AD_Token', (new Date(Date.now() + 5000).toUTCString())); + + setSubmoduleRegistry([uid2IdSubmodule]); + init(config); + config.setConfig(getConfigMock(['uid2', 'uid2id', 'cookie'])); + + requestBidsHook(function () { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.uid2'); + expect(bid.userId.uid2).to.have.deep.nested.property('id'); + expect(bid.userId.uid2).to.deep.equal({ + id: 'Sample_AD_Token' + }); + expect(bid.userIdAsEids[0]).to.deep.equal({ + source: 'uidapi.com', + uids: [{ + id: 'Sample_AD_Token', + atype: 3, + }] + }); + }); + }); + coreStorage.setCookie('uid2id', '', EXPIRED_COOKIE_DATE); + done(); + }, {adUnits}); + }); it('should add new id system ', function (done) { coreStorage.setCookie('pubcid', 'testpubcid', (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('unifiedid', JSON.stringify({'TDID': 'cookie-value-add-module-variations'}), new Date(Date.now() + 5000).toUTCString()); @@ -1882,9 +2191,11 @@ describe('User ID', function () { coreStorage.setCookie('intentIqId', 'testintentIqId', (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('IDP', btoa(JSON.stringify('zeotapId')), (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('haloId', JSON.stringify({'haloId': 'testHaloId'}), (new Date(Date.now() + 5000).toUTCString())); + coreStorage.setCookie('admixerId', 'testadmixerId', new Date(Date.now() + 5000).toUTCString()); coreStorage.setCookie('MOCKID', JSON.stringify({'MOCKID': '123456778'}), new Date(Date.now() + 5000).toUTCString()); + coreStorage.setCookie('__uid2_advertising_token', 'Sample_AD_Token', (new Date(Date.now() + 5000).toUTCString())); - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, britepoolIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, britepoolIdSubmodule, netIdSubmodule, sharedIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, uid2IdSubmodule, admixerIdSubmodule]); init(config); config.setConfig({ @@ -1910,8 +2221,12 @@ describe('User ID', function () { name: 'zeotapIdPlus' }, { name: 'haloId', storage: {name: 'haloId', type: 'cookie'} + }, { + name: 'admixerId', storage: {name: 'admixerId', type: 'cookie'} }, { name: 'mockId', storage: {name: 'MOCKID', type: 'cookie'} + }, { + name: 'uid2' }] } }); @@ -1969,7 +2284,14 @@ describe('User ID', function () { // also check that haloId id data was copied to bid expect(bid).to.have.deep.nested.property('userId.haloId'); expect(bid.userId.haloId).to.equal('testHaloId'); - expect(bid.userIdAsEids.length).to.equal(10); + expect(bid.userId.uid2).to.deep.equal({ + id: 'Sample_AD_Token' + }); + + // also check that admixerId id data was copied to bid + expect(bid).to.have.deep.nested.property('userId.admixerId'); + expect(bid.userId.admixerId).to.equal('testadmixerId'); + expect(bid.userIdAsEids.length).to.equal(12); }); }); coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); @@ -1982,7 +2304,9 @@ describe('User ID', function () { coreStorage.setCookie('intentIqId', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('IDP', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('haloId', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('admixerId', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('MOCKID', '', EXPIRED_COOKIE_DATE); + coreStorage.setCookie('mwol', '', EXPIRED_COOKIE_DATE); done(); }, {adUnits}); }); @@ -2005,6 +2329,8 @@ describe('User ID', function () { coreStorage.setCookie('pubcid_sharedid', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('unifiedid', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('_parrable_eid', '', EXPIRED_COOKIE_DATE); + resetConsentData(); + delete window.__tcfapi; }); it('pubcid callback with url', function () { @@ -2139,12 +2465,65 @@ describe('User ID', function () { expect(server.requests[0].url).to.equal('https://id.sharedid.org/id'); expect(coreStorage.getCookie('pubcid_sharedid')).to.be.null; }); + + it('verify sharedid called with consent data when gdpr applies', function () { + let adUnits = [getAdUnitMock()]; + let customCfg = getConfigMock(['pubCommonId', 'pubcid', 'cookie']); + let consentConfig = { + cmpApi: 'iab', + timeout: 7500, + allowAuctionWithoutConsent: false + }; + customCfg = addConfig(customCfg, 'params', {pixelUrl: '/any/pubcid/url', enableSharedId: true}); + + server.respondWith('https://id.sharedid.org/id?gdpr=1&gdpr_consent=abc12345234', function(xhr) { + xhr.respond(200, {}, '{"sharedId":"testsharedid"}'); + }); + server.respondImmediately = true; + + let testConsentData = { + tcString: 'abc12345234', + gdprApplies: true, + purposeOneTreatment: false, + eventStatus: 'tcloaded', + vendor: {consents: {887: true}}, + purpose: { + consents: { + 1: true + } + } + }; + + window.__tcfapi = function () { }; + sinon.stub(window, '__tcfapi').callsFake((...args) => { + args[2](testConsentData, true); + }); + + setSubmoduleRegistry([pubCommonIdSubmodule]); + init(config); + config.setConfig(customCfg); + setConsentConfig(consentConfig); + + consentManagementRequestBidsHook(() => { + }, {}); + requestBidsHook((config) => { + }, {adUnits}); + + expect(utils.triggerPixel.called).to.be.false; + events.emit(CONSTANTS.EVENTS.AUCTION_END, {}); + expect(utils.triggerPixel.getCall(0).args[0]).to.include('/any/pubcid/url'); + + expect(server.requests[0].url).to.equal('https://id.sharedid.org/id?gdpr=1&gdpr_consent=abc12345234'); + expect(coreStorage.getCookie('pubcid_sharedid')).to.equal('testsharedid'); + }); }); describe('Set cookie behavior', function () { let coreStorageSpy; beforeEach(function () { coreStorageSpy = sinon.spy(coreStorage, 'setCookie'); + setSubmoduleRegistry([pubCommonIdSubmodule]); + init(config); }); afterEach(function () { coreStorageSpy.restore(); @@ -2360,5 +2739,46 @@ describe('User ID', function () { sinon.assert.calledOnce(mockExtendId); }); }); + + describe('findRootDomain', function () { + let sandbox; + + beforeEach(function () { + setSubmoduleRegistry([pubCommonIdSubmodule]); + init(config); + config.setConfig({ + userSync: { + syncDelay: 0, + userIds: [ + { + name: 'pubCommonId', + value: { pubcid: '11111' }, + }, + ], + }, + }); + sandbox = sinon.createSandbox(); + sandbox + .stub(coreStorage, 'getCookie') + .onFirstCall() + .returns(null) // .co.uk + .onSecondCall() + .returns('writeable'); // realdomain.co.uk; + }); + + afterEach(function () { + sandbox.restore(); + }); + + it('should just find the root domain', function () { + var domain = findRootDomain('sub.realdomain.co.uk'); + expect(domain).to.be.eq('realdomain.co.uk'); + }); + + it('should find the full domain when no subdomain is present', function () { + var domain = findRootDomain('realdomain.co.uk'); + expect(domain).to.be.eq('realdomain.co.uk'); + }); + }); }); }); diff --git a/test/spec/modules/vdoaiBidAdapter_spec.js b/test/spec/modules/vdoaiBidAdapter_spec.js index 5318bb43eca..d78a5ce04f6 100644 --- a/test/spec/modules/vdoaiBidAdapter_spec.js +++ b/test/spec/modules/vdoaiBidAdapter_spec.js @@ -8,7 +8,7 @@ describe('vdoaiBidAdapter', function () { const adapter = newBidder(spec); describe('isBidRequestValid', function () { let bid = { - 'bidder': 'vdo.ai', + 'bidder': 'vdoai', 'params': { placementId: 'testPlacementId' }, @@ -27,7 +27,7 @@ describe('vdoaiBidAdapter', function () { describe('buildRequests', function () { let bidRequests = [ { - 'bidder': 'vdo.ai', + 'bidder': 'vdoai', 'params': { placementId: 'testPlacementId' }, diff --git a/test/spec/modules/verizonMediaIdSystem_spec.js b/test/spec/modules/verizonMediaIdSystem_spec.js index c5d743235d6..10054b5674c 100644 --- a/test/spec/modules/verizonMediaIdSystem_spec.js +++ b/test/spec/modules/verizonMediaIdSystem_spec.js @@ -87,7 +87,7 @@ describe('Verizon Media ID Submodule', () => { pixelId: PIXEL_ID, '1p': '0', gdpr: '1', - euconsent: consentData.gdpr.consentString, + gdpr_consent: consentData.gdpr.consentString, us_privacy: consentData.uspConsent }; const requestQueryParams = utils.parseQS(ajaxStub.firstCall.args[0].split('?')[1]); @@ -107,7 +107,7 @@ describe('Verizon Media ID Submodule', () => { he: HASHED_EMAIL, '1p': '0', gdpr: '1', - euconsent: consentData.gdpr.consentString, + gdpr_consent: consentData.gdpr.consentString, us_privacy: consentData.uspConsent }; const requestQueryParams = utils.parseQS(ajaxStub.firstCall.args[0].split('?')[1]); @@ -134,7 +134,7 @@ describe('Verizon Media ID Submodule', () => { const requestQueryParams = utils.parseQS(ajaxStub.firstCall.args[0].split('?')[1]); expect(requestQueryParams.gdpr).to.equal('1'); - expect(requestQueryParams.euconsent).to.equal(consentData.gdpr.consentString); + expect(requestQueryParams.gdpr_consent).to.equal(consentData.gdpr.consentString); }); it('sets GDPR consent data flag correctly when call is NOT under GDPR jurisdiction.', () => { @@ -147,7 +147,7 @@ describe('Verizon Media ID Submodule', () => { const requestQueryParams = utils.parseQS(ajaxStub.firstCall.args[0].split('?')[1]); expect(requestQueryParams.gdpr).to.equal('0'); - expect(requestQueryParams.euconsent).to.equal(''); + expect(requestQueryParams.gdpr_consent).to.equal(''); }); [1, '1', true].forEach(firstPartyParamValue => { diff --git a/test/spec/modules/voxBidAdapter_spec.js b/test/spec/modules/voxBidAdapter_spec.js new file mode 100644 index 00000000000..c6221cba9e5 --- /dev/null +++ b/test/spec/modules/voxBidAdapter_spec.js @@ -0,0 +1,320 @@ +import { expect } from 'chai' +import { spec } from 'modules/voxBidAdapter.js' + +function getSlotConfigs(mediaTypes, params) { + return { + params: params, + sizes: [], + bidId: '2df8c0733f284e', + bidder: 'vox', + mediaTypes: mediaTypes, + transactionId: '31a58515-3634-4e90-9c96-f86196db1459' + } +} + +describe('VOX Adapter', function() { + const PLACE_ID = '5af45ad34d506ee7acad0c26'; + const bidderRequest = { + refererInfo: { referer: 'referer' } + } + const bannerMandatoryParams = { + placementId: PLACE_ID, + placement: 'banner' + } + const videoMandatoryParams = { + placementId: PLACE_ID, + placement: 'video' + } + const inImageMandatoryParams = { + placementId: PLACE_ID, + placement: 'inImage', + imageUrl: 'https://hybrid.ai/images/image.jpg' + } + const validBidRequests = [ + getSlotConfigs({ banner: {} }, bannerMandatoryParams), + getSlotConfigs({ video: {playerSize: [[640, 480]], context: 'outstream'} }, videoMandatoryParams), + getSlotConfigs({ banner: {sizes: [0, 0]} }, inImageMandatoryParams) + ] + describe('isBidRequestValid method', function() { + describe('returns true', function() { + describe('when banner slot config has all mandatory params', () => { + describe('and banner placement has the correct value', function() { + const slotConfig = getSlotConfigs( + {banner: {}}, + { + placementId: PLACE_ID, + placement: 'banner' + } + ) + const isBidRequestValid = spec.isBidRequestValid(slotConfig) + expect(isBidRequestValid).to.equal(true) + }) + describe('and In-Image placement has the correct value', function() { + const slotConfig = getSlotConfigs( + { + banner: { + sizes: [[0, 0]] + } + }, + { + placementId: PLACE_ID, + placement: 'inImage', + imageUrl: 'imageUrl' + } + ) + const isBidRequestValid = spec.isBidRequestValid(slotConfig) + expect(isBidRequestValid).to.equal(true) + }) + describe('when video slot has all mandatory params.', function() { + it('should return true, when video mediatype object are correct.', function() { + const slotConfig = getSlotConfigs( + { + video: { + context: 'instream', + playerSize: [[640, 480]] + } + }, + { + placementId: PLACE_ID, + placement: 'video' + } + ) + const isBidRequestValid = spec.isBidRequestValid(slotConfig) + expect(isBidRequestValid).to.equal(true) + }) + }) + }) + }) + describe('returns false', function() { + describe('when params are not correct', function() { + function createSlotconfig(params) { + return getSlotConfigs({ banner: {} }, params) + } + it('does not have the placementId.', function() { + const isBidRequestValid = spec.isBidRequestValid( + createSlotconfig({ + placement: 'banner' + }) + ) + expect(isBidRequestValid).to.equal(false) + }) + it('does not have the placement.', function() { + const isBidRequestValid = spec.isBidRequestValid( + createSlotconfig({ + placementId: PLACE_ID + }) + ) + expect(isBidRequestValid).to.equal(false) + }) + it('does not have the imageUrl.', function() { + const isBidRequestValid = spec.isBidRequestValid( + createSlotconfig({ + placementId: PLACE_ID, + placement: 'inImage' + }) + ) + expect(isBidRequestValid).to.equal(false) + }) + it('does not have a the correct placement.', function() { + const isBidRequestValid = spec.isBidRequestValid( + createSlotconfig({ + placementId: PLACE_ID, + placement: 'something' + }) + ) + expect(isBidRequestValid).to.equal(false) + }) + }) + describe('when video mediaType object is not correct.', function() { + function createVideoSlotconfig(mediaType) { + return getSlotConfigs(mediaType, { + placementId: PLACE_ID, + placement: 'video' + }) + } + it('is a void object', function() { + const isBidRequestValid = spec.isBidRequestValid( + createVideoSlotconfig({ video: {} }) + ) + expect(isBidRequestValid).to.equal(false) + }) + it('does not have playerSize.', function() { + const isBidRequestValid = spec.isBidRequestValid( + createVideoSlotconfig({ video: { context: 'instream' } }) + ) + expect(isBidRequestValid).to.equal(false) + }) + it('does not have context', function() { + const isBidRequestValid = spec.isBidRequestValid( + createVideoSlotconfig({ + video: { + playerSize: [[640, 480]] + } + }) + ) + expect(isBidRequestValid).to.equal(false) + }) + }) + }) + }) + it('Url params should be correct ', function() { + const request = spec.buildRequests(validBidRequests, bidderRequest) + expect(request.method).to.equal('POST') + expect(request.url).to.equal('https://ssp.hybrid.ai/auction/prebid') + }) + + describe('buildRequests method', function() { + it('Common data request should be correct', function() { + const request = spec.buildRequests(validBidRequests, bidderRequest) + const data = JSON.parse(request.data) + expect(Array.isArray(data.bidRequests)).to.equal(true) + expect(data.url).to.equal('referer') + data.bidRequests.forEach(bid => { + expect(bid.bidId).to.equal('2df8c0733f284e') + expect(bid.placeId).to.equal(PLACE_ID) + expect(bid.transactionId).to.equal('31a58515-3634-4e90-9c96-f86196db1459') + }) + }) + + describe('GDPR params', function() { + describe('when there are not consent management platform', function() { + it('cmp should be false', function() { + const request = spec.buildRequests(validBidRequests, bidderRequest) + const data = JSON.parse(request.data) + expect(data.cmp).to.equal(false) + }) + }) + describe('when there are consent management platform', function() { + it('cmps should be true and ga should not sended, when gdprApplies is undefined', function() { + bidderRequest['gdprConsent'] = { + gdprApplies: undefined, + consentString: 'consentString' + } + const request = spec.buildRequests(validBidRequests, bidderRequest) + const data = JSON.parse(request.data) + expect(data.cmp).to.equal(true) + expect(Object.keys(data).indexOf('data')).to.equal(-1) + expect(data.cs).to.equal('consentString') + }) + it('cmps should be true and all gdpr parameters should be sended, when there are gdprApplies', function() { + bidderRequest['gdprConsent'] = { + gdprApplies: true, + consentString: 'consentString' + } + const request = spec.buildRequests(validBidRequests, bidderRequest) + const data = JSON.parse(request.data) + expect(data.cmp).to.equal(true) + expect(data.ga).to.equal(true) + expect(data.cs).to.equal('consentString') + }) + }) + }) + }) + + describe('interpret response method', function() { + it('should return a void array, when the server response are not correct.', function() { + const request = { data: JSON.stringify({}) } + const serverResponse = { + body: {} + } + const bids = spec.interpretResponse(serverResponse, request) + expect(typeof bids).to.equal('object') + expect(bids.length).to.equal(0) + }) + it('should return a void array, when the server response have not got bids.', function() { + const request = { data: JSON.stringify({}) } + const serverResponse = { body: { bids: [] } } + const bids = spec.interpretResponse(serverResponse, request) + expect(typeof bids).to.equal('object') + expect(bids.length).to.equal(0) + }) + describe('when the server response return a bid', function() { + describe('the bid is a banner', function() { + it('should return a banner bid', function() { + const request = spec.buildRequests([validBidRequests[0]], bidderRequest) + const serverResponse = { + body: { + bids: [ + { + bidId: '2df8c0733f284e', + price: 0.5, + currency: 'USD', + content: { + content: 'html', + width: 100, + height: 100 + } + } + ] + } + } + const bids = spec.interpretResponse(serverResponse, request) + expect(bids.length).to.equal(1) + expect(bids[0].requestId).to.equal('2df8c0733f284e') + expect(bids[0].mediaType).to.equal(spec.supportedMediaTypes[0]) + expect(bids[0].cpm).to.equal(0.5) + expect(bids[0].width).to.equal(100) + expect(bids[0].height).to.equal(100) + expect(bids[0].currency).to.equal('USD') + expect(bids[0].netRevenue).to.equal(true) + expect(typeof bids[0].ad).to.equal('string') + }) + it('should return a In-Image bid', function() { + const request = spec.buildRequests([validBidRequests[2]], bidderRequest) + const serverResponse = { + body: { + bids: [ + { + bidId: '2df8c0733f284e', + price: 0.5, + currency: 'USD', + content: { + content: 'html', + width: 100, + height: 100 + }, + ttl: 360 + } + ] + } + } + const bids = spec.interpretResponse(serverResponse, request) + expect(bids.length).to.equal(1) + expect(bids[0].requestId).to.equal('2df8c0733f284e') + expect(bids[0].cpm).to.equal(0.5) + expect(bids[0].width).to.equal(100) + expect(bids[0].height).to.equal(100) + expect(bids[0].currency).to.equal('USD') + expect(bids[0].netRevenue).to.equal(true) + expect(typeof bids[0].ad).to.equal('string') + }) + }) + describe('the bid is a video', function() { + it('should return a video bid', function() { + const request = spec.buildRequests([validBidRequests[1]], bidderRequest) + const serverResponse = { + body: { + bids: [ + { + bidId: '2df8c0733f284e', + price: 0.5, + currency: 'USD', + content: 'html', + transactionId: '31a58515-3634-4e90-9c96-f86196db1459' + } + ] + } + } + const bids = spec.interpretResponse(serverResponse, request) + expect(bids.length).to.equal(1) + expect(bids[0].requestId).to.equal('2df8c0733f284e') + expect(bids[0].mediaType).to.equal(spec.supportedMediaTypes[1]) + expect(bids[0].cpm).to.equal(0.5) + expect(bids[0].currency).to.equal('USD') + expect(bids[0].netRevenue).to.equal(true) + expect(typeof bids[0].vastXml).to.equal('string') + }) + }) + }) + }) +}) diff --git a/test/spec/modules/waardexBidAdapter_spec.js b/test/spec/modules/waardexBidAdapter_spec.js index 8732b2bd51f..73094dd72a0 100644 --- a/test/spec/modules/waardexBidAdapter_spec.js +++ b/test/spec/modules/waardexBidAdapter_spec.js @@ -1,51 +1,201 @@ import {expect} from 'chai'; import {spec} from '../../../modules/waardexBidAdapter.js'; -import { auctionManager } from 'src/auctionManager.js'; -import { deepClone } from 'src/utils.js'; +import {auctionManager} from 'src/auctionManager.js'; +import {deepClone} from 'src/utils.js'; describe('waardexBidAdapter', () => { - const validBid = { - bidId: '112435ry', - bidder: 'waardex', - params: { - placementId: 1, - traffic: 'banner', - zoneId: 1, - } - }; - describe('isBidRequestValid', () => { - it('Should return true. bidId and params such as placementId and zoneId are present', () => { - expect(spec.isBidRequestValid(validBid)).to.be.true; + it('should return true. bidId and params such as placementId and zoneId are present', () => { + const result = spec.isBidRequestValid({ + bidId: '112435ry', + bidder: 'waardex', + params: { + placementId: 1, + traffic: 'banner', + zoneId: 1, + } + }); + + expect(result).to.be.true; + }); + + it('should return false. bidId is not present in bid object', () => { + const result = spec.isBidRequestValid({ + bidder: 'waardex', + params: { + placementId: 1, + traffic: 'banner', + zoneId: 1, + } + }); + + expect(result).to.be.false; + }); + + it('should return false. zoneId is not present in bid.params object', () => { + const result = spec.isBidRequestValid({ + bidId: '112435ry', + bidder: 'waardex', + params: { + placementId: 1, + traffic: 'banner', + } + }); + + expect(result).to.be.false; + }); + + it('should return true when mediaTypes field is empty', () => { + const result = spec.isBidRequestValid({ + bidId: '112435ry', + bidder: 'waardex', + params: { + placementId: 1, + traffic: 'banner', + zoneId: 1, + } + }); + + expect(result).to.be.true; }); - it('Should return false. bidId is not present in bid object', () => { - const invalidBid = deepClone(validBid); - delete invalidBid.bidId; - expect(spec.isBidRequestValid(invalidBid)).to.be.false; + + it('should return false when mediaTypes.video.playerSize field is empty', () => { + const result = spec.isBidRequestValid({ + bidId: '112435ry', + bidder: 'waardex', + params: { + placementId: 1, + traffic: 'banner', + zoneId: 1, + }, + mediaTypes: { + video: {} + } + }); + + expect(result).to.be.false; }); - it('Should return false. zoneId is not present in bid.params object', () => { - const invalidBid = deepClone(validBid); - delete invalidBid.params.zoneId; - expect(spec.isBidRequestValid(invalidBid)).to.be.false; + + it('should return false when mediaTypes.video.playerSize field is not an array', () => { + const result = spec.isBidRequestValid({ + bidId: '112435ry', + bidder: 'waardex', + params: { + placementId: 1, + traffic: 'banner', + zoneId: 1, + }, + mediaTypes: { + video: { + playerSize: 'not-array' + } + } + }); + + expect(result).to.be.false; + }); + + it('should return false when mediaTypes.video.playerSize field is an empty array', () => { + const result = spec.isBidRequestValid({ + bidId: '112435ry', + bidder: 'waardex', + params: { + placementId: 1, + traffic: 'banner', + zoneId: 1, + }, + mediaTypes: { + video: { + playerSize: [] + } + } + }); + + expect(result).to.be.false; + }); + + it('should return false when mediaTypes.video.playerSize field is empty array', () => { + const result = spec.isBidRequestValid({ + bidId: '112435ry', + bidder: 'waardex', + params: { + placementId: 1, + traffic: 'banner', + zoneId: 1, + }, + mediaTypes: { + video: { + playerSize: [] + } + } + }); + + expect(result).to.be.false; + }); + + it('should return true when mediaTypes.video.playerSize field is non-empty array', () => { + const result = spec.isBidRequestValid({ + bidId: '112435ry', + bidder: 'waardex', + params: { + placementId: 1, + traffic: 'banner', + zoneId: 1, + }, + mediaTypes: { + video: { + playerSize: [[640, 400]] + } + } + }); + + expect(result).to.be.true; }); }); describe('buildRequests', () => { let getAdUnitsStub; - const validBidRequests = [{ - bidId: 'fergr675ujgh', - mediaTypes: { - banner: { - sizes: [[300, 600], [300, 250]] - } - }, - params: { - bidfloor: 1.5, - position: 1, - instl: 1, - zoneId: 100 + const validBidRequests = [ + { + bidId: 'fergr675ujgh', + mediaTypes: { + banner: { + sizes: [[300, 600], [300, 250]] + } + }, + params: { + bidfloor: 1.5, + position: 1, + instl: 1, + zoneId: 100 + }, }, - }]; + { + bidId: 'unique-bid-id-2', + mediaTypes: { + video: { + context: 'outstream', + playerSize: [[640, 400]] + } + }, + params: { + mimes: ['video/x-ms-wmv', 'video/mp4'], + minduration: 2, + maxduration: 10, + protocols: ['VAST 1.0', 'VAST 2.0'], + startdelay: -1, + placement: 1, + skip: 1, + skipafter: 2, + minbitrate: 0, + maxbitrate: 0, + delivery: [1, 2, 3], + playbackmethod: [1, 2], + api: [1, 2, 3, 4, 5, 6], + linearity: 1, + } + } + ]; const bidderRequest = { refererInfo: { @@ -57,12 +207,13 @@ describe('waardexBidAdapter', () => { afterEach(() => getAdUnitsStub.restore()); it('should return valid build request object', () => { - const request = spec.buildRequests(validBidRequests, bidderRequest); const { data: payload, url, method, - } = request; + } = spec.buildRequests(validBidRequests, bidderRequest); + + const ENDPOINT = `https://hb.justbidit.xyz:8843/prebid?pubId=${validBidRequests[0].params.zoneId}`; expect(payload.bidRequests[0]).deep.equal({ bidId: validBidRequests[0].bidId, @@ -82,7 +233,6 @@ describe('waardexBidAdapter', () => { ], } }); - const ENDPOINT = `https://hb.justbidit.xyz:8843/prebid?pubId=${validBidRequests[0].params.zoneId}`; expect(url).to.equal(ENDPOINT); expect(method).to.equal('POST'); }); @@ -91,44 +241,88 @@ describe('waardexBidAdapter', () => { describe('interpretResponse', () => { const serverResponse = { body: { - seatbid: [{ - bid: [{ - id: 'someId', - price: 3.3, - w: 250, - h: 300, - crid: 'dspCreativeIdHere', - adm: 'html markup here', - dealId: '123456789', - cid: 'dsp campaign id', - adomain: 'advertisers domain', - ext: { - mediaType: 'banner', - }, - }], - }], + seatbid: [ + { + bid: [ + { + 'id': 'ec90d4ee25433c0875c56f59acc12109', + 'adm': 'banner should be here', + 'w': 300, + 'h': 250, + 'price': 0.15827000000000002, + 'nurl': 'http://n63.justbidit.xyz?w=n&p=${AUCTION_PRICE}&t=b&uq=eb31018b3da8965dde414c0006b1fb31', + 'impid': 'unique-bid-id-1', + 'adomain': [ + 'www.kraeuterhaus.de' + ], + 'cat': [ + 'IAB24' + ], + 'attr': [ + 4 + ], + 'iurl': 'https://rtb.bsmartdata.com/preview.php?id=13', + 'cid': '286557fda4585dc1c7f98b773de92509', + 'crid': 'dd2b5a1f3f1fc5e63b042ebf4b00ca80' + }, + ], + } + ], }, }; + const request = { + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/86.0.4240.198 Safari/537.36', + 'language': 'en', + 'referer': 'https%3A%2F%2Fwww.google.com%2F%3Fsome_param%3Dsome_value', + 'coppa': false, + 'data': { + 'bidRequests': [ + { + 'bidId': 'unique-bid-id-1', + 'bidfloor': 0.1, + 'position': 1, + 'instl': 1, + 'banner': { + 'sizes': [ + { + 'width': 300, + 'height': 600 + }, + { + 'width': 300, + 'height': 250 + } + ] + } + }, + ] + } + }; + it('bid response is valid', () => { - const result = spec.interpretResponse(serverResponse); - const expected = [{ - requestId: serverResponse.body.seatbid[0].bid[0].id, - cpm: serverResponse.body.seatbid[0].bid[0].price, - currency: 'USD', - width: serverResponse.body.seatbid[0].bid[0].w, - height: serverResponse.body.seatbid[0].bid[0].h, - creativeId: serverResponse.body.seatbid[0].bid[0].crid, - netRevenue: true, - ttl: 3000, - ad: serverResponse.body.seatbid[0].bid[0].adm, - dealId: serverResponse.body.seatbid[0].bid[0].dealid, - meta: { - cid: serverResponse.body.seatbid[0].bid[0].cid, - adomain: serverResponse.body.seatbid[0].bid[0].adomain, - mediaType: serverResponse.body.seatbid[0].bid[0].ext.mediaType, - }, - }]; + const result = spec.interpretResponse(serverResponse, request); + + const expected = [ + { + requestId: 'unique-bid-id-1', + cpm: serverResponse.body.seatbid[0].bid[0].price, + currency: 'USD', + width: serverResponse.body.seatbid[0].bid[0].w, + height: serverResponse.body.seatbid[0].bid[0].h, + mediaType: 'banner', + creativeId: serverResponse.body.seatbid[0].bid[0].crid, + netRevenue: true, + ttl: 3000, + ad: serverResponse.body.seatbid[0].bid[0].adm, + dealId: serverResponse.body.seatbid[0].bid[0].dealid, + meta: { + cid: serverResponse.body.seatbid[0].bid[0].cid, + adomain: serverResponse.body.seatbid[0].bid[0].adomain, + mediaType: (serverResponse.body.seatbid[0].bid[0].ext || {}).mediaType, + }, + } + ]; expect(result).deep.equal(expected); }); diff --git a/test/spec/modules/yieldlabBidAdapter_spec.js b/test/spec/modules/yieldlabBidAdapter_spec.js index cd2c46a5664..6dde671578c 100644 --- a/test/spec/modules/yieldlabBidAdapter_spec.js +++ b/test/spec/modules/yieldlabBidAdapter_spec.js @@ -7,7 +7,6 @@ const REQUEST = { 'params': { 'adslotId': '1111', 'supplyId': '2222', - 'adSize': '728x90', 'targeting': { 'key1': 'value1', 'key2': 'value2', @@ -57,6 +56,7 @@ const RESPONSE = { id: 1111, price: 1, pid: 2222, + adsize: '728x90', adtype: 'BANNER' } @@ -88,8 +88,7 @@ describe('yieldlabBidAdapter', function () { const request = { 'params': { 'adslotId': '1111', - 'supplyId': '2222', - 'adSize': '728x90' + 'supplyId': '2222' } } expect(spec.isBidRequestValid(request)).to.equal(true) @@ -180,7 +179,7 @@ describe('yieldlabBidAdapter', function () { expect(result[0].netRevenue).to.equal(false) expect(result[0].ttl).to.equal(300) expect(result[0].referrer).to.equal('') - expect(result[0].ad).to.include('' + '
', creative_id: '9874652394875', + adomain: ['www.example.com'], }], header: 'header?', }); @@ -296,6 +297,59 @@ describe('YieldmoAdapter', function () { ttl: 300, ad: '' + '
', + meta: { + advertiserDomains: ['www.example.com'], + mediaType: 'banner', + }, + }); + }); + + it('should correctly reorder video bids', function () { + const response = mockServerResponse(); + const seatbid = [ + { + bid: { + adm: '', + adomain: ['www.example.com'], + crid: 'dd65c0a7536aff', + impid: '91ea8bba1', + price: 1.5, + }, + }, + ]; + const bidRequest = { + data: { + imp: [ + { + id: '91ea8bba1', + video: { + h: 250, + w: 300, + }, + }, + ], + }, + }; + + response.body.seatbid = seatbid; + + const newResponse = spec.interpretResponse(response, bidRequest); + expect(newResponse.length).to.be.equal(2); + expect(newResponse[1]).to.deep.equal({ + cpm: 1.5, + creativeId: 'dd65c0a7536aff', + currency: 'USD', + height: 250, + mediaType: 'video', + meta: { + advertiserDomains: ['www.example.com'], + mediaType: 'video', + }, + netRevenue: true, + requestId: '91ea8bba1', + ttl: 300, + vastXml: '', + width: 300, }); }); diff --git a/test/spec/modules/zemantaBidAdapter_spec.js b/test/spec/modules/zemantaBidAdapter_spec.js new file mode 100644 index 00000000000..523cdcd2eb3 --- /dev/null +++ b/test/spec/modules/zemantaBidAdapter_spec.js @@ -0,0 +1,558 @@ +import {expect} from 'chai'; +import {spec} from 'modules/zemantaBidAdapter.js'; +import {config} from 'src/config.js'; +import {server} from 'test/mocks/xhr'; + +describe('Zemanta Adapter', function () { + describe('Bid request and response', function () { + const commonBidRequest = { + bidder: 'zemanta', + params: { + publisher: { + id: 'publisher-id' + }, + }, + bidId: '2d6815a92ba1ba', + auctionId: '12043683-3254-4f74-8934-f941b085579e', + } + const nativeBidRequestParams = { + nativeParams: { + image: { + required: true, + sizes: [ + 120, + 100 + ], + sendId: true + }, + title: { + required: true, + sendId: true + }, + sponsoredBy: { + required: false + } + }, + } + + const displayBidRequestParams = { + sizes: [ + [ + 300, + 250 + ] + ] + } + + describe('isBidRequestValid', function () { + before(() => { + config.setConfig({ + zemanta: { + bidderUrl: 'https://bidder-url.com', + } + } + ) + }) + after(() => { + config.resetConfig() + }) + + it('should fail when bid is invalid', function () { + const bid = { + bidder: 'zemanta', + params: { + publisher: { + id: 'publisher-id', + } + }, + } + expect(spec.isBidRequestValid(bid)).to.equal(false) + }) + it('should succeed when bid contains native params', function () { + const bid = { + bidder: 'zemanta', + params: { + publisher: { + id: 'publisher-id', + } + }, + ...nativeBidRequestParams, + } + expect(spec.isBidRequestValid(bid)).to.equal(true) + }) + it('should succeed when bid contains sizes', function () { + const bid = { + bidder: 'zemanta', + params: { + publisher: { + id: 'publisher-id', + } + }, + ...displayBidRequestParams, + } + expect(spec.isBidRequestValid(bid)).to.equal(true) + }) + it('should fail if publisher id is not set', function () { + const bid = { + bidder: 'zemanta', + ...nativeBidRequestParams, + } + expect(spec.isBidRequestValid(bid)).to.equal(false) + }) + it('should succeed with outbrain config', function () { + const bid = { + bidder: 'zemanta', + params: { + publisher: { + id: 'publisher-id', + } + }, + ...nativeBidRequestParams, + } + config.resetConfig() + config.setConfig({ + outbrain: { + bidderUrl: 'https://bidder-url.com', + } + }) + expect(spec.isBidRequestValid(bid)).to.equal(true) + }) + it('should fail if bidder url is not set', function () { + const bid = { + bidder: 'zemanta', + params: { + publisher: { + id: 'publisher-id', + } + }, + ...nativeBidRequestParams, + } + config.resetConfig() + expect(spec.isBidRequestValid(bid)).to.equal(false) + }) + }) + + describe('buildRequests', function () { + before(() => { + config.setConfig({ + zemanta: { + bidderUrl: 'https://bidder-url.com', + } + } + ) + }) + after(() => { + config.resetConfig() + }) + + const commonBidderRequest = { + refererInfo: { + referer: 'https://example.com/' + } + } + + it('should build native request', function () { + const bidRequest = { + ...commonBidRequest, + ...nativeBidRequestParams, + } + const expectedNativeAssets = { + assets: [ + { + required: 1, + id: 3, + img: { + type: 3, + w: 120, + h: 100 + } + }, + { + required: 1, + id: 0, + title: {} + }, + { + required: 0, + id: 5, + data: { + type: 1 + } + } + ] + } + const expectedData = { + site: { + page: 'https://example.com/', + publisher: { + id: 'publisher-id' + } + }, + device: { + ua: navigator.userAgent + }, + source: { + fd: 1 + }, + cur: [ + 'USD' + ], + imp: [ + { + id: '1', + native: { + request: JSON.stringify(expectedNativeAssets) + } + } + ] + } + const res = spec.buildRequests([bidRequest], commonBidderRequest) + expect(res.url).to.equal('https://bidder-url.com') + expect(res.data).to.deep.equal(JSON.stringify(expectedData)) + }); + + it('should build display request', function () { + const bidRequest = { + ...commonBidRequest, + ...displayBidRequestParams, + } + const expectedData = { + site: { + page: 'https://example.com/', + publisher: { + id: 'publisher-id' + } + }, + device: { + ua: navigator.userAgent + }, + source: { + fd: 1 + }, + cur: [ + 'USD' + ], + imp: [ + { + id: '1', + banner: { + format: [ + { + w: 300, + h: 250 + } + ] + } + } + ] + } + const res = spec.buildRequests([bidRequest], commonBidderRequest) + expect(res.url).to.equal('https://bidder-url.com') + expect(res.data).to.deep.equal(JSON.stringify(expectedData)) + }) + + it('should pass optional parameters in request', function () { + const bidRequest = { + ...commonBidRequest, + ...nativeBidRequestParams, + } + bidRequest.params.tagid = 'test-tag' + bidRequest.params.publisher.name = 'test-publisher' + bidRequest.params.publisher.domain = 'test-publisher.com' + bidRequest.params.bcat = ['bad-category'] + bidRequest.params.badv = ['bad-advertiser'] + + const res = spec.buildRequests([bidRequest], commonBidderRequest) + const resData = JSON.parse(res.data) + expect(resData.imp[0].tagid).to.equal('test-tag') + expect(resData.site.publisher.name).to.equal('test-publisher') + expect(resData.site.publisher.domain).to.equal('test-publisher.com') + expect(resData.bcat).to.deep.equal(['bad-category']) + expect(resData.badv).to.deep.equal(['bad-advertiser']) + }); + + it('should pass bidder timeout', function () { + const bidRequest = { + ...commonBidRequest, + ...nativeBidRequestParams, + } + const bidderRequest = { + ...commonBidderRequest, + timeout: 500 + } + const res = spec.buildRequests([bidRequest], bidderRequest) + const resData = JSON.parse(res.data) + expect(resData.tmax).to.equal(500) + }); + + it('should pass GDPR consent', function () { + const bidRequest = { + ...commonBidRequest, + ...nativeBidRequestParams, + } + const bidderRequest = { + ...commonBidderRequest, + gdprConsent: { + gdprApplies: true, + consentString: 'consentString', + } + } + const res = spec.buildRequests([bidRequest], bidderRequest) + const resData = JSON.parse(res.data) + expect(resData.user.ext.consent).to.equal('consentString') + expect(resData.regs.ext.gdpr).to.equal(1) + }); + + it('should pass us privacy consent', function () { + const bidRequest = { + ...commonBidRequest, + ...nativeBidRequestParams, + } + const bidderRequest = { + ...commonBidderRequest, + uspConsent: 'consentString' + } + const res = spec.buildRequests([bidRequest], bidderRequest) + const resData = JSON.parse(res.data) + expect(resData.regs.ext.us_privacy).to.equal('consentString') + }); + + it('should pass coppa consent', function () { + const bidRequest = { + ...commonBidRequest, + ...nativeBidRequestParams, + } + config.setConfig({coppa: true}) + + const res = spec.buildRequests([bidRequest], commonBidderRequest) + const resData = JSON.parse(res.data) + expect(resData.regs.coppa).to.equal(1) + + config.resetConfig() + }); + }) + + describe('interpretResponse', function () { + it('should return empty array if no valid bids', function () { + const res = spec.interpretResponse({}, []) + expect(res).to.be.an('array').that.is.empty + }); + + it('should interpret native response', function () { + const serverResponse = { + body: { + id: '0a73e68c-9967-4391-b01b-dda2d9fc54e4', + seatbid: [ + { + bid: [ + { + id: '82822cf5-259c-11eb-8a52-f29e5275aa57', + impid: '1', + price: 1.1, + nurl: 'http://example.com/win/${AUCTION_PRICE}', + adm: '{"ver":"1.2","assets":[{"id":3,"required":1,"img":{"url":"http://example.com/img/url","w":120,"h":100}},{"id":0,"required":1,"title":{"text":"Test title"}},{"id":5,"data":{"value":"Test sponsor"}}],"link":{"url":"http://example.com/click/url"},"eventtrackers":[{"event":1,"method":1,"url":"http://example.com/impression"}]}', + adomain: [ + 'example.com' + ], + cid: '3487171', + crid: '28023739', + cat: [ + 'IAB10-2' + ] + } + ], + seat: 'acc-5537' + } + ], + bidid: '82822cf5-259c-11eb-8a52-b48e7518c657', + cur: 'USD' + }, + } + const request = { + bids: [ + { + ...commonBidRequest, + ...nativeBidRequestParams, + } + ] + } + const expectedRes = [ + { + requestId: request.bids[0].bidId, + cpm: 1.1, + creativeId: '28023739', + ttl: 360, + netRevenue: false, + currency: 'USD', + mediaType: 'native', + nurl: 'http://example.com/win/${AUCTION_PRICE}', + meta: { + 'advertiserDomains': [ + 'example.com' + ] + }, + native: { + clickTrackers: undefined, + clickUrl: 'http://example.com/click/url', + image: { + url: 'http://example.com/img/url', + width: 120, + height: 100 + }, + title: 'Test title', + sponsoredBy: 'Test sponsor', + impressionTrackers: [ + 'http://example.com/impression', + ] + } + } + ] + + const res = spec.interpretResponse(serverResponse, request) + expect(res).to.deep.equal(expectedRes) + }); + + it('should interpret display response', function () { + const serverResponse = { + body: { + id: '6b2eedc8-8ff5-46ef-adcf-e701b508943e', + seatbid: [ + { + bid: [ + { + id: 'd90fe7fa-28d7-11eb-8ce4-462a842a7cf9', + impid: '1', + price: 1.1, + nurl: 'http://example.com/win/${AUCTION_PRICE}', + adm: '
ad
', + adomain: [ + 'example.com' + ], + cid: '3865084', + crid: '29998660', + cat: [ + 'IAB10-2' + ], + w: 300, + h: 250 + } + ], + seat: 'acc-6536' + } + ], + bidid: 'd90fe7fa-28d7-11eb-8ce4-13d94bfa26f9', + cur: 'USD' + } + } + const request = { + bids: [ + { + ...commonBidRequest, + ...displayBidRequestParams + } + ] + } + const expectedRes = [ + { + requestId: request.bids[0].bidId, + cpm: 1.1, + creativeId: '29998660', + ttl: 360, + netRevenue: false, + currency: 'USD', + mediaType: 'banner', + nurl: 'http://example.com/win/${AUCTION_PRICE}', + ad: '
ad
', + width: 300, + height: 250, + meta: { + 'advertiserDomains': [ + 'example.com' + ] + }, + } + ] + + const res = spec.interpretResponse(serverResponse, request) + expect(res).to.deep.equal(expectedRes) + }); + }) + }) + + describe('getUserSyncs', function () { + const usersyncUrl = 'https://usersync-url.com'; + beforeEach(() => { + config.setConfig({ + zemanta: { + usersyncUrl: usersyncUrl, + } + } + ) + }) + after(() => { + config.resetConfig() + }) + + it('should return user sync if pixel enabled', function () { + const ret = spec.getUserSyncs({pixelEnabled: true}) + expect(ret).to.deep.equal([{type: 'image', url: 'https://usersync-url.com'}]) + }) + it('should return user sync if pixel enabled with outbrain config', function () { + config.resetConfig() + config.setConfig({ + outbrain: { + usersyncUrl: 'https://usersync-url.com', + } + }) + const ret = spec.getUserSyncs({pixelEnabled: true}) + expect(ret).to.deep.equal([{type: 'image', url: 'https://usersync-url.com'}]) + }) + + it('should not return user sync if pixel disabled', function () { + const ret = spec.getUserSyncs({pixelEnabled: false}) + expect(ret).to.be.an('array').that.is.empty + }) + + it('should not return user sync if url is not set', function () { + config.resetConfig() + const ret = spec.getUserSyncs({pixelEnabled: true}) + expect(ret).to.be.an('array').that.is.empty + }) + + it('should pass GDPR consent', function() { + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, {gdprApplies: true, consentString: 'foo'}, undefined)).to.deep.equal([{ + type: 'image', url: `${usersyncUrl}&gdpr=1&gdpr_consent=foo` + }]); + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, {gdprApplies: false, consentString: 'foo'}, undefined)).to.deep.equal([{ + type: 'image', url: `${usersyncUrl}&gdpr=0&gdpr_consent=foo` + }]); + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, {gdprApplies: true, consentString: undefined}, undefined)).to.deep.equal([{ + type: 'image', url: `${usersyncUrl}&gdpr=1&gdpr_consent=` + }]); + }); + + it('should pass US consent', function() { + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, undefined, '1NYN')).to.deep.equal([{ + type: 'image', url: `${usersyncUrl}&us_privacy=1NYN` + }]); + }); + + it('should pass GDPR and US consent', function() { + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, {gdprApplies: true, consentString: 'foo'}, '1NYN')).to.deep.equal([{ + type: 'image', url: `${usersyncUrl}&gdpr=1&gdpr_consent=foo&us_privacy=1NYN` + }]); + }); + }) + + describe('onBidWon', function () { + it('should make an ajax call with the original cpm', function () { + const bid = { + nurl: 'http://example.com/win/${AUCTION_PRICE}', + cpm: 2.1, + originalCpm: 1.1, + } + spec.onBidWon(bid) + expect(server.requests[0].url).to.equals('http://example.com/win/1.1') + }); + }) +}) diff --git a/test/spec/modules/zeotapIdPlusIdSystem_spec.js b/test/spec/modules/zeotapIdPlusIdSystem_spec.js index 4f9e691f12e..9de6fa843bc 100644 --- a/test/spec/modules/zeotapIdPlusIdSystem_spec.js +++ b/test/spec/modules/zeotapIdPlusIdSystem_spec.js @@ -2,7 +2,8 @@ import { expect } from 'chai'; import find from 'core-js-pure/features/array/find.js'; import { config } from 'src/config.js'; import { init, requestBidsHook, setSubmoduleRegistry } from 'modules/userId/index.js'; -import { storage, zeotapIdPlusSubmodule } from 'modules/zeotapIdPlusIdSystem.js'; +import { storage, getStorage, zeotapIdPlusSubmodule } from 'modules/zeotapIdPlusIdSystem.js'; +import * as storageManager from 'src/storageManager.js'; const ZEOTAP_COOKIE_NAME = 'IDP'; const ZEOTAP_COOKIE = 'THIS-IS-A-DUMMY-COOKIE'; @@ -43,6 +44,67 @@ function unsetLocalStorage() { } describe('Zeotap ID System', function() { + describe('Zeotap Module invokes StorageManager with appropriate arguments', function() { + let getStorageManagerSpy; + + beforeEach(function() { + getStorageManagerSpy = sinon.spy(storageManager, 'getStorageManager'); + }); + + it('when a stored Zeotap ID exists it is added to bids', function() { + let store = getStorage(); + expect(getStorageManagerSpy.calledOnce).to.be.true; + sinon.assert.calledWith(getStorageManagerSpy, 301, 'zeotapIdPlus'); + }); + }); + + describe('test method: getId calls storage methods to fetch ID', function() { + let cookiesAreEnabledStub; + let getCookieStub; + let localStorageIsEnabledStub; + let getDataFromLocalStorageStub; + + beforeEach(() => { + cookiesAreEnabledStub = sinon.stub(storage, 'cookiesAreEnabled'); + getCookieStub = sinon.stub(storage, 'getCookie'); + localStorageIsEnabledStub = sinon.stub(storage, 'localStorageIsEnabled'); + getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage'); + }); + + afterEach(() => { + storage.cookiesAreEnabled.restore(); + storage.getCookie.restore(); + storage.localStorageIsEnabled.restore(); + storage.getDataFromLocalStorage.restore(); + unsetCookie(); + unsetLocalStorage(); + }); + + it('should check if cookies are enabled', function() { + let id = zeotapIdPlusSubmodule.getId(); + expect(cookiesAreEnabledStub.calledOnce).to.be.true; + }); + + it('should call getCookie if cookies are enabled', function() { + cookiesAreEnabledStub.returns(true); + let id = zeotapIdPlusSubmodule.getId(); + expect(cookiesAreEnabledStub.calledOnce).to.be.true; + expect(getCookieStub.calledOnce).to.be.true; + sinon.assert.calledWith(getCookieStub, 'IDP'); + }); + + it('should check for localStorage if cookies are disabled', function() { + cookiesAreEnabledStub.returns(false); + localStorageIsEnabledStub.returns(true) + let id = zeotapIdPlusSubmodule.getId(); + expect(cookiesAreEnabledStub.calledOnce).to.be.true; + expect(getCookieStub.called).to.be.false; + expect(localStorageIsEnabledStub.calledOnce).to.be.true; + expect(getDataFromLocalStorageStub.calledOnce).to.be.true; + sinon.assert.calledWith(getDataFromLocalStorageStub, 'IDP'); + }); + }); + describe('test method: getId', function() { afterEach(() => { unsetCookie(); diff --git a/test/spec/modules/zetaBidAdapter_spec.js b/test/spec/modules/zetaBidAdapter_spec.js index 0d11614c926..b3380ab35db 100644 --- a/test/spec/modules/zetaBidAdapter_spec.js +++ b/test/spec/modules/zetaBidAdapter_spec.js @@ -18,7 +18,12 @@ describe('Zeta Bid Adapter', function() { uid: 12345, buyeruid: 12345 }, - ip: '111.222.33.44', + device: { + ip: '111.222.33.44', + geo: { + country: 'USA' + } + }, definerId: 1, test: 1 } diff --git a/test/spec/native_spec.js b/test/spec/native_spec.js index bd56ba53e4a..7154a0fde37 100644 --- a/test/spec/native_spec.js +++ b/test/spec/native_spec.js @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { fireNativeTrackers, getNativeTargeting, nativeBidIsValid, getAssetMessage } from 'src/native.js'; +import { fireNativeTrackers, getNativeTargeting, nativeBidIsValid, getAssetMessage, getAllAssetsMessage } from 'src/native.js'; import CONSTANTS from 'src/constants.json'; const utils = require('src/utils'); @@ -23,7 +23,11 @@ const bid = { clickUrl: 'https://www.link.example', clickTrackers: ['https://tracker.example'], impressionTrackers: ['https://impression.example'], - javascriptTrackers: '' + javascriptTrackers: '', + ext: { + foo: 'foo-value', + baz: 'baz-value' + } } }; @@ -36,7 +40,11 @@ const bidWithUndefinedFields = { clickUrl: 'https://www.link.example', clickTrackers: ['https://tracker.example'], impressionTrackers: ['https://impression.example'], - javascriptTrackers: '' + javascriptTrackers: '', + ext: { + foo: 'foo-value', + baz: undefined + } } }; @@ -59,14 +67,21 @@ describe('native.js', function () { expect(targeting[CONSTANTS.NATIVE_KEYS.title]).to.equal(bid.native.title); expect(targeting[CONSTANTS.NATIVE_KEYS.body]).to.equal(bid.native.body); expect(targeting[CONSTANTS.NATIVE_KEYS.clickUrl]).to.equal(bid.native.clickUrl); + expect(targeting.hb_native_foo).to.equal(bid.native.foo); }); it('sends placeholders for configured assets', function () { const bidRequest = { - mediaTypes: { - native: { - body: { sendId: true }, - clickUrl: { sendId: true }, + nativeParams: { + body: { sendId: true }, + clickUrl: { sendId: true }, + ext: { + foo: { + sendId: false + }, + baz: { + sendId: true + } } } }; @@ -75,16 +90,171 @@ describe('native.js', function () { expect(targeting[CONSTANTS.NATIVE_KEYS.title]).to.equal(bid.native.title); expect(targeting[CONSTANTS.NATIVE_KEYS.body]).to.equal('hb_native_body:123'); expect(targeting[CONSTANTS.NATIVE_KEYS.clickUrl]).to.equal('hb_native_linkurl:123'); + expect(targeting.hb_native_foo).to.equal(bid.native.ext.foo); + expect(targeting.hb_native_baz).to.equal('hb_native_baz:123'); }); it('should only include native targeting keys with values', function () { - const targeting = getNativeTargeting(bidWithUndefinedFields); + const bidRequest = { + nativeParams: { + body: { sendId: true }, + clickUrl: { sendId: true }, + ext: { + foo: { + required: false + }, + baz: { + required: false + } + } + } + }; + + const targeting = getNativeTargeting(bidWithUndefinedFields, bidRequest); + + expect(Object.keys(targeting)).to.deep.equal([ + CONSTANTS.NATIVE_KEYS.title, + CONSTANTS.NATIVE_KEYS.sponsoredBy, + CONSTANTS.NATIVE_KEYS.clickUrl, + 'hb_native_foo' + ]); + }); + + it('should only include targeting that has sendTargetingKeys set to true', function () { + const bidRequest = { + nativeParams: { + image: { + required: true, + sizes: [150, 50] + }, + title: { + required: true, + len: 80, + sendTargetingKeys: true + }, + sendTargetingKeys: false, + } + + }; + const targeting = getNativeTargeting(bid, bidRequest); + + expect(Object.keys(targeting)).to.deep.equal([ + CONSTANTS.NATIVE_KEYS.title + ]); + }); + + it('should only include targeting if sendTargetingKeys not set to false', function () { + const bidRequest = { + nativeParams: { + image: { + required: true, + sizes: [150, 50] + }, + title: { + required: true, + len: 80 + }, + body: { + required: true + }, + clickUrl: { + required: true + }, + icon: { + required: false, + sendTargetingKeys: false + }, + cta: { + required: false, + sendTargetingKeys: false + }, + sponsoredBy: { + required: false, + sendTargetingKeys: false + }, + ext: { + foo: { + required: false, + sendTargetingKeys: true + } + } + } + + }; + const targeting = getNativeTargeting(bid, bidRequest); + + expect(Object.keys(targeting)).to.deep.equal([ + CONSTANTS.NATIVE_KEYS.title, + CONSTANTS.NATIVE_KEYS.body, + CONSTANTS.NATIVE_KEYS.image, + CONSTANTS.NATIVE_KEYS.clickUrl, + 'hb_native_foo' + ]); + }); + + it('should copy over rendererUrl to bid object and include it in targeting', function () { + const bidRequest = { + nativeParams: { + image: { + required: true, + sizes: [150, 50] + }, + title: { + required: true, + len: 80, + }, + rendererUrl: { + url: 'https://www.renderer.com/' + } + } + + }; + const targeting = getNativeTargeting(bid, bidRequest); + + expect(Object.keys(targeting)).to.deep.equal([ + CONSTANTS.NATIVE_KEYS.title, + CONSTANTS.NATIVE_KEYS.body, + CONSTANTS.NATIVE_KEYS.cta, + CONSTANTS.NATIVE_KEYS.image, + CONSTANTS.NATIVE_KEYS.icon, + CONSTANTS.NATIVE_KEYS.sponsoredBy, + CONSTANTS.NATIVE_KEYS.clickUrl, + CONSTANTS.NATIVE_KEYS.rendererUrl + ]); + + expect(bid.native.rendererUrl).to.deep.equal('https://www.renderer.com/'); + delete bid.native.rendererUrl; + }); + + it('should copy over adTemplate to bid object and include it in targeting', function () { + const bidRequest = { + nativeParams: { + image: { + required: true, + sizes: [150, 50] + }, + title: { + required: true, + len: 80, + }, + adTemplate: '

##hb_native_body##<\/p><\/div>' + } + + }; + const targeting = getNativeTargeting(bid, bidRequest); expect(Object.keys(targeting)).to.deep.equal([ CONSTANTS.NATIVE_KEYS.title, + CONSTANTS.NATIVE_KEYS.body, + CONSTANTS.NATIVE_KEYS.cta, + CONSTANTS.NATIVE_KEYS.image, + CONSTANTS.NATIVE_KEYS.icon, CONSTANTS.NATIVE_KEYS.sponsoredBy, CONSTANTS.NATIVE_KEYS.clickUrl ]); + + expect(bid.native.adTemplate).to.deep.equal('

##hb_native_body##<\/p><\/div>'); + delete bid.native.adTemplate; }); it('fires impression trackers', function () { @@ -125,6 +295,82 @@ describe('native.js', function () { value: bid.native.clickUrl }); }); + + it('creates native all asset message', function() { + const messageRequest = { + message: 'Prebid Native', + action: 'allAssetRequest', + adId: '123', + }; + + const message = getAllAssetsMessage(messageRequest, bid); + + expect(message.assets.length).to.equal(9); + expect(message.assets).to.deep.include({ + key: 'body', + value: bid.native.body + }); + expect(message.assets).to.deep.include({ + key: 'image', + value: bid.native.image.url + }); + expect(message.assets).to.deep.include({ + key: 'clickUrl', + value: bid.native.clickUrl + }); + expect(message.assets).to.deep.include({ + key: 'title', + value: bid.native.title + }); + expect(message.assets).to.deep.include({ + key: 'icon', + value: bid.native.icon.url + }); + expect(message.assets).to.deep.include({ + key: 'cta', + value: bid.native.cta + }); + expect(message.assets).to.deep.include({ + key: 'sponsoredBy', + value: bid.native.sponsoredBy + }); + expect(message.assets).to.deep.include({ + key: 'foo', + value: bid.native.ext.foo + }); + expect(message.assets).to.deep.include({ + key: 'baz', + value: bid.native.ext.baz + }); + }); + + it('creates native all asset message with only defined fields', function() { + const messageRequest = { + message: 'Prebid Native', + action: 'allAssetRequest', + adId: '123', + }; + + const message = getAllAssetsMessage(messageRequest, bidWithUndefinedFields); + + expect(message.assets.length).to.equal(4); + expect(message.assets).to.deep.include({ + key: 'clickUrl', + value: bid.native.clickUrl + }); + expect(message.assets).to.deep.include({ + key: 'title', + value: bid.native.title + }); + expect(message.assets).to.deep.include({ + key: 'sponsoredBy', + value: bid.native.sponsoredBy + }); + expect(message.assets).to.deep.include({ + key: 'foo', + value: bid.native.ext.foo + }); + }); }); describe('validate native', function () { diff --git a/test/spec/unit/core/adapterManager_spec.js b/test/spec/unit/core/adapterManager_spec.js index 9ccdd6aef59..25b2307b943 100644 --- a/test/spec/unit/core/adapterManager_spec.js +++ b/test/spec/unit/core/adapterManager_spec.js @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import adapterManager, { gdprDataHandler } from 'src/adapterManager.js'; +import adapterManager, { allS2SBidders, clientTestAdapters, gdprDataHandler } from 'src/adapterManager.js'; import { getAdUnits, getServerTestingConfig, @@ -25,6 +25,17 @@ const CONFIG = { bidders: ['appnexus'], accountId: 'abc' }; + +const CONFIG2 = { + enabled: true, + endpoint: 'https://prebid-server.rubiconproject.com/openrtb2/auction', + timeout: 1000, + maxBids: 1, + adapter: 'prebidServer', + bidders: ['pubmatic'], + accountId: 'def' +} + var prebidServerAdapterMock = { bidder: 'prebidServer', callBids: sinon.stub() @@ -43,6 +54,11 @@ var rubiconAdapterMock = { callBids: sinon.stub() }; +var pubmaticAdapterMock = { + bidder: 'rubicon', + callBids: sinon.stub() +}; + var badAdapterMock = { bidder: 'badBidder', callBids: sinon.stub().throws(Error('some fake error')) @@ -82,6 +98,7 @@ describe('adapterManager tests', function () { adapterManager.bidderRegistry['appnexus'] = appnexusAdapterMock; adapterManager.bidderRegistry['rubicon'] = rubiconAdapterMock; adapterManager.bidderRegistry['badBidder'] = badAdapterMock; + adapterManager.bidderRegistry['badBidder'] = badAdapterMock; }); afterEach(function () { @@ -387,6 +404,38 @@ describe('adapterManager tests', function () { }); }); // end onSetTargeting + describe('onBidViewable', function () { + var criteoSpec = { onBidViewable: sinon.stub() } + var criteoAdapter = { + bidder: 'criteo', + getSpec: function() { return criteoSpec; } + } + before(function () { + config.setConfig({s2sConfig: { enabled: false }}); + }); + + beforeEach(function () { + adapterManager.bidderRegistry['criteo'] = criteoAdapter; + }); + + afterEach(function () { + delete adapterManager.bidderRegistry['criteo']; + }); + + it('should call spec\'s onBidViewable callback when callBidViewableBidder is called', function () { + const bids = [ + {bidder: 'criteo', params: {placementId: 'id'}}, + ]; + const adUnits = [{ + code: 'adUnit-code', + sizes: [[728, 90]], + bids + }]; + adapterManager.callBidViewableBidder(bids[0].bidder, bids[0]); + sinon.assert.called(criteoSpec.onBidViewable); + }); + }); // end onBidViewable + describe('S2S tests', function () { beforeEach(function () { config.setConfig({s2sConfig: CONFIG}); @@ -394,144 +443,144 @@ describe('adapterManager tests', function () { prebidServerAdapterMock.callBids.reset(); }); - it('invokes callBids on the S2S adapter', function () { - let bidRequests = [{ - 'bidderCode': 'appnexus', - 'auctionId': '1863e370099523', - 'bidderRequestId': '2946b569352ef2', - 'tid': '34566b569352ef2', - 'timeout': 1000, - 'src': 's2s', - 'adUnitsS2SCopy': [ - { - 'code': '/19968336/header-bid-tag1', - 'sizes': [ - { - 'w': 728, - 'h': 90 + const bidRequests = [{ + 'bidderCode': 'appnexus', + 'auctionId': '1863e370099523', + 'bidderRequestId': '2946b569352ef2', + 'tid': '34566b569352ef2', + 'timeout': 1000, + 'src': 's2s', + 'adUnitsS2SCopy': [ + { + 'code': '/19968336/header-bid-tag1', + 'sizes': [ + { + 'w': 728, + 'h': 90 + }, + { + 'w': 970, + 'h': 90 + } + ], + 'bids': [ + { + 'bidder': 'appnexus', + 'params': { + 'placementId': '543221', + 'test': 'me' }, - { - 'w': 970, - 'h': 90 - } - ], - 'bids': [ - { - 'bidder': 'appnexus', - 'params': { - 'placementId': '543221', - 'test': 'me' - }, - 'adUnitCode': '/19968336/header-bid-tag1', - 'sizes': [ - [ - 728, - 90 - ], - [ - 970, - 90 - ] + 'adUnitCode': '/19968336/header-bid-tag1', + 'sizes': [ + [ + 728, + 90 ], - 'bidId': '68136e1c47023d', - 'bidderRequestId': '55e24a66bed717', - 'auctionId': '1ff753bd4ae5cb', - 'startTime': 1463510220995, - 'status': 1, - 'bid_id': '68136e1c47023d' - } - ] - }, - { - 'code': '/19968336/header-bid-tag-0', - 'sizes': [ - { - 'w': 300, - 'h': 250 + [ + 970, + 90 + ] + ], + 'bidId': '68136e1c47023d', + 'bidderRequestId': '55e24a66bed717', + 'auctionId': '1ff753bd4ae5cb', + 'startTime': 1463510220995, + 'status': 1, + 'bid_id': '68136e1c47023d' + } + ] + }, + { + 'code': '/19968336/header-bid-tag-0', + 'sizes': [ + { + 'w': 300, + 'h': 250 + }, + { + 'w': 300, + 'h': 600 + } + ], + 'bids': [ + { + 'bidder': 'appnexus', + 'params': { + 'placementId': '5324321' }, - { - 'w': 300, - 'h': 600 - } - ], - 'bids': [ - { - 'bidder': 'appnexus', - 'params': { - 'placementId': '5324321' - }, - 'adUnitCode': '/19968336/header-bid-tag-0', - 'sizes': [ - [ - 300, - 250 - ], - [ - 300, - 600 - ] + 'adUnitCode': '/19968336/header-bid-tag-0', + 'sizes': [ + [ + 300, + 250 ], - 'bidId': '7e5d6af25ed188', - 'bidderRequestId': '55e24a66bed717', - 'auctionId': '1ff753bd4ae5cb', - 'startTime': 1463510220996, - 'bid_id': '7e5d6af25ed188' - } - ] - } - ], - 'bids': [ - { - 'bidder': 'appnexus', - 'params': { - 'placementId': '4799418', - 'test': 'me' - }, - 'adUnitCode': '/19968336/header-bid-tag1', - 'sizes': [ - [ - 728, - 90 + [ + 300, + 600 + ] ], - [ - 970, - 90 - ] + 'bidId': '7e5d6af25ed188', + 'bidderRequestId': '55e24a66bed717', + 'auctionId': '1ff753bd4ae5cb', + 'startTime': 1463510220996, + 'bid_id': '7e5d6af25ed188' + } + ] + } + ], + 'bids': [ + { + 'bidder': 'appnexus', + 'params': { + 'placementId': '4799418', + 'test': 'me' + }, + 'adUnitCode': '/19968336/header-bid-tag1', + 'sizes': [ + [ + 728, + 90 ], - 'bidId': '392b5a6b05d648', - 'bidderRequestId': '2946b569352ef2', - 'auctionId': '1863e370099523', - 'startTime': 1462918897462, - 'status': 1, - 'transactionId': 'fsafsa' + [ + 970, + 90 + ] + ], + 'bidId': '392b5a6b05d648', + 'bidderRequestId': '2946b569352ef2', + 'auctionId': '1863e370099523', + 'startTime': 1462918897462, + 'status': 1, + 'transactionId': 'fsafsa' + }, + { + 'bidder': 'appnexus', + 'params': { + 'placementId': '4799418' }, - { - 'bidder': 'appnexus', - 'params': { - 'placementId': '4799418' - }, - 'adUnitCode': '/19968336/header-bid-tag-0', - 'sizes': [ - [ - 300, - 250 - ], - [ - 300, - 600 - ] + 'adUnitCode': '/19968336/header-bid-tag-0', + 'sizes': [ + [ + 300, + 250 ], - 'bidId': '4dccdc37746135', - 'bidderRequestId': '2946b569352ef2', - 'auctionId': '1863e370099523', - 'startTime': 1462918897463, - 'status': 1, - 'transactionId': 'fsafsa' - } - ], - 'start': 1462918897460 - }]; + [ + 300, + 600 + ] + ], + 'bidId': '4dccdc37746135', + 'bidderRequestId': '2946b569352ef2', + 'auctionId': '1863e370099523', + 'startTime': 1462918897463, + 'status': 1, + 'transactionId': 'fsafsa' + } + ], + 'start': 1462918897460 + }]; + it('invokes callBids on the S2S adapter', function () { adapterManager.callBids( getAdUnits(), bidRequests, @@ -559,143 +608,6 @@ describe('adapterManager tests', function () { ] }); - let bidRequests = [{ - 'bidderCode': 'appnexus', - 'auctionId': '1863e370099523', - 'bidderRequestId': '2946b569352ef2', - 'tid': '34566b569352ef2', - 'src': 's2s', - 'timeout': 1000, - 'adUnitsS2SCopy': [ - { - 'code': '/19968336/header-bid-tag1', - 'sizes': [ - { - 'w': 728, - 'h': 90 - }, - { - 'w': 970, - 'h': 90 - } - ], - 'bids': [ - { - 'bidder': 'appnexus', - 'params': { - 'placementId': '543221', - 'test': 'me' - }, - 'adUnitCode': '/19968336/header-bid-tag1', - 'sizes': [ - [ - 728, - 90 - ], - [ - 970, - 90 - ] - ], - 'bidId': '68136e1c47023d', - 'bidderRequestId': '55e24a66bed717', - 'auctionId': '1ff753bd4ae5cb', - 'startTime': 1463510220995, - 'status': 1, - 'bid_id': '378a8914450b334' - } - ] - }, - { - 'code': '/19968336/header-bid-tag-0', - 'sizes': [ - { - 'w': 300, - 'h': 250 - }, - { - 'w': 300, - 'h': 600 - } - ], - 'bids': [ - { - 'bidder': 'appnexus', - 'params': { - 'placementId': '5324321' - }, - 'adUnitCode': '/19968336/header-bid-tag-0', - 'sizes': [ - [ - 300, - 250 - ], - [ - 300, - 600 - ] - ], - 'bidId': '7e5d6af25ed188', - 'bidderRequestId': '55e24a66bed717', - 'auctionId': '1ff753bd4ae5cb', - 'startTime': 1463510220996, - 'bid_id': '387d9d9c32ca47c' - } - ] - } - ], - 'bids': [ - { - 'bidder': 'appnexus', - 'params': { - 'placementId': '4799418', - 'test': 'me' - }, - 'adUnitCode': '/19968336/header-bid-tag1', - 'sizes': [ - [ - 728, - 90 - ], - [ - 970, - 90 - ] - ], - 'bidId': '392b5a6b05d648', - 'bidderRequestId': '2946b569352ef2', - 'auctionId': '1863e370099523', - 'startTime': 1462918897462, - 'status': 1, - 'transactionId': 'fsafsa' - }, - { - 'bidder': 'appnexus', - 'params': { - 'placementId': '4799418' - }, - 'adUnitCode': '/19968336/header-bid-tag-0', - 'sizes': [ - [ - 300, - 250 - ], - [ - 300, - 600 - ] - ], - 'bidId': '4dccdc37746135', - 'bidderRequestId': '2946b569352ef2', - 'auctionId': '1863e370099523', - 'startTime': 1462918897463, - 'status': 1, - 'transactionId': 'fsafsa' - } - ], - 'start': 1462918897460 - }]; - adapterManager.callBids( adUnits, bidRequests, @@ -749,133 +661,764 @@ describe('adapterManager tests', function () { }); }); // end s2s tests - describe('s2sTesting', function () { - let doneStub = sinon.stub(); - let ajaxStub = sinon.stub(); - - function getTestAdUnits() { - // copy adUnits - // return JSON.parse(JSON.stringify(getAdUnits())); - return utils.deepClone(getAdUnits()).map(adUnit => { - adUnit.bids = adUnit.bids.filter(bid => includes(['adequant', 'appnexus', 'rubicon'], bid.bidder)); - return adUnit; - }) - } - - function callBids(adUnits = getTestAdUnits()) { - let bidRequests = adapterManager.makeBidRequests(adUnits, 1111, 2222, 1000); - adapterManager.callBids(adUnits, bidRequests, doneStub, ajaxStub); - } - - function checkServerCalled(numAdUnits, numBids) { - sinon.assert.calledOnce(prebidServerAdapterMock.callBids); - let requestObj = prebidServerAdapterMock.callBids.firstCall.args[0]; - expect(requestObj.ad_units.length).to.equal(numAdUnits); - for (let i = 0; i < numAdUnits; i++) { - expect(requestObj.ad_units[i].bids.filter((bid) => { - return bid.bidder === 'appnexus' || bid.bidder === 'adequant'; - }).length).to.equal(numBids); - } - } - - function checkClientCalled(adapter, numBids) { - sinon.assert.calledOnce(adapter.callBids); - expect(adapter.callBids.firstCall.args[0].bids.length).to.equal(numBids); - } - - let TESTING_CONFIG = utils.deepClone(CONFIG); - Object.assign(TESTING_CONFIG, { - bidders: ['appnexus', 'adequant'], - testing: true - }); - let stubGetSourceBidderMap; - + describe('Multiple S2S tests', function () { beforeEach(function () { - config.setConfig({s2sConfig: TESTING_CONFIG}); + config.setConfig({s2sConfig: [CONFIG, CONFIG2]}); adapterManager.bidderRegistry['prebidServer'] = prebidServerAdapterMock; - adapterManager.bidderRegistry['adequant'] = adequantAdapterMock; - adapterManager.bidderRegistry['appnexus'] = appnexusAdapterMock; - adapterManager.bidderRegistry['rubicon'] = rubiconAdapterMock; - - stubGetSourceBidderMap = sinon.stub(s2sTesting, 'getSourceBidderMap'); - prebidServerAdapterMock.callBids.reset(); - adequantAdapterMock.callBids.reset(); - appnexusAdapterMock.callBids.reset(); - rubiconAdapterMock.callBids.reset(); }); afterEach(function () { - config.setConfig({s2sConfig: {}}); - s2sTesting.getSourceBidderMap.restore(); - }); - - it('calls server adapter if no sources defined', function () { - stubGetSourceBidderMap.returns({[s2sTesting.CLIENT]: [], [s2sTesting.SERVER]: []}); - callBids(); - - // server adapter - checkServerCalled(2, 2); - - // appnexus - sinon.assert.notCalled(appnexusAdapterMock.callBids); - - // adequant - sinon.assert.notCalled(adequantAdapterMock.callBids); - }); - - it('calls client adapter if one client source defined', function () { - stubGetSourceBidderMap.returns({[s2sTesting.CLIENT]: ['appnexus'], [s2sTesting.SERVER]: []}); - callBids(); - - // server adapter - checkServerCalled(2, 2); - - // appnexus - checkClientCalled(appnexusAdapterMock, 2); - - // adequant - sinon.assert.notCalled(adequantAdapterMock.callBids); + allS2SBidders.length = 0; }); - it('calls client adapters if client sources defined', function () { - stubGetSourceBidderMap.returns({[s2sTesting.CLIENT]: ['appnexus', 'adequant'], [s2sTesting.SERVER]: []}); - callBids(); - - // server adapter - checkServerCalled(2, 2); - - // appnexus - checkClientCalled(appnexusAdapterMock, 2); - - // adequant - checkClientCalled(adequantAdapterMock, 2); + const bidRequests = [{ + 'bidderCode': 'appnexus', + 'auctionId': '1863e370099523', + 'bidderRequestId': '2946b569352ef2', + 'tid': '34566b569352ef2', + 'timeout': 1000, + 'src': 's2s', + 'adUnitsS2SCopy': [ + { + 'code': '/19968336/header-bid-tag1', + 'sizes': [ + { + 'w': 728, + 'h': 90 + }, + { + 'w': 970, + 'h': 90 + } + ], + 'bids': [ + { + 'bidder': 'appnexus', + 'params': { + 'placementId': '543221', + 'test': 'me' + }, + 'adUnitCode': '/19968336/header-bid-tag1', + 'sizes': [ + [ + 728, + 90 + ], + [ + 970, + 90 + ] + ], + 'bidId': '68136e1c47023d', + 'bidderRequestId': '55e24a66bed717', + 'auctionId': '1ff753bd4ae5cb', + 'startTime': 1463510220995, + 'status': 1, + 'bid_id': '68136e1c47023d' + } + ] + }, + { + 'code': '/19968336/header-bid-tag-0', + 'sizes': [ + { + 'w': 300, + 'h': 250 + }, + { + 'w': 300, + 'h': 600 + } + ], + 'bids': [ + { + 'bidder': 'appnexus', + 'params': { + 'placementId': '5324321' + }, + 'adUnitCode': '/19968336/header-bid-tag-0', + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + 'bidId': '7e5d6af25ed188', + 'bidderRequestId': '55e24a66bed717', + 'auctionId': '1ff753bd4ae5cb', + 'startTime': 1463510220996, + 'bid_id': '7e5d6af25ed188' + } + ] + } + ], + 'bids': [ + { + 'bidder': 'appnexus', + 'params': { + 'placementId': '4799418', + 'test': 'me' + }, + 'adUnitCode': '/19968336/header-bid-tag1', + 'sizes': [ + [ + 728, + 90 + ], + [ + 970, + 90 + ] + ], + 'bidId': '392b5a6b05d648', + 'bidderRequestId': '2946b569352ef2', + 'auctionId': '1863e370099523', + 'startTime': 1462918897462, + 'status': 1, + 'transactionId': 'fsafsa' + }, + { + 'bidder': 'appnexus', + 'params': { + 'placementId': '4799418' + }, + 'adUnitCode': '/19968336/header-bid-tag-0', + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + 'bidId': '4dccdc37746135', + 'bidderRequestId': '2946b569352ef2', + 'auctionId': '1863e370099523', + 'startTime': 1462918897463, + 'status': 1, + 'transactionId': 'fsafsa' + } + ], + 'start': 1462918897460 + }, + { + 'bidderCode': 'pubmatic', + 'auctionId': '1863e370099523', + 'bidderRequestId': '2946b569352ef2', + 'tid': '2342342342lfi23', + 'timeout': 1000, + 'src': 's2s', + 'adUnitsS2SCopy': [ + { + 'code': '/19968336/header-bid-tag1', + 'sizes': [ + { + 'w': 728, + 'h': 90 + }, + { + 'w': 970, + 'h': 90 + } + ], + 'bids': [ + { + 'bidder': 'pubmatic', + 'params': { + 'placementId': '543221', + 'test': 'me' + }, + 'adUnitCode': '/19968336/header-bid-tag1', + 'sizes': [ + [ + 728, + 90 + ], + [ + 970, + 90 + ] + ], + 'bidId': '68136e1c47023d', + 'bidderRequestId': '55e24a66bed717', + 'auctionId': '1ff753bd4ae5cb', + 'startTime': 1463510220995, + 'status': 1, + 'bid_id': '68136e1c47023d' + } + ] + }, + { + 'code': '/19968336/header-bid-tag-0', + 'sizes': [ + { + 'w': 300, + 'h': 250 + }, + { + 'w': 300, + 'h': 600 + } + ], + 'bids': [ + { + 'bidder': 'pubmatic', + 'params': { + 'placementId': '5324321' + }, + 'adUnitCode': '/19968336/header-bid-tag-0', + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + 'bidId': '7e5d6af25ed188', + 'bidderRequestId': '55e24a66bed717', + 'auctionId': '1ff753bd4ae5cb', + 'startTime': 1463510220996, + 'bid_id': '7e5d6af25ed188' + } + ] + } + ], + 'bids': [ + { + 'bidder': 'pubmatic', + 'params': { + 'placementId': '4799418', + 'test': 'me' + }, + 'adUnitCode': '/19968336/header-bid-tag1', + 'sizes': [ + [ + 728, + 90 + ], + [ + 970, + 90 + ] + ], + 'bidId': '392b5a6b05d648', + 'bidderRequestId': '2946b569352ef2', + 'auctionId': '1863e370099523', + 'startTime': 1462918897462, + 'status': 1, + 'transactionId': '4r42r23r23' + }, + { + 'bidder': 'pubmatic', + 'params': { + 'placementId': '4799418' + }, + 'adUnitCode': '/19968336/header-bid-tag-0', + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + 'bidId': '4dccdc37746135', + 'bidderRequestId': '2946b569352ef2', + 'auctionId': '1863e370099523', + 'startTime': 1462918897463, + 'status': 1, + 'transactionId': '4r42r23r23' + } + ], + 'start': 1462918897460 + }]; + + it('invokes callBids on the S2S adapter', function () { + adapterManager.callBids( + getAdUnits(), + bidRequests, + () => {}, + () => () => {} + ); + sinon.assert.calledTwice(prebidServerAdapterMock.callBids); + }); + + // Enable this test when prebidServer adapter is made 1.0 compliant + it('invokes callBids with only s2s bids', function () { + const adUnits = getAdUnits(); + // adUnit without appnexus bidder + adUnits.push({ + 'code': '123', + 'sizes': [300, 250], + 'bids': [ + { + 'bidder': 'adequant', + 'params': { + 'publisher_id': '1234567', + 'bidfloor': 0.01 + } + } + ] + }); + + adapterManager.callBids( + adUnits, + bidRequests, + () => {}, + () => () => {} + ); + const requestObj = prebidServerAdapterMock.callBids.firstCall.args[0]; + expect(requestObj.ad_units.length).to.equal(2); + sinon.assert.calledTwice(prebidServerAdapterMock.callBids); + }); + + describe('BID_REQUESTED event', function () { + // function to count BID_REQUESTED events + let cnt, count = () => cnt++; + + beforeEach(function () { + prebidServerAdapterMock.callBids.reset(); + cnt = 0; + events.on(CONSTANTS.EVENTS.BID_REQUESTED, count); + }); + + afterEach(function () { + events.off(CONSTANTS.EVENTS.BID_REQUESTED, count); + }); + + it('should fire for s2s requests', function () { + let adUnits = utils.deepClone(getAdUnits()).map(adUnit => { + adUnit.bids = adUnit.bids.filter(bid => includes(['appnexus', 'pubmatic'], bid.bidder)); + return adUnit; + }) + let bidRequests = adapterManager.makeBidRequests(adUnits, 1111, 2222, 1000); + adapterManager.callBids(adUnits, bidRequests, () => {}, () => {}); + expect(cnt).to.equal(2); + sinon.assert.calledTwice(prebidServerAdapterMock.callBids); + }); + + it('should fire for simultaneous s2s and client requests', function () { + adapterManager.bidderRegistry['adequant'] = adequantAdapterMock; + let adUnits = utils.deepClone(getAdUnits()).map(adUnit => { + adUnit.bids = adUnit.bids.filter(bid => includes(['adequant', 'appnexus', 'pubmatic'], bid.bidder)); + return adUnit; + }) + let bidRequests = adapterManager.makeBidRequests(adUnits, 1111, 2222, 1000); + adapterManager.callBids(adUnits, bidRequests, () => {}, () => {}); + expect(cnt).to.equal(3); + sinon.assert.calledTwice(prebidServerAdapterMock.callBids); + sinon.assert.calledOnce(adequantAdapterMock.callBids); + adequantAdapterMock.callBids.reset(); + delete adapterManager.bidderRegistry['adequant']; + }); + }); + }); // end multiple s2s tests + + describe('s2sTesting', function () { + let doneStub = sinon.stub(); + let ajaxStub = sinon.stub(); + + function getTestAdUnits() { + // copy adUnits + // return JSON.parse(JSON.stringify(getAdUnits())); + return utils.deepClone(getAdUnits()).map(adUnit => { + adUnit.bids = adUnit.bids.filter(bid => includes(['adequant', 'appnexus', 'rubicon'], bid.bidder)); + return adUnit; + }) + } + + function callBids(adUnits = getTestAdUnits()) { + let bidRequests = adapterManager.makeBidRequests(adUnits, 1111, 2222, 1000); + adapterManager.callBids(adUnits, bidRequests, doneStub, ajaxStub); + } + + function checkServerCalled(numAdUnits, numBids) { + sinon.assert.calledOnce(prebidServerAdapterMock.callBids); + let requestObj = prebidServerAdapterMock.callBids.firstCall.args[0]; + expect(requestObj.ad_units.length).to.equal(numAdUnits); + for (let i = 0; i < numAdUnits; i++) { + expect(requestObj.ad_units[i].bids.filter((bid) => { + return bid.bidder === 'appnexus' || bid.bidder === 'adequant'; + }).length).to.equal(numBids); + } + } + + function checkClientCalled(adapter, numBids) { + sinon.assert.calledOnce(adapter.callBids); + expect(adapter.callBids.firstCall.args[0].bids.length).to.equal(numBids); + } + + let TESTING_CONFIG = utils.deepClone(CONFIG); + Object.assign(TESTING_CONFIG, { + bidders: ['appnexus', 'adequant'], + testing: true + }); + let stubGetSourceBidderMap; + + beforeEach(function () { + config.setConfig({s2sConfig: TESTING_CONFIG}); + adapterManager.bidderRegistry['prebidServer'] = prebidServerAdapterMock; + adapterManager.bidderRegistry['adequant'] = adequantAdapterMock; + adapterManager.bidderRegistry['appnexus'] = appnexusAdapterMock; + adapterManager.bidderRegistry['rubicon'] = rubiconAdapterMock; + + stubGetSourceBidderMap = sinon.stub(s2sTesting, 'getSourceBidderMap'); + + prebidServerAdapterMock.callBids.reset(); + adequantAdapterMock.callBids.reset(); + appnexusAdapterMock.callBids.reset(); + rubiconAdapterMock.callBids.reset(); + }); + + afterEach(function () { + s2sTesting.getSourceBidderMap.restore(); + }); + + it('calls server adapter if no sources defined', function () { + stubGetSourceBidderMap.returns({[s2sTesting.CLIENT]: [], [s2sTesting.SERVER]: []}); + callBids(); + + // server adapter + checkServerCalled(2, 2); + + // appnexus + sinon.assert.notCalled(appnexusAdapterMock.callBids); + + // adequant + sinon.assert.notCalled(adequantAdapterMock.callBids); + }); + + it('calls client adapter if one client source defined', function () { + stubGetSourceBidderMap.returns({[s2sTesting.CLIENT]: ['appnexus'], [s2sTesting.SERVER]: []}); + callBids(); + + // server adapter + checkServerCalled(2, 2); + + // appnexus + checkClientCalled(appnexusAdapterMock, 2); + + // adequant + sinon.assert.notCalled(adequantAdapterMock.callBids); + }); + + it('calls client adapters if client sources defined', function () { + stubGetSourceBidderMap.returns({[s2sTesting.CLIENT]: ['appnexus', 'adequant'], [s2sTesting.SERVER]: []}); + callBids(); + + // server adapter + checkServerCalled(2, 2); + + // appnexus + checkClientCalled(appnexusAdapterMock, 2); + + // adequant + checkClientCalled(adequantAdapterMock, 2); + }); + + it('does not call server adapter for bidders that go to client', function () { + stubGetSourceBidderMap.returns({[s2sTesting.CLIENT]: ['appnexus', 'adequant'], [s2sTesting.SERVER]: []}); + var adUnits = getTestAdUnits(); + adUnits[0].bids[0].finalSource = s2sTesting.CLIENT; + adUnits[0].bids[1].finalSource = s2sTesting.CLIENT; + adUnits[1].bids[0].finalSource = s2sTesting.CLIENT; + adUnits[1].bids[1].finalSource = s2sTesting.CLIENT; + callBids(adUnits); + + // server adapter + sinon.assert.notCalled(prebidServerAdapterMock.callBids); + + // appnexus + checkClientCalled(appnexusAdapterMock, 2); + + // adequant + checkClientCalled(adequantAdapterMock, 2); + }); + + it('does not call client adapters for bidders that go to server', function () { + stubGetSourceBidderMap.returns({[s2sTesting.CLIENT]: ['appnexus', 'adequant'], [s2sTesting.SERVER]: []}); + var adUnits = getTestAdUnits(); + adUnits[0].bids[0].finalSource = s2sTesting.SERVER; + adUnits[0].bids[1].finalSource = s2sTesting.SERVER; + adUnits[1].bids[0].finalSource = s2sTesting.SERVER; + adUnits[1].bids[1].finalSource = s2sTesting.SERVER; + callBids(adUnits); + + // server adapter + checkServerCalled(2, 2); + + // appnexus + sinon.assert.notCalled(appnexusAdapterMock.callBids); + + // adequant + sinon.assert.notCalled(adequantAdapterMock.callBids); + }); + + it('calls client and server adapters for bidders that go to both', function () { + stubGetSourceBidderMap.returns({[s2sTesting.CLIENT]: ['appnexus', 'adequant'], [s2sTesting.SERVER]: []}); + var adUnits = getTestAdUnits(); + // adUnits[0].bids[0].finalSource = s2sTesting.BOTH; + // adUnits[0].bids[1].finalSource = s2sTesting.BOTH; + // adUnits[1].bids[0].finalSource = s2sTesting.BOTH; + // adUnits[1].bids[1].finalSource = s2sTesting.BOTH; + callBids(adUnits); + + // server adapter + checkServerCalled(2, 2); + + // appnexus + checkClientCalled(appnexusAdapterMock, 2); + + // adequant + checkClientCalled(adequantAdapterMock, 2); + }); + + it('makes mixed client/server adapter calls for mixed bidder sources', function () { + stubGetSourceBidderMap.returns({[s2sTesting.CLIENT]: ['appnexus', 'adequant'], [s2sTesting.SERVER]: []}); + var adUnits = getTestAdUnits(); + adUnits[0].bids[0].finalSource = s2sTesting.CLIENT; + adUnits[0].bids[1].finalSource = s2sTesting.CLIENT; + adUnits[1].bids[0].finalSource = s2sTesting.SERVER; + adUnits[1].bids[1].finalSource = s2sTesting.SERVER; + callBids(adUnits); + + // server adapter + checkServerCalled(1, 2); + + // appnexus + checkClientCalled(appnexusAdapterMock, 1); + + // adequant + checkClientCalled(adequantAdapterMock, 1); + }); + }); + + describe('Multiple Server s2sTesting', function () { + let doneStub = sinon.stub(); + let ajaxStub = sinon.stub(); + + function getTestAdUnits() { + // copy adUnits + return utils.deepClone(getAdUnits()).map(adUnit => { + adUnit.bids = adUnit.bids.filter(bid => { + return includes(['adequant', 'appnexus', 'pubmatic', 'rubicon'], + bid.bidder); + }); + return adUnit; + }) + } + + function callBids(adUnits = getTestAdUnits()) { + let bidRequests = adapterManager.makeBidRequests(adUnits, 1111, 2222, 1000); + adapterManager.callBids(adUnits, bidRequests, doneStub, ajaxStub); + } + + function checkServerCalled(numAdUnits, firstConfigNumBids, secondConfigNumBids) { + let requestObjects = []; + let configBids; + if (firstConfigNumBids === 0 || secondConfigNumBids === 0) { + configBids = Math.max(firstConfigNumBids, secondConfigNumBids) + sinon.assert.calledOnce(prebidServerAdapterMock.callBids); + let requestObj1 = prebidServerAdapterMock.callBids.firstCall.args[0]; + requestObjects.push(requestObj1) + } else { + sinon.assert.calledTwice(prebidServerAdapterMock.callBids); + let requestObj1 = prebidServerAdapterMock.callBids.firstCall.args[0]; + let requestObj2 = prebidServerAdapterMock.callBids.secondCall.args[0]; + requestObjects.push(requestObj1, requestObj2); + } + + requestObjects.forEach((requestObj, index) => { + const numBids = configBids !== undefined ? configBids : index === 0 ? firstConfigNumBids : secondConfigNumBids + expect(requestObj.ad_units.length).to.equal(numAdUnits); + for (let i = 0; i < numAdUnits; i++) { + expect(requestObj.ad_units[i].bids.filter((bid) => { + return bid.bidder === 'appnexus' || bid.bidder === 'adequant' || bid.bidder === 'pubmatic'; + }).length).to.equal(numBids); + } + }) + } + + function checkClientCalled(adapter, numBids) { + sinon.assert.calledOnce(adapter.callBids); + expect(adapter.callBids.firstCall.args[0].bids.length).to.equal(numBids); + } + + beforeEach(function () { + allS2SBidders.length = 0; + clientTestAdapters.length = 0 + + adapterManager.bidderRegistry['prebidServer'] = prebidServerAdapterMock; + adapterManager.bidderRegistry['adequant'] = adequantAdapterMock; + adapterManager.bidderRegistry['appnexus'] = appnexusAdapterMock; + adapterManager.bidderRegistry['rubicon'] = rubiconAdapterMock; + adapterManager.bidderRegistry['pubmatic'] = pubmaticAdapterMock; + + prebidServerAdapterMock.callBids.reset(); + adequantAdapterMock.callBids.reset(); + appnexusAdapterMock.callBids.reset(); + rubiconAdapterMock.callBids.reset(); + pubmaticAdapterMock.callBids.reset(); + }); + + it('calls server adapter if no sources defined for config where testing is true, ' + + 'calls client adapter for second config where testing is false', function () { + let TEST_CONFIG = utils.deepClone(CONFIG); + Object.assign(TEST_CONFIG, { + bidders: ['appnexus', 'adequant'], + testing: true, + }); + let TEST_CONFIG2 = utils.deepClone(CONFIG2); + Object.assign(TEST_CONFIG2, { + bidders: ['pubmatic'], + testing: true + }); + + config.setConfig({s2sConfig: [TEST_CONFIG, TEST_CONFIG2]}); + + callBids(); + + // server adapter + checkServerCalled(2, 2, 1); + + // appnexus + sinon.assert.notCalled(appnexusAdapterMock.callBids); + + // adequant + sinon.assert.notCalled(adequantAdapterMock.callBids); + + // pubmatic + sinon.assert.notCalled(pubmaticAdapterMock.callBids); + + // rubicon + sinon.assert.called(rubiconAdapterMock.callBids); + }); + + it('calls client adapter if one client source defined for config where testing is true, ' + + 'calls client adapter for second config where testing is false', function () { + let TEST_CONFIG = utils.deepClone(CONFIG); + Object.assign(TEST_CONFIG, { + bidders: ['appnexus', 'adequant'], + bidderControl: { + appnexus: { + bidSource: { server: 0, client: 100 }, + includeSourceKvp: true, + }, + }, + testing: true, + }); + let TEST_CONFIG2 = utils.deepClone(CONFIG2); + Object.assign(TEST_CONFIG2, { + bidders: ['pubmatic'], + testing: true + }); + + config.setConfig({s2sConfig: [TEST_CONFIG, TEST_CONFIG2]}); + callBids(); + + // server adapter + checkServerCalled(2, 1, 1); + + // appnexus + checkClientCalled(appnexusAdapterMock, 2); + + // adequant + sinon.assert.notCalled(adequantAdapterMock.callBids); + + // pubmatic + sinon.assert.notCalled(pubmaticAdapterMock.callBids); + + // rubicon + checkClientCalled(rubiconAdapterMock, 1); }); - it('calls client adapters if client sources defined', function () { - stubGetSourceBidderMap.returns({[s2sTesting.CLIENT]: ['appnexus', 'adequant'], [s2sTesting.SERVER]: []}); + it('calls client adapters if client sources defined in first config and server in second config', function () { + let TEST_CONFIG = utils.deepClone(CONFIG); + Object.assign(TEST_CONFIG, { + bidders: ['appnexus', 'adequant'], + bidderControl: { + appnexus: { + bidSource: { server: 0, client: 100 }, + includeSourceKvp: true, + }, + adequant: { + bidSource: { server: 0, client: 100 }, + includeSourceKvp: true, + }, + }, + testing: true, + }); + + let TEST_CONFIG2 = utils.deepClone(CONFIG2); + Object.assign(TEST_CONFIG2, { + bidders: ['pubmatic'], + testing: true + }); + + config.setConfig({s2sConfig: [TEST_CONFIG, TEST_CONFIG2]}); + callBids(); // server adapter - checkServerCalled(2, 2); + checkServerCalled(2, 0, 1); // appnexus checkClientCalled(appnexusAdapterMock, 2); // adequant checkClientCalled(adequantAdapterMock, 2); + + // pubmatic + sinon.assert.notCalled(pubmaticAdapterMock.callBids); + + // rubicon + checkClientCalled(rubiconAdapterMock, 1); }); - it('does not call server adapter for bidders that go to client', function () { - stubGetSourceBidderMap.returns({[s2sTesting.CLIENT]: ['appnexus', 'adequant'], [s2sTesting.SERVER]: []}); - var adUnits = getTestAdUnits(); - adUnits[0].bids[0].finalSource = s2sTesting.CLIENT; - adUnits[0].bids[1].finalSource = s2sTesting.CLIENT; - adUnits[1].bids[0].finalSource = s2sTesting.CLIENT; - adUnits[1].bids[1].finalSource = s2sTesting.CLIENT; - callBids(adUnits); + it('does not call server adapter for bidders that go to client when both configs are set to client', function () { + let TEST_CONFIG = utils.deepClone(CONFIG); + Object.assign(TEST_CONFIG, { + bidders: ['appnexus', 'adequant'], + bidderControl: { + appnexus: { + bidSource: { server: 0, client: 100 }, + includeSourceKvp: true, + }, + adequant: { + bidSource: { server: 0, client: 100 }, + includeSourceKvp: true, + }, + }, + testing: true, + }); + + let TEST_CONFIG2 = utils.deepClone(CONFIG2); + Object.assign(TEST_CONFIG2, { + bidders: ['pubmatic'], + bidderControl: { + pubmatic: { + bidSource: { server: 0, client: 100 }, + includeSourceKvp: true, + }, + }, + testing: true + }); + + config.setConfig({s2sConfig: [TEST_CONFIG, TEST_CONFIG2]}); + callBids(); - // server adapter sinon.assert.notCalled(prebidServerAdapterMock.callBids); // appnexus @@ -883,63 +1426,110 @@ describe('adapterManager tests', function () { // adequant checkClientCalled(adequantAdapterMock, 2); + + // pubmatic + checkClientCalled(pubmaticAdapterMock, 2); + + // rubicon + checkClientCalled(rubiconAdapterMock, 1); }); - it('does not call client adapters for bidders that go to server', function () { - stubGetSourceBidderMap.returns({[s2sTesting.CLIENT]: ['appnexus', 'adequant'], [s2sTesting.SERVER]: []}); - var adUnits = getTestAdUnits(); - adUnits[0].bids[0].finalSource = s2sTesting.SERVER; - adUnits[0].bids[1].finalSource = s2sTesting.SERVER; - adUnits[1].bids[0].finalSource = s2sTesting.SERVER; - adUnits[1].bids[1].finalSource = s2sTesting.SERVER; - callBids(adUnits); + it('does not call client adapters for bidders in either config when testServerOnly if true in first config', function () { + let TEST_CONFIG = utils.deepClone(CONFIG); + Object.assign(TEST_CONFIG, { + bidders: ['appnexus', 'adequant'], + testServerOnly: true, + bidderControl: { + appnexus: { + bidSource: { server: 0, client: 100 }, + includeSourceKvp: true, + }, + adequant: { + bidSource: { server: 100, client: 0 }, + includeSourceKvp: true, + }, + }, + testing: true, + }); + + let TEST_CONFIG2 = utils.deepClone(CONFIG2); + Object.assign(TEST_CONFIG2, { + bidders: ['pubmatic'], + bidderControl: { + pubmatic: { + bidSource: { server: 0, client: 100 }, + includeSourceKvp: true, + } + }, + testing: true + }); + + config.setConfig({s2sConfig: [TEST_CONFIG, TEST_CONFIG2]}); + callBids(); // server adapter - checkServerCalled(2, 2); + checkServerCalled(2, 1, 0); // appnexus sinon.assert.notCalled(appnexusAdapterMock.callBids); // adequant sinon.assert.notCalled(adequantAdapterMock.callBids); - }); - it('calls client and server adapters for bidders that go to both', function () { - stubGetSourceBidderMap.returns({[s2sTesting.CLIENT]: ['appnexus', 'adequant'], [s2sTesting.SERVER]: []}); - var adUnits = getTestAdUnits(); - // adUnits[0].bids[0].finalSource = s2sTesting.BOTH; - // adUnits[0].bids[1].finalSource = s2sTesting.BOTH; - // adUnits[1].bids[0].finalSource = s2sTesting.BOTH; - // adUnits[1].bids[1].finalSource = s2sTesting.BOTH; - callBids(adUnits); + // pubmatic + sinon.assert.notCalled(pubmaticAdapterMock.callBids); - // server adapter - checkServerCalled(2, 2); + // rubicon + sinon.assert.notCalled(rubiconAdapterMock.callBids); + }); - // appnexus - checkClientCalled(appnexusAdapterMock, 2); + it('does not call client adapters for bidders in either config when testServerOnly if true in second config', function () { + let TEST_CONFIG = utils.deepClone(CONFIG); + Object.assign(TEST_CONFIG, { + bidders: ['appnexus', 'adequant'], + bidderControl: { + appnexus: { + bidSource: { server: 0, client: 100 }, + includeSourceKvp: true, + }, + adequant: { + bidSource: { server: 100, client: 0 }, + includeSourceKvp: true, + }, + }, + testing: true, + }); - // adequant - checkClientCalled(adequantAdapterMock, 2); - }); + let TEST_CONFIG2 = utils.deepClone(CONFIG2); + Object.assign(TEST_CONFIG2, { + bidders: ['pubmatic'], + testServerOnly: true, + bidderControl: { + pubmatic: { + bidSource: { server: 100, client: 0 }, + includeSourceKvp: true, + } + }, + testing: true + }); - it('makes mixed client/server adapter calls for mixed bidder sources', function () { - stubGetSourceBidderMap.returns({[s2sTesting.CLIENT]: ['appnexus', 'adequant'], [s2sTesting.SERVER]: []}); - var adUnits = getTestAdUnits(); - adUnits[0].bids[0].finalSource = s2sTesting.CLIENT; - adUnits[0].bids[1].finalSource = s2sTesting.CLIENT; - adUnits[1].bids[0].finalSource = s2sTesting.SERVER; - adUnits[1].bids[1].finalSource = s2sTesting.SERVER; - callBids(adUnits); + config.setConfig({s2sConfig: [TEST_CONFIG, TEST_CONFIG2]}); + callBids(); // server adapter - checkServerCalled(1, 2); + checkServerCalled(2, 1, 1); // appnexus - checkClientCalled(appnexusAdapterMock, 1); + sinon.assert.notCalled(appnexusAdapterMock.callBids); // adequant - checkClientCalled(adequantAdapterMock, 1); + sinon.assert.notCalled(adequantAdapterMock.callBids); + + // pubmatic + sinon.assert.notCalled(pubmaticAdapterMock.callBids); + + // rubicon + sinon.assert.notCalled(rubiconAdapterMock.callBids); }); }); @@ -988,6 +1578,27 @@ describe('adapterManager tests', function () { expect(adapterManager.aliasRegistry).to.have.property('s2sAlias'); }); + it('should allow an alias if alias is part of s2sConfig.bidders for multiple s2sConfigs', function () { + let testS2sConfig = utils.deepClone(CONFIG); + testS2sConfig.bidders = ['s2sAlias']; + config.setConfig({s2sConfig: [ + testS2sConfig, { + enabled: true, + endpoint: 'rp-pbs-endpoint-test.com', + timeout: 500, + maxBids: 1, + adapter: 'prebidServer', + bidders: ['s2sRpAlias'], + accountId: 'def' + } + ]}); + + adapterManager.aliasBidAdapter('s2sBidder', 's2sAlias'); + expect(adapterManager.aliasRegistry).to.have.property('s2sAlias'); + adapterManager.aliasBidAdapter('s2sBidder', 's2sRpAlias'); + expect(adapterManager.aliasRegistry).to.have.property('s2sRpAlias'); + }); + it('should throw an error if alias + bidder are unknown and not part of s2sConfig.bidders', function () { let testS2sConfig = utils.deepClone(CONFIG); testS2sConfig.bidders = ['s2sAlias']; @@ -1003,6 +1614,7 @@ describe('adapterManager tests', function () { describe('makeBidRequests', function () { let adUnits; beforeEach(function () { + allS2SBidders.length = 0 adUnits = utils.deepClone(getAdUnits()).map(adUnit => { adUnit.bids = adUnit.bids.filter(bid => includes(['appnexus', 'rubicon'], bid.bidder)); return adUnit; @@ -1054,6 +1666,8 @@ describe('adapterManager tests', function () { describe('sizeMapping', function () { beforeEach(function () { + allS2SBidders.length = 0; + clientTestAdapters.length = 0; sinon.stub(window, 'matchMedia').callsFake(() => ({matches: true})); }); @@ -1187,7 +1801,7 @@ describe('adapterManager tests', function () { [] ); - // only valid sizes as specified in size config should show up in bidRequests + // only valid sizes as specified in size config should show up in bidRequests bidRequests.forEach(bidRequest => { bidRequest.bids.forEach(bid => { bid.sizes.forEach(size => { @@ -1294,9 +1908,13 @@ describe('adapterManager tests', function () { describe('s2sTesting - testServerOnly', () => { beforeEach(() => { config.setConfig({ s2sConfig: getServerTestingConfig(CONFIG) }); + allS2SBidders.length = 0 + s2sTesting.bidSource = {}; }); - afterEach(() => config.resetConfig()); + afterEach(() => { + config.resetConfig(); + }); const makeBidRequests = ads => { let bidRequests = adapterManager.makeBidRequests( @@ -1413,5 +2031,192 @@ describe('adapterManager tests', function () { } ); }); + + describe('Multiple s2sTesting - testServerOnly', () => { + beforeEach(() => { + config.setConfig({s2sConfig: [getServerTestingConfig(CONFIG), CONFIG2]}); + }); + + afterEach(() => { + config.resetConfig() + allS2SBidders.length = 0; + s2sTesting.bidSource = {}; + }); + + const makeBidRequests = ads => { + let bidRequests = adapterManager.makeBidRequests( + ads, 1111, 2222, 1000 + ); + + bidRequests.sort((a, b) => { + if (a.bidderCode < b.bidderCode) return -1; + if (a.bidderCode > b.bidderCode) return 1; + return 0; + }); + + return bidRequests; + }; + + const removeAdUnitsBidSource = adUnits => adUnits.map(adUnit => { + const newAdUnit = { ...adUnit }; + newAdUnit.bids = newAdUnit.bids.map(bid => { + if (bid.bidSource) delete bid.bidSource; + return bid; + }); + return newAdUnit; + }); + + it('suppresses all client bids if there are server bids resulting from bidSource at the adUnit Level', () => { + let ads = getServerTestingsAds(); + ads.push({ + code: 'test_div_5', + sizes: [[300, 250]], + bids: [{ bidder: 'pubmatic' }] + }) + const bidRequests = makeBidRequests(ads); + + expect(bidRequests).lengthOf(3); + + expect(bidRequests[0].bids).lengthOf(1); + expect(bidRequests[0].bids[0].bidder).equals('openx'); + expect(bidRequests[0].bids[0].finalSource).equals('server'); + + expect(bidRequests[0].bids).lengthOf(1); + expect(bidRequests[1].bids[0].bidder).equals('pubmatic'); + expect(bidRequests[1].bids[0].finalSource).equals('server'); + + expect(bidRequests[0].bids).lengthOf(1); + expect(bidRequests[2].bids[0].bidder).equals('rubicon'); + expect(bidRequests[2].bids[0].finalSource).equals('server'); + }); + + it('should not surpress client side bids if testServerOnly is true in one config, ' + + ',bidderControl resolves to server in another config' + + 'and there are no bid with bidSource at the adUnit Level', () => { + let testConfig1 = utils.deepClone(getServerTestingConfig(CONFIG)); + let testConfig2 = utils.deepClone(CONFIG2); + testConfig1.testServerOnly = false; + testConfig2.testServerOnly = true; + testConfig2.testing = true; + testConfig2.bidderControl = { + 'pubmatic': { + bidSource: { server: 0, client: 100 }, + includeSourceKvp: true, + }, + }; + config.setConfig({s2sConfig: [testConfig1, testConfig2]}); + + let ads = [ + { + code: 'test_div_1', + sizes: [[300, 250]], + bids: [{ bidder: 'adequant' }] + }, + { + code: 'test_div_2', + sizes: [[300, 250]], + bids: [{ bidder: 'openx' }] + }, + { + code: 'test_div_3', + sizes: [[300, 250]], + bids: [{ bidder: 'pubmatic' }] + }, + ]; + const bidRequests = makeBidRequests(ads); + + expect(bidRequests).lengthOf(3); + + expect(bidRequests[0].bids).lengthOf(1); + expect(bidRequests[0].bids[0].bidder).equals('adequant'); + expect(bidRequests[0].bids[0].finalSource).equals('client'); + + expect(bidRequests[1].bids).lengthOf(1); + expect(bidRequests[1].bids[0].bidder).equals('openx'); + expect(bidRequests[1].bids[0].finalSource).equals('server'); + + expect(bidRequests[2].bids).lengthOf(1); + expect(bidRequests[2].bids[0].bidder).equals('pubmatic'); + expect(bidRequests[2].bids[0].finalSource).equals('client'); + }); + + // todo: update description + it('suppresses all, and only, client bids if there are bids resulting from bidSource at the adUnit Level', () => { + const ads = getServerTestingsAds(); + + // change this adUnit to be server based + ads[1].bids[1].bidSource.client = 0; + ads[1].bids[1].bidSource.server = 100; + + const bidRequests = makeBidRequests(ads); + + expect(bidRequests).lengthOf(3); + + expect(bidRequests[0].bids).lengthOf(1); + expect(bidRequests[0].bids[0].bidder).equals('appnexus'); + expect(bidRequests[0].bids[0].finalSource).equals('server'); + + expect(bidRequests[1].bids).lengthOf(1); + expect(bidRequests[1].bids[0].bidder).equals('openx'); + expect(bidRequests[1].bids[0].finalSource).equals('server'); + + expect(bidRequests[2].bids).lengthOf(1); + expect(bidRequests[2].bids[0].bidder).equals('rubicon'); + expect(bidRequests[2].bids[0].finalSource).equals('server'); + }); + + // we have a server call now + it('does not suppress client bids if no "test case" bids result in a server bid', () => { + const ads = getServerTestingsAds(); + + // change this adUnit to be client based + ads[0].bids[0].bidSource.client = 100; + ads[0].bids[0].bidSource.server = 0; + + const bidRequests = makeBidRequests(ads); + + expect(bidRequests).lengthOf(4); + + expect(bidRequests[0].bids).lengthOf(1); + expect(bidRequests[0].bids[0].bidder).equals('adequant'); + expect(bidRequests[0].bids[0].finalSource).equals('client'); + + expect(bidRequests[1].bids).lengthOf(2); + expect(bidRequests[1].bids[0].bidder).equals('appnexus'); + expect(bidRequests[1].bids[0].finalSource).equals('client'); + expect(bidRequests[1].bids[1].bidder).equals('appnexus'); + expect(bidRequests[1].bids[1].finalSource).equals('client'); + + expect(bidRequests[2].bids).lengthOf(1); + expect(bidRequests[2].bids[0].bidder).equals('openx'); + expect(bidRequests[2].bids[0].finalSource).equals('server'); + + expect(bidRequests[3].bids).lengthOf(2); + expect(bidRequests[3].bids[0].bidder).equals('rubicon'); + expect(bidRequests[3].bids[0].finalSource).equals('client'); + expect(bidRequests[3].bids[1].bidder).equals('rubicon'); + expect(bidRequests[3].bids[1].finalSource).equals('client'); + }); + + it( + 'should surpress client side bids if no ad unit bidSources are set, ' + + 'but bidderControl resolves to server', + () => { + const ads = removeAdUnitsBidSource(getServerTestingsAds()); + + const bidRequests = makeBidRequests(ads); + + expect(bidRequests).lengthOf(2); + + expect(bidRequests[0].bids).lengthOf(1); + expect(bidRequests[0].bids[0].bidder).equals('openx'); + expect(bidRequests[0].bids[0].finalSource).equals('server'); + + expect(bidRequests[1].bids).lengthOf(2); + expect(bidRequests[1].bids[0].bidder).equals('rubicon'); + expect(bidRequests[1].bids[0].finalSource).equals('server'); + } + ); + }); }); }); diff --git a/test/spec/unit/core/bidderFactory_spec.js b/test/spec/unit/core/bidderFactory_spec.js index 692cf9a6475..a7e8a0d7871 100644 --- a/test/spec/unit/core/bidderFactory_spec.js +++ b/test/spec/unit/core/bidderFactory_spec.js @@ -673,6 +673,28 @@ describe('registerBidder', function () { expect(registerBidAdapterStub.getCall(2).args[0].getSpec().gvlid).to.equal(2); expect(registerBidAdapterStub.getCall(3).args[0].getSpec().gvlid).to.equal(undefined); }) + + it('should register alias with skipPbsAliasing', function() { + const aliases = [ + { + code: 'foo', + skipPbsAliasing: true + }, + { + code: 'bar', + skipPbsAliasing: false + }, + { + code: 'baz' + } + ] + const thisSpec = Object.assign(newEmptySpec(), { aliases: aliases }); + registerBidder(thisSpec); + + expect(registerBidAdapterStub.getCall(1).args[0].getSpec().skipPbsAliasing).to.equal(true); + expect(registerBidAdapterStub.getCall(2).args[0].getSpec().skipPbsAliasing).to.equal(false); + expect(registerBidAdapterStub.getCall(3).args[0].getSpec().skipPbsAliasing).to.equal(undefined); + }) }) describe('validate bid response: ', function () { diff --git a/test/spec/unit/core/storageManager_spec.js b/test/spec/unit/core/storageManager_spec.js index de09df5b196..5bb766217f5 100644 --- a/test/spec/unit/core/storageManager_spec.js +++ b/test/spec/unit/core/storageManager_spec.js @@ -46,16 +46,17 @@ describe('storage manager', function() { describe('localstorage forbidden access in 3rd-party context', function() { let errorLogSpy; - const originalLocalStorage = { get: () => window.localStorage }; + let originalLocalStorage; const localStorageMock = { get: () => { throw Error } }; beforeEach(function() { + originalLocalStorage = window.localStorage; Object.defineProperty(window, 'localStorage', localStorageMock); errorLogSpy = sinon.spy(utils, 'logError'); }); afterEach(function() { - Object.defineProperty(window, 'localStorage', originalLocalStorage); + Object.defineProperty(window, 'localStorage', { get: () => originalLocalStorage }); errorLogSpy.restore(); }) @@ -70,4 +71,28 @@ describe('storage manager', function() { sinon.assert.calledThrice(errorLogSpy); }) }) + + describe('localstorage is enabled', function() { + let localStorage; + + beforeEach(function() { + localStorage = window.localStorage; + localStorage.clear(); + }); + + afterEach(function() { + localStorage.clear(); + }) + + it('should remove side-effect after checking', function () { + const storage = getStorageManager(); + + localStorage.setItem('unrelated', 'dummy'); + const val = storage.localStorageIsEnabled(); + + expect(val).to.be.true; + expect(localStorage.length).to.be.eq(1); + expect(localStorage.getItem('unrelated')).to.be.eq('dummy'); + }); + }); }); diff --git a/test/spec/userSync_spec.js b/test/spec/userSync_spec.js index 2b68349ca31..55b613ce929 100644 --- a/test/spec/userSync_spec.js +++ b/test/spec/userSync_spec.js @@ -475,6 +475,24 @@ describe('user sync', function () { }); expect(userSync.canBidderRegisterSync('iframe', 'otherTestBidder')).to.equal(false); }); + it('should return false for iframe if there is no iframe filterSettings', function () { + const userSync = newUserSync({ + config: { + syncEnabled: true, + filterSettings: { + image: { + bidders: '*', + filter: 'include' + } + }, + syncsPerBidder: 5, + syncDelay: 3000, + auctionDelay: 0 + } + }); + + expect(userSync.canBidderRegisterSync('iframe', 'otherTestBidder')).to.equal(false); + }); it('should return true if filter settings does allow it', function () { const userSync = newUserSync({ config: { diff --git a/webpack.conf.js b/webpack.conf.js index a5c75fa8a1a..6f0841757b0 100644 --- a/webpack.conf.js +++ b/webpack.conf.js @@ -13,7 +13,8 @@ var neverBundle = [ ]; var plugins = [ - new RequireEnsureWithoutJsonp() + new RequireEnsureWithoutJsonp(), + new webpack.EnvironmentPlugin(['LiveConnectMode']) ]; if (argv.analyze) {