diff --git a/.babelrc b/.babelrc deleted file mode 100644 index 5d42055cabb..00000000000 --- a/.babelrc +++ /dev/null @@ -1,19 +0,0 @@ -{ - "presets": [ - ["env", { - "targets": { - "browsers": [ - "chrome >= 61", - "safari >=8", - "edge >= 14", - "ff >= 57", - "ie >= 10", - "ios >= 8" - ] - } - }] - ], - "plugins": [ - "transform-object-assign" - ] -} diff --git a/.babelrc.js b/.babelrc.js new file mode 100644 index 00000000000..bece57ec4a5 --- /dev/null +++ b/.babelrc.js @@ -0,0 +1,34 @@ + +let path = require('path'); + +function useLocal(module) { + return require.resolve(module, { + paths: [ + __dirname + ] + }) +} + +module.exports = { + "presets": [ + [ + useLocal('@babel/preset-env'), + { + "targets": { + "browsers": [ + "chrome >= 61", + "safari >=8", + "edge >= 14", + "ff >= 57", + "ie >= 10", + "ios >= 8" + ] + } + } + ] + ], + "plugins": [ + path.resolve(__dirname, './plugins/pbjsGlobals.js'), + useLocal('babel-plugin-transform-object-assign') + ] +}; diff --git a/.circleci/config.yml b/.circleci/config.yml index fbf7e77e10f..0d48ec13fa1 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -2,49 +2,93 @@ # # Check https://circleci.com/docs/2.0/language-javascript/ for more details # + +aliases: + - &environment + docker: + # specify the version you desire here + - image: circleci/node:8.9.0 + + # Specify service dependencies here if necessary + # CircleCI maintains a library of pre-built images + # documented at https://circleci.com/docs/2.0/circleci-images/ + # - image: circleci/mongo:3.4.4 + working_directory: ~/Prebid.js + + - &restore_dep_cache + keys: + - v1-dependencies-{{ checksum "package.json" }} + # fallback to using the latest cache if no exact match is found + - v1-dependencies- + + - &save_dep_cache + paths: + - node_modules + key: v1-dependencies-{{ checksum "package.json" }} + + - &install + name: Install gulp cli + command: sudo npm install -g gulp-cli + + - &run_unit_test + name: BrowserStack testing + command: gulp test --browserstack --nolintfix + + - &run_endtoend_test + name: BrowserStack End to end testing + command: echo "127.0.0.1 test.localhost" | sudo tee -a /etc/hosts && gulp e2e-test --host=test.localhost + + # Download and run BrowserStack local + - &setup_browserstack + name : Download BrowserStack Local binary and start it. + command : | + # Download the browserstack binary file + wget "https://www.browserstack.com/browserstack-local/BrowserStackLocal-linux-x64.zip" + # Unzip it + unzip BrowserStackLocal-linux-x64.zip + # Run the file with user's access key + ./BrowserStackLocal ${BROWSERSTACK_ACCESS_KEY} & + + - &unit_test_steps + - checkout + - restore_cache: *restore_dep_cache + - run: npm install + - save_cache: *save_dep_cache + - run: *install + - run: *setup_browserstack + - run: *run_unit_test + + - &endtoend_test_steps + - checkout + - restore_cache: *restore_dep_cache + - run: npm install + - save_cache: *save_dep_cache + - run: *install + - run: *setup_browserstack + - run: *run_endtoend_test + version: 2 jobs: build: - docker: - # specify the version you desire here - - image: circleci/node:7.10 + <<: *environment + steps: *unit_test_steps - # Specify service dependencies here if necessary - # CircleCI maintains a library of pre-built images - # documented at https://circleci.com/docs/2.0/circleci-images/ - # - image: circleci/mongo:3.4.4 - - working_directory: ~/Prebid.js - - steps: - - checkout - - # Download and cache dependencies - - restore_cache: - keys: - - v1-dependencies-{{ checksum "package.json" }} - # fallback to using the latest cache if no exact match is found - - v1-dependencies- - - - run: npm install - - - save_cache: - paths: - - node_modules - key: v1-dependencies-{{ checksum "package.json" }} - - - run: sudo npm install -g gulp-cli - # Download and run BrowserStack local - - run: - name : Download BrowserStack Local binary and start it. - command : | - # Download the browserstack binary file - wget "https://www.browserstack.com/browserstack-local/BrowserStackLocal-linux-x64.zip" - # Unzip it - unzip BrowserStackLocal-linux-x64.zip - # Run the file with user's access key - ./BrowserStackLocal ${BROWSERSTACK_ACCESS_KEY} & - # run tests! - - run: - name: BrowserStack testing - command: gulp test --browserstack + e2etest: + <<: *environment + steps: *endtoend_test_steps + +workflows: + version: 2 + commit: + jobs: + - build + nightly: + triggers: + - schedule: + cron: "0 0 * * *" + filters: + branches: + only: + - master + jobs: + - e2etest diff --git a/.eslintrc.js b/.eslintrc.js index 02ff81614c7..56e4808f985 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,3 +1,6 @@ + +const allowedModules = require("./allowedModules"); + module.exports = { "env": { "browser": true, @@ -11,6 +14,9 @@ module.exports = { } }, "extends": "standard", + "plugins": [ + "prebid" + ], "globals": { "$$PREBID_GLOBAL$$": false }, @@ -31,5 +37,11 @@ module.exports = { "no-throw-literal": "off", "no-undef": "off", "no-useless-escape": "off", - } + }, + "overrides": Object.keys(allowedModules).map((key) => ({ + "files": key + "/**/*.js", + "rules": { + "prebid/validate-imports": ["error", allowedModules[key]] + } + })) }; diff --git a/.github/stale.yml b/.github/stale.yml index 0925c69c703..2afa7ecf0ec 100644 --- a/.github/stale.yml +++ b/.github/stale.yml @@ -8,6 +8,7 @@ exemptLabels: - security - bug - feature + - on hold # Label to use when marking an issue as stale staleLabel: stale # Comment to post when marking an issue as stale. Set to `false` to disable diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000000..795fe6a41a0 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,40 @@ +name: Build +on: + push: + branches: + - marfeel-master + +jobs: + build: + name: Build + runs-on: ubuntu-latest + steps: + - name: ⬇ Checkout + uses: actions/checkout@v2 + + - name: 🔧 Setup node + uses: actions/setup-node@v1 + node-version: '12.x' + registry-url: 'https://npm.pkg.github.com' + + - name: 📦 Install + uses: Marfeel/github-actions/common/actions/initialize@master + with: + gh-token: ${{ secrets.BOB_GITHUB_TOKEN }} + + - name: 🛠 Build + run: gulp build --modules=modules.json + + - name: ➗ Create prebid.ww.js from prebid.js + run: cp /build/dist/prebid.js /build/dist/prebid.ww.js + + - name: ♻️ Adapt to web worker + run: sed -i 's/location/originalLocation/g' ./build/dist/prebid.ww.js + + - name: 🚀 Publish + run: | + git config --local user.email 'tech@marfeel.com' + git config --local user.name 'GitHub Action' + + npm publish + diff --git a/.gitignore b/.gitignore index 88e849a35ad..c0452b7b3d0 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,9 @@ build/coverage/ .idea/ # if you remove the above rule, at least ignore the following: +# VS Code +.vscode/ + # User-specific stuff: # .idea/workspace.xml # .idea/tasks.xml diff --git a/.npmrc b/.npmrc new file mode 100644 index 00000000000..0b74ee7c128 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +registry=https://npm.pkg.github.com/marfeel \ No newline at end of file diff --git a/.nvmrc b/.nvmrc index 4fedf1d20e1..fa97ecedc28 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -7.0 +8.9 diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 00000000000..1d01478fcfd --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1,2 @@ +#Marfeel Prebid +* @Marfeel/alot diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7f4127cf3ba..9c00a2bf51a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,7 +4,7 @@ commit your changes, and [open a pull request](https://help.github.com/articles/ master branch. Pull requests must have 80% code coverage before beign considered for merge. -Additional details about the process can be found [here](./pr_review.md). +Additional details about the process can be found [here](./PR_REVIEW.md). ## Issues [prebid.org](http://prebid.org/) contains documentation that may help answer questions you have about using Prebid.js. @@ -64,8 +64,8 @@ A test module might have the following general structure: import { expect } from 'chai'; // may prefer 'assert' in place of 'expect' import adapter from 'src/adapters/'; -describe('', () => { - it('', () => { +describe('', function () { + it('', function () { // Arrange - set up preconditions and inputs // Act - call or act on the code under test // Assert - use chai to check that expected results have occurred diff --git a/PREBID_VERSIONING_DEPRECATION.md b/PREBID_VERSIONING_DEPRECATION.md new file mode 100644 index 00000000000..f006922259b --- /dev/null +++ b/PREBID_VERSIONING_DEPRECATION.md @@ -0,0 +1,25 @@ +# Prebid versioning and deprecation policy + +## Goals +Provide clear definitions and policy around versioning and breaking changes to APIs that are both publisher and demand partner facing. + + - Limit the number of breaking changes. + - Ensure significant time for updates for breaking changes so that publisher or demand partners do not break. + - Provide a path to deprecation and reduce technical debt and increase security. + - Major versions should not be changed more than once per 30 days. + +## Versioning + +Follow semantic versioning so that all breaking changes occur within a major release. A breaking change includes both demand partner internal APIs* and publisher facing APIs (global APIs). + +*Demand partner APIs may be excluded from breaking change policy at the core teams discretion if the changes are made so to be transparent to the bidders (such as internal refactoring). + +## Deprecation process + + - Open an issue with an "intent to implement" and "API impact" labels. + - Allow 2 weeks for discussion. + - Announce breaking change to the mailing list (TBD needs to be created). + - At least 2 core members needs to provide explicit approval for the deprecation. + - Open a PR against current master for console warning for possible breakage. + - Support the previous major version for a minimum of 30 days. + - Coordinate with the core team to ensure clean merging into feature branch if applicable (future major version branch). diff --git a/PR_REVIEW.md b/PR_REVIEW.md index 012a2d8b501..4ad8b8ec372 100644 --- a/PR_REVIEW.md +++ b/PR_REVIEW.md @@ -14,10 +14,16 @@ For modules and core platform updates, the initial reviewer should request an ad - Review for obvious errors or bad coding practice / use best judgement here. - If the change is a new feature / change to core prebid.js - review the change with a Tech Lead on the project and make sure they agree with the nature of change. - If the change results in needing updates to docs (such as public API change, module interface etc), add a label for "needs docs" and inform the submitter they must submit a docs PR to update the appropriate area of Prebid.org **before the PR can merge**. Help them with finding where the docs are located on prebid.org if needed. + - Below are some examples of bidder specific updates that should require docs update (in their dev-docs/bidders/bidder.md file): + - Add support for GDPR consentManagement module > add `gdpr_supported: true` + - Add support for userId module > add `userId: pubCommon, digitrust, newProviderHere` + - Add support for video and/or native mediaTypes > add `media_types: video, native` + - Add support for COPPA > add `coppa_supported: true` - If all above is good, add a `LGTM` comment and request 1 additional core member to review. - Once there is 2 `LGTM` on the PR, merge to master - Ask the submitter to add a PR for documentation if applicable. - Add a line into the [draft release](https://github.com/prebid/Prebid.js/releases) notes for this submission. If no draft release is available, create one using [this template]( https://gist.github.com/mkendall07/c3af6f4691bed8a46738b3675cb5a479) +- Add the PR to the appropriate project board (I.E. 1.23.0 Release) for the week, [see](https://github.com/prebid/Prebid.js/projects) ### New Adapter or updates to adapter process - Follow steps above for general review process. In addition, please verify the following: @@ -39,9 +45,9 @@ For modules and core platform updates, the initial reviewer should request an ad ## Ticket Coordinator Each week, Prebid Org assigns one person to keep an eye on incoming issues and PRs. That person should: -- Review issues and PRs at least once per weekday for new items. +- Review issues and PRs at least once per weekday for new items. Encourage a 48 "SLA" on PRs/issues assigned. Aim for touchpoint once every 48/hours. - For PRs: assign PRs to individuals on the PR review list. Try to be equitable -- not all PRs are created equally. Use the "Assigned" field and add the "Needs Review" label. -- For Issues: try to address questions and troubleshooting requests on your own, assigning them to others as needed. +- For Issues: try to address questions and troubleshooting requests on your own, assigning them to others as needed. Please add labels as appropriate (I.E. bug, question, backlog etc). - Issues that are questions or troubleshooting requests may be closed if the originator doesn't respond within a week to requests for confirmation or details. - Issues that are bug reports should be left open and assigned to someone in PR rotation to confirm or deny the bug status. - It's polite to check with others before assigning them large tasks. diff --git a/README.md b/README.md index 137374ebaa7..be07a27ddc1 100644 --- a/README.md +++ b/README.md @@ -16,11 +16,94 @@ Working examples can be found in [the developer docs](http://prebid.org/dev-docs **Table of Contents** +- [Usage](#Usage) - [Install](#Install) - [Build](#Build) - [Run](#Run) - [Contribute](#Contribute) + + +## Usage (as a npm dependency) + +*Note:* Requires Prebid.js v1.38.0+ + +Prebid.js depends on Babel and some Babel Plugins in order to run correctly in the browser. Here are some examples for +configuring webpack to work with Prebid.js. + +With Babel 7: +```javascript +// webpack.conf.js +let path = require('path'); +module.exports = { + mode: 'production', + module: { + rules: [ + + // this rule can be excluded if you don't require babel-loader for your other application files + { + test: /\.m?js$/, + exclude: /node_modules/, + use: { + loader: 'babel-loader', + } + }, + + // this separate rule is required to make sure that the Prebid.js files are babel-ified. this rule will + // override the regular exclusion from above (for being inside node_modules). + { + test: /.js$/, + include: new RegExp(`\\${path.sep}prebid\.js`), + use: { + loader: 'babel-loader', + // presets and plugins for Prebid.js must be manually specified separate from your other babel rule. + // this can be accomplished by requiring prebid's .babelrc.js file (requires Babel 7 and Node v8.9.0+) + options: require('prebid.js/.babelrc.js') + } + } + ] + } +} +``` + +Or for Babel 6: +```javascript + // you must manually install and specify the presets and plugins yourself + options: { + plugins: [ + "transform-object-assign", // required (for IE support) and "babel-plugin-transform-object-assign" + // must be installed as part of your package. + require('prebid.js/plugins/pbjsGlobals.js') // required! + ], + presets: [ + ["env", { // you can use other presets if you wish. + "targets": { // this example is using "babel-presets-env", which must be installed if you + "browsers": [ // follow this example. + ... // your browser targets. they should probably match the targets you're using for the rest + // of your application + ] + } + }] + ] + } +``` + +Then you can use Prebid.js as any other npm depedendency + +```javascript +import prebid from 'prebid.js'; +import 'prebid.js/modules/rubiconBidAdapter'; // imported modules will register themselves automatically with prebid +import 'prebid.js/modules/appnexusBidAdapter'; +prebid.processQueue(); // required to process existing pbjs.queue blocks and setup any further pbjs.queue execution + +prebid.requestBids({ + ... +}) + +``` + + + ## Install @@ -29,9 +112,16 @@ Working examples can be found in [the developer docs](http://prebid.org/dev-docs $ cd Prebid.js $ npm install -*Note:* You need to have `NodeJS` 4.x or greater installed. -*Note:* Because we have transitioned to using gulp 4.0 - you need to have `gulp-cli` installed globally prior to running the general `npm install`. Run the following command to perform the install: `npm install gulp-cli -g` -If you have a previous version of `gulp` installed globally, you'll need to remove it before installing `gulp-cli`. This removal can be done with the command: `npm rm gulp -g` +*Note:* You need to have `NodeJS` 8.9.x 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. + +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). + +To remove the old package, you can use the command: `npm rm gulp -g` + +Once setup, run the following command to globally install the `gulp-cli` package: `npm install gulp-cli -g` + @@ -117,10 +207,20 @@ gulp test-coverage gulp view-coverage ``` -For end-to-end testing, edit the example file `./integrationExamples/gpt/pbjs_example_gpt.html`: +For Prebid.org members with access to BrowserStack, additional end-to-end testing can be done with: -1. Change `{id}` values appropriately to set up ad units and bidders -2. Set the path to Prebid.js in your example file as shown below (see `pbs.src`). +```bash +gulp e2e-test --host=test.localhost +``` + +To run these tests, the following items are required: +- setup an alias of localhost in your `hosts` file (eg `127.0.0.1 test.localhost`); note - you can use any alias. Use this alias in the command-line argument above. +- access to [BrowserStack](https://www.browserstack.com/) account. Assign the following variables in your bash_profile: +```bash +export BROWSERSTACK_USERNAME='YourUserNameHere' +export BROWSERSTACK_ACCESS_KEY='YourAccessKeyHere' +``` +You can get these BrowserStack values from your profile page. For development: @@ -170,7 +270,7 @@ Many SSPs, bidders, and publishers have contributed to this project. [60+ Bidder For guidelines, see [Contributing](./CONTRIBUTING.md). -Our PR review process can be found [here](https://github.com/prebid/Prebid.js/tree/master/pr_review.md). +Our PR review process can be found [here](https://github.com/prebid/Prebid.js/tree/master/PR_REVIEW.md). ### Add a Bidder Adapter @@ -210,7 +310,7 @@ For instructions on writing tests for Prebid.js, see [Testing Prebid.js](http:// ### Supported Browsers -Prebid.js is supported on IE10+ and modern browsers. +Prebid.js is supported on IE11 and modern browsers. ### Governance Review our governance model [here](https://github.com/prebid/Prebid.js/tree/master/governance.md). diff --git a/RELEASE_SCHEDULE.md b/RELEASE_SCHEDULE.md index a1fa77b7db0..7b2c6244bd7 100644 --- a/RELEASE_SCHEDULE.md +++ b/RELEASE_SCHEDULE.md @@ -6,7 +6,7 @@ ## Release Schedule -We push a new release of Prebid.js every other week on Tuesday. During the adoption phase for 1.x, we are releasing updates for 1.x and 0.x at the same time. +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. @@ -19,7 +19,7 @@ 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) for master branch will show you detailed results. +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, - Try to fix the failing tests. @@ -128,7 +128,7 @@ Characteristics of a `GA` release: ## FAQs -**1. Is there flexibility in the 2-week schedule?** +**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. diff --git a/allowedModules.js b/allowedModules.js new file mode 100644 index 00000000000..e66b8e24098 --- /dev/null +++ b/allowedModules.js @@ -0,0 +1,24 @@ + +const sharedWhiteList = [ + "core-js/library/fn/array/find", // no ie11 + "core-js/library/fn/array/includes", // no ie11 + "core-js/library/fn/set", // ie11 supports Set but not Set#values + "core-js/library/fn/string/includes", // no ie11 + "core-js/library/fn/number/is-integer", // no ie11, + "core-js/library/fn/array/from" // no ie11 +]; + +module.exports = { + 'modules': [ + ...sharedWhiteList, + 'jsencrypt', + 'crypto-js' + ], + 'src': [ + ...sharedWhiteList, + 'fun-hooks/no-eval', + 'just-clone', + 'dlv', + 'dset' + ] +}; diff --git a/browsers.json b/browsers.json index 703bf44d41d..9042d7d0627 100644 --- a/browsers.json +++ b/browsers.json @@ -1,9 +1,17 @@ { - "bs_ie_14_windows_10": { + "bs_edge_17_windows_10": { "base": "BrowserStack", "os_version": "10", "browser": "edge", - "browser_version": "14.0", + "browser_version": "17.0", + "device": null, + "os": "Windows" + }, + "bs_edge_16_windows_10": { + "base": "BrowserStack", + "os_version": "10", + "browser": "edge", + "browser_version": "16.0", "device": null, "os": "Windows" }, @@ -15,52 +23,52 @@ "device": null, "os": "Windows" }, - "bs_chrome_62_windows_10": { + "bs_chrome_74_windows_10": { "base": "BrowserStack", "os_version": "10", "browser": "chrome", - "browser_version": "62.0", + "browser_version": "74.0", "device": null, "os": "Windows" }, - "bs_chrome_61_windows_10": { + "bs_chrome_75_windows_10": { "base": "BrowserStack", "os_version": "10", "browser": "chrome", - "browser_version": "61.0", + "browser_version": "75.0", "device": null, "os": "Windows" }, - "bs_firefox_58_windows_10": { + "bs_firefox_66_windows_10": { "base": "BrowserStack", "os_version": "10", "browser": "firefox", - "browser_version": "58.0", + "browser_version": "66.0", "device": null, "os": "Windows" }, - "bs_firefox_57_windows_10": { + "bs_firefox_67_windows_10": { "base": "BrowserStack", "os_version": "10", "browser": "firefox", - "browser_version": "57.0", + "browser_version": "67.0", "device": null, "os": "Windows" }, - "bs_safari_9.1_mac_elcapitan": { + "bs_safari_11_mac_high_sierra": { "base": "BrowserStack", - "os_version": "El Capitan", + "os_version": "High Sierra", "browser": "safari", - "browser_version": "9.1", + "browser_version": "11.1", "device": null, "os": "OS X" }, - "bs_safari_8_mac_yosemite": { + "bs_safari_12_mac_mojave": { "base": "BrowserStack", - "os_version": "Yosemite", + "os_version": "Mojave", "browser": "safari", - "browser_version": "8.0", + "browser_version": "12.0", "device": null, "os": "OS X" } -} \ No newline at end of file +} diff --git a/gulpHelpers.js b/gulpHelpers.js index d4078aa3dbc..84f01b4e966 100644 --- a/gulpHelpers.js +++ b/gulpHelpers.js @@ -3,15 +3,15 @@ const fs = require('fs.extra'); const path = require('path'); const argv = require('yargs').argv; const MANIFEST = 'package.json'; -const exec = require('child_process').exec; const through = require('through2'); const _ = require('lodash'); const gutil = require('gulp-util'); +const submodules = require('./modules/.submodules.json'); const MODULE_PATH = './modules'; const BUILD_PATH = './build/dist'; const DEV_PATH = './build/dev'; - +const ANALYTICS_PATH = '../analytics'; // get only subdirectories that contain package.json with 'main' property function isModuleDirectory(filePath) { @@ -21,8 +21,7 @@ function isModuleDirectory(filePath) { const module = require(manifestPath); return module && module.main; } - } - catch (error) {} + } catch (error) {} } module.exports = { @@ -37,11 +36,13 @@ module.exports = { jsonifyHTML: function (str) { console.log(arguments); return str.replace(/\n/g, '') - .replace(/<\//g, '<\\/') - .replace(/\/>/g, '\\/>'); + .replace(/<\//g, '<\\/') + .replace(/\/>/g, '\\/>'); }, getArgModules() { - var modules = (argv.modules || '').split(',').filter(module => !!module); + var modules = (argv.modules || '') + .split(',') + .filter(module => !!module); try { if (modules.length === 1 && path.extname(modules[0]).toLowerCase() === '.json') { @@ -51,13 +52,22 @@ module.exports = { fs.readFileSync(moduleFile, 'utf8') ); } - } catch(e) { + } catch (e) { throw new gutil.PluginError({ plugin: 'modules', message: 'failed reading: ' + argv.modules }); } + Object.keys(submodules).forEach(parentModule => { + if ( + !modules.includes(parentModule) && + modules.some(module => submodules[parentModule].includes(module)) + ) { + modules.unshift(parentModule); + } + }); + return modules; }, getModules: _.memoize(function(externalModules) { @@ -66,24 +76,27 @@ module.exports = { try { var absoluteModulePath = path.join(__dirname, MODULE_PATH); internalModules = fs.readdirSync(absoluteModulePath) - .filter(file => !(/(^|\/)\.[^\/\.]/g).test(file)) + .filter(file => (/^[^\.]+(\.js)?$/).test(file)) .reduce((memo, file) => { var moduleName = file.split(new RegExp('[.\\' + path.sep + ']'))[0]; var modulePath = path.join(absoluteModulePath, file); if (fs.lstatSync(modulePath).isDirectory()) { - modulePath = path.join(modulePath, "index.js") + modulePath = path.join(modulePath, 'index.js') } memo[modulePath] = moduleName; return memo; }, {}); - } catch(err) { + } catch (err) { internalModules = {}; } return Object.assign(externalModules.reduce((memo, module) => { try { - var modulePath = require.resolve(module); + // prefer internal project modules before looking at project dependencies + var modulePath = require.resolve(module, {paths: ['./modules']}); + if (modulePath === '') modulePath = require.resolve(module); + memo[modulePath] = module; - } catch(err) { + } catch (err) { // do something } return memo; @@ -92,7 +105,7 @@ module.exports = { getBuiltModules: function(dev, externalModules) { var modules = this.getModuleNames(externalModules); - if(Array.isArray(externalModules)) { + if (Array.isArray(externalModules)) { modules = _.intersection(modules, externalModules); } return modules.map(name => path.join(__dirname, dev ? DEV_PATH : BUILD_PATH, name + '.js')); @@ -126,72 +139,33 @@ module.exports = { * Invoke with gulp --analytics * Returns an array of source files for inclusion in build process */ - getAnalyticsSources: function(directory) { - if (!argv.analytics) {return [];} // empty arrays won't affect a standard build + getAnalyticsSources: function() { + if (!argv.analytics) { return []; } // empty arrays won't affect a standard build - const directoryContents = fs.readdirSync(directory); + const directoryContents = fs.readdirSync(ANALYTICS_PATH); return directoryContents - .filter(file => isModuleDirectory(path.join(directory, file))) + .filter(file => isModuleDirectory(path.join(ANALYTICS_PATH, file))) .map(moduleDirectory => { - const module = require(path.join(directory, moduleDirectory, MANIFEST)); - return path.join(directory, moduleDirectory, module.main); + const module = require(path.join(ANALYTICS_PATH, moduleDirectory, MANIFEST)); + return path.join(ANALYTICS_PATH, moduleDirectory, module.main); }); }, - createEnd2EndTestReport : function(targetDestinationDir) { - var browsers = require('./browsers.json'); - var env = []; - var input = 'bs'; - for(var key in browsers) { - if(key.substring(0, input.length) === input && browsers[key].browser !== 'iphone') { - env.push(key); + /* + * Returns the babel options object necessary for allowing analytics packages + * to have their own configs. Gets added to prebid's webpack config with the + * flag --analytics + */ + getAnalyticsOptions: function() { + let options; + + if (argv.analytics) { + // https://babeljs.io/docs/en/options#babelrcroots + options = { + babelrcRoots: ['.', ANALYTICS_PATH], } } - //create new directory structure - fs.rmrfSync(targetDestinationDir); - env.forEach(item => { - fs.mkdirpSync(targetDestinationDir + '/' + item); - }); - - //move xml files to newly created directory - var walker = fs.walk('./build/coverage/e2e/reports'); - walker.on("file", function (root, stat, next) { - env.forEach(item => { - if(stat.name.search(item) !== -1) { - var src = root + '/' + stat.name; - var dest = targetDestinationDir + '/' + item + '/' + stat.name; - fs.copy(src, dest, {replace: true}, function(err) { - if(err) { - throw err; - } - }); - } - }); - next(); - }); - - //run junit-viewer to read xml and create html - env.forEach(item => { - //junit-viewer --results="./custom-reports/chrome51" --save="./chrome.html" - var cmd = 'junit-viewer --results="' + targetDestinationDir + '/' + item + '" --save="' + targetDestinationDir + '/' + item +'.html"'; - exec(cmd); - }); - - //create e2e-results.html - var html = 'End to End Testing Result
Note: Refresh in 2-3 seconds if it says "Cannot get ....."
'; - var li = ''; - var tabs = ''; - env.forEach(function(item,i) { - i++; - li = li + '
  • '+item+'
  • '; - tabs = tabs + '
    '; - }); - html = html + '
      ' + li + '
    ' + tabs; - html = html + '
    '; - - var filepath = targetDestinationDir + '/results.html'; - fs.openSync(filepath, 'w+'); - fs.writeFileSync(filepath, html); + return options; } }; diff --git a/gulpfile.js b/gulpfile.js index b373e6299c6..2566b52de59 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -11,7 +11,7 @@ var uglify = require('gulp-uglify'); var gulpClean = require('gulp-clean'); var KarmaServer = require('karma').Server; var karmaConfMaker = require('./karma.conf.maker'); -var opens = require('open'); +var opens = require('opn'); var webpackConfig = require('./webpack.conf'); var helpers = require('./gulpHelpers'); var concat = require('gulp-concat'); @@ -19,18 +19,18 @@ var header = require('gulp-header'); var footer = require('gulp-footer'); var replace = require('gulp-replace'); var shell = require('gulp-shell'); -var optimizejs = require('gulp-optimize-js'); var eslint = require('gulp-eslint'); var gulpif = require('gulp-if'); var sourcemaps = require('gulp-sourcemaps'); var through = require('through2'); var fs = require('fs'); var jsEscape = require('gulp-js-escape'); +const path = require('path'); +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 analyticsDirectory = '../analytics'; var port = 9999; // these modules must be explicitly listed in --modules to be included in the build, won't be part of "all" modules @@ -52,22 +52,6 @@ function clean() { .pipe(gulpClean()); } -function e2etestReport() { - var reportPort = 9010; - var targetDestinationDir = './e2etest-report'; - helpers.createEnd2EndTestReport(targetDestinationDir); - connect.server({ - port: reportPort, - root: './', - livereload: true - }); - - setTimeout(function() { - opens('http://localhost:' + reportPort + '/' + targetDestinationDir.slice(2) + '/results.html'); - }, 5000); -}; -e2etestReport.displayName = 'e2etest-report'; - // Dependant task for building postbid. It escapes postbid-config file. function escapePostbidConfig() { gulp.src('./integrationExamples/postbid/oas/postbid-config.js') @@ -76,25 +60,34 @@ function escapePostbidConfig() { }; escapePostbidConfig.displayName = 'escape-postbid-config'; -function lint() { - return gulp.src(['src/**/*.js', 'modules/**/*.js', 'test/**/*.js']) - .pipe(eslint()) +function lint(done) { + if (argv.nolint) { + return done(); + } + const isFixed = function(file) { + return file.eslint != null && file.eslint.fixed; + } + return gulp.src(['src/**/*.js', 'modules/**/*.js', 'test/**/*.js'], {base: './'}) + .pipe(gulpif(argv.nolintfix, eslint(), eslint({fix: true}))) .pipe(eslint.format('stylish')) - .pipe(eslint.failAfterError()); + .pipe(eslint.failAfterError()) + .pipe(gulpif(isFixed, gulp.dest('./'))); }; // View the code coverage report in the browser. function viewCoverage(done) { var coveragePort = 1999; + var mylocalhost = (argv.host) ? argv.host : 'localhost'; connect.server({ port: coveragePort, root: 'build/coverage/karma_html', livereload: false }); - opens('http://localhost:' + coveragePort); + opens('http://' + mylocalhost + ':' + coveragePort); done(); }; + viewCoverage.displayName = 'view-coverage'; // Watch Task with Live Reload @@ -127,36 +120,31 @@ function makeDevpackPkg() { cloned.devtool = 'source-map'; var externalModules = helpers.getArgModules(); - const analyticsSources = helpers.getAnalyticsSources(analyticsDirectory); + const analyticsSources = helpers.getAnalyticsSources(); const moduleSources = helpers.getModulePaths(externalModules); return gulp.src([].concat(moduleSources, analyticsSources, 'src/prebid.js')) .pipe(helpers.nameModules(externalModules)) .pipe(webpackStream(cloned, webpack)) - .pipe(replace('$prebid.version$', prebid.version)) .pipe(gulp.dest('build/dev')) .pipe(connect.reload()); } function makeWebpackPkg() { var cloned = _.cloneDeep(webpackConfig); - delete cloned.devtool; var externalModules = helpers.getArgModules(); - const analyticsSources = helpers.getAnalyticsSources(analyticsDirectory); + const analyticsSources = helpers.getAnalyticsSources(); const moduleSources = helpers.getModulePaths(externalModules); return gulp.src([].concat(moduleSources, analyticsSources, 'src/prebid.js')) .pipe(helpers.nameModules(externalModules)) .pipe(webpackStream(cloned, webpack)) - .pipe(replace('$prebid.version$', prebid.version)) .pipe(uglify()) .pipe(gulpif(file => file.basename === 'prebid-core.js', header(banner, { prebid: prebid }))) - .pipe(optimizejs()) - .pipe(gulp.dest('build/dist')) - .pipe(connect.reload()); + .pipe(gulp.dest('build/dist')); } function gulpBundle(dev) { @@ -181,7 +169,7 @@ function bundle(dev, moduleArr) { var allModules = helpers.getModuleNames(modules); if (modules.length === 0) { - modules = allModules.filter(module => !explicitModules.includes(module)); + modules = allModules.filter(module => explicitModules.indexOf(module) === -1); } else { var diff = _.difference(modules, allModules); if (diff.length !== 0) { @@ -217,23 +205,6 @@ function bundle(dev, moduleArr) { .pipe(gulpif(dev, sourcemaps.write('.'))); } -// Workaround for incompatibility between Karma & gulp callbacks. -// See https://github.com/karma-runner/gulp-karma/issues/18 for some related discussion. -function newKarmaCallback(done) { - return function (exitCode) { - if (exitCode) { - done(new Error('Karma tests failed with exit code ' + exitCode)); - } else { - if (argv.browserstack) { - // process.exit(0); - done(); // test this with travis (or circleci) - } else { - done(); - } - } - } -} - // Run the unit tests. // // By default, this runs in headless chrome. @@ -243,9 +214,35 @@ function newKarmaCallback(done) { // If --browserstack is given, it will run the full suite of currently supported browsers. // If --browsers is given, browsers can be chosen explicitly. e.g. --browsers=chrome,firefox,ie9 // If --notest is given, it will immediately skip the test task (useful for developing changes with `gulp serve --notest`) + function test(done) { if (argv.notest) { done(); + } else if (argv.e2e) { + let wdioCmd = path.join(__dirname, 'node_modules/.bin/wdio'); + let wdioConf = path.join(__dirname, 'wdio.conf.js'); + let wdioOpts; + + if (argv.file) { + wdioOpts = [ + wdioConf, + `--spec`, + `${argv.file}` + ] + } else { + wdioOpts = [ + wdioConf + ]; + } + execa(wdioCmd, wdioOpts, { stdio: 'inherit' }) + .then(stdout => { + done(); + process.exit(0); + }) + .catch(err => { + done(new Error(`Tests failed with error: ${err}`)); + process.exit(1); + }); } else { var karmaConf = karmaConfMaker(false, argv.browserstack, argv.watch, argv.file); @@ -258,6 +255,22 @@ function test(done) { } } +function newKarmaCallback(done) { + return function(exitCode) { + if (exitCode) { + done(new Error('Karma tests failed with exit code ' + exitCode)); + if (argv.browserstack) { + process.exit(exitCode); + } + } else { + done(); + if (argv.browserstack) { + process.exit(exitCode); + } + } + } +} + // If --file "" is given, the task will only run tests in the specified file. function testCoverage(done) { new KarmaServer(karmaConfMaker(true, false, false, argv.file), newKarmaCallback(done)).start(); @@ -270,55 +283,6 @@ function coveralls() { // 2nd arg is a dependency: 'test' must be finished .pipe(shell('cat build/coverage/lcov.info | node_modules/coveralls/bin/coveralls.js')); } -// Watch Task with Live Reload -gulp.task('watch', function () { - gulp.watch([ - 'src/**/*.js', - 'modules/**/*.js', - 'test/spec/**/*.js', - '!test/spec/loaders/**/*.js' - ], ['build-bundle-dev', 'test']); - gulp.watch([ - 'loaders/**/*.js', - 'test/spec/loaders/**/*.js' - ], ['lint']); - connect.server({ - https: argv.https, - port: port, - root: './', - livereload: true - }); -}); - -function e2eTest() { - var cmdQueue = []; - if (argv.browserstack) { - var browsers = require('./browsers.json'); - delete browsers['bs_ie_9_windows_7']; - - var cmdStr = ' --config nightwatch.conf.js'; - if (argv.group) { - cmdStr = cmdStr + ' --group ' + argv.group; - } - cmdStr = cmdStr + ' --reporter ./test/spec/e2e/custom-reporter/pbjs-html-reporter.js'; - - var startWith = 'bs'; - - Object.keys(browsers).filter(function(v) { - return v.substring(0, startWith.length) === startWith && browsers[v].browser !== 'iphone'; - }).map(function(v, i, arr) { - var newArr = (i % 2 === 0) ? arr.slice(i, i + 2) : null; - if (newArr) { - var cmd = 'nightwatch --env ' + newArr.join(',') + cmdStr; - cmdQueue.push(cmd); - } - }); - } - - return gulp.src('') - .pipe(shell(cmdQueue.join(';'))); -} - // This task creates postbid.js. Postbid setup is different from prebid.js // More info can be found here http://prebid.org/overview/what-is-post-bid.html @@ -330,6 +294,21 @@ function buildPostbid() { .pipe(gulp.dest('build/postbid/')); } +function setupE2e(done) { + if (!argv.host) { + throw new gutil.PluginError({ + plugin: 'E2E test', + message: gutil.colors.red('Host should be defined e.g. ap.localhost, anlocalhost. localhost cannot be used as safari browserstack is not able to connect to localhost') + }); + } + process.env.TEST_SERVER_HOST = argv.host; + if (argv.https) { + process.env.TEST_SERVER_PROTOCOL = argv.https; + } + argv.e2e = true; + done(); +} + // support tasks gulp.task(lint); gulp.task(watch); @@ -355,12 +334,9 @@ gulp.task('build-postbid', gulp.series(escapePostbidConfig, buildPostbid)); gulp.task('serve', gulp.series(clean, lint, gulp.parallel('build-bundle-dev', watch, test))); gulp.task('default', gulp.series(clean, makeWebpackPkg)); -gulp.task(e2etestReport); -gulp.task('e2etest', gulp.series(clean, gulp.parallel(makeDevpackPkg, makeWebpackPkg), e2eTest)); - +gulp.task('e2e-test', gulp.series(clean, setupE2e, gulp.parallel('build-bundle-prod', watch), test)) // other tasks gulp.task(bundleToStdout); gulp.task('bundle', gulpBundle.bind(null, false)); // used for just concatenating pre-built files with no build step -gulp.task('serve-nw', gulp.parallel(lint, watch, 'e2etest')); module.exports = nodeBundle; diff --git a/integrationExamples/gpt/digitrust_Full.html b/integrationExamples/gpt/digitrust_Full.html new file mode 100644 index 00000000000..7ec268a619a --- /dev/null +++ b/integrationExamples/gpt/digitrust_Full.html @@ -0,0 +1,222 @@ + + + Full DigiTrust Prebid Sample + + + + + + + + + + + + + +

    DigiTrust Prebid Full Sample

    + + +

    + This sample shows the simplest integration path for using DigiTrust ID with Prebid. + You can use DigiTrust ID without integrating the entire DigiTrust suite. +

    + +
    + +
    + +
    + + + + diff --git a/integrationExamples/gpt/digitrust_Simple.html b/integrationExamples/gpt/digitrust_Simple.html new file mode 100644 index 00000000000..c9a8c1d2ad6 --- /dev/null +++ b/integrationExamples/gpt/digitrust_Simple.html @@ -0,0 +1,220 @@ + + + Simple DigiTrust Prebid - No Framework + + + + + + + + + + + + + + +

    DigiTrust Prebid Sample - No Framework

    + +

    + This sample shows the simplest integration path for using DigiTrust ID with Prebid. + You can use DigiTrust ID without integrating the entire DigiTrust suite. +

    +
    + +
    + +
    + + diff --git a/integrationExamples/gpt/gdpr_hello_world.html b/integrationExamples/gpt/gdpr_hello_world.html index 084310b57d2..de0630178f1 100644 --- a/integrationExamples/gpt/gdpr_hello_world.html +++ b/integrationExamples/gpt/gdpr_hello_world.html @@ -1,5 +1,7 @@ + + - - - + setTimeout(function() { + sendAdserverRequest(); + console.log('timeout in main pbjs fired'); + }, FAILSAFE_TIMEOUT); + - \ No newline at end of file + diff --git a/integrationExamples/gpt/gpt_aliasingBidder.html b/integrationExamples/gpt/gpt_aliasingBidder.html deleted file mode 100644 index 693be76e82e..00000000000 --- a/integrationExamples/gpt/gpt_aliasingBidder.html +++ /dev/null @@ -1,191 +0,0 @@ - - - - - - - - - - - - - -

    Prebid.js Test

    - -
    - -
    - - -
    - -
    - - - - - - - diff --git a/integrationExamples/gpt/hello_world.html b/integrationExamples/gpt/hello_world.html index e1cdaa0dc29..337c762adc5 100644 --- a/integrationExamples/gpt/hello_world.html +++ b/integrationExamples/gpt/hello_world.html @@ -9,8 +9,11 @@ + + - - - - \ No newline at end of file + diff --git a/integrationExamples/gpt/inskin_example.html b/integrationExamples/gpt/inskin_example.html new file mode 100644 index 00000000000..197a5b1ffe1 --- /dev/null +++ b/integrationExamples/gpt/inskin_example.html @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + + +

    Prebid.js Test

    +
    Div-1
    +
    + +
    + + diff --git a/integrationExamples/gpt/load_pbjs_before_dfp_example.html b/integrationExamples/gpt/load_pbjs_before_dfp_example.html deleted file mode 100644 index cb17b8c3348..00000000000 --- a/integrationExamples/gpt/load_pbjs_before_dfp_example.html +++ /dev/null @@ -1,265 +0,0 @@ - - - - - - -

    Prebid.js Test

    -
    - -
    -
    - -
    - - diff --git a/integrationExamples/gpt/load_pbjs_dfp_concurrently.html b/integrationExamples/gpt/load_pbjs_dfp_concurrently.html deleted file mode 100644 index 0d6270ba7a5..00000000000 --- a/integrationExamples/gpt/load_pbjs_dfp_concurrently.html +++ /dev/null @@ -1,282 +0,0 @@ - - - - - - -

    Prebid.js Test

    -
    - -
    -
    - -
    - - diff --git a/integrationExamples/gpt/pbjs_example_gpt.html b/integrationExamples/gpt/pbjs_example_gpt.html deleted file mode 100644 index 536bc5a655d..00000000000 --- a/integrationExamples/gpt/pbjs_example_gpt.html +++ /dev/null @@ -1,616 +0,0 @@ - - - -Prebid.js integration example - - - - -

    Prebid.js Test

    - -
    - -
    - - -
    - -
    - - - - - - - diff --git a/integrationExamples/gpt/pbjs_innity_gpt.html b/integrationExamples/gpt/pbjs_innity_gpt.html deleted file mode 100644 index 7882d44791d..00000000000 --- a/integrationExamples/gpt/pbjs_innity_gpt.html +++ /dev/null @@ -1,94 +0,0 @@ - - - - - - - - - - - - - - - - - - - - -

    Prebid.js Test

    -
    Div-1
    -
    - -
    - - \ No newline at end of file diff --git a/integrationExamples/gpt/pbjs_partial_refresh_gpt.html b/integrationExamples/gpt/pbjs_partial_refresh_gpt.html deleted file mode 100644 index 09009a24d76..00000000000 --- a/integrationExamples/gpt/pbjs_partial_refresh_gpt.html +++ /dev/null @@ -1,296 +0,0 @@ - - - - - - - - - - - - - -

    Prebid.js Test

    - -
    Div-1, 300x250 or 300x600
    - - - -
    - -
    - - -
    Div-2, 728x90 or 970x90
    - - - - -
    - -
    - - - - - diff --git a/integrationExamples/gpt/pbjs_yieldbot_gpt.html b/integrationExamples/gpt/pbjs_yieldbot_gpt.html deleted file mode 100644 index 986eed8fc5e..00000000000 --- a/integrationExamples/gpt/pbjs_yieldbot_gpt.html +++ /dev/null @@ -1,201 +0,0 @@ - - - - - - - - -

    Prebid.js Yieldbot Adapter Basic Example

    - Use the links below to enable and disable Yieldbot test bids.
    -
    - Note: -
    - The "Enable - Yieldbot Test Bids" link below will set a cookie to force Yieldbot bid requests to return static test creative: the cookie expires in 24 hrs. -
    - -
      -
    1. Enable - Yieldbot Test Bids
    2. -
    3. Disable - Yieldbot Test Bids
    4. -
    -
    Div-0, 728x90
    - -
    - -
    -
    Div-1, 300x250 or 300x600
    - -
    - -
    -
    Div-2, 300x250 or 300x600
    - The bid for the 300x250 | 300x600 slot is shown under Div-1 above. -
      -
    • Refresh this slot after initial page view and you should see the Yieldbot test creative.
    • -
    - -
    - -
    - - diff --git a/integrationExamples/gpt/pollux_example.html b/integrationExamples/gpt/pollux_example.html deleted file mode 100644 index 56eedbf2a9c..00000000000 --- a/integrationExamples/gpt/pollux_example.html +++ /dev/null @@ -1,132 +0,0 @@ - - - - - - - - - - - - - - - - - - - test - - - -
    - -
    - -
    -
    - -
    - -
    - - \ No newline at end of file diff --git a/integrationExamples/gpt/prebidServer_example.html b/integrationExamples/gpt/prebidServer_example.html index f13c93963c6..db61a6a46d6 100644 --- a/integrationExamples/gpt/prebidServer_example.html +++ b/integrationExamples/gpt/prebidServer_example.html @@ -41,7 +41,7 @@ { bidder: 'appnexus', params: { - placementId: '13144370' + placementId: 13144370 } } ] diff --git a/integrationExamples/gpt/prebidServer_native_example.html b/integrationExamples/gpt/prebidServer_native_example.html new file mode 100644 index 00000000000..16c7d38a427 --- /dev/null +++ b/integrationExamples/gpt/prebidServer_native_example.html @@ -0,0 +1,174 @@ + + + + + + + + + + + + + + + + +

    Prebid Native

    +
    +

    No response

    + +
    + +
    +
    + +
    +

    No response

    + +
    + + + + diff --git a/integrationExamples/gpt/unruly_example.html b/integrationExamples/gpt/unruly_example.html deleted file mode 100644 index 77a9b02b3dd..00000000000 --- a/integrationExamples/gpt/unruly_example.html +++ /dev/null @@ -1,120 +0,0 @@ - - - - - - - - - - - - - - - - - - test - - - -
    - -
    - - - diff --git a/integrationExamples/gpt/userId_example.html b/integrationExamples/gpt/userId_example.html new file mode 100644 index 00000000000..6d2c2ce677a --- /dev/null +++ b/integrationExamples/gpt/userId_example.html @@ -0,0 +1,245 @@ + + + + + + + + + + + + + + + +

    Rubicon Project Prebid

    + +
    + +
    + + diff --git a/integrationExamples/gpt/x-domain/creative.html b/integrationExamples/gpt/x-domain/creative.html index 3b0058f2ee8..a6981706227 100644 --- a/integrationExamples/gpt/x-domain/creative.html +++ b/integrationExamples/gpt/x-domain/creative.html @@ -2,10 +2,11 @@ // this script can be returned by an ad server delivering a cross domain iframe, into which the // creative will be rendered, e.g. DFP delivering a SafeFrame +let windowLocation = window.location; var urlParser = document.createElement('a'); urlParser.href = '%%PATTERN:url%%'; var publisherDomain = urlParser.protocol + '//' + urlParser.hostname; -var adServerDomain = urlParser.protocol + '//tpc.googlesyndication.com'; +var adServerDomain = windowLocation.protocol + '//tpc.googlesyndication.com'; function renderAd(ev) { var key = ev.message ? 'message' : 'data'; diff --git a/integrationExamples/longform/basic_w_bidderSettings.html b/integrationExamples/longform/basic_w_bidderSettings.html new file mode 100644 index 00000000000..f9389686b1f --- /dev/null +++ b/integrationExamples/longform/basic_w_bidderSettings.html @@ -0,0 +1,145 @@ + + + + + Prebid Freewheel Integration Demo + + + + + + + + + + + + + + + + + + +

    Prebid Freewheel Test Page

    +

    requireExactDuration = false

    +
    +
    + +
    +
    +
    +
    +

    + +

    +
    +
    +
    + // bids +
    +
    +
    +
    +
    +

    + +

    +
    +
    +
    + // bids +
    +
    +
    +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/integrationExamples/longform/basic_w_custom_adserver_translation.html b/integrationExamples/longform/basic_w_custom_adserver_translation.html new file mode 100644 index 00000000000..8f4d46c3079 --- /dev/null +++ b/integrationExamples/longform/basic_w_custom_adserver_translation.html @@ -0,0 +1,132 @@ + + + + + Prebid Freewheel Integration Demo + + + + + + + + + + + + + + + + + + +

    Prebid Freewheel Integration Demo

    +

    custom adserver translation file

    +
    +
    + +
    +
    +
    +
    +

    + +

    +
    +
    +
    + // bids +
    +
    +
    +
    +
    +

    + +

    +
    +
    +
    + // bids +
    +
    +
    +
    +
    +
    +
    + + + + diff --git a/integrationExamples/longform/basic_w_priceGran.html b/integrationExamples/longform/basic_w_priceGran.html new file mode 100644 index 00000000000..fa9e1f9e84f --- /dev/null +++ b/integrationExamples/longform/basic_w_priceGran.html @@ -0,0 +1,153 @@ + + + + + Prebid Freewheel Integration Demo + + + + + + + + + + + + + + + + + + +

    Prebid Freewheel Test Page

    +

    requireExactDuration = false

    +
    +
    + +
    +
    +
    +
    +

    + +

    +
    +
    +
    + // bids +
    +
    +
    +
    +
    +

    + +

    +
    +
    +
    + // bids +
    +
    +
    +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/integrationExamples/longform/basic_w_requireExactDuration.html b/integrationExamples/longform/basic_w_requireExactDuration.html new file mode 100644 index 00000000000..5a902cf913e --- /dev/null +++ b/integrationExamples/longform/basic_w_requireExactDuration.html @@ -0,0 +1,130 @@ + + + + + Prebid Freewheel Integration Demo + + + + + + + + + + + + + + + + + + +

    Prebid Freewheel Test Page

    +

    requireExactDuration = true

    +
    +
    + +
    +
    +
    +
    +

    + +

    +
    +
    +
    + // bids +
    +
    +
    +
    +
    +

    + +

    +
    +
    +
    + // bids +
    +
    +
    +
    +
    +
    +
    + + + + diff --git a/integrationExamples/longform/basic_wo_brandCategoryExclusion.html b/integrationExamples/longform/basic_wo_brandCategoryExclusion.html new file mode 100644 index 00000000000..d60effe92f7 --- /dev/null +++ b/integrationExamples/longform/basic_wo_brandCategoryExclusion.html @@ -0,0 +1,130 @@ + + + + + Prebid Freewheel Integration Demo + + + + + + + + + + + + + + + + + + +

    Prebid Freewheel Test Page

    +

    brandCategoryExclusion = false

    +
    +
    + +
    +
    +
    +
    +

    + +

    +
    +
    +
    + // bids +
    +
    +
    +
    +
    +

    + +

    +
    +
    +
    + // bids +
    +
    +
    +
    +
    +
    +
    + + + + diff --git a/integrationExamples/longform/basic_wo_requireExactDuration.html b/integrationExamples/longform/basic_wo_requireExactDuration.html new file mode 100644 index 00000000000..b3cc0304585 --- /dev/null +++ b/integrationExamples/longform/basic_wo_requireExactDuration.html @@ -0,0 +1,131 @@ + + + + + Prebid Freewheel Integration Demo + + + + + + + + + + + + + + + + + + +

    Prebid Freewheel Test Page

    +

    requireExactDuration = false

    +
    +
    + +
    +
    +
    +
    +

    + +

    +
    +
    +
    + // bids +
    +
    +
    +
    +
    +

    + +

    +
    +
    +
    + // bids +
    +
    +
    +
    +
    +
    +
    + + + + diff --git a/integrationExamples/longform/custom_adserver_translation.json b/integrationExamples/longform/custom_adserver_translation.json new file mode 100644 index 00000000000..377fe9cdeda --- /dev/null +++ b/integrationExamples/longform/custom_adserver_translation.json @@ -0,0 +1,1180 @@ +{ + "mapping":{ + "IAB1-1":{ + "id":404, + "name":"Publishing" + }, + "IAB1-2":{ + "id":392, + "name":"Entertainment" + }, + "IAB1-5":{ + "id":419, + "name":"Filmed Entertainment" + }, + "IAB1-6":{ + "id":392, + "name":"Entertainment" + }, + "IAB1-7":{ + "id":392, + "name":"Entertainment" + }, + "IAB2-1":{ + "id":399, + "name":"Automotive" + }, + "IAB2-2":{ + "id":399, + "name":"Automotive" + }, + "IAB2-3":{ + "id":399, + "name":"Automotive" + }, + "IAB2-4":{ + "id":399, + "name":"Automotive" + }, + "IAB2-5":{ + "id":399, + "name":"Automotive" + }, + "IAB2-6":{ + "id":399, + "name":"Automotive" + }, + "IAB2-7":{ + "id":399, + "name":"Automotive" + }, + "IAB2-8":{ + "id":399, + "name":"Automotive" + }, + "IAB2-9":{ + "id":399, + "name":"Automotive" + }, + "IAB2-10":{ + "id":399, + "name":"Automotive" + }, + "IAB2-11":{ + "id":399, + "name":"Automotive" + }, + "IAB2-12":{ + "id":399, + "name":"Automotive" + }, + "IAB2-13":{ + "id":399, + "name":"Automotive" + }, + "IAB2-14":{ + "id":399, + "name":"Automotive" + }, + "IAB2-15":{ + "id":399, + "name":"Automotive" + }, + "IAB2-16":{ + "id":399, + "name":"Automotive" + }, + "IAB2-17":{ + "id":399, + "name":"Automotive" + }, + "IAB2-18":{ + "id":399, + "name":"Automotive" + }, + "IAB2-19":{ + "id":399, + "name":"Automotive" + }, + "IAB2-20":{ + "id":399, + "name":"Automotive" + }, + "IAB2-21":{ + "id":399, + "name":"Automotive" + }, + "IAB2-22":{ + "id":399, + "name":"Automotive" + }, + "IAB2-23":{ + "id":399, + "name":"Automotive" + }, + "IAB3-1":{ + "id":393, + "name":"Business Services" + }, + "IAB3-2":{ + "id":393, + "name":"Business Services" + }, + "IAB3-3":{ + "id":393, + "name":"Business Services" + }, + "IAB3-4":{ + "id":409, + "name":"Computing Product" + }, + "IAB3-5":{ + "id":393, + "name":"Business Services" + }, + "IAB3-6":{ + "id":393, + "name":"Business Services" + }, + "IAB3-7":{ + "id":398, + "name":"Government/Municipal" + }, + "IAB3-8":{ + "id":393, + "name":"Business Services" + }, + "IAB3-9":{ + "id":393, + "name":"Business Services" + }, + "IAB3-10":{ + "id":393, + "name":"Business Services" + }, + "IAB3-11":{ + "id":393, + "name":"Business Services" + }, + "IAB3-12":{ + "id":393, + "name":"Business Services" + }, + "IAB4-1":{ + "id":393, + "name":"Business Services" + }, + "IAB4-2":{ + "id":405, + "name":"Educational Services" + }, + "IAB4-3":{ + "id":405, + "name":"Educational Services" + }, + "IAB4-4":{ + "id":393, + "name":"Business Services" + }, + "IAB4-5":{ + "id":393, + "name":"Business Services" + }, + "IAB4-6":{ + "id":393, + "name":"Business Services" + }, + "IAB4-7":{ + "id":406, + "name":"Health Care Services" + }, + "IAB4-8":{ + "id":405, + "name":"Educational Services" + }, + "IAB4-9":{ + "id":417, + "name":"Telecommunications" + }, + "IAB4-10":{ + "id":429, + "name":"Military" + }, + "IAB4-11":{ + "id":393, + "name":"Business Services" + }, + "IAB5-1":{ + "id":405, + "name":"Educational Services" + }, + "IAB5-2":{ + "id":405, + "name":"Educational Services" + }, + "IAB5-3":{ + "id":405, + "name":"Educational Services" + }, + "IAB5-4":{ + "id":405, + "name":"Educational Services" + }, + "IAB5-5":{ + "id":405, + "name":"Educational Services" + }, + "IAB5-6":{ + "id":405, + "name":"Educational Services" + }, + "IAB5-7":{ + "id":405, + "name":"Educational Services" + }, + "IAB5-8":{ + "id":405, + "name":"Educational Services" + }, + "IAB5-9":{ + "id":405, + "name":"Educational Services" + }, + "IAB5-10":{ + "id":405, + "name":"Educational Services" + }, + "IAB5-11":{ + "id":405, + "name":"Educational Services" + }, + "IAB5-12":{ + "id":405, + "name":"Educational Services" + }, + "IAB5-13":{ + "id":405, + "name":"Educational Services" + }, + "IAB5-14":{ + "id":405, + "name":"Educational Services" + }, + "IAB5-15":{ + "id":405, + "name":"Educational Services" + }, + "IAB7-1":{ + "id":406, + "name":"Health Care Services" + }, + "IAB7-2":{ + "id":406, + "name":"Health Care Services" + }, + "IAB7-3":{ + "id":406, + "name":"Health Care Services" + }, + "IAB7-4":{ + "id":406, + "name":"Health Care Services" + }, + "IAB7-5":{ + "id":406, + "name":"Health Care Services" + }, + "IAB7-6":{ + "id":406, + "name":"Health Care Services" + }, + "IAB7-7":{ + "id":406, + "name":"Health Care Services" + }, + "IAB7-8":{ + "id":406, + "name":"Health Care Services" + }, + "IAB7-9":{ + "id":406, + "name":"Health Care Services" + }, + "IAB7-10":{ + "id":406, + "name":"Health Care Services" + }, + "IAB7-11":{ + "id":406, + "name":"Health Care Services" + }, + "IAB7-12":{ + "id":406, + "name":"Health Care Services" + }, + "IAB7-13":{ + "id":406, + "name":"Health Care Services" + }, + "IAB7-14":{ + "id":406, + "name":"Health Care Services" + }, + "IAB7-15":{ + "id":406, + "name":"Health Care Services" + }, + "IAB7-16":{ + "id":406, + "name":"Health Care Services" + }, + "IAB7-17":{ + "id":406, + "name":"Health Care Services" + }, + "IAB7-18":{ + "id":406, + "name":"Health Care Services" + }, + "IAB7-19":{ + "id":406, + "name":"Health Care Services" + }, + "IAB7-20":{ + "id":406, + "name":"Health Care Services" + }, + "IAB7-21":{ + "id":406, + "name":"Health Care Services" + }, + "IAB7-22":{ + "id":406, + "name":"Health Care Services" + }, + "IAB7-23":{ + "id":406, + "name":"Health Care Services" + }, + "IAB7-24":{ + "id":406, + "name":"Health Care Services" + }, + "IAB7-25":{ + "id":406, + "name":"Health Care Services" + }, + "IAB7-26":{ + "id":406, + "name":"Health Care Services" + }, + "IAB7-27":{ + "id":406, + "name":"Health Care Services" + }, + "IAB7-28":{ + "id":406, + "name":"Health Care Services" + }, + "IAB7-29":{ + "id":406, + "name":"Health Care Services" + }, + "IAB7-30":{ + "id":406, + "name":"Health Care Services" + }, + "IAB7-31":{ + "id":406, + "name":"Health Care Services" + }, + "IAB7-32":{ + "id":406, + "name":"Health Care Services" + }, + "IAB7-33":{ + "id":406, + "name":"Health Care Services" + }, + "IAB7-34":{ + "id":406, + "name":"Health Care Services" + }, + "IAB7-35":{ + "id":406, + "name":"Health Care Services" + }, + "IAB7-36":{ + "id":406, + "name":"Health Care Services" + }, + "IAB7-37":{ + "id":406, + "name":"Health Care Services" + }, + "IAB7-38":{ + "id":406, + "name":"Health Care Services" + }, + "IAB7-39":{ + "id":406, + "name":"Health Care Services" + }, + "IAB7-40":{ + "id":406, + "name":"Health Care Services" + }, + "IAB7-41":{ + "id":406, + "name":"Health Care Services" + }, + "IAB7-42":{ + "id":406, + "name":"Health Care Services" + }, + "IAB7-43":{ + "id":406, + "name":"Health Care Services" + }, + "IAB7-44":{ + "id":406, + "name":"Health Care Services" + }, + "IAB7-45":{ + "id":406, + "name":"Health Care Services" + }, + "IAB8-1":{ + "id":394, + "name":"Food" + }, + "IAB8-2":{ + "id":394, + "name":"Food" + }, + "IAB8-3":{ + "id":394, + "name":"Food" + }, + "IAB8-4":{ + "id":394, + "name":"Food" + }, + "IAB8-5":{ + "id":400, + "name":"Beer/Wine/Liquor" + }, + "IAB8-6":{ + "id":401, + "name":"Beverages" + }, + "IAB8-7":{ + "id":394, + "name":"Food" + }, + "IAB8-8":{ + "id":394, + "name":"Food" + }, + "IAB8-9":{ + "id":407, + "name":"Restaurant/Fast Food" + }, + "IAB8-10":{ + "id":394, + "name":"Food" + }, + "IAB8-11":{ + "id":394, + "name":"Food" + }, + "IAB8-12":{ + "id":394, + "name":"Food" + }, + "IAB8-13":{ + "id":394, + "name":"Food" + }, + "IAB8-14":{ + "id":394, + "name":"Food" + }, + "IAB8-15":{ + "id":394, + "name":"Food" + }, + "IAB8-16":{ + "id":394, + "name":"Food" + }, + "IAB8-17":{ + "id":394, + "name":"Food" + }, + "IAB8-18":{ + "id":400, + "name":"Beer/Wine/Liquor" + }, + "IAB9-1":{ + "id":392, + "name":"Entertainment" + }, + "IAB9-3":{ + "id":418, + "name":"Jewelry" + }, + "IAB9-5":{ + "id":413, + "name":"Gaming" + }, + "IAB9-6":{ + "id":412, + "name":"Household Products" + }, + "IAB9-9":{ + "id":426, + "name":"Tobacco" + }, + "IAB9-11":{ + "id":404, + "name":"Publishing" + }, + "IAB9-15":{ + "id":404, + "name":"Publishing" + }, + "IAB9-16":{ + "id":392, + "name":"Entertainment" + }, + "IAB9-18":{ + "id":393, + "name":"Business Services" + }, + "IAB9-19":{ + "id":418, + "name":"Jewelry" + }, + "IAB9-23":{ + "id":424, + "name":"Photographic Equipment" + }, + "IAB9-24":{ + "id":392, + "name":"Entertainment" + }, + "IAB9-25":{ + "id":392, + "name":"Entertainment" + }, + "IAB9-30":{ + "id":392, + "name":"Entertainment" + }, + "IAB10-1":{ + "id":415, + "name":"Appliances" + }, + "IAB10-5":{ + "id":434, + "name":"Home Furnishings" + }, + "IAB10-6":{ + "id":434, + "name":"Home Furnishings" + }, + "IAB10-7":{ + "id":434, + "name":"Home Furnishings" + }, + "IAB10-8":{ + "id":393, + "name":"Business Services" + }, + "IAB10-9":{ + "id":434, + "name":"Home Furnishings" + }, + "IAB11-1":{ + "id":398, + "name":"Government/Municipal" + }, + "IAB11-2":{ + "id":398, + "name":"Government/Municipal" + }, + "IAB11-3":{ + "id":398, + "name":"Government/Municipal" + }, + "IAB11-4":{ + "id":398, + "name":"Government/Municipal" + }, + "IAB11-5":{ + "id":398, + "name":"Government/Municipal" + }, + "IAB12-1":{ + "id":438, + "name":"News" + }, + "IAB12-2":{ + "id":438, + "name":"News" + }, + "IAB12-3":{ + "id":438, + "name":"News" + }, + "IAB13-1":{ + "id":393, + "name":"Business Services" + }, + "IAB13-2":{ + "id":393, + "name":"Business Services" + }, + "IAB13-3":{ + "id":438, + "name":"News" + }, + "IAB13-4":{ + "id":391, + "name":"Financial Services" + }, + "IAB13-5":{ + "id":393, + "name":"Business Services" + }, + "IAB13-6":{ + "id":436, + "name":"Insurance" + }, + "IAB13-7":{ + "id":393, + "name":"Business Services" + }, + "IAB13-8":{ + "id":393, + "name":"Business Services" + }, + "IAB13-9":{ + "id":393, + "name":"Business Services" + }, + "IAB13-10":{ + "id":393, + "name":"Business Services" + }, + "IAB13-11":{ + "id":393, + "name":"Business Services" + }, + "IAB13-12":{ + "id":393, + "name":"Business Services" + }, + "IAB16-1":{ + "id":423, + "name":"Pet Food/Supplies" + }, + "IAB16-2":{ + "id":423, + "name":"Pet Food/Supplies" + }, + "IAB16-3":{ + "id":423, + "name":"Pet Food/Supplies" + }, + "IAB16-4":{ + "id":423, + "name":"Pet Food/Supplies" + }, + "IAB16-5":{ + "id":423, + "name":"Pet Food/Supplies" + }, + "IAB16-6":{ + "id":423, + "name":"Pet Food/Supplies" + }, + "IAB16-7":{ + "id":423, + "name":"Pet Food/Supplies" + }, + "IAB17-1":{ + "id":425, + "name":"Professional Sports" + }, + "IAB17-2":{ + "id":425, + "name":"Professional Sports" + }, + "IAB17-3":{ + "id":425, + "name":"Professional Sports" + }, + "IAB17-4":{ + "id":425, + "name":"Professional Sports" + }, + "IAB17-5":{ + "id":425, + "name":"Professional Sports" + }, + "IAB17-6":{ + "id":425, + "name":"Professional Sports" + }, + "IAB17-7":{ + "id":425, + "name":"Professional Sports" + }, + "IAB17-8":{ + "id":425, + "name":"Professional Sports" + }, + "IAB17-9":{ + "id":425, + "name":"Professional Sports" + }, + "IAB17-10":{ + "id":425, + "name":"Professional Sports" + }, + "IAB17-11":{ + "id":425, + "name":"Professional Sports" + }, + "IAB17-12":{ + "id":425, + "name":"Professional Sports" + }, + "IAB17-13":{ + "id":425, + "name":"Professional Sports" + }, + "IAB17-14":{ + "id":425, + "name":"Professional Sports" + }, + "IAB17-15":{ + "id":425, + "name":"Professional Sports" + }, + "IAB17-16":{ + "id":425, + "name":"Professional Sports" + }, + "IAB17-17":{ + "id":425, + "name":"Professional Sports" + }, + "IAB17-18":{ + "id":425, + "name":"Professional Sports" + }, + "IAB17-19":{ + "id":425, + "name":"Professional Sports" + }, + "IAB17-20":{ + "id":425, + "name":"Professional Sports" + }, + "IAB17-21":{ + "id":425, + "name":"Professional Sports" + }, + "IAB17-22":{ + "id":425, + "name":"Professional Sports" + }, + "IAB17-23":{ + "id":425, + "name":"Professional Sports" + }, + "IAB17-24":{ + "id":425, + "name":"Professional Sports" + }, + "IAB17-25":{ + "id":425, + "name":"Professional Sports" + }, + "IAB17-26":{ + "id":425, + "name":"Professional Sports" + }, + "IAB17-27":{ + "id":425, + "name":"Professional Sports" + }, + "IAB17-28":{ + "id":425, + "name":"Professional Sports" + }, + "IAB17-29":{ + "id":425, + "name":"Professional Sports" + }, + "IAB17-30":{ + "id":425, + "name":"Professional Sports" + }, + "IAB17-31":{ + "id":425, + "name":"Professional Sports" + }, + "IAB17-32":{ + "id":425, + "name":"Professional Sports" + }, + "IAB17-33":{ + "id":425, + "name":"Professional Sports" + }, + "IAB17-34":{ + "id":425, + "name":"Professional Sports" + }, + "IAB17-35":{ + "id":425, + "name":"Professional Sports" + }, + "IAB17-36":{ + "id":425, + "name":"Professional Sports" + }, + "IAB17-37":{ + "id":425, + "name":"Professional Sports" + }, + "IAB17-38":{ + "id":425, + "name":"Professional Sports" + }, + "IAB17-39":{ + "id":425, + "name":"Professional Sports" + }, + "IAB17-40":{ + "id":425, + "name":"Professional Sports" + }, + "IAB17-41":{ + "id":425, + "name":"Professional Sports" + }, + "IAB17-42":{ + "id":425, + "name":"Professional Sports" + }, + "IAB17-43":{ + "id":425, + "name":"Professional Sports" + }, + "IAB17-44":{ + "id":425, + "name":"Professional Sports" + }, + "IAB18-1":{ + "id":411, + "name":"Cosmetics/Toiletries" + }, + "IAB18-2":{ + "id":397, + "name":"Apparel" + }, + "IAB18-3":{ + "id":397, + "name":"Apparel" + }, + "IAB18-4":{ + "id":418, + "name":"Jewelry" + }, + "IAB18-5":{ + "id":397, + "name":"Apparel" + }, + "IAB18-6":{ + "id":397, + "name":"Apparel" + }, + "IAB19-2":{ + "id":409, + "name":"Computing Product" + }, + "IAB19-3":{ + "id":409, + "name":"Computing Product" + }, + "IAB19-4":{ + "id":409, + "name":"Computing Product" + }, + "IAB19-5":{ + "id":424, + "name":"Photographic Equipment" + }, + "IAB19-6":{ + "id":417, + "name":"Telecommunications" + }, + "IAB19-7":{ + "id":409, + "name":"Computing Product" + }, + "IAB19-8":{ + "id":409, + "name":"Computing Product" + }, + "IAB19-9":{ + "id":409, + "name":"Computing Product" + }, + "IAB19-10":{ + "id":409, + "name":"Computing Product" + }, + "IAB19-11":{ + "id":409, + "name":"Computing Product" + }, + "IAB19-12":{ + "id":409, + "name":"Computing Product" + }, + "IAB19-13":{ + "id":404, + "name":"Publishing" + }, + "IAB19-14":{ + "id":409, + "name":"Computing Product" + }, + "IAB19-15":{ + "id":409, + "name":"Computing Product" + }, + "IAB19-16":{ + "id":409, + "name":"Computing Product" + }, + "IAB19-17":{ + "id":419, + "name":"Filmed Entertainment" + }, + "IAB19-18":{ + "id":409, + "name":"Computing Product" + }, + "IAB19-19":{ + "id":409, + "name":"Computing Product" + }, + "IAB19-20":{ + "id":409, + "name":"Computing Product" + }, + "IAB19-21":{ + "id":409, + "name":"Computing Product" + }, + "IAB19-22":{ + "id":409, + "name":"Computing Product" + }, + "IAB19-23":{ + "id":409, + "name":"Computing Product" + }, + "IAB19-24":{ + "id":409, + "name":"Computing Product" + }, + "IAB19-25":{ + "id":409, + "name":"Computing Product" + }, + "IAB19-26":{ + "id":409, + "name":"Computing Product" + }, + "IAB19-27":{ + "id":409, + "name":"Computing Product" + }, + "IAB19-28":{ + "id":409, + "name":"Computing Product" + }, + "IAB19-29":{ + "id":392, + "name":"Entertainment" + }, + "IAB19-30":{ + "id":409, + "name":"Computing Product" + }, + "IAB19-31":{ + "id":409, + "name":"Computing Product" + }, + "IAB19-32":{ + "id":409, + "name":"Computing Product" + }, + "IAB19-33":{ + "id":409, + "name":"Computing Product" + }, + "IAB19-34":{ + "id":409, + "name":"Computing Product" + }, + "IAB19-35":{ + "id":409, + "name":"Computing Product" + }, + "IAB19-36":{ + "id":409, + "name":"Computing Product" + }, + "IAB20-1":{ + "id":395, + "name":"Travel/Hotels/Airlines" + }, + "IAB20-2":{ + "id":395, + "name":"Travel/Hotels/Airlines" + }, + "IAB20-3":{ + "id":395, + "name":"Travel/Hotels/Airlines" + }, + "IAB20-4":{ + "id":395, + "name":"Travel/Hotels/Airlines" + }, + "IAB20-5":{ + "id":395, + "name":"Travel/Hotels/Airlines" + }, + "IAB20-6":{ + "id":395, + "name":"Travel/Hotels/Airlines" + }, + "IAB20-7":{ + "id":395, + "name":"Travel/Hotels/Airlines" + }, + "IAB20-8":{ + "id":395, + "name":"Travel/Hotels/Airlines" + }, + "IAB20-9":{ + "id":395, + "name":"Travel/Hotels/Airlines" + }, + "IAB20-10":{ + "id":395, + "name":"Travel/Hotels/Airlines" + }, + "IAB20-11":{ + "id":395, + "name":"Travel/Hotels/Airlines" + }, + "IAB20-12":{ + "id":395, + "name":"Travel/Hotels/Airlines" + }, + "IAB20-13":{ + "id":395, + "name":"Travel/Hotels/Airlines" + }, + "IAB20-14":{ + "id":395, + "name":"Travel/Hotels/Airlines" + }, + "IAB20-15":{ + "id":395, + "name":"Travel/Hotels/Airlines" + }, + "IAB20-16":{ + "id":395, + "name":"Travel/Hotels/Airlines" + }, + "IAB20-17":{ + "id":395, + "name":"Travel/Hotels/Airlines" + }, + "IAB20-18":{ + "id":395, + "name":"Travel/Hotels/Airlines" + }, + "IAB20-19":{ + "id":395, + "name":"Travel/Hotels/Airlines" + }, + "IAB20-20":{ + "id":395, + "name":"Travel/Hotels/Airlines" + }, + "IAB20-21":{ + "id":395, + "name":"Travel/Hotels/Airlines" + }, + "IAB20-22":{ + "id":395, + "name":"Travel/Hotels/Airlines" + }, + "IAB20-23":{ + "id":395, + "name":"Travel/Hotels/Airlines" + }, + "IAB20-24":{ + "id":395, + "name":"Travel/Hotels/Airlines" + }, + "IAB20-25":{ + "id":395, + "name":"Travel/Hotels/Airlines" + }, + "IAB20-26":{ + "id":395, + "name":"Travel/Hotels/Airlines" + }, + "IAB20-27":{ + "id":395, + "name":"Travel/Hotels/Airlines" + }, + "IAB21-1":{ + "id":416, + "name":"Real Estate" + }, + "IAB21-2":{ + "id":416, + "name":"Real Estate" + }, + "IAB21-3":{ + "id":416, + "name":"Real Estate" + }, + "IAB22-1":{ + "id":403, + "name":"Retail Stores/Chains" + }, + "IAB22-2":{ + "id":403, + "name":"Retail Stores/Chains" + }, + "IAB22-3":{ + "id":403, + "name":"Retail Stores/Chains" + } + } +} \ No newline at end of file diff --git a/integrationExamples/longform/longformTestUtils.js b/integrationExamples/longform/longformTestUtils.js new file mode 100644 index 00000000000..096be91bb77 --- /dev/null +++ b/integrationExamples/longform/longformTestUtils.js @@ -0,0 +1,66 @@ +var prebidTestUtils = prebidTestUtils || {}; + +function getIndustry(id) { + var mapping = window.localStorage.getItem('iabToFwMappingkey'); + if (mapping) { + try { + mapping = JSON.parse(mapping); + } catch (error) { + // + } + var industry; + mapping = mapping['mapping']; + for (var v in mapping) { + if (mapping[v]['id'] == id) { + industry = mapping[v]['name']; + break; + } + } + return industry; + } +} + +prebidTestUtils.loadKv = function (targetingArr) { + var div = document.getElementById('collapseThree').children[0]; + var html = ''; + Object.keys(targetingArr).forEach(function(adUnitCode) { + targetingArr[adUnitCode].forEach(function (targeting) { + Object.keys(targeting).forEach(function (key) { + html += '' + }); + }); + }); + html += '
    ' + key + '' + targeting[key] + '
    '; + div.innerHTML = html; +} + +prebidTestUtils.loadBids = function (targetingArr, brandCatExclusion) { + var div = document.getElementById('collapseTwo').children[0]; + var html = ''; + var index = 1; + Object.keys(targetingArr).forEach(function(adUnitCode) { + targetingArr[adUnitCode].forEach(function (targeting) { + Object.keys(targeting).forEach(function (key) { + if (key !== 'hb_cache_id') { + var result = targeting[key].split('_'); + html += ''; + html += ''; + html += ''; + if (brandCatExclusion) { + html += ''; + html += ''; + } else { + html += ''; + html += ''; + } + html += ''; + html += ''; + html += ''; + index++; + } + }); + }); + }); + html += '
    #CPMIndustryDurationStatusComm Break #
    ' + index + '' + result[0] + '' + getIndustry(result[1]) + '' + result[2] + '' + result[1] + '
    '; + div.innerHTML = html; +} diff --git a/integrationExamples/longform/longform_testpages_style.css b/integrationExamples/longform/longform_testpages_style.css new file mode 100644 index 00000000000..fe8105edcfa --- /dev/null +++ b/integrationExamples/longform/longform_testpages_style.css @@ -0,0 +1,34 @@ +#videoPlayer { + height: 480px; + width: 100%; + background-color: #000; +} + +.outer { + position: relative; +} + +#skip { + position: absolute; + right: 20px; + bottom: 20px; + display: none; + color: white; + background-color: black; + border: 1px solid gray; +} + +#adCount { + color: white; + position: absolute; + left: 20px; + bottom: 20px; + display: none; +} + +#showad { + color: white; + position: absolute; + left: 20px; + top: 20px; +} \ No newline at end of file diff --git a/karma.conf.maker.js b/karma.conf.maker.js index 7faf91f0847..9aa416375b5 100644 --- a/karma.conf.maker.js +++ b/karma.conf.maker.js @@ -20,7 +20,10 @@ function newWebpackConfig(codeCoverage) { webpackConfig.module.rules.push({ enforce: 'post', exclude: /(node_modules)|(test)|(integrationExamples)|(build)|polyfill.js|(src\/adapters\/analytics\/ga.js)/, - loader: 'istanbul-instrumenter-loader', + use: { + loader: 'istanbul-instrumenter-loader', + options: { esModules: true } + }, test: /\.js$/ }) } @@ -34,7 +37,6 @@ function newPluginsArray(browserstack) { 'karma-es5-shim', 'karma-mocha', 'karma-chai', - 'karma-requirejs', 'karma-sinon', 'karma-sourcemap-loader', 'karma-spec-reporter', @@ -83,7 +85,8 @@ function setBrowsers(karmaConf, browserstack) { if (browserstack) { karmaConf.browserStack = { username: process.env.BROWSERSTACK_USERNAME, - accessKey: process.env.BROWSERSTACK_ACCESS_KEY + accessKey: process.env.BROWSERSTACK_ACCESS_KEY, + build: 'Prebidjs Unit Tests ' + new Date().toLocaleString() } if (process.env.TRAVIS) { karmaConf.browserStack.startTunnel = false; @@ -125,6 +128,7 @@ module.exports = function(codeCoverage, browserstack, watchMode, file) { webpack: webpackConfig, webpackMiddleware: { + stats: 'errors-only', noInfo: true }, // frameworks to use diff --git a/modules.json b/modules.json new file mode 100644 index 00000000000..266ec93ee2a --- /dev/null +++ b/modules.json @@ -0,0 +1,26 @@ +[ + "33acrossBidAdapter", + "appnexusBidAdapter", + "appnexusClientBidAdapter", + "eplanningBidAdapter", + "rubiconBidAdapter", + "rabiconBidAdapter", + "ixBidAdapter", + "pulsepointBidAdapter", + "sovrnBidAdapter", + "openxBidAdapter", + "openxClientBidAdapter", + "pubmaticBidAdapter", + "pubmaticClientBidAdapter", + "smartadserverBidAdapter", + "prebidServerBidAdapter", + "consentManagement", + "criteoBidAdapter", + "audienceNetworkBidAdapter", + "teadsBidAdapter", + "improvedigitalBidAdapter", + "improvedigitalClientBidAdapter", + "currency", + "schain", + "marfeelAnalyticsAdapter" +] diff --git a/modules/.submodules.json b/modules/.submodules.json new file mode 100644 index 00000000000..09063deea40 --- /dev/null +++ b/modules/.submodules.json @@ -0,0 +1,13 @@ +{ + "userId": [ + "digiTrustIdSystem", + "id5IdSystem", + "criteortusIdSystem", + "parrableIdSystem", + "liveIntentIdSystem" + ], + "adpod": [ + "freeWheelAdserverVideo", + "dfpAdServerVideo" + ] +} diff --git a/modules/33acrossBidAdapter.js b/modules/33acrossBidAdapter.js index 6b41c652152..dc075162141 100644 --- a/modules/33acrossBidAdapter.js +++ b/modules/33acrossBidAdapter.js @@ -1,12 +1,15 @@ -import { uniques } from 'src/utils'; -const { registerBidder } = require('../src/adapters/bidderFactory'); -const { config } = require('../src/config'); +import { registerBidder } from '../src/adapters/bidderFactory'; +import { config } from '../src/config'; +import * as utils from '../src/utils'; + const BIDDER_CODE = '33across'; const END_POINT = 'https://ssc.33across.com/api/v1/hb'; const SYNC_ENDPOINT = 'https://de.tynt.com/deb/v2?m=xch&rt=html'; const adapterState = {}; +const NON_MEASURABLE = 'nm'; + // All this assumes that only one bid is ever returned by ttx function _createBidResponse(response) { return { @@ -23,11 +26,56 @@ function _createBidResponse(response) { } } +function _isViewabilityMeasurable(element) { + return !_isIframe() && element !== null; +} + +function _getViewability(element, topWin, { w, h } = {}) { + return topWin.document.visibilityState === 'visible' + ? _getPercentInView(element, topWin, { w, h }) + : 0; +} + +function _mapAdUnitPathToElementId(adUnitCode) { + if (utils.isGptPubadsDefined()) { + const adSlots = googletag.pubads().getSlots(); + const isMatchingAdSlot = utils.isSlotMatchingAdUnitCode(adUnitCode); + + for (let i = 0; i < adSlots.length; i++) { + if (isMatchingAdSlot(adSlots[i])) { + const id = adSlots[i].getSlotElementId(); + + utils.logInfo(`[33Across Adapter] Map ad unit path to HTML element id: '${adUnitCode}' -> ${id}`); + + return id; + } + } + } + + utils.logWarn(`[33Across Adapter] Unable to locate element for ad unit code: '${adUnitCode}'`); + + return null; +} + +function _getAdSlotHTMLElement(adUnitCode) { + return document.getElementById(adUnitCode) || + document.getElementById(_mapAdUnitPathToElementId(adUnitCode)); +} + // Infer the necessary data from valid bid for a minimal ttxRequest and create HTTP request // NOTE: At this point, TTX only accepts request for a single impression -function _createServerRequest(bidRequest, gdprConsent) { +function _createServerRequest(bidRequest, gdprConsent = {}) { const ttxRequest = {}; const params = bidRequest.params; + const element = _getAdSlotHTMLElement(bidRequest.adUnitCode); + const sizes = _transformSizes(bidRequest.sizes); + const minSize = _getMinSize(sizes); + + const viewabilityAmount = _isViewabilityMeasurable(element) + ? _getViewability(element, utils.getWindowTop(), minSize) + : NON_MEASURABLE; + + const contributeViewability = ViewabilityContributor(viewabilityAmount); /* * Infer data for the request payload @@ -35,14 +83,14 @@ function _createServerRequest(bidRequest, gdprConsent) { ttxRequest.imp = []; ttxRequest.imp[0] = { banner: { - format: bidRequest.sizes.map(_getFormatSize) + format: sizes.map(size => Object.assign(size, {ext: {}})) }, ext: { ttx: { prod: params.productId } } - } + }; ttxRequest.site = { id: params.siteId }; // Go ahead send the bidId in request to 33exchange so it's kept track of in the bid response and @@ -54,12 +102,21 @@ function _createServerRequest(bidRequest, gdprConsent) { ext: { consent: gdprConsent.consentString } - } + }; ttxRequest.regs = { ext: { gdpr: (gdprConsent.gdprApplies === true) ? 1 : 0 } - } + }; + ttxRequest.ext = { + ttx: { + prebidStartedAt: Date.now(), + caller: [{ + 'name': 'prebidjs', + 'version': '$prebid.version$' + }] + } + }; // Finally, set the openRTB 'test' param if this is to be a test bid if (params.test === 1) { @@ -81,27 +138,142 @@ function _createServerRequest(bidRequest, gdprConsent) { return { 'method': 'POST', 'url': url, - 'data': JSON.stringify(ttxRequest), + 'data': JSON.stringify(contributeViewability(ttxRequest)), 'options': options } } // Sync object will always be of type iframe for TTX -function _createSync(siteId) { +function _createSync({siteId, gdprConsent = {}}) { const ttxSettings = config.getConfig('ttxSettings'); const syncUrl = (ttxSettings && ttxSettings.syncUrl) || SYNC_ENDPOINT; - return { + const {consentString, gdprApplies} = gdprConsent; + + const sync = { type: 'iframe', - url: `${syncUrl}&id=${siteId}` + url: `${syncUrl}&id=${siteId}&gdpr_consent=${encodeURIComponent(consentString)}` + }; + + if (typeof gdprApplies === 'boolean') { + sync.url += `&gdpr=${Number(gdprApplies)}`; } + + return sync; } -function _getFormatSize(sizeArr) { +function _getSize(size) { return { - w: sizeArr[0], - h: sizeArr[1], - ext: {} + w: parseInt(size[0], 10), + h: parseInt(size[1], 10) + } +} + +function _getMinSize(sizes) { + return sizes.reduce((min, size) => size.h * size.w < min.h * min.w ? size : min); +} + +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 _transformSizes(sizes) { + if (utils.isArray(sizes) && sizes.length === 2 && !utils.isArray(sizes[0])) { + return [_getSize(sizes)]; + } + + return sizes.map(_getSize); +} + +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; +} + +function _getPercentInView(element, topWin, { w, h } = {}) { + const elementBoundingBox = _getBoundingBox(element, { w, h }); + + // Obtain the intersection of the element and the viewport + 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); + } + + // No overlap between element and the viewport; therefore, the element + // lies completely out of view + return 0; +} + +/** + * Viewability contribution to request.. + */ +function ViewabilityContributor(viewabilityAmount) { + function contributeViewability(ttxRequest) { + const req = Object.assign({}, ttxRequest); + const imp = req.imp = req.imp.map(impItem => Object.assign({}, impItem)); + const banner = imp[0].banner = Object.assign({}, imp[0].banner); + const ext = banner.ext = Object.assign({}, banner.ext); + const ttx = ext.ttx = Object.assign({}, ext.ttx); + + ttx.viewability = { amount: isNaN(viewabilityAmount) ? viewabilityAmount : Math.round(viewabilityAmount) }; + + return req; + } + + return contributeViewability; +} + +function _isIframe() { + try { + return utils.getWindowSelf() !== utils.getWindowTop(); + } catch (e) { + return true; } } @@ -119,16 +291,15 @@ function isBidRequestValid(bid) { // NOTE: With regards to gdrp consent data, // - the server independently infers gdpr applicability therefore, setting the default value to false -// - the server, at this point, also doesn't need the consent string to handle gdpr compliance. So passing -// value whether set or not, for the sake of future dev. function buildRequests(bidRequests, bidderRequest) { - const gdprConsent = Object.assign({ consentString: undefined, gdprApplies: false }, bidderRequest && bidderRequest.gdprConsent) + const gdprConsent = Object.assign({ + consentString: undefined, + gdprApplies: false + }, bidderRequest && bidderRequest.gdprConsent); - adapterState.uniqueSiteIds = bidRequests.map(req => req.params.siteId).filter(uniques); + adapterState.uniqueSiteIds = bidRequests.map(req => req.params.siteId).filter(utils.uniques); - return bidRequests.map((req) => { - return _createServerRequest(req, gdprConsent); - }); + return bidRequests.map(req => _createServerRequest(req, gdprConsent)); } // NOTE: At this point, the response from 33exchange will only ever contain one bid i.e. the highest bid @@ -143,24 +314,23 @@ function interpretResponse(serverResponse, bidRequest) { return bidResponses; } -// Register one sync per unique guid -// NOTE: If gdpr applies do not sync +// Register one sync per unique guid so long as iframe is enable +// Else no syncs +// For logic on how we handle gdpr data see _createSyncs and module's unit tests +// '33acrossBidAdapter#getUserSyncs' function getUserSyncs(syncOptions, responses, gdprConsent) { - if (gdprConsent && gdprConsent.gdprApplies === true) { - return [] - } else { - return (syncOptions.iframeEnabled) ? adapterState.uniqueSiteIds.map(_createSync) : ([]); - } + return (syncOptions.iframeEnabled) ? adapterState.uniqueSiteIds.map((siteId) => _createSync({gdprConsent, siteId})) : ([]); } -const spec = { +export const spec = { + NON_MEASURABLE, + code: BIDDER_CODE, + isBidRequestValid, buildRequests, interpretResponse, - getUserSyncs -} + getUserSyncs, +}; registerBidder(spec); - -module.exports = spec; diff --git a/modules/33acrossBidAdapter.md b/modules/33acrossBidAdapter.md index bdb2b944861..c313f3b6e0b 100644 --- a/modules/33acrossBidAdapter.md +++ b/modules/33acrossBidAdapter.md @@ -16,7 +16,7 @@ Connects to 33Across's exchange for bids. ``` var adUnits = [ { - code: '33across-hb-ad-123456-1', + code: '33across-hb-ad-123456-1', // ad slot HTML element ID sizes: [ [300, 250], [728, 90] @@ -24,7 +24,7 @@ var adUnits = [ bids: [{ bidder: '33across', params: { - siteId: 'examplePub1234', + siteId: 'cxBE0qjUir6iopaKkGJozW', productId: 'siab' } }] diff --git a/modules/a4gBidAdapter.js b/modules/a4gBidAdapter.js index d6c0fa0b303..d66630ce4db 100644 --- a/modules/a4gBidAdapter.js +++ b/modules/a4gBidAdapter.js @@ -1,5 +1,5 @@ -import {registerBidder} from 'src/adapters/bidderFactory'; -import * as utils from 'src/utils'; +import {registerBidder} from '../src/adapters/bidderFactory'; +import * as utils from '../src/utils'; const A4G_BIDDER_CODE = 'a4g'; const A4G_CURRENCY = 'USD'; diff --git a/modules/aardvarkBidAdapter.js b/modules/aardvarkBidAdapter.js index 6a4c8b99572..9caaaaa747c 100644 --- a/modules/aardvarkBidAdapter.js +++ b/modules/aardvarkBidAdapter.js @@ -1,5 +1,5 @@ -import * as utils from 'src/utils'; -import {registerBidder} from 'src/adapters/bidderFactory'; +import * as utils from '../src/utils'; +import {registerBidder} from '../src/adapters/bidderFactory'; const BIDDER_CODE = 'aardvark'; const DEFAULT_ENDPOINT = 'bidder.rtk.io'; @@ -15,6 +15,7 @@ export function resetUserSync() { export const spec = { code: BIDDER_CODE, + aliases: ['adsparc', 'safereach'], isBidRequestValid: function(bid) { return ((typeof bid.params.ai === 'string') && !!bid.params.ai.length && @@ -25,11 +26,24 @@ export const spec = { var auctionCodes = []; var requests = []; var requestsMap = {}; - var referer = utils.getTopWindowUrl(); + var referer = bidderRequest.refererInfo.referer; var pageCategories = []; + var tdId = ''; + var width = window.innerWidth; + var height = window.innerHeight; + + // This reference to window.top can cause issues when loaded in an iframe if not protected with a try/catch. + try { + var topWin = utils.getWindowTop(); + if (topWin.rtkcategories && Array.isArray(topWin.rtkcategories)) { + pageCategories = topWin.rtkcategories; + } + width = topWin.innerWidth; + height = topWin.innerHeight; + } catch (e) {} - if (window.top.rtkcategories && Array.isArray(window.top.rtkcategories)) { - pageCategories = window.top.rtkcategories; + if (utils.isStr(utils.deepAccess(validBidRequests, '0.userId.tdid'))) { + tdId = validBidRequests[0].userId.tdid; } utils._each(validBidRequests, function(b) { @@ -40,11 +54,17 @@ export const spec = { payload: { version: 1, jsonp: false, - rtkreferer: referer + rtkreferer: referer, + w: width, + h: height }, endpoint: DEFAULT_ENDPOINT }; + if (tdId) { + rMap.payload.tdid = tdId; + } + if (pageCategories && pageCategories.length) { rMap.payload.categories = pageCategories.slice(0); } @@ -121,6 +141,10 @@ export const spec = { bidResponse.dealId = rawBid.dealId } + if (rawBid.hasOwnProperty('ex')) { + bidResponse.ex = rawBid.ex; + } + switch (rawBid.media) { case 'banner': bidResponse.ad = rawBid.adm + utils.createTrackPixelHtml(decodeURIComponent(rawBid.nurl)); diff --git a/modules/ablidaBidAdapter.js b/modules/ablidaBidAdapter.js new file mode 100644 index 00000000000..33642a1ac9e --- /dev/null +++ b/modules/ablidaBidAdapter.js @@ -0,0 +1,93 @@ +import * as utils from '../src/utils'; +import {config} from '../src/config'; +import {registerBidder} from '../src/adapters/bidderFactory'; + +const BIDDER_CODE = 'ablida'; +const ENDPOINT_URL = 'https://bidder.ablida.net/prebid'; + +export const spec = { + code: BIDDER_CODE, + + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function (bid) { + return !!(bid.params.placementId); + }, + + /** + * Make a server request from the list of BidRequests. + * + * @return Array Info describing the request to the server. + * @param validBidRequests + * @param bidderRequest + */ + buildRequests: function (validBidRequests, bidderRequest) { + if (validBidRequests.length === 0) { + return []; + } + return validBidRequests.map(bidRequest => { + const sizes = utils.parseSizesInput(bidRequest.sizes)[0]; + const size = sizes.split('x'); + const jaySupported = 'atob' in window && 'currentScript' in document; + const device = getDevice(); + const payload = { + placementId: bidRequest.params.placementId, + width: size[0], + height: size[1], + bidId: bidRequest.bidId, + categories: bidRequest.params.categories, + referer: bidderRequest.refererInfo.referer, + jaySupported: jaySupported, + device: device + }; + return { + method: 'POST', + url: ENDPOINT_URL, + data: payload + }; + }); + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} serverResponse A successful response from the server. + * @param bidRequest + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function (serverResponse, bidRequest) { + const bidResponses = []; + const response = serverResponse.body; + + response.forEach(function(bid) { + bid.ttl = config.getConfig('_bidderTimeout'); + bidResponses.push(bid); + }); + return bidResponses; + }, +}; + +function getDevice() { + const ua = navigator.userAgent; + const topWindow = window.top; + if ((/(ipad|xoom|sch-i800|playbook|silk|tablet|kindle)|(android(?!.*mobi))/i).test(ua)) { + return 'tablet'; + } + if ((/(smart[-]?tv|hbbtv|appletv|googletv|hdmi|netcast\.tv|viera|nettv|roku|\bdtv\b|sonydtv|inettvbrowser|\btv\b)/i).test(ua)) { + return 'connectedtv'; + } + if ((/Mobile|iP(hone|od|ad)|Android|BlackBerry|IEMobile|Kindle|NetFront|Windows\sCE|Silk-Accelerated|(hpw|web)OS|Fennec|Minimo|Opera M(obi|ini)|Blazer|Dolfin|Dolphin|Skyfire|Zune/i).test(ua)) { + return 'smartphone'; + } + const width = topWindow.innerWidth || topWindow.document.documentElement.clientWidth || topWindow.document.body.clientWidth; + if (width > 320) { + return 'desktop'; + } + return 'other'; +} + +registerBidder(spec); diff --git a/modules/ablidaBidAdapter.md b/modules/ablidaBidAdapter.md new file mode 100644 index 00000000000..70e6576cd30 --- /dev/null +++ b/modules/ablidaBidAdapter.md @@ -0,0 +1,32 @@ +# Overview + +**Module Name**: Ablida Bidder Adapter +**Module Type**: Bidder Adapter +**Maintainer**: d.kuster@ablida.de + +# Description + +Module that connects to Ablida's bidder for bids. + +# Test Parameters +``` + var adUnits = [ + { + code: 'ad-div', + mediaTypes: { + banner: { + sizes: [[300, 250]], + } + }, + bids: [ + { + bidder: 'ablida', + params: { + placementId: 'mediumrectangle-demo', + categories: ['automotive', 'news-and-politics'] // optional: categories of page + } + } + ] + } + ]; +``` diff --git a/modules/adagioAnalyticsAdapter.js b/modules/adagioAnalyticsAdapter.js new file mode 100644 index 00000000000..1cdbec829d9 --- /dev/null +++ b/modules/adagioAnalyticsAdapter.js @@ -0,0 +1,23 @@ +/** + * Analytics Adapter for Adagio + */ + +import adapter from '../src/AnalyticsAdapter'; +import adapterManager from '../src/adapterManager'; + +// This config makes Prebid.js call this function on each event: +// `window['AdagioPrebidAnalytics']('on', eventType, args)` +// If it is missing, then Prebid.js will immediately log an error, +// instead of queueing the events until the function appears. +var adagioAdapter = adapter({ + global: 'AdagioPrebidAnalytics', + handler: 'on', + analyticsType: 'bundle' +}); + +adapterManager.registerAnalyticsAdapter({ + adapter: adagioAdapter, + code: 'adagio' +}); + +export default adagioAdapter; diff --git a/modules/adagioAnalyticsAdapter.md b/modules/adagioAnalyticsAdapter.md new file mode 100644 index 00000000000..5734bc85b2a --- /dev/null +++ b/modules/adagioAnalyticsAdapter.md @@ -0,0 +1,17 @@ +# Overview + +Module Name: Adagio Analytics Adapter +Module Type: Adagio Adapter +Maintainer: dev@adagio.io + +# Description + +Analytics adapter for Adagio + +# Test Parameters + +``` +{ + provider: 'adagio' +} +``` diff --git a/modules/adagioBidAdapter.js b/modules/adagioBidAdapter.js new file mode 100644 index 00000000000..8f6e59b0633 --- /dev/null +++ b/modules/adagioBidAdapter.js @@ -0,0 +1,178 @@ +import find from 'core-js/library/fn/array/find'; +import * as utils from '../src/utils'; +import { registerBidder } from '../src/adapters/bidderFactory'; + +const BIDDER_CODE = 'adagio'; +const VERSION = '1.0.0'; +const ENDPOINT = 'https://mp.4dex.io/prebid'; +const SUPPORTED_MEDIA_TYPES = ['banner']; + +/** + * Based on https://github.com/ua-parser/uap-cpp/blob/master/UaParser.cpp#L331, with the following updates: + * - replaced `mobile` by `mobi` in the table regexp, so Opera Mobile on phones is not detected as a tablet. + */ +function _getDeviceType() { + let ua = navigator.userAgent; + + // Tablets must be checked before phones. + if ((/(tablet|ipad|playbook|silk)|(android(?!.*mobi))/i).test(ua)) { + return 5; // "tablet" + } + if ((/Mobile|iP(hone|od|ad)|Android|BlackBerry|IEMobile|Kindle|NetFront|Silk-Accelerated|(hpw|web)OS|Fennec|Minimo|Opera M(obi|ini)|Blazer|Dolfin|Dolphin|Skyfire|Zune/).test(ua)) { + return 4; // "phone" + } + // Consider that all other devices are personal computers + return 2; +}; + +function _getDevice() { + const language = navigator.language ? 'language' : 'userLanguage'; + return { + userAgent: navigator.userAgent, + language: navigator[language], + deviceType: _getDeviceType(), + dnt: utils.getDNT() ? 1 : 0, + geo: {}, + js: 1 + }; +}; + +function _getSite() { + const topLocation = utils.getTopWindowLocation(); + return { + domain: topLocation.hostname, + page: topLocation.href, + referrer: utils.getTopWindowReferrer() + }; +}; + +function _getPageviewId() { + return (!window.top.ADAGIO || !window.top.ADAGIO.pageviewId) ? '_' : window.top.ADAGIO.pageviewId; +}; + +function _getFeatures(bidRequest) { + if (!window.top._ADAGIO || !window.top._ADAGIO.features) { + return {}; + } + + const rawFeatures = window.top._ADAGIO.features.getFeatures( + document.getElementById(bidRequest.adUnitCode), + function(features) { + return { + site_id: bidRequest.params.siteId, + placement: bidRequest.params.placementId, + pagetype: bidRequest.params.pagetypeId, + categories: bidRequest.params.categories + }; + } + ); + return rawFeatures; +} + +function _getGdprConsent(bidderRequest) { + const consent = {}; + if (utils.deepAccess(bidderRequest, 'gdprConsent')) { + if (bidderRequest.gdprConsent.consentString !== undefined) { + consent.consentString = bidderRequest.gdprConsent.consentString; + } + if (bidderRequest.gdprConsent.gdprApplies !== undefined) { + consent.consentRequired = bidderRequest.gdprConsent.gdprApplies ? 1 : 0; + } + if (bidderRequest.gdprConsent.allowAuctionWithoutConsent !== undefined) { + consent.allowAuctionWithoutConsent = bidderRequest.gdprConsent.allowAuctionWithoutConsent ? 1 : 0; + } + } + return consent; +} + +export const spec = { + code: BIDDER_CODE, + + supportedMediaType: SUPPORTED_MEDIA_TYPES, + + isBidRequestValid: function(bid) { + return !!(bid.params.siteId && bid.params.placementId); + }, + + buildRequests: function(validBidRequests, bidderRequest) { + const secure = (location.protocol === 'https:') ? 1 : 0; + const device = _getDevice(); + const site = _getSite(); + const pageviewId = _getPageviewId(); + const gdprConsent = _getGdprConsent(bidderRequest); + const adUnits = utils._map(validBidRequests, (bidRequest) => { + bidRequest.params.features = _getFeatures(bidRequest); + const categories = bidRequest.params.categories; + if (typeof categories !== 'undefined' && !Array.isArray(categories)) { + bidRequest.params.categories = [categories]; + } + return bidRequest; + }); + + // Regroug ad units by siteId + const groupedAdUnits = adUnits.reduce((groupedAdUnits, adUnit) => { + (groupedAdUnits[adUnit.params.siteId] = groupedAdUnits[adUnit.params.siteId] || []).push(adUnit); + return groupedAdUnits; + }, {}); + + // Build one request per siteId + const requests = utils._map(Object.keys(groupedAdUnits), (siteId) => { + return { + method: 'POST', + url: ENDPOINT, + data: { + id: utils.generateUUID(), + secure: secure, + device: device, + site: site, + siteId: siteId, + pageviewId: pageviewId, + adUnits: groupedAdUnits[siteId], + gdpr: gdprConsent, + adapterVersion: VERSION + }, + options: { + contentType: 'application/json' + } + } + }); + + return requests; + }, + + interpretResponse: function(serverResponse, bidRequest) { + let bidResponses = []; + try { + const response = serverResponse.body; + if (response) { + response.bids.forEach(bidObj => { + const bidReq = (find(bidRequest.data.adUnits, bid => bid.bidId === bidObj.requestId)); + if (bidReq) { + bidObj.placementId = bidReq.params.placementId; + bidObj.pagetypeId = bidReq.params.pagetypeId; + bidObj.categories = (bidReq.params.features && bidReq.params.features.categories) ? bidReq.params.features.categories : []; + } + bidResponses.push(bidObj); + }); + } + } catch (err) { + utils.logError(err); + } + return bidResponses; + }, + + getUserSyncs: function(syncOptions, serverResponses) { + if (!serverResponses.length || serverResponses[0].body === '' || !serverResponses[0].body.userSyncs) { + return false; + } + const syncs = serverResponses[0].body.userSyncs.map((sync) => { + return { + type: sync.t === 'p' ? 'image' : 'iframe', + url: sync.u + } + }) + return syncs; + } +} + +registerBidder(spec); diff --git a/modules/adagioBidAdapter.md b/modules/adagioBidAdapter.md new file mode 100644 index 00000000000..ff33b035e5f --- /dev/null +++ b/modules/adagioBidAdapter.md @@ -0,0 +1,60 @@ +# Overview + +Module Name: Adagio Bid Adapter +Module Type: Adagio Adapter +Maintainer: dev@adagio.io + +## Description + +Connects to Adagio demand source to fetch bids. + +## Test Parameters + +```javascript + var adUnits = [ + { + code: 'ad-unit_code', + sizes: [[300, 250], [300, 600]], + bids: [ + { + bidder: 'adagio', // Required + params: { + siteId: '0', // Required - Site ID from Adagio. + placementId: '4', // Required - Placement ID from Adagio. Refers to the placement of an ad unit in a page. + pagetypeId: '343', // Required - Page type ID from Adagio. + categories: ['IAB12', 'IAB12-2'], // IAB categories of the page. + } + } + ] + } + ]; + + pbjs.addAdUnits(adUnits); + + pbjs.bidderSettings = { + adagio: { + alwaysUseBid: true, + adserverTargeting: [ + { + key: "placement", + val: function (bidResponse) { + return bidResponse.placementId; + } + }, + { + key: "pagetype", + val: function (bidResponse) { + return bidResponse.pagetypeId; + } + }, + { + key: "categories", + val: function (bidResponse) { + return bidResponse.categories.join(","); + } + } + ] + } + } + +``` diff --git a/modules/adbutlerBidAdapter.js b/modules/adbutlerBidAdapter.js index 44a2ef49f51..88aa4f158b7 100644 --- a/modules/adbutlerBidAdapter.js +++ b/modules/adbutlerBidAdapter.js @@ -1,14 +1,15 @@ 'use strict'; -import * as utils from 'src/utils'; -import {config} from 'src/config'; -import {registerBidder} from 'src/adapters/bidderFactory'; +import * as utils from '../src/utils'; +import {config} from '../src/config'; +import {registerBidder} from '../src/adapters/bidderFactory'; const BIDDER_CODE = 'adbutler'; export const spec = { code: BIDDER_CODE, pageID: Math.floor(Math.random() * 10e6), + aliases: ['divreach'], isBidRequestValid: function (bid) { return !!(bid.params.accountID && bid.params.zoneID); @@ -98,7 +99,7 @@ export const spec = { }); if (isCorrectCPM && isCorrectSize) { bidResponse.requestId = bidObj.bidId; - bidResponse.bidderCode = spec.code; + bidResponse.bidderCode = bidObj.bidder; bidResponse.creativeId = serverResponse.placement_id; bidResponse.cpm = CPM; bidResponse.width = width; diff --git a/modules/adformBidAdapter.js b/modules/adformBidAdapter.js index cc64b49d8f7..a3aef10e41e 100644 --- a/modules/adformBidAdapter.js +++ b/modules/adformBidAdapter.js @@ -1,7 +1,8 @@ 'use strict'; -import {registerBidder} from 'src/adapters/bidderFactory'; -import { BANNER, VIDEO } from 'src/mediaTypes'; +import {registerBidder} from '../src/adapters/bidderFactory'; +import { config } from '../src/config'; +import { BANNER, VIDEO } from '../src/mediaTypes'; const BIDDER_CODE = 'adform'; export const spec = { @@ -12,6 +13,8 @@ export const spec = { }, buildRequests: function (validBidRequests, bidderRequest) { var i, l, j, k, bid, _key, _value, reqParams, netRevenue, gdprObject; + const currency = config.getConfig('currency.adServerCurrency'); + var request = []; var globalParams = [ [ 'adxDomain', 'adx.adform.net' ], [ 'fd', 1 ], [ 'url', null ], [ 'tid', null ] ]; var bids = JSON.parse(JSON.stringify(validBidRequests)); @@ -31,6 +34,7 @@ export const spec = { } reqParams = bid.params; reqParams.transactionId = bid.transactionId; + reqParams.rcur = reqParams.rcur || currency; request.push(formRequestUrl(reqParams)); } diff --git a/modules/adformOpenRTBBidAdapter.js b/modules/adformOpenRTBBidAdapter.js new file mode 100644 index 00000000000..98e6de8036a --- /dev/null +++ b/modules/adformOpenRTBBidAdapter.js @@ -0,0 +1,202 @@ +// jshint esversion: 6, es3: false, node: true +'use strict'; + +import { + registerBidder +} from '../src/adapters/bidderFactory'; +import { + NATIVE +} from '../src/mediaTypes'; +import * as utils from '../src/utils'; +import { config } from '../src/config'; + +const BIDDER_CODE = 'adformOpenRTB'; +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, + supportedMediaTypes: [ NATIVE ], + isBidRequestValid: bid => !!bid.params.mid, + buildRequests: (validBidRequests, bidderRequest) => { + const page = bidderRequest.refererInfo.referer; + const adxDomain = setOnAny(validBidRequests, 'params.adxDomain') || 'adx.adform.net'; + const ua = navigator.userAgent; + const pt = setOnAny(validBidRequests, 'params.pt') || setOnAny(validBidRequests, 'params.priceType') || 'net'; + const tid = validBidRequests[0].transactionId; // ??? check with ssp + const test = setOnAny(validBidRequests, 'params.test'); + const publisher = setOnAny(validBidRequests, 'params.publisher'); + const siteId = setOnAny(validBidRequests, 'params.siteId'); + const currency = config.getConfig('currency.adServerCurrency'); + const cur = currency && [ currency ]; + + const imp = validBidRequests.map((bid, id) => { + bid.netRevenue = pt; + const assets = 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); + + return { + id: id + 1, + tagid: bid.params.mid, + native: { + request: { + assets + } + } + }; + }); + + const request = { + id: bidderRequest.auctionId, + site: { id: siteId, page, publisher }, + device: { ua }, + source: { tid, fd: 1 }, + ext: { pt }, + cur, + imp + }; + + if (test) { + request.is_debug = !!test; + request.test = 1; + } + if (utils.deepAccess(bidderRequest, 'gdprConsent.gdprApplies')) { + request.user = { ext: { consent: bidderRequest.gdprConsent.consentString } }; + request.regs = { ext: { gdpr: bidderRequest.gdprConsent.gdprApplies & 1 } }; + } + + return { + method: 'POST', + url: '//' + adxDomain + '/adx/openrtb', + data: JSON.stringify(request), + options: { + contentType: 'application/json' + }, + bids: validBidRequests + }; + }, + interpretResponse: function(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) { + return { + requestId: bid.bidId, + cpm: bidResponse.price, + creativeId: bidResponse.crid, + ttl: 360, + netRevenue: bid.netRevenue === 'net', + currency: cur, + mediaType: NATIVE, + bidderCode: BIDDER_CODE, + native: parseNative(bidResponse) + }; + } + }).filter(Boolean); + } +}; + +registerBidder(spec); + +function parseNative(bid) { + const { assets, link, imptrackers, jstracker } = bid.native; + const result = { + clickUrl: link.url, + clickTrackers: link.clicktrackers || undefined, + impressionTrackers: imptrackers || undefined, + javascriptTrackers: jstracker ? [ jstracker ] : undefined + }; + assets.forEach(asset => { + const kind = NATIVE_ASSET_IDS[asset.id]; + const content = kind && asset[NATIVE_PARAMS[kind].name]; + if (content) { + result[kind] = content.text || content.value || { url: content.url, width: content.w, height: content.h }; + } + }); + + return result; +} + +function setOnAny(collection, key) { + for (let i = 0, result; i < collection.length; i++) { + result = utils.deepAccess(collection[i], key); + if (result) { + return result; + } + } +} + +function flatten(arr) { + return [].concat(...arr); +} diff --git a/modules/adformOpenRTBBidAdapter.md b/modules/adformOpenRTBBidAdapter.md new file mode 100644 index 00000000000..0dd98ad07b8 --- /dev/null +++ b/modules/adformOpenRTBBidAdapter.md @@ -0,0 +1,59 @@ +# Overview + +Module Name: Adform OpenRTB Adapter +Module Type: Bidder Adapter +Maintainer: Scope.FL.Scripts@adform.com + +# Description + +Module that connects to Adform demand sources to fetch bids. +Only native format is supported. Using OpenRTB standard. + +# Test Parameters +``` + var adUnits = [ + code: '/19968336/prebid_native_example_1', + sizes: [ + [360, 360] + ], + 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: 'adformOpenRTB', + params: { + mid: 606169, // required + adxDomain: 'adx.adform.net', // optional + siteId: '23455', // optional + priceType: 'gross' // optional, default is 'net' + publisher: { // optional block + id: "2706", + name: "Publishers Name", + domain: "publisher.com" + } + } + }] + ]; +``` diff --git a/modules/adgenerationBidAdapter.js b/modules/adgenerationBidAdapter.js index 1753446fc67..77b6acbf0e0 100644 --- a/modules/adgenerationBidAdapter.js +++ b/modules/adgenerationBidAdapter.js @@ -1,7 +1,7 @@ -import * as utils from 'src/utils'; -// import {config} from 'src/config'; -import {registerBidder} from 'src/adapters/bidderFactory'; -import {BANNER, NATIVE} from 'src/mediaTypes'; +import * as utils from '../src/utils'; +import {registerBidder} from '../src/adapters/bidderFactory'; +import {BANNER, NATIVE} from '../src/mediaTypes'; +import {config} from '../src/config'; const ADG_BIDDER_CODE = 'adgeneration'; export const spec = { @@ -23,7 +23,8 @@ export const spec = { * @param {validBidRequests[]} - an array of bids * @return ServerRequest Info describing the request to the server. */ - buildRequests: function (validBidRequests) { + buildRequests: function (validBidRequests, bidderRequest) { + const ADGENE_PREBID_VERSION = '1.0.1'; let serverRequests = []; for (let i = 0, len = validBidRequests.length; i < len; i++) { const validReq = validBidRequests[i]; @@ -38,12 +39,16 @@ export const spec = { data = utils.tryAppendQueryString(data, 'hb', 'true'); data = utils.tryAppendQueryString(data, 't', 'json3'); data = utils.tryAppendQueryString(data, 'transactionid', validReq.transactionId); - + data = utils.tryAppendQueryString(data, 'sizes', getSizes(validReq)); + data = utils.tryAppendQueryString(data, 'currency', getCurrencyType()); + data = utils.tryAppendQueryString(data, 'pbver', '$prebid.version$'); + data = utils.tryAppendQueryString(data, 'sdkname', 'prebidjs'); + data = utils.tryAppendQueryString(data, 'adapterver', ADGENE_PREBID_VERSION); // native以外にvideo等の対応が入った場合は要修正 if (!validReq.mediaTypes || !validReq.mediaTypes.native) { data = utils.tryAppendQueryString(data, 'imark', '1'); } - + data = utils.tryAppendQueryString(data, 'tp', bidderRequest.refererInfo.referer); // remove the trailing "&" if (data.lastIndexOf('&') === data.length - 1) { data = data.substring(0, data.length - 1); @@ -70,11 +75,6 @@ export const spec = { return []; } const bidRequest = bidRequests.bidRequest; - if (!bidRequest.mediaTypes || bidRequest.mediaTypes.banner) { - if (!body.w || !body.h) { - return []; - } - } const bidResponse = { requestId: bidRequest.bidId, cpm: body.cpm || 0, @@ -82,12 +82,11 @@ export const spec = { height: body.h ? body.h : 1, creativeId: body.creativeid || '', dealId: body.dealid || '', - currency: 'JPY', + currency: getCurrencyType(), netRevenue: true, ttl: body.ttl || 10, - referrer: utils.getTopWindowUrl(), }; - if (bidRequest.mediaTypes && bidRequest.mediaTypes.native) { + if (isNative(body)) { bidResponse.native = createNativeAd(body); bidResponse.mediaType = NATIVE; } else { @@ -120,6 +119,11 @@ function createAd(body, bidRequest) { return ad; } +function isNative(body) { + if (!body) return false; + return body.native_ad && body.native_ad.assets.length > 0; +} + function createNativeAd(body) { let native = {}; if (body.native_ad && body.native_ad.assets.length > 0) { @@ -152,6 +156,9 @@ function createNativeAd(body) { case 6: native.cta = assets[i].data.value; break; + case 502: + native.privacyLink = encodeURIComponent(assets[i].data.value); + break; } } native.clickUrl = body.native_ad.link.url; @@ -198,4 +205,32 @@ function removeWrapper(ad) { return ad.substr(bodyIndex, lastBodyIndex).replace('', '').replace('', ''); } +/** + * request + * @param validReq request + * @returns {?string} 300x250,320x50... + */ +function getSizes(validReq) { + const sizes = validReq.sizes; + if (!sizes || sizes.length < 1) return null; + let sizesStr = ''; + for (const i in sizes) { + const size = sizes[i]; + if (size.length !== 2) return null; + sizesStr += `${size[0]}x${size[1]},`; + } + if (sizesStr || sizesStr.lastIndexOf(',') === sizesStr.length - 1) { + sizesStr = sizesStr.substring(0, sizesStr.length - 1); + } + return sizesStr; +} + +/** + * @return {?string} USD or JPY + */ +function getCurrencyType() { + if (config.getConfig('currency.adServerCurrency') && config.getConfig('currency.adServerCurrency').toUpperCase() === 'USD') return 'USD'; + return 'JPY'; +} + registerBidder(spec); diff --git a/modules/adgenerationBidAdapter.md b/modules/adgenerationBidAdapter.md index 7d8281be9b2..7dfc301e657 100644 --- a/modules/adgenerationBidAdapter.md +++ b/modules/adgenerationBidAdapter.md @@ -52,7 +52,10 @@ var adUnits = [ }, icon: { required: true - } + }, + privacyLink: { + required: true + }, }, }, bids: [ diff --git a/modules/adheseBidAdapter.js b/modules/adheseBidAdapter.js new file mode 100644 index 00000000000..6ca8c8a6aa6 --- /dev/null +++ b/modules/adheseBidAdapter.js @@ -0,0 +1,175 @@ +'use strict'; + +import { registerBidder } from '../src/adapters/bidderFactory'; +import { BANNER, VIDEO } from '../src/mediaTypes'; + +const BIDDER_CODE = 'adhese'; +const USER_SYNC_BASE_URL = 'https://user-sync.adhese.com/iframe/user_sync.html'; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO], + + isBidRequestValid: function(bid) { + return !!(bid.params.account && bid.params.location && bid.params.format); + }, + + buildRequests: function(validBidRequests, bidderRequest) { + if (validBidRequests.length === 0) { + return null; + } + const { gdprConsent, refererInfo } = bidderRequest; + + const account = getAccount(validBidRequests); + 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 targetsParams = Object.keys(targets).map(targetCode => targetCode + targets[targetCode].join(';')); + const slotsParams = validBidRequests.map(bid => 'sl' + bidToSlotName(bid)); + const params = [...slotsParams, ...targetsParams, ...gdprParams, ...refererParams].map(s => `/${s}`).join(''); + const cacheBuster = '?t=' + new Date().getTime(); + const uri = 'https://ads-' + account + '.adhese.com/json' + params + cacheBuster; + + return { + method: 'GET', + url: uri, + bids: validBidRequests + }; + }, + + interpretResponse: function(serverResponse, request) { + const serverAds = serverResponse.body.reduce(function(map, ad) { + map[ad.slotName] = ad; + return map; + }, {}); + + serverResponse.account = getAccount(request.bids); + + return request.bids + .map(bid => ({ + bid: bid, + ad: serverAds[bidToSlotName(bid)] + })) + .filter(item => item.ad) + .map(item => adResponse(item.bid, item.ad)); + }, + + getUserSyncs: function(syncOptions, serverResponses, gdprConsent) { + if (syncOptions.iframeEnabled && serverResponses.length > 0) { + const account = serverResponses[0].account; + if (account) { + let syncurl = USER_SYNC_BASE_URL + '?account=' + account; + if (gdprConsent) { + syncurl += '&gdpr=' + (gdprConsent.gdprApplies ? 1 : 0); + syncurl += '&consentString=' + encodeURIComponent(gdprConsent.consentString || ''); + } + return [{type: 'iframe', url: syncurl}]; + } + } + return []; + } +}; + +function adResponse(bid, ad) { + const price = getPrice(ad); + const adDetails = getAdDetails(ad); + const markup = getAdMarkup(ad); + + const bidResponse = getbaseAdResponse({ + requestId: bid.bidId, + mediaType: getMediaType(markup), + cpm: Number(price.amount), + currency: price.currency, + width: Number(ad.width), + height: Number(ad.height), + creativeId: adDetails.creativeId, + dealId: adDetails.dealId + }); + + if (bidResponse.mediaType === VIDEO) { + bidResponse.vastXml = markup; + } else { + const counter = ad.impressionCounter ? "" : ''; + bidResponse.ad = markup + counter; + } + return bidResponse; +} + +function mergeTargets(targets, target) { + if (target) { + Object.keys(target).forEach(function (key) { + const val = target[key]; + const values = Array.isArray(val) ? val : [val]; + if (targets[key]) { + const distinctValues = values.filter(v => targets[key].indexOf(v) < 0); + targets[key].push.apply(targets[key], distinctValues); + } else { + targets[key] = values; + } + }); + } + return targets; +} + +function bidToSlotName(bid) { + return bid.params.location + '-' + bid.params.format; +} + +function getAccount(validBidRequests) { + return validBidRequests[0].params.account; +} + +function getbaseAdResponse(response) { + return Object.assign({ netRevenue: true, ttl: 360 }, response); +} + +function isAdheseAd(ad) { + return !ad.origin || ad.origin === 'JERLICIA'; +} + +function getMediaType(markup) { + const isVideo = markup.trim().toLowerCase().match(/<\?xml| { analyticsAdapter.originEnableAnalytics(config); }; -adaptermanager.registerAnalyticsAdapter({ +adapterManager.registerAnalyticsAdapter({ adapter: analyticsAdapter, code: 'adkernelAdn' }); @@ -110,11 +110,15 @@ function sendAll() { let events = analyticsAdapter.context.queue.popAll(); if (events.length !== 0) { let req = Object.assign({}, analyticsAdapter.context.requestTemplate, {hb_ev: events}); - ajax(`//${analyticsAdapter.context.host}/hb-analytics`, () => { - }, JSON.stringify(req)); + analyticsAdapter.ajaxCall(JSON.stringify(req)); } } +analyticsAdapter.ajaxCall = function ajaxCall(data) { + ajax(`//${analyticsAdapter.context.host}/hb-analytics`, () => { + }, data); +}; + function trackAuctionInit() { analyticsAdapter.context.auctionTimeStart = Date.now(); const event = createHbEvent(undefined, ADK_HB_EVENTS.AUCTION_INIT); diff --git a/modules/adkernelAdnBidAdapter.js b/modules/adkernelAdnBidAdapter.js index 60c33170b3c..08842db37e3 100644 --- a/modules/adkernelAdnBidAdapter.js +++ b/modules/adkernelAdnBidAdapter.js @@ -1,16 +1,15 @@ -import * as utils from 'src/utils'; -import {registerBidder} from 'src/adapters/bidderFactory'; -import {BANNER, VIDEO} from 'src/mediaTypes'; -import includes from 'core-js/library/fn/array/includes'; +import * as utils from '../src/utils'; +import {registerBidder} from '../src/adapters/bidderFactory'; +import {BANNER, VIDEO} from '../src/mediaTypes'; +import {parse as parseUrl} from '../src/url'; const DEFAULT_ADKERNEL_DSP_DOMAIN = 'tag.adkernel.com'; -const VIDEO_TARGETING = ['mimes', 'protocols', 'api']; 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]; -function isRtbDebugEnabled() { - return utils.getTopWindowLocation().href.indexOf('adk_debug=true') !== -1; +function isRtbDebugEnabled(refInfo) { + return refInfo.referer.indexOf('adk_debug=true') !== -1; } function buildImp(bidRequest) { @@ -18,26 +17,22 @@ function buildImp(bidRequest) { id: bidRequest.bidId, tagid: bidRequest.adUnitCode }; - if (bidRequest.mediaType === BANNER || utils.deepAccess(bidRequest, `mediaTypes.banner`) || - (bidRequest.mediaTypes === undefined && bidRequest.mediaType === undefined)) { - let sizes = canonicalizeSizesArray(bidRequest.sizes); + let bannerReq = utils.deepAccess(bidRequest, `mediaTypes.banner`); + let videoReq = utils.deepAccess(bidRequest, `mediaTypes.video`); + if (bannerReq) { + let sizes = canonicalizeSizesArray(bannerReq.sizes); imp.banner = { format: utils.parseSizesInput(sizes) } - } else if (bidRequest.mediaType === VIDEO || utils.deepAccess(bidRequest, `mediaTypes.video`)) { - let size = canonicalizeSizesArray(bidRequest.sizes)[0]; + } else if (videoReq) { + let size = canonicalizeSizesArray(videoReq.playerSize)[0]; imp.video = { w: size[0], h: size[1], - mimes: DEFAULT_MIMES, - protocols: DEFAULT_PROTOCOLS, - api: DEFAULT_APIS + mimes: videoReq.mimes || DEFAULT_MIMES, + protocols: videoReq.protocols || DEFAULT_PROTOCOLS, + api: videoReq.api || DEFAULT_APIS }; - if (bidRequest.params.video) { - Object.keys(bidRequest.params.video) - .filter(param => includes(VIDEO_TARGETING, param)) - .forEach(param => imp.video[param] = bidRequest.params.video[param]); - } } return imp; } @@ -54,11 +49,11 @@ function canonicalizeSizesArray(sizes) { return sizes; } -function buildRequestParams(tags, auctionId, transactionId, gdprConsent) { +function buildRequestParams(tags, auctionId, transactionId, gdprConsent, refInfo) { let req = { id: auctionId, tid: transactionId, - site: buildSite(), + site: buildSite(refInfo), imp: tags }; @@ -74,13 +69,15 @@ function buildRequestParams(tags, auctionId, transactionId, gdprConsent) { return req; } -function buildSite() { - let loc = utils.getTopWindowLocation(); +function buildSite(refInfo) { + let loc = parseUrl(refInfo.referer); let result = { - page: loc.href, - ref: utils.getTopWindowReferrer(), - secure: ~~(loc.protocol === 'https:') + page: `${loc.protocol}://${loc.hostname}${loc.pathname}`, + secure: ~~(loc.protocol === 'https') }; + if (self === top && document.referrer) { + result.ref = document.referrer; + } let keywords = document.getElementsByTagName('meta')['keywords']; if (keywords && keywords.content) { result.keywords = keywords.content; @@ -101,7 +98,7 @@ function buildBid(tag) { netRevenue: true }; if (tag.tag) { - bid.ad = `${tag.tag}`; + bid.ad = tag.tag; bid.mediaType = BANNER; } else if (tag.vast_url) { bid.vastUrl = tag.vast_url; @@ -113,10 +110,14 @@ function buildBid(tag) { export const spec = { code: 'adkernelAdn', supportedMediaTypes: [BANNER, VIDEO], + aliases: ['engagesimply'], isBidRequestValid: function(bidRequest) { - return 'params' in bidRequest && (typeof bidRequest.params.host === 'undefined' || typeof bidRequest.params.host === 'string') && - typeof bidRequest.params.pubId === 'number'; + return 'params' in bidRequest && + (typeof bidRequest.params.host === 'undefined' || typeof bidRequest.params.host === 'string') && + typeof bidRequest.params.pubId === 'number' && + 'mediaTypes' in bidRequest && + ('banner' in bidRequest.mediaTypes || 'video' in bidRequest.mediaTypes); }, buildRequests: function(bidRequests, bidderRequest) { @@ -130,16 +131,15 @@ export const spec = { acc[host][pubId].push(curr); return acc; }, {}); - let auctionId = bidderRequest.auctionId; - let gdprConsent = bidderRequest.gdprConsent; - let transactionId = bidderRequest.transactionId; + + let {auctionId, gdprConsent, transactionId, refererInfo} = bidderRequest; let requests = []; Object.keys(dispatch).forEach(host => { Object.keys(dispatch[host]).forEach(pubId => { - let request = buildRequestParams(dispatch[host][pubId], auctionId, transactionId, gdprConsent); + let request = buildRequestParams(dispatch[host][pubId], auctionId, transactionId, gdprConsent, refererInfo); requests.push({ method: 'POST', - url: `//${host}/tag?account=${pubId}&pb=1${isRtbDebugEnabled() ? '&debug=1' : ''}`, + url: `//${host}/tag?account=${pubId}&pb=1${isRtbDebugEnabled(refererInfo) ? '&debug=1' : ''}`, data: JSON.stringify(request) }) }); diff --git a/modules/adkernelAdnBidAdapter.md b/modules/adkernelAdnBidAdapter.md index d69bf3b8998..c536ee1438c 100644 --- a/modules/adkernelAdnBidAdapter.md +++ b/modules/adkernelAdnBidAdapter.md @@ -3,7 +3,7 @@ ``` Module Name: AdKernel ADN Bidder Adapter Module Type: Bidder Adapter -Maintainer: denis@adkernel.com +Maintainer: prebid-dev@adkernel.com ``` # Description @@ -14,32 +14,37 @@ Banner and video formats are supported. # Test Parameters ``` - var adUnits = [ - { - code: 'banner-ad-div', - sizes: [[300, 250], [300, 200]], - bids: [ - { - bidder: 'adkernelAdn', - params: { - pubId: 50357, - host: 'dsp-staging.adkernel.com' - } - } - ] - }, { - code: 'video-ad-player', - sizes: [640, 480], - bids: [ - { - bidder: 'adkernelAdn', - mediaType : 'video', - params: { - pubId: 50357, - host: 'dsp-staging.adkernel.com' - } - } - ] - } - ]; +var adUnits = [{ + code: 'banner-ad-div', + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 200] // banner sizes + ], + } + }, + bids: [{ + bidder: 'adkernelAdn', + params: { + pubId: 50357, + host: 'dsp-staging.adkernel.com' + } + }] +}, { + code: 'video-ad-player', + mediaTypes: { + video: { + context: 'instream', // or 'outstream' + playerSize: [640, 480] // video player size + } + }, + bids: [{ + bidder: 'adkernelAdn', + params: { + pubId: 50357, + host: 'dsp-staging.adkernel.com' + } + }] +}]; ``` diff --git a/modules/adkernelBidAdapter.js b/modules/adkernelBidAdapter.js index dffcaacec3d..492b5b3c6ab 100644 --- a/modules/adkernelBidAdapter.js +++ b/modules/adkernelBidAdapter.js @@ -1,8 +1,16 @@ -import * as utils from 'src/utils'; -import { BANNER, VIDEO } from 'src/mediaTypes'; -import {registerBidder} from 'src/adapters/bidderFactory'; +import * as utils from '../src/utils'; +import { BANNER, VIDEO } from '../src/mediaTypes'; +import {registerBidder} from '../src/adapters/bidderFactory'; import find from 'core-js/library/fn/array/find'; import includes from 'core-js/library/fn/array/includes'; +import {parse as parseUrl} from '../src/url'; + +/* + * In case you're AdKernel whitelable platform's client who needs branded adapter to + * work with Adkernel platform - DO NOT COPY THIS ADAPTER UNDER NEW NAME + * + * Please contact prebid@adkernel.com and we'll add your adapter as an alias. + */ const VIDEO_TARGETING = ['mimes', 'minduration', 'maxduration', 'protocols', 'startdelay', 'linearity', 'boxingallowed', 'playbackmethod', 'delivery', @@ -15,29 +23,28 @@ const VERSION = '1.3'; export const spec = { code: 'adkernel', - aliases: ['headbidding'], + aliases: ['headbidding', 'adsolut', 'oftmediahb', 'audiencemedia', 'waardex_ak'], supportedMediaTypes: [BANNER, VIDEO], isBidRequestValid: function(bidRequest) { - return 'params' in bidRequest && typeof bidRequest.params.host !== 'undefined' && - 'zoneId' in bidRequest.params && !isNaN(Number(bidRequest.params.zoneId)); + return 'params' in bidRequest && + typeof bidRequest.params.host !== 'undefined' && + 'zoneId' in bidRequest.params && + !isNaN(Number(bidRequest.params.zoneId)) && + bidRequest.params.zoneId > 0 && + bidRequest.mediaTypes && + (bidRequest.mediaTypes.banner || bidRequest.mediaTypes.video); }, buildRequests: function(bidRequests, bidderRequest) { - let impDispatch = dispatchImps(bidRequests); - const gdprConsent = bidderRequest.gdprConsent; - const auctionId = bidderRequest.auctionId; + let impDispatch = dispatchImps(bidRequests, bidderRequest.refererInfo); + const {gdprConsent, auctionId} = bidderRequest; const requests = []; Object.keys(impDispatch).forEach(host => { Object.keys(impDispatch[host]).forEach(zoneId => { - const request = buildRtbRequest(impDispatch[host][zoneId], auctionId, gdprConsent); + const request = buildRtbRequest(impDispatch[host][zoneId], auctionId, gdprConsent, bidderRequest.refererInfo); requests.push({ - method: 'GET', - url: `${window.location.protocol}//${host}/rtbg`, - data: { - zone: Number(zoneId), - ad_type: 'rtb', - v: VERSION, - r: JSON.stringify(request) - } + method: 'POST', + url: `${window.originalLocation.protocol}//${host}/hb?zone=${zoneId}&v=${VERSION}`, + data: JSON.stringify(request) }); }); }); @@ -49,14 +56,13 @@ export const spec = { return []; } - let rtbRequest = JSON.parse(request.data.r); - let rtbImps = rtbRequest.imp; + let rtbRequest = JSON.parse(request.data); let rtbBids = response.seatbid .map(seatbid => seatbid.bid) .reduce((a, b) => a.concat(b), []); return rtbBids.map(rtbBid => { - let imp = find(rtbImps, imp => imp.id === rtbBid.impid); + let imp = find(rtbRequest.imp, imp => imp.id === rtbBid.impid); let prBid = { requestId: rtbBid.impid, cpm: rtbBid.price, @@ -96,8 +102,9 @@ registerBidder(spec); /** * Dispatch impressions by ad network host and zone */ -function dispatchImps(bidRequests) { - return bidRequests.map(buildImp) +function dispatchImps(bidRequests, refererInfo) { + let secure = (refererInfo && refererInfo.referer.indexOf('https:') === 0); + return bidRequests.map(bidRequest => buildImp(bidRequest, secure)) .reduce((acc, curr, index) => { let bidRequest = bidRequests[index]; let zoneId = bidRequest.params.zoneId; @@ -112,32 +119,28 @@ function dispatchImps(bidRequests) { /** * Builds parameters object for single impression */ -function buildImp(bidRequest) { +function buildImp(bidRequest, secure) { const imp = { 'id': bidRequest.bidId, 'tagid': bidRequest.adUnitCode }; - if (bidRequest.mediaType === BANNER || utils.deepAccess(bidRequest, `mediaTypes.banner`) || - (bidRequest.mediaTypes === undefined && bidRequest.mediaType === undefined)) { - let sizes = canonicalizeSizesArray(bidRequest.sizes); + if (utils.deepAccess(bidRequest, `mediaTypes.banner`)) { + let sizes = canonicalizeSizesArray(bidRequest.mediaTypes.banner.sizes); imp.banner = { - format: sizes.map(s => ({'w': s[0], 'h': s[1]})), + format: sizes.map(wh => utils.parseGPTSingleSizeArrayToRtbSize(wh)), topframe: 0 }; - } else if (bidRequest.mediaType === VIDEO || utils.deepAccess(bidRequest, 'mediaTypes.video')) { - let size = canonicalizeSizesArray(bidRequest.sizes)[0]; - imp.video = { - w: size[0], - h: size[1] - }; + } else if (utils.deepAccess(bidRequest, 'mediaTypes.video')) { + let size = canonicalizeSizesArray(bidRequest.mediaTypes.video.playerSize)[0]; + imp.video = utils.parseGPTSingleSizeArrayToRtbSize(size); if (bidRequest.params.video) { Object.keys(bidRequest.params.video) - .filter(param => includes(VIDEO_TARGETING, param)) - .forEach(param => imp.video[param] = bidRequest.params.video[param]); + .filter(key => includes(VIDEO_TARGETING, key)) + .forEach(key => imp.video[key] = bidRequest.params.video[key]); } } - if (utils.getTopWindowLocation().protocol === 'https:') { + if (secure) { imp.secure = 1; } return imp; @@ -149,7 +152,7 @@ function buildImp(bidRequest) { * @return Array[Array[Number]] */ function canonicalizeSizesArray(sizes) { - if (sizes.length == 2 && !utils.isArray(sizes[0])) { + if (sizes.length === 2 && !utils.isArray(sizes[0])) { return [sizes]; } return sizes; @@ -160,12 +163,14 @@ function canonicalizeSizesArray(sizes) { * @param imps collection of impressions * @param auctionId * @param gdprConsent + * @param refInfo + * @return Object complete rtb request */ -function buildRtbRequest(imps, auctionId, gdprConsent) { +function buildRtbRequest(imps, auctionId, gdprConsent, refInfo) { let req = { 'id': auctionId, 'imp': imps, - 'site': createSite(), + 'site': createSite(refInfo), 'at': 1, 'device': { 'ip': 'caller', @@ -197,12 +202,20 @@ function getLanguage() { /** * Creates site description object */ -function createSite() { - var location = utils.getTopWindowLocation(); - return { - 'domain': location.hostname, - 'page': location.href.split('?')[0] +function createSite(refInfo) { + let url = parseUrl(refInfo.referer); + let site = { + 'domain': url.hostname, + 'page': url.protocol + '://' + url.hostname + url.pathname }; + if (self === top && document.referrer) { + site.ref = document.referrer; + } + let keywords = document.getElementsByTagName('meta')['keywords']; + if (keywords && keywords.content) { + site.keywords = keywords.content; + } + return site; } /** @@ -210,9 +223,9 @@ function createSite() { * @param bid rtb Bid object */ function formatAdMarkup(bid) { - var adm = bid.adm; + let adm = bid.adm; if ('nurl' in bid) { adm += utils.createTrackPixelHtml(`${bid.nurl}&px=1`); } - return `${adm}`; + return adm; } diff --git a/modules/adkernelBidAdapter.md b/modules/adkernelBidAdapter.md index 902be481473..f89fa5a26df 100644 --- a/modules/adkernelBidAdapter.md +++ b/modules/adkernelBidAdapter.md @@ -3,7 +3,7 @@ ``` Module Name: AdKernel Bidder Adapter Module Type: Bidder Adapter -Maintainer: denis@adkernel.com +Maintainer: prebid-dev@adkernel.com ``` # Description @@ -14,32 +14,38 @@ Banner and video formats are supported. # Test Parameters ``` - var adUnits = [ - { - code: 'banner-ad-div', - sizes: [[300, 250]], // banner size - bids: [ - { - bidder: 'adkernel', - params: { - zoneId: '30164', //required parameter - host: 'cpm.metaadserving.com' //required parameter - } - } - ] - }, { - code: 'video-ad-player', - sizes: [640, 480], // video player size - bids: [ - { - bidder: 'adkernel', - mediaType : 'video', - params: { - zoneId: '30164', //required parameter - host: 'cpm.metaadserving.com' //required parameter - } - } - ] - } - ]; + var adUnits = [{ + code: 'banner-ad-div', + mediaTypes: { + banner: { + sizes: [[300, 250]], // banner size + } + }, + bids: [ + { + bidder: 'adkernel', + params: { + zoneId: '30164', //required parameter + host: 'cpm.metaadserving.com' //required parameter + } + } + ] + }, { + code: 'video-ad-player', + mediaTypes: { + video: { + context: 'instream', // or 'outstream' + playerSize: [640, 480] // video player size + } + }, + bids: [ + { + bidder: 'adkernel', + params: { + zoneId: '30164', //required parameter + host: 'cpm.metaadserving.com' //required parameter + } + } + ] + }]; ``` diff --git a/modules/adliveBidAdapter.js b/modules/adliveBidAdapter.js new file mode 100644 index 00000000000..cb3d9579832 --- /dev/null +++ b/modules/adliveBidAdapter.js @@ -0,0 +1,69 @@ +import * as utils from '../src/utils'; +import { registerBidder } from '../src/adapters/bidderFactory'; +import { BANNER } from '../src/mediaTypes'; + +const BIDDER_CODE = 'adlive'; +const ENDPOINT_URL = 'https://api.publishers.adlive.io/get?pbjs=1'; + +const CURRENCY = 'USD'; +const TIME_TO_LIVE = 360; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + + isBidRequestValid: function(bid) { + return !!(bid.params.hashes && utils.isArray(bid.params.hashes)); + }, + + buildRequests: function(validBidRequests) { + let requests = []; + + utils._each(validBidRequests, function(bid) { + requests.push({ + method: 'POST', + url: ENDPOINT_URL, + options: { + contentType: 'application/json', + withCredentials: true + }, + data: JSON.stringify({ + transaction_id: bid.bidId, + hashes: utils.getBidIdParameter('hashes', bid.params) + }), + bidId: bid.bidId + }); + }); + + return requests; + }, + + interpretResponse: function(serverResponse, bidRequest) { + try { + const response = serverResponse.body; + const bidResponses = []; + + utils._each(response, function(bidResponse) { + if (!bidResponse.is_passback) { + bidResponses.push({ + requestId: bidRequest.bidId, + cpm: bidResponse.price, + width: bidResponse.size[0], + height: bidResponse.size[1], + creativeId: bidResponse.hash, + currency: CURRENCY, + netRevenue: false, + ttl: TIME_TO_LIVE, + ad: bidResponse.content + }); + } + }); + + return bidResponses; + } catch (err) { + utils.logError(err); + return []; + } + } +}; +registerBidder(spec); diff --git a/modules/adliveBidAdapter.md b/modules/adliveBidAdapter.md new file mode 100644 index 00000000000..4fc6a112e82 --- /dev/null +++ b/modules/adliveBidAdapter.md @@ -0,0 +1,28 @@ +# Overview +``` +Module Name: Adlive Bid Adapter +Module Type: Bidder Adapter +Maintainer: traffic@adlive.io +``` + +# Description +Module that connects to Adlive's server for bids. +Currently module supports only banner mediaType. + +# Test Parameters +``` + var adUnits = [{ + code: '/test/div', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bids: [{ + bidder: 'adlive', + params: { + hashes: ['1e100887dd614b0909bf6c49ba7f69fdd1360437'] + } + }] + }]; +``` \ No newline at end of file diff --git a/modules/admanBidAdapter.js b/modules/admanBidAdapter.js new file mode 100644 index 00000000000..4720d06d094 --- /dev/null +++ b/modules/admanBidAdapter.js @@ -0,0 +1,79 @@ +import {registerBidder} from '../src/adapters/bidderFactory'; +import * as utils from '../src/utils'; + +const BIDDER_CODE = 'adman'; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: ['video', 'banner'], + isBidRequestValid: function(bid) { + const isValid = _validateId(utils.deepAccess(bid, 'params.id')); + if (!isValid) { + utils.logError('Adman id parameter is required. Bid aborted.'); + } + return isValid; + }, + buildRequests: function(validBidRequests, bidderRequest) { + const ENDPOINT_URL = '//bidtor.admanmedia.com/prebid'; + const bids = validBidRequests.map(buildRequestObject); + const payload = { + referer: utils.getTopWindowUrl(), + bids, + deviceWidth: screen.width + }; + + if (bidderRequest && bidderRequest.gdprConsent) { + payload.gdpr = { + consent: bidderRequest.gdprConsent.consentString, + applies: bidderRequest.gdprConsent.gdprApplies + }; + } else { + payload.gdpr = { + consent: '' + } + } + + const payloadString = JSON.stringify(payload); + return { + method: 'POST', + url: ENDPOINT_URL, + data: payloadString, + }; + }, + interpretResponse: function(serverResponse) { + serverResponse = serverResponse.body; + if (serverResponse && typeof serverResponse.bids === 'object') { + return serverResponse.bids; + } + return []; + }, + getUserSyncs: function(syncOptions) { + if (syncOptions.iframeEnabled) { + return [{ + type: 'iframe', + url: '//cs.admanmedia.com/sync_tag/html' + }]; + } + } +}; + +function buildRequestObject(bid) { + return { + params: { + id: utils.getValue(bid.params, 'id'), + bidId: bid.bidId + }, + sizes: bid.sizes, + bidId: utils.getBidIdParameter('bidId', bid), + bidderRequestId: utils.getBidIdParameter('bidderRequestId', bid), + adUnitCode: utils.getBidIdParameter('adUnitCode', bid), + auctionId: utils.getBidIdParameter('auctionId', bid), + transactionId: utils.getBidIdParameter('transactionId', bid) + }; +} + +function _validateId(id = '') { + return (id.length === 8); +} + +registerBidder(spec); diff --git a/modules/admanBidAdapter.md b/modules/admanBidAdapter.md new file mode 100644 index 00000000000..900c828ea5c --- /dev/null +++ b/modules/admanBidAdapter.md @@ -0,0 +1,35 @@ +# Overview + +**Module Name**: Adman Bidder Adapter +**Module Type**: Bidder Adapter +**Maintainer**: prebid@admanmedia.com + +# Description + +Use `adman` as bidder. + +`id` is required and must be 8 alphanumeric characters. + +## AdUnits configuration example +``` + var adUnits = [{ + code: 'test-div', + sizes: [[300, 250]], + bids: [{ + bidder: 'adman', + params: { + id: 1234asdf + } + }] + },{ + code: 'test-div, + sizes: [[600, 338]], + bids: [{ + bidder: 'adman', + params: { + id: asdf1234 + } + }] + }]; +``` + diff --git a/modules/admaticBidAdapter.js b/modules/admaticBidAdapter.js index 28858aceaa1..727b1553d21 100644 --- a/modules/admaticBidAdapter.js +++ b/modules/admaticBidAdapter.js @@ -1,5 +1,5 @@ -import * as utils from 'src/utils'; -import { registerBidder } from 'src/adapters/bidderFactory'; +import * as utils from '../src/utils'; +import { registerBidder } from '../src/adapters/bidderFactory'; const BIDDER_CODE = 'admatic'; const ENDPOINT_URL = '//ads4.admatic.com.tr/prebid/v3/bidrequest'; diff --git a/modules/admaticBidAdapter.md b/modules/admaticBidAdapter.md index f6e822b9c06..2f2381eb6eb 100644 --- a/modules/admaticBidAdapter.md +++ b/modules/admaticBidAdapter.md @@ -27,7 +27,7 @@ Module that connects to AdMatic demand sources pid: 193937152158, // publisher id without "adm-pub-" prefix wid: 104276324971, // website id priceType: 'gross', // default is net - url: window.location.href || window.top.location.href //page url from js + url: window.originalLocation.href || window.top.location.href //page url from js } } ] @@ -45,7 +45,7 @@ Module that connects to AdMatic demand sources pid: 193937152158, // publisher id without "adm-pub-" prefix wid: 104276324971, // website id priceType: 'gross', // default is net - url: window.location.href || window.top.location.href //page url from js + url: window.originalLocation.href || window.top.location.href //page url from js } } ] diff --git a/modules/admediaBidAdapter.js b/modules/admediaBidAdapter.js new file mode 100644 index 00000000000..73d6ea08eea --- /dev/null +++ b/modules/admediaBidAdapter.js @@ -0,0 +1,71 @@ +import * as utils from '../src/utils'; +import {registerBidder} from '../src/adapters/bidderFactory'; + +const BIDDER_CODE = 'admedia'; +const ENDPOINT_URL = '//prebid.admedia.com/bidder/'; + +export const spec = { + code: BIDDER_CODE, + + isBidRequestValid: function (bid) { + return bid.params && !!bid.params.aid; + }, + + buildRequests: function (validBidRequests, bidderRequest) { + let payload = {}; + + if (bidderRequest && bidderRequest.refererInfo) { + payload.referer = encodeURIComponent(bidderRequest.refererInfo.referer); + } + + payload.tags = []; + + utils._each(validBidRequests, function (bid) { + const tag = { + id: bid.bidId, + sizes: bid.sizes, + aid: bid.params.aid + }; + payload.tags.push(tag); + }); + + const payloadString = JSON.stringify(payload); + return { + method: 'POST', + url: ENDPOINT_URL, + data: payloadString, + }; + }, + + interpretResponse: function (serverResponse, bidRequest) { + const bidResponses = []; + + if (!serverResponse.body.tags) { + return bidResponses; + } + + utils._each(serverResponse.body.tags, function (response) { + if (!response.error && response.cpm > 0) { + const bidResponse = { + requestId: response.id, + cpm: response.cpm, + width: response.width, + height: response.height, + creativeId: response.id, + dealId: response.id, + currency: 'USD', + netRevenue: true, + ttl: 120, + // referrer: REFERER, + ad: response.ad + }; + + bidResponses.push(bidResponse); + } + }); + + return bidResponses; + } +}; + +registerBidder(spec); diff --git a/modules/admediaBidAdapter.md b/modules/admediaBidAdapter.md new file mode 100644 index 00000000000..a03a7b49529 --- /dev/null +++ b/modules/admediaBidAdapter.md @@ -0,0 +1,42 @@ +# Overview + +``` +Module Name: Admedia Bidder Adapter +Module Type: Bidder Adapter +Maintainer: developers@admedia.com +``` + +# Description + +Admedia Bidder Adapter for Prebid.js. +Only Banner format is supported. + +# Test Parameters +``` + var adUnits = [ + { + code: 'test-div-0', + sizes: [[300, 250]], // a display size + bids: [ + { + bidder: 'admedia', + params: { + aid: 86858 + } + } + ] + }, + { + code: 'test-div-1', + sizes: [[300, 50]], // a mobile size + bids: [ + { + bidder: 'admedia', + params: { + aid: 86858 + } + } + ] + } + ]; +``` diff --git a/modules/admixerBidAdapter.js b/modules/admixerBidAdapter.js index 679e11270ab..c6d6dd34a11 100644 --- a/modules/admixerBidAdapter.js +++ b/modules/admixerBidAdapter.js @@ -1,11 +1,12 @@ -import * as utils from 'src/utils'; -import {registerBidder} from 'src/adapters/bidderFactory'; +import * as utils from '../src/utils'; +import {registerBidder} from '../src/adapters/bidderFactory'; const BIDDER_CODE = 'admixer'; +const ALIASES = ['go2net']; const ENDPOINT_URL = '//inv-nets.admixer.net/prebid.1.0.aspx'; export const spec = { code: BIDDER_CODE, - aliases: [], + aliases: ALIASES, supportedMediaTypes: ['banner', 'video'], /** * Determines whether or not the given bid request is valid. @@ -28,9 +29,7 @@ export const spec = { referrer: encodeURIComponent(utils.getTopWindowUrl()), }; bidderRequest.forEach((bid) => { - if (bid.bidder === BIDDER_CODE) { - payload.imps.push(bid); - } + payload.imps.push(bid); }); const payloadString = JSON.stringify(payload); return { diff --git a/modules/adoceanBidAdapter.js b/modules/adoceanBidAdapter.js index 21e13e77a0a..aa89bf3a23d 100644 --- a/modules/adoceanBidAdapter.js +++ b/modules/adoceanBidAdapter.js @@ -1,5 +1,5 @@ -import * as utils from 'src/utils'; -import { registerBidder } from 'src/adapters/bidderFactory'; +import * as utils from '../src/utils'; +import { registerBidder } from '../src/adapters/bidderFactory'; const BIDDER_CODE = 'adocean'; @@ -16,7 +16,7 @@ function buildEndpointUrl(emiter, payload) { } function buildRequest(masterBidRequests, masterId, gdprConsent) { - const firstBid = masterBidRequests[0]; + let emiter; const payload = { id: masterId, }; @@ -27,13 +27,16 @@ function buildRequest(masterBidRequests, masterId, gdprConsent) { const bidIdMap = {}; - utils._each(masterBidRequests, function(v) { - bidIdMap[v.params.slaveId] = v.bidId; + utils._each(masterBidRequests, function(bid, slaveId) { + if (!emiter) { + emiter = bid.params.emiter; + } + bidIdMap[slaveId] = bid.bidId; }); return { method: 'GET', - url: buildEndpointUrl(firstBid.params.emiter, payload), + url: buildEndpointUrl(emiter, payload), data: {}, bidIdMap: bidIdMap }; @@ -41,8 +44,16 @@ function buildRequest(masterBidRequests, masterId, gdprConsent) { function assignToMaster(bidRequest, bidRequestsByMaster) { const masterId = bidRequest.params.masterId; - bidRequestsByMaster[masterId] = bidRequestsByMaster[masterId] || []; - bidRequestsByMaster[masterId].push(bidRequest); + const slaveId = bidRequest.params.slaveId; + const masterBidRequests = bidRequestsByMaster[masterId] = bidRequestsByMaster[masterId] || [{}]; + let i = 0; + while (masterBidRequests[i] && masterBidRequests[i][slaveId]) { + i++; + } + if (!masterBidRequests[i]) { + masterBidRequests[i] = {}; + } + masterBidRequests[i][slaveId] = bidRequest; } function interpretResponse(placementResponse, bidRequest, bids) { @@ -83,8 +94,11 @@ export const spec = { utils._each(validBidRequests, function(bidRequest) { assignToMaster(bidRequest, bidRequestsByMaster); }); - requests = utils._map(bidRequestsByMaster, function(requests, masterId) { - return buildRequest(requests, masterId, bidderRequest.gdprConsent); + + utils._each(bidRequestsByMaster, function(masterRequests, masterId) { + utils._each(masterRequests, function(instanceRequests) { + requests.push(buildRequest(instanceRequests, masterId, bidderRequest.gdprConsent)); + }); }); return requests; diff --git a/modules/adomikAnalyticsAdapter.js b/modules/adomikAnalyticsAdapter.js index 30ef9c7dd90..93da72721ee 100644 --- a/modules/adomikAnalyticsAdapter.js +++ b/modules/adomikAnalyticsAdapter.js @@ -1,7 +1,7 @@ -import adapter from 'src/AnalyticsAdapter'; -import CONSTANTS from 'src/constants.json'; -import adaptermanager from 'src/adaptermanager'; -import { logInfo } from 'src/utils'; +import adapter from '../src/AnalyticsAdapter'; +import CONSTANTS from '../src/constants.json'; +import adapterManager from '../src/adapterManager'; +import { logInfo } from '../src/utils'; import find from 'core-js/library/fn/array/find'; import findIndex from 'core-js/library/fn/array/find-index'; @@ -73,7 +73,7 @@ adomikAdapter.sendTypedEvent = function() { const bulkEvents = { uid: adomikAdapter.currentContext.uid, ahbaid: adomikAdapter.currentContext.id, - hostname: window.location.hostname, + hostname: window.originalLocation.hostname, eventsByPlacementCode: groupedTypedEvents.map(function(typedEventsByType) { let sizes = []; const eventKeys = ['request', 'response', 'winner']; @@ -207,7 +207,7 @@ adomikAdapter.enableAnalytics = function (config) { } }; -adaptermanager.registerAnalyticsAdapter({ +adapterManager.registerAnalyticsAdapter({ adapter: adomikAdapter, code: 'adomik' }); diff --git a/modules/adpod.js b/modules/adpod.js new file mode 100644 index 00000000000..875809b8df5 --- /dev/null +++ b/modules/adpod.js @@ -0,0 +1,589 @@ +/** + * This module houses the functionality to evaluate and process adpod adunits/bids. Specifically there are several hooked functions, + * that either supplement the base function (ie to check something additional or unique to adpod objects) or to replace the base funtion + * entirely when appropriate. + * + * Brief outline of each hook: + * - `callPrebidCacheHook` - for any adpod bids, this function will temporarily hold them in a queue in order to send the bids to Prebid Cache in bulk + * - `checkAdUnitSetupHook` - evaluates the adUnits to ensure that required fields for adpod adUnits are present. Invalid adpod adUntis are removed from the array. + * - `checkVideoBidSetupHook` - evaluates the adpod bid returned from an adaptor/bidder to ensure required fields are populated; also initializes duration bucket field. + * + * To initialize the module, there is an `initAdpodHooks()` function that should be imported and executed by a corresponding `...AdServerVideo` + * module that designed to support adpod video type ads. This import process allows this module to effectively act as a sub-module. + */ + +import * as utils from '../src/utils'; +import { addBidToAuction, doCallbacksIfTimedout, AUCTION_IN_PROGRESS, callPrebidCache, getPriceByGranularity, getPriceGranularity } from '../src/auction'; +import { checkAdUnitSetup } from '../src/prebid'; +import { checkVideoBidSetup } from '../src/video'; +import { setupBeforeHookFnOnce, module } from '../src/hook'; +import { store } from '../src/videoCache'; +import { config } from '../src/config'; +import { ADPOD } from '../src/mediaTypes'; +import Set from 'core-js/library/fn/set'; +import find from 'core-js/library/fn/array/find'; +import { auctionManager } from '../src/auctionManager'; +import CONSTANTS from '../src/constants.json'; + +const from = require('core-js/library/fn/array/from'); + +const TARGETING_KEY_PB_CAT_DUR = 'hb_pb_cat_dur'; +const TARGETING_KEY_CACHE_ID = 'hb_cache_id'; + +let queueTimeDelay = 50; +let queueSizeLimit = 5; +let bidCacheRegistry = createBidCacheRegistry(); + +/** + * Create a registry object that stores/manages bids while be held in queue for Prebid Cache. + * @returns registry object with defined accessor functions + */ +function createBidCacheRegistry() { + let registry = {}; + + function setupRegistrySlot(auctionId) { + registry[auctionId] = {}; + registry[auctionId].bidStorage = new Set(); + registry[auctionId].queueDispatcher = createDispatcher(queueTimeDelay); + registry[auctionId].initialCacheKey = utils.generateUUID(); + } + + return { + addBid: function (bid) { + // create parent level object based on auction ID (in case there are concurrent auctions running) to store objects for that auction + if (!registry[bid.auctionId]) { + setupRegistrySlot(bid.auctionId); + } + registry[bid.auctionId].bidStorage.add(bid); + }, + removeBid: function (bid) { + registry[bid.auctionId].bidStorage.delete(bid); + }, + getBids: function (bid) { + return registry[bid.auctionId] && registry[bid.auctionId].bidStorage.values(); + }, + getQueueDispatcher: function(bid) { + return registry[bid.auctionId] && registry[bid.auctionId].queueDispatcher; + }, + setupInitialCacheKey: function(bid) { + if (!registry[bid.auctionId]) { + registry[bid.auctionId] = {}; + registry[bid.auctionId].initialCacheKey = utils.generateUUID(); + } + }, + getInitialCacheKey: function(bid) { + return registry[bid.auctionId] && registry[bid.auctionId].initialCacheKey; + } + } +} + +/** + * Creates a function that when called updates the bid queue and extends the running timer (when called subsequently). + * Once the time threshold for the queue (defined by queueSizeLimit) is reached, the queue will be flushed by calling the `firePrebidCacheCall` function. + * If there is a long enough time between calls (based on timeoutDration), the queue will automatically flush itself. + * @param {Number} timeoutDuration number of milliseconds to pass before timer expires and current bid queue is flushed + * @returns {Function} + */ +function createDispatcher(timeoutDuration) { + let timeout; + let counter = 1; + + return function(auctionInstance, bidListArr, afterBidAdded, killQueue) { + const context = this; + + var callbackFn = function() { + firePrebidCacheCall.call(context, auctionInstance, bidListArr, afterBidAdded); + }; + + clearTimeout(timeout); + + if (!killQueue) { + // want to fire off the queue if either: size limit is reached or time has passed since last call to dispatcher + if (counter === queueSizeLimit) { + counter = 1; + callbackFn(); + } else { + counter++; + timeout = setTimeout(callbackFn, timeoutDuration); + } + } else { + counter = 1; + } + }; +} + +/** + * This function reads certain fields from the bid to generate a specific key used for caching the bid in Prebid Cache + * @param {Object} bid bid object to update + * @param {Boolean} brandCategoryExclusion value read from setConfig; influences whether category is required or not + */ +function attachPriceIndustryDurationKeyToBid(bid, brandCategoryExclusion) { + let initialCacheKey = bidCacheRegistry.getInitialCacheKey(bid); + let duration = utils.deepAccess(bid, 'video.durationBucket'); + const granularity = getPriceGranularity(bid.mediaType); + let cpmFixed = getPriceByGranularity(granularity)(bid); + + let pcd; + + if (brandCategoryExclusion) { + let category = utils.deepAccess(bid, 'meta.adServerCatId'); + pcd = `${cpmFixed}_${category}_${duration}s`; + } else { + pcd = `${cpmFixed}_${duration}s`; + } + + if (!bid.adserverTargeting) { + bid.adserverTargeting = {}; + } + bid.adserverTargeting[TARGETING_KEY_PB_CAT_DUR] = pcd; + bid.adserverTargeting[TARGETING_KEY_CACHE_ID] = initialCacheKey; + bid.videoCacheKey = initialCacheKey; + bid.customCacheKey = `${pcd}_${initialCacheKey}`; +} + +/** + * Updates the running queue for the associated auction. + * Does a check to ensure the auction is still running; if it's not - the previously running queue is killed. + * @param {*} auctionInstance running context of the auction + * @param {Object} bidResponse bid object being added to queue + * @param {Function} afterBidAdded callback function used when Prebid Cache responds + */ +function updateBidQueue(auctionInstance, bidResponse, afterBidAdded) { + let bidListIter = bidCacheRegistry.getBids(bidResponse); + + if (bidListIter) { + let bidListArr = from(bidListIter); + let callDispatcher = bidCacheRegistry.getQueueDispatcher(bidResponse); + let killQueue = !!(auctionInstance.getAuctionStatus() !== AUCTION_IN_PROGRESS); + callDispatcher(auctionInstance, bidListArr, afterBidAdded, killQueue); + } else { + utils.logWarn('Attempted to cache a bid from an unknown auction. Bid:', bidResponse); + } +} + +/** + * Small helper function to remove bids from internal storage; normally b/c they're about to sent to Prebid Cache for processing. + * @param {Array[Object]} bidResponses list of bids to remove + */ +function removeBidsFromStorage(bidResponses) { + for (let i = 0; i < bidResponses.length; i++) { + bidCacheRegistry.removeBid(bidResponses[i]); + } +} + +/** + * This function will send a list of bids to Prebid Cache. It also removes the same bids from the internal bidCacheRegistry + * to maintain which bids are in queue. + * If the bids are successfully cached, they will be added to the respective auction. + * @param {*} auctionInstance running context of the auction + * @param {Array[Object]} bidList list of bid objects that need to be sent to Prebid Cache + * @param {Function} afterBidAdded callback function used when Prebid Cache responds + */ +function firePrebidCacheCall(auctionInstance, bidList, afterBidAdded) { + // remove entries now so other incoming bids won't accidentally have a stale version of the list while PBC is processing the current submitted list + removeBidsFromStorage(bidList); + + store(bidList, function (error, cacheIds) { + if (error) { + utils.logWarn(`Failed to save to the video cache: ${error}. Video bid(s) must be discarded.`); + for (let i = 0; i < bidList.length; i++) { + doCallbacksIfTimedout(auctionInstance, bidList[i]); + } + } else { + for (let i = 0; i < cacheIds.length; i++) { + // when uuid in response is empty string then the key already existed, so this bid wasn't cached + if (cacheIds[i].uuid !== '') { + addBidToAuction(auctionInstance, bidList[i]); + } else { + utils.logInfo(`Detected a bid was not cached because the custom key was already registered. Attempted to use key: ${bidList[i].customCacheKey}. Bid was: `, bidList[i]); + } + afterBidAdded(); + } + } + }); +} + +/** + * This is the main hook function to handle adpod bids; maintains the logic to temporarily hold bids in a queue in order to send bulk requests to Prebid Cache. + * @param {Function} fn reference to original function (used by hook logic) + * @param {*} auctionInstance running context of the auction + * @param {Object} bidResponse incoming bid; if adpod, will be processed through hook function. If not adpod, returns to original function. + * @param {Function} afterBidAdded callback function used when Prebid Cache responds + * @param {Object} bidderRequest copy of bid's associated bidderRequest object + */ +export function callPrebidCacheHook(fn, auctionInstance, bidResponse, afterBidAdded, bidderRequest) { + let videoConfig = utils.deepAccess(bidderRequest, 'mediaTypes.video'); + if (videoConfig && videoConfig.context === ADPOD) { + let brandCategoryExclusion = config.getConfig('adpod.brandCategoryExclusion'); + let adServerCatId = utils.deepAccess(bidResponse, 'meta.adServerCatId'); + if (!adServerCatId && brandCategoryExclusion) { + utils.logWarn('Detected a bid without meta.adServerCatId while setConfig({adpod.brandCategoryExclusion}) was enabled. This bid has been rejected:', bidResponse) + afterBidAdded(); + } else { + if (config.getConfig('adpod.deferCaching') === false) { + bidCacheRegistry.addBid(bidResponse); + attachPriceIndustryDurationKeyToBid(bidResponse, brandCategoryExclusion); + + updateBidQueue(auctionInstance, bidResponse, afterBidAdded); + } else { + // generate targeting keys for bid + bidCacheRegistry.setupInitialCacheKey(bidResponse); + attachPriceIndustryDurationKeyToBid(bidResponse, brandCategoryExclusion); + + // add bid to auction + addBidToAuction(auctionInstance, bidResponse); + afterBidAdded(); + } + } + } else { + fn.call(this, auctionInstance, bidResponse, afterBidAdded, bidderRequest); + } +} + +/** + * This hook function will review the adUnit setup and verify certain required values are present in any adpod adUnits. + * If the fields are missing or incorrectly setup, the adUnit is removed from the list. + * @param {Function} fn reference to original function (used by hook logic) + * @param {Array[Object]} adUnits list of adUnits to be evaluated + * @returns {Array[Object]} list of adUnits that passed the check + */ +export function checkAdUnitSetupHook(fn, adUnits) { + let goodAdUnits = adUnits.filter(adUnit => { + let mediaTypes = utils.deepAccess(adUnit, 'mediaTypes'); + let videoConfig = utils.deepAccess(mediaTypes, 'video'); + if (videoConfig && videoConfig.context === ADPOD) { + // run check to see if other mediaTypes are defined (ie multi-format); reject adUnit if so + if (Object.keys(mediaTypes).length > 1) { + utils.logWarn(`Detected more than one mediaType in adUnitCode: ${adUnit.code} while attempting to define an 'adpod' video adUnit. 'adpod' adUnits cannot be mixed with other mediaTypes. This adUnit will be removed from the auction.`); + return false; + } + + let errMsg = `Detected missing or incorrectly setup fields for an adpod adUnit. Please review the following fields of adUnitCode: ${adUnit.code}. This adUnit will be removed from the auction.`; + + let playerSize = !!(videoConfig.playerSize && utils.isArrayOfNums(videoConfig.playerSize)); + let adPodDurationSec = !!(videoConfig.adPodDurationSec && utils.isNumber(videoConfig.adPodDurationSec) && videoConfig.adPodDurationSec > 0); + let durationRangeSec = !!(videoConfig.durationRangeSec && utils.isArrayOfNums(videoConfig.durationRangeSec) && videoConfig.durationRangeSec.every(range => range > 0)); + + if (!playerSize || !adPodDurationSec || !durationRangeSec) { + errMsg += (!playerSize) ? '\nmediaTypes.video.playerSize' : ''; + errMsg += (!adPodDurationSec) ? '\nmediaTypes.video.adPodDurationSec' : ''; + errMsg += (!durationRangeSec) ? '\nmediaTypes.video.durationRangeSec' : ''; + utils.logWarn(errMsg); + return false; + } + } + return true; + }); + adUnits = goodAdUnits; + fn.call(this, adUnits); +} + +/** + * This check evaluates the incoming bid's `video.durationSeconds` field and tests it against specific logic depending on adUnit config. Summary of logic below: + * when adUnit.mediaTypes.video.requireExactDuration is true + * - only bids that exactly match those listed values are accepted (don't round at all). + * - populate the `bid.video.durationBucket` field with the matching duration value + * when adUnit.mediaTypes.video.requireExactDuration is false + * - round the duration to the next highest specified duration value based on adunit. If the duration is above a range within a set buffer, that bid falls down into that bucket. + * (eg if range was [5, 15, 30] -> 2s is rounded to 5s; 17s is rounded back to 15s; 18s is rounded up to 30s) + * - if the bid is above the range of the listed durations (and outside the buffer), reject the bid + * - set the rounded duration value in the `bid.video.durationBucket` field for accepted bids + * @param {Object} bidderRequest copy of the bidderRequest object associated to bidResponse + * @param {Object} bidResponse incoming bidResponse being evaluated by bidderFactory + * @returns {boolean} return false if bid duration is deemed invalid as per adUnit configuration; return true if fine +*/ +function checkBidDuration(bidderRequest, bidResponse) { + const buffer = 2; + let bidDuration = utils.deepAccess(bidResponse, 'video.durationSeconds'); + let videoConfig = utils.deepAccess(bidderRequest, 'mediaTypes.video'); + let adUnitRanges = videoConfig.durationRangeSec; + adUnitRanges.sort((a, b) => a - b); // ensure the ranges are sorted in numeric order + + if (!videoConfig.requireExactDuration) { + let max = Math.max(...adUnitRanges); + if (bidDuration <= (max + buffer)) { + let nextHighestRange = find(adUnitRanges, range => (range + buffer) >= bidDuration); + bidResponse.video.durationBucket = nextHighestRange; + } else { + utils.logWarn(`Detected a bid with a duration value outside the accepted ranges specified in adUnit.mediaTypes.video.durationRangeSec. Rejecting bid: `, bidResponse); + return false; + } + } else { + if (find(adUnitRanges, range => range === bidDuration)) { + bidResponse.video.durationBucket = bidDuration; + } else { + utils.logWarn(`Detected a bid with a duration value not part of the list of accepted ranges specified in adUnit.mediaTypes.video.durationRangeSec. Exact match durations must be used for this adUnit. Rejecting bid: `, bidResponse); + return false; + } + } + return true; +} + +/** + * This hooked function evaluates an adpod bid and determines if the required fields are present. + * If it's found to not be an adpod bid, it will return to original function via hook logic + * @param {Function} fn reference to original function (used by hook logic) + * @param {Object} bid incoming bid object + * @param {Object} bidRequest bidRequest object of associated bid + * @param {Object} videoMediaType copy of the `bidRequest.mediaTypes.video` object; used in original function + * @param {String} context value of the `bidRequest.mediaTypes.video.context` field; used in original function + * @returns {boolean} this return is only used for adpod bids + */ +export function checkVideoBidSetupHook(fn, bid, bidRequest, videoMediaType, context) { + if (context === ADPOD) { + let result = true; + let brandCategoryExclusion = config.getConfig('adpod.brandCategoryExclusion'); + if (brandCategoryExclusion && !utils.deepAccess(bid, 'meta.iabSubCatId')) { + result = false; + } + + if (utils.deepAccess(bid, 'video')) { + if (!utils.deepAccess(bid, 'video.context') || bid.video.context !== ADPOD) { + result = false; + } + + if (!utils.deepAccess(bid, 'video.durationSeconds') || bid.video.durationSeconds <= 0) { + result = false; + } else { + let isBidGood = checkBidDuration(bidRequest, bid); + if (!isBidGood) result = false; + } + } + + if (!config.getConfig('cache.url') && bid.vastXml && !bid.vastUrl) { + utils.logError(` + This bid contains only vastXml and will not work when a prebid cache url is not specified. + Try enabling prebid cache with pbjs.setConfig({ cache: {url: "..."} }); + `); + result = false; + }; + + fn.bail(result); + } else { + fn.call(this, bid, bidRequest, videoMediaType, context); + } +} + +/** + * This function reads the (optional) settings for the adpod as set from the setConfig() + * @param {Object} config contains the config settings for adpod module + */ +export function adpodSetConfig(config) { + if (config.bidQueueTimeDelay !== undefined) { + if (typeof config.bidQueueTimeDelay === 'number' && config.bidQueueTimeDelay > 0) { + queueTimeDelay = config.bidQueueTimeDelay; + } else { + utils.logWarn(`Detected invalid value for adpod.bidQueueTimeDelay in setConfig; must be a positive number. Using default: ${queueTimeDelay}`) + } + } + + if (config.bidQueueSizeLimit !== undefined) { + if (typeof config.bidQueueSizeLimit === 'number' && config.bidQueueSizeLimit > 0) { + queueSizeLimit = config.bidQueueSizeLimit; + } else { + utils.logWarn(`Detected invalid value for adpod.bidQueueSizeLimit in setConfig; must be a positive number. Using default: ${queueSizeLimit}`) + } + } +} +config.getConfig('adpod', config => adpodSetConfig(config.adpod)); + +/** + * This function initializes the adpod module's hooks. This is called by the corresponding adserver video module. + */ +function initAdpodHooks() { + setupBeforeHookFnOnce(callPrebidCache, callPrebidCacheHook); + setupBeforeHookFnOnce(checkAdUnitSetup, checkAdUnitSetupHook); + setupBeforeHookFnOnce(checkVideoBidSetup, checkVideoBidSetupHook); +} + +initAdpodHooks() + +/** + * + * @param {Array[Object]} bids list of 'winning' bids that need to be cached + * @param {Function} callback send the cached bids (or error) back to adserverVideoModule for further processing + }} + */ +export function callPrebidCacheAfterAuction(bids, callback) { + // will call PBC here and execute cb param to initialize player code + store(bids, function(error, cacheIds) { + if (error) { + callback(error, null); + } else { + let successfulCachedBids = []; + for (let i = 0; i < cacheIds.length; i++) { + if (cacheIds[i] !== '') { + successfulCachedBids.push(bids[i]); + } + } + callback(null, successfulCachedBids); + } + }) +} + +/** + * Compare function to be used in sorting long-form bids. This will compare bids on price per second. + * @param {Object} bid + * @param {Object} bid + */ +export function sortByPricePerSecond(a, b) { + if (a.adserverTargeting[CONSTANTS.TARGETING_KEYS.PRICE_BUCKET] / a.video.durationBucket < b.adserverTargeting[CONSTANTS.TARGETING_KEYS.PRICE_BUCKET] / b.video.durationBucket) { + return 1; + } + if (a.adserverTargeting[CONSTANTS.TARGETING_KEYS.PRICE_BUCKET] / a.video.durationBucket > b.adserverTargeting[CONSTANTS.TARGETING_KEYS.PRICE_BUCKET] / b.video.durationBucket) { + return -1; + } + return 0; +} + +/** + * This function returns targeting keyvalue pairs for long-form adserver modules. Freewheel and GAM are currently supporting Prebid long-form + * @param {Object} options + * @param {Array[string]} codes + * @param {function} callback + * @returns targeting kvs for adUnitCodes + */ +export function getTargeting({codes, callback} = {}) { + if (!callback) { + utils.logError('No callback function was defined in the getTargeting call. Aborting getTargeting().'); + return; + } + codes = codes || []; + const adPodAdUnits = getAdPodAdUnits(codes); + const bidsReceived = auctionManager.getBidsReceived(); + const competiveExclusionEnabled = config.getConfig('adpod.brandCategoryExclusion'); + const deferCachingSetting = config.getConfig('adpod.deferCaching'); + const deferCachingEnabled = (typeof deferCachingSetting === 'boolean') ? deferCachingSetting : true; + + let bids = getBidsForAdpod(bidsReceived, adPodAdUnits); + bids = (competiveExclusionEnabled || deferCachingEnabled) ? getExclusiveBids(bids) : bids; + bids.sort(sortByPricePerSecond); + + let targeting = {}; + if (deferCachingEnabled === false) { + adPodAdUnits.forEach((adUnit) => { + let adPodTargeting = []; + let adPodDurationSeconds = utils.deepAccess(adUnit, 'mediaTypes.video.adPodDurationSec'); + + bids + .filter((bid) => bid.adUnitCode === adUnit.code) + .forEach((bid, index, arr) => { + if (bid.video.durationBucket <= adPodDurationSeconds) { + adPodTargeting.push({ + [TARGETING_KEY_PB_CAT_DUR]: bid.adserverTargeting[TARGETING_KEY_PB_CAT_DUR] + }); + adPodDurationSeconds -= bid.video.durationBucket; + } + if (index === arr.length - 1 && adPodTargeting.length > 0) { + adPodTargeting.push({ + [TARGETING_KEY_CACHE_ID]: bid.adserverTargeting[TARGETING_KEY_CACHE_ID] + }); + } + }); + targeting[adUnit.code] = adPodTargeting; + }); + + callback(null, targeting); + } else { + let bidsToCache = []; + adPodAdUnits.forEach((adUnit) => { + let adPodDurationSeconds = utils.deepAccess(adUnit, 'mediaTypes.video.adPodDurationSec'); + + bids + .filter((bid) => bid.adUnitCode === adUnit.code) + .forEach((bid) => { + if (bid.video.durationBucket <= adPodDurationSeconds) { + bidsToCache.push(bid); + adPodDurationSeconds -= bid.video.durationBucket; + } + }); + }); + + callPrebidCacheAfterAuction(bidsToCache, function(error, bidsSuccessfullyCached) { + if (error) { + callback(error, null); + } else { + let groupedBids = utils.groupBy(bidsSuccessfullyCached, 'adUnitCode'); + Object.keys(groupedBids).forEach((adUnitCode) => { + let adPodTargeting = []; + + groupedBids[adUnitCode].forEach((bid, index, arr) => { + adPodTargeting.push({ + [TARGETING_KEY_PB_CAT_DUR]: bid.adserverTargeting[TARGETING_KEY_PB_CAT_DUR] + }); + + if (index === arr.length - 1 && adPodTargeting.length > 0) { + adPodTargeting.push({ + [TARGETING_KEY_CACHE_ID]: bid.adserverTargeting[TARGETING_KEY_CACHE_ID] + }); + } + }); + targeting[adUnitCode] = adPodTargeting; + }); + + callback(null, targeting); + } + }); + } + return targeting; +} + +/** + * This function returns the adunit of mediaType adpod + * @param {Array} codes adUnitCodes + * @returns {Array[Object]} adunits of mediaType adpod + */ +function getAdPodAdUnits(codes) { + return auctionManager.getAdUnits() + .filter((adUnit) => utils.deepAccess(adUnit, 'mediaTypes.video.context') === ADPOD) + .filter((adUnit) => (codes.length > 0) ? codes.indexOf(adUnit.code) != -1 : true); +} + +/** + * This function removes bids of same category. It will be used when competitive exclusion is enabled. + * @param {Array[Object]} bidsReceived + * @returns {Array[Object]} unique category bids + */ +function getExclusiveBids(bidsReceived) { + let bids = bidsReceived + .map((bid) => Object.assign({}, bid, {[TARGETING_KEY_PB_CAT_DUR]: bid.adserverTargeting[TARGETING_KEY_PB_CAT_DUR]})); + bids = utils.groupBy(bids, TARGETING_KEY_PB_CAT_DUR); + let filteredBids = []; + Object.keys(bids).forEach((targetingKey) => { + bids[targetingKey].sort(utils.compareOn('responseTimestamp')); + filteredBids.push(bids[targetingKey][0]); + }); + return filteredBids; +} + +/** + * This function returns bids for adpod adunits + * @param {Array[Object]} bidsReceived + * @param {Array[Object]} adPodAdUnits + * @returns {Array[Object]} bids of mediaType adpod + */ +function getBidsForAdpod(bidsReceived, adPodAdUnits) { + let adUnitCodes = adPodAdUnits.map((adUnit) => adUnit.code); + return bidsReceived + .filter((bid) => adUnitCodes.indexOf(bid.adUnitCode) != -1 && (bid.video && bid.video.context === ADPOD)) +} + +const sharedMethods = { + TARGETING_KEY_PB_CAT_DUR: TARGETING_KEY_PB_CAT_DUR, + TARGETING_KEY_CACHE_ID: TARGETING_KEY_CACHE_ID, + 'getTargeting': getTargeting +} +Object.freeze(sharedMethods); + +module('adpod', function shareAdpodUtilities(...args) { + if (!utils.isPlainObject(args[0])) { + utils.logError('Adpod module needs plain object to share methods with submodule'); + return; + } + function addMethods(object, func) { + for (let name in func) { + object[name] = func[name]; + } + } + addMethods(args[0], sharedMethods); +}); diff --git a/modules/adponeBidAdapter.js b/modules/adponeBidAdapter.js new file mode 100644 index 00000000000..1b6cd1cc589 --- /dev/null +++ b/modules/adponeBidAdapter.js @@ -0,0 +1,96 @@ +import {BANNER} from '../src/mediaTypes'; +import {registerBidder} from '../src/adapters/bidderFactory'; + +const ADPONE_CODE = 'adpone'; +const ADPONE_ENDPOINT = 'https://rtb.adpone.com/bid-request'; +const ADPONE_SYNC_ENDPOINT = 'https://eu-ads.adpone.com'; +const ADPONE_REQUEST_METHOD = 'POST'; +const ADPONE_CURRENCY = 'EUR'; + +function _createSync() { + return { + type: 'iframe', + url: ADPONE_SYNC_ENDPOINT + } +} + +function getUserSyncs(syncOptions) { + return (syncOptions && syncOptions.iframeEnabled) ? _createSync() : ([]); +} + +export const spec = { + code: ADPONE_CODE, + supportedMediaTypes: [BANNER], + + getUserSyncs, + + isBidRequestValid: bid => { + return !!bid.params.placementId && !!bid.bidId && bid.bidder === 'adpone' + }, + + buildRequests: bidRequests => { + return bidRequests.map(bid => { + const url = ADPONE_ENDPOINT + '?pid=' + bid.params.placementId; + const data = { + at: 1, + id: bid.bidId, + imp: bid.sizes.map((size, index) => ( + { + id: bid.bidId + '_' + index, + banner: { + w: size[0], + h: size[1] + } + })) + }; + + const options = { + withCredentials: true + }; + + return { + method: ADPONE_REQUEST_METHOD, + url, + data, + options, + }; + }); + }, + + interpretResponse: (serverResponse, bidRequest) => { + if (!serverResponse || !serverResponse.body) { + return []; + } + + let answer = []; + + serverResponse.body.seatbid.forEach(seatbid => { + if (seatbid.bid.length) { + answer = [...answer, ...seatbid.bid.filter(bid => bid.price > 0).map(bid => ({ + id: bid.id, + requestId: bidRequest.data.id, + cpm: bid.price, + ad: bid.adm, + width: bid.w || 0, + height: bid.h || 0, + currency: serverResponse.body.cur || ADPONE_CURRENCY, + netRevenue: true, + ttl: 300, + creativeId: bid.crid || 0 + }))]; + } + }); + + return answer; + }, + + onBidWon: bid => { + const bidString = JSON.stringify(bid); + const encodedBuf = window.btoa(bidString); + const img = new Image(1, 1); + img.src = `https://rtb.adpone.com/prebid/analytics?q=${encodedBuf}`; + }, + +}; + +registerBidder(spec); diff --git a/modules/adponeBidAdapter.md b/modules/adponeBidAdapter.md new file mode 100644 index 00000000000..575b1620bac --- /dev/null +++ b/modules/adponeBidAdapter.md @@ -0,0 +1,32 @@ +# Overview + +Module Name: Adpone Bidder Adapter + +Module Type: Bidder Adapter + +Maintainer: tech@adpone.com + +# Description + +You can use this adapter to get a bid from adpone.com. + +About us : https://www.adpone.com + + +# Test Parameters +```javascript + var adUnits = [ + { + code: 'div-adpone-example', + sizes: [[300, 250]], + bids: [ + { + bidder: "adpone", + params: { + placementId: "1234" + } + } + ] + } + ]; +``` diff --git a/modules/adspendBidAdapter.js b/modules/adspendBidAdapter.js new file mode 100644 index 00000000000..7818e3fc910 --- /dev/null +++ b/modules/adspendBidAdapter.js @@ -0,0 +1,165 @@ +import * as utils from '../src/utils'; +import { ajax } from '../src/ajax' +import { config } from '../src/config'; +import { registerBidder } from '../src/adapters/bidderFactory'; +import { BANNER } from '../src/mediaTypes'; + +const BIDDER_CODE = 'adspend'; +const BID_URL = '//rtb.com.ru/headerbidding-bid'; +const SYNC_URL = '//rtb.com.ru/headerbidding-sync?uid={UUID}'; +const COOKIE_NAME = 'hb-adspend-id'; +const UUID_LEN = 36; +const TTL = 10000; +const RUB = 'RUB'; +const FIRST_PRICE = 1; +const NET_REVENUE = true; + +const winEventURLs = {}; +const placementToBidMap = {}; + +export const spec = { + code: BIDDER_CODE, + aliases: ['as'], + supportedMediaTypes: [BANNER], + + onBidWon: function(winObj) { + const requestId = winObj.requestId; + const cpm = winObj.cpm; + const event = winEventURLs[requestId].replace( + /\$\{AUCTION_PRICE\}/, + cpm + ); + + ajax(event, null); + }, + + isBidRequestValid: function(bid) { + const adServerCur = config.getConfig('currency.adServerCurrency') === RUB; + + return !!(adServerCur && + bid.params && + bid.params.bidfloor && + bid.crumbs.pubcid && + utils.checkCookieSupport() && + utils.cookiesAreEnabled() + ); + }, + + buildRequests: function(bidRequests, bidderRequest) { + const req = bidRequests[Math.floor(Math.random() * bidRequests.length)]; + const bidId = req.bidId; + const at = FIRST_PRICE; + const site = { id: req.crumbs.pubcid, domain: document.domain }; + const device = { ua: navigator.userAgent, ip: '' }; + const user = { id: getUserID() } + const cur = [ RUB ]; + const tmax = bidderRequest.timeout; + + const imp = bidRequests.map(req => { + const params = req.params; + + const tagId = params.tagId; + const id = params.placement; + const banner = { 'format': getFormats(req.sizes) }; + const bidfloor = params.bidfloor !== undefined + ? Number(params.bidfloor) : 1; + const bidfloorcur = RUB; + + placementToBidMap[id] = bidId; + + return { + id, + tagId, + banner, + bidfloor, + bidfloorcur, + secure: 0, + }; + }); + + const payload = { + bidId, + at, + site, + device, + user, + imp, + cur, + tmax, + }; + + return { + method: 'POST', + url: BID_URL, + data: JSON.stringify(payload), + }; + }, + + interpretResponse: function(resp, {bidderRequest}) { + if (resp.body === '') return []; + + const bids = resp.body.seatbid[0].bid.map(bid => { + const cpm = bid.price; + const impid = bid.impid; + const requestId = placementToBidMap[impid]; + const width = bid.w; + const height = bid.h; + const creativeId = bid.adid; + const dealId = bid.dealid; + const currency = resp.body.cur; + const netRevenue = NET_REVENUE; + const ttl = TTL; + const ad = bid.adm; + + return { + cpm, + requestId, + width, + height, + creativeId, + dealId, + currency, + netRevenue, + ttl, + ad, + }; + }); + + return bids; + }, + + getUserSyncs: function(syncOptions, resps) { + let syncs = []; + + resps.forEach(resp => { + if (syncOptions.pixelEnabled && resp.body === '') { + const uuid = getUserID(); + syncs.push({ + type: 'image', + url: SYNC_URL.replace('{UUID}', uuid), + }); + } + }); + + return syncs + } +} + +const getUserID = () => { + const i = document.cookie.indexOf(COOKIE_NAME); + + if (i === -1) { + const uuid = utils.generateUUID(); + document.cookie = `${COOKIE_NAME}=${uuid}; path=/`; + return uuid; + } + + const j = i + COOKIE_NAME.length + 1; + return document.cookie.substring(j, j + UUID_LEN); +}; + +const getFormats = arr => arr.map((s) => { + return { w: s[0], h: s[1] }; +}); + +registerBidder(spec); diff --git a/modules/adspendBidAdapter.md b/modules/adspendBidAdapter.md new file mode 100644 index 00000000000..dc3409b0057 --- /dev/null +++ b/modules/adspendBidAdapter.md @@ -0,0 +1,60 @@ +# Overview + +``` +Module Name: AdSpend Bidder Adapter +Module Type: Bidder Adapter +Maintainer: gaffoonster@gmail.com +``` + +# Description + +Connects to AdSpend bidder. +AdSpend adapter supports only Banner at the moment. Video and Native will be add soon. + +# Test Parameters +``` +var adUnits = [ + // Banner + { + code: 'div-gpt-ad-1460505748561-0', + mediaTypes: { + banner: { + // You can choose one of them + sizes: [ + [300, 250], + [300, 600], + [240, 400], + [728, 90], + ] + } + }, + bids: [ + { + bidder: "adspend", + params: { + bidfloor: 1, + placement: 'test', + tagId: 'test-ad', + } + } + ] + } +]; + +pbjs.que.push(() => { + pbjs.setConfig({ + userSync: { + syncEnabled: true, + enabledBidders: ['adspend'], + pixelEnabled: true, + syncsPerBidder: 200, + syncDelay: 100, + }, + currency: { + adServerCurrency: 'RUB' // We work only with rubles for now + } + }); +}); +``` + +**It's a test banner, so you'll see some errors in console cause it will be trying to call our system's events.** diff --git a/modules/adspiritBidAdapter.js b/modules/adspiritBidAdapter.js index eeff89923ca..a428a5c8829 100644 --- a/modules/adspiritBidAdapter.js +++ b/modules/adspiritBidAdapter.js @@ -1,5 +1,5 @@ -import * as utils from 'src/utils'; -import {registerBidder} from 'src/adapters/bidderFactory'; +import * as utils from '../src/utils'; +import {registerBidder} from '../src/adapters/bidderFactory'; const RTB_URL = '/rtb/getbid.php?rtbprovider=prebid'; const SCRIPT_URL = '/adasync.min.js'; export const spec = { diff --git a/modules/adtelligentBidAdapter.js b/modules/adtelligentBidAdapter.js index ed7da360e0b..0db8394ee0f 100644 --- a/modules/adtelligentBidAdapter.js +++ b/modules/adtelligentBidAdapter.js @@ -1,7 +1,7 @@ -import * as utils from 'src/utils'; -import {registerBidder} from 'src/adapters/bidderFactory'; -import {VIDEO, BANNER} from 'src/mediaTypes'; -import {Renderer} from 'src/Renderer'; +import * as utils from '../src/utils'; +import {registerBidder} from '../src/adapters/bidderFactory'; +import {VIDEO, BANNER} from '../src/mediaTypes'; +import {Renderer} from '../src/Renderer'; import findIndex from 'core-js/library/fn/array/find-index'; const URL = '//hb.adtelligent.com/auction/'; @@ -12,11 +12,50 @@ const DISPLAY = 'display'; export const spec = { code: BIDDER_CODE, + aliases: ['onefiftytwomedia'], supportedMediaTypes: [VIDEO, BANNER], isBidRequestValid: function (bid) { return bid && bid.params && bid.params.aid; }, + getUserSyncs: function (syncOptions, serverResponses) { + var syncs = []; + + function addSyncs(bid) { + const uris = bid.cookieURLs; + const types = bid.cookieURLSTypes || []; + + if (uris && uris.length) { + uris.forEach((uri, i) => { + let type = types[i] || 'image'; + + if ((!syncOptions.pixelEnabled && type == 'image') || + (!syncOptions.iframeEnabled && type == 'iframe')) { + return; + } + + syncs.push({ + type: type, + url: uri + }) + }) + } + } + if (syncOptions.pixelEnabled || syncOptions.iframeEnabled) { + serverResponses && serverResponses.length && serverResponses.forEach((response) => { + if (response.body) { + if (utils.isArray(response.body)) { + response.body.forEach(b => { + addSyncs(b); + }) + } else { + addSyncs(response.body) + } + } + }) + } + return syncs; + }, /** * Make a server request from the list of BidRequests * @param bidRequests @@ -24,7 +63,7 @@ export const spec = { */ buildRequests: function (bidRequests, bidderRequest) { return { - data: bidToTag(bidRequests), + data: bidToTag(bidRequests, bidderRequest), bidderRequest, method: 'GET', url: URL @@ -82,11 +121,16 @@ function parseRTBResponse(serverResponse, bidderRequest) { return bids; } -function bidToTag(bidRequests) { +function bidToTag(bidRequests, bidderRequest) { let tag = { domain: utils.getTopWindowLocation().hostname }; + if (bidderRequest && bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies) { + tag.gdpr = 1; + tag.gdpr_consent = bidderRequest.gdprConsent.consentString; + } + for (let i = 0, length = bidRequests.length; i < length; i++) { Object.assign(tag, prepareRTBRequestParams(i, bidRequests[i])); } diff --git a/modules/aduptechBidAdapter.js b/modules/aduptechBidAdapter.js index d2a53f0718c..2ee2bcad3c7 100644 --- a/modules/aduptechBidAdapter.js +++ b/modules/aduptechBidAdapter.js @@ -1,10 +1,10 @@ -import * as utils from 'src/utils'; -import { registerBidder } from 'src/adapters/bidderFactory'; -import { BANNER } from 'src/mediaTypes' +import * as utils from '../src/utils'; +import { registerBidder } from '../src/adapters/bidderFactory'; +import { BANNER } from '../src/mediaTypes' export const BIDDER_CODE = 'aduptech'; export const PUBLISHER_PLACEHOLDER = '{PUBLISHER}'; -export const ENDPOINT_URL = window.location.protocol + '//rtb.d.adup-tech.com/prebid/' + PUBLISHER_PLACEHOLDER + '_bid'; +export const ENDPOINT_URL = window.originalLocation.protocol + '//rtb.d.adup-tech.com/prebid/' + PUBLISHER_PLACEHOLDER + '_bid'; export const ENDPOINT_METHOD = 'POST'; export const spec = { diff --git a/modules/advangelistsBidAdapter.js b/modules/advangelistsBidAdapter.js new file mode 100644 index 00000000000..e98de8dd77e --- /dev/null +++ b/modules/advangelistsBidAdapter.js @@ -0,0 +1,379 @@ +import * as utils from '../src/utils'; +import { parse as parseUrl } from '../src/url'; +import { config } from '../src/config'; +import { registerBidder } from '../src/adapters/bidderFactory'; +import { VIDEO, BANNER } from '../src/mediaTypes'; +import find from 'core-js/library/fn/array/find'; +import includes from 'core-js/library/fn/array/includes'; + +const ADAPTER_VERSION = '1.0'; +const BIDDER_CODE = 'advangelists'; + +export const VIDEO_ENDPOINT = '//nep.advangelists.com/xp/get?pubid=';// 0cf8d6d643e13d86a5b6374148a4afac'; +export const BANNER_ENDPOINT = '//nep.advangelists.com/xp/get?pubid=';// 0cf8d6d643e13d86a5b6374148a4afac'; +export const OUTSTREAM_SRC = '//player-cdn.beachfrontmedia.com/playerapi/loader/outstream.js'; +export const VIDEO_TARGETING = ['mimes', 'playbackmethod', 'maxduration', 'skip']; +export const DEFAULT_MIMES = ['video/mp4', 'application/javascript']; + +let pubid = ''; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO], + + isBidRequestValid(bidRequest) { + if (typeof bidRequest != 'undefined') { + if (bidRequest.bidder !== BIDDER_CODE && typeof bidRequest.params === 'undefined') { return false; } + if (bidRequest === '' || bidRequest.params.placement === '' || bidRequest.params.pubid === '') { return false; } + return true; + } else { return false; } + }, + + buildRequests(bids, bidderRequest) { + let requests = []; + let videoBids = bids.filter(bid => isVideoBidValid(bid)); + let bannerBids = bids.filter(bid => isBannerBidValid(bid)); + videoBids.forEach(bid => { + pubid = getVideoBidParam(bid, 'pubid'); + requests.push({ + method: 'POST', + url: VIDEO_ENDPOINT + pubid, + data: createVideoRequestData(bid, bidderRequest), + bidRequest: bid + }); + }); + + bannerBids.forEach(bid => { + pubid = getBannerBidParam(bid, 'pubid'); + requests.push({ + method: 'POST', + url: BANNER_ENDPOINT + pubid, + data: createBannerRequestData(bid, bidderRequest), + bidRequest: bid + }); + }); + return requests; + }, + + interpretResponse(serverResponse, {bidRequest}) { + let response = serverResponse.body; + if (response !== null && utils.isEmpty(response) == false) { + if (isVideoBid(bidRequest)) { + let bidResponse = { + requestId: response.id, + bidderCode: BIDDER_CODE, + cpm: response.seatbid[0].bid[0].price, + width: response.seatbid[0].bid[0].w, + height: response.seatbid[0].bid[0].h, + ttl: response.seatbid[0].bid[0].ttl || 60, + creativeId: response.seatbid[0].bid[0].crid, + currency: response.cur, + mediaType: VIDEO, + netRevenue: true + } + + if (response.seatbid[0].bid[0].adm) { + bidResponse.vastXml = response.seatbid[0].bid[0].adm; + bidResponse.adResponse = { + content: response.seatbid[0].bid[0].adm + }; + } else { + bidResponse.vastUrl = response.seatbid[0].bid[0].nurl; + } + + return bidResponse; + } else { + return { + requestId: response.id, + bidderCode: BIDDER_CODE, + cpm: response.seatbid[0].bid[0].price, + width: response.seatbid[0].bid[0].w, + height: response.seatbid[0].bid[0].h, + ad: response.seatbid[0].bid[0].adm, + ttl: response.seatbid[0].bid[0].ttl || 60, + creativeId: response.seatbid[0].bid[0].crid, + currency: response.cur, + mediaType: BANNER, + netRevenue: true + } + } + } + } +}; + +function isBannerBid(bid) { + return utils.deepAccess(bid, 'mediaTypes.banner') || !isVideoBid(bid); +} + +function isVideoBid(bid) { + return utils.deepAccess(bid, 'mediaTypes.video'); +} + +function isVideoBidValid(bid) { + return isVideoBid(bid) && getVideoBidParam(bid, 'pubid') && getVideoBidParam(bid, 'placement'); +} + +function isBannerBidValid(bid) { + return isBannerBid(bid) && getBannerBidParam(bid, 'pubid') && getBannerBidParam(bid, 'placement'); +} + +function getVideoBidParam(bid, key) { + return utils.deepAccess(bid, 'params.video.' + key) || utils.deepAccess(bid, 'params.' + key); +} + +function getBannerBidParam(bid, key) { + return utils.deepAccess(bid, 'params.banner.' + key) || utils.deepAccess(bid, 'params.' + key); +} + +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 getDoNotTrack() { + return navigator.doNotTrack === '1' || window.doNotTrack === '1' || navigator.msDoNoTrack === '1' || navigator.doNotTrack === 'yes'; +} + +function findAndFillParam(o, key, value) { + try { + if (typeof value === 'function') { + o[key] = value(); + } else { + o[key] = value; + } + } catch (ex) {} +} + +function getOsVersion() { + let clientStrings = [ + { s: 'Android', r: /Android/ }, + { s: 'iOS', r: /(iPhone|iPad|iPod)/ }, + { s: 'Mac OS X', r: /Mac OS X/ }, + { s: 'Mac OS', r: /(MacPPC|MacIntel|Mac_PowerPC|Macintosh)/ }, + { s: 'Linux', r: /(Linux|X11)/ }, + { s: 'Windows 10', r: /(Windows 10.0|Windows NT 10.0)/ }, + { s: 'Windows 8.1', r: /(Windows 8.1|Windows NT 6.3)/ }, + { s: 'Windows 8', r: /(Windows 8|Windows NT 6.2)/ }, + { s: 'Windows 7', r: /(Windows 7|Windows NT 6.1)/ }, + { s: 'Windows Vista', r: /Windows NT 6.0/ }, + { s: 'Windows Server 2003', r: /Windows NT 5.2/ }, + { s: 'Windows XP', r: /(Windows NT 5.1|Windows XP)/ }, + { s: 'UNIX', r: /UNIX/ }, + { s: 'Search Bot', r: /(nuhk|Googlebot|Yammybot|Openbot|Slurp|MSNBot|Ask Jeeves\/Teoma|ia_archiver)/ } + ]; + let cs = find(clientStrings, cs => cs.r.test(navigator.userAgent)); + return cs ? cs.s : 'unknown'; +} + +function getFirstSize(sizes) { + return (sizes && sizes.length) ? sizes[0] : { w: undefined, h: undefined }; +} + +function parseSizes(sizes) { + return utils.parseSizesInput(sizes).map(size => { + let [ width, height ] = size.split('x'); + return { + w: parseInt(width, 10) || undefined, + h: parseInt(height, 10) || undefined + }; + }); +} + +function getVideoSizes(bid) { + return parseSizes(utils.deepAccess(bid, 'mediaTypes.video.playerSize') || bid.sizes); +} + +function getBannerSizes(bid) { + return parseSizes(utils.deepAccess(bid, 'mediaTypes.banner.sizes') || bid.sizes); +} + +function getTopWindowReferrer() { + try { + return window.top.document.referrer; + } catch (e) { + return ''; + } +} + +function getVideoTargetingParams(bid) { + return Object.keys(Object(bid.params.video)) + .filter(param => includes(VIDEO_TARGETING, param)) + .reduce((obj, param) => { + obj[ param ] = bid.params.video[ param ]; + return obj; + }, {}); +} + +function createVideoRequestData(bid, bidderRequest) { + let topLocation = getTopWindowLocation(bidderRequest); + let topReferrer = getTopWindowReferrer(); + + let sizes = getVideoSizes(bid); + let firstSize = getFirstSize(sizes); + + let video = getVideoTargetingParams(bid); + const o = { + 'device': { + 'langauge': (global.navigator.language).split('-')[0], + 'dnt': (global.navigator.doNotTrack === 1 ? 1 : 0), + 'devicetype': isMobile() ? 4 : isConnectedTV() ? 3 : 2, + 'js': 1, + 'os': getOsVersion() + }, + 'at': 2, + 'site': {}, + 'tmax': 3000, + 'cur': ['USD'], + 'id': bid.bidId, + 'imp': [], + 'regs': { + 'ext': { + } + }, + 'user': { + 'ext': { + } + } + }; + + o.site['page'] = topLocation.href; + o.site['domain'] = topLocation.hostname; + o.site['search'] = topLocation.search; + o.site['domain'] = topLocation.hostname; + o.site['ref'] = topReferrer; + o.site['mobile'] = isMobile() ? 1 : 0; + const secure = topLocation.protocol.indexOf('https') === 0 ? 1 : 0; + + o.device['dnt'] = getDoNotTrack() ? 1 : 0; + + findAndFillParam(o.site, 'name', function() { + return global.top.document.title; + }); + + findAndFillParam(o.device, 'h', function() { + return global.screen.height; + }); + findAndFillParam(o.device, 'w', function() { + return global.screen.width; + }); + + let placement = getVideoBidParam(bid, 'placement'); + + for (let j = 0; j < sizes.length; j++) { + o.imp.push({ + 'id': '' + j, + 'displaymanager': '' + BIDDER_CODE, + 'displaymanagerver': '' + ADAPTER_VERSION, + 'tagId': placement, + 'bidfloor': 2.0, + 'bidfloorcur': 'USD', + 'secure': secure, + 'video': Object.assign({ + 'id': utils.generateUUID(), + 'pos': 0, + 'w': firstSize.w, + 'h': firstSize.h, + 'mimes': DEFAULT_MIMES + }, video) + + }); + } + + if (bidderRequest && bidderRequest.gdprConsent) { + let { gdprApplies, consentString } = bidderRequest.gdprConsent; + o.regs.ext = {'gdpr': gdprApplies ? 1 : 0}; + o.user.ext = {'consent': consentString}; + } + + return o; +} + +function getTopWindowLocation(bidderRequest) { + let url = bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.referer; + return parseUrl(config.getConfig('pageUrl') || url, { decodeSearchAsString: true }); +} + +function createBannerRequestData(bid, bidderRequest) { + let topLocation = getTopWindowLocation(bidderRequest); + let topReferrer = getTopWindowReferrer(); + + let sizes = getBannerSizes(bid); + + const o = { + 'device': { + 'langauge': (global.navigator.language).split('-')[0], + 'dnt': (global.navigator.doNotTrack === 1 ? 1 : 0), + 'devicetype': isMobile() ? 4 : isConnectedTV() ? 3 : 2, + 'js': 1 + }, + 'at': 2, + 'site': {}, + 'tmax': 3000, + 'cur': ['USD'], + 'id': bid.bidId, + 'imp': [], + 'regs': { + 'ext': { + } + }, + 'user': { + 'ext': { + } + } + }; + + o.site['page'] = topLocation.href; + o.site['domain'] = topLocation.hostname; + o.site['search'] = topLocation.search; + o.site['domain'] = topLocation.hostname; + o.site['ref'] = topReferrer; + o.site['mobile'] = isMobile() ? 1 : 0; + const secure = topLocation.protocol.indexOf('https') === 0 ? 1 : 0; + + o.device['dnt'] = getDoNotTrack() ? 1 : 0; + + findAndFillParam(o.site, 'name', function() { + return global.top.document.title; + }); + + findAndFillParam(o.device, 'h', function() { + return global.screen.height; + }); + findAndFillParam(o.device, 'w', function() { + return global.screen.width; + }); + + let placement = getBannerBidParam(bid, 'placement'); + for (let j = 0; j < sizes.length; j++) { + let size = sizes[j]; + + o.imp.push({ + 'id': '' + j, + 'displaymanager': '' + BIDDER_CODE, + 'displaymanagerver': '' + ADAPTER_VERSION, + 'tagId': placement, + 'bidfloor': 2.0, + 'bidfloorcur': 'USD', + 'secure': secure, + 'banner': { + 'id': utils.generateUUID(), + 'pos': 0, + 'w': size['w'], + 'h': size['h'] + } + }); + } + + if (bidderRequest && bidderRequest.gdprConsent) { + let { gdprApplies, consentString } = bidderRequest.gdprConsent; + o.regs.ext = {'gdpr': gdprApplies ? 1 : 0}; + o.user.ext = {'consent': consentString}; + } + + return o; +} + +registerBidder(spec); diff --git a/modules/advangelistsBidAdapter.md b/modules/advangelistsBidAdapter.md new file mode 100644 index 00000000000..1765241eaf3 --- /dev/null +++ b/modules/advangelistsBidAdapter.md @@ -0,0 +1,65 @@ +# Overview + +``` +Module Name: Advangelists Bidder Adapter +Module Type: Bidder Adapter +Maintainer: lokesh@advangelists.com +``` + +# Description + +Connects to Advangelists exchange for bids. + +Advangelists bid adapter supports Banner and Video ads currently. + +For more informatio + +# Sample Display Ad Unit: For Publishers +```javascript +var displayAdUnit = [ +{ + code: 'display', + sizes: [ + [300, 250], + [320, 50] + ], + bids: [{ + bidder: 'advangelists', + params: { + pubid: '0cf8d6d643e13d86a5b6374148a4afac', + placement: 1234 + } + }] +}]; +``` + +# Sample Video Ad Unit: For Publishers +```javascript + +var videoAdUnit = { + code: 'video', + sizes: [320,480], + mediaTypes: { + video: { + playerSize : [[320, 480]], + context: 'instream' + } + }, + bids: [ + { + bidder: 'advangelists', + params: { + pubid: '8537f00948fc37cc03c5f0f88e198a76', + placement: 1234, + video: { + id: 123, + skip: 1, + mimes : ['video/mp4', 'application/javascript'], + playbackmethod : [2,6], + maxduration: 30 + } + } + } + ] + }; +``` \ No newline at end of file diff --git a/modules/advenueBidAdapter.js b/modules/advenueBidAdapter.js new file mode 100644 index 00000000000..6dc5856eacb --- /dev/null +++ b/modules/advenueBidAdapter.js @@ -0,0 +1,86 @@ +import { registerBidder } from '../src/adapters/bidderFactory'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes'; +import * as utils from '../src/utils'; + +const BIDDER_CODE = 'advenue'; +const URL_MULTI = '//ssp.advenuemedia.co.uk/?c=o&m=multi'; + +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.bidId && + bid.params && + !isNaN(bid.params.placementId) && + spec.supportedMediaTypes.indexOf(bid.params.traffic) !== -1 + ); + }, + + /** + * 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) => { + let winTop; + try { + winTop = window.top; + winTop.location.toString(); + } catch (e) { + utils.logMessage(e); + winTop = window; + }; + + const location = bidderRequest ? new URL(bidderRequest.refererInfo.referer) : winTop.location; + const placements = []; + const request = { + 'secure': (location.protocol === 'https:') ? 1 : 0, + 'deviceWidth': winTop.screen.width, + 'deviceHeight': winTop.screen.height, + 'host': location.host, + 'page': location.pathname, + 'placements': placements + }; + + for (let i = 0; i < validBidRequests.length; i++) { + const bid = validBidRequests[i]; + const params = bid.params; + placements.push({ + placementId: params.placementId, + bidId: bid.bidId, + sizes: bid.sizes, + traffic: params.traffic + }); + } + return { + method: 'POST', + url: URL_MULTI, + data: request + }; + }, + + /** + * 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) => { + try { + serverResponse = serverResponse.body; + } catch (e) { + utils.logMessage(e); + }; + return serverResponse; + }, +}; + +registerBidder(spec); diff --git a/modules/advenueBidAdapter.md b/modules/advenueBidAdapter.md new file mode 100644 index 00000000000..ec5287330db --- /dev/null +++ b/modules/advenueBidAdapter.md @@ -0,0 +1,27 @@ +# Overview + +``` +Module Name: Advenue SSP Bidder Adapter +Module Type: Bidder Adapter +Maintainer: dev.advenue@gmail.com +``` + +# Description + +Module that connects to Advenue SSP demand sources + +# Test Parameters +``` + var adUnits = [{ + code: 'placementCode', + sizes: [[300, 250]], + bids: [{ + bidder: 'advenue', + params: { + placementId: 0, + traffic: 'banner' + } + }] + } + ]; +``` diff --git a/modules/adxcgAnalyticsAdapter.js b/modules/adxcgAnalyticsAdapter.js index 5dc934a861e..0aa6df3d03d 100644 --- a/modules/adxcgAnalyticsAdapter.js +++ b/modules/adxcgAnalyticsAdapter.js @@ -1,9 +1,9 @@ -import { ajax } from 'src/ajax'; -import adapter from 'src/AnalyticsAdapter'; -import adaptermanager from 'src/adaptermanager'; -import CONSTANTS from 'src/constants.json'; -import * as url from 'src/url'; -import * as utils from 'src/utils'; +import { ajax } from '../src/ajax'; +import adapter from '../src/AnalyticsAdapter'; +import adapterManager from '../src/adapterManager'; +import CONSTANTS from '../src/constants.json'; +import * as url from '../src/url'; +import * as utils from '../src/utils'; const emptyUrl = ''; const analyticsType = 'endpoint'; @@ -158,7 +158,7 @@ adxcgAnalyticsAdapter.enableAnalytics = function (config) { adxcgAnalyticsAdapter.originEnableAnalytics(config); }; -adaptermanager.registerAnalyticsAdapter({ +adapterManager.registerAnalyticsAdapter({ adapter: adxcgAnalyticsAdapter, code: 'adxcg' }); diff --git a/modules/adxcgBidAdapter.js b/modules/adxcgBidAdapter.js index 6b95fb1d38a..34b5ea25fb0 100644 --- a/modules/adxcgBidAdapter.js +++ b/modules/adxcgBidAdapter.js @@ -1,8 +1,8 @@ -import { config } from 'src/config' -import * as utils from 'src/utils' -import * as url from 'src/url' -import { registerBidder } from 'src/adapters/bidderFactory' -import { BANNER, NATIVE, VIDEO } from 'src/mediaTypes' +import { config } from '../src/config' +import * as utils from '../src/utils' +import * as url from '../src/url' +import { registerBidder } from '../src/adapters/bidderFactory' +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes' import includes from 'core-js/library/fn/array/includes' /** @@ -10,6 +10,8 @@ import includes from 'core-js/library/fn/array/includes' * updated to latest prebid repo on 2017.10.20 * updated for gdpr compliance on 2018.05.22 -requires gdpr compliance module * updated to pass aditional auction and impression level parameters. added pass for video targeting parameters + * updated to fix native support for image width/height and icon 2019.03.17 + * updated support for userid - pubcid,ttid 2019.05.28 */ const BIDDER_CODE = 'adxcg' @@ -158,6 +160,14 @@ export const spec = { beaconParams.prebidBidIds = prebidBidIds.join(',') beaconParams.bidfloors = bidfloors.join(',') + if (utils.isStr(utils.deepAccess(validBidRequests, '0.userId.pubcid'))) { + beaconParams.pubcid = validBidRequests[0].userId.pubcid; + } + + if (utils.isStr(utils.deepAccess(validBidRequests, '0.userId.tdid'))) { + beaconParams.tdid = validBidRequests[0].userId.tdid; + } + let adxcgRequestUrl = url.format({ protocol: secure ? 'https' : 'http', hostname: secure ? 'hbps.adxcg.net' : 'hbp.adxcg.net', @@ -210,8 +220,10 @@ export const spec = { let nativeResponse = serverResponseOneItem.nativeResponse bid['native'] = { - clickUrl: encodeURIComponent(nativeResponse.link.url), - impressionTrackers: nativeResponse.imptrackers + clickUrl: nativeResponse.link.url, + impressionTrackers: nativeResponse.imptrackers, + clickTrackers: nativeResponse.clktrackers, + javascriptTrackers: nativeResponse.jstrackers } nativeResponse.assets.forEach(asset => { @@ -220,7 +232,19 @@ export const spec = { } if (asset.img && asset.img.url) { - bid['native'].image = asset.img.url + let nativeImage = {} + nativeImage.url = asset.img.url + nativeImage.height = asset.img.h + nativeImage.width = asset.img.w + bid['native'].image = nativeImage + } + + if (asset.icon && asset.icon.url) { + let nativeIcon = {} + nativeIcon.url = asset.icon.url + nativeIcon.height = asset.icon.h + nativeIcon.width = asset.icon.w + bid['native'].icon = nativeIcon } if (asset.data && asset.data.label === 'DESC' && asset.data.value) { diff --git a/modules/adxpremiumAnalyticsAdapter.js b/modules/adxpremiumAnalyticsAdapter.js new file mode 100644 index 00000000000..2224759dc6a --- /dev/null +++ b/modules/adxpremiumAnalyticsAdapter.js @@ -0,0 +1,160 @@ +import { ajax } from '../src/ajax'; +import adapter from '../src/AnalyticsAdapter'; +import adapterManager from '../src/adapterManager'; +import CONSTANTS from '../src/constants.json'; +import * as utils from '../src/utils'; + +const analyticsType = 'endpoint'; +const url = 'https://adxpremium.services/graphql'; + +// Events needed +const { + EVENTS: { + AUCTION_INIT, + BID_REQUESTED, + BID_TIMEOUT, + BID_RESPONSE, + BID_WON, + AUCTION_END + } +} = CONSTANTS; + +// Memory objects +let completeObject = { + publisher_id: null, + auction_id: null, + referer: null, + screen_resolution: window.screen.width + 'x' + window.screen.height, + device_type: null, + geo: null, + events: [] +}; + +let adxpremiumAnalyticsAdapter = Object.assign(adapter({ url, analyticsType }), { + track({ eventType, args }) { + switch (eventType) { + case AUCTION_INIT: + auctionInit(args); + break; + case BID_REQUESTED: + bidRequested(args); + break; + case BID_RESPONSE: + bidResponse(args); + break; + case BID_WON: + bidWon(args); + break; + case BID_TIMEOUT: + bidTimeout(args); + break; + case AUCTION_END: + setTimeout(function () { sendEvent(completeObject) }, 3100); + break; + default: + break; + } + } +}); + +// DFP support +let googletag = window.googletag || {}; +googletag.cmd = googletag.cmd || []; +googletag.cmd.push(function() { + googletag.pubads().addEventListener('slotRenderEnded', args => { + console.log(Date.now() + ' GOOGLE SLOT: ' + JSON.stringify(args)); + }); +}); + +// Event handlers +let bidResponsesMapper = {}; + +function auctionInit(args) { + completeObject.auction_id = args.auctionId; + completeObject.publisher_id = adxpremiumAnalyticsAdapter.initOptions.pubId; + try { completeObject.referer = args.bidderRequests[0].refererInfo.referer.split('?')[0]; } catch (e) { console.log(e.message); } + completeObject.device_type = deviceType(); +} +function bidRequested(args) { + let tmpObject = { + type: 'REQUEST', + bidder_code: args.bidderCode, + event_timestamp: args.start, + bid_gpt_codes: {} + }; + + args.bids.forEach(bid => { + tmpObject.bid_gpt_codes[bid.adUnitCode] = bid.sizes; + }); + + completeObject.events.push(tmpObject); +} + +function bidResponse(args) { + let tmpObject = { + type: 'RESPONSE', + bidder_code: args.bidderCode, + event_timestamp: args.responseTimestamp, + size: args.size, + gpt_code: args.adUnitCode, + currency: args.currency, + creative_id: args.creativeId, + time_to_respond: args.timeToRespond, + cpm: args.cpm, + is_winning: false + }; + + bidResponsesMapper[args.requestId] = completeObject.events.push(tmpObject) - 1; +} + +function bidWon(args) { + let eventIndex = bidResponsesMapper[args.requestId]; + completeObject.events[eventIndex].is_winning = true; +} + +function bidTimeout(args) { /* TODO: implement timeout */ } + +// Methods +function deviceType() { + if ((/ipad|android 3.0|xoom|sch-i800|playbook|tablet|kindle/i.test(navigator.userAgent.toLowerCase()))) { + return 'tablet'; + } + if ((/iphone|ipod|android|blackberry|opera|mini|windows\sce|palm|smartphone|iemobile/i.test(navigator.userAgent.toLowerCase()))) { + return 'mobile'; + } + return 'desktop'; +} + +function sendEvent(completeObject) { + try { + let responseEvents = btoa(JSON.stringify(completeObject)); + let mutation = `mutation {createEvent(input: {event: {eventData: "${responseEvents}"}}) {event {createTime } } }`; + let dataToSend = JSON.stringify({ query: mutation }); + ajax(url, function () { console.log(Date.now() + ' Sending event to adxpremium server.') }, dataToSend, { + contentType: 'application/json', + method: 'POST' + }); + } catch (err) { console.log(err) } +} + +// save the base class function +adxpremiumAnalyticsAdapter.originEnableAnalytics = adxpremiumAnalyticsAdapter.enableAnalytics; + +// override enableAnalytics so we can get access to the config passed in from the page +adxpremiumAnalyticsAdapter.enableAnalytics = function (config) { + adxpremiumAnalyticsAdapter.initOptions = config.options; + + if (!config.options.pubId) { + utils.logError('Publisher ID (pubId) option is not defined. Analytics won\'t work'); + return; + } + + adxpremiumAnalyticsAdapter.originEnableAnalytics(config); // call the base class function +} + +adapterManager.registerAnalyticsAdapter({ + adapter: adxpremiumAnalyticsAdapter, + code: 'adxpremium' +}); + +export default adxpremiumAnalyticsAdapter; diff --git a/modules/adxpremiumAnalyticsAdapter.md b/modules/adxpremiumAnalyticsAdapter.md new file mode 100644 index 00000000000..b2a5efd653f --- /dev/null +++ b/modules/adxpremiumAnalyticsAdapter.md @@ -0,0 +1,41 @@ +# Overview + +Module Name: AdxPremium Analytics Adapter + +Module Type: Analytics Adapter + +Maintainer: info@luponmedia.com + +--- + +# Description + +Analytics adapter for luponmedia.com prebid platform. Contact [info@luponmedia.com]() if you have any questions about integration. + +--- + +# Integration + +AdxPremium Anaytics Adapter can be used as: + +- Part of the whole AdxPremium Header Bidding Ecosystem *(free)* + +- External Analytics tool for your Prebid script *(Paid)* + +##### AdxPremium Header Bidding Ecosystem + +Integration is as easy as adding the following lines of code: + +```javascript +pbjs.que.push(function () { + pbjs.enableAnalytics([{ + provider: 'adxpremium', + options: { + pubID: 12345678 + } + }); + }]); +}); +``` + +*Note*: To use AdxPremium Prebid Analytics Adapter, you have to be AdxPremium publisher and get the publisher ID as well as include the adapter in your **Prebid Core** script. diff --git a/modules/adyoulikeBidAdapter.js b/modules/adyoulikeBidAdapter.js index b9f57115e21..a3e07b25c35 100644 --- a/modules/adyoulikeBidAdapter.js +++ b/modules/adyoulikeBidAdapter.js @@ -1,7 +1,7 @@ -import * as utils from 'src/utils'; -import { format } from 'src/url'; -// import { config } from 'src/config'; -import { registerBidder } from 'src/adapters/bidderFactory'; +import * as utils from '../src/utils'; +import { format } from '../src/url'; +// import { config } from '../src/config'; +import { registerBidder } from '../src/adapters/bidderFactory'; import find from 'core-js/library/fn/array/find'; const VERSION = '1.0'; @@ -18,7 +18,7 @@ export const spec = { * @return boolean True if this is a valid bid, and false otherwise. */ isBidRequestValid: function (bid) { - const sizes = getSize(bid.sizes); + const sizes = getSize(getSizeArray(bid)); if (!bid.params || !bid.params.placement || !sizes.width || !sizes.height) { return false; } @@ -31,16 +31,17 @@ export const spec = { * @return ServerRequest Info describing the request to the server. */ buildRequests: function (bidRequests, bidderRequest) { - let dcHostname = getHostname(bidRequests); const payload = { Version: VERSION, Bids: bidRequests.reduce((accumulator, bid) => { - let size = getSize(bid.sizes); + let sizesArray = getSizeArray(bid); + let size = getSize(sizesArray); accumulator[bid.bidId] = {}; accumulator[bid.bidId].PlacementID = bid.params.placement; accumulator[bid.bidId].TransactionID = bid.transactionId; accumulator[bid.bidId].Width = size.width; accumulator[bid.bidId].Height = size.height; + accumulator[bid.bidId].AvailableSizes = sizesArray.join(','); return accumulator; }, {}), PageRefreshed: getPageRefreshed() @@ -60,7 +61,7 @@ export const spec = { return { method: 'POST', - url: createEndpoint(dcHostname), + url: createEndpoint(bidRequests, bidderRequest), data, options }; @@ -94,14 +95,10 @@ function getHostname(bidderRequest) { } /* Get current page referrer url */ -function getReferrerUrl() { +function getReferrerUrl(bidderRequest) { let referer = ''; - if (window.self !== window.top) { - try { - referer = window.top.document.referrer; - } catch (e) { } - } else { - referer = document.referrer; + if (bidderRequest && bidderRequest.refererInfo) { + referer = encodeURIComponent(bidderRequest.refererInfo.referer); } return referer; } @@ -134,20 +131,21 @@ function getPageRefreshed() { } /* Create endpoint url */ -function createEndpoint(host) { +function createEndpoint(bidRequests, bidderRequest) { + let host = getHostname(bidRequests); return format({ - protocol: (document.location.protocol === 'https:') ? 'https' : 'http', + protocol: 'https', host: `${DEFAULT_DC}${host}.omnitagjs.com`, pathname: '/hb-api/prebid/v1', - search: createEndpointQS() + search: createEndpointQS(bidderRequest) }); } /* Create endpoint query string */ -function createEndpointQS() { +function createEndpointQS(bidderRequest) { const qs = {}; - const ref = getReferrerUrl(); + const ref = getReferrerUrl(bidderRequest); if (ref) { qs.RefererUrl = encodeURIComponent(ref); } @@ -160,10 +158,20 @@ function createEndpointQS() { return qs; } +function getSizeArray(bid) { + let inputSize = bid.sizes; + if (bid.mediaTypes && bid.mediaTypes.banner) { + inputSize = bid.mediaTypes.banner.sizes; + } + + return utils.parseSizesInput(inputSize); +} + /* Get parsed size from request size */ -function getSize(requestSizes) { +function getSize(sizesArray) { const parsed = {}; - const size = utils.parseSizesInput(requestSizes)[0]; + // the main requested size is the first one + const size = sizesArray[0]; if (typeof size !== 'string') { return parsed; @@ -191,7 +199,6 @@ function createBid(response) { return { requestId: response.BidID, - bidderCode: spec.code, width: response.Width, height: response.Height, ad: response.Ad, diff --git a/modules/ajaBidAdapter.js b/modules/ajaBidAdapter.js index bbdbeb53886..be7e1238f64 100644 --- a/modules/ajaBidAdapter.js +++ b/modules/ajaBidAdapter.js @@ -1,7 +1,7 @@ -import { Renderer } from 'src/Renderer'; -import * as utils from 'src/utils'; -import { registerBidder } from 'src/adapters/bidderFactory'; -import { VIDEO, BANNER } from 'src/mediaTypes'; +import { Renderer } from '../src/Renderer'; +import * as utils from '../src/utils'; +import { registerBidder } from '../src/adapters/bidderFactory'; +import { VIDEO, BANNER, NATIVE } from '../src/mediaTypes'; const BIDDER_CODE = 'aja'; const URL = '//ad.as.amanad.adtdp.com/v2/prebid'; @@ -14,7 +14,7 @@ const AD_TYPE = { export const spec = { code: BIDDER_CODE, - supportedMediaTypes: [VIDEO, BANNER], + supportedMediaTypes: [VIDEO, BANNER, NATIVE], isBidRequestValid: function(bid) { return !!(bid.params.asi); @@ -31,6 +31,10 @@ export const spec = { queryString = utils.tryAppendQueryString(queryString, 'prebid_id', bid.bidId); queryString = utils.tryAppendQueryString(queryString, 'prebid_ver', '$prebid.version$'); + if (bidderRequest && bidderRequest.refererInfo) { + queryString = utils.tryAppendQueryString(queryString, 'page_url', bidderRequest.refererInfo.referer); + } + bidRequests.push({ method: 'GET', url: URL, @@ -86,6 +90,42 @@ export const spec = { } catch (error) { utils.logError('Error appending tracking pixel', error); } + } else if (AD_TYPE.NATIVE === ad.ad_type) { + const nativeAds = ad.native.template_and_ads.ads; + + nativeAds.forEach(nativeAd => { + const assets = nativeAd.assets; + + Object.assign(bid, { + mediaType: NATIVE + }); + + bid.native = { + title: assets.title, + body: assets.description, + cta: assets.cta_text, + sponsoredBy: assets.sponsor, + clickUrl: assets.lp_link, + impressionTrackers: nativeAd.imps, + privacyLink: assets.adchoice_url, + }; + + if (assets.img_main !== undefined) { + bid.native.image = { + url: assets.img_main, + width: parseInt(assets.img_main_width, 10), + height: parseInt(assets.img_main_height, 10) + }; + } + + if (assets.img_icon !== undefined) { + bid.native.icon = { + url: assets.img_icon, + width: parseInt(assets.img_icon_width, 10), + height: parseInt(assets.img_icon_height, 10) + }; + } + }); } return [bid]; @@ -93,16 +133,28 @@ export const spec = { getUserSyncs: function(syncOptions, serverResponses) { const syncs = []; - if (syncOptions.pixelEnabled) { - const bidderResponseBody = serverResponses[0].body; - if (bidderResponseBody.syncs) { - bidderResponseBody.syncs.forEach(sync => { - syncs.push({ - type: 'image', - url: sync - }); + if (!serverResponses.length) { + return syncs; + } + + const bidderResponseBody = serverResponses[0].body; + + if (syncOptions.pixelEnabled && bidderResponseBody.syncs) { + bidderResponseBody.syncs.forEach(sync => { + syncs.push({ + type: 'image', + url: sync }); - } + }); + } + + if (syncOptions.iframeEnabled && bidderResponseBody.sync_htmls) { + bidderResponseBody.sync_htmls.forEach(sync => { + syncs.push({ + type: 'iframe', + url: sync + }); + }); } return syncs; diff --git a/modules/ajaBidAdapter.md b/modules/ajaBidAdapter.md index ea2b8e97a43..66155875f4d 100644 --- a/modules/ajaBidAdapter.md +++ b/modules/ajaBidAdapter.md @@ -11,42 +11,82 @@ Connects to Aja exchange for bids. Aja bid adapter supports Banner and Outstream Video. # Test Parameters -``` +```js var adUnits = [ - // Banner adUnit - { - code: 'banner-div', - mediaTypes: { - banner: { - sizes: [ - [300, 250] - ], - } - }, - bids: [{ - bidder: 'aja', - params: { - asi: 'szs4htFiR' - } - }] - }, - // Video outstream adUnit - { - code: 'video-outstream', - mediaTypes: { - video: { - context: 'outstream', - playerSize: [300, 250] - } - }, - bids: [ - { - bidder: 'aja', - params: { - asi: 'Kp2O2tFig' - } - } - ] - } + // Banner adUnit + { + code: 'prebid_banner', + mediaTypes: { + banner: { + sizes: [ + [300, 250] + ], + } + }, + bids: [{ + bidder: 'aja', + params: { + asi: 'tk82gbLmg' + } + }] + }, + // Video outstream adUnit + { + code: 'prebid_video', + mediaTypes: { + video: { + context: 'outstream', + playerSize: [300, 250] + } + }, + bids: [{ + bidder: 'aja', + params: { + asi: '1-KwEG_iR' + } + }] + }, + // Native adUnit + { + code: 'prebid_native', + mediaTypes: { + native: { + image: { + required: true, + sendId: false + }, + title: { + required: true, + sendId: true + }, + sponsoredBy: { + required: false, + sendId: true + }, + clickUrl: { + required: false, + sendId: true + }, + body: { + required: false, + sendId: true + }, + icon: { + required: false, + sendId: false + }, + privacyLink: { + required: true, + sendId: true + }, + } + }, + bids: [{ + bidder: 'aja', + params: { + asi: 'qxueUGliR' + } + }] + } ]; ``` diff --git a/modules/andbeyondBidAdapter.js b/modules/andbeyondBidAdapter.js index 710d75aec6d..660c1fc40c2 100644 --- a/modules/andbeyondBidAdapter.js +++ b/modules/andbeyondBidAdapter.js @@ -1,6 +1,6 @@ -import * as utils from 'src/utils'; -import { BANNER } from 'src/mediaTypes'; -import {registerBidder} from 'src/adapters/bidderFactory'; +import * as utils from '../src/utils'; +import { BANNER } from '../src/mediaTypes'; +import {registerBidder} from '../src/adapters/bidderFactory'; import find from 'core-js/library/fn/array/find'; const VERSION = '1.1'; @@ -36,7 +36,7 @@ export const spec = { const request = buildRtbRequest(dispatch[host][zoneId], auctionId); requests.push({ method: 'GET', - url: `${window.location.protocol}//${host}/rtbg`, + url: `${window.originalLocation.protocol}//${host}/rtbg`, data: { zone: Number(zoneId), ad_type: 'rtb', diff --git a/modules/aniviewBidAdapter.js b/modules/aniviewBidAdapter.js new file mode 100644 index 00000000000..0d53ca8dded --- /dev/null +++ b/modules/aniviewBidAdapter.js @@ -0,0 +1,191 @@ +import { VIDEO } from '../src/mediaTypes'; +import * as utils from '../src/utils'; +import { registerBidder } from '../src/adapters/bidderFactory'; +import { Renderer } from '../src/Renderer'; + +const BIDDER_CODE = 'aniview'; +const TTL = 600; + +function avRenderer(bid) { + bid.renderer.push(function() { + let eventCallback = bid && bid.renderer && bid.renderer.handleVideoEvent ? bid.renderer.handleVideoEvent : null; + window.aniviewRenderer.renderAd({ + id: bid.adUnitCode + '_' + bid.adId, + debug: window.location.href.indexOf('pbjsDebug') >= 0, + placement: bid.adUnitCode, + width: bid.width, + height: bid.height, + vastUrl: bid.vastUrl, + vastXml: bid.vastXml, + config: bid.params[0].rendererConfig, + eventsCallback: eventCallback, + bid: bid + }); + }); +} + +function newRenderer(bidRequest) { + const renderer = Renderer.install({ + url: 'https://player.aniview.com/script/6.1/prebidRenderer.js', + config: {}, + loaded: false, + }); + + try { + renderer.setRender(avRenderer); + } catch (err) { + } + + return renderer; +} + +function isBidRequestValid(bid) { + if (!bid.params || !bid.params.AV_PUBLISHERID || !bid.params.AV_CHANNELID) { return false; } + + return true; +} + +function buildRequests(validBidRequests, bidderRequest) { + let bidRequests = []; + + for (let i = 0; i < validBidRequests.length; i++) { + let bidRequest = validBidRequests[i]; + + if (!bidRequest.sizes || !bidRequest.sizes.length) { + bidRequest.sizes = [[640, 480]]; + } + + if (bidRequest.sizes.length === 2 && typeof bidRequest.sizes[0] === 'number' && typeof bidRequest.sizes[1] === 'number') { + let adWidth = bidRequest.sizes[0]; + let adHeight = bidRequest.sizes[1]; + bidRequest.sizes = [[adWidth, adHeight]]; + } + + for (let j = 0; j < bidRequest.sizes.length; j++) { + let size = bidRequest.sizes[j]; + let playerWidth; + let playerHeight; + if (size && size.length == 2) { + playerWidth = size[0]; + playerHeight = size[1]; + } else { + playerWidth = 640; + playerHeight = 480; + } + + let s2sParams = {}; + + for (var attrname in bidRequest.params) { + if (bidRequest.params.hasOwnProperty(attrname) && attrname.indexOf('AV_') == 0) { + s2sParams[attrname] = bidRequest.params[attrname]; + } + }; + + if (s2sParams.AV_APPPKGNAME && !s2sParams.AV_URL) { s2sParams.AV_URL = s2sParams.AV_APPPKGNAME; } + if (!s2sParams.AV_IDFA && !s2sParams.AV_URL) { s2sParams.AV_URL = utils.getTopWindowUrl(); } + if (s2sParams.AV_IDFA && !s2sParams.AV_AID) { s2sParams.AV_AID = s2sParams.AV_IDFA; } + if (s2sParams.AV_AID && !s2sParams.AV_IDFA) { s2sParams.AV_IDFA = s2sParams.AV_AID; } + + s2sParams.pbjs = 1; + s2sParams.cb = Math.floor(Math.random() * 999999999); + s2sParams.AV_WIDTH = playerWidth; + s2sParams.AV_HEIGHT = playerHeight; + s2sParams.s2s = '1'; + s2sParams.bidId = bidRequest.bidId; + s2sParams.bidWidth = playerWidth; + s2sParams.bidHeight = playerHeight; + + if (bidderRequest && bidderRequest.gdprConsent) { + if (bidderRequest.gdprConsent.gdprApplies) { + s2sParams.AV_GDPR = 1; + s2sParams.AV_CONSENT = bidderRequest.gdprConsent.consentString + } + } + + let serverDomain = bidRequest.params && bidRequest.params.serverDomain ? bidRequest.params.serverDomain : 'gov.aniview.com'; + let serverUrl = 'https://' + serverDomain + '/api/adserver/vast3/'; + + bidRequests.push({ + method: 'GET', + url: serverUrl, + data: s2sParams, + bidRequest + }); + } + } + + return bidRequests; +} +function getCpmData(xml) { + let ret = {cpm: 0, currency: 'USD'}; + if (xml) { + let ext = xml.getElementsByTagName('Extensions'); + if (ext && ext.length > 0) { + ext = ext[0].getElementsByTagName('Extension'); + if (ext && ext.length > 0) { + for (var i = 0; i < ext.length; i++) { + if (ext[i].getAttribute('type') == 'ANIVIEW') { + let price = ext[i].getElementsByTagName('Cpm'); + if (price && price.length == 1) { + ret.cpm = price[0].textContent; + } + break; + } + } + } + } + } + return ret; +} +function interpretResponse(serverResponse, bidRequest) { + let bidResponses = []; + if (serverResponse && serverResponse.body) { + if (serverResponse.error) { + return bidResponses; + } else { + try { + let bidResponse = {}; + if (bidRequest && bidRequest.data && bidRequest.data.bidId && bidRequest.data.bidId !== '') { + let xmlStr = serverResponse.body; + let xml = new window.DOMParser().parseFromString(xmlStr, 'text/xml'); + if (xml && xml.getElementsByTagName('parsererror').length == 0) { + let cpmData = getCpmData(xml); + if (cpmData && cpmData.cpm > 0) { + bidResponse.requestId = bidRequest.data.bidId; + bidResponse.bidderCode = BIDDER_CODE; + bidResponse.ad = ''; + bidResponse.cpm = cpmData.cpm; + bidResponse.width = bidRequest.data.AV_WIDTH; + bidResponse.height = bidRequest.data.AV_HEIGHT; + bidResponse.ttl = TTL; + bidResponse.creativeId = xml.getElementsByTagName('Ad') && xml.getElementsByTagName('Ad')[0] && xml.getElementsByTagName('Ad')[0].getAttribute('id') ? xml.getElementsByTagName('Ad')[0].getAttribute('id') : 'creativeId'; + bidResponse.currency = cpmData.currency; + bidResponse.netRevenue = true; + var blob = new Blob([xmlStr], { + type: 'application/xml' + }); + bidResponse.vastUrl = window.URL.createObjectURL(blob); + bidResponse.vastXml = xmlStr; + bidResponse.mediaType = VIDEO; + if (bidRequest.bidRequest && bidRequest.bidRequest.mediaTypes && bidRequest.bidRequest.mediaTypes.video && bidRequest.bidRequest.mediaTypes.video.context === 'outstream') { bidResponse.renderer = newRenderer(bidRequest); } + + bidResponses.push(bidResponse); + } + } else {} + } else {} + } catch (e) {} + } + } else {} + + return bidResponses; +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [VIDEO], + isBidRequestValid, + buildRequests, + interpretResponse +} + +registerBidder(spec); diff --git a/modules/aniviewBidAdapter.md b/modules/aniviewBidAdapter.md new file mode 100644 index 00000000000..412b8fc3016 --- /dev/null +++ b/modules/aniviewBidAdapter.md @@ -0,0 +1,36 @@ +# Overview + +``` +Module Name: ANIVIEW Bidder Adapter +Module Type: Bidder Adapter +Maintainer: support@aniview.com +``` + +# Description + +Connects to ANIVIEW Ad server for bids. + +ANIVIEW bid adapter supports Video ads currently. + +For more information about [Aniview](http://www.aniview.com), please contact [support@aniview.com](support@aniview.com). + +# Sample Ad Unit: For Publishers +```javascript +var videoAdUnit = [ +{ + code: 'video1', + sizes: [ + [300, 250], + [640, 480] + ], + bids: [{ + bidder: 'aniview', + params: { + AV_PUBLISHERID: '55b78633181f4603178b4568', + AV_CHANNELID: '55b7904d181f46410f8b4568' + } + }] +}]; +``` + +``` diff --git a/modules/aolBidAdapter.js b/modules/aolBidAdapter.js index 28e8cb0b46e..c065828c10d 100644 --- a/modules/aolBidAdapter.js +++ b/modules/aolBidAdapter.js @@ -1,8 +1,6 @@ -import * as utils from 'src/utils'; -import { registerBidder } from 'src/adapters/bidderFactory'; -import { config } from 'src/config'; -import { EVENTS } from 'src/constants.json'; -import { BANNER } from 'src/mediaTypes'; +import * as utils from '../src/utils'; +import { registerBidder } from '../src/adapters/bidderFactory'; +import { BANNER } from '../src/mediaTypes'; const AOL_BIDDERS_CODES = { AOL: 'aol', @@ -31,43 +29,24 @@ const SYNC_TYPES = { } }; -const pubapiTemplate = template`//${'host'}/pubapi/3.0/${'network'}/${'placement'}/${'pageid'}/${'sizeid'}/ADTECH;v=2;cmd=bid;cors=yes;alias=${'alias'};misc=${'misc'};${'dynamicParams'}`; -const nexageBaseApiTemplate = template`//${'host'}/bidRequest?`; +const pubapiTemplate = template`${'host'}/pubapi/3.0/${'network'}/${'placement'}/${'pageid'}/${'sizeid'}/ADTECH;v=2;cmd=bid;cors=yes;alias=${'alias'};misc=${'misc'};${'dynamicParams'}`; +const nexageBaseApiTemplate = template`${'host'}/bidRequest?`; const nexageGetApiTemplate = template`dcn=${'dcn'}&pos=${'pos'}&cmd=bid${'dynamicParams'}`; const MP_SERVER_MAP = { us: 'adserver-us.adtech.advertising.com', eu: 'adserver-eu.adtech.advertising.com', as: 'adserver-as.adtech.advertising.com' }; -const NEXAGE_SERVER = 'hb.nexage.com'; +const NEXAGE_SERVER = 'c2shb.ssp.yahoo.com'; const ONE_DISPLAY_TTL = 60; const ONE_MOBILE_TTL = 3600; - -$$PREBID_GLOBAL$$.aolGlobals = { - pixelsDropped: false -}; +const DEFAULT_PROTO = 'https'; const NUMERIC_VALUES = { TRUE: 1, FALSE: 0 }; -let showCpmAdjustmentWarning = (function() { - let showCpmWarning = true; - - return function() { - let bidderSettings = $$PREBID_GLOBAL$$.bidderSettings; - if (showCpmWarning && bidderSettings && bidderSettings.aol && - typeof bidderSettings.aol.bidCpmAdjustment === 'function') { - utils.logWarn( - 'bidCpmAdjustment is active for the AOL adapter. ' + - 'As of Prebid 0.14, AOL can bid in net – please contact your accounts team to enable.' - ); - showCpmWarning = false; // warning is shown at most once - } - }; -})(); - function template(strings, ...keys) { return function(...values) { let dict = values[values.length - 1] || {}; @@ -80,32 +59,6 @@ function template(strings, ...keys) { }; } -function parsePixelItems(pixels) { - let itemsRegExp = /(img|iframe)[\s\S]*?src\s*=\s*("|')(.*?)\2/gi; - let tagNameRegExp = /\w*(?=\s)/; - let srcRegExp = /src=("|')(.*?)\1/; - let pixelsItems = []; - - if (pixels) { - let matchedItems = pixels.match(itemsRegExp); - if (matchedItems) { - matchedItems.forEach(item => { - let tagName = item.match(tagNameRegExp)[0]; - let url = item.match(srcRegExp)[2]; - - if (tagName && tagName) { - pixelsItems.push({ - type: tagName === SYNC_TYPES.IMAGE.TAG ? SYNC_TYPES.IMAGE.TYPE : SYNC_TYPES.IFRAME.TYPE, - url: url - }); - } - }); - } - } - - return pixelsItems; -} - function _isMarketplaceBidder(bidder) { return bidder === AOL_BIDDERS_CODES.AOL || bidder === AOL_BIDDERS_CODES.ONEDISPLAY; } @@ -164,8 +117,6 @@ export const spec = { }); }, interpretResponse({body}, bidRequest) { - showCpmAdjustmentWarning(); - if (!body) { utils.logError('Empty bid response', bidRequest.bidderCode, body); } else { @@ -176,15 +127,11 @@ export const spec = { } } }, - getUserSyncs(options, bidResponses) { - let bidResponse = bidResponses[0]; - - if (config.getConfig('aol.userSyncOn') === EVENTS.BID_RESPONSE) { - if (!$$PREBID_GLOBAL$$.aolGlobals.pixelsDropped && bidResponse && bidResponse.ext && bidResponse.ext.pixels) { - $$PREBID_GLOBAL$$.aolGlobals.pixelsDropped = true; + getUserSyncs(options, serverResponses) { + const bidResponse = !utils.isEmpty(serverResponses) && serverResponses[0].body; - return parsePixelItems(bidResponse.ext.pixels); - } + if (bidResponse && bidResponse.ext && bidResponse.ext.pixels) { + return this.parsePixelItems(bidResponse.ext.pixels); } return []; @@ -252,7 +199,7 @@ export const spec = { // Set region param, used by AOL analytics. params.region = regionParam; - return pubapiTemplate({ + return this.applyProtocol(pubapiTemplate({ host: server, network: params.network, placement: parseInt(params.placement), @@ -261,7 +208,7 @@ export const spec = { alias: params.alias || utils.getUniqueIdentifierStr(), misc: new Date().getTime(), // cache busting dynamicParams: this.formatMarketplaceDynamicParams(params, consentData) - }); + })); }, buildOneMobileGetUrl(bid, consentData) { let {dcn, pos, ext} = bid.params; @@ -273,9 +220,15 @@ export const spec = { return nexageApi; }, buildOneMobileBaseUrl(bid) { - return nexageBaseApiTemplate({ + return this.applyProtocol(nexageBaseApiTemplate({ host: bid.params.host || NEXAGE_SERVER - }); + })); + }, + applyProtocol(url) { + if (/^https?:\/\//i.test(url)) { + return url; + } + return (url.indexOf('//') === 0) ? `${DEFAULT_PROTO}:${url}` : `${DEFAULT_PROTO}://${url}`; }, formatMarketplaceDynamicParams(params = {}, consentData) { let queryParams = {}; @@ -357,6 +310,31 @@ export const spec = { return params; }, + parsePixelItems(pixels) { + let itemsRegExp = /(img|iframe)[\s\S]*?src\s*=\s*("|')(.*?)\2/gi; + let tagNameRegExp = /\w*(?=\s)/; + let srcRegExp = /src=("|')(.*?)\1/; + let pixelsItems = []; + + if (pixels) { + let matchedItems = pixels.match(itemsRegExp); + if (matchedItems) { + matchedItems.forEach(item => { + let tagName = item.match(tagNameRegExp)[0]; + let url = item.match(srcRegExp)[2]; + + if (tagName && tagName) { + pixelsItems.push({ + type: tagName === SYNC_TYPES.IMAGE.TAG ? SYNC_TYPES.IMAGE.TYPE : SYNC_TYPES.IFRAME.TYPE, + url: url + }); + } + }); + } + } + + return pixelsItems; + }, _parseBidResponse(response, bidRequest) { let bidData; @@ -380,38 +358,20 @@ export const spec = { } } - let bidResponse = { + return { bidderCode: bidRequest.bidderCode, requestId: bidRequest.bidId, ad: bidData.adm, cpm: cpm, width: bidData.w, height: bidData.h, - creativeId: bidData.crid, + creativeId: bidData.crid || 0, pubapiId: response.id, - currency: response.cur, + currency: response.cur || 'USD', dealId: bidData.dealid, netRevenue: true, ttl: bidRequest.ttl }; - - if (response.ext && response.ext.pixels) { - if (config.getConfig('aol.userSyncOn') !== EVENTS.BID_RESPONSE) { - bidResponse.ad += this.formatPixels(response.ext.pixels); - } - } - - return bidResponse; - }, - formatPixels(pixels) { - let formattedPixels = pixels.replace(/<\/?script( type=('|")text\/javascript('|")|)?>/g, ''); - - return ''; }, isOneMobileBidder: _isOneMobileBidder, isSecureProtocol() { diff --git a/modules/appierAnalyticsAdapter.js b/modules/appierAnalyticsAdapter.js new file mode 100644 index 00000000000..76811b598e0 --- /dev/null +++ b/modules/appierAnalyticsAdapter.js @@ -0,0 +1,244 @@ +import {ajax} from '../src/ajax'; +import adapter from '../src/AnalyticsAdapter'; +import CONSTANTS from '../src/constants.json'; +import adapterManager from '../src/adapterManager'; +import {logError, logInfo, deepClone} from '../src/utils'; + +const analyticsType = 'endpoint'; + +export const ANALYTICS_VERSION = '1.0.0'; + +const DEFAULT_SERVER = 'https://prebid-analytics.c.appier.net/v1'; + +const { + EVENTS: { + AUCTION_END, + BID_WON, + BID_TIMEOUT + } +} = CONSTANTS; + +export const BIDDER_STATUS = { + BID: 'bid', + NO_BID: 'noBid', + BID_WON: 'bidWon', + TIMEOUT: 'timeout' +}; + +export const getCpmInUsd = function (bid) { + if (bid.currency === 'USD') { + return bid.cpm; + } else { + return bid.getCpmInNewCurrency('USD'); + } +}; + +const analyticsOptions = {}; + +export const parseBidderCode = function (bid) { + let bidderCode = bid.bidderCode || bid.bidder; + return bidderCode.toLowerCase(); +}; + +export const parseAdUnitCode = function (bidResponse) { + return bidResponse.adUnitCode.toLowerCase(); +}; + +export const appierAnalyticsAdapter = Object.assign(adapter({DEFAULT_SERVER, analyticsType}), { + + cachedAuctions: {}, + + initConfig(config) { + /** + * Required option: affiliateId + * Required option: configId + * + * Optional option: server + * Optional option: sampling + * Optional option: adSampling + * Optional option: autoPick + * Optional option: predictionId + * @type {boolean} + */ + analyticsOptions.options = deepClone(config.options); + if (typeof config.options.affiliateId !== 'string' || config.options.affiliateId.length < 1) { + logError('"options.affiliateId" is required.'); + return false; + } + if (typeof config.options.configId !== 'string' || config.options.configId.length < 1) { + logError('"options.configId" is required.'); + return false; + } + + analyticsOptions.affiliateId = config.options.affiliateId; + analyticsOptions.configId = config.options.configId; + analyticsOptions.server = config.options.server || DEFAULT_SERVER; + + analyticsOptions.sampled = true; + if (typeof config.options.sampling === 'number') { + analyticsOptions.sampled = Math.random() < parseFloat(config.options.sampling); + } + analyticsOptions.adSampled = false; + if (typeof config.options.adSampling === 'number') { + analyticsOptions.adSampled = Math.random() < parseFloat(config.options.adSampling); + } + analyticsOptions.autoPick = config.options.autoPick || null; + analyticsOptions.predictionId = config.options.predictionId || null; + + return true; + }, + sendEventMessage(endPoint, data) { + logInfo(`AJAX: ${endPoint}: ` + JSON.stringify(data)); + + ajax(`${analyticsOptions.server}/${endPoint}`, null, JSON.stringify(data), { + contentType: 'application/json', + withCredentials: true + }); + }, + createCommonMessage(auctionId) { + return { + version: ANALYTICS_VERSION, + auctionId: auctionId, + affiliateId: analyticsOptions.affiliateId, + configId: analyticsOptions.configId, + referrer: window.location.href, + sampling: analyticsOptions.options.sampling, + adSampling: analyticsOptions.options.adSampling, + prebid: '$prebid.version$', + autoPick: analyticsOptions.autoPick, + predictionId: analyticsOptions.predictionId, + adUnits: {}, + }; + }, + serializeBidResponse(bid, status) { + const result = { + prebidWon: (status === BIDDER_STATUS.BID_WON), + isTimeout: (status === BIDDER_STATUS.TIMEOUT), + status: status, + }; + if (status === BIDDER_STATUS.BID || status === BIDDER_STATUS.BID_WON) { + Object.assign(result, { + time: bid.timeToRespond, + cpm: bid.cpm, + currency: bid.currency, + originalCpm: bid.originalCpm || bid.cpm, + cpmUsd: getCpmInUsd(bid), + originalCurrency: bid.originalCurrency || bid.currency, + }); + } + return result; + }, + addBidResponseToMessage(message, bid, status) { + const adUnitCode = parseAdUnitCode(bid); + message.adUnits[adUnitCode] = message.adUnits[adUnitCode] || {}; + const bidder = parseBidderCode(bid); + const bidResponse = this.serializeBidResponse(bid, status); + message.adUnits[adUnitCode][bidder] = bidResponse; + }, + createBidMessage(auctionEndArgs, winningBids, timeoutBids) { + const {auctionId, timestamp, timeout, auctionEnd, adUnitCodes, bidsReceived, noBids} = auctionEndArgs; + const message = this.createCommonMessage(auctionId); + + message.auctionElapsed = (auctionEnd - timestamp); + message.timeout = timeout; + + adUnitCodes.forEach((adUnitCode) => { + message.adUnits[adUnitCode] = {}; + }); + + // We handled noBids first because when currency conversion is enabled, a bid with a foreign currency + // will be set to NO_BID initially, and then set to BID after the currency rate json file is fully loaded. + // In this situation, the bid exists in both noBids and bids arrays. + noBids.forEach(bid => this.addBidResponseToMessage(message, bid, BIDDER_STATUS.NO_BID)); + + // This array may contain some timeout bids (responses come back after auction timeout) + bidsReceived.forEach(bid => this.addBidResponseToMessage(message, bid, BIDDER_STATUS.BID)); + + // We handle timeout after bids since it's possible that a bid has a response, but the response comes back + // after auction end. In this case, the bid exists in both bidsReceived and timeoutBids arrays. + timeoutBids.forEach(bid => this.addBidResponseToMessage(message, bid, BIDDER_STATUS.TIMEOUT)); + + // mark the winning bids with prebidWon = true + winningBids.forEach(bid => { + const adUnitCode = parseAdUnitCode(bid); + const bidder = parseBidderCode(bid); + message.adUnits[adUnitCode][bidder].prebidWon = true; + }); + return message; + }, + createImpressionMessage(bid) { + const message = this.createCommonMessage(bid.auctionId); + this.addBidResponseToMessage(message, bid, BIDDER_STATUS.BID_WON); + return message; + }, + createCreativeMessage(auctionId, bids) { + const message = this.createCommonMessage(auctionId); + bids.forEach((bid) => { + const adUnitCode = parseAdUnitCode(bid); + const bidder = parseBidderCode(bid); + message.adUnits[adUnitCode] = message.adUnits[adUnitCode] || {}; + message.adUnits[adUnitCode][bidder] = {ad: bid.ad}; + }); + return message; + }, + getCachedAuction(auctionId) { + this.cachedAuctions[auctionId] = this.cachedAuctions[auctionId] || { + timeoutBids: [], + }; + return this.cachedAuctions[auctionId]; + }, + handleAuctionEnd(auctionEndArgs) { + const cachedAuction = this.getCachedAuction(auctionEndArgs.auctionId); + const highestCpmBids = pbjs.getHighestCpmBids(); + this.sendEventMessage('bid', + this.createBidMessage(auctionEndArgs, highestCpmBids, cachedAuction.timeoutBids) + ); + if (analyticsOptions.adSampled) { + this.sendEventMessage('cr', + this.createCreativeMessage(auctionEndArgs.auctionId, auctionEndArgs.bidsReceived) + ); + } + }, + handleBidTimeout(timeoutBids) { + timeoutBids.forEach((bid) => { + const cachedAuction = this.getCachedAuction(bid.auctionId); + cachedAuction.timeoutBids.push(bid); + }); + }, + handleBidWon(bidWonArgs) { + this.sendEventMessage('imp', this.createImpressionMessage(bidWonArgs)); + }, + track({eventType, args}) { + if (analyticsOptions.sampled) { + switch (eventType) { + case BID_WON: + this.handleBidWon(args); + break; + case BID_TIMEOUT: + this.handleBidTimeout(args); + break; + case AUCTION_END: + this.handleAuctionEnd(args); + break; + } + } + }, + getAnalyticsOptions() { + return analyticsOptions; + }, +}); + +// save the base class function +appierAnalyticsAdapter.originEnableAnalytics = appierAnalyticsAdapter.enableAnalytics; + +// override enableAnalytics so we can get access to the config passed in from the page +appierAnalyticsAdapter.enableAnalytics = function (config) { + if (this.initConfig(config)) { + appierAnalyticsAdapter.originEnableAnalytics(config); // call the base class function + } +}; + +adapterManager.registerAnalyticsAdapter({ + adapter: appierAnalyticsAdapter, + code: 'appierAnalytics' +}); diff --git a/modules/appierAnalyticsAdapter.md b/modules/appierAnalyticsAdapter.md new file mode 100644 index 00000000000..09f0676d054 --- /dev/null +++ b/modules/appierAnalyticsAdapter.md @@ -0,0 +1,23 @@ +# Overview + +Module Name: Appier Analytics Adapter +Module Type: Analytics Adapter +Maintainer: apn-dev@appier.com + +# Description + +Analytics adapter for Appier + +# Test Parameters + +``` +{ + provider: 'appierAnalytics', + options: { + 'configId': 'YOUR_CONFIG_ID', + 'affiliateId': 'YOUR_AFFILIATE_ID', + } +} +``` + +PS. [Prebid currency module](http://prebid.org/dev-docs/modules/currency.html) is required, please make sure your prebid code contains currency module code. diff --git a/modules/appierBidAdapter.js b/modules/appierBidAdapter.js new file mode 100644 index 00000000000..a8e05f8edac --- /dev/null +++ b/modules/appierBidAdapter.js @@ -0,0 +1,89 @@ +import { registerBidder } from '../src/adapters/bidderFactory'; +import { BANNER } from '../src/mediaTypes'; +import { config } from '../src/config'; + +export const ADAPTER_VERSION = '1.0.0'; +const SUPPORTED_AD_TYPES = [BANNER]; + +// we have different servers for different regions / farms +export const API_SERVERS_MAP = { + 'default': 'ad2.apx.appier.net', + 'tw': 'ad2.apx.appier.net', + 'jp': 'ad-jp.apx.appier.net' +}; + +const BIDDER_API_ENDPOINT = '/v1/prebid/bid'; + +export const spec = { + code: 'appier', + supportedMediaTypes: SUPPORTED_AD_TYPES, + + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function (bid) { + return typeof bid.params.hzid === 'string'; + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param {bidRequests[]} - an array of bids + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function (bidRequests, bidderRequest) { + if (bidRequests.length === 0) { + return []; + } + const server = this.getApiServer(); + const bidderApiUrl = `//${server}${BIDDER_API_ENDPOINT}` + const payload = { + 'bids': bidRequests, + 'refererInfo': bidderRequest.refererInfo, + 'version': ADAPTER_VERSION + }; + return [{ + method: 'POST', + url: bidderApiUrl, + data: payload, + // keep the bidder request object for later use + bidderRequest: bidderRequest + }]; + }, + + /** + * 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, serverRequest) { + if (!Array.isArray(serverResponse.body)) { + return []; + } + // server response body is an array of bid results + const bidResults = serverResponse.body; + // our server directly returns the format needed by prebid.js so no more + // transformation is needed here. + return bidResults; + }, + + /** + * Get the hostname of the server we want to use. + */ + getApiServer() { + // we may use different servers for different farms (geographical regions) + // if a server is specified explicitly, use it. otherwise, use farm specific server. + let server = config.getConfig('appier.server'); + if (!server) { + const farm = config.getConfig('appier.farm'); + server = API_SERVERS_MAP[farm] || API_SERVERS_MAP['default']; + } + return server; + } +}; + +registerBidder(spec); diff --git a/modules/appierBidAdapter.md b/modules/appierBidAdapter.md new file mode 100644 index 00000000000..92fdaab1e40 --- /dev/null +++ b/modules/appierBidAdapter.md @@ -0,0 +1,57 @@ +# Overview + +``` +Module Name: Appier Bid Adapter +Module Type: Bidder Adapter +Maintainer: apn-dev@appier.com +``` + +# Description + +Connects to Appier exchange for bids. + +NOTE: +- Appier bid adapter only supports Banner at the moment. +- Multi-currency is not supported. Please make sure you have correct DFP currency settings according to your deal with Appier. + +# Sample Ad Unit Config +``` +var adUnits = [ + // Banner adUnit + { + code: 'banner-div', + mediaTypes: { + banner: { + sizes: [[300, 250], [300,600]] + } + }, + bids: [{ + bidder: 'appier', + params: { + hzid: 'WhM5WIOp' + } + }] + } +]; +``` + +# Additional Config (Optional) +Set the "farm" to use region-specific server +``` + // use the bid server in Taiwan (country code: tw) + pbjs.setConfig({ + appier: { + 'farm': 'tw' + } + }); +``` + +Explicitly override the bid server used for bidding +``` + // use the bid server specified and override the default + pbjs.setConfig({ + appier: { + 'server': '${HOST_NAME_OF_THE_SERVER}' + } + }); +``` diff --git a/modules/appnexusAnalyticsAdapter.js b/modules/appnexusAnalyticsAdapter.js index f9756de23e3..f0f5ece26e8 100644 --- a/modules/appnexusAnalyticsAdapter.js +++ b/modules/appnexusAnalyticsAdapter.js @@ -2,8 +2,8 @@ * appnexus.js - AppNexus Prebid Analytics Adapter */ -import adapter from 'src/AnalyticsAdapter'; -import adaptermanager from 'src/adaptermanager'; +import adapter from '../src/AnalyticsAdapter'; +import adapterManager from '../src/adapterManager'; var appnexusAdapter = adapter({ global: 'AppNexusPrebidAnalytics', @@ -11,7 +11,7 @@ var appnexusAdapter = adapter({ analyticsType: 'bundle' }); -adaptermanager.registerAnalyticsAdapter({ +adapterManager.registerAnalyticsAdapter({ adapter: appnexusAdapter, code: 'appnexus' }); diff --git a/modules/appnexusBidAdapter.js b/modules/appnexusBidAdapter.js index 7dac4b8b182..9488a1be751 100644 --- a/modules/appnexusBidAdapter.js +++ b/modules/appnexusBidAdapter.js @@ -1,36 +1,47 @@ -import { Renderer } from 'src/Renderer'; -import * as utils from 'src/utils'; -import { registerBidder } from 'src/adapters/bidderFactory'; -import { BANNER, NATIVE, VIDEO } from 'src/mediaTypes'; +import { Renderer } from '../src/Renderer'; +import * as utils from '../src/utils'; +import { config } from '../src/config'; +import { registerBidder, getIabSubCategory } from '../src/adapters/bidderFactory'; +import { BANNER, NATIVE, VIDEO, ADPOD } from '../src/mediaTypes'; +import { auctionManager } from '../src/auctionManager'; import find from 'core-js/library/fn/array/find'; import includes from 'core-js/library/fn/array/includes'; +import { OUTSTREAM, INSTREAM } from '../src/video'; const BIDDER_CODE = 'appnexus'; const URL = '//ib.adnxs.com/ut/v3/prebid'; const VIDEO_TARGETING = ['id', 'mimes', 'minduration', 'maxduration', 'startdelay', 'skippable', 'playback_method', 'frameworks']; -const USER_PARAMS = ['age', 'external_uid', 'segments', 'gender', 'dnt', 'language']; +const USER_PARAMS = ['age', 'externalUid', 'segments', 'gender', 'dnt', 'language']; const APP_DEVICE_PARAMS = ['geo', 'device_id']; // appid is collected separately +const DEBUG_PARAMS = ['enabled', 'dongle', 'member_id', 'debug_timeout']; const NATIVE_MAPPING = { body: 'description', + body2: 'desc2', cta: 'ctatext', image: { serverName: 'main_image', - requiredParams: { required: true }, - minimumParams: { sizes: [{}] }, + requiredParams: { required: true } }, icon: { serverName: 'icon', - requiredParams: { required: true }, - minimumParams: { sizes: [{}] }, + requiredParams: { required: true } }, sponsoredBy: 'sponsored_by', + privacyLink: 'privacy_link', + salePrice: 'saleprice', + displayUrl: 'displayurl' }; const SOURCE = 'pbjs'; +const MAX_IMPS_PER_REQUEST = 15; +const mappingFileUrl = '//acdn.adnxs.com/prebid/appnexus-mapping/mappings.json'; +const SCRIPT_TAG_START = ' includes(USER_PARAMS, param)) .forEach(param => userObj[param] = userObjBid.params.user[param]); @@ -77,8 +90,35 @@ export const spec = { }; } + let debugObj = {}; + let debugObjParams = {}; + const debugCookieName = 'apn_prebid_debug'; + const debugCookie = utils.getCookie(debugCookieName) || null; + + if (debugCookie) { + try { + debugObj = JSON.parse(debugCookie); + } catch (e) { + utils.logError('AppNexus Debug Auction Cookie Error:\n\n' + e); + } + } else { + const debugBidRequest = find(bidRequests, hasDebug); + if (debugBidRequest && debugBidRequest.debug) { + debugObj = debugBidRequest.debug; + } + } + + if (debugObj && debugObj.enabled) { + Object.keys(debugObj) + .filter(param => includes(DEBUG_PARAMS, param)) + .forEach(param => { + debugObjParams[param] = debugObj[param]; + }); + } + const memberIdBid = find(bidRequests, hasMemberId); const member = memberIdBid ? parseInt(memberIdBid.params.member, 10) : 0; + const schain = bidRequests[0].schain; const payload = { tags: [...tags], @@ -86,8 +126,10 @@ export const spec = { sdk: { source: SOURCE, version: '$prebid.version$' - } + }, + schain: schain }; + if (member > 0) { payload.member_id = member; } @@ -99,6 +141,15 @@ export const spec = { payload.app = appIdObj; } + if (config.getConfig('adpod.brandCategoryExclusion')) { + payload.brand_category_uniqueness = true; + } + + if (debugObjParams.enabled) { + payload.debug = debugObjParams; + utils.logInfo('AppNexus Debug Auction Settings:\n\n' + JSON.stringify(debugObjParams, null, 4)); + } + if (bidderRequest && bidderRequest.gdprConsent) { // note - objects for impbus use underscore instead of camelCase payload.gdpr_consent = { @@ -107,13 +158,38 @@ export const spec = { }; } - const payloadString = JSON.stringify(payload); - return { - method: 'POST', - url: URL, - data: payloadString, - bidderRequest - }; + if (bidderRequest && bidderRequest.refererInfo) { + let refererinfo = { + rd_ref: encodeURIComponent(bidderRequest.refererInfo.referer), + rd_top: bidderRequest.refererInfo.reachedTop, + rd_ifs: bidderRequest.refererInfo.numIframes, + rd_stk: bidderRequest.refererInfo.stack.map((url) => encodeURIComponent(url)).join(',') + } + payload.referrer_detection = refererinfo; + } + + const hasAdPodBid = find(bidRequests, hasAdPod); + if (hasAdPodBid) { + bidRequests.filter(hasAdPod).forEach(adPodBid => { + const adPodTags = createAdPodRequest(tags, adPodBid); + // don't need the original adpod placement because it's in adPodTags + const nonPodTags = payload.tags.filter(tag => tag.uuid !== adPodBid.bidId); + payload.tags = [...nonPodTags, ...adPodTags]; + }); + } + + const rtusId = utils.deepAccess(bidRequests[0], `userId.criteortus.${BIDDER_CODE}.userid`); + if (rtusId) { + let tpuids = []; + tpuids.push({ + 'provider': 'criteo', + 'user_id': rtusId + }); + payload.tpuids = tpuids; + } + + const request = formatRequest(payload, bidderRequest); + return request; }, /** @@ -144,9 +220,43 @@ export const spec = { } }); } + + if (serverResponse.debug && serverResponse.debug.debug_info) { + let debugHeader = 'AppNexus Debug Auction for Prebid\n\n' + let debugText = debugHeader + serverResponse.debug.debug_info + debugText = debugText + .replace(/(|)/gm, '\t') // Tables + .replace(/(<\/td>|<\/th>)/gm, '\n') // Tables + .replace(/^
    /gm, '') // Remove leading
    + .replace(/(
    \n|
    )/gm, '\n') //
    + .replace(/

    (.*)<\/h1>/gm, '\n\n===== $1 =====\n\n') // Header H1 + .replace(/(.*)<\/h[2-6]>/gm, '\n\n*** $1 ***\n\n') // Headers + .replace(/(<([^>]+)>)/igm, ''); // Remove any other tags + utils.logMessage('https://console.appnexus.com/docs/understanding-the-debug-auction'); + utils.logMessage(debugText); + } + return bids; }, + /** + * @typedef {Object} mappingFileInfo + * @property {string} url mapping file json url + * @property {number} refreshInDays prebid stores mapping data in localstorage so you can return in how many days you want to update value stored in localstorage. + * @property {string} localStorageKey unique key to store your mapping json in localstorage + */ + + /** + * Returns mapping file info. This info will be used by bidderFactory to preload mapping file and store data in local storage + * @returns {mappingFileInfo} + */ + getMappingFileInfo: function() { + return { + url: mappingFileUrl, + refreshInDays: 7 + } + }, + getUserSyncs: function(syncOptions) { if (syncOptions.iframeEnabled) { return [{ @@ -168,6 +278,10 @@ export const spec = { params.use_pmt_rule = (typeof params.usePaymentRule === 'boolean') ? params.usePaymentRule : false; if (params.usePaymentRule) { delete params.usePaymentRule; } + if (isPopulatedArray(params.keywords)) { + params.keywords.forEach(deleteValues); + } + Object.keys(params).forEach(paramKey => { let convertedKey = utils.convertCamelToUnderscore(paramKey); if (convertedKey !== paramKey) { @@ -178,15 +292,150 @@ export const spec = { } return params; + }, + + /** + * Add element selector to javascript tracker to improve native viewability + * @param {Bid} bid + */ + onBidWon: function(bid) { + if (bid.native) { + reloadViewabilityScriptWithCorrectParameters(bid); + } } } +function isPopulatedArray(arr) { + return !!(utils.isArray(arr) && arr.length > 0); +} + +function deleteValues(keyPairObj) { + if (isPopulatedArray(keyPairObj.value) && keyPairObj.value[0] === '') { + delete keyPairObj.value; + } +} + +function reloadViewabilityScriptWithCorrectParameters(bid) { + let viewJsPayload = getAppnexusViewabilityScriptFromJsTrackers(bid.native.javascriptTrackers); + + if (viewJsPayload) { + let prebidParams = 'pbjs_adid=' + bid.adId + ';pbjs_auc=' + bid.adUnitCode; + + let jsTrackerSrc = getViewabilityScriptUrlFromPayload(viewJsPayload) + + let newJsTrackerSrc = jsTrackerSrc.replace('dom_id=%native_dom_id%', prebidParams); + + // find iframe containing script tag + let frameArray = document.getElementsByTagName('iframe'); + + // boolean var to modify only one script. That way if there are muliple scripts, + // they won't all point to the same creative. + let modifiedAScript = false; + + // first, loop on all ifames + for (let i = 0; i < frameArray.length && !modifiedAScript; i++) { + let currentFrame = frameArray[i]; + try { + // IE-compatible, see https://stackoverflow.com/a/3999191/2112089 + let nestedDoc = currentFrame.contentDocument || currentFrame.contentWindow.document; + + if (nestedDoc) { + // if the doc is present, we look for our jstracker + let scriptArray = nestedDoc.getElementsByTagName('script'); + for (let j = 0; j < scriptArray.length && !modifiedAScript; j++) { + let currentScript = scriptArray[j]; + if (currentScript.getAttribute('data-src') == jsTrackerSrc) { + currentScript.setAttribute('src', newJsTrackerSrc); + currentScript.setAttribute('data-src', ''); + if (currentScript.removeAttribute) { + currentScript.removeAttribute('data-src'); + } + modifiedAScript = true; + } + } + } + } catch (exception) { + // trying to access a cross-domain iframe raises a SecurityError + // this is expected and ignored + if (!(exception instanceof DOMException && exception.name === 'SecurityError')) { + // all other cases are raised again to be treated by the calling function + throw exception; + } + } + } + } +} + +function strIsAppnexusViewabilityScript(str) { + let regexMatchUrlStart = str.match(VIEWABILITY_URL_START); + let viewUrlStartInStr = regexMatchUrlStart != null && regexMatchUrlStart.length >= 1; + + let regexMatchFileName = str.match(VIEWABILITY_FILE_NAME); + let fileNameInStr = regexMatchFileName != null && regexMatchFileName.length >= 1; + + return str.startsWith(SCRIPT_TAG_START) && fileNameInStr && viewUrlStartInStr; +} + +function getAppnexusViewabilityScriptFromJsTrackers(jsTrackerArray) { + let viewJsPayload; + if (utils.isStr(jsTrackerArray) && strIsAppnexusViewabilityScript(jsTrackerArray)) { + viewJsPayload = jsTrackerArray; + } else if (utils.isArray(jsTrackerArray)) { + for (let i = 0; i < jsTrackerArray.length; i++) { + let currentJsTracker = jsTrackerArray[i]; + if (strIsAppnexusViewabilityScript(currentJsTracker)) { + viewJsPayload = currentJsTracker; + } + } + } + return viewJsPayload; +} + +function getViewabilityScriptUrlFromPayload(viewJsPayload) { + // extracting the content of the src attribute + // -> substring between src=" and " + let indexOfFirstQuote = viewJsPayload.indexOf('src="') + 5; // offset of 5: the length of 'src=' + 1 + let indexOfSecondQuote = viewJsPayload.indexOf('"', indexOfFirstQuote); + let jsTrackerSrc = viewJsPayload.substring(indexOfFirstQuote, indexOfSecondQuote); + return jsTrackerSrc; +} + +function formatRequest(payload, bidderRequest) { + let request = []; + + if (payload.tags.length > MAX_IMPS_PER_REQUEST) { + const clonedPayload = utils.deepClone(payload); + + utils.chunk(payload.tags, MAX_IMPS_PER_REQUEST).forEach(tags => { + clonedPayload.tags = tags; + const payloadString = JSON.stringify(clonedPayload); + request.push({ + method: 'POST', + url: URL, + data: payloadString, + bidderRequest + }); + }); + } else { + const payloadString = JSON.stringify(payload); + request = { + method: 'POST', + url: URL, + data: payloadString, + bidderRequest + }; + } + + return request; +} + function newRenderer(adUnitCode, rtbBid, rendererOptions = {}) { const renderer = Renderer.install({ id: rtbBid.renderer_id, url: rtbBid.renderer_url, config: rendererOptions, loaded: false, + adUnitCode }); try { @@ -214,6 +463,7 @@ function newRenderer(adUnitCode, rtbBid, rendererOptions = {}) { * @return Bid */ function newBid(serverBid, rtbBid, bidderRequest) { + const bidRequest = utils.getBidRequest(serverBid.uuid, [bidderRequest]); const bid = { requestId: serverBid.uuid, cpm: rtbBid.cpm, @@ -222,44 +472,91 @@ function newBid(serverBid, rtbBid, bidderRequest) { currency: 'USD', netRevenue: true, ttl: 300, + adUnitCode: bidRequest.adUnitCode, appnexus: { - buyerMemberId: rtbBid.buyer_member_id + buyerMemberId: rtbBid.buyer_member_id, + dealPriority: rtbBid.deal_priority, + dealCode: rtbBid.deal_code } }; + if (rtbBid.advertiser_id) { + bid.meta = Object.assign({}, bid.meta, { advertiserId: rtbBid.advertiser_id }); + } + if (rtbBid.rtb.video) { + // shared video properties used for all 3 contexts Object.assign(bid, { width: rtbBid.rtb.video.player_width, height: rtbBid.rtb.video.player_height, - vastUrl: rtbBid.rtb.video.asset_url, vastImpUrl: rtbBid.notify_url, ttl: 3600 }); - // This supports Outstream Video - if (rtbBid.renderer_url) { - const rendererOptions = utils.deepAccess( - bidderRequest.bids[0], - 'renderer.options' - ); - - Object.assign(bid, { - adResponse: serverBid, - renderer: newRenderer(bid.adUnitCode, rtbBid, rendererOptions) - }); - bid.adResponse.ad = bid.adResponse.ads[0]; - bid.adResponse.ad.video = bid.adResponse.ad.rtb.video; + + const videoContext = utils.deepAccess(bidRequest, 'mediaTypes.video.context'); + switch (videoContext) { + case ADPOD: + const iabSubCatId = getIabSubCategory(bidRequest.bidder, rtbBid.brand_category_id); + bid.meta = Object.assign({}, bid.meta, { iabSubCatId }); + bid.video = { + context: ADPOD, + durationSeconds: Math.floor(rtbBid.rtb.video.duration_ms / 1000), + }; + bid.vastUrl = rtbBid.rtb.video.asset_url; + break; + case OUTSTREAM: + bid.adResponse = serverBid; + bid.adResponse.ad = bid.adResponse.ads[0]; + bid.adResponse.ad.video = bid.adResponse.ad.rtb.video; + bid.vastXml = rtbBid.rtb.video.content; + + if (rtbBid.renderer_url) { + const videoBid = find(bidderRequest.bids, bid => bid.bidId === serverBid.uuid); + const rendererOptions = utils.deepAccess(videoBid, 'renderer.options'); + bid.renderer = newRenderer(bid.adUnitCode, rtbBid, rendererOptions); + } + break; + case INSTREAM: + bid.vastUrl = rtbBid.rtb.video.asset_url; + break; } } else if (rtbBid.rtb[NATIVE]) { const nativeAd = rtbBid.rtb[NATIVE]; + + // setting up the jsTracker: + // we put it as a data-src attribute so that the tracker isn't called + // until we have the adId (see onBidWon) + let jsTrackerDisarmed = rtbBid.viewability.config.replace('src=', 'data-src='); + + let jsTrackers = nativeAd.javascript_trackers; + + if (jsTrackers == undefined) { + jsTrackers = jsTrackerDisarmed; + } else if (utils.isStr(jsTrackers)) { + jsTrackers = [jsTrackers, jsTrackerDisarmed]; + } else { + jsTrackers.push(jsTrackerDisarmed); + } + bid[NATIVE] = { title: nativeAd.title, body: nativeAd.desc, + body2: nativeAd.desc2, cta: nativeAd.ctatext, + rating: nativeAd.rating, sponsoredBy: nativeAd.sponsored, + privacyLink: nativeAd.privacy_link, + address: nativeAd.address, + downloads: nativeAd.downloads, + likes: nativeAd.likes, + phone: nativeAd.phone, + price: nativeAd.price, + salePrice: nativeAd.saleprice, clickUrl: nativeAd.link.url, + displayUrl: nativeAd.displayurl, clickTrackers: nativeAd.link.click_trackers, impressionTrackers: nativeAd.impression_trackers, - javascriptTrackers: nativeAd.javascript_trackers, + javascriptTrackers: jsTrackers }; if (nativeAd.main_img) { bid['native'].image = { @@ -333,11 +630,19 @@ function bidToTag(bid) { tag.external_imp_id = bid.params.externalImpId; } if (!utils.isEmpty(bid.params.keywords)) { - tag.keywords = utils.transformBidderParamKeywords(bid.params.keywords); + let keywords = utils.transformBidderParamKeywords(bid.params.keywords); + + if (keywords.length > 0) { + keywords.forEach(deleteValues); + } + tag.keywords = keywords; } if (bid.mediaType === NATIVE || utils.deepAccess(bid, `mediaTypes.${NATIVE}`)) { tag.ad_types.push(NATIVE); + if (tag.sizes.length === 0) { + tag.sizes = transformSizes([1, 1]); + } if (bid.nativeParams) { const nativeRequest = buildNativeRequest(bid.nativeParams); @@ -365,13 +670,19 @@ function bidToTag(bid) { .forEach(param => tag.video[param] = bid.params.video[param]); } - if ( - (utils.isEmpty(bid.mediaType) && utils.isEmpty(bid.mediaTypes)) || - (bid.mediaType === BANNER || (bid.mediaTypes && bid.mediaTypes[BANNER])) - ) { + if (bid.renderer) { + tag.video = Object.assign({}, tag.video, {custom_renderer_present: true}); + } + + let adUnit = find(auctionManager.getAdUnits(), au => bid.transactionId === au.transactionId); + if (adUnit && adUnit.mediaTypes && adUnit.mediaTypes.banner) { tag.ad_types.push(BANNER); } + if (tag.ad_types.length === 0) { + delete tag.ad_types; + } + return tag; } @@ -419,6 +730,66 @@ function hasAppId(bid) { return !!bid.params.app } +function hasDebug(bid) { + return !!bid.debug +} + +function hasAdPod(bid) { + return ( + bid.mediaTypes && + bid.mediaTypes.video && + bid.mediaTypes.video.context === ADPOD + ); +} + +/** + * Expand an adpod placement into a set of request objects according to the + * total adpod duration and the range of duration seconds. Sets minduration/ + * maxduration video property according to requireExactDuration configuration + */ +function createAdPodRequest(tags, adPodBid) { + const { durationRangeSec, requireExactDuration } = adPodBid.mediaTypes.video; + + const numberOfPlacements = getAdPodPlacementNumber(adPodBid.mediaTypes.video); + const maxDuration = utils.getMaxValueFromArray(durationRangeSec); + + const tagToDuplicate = tags.filter(tag => tag.uuid === adPodBid.bidId); + let request = utils.fill(...tagToDuplicate, numberOfPlacements); + + if (requireExactDuration) { + const divider = Math.ceil(numberOfPlacements / durationRangeSec.length); + const chunked = utils.chunk(request, divider); + + // each configured duration is set as min/maxduration for a subset of requests + durationRangeSec.forEach((duration, index) => { + chunked[index].map(tag => { + setVideoProperty(tag, 'minduration', duration); + setVideoProperty(tag, 'maxduration', duration); + }); + }); + } else { + // all maxdurations should be the same + request.map(tag => setVideoProperty(tag, 'maxduration', maxDuration)); + } + + return request; +} + +function getAdPodPlacementNumber(videoParams) { + const { adPodDurationSec, durationRangeSec, requireExactDuration } = videoParams; + const minAllowedDuration = utils.getMinValueFromArray(durationRangeSec); + const numberOfPlacements = Math.floor(adPodDurationSec / minAllowedDuration); + + return requireExactDuration + ? Math.max(numberOfPlacements, durationRangeSec.length) + : numberOfPlacements; +} + +function setVideoProperty(tag, key, value) { + if (utils.isEmpty(tag.video)) { tag.video = {}; } + tag.video[key] = value; +} + function getRtbBid(tag) { return tag && tag.ads && tag.ads.length && find(tag.ads, ad => ad.rtb); } @@ -442,20 +813,18 @@ function buildNativeRequest(params) { const requiredParams = NATIVE_MAPPING[key] && NATIVE_MAPPING[key].requiredParams; request[requestKey] = Object.assign({}, requiredParams, params[key]); - // minimum params are passed if no non-required params given on adunit - const minimumParams = NATIVE_MAPPING[key] && NATIVE_MAPPING[key].minimumParams; - - if (requiredParams && minimumParams) { - // subtract required keys from adunit keys - const adunitKeys = Object.keys(params[key]); - const requiredKeys = Object.keys(requiredParams); - const remaining = adunitKeys.filter(key => !includes(requiredKeys, key)); - - // if none are left over, the minimum params needs to be sent - if (remaining.length === 0) { - request[requestKey] = Object.assign({}, request[requestKey], minimumParams); + // convert the sizes of image/icon assets to proper format (if needed) + const isImageAsset = !!(requestKey === NATIVE_MAPPING.image.serverName || requestKey === NATIVE_MAPPING.icon.serverName); + if (isImageAsset && request[requestKey].sizes) { + let sizes = request[requestKey].sizes; + if (utils.isArrayOfNums(sizes) || (utils.isArray(sizes) && sizes.length > 0 && sizes.every(sz => utils.isArrayOfNums(sz)))) { + request[requestKey].sizes = transformSizes(request[requestKey].sizes); } } + + if (requestKey === NATIVE_MAPPING.privacyLink) { + request.privacy_supported = true; + } }); return request; diff --git a/modules/appnexusClientBidAdapter.js b/modules/appnexusClientBidAdapter.js new file mode 100644 index 00000000000..09bc8aad5b5 --- /dev/null +++ b/modules/appnexusClientBidAdapter.js @@ -0,0 +1,862 @@ +import { Renderer } from '../src/Renderer'; +import * as utils from '../src/utils'; +import { config } from '../src/config'; +import { registerBidder, getIabSubCategory } from '../src/adapters/bidderFactory'; +import { BANNER, NATIVE, VIDEO, ADPOD } from '../src/mediaTypes'; +import { auctionManager } from '../src/auctionManager'; +import find from 'core-js/library/fn/array/find'; +import includes from 'core-js/library/fn/array/includes'; +import { OUTSTREAM, INSTREAM } from '../src/video'; + +const BIDDER_CODE = 'appnexus_client'; +const URL = '//ib.adnxs.com/ut/v3/prebid'; +const VIDEO_TARGETING = ['id', 'mimes', 'minduration', 'maxduration', + 'startdelay', 'skippable', 'playback_method', 'frameworks']; +const USER_PARAMS = ['age', 'externalUid', 'segments', 'gender', 'dnt', 'language']; +const APP_DEVICE_PARAMS = ['geo', 'device_id']; // appid is collected separately +const DEBUG_PARAMS = ['enabled', 'dongle', 'member_id', 'debug_timeout']; +const NATIVE_MAPPING = { + body: 'description', + body2: 'desc2', + cta: 'ctatext', + image: { + serverName: 'main_image', + requiredParams: { required: true } + }, + icon: { + serverName: 'icon', + requiredParams: { required: true } + }, + sponsoredBy: 'sponsored_by', + privacyLink: 'privacy_link', + salePrice: 'saleprice', + displayUrl: 'displayurl' +}; +const SOURCE = 'pbjs'; +const MAX_IMPS_PER_REQUEST = 15; +const mappingFileUrl = '//acdn.adnxs.com/prebid/appnexus-mapping/mappings.json'; +const SCRIPT_TAG_START = ' includes(USER_PARAMS, param)) + .forEach(param => userObj[param] = userObjBid.params.user[param]); + } + + const appDeviceObjBid = find(bidRequests, hasAppDeviceInfo); + let appDeviceObj; + if (appDeviceObjBid && appDeviceObjBid.params && appDeviceObjBid.params.app) { + appDeviceObj = {}; + Object.keys(appDeviceObjBid.params.app) + .filter(param => includes(APP_DEVICE_PARAMS, param)) + .forEach(param => appDeviceObj[param] = appDeviceObjBid.params.app[param]); + } + + const appIdObjBid = find(bidRequests, hasAppId); + let appIdObj; + if (appIdObjBid && appIdObjBid.params && appDeviceObjBid.params.app && appDeviceObjBid.params.app.id) { + appIdObj = { + appid: appIdObjBid.params.app.id + }; + } + + let debugObj = {}; + let debugObjParams = {}; + const debugCookieName = 'apn_prebid_debug'; + const debugCookie = utils.getCookie(debugCookieName) || null; + + if (debugCookie) { + try { + debugObj = JSON.parse(debugCookie); + } catch (e) { + utils.logError('AppNexus Debug Auction Cookie Error:\n\n' + e); + } + } else { + const debugBidRequest = find(bidRequests, hasDebug); + if (debugBidRequest && debugBidRequest.debug) { + debugObj = debugBidRequest.debug; + } + } + + if (debugObj && debugObj.enabled) { + Object.keys(debugObj) + .filter(param => includes(DEBUG_PARAMS, param)) + .forEach(param => { + debugObjParams[param] = debugObj[param]; + }); + } + + const memberIdBid = find(bidRequests, hasMemberId); + const member = memberIdBid ? parseInt(memberIdBid.params.member, 10) : 0; + const schain = bidRequests[0].schain; + + const payload = { + tags: [...tags], + user: userObj, + sdk: { + source: SOURCE, + version: '$prebid.version$' + }, + schain: schain + }; + + if (member > 0) { + payload.member_id = member; + } + + if (appDeviceObjBid) { + payload.device = appDeviceObj + } + if (appIdObjBid) { + payload.app = appIdObj; + } + + if (config.getConfig('adpod.brandCategoryExclusion')) { + payload.brand_category_uniqueness = true; + } + + if (debugObjParams.enabled) { + payload.debug = debugObjParams; + utils.logInfo('AppNexus Debug Auction Settings:\n\n' + JSON.stringify(debugObjParams, null, 4)); + } + + if (bidderRequest && bidderRequest.gdprConsent) { + // note - objects for impbus use underscore instead of camelCase + payload.gdpr_consent = { + consent_string: bidderRequest.gdprConsent.consentString, + consent_required: bidderRequest.gdprConsent.gdprApplies + }; + } + + if (bidderRequest && bidderRequest.refererInfo) { + let refererinfo = { + rd_ref: encodeURIComponent(bidderRequest.refererInfo.referer), + rd_top: bidderRequest.refererInfo.reachedTop, + rd_ifs: bidderRequest.refererInfo.numIframes, + rd_stk: bidderRequest.refererInfo.stack.map((url) => encodeURIComponent(url)).join(',') + } + payload.referrer_detection = refererinfo; + } + + const hasAdPodBid = find(bidRequests, hasAdPod); + if (hasAdPodBid) { + bidRequests.filter(hasAdPod).forEach(adPodBid => { + const adPodTags = createAdPodRequest(tags, adPodBid); + // don't need the original adpod placement because it's in adPodTags + const nonPodTags = payload.tags.filter(tag => tag.uuid !== adPodBid.bidId); + payload.tags = [...nonPodTags, ...adPodTags]; + }); + } + + const rtusId = utils.deepAccess(bidRequests[0], `userId.criteortus.${BIDDER_CODE}.userid`); + if (rtusId) { + let tpuids = []; + tpuids.push({ + 'provider': 'criteo', + 'user_id': rtusId + }); + payload.tpuids = tpuids; + } + + const request = formatRequest(payload, bidderRequest); + return request; + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {*} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function(serverResponse, {bidderRequest}) { + serverResponse = serverResponse.body; + const bids = []; + if (!serverResponse || serverResponse.error) { + let errorMessage = `in response for ${bidderRequest.bidderCode} adapter`; + if (serverResponse && serverResponse.error) { errorMessage += `: ${serverResponse.error}`; } + utils.logError(errorMessage); + return bids; + } + + if (serverResponse.tags) { + serverResponse.tags.forEach(serverBid => { + const rtbBid = getRtbBid(serverBid); + if (rtbBid) { + if (rtbBid.cpm !== 0 && includes(this.supportedMediaTypes, rtbBid.ad_type)) { + const bid = newBid(serverBid, rtbBid, bidderRequest); + bid.mediaType = parseMediaType(rtbBid); + bids.push(bid); + } + } + }); + } + + if (serverResponse.debug && serverResponse.debug.debug_info) { + let debugHeader = 'AppNexus Debug Auction for Prebid\n\n' + let debugText = debugHeader + serverResponse.debug.debug_info + debugText = debugText + .replace(/(|)/gm, '\t') // Tables + .replace(/(<\/td>|<\/th>)/gm, '\n') // Tables + .replace(/^
    /gm, '') // Remove leading
    + .replace(/(
    \n|
    )/gm, '\n') //
    + .replace(/

    (.*)<\/h1>/gm, '\n\n===== $1 =====\n\n') // Header H1 + .replace(/(.*)<\/h[2-6]>/gm, '\n\n*** $1 ***\n\n') // Headers + .replace(/(<([^>]+)>)/igm, ''); // Remove any other tags + utils.logMessage('https://console.appnexus.com/docs/understanding-the-debug-auction'); + utils.logMessage(debugText); + } + + return bids; + }, + + /** + * @typedef {Object} mappingFileInfo + * @property {string} url mapping file json url + * @property {number} refreshInDays prebid stores mapping data in localstorage so you can return in how many days you want to update value stored in localstorage. + * @property {string} localStorageKey unique key to store your mapping json in localstorage + */ + + /** + * Returns mapping file info. This info will be used by bidderFactory to preload mapping file and store data in local storage + * @returns {mappingFileInfo} + */ + getMappingFileInfo: function() { + return { + url: mappingFileUrl, + refreshInDays: 7 + } + }, + + getUserSyncs: function(syncOptions) { + if (syncOptions.iframeEnabled) { + return [{ + type: 'iframe', + url: '//acdn.adnxs.com/ib/static/usersync/v3/async_usersync.html' + }]; + } + }, + + transformBidParams: function(params, isOpenRtb) { + params = utils.convertTypes({ + 'member': 'string', + 'invCode': 'string', + 'placementId': 'number', + 'keywords': utils.transformBidderParamKeywords + }, params); + + if (isOpenRtb) { + params.use_pmt_rule = (typeof params.usePaymentRule === 'boolean') ? params.usePaymentRule : false; + if (params.usePaymentRule) { delete params.usePaymentRule; } + + if (isPopulatedArray(params.keywords)) { + params.keywords.forEach(deleteValues); + } + + Object.keys(params).forEach(paramKey => { + let convertedKey = utils.convertCamelToUnderscore(paramKey); + if (convertedKey !== paramKey) { + params[convertedKey] = params[paramKey]; + delete params[paramKey]; + } + }); + } + + return params; + }, + + /** + * Add element selector to javascript tracker to improve native viewability + * @param {Bid} bid + */ + onBidWon: function(bid) { + if (bid.native) { + reloadViewabilityScriptWithCorrectParameters(bid); + } + } +} + +function isPopulatedArray(arr) { + return !!(utils.isArray(arr) && arr.length > 0); +} + +function deleteValues(keyPairObj) { + if (isPopulatedArray(keyPairObj.value) && keyPairObj.value[0] === '') { + delete keyPairObj.value; + } +} + +function reloadViewabilityScriptWithCorrectParameters(bid) { + let viewJsPayload = getAppnexusViewabilityScriptFromJsTrackers(bid.native.javascriptTrackers); + + if (viewJsPayload) { + let prebidParams = 'pbjs_adid=' + bid.adId + ';pbjs_auc=' + bid.adUnitCode; + + let jsTrackerSrc = getViewabilityScriptUrlFromPayload(viewJsPayload) + + let newJsTrackerSrc = jsTrackerSrc.replace('dom_id=%native_dom_id%', prebidParams); + + // find iframe containing script tag + let frameArray = document.getElementsByTagName('iframe'); + + // boolean var to modify only one script. That way if there are muliple scripts, + // they won't all point to the same creative. + let modifiedAScript = false; + + // first, loop on all ifames + for (let i = 0; i < frameArray.length && !modifiedAScript; i++) { + let currentFrame = frameArray[i]; + try { + // IE-compatible, see https://stackoverflow.com/a/3999191/2112089 + let nestedDoc = currentFrame.contentDocument || currentFrame.contentWindow.document; + + if (nestedDoc) { + // if the doc is present, we look for our jstracker + let scriptArray = nestedDoc.getElementsByTagName('script'); + for (let j = 0; j < scriptArray.length && !modifiedAScript; j++) { + let currentScript = scriptArray[j]; + if (currentScript.getAttribute('data-src') == jsTrackerSrc) { + currentScript.setAttribute('src', newJsTrackerSrc); + currentScript.setAttribute('data-src', ''); + if (currentScript.removeAttribute) { + currentScript.removeAttribute('data-src'); + } + modifiedAScript = true; + } + } + } + } catch (exception) { + // trying to access a cross-domain iframe raises a SecurityError + // this is expected and ignored + if (!(exception instanceof DOMException && exception.name === 'SecurityError')) { + // all other cases are raised again to be treated by the calling function + throw exception; + } + } + } + } +} + +function strIsAppnexusViewabilityScript(str) { + let regexMatchUrlStart = str.match(VIEWABILITY_URL_START); + let viewUrlStartInStr = regexMatchUrlStart != null && regexMatchUrlStart.length >= 1; + + let regexMatchFileName = str.match(VIEWABILITY_FILE_NAME); + let fileNameInStr = regexMatchFileName != null && regexMatchFileName.length >= 1; + + return str.startsWith(SCRIPT_TAG_START) && fileNameInStr && viewUrlStartInStr; +} + +function getAppnexusViewabilityScriptFromJsTrackers(jsTrackerArray) { + let viewJsPayload; + if (utils.isStr(jsTrackerArray) && strIsAppnexusViewabilityScript(jsTrackerArray)) { + viewJsPayload = jsTrackerArray; + } else if (utils.isArray(jsTrackerArray)) { + for (let i = 0; i < jsTrackerArray.length; i++) { + let currentJsTracker = jsTrackerArray[i]; + if (strIsAppnexusViewabilityScript(currentJsTracker)) { + viewJsPayload = currentJsTracker; + } + } + } + return viewJsPayload; +} + +function getViewabilityScriptUrlFromPayload(viewJsPayload) { + // extracting the content of the src attribute + // -> substring between src=" and " + let indexOfFirstQuote = viewJsPayload.indexOf('src="') + 5; // offset of 5: the length of 'src=' + 1 + let indexOfSecondQuote = viewJsPayload.indexOf('"', indexOfFirstQuote); + let jsTrackerSrc = viewJsPayload.substring(indexOfFirstQuote, indexOfSecondQuote); + return jsTrackerSrc; +} + +function formatRequest(payload, bidderRequest) { + let request = []; + + if (payload.tags.length > MAX_IMPS_PER_REQUEST) { + const clonedPayload = utils.deepClone(payload); + + utils.chunk(payload.tags, MAX_IMPS_PER_REQUEST).forEach(tags => { + clonedPayload.tags = tags; + const payloadString = JSON.stringify(clonedPayload); + request.push({ + method: 'POST', + url: URL, + data: payloadString, + bidderRequest + }); + }); + } else { + const payloadString = JSON.stringify(payload); + request = { + method: 'POST', + url: URL, + data: payloadString, + bidderRequest + }; + } + + return request; +} + +function newRenderer(adUnitCode, rtbBid, rendererOptions = {}) { + const renderer = Renderer.install({ + id: rtbBid.renderer_id, + url: rtbBid.renderer_url, + config: rendererOptions, + loaded: false, + adUnitCode + }); + + try { + renderer.setRender(outstreamRender); + } catch (err) { + utils.logWarn('Prebid Error calling setRender on renderer', err); + } + + renderer.setEventHandlers({ + impression: () => utils.logMessage('AppNexus outstream video impression event'), + loaded: () => utils.logMessage('AppNexus outstream video loaded event'), + ended: () => { + utils.logMessage('AppNexus outstream renderer video event'); + document.querySelector(`#${adUnitCode}`).style.display = 'none'; + } + }); + return renderer; +} + +/** + * Unpack the Server's Bid into a Prebid-compatible one. + * @param serverBid + * @param rtbBid + * @param bidderRequest + * @return Bid + */ +function newBid(serverBid, rtbBid, bidderRequest) { + const bidRequest = utils.getBidRequest(serverBid.uuid, [bidderRequest]); + const bid = { + requestId: serverBid.uuid, + cpm: rtbBid.cpm, + creativeId: rtbBid.creative_id, + dealId: rtbBid.deal_id, + currency: 'USD', + netRevenue: true, + ttl: 300, + adUnitCode: bidRequest.adUnitCode, + appnexus: { + buyerMemberId: rtbBid.buyer_member_id, + dealPriority: rtbBid.deal_priority, + dealCode: rtbBid.deal_code + } + }; + + if (rtbBid.advertiser_id) { + bid.meta = Object.assign({}, bid.meta, { advertiserId: rtbBid.advertiser_id }); + } + + if (rtbBid.rtb.video) { + // shared video properties used for all 3 contexts + Object.assign(bid, { + width: rtbBid.rtb.video.player_width, + height: rtbBid.rtb.video.player_height, + vastImpUrl: rtbBid.notify_url, + ttl: 3600 + }); + + const videoContext = utils.deepAccess(bidRequest, 'mediaTypes.video.context'); + switch (videoContext) { + case ADPOD: + const iabSubCatId = getIabSubCategory(bidRequest.bidder, rtbBid.brand_category_id); + bid.meta = Object.assign({}, bid.meta, { iabSubCatId }); + bid.video = { + context: ADPOD, + durationSeconds: Math.floor(rtbBid.rtb.video.duration_ms / 1000), + }; + bid.vastUrl = rtbBid.rtb.video.asset_url; + break; + case OUTSTREAM: + bid.adResponse = serverBid; + bid.adResponse.ad = bid.adResponse.ads[0]; + bid.adResponse.ad.video = bid.adResponse.ad.rtb.video; + bid.vastXml = rtbBid.rtb.video.content; + + if (rtbBid.renderer_url) { + const videoBid = find(bidderRequest.bids, bid => bid.bidId === serverBid.uuid); + const rendererOptions = utils.deepAccess(videoBid, 'renderer.options'); + bid.renderer = newRenderer(bid.adUnitCode, rtbBid, rendererOptions); + } + break; + case INSTREAM: + bid.vastUrl = rtbBid.rtb.video.asset_url; + break; + } + } else if (rtbBid.rtb[NATIVE]) { + const nativeAd = rtbBid.rtb[NATIVE]; + + // setting up the jsTracker: + // we put it as a data-src attribute so that the tracker isn't called + // until we have the adId (see onBidWon) + let jsTrackerDisarmed = rtbBid.viewability.config.replace('src=', 'data-src='); + + let jsTrackers = nativeAd.javascript_trackers; + + if (jsTrackers == undefined) { + jsTrackers = jsTrackerDisarmed; + } else if (utils.isStr(jsTrackers)) { + jsTrackers = [jsTrackers, jsTrackerDisarmed]; + } else { + jsTrackers.push(jsTrackerDisarmed); + } + + bid[NATIVE] = { + title: nativeAd.title, + body: nativeAd.desc, + body2: nativeAd.desc2, + cta: nativeAd.ctatext, + rating: nativeAd.rating, + sponsoredBy: nativeAd.sponsored, + privacyLink: nativeAd.privacy_link, + address: nativeAd.address, + downloads: nativeAd.downloads, + likes: nativeAd.likes, + phone: nativeAd.phone, + price: nativeAd.price, + salePrice: nativeAd.saleprice, + clickUrl: nativeAd.link.url, + displayUrl: nativeAd.displayurl, + clickTrackers: nativeAd.link.click_trackers, + impressionTrackers: nativeAd.impression_trackers, + javascriptTrackers: jsTrackers + }; + if (nativeAd.main_img) { + bid['native'].image = { + url: nativeAd.main_img.url, + height: nativeAd.main_img.height, + width: nativeAd.main_img.width, + }; + } + if (nativeAd.icon) { + bid['native'].icon = { + url: nativeAd.icon.url, + height: nativeAd.icon.height, + width: nativeAd.icon.width, + }; + } + } else { + Object.assign(bid, { + width: rtbBid.rtb.banner.width, + height: rtbBid.rtb.banner.height, + ad: rtbBid.rtb.banner.content + }); + try { + const url = rtbBid.rtb.trackers[0].impression_urls[0]; + const tracker = utils.createTrackPixelHtml(url); + bid.ad += tracker; + } catch (error) { + utils.logError('Error appending tracking pixel', error); + } + } + + return bid; +} + +function bidToTag(bid) { + const tag = {}; + tag.sizes = transformSizes(bid.sizes); + tag.primary_size = tag.sizes[0]; + tag.ad_types = []; + tag.uuid = bid.bidId; + if (bid.params.placementId) { + tag.id = parseInt(bid.params.placementId, 10); + } else { + tag.code = bid.params.invCode; + } + tag.allow_smaller_sizes = bid.params.allowSmallerSizes || false; + tag.use_pmt_rule = bid.params.usePaymentRule || false + tag.prebid = true; + tag.disable_psa = true; + if (bid.params.reserve) { + tag.reserve = bid.params.reserve; + } + if (bid.params.position) { + tag.position = {'above': 1, 'below': 2}[bid.params.position] || 0; + } + if (bid.params.trafficSourceCode) { + tag.traffic_source_code = bid.params.trafficSourceCode; + } + if (bid.params.privateSizes) { + tag.private_sizes = transformSizes(bid.params.privateSizes); + } + if (bid.params.supplyType) { + tag.supply_type = bid.params.supplyType; + } + if (bid.params.pubClick) { + tag.pubclick = bid.params.pubClick; + } + if (bid.params.extInvCode) { + tag.ext_inv_code = bid.params.extInvCode; + } + if (bid.params.externalImpId) { + tag.external_imp_id = bid.params.externalImpId; + } + if (!utils.isEmpty(bid.params.keywords)) { + let keywords = utils.transformBidderParamKeywords(bid.params.keywords); + + if (keywords.length > 0) { + keywords.forEach(deleteValues); + } + tag.keywords = keywords; + } + + if (bid.mediaType === NATIVE || utils.deepAccess(bid, `mediaTypes.${NATIVE}`)) { + tag.ad_types.push(NATIVE); + if (tag.sizes.length === 0) { + tag.sizes = transformSizes([1, 1]); + } + + if (bid.nativeParams) { + const nativeRequest = buildNativeRequest(bid.nativeParams); + tag[NATIVE] = {layouts: [nativeRequest]}; + } + } + + const videoMediaType = utils.deepAccess(bid, `mediaTypes.${VIDEO}`); + const context = utils.deepAccess(bid, 'mediaTypes.video.context'); + + if (bid.mediaType === VIDEO || videoMediaType) { + tag.ad_types.push(VIDEO); + } + + // instream gets vastUrl, outstream gets vastXml + if (bid.mediaType === VIDEO || (videoMediaType && context !== 'outstream')) { + tag.require_asset_url = true; + } + + if (bid.params.video) { + tag.video = {}; + // place any valid video params on the tag + Object.keys(bid.params.video) + .filter(param => includes(VIDEO_TARGETING, param)) + .forEach(param => tag.video[param] = bid.params.video[param]); + } + + if (bid.renderer) { + tag.video = Object.assign({}, tag.video, {custom_renderer_present: true}); + } + + let adUnit = find(auctionManager.getAdUnits(), au => bid.transactionId === au.transactionId); + if (adUnit && adUnit.mediaTypes && adUnit.mediaTypes.banner) { + tag.ad_types.push(BANNER); + } + + if (tag.ad_types.length === 0) { + delete tag.ad_types; + } + + return tag; +} + +/* Turn bid request sizes into ut-compatible format */ +function transformSizes(requestSizes) { + let sizes = []; + let sizeObj = {}; + + if (utils.isArray(requestSizes) && requestSizes.length === 2 && + !utils.isArray(requestSizes[0])) { + sizeObj.width = parseInt(requestSizes[0], 10); + sizeObj.height = parseInt(requestSizes[1], 10); + sizes.push(sizeObj); + } else if (typeof requestSizes === 'object') { + for (let i = 0; i < requestSizes.length; i++) { + let size = requestSizes[i]; + sizeObj = {}; + sizeObj.width = parseInt(size[0], 10); + sizeObj.height = parseInt(size[1], 10); + sizes.push(sizeObj); + } + } + + return sizes; +} + +function hasUserInfo(bid) { + return !!bid.params.user; +} + +function hasMemberId(bid) { + return !!parseInt(bid.params.member, 10); +} + +function hasAppDeviceInfo(bid) { + if (bid.params) { + return !!bid.params.app + } +} + +function hasAppId(bid) { + if (bid.params && bid.params.app) { + return !!bid.params.app.id + } + return !!bid.params.app +} + +function hasDebug(bid) { + return !!bid.debug +} + +function hasAdPod(bid) { + return ( + bid.mediaTypes && + bid.mediaTypes.video && + bid.mediaTypes.video.context === ADPOD + ); +} + +/** + * Expand an adpod placement into a set of request objects according to the + * total adpod duration and the range of duration seconds. Sets minduration/ + * maxduration video property according to requireExactDuration configuration + */ +function createAdPodRequest(tags, adPodBid) { + const { durationRangeSec, requireExactDuration } = adPodBid.mediaTypes.video; + + const numberOfPlacements = getAdPodPlacementNumber(adPodBid.mediaTypes.video); + const maxDuration = utils.getMaxValueFromArray(durationRangeSec); + + const tagToDuplicate = tags.filter(tag => tag.uuid === adPodBid.bidId); + let request = utils.fill(...tagToDuplicate, numberOfPlacements); + + if (requireExactDuration) { + const divider = Math.ceil(numberOfPlacements / durationRangeSec.length); + const chunked = utils.chunk(request, divider); + + // each configured duration is set as min/maxduration for a subset of requests + durationRangeSec.forEach((duration, index) => { + chunked[index].map(tag => { + setVideoProperty(tag, 'minduration', duration); + setVideoProperty(tag, 'maxduration', duration); + }); + }); + } else { + // all maxdurations should be the same + request.map(tag => setVideoProperty(tag, 'maxduration', maxDuration)); + } + + return request; +} + +function getAdPodPlacementNumber(videoParams) { + const { adPodDurationSec, durationRangeSec, requireExactDuration } = videoParams; + const minAllowedDuration = utils.getMinValueFromArray(durationRangeSec); + const numberOfPlacements = Math.floor(adPodDurationSec / minAllowedDuration); + + return requireExactDuration + ? Math.max(numberOfPlacements, durationRangeSec.length) + : numberOfPlacements; +} + +function setVideoProperty(tag, key, value) { + if (utils.isEmpty(tag.video)) { tag.video = {}; } + tag.video[key] = value; +} + +function getRtbBid(tag) { + return tag && tag.ads && tag.ads.length && find(tag.ads, ad => ad.rtb); +} + +function buildNativeRequest(params) { + const request = {}; + + // map standard prebid native asset identifier to /ut parameters + // e.g., tag specifies `body` but /ut only knows `description`. + // mapping may be in form {tag: ''} or + // {tag: {serverName: '', requiredParams: {...}}} + Object.keys(params).forEach(key => { + // check if one of the forms is used, otherwise + // a mapping wasn't specified so pass the key straight through + const requestKey = + (NATIVE_MAPPING[key] && NATIVE_MAPPING[key].serverName) || + NATIVE_MAPPING[key] || + key; + + // required params are always passed on request + const requiredParams = NATIVE_MAPPING[key] && NATIVE_MAPPING[key].requiredParams; + request[requestKey] = Object.assign({}, requiredParams, params[key]); + + // convert the sizes of image/icon assets to proper format (if needed) + const isImageAsset = !!(requestKey === NATIVE_MAPPING.image.serverName || requestKey === NATIVE_MAPPING.icon.serverName); + if (isImageAsset && request[requestKey].sizes) { + let sizes = request[requestKey].sizes; + if (utils.isArrayOfNums(sizes) || (utils.isArray(sizes) && sizes.length > 0 && sizes.every(sz => utils.isArrayOfNums(sz)))) { + request[requestKey].sizes = transformSizes(request[requestKey].sizes); + } + } + + if (requestKey === NATIVE_MAPPING.privacyLink) { + request.privacy_supported = true; + } + }); + + return request; +} + +function outstreamRender(bid) { + // push to render queue because ANOutstreamVideo may not be loaded yet + bid.renderer.push(() => { + window.ANOutstreamVideo.renderAd({ + tagId: bid.adResponse.tag_id, + sizes: [bid.getSize().split('x')], + targetId: bid.adUnitCode, // target div id to render video + uuid: bid.adResponse.uuid, + adResponse: bid.adResponse, + rendererOptions: bid.renderer.getConfig() + }, handleOutstreamRendererEvents.bind(null, bid)); + }); +} + +function handleOutstreamRendererEvents(bid, id, eventName) { + bid.renderer.handleVideoEvent({ id, eventName }); +} + +function parseMediaType(rtbBid) { + const adType = rtbBid.ad_type; + if (adType === VIDEO) { + return VIDEO; + } else if (adType === NATIVE) { + return NATIVE; + } else { + return BANNER; + } +} + +registerBidder(spec); diff --git a/modules/arteebeeBidAdapter.js b/modules/arteebeeBidAdapter.js index e8d319c8845..742ef13a598 100644 --- a/modules/arteebeeBidAdapter.js +++ b/modules/arteebeeBidAdapter.js @@ -1,7 +1,7 @@ -import * as utils from 'src/utils'; -import {BANNER} from 'src/mediaTypes'; -import {registerBidder} from 'src/adapters/bidderFactory'; -import {config} from 'src/config'; +import * as utils from '../src/utils'; +import {BANNER} from '../src/mediaTypes'; +import {registerBidder} from '../src/adapters/bidderFactory'; +import {config} from '../src/config'; const BIDDER_CODE = 'arteebee'; @@ -69,7 +69,7 @@ function makePrebidRequest(req, bidderRequest) { var host = req.params.host || DEFAULT_HOST; var ssp = req.params.ssp || DEFAULT_SSP; - var url = window.location.protocol + '//' + host + '/rtb/bid/' + ssp + '?type=json®ister=0'; + var url = window.originalLocation.protocol + '//' + host + '/rtb/bid/' + ssp + '?type=json®ister=0'; const payload = makeRtbRequest(req, bidderRequest); const payloadString = JSON.stringify(payload); @@ -96,7 +96,7 @@ function makeRtbRequest(req, bidderRequest) { 'tmax': config.getConfig('bidderTimeout') }; - if (req.params.coppa) { + if (config.getConfig('coppa') === true || req.params.coppa) { rtbReq.regs = {coppa: 1}; } @@ -125,7 +125,7 @@ function makeImp(req) { 'tagid': req.placementCode }; - if (window.location.protocol === 'https:') { + if (window.originalLocation.protocol === 'https:') { imp.secure = 1; } diff --git a/modules/atomxBidAdapter.js b/modules/atomxBidAdapter.js index f946841dffc..78d222fb929 100644 --- a/modules/atomxBidAdapter.js +++ b/modules/atomxBidAdapter.js @@ -1,5 +1,5 @@ -import * as utils from 'src/utils'; -import {registerBidder} from 'src/adapters/bidderFactory'; +import * as utils from '../src/utils'; +import {registerBidder} from '../src/adapters/bidderFactory'; const BIDDER_CODE = 'atomx'; @@ -8,7 +8,7 @@ function getDomain() { try { if ((domain === '') && (window.top == window)) { - domain = window.location.href; + domain = window.originalLocation.href; } if ((domain === '') && (window.top == window.parent)) { @@ -18,20 +18,20 @@ function getDomain() { if (domain == '') { var atomxt = 'atomxtest'; - // It should be impossible to change the window.location.ancestorOrigins. - window.location.ancestorOrigins[0] = atomxt; - if (window.location.ancestorOrigins[0] != atomxt) { - var ancestorOrigins = window.location.ancestorOrigins; + // It should be impossible to change the window.originalLocation.ancestorOrigins. + window.originalLocation.ancestorOrigins[0] = atomxt; + if (window.originalLocation.ancestorOrigins[0] != atomxt) { + var ancestorOrigins = window.originalLocation.ancestorOrigins; // If the length is 0 we are a javascript tag running in the main domain. - // But window.top != window or window.location.hostname is empty. + // But window.top != window or window.originalLocation.hostname is empty. if (ancestorOrigins.length == 0) { // This browser is so fucked up, just return an empty string. return ''; } - // ancestorOrigins is an array where [0] is our own window.location - // and [length-1] is the top window.location. + // ancestorOrigins is an array where [0] is our own window.originalLocation + // and [length-1] is the top window.originalLocation. domain = ancestorOrigins[ancestorOrigins.length - 1]; } } @@ -43,7 +43,7 @@ function getDomain() { } if (domain === '') { - domain = window.location.href; + domain = window.originalLocation.href; } return domain.substr(0, 512); diff --git a/modules/audienceNetworkBidAdapter.js b/modules/audienceNetworkBidAdapter.js index 544670863b8..43fbc2feeef 100644 --- a/modules/audienceNetworkBidAdapter.js +++ b/modules/audienceNetworkBidAdapter.js @@ -1,9 +1,9 @@ /** * @file AudienceNetwork adapter. */ -import { registerBidder } from 'src/adapters/bidderFactory'; -import { formatQS } from 'src/url'; -import { generateUUID, getTopWindowUrl, isSafariBrowser, convertTypes } from 'src/utils'; +import { registerBidder } from '../src/adapters/bidderFactory'; +import { formatQS } from '../src/url'; +import { generateUUID, getTopWindowUrl, convertTypes } from '../src/utils'; import findIndex from 'core-js/library/fn/array/find-index'; import includes from 'core-js/library/fn/array/includes'; @@ -18,7 +18,7 @@ const ttl = 600; const videoTtl = 3600; const platver = '$prebid.version$'; const platform = '241394079772386'; -const adapterver = '1.0.1'; +const adapterver = '1.3.0'; /** * Does this bid request contain valid parameters? @@ -73,6 +73,22 @@ const isValidSizeAndFormat = (size, format) => isValidNonSizedFormat(format) || isValidSize(flattenSize(size)); +/** + * Find a preferred entry, if any, from an array of valid sizes. + * @param {Array} acc + * @param {String} cur + */ +const sortByPreferredSize = (acc, cur) => + (cur === '300x250') ? [cur, ...acc] : [...acc, cur]; + +/** + * Map any deprecated size/formats to new values. + * @param {String} size + * @param {String} format + */ +const mapDeprecatedSizeAndFormat = (size, format) => + isFullWidth(format) ? ['300x250', null] : [size, format]; + /** * Is this a video format? * @param {String} format @@ -92,7 +108,7 @@ const isFullWidth = format => format === 'fullwidth'; * @param {String} format * @returns {String} */ -const sdkVersion = format => isVideo(format) ? '' : '5.5.web'; +const sdkVersion = format => isVideo(format) ? '' : '6.0.web'; /** * Which platform identifier should be used? @@ -107,9 +123,9 @@ const findPlatform = platforms => [...platforms.filter(Boolean), platform][0]; * @returns {String} "true" or "false" */ const isTestmode = () => Boolean( - window && window.location && - typeof window.location.search === 'string' && - window.location.search.indexOf('anhb_testmode') !== -1 + window && window.originalLocation && + typeof window.originalLocation.search === 'string' && + window.originalLocation.search.indexOf('anhb_testmode') !== -1 ).toString(); /** @@ -122,11 +138,34 @@ const isTestmode = () => Boolean( const createAdHtml = (placementId, format, bidId) => { const nativeStyle = format === 'native' ? '' : ''; const nativeContainer = format === 'native' ? '
    ' : ''; - return `${nativeStyle}
    - -${nativeContainer}
    `; + return ` + ${nativeStyle} + +
    + + + ${nativeContainer} +
    + +`; }; /** @@ -142,9 +181,9 @@ const getTopWindowUrlEncoded = () => encodeURIComponent(getTopWindowUrl()); * @param {Object} bids[].params * @param {String} bids[].params.placementId - Audience Network placement identifier * @param {String} bids[].params.platform - Audience Network platform identifier (optional) - * @param {String} bids[].params.format - Optional format, one of 'video', 'native' or 'fullwidth' if set + * @param {String} bids[].params.format - Optional format, one of 'video' or 'native' if set * @param {Array} bids[].sizes - list of desired advert sizes - * @param {Array} bids[].sizes[] - Size arrays [h,w]: should include one of [300, 250], [320, 50]: first matched size is used + * @param {Array} bids[].sizes[] - Size arrays [h,w]: should include one of [300, 250], [320, 50] * @returns {Array} List of URLs to fetch, plus formats and sizes for later use with interpretResponse */ const buildRequests = bids => { @@ -159,12 +198,14 @@ const buildRequests = bids => { bids.forEach(bid => bid.sizes .map(flattenSize) .filter(size => isValidSizeAndFormat(size, bid.params.format)) + .reduce(sortByPreferredSize, []) .slice(0, 1) - .forEach(size => { + .forEach(preferredSize => { + const [size, format] = mapDeprecatedSizeAndFormat(preferredSize, bid.params.format); placementids.push(bid.params.placementId); - adformats.push(bid.params.format || size); + adformats.push(format || size); sizes.push(size); - sdk.push(sdkVersion(bid.params.format)); + sdk.push(sdkVersion(format)); platforms.push(bid.params.platform); requestIds.push(bid.bidId); }) @@ -174,6 +215,7 @@ const buildRequests = bids => { const testmode = isTestmode(); const pageurl = getTopWindowUrlEncoded(); const platform = findPlatform(platforms); + const cb = generateUUID(); const search = { placementids, adformats, @@ -182,15 +224,13 @@ const buildRequests = bids => { sdk, adapterver, platform, - platver + platver, + cb }; const video = findIndex(adformats, isVideo); if (video !== -1) { [search.playerwidth, search.playerheight] = expandSize(sizes[video]); } - if (isSafariBrowser()) { - search.cb = generateUUID(); - } const data = formatQS(search); return [{ adformats, data, method, requestIds, sizes, url }]; diff --git a/modules/audienceNetworkBidAdapter.md b/modules/audienceNetworkBidAdapter.md index 72013c8610b..6147191f4b7 100644 --- a/modules/audienceNetworkBidAdapter.md +++ b/modules/audienceNetworkBidAdapter.md @@ -11,7 +11,7 @@ Maintainer: Lovell Fuller | Name | Scope | Description | Example | | :------------ | :------- | :---------------------------------------------- | :--------------------------------- | | `placementId` | required | The Placement ID from Audience Network | "555555555555555\_555555555555555" | -| `format` | optional | Format, one of "native", "fullwidth" or "video" | "native" | +| `format` | optional | Format, one of "native" or "video" | "native" | # Example ad units diff --git a/modules/beachfrontBidAdapter.js b/modules/beachfrontBidAdapter.js index 7ce1476627c..49b6b0c4edb 100644 --- a/modules/beachfrontBidAdapter.js +++ b/modules/beachfrontBidAdapter.js @@ -1,19 +1,21 @@ -import * as utils from 'src/utils'; -import { registerBidder } from 'src/adapters/bidderFactory'; -import { Renderer } from 'src/Renderer'; -import { VIDEO, BANNER } from 'src/mediaTypes'; +import * as utils from '../src/utils'; +import { parse as parseUrl } from '../src/url'; +import { config } from '../src/config'; +import { registerBidder } from '../src/adapters/bidderFactory'; +import { Renderer } from '../src/Renderer'; +import { VIDEO, BANNER } from '../src/mediaTypes'; import find from 'core-js/library/fn/array/find'; import includes from 'core-js/library/fn/array/includes'; -const ADAPTER_VERSION = '1.2'; +const ADAPTER_VERSION = '1.8'; const ADAPTER_NAME = 'BFIO_PREBID'; const OUTSTREAM = 'outstream'; -export const VIDEO_ENDPOINT = '//reachms.bfmio.com/bid.json?exchange_id='; -export const BANNER_ENDPOINT = '//display.bfmio.com/prebid_display'; -export const OUTSTREAM_SRC = '//player-cdn.beachfrontmedia.com/playerapi/loader/outstream.js'; +export const VIDEO_ENDPOINT = 'https://reachms.bfmio.com/bid.json?exchange_id='; +export const BANNER_ENDPOINT = 'https://display.bfmio.com/prebid_display'; +export const OUTSTREAM_SRC = 'https://player-cdn.beachfrontmedia.com/playerapi/loader/outstream.js'; -export const VIDEO_TARGETING = ['mimes']; +export const VIDEO_TARGETING = ['mimes', 'playbackmethod', 'maxduration', 'placement']; export const DEFAULT_MIMES = ['video/mp4', 'application/javascript']; let appId = ''; @@ -66,10 +68,11 @@ export const spec = { requestId: bidRequest.bidId, bidderCode: spec.code, vastUrl: response.url, + vastXml: response.vast, cpm: response.bidPrice, width: firstSize.w, height: firstSize.h, - creativeId: response.cmpId, + creativeId: response.crid || response.cmpId, renderer: context === OUTSTREAM ? createRenderer(bidRequest) : null, mediaType: VIDEO, currency: 'USD', @@ -121,12 +124,12 @@ export const spec = { } else if (syncOptions.iframeEnabled) { syncs.push({ type: 'iframe', - url: `//sync.bfmio.com/sync_iframe?ifg=1&id=${appId}&gdpr=${gdprApplies ? 1 : 0}&gc=${consentString || ''}&gce=1` + url: `https://sync.bfmio.com/sync_iframe?ifg=1&id=${appId}&gdpr=${gdprApplies ? 1 : 0}&gc=${consentString || ''}&gce=1` }); } else if (syncOptions.pixelEnabled) { syncs.push({ type: 'image', - url: `//sync.bfmio.com/syncb?pid=144&id=${appId}&gdpr=${gdprApplies ? 1 : 0}&gc=${consentString || ''}&gce=1` + url: `https://sync.bfmio.com/syncb?pid=144&id=${appId}&gdpr=${gdprApplies ? 1 : 0}&gc=${consentString || ''}&gce=1` }); } @@ -141,21 +144,21 @@ function createRenderer(bidRequest) { loaded: false }); - renderer.setRender(outstreamRender); - - return renderer; -} - -function outstreamRender(bid) { - bid.renderer.push(() => { - window.Beachfront.Player(bid.adUnitCode, { - ad_tag_url: bid.vastUrl, - width: bid.width, - height: bid.height, - expand_in_view: false, - collapse_on_complete: true + renderer.setRender(bid => { + bid.renderer.push(() => { + window.Beachfront.Player(bid.adUnitCode, { + adTagUrl: bid.vastUrl, + width: bid.width, + height: bid.height, + expandInView: getPlayerBidParam(bidRequest, 'expandInView', false), + collapseOnComplete: getPlayerBidParam(bidRequest, 'collapseOnComplete', true), + progressColor: getPlayerBidParam(bidRequest, 'progressColor'), + adPosterColor: getPlayerBidParam(bidRequest, 'adPosterColor') + }); }); }); + + return renderer; } function getFirstSize(sizes) { @@ -229,6 +232,11 @@ function getBannerBidParam(bid, key) { return utils.deepAccess(bid, 'params.banner.' + key) || utils.deepAccess(bid, 'params.' + key); } +function getPlayerBidParam(bid, key, defaultValue) { + let param = utils.deepAccess(bid, 'params.player.' + key); + return param === undefined ? defaultValue : param; +} + function isVideoBidValid(bid) { return isVideoBid(bid) && getVideoBidParam(bid, 'appId') && getVideoBidParam(bid, 'bidfloor'); } @@ -237,6 +245,19 @@ function isBannerBidValid(bid) { return isBannerBid(bid) && getBannerBidParam(bid, 'appId') && getBannerBidParam(bid, 'bidfloor'); } +function getTopWindowLocation(bidderRequest) { + let url = bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.referer; + return parseUrl(config.getConfig('pageUrl') || url, { decodeSearchAsString: true }); +} + +function getTopWindowReferrer() { + try { + return window.top.document.referrer; + } catch (e) { + return ''; + } +} + function getVideoTargetingParams(bid) { return Object.keys(Object(bid.params.video)) .filter(param => includes(VIDEO_TARGETING, param)) @@ -252,7 +273,7 @@ function createVideoRequestData(bid, bidderRequest) { let video = getVideoTargetingParams(bid); let appId = getVideoBidParam(bid, 'appId'); let bidfloor = getVideoBidParam(bid, 'bidfloor'); - let topLocation = utils.getTopWindowLocation(); + let topLocation = getTopWindowLocation(bidderRequest); let payload = { isPrebid: true, appId: appId, @@ -265,7 +286,9 @@ function createVideoRequestData(bid, bidderRequest) { mimes: DEFAULT_MIMES }, video), bidfloor: bidfloor, - secure: topLocation.protocol === 'https:' ? 1 : 0 + secure: topLocation.protocol === 'https:' ? 1 : 0, + displaymanager: ADAPTER_NAME, + displaymanagerver: ADAPTER_VERSION }], site: { page: topLocation.href, @@ -279,23 +302,44 @@ function createVideoRequestData(bid, bidderRequest) { js: 1, geo: {} }, - regs: {}, - user: {}, + regs: { + ext: {} + }, + user: { + ext: {} + }, cur: ['USD'] }; if (bidderRequest && bidderRequest.gdprConsent) { let { gdprApplies, consentString } = bidderRequest.gdprConsent; - payload.regs.ext = { gdpr: gdprApplies ? 1 : 0 }; - payload.user.ext = { consent: consentString }; + payload.regs.ext.gdpr = gdprApplies ? 1 : 0; + payload.user.ext.consent = consentString; + } + + if (bid.userId && bid.userId.tdid) { + payload.user.ext.eids = [{ + source: 'adserver.org', + uids: [{ + id: bid.userId.tdid, + ext: { + rtiPartner: 'TDID' + } + }] + }]; + } + + let connection = navigator.connection || navigator.webkitConnection; + if (connection && connection.effectiveType) { + payload.device.connectiontype = connection.effectiveType; } return payload; } function createBannerRequestData(bids, bidderRequest) { - let topLocation = utils.getTopWindowLocation(); - let referrer = utils.getTopWindowReferrer(); + let topLocation = getTopWindowLocation(bidderRequest); + let topReferrer = getTopWindowReferrer(); let slots = bids.map(bid => { return { slot: bid.adUnitCode, @@ -309,8 +353,8 @@ function createBannerRequestData(bids, bidderRequest) { page: topLocation.href, domain: topLocation.hostname, search: topLocation.search, - secure: topLocation.protocol === 'https:' ? 1 : 0, - referrer: referrer, + secure: topLocation.protocol.indexOf('https') === 0 ? 1 : 0, + referrer: topReferrer, ua: navigator.userAgent, deviceOs: getOsVersion(), isMobile: isMobile() ? 1 : 0, @@ -325,6 +369,10 @@ function createBannerRequestData(bids, bidderRequest) { payload.gdprConsent = consentString; } + if (bids[0] && bids[0].userId && bids[0].userId.tdid) { + payload.tdid = bids[0].userId.tdid; + } + return payload; } diff --git a/modules/beachfrontBidAdapter.md b/modules/beachfrontBidAdapter.md index 5defb358f7b..0a6b8b73da4 100644 --- a/modules/beachfrontBidAdapter.md +++ b/modules/beachfrontBidAdapter.md @@ -4,7 +4,7 @@ Module Name: Beachfront Bid Adapter Module Type: Bidder Adapter -Maintainer: johnsalis@beachfront.com +Maintainer: john@beachfront.com # Description @@ -85,4 +85,37 @@ Module that connects to Beachfront's demand sources ] } ]; -``` \ No newline at end of file +``` + +# Outstream Player Params Example +```javascript + var adUnits = [ + { + code: 'test-video-outstream', + mediaTypes: { + video: { + context: 'outstream', + playerSize: [ 640, 360 ] + } + }, + bids: [ + { + bidder: 'beachfront', + params: { + video: { + bidfloor: 0.01, + appId: '11bc5dd5-7421-4dd8-c926-40fa653bec76', + mimes: [ 'video/mp4', 'application/javascript' ] + }, + player: { + progressColor: '#50A8FA', + adPosterColor: '#FFF', + expandInView: false, + collapseOnComplete: true + } + } + } + ] + } + ]; +``` diff --git a/modules/betweenBidAdapter.js b/modules/betweenBidAdapter.js index 98140fe68e6..9da6b4dbe29 100644 --- a/modules/betweenBidAdapter.js +++ b/modules/betweenBidAdapter.js @@ -1,4 +1,4 @@ -import {registerBidder} from 'src/adapters/bidderFactory'; +import {registerBidder} from '../src/adapters/bidderFactory'; const BIDDER_CODE = 'between'; @@ -69,13 +69,13 @@ export const spec = { for (var i = 0; i < serverResponse.body.length; i++) { let bidResponse = { requestId: serverResponse.body[i].bidid, - cpm: serverResponse.body[i].cpm || 123, - width: serverResponse.body[i].w || 200, - height: serverResponse.body[i].h || 400, - ttl: serverResponse.body[i].ttl || 120, - creativeId: serverResponse.body[i].creativeid || 123, + cpm: serverResponse.body[i].cpm || 0, + width: serverResponse.body[i].w, + height: serverResponse.body[i].h, + ttl: serverResponse.body[i].ttl, + creativeId: serverResponse.body[i].creativeid, currency: serverResponse.body[i].currency || 'RUB', - netRevenue: serverResponse.body[i].netRevenue || false, + netRevenue: serverResponse.body[i].netRevenue || true, ad: serverResponse.body[i].ad }; bidResponses.push(bidResponse); @@ -106,10 +106,14 @@ export const spec = { }); } */ + // syncs.push({ + // type: 'iframe', + // url: '//acdn.adnxs.com/ib/static/usersync/v3/async_usersync.html' + // }); syncs.push({ type: 'iframe', - url: '//acdn.adnxs.com/ib/static/usersync/v3/async_usersync.html' - }) + url: '//ads.betweendigital.com/sspmatch-iframe' + }); return syncs; } } diff --git a/modules/betweenBidAdapter.md b/modules/betweenBidAdapter.md index 4ecd07e60bc..426d0aa2ed7 100644 --- a/modules/betweenBidAdapter.md +++ b/modules/betweenBidAdapter.md @@ -12,7 +12,7 @@ About us : http://betweendigital.com # Test Parameters -``` +```javascript var adUnits = [ { code: 'test-div', @@ -28,4 +28,69 @@ About us : http://betweendigital.com ] } ]; +``` + +Where: + +* s - the section id +* code - the id of the iframe tag to which the ads will be rendered + +# Example page + +```html + + + + + + + + + + +

    Prebid.js BetweenBidAdapter Test

    + + + ``` \ No newline at end of file diff --git a/modules/bidfluenceBidAdapter.js b/modules/bidfluenceBidAdapter.js new file mode 100644 index 00000000000..4a9c4433ee0 --- /dev/null +++ b/modules/bidfluenceBidAdapter.js @@ -0,0 +1,128 @@ +import * as utils from '../src/utils'; +import { registerBidder } from '../src/adapters/bidderFactory'; +const BIDDER_CODE = 'bidfluence'; + +function stdTimezoneOffset(t) { + const jan = new Date(t.getFullYear(), 0, 1); + const jul = new Date(t.getFullYear(), 6, 1); + return Math.max(jan.getTimezoneOffset(), jul.getTimezoneOffset()); +} +function dst(t) { + return t.getTimezoneOffset() < stdTimezoneOffset(t); +} +function getBdfTz(d) { + let tz = d.getTimezoneOffset(); + if (dst(d)) { + tz += 60; + } + return tz.toString(); +} +function getUTCDate() { + var m = new Date(); + var dateString = m.getUTCFullYear() + '/' + + ('0' + (m.getUTCMonth() + 1)).slice(-2) + '/' + + ('0' + m.getUTCDate()).slice(-2) + ' ' + + ('0' + m.getUTCHours()).slice(-2) + ':' + + ('0' + m.getUTCMinutes()).slice(-2) + ':' + + ('0' + m.getUTCSeconds()).slice(-2); + + return dateString; +} + +export const spec = { + code: BIDDER_CODE, + isBidRequestValid: function (bid) { + return !!bid.params.placementId || !!bid.params.publisherId; + }, + + buildRequests: function (validBidRequests, bidderRequest) { + const body = document.getElementsByTagName('body')[0]; + const refInfo = bidderRequest.refererInfo; + const gdpr = bidderRequest.gdprConsent; + const vpW = Math.max(window.innerWidth || body.clientWidth || 0) + 2; + const vpH = Math.max(window.innerHeight || body.clientHeight || 0) + 2; + const sr = screen.height > screen.width ? screen.height + 'x' + screen.width + 'x' + screen.colorDepth : screen.width + 'x' + screen.height + 'x' + screen.colorDepth; + + var payload = { + v: '2.0', + azr: true, + ck: utils.cookiesAreEnabled(), + re: refInfo ? refInfo.referer : '', + st: refInfo ? refInfo.stack : [], + tz: getBdfTz(new Date()), + sr: sr, + tm: bidderRequest.timeout, + vp: vpW + 'x' + vpH, + sdt: getUTCDate(), + top: refInfo ? refInfo.reachedTop : false, + gdpr: gdpr ? gdpr.gdprApplies : false, + gdprc: gdpr ? gdpr.consentString : '', + bids: [] + }; + + utils._each(validBidRequests, function (bidRequest) { + var params = bidRequest.params; + var sizes = utils.parseSizesInput(bidRequest.sizes)[0]; + var width = sizes.split('x')[0]; + var height = sizes.split('x')[1]; + + var currentBidPayload = { + bid: bidRequest.bidId, + tid: params.placementId, + pid: params.publisherId, + rp: params.reservePrice || 0, + w: width, + h: height + }; + + payload.bids.push(currentBidPayload); + }); + + const payloadString = JSON.stringify(payload); + return { + method: 'POST', + url: `//bdf${payload.bids[0].pid}.bidfluence.com/Prebid`, + data: payloadString, + options: { contentType: 'text/plain' } + }; + }, + + interpretResponse: function (serverResponse, bidRequest) { + const bidResponses = []; + const response = serverResponse.body; + + utils._each(response.Bids, function (currentResponse) { + var cpm = currentResponse.Cpm || 0; + + if (cpm > 0) { + const bidResponse = { + requestId: currentResponse.BidId, + cpm: cpm, + width: currentResponse.Width, + height: currentResponse.Height, + creativeId: currentResponse.CreativeId, + ad: currentResponse.Ad, + currency: 'USD', + netRevenue: true, + ttl: 360 + }; + bidResponses.push(bidResponse); + } + }); + + return bidResponses; + }, + + getUserSyncs: function (serverResponses) { + if (serverResponses.userSyncs) { + const syncs = serverResponses.UserSyncs.map((sync) => { + return { + type: sync.Type === 'ifr' ? 'iframe' : 'image', + url: sync.Url + }; + }); + return syncs; + } + } +}; +registerBidder(spec); diff --git a/modules/bidfluenceBidAdapter.md b/modules/bidfluenceBidAdapter.md new file mode 100644 index 00000000000..34dbb3d3a1c --- /dev/null +++ b/modules/bidfluenceBidAdapter.md @@ -0,0 +1,31 @@ +# Overview + +``` +Module Name: Bidfluence Adapter +Module Type: Bidder Adapter +Maintainer: integrations@bidfluence.com +prebid_1_0_supported : true +gdpr_supported: true +``` + +# Description + +Bidfluence adapter for prebid. + +# Test Parameters + +``` +var adUnits = [ + { + code: 'test-prebid', + sizes: [[300, 250]], + bids: [{ + bidder: 'bidfluence', + params: { + placementId: '1000', + publisherId: '1000' + } + }] + } +] +``` diff --git a/modules/bidglassBidAdapter.js b/modules/bidglassBidAdapter.js new file mode 100644 index 00000000000..f5991f7f3a5 --- /dev/null +++ b/modules/bidglassBidAdapter.js @@ -0,0 +1,134 @@ +import * as utils from '../src/utils'; +// import {config} from 'src/config'; +import {registerBidder} from '../src/adapters/bidderFactory'; + +const BIDDER_CODE = 'bidglass'; + +export const spec = { + code: BIDDER_CODE, + aliases: ['bg'], // short code + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function(bid) { + return !!(bid.params.adUnitId && !isNaN(parseFloat(bid.params.adUnitId)) && isFinite(bid.params.adUnitId)); + }, + /** + * Make a server request from the list of BidRequests. + * + * @param {validBidRequests[]} - an array of bids + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function(validBidRequests, bidderRequest) { + /* + Sample array entry for validBidRequests[]: + [{ + "bidder": "bidglass", + "bidId": "51ef8751f9aead", + "params": { + "adUnitId": 11, + ... + }, + "adUnitCode": "div-gpt-ad-1460505748561-0", + "transactionId": "d7b773de-ceaa-484d-89ca-d9f51b8d61ec", + "sizes": [[320,50],[300,250],[300,600]], + "bidderRequestId": "418b37f85e772c", + "auctionId": "18fd8b8b0bd757", + "bidRequestsCount": 1 + }] + */ + + let imps = []; + let getReferer = function() { + return window === window.top ? window.location.href : window.parent === window.top ? document.referrer : null; + }; + let getOrigins = function() { + var ori = [window.location.protocol + '//' + window.location.hostname]; + + if (window.location.ancestorOrigins) { + for (var i = 0; i < window.location.ancestorOrigins.length; i++) { + ori.push(window.location.ancestorOrigins[i]); + } + } else if (window !== window.top) { + // Derive the parent origin + var parts = document.referrer.split('/'); + + ori.push(parts[0] + '//' + parts[2]); + + if (window.parent !== window.top) { + // Additional unknown origins exist + ori.push('null'); + } + } + + return ori; + }; + + 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] + imps.push({ + bidId: bid.bidId, + sizes: bid.sizes, + adUnitId: utils.getBidIdParameter('adUnitId', bid.params) + }); + }); + + // Stuff to send: page URL + const bidReq = { + reqId: utils.getUniqueIdentifierStr(), + imps: imps, + ref: getReferer(), + ori: getOrigins() + }; + + let url = 'https://bid.glass/ad/hb.php?' + + `src=$$REPO_AND_VERSION$$`; + + return { + method: 'POST', + url: url, + data: JSON.stringify(bidReq), + options: { + contentType: 'text/plain', + withCredentials: false + } + } + }, + + /** + * 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) { + const bidResponses = []; + + utils._each(serverResponse.body.bidResponses, function(bid) { + bidResponses.push({ + requestId: bid.requestId, + cpm: parseFloat(bid.cpm), + width: parseInt(bid.width, 10), + height: parseInt(bid.height, 10), + creativeId: bid.creativeId, + dealId: bid.dealId || null, + currency: bid.currency || 'USD', + mediaType: bid.mediaType || 'banner', + netRevenue: true, + ttl: bid.ttl || 10, + ad: bid.ad + }); + }); + + return bidResponses; + } + +} + +registerBidder(spec); diff --git a/modules/bidglassBidAdapter.md b/modules/bidglassBidAdapter.md new file mode 100644 index 00000000000..5384a095314 --- /dev/null +++ b/modules/bidglassBidAdapter.md @@ -0,0 +1,34 @@ +# Overview + +``` +Module Name: Bid Glass Bid Adapter +Module Type: Bidder Adapter +Maintainer: dliebner@gmail.com +``` + +# Description + +Connects to Bid Glass and allows bids on ad units to compete within prebid. + +# Sample Ad Unit: For Publishers +``` +var adUnits = [{ + code: 'bg-test-rectangle', + sizes: [[300, 250]], + bids: [{ + bidder: 'bidglass', + params: { + adUnitId: '-1' + } + }] +},{ + code: 'bg-test-leaderboard', + sizes: [[728, 90]], + bids: [{ + bidder: 'bidglass', + params: { + adUnitId: '-1' + } + }] +}] +``` \ No newline at end of file diff --git a/modules/bidphysicsBidAdapter.js b/modules/bidphysicsBidAdapter.js new file mode 100644 index 00000000000..cbd76c8bc10 --- /dev/null +++ b/modules/bidphysicsBidAdapter.js @@ -0,0 +1,134 @@ +import {registerBidder} from '../src/adapters/bidderFactory'; +import * as utils from '../src/utils'; +import {BANNER} from '../src/mediaTypes'; + +const ENDPOINT_URL = '//exchange.bidphysics.com/auction'; + +const DEFAULT_BID_TTL = 30; +const DEFAULT_CURRENCY = 'USD'; +const DEFAULT_NET_REVENUE = true; + +export const spec = { + code: 'bidphysics', + aliases: ['yieldlift'], + supportedMediaTypes: [BANNER], + + isBidRequestValid: function (bid) { + return (!!bid.params.unitId && typeof bid.params.unitId === 'string') || + (!!bid.params.networkId && typeof bid.params.networkId === 'string') || + (!!bid.params.publisherId && typeof bid.params.publisherId === 'string'); + }, + + buildRequests: function (validBidRequests, bidderRequest) { + if (!validBidRequests || !bidderRequest) { + return; + } + const publisherId = validBidRequests[0].params.publisherId; + const networkId = validBidRequests[0].params.networkId; + const impressions = validBidRequests.map(bidRequest => ({ + id: bidRequest.bidId, + banner: { + format: bidRequest.sizes.map(sizeArr => ({ + w: sizeArr[0], + h: sizeArr[1] + })) + }, + ext: { + bidphysics: { + unitId: bidRequest.params.unitId + } + } + })); + + const openrtbRequest = { + id: bidderRequest.auctionId, + imp: impressions, + site: { + domain: window.location.hostname, + page: window.location.href, + ref: bidderRequest.refererInfo ? bidderRequest.refererInfo.referer || null : null + }, + ext: { + bidphysics: { + publisherId: publisherId, + networkId: networkId, + } + } + }; + + // apply gdpr + if (bidderRequest.gdprConsent) { + openrtbRequest.regs = {ext: {gdpr: bidderRequest.gdprConsent.gdprApplies ? 1 : 0}}; + openrtbRequest.user = {ext: {consent: bidderRequest.gdprConsent.consentString}}; + } + + const payloadString = JSON.stringify(openrtbRequest); + return { + method: 'POST', + url: ENDPOINT_URL, + data: payloadString, + }; + }, + + interpretResponse: function (serverResponse, request) { + const bidResponses = []; + const response = (serverResponse || {}).body; + // response is always one seat (bidphysics) with (optional) bids for each impression + if (response && response.seatbid && response.seatbid.length === 1 && response.seatbid[0].bid && response.seatbid[0].bid.length) { + response.seatbid[0].bid.forEach(bid => { + bidResponses.push({ + requestId: bid.impid, + cpm: bid.price, + width: bid.w, + height: bid.h, + ad: bid.adm, + ttl: DEFAULT_BID_TTL, + creativeId: bid.crid, + netRevenue: DEFAULT_NET_REVENUE, + currency: DEFAULT_CURRENCY, + }) + }) + } else { + utils.logInfo('bidphysics.interpretResponse :: no valid responses to interpret'); + } + return bidResponses; + }, + getUserSyncs: function (syncOptions, serverResponses) { + utils.logInfo('bidphysics.getUserSyncs', 'syncOptions', syncOptions, 'serverResponses', serverResponses); + let syncs = []; + + if (!syncOptions.iframeEnabled && !syncOptions.pixelEnabled) { + return syncs; + } + + serverResponses.forEach(resp => { + const userSync = utils.deepAccess(resp, 'body.ext.usersync'); + if (userSync) { + let syncDetails = []; + Object.keys(userSync).forEach(key => { + const value = userSync[key]; + if (value.syncs && value.syncs.length) { + syncDetails = syncDetails.concat(value.syncs); + } + }); + syncDetails.forEach(syncDetails => { + syncs.push({ + type: syncDetails.type === 'iframe' ? 'iframe' : 'image', + url: syncDetails.url + }); + }); + + if (!syncOptions.iframeEnabled) { + syncs = syncs.filter(s => s.type !== 'iframe') + } + if (!syncOptions.pixelEnabled) { + syncs = syncs.filter(s => s.type !== 'image') + } + } + }); + utils.logInfo('bidphysics.getUserSyncs result=%o', syncs); + return syncs; + }, + +}; +registerBidder(spec); diff --git a/modules/bidphysicsBidAdapter.md b/modules/bidphysicsBidAdapter.md new file mode 100644 index 00000000000..d7d8b355027 --- /dev/null +++ b/modules/bidphysicsBidAdapter.md @@ -0,0 +1,33 @@ +# Overview + +``` +Module Name: BidPhysics Bid Adapter +Module Type: Bidder Adapter +Maintainer: info@bidphysics.com +``` + +# Description + +Connects to BidPhysics exchange for bids. + +BidPhysics bid adapter supports Banner ads. + +# Test Parameters +``` +var adUnits = [ + { + code: 'banner-ad-div', + mediaTypes: { + banner: { + sizes: [[300, 250], [300,600]] + } + }, + bids: [{ + bidder: 'bidphysics', + params: { + unitId: 'bidphysics-test' + } + }] + } +]; +``` diff --git a/modules/bizzclickBidAdapter.js b/modules/bizzclickBidAdapter.js index 62ada43b970..a9b202b4c97 100644 --- a/modules/bizzclickBidAdapter.js +++ b/modules/bizzclickBidAdapter.js @@ -1,6 +1,6 @@ -import { registerBidder } from 'src/adapters/bidderFactory'; -import { BANNER, NATIVE, VIDEO } from 'src/mediaTypes'; -import * as utils from 'src/utils'; +import { registerBidder } from '../src/adapters/bidderFactory'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes'; +import * as utils from '../src/utils'; const BIDDER_CODE = 'bizzclick'; const URL = '//supply.bizzclick.com/?c=o&m=multi'; diff --git a/modules/brainyBidAdapter.js b/modules/brainyBidAdapter.js index e8e5bda9f37..a5d076d8fd0 100644 --- a/modules/brainyBidAdapter.js +++ b/modules/brainyBidAdapter.js @@ -1,6 +1,6 @@ -import * as utils from 'src/utils'; -import {registerBidder} from 'src/adapters/bidderFactory'; -import { BANNER } from 'src/mediaTypes'; +import * as utils from '../src/utils'; +import {registerBidder} from '../src/adapters/bidderFactory'; +import { BANNER } from '../src/mediaTypes'; const BIDDER_CODE = 'brainy'; const BASE_URL = '//proparm.jp/ssp/p/pbjs'; diff --git a/modules/bridgewellBidAdapter.js b/modules/bridgewellBidAdapter.js index 65fa49a25f8..cac827e5a5d 100644 --- a/modules/bridgewellBidAdapter.js +++ b/modules/bridgewellBidAdapter.js @@ -1,6 +1,6 @@ -import * as utils from 'src/utils'; -import {registerBidder} from 'src/adapters/bidderFactory'; -import {BANNER, NATIVE} from 'src/mediaTypes'; +import * as utils from '../src/utils'; +import {registerBidder} from '../src/adapters/bidderFactory'; +import {BANNER, NATIVE} from '../src/mediaTypes'; import find from 'core-js/library/fn/array/find'; const BIDDER_CODE = 'bridgewell'; diff --git a/modules/brightcomBidAdapter.js b/modules/brightcomBidAdapter.js new file mode 100644 index 00000000000..626aa99f5de --- /dev/null +++ b/modules/brightcomBidAdapter.js @@ -0,0 +1,246 @@ +import * as utils from '../src/utils'; +import * as url from '../src/url'; +import { registerBidder } from '../src/adapters/bidderFactory'; +import { BANNER } from '../src/mediaTypes'; +import { config } from '../src/config'; + +const BIDDER_CODE = 'brightcom'; +const URL = 'https://brightcombid.marphezis.com/hb'; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + isBidRequestValid, + buildRequests, + interpretResponse, + getUserSyncs +}; + +function buildRequests(bidReqs, bidderRequest) { + try { + let referrer = ''; + if (bidderRequest && bidderRequest.refererInfo) { + referrer = bidderRequest.refererInfo.referer; + } + const brightcomImps = []; + const publisherId = utils.getBidIdParameter('publisherId', bidReqs[0].params); + utils._each(bidReqs, 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)); + const processedSizes = bid.sizes.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); + + const imp = { + id: bid.bidId, + banner: { + format: processedSizes, + ext: { + viewability: viewabilityAmountRounded + } + }, + tagid: String(bid.adUnitCode) + }; + const bidFloor = utils.getBidIdParameter('bidFloor', bid.params); + if (bidFloor) { + imp.bidfloor = bidFloor; + } + brightcomImps.push(imp); + }); + const brightcomBidReq = { + id: utils.getUniqueIdentifierStr(), + imp: brightcomImps, + site: { + domain: url.parse(referrer).host, + page: referrer, + publisher: { + id: publisherId + } + }, + device: { + devicetype: _getDeviceType(), + w: screen.width, + h: screen.height + }, + tmax: config.getConfig('bidderTimeout') + }; + + return { + method: 'POST', + url: URL, + data: JSON.stringify(brightcomBidReq), + options: {contentType: 'text/plain', withCredentials: false} + }; + } catch (e) { + utils.logError(e, {bidReqs, bidderRequest}); + } +} + +function isBidRequestValid(bid) { + if (bid.bidder !== BIDDER_CODE || typeof bid.params === 'undefined') { + return false; + } + + if (typeof bid.params.publisherId === 'undefined') { + return false; + } + + return true; +} + +function interpretResponse(serverResponse) { + if (!serverResponse.body || typeof serverResponse.body != 'object') { + utils.logWarn('Brightcom server returned empty/non-json response: ' + JSON.stringify(serverResponse.body)); + return []; + } + const { body: {id, seatbid} } = serverResponse; + try { + const brightcomBidResponses = []; + if (id && + seatbid && + seatbid.length > 0 && + seatbid[0].bid && + seatbid[0].bid.length > 0) { + seatbid[0].bid.map(brightcomBid => { + brightcomBidResponses.push({ + requestId: brightcomBid.impid, + cpm: parseFloat(brightcomBid.price), + width: parseInt(brightcomBid.w), + height: parseInt(brightcomBid.h), + creativeId: brightcomBid.crid || brightcomBid.id, + currency: 'USD', + netRevenue: true, + mediaType: BANNER, + ad: _getAdMarkup(brightcomBid), + ttl: 60 + }); + }); + } + return brightcomBidResponses; + } catch (e) { + utils.logError(e, {id, seatbid}); + } +} + +// Don't do user sync for now +function getUserSyncs(syncOptions, responses, gdprConsent) { + return []; +} + +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 _getDeviceType() { + return _isMobile() ? 1 : _isConnectedTV() ? 3 : 2; +} + +function _getAdMarkup(bid) { + let adm = bid.adm; + if ('nurl' in bid) { + adm += utils.createTrackPixelHtml(bid.nurl); + } + return adm; +} + +function _isViewabilityMeasurable(element) { + return !_isIframe() && element !== null; +} + +function _getViewability(element, topWin, { w, h } = {}) { + return utils.getWindowTop().document.visibilityState === 'visible' + ? _getPercentInView(element, topWin, { w, h }) + : 0; +} + +function _isIframe() { + try { + return utils.getWindowSelf() !== utils.getWindowTop(); + } catch (e) { + return true; + } +} + +function _getMinSize(sizes) { + return sizes.reduce((min, size) => size.h * size.w < min.h * min.w ? size : min); +} + +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; +} + +function _getPercentInView(element, topWin, { w, h } = {}) { + const elementBoundingBox = _getBoundingBox(element, { w, h }); + + // Obtain the intersection of the element and the viewport + 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); + } + + // No overlap between element and the viewport; therefore, the element + // lies completely out of view + return 0; +} + +registerBidder(spec); diff --git a/modules/brightcomBidAdapter.md b/modules/brightcomBidAdapter.md new file mode 100644 index 00000000000..badc6ea94a4 --- /dev/null +++ b/modules/brightcomBidAdapter.md @@ -0,0 +1,38 @@ +# Overview + +``` +Module Name: Brightcom Bid Adapter +Module Type: Bidder Adapter +Maintainer: vladislavy@brightcom.com +``` + +# Description + +Brightcom's adapter integration to the Prebid library. + +# Test Parameters + +``` +var adUnits = [ + { + code: 'test-leaderboard', + sizes: [[728, 90]], + bids: [{ + bidder: 'brightcom', + params: { + publisherId: 2141020, + bidFloor: 0.01 + } + }] + }, { + code: 'test-banner', + sizes: [[300, 250]], + bids: [{ + bidder: 'brightcom', + params: { + publisherId: 2141020 + } + }] + } +] +``` diff --git a/modules/bucksenseBidAdapter.js b/modules/bucksenseBidAdapter.js new file mode 100644 index 00000000000..12a9e287f38 --- /dev/null +++ b/modules/bucksenseBidAdapter.js @@ -0,0 +1,124 @@ +import { registerBidder } from '../src/adapters/bidderFactory'; +import { BANNER } from '../src/mediaTypes'; +import * as utils from '../src/utils'; + +const WHO = 'BKSHBID-005'; +const BIDDER_CODE = 'bucksense'; +const URL = 'https://prebid.bksn.se:445/prebidjs/'; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + + /** + * Determines whether or not the given bid request is valid. + * + * @param {object} bid The bid to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function (bid) { + utils.logInfo(WHO + ' isBidRequestValid() - INPUT bid:', bid); + if (bid.bidder !== BIDDER_CODE || typeof bid.params === 'undefined') { + return false; + } + if (typeof bid.params.placementId === 'undefined') { + return false; + } + return true; + }, + + /** + * 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: function (validBidRequests, bidderRequest) { + utils.logInfo(WHO + ' buildRequests() - INPUT validBidRequests:', validBidRequests, 'INPUT bidderRequest:', bidderRequest); + let requests = []; + const len = validBidRequests.length; + for (let i = 0; i < len; i++) { + var bid = validBidRequests[i]; + var params = {}; + for (var key in bid.params) { + if (bid.params.hasOwnProperty(key)) { + params[key] = encodeURI(bid.params[key]); + } + } + delete bid.params; + var sizes = bid.sizes; + delete bid.sizes; + var sendData = { + 'pub_id': location.host, + 'pl_id': '' + params.placementId, + 'secure': (location.protocol === 'https:') ? 1 : 0, + 'href': encodeURI(location.href), + 'bid_id': bid.bidId, + 'params': params, + 'sizes': sizes, + '_bid': bidderRequest + }; + requests.push({ + method: 'POST', + url: URL, + data: sendData + }); + } + utils.logInfo(WHO + ' buildRequests() - requests:', requests); + return requests; + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {*} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function (serverResponse, request) { + utils.logInfo(WHO + ' interpretResponse() - INPUT serverResponse:', serverResponse, 'INPUT request:', request); + + const bidResponses = []; + if (serverResponse.body) { + var oResponse = serverResponse.body; + + var sRequestID = oResponse.requestId || ''; + var nCPM = oResponse.cpm || 0; + var nWidth = oResponse.width || 0; + var nHeight = oResponse.height || 0; + var nTTL = oResponse.ttl || 0; + var sCreativeID = oResponse.creativeId || 0; + var sCurrency = oResponse.currency || 'USD'; + var bNetRevenue = oResponse.netRevenue || true; + var sAd = oResponse.ad || ''; + + if (request && sRequestID.length == 0) { + utils.logInfo(WHO + ' interpretResponse() - use RequestID from Placments'); + sRequestID = request.data.bid_id || ''; + } + + if (request && request.data.params.hasOwnProperty('testcpm')) { + utils.logInfo(WHO + ' interpretResponse() - use Test CPM '); + nCPM = request.data.params.testcpm; + } + + let bidResponse = { + requestId: sRequestID, + cpm: nCPM, + width: nWidth, + height: nHeight, + ttl: nTTL, + creativeId: sCreativeID, + currency: sCurrency, + netRevenue: bNetRevenue, + ad: sAd + }; + bidResponses.push(bidResponse); + } else { + utils.logInfo(WHO + ' interpretResponse() - serverResponse not valid'); + } + utils.logInfo(WHO + ' interpretResponse() - return', bidResponses); + return bidResponses; + }, + +}; +registerBidder(spec); diff --git a/modules/bucksenseBidAdapter.md b/modules/bucksenseBidAdapter.md new file mode 100644 index 00000000000..a240a2c31d8 --- /dev/null +++ b/modules/bucksenseBidAdapter.md @@ -0,0 +1,38 @@ +# Overview + +``` +Module Name: Bucksense Bidder Adapter +Module Type: Bidder Adapter +Maintainer: stefano.dechicchis@bucksense.com +``` + +# Description + +Use `bucksense` as bidder. + +`placementId` is required, use the Placement ID received by Bucksense. + + +Module that connects to Example's demand sources + +## AdUnits configuration example +``` + var adUnits = [ + { + code: 'test-div', + mediaTypes: { + banner: { + sizes: [[300, 250]], + } + }, + bids: [ + { + bidder: "bucksense", + params: { + placementId : 1000 + } + } + ] + } + ]; +``` \ No newline at end of file diff --git a/modules/buzzoolaBidAdapter.js b/modules/buzzoolaBidAdapter.js new file mode 100644 index 00000000000..da2a3b30c2e --- /dev/null +++ b/modules/buzzoolaBidAdapter.js @@ -0,0 +1,108 @@ +import * as utils from '../src/utils'; +import {registerBidder} from '../src/adapters/bidderFactory'; +import {BANNER, VIDEO} from '../src/mediaTypes'; +import {Renderer} from '../src/Renderer'; +import {OUTSTREAM} from '../src/video'; + +const BIDDER_CODE = 'buzzoola'; +const ENDPOINT = 'https://exchange.buzzoola.com/ssp/prebidjs'; +const RENDERER_SRC = 'https://tube.buzzoola.com/new/build/buzzlibrary.js'; + +export const spec = { + code: BIDDER_CODE, + aliases: ['buzzoolaAdapter'], + 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: function (bid) { + let types = bid.mediaTypes; + return !!(bid && bid.mediaTypes && (types.banner || types.video) && bid.params && bid.params.placementId); + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param {BidRequest[]} validBidRequests an array of bids + * @param bidderRequest + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function (validBidRequests, bidderRequest) { + return { + url: ENDPOINT, + method: 'POST', + data: bidderRequest, + } + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} serverResponse A successful response from the server. + * @param bidderRequest + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function ({body}, {data}) { + let requestBids = {}; + let response; + + try { + response = JSON.parse(body); + } catch (ex) { + response = body; + } + + if (!Array.isArray(response)) response = []; + + data.bids.forEach(bid => requestBids[bid.bidId] = bid); + + return response.map(bid => { + let requestBid = requestBids[bid.requestId]; + let context = utils.deepAccess(requestBid, 'mediaTypes.video.context'); + let validBid = utils.deepClone(bid); + + if (validBid.mediaType === VIDEO && context === OUTSTREAM) { + let renderer = Renderer.install({ + id: validBid.requestId, + url: RENDERER_SRC, + loaded: false + }); + + renderer.setRender(setOutstreamRenderer); + validBid.renderer = renderer + } + + return validBid; + }); + } +}; + +/** + * Initialize Buzzoola Outstream player + * + * @param bid + */ +function setOutstreamRenderer(bid) { + let adData = JSON.parse(bid.ad); + let unitSettings = utils.deepAccess(adData, 'placement.unit_settings'); + let extendedSettings = { + width: '' + bid.width, + height: '' + bid.height, + container_height: '' + bid.height + }; + + adData.placement = Object.assign({}, adData.placement); + adData.placement.unit_settings = Object.assign({}, unitSettings, extendedSettings); + + bid.renderer.push(() => { + window.Buzzoola.Core.install(document.querySelector(`#${bid.adUnitCode}`), { + data: adData + }); + }); +} + +registerBidder(spec); diff --git a/modules/buzzoolaBidAdapter.md b/modules/buzzoolaBidAdapter.md new file mode 100644 index 00000000000..aec3eda6c58 --- /dev/null +++ b/modules/buzzoolaBidAdapter.md @@ -0,0 +1,72 @@ +# Overview + +``` +Module Name: Buzzoola Bid Adapter +Module Type: Bidder Adapter +Maintainer: devteam@buzzoola.com +``` + +# Description + +Connects to Buzzoola exchange for bids. + +Buzzoola bid adapter supports Banner and Video (instream and outstream). + +# Test Parameters +``` +var adUnits = [ + // Banner adUnit + { + code: 'banner-div', + mediaTypes: { + banner: { + sizes: [[240, 400], [300, 600]], + } + }, + bids: [{ + bidder: 'buzzoola', + params: { + placementId: 417846 + } + }] + }, + // Video instream adUnit + { + code: 'video-instream', + mediaTypes: { + video: { + context: 'instream', + playerSize: [640, 380], + mimes: ['video/mp4'], + minduration: 1, + maxduration: 2, + } + }, + bids: [{ + bidder: 'buzzoola', + params: { + placementId: 417845 + } + }] + }, + // Video outstream adUnit + { + code: 'video-outstream', + mediaTypes: { + video: { + context: 'outstream', + playerSize: [640, 380], + mimes: ['video/mp4'], + minduration: 1, + maxduration: 2, + } + }, + bids: [{ + bidder: 'buzzoola', + params: { + placementId: 417845 + } + }] + } +]; +``` diff --git a/modules/c1xBidAdapter.js b/modules/c1xBidAdapter.js index ff1b011f787..1e8d3cf2e0a 100644 --- a/modules/c1xBidAdapter.js +++ b/modules/c1xBidAdapter.js @@ -1,6 +1,6 @@ -import { registerBidder } from 'src/adapters/bidderFactory'; -import * as utils from 'src/utils'; -import { userSync } from 'src/userSync'; +import { registerBidder } from '../src/adapters/bidderFactory'; +import * as utils from '../src/utils'; +import { userSync } from '../src/userSync'; const BIDDER_CODE = 'c1x'; const URL = 'https://ht.c1exchange.com/ht'; diff --git a/modules/categoryTranslation.js b/modules/categoryTranslation.js new file mode 100644 index 00000000000..091b16c8211 --- /dev/null +++ b/modules/categoryTranslation.js @@ -0,0 +1,96 @@ +/** + * This module translates iab category to freewheel industry using translation mapping file + * Publisher can set translation file by using setConfig method + * + * Example: + * config.setConfig({ + * 'brandCategoryTranslation': { + * 'translationFile': 'http://sample.com' + * } + * }); + * If publisher has not defined translation file than prebid will use default prebid translation file provided here //cdn.jsdelivr.net/gh/prebid/category-mapping-file@1/freewheel-mapping.json + */ + +import { config } from '../src/config'; +import { setupBeforeHookFnOnce, hook } from '../src/hook'; +import { ajax } from '../src/ajax'; +import { timestamp, logError, setDataInLocalStorage, getDataFromLocalStorage } from '../src/utils'; +import { addBidResponse } from '../src/auction'; + +const DEFAULT_TRANSLATION_FILE_URL = 'https://cdn.jsdelivr.net/gh/prebid/category-mapping-file@1/freewheel-mapping.json'; +const DEFAULT_IAB_TO_FW_MAPPING_KEY = 'iabToFwMappingkey'; +const DEFAULT_IAB_TO_FW_MAPPING_KEY_PUB = 'iabToFwMappingkeyPub'; +const refreshInDays = 1; + +export const registerAdserver = hook('async', function(adServer) { + let url; + if (adServer === 'freewheel') { + url = DEFAULT_TRANSLATION_FILE_URL; + initTranslation(url, DEFAULT_IAB_TO_FW_MAPPING_KEY); + } +}, 'registerAdserver'); +registerAdserver(); + +export function getAdserverCategoryHook(fn, adUnitCode, bid) { + if (!bid) { + return fn.call(this, adUnitCode); // if no bid, call original and let it display warnings + } + + if (!config.getConfig('adpod.brandCategoryExclusion')) { + return fn.call(this, adUnitCode, bid); + } + + let localStorageKey = (config.getConfig('brandCategoryTranslation.translationFile')) ? DEFAULT_IAB_TO_FW_MAPPING_KEY_PUB : DEFAULT_IAB_TO_FW_MAPPING_KEY; + + if (bid.meta && !bid.meta.adServerCatId) { + let mapping = getDataFromLocalStorage(localStorageKey); + if (mapping) { + try { + mapping = JSON.parse(mapping); + } catch (error) { + logError('Failed to parse translation mapping file'); + } + if (bid.meta.iabSubCatId && mapping['mapping'] && mapping['mapping'][bid.meta.iabSubCatId]) { + bid.meta.adServerCatId = mapping['mapping'][bid.meta.iabSubCatId]['id']; + } else { + // This bid will be automatically ignored by adpod module as adServerCatId was not found + bid.meta.adServerCatId = undefined; + } + } else { + logError('Translation mapping data not found in local storage'); + } + } + fn.call(this, adUnitCode, bid); +} + +export function initTranslation(url, localStorageKey) { + setupBeforeHookFnOnce(addBidResponse, getAdserverCategoryHook, 50); + let mappingData = getDataFromLocalStorage(localStorageKey); + if (!mappingData || timestamp() < mappingData.lastUpdated + refreshInDays * 24 * 60 * 60 * 1000) { + ajax(url, + { + success: (response) => { + try { + response = JSON.parse(response); + response['lastUpdated'] = timestamp(); + setDataInLocalStorage(localStorageKey, JSON.stringify(response)); + } catch (error) { + logError('Failed to parse translation mapping file'); + } + }, + error: () => { + logError('Failed to load brand category translation file.') + } + }, + ); + } +} + +function setConfig(config) { + if (config.translationFile) { + // if publisher has defined the translation file, preload that file here + initTranslation(config.translationFile, DEFAULT_IAB_TO_FW_MAPPING_KEY_PUB); + } +} + +config.getConfig('brandCategoryTranslation', config => setConfig(config.brandCategoryTranslation)); diff --git a/modules/ccxBidAdapter.js b/modules/ccxBidAdapter.js index ee287592975..226ed44f6da 100644 --- a/modules/ccxBidAdapter.js +++ b/modules/ccxBidAdapter.js @@ -1,6 +1,6 @@ -import * as utils from 'src/utils' -import { registerBidder } from 'src/adapters/bidderFactory' -import { config } from 'src/config' +import * as utils from '../src/utils' +import { registerBidder } from '../src/adapters/bidderFactory' +import { config } from '../src/config' const BIDDER_CODE = 'ccx' const BID_URL = 'https://delivery.clickonometrics.pl/ortb/prebid/bid' const SUPPORTED_VIDEO_PROTOCOLS = [2, 3, 5, 6] diff --git a/modules/cedatoBidAdapter.js b/modules/cedatoBidAdapter.js new file mode 100644 index 00000000000..d81ae858869 --- /dev/null +++ b/modules/cedatoBidAdapter.js @@ -0,0 +1,213 @@ +import * as utils from '../src/utils'; +import { registerBidder } from '../src/adapters/bidderFactory'; +import { BANNER, VIDEO } from '../src/mediaTypes'; + +const BIDDER_CODE = 'cedato'; +const BID_URL = '//h.cedatoplayer.com/hb'; +const SYNC_URL = '//h.cedatoplayer.com/hb_usync?uid={UUID}'; +const COOKIE_NAME = 'hb-cedato-id'; +const UUID_LEN = 36; +const TTL = 10000; +const CURRENCY = 'USD'; +const FIRST_PRICE = 1; +const NET_REVENUE = true; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO], + + isBidRequestValid: function(bid) { + return !!( + bid && + bid.params && + bid.params.player_id && + utils.checkCookieSupport() && + utils.cookiesAreEnabled() + ); + }, + + buildRequests: function(bidRequests, bidderRequest) { + const req = bidRequests[Math.floor(Math.random() * bidRequests.length)]; + const params = req.params; + const at = FIRST_PRICE; + const site = { id: params.player_id, domain: document.domain }; + const device = { ua: navigator.userAgent }; + const user = { id: getUserID() } + const currency = CURRENCY; + const tmax = bidderRequest.timeout; + + const imp = bidRequests.map(req => { + const banner = getMediaType(req, 'banner'); + const video = getMediaType(req, 'video'); + const bidfloor = params.bidfloor; + const bidId = req.bidId; + const adUnitCode = req.adUnitCode; + + return { + bidId, + banner, + video, + adUnitCode, + bidfloor, + }; + }); + + const payload = { + version: '$prebid.version$', + at, + site, + device, + user, + imp, + currency, + tmax, + }; + + if (bidderRequest && bidderRequest.gdprConsent) { + payload.gdpr_consent = { + consent_string: bidderRequest.gdprConsent.consentString, + consent_required: bidderRequest.gdprConsent.gdprApplies + }; + } + + return { + method: 'POST', + url: params.bid_url || BID_URL, + data: JSON.stringify(payload), + bidderRequest + }; + }, + + interpretResponse: function(resp, {bidderRequest}) { + resp = resp.body; + const bids = []; + + if (!resp) { + return bids; + } + + resp.seatbid[0].bid.map(serverBid => { + const bid = newBid(serverBid, bidderRequest); + bid.currency = resp.cur; + bids.push(bid); + }); + + return bids; + }, + + getUserSyncs: function(syncOptions, resps, gdprConsent) { + const syncs = []; + if (syncOptions.iframeEnabled) { + syncs.push(getSync('iframe', gdprConsent)); + } else if (syncOptions.pixelEnabled) { + syncs.push(getSync('image', gdprConsent)); + } + return syncs; + } +} + +function getMediaType(req, type) { + const { mediaTypes } = req; + + if (!mediaTypes) { + return; + } + + switch (type) { + case 'banner': + if (mediaTypes.banner) { + const { sizes } = mediaTypes.banner; + return { + format: getFormats(sizes) + }; + } + break; + + case 'video': + if (mediaTypes.video) { + const { playerSize, context } = mediaTypes.video; + return { + context: context, + format: getFormats(playerSize) + }; + } + } +} + +function newBid(serverBid, bidderRequest) { + const bidRequest = utils.getBidRequest(serverBid.uuid, [bidderRequest]); + + const cpm = serverBid.price; + const requestId = serverBid.uuid; + const width = serverBid.w; + const height = serverBid.h; + const creativeId = serverBid.crid; + const dealId = serverBid.dealid; + const mediaType = serverBid.media_type; + const netRevenue = NET_REVENUE; + const ttl = TTL; + + const bid = { + cpm, + requestId, + width, + height, + mediaType, + creativeId, + dealId, + netRevenue, + ttl, + }; + + if (mediaType == 'video') { + const videoContext = utils.deepAccess(bidRequest, 'mediaTypes.video.context'); + + if (videoContext == 'instream') { + bid.vastUrl = serverBid.vast_url; + bid.vastImpUrl = serverBid.notify_url; + } + } else { + bid.ad = serverBid.adm; + } + + return bid; +} + +const getSync = (type, gdprConsent) => { + const uuid = getUserID(); + const syncUrl = SYNC_URL; + let params = '&type=' + type; + 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}`; + } + } + return { + type: type, + url: syncUrl.replace('{UUID}', uuid) + params, + }; +} + +const getUserID = () => { + const cookieName = COOKIE_NAME; + const uuidLen = UUID_LEN; + + const i = document.cookie.indexOf(cookieName); + + if (i === -1) { + const uuid = utils.generateUUID(); + document.cookie = `${cookieName}=${uuid}; path=/`; + return uuid; + } + + const j = i + cookieName.length + 1; + return document.cookie.substring(j, j + uuidLen); +}; + +const getFormats = arr => arr.map((s) => { + return { w: s[0], h: s[1] }; +}); + +registerBidder(spec); diff --git a/modules/cedatoBidAdapter.md b/modules/cedatoBidAdapter.md new file mode 100644 index 00000000000..088f8a4baef --- /dev/null +++ b/modules/cedatoBidAdapter.md @@ -0,0 +1,53 @@ +# Overview + +``` +Module Name: Cedato Bidder Adapter +Module Type: Bidder Adapter +Maintainer: alexk@cedato.com +``` + +# Description + +Connects to Cedato Bidder. +Player ID must be replaced. You can approach your Cedato account manager to get one. + +# Test Parameters +``` +var adUnits = [ + // Banner + { + code: 'div-gpt-ad-1460505748561-0', + mediaTypes: { + banner: { + // You can choose one of them + sizes: [ + [300, 250], + [300, 600], + [240, 400], + [728, 90], + ] + } + }, + bids: [ + { + bidder: "cedato", + params: { + player_id: 1450133326, + } + } + ] + } +]; + +pbjs.que.push(() => { + pbjs.setConfig({ + userSync: { + syncEnabled: true, + enabledBidders: ['cedato'], + pixelEnabled: true, + syncsPerBidder: 200, + syncDelay: 100, + }, + }); +}); +``` diff --git a/modules/cleanmedianetBidAdapter.js b/modules/cleanmedianetBidAdapter.js new file mode 100644 index 00000000000..15871e1a6ae --- /dev/null +++ b/modules/cleanmedianetBidAdapter.js @@ -0,0 +1,300 @@ +import * as utils from '../src/utils'; +import {parse} from '../src/url'; +import {registerBidder} from '../src/adapters/bidderFactory'; +import {config} from '../src/config'; +import {Renderer} from '../src/Renderer'; +import {BANNER, VIDEO} from '../src/mediaTypes'; + +export const helper = { + startsWith: function (str, search) { + return str.substr(0, search.length) === search; + }, + getMediaType: function (bid) { + if (bid.ext) { + if (bid.ext.media_type) { + return bid.ext.media_type.toLowerCase(); + } else if (bid.ext.vast_url) { + return VIDEO; + } else { + return BANNER; + } + } + return BANNER; + } +}; + +export const spec = { + code: 'cleanmedianet', + aliases: [], + supportedMediaTypes: [BANNER, VIDEO], + + isBidRequestValid: function (bid) { + return ( + !!bid.params.supplyPartnerId && + typeof bid.params.supplyPartnerId === 'string' && + (typeof bid.params.bidfloor === 'undefined' || + typeof bid.params.bidfloor === 'number') && + (typeof bid.params['adpos'] === 'undefined' || + typeof bid.params['adpos'] === 'number') && + (typeof bid.params['protocols'] === 'undefined' || + Array.isArray(bid.params['protocols'])) && + (typeof bid.params.instl === 'undefined' || + bid.params.instl === 0 || + bid.params.instl === 1) + ); + }, + + buildRequests: function (validBidRequests, bidderRequest) { + return validBidRequests.map(bidRequest => { + const { + adUnitCode, + auctionId, + mediaTypes, + params, + sizes, + transactionId + } = bidRequest; + const baseEndpoint = 'https://bidder.cleanmediaads.com'; + const rtbEndpoint = + `${baseEndpoint}/r/${ + params.supplyPartnerId + }/bidr?rformat=open_rtb&reqformat=rtb_json&bidder=prebid` + + (params.query ? '&' + params.query : ''); + let url = + config.getConfig('pageUrl') || bidderRequest.refererInfo.referer; + + const rtbBidRequest = { + id: auctionId, + site: { + domain: parse(url).hostname, + page: url, + ref: bidderRequest.refererInfo.referer + }, + device: { + ua: navigator.userAgent + }, + imp: [], + ext: {} + }; + + if ( + bidderRequest.gdprConsent && + bidderRequest.gdprConsent.consentString && + bidderRequest.gdprConsent.gdprApplies + ) { + rtbBidRequest.ext.gdpr_consent = { + consent_string: bidderRequest.gdprConsent.consentString, + consent_required: bidderRequest.gdprConsent.gdprApplies + }; + } + + const imp = { + id: transactionId, + instl: params.instl === 1 ? 1 : 0, + tagid: adUnitCode, + bidfloor: params.bidfloor || 0, + bidfloorcur: 'USD', + secure: helper.startsWith( + utils.getTopWindowUrl().toLowerCase(), + 'http://' + ) + ? 0 + : 1 + }; + + const hasFavoredMediaType = + params.favoredMediaType && + this.supportedMediaTypes.includes(params.favoredMediaType); + + if (!mediaTypes || mediaTypes.banner) { + if (!hasFavoredMediaType || params.favoredMediaType === BANNER) { + const bannerImp = Object.assign({}, imp, { + banner: { + w: sizes.length ? sizes[0][0] : 300, + h: sizes.length ? sizes[0][1] : 250, + pos: params.pos || 0, + topframe: bidderRequest.refererInfo.reachedTop + } + }); + rtbBidRequest.imp.push(bannerImp); + } + } + + if (mediaTypes && mediaTypes.video) { + if (!hasFavoredMediaType || params.favoredMediaType === VIDEO) { + let videoImp = { + video: { + protocols: params.protocols || [1, 2, 3, 4, 5, 6], + pos: params.pos || 0, + ext: {context: mediaTypes.video.context} + } + }; + + let playerSize = mediaTypes.video.playerSize || sizes; + if (utils.isArray(playerSize[0])) { + videoImp.video.w = playerSize[0][0]; + videoImp.video.h = playerSize[0][1]; + } else if (utils.isNumber(playerSize[0])) { + videoImp.video.w = playerSize[0]; + videoImp.video.h = playerSize[1]; + } else { + videoImp.video.w = 300; + videoImp.video.h = 250; + } + + videoImp = Object.assign({}, imp, videoImp); + rtbBidRequest.imp.push(videoImp); + } + } + + if (rtbBidRequest.imp.length === 0) { + return; + } + + return { + method: 'POST', + url: rtbEndpoint, + data: rtbBidRequest, + bidRequest + }; + }); + }, + + interpretResponse: function (serverResponse, bidRequest) { + const response = serverResponse && serverResponse.body; + if (!response) { + utils.logError('empty response'); + return []; + } + + const bids = response.seatbid.reduce( + (acc, seatBid) => acc.concat(seatBid.bid), + [] + ); + let outBids = []; + + bids.forEach(bid => { + const outBid = { + requestId: bidRequest.bidRequest.bidId, + cpm: bid.price, + width: bid.w, + height: bid.h, + ttl: 60 * 10, + creativeId: bid.crid || bid.adid, + netRevenue: true, + currency: bid.cur || response.cur, + mediaType: helper.getMediaType(bid) + }; + + if ( + utils.deepAccess( + bidRequest.bidRequest, + 'mediaTypes.' + outBid.mediaType + ) + ) { + if (outBid.mediaType === BANNER) { + outBids.push(Object.assign({}, outBid, {ad: bid.adm})); + } else if (outBid.mediaType === VIDEO) { + const context = utils.deepAccess( + bidRequest.bidRequest, + 'mediaTypes.video.context' + ); + outBids.push( + Object.assign({}, outBid, { + vastUrl: bid.ext.vast_url, + vastXml: bid.adm, + renderer: + context === 'outstream' + ? newRenderer(bidRequest.bidRequest, bid) + : undefined + }) + ); + } + } + }); + return outBids; + }, + + getUserSyncs: function (syncOptions, serverResponses, gdprConsent) { + const syncs = []; + const gdprApplies = + gdprConsent && typeof gdprConsent.gdprApplies === 'boolean' + ? gdprConsent.gdprApplies + : false; + const suffix = gdprApplies + ? 'gc=' + encodeURIComponent(gdprConsent.consentString) + : 'gc=missing'; + serverResponses.forEach(resp => { + if (resp.body) { + const bidResponse = resp.body; + if (bidResponse.ext && Array.isArray(bidResponse.ext['utrk'])) { + bidResponse.ext['utrk'].forEach(pixel => { + const url = + pixel.url + + (pixel.url.indexOf('?') > 0 ? '&' + suffix : '?' + suffix); + return syncs.push({type: pixel.type, url}); + }); + } + if (Array.isArray(bidResponse.seatbid)) { + bidResponse.seatbid.forEach(seatBid => { + if (Array.isArray(seatBid.bid)) { + seatBid.bid.forEach(bid => { + if (bid.ext && Array.isArray(bid.ext['utrk'])) { + bid.ext['utrk'].forEach(pixel => { + const url = + pixel.url + + (pixel.url.indexOf('?') > 0 + ? '&' + suffix + : '?' + suffix); + return syncs.push({type: pixel.type, url}); + }); + } + }); + } + }); + } + } + }); + return syncs; + } +}; + +function newRenderer(bidRequest, bid, rendererOptions = {}) { + const renderer = Renderer.install({ + url: + (bidRequest.params && bidRequest.params.rendererUrl) || + (bid.ext && bid.ext.renderer_url) || + '//s.wlplayer.com/video/latest/renderer.js', + config: rendererOptions, + loaded: false + }); + try { + renderer.setRender(renderOutstream); + } catch (err) { + utils.logWarn('Prebid Error calling setRender on renderer', err); + } + return renderer; +} + +function renderOutstream(bid) { + bid.renderer.push(() => { + const unitId = bid.adUnitCode + '/' + bid.adId; + window['GamoshiPlayer'].renderAd({ + id: unitId, + debug: window.location.href.indexOf('pbjsDebug') >= 0, + placement: document.getElementById(bid.adUnitCode), + width: bid.width, + height: bid.height, + events: { + ALL_ADS_COMPLETED: () => + window.setTimeout(() => { + window['GamoshiPlayer'].removeAd(unitId); + }, 300) + }, + vastUrl: bid.vastUrl, + vastXml: bid.vastXml + }); + }); +} + +registerBidder(spec); diff --git a/modules/cleanmedianetBidAdapter.md b/modules/cleanmedianetBidAdapter.md new file mode 100644 index 00000000000..f2bc8feb0f0 --- /dev/null +++ b/modules/cleanmedianetBidAdapter.md @@ -0,0 +1,66 @@ +# Overview + +``` +Module Name: Clean Media Net Adapter +Module Type: Bidder Adapter +Maintainer: dev@cleanmedia.net +``` + +# Description + +Connects to Clean Media Net's Programmatic advertising platform as a service. + +Clean Media bid adapter supports Banner & Video (Instream and Outstream). +The *only* required parameter (in the `params` section) is the `supplyPartnerId` parameter. + +# Test Parameters +``` +var adUnits = [ + // Banner adUnit + { + code: 'banner-div', + sizes: [[300, 250]], + bids: [{ + bidder: 'cleanmedianet', + params: { + // ID of the supply partner you created in the Clean Media Net dashboard + supplyPartnerId: '1253', + // OPTIONAL: custom bid floor + bidfloor: 0.01, + // OPTIONAL: if you know the ad position on the page, specify it here + // (this corresponds to "Ad Position" in OpenRTB 2.3, section 5.4) + //adpos: 1, + // OPTIONAL: whether this is an interstitial placement (0 or 1) + // (see "instl" property in "Imp" object in the OpenRTB 2.3, section 3.2.2) + //instl: 0 + } + }] + }, + // Video outstream adUnit + { + code: 'video-outstream', + sizes: [[300, 250]], + mediaTypes: { + video: { + context: 'outstream', + playerSize: [300, 250] + } + }, + bids: [ { + bidder: 'cleanmedianet', + params: { + // ID of the supply partner you created in the dashboard + supplyPartnerId: '1254', + // OPTIONAL: custom bid floor + bidfloor: 0.01, + // OPTIONAL: if you know the ad position on the page, specify it here + // (this corresponds to "Ad Position" in OpenRTB 2.3, section 5.4) + //adpos: 1, + // OPTIONAL: whether this is an interstitial placement (0 or 1) + // (see "instl" property in "Imp" object in the OpenRTB 2.3, section 3.2.2) + //instl: 0 + } + }] + } +]; +``` diff --git a/modules/clickforceBidAdapter.js b/modules/clickforceBidAdapter.js index c9e54f9efac..16ecdf713d9 100644 --- a/modules/clickforceBidAdapter.js +++ b/modules/clickforceBidAdapter.js @@ -1,6 +1,6 @@ -import * as utils from 'src/utils'; -import {registerBidder} from 'src/adapters/bidderFactory'; -import {BANNER, NATIVE} from 'src/mediaTypes'; +import * as utils from '../src/utils'; +import {registerBidder} from '../src/adapters/bidderFactory'; +import {BANNER, NATIVE} from '../src/mediaTypes'; const BIDDER_CODE = 'clickforce'; const ENDPOINT_URL = '//ad.doublemax.net/adserver/prebid.json?cb=' + new Date().getTime() + '&hb=1&ver=1.21'; diff --git a/modules/coinzillaBidAdapter.js b/modules/coinzillaBidAdapter.js new file mode 100644 index 00000000000..6918d47eb10 --- /dev/null +++ b/modules/coinzillaBidAdapter.js @@ -0,0 +1,89 @@ +import * as utils from '../src/utils'; +import {config} from '../src/config'; +import {registerBidder} from '../src/adapters/bidderFactory'; + +const BIDDER_CODE = 'coinzilla'; +const ENDPOINT_URL = 'https://request.czilladx.com/serve/request.php'; + +export const spec = { + code: BIDDER_CODE, + aliases: ['czlla'], // short code + + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function (bid) { + return !!(bid.params.placementId); + }, + + /** + * Make a server request from the list of BidRequests. + * + * @return Array Info describing the request to the server. + * @param validBidRequests + * @param bidderRequest + */ + buildRequests: function (validBidRequests, bidderRequest) { + if (validBidRequests.length === 0) { + return []; + } + return validBidRequests.map(bidRequest => { + const sizes = utils.parseSizesInput(bidRequest.sizes)[0]; + const width = sizes.split('x')[0]; + const height = sizes.split('x')[1]; + const payload = { + placementId: bidRequest.params.placementId, + width: width, + height: height, + bidId: bidRequest.bidId, + referer: bidderRequest.refererInfo.referer, + }; + return { + method: 'POST', + url: ENDPOINT_URL, + data: payload + }; + }); + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} serverResponse A successful response from the server. + * @param bidRequest + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function (serverResponse, bidRequest) { + const bidResponses = []; + const response = serverResponse.body; + const creativeId = response.creativeId || 0; + const width = response.width || 0; + const height = response.height || 0; + const cpm = response.cpm || 0; + if (width !== 0 && height !== 0 && cpm !== 0 && creativeId !== 0) { + const dealId = response.dealid || ''; + const currency = response.currency || 'EUR'; + const netRevenue = (response.netRevenue === undefined) ? true : response.netRevenue; + const referrer = bidRequest.data.referer; + const bidResponse = { + requestId: response.requestId, + cpm: cpm, + width: response.width, + height: response.height, + creativeId: creativeId, + dealId: dealId, + currency: currency, + netRevenue: netRevenue, + ttl: config.getConfig('_bidderTimeout'), + referrer: referrer, + ad: response.ad + }; + bidResponses.push(bidResponse); + } + return bidResponses; + }, +}; +registerBidder(spec); diff --git a/modules/coinzillaBidAdapter.md b/modules/coinzillaBidAdapter.md new file mode 100644 index 00000000000..c7da4efb1a4 --- /dev/null +++ b/modules/coinzillaBidAdapter.md @@ -0,0 +1,24 @@ +# Overview + +``` +Module Name: Coinzilla Bidder Adapter +Module Type: Coinzilla Adapter +Maintainer: technical@sevio.com +``` + +# Description + +Our module helps you have an easier time implementing Coinzilla on your website. All you have to do is replace the ``placementId`` with your zoneID, depending on the required size in your account dashboard. If you need additional information please contact us at ``publishers@coinzilla.com``. +# Test Parameters +``` + var adUnits = [{ + code: 'test-ad-div', + sizes: [[300, 250]], + bids: [{ + bidder: 'coinzilla', + params: { + placementId: 'testPlacementId' + } + }] + }]; +``` \ No newline at end of file diff --git a/modules/collectcentBidAdapter.js b/modules/collectcentBidAdapter.js new file mode 100644 index 00000000000..50ac377788e --- /dev/null +++ b/modules/collectcentBidAdapter.js @@ -0,0 +1,93 @@ +import { registerBidder } from '../src/adapters/bidderFactory'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes'; +import * as utils from '../src/utils'; + +const BIDDER_CODE = 'collectcent'; +const URL_MULTI = '//publishers.motionspots.com/?c=o&m=multi'; +const URL_SYNC = '//publishers.motionspots.com/?c=o&m=cookie'; + +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.bidId && + bid.params && + !isNaN(bid.params.placementId) && + spec.supportedMediaTypes.indexOf(bid.params.traffic) !== -1 + ); + }, + + /** + * 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) => { + let winTop; + try { + winTop = window.top; + } catch (e) { + utils.logMessage(e); + winTop = window; + }; + + const placements = []; + const location = bidderRequest ? new URL(bidderRequest.refererInfo.referer) : winTop.location; + const request = { + 'secure': (location.protocol === 'https:') ? 1 : 0, + 'deviceWidth': winTop.screen.width, + 'deviceHeight': winTop.screen.height, + 'host': location.host, + 'page': location.pathname, + 'placements': placements + }; + + for (let i = 0; i < validBidRequests.length; i++) { + const bid = validBidRequests[i]; + const params = bid.params; + placements.push({ + placementId: params.placementId, + bidId: bid.bidId, + sizes: bid.sizes, + traffic: params.traffic + }); + } + return { + method: 'POST', + url: URL_MULTI, + data: request + }; + }, + + /** + * 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) => { + try { + serverResponse = serverResponse.body; + } catch (e) { + utils.logMessage(e); + }; + return serverResponse; + }, + + getUserSyncs: () => { + return [{ + type: 'image', + url: URL_SYNC + }]; + } +}; + +registerBidder(spec); diff --git a/modules/collectcentBidAdapter.md b/modules/collectcentBidAdapter.md new file mode 100644 index 00000000000..938bdc420cd --- /dev/null +++ b/modules/collectcentBidAdapter.md @@ -0,0 +1,27 @@ +# Overview + +``` +Module Name: Collectcent SSP Bidder Adapter +Module Type: Bidder Adapter +Maintainer: dev.collectcent@gmail.com +``` + +# Description + +Module that connects to Collectcent SSP demand sources + +# Test Parameters +``` + var adUnits = [{ + code: 'placementCode', + sizes: [[300, 250]], + bids: [{ + bidder: 'collectcent', + params: { + placementId: 0, + traffic: 'banner' + } + }] + } + ]; +``` diff --git a/modules/colombiaBidAdapter.js b/modules/colombiaBidAdapter.js new file mode 100644 index 00000000000..e5ebc41ebfd --- /dev/null +++ b/modules/colombiaBidAdapter.js @@ -0,0 +1,72 @@ +import * as utils from '../src/utils'; +import {config} from '../src/config'; +import {registerBidder} from '../src/adapters/bidderFactory'; +const BIDDER_CODE = 'colombia'; +const ENDPOINT_URL = 'https://ade.clmbtech.com/cde/prebid.htm'; +const HOST_NAME = document.location.protocol + '//' + window.location.host; + +export const spec = { + code: BIDDER_CODE, + aliases: ['clmb'], + isBidRequestValid: function(bid) { + return !!(bid.params.placementId); + }, + buildRequests: function(validBidRequests) { + return validBidRequests.map(bidRequest => { + const params = bidRequest.params; + const sizes = utils.parseSizesInput(bidRequest.sizes)[0]; + const width = sizes.split('x')[0]; + const height = sizes.split('x')[1]; + const placementId = params.placementId; + const cb = Math.floor(Math.random() * 99999999999); + const referrer = encodeURIComponent(utils.getTopWindowUrl()); + const bidId = bidRequest.bidId; + const payload = { + v: 'hb1', + p: placementId, + w: width, + h: height, + cb: cb, + r: referrer, + uid: bidId, + t: 'i', + d: HOST_NAME, + }; + return { + method: 'POST', + url: ENDPOINT_URL, + data: payload, + } + }); + }, + interpretResponse: function(serverResponse, bidRequest) { + const bidResponses = []; + const response = serverResponse.body; + const crid = response.creativeId || 0; + const width = response.width || 0; + const height = response.height || 0; + const cpm = response.cpm || 0; + if (width !== 0 && height !== 0 && cpm !== 0 && crid !== 0) { + const dealId = response.dealid || ''; + const currency = response.currency || 'USD'; + const netRevenue = (response.netRevenue === undefined) ? true : response.netRevenue; + const referrer = utils.getTopWindowUrl(); + const bidResponse = { + requestId: bidRequest.data.uid, + cpm: cpm, + width: response.width, + height: response.height, + creativeId: crid, + dealId: dealId, + currency: currency, + netRevenue: netRevenue, + ttl: config.getConfig('_bidderTimeout'), + referrer: referrer, + ad: response.ad + }; + bidResponses.push(bidResponse); + } + return bidResponses; + } +} +registerBidder(spec); diff --git a/modules/colombiaBidAdapter.md b/modules/colombiaBidAdapter.md new file mode 100644 index 00000000000..2131fcb4c5a --- /dev/null +++ b/modules/colombiaBidAdapter.md @@ -0,0 +1,27 @@ +# Overview + +``` +Module Name: COLOMBIA Bidder Adapter +Module Type: Bidder Adapter +Maintainer: colombiaonline@timesinteret.in +``` + +# Description + +Connect to COLOMBIA for bids. + +THE COLOMBIA adapter requires setup and approval from the COLOMBIA team. Please reach out to your account team or colombiaonline@timesinteret.in for more information. + +# Test Parameters +``` + var adUnits = [{ + code: 'test-ad-div', + sizes: [[300, 250]], + bids: [{ + bidder: 'colombia', + params: { + placementId: '307466' + } + }] + }]; +``` diff --git a/modules/colossussspBidAdapter.js b/modules/colossussspBidAdapter.js index 22b0415936c..2ad320ede38 100644 --- a/modules/colossussspBidAdapter.js +++ b/modules/colossussspBidAdapter.js @@ -1,6 +1,6 @@ -import { registerBidder } from 'src/adapters/bidderFactory'; -import { BANNER, NATIVE, VIDEO } from 'src/mediaTypes'; -import * as utils from 'src/utils'; +import { registerBidder } from '../src/adapters/bidderFactory'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes'; +import * as utils from '../src/utils'; const BIDDER_CODE = 'colossusssp'; const URL = '//colossusssp.com/?c=o&m=multi'; diff --git a/modules/consentManagement.js b/modules/consentManagement.js index 0618a3f752c..bf4eb48f25d 100644 --- a/modules/consentManagement.js +++ b/modules/consentManagement.js @@ -4,11 +4,13 @@ * and make it available for any GDPR supported adapters to read/pass this information to * their system. */ -import * as utils from 'src/utils'; -import { config } from 'src/config'; -import { gdprDataHandler } from 'src/adaptermanager'; +import * as utils from '../src/utils'; +import { config } from '../src/config'; +import events from '../src/events'; +import { gdprDataHandler } from '../src/adapterManager'; import includes from 'core-js/library/fn/array/includes'; import strIncludes from 'core-js/library/fn/string/includes'; +import { EVENTS } from '../src/constants'; const DEFAULT_CMP = 'iab'; const DEFAULT_CONSENT_TIMEOUT = 10000; @@ -17,14 +19,27 @@ const DEFAULT_ALLOW_AUCTION_WO_CONSENT = true; export let userCMP; export let consentTimeout; export let allowAuction; +export let staticConsentData; let consentData; +let addedConsentHook = false; // add new CMPs here, with their dedicated lookup function const cmpCallMap = { - 'iab': lookupIabConsent + 'iab': lookupIabConsent, + 'static': lookupStaticConsentData }; +/** + * This function reads the consent string from the config to obtain the consent information of the user. + * @param {function(string)} cmpSuccess acts as a success callback when the value is read from config; pass along consentObject (string) from CMP + * @param {function(string)} cmpError acts as an error callback while interacting with the config string; pass along an error message (string) + * @param {object} hookConfig contains module related variables (see comment in requestBidsHook function) + */ +function lookupStaticConsentData(cmpSuccess, cmpError, hookConfig) { + cmpSuccess(staticConsentData, hookConfig); +} + /** * This function handles interacting with an IAB compliant CMP to obtain the consent information of the user. * Given the async nature of the CMP's API, we pass in acting success/error callback functions to exit this function @@ -44,11 +59,11 @@ function lookupIabConsent(cmpSuccess, cmpError, hookConfig) { } return { - consentDataCallback: function(consentResponse) { + consentDataCallback: function (consentResponse) { cmpResponse.getConsentData = consentResponse; afterEach(); }, - vendorConsentsCallback: function(consentResponse) { + vendorConsentsCallback: function (consentResponse) { cmpResponse.getVendorConsents = consentResponse; afterEach(); } @@ -70,7 +85,7 @@ function lookupIabConsent(cmpSuccess, cmpError, hookConfig) { // if the CMP is not found, the iframe function will call the cmpError exit callback to abort the rest of the CMP workflow try { cmpFunction = window.__cmp || utils.getWindowTop().__cmp; - } catch (e) {} + } catch (e) { } if (utils.isFn(cmpFunction)) { cmpFunction('getConsentData', null, callbackHandler.consentDataCallback); @@ -85,7 +100,7 @@ function lookupIabConsent(cmpSuccess, cmpError, hookConfig) { while (!cmpFrame) { try { if (f.frames['__cmpLocator']) cmpFrame = f; - } catch (e) {} + } catch (e) { } if (f === window.top) break; f = f.parent; } @@ -127,13 +142,15 @@ function lookupIabConsent(cmpSuccess, cmpError, hookConfig) { function callCmpWhileInIframe(commandName, cmpFrame, moduleCallback) { /* Setup up a __cmp function to do the postMessage and stash the callback. This function behaves (from the caller's perspective identicially to the in-frame __cmp call */ - window.__cmp = function(cmd, arg, callback) { + window.__cmp = function (cmd, arg, callback) { let callId = Math.random() + ''; - let msg = {__cmpCall: { - command: cmd, - parameter: arg, - callId: callId - }}; + let msg = { + __cmpCall: { + command: cmd, + parameter: arg, + callId: callId + } + }; cmpCallbacks[callId] = callback; cmpFrame.postMessage(msg, '*'); } @@ -170,16 +187,16 @@ function lookupIabConsent(cmpSuccess, cmpError, hookConfig) { /** * If consentManagement module is enabled (ie included in setConfig), this hook function will attempt to fetch the * user's encoded consent string from the supported CMP. Once obtained, the module will store this - * data as part of a gdprConsent object which gets transferred to adaptermanager's gdprDataHandler object. + * data as part of a gdprConsent object which gets transferred to adapterManager's gdprDataHandler object. * This information is later added into the bidRequest object for any supported adapters to read/pass along to their system. * @param {object} reqBidsConfigObj required; This is the same param that's used in pbjs.requestBids. * @param {function} fn required; The next function in the chain, used by hook.js */ -export function requestBidsHook(reqBidsConfigObj, fn) { +export function requestBidsHook(fn, reqBidsConfigObj) { // preserves all module related variables for the current auction instance (used primiarily for concurrent auctions) const hookConfig = { context: this, - args: arguments, + args: [reqBidsConfigObj], nextFn: fn, adUnits: reqBidsConfigObj.adUnits || $$PREBID_GLOBAL$$.adUnits, bidsBackHandler: reqBidsConfigObj.bidsBackHandler, @@ -241,6 +258,7 @@ function processCmpData(consentObject, hookConfig) { */ function cmpTimedOut(hookConfig) { cmpFailed('CMP workflow exceeded timeout threshold.', hookConfig); + events.emit(EVENTS.CMP_UPDATE, { timeout: true }); } /** @@ -270,6 +288,11 @@ function storeConsentData(cmpConsentObject) { gdprApplies: (cmpConsentObject) ? cmpConsentObject.getConsentData.gdprApplies : undefined }; gdprDataHandler.setConsentData(consentData); + + if (!!consentData.vendorData) { + consentData.vendorData.timeout = false; + events.emit(EVENTS.CMP_UPDATE, consentData.vendorData); + } } /** @@ -327,7 +350,7 @@ export function resetConsentData() { * A configuration function that initializes some module variables, as well as add a hook into the requestBids function * @param {object} config required; consentManagement module config settings; cmp (string), timeout (int), allowAuctionWithoutConsent (boolean) */ -export function setConfig(config) { +export function setConsentConfig(config) { if (utils.isStr(config.cmpApi)) { userCMP = config.cmpApi; } else { @@ -348,7 +371,20 @@ export function setConfig(config) { allowAuction = DEFAULT_ALLOW_AUCTION_WO_CONSENT; utils.logInfo(`consentManagement config did not specify allowAuctionWithoutConsent. Using system default setting (${DEFAULT_ALLOW_AUCTION_WO_CONSENT}).`); } + utils.logInfo('consentManagement module has been activated...'); - $$PREBID_GLOBAL$$.requestBids.addHook(requestBidsHook, 50); + + if (userCMP === 'static') { + if (utils.isPlainObject(config.consentData)) { + staticConsentData = config.consentData; + consentTimeout = 0; + } else { + utils.logError(`consentManagement config with cmpApi: 'static' did not specify consentData. No consents will be available to adapters.`); + } + } + if (!addedConsentHook) { + $$PREBID_GLOBAL$$.requestBids.before(requestBidsHook, 50); + } + addedConsentHook = true; } -config.getConfig('consentManagement', config => setConfig(config.consentManagement)); +config.getConfig('consentManagement', config => setConsentConfig(config.consentManagement)); diff --git a/modules/consumableBidAdapter.js b/modules/consumableBidAdapter.js index 7d8cc1b2deb..d462acaee59 100644 --- a/modules/consumableBidAdapter.js +++ b/modules/consumableBidAdapter.js @@ -1,5 +1,5 @@ -import * as utils from 'src/utils'; -import { registerBidder } from 'src/adapters/bidderFactory'; +import * as utils from '../src/utils'; +import { registerBidder } from '../src/adapters/bidderFactory'; const BIDDER_CODE = 'consumable'; @@ -28,10 +28,7 @@ export const spec = { * @return ServerRequest Info describing the request to the server. */ - buildRequests: function(validBidRequests) { - // Do we need to group by bidder? i.e. to make multiple requests for - // different endpoints. - + buildRequests: function(validBidRequests, bidderRequest) { let ret = { method: 'POST', url: '', @@ -50,14 +47,21 @@ export const spec = { const data = Object.assign({ placements: [], time: Date.now(), - user: {}, url: utils.getTopWindowUrl(), referrer: document.referrer, - enableBotFiltering: true, - includePricingData: true, - parallel: true + source: [{ + 'name': 'prebidjs', + 'version': '$prebid.version$' + }] }, validBidRequests[0].params); + if (bidderRequest && bidderRequest.gdprConsent) { + data.gdpr = { + consent: bidderRequest.gdprConsent.consentString, + applies: (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') ? bidderRequest.gdprConsent.gdprApplies : true + }; + } + validBidRequests.map(bid => { const placement = Object.assign({ divName: bid.bidId, @@ -111,7 +115,7 @@ export const spec = { bid.ad = retrieveAd(decision, bid.unitId, bid.unitName); bid.currency = 'USD'; bid.creativeId = decision.adId; - bid.ttl = 360; + bid.ttl = 30; bid.netRevenue = true; bid.referrer = utils.getTopWindowUrl(); @@ -123,12 +127,16 @@ export const spec = { return bidResponses; }, - getUserSyncs: function(syncOptions) { + getUserSyncs: function(syncOptions, serverResponses) { if (syncOptions.iframeEnabled) { return [{ type: 'iframe', - url: '//s.zkcdn.net/ss/' + siteId + '.html' + url: '//sync.serverbid.com/ss/' + siteId + '.html' }]; + } + + if (syncOptions.pixelEnabled && serverResponses.length > 0) { + return serverResponses[0].body.pixels; } else { utils.logWarn(bidder + ': Please enable iframe based user syncing.'); } @@ -192,12 +200,7 @@ function getSize(sizes) { } function retrieveAd(decision, unitId, unitName) { - let oad = decision.contents && decision.contents[0] && decision.contents[0].body + utils.createTrackPixelHtml(decision.impressionUrl); - let cb = Math.round(new Date().getTime()); - let ad = '' + oad; - ad += ''; - ad += ''; - ad += '' + let ad = decision.contents && decision.contents[0] && decision.contents[0].body + utils.createTrackPixelHtml(decision.impressionUrl); return ad; } diff --git a/modules/contentigniteBidAdapter.js b/modules/contentigniteBidAdapter.js index 423ec0ba8da..2e3092114f6 100644 --- a/modules/contentigniteBidAdapter.js +++ b/modules/contentigniteBidAdapter.js @@ -1,6 +1,6 @@ -import { registerBidder } from 'src/adapters/bidderFactory'; -import { config } from 'src/config'; -import * as utils from 'src/utils'; +import { registerBidder } from '../src/adapters/bidderFactory'; +import { config } from '../src/config'; +import * as utils from '../src/utils'; const BIDDER_CODE = 'contentignite'; diff --git a/modules/conversantBidAdapter.js b/modules/conversantBidAdapter.js index aae892f4eed..a3479a9d1d1 100644 --- a/modules/conversantBidAdapter.js +++ b/modules/conversantBidAdapter.js @@ -1,10 +1,9 @@ -import * as utils from 'src/utils'; -import {registerBidder} from 'src/adapters/bidderFactory'; -import { BANNER, VIDEO } from 'src/mediaTypes'; +import * as utils from '../src/utils'; +import {registerBidder} from '../src/adapters/bidderFactory'; +import { BANNER, VIDEO } from '../src/mediaTypes'; const BIDDER_CODE = 'conversant'; -const URL = '//web.hb.ad.cpe.dotomi.com/s2s/header/24'; -const VERSION = '2.2.3'; +const URL = 'https://web.hb.ad.cpe.dotomi.com/s2s/header/24'; export const spec = { code: BIDDER_CODE, @@ -24,7 +23,7 @@ export const spec = { } if (!utils.isStr(bid.params.site_id)) { - utils.logWarn(BIDDER_CODE + ': site_id must be specified as a string') + utils.logWarn(BIDDER_CODE + ': site_id must be specified as a string'); return false; } @@ -45,57 +44,61 @@ export const spec = { * Make a server request from the list of BidRequests. * * @param {BidRequest[]} validBidRequests - an array of bids + * @param bidderRequest * @return {ServerRequest} Info describing the request to the server. */ buildRequests: function(validBidRequests, bidderRequest) { - const loc = utils.getTopWindowLocation(); - const page = loc.href; - const isPageSecure = (loc.protocol === 'https:') ? 1 : 0; + const page = (bidderRequest && bidderRequest.refererInfo) ? bidderRequest.refererInfo.referer : ''; let siteId = ''; let requestId = ''; let pubcid = null; const conversantImps = validBidRequests.map(function(bid) { const bidfloor = utils.getBidIdParameter('bidfloor', bid.params); - const secure = isPageSecure || (utils.getBidIdParameter('secure', bid.params) ? 1 : 0); siteId = utils.getBidIdParameter('site_id', bid.params); requestId = bid.auctionId; - const format = convertSizes(bid.sizes); - const imp = { id: bid.bidId, - secure: secure, + secure: 1, bidfloor: bidfloor || 0, displaymanager: 'Prebid.js', - displaymanagerver: VERSION + displaymanagerver: '$prebid.version$' }; - copyOptProperty(bid.params, 'tag_id', imp, 'tagid'); + copyOptProperty(bid.params.tag_id, imp, 'tagid'); if (isVideoRequest(bid)) { - const video = { - w: format[0].w, - h: format[0].h - }; + const videoData = utils.deepAccess(bid, 'mediaTypes.video') || {}; + const format = convertSizes(videoData.playerSize || bid.sizes); + const video = {}; + + if (format && format[0]) { + copyOptProperty(format[0].w, video, 'w'); + copyOptProperty(format[0].h, video, 'h'); + } - copyOptProperty(bid.params, 'position', video, 'pos'); - copyOptProperty(bid.params, 'mimes', video); - copyOptProperty(bid.params, 'maxduration', video); - copyOptProperty(bid.params, 'protocols', video); - copyOptProperty(bid.params, 'api', video); + copyOptProperty(bid.params.position, video, 'pos'); + copyOptProperty(bid.params.mimes || videoData.mimes, video, 'mimes'); + copyOptProperty(bid.params.maxduration, video, 'maxduration'); + copyOptProperty(bid.params.protocols || videoData.protocols, video, 'protocols'); + copyOptProperty(bid.params.api || videoData.api, video, 'api'); imp.video = video; } else { + const bannerData = utils.deepAccess(bid, 'mediaTypes.banner') || {}; + const format = convertSizes(bannerData.sizes || bid.sizes); const banner = {format: format}; - copyOptProperty(bid.params, 'position', banner, 'pos'); + copyOptProperty(bid.params.position, banner, 'pos'); imp.banner = banner; } - if (bid.crumbs && bid.crumbs.pubcid) { + if (bid.userId && bid.userId.pubcid) { + pubcid = bid.userId.pubcid; + } else if (bid.crumbs && bid.crumbs.pubcid) { pubcid = bid.crumbs.pubcid; } @@ -149,6 +152,7 @@ export const spec = { * Unpack the response from the server into a list of bids. * * @param {*} serverResponse A successful response from the server. + * @param bidRequest * @return {Bid[]} An array of bids which were nested inside the server. */ interpretResponse: function(serverResponse, bidRequest) { @@ -244,16 +248,17 @@ function getDevice() { * * [[300, 250], [300, 600]] => [{w: 300, h: 250}, {w: 300, h: 600}] * - * @param {number[2][]|number[2]} bidSizes - arrays of widths and heights + * @param {Array.>} bidSizes - arrays of widths and heights * @returns {object[]} Array of objects with w and h */ function convertSizes(bidSizes) { let format; - - if (bidSizes.length === 2 && typeof bidSizes[0] === 'number' && typeof bidSizes[1] === 'number') { - format = [{w: bidSizes[0], h: bidSizes[1]}]; - } else { - format = utils._map(bidSizes, d => { return {w: d[0], h: d[1]}; }); + if (Array.isArray(bidSizes)) { + if (bidSizes.length === 2 && typeof bidSizes[0] === 'number' && typeof bidSizes[1] === 'number') { + format = [{w: bidSizes[0], h: bidSizes[1]}]; + } else { + format = utils._map(bidSizes, d => { return {w: d[0], h: d[1]}; }); + } } return format; @@ -272,16 +277,13 @@ function isVideoRequest(bid) { /** * Copy property if exists from src to dst * - * @param {object} src - * @param {string} srcName - * @param {object} dst - * @param {string} [dstName] - Optional. If not specified then srcName is used. + * @param {object} src - source object + * @param {object} dst - destination object + * @param {string} dstName - destination property name */ -function copyOptProperty(src, srcName, dst, dstName) { - dstName = dstName || srcName; - const obj = utils.getBidIdParameter(srcName, src); - if (obj !== '') { - dst[dstName] = obj; +function copyOptProperty(src, dst, dstName) { + if (src) { + dst[dstName] = src; } } diff --git a/modules/conversantBidAdapter.md b/modules/conversantBidAdapter.md index 1afdad6d544..5aba5653043 100644 --- a/modules/conversantBidAdapter.md +++ b/modules/conversantBidAdapter.md @@ -13,7 +13,11 @@ Module that connects to Conversant's demand sources. Supports banners and video var adUnits = [ { code: 'banner-test-div', - sizes: [[300, 250]], + mediaTypes: { + banner: { + sizes: [[300, 250],[300,600]] + } + }, bids: [{ bidder: "conversant", params: { @@ -22,20 +26,20 @@ var adUnits = [ }] },{ code: 'video-test-div', - sizes: [640, 480], mediaTypes: { video: { - context: 'instream' + context: 'instream', + playerSize: [640, 480] } }, bids: [{ bidder: "conversant", params: { - site_id: '88563', + site_id: '108060', api: [2], protocols: [1, 2], mimes: ['video/mp4'] } }] }]; -``` \ No newline at end of file +``` diff --git a/modules/cosmosBidAdapter.js b/modules/cosmosBidAdapter.js new file mode 100644 index 00000000000..84131bfa131 --- /dev/null +++ b/modules/cosmosBidAdapter.js @@ -0,0 +1,392 @@ +import { registerBidder } from '../src/adapters/bidderFactory'; +import { BANNER, VIDEO } from '../src/mediaTypes'; +import * as utils from '../src/utils'; + +const BIDDER_CODE = 'cosmos'; +const BID_ENDPOINT = '//bid.cosmoshq.com/openrtb2/bids'; +const USER_SYNC_ENDPOINT = '//sync.cosmoshq.com/js/v1/usersync.html'; +const HTTP_POST = 'POST'; +const LOG_PREFIX = 'COSMOS: '; +const DEFAULT_CURRENCY = 'USD'; +const HTTPS = 'https:'; +const MEDIA_TYPES = 'mediaTypes'; +const MIMES = 'mimes'; +const DEFAULT_NET_REVENUE = false; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO], + + /** + * generate UUID + **/ + _createUUID: function () { + return ('' + new Date().getTime()); + }, + + /** + * copy object if not null + **/ + _copyObject: function (src, dst) { + if (src) { + // copy complete object + Object.keys(src).forEach(param => dst[param] = src[param]); + } + }, + + /** + * parse object + **/ + _parse: function (rawPayload) { + try { + if (rawPayload) { + return JSON.parse(rawPayload); + } + } catch (ex) { + utils.logError(LOG_PREFIX, 'Exception: ', ex); + } + return null; + }, + + /** + * 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 || !bid.params) { + utils.logError(LOG_PREFIX, 'nil/empty bid object'); + return false; + } + + if (!utils.isEmpty(bid.params.publisherId) || + !utils.isNumber(bid.params.publisherId)) { + utils.logError(LOG_PREFIX, 'publisherId is mandatory and must be numeric. Ad Unit: ', JSON.stringify(bid)); + return false; + } + // video bid request validation + if (bid.hasOwnProperty(MEDIA_TYPES) && bid.mediaTypes.hasOwnProperty(VIDEO)) { + if (!bid.mediaTypes.video.hasOwnProperty(MIMES) || + !utils.isArray(bid.mediaTypes.video.mimes) || + bid.mediaTypes.video.mimes.length === 0) { + utils.logError(LOG_PREFIX, 'mimes are mandatory for video bid request. Ad Unit: ', JSON.stringify(bid)); + return false; + } + } + + return true; + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param {validBidRequests[]} - an array of bids + * @return ServerRequest Info describing the request to the server. + **/ + buildRequests: function (validBidRequests, bidderRequest) { + if (validBidRequests.length === 0) { + return []; + } + + var refererInfo; + if (bidderRequest && bidderRequest.refererInfo) { + refererInfo = bidderRequest.refererInfo; + } + + let clonedBidRequests = utils.deepClone(validBidRequests); + return clonedBidRequests.map(bidRequest => { + const oRequest = spec._createRequest(bidRequest, refererInfo); + if (oRequest) { + spec._setGDPRParams(bidderRequest, oRequest); + return { + method: HTTP_POST, + url: BID_ENDPOINT, + data: JSON.stringify(oRequest) + }; + } + }); + }, + + /** + * 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, request) { + let response = serverResponse.body; + var bidResponses = []; + try { + if (response.seatbid) { + var currency = response.cur ? response.cur : DEFAULT_CURRENCY; + response.seatbid.forEach(seatbid => { + var bids = seatbid.bid ? seatbid.bid : []; + bids.forEach(bid => { + var bidResponse = { + requestId: bid.impid, + cpm: (parseFloat(bid.price) || 0).toFixed(2), + width: bid.w, + height: bid.h, + creativeId: bid.crid, + currency: currency, + netRevenue: DEFAULT_NET_REVENUE, + ttl: 300 + }; + if (bid.dealid) { + bidResponse.dealId = bid.dealid; + } + + var req = spec._parse(request.data); + if (req.imp && req.imp.length > 0) { + req.imp.forEach(impr => { + if (impr.id === bid.impid) { + if (impr.banner) { + bidResponse.ad = bid.adm; + bidResponse.mediaType = BANNER; + } else { + bidResponse.width = bid.hasOwnProperty('w') ? bid.w : impr.video.w; + bidResponse.height = bid.hasOwnProperty('h') ? bid.h : impr.video.h; + bidResponse.vastXml = bid.adm; + bidResponse.mediaType = VIDEO; + } + } + }); + } + bidResponses.push(bidResponse); + }); + }); + } + } catch (ex) { + utils.logError(LOG_PREFIX, 'Exception: ', ex); + } + 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) { + if (syncOptions.iframeEnabled) { + return [{ + type: 'iframe', + url: USER_SYNC_ENDPOINT + }]; + } else { + utils.logWarn(LOG_PREFIX + 'Please enable iframe based user sync.'); + } + }, + + /** + * create IAB standard OpenRTB bid request + **/ + _createRequest: function (bidRequests, refererInfo) { + var oRequest = {}; + try { + oRequest = { + id: spec._createUUID(), + imp: spec._createImpressions(bidRequests), + user: {}, + ext: {} + }; + var site = spec._createSite(bidRequests, refererInfo); + var app = spec._createApp(bidRequests); + var device = spec._createDevice(bidRequests); + if (app) { + oRequest.app = app; + } + if (site) { + oRequest.site = site; + } + if (device) { + oRequest.device = device; + } + } catch (ex) { + utils.logError(LOG_PREFIX, 'Exception: ', ex); + oRequest = null; + } + return oRequest; + }, + + /** + * create impression array objects + **/ + _createImpressions: function (request) { + var impressions = []; + var impression = spec._creatImpression(request); + if (impression) { + impressions.push(impression); + } + return impressions; + }, + + /** + * create impression (single) object + **/ + _creatImpression: function (request) { + if (!request.hasOwnProperty(MEDIA_TYPES)) { + return undefined; + } + + var params = request && request.params ? request.params : null; + var impression = { + id: request.bidId ? request.bidId : spec._createUUID(), + secure: window.location.protocol === HTTPS ? 1 : 0, + bidfloorcur: request.params.currency ? request.params.currency : DEFAULT_CURRENCY + }; + if (params.bidFloor) { + impression.bidfloor = params.bidFloor; + } + + if (params.tagId) { + impression.tagid = params.tagId.toString(); + } + + var banner; + var video; + var mediaType; + for (mediaType in request.mediaTypes) { + switch (mediaType) { + case BANNER: + banner = spec._createBanner(request); + if (banner) { + impression.banner = banner; + } + break; + case VIDEO: + video = spec._createVideo(request); + if (video) { + impression.video = video; + } + break; + } + } + + return impression.hasOwnProperty(BANNER) || + impression.hasOwnProperty(VIDEO) ? impression : undefined; + }, + + /** + * create the banner object + **/ + _createBanner: function (request) { + if (utils.deepAccess(request, 'mediaTypes.banner')) { + var banner = {}; + var sizes = request.mediaTypes.banner.sizes; + if (sizes && utils.isArray(sizes) && sizes.length > 0) { + var format = []; + banner.w = sizes[0][0]; + banner.h = sizes[0][1]; + sizes.forEach(size => { + format.push({ + w: size[0], + h: size[1] + }); + }); + banner.format = format; + } + + spec._copyObject(request.mediaTypes.banner, banner); + spec._copyObject(request.params.banner, banner); + return banner; + } + return undefined; + }, + + /** + * create video object + **/ + _createVideo: function (request) { + if (utils.deepAccess(request, 'mediaTypes.video')) { + var video = {}; + var sizes = request.mediaTypes.video.playerSize; + if (sizes && utils.isArray(sizes) && sizes.length > 1) { + video.w = sizes[0]; + video.h = sizes[1]; + } + spec._copyObject(request.mediaTypes.video, video); + spec._copyObject(request.params.video, video); + return video; + } + return undefined; + }, + + /** + * create site object + **/ + _createSite: function (request, refererInfo) { + var rSite = request.params.site; + if (rSite || !request.params.app) { + var site = {}; + spec._copyObject(rSite, site); + + if (refererInfo) { + if (refererInfo.referer) { + site.ref = encodeURIComponent(refererInfo.referer); + } + if (utils.isArray(refererInfo.stack) && refererInfo.stack.length > 0) { + site.page = encodeURIComponent(refererInfo.stack[0]); + let anchrTag = document.createElement('a'); + anchrTag.href = site.page; + site.domain = anchrTag.hostname; + } + } + + // override publisher object + site.publisher = { + id: request.params.publisherId.toString() + }; + return site; + } + return undefined; + }, + + /** + * create app object + **/ + _createApp: function (request) { + var rApp = request.params.app; + if (rApp) { + var app = {}; + spec._copyObject(rApp, app); + // override publisher object + app.publisher = { + id: request.params.publisherId.toString() + }; + return app; + } + return undefined; + }, + + /** + * create device obejct + **/ + _createDevice: function (request) { + var device = {}; + var rDevice = request.params.device; + spec._copyObject(rDevice, device); + device.dnt = utils.getDNT() ? 1 : 0; + device.ua = navigator.userAgent; + device.language = (navigator.language || navigator.browserLanguage || navigator.userLanguage || navigator.systemLanguage); + device.w = (window.screen.width || window.innerWidth); + device.h = (window.screen.height || window.innerHeigh); + return device; + }, + + /** + * set GDPR parameters + **/ + _setGDPRParams: function (bidderRequest, oRequest) { + if (!bidderRequest || !bidderRequest.gdprConsent) { + return; + } + + oRequest.regs = { ext: { gdpr: bidderRequest.gdprConsent.gdprApplies ? 1 : 0 } }; + oRequest.user = { ext: { consent: bidderRequest.gdprConsent.consentString } }; + }, + +} +registerBidder(spec); diff --git a/modules/cosmosBidAdapter.md b/modules/cosmosBidAdapter.md new file mode 100644 index 00000000000..187a19ba17a --- /dev/null +++ b/modules/cosmosBidAdapter.md @@ -0,0 +1,80 @@ +# Overview + +``` +Module Name: Cosmos Bid Adapter +Module Type: Bidder Adapter +Maintainer: dev@cosmoshq.com +``` + +# Description + +Module that connects to Cosmos server for bids. +Supported Ad Fortmats: +* Banner +* Video + +# Configuration +## Following configuration required for enabling user sync. +```javascript +pbjs.setConfig({ + userSync: { + iframeEnabled: true, + enabledBidders: ['cosmos'], + syncDelay: 6000 + }}); +``` +## For Video ads, enable prebid cache +```javascript +pbjs.setConfig({ + cache: { + url: 'https://prebid.adnxs.com/pbc/v1/cache' + } +}); +``` + +# Test Parameters +``` + var adUnits = [ + // Banner adUnit + { + code: 'banner-div', + mediaTypes: { + banner: { //supported as per the openRTB spec + sizes: [[300, 250]] // required + } + }, + bids: [ + { + bidder: "cosmos", + params: { + publisherId: 1001, // required + tagId: 1 // optional + } + } + ] + }, + // Video adUnit + { + code: 'video-div', + mediaTypes: { + video: { // supported as per the openRTB spec + sizes: [[300, 50]], // required + mimes : ['video/mp4', 'application/javascript'], // required + context: 'instream' // optional + } + }, + bids: [ + { + bidder: "cosmos", + params: { + publisherId: 1001, // required + tagId: 1, // optional + video: { // supported as per the openRTB spec + + } + } + } + ] + } + ]; +``` diff --git a/modules/coxBidAdapter.js b/modules/coxBidAdapter.js deleted file mode 100644 index eac1b2081d2..00000000000 --- a/modules/coxBidAdapter.js +++ /dev/null @@ -1,165 +0,0 @@ -'use strict'; - -import * as utils from 'src/utils'; -import { BANNER } from 'src/mediaTypes'; -import { config } from 'src/config'; -import { registerBidder } from 'src/adapters/bidderFactory'; - -const helper = (() => { - let srTestCapabilities = () => { // Legacy - let plugins = navigator.plugins; - let flashVer = -1; - let sf = 'Shockwave Flash'; - - if (plugins && plugins.length > 0) { - if (plugins[sf + ' 2.0'] || plugins[sf]) { - var swVer2 = plugins[sf + ' 2.0'] ? ' 2.0' : ''; - var flashDescription = plugins[sf + swVer2].description; - flashVer = flashDescription.split(' ')[2].split('.')[0]; - } - } - if (flashVer > 4) return 15; else return 7; - }; - - let getRand = () => { - return Math.round(Math.random() * 100000000); - }; - - return { - ingest: function(rawBids = []) { - const adZoneAttributeKeys = ['id', 'size', 'thirdPartyClickUrl', 'dealId']; - const otherKeys = ['siteId', 'wrapper', 'referrerUrl']; - let state = this.createState(); - - rawBids.forEach(oneBid => { - let params = oneBid.params || {}; - - state.tag.auctionId = oneBid.auctionId; - state.tag.responseJSON = true; - - if (params.id && (/^\d+x\d+$/).test(params.size)) { - let adZoneKey = 'as' + params.id; - let zone = {}; - - zone.transactionId = oneBid.transactionId; - zone.bidId = oneBid.bidId; - state.tag.zones = state.tag.zones || {}; - state.tag.zones[adZoneKey] = zone; - - adZoneAttributeKeys.forEach(key => { if (params[key]) zone[key] = params[key]; }); - otherKeys.forEach(key => { if (params[key]) state.tag[key] = params[key]; }); - - // Check for an environment setting - if (params.env) state.env = params.env; - - // Update the placement map - let [x, y] = (params.size).split('x'); - state.placementMap[adZoneKey] = { - 'b': oneBid.bidId, - 'w': x, - 'h': y - }; - } - }); - return state; - }, - - transform: function(coxRawBids = {}, state) { - const pbjsBids = []; - - for (let adZoneKey in state.placementMap) { - let responded = coxRawBids[adZoneKey] - let ingested = state.placementMap[adZoneKey]; - - utils.logInfo('coxBidAdapter.transform', adZoneKey, responded, ingested); - - if (ingested && responded && responded['ad'] && responded['price'] > 0) { - pbjsBids.push({ - requestId: ingested['b'], - cpm: responded['price'], - width: ingested['w'], - height: ingested['h'], - creativeId: responded['adid'], - dealId: responded['dealid'], - currency: 'USD', - netRevenue: true, - ttl: 300, - ad: responded['ad'] - }); - } - } - return pbjsBids; - }, - - getUrl: state => { - // Bounce if the tag is invalid - if (!state.tag.zones) return null; - - let src = (document.location.protocol === 'https:' ? 'https://' : 'http://') + - (!state.env || state.env === 'PRD' ? '' : state.env === 'PPE' ? 'ppe-' : state.env === 'STG' ? 'staging-' : '') + - 'ad.afy11.net/ad?mode=11&nif=0&sf=0&sfd=0&ynw=0&hb=1' + - '&ct=' + srTestCapabilities() + - '&rand=' + getRand() + - '&rk1=' + getRand() + - '&rk2=' + new Date().valueOf() / 1000; - - state.tag.pageUrl = config.getConfig('pageUrl') || utils.getTopWindowUrl(); - state.tag.puTop = true; - - // Attach the serialized tag to our string - src += '&ab=' + encodeURIComponent(JSON.stringify(state.tag)); - - return src; - }, - - createState: () => ({ - env: '', - tag: {}, - placementMap: {} - }) - }; -})(); - -export const spec = { - code: 'cox', - supportedMediaTypes: [BANNER], - - isBidRequestValid: function(bid) { - return !!(bid.params && bid.params.id && bid.params.size); - }, - - buildRequests: function(validBidReqs) { - let state = helper.ingest(validBidReqs); - let url = helper.getUrl(state); - - return !url ? {} : { - method: 'GET', - url: url, - state - }; - }, - - interpretResponse: function({ body: { zones: coxRawBids } }, { state }) { - let bids = helper.transform(coxRawBids, state); - - utils.logInfo('coxBidAdapter.interpretResponse', bids); - return bids; - }, - - getUserSyncs: function(syncOptions, thing) { - try { - var [{ body: { tpCookieSync: urls = [] } }] = thing; - } catch (ignore) { - return []; - } - - let syncs = []; - if (syncOptions.pixelEnabled && urls.length > 0) { - syncs = urls.map((url) => ({ type: 'image', url: url })) - } - utils.logInfo('coxBidAdapter.getuserSyncs', syncs); - return syncs; - } -}; - -registerBidder(spec); diff --git a/modules/coxBidAdapter.md b/modules/coxBidAdapter.md deleted file mode 100644 index f4460b969ed..00000000000 --- a/modules/coxBidAdapter.md +++ /dev/null @@ -1,41 +0,0 @@ -# Overview - -``` -Module Name: Cox/COMET Bid Adapter -Module Type: Bidder Adapter -Maintainer: reynold@coxds.com -``` - -# Description - -Cox/COMET's adapter integration to the Prebid library. - -# Test Parameters - -``` -var adUnits = [ - { - code: 'test-leaderboard', - sizes: [[728, 90]], - bids: [{ - bidder: 'cox', - params: { - size: '728x90', - id: 2000005991607, - siteId: 2000100948180, - } - }] - }, { - code: 'test-banner', - sizes: [[300, 250]], - bids: [{ - bidder: 'cox', - params: { - size: '300x250', - id: 2000005991707, - siteId: 2000100948180, - } - }] - } -] -``` \ No newline at end of file diff --git a/modules/cpmstarBidAdapter.js b/modules/cpmstarBidAdapter.js new file mode 100644 index 00000000000..84b76cbbc35 --- /dev/null +++ b/modules/cpmstarBidAdapter.js @@ -0,0 +1,118 @@ + +import * as utils from '../src/utils'; +import { registerBidder } from '../src/adapters/bidderFactory'; +import {VIDEO, BANNER} from '../src/mediaTypes'; + +const BIDDER_CODE = 'cpmstar'; + +const ENDPOINT_DEV = '//dev.server.cpmstar.com/view.aspx'; +const ENDPOINT_STAGING = '//staging.server.cpmstar.com/view.aspx'; +const ENDPOINT_PRODUCTION = '//server.cpmstar.com/view.aspx'; + +const DEFAULT_TTL = 300; +const DEFAULT_CURRENCY = 'USD'; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO], + pageID: Math.floor(Math.random() * 10e6), + + getMediaType: function(bidRequest) { + if (bidRequest == null) return BANNER; + return !utils.deepAccess(bidRequest, 'mediaTypes.video') ? BANNER : VIDEO; + }, + + getPlayerSize: function(bidRequest) { + var playerSize = utils.deepAccess(bidRequest, 'mediaTypes.video.playerSize'); + if (playerSize == null) return [640, 440]; + if (playerSize[0] != null) playerSize = playerSize[0]; + if (playerSize == null || playerSize[0] == null || playerSize[1] == null) return [640, 440]; + return playerSize; + }, + + isBidRequestValid: function (bid) { + return ((typeof bid.params.placementId === 'string') && !!bid.params.placementId.length) || (typeof bid.params.placementId === 'number'); + }, + + buildRequests: function (validBidRequests, bidderRequest) { + var requests = []; + // This reference to window.top can cause issues when loaded in an iframe if not protected with a try/catch. + + for (var i = 0; i < validBidRequests.length; i++) { + var bidRequest = validBidRequests[i]; + var referer = encodeURIComponent(bidderRequest.refererInfo.referer); + var e = utils.getBidIdParameter('endpoint', bidRequest.params); + var ENDPOINT = e == 'dev' ? ENDPOINT_DEV : e == 'staging' ? ENDPOINT_STAGING : ENDPOINT_PRODUCTION; + var mediaType = spec.getMediaType(bidRequest); + var playerSize = spec.getPlayerSize(bidRequest); + var videoArgs = '&fv=0' + (playerSize ? ('&w=' + playerSize[0] + '&h=' + playerSize[1]) : ''); + requests.push({ + method: 'GET', + url: ENDPOINT + '?media=' + mediaType + (mediaType == VIDEO ? videoArgs : '') + '&json=c_b&mv=1&poolid=' + utils.getBidIdParameter('placementId', bidRequest.params) + '&reachedTop=' + encodeURIComponent(bidderRequest.refererInfo.reachedTop) + '&requestid=' + bidRequest.bidId + '&referer=' + referer, + bidRequest: bidRequest, + }); + } + + return requests; + }, + + interpretResponse: function (serverResponse, request) { + var bidRequest = request.bidRequest; + var mediaType = spec.getMediaType(bidRequest); + + var bidResponses = []; + + if (!Array.isArray(serverResponse.body)) { + serverResponse.body = [serverResponse.body]; + } + + for (var i = 0; i < serverResponse.body.length; i++) { + var raw = serverResponse.body[i]; + var rawBid = raw.creatives[0]; + if (!rawBid) { + utils.logWarn('cpmstarBidAdapter: server response failed check'); + return; + } + var cpm = (parseFloat(rawBid.cpm) || 0); + + if (!cpm) { + utils.logWarn('cpmstarBidAdapter: server response failed check. Missing cpm') + return; + } + + var bidResponse = { + requestId: rawBid.requestid, + cpm: cpm, + width: rawBid.width || 0, + height: rawBid.height || 0, + currency: rawBid.currency ? rawBid.currency : DEFAULT_CURRENCY, + netRevenue: rawBid.netRevenue ? rawBid.netRevenue : true, + ttl: rawBid.ttl ? rawBid.ttl : DEFAULT_TTL, + creativeId: rawBid.creativeid || 0, + }; + + if (rawBid.hasOwnProperty('dealId')) { + bidResponse.dealId = rawBid.dealId + } + + if (mediaType == BANNER && rawBid.code) { + bidResponse.ad = rawBid.code + (rawBid.px_cr ? "\n" : ''); + } else if (mediaType == VIDEO && rawBid.creativemacros && rawBid.creativemacros.HTML5VID_VASTSTRING) { + var playerSize = spec.getPlayerSize(bidRequest); + if (playerSize != null) { + bidResponse.width = playerSize[0]; + bidResponse.height = playerSize[1]; + } + bidResponse.mediaType = VIDEO; + bidResponse.vastXml = rawBid.creativemacros.HTML5VID_VASTSTRING; + } else { + return utils.logError('bad response', rawBid); + } + + bidResponses.push(bidResponse); + } + + return bidResponses; + } +}; +registerBidder(spec); diff --git a/modules/cpmstarBidAdapter.md b/modules/cpmstarBidAdapter.md new file mode 100644 index 00000000000..7dab435b0f0 --- /dev/null +++ b/modules/cpmstarBidAdapter.md @@ -0,0 +1,50 @@ +# Overview + +``` +Module Name: Cpmstar Bidder Adapter +Module Type: Bidder Adapter +Maintainer: josh@cpmstar.com +``` + +# Description + +Module that connects to Cpmstar's demand sources + +# Test Parameters +``` +var adUnits = [ + { + code: 'banner-ad-div', + mediaTypes: { + banner: { + sizes: [[300, 250]], + } + }, + bids: [ + { + bidder: 'cpmstar', + params: { + placementId: 81006 + } + }, + ] + }, + { + code: 'video-ad-div', + mediaTypes: { + video: { + context: 'instream', + sizes: [[640, 480]] + } + }, + bids:[ + { + bidder: 'cpmstar', + params: { + placementId: 81007 + } + } + ] + } +]; +``` \ No newline at end of file diff --git a/modules/criteoBidAdapter.js b/modules/criteoBidAdapter.js old mode 100755 new mode 100644 index 0595fc890f0..7f292c76e20 --- a/modules/criteoBidAdapter.js +++ b/modules/criteoBidAdapter.js @@ -1,33 +1,54 @@ -import { loadExternalScript } from 'src/adloader'; -import { registerBidder } from 'src/adapters/bidderFactory'; -import { parse } from 'src/url'; -import * as utils from 'src/utils'; +import { loadExternalScript } from '../src/adloader'; +import { registerBidder } from '../src/adapters/bidderFactory'; +import { config } from '../src/config'; +import { BANNER, VIDEO } from '../src/mediaTypes'; +import { parse } from '../src/url'; +import * as utils from '../src/utils'; import find from 'core-js/library/fn/array/find'; +import JSEncrypt from 'jsencrypt/bin/jsencrypt'; +import sha256 from 'crypto-js/sha256'; -const ADAPTER_VERSION = 11; +export const ADAPTER_VERSION = 23; const BIDDER_CODE = 'criteo'; -const CDB_ENDPOINT = '//bidder.criteo.com/cdb'; +const CDB_ENDPOINT = 'https://bidder.criteo.com/cdb'; const CRITEO_VENDOR_ID = 91; -const INTEGRATION_MODES = { - 'amp': 1, -}; const PROFILE_ID_INLINE = 207; -const PROFILE_ID_PUBLISHERTAG = 185; +export const PROFILE_ID_PUBLISHERTAG = 185; // Unminified source code can be found in: https://github.com/Prebid-org/prebid-js-external-js-criteo/blob/master/dist/prod.js const PUBLISHER_TAG_URL = '//static.criteo.net/js/ld/publishertag.prebid.js'; +export const FAST_BID_PUBKEY = `-----BEGIN PUBLIC KEY----- +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDO1BjAITkFTtP0IMzmF7qsqhpu +y1dGaTPHnjMU9mRZsrnfR3C0sEN5pYEzEcFRPnkJjJuhH8Rnh5+CE+LcKg0Z8ZZ7 +OmOSj0/qnYTAYCu0cR5LiyWG79KlIgUyMbp92ulGg24gAyGrVn4+v/4c53WlOEUp +4YWvb82G0CD5NcDNpQIDAQAB +-----END PUBLIC KEY-----`; + /** @type {BidderSpec} */ export const spec = { code: BIDDER_CODE, + supportedMediaTypes: [ BANNER, VIDEO ], /** * @param {object} bid * @return {boolean} */ - isBidRequestValid: bid => ( - !!(bid && bid.params && (bid.params.zoneId || bid.params.networkId)) - ), + isBidRequestValid: (bid) => { + // either one of zoneId or networkId should be set + if (!(bid && bid.params && (bid.params.zoneId || bid.params.networkId))) { + return false; + } + + // video media types requires some mandatory params + if (hasVideoMediaType(bid)) { + if (!hasValidVideoMediaType(bid)) { + return false; + } + } + + return true; + }, /** * @param {BidRequest[]} bidRequests @@ -38,6 +59,8 @@ export const spec = { let url; let data; + Object.assign(bidderRequest, { ceh: config.getConfig('criteo.ceh') }); + // If publisher tag not already loaded try to get it from fast bid if (!publisherTagAvailable()) { window.Criteo = window.Criteo || {}; @@ -56,7 +79,7 @@ export const spec = { url = adapter.buildCdbUrl(); data = adapter.buildCdbRequest(); } else { - const context = buildContext(bidRequests); + const context = buildContext(bidRequests, bidderRequest); url = buildCdbUrl(context); data = buildCdbRequest(context, bidRequests, bidderRequest); } @@ -89,6 +112,7 @@ export const spec = { const bidId = bidRequest.bidId; const bid = { requestId: bidId, + adId: slot.bidId || utils.getUniqueIdentifierStr(), cpm: slot.cpm, currency: slot.currency, netRevenue: true, @@ -96,9 +120,13 @@ export const spec = { creativeId: bidId, width: slot.width, height: slot.height, + dealId: slot.dealCode, } if (slot.native) { bid.ad = createNativeAd(bidId, slot.native, bidRequest.params.nativeCallback); + } else if (slot.video) { + bid.vastUrl = slot.displayurl; + bid.mediaType = VIDEO; } else { bid.ad = slot.creative; } @@ -118,6 +146,26 @@ export const spec = { adapter.handleBidTimeout(); } }, + + /** + * @param {Bid} bid + */ + onBidWon: (bid) => { + if (publisherTagAvailable()) { + const adapter = Criteo.PubTag.Adapters.Prebid.GetAdapter(bid.auctionId); + adapter.handleBidWon(bid); + } + }, + + /** + * @param {Bid} bid + */ + onSetTargeting: (bid) => { + if (publisherTagAvailable()) { + const adapter = Criteo.PubTag.Adapters.Prebid.GetAdapter(bid.auctionId); + adapter.handleSetTargeting(bid); + } + }, }; /** @@ -129,22 +177,26 @@ function publisherTagAvailable() { /** * @param {BidRequest[]} bidRequests + * @param bidderRequest * @return {CriteoContext} */ -function buildContext(bidRequests) { - const url = utils.getTopWindowUrl(); - const queryString = parse(url).search; +function buildContext(bidRequests, bidderRequest) { + let referrer = ''; + if (bidderRequest && bidderRequest.refererInfo) { + referrer = bidderRequest.refererInfo.referer; + } + const queryString = parse(referrer).search; const context = { - url: url, + url: referrer, debug: queryString['pbt_debug'] === '1', noLog: queryString['pbt_nolog'] === '1', - integrationMode: undefined, + amp: false, }; bidRequests.forEach(bidRequest => { - if (bidRequest.params.integrationMode) { - context.integrationMode = bidRequest.params.integrationMode; + if (bidRequest.params.integrationMode === 'amp') { + context.amp = true; } }) @@ -162,8 +214,8 @@ function buildCdbUrl(context) { url += '&wv=' + encodeURIComponent('$prebid.version$'); url += '&cb=' + String(Math.floor(Math.random() * 99999999999)); - if (context.integrationMode in INTEGRATION_MODES) { - url += '&im=' + INTEGRATION_MODES[context.integrationMode]; + if (context.amp) { + url += '&im=1'; } if (context.debug) { url += '&debug=1'; @@ -192,7 +244,7 @@ function buildCdbRequest(context, bidRequests, bidderRequest) { impid: bidRequest.adUnitCode, transactionid: bidRequest.transactionId, auctionId: bidRequest.auctionId, - sizes: bidRequest.sizes.map(size => size[0] + 'x' + size[1]), + sizes: getBannerSizes(bidRequest), }; if (bidRequest.params.zoneId) { slot.zoneid = bidRequest.params.zoneId; @@ -203,12 +255,33 @@ function buildCdbRequest(context, bidRequests, bidderRequest) { if (bidRequest.params.nativeCallback) { slot.native = true; } + if (hasVideoMediaType(bidRequest)) { + const video = { + playersizes: getVideoSizes(bidRequest), + mimes: bidRequest.mediaTypes.video.mimes, + protocols: bidRequest.mediaTypes.video.protocols, + maxduration: bidRequest.mediaTypes.video.maxduration, + api: bidRequest.mediaTypes.video.api + } + + video.skip = bidRequest.params.video.skip; + video.placement = bidRequest.params.video.placement; + video.minduration = bidRequest.params.video.minduration; + video.playbackmethod = bidRequest.params.video.playbackmethod; + video.startdelay = bidRequest.params.video.startdelay; + + slot.video = video; + } return slot; }), }; if (networkId) { request.publisher.networkid = networkId; } + request.user = {}; + if (bidderRequest && bidderRequest.ceh) { + request.user.ceh = bidderRequest.ceh; + } if (bidderRequest && bidderRequest.gdprConsent) { request.gdprConsent = {}; if (typeof bidderRequest.gdprConsent.gdprApplies !== 'undefined') { @@ -225,6 +298,66 @@ function buildCdbRequest(context, bidRequests, bidderRequest) { return request; } +function getVideoSizes(bidRequest) { + return parseSizes(utils.deepAccess(bidRequest, 'mediaTypes.video.playerSize')); +} + +function getBannerSizes(bidRequest) { + return parseSizes(utils.deepAccess(bidRequest, 'mediaTypes.banner.sizes') || bidRequest.sizes); +} + +function parseSize(size) { + return size[0] + 'x' + size[1]; +} + +function parseSizes(sizes) { + if (Array.isArray(sizes[0])) { // is there several sizes ? (ie. [[728,90],[200,300]]) + return sizes.map(size => parseSize(size)); + } + + return [parseSize(sizes)]; // or a single one ? (ie. [728,90]) +} + +function hasVideoMediaType(bidRequest) { + if (utils.deepAccess(bidRequest, 'params.video') === undefined) { + return false; + } + return utils.deepAccess(bidRequest, 'mediaTypes.video') !== undefined; +} + +function hasValidVideoMediaType(bidRequest) { + let isValid = true; + + var requiredMediaTypesParams = ['mimes', 'playerSize', 'maxduration', 'protocols', 'api']; + + requiredMediaTypesParams.forEach(function(param) { + if (utils.deepAccess(bidRequest, 'mediaTypes.video.' + param) === undefined) { + isValid = false; + utils.logError('Criteo Bid Adapter: mediaTypes.video.' + param + ' is required'); + } + }); + + var requiredParams = ['skip', 'placement', 'playbackmethod']; + + requiredParams.forEach(function(param) { + if (utils.deepAccess(bidRequest, 'params.video.' + param) === undefined) { + isValid = false; + utils.logError('Criteo Bid Adapter: params.video.' + param + ' is required'); + } + }); + + if (isValid) { + // We do not support long form for now, also we have to check that context & placement are consistent + if (bidRequest.mediaTypes.video.context == 'instream' && bidRequest.params.video.placement === 1) { + return true; + } else if (bidRequest.mediaTypes.video.context == 'outstream' && bidRequest.params.video.placement !== 1) { + return true; + } + } + + return false; +} + /** * @param {string} id * @param {*} payload @@ -254,17 +387,39 @@ function createNativeAd(id, payload, callback) { /** * @return {boolean} */ -function tryGetCriteoFastBid() { +export function tryGetCriteoFastBid() { try { - const fastBid = localStorage.getItem('criteo_fast_bid'); - if (fastBid !== null) { - eval(fastBid); // eslint-disable-line no-eval - return true; + const fastBidStorageKey = 'criteo_fast_bid'; + const hashPrefix = '// Hash: '; + const fastBidFromStorage = localStorage.getItem(fastBidStorageKey); + + if (fastBidFromStorage !== null) { + // The value stored must contain the file's encrypted hash as first line + const firstLineEndPosition = fastBidFromStorage.indexOf('\n'); + const firstLine = fastBidFromStorage.substr(0, firstLineEndPosition).trim(); + + if (firstLine.substr(0, hashPrefix.length) !== hashPrefix) { + utils.logWarn('No hash found in FastBid'); + localStorage.removeItem(fastBidStorageKey); + } else { + // Remove the hash part from the locally stored value + const publisherTagHash = firstLine.substr(hashPrefix.length); + const publisherTag = fastBidFromStorage.substr(firstLineEndPosition + 1); + + var jsEncrypt = new JSEncrypt(); + jsEncrypt.setPublicKey(FAST_BID_PUBKEY); + if (jsEncrypt.verify(publisherTag, publisherTagHash, sha256)) { + utils.logInfo('Using Criteo FastBid'); + eval(publisherTag); // eslint-disable-line no-eval + } else { + utils.logWarn('Invalid Criteo FastBid found'); + localStorage.removeItem(fastBidStorageKey); + } + } } } catch (e) { // Unable to get fast bid } - return false; } registerBidder(spec); diff --git a/modules/criteoBidAdapter.md b/modules/criteoBidAdapter.md old mode 100755 new mode 100644 index 796c70a980f..e4c441c758d --- a/modules/criteoBidAdapter.md +++ b/modules/criteoBidAdapter.md @@ -25,3 +25,13 @@ Module that connects to Criteo's demand sources. } ]; ``` + +# Additional Config (Optional) +Set the "ceh" property to provides the user's hashed email if available +``` + pbjs.setConfig({ + criteo: { + ceh: 'hashed mail' + } + }); +``` \ No newline at end of file diff --git a/modules/criteortusIdSystem.js b/modules/criteortusIdSystem.js new file mode 100644 index 00000000000..8486bfae9f3 --- /dev/null +++ b/modules/criteortusIdSystem.js @@ -0,0 +1,106 @@ +/** + * This module adds Criteo Real Time User Sync to the User ID module + * The {@link module:modules/userId} module is required + * @module modules/criteortusIdSystem + * @requires module:modules/userId + */ + +import * as utils from '../src/utils' +import { ajax } from '../src/ajax'; +import { submodule } from '../src/hook'; + +const key = '__pbjs_criteo_rtus'; + +/** @type {Submodule} */ +export const criteortusIdSubmodule = { + /** + * used to link submodule with config + * @type {string} + */ + name: 'criteortus', + /** + * decode the stored id value for passing to bid requests + * @function + * @returns {{criteortus:Object}} + */ + decode() { + let uid = utils.getCookie(key); + try { + uid = JSON.parse(uid); + return { 'criteortus': uid }; + } catch (error) { + utils.logError('Error in parsing criteo rtus data', error); + } + }, + /** + * performs action to obtain id and return a value in the callback's response argument + * @function + * @param {SubmoduleParams} [configParams] + * @returns {IdResponse|undefined} + */ + getId(configParams) { + if (!configParams || !utils.isPlainObject(configParams.clientIdentifier)) { + utils.logError('User ID - Criteo rtus requires client identifier to be defined'); + return; + } + + let uid = utils.getCookie(key); + if (uid) { + return {id: uid}; + } else { + let userIds = {}; + const resp = function(callback) { + let bidders = Object.keys(configParams.clientIdentifier); + + function afterAllResponses() { + // criteo rtus user id expires in 1 hour + const expiresStr = (new Date(Date.now() + (60 * 60 * 1000))).toUTCString(); + utils.setCookie(key, JSON.stringify(userIds), expiresStr); + callback(userIds); + } + + const onResponse = utils.delayExecution(afterAllResponses, bidders.length); + + bidders.forEach((bidder) => { + let url = `https://gum.criteo.com/sync?c=${configParams.clientIdentifier[bidder]}&r=3`; + const getSuccessHandler = (bidder) => { + return function onSuccess(response) { + if (response) { + try { + response = JSON.parse(response); + userIds[bidder] = response; + onResponse(); + } catch (error) { + utils.logError(error); + } + } + } + } + + const getFailureHandler = (bidder) => { + return function onFailure(error) { + utils.logError(`Criteo RTUS server call failed for ${bidder}`, error); + onResponse(); + } + } + + ajax( + url, + { + success: getSuccessHandler(bidder), + error: getFailureHandler(bidder) + }, + undefined, + Object.assign({ + method: 'GET', + withCredentials: true + }) + ); + }) + }; + return {callback: resp}; + } + } +}; + +submodule('userId', criteortusIdSubmodule); diff --git a/modules/currency.js b/modules/currency.js index f66c33bbed8..ae2f9ac1f1b 100644 --- a/modules/currency.js +++ b/modules/currency.js @@ -1,16 +1,17 @@ -import bidfactory from 'src/bidfactory'; -import { STATUS } from 'src/constants'; -import { ajax } from 'src/ajax'; -import * as utils from 'src/utils'; -import { config } from 'src/config'; -import { hooks } from 'src/hook.js'; - -const DEFAULT_CURRENCY_RATE_URL = 'https://currency.prebid.org/latest.json'; +import { createBid } from '../src/bidfactory'; +import { STATUS } from '../src/constants'; +import { ajax } from '../src/ajax'; +import * as utils from '../src/utils'; +import { config } from '../src/config'; +import { getHook } from '../src/hook.js'; + +const DEFAULT_CURRENCY_RATE_URL = 'https://cdn.jsdelivr.net/gh/prebid/currency-file@1/latest.json?date=$$TODAY$$'; const CURRENCY_RATE_PRECISION = 4; var bidResponseQueue = []; var conversionCache = {}; var currencyRatesLoaded = false; +var needToCallForCurrencyFile = true; var adServerCurrency = 'USD'; export var currencySupportEnabled = false; @@ -34,7 +35,7 @@ var defaultRates; * { * rubicon: 'USD' * } - * @param {string} [config.conversionRateFile = 'http://currency.prebid.org/latest.json'] + * @param {string} [config.conversionRateFile = 'URL pointing to conversion file'] * Optional path to a file containing currency conversion data. Prebid.org hosts a file that is used as the default, * if not specified. * @param {object} [config.rates] @@ -56,10 +57,15 @@ export function setConfig(config) { if (typeof config.rates === 'object') { currencyRates.conversions = config.rates; currencyRatesLoaded = true; + needToCallForCurrencyFile = false; // don't call if rates are already specified } if (typeof config.defaultRates === 'object') { defaultRates = config.defaultRates; + + // set up the default rates to be used if the rate file doesn't get loaded in time + currencyRates.conversions = defaultRates; + currencyRatesLoaded = true; } if (typeof config.adServerCurrency === 'string') { @@ -70,6 +76,25 @@ export function setConfig(config) { utils.logInfo('currency using override conversionRateFile:', config.conversionRateFile); url = config.conversionRateFile; } + + // see if the url contains a date macro + // this is a workaround to the fact that jsdelivr doesn't currently support setting a 24-hour HTTP cache header + // So this is an approach to let the browser cache a copy of the file each day + // We should remove the macro once the CDN support a day-level HTTP cache setting + const macroLocation = url.indexOf('$$TODAY$$'); + if (macroLocation !== -1) { + // get the date to resolve the macro + const d = new Date(); + let month = `${d.getMonth() + 1}`; + let day = `${d.getDate()}`; + if (month.length < 2) month = `0${month}`; + if (day.length < 2) day = `0${day}`; + const todaysDate = `${d.getFullYear()}${month}${day}`; + + // replace $$TODAY$$ with todaysDate + url = `${url.substring(0, macroLocation)}${todaysDate}${url.substring(macroLocation + 9, url.length)}`; + } + initCurrency(url); } else { // currency support is disabled, setting defaults @@ -84,8 +109,6 @@ config.getConfig('currency', config => setConfig(config.currency)); function errorSettingsRates(msg) { if (defaultRates) { - currencyRates.conversions = defaultRates; - currencyRatesLoaded = true; utils.logWarn(msg); utils.logWarn('Currency failed loading rates, falling back to currency.defaultRates'); } else { @@ -99,9 +122,11 @@ function initCurrency(url) { utils.logInfo('Installing addBidResponse decorator for currency module', arguments); - hooks['addBidResponse'].addHook(addBidResponseHook, 100); + getHook('addBidResponse').before(addBidResponseHook, 100); - if (!currencyRates.conversions) { + // call for the file if we haven't already + if (needToCallForCurrencyFile) { + needToCallForCurrencyFile = false; ajax(url, { success: function (response) { @@ -123,19 +148,20 @@ function initCurrency(url) { function resetCurrency() { utils.logInfo('Uninstalling addBidResponse decorator for currency module', arguments); - hooks['addBidResponse'].removeHook(addBidResponseHook, 100); + getHook('addBidResponse').getHooks({hook: addBidResponseHook}).remove(); adServerCurrency = 'USD'; conversionCache = {}; currencySupportEnabled = false; currencyRatesLoaded = false; + needToCallForCurrencyFile = true; currencyRates = {}; bidderCurrencyDefault = {}; } -export function addBidResponseHook(adUnitCode, bid, fn) { +export function addBidResponseHook(fn, adUnitCode, bid) { if (!bid) { - return fn.apply(this, arguments); // if no bid, call original and let it display warnings + return fn.call(this, adUnitCode); // if no bid, call original and let it display warnings } let bidder = bid.bidderCode || bid.bidder; @@ -154,20 +180,20 @@ export function addBidResponseHook(adUnitCode, bid, fn) { bid.currency = 'USD'; } - let fromCurrency = bid.currency; - let cpm = bid.cpm; - // used for analytics bid.getCpmInNewCurrency = function(toCurrency) { - return (parseFloat(cpm) * getCurrencyConversion(fromCurrency, toCurrency)).toFixed(3); + return (parseFloat(this.cpm) * getCurrencyConversion(this.currency, toCurrency)).toFixed(3); }; + bid.originalCpm = bid.cpm; + bid.originalCurrency = bid.currency; + // execute immediately if the bid is already in the desired currency if (bid.currency === adServerCurrency) { - return fn.apply(this, arguments); + return fn.call(this, adUnitCode, bid); } - bidResponseQueue.push(wrapFunction(fn, this, arguments)); + bidResponseQueue.push(wrapFunction(fn, this, [adUnitCode, bid])); if (!currencySupportEnabled || currencyRatesLoaded) { processBidResponseQueue(); } @@ -186,17 +212,15 @@ function wrapFunction(fn, context, params) { let fromCurrency = bid.currency; try { let conversion = getCurrencyConversion(fromCurrency); - bid.originalCpm = bid.cpm; - bid.originalCurrency = bid.currency; if (conversion !== 1) { bid.cpm = (parseFloat(bid.cpm) * conversion).toFixed(4); bid.currency = adServerCurrency; } } catch (e) { utils.logWarn('Returning NO_BID, getCurrencyConversion threw error: ', e); - params[1] = bidfactory.createBid(STATUS.NO_BID, { + params[1] = createBid(STATUS.NO_BID, { bidder: bid.bidderCode || bid.bidder, - bidId: bid.adId + bidId: bid.requestId }); } } diff --git a/modules/danmarketBidAdapter.js b/modules/danmarketBidAdapter.js index d851af424ce..77f90f43319 100644 --- a/modules/danmarketBidAdapter.js +++ b/modules/danmarketBidAdapter.js @@ -1,5 +1,5 @@ -import * as utils from 'src/utils'; -import {registerBidder} from 'src/adapters/bidderFactory'; +import * as utils from '../src/utils'; +import {registerBidder} from '../src/adapters/bidderFactory'; const BIDDER_CODE = 'danmarket'; const ENDPOINT_URL = '//ads.danmarketplace.com/hb'; const TIME_TO_LIVE = 360; diff --git a/modules/danmarketBidAdapter.md b/modules/danmarketBidAdapter.md old mode 100755 new mode 100644 diff --git a/modules/datablocksAnalyticsAdapter.js b/modules/datablocksAnalyticsAdapter.js new file mode 100644 index 00000000000..76dd490180b --- /dev/null +++ b/modules/datablocksAnalyticsAdapter.js @@ -0,0 +1,19 @@ +/** + * Analytics Adapter for Datablocks + */ + +import adapter from '../src/AnalyticsAdapter'; +import adapterManager from '../src/adapterManager'; + +var datablocksAdapter = adapter({ + global: 'datablocksAnalytics', + handler: 'on', + analyticsType: 'bundle' +}); + +adapterManager.registerAnalyticsAdapter({ + adapter: datablocksAdapter, + code: 'datablocks' +}); + +export default datablocksAdapter; diff --git a/modules/datablocksAnalyticsAdapter.md b/modules/datablocksAnalyticsAdapter.md new file mode 100644 index 00000000000..07f65da6e2c --- /dev/null +++ b/modules/datablocksAnalyticsAdapter.md @@ -0,0 +1,23 @@ +# Overview + +Module Name: Datablocks Analytics Adapter +Module Type: Datablocks Adapter +Maintainer: support@datablocks.net + +# Description + +Analytics adapter for Datablocks.net. Contact support@datablocks.net for information. + +# Test Parameters + +``` +{ + provider: 'datablocks', + options: { + publisherId: 12345, + sourceId: 12356, + host: 'prebid.datablocks.net' + + } +} +``` \ No newline at end of file diff --git a/modules/datablocksBidAdapter.js b/modules/datablocksBidAdapter.js new file mode 100644 index 00000000000..3e9bf219c75 --- /dev/null +++ b/modules/datablocksBidAdapter.js @@ -0,0 +1,331 @@ +import * as utils from '../src/utils'; +import { registerBidder } from '../src/adapters/bidderFactory'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes'; +import { parse as parseUrl } from '../src/url'; +const NATIVE_MAP = { + 'body': 2, + 'body2': 10, + 'price': 6, + 'displayUrl': 11, + 'cta': 12 +}; +const NATIVE_IMAGE = [{ + id: 1, + required: 1, + title: { + len: 140 + } +}, { + id: 2, + required: 1, + img: { type: 3 } +}, { + id: 3, + required: 1, + data: { + type: 11 + } +}, { + id: 4, + required: 0, + data: { + type: 2 + } +}, { + id: 5, + required: 0, + img: { type: 1 } +}, { + id: 6, + required: 0, + data: { + type: 12 + } +}]; + +const VIDEO_PARAMS = ['mimes', 'minduration', 'maxduration', 'protocols', 'w', 'h', 'startdelay', + 'placement', 'linearity', 'skip', 'skipmin', 'skipafter', 'sequence', 'battr', 'maxextended', + 'minbitrate', 'maxbitrate', 'boxingallowed', 'playbackmethod', 'playbackend', 'delivery', + 'pos', 'companionad', 'api', 'companiontype', 'ext']; + +export const spec = { + supportedMediaTypes: [BANNER, NATIVE, VIDEO], + code: 'datablocks', + isBidRequestValid: function(bid) { + return !!(bid.params.host && bid.params.sourceId && + bid.mediaTypes && (bid.mediaTypes.banner || bid.mediaTypes.native || bid.mediaTypes.video)); + }, + buildRequests: function(validBidRequests, bidderRequest) { + if (!validBidRequests.length) { return []; } + + let imps = {}; + let site = {}; + let device = {}; + let refurl = parseUrl(bidderRequest.referrer); + let requests = []; + + validBidRequests.forEach(bidRequest => { + let imp = { + id: bidRequest.bidId, + tagid: bidRequest.adUnitCode, + secure: window.location.protocol == 'https:' + } + + if (utils.deepAccess(bidRequest, `mediaTypes.banner`)) { + let sizes = bidRequest.mediaTypes.banner.sizes; + if (sizes.length == 1) { + imp.banner = { + w: sizes[0][0], + h: sizes[0][1] + } + } else if (sizes.length > 1) { + imp.banner = { + format: sizes.map(size => ({ w: size[0], h: size[1] })) + }; + } else { + return; + } + } else if (utils.deepAccess(bidRequest, 'mediaTypes.native')) { + let nativeImp = bidRequest.mediaTypes.native; + + if (nativeImp.type) { + let nativeAssets = []; + switch (nativeImp.type) { + case 'image': + nativeAssets = NATIVE_IMAGE; + break; + default: + return; + } + imp.native = JSON.stringify({ assets: nativeAssets }); + } else { + let nativeAssets = []; + let nativeKeys = Object.keys(nativeImp); + nativeKeys.forEach((nativeKey, index) => { + let required = !!nativeImp[nativeKey].required; + let assetId = index + 1; + switch (nativeKey) { + case 'title': + nativeAssets.push({ + id: assetId, + required: required, + title: { + len: nativeImp[nativeKey].len || 140 + } + }); + break; + case 'body': // desc + case 'body2': // desc2 + case 'price': + case 'display_url': + let data = { + id: assetId, + required: required, + data: { + type: NATIVE_MAP[nativeKey] + } + } + if (nativeImp[nativeKey].data && nativeImp[nativeKey].data.len) { data.data.len = nativeImp[nativeKey].data.len; } + + nativeAssets.push(data); + break; + case 'image': + if (nativeImp[nativeKey].sizes && nativeImp[nativeKey].sizes.length) { + nativeAssets.push({ + id: assetId, + required: required, + image: { + type: 3, + w: nativeImp[nativeKey].sizes[0], + h: nativeImp[nativeKey].sizes[1] + } + }) + } + } + }); + imp.native = { + request: JSON.stringify({native: {assets: nativeAssets}}) + }; + } + } else if (utils.deepAccess(bidRequest, 'mediaTypes.video')) { + let video = bidRequest.mediaTypes.video; + let sizes = video.playerSize || bidRequest.sizes || []; + if (sizes.length && Array.isArray(sizes[0])) { + imp.video = { + w: sizes[0][0], + h: sizes[0][1] + }; + } else if (sizes.length == 2 && !Array.isArray(sizes[0])) { + imp.video = { + w: sizes[0], + h: sizes[1] + }; + } else { + return; + } + + if (video.durationRangeSec) { + if (Array.isArray(video.durationRangeSec)) { + if (video.durationRangeSec.length == 1) { + imp.video.maxduration = video.durationRangeSec[0]; + } else if (video.durationRangeSec.length == 2) { + imp.video.minduration = video.durationRangeSec[0]; + imp.video.maxduration = video.durationRangeSec[1]; + } + } else { + imp.video.maxduration = video.durationRangeSec; + } + } + + if (bidRequest.params.video) { + Object.keys(bidRequest.params.video).forEach(k => { + if (VIDEO_PARAMS.indexOf(k) > -1) { + imp.video[k] = bidRequest.params.video[k]; + } + }) + } + } + let host = bidRequest.params.host; + let sourceId = bidRequest.params.sourceId; + imps[host] = imps[host] || {}; + let hostImp = imps[host][sourceId] = imps[host][sourceId] || { imps: [] }; + hostImp.imps.push(imp); + hostImp.subid = hostImp.imps.subid || bidRequest.params.subid || 'blank'; + hostImp.path = 'search'; + hostImp.idParam = 'sid'; + hostImp.protocol = '//'; + }); + + // Generate Site obj + site.domain = refurl.hostname; + site.page = refurl.protocol + '://' + refurl.hostname + refurl.pathname; + if (self === top && document.referrer) { + site.ref = document.referrer; + } + let keywords = document.getElementsByTagName('meta')['keywords']; + if (keywords && keywords.content) { + site.keywords = keywords.content; + } + + // Generate Device obj. + device.ip = 'peer'; + device.ua = window.navigator.userAgent; + device.js = 1; + device.language = ((navigator.language || navigator.userLanguage || '').split('-'))[0] || 'en'; + + RtbRequest(device, site, imps).forEach(formatted => { + requests.push({ + method: 'POST', + url: formatted.url, + data: formatted.body, + options: { + withCredentials: false + } + }) + }); + return requests; + + function RtbRequest(device, site, imps) { + let collection = []; + Object.keys(imps).forEach(host => { + let sourceIds = imps[host]; + Object.keys(sourceIds).forEach(sourceId => { + let impObj = sourceIds[sourceId]; + collection.push({ + url: `${impObj.protocol}${host}/${impObj.path}/?${impObj.idParam}=${sourceId}`, + body: { + id: bidderRequest.auctionId, + imp: impObj.imps, + site: Object.assign({ id: impObj.subid || 'blank' }, site), + device: Object.assign({}, device) + } + }) + }) + }) + + return collection; + } + }, + interpretResponse: function(serverResponse, bidRequest) { + if (!serverResponse || !serverResponse.body || !serverResponse.body.seatbid) { + return []; + } + let body = serverResponse.body; + + let bids = body.seatbid + .map(seatbid => seatbid.bid) + .reduce((memo, bid) => memo.concat(bid), []); + let req = bidRequest.data; + let reqImps = req.imp; + + return bids.map(rtbBid => { + let imp; + for (let i in reqImps) { + let testImp = reqImps[i] + if (testImp.id == rtbBid.impid) { + imp = testImp; + break; + } + } + let br = { + requestId: rtbBid.impid, + cpm: rtbBid.price, + creativeId: rtbBid.crid, + currency: rtbBid.currency || 'USD', + netRevenue: true, + ttl: 360 + }; + if (!imp) { + return br; + } else if (imp.banner) { + br.mediaType = BANNER; + br.width = rtbBid.w; + br.height = rtbBid.h; + br.ad = rtbBid.adm; + } else if (imp.native) { + br.mediaType = NATIVE; + + let reverseNativeMap = {}; + let nativeKeys = Object.keys(NATIVE_MAP); + nativeKeys.forEach(k => { + reverseNativeMap[NATIVE_MAP[k]] = k; + }); + + let idMap = {}; + let nativeReq = JSON.parse(imp.native.request); + if (nativeReq.native && nativeReq.native.assets) { + nativeReq.native.assets.forEach(asset => { + if (asset.data) { idMap[asset.id] = reverseNativeMap[asset.data.type]; } + }) + } + + const nativeResponse = JSON.parse(rtbBid.adm); + const { assets, link, imptrackers, jstrackers } = nativeResponse.native; + const result = { + clickUrl: link.url, + clickTrackers: link.clicktrackers || undefined, + impressionTrackers: imptrackers || undefined, + javascriptTrackers: jstrackers ? [jstrackers] : undefined + }; + assets.forEach(asset => { + if (asset.title) { + result.title = asset.title.text; + } else if (asset.img) { + result.image = asset.img.url; + } else if (idMap[asset.id]) { + result[idMap[asset.id]] = asset.data.value; + } + }) + br.native = result; + } else if (imp.video) { + br.mediaType = VIDEO; + br.width = rtbBid.w; + br.height = rtbBid.h; + if (rtbBid.adm) { br.vastXml = rtbBid.adm; } else if (rtbBid.nurl) { br.vastUrl = rtbBid.nurl; } + } + return br; + }); + } + +}; +registerBidder(spec); diff --git a/modules/datablocksBidAdapter.md b/modules/datablocksBidAdapter.md new file mode 100644 index 00000000000..e30cd361974 --- /dev/null +++ b/modules/datablocksBidAdapter.md @@ -0,0 +1,73 @@ +# Overview + +``` +Module Name: Datablocks Bidder Adapter +Module Type: Bidder Adapter +Maintainer: support@datablocks.net +``` + +# Description + +Connects to Datablocks Version 5 Platform +Banner Native and Video + + +# Test Parameters +``` + var adUnits = [ + { + code: 'banner-div', + sizes: [[300, 250]], + mediaTypes:{ + banner: { + sizes: [300,250] + } + }, + bids: [ + { + bidder: 'datablocks', + params: { + sourceId: 12345, + host: 'prebid.datablocks.net' + } + } + ] + }, { + code: 'native-div', + mediaTypes : { + native: { + title:{required:true}, + body:{required:true} + } + }, + bids: [ + { + bidder: 'datablocks', + params: { + sourceId: 12345, + host: 'prebid.datablocks.net' + } + }, { + code: 'video-div', + mediaTypes : { + video: { + playerSize:[500,400], + durationRangeSec:[15,30], + context: "linear" + } + }, + bids: [ + { + bidder: 'datablocks', + params: { + sourceId: 12345, + host: 'prebid.datablocks.net', + video: { + mimes:["video/flv"] + } + } + } + ] + } + ]; +``` \ No newline at end of file diff --git a/modules/decenteradsBidAdapter.js b/modules/decenteradsBidAdapter.js new file mode 100644 index 00000000000..65d3032d3f8 --- /dev/null +++ b/modules/decenteradsBidAdapter.js @@ -0,0 +1,90 @@ +import { registerBidder } from '../src/adapters/bidderFactory' +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes' +import * as utils from '../src/utils' + +const BIDDER_CODE = 'decenterads' +const URL = '//supply.decenterads.com/?c=o&m=multi' +const URL_SYNC = '//supply.decenterads.com/?c=o&m=cookie' + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + + isBidRequestValid: function (opts) { + return Boolean(opts.bidId && opts.params && !isNaN(opts.params.placementId)) + }, + + buildRequests: function (validBidRequests) { + validBidRequests = validBidRequests || [] + let winTop = window + try { + window.top.location.toString() + winTop = window.top + } catch (e) { utils.logMessage(e) } + + const location = utils.getTopWindowLocation() + const placements = [] + + for (let i = 0; i < validBidRequests.length; i++) { + const p = validBidRequests[i] + + placements.push({ + placementId: p.params.placementId, + bidId: p.bidId, + traffic: p.params.traffic || BANNER + }) + } + + return { + method: 'POST', + url: URL, + data: { + deviceWidth: winTop.screen.width, + deviceHeight: winTop.screen.height, + language: (navigator && navigator.language) ? navigator.language : '', + secure: +(location.protocol === 'https:'), + host: location.host, + page: location.pathname, + placements: placements + } + } + }, + + interpretResponse: function (opts) { + const body = opts.body + const response = [] + + for (let i = 0; i < body.length; i++) { + const item = body[i] + if (isBidResponseValid(item)) { + delete item.mediaType + response.push(item) + } + } + + return response + }, + + getUserSyncs: function (syncOptions, serverResponses) { + return [{ type: 'image', url: URL_SYNC }] + } +} + +registerBidder(spec) + +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.title && bid.image && bid.impressionTrackers) + default: + return false + } +} diff --git a/modules/decenteradsBidAdapter.md b/modules/decenteradsBidAdapter.md new file mode 100644 index 00000000000..04260a9da58 --- /dev/null +++ b/modules/decenteradsBidAdapter.md @@ -0,0 +1,27 @@ +# Overview + +``` +Module Name: DecenterAds Bidder Adapter +Module Type: Bidder Adapter +Maintainer: publishers@decenterads.com +``` + +# Description + +Module that connects to DecenterAds' demand sources + +# Test Parameters +``` + var adUnits = [{ + code: 'placementId_0', + sizes: [[300, 250]], + bids: [{ + bidder: 'decenterads', + params: { + placementId: 0, + traffic: 'banner' + } + }] + } + ]; +``` diff --git a/modules/dfpAdServerVideo.js b/modules/dfpAdServerVideo.js index 1b5f8509559..c3f867308d1 100644 --- a/modules/dfpAdServerVideo.js +++ b/modules/dfpAdServerVideo.js @@ -7,6 +7,8 @@ import { targeting } from '../src/targeting'; import { formatQS, format as buildUrl, parse } from '../src/url'; import { deepAccess, isEmpty, logError, parseSizesInput } from '../src/utils'; import { config } from '../src/config'; +import { getHook, submodule } from '../src/hook'; +import { auctionManager } from '../src/auctionManager'; /** * @typedef {Object} DfpVideoParams @@ -45,6 +47,8 @@ const defaultParamConstants = { unviewed_position_start: 1, }; +export const adpodUtils = {}; + /** * Merge all the bid data and publisher-supplied options into a single URL, and then return it. * @@ -56,9 +60,9 @@ const defaultParamConstants = { * (or the auction's winning bid for this adUnit, if undefined) compete alongside the rest of the * demand in DFP. */ -export default function buildDfpVideoUrl(options) { +export function buildDfpVideoUrl(options) { if (!options.params && !options.url) { - logError(`A params object or a url is required to use pbjs.adServers.dfp.buildVideoUrl`); + logError(`A params object or a url is required to use $$PREBID_GLOBAL$$.adServers.dfp.buildVideoUrl`); return; } @@ -97,12 +101,98 @@ export default function buildDfpVideoUrl(options) { return buildUrl({ protocol: 'https', - host: 'pubads.g.doubleclick.net', + host: 'securepubads.g.doubleclick.net', pathname: '/gampad/ads', search: queryParams }); } +export function notifyTranslationModule(fn) { + fn.call(this, 'dfp'); +} + +getHook('registerAdserver').before(notifyTranslationModule); + +/** + * @typedef {Object} DfpAdpodOptions + * + * @param {string} code Ad Unit code + * @param {Object} params Query params which should be set on the DFP request. + * These will override this module's defaults whenever they conflict. + * @param {function} callback Callback function to execute when master tag is ready + */ + +/** + * Creates master tag url for long-form + * @param {DfpAdpodOptions} options + * @returns {string} A URL which calls DFP with custom adpod targeting key values to compete with rest of the demand in DFP + */ +export function buildAdpodVideoUrl({code, params, callback} = {}) { + if (!params || !callback) { + logError(`A params object and a callback is required to use pbjs.adServers.dfp.buildAdpodVideoUrl`); + return; + } + + const derivedParams = { + correlator: Date.now(), + sz: getSizeForAdUnit(code), + url: encodeURIComponent(location.href), + }; + + function getSizeForAdUnit(code) { + let adUnit = auctionManager.getAdUnits() + .filter((adUnit) => adUnit.code === code) + let sizes = deepAccess(adUnit[0], 'mediaTypes.video.playerSize'); + return parseSizesInput(sizes).join('|'); + } + + adpodUtils.getTargeting({ + 'codes': [code], + 'callback': createMasterTag + }); + + function createMasterTag(err, targeting) { + if (err) { + callback(err, null); + return; + } + + let initialValue = { + [adpodUtils.TARGETING_KEY_PB_CAT_DUR]: undefined, + [adpodUtils.TARGETING_KEY_CACHE_ID]: undefined + } + let customParams = {}; + if (targeting[code]) { + customParams = targeting[code].reduce((acc, curValue) => { + if (Object.keys(curValue)[0] === adpodUtils.TARGETING_KEY_PB_CAT_DUR) { + acc[adpodUtils.TARGETING_KEY_PB_CAT_DUR] = (typeof acc[adpodUtils.TARGETING_KEY_PB_CAT_DUR] !== 'undefined') ? acc[adpodUtils.TARGETING_KEY_PB_CAT_DUR] + ',' + curValue[adpodUtils.TARGETING_KEY_PB_CAT_DUR] : curValue[adpodUtils.TARGETING_KEY_PB_CAT_DUR]; + } else if (Object.keys(curValue)[0] === adpodUtils.TARGETING_KEY_CACHE_ID) { + acc[adpodUtils.TARGETING_KEY_CACHE_ID] = curValue[adpodUtils.TARGETING_KEY_CACHE_ID] + } + return acc; + }, initialValue); + } + + let encodedCustomParams = encodeURIComponent(formatQS(customParams)); + + const queryParams = Object.assign({}, + defaultParamConstants, + derivedParams, + params, + { cust_params: encodedCustomParams } + ); + + const masterTag = buildUrl({ + protocol: 'https', + host: 'securepubads.g.doubleclick.net', + pathname: '/gampad/ads', + search: queryParams + }); + + callback(null, masterTag); + } +} + /** * Builds a video url from a base dfp video url and a winning bid, appending * Prebid-specific key-values. @@ -158,16 +248,21 @@ function getCustParams(bid, options) { const optCustParams = deepAccess(options, 'params.cust_params'); let customParams = Object.assign({}, - allTargetingData, - adserverTargeting, + // Why are we adding standard keys here ? Refer https://github.com/prebid/Prebid.js/issues/3664 { hb_uuid: bid && bid.videoCacheKey }, // hb_uuid will be deprecated and replaced by hb_cache_id { hb_cache_id: bid && bid.videoCacheKey }, + allTargetingData, + adserverTargeting, optCustParams, ); return encodeURIComponent(formatQS(customParams)); } registerVideoSupport('dfp', { - buildVideoUrl: buildDfpVideoUrl + buildVideoUrl: buildDfpVideoUrl, + buildAdpodVideoUrl: buildAdpodVideoUrl, + getAdpodTargeting: (args) => adpodUtils.getTargeting(args) }); + +submodule('adpod', adpodUtils); diff --git a/modules/dgadsBidAdapter.js b/modules/dgadsBidAdapter.js index 7d47cc7acf6..c8a97d86990 100644 --- a/modules/dgadsBidAdapter.js +++ b/modules/dgadsBidAdapter.js @@ -1,8 +1,9 @@ -import {registerBidder} from 'src/adapters/bidderFactory'; -import * as utils from 'src/utils'; -import { BANNER, NATIVE } from 'src/mediaTypes'; +import {registerBidder} from '../src/adapters/bidderFactory'; +import * as utils from '../src/utils'; +import { BANNER, NATIVE } from '../src/mediaTypes'; const BIDDER_CODE = 'dgads'; +const UID_NAME = 'dgads_uid'; const ENDPOINT = 'https://ads-tr.bigmining.com/ad/p/bid'; export const spec = { @@ -27,13 +28,15 @@ export const spec = { const params = bidRequest.params; const data = {}; - data['location_id'] = params.location_id; - data['site_id'] = params.site_id; + data['_loc'] = params.location_id; + data['_medium'] = params.site_id; data['transaction_id'] = bidRequest.transactionId; data['bid_id'] = bidRequest.bidId; + data['referer'] = utils.getTopWindowUrl(); + data['_uid'] = getCookieUid(UID_NAME); return { - method: 'POST', + method: 'GET', url: ENDPOINT, data, }; @@ -84,5 +87,17 @@ function setNativeResponse(ad) { nativeResponce.impressionTrackers = ad.impressionTrackers || []; return nativeResponce; } +export function getCookieUid(uidName) { + if (utils.cookiesAreEnabled()) { + let cookies = document.cookie.split(';'); + for (let i = 0; i < cookies.length; i++) { + let value = cookies[i].split('='); + if (value[0].indexOf(uidName) > -1) { + return value[1]; + } + } + } + return ''; +} registerBidder(spec); diff --git a/modules/digiTrustIdSystem.js b/modules/digiTrustIdSystem.js new file mode 100644 index 00000000000..89557e0917e --- /dev/null +++ b/modules/digiTrustIdSystem.js @@ -0,0 +1,365 @@ +/** + * This module adds DigiTrust ID support to the User ID module + * The {@link module:modules/userId} module is required + * If the full DigiTrust Id library is included the standard functions + * will be invoked to obtain the user's DigiTrust Id. + * When the full library is not included this will fall back to the + * DigiTrust Identity API and generate a mock DigiTrust object. + * @module modules/digiTrustIdSystem + * @requires module:modules/userId + */ + +import * as utils from '../src/utils' +import { ajax } from '../src/ajax'; +import { submodule } from '../src/hook'; + +var fallbackTimeout = 1550; // timeout value that allows userId system to execute first +var fallbackTimer = 0; // timer Id for fallback init so we don't double call + +/** + * Checks to see if the DigiTrust framework is initialized. + * @function + */ +function isInitialized() { + if (window.DigiTrust == null) { + return false; + } + return DigiTrust.isClient; // this is set to true after init +} + +/** + * Tests for presence of the DigiTrust object + * */ +function isPresent() { + return (window.DigiTrust != null); +} + +var noop = function () { +}; + +const MAX_RETRIES = 2; +const DT_ID_SVC = 'https://prebid.digitru.st/id/v1'; + +var isFunc = function (fn) { + return typeof (fn) === 'function'; +} + +function callApi(options) { + ajax( + DT_ID_SVC, + { + success: options.success, + error: options.fail + }, + null, + { + method: 'GET' + } + ); +} + +/** + * Encode the Id per DigiTrust lib + * @param {any} id + */ +function encId(id) { + try { + if (typeof (id) !== 'string') { + id = JSON.stringify(id); + } + return encodeURIComponent(btoa(id)); + } catch (ex) { + return id; + } +} + +/** + * Writes the Identity into the expected DigiTrust cookie + * @param {any} id + */ +function writeDigiId(id) { + var key = 'DigiTrust.v1.identity'; + var date = new Date(); + date.setTime(date.getTime() + 604800000); + var exp = 'expires=' + date.toUTCString(); + document.cookie = key + '=' + encId(id) + '; ' + exp + '; path=/;SameSite=none;'; +} + +/** + * Set up a DigiTrust facade object to mimic the API + * + */ +function initDigitrustFacade(config) { + var _savedId = null; // closure variable for storing Id to avoid additional requests + + clearTimeout(fallbackTimer); + fallbackTimer = 0; + + var facade = { + isClient: true, + isMock: true, + _internals: { + callCount: 0, + initCallback: null + }, + getUser: function (obj, callback) { + var isAsync = !!isFunc(callback); + var cb = isAsync ? callback : noop; + var errResp = { success: false }; + var inter = facade._internals; + inter.callCount++; + + // wrap the initializer callback, if present + var checkCallInitializeCb = function (idResponse) { + if (inter.callCount <= 1 && isFunc(inter.initCallback)) { + try { + inter.initCallback(idResponse); + } catch (ex) { + utils.logError('Exception in passed DigiTrust init callback'); + } + } + } + + if (!isMemberIdValid) { + if (!isAsync) { + return errResp + } else { + cb(errResp); + return; + } + } + + if (_savedId != null) { + checkCallInitializeCb(_savedId); + if (isAsync) { + cb(_savedId); + return; + } else { + return _savedId; + } + } + + var opts = { + success: function (respText, result) { + var idResult = { + success: true + } + try { + writeDigiId(respText); + idResult.identity = JSON.parse(respText); + _savedId = idResult; + } catch (ex) { + idResult.success = false; + } + checkCallInitializeCb(idResult); + cb(idResult); + }, + fail: function (statusErr, result) { + utils.logError('DigiTrustId API error: ' + statusErr); + } + } + + callApi(opts); + + if (!isAsync) { + return errResp; // even if it will be successful later, without a callback we report a "failure in this moment" + } + } + } + + if (config && isFunc(config.callback)) { + facade._internals.initCallback = config.callback; + } + + if (window && window.DigiTrust == null) { + window.DigiTrust = facade; + } +} + +/** + * Tests to see if a member ID is valid within facade + * @param {any} memberId + */ +var isMemberIdValid = function (memberId) { + if (memberId && memberId.length > 0) { + return true; + } else { + utils.logError('[DigiTrust Prebid Client Error] Missing member ID, add the member ID to the function call options'); + return false; + } +}; + +/** + * Encapsulation of needed info for the callback return. + * + * @param {any} opts + */ +var ResultWrapper = function (opts) { + var me = this; + this.idObj = null; + + var idSystemFn = null; + + /** + * Callback method that is passed back to the userId module. + * + * @param {function} callback + */ + this.userIdCallback = function (callback) { + idSystemFn = callback; + if (me.idObj != null && isFunc(callback)) { + callback(wrapIdResult()); + } + } + + /** + * Return a wrapped result formatted for userId system + */ + function wrapIdResult() { + if (me.idObj == null) { + return null; + } + + var cp = me.configParams; + var exp = (cp && cp.storage && cp.storage.expires) || 60; + + var rslt = { + data: null, + expires: exp + }; + if (me.idObj && me.idObj.success && me.idObj.identity) { + rslt.data = me.idObj.identity; + } else { + rslt.err = 'Failure getting id'; + } + + return rslt; + } + + this.retries = 0; + this.retryId = 0; + + this.executeIdRequest = function (configParams) { + DigiTrust.getUser({ member: 'prebid' }, function (idResult) { + me.idObj = idResult; + var cb = function () { + if (isFunc(idSystemFn)) { + idSystemFn(wrapIdResult()); + } + } + + cb(); + if (configParams && configParams.callback && isFunc(configParams.callback)) { + try { + configParams.callback(idResult); + } catch (ex) { + utils.logError('Failure in DigiTrust executeIdRequest', ex); + } + } + }); + } +} + +// An instance of the result wrapper object. +var resultHandler = new ResultWrapper(); + +/* + * Internal implementation to get the Id and trigger callback + */ +function getDigiTrustId(configParams) { + if (resultHandler.configParams == null) { + resultHandler.configParams = configParams; + } + + // First see if we should initialize DigiTrust framework + if (isPresent() && !isInitialized()) { + initializeDigiTrust(configParams); + resultHandler.retryId = setTimeout(function () { + getDigiTrustId(configParams); + }, 100 * (1 + resultHandler.retries++)); + return resultHandler.userIdCallback; + } else if (!isInitialized()) { // Second see if we should build a facade object + if (resultHandler.retries >= MAX_RETRIES) { + initDigitrustFacade(configParams); // initialize a facade object that relies on the AJAX call + resultHandler.executeIdRequest(configParams); + } else { + // use expanding envelope + if (resultHandler.retryId != 0) { + clearTimeout(resultHandler.retryId); + } + resultHandler.retryId = setTimeout(function () { + getDigiTrustId(configParams); + }, 100 * (1 + resultHandler.retries++)); + } + return resultHandler.userIdCallback; + } else { // Third get the ID + resultHandler.executeIdRequest(configParams); + return resultHandler.userIdCallback; + } +} + +function initializeDigiTrust(config) { + utils.logInfo('Digitrust Init'); + var dt = window.DigiTrust; + if (dt && !dt.isClient && config != null) { + dt.initialize(config.init, config.callback); + } else if (dt == null) { + // Assume we are already on a delay and DigiTrust is not on page + initDigitrustFacade(config); + } +} + +var testHook = {}; + +/** + * Exposes the test hook object by attaching to the digitrustIdModule. + * This method is called in the unit tests to surface internals. + */ +export function surfaceTestHook() { + digiTrustIdSubmodule['_testHook'] = testHook; + return testHook; +} + +testHook.initDigitrustFacade = initDigitrustFacade; // expose for unit tests + +/** @type {Submodule} */ +export const digiTrustIdSubmodule = { + /** + * used to link submodule with config + * @type {string} + */ + name: 'digitrust', + /** + * decode the stored id value for passing to bid requests + * @function + * @param {string} value + * @returns {{pubcid:string}} + */ + decode: function (idData) { + try { + return { 'digitrustid': idData }; + } catch (e) { + utils.logError('DigiTrust ID submodule decode error'); + } + }, + getId: function (configParams) { + return {callback: getDigiTrustId(configParams)}; + }, + _testInit: surfaceTestHook +}; + +// check for fallback init of DigiTrust +function fallbackInit() { + if (resultHandler.retryId == 0 && !isInitialized()) { + // this triggers an init + var conf = { + member: 'fallback', + callback: noop + }; + getDigiTrustId(conf); + } +} + +fallbackTimer = setTimeout(fallbackInit, fallbackTimeout); + +submodule('userId', digiTrustIdSubmodule); diff --git a/modules/digiTrustIdSystem.md b/modules/digiTrustIdSystem.md new file mode 100644 index 00000000000..c0b274d3292 --- /dev/null +++ b/modules/digiTrustIdSystem.md @@ -0,0 +1,156 @@ +## DigiTrust Universal Id Integration + +Setup +----- +The DigiTrust Id integration for Prebid may be used with or without the full +DigiTrust library. This is an optional module that must be used in conjunction +with the userId module. + +See the [Prebid Integration Guide for DigiTrust](https://github.com/digi-trust/dt-cdn/wiki/Prebid-Integration-for-DigiTrust-Id) +and the [DigiTrust wiki](https://github.com/digi-trust/dt-cdn/wiki) +for further instructions. + + +## Example Prebid Configuration for Digitrust Id +``` + pbjs.que.push(function() { + pbjs.setConfig({ + usersync: { + userIds: [{ + name: "digitrust", + params: { + init: { + member: 'example_member_id', + site: 'example_site_id' + }, + callback: function (digiTrustResult) { + // This callback method is optional and used for error handling + // in many if not most cases. + /* + if (digiTrustResult.success) { + // Success in Digitrust init; + // 'DigiTrust Id (encrypted): ' + digiTrustResult.identity.id; + } + else { + // Digitrust init failed + } + */ + } + }, + storage: { + type: "html5", + name: "pbjsdigitrust", + expires: 60 + } + }] + } + }); + pbjs.addAdUnits(adUnits); + pbjs.requestBids({ + bidsBackHandler: sendAdserverRequest + }); + }); + +``` + + +## Building Prebid with DigiTrust Support +Your Prebid build must include the modules for both **userId** and **digitrustIdLoader**. Follow the build instructions for Prebid as +explained in the top level README.md file of the Prebid source tree. + +ex: $ gulp build --modules=userId,digitrustIdLoader + +### Step by step Prebid build instructions for DigiTrust + +1. Download the Prebid source from [Prebid Git Repo](https://github.com/prebid/Prebid.js) +2. Set up your environment as outlined in the [Readme File](https://github.com/prebid/Prebid.js/blob/master/README.md#Build) +3. Execute the build command either with all modules or with the `userId` and `digitrustIdLoader` modules. + ``` + $ gulp build --modules=userId,digitrustIdLoader + ``` +4. (Optional) Concatenate the DigiTrust source code to the end of your `prebid.js` file for a single source distribution. +5. Upload the resulting source file to your CDN. + + +## Deploying Prebid with DigiTrust ID support +**Precondition:** You must be a DigiTrust member and have registered through the [DigiTrust Signup Process](http://www.digitru.st/signup/). +Your assigned publisher ID will be required in the configuration settings for all deployment scenarios. + +There are three supported approaches to deploying the Prebid-integrated DigiTrust package: + +* "Bare bones" deployment using only the integrated DigiTrust module code. +* Full DigiTrust with CDN referenced DigiTrust.js library. +* Full DigiTrust packaged with Prebid or site js. + +### Bare Bones Deployment + +This deployment results in the smallest Javascript package and is the simplest deployment. +It is appropriate for testing or deployments where simplicity is key. This approach +utilizes the REST API for ID generation. While there is less Javascript in use, +the user may experience more network requests than the scenarios that include the full +DigiTrust library. + +1. Build your Prebid package as above, skipping step 4. +2. Add the DigiTrust initializer section to your Prebid initialization object as below, + using your Member ID and Site ID. +3. Add a reference to your Prebid package and the initialization code on all pages you wish + to utilize Prebid with integrated DigiTrust ID. + + + + +### Full DigiTrust with CDN referenced DigiTrust library + +Both "Full DigiTrust" deployments will result in a larger initial Javascript payload. +The end user may experience fewer overall network requests as the encrypted and anonymous +DigiTrust ID can often be generated fully in client-side code. Utilizing the CDN reference +to the official DigiTrust distribution insures you will be running the latest version of the library. + +The Full DigiTrust deployment is designed to work with both new DigiTrust with Prebid deployments, and with +Prebid deployments by existing DigiTrust members. This allows you to migrate your code more slowly +without losing DigiTrust support in the process. + +1. Deploy your built copy of `prebid.js` to your CDN. +2. On each page reference both your `prebid.js` and a copy of the **DigiTrust** library. + This may either be a copy downloaded from the [DigiTrust CDN](https://cdn.digitru.st/prod/1/digitrust.min.js) to your CDN, + or directly referenced from the URL https://cdn.digitru.st/prod/1/digitrust.min.js. These may be added to the page in any order. +3. Add a configuration section for Prebid that includes the `usersync` settings and the `digitrust` settings. + +### Full DigiTrust packaged with Prebid + + +1. Deploy your built copy of `prebid.js` to your CDN. Be sure to perform *Step 4* of the build to concatenate or + integrate the full DigiTrust library code with your Prebid package. +2. On each page reference your `prebid.js` +3. Add a configuration section for Prebid that includes the `usersync` settings and the `digitrust` settings. + This code may also be appended to your Prebid package or placed in other initialization methods. + + + +## Parameter Descriptions for the `usersync` Configuration Section +The below parameters apply only to the DigiTrust ID integration. + +| Param under usersync.userIds[] | Scope | Type | Description | Example | +| --- | --- | --- | --- | --- | +| name | Required | String | ID value for the DigiTrust module - `"digitrust"` | `"digitrust"` | +| params | Required | Object | Details for DigiTrust initialization. | | +| params.init | Required | Object | Initialization parameters, including the DigiTrust Publisher ID and Site ID. | | +| params.init.member | Required | String | DigiTrust Publisher Id | "A897dTzB" | +| params.init.site | Required | String | DigiTrust Site Id | "MM2123" | +| params.callback | Optional | Function | Callback method to fire after initialization of the DigiTrust framework. The argument indicates failure and success and the identity object upon success. | | +| storage | Required | Object | The publisher must specify the local storage in which to store the results of the call to get the user ID. This can be either cookie or HTML5 storage. | | +| storage.type | Required | String | This is where the results of the user ID will be stored. The recommended method is `localStorage` by specifying `html5`. | `"html5"` | +| storage.name | Required | String | The name of the cookie or html5 local storage where the user ID will be stored. | `"pbjsdigitrust"` | +| storage.expires | Optional | Integer | How long (in days) the user ID information will be stored. Default is 30 for UnifiedId and 1825 for PubCommonID | `365` | +| value | Optional | Object | Used only if the page has a separate mechanism for storing the Unified ID. The value is an object containing the values to be sent to the adapters. In this scenario, no URL is called and nothing is added to local storage | `{"tdid": "D6885E90-2A7A-4E0F-87CB-7734ED1B99A3"}` | + + + +## Further Reading + ++ [DigiTrust Home Page](http://digitru.st) + ++ [DigiTrust integration guide](https://github.com/digi-trust/dt-cdn/wiki/Integration-Guide) + ++ [DigiTrust ID Encryption](https://github.com/digi-trust/dt-cdn/wiki/ID-encryption) + diff --git a/modules/districtmDMXBidAdapter.js b/modules/districtmDMXBidAdapter.js index 51ceedfc470..7a6f601aba1 100644 --- a/modules/districtmDMXBidAdapter.js +++ b/modules/districtmDMXBidAdapter.js @@ -1,6 +1,6 @@ -import * as utils from 'src/utils'; -import { registerBidder } from 'src/adapters/bidderFactory'; -import {config} from 'src/config'; +import * as utils from '../src/utils'; +import { registerBidder } from '../src/adapters/bidderFactory'; +import {config} from '../src/config'; const BIDDER_CODE = 'districtmDMX'; @@ -60,6 +60,7 @@ export const spec = { }, buildRequests(bidRequest, bidderRequest) { let timeout = config.getConfig('bidderTimeout'); + let schain = null; let dmxRequest = { id: utils.generateUUID(), cur: ['USD'], @@ -80,11 +81,17 @@ export const spec = { dmxRequest.user.ext = {}; dmxRequest.user.ext.consent = bidderRequest.gdprConsent.consentString; } + try { + schain = bidRequest[0].schain; + dmxRequest.source = {}; + dmxRequest.source.ext = {}; + dmxRequest.source.ext.schain = schain || {} + } catch (e) {} let tosendtags = bidRequest.map(dmx => { var obj = {}; obj.id = dmx.bidId; obj.tagid = String(dmx.params.dmxid); - obj.secure = window.location.protocol === 'https:' ? 1 : 0; + obj.secure = window.originalLocation.protocol === 'https:' ? 1 : 0; obj.banner = { topframe: 1, w: dmx.sizes[0][0] || 0, @@ -96,19 +103,16 @@ export const spec = { return obj; }); dmxRequest.imp = tosendtags; + return { method: 'POST', url: DMXURI, data: JSON.stringify(dmxRequest), - options: { - contentType: 'application/json', - withCredentials: true - }, bidderRequest } }, test() { - return window.location.href.indexOf('dmTest=true') !== -1 ? 1 : 0; + return window.originalLocation.href.indexOf('dmTest=true') !== -1 ? 1 : 0; }, getUserSyncs(optionsType) { if (optionsType.iframeEnabled) { diff --git a/modules/divreachBidAdapter.js b/modules/divreachBidAdapter.js deleted file mode 100644 index e597e32f4be..00000000000 --- a/modules/divreachBidAdapter.js +++ /dev/null @@ -1,74 +0,0 @@ -import * as utils from 'src/utils'; -import {registerBidder} from 'src/adapters/bidderFactory'; - -const BIDDER_CODE = 'divreach'; -const ENDPOINT_URL = '//ads.divreach.com/prebid.1.0.aspx'; -export const spec = { - code: BIDDER_CODE, - aliases: [], - 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: function (bid) { - return !!bid.params.zone; - }, - /** - * Make a server request from the list of BidRequests. - * - * @param {bidderRequest} - bidderRequest.bids[] is an array of AdUnits and bids - * @return ServerRequest Info describing the request to the server. - */ - buildRequests: function (bidderRequest) { - const payload = { - imps: [], - referrer: encodeURIComponent(utils.getTopWindowUrl()), - }; - bidderRequest.forEach((bid) => { - if (bid.bidder === BIDDER_CODE) { - payload.imps.push(bid); - } - }); - const payloadString = JSON.stringify(payload); - return { - method: 'GET', - url: ENDPOINT_URL, - data: `data=${payloadString}`, - }; - }, - /** - * Unpack the response from the server into a list of bids. - * - * @param {*} serverResponse A successful response from the server. - * @return {Bid[]} An array of bids which were nested inside the server. - */ - interpretResponse: function (serverResponse, bidRequest) { - const bidResponses = []; - // loop through serverResponses { - try { - serverResponse = serverResponse.body; - serverResponse.forEach((bidResponse) => { - const bidResp = { - requestId: bidResponse.bidId, - cpm: bidResponse.cpm, - width: bidResponse.width, - height: bidResponse.height, - ad: bidResponse.ad, - ttl: bidResponse.ttl, - creativeId: bidResponse.creativeId, - netRevenue: bidResponse.netRevenue, - currency: bidResponse.currency, - vastUrl: bidResponse.vastUrl, - }; - bidResponses.push(bidResp); - }); - } catch (e) { - utils.logError(e); - } - return bidResponses; - } -}; -registerBidder(spec); diff --git a/modules/divreachBidAdapter.md b/modules/divreachBidAdapter.md index da2ebee97cf..643845782b8 100644 --- a/modules/divreachBidAdapter.md +++ b/modules/divreachBidAdapter.md @@ -7,7 +7,6 @@ Maintainer: Zeke@divreach.com # Description Connects to DivReach demand source to fetch bids. -Banner and Video formats are supported. Please use ```divreach``` as the bidder code. # Test Parameters @@ -15,35 +14,14 @@ Please use ```divreach``` as the bidder code. var adUnits = [ { code: 'desktop-banner-ad-div', - sizes: [[300, 250]], // a display size + sizes: [[300, 250]], bids: [ { bidder: "divreach", params: { - zone: '261eae83-0508-4e1a-8c9b-19561fa9279e' - } - } - ] - },{ - code: 'mobile-banner-ad-div', - sizes: [[300, 50]], // a mobile size - bids: [ - { - bidder: "divreach", - params: { - zone: '561e26ea-1999-4fb6-ad0b-9d72929e545e' - } - } - ] - },{ - code: 'video-ad', - sizes: [[300, 50]], - mediaType: 'video', - bids: [ - { - bidder: "divreach", - params: { - zone: 'e784ecbe-720f-46f7-8388-aff8c2c4ed86' + accountID: '167283', + zoneID: '335105', + domain: 'ad.divreach.com', } } ] diff --git a/modules/djaxBidAdapter.js b/modules/djaxBidAdapter.js new file mode 100644 index 00000000000..58f500d2a2b --- /dev/null +++ b/modules/djaxBidAdapter.js @@ -0,0 +1,129 @@ +import { registerBidder } from '../src/adapters/bidderFactory'; +import { config } from '../src/config'; +import * as utils from '../src/utils'; +import {BANNER, VIDEO} from '../src/mediaTypes'; +import { ajax } from '../src/ajax'; +import {Renderer} from '../src/Renderer'; + +const SUPPORTED_AD_TYPES = [BANNER, VIDEO]; +const BIDDER_CODE = 'djax'; +const DOMAIN = 'https://demo.reviveadservermod.com/headerbidding_adminshare/'; +const RENDERER_URL = '//acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; + +function isBidRequestValid(bid) { + return (typeof bid.params !== 'undefined' && parseInt(utils.getValue(bid.params, 'publisherId')) > 0); +} + +function buildRequests(validBidRequests) { + return { + method: 'POST', + url: DOMAIN + 'www/admin/plugins/Prebid/getAd.php', + options: { + withCredentials: false, + crossOrigin: true + }, + data: validBidRequests, + }; +} + +function interpretResponse(serverResponse, request) { + const response = serverResponse.body; + const bidResponses = []; + var bidRequestResponses = []; + + utils._each(response, function(bidAd) { + bidAd.adResponse = { + content: bidAd.vastXml, + height: bidAd.height, + width: bidAd.width + }; + bidAd.ttl = config.getConfig('_bidderTimeout') + bidAd.renderer = bidAd.context === 'outstream' ? createRenderer(bidAd, { + id: bidAd.adUnitCode, + url: RENDERER_URL + }, bidAd.adUnitCode) : undefined; + bidResponses.push(bidAd); + }); + + bidRequestResponses.push({ + function: 'saveResponses', + request: request, + response: bidResponses + }); + sendResponseToServer(bidRequestResponses); + return bidResponses; +} + +function outstreamRender(bidAd) { + bidAd.renderer.push(() => { + window.ANOutstreamVideo.renderAd({ + sizes: [bidAd.width, bidAd.height], + width: bidAd.width, + height: bidAd.height, + targetId: bidAd.adUnitCode, + adResponse: bidAd.adResponse, + rendererOptions: { + showVolume: false, + allowFullscreen: false + } + }); + }); +} + +function createRenderer(bidAd, rendererParams, adUnitCode) { + const renderer = Renderer.install({ + id: rendererParams.id, + url: rendererParams.url, + loaded: false, + config: {'player_height': bidAd.height, 'player_width': bidAd.width}, + adUnitCode + }); + try { + renderer.setRender(outstreamRender); + } catch (err) { + utils.logWarn('Prebid Error calling setRender on renderer', err); + } + return renderer; +} + +function onBidWon(bid) { + let wonBids = []; + wonBids.push(bid); + wonBids[0].function = 'onBidWon'; + sendResponseToServer(wonBids); +} + +function onTimeout(details) { + details.unshift({ 'function': 'onTimeout' }); + sendResponseToServer(details); +} + +function sendResponseToServer(data) { + ajax(DOMAIN + 'www/admin/plugins/Prebid/tracking/track.php', null, JSON.stringify(data), { + withCredentials: false, + method: 'POST', + crossOrigin: true + }); +} + +function getUserSyncs(syncOptions) { + if (syncOptions.iframeEnabled) { + return [{ + type: 'iframe', + url: DOMAIN + 'www/admin/plugins/Prebid/userSync.php' + }]; + } +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: SUPPORTED_AD_TYPES, + isBidRequestValid, + buildRequests, + interpretResponse, + getUserSyncs, + onBidWon, + onTimeout +}; + +registerBidder(spec); diff --git a/modules/djaxBidAdapter.md b/modules/djaxBidAdapter.md new file mode 100644 index 00000000000..d597eb59b58 --- /dev/null +++ b/modules/djaxBidAdapter.md @@ -0,0 +1,50 @@ +# Overview + +``` +Module Name: djax Bid Adapter +Module Type: Bidder Adapter +Maintainer : support@djaxtech.com +``` + +# Description + +Connects to Djax Ad Server for bids. + +djax bid adapter supports Banner and Video. + +# Test Parameters +``` + var adUnits = [ + //bannner object + { + code: 'banner-ad-slot', + mediaTypes: { + banner: { + sizes: [[300, 250], [300,600]], + } + }, + bids: [{ + bidder: 'djax', + params: { + publisherId: 2 + } + }] + + }, + //video object + { + code: 'video-ad-slot', + mediaTypes: { + video: { + context: 'instream', + playerSize: [640, 480], + }, + }, + bids: [{ + bidder: "djax", + params: { + publisherId: 2 + } + }] + }]; +``` \ No newline at end of file diff --git a/modules/dspxBidAdapter.js b/modules/dspxBidAdapter.js new file mode 100644 index 00000000000..8b763202b7c --- /dev/null +++ b/modules/dspxBidAdapter.js @@ -0,0 +1,96 @@ +import * as utils from '../src/utils'; +import {config} from '../src/config'; +import {registerBidder} from '../src/adapters/bidderFactory'; + +const BIDDER_CODE = 'dspx'; +const ENDPOINT_URL = 'https://buyer.dspx.tv/request/'; + +export const spec = { + code: BIDDER_CODE, + aliases: ['dspx'], + isBidRequestValid: function(bid) { + return !!(bid.params.placement); + }, + buildRequests: function(validBidRequests, bidderRequest) { + return validBidRequests.map(bidRequest => { + const params = bidRequest.params; + const sizes = utils.parseSizesInput(bidRequest.sizes)[0]; + const width = sizes.split('x')[0]; + const height = sizes.split('x')[1]; + const placementId = params.placement; + + const rnd = Math.floor(Math.random() * 99999999999); + const referrer = encodeURIComponent(bidderRequest.refererInfo.referer); + const bidId = bidRequest.bidId; + const payload = { + _f: 'html', + alternative: 'prebid_js', + inventory_item_id: placementId, + srw: width, + srh: height, + idt: 100, + rnd: rnd, + ref: referrer, + bid_id: bidId, + }; + if (params.pfilter !== undefined) { + payload.pfilter = params.pfilter; + } + if (params.bcat !== undefined) { + payload.bcat = params.bcat; + } + if (params.dvt !== undefined) { + payload.dvt = params.dvt; + } + return { + method: 'GET', + url: ENDPOINT_URL, + data: objectToQueryString(payload), + } + }); + }, + interpretResponse: function(serverResponse, bidRequest) { + const bidResponses = []; + const response = serverResponse.body; + const crid = response.crid || 0; + const cpm = response.cpm / 1000000 || 0; + if (cpm !== 0 && crid !== 0) { + const dealId = response.dealid || ''; + const currency = response.currency || 'EUR'; + const netRevenue = (response.netRevenue === undefined) ? true : response.netRevenue; + const referrer = utils.getTopWindowUrl(); + const bidResponse = { + requestId: response.bid_id, + cpm: cpm, + width: response.width, + height: response.height, + creativeId: crid, + dealId: dealId, + currency: currency, + netRevenue: netRevenue, + ttl: config.getConfig('_bidderTimeout'), + referrer: referrer, + ad: response.adTag + }; + bidResponses.push(bidResponse); + } + return bidResponses; + } +} + +function objectToQueryString(obj, prefix) { + let str = []; + let p; + for (p in obj) { + if (obj.hasOwnProperty(p)) { + let k = prefix ? prefix + '[' + p + ']' : p; + let v = obj[p]; + str.push((v !== null && typeof v === 'object') + ? objectToQueryString(v, k) + : encodeURIComponent(k) + '=' + encodeURIComponent(v)); + } + } + return str.join('&'); +} + +registerBidder(spec); diff --git a/modules/dspxBidAdapter.md b/modules/dspxBidAdapter.md new file mode 100644 index 00000000000..362f4fbcb69 --- /dev/null +++ b/modules/dspxBidAdapter.md @@ -0,0 +1,73 @@ +# Overview + +``` +Module Name: Dspx Bidder Adapter +Module Type: Bidder Adapter +Maintainer: prebid@dspx.tv +``` + +# Description + +Dspx adapter for Prebid.js 1.0 + +# Test Parameters +``` + var adUnits = [ + { + code: 'test-div', + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 600], + ], // a display size + } + }, + bids: [ + { + bidder: "dspx", + params: { + placement: '101', + pfilter: { + floorprice: 1000000, // EUR * 1,000,000 + private_auction: 1, // Is private auction? 0 - no, 1 - yes + deals: [ + "666-9315-d58a7f9a-bdb9-4450-a3a2-046ba8ab2489;3;25000000;dspx-tv",// DEAL_ID;at;bidfloor;wseat1,wseat2;wadomain1,wadomain2" + "666-9315-d58a7f9a-bdb9-4450-a6a2-046ba8ab2489;3;25000000;dspx-tv",// DEAL_ID;at;bidfloor;wseat1,wseat2;wadomain1,wadomain2" + ], + geo: { // set client geo info manually (empty for auto detect) + lat: 52.52437, // Latitude from -90.0 to +90.0, where negative is south. + lon: 13.41053, // Longitude from -180.0 to +180.0, where negative is west + type: 1, // Source of location data: 1 - GPS/Location Services, 2 - IP Address, 3 - User provided (e.g. registration form) + country: 'DE', // Region of a country using FIPS 10-4 notation + region: 'DE-BE', // Region code using ISO-3166-2; 2-letter state code if USA. + regionfips104: 'GM', // Region of a country using FIPS 10-4 notation + city: 'BER', // City using United Nations Code for Trade and Transport Locations + zip: '10115' // Zip or postal code. + } + }, + bcat: "IAB2,IAB4", // List of Blocked Categories (IAB) - comma separated + dvt: "desktop|smartphone|tv|tablet" // DeVice Type (autodetect if not exists) + } + } + ] + },{ + code: 'test-div', + mediaTypes: { + banner: { + sizes: [[320, 50]], // a mobile size + } + }, + bids: [ + { + bidder: "dspx", + params: { + placement: 101 + } + } + ] + } + ]; +``` + +Required param field is only `placement`. \ No newline at end of file diff --git a/modules/ebdrBidAdapter.js b/modules/ebdrBidAdapter.js index f0f3d614a7d..1da1770e99d 100644 --- a/modules/ebdrBidAdapter.js +++ b/modules/ebdrBidAdapter.js @@ -1,6 +1,6 @@ -import * as utils from 'src/utils'; -import { VIDEO, BANNER } from 'src/mediaTypes'; -import { registerBidder } from 'src/adapters/bidderFactory'; +import * as utils from '../src/utils'; +import { VIDEO, BANNER } from '../src/mediaTypes'; +import { registerBidder } from '../src/adapters/bidderFactory'; const BIDDER_CODE = 'ebdr'; export const spec = { code: BIDDER_CODE, @@ -10,8 +10,8 @@ export const spec = { }, buildRequests: function(bids) { const rtbServerDomain = 'dsp.bnmla.com'; - let domain = window.location.host; - let page = window.location.pathname + location.search + location.hash; + let domain = window.originalLocation.host; + let page = window.originalLocation.pathname + location.search + location.hash; let ebdrImps = []; const ebdrReq = {}; let ebdrParams = {}; diff --git a/modules/emoteevBidAdapter.js b/modules/emoteevBidAdapter.js new file mode 100644 index 00000000000..db84b6ea36d --- /dev/null +++ b/modules/emoteevBidAdapter.js @@ -0,0 +1,523 @@ +/** + * This file contains Emoteev bid adpater. + * + * It is organised as follows: + * - Constants values; + * - Spec API functions, which should be pristine pure; + * - Ancillary functions, which should be as pure as possible; + * - Adapter API, where unpure side-effects happen. + * + * The code style is « functional core, imperative shell ». + * + * @link https://www.emoteev.io + * @file This files defines the spec of EmoteevBidAdapter. + * @author Emoteev Engineering . + */ + +import {registerBidder} from '../src/adapters/bidderFactory'; +import {BANNER} from '../src/mediaTypes'; +import { + triggerPixel, + getUniqueIdentifierStr, + contains, + deepAccess, + isArray, + isInteger, + getParameterByName, + getCookie +} from '../src/utils'; +import {config} from '../src/config'; +import * as url from '../src/url'; + +export const BIDDER_CODE = 'emoteev'; + +/** + * Version number of the adapter API. + */ +export const ADAPTER_VERSION = '1.35.0'; + +export const DOMAIN = 'prebid.emoteev.io'; +export const DOMAIN_STAGING = 'prebid-staging.emoteev.io'; +export const DOMAIN_DEVELOPMENT = 'localhost:3000'; + +/** + * Path of Emoteev endpoint for events. + */ +export const EVENTS_PATH = '/api/ad_event.json'; + +/** + * Path of Emoteev bidder. + */ +export const BIDDER_PATH = '/api/prebid/bid'; +export const USER_SYNC_IFRAME_PATH = '/api/prebid/sync-iframe'; +export const USER_SYNC_IMAGE_PATH = '/api/prebid/sync-image'; + +export const PRODUCTION = 'production'; +export const STAGING = 'staging'; +export const DEVELOPMENT = 'development'; +export const DEFAULT_ENV = PRODUCTION; + +export const ON_ADAPTER_CALLED = 'on_adapter_called'; +export const ON_BID_WON = 'on_bid_won'; +export const ON_BIDDER_TIMEOUT = 'on_bidder_timeout'; + +export const IN_CONTENT = 'content'; +export const FOOTER = 'footer'; +export const OVERLAY = 'overlay'; +export const WALLPAPER = 'wallpaper'; + +/** + * Vendor ID assigned to Emoteev from the Global Vendor & CMP List. + * + * See https://vendorlist.consensu.org/vendorinfo.json for more information. + * @type {number} + */ +export const VENDOR_ID = 15; + +/** + * Pure function. See http://prebid.org/dev-docs/bidder-adaptor.html#valid-build-requests-array for detailed semantic. + * + * @param {AdUnit.bidRequest} bidRequest + * @returns {boolean} Is this bidRequest valid? + */ +export const isBidRequestValid = (bidRequest) => { + return !!( + bidRequest && + bidRequest.params && + deepAccess(bidRequest, 'params.adSpaceId') && + validateContext(deepAccess(bidRequest, 'params.context')) && + validateExternalId(deepAccess(bidRequest, 'params.externalId')) && + bidRequest.bidder === BIDDER_CODE && + validateSizes(deepAccess(bidRequest, 'mediaTypes.banner.sizes'))); +}; + +/** + * Pure function. See http://prebid.org/dev-docs/bidder-adaptor.html#serverrequest-objects for detailed semantic. + * + * @param {string} env Emoteev environment parameter + * @param {boolean} debug Pbjs debug parameter. + * @param {string} currency See http://prebid.org/dev-docs/modules/currency.html for detailed semantic. + * @param {Array} validBidRequests Takes an array of bid requests, which are guaranteed to have passed the isBidRequestValid() test. + * @param bidderRequest General context for a bidder request being constructed + * @returns {ServerRequest} + */ +export const buildRequests = (env, debug, currency, validBidRequests, bidderRequest) => { + return { + method: 'POST', + url: bidderUrl(env), + data: JSON.stringify(requestsPayload(debug, currency, validBidRequests, bidderRequest)) // Keys with undefined values will be filtered out. + }; +}; + +/** + * Pure function. See http://prebid.org/dev-docs/bidder-adaptor.html#interpreting-the-response for detailed semantic. + * + * @param {Array} serverResponse.body The body of the server response is an array of bid objects. + * @returns {Array} + */ +export const interpretResponse = (serverResponse) => serverResponse.body; + +/** + * Pure function. See http://prebid.org/dev-docs/bidder-adaptor.html#registering-on-set-targeting for detailed semantic. + * + * @param {string} env Emoteev environment parameter. + * @param {BidRequest} bidRequest + * @returns {UrlObject} + */ +export function onAdapterCalled(env, bidRequest) { + return { + protocol: 'https', + hostname: domain(env), + pathname: EVENTS_PATH, + search: { + eventName: ON_ADAPTER_CALLED, + pubcId: deepAccess(bidRequest, 'crumbs.pubcid'), + bidId: bidRequest.bidId, + adSpaceId: deepAccess(bidRequest, 'params.adSpaceId'), + cache_buster: getUniqueIdentifierStr() + } + }; +} + +/** + * Pure function. See http://prebid.org/dev-docs/bidder-adaptor.html#registering-on-bid-won for detailed semantic. + * + * @param {string} env Emoteev environment parameter. + * @param {string} pubcId Publisher common id. See http://prebid.org/dev-docs/modules/pubCommonId.html for detailed semantic. + * @param bidObject + * @returns {UrlObject} + */ +export const onBidWon = (env, pubcId, bidObject) => { + const bidId = bidObject.requestId; + return { + protocol: 'https', + hostname: domain(env), + pathname: EVENTS_PATH, + search: { + eventName: ON_BID_WON, + pubcId, + bidId, + cache_buster: getUniqueIdentifierStr() + } + }; +}; + +/** + * Pure function. See http://prebid.org/dev-docs/bidder-adaptor.html#registering-on-timeout for detailed semantic. + * + * @param {string} env Emoteev environment parameter. + * @param {BidRequest} bidRequest + * @returns {UrlObject} + */ +export const onTimeout = (env, bidRequest) => { + return { + protocol: 'https', + hostname: domain(env), + pathname: EVENTS_PATH, + search: { + eventName: ON_BIDDER_TIMEOUT, + pubcId: deepAccess(bidRequest, 'crumbs.pubcid'), + bidId: bidRequest.bidId, + adSpaceId: deepAccess(bidRequest, 'params.adSpaceId'), + timeout: bidRequest.timeout, + cache_buster: getUniqueIdentifierStr() + } + } +}; + +/** + * Pure function. See http://prebid.org/dev-docs/bidder-adaptor.html#registering-user-syncs for detailed semantic. + * + * @param {string} env Emoteev environment parameter + * @param {SyncOptions} syncOptions + * @returns userSyncs + */ +export const getUserSyncs = (env, syncOptions) => { + let syncs = []; + if (syncOptions.pixelEnabled) { + syncs.push({ + type: 'image', + url: userSyncImageUrl(env), + }); + } + if (syncOptions.iframeEnabled) { + syncs.push({ + type: 'iframe', + url: userSyncIframeUrl(env), + }); + } + return syncs; +}; + +/** + * Pure function. + * + * @param {string} env Emoteev environment parameter + * @returns {string} The domain for network calls to Emoteev. + */ +export const domain = (env) => { + switch (env) { + case DEVELOPMENT: + return DOMAIN_DEVELOPMENT; + case STAGING: + return DOMAIN_STAGING; + default: + return DOMAIN; + } +}; + +/** + * Pure function. + * + * @param {string} env Emoteev environment parameter + * @returns {string} The full URL which events is sent to. + */ +export const eventsUrl = env => url.format({ + protocol: (env === DEVELOPMENT) ? 'http' : 'https', + hostname: domain(env), + pathname: EVENTS_PATH +}); + +/** + * Pure function. + * + * @param {string} env Emoteev environment parameter + * @returns {string} The full URL which bidderRequest is sent to. + */ +export const bidderUrl = env => url.format({ + protocol: (env === DEVELOPMENT) ? 'http' : 'https', + hostname: domain(env), + pathname: BIDDER_PATH +}); + +/** + * Pure function. + * + * @param {string} env Emoteev environment parameter + * @returns {string} The full URL called for iframe-based user sync + */ +export const userSyncIframeUrl = env => url.format({ + protocol: (env === DEVELOPMENT) ? 'http' : 'https', + hostname: domain(env), + pathname: USER_SYNC_IFRAME_PATH +}); + +/** + * Pure function. + * + * @param {string} env Emoteev environment parameter + * @returns {string} The full URL called for image-based user sync + */ +export const userSyncImageUrl = env => url.format({ + protocol: (env === DEVELOPMENT) ? 'http' : 'https', + hostname: domain(env), + pathname: USER_SYNC_IMAGE_PATH +}); + +/** + * Pure function. + * + * @param {Array>} sizes + * @returns {boolean} are sizes valid? + */ +export const validateSizes = sizes => isArray(sizes) && sizes.length > 0 && sizes.every(size => isArray(size) && size.length === 2); + +/** + * Pure function. + * + * @param {string} context + * @returns {boolean} is param `context` valid? + */ +export const validateContext = context => contains([IN_CONTENT, FOOTER, OVERLAY, WALLPAPER], context); + +/** + * Pure function. + * + * @param {(number|null|undefined)} externalId + * @returns {boolean} is param `externalId` valid? + */ +export const validateExternalId = externalId => externalId === undefined || externalId === null || (isInteger(externalId) && externalId > 0); + +/** + * Pure function. + * + * @param {BidRequest} bidRequest + * @returns {object} An object which represents a BidRequest for Emoteev server side. + */ +export const conformBidRequest = bidRequest => { + return { + params: bidRequest.params, + crumbs: bidRequest.crumbs, + sizes: bidRequest.sizes, + bidId: bidRequest.bidId, + bidderRequestId: bidRequest.bidderRequestId, + }; +}; + +/** + * Pure function. + * + * @param {object} bidderRequest + * @returns {(boolean|undefined)} raw consent data. + */ +export const gdprConsent = (bidderRequest) => (deepAccess(bidderRequest, 'gdprConsent.vendorData.vendorConsents') || {})[VENDOR_ID]; + +/** + * Pure function. + * + * @param {boolean} debug Pbjs debug parameter + * @param {string} currency See http://prebid.org/dev-docs/modules/currency.html for detailed information + * @param {BidRequest} validBidRequests + * @param {object} bidderRequest + * @returns + */ +export const requestsPayload = (debug, currency, validBidRequests, bidderRequest) => { + return { + akPbjsVersion: ADAPTER_VERSION, + bidRequests: validBidRequests.map(conformBidRequest), + currency: currency, + debug: debug, + language: navigator.language, + refererInfo: bidderRequest.refererInfo, + deviceInfo: getDeviceInfo( + getDeviceDimensions(window), + getViewDimensions(window, document), + getDocumentDimensions(document), + isWebGLEnabled(document)), + userAgent: navigator.userAgent, + gdprApplies: deepAccess(bidderRequest, 'gdprConsent.gdprApplies'), + gdprConsent: gdprConsent(bidderRequest), + }; +}; + +/** + * Pure function + * @param {Window} window + * @param {Document} document + * @returns {{width: number, height: number}} View dimensions + */ +export const getViewDimensions = (window, document) => { + let w = window; + let prefix = 'inner'; + + if (window.innerWidth === undefined || window.innerWidth === null) { + w = document.documentElement || document.body; + prefix = 'client'; + } + + return { + width: w[`${prefix}Width`], + height: w[`${prefix}Height`], + }; +}; + +/** + * Pure function + * @param {Window} window + * @returns {{width: number, height: number}} Device dimensions + */ +export const getDeviceDimensions = (window) => { + return { + width: window.screen ? window.screen.width : '', + height: window.screen ? window.screen.height : '', + }; +}; + +/** + * Pure function + * @param {Document} document + * @returns {{width: number, height: number}} Document dimensions + */ +export const getDocumentDimensions = (document) => { + const de = document.documentElement; + const be = document.body; + + const bodyHeight = be ? Math.max(be.offsetHeight, be.scrollHeight) : 0; + + const w = Math.max(de.clientWidth, de.offsetWidth, de.scrollWidth); + const h = Math.max( + de.clientHeight, + de.offsetHeight, + de.scrollHeight, + bodyHeight + ); + + return { + width: isNaN(w) ? '' : w, + height: isNaN(h) ? '' : h, + }; +}; + +/** + * Unpure function + * @param {Document} document + * @returns {boolean} Is WebGL enabled? + */ +export const isWebGLEnabled = (document) => { + // Create test canvas + let canvas = document.createElement('canvas'); + + // The gl context + let gl = null; + + // Try to get the regular WebGL + try { + gl = canvas.getContext('webgl'); + } catch (ex) { + canvas = undefined; + return false; + } + + // No regular WebGL found + if (!gl) { + // Try experimental WebGL + try { + gl = canvas.getContext('experimental-webgl'); + } catch (ex) { + canvas = undefined; + return false; + } + } + + return !!gl; +}; + +/** + * Pure function + * @param {{width: number, height: number}} deviceDimensions + * @param {{width: number, height: number}} viewDimensions + * @param {{width: number, height: number}} documentDimensions + * @param {boolean} webGL + * @returns {object} Device information + */ +export const getDeviceInfo = (deviceDimensions, viewDimensions, documentDimensions, webGL) => { + return { + browserWidth: viewDimensions.width, + browserHeight: viewDimensions.height, + deviceWidth: deviceDimensions.width, + deviceHeight: deviceDimensions.height, + documentWidth: documentDimensions.width, + documentHeight: documentDimensions.height, + webGL: webGL, + }; +}; + +/** + * Pure function + * @param {object} config pbjs config value + * @param {string} parameter Environment override from URL query param. + * @returns {string} One of [PRODUCTION, STAGING, DEVELOPMENT]. + */ +export const resolveEnv = (config, parameter) => { + const configEnv = deepAccess(config, 'emoteev.env'); + + if (contains([PRODUCTION, STAGING, DEVELOPMENT], parameter)) return parameter; + else if (contains([PRODUCTION, STAGING, DEVELOPMENT], configEnv)) return configEnv; + else return DEFAULT_ENV; +}; + +/** + * Pure function + * @param {object} config pbjs config value + * @param {string} parameter Debug override from URL query param. + * @returns {boolean} + */ +export const resolveDebug = (config, parameter) => { + if (parameter && parameter.length && parameter.length > 0) return JSON.parse(parameter); + else if (config.debug) return config.debug; + else return false; +}; + +/** + * EmoteevBidAdapter spec + * @access public + * @type {BidderSpec} + */ +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + isBidRequestValid: isBidRequestValid, + buildRequests: (validBidRequests, bidderRequest) => + buildRequests( + resolveEnv(config.getConfig(), getParameterByName('emoteevEnv')), + resolveDebug(config.getConfig(), getParameterByName('debug')), + config.getConfig('currency'), + validBidRequests, + bidderRequest), + interpretResponse: interpretResponse, + onBidWon: (bidObject) => + triggerPixel(url.format(onBidWon( + resolveEnv(config.getConfig(), getParameterByName('emoteevEnv')), + getCookie('_pubcid'), + bidObject))), + onTimeout: (bidRequest) => + triggerPixel(url.format(onTimeout( + resolveEnv(config.getConfig(), getParameterByName('emoteevEnv')), + bidRequest))), + getUserSyncs: (syncOptions) => + getUserSyncs( + resolveEnv(config.getConfig(), getParameterByName('emoteevEnv')), + syncOptions), +}; + +registerBidder(spec); diff --git a/modules/emoteevBidAdapter.md b/modules/emoteevBidAdapter.md new file mode 100644 index 00000000000..226a8374369 --- /dev/null +++ b/modules/emoteevBidAdapter.md @@ -0,0 +1,36 @@ +# Overview + +``` +Module Name: Emoteev Bidder Adapter +Module Type: Bidder Adapter +Maintainer: engineering@emoteev.io +``` + +# Description + +Module that connects to Emoteev's demand sources + +# Test Parameters + +``` javascript + var adUnits = [ + { + code: 'test-div', + mediaTypes: { + banner: { + sizes: [[720, 90]], + } + }, + bids: [ + { + bidder: 'emoteev', + params: { + adSpaceId: 5084, + context: 'footer', + externalId: 42, + } + } + ] + } + ]; +``` diff --git a/modules/emx_digitalBidAdapter.js b/modules/emx_digitalBidAdapter.js new file mode 100644 index 00000000000..7167f9018aa --- /dev/null +++ b/modules/emx_digitalBidAdapter.js @@ -0,0 +1,284 @@ +import * as utils from '../src/utils'; +import { registerBidder } from '../src/adapters/bidderFactory'; +import { BANNER, VIDEO } from '../src/mediaTypes'; +import { Renderer } from '../src/Renderer'; +import includes from 'core-js/library/fn/array/includes'; + +const BIDDER_CODE = 'emx_digital'; +const ENDPOINT = 'hb.emxdgt.com'; +const RENDERER_URL = '//js.brealtime.com/outstream/1.30.0/bundle.js'; +const ADAPTER_VERSION = '1.41.1'; +const DEFAULT_CUR = 'USD'; + +export const emxAdapter = { + validateSizes: (sizes) => { + if (!utils.isArray(sizes) || typeof sizes[0] === 'undefined') { + utils.logWarn(BIDDER_CODE + ': Sizes should be an array'); + return false; + } + return sizes.every(size => utils.isArray(size) && size.length === 2); + }, + checkVideoContext: (bid) => { + return ((bid && bid.mediaTypes && bid.mediaTypes.video && bid.mediaTypes.video.context) && ((bid.mediaTypes.video.context === 'instream') || (bid.mediaTypes.video.context === 'outstream'))); + }, + buildBanner: (bid) => { + let sizes = []; + bid.mediaTypes && bid.mediaTypes.banner && bid.mediaTypes.banner.sizes ? sizes = bid.mediaTypes.banner.sizes : sizes = bid.sizes; + if (!emxAdapter.validateSizes(sizes)) { + utils.logWarn(BIDDER_CODE + ': could not detect mediaType banner sizes. Assigning to bid sizes instead'); + sizes = bid.sizes + } + return { + format: sizes.map((size) => { + return { + w: size[0], + h: size[1] + }; + }), + w: sizes[0][0], + h: sizes[0][1] + }; + }, + formatVideoResponse: (bidResponse, emxBid, bidRequest) => { + bidResponse.vastXml = emxBid.adm; + if (bidRequest.bidRequest && bidRequest.bidRequest.mediaTypes && bidRequest.bidRequest.mediaTypes.video && bidRequest.bidRequest.mediaTypes.video.context === 'outstream') { + bidResponse.renderer = emxAdapter.createRenderer(bidResponse, { + id: emxBid.id, + url: RENDERER_URL + }); + } + return bidResponse; + }, + isMobile: () => { + return (/(ios|ipod|ipad|iphone|android)/i).test(navigator.userAgent); + }, + isConnectedTV: () => { + return (/(smart[-]?tv|hbbtv|appletv|googletv|hdmi|netcast\.tv|viera|nettv|roku|\bdtv\b|sonydtv|inettvbrowser|\btv\b)/i).test(navigator.userAgent); + }, + getDevice: () => { + return { + ua: navigator.userAgent, + js: 1, + dnt: (navigator.doNotTrack === 'yes' || navigator.doNotTrack === '1' || navigator.msDoNotTrack === '1') ? 1 : 0, + h: screen.height, + w: screen.width, + devicetype: emxAdapter.isMobile() ? 1 : emxAdapter.isConnectedTV() ? 3 : 2, + language: (navigator.language || navigator.browserLanguage || navigator.userLanguage || navigator.systemLanguage), + }; + }, + cleanProtocols: (video) => { + if (video.protocols && includes(video.protocols, 7)) { + // not supporting VAST protocol 7 (VAST 4.0); + utils.logWarn(BIDDER_CODE + ': VAST 4.0 is currently not supported. This protocol has been filtered out of the request.'); + video.protocols = video.protocols.filter(protocol => protocol !== 7); + } + return video; + }, + outstreamRender: (bid) => { + bid.renderer.push(function () { + let params = (bid && bid.params && bid.params[0] && bid.params[0].video) ? bid.params[0].video : {}; + window.emxVideoQueue = window.emxVideoQueue || []; + window.queueEmxVideo({ + id: bid.adUnitCode, + adsResponses: bid.vastXml, + options: params + }); + if (window.emxVideoReady && window.videojs) { + window.emxVideoReady(); + } + }); + }, + createRenderer: (bid, rendererParams) => { + const renderer = Renderer.install({ + id: rendererParams.id, + url: RENDERER_URL, + loaded: false + }); + try { + renderer.setRender(emxAdapter.outstreamRender); + } catch (err) { + utils.logWarn('Prebid Error calling setRender on renderer', err); + } + + return renderer; + }, + buildVideo: (bid) => { + let videoObj = Object.assign(bid.mediaTypes.video, bid.params.video); + + if (utils.isArray(bid.mediaTypes.video.playerSize[0])) { + videoObj['w'] = bid.mediaTypes.video.playerSize[0][0]; + videoObj['h'] = bid.mediaTypes.video.playerSize[0][1]; + } else { + videoObj['w'] = bid.mediaTypes.video.playerSize[0]; + videoObj['h'] = bid.mediaTypes.video.playerSize[1]; + } + return emxAdapter.cleanProtocols(videoObj); + }, + parseResponse: (bidResponseAdm) => { + try { + return decodeURIComponent(bidResponseAdm.replace(/%(?![0-9][0-9a-fA-F]+)/g, '%25')); + } catch (err) { + utils.logError('emx_digitalBidAdapter', 'error', err); + } + }, + getReferrer: () => { + try { + return window.top.document.referrer; + } catch (err) { + return document.referrer; + } + }, + getGdpr: (bidRequests, emxData) => { + if (bidRequests.gdprConsent) { + emxData.regs = { + ext: { + gdpr: bidRequests.gdprConsent.gdprApplies === true ? 1 : 0 + } + }; + } + if (bidRequests.gdprConsent && bidRequests.gdprConsent.gdprApplies) { + emxData.user = { + ext: { + consent: bidRequests.gdprConsent.consentString + } + }; + } + + return emxData; + } +}; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO], + isBidRequestValid: function (bid) { + if (!bid || !bid.params) { + utils.logWarn(BIDDER_CODE + ': Missing bid or bid params.'); + return false; + } + + if (bid.bidder !== BIDDER_CODE) { + utils.logWarn(BIDDER_CODE + ': Must use "emx_digital" as bidder code.'); + return false; + } + + if (!bid.params.tagid || !utils.isStr(bid.params.tagid)) { + utils.logWarn(BIDDER_CODE + ': Missing tagid param or tagid present and not type String.'); + return false; + } + + if (bid.mediaTypes && bid.mediaTypes.banner) { + let sizes; + bid.mediaTypes.banner.sizes ? sizes = bid.mediaTypes.banner.sizes : sizes = bid.sizes; + if (!emxAdapter.validateSizes(sizes)) { + utils.logWarn(BIDDER_CODE + ': Missing sizes in bid'); + return false; + } + } else if (bid.mediaTypes && bid.mediaTypes.video) { + if (!emxAdapter.checkVideoContext(bid)) { + utils.logWarn(BIDDER_CODE + ': Missing video context: instream or outstream'); + return false; + } + + if (!bid.mediaTypes.video.playerSize) { + utils.logWarn(BIDDER_CODE + ': Missing video playerSize'); + return false; + } + } + + return true; + }, + buildRequests: function (validBidRequests, bidRequest) { + const emxImps = []; + const timeout = bidRequest.timeout || ''; + const timestamp = Date.now(); + const url = location.protocol + '//' + ENDPOINT + ('?t=' + timeout + '&ts=' + timestamp + '&src=pbjs'); + const secure = location.protocol.indexOf('https') > -1 ? 1 : 0; + const domain = utils.getTopWindowLocation().hostname; + const page = bidRequest.refererInfo.referer; + const device = emxAdapter.getDevice(); + const ref = emxAdapter.getReferrer(); + + utils._each(validBidRequests, function (bid) { + let tagid = utils.getBidIdParameter('tagid', bid.params); + let bidfloor = parseFloat(utils.getBidIdParameter('bidfloor', bid.params)) || 0; + let isVideo = !!bid.mediaTypes.video; + let data = { + id: bid.bidId, + tid: bid.transactionId, + tagid, + secure + }; + let typeSpecifics = isVideo ? { video: emxAdapter.buildVideo(bid) } : { banner: emxAdapter.buildBanner(bid) }; + let bidfloorObj = bidfloor > 0 ? { bidfloor, bidfloorcur: DEFAULT_CUR } : {}; + let emxBid = Object.assign(data, typeSpecifics, bidfloorObj); + + emxImps.push(emxBid); + }); + + let emxData = { + id: bidRequest.auctionId, + imp: emxImps, + device, + site: { + domain, + page, + ref + }, + cur: DEFAULT_CUR, + version: ADAPTER_VERSION + }; + + emxData = emxAdapter.getGdpr(bidRequest, Object.assign({}, emxData)); + return { + method: 'POST', + url: url, + data: JSON.stringify(emxData), + options: { + withCredentials: true + }, + bidRequest + }; + }, + interpretResponse: function (serverResponse, bidRequest) { + let emxBidResponses = []; + let response = serverResponse.body || {}; + if (response.seatbid && response.seatbid.length > 0 && response.seatbid[0].bid) { + response.seatbid.forEach(function (emxBid) { + emxBid = emxBid.bid[0]; + let isVideo = false; + let adm = emxAdapter.parseResponse(emxBid.adm) || ''; + let bidResponse = { + requestId: emxBid.id, + cpm: emxBid.price, + width: emxBid.w, + height: emxBid.h, + creativeId: emxBid.crid || emxBid.id, + dealId: emxBid.dealid || null, + currency: 'USD', + netRevenue: true, + ttl: emxBid.ttl, + ad: adm + }; + if (emxBid.adm && emxBid.adm.indexOf(' -1) { + isVideo = true; + bidResponse = emxAdapter.formatVideoResponse(bidResponse, Object.assign({}, emxBid), bidRequest); + } + bidResponse.mediaType = (isVideo ? VIDEO : BANNER); + emxBidResponses.push(bidResponse); + }); + } + return emxBidResponses; + }, + getUserSyncs: function (syncOptions) { + const syncs = []; + if (syncOptions.iframeEnabled) { + syncs.push({ + type: 'iframe', + url: '//biddr.brealtime.com/check.html' + }); + } + return syncs; + } +}; +registerBidder(spec); diff --git a/modules/emx_digitalBidAdapter.md b/modules/emx_digitalBidAdapter.md new file mode 100644 index 00000000000..03ba554c5ad --- /dev/null +++ b/modules/emx_digitalBidAdapter.md @@ -0,0 +1,62 @@ +# Overview + +``` +Module Name: EMX Digital Adapter +Module Type: Bidder Adapter +Maintainer: git@emxdigital.com +``` + +# Description + +The EMX Digital adapter provides publishers with access to the EMX Marketplace. The adapter is GDPR compliant. Please note that the adapter supports Banner and Video (Instream & Outstream) media types. + +Note: The EMX Digital adapter requires approval and implementation guidelines from the EMX team, including existing publishers that work with EMX Digital. Please reach out to your account manager or prebid@emxdigital.com for more information. + +The bidder code should be ```emx_digital``` +The params used by the bidder are : +```tagid``` - string (mandatory) +```bidfloor``` - string (optional) + +# Test Parameters +``` +var adUnits = [{ + code: 'banner-div', + mediaTypes: { + banner: { + sizes: [ + [300, 250], [300, 600] + } + }, + bids: [ + { + bidder: 'emx_digital', + params: { + tagid: '25251', + } + }] +}]; +``` + +# Video Example +``` +var adUnits = [{ + code: 'video-div', + mediaTypes: { + video: { + context: 'instream', // also applicable for 'outstream' + playerSize: [640, 480] + } + }, + bids: [ + { + bidder: 'emx_digital', + params: { + tagid: '25251', + video: { + skippable: true, + playback_methods: ['auto_play_sound_off'] + } + } + }] +}]; +``` diff --git a/modules/eplanningAnalyticsAdapter.js b/modules/eplanningAnalyticsAdapter.js index 2fe26488ebe..21ecddfbc3a 100644 --- a/modules/eplanningAnalyticsAdapter.js +++ b/modules/eplanningAnalyticsAdapter.js @@ -1,9 +1,9 @@ -import {ajax} from 'src/ajax'; -import adapter from 'src/AnalyticsAdapter'; -import adaptermanager from 'src/adaptermanager'; -import * as utils from 'src/utils'; +import {ajax} from '../src/ajax'; +import adapter from '../src/AnalyticsAdapter'; +import adapterManager from '../src/adapterManager'; +import * as utils from '../src/utils'; -const CONSTANTS = require('src/constants.json'); +const CONSTANTS = require('../src/constants.json'); const analyticsType = 'endpoint'; const EPL_HOST = 'https://ads.us.e-planning.net/hba/1/'; @@ -123,7 +123,7 @@ eplAnalyticsAdapter.enableAnalytics = function (config) { eplAnalyticsAdapter.originEnableAnalytics(config); }; -adaptermanager.registerAnalyticsAdapter({ +adapterManager.registerAnalyticsAdapter({ adapter: eplAnalyticsAdapter, code: 'eplanning' }); diff --git a/modules/eplanningBidAdapter.js b/modules/eplanningBidAdapter.js index 6ead42d4b2d..01a956d1bd8 100644 --- a/modules/eplanningBidAdapter.js +++ b/modules/eplanningBidAdapter.js @@ -1,5 +1,5 @@ -import * as utils from 'src/utils'; -import { registerBidder } from 'src/adapters/bidderFactory'; +import * as utils from '../src/utils'; +import { registerBidder } from '../src/adapters/bidderFactory'; const BIDDER_CODE = 'eplanning'; const rnd = Math.random(); @@ -112,7 +112,7 @@ export const spec = { } function cleanName(name) { - return name.replace(/_|\.|-|\//g, '').replace(/\)\(|\(|\)/g, '_').replace(/^_+|_+$/g, ''); + return name.replace(/_|\.|-|\//g, '').replace(/\)\(|\(|\)|:/g, '_').replace(/^_+|_+$/g, ''); } function getUrlConfig(bidRequests) { if (isTestRequest(bidRequests)) { diff --git a/modules/etargetBidAdapter.js b/modules/etargetBidAdapter.js index 0804fa25e87..bdf07742497 100644 --- a/modules/etargetBidAdapter.js +++ b/modules/etargetBidAdapter.js @@ -1,7 +1,7 @@ 'use strict'; -import {registerBidder} from 'src/adapters/bidderFactory'; -import { BANNER, VIDEO } from 'src/mediaTypes'; +import {registerBidder} from '../src/adapters/bidderFactory'; +import { BANNER, VIDEO } from '../src/mediaTypes'; const BIDDER_CODE = 'etarget'; const countryMap = { @@ -28,18 +28,18 @@ export const spec = { var i, l, bid, reqParams, netRevenue, gdprObject; var request = []; var bids = JSON.parse(JSON.stringify(validBidRequests)); - var lastContry = 'sk'; + var lastCountry = 'sk'; for (i = 0, l = bids.length; i < l; i++) { bid = bids[i]; if (countryMap[bid.params.country]) { - lastContry = countryMap[bid.params.country]; + lastCountry = countryMap[bid.params.country]; } reqParams = bid.params; reqParams.transactionId = bid.transactionId; request.push(formRequestUrl(reqParams)); } - request.unshift('//' + lastContry + '.search.etargetnet.com/hb/?hbget=1'); + request.unshift('//' + lastCountry + '.search.etargetnet.com/hb/?hbget=1'); netRevenue = 'net'; if (bidderRequest && bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies) { diff --git a/modules/express.js b/modules/express.js index 8a5dc095476..1249822f587 100644 --- a/modules/express.js +++ b/modules/express.js @@ -1,5 +1,5 @@ -import * as utils from 'src/utils'; +import * as utils from '../src/utils'; const MODULE_NAME = 'express'; diff --git a/modules/eywamediaBidAdapter.js b/modules/eywamediaBidAdapter.js new file mode 100644 index 00000000000..543775dc3aa --- /dev/null +++ b/modules/eywamediaBidAdapter.js @@ -0,0 +1,181 @@ +import * as utils from '../src/utils'; +import { registerBidder } from '../src/adapters/bidderFactory'; + +const BIDDER_CODE = 'eywamedia'; +const CURRENCY = 'USD'; +const VERSION = '1.0.0'; +const TIME_TO_LIVE = 360; +const NET_REVENUE = true; +const COOKIE_NAME = 'emaduuid'; +const UUID_LEN = 36; +const SERVER_ENDPOINT = 'https://adtarbostg.eywamedia.com/auctions/prebidjs/3000'; +const localWindow = getTopWindow(); + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: ['banner'], + /** + * Determines whether or not the given bid request is valid. + * @param {object} bid, bid to validate + * @return boolean, true if valid, otherwise false + */ + isBidRequestValid: function(bid) { + return !!(bid.params.publisherId); + }, + /** + * Make a server request from the list of BidRequests. + * + * @param {BidRequest[]} bidRequests A non-empty list of bid requests which should be sent to the Server. + * @return requestPayload Info describing the request to the server. + */ + buildRequests: function(bidRequests, bidRequest) { + const device = getDeviceInfo(); + const site = getSiteInfo(); + const user = getUserInfo(); + + let requestPayload = { + id: utils.generateUUID(), + publisherId: bidRequests[0].params.publisherId, + device: device, + site: site, + user: user, + bidPayload: bidRequests, + cacheBust: new Date().getTime().toString(), + adapterVersion: VERSION, + tmax: bidRequest.timeout + }; + + return { + method: 'POST', + url: SERVER_ENDPOINT, + options: { + contentType: 'application/json' + }, + data: requestPayload + } + }, + + /** + * Makes Eywamedia Ad Server response compatible to Prebid specs + * @param serverResponse successful response from Ad Server + * @param bidderRequest original bidRequest + * @return {Bid[]} an array of bids + */ + interpretResponse: function (serverResponse, bidRequest) { + var bidObject, response; + var bidRespones = []; + var responses = serverResponse.body; + for (var i = 0; i < responses.length; i++) { + response = responses[i]; + bidObject = { + requestId: response.bidId, + cpm: response.cpm, + width: parseInt(response.width), + height: parseInt(response.height), + creativeId: response.bidId, + currency: CURRENCY, + netRevenue: NET_REVENUE, + ttl: TIME_TO_LIVE, + ad: response.ad, + bidderCode: BIDDER_CODE, + transactionId: response.transactionId, + mediaType: response.respType, + }; + bidRespones.push(bidObject); + } + return bidRespones; + } +} +registerBidder(spec); + +/*************************************** + * Helper Functions + ***************************************/ + +/** + * get device type + */ +function getDeviceType() { + let ua = navigator.userAgent; + // Tablets must be checked before phones. + if ((/(tablet|ipad|playbook|silk)|(android(?!.*mobi))/i).test(ua)) { + return 5; // "Tablet" + } + if ((/Mobile|iP(hone|od|ad)|Android|BlackBerry|IEMobile|Kindle|NetFront|Silk-Accelerated|(hpw|web)OS|Fennec|Minimo|Opera M(obi|ini)|Blazer|Dolfin|Dolphin|Skyfire|Zune/).test(ua)) { + return 4; // "Phone" + } + return 2; // Personal Computers +}; + +/** + * get device info + */ +function getDeviceInfo() { + const language = navigator.language; + return { + ua: navigator.userAgent, + language: navigator[language], + devicetype: getDeviceType(), + dnt: utils.getDNT(), + geo: {}, + js: 1 + }; +}; + +/** + * get site info + */ +function getSiteInfo() { + const topLocation = utils.getTopWindowLocation(); + return { + domain: topLocation.hostname, + page: topLocation.href, + referrer: utils.getTopWindowReferrer(), + desc: getPageDescription(), + title: localWindow.document.title, + }; +}; + +/** + * get user info + */ +function getUserInfo() { + return { + id: getUserID(), + }; +}; + +/** + * get user Id + */ +const getUserID = () => { + const i = document.cookie.indexOf(COOKIE_NAME); + + if (i === -1) { + const uuid = utils.generateUUID(); + document.cookie = `${COOKIE_NAME}=${uuid}; path=/`; + return uuid; + } + + const j = i + COOKIE_NAME.length + 1; + return document.cookie.substring(j, j + UUID_LEN); +}; + +/** + * get page description + */ +function getPageDescription() { + if (document.querySelector('meta[name="description"]')) { + return document.querySelector('meta[name="description"]').getAttribute('content'); // Value of the description metadata from the publisher's page. + } else { + return ''; + } +}; + +function getTopWindow() { + try { + return window.top; + } catch (e) { + return window; + } +}; diff --git a/modules/eywamediaBidAdapter.md b/modules/eywamediaBidAdapter.md new file mode 100644 index 00000000000..76b9b032c1b --- /dev/null +++ b/modules/eywamediaBidAdapter.md @@ -0,0 +1,37 @@ +# Overview + +``` +Module Name: Eywamedia Bid Adapter +Module Type: Bidder Adapter +Maintainer: sharath@eywamedia.com +Note: Our ads will only render in mobile and desktop +``` + +# Description + +Connects to Eywamedia Ad Server for bids. + +Eywamedia bid adapter supports Banners. + +# Test Parameters +``` +var adUnits = [ + // Banner adUnit + { + code: 'div-gpt-ad-1460505748561-0', + sizes: [[300, 250], [300,600]], + bids: [{ + bidder: 'eywamedia', + params: { + publisherId: 'f63a2362-5aa4-4829-bbd2-2678ced8b63e', //Required - GUID (may include numbers and characters) + bidFloor: 0.50, // optional + cats: ["iab1-1","iab23-2"], // optional + keywords: ["sports", "cricket"], // optional + lat: 12.33333, // optional + lon: 77.32322, // optional + locn: "country$region$city$zip" // optional + } + }] + } +]; +``` diff --git a/modules/fairtradeBidAdapter.js b/modules/fairtradeBidAdapter.js index dde64d839ca..55f24ab8906 100644 --- a/modules/fairtradeBidAdapter.js +++ b/modules/fairtradeBidAdapter.js @@ -1,5 +1,5 @@ -import * as utils from 'src/utils'; -import {registerBidder} from 'src/adapters/bidderFactory'; +import * as utils from '../src/utils'; +import {registerBidder} from '../src/adapters/bidderFactory'; const BIDDER_CODE = 'fairtrade'; const ENDPOINT_URL = '//pool.fair-trademedia.com/hb'; const TIME_TO_LIVE = 360; diff --git a/modules/fairtradeBidAdapter.md b/modules/fairtradeBidAdapter.md old mode 100755 new mode 100644 diff --git a/modules/feedadBidAdapter.js b/modules/feedadBidAdapter.js new file mode 100644 index 00000000000..1e995ee8914 --- /dev/null +++ b/modules/feedadBidAdapter.js @@ -0,0 +1,290 @@ +import * as utils from '../src/utils'; +import {registerBidder} from '../src/adapters/bidderFactory'; +import {BANNER, VIDEO} from '../src/mediaTypes'; +import {ajax} from '../src/ajax'; + +/** + * Version of the FeedAd bid adapter + * @type {string} + */ +const VERSION = '1.0.0'; + +/** + * @typedef {object} FeedAdApiBidRequest + * @inner + * + * @property {number} ad_type + * @property {string} client_token + * @property {string} placement_id + * @property {string} sdk_version + * @property {boolean} app_hybrid + * + * @property {string} [app_bundle_id] + * @property {string} [app_name] + * @property {object} [custom_params] + * @property {number} [connectivity] + * @property {string} [device_adid] + * @property {string} [device_platform] + */ + +/** + * @typedef {object} FeedAdApiBidResponse + * @inner + * + * @property {string} ad - Ad HTML payload + * @property {number} cpm - number / float + * @property {string} creativeId - ID of creative for tracking + * @property {string} currency - 3-letter ISO 4217 currency-code + * @property {number} height - Height of creative returned in [].ad + * @property {boolean} netRevenue - Is the CPM net (true) or gross (false)? + * @property {string} requestId - bids[].bidId + * @property {number} ttl - Time to live for this ad + * @property {number} width - Width of creative returned in [].ad + */ + +/** + * @typedef {object} FeedAdApiTrackingParams + * @inner + * + * @property app_hybrid {boolean} + * @property client_token {string} + * @property klass {'prebid_bidWon'|'prebid_bidTimeout'} + * @property placement_id {string} + * @property prebid_auction_id {string} + * @property prebid_bid_id {string} + * @property prebid_transaction_id {string} + * @property referer {string} + * @property sdk_version {string} + * @property [app_bundle_id] {string} + * @property [app_name] {string} + * @property [device_adid] {string} + * @property [device_platform] {1|2|3} 1 - Android | 2 - iOS | 3 - Windows + */ + +/** + * Bidder network identity code + * @type {string} + */ +const BIDDER_CODE = 'feedad'; + +/** + * The media types supported by FeedAd + * @type {MediaType[]} + */ +const MEDIA_TYPES = [VIDEO, BANNER]; + +/** + * Tag for logging + * @type {string} + */ +const TAG = '[FeedAd]'; + +/** + * Pattern for valid placement IDs + * @type {RegExp} + */ +const PLACEMENT_ID_PATTERN = /^[a-z0-9][a-z0-9_-]+[a-z0-9]$/; + +const API_ENDPOINT = 'https://api.feedad.com'; +const API_PATH_BID_REQUEST = '/1/prebid/web/bids'; +const API_PATH_TRACK_REQUEST = '/1/prebid/web/events'; + +/** + * Stores temporary auction metadata + * @type {Object.} + */ +const BID_METADATA = {}; + +/** + * Checks if the bid is compatible with FeedAd. + * + * @param {BidRequest} bid - the bid to check + * @return {boolean} true if the bid is valid + */ +function isBidRequestValid(bid) { + const clientToken = utils.deepAccess(bid, 'params.clientToken'); + if (!clientToken || !isValidClientToken(clientToken)) { + utils.logWarn(TAG, "missing or invalid parameter 'clientToken'. found value:", clientToken); + return false; + } + + const placementId = utils.deepAccess(bid, 'params.placementId'); + if (!placementId || !isValidPlacementId(placementId)) { + utils.logWarn(TAG, "missing or invalid parameter 'placementId'. found value:", placementId); + return false; + } + + return true; +} + +/** + * Checks if a client token is valid + * @param {string} clientToken - the client token + * @return {boolean} true if the token is valid + */ +function isValidClientToken(clientToken) { + return typeof clientToken === 'string' && clientToken.length > 0; +} + +/** + * Checks if the given placement id is of a correct format. + * Valid IDs are words of lowercase letters from a to z and numbers from 0 to 9. + * The words can be separated by hyphens or underscores. + * Multiple separators must not follow each other. + * The whole placement ID must not be larger than 256 characters. + * + * @param placementId - the placement id to verify + * @returns if the placement ID is valid. + */ +function isValidPlacementId(placementId) { + return typeof placementId === 'string' && + placementId.length > 0 && + placementId.length <= 256 && + PLACEMENT_ID_PATTERN.test(placementId); +} + +/** + * Checks if the given media types contain unsupported settings + * @param {MediaTypes} mediaTypes - the media types to check + * @return {MediaTypes} the unsupported settings, empty when all types are supported + */ +function filterSupportedMediaTypes(mediaTypes) { + return { + banner: mediaTypes.banner, + video: mediaTypes.video && mediaTypes.video.context === 'outstream' ? mediaTypes.video : undefined, + native: undefined + }; +} + +/** + * Checks if the given media types are empty + * @param {MediaTypes} mediaTypes - the types to check + * @return {boolean} true if the types are empty + */ +function isMediaTypesEmpty(mediaTypes) { + return Object.keys(mediaTypes).every(type => mediaTypes[type] === undefined); +} + +/** + * Creates the bid request params the api expects from the prebid bid request + * @param {BidRequest} request - the validated prebid bid request + * @return {FeedAdApiBidRequest} + */ +function createApiBidRParams(request) { + return { + ad_type: 0, + client_token: request.params.clientToken, + placement_id: request.params.placementId, + sdk_version: `prebid_${VERSION}`, + app_hybrid: false, + }; +} + +/** + * Builds the bid request to the FeedAd Server + * @param {BidRequest[]} validBidRequests - all validated bid requests + * @param {object} bidderRequest - meta information + * @return {ServerRequest|ServerRequest[]} + */ +function buildRequests(validBidRequests, bidderRequest) { + if (!bidderRequest) { + return []; + } + let acceptableRequests = validBidRequests.filter(request => !isMediaTypesEmpty(filterSupportedMediaTypes(request.mediaTypes))); + if (acceptableRequests.length === 0) { + return []; + } + let data = Object.assign({}, bidderRequest, { + bids: acceptableRequests.map(req => { + req.params = createApiBidRParams(req); + return req; + }) + }); + data.bids.forEach(bid => BID_METADATA[bid.bidId] = { + referer: data.refererInfo.referer, + transactionId: bid.transactionId + }); + return { + method: 'POST', + url: `${API_ENDPOINT}${API_PATH_BID_REQUEST}`, + data, + options: { + contentType: 'application/json' + } + }; +} + +/** + * Adapts the FeedAd server response to Prebid format + * @param {ServerResponse} serverResponse - the FeedAd server response + * @param {BidRequest} request - the initial bid request + * @returns {Bid[]} the FeedAd bids + */ +function interpretResponse(serverResponse, request) { + /** + * @type FeedAdApiBidResponse[] + */ + return typeof serverResponse.body === 'string' ? JSON.parse(serverResponse.body) : serverResponse.body; +} + +/** + * Creates the parameters for the FeedAd tracking call + * @param {object} data - prebid data + * @param {'prebid_bidWon'|'prebid_bidTimeout'} klass - type of tracking call + * @return {FeedAdApiTrackingParams|null} + */ +function createTrackingParams(data, klass) { + const bidId = data.bidId || data.requestId; + if (!BID_METADATA.hasOwnProperty(bidId)) { + return null; + } + const {referer, transactionId} = BID_METADATA[bidId]; + delete BID_METADATA[bidId]; + return { + app_hybrid: false, + client_token: data.params[0].clientToken, + placement_id: data.params[0].placementId, + klass, + prebid_auction_id: data.auctionId, + prebid_bid_id: bidId, + prebid_transaction_id: transactionId, + referer, + sdk_version: VERSION + }; +} + +/** + * Creates a tracking handler for the given event type + * @param klass - the event type + * @return {Function} the tracking handler function + */ +function trackingHandlerFactory(klass) { + return (data) => { + if (!data) { + return; + } + let params = createTrackingParams(data, klass); + if (params) { + ajax(`${API_ENDPOINT}${API_PATH_TRACK_REQUEST}`, null, JSON.stringify(params), { + withCredentials: true, + method: 'POST', + contentType: 'application/json' + }); + } + } +} + +/** + * @type {BidderSpec} + */ +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: MEDIA_TYPES, + isBidRequestValid, + buildRequests, + interpretResponse, + onTimeout: trackingHandlerFactory('prebid_bidTimeout'), + onBidWon: trackingHandlerFactory('prebid_bidWon') +}; + +registerBidder(spec); diff --git a/modules/feedadBidAdapter.md b/modules/feedadBidAdapter.md new file mode 100644 index 00000000000..fd57025c29e --- /dev/null +++ b/modules/feedadBidAdapter.md @@ -0,0 +1,44 @@ +# Overview + +``` +Module Name: FeedAd Adapter +Module Type: Bidder Adapter +Maintainer: mail@feedad.com +``` + +# Description + +Prebid.JS adapter that connects to the FeedAd demand sources. + +# Test Parameters +``` + var adUnits = [ + { + code: 'test-div', + mediaTypes: { + banner: { // supports all banner sizes + sizes: [[300, 250]], + }, + video: { // supports only outstream video + context: 'outstream' + } + }, + bids: [ + { + bidder: "feedad", + params: { + clientToken: 'your-client-token' // see below for more info + placementId: 'your-placement-id' // see below for more info + } + } + ] + } + ]; +``` + +# Required Parameters + +| Parameter | Description | +| --------- | ----------- | +| `clientToken` | Your FeedAd web client token. You can view your client token inside the FeedAd admin panel. | +| `placementId` | You can choose placement IDs yourself. A placement ID should be named after the ad position inside your product. For example, if you want to display an ad inside a list of news articles, you could name it "ad-news-overview".
    A placement ID may consist of lowercase `a-z`, `0-9`, `-` and `_`. You do not have to manually create the placement IDs before using them. Just specify them within the code, and they will appear in the FeedAd admin panel after the first request.
    [Learn more](/concept/feed_ad/index.html) about Placement IDs and how they are grouped to play the same Creative. | diff --git a/modules/fidelityBidAdapter.js b/modules/fidelityBidAdapter.js index 08a032bcba9..078e9d2fcce 100644 --- a/modules/fidelityBidAdapter.js +++ b/modules/fidelityBidAdapter.js @@ -1,5 +1,5 @@ -import * as utils from 'src/utils'; -import {registerBidder} from 'src/adapters/bidderFactory'; +import * as utils from '../src/utils'; +import {registerBidder} from '../src/adapters/bidderFactory'; const BIDDER_CODE = 'fidelity'; const BIDDER_SERVER = 'x.fidelity-media.com'; diff --git a/modules/fintezaAnalyticsAdapter.js b/modules/fintezaAnalyticsAdapter.js new file mode 100644 index 00000000000..a1376c28427 --- /dev/null +++ b/modules/fintezaAnalyticsAdapter.js @@ -0,0 +1,452 @@ +import { ajax } from '../src/ajax'; +import adapter from '../src/AnalyticsAdapter'; +import adapterManager from '../src/adapterManager'; +import * as utils from '../src/utils'; +import { parse as parseURL } from '../src/url'; + +const CONSTANTS = require('../src/constants.json'); + +const ANALYTICS_TYPE = 'endpoint'; +const FINTEZA_HOST = 'https://content.mql5.com/tr'; +const BID_REQUEST_TRACK = 'Bid Request %BIDDER%'; +const BID_RESPONSE_PRICE_TRACK = 'Bid Response Price %BIDDER%'; +const BID_RESPONSE_TIME_TRACK = 'Bid Response Time %BIDDER%'; +const BID_TIMEOUT_TRACK = 'Bid Timeout %BIDDER%'; +const BID_WON_TRACK = 'Bid Won %BIDDER%'; + +const FIRST_VISIT_DATE = '_fz_fvdt'; +const SESSION_ID = '_fz_ssn'; +const SESSION_DURATION = 30 * 60 * 1000; +const SESSION_RAND_PART = 9; +const TRACK_TIME_KEY = '_fz_tr'; +const UNIQ_ID_KEY = '_fz_uniq'; + +function getPageInfo() { + const pageInfo = { + domain: window.location.hostname, + } + + if (document.referrer) { + pageInfo.referrerDomain = parseURL(document.referrer).hostname; + } + + return pageInfo; +} + +function getUniqId() { + let cookies; + + try { + cookies = parseCookies(document.cookie); + } catch (a) { + cookies = {}; + } + + let isUniqFromLS; + let uniq = cookies[ UNIQ_ID_KEY ]; + if (!uniq) { + try { + if (window.localStorage) { + uniq = window.localStorage.getItem(UNIQ_ID_KEY) || ''; + isUniqFromLS = true; + } + } catch (b) {} + } + + if (uniq && isNaN(uniq)) { + uniq = null; + } + + if (uniq && isUniqFromLS) { + let expires = new Date(); + expires.setFullYear(expires.getFullYear() + 10); + + try { + document.cookie = UNIQ_ID_KEY + '=' + uniq + '; path=/; expires=' + expires.toUTCString(); + } catch (e) {} + } + + return uniq; +} + +function initFirstVisit() { + let now; + let visitDate; + let cookies; + + try { + cookies = parseCookies(document.cookie); + } catch (a) { + cookies = {}; + } + + visitDate = cookies[ FIRST_VISIT_DATE ]; + + if (!visitDate) { + now = new Date(); + + visitDate = parseInt(now.getTime() / 1000, 10); + + now.setFullYear(now.getFullYear() + 20); + + try { + document.cookie = FIRST_VISIT_DATE + '=' + visitDate + '; path=/; expires=' + now.toUTCString(); + } catch (e) {} + } + + return visitDate; +} + +function trim(string) { + if (string.trim) { + return string.trim(); + } + return string.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, ''); +} + +function parseCookies(cookie) { + let values = {}; + let arr, item; + let param, value; + let i, j; + + if (!cookie) { + return {}; + } + + arr = cookie.split(';'); + + for (i = 0, j = arr.length; i < j; i++) { + item = arr[ i ]; + if (!item) { + continue; + } + + param = item.split('='); + if (param.length <= 1) { + continue; + } + + value = decodeURIComponent(param[0]); + value = trim(value); + + values[value] = decodeURIComponent(param[1]); + } + + return values; +} + +function getRandAsStr(digits) { + let str = ''; + let rand = 0; + let i; + + digits = digits || 4; + + for (i = 0; i < digits; i++) { + rand = (Math.random() * 10) >>> 0; + str += '' + rand; + } + + return str; +} + +function getSessionBegin(session) { + if (!session || (typeof session !== 'string')) { + return 0; + } + + const len = session.length; + if (len && len <= SESSION_RAND_PART) { + return 0; + } + + const timestamp = session.substring(0, len - SESSION_RAND_PART); + + return parseInt(timestamp, 10); +} + +function initSession() { + const now = new Date(); + const expires = new Date(now.getTime() + SESSION_DURATION); + const timestamp = Math.floor(now.getTime() / 1000); + let begin = 0; + let cookies; + let sessionId; + let sessionDuration; + let isNew = false; + + try { + cookies = parseCookies(document.cookie); + } catch (a) { + cookies = {}; + } + + sessionId = cookies[ SESSION_ID ]; + + if (!sessionId || + !checkSessionByExpires() || + !checkSessionByReferer() || + !checkSessionByDay()) { + sessionId = '' + timestamp + getRandAsStr(SESSION_RAND_PART); + begin = timestamp; + + isNew = true; + } else { + begin = getSessionBegin(sessionId); + } + + if (begin > 0) { + sessionDuration = Math.floor(timestamp - begin); + } else { + sessionDuration = -1; + } + + try { + document.cookie = SESSION_ID + '=' + sessionId + '; path=/; expires=' + expires.toUTCString(); + } catch (e) {} + + return { + isNew: isNew, + id: sessionId, + duration: sessionDuration + }; +} + +function checkSessionByExpires() { + const timestamp = getTrackRequestLastTime(); + const now = new Date().getTime(); + + if (now > timestamp + SESSION_DURATION) { + return false; + } + return true; +} + +function checkSessionByReferer() { + const referrer = fntzAnalyticsAdapter.context.pageInfo.referrerDomain; + const domain = fntzAnalyticsAdapter.context.pageInfo.domain; + + return referrer === '' || domain === referrer; +} + +function checkSessionByDay() { + let last = getTrackRequestLastTime(); + if (last) { + last = new Date(last); + const now = new Date(); + + return last.getUTCDate() === now.getUTCDate() && + last.getUTCMonth() === now.getUTCMonth() && + last.getUTCFullYear() === now.getUTCFullYear(); + } + + return false; +} + +function saveTrackRequestTime() { + const now = new Date().getTime(); + const expires = new Date(now + SESSION_DURATION); + + try { + if (window.localStorage) { + window.localStorage.setItem(TRACK_TIME_KEY, now.toString()); + } else { + document.cookie = TRACK_TIME_KEY + '=' + now + '; path=/; expires=' + expires.toUTCString(); + } + } catch (a) {} +} + +function getTrackRequestLastTime() { + let cookie; + + try { + if (window.localStorage) { + return parseInt( + window.localStorage.getItem(TRACK_TIME_KEY) || 0, + 10, + ); + } + + cookie = parseCookies(document.cookie); + cookie = cookie[ TRACK_TIME_KEY ]; + if (cookie) { + return parseInt(cookie, 10); + } + } catch (e) {} + + return 0; +} + +function getAntiCacheParam() { + const date = new Date(); + const rand = (Math.random() * 99999 + 1) >>> 0; + + return ([ date.getTime(), rand ].join('')); +} + +function replaceBidder(str, bidder) { + let _str = str; + _str = _str.replace(/\%bidder\%/, bidder.toLowerCase()); + _str = _str.replace(/\%BIDDER\%/, bidder.toUpperCase()); + _str = _str.replace(/\%Bidder\%/, bidder.charAt(0).toUpperCase() + bidder.slice(1).toLowerCase()); + + return _str; +} + +function prepareBidRequestedParams(args) { + return [{ + event: encodeURIComponent(replaceBidder(fntzAnalyticsAdapter.context.bidRequestTrack, args.bidderCode)), + ref: encodeURIComponent(window.location.href), + }]; +} + +function prepareBidResponseParams(args) { + return [{ + event: encodeURIComponent(replaceBidder(fntzAnalyticsAdapter.context.bidResponsePriceTrack, args.bidderCode)), + value: args.cpm, + unit: 'usd' + }, { + event: encodeURIComponent(replaceBidder(fntzAnalyticsAdapter.context.bidResponseTimeTrack, args.bidderCode)), + value: args.timeToRespond, + unit: 'ms' + }]; +} + +function prepareBidWonParams(args) { + return [{ + event: encodeURIComponent(replaceBidder(fntzAnalyticsAdapter.context.bidWonTrack, args.bidderCode)), + value: args.cpm, + unit: 'usd' + }]; +} + +function prepareBidTimeoutParams(args) { + return args.map(function(bid) { + return { + event: encodeURIComponent(replaceBidder(fntzAnalyticsAdapter.context.bidTimeoutTrack, bid.bidder)), + value: bid.timeout, + unit: 'ms' + }; + }) +} + +function prepareTrackData(evtype, args) { + let prepareParams = null; + + switch (evtype) { + case CONSTANTS.EVENTS.BID_REQUESTED: + prepareParams = prepareBidRequestedParams; + break; + case CONSTANTS.EVENTS.BID_RESPONSE: + prepareParams = prepareBidResponseParams; + break; + case CONSTANTS.EVENTS.BID_WON: + prepareParams = prepareBidWonParams; + break; + case CONSTANTS.EVENTS.BID_TIMEOUT: + prepareParams = prepareBidTimeoutParams; + break; + } + + if (!prepareParams) { return null; } + + const data = prepareParams(args); + + if (!data) { return null; } + + const session = initSession(); + + return data.map(d => { + const trackData = Object.assign(d, { + id: fntzAnalyticsAdapter.context.id, + ref: encodeURIComponent(window.location.href), + title: encodeURIComponent(document.title), + scr_res: fntzAnalyticsAdapter.context.screenResolution, + fv_date: fntzAnalyticsAdapter.context.firstVisit, + ac: getAntiCacheParam(), + }) + + if (fntzAnalyticsAdapter.context.uniqId) { + trackData.fz_uniq = fntzAnalyticsAdapter.context.uniqId; + } + + if (session.id) { + trackData.ssn = session.id; + } + if (session.isNew) { + session.isNew = false; + trackData.ssn_start = 1; + } + trackData.ssn_dr = session.duration; + + return trackData; + }); +} + +function sendTrackRequest(trackData) { + try { + ajax( + fntzAnalyticsAdapter.context.host, + null, + trackData, + { + method: 'GET', + withCredentials: true, + contentType: 'application/x-www-form-urlencoded' + }, + ); + saveTrackRequestTime(); + } catch (err) { + utils.logError('Error on send data: ', err); + } +} + +const fntzAnalyticsAdapter = Object.assign( + adapter({ + FINTEZA_HOST, + ANALYTICS_TYPE + }), + { + track({ eventType, args }) { + if (typeof args !== 'undefined') { + const trackData = prepareTrackData(eventType, args); + if (!trackData) { return; } + + trackData.forEach(sendTrackRequest); + } + } + } +); + +fntzAnalyticsAdapter.originEnableAnalytics = fntzAnalyticsAdapter.enableAnalytics; + +fntzAnalyticsAdapter.enableAnalytics = function (config) { + if (!config.options.id) { + utils.logError('Client ID (id) option is not defined. Analytics won\'t work'); + return; + } + + fntzAnalyticsAdapter.context = { + host: config.options.host || FINTEZA_HOST, + id: config.options.id, + bidRequestTrack: config.options.bidRequestTrack || BID_REQUEST_TRACK, + bidResponsePriceTrack: config.options.bidResponsePriceTrack || BID_RESPONSE_PRICE_TRACK, + bidResponseTimeTrack: config.options.bidResponseTimeTrack || BID_RESPONSE_TIME_TRACK, + bidTimeoutTrack: config.options.bidTimeoutTrack || BID_TIMEOUT_TRACK, + bidWonTrack: config.options.bidWonTrack || BID_WON_TRACK, + firstVisit: initFirstVisit(), + screenResolution: `${window.screen.width}x${window.screen.height}`, + uniqId: getUniqId(), + pageInfo: getPageInfo(), + }; + + fntzAnalyticsAdapter.originEnableAnalytics(config); +}; + +adapterManager.registerAnalyticsAdapter({ + adapter: fntzAnalyticsAdapter, + code: 'finteza' +}); + +export default fntzAnalyticsAdapter; diff --git a/modules/fintezaAnalyticsAdapter.md b/modules/fintezaAnalyticsAdapter.md new file mode 100644 index 00000000000..7c07861d89f --- /dev/null +++ b/modules/fintezaAnalyticsAdapter.md @@ -0,0 +1,29 @@ +# Overview + +``` +Module Name: Finteza Analytics Adapter +Module Type: Analytics Adapter +Maintainer: renat@finteza.com +``` + +# Description + +The Finteza adapter for integration with Prebid is an analytics tool for publishers who use the Header Bidding technology. The adapter tracks auction opening, offer sending to advertisers, receipt of bids by the publisher and auction winner selection. All tracks are sent to Finteza and enable visual advertiser quality evaluation: how many offers partners accept, what prices they provide, how fast they respond and how often their bids win. + +For more information, visit the [official Finteza website](https://www.finteza.com/). + +# Test Parameters + +``` +{ + provider: 'finteza', + options: { + id: 'xxxxx', // Website ID (required) + bidRequestTrack: 'Bid Request %BIDDER%', + bidResponsePriceTrack: 'Bid Response Price %BIDDER%', + bidResponseTimeTrack: 'Bid Response Time %BIDDER%', + bidTimeoutTrack: 'Bid Timeout %BIDDER%', + bidWonTrack: 'Bid Won %BIDDER%' + } +} +``` diff --git a/modules/freeWheelAdserverVideo.js b/modules/freeWheelAdserverVideo.js new file mode 100644 index 00000000000..03217b1165d --- /dev/null +++ b/modules/freeWheelAdserverVideo.js @@ -0,0 +1,19 @@ +/** + * This module adds Freewheel support for Video to Prebid. + */ + +import { registerVideoSupport } from '../src/adServerManager'; +import { getHook, submodule } from '../src/hook'; + +export const adpodUtils = {}; +export function notifyTranslationModule(fn) { + fn.call(this, 'freewheel'); +} + +getHook('registerAdserver').before(notifyTranslationModule); + +registerVideoSupport('freewheel', { + getTargeting: (args) => adpodUtils.getTargeting(args) +}); + +submodule('adpod', adpodUtils); diff --git a/modules/freewheel-sspBidAdapter.js b/modules/freewheel-sspBidAdapter.js index 87c1979ac5d..3e52ba2cbe9 100644 --- a/modules/freewheel-sspBidAdapter.js +++ b/modules/freewheel-sspBidAdapter.js @@ -1,6 +1,6 @@ -import * as utils from 'src/utils'; -import { registerBidder } from 'src/adapters/bidderFactory'; -// import { config } from 'src/config'; +import * as utils from '../src/utils'; +import { registerBidder } from '../src/adapters/bidderFactory'; +// import { config } from '../src/config'; const BIDDER_CODE = 'freewheel-ssp'; @@ -68,6 +68,18 @@ function getPricing(xmlNode) { return princingData; } +function hashcode(inputString) { + var hash = 0; + var char; + if (inputString.length == 0) return hash; + for (var i = 0; i < inputString.length; i++) { + char = inputString.charCodeAt(i); + hash = ((hash << 5) - hash) + char; + hash = hash & hash; // Convert to 32bit integer + } + return hash; +} + function getCreativeId(xmlNode) { var creaId = ''; var adNodes = xmlNode.querySelectorAll('Ad'); @@ -116,7 +128,7 @@ function getAPIName(componentId) { function formatAdHTML(bid, size) { var integrationType = bid.params.format; - var divHtml = '
    '; + var divHtml = '
    '; var script = ''; var libUrl = ''; @@ -161,13 +173,15 @@ var getInBannerScript = function(bid, size) { }; var getOutstreamScript = function(bid) { - var placementCode = bid.adUnitCode; - var config = bid.params; // default placement if no placement is set if (!config.hasOwnProperty('domId') && !config.hasOwnProperty('auto') && !config.hasOwnProperty('p') && !config.hasOwnProperty('article')) { - config.domId = placementCode; + if (config.format === 'intext-roll') { + config.iframeMode = 'dfp'; + } else { + config.domId = 'freewheelssp_prebid_target'; + } } var script = 'var config = {' + @@ -216,11 +230,17 @@ export const spec = { utils.logMessage('Prebid.JS - freewheel bid adapter: only one ad unit is required.'); } + var zone = currentBidRequest.params.zoneId; + var timeInMillis = new Date().getTime(); + var keyCode = hashcode(zone + '' + timeInMillis); + var requestParams = { reqType: 'AdsSetup', protocolVersion: '2.0', - zoneId: currentBidRequest.params.zoneId, - componentId: getComponentId(currentBidRequest.params.format) + zoneId: zone, + componentId: getComponentId(currentBidRequest.params.format), + timestamp: timeInMillis, + pKey: keyCode }; // Add GDPR flag and consent string @@ -232,6 +252,10 @@ export const spec = { } } + if (currentBidRequest.params.gdpr_consented_providers) { + requestParams._fw_gdpr_consented_providers = currentBidRequest.params.gdpr_consented_providers; + } + var vastParams = currentBidRequest.params.vastUrlParams; if (typeof vastParams === 'object') { for (var key in vastParams) { @@ -330,6 +354,7 @@ export const spec = { url: USER_SYNC_URL }]; } - } + }, + } registerBidder(spec); diff --git a/modules/freewheel-sspBidAdapter.md b/modules/freewheel-sspBidAdapter.md index ba7915c87e1..70ab2415279 100644 --- a/modules/freewheel-sspBidAdapter.md +++ b/modules/freewheel-sspBidAdapter.md @@ -18,7 +18,7 @@ Module that connects to Freewheel ssp's demand sources { bidder: "freewheel-ssp", params: { - zoneId : '277225' + zoneId : '41852' } } ] diff --git a/modules/fyberBidAdapter.js b/modules/fyberBidAdapter.js index 8309b1996c3..3586d0775ac 100644 --- a/modules/fyberBidAdapter.js +++ b/modules/fyberBidAdapter.js @@ -1,7 +1,7 @@ -import {logError, getTopWindowUrl, getTopWindowReferrer, getTopWindowLocation, createTrackPixelHtml} from 'src/utils'; -import { registerBidder } from 'src/adapters/bidderFactory'; -import { formatQS } from 'src/url'; -import { config } from 'src/config'; +import {logError, getTopWindowUrl, getTopWindowReferrer, getTopWindowLocation, createTrackPixelHtml} from '../src/utils'; +import { registerBidder } from '../src/adapters/bidderFactory'; +import { formatQS } from '../src/url'; +import { config } from '../src/config'; /** * @type {{CODE: string, V: string, RECTANGLE_SIZE: {W: number, H: number}, SPOT_TYPES: {INTERSTITIAL: string, RECTANGLE: string, FLOATING: string, BANNER: string}, DISPLAY_AD: number, ENDPOINT_URL: string, EVENTS_ENDPOINT_URL: string, RESPONSE_HEADERS_NAME: {PRICING_VALUE: string, AD_H: string, AD_W: string}}} diff --git a/modules/gambidBidAdapter.js b/modules/gambidBidAdapter.js index f7026f9c76f..f86f9e5e404 100644 --- a/modules/gambidBidAdapter.js +++ b/modules/gambidBidAdapter.js @@ -193,7 +193,7 @@ function renderOutstream(bid) { const unitId = bid.adUnitCode + '/' + bid.adId; window['GamoshiPlayer'].renderAd({ id: unitId, - debug: window.location.href.indexOf('pbjsDebug') >= 0, + debug: window.originalLocation.href.indexOf('pbjsDebug') >= 0, placement: document.getElementById(bid.adUnitCode), width: bid.width, height: bid.height, diff --git a/modules/gambidBidAdapter.md b/modules/gambidBidAdapter.md deleted file mode 100644 index b34d05070a9..00000000000 --- a/modules/gambidBidAdapter.md +++ /dev/null @@ -1,81 +0,0 @@ -# Overview - -``` -Module Name: Gamoshi's Gambid Bid Adapter -Module Type: Bidder Adapter -Maintainer: arik@gamoshi.com -``` - -# Description - -Connects to Gamoshi's Gambid platform & exchange for bids. - -Gambid bid adapter supports Banner & Outstream Video. The *only* required parameter (in the `params` section) is the `supplyPartnerId` parameter. - -# Test Parameters -``` -var adUnits = [ - - // Banner adUnit - { - code: 'banner-div', - sizes: [[300, 250]], - bids: [{ - bidder: 'gambid', - params: { - - // ID of the supply partner you created in the Gambid dashboard - supplyPartnerId: '1253', - - // OPTIONAL: if you have a whitelabel account on Gamoshi, specify it here - //rtbEndpoint: 'https://my.custom-whitelabel-domain.io', - - // OPTIONAL: custom bid floor - bidfloor: 0.01, - - // OPTIONAL: if you know the ad position on the page, specify it here - // (this corresponds to "Ad Position" in OpenRTB 2.3, section 5.4) - //adpos: 1, - - // OPTIONAL: whether this is an interstitial placement (0 or 1) - // (see "instl" property in "Imp" object in the OpenRTB 2.3, section 3.2.2) - //instl: 0 - } - }] - }, - - // Video outstream adUnit - { - code: 'video-outstream', - sizes: [[300, 250]], - mediaTypes: { - video: { - context: 'outstream', - playerSize: [300, 250] - } - }, - bids: [ { - bidder: 'gambid', - params: { - - // ID of the supply partner you created in the Gambid dashboard - supplyPartnerId: '1254', - - // OPTIONAL: if you have a whitelabel account on Gamoshi, specify it here - //rtbEndpoint: 'https://my.custom-whitelabel-domain.io', - - // OPTIONAL: custom bid floor - bidfloor: 0.01, - - // OPTIONAL: if you know the ad position on the page, specify it here - // (this corresponds to "Ad Position" in OpenRTB 2.3, section 5.4) - //adpos: 1, - - // OPTIONAL: whether this is an interstitial placement (0 or 1) - // (see "instl" property in "Imp" object in the OpenRTB 2.3, section 3.2.2) - //instl: 0 - } - }] - } -]; -``` diff --git a/modules/gammaBidAdapter.js b/modules/gammaBidAdapter.js index add3aad520b..926dae14790 100644 --- a/modules/gammaBidAdapter.js +++ b/modules/gammaBidAdapter.js @@ -1,5 +1,5 @@ -import * as utils from 'src/utils'; -import { registerBidder } from 'src/adapters/bidderFactory'; +import * as utils from '../src/utils'; +import { registerBidder } from '../src/adapters/bidderFactory'; const ENDPOINT = 'hb.gammaplatform.com'; const BIDDER_CODE = 'gamma'; diff --git a/modules/gamoshiBidAdapter.js b/modules/gamoshiBidAdapter.js new file mode 100644 index 00000000000..b18188cf33a --- /dev/null +++ b/modules/gamoshiBidAdapter.js @@ -0,0 +1,276 @@ +import * as utils from '../src/utils'; +import {registerBidder} from '../src/adapters/bidderFactory'; +import {config} from '../src/config'; +import {Renderer} from '../src/Renderer'; +import {BANNER, VIDEO} from '../src/mediaTypes'; + +const ENDPOINTS = { + 'gamoshi': 'https://rtb.gamoshi.io' +}; + +const DEFAULT_TTL = 360; + +export const helper = { + getTopFrame: function () { + try { + return window.top === window ? 1 : 0; + } catch (e) { + } + return 0; + }, + startsWith: function (str, search) { + return str.substr(0, search.length) === search; + }, + getTopWindowDomain: function (url) { + const domainStart = url.indexOf('://') + '://'.length; + return url.substring(domainStart, url.indexOf('/', domainStart) < 0 ? url.length : url.indexOf('/', domainStart)); + }, + + getMediaType: function (bid) { + if (bid.ext) { + if (bid.ext.media_type) { + return bid.ext.media_type.toLowerCase(); + } else if (bid.ext.vast_url) { + return VIDEO; + } else { + return BANNER; + } + } + return BANNER; + } +}; + +export const spec = { + code: 'gamoshi', + aliases: ['gambid', 'cleanmedia', '9MediaOnline'], + supportedMediaTypes: ['banner', 'video'], + + isBidRequestValid: function (bid) { + return !!bid.params.supplyPartnerId && utils.isStr(bid.params.supplyPartnerId) && + (!bid.params['rtbEndpoint'] || utils.isStr(bid.params['rtbEndpoint'])) && + (!bid.params.bidfloor || utils.isNumber(bid.params.bidfloor)) && + (!bid.params['adpos'] || utils.isNumber(bid.params['adpos'])) && + (!bid.params['protocols'] || Array.isArray(bid.params['protocols'])) && + (!bid.params.instl || bid.params.instl === 0 || bid.params.instl === 1); + }, + + buildRequests: function (validBidRequests, bidderRequest) { + return validBidRequests.map(bidRequest => { + const {adUnitCode, auctionId, mediaTypes, params, sizes, transactionId} = bidRequest; + const baseEndpoint = params['rtbEndpoint'] || ENDPOINTS['gamoshi']; + const rtbEndpoint = `${baseEndpoint}/r/${params.supplyPartnerId}/bidr?rformat=open_rtb&reqformat=rtb_json&bidder=prebid` + (params.query ? '&' + params.query : ''); + let url = config.getConfig('pageUrl') || bidderRequest.refererInfo.referer; + + const rtbBidRequest = { + 'id': auctionId, + 'site': { + 'domain': helper.getTopWindowDomain(url), + 'page': url, + 'ref': bidderRequest.refererInfo.referer + }, + 'device': { + 'ua': navigator.userAgent + }, + 'imp': [], + 'ext': {} + }; + const gdprConsent = bidderRequest.gdprConsent; + + if (gdprConsent && gdprConsent.consentString && gdprConsent.gdprApplies) { + rtbBidRequest.ext.gdpr_consent = { + consent_string: gdprConsent.consentString, + consent_required: gdprConsent.gdprApplies + }; + rtbBidRequest.regs = { + ext: { + gdpr: gdprConsent.gdprApplies === true ? 1 : 0 + } + }; + rtbBidRequest.user = { + ext: { + consent: gdprConsent.consentString + } + } + } + const imp = { + 'id': transactionId, + 'instl': params.instl === 1 ? 1 : 0, + 'tagid': adUnitCode, + 'bidfloor': params.bidfloor || 0, + 'bidfloorcur': 'USD', + 'secure': helper.startsWith(utils.getTopWindowUrl().toLowerCase(), 'http://') ? 0 : 1 + }; + + const hasFavoredMediaType = + params.favoredMediaType && this.supportedMediaTypes.includes(params.favoredMediaType); + + if ((!mediaTypes || mediaTypes.banner)) { + if (!hasFavoredMediaType || params.favoredMediaType === BANNER) { + const bannerImp = Object.assign({}, imp, { + banner: { + w: sizes.length ? sizes[0][0] : 300, + h: sizes.length ? sizes[0][1] : 250, + pos: params.pos || 0, + topframe: helper.getTopFrame() + } + }); + rtbBidRequest.imp.push(bannerImp); + } + } + + if (mediaTypes && mediaTypes.video) { + if (!hasFavoredMediaType || params.favoredMediaType === VIDEO) { + const playerSize = mediaTypes.video.playerSize || sizes; + const videoImp = Object.assign({}, imp, { + video: { + w: playerSize ? playerSize[0][0] : 300, + h: playerSize ? playerSize[0][1] : 250, + protocols: params.protocols || [1, 2, 3, 4, 5, 6], + pos: params.pos || 0, + ext: { + context: mediaTypes.video.context + } + } + }); + rtbBidRequest.imp.push(videoImp); + } + } + + let eids = []; + if (bidRequest && bidRequest.userId) { + addExternalUserId(eids, utils.deepAccess(bidRequest, `userId.id5id`), 'id5-sync.com', 'ID5ID'); + addExternalUserId(eids, utils.deepAccess(bidRequest, `userId.tdid`), 'adserver.org', 'TDID'); + } + if (eids.length > 0) { + rtbBidRequest.user.ext.eids = eids; + } + + if (rtbBidRequest.imp.length === 0) { + return; + } + + return {method: 'POST', url: rtbEndpoint, data: rtbBidRequest, bidRequest}; + }); + }, + + interpretResponse: function (serverResponse, bidRequest) { + const response = serverResponse && serverResponse.body; + if (!response) { + utils.logError('empty response'); + return []; + } + + const bids = response.seatbid.reduce((acc, seatBid) => acc.concat(seatBid.bid), []); + let outBids = []; + + bids.forEach(bid => { + const outBid = { + requestId: bidRequest.bidRequest.bidId, + cpm: bid.price, + width: bid.w, + height: bid.h, + ttl: DEFAULT_TTL, + creativeId: bid.crid || bid.adid, + netRevenue: true, + currency: bid.cur || response.cur, + mediaType: helper.getMediaType(bid) + }; + + if (utils.deepAccess(bidRequest.bidRequest, 'mediaTypes.' + outBid.mediaType)) { + if (outBid.mediaType === BANNER) { + outBids.push(Object.assign({}, outBid, {ad: bid.adm})); + } else if (outBid.mediaType === VIDEO) { + const context = utils.deepAccess(bidRequest.bidRequest, 'mediaTypes.video.context'); + outBids.push(Object.assign({}, outBid, { + vastUrl: bid.ext.vast_url, + vastXml: bid.adm, + renderer: context === 'outstream' ? newRenderer(bidRequest.bidRequest, bid) : undefined + })); + } + } + }); + return outBids; + }, + + getUserSyncs: function (syncOptions, serverResponses, gdprConsent) { + const syncs = []; + const gdprApplies = gdprConsent && (typeof gdprConsent.gdprApplies === 'boolean') ? gdprConsent.gdprApplies : false; + const suffix = gdprApplies ? 'gc=' + encodeURIComponent(gdprConsent.consentString) : 'gc=missing'; + serverResponses.forEach(resp => { + if (resp.body) { + const bidResponse = resp.body; + if (bidResponse.ext && Array.isArray(bidResponse.ext['utrk'])) { + bidResponse.ext['utrk'].forEach(pixel => { + const url = pixel.url + (pixel.url.indexOf('?') > 0 ? '&' + suffix : '?' + suffix); + return syncs.push({type: pixel.type, url}); + }); + } + if (Array.isArray(bidResponse.seatbid)) { + bidResponse.seatbid.forEach(seatBid => { + if (Array.isArray(seatBid.bid)) { + seatBid.bid.forEach(bid => { + if (bid.ext && Array.isArray(bid.ext['utrk'])) { + bid.ext['utrk'].forEach(pixel => { + const url = pixel.url + (pixel.url.indexOf('?') > 0 ? '&' + suffix : '?' + suffix); + return syncs.push({type: pixel.type, url}); + }); + } + }); + } + }); + } + } + }); + return syncs; + } +}; + +function newRenderer(bidRequest, bid, rendererOptions = {}) { + const renderer = Renderer.install({ + url: (bidRequest.params && bidRequest.params.rendererUrl) || (bid.ext && bid.ext.renderer_url) || '//s.wlplayer.com/video/latest/renderer.js', + config: rendererOptions, + loaded: false, + }); + try { + renderer.setRender(renderOutstream); + } catch (err) { + utils.logWarn('Prebid Error calling setRender on renderer', err); + } + return renderer; +} + +function renderOutstream(bid) { + bid.renderer.push(() => { + const unitId = bid.adUnitCode + '/' + bid.adId; + window['GamoshiPlayer'].renderAd({ + id: unitId, + debug: window.location.href.indexOf('pbjsDebug') >= 0, + placement: document.getElementById(bid.adUnitCode), + width: bid.width, + height: bid.height, + events: { + ALL_ADS_COMPLETED: () => window.setTimeout(() => { + window['GamoshiPlayer'].removeAd(unitId); + }, 300) + }, + vastUrl: bid.vastUrl, + vastXml: bid.vastXml + }); + }); +} + +function addExternalUserId(eids, value, source, rtiPartner) { + if (utils.isStr(value)) { + eids.push({ + source, + uids: [{ + id: value, + ext: { + rtiPartner + } + }] + }); + } +} + +registerBidder(spec); diff --git a/modules/gamoshiBidAdapter.md b/modules/gamoshiBidAdapter.md new file mode 100644 index 00000000000..6e930375059 --- /dev/null +++ b/modules/gamoshiBidAdapter.md @@ -0,0 +1,121 @@ +# Overview + +``` +Module Name: Gamoshi Bid Adapter +Module Type: Bidder Adapter +Maintainer: salomon@gamoshi.com +``` + +# Description + +Connects to Gamoshi's Programmatic advertising platform as a service. + +Gamoshi bid adapter supports Banner & Outstream Video. The *only* required parameter (in the `params` section) is the `supplyPartnerId` parameter. + +# Test Parameters +``` +var adUnits = [ + + // Banner adUnit + { + code: 'banner-div', + sizes: [[300, 250]], + bids: [{ + bidder: 'gamoshi', + params: { + + // ID of the supply partner you created in the Gamoshi dashboard + supplyPartnerId: '1253', + + // OPTIONAL: if you have a whitelabel account on Gamoshi, specify it here + //rtbEndpoint: 'https://my.custom-whitelabel-domain.io', + + // OPTIONAL: custom bid floor + bidfloor: 0.01, + + // OPTIONAL: if you know the ad position on the page, specify it here + // (this corresponds to "Ad Position" in OpenRTB 2.3, section 5.4) + //adpos: 1, + + // OPTIONAL: whether this is an interstitial placement (0 or 1) + // (see "instl" property in "Imp" object in the OpenRTB 2.3, section 3.2.2) + //instl: 0 + } + }] + }, + + // Video outstream adUnit + { + code: 'video-outstream', + mediaTypes: { + video: { + context: 'outstream', + playerSize: [300, 250] + } + }, + bids: [ { + bidder: 'gamoshi', + params: { + + // ID of the supply partner you created in the dashboard + supplyPartnerId: '1254', + + // OPTIONAL: if you have a whitelabel account on Gamoshi, specify it here + //rtbEndpoint: 'https://my.custom-whitelabel-domain.io', + + // OPTIONAL: custom bid floor + bidfloor: 0.01, + + // OPTIONAL: if you know the ad position on the page, specify it here + // (this corresponds to "Ad Position" in OpenRTB 2.3, section 5.4) + //adpos: 1, + + // OPTIONAL: whether this is an interstitial placement (0 or 1) + // (see "instl" property in "Imp" object in the OpenRTB 2.3, section 3.2.2) + //instl: 0 + } + }] + }, + + // Multi-Format adUnit + { + code: 'banner-div', + mediaTypes: { + video: { + context: 'outstream', + playerSize: [300, 250] + }, + banner: { + sizes: [[300, 250]] + } + }, + bids: [{ + bidder: 'gamoshi', + params: { + + // ID of the supply partner you created in the Gamoshi dashboard + supplyPartnerId: '1253', + + // OPTIONAL: if you have a whitelabel account on Gamoshi, specify it here + //rtbEndpoint: 'https://my.custom-whitelabel-domain.io', + + // OPTIONAL: custom bid floor + bidfloor: 0.01, + + // OPTIONAL: if you know the ad position on the page, specify it here + // (this corresponds to "Ad Position" in OpenRTB 2.3, section 5.4) + //adpos: 1, + + // OPTIONAL: whether this is an interstitial placement (0 or 1) + // (see "instl" property in "Imp" object in the OpenRTB 2.3, section 3.2.2) + //instl: 0, + + // OPTIONAL: enable enforcement bids of a specific media type (video, banner) + // in this ad placement + // query: 'key1=value1&k2=value2', + // favoredMediaType: 'video', + } + }] + }, +]; +``` diff --git a/modules/getintentBidAdapter.js b/modules/getintentBidAdapter.js index 2ac66731153..bc2ed093665 100644 --- a/modules/getintentBidAdapter.js +++ b/modules/getintentBidAdapter.js @@ -1,5 +1,5 @@ -import { registerBidder } from 'src/adapters/bidderFactory'; -import { isInteger } from 'src/utils'; +import { registerBidder } from '../src/adapters/bidderFactory'; +import { isInteger } from '../src/utils'; const BIDDER_CODE = 'getintent'; const IS_NET_REVENUE = true; diff --git a/modules/giantsBidAdapter.js b/modules/giantsBidAdapter.js index 6844cb684bc..e2693392578 100644 --- a/modules/giantsBidAdapter.js +++ b/modules/giantsBidAdapter.js @@ -1,343 +1,343 @@ -import * as utils from 'src/utils'; -import { registerBidder } from 'src/adapters/bidderFactory'; -import { BANNER, NATIVE, VIDEO } from 'src/mediaTypes'; -import includes from 'core-js/library/fn/array/includes'; - -const BIDDER_CODE = 'giants'; -const URL = '//d.admp.io/hb'; -const VIDEO_TARGETING = ['id', 'mimes', 'minduration', 'maxduration', - 'startdelay', 'skippable', 'playback_method', 'frameworks']; -const NATIVE_MAPPING = { - body: 'description', - cta: 'ctatext', - image: { - serverName: 'main_image', - requiredParams: { required: true }, - minimumParams: { sizes: [{}] }, - }, - icon: { - serverName: 'icon', - requiredParams: { required: true }, - minimumParams: { sizes: [{}] }, - }, - sponsoredBy: 'sponsored_by', -}; -const SOURCE = 'pbjs'; - -export const spec = { - code: BIDDER_CODE, - aliases: [], - 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: function(bid) { - return !!(bid.params.zoneId); - }, - - /** - * Make a server request from the list of BidRequests. - * - * @param {BidRequest[]} bidRequests A non-empty list of bid requests which should be sent to the Server. - * @return ServerRequest Info describing the request to the server. - */ - buildRequests: function(bidRequests, bidderRequest) { - const tags = bidRequests.map(bidToTag); - // const zoneIds = bidRequests.map(bidToZoneId); - // var firstBid = bidRequests[0]; - var ref = utils.getTopWindowUrl(); - const url = URL + '/multi?url=' + ref; - // + '&callback=window.$$PREBID_GLOBAL$$.giantsResponse&callback_uid=' + bid.bidId; - - const payload = { - tags: [...tags], - // user: userObj, - sdk: { - source: SOURCE, - version: '$prebid.version$' - } - }; - // if (member > 0) { - // payload.member_id = member; - // } - - if (bidderRequest && bidderRequest.gdprConsent) { - // note - objects for impbus use underscore instead of camelCase - payload.gdpr_consent = { - consent_string: bidderRequest.gdprConsent.consentString, - consent_required: bidderRequest.gdprConsent.gdprApplies - }; - } - - const payloadString = JSON.stringify(payload); - - return { - method: 'POST', - // url: URL, - url: url, - data: payloadString, - bidderRequest - }; - }, - - /** - * Unpack the response from the server into a list of bids. - * - * @param {*} serverResponse A successful response from the server. - * @return {Bid[]} An array of bids which were nested inside the server. - */ - interpretResponse: function(serverResponse, {bidderRequest}) { - serverResponse = serverResponse.body; - const bids = []; - if (!serverResponse || serverResponse.error) { - let errorMessage = `in response for ${bidderRequest.bidderCode} adapter`; - if (serverResponse && serverResponse.error) { errorMessage += `: ${serverResponse.error}`; } - utils.logError(errorMessage); - return bids; - } - if (serverResponse.tags) { - serverResponse.tags.forEach(serverBid => { - if (serverBid.cpm && serverBid.cpm !== 0) { - const bid = newBid(serverBid, bidderRequest); - bid.mediaType = BANNER; - bids.push(bid); - } - }); - } - return bids; - }, - - getUserSyncs: function(syncOptions) { - if (syncOptions.iframeEnabled) { - return [{ - type: 'iframe', - url: '//d.admp.io/ping' - }]; - } - } -} - -/* Turn keywords parameter into ut-compatible format */ -function getKeywords(keywords) { - let arrs = []; - - utils._each(keywords, (v, k) => { - if (utils.isArray(v)) { - let values = []; - utils._each(v, (val) => { - val = utils.getValueString('keywords.' + k, val); - if (val) { values.push(val); } - }); - v = values; - } else { - v = utils.getValueString('keywords.' + k, v); - if (utils.isStr(v)) { - v = [v]; - } else { - return; - } // unsuported types - don't send a key - } - arrs.push({key: k, value: v}); - }); - - return arrs; -} - -/** - * Unpack the Server's Bid into a Prebid-compatible one. - * @param serverBid - * @param rtbBid - * @param bidderRequest - * @return Bid - */ -function newBid(serverBid, bidderRequest) { - const bid = { - requestId: serverBid.uuid, - cpm: serverBid.cpm, - creativeId: serverBid.creative_id, - // dealId: rtbBid.deal_id, - currency: 'USD', - netRevenue: true, - ttl: 300 - }; - - Object.assign(bid, { - width: serverBid.width, - height: serverBid.height, - // ad: serverBid.ad - ad: _renderCreative(serverBid.adUrl, serverBid.width, serverBid.height) - }); - // try { - // const url = rtbBid.rtb.trackers[0].impression_urls[0]; - // const tracker = utils.createTrackPixelHtml(url); - // bid.ad += tracker; - // } catch (error) { - // utils.logError('Error appending tracking pixel', error); - // } - - return bid; -} - -function bidToTag(bid) { - const tag = {}; - tag.sizes = transformSizes(bid.sizes); - tag.primary_size = tag.sizes[0]; - tag.ad_types = []; - tag.uuid = bid.bidId; - if (bid.params.zoneId) { - tag.id = bid.params.zoneId; - } else { - tag.code = bid.params.invCode; - } - tag.allow_smaller_sizes = bid.params.allowSmallerSizes || false; - tag.use_pmt_rule = bid.params.usePaymentRule || false - tag.prebid = true; - tag.disable_psa = true; - if (bid.params.reserve) { - tag.reserve = bid.params.reserve; - } - if (bid.params.position) { - tag.position = {'above': 1, 'below': 2}[bid.params.position] || 0; - } - if (bid.params.trafficSourceCode) { - tag.traffic_source_code = bid.params.trafficSourceCode; - } - if (bid.params.privateSizes) { - tag.private_sizes = transformSizes(bid.params.privateSizes); - } - if (bid.params.supplyType) { - tag.supply_type = bid.params.supplyType; - } - if (bid.params.pubClick) { - tag.pubclick = bid.params.pubClick; - } - if (bid.params.extInvCode) { - tag.ext_inv_code = bid.params.extInvCode; - } - if (bid.params.externalImpId) { - tag.external_imp_id = bid.params.externalImpId; - } - if (!utils.isEmpty(bid.params.keywords)) { - tag.keywords = getKeywords(bid.params.keywords); - } - - if (bid.mediaType === NATIVE || utils.deepAccess(bid, `mediaTypes.${NATIVE}`)) { - tag.ad_types.push(NATIVE); - - if (bid.nativeParams) { - const nativeRequest = buildNativeRequest(bid.nativeParams); - tag[NATIVE] = {layouts: [nativeRequest]}; - } - } - - const videoMediaType = utils.deepAccess(bid, `mediaTypes.${VIDEO}`); - const context = utils.deepAccess(bid, 'mediaTypes.video.context'); - - if (bid.mediaType === VIDEO || videoMediaType) { - tag.ad_types.push(VIDEO); - } - - // instream gets vastUrl, outstream gets vastXml - if (bid.mediaType === VIDEO || (videoMediaType && context !== 'outstream')) { - tag.require_asset_url = true; - } - - if (bid.params.video) { - tag.video = {}; - // place any valid video params on the tag - Object.keys(bid.params.video) - .filter(param => includes(VIDEO_TARGETING, param)) - .forEach(param => tag.video[param] = bid.params.video[param]); - } - - if ( - (utils.isEmpty(bid.mediaType) && utils.isEmpty(bid.mediaTypes)) || - (bid.mediaType === BANNER || (bid.mediaTypes && bid.mediaTypes[BANNER])) - ) { - tag.ad_types.push(BANNER); - } - - return tag; -} - -// function bidToZoneId(bid) { -// return bid.params.zoneId; -// } - -/* Turn bid request sizes into ut-compatible format */ -function transformSizes(requestSizes) { - let sizes = []; - let sizeObj = {}; - - if (utils.isArray(requestSizes) && requestSizes.length === 2 && - !utils.isArray(requestSizes[0])) { - sizeObj.width = parseInt(requestSizes[0], 10); - sizeObj.height = parseInt(requestSizes[1], 10); - sizes.push(sizeObj); - } else if (typeof requestSizes === 'object') { - for (let i = 0; i < requestSizes.length; i++) { - let size = requestSizes[i]; - sizeObj = {}; - sizeObj.width = parseInt(size[0], 10); - sizeObj.height = parseInt(size[1], 10); - sizes.push(sizeObj); - } - } - - return sizes; -} - -function buildNativeRequest(params) { - const request = {}; - - // map standard prebid native asset identifier to /ut parameters - // e.g., tag specifies `body` but /ut only knows `description`. - // mapping may be in form {tag: ''} or - // {tag: {serverName: '', requiredParams: {...}}} - Object.keys(params).forEach(key => { - // check if one of the forms is used, otherwise - // a mapping wasn't specified so pass the key straight through - const requestKey = - (NATIVE_MAPPING[key] && NATIVE_MAPPING[key].serverName) || - NATIVE_MAPPING[key] || - key; - - // required params are always passed on request - const requiredParams = NATIVE_MAPPING[key] && NATIVE_MAPPING[key].requiredParams; - request[requestKey] = Object.assign({}, requiredParams, params[key]); - - // minimum params are passed if no non-required params given on adunit - const minimumParams = NATIVE_MAPPING[key] && NATIVE_MAPPING[key].minimumParams; - - if (requiredParams && minimumParams) { - // subtract required keys from adunit keys - const adunitKeys = Object.keys(params[key]); - const requiredKeys = Object.keys(requiredParams); - const remaining = adunitKeys.filter(key => !includes(requiredKeys, key)); - - // if none are left over, the minimum params needs to be sent - if (remaining.length === 0) { - request[requestKey] = Object.assign({}, request[requestKey], minimumParams); - } - } - }); - - return request; -} - -function _renderCreative(adUrl, width, height) { - return ` - - - - `; -}; +} /** * Returns iframe document in a browser agnostic way - * @param {object} iframe reference - * @return {object} iframe `document` reference + * @param {Object} iframe reference + * @return {Object} iframe `document` reference */ -exports.getIframeDocument = function (iframe) { +export function getIframeDocument(iframe) { if (!iframe) { return; } @@ -660,24 +740,24 @@ exports.getIframeDocument = function (iframe) { doc = iframe.contentDocument; } } catch (e) { - exports.logError('Cannot get iframe document', e); + internal.logError('Cannot get iframe document', e); } return doc; -}; +} -exports.getValueString = function(param, val, defaultValue) { +export function getValueString(param, val, defaultValue) { if (val === undefined || val === null) { return defaultValue; } - if (exports.isStr(val)) { + if (isStr(val)) { return val; } - if (exports.isNumber(val)) { + if (isNumber(val)) { return val.toString(); } - exports.logWarn('Unsuported type for param: ' + param + ' required type: String'); -}; + internal.logWarn('Unsuported type for param: ' + param + ' required type: String'); +} export function uniques(value, index, arry) { return arry.indexOf(value) === index; @@ -688,6 +768,9 @@ export function flatten(a, b) { } export function getBidRequest(id, bidderRequests) { + if (!id) { + return; + } let bidRequest; bidderRequests.some(bidderRequest => { let result = find(bidderRequest.bids, bid => ['bidId', 'adId', 'bid_id'].some(type => bid[type] === id)); @@ -707,6 +790,19 @@ export function getValue(obj, key) { return obj[key]; } +/** + * Get the key of an object for a given value + */ +export function getKeyByValue(obj, value) { + for (let prop in obj) { + if (obj.hasOwnProperty(prop)) { + if (obj[prop] === value) { + return prop; + } + } + } +} + export function getBidderCodes(adUnits = $$PREBID_GLOBAL$$.adUnits) { // this could memoize adUnits return adUnits.map(unit => unit.bids.map(bid => bid.bidder) @@ -714,7 +810,7 @@ export function getBidderCodes(adUnits = $$PREBID_GLOBAL$$.adUnits) { } export function isGptPubadsDefined() { - if (window.googletag && exports.isFn(window.googletag.pubads) && exports.isFn(window.googletag.pubads().getSlots)) { + if (window.googletag && isFn(window.googletag.pubads) && isFn(window.googletag.pubads().getSlots)) { return true; } } @@ -785,7 +881,7 @@ export function deepClone(obj) { export function inIframe() { try { - return exports.getWindowSelf() !== exports.getWindowTop(); + return internal.getWindowSelf() !== internal.getWindowTop(); } catch (e) { return true; } @@ -810,13 +906,34 @@ export function checkCookieSupport() { } } export function cookiesAreEnabled() { - if (exports.checkCookieSupport()) { + if (internal.checkCookieSupport()) { return true; } window.document.cookie = 'prebid.cookieTest'; return window.document.cookie.indexOf('prebid.cookieTest') != -1; } +export function getCookie(name) { + let m = window.document.cookie.match('(^|;)\\s*' + name + '\\s*=\\s*([^;]*)\\s*(;|$)'); + return m ? decodeURIComponent(m[2]) : null; +} + +export function setCookie(key, value, expires, sameSite) { + document.cookie = `${key}=${encodeURIComponent(value)}${(expires !== '') ? `; expires=${expires}` : ''}; path=/${sameSite ? `; SameSite=${sameSite}` : ''}`; +} + +/** + * @returns {boolean} + */ +export function localStorageIsEnabled () { + try { + localStorage.setItem('prebid.cookieTest', '1'); + return localStorage.getItem('prebid.cookieTest') === '1'; + } catch (error) { + return false; + } +} + /** * Given a function, return a function which only executes the original after * it's been called numRequiredCalls times. @@ -847,7 +964,7 @@ export function delayExecution(func, numRequiredCalls) { * @export * @param {array} xs * @param {string} key - * @returns {${key_value}: ${groupByArray}, key_value: {groupByArray}} + * @returns {Object} {${key_value}: ${groupByArray}, key_value: {groupByArray}} */ export function groupBy(xs, key) { return xs.reduce(function(rv, x) { @@ -856,29 +973,9 @@ export function groupBy(xs, key) { }, {}); } -/** - * deepAccess utility function useful for doing safe access (will not throw exceptions) of deep object paths. - * @param {object} obj The object containing the values you would like to access. - * @param {string|number} path Object path to the value you would like to access. Non-strings are coerced to strings. - * @returns {*} The value found at the specified object path, or undefined if path is not found. - */ -export function deepAccess(obj, path) { - if (!obj) { - return; - } - path = String(path).split('.'); - for (let i = 0; i < path.length; i++) { - obj = obj[path[i]]; - if (typeof obj === 'undefined') { - return; - } - } - return obj; -} - /** * Returns content for a friendly iframe to execute a URL in script tag - * @param {url} URL to be executed in a script tag in a friendly iframe + * @param {string} url URL to be executed in a script tag in a friendly iframe * and are macros left to be replaced if required */ export function createContentToExecuteExtScriptInFriendlyFrame(url) { @@ -892,9 +989,9 @@ export function createContentToExecuteExtScriptInFriendlyFrame(url) { /** * Build an object consisting of only defined parameters to avoid creating an * object with defined keys and undefined values. - * @param {object} object The object to pick defined params out of + * @param {Object} object The object to pick defined params out of * @param {string[]} params An array of strings representing properties to look for in the object - * @returns {object} An object containing all the specified values that are defined + * @returns {Object} An object containing all the specified values that are defined */ export function getDefinedParams(object, params) { return params @@ -916,7 +1013,7 @@ export function getDefinedParams(object, params) { */ export function isValidMediaTypes(mediaTypes) { const SUPPORTED_MEDIA_TYPES = ['banner', 'native', 'video']; - const SUPPORTED_STREAM_TYPES = ['instream', 'outstream']; + const SUPPORTED_STREAM_TYPES = ['instream', 'outstream', 'adpod']; const types = Object.keys(mediaTypes); @@ -939,8 +1036,8 @@ export function getBidderRequest(bidRequests, bidder, adUnitCode) { } /** * Returns user configured bidder params from adunit - * @param {object} adunits - * @param {string} adunit code + * @param {Object} adUnits + * @param {string} adUnitCode code * @param {string} bidder code * @return {Array} user configured param for the given bidder adunit configuration */ @@ -957,10 +1054,10 @@ export function getUserConfiguredParams(adUnits, adUnitCode, bidder) { */ export function getOrigin() { // IE10 does not have this property. https://gist.github.com/hbogs/7908703 - if (!window.location.origin) { - return window.location.protocol + '//' + window.location.hostname + (window.location.port ? ':' + window.location.port : ''); + if (!window.originalLocation.origin) { + return window.originalLocation.protocol + '//' + window.originalLocation.hostname + (window.originalLocation.port ? ':' + window.originalLocation.port : ''); } else { - return window.location.origin; + return window.originalLocation.origin; } } @@ -975,7 +1072,7 @@ const compareCodeAndSlot = (slot, adUnitCode) => slot.getAdUnitPath() === adUnit /** * Returns filter function to match adUnitCode in slot - * @param {object} slot GoogleTag slot + * @param {Object} slot GoogleTag slot * @return {function} filter function */ export function isAdUnitCodeMatchingSlot(slot) { @@ -1014,18 +1111,9 @@ export function unsupportedBidderMessage(adUnit, bidder) { * @return {Object} object */ export function deletePropertyFromObject(object, prop) { - let result = Object.assign({}, object) + let result = Object.assign({}, object); delete result[prop]; - return result -} - -/** - * Delete requestId from external bid object. - * @param {Object} bid - * @return {Object} bid - */ -export function removeRequestId(bid) { - return exports.deletePropertyFromObject(bid, 'requestId'); + return result; } /** @@ -1049,28 +1137,75 @@ export function convertCamelToUnderscore(value) { return value.replace(/(?:^|\.?)([A-Z])/g, function (x, y) { return '_' + y.toLowerCase() }).replace(/^_/, ''); } +/** + * Returns a new object with undefined properties removed from given object + * @param obj the object to clean + */ +export function cleanObj(obj) { + return Object.keys(obj).reduce((newObj, key) => { + if (typeof obj[key] !== 'undefined') { + newObj[key] = obj[key]; + } + return newObj; + }, {}) +} + +/** + * Create a new object with selected properties. Also allows property renaming and transform functions. + * @param obj the original object + * @param properties An array of desired properties + */ +export function pick(obj, properties) { + if (typeof obj !== 'object') { + return {}; + } + return properties.reduce((newObj, prop, i) => { + if (typeof prop === 'function') { + return newObj; + } + + let newProp = prop; + let match = prop.match(/^(.+?)\sas\s(.+?)$/i); + + if (match) { + prop = match[1]; + newProp = match[2]; + } + + let value = obj[prop]; + if (typeof properties[i + 1] === 'function') { + value = properties[i + 1](value, newObj); + } + if (typeof value !== 'undefined') { + newObj[newProp] = value; + } + + return newObj; + }, {}); +} + /** * Converts an object of arrays (either strings or numbers) into an array of objects containing key and value properties * normally read from bidder params * eg { foo: ['bar', 'baz'], fizz: ['buzz'] } * becomes [{ key: 'foo', value: ['bar', 'baz']}, {key: 'fizz', value: ['buzz']}] - * @param {Object{Arrays}} keywords object of arrays representing keyvalue pairs + * @param {Object} keywords object of arrays representing keyvalue pairs * @param {string} paramName name of parent object (eg 'keywords') containing keyword data, used in error handling */ export function transformBidderParamKeywords(keywords, paramName = 'keywords') { let arrs = []; - exports._each(keywords, (v, k) => { - if (exports.isArray(v)) { + _each(keywords, (v, k) => { + if (isArray(v)) { let values = []; - exports._each(v, (val) => { - val = exports.getValueString(paramName + '.' + k, val); - if (val) { values.push(val); } + _each(v, (val) => { + val = getValueString(paramName + '.' + k, val); + if (val || val === '') { values.push(val); } }); v = values; } else { - v = exports.getValueString(paramName + '.' + k, v); - if (exports.isStr(v)) { + v = getValueString(paramName + '.' + k, v); + if (isStr(v)) { v = [v]; } else { return; @@ -1102,7 +1237,7 @@ function tryConvertType(typeToConvert, value) { export function convertTypes(types, params) { Object.keys(types).forEach(key => { if (params[key]) { - if (exports.isFn(types[key])) { + if (isFn(types[key])) { params[key] = types[key](params[key]); } else { params[key] = tryConvertType(types[key], params[key]); @@ -1116,3 +1251,86 @@ export function convertTypes(types, params) { }); return params; } + +export function setDataInLocalStorage(key, value) { + if (hasLocalStorage()) { + window.localStorage.setItem(key, value); + } +} + +export function getDataFromLocalStorage(key) { + if (hasLocalStorage()) { + return window.localStorage.getItem(key); + } +} + +export function hasLocalStorage() { + try { + return !!window.localStorage; + } catch (e) { + logError('Local storage api disabled'); + } +} + +export function isArrayOfNums(val, size) { + return (isArray(val)) && ((size) ? val.length === size : true) && (val.every(v => isInteger(v))); +} + +/** + * Creates an array of n length and fills each item with the given value + */ +export function fill(value, length) { + let newArray = []; + + for (let i = 0; i < length; i++) { + let valueToPush = isPlainObject(value) ? deepClone(value) : value; + newArray.push(valueToPush); + } + + return newArray; +} + +/** + * http://npm.im/chunk + * Returns an array with *size* chunks from given array + * + * Example: + * ['a', 'b', 'c', 'd', 'e'] chunked by 2 => + * [['a', 'b'], ['c', 'd'], ['e']] + */ +export function chunk(array, size) { + let newArray = []; + + for (let i = 0; i < Math.ceil(array.length / size); i++) { + let start = i * size; + let end = start + size; + newArray.push(array.slice(start, end)); + } + + return newArray; +} + +export function getMinValueFromArray(array) { + return Math.min(...array); +} + +export function getMaxValueFromArray(array) { + return Math.max(...array); +} + +/** + * This function will create compare function to sort on object property + * @param {string} property + * @returns {function} compare function to be used in sorting + */ +export function compareOn(property) { + return function compare(a, b) { + if (a[property] < b[property]) { + return 1; + } + if (a[property] > b[property]) { + return -1; + } + return 0; + } +} diff --git a/src/video.js b/src/video.js index 8e0775a6d62..57f44a76764 100644 --- a/src/video.js +++ b/src/video.js @@ -1,10 +1,12 @@ -import { videoAdapters } from './adaptermanager'; +import adapterManager from './adapterManager'; import { getBidRequest, deepAccess, logError } from './utils'; import { config } from '../src/config'; import includes from 'core-js/library/fn/array/includes'; +import { hook } from './hook'; const VIDEO_MEDIA_TYPE = 'video'; -const OUTSTREAM = 'outstream'; +export const OUTSTREAM = 'outstream'; +export const INSTREAM = 'instream'; /** * Helper functions for working with video-enabled adUnits @@ -14,7 +16,7 @@ export const videoAdUnit = adUnit => { const mediaTypes = deepAccess(adUnit, 'mediaTypes.video'); return mediaType || mediaTypes; }; -export const videoBidder = bid => includes(videoAdapters, bid.bidder); +export const videoBidder = bid => includes(adapterManager.videoAdapters, bid.bidder); export const hasNonVideoBidder = adUnit => adUnit.bids.filter(bid => !videoBidder(bid)).length; @@ -30,7 +32,7 @@ export const hasNonVideoBidder = adUnit => * @return {Boolean} If object is valid */ export function isValidVideoBid(bid, bidRequests) { - const bidRequest = getBidRequest(bid.adId, bidRequests); + const bidRequest = getBidRequest(bid.requestId, bidRequests); const videoMediaType = bidRequest && deepAccess(bidRequest, 'mediaTypes.video'); @@ -38,12 +40,16 @@ export function isValidVideoBid(bid, bidRequests) { // if context not defined assume default 'instream' for video bids // instream bids require a vast url or vast xml content + return checkVideoBidSetup(bid, bidRequest, videoMediaType, context); +} + +export const checkVideoBidSetup = hook('sync', function(bid, bidRequest, videoMediaType, context) { if (!bidRequest || (videoMediaType && context !== OUTSTREAM)) { // xml-only video bids require a prebid cache url if (!config.getConfig('cache.url') && bid.vastXml && !bid.vastUrl) { logError(` This bid contains only vastXml and will not work when a prebid cache url is not specified. - Try enabling prebid cache with pbjs.setConfig({ cache: {url: "..."} }); + Try enabling prebid cache with $$PREBID_GLOBAL$$.setConfig({ cache: {url: "..."} }); `); return false; } @@ -57,4 +63,4 @@ export function isValidVideoBid(bid, bidRequests) { } return true; -} +}, 'checkVideoBidSetup'); diff --git a/src/videoCache.js b/src/videoCache.js index cec2a3ec864..4a715cb1fe3 100644 --- a/src/videoCache.js +++ b/src/videoCache.js @@ -60,10 +60,23 @@ function wrapURI(uri, impUrl) { */ function toStorageRequest(bid) { const vastValue = bid.vastXml ? bid.vastXml : wrapURI(bid.vastUrl, bid.vastImpUrl); - return { + + let payload = { type: 'xml', - value: vastValue + value: vastValue, + ttlseconds: Number(bid.ttl) }; + + if (config.getConfig('cache.vasttrack')) { + payload.bidder = bid.bidder; + payload.bidid = bid.requestId; + } + + if (typeof bid.customCacheKey === 'string' && bid.customCacheKey !== '') { + payload.key = bid.customCacheKey; + } + + return payload; } /** @@ -87,7 +100,7 @@ function toStorageRequest(bid) { */ function shimStorageCallback(done) { return { - success: function(responseBody) { + success: function (responseBody) { let ids; try { ids = JSON.parse(responseBody).responses @@ -102,7 +115,7 @@ function shimStorageCallback(done) { done(new Error("The cache server didn't respond with a responses property."), []); } }, - error: function(statusText, responseBody) { + error: function (statusText, responseBody) { done(new Error(`Error storing video ad in the cache: ${statusText}: ${JSON.stringify(responseBody)}`), []); } } @@ -113,7 +126,7 @@ function shimStorageCallback(done) { * * @param {CacheableBid[]} bids A list of bid objects which should be cached. * @param {videoCacheStoreCallback} [done] An optional callback which should be executed after - * the data has been stored in the cache. + * the data has been stored in the cache. */ export function store(bids, done) { const requestData = { diff --git a/test/fixtures/allAdapters.js b/test/fixtures/allAdapters.js deleted file mode 100644 index e2baa12cd10..00000000000 --- a/test/fixtures/allAdapters.js +++ /dev/null @@ -1,3 +0,0 @@ -exports.getAllAdaptersString = function () { - return `var AardvarkAdapter = require(\'./adapters/aardvark.js\');\n exports.registerBidAdapter(new AardvarkAdapter(), \'aardvark\');\nvar AdbladeAdapter = require(\'./adapters/adblade.js\');\n exports.registerBidAdapter(new AdbladeAdapter(), \'adblade\');\nvar AdbutlerAdapter = require(\'./adapters/adbutler.js\');\n exports.registerBidAdapter(new AdbutlerAdapter(), \'adbutler\');\nvar AdequantAdapter = require(\'./adapters/adequant.js\');\n exports.registerBidAdapter(new AdequantAdapter(), \'adequant\');\nvar AdformAdapter = require(\'./adapters/adform.js\');\n exports.registerBidAdapter(new AdformAdapter(), \'adform\');\nvar AdmediaAdapter = require(\'./adapters/admedia.js\');\n exports.registerBidAdapter(new AdmediaAdapter(), \'admedia\');\nvar AolAdapter = require(\'./adapters/aol.js\');\n exports.registerBidAdapter(new AolAdapter(), \'aol\');\nvar AppnexusAdapter = require(\'./adapters/appnexus.js\');\n exports.registerBidAdapter(new AppnexusAdapter(), \'appnexus\');\nvar AppnexusAstAdapter = require(\'./adapters/appnexusAst.js\');\n exports.registerBidAdapter(new AppnexusAstAdapter(), \'appnexusAst\');\nvar GetintentAdapter = require(\'./adapters/getintent.js\');\n exports.registerBidAdapter(new GetintentAdapter(), \'getintent\');\nvar HiromediaAdapter = require(\'./adapters/hiromedia.js\');\n exports.registerBidAdapter(new HiromediaAdapter(), \'hiromedia\');\nvar IndexExchangeAdapter = require(\'./adapters/indexExchange.js\');\n exports.registerBidAdapter(new IndexExchangeAdapter(), \'indexExchange\');\nvar KruxlinkAdapter = require(\'./adapters/kruxlink.js\');\n exports.registerBidAdapter(new KruxlinkAdapter(), \'kruxlink\');\nvar KomoonaAdapter = require(\'./adapters/komoona.js\');\n exports.registerBidAdapter(new KomoonaAdapter(), \'komoona\');\nvar OpenxAdapter = require(\'./adapters/openx.js\');\n exports.registerBidAdapter(new OpenxAdapter(), \'openx\');\nvar PiximediaAdapter = require(\'./adapters/piximedia.js\');\n exports.registerBidAdapter(new PiximediaAdapter(), \'piximedia\');\nvar PubmaticAdapter = require(\'./adapters/pubmatic.js\');\n exports.registerBidAdapter(new PubmaticAdapter(), \'pubmatic\');\nvar PulsepointAdapter = require(\'./adapters/pulsepoint.js\');\n exports.registerBidAdapter(new PulsepointAdapter(), \'pulsepoint\');\nvar RubiconAdapter = require(\'./adapters/rubicon.js\');\n exports.registerBidAdapter(new RubiconAdapter(), \'rubicon\');\nvar SonobiAdapter = require(\'./adapters/sonobi.js\');\n exports.registerBidAdapter(new SonobiAdapter(), \'sonobi\');\nvar SovrnAdapter = require(\'./adapters/sovrn.js\');\n exports.registerBidAdapter(new SovrnAdapter(), \'sovrn\');\nvar SpringserveAdapter = require(\'./adapters/springserve.js\');\n exports.registerBidAdapter(new SpringserveAdapter(), \'springserve\');\nvar ThoughtleadrAdapter = require('./adapters/thoughtleadr.js');\n exports.registerBidAdapter(new ThoughtleadrAdapter(), 'thoughtleadr');\nvar TripleliftAdapter = require(\'./adapters/triplelift.js\');\n exports.registerBidAdapter(new TripleliftAdapter(), \'triplelift\');\nvar TwengaAdapter = require(\'./adapters/twenga.js\');\n exports.registerBidAdapter(new TwengaAdapter(), \'twenga\');\nvar YieldbotAdapter = require(\'./adapters/yieldbot.js\');\n exports.registerBidAdapter(new YieldbotAdapter(), \'yieldbot\');\nvar NginadAdapter = require(\'./adapters/nginad.js\');\n exports.registerBidAdapter(new NginadAdapter(), \'nginad\');\nvar BrightcomAdapter = require(\'./adapters/brightcom.js\');\n exports.registerBidAdapter(new BrightcomAdapter(), \'brightcom\');\nvar WideorbitAdapter = require(\'./adapters/wideorbit.js\');\n exports.registerBidAdapter(new WideorbitAdapter(), \'wideorbit\');\nvar JcmAdapter = require(\'./adapters/jcm.js\');\n exports.registerBidAdapter(new JcmAdapter(), \'jcm\');\nvar UnderdogmediaAdapter = require(\'./adapters/underdogmedia.js\');\n exports.registerBidAdapter(new UnderdogmediaAdapter(), \'underdogmedia\');\nvar MemeglobalAdapter = require(\'./adapters/memeglobal.js\');\n exports.registerBidAdapter(new MemeglobalAdapter(), \'memeglobal\');\nvar CentroAdapter = require(\'./adapters/centro.js\');\n exports.registerBidAdapter(new CentroAdapter(), \'centro\');\nvar RoxotAdapter = require(\'./adapters/roxot.js\');\n exports.registerBidAdapter(new RoxotAdapter(), \'roxot\');\nexports.aliasBidAdapter(\'appnexus\',\'brealtime\');\nexports.aliasBidAdapter(\'appnexus\',\'pagescience\');\nexports.aliasBidAdapter(\'appnexus\',\'defymedia\');\nexports.videoAdapters = ["appnexusAst"];`; -}; diff --git a/test/fixtures/fixtures.js b/test/fixtures/fixtures.js index fc59d7eeab3..2a0a7638fc4 100644 --- a/test/fixtures/fixtures.js +++ b/test/fixtures/fixtures.js @@ -1,4 +1,33 @@ // jscs:disable +import CONSTANTS from 'src/constants.json'; +const utils = require('src/utils.js'); + +function convertTargetingsFromOldToNew(targetings) { + var mapOfOldToNew = { + 'hb_bidder': CONSTANTS.TARGETING_KEYS.BIDDER, + 'hb_adid': CONSTANTS.TARGETING_KEYS.AD_ID, + 'hb_pb': CONSTANTS.TARGETING_KEYS.PRICE_BUCKET, + 'hb_size': CONSTANTS.TARGETING_KEYS.SIZE, + 'hb_deal': CONSTANTS.TARGETING_KEYS.DEAL, + 'hb_source': CONSTANTS.TARGETING_KEYS.SOURCE, + 'hb_format': CONSTANTS.TARGETING_KEYS.FORMAT + }; + var newTargetings = {}; + utils._each(targetings, function(value, currentKey) { + var replaced = false; + utils._each(mapOfOldToNew, function(newKey, oldKey) { + if (currentKey.indexOf(oldKey) === 0 && oldKey !== newKey) { + var updatedKey = currentKey.replace(oldKey, newKey); + newTargetings[updatedKey] = targetings[currentKey]; + replaced = true; + } + }); + if (!replaced) { + newTargetings[currentKey] = targetings[currentKey]; + } + }) + return newTargetings; +} export function getBidRequests() { return [ @@ -13,7 +42,7 @@ export function getBidRequests() { 'placementId': '4799418', 'test': 'me' }, - 'placementCode': '/19968336/header-bid-tag1', + 'adUnitCode': '/19968336/header-bid-tag1', 'sizes': [ [ 728, @@ -24,11 +53,14 @@ export function getBidRequests() { 90 ] ], + 'mediaTypes': { + 'banner': { + 'sizes': [[728, 90], [970, 90]] + } + }, 'bidId': '392b5a6b05d648', 'bidderRequestId': '2946b569352ef2', 'auctionId': '1863e370099523', - 'startTime': 1462918897462, - 'status': 1, 'transactionId': 'fsafsa' }, { @@ -36,7 +68,7 @@ export function getBidRequests() { 'params': { 'placementId': '4799418' }, - 'placementCode': '/19968336/header-bid-tag-0', + 'adUnitCode': '/19968336/header-bid-tag-0', 'sizes': [ [ 300, @@ -47,11 +79,14 @@ export function getBidRequests() { 600 ] ], + 'mediaTypes': { + 'banner': { + 'sizes': [[728, 90], [970, 90]] + } + }, 'bidId': '4dccdc37746135', 'bidderRequestId': '2946b569352ef2', 'auctionId': '1863e370099523', - 'startTime': 1462918897463, - 'status': 1, 'transactionId': 'fsafsa' } ], @@ -68,7 +103,7 @@ export function getBidRequests() { 'publisherId': 39741, 'adSlot': '39620189@300x250' }, - 'placementCode': '/19968336/header-bid-tag-0', + 'adUnitCode': '/19968336/header-bid-tag-0', 'sizes': [ [ 300, @@ -79,6 +114,11 @@ export function getBidRequests() { 600 ] ], + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250], [300, 600]] + } + }, 'bidId': '6d11aa2d5b3659', 'bidderRequestId': '5e1525bae3eb11', 'auctionId': '1863e370099523', @@ -117,7 +157,7 @@ export function getBidRequests() { 10 ], }, - 'placementCode': '/19968336/header-bid-tag-0', + 'adUnitCode': '/19968336/header-bid-tag-0', 'sizes': [ [ 300, @@ -128,6 +168,11 @@ export function getBidRequests() { 600 ] ], + 'mediaType': { + 'banner': { + 'sizes': [[300, 250], [300, 600]] + } + }, 'bidId': '96aff279720d39', 'bidderRequestId': '8778750ee15a77', 'auctionId': '1863e370099523', @@ -146,7 +191,7 @@ export function getBidRequests() { 'params': { 'inventoryCode': 'sortable_all_right_sports' }, - 'placementCode': '/19968336/header-bid-tag-0', + 'adUnitCode': '/19968336/header-bid-tag-0', 'sizes': [ [ 300, @@ -157,10 +202,14 @@ export function getBidRequests() { 600 ] ], + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250], [300, 600]] + } + }, 'bidId': '1144e2f0de84363', 'bidderRequestId': '107f5e6e98dcf09', 'auctionId': '1863e370099523', - 'startTime': 1462918897477, 'transactionId': 'fsafsa' } ], @@ -176,7 +225,7 @@ export function getBidRequests() { 'params': { 'tagId': 16577 }, - 'placementCode': '/19968336/header-bid-tag-0', + 'adUnitCode': '/19968336/header-bid-tag-0', 'sizes': [ [ 300, @@ -187,10 +236,14 @@ export function getBidRequests() { 600 ] ], + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250], [300, 600]] + } + }, 'bidId': '135e89c039705da', 'bidderRequestId': '12eeded736650b4', 'auctionId': '1863e370099523', - 'status': 1, 'transactionId': 'fsafsa' } ], @@ -206,7 +259,7 @@ export function getBidRequests() { 'params': { 'placementId': '4799418' }, - 'placementCode': '/19968336/header-bid-tag-0', + 'adUnitCode': '/19968336/header-bid-tag-0', 'sizes': [ [ 300, @@ -217,11 +270,14 @@ export function getBidRequests() { 600 ] ], + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250], [300, 600]] + } + }, 'bidId': '17dd1d869bed44e', 'bidderRequestId': '167c4d79b615948', 'auctionId': '1863e370099523', - 'startTime': 1462918897480, - 'status': 1, 'transactionId': 'fsafsa' } ], @@ -237,7 +293,7 @@ export function getBidRequests() { 'params': { 'placementId': '4799418' }, - 'placementCode': '/19968336/header-bid-tag-0', + 'adUnitCode': '/19968336/header-bid-tag-0', 'sizes': [ [ 300, @@ -248,11 +304,14 @@ export function getBidRequests() { 600 ] ], + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250], [300, 600]] + } + }, 'bidId': '192c8c1df0f5d1d', 'bidderRequestId': '18bed198c172a69', 'auctionId': '1863e370099523', - 'startTime': 1462918897481, - 'status': 1, 'transactionId': 'fsafsa' } ], @@ -268,7 +327,7 @@ export function getBidRequests() { 'params': { 'aId': 3080 }, - 'placementCode': '/19968336/header-bid-tag-0', + 'adUnitCode': '/19968336/header-bid-tag-0', 'sizes': [ [ 300, @@ -279,6 +338,11 @@ export function getBidRequests() { 600 ] ], + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250], [300, 600]] + } + }, 'bidId': '21ae8131ec04f6e', 'bidderRequestId': '20d0d30333715a7', 'auctionId': '1863e370099523', @@ -294,6 +358,7 @@ export function getBidResponses() { return [ { 'bidderCode': 'triplelift', + 'mediaType': 'banner', 'width': 0, 'height': 0, 'statusMessage': 'Bid available', @@ -311,19 +376,21 @@ export function getBidResponses() { 'pbAg': '0.10', 'size': '0x0', 'auctionId': 123456, - 'adserverTargeting': { + 'requestId': '1144e2f0de84363', + 'adserverTargeting': convertTargetingsFromOldToNew({ 'hb_bidder': 'triplelift', 'hb_adid': '222bb26f9e8bd', 'hb_pb': '10.00', 'hb_size': '0x0', 'foobar': '0x0' - }, + }), 'netRevenue': true, 'currency': 'USD', 'ttl': 300 }, { 'bidderCode': 'appnexus', + 'mediaType': 'banner', 'width': 300, 'height': 250, 'statusMessage': 'Bid available', @@ -343,19 +410,21 @@ export function getBidResponses() { 'size': '300x250', 'alwaysUseBid': true, 'auctionId': 123456, - 'adserverTargeting': { + 'requestId': '4dccdc37746135', + 'adserverTargeting': convertTargetingsFromOldToNew({ 'hb_bidder': 'appnexus', 'hb_adid': '233bcbee889d46d', 'hb_pb': '10.00', 'hb_size': '300x250', 'foobar': '300x250' - }, + }), 'netRevenue': true, 'currency': 'USD', 'ttl': 300 }, { 'bidderCode': 'appnexus', + 'mediaType': 'banner', 'width': 728, 'height': 90, 'statusMessage': 'Bid available', @@ -375,19 +444,21 @@ export function getBidResponses() { 'size': '728x90', 'alwaysUseBid': true, 'auctionId': 123456, - 'adserverTargeting': { + 'requestId': '392b5a6b05d648', + 'adserverTargeting': convertTargetingsFromOldToNew({ 'hb_bidder': 'appnexus', 'hb_adid': '24bd938435ec3fc', 'hb_pb': '10.00', 'hb_size': '728x90', 'foobar': '728x90' - }, + }), 'netRevenue': true, 'currency': 'USD', 'ttl': 300 }, { 'bidderCode': 'pagescience', + 'mediaType': 'banner', 'width': 300, 'height': 250, 'statusMessage': 'Bid available', @@ -406,19 +477,21 @@ export function getBidResponses() { 'pbAg': '0.50', 'size': '300x250', 'auctionId': 123456, - 'adserverTargeting': { + 'requestId': '192c8c1df0f5d1d', + 'adserverTargeting': convertTargetingsFromOldToNew({ 'hb_bidder': 'pagescience', 'hb_adid': '25bedd4813632d7', 'hb_pb': '10.00', 'hb_size': '300x250', 'foobar': '300x250' - }, + }), 'netRevenue': true, 'currency': 'USD', 'ttl': 300 }, { 'bidderCode': 'brightcom', + 'mediaType': 'banner', 'width': 300, 'height': 250, 'statusMessage': 'Bid available', @@ -436,19 +509,21 @@ export function getBidResponses() { 'pbAg': '0.15', 'size': '300x250', 'auctionId': 654321, - 'adserverTargeting': { + 'requestId': '135e89c039705da', + 'adserverTargeting': convertTargetingsFromOldToNew({ 'hb_bidder': 'brightcom', 'hb_adid': '26e0795ab963896', 'hb_pb': '10.00', 'hb_size': '300x250', 'foobar': '300x250' - }, + }), 'netRevenue': true, 'currency': 'USD', 'ttl': 300 }, { 'bidderCode': 'brealtime', + 'mediaType': 'banner', 'width': 300, 'height': 250, 'statusMessage': 'Bid available', @@ -467,19 +542,21 @@ export function getBidResponses() { 'pbAg': '0.50', 'size': '300x250', 'auctionId': 654321, - 'adserverTargeting': { + 'requestId': '17dd1d869bed44e', + 'adserverTargeting': convertTargetingsFromOldToNew({ 'hb_bidder': 'brealtime', 'hb_adid': '275bd666f5a5a5d', 'hb_pb': '10.00', 'hb_size': '300x250', 'foobar': '300x250' - }, + }), 'netRevenue': true, 'currency': 'USD', 'ttl': 300 }, { 'bidderCode': 'pubmatic', + 'mediaType': 'banner', 'width': '300', 'height': '250', 'statusMessage': 'Bid available', @@ -499,19 +576,21 @@ export function getBidResponses() { 'pbAg': '5.90', 'size': '300x250', 'auctionId': 654321, - 'adserverTargeting': { + 'requestId': '6d11aa2d5b3659', + 'adserverTargeting': convertTargetingsFromOldToNew({ 'hb_bidder': 'pubmatic', 'hb_adid': '28f4039c636b6a7', 'hb_pb': '10.00', 'hb_size': '300x250', 'foobar': '300x250' - }, + }), 'netRevenue': true, 'currency': 'USD', 'ttl': 300 }, { 'bidderCode': 'rubicon', + 'mediaType': 'banner', 'width': 300, 'height': 600, 'statusMessage': 'Bid available', @@ -529,13 +608,14 @@ export function getBidResponses() { 'pbAg': '2.70', 'size': '300x600', 'auctionId': 654321, - 'adserverTargeting': { + 'requestId': '96aff279720d39', + 'adserverTargeting': convertTargetingsFromOldToNew({ 'hb_bidder': 'rubicon', 'hb_adid': '29019e2ab586a5a', 'hb_pb': '10.00', 'hb_size': '300x600', 'foobar': '300x600' - }, + }), 'netRevenue': true, 'currency': 'USD', 'ttl': 300 @@ -546,26 +626,26 @@ export function getBidResponses() { export function getSlotTargeting() { return { '/19968336/header-bid-tag-0': [ - { + convertTargetingsFromOldToNew({ 'hb_bidder': [ 'appnexus' ] - }, - { + }), + convertTargetingsFromOldToNew({ 'hb_adid': [ '233bcbee889d46d' ] - }, - { + }), + convertTargetingsFromOldToNew({ 'hb_pb': [ '10.00' ] - }, - { + }), + convertTargetingsFromOldToNew({ 'hb_size': [ '300x250' ] - }, + }), { 'foobar': [ '300x250' @@ -579,138 +659,54 @@ export function getAdUnits() { return [ { 'code': '/19968336/header-bid-tag1', - 'sizes': [ - [ - 728, - 90 - ], - [ - 970, - 90 - ] - ], + 'mediaTypes': { + 'banner': { + 'sizes': [[728, 90], [970, 90]] + }, + }, 'bids': [ { 'bidder': 'adequant', 'params': { 'publisher_id': '1234567', 'bidfloor': 0.01 - }, - 'placementCode': '/19968336/header-bid-tag1', - 'sizes': [ - [ - 728, - 90 - ], - [ - 970, - 90 - ] - ], - 'bidId': '3692954f816efc', - 'bidderRequestId': '2b1a75d5e826c4', - 'auctionId': '1ff753bd4ae5cb' + } }, { 'bidder': 'appnexus', 'params': { 'placementId': '543221', 'test': 'me' - }, - 'placementCode': '/19968336/header-bid-tag1', - 'sizes': [ - [ - 728, - 90 - ], - [ - 970, - 90 - ] - ], - 'bidId': '68136e1c47023d', - 'bidderRequestId': '55e24a66bed717', - 'auctionId': '1ff753bd4ae5cb', - 'startTime': 1463510220995, - 'status': 1 + } } ] }, { 'code': '/19968336/header-bid-tag-0', - 'sizes': [ - [ - 300, - 250 - ], - [ - 300, - 600 - ] - ], + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250], [300, 600]] + } + }, 'bids': [ { 'bidder': 'appnexus', 'params': { 'placementId': '5324321' - }, - 'placementCode': '/19968336/header-bid-tag-0', - 'sizes': [ - [ - 300, - 250 - ], - [ - 300, - 600 - ] - ], - 'bidId': '7e5d6af25ed188', - 'bidderRequestId': '55e24a66bed717', - 'auctionId': '1ff753bd4ae5cb', - 'startTime': 1463510220996 + } }, { 'bidder': 'adequant', 'params': { 'publisher_id': '12353433', 'bidfloor': 0.01 - }, - 'placementCode': '/19968336/header-bid-tag-0', - 'sizes': [ - [ - 300, - 250 - ], - [ - 300, - 600 - ] - ], - 'bidId': '4448d80ac1374e', - 'bidderRequestId': '2b1a75d5e826c4', - 'auctionId': '1ff753bd4ae5cb' + } }, { 'bidder': 'triplelift', 'params': { 'inventoryCode': 'inv_code_here' - }, - 'placementCode': '/19968336/header-bid-tag-0', - 'sizes': [ - [ - 300, - 250 - ], - [ - 300, - 600 - ] - ], - 'bidId': '9514d586c52abf', - 'bidderRequestId': '8c4f03b838d7ee', - 'auctionId': '1ff753bd4ae5cb', - 'startTime': 1463510220997 + } }, { 'bidder': 'springserve', @@ -718,21 +714,7 @@ export function getAdUnits() { 'impId': 1234, 'supplyPartnerId': 1, 'test': true - }, - 'placementCode': '/19968336/header-bid-tag-0', - 'sizes': [ - [ - 300, - 250 - ], - [ - 300, - 600 - ] - ], - 'bidId': '113079fed03f58c', - 'bidderRequestId': '1048e0df882e965', - 'auctionId': '1ff753bd4ae5cb' + } }, { 'bidder': 'rubicon', @@ -758,105 +740,33 @@ export function getAdUnits() { 15, 10 ] - }, - 'placementCode': '/19968336/header-bid-tag-0', - 'sizes': [ - [ - 300, - 250 - ], - [ - 300, - 600 - ] - ], - 'bidId': '13c2c2a79d155ea', - 'bidderRequestId': '129e383ac549e5d', - 'auctionId': '1ff753bd4ae5cb' + } }, { 'bidder': 'openx', 'params': { 'jstag_url': 'http://servedbyopenx.com/w/1.0/jstag?nc=account_key', 'unit': 2345677 - }, - 'placementCode': '/19968336/header-bid-tag-0', - 'sizes': [ - [ - 300, - 250 - ], - [ - 300, - 600 - ] - ], - 'bidId': '154f9cbf82df565', - 'bidderRequestId': '1448569c2453b84', - 'auctionId': '1ff753bd4ae5cb' + } }, { 'bidder': 'pubmatic', 'params': { 'publisherId': 1234567, 'adSlot': '1234567@300x250' - }, - 'placementCode': '/19968336/header-bid-tag-0', - 'sizes': [ - [ - 300, - 250 - ], - [ - 300, - 600 - ] - ], - 'bidId': '17f8c3a8fb13308', - 'bidderRequestId': '16095445eeb05e4', - 'auctionId': '1ff753bd4ae5cb' + } }, { 'bidder': 'pagescience', 'params': { 'placementId': '1234567' - }, - 'placementCode': '/19968336/header-bid-tag-0', - 'sizes': [ - [ - 300, - 250 - ], - [ - 300, - 600 - ] - ], - 'bidId': '2074d5757675542', - 'bidderRequestId': '19883380ef5453a', - 'auctionId': '1ff753bd4ae5cb', - 'startTime': 1463510221014 + } }, { 'bidder': 'brealtime', 'params': { 'placementId': '1234567' - }, - 'placementCode': '/19968336/header-bid-tag-0', - 'sizes': [ - [ - 300, - 250 - ], - [ - 300, - 600 - ] - ], - 'bidId': '222b6ad5a9b835d', - 'bidderRequestId': '2163409fdf6f333', - 'auctionId': '1ff753bd4ae5cb', - 'startTime': 1463510221015 + } }, { 'bidder': 'indexExchange', @@ -864,21 +774,7 @@ export function getAdUnits() { 'id': '1', 'siteID': 123456, 'timeout': 10000 - }, - 'placementCode': '/19968336/header-bid-tag-0', - 'sizes': [ - [ - 300, - 250 - ], - [ - 300, - 600 - ] - ], - 'bidId': '2499961ab3f937a', - 'bidderRequestId': '23b57a2de4ae50b', - 'auctionId': '1ff753bd4ae5cb' + } }, { 'bidder': 'adform', @@ -886,82 +782,26 @@ export function getAdUnits() { 'adxDomain': 'adx.adform.net', 'mid': 123456, 'test': 1 - }, - 'placementCode': '/19968336/header-bid-tag-0', - 'sizes': [ - [ - 300, - 250 - ], - [ - 300, - 600 - ] - ], - 'bidId': '26605265bf5e9c5', - 'bidderRequestId': '25a0902299c17d3', - 'auctionId': '1ff753bd4ae5cb' + } }, { 'bidder': 'amazon', 'params': { 'aId': 3080 - }, - 'placementCode': '/19968336/header-bid-tag-0', - 'sizes': [ - [ - 300, - 250 - ], - [ - 300, - 600 - ] - ], - 'bidId': '2935d8f6764fe45', - 'bidderRequestId': '28afa21ca9246c1', - 'auctionId': '1ff753bd4ae5cb' + } }, { 'bidder': 'aol', 'params': { 'network': '112345.45', 'placement': 12345 - }, - 'placementCode': '/19968336/header-bid-tag-0', - 'sizes': [ - [ - 300, - 250 - ], - [ - 300, - 600 - ] - ], - 'bidId': '31d1489681dc539', - 'bidderRequestId': '30bf32da9080fdd', - 'auctionId': '1ff753bd4ae5cb' + } }, { 'bidder': 'sovrn', 'params': { 'tagid': '123556' - }, - 'placementCode': '/19968336/header-bid-tag-0', - 'sizes': [ - [ - 300, - 250 - ], - [ - 300, - 600 - ] - ], - 'bidId': '33c1a8028d91563', - 'bidderRequestId': '324bcb47cfcf034', - 'auctionId': '1ff753bd4ae5cb' + } }, { 'bidder': 'pulsepoint', @@ -969,41 +809,13 @@ export function getAdUnits() { 'cf': '300X250', 'cp': 1233456, 'ct': 12357 - }, - 'placementCode': '/19968336/header-bid-tag-0', - 'sizes': [ - [ - 300, - 250 - ], - [ - 300, - 600 - ] - ], - 'bidId': '379219f0506a26f', - 'bidderRequestId': '360ec66bbb0719c', - 'auctionId': '1ff753bd4ae5cb' + } }, { 'bidder': 'brightcom', 'params': { 'tagId': 75423 - }, - 'placementCode': '/19968336/header-bid-tag-0', - 'sizes': [ - [ - 300, - 250 - ], - [ - 300, - 600 - ] - ], - 'bidId': '395cfcf496e7d6d', - 'bidderRequestId': '38a776c7f001ea', - 'auctionId': '1ff753bd4ae5cb' + } } ] } @@ -1018,6 +830,7 @@ export function getBidResponsesFromAPI() { 'bidderCode': 'brightcom', 'width': 300, 'height': 250, + 'mediaType': 'banner', 'statusMessage': 'Bid available', 'adId': '26e0795ab963896', 'cpm': 0.17, @@ -1033,13 +846,14 @@ export function getBidResponsesFromAPI() { 'pbAg': '0.15', 'size': '300x250', 'auctionId': 654321, - 'adserverTargeting': { + 'requestId': '135e89c039705da', + 'adserverTargeting': convertTargetingsFromOldToNew({ 'hb_bidder': 'brightcom', 'hb_adid': '26e0795ab963896', 'hb_pb': '10.00', 'hb_size': '300x250', 'foobar': '300x250' - }, + }), 'netRevenue': true, 'currency': 'USD', 'ttl': 300 @@ -1048,6 +862,7 @@ export function getBidResponsesFromAPI() { 'bidderCode': 'brealtime', 'width': 300, 'height': 250, + 'mediaType': 'banner', 'statusMessage': 'Bid available', 'adId': '275bd666f5a5a5d', 'creative_id': 29681110, @@ -1064,13 +879,14 @@ export function getBidResponsesFromAPI() { 'pbAg': '0.50', 'size': '300x250', 'auctionId': 654321, - 'adserverTargeting': { + 'requestId': '17dd1d869bed44e', + 'adserverTargeting': convertTargetingsFromOldToNew({ 'hb_bidder': 'brealtime', 'hb_adid': '275bd666f5a5a5d', 'hb_pb': '10.00', 'hb_size': '300x250', 'foobar': '300x250' - }, + }), 'netRevenue': true, 'currency': 'USD', 'ttl': 300 @@ -1079,6 +895,7 @@ export function getBidResponsesFromAPI() { 'bidderCode': 'pubmatic', 'width': '300', 'height': '250', + 'mediaType': 'banner', 'statusMessage': 'Bid available', 'adId': '28f4039c636b6a7', 'adSlot': '39620189@300x250', @@ -1096,13 +913,14 @@ export function getBidResponsesFromAPI() { 'pbAg': '5.90', 'size': '300x250', 'auctionId': 654321, - 'adserverTargeting': { + 'requestId': '6d11aa2d5b3659', + 'adserverTargeting': convertTargetingsFromOldToNew({ 'hb_bidder': 'pubmatic', 'hb_adid': '28f4039c636b6a7', 'hb_pb': '10.00', 'hb_size': '300x250', 'foobar': '300x250' - }, + }), 'netRevenue': true, 'currency': 'USD', 'ttl': 300 @@ -1111,6 +929,7 @@ export function getBidResponsesFromAPI() { 'bidderCode': 'rubicon', 'width': 300, 'height': 600, + 'mediaType': 'banner', 'statusMessage': 'Bid available', 'adId': '29019e2ab586a5a', 'cpm': 2.74, @@ -1126,13 +945,14 @@ export function getBidResponsesFromAPI() { 'pbAg': '2.70', 'size': '300x600', 'auctionId': 654321, - 'adserverTargeting': { + 'requestId': '96aff279720d39', + 'adserverTargeting': convertTargetingsFromOldToNew({ 'hb_bidder': 'rubicon', 'hb_adid': '29019e2ab586a5a', 'hb_pb': '10.00', 'hb_size': '300x600', 'foobar': '300x600' - }, + }), 'netRevenue': true, 'currency': 'USD', 'ttl': 300 @@ -1145,7 +965,7 @@ export function getBidResponsesFromAPI() { // Ad server targeting when `setConfig({ enableSendAllBids: true })` is set. export function getAdServerTargeting() { return { - '/19968336/header-bid-tag-0': { + '/19968336/header-bid-tag-0': convertTargetingsFromOldToNew({ 'foobar': '0x0,300x250,300x600', 'hb_size': '300x250', 'hb_pb': '10.00', @@ -1179,8 +999,8 @@ export function getAdServerTargeting() { 'hb_pb_rubicon': '10.00', 'hb_adid_rubicon': '29019e2ab586a5a', 'hb_bidder_rubicon': 'rubicon' - }, - '/19968336/header-bid-tag1': { + }), + '/19968336/header-bid-tag1': convertTargetingsFromOldToNew({ 'foobar': '728x90', 'hb_size': '728x90', 'hb_pb': '10.00', @@ -1190,7 +1010,7 @@ export function getAdServerTargeting() { 'hb_pb_appnexus': '10.00', 'hb_adid_appnexus': '24bd938435ec3fc', 'hb_bidder_appnexus': 'appnexus' - } + }) }; } @@ -1198,19 +1018,19 @@ export function getAdServerTargeting() { export function getTargetingKeys() { return [ [ - 'hb_bidder', + CONSTANTS.TARGETING_KEYS.BIDDER, 'appnexus' ], [ - 'hb_adid', + CONSTANTS.TARGETING_KEYS.AD_ID, '233bcbee889d46d' ], [ - 'hb_pb', + CONSTANTS.TARGETING_KEYS.PRICE_BUCKET, '10.00' ], [ - 'hb_size', + CONSTANTS.TARGETING_KEYS.SIZE, '300x250' ], [ @@ -1225,19 +1045,19 @@ export function getTargetingKeys() { export function getTargetingKeysBidLandscape() { return [ [ - 'hb_bidder', + CONSTANTS.TARGETING_KEYS.BIDDER, 'appnexus' ], [ - 'hb_adid_appnexus', + CONSTANTS.TARGETING_KEYS.AD_ID + '_appnexus', '233bcbee889d46d' ], [ - 'hb_pb', + CONSTANTS.TARGETING_KEYS.PRICE_BUCKET, '10.00' ], [ - 'hb_size', + CONSTANTS.TARGETING_KEYS.SIZE, '300x250' ], [ @@ -1245,111 +1065,111 @@ export function getTargetingKeysBidLandscape() { ['0x0', '300x250', '300x600'] ], [ - 'hb_bidder_triplelift', + CONSTANTS.TARGETING_KEYS.BIDDER + '_triplelift', 'triplelift' ], [ - 'hb_adid_triplelift', + CONSTANTS.TARGETING_KEYS.AD_ID + '_triplelift', '222bb26f9e8bd' ], [ - 'hb_pb_triplelift', + CONSTANTS.TARGETING_KEYS.PRICE_BUCKET + '_triplelift', '10.00' ], [ - 'hb_size_triplelift', + CONSTANTS.TARGETING_KEYS.SIZE + '_triplelift', '0x0' ], [ - 'hb_bidder_appnexus', + CONSTANTS.TARGETING_KEYS.BIDDER + '_appnexus', 'appnexus' ], [ - 'hb_pb_appnexus', + CONSTANTS.TARGETING_KEYS.PRICE_BUCKET + '_appnexus', '10.00' ], [ - 'hb_size_appnexus', + CONSTANTS.TARGETING_KEYS.SIZE + '_appnexus', '300x250' ], [ - 'hb_bidder_pagescienc', + CONSTANTS.TARGETING_KEYS.BIDDER + '_pagescienc', 'pagescience' ], [ - 'hb_adid_pagescience', + CONSTANTS.TARGETING_KEYS.AD_ID + '_pagescience', '25bedd4813632d7' ], [ - 'hb_pb_pagescience', + CONSTANTS.TARGETING_KEYS.PRICE_BUCKET + '_pagescience', '10.00' ], [ - 'hb_size_pagescience', + CONSTANTS.TARGETING_KEYS.SIZE + '_pagescience', '300x250' ], [ - 'hb_bidder_brightcom', + CONSTANTS.TARGETING_KEYS.BIDDER + '_brightcom', 'brightcom' ], [ - 'hb_adid_brightcom', + CONSTANTS.TARGETING_KEYS.AD_ID + '_brightcom', '26e0795ab963896' ], [ - 'hb_pb_brightcom', + CONSTANTS.TARGETING_KEYS.PRICE_BUCKET + '_brightcom', '10.00' ], [ - 'hb_size_brightcom', + CONSTANTS.TARGETING_KEYS.SIZE + '_brightcom', '300x250' ], [ - 'hb_bidder_brealtime', + CONSTANTS.TARGETING_KEYS.BIDDER + '_brealtime', 'brealtime' ], [ - 'hb_adid_brealtime', + CONSTANTS.TARGETING_KEYS.AD_ID + '_brealtime', '275bd666f5a5a5d' ], [ - 'hb_pb_brealtime', + CONSTANTS.TARGETING_KEYS.PRICE_BUCKET + '_brealtime', '10.00' ], [ - 'hb_size_brealtime', + CONSTANTS.TARGETING_KEYS.SIZE + '_brealtime', '300x250' ], [ - 'hb_bidder_pubmatic', + CONSTANTS.TARGETING_KEYS.BIDDER + '_pubmatic', 'pubmatic' ], [ - 'hb_adid_pubmatic', + CONSTANTS.TARGETING_KEYS.AD_ID + '_pubmatic', '28f4039c636b6a7' ], [ - 'hb_pb_pubmatic', + CONSTANTS.TARGETING_KEYS.PRICE_BUCKET + '_pubmatic', '10.00' ], [ - 'hb_size_pubmatic', + CONSTANTS.TARGETING_KEYS.SIZE + '_pubmatic', '300x250' ], [ - 'hb_bidder_rubicon', + CONSTANTS.TARGETING_KEYS.BIDDER + '_rubicon', 'rubicon' ], [ - 'hb_adid_rubicon', + CONSTANTS.TARGETING_KEYS.AD_ID + '_rubicon', '29019e2ab586a5a' ], [ - 'hb_pb_rubicon', + CONSTANTS.TARGETING_KEYS.PRICE_BUCKET + '_rubicon', '10.00' ], [ - 'hb_size_rubicon', + CONSTANTS.TARGETING_KEYS.SIZE + '_rubicon', '300x600' ] ]; @@ -1367,7 +1187,7 @@ export function getBidRequestedPayload() { 'publisher_id': '5000563', 'bidfloor': 0.01 }, - 'placementCode': '/19968336/header-bid-tag-1', + 'adUnitCode': '/19968336/header-bid-tag-1', 'sizes': [ [ 300, @@ -1405,7 +1225,7 @@ export function getCurrencyRates() { }; } -export function createBidReceived({bidder, cpm, auctionId, responseTimestamp, adUnitCode, adId, status, ttl}) { +export function createBidReceived({bidder, cpm, auctionId, responseTimestamp, adUnitCode, adId, status, ttl, requestId}) { let bid = { 'bidderCode': bidder, 'width': '300', @@ -1419,6 +1239,7 @@ export function createBidReceived({bidder, cpm, auctionId, responseTimestamp, ad 'requestTimestamp': 1454535718610, 'responseTimestamp': responseTimestamp, 'auctionId': auctionId, + 'requestId': requestId, 'timeToRespond': 123, 'pbLg': '0.50', 'pbMg': '0.50', @@ -1426,12 +1247,12 @@ export function createBidReceived({bidder, cpm, auctionId, responseTimestamp, ad 'adUnitCode': adUnitCode, 'bidder': bidder, 'size': '300x250', - 'adserverTargeting': { + 'adserverTargeting': convertTargetingsFromOldToNew({ 'hb_bidder': bidder, 'hb_adid': adId, 'hb_pb': cpm, 'foobar': '300x250' - }, + }), 'netRevenue': true, 'currency': 'USD', 'ttl': (!ttl) ? 300 : ttl @@ -1442,3 +1263,64 @@ export function createBidReceived({bidder, cpm, auctionId, responseTimestamp, ad } return bid; } + +export function getServerTestingsAds() { + return [ + { + code: 'test_div_1', + sizes: [[728, 90]], + bids: [ + { + 'bidSource': { 'client': 0, 'server': 100 }, + 'bidder': 'rubicon' + }, + { + 'bidSource': { 'client': 100, 'server': 0 }, + 'bidder': 'appnexus' + } + ] + }, + { + code: 'test_div_2', + sizes: [[300, 250]], + bids: [ + { + 'bidSource': { 'client': 100, 'server': 0 }, + 'bidder': 'rubicon' + }, + { + 'bidSource': { 'client': 100, 'server': 0 }, + 'bidder': 'appnexus' + } + ] + }, + { + code: 'test_div_3', + sizes: [[300, 250]], + bids: [{ bidder: 'adequant' }] + }, + { + code: 'test_div_4', + sizes: [[300, 250]], + bids: [{ bidder: 'openx' }] + } + ]; +}; + +export const getServerTestingConfig = (config, override = {}) => + Object.assign({}, config, { + enabled: true, + testing: true, + testServerOnly: true, + bidders: ['appnexus', 'rubicon', 'openx'], + bidderControl: { + rubicon: { + bidSource: { server: 100, client: 0 }, + includeSourceKvp: true + }, + appnexus: { + bidSource: { server: 0, client: 100 }, + includeSourceKvp: true + } + } + }, override); diff --git a/test/helpers/karma-init.js b/test/helpers/karma-init.js index 56e936aa741..147d31d0d39 100644 --- a/test/helpers/karma-init.js +++ b/test/helpers/karma-init.js @@ -1,5 +1,5 @@ (function (window) { - if (!window.parent.pbjsKarmaInitDone && window.location.pathname === '/context.html') { + if (!window.parent.pbjsKarmaInitDone && window.originalLocation.pathname === '/context.html') { window.parent.pbjsKarmaInitDone = true; window.open('/debug.html', '_blank'); } diff --git a/test/helpers/testing-utils.js b/test/helpers/testing-utils.js new file mode 100644 index 00000000000..21d5992873e --- /dev/null +++ b/test/helpers/testing-utils.js @@ -0,0 +1,4 @@ +module.exports = { + host: (process.env.TEST_SERVER_HOST) ? process.env.TEST_SERVER_HOST : 'localhost', + protocol: (process.env.TEST_SERVER_PROTOCOL) ? 'https' : 'http' +} diff --git a/test/mocks/adloaderStub.js b/test/mocks/adloaderStub.js new file mode 100644 index 00000000000..9b9e62a4c3b --- /dev/null +++ b/test/mocks/adloaderStub.js @@ -0,0 +1,23 @@ + +import * as adloader from 'src/adloader'; + +let sandbox; + +export let loadScript; +export let loadExternalScript; +export let loadScriptStub; +export let loadExternalScriptStub; + +beforeEach(function() { + sandbox = sinon.sandbox.create(); + loadScript = adloader.loadScript; + loadExternalScript = adloader.loadExternalScript; + loadScriptStub = sandbox.stub(adloader, 'loadScript').callsFake((...args) => { + args[1](); + }); + loadExternalScriptStub = sandbox.stub(adloader, 'loadExternalScript'); +}); + +afterEach(function() { + sandbox.restore(); +}); diff --git a/test/pages/banner.html b/test/pages/banner.html new file mode 100644 index 00000000000..05085089f72 --- /dev/null +++ b/test/pages/banner.html @@ -0,0 +1,96 @@ + + + + + + + Prebid.js Banner Example + + + + + + + + + + + + + + + +

    Prebid.js Banner Ad Unit Test

    +
    + +
    +
    + + + diff --git a/test/pages/native.html b/test/pages/native.html new file mode 100644 index 00000000000..f382ab8aad7 --- /dev/null +++ b/test/pages/native.html @@ -0,0 +1,123 @@ + + + + + + + Prebid.js Native Example + + + + + + + + + + + + + + + +

    Prebid.js Native Ad Unit Test

    +
    +

    No response

    + +
    + + + diff --git a/test/pages/outstream.html b/test/pages/outstream.html new file mode 100644 index 00000000000..2a0543095cd --- /dev/null +++ b/test/pages/outstream.html @@ -0,0 +1,168 @@ + + + + + + + Prebid.js Video Outstream Example + + + + + + + + + + + + +
    +

    + In scelerisque sem sed tortor posuere sagittis. Fusce scelerisque odio at tincidunt ultricies. Fusce egestas, erat + non finibus dictum, nulla arcu viverra nibh, at bibendum ligula nisi egestas magna. Nulla eu finibus nulla. + Pellentesque at mi eget turpis + consequat scelerisque. Sed lacinia, nisi sit amet egestas vestibulum, elit odio iaculis leo, et lacinia risus enim + non lacus. Cras nec neque eget nunc gravida maximus. Ut hendrerit convallis sollicitudin. Donec cursus erat vel + metus gravida, + et pretium justo iaculis. Curabitur condimentum blandit augue, quis interdum leo. Vivamus dapibus est nec dui + efficitur, eu imperdiet nulla sollicitudin. Suspendisse laoreet velit vitae arcu mollis, ac interdum lorem + venenatis. Aenean + nec purus varius, accumsan ex at, luctus arcu. Quisque consectetur tortor eros, placerat lacinia eros aliquam a. + Proin non porttitor libero. +

    +

    + Proin eget vulputate est. Nunc sit amet neque a tortor ullamcorper suscipit non eu neque. Quisque at massa in + metus feugiat rutrum. Nulla et orci orci. Aliquam erat volutpat. Cras tincidunt metus lectus, sed suscipit augue + mollis vitae. Sed quis condimentum + tortor, sit amet consectetur erat. Nulla pellentesque turpis lacus, eu venenatis massa fringilla at. Duis sed + pharetra turpis. Maecenas vel porttitor neque. Praesent quis felis sapien. Donec suscipit euismod dui, vitae + fermentum nisi ornare + in. +

    +

    + Suspendisse tempor felis accumsan orci finibus, imperdiet mollis arcu imperdiet. In eu dolor condimentum, pulvinar + nisl a, sollicitudin nunc. Ut vel lectus libero. Praesent rhoncus leo tortor, at mollis nulla sagittis eget. + Quisque tempus tempor augue + sed rutrum. Sed vitae volutpat quam. Proin vestibulum eros metus, a luctus erat condimentum eu. Vivamus + ullamcorper ultricies dui, ac malesuada leo finibus semper. Cras diam augue, imperdiet sed efficitur id, aliquam + sed purus. Praesent + eget turpis quis sapien interdum sagittis. Vivamus placerat nunc a tempus fermentum. Praesent laoreet leo at + tellus porta, ut viverra tortor pharetra. Quisque elit velit, eleifend eget imperdiet vel, suscipit ac nisi. + Aliquam egestas mauris + ut massa fringilla laoreet. +

    +
    +

    Prebid Outstream Video Ad

    + +
    +

    + Quisque ac luctus nisi, vitae ornare arcu. Proin fermentum sapien vitae odio vestibulum porta. Suspendisse + faucibus sapien enim, et faucibus urna tempus et. Integer porttitor justo sed faucibus blandit. Morbi semper + lectus vitae semper facilisis. Quisque + molestie accumsan arcu, eget bibendum dui euismod et. Sed in mattis lacus, nec lacinia sem. Fusce sed tortor + posuere, iaculis justo varius, elementum est. +

    +

    + Etiam condimentum, eros commodo semper tristique, lorem leo pharetra massa, eget cursus justo enim id urna. Sed + imperdiet mauris vitae ante bibendum elementum. Etiam eu dui porttitor leo imperdiet cursus. Maecenas consequat, + neque a dapibus viverra, nunc + velit volutpat nibh, ut cursus sem tortor ac arcu. Praesent convallis lacus vel nisi aliquam, in posuere libero + scelerisque. Curabitur et lacinia nisl. Nunc id ligula neque. Phasellus non eros et leo ultrices ultricies. Nulla + facilisi. + Donec ut augue urna. Suspendisse sodales nisi at ex faucibus, et tempus magna fermentum. Proin non arcu interdum, + pulvinar est at, vehicula odio. Morbi nec maximus sem. Ut eu tristique urna. +

    +

    + Pellentesque eget quam sem. Nam interdum eleifend leo, mattis sagittis metus ornare tristique. Cras pretium odio + lectus, vitae viverra massa consequat eget. Suspendisse porttitor pretium lectus in scelerisque. Phasellus euismod + porta lectus eget pharetra. + Ut et viverra mi, ut imperdiet lacus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere + cubilia Curae; Nunc tempus sapien sit amet tortor rhoncus dignissim. Sed at augue et sem lacinia feugiat. Nulla + vitae convallis + urna. Morbi scelerisque erat quis nibh pretium, non elementum elit consectetur. Proin in feugiat nisl. +

    +

    + Morbi et ipsum purus. Integer ut pulvinar metus. Fusce maximus ex nec purus sollicitudin gravida. Vivamus dapibus + volutpat erat nec tristique. Aliquam mi dolor, pretium non elementum quis, viverra non est. Pellentesque egestas, + lectus a posuere imperdiet, + nisi sem elementum neque, eu volutpat arcu turpis venenatis magna. Curabitur non neque consectetur, vulputate urna + sed, vestibulum lacus. Aenean mollis, risus non pulvinar egestas, lectus lectus finibus dui, sit amet pretium + metus mauris + vitae nibh. In non ultricies odio. +

    +
    + + + diff --git a/test/pages/video.html b/test/pages/video.html index c6a72b6e26b..3fabeb14b94 100644 --- a/test/pages/video.html +++ b/test/pages/video.html @@ -16,7 +16,7 @@ - + - - - - - -

    Prebid.js Test3

    - -

    adequant

    -
    -

    No response

    - -
    - -

    adform

    -
    -

    No response

    - -
    - - -

    aol

    -
    -

    No response

    - -
    - -

    appnexus

    -
    -

    No response

    - -
    - -

    indexExchange

    -
    -

    No response

    - -
    - -

    openx

    -
    -

    No response

    - -
    - -

    pubmatic

    -
    -

    No response

    - -
    - -

    pulsepoint

    -
    -

    No response

    - -
    - -

    rubicon

    -
    -

    No response

    - -
    - -

    sonobi

    -
    -

    No response

    - -
    - -

    sovrn

    -
    -

    No response

    - -
    - -

    springserve

    -
    -

    No response

    - -
    - -

    triplelift

    -
    -

    No response

    - -
    - -

    yieldbot

    -
    -

    No response

    - -
    - -

    nginad

    -
    -

    No response

    - -
    - -

    brightcom

    -
    -

    No response

    - -
    - -

    sekindo

    -
    -

    No response

    - -
    - -

    kruxlink

    -
    -

    No response

    - -
    - -

    AdMedia

    -
    -

    No response

    - -
    - - - - - - - - - - diff --git a/test/spec/e2e/gpt-examples/e2e_default.html b/test/spec/e2e/gpt-examples/e2e_default.html deleted file mode 100644 index 9e0437d6275..00000000000 --- a/test/spec/e2e/gpt-examples/e2e_default.html +++ /dev/null @@ -1,117 +0,0 @@ - - - - - - - - - - - - - - - - - - -

    Prebid.js TestCase1

    - - -
    - -
    -
    - -
    - - - \ No newline at end of file diff --git a/test/spec/e2e/gpt-examples/gpt_default.html b/test/spec/e2e/gpt-examples/gpt_default.html deleted file mode 100644 index 93ec054b59a..00000000000 --- a/test/spec/e2e/gpt-examples/gpt_default.html +++ /dev/null @@ -1,684 +0,0 @@ - - - - - - - - - - -

    Prebid.js Test3

    - - -
    - -
    - -
    - -
    -
    - -
    -
    - -
    -
    - -
    -
    - -
    -
    - -
    -
    - -
    - -
    - -
    - -
    - -
    - -
    - -
    - -
    - -
    - -
    - - -
    - -
    - -
    - -
    -
    - -
    -
    - -
    - - - -
    - -
    - - - - - - - - - diff --git a/test/spec/e2e/gpt-examples/gpt_outstream.html b/test/spec/e2e/gpt-examples/gpt_outstream.html deleted file mode 100644 index 42ba48c98e7..00000000000 --- a/test/spec/e2e/gpt-examples/gpt_outstream.html +++ /dev/null @@ -1,250 +0,0 @@ - - - - - Prebid.js outstream video example - - - - - -

    Prebid Outstream Video Ads

    - -
    -

    -In scelerisque sem sed tortor posuere sagittis. Fusce scelerisque odio at tincidunt ultricies. Fusce egestas, erat non finibus dictum, nulla arcu viverra nibh, at bibendum ligula nisi egestas magna. Nulla eu finibus nulla. Pellentesque at mi eget turpis consequat scelerisque. Sed lacinia, nisi sit amet egestas vestibulum, elit odio iaculis leo, et lacinia risus enim non lacus. Cras nec neque eget nunc gravida maximus. Ut hendrerit convallis sollicitudin. Donec cursus erat vel metus gravida, et pretium justo iaculis. Curabitur condimentum blandit augue, quis interdum leo. Vivamus dapibus est nec dui efficitur, eu imperdiet nulla sollicitudin. Suspendisse laoreet velit vitae arcu mollis, ac interdum lorem venenatis. Aenean nec purus varius, accumsan ex at, luctus arcu. Quisque consectetur tortor eros, placerat lacinia eros aliquam a. Proin non porttitor libero. -

    -

    -Proin eget vulputate est. Nunc sit amet neque a tortor ullamcorper suscipit non eu neque. Quisque at massa in metus feugiat rutrum. Nulla et orci orci. Aliquam erat volutpat. Cras tincidunt metus lectus, sed suscipit augue mollis vitae. Sed quis condimentum tortor, sit amet consectetur erat. Nulla pellentesque turpis lacus, eu venenatis massa fringilla at. Duis sed pharetra turpis. Maecenas vel porttitor neque. Praesent quis felis sapien. Donec suscipit euismod dui, vitae fermentum nisi ornare in. -

    -

    -Suspendisse tempor felis accumsan orci finibus, imperdiet mollis arcu imperdiet. In eu dolor condimentum, pulvinar nisl a, sollicitudin nunc. Ut vel lectus libero. Praesent rhoncus leo tortor, at mollis nulla sagittis eget. Quisque tempus tempor augue sed rutrum. Sed vitae volutpat quam. Proin vestibulum eros metus, a luctus erat condimentum eu. Vivamus ullamcorper ultricies dui, ac malesuada leo finibus semper. Cras diam augue, imperdiet sed efficitur id, aliquam sed purus. Praesent eget turpis quis sapien interdum sagittis. Vivamus placerat nunc a tempus fermentum. Praesent laoreet leo at tellus porta, ut viverra tortor pharetra. Quisque elit velit, eleifend eget imperdiet vel, suscipit ac nisi. Aliquam egestas mauris ut massa fringilla laoreet. -

    -

    -Quisque ac luctus nisi, vitae ornare arcu. Proin fermentum sapien vitae odio vestibulum porta. Suspendisse faucibus sapien enim, et faucibus urna tempus et. Integer porttitor justo sed faucibus blandit. Morbi semper lectus vitae semper facilisis. Quisque molestie accumsan arcu, eget bibendum dui euismod et. Sed in mattis lacus, nec lacinia sem. Fusce sed tortor posuere, iaculis justo varius, elementum est. -

    -

    -Etiam condimentum, eros commodo semper tristique, lorem leo pharetra massa, eget cursus justo enim id urna. Sed imperdiet mauris vitae ante bibendum elementum. Etiam eu dui porttitor leo imperdiet cursus. Maecenas consequat, neque a dapibus viverra, nunc velit volutpat nibh, ut cursus sem tortor ac arcu. Praesent convallis lacus vel nisi aliquam, in posuere libero scelerisque. Curabitur et lacinia nisl. Nunc id ligula neque. Phasellus non eros et leo ultrices ultricies. Nulla facilisi. Donec ut augue urna. Suspendisse sodales nisi at ex faucibus, et tempus magna fermentum. Proin non arcu interdum, pulvinar est at, vehicula odio. Morbi nec maximus sem. Ut eu tristique urna. -

    -

    -Pellentesque eget quam sem. Nam interdum eleifend leo, mattis sagittis metus ornare tristique. Cras pretium odio lectus, vitae viverra massa consequat eget. Suspendisse porttitor pretium lectus in scelerisque. Phasellus euismod porta lectus eget pharetra. Ut et viverra mi, ut imperdiet lacus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Nunc tempus sapien sit amet tortor rhoncus dignissim. Sed at augue et sem lacinia feugiat. Nulla vitae convallis urna. Morbi scelerisque erat quis nibh pretium, non elementum elit consectetur. Proin in feugiat nisl. -

    -

    -Morbi et ipsum purus. Integer ut pulvinar metus. Fusce maximus ex nec purus sollicitudin gravida. Vivamus dapibus volutpat erat nec tristique. Aliquam mi dolor, pretium non elementum quis, viverra non est. Pellentesque egestas, lectus a posuere imperdiet, nisi sem elementum neque, eu volutpat arcu turpis venenatis magna. Curabitur non neque consectetur, vulputate urna sed, vestibulum lacus. Aenean mollis, risus non pulvinar egestas, lectus lectus finibus dui, sit amet pretium metus mauris vitae nibh. In non ultricies odio. -

    -

    -Donec dictum sem ac risus molestie lobortis. Maecenas at justo vehicula, iaculis orci eget, eleifend nunc. In non justo imperdiet, blandit leo in, interdum mi. Proin feugiat libero et erat dictum efficitur. Nunc auctor lacus feugiat erat euismod cursus. Sed vehicula ante vel quam pretium blandit. Maecenas congue quis mauris vitae efficitur. Cras sit amet justo at sem dictum ornare vitae eu ex. Nunc ornare odio nec leo consectetur cursus. Mauris eu dolor tellus. Etiam dignissim ut nunc et mollis. Cras at pulvinar velit, ut tincidunt velit. Cras vitae fermentum ante. Aenean interdum dolor in scelerisque consectetur. -

    -

    -Curabitur auctor leo sit amet massa faucibus ultrices. Maecenas dignissim libero ac cursus cursus. Curabitur eget sapien leo. Phasellus pretium blandit facilisis. Proin egestas urna a sagittis tempus. Donec in nibh ex. Vestibulum efficitur felis aliquam urna ultrices, at gravida nibh pretium. Morbi dictum vulputate pretium. Donec at nisi rutrum, pharetra nunc a, placerat felis. Quisque rhoncus congue fermentum. Quisque pharetra est at nisl sagittis suscipit. Maecenas scelerisque porta eleifend. Mauris nulla leo, consectetur at eros vel, elementum pretium diam. -

    -

    -In nisi libero, porta ut ullamcorper a, dapibus nec velit. Vestibulum congue rhoncus congue. Nulla a libero sit amet risus feugiat hendrerit id placerat ex. In hac habitasse platea dictumst. Pellentesque ut ullamcorper risus. Nunc et ipsum nisi. Vivamus a interdum diam, hendrerit pellentesque orci. -

    -

    -Vestibulum ut massa blandit, maximus sem vitae, vulputate mauris. Nam condimentum velit a facilisis dignissim. Nunc venenatis pharetra dapibus. Praesent ullamcorper risus sit amet molestie consectetur. Cras mauris felis, consequat et enim a, ultricies pretium enim. Nulla porttitor nunc mi, sed posuere magna venenatis non. Donec lobortis consectetur mauris, fermentum auctor dui dignissim sed. Sed vel venenatis urna. Donec velit velit, imperdiet non vulputate non, eleifend sed nisi. -

    -

    -Proin et turpis velit. Donec tempus dictum dolor, eget eleifend lacus. Donec eu felis in ante iaculis ultrices. Mauris varius, turpis quis venenatis convallis, enim dolor ornare justo, in dictum ipsum purus quis dolor. Ut condimentum feugiat lectus ut auctor. Maecenas luctus consequat erat, nec pretium urna pulvinar in. Donec gravida rhoncus aliquet. Cras aliquet odio eget orci hendrerit, non posuere velit dignissim. Nunc tempus aliquam iaculis. Nunc viverra lobortis mauris et malesuada. Donec congue suscipit mauris. Phasellus efficitur, leo at mollis maximus, lorem mauris pretium urna, nec scelerisque ante neque eu erat. Nam rhoncus malesuada velit nec ultricies. -

    -
    -

    Prebid Outstream Video Ad

    - -
    - -

    -Proin blandit in arcu sed porttitor. Morbi in erat vel risus mollis interdum. Proin vel odio semper, porttitor risus sed, tristique odio. Donec viverra massa et dui scelerisque, ac sagittis odio viverra. Aliquam lacinia enim sit amet dapibus ultrices. Nulla mollis, massa eget interdum egestas, lectus eros pretium eros, vel consectetur velit odio vel odio. Proin euismod aliquam finibus. Phasellus facilisis mollis est, non consequat lectus volutpat nec. -

    -

    -Ut vel ultricies erat. Pellentesque non ipsum quis odio ornare tempus in cursus ex. In a turpis non quam pulvinar tincidunt. Maecenas tortor neque, dapibus a quam aliquet, dictum pellentesque leo. Sed aliquet tellus est, in tempus magna congue ut. Phasellus at tincidunt lorem, id fringilla risus. Nunc vel pulvinar massa. Aliquam erat volutpat. Phasellus semper interdum justo at eleifend. Curabitur feugiat quam sed mollis facilisis. -

    -

    -Quisque consectetur sem a elit aliquet facilisis. Quisque dignissim velit at quam rhoncus dignissim. Proin feugiat sem at turpis ultrices imperdiet. Integer vel eros vel ante ultricies dapibus vitae eget dui. Fusce sollicitudin semper tortor at molestie. Pellentesque nec metus sed mauris aliquet iaculis. Mauris malesuada tortor nec mi dictum, feugiat euismod enim gravida. Vestibulum dapibus ut nulla vel euismod. Nunc lobortis, mauris at pretium faucibus, ligula diam venenatis nulla, in mattis sapien arcu feugiat dolor. In at dui leo. Cras elementum condimentum turpis. -

    -

    -Donec eget dolor ac nulla lobortis bibendum. Praesent commodo accumsan ligula eget commodo. Suspendisse sit amet dignissim metus. Sed ut eros viverra, viverra lectus eget, ornare eros. Mauris elementum lacinia dapibus. Donec magna nisl, suscipit quis mattis eget, tincidunt sed sapien. Curabitur elementum nulla eget lorem gravida dapibus. Nunc vel dolor et libero pretium interdum vitae eget mauris. Vestibulum et erat in nulla sollicitudin luctus ut quis nulla. -

    -

    -Maecenas iaculis pellentesque quam at fringilla. Donec dui elit, suscipit eget varius id, suscipit efficitur metus. Nulla rutrum ultrices tempor. Vivamus hendrerit justo ac fermentum euismod. Vestibulum tempus pulvinar tempus. Curabitur congue neque luctus dolor vehicula, non efficitur metus fringilla. Nam a imperdiet ex. Integer at est hendrerit, rutrum justo eu, ornare odio. Etiam convallis sapien a purus vehicula, eget gravida purus semper. Fusce ex enim, volutpat ac feugiat et, rhoncus vel tortor. Aenean ultrices libero sed neque fermentum tempor. Morbi tincidunt dui turpis, non mollis est dignissim at. Suspendisse molestie convallis interdum. Donec sit amet fermentum purus. -

    -

    -Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam et ex orci. Vivamus rutrum est vel porta imperdiet. Cras ultricies tortor dolor, nec mollis felis ullamcorper vel. Praesent scelerisque vehicula sem, nec feugiat mauris tempus ac. Donec at enim non sem commodo sodales. Ut sit amet risus sit amet ante viverra venenatis. Aliquam sodales mollis est eget ultricies. Etiam pulvinar sapien et ipsum elementum pharetra. -

    -

    -Nam blandit metus erat, sit amet congue ipsum cursus sed. In a commodo ante, sit amet tincidunt quam. Aenean lobortis et nibh in venenatis. Aliquam faucibus purus quis neque consectetur, quis consequat risus maximus. Proin mollis imperdiet felis, eget tempus ipsum scelerisque ut. Sed euismod interdum augue sed varius. Sed volutpat tellus ut risus porta accumsan. -

    -

    -Mauris aliquet eu arcu sed pharetra. Duis nec leo volutpat libero finibus malesuada in eget velit. Aliquam facilisis urna mauris, et aliquam ipsum dictum finibus. Donec eget mi fermentum, vehicula odio at, molestie orci. In a hendrerit lectus. Aenean congue ipsum ac imperdiet suscipit. Maecenas eleifend pretium metus id mollis. -

    -
    -

    Prebid Outstream Video Ad

    - -
    - -

    -Ut vel ultricies erat. Pellentesque non ipsum quis odio ornare tempus in cursus ex. In a turpis non quam pulvinar tincidunt. Maecenas tortor neque, dapibus a quam aliquet, dictum pellentesque leo. Sed aliquet tellus est, in tempus magna congue ut. Phasellus at tincidunt lorem, id fringilla risus. Nunc vel pulvinar massa. Aliquam erat volutpat. Phasellus semper interdum justo at eleifend. Curabitur feugiat quam sed mollis facilisis. -

    -

    -Quisque consectetur sem a elit aliquet facilisis. Quisque dignissim velit at quam rhoncus dignissim. Proin feugiat sem at turpis ultrices imperdiet. Integer vel eros vel ante ultricies dapibus vitae eget dui. Fusce sollicitudin semper tortor at molestie. Pellentesque nec metus sed mauris aliquet iaculis. Mauris malesuada tortor nec mi dictum, feugiat euismod enim gravida. Vestibulum dapibus ut nulla vel euismod. Nunc lobortis, mauris at pretium faucibus, ligula diam venenatis nulla, in mattis sapien arcu feugiat dolor. In at dui leo. Cras elementum condimentum turpis. -

    -

    -Donec eget dolor ac nulla lobortis bibendum. Praesent commodo accumsan ligula eget commodo. Suspendisse sit amet dignissim metus. Sed ut eros viverra, viverra lectus eget, ornare eros. Mauris elementum lacinia dapibus. Donec magna nisl, suscipit quis mattis eget, tincidunt sed sapien. Curabitur elementum nulla eget lorem gravida dapibus. Nunc vel dolor et libero pretium interdum vitae eget mauris. Vestibulum et erat in nulla sollicitudin luctus ut quis nulla. -

    -

    -Maecenas iaculis pellentesque quam at fringilla. Donec dui elit, suscipit eget varius id, suscipit efficitur metus. Nulla rutrum ultrices tempor. Vivamus hendrerit justo ac fermentum euismod. Vestibulum tempus pulvinar tempus. Curabitur congue neque luctus dolor vehicula, non efficitur metus fringilla. Nam a imperdiet ex. Integer at est hendrerit, rutrum justo eu, ornare odio. Etiam convallis sapien a purus vehicula, eget gravida purus semper. Fusce ex enim, volutpat ac feugiat et, rhoncus vel tortor. Aenean ultrices libero sed neque fermentum tempor. Morbi tincidunt dui turpis, non mollis est dignissim at. Suspendisse molestie convallis interdum. Donec sit amet fermentum purus. -

    - -
    - - - diff --git a/test/spec/e2e/gpt-examples/gpt_yieldbot.html b/test/spec/e2e/gpt-examples/gpt_yieldbot.html deleted file mode 100644 index 12766c537f6..00000000000 --- a/test/spec/e2e/gpt-examples/gpt_yieldbot.html +++ /dev/null @@ -1,241 +0,0 @@ - - - - - - - - - - -
    -
    - Yieldbot integration test mode: - -
      -
    • START (i.e.force bids to be returned)
    • -
    • STOP
    • -
    -
    - -

    Prebid.js Yieldbot Adapter Test

    -
    -
    - -
    -

    Lorem ipsum dolor. Sit amet proin. Integer cursus mi mus curabitur euismod vel quos duis bibendum nec interdum porta dolor a viverra nisl fusce. Volutpat sit at. Donec nisl taciti. Eget eu lobortis. Excepteur diam orci lacus nibh pharetra. Justo neque maecenas. Viverra molestie dolor ante rutrum vivamus libero urna suscipit leo praesent ultricies. In dignissim qui ante bibendum in. Habitasse ac arcu non nulla augue. Felis lectus non tempus in aliquam. Sit porttitor nec. Sodales non sit eu duis.

    -
    - -
    -

    Donec feugiat ornare a amet optio. Vitae sit sapien. Vitae nec justo. Fusce ac in semper ligula duis eget vel sit. Augue mauris sit. A adipisicing orci est augue dapibus ullamcorper faucibus fermentum. Et phasellus in tempus vivamus praesent. Nisl dui porttitor. Iaculis vulputate eros ut interdum eu. Lacus quis magna varius in quis. Congue erat porttitor sit eu vitae pharetra scelerisque nec. Dolor dui vel ut velit vestibulum. Lectus ullamcorper mi. Curabitur ipsum pellentesque sed erat est est sapien in tempor sodales viverra. Dui volutpat morbi eleifend fringilla quis. Neque erat erat. Rhoncus sed posuere. Dapibus fusce ut lacus mus est pede sed quisquam. Quis aliquam pellentesque. Wisi ac odio eu wisi amet ut ipsum a erat aliquam nunc.

    -

    Dis vitae penatibus. Mollis mauris bibendum. Porta orci amet dolor sed felis in neque per cursus molestie pellentesque. Quis accusamus vel sed dapibus orci congue ut eros. Nec faucibus inceptos. Hendrerit in eget nulla tellus lacinia. Fusce ut ut mattis.

    -

    Vivamus mauris metus. Ridiculus habitant lorem in nulla in quam eros ut. Libero aliquam platea. In enim consectetuer eget mattis accumsan aenean faucibus tincidunt. Amet donec vitae wisi pellentesque magna non lacinia qui. In erat in maecenas amet dui. Aliquam elit vel. Ligula sodales lacus. Nisl a purus. Pharetra velit porttitor vel vel non turpis viverra fringilla lorem arcu pellentesque sed aliquet nonummy quisque dapibus ullamcorper. Mollis ipsum nulla. Tempor tempus vitae. Luctus amet vel. Suspendisse sagittis vestibulum fusce eu.

    -

    Urna et felis bibendum felis sit vestibulum wisi pharetra quisque ac quis cursus suspendisse quisque aenean luctus curabitur. Eget nec leo. Mi placerat cras nulla et integer eget in sed. Non magni parturient. Egestas iaculis malesuada. Nec a duis. Pede condimentum ullamcorper. Augue arcu tellus. In velit in in duis odio dictum wisi proin quis eget sit. Felis tempus inceptos. Turpis risus eu. Mi vivamus consequat. Lectus dui imperdiet amet orci vehicula in vel pellentesque habitant suscipit aliquam. Proin porttitor vitae ultricies a in. Est duis pede. Tristique velit vestibulum odio sodales morbi magna ut vitae. Elementum imperdiet sodales ultrices tortor mollis vehicula lorem varius pellentesque mi ut sit turpis feugiat. In convallis urna. Justo aliquam sed quis scelerisque nonummy. Lobortis rhoncus ornare. Pellentesque leo quam at beatae in. Erat lorem tempus. Molestie faucibus a id mauris montes. Sed orci vulputate. Libero justo curabitur. Amet orci ante. Non felis est erat cras purus id at id nibh nunc facilisis amet metus sagittis pellentesque eros amet. Vitae sit nulla vitae wisi diam. Tincidunt ipsum eleifend semper tortor non. Tellus amet aliquet.

    -

    Dis vitae penatibus. Mollis mauris bibendum. Porta orci amet dolor sed felis in neque per cursus molestie pellentesque. Quis accusamus vel sed dapibus orci congue ut eros. Nec faucibus inceptos. Hendrerit in eget nulla tellus lacinia. Fusce ut ut mattis.

    -

    Vivamus mauris metus. Ridiculus habitant lorem in nulla in quam eros ut. Libero aliquam platea. In enim consectetuer eget mattis accumsan aenean faucibus tincidunt. Amet donec vitae wisi pellentesque magna non lacinia qui. In erat in maecenas amet dui. Aliquam elit vel. Ligula sodales lacus. Nisl a purus. Pharetra velit porttitor vel vel non turpis viverra fringilla lorem arcu pellentesque sed aliquet nonummy quisque dapibus ullamcorper. Mollis ipsum nulla. Tempor tempus vitae. Luctus amet vel. Suspendisse sagittis vestibulum fusce eu.

    -

    Urna et felis bibendum felis sit vestibulum wisi pharetra quisque ac quis cursus suspendisse quisque aenean luctus curabitur. Eget nec leo. Mi placerat cras nulla et integer eget in sed. Non magni parturient. Egestas iaculis malesuada. Nec a duis. Pede condimentum ullamcorper. Augue arcu tellus. In velit in in duis odio dictum wisi proin quis eget sit. Felis tempus inceptos. Turpis risus eu. Mi vivamus consequat. Lectus dui imperdiet amet orci vehicula in vel pellentesque habitant suscipit aliquam. Proin porttitor vitae ultricies a in. Est duis pede. Tristique velit vestibulum odio sodales morbi magna ut vitae. Elementum imperdiet sodales ultrices tortor mollis vehicula lorem varius pellentesque mi ut sit turpis feugiat. In convallis urna. Justo aliquam sed quis scelerisque nonummy. Lobortis rhoncus ornare. Pellentesque leo quam at beatae in. Erat lorem tempus. Molestie faucibus a id mauris montes. Sed orci vulputate. Libero justo curabitur. Amet orci ante. Non felis est erat cras purus id at id nibh nunc facilisis amet metus sagittis pellentesque eros amet. Vitae sit nulla vitae wisi diam. Tincidunt ipsum eleifend semper tortor non. Tellus amet aliquet.

    -
    -
    - -
    -
    - -
    -
    -

    Dis vitae penatibus. Mollis mauris bibendum. Porta orci amet dolor sed felis in neque per cursus molestie pellentesque. Quis accusamus vel sed dapibus orci congue ut eros. Nec faucibus inceptos. Hendrerit in eget nulla tellus lacinia. Fusce ut ut mattis.

    -
    - - diff --git a/test/spec/e2e/longform/basic_w_bidderSettings.spec.js b/test/spec/e2e/longform/basic_w_bidderSettings.spec.js new file mode 100644 index 00000000000..06413fb809a --- /dev/null +++ b/test/spec/e2e/longform/basic_w_bidderSettings.spec.js @@ -0,0 +1,68 @@ +const includes = require('core-js/library/fn/array/includes'); +const expect = require('chai').expect; +const testServer = require('../../../helpers/testing-utils'); + +const host = testServer.host; +const protocol = testServer.protocol; + +const validDurations = ['15s', '30s']; +const validCats = ['Food', 'Retail Stores/Chains', 'Pet Food/Supplies', 'Travel/Hotels/Airlines', 'Automotive', 'Health Care Services']; +const validCpms = ['14.00', '13.00', '12.00', '9.00']; +const customKeyRegex = /\d{2}\.\d{2}_\d{1,3}_\d{2}s/; +const uuidRegex = /(\d|\w){8}-((\d|\w){4}-){3}(\d|\w){12}/; + +describe('longform ads not using requireExactDuration field', function() { + this.retries(3); + it('process the bids successfully', function() { + browser + .url(protocol + '://' + host + ':9999/integrationExamples/longform/basic_w_bidderSettings.html?pbjs_debug=true') + .pause(10000); + + const loadPrebidBtnXpath = '//*[@id="loadPrebidRequestBtn"]'; + browser.waitForExist(loadPrebidBtnXpath); + $(loadPrebidBtnXpath).click(); + browser.pause(3000); + + const listOfCpmsXpath = '/html/body/div[1]/div/div/div/div[1]/div[2]/div/table/tbody/tr/td[2]'; + const listOfCategoriesXpath = '/html/body/div[1]/div/div/div/div[1]/div[2]/div/table/tbody/tr/td[3]'; + const listOfDurationsXpath = '/html/body/div[1]/div/div/div/div[1]/div[2]/div/table/tbody/tr/td[4]'; + + browser.waitForExist(listOfCpmsXpath); + + let listOfCpms = $$(listOfCpmsXpath); + let listOfCats = $$(listOfCategoriesXpath); + let listOfDuras = $$(listOfDurationsXpath); + + expect(listOfCpms.length).to.equal(listOfCats.length).and.to.equal(listOfDuras.length); + for (let i = 0; i < listOfCpms.length; i++) { + let cpm = listOfCpms[i].getText(); + let cat = listOfCats[i].getText(); + let dura = listOfDuras[i].getText(); + expect(includes(validCpms, cpm), `Could not find CPM ${cpm} in accepted list`).to.equal(true); + expect(includes(validCats, cat), `Could not find Category ${cat} in accepted list`).to.equal(true); + expect(includes(validDurations, dura), `Could not find Duration ${dura} in accepted list`).to.equal(true); + } + }); + + it('formats the targeting keys properly', function () { + const listOfKeyElementsXpath = '/html/body/div[1]/div/div/div/div[2]/div[2]/div/table/tbody/tr/td[1]'; + const listOfKeyValuesXpath = '/html/body/div[1]/div/div/div/div[2]/div[2]/div/table/tbody/tr/td[2]'; + browser.waitForExist(listOfKeyElementsXpath); + browser.waitForExist(listOfKeyValuesXpath); + + let listOfKeyElements = $$(listOfKeyElementsXpath); + let listOfKeyValues = $$(listOfKeyValuesXpath); + + let firstKey = listOfKeyElements[0].getText(); + expect(firstKey).to.equal('hb_pb_cat_dur'); + + let firstKeyValue = listOfKeyValues[0].getText(); + expect(firstKeyValue).match(customKeyRegex); + + let lastKey = listOfKeyElements[listOfKeyElements.length - 1].getText(); + expect(lastKey).to.equal('hb_cache_id'); + + let lastKeyValue = listOfKeyValues[listOfKeyValues.length - 1].getText(); + expect(lastKeyValue).to.match(uuidRegex); + }); +}) diff --git a/test/spec/e2e/longform/basic_w_custom_adserver_translation.spec.js b/test/spec/e2e/longform/basic_w_custom_adserver_translation.spec.js new file mode 100644 index 00000000000..82310738246 --- /dev/null +++ b/test/spec/e2e/longform/basic_w_custom_adserver_translation.spec.js @@ -0,0 +1,68 @@ +const includes = require('core-js/library/fn/array/includes'); +const expect = require('chai').expect; +const testServer = require('../../../helpers/testing-utils'); + +const host = testServer.host; +const protocol = testServer.protocol; + +const validDurations = ['15s', '30s']; +const validCats = ['Food', 'Retail Stores/Chains', 'Pet Food/Supplies', 'Travel/Hotels/Airlines', 'Automotive', 'Health Care Services']; +const validCpms = ['15.00', '14.00', '13.00', '10.00']; +const customKeyRegex = /\d{2}\.\d{2}_\d{1,3}_\d{2}s/; +const uuidRegex = /(\d|\w){8}-((\d|\w){4}-){3}(\d|\w){12}/; + +describe('longform ads using custom adserver translation file', function() { + this.retries(3); + it('process the bids successfully', function() { + browser + .url(protocol + '://' + host + ':9999/integrationExamples/longform/basic_w_custom_adserver_translation.html?pbjs_debug=true') + .pause(10000); + + const loadPrebidBtnXpath = '//*[@id="loadPrebidRequestBtn"]'; + browser.waitForExist(loadPrebidBtnXpath); + $(loadPrebidBtnXpath).click(); + browser.pause(3000); + + const listOfCpmsXpath = '/html/body/div[1]/div/div/div/div[1]/div[2]/div/table/tbody/tr/td[2]'; + const listOfCategoriesXpath = '/html/body/div[1]/div/div/div/div[1]/div[2]/div/table/tbody/tr/td[3]'; + const listOfDurationsXpath = '/html/body/div[1]/div/div/div/div[1]/div[2]/div/table/tbody/tr/td[4]'; + + browser.waitForExist(listOfCpmsXpath); + + let listOfCpms = $$(listOfCpmsXpath); + let listOfCats = $$(listOfCategoriesXpath); + let listOfDuras = $$(listOfDurationsXpath); + + expect(listOfCpms.length).to.equal(listOfCats.length).and.to.equal(listOfDuras.length); + for (let i = 0; i < listOfCpms.length; i++) { + let cpm = listOfCpms[i].getText(); + let cat = listOfCats[i].getText(); + let dura = listOfDuras[i].getText(); + expect(includes(validCpms, cpm), `Could not find CPM ${cpm} in accepted list`).to.equal(true); + expect(includes(validCats, cat), `Could not find Category ${cat} in accepted list`).to.equal(true); + expect(includes(validDurations, dura), `Could not find Duration ${dura} in accepted list`).to.equal(true); + } + }); + + it('formats the targeting keys properly', function () { + const listOfKeyElementsXpath = '/html/body/div[1]/div/div/div/div[2]/div[2]/div/table/tbody/tr/td[1]'; + const listOfKeyValuesXpath = '/html/body/div[1]/div/div/div/div[2]/div[2]/div/table/tbody/tr/td[2]'; + browser.waitForExist(listOfKeyElementsXpath); + browser.waitForExist(listOfKeyValuesXpath); + + let listOfKeyElements = $$(listOfKeyElementsXpath); + let listOfKeyValues = $$(listOfKeyValuesXpath); + + let firstKey = listOfKeyElements[0].getText(); + expect(firstKey).to.equal('hb_pb_cat_dur'); + + let firstKeyValue = listOfKeyValues[0].getText(); + expect(firstKeyValue).match(customKeyRegex); + + let lastKey = listOfKeyElements[listOfKeyElements.length - 1].getText(); + expect(lastKey).to.equal('hb_cache_id'); + + let lastKeyValue = listOfKeyValues[listOfKeyValues.length - 1].getText(); + expect(lastKeyValue).to.match(uuidRegex); + }); +}) diff --git a/test/spec/e2e/longform/basic_w_priceGran.spec.js b/test/spec/e2e/longform/basic_w_priceGran.spec.js new file mode 100644 index 00000000000..696b7fa3359 --- /dev/null +++ b/test/spec/e2e/longform/basic_w_priceGran.spec.js @@ -0,0 +1,68 @@ +const includes = require('core-js/library/fn/array/includes'); +const expect = require('chai').expect; +const testServer = require('../../../helpers/testing-utils'); + +const host = testServer.host; +const protocol = testServer.protocol; + +const validDurations = ['15s', '30s']; +const validCats = ['Food', 'Retail Stores/Chains', 'Pet Food/Supplies', 'Travel/Hotels/Airlines', 'Automotive', 'Health Care Services']; +const validCpms = ['15.00', '14.00', '13.00', '10.00']; +const customKeyRegex = /\d{2}\.\d{2}_\d{1,3}_\d{2}s/; +const uuidRegex = /(\d|\w){8}-((\d|\w){4}-){3}(\d|\w){12}/; + +describe('longform ads not using requireExactDuration field', function() { + this.retries(3); + it('process the bids successfully', function() { + browser + .url(protocol + '://' + host + ':9999/integrationExamples/longform/basic_w_priceGran.html?pbjs_debug=true') + .pause(10000); + + const loadPrebidBtnXpath = '//*[@id="loadPrebidRequestBtn"]'; + browser.waitForExist(loadPrebidBtnXpath); + $(loadPrebidBtnXpath).click(); + browser.pause(3000); + + const listOfCpmsXpath = '/html/body/div[1]/div/div/div/div[1]/div[2]/div/table/tbody/tr/td[2]'; + const listOfCategoriesXpath = '/html/body/div[1]/div/div/div/div[1]/div[2]/div/table/tbody/tr/td[3]'; + const listOfDurationsXpath = '/html/body/div[1]/div/div/div/div[1]/div[2]/div/table/tbody/tr/td[4]'; + + browser.waitForExist(listOfCpmsXpath); + + let listOfCpms = $$(listOfCpmsXpath); + let listOfCats = $$(listOfCategoriesXpath); + let listOfDuras = $$(listOfDurationsXpath); + + expect(listOfCpms.length).to.equal(listOfCats.length).and.to.equal(listOfDuras.length); + for (let i = 0; i < listOfCpms.length; i++) { + let cpm = listOfCpms[i].getText(); + let cat = listOfCats[i].getText(); + let dura = listOfDuras[i].getText(); + expect(includes(validCpms, cpm), `Could not find CPM ${cpm} in accepted list`).to.equal(true); + expect(includes(validCats, cat), `Could not find Category ${cat} in accepted list`).to.equal(true); + expect(includes(validDurations, dura), `Could not find Duration ${dura} in accepted list`).to.equal(true); + } + }); + + it('formats the targeting keys properly', function () { + const listOfKeyElementsXpath = '/html/body/div[1]/div/div/div/div[2]/div[2]/div/table/tbody/tr/td[1]'; + const listOfKeyValuesXpath = '/html/body/div[1]/div/div/div/div[2]/div[2]/div/table/tbody/tr/td[2]'; + browser.waitForExist(listOfKeyElementsXpath); + browser.waitForExist(listOfKeyValuesXpath); + + let listOfKeyElements = $$(listOfKeyElementsXpath); + let listOfKeyValues = $$(listOfKeyValuesXpath); + + let firstKey = listOfKeyElements[0].getText(); + expect(firstKey).to.equal('hb_pb_cat_dur'); + + let firstKeyValue = listOfKeyValues[0].getText(); + expect(firstKeyValue).match(customKeyRegex); + + let lastKey = listOfKeyElements[listOfKeyElements.length - 1].getText(); + expect(lastKey).to.equal('hb_cache_id'); + + let lastKeyValue = listOfKeyValues[listOfKeyValues.length - 1].getText(); + expect(lastKeyValue).to.match(uuidRegex); + }); +}) diff --git a/test/spec/e2e/longform/basic_w_requireExactDuration.spec.js b/test/spec/e2e/longform/basic_w_requireExactDuration.spec.js new file mode 100644 index 00000000000..224ff1cbc34 --- /dev/null +++ b/test/spec/e2e/longform/basic_w_requireExactDuration.spec.js @@ -0,0 +1,68 @@ +const includes = require('core-js/library/fn/array/includes'); +const expect = require('chai').expect; +const testServer = require('../../../helpers/testing-utils'); + +const host = testServer.host; +const protocol = testServer.protocol; + +const validDurations = ['15s', '30s']; +const validCats = ['Food', 'Retail Stores/Chains', 'Pet Food/Supplies', 'Travel/Hotels/Airlines', 'Automotive', 'Health Care Services']; +const validCpms = ['15.00', '14.00', '13.00', '10.00']; +const customKeyRegex = /\d{2}\.\d{2}_\d{1,3}_\d{2}s/; +const uuidRegex = /(\d|\w){8}-((\d|\w){4}-){3}(\d|\w){12}/; + +describe('longform ads using requireExactDuration field', function() { + this.retries(3); + it('process the bids successfully', function() { + browser + .url(protocol + '://' + host + ':9999/integrationExamples/longform/basic_w_requireExactDuration.html?pbjs_debug=true') + .pause(10000); + + const loadPrebidBtnXpath = '//*[@id="loadPrebidRequestBtn"]'; + browser.waitForExist(loadPrebidBtnXpath); + $(loadPrebidBtnXpath).click(); + browser.pause(3000); + + const listOfCpmsXpath = '/html/body/div[1]/div/div/div/div[1]/div[2]/div/table/tbody/tr/td[2]'; + const listOfCategoriesXpath = '/html/body/div[1]/div/div/div/div[1]/div[2]/div/table/tbody/tr/td[3]'; + const listOfDurationsXpath = '/html/body/div[1]/div/div/div/div[1]/div[2]/div/table/tbody/tr/td[4]'; + + browser.waitForExist(listOfCpmsXpath); + + let listOfCpms = $$(listOfCpmsXpath); + let listOfCats = $$(listOfCategoriesXpath); + let listOfDuras = $$(listOfDurationsXpath); + + expect(listOfCpms.length).to.equal(listOfCats.length).and.to.equal(listOfDuras.length); + for (let i = 0; i < listOfCpms.length; i++) { + let cpm = listOfCpms[i].getText(); + let cat = listOfCats[i].getText(); + let dura = listOfDuras[i].getText(); + expect(includes(validCpms, cpm), `Could not find CPM ${cpm} in accepted list`).to.equal(true); + expect(includes(validCats, cat), `Could not find Category ${cat} in accepted list`).to.equal(true); + expect(includes(validDurations, dura), `Could not find Duration ${dura} in accepted list`).to.equal(true); + } + }); + + it('formats the targeting keys properly', function () { + const listOfKeyElementsXpath = '/html/body/div[1]/div/div/div/div[2]/div[2]/div/table/tbody/tr/td[1]'; + const listOfKeyValuesXpath = '/html/body/div[1]/div/div/div/div[2]/div[2]/div/table/tbody/tr/td[2]'; + browser.waitForExist(listOfKeyElementsXpath); + browser.waitForExist(listOfKeyValuesXpath); + + let listOfKeyElements = $$(listOfKeyElementsXpath); + let listOfKeyValues = $$(listOfKeyValuesXpath); + + let firstKey = listOfKeyElements[0].getText(); + expect(firstKey).to.equal('hb_pb_cat_dur'); + + let firstKeyValue = listOfKeyValues[0].getText(); + expect(firstKeyValue).match(customKeyRegex); + + let lastKey = listOfKeyElements[listOfKeyElements.length - 1].getText(); + expect(lastKey).to.equal('hb_cache_id'); + + let lastKeyValue = listOfKeyValues[listOfKeyValues.length - 1].getText(); + expect(lastKeyValue).to.match(uuidRegex); + }); +}) diff --git a/test/spec/e2e/longform/basic_wo_brandCategoryExclusion.spec.js b/test/spec/e2e/longform/basic_wo_brandCategoryExclusion.spec.js new file mode 100644 index 00000000000..95237366d0e --- /dev/null +++ b/test/spec/e2e/longform/basic_wo_brandCategoryExclusion.spec.js @@ -0,0 +1,63 @@ +const includes = require('core-js/library/fn/array/includes'); +const expect = require('chai').expect; +const testServer = require('../../../helpers/testing-utils'); + +const host = testServer.host; +const protocol = testServer.protocol; + +const validDurations = ['15s', '30s']; +const validCpms = ['15.00', '14.00', '13.00', '10.00']; +const customKeyRegex = /\d{2}\.\d{2}_\d{2}s/; +const uuidRegex = /(\d|\w){8}-((\d|\w){4}-){3}(\d|\w){12}/; + +describe('longform ads without using brandCategoryExclusion', function() { + this.retries(3); + it('process the bids successfully', function() { + browser + .url(protocol + '://' + host + ':9999/integrationExamples/longform/basic_wo_brandCategoryExclusion.html?pbjs_debug=true') + .pause(10000); + + const loadPrebidBtnXpath = '//*[@id="loadPrebidRequestBtn"]'; + browser.waitForExist(loadPrebidBtnXpath); + $(loadPrebidBtnXpath).click(); + browser.pause(3000); + + const listOfCpmsXpath = '/html/body/div[1]/div/div/div/div[1]/div[2]/div/table/tbody/tr/td[2]'; + const listOfDurationsXpath = '/html/body/div[1]/div/div/div/div[1]/div[2]/div/table/tbody/tr/td[4]'; + + browser.waitForExist(listOfCpmsXpath); + + let listOfCpms = $$(listOfCpmsXpath); + let listOfDuras = $$(listOfDurationsXpath); + + expect(listOfCpms.length).to.equal(listOfDuras.length); + for (let i = 0; i < listOfCpms.length; i++) { + let cpm = listOfCpms[i].getText(); + let dura = listOfDuras[i].getText(); + expect(includes(validCpms, cpm), `Could not find CPM ${cpm} in accepted list`).to.equal(true); + expect(includes(validDurations, dura), `Could not find Duration ${dura} in accepted list`).to.equal(true); + } + }); + + it('formats the targeting keys properly', function () { + const listOfKeyElementsXpath = '/html/body/div[1]/div/div/div/div[2]/div[2]/div/table/tbody/tr/td[1]'; + const listOfKeyValuesXpath = '/html/body/div[1]/div/div/div/div[2]/div[2]/div/table/tbody/tr/td[2]'; + browser.waitForExist(listOfKeyElementsXpath); + browser.waitForExist(listOfKeyValuesXpath); + + let listOfKeyElements = $$(listOfKeyElementsXpath); + let listOfKeyValues = $$(listOfKeyValuesXpath); + + let firstKey = listOfKeyElements[0].getText(); + expect(firstKey).to.equal('hb_pb_cat_dur'); + + let firstKeyValue = listOfKeyValues[0].getText(); + expect(firstKeyValue).match(customKeyRegex); + + let lastKey = listOfKeyElements[listOfKeyElements.length - 1].getText(); + expect(lastKey).to.equal('hb_cache_id'); + + let lastKeyValue = listOfKeyValues[listOfKeyValues.length - 1].getText(); + expect(lastKeyValue).to.match(uuidRegex); + }); +}) diff --git a/test/spec/e2e/longform/basic_wo_requireExactDuration.spec.js b/test/spec/e2e/longform/basic_wo_requireExactDuration.spec.js new file mode 100644 index 00000000000..6b628067138 --- /dev/null +++ b/test/spec/e2e/longform/basic_wo_requireExactDuration.spec.js @@ -0,0 +1,68 @@ +const includes = require('core-js/library/fn/array/includes'); +const expect = require('chai').expect; +const testServer = require('../../../helpers/testing-utils'); + +const host = testServer.host; +const protocol = testServer.protocol; + +const validDurations = ['15s', '30s']; +const validCats = ['Food', 'Retail Stores/Chains', 'Pet Food/Supplies', 'Travel/Hotels/Airlines', 'Automotive', 'Health Care Services']; +const validCpms = ['15.00', '14.00', '13.00', '10.00']; +const customKeyRegex = /\d{2}\.\d{2}_\d{1,3}_\d{2}s/; +const uuidRegex = /(\d|\w){8}-((\d|\w){4}-){3}(\d|\w){12}/; + +describe('longform ads not using requireExactDuration field', function() { + this.retries(3); + it('process the bids successfully', function() { + browser + .url(protocol + '://' + host + ':9999/integrationExamples/longform/basic_wo_requireExactDuration.html?pbjs_debug=true') + .pause(10000); + + const loadPrebidBtnXpath = '//*[@id="loadPrebidRequestBtn"]'; + browser.waitForExist(loadPrebidBtnXpath); + $(loadPrebidBtnXpath).click(); + browser.pause(3000); + + const listOfCpmsXpath = '/html/body/div[1]/div/div/div/div[1]/div[2]/div/table/tbody/tr/td[2]'; + const listOfCategoriesXpath = '/html/body/div[1]/div/div/div/div[1]/div[2]/div/table/tbody/tr/td[3]'; + const listOfDurationsXpath = '/html/body/div[1]/div/div/div/div[1]/div[2]/div/table/tbody/tr/td[4]'; + + browser.waitForExist(listOfCpmsXpath); + + let listOfCpms = $$(listOfCpmsXpath); + let listOfCats = $$(listOfCategoriesXpath); + let listOfDuras = $$(listOfDurationsXpath); + + expect(listOfCpms.length).to.equal(listOfCats.length).and.to.equal(listOfDuras.length); + for (let i = 0; i < listOfCpms.length; i++) { + let cpm = listOfCpms[i].getText(); + let cat = listOfCats[i].getText(); + let dura = listOfDuras[i].getText(); + expect(includes(validCpms, cpm), `Could not find CPM ${cpm} in accepted list`).to.equal(true); + expect(includes(validCats, cat), `Could not find Category ${cat} in accepted list`).to.equal(true); + expect(includes(validDurations, dura), `Could not find Duration ${dura} in accepted list`).to.equal(true); + } + }); + + it('formats the targeting keys properly', function () { + const listOfKeyElementsXpath = '/html/body/div[1]/div/div/div/div[2]/div[2]/div/table/tbody/tr/td[1]'; + const listOfKeyValuesXpath = '/html/body/div[1]/div/div/div/div[2]/div[2]/div/table/tbody/tr/td[2]'; + browser.waitForExist(listOfKeyElementsXpath); + browser.waitForExist(listOfKeyValuesXpath); + + let listOfKeyElements = $$(listOfKeyElementsXpath); + let listOfKeyValues = $$(listOfKeyValuesXpath); + + let firstKey = listOfKeyElements[0].getText(); + expect(firstKey).to.equal('hb_pb_cat_dur'); + + let firstKeyValue = listOfKeyValues[0].getText(); + expect(firstKeyValue).match(customKeyRegex); + + let lastKey = listOfKeyElements[listOfKeyElements.length - 1].getText(); + expect(lastKey).to.equal('hb_cache_id'); + + let lastKeyValue = listOfKeyValues[listOfKeyValues.length - 1].getText(); + expect(lastKeyValue).to.match(uuidRegex); + }); +}) diff --git a/test/spec/e2e/native/basic_native_ad.spec.js b/test/spec/e2e/native/basic_native_ad.spec.js new file mode 100644 index 00000000000..ed09228b532 --- /dev/null +++ b/test/spec/e2e/native/basic_native_ad.spec.js @@ -0,0 +1,59 @@ +const expect = require('chai').expect; +const { host, protocol } = require('../../../helpers/testing-utils'); + +const TEST_PAGE_URL = `${protocol}://${host}:9999/test/pages/native.html`; +const CREATIVE_IFRAME_CSS_SELECTOR = 'iframe[id="google_ads_iframe_/19968336/prebid_native_example_1_0"]'; + +const EXPECTED_TARGETING_KEYS = { + hb_source: 'client', + hb_source_appnexus: 'client', + hb_pb_appnexus: '10.00', + hb_native_title_appn: 'This is a Prebid Native Creative', + hb_native_linkurl: 'http://prebid.org/dev-docs/show-native-ads.html', + hb_format: 'native', + hb_native_brand: 'Prebid.org', + hb_size: '0x0', + hb_bidder_appnexus: 'appnexus', + hb_native_linkurl_ap: 'http://prebid.org/dev-docs/show-native-ads.html', + hb_native_title: 'This is a Prebid Native Creative', + hb_pb: '10.00', + hb_native_brand_appn: 'Prebid.org', + hb_bidder: 'appnexus', + hb_format_appnexus: 'native', + hb_size_appnexus: '0x0' +} + +describe('Prebid.js Native Ad Unit Test', function () { + before(function loadTestPage() { + browser.url(TEST_PAGE_URL).pause(3000); + try { + browser.waitForExist(CREATIVE_IFRAME_CSS_SELECTOR, 2000); + } catch (e) { + // If creative Iframe didn't load, repeat the steps again! + // Due to some reason if the Ad server doesn't respond, the test case will time out after 60000 ms as defined in file wdio.conf.js + loadTestPage(); + } + }); + + it('should load the targeting keys with correct values', function () { + const result = browser.execute(function () { + return window.pbjs.getAdserverTargeting('/19968336/prebid_native_example_2'); + }); + + const targetingKeys = result.value['/19968336/prebid_native_example_2']; + expect(targetingKeys).to.include(EXPECTED_TARGETING_KEYS); + expect(targetingKeys.hb_adid).to.be.a('string'); + expect(targetingKeys.hb_native_body).to.be.a('string'); + expect(targetingKeys.hb_native_body_appne).to.be.a('string'); + expect(targetingKeys.hb_native_icon).to.be.a('string'); + expect(targetingKeys.hb_native_icon_appne).to.be.a('string'); + expect(targetingKeys.hb_native_image).to.be.a('string'); + expect(targetingKeys.hb_adid_appnexus).to.be.a('string'); + }); + + it('should render the native ad on the page', function () { + const creativeIframe = $(CREATIVE_IFRAME_CSS_SELECTOR).value; + browser.frame(creativeIframe); + expect(browser.isVisible('body > div[class="GoogleActiveViewElement"] > div[class="card"]')).to.be.true; + }); +}); diff --git a/test/spec/e2e/outstream/basic_outstream_video_ad.spec.js b/test/spec/e2e/outstream/basic_outstream_video_ad.spec.js new file mode 100644 index 00000000000..15b0bb29309 --- /dev/null +++ b/test/spec/e2e/outstream/basic_outstream_video_ad.spec.js @@ -0,0 +1,53 @@ +const expect = require('chai').expect; +const { host, protocol } = require('../../../helpers/testing-utils'); + +const TEST_PAGE_URL = `${protocol}://${host}:9999/test/pages/outstream.html`; +const CREATIVE_IFRAME_CSS_SELECTOR = 'div[id="video_ad_unit_1"] > div:nth-child(2) > iframe:nth-child(1)'; + +const EXPECTED_TARGETING_KEYS = { + hb_cache_id: '', + hb_uuid: '', + hb_format: 'video', + hb_source: 'client', + hb_size: '640x480', + hb_pb: '10.00', + hb_bidder: 'appnexus', + hb_format_appnexus: 'video', + hb_source_appnexus: 'client', + hb_size_appnexus: '640x480', + hb_pb_appnexus: '10.00', + hb_bidder_appnexus: 'appnexus' +}; + +describe('Prebid.js Outstream Video Ad Test', function () { + before(function loadTestPage() { + browser + .url(TEST_PAGE_URL) + .scroll(0, 300) + .pause(3000); + try { + browser.waitForExist(CREATIVE_IFRAME_CSS_SELECTOR, 5000); + } catch (e) { + // If creative Iframe didn't load, repeat the steps again! + // Due to some reason if the Ad server doesn't respond, the test case will time out after 60000 ms as defined in file wdio.conf.js + loadTestPage(); + } + }); + + it('should load the targeting keys with correct values', function () { + const result = browser.execute(function () { + return window.pbjs.getAdserverTargeting('video_ad_unit_2'); + }); + + const targetingKeys = result.value['video_ad_unit_2']; + expect(targetingKeys).to.include(EXPECTED_TARGETING_KEYS); + expect(targetingKeys.hb_adid).to.be.a('string'); + expect(targetingKeys.hb_adid_appnexus).to.be.a('string'); + }); + + it('should render the native ad on the page', function() { + const creativeIframe = $(CREATIVE_IFRAME_CSS_SELECTOR).value; + browser.frame(creativeIframe); + expect(browser.isVisible('body > div[class="video-js"] > video')); + }); +}); diff --git a/test/spec/e2e/testcase1/dom-group/allbidders_dom_spec.js b/test/spec/e2e/testcase1/dom-group/allbidders_dom_spec.js deleted file mode 100644 index 5803ad5ceb3..00000000000 --- a/test/spec/e2e/testcase1/dom-group/allbidders_dom_spec.js +++ /dev/null @@ -1,125 +0,0 @@ -// var verify = require('verify'); -var util = require('../../common/utils.js'); - -module.exports = { - 'adequant ad rendering': function (browser) { - browser - .url('http://an.localhost:9999/test/spec/e2e/gpt-examples/all_bidders_instant_load.html') - .waitForElementVisible('body', 5000) - .pause(7000) - .execute(util.findIframeInDiv, ['div-1'], function(result) { - this.verify.equal(result.value, true, 'adequant ad not rendered'); - }); - }, - 'adform ad rendering': function (browser) { - browser - .execute(util.findIframeInDiv, ['div-2'], function(result) { - this.verify.equal(result.value, true, 'adform ad not rendered'); - }); - }, - 'aol ad rendering': function (browser) { - browser - .execute(util.findIframeInDiv, ['div-3'], function(result) { - this.verify.equal(result.value, true, 'aol ad not rendered'); - }); - }, - 'appnexus ad rendering': function (browser) { - browser - .execute(util.findIframeInDiv, ['div-4'], function(result) { - this.verify.equal(result.value, true, 'appnexus ad not rendered'); - }); - }, - 'indexExchange ad rendering': function (browser) { - browser - .execute(util.findIframeInDiv, ['div-5'], function(result) { - this.verify.equal(result.value, true, 'indexExchange ad not rendered'); - }); - }, - 'openx ad rendering': function (browser) { - browser - .execute(util.findIframeInDiv, ['div-6'], function(result) { - this.verify.equal(result.value, true, 'openx ad not rendered'); - }); - }, - 'pubmatic ad rendering': function (browser) { - browser - .execute(util.findIframeInDiv, ['div-7'], function(result) { - this.verify.equal(result.value, true, 'pubmatic ad not rendered'); - }); - }, - 'pulsepoint ad rendering': function (browser) { - browser - .execute(util.findIframeInDiv, ['div-8'], function(result) { - this.verify.equal(result.value, true, 'pulsepoint ad not rendered'); - }); - }, - 'rubicon ad rendering': function (browser) { - browser - .execute(util.findIframeInDiv, ['div-9'], function(result) { - this.verify.equal(result.value, true, 'rubicon ad not rendered'); - }); - }, - 'sonobi ad rendering': function (browser) { - browser - .execute(util.findIframeInDiv, ['div-10'], function(result) { - this.verify.equal(result.value, true, 'sonobi ad not rendered'); - }); - }, - 'sovrn ad rendering': function (browser) { - browser - .execute(util.findIframeInDiv, ['div-11'], function(result) { - this.verify.equal(result.value, true, 'sovrn ad not rendered'); - }); - }, - 'springserve ad rendering': function (browser) { - browser - .execute(util.findIframeInDiv, ['div-12'], function(result) { - this.verify.equal(result.value, true, 'springserve ad not rendered'); - }); - }, - 'triplelift ad rendering': function (browser) { - browser - .execute(util.findIframeInDiv, ['div-13'], function(result) { - this.verify.equal(result.value, true, 'triplelift ad not rendered'); - }); - }, - 'yieldbot ad rendering': function (browser) { - browser - .execute(util.findIframeInDiv, ['div-14'], function(result) { - this.verify.equal(result.value, true, 'yieldbot ad not rendered'); - }); - }, - 'nginad ad rendering': function (browser) { - browser - .execute(util.findIframeInDiv, ['div-15'], function(result) { - this.verify.equal(result.value, true, 'nginad ad not rendered'); - }); - }, - 'brightcom ad rendering': function (browser) { - browser - .execute(util.findIframeInDiv, ['div-16'], function(result) { - this.verify.equal(result.value, true, 'brightcom ad not rendered'); - }); - }, - 'sekindo ad rendering': function (browser) { - browser - .execute(util.findIframeInDiv, ['div-17'], function(result) { - this.verify.equal(result.value, true, 'sekindo ad not rendered'); - }); - }, - 'kruxlink ad rendering': function (browser) { - browser - .execute(util.findIframeInDiv, ['div-18'], function(result) { - this.verify.equal(result.value, true, 'kruxlink ad not rendered'); - }); - }, - 'AdMedia ad rendering': function (browser) { - browser - .execute(util.findIframeInDiv, ['div-19'], function(result) { - this.verify.equal(result.value, true, 'AdMedia ad not rendered'); - }); - }, - after: function(browser) { - browser.end(); - } -}; diff --git a/test/spec/e2e/testcase1/dom-group/dom_spec.js b/test/spec/e2e/testcase1/dom-group/dom_spec.js deleted file mode 100644 index a58030c32b7..00000000000 --- a/test/spec/e2e/testcase1/dom-group/dom_spec.js +++ /dev/null @@ -1,51 +0,0 @@ -// var assert = require('assert'); - -module.exports = { - - 'Test rendering ad div-2': function (browser) { - var checkAdRendering2 = function() { - var div = document.getElementById('div-2'); - var iframes = div.getElementsByTagName('iframe'); - try { - if (iframes.length == 1 && iframes[0].contentWindow.document.body.innerHTML == '') { - return false; - } else { - return true; - } - } catch (e) { - return true; - } - } - - browser - .url('http://an.localhost:9999/test/spec/e2e/gpt-examples/e2e_default.html') - .waitForElementVisible('body', 3000) - .pause(3000) - .execute(checkAdRendering2, [], function(result) { - this.assert.equal(result.value, true, 'Ad of div-2 not rendered'); - }); - }, - 'Test rendering ad div-1': function (browser) { - var checkAdRendering = function() { - var div = document.getElementById('div-1'); - var iframes = div.getElementsByTagName('iframe'); - try { - if (iframes.length == 1 && iframes[0].contentWindow.document.body.innerHTML == '') { - return false; - } else { - return true; - } - } catch (e) { - return true; - } - } - - browser - .execute(checkAdRendering, [], function(result) { - this.assert.equal(result.value, true, 'Ad of div-1 not rendered'); - }); - }, - after: function(browser) { - browser.end(); - } -}; diff --git a/test/spec/e2e/testcase1/pbjsapi-group/adservertargeting_spec.js b/test/spec/e2e/testcase1/pbjsapi-group/adservertargeting_spec.js deleted file mode 100644 index 796707641cd..00000000000 --- a/test/spec/e2e/testcase1/pbjsapi-group/adservertargeting_spec.js +++ /dev/null @@ -1,62 +0,0 @@ -var assert = require('assert'); -var utils = require('util'); - -module.exports = { - 'AdserverTargeting Test Case 1': function (browser) { - browser - .url('http://localhost:9999/test/spec/e2e/gpt-examples/gpt_default.html') - .waitForElementVisible('body', 3000) - .pause(3000) - .execute(function() { - if (typeof window.pbjs.bidderSettings === 'undefined') { - var pbjsBidderSettingsObject = [ - 'hb_bidder', - 'hb_adid', - 'hb_pb', - 'hb_size' - ]; - } else { - var pbjsBidderSettings = window.pbjs.bidderSettings; - var pbjsBidderSettingsObject = {}; - Object.keys(pbjsBidderSettings).forEach(function (prop) { - // if(prop == 'standard') return; - var value = pbjsBidderSettings[prop]; - var bs = value.adserverTargeting.map(function(item) { - return item.key; - }); - pbjsBidderSettings.standard.adserverTargeting.map(function(value) { - if (bs.indexOf(value.key) == -1) { - bs.push(value.key) - } - }); - pbjsBidderSettingsObject[prop] = bs; - }); - } - - var adserverTargetingObject = {}; - var adserverTargeting = window.pbjs.getAdserverTargeting(); - Object.keys(adserverTargeting).forEach(function(value) { - if (Object.keys(adserverTargeting[value]).length == 0) return; - adserverTargetingObject[adserverTargeting[value].hb_bidder] = Object.keys(adserverTargeting[value]) - }); - - return [pbjsBidderSettingsObject, adserverTargetingObject]; - }, [], function(result) { - Object.keys(result.value[1]).forEach(function(key) { - if (utils.isArray(result.value[0])) { - assert.deepEqual(result.value[0].sort(), result.value[1][key].sort()); - } else { - if (result.value[0].hasOwnProperty(key)) { - var obj1 = result.value[0][key].sort(); - } else { - var obj1 = result.value[0]['standard'].sort(); - } - assert.deepEqual(obj1, result.value[1][key].sort()); - } - }); - }); - }, - after: function(browser) { - browser.end(); - } -}; diff --git a/test/spec/e2e/testcase1/pbjsapi-group/getbidresponses_spec.js b/test/spec/e2e/testcase1/pbjsapi-group/getbidresponses_spec.js deleted file mode 100644 index c7921709c0f..00000000000 --- a/test/spec/e2e/testcase1/pbjsapi-group/getbidresponses_spec.js +++ /dev/null @@ -1,34 +0,0 @@ -// var assert = require('assert'); -var assert = require('chai').assert; -var utils = require('util'); - -module.exports = { - 'bidReceived not empty': function(browser) { - browser - .url('http://localhost:9999/test/spec/e2e/gpt-examples/gpt_default.html') - .waitForElementVisible('body', 3000) - .pause(5000) - .execute(function() { - return window.pbjs._bidsReceived.length; - }, [], function(result) { - // browser.assert.first(false, 'Bid response empty'); - assert.isOk(result.value, 'Bid response empty'); - }); - }, - 'check keys': function(browser) { - browser - .execute(function() { - return window.pbjs._bidsReceived; - }, [], function(result) { - // minimum expected keys in bid received - var expected = ['bidderCode', 'width', 'height', 'adId', 'cpm', 'requestId', 'bidder', 'adUnitCode', 'timeToRespond']; - Object.keys(result.value).forEach(function(key) { - var compare = Object.keys(result.value[key]); - assert.includeMembers(compare, expected, 'include members'); - }); - }); - }, - after: function(browser) { - browser.end(); - } -}; diff --git a/test/spec/hook_spec.js b/test/spec/hook_spec.js deleted file mode 100644 index 7536f8316d5..00000000000 --- a/test/spec/hook_spec.js +++ /dev/null @@ -1,151 +0,0 @@ - -import { expect } from 'chai'; -import { createHook, hooks } from 'src/hook'; - -describe('the hook module', function () { - let sandbox; - - beforeEach(function () { - sandbox = sinon.sandbox.create(); - }); - - afterEach(function () { - sandbox.restore(); - }); - - it('should call all sync hooks attached to a function', function () { - let called = []; - let calledWith; - - let testFn = () => { - called.push(testFn); - }; - let testHook = (...args) => { - called.push(testHook); - calledWith = args; - }; - let testHook2 = () => { - called.push(testHook2); - }; - let testHook3 = () => { - called.push(testHook3); - }; - - let hookedTestFn = createHook('sync', testFn, 'testHook'); - - hookedTestFn.addHook(testHook, 50); - hookedTestFn.addHook(testHook2, 100); - - // make sure global test hooks work as well (with default priority) - hooks['testHook'].addHook(testHook3); - - hookedTestFn(1, 2, 3); - - expect(called).to.deep.equal([ - testHook2, - testHook, - testHook3, - testFn - ]); - - expect(calledWith).to.deep.equal([1, 2, 3]); - - called = []; - - hookedTestFn.removeHook(testHook); - hooks['testHook'].removeHook(testHook3); - - hookedTestFn(1, 2, 3); - - expect(called).to.deep.equal([ - testHook2, - testFn - ]); - }); - - it('should allow context to be passed to hooks, but keep bound contexts', function () { - let context; - let fn = function() { - context = this; - }; - - let boundContext = {}; - let calledBoundContext; - let hook = function() { - calledBoundContext = this; - }.bind(boundContext); - - let hookFn = createHook('sync', fn); - hookFn.addHook(hook); - - let newContext = {}; - hookFn.bind(newContext)(); - - expect(context).to.equal(newContext); - expect(calledBoundContext).to.equal(boundContext); - }); - - describe('asyncSeries', function () { - it('should call function as normal if no hooks attached', function () { - let fn = sandbox.spy(); - let hookFn = createHook('asyncSeries', fn); - - hookFn(1); - - expect(fn.calledOnce).to.equal(true); - expect(fn.firstCall.args[0]).to.equal(1); - }); - - it('should call hooks correctly applied in asyncSeries', function () { - let called = []; - - let testFn = (called) => { - called.push(testFn); - }; - let testHook = (called, next) => { - called.push(testHook); - next(called); - }; - let testHook2 = (called, next) => { - called.push(testHook2); - next(called); - }; - - let hookedTestFn = createHook('asyncSeries', testFn); - hookedTestFn.addHook(testHook); - hookedTestFn.addHook(testHook2); - - hookedTestFn(called); - - expect(called).to.deep.equal([ - testHook, - testHook2, - testFn - ]); - }); - - it('should allow context to be passed to hooks, but keep bound contexts', function () { - let context; - let fn = function() { - context = this; - }; - - let boundContext1 = {}; - let calledBoundContext1; - let hook1 = function(next) { - calledBoundContext1 = this; - next() - }.bind(boundContext1); - - let hookFn = createHook('asyncSeries', fn); - hookFn.addHook(hook1); - - let newContext = {}; - hookFn = hookFn.bind(newContext); - hookFn(); - - expect(context).to.equal(newContext); - expect(calledBoundContext1).to.equal(boundContext1); - }); - }); -}); diff --git a/test/spec/marfeelTools_spec.js b/test/spec/marfeelTools_spec.js new file mode 100644 index 00000000000..6681bd7b73d --- /dev/null +++ b/test/spec/marfeelTools_spec.js @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2020 by Marfeel Solutions (http://www.marfeel.com) + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Marfeel Solutions S.L and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to Marfeel Solutions S.L and its + * suppliers and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Marfeel Solutions SL. + */ + +import { isBidSizeAllowed, getAllowedSizes, isBidAllowed } from './marfeelTools'; +import { auctionManager } from './auctionManager'; + +describe('marfeelTools', function () { + describe('isBidSizeAllowed', function() { + it('filter bids within the proper sizes', function () { + const bidsToFilter = [{ + height: 100, + width: 200 + }, { + height: 200, + width: 300 + }, { + height: 100, + width: 50 + }, { + height: 200, + width: 200 + }]; + const sizesToFilter = [[100, 200], [200, 200]]; + const bidsFiltered = bidsToFilter.filter(bid => isBidSizeAllowed(bid, sizesToFilter)); + + assert.deepEqual(bidsFiltered, [{ + height: 100, + width: 200 + }, { + height: 200, + width: 200 + }]) + }); + }); + describe('getAllowedSizes', function() { + it('adds 1x1 to allowed sizes if 300x250 is present', function() { + const currentSizes = [[100, 100], [300, 150], [300, 250]]; + const fakeGetAdUnits = () => [{ + mediaTypes: { + banner: { + sizes: currentSizes + } + } + }]; + sinon.replace(auctionManager, 'getAdUnits', fakeGetAdUnits); + const expectedSizes = [[100, 100], [300, 150], [300, 250], [1, 1]]; + + assert.deepEqual(getAllowedSizes(), expectedSizes); + }); + + it('does not add 1x1 to allowed sizes if 300x250 is not present', function() { + const currentSizes = [[100, 100], [300, 150]]; + const fakeGetAdUnits = () => [{ + mediaTypes: { + banner: { + sizes: currentSizes + } + } + }]; + sinon.replace(auctionManager, 'getAdUnits', fakeGetAdUnits); + const expectedSizes = [[100, 100], [300, 150]]; + + assert.deepEqual(getAllowedSizes(), expectedSizes); + }); + }); + describe('isBidAllowed', function() { + it('returns false with disallowed Bid', function() { + const disAllowedBid = { + adserverTargeting: + { + hb_bidder: 'teads', + hb_cached: true + } + }; + + assert.deepEqual(isBidAllowed(disAllowedBid), false); + }); + + it('returns true with allowed Bid', function() { + const allowedBid1 = { + adserverTargeting: + { + hb_bidder: 'teads', + hb_cached: false + } + }; + + const allowedBid2 = { + adserverTargeting: + { + hb_bidder: 'rubicon', + hb_cached: true + } + }; + assert.deepEqual(isBidAllowed(allowedBid1), true); + assert.deepEqual(isBidAllowed(allowedBid2), true); + }); + }); +}); diff --git a/test/spec/modules/33acrossBidAdapter_spec.js b/test/spec/modules/33acrossBidAdapter_spec.js index 2779209c1cf..e98b4600d90 100644 --- a/test/spec/modules/33acrossBidAdapter_spec.js +++ b/test/spec/modules/33acrossBidAdapter_spec.js @@ -1,23 +1,170 @@ -const { userSync } = require('../../../src/userSync'); -const { config } = require('../../../src/config'); +import { expect } from 'chai'; -const { expect } = require('chai'); -const { - isBidRequestValid, - buildRequests, - interpretResponse, - getUserSyncs -} = require('../../../modules/33acrossBidAdapter'); +import * as utils from 'src/utils'; +import { config } from 'src/config'; + +import { spec } from 'modules/33acrossBidAdapter'; describe('33acrossBidAdapter:', function () { const BIDDER_CODE = '33across'; const SITE_ID = 'pub1234'; const PRODUCT_ID = 'product1'; const END_POINT = 'https://ssc.33across.com/api/v1/hb'; - const SYNC_ENDPOINT = 'https://de.tynt.com/deb/v2?m=xch&rt=html'; + + let element, win; + let bidRequests; + let sandbox; + + function TtxRequestBuilder() { + const ttxRequest = { + imp: [{ + banner: { + format: [ + { + w: 300, + h: 250, + ext: {} + }, + { + w: 728, + h: 90, + ext: {} + } + ], + ext: { + ttx: { + viewability: { + amount: 100 + } + } + } + }, + ext: { + ttx: { + prod: PRODUCT_ID + } + } + }], + site: { + id: SITE_ID + }, + id: 'b1', + user: { + ext: { + consent: undefined + } + }, + regs: { + ext: { + gdpr: 0 + } + }, + ext: { + ttx: { + prebidStartedAt: 1, + caller: [{ + 'name': 'prebidjs', + 'version': '$prebid.version$' + }] + } + } + }; + + this.withSizes = sizes => { + Object.assign(ttxRequest.imp[0].banner, { format: sizes }); + return this; + }; + + this.withViewabiliuty = viewability => { + Object.assign(ttxRequest.imp[0].banner, { + ext: { + ttx: { viewability } + } + }); + return this; + }; + + this.withGdprConsent = (consent, gdpr) => { + Object.assign(ttxRequest, { + user: { + ext: { consent } + } + }); + Object.assign(ttxRequest, { + regs: { + ext: { gdpr } + } + }); + return this; + }; + + this.withSite = site => { + Object.assign(ttxRequest, { site }); + return this; + }; + + this.build = () => ttxRequest; + } + + function ServerRequestBuilder() { + const serverRequest = { + 'method': 'POST', + 'url': END_POINT, + 'data': null, + 'options': { + 'contentType': 'text/plain', + 'withCredentials': true + } + }; + + this.withData = data => { + serverRequest['data'] = JSON.stringify(data); + return this; + }; + + this.withUrl = url => { + serverRequest['url'] = url; + return this; + }; + + this.withOptions = options => { + serverRequest['options'] = options; + return this; + }; + + this.build = () => serverRequest; + } beforeEach(function() { - this.bidRequests = [ + element = { + x: 0, + y: 0, + + width: 0, + height: 0, + + getBoundingClientRect: () => { + return { + width: element.width, + height: element.height, + + left: element.x, + top: element.y, + right: element.x + element.width, + bottom: element.y + element.height + }; + } + }; + win = { + document: { + visibilityState: 'visible' + }, + + innerWidth: 800, + innerHeight: 600 + }; + + bidRequests = [ { bidId: 'b1', bidder: '33across', @@ -29,21 +176,25 @@ describe('33acrossBidAdapter:', function () { adUnitCode: 'div-id', auctionId: 'r1', sizes: [ - [ 300, 250 ], - [ 728, 90 ] + [300, 250], + [728, 90] ], transactionId: 't1' } ]; - this.sandbox = sinon.sandbox.create(); + + sandbox = sinon.sandbox.create(); + sandbox.stub(Date, 'now').returns(1); + sandbox.stub(document, 'getElementById').withArgs('div-id').returns(element); + sandbox.stub(utils, 'getWindowTop').returns(win); + sandbox.stub(utils, 'getWindowSelf').returns(win); }); afterEach(function() { - this.sandbox.restore(); - delete this.bidRequests; + sandbox.restore(); }); - describe('isBidRequestValid:', function () { + describe('isBidRequestValid:', function() { it('returns true when valid bid request is sent', function() { const validBid = { bidder: BIDDER_CODE, @@ -51,9 +202,9 @@ describe('33acrossBidAdapter:', function () { siteId: SITE_ID, productId: PRODUCT_ID } - } + }; - expect(isBidRequestValid(validBid)).to.be.true; + expect(spec.isBidRequestValid(validBid)).to.be.true; }); it('returns true when valid test bid request is sent', function() { @@ -64,29 +215,29 @@ describe('33acrossBidAdapter:', function () { productId: PRODUCT_ID, test: 1 } - } + }; - expect(isBidRequestValid(validBid)).to.be.true; + expect(spec.isBidRequestValid(validBid)).to.be.true; }); - it('returns false when bidder not set to "33across"', function () { + it('returns false when bidder not set to "33across"', function() { const invalidBid = { bidder: 'foo', params: { siteId: SITE_ID, productId: PRODUCT_ID } - } + }; - expect(isBidRequestValid(invalidBid)).to.be.false; + expect(spec.isBidRequestValid(invalidBid)).to.be.false; }); it('returns false when params not set', function() { const invalidBid = { bidder: 'foo' - } + }; - expect(isBidRequestValid(invalidBid)).to.be.false; + expect(spec.isBidRequestValid(invalidBid)).to.be.false; }); it('returns false when site ID is not set in params', function() { @@ -95,9 +246,9 @@ describe('33acrossBidAdapter:', function () { params: { productId: PRODUCT_ID } - } + }; - expect(isBidRequestValid(invalidBid)).to.be.false; + expect(spec.isBidRequestValid(invalidBid)).to.be.false; }); it('returns false when product ID not set in params', function() { @@ -106,16 +257,119 @@ describe('33acrossBidAdapter:', function () { params: { siteId: SITE_ID } - } + }; - expect(isBidRequestValid(invalidBid)).to.be.false; + expect(spec.isBidRequestValid(invalidBid)).to.be.false; }); }); describe('buildRequests:', function() { + context('when element is fully in view', function() { + it('returns 100', function() { + const ttxRequest = new TtxRequestBuilder() + .withViewabiliuty({amount: 100}) + .build(); + const serverRequest = new ServerRequestBuilder() + .withData(ttxRequest) + .build(); + + Object.assign(element, { width: 600, height: 400 }); + + expect(spec.buildRequests(bidRequests)).to.deep.equal([ serverRequest ]); + }); + }); + + context('when element is out of view', function() { + it('returns 0', function() { + const ttxRequest = new TtxRequestBuilder() + .withViewabiliuty({amount: 0}) + .build(); + const serverRequest = new ServerRequestBuilder() + .withData(ttxRequest) + .build(); + + Object.assign(element, { x: -300, y: 0, width: 207, height: 320 }); + + expect(spec.buildRequests(bidRequests)).to.deep.equal([ serverRequest ]); + }); + }); + + context('when element is partially in view', function() { + it('returns percentage', function() { + const ttxRequest = new TtxRequestBuilder() + .withViewabiliuty({amount: 75}) + .build(); + const serverRequest = new ServerRequestBuilder() + .withData(ttxRequest) + .build(); + + Object.assign(element, { width: 800, height: 800 }); + + expect(spec.buildRequests(bidRequests)).to.deep.equal([ serverRequest ]); + }); + }); + + context('when width or height of the element is zero', function() { + it('try to use alternative values', function() { + const ttxRequest = new TtxRequestBuilder() + .withSizes([{ w: 800, h: 2400, ext: {} }]) + .withViewabiliuty({amount: 25}) + .build(); + const serverRequest = new ServerRequestBuilder() + .withData(ttxRequest) + .build(); + + Object.assign(element, { width: 0, height: 0 }); + bidRequests[0].sizes = [[800, 2400]]; + + expect(spec.buildRequests(bidRequests)).to.deep.equal([ serverRequest ]); + }); + }); + + context('when nested iframes', function() { + it('returns \'nm\'', function() { + const ttxRequest = new TtxRequestBuilder() + .withViewabiliuty({amount: spec.NON_MEASURABLE}) + .build(); + const serverRequest = new ServerRequestBuilder() + .withData(ttxRequest) + .build(); + + Object.assign(element, { width: 600, height: 400 }); + + utils.getWindowTop.restore(); + utils.getWindowSelf.restore(); + sandbox.stub(utils, 'getWindowTop').returns({}); + sandbox.stub(utils, 'getWindowSelf').returns(win); + + expect(spec.buildRequests(bidRequests)).to.deep.equal([ serverRequest ]); + }); + }); + + context('when tab is inactive', function() { + it('returns 0', function() { + const ttxRequest = new TtxRequestBuilder() + .withViewabiliuty({amount: 0}) + .build(); + const serverRequest = new ServerRequestBuilder() + .withData(ttxRequest) + .build(); + + Object.assign(element, { width: 600, height: 400 }); + + utils.getWindowTop.restore(); + win.document.visibilityState = 'hidden'; + sandbox.stub(utils, 'getWindowTop').returns(win); + + expect(spec.buildRequests(bidRequests)).to.deep.equal([ serverRequest ]); + }); + }); + context('when gdpr consent data exists', function() { + let bidderRequest; + beforeEach(function() { - this.bidderRequest = { + bidderRequest = { gdprConsent: { consentString: 'foobarMyPreference', gdprApplies: true @@ -124,284 +378,93 @@ describe('33acrossBidAdapter:', function () { }); it('returns corresponding server requests with gdpr consent data', function() { - const ttxRequest = { - imp: [ { - banner: { - format: [ - { - w: 300, - h: 250, - ext: {} - }, - { - w: 728, - h: 90, - ext: {} - } - ] - }, - ext: { - ttx: { - prod: PRODUCT_ID - } - } - } ], - site: { - id: SITE_ID - }, - id: 'b1', - user: { - ext: { - consent: 'foobarMyPreference' - } - }, - regs: { - ext: { - gdpr: 1 - } - } - }; - - const serverRequest = { - 'method': 'POST', - 'url': END_POINT, - 'data': JSON.stringify(ttxRequest), - 'options': { - 'contentType': 'text/plain', - 'withCredentials': true - } - } - const builtServerRequests = buildRequests(this.bidRequests, this.bidderRequest); - expect(builtServerRequests).to.deep.equal([ serverRequest ]); - expect(builtServerRequests.length).to.equal(1); + const ttxRequest = new TtxRequestBuilder() + .withGdprConsent('foobarMyPreference', 1) + .build(); + const serverRequest = new ServerRequestBuilder() + .withData(ttxRequest) + .build(); + const builtServerRequests = spec.buildRequests(bidRequests, bidderRequest); + + expect(builtServerRequests).to.deep.equal([serverRequest]); }); it('returns corresponding test server requests with gdpr consent data', function() { - this.sandbox.stub(config, 'getConfig').callsFake(() => { + sandbox.stub(config, 'getConfig').callsFake(() => { return { 'url': 'https://foo.com/hb/' } }); - const ttxRequest = { - imp: [ { - banner: { - format: [ - { - w: 300, - h: 250, - ext: { } - }, - { - w: 728, - h: 90, - ext: { } - } - ] - }, - ext: { - ttx: { - prod: PRODUCT_ID - } - } - } ], - site: { - id: SITE_ID - }, - id: 'b1', - user: { - ext: { - consent: 'foobarMyPreference' - } - }, - regs: { - ext: { - gdpr: 1 - } - } - }; - const serverRequest = { - method: 'POST', - url: 'https://foo.com/hb/', - data: JSON.stringify(ttxRequest), - options: { - contentType: 'text/plain', - withCredentials: true - } - }; + const ttxRequest = new TtxRequestBuilder() + .withGdprConsent('foobarMyPreference', 1) + .build(); + const serverRequest = new ServerRequestBuilder() + .withData(ttxRequest) + .withUrl('https://foo.com/hb/') + .build(); + const builtServerRequests = spec.buildRequests(bidRequests, bidderRequest); - const builtServerRequests = buildRequests(this.bidRequests, this.bidderRequest); - expect(builtServerRequests).to.deep.equal([ serverRequest ]); - expect(builtServerRequests.length).to.equal(1); + expect(builtServerRequests).to.deep.equal([serverRequest]); }); - - afterEach(function() { - delete this.bidderRequest; - }) }); context('when gdpr consent data does not exist', function() { + let bidderRequest; + beforeEach(function() { - this.bidderRequest = { } + bidderRequest = {}; }); it('returns corresponding server requests with default gdpr consent data', function() { - const ttxRequest = { - imp: [ { - banner: { - format: [ - { - w: 300, - h: 250, - ext: {} - }, - { - w: 728, - h: 90, - ext: {} - } - ] - }, - ext: { - ttx: { - prod: PRODUCT_ID - } - } - } ], - site: { - id: SITE_ID - }, - id: 'b1', - user: { - ext: { - consent: undefined - } - }, - regs: { - ext: { - gdpr: 0 - } - } - }; - - const serverRequest = { - 'method': 'POST', - 'url': END_POINT, - 'data': JSON.stringify(ttxRequest), - 'options': { - 'contentType': 'text/plain', - 'withCredentials': true - } - } - const builtServerRequests = buildRequests(this.bidRequests, this.bidderRequest); - expect(builtServerRequests).to.deep.equal([ serverRequest ]); - expect(builtServerRequests.length).to.equal(1); + const ttxRequest = new TtxRequestBuilder() + .build(); + const serverRequest = new ServerRequestBuilder() + .withData(ttxRequest) + .build(); + const builtServerRequests = spec.buildRequests(bidRequests, bidderRequest); + + expect(builtServerRequests).to.deep.equal([serverRequest]); }); it('returns corresponding test server requests with default gdpr consent data', function() { - this.sandbox.stub(config, 'getConfig').callsFake(() => { + sandbox.stub(config, 'getConfig').callsFake(() => { return { 'url': 'https://foo.com/hb/' } }); - const ttxRequest = { - imp: [ { - banner: { - format: [ - { - w: 300, - h: 250, - ext: { } - }, - { - w: 728, - h: 90, - ext: { } - } - ] - }, - ext: { - ttx: { - prod: PRODUCT_ID - } - } - } ], - site: { - id: SITE_ID - }, - id: 'b1', - user: { - ext: { - consent: undefined - } - }, - regs: { - ext: { - gdpr: 0 - } - } - }; - const serverRequest = { - method: 'POST', - url: 'https://foo.com/hb/', - data: JSON.stringify(ttxRequest), - options: { - contentType: 'text/plain', - withCredentials: true - } - }; + const ttxRequest = new TtxRequestBuilder() + .build(); + const serverRequest = new ServerRequestBuilder() + .withData(ttxRequest) + .withUrl('https://foo.com/hb/') + .build(); + const builtServerRequests = spec.buildRequests(bidRequests, bidderRequest); - const builtServerRequests = buildRequests(this.bidRequests, this.bidderRequest); - expect(builtServerRequests).to.deep.equal([ serverRequest ]); - expect(builtServerRequests.length).to.equal(1); + expect(builtServerRequests).to.deep.equal([serverRequest]); }); - - afterEach(function() { - delete this.bidderRequest; - }) }); }); describe('interpretResponse', function() { + let ttxRequest, serverRequest; + beforeEach(function() { - this.ttxRequest = { - imp: [ { - banner: { - format: [ - { - w: 300, - h: 250, - ext: {} - }, - { - w: 728, - h: 90, - ext: {} - } - ] - }, - ext: { - ttx: { - prod: PRODUCT_ID - } - } - } ], - site: { + ttxRequest = new TtxRequestBuilder() + .withSite({ id: SITE_ID, page: 'http://test-url.com' - }, - id: 'b1' - }; - this.serverRequest = { - method: 'POST', - url: '//staging-ssc.33across.com/api/v1/hb', - data: JSON.stringify(this.ttxRequest), - options: { + }) + .build(); + serverRequest = new ServerRequestBuilder() + .withUrl('//staging-ssc.33across.com/api/v1/hb') + .withData(ttxRequest) + .withOptions({ contentType: 'text/plain', withCredentials: false - } - }; + }) + .build(); }); context('when exactly one bid is returned', function() { @@ -412,18 +475,17 @@ describe('33acrossBidAdapter:', function () { id: 'b1', seatbid: [ { - bid: [ { + bid: [{ id: '1', adm: '

    I am an ad

    ', crid: 1, h: 250, w: 300, price: 0.0938 - } ] + }] } ] }; - const bidResponse = { requestId: 'b1', bidderCode: BIDDER_CODE, @@ -435,9 +497,9 @@ describe('33acrossBidAdapter:', function () { creativeId: 1, currency: 'USD', netRevenue: true - } + }; - expect(interpretResponse({ body: serverResponse }, this.serverRequest)).to.deep.equal([ bidResponse ]); + expect(spec.interpretResponse({ body: serverResponse }, serverRequest)).to.deep.equal([bidResponse]); }); }); @@ -450,7 +512,7 @@ describe('33acrossBidAdapter:', function () { seatbid: [] }; - expect(interpretResponse({ body: serverResponse }, this.serverRequest)).to.deep.equal([]); + expect(spec.interpretResponse({ body: serverResponse }, serverRequest)).to.deep.equal([]); }); }); @@ -462,7 +524,7 @@ describe('33acrossBidAdapter:', function () { id: 'b1', seatbid: [ { - bid: [ { + bid: [{ id: '1', adm: '

    I am an ad

    ', crid: 1, @@ -481,18 +543,17 @@ describe('33acrossBidAdapter:', function () { ] }, { - bid: [ { + bid: [{ id: '3', adm: '

    I am an ad

    ', crid: 3, h: 250, w: 300, price: 0.0938 - } ] + }] } ] }; - const bidResponse = { requestId: 'b1', bidderCode: BIDDER_CODE, @@ -506,14 +567,16 @@ describe('33acrossBidAdapter:', function () { netRevenue: true }; - expect(interpretResponse({ body: serverResponse }, this.serverRequest)).to.deep.equal([ bidResponse ]); + expect(spec.interpretResponse({ body: serverResponse }, serverRequest)).to.deep.equal([bidResponse]); }); }); }); describe('getUserSyncs', function() { + let syncs; + beforeEach(function() { - this.syncs = [ + syncs = [ { type: 'iframe', url: 'https://de.tynt.com/deb/v2?m=xch&rt=html&id=id1' @@ -523,7 +586,7 @@ describe('33acrossBidAdapter:', function () { url: 'https://de.tynt.com/deb/v2?m=xch&rt=html&id=id2' }, ]; - this.bidRequests = [ + bidRequests = [ { bidId: 'b1', bidder: '33across', @@ -535,7 +598,7 @@ describe('33acrossBidAdapter:', function () { adUnitCode: 'div-id', auctionId: 'r1', sizes: [ - [ 300, 250 ] + [300, 250] ], transactionId: 't1' }, @@ -550,70 +613,147 @@ describe('33acrossBidAdapter:', function () { adUnitCode: 'div-id', auctionId: 'r1', sizes: [ - [ 300, 250 ] + [300, 250] ], transactionId: 't2' } ]; }); - context('when gdpr does not apply', function() { + context('when iframe is not enabled', function() { + it('returns empty sync array', function() { + const syncOptions = {}; + + spec.buildRequests(bidRequests); + + expect(spec.getUserSyncs(syncOptions)).to.deep.equal([]); + }); + }); + + context('when iframe is enabled', function() { + let syncOptions; beforeEach(function() { - this.gdprConsent = { - gdprApplies: false - } + syncOptions = { + iframeEnabled: true + }; }); - context('when iframe is not enabled', function() { - it('returns empty sync array', function() { - const syncOptions = {}; - buildRequests(this.bidRequests); - expect(getUserSyncs(syncOptions, {}, this.gdprConsent)).to.deep.equal([]); + context('when there is no gdpr consent data', function() { + it('returns sync urls with undefined consent string as param', function() { + spec.buildRequests(bidRequests); + + const syncResults = spec.getUserSyncs(syncOptions, {}, undefined); + const expectedSyncs = [ + { + type: 'iframe', + url: `${syncs[0].url}&gdpr_consent=undefined` + }, + { + type: 'iframe', + url: `${syncs[1].url}&gdpr_consent=undefined` + } + ] + + expect(syncResults).to.deep.equal(expectedSyncs); + }) + }); + + context('when gdpr applies but there is no consent string', function() { + it('returns sync urls with undefined consent string as param and gdpr=1', function() { + spec.buildRequests(bidRequests); + + const syncResults = spec.getUserSyncs(syncOptions, {}, {gdprApplies: true}); + const expectedSyncs = [ + { + type: 'iframe', + url: `${syncs[0].url}&gdpr_consent=undefined&gdpr=1` + }, + { + type: 'iframe', + url: `${syncs[1].url}&gdpr_consent=undefined&gdpr=1` + } + ]; + + expect(syncResults).to.deep.equal(expectedSyncs); }); }); - context('when iframe is enabled', function() { - it('returns sync array equal to number of unique siteIDs', function() { - const syncOptions = { - iframeEnabled: true - }; - buildRequests(this.bidRequests); - const syncs = getUserSyncs(syncOptions, {}, this.gdprConsent); - expect(syncs).to.deep.equal(this.syncs); + context('when gdpr applies and there is consent string', function() { + it('returns sync urls with gdpr_consent=consent string as param and gdpr=1', function() { + spec.buildRequests(bidRequests); + + const syncResults = spec.getUserSyncs(syncOptions, {}, {gdprApplies: true, consentString: 'consent123A'}); + const expectedSyncs = [ + { + type: 'iframe', + url: `${syncs[0].url}&gdpr_consent=consent123A&gdpr=1` + }, + { + type: 'iframe', + url: `${syncs[1].url}&gdpr_consent=consent123A&gdpr=1` + } + ]; + + expect(syncResults).to.deep.equal(expectedSyncs); }); }); - }); - context('when consent data is not defined', function() { - context('when iframe is not enabled', function() { - it('returns empty sync array', function() { - const syncOptions = {}; - buildRequests(this.bidRequests); - expect(getUserSyncs(syncOptions)).to.deep.equal([]); + context('when gdpr does not apply and there is no consent string', function() { + it('returns sync urls with undefined consent string as param and gdpr=0', function() { + spec.buildRequests(bidRequests); + + const syncResults = spec.getUserSyncs(syncOptions, {}, {gdprApplies: false}); + const expectedSyncs = [ + { + type: 'iframe', + url: `${syncs[0].url}&gdpr_consent=undefined&gdpr=0` + }, + { + type: 'iframe', + url: `${syncs[1].url}&gdpr_consent=undefined&gdpr=0` + } + ]; + expect(syncResults).to.deep.equal(expectedSyncs); }); }); - context('when iframe is enabled', function() { - it('returns sync array equal to number of unique siteIDs', function() { - const syncOptions = { - iframeEnabled: true - }; - buildRequests(this.bidRequests); - const syncs = getUserSyncs(syncOptions); - expect(syncs).to.deep.equal(this.syncs); + context('when gdpr is unknown and there is consent string', function() { + it('returns sync urls with only consent string as param', function() { + spec.buildRequests(bidRequests); + + const syncResults = spec.getUserSyncs(syncOptions, {}, {consentString: 'consent123A'}); + const expectedSyncs = [ + { + type: 'iframe', + url: `${syncs[0].url}&gdpr_consent=consent123A` + }, + { + type: 'iframe', + url: `${syncs[1].url}&gdpr_consent=consent123A` + } + ]; + expect(syncResults).to.deep.equal(expectedSyncs); }); }); - }); - context('when gdpr applies', function() { - it('returns empty sync array', function() { - const syncOptions = {}; - const gdprConsent = { - gdprApplies: true - } - buildRequests(this.bidRequests); - expect(getUserSyncs(syncOptions, {}, gdprConsent)).to.deep.equal([]); + context('when gdpr does not apply and there is consent string (yikes!)', function() { + it('returns sync urls with consent string as param and gdpr=0', function() { + spec.buildRequests(bidRequests); + + const syncResults = spec.getUserSyncs(syncOptions, {}, {gdprApplies: false, consentString: 'consent123A'}); + const expectedSyncs = [ + { + type: 'iframe', + url: `${syncs[0].url}&gdpr_consent=consent123A&gdpr=0` + }, + { + type: 'iframe', + url: `${syncs[1].url}&gdpr_consent=consent123A&gdpr=0` + } + ]; + expect(syncResults).to.deep.equal(expectedSyncs); + }); }); - }) + }); }); }); diff --git a/test/spec/modules/a4gBidAdapter_spec.js b/test/spec/modules/a4gBidAdapter_spec.js index 4c7520d3b0c..f5aa1014702 100644 --- a/test/spec/modules/a4gBidAdapter_spec.js +++ b/test/spec/modules/a4gBidAdapter_spec.js @@ -70,7 +70,7 @@ describe('a4gAdapterTests', function () { it('bidRequest data', function () { const request = spec.buildRequests(bidRequests); - expect(request.data).to.exists; + expect(request.data).to.exist; }); it('bidRequest zoneIds', function () { diff --git a/test/spec/modules/aardvarkBidAdapter_spec.js b/test/spec/modules/aardvarkBidAdapter_spec.js index 628f19f8fd2..b532fa4264a 100644 --- a/test/spec/modules/aardvarkBidAdapter_spec.js +++ b/test/spec/modules/aardvarkBidAdapter_spec.js @@ -1,5 +1,6 @@ import { expect } from 'chai'; -import { spec } from 'modules/aardvarkBidAdapter'; +import * as utils from 'src/utils'; +import { spec, resetUserSync } from 'modules/aardvarkBidAdapter'; describe('aardvarkAdapterTest', function () { describe('forming valid bidRequests', function () { @@ -37,7 +38,8 @@ describe('aardvarkAdapterTest', function () { sizes: [300, 250], bidId: '1abgs362e0x48a8', bidderRequestId: '70deaff71c281d', - auctionId: '5c66da22-426a-4bac-b153-77360bef5337' + auctionId: '5c66da22-426a-4bac-b153-77360bef5337', + userId: { tdid: 'eff98622-b5fd-44fa-9a49-6e846922d532' } }, { bidder: 'aardvark', @@ -54,21 +56,27 @@ describe('aardvarkAdapterTest', function () { auctionId: 'e97cafd0-ebfc-4f5c-b7c9-baa0fd335a4a' }]; + const bidderRequest = { + refererInfo: { + referer: 'http://example.com' + } + }; + it('should use HTTP GET method', function () { - const requests = spec.buildRequests(bidRequests); - requests.forEach(function(requestItem) { + const requests = spec.buildRequests(bidRequests, bidderRequest); + requests.forEach(function (requestItem) { expect(requestItem.method).to.equal('GET'); }); }); it('should call the correct bidRequest url', function () { - const requests = spec.buildRequests(bidRequests); + const requests = spec.buildRequests(bidRequests, bidderRequest); expect(requests.length).to.equal(1); expect(requests[0].url).to.match(new RegExp('^\/\/adzone.pub.com/xiby/TdAx_RAZd/aardvark\?')); }); it('should have correct data', function () { - const requests = spec.buildRequests(bidRequests); + const requests = spec.buildRequests(bidRequests, bidderRequest); expect(requests.length).to.equal(1); expect(requests[0].data.version).to.equal(1); expect(requests[0].data.jsonp).to.equal(false); @@ -76,6 +84,13 @@ describe('aardvarkAdapterTest', function () { expect(requests[0].data.rtkreferer).to.not.be.undefined; expect(requests[0].data.RAZd).to.equal('22aidtbx5eabd9'); }); + + it('should have tdid, it is available in bidRequest', function () { + const requests = spec.buildRequests(bidRequests, bidderRequest); + requests.forEach(function (requestItem) { + expect(requestItem.data.tdid).to.equal('eff98622-b5fd-44fa-9a49-6e846922d532'); + }); + }); }); describe('splitting multi-auction ad units into own requests', function () { @@ -108,22 +123,28 @@ describe('aardvarkAdapterTest', function () { auctionId: 'e97cafd0-ebfc-4f5c-b7c9-baa0fd335a4a' }]; + const bidderRequest = { + refererInfo: { + referer: 'http://example.com' + } + }; + it('should use HTTP GET method', function () { - const requests = spec.buildRequests(bidRequests); - requests.forEach(function(requestItem) { + const requests = spec.buildRequests(bidRequests, bidderRequest); + requests.forEach(function (requestItem) { expect(requestItem.method).to.equal('GET'); }); }); it('should call the correct bidRequest urls for each auction', function () { - const requests = spec.buildRequests(bidRequests); + const requests = spec.buildRequests(bidRequests, bidderRequest); expect(requests[0].url).to.match(new RegExp('^\/\/bidder.rtk.io/Toby/TdAx/aardvark\?')); expect(requests[0].data.categories.length).to.equal(2); expect(requests[1].url).to.match(new RegExp('^\/\/adzone.pub.com/xiby/RAZd/aardvark\?')); }); it('should have correct data', function () { - const requests = spec.buildRequests(bidRequests); + const requests = spec.buildRequests(bidRequests, bidderRequest); expect(requests.length).to.equal(2); expect(requests[0].data.version).to.equal(1); expect(requests[0].data.jsonp).to.equal(false); @@ -136,6 +157,13 @@ describe('aardvarkAdapterTest', function () { expect(requests[1].data.rtkreferer).to.not.be.undefined; expect(requests[1].data.RAZd).to.equal('22aidtbx5eabd9'); }); + + it('should have no tdid, it is not available in bidRequest', function () { + const requests = spec.buildRequests(bidRequests, bidderRequest); + requests.forEach(function (requestItem) { + expect(requestItem.data.tdid).to.be.undefined; + }); + }); }); describe('GDPR conformity', function () { @@ -157,6 +185,9 @@ describe('aardvarkAdapterTest', function () { gdprConsent: { consentString: 'awefasdfwefasdfasd', gdprApplies: true + }, + refererInfo: { + referer: 'http://example.com' } }; @@ -184,7 +215,10 @@ describe('aardvarkAdapterTest', function () { }]; const bidderRequest = { - gdprConsent: undefined + gdprConsent: undefined, + refererInfo: { + referer: 'http://example.com' + } }; it('should transmit correct data', function () { @@ -219,6 +253,7 @@ describe('aardvarkAdapterTest', function () { cid: '1abgs362e0x48a8', adm: '', ttl: 200, + ex: 'extraproperty' } ], headers: {} @@ -234,6 +269,7 @@ describe('aardvarkAdapterTest', function () { expect(result[0].currency).to.equal('USD'); expect(result[0].ttl).to.equal(200); expect(result[0].dealId).to.equal('dealing'); + expect(result[0].ex).to.be.undefined; expect(result[0].ad).to.not.be.undefined; expect(result[1].requestId).to.equal('1abgs362e0x48a8'); @@ -243,6 +279,7 @@ describe('aardvarkAdapterTest', function () { expect(result[1].currency).to.equal('USD'); expect(result[1].ttl).to.equal(200); expect(result[1].ad).to.not.be.undefined; + expect(result[1].ex).to.equal('extraproperty'); }); it('should handle nobid responses', function () { @@ -260,4 +297,126 @@ describe('aardvarkAdapterTest', function () { expect(result.length).to.equal(0); }); }); + + describe('getUserSyncs', function () { + const syncOptions = { + iframeEnabled: true + }; + + it('should produce sync url', function () { + const syncs = spec.getUserSyncs(syncOptions); + expect(syncs.length).to.equal(1); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.equal('//sync.rtk.io/cs'); + }); + + it('should return empty, as we sync only once', function () { + const syncs = spec.getUserSyncs(syncOptions); + expect(syncs.length).to.equal(0); + }); + + it('should reset hasSynced flag, allowing another sync', function () { + resetUserSync(); + + const syncs = spec.getUserSyncs(syncOptions); + expect(syncs.length).to.equal(1); + }); + + it('should return empty when iframe disallowed', function () { + resetUserSync(); + + const noIframeOptions = { iframeEnabled: false }; + const syncs = spec.getUserSyncs(noIframeOptions); + expect(syncs.length).to.equal(0); + }); + + it('should produce sync url with gdpr params', function () { + const gdprConsent = { + gdprApplies: true, + consentString: 'BOEFEAyOEFEAyAHABDENAI4AAAB9vABAASA' + }; + + resetUserSync(); + + const syncs = spec.getUserSyncs(syncOptions, null, gdprConsent); + expect(syncs.length).to.equal(1); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.equal('//sync.rtk.io/cs?g=1&c=BOEFEAyOEFEAyAHABDENAI4AAAB9vABAASA'); + }); + }); + + describe('reading window.top properties', function () { + const bidCategories = ['bcat1', 'bcat2', 'bcat3']; + const bidRequests = [{ + bidder: 'aardvark', + params: { + ai: 'xiby', + sc: 'TdAx', + host: 'adzone.pub.com', + categories: bidCategories + }, + adUnitCode: 'RTK_aaaa', + transactionId: '1b8389fe-615c-482d-9f1a-177fb8f7d5b0', + sizes: [300, 250], + bidId: '1abgs362e0x48a8', + bidderRequestId: '70deaff71c281d', + auctionId: '5c66da22-426a-4bac-b153-77360bef5337', + userId: { tdid: 'eff98622-b5fd-44fa-9a49-6e846922d532' } + }]; + + const bidderRequest = { + refererInfo: { + referer: 'http://example.com' + } + }; + + const topWin = { + innerWidth: 1366, + innerHeight: 768, + rtkcategories: ['cat1', 'cat2', 'cat3'] + }; + + let sandbox; + beforeEach(function () { + sandbox = sinon.createSandbox(); + }); + + afterEach(function () { + sandbox.restore(); + }); + + it('should have window.top dimensions', function () { + sandbox.stub(utils, 'getWindowTop').returns(topWin); + + const requests = spec.buildRequests(bidRequests, bidderRequest); + requests.forEach(function (requestItem) { + expect(requestItem.data.w).to.equal(topWin.innerWidth); + expect(requestItem.data.h).to.equal(topWin.innerHeight); + }); + }); + + it('should have window dimensions, as backup', function () { + sandbox.stub(utils, 'getWindowTop').returns(undefined); + + const requests = spec.buildRequests(bidRequests, bidderRequest); + requests.forEach(function (requestItem) { + expect(requestItem.data.w).to.equal(window.innerWidth); + expect(requestItem.data.h).to.equal(window.innerHeight); + }); + }); + + it('should have window.top & bid categories', function () { + sandbox.stub(utils, 'getWindowTop').returns(topWin); + + const requests = spec.buildRequests(bidRequests, bidderRequest); + requests.forEach(function (requestItem) { + utils._each(topWin.categories, function (cat) { + expect(requestItem.data.categories).to.contain(cat); + }); + utils._each(bidCategories, function (cat) { + expect(requestItem.data.categories).to.contain(cat); + }); + }); + }); + }); }); diff --git a/test/spec/modules/ablidaBidAdapter_spec.js b/test/spec/modules/ablidaBidAdapter_spec.js new file mode 100644 index 00000000000..8e0424aee23 --- /dev/null +++ b/test/spec/modules/ablidaBidAdapter_spec.js @@ -0,0 +1,102 @@ +import {assert, expect} from 'chai'; +import {spec} from 'modules/ablidaBidAdapter'; +import {newBidder} from 'src/adapters/bidderFactory'; + +const ENDPOINT_URL = 'https://bidder.ablida.net/prebid'; + +describe('ablidaBidAdapter', function () { + const adapter = newBidder(spec); + describe('isBidRequestValid', function () { + let bid = { + bidder: 'ablida', + params: { + placementId: 123 + }, + adUnitCode: 'adunit-code', + sizes: [ + [300, 250] + ], + bidId: '1234asdf1234', + bidderRequestId: '1234asdf1234asdf', + auctionId: '69e8fef8-5105-4a99-b011-d5669f3bc7f0' + }; + it('should return true where required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + }); + describe('buildRequests', function () { + let bidRequests = [ + { + bidder: 'ablida', + params: { + placementId: 123 + }, + sizes: [ + [300, 250] + ], + adUnitCode: 'adunit-code', + bidId: '23beaa6af6cdde', + bidderRequestId: '14d2939272a26a', + auctionId: '69e8fef8-5105-4a99-b011-d5669f3bc7f0', + } + ]; + + let bidderRequests = { + refererInfo: { + numIframes: 0, + reachedTop: true, + referer: 'http://example.com', + stack: ['http://example.com'] + } + }; + + const request = spec.buildRequests(bidRequests, bidderRequests); + it('sends bid request via POST', function () { + expect(request[0].method).to.equal('POST'); + }); + }); + + describe('interpretResponse', function () { + let bidRequest = { + method: 'POST', + url: ENDPOINT_URL, + data: { + placementId: 'testPlacementId', + width: 300, + height: 200, + bidId: '2b8c4de0116e54', + jaySupported: true, + device: 'desktop', + referer: 'www.example.com' + } + }; + let serverResponse = { + body: [{ + requestId: '2b8c4de0116e54', + cpm: 1.00, + width: 300, + height: 250, + creativeId: '2b8c4de0116e54', + currency: 'EUR', + netRevenue: true, + ttl: 3000, + ad: '' + }] + }; + it('should get the correct bid response', function () { + let expectedResponse = [{ + requestId: '2b8c4de0116e54', + cpm: 1.00, + width: 300, + height: 250, + creativeId: '2b8c4de0116e54', + currency: 'EUR', + netRevenue: true, + ttl: 3000, + ad: '' + }]; + let result = spec.interpretResponse(serverResponse, bidRequest[0]); + expect(Object.keys(result)).to.deep.equal(Object.keys(expectedResponse)); + }); + }); +}); diff --git a/test/spec/modules/adagioBidAdapter_spec.js b/test/spec/modules/adagioBidAdapter_spec.js new file mode 100644 index 00000000000..7437b45b6c1 --- /dev/null +++ b/test/spec/modules/adagioBidAdapter_spec.js @@ -0,0 +1,273 @@ +import { expect } from 'chai'; +import { newBidder } from 'src/adapters/bidderFactory'; +import { spec } from 'modules/adagioBidAdapter'; + +describe('adagioAdapter', () => { + const adapter = newBidder(spec); + const ENDPOINT = 'https://mp.4dex.io/prebid'; + + describe('inherited functions', () => { + it('exists and is a function', () => { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', () => { + let bid = { + 'bidder': 'adagio', + 'params': { + siteId: '123', + placementId: 4, + categories: ['IAB12', 'IAB12-2'] + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': 'c180kg4267tyqz', + 'bidderRequestId': '8vfscuixrovn8i', + 'auctionId': 'lel4fhp239i9km', + }; + + it('should return true when required params found', () => { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when site params is not passed', () => { + let bidTest = Object.assign({}, bid); + delete bidTest.params.siteId; + expect(spec.isBidRequestValid(bidTest)).to.equal(false); + }); + + it('should return false when placement params is not passed', () => { + let bidTest = Object.assign({}, bid); + delete bidTest.params.placementId; + expect(spec.isBidRequestValid(bidTest)).to.equal(false); + }); + }); + + describe('buildRequests', () => { + let bidRequests = [ + // siteId 123 + { + 'bidder': 'adagio', + 'params': { + siteId: '123', + placementId: 4, + pagetypeId: '232', + categories: ['IAB12'] + }, + 'adUnitCode': 'adunit-code1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': 'c180kg4267tyqz', + 'bidderRequestId': '8vfscuixrovn8i', + 'auctionId': 'lel4fhp239i9km', + }, + { + 'bidder': 'adagio', + 'params': { + siteId: '123', + placementId: 3, + pagetypeId: '232', + categories: ['IAB12'] + }, + 'adUnitCode': 'adunit-code2', + 'sizes': [[300, 250], [300, 600]], + 'bidId': 'c180kg4267tyqz', + 'bidderRequestId': '8vfscuixrovn8i', + 'auctionId': 'lel4fhp239i9km', + }, + // siteId 456 + { + 'bidder': 'adagio', + 'params': { + siteId: '456', + placementId: 4, + pagetypeId: '232', + categories: ['IAB12'] + }, + 'adUnitCode': 'adunit-code3', + 'sizes': [[300, 250], [300, 600]], + 'bidId': 'c180kg4267tyqz', + 'bidderRequestId': '8vfscuixrovn8i', + 'auctionId': 'lel4fhp239i9km', + } + ]; + + let consentString = 'theConsentString'; + let bidderRequest = { + 'bidderCode': 'adagio', + 'auctionId': '12jejebn', + 'bidderRequestId': 'hehehehbeheh', + 'timeout': 3000, + 'gdprConsent': { + consentString: consentString, + gdprApplies: true, + allowAuctionWithoutConsent: true + } + }; + + it('groups requests by siteId', () => { + const requests = spec.buildRequests(bidRequests); + expect(requests).to.have.lengthOf(2); + + expect(requests[0].data.siteId).to.equal('123'); + expect(requests[0].data.adUnits).to.have.lengthOf(2); + + expect(requests[1].data.siteId).to.equal('456'); + expect(requests[1].data.adUnits).to.have.lengthOf(1); + }); + + it('sends bid request to ENDPOINT_PB via POST', () => { + const requests = spec.buildRequests(bidRequests); + expect(requests).to.have.lengthOf(2); + const request = requests[0]; + expect(request.method).to.equal('POST'); + expect(request.url).to.equal(ENDPOINT); + }); + + it('features params must be an empty object if featurejs is not loaded', () => { + const requests = spec.buildRequests(bidRequests); + expect(requests).to.have.lengthOf(2); + const request = requests[0]; + const expected = {}; + expect(request.data.adUnits[0].params.features).to.deep.equal(expected); + }); + + it('GDPR consent is applied', () => { + const requests = spec.buildRequests(bidRequests, bidderRequest); + expect(requests).to.have.lengthOf(2); + const request = requests[0]; + expect(request.data.gdpr).to.exist; + expect(request.data.gdpr.consentString).to.exist.and.to.equal(consentString); + expect(request.data.gdpr.consentRequired).to.exist.and.to.equal(1); + }); + + it('GDPR consent is not applied', () => { + bidderRequest.gdprConsent.gdprApplies = false; + const requests = spec.buildRequests(bidRequests, bidderRequest); + expect(requests).to.have.lengthOf(2); + const request = requests[0]; + expect(request.data.gdpr).to.exist; + expect(request.data.gdpr.consentString).to.exist.and.to.equal(consentString); + expect(request.data.gdpr.consentRequired).to.exist.and.to.equal(0); + }); + + it('GDPR consent is undefined', () => { + delete bidderRequest.gdprConsent.consentString; + delete bidderRequest.gdprConsent.gdprApplies; + delete bidderRequest.gdprConsent.allowAuctionWithoutConsent; + const requests = spec.buildRequests(bidRequests, bidderRequest); + expect(requests).to.have.lengthOf(2); + const request = requests[0]; + expect(request.data.gdpr).to.exist; + expect(request.data.gdpr).to.not.have.property('consentString'); + expect(request.data.gdpr).to.not.have.property('gdprApplies'); + expect(request.data.gdpr).to.not.have.property('allowAuctionWithoutConsent'); + }); + + it('GDPR consent bidderRequest does not have gdprConsent', () => { + delete bidderRequest.gdprConsent; + const requests = spec.buildRequests(bidRequests, bidderRequest); + expect(requests).to.have.lengthOf(2); + const request = requests[0]; + expect(request.data.gdpr).to.exist; + expect(request.data.gdpr).to.be.empty; + }); + }); + + describe('interpretResponse', () => { + let serverResponse = { + body: { + bids: [ + { + ad: '
    ', + cpm: 1, + creativeId: 'creativeId', + currency: 'EUR', + height: 250, + netRevenue: true, + requestId: 'c180kg4267tyqz', + ttl: 360, + width: 300 + } + ] + } + }; + + let bidRequest = { + 'data': { + 'adUnits': [ + { + 'bidder': 'adagio', + 'params': { + siteId: '666', + placementId: 4, + pagetypeId: '232', + categories: ['IAB12'] + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': 'c180kg4267tyqz', + 'bidderRequestId': '8vfscuixrovn8i', + 'auctionId': 'lel4fhp239i9km', + } + ] + } + }; + + it('should get correct bid response', () => { + let expectedResponse = [{ + ad: '
    ', + cpm: 1, + creativeId: 'creativeId', + currency: 'EUR', + height: 250, + netRevenue: true, + requestId: 'c180kg4267tyqz', + ttl: 360, + width: 300, + categories: [], + pagetypeId: '232', + placementId: 4, + }]; + expect(spec.interpretResponse(serverResponse, bidRequest)).to.be.an('array'); + expect(spec.interpretResponse(serverResponse, bidRequest)).to.deep.equal(expectedResponse); + }); + }); + + describe('getUserSyncs', () => { + const syncOptions = { + 'iframeEnabled': 'true' + } + const serverResponses = [ + { + body: { + userSyncs: [ + { + t: 'i', + u: 'https://test.url.com/setuid' + }, + { + t: 'p', + u: 'https://test.url.com/setuid' + } + ] + } + } + ]; + + const emptyServerResponses = [ + { + body: '' + } + ]; + + it('should handle correctly user syncs', () => { + let result = spec.getUserSyncs(syncOptions, serverResponses); + let emptyResult = spec.getUserSyncs(syncOptions, emptyServerResponses); + expect(result[0].type).to.equal('iframe'); + expect(result[0].url).contain('setuid'); + expect(result[1].type).to.equal('image'); + expect(emptyResult).to.equal(false); + }); + }); +}); diff --git a/test/spec/modules/adformBidAdapter_spec.js b/test/spec/modules/adformBidAdapter_spec.js index d3054794485..f50474ae500 100644 --- a/test/spec/modules/adformBidAdapter_spec.js +++ b/test/spec/modules/adformBidAdapter_spec.js @@ -2,6 +2,7 @@ import {assert, expect} from 'chai'; import * as url from 'src/url'; import {spec} from 'modules/adformBidAdapter'; import { BANNER, VIDEO } from 'src/mediaTypes'; +import { config } from 'src/config'; describe('Adform adapter', function () { let serverResponse, bidRequest, bidResponses; @@ -50,6 +51,24 @@ describe('Adform adapter', function () { assert.equal(request.method, 'GET'); }); + it('should pass request currency from config', function () { + config.setConfig({ currency: { adServerCurrency: 'PLN' } }); + let request = parseUrl(spec.buildRequests(bids).url); + + request.items.forEach(item => { + assert.equal(item.rcur, 'PLN'); + }); + }); + + it('should prefer bid currency over global config', function () { + config.setConfig({ currency: { adServerCurrency: 'PLN' } }); + bids[0].params.rcur = 'USD'; + let request = parseUrl(spec.buildRequests(bids).url); + const currencies = request.items.map(item => item.rcur); + + assert.deepEqual(currencies, [ 'USD', 'PLN', 'PLN', 'PLN', 'PLN', 'PLN', 'PLN' ]); + }); + it('should correctly form bid items', function () { let bidList = bids; let request = spec.buildRequests(bidList); @@ -286,6 +305,8 @@ describe('Adform adapter', function () { }); beforeEach(function () { + config.setConfig({ currency: {} }); + let sizes = [[250, 300], [300, 250], [300, 600]]; let placementCode = ['div-01', 'div-02', 'div-03', 'div-04', 'div-05']; let params = [{ mid: 1, url: 'some// there' }, {adxDomain: null, mid: 2, someVar: 'someValue', pt: 'gross'}, { adxDomain: null, mid: 3, pdom: 'home' }, {mid: 5, pt: 'net'}, {mid: 6, pt: 'gross'}]; diff --git a/test/spec/modules/adformOpenRTBBidAdapter_spec.js b/test/spec/modules/adformOpenRTBBidAdapter_spec.js new file mode 100644 index 00000000000..32795c24ef6 --- /dev/null +++ b/test/spec/modules/adformOpenRTBBidAdapter_spec.js @@ -0,0 +1,637 @@ +// jshint esversion: 6, es3: false, node: true +import {assert, expect} from 'chai'; +import * as url from 'src/url'; +import {spec} from 'modules/adformOpenRTBBidAdapter'; +import { NATIVE } from 'src/mediaTypes'; +import { config } from 'src/config'; + +describe('AdformOpenRTB adapter', function () { + let serverResponse, bidRequest, bidResponses; + let bids = []; + + describe('isBidRequestValid', function () { + let bid = { + 'bidder': 'adformOpenRTB', + 'params': { + 'mid': '19910113' + } + }; + + it('should return true when required params found', function () { + assert(spec.isBidRequestValid(bid)); + }); + + it('should return false when required params are missing', function () { + bid.params = { adxDomain: 'adx.adform.net' }; + assert.isFalse(spec.isBidRequestValid(bid)); + }); + }); + + describe('buildRequests', function () { + it('should send request with correct structure', function () { + let validBidRequests = [{ + bidId: 'bidId', + params: { + siteId: 'siteId', + adxDomain: '10.8.57.207' + } + }]; + let request = spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }); + + assert.equal(request.method, 'POST'); + assert.equal(request.url, '//10.8.57.207/adx/openrtb'); + assert.deepEqual(request.options, {contentType: 'application/json'}); + assert.ok(request.data); + }); + + describe('gdpr', function () { + it('should send GDPR Consent data to adform if gdprApplies', function () { + let validBidRequests = [{ bidId: 'bidId', params: { siteId: 'siteId', test: 1 } }]; + let bidderRequest = { gdprConsent: { gdprApplies: true, consentString: 'consentDataString' }, refererInfo: { referer: 'page' } }; + let request = JSON.parse(spec.buildRequests(validBidRequests, bidderRequest).data); + + assert.equal(request.user.ext.consent, bidderRequest.gdprConsent.consentString); + assert.equal(request.regs.ext.gdpr, bidderRequest.gdprConsent.gdprApplies); + assert.equal(typeof request.regs.ext.gdpr, 'number'); + }); + + it('should send gdpr as number', function () { + let validBidRequests = [{ bidId: 'bidId', params: { siteId: 'siteId', test: 1 } }]; + let bidderRequest = { gdprConsent: { gdprApplies: true, consentString: 'consentDataString' }, refererInfo: { referer: 'page' } }; + let request = JSON.parse(spec.buildRequests(validBidRequests, bidderRequest).data); + + assert.equal(typeof request.regs.ext.gdpr, 'number'); + }); + + it('should not send GDPR Consent data to adform if gdprApplies is false or undefined', function () { + let validBidRequests = [{ + bidId: 'bidId', + params: { siteId: 'siteId' } + }]; + let bidderRequest = {gdprConsent: {gdprApplies: false, consentString: 'consentDataString'}, refererInfo: { referer: 'page' }}; + let request = JSON.parse(spec.buildRequests(validBidRequests, bidderRequest).data); + + assert.equal(request.user, undefined); + assert.equal(request.regs, undefined); + }); + it('should send default GDPR Consent data to adform', function () { + let validBidRequests = [{ + bidId: 'bidId', + params: { siteId: 'siteId' } + }]; + let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }).data); + + assert.equal(request.user, undefined); + assert.equal(request.regs, undefined); + }); + }); + + it('should add test and is_debug to request, if test is set in parameters', function () { + let validBidRequests = [{ + bidId: 'bidId', + params: { siteId: 'siteId', test: 1 } + }]; + let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }).data); + + assert.ok(request.is_debug); + assert.equal(request.test, 1); + }); + + it('should have default request structure', function () { + let keys = 'site,device,source,ext,imp'.split(','); + let validBidRequests = [{ + bidId: 'bidId', + params: { siteId: 'siteId' } + }]; + let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }).data); + let data = Object.keys(request); + + assert.deepEqual(keys, data); + }); + + it('should set request keys correct values', function () { + let validBidRequests = [{ + bidId: 'bidId', + params: { siteId: 'siteId' }, + transactionId: 'transactionId' + }]; + let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }).data); + + assert.equal(request.source.tid, validBidRequests[0].transactionId); + assert.equal(request.source.fd, 1); + }); + + it('should send info about device', function () { + let validBidRequests = [{ + bidId: 'bidId', + params: { siteId: 'siteId' } + }]; + let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }).data); + + assert.equal(request.device.ua, navigator.userAgent); + }); + it('should send info about the site', function () { + let validBidRequests = [{ + bidId: 'bidId', + params: { siteId: 'siteId', publisher: {id: '123123', domain: 'publisher.domain.com', name: 'publisher\'s name'} } + }]; + let refererInfo = { referer: 'page' }; + let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo }).data); + + assert.deepEqual(request.site, { + page: refererInfo.referer, + publisher: validBidRequests[0].params.publisher, + id: validBidRequests[0].params.siteId + }); + }); + + it('should send currency if defined', function () { + config.setConfig({ currency: { adServerCurrency: 'EUR' } }); + let validBidRequests = [{ params: {} }]; + let refererInfo = { referer: 'page' }; + let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo }).data); + + assert.deepEqual(request.cur, [ 'EUR' ]); + }); + + describe('priceType', function () { + it('should send default priceType', function () { + let validBidRequests = [{ + bidId: 'bidId', + params: { siteId: 'siteId' } + }]; + let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }).data); + + assert.equal(request.ext.pt, 'net'); + }); + it('should send correct priceType value', function () { + let validBidRequests = [{ + bidId: 'bidId', + params: { siteId: 'siteId', priceType: 'net' } + }]; + let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }).data); + + assert.equal(request.ext.pt, 'net'); + }); + }); + + describe('bids', function () { + it('should add more than one bid to the request', function () { + let validBidRequests = [{ + bidId: 'bidId', + params: { siteId: 'siteId' } + }, { + bidId: 'bidId2', + params: { siteId: 'siteId' } + }]; + let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }).data); + + assert.equal(request.imp.length, 2); + }); + it('should add incrementing values of id', function () { + let validBidRequests = [{ + bidId: 'bidId', + params: { siteId: 'siteId' } + }, { + bidId: 'bidId2', + params: { siteId: 'siteId' } + }, { + bidId: 'bidId3', + params: { siteId: 'siteId' } + }]; + let imps = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }).data).imp; + + for (let i = 0; i < 3; i++) { + assert.equal(imps[i].id, i + 1); + } + }); + + it('should add mid', function () { + let validBidRequests = [{ bidId: 'bidId', params: { siteId: 'siteId', mid: 1000 } }, + { bidId: 'bidId2', params: { siteId: 'siteId', mid: 1001 } }, + { bidId: 'bidId3', params: { siteId: 'siteId', mid: 1002 } }]; + let imps = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }).data).imp; + for (let i = 0; i < 3; i++) { + assert.equal(imps[i].tagid, validBidRequests[i].params.mid); + } + }); + + describe('native', function () { + describe('assets', function () { + it('should set correct asset id', function () { + let validBidRequests = [{ + bidId: 'bidId', + params: { siteId: 'siteId', mid: 1000 }, + nativeParams: { + title: { required: true, len: 140 }, + image: { required: false, wmin: 836, hmin: 627, w: 325, h: 300, mimes: ['image/jpg', 'image/gif'] }, + body: { len: 140 } + } + }]; + let assets = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }).data).imp[0].native.request.assets; + + assert.equal(assets[0].id, 0); + assert.equal(assets[1].id, 3); + assert.equal(assets[2].id, 4); + }); + it('should add required key if it is necessary', function () { + let validBidRequests = [{ + bidId: 'bidId', + params: { siteId: 'siteId', mid: 1000 }, + nativeParams: { + title: { required: true, len: 140 }, + image: { required: false, wmin: 836, hmin: 627, w: 325, h: 300, mimes: ['image/jpg', 'image/gif'] }, + body: { len: 140 }, + sponsoredBy: { required: true, len: 140 } + } + }]; + + let assets = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }).data).imp[0].native.request.assets; + + assert.equal(assets[0].required, 1); + assert.ok(!assets[1].required); + assert.ok(!assets[2].required); + assert.equal(assets[3].required, 1); + }); + + it('should map img and data assets', function () { + let validBidRequests = [{ + bidId: 'bidId', + params: { siteId: 'siteId', mid: 1000 }, + nativeParams: { + title: { required: true, len: 140 }, + image: { required: true, sizes: [150, 50] }, + icon: { required: false, sizes: [50, 50] }, + body: { required: false, len: 140 }, + sponsoredBy: { required: true }, + cta: { required: false }, + clickUrl: { required: false } + } + }]; + + let assets = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }).data).imp[0].native.request.assets; + assert.ok(assets[0].title); + assert.equal(assets[0].title.len, 140); + assert.deepEqual(assets[1].img, { type: 3, w: 150, h: 50 }); + assert.deepEqual(assets[2].img, { type: 1, w: 50, h: 50 }); + assert.deepEqual(assets[3].data, { type: 2, len: 140 }); + assert.deepEqual(assets[4].data, { type: 1 }); + assert.deepEqual(assets[5].data, { type: 12 }); + assert.ok(!assets[6]); + }); + + describe('icon/image sizing', function () { + it('should flatten sizes and utilise first pair', function () { + const validBidRequests = [{ + bidId: 'bidId', + params: { siteId: 'siteId', mid: 1000 }, + nativeParams: { + image: { + sizes: [[200, 300], [100, 200]] + }, + } + }]; + + let assets = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }).data).imp[0].native.request.assets; + assert.ok(assets[0].img); + assert.equal(assets[0].img.w, 200); + assert.equal(assets[0].img.h, 300); + }); + }); + + it('should utilise aspect_ratios', function () { + const validBidRequests = [{ + bidId: 'bidId', + params: { siteId: 'siteId', mid: 1000 }, + nativeParams: { + image: { + aspect_ratios: [{ + min_width: 100, + ratio_height: 3, + ratio_width: 1 + }] + }, + icon: { + aspect_ratios: [{ + min_width: 10, + ratio_height: 5, + ratio_width: 2 + }] + } + } + }]; + + let assets = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }).data).imp[0].native.request.assets; + assert.ok(assets[0].img); + assert.equal(assets[0].img.wmin, 100); + assert.equal(assets[0].img.hmin, 300); + + assert.ok(assets[1].img); + assert.equal(assets[1].img.wmin, 10); + assert.equal(assets[1].img.hmin, 25); + }); + + it('should not throw error if aspect_ratios config is not defined', function () { + const validBidRequests = [{ + bidId: 'bidId', + params: { siteId: 'siteId', mid: 1000 }, + nativeParams: { + image: { + aspect_ratios: [] + }, + icon: { + aspect_ratios: [] + } + } + }]; + + assert.doesNotThrow(() => spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } })); + }); + }); + + it('should expect any dimensions if min_width not passed', function () { + const validBidRequests = [{ + bidId: 'bidId', + params: { siteId: 'siteId', mid: 1000 }, + nativeParams: { + image: { + aspect_ratios: [{ + ratio_height: 3, + ratio_width: 1 + }] + } + } + }]; + + let assets = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }).data).imp[0].native.request.assets; + assert.ok(assets[0].img); + assert.equal(assets[0].img.wmin, 0); + assert.equal(assets[0].img.hmin, 0); + assert.ok(!assets[1]); + }); + }); + }); + }); + + describe('interpretResponse', function () { + it('should return if no body in response', function () { + let serverResponse = {}; + let bidRequest = {}; + + assert.ok(!spec.interpretResponse(serverResponse, bidRequest)); + }); + it('should return more than one bids', function () { + let serverResponse = { + body: { + seatbid: [{ + bid: [{impid: '1', native: {ver: '1.1', link: { url: 'link' }, assets: [{id: 1, title: {text: 'Asset title text'}}]}}] + }, { + bid: [{impid: '2', native: {ver: '1.1', link: { url: 'link' }, assets: [{id: 1, data: {value: 'Asset title text'}}]}}] + }] + } + }; + let bidRequest = { + data: {}, + bids: [ + { + bidId: 'bidId1', + params: { siteId: 'siteId', mid: 1000 }, + nativeParams: { + title: { required: true, len: 140 }, + image: { required: false, wmin: 836, hmin: 627, w: 325, h: 300, mimes: ['image/jpg', 'image/gif'] }, + body: { len: 140 } + } + }, + { + bidId: 'bidId2', + params: { siteId: 'siteId', mid: 1000 }, + nativeParams: { + title: { required: true, len: 140 }, + image: { required: false, wmin: 836, hmin: 627, w: 325, h: 300, mimes: ['image/jpg', 'image/gif'] }, + body: { len: 140 } + } + } + ] + }; + + bids = spec.interpretResponse(serverResponse, bidRequest); + assert.equal(spec.interpretResponse(serverResponse, bidRequest).length, 2); + }); + + it('should parse seatbids', function () { + let serverResponse = { + body: { + seatbid: [{ + bid: [ + {impid: '1', native: {ver: '1.1', link: { url: 'link1' }, assets: [{id: 1, title: {text: 'Asset title text'}}]}}, + {impid: '4', native: {ver: '1.1', link: { url: 'link4' }, assets: [{id: 1, title: {text: 'Asset title text'}}]}} + ] + }, { + bid: [{impid: '2', native: {ver: '1.1', link: { url: 'link2' }, assets: [{id: 1, data: {value: 'Asset title text'}}]}}] + }] + } + }; + let bidRequest = { + data: {}, + bids: [ + { + bidId: 'bidId1', + params: { siteId: 'siteId', mid: 1000 }, + nativeParams: { + title: { required: true, len: 140 }, + image: { required: false, wmin: 836, hmin: 627, w: 325, h: 300, mimes: ['image/jpg', 'image/gif'] }, + body: { len: 140 } + } + }, + { + bidId: 'bidId2', + params: { siteId: 'siteId', mid: 1000 }, + nativeParams: { + title: { required: true, len: 140 }, + image: { required: false, wmin: 836, hmin: 627, w: 325, h: 300, mimes: ['image/jpg', 'image/gif'] }, + body: { len: 140 } + } + }, + { + bidId: 'bidId3', + params: { siteId: 'siteId', mid: 1000 }, + nativeParams: { + title: { required: true, len: 140 }, + image: { required: false, wmin: 836, hmin: 627, w: 325, h: 300, mimes: ['image/jpg', 'image/gif'] }, + body: { len: 140 } + } + }, + { + bidId: 'bidId4', + params: { siteId: 'siteId', mid: 1000 }, + nativeParams: { + title: { required: true, len: 140 }, + image: { required: false, wmin: 836, hmin: 627, w: 325, h: 300, mimes: ['image/jpg', 'image/gif'] }, + body: { len: 140 } + } + } + ] + }; + + bids = spec.interpretResponse(serverResponse, bidRequest).map(bid => { + const { requestId, native: { clickUrl } } = bid; + return [ requestId, clickUrl ]; + }); + + assert.equal(bids.length, 3); + assert.deepEqual(bids, [[ 'bidId1', 'link1' ], [ 'bidId2', 'link2' ], [ 'bidId4', 'link4' ]]); + }); + + it('should set correct values to bid', function () { + let serverResponse = { + body: { + id: null, + bidid: null, + seatbid: [{ + bid: [ + { + impid: '1', + price: 93.1231, + crid: '12312312', + native: { + assets: [], + link: { url: 'link' }, + imptrackers: ['imptrackers url1', 'imptrackers url2'] + } + } + ] + }], + cur: 'NOK' + } + }; + let bidRequest = { + data: {}, + bids: [ + { + bidId: 'bidId1', + params: { siteId: 'siteId', mid: 1000 }, + nativeParams: { + title: { required: true, len: 140 }, + image: { required: false, wmin: 836, hmin: 627, w: 325, h: 300, mimes: ['image/jpg', 'image/gif'] }, + body: { len: 140 } + } + } + ] + }; + + const bids = spec.interpretResponse(serverResponse, bidRequest); + const bid = serverResponse.body.seatbid[0].bid[0]; + assert.deepEqual(bids[0].requestId, bidRequest.bids[0].bidId); + assert.deepEqual(bids[0].cpm, bid.price); + assert.deepEqual(bids[0].creativeId, bid.crid); + assert.deepEqual(bids[0].ttl, 360); + assert.deepEqual(bids[0].netRevenue, false); + assert.deepEqual(bids[0].currency, serverResponse.body.cur); + assert.deepEqual(bids[0].mediaType, 'native'); + assert.deepEqual(bids[0].bidderCode, 'adformOpenRTB'); + }); + it('should set correct native params', function () { + const bid = [ + { + impid: '1', + price: 93.1231, + crid: '12312312', + native: { + assets: [ + { + data: null, + id: 0, + img: null, + required: 0, + title: {text: 'title', len: null}, + video: null + }, { + data: null, + id: 2, + img: {type: null, url: 'test.url.com/Files/58345/308185.jpg?bv=1', w: 30, h: 10}, + required: 0, + title: null, + video: null + }, { + data: null, + id: 3, + img: {type: null, url: 'test.url.com/Files/58345/308200.jpg?bv=1', w: 100, h: 100}, + required: 0, + title: null, + video: null + }, { + data: {type: null, len: null, value: 'body'}, + id: 4, + img: null, + required: 0, + title: null, + video: null + }, { + data: {type: null, len: null, value: 'cta'}, + id: 1, + img: null, + required: 0, + title: null, + video: null + }, { + data: {type: null, len: null, value: 'sponsoredBy'}, + id: 5, + img: null, + required: 0, + title: null, + video: null + } + ], + link: { url: 'clickUrl', clicktrackers: ['clickTracker1', 'clickTracker2'] }, + imptrackers: ['imptrackers url1', 'imptrackers url2'], + jstracker: 'jstracker' + } + } + ]; + const serverResponse = { + body: { + id: null, + bidid: null, + seatbid: [{ bid }], + cur: 'NOK' + } + }; + let bidRequest = { + data: {}, + bids: [{ bidId: 'bidId1' }] + }; + + const result = spec.interpretResponse(serverResponse, bidRequest)[0].native; + const native = bid[0].native; + const assets = native.assets; + assert.deepEqual({ + clickUrl: native.link.url, + clickTrackers: native.link.clicktrackers, + impressionTrackers: native.imptrackers, + javascriptTrackers: [ native.jstracker ], + title: assets[0].title.text, + icon: {url: assets[1].img.url, width: assets[1].img.w, height: assets[1].img.h}, + image: {url: assets[2].img.url, width: assets[2].img.w, height: assets[2].img.h}, + body: assets[3].data.value, + cta: assets[4].data.value, + sponsoredBy: assets[5].data.value + }, result); + }); + it('should return empty when there is no bids in response', function () { + const serverResponse = { + body: { + id: null, + bidid: null, + seatbid: [{ bid: [] }], + cur: 'NOK' + } + }; + let bidRequest = { + data: {}, + bids: [{ bidId: 'bidId1' }] + }; + const result = spec.interpretResponse(serverResponse, bidRequest)[0]; + assert.ok(!result); + }); + }); +}); diff --git a/test/spec/modules/adgenerationBidAdapter_spec.js b/test/spec/modules/adgenerationBidAdapter_spec.js index 558303ccecb..2b8834f1e1d 100644 --- a/test/spec/modules/adgenerationBidAdapter_spec.js +++ b/test/spec/modules/adgenerationBidAdapter_spec.js @@ -1,8 +1,9 @@ import {expect} from 'chai'; -import * as utils from 'src/utils'; import {spec} from 'modules/adgenerationBidAdapter'; import {newBidder} from 'src/adapters/bidderFactory'; import {NATIVE} from 'src/mediaTypes'; +import {config} from 'src/config'; +import prebid from '../../../package.json'; describe('AdgenerationAdapter', function () { const adapter = newBidder(spec); @@ -15,7 +16,7 @@ describe('AdgenerationAdapter', function () { }); describe('isBidRequestValid', function () { - let bid = { + const bid = { 'bidder': 'adg', 'params': { id: '58278', // banner @@ -39,11 +40,10 @@ describe('AdgenerationAdapter', function () { bidder: 'adg', params: { id: '58278', - width: '300', - height: '250' + currency: 'JPY', }, adUnitCode: 'adunit-code', - sizes: [[300, 250]], + sizes: [[300, 250], [320, 100]], bidId: '2f6ac468a9c15e', bidderRequestId: '14a9f773e30243', auctionId: '4aae9f05-18c6-4fcd-80cf-282708cd584a', @@ -53,8 +53,7 @@ describe('AdgenerationAdapter', function () { bidder: 'adg', params: { id: '58278', - width: '300', - height: '250' + currency: 'JPY', }, mediaTypes: { native: { @@ -87,34 +86,59 @@ describe('AdgenerationAdapter', function () { transactionTd: 'f76f6dfd-d64f-4645-a29f-682bac7f431a' } ]; + const bidderRequest = { + refererInfo: { + referer: 'http://example.com' + } + }; const data = { - banner: 'posall=SSPLOC&id=58278&sdktype=0&hb=true&t=json3&imark=1', - native: 'posall=SSPLOC&id=58278&sdktype=0&hb=true&t=json3' + banner: `posall=SSPLOC&id=58278&sdktype=0&hb=true&t=json3&sizes=300x250%2C320x100¤cy=JPY&pbver=${prebid.version}&sdkname=prebidjs&adapterver=1.0.1&imark=1&tp=http%3A%2F%2Fexample.com`, + bannerUSD: `posall=SSPLOC&id=58278&sdktype=0&hb=true&t=json3&sizes=300x250%2C320x100¤cy=USD&pbver=${prebid.version}&sdkname=prebidjs&adapterver=1.0.1&imark=1&tp=http%3A%2F%2Fexample.com`, + native: 'posall=SSPLOC&id=58278&sdktype=0&hb=true&t=json3&sizes=1x1¤cy=JPY&pbver=' + prebid.version + '&sdkname=prebidjs&adapterver=1.0.1&tp=http%3A%2F%2Fexample.com' }; it('sends bid request to ENDPOINT via GET', function () { - const request = spec.buildRequests(bidRequests)[0]; + const request = spec.buildRequests(bidRequests, bidderRequest)[0]; expect(request.url).to.equal(ENDPOINT[1]); expect(request.method).to.equal('GET'); }); it('sends bid request to debug ENDPOINT via GET', function () { bidRequests[0].params.debug = true; - const request = spec.buildRequests(bidRequests)[0]; + const request = spec.buildRequests(bidRequests, bidderRequest)[0]; expect(request.url).to.equal(ENDPOINT[0]); expect(request.method).to.equal('GET'); }); it('should attache params to the banner request', function () { - const request = spec.buildRequests(bidRequests)[0]; + const request = spec.buildRequests(bidRequests, bidderRequest)[0]; expect(request.data).to.equal(data.banner); }); it('should attache params to the native request', function () { - const request = spec.buildRequests(bidRequests)[1]; + const request = spec.buildRequests(bidRequests, bidderRequest)[1]; expect(request.data).to.equal(data.native); }); + it('allows setConfig to set bidder currency for JPY', function () { + config.setConfig({ + currency: { + adServerCurrency: 'JPY' + } + }); + const request = spec.buildRequests(bidRequests, bidderRequest)[0]; + expect(request.data).to.equal(data.banner); + config.resetConfig(); + }); + it('allows setConfig to set bidder currency for USD', function () { + config.setConfig({ + currency: { + adServerCurrency: 'USD' + } + }); + const request = spec.buildRequests(bidRequests, bidderRequest)[0]; + expect(request.data).to.equal(data.bannerUSD); + config.resetConfig(); + }); }); - describe('interpretResponse', function () { const bidRequests = { banner: { @@ -175,7 +199,7 @@ describe('AdgenerationAdapter', function () { results: [], }, banner: { - ad: '
    ', + ad: '
    ', beacon: '', cpm: 36.0008, displaytype: '1', @@ -191,11 +215,11 @@ describe('AdgenerationAdapter', function () { dealid: 'fd5sa5fa7f', ttl: 1000, results: [ - {ad: '
    '}, + {ad: '
    '}, ] }, native: { - ad: '↵ ↵ ↵ ↵ ↵
    ↵ ', + ad: '↵ ↵ ↵ ↵ ↵
    ↵ ', beacon: '', cpm: 36.0008, displaytype: '1', @@ -214,7 +238,7 @@ describe('AdgenerationAdapter', function () { { data: { label: 'optout_url', - value: 'https://supership.jp/optout/' + value: 'https://supership.jp/optout/#' }, id: 502 }, @@ -237,7 +261,7 @@ describe('AdgenerationAdapter', function () { id: 2, img: { h: 250, - url: 'https://s3-ap-northeast-1.amazonaws.com/sdk-temp/adg-sample-ad/img/300x250.png', + url: 'https://sdk-temp.s3-ap-northeast-1.amazonaws.com/adg-sample-ad/img/300x250.png', w: 300 }, required: 1 @@ -267,10 +291,10 @@ describe('AdgenerationAdapter', function () { required: 0 } ], - imptrackers: ['https://s3-ap-northeast-1.amazonaws.com/adg-dummy-dsp/1x1.gif'], + imptrackers: ['https://adg-dummy-dsp.s3-ap-northeast-1.amazonaws.com/1x1.gif'], link: { clicktrackers: [ - 'https://s3-ap-northeast-1.amazonaws.com/adg-dummy-dsp/1x1_clicktracker_access.gif' + 'https://adg-dummy-dsp.s3-ap-northeast-1.amazonaws.com/1x1_clicktracker_access.gif' ], url: 'https://supership.jp' }, @@ -298,8 +322,7 @@ describe('AdgenerationAdapter', function () { currency: 'JPY', netRevenue: true, ttl: 1000, - referrer: utils.getTopWindowUrl(), - ad: '
    ', + ad: '
    ', }, native: { requestId: '2f6ac468a9c15e', @@ -311,12 +334,11 @@ describe('AdgenerationAdapter', function () { currency: 'JPY', netRevenue: true, ttl: 1000, - referrer: utils.getTopWindowUrl(), - ad: '↵
    ', + ad: '↵
    ', native: { title: 'Title', image: { - url: 'https://s3-ap-northeast-1.amazonaws.com/sdk-temp/adg-sample-ad/img/300x250.png', + url: 'https://sdk-temp.s3-ap-northeast-1.amazonaws.com/adg-sample-ad/img/300x250.png', height: 250, width: 300 }, @@ -328,9 +350,10 @@ describe('AdgenerationAdapter', function () { sponsoredBy: 'Sponsored', body: 'Description', cta: 'CTA', + privacyLink: 'https://supership.jp/optout/#', clickUrl: 'https://supership.jp', - clickTrackers: ['https://s3-ap-northeast-1.amazonaws.com/adg-dummy-dsp/1x1_clicktracker_access.gif'], - impressionTrackers: ['https://s3-ap-northeast-1.amazonaws.com/adg-dummy-dsp/1x1.gif'] + clickTrackers: ['https://adg-dummy-dsp.s3-ap-northeast-1.amazonaws.com/1x1_clicktracker_access.gif'], + impressionTrackers: ['https://adg-dummy-dsp.s3-ap-northeast-1.amazonaws.com/1x1.gif'] }, mediaType: NATIVE } @@ -351,7 +374,6 @@ describe('AdgenerationAdapter', function () { expect(result.currency).to.equal(bidResponses.banner.currency); expect(result.netRevenue).to.equal(bidResponses.banner.netRevenue); expect(result.ttl).to.equal(bidResponses.banner.ttl); - expect(result.referrer).to.equal(bidResponses.banner.referrer); expect(result.ad).to.equal(bidResponses.banner.ad); }); @@ -365,7 +387,6 @@ describe('AdgenerationAdapter', function () { expect(result.currency).to.equal(bidResponses.native.currency); expect(result.netRevenue).to.equal(bidResponses.native.netRevenue); expect(result.ttl).to.equal(bidResponses.native.ttl); - expect(result.referrer).to.equal(bidResponses.native.referrer); expect(result.native.title).to.equal(bidResponses.native.native.title); expect(result.native.image.url).to.equal(bidResponses.native.native.image.url); expect(result.native.image.height).to.equal(bidResponses.native.native.image.height); @@ -376,6 +397,7 @@ describe('AdgenerationAdapter', function () { expect(result.native.sponsoredBy).to.equal(bidResponses.native.native.sponsoredBy); expect(result.native.body).to.equal(bidResponses.native.native.body); expect(result.native.cta).to.equal(bidResponses.native.native.cta); + expect(decodeURIComponent(result.native.privacyLink)).to.equal(bidResponses.native.native.privacyLink); expect(result.native.clickUrl).to.equal(bidResponses.native.native.clickUrl); expect(result.native.impressionTrackers[0]).to.equal(bidResponses.native.native.impressionTrackers[0]); expect(result.native.clickTrackers[0]).to.equal(bidResponses.native.native.clickTrackers[0]); diff --git a/test/spec/modules/adheseBidAdapter_spec.js b/test/spec/modules/adheseBidAdapter_spec.js new file mode 100644 index 00000000000..348fb772319 --- /dev/null +++ b/test/spec/modules/adheseBidAdapter_spec.js @@ -0,0 +1,300 @@ +import {expect} from 'chai'; +import {spec} from 'modules/adheseBidAdapter'; + +const BID_ID = 456; +const TTL = 360; +const NET_REVENUE = true; + +let minimalBid = function() { + return { + 'bidId': BID_ID, + 'bidder': 'adhese', + 'params': { + account: 'demo', + location: '_main_page_', + format: 'leaderboard' + } + } +}; + +let bidWithParams = function(data) { + let bid = minimalBid(); + bid.params.data = data; + return bid; +}; + +describe('AdheseAdapter', function () { + describe('getUserSyncs', function () { + const serverResponses = [{ + account: 'demo' + }]; + const gdprConsent = { + gdprApplies: true, + consentString: 'CONSENT_STRING' + }; + it('should return empty when iframe disallowed', function () { + expect(spec.getUserSyncs({ iframeEnabled: false }, serverResponses, gdprConsent)).to.be.empty; + }); + it('should return empty when no serverResponses present', function () { + expect(spec.getUserSyncs({ iframeEnabled: true }, [], gdprConsent)).to.be.empty; + }); + it('should return empty when no account info present in the response', function () { + expect(spec.getUserSyncs({ iframeEnabled: true }, [{}], gdprConsent)).to.be.empty; + }); + it('should return usersync url when iframe allowed', function () { + expect(spec.getUserSyncs({ iframeEnabled: true }, serverResponses, gdprConsent)).to.deep.equal([{ type: 'iframe', url: 'https://user-sync.adhese.com/iframe/user_sync.html?account=demo&gdpr=1&consentString=CONSENT_STRING' }]); + }); + }); + + describe('isBidRequestValid', function () { + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(minimalBid())).to.equal(true); + }); + + it('should return false when required params are not passed', function () { + let bid = Object.assign({}, minimalBid()); + delete bid.params; + bid.params = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + let bidderRequest = { + gdprConsent: { + gdprApplies: true, + consentString: 'CONSENT_STRING' + }, + refererInfo: { + referer: 'http://prebid.org/dev-docs/subjects?_d=1' + } + }; + + it('should include all extra bid params', function () { + let req = spec.buildRequests([ bidWithParams({ 'ag': '25' }) ], bidderRequest); + + expect(req.url).to.contain('/sl_main_page_-leaderboard/ag25'); + }); + + it('should include duplicate bid params once', function () { + let req = spec.buildRequests([ bidWithParams({ 'ag': '25' }), bidWithParams({ 'ag': '25', 'ci': 'gent' }) ], bidderRequest); + + expect(req.url).to.contain('/sl_main_page_-leaderboard/ag25/cigent'); + }); + + it('should split multiple target values', function () { + let req = spec.buildRequests([ bidWithParams({ 'ci': 'london' }), bidWithParams({ 'ci': 'gent' }) ], bidderRequest); + + expect(req.url).to.contain('/sl_main_page_-leaderboard/cilondon;gent'); + }); + + it('should include gdpr consent param', function () { + let req = spec.buildRequests([ minimalBid() ], bidderRequest); + + expect(req.url).to.contain('/xtCONSENT_STRING'); + }); + + it('should include referer param in base64url format', function () { + let req = spec.buildRequests([ minimalBid() ], bidderRequest); + + expect(req.url).to.contain('/xfaHR0cDovL3ByZWJpZC5vcmcvZGV2LWRvY3Mvc3ViamVjdHM_X2Q9MQ'); + }); + + it('should include bids', function () { + let bid = minimalBid(); + let req = spec.buildRequests([ bid ], bidderRequest); + + expect(req.bids).to.deep.equal([ bid ]); + }); + }); + + describe('interpretResponse', () => { + let bidRequest = { + bids: [ minimalBid() ] + }; + + it('should get correct ssp banner response', () => { + let sspBannerResponse = { + body: [ + { + origin: 'APPNEXUS', + originInstance: '', + ext: 'js', + slotName: '_main_page_-leaderboard', + adType: 'leaderboard', + originData: { + seatbid: [{ + bid: [{ + crid: '60613369', + dealid: null + }], + seat: '958' + }] + }, + width: '728', + height: '90', + 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'}}} + } + ] + }; + + let expectedResponse = [{ + requestId: BID_ID, + ad: '
    ', + cpm: 1, + currency: 'USD', + creativeId: '60613369', + dealId: '', + width: 728, + height: 90, + mediaType: 'banner', + netRevenue: NET_REVENUE, + ttl: TTL, + }]; + expect(spec.interpretResponse(sspBannerResponse, bidRequest)).to.deep.equal(expectedResponse); + }); + + it('should get correct ssp video response', () => { + let sspVideoResponse = { + body: [ + { + origin: 'RUBICON', + ext: 'js', + slotName: '_main_page_-leaderboard', + adType: 'leaderboard', + width: '640', + height: '350', + body: '', + extension: {'prebid': {'cpm': {'amount': '2.1', 'currency': 'USD'}}} + } + ] + }; + + let expectedResponse = [{ + requestId: BID_ID, + vastXml: '', + cpm: 2.1, + currency: 'USD', + creativeId: 'RUBICON', + dealId: '', + width: 640, + height: 350, + mediaType: 'video', + netRevenue: NET_REVENUE, + ttl: TTL, + }]; + expect(spec.interpretResponse(sspVideoResponse, bidRequest)).to.deep.equal(expectedResponse); + }); + + it('should get correct Adhese banner response', () => { + const adheseBannerResponse = { + body: [ + { + adType: 'largeleaderboard', // it can differ from the requested slot + adFormat: 'largeleaderboard', + timeStamp: '1544009030000', + orderId: '22051', + adspaceId: '162363', + body: '', + tag: '', + tracker: 'https://hosts-demo.adhese.com/track/tracker', + altText: '', + height: '150', + width: '840', + tagUrl: 'https://pool-demo.adhese.com/pool/lib/90511.js', + libId: '90511', + id: '742898', + advertiserId: '2081', + ext: 'js', + url: 'https://hosts-demo.adhese.com/raylene/url', + clickTag: 'https://hosts-demo.adhese.com/raylene/clickTag', + poolPath: 'https://hosts-demo.adhese.com/pool/lib/', + orderName: 'Luminus boiler comodity-Pareto -201812', + creativeName: 'nl_demo _network_ron_dlbd_840x150_fix_dir_asv_std_dis_brd_nrt_na_red', + slotName: '_main_page_-leaderboard', + slotID: '29306', + impressionCounter: 'https://hosts-demo.adhese.com/track/742898', + origin: 'JERLICIA', + originData: {}, + auctionable: true, + extension: { + prebid: { + cpm: { + amount: '5.96', + currency: 'USD' + } + } + } + } + ] + }; + + let expectedResponse = [{ + requestId: BID_ID, + ad: '', + cpm: 5.96, + currency: 'USD', + creativeId: '742898', + dealId: '22051', + width: 840, + height: 150, + mediaType: 'banner', + netRevenue: NET_REVENUE, + ttl: TTL, + }]; + expect(spec.interpretResponse(adheseBannerResponse, bidRequest)).to.deep.equal(expectedResponse); + }); + + it('should get correct Adhese video response', () => { + const adheseVideoResponse = { + body: [ + { + adType: 'preroll', + adFormat: '', + orderId: '22248', + adspaceId: '164196', + body: '', + height: '360', + width: '640', + tag: "", + 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, + vastXml: '', + 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/adkernelAdnAnalyticsAdapter_spec.js b/test/spec/modules/adkernelAdnAnalyticsAdapter_spec.js index 1291a375fc0..26fd13afd1f 100644 --- a/test/spec/modules/adkernelAdnAnalyticsAdapter_spec.js +++ b/test/spec/modules/adkernelAdnAnalyticsAdapter_spec.js @@ -1,7 +1,6 @@ import analyticsAdapter, {ExpiringQueue, getUmtSource, storage} from 'modules/adkernelAdnAnalyticsAdapter'; import {expect} from 'chai'; -import adaptermanager from 'src/adaptermanager'; -import * as ajax from 'src/ajax'; +import adapterManager from 'src/adapterManager'; import CONSTANTS from 'src/constants.json'; const events = require('../../../src/events'); @@ -189,7 +188,7 @@ describe('', function () { let timer; before(function () { - ajaxStub = sandbox.stub(ajax, 'ajax'); + ajaxStub = sandbox.stub(analyticsAdapter, 'ajaxCall'); timer = sandbox.useFakeTimers(0); }); @@ -204,12 +203,12 @@ describe('', function () { }); it('should be configurable', function () { - adaptermanager.registerAnalyticsAdapter({ + adapterManager.registerAnalyticsAdapter({ code: 'adkernelAdn', adapter: analyticsAdapter }); - adaptermanager.enableAnalytics({ + adapterManager.enableAnalytics({ provider: 'adkernelAdn', options: { pubId: 777, @@ -254,7 +253,7 @@ describe('', function () { let ev = analyticsAdapter.context.queue.peekAll(); expect(ev).to.have.length(0); expect(ajaxStub.calledOnce).to.be.equal(true); - ev = JSON.parse(ajaxStub.firstCall.args[2]).hb_ev; + ev = JSON.parse(ajaxStub.firstCall.args[0]).hb_ev; expect(ev[3]).to.be.eql({event: 'auctionEnd', time: 0.447}); }); @@ -262,7 +261,7 @@ describe('', function () { events.emit(CONSTANTS.EVENTS.BID_WON, RESPONSE); timer.tick(4500); expect(ajaxStub.calledTwice).to.be.equal(true); - let ev = JSON.parse(ajaxStub.secondCall.args[2]).hb_ev; + let ev = JSON.parse(ajaxStub.secondCall.args[0]).hb_ev; expect(ev[0]).to.be.eql({event: 'bidWon', adapter: 'adapter', tagid: 'container-1', val: 0.015}); }); }); diff --git a/test/spec/modules/adkernelAdnBidAdapter_spec.js b/test/spec/modules/adkernelAdnBidAdapter_spec.js index fe71d968571..1147520131b 100644 --- a/test/spec/modules/adkernelAdnBidAdapter_spec.js +++ b/test/spec/modules/adkernelAdnBidAdapter_spec.js @@ -1,6 +1,5 @@ import {expect} from 'chai'; import {spec} from 'modules/adkernelAdnBidAdapter'; -import * as utils from 'src/utils'; describe('AdkernelAdn adapter', function () { const bid1_pub1 = { @@ -10,12 +9,16 @@ describe('AdkernelAdn adapter', function () { auctionId: '5c66da22-426a-4bac-b153-77360bef5337', bidId: 'bidid_1', params: { - pubId: 1 + pubId: 1, + host: 'tag.adkernel.com' + }, + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 200]] + } }, adUnitCode: 'ad-unit-1', - sizes: [[300, 250], [300, 200]] - }, - bid2_pub1 = { + }, bid2_pub1 = { bidder: 'adkernelAdn', transactionId: 'transact0', bidderRequestId: 'req0', @@ -25,9 +28,12 @@ describe('AdkernelAdn adapter', function () { pubId: 1 }, adUnitCode: 'ad-unit-2', - sizes: [300, 250] - }, - bid1_pub2 = { + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + } + }, bid1_pub2 = { bidder: 'adkernelAdn', transactionId: 'transact2', bidderRequestId: 'req1', @@ -38,23 +44,29 @@ describe('AdkernelAdn adapter', function () { host: 'dps-test.com' }, adUnitCode: 'ad-unit-2', - sizes: [[728, 90]] + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + } }, bid_video1 = { bidder: 'adkernelAdn', transactionId: 'transact3', bidderRequestId: 'req1', auctionId: '5c66da22-426a-4bac-b153-77360bef5337', bidId: 'bidid_4', - mediaType: 'video', - sizes: [640, 300], - adUnitCode: 'video_wrapper', - params: { - pubId: 7, + mediaTypes: { video: { - mimes: ['video/mp4', 'video/webm', 'video/x-flv'], - api: [1, 2, 3, 4], - protocols: [1, 2, 3, 4, 5, 6] + context: 'instream', + playerSize: [640, 300], + mimes: ['video/mp4', 'video/webm'], + api: [1, 2], + protocols: [5, 6] } + }, + adUnitCode: 'video_wrapper', + params: { + pubId: 7 } }, bid_video2 = { bidder: 'adkernelAdn', @@ -69,16 +81,23 @@ describe('AdkernelAdn adapter', function () { context: 'instream' } }, - adUnitCode: 'video_wrapper2', params: { - pubId: 7, - video: { - mimes: ['video/mp4', 'video/webm', 'video/x-flv'], - api: [1, 2, 3, 4], - protocols: [1, 2, 3, 4, 5, 6] - } + pubId: 7 } + }, bid_multiformat = { + bidder: 'adkernelAdn', + transactionId: 'f82c64b8-c602-42a4-9791-4a268f6559ed', + bidderRequestId: 'req-001', + auctionId: 'auc-001', + bidId: 'Bid_01', + sizes: [[300, 250], [300, 200]], + mediaTypes: { + banner: {sizes: [[300, 250], [300, 200]]}, + video: {context: 'instream', playerSize: [[640, 480]]} + }, + adUnitCode: 'ad-unit-1', + params: {pubId: 7} }; const response = { @@ -110,12 +129,28 @@ describe('AdkernelAdn adapter', function () { syncpages: ['https://dsp.adkernel.com/sync'] }; - describe('input parameters validation', function () { - it('empty request shouldn\'t generate exception', function () { - expect(spec.isBidRequestValid({bidderCode: 'adkernelAdn' + const defaultBidderRequest = { + bidderCode: 'adkernelAdn', + bids: [], + auctionStart: 1545836987704, + timeout: 3000, + refererInfo: { + referer: 'https://example.com/index.html', + reachedTop: true, + numIframes: 0, + stack: ['https://example.com/index.html'] + }, + start: 1545836987707 + }; + + describe('input parameters validation', () => { + it('empty request shouldn\'t generate exception', () => { + expect(spec.isBidRequestValid({ + bidderCode: 'adkernelAdn' })).to.be.equal(false); }); - it('request without pubid should be ignored', function () { + + it('request without pubid should be ignored', () => { expect(spec.isBidRequestValid({ bidder: 'adkernelAdn', params: {}, @@ -123,7 +158,8 @@ describe('AdkernelAdn adapter', function () { sizes: [[300, 250]] })).to.be.equal(false); }); - it('request with invalid pubid should be ignored', function () { + + it('request with invalid pubid should be ignored', () => { expect(spec.isBidRequestValid({ bidder: 'adkernelAdn', params: { @@ -133,26 +169,44 @@ describe('AdkernelAdn adapter', function () { sizes: [[300, 250]] })).to.be.equal(false); }); - }); - function buildRequest(bidRequests, bidderRequest = {}) { - let mock = sinon.stub(utils, 'getTopWindowLocation').callsFake(() => { - return { - protocol: 'https:', - hostname: 'example.com', - host: 'example.com', - pathname: '/index.html', - href: 'https://example.com/index.html' - }; + it('request with totally invalid host should be ignored', () => { + expect(spec.isBidRequestValid({ + bidder: 'adkernelAdn', + params: { + pubId: 1, + host: 1 + }, + placementCode: 'ad-unit-0', + sizes: [[300, 250]] + })).to.be.equal(false); }); - bidderRequest.auctionId = bidRequests[0].auctionId; - bidderRequest.transactionId = bidRequests[0].transactionId; - bidderRequest.bidderRequestId = bidRequests[0].bidderRequestId; + it('valid request should be accepted', () => { + expect(spec.isBidRequestValid({ + bidder: 'adkernelAdn', + params: { + pubId: 1, + host: 'network.com' + }, + placementCode: 'ad-unit-0', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 200]] + } + } + })).to.be.equal(true); + }); + }); - let pbRequests = spec.buildRequests(bidRequests, bidderRequest); + function buildRequest(bidRequests, bidderRequestAugments = {}) { + let fullBidderRequest = Object.assign(defaultBidderRequest, bidderRequestAugments); + fullBidderRequest.auctionId = bidRequests[0].auctionId; + fullBidderRequest.transactionId = bidRequests[0].transactionId; + fullBidderRequest.bidderRequestId = bidRequests[0].bidderRequestId; + fullBidderRequest.bids = bidRequests; + let pbRequests = spec.buildRequests(bidRequests, fullBidderRequest); let tagRequests = pbRequests.map(r => JSON.parse(r.data)); - mock.restore(); return [pbRequests, tagRequests]; } @@ -164,19 +218,24 @@ describe('AdkernelAdn adapter', function () { it('should have request id', function () { expect(tagRequest).to.have.property('id'); }); + it('should have transaction id', function () { expect(tagRequest).to.have.property('tid'); }); + it('should have sizes', function () { expect(tagRequest.imp[0].banner).to.have.property('format'); expect(tagRequest.imp[0].banner.format).to.be.eql(['300x250', '300x200']); }); + it('should have impression id', function () { expect(tagRequest.imp[0]).to.have.property('id', 'bidid_1'); }); + it('should have tagid', function () { expect(tagRequest.imp[0]).to.have.property('tagid', 'ad-unit-1'); }); + it('should create proper site block', function () { expect(tagRequest.site).to.have.property('page', 'https://example.com/index.html'); expect(tagRequest.site).to.have.property('secure', 1); @@ -207,24 +266,50 @@ describe('AdkernelAdn adapter', function () { }); }); - describe('video request building', function () { + describe('video request building', () => { let [_, tagRequests] = buildRequest([bid_video1, bid_video2]); let tagRequest = tagRequests[0]; - it('should have video object', function () { + it('should have video object', () => { expect(tagRequest.imp[0]).to.have.property('video'); expect(tagRequest.imp[1]).to.have.property('video'); }); - it('should have tagid', function () { + + it('should have tagid', () => { expect(tagRequest.imp[0]).to.have.property('tagid', 'video_wrapper'); expect(tagRequest.imp[1]).to.have.property('tagid', 'video_wrapper2'); }); - it('should have size', function () { + + it('should have size', () => { expect(tagRequest.imp[0].video).to.have.property('w', 640); expect(tagRequest.imp[0].video).to.have.property('h', 300); expect(tagRequest.imp[1].video).to.have.property('w', 1920); expect(tagRequest.imp[1].video).to.have.property('h', 1080); }); + + it('should have video params', () => { + expect(tagRequest.imp[0].video).to.have.property('mimes'); + expect(tagRequest.imp[0].video.mimes).to.be.eql(['video/mp4', 'video/webm']); + expect(tagRequest.imp[0].video).to.have.property('api'); + expect(tagRequest.imp[0].video.api).to.be.eql([1, 2]); + expect(tagRequest.imp[0].video).to.have.property('protocols'); + expect(tagRequest.imp[0].video.protocols).to.be.eql([5, 6]); + }); + }); + + describe('multiformat request building', function () { + let [_, tagRequests] = buildRequest([bid_multiformat]); + + it('should contain single request', function () { + expect(tagRequests).to.have.length(1); + expect(tagRequests[0].imp).to.have.length(1); + }); + + it('should contain banner-only impression', function () { + expect(tagRequests[0].imp).to.have.length(1); + expect(tagRequests[0].imp[0]).to.have.property('banner'); + expect(tagRequests[0].imp[0]).to.not.have.property('video'); + }); }); describe('requests routing', function () { @@ -236,6 +321,7 @@ describe('AdkernelAdn adapter', function () { expect(tagRequests[0].imp).to.have.length(1); expect(tagRequests[1].imp).to.have.length(1); }); + it('should issue a request for each host', function () { let [pbRequests, tagRequests] = buildRequest([bid1_pub1, bid1_pub2]); expect(pbRequests).to.have.length(2); @@ -248,12 +334,15 @@ describe('AdkernelAdn adapter', function () { describe('responses processing', function () { let responses; + before(function () { responses = spec.interpretResponse({body: response}); }); + it('should parse all responses', function () { expect(responses).to.have.length(3); }); + it('should return fully-initialized bid-response', function () { let resp = responses[0]; expect(resp).to.have.property('bidderCode', 'adkernelAdn'); @@ -268,6 +357,7 @@ describe('AdkernelAdn adapter', function () { expect(resp).to.have.property('ad'); expect(resp.ad).to.have.string(''); }); + it('should return fully-initialized video bid-response', function () { let resp = responses[2]; expect(resp).to.have.property('bidderCode', 'adkernelAdn'); @@ -280,6 +370,7 @@ describe('AdkernelAdn adapter', function () { expect(resp).to.have.property('vastUrl', 'http://vast.com/vast.xml'); expect(resp).to.not.have.property('ad'); }); + it('should perform usersync', function () { let syncs = spec.getUserSyncs({iframeEnabled: false}, [{body: response}]); expect(syncs).to.have.length(0); @@ -288,14 +379,23 @@ describe('AdkernelAdn adapter', function () { expect(syncs[0]).to.have.property('type', 'iframe'); expect(syncs[0]).to.have.property('url', 'https://dsp.adkernel.com/sync'); }); + it('should handle user-sync only response', function () { let [pbRequests, tagRequests] = buildRequest([bid1_pub1]); let resp = spec.interpretResponse({body: usersyncOnlyResponse}, pbRequests[0]); expect(resp).to.have.length(0); }); + it('shouldn\' fail on empty response', function () { let syncs = spec.getUserSyncs({iframeEnabled: true}, [{body: ''}]); expect(syncs).to.have.length(0); }); }); + + describe('adapter configuration', () => { + it('should have aliases', () => { + expect(spec.aliases).to.have.lengthOf(1); + expect(spec.aliases[0]).to.be.equal('engagesimply'); + }); + }); }); diff --git a/test/spec/modules/adkernelBidAdapter_spec.js b/test/spec/modules/adkernelBidAdapter_spec.js index 590e0ebb96e..ddb3f3dddf2 100644 --- a/test/spec/modules/adkernelBidAdapter_spec.js +++ b/test/spec/modules/adkernelBidAdapter_spec.js @@ -1,45 +1,80 @@ import {expect} from 'chai'; import {spec} from 'modules/adkernelBidAdapter'; import * as utils from 'src/utils'; -import {parse as parseUrl} from 'src/url'; describe('Adkernel adapter', function () { const bid1_zone1 = { bidder: 'adkernel', - bidId: 'Bid_01', params: {zoneId: 1, host: 'rtb.adkernel.com'}, adUnitCode: 'ad-unit-1', - sizes: [[300, 250], [300, 200]] + bidId: 'Bid_01', + bidderRequestId: 'req-001', + auctionId: 'auc-001', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 200]] + } + } }, bid2_zone2 = { bidder: 'adkernel', - bidId: 'Bid_02', params: {zoneId: 2, host: 'rtb.adkernel.com'}, adUnitCode: 'ad-unit-2', - sizes: [728, 90] + bidId: 'Bid_02', + bidderRequestId: 'req-001', + auctionId: 'auc-001', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + } }, bid3_host2 = { bidder: 'adkernel', - bidId: 'Bid_02', params: {zoneId: 1, host: 'rtb-private.adkernel.com'}, adUnitCode: 'ad-unit-2', - sizes: [[728, 90]] + bidId: 'Bid_02', + bidderRequestId: 'req-001', + auctionId: 'auc-001', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + } }, bid_without_zone = { bidder: 'adkernel', - bidId: 'Bid_W', params: {host: 'rtb-private.adkernel.com'}, adUnitCode: 'ad-unit-1', - sizes: [[728, 90]] + bidId: 'Bid_W', + bidderRequestId: 'req-002', + auctionId: 'auc-002', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + } }, bid_without_host = { bidder: 'adkernel', - bidId: 'Bid_W', params: {zoneId: 1}, adUnitCode: 'ad-unit-1', - sizes: [[728, 90]] + bidId: 'Bid_W', + bidderRequestId: 'req-002', + auctionId: 'auc-002', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + } }, bid_with_wrong_zoneId = { bidder: 'adkernel', - bidId: 'Bid_02', params: {zoneId: 'wrong id', host: 'rtb.adkernel.com'}, adUnitCode: 'ad-unit-2', - sizes: [[728, 90]] + bidId: 'Bid_02', + bidderRequestId: 'req-002', + auctionId: 'auc-002', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + } }, bid_video = { bidder: 'adkernel', transactionId: '866394b8-5d37-4d49-803e-f1bdb595f73e', @@ -49,7 +84,8 @@ describe('Adkernel adapter', function () { sizes: [[640, 480]], params: { zoneId: 1, - host: 'rtb.adkernel.com' + host: 'rtb.adkernel.com', + video: {api: [1, 2]} }, mediaTypes: { video: { @@ -58,6 +94,19 @@ describe('Adkernel adapter', function () { } }, adUnitCode: 'ad-unit-1' + }, bid_multiformat = { + bidder: 'adkernel', + params: {zoneId: 1, host: 'rtb.adkernel.com'}, + mediaTypes: { + banner: {sizes: [[300, 250], [300, 200]]}, + video: {context: 'instream', playerSize: [[640, 480]]} + }, + adUnitCode: 'ad-unit-1', + transactionId: 'f82c64b8-c602-42a4-9791-4a268f6559ed', + sizes: [[300, 250], [300, 200]], + bidId: 'Bid_01', + bidderRequestId: 'req-001', + auctionId: 'auc-001' }; const bidResponse1 = { @@ -113,17 +162,15 @@ describe('Adkernel adapter', function () { } }; - function buildRequest(bidRequests, bidderRequest = {}, url = 'https://example.com/index.html', dnt = true) { - let wmock = sinon.stub(utils, 'getTopWindowLocation').callsFake(() => { - let loc = parseUrl(url); - loc.protocol += ':'; - return loc; - }); + function buildBidderRequest(url = 'https://example.com/index.html', params = {}) { + return Object.assign({}, params, {refererInfo: {referer: url, reachedTop: true}}) + } + const DEFAULT_BIDDER_REQUEST = buildBidderRequest(); + function buildRequest(bidRequests, bidderRequest = DEFAULT_BIDDER_REQUEST, dnt = true) { let dntmock = sinon.stub(utils, 'getDNT').callsFake(() => dnt); let pbRequests = spec.buildRequests(bidRequests, bidderRequest); - wmock.restore(); dntmock.restore(); - let rtbRequests = pbRequests.map(r => JSON.parse(r.data.r)); + let rtbRequests = pbRequests.map(r => JSON.parse(r.data)); return [pbRequests, rtbRequests]; } @@ -162,6 +209,11 @@ describe('Adkernel adapter', function () { expect(bidRequest.imp[0]).to.have.property('banner'); }); + it('should have id', function () { + expect(bidRequest.imp[0]).to.have.property('id'); + expect(bidRequest.imp[0].id).to.be.eql('Bid_01'); + }); + it('should have w/h', function () { expect(bidRequest.imp[0].banner).to.have.property('format'); expect(bidRequest.imp[0].banner.format).to.be.eql([{w: 300, h: 250}, {w: 300, h: 200}]); @@ -195,7 +247,8 @@ describe('Adkernel adapter', function () { it('should contain gdpr-related information if consent is configured', function () { let [_, bidRequests] = buildRequest([bid1_zone1], - {gdprConsent: {gdprApplies: true, consentString: 'test-consent-string', vendorData: {}}}); + buildBidderRequest('http://example.com/index.html', + {gdprConsent: {gdprApplies: true, consentString: 'test-consent-string', vendorData: {}}})); let bidRequest = bidRequests[0]; expect(bidRequest).to.have.property('regs'); expect(bidRequest.regs.ext).to.be.eql({'gdpr': 1}); @@ -204,7 +257,7 @@ describe('Adkernel adapter', function () { }); it('should\'t contain consent string if gdpr isn\'t applied', function () { - let [_, bidRequests] = buildRequest([bid1_zone1], {gdprConsent: {gdprApplies: false}}); + let [_, bidRequests] = buildRequest([bid1_zone1], buildBidderRequest('https://example.com/index.html', {gdprConsent: {gdprApplies: false}})); let bidRequest = bidRequests[0]; expect(bidRequest).to.have.property('regs'); expect(bidRequest.regs.ext).to.be.eql({'gdpr': 0}); @@ -212,7 +265,7 @@ describe('Adkernel adapter', function () { }); it('should\'t pass dnt if state is unknown', function () { - let [_, bidRequests] = buildRequest([bid1_zone1], {}, 'https://example.com/index.html', false); + let [_, bidRequests] = buildRequest([bid1_zone1], DEFAULT_BIDDER_REQUEST, false); expect(bidRequests[0].device).to.not.have.property('dnt'); }); }); @@ -235,6 +288,27 @@ describe('Adkernel adapter', function () { it('should have tagid', function () { expect(bidRequests[0].imp[0]).to.have.property('tagid', 'ad-unit-1'); }); + + it('should have openrtb video impression parameters', function() { + expect(bidRequests[0].imp[0].video).to.have.property('api'); + expect(bidRequests[0].imp[0].video.api).to.be.eql([1, 2]); + }); + }); + + describe('multiformat request building', function () { + let _, bidRequests; + before(function () { + [_, bidRequests] = buildRequest([bid_multiformat]); + }); + it('should contain single request', function () { + expect(bidRequests).to.have.length(1); + expect(bidRequests[0].imp).to.have.length(1); + }); + it('should contain banner-only impression', function () { + expect(bidRequests[0].imp).to.have.length(1); + expect(bidRequests[0].imp[0]).to.have.property('banner'); + expect(bidRequests[0].imp[0]).to.not.have.property('video'); + }); }); describe('requests routing', function () { @@ -248,8 +322,8 @@ describe('Adkernel adapter', function () { it('should issue a request for each zone', function () { let [pbRequests, _] = buildRequest([bid1_zone1, bid2_zone2]); expect(pbRequests).to.have.length(2); - expect(pbRequests[0].data.zone).to.be.equal(bid1_zone1.params.zoneId); - expect(pbRequests[1].data.zone).to.be.equal(bid2_zone2.params.zoneId); + expect(pbRequests[0].url).to.include(`zone=${bid1_zone1.params.zoneId}`); + expect(pbRequests[1].url).to.include(`zone=${bid2_zone2.params.zoneId}`); }); }); @@ -302,4 +376,11 @@ describe('Adkernel adapter', function () { expect(syncs[0]).to.have.property('url', 'http://adk.sync.com/sync'); }); }); + + describe('adapter configuration', () => { + it('should have aliases', () => { + expect(spec.aliases).to.have.lengthOf(5); + expect(spec.aliases).to.be.eql(['headbidding', 'adsolut', 'oftmediahb', 'audiencemedia', 'waardex_ak']); + }); + }); }); diff --git a/test/spec/modules/adliveBidAdapter_spec.js b/test/spec/modules/adliveBidAdapter_spec.js new file mode 100644 index 00000000000..0048fc028b8 --- /dev/null +++ b/test/spec/modules/adliveBidAdapter_spec.js @@ -0,0 +1,78 @@ +import { expect } from 'chai'; +import { spec } from 'modules/adliveBidAdapter'; + +describe('adliveBidAdapterTests', function() { + let bidRequestData = { + bids: [ + { + bidId: 'transaction_1234', + bidder: 'adlive', + params: { + hashes: ['1e100887dd614b0909bf6c49ba7f69fdd1360437'] + }, + sizes: [[300, 250]] + } + ] + }; + let request = []; + + it('validate_pub_params', function() { + expect( + spec.isBidRequestValid({ + bidder: 'adlive', + params: { + hashes: ['1e100887dd614b0909bf6c49ba7f69fdd1360437'] + } + }) + ).to.equal(true); + }); + + it('validate_generated_params', function() { + request = spec.buildRequests(bidRequestData.bids); + let req_data = JSON.parse(request[0].data); + + expect(req_data.transaction_id).to.equal('transaction_1234'); + }); + + it('validate_response_params', function() { + let serverResponse = { + body: [ + { + hash: '1e100887dd614b0909bf6c49ba7f69fdd1360437', + content: 'Ad html', + price: 1.12, + size: [300, 250], + is_passback: 0 + } + ] + }; + + let bids = spec.interpretResponse(serverResponse, bidRequestData.bids[0]); + expect(bids).to.have.lengthOf(1); + + let bid = bids[0]; + + expect(bid.creativeId).to.equal('1e100887dd614b0909bf6c49ba7f69fdd1360437'); + expect(bid.ad).to.equal('Ad html'); + expect(bid.cpm).to.equal(1.12); + expect(bid.width).to.equal(300); + expect(bid.height).to.equal(250); + expect(bid.currency).to.equal('USD'); + }); + + it('validate_response_params_with passback', function() { + let serverResponse = { + body: [ + { + hash: '1e100887dd614b0909bf6c49ba7f69fdd1360437', + content: 'Ad html passback', + size: [300, 250], + is_passback: 1 + } + ] + }; + let bids = spec.interpretResponse(serverResponse, bidRequestData.bids[0]); + + expect(bids).to.have.lengthOf(0); + }); +}); diff --git a/test/spec/modules/admanBidAdapter_spec.js b/test/spec/modules/admanBidAdapter_spec.js new file mode 100644 index 00000000000..37a097427d5 --- /dev/null +++ b/test/spec/modules/admanBidAdapter_spec.js @@ -0,0 +1,215 @@ +import {expect} from 'chai'; +import {spec} from 'modules/admanBidAdapter'; +import {newBidder} from 'src/adapters/bidderFactory'; + +const ENDPOINT = '//bidtor.admanmedia.com/prebid'; +const BANNER = '"'; +const VAST = ''; +const USER_SYNC_IFRAME_URL = '//cs.admanmedia.com/sync_tag/html'; + +describe('admanBidAdapter', function() { + const adapter = newBidder(spec); + + describe('inherited functions', function() { + it('exists and is a function', function() { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function() { + let bid = { + 'bidder': 'adman', + 'params': { + 'id': '1234asdf' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + 'creativeId': 'er2ee' + }; + + it('should return true when required params found', function() { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when id is not valid (not string)', function() { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'id': 1234 + }; + + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when required params are not passed', function() { + let bid = Object.assign({}, bid); + delete bid.params; + + bid.params = {}; + + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function() { + let bidRequests = [ + { + 'bidder': 'adman', + 'bidId': '51ef8751f9aead', + 'params': { + 'id': '1234asdf' + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + 'sizes': [[320, 50], [300, 250], [300, 600]], + 'bidderRequestId': '418b37f85e772c', + 'auctionId': '18fd8b8b0bd757', + 'bidRequestsCount': 1 + } + ]; + + it('sends a valid bid request to ENDPOINT via POST', function() { + const request = spec.buildRequests(bidRequests, { + 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 + } + }); + + expect(request.url).to.equal(ENDPOINT); + expect(request.method).to.equal('POST'); + + const payload = JSON.parse(request.data); + expect(payload.gdpr).to.exist; + + expect(payload.bids).to.exist.and.to.be.an('array').and.to.have.lengthOf(1); + expect(payload.referer).to.exist; + + const bid = payload.bids[0]; + expect(bid).to.exist; + expect(bid.params).to.exist; + expect(bid.params.id).to.exist; + expect(bid.params.bidId).to.exist; + expect(bid.sizes).to.exist.and.to.be.an('array').and.to.have.lengthOf(3); + bid.sizes.forEach(size => { + expect(size).to.be.an('array').and.to.have.lengthOf(2); + expect(size[0]).to.be.a('number'); + expect(size[1]).to.be.a('number'); + }) + }); + + it('should send GDPR to endpoint and honor gdprApplies value', function() { + let consentString = 'bogusConsent'; + let bidderRequest = { + 'gdprConsent': { + 'consentString': consentString, + 'gdprApplies': true + } + }; + + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = JSON.parse(request.data); + expect(payload.gdpr).to.exist; + expect(payload.gdpr.consent).to.equal(consentString); + expect(payload.gdpr.applies).to.equal(true); + + let bidderRequest2 = { + 'gdprConsent': { + 'consentString': consentString, + 'gdprApplies': false + } + }; + + const request2 = spec.buildRequests(bidRequests, bidderRequest2); + const payload2 = JSON.parse(request2.data); + + expect(payload2.gdpr).to.exist; + expect(payload2.gdpr.consent).to.equal(consentString); + expect(payload2.gdpr.applies).to.equal(false); + }); + }); + + describe('interpretResponse', function() { + let bids = { + 'body': { + 'bids': [{ + 'ad': BANNER, + 'height': 250, + 'cpm': 0.5, + 'currency': 'USD', + 'netRevenue': true, + 'requestId': '3ede2a3fa0db94', + 'ttl': 3599, + 'width': 300, + 'creativeId': 'er2ee' + }, + { + 'vastXml': VAST, + 'cpm': 0.5, + 'currency': 'USD', + 'height': 250, + 'netRevenue': true, + 'requestId': '3ede2a3fa0db95', + 'ttl': 3599, + 'width': 300, + 'creativeId': 'er2ef' + }] + } + }; + + it('should get correct bid response', function() { + let expectedResponse = [{ + 'ad': BANNER, + 'cpm': 0.5, + 'creativeId': 'er2ee', + 'currency': 'USD', + 'height': 250, + 'netRevenue': true, + 'requestId': '3ede2a3fa0db94', + 'ttl': 3599, + 'width': 300, + }, + { + 'vastXml': VAST, + 'cpm': 0.5, + 'creativeId': 'er2ef', + 'currency': 'USD', + 'height': 250, + 'netRevenue': true, + 'requestId': '3ede2a3fa0db95', + 'ttl': 3599, + 'width': 300, + }]; + // los bids vienen formateados de server + let result = spec.interpretResponse(bids); + + expect(result[0]).to.deep.equal(expectedResponse[0]); + expect(result[1]).to.deep.equal(expectedResponse[1]); + // expect(Object.keys(result[1])).to.deep.equal(Object.keys(bids[1])); + }); + + it('handles nobid responses', function() { + let bids = { + 'body': { + 'bids': [] + } + }; + + let result = spec.interpretResponse(bids); + expect(result.length).to.equal(0); + }); + }); + describe('getUserSyncs', () => { + it('should get correct user sync iframe url', function() { + expect(spec.getUserSyncs({ + iframeEnabled: true + }, [{}])).to.deep.equal([{ + type: 'iframe', + url: USER_SYNC_IFRAME_URL + }]); + }); + }); +}); diff --git a/test/spec/modules/admediaBidAdapter_spec.js b/test/spec/modules/admediaBidAdapter_spec.js new file mode 100644 index 00000000000..2e14eee938c --- /dev/null +++ b/test/spec/modules/admediaBidAdapter_spec.js @@ -0,0 +1,138 @@ +import { expect } from 'chai'; +import { spec } from 'modules/admediaBidAdapter'; + +describe('admediaAdapterTests', function () { + describe('bidRequestValidity', function () { + it('bidRequest with aid', function () { + expect(spec.isBidRequestValid({ + bidder: 'admedia', + params: { + aid: 86858, + } + })).to.equal(true); + }); + + it('bidRequest without aid', function () { + expect(spec.isBidRequestValid({ + bidder: 'a4g', + params: { + key: 86858 + } + })).to.equal(false); + }); + }); + + describe('bidRequest', function () { + const validBidRequests = [{ + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'auctionId': 'e3010a3c-5b95-4475-9ba2-1b004c737c30', + 'bidId': '2758de47c84ab58', + 'bidRequestsCount': 1, + 'bidder': 'admedia', + 'bidderRequestId': '1033407c6af0c7', + 'params': { + 'aid': 86858, + }, + 'sizes': [[300, 250], [300, 600]], + 'transactionId': '5851b2cf-ee2d-4022-abd2-d581ef01604e' + }, { + 'adUnitCode': 'div-gpt-ad-1460505748561-1', + 'auctionId': 'e3010a3c-5b95-4475-9ba2-1b004c737c30', + 'bidId': '3d2aaa400371fa', + 'bidRequestsCount': 1, + 'bidder': 'admedia', + 'bidderRequestId': '1033407c6af0c7', + 'params': { + 'aid': 84977, + }, + 'sizes': [[728, 90]], + 'transactionId': 'f8b5247e-7715-4e60-9d51-33153e78c190' + }]; + + const bidderRequest = { + 'auctionId': 'e3010a3c-5b95-4475-9ba2-1b004c737c30', + 'bidderCode': 'admedia', + 'bidderRequestId': '1033407c6af0c7', + 'refererInfo': { + 'numIframes': 0, + 'reachedTop': true, + 'referer': 'http://test.com/index.html?pbjs_debug=true' + } + + }; + + const request = spec.buildRequests(validBidRequests, bidderRequest); + + it('bidRequest method', function () { + expect(request.method).to.equal('POST'); + }); + + it('bidRequest url', function () { + expect(request.url).to.equal('//prebid.admedia.com/bidder/'); + }); + + it('bidRequest data', function () { + const data = JSON.parse(request.data); + expect(decodeURIComponent(data.referer)).to.be.eql(bidderRequest.refererInfo.referer); + expect(data.tags).to.be.an('array'); + expect(data.tags[0].aid).to.be.eql(validBidRequests[0].params.aid); + expect(data.tags[0].id).to.be.eql(validBidRequests[0].bidId); + expect(data.tags[0].sizes).to.be.eql(validBidRequests[0].sizes); + expect(data.tags[1].aid).to.be.eql(validBidRequests[1].params.aid); + expect(data.tags[1].id).to.be.eql(validBidRequests[1].bidId); + expect(data.tags[1].sizes).to.be.eql(validBidRequests[1].sizes); + }); + }); + + describe('interpretResponse', function () { + const serverResponse = { + body: { + tags: [ + { + ad: '', + cpm: 0.9, + height: 250, + id: '5582180864bc41', + width: 300, + }, + { + error: 'Error message', + id: '6dc6ee4e157749' + }, + { + ad: '', + cpm: 0, + height: 728, + id: '5762180864bc41', + width: 90, + } + ] + }, + headers: {} + }; + + const bidRequest = {}; + + const result = spec.interpretResponse(serverResponse, bidRequest); + + it('Should return an empty array if empty or no tags in response', function () { + expect(spec.interpretResponse({body: ''}, {}).length).to.equal(0); + }); + + it('Should have only one bid', function () { + expect(result.length).to.equal(1); + }); + + it('Should have required keys', function () { + expect(result[0].requestId).to.be.eql(serverResponse.body.tags[0].id); + expect(result[0].cpm).to.be.eql(serverResponse.body.tags[0].cpm); + expect(result[0].width).to.be.eql(serverResponse.body.tags[0].width); + expect(result[0].height).to.be.eql(serverResponse.body.tags[0].height); + expect(result[0].creativeId).to.be.eql(serverResponse.body.tags[0].id); + expect(result[0].dealId).to.be.eql(serverResponse.body.tags[0].id); + expect(result[0].netRevenue).to.be.eql(true); + expect(result[0].ttl).to.be.eql(120); + expect(result[0].ad).to.be.eql(serverResponse.body.tags[0].ad); + }) + }); +}); diff --git a/test/spec/modules/adoceanBidAdapter_spec.js b/test/spec/modules/adoceanBidAdapter_spec.js index c9f33b940b5..1fd009ce526 100644 --- a/test/spec/modules/adoceanBidAdapter_spec.js +++ b/test/spec/modules/adoceanBidAdapter_spec.js @@ -36,6 +36,7 @@ describe('AdoceanAdapter', function () { bid.params = { 'masterId': 0 }; + expect(spec.isBidRequestValid(bid)).to.equal(false); }); }); @@ -54,6 +55,19 @@ describe('AdoceanAdapter', function () { 'bidId': '30b31c1838de1e', 'bidderRequestId': '22edbae2733bf6', 'auctionId': '1d1a030790a475', + }, + { + 'bidder': 'adocean', + 'params': { + 'masterId': 'tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7', + 'slaveId': 'adoceanmyaozpniqismex', + 'emiter': 'myao.adocean.pl' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250]], + 'bidId': '30b31c1838de1f', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', } ]; @@ -64,11 +78,18 @@ describe('AdoceanAdapter', function () { } }; - it('should add bidIdMap with slaveId => bidId mapping', function () { - const request = spec.buildRequests(bidRequests, bidderRequest)[0]; - expect(request.bidIdMap).to.exists; - const bidIdMap = request.bidIdMap; - expect(bidIdMap[bidRequests[0].params.slaveId]).to.equal(bidRequests[0].bidId); + it('should send two requests if slave is duplicated', () => { + const nrOfRequests = spec.buildRequests(bidRequests, bidderRequest).length; + expect(nrOfRequests).to.equal(2); + }); + + it('should add bidIdMap with correct slaveId => bidId mapping', () => { + const requests = spec.buildRequests(bidRequests, bidderRequest); + for (let i = 0; i < bidRequests.length; i++) { + expect(requests[i]).to.exist; + expect(requests[i].bidIdMap).to.exist; + expect(requests[i].bidIdMap[bidRequests[i].params.slaveId]).to.equal(bidRequests[i].bidId); + } }); it('sends bid request to url via GET', function () { diff --git a/test/spec/modules/adomikAnalyticsAdapter_spec.js b/test/spec/modules/adomikAnalyticsAdapter_spec.js index cb3c30705f4..16480d746b7 100644 --- a/test/spec/modules/adomikAnalyticsAdapter_spec.js +++ b/test/spec/modules/adomikAnalyticsAdapter_spec.js @@ -1,7 +1,7 @@ import adomikAnalytics from 'modules/adomikAnalyticsAdapter'; import {expect} from 'chai'; let events = require('src/events'); -let adaptermanager = require('src/adaptermanager'); +let adapterManager = require('src/adapterManager').default; let constants = require('src/constants.json'); describe('Adomik Prebid Analytic', function () { @@ -28,7 +28,7 @@ describe('Adomik Prebid Analytic', function () { }); it('should catch all events', function (done) { - adaptermanager.registerAnalyticsAdapter({ + adapterManager.registerAnalyticsAdapter({ code: 'adomik', adapter: adomikAnalytics }); @@ -55,7 +55,7 @@ describe('Adomik Prebid Analytic', function () { } // Step 1: Initialize adapter - adaptermanager.enableAnalytics({ + adapterManager.enableAnalytics({ provider: 'adomik', options: initOptions }); diff --git a/test/spec/modules/adpod_spec.js b/test/spec/modules/adpod_spec.js new file mode 100644 index 00000000000..8b7701c5631 --- /dev/null +++ b/test/spec/modules/adpod_spec.js @@ -0,0 +1,1232 @@ +import * as utils from 'src/utils'; +import { config } from 'src/config'; +import * as videoCache from 'src/videoCache'; +import * as auction from 'src/auction'; +import { ADPOD } from 'src/mediaTypes'; + +import { callPrebidCacheHook, checkAdUnitSetupHook, checkVideoBidSetupHook, adpodSetConfig, sortByPricePerSecond } from 'modules/adpod'; + +let expect = require('chai').expect; + +describe('adpod.js', function () { + let logErrorStub; + let logWarnStub; + let logInfoStub; + + describe('callPrebidCacheHook', function () { + let callbackResult; + let clock; + let addBidToAuctionStub; + let doCallbacksIfTimedoutStub; + let storeStub; + let afterBidAddedSpy; + let auctionBids = []; + + let callbackFn = function() { + callbackResult = true; + }; + + let auctionInstance = { + getAuctionStatus: function() { + return auction.AUCTION_IN_PROGRESS; + } + } + + const fakeStoreFn = function(bids, callback) { + let payload = []; + bids.forEach(bid => payload.push({uuid: bid.customCacheKey})); + callback(null, payload); + }; + + beforeEach(function() { + callbackResult = null; + afterBidAddedSpy = sinon.spy(); + storeStub = sinon.stub(videoCache, 'store'); + logWarnStub = sinon.stub(utils, 'logWarn'); + logInfoStub = sinon.stub(utils, 'logInfo'); + addBidToAuctionStub = sinon.stub(auction, 'addBidToAuction').callsFake(function (auctionInstance, bid) { + auctionBids.push(bid); + }); + doCallbacksIfTimedoutStub = sinon.stub(auction, 'doCallbacksIfTimedout'); + clock = sinon.useFakeTimers(); + config.setConfig({ + cache: { + url: 'https://prebid.adnxs.com/pbc/v1/cache' + } + }); + }); + + afterEach(function() { + storeStub.restore(); + logWarnStub.restore(); + logInfoStub.restore(); + addBidToAuctionStub.restore(); + doCallbacksIfTimedoutStub.restore(); + clock.restore(); + config.resetConfig(); + auctionBids = []; + }) + + it('should redirect back to the original function if bid is not an adpod video', function () { + let bid = { + adId: 'testAdId_123', + mediaType: 'video' + }; + + let bidderRequest = { + adUnitCode: 'adUnit_123', + mediaTypes: { + video: { + context: 'outstream' + } + } + } + + callPrebidCacheHook(callbackFn, auctionInstance, bid, function () {}, bidderRequest); + expect(callbackResult).to.equal(true); + }); + + it('should immediately add the adpod bid to auction if adpod.deferCaching in config is true', function() { + config.setConfig({ + adpod: { + deferCaching: true, + brandCategoryExclusion: true + } + }); + + let bidResponse1 = { + adId: 'adId01277', + auctionId: 'no_defer_123', + mediaType: 'video', + cpm: 5, + pbMg: '5.00', + adserverTargeting: { + hb_pb: '5.00' + }, + meta: { + adServerCatId: 'test' + }, + video: { + context: ADPOD, + durationSeconds: 15, + durationBucket: 15 + } + }; + + let bidResponse2 = { + adId: 'adId46547', + auctionId: 'no_defer_123', + mediaType: 'video', + cpm: 12, + pbMg: '12.00', + adserverTargeting: { + hb_pb: '12.00' + }, + meta: { + adServerCatId: 'value' + }, + video: { + context: ADPOD, + durationSeconds: 15, + durationBucket: 15 + } + }; + + let bidderRequest = { + adUnitCode: 'adpod_1', + auctionId: 'no_defer_123', + mediaTypes: { + video: { + context: ADPOD, + playerSize: [300, 300], + adPodDurationSec: 300, + durationRangeSec: [15, 30, 45], + requireExactDuration: false + } + }, + }; + + callPrebidCacheHook(callbackFn, auctionInstance, bidResponse1, afterBidAddedSpy, bidderRequest); + callPrebidCacheHook(callbackFn, auctionInstance, bidResponse2, afterBidAddedSpy, bidderRequest); + + // check if bid adsereverTargeting is setup + expect(callbackResult).to.be.null; + expect(storeStub.called).to.equal(false); + expect(afterBidAddedSpy.calledTwice).to.equal(true); + expect(auctionBids.length).to.equal(2); + expect(auctionBids[0].adId).to.equal(bidResponse1.adId); + expect(auctionBids[0].customCacheKey).to.exist.and.to.match(/^5\.00_test_15s_.*/); + expect(auctionBids[0].adserverTargeting.hb_pb_cat_dur).to.equal('5.00_test_15s'); + expect(auctionBids[0].adserverTargeting.hb_cache_id).to.exist; + expect(auctionBids[0].videoCacheKey).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id) + expect(auctionBids[1].adId).to.equal(bidResponse2.adId); + expect(auctionBids[1].customCacheKey).to.exist.and.to.match(/^12\.00_value_15s_.*/); + expect(auctionBids[1].adserverTargeting.hb_pb_cat_dur).to.equal('12.00_value_15s'); + expect(auctionBids[1].adserverTargeting.hb_cache_id).to.exist; + expect(auctionBids[1].adserverTargeting.hb_cache_id).to.equal(auctionBids[0].adserverTargeting.hb_cache_id); + expect(auctionBids[1].videoCacheKey).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id); + }); + + it('should send prebid cache call once bid queue is full', function () { + storeStub.callsFake(fakeStoreFn); + + config.setConfig({ + adpod: { + bidQueueSizeLimit: 2, + deferCaching: false, + brandCategoryExclusion: true + } + }); + + let bidResponse1 = { + adId: 'adId123', + auctionId: 'full_abc123', + mediaType: 'video', + cpm: 10, + pbMg: '10.00', + adserverTargeting: { + hb_pb: '10.00' + }, + meta: { + adServerCatId: 'airline' + }, + video: { + context: ADPOD, + durationSeconds: 20, + durationBucket: 30 + } + }; + let bidResponse2 = { + adId: 'adId234', + auctionId: 'full_abc123', + mediaType: 'video', + cpm: 15, + pbMg: '15.00', + adserverTargeting: { + hb_pb: '15.00' + }, + meta: { + adServerCatId: 'airline' + }, + video: { + context: ADPOD, + durationSeconds: 25, + durationBucket: 30 + } + }; + let bidderRequest = { + adUnitCode: 'adpod_1', + auctionId: 'full_abc123', + mediaTypes: { + video: { + context: ADPOD, + playerSize: [300, 300], + adPodDurationSec: 120, + durationRangeSec: [15, 30], + requireExactDuration: false + } + } + }; + + callPrebidCacheHook(callbackFn, auctionInstance, bidResponse1, afterBidAddedSpy, bidderRequest); + callPrebidCacheHook(callbackFn, auctionInstance, bidResponse2, afterBidAddedSpy, bidderRequest); + + expect(callbackResult).to.be.null; + expect(afterBidAddedSpy.calledTwice).to.equal(true); + expect(auctionBids.length).to.equal(2); + expect(auctionBids[0].adId).to.equal('adId123'); + expect(auctionBids[0].customCacheKey).to.exist.and.to.match(/^10\.00_airline_30s_.*/); + expect(auctionBids[0].adserverTargeting.hb_pb_cat_dur).to.equal('10.00_airline_30s'); + expect(auctionBids[0].adserverTargeting.hb_cache_id).to.exist; + expect(auctionBids[0].videoCacheKey).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id) + expect(auctionBids[1].adId).to.equal('adId234'); + expect(auctionBids[1].customCacheKey).to.exist.and.to.match(/^15\.00_airline_30s_.*/); + expect(auctionBids[1].adserverTargeting.hb_pb_cat_dur).to.equal('15.00_airline_30s'); + expect(auctionBids[1].adserverTargeting.hb_cache_id).to.exist; + expect(auctionBids[1].videoCacheKey).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id) + }); + + it('should send prebid cache call after set period of time (even if queue is not full)', function () { + storeStub.callsFake(fakeStoreFn); + + config.setConfig({ + adpod: { + bidQueueSizeLimit: 2, + bidQueueTimeDelay: 30, + deferCaching: false, + brandCategoryExclusion: true + } + }); + + let bidResponse = { + adId: 'adId234', + auctionId: 'timer_abc234', + mediaType: 'video', + cpm: 15, + pbMg: '15.00', + adserverTargeting: { + hb_pb: '15.00' + }, + meta: { + adServerCatId: 'airline' + }, + video: { + context: ADPOD, + durationSeconds: 30, + durationBucket: 30 + } + }; + let bidderRequest = { + adUnitCode: 'adpod_2', + auctionId: 'timer_abc234', + mediaTypes: { + video: { + context: ADPOD, + playerSize: [300, 300], + adPodDurationSec: 120, + durationRangeSec: [15, 30], + requireExactDuration: true + } + } + }; + + callPrebidCacheHook(callbackFn, auctionInstance, bidResponse, afterBidAddedSpy, bidderRequest); + clock.tick(31); + + expect(callbackResult).to.be.null; + expect(afterBidAddedSpy.calledOnce).to.equal(true); + expect(auctionBids.length).to.equal(1); + expect(auctionBids[0].adId).to.equal('adId234'); + expect(auctionBids[0].customCacheKey).to.exist.and.to.match(/^15\.00_airline_30s_.*/); + expect(auctionBids[0].adserverTargeting.hb_pb_cat_dur).to.equal('15.00_airline_30s'); + expect(auctionBids[0].adserverTargeting.hb_cache_id).to.exist; + expect(auctionBids[0].videoCacheKey).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id) + }); + + it('should execute multiple prebid cache calls when number of bids exceeds queue size', function () { + storeStub.callsFake(fakeStoreFn); + + config.setConfig({ + adpod: { + bidQueueSizeLimit: 2, + bidQueueTimeDelay: 30, + deferCaching: false, + brandCategoryExclusion: true + } + }); + + let bidResponse1 = { + adId: 'multi_ad1', + auctionId: 'multi_call_abc345', + mediaType: 'video', + cpm: 15, + pbMg: '15.00', + adserverTargeting: { + hb_pb: '15.00' + }, + meta: { + adServerCatId: 'airline' + }, + video: { + context: ADPOD, + durationSeconds: 15, + durationBucket: 15 + } + }; + let bidResponse2 = { + adId: 'multi_ad2', + auctionId: 'multi_call_abc345', + mediaType: 'video', + cpm: 15, + pbMg: '15.00', + adserverTargeting: { + hb_pb: '15.00' + }, + meta: { + adServerCatId: 'news' + }, + video: { + context: ADPOD, + durationSeconds: 15, + durationBucket: 15 + } + }; + let bidResponse3 = { + adId: 'multi_ad3', + auctionId: 'multi_call_abc345', + mediaType: 'video', + cpm: 10, + pbMg: '10.00', + adserverTargeting: { + hb_pb: '10.00' + }, + meta: { + adServerCatId: 'sports' + }, + video: { + context: ADPOD, + durationSeconds: 15, + durationBucket: 15 + } + }; + + let bidderRequest = { + adUnitCode: 'adpod_3', + auctionId: 'multi_call_abc345', + mediaTypes: { + video: { + context: ADPOD, + playerSize: [300, 300], + adPodDurationSec: 45, + durationRangeSec: [15, 30], + requireExactDuration: false + } + } + }; + + callPrebidCacheHook(callbackFn, auctionInstance, bidResponse1, afterBidAddedSpy, bidderRequest); + callPrebidCacheHook(callbackFn, auctionInstance, bidResponse2, afterBidAddedSpy, bidderRequest); + callPrebidCacheHook(callbackFn, auctionInstance, bidResponse3, afterBidAddedSpy, bidderRequest); + clock.next(); + + expect(callbackResult).to.be.null; + expect(afterBidAddedSpy.calledThrice).to.equal(true); + expect(storeStub.calledTwice).to.equal(true); + expect(auctionBids.length).to.equal(3); + expect(auctionBids[0].adId).to.equal('multi_ad1'); + expect(auctionBids[0].customCacheKey).to.exist.and.to.match(/^15\.00_airline_15s_.*/); + expect(auctionBids[0].adserverTargeting.hb_pb_cat_dur).to.equal('15.00_airline_15s'); + expect(auctionBids[0].adserverTargeting.hb_cache_id).to.exist; + expect(auctionBids[0].videoCacheKey).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id) + expect(auctionBids[1].adId).to.equal('multi_ad2'); + expect(auctionBids[1].customCacheKey).to.exist.and.to.match(/^15\.00_news_15s_.*/); + expect(auctionBids[1].adserverTargeting.hb_pb_cat_dur).to.equal('15.00_news_15s'); + expect(auctionBids[1].adserverTargeting.hb_cache_id).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id); + expect(auctionBids[1].videoCacheKey).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id) + expect(auctionBids[2].adId).to.equal('multi_ad3'); + expect(auctionBids[2].customCacheKey).to.exist.and.to.match(/^10\.00_sports_15s_.*/); + expect(auctionBids[2].adserverTargeting.hb_pb_cat_dur).to.equal('10.00_sports_15s'); + expect(auctionBids[2].adserverTargeting.hb_cache_id).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id); + expect(auctionBids[2].videoCacheKey).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id) + }); + + it('should cache the bids with a shortened custom key when adpod.brandCategoryExclusion is false', function() { + storeStub.callsFake(fakeStoreFn); + + config.setConfig({ + adpod: { + bidQueueSizeLimit: 2, + bidQueueTimeDelay: 30, + deferCaching: false, + brandCategoryExclusion: false + } + }); + + let bidResponse1 = { + adId: 'nocat_ad1', + auctionId: 'no_category_abc345', + mediaType: 'video', + cpm: 10, + pbMg: '10.00', + adserverTargeting: { + hb_pb: '10.00' + }, + meta: { + adServerCatId: undefined + }, + video: { + context: ADPOD, + durationSeconds: 15, + durationBucket: 15 + } + }; + let bidResponse2 = { + adId: 'nocat_ad2', + auctionId: 'no_category_abc345', + mediaType: 'video', + cpm: 15, + pbMg: '15.00', + adserverTargeting: { + hb_pb: '15.00' + }, + meta: { + adServerCatId: undefined + }, + video: { + context: ADPOD, + durationSeconds: 15, + durationBucket: 15 + } + }; + + let bidderRequest = { + adUnitCode: 'adpod_4', + auctionId: 'no_category_abc345', + mediaTypes: { + video: { + context: ADPOD, + playerSize: [300, 300], + adPodDurationSec: 45, + durationRangeSec: [15, 30], + requireExactDuration: false + } + } + }; + + callPrebidCacheHook(callbackFn, auctionInstance, bidResponse1, afterBidAddedSpy, bidderRequest); + callPrebidCacheHook(callbackFn, auctionInstance, bidResponse2, afterBidAddedSpy, bidderRequest); + + expect(callbackResult).to.be.null; + expect(afterBidAddedSpy.calledTwice).to.equal(true); + expect(storeStub.calledOnce).to.equal(true); + expect(auctionBids.length).to.equal(2); + expect(auctionBids[0].adId).to.equal('nocat_ad1'); + expect(auctionBids[0].customCacheKey).to.exist.and.to.match(/^10\.00_15s_.*/); + expect(auctionBids[0].adserverTargeting.hb_pb_cat_dur).to.equal('10.00_15s'); + expect(auctionBids[0].adserverTargeting.hb_cache_id).to.exist; + expect(auctionBids[0].videoCacheKey).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id) + expect(auctionBids[1].adId).to.equal('nocat_ad2'); + expect(auctionBids[1].customCacheKey).to.exist.and.to.match(/^15\.00_15s_.*/); + expect(auctionBids[1].adserverTargeting.hb_pb_cat_dur).to.equal('15.00_15s'); + expect(auctionBids[1].adserverTargeting.hb_cache_id).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id); + expect(auctionBids[1].videoCacheKey).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id) + }); + + it('should not add bid to auction when config adpod.brandCategoryExclusion is true but bid is missing adServerCatId', function() { + storeStub.callsFake(fakeStoreFn); + + config.setConfig({ + adpod: { + bidQueueSizeLimit: 2, + bidQueueTimeDelay: 30, + deferCaching: false, + brandCategoryExclusion: true + } + }); + + let bidResponse1 = { + adId: 'missingCat_ad1', + auctionId: 'missing_category_abc345', + mediaType: 'video', + cpm: 10, + meta: { + adServerCatId: undefined + }, + video: { + context: ADPOD, + durationSeconds: 15, + durationBucket: 15 + } + }; + + let bidderRequest = { + adUnitCode: 'adpod_5', + auctionId: 'missing_category_abc345', + mediaTypes: { + video: { + context: ADPOD, + playerSize: [300, 300], + adPodDurationSec: 45, + durationRangeSec: [15, 30], + requireExactDuration: false + } + } + }; + + callPrebidCacheHook(callbackFn, auctionInstance, bidResponse1, afterBidAddedSpy, bidderRequest); + + expect(callbackResult).to.be.null; + expect(afterBidAddedSpy.calledOnce).to.equal(true); + expect(storeStub.called).to.equal(false); + expect(logWarnStub.calledOnce).to.equal(true); + expect(auctionBids.length).to.equal(0); + }); + + it('should not add bid to auction when Prebid Cache detects an existing key', function () { + storeStub.callsFake(function(bids, callback) { + let payload = []; + bids.forEach(bid => payload.push({uuid: bid.customCacheKey})); + + // fake a duplicate bid response from PBC (sets an empty string for the uuid) + payload[1].uuid = ''; + callback(null, payload); + }); + + config.setConfig({ + adpod: { + bidQueueSizeLimit: 2, + deferCaching: false, + brandCategoryExclusion: true + } + }); + + let bidResponse1 = { + adId: 'dup_ad_1', + auctionId: 'duplicate_def123', + mediaType: 'video', + cpm: 5, + pbMg: '5.00', + adserverTargeting: { + hb_pb: '5.00' + }, + meta: { + adServerCatId: 'tech' + }, + video: { + context: ADPOD, + durationSeconds: 45, + durationBucket: 45 + } + }; + let bidResponse2 = { + adId: 'dup_ad_2', + auctionId: 'duplicate_def123', + mediaType: 'video', + cpm: 5, + pbMg: '5.00', + adserverTargeting: { + hb_pb: '5.00' + }, + meta: { + adServerCatId: 'tech' + }, + video: { + context: ADPOD, + durationSeconds: 45, + durationBucket: 45 + } + }; + let bidderRequest = { + adUnitCode: 'adpod_4', + auctionId: 'duplicate_def123', + mediaTypes: { + video: { + context: ADPOD, + playerSize: [300, 300], + adPodDurationSec: 120, + durationRangeSec: [15, 30, 45], + requireExactDuration: false + } + } + }; + + callPrebidCacheHook(callbackFn, auctionInstance, bidResponse1, afterBidAddedSpy, bidderRequest); + callPrebidCacheHook(callbackFn, auctionInstance, bidResponse2, afterBidAddedSpy, bidderRequest); + + expect(callbackResult).to.be.null; + expect(afterBidAddedSpy.calledTwice).to.equal(true); + expect(storeStub.calledOnce).to.equal(true); + expect(logInfoStub.calledOnce).to.equal(true); + expect(auctionBids.length).to.equal(1); + expect(auctionBids[0].adId).to.equal('dup_ad_1'); + expect(auctionBids[0].customCacheKey).to.exist.and.to.match(/^5\.00_tech_45s_.*/); + expect(auctionBids[0].adserverTargeting.hb_pb_cat_dur).to.equal('5.00_tech_45s'); + expect(auctionBids[0].adserverTargeting.hb_cache_id).to.exist; + expect(auctionBids[0].videoCacheKey).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id) + }); + + it('should not add bids to auction if PBC returns an error', function() { + storeStub.callsFake(function(bids, callback) { + let payload = []; + let errmsg = 'invalid json'; + + callback(errmsg, payload); + }); + + config.setConfig({ + adpod: { + bidQueueSizeLimit: 2, + deferCaching: false, + brandCategoryExclusion: true + } + }); + + let bidResponse1 = { + adId: 'err_ad_1', + auctionId: 'error_xyz123', + mediaType: 'video', + cpm: 5, + meta: { + adServerCatId: 'tech' + }, + video: { + context: ADPOD, + durationSeconds: 30, + durationBucket: 30 + } + }; + let bidResponse2 = { + adId: 'err_ad_2', + auctionId: 'error_xyz123', + mediaType: 'video', + cpm: 5, + meta: { + adServerCatId: 'tech' + }, + video: { + context: ADPOD, + durationSeconds: 30, + durationBucket: 30 + } + }; + let bidderRequest = { + adUnitCode: 'adpod_5', + auctionId: 'error_xyz123', + mediaTypes: { + video: { + context: ADPOD, + playerSize: [300, 300], + adPodDurationSec: 120, + durationRangeSec: [15, 30, 45], + requireExactDuration: false + } + } + }; + + callPrebidCacheHook(callbackFn, auctionInstance, bidResponse1, afterBidAddedSpy, bidderRequest); + callPrebidCacheHook(callbackFn, auctionInstance, bidResponse2, afterBidAddedSpy, bidderRequest); + + expect(doCallbacksIfTimedoutStub.calledTwice).to.equal(true); + expect(logWarnStub.calledOnce).to.equal(true); + expect(auctionBids.length).to.equal(0); + }); + + it('should use bid.adserverTargeting.hb_pb when custom price granularity is configured', function() { + storeStub.callsFake(fakeStoreFn); + + const customConfigObject = { + 'buckets': [{ + 'precision': 2, // default is 2 if omitted - means 2.1234 rounded to 2 decimal places = 2.12 + 'min': 0, + 'max': 5, + 'increment': 0.01 // from $0 to $5, 1-cent increments + }, + { + 'precision': 2, + 'min': 5, + 'max': 8, + 'increment': 0.05 // from $5 to $8, round down to the previous 5-cent increment + }, + { + 'precision': 2, + 'min': 8, + 'max': 40, + 'increment': 0.5 // from $8 to $40, round down to the previous 50-cent increment + }] + }; + config.setConfig({ + priceGranularity: customConfigObject, + adpod: { + brandCategoryExclusion: true + } + }); + + let bidResponse1 = { + adId: 'cat_ad1', + auctionId: 'test_category_abc345', + mediaType: 'video', + cpm: 15, + pbAg: '15.00', + pbCg: '15.00', + pbDg: '15.00', + pbHg: '15.00', + pbLg: '5.00', + pbMg: '15.00', + adserverTargeting: { + hb_pb: '15.00', + }, + meta: { + adServerCatId: 'test' + }, + video: { + context: ADPOD, + durationSeconds: 15, + durationBucket: 15 + } + }; + + let bidderRequest = { + adUnitCode: 'adpod_5', + auctionId: 'test_category_abc345', + mediaTypes: { + video: { + context: ADPOD, + playerSize: [300, 300], + adPodDurationSec: 45, + durationRangeSec: [15, 30], + requireExactDuration: false + } + } + }; + + callPrebidCacheHook(callbackFn, auctionInstance, bidResponse1, afterBidAddedSpy, bidderRequest); + + expect(callbackResult).to.be.null; + expect(afterBidAddedSpy.calledOnce).to.equal(true); + expect(storeStub.called).to.equal(false); + expect(auctionBids.length).to.equal(1); + }); + }); + + describe('checkAdUnitSetupHook', function () { + let results; + let callbackFn = function (adUnits) { + results = adUnits; + }; + + beforeEach(function () { + logWarnStub = sinon.stub(utils, 'logWarn'); + results = null; + }); + + afterEach(function() { + utils.logWarn.restore(); + }); + + it('removes an incorrectly setup adpod adunit - required fields are missing', function() { + let adUnits = [{ + code: 'test1', + mediaTypes: { + video: { + context: ADPOD + } + } + }, { + code: 'test2', + mediaTypes: { + video: { + context: 'instream' + } + } + }]; + + checkAdUnitSetupHook(callbackFn, adUnits); + + expect(results).to.deep.equal([{ + code: 'test2', + mediaTypes: { + video: { + context: 'instream' + } + } + }]); + expect(logWarnStub.calledOnce).to.equal(true); + }); + + it('removes an incorrectly setup adpod adunit - required fields are using invalid values', function() { + let adUnits = [{ + code: 'test1', + mediaTypes: { + video: { + context: ADPOD, + durationRangeSec: [-5, 15, 30, 45], + adPodDurationSec: 300 + } + } + }]; + + checkAdUnitSetupHook(callbackFn, adUnits); + + expect(results).to.deep.equal([]); + expect(logWarnStub.calledOnce).to.equal(true); + + adUnits[0].mediaTypes.video.durationRangeSec = [15, 30, 45]; + adUnits[0].mediaTypes.video.adPodDurationSec = 0; + + checkAdUnitSetupHook(callbackFn, adUnits); + + expect(results).to.deep.equal([]); + expect(logWarnStub.calledTwice).to.equal(true); + }); + + it('removes an incorrectly setup adpod adunit - attempting to use multi-format adUnit', function() { + let adUnits = [{ + code: 'multi_test1', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + }, + video: { + context: 'adpod', + playerSize: [300, 250], + durationRangeSec: [15, 30, 45], + adPodDurationSec: 300 + } + } + }]; + + checkAdUnitSetupHook(callbackFn, adUnits); + + expect(results).to.deep.equal([]); + expect(logWarnStub.calledOnce).to.equal(true); + }); + + it('accepts mixed set of adunits', function() { + let adUnits = [{ + code: 'test3', + mediaTypes: { + video: { + context: ADPOD, + playerSize: [300, 300], + adPodDurationSec: 360, + durationRangeSec: [15, 30, 45], + requireExactDuration: true + } + } + }, { + code: 'test4', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + } + }]; + + checkAdUnitSetupHook(callbackFn, adUnits); + + expect(results).to.deep.equal(adUnits); + expect(logWarnStub.called).to.equal(false); + }); + }); + + describe('checkVideoBidSetupHook', function () { + let callbackResult; + let bailResult; + const callbackFn = { + call: function(context, bid) { + callbackResult = bid; + }, + bail: function(result) { + bailResult = result; + } + } + const adpodTestBid = { + video: { + context: ADPOD, + durationSeconds: 15, + durationBucket: 15 + }, + meta: { + iabSubCatId: 'testCategory_123' + }, + vastXml: 'test XML here' + }; + const bidderRequestNoExact = { + mediaTypes: { + video: { + context: ADPOD, + playerSize: [300, 400], + durationRangeSec: [15, 45], + requireExactDuration: false, + adPodDurationSec: 300 + } + } + }; + const bidderRequestWithExact = { + mediaTypes: { + video: { + context: ADPOD, + playerSize: [300, 400], + durationRangeSec: [15, 30, 45, 60], + requireExactDuration: true, + adPodDurationSec: 300 + } + } + }; + + beforeEach(function() { + callbackResult = null; + bailResult = null; + config.setConfig({ + cache: { + url: 'http://test.cache.url/endpoint' + }, + adpod: { + brandCategoryExclusion: true + } + }); + logWarnStub = sinon.stub(utils, 'logWarn'); + logErrorStub = sinon.stub(utils, 'logError'); + }); + + afterEach(function() { + config.resetConfig(); + logWarnStub.restore(); + logErrorStub.restore(); + }) + + it('redirects to original function for non-adpod type video bids', function() { + let bannerTestBid = { + mediaType: 'video' + }; + checkVideoBidSetupHook(callbackFn, bannerTestBid, {}, {}, 'instream'); + expect(callbackResult).to.deep.equal(bannerTestBid); + expect(bailResult).to.be.null; + expect(logErrorStub.called).to.equal(false); + }); + + it('returns true when adpod bid is properly setup', function() { + config.setConfig({ + cache: { + url: 'http://test.cache.url/endpoint' + }, + adpod: { + brandCategoryExclusion: false + } + }); + + let goodBid = utils.deepClone(adpodTestBid); + goodBid.meta.iabSubCatId = undefined; + checkVideoBidSetupHook(callbackFn, goodBid, bidderRequestNoExact, {}, ADPOD); + expect(callbackResult).to.be.null; + expect(bailResult).to.equal(true); + expect(logErrorStub.called).to.equal(false); + }); + + it('returns true when adpod bid is missing iab category while brandCategoryExclusion in config is false', function() { + let goodBid = utils.deepClone(adpodTestBid); + checkVideoBidSetupHook(callbackFn, goodBid, bidderRequestNoExact, {}, ADPOD); + expect(callbackResult).to.be.null; + expect(bailResult).to.equal(true); + expect(logErrorStub.called).to.equal(false); + }); + + it('returns false when a required property from an adpod bid is missing', function() { + function testInvalidAdpodBid(badTestBid, shouldErrorBeLogged) { + checkVideoBidSetupHook(callbackFn, badTestBid, bidderRequestNoExact, {}, ADPOD); + expect(callbackResult).to.be.null; + expect(bailResult).to.equal(false); + expect(logErrorStub.called).to.equal(shouldErrorBeLogged); + } + + let noCatBid = utils.deepClone(adpodTestBid); + noCatBid.meta.iabSubCatId = undefined; + testInvalidAdpodBid(noCatBid, false); + + let noContextBid = utils.deepClone(adpodTestBid); + delete noContextBid.video.context; + testInvalidAdpodBid(noContextBid, false); + + let wrongContextBid = utils.deepClone(adpodTestBid); + wrongContextBid.video.context = 'instream'; + testInvalidAdpodBid(wrongContextBid, false); + + let noDurationBid = utils.deepClone(adpodTestBid); + delete noDurationBid.video.durationSeconds; + testInvalidAdpodBid(noDurationBid, false); + + config.resetConfig(); + let noCacheUrlBid = utils.deepClone(adpodTestBid); + testInvalidAdpodBid(noCacheUrlBid, true); + }); + + describe('checkBidDuration', function() { + const basicBid = { + video: { + context: ADPOD, + durationSeconds: 30 + }, + meta: { + iabSubCatId: 'testCategory_123' + }, + vastXml: '' + }; + + it('when requireExactDuration is true', function() { + let goodBid = utils.deepClone(basicBid); + checkVideoBidSetupHook(callbackFn, goodBid, bidderRequestWithExact, {}, ADPOD); + + expect(callbackResult).to.be.null; + expect(goodBid.video.durationBucket).to.equal(30); + expect(bailResult).to.equal(true); + expect(logWarnStub.called).to.equal(false); + + let badBid = utils.deepClone(basicBid); + badBid.video.durationSeconds = 14; + checkVideoBidSetupHook(callbackFn, badBid, bidderRequestWithExact, {}, ADPOD); + + expect(callbackResult).to.be.null; + expect(badBid.video.durationBucket).to.be.undefined; + expect(bailResult).to.equal(false); + expect(logWarnStub.calledOnce).to.equal(true); + }); + + it('when requireExactDuration is false and bids are bucketed properly', function() { + function testRoundingForGoodBId(bid, bucketValue) { + checkVideoBidSetupHook(callbackFn, bid, bidderRequestNoExact, {}, ADPOD); + expect(callbackResult).to.be.null; + expect(bid.video.durationBucket).to.equal(bucketValue); + expect(bailResult).to.equal(true); + expect(logWarnStub.called).to.equal(false); + } + + let goodBid45 = utils.deepClone(basicBid); + goodBid45.video.durationSeconds = 45; + testRoundingForGoodBId(goodBid45, 45); + + let goodBid30 = utils.deepClone(basicBid); + goodBid30.video.durationSeconds = 30; + testRoundingForGoodBId(goodBid30, 45); + + let goodBid10 = utils.deepClone(basicBid); + goodBid10.video.durationSeconds = 10; + testRoundingForGoodBId(goodBid10, 15); + + let goodBid16 = utils.deepClone(basicBid); + goodBid16.video.durationSeconds = 16; + testRoundingForGoodBId(goodBid16, 15); + + let goodBid47 = utils.deepClone(basicBid); + goodBid47.video.durationSeconds = 47; + testRoundingForGoodBId(goodBid47, 45); + }); + + it('when requireExactDuration is false and bid duration exceeds listed buckets', function() { + function testRoundingForBadBid(bid) { + checkVideoBidSetupHook(callbackFn, bid, bidderRequestNoExact, {}, ADPOD); + expect(callbackResult).to.be.null; + expect(bid.video.durationBucket).to.be.undefined; + expect(bailResult).to.equal(false); + expect(logWarnStub.called).to.equal(true); + } + + let badBid100 = utils.deepClone(basicBid); + badBid100.video.durationSeconds = 100; + testRoundingForBadBid(badBid100); + + let badBid48 = utils.deepClone(basicBid); + badBid48.video.durationSeconds = 48; + testRoundingForBadBid(badBid48); + }); + }); + }); + + describe('adpodSetConfig', function () { + let logWarnStub; + beforeEach(function() { + logWarnStub = sinon.stub(utils, 'logWarn'); + }); + + afterEach(function () { + logWarnStub.restore(); + }); + + it('should log a warning when values other than numbers are used in setConfig', function() { + adpodSetConfig({ + bidQueueSizeLimit: '2', + bidQueueTimeDelay: '50' + }); + expect(logWarnStub.calledTwice).to.equal(true); + }); + + it('should log a warning when numbers less than or equal to zero are used in setConfig', function() { + adpodSetConfig({ + bidQueueSizeLimit: 0, + bidQueueTimeDelay: -2 + }); + expect(logWarnStub.calledTwice).to.equal(true); + }); + + it('should not log any warning when using a valid config', function() { + adpodSetConfig({ + bidQueueSizeLimit: 10 + }); + expect(logWarnStub.called).to.equal(false); + + adpodSetConfig({ + bidQueueTimeDelay: 100, + bidQueueSizeLimit: 20 + }); + expect(logWarnStub.called).to.equal(false); + }) + }); + + describe('adpod utils', function() { + it('should sort bids array', function() { + let bids = [{ + cpm: 10.12345, + adserverTargeting: { + hb_pb: '10.00', + }, + video: { + durationBucket: 15 + } + }, { + cpm: 15, + adserverTargeting: { + hb_pb: '15.00', + }, + video: { + durationBucket: 15 + } + }, { + cpm: 15.00, + adserverTargeting: { + hb_pb: '15.00', + }, + video: { + durationBucket: 30 + } + }, { + cpm: 5.45, + adserverTargeting: { + hb_pb: '5.00', + }, + video: { + durationBucket: 5 + } + }, { + cpm: 20.1234567, + adserverTargeting: { + hb_pb: '20.10', + }, + video: { + durationBucket: 60 + } + }] + bids.sort(sortByPricePerSecond); + let sortedBids = [{ + cpm: 15, + adserverTargeting: { + hb_pb: '15.00', + }, + video: { + durationBucket: 15 + } + }, { + cpm: 5.45, + adserverTargeting: { + hb_pb: '5.00', + }, + video: { + durationBucket: 5 + } + }, { + cpm: 10.12345, + adserverTargeting: { + hb_pb: '10.00', + }, + video: { + durationBucket: 15 + } + }, { + cpm: 15.00, + adserverTargeting: { + hb_pb: '15.00', + }, + video: { + durationBucket: 30 + } + }, { + cpm: 20.1234567, + adserverTargeting: { + hb_pb: '20.10', + }, + video: { + durationBucket: 60 + } + }] + expect(bids).to.include.deep.ordered.members(sortedBids); + }); + }) +}); diff --git a/test/spec/modules/adponeBidAdapter_spec.js b/test/spec/modules/adponeBidAdapter_spec.js new file mode 100644 index 00000000000..da685a56394 --- /dev/null +++ b/test/spec/modules/adponeBidAdapter_spec.js @@ -0,0 +1,223 @@ +import { expect } from 'chai'; +import { spec } from 'modules/adponeBidAdapter'; +import {newBidder} from '../../../src/adapters/bidderFactory'; + +const EMPTY_ARRAY = []; +describe('adponeBidAdapter', function () { + let bid = { + bidder: 'adpone', + adUnitCode: 'adunit-code', + sizes: [[300, 250]], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + params: { + placementId: '1', + } + }; + + describe('build requests', () => { + it('sends bid request to ENDPOINT via POST', function () { + const request = spec.buildRequests([ + { + bidder: 'adpone', + adUnitCode: 'adunit-code', + sizes: [[300, 250]], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + params: { + placementId: '1', + } + } + ]); + expect(request[0].method).to.equal('POST'); + }); + it('sends bid request to adpone endpoint', function () { + const request = spec.buildRequests([ + { + bidder: 'adpone', + adUnitCode: 'adunit-code', + sizes: [[300, 250]], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + params: { + placementId: '1', + } + } + ]); + expect(request[0].url).to.equal('https://rtb.adpone.com/bid-request?pid=1'); + }); + }); + + describe('inherited functions', () => { + const adapter = newBidder(spec); + it('exists and is a function', () => { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function () { + it('should return true when necessary information is found', function () { + expect(spec.isBidRequestValid(bid)).to.be.true; + }); + + it('should return false when necessary information is not found', function () { + // empty bid + expect(spec.isBidRequestValid({bidId: '', params: {}})).to.be.false; + + // empty bidId + bid.bidId = ''; + expect(spec.isBidRequestValid(bid)).to.be.false; + + // empty placementId + bid.bidId = '30b31c1838de1e'; + bid.params.placementId = ''; + expect(spec.isBidRequestValid(bid)).to.be.false; + + bid.adUnitCode = 'adunit-code'; + }); + + it('returns false when bidder not set to "adpone"', function() { + const invalidBid = { + bidder: 'enopda', + adUnitCode: 'adunit-code', + sizes: [[300, 250]], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + params: { + placementId: '1', + } + }; + expect(spec.isBidRequestValid(invalidBid)).to.be.false; + }); + + it('returns false when placementId is not set in params', function() { + const invalidBid = { + bidder: 'adpone', + adUnitCode: 'adunit-code', + sizes: [[300, 250]], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + params: { + } + }; + + expect(spec.isBidRequestValid(invalidBid)).to.be.false; + }); + }); +}); + +describe('interpretResponse', function () { + let serverResponse; + let bidRequest = { data: {id: '1234'} }; + + beforeEach(function () { + serverResponse = { + body: { + id: '2579e20c0bb89', + seatbid: [ + { + bid: [ + { + id: '613673EF-A07C-4486-8EE9-3FC71A7DC73D', + impid: '2579e20c0bb89_0', + price: 1, + adm: '', + adomain: [ + 'www.addomain.com' + ], + iurl: 'http://localhost11', + crid: 'creative111', + h: 250, + w: 300, + ext: { + dspid: 6 + } + } + ], + seat: 'adpone' + } + ], + cur: 'USD' + }, + }; + }); + + it('validate_response_params', function() { + const newResponse = spec.interpretResponse(serverResponse, bidRequest); + expect(newResponse[0].id).to.be.equal('613673EF-A07C-4486-8EE9-3FC71A7DC73D'); + expect(newResponse[0].requestId).to.be.equal('1234'); + expect(newResponse[0].cpm).to.be.equal(1); + expect(newResponse[0].width).to.be.equal(300); + expect(newResponse[0].height).to.be.equal(250); + expect(newResponse[0].currency).to.be.equal('USD'); + expect(newResponse[0].netRevenue).to.be.equal(true); + expect(newResponse[0].ttl).to.be.equal(300); + expect(newResponse[0].ad).to.be.equal(''); + }); + + it('should correctly reorder the server response', function () { + const newResponse = spec.interpretResponse(serverResponse, bidRequest); + expect(newResponse.length).to.be.equal(1); + expect(newResponse[0]).to.deep.equal({ + id: '613673EF-A07C-4486-8EE9-3FC71A7DC73D', + requestId: '1234', + cpm: 1, + width: 300, + height: 250, + creativeId: 'creative111', + currency: 'USD', + netRevenue: true, + ttl: 300, + ad: '' + }); + }); + + it('should not add responses if the cpm is 0 or null', function () { + serverResponse.body.seatbid[0].bid[0].price = 0; + let response = spec.interpretResponse(serverResponse, bidRequest); + expect(response).to.deep.equal([]); + + serverResponse.body.seatbid[0].bid[0].price = null; + response = spec.interpretResponse(serverResponse, bidRequest); + expect(response).to.deep.equal([]) + }); + it('should add responses if the cpm is valid', function () { + serverResponse.body.seatbid[0].bid[0].price = 0.5; + let response = spec.interpretResponse(serverResponse, bidRequest); + expect(response).to.not.deep.equal([]); + }); +}); + +describe('getUserSyncs', function () { + it('Verifies that getUserSyncs is a function', function () { + expect((typeof (spec.getUserSyncs)).should.equals('function')); + }); + it('Verifies getUserSyncs returns expected result', function () { + expect((typeof (spec.getUserSyncs)).should.equals('function')); + expect(spec.getUserSyncs({iframeEnabled: true})).to.deep.equal({ + type: 'iframe', + url: 'https://eu-ads.adpone.com' + }); + }); + it('Verifies that iframeEnabled: false returns an empty array', function () { + expect(spec.getUserSyncs({iframeEnabled: false})).to.deep.equal(EMPTY_ARRAY); + }); + it('Verifies that iframeEnabled: null returns an empty array', function () { + expect(spec.getUserSyncs(null)).to.deep.equal(EMPTY_ARRAY); + }); +}); + +describe('test onBidWon function', function () { + it('exists and is a function', () => { + expect(spec.onBidWon).to.exist.and.to.be.a('function'); + }); + it('should return nothing', function () { + var response = spec.onBidWon({}); + expect(response).to.be.an('undefined') + }); +}); diff --git a/test/spec/modules/adtelligentBidAdapter_spec.js b/test/spec/modules/adtelligentBidAdapter_spec.js index 6190f53fdc4..28bb057dffe 100644 --- a/test/spec/modules/adtelligentBidAdapter_spec.js +++ b/test/spec/modules/adtelligentBidAdapter_spec.js @@ -46,6 +46,7 @@ const SERVER_VIDEO_RESPONSE = { } ] }; + const SERVER_DISPLAY_RESPONSE = { 'source': {'aid': 12345, 'pubId': 54321}, 'bids': [{ @@ -57,7 +58,23 @@ const SERVER_DISPLAY_RESPONSE = { 'cur': 'USD', 'width': 300, 'cpm': 0.9 - }] + }], + 'cookieURLs': ['link1', 'link2'] +}; +const SERVER_DISPLAY_RESPONSE_WITH_MIXED_SYNCS = { + 'source': {'aid': 12345, 'pubId': 54321}, + 'bids': [{ + 'ad': '', + 'requestId': '2e41f65424c87c', + 'creative_id': 342516, + 'cmpId': 342516, + 'height': 250, + 'cur': 'USD', + 'width': 300, + 'cpm': 0.9 + }], + 'cookieURLs': ['link1', 'link2'], + 'cookieURLSTypes': ['image', 'iframe'] }; const videoBidderRequest = { @@ -70,6 +87,15 @@ const displayBidderRequest = { bids: [{bidId: '2e41f65424c87c'}] }; +const displayBidderRequestWithGdpr = { + bidderCode: 'bidderCode', + bids: [{bidId: '2e41f65424c87c'}], + gdprConsent: { + gdprApplies: true, + consentString: 'test' + } +}; + const videoEqResponse = [{ vastUrl: 'http://rtb.adtelligent.com/vast/?adid=44F2AEB9BFC881B3', requestId: '2e41f65424c87c', @@ -96,9 +122,47 @@ const displayEqResponse = [{ cpm: 0.9 }]; -describe('adtelligentBidAdapter', function () { +describe('adtelligentBidAdapter', function () { // todo remove only const adapter = newBidder(spec); + describe('user syncs as image', function () { + it('should be returned if pixel enabled', function () { + const syncs = spec.getUserSyncs({pixelEnabled: true}, [{body: SERVER_DISPLAY_RESPONSE_WITH_MIXED_SYNCS}]); + + expect(syncs.map(s => s.url)).to.deep.equal([SERVER_DISPLAY_RESPONSE_WITH_MIXED_SYNCS.cookieURLs[0]]); + expect(syncs.map(s => s.type)).to.deep.equal(['image']); + }) + }) + + describe('user syncs as iframe', function () { + it('should be returned if iframe enabled', function () { + const syncs = spec.getUserSyncs({iframeEnabled: true}, [{body: SERVER_DISPLAY_RESPONSE_WITH_MIXED_SYNCS}]); + + expect(syncs.map(s => s.url)).to.deep.equal([SERVER_DISPLAY_RESPONSE_WITH_MIXED_SYNCS.cookieURLs[1]]); + expect(syncs.map(s => s.type)).to.deep.equal(['iframe']); + }) + }) + + describe('user syncs with both types', function () { + it('should be returned if pixel and iframe enabled', function () { + const syncs = spec.getUserSyncs({ + iframeEnabled: true, + pixelEnabled: true + }, [{body: SERVER_DISPLAY_RESPONSE_WITH_MIXED_SYNCS}]); + + expect(syncs.map(s => s.url)).to.deep.equal(SERVER_DISPLAY_RESPONSE_WITH_MIXED_SYNCS.cookieURLs); + expect(syncs.map(s => s.type)).to.deep.equal(SERVER_DISPLAY_RESPONSE_WITH_MIXED_SYNCS.cookieURLSTypes); + }) + }) + + describe('user syncs', function () { + it('should not be returned if pixel not set', function () { + const syncs = spec.getUserSyncs({}, [{body: SERVER_DISPLAY_RESPONSE_WITH_MIXED_SYNCS}]); + + expect(syncs).to.be.empty; + }) + }) + describe('inherited functions', function () { it('exists and is a function', function () { expect(adapter.callBids).to.exist.and.to.be.a('function'); @@ -212,6 +276,13 @@ describe('adtelligentBidAdapter', function () { bidServerResponseCheck(); }); + it('should set gdpr data correctly', function () { + const builtRequestData = spec.buildRequests([DISPLAY_REQUEST], displayBidderRequestWithGdpr); + + expect(builtRequestData.data.gdpr).to.be.equal(1); + expect(builtRequestData.data.gdpr_consent).to.be.equal(displayBidderRequestWithGdpr.gdprConsent.consentString); + }); + function bidServerResponseCheck() { const result = spec.interpretResponse({body: serverResponse}, {bidderRequest}); diff --git a/test/spec/modules/advangelistsBidAdapter_spec.js b/test/spec/modules/advangelistsBidAdapter_spec.js new file mode 100755 index 00000000000..fbdfc9f30ee --- /dev/null +++ b/test/spec/modules/advangelistsBidAdapter_spec.js @@ -0,0 +1,137 @@ +import { expect } from 'chai'; +import { spec } from 'modules/advangelistsBidAdapter'; +import { BANNER, VIDEO } from 'src/mediaTypes'; + +describe('advangelistsBidAdapter', function () { + let bidRequests; + let bidRequestsVid; + + beforeEach(function () { + bidRequests = [{'bidder': 'advangelists', 'params': {'pubid': '0cf8d6d643e13d86a5b6374148a4afac', 'placement': 1234}, 'crumbs': {'pubcid': '979fde13-c71e-4ac2-98b7-28c90f99b449'}, 'mediaTypes': {'banner': {'sizes': [[300, 250]]}}, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'transactionId': 'f72931e6-2b0e-4e37-a2bc-1ea912141f81', 'sizes': [[300, 250]], 'bidId': '2aa73f571eaf29', 'bidderRequestId': '1bac84515a7af3', 'auctionId': '5dbc60fa-1aa1-41ce-9092-e6bbd4d478f7', 'src': 'client', 'bidRequestsCount': 1, 'pageurl': 'http://google.com'}]; + + bidRequestsVid = [{'bidder': 'advangelists', 'params': {'pubid': '8537f00948fc37cc03c5f0f88e198a76', 'placement': 1234, 'video': {'id': 123, 'skip': 1, 'mimes': ['video/mp4', 'application/javascript'], 'playbackmethod': [2, 6], 'maxduration': 30}}, 'crumbs': {'pubcid': '979fde13-c71e-4ac2-98b7-28c90f99b449'}, 'mediaTypes': {'video': {'playerSize': [[320, 480]], 'context': 'instream'}}, 'adUnitCode': 'video1', 'transactionId': '8b060952-93f7-4863-af44-bb8796b97c42', 'sizes': [], 'bidId': '25c6ab92aa0e81', 'bidderRequestId': '1d420b73a013fc', 'auctionId': '9a69741c-34fb-474c-83e1-cfa003aaee17', 'src': 'client', 'bidRequestsCount': 1, 'pageurl': 'http://google.com'}]; + }); + + describe('spec.isBidRequestValid', function () { + it('should return true when the required params are passed for banner', function () { + const bidRequest = bidRequests[0]; + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }); + + it('should return true when the required params are passed for video', function () { + const bidRequests = bidRequestsVid[0]; + expect(spec.isBidRequestValid(bidRequests)).to.equal(true); + }); + + it('should return false when no pub id params are passed', function () { + const bidRequest = bidRequests[0]; + bidRequest.params.pubid = ''; + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + + it('should return false when no placement params are passed', function () { + const bidRequest = bidRequests[0]; + bidRequest.params.placement = ''; + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + + it('should return false when a bid request is not passed', function () { + expect(spec.isBidRequestValid()).to.equal(false); + expect(spec.isBidRequestValid({})).to.equal(false); + }); + }); + + describe('spec.buildRequests', function () { + it('should create a POST request for each bid', function () { + const bidRequest = bidRequests[0]; + const requests = spec.buildRequests([ bidRequest ]); + expect(requests[0].method).to.equal('POST'); + }); + + it('should create a POST request for each bid in video request', function () { + const bidRequest = bidRequestsVid[0]; + const requests = spec.buildRequests([ bidRequest ]); + expect(requests[0].method).to.equal('POST'); + }); + + it('should have domain in request', function () { + const bidRequest = bidRequests[0]; + const requests = spec.buildRequests([ bidRequest ]); + expect(requests[0].data.site.domain).length !== 0; + }); + }); + + describe('spec.interpretResponse', function () { + describe('for banner bids', function () { + it('should return no bids if the response is not valid', function () { + const bidRequest = bidRequests[0]; + bidRequest.mediaTypes = { banner: {} }; + const bidResponse = spec.interpretResponse({ body: null }, { bidRequest }); + + if (typeof bidResponse !== 'undefined') { + expect(bidResponse.length).to.equal(0); + } else { + expect(true).to.equal(true); + } + }); + + it('should return no bids if the response is empty', function () { + const bidRequest = bidRequests[0]; + bidRequest.mediaTypes = { banner: {} }; + const bidResponse = spec.interpretResponse({ body: [] }, { bidRequest }); + if (typeof bidResponse !== 'undefined') { + expect(bidResponse.length).to.equal(0); + } else { expect(true).to.equal(true); } + }); + + it('should return valid video bid responses', function () { + let _mediaTypes = VIDEO; + const advangelistsbidreqVid = {'bidRequest': {'mediaTypes': {'video': {'w': 320, 'h': 480}}}}; + const serverResponseVid = {'cur': 'USD', 'id': '25c6ab92aa0e81', 'seatbid': [{'seat': '3', 'bid': [{'crid': '1855', 'h': 480, 'protocol': 2, 'nurl': 'http://nep.advangelists.com/xp/evt?pp=1MO1wiaMhhq7wLRzZZwwwPkJxxKpYEnM5k5MH4qSGm1HR8rp3Nl7vDocvzZzSAvE4pnREL9mQ1kf5PDjk6E8em6DOk7vVrYUH1TYQyqCucd58PFpJNN7h30RXKHHFg3XaLuQ3PKfMuI1qZATBJ6WHcu875y0hqRdiewn0J4JsCYF53M27uwmcV0HnQxARQZZ72mPqrW95U6wgkZljziwKrICM3aBV07TU6YK5R5AyzJRuD6mtrQ2xtHlQ3jXVYKE5bvWFiUQd90t0jOGhPtYBNoOfP7uQ4ZZj4pyucxbr96orHe9PSOn9UpCSWArdx7s8lOfDpwOvbMuyGxynbStDWm38sDgd4bMHnIt762m5VMDNJfiUyX0vWzp05OsufJDVEaWhAM62i40lQZo7mWP4ipoOWLkmlaAzFIMsTcNaHAHiKKqGEOZLkCEhFNM0SLcvgN2HFRULOOIZvusq7TydOKxuXgCS91dLUDxDDDFUK83BFKlMkTxnCzkLbIR1bd9GKcr1TRryOrulyvRWAKAIhEsUzsc5QWFUhmI2dZ1eqnBQJ0c89TaPcnoaP2WipF68UgyiOstf2CBy0M34858tC5PmuQwQYwXscg6zyqDwR0i9MzGH4FkTyU5yeOlPcsA0ht6UcoCdFpHpumDrLUwAaxwGk1Nj8S6YlYYT5wNuTifDGbg22QKXzZBkUARiyVvgPn9nRtXnrd7WmiMYq596rya9RQj7LC0auQW8bHVQLEe49shsZDnAwZTWr4QuYKqgRGZcXteG7RVJe0ryBZezOq11ha9C0Lv0siNVBahOXE35Wzoq4c4BDaGpqvhaKN7pjeWLGlQR04ufWekwxiMWAvjmfgAfexBJ7HfbYNZpq__', 'adid': '61_1855', 'adomain': ['chevrolet.com.ar'], 'price': 2, 'w': 320, 'iurl': 'https://daf37cpxaja7f.cloudfront.net/c61/creative_url_14922301369663_1.png', 'cat': ['IAB2'], 'id': '7f570b40-aca1-4806-8ea8-818ea679c82b_0', 'attr': [], 'impid': '0', 'cid': '61'}]}], 'bidid': '7f570b40-aca1-4806-8ea8-818ea679c82b'} + const bidResponseVid = spec.interpretResponse({ body: serverResponseVid }, advangelistsbidreqVid); + delete bidResponseVid['vastUrl']; + delete bidResponseVid['ad']; + expect(bidResponseVid).to.deep.equal({ + requestId: bidRequestsVid[0].bidId, + bidderCode: 'advangelists', + creativeId: serverResponseVid.seatbid[0].bid[0].crid, + cpm: serverResponseVid.seatbid[0].bid[0].price, + width: serverResponseVid.seatbid[0].bid[0].w, + height: serverResponseVid.seatbid[0].bid[0].h, + mediaType: 'video', + currency: 'USD', + netRevenue: true, + ttl: 60 + }); + }); + + it('should return valid banner bid responses', function () { + const advangelistsbidreq = {bids: {}}; + bidRequests.forEach(bid => { + let _mediaTypes = (bid.mediaTypes && bid.mediaTypes.video ? VIDEO : BANNER); + advangelistsbidreq.bids[bid.bidId] = {mediaTypes: _mediaTypes, + w: _mediaTypes == BANNER ? bid.mediaTypes[_mediaTypes].sizes[0][0] : bid.mediaTypes[_mediaTypes].playerSize[0], + h: _mediaTypes == BANNER ? bid.mediaTypes[_mediaTypes].sizes[0][1] : bid.mediaTypes[_mediaTypes].playerSize[1] + + }; + }); + const serverResponse = {'id': '2aa73f571eaf29', 'seatbid': [{'bid': [{'id': '2c5e8a1a84522d', 'impid': '2c5e8a1a84522d', 'price': 0.81, 'adid': 'abcde-12345', 'nurl': '', 'adm': '
    ', 'adomain': ['advertiserdomain.com'], 'iurl': '', 'cid': 'campaign1', 'crid': 'abcde-12345', 'w': 300, 'h': 250}], 'seat': '19513bcfca8006'}], 'bidid': '19513bcfca8006', 'cur': 'USD', 'w': 300, 'h': 250}; + + const bidResponse = spec.interpretResponse({ body: serverResponse }, advangelistsbidreq); + expect(bidResponse).to.deep.equal({ + requestId: bidRequests[0].bidId, + ad: serverResponse.seatbid[0].bid[0].adm, + bidderCode: 'advangelists', + creativeId: serverResponse.seatbid[0].bid[0].crid, + cpm: serverResponse.seatbid[0].bid[0].price, + width: serverResponse.seatbid[0].bid[0].w, + height: serverResponse.seatbid[0].bid[0].h, + mediaType: 'banner', + currency: 'USD', + netRevenue: true, + ttl: 60 + }); + }); + }); + }); +}); diff --git a/test/spec/modules/advenueBidAdapter_spec.js b/test/spec/modules/advenueBidAdapter_spec.js new file mode 100644 index 00000000000..f6ffb277bf9 --- /dev/null +++ b/test/spec/modules/advenueBidAdapter_spec.js @@ -0,0 +1,107 @@ +import {expect} from 'chai'; +import {spec} from '../../../modules/advenueBidAdapter'; + +describe('AdvenueAdapter', function () { + let bid = { + bidId: '2dd581a2b6281d', + bidder: 'advenue', + bidderRequestId: '145e1d6a7837c9', + params: { + placementId: 123, + traffic: 'banner' + }, + placementCode: 'placement_0', + auctionId: '74f78609-a92d-4cf1-869f-1b244bbfb5d2', + sizes: [[300, 250]], + transactionId: '3bb2f6da-87a6-4029-aeb0-bfe951372e62' + }; + + describe('isBidRequestValid', function () { + it('Should return true when placementId can be cast to a number', function () { + expect(spec.isBidRequestValid(bid)).to.be.true; + }); + it('Should return false when placementId is not a number', function () { + bid.params.placementId = 'aaa'; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + }); + + describe('buildRequests', function () { + let serverRequest = spec.buildRequests([bid]); + 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('//ssp.advenuemedia.co.uk/?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', 'secure', 'host', 'page', 'placements'); + expect(data.deviceWidth).to.be.a('number'); + expect(data.deviceHeight).to.be.a('number'); + expect(data.secure).to.be.within(0, 1); + expect(data.host).to.be.a('string'); + expect(data.page).to.be.a('string'); + let placements = data['placements']; + for (let i = 0; i < placements.length; i++) { + let placement = placements[i]; + expect(placement).to.have.all.keys('placementId', 'bidId', 'traffic', 'sizes'); + 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'); + } + }); + 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 () { + let resObject = { + body: [ { + requestId: '123', + mediaType: 'banner', + cpm: 0.3, + width: 320, + height: 50, + ad: '

    Hello ad

    ', + ttl: 1000, + creativeId: '123asd', + netRevenue: true, + currency: 'USD' + } ] + }; + let serverResponses = spec.interpretResponse(resObject); + it('Returns an array of valid server responses if response object is valid', function () { + expect(serverResponses).to.be.an('array').that.is.not.empty; + for (let i = 0; i < serverResponses.length; i++) { + let dataItem = serverResponses[i]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'mediaType'); + expect(dataItem.requestId).to.be.a('string'); + expect(dataItem.cpm).to.be.a('number'); + expect(dataItem.width).to.be.a('number'); + expect(dataItem.height).to.be.a('number'); + expect(dataItem.ad).to.be.a('string'); + expect(dataItem.ttl).to.be.a('number'); + expect(dataItem.creativeId).to.be.a('string'); + expect(dataItem.netRevenue).to.be.a('boolean'); + expect(dataItem.currency).to.be.a('string'); + expect(dataItem.mediaType).to.be.a('string'); + } + it('Returns an empty array if invalid response is passed', function () { + serverResponses = spec.interpretResponse('invalid_response'); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + }); + }); +}); diff --git a/test/spec/modules/adxcgAnalyticsAdapter_spec.js b/test/spec/modules/adxcgAnalyticsAdapter_spec.js index dfccbf6e87e..605da3bd6bc 100644 --- a/test/spec/modules/adxcgAnalyticsAdapter_spec.js +++ b/test/spec/modules/adxcgAnalyticsAdapter_spec.js @@ -1,7 +1,7 @@ import adxcgAnalyticsAdapter from 'modules/adxcgAnalyticsAdapter'; import { expect } from 'chai'; +import adapterManager from 'src/adapterManager.js'; -let adaptermanager = require('src/adaptermanager'); let events = require('src/events'); let constants = require('src/constants.json'); @@ -159,13 +159,13 @@ describe('adxcg analytics adapter', function () { }] }; - adaptermanager.registerAnalyticsAdapter({ + adapterManager.registerAnalyticsAdapter({ code: 'adxcg', adapter: adxcgAnalyticsAdapter }); beforeEach(function () { - adaptermanager.enableAnalytics({ + adapterManager.enableAnalytics({ provider: 'adxcg', options: initOptions }); diff --git a/test/spec/modules/adxcgBidAdapter_spec.js b/test/spec/modules/adxcgBidAdapter_spec.js index faa8db8050a..5bac9523b18 100644 --- a/test/spec/modules/adxcgBidAdapter_spec.js +++ b/test/spec/modules/adxcgBidAdapter_spec.js @@ -91,7 +91,7 @@ describe('AdxcgAdapter', function () { expect(query.pbjs).to.equal('$prebid.version$') expect(query.adzoneid).to.equal('1') expect(query.format).to.equal('300x250|640x360|1x1') - expect(query.jsonp).to.be.empty + expect(query.jsonp).to.be.undefined expect(query.prebidBidIds).to.equal('84ab500420319d') }) }) @@ -128,8 +128,58 @@ describe('AdxcgAdapter', function () { let parsedRequestUrl = url.parse(request.url) let query = parsedRequestUrl.search - expect(query.gdpr).to.be.empty - expect(query.gdpr_consent).to.be.empty + expect(query.gdpr).to.be.undefined + expect(query.gdpr_consent).to.be.undefined + }) + }) + + describe('userid pubcid should be passed to querystring', function () { + let bid = [{ + 'bidder': 'adxcg', + 'params': { + 'adzoneid': '1' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [640, 360], [1, 1]], + 'bidId': '84ab500420319d', + 'bidderRequestId': '7101db09af0db2', + 'auctionId': '1d1a030790a475', + }] + + let bidderRequests = {}; + + bid[0].userId = {'pubcid': 'pubcidabcd'}; + + it('should send pubcid if available', function () { + let request = spec.buildRequests(bid, bidderRequests) + let parsedRequestUrl = url.parse(request.url) + let query = parsedRequestUrl.search + expect(query.pubcid).to.equal('pubcidabcd') + }) + }) + + describe('userid tdid should be passed to querystring', function () { + let bid = [{ + 'bidder': 'adxcg', + 'params': { + 'adzoneid': '1' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [640, 360], [1, 1]], + 'bidId': '84ab500420319d', + 'bidderRequestId': '7101db09af0db2', + 'auctionId': '1d1a030790a475', + }] + + let bidderRequests = {}; + + bid[0].userId = {'tdid': 'tdidabcd'}; + + it('should send pubcid if available', function () { + let request = spec.buildRequests(bid, bidderRequests) + let parsedRequestUrl = url.parse(request.url) + let query = parsedRequestUrl.search + expect(query.tdid).to.equal('tdidabcd'); }) }) @@ -235,6 +285,14 @@ describe('AdxcgAdapter', function () { 'label': 'SPONSORED', 'value': 'sponsoredByContent' } + }, { + 'id': 5, + 'required': 0, + 'icon': { + 'url': 'iconContent', + 'w': 400, + 'h': 400 + } }], 'link': { 'url': 'linkContent' @@ -307,7 +365,15 @@ describe('AdxcgAdapter', function () { expect(result[0].native.clickUrl).to.equal('linkContent') expect(result[0].native.impressionTrackers).to.deep.equal(['impressionTracker1', 'impressionTracker2']) expect(result[0].native.title).to.equal('titleContent') - expect(result[0].native.image).to.equal('imageContent') + + expect(result[0].native.image.url).to.equal('imageContent') + expect(result[0].native.image.height).to.equal(600) + expect(result[0].native.image.width).to.equal(600) + + expect(result[0].native.icon.url).to.equal('iconContent') + expect(result[0].native.icon.height).to.equal(400) + expect(result[0].native.icon.width).to.equal(400) + expect(result[0].native.body).to.equal('descriptionContent') expect(result[0].native.sponsoredBy).to.equal('sponsoredByContent') }) diff --git a/test/spec/modules/adyoulikeBidAdapter_spec.js b/test/spec/modules/adyoulikeBidAdapter_spec.js index 26898d3f57e..7edb9416b03 100644 --- a/test/spec/modules/adyoulikeBidAdapter_spec.js +++ b/test/spec/modules/adyoulikeBidAdapter_spec.js @@ -7,7 +7,6 @@ import { newBidder } from 'src/adapters/bidderFactory'; describe('Adyoulike Adapter', function () { const canonicalUrl = 'http://canonical.url/?t=%26'; const defaultDC = 'hb-api'; - const bidderCode = 'adyoulike'; const bidRequestWithEmptyPlacement = [ { 'bidId': 'bid_id_0', @@ -18,7 +17,6 @@ describe('Adyoulike Adapter', function () { } ]; const bidRequestWithEmptySizes = { - 'bidderCode': 'adyoulike', 'bids': [ { 'bidId': 'bid_id_0', @@ -134,7 +132,7 @@ describe('Adyoulike Adapter', function () { ]; const adapter = newBidder(spec); - let getEndpoint = (dc = defaultDC) => `http://${dc}.omnitagjs.com/hb-api/prebid`; + let getEndpoint = (dc = defaultDC) => `https://${dc}.omnitagjs.com/hb-api/prebid`; describe('inherited functions', function () { it('exists and is a function', function () { @@ -193,7 +191,6 @@ describe('Adyoulike Adapter', function () { it('should add gdpr consent information to the request', function () { let consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; let bidderRequest = { - 'bidderCode': 'adyoulike', 'auctionId': '1d1a030790a475', 'bidderRequestId': '22edbae2733bf6', 'timeout': 3000, @@ -306,13 +303,11 @@ describe('Adyoulike Adapter', function () { expect(result.length).to.equal(2); - expect(result[0].bidderCode).to.equal(bidderCode); expect(result[0].cpm).to.equal(0.5); expect(result[0].ad).to.equal('placement_0'); expect(result[0].width).to.equal(300); expect(result[0].height).to.equal(300); - expect(result[1].bidderCode).to.equal(bidderCode); expect(result[1].cpm).to.equal(0.6); expect(result[1].ad).to.equal('placement_1'); expect(result[1].width).to.equal(300); diff --git a/test/spec/modules/ajaBidAdapter_spec.js b/test/spec/modules/ajaBidAdapter_spec.js index 5d5e5924cd5..642ac2f0df4 100644 --- a/test/spec/modules/ajaBidAdapter_spec.js +++ b/test/spec/modules/ajaBidAdapter_spec.js @@ -48,36 +48,44 @@ describe('AjaAdapter', function () { } ]; + let bidderRequest = { + refererInfo: { + referer: 'http://hoge.com' + } + }; + it('sends bid request to ENDPOINT via GET', function () { - const requests = spec.buildRequests(bidRequests); + const requests = spec.buildRequests(bidRequests, bidderRequest); expect(requests[0].url).to.equal(ENDPOINT); expect(requests[0].method).to.equal('GET'); + expect(requests[0].data).to.equal('asi=123456&skt=5&prebid_id=30b31c1838de1e&prebid_ver=$prebid.version$&page_url=http%3A%2F%2Fhoge.com&'); }); }); - describe('interpretResponse', function () { - let response = { - 'is_ad_return': true, - 'ad': { - 'ad_type': 1, - 'prebid_id': '51ef8751f9aead', - 'price': 12.34, - 'currency': 'USD', - 'creative_id': '123abc', - 'banner': { - 'w': 300, - 'h': 250, - 'tag': '
    ', - 'imps': [ - '//as.amanad.adtdp.com/v1/imp' - ] - } - }, - 'syncs': [ - 'https://example.com' - ] - }; + describe('interpretResponse', function () { it('should get correct banner bid response', function () { + let response = { + 'is_ad_return': true, + 'ad': { + 'ad_type': 1, + 'prebid_id': '51ef8751f9aead', + 'price': 12.34, + 'currency': 'USD', + 'creative_id': '123abc', + 'banner': { + 'w': 300, + 'h': 250, + 'tag': '
    ', + 'imps': [ + '//as.amanad.adtdp.com/v1/imp' + ] + } + }, + 'syncs': [ + 'https://example.com' + ] + }; + let expectedResponse = [ { 'requestId': '51ef8751f9aead', @@ -130,6 +138,96 @@ describe('AjaAdapter', function () { expect(result[0]).to.have.property('mediaType', 'video'); }); + it('handles native response', function () { + let response = { + 'is_ad_return': true, + 'ad': { + 'ad_type': 2, + 'prebid_id': '51ef8751f9aead', + 'price': 12.34, + 'currency': 'JPY', + 'creative_id': '123abc', + 'native': { + 'template_and_ads': { + 'head': '', + 'body_wrapper': '', + 'body': '', + 'ads': [ + { + 'ad_format_id': 10, + 'assets': { + 'ad_spot_id': '123abc', + 'index': 0, + 'adchoice_url': 'https://aja-kk.co.jp/optout', + 'cta_text': 'cta', + 'img_icon': 'https://example.com/img_icon', + 'img_icon_width': '50', + 'img_icon_height': '50', + 'img_main': 'https://example.com/img_main', + 'img_main_width': '200', + 'img_main_height': '100', + 'lp_link': 'https://example.com/lp?k=v', + 'sponsor': 'sponsor', + 'title': 'ad_title', + 'description': 'ad_desc' + }, + 'imps': [ + 'https://example.com/imp' + ], + 'inviews': [ + 'https://example.com/inview' + ], + 'jstracker': '', + 'disable_trimming': false + } + ] + } + } + }, + 'syncs': [ + 'https://example.com' + ] + }; + + let expectedResponse = [ + { + 'requestId': '51ef8751f9aead', + 'cpm': 12.34, + 'creativeId': '123abc', + 'dealId': undefined, + 'mediaType': 'native', + 'currency': 'JPY', + 'ttl': 300, + 'netRevenue': true, + 'native': { + 'title': 'ad_title', + 'body': 'ad_desc', + 'cta': 'cta', + 'sponsoredBy': 'sponsor', + 'image': { + 'url': 'https://example.com/img_main', + 'width': 200, + 'height': 100 + }, + 'icon': { + 'url': 'https://example.com/img_icon', + 'width': 50, + 'height': 50 + }, + 'clickUrl': 'https://example.com/lp?k=v', + 'impressionTrackers': [ + 'https://example.com/imp' + ], + 'privacyLink': 'https://aja-kk.co.jp/optout', + } + } + ]; + + let bidderRequest; + let result = spec.interpretResponse({ body: response }, {bidderRequest}) + expect(result).to.deep.equal(expectedResponse) + }); + it('handles nobid responses', function () { let response = { 'is_ad_return': false, @@ -141,4 +239,73 @@ describe('AjaAdapter', function () { expect(result.length).to.equal(0); }); }); + + describe('getUserSyncs', function () { + const bidResponse1 = { + body: { + 'is_ad_return': true, + 'ad': { /* ad body */ }, + 'syncs': [ + 'https://example.test/pixel/1' + ], + 'sync_htmls': [ + 'https://example.test/iframe/1' + ] + } + }; + + const bidResponse2 = { + body: { + 'is_ad_return': true, + 'ad': { /* ad body */ }, + 'syncs': [ + 'https://example.test/pixel/2' + ] + } + }; + + it('should use a sync url from first response (pixel and iframe)', function () { + const syncs = spec.getUserSyncs({ pixelEnabled: true, iframeEnabled: true }, [bidResponse1, bidResponse2]); + expect(syncs).to.deep.equal([ + { + type: 'image', + url: 'https://example.test/pixel/1' + }, + { + type: 'iframe', + url: 'https://example.test/iframe/1' + } + ]); + }); + + it('handle empty response (e.g. timeout)', function () { + const syncs = spec.getUserSyncs({ pixelEnabled: true, iframeEnabled: true }, []); + expect(syncs).to.deep.equal([]); + }); + + it('returns empty syncs when not pixel enabled and not iframe enabled', function () { + const syncs = spec.getUserSyncs({ pixelEnabled: false, iframeEnabled: false }, [bidResponse1]); + expect(syncs).to.deep.equal([]); + }); + + it('returns pixel syncs when pixel enabled and not iframe enabled', function() { + const syncs = spec.getUserSyncs({ pixelEnabled: true, iframeEnabled: false }, [bidResponse1]); + expect(syncs).to.deep.equal([ + { + type: 'image', + url: 'https://example.test/pixel/1' + } + ]); + }); + + it('returns iframe syncs when not pixel enabled and iframe enabled', function() { + const syncs = spec.getUserSyncs({ pixelEnabled: false, iframeEnabled: true }, [bidResponse1]); + expect(syncs).to.deep.equal([ + { + type: 'iframe', + url: 'https://example.test/iframe/1' + } + ]); + }); + }); }); diff --git a/test/spec/modules/aniviewBidAdapter_spec.js b/test/spec/modules/aniviewBidAdapter_spec.js new file mode 100644 index 00000000000..ce8a34509c4 --- /dev/null +++ b/test/spec/modules/aniviewBidAdapter_spec.js @@ -0,0 +1,183 @@ +import { spec } from 'modules/aniviewBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; +const { expect } = require('chai'); + +describe('ANIVIEW Bid Adapter Test', function () { + const adapter = newBidder(spec); + + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function () { + let bid = { + 'bidder': 'aniview', + 'params': { + 'AV_PUBLISHERID': '123456', + 'AV_CHANNELID': '123456' + }, + 'adUnitCode': 'video1', + 'sizes': [[300, 250], [640, 480]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'requestId': 'a09c66c3-53e3-4428-b296-38fc08e7cd2a', + 'transactionId': 'd6f6b392-54a9-454c-85fb-a2fd882c4a2d', + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not passed', function () { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + something: 'is wrong' + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + const ENDPOINT = 'https://v.lkqd.net/ad'; + let bid2Requests = [ + { + 'bidder': 'aniview', + 'params': { + 'AV_PUBLISHERID': '123456', + 'AV_CHANNELID': '123456' + }, + 'adUnitCode': 'test1', + 'sizes': [[300, 250], [640, 480]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'requestId': 'a09c66c3-53e3-4428-b296-38fc08e7cd2a', + 'transactionId': 'd6f6b392-54a9-454c-85fb-a2fd882c4a2d', + } + ]; + let bid1Request = [ + { + 'bidder': 'aniview', + 'params': { + 'AV_PUBLISHERID': '123456', + 'AV_CHANNELID': '123456' + }, + 'adUnitCode': 'test1', + 'sizes': [640, 480], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'requestId': 'a09c66c3-53e3-4428-b296-38fc08e7cd2a', + 'transactionId': 'd6f6b392-54a9-454c-85fb-a2fd882c4a2d', + } + ]; + + it('Test 2 requests', function () { + const requests = spec.buildRequests(bid2Requests); + expect(requests.length).to.equal(2); + const r1 = requests[0]; + const d1 = requests[0].data; + expect(d1).to.have.property('AV_PUBLISHERID'); + expect(d1.AV_PUBLISHERID).to.equal('123456'); + expect(d1).to.have.property('AV_CHANNELID'); + expect(d1.AV_CHANNELID).to.equal('123456'); + expect(d1).to.have.property('AV_WIDTH'); + expect(d1.AV_WIDTH).to.equal(300); + expect(d1).to.have.property('AV_HEIGHT'); + expect(d1.AV_HEIGHT).to.equal(250); + expect(d1).to.have.property('AV_URL'); + expect(d1).to.have.property('cb'); + expect(d1).to.have.property('s2s'); + expect(d1.s2s).to.equal('1'); + expect(d1).to.have.property('pbjs'); + expect(d1.pbjs).to.equal(1); + expect(r1).to.have.property('url'); + expect(r1.url).to.contain('https://gov.aniview.com/api/adserver/vast3/'); + const r2 = requests[1]; + const d2 = requests[1].data; + expect(d2).to.have.property('AV_PUBLISHERID'); + expect(d2.AV_PUBLISHERID).to.equal('123456'); + expect(d2).to.have.property('AV_CHANNELID'); + expect(d2.AV_CHANNELID).to.equal('123456'); + expect(d2).to.have.property('AV_WIDTH'); + expect(d2.AV_WIDTH).to.equal(640); + expect(d2).to.have.property('AV_HEIGHT'); + expect(d2.AV_HEIGHT).to.equal(480); + expect(d2).to.have.property('AV_URL'); + expect(d2).to.have.property('cb'); + expect(d2).to.have.property('s2s'); + expect(d2.s2s).to.equal('1'); + expect(d2).to.have.property('pbjs'); + expect(d2.pbjs).to.equal(1); + expect(r2).to.have.property('url'); + expect(r2.url).to.contain('https://gov.aniview.com/api/adserver/vast3/'); + }); + + it('Test 1 request', function () { + const requests = spec.buildRequests(bid1Request); + expect(requests.length).to.equal(1); + const r = requests[0]; + const d = requests[0].data; + expect(d).to.have.property('AV_PUBLISHERID'); + expect(d.AV_PUBLISHERID).to.equal('123456'); + expect(d).to.have.property('AV_CHANNELID'); + expect(d.AV_CHANNELID).to.equal('123456'); + expect(d).to.have.property('AV_WIDTH'); + expect(d.AV_WIDTH).to.equal(640); + expect(d).to.have.property('AV_HEIGHT'); + expect(d.AV_HEIGHT).to.equal(480); + expect(d).to.have.property('AV_URL'); + expect(d).to.have.property('cb'); + expect(d).to.have.property('s2s'); + expect(d.s2s).to.equal('1'); + expect(d).to.have.property('pbjs'); + expect(d.pbjs).to.equal(1); + expect(r).to.have.property('url'); + expect(r.url).to.contain('https://gov.aniview.com/api/adserver/vast3/'); + }); + }); + + describe('interpretResponse', function () { + let bidRequest = { + 'url': 'https://gov.aniview.com/api/adserver/vast3/', + 'data': { + 'bidId': '253dcb69fb2577', + AV_PUBLISHERID: '55b78633181f4603178b4568', + AV_CHANNELID: '55b7904d181f46410f8b4568', + } + }; + let serverResponse = {}; + serverResponse.body = 'FORDFORD00:00:15'; + + it('Check bid interpretResponse', function () { + const BIDDER_CODE = 'aniview'; + let bidResponses = spec.interpretResponse(serverResponse, bidRequest); + expect(bidResponses.length).to.equal(1); + let bidResponse = bidResponses[0]; + expect(bidResponse.requestId).to.equal(bidRequest.data.bidId); + expect(bidResponse.bidderCode).to.equal(BIDDER_CODE); + expect(bidResponse.cpm).to.equal('2'); + expect(bidResponse.ttl).to.equal(600); + expect(bidResponse.currency).to.equal('USD'); + expect(bidResponse.netRevenue).to.equal(true); + expect(bidResponse.mediaType).to.equal('video'); + }); + + it('safely handles XML parsing failure from invalid bid response', function () { + let invalidServerResponse = {}; + invalidServerResponse.body = ''; + + let result = spec.interpretResponse(invalidServerResponse, bidRequest); + expect(result.length).to.equal(0); + }); + + it('handles nobid responses', function () { + let nobidResponse = {}; + nobidResponse.body = ''; + + let result = spec.interpretResponse(nobidResponse, bidRequest); + expect(result.length).to.equal(0); + }); + }); +}); diff --git a/test/spec/modules/aolBidAdapter_spec.js b/test/spec/modules/aolBidAdapter_spec.js index 396ebf36c7d..8386c2c2462 100644 --- a/test/spec/modules/aolBidAdapter_spec.js +++ b/test/spec/modules/aolBidAdapter_spec.js @@ -76,8 +76,8 @@ let getPixels = () => { }; describe('AolAdapter', function () { - const MARKETPLACE_URL = '//adserver-us.adtech.advertising.com/pubapi/3.0/'; - const NEXAGE_URL = '//hb.nexage.com/bidRequest?'; + const MARKETPLACE_URL = 'https://adserver-us.adtech.advertising.com/pubapi/3.0/'; + const NEXAGE_URL = 'https://c2shb.ssp.yahoo.com/bidRequest?'; const ONE_DISPLAY_TTL = 60; const ONE_MOBILE_TTL = 3600; @@ -97,7 +97,6 @@ describe('AolAdapter', function () { let bidResponse; let bidRequest; let logWarnSpy; - let formatPixelsStub; let isOneMobileBidderStub; beforeEach(function () { @@ -111,14 +110,12 @@ describe('AolAdapter', function () { body: getDefaultBidResponse() }; logWarnSpy = sinon.spy(utils, 'logWarn'); - formatPixelsStub = sinon.stub(spec, 'formatPixels'); isOneMobileBidderStub = sinon.stub(spec, 'isOneMobileBidder'); }); afterEach(function () { $$PREBID_GLOBAL$$.bidderSettings = bidderSettingsBackup; logWarnSpy.restore(); - formatPixelsStub.restore(); isOneMobileBidderStub.restore(); }); @@ -139,27 +136,6 @@ describe('AolAdapter', function () { ttl: bidRequest.ttl }); }); - - it('should add pixels to ad content when pixels are present in the response', function () { - bidResponse.body.ext = { - pixels: 'pixels-content' - }; - - formatPixelsStub.returns('pixels-content'); - let formattedBidResponse = spec.interpretResponse(bidResponse, bidRequest); - - expect(formattedBidResponse.ad).to.equal(DEFAULT_AD_CONTENT + 'pixels-content'); - }); - - it('should show warning in the console', function() { - $$PREBID_GLOBAL$$.bidderSettings = { - aol: { - bidCpmAdjustment: function() {} - } - }; - spec.interpretResponse(bidResponse, bidRequest); - expect(utils.logWarn.calledOnce).to.be.true; - }); }); describe('buildRequests()', function () { @@ -170,13 +146,13 @@ describe('AolAdapter', function () { describe('Marketplace', function () { it('should not return request when no bids are present', function () { let [request] = spec.buildRequests([]); - expect(request).to.be.empty; + expect(request).to.be.undefined; }); it('should return request for Marketplace endpoint', function () { let bidRequest = getDefaultBidRequest(); let [request] = spec.buildRequests(bidRequest.bids); - expect(request.url).to.contain(MARKETPLACE_URL); + expect(request.url.indexOf(MARKETPLACE_URL)).to.equal(0); }); it('should return request for Marketplace via onedisplay bidder code', function () { @@ -188,7 +164,7 @@ describe('AolAdapter', function () { }); let [request] = spec.buildRequests(bidRequest.bids); - expect(request.url).to.contain(MARKETPLACE_URL); + expect(request.url.indexOf(MARKETPLACE_URL)).to.equal(0); }); it('should return Marketplace request via onedisplay bidder code when' + @@ -202,7 +178,7 @@ describe('AolAdapter', function () { }); let [request] = spec.buildRequests(bidRequest.bids); - expect(request.url).to.contain(MARKETPLACE_URL); + expect(request.url.indexOf(MARKETPLACE_URL)).to.equal(0); }); it('should return Marketplace request via onedisplay bidder code when' + @@ -216,7 +192,7 @@ describe('AolAdapter', function () { }); let [request] = spec.buildRequests(bidRequest.bids); - expect(request.url).to.contain(MARKETPLACE_URL); + expect(request.url.indexOf(MARKETPLACE_URL)).to.equal(0); }); it('should not resolve endpoint for onedisplay bidder code ' + @@ -230,7 +206,7 @@ describe('AolAdapter', function () { }); let [request] = spec.buildRequests(bidRequest.bids); - expect(request).to.be.empty; + expect(request).to.be.undefined; }); it('should return Marketplace URL for eu region', function () { @@ -242,10 +218,37 @@ describe('AolAdapter', function () { } }); let [request] = spec.buildRequests(bidRequest.bids); - expect(request.url).to.contain('adserver-eu.adtech.advertising.com/pubapi/3.0/'); + expect(request.url.indexOf('https://adserver-eu.adtech.advertising.com/pubapi/3.0/')) + .to.equal(0); + }); + + it('should return insecure MP URL if insecure server option is present', function () { + let bidRequest = createCustomBidRequest({ + params: { + placement: 1234567, + network: '9599.1', + server: 'http://adserver-eu.adtech.advertising.com' + } + }); + let [request] = spec.buildRequests(bidRequest.bids); + expect(request.url.indexOf('http://adserver-eu.adtech.advertising.com/pubapi/3.0/')) + .to.equal(0); }); - it('should return Marketplace URL for eu region when server option is present', function () { + it('should return a secure MP URL if relative proto server option is present', function () { + let bidRequest = createCustomBidRequest({ + params: { + placement: 1234567, + network: '9599.1', + server: '//adserver-eu.adtech.advertising.com' + } + }); + let [request] = spec.buildRequests(bidRequest.bids); + expect(request.url.indexOf('https://adserver-eu.adtech.advertising.com/pubapi/3.0/')) + .to.equal(0); + }); + + it('should return a secure MP URL when server option without protocol is present', function () { let bidRequest = createCustomBidRequest({ params: { placement: 1234567, @@ -254,7 +257,8 @@ describe('AolAdapter', function () { } }); let [request] = spec.buildRequests(bidRequest.bids); - expect(request.url).to.contain('adserver-eu.adtech.advertising.com/pubapi/3.0/'); + expect(request.url.indexOf('https://adserver-eu.adtech.advertising.com/pubapi/3.0/')) + .to.equal(0); }); it('should return default Marketplace URL in case of unknown region config option', function () { @@ -266,7 +270,7 @@ describe('AolAdapter', function () { } }); let [request] = spec.buildRequests(bidRequest.bids); - expect(request.url).to.contain(MARKETPLACE_URL); + expect(request.url.indexOf(MARKETPLACE_URL)).to.equal(0); }); it('should return url with pubapi bid option', function () { @@ -382,13 +386,13 @@ describe('AolAdapter', function () { it('should return One Mobile url with different host when host option is present', function () { let bidParams = Object.assign({ - host: 'qa-hb.nexage.com' + host: 'http://qa-hb.nexage.com' }, getNexageGetBidParams()); let bidRequest = createCustomBidRequest({ params: bidParams }); let [request] = spec.buildRequests(bidRequest.bids); - expect(request.url).to.contain('qa-hb.nexage.com/bidRequest?'); + expect(request.url).to.contain('http://qa-hb.nexage.com/bidRequest?'); }); it('should return One Mobile url when One Mobile and Marketplace params are present', function () { @@ -422,7 +426,7 @@ describe('AolAdapter', function () { params: getMarketplaceBidParams() }); let [request] = spec.buildRequests(bidRequest.bids); - expect(request).to.be.empty; + expect(request).to.be.undefined; }); it('should return One Mobile url with required params - dcn & pos', function () { @@ -455,7 +459,7 @@ describe('AolAdapter', function () { } }); let [request] = spec.buildRequests(bidRequest.bids); - expect(request.url).to.contain('hb.nexage.com/bidRequest?dcn=54321123&pos=footer-2324&cmd=bid' + + expect(request.url).to.equal('https://c2shb.ssp.yahoo.com/bidRequest?dcn=54321123&pos=footer-2324&cmd=bid' + '¶m1=val1¶m2=val2¶m3=val3¶m4=val4'); }); @@ -479,82 +483,52 @@ describe('AolAdapter', function () { }); it('should not return request object for One Mobile POST endpoint' + - 'if required parameterers are missed', () => { + 'if required parameters are missed', () => { let bidRequest = createCustomBidRequest({ params: { imp: [] } }); let [request] = spec.buildRequests(bidRequest.bids); - expect(request).to.be.empty; + expect(request).to.be.undefined; }); }); }); describe('getUserSyncs()', function () { + let serverResponses; let bidResponse; - let bidRequest; beforeEach(function () { - $$PREBID_GLOBAL$$.aolGlobals.pixelsDropped = false; - config.setConfig({ - aol: { - userSyncOn: 'bidResponse' - }, - }); bidResponse = getDefaultBidResponse(); bidResponse.ext = { pixels: getPixels() }; + + serverResponses = [ + {body: bidResponse} + ]; }); - it('should return user syncs only if userSyncOn equals to "bidResponse"', function () { - let userSyncs = spec.getUserSyncs({}, [bidResponse], bidRequest); + it('should return user syncs if pixels are present in the response', function () { + let userSyncs = spec.getUserSyncs({}, serverResponses); - expect($$PREBID_GLOBAL$$.aolGlobals.pixelsDropped).to.be.true; expect(userSyncs).to.deep.equal([ {type: 'image', url: 'img.org'}, {type: 'iframe', url: 'pixels1.org'} ]); }); - it('should not return user syncs if it has already been returned', function () { - $$PREBID_GLOBAL$$.aolGlobals.pixelsDropped = true; - - let userSyncs = spec.getUserSyncs({}, [bidResponse], bidRequest); - - expect($$PREBID_GLOBAL$$.aolGlobals.pixelsDropped).to.be.true; - expect(userSyncs).to.deep.equal([]); - }); - it('should not return user syncs if pixels are not present', function () { bidResponse.ext.pixels = null; + let userSyncs = spec.getUserSyncs({}, serverResponses); - let userSyncs = spec.getUserSyncs({}, [bidResponse], bidRequest); - - expect($$PREBID_GLOBAL$$.aolGlobals.pixelsDropped).to.be.false; expect(userSyncs).to.deep.equal([]); }); }); - describe('formatPixels()', function () { - it('should return pixels wrapped for dropping them once and within nested frames ', function () { - let pixels = ''; - let formattedPixels = spec.formatPixels(pixels); - - expect(formattedPixels).to.equal( - ''); - }); - }); - describe('isOneMobileBidder()', function () { - it('should return false when when bidderCode is not present', function () { + it('should return false when when bidderCode is not present', () => { expect(spec.isOneMobileBidder(null)).to.be.false; }); diff --git a/test/spec/modules/appierAnalyticsAdapter_spec.js b/test/spec/modules/appierAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..07b9796a4cb --- /dev/null +++ b/test/spec/modules/appierAnalyticsAdapter_spec.js @@ -0,0 +1,709 @@ +import { + appierAnalyticsAdapter, getCpmInUsd, parseBidderCode, parseAdUnitCode, + ANALYTICS_VERSION, BIDDER_STATUS +} from 'modules/appierAnalyticsAdapter'; +import {expect} from 'chai'; +const events = require('src/events'); +const constants = require('src/constants.json'); + +const affiliateId = 'WhctHaViHtI'; +const configId = 'd9cc9a9be9b240eda17cf1c9a8a4b29c'; +const serverUrl = 'https://analytics.server.url/v1'; +const autoPick = 'none'; +const predictionId = '2a91ca5de54a4a2e89950af439f7a27f'; +const auctionId = 'b0b39610-b941-4659-a87c-de9f62d3e13e'; + +describe('Appier Prebid AnalyticsAdapter Testing', function () { + describe('event tracking and message cache manager', function () { + let xhr; + + beforeEach(function () { + const configOptions = { + affiliateId: affiliateId, + configId: configId, + server: serverUrl, + autoPick: autoPick, + predictionId: predictionId, + sampling: 0, + adSampling: 1, + }; + + appierAnalyticsAdapter.enableAnalytics({ + provider: 'appierAnalytics', + options: configOptions + }); + xhr = sinon.useFakeXMLHttpRequest(); + }); + + afterEach(function () { + appierAnalyticsAdapter.disableAnalytics(); + xhr.restore(); + }); + + describe('#getCpmInUsd()', function() { + it('should get bid cpm as currency is USD', function() { + const receivedBids = [ + { + auctionId: auctionId, + adUnitCode: 'adunit_1', + bidder: 'appier', + bidderCode: 'APPIER', + requestId: 'a1b2c3d4', + timeToRespond: 72, + cpm: 0.1, + currency: 'USD', + originalCpm: 0.1, + originalCurrency: 'USD', + ad: 'fake ad1' + }, + ] + const result = getCpmInUsd(receivedBids[0]); + expect(result).to.equal(0.1); + }); + }); + + describe('#parseBidderCode()', function() { + it('should get lower case bidder code from bidderCode field value', function() { + const receivedBids = [ + { + auctionId: auctionId, + adUnitCode: 'adunit_1', + bidder: 'appier', + bidderCode: 'APPIER', + requestId: 'a1b2c3d4', + timeToRespond: 72, + cpm: 0.1, + currency: 'USD', + originalCpm: 0.1, + originalCurrency: 'USD', + ad: 'fake ad1' + }, + ]; + const result = parseBidderCode(receivedBids[0]); + expect(result).to.equal('appier'); + }); + it('should get lower case bidder code from bidder field value as bidderCode field is missing', function() { + const receivedBids = [ + { + auctionId: auctionId, + adUnitCode: 'adunit_1', + bidder: 'APPIER', + bidderCode: '', + requestId: 'a1b2c3d4', + timeToRespond: 72, + cpm: 0.1, + currency: 'USD', + originalCpm: 0.1, + originalCurrency: 'USD', + ad: 'fake ad1' + }, + ]; + const result = parseBidderCode(receivedBids[0]); + expect(result).to.equal('appier'); + }); + }); + + describe('#parseAdUnitCode()', function() { + it('should get lower case adUnit code from adUnitCode field value', function() { + const receivedBids = [ + { + auctionId: auctionId, + adUnitCode: 'ADUNIT', + bidder: 'appier', + bidderCode: 'APPIER', + requestId: 'a1b2c3d4', + timeToRespond: 72, + cpm: 0.1, + currency: 'USD', + originalCpm: 0.1, + originalCurrency: 'USD', + ad: 'fake ad1' + }, + ]; + const result = parseAdUnitCode(receivedBids[0]); + expect(result).to.equal('adunit'); + }); + }); + + describe('#getCachedAuction()', function() { + const existing = {timeoutBids: [{}]}; + appierAnalyticsAdapter.cachedAuctions['test_auction_id'] = existing; + + it('should get the existing cached object if it exists', function() { + const result = appierAnalyticsAdapter.getCachedAuction('test_auction_id'); + + expect(result).to.equal(existing); + }); + + it('should create a new object and store it in the cache on cache miss', function() { + const result = appierAnalyticsAdapter.getCachedAuction('no_such_id'); + + expect(result).to.deep.include({ + timeoutBids: [], + }); + }); + }); + + describe('when formatting JSON payload sent to backend', function() { + const receivedBids = [ + { + auctionId: auctionId, + adUnitCode: 'adunit_1', + bidder: 'appier', + bidderCode: 'appier', + requestId: 'a1b2c3d4', + timeToRespond: 72, + cpm: 0.1, + currency: 'USD', + originalCpm: 0.1, + originalCurrency: 'USD', + ad: 'fake ad1' + }, + { + auctionId: auctionId, + adUnitCode: 'adunit_1', + bidder: 'reippa', + bidderCode: 'reippa', + requestId: 'b2c3d4e5', + timeToRespond: 100, + cpm: 0.08, + currency: 'USD', + originalCpm: 0.08, + originalCurrency: 'USD', + ad: 'fake ad2' + }, + { + auctionId: auctionId, + adUnitCode: 'adunit_2', + bidder: 'appier', + bidderCode: 'appier', + requestId: 'c3d4e5f6', + timeToRespond: 120, + cpm: 0.09, + currency: 'USD', + originalCpm: 0.09, + originalCurrency: 'USD', + ad: 'fake ad3' + }, + ]; + const highestCpmBids = [ + { + auctionId: auctionId, + adUnitCode: 'adunit_1', + bidder: 'appier', + bidderCode: 'appier', + // No requestId + timeToRespond: 72, + cpm: 0.1, + currency: 'USD', + originalCpm: 0.1, + originalCurrency: 'USD', + ad: 'fake ad1' + } + ]; + const noBids = [ + { + auctionId: auctionId, + adUnitCode: 'adunit_2', + bidder: 'appier', + bidderCode: 'appier', + bidId: 'a1b2c3d4', + } + ]; + const timeoutBids = [ + { + auctionId: auctionId, + adUnitCode: 'adunit_2', + bidder: 'reippa', + bidderCode: 'reippa', + bidId: '00123d4c', + } + ]; + const withoutOriginalCpmBids = [ + { + auctionId: auctionId, + adUnitCode: 'adunit_2', + bidder: 'appier', + bidderCode: 'appier', + requestId: 'c3d4e5f6', + timeToRespond: 120, + cpm: 0.29, + currency: 'USD', + originalCpm: '', + originalCurrency: 'USD', + ad: 'fake ad3' + }, + ]; + const withoutOriginalCurrencyBids = [ + { + auctionId: auctionId, + adUnitCode: 'adunit_2', + bidder: 'appier', + bidderCode: 'appier', + requestId: 'c3d4e5f6', + timeToRespond: 120, + cpm: 0.09, + currency: 'USD', + originalCpm: 0.09, + originalCurrency: '', + ad: 'fake ad3' + }, + ]; + function assertHavingRequiredMessageFields(message) { + expect(message).to.include({ + version: ANALYTICS_VERSION, + auctionId: auctionId, + affiliateId: affiliateId, + configId: configId, + // referrer: window.location.href, + sampling: 0, + adSampling: 1, + prebid: '$prebid.version$', + // autoPick: 'manual', + }); + } + + describe('#createCommonMessage', function() { + it('should correctly serialize some common fields', function() { + const message = appierAnalyticsAdapter.createCommonMessage(auctionId); + + assertHavingRequiredMessageFields(message); + }); + }); + + describe('#serializeBidResponse', function() { + it('should handle BID properly and serialize bid price related fields', function() { + const result = appierAnalyticsAdapter.serializeBidResponse(receivedBids[0], BIDDER_STATUS.BID); + + expect(result).to.include({ + prebidWon: false, + isTimeout: false, + status: BIDDER_STATUS.BID, + time: 72, + cpm: 0.1, + currency: 'USD', + originalCpm: 0.1, + originalCurrency: 'USD', + cpmUsd: 0.1, + }); + }); + + it('should handle NO_BID properly and set status to noBid', function() { + const result = appierAnalyticsAdapter.serializeBidResponse(noBids[0], BIDDER_STATUS.NO_BID); + + expect(result).to.include({ + prebidWon: false, + isTimeout: false, + status: BIDDER_STATUS.NO_BID, + }); + }); + + it('should handle BID_WON properly and serialize bid price related fields', function() { + const result = appierAnalyticsAdapter.serializeBidResponse(receivedBids[0], BIDDER_STATUS.BID_WON); + + expect(result).to.include({ + prebidWon: true, + isTimeout: false, + status: BIDDER_STATUS.BID_WON, + time: 72, + cpm: 0.1, + currency: 'USD', + originalCpm: 0.1, + originalCurrency: 'USD', + cpmUsd: 0.1, + }); + }); + + it('should handle TIMEOUT properly and set status to timeout and isTimeout to true', function() { + const result = appierAnalyticsAdapter.serializeBidResponse(noBids[0], BIDDER_STATUS.TIMEOUT); + + expect(result).to.include({ + prebidWon: false, + isTimeout: true, + status: BIDDER_STATUS.TIMEOUT, + }); + }); + + it('should handle BID_WON properly and fill originalCpm field with cpm in missing originalCpm case', function() { + const result = appierAnalyticsAdapter.serializeBidResponse(withoutOriginalCpmBids[0], BIDDER_STATUS.BID_WON); + + expect(result).to.include({ + prebidWon: true, + isTimeout: false, + status: BIDDER_STATUS.BID_WON, + time: 120, + cpm: 0.29, + currency: 'USD', + originalCpm: 0.29, + originalCurrency: 'USD', + cpmUsd: 0.29, + }); + }); + + it('should handle BID_WON properly and fill originalCurrency field with currency in missing originalCurrency case', function() { + const result = appierAnalyticsAdapter.serializeBidResponse(withoutOriginalCurrencyBids[0], BIDDER_STATUS.BID_WON); + expect(result).to.include({ + prebidWon: true, + isTimeout: false, + status: BIDDER_STATUS.BID_WON, + time: 120, + cpm: 0.09, + currency: 'USD', + originalCpm: 0.09, + originalCurrency: 'USD', + cpmUsd: 0.09, + }); + }); + }); + + describe('#addBidResponseToMessage()', function() { + it('should add a bid response in the output message, grouped by adunit_id and bidder', function() { + const message = { + adUnits: {} + }; + appierAnalyticsAdapter.addBidResponseToMessage(message, noBids[0], BIDDER_STATUS.NO_BID); + + expect(message.adUnits).to.deep.include({ + 'adunit_2': { + 'appier': { + prebidWon: false, + isTimeout: false, + status: BIDDER_STATUS.NO_BID, + } + } + }); + }); + }); + + describe('#createBidMessage()', function() { + it('should format auction message sent to the backend', function() { + const args = { + auctionId: auctionId, + timestamp: 1234567890, + timeout: 3000, + auctionEnd: 1234567990, + adUnitCodes: ['adunit_1', 'adunit_2'], + bidsReceived: receivedBids, + noBids: noBids + }; + + const result = appierAnalyticsAdapter.createBidMessage(args, highestCpmBids, timeoutBids); + + assertHavingRequiredMessageFields(result); + expect(result).to.deep.include({ + auctionElapsed: 100, + timeout: 3000, + adUnits: { + 'adunit_1': { + 'appier': { + prebidWon: true, + isTimeout: false, + status: BIDDER_STATUS.BID, + time: 72, + cpm: 0.1, + currency: 'USD', + originalCpm: 0.1, + originalCurrency: 'USD', + cpmUsd: 0.1, + }, + 'reippa': { + prebidWon: false, + isTimeout: false, + status: BIDDER_STATUS.BID, + time: 100, + cpm: 0.08, + currency: 'USD', + originalCpm: 0.08, + originalCurrency: 'USD', + cpmUsd: 0.08, + } + }, + 'adunit_2': { + // this bid result exists in both bid and noBid arrays and should be treated as bid + 'appier': { + prebidWon: false, + isTimeout: false, + time: 120, + cpm: 0.09, + currency: 'USD', + originalCpm: 0.09, + originalCurrency: 'USD', + cpmUsd: 0.09, + status: BIDDER_STATUS.BID, + }, + 'reippa': { + prebidWon: false, + isTimeout: true, + status: BIDDER_STATUS.TIMEOUT, + } + } + } + }); + }); + }); + + describe('#createImpressionMessage()', function() { + it('should format message sent to the backend with the bid result', function() { + const bid = receivedBids[0]; + const result = appierAnalyticsAdapter.createImpressionMessage(bid); + + assertHavingRequiredMessageFields(result); + expect(result.adUnits).to.deep.include({ + 'adunit_1': { + 'appier': { + prebidWon: true, + isTimeout: false, + status: BIDDER_STATUS.BID_WON, + time: 72, + cpm: 0.1, + currency: 'USD', + originalCpm: 0.1, + originalCurrency: 'USD', + cpmUsd: 0.1, + } + } + }); + }); + }); + + describe('#createCreativeMessage()', function() { + it('should generate message sent to the backend with ad html grouped by adunit and bidder', function() { + const result = appierAnalyticsAdapter.createCreativeMessage(auctionId, receivedBids); + + assertHavingRequiredMessageFields(result); + expect(result.adUnits).to.deep.include({ + 'adunit_1': { + 'appier': { + ad: 'fake ad1' + }, + 'reippa': { + ad: 'fake ad2' + }, + }, + 'adunit_2': { + 'appier': { + ad: 'fake ad3' + } + } + }); + }); + }); + describe('#handleBidTimeout()', function() { + it('should cached the timeout bid as BID_TIMEOUT event was triggered', function() { + appierAnalyticsAdapter.cachedAuctions['test_timeout_auction_id'] = { 'timeoutBids': [] }; + const args = [{ + auctionId: 'test_timeout_auction_id', + timestamp: 1234567890, + timeout: 3000, + auctionEnd: 1234567990, + bidsReceived: receivedBids, + noBids: noBids + }]; + + appierAnalyticsAdapter.handleBidTimeout(args); + const result = appierAnalyticsAdapter.getCachedAuction('test_timeout_auction_id'); + expect(result).to.deep.include({ + timeoutBids: [{ + auctionId: 'test_timeout_auction_id', + timestamp: 1234567890, + timeout: 3000, + auctionEnd: 1234567990, + bidsReceived: receivedBids, + noBids: noBids + }] + }); + }); + }); + describe('#handleBidWon()', function() { + it('should call createImpressionMessage once as BID_WON event was triggered', function() { + sinon.spy(appierAnalyticsAdapter, 'createImpressionMessage'); + const receivedBids = [ + { + auctionId: auctionId, + adUnitCode: 'adunit_1', + bidder: 'appier', + bidderCode: 'appier', + requestId: 'a1b2c3d4', + timeToRespond: 72, + cpm: 0.1, + currency: 'USD', + originalCpm: 0.1, + originalCurrency: 'USD', + ad: 'fake ad1' + }, + ] + + appierAnalyticsAdapter.handleBidWon(receivedBids[0]); + sinon.assert.callCount(appierAnalyticsAdapter.createImpressionMessage, 1); + appierAnalyticsAdapter.createImpressionMessage.restore(); + }); + }); + }); + }); + + describe('Appier Analytics Adapter track handler ', function () { + const configOptions = { + affiliateId: affiliateId, + configId: configId, + server: serverUrl, + autoPick: autoPick, + sampling: 1, + adSampling: 1, + }; + + beforeEach(function () { + sinon.stub(events, 'getEvents').returns([]); + appierAnalyticsAdapter.enableAnalytics({ + provider: 'appierAnalytics', + options: configOptions + }); + }); + + afterEach(function () { + appierAnalyticsAdapter.disableAnalytics(); + events.getEvents.restore(); + }); + + it('should call handleBidWon as BID_WON trigger event', function() { + sinon.spy(appierAnalyticsAdapter, 'handleBidWon'); + events.emit(constants.EVENTS.BID_WON, {}); + sinon.assert.callCount(appierAnalyticsAdapter.handleBidWon, 1); + appierAnalyticsAdapter.handleBidWon.restore(); + }); + + it('should call handleBidTimeout as BID_TIMEOUT trigger event', function() { + sinon.spy(appierAnalyticsAdapter, 'handleBidTimeout'); + events.emit(constants.EVENTS.BID_TIMEOUT, {}); + sinon.assert.callCount(appierAnalyticsAdapter.handleBidTimeout, 1); + appierAnalyticsAdapter.handleBidTimeout.restore(); + }); + + it('should call handleAuctionEnd as AUCTION_END trigger event', function() { + sinon.spy(appierAnalyticsAdapter, 'handleAuctionEnd'); + events.emit(constants.EVENTS.AUCTION_END, {}); + sinon.assert.callCount(appierAnalyticsAdapter.handleAuctionEnd, 1); + appierAnalyticsAdapter.handleAuctionEnd.restore(); + }); + + it('should call createCreativeMessage as AUCTION_END trigger event in adSampled is true', function() { + const configOptions = { + options: { + affiliateId: affiliateId, + configId: configId, + server: serverUrl, + autoPick: autoPick, + sampling: 1, + adSampling: 1, + adSampled: true, + } + }; + const args = { + auctionId: 'test_timeout_auction_id', + timestamp: 1234567890, + timeout: 3000, + auctionEnd: 1234567990, + }; + appierAnalyticsAdapter.initConfig(configOptions); + sinon.stub(appierAnalyticsAdapter, 'sendEventMessage').returns({}); + sinon.stub(appierAnalyticsAdapter, 'createBidMessage').returns({}); + sinon.spy(appierAnalyticsAdapter, 'createCreativeMessage'); + events.emit(constants.EVENTS.AUCTION_END, args); + sinon.assert.callCount(appierAnalyticsAdapter.createCreativeMessage, 1); + appierAnalyticsAdapter.sendEventMessage.restore(); + appierAnalyticsAdapter.createBidMessage.restore(); + appierAnalyticsAdapter.createCreativeMessage.restore(); + }); + }); + + describe('enableAnalytics and config parser', function () { + const configOptions = { + affiliateId: affiliateId, + configId: configId, + server: serverUrl, + autoPick: autoPick, + sampling: 0, + adSampling: 1, + }; + + beforeEach(function () { + appierAnalyticsAdapter.enableAnalytics({ + provider: 'appierAnalytics', + options: configOptions + }); + }); + + afterEach(function () { + appierAnalyticsAdapter.disableAnalytics(); + }); + + it('should parse config correctly with optional values', function () { + expect(appierAnalyticsAdapter.getAnalyticsOptions().options).to.deep.equal(configOptions); + expect(appierAnalyticsAdapter.getAnalyticsOptions().affiliateId).to.equal(configOptions.affiliateId); + expect(appierAnalyticsAdapter.getAnalyticsOptions().configId).to.equal(configOptions.configId); + expect(appierAnalyticsAdapter.getAnalyticsOptions().server).to.equal(configOptions.server); + expect(appierAnalyticsAdapter.getAnalyticsOptions().autoPick).to.equal(configOptions.autoPick); + expect(appierAnalyticsAdapter.getAnalyticsOptions().sampled).to.equal(false); + expect(appierAnalyticsAdapter.getAnalyticsOptions().adSampled).to.equal(true); + }); + + it('should not enable Analytics when affiliateId is missing', function () { + const configOptions = { + options: { + configId: configId + } + }; + const validConfig = appierAnalyticsAdapter.initConfig(configOptions); + expect(validConfig).to.equal(false); + }); + + it('should use DEFAULT_SERVER when server is missing', function () { + const configOptions = { + options: { + configId: configId, + affiliateId: affiliateId + } + }; + appierAnalyticsAdapter.initConfig(configOptions); + expect(appierAnalyticsAdapter.getAnalyticsOptions().server).to.equal('https://prebid-analytics.c.appier.net/v1'); + }); + + it('should use null when autoPick is missing', function () { + const configOptions = { + options: { + configId: configId, + affiliateId: affiliateId + } + }; + appierAnalyticsAdapter.initConfig(configOptions); + expect(appierAnalyticsAdapter.getAnalyticsOptions().autoPick).to.equal(null); + }); + + it('should not enable Analytics when configId is missing', function () { + const configOptions = { + options: { + affiliateId: affiliateId + } + }; + const validConfig = appierAnalyticsAdapter.initConfig(configOptions); + expect(validConfig).to.equal(false); + }); + + it('should fall back to default value when sampling factor is not number', function () { + const configOptions = { + options: { + affiliateId: affiliateId, + configId: configId, + sampling: 'string', + adSampling: 'string' + } + }; + appierAnalyticsAdapter.enableAnalytics({ + provider: 'appierAnalytics', + options: configOptions + }); + + expect(appierAnalyticsAdapter.getAnalyticsOptions().sampled).to.equal(false); + expect(appierAnalyticsAdapter.getAnalyticsOptions().adSampled).to.equal(true); + }); + }); +}); diff --git a/test/spec/modules/appierBidAdapter_spec.js b/test/spec/modules/appierBidAdapter_spec.js new file mode 100644 index 00000000000..c7fc5744d1c --- /dev/null +++ b/test/spec/modules/appierBidAdapter_spec.js @@ -0,0 +1,174 @@ +import { expect } from 'chai'; +import { spec, API_SERVERS_MAP, ADAPTER_VERSION } from 'modules/appierBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; +import { config } from 'src/config'; + +describe('AppierAdapter', function () { + const adapter = newBidder(spec); + + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function () { + let bid = { + 'bidder': 'appier', + 'params': { + 'hzid': 'abcd' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + + it('should return true when required params zoneId found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required param zoneId is missing', function () { + let bid = Object.assign({}, bid); + bid.params = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when required param zoneId has wrong type', function () { + let bid = Object.assign({}, bid); + bid.params = { + 'hzid': null + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function() { + it('should return an empty list when there are no bid requests', function() { + const fakeBidRequests = []; + const fakeBidderRequest = {}; + expect(spec.buildRequests(fakeBidRequests, fakeBidderRequest)).to.be.an('array').that.is.empty; + }); + + it('should generate a POST bid request with method, url, and data fields', function() { + const bid = { + 'bidder': 'appier', + 'params': { + 'hzid': 'abcd' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + const fakeBidRequests = [bid]; + const fakeBidderRequest = {refererInfo: { + 'referer': 'fakeReferer', + 'reachedTop': true, + 'numIframes': 1, + 'stack': [] + }}; + + const builtRequests = spec.buildRequests(fakeBidRequests, fakeBidderRequest); + expect(builtRequests.length).to.equal(1); + expect(builtRequests[0].method).to.equal('POST'); + expect(builtRequests[0].url).match(/v1\/prebid\/bid/); + expect(builtRequests[0].data).deep.equal({ + 'bids': fakeBidRequests, + 'refererInfo': fakeBidderRequest.refererInfo, + 'version': ADAPTER_VERSION + }); + }); + }); + + describe('interpretResponse', function() { + const bid = { + 'bidder': 'appier', + 'params': { + 'hzid': 'abcd' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + const fakeBidRequests = [bid]; + + it('should return an empty aray to indicate no valid bids', function() { + const fakeServerResponse = {}; + + const bidResponses = spec.interpretResponse(fakeServerResponse, fakeBidRequests); + + expect(bidResponses).is.an('array').that.is.empty; + }); + + it('should generate correct response array for bidder', function() { + const fakeBidResult = { + 'requestId': '30b31c1838de1e', + 'cpm': 0.0029346001, + 'creativeId': 'Idl0P0d5S3Ca5kVWcia-wQ', + 'width': 300, + 'height': 250, + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 300, + 'ad': '
    fake html
    ', + 'appierParams': { + 'hzid': 'test_hzid' + } + }; + const fakeServerResponse = { + headers: [], + body: [fakeBidResult] + }; + + const bidResponses = spec.interpretResponse(fakeServerResponse, fakeBidRequests); + expect(bidResponses).deep.equal([fakeBidResult]); + }); + }); + + describe('getApiServer', function() { + it('should use the server specified by setConfig(appier.server)', function() { + config.setConfig({ + 'appier': {'server': 'fake_server'} + }); + + const server = spec.getApiServer(); + + expect(server).equals('fake_server'); + }); + + it('should retrieve a farm specific hostname if server is not specpfied', function() { + config.setConfig({ + 'appier': {'farm': 'tw'} + }); + + const server = spec.getApiServer(); + + expect(server).equals(API_SERVERS_MAP['tw']); + }); + + it('if farm is not recognized, use the default farm', function() { + config.setConfig({ + 'appier': {'farm': 'no_this_farm'} + }); + + const server = spec.getApiServer(); + + expect(server).equals(API_SERVERS_MAP['default']); + }); + + it('if farm is not specified, use the default farm', function() { + config.setConfig({ + 'appier': {} + }); + + const server = spec.getApiServer(); + + expect(server).equals(API_SERVERS_MAP['default']); + }); + }); +}); diff --git a/test/spec/modules/appnexusBidAdapter_spec.js b/test/spec/modules/appnexusBidAdapter_spec.js index d9e21a95f78..762833f29b8 100644 --- a/test/spec/modules/appnexusBidAdapter_spec.js +++ b/test/spec/modules/appnexusBidAdapter_spec.js @@ -1,7 +1,10 @@ import { expect } from 'chai'; import { spec } from 'modules/appnexusBidAdapter'; import { newBidder } from 'src/adapters/bidderFactory'; +import * as bidderFactory from 'src/adapters/bidderFactory'; +import { auctionManager } from 'src/auctionManager'; import { deepClone } from 'src/utils'; +import { config } from 'src/config'; const ENDPOINT = '//ib.adnxs.com/ut/v3/prebid'; @@ -53,6 +56,7 @@ describe('AppNexusAdapter', function () { }); describe('buildRequests', function () { + let getAdUnitsStub; let bidRequests = [ { 'bidder': 'appnexus', @@ -64,9 +68,20 @@ describe('AppNexusAdapter', function () { 'bidId': '30b31c1838de1e', 'bidderRequestId': '22edbae2733bf6', 'auctionId': '1d1a030790a475', + 'transactionId': '04f2659e-c005-4eb1-a57c-fa93145e3843' } ]; + beforeEach(function() { + getAdUnitsStub = sinon.stub(auctionManager, 'getAdUnits').callsFake(function() { + return []; + }); + }); + + afterEach(function() { + getAdUnitsStub.restore(); + }); + it('should parse out private sizes', function () { let bidRequest = Object.assign({}, bidRequests[0], @@ -96,7 +111,27 @@ describe('AppNexusAdapter', function () { }); it('should populate the ad_types array on all requests', function () { + let adUnits = [{ + code: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + bids: [{ + bidder: 'appnexus', + params: { + placementId: '10433394' + } + }], + transactionId: '04f2659e-c005-4eb1-a57c-fa93145e3843' + }]; + ['banner', 'video', 'native'].forEach(type => { + getAdUnitsStub.callsFake(function(...args) { + return adUnits; + }); + const bidRequest = Object.assign({}, bidRequests[0]); bidRequest.mediaTypes = {}; bidRequest.mediaTypes[type] = {}; @@ -105,9 +140,21 @@ describe('AppNexusAdapter', function () { const payload = JSON.parse(request.data); expect(payload.tags[0].ad_types).to.deep.equal([type]); + + if (type === 'banner') { + delete adUnits[0].mediaTypes; + } }); }); + it('should not populate the ad_types array when adUnit.mediaTypes is undefined', function() { + const bidRequest = Object.assign({}, bidRequests[0]); + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + expect(payload.tags[0].ad_types).to.not.exist; + }); + it('should populate the ad_types array on outstream requests', function () { const bidRequest = Object.assign({}, bidRequests[0]); bidRequest.mediaTypes = {}; @@ -148,6 +195,48 @@ describe('AppNexusAdapter', function () { }); }); + it('should add video property when adUnit includes a renderer', function () { + const videoData = { + mediaTypes: { + video: { + context: 'outstream', + mimes: ['video/mp4'] + } + }, + params: { + placementId: '10433394', + video: { + skippable: true, + playback_method: ['auto_play_sound_off'] + } + } + }; + + let bidRequest1 = deepClone(bidRequests[0]); + bidRequest1 = Object.assign({}, bidRequest1, videoData, { + renderer: { + url: 'http://test.renderer.url', + render: function () {} + } + }); + + let bidRequest2 = deepClone(bidRequests[0]); + bidRequest2.adUnitCode = 'adUnit_code_2'; + bidRequest2 = Object.assign({}, bidRequest2, videoData); + + const request = spec.buildRequests([bidRequest1, bidRequest2]); + const payload = JSON.parse(request.data); + expect(payload.tags[0].video).to.deep.equal({ + skippable: true, + playback_method: ['auto_play_sound_off'], + custom_renderer_present: true + }); + expect(payload.tags[1].video).to.deep.equal({ + skippable: true, + playback_method: ['auto_play_sound_off'] + }); + }); + it('should attach valid user params to the tag', function () { let bidRequest = Object.assign({}, bidRequests[0], @@ -155,7 +244,7 @@ describe('AppNexusAdapter', function () { params: { placementId: '10433394', user: { - external_uid: '123', + externalUid: '123', foobar: 'invalid' } } @@ -167,69 +256,204 @@ describe('AppNexusAdapter', function () { expect(payload.user).to.exist; expect(payload.user).to.deep.equal({ - external_uid: '123', + externalUid: '123', }); }); - it('should attach native params to the request', function () { + it('should duplicate adpod placements into batches and set correct maxduration', function() { let bidRequest = Object.assign({}, bidRequests[0], { - mediaType: 'native', - nativeParams: { - title: {required: true}, - body: {required: true}, - image: {required: true, sizes: [{ width: 100, height: 100 }]}, - cta: {required: false}, - sponsoredBy: {required: true} + params: { placementId: '14542875' } + }, + { + mediaTypes: { + video: { + context: 'adpod', + playerSize: [640, 480], + adPodDurationSec: 300, + durationRangeSec: [15, 30], + } + } + } + ); + + const request = spec.buildRequests([bidRequest]); + const payload1 = JSON.parse(request[0].data); + const payload2 = JSON.parse(request[1].data); + + // 300 / 15 = 20 total + expect(payload1.tags.length).to.equal(15); + expect(payload2.tags.length).to.equal(5); + + expect(payload1.tags[0]).to.deep.equal(payload1.tags[1]); + expect(payload1.tags[0].video.maxduration).to.equal(30); + + expect(payload2.tags[0]).to.deep.equal(payload1.tags[1]); + expect(payload2.tags[0].video.maxduration).to.equal(30); + }); + + it('should round down adpod placements when numbers are uneven', function() { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { placementId: '14542875' } + }, + { + mediaTypes: { + video: { + context: 'adpod', + playerSize: [640, 480], + adPodDurationSec: 123, + durationRangeSec: [45], + } } } ); const request = spec.buildRequests([bidRequest]); const payload = JSON.parse(request.data); + expect(payload.tags.length).to.equal(2); + }); - expect(payload.tags[0].native.layouts[0]).to.deep.equal({ - title: {required: true}, - description: {required: true}, - main_image: {required: true, sizes: [{ width: 100, height: 100 }]}, - ctatext: {required: false}, - sponsored_by: {required: true} - }); + it('should duplicate adpod placements when requireExactDuration is set', function() { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { placementId: '14542875' } + }, + { + mediaTypes: { + video: { + context: 'adpod', + playerSize: [640, 480], + adPodDurationSec: 300, + durationRangeSec: [15, 30], + requireExactDuration: true, + } + } + } + ); + + // 20 total placements with 15 max impressions = 2 requests + const request = spec.buildRequests([bidRequest]); + expect(request.length).to.equal(2); + + // 20 spread over 2 requests = 15 in first request, 5 in second + const payload1 = JSON.parse(request[0].data); + const payload2 = JSON.parse(request[1].data); + expect(payload1.tags.length).to.equal(15); + expect(payload2.tags.length).to.equal(5); + + // 10 placements should have max/min at 15 + // 10 placemenst should have max/min at 30 + const payload1tagsWith15 = payload1.tags.filter(tag => tag.video.maxduration === 15); + const payload1tagsWith30 = payload1.tags.filter(tag => tag.video.maxduration === 30); + expect(payload1tagsWith15.length).to.equal(10); + expect(payload1tagsWith30.length).to.equal(5); + + // 5 placemenst with min/max at 30 were in the first request + // so 5 remaining should be in the second + const payload2tagsWith30 = payload2.tags.filter(tag => tag.video.maxduration === 30); + expect(payload2tagsWith30.length).to.equal(5); }); - it('sets minimum native asset params when not provided on adunit', function () { + it('should set durations for placements when requireExactDuration is set and numbers are uneven', function() { let bidRequest = Object.assign({}, bidRequests[0], { - mediaType: 'native', - nativeParams: { - image: {required: true}, + params: { placementId: '14542875' } + }, + { + mediaTypes: { + video: { + context: 'adpod', + playerSize: [640, 480], + adPodDurationSec: 105, + durationRangeSec: [15, 30, 60], + requireExactDuration: true, + } } } ); const request = spec.buildRequests([bidRequest]); const payload = JSON.parse(request.data); + expect(payload.tags.length).to.equal(7); - expect(payload.tags[0].native.layouts[0]).to.deep.equal({ - main_image: {required: true, sizes: [{}]}, - }); + const tagsWith15 = payload.tags.filter(tag => tag.video.maxduration === 15); + const tagsWith30 = payload.tags.filter(tag => tag.video.maxduration === 30); + const tagsWith60 = payload.tags.filter(tag => tag.video.maxduration === 60); + expect(tagsWith15.length).to.equal(3); + expect(tagsWith30.length).to.equal(3); + expect(tagsWith60.length).to.equal(1); }); - it('does not overwrite native ad unit params with mimimum params', function () { + it('should break adpod request into batches', function() { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { placementId: '14542875' } + }, + { + mediaTypes: { + video: { + context: 'adpod', + playerSize: [640, 480], + adPodDurationSec: 225, + durationRangeSec: [5], + } + } + } + ); + + const request = spec.buildRequests([bidRequest]); + const payload1 = JSON.parse(request[0].data); + const payload2 = JSON.parse(request[1].data); + const payload3 = JSON.parse(request[2].data); + + expect(payload1.tags.length).to.equal(15); + expect(payload2.tags.length).to.equal(15); + expect(payload3.tags.length).to.equal(15); + }); + + it('adds brand_category_exclusion to request when set', function() { + let bidRequest = Object.assign({}, bidRequests[0]); + sinon + .stub(config, 'getConfig') + .withArgs('adpod.brandCategoryExclusion') + .returns(true); + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + expect(payload.brand_category_uniqueness).to.equal(true); + + config.getConfig.restore(); + }); + + it('should attach native params to the request', function () { let bidRequest = Object.assign({}, bidRequests[0], { mediaType: 'native', nativeParams: { - image: { - aspect_ratios: [{ - min_width: 100, - ratio_width: 2, - ratio_height: 3, - }] - } + title: {required: true}, + body: {required: true}, + body2: {required: true}, + image: {required: true, sizes: [100, 100]}, + icon: {required: true}, + cta: {required: false}, + rating: {required: true}, + sponsoredBy: {required: true}, + privacyLink: {required: true}, + displayUrl: {required: true}, + address: {required: true}, + downloads: {required: true}, + likes: {required: true}, + phone: {required: true}, + price: {required: true}, + salePrice: {required: true} } } ); @@ -238,17 +462,50 @@ describe('AppNexusAdapter', function () { const payload = JSON.parse(request.data); expect(payload.tags[0].native.layouts[0]).to.deep.equal({ - main_image: { - required: true, - aspect_ratios: [{ - min_width: 100, - ratio_width: 2, - ratio_height: 3, - }] - }, + title: {required: true}, + description: {required: true}, + desc2: {required: true}, + main_image: {required: true, sizes: [{ width: 100, height: 100 }]}, + icon: {required: true}, + ctatext: {required: false}, + rating: {required: true}, + sponsored_by: {required: true}, + privacy_link: {required: true}, + displayurl: {required: true}, + address: {required: true}, + downloads: {required: true}, + likes: {required: true}, + phone: {required: true}, + price: {required: true}, + saleprice: {required: true}, + privacy_supported: true }); }); + it('should always populated tags[].sizes with 1,1 for native if otherwise not defined', function () { + let bidRequest = Object.assign({}, + bidRequests[0], + { + mediaType: 'native', + nativeParams: { + image: { required: true } + } + } + ); + bidRequest.sizes = [[150, 100], [300, 250]]; + + let request = spec.buildRequests([bidRequest]); + let payload = JSON.parse(request.data); + expect(payload.tags[0].sizes).to.deep.equal([{width: 150, height: 100}, {width: 300, height: 250}]); + + delete bidRequest.sizes; + + request = spec.buildRequests([bidRequest]); + payload = JSON.parse(request.data); + + expect(payload.tags[0].sizes).to.deep.equal([{width: 1, height: 1}]); + }); + it('should convert keyword params to proper form and attaches to request', function () { let bidRequest = Object.assign({}, bidRequests[0], @@ -261,6 +518,8 @@ describe('AppNexusAdapter', function () { singleArrNum: [5], multiValMixed: ['value1', 2, 'value3'], singleValNum: 123, + emptyStr: '', + emptyArr: [''], badValue: {'foo': 'bar'} // should be dropped } } @@ -285,6 +544,10 @@ describe('AppNexusAdapter', function () { }, { 'key': 'singleValNum', 'value': ['123'] + }, { + 'key': 'emptyStr' + }, { + 'key': 'emptyArr' }]); }); @@ -370,9 +633,104 @@ describe('AppNexusAdapter', function () { lng: -75.3009142 }); }); + + it('should add referer info to payload', function () { + const bidRequest = Object.assign({}, bidRequests[0]) + const bidderRequest = { + refererInfo: { + referer: 'http://example.com/page.html', + reachedTop: true, + numIframes: 2, + stack: [ + 'http://example.com/page.html', + 'http://example.com/iframe1.html', + 'http://example.com/iframe2.html' + ] + } + } + const request = spec.buildRequests([bidRequest], bidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.referrer_detection).to.exist; + expect(payload.referrer_detection).to.deep.equal({ + rd_ref: 'http%3A%2F%2Fexample.com%2Fpage.html', + rd_top: true, + rd_ifs: 2, + rd_stk: bidderRequest.refererInfo.stack.map((url) => encodeURIComponent(url)).join(',') + }); + }); + + it('should populate tpids array when userId is available', function () { + const bidRequest = Object.assign({}, bidRequests[0], { + userId: { + criteortus: { + appnexus: { + userid: 'sample-userid' + } + } + } + }); + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + expect(payload.tpuids).to.deep.equal([{provider: 'criteo', user_id: 'sample-userid'}]); + }); + + it('should populate schain if available', function () { + const bidRequest = Object.assign({}, bidRequests[0], { + schain: { + ver: '1.0', + complete: 1, + nodes: [ + { + 'asi': 'blob.com', + 'sid': '001', + 'hp': 1 + } + ] + } + }); + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + expect(payload.schain).to.deep.equal({ + ver: '1.0', + complete: 1, + nodes: [ + { + 'asi': 'blob.com', + 'sid': '001', + 'hp': 1 + } + ] + }); + }); + + it('should populate coppa if set in config', function () { + let bidRequest = Object.assign({}, bidRequests[0]); + sinon.stub(config, 'getConfig') + .withArgs('coppa') + .returns(true); + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + expect(payload.user.coppa).to.equal(true); + + config.getConfig.restore(); + }); }) describe('interpretResponse', function () { + let bfStub; + before(function() { + bfStub = sinon.stub(bidderFactory, 'getIabSubCategory'); + }); + + after(function() { + bfStub.restore(); + }); + let response = { 'version': '3.0.0', 'tags': [ @@ -396,6 +754,9 @@ describe('AppNexusAdapter', function () { 'cpm_publisher_currency': 0.5, 'publisher_currency_code': '$', 'client_initiated_ad_counting': true, + 'viewability': { + 'config': '' + }, 'rtb': { 'banner': { 'content': '', @@ -431,12 +792,18 @@ describe('AppNexusAdapter', function () { 'currency': 'USD', 'ttl': 300, 'netRevenue': true, + 'adUnitCode': 'code', 'appnexus': { 'buyerMemberId': 958 } } ]; - let bidderRequest; + let bidderRequest = { + bids: [{ + bidId: '3db3773286ee59', + adUnitCode: 'code' + }] + } let result = spec.interpretResponse({ body: response }, {bidderRequest}); expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); }); @@ -457,7 +824,7 @@ describe('AppNexusAdapter', function () { expect(result.length).to.equal(0); }); - it('handles non-banner media responses', function () { + it('handles outstream video responses', function () { let response = { 'tags': [{ 'uuid': '84ab500420319d', @@ -467,13 +834,59 @@ describe('AppNexusAdapter', function () { 'notify_url': 'imptracker.com', 'rtb': { 'video': { - 'content': '' + 'content': '' } + }, + 'javascriptTrackers': '' + }] + }] + }; + let bidderRequest = { + bids: [{ + bidId: '84ab500420319d', + adUnitCode: 'code', + mediaTypes: { + video: { + context: 'outstream' } + } + }] + } + + let result = spec.interpretResponse({ body: response }, {bidderRequest}); + expect(result[0]).to.have.property('vastXml'); + expect(result[0]).to.have.property('vastImpUrl'); + expect(result[0]).to.have.property('mediaType', 'video'); + }); + + it('handles instream video responses', function () { + let response = { + 'tags': [{ + 'uuid': '84ab500420319d', + 'ads': [{ + 'ad_type': 'video', + 'cpm': 0.500000, + 'notify_url': 'imptracker.com', + 'rtb': { + 'video': { + 'asset_url': 'http://sample.vastURL.com/here/vid' + } + }, + 'javascriptTrackers': '' }] }] }; - let bidderRequest; + let bidderRequest = { + bids: [{ + bidId: '84ab500420319d', + adUnitCode: 'code', + mediaTypes: { + video: { + context: 'instream' + } + } + }] + } let result = spec.interpretResponse({ body: response }, {bidderRequest}); expect(result[0]).to.have.property('vastUrl'); @@ -481,12 +894,54 @@ describe('AppNexusAdapter', function () { expect(result[0]).to.have.property('mediaType', 'video'); }); + it('handles adpod responses', function () { + let response = { + 'tags': [{ + 'uuid': '84ab500420319d', + 'ads': [{ + 'ad_type': 'video', + 'brand_category_id': 10, + 'cpm': 0.500000, + 'notify_url': 'imptracker.com', + 'rtb': { + 'video': { + 'asset_url': 'http://sample.vastURL.com/here/adpod', + 'duration_ms': 30000, + } + }, + 'viewability': { + 'config': '' + } + }] + }] + }; + + let bidderRequest = { + bids: [{ + bidId: '84ab500420319d', + adUnitCode: 'code', + mediaTypes: { + video: { + context: 'adpod' + } + } + }] + }; + bfStub.returns('1'); + + let result = spec.interpretResponse({ body: response }, {bidderRequest}); + expect(result[0]).to.have.property('vastUrl'); + expect(result[0].video.context).to.equal('adpod'); + expect(result[0].video.durationSeconds).to.equal(30); + }); + it('handles native responses', function () { let response1 = deepClone(response); response1.tags[0].ads[0].ad_type = 'native'; response1.tags[0].ads[0].rtb.native = { 'title': 'Native Creative', 'desc': 'Cool description great stuff', + 'desc2': 'Additional body text', 'ctatext': 'Do it', 'sponsored': 'AppNexus', 'icon': { @@ -505,8 +960,23 @@ describe('AppNexusAdapter', function () { 'click_trackers': ['http://nym1-ib.adnxs.com/click'] }, 'impression_trackers': ['http://example.com'], + 'rating': '5', + 'displayurl': 'http://AppNexus.com/?url=display_url', + 'likes': '38908320', + 'downloads': '874983', + 'price': '9.99', + 'saleprice': 'FREE', + 'phone': '1234567890', + 'address': '28 W 23rd St, New York, NY 10010', + 'privacy_link': 'http://appnexus.com/?url=privacy_url', + 'javascriptTrackers': '' }; - let bidderRequest; + let bidderRequest = { + bids: [{ + bidId: '3db3773286ee59', + adUnitCode: 'code' + }] + } let result = spec.interpretResponse({ body: response1 }, {bidderRequest}); expect(result[0].native.title).to.equal('Native Creative'); @@ -522,10 +992,16 @@ describe('AppNexusAdapter', function () { const bidderRequest = { bids: [{ + bidId: '3db3773286ee59', renderer: { options: { adText: 'configured' } + }, + mediaTypes: { + video: { + context: 'outstream' + } } }] }; @@ -535,5 +1011,34 @@ describe('AppNexusAdapter', function () { bidderRequest.bids[0].renderer.options ); }); + + it('should add deal_priority and deal_code', function() { + let responseWithDeal = deepClone(response); + responseWithDeal.tags[0].ads[0].deal_priority = 'high'; + responseWithDeal.tags[0].ads[0].deal_code = '123'; + + let bidderRequest = { + bids: [{ + bidId: '3db3773286ee59', + adUnitCode: 'code' + }] + } + let result = spec.interpretResponse({ body: responseWithDeal }, {bidderRequest}); + expect(Object.keys(result[0].appnexus)).to.include.members(['buyerMemberId', 'dealPriority', 'dealCode']); + }); + + it('should add advertiser id', function() { + let responseAdvertiserId = deepClone(response); + responseAdvertiserId.tags[0].ads[0].advertiser_id = '123'; + + let bidderRequest = { + bids: [{ + bidId: '3db3773286ee59', + adUnitCode: 'code' + }] + } + let result = spec.interpretResponse({ body: responseAdvertiserId }, {bidderRequest}); + expect(Object.keys(result[0].meta)).to.include.members(['advertiserId']); + }) }); }); diff --git a/test/spec/modules/audienceNetworkBidAdapter_spec.js b/test/spec/modules/audienceNetworkBidAdapter_spec.js index 9e3f37b7395..a495b33438c 100644 --- a/test/spec/modules/audienceNetworkBidAdapter_spec.js +++ b/test/spec/modules/audienceNetworkBidAdapter_spec.js @@ -19,7 +19,7 @@ const placementId = 'test-placement-id'; const playerwidth = 320; const playerheight = 180; const requestId = 'test-request-id'; -const debug = 'adapterver=1.0.1&platform=241394079772386&platver=$prebid.version$'; +const debug = 'adapterver=1.3.0&platform=241394079772386&platver=$prebid.version$&cb=test-uuid'; const pageUrl = encodeURIComponent(utils.getTopWindowUrl()); describe('AudienceNetwork adapter', function () { @@ -119,20 +119,21 @@ describe('AudienceNetwork adapter', function () { }); describe('buildRequests', function () { - let isSafariBrowserStub; before(function () { - isSafariBrowserStub = sinon.stub(utils, 'isSafariBrowser'); + sinon + .stub(utils, 'generateUUID') + .returns('test-uuid'); }); after(function () { - isSafariBrowserStub.restore(); + utils.generateUUID.restore(); }); it('can build URL for IAB unit', function () { expect(buildRequests([{ bidder, bidId: requestId, - sizes: [[300, 250], [320, 50]], + sizes: [[300, 50], [300, 250], [320, 50]], params: { placementId } }])).to.deep.equal([{ adformats: ['300x250'], @@ -140,7 +141,7 @@ describe('AudienceNetwork adapter', function () { requestIds: [requestId], sizes: ['300x250'], url: 'https://an.facebook.com/v2/placementbid.json', - data: `placementids[]=test-placement-id&adformats[]=300x250&testmode=false&pageurl=${pageUrl}&sdk[]=5.5.web&${debug}` + data: `placementids[]=test-placement-id&adformats[]=300x250&testmode=false&pageurl=${pageUrl}&sdk[]=6.0.web&${debug}` }]); }); @@ -178,11 +179,11 @@ describe('AudienceNetwork adapter', function () { requestIds: [requestId], sizes: ['728x90'], url: 'https://an.facebook.com/v2/placementbid.json', - data: `placementids[]=test-placement-id&adformats[]=native&testmode=false&pageurl=${pageUrl}&sdk[]=5.5.web&${debug}` + data: `placementids[]=test-placement-id&adformats[]=native&testmode=false&pageurl=${pageUrl}&sdk[]=6.0.web&${debug}` }]); }); - it('can build URL for fullwidth 300x250 unit, overriding platform', function () { + it('can build URL for deprecated fullwidth unit, overriding platform', function () { const platform = 'test-platform'; const debugPlatform = debug.replace('241394079772386', platform); @@ -196,24 +197,14 @@ describe('AudienceNetwork adapter', function () { format: 'fullwidth' } }])).to.deep.equal([{ - adformats: ['fullwidth'], + adformats: ['300x250'], method: 'GET', requestIds: [requestId], sizes: ['300x250'], url: 'https://an.facebook.com/v2/placementbid.json', - data: `placementids[]=test-placement-id&adformats[]=fullwidth&testmode=false&pageurl=${pageUrl}&sdk[]=5.5.web&${debugPlatform}` + data: `placementids[]=test-placement-id&adformats[]=300x250&testmode=false&pageurl=${pageUrl}&sdk[]=6.0.web&${debugPlatform}` }]); }); - - it('can build URL on Safari that includes a cachebuster param', function () { - isSafariBrowserStub.returns(true); - expect(buildRequests([{ - bidder, - bidId: requestId, - sizes: [[300, 250]], - params: { placementId } - }])[0].data).to.contain('&cb='); - }); }); describe('interpretResponse', function () { @@ -250,7 +241,9 @@ describe('AudienceNetwork adapter', function () { expect(bidResponse.width).to.equal(300); expect(bidResponse.height).to.equal(250); expect(bidResponse.ad) - .to.contain(`placementid:'${placementId}',format:'native',bidid:'test-bid-id'`, 'ad missing parameters') + .to.contain(`placementid: '${placementId}',`) + .and.to.contain(`format: 'native',`) + .and.to.contain(`bidid: 'test-bid-id',`) .and.to.contain('getElementsByTagName("style")', 'ad missing native styles') .and.to.contain('
    ', 'ad missing native container'); expect(bidResponse.ttl).to.equal(600); @@ -289,7 +282,9 @@ describe('AudienceNetwork adapter', function () { expect(bidResponse.width).to.equal(300); expect(bidResponse.height).to.equal(250); expect(bidResponse.ad) - .to.contain(`placementid:'${placementId}',format:'300x250',bidid:'test-bid-id'`, 'ad missing parameters') + .to.contain(`placementid: '${placementId}',`) + .and.to.contain(`format: '300x250',`) + .and.to.contain(`bidid: 'test-bid-id',`) .and.not.to.contain('getElementsByTagName("style")', 'ad should not contain native styles') .and.not.to.contain('
    ', 'ad should not contain native container'); expect(bidResponse.ttl).to.equal(600); @@ -370,7 +365,10 @@ describe('AudienceNetwork adapter', function () { expect(bidResponseNative.requestId).to.equal(requestId); expect(bidResponseNative.width).to.equal(300); expect(bidResponseNative.height).to.equal(250); - expect(bidResponseNative.ad).to.contain(`placementid:'${placementIdNative}',format:'native',bidid:'test-bid-id-native'`, 'ad missing parameters'); + expect(bidResponseNative.ad) + .to.contain(`placementid: '${placementIdNative}',`) + .and.to.contain(`format: 'native',`) + .and.to.contain(`bidid: 'test-bid-id-native',`); expect(bidResponseNative.ttl).to.equal(600); expect(bidResponseNative.creativeId).to.equal(placementIdNative); expect(bidResponseNative.netRevenue).to.equal(true); @@ -384,7 +382,10 @@ describe('AudienceNetwork adapter', function () { expect(bidResponseIab.requestId).to.equal(requestId); expect(bidResponseIab.width).to.equal(300); expect(bidResponseIab.height).to.equal(250); - expect(bidResponseIab.ad).to.contain(`placementid:'${placementIdIab}',format:'300x250',bidid:'test-bid-id-iab'`, 'ad missing parameters'); + expect(bidResponseIab.ad) + .to.contain(`placementid: '${placementIdIab}',`) + .and.to.contain(`format: '300x250',`) + .and.to.contain(`bidid: 'test-bid-id-iab',`); expect(bidResponseIab.ttl).to.equal(600); expect(bidResponseIab.creativeId).to.equal(placementIdIab); expect(bidResponseIab.netRevenue).to.equal(true); @@ -471,7 +472,10 @@ describe('AudienceNetwork adapter', function () { expect(bidResponseNative.ttl).to.equal(600); expect(bidResponseNative.width).to.equal(300); expect(bidResponseNative.height).to.equal(250); - expect(bidResponseNative.ad).to.contain(`placementid:'${nativePlacementId}',format:'native',bidid:'${nativeBidId}'`); + expect(bidResponseNative.ad) + .to.contain(`placementid: '${nativePlacementId}',`) + .and.to.contain(`format: 'native',`) + .and.to.contain(`bidid: '${nativeBidId}',`); }); it('mixture of valid native bid and error in response', function () { @@ -499,7 +503,9 @@ describe('AudienceNetwork adapter', function () { expect(bidResponse.width).to.equal(300); expect(bidResponse.height).to.equal(250); expect(bidResponse.ad) - .to.contain(`placementid:'${placementId}',format:'native',bidid:'test-bid-id'`, 'ad missing parameters') + .to.contain(`placementid: '${placementId}',`) + .and.to.contain(`format: 'native',`) + .and.to.contain(`bidid: 'test-bid-id',`) .and.to.contain('getElementsByTagName("style")', 'ad missing native styles') .and.to.contain('
    ', 'ad missing native container'); expect(bidResponse.ttl).to.equal(600); diff --git a/test/spec/modules/beachfrontBidAdapter_spec.js b/test/spec/modules/beachfrontBidAdapter_spec.js index a70fdfb77b6..155da4fb309 100644 --- a/test/spec/modules/beachfrontBidAdapter_spec.js +++ b/test/spec/modules/beachfrontBidAdapter_spec.js @@ -1,6 +1,7 @@ import { expect } from 'chai'; +import sinon from 'sinon'; import { spec, VIDEO_ENDPOINT, BANNER_ENDPOINT, OUTSTREAM_SRC, DEFAULT_MIMES } from 'modules/beachfrontBidAdapter'; -import * as utils from 'src/utils'; +import { parse as parseUrl } from 'src/url'; describe('BeachfrontAdapter', function () { let bidRequests; @@ -150,9 +151,14 @@ describe('BeachfrontAdapter', function () { playerSize: [ width, height ] } }; - const requests = spec.buildRequests([ bidRequest ]); + const topLocation = parseUrl('http://www.example.com?foo=bar', { decodeSearchAsString: true }); + const bidderRequest = { + refererInfo: { + referer: topLocation.href + } + }; + const requests = spec.buildRequests([ bidRequest ], bidderRequest); const data = requests[0].data; - const topLocation = utils.getTopWindowLocation(); expect(data.isPrebid).to.equal(true); expect(data.appId).to.equal(bidRequest.params.appId); expect(data.domain).to.equal(document.location.hostname); @@ -218,11 +224,14 @@ describe('BeachfrontAdapter', function () { it('must override video targeting params', function () { const bidRequest = bidRequests[0]; const mimes = ['video/webm']; + const playbackmethod = 2; + const maxduration = 30; + const placement = 4; bidRequest.mediaTypes = { video: {} }; - bidRequest.params.video = { mimes }; + bidRequest.params.video = { mimes, playbackmethod, maxduration, placement }; const requests = spec.buildRequests([ bidRequest ]); const data = requests[0].data; - expect(data.imp[0].video).to.deep.contain({ mimes }); + expect(data.imp[0].video).to.deep.contain({ mimes, playbackmethod, maxduration, placement }); }); it('must add GDPR consent data to the request', function () { @@ -240,6 +249,24 @@ describe('BeachfrontAdapter', function () { expect(data.regs.ext.gdpr).to.equal(1); expect(data.user.ext.consent).to.equal(consentString); }); + + it('must add the Trade Desk User ID to the request', () => { + const tdid = '4321'; + const bidRequest = bidRequests[0]; + bidRequest.mediaTypes = { video: {} }; + bidRequest.userId = { tdid }; + const requests = spec.buildRequests([ bidRequest ]); + const data = requests[0].data; + expect(data.user.ext.eids[0]).to.deep.equal({ + source: 'adserver.org', + uids: [{ + id: tdid, + ext: { + rtiPartner: 'TDID' + } + }] + }); + }); }); describe('for banner bids', function () { @@ -268,9 +295,14 @@ describe('BeachfrontAdapter', function () { sizes: [ width, height ] } }; - const requests = spec.buildRequests([ bidRequest ]); + const topLocation = parseUrl('http://www.example.com?foo=bar', { decodeSearchAsString: true }); + const bidderRequest = { + refererInfo: { + referer: topLocation.href + } + }; + const requests = spec.buildRequests([ bidRequest ], bidderRequest); const data = requests[0].data; - const topLocation = utils.getTopWindowLocation(); expect(data.slots).to.deep.equal([ { slot: bidRequest.adUnitCode, @@ -355,6 +387,16 @@ describe('BeachfrontAdapter', function () { expect(data.gdpr).to.equal(1); expect(data.gdprConsent).to.equal(consentString); }); + + it('must add the Trade Desk User ID to the request', () => { + const tdid = '4321'; + const bidRequest = bidRequests[0]; + bidRequest.mediaTypes = { banner: {} }; + bidRequest.userId = { tdid }; + const requests = spec.buildRequests([ bidRequest ]); + const data = requests[0].data; + expect(data.tdid).to.equal(tdid); + }); }); describe('for multi-format bids', function () { @@ -454,15 +496,16 @@ describe('BeachfrontAdapter', function () { const serverResponse = { bidPrice: 5.00, url: 'http://reachms.bfmio.com/getmu?aid=bid:19c4a196-fb21-4c81-9a1a-ecc5437a39da', - cmpId: '123abc' + crid: '123abc' }; const bidResponse = spec.interpretResponse({ body: serverResponse }, { bidRequest }); expect(bidResponse).to.deep.equal({ requestId: bidRequest.bidId, bidderCode: spec.code, cpm: serverResponse.bidPrice, - creativeId: serverResponse.cmpId, + creativeId: serverResponse.crid, vastUrl: serverResponse.url, + vastXml: undefined, width: width, height: height, renderer: null, @@ -473,6 +516,48 @@ describe('BeachfrontAdapter', function () { }); }); + it('should default to the legacy "cmpId" value for the creative ID', () => { + const width = 640; + const height = 480; + const bidRequest = bidRequests[0]; + bidRequest.mediaTypes = { + video: { + playerSize: [ width, height ] + } + }; + const serverResponse = { + bidPrice: 5.00, + url: 'http://reachms.bfmio.com/getmu?aid=bid:19c4a196-fb21-4c81-9a1a-ecc5437a39da', + cmpId: '123abc' + }; + const bidResponse = spec.interpretResponse({ body: serverResponse }, { bidRequest }); + expect(bidResponse).to.deep.contain({ + creativeId: serverResponse.cmpId + }); + }); + + it('should return vast xml if found on the bid response', () => { + const width = 640; + const height = 480; + const bidRequest = bidRequests[0]; + bidRequest.mediaTypes = { + video: { + playerSize: [ width, height ] + } + }; + const serverResponse = { + bidPrice: 5.00, + url: 'http://reachms.bfmio.com/getmu?aid=bid:19c4a196-fb21-4c81-9a1a-ecc5437a39da', + vast: '', + crid: '123abc' + }; + const bidResponse = spec.interpretResponse({ body: serverResponse }, { bidRequest }); + expect(bidResponse).to.deep.contain({ + vastUrl: serverResponse.url, + vastXml: serverResponse.vast + }); + }); + it('should return a renderer for outstream video bids', function () { const bidRequest = bidRequests[0]; bidRequest.mediaTypes = { @@ -483,7 +568,7 @@ describe('BeachfrontAdapter', function () { const serverResponse = { bidPrice: 5.00, url: 'http://reachms.bfmio.com/getmu?aid=bid:19c4a196-fb21-4c81-9a1a-ecc5437a39da', - cmpId: '123abc' + crid: '123abc' }; const bidResponse = spec.interpretResponse({ body: serverResponse }, { bidRequest }); expect(bidResponse.renderer).to.deep.contain({ @@ -491,6 +576,63 @@ describe('BeachfrontAdapter', function () { url: OUTSTREAM_SRC }); }); + + it('should initialize a player for outstream bids', () => { + const width = 640; + const height = 480; + const bidRequest = bidRequests[0]; + bidRequest.mediaTypes = { + video: { + context: 'outstream', + playerSize: [ width, height ] + } + }; + const serverResponse = { + bidPrice: 5.00, + url: 'http://reachms.bfmio.com/getmu?aid=bid:19c4a196-fb21-4c81-9a1a-ecc5437a39da', + crid: '123abc' + }; + const bidResponse = spec.interpretResponse({ body: serverResponse }, { bidRequest }); + window.Beachfront = { Player: sinon.spy() }; + bidResponse.adUnitCode = bidRequest.adUnitCode; + bidResponse.renderer.render(bidResponse); + sinon.assert.calledWith(window.Beachfront.Player, bidResponse.adUnitCode, sinon.match({ + adTagUrl: bidResponse.vastUrl, + width: bidResponse.width, + height: bidResponse.height, + expandInView: false, + collapseOnComplete: true + })); + delete window.Beachfront; + }); + + it('should configure outstream player settings from the bidder params', () => { + const width = 640; + const height = 480; + const bidRequest = bidRequests[0]; + bidRequest.mediaTypes = { + video: { + context: 'outstream', + playerSize: [ width, height ] + } + }; + bidRequest.params.player = { + expandInView: true, + collapseOnComplete: false, + progressColor: 'green' + }; + const serverResponse = { + bidPrice: 5.00, + url: 'http://reachms.bfmio.com/getmu?aid=bid:19c4a196-fb21-4c81-9a1a-ecc5437a39da', + crid: '123abc' + }; + const bidResponse = spec.interpretResponse({ body: serverResponse }, { bidRequest }); + window.Beachfront = { Player: sinon.spy() }; + bidResponse.adUnitCode = bidRequest.adUnitCode; + bidResponse.renderer.render(bidResponse); + sinon.assert.calledWith(window.Beachfront.Player, bidResponse.adUnitCode, sinon.match(bidRequest.params.player)); + delete window.Beachfront; + }); }); describe('for banner bids', function () { @@ -563,7 +705,7 @@ describe('BeachfrontAdapter', function () { bidResponse = { bidPrice: 5.00, url: 'http://reachms.bfmio.com/getmu?aid=bid:19c4a196-fb21-4c81-9a1a-ecc5437a39da', - cmpId: '123abc' + crid: '123abc' }; }); @@ -621,7 +763,7 @@ describe('BeachfrontAdapter', function () { }); it('should return user syncs defined the bid response', function () { - const syncUrl = 'http://sync.bfmio.com/sync_iframe?ifpl=5&ifg=1&id=test&gdpr=0&gc=&gce=0'; + const syncUrl = 'https://sync.bfmio.com/sync_iframe?ifpl=5&ifg=1&id=test&gdpr=0&gc=&gce=0'; const syncOptions = { iframeEnabled: true, pixelEnabled: true @@ -639,7 +781,7 @@ describe('BeachfrontAdapter', function () { }); it('should not return user syncs if iframes are disabled', function () { - const syncUrl = 'http://sync.bfmio.com/sync_iframe?ifpl=5&ifg=1&id=test&gdpr=0&gc=&gce=0'; + const syncUrl = 'https://sync.bfmio.com/sync_iframe?ifpl=5&ifg=1&id=test&gdpr=0&gc=&gce=0'; const syncOptions = { iframeEnabled: false, pixelEnabled: true diff --git a/test/spec/modules/betweenBidAdapter_spec.js b/test/spec/modules/betweenBidAdapter_spec.js index b1eea08a147..f2d770805c5 100644 --- a/test/spec/modules/betweenBidAdapter_spec.js +++ b/test/spec/modules/betweenBidAdapter_spec.js @@ -6,7 +6,6 @@ describe('betweenBidAdapterTests', function () { expect(spec.isBidRequestValid({ bidder: 'between', params: { - placementId: 'example', w: 240, h: 400, s: 1112 @@ -17,7 +16,7 @@ describe('betweenBidAdapterTests', function () { let bidRequestData = [{ bidId: 'bid1234', bidder: 'between', - params: {w: 240, h: 400, s: 1112, placementId: 'example'}, + params: {w: 240, h: 400, s: 1112}, sizes: [[240, 400]] }] let request = spec.buildRequests(bidRequestData); @@ -42,6 +41,28 @@ describe('betweenBidAdapterTests', function () { expect(bid.currency).to.equal('USD'); expect(bid.width).to.equal(240); expect(bid.height).to.equal(400); + expect(bid.netRevenue).to.equal(true); + expect(bid.requestId).to.equal('bid1234'); + expect(bid.ad).to.equal('Ad html'); + }); + it('validate_response_params', function () { + let serverResponse = { + body: [{ + bidid: 'bid1234', + w: 240, + h: 400, + currency: 'USD', + ad: 'Ad html' + }] + }; + let bids = spec.interpretResponse(serverResponse); + expect(bids).to.have.lengthOf(1); + let bid = bids[0]; + expect(bid.cpm).to.equal(0); + expect(bid.currency).to.equal('USD'); + expect(bid.width).to.equal(240); + expect(bid.height).to.equal(400); + expect(bid.netRevenue).to.equal(true); expect(bid.requestId).to.equal('bid1234'); expect(bid.ad).to.equal('Ad html'); }); diff --git a/test/spec/modules/bidfluenceBidAdapter_spec.js b/test/spec/modules/bidfluenceBidAdapter_spec.js new file mode 100644 index 00000000000..9ce6e808c6b --- /dev/null +++ b/test/spec/modules/bidfluenceBidAdapter_spec.js @@ -0,0 +1,114 @@ +import { expect } from 'chai'; +import { spec } from 'modules/bidfluenceBidAdapter'; + +const BIDDER_CODE = 'bidfluence'; +const PLACEMENT_ID = '1000'; +const PUB_ID = '1000'; +const CONSENT_STRING = 'DUXDSDFSFWRRR8345F=='; + +const validBidRequests = [{ + 'bidder': BIDDER_CODE, + 'params': { + 'placementId': PLACEMENT_ID, + 'publisherId': PUB_ID, + 'reservePrice': 0 + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250]], + 'bidId': '2b1f23307fb8ef', + 'bidderRequestId': '10edf38ec1a719', + 'auctionId': '1025ba77-5463-4877-b0eb-14b205cb9304' +}]; + +const bidderRequest = { + 'bidderCode': 'bidfluence', + 'auctionId': '1025ba77-5463-4877-b0eb-14b205cb9304', + 'bidderRequestId': '10edf38ec1a719', + 'refererInfo': { + 'numIframes': 0, + 'reachedTop': true, + 'referer': 'test', + 'stack': ['test'] + }, + 'timeout': 1000, + 'gdprConsent': { + 'gdprApplies': true, + 'consentString': CONSENT_STRING, + 'vendorData': '' + } +}; + +bidderRequest.bids = validBidRequests; + +describe('Bidfluence Adapter test', () => { + describe('isBidRequestValid', function () { + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(validBidRequests[0])).to.equal(true); + }); + it('should return the right bidder code', function () { + expect(spec.code).to.eql(BIDDER_CODE); + }); + }); + + describe('buildRequests', function () { + const request = spec.buildRequests(validBidRequests, bidderRequest); + + it('sends bid request to our endpoint via POST', function () { + expect(request.method).to.equal('POST'); + }); + + const payload = JSON.parse(request.data); + + expect(payload.bids[0].bid).to.equal(validBidRequests[0].bidId); + expect(payload.azr).to.equal(true); + expect(payload.ck).to.not.be.undefined; + expect(payload.bids[0].tid).to.equal(PLACEMENT_ID); + expect(payload.bids[0].pid).to.equal(PUB_ID); + expect(payload.bids[0].rp).to.be.a('number'); + expect(payload.re).to.not.be.undefined; + expect(payload.st).to.not.be.undefined; + expect(payload.tz).to.not.be.undefined; + expect(payload.sr).to.not.be.undefined; + expect(payload.vp).to.not.be.undefined; + expect(payload.sdt).to.not.be.undefined; + expect(payload.bids[0].w).to.equal('300'); + expect(payload.bids[0].h).to.equal('250'); + + it('sends gdpr info if exists', function () { + expect(payload.gdpr).to.equal(true); + expect(payload.gdprc).to.equal(CONSENT_STRING); + }); + }); + + describe('interpretResponse', function () { + const response = { + body: { + Bids: + [{ + 'CreativeId': '1000', + 'Cpm': 0.50, + 'Ad': '
    ', + 'Height': 250, + 'Width': 300 + }] + } + }; + + it('should get correct bid response', function () { + const expectedResponse = [{ + requestId: response.body.Bids[0].BidId, + cpm: response.body.Bids[0].Cpm, + width: response.body.Bids[0].Width, + height: response.body.Bids[0].Height, + creativeId: response.body.Bids[0].CreativeId, + ad: response.body.Bids[0].Ad, + currency: 'USD', + netRevenue: true, + ttl: 360 + }]; + + let result = spec.interpretResponse(response, { 'bidderRequest': validBidRequests[0] }); + expect(result).to.deep.equal(expectedResponse); + }); + }); +}); diff --git a/test/spec/modules/bidglassAdapter_spec.js b/test/spec/modules/bidglassAdapter_spec.js new file mode 100644 index 00000000000..00a47fc997a --- /dev/null +++ b/test/spec/modules/bidglassAdapter_spec.js @@ -0,0 +1,103 @@ +import { expect } from 'chai'; +import { spec } from 'modules/bidglassBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; + +describe('Bid Glass Adapter', function () { + const adapter = newBidder(spec); + + describe('isBidRequestValid', function () { + let bid = { + 'bidder': 'bidglass', + 'params': { + 'adUnitId': '3' + }, + 'adUnitCode': 'bidglass-testunit', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not passed', function () { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + const bidRequests = [{ + 'bidder': 'bidglass', + 'params': { + 'adUnitId': '3' + }, + 'adUnitCode': 'bidglass-testunit', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }]; + + const request = spec.buildRequests(bidRequests); + + it('sends bid request to our endpoint via POST', function () { + expect(request.method).to.equal('POST'); + }); + + it('sets withCredentials to false', function () { + expect(request.options.withCredentials).to.equal(false); + }); + }); + + describe('interpretResponse', function () { + let response; + beforeEach(function () { + response = { + body: { + 'bidResponses': [{ + 'ad': '', + 'cpm': '0.01', + 'creativeId': '-1', + 'width': '300', + 'height': '250', + 'requestId': '30b31c1838de1e' + }] + } + }; + }); + + it('should get the correct bid response', function () { + let expectedResponse = [{ + 'requestId': '30b31c1838de1e', + 'cpm': 0.01, + 'width': 300, + 'height': 250, + 'creativeId': '-1', + 'dealId': null, + 'currency': 'USD', + 'mediaType': 'banner', + 'netRevenue': true, + 'ttl': 10, + 'ad': '' + }]; + + let result = spec.interpretResponse(response); + expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedResponse[0])); + }); + + it('handles empty bid response', function () { + let response = { + body: { + 'bidResponses': [] + } + }; + let result = spec.interpretResponse(response); + expect(result.length).to.equal(0); + }); + }); +}); diff --git a/test/spec/modules/bidphysicsBidAdapter_spec.js b/test/spec/modules/bidphysicsBidAdapter_spec.js new file mode 100644 index 00000000000..ba93642ad81 --- /dev/null +++ b/test/spec/modules/bidphysicsBidAdapter_spec.js @@ -0,0 +1,261 @@ +import {expect} from 'chai'; +import {spec} from 'modules/bidphysicsBidAdapter'; + +const REQUEST = { + 'bidderCode': 'bidphysics', + 'auctionId': 'auctionId-56a2-4f71-9098-720a68f2f708', + 'bidderRequestId': 'requestId', + 'bidRequest': [{ + 'bidder': 'bidphysics', + 'params': { + 'unitId': 123456, + }, + 'placementCode': 'div-gpt-dummy-placement-code', + 'sizes': [ + [300, 250] + ], + 'bidId': 'bidId1', + 'bidderRequestId': 'bidderRequestId', + 'auctionId': 'auctionId-56a2-4f71-9098-720a68f2f708' + }, + { + 'bidder': 'bidphysics', + 'params': { + 'unitId': 123456, + }, + 'placementCode': 'div-gpt-dummy-placement-code', + 'sizes': [ + [300, 250] + ], + 'bidId': 'bidId2', + 'bidderRequestId': 'bidderRequestId', + 'auctionId': 'auctionId-56a2-4f71-9098-720a68f2f708' + }], + 'start': 1487883186070, + 'auctionStart': 1487883186069, + 'timeout': 3000 +}; + +const RESPONSE = { + 'headers': null, + 'body': { + 'id': 'responseId', + 'seatbid': [ + { + 'bid': [ + { + 'id': 'bidId1', + 'impid': 'bidId1', + 'price': 0.18, + 'adm': '', + 'adid': '144762342', + 'adomain': [ + 'http://dummydomain.com' + ], + 'iurl': 'iurl', + 'cid': '109', + 'crid': 'creativeId', + 'cat': [], + 'w': 300, + 'h': 250, + 'ext': { + 'prebid': { + 'type': 'banner' + }, + 'bidder': { + 'appnexus': { + 'brand_id': 334553, + 'auction_id': 514667951122925701, + 'bidder_id': 2, + 'bid_ad_type': 0 + } + } + } + }, + { + 'id': 'bidId2', + 'impid': 'bidId2', + 'price': 0.1, + 'adm': '', + 'adid': '144762342', + 'adomain': [ + 'http://dummydomain.com' + ], + 'iurl': 'iurl', + 'cid': '109', + 'crid': 'creativeId', + 'cat': [], + 'w': 300, + 'h': 250, + 'ext': { + 'prebid': { + 'type': 'banner' + }, + 'bidder': { + 'appnexus': { + 'brand_id': 386046, + 'auction_id': 517067951122925501, + 'bidder_id': 2, + 'bid_ad_type': 0 + } + } + } + } + ], + 'seat': 'bidphysics' + } + ], + 'ext': { + 'usersync': { + 'sovrn': { + 'status': 'none', + 'syncs': [ + { + 'url': 'urlsovrn', + 'type': 'iframe' + } + ] + }, + 'appnexus': { + 'status': 'none', + 'syncs': [ + { + 'url': 'urlappnexus', + 'type': 'pixel' + } + ] + } + }, + 'responsetimemillis': { + 'appnexus': 127 + } + } + } +}; + +describe('BidPhysics bid adapter', function () { + describe('isBidRequestValid', function () { + it('should accept request if only unitId is passed', function () { + let bid = { + bidder: 'bidphysics', + params: { + unitId: 'unitId', + } + }; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + it('should accept request if only networkId is passed', function () { + let bid = { + bidder: 'bidphysics', + params: { + networkId: 'networkId', + } + }; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + it('should accept request if only publisherId is passed', function () { + let bid = { + bidder: 'bidphysics', + params: { + publisherId: 'publisherId', + } + }; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('reject requests without params', function () { + let bid = { + bidder: 'bidphysics', + params: {} + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + it('creates request data', function () { + let request = spec.buildRequests(REQUEST.bidRequest, REQUEST); + + expect(request).to.exist.and.to.be.a('object'); + const payload = JSON.parse(request.data); + expect(payload.imp[0]).to.have.property('id', REQUEST.bidRequest[0].bidId); + expect(payload.imp[1]).to.have.property('id', REQUEST.bidRequest[1].bidId); + }); + + it('has gdpr data if applicable', function () { + const req = Object.assign({}, REQUEST, { + gdprConsent: { + consentString: 'consentString', + gdprApplies: true, + } + }); + let request = spec.buildRequests(REQUEST.bidRequest, req); + + const payload = JSON.parse(request.data); + expect(payload.user.ext).to.have.property('consent', req.gdprConsent.consentString); + expect(payload.regs.ext).to.have.property('gdpr', 1); + }); + }); + + describe('interpretResponse', function () { + it('have bids', function () { + let bids = spec.interpretResponse(RESPONSE, REQUEST); + expect(bids).to.be.an('array').that.is.not.empty; + validateBidOnIndex(0); + validateBidOnIndex(1); + + function validateBidOnIndex(index) { + expect(bids[index]).to.have.property('currency', 'USD'); + expect(bids[index]).to.have.property('requestId', RESPONSE.body.seatbid[0].bid[index].impid); + expect(bids[index]).to.have.property('cpm', RESPONSE.body.seatbid[0].bid[index].price); + expect(bids[index]).to.have.property('width', RESPONSE.body.seatbid[0].bid[index].w); + expect(bids[index]).to.have.property('height', RESPONSE.body.seatbid[0].bid[index].h); + expect(bids[index]).to.have.property('ad', RESPONSE.body.seatbid[0].bid[index].adm); + expect(bids[index]).to.have.property('creativeId', RESPONSE.body.seatbid[0].bid[index].crid); + expect(bids[index]).to.have.property('ttl', 30); + expect(bids[index]).to.have.property('netRevenue', true); + } + }); + + it('handles empty response', function () { + const EMPTY_RESP = Object.assign({}, RESPONSE, {'body': {}}); + const bids = spec.interpretResponse(EMPTY_RESP, REQUEST); + + expect(bids).to.be.empty; + }); + }); + + describe('getUserSyncs', function () { + it('handles no parameters', function () { + let opts = spec.getUserSyncs({}); + expect(opts).to.be.an('array').that.is.empty; + }); + it('returns non if sync is not allowed', function () { + let opts = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: false}); + + expect(opts).to.be.an('array').that.is.empty; + }); + + it('iframe sync enabled should return results', function () { + let opts = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: false}, [RESPONSE]); + + expect(opts.length).to.equal(1); + expect(opts[0].type).to.equal('iframe'); + expect(opts[0].url).to.equal(RESPONSE.body.ext.usersync['sovrn'].syncs[0].url); + }); + + it('pixel sync enabled should return results', function () { + let opts = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: true}, [RESPONSE]); + + expect(opts.length).to.equal(1); + expect(opts[0].type).to.equal('image'); + expect(opts[0].url).to.equal(RESPONSE.body.ext.usersync['appnexus'].syncs[0].url); + }); + + it('all sync enabled should return all results', function () { + let opts = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, [RESPONSE]); + + expect(opts.length).to.equal(2); + }); + }); +}); diff --git a/test/spec/modules/brightcomBidAdapter_spec.js b/test/spec/modules/brightcomBidAdapter_spec.js new file mode 100644 index 00000000000..73186250f5b --- /dev/null +++ b/test/spec/modules/brightcomBidAdapter_spec.js @@ -0,0 +1,283 @@ +import { expect } from 'chai'; +import * as utils from 'src/utils'; +import { spec } from 'modules/brightcomBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; + +const URL = 'https://brightcombid.marphezis.com/hb'; + +describe('brightcomBidAdapter', function() { + const adapter = newBidder(spec); + let element, win; + let bidRequests; + let sandbox; + + beforeEach(function() { + element = { + x: 0, + y: 0, + + width: 0, + height: 0, + + getBoundingClientRect: () => { + return { + width: element.width, + height: element.height, + + left: element.x, + top: element.y, + right: element.x + element.width, + bottom: element.y + element.height + }; + } + }; + win = { + document: { + visibilityState: 'visible' + }, + + innerWidth: 800, + innerHeight: 600 + }; + bidRequests = [{ + 'bidder': 'brightcom', + 'params': { + 'publisherId': 1234567 + }, + 'adUnitCode': 'adunit-code', + 'sizes': [ + [300, 250], + [300, 600] + ], + 'bidId': '5fb26ac22bde4', + 'bidderRequestId': '4bf93aeb730cb9', + 'auctionId': 'ffe9a1f7-7b67-4bda-a8e0-9ee5dc9f442e' + }]; + + sandbox = sinon.sandbox.create(); + sandbox.stub(document, 'getElementById').withArgs('adunit-code').returns(element); + sandbox.stub(utils, 'getWindowTop').returns(win); + sandbox.stub(utils, 'getWindowSelf').returns(win); + }); + + afterEach(function() { + sandbox.restore(); + }); + + describe('isBidRequestValid', function () { + let bid = { + 'bidder': 'brightcom', + 'params': { + 'publisherId': 1234567 + }, + 'adUnitCode': 'adunit-code', + 'sizes': [ + [300, 250], + [300, 600] + ], + 'bidId': '5fb26ac22bde4', + 'bidderRequestId': '4bf93aeb730cb9', + 'auctionId': 'ffe9a1f7-7b67-4bda-a8e0-9ee5dc9f442e', + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when tagid not passed correctly', function () { + bid.params.publisherId = undefined; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when require params are not passed', function () { + let bid = Object.assign({}, bid); + bid.params = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + it('sends bid request to our endpoint via POST', function () { + const request = spec.buildRequests(bidRequests); + expect(request.method).to.equal('POST'); + }); + + it('request url should match our endpoint url', function () { + const request = spec.buildRequests(bidRequests); + expect(request.url).to.equal(URL); + }); + + it('sets the proper banner object', function() { + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.format).to.deep.equal([{w: 300, h: 250}, {w: 300, h: 600}]); + }); + + it('accepts a single array as a size', function() { + bidRequests[0].sizes = [300, 250]; + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.format).to.deep.equal([{w: 300, h: 250}]); + }); + + it('sends bidfloor param if present', function () { + bidRequests[0].params.bidFloor = 0.05; + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].bidfloor).to.equal(0.05); + }); + + it('sends tagid', function () { + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].tagid).to.equal('adunit-code'); + }); + + it('sends publisher id', function () { + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.site.publisher.id).to.equal(1234567); + }); + + context('when element is fully in view', function() { + it('returns 100', function() { + Object.assign(element, { width: 600, height: 400 }); + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.ext.viewability).to.equal(100); + }); + }); + + context('when element is out of view', function() { + it('returns 0', function() { + Object.assign(element, { x: -300, y: 0, width: 207, height: 320 }); + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.ext.viewability).to.equal(0); + }); + }); + + context('when element is partially in view', function() { + it('returns percentage', function() { + Object.assign(element, { width: 800, height: 800 }); + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.ext.viewability).to.equal(75); + }); + }); + + context('when width or height of the element is zero', function() { + it('try to use alternative values', function() { + Object.assign(element, { width: 0, height: 0 }); + bidRequests[0].sizes = [[800, 2400]]; + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.ext.viewability).to.equal(25); + }); + }); + + context('when nested iframes', function() { + it('returns \'na\'', function() { + Object.assign(element, { width: 600, height: 400 }); + + utils.getWindowTop.restore(); + utils.getWindowSelf.restore(); + sandbox.stub(utils, 'getWindowTop').returns(win); + sandbox.stub(utils, 'getWindowSelf').returns({}); + + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.ext.viewability).to.equal('na'); + }); + }); + + context('when tab is inactive', function() { + it('returns 0', function() { + Object.assign(element, { width: 600, height: 400 }); + + utils.getWindowTop.restore(); + win.document.visibilityState = 'hidden'; + sandbox.stub(utils, 'getWindowTop').returns(win); + + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.ext.viewability).to.equal(0); + }); + }); + }); + + describe('interpretResponse', function () { + let response; + beforeEach(function () { + response = { + body: { + 'id': '37386aade21a71', + 'seatbid': [{ + 'bid': [{ + 'id': '376874781', + 'impid': '283a9f4cd2415d', + 'price': 0.35743275, + 'nurl': '', + 'adm': '', + 'w': 300, + 'h': 250 + }] + }] + } + }; + }); + + it('should get the correct bid response', function () { + let expectedResponse = [{ + 'requestId': '283a9f4cd2415d', + 'cpm': 0.35743275, + 'width': 300, + 'height': 250, + 'creativeId': '376874781', + 'currency': 'USD', + 'netRevenue': true, + 'mediaType': 'banner', + 'ad': `
    `, + 'ttl': 60 + }]; + + let result = spec.interpretResponse(response); + expect(result[0]).to.deep.equal(expectedResponse[0]); + }); + + it('crid should default to the bid id if not on the response', function () { + let expectedResponse = [{ + 'requestId': '283a9f4cd2415d', + 'cpm': 0.35743275, + 'width': 300, + 'height': 250, + 'creativeId': response.body.seatbid[0].bid[0].id, + 'currency': 'USD', + 'netRevenue': true, + 'mediaType': 'banner', + 'ad': `
    `, + 'ttl': 60 + }]; + + let result = spec.interpretResponse(response); + expect(result[0]).to.deep.equal(expectedResponse[0]); + }); + + it('handles empty bid response', function () { + let response = { + body: '' + }; + let result = spec.interpretResponse(response); + expect(result.length).to.equal(0); + }); + }); + + describe('getUserSyncs ', () => { + let syncOptions = {iframeEnabled: true, pixelEnabled: true}; + + it('should not return', () => { + let returnStatement = spec.getUserSyncs(syncOptions, []); + expect(returnStatement).to.be.empty; + }); + }); +}); diff --git a/test/spec/modules/bucksenseBidAdapter_spec.js b/test/spec/modules/bucksenseBidAdapter_spec.js new file mode 100644 index 00000000000..17b5c3ceff5 --- /dev/null +++ b/test/spec/modules/bucksenseBidAdapter_spec.js @@ -0,0 +1,147 @@ +import {expect} from 'chai'; +import {spec} from 'modules/bucksenseBidAdapter'; + +describe('Bucksense Adapter', function() { + const BIDDER_CODE = 'bucksense'; + const BID_ID = '12345'; + const PLACE_ID = '1000'; + + function getValidBidObject() { + return { + bidder: BIDDER_CODE, + params: { + placementId: PLACE_ID, + } + } + }; + + describe('isBidRequestValid', function() { + var bid; + + beforeEach(function() { + bid = getValidBidObject(); + }); + + it('returns true when valid bid request is sent', function() { + expect(spec.isBidRequestValid(bid)).to.be.true; + }); + + it('returns true when valid test bid request is sent', function() { + bid.params['test'] = 1; + expect(spec.isBidRequestValid(bid)).to.be.true; + }); + + it('returns false when bidder not set to "bucksense"', function() { + bid.bidder = 'dummy'; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + + it('returns false when params not set', function() { + delete bid.params; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + }); + + describe('buildRequests', function() { + var bid, bidRequestObj; + + beforeEach(function() { + bid = getValidBidObject(); + bidRequestObj = { + 'bidderCode': 'bucksense', + 'auctionId': '73540558-86cb-4eef-895f-bf99c5353bd7', + 'bidderRequestId': '1feebcb5938c7e', + 'bids': [ + { + 'bidder': 'bucksense', + 'params': { + 'placementId': 1000 + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ [ 300, 250 ], [ 300, 600 ] ] + } + }, + 'adUnitCode': 'test-div', + 'transactionId': '52b3ed07-2a09-4f58-a426-75acc7602c96', + 'sizes': [ [ 300, 250 ], [ 300, 600 ] ], + 'bidId': '22aecdacdcd773', + 'bidderRequestId': '1feebcb5938c7e', + 'auctionId': '73540558-86cb-4eef-895f-bf99c5353bd7', + 'src': 'client', + 'bidRequestsCount': 1 + } + ], + 'auctionStart': 1557176022728, + 'timeout': 1000, + 'refererInfo': { + 'referer': 'http://stefanod.hera.pe/prebid/?pbjs_debug=true', + 'reachedTop': true, + 'numIframes': 0, + 'stack': [ + 'http://stefanod.hera.pe/prebid/?pbjs_debug=true' + ] + }, + 'start': 1557176022731 + }; + }); + + it('should build a very basic request', function() { + var request = spec.buildRequests([bid], bidRequestObj); + expect(request[0].method).to.equal('POST'); + }); + + it('bidRequest data', function () { + var request = spec.buildRequests([bid], bidRequestObj); + expect(request[0].data).to.exist; + }); + }); + + describe('interpretResponse', function() { + var serverResponse; + var serverRequest; + + beforeEach(function() { + serverRequest = { + 'method': 'POST', + 'url': 'https://prebid.bksn.se:445/prebidjs/', + 'data': { + 'pub_id': 'prebid.org', + 'pl_id': '1000', + 'secure': 0, + 'href': 'http://prebid.org/developers.html', + 'bid_id': '27aaf8e96d9fd5', + 'params': { + 'placementId': '1000' + }, + 'sizes': [ [ 300, 250 ], [ 300, 600 ] ] + } + }; + + serverResponse = { + body: { + 'requestId': '', + 'cpm': 0.3, + 'width': 300, + 'height': 250, + 'ttl': 360, + 'creativeId': 'creative002', + 'currency': 'USD', + 'netRevenue': false, + 'ad': '
    ' + } + }; + }); + + it('should return an array of bid responses', function() { + var responses = spec.interpretResponse(serverResponse, serverRequest); + expect(responses).to.be.an('array').with.length(1); + }); + + it('should return an array of bid responses', function() { + serverResponse = {}; + var responses = spec.interpretResponse(serverResponse, serverRequest); + expect(responses).to.be.an('array').with.length(0); + }); + }); +}); diff --git a/test/spec/modules/buzzoolaBidAdapter_spec.js b/test/spec/modules/buzzoolaBidAdapter_spec.js new file mode 100644 index 00000000000..e6f22d1da20 --- /dev/null +++ b/test/spec/modules/buzzoolaBidAdapter_spec.js @@ -0,0 +1,279 @@ +import {expect} from 'chai'; +import {spec} from 'modules/buzzoolaBidAdapter'; +import {newBidder} from 'src/adapters/bidderFactory'; +import {executeRenderer, Renderer} from '../../../src/Renderer'; +import {deepClone} from '../../../src/utils'; + +const ENDPOINT = 'https://exchange.buzzoola.com/ssp/prebidjs'; +const RENDERER_SRC = 'https://tube.buzzoola.com/new/build/buzzlibrary.js'; + +const INVALID_BIDS = [{ + 'bidder': 'buzzoola', + 'mediaTypes': {'banner': {'sizes': [[240, 400], [300, 600]]}}, + 'sizes': [[240, 400], [300, 600]] +}, { + 'bidder': 'buzzoola', + 'params': {'placementId': 417846}, + 'sizes': [[240, 400], [300, 600]] +}, { + 'bidder': 'buzzoola', + 'mediaTypes': { + 'video': { + 'playerSize': [[640, 380]], + 'mimes': ['video/mp4'], + 'minduration': 1, + 'maxduration': 2 + } + } +}, { + 'bidder': 'buzzoola', + 'params': {'placementId': 417845} +}]; + +const BANNER_BID = { + 'bidder': 'buzzoola', + 'params': {'placementId': 417846}, + 'mediaTypes': {'banner': {'sizes': [[240, 400], [300, 600]]}}, + 'sizes': [[240, 400], [300, 600]], + 'bidId': '2a11641ada3c6a' +}; + +const BANNER_BID_REQUEST = { + bidderCode: 'buzzoola', + bids: [BANNER_BID] +}; + +const BANNER_RESPONSE = [{ + 'requestId': '2a11641ada3c6a', + 'cpm': 5.583115, + 'width': 240, + 'height': 400, + 'creativeId': '11773', + 'dealId': '', + 'currency': 'RUB', + 'netRevenue': true, + 'ttl': 10800, + 'ad': '
    ', + 'mediaType': 'banner' +}]; + +const REQUIRED_BANNER_FIELDS = [ + 'requestId', + 'cpm', + 'width', + 'height', + 'ad', + 'ttl', + 'creativeId', + 'netRevenue', + 'currency', + 'mediaType' +]; + +const VIDEO_BID = { + 'bidder': 'buzzoola', + 'params': {'placementId': 417845}, + 'mediaTypes': { + 'video': { + 'context': 'instream', + 'playerSize': [[640, 380]], + 'mimes': ['video/mp4'], + 'minduration': 1, + 'maxduration': 2 + } + }, + 'bidId': '325a54271dc40a' +}; + +const VIDEO_BID_REQUEST = { + bidderCode: 'buzzoola', + bids: [VIDEO_BID] +}; + +const VIDEO_RESPONSE = [{ + 'requestId': '325a54271dc40a', + 'cpm': 4.6158956756756755, + 'width': 640, + 'height': 380, + 'creativeId': '11774', + 'dealId': '', + 'currency': 'RUB', + 'netRevenue': true, + 'ttl': 10800, + 'ad': '{"crs":[{"advertiser_id":165,"title":"qa//PrebidJStestVideoURL","description":"qa//PrebidJStest","duration":0,"ya_id":"55038886","raw_content":"{\\"main_content\\": \\"https://tube.buzzoola.com/xstatic/o42/mcaug/2.mp4\\"}","content":{"main_content":"https://tube.buzzoola.com/xstatic/o42/mcaug/2.mp4"},"content_type":"video_url","sponsor_link":"","sponsor_name":"","overlay":"","overlay_start_after":0,"overlay_close_after":0,"action_button_title":"","tracking_url":{},"iframe_domains":[],"soc_share_url":"https://tube.buzzoola.com/share.html","player_show_skip_button_before_play":false,"player_show_skip_button_seconds":5,"player_show_title":true,"player_data_attributes":{"expandable":"default","overroll":"default"},"click_event_view":"default","share_panel_position":"left","auto_play":true,"logo_url":{},"share_buttons":["vkontakte","facebook","twitter","moimir","odnoklassniki","embed"],"player_show_panels":false,"thumbnail":"","tracking_js":{},"click_event_url":"https://exchange.buzzoola.com/event/f9382ceb-49c2-4683-50d8-5c516c53cd69/14795a96-6261-49dc-7241-207333ab1490/m7JVQI9Y7J35_gEDugNO2bIiP2qTqPKfuLrqqh_LoJu0tD6PoLEglMXUBzVpSg75c-unsaijXpIERGosa1adogXgqjDml4Pm/click/0/","vpaid_js_url":"https://tube.buzzoola.com/new/js/lib/vpaid_js_proxy.js","skip_clickthru":false,"landing_link_text":"","sound_enabled_by_default":false,"landing_link_position":"right","displayed_price":"","js_wrapper_url":"","enable_moat":false,"branding_template":"","event_url":"https://exchange.buzzoola.com/event/f9382ceb-49c2-4683-50d8-5c516c53cd69/14795a96-6261-49dc-7241-207333ab1490/m7JVQI9Y7J35_gEDugNO2bIiP2qTqPKfuLrqqh_LoJu0tD6PoLEglMXUBzVpSg75c-unsaijXpIERGosa1adogXgqjDml4Pm/","resend_event_url":"https://exchange.buzzoola.com/resend_event/f9382ceb-49c2-4683-50d8-5c516c53cd69/14795a96-6261-49dc-7241-207333ab1490/m7JVQI9Y7J35_gEDugNO2bIiP2qTqPKfuLrqqh_LoJu0tD6PoLEglMXUBzVpSg75c-unsaijXpIERGosa1adogXgqjDml4Pm/","creative_hash":"m7JVQI9Y7J35_gEDugNO2bIiP2qTqPKfuLrqqh_LoJu0tD6PoLEglMXUBzVpSg75c-unsaijXpIERGosa1adogXgqjDml4Pm","custom_html":"","custom_js":"","height":0,"width":0,"campaign_id":5758,"line_item_id":17319,"creative_id":11774,"extra":{"imp_id":"14795a96-6261-49dc-7241-207333ab1490","rtime":"2019-08-27 13:58:36"},"subcontent":"vast","auction_settings":{"price":"4.6158956756756755","currency":"RUB","event_name":"player_seen","time_slice":0},"hash_to_embed":"kbDH64c7yFYkSu0KCwSkoUD2bNHAnUTHBERqLGtWnaIF4Kow5peD5g","need_ad":false}],"tracking_urls":{"ctor":["https://www.tns-counter.ru/V13a****buzzola_com/ru/CP1251/tmsec=buzzola_total/1322650417245790778","https://www.tns-counter.ru/V13a****buzzoola_kz/ru/UTF-8/tmsec=buzzoola_video/5395765100939533275","https://buzzoolaru.solution.weborama.fr/fcgi-bin/dispatch.fcgi?a.A=ev&a.si=3071&a.te=37&a.aap=1&a.agi=862&a.evn=PrebidJS.test&g.ra=4581478478720298652","https://x01.aidata.io/0.gif?pid=BUZZOOLA&id=dbdb5b13-e719-4987-7f6a-a882322bbfce","https://top-fwz1.mail.ru/counter?id=3026769","https://www.tns-counter.ru/V13a****buzzola_com/ru/UTF-8/tmsec=buzzola_inread/542059452789128996","https://dm.hybrid.ai/match?id=111&vid=dbdb5b13-e719-4987-7f6a-a882322bbfce","https://px.adhigh.net/p/cm/buzzoola?u=dbdb5b13-e719-4987-7f6a-a882322bbfce","https://ssp1.rtb.beeline.ru/userbind?src=buz&ssp_user_id=dbdb5b13-e719-4987-7f6a-a882322bbfce","https://sync.upravel.com/image?source=buzzoola&id=dbdb5b13-e719-4987-7f6a-a882322bbfce","https://relap.io/api/partners/bzcs.gif?uid=dbdb5b13-e719-4987-7f6a-a882322bbfce","https://x.bidswitch.net/sync?ssp=sspicyads","https://inv-nets.admixer.net/adxcm.aspx?ssp=3C5173FC-CA30-4692-9116-009C19CB1BF9&rurl=%2F%2Fexchange.buzzoola.com%2Fcookiesync%2Fdsp%2Fadmixer-video%2F%24%24visitor_cookie%24%24","https://sync.datamind.ru/cookie/accepter?source=buzzoola&id=dbdb5b13-e719-4987-7f6a-a882322bbfce","https://dmp.vihub.ru/match?sysid=buz&redir=no&uid=dbdb5b13-e719-4987-7f6a-a882322bbfce","https://ad.adriver.ru/cgi-bin/rle.cgi?sid=1&ad=608223&bt=21&pid=2551979&bid=6150299&bn=6150299&rnd=1279444531737367663","https://reichelcormier.bid/point/?method=match&type=ssp&key=4677290772f9000878093d69c199bfba&id=3509&extUid=dbdb5b13-e719-4987-7f6a-a882322bbfce","https://sync.republer.com/match?src=buzzoola&id=dbdb5b13-e719-4987-7f6a-a882322bbfce","https://sm.rtb.mts.ru/p?id=dbdb5b13-e719-4987-7f6a-a882322bbfce&ssp=buzzoola","https://cm.mgid.com/m?cdsp=371151&adu=https%3A%2F%2Fexchange.buzzoola.com%2Fcookiesync%2Fdsp%2Fmarketgid-native%2F%7Bmuidn%7D","https://dmp.gotechnology.io/dmp/syncsspdmp?sspid=122258"]},"tracking_js":{"ctor":["https://buzzoola.fraudscore.mobi/dooJ9sheeeDaZ3fe.js?s=268671&l=417845"]},"placement":{"placement_id":417845,"unit_type":"inread","unit_settings":{"align":"left","autoplay_enable_sound":false,"creatives_amount":1,"debug_mode":false,"expandable":"never","sound_control":"default","target":"","width":"100%"},"unit_settings_list":["width","sound_control","debug_mode","target","creatives_amount","expandable","container_height","align","height"]},"uuid":"dbdb5b13-e719-4987-7f6a-a882322bbfce","auction_id":"f9382ceb-49c2-4683-50d8-5c516c53cd69","env":"prod"}', + 'vastXml': '\n00:00:30', + 'mediaType': 'video' +}]; + +const RENDERER_DATA = { + data: JSON.parse(VIDEO_RESPONSE[0].ad) +}; +RENDERER_DATA.data.placement.unit_settings.width = '' + VIDEO_RESPONSE[0].width; +RENDERER_DATA.data.placement.unit_settings.height = RENDERER_DATA.data.placement.unit_settings.container_height = '' + VIDEO_RESPONSE[0].height; + +const REQUIRED_VIDEO_FIELDS = [ + 'requestId', + 'cpm', + 'width', + 'height', + 'ad', + 'ttl', + 'creativeId', + 'netRevenue', + 'currency', + 'vastXml', + 'mediaType' +]; + +describe('buzzoolaBidAdapter', () => { + const adapter = newBidder(spec); + + describe('inherited functions', () => { + it('exists and is a function', () => { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', () => { + it('should return true when required params found', () => { + expect(spec.isBidRequestValid(VIDEO_BID)).to.be.true; + }); + + it('should return false when required params are not passed', () => { + INVALID_BIDS.forEach(bid => { + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + }); + }); + + describe('buildRequests', () => { + let videoBidRequests = [VIDEO_BID]; + let bannerBidRequests = [BANNER_BID]; + + const bannerRequest = spec.buildRequests(bannerBidRequests, BANNER_BID_REQUEST); + const videoRequest = spec.buildRequests(videoBidRequests, VIDEO_BID_REQUEST); + + it('sends bid request to ENDPOINT via POST', () => { + expect(videoRequest.method).to.equal('POST'); + expect(bannerRequest.method).to.equal('POST'); + }); + + it('sends bid request to correct ENDPOINT', () => { + expect(videoRequest.url).to.equal(ENDPOINT); + expect(bannerRequest.url).to.equal(ENDPOINT); + }); + + it('sends correct video bid parameters', () => { + expect(videoRequest.data).to.deep.equal(VIDEO_BID_REQUEST); + }); + + it('sends correct banner bid parameters', () => { + expect(bannerRequest.data).to.deep.equal(BANNER_BID_REQUEST); + }); + }); + + describe('interpretResponse', () => { + const noBidServerResponse = []; + const emptyResponse = ''; + + function nobidServerResponseCheck(request, response = noBidServerResponse) { + const noBidResult = spec.interpretResponse({body: response}, {data: request}); + + expect(noBidResult.length).to.equal(0); + } + + function bidServerResponseCheck(response, request, fields) { + const result = spec.interpretResponse({body: response}, {data: request}); + + expect(result).to.deep.equal(response); + result.forEach(bid => { + fields.forEach(field => { + expect(bid).to.have.own.property(field); + }) + }); + } + + it('handles video nobid responses', () => { + nobidServerResponseCheck(VIDEO_BID_REQUEST); + }); + + it('handles banner nobid responses', () => { + nobidServerResponseCheck(BANNER_BID_REQUEST); + }); + + it('handles video empty responses', () => { + nobidServerResponseCheck(VIDEO_BID_REQUEST, emptyResponse); + }); + + it('handles banner empty responses', () => { + nobidServerResponseCheck(BANNER_BID_REQUEST, emptyResponse); + }); + + it('should get correct video bid response', () => { + bidServerResponseCheck(VIDEO_RESPONSE, VIDEO_BID_REQUEST, REQUIRED_VIDEO_FIELDS); + }); + + it('should get correct banner bid response', () => { + bidServerResponseCheck(BANNER_RESPONSE, BANNER_BID_REQUEST, REQUIRED_BANNER_FIELDS); + }); + }); + + describe('outstream renderer', () => { + let result; + let renderer; + + before(() => { + const adContainer = document.createElement('div'); + adContainer.id = 'adUnitCode'; + document.body.appendChild(adContainer); + + const outstreamVideoBid = deepClone(VIDEO_BID); + outstreamVideoBid.mediaTypes.video.context = 'outstream'; + + const outstreamVideoRequest = deepClone(VIDEO_BID_REQUEST); + outstreamVideoRequest.bids = [outstreamVideoBid]; + + const scriptElement = document.createElement('div'); + + const scriptStub = sinon.stub(document, 'createElement'); + scriptStub.withArgs('script').returns(scriptElement); + + result = spec.interpretResponse({body: VIDEO_RESPONSE}, {data: outstreamVideoRequest})[0]; + renderer = result.renderer; + + result.adUnitCode = 'adUnitCode'; + + scriptElement.onload && scriptElement.onload(); + + scriptStub.restore(); + }); + + it('should add renderer for outstream video', () => { + expect(result).to.have.own.property('renderer'); + }); + + it('should be instance of Renderer', () => { + expect(renderer).to.be.instanceof(Renderer); + }); + + it('should have valid src', () => { + expect(renderer.url).to.equal(RENDERER_SRC); + }); + + it('should create player instance', () => { + window.Buzzoola = { + Core: { + install: () => {} + } + }; + const spy = sinon.spy(window.Buzzoola.Core, 'install'); + executeRenderer(renderer, result); + expect(spy.called).to.be.true; + + const spyCall = spy.getCall(0); + + expect(spyCall.args[0]).to.be.instanceof(Element); + expect(spyCall.args[1]).to.deep.equal(RENDERER_DATA); + }); + }); +}); diff --git a/test/spec/modules/categoryTranslation_spec.js b/test/spec/modules/categoryTranslation_spec.js new file mode 100644 index 00000000000..17cc07269b0 --- /dev/null +++ b/test/spec/modules/categoryTranslation_spec.js @@ -0,0 +1,98 @@ +import { getAdserverCategoryHook, initTranslation } from 'modules/categoryTranslation'; +import { config } from 'src/config'; +import * as utils from 'src/utils'; +import { expect } from 'chai'; + +describe('category translation', function () { + let fakeTranslationServer; + let getLocalStorageStub; + + beforeEach(function () { + fakeTranslationServer = sinon.fakeServer.create(); + getLocalStorageStub = sinon.stub(utils, 'getDataFromLocalStorage'); + }); + + afterEach(function() { + fakeTranslationServer.reset(); + getLocalStorageStub.restore(); + config.resetConfig(); + }); + + it('should translate iab category to adserver category', function () { + config.setConfig({ + 'adpod': { + 'brandCategoryExclusion': true + } + }); + getLocalStorageStub.returns(JSON.stringify({ + 'mapping': { + 'iab-1': { + 'id': 1, + 'name': 'sample' + } + } + })); + let bid = { + meta: { + iabSubCatId: 'iab-1' + } + } + getAdserverCategoryHook(sinon.spy(), 'code', bid); + expect(bid.meta.adServerCatId).to.equal(1); + }); + + it('should set adserverCatId to undefined if not found in mapping file', function() { + config.setConfig({ + 'adpod': { + 'brandCategoryExclusion': true + } + }); + getLocalStorageStub.returns(JSON.stringify({ + 'mapping': { + 'iab-1': { + 'id': 1, + 'name': 'sample' + } + } + })); + let bid = { + meta: { + iabSubCatId: 'iab-2' + } + } + getAdserverCategoryHook(sinon.spy(), 'code', bid); + expect(bid.meta.adServerCatId).to.equal(undefined); + }); + + it('should not make ajax call to update mapping file if data found in localstorage and is not expired', function () { + let clock = sinon.useFakeTimers(utils.timestamp()); + getLocalStorageStub.returns(JSON.stringify({ + lastUpdated: utils.timestamp(), + mapping: { + 'iab-1': '1' + } + })); + initTranslation(); + expect(fakeTranslationServer.requests.length).to.equal(0); + clock.restore(); + }); + + it('should use default mapping file if publisher has not defined in config', function () { + getLocalStorageStub.returns(null); + initTranslation('http://sample.com', 'somekey'); + expect(fakeTranslationServer.requests.length).to.equal(1); + expect(fakeTranslationServer.requests[0].url).to.equal('http://sample.com'); + }); + + it('should use publisher defined defined mapping file', function () { + config.setConfig({ + 'brandCategoryTranslation': { + 'translationFile': 'http://sample.com' + } + }); + getLocalStorageStub.returns(null); + initTranslation('http://sample.com', 'somekey'); + expect(fakeTranslationServer.requests.length).to.equal(2); + expect(fakeTranslationServer.requests[0].url).to.equal('http://sample.com'); + }); +}); diff --git a/test/spec/modules/ccxBidAdapter_spec.js b/test/spec/modules/ccxBidAdapter_spec.js index 292503c9e04..a89a0402a97 100644 --- a/test/spec/modules/ccxBidAdapter_spec.js +++ b/test/spec/modules/ccxBidAdapter_spec.js @@ -77,7 +77,7 @@ describe('ccxAdapter', function () { }); describe('buildRequests', function () { it('No valid bids', function () { - expect(spec.buildRequests([])).to.be.empty; + expect(spec.buildRequests([])).to.be.undefined; }); it('Valid bid request - default', function () { diff --git a/test/spec/modules/cedatoBidAdapter_spec.js b/test/spec/modules/cedatoBidAdapter_spec.js new file mode 100644 index 00000000000..969c06a64a2 --- /dev/null +++ b/test/spec/modules/cedatoBidAdapter_spec.js @@ -0,0 +1,91 @@ +import {expect} from 'chai'; +import {spec} from 'modules/cedatoBidAdapter'; + +describe('the cedato adapter', function () { + function getValidBidObject() { + return { + bidId: '2f4a613a702b6c', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + params: { + player_id: 1450133326, + } + }; + }; + + describe('isBidRequestValid', function() { + var bid; + + beforeEach(function() { + bid = getValidBidObject(); + }); + + it('should fail validation if the bid isn\'t defined or not an object', function() { + var result = spec.isBidRequestValid(); + + expect(result).to.equal(false); + + result = spec.isBidRequestValid('not an object'); + + expect(result).to.equal(false); + }); + }); + describe('buildRequests', function() { + var bid, bidRequestObj; + + beforeEach(function() { + bid = getValidBidObject(); + bidRequestObj = {refererInfo: {referer: 'prebid.js'}}; + }); + + it('should build a very basic request', function() { + var request = spec.buildRequests([bid], bidRequestObj); + expect(request.method).to.equal('POST'); + }); + }); + + describe('interpretResponse', function() { + var bid, serverResponse, bidderRequest; + + beforeEach(function() { + bid = getValidBidObject(); + serverResponse = { + body: { + bidid: '0.36157306192821', + seatbid: [ + { + seat: '0', + bid: [{ + gp: { + 'negative': 0.496954, + 'positive': 0.503046, + 'class': '0' + }, + id: '0.75549202124378', + adomain: 'cedato.com', + uuid: bid.bidId, + crid: '1450133326', + adm: "
    \n\n\n", + h: 250, + w: 300, + price: '0.1' + }] + } + ], + cur: 'USD' + } + }; + bidderRequest = { + bids: [bid] + }; + }); + + it('should return an array of bid responses', function() { + var responses = spec.interpretResponse(serverResponse, {bidderRequest}); + expect(responses).to.be.an('array').with.length(1); + }); + }); +}); diff --git a/test/spec/modules/cleanmedianetBidAdapter_spec.js b/test/spec/modules/cleanmedianetBidAdapter_spec.js new file mode 100644 index 00000000000..09f76806fd7 --- /dev/null +++ b/test/spec/modules/cleanmedianetBidAdapter_spec.js @@ -0,0 +1,603 @@ +import { expect } from 'chai'; +import { spec } from 'modules/cleanmedianetBidAdapter'; +import { helper } from 'modules/cleanmedianetBidAdapter'; +import * as utils from 'src/utils'; +import { newBidder } from '../../../src/adapters/bidderFactory'; +import { deepClone } from 'src/utils'; + +const supplyPartnerId = '123'; +const adapter = newBidder(spec); +describe('CleanmedianetAdapter', function() { + describe('Is String start with search ', function() { + it('check if a string started with', function() { + expect(helper.startsWith('cleanmediaads.com', 'cleanmediaads')).to.equal( + true + ); + }); + }); + + 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 validate supply-partner ID', function() { + expect(spec.isBidRequestValid({ params: {} })).to.equal(false); + expect( + spec.isBidRequestValid({ params: { supplyPartnerId: 123 } }) + ).to.equal(false); + expect( + spec.isBidRequestValid({ params: { supplyPartnerId: '123' } }) + ).to.equal(true); + }); + + it('should validate bid floor', function() { + expect( + spec.isBidRequestValid({ params: { supplyPartnerId: '123' } }) + ).to.equal(true); // bidfloor has a default + expect( + spec.isBidRequestValid({ + params: { supplyPartnerId: '123', bidfloor: '123' } + }) + ).to.equal(false); + expect( + spec.isBidRequestValid({ + params: { supplyPartnerId: '123', bidfloor: 0.1 } + }) + ).to.equal(true); + }); + + it('should validate adpos', function() { + expect( + spec.isBidRequestValid({ params: { supplyPartnerId: '123' } }) + ).to.equal(true); // adpos has a default + expect( + spec.isBidRequestValid({ + params: { supplyPartnerId: '123', adpos: '123' } + }) + ).to.equal(false); + expect( + spec.isBidRequestValid({ + params: { supplyPartnerId: '123', adpos: 0.1 } + }) + ).to.equal(true); + }); + + it('should validate instl', function() { + expect( + spec.isBidRequestValid({ params: { supplyPartnerId: '123' } }) + ).to.equal(true); // adpos has a default + expect( + spec.isBidRequestValid({ + params: { supplyPartnerId: '123', instl: '123' } + }) + ).to.equal(false); + expect( + spec.isBidRequestValid({ + params: { supplyPartnerId: '123', instl: -1 } + }) + ).to.equal(false); + expect( + spec.isBidRequestValid({ params: { supplyPartnerId: '123', instl: 0 } }) + ).to.equal(true); + expect( + spec.isBidRequestValid({ params: { supplyPartnerId: '123', instl: 1 } }) + ).to.equal(true); + expect( + spec.isBidRequestValid({ params: { supplyPartnerId: '123', instl: 2 } }) + ).to.equal(false); + }); + }); + + describe('buildRequests', function() { + const bidRequest = { + adUnitCode: 'adunit-code', + auctionId: '1d1a030790a475', + mediaTypes: { + banner: {} + }, + params: { + supplyPartnerId: supplyPartnerId + }, + sizes: [[300, 250], [300, 600]], + transactionId: 'a123456789', + refererInfo: { referer: 'http://examplereferer.com' }, + gdprConsent: { + consentString: 'some string', + gdprApplies: true + } + }; + it('returns an array', function() { + let response; + response = spec.buildRequests([]); + expect(Array.isArray(response)).to.equal(true); + expect(response.length).to.equal(0); + response = spec.buildRequests([bidRequest], bidRequest); + expect(Array.isArray(response)).to.equal(true); + expect(response.length).to.equal(1); + const adUnit1 = Object.assign({}, utils.deepClone(bidRequest), { + auctionId: '1', + adUnitCode: 'a' + }); + const adUnit2 = Object.assign({}, utils.deepClone(bidRequest), { + auctionId: '1', + adUnitCode: 'b' + }); + response = spec.buildRequests([adUnit1, adUnit2], bidRequest); + expect(Array.isArray(response)).to.equal(true); + expect(response.length).to.equal(2); + }); + + it('builds request correctly', function() { + let stub = sinon + .stub(utils, 'getTopWindowUrl') + .returns('http://www.test.com/page.html'); + let bidRequest2 = deepClone(bidRequest); + bidRequest2.refererInfo.referer = 'http://www.test.com/page.html'; + let response = spec.buildRequests([bidRequest], bidRequest2)[0]; + expect(response.data.site.domain).to.equal('www.test.com'); + expect(response.data.site.page).to.equal('http://www.test.com/page.html'); + expect(response.data.site.ref).to.equal('http://www.test.com/page.html'); + expect(response.data.imp.length).to.equal(1); + expect(response.data.imp[0].id).to.equal(bidRequest.transactionId); + expect(response.data.imp[0].instl).to.equal(0); + expect(response.data.imp[0].tagid).to.equal(bidRequest.adUnitCode); + expect(response.data.imp[0].bidfloor).to.equal(0); + expect(response.data.imp[0].bidfloorcur).to.equal('USD'); + const bidRequestWithInstlEquals1 = utils.deepClone(bidRequest); + bidRequestWithInstlEquals1.params.instl = 1; + response = spec.buildRequests( + [bidRequestWithInstlEquals1], + bidRequest2 + )[0]; + expect(response.data.imp[0].instl).to.equal( + bidRequestWithInstlEquals1.params.instl + ); + const bidRequestWithInstlEquals0 = utils.deepClone(bidRequest); + bidRequestWithInstlEquals0.params.instl = 1; + response = spec.buildRequests( + [bidRequestWithInstlEquals0], + bidRequest2 + )[0]; + expect(response.data.imp[0].instl).to.equal( + bidRequestWithInstlEquals0.params.instl + ); + const bidRequestWithBidfloorEquals1 = utils.deepClone(bidRequest); + bidRequestWithBidfloorEquals1.params.bidfloor = 1; + response = spec.buildRequests( + [bidRequestWithBidfloorEquals1], + bidRequest2 + )[0]; + expect(response.data.imp[0].bidfloor).to.equal( + bidRequestWithBidfloorEquals1.params.bidfloor + ); + stub.restore(); + }); + + it('builds request banner object correctly', function() { + let response; + const bidRequestWithBanner = utils.deepClone(bidRequest); + bidRequestWithBanner.mediaTypes = { + banner: { + sizes: [[300, 250], [120, 600]] + } + }; + response = spec.buildRequests([bidRequestWithBanner], bidRequest)[0]; + expect(response.data.imp[0].banner.w).to.equal( + bidRequestWithBanner.mediaTypes.banner.sizes[0][0] + ); + expect(response.data.imp[0].banner.h).to.equal( + bidRequestWithBanner.mediaTypes.banner.sizes[0][1] + ); + expect(response.data.imp[0].banner.pos).to.equal(0); + const bidRequestWithPosEquals1 = utils.deepClone(bidRequestWithBanner); + bidRequestWithPosEquals1.params.pos = 1; + response = spec.buildRequests([bidRequestWithPosEquals1], bidRequest)[0]; + expect(response.data.imp[0].banner.pos).to.equal( + bidRequestWithPosEquals1.params.pos + ); + }); + + it('builds request video object correctly', function() { + let response; + const bidRequestWithVideo = utils.deepClone(bidRequest); + bidRequestWithVideo.mediaTypes = { + video: { + sizes: [[300, 250], [120, 600]] + } + }; + response = spec.buildRequests([bidRequestWithVideo], bidRequest)[0]; + expect(response.data.imp[0].video.w).to.equal( + bidRequestWithVideo.mediaTypes.video.sizes[0][0] + ); + expect(response.data.imp[0].video.h).to.equal( + bidRequestWithVideo.mediaTypes.video.sizes[0][1] + ); + expect(response.data.imp[0].video.pos).to.equal(0); + const bidRequestWithPosEquals1 = utils.deepClone(bidRequestWithVideo); + bidRequestWithPosEquals1.params.pos = 1; + response = spec.buildRequests([bidRequestWithPosEquals1], bidRequest)[0]; + expect(response.data.imp[0].video.pos).to.equal( + bidRequestWithPosEquals1.params.pos + ); + }); + + it('builds request video object correctly with context', function() { + let response; + const bidRequestWithVideo = utils.deepClone(bidRequest); + bidRequestWithVideo.mediaTypes = { + video: { + context: 'instream' + } + }; + response = spec.buildRequests([bidRequestWithVideo], bidRequest)[0]; + expect(response.data.imp[0].video.ext.context).to.equal('instream'); + bidRequestWithVideo.mediaTypes.video.context = 'outstream'; + + const bidRequestWithPosEquals1 = utils.deepClone(bidRequestWithVideo); + bidRequestWithPosEquals1.mediaTypes.video.context = 'outstream'; + response = spec.buildRequests([bidRequestWithPosEquals1], bidRequest)[0]; + expect(response.data.imp[0].video.ext.context).to.equal('outstream'); + + const bidRequestWithPosEquals2 = utils.deepClone(bidRequestWithVideo); + bidRequestWithPosEquals2.mediaTypes.video.context = null; + response = spec.buildRequests([bidRequestWithPosEquals2], bidRequest)[0]; + expect(response.data.imp[0].video.ext.context).to.equal(null); + }); + it('builds request video object correctly with multi-dimensions size array', function () { + let bidRequestWithVideo = utils.deepClone(bidRequest); + bidRequestWithVideo.mediaTypes.video = { + playerSize: [[304, 254], [305, 255]], + context: 'instream' + }; + + let response = spec.buildRequests([bidRequestWithVideo], bidRequest)[0]; + expect(response.data.imp[1].video.w).to.equal(304); + expect(response.data.imp[1].video.h).to.equal(254); + + bidRequestWithVideo = utils.deepClone(bidRequest); + bidRequestWithVideo.mediaTypes.video = { + playerSize: [304, 254] + }; + + response = spec.buildRequests([bidRequestWithVideo], bidRequest)[0]; + expect(response.data.imp[1].video.w).to.equal(304); + expect(response.data.imp[1].video.h).to.equal(254); + }); + + it('builds request with gdpr consent', function() { + let response = spec.buildRequests([bidRequest], bidRequest)[0]; + expect(response.data.ext).to.have.property('gdpr_consent'); + expect(response.data.ext.gdpr_consent.consent_string).to.equal( + 'some string' + ); + expect(response.data.ext.gdpr_consent.consent_required).to.equal(true); + }); + }); + + describe('interpretResponse', function() { + const bannerBidRequest = { + adUnitCode: 'adunit-code', + auctionId: '1d1a030790a475', + mediaTypes: { + banner: {} + }, + params: { + supplyPartnerId: supplyPartnerId + }, + sizes: [[300, 250], [300, 600]], + transactionId: 'a123456789', + bidId: '111', + refererInfo: { referer: 'http://examplereferer.com' } + }; + + const videoBidRequest = { + adUnitCode: 'adunit-code', + auctionId: '1d1a030790a475', + mediaTypes: { + video: {} + }, + params: { + supplyPartnerId: supplyPartnerId + }, + sizes: [[300, 250], [300, 600]], + transactionId: 'a123456789', + bidId: '111', + refererInfo: { referer: 'http://examplereferer.com' } + }; + + const rtbResponse = { + id: 'imp_5b05b9fde4b09084267a556f', + bidid: 'imp_5b05b9fde4b09084267a556f', + cur: 'USD', + ext: { + utrk: [ + { type: 'iframe', url: '//bidder.cleanmediaads.com/user/sync/1' }, + { type: 'image', url: '//bidder.cleanmediaads.com/user/sync/2' } + ] + }, + seatbid: [ + { + seat: 'seat1', + group: 0, + bid: [ + { + id: '0', + impid: '1', + price: 2.016, + adid: '579ef31bfa788b9d2000d562', + nurl: + 'https://bidder.cleanmediaads.com/pix/monitoring/win_notice/imp_5b05b9fde4b09084267a556f/im.gif?r=imp_5b05b9fde4b09084267a556f&i=1&a=579ef31bfa788b9d2000d562&b=0&p=${AUCTION_PRICE}', + adm: + '', + adomain: ['aaa.com'], + cid: '579ef268fa788b9d2000d55c', + crid: '579ef31bfa788b9d2000d562', + attr: [], + h: 600, + w: 120, + ext: { + vast_url: 'http://my.vast.com', + utrk: [{ type: 'iframe', url: '//p.partner1.io/user/sync/1' }] + } + } + ] + }, + { + seat: 'seat2', + group: 0, + bid: [ + { + id: '1', + impid: '1', + price: 3, + adid: '542jlhdfd2112jnjf3x', + nurl: + 'https://bidder.cleanmediaads.com/pix/monitoring/win_notice/imp_5b05b9fde4b09084267a556f/im.gif?r=imp_5b05b9fde4b09084267a556f&i=1&a=579ef31bfa788b9d2000d562&b=0&p=${AUCTION_PRICE}', + adm: + ' ', + adomain: ['bbb.com'], + cid: 'fgdlwjh2498ydjhg1', + crid: 'kjh34297ydh2133d', + attr: [], + h: 250, + w: 300, + ext: { + utrk: [{ type: 'image', url: '//p.partner2.io/user/sync/1' }] + } + } + ] + } + ] + }; + + it('returns an empty array on missing response', function() { + let response; + + response = spec.interpretResponse(undefined, { + bidRequest: bannerBidRequest + }); + expect(Array.isArray(response)).to.equal(true); + expect(response.length).to.equal(0); + + response = spec.interpretResponse({}, { bidRequest: bannerBidRequest }); + expect(Array.isArray(response)).to.equal(true); + expect(response.length).to.equal(0); + }); + + it('aggregates banner bids from all seat bids', function() { + const response = spec.interpretResponse( + { body: rtbResponse }, + { bidRequest: bannerBidRequest } + ); + expect(Array.isArray(response)).to.equal(true); + expect(response.length).to.equal(1); + + const ad0 = response[0]; + expect(ad0.requestId).to.equal(bannerBidRequest.bidId); + expect(ad0.cpm).to.equal(rtbResponse.seatbid[1].bid[0].price); + expect(ad0.width).to.equal(rtbResponse.seatbid[1].bid[0].w); + expect(ad0.height).to.equal(rtbResponse.seatbid[1].bid[0].h); + expect(ad0.ttl).to.equal(60 * 10); + expect(ad0.creativeId).to.equal(rtbResponse.seatbid[1].bid[0].crid); + expect(ad0.netRevenue).to.equal(true); + expect(ad0.currency).to.equal( + rtbResponse.seatbid[1].bid[0].cur || rtbResponse.cur || 'USD' + ); + expect(ad0.ad).to.equal(rtbResponse.seatbid[1].bid[0].adm); + expect(ad0.vastXml).to.be.an('undefined'); + expect(ad0.vastUrl).to.be.an('undefined'); + }); + + it('aggregates video bids from all seat bids', function() { + const response = spec.interpretResponse( + { body: rtbResponse }, + { bidRequest: videoBidRequest } + ); + expect(Array.isArray(response)).to.equal(true); + expect(response.length).to.equal(1); + + const ad0 = response[0]; + expect(ad0.requestId).to.equal(videoBidRequest.bidId); + expect(ad0.cpm).to.equal(rtbResponse.seatbid[0].bid[0].price); + expect(ad0.width).to.equal(rtbResponse.seatbid[0].bid[0].w); + expect(ad0.height).to.equal(rtbResponse.seatbid[0].bid[0].h); + expect(ad0.ttl).to.equal(60 * 10); + expect(ad0.creativeId).to.equal(rtbResponse.seatbid[0].bid[0].crid); + expect(ad0.netRevenue).to.equal(true); + expect(ad0.currency).to.equal( + rtbResponse.seatbid[0].bid[0].cur || rtbResponse.cur || 'USD' + ); + expect(ad0.ad).to.be.an('undefined'); + expect(ad0.vastXml).to.equal(rtbResponse.seatbid[0].bid[0].adm); + expect(ad0.vastUrl).to.equal(rtbResponse.seatbid[0].bid[0].ext.vast_url); + }); + + it('aggregates user-sync pixels', function() { + const response = spec.getUserSyncs({}, [{ body: rtbResponse }]); + expect(Array.isArray(response)).to.equal(true); + expect(response.length).to.equal(4); + expect(response[0].type).to.equal(rtbResponse.ext.utrk[0].type); + expect(response[0].url).to.equal( + rtbResponse.ext.utrk[0].url + '?gc=missing' + ); + expect(response[1].type).to.equal(rtbResponse.ext.utrk[1].type); + expect(response[1].url).to.equal( + rtbResponse.ext.utrk[1].url + '?gc=missing' + ); + expect(response[2].type).to.equal( + rtbResponse.seatbid[0].bid[0].ext.utrk[0].type + ); + expect(response[2].url).to.equal( + rtbResponse.seatbid[0].bid[0].ext.utrk[0].url + '?gc=missing' + ); + expect(response[3].type).to.equal( + rtbResponse.seatbid[1].bid[0].ext.utrk[0].type + ); + expect(response[3].url).to.equal( + rtbResponse.seatbid[1].bid[0].ext.utrk[0].url + '?gc=missing' + ); + }); + + it('supports configuring outstream renderers', function() { + const videoResponse = { + id: '64f32497-b2f7-48ec-9205-35fc39894d44', + bidid: 'imp_5c24924de4b0d106447af333', + cur: 'USD', + seatbid: [ + { + seat: '3668', + group: 0, + bid: [ + { + id: 'gb_1', + impid: 'afbb5852-7cea-4a81-aa9a-a41aab505c23', + price: 5.0, + adid: '1274', + nurl: + 'https://bidder.cleanmediaads.com/pix/1275/win_notice/imp_5c24924de4b0d106447af333/im.gif?r=imp_5c24924de4b0d106447af333&i=afbb5852-7cea-4a81-aa9a-a41aab505c23&a=1274&b=gb_1&p=${AUCTION_PRICE}', + adomain: [], + adm: + '\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n', + cid: '3668', + crid: '1274', + cat: [], + attr: [], + h: 250, + w: 300, + ext: { + vast_url: + 'https://bidder.cleanmediaads.com/pix/1275/vast_o/imp_5c24924de4b0d106447af333/im.xml?r=imp_5c24924de4b0d106447af333&i=afbb5852-7cea-4a81-aa9a-a41aab505c23&a=1274&b=gb_1&w=300&h=250&vatu=aHR0cHM6Ly9zdGF0aWMuZ2FtYmlkLmlvL2RlbW8vdmFzdC54bWw&vwarv', + imptrackers: [ + 'https://bidder.cleanmediaads.com/pix/1275/imp/imp_5c24924de4b0d106447af333/im.gif?r=imp_5c24924de4b0d106447af333&i=afbb5852-7cea-4a81-aa9a-a41aab505c23&a=1274&b=gb_1' + ] + } + } + ] + } + ], + ext: { + utrk: [ + { + type: 'image', + url: + 'https://bidder.cleanmediaads.com/pix/1275/scm?cb=1545900621675' + } + ] + } + }; + const videoRequest = deepClone(videoBidRequest); + videoRequest.mediaTypes.video.context = 'outstream'; + const result = spec.interpretResponse( + { body: videoResponse }, + { bidRequest: videoRequest } + ); + expect(result[0].renderer).to.not.equal(undefined); + }); + + it('validates in/existing of gdpr consent', function() { + let videoResponse = { + id: '64f32497-b2f7-48ec-9205-35fc39894d44', + bidid: 'imp_5c24924de4b0d106447af333', + cur: 'USD', + seatbid: [ + { + seat: '3668', + group: 0, + bid: [ + { + id: 'gb_1', + impid: 'afbb5852-7cea-4a81-aa9a-a41aab505c23', + price: 5.0, + adid: '1274', + nurl: + 'https://bidder.cleanmediaads.com/pix/1275/win_notice/imp_5c24924de4b0d106447af333/im.gif?r=imp_5c24924de4b0d106447af333&i=afbb5852-7cea-4a81-aa9a-a41aab505c23&a=1274&b=gb_1&p=${AUCTION_PRICE}', + adomain: [], + adm: + '\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n', + cid: '3668', + crid: '1274', + cat: [], + attr: [], + h: 250, + w: 300, + ext: { + vast_url: + 'https://bidder.cleanmediaads.com/pix/1275/vast_o/imp_5c24924de4b0d106447af333/im.xml?r=imp_5c24924de4b0d106447af333&i=afbb5852-7cea-4a81-aa9a-a41aab505c23&a=1274&b=gb_1&w=300&h=250&vatu=aHR0cHM6Ly9zdGF0aWMuZ2FtYmlkLmlvL2RlbW8vdmFzdC54bWw&vwarv', + imptrackers: [ + 'https://bidder.cleanmediaads.com/pix/1275/imp/imp_5c24924de4b0d106447af333/im.gif?r=imp_5c24924de4b0d106447af333&i=afbb5852-7cea-4a81-aa9a-a41aab505c23&a=1274&b=gb_1' + ] + } + } + ] + } + ], + ext: { + utrk: [ + { + type: 'image', + url: + 'https://bidder.cleanmediaads.com/pix/1275/scm?cb=1545900621675' + } + ] + } + }; + let gdprConsent = { + gdprApplies: true, + consentString: 'consent string' + }; + let result = spec.getUserSyncs( + {}, + [{ body: videoResponse }], + gdprConsent + ); + expect(result).to.be.an('array'); + expect(result.length).to.equal(1); + expect(result[0].type).to.equal('image'); + expect(result[0].url).to.equal( + 'https://bidder.cleanmediaads.com/pix/1275/scm?cb=1545900621675&gc=consent%20string' + ); + + gdprConsent.gdprApplies = false; + result = spec.getUserSyncs({}, [{ body: videoResponse }], gdprConsent); + expect(result).to.be.an('array'); + expect(result.length).to.equal(1); + expect(result[0].type).to.equal('image'); + expect(result[0].url).to.equal( + 'https://bidder.cleanmediaads.com/pix/1275/scm?cb=1545900621675&gc=missing' + ); + + videoResponse.ext.utrk[0].url = + 'https://bidder.cleanmediaads.com/pix/1275/scm'; + result = spec.getUserSyncs({}, [{ body: videoResponse }], gdprConsent); + expect(result).to.be.an('array'); + expect(result.length).to.equal(1); + expect(result[0].type).to.equal('image'); + expect(result[0].url).to.equal( + 'https://bidder.cleanmediaads.com/pix/1275/scm?gc=missing' + ); + }); + }); +}); diff --git a/test/spec/modules/coinzillaBidAdapter_spec.js b/test/spec/modules/coinzillaBidAdapter_spec.js new file mode 100644 index 00000000000..7a0c745d57d --- /dev/null +++ b/test/spec/modules/coinzillaBidAdapter_spec.js @@ -0,0 +1,120 @@ +import {assert, expect} from 'chai'; +import {spec} from 'modules/coinzillaBidAdapter'; +import {newBidder} from 'src/adapters/bidderFactory'; + +const ENDPOINT_URL = 'https://request.czilladx.com/serve/request.php'; + +describe('coinzillaBidAdapter', function () { + const adapter = newBidder(spec); + describe('isBidRequestValid', function () { + let bid = { + 'bidder': 'coinzilla', + 'params': { + placementId: 'testPlacementId' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [ + [300, 250] + ], + 'bidId': '1234asdf1234', + 'bidderRequestId': '1234asdf1234asdf', + 'auctionId': '61466567-d482-4a16-96f0-fe5f25ffbdf120' + }; + it('should return true where required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + }); + describe('buildRequests', function () { + let bidRequests = [ + { + 'bidder': 'coinzilla', + 'params': { + placementId: 'testPlacementId' + }, + 'sizes': [ + [300, 250] + ], + 'bidId': '23beaa6af6cdde', + 'bidderRequestId': '19c0c1efdf37e7', + 'auctionId': '61466567-d482-4a16-96f0-fe5f25ffbdf1', + }, + { + 'bidder': 'coinzilla', + 'params': { + placementId: 'testPlacementId' + }, + 'adUnitCode': 'adunit-code2', + 'sizes': [ + [300, 250] + ], + 'bidId': '382091349b149f"', + 'bidderRequestId': '1f9c98192de251', + 'auctionId': '61466567-d482-4a16-96f0-fe5f25ffbdf1', + } + ]; + + let bidderRequests = { + 'refererInfo': { + 'numIframes': 0, + 'reachedTop': true, + 'referer': 'http://example.com', + 'stack': ['http://example.com'] + } + }; + + const request = spec.buildRequests(bidRequests, bidderRequests); + it('sends bid request to our endpoint via POST', function () { + expect(request[0].method).to.equal('POST'); + expect(request[1].method).to.equal('POST'); + }); + it('attaches source and version to endpoint URL as query params', function () { + expect(request[0].url).to.equal(ENDPOINT_URL); + expect(request[1].url).to.equal(ENDPOINT_URL); + }); + }); + + describe('interpretResponse', function () { + let bidRequest = [ + { + 'method': 'POST', + 'url': ENDPOINT_URL, + 'data': { + 'placementId': 'testPlacementId', + 'width': '300', + 'height': '200', + 'bidId': 'bidId123', + 'referer': 'www.example.com' + } + + } + ]; + let serverResponse = { + body: { + 'ad': '

    I am an ad

    ', + 'cpm': 4.2, + 'creativeId': '12345asdfg', + 'currency': 'EUR', + 'statusMessage': 'Bid available', + 'requestId': 'bidId123', + 'width': 300, + 'height': 250, + 'netRevenue': true + } + }; + it('should get the correct bid response', function () { + let expectedResponse = [{ + 'requestId': 'bidId123', + 'cpm': 4.2, + 'width': 300, + 'height': 250, + 'creativeId': '12345asdfg', + 'currency': 'EUR', + 'netRevenue': true, + 'ttl': 3000, + 'ad': '

    I am an ad

    ' + }]; + let result = spec.interpretResponse(serverResponse, bidRequest[0]); + expect(Object.keys(result)).to.deep.equal(Object.keys(expectedResponse)); + }); + }); +}); diff --git a/test/spec/modules/collectcentBidAdapter_spec.js b/test/spec/modules/collectcentBidAdapter_spec.js new file mode 100644 index 00000000000..04c819992cd --- /dev/null +++ b/test/spec/modules/collectcentBidAdapter_spec.js @@ -0,0 +1,118 @@ +import {expect} from 'chai'; +import {spec} from '../../../modules/collectcentBidAdapter'; + +describe('Collectcent', function () { + let bid = { + bidId: '2dd581a2b6281d', + bidder: 'collectcent', + bidderRequestId: '145e1d6a7837c9', + params: { + placementId: 123, + traffic: 'banner' + }, + placementCode: 'placement_0', + auctionId: '74f78609-a92d-4cf1-869f-1b244bbfb5d2', + sizes: [[300, 250]], + transactionId: '3bb2f6da-87a6-4029-aeb0-bfe951372e62' + }; + + describe('isBidRequestValid', function () { + it('Should return true when placementId can be cast to a number', function () { + expect(spec.isBidRequestValid(bid)).to.be.true; + }); + it('Should return false when placementId is not a number', function () { + bid.params.placementId = 'aaa'; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + }); + + describe('buildRequests', function () { + let serverRequest = spec.buildRequests([bid]); + 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('//publishers.motionspots.com/?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', 'secure', 'host', 'page', 'placements'); + expect(data.deviceWidth).to.be.a('number'); + expect(data.deviceHeight).to.be.a('number'); + expect(data.secure).to.be.within(0, 1); + expect(data.host).to.be.a('string'); + expect(data.page).to.be.a('string'); + let placements = data['placements']; + for (let i = 0; i < placements.length; i++) { + let placement = placements[i]; + expect(placement).to.have.all.keys('placementId', 'bidId', 'traffic', 'sizes'); + 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'); + } + }); + 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 () { + let resObject = { + body: [ { + requestId: '123', + mediaType: 'banner', + cpm: 0.3, + width: 320, + height: 50, + ad: '

    Hello ad

    ', + ttl: 1000, + creativeId: '123asd', + netRevenue: true, + currency: 'USD' + } ] + }; + let serverResponses = spec.interpretResponse(resObject); + it('Returns an array of valid server responses if response object is valid', function () { + expect(serverResponses).to.be.an('array').that.is.not.empty; + for (let i = 0; i < serverResponses.length; i++) { + let dataItem = serverResponses[i]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'mediaType'); + expect(dataItem.requestId).to.be.a('string'); + expect(dataItem.cpm).to.be.a('number'); + expect(dataItem.width).to.be.a('number'); + expect(dataItem.height).to.be.a('number'); + expect(dataItem.ad).to.be.a('string'); + expect(dataItem.ttl).to.be.a('number'); + expect(dataItem.creativeId).to.be.a('string'); + expect(dataItem.netRevenue).to.be.a('boolean'); + expect(dataItem.currency).to.be.a('string'); + expect(dataItem.mediaType).to.be.a('string'); + } + it('Returns an empty array if invalid response is passed', function () { + serverResponses = spec.interpretResponse('invalid_response'); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + }); + }); + + describe('getUserSyncs', function () { + let userSync = spec.getUserSyncs(); + it('Returns valid URL and `', 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('//publishers.motionspots.com/?c=o&m=cookie'); + }); + }); +}); diff --git a/test/spec/modules/colombiaBidAdapter_spec.js b/test/spec/modules/colombiaBidAdapter_spec.js new file mode 100644 index 00000000000..5a8678e866c --- /dev/null +++ b/test/spec/modules/colombiaBidAdapter_spec.js @@ -0,0 +1,152 @@ +import { expect } from 'chai'; +import { spec } from 'modules/colombiaBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; + +const HOST_NAME = document.location.protocol + '//' + window.location.host; +const ENDPOINT = 'https://ade.clmbtech.com/cde/prebid.htm'; + +describe('colombiaBidAdapter', function() { + const adapter = newBidder(spec); + + describe('isBidRequestValid', function () { + let bid = { + 'bidder': 'colombia', + 'params': { + placementId: '307466' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [ + [300, 250] + ], + 'bidId': '23beaa6af6cdde', + 'bidderRequestId': '19c0c1efdf37e7', + 'auctionId': '61466567-d482-4a16-96f0-fe5f25ffbdf1', + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when placementId not passed correctly', function () { + bid.params.placementId = ''; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when require params are not passed', function () { + let bid = Object.assign({}, bid); + bid.params = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + let bidRequests = [ + { + 'bidder': 'colombia', + 'params': { + placementId: '307466' + }, + 'adUnitCode': 'adunit-code1', + 'sizes': [ + [300, 250] + ], + 'bidId': '23beaa6af6cdde', + 'bidderRequestId': '19c0c1efdf37e7', + 'auctionId': '61466567-d482-4a16-96f0-fe5f25ffbdf1', + }, + { + 'bidder': 'colombia', + 'params': { + placementId: '307466' + }, + 'adUnitCode': 'adunit-code2', + 'sizes': [ + [300, 250] + ], + 'bidId': '382091349b149f"', + 'bidderRequestId': '"1f9c98192de251"', + 'auctionId': '61466567-d482-4a16-96f0-fe5f25ffbdf1', + } + ]; + + const request = spec.buildRequests(bidRequests); + + it('sends bid request to our endpoint via POST', function () { + expect(request[0].method).to.equal('POST'); + expect(request[1].method).to.equal('POST'); + }); + + it('attaches source and version to endpoint URL as query params', function () { + expect(request[0].url).to.equal(ENDPOINT); + expect(request[1].url).to.equal(ENDPOINT); + }); + }); + + describe('interpretResponse', function () { + let bidRequest = [ + { + 'method': 'POST', + 'url': ENDPOINT, + 'data': { + 'v': 'hb1', + 'p': '307466', + 'w': '300', + 'h': '250', + 'cb': 12892917383, + 'r': 'http%3A%2F%2Flocalhost%3A9876%2F%3Fid%3D74552836', + 'uid': '23beaa6af6cdde', + 't': 'i', + 'd': HOST_NAME + } + } + ]; + + let serverResponse = { + body: { + 'ad': '
    This is test case
    ', + 'cpm': 3.14, + 'creativeId': '6b958110-612c-4b03-b6a9-7436c9f746dc-1sk24', + 'currency': 'USD', + 'statusMessage': 'Bid available', + 'uid': '23beaa6af6cdde', + 'width': 300, + 'height': 250, + 'netRevenue': true, + 'ttl': 600 + } + }; + + it('should get the correct bid response', function () { + let expectedResponse = [{ + 'requestId': '23beaa6af6cdde', + 'cpm': 3.14, + 'width': 300, + 'height': 250, + 'creativeId': '6b958110-612c-4b03-b6a9-7436c9f746dc-1sk24', + 'dealId': '', + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 3000, + 'referrer': '', + 'ad': '
    This is test case
    ' + }]; + let result = spec.interpretResponse(serverResponse, bidRequest[0]); + expect(Object.keys(result)).to.deep.equal(Object.keys(expectedResponse)); + }); + + it('handles empty bid response', function () { + let response = { + body: { + 'uid': '2c0b634db95a01', + 'height': 0, + 'crid': '', + 'statusMessage': 'Bid returned empty or error response', + 'width': 0, + 'cpm': 0 + } + }; + let result = spec.interpretResponse(response, bidRequest[0]); + expect(result.length).to.equal(0); + }); + }); +}); diff --git a/test/spec/modules/consentManagement_spec.js b/test/spec/modules/consentManagement_spec.js index baa9a43f6aa..6be96427750 100644 --- a/test/spec/modules/consentManagement_spec.js +++ b/test/spec/modules/consentManagement_spec.js @@ -1,5 +1,5 @@ -import {setConfig, requestBidsHook, resetConsentData, userCMP, consentTimeout, allowAuction} from 'modules/consentManagement'; -import {gdprDataHandler} from 'src/adaptermanager'; +import {setConsentConfig, requestBidsHook, resetConsentData, userCMP, consentTimeout, allowAuction, staticConsentData} from 'modules/consentManagement'; +import {gdprDataHandler} from 'src/adapterManager'; import * as utils from 'src/utils'; import { config } from 'src/config'; @@ -7,8 +7,8 @@ let assert = require('chai').assert; let expect = require('chai').expect; describe('consentManagement', function () { - describe('setConfig tests:', function () { - describe('empty setConfig value', function () { + describe('setConsentConfig tests:', function () { + describe('empty setConsentConfig value', function () { beforeEach(function () { sinon.stub(utils, 'logInfo'); }); @@ -19,7 +19,7 @@ describe('consentManagement', function () { }); it('should use system default values', function () { - setConfig({}); + setConsentConfig({}); expect(userCMP).to.be.equal('iab'); expect(consentTimeout).to.be.equal(10000); expect(allowAuction).to.be.true; @@ -27,10 +27,10 @@ describe('consentManagement', function () { }); }); - describe('valid setConfig value', function () { + describe('valid setConsentConfig value', function () { afterEach(function () { config.resetConfig(); - $$PREBID_GLOBAL$$.requestBids.removeHook(requestBidsHook); + $$PREBID_GLOBAL$$.requestBids.removeAll(); }); it('results in all user settings overriding system defaults', function () { let allConfig = { @@ -39,12 +39,421 @@ describe('consentManagement', function () { allowAuctionWithoutConsent: false }; - setConfig(allConfig); + setConsentConfig(allConfig); expect(userCMP).to.be.equal('iab'); expect(consentTimeout).to.be.equal(7500); expect(allowAuction).to.be.false; }); }); + + describe('static consent string setConsentConfig value', () => { + afterEach(() => { + config.resetConfig(); + $$PREBID_GLOBAL$$.requestBids.removeAll(); + }); + it('results in user settings overriding system defaults', () => { + let staticConfig = { + cmpApi: 'static', + timeout: 7500, + allowAuctionWithoutConsent: false, + consentData: { + getConsentData: { + 'gdprApplies': true, + 'hasGlobalScope': false, + 'consentData': 'BOOgjO9OOgjO9APABAENAi-AAAAWd7_______9____7_9uz_Gv_r_ff_3nW0739P1A_r_Oz_rm_-zzV44_lpQQRCEA' + }, + getVendorConsents: { + 'metadata': 'BOOgjO9OOgjO9APABAENAi-AAAAWd7_______9____7_9uz_Gv_r_ff_3nW0739P1A_r_Oz_rm_-zzV44_lpQQRCEA', + 'gdprApplies': true, + 'hasGlobalScope': false, + 'isEU': true, + 'cookieVersion': 1, + 'created': '2018-05-29T07:45:48.522Z', + 'lastUpdated': '2018-05-29T07:45:48.522Z', + 'cmpId': 15, + 'cmpVersion': 1, + 'consentLanguage': 'EN', + 'vendorListVersion': 34, + 'maxVendorId': 359, + 'purposeConsents': { + '1': true, + '2': true, + '3': true, + '4': true, + '5': true + }, + 'vendorConsents': { + '1': true, + '2': true, + '3': true, + '4': true, + '5': false, + '6': true, + '7': true, + '8': true, + '9': true, + '10': true, + '11': true, + '12': true, + '13': true, + '14': true, + '15': true, + '16': true, + '17': true, + '18': true, + '19': true, + '20': true, + '21': true, + '22': true, + '23': true, + '24': true, + '25': true, + '26': true, + '27': true, + '28': true, + '29': true, + '30': true, + '31': true, + '32': true, + '33': true, + '34': true, + '35': true, + '36': true, + '37': true, + '38': true, + '39': true, + '40': true, + '41': true, + '42': true, + '43': true, + '44': true, + '45': true, + '46': true, + '47': true, + '48': true, + '49': true, + '50': true, + '51': true, + '52': true, + '53': true, + '54': false, + '55': true, + '56': true, + '57': true, + '58': true, + '59': true, + '60': true, + '61': true, + '62': true, + '63': true, + '64': true, + '65': true, + '66': true, + '67': true, + '68': true, + '69': true, + '70': true, + '71': true, + '72': true, + '73': true, + '74': true, + '75': true, + '76': true, + '77': true, + '78': true, + '79': true, + '80': true, + '81': true, + '82': true, + '83': false, + '84': true, + '85': true, + '86': true, + '87': true, + '88': true, + '89': true, + '90': true, + '91': true, + '92': true, + '93': true, + '94': true, + '95': true, + '96': false, + '97': true, + '98': true, + '99': false, + '100': true, + '101': true, + '102': true, + '103': false, + '104': true, + '105': true, + '106': false, + '107': false, + '108': true, + '109': true, + '110': true, + '111': true, + '112': true, + '113': true, + '114': true, + '115': true, + '116': false, + '117': false, + '118': false, + '119': true, + '120': true, + '121': false, + '122': true, + '123': false, + '124': true, + '125': true, + '126': true, + '127': true, + '128': true, + '129': true, + '130': true, + '131': true, + '132': true, + '133': true, + '134': true, + '135': false, + '136': true, + '137': false, + '138': true, + '139': true, + '140': true, + '141': true, + '142': true, + '143': true, + '144': true, + '145': true, + '146': false, + '147': true, + '148': true, + '149': true, + '150': true, + '151': true, + '152': false, + '153': true, + '154': true, + '155': true, + '156': true, + '157': true, + '158': true, + '159': true, + '160': true, + '161': true, + '162': true, + '163': true, + '164': true, + '165': true, + '166': false, + '167': true, + '168': true, + '169': true, + '170': true, + '171': false, + '172': false, + '173': true, + '174': true, + '175': true, + '176': false, + '177': true, + '178': false, + '179': true, + '180': true, + '181': false, + '182': true, + '183': true, + '184': false, + '185': true, + '186': false, + '187': false, + '188': true, + '189': true, + '190': true, + '191': false, + '192': true, + '193': true, + '194': true, + '195': true, + '196': false, + '197': true, + '198': true, + '199': true, + '200': true, + '201': true, + '202': true, + '203': true, + '204': false, + '205': true, + '206': false, + '207': false, + '208': true, + '209': true, + '210': true, + '211': true, + '212': true, + '213': true, + '214': false, + '215': true, + '216': false, + '217': true, + '218': false, + '219': false, + '220': false, + '221': false, + '222': false, + '223': false, + '224': true, + '225': true, + '226': true, + '227': true, + '228': true, + '229': true, + '230': true, + '231': false, + '232': true, + '233': false, + '234': true, + '235': true, + '236': true, + '237': true, + '238': true, + '239': true, + '240': true, + '241': true, + '242': false, + '243': false, + '244': true, + '245': true, + '246': true, + '247': false, + '248': true, + '249': true, + '250': false, + '251': false, + '252': true, + '253': true, + '254': true, + '255': true, + '256': true, + '257': true, + '258': true, + '259': true, + '260': true, + '261': false, + '262': true, + '263': false, + '264': true, + '265': true, + '266': true, + '267': false, + '268': false, + '269': true, + '270': true, + '271': false, + '272': true, + '273': true, + '274': true, + '275': true, + '276': true, + '277': true, + '278': true, + '279': true, + '280': true, + '281': true, + '282': true, + '283': false, + '284': true, + '285': true, + '286': false, + '287': false, + '288': true, + '289': true, + '290': true, + '291': true, + '292': false, + '293': false, + '294': true, + '295': true, + '296': false, + '297': true, + '298': false, + '299': true, + '300': false, + '301': true, + '302': true, + '303': true, + '304': true, + '305': false, + '306': false, + '307': false, + '308': true, + '309': true, + '310': true, + '311': false, + '312': false, + '313': false, + '314': true, + '315': true, + '316': true, + '317': true, + '318': true, + '319': true, + '320': true, + '321': false, + '322': false, + '323': true, + '324': false, + '325': true, + '326': true, + '327': false, + '328': true, + '329': false, + '330': false, + '331': true, + '332': false, + '333': true, + '334': false, + '335': false, + '336': false, + '337': false, + '338': false, + '339': true, + '340': false, + '341': false, + '342': false, + '343': false, + '344': false, + '345': true, + '346': false, + '347': false, + '348': false, + '349': true, + '350': false, + '351': false, + '352': false, + '353': false, + '354': true, + '355': false, + '356': false, + '357': false, + '358': false, + '359': true + } + } + } + }; + + setConsentConfig(staticConfig); + expect(userCMP).to.be.equal('static'); + expect(consentTimeout).to.be.equal(0); // should always return without a timeout when config is used + expect(allowAuction).to.be.false; + expect(staticConsentData).to.be.equal(staticConfig.consentData); + }); + }); }); describe('requestBidsHook tests:', function () { @@ -78,7 +487,7 @@ describe('consentManagement', function () { utils.logWarn.restore(); utils.logError.restore(); config.resetConfig(); - $$PREBID_GLOBAL$$.requestBids.removeHook(requestBidsHook); + $$PREBID_GLOBAL$$.requestBids.removeAll(); resetConsentData(); }); @@ -86,12 +495,12 @@ describe('consentManagement', function () { let badCMPConfig = { cmpApi: 'bad' }; - setConfig(badCMPConfig); + setConsentConfig(badCMPConfig); expect(userCMP).to.be.equal(badCMPConfig.cmpApi); - requestBidsHook({}, () => { + requestBidsHook(() => { didHookReturn = true; - }); + }, {}); let consent = gdprDataHandler.getConsentData(); sinon.assert.calledOnce(utils.logWarn); expect(didHookReturn).to.be.true; @@ -99,11 +508,11 @@ describe('consentManagement', function () { }); it('should throw proper errors when CMP is not found', function () { - setConfig(goodConfigWithCancelAuction); + setConsentConfig(goodConfigWithCancelAuction); - requestBidsHook({}, () => { + requestBidsHook(() => { didHookReturn = true; - }); + }, {}); let consent = gdprDataHandler.getConsentData(); // throw 2 errors; one for no bidsBackHandler and for CMP not being found (this is an error due to gdpr config) sinon.assert.calledTwice(utils.logError); @@ -122,7 +531,7 @@ describe('consentManagement', function () { afterEach(function () { config.resetConfig(); - $$PREBID_GLOBAL$$.requestBids.removeHook(requestBidsHook); + $$PREBID_GLOBAL$$.requestBids.removeAll(); cmpStub.restore(); delete window.__cmp; resetConsentData(); @@ -137,8 +546,8 @@ describe('consentManagement', function () { cmpStub = sinon.stub(window, '__cmp').callsFake((...args) => { args[2](testConsentData); }); - setConfig(goodConfigWithAllowAuction); - requestBidsHook({}, () => {}); + setConsentConfig(goodConfigWithAllowAuction); + requestBidsHook(() => {}, {}); cmpStub.restore(); // reset the stub to ensure it wasn't called during the second round of calls @@ -146,9 +555,9 @@ describe('consentManagement', function () { args[2](testConsentData); }); - requestBidsHook({}, () => { + requestBidsHook(() => { didHookReturn = true; - }); + }, {}); let consent = gdprDataHandler.getConsentData(); expect(didHookReturn).to.be.true; @@ -176,7 +585,7 @@ describe('consentManagement', function () { afterEach(function () { delete window.$sf; config.resetConfig(); - $$PREBID_GLOBAL$$.requestBids.removeHook(requestBidsHook); + $$PREBID_GLOBAL$$.requestBids.removeAll(); registerStub.restore(); utils.logError.restore(); utils.logWarn.restore(); @@ -201,10 +610,10 @@ describe('consentManagement', function () { args[2](testConsentData.data.msgName, testConsentData.data); }); - setConfig(goodConfigWithAllowAuction); - requestBidsHook({adUnits: [{ sizes: [[300, 250]] }]}, () => { + setConsentConfig(goodConfigWithAllowAuction); + requestBidsHook(() => { didHookReturn = true; - }); + }, {adUnits: [{ sizes: [[300, 250]] }]}); let consent = gdprDataHandler.getConsentData(); sinon.assert.notCalled(utils.logWarn); @@ -228,7 +637,7 @@ describe('consentManagement', function () { afterEach(function () { config.resetConfig(); - $$PREBID_GLOBAL$$.requestBids.removeHook(requestBidsHook); + $$PREBID_GLOBAL$$.requestBids.removeAll(); delete window.__cmp; utils.logError.restore(); utils.logWarn.restore(); @@ -275,15 +684,15 @@ describe('consentManagement', function () { function testIFramedPage(testName, messageFormatString) { it(`should return the consent string from a postmessage + addEventListener response - ${testName}`, (done) => { stringifyResponse = messageFormatString; - setConfig(goodConfigWithAllowAuction); - requestBidsHook({}, () => { + setConsentConfig(goodConfigWithAllowAuction); + requestBidsHook(() => { let consent = gdprDataHandler.getConsentData(); sinon.assert.notCalled(utils.logWarn); sinon.assert.notCalled(utils.logError); expect(consent.consentString).to.equal('encoded_consent_data_via_post_message'); expect(consent.gdprApplies).to.be.true; done(); - }); + }, {}); }); } }); @@ -300,7 +709,7 @@ describe('consentManagement', function () { afterEach(function () { config.resetConfig(); - $$PREBID_GLOBAL$$.requestBids.removeHook(requestBidsHook); + $$PREBID_GLOBAL$$.requestBids.removeAll(); cmpStub.restore(); utils.logError.restore(); utils.logWarn.restore(); @@ -317,11 +726,11 @@ describe('consentManagement', function () { args[2](testConsentData); }); - setConfig(goodConfigWithAllowAuction); + setConsentConfig(goodConfigWithAllowAuction); - requestBidsHook({}, () => { + requestBidsHook(() => { didHookReturn = true; - }); + }, {}); let consent = gdprDataHandler.getConsentData(); sinon.assert.notCalled(utils.logWarn); @@ -339,11 +748,11 @@ describe('consentManagement', function () { args[2](testConsentData); }); - setConfig(goodConfigWithCancelAuction); + setConsentConfig(goodConfigWithCancelAuction); - requestBidsHook({ bidsBackHandler: () => bidsBackHandlerReturn = true }, () => { + requestBidsHook(() => { didHookReturn = true; - }); + }, { bidsBackHandler: () => bidsBackHandlerReturn = true }); let consent = gdprDataHandler.getConsentData(); sinon.assert.calledOnce(utils.logError); @@ -359,11 +768,11 @@ describe('consentManagement', function () { args[2](testConsentData); }); - setConfig(goodConfigWithAllowAuction); + setConsentConfig(goodConfigWithAllowAuction); - requestBidsHook({}, () => { + requestBidsHook(() => { didHookReturn = true; - }); + }, {}); let consent = gdprDataHandler.getConsentData(); sinon.assert.calledOnce(utils.logWarn); diff --git a/test/spec/modules/consumableBidAdapter_spec.js b/test/spec/modules/consumableBidAdapter_spec.js index d13c3c56398..fc5e1d1b45a 100644 --- a/test/spec/modules/consumableBidAdapter_spec.js +++ b/test/spec/modules/consumableBidAdapter_spec.js @@ -1,7 +1,6 @@ import { expect } from 'chai'; import { spec } from 'modules/consumableBidAdapter'; - -var bidFactory = require('src/bidfactory.js'); +import { createBid } from 'src/bidfactory'; const ENDPOINT = 'https://e.serverbid.com/api/v2'; const SMARTSYNC_CALLBACK = 'serverbidCallBids'; @@ -44,6 +43,10 @@ const REQUEST = { 'bidderRequestId': '109f2a181342a9', 'auctionId': 'a4713c32-3762-4798-b342-4ab810ca770d' }], + 'gdprConsent': { + 'consentString': 'consent-test', + 'gdprApplies': true + }, 'start': 1487883186070, 'auctionStart': 1487883186069, 'timeout': 3000 @@ -53,6 +56,7 @@ const RESPONSE = { 'headers': null, 'body': { 'user': { 'key': 'ue1-2d33e91b71e74929b4aeecc23f4376f1' }, + 'pixels': [{ 'type': 'image', 'url': '//sync.serverbid.com/ss/' }], 'decisions': { '2b0f82502298c9': { 'adId': 2364764, @@ -207,8 +211,8 @@ describe('Consumable BidAdapter', function () { }); describe('interpretResponse validation', function () { it('response should have valid bidderCode', function () { - let bidRequest = spec.buildRequests(REQUEST.bidRequest); - let bid = bidFactory.createBid(1, bidRequest.bidRequest[0]); + let bidRequest = spec.buildRequests(REQUEST.bidRequest, REQUEST); + let bid = createBid(1, bidRequest.bidRequest[0]); expect(bid.bidderCode).to.equal('consumable'); }); @@ -232,7 +236,7 @@ describe('Consumable BidAdapter', function () { expect(b).to.have.property('ad'); expect(b).to.have.property('currency', 'USD'); expect(b).to.have.property('creativeId'); - expect(b).to.have.property('ttl', 360); + expect(b).to.have.property('ttl', 30); expect(b).to.have.property('netRevenue', true); expect(b).to.have.property('referrer'); }); @@ -257,7 +261,7 @@ describe('Consumable BidAdapter', function () { it('handles empty sync options', function () { let opts = spec.getUserSyncs({}); - expect(opts).to.be.empty; + expect(opts).to.be.undefined; }); it('should return a sync url if iframe syncs are enabled', function () { @@ -265,5 +269,12 @@ describe('Consumable BidAdapter', function () { expect(opts.length).to.equal(1); }); + + it('should return a sync url if pixel syncs are enabled and some are returned from the server', function () { + let syncOptions = {'pixelEnabled': true}; + let opts = spec.getUserSyncs(syncOptions, [RESPONSE]); + + expect(opts.length).to.equal(1); + }); }); }); diff --git a/test/spec/modules/conversantBidAdapter_spec.js b/test/spec/modules/conversantBidAdapter_spec.js index 91b3ed6892b..6c9f0c8e6e5 100644 --- a/test/spec/modules/conversantBidAdapter_spec.js +++ b/test/spec/modules/conversantBidAdapter_spec.js @@ -2,19 +2,18 @@ import {expect} from 'chai'; import {spec} from 'modules/conversantBidAdapter'; import * as utils from 'src/utils'; -var Adapter = require('modules/conversantBidAdapter'); - describe('Conversant adapter tests', function() { const siteId = '108060'; + const versionPattern = /^\d+\.\d+\.\d+(.)*$/; const bidRequests = [ + // banner with single size { bidder: 'conversant', params: { site_id: siteId, position: 1, tag_id: 'tagid-1', - secure: false, bidfloor: 0.5 }, placementCode: 'pcode000', @@ -23,25 +22,31 @@ describe('Conversant adapter tests', function() { bidId: 'bid000', bidderRequestId: '117d765b87bed38', auctionId: 'req000' - }, { + }, + // banner with sizes in mediaTypes.banner.sizes + { bidder: 'conversant', params: { - site_id: siteId, - secure: false + site_id: siteId + }, + mediaTypes: { + banner: { + sizes: [[728, 90], [468, 60]] + } }, placementCode: 'pcode001', transactionId: 'tx001', - sizes: [[468, 60]], bidId: 'bid001', bidderRequestId: '117d765b87bed38', auctionId: 'req000' - }, { + }, + // banner with tag id and position + { bidder: 'conversant', params: { site_id: siteId, position: 2, - tag_id: '', - secure: false + tag_id: '' }, placementCode: 'pcode002', transactionId: 'tx002', @@ -49,7 +54,9 @@ describe('Conversant adapter tests', function() { bidId: 'bid002', bidderRequestId: '117d765b87bed38', auctionId: 'req000' - }, { + }, + // video with single size + { bidder: 'conversant', params: { site_id: siteId, @@ -60,7 +67,8 @@ describe('Conversant adapter tests', function() { }, mediaTypes: { video: { - context: 'instream' + context: 'instream', + playerSize: [632, 499], } }, placementCode: 'pcode003', @@ -69,6 +77,47 @@ describe('Conversant adapter tests', function() { bidId: 'bid003', bidderRequestId: '117d765b87bed38', auctionId: 'req000' + }, + // video with playerSize + { + bidder: 'conversant', + params: { + site_id: siteId, + maxduration: 30, + api: [2, 3] + }, + mediaTypes: { + video: { + context: 'instream', + playerSize: [1024, 768], + api: [1, 2], + protocols: [1, 2, 3], + mimes: ['video/mp4', 'video/x-flv'] + } + }, + placementCode: 'pcode004', + transactionId: 'tx004', + bidId: 'bid004', + bidderRequestId: '117d765b87bed38', + auctionId: 'req000' + }, + // video without sizes + { + bidder: 'conversant', + params: { + site_id: siteId + }, + mediaTypes: { + video: { + context: 'instream', + mimes: ['video/mp4', 'video/x-flv'] + } + }, + placementCode: 'pcode005', + transactionId: 'tx005', + bidId: 'bid005', + bidderRequestId: '117d765b87bed38', + auctionId: 'req000' }]; const bidResponses = { @@ -128,6 +177,8 @@ describe('Conversant adapter tests', function() { expect(spec.isBidRequestValid(bidRequests[1])).to.be.true; expect(spec.isBidRequestValid(bidRequests[2])).to.be.true; expect(spec.isBidRequestValid(bidRequests[3])).to.be.true; + expect(spec.isBidRequestValid(bidRequests[4])).to.be.true; + expect(spec.isBidRequestValid(bidRequests[5])).to.be.true; const simpleVideo = JSON.parse(JSON.stringify(bidRequests[3])); simpleVideo.params.site_id = 123; @@ -142,21 +193,27 @@ describe('Conversant adapter tests', function() { }); it('Verify buildRequest', function() { - const request = spec.buildRequests(bidRequests); + const page = 'http://test.com?a=b&c=123'; + const bidderRequest = { + refererInfo: { + referer: page + } + }; + const request = spec.buildRequests(bidRequests, bidderRequest); expect(request.method).to.equal('POST'); - expect(request.url).to.equal('//web.hb.ad.cpe.dotomi.com/s2s/header/24'); + expect(request.url).to.equal('https://web.hb.ad.cpe.dotomi.com/s2s/header/24'); const payload = request.data; expect(payload).to.have.property('id', 'req000'); expect(payload).to.have.property('at', 1); expect(payload).to.have.property('imp'); - expect(payload.imp).to.be.an('array').with.lengthOf(4); + expect(payload.imp).to.be.an('array').with.lengthOf(6); expect(payload.imp[0]).to.have.property('id', 'bid000'); - expect(payload.imp[0]).to.have.property('secure', 0); + expect(payload.imp[0]).to.have.property('secure', 1); expect(payload.imp[0]).to.have.property('bidfloor', 0.5); expect(payload.imp[0]).to.have.property('displaymanager', 'Prebid.js'); - expect(payload.imp[0]).to.have.property('displaymanagerver').that.matches(/^\d+\.\d+\.\d+$/); + expect(payload.imp[0]).to.have.property('displaymanagerver').that.matches(versionPattern); expect(payload.imp[0]).to.have.property('tagid', 'tagid-1'); expect(payload.imp[0]).to.have.property('banner'); expect(payload.imp[0].banner).to.have.property('pos', 1); @@ -165,36 +222,36 @@ describe('Conversant adapter tests', function() { expect(payload.imp[0]).to.not.have.property('video'); expect(payload.imp[1]).to.have.property('id', 'bid001'); - expect(payload.imp[1]).to.have.property('secure', 0); + expect(payload.imp[1]).to.have.property('secure', 1); expect(payload.imp[1]).to.have.property('bidfloor', 0); expect(payload.imp[1]).to.have.property('displaymanager', 'Prebid.js'); - expect(payload.imp[0]).to.have.property('displaymanagerver').that.matches(/^\d+\.\d+\.\d+$/); + expect(payload.imp[1]).to.have.property('displaymanagerver').that.matches(versionPattern); expect(payload.imp[1]).to.not.have.property('tagid'); expect(payload.imp[1]).to.have.property('banner'); expect(payload.imp[1].banner).to.not.have.property('pos'); expect(payload.imp[1].banner).to.have.property('format'); - expect(payload.imp[1].banner.format).to.deep.equal([{w: 468, h: 60}]); + expect(payload.imp[1].banner.format).to.deep.equal([{w: 728, h: 90}, {w: 468, h: 60}]); expect(payload.imp[2]).to.have.property('id', 'bid002'); - expect(payload.imp[2]).to.have.property('secure', 0); + expect(payload.imp[2]).to.have.property('secure', 1); expect(payload.imp[2]).to.have.property('bidfloor', 0); expect(payload.imp[2]).to.have.property('displaymanager', 'Prebid.js'); - expect(payload.imp[0]).to.have.property('displaymanagerver').that.matches(/^\d+\.\d+\.\d+$/); + expect(payload.imp[2]).to.have.property('displaymanagerver').that.matches(versionPattern); expect(payload.imp[2]).to.have.property('banner'); expect(payload.imp[2].banner).to.have.property('pos', 2); expect(payload.imp[2].banner).to.have.property('format'); expect(payload.imp[2].banner.format).to.deep.equal([{w: 300, h: 600}, {w: 160, h: 600}]); expect(payload.imp[3]).to.have.property('id', 'bid003'); - expect(payload.imp[3]).to.have.property('secure', 0); + expect(payload.imp[3]).to.have.property('secure', 1); expect(payload.imp[3]).to.have.property('bidfloor', 0); expect(payload.imp[3]).to.have.property('displaymanager', 'Prebid.js'); - expect(payload.imp[0]).to.have.property('displaymanagerver').that.matches(/^\d+\.\d+\.\d+$/); + expect(payload.imp[3]).to.have.property('displaymanagerver').that.matches(versionPattern); expect(payload.imp[3]).to.not.have.property('tagid'); expect(payload.imp[3]).to.have.property('video'); expect(payload.imp[3].video).to.not.have.property('pos'); - expect(payload.imp[3].video).to.have.property('w', 640); - expect(payload.imp[3].video).to.have.property('h', 480); + expect(payload.imp[3].video).to.have.property('w', 632); + expect(payload.imp[3].video).to.have.property('h', 499); expect(payload.imp[3].video).to.have.property('mimes'); expect(payload.imp[3].video.mimes).to.deep.equal(['video/mp4', 'video/x-flv']); expect(payload.imp[3].video).to.have.property('protocols'); @@ -204,11 +261,46 @@ describe('Conversant adapter tests', function() { expect(payload.imp[3].video).to.have.property('maxduration', 30); expect(payload.imp[3]).to.not.have.property('banner'); + expect(payload.imp[4]).to.have.property('id', 'bid004'); + expect(payload.imp[4]).to.have.property('secure', 1); + expect(payload.imp[4]).to.have.property('bidfloor', 0); + expect(payload.imp[4]).to.have.property('displaymanager', 'Prebid.js'); + expect(payload.imp[4]).to.have.property('displaymanagerver').that.matches(versionPattern); + expect(payload.imp[4]).to.not.have.property('tagid'); + expect(payload.imp[4]).to.have.property('video'); + expect(payload.imp[4].video).to.not.have.property('pos'); + expect(payload.imp[4].video).to.have.property('w', 1024); + expect(payload.imp[4].video).to.have.property('h', 768); + expect(payload.imp[4].video).to.have.property('mimes'); + expect(payload.imp[4].video.mimes).to.deep.equal(['video/mp4', 'video/x-flv']); + expect(payload.imp[4].video).to.have.property('protocols'); + expect(payload.imp[4].video.protocols).to.deep.equal([1, 2, 3]); + expect(payload.imp[4].video).to.have.property('api'); + expect(payload.imp[4].video.api).to.deep.equal([2, 3]); + expect(payload.imp[4].video).to.have.property('maxduration', 30); + expect(payload.imp[4]).to.not.have.property('banner'); + + expect(payload.imp[5]).to.have.property('id', 'bid005'); + expect(payload.imp[5]).to.have.property('secure', 1); + expect(payload.imp[5]).to.have.property('bidfloor', 0); + expect(payload.imp[5]).to.have.property('displaymanager', 'Prebid.js'); + expect(payload.imp[5]).to.have.property('displaymanagerver').that.matches(versionPattern); + expect(payload.imp[5]).to.not.have.property('tagid'); + expect(payload.imp[5]).to.have.property('video'); + expect(payload.imp[5].video).to.not.have.property('pos'); + expect(payload.imp[5].video).to.not.have.property('w'); + expect(payload.imp[5].video).to.not.have.property('h'); + expect(payload.imp[5].video).to.have.property('mimes'); + expect(payload.imp[5].video.mimes).to.deep.equal(['video/mp4', 'video/x-flv']); + expect(payload.imp[5].video).to.not.have.property('protocols'); + expect(payload.imp[5].video).to.not.have.property('api'); + expect(payload.imp[5].video).to.not.have.property('maxduration'); + expect(payload.imp[5]).to.not.have.property('banner'); + expect(payload).to.have.property('site'); expect(payload.site).to.have.property('id', siteId); expect(payload.site).to.have.property('mobile').that.is.oneOf([0, 1]); - const loc = utils.getTopWindowLocation(); - const page = loc.href; + expect(payload.site).to.have.property('page', page); expect(payload).to.have.property('device'); @@ -254,8 +346,8 @@ describe('Conversant adapter tests', function() { expect(bid).to.have.property('currency', 'USD'); expect(bid).to.have.property('cpm', 3.99); expect(bid).to.have.property('creativeId', '1003'); - expect(bid).to.have.property('width', 640); - expect(bid).to.have.property('height', 480); + expect(bid).to.have.property('width', 632); + expect(bid).to.have.property('height', 499); expect(bid).to.have.property('vastUrl', 'markup003'); expect(bid).to.have.property('mediaType', 'video'); expect(bid).to.have.property('ttl', 300); @@ -273,7 +365,7 @@ describe('Conversant adapter tests', function() { it('Verify publisher commond id support', function() { // clone bidRequests - let requests = utils.deepClone(bidRequests) + let requests = utils.deepClone(bidRequests); // add pubcid to every entry requests.forEach((unit) => { @@ -281,7 +373,20 @@ describe('Conversant adapter tests', function() { }); // construct http post payload const payload = spec.buildRequests(requests).data; - expect(payload).to.have.deep.property('user.ext.fpc', 12345); + expect(payload).to.have.deep.nested.property('user.ext.fpc', 12345); + }); + + it('Verify User ID publisher commond id support', function() { + // clone bidRequests + let requests = utils.deepClone(bidRequests); + + // add pubcid to every entry + requests.forEach((unit) => { + Object.assign(unit, {userId: {pubcid: 67890}}); + }); + // construct http post payload + const payload = spec.buildRequests(requests).data; + expect(payload).to.have.deep.nested.property('user.ext.fpc', 67890); }); it('Verify GDPR bid request', function() { @@ -294,8 +399,8 @@ describe('Conversant adapter tests', function() { }; const payload = spec.buildRequests(bidRequests, bidRequest).data; - expect(payload).to.have.deep.property('user.ext.consent', 'BOJObISOJObISAABAAENAA4AAAAAoAAA'); - expect(payload).to.have.deep.property('regs.ext.gdpr', 1); + expect(payload).to.have.deep.nested.property('user.ext.consent', 'BOJObISOJObISAABAAENAA4AAAAAoAAA'); + expect(payload).to.have.deep.nested.property('regs.ext.gdpr', 1); }); it('Verify GDPR bid request without gdprApplies', function() { @@ -307,7 +412,7 @@ describe('Conversant adapter tests', function() { }; const payload = spec.buildRequests(bidRequests, bidRequest).data; - expect(payload).to.have.deep.property('user.ext.consent', ''); - expect(payload).to.not.have.deep.property('regs.ext.gdpr'); + expect(payload).to.have.deep.nested.property('user.ext.consent', ''); + expect(payload).to.not.have.deep.nested.property('regs.ext.gdpr'); }); -}) +}); diff --git a/test/spec/modules/cosmosBidAdapter_spec.js b/test/spec/modules/cosmosBidAdapter_spec.js new file mode 100644 index 00000000000..348f5ae3ddf --- /dev/null +++ b/test/spec/modules/cosmosBidAdapter_spec.js @@ -0,0 +1,355 @@ +import { expect } from 'chai'; +import { spec } from 'modules/cosmosBidAdapter'; +import * as utils from 'src/utils'; +const constants = require('src/constants.json'); + +describe('Cosmos adapter', function () { + let bannerBidRequests; + let bannerBidResponse; + let videoBidRequests; + let videoBidResponse; + + beforeEach(function () { + bannerBidRequests = [ + { + bidder: 'cosmos', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]], + } + }, + params: { + publisherId: '1001', + currency: 'USD', + geo: { + lat: '09.5', + lon: '21.2', + } + }, + bidId: '29f8bd96defe76' + } + ]; + + videoBidRequests = + [ + { + mediaTypes: { + video: { + mimes: ['video/mp4', 'video/x-flv'], + context: 'instream' + } + }, + bidder: 'cosmos', + params: { + publisherId: 1001, + video: { + skippable: true, + minduration: 5, + maxduration: 30 + } + }, + bidId: '39f5cc6eff9b37' + } + ]; + + bannerBidResponse = { + 'body': { + 'id': '93D3BAD6-E2E2-49FB-9D89-920B1761C865', + 'seatbid': [{ + 'bid': [{ + 'id': '82DAAE22-FF66-4FAB-84AB-347B0C5CD02C', + 'impid': '29f8bd96defe76', + 'price': 1.858309, + 'adm': '

    COSMOS\"Connecting Advertisers and Publishers directly\"

    ', + 'adid': 'v55jutrh', + 'adomain': ['febreze.com'], + 'iurl': 'https://thetradedesk-t-general.s3.amazonaws.com/AdvertiserLogos/vgl908z.png', + 'cid': '1234', + 'crid': 'v55jutrh', + 'w': 300, + 'h': 250, + 'ext': { + 'prebid': { + 'type': 'banner' + } + } + }], + 'seat': 'zeta' + }] + } + }; + + videoBidResponse = { + 'body': { + 'id': '93D3BAD6-E2E2-49FB-9D89-920B1761C865', + 'seatbid': [{ + 'bid': [{ + 'id': '82DAAE22-FF66-4FAB-84AB-347B0C5CD02C', + 'impid': '39f5cc6eff9b37', + 'price': 0.858309, + 'adm': 'CosmosHQVAST 2.0 Instream Test 1VAST 2.0 Instream Test 1https://track.cosmoshq.com/event?data=%7B%22id%22%3A%221566011421045%22%2C%22bid%22%3A%2282DAAE22-FF66-4FAB-84AB-347B0C5CD02C%22%2C%22ts%22%3A%2220190817031021%22%2C%22pid%22%3A1001%2C%22plcid%22%3A1%2C%22aid%22%3A1%2C%22did%22%3A1%2C%22cid%22%3A%2222918%22%2C%22af%22%3A3%2C%22at%22%3A1%2C%22w%22%3A300%2C%22h%22%3A250%2C%22crid%22%3A%22v55jutrh%22%2C%22pp%22%3A0.858309%2C%22cp%22%3A0.858309%2C%22mg%22%3A0%7D&type=1http://track.dsp.impression.com/impression00:00:60http://sync.cosmoshq.com/static/video/SampleVideo_1280x720_10mb.mp4', + 'adid': 'v55jutrh', + 'adomain': ['febreze.com'], + 'iurl': 'https://thetradedesk-t-general.s3.amazonaws.com/AdvertiserLogos/vgl908z.png', + 'cid': '1234', + 'crid': 'v55jutrh', + 'w': 300, + 'h': 250, + 'ext': { + 'prebid': { + 'type': 'video' + } + } + }], + 'seat': 'zeta' + }] + } + }; + }); + + describe('isBidRequestValid', function () { + describe('validate the bid object: valid bid', function () { + it('valid bid case', function () { + let validBid = { + bidder: 'cosmos', + params: { + publisherId: 1001, + tagId: 1 + } + }, + isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(true); + }); + + it('validate the bid object: nil/empty bid object', function () { + let validBid = { + }, + isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(false); + }); + + it('validate the bid object: publisherId not passed', function () { + let validBid = { + bidder: 'cosmos', + params: { + tagId: 1 + } + }, + isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(false); + }); + + it('validate the bid object: publisherId is not number', function () { + let validBid = { + bidder: 'cosmos', + params: { + publisherId: '301', + tagId: 1 + } + }, + isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(false); + }); + + it('validate the bid object: mimes absent', function () { + let validBid = { + bidder: 'cosmos', + mediaTypes: { + video: {} + }, + params: { + publisherId: 1001 + } + }, + isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(false); + }); + + it('validate the bid object: mimes present', function () { + let validBid = { + bidder: 'cosmos', + mediaTypes: { + video: { + mimes: ['video/mp4', 'application/javascript'] + } + }, + params: { + publisherId: 1001 + } + }, + isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(true); + }); + + it('validate the bid object: tagId is not passed', function () { + let validBid = { + bidder: 'cosmos', + params: { + publisherId: 1001 + } + }, + isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(true); + }); + }); + + describe('buildRequests', function () { + it('build request object: buildRequests function should not modify original bannerBidRequests object', function () { + let originalBidRequests = utils.deepClone(bannerBidRequests); + let request = spec.buildRequests(bannerBidRequests); + expect(bannerBidRequests).to.deep.equal(originalBidRequests); + }); + + it('build request object: endpoint check', function () { + let request = spec.buildRequests(bannerBidRequests); + expect(request[0].url).to.equal('//bid.cosmoshq.com/openrtb2/bids'); + expect(request[0].method).to.equal('POST'); + }); + + it('build request object: request params check', function () { + let request = spec.buildRequests(bannerBidRequests); + let data = JSON.parse(request[0].data); + expect(data.site.publisher.id).to.equal(bannerBidRequests[0].params.publisherId); // publisher Id + expect(data.imp[0].bidfloorcur).to.equal(bannerBidRequests[0].params.currency); + }); + + it('build request object: request params check without tagId', function () { + delete bannerBidRequests[0].params.tagId; + let request = spec.buildRequests(bannerBidRequests); + let data = JSON.parse(request[0].data); + expect(data.site.publisher.id).to.equal(bannerBidRequests[0].params.publisherId); // publisher Id + expect(data.imp[0].tagid).to.equal(undefined); // tagid + expect(data.imp[0].bidfloorcur).to.equal(bannerBidRequests[0].params.currency); + }); + + it('build request object: request params multi size format object check', function () { + let bidRequest = [ + { + bidder: 'cosmos', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]], + } + }, + params: { + publisherId: 1001, + currency: 'USD' + } + } + ]; + /* case 1 - size passed in adslot */ + let request = spec.buildRequests(bidRequest); + let data = JSON.parse(request[0].data); + expect(data.imp[0].banner.w).to.equal(300); // width + expect(data.imp[0].banner.h).to.equal(250); // height + + /* case 2 - size passed in adslot as well as in sizes array */ + bidRequest[0].sizes = [[300, 600], [300, 250]]; + bidRequest[0].mediaTypes = { + banner: { + sizes: [[300, 600], [300, 250]] + } + }; + request = spec.buildRequests(bidRequest); + data = JSON.parse(request[0].data); + + expect(data.imp[0].banner.w).to.equal(300); // width + expect(data.imp[0].banner.h).to.equal(600); // height + + /* case 3 - size passed in sizes but not in adslot */ + bidRequest[0].params.tagId = 1; + bidRequest[0].sizes = [[300, 250], [300, 600]]; + bidRequest[0].mediaTypes = { + banner: { + sizes: [[300, 250], [300, 600]] + } + }; + request = spec.buildRequests(bidRequest); + data = JSON.parse(request[0].data); + + expect(data.imp[0].banner.w).to.equal(300); // width + expect(data.imp[0].banner.h).to.equal(250); // height + expect(data.imp[0].banner.format).exist.and.to.be.an('array'); + expect(data.imp[0].banner.format[0]).exist.and.to.be.an('object'); + expect(data.imp[0].banner.format[0].w).to.equal(300); // width + expect(data.imp[0].banner.format[0].h).to.equal(250); // height + }); + + it('build request object: request params currency check', function () { + let bidRequest = [ + { + bidder: 'cosmos', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]], + } + }, + params: { + publisherId: 1001, + tagId: 1, + currency: 'USD' + }, + sizes: [[300, 250], [300, 600]] + } + ]; + + /* case 1 - + currency specified in adunits + output: imp[0] use currency specified in bannerBidRequests[0].params.currency + + */ + let request = spec.buildRequests(bidRequest); + let data = JSON.parse(request[0].data); + expect(data.imp[0].bidfloorcur).to.equal(bidRequest[0].params.currency); + + /* case 2 - + currency specified in adunit + output: imp[0] use default currency - USD + + */ + delete bidRequest[0].params.currency; + request = spec.buildRequests(bidRequest); + data = JSON.parse(request[0].data); + expect(data.imp[0].bidfloorcur).to.equal('USD'); + }); + + it('build request object: request params check for video ad', function () { + let request = spec.buildRequests(videoBidRequests); + let data = JSON.parse(request[0].data); + expect(data.imp[0].video).to.exist; + expect(data.imp[0]['video']['mimes']).to.exist.and.to.be.an('array'); + expect(data.imp[0]['video']['mimes'][0]).to.equal(videoBidRequests[0].mediaTypes.video['mimes'][0]); + expect(data.imp[0]['video']['mimes'][1]).to.equal(videoBidRequests[0].mediaTypes.video['mimes'][1]); + expect(data.imp[0]['video']['minduration']).to.equal(videoBidRequests[0].params.video['minduration']); + expect(data.imp[0]['video']['maxduration']).to.equal(videoBidRequests[0].params.video['maxduration']); + }); + + describe('interpretResponse', function () { + it('check for banner response', function () { + let request = spec.buildRequests(bannerBidRequests); + let data = JSON.parse(request[0].data); + let response = spec.interpretResponse(bannerBidResponse, request[0]); + expect(response).to.be.an('array').with.length.above(0); + expect(response[0].requestId).to.equal(bannerBidResponse.body.seatbid[0].bid[0].impid); + expect(response[0].cpm).to.equal((bannerBidResponse.body.seatbid[0].bid[0].price).toFixed(2)); + expect(response[0].width).to.equal(bannerBidResponse.body.seatbid[0].bid[0].w); + expect(response[0].height).to.equal(bannerBidResponse.body.seatbid[0].bid[0].h); + if (bannerBidResponse.body.seatbid[0].bid[0].crid) { + expect(response[0].creativeId).to.equal(bannerBidResponse.body.seatbid[0].bid[0].crid); + } else { + expect(response[0].creativeId).to.equal(bannerBidResponse.body.seatbid[0].bid[0].id); + } + expect(response[0].dealId).to.equal(bannerBidResponse.body.seatbid[0].bid[0].dealid); + expect(response[0].currency).to.equal('USD'); + expect(response[0].netRevenue).to.equal(false); + expect(response[0].ttl).to.equal(300); + }); + it('check for video response', function () { + let request = spec.buildRequests(videoBidRequests); + let data = JSON.parse(request[0].data); + let response = spec.interpretResponse(videoBidResponse, request[0]); + }); + }); + }); + }); +}); diff --git a/test/spec/modules/coxBidAdapter_spec.js b/test/spec/modules/coxBidAdapter_spec.js deleted file mode 100644 index 8d8b29ed4d7..00000000000 --- a/test/spec/modules/coxBidAdapter_spec.js +++ /dev/null @@ -1,233 +0,0 @@ -import { expect } from 'chai'; -import { spec } from 'modules/coxBidAdapter'; -import { newBidder } from 'src/adapters/bidderFactory'; -import { deepClone } from 'src/utils'; - -describe('CoxBidAdapter', function () { - const adapter = newBidder(spec); - - describe('isBidRequestValid', function () { - const CONFIG = { - 'bidder': 'cox', - 'params': { - 'id': '8888', - 'siteId': '1000', - 'size': '300x250' - } - }; - - it('should return true when required params present', function () { - expect(spec.isBidRequestValid(CONFIG)).to.equal(true); - }); - - it('should return false when id param is missing', function () { - let config = deepClone(CONFIG); - config.params.id = null; - - expect(spec.isBidRequestValid(config)).to.equal(false); - }); - - it('should return false when size param is missing', function () { - let config = deepClone(CONFIG); - config.params.size = null; - - expect(spec.isBidRequestValid(config)).to.equal(false); - }); - }); - - describe('buildRequests', function () { - const PROD_DOMAIN = 'ad.afy11.net'; - const PPE_DOMAIN = 'ppe-ad.afy11.net'; - const STG_DOMAIN = 'staging-ad.afy11.net'; - - const BID_INFO = [{ - 'bidder': 'cox', - 'params': { - 'id': '8888', - 'siteId': '1000', - 'size': '300x250' - }, - 'sizes': [[300, 250]], - 'transactionId': 'tId-foo', - 'bidId': 'bId-bar' - }]; - - it('should send bid request to PROD_DOMAIN via GET', function () { - let request = spec.buildRequests(BID_INFO); - expect(request.url).to.have.string(PROD_DOMAIN); - expect(request.method).to.equal('GET'); - }); - - it('should send bid request to PPE_DOMAIN when configured', function () { - let clone = deepClone(BID_INFO); - clone[0].params.env = 'PPE'; - - let request = spec.buildRequests(clone); - expect(request.url).to.have.string(PPE_DOMAIN); - }); - - it('should send bid request to STG_DOMAIN when configured', function () { - let clone = deepClone(BID_INFO); - clone[0].params.env = 'STG'; - - let request = spec.buildRequests(clone); - expect(request.url).to.have.string(STG_DOMAIN); - }); - - it('should return empty when id is invalid', function () { - let clone = deepClone(BID_INFO); - clone[0].params.id = null; - - let request = spec.buildRequests(clone); - expect(request).to.be.an('object').that.is.empty; - }); - - it('should return empty when size is invalid', function () { - let clone = deepClone(BID_INFO); - clone[0].params.size = 'FOO'; - - let request = spec.buildRequests(clone); - expect(request).to.be.an('object').that.is.empty; - }); - }) - - describe('interpretResponse', function () { - const BID_INFO_1 = [{ - 'bidder': 'cox', - 'params': { - 'id': '2000005657007', - 'siteId': '2000101880180', - 'size': '728x90' - }, - 'transactionId': 'foo_1', - 'bidId': 'bar_1' - }]; - - const BID_INFO_2 = [{ - 'bidder': 'cox', - 'params': { - 'id': '2000005658887', - 'siteId': '2000101880180', - 'size': '300x250' - }, - 'transactionId': 'foo_2', - 'bidId': 'bar_2' - }]; - - const RESPONSE_1 = { body: { - 'zones': { - 'as2000005657007': { - 'price': 1.88, - 'dealid': 'AA128460', - 'ad': '

    2000005657007
    728x90

    ', - 'adid': '7007-728-90' - }}}}; - - const RESPONSE_2 = { body: { - 'zones': { - 'as2000005658887': { - 'price': 2.88, - 'ad': '

    2000005658887
    300x250

    ', - 'adid': '888-88' - }}}}; - - const PBJS_BID_1 = { - 'requestId': 'bar_1', - 'cpm': 1.88, - 'width': '728', - 'height': '90', - 'creativeId': '7007-728-90', - 'dealId': 'AA128460', - 'currency': 'USD', - 'netRevenue': true, - 'ttl': 300, - 'ad': '

    2000005657007
    728x90

    ' - }; - - const PBJS_BID_2 = { - 'requestId': 'bar_2', - 'cpm': 2.88, - 'width': '300', - 'height': '250', - 'creativeId': '888-88', - 'dealId': undefined, - 'currency': 'USD', - 'netRevenue': true, - 'ttl': 300, - 'ad': '

    2000005658887
    300x250

    ' - }; - - it('should return correct pbjs bid', function () { - let result = spec.interpretResponse(RESPONSE_2, spec.buildRequests(BID_INFO_2)); - expect(result[0]).to.eql(PBJS_BID_2); - }); - - it('should handle multiple bid instances', function () { - let request1 = spec.buildRequests(BID_INFO_1); - let request2 = spec.buildRequests(BID_INFO_2); - - let result2 = spec.interpretResponse(RESPONSE_2, request2); - expect(result2[0]).to.eql(PBJS_BID_2); - - let result1 = spec.interpretResponse(RESPONSE_1, request1); - expect(result1[0]).to.eql(PBJS_BID_1); - }); - - it('should return empty when price is zero', function () { - let clone = deepClone(RESPONSE_1); - clone.body.zones.as2000005657007.price = 0; - - let result = spec.interpretResponse(clone, spec.buildRequests(BID_INFO_1)); - expect(result).to.be.an('array').that.is.empty; - }); - - it('should return empty when there is no ad', function () { - let clone = deepClone(RESPONSE_1); - clone.body.zones.as2000005657007.ad = null; - - let result = spec.interpretResponse(clone, spec.buildRequests(BID_INFO_1)); - expect(result).to.be.an('array').that.is.empty; - }); - - it('should return empty when there is no ad unit info', function () { - let clone = deepClone(RESPONSE_1); - delete (clone.body.zones.as2000005657007); - - let result = spec.interpretResponse(clone, spec.buildRequests(BID_INFO_1)); - expect(result).to.be.an('array').that.is.empty; - }); - }); - - describe('getUserSyncs', function () { - const RESPONSE = [{ body: { - 'zones': {}, - 'tpCookieSync': ['http://pixel.foo.com/', 'http://pixel.bar.com/'] - }}]; - - it('should return correct pbjs syncs when pixels are enabled', function () { - let syncs = spec.getUserSyncs({ pixelEnabled: true }, RESPONSE); - - expect(syncs.map(x => x.type)).to.eql(['image', 'image']); - expect(syncs.map(x => x.url)).to.have.members(['http://pixel.bar.com/', 'http://pixel.foo.com/']); - }); - - it('should return empty when pixels are not enabled', function () { - let syncs = spec.getUserSyncs({ pixelEnabled: false }, RESPONSE); - - expect(syncs).to.be.an('array').that.is.empty; - }); - - it('should return empty when response has no sync data', function () { - let clone = deepClone(RESPONSE); - delete (clone[0].body.tpCookieSync); - - let syncs = spec.getUserSyncs({ pixelEnabled: true }, clone); - expect(syncs).to.be.an('array').that.is.empty; - }); - - it('should return empty when response is empty', function () { - let syncs = spec.getUserSyncs({ pixelEnabled: true }, [{}]); - expect(syncs).to.be.an('array').that.is.empty; - }); - }); -}); diff --git a/test/spec/modules/cpmstarBidAdapter_spec.js b/test/spec/modules/cpmstarBidAdapter_spec.js new file mode 100755 index 00000000000..3dd06a484d9 --- /dev/null +++ b/test/spec/modules/cpmstarBidAdapter_spec.js @@ -0,0 +1,158 @@ +import { expect } from 'chai'; +import { spec } from 'modules/cpmstarBidAdapter'; +import { deepClone } from 'src/utils'; + +describe('Cpmstar Bid Adapter', function () { + describe('isBidRequestValid', function () { + it('should return true since the bid is valid', + function () { + var bid = { params: { placementId: 123456 } }; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }) + + it('should return false since the bid is invalid', function () { + var bid = { params: { placementId: '' } }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }) + + it('should return a valid player size', function() { + var bid = { mediaTypes: { + video: { + playerSize: [[960, 540]] + } + }} + expect(spec.getPlayerSize(bid)[0]).to.equal(960); + expect(spec.getPlayerSize(bid)[1]).to.equal(540); + }) + + it('should return a default player size', function() { + var bid = { mediaTypes: { + video: { + playerSize: null + } + }} + expect(spec.getPlayerSize(bid)[0]).to.equal(640); + expect(spec.getPlayerSize(bid)[1]).to.equal(440); + }) + }); + + describe('buildRequests', function () { + const valid_bid_requests = [{ + 'bidder': 'cpmstar', + 'params': { + 'placementId': '57' + }, + 'sizes': [[300, 250]], + 'bidId': 'bidId' + }]; + + const bidderRequest = { + refererInfo: { + referer: 'referer', + reachedTop: false, + } + + }; + + it('should produce a valid production request', function () { + var requests = spec.buildRequests(valid_bid_requests, bidderRequest); + expect(requests[0]).to.have.property('method'); + expect(requests[0]).to.have.property('url'); + expect(requests[0]).to.have.property('bidRequest'); + expect(requests[0].url).to.include('//server.cpmstar.com/view.aspx'); + }); + it('should produce a valid staging request', function () { + var stgReq = deepClone(valid_bid_requests); + stgReq[0].params.endpoint = 'staging'; + var requests = spec.buildRequests(stgReq, bidderRequest); + expect(requests[0]).to.have.property('method'); + expect(requests[0]).to.have.property('url'); + expect(requests[0]).to.have.property('bidRequest'); + expect(requests[0].url).to.include('//staging.server.cpmstar.com/view.aspx'); + }); + it('should produce a valid dev request', function () { + var devReq = deepClone(valid_bid_requests); + devReq[0].params.endpoint = 'dev'; + var requests = spec.buildRequests(devReq, bidderRequest); + expect(requests[0]).to.have.property('method'); + expect(requests[0]).to.have.property('url'); + expect(requests[0]).to.have.property('bidRequest'); + expect(requests[0].url).to.include('//dev.server.cpmstar.com/view.aspx'); + }); + }) + + describe('interpretResponse', function () { + const request = { + bidRequest: { + mediaType: 'BANNER' + } + }; + const serverResponse = { + body: [{ + creatives: [{ + cpm: 1, + width: 0, + height: 0, + currency: 'USD', + netRevenue: true, + ttl: 1, + creativeid: '1234', + requestid: '11123', + code: 'no idea', + media: 'banner', + } + ], + }] + }; + + it('should return a valid bidresponse array', function () { + var r = spec.interpretResponse(serverResponse, request) + var c = serverResponse.body[0].creatives[0]; + expect(r[0].length).to.not.equal(0); + expect(r[0].requestId).equal(c.requestid); + expect(r[0].creativeId).equal(c.creativeid); + expect(r[0].cpm).equal(c.cpm); + expect(r[0].width).equal(c.width); + expect(r[0].height).equal(c.height); + expect(r[0].currency).equal(c.currency); + expect(r[0].netRevenue).equal(c.netRevenue); + expect(r[0].ttl).equal(c.ttl); + expect(r[0].ad).equal(c.code); + }); + + it('should return a valid bidresponse array from a non-array-body', function () { + var r = spec.interpretResponse({ body: serverResponse.body[0] }, request) + var c = serverResponse.body[0].creatives[0]; + expect(r[0].length).to.not.equal(0); + expect(r[0].requestId).equal(c.requestid); + expect(r[0].creativeId).equal(c.creativeid); + expect(r[0].cpm).equal(c.cpm); + expect(r[0].width).equal(c.width); + expect(r[0].height).equal(c.height); + expect(r[0].currency).equal(c.currency); + expect(r[0].netRevenue).equal(c.netRevenue); + expect(r[0].ttl).equal(c.ttl); + expect(r[0].ad).equal(c.code); + }); + + it('should return undefined due to an invalid cpm value', function () { + var badServer = deepClone(serverResponse); + badServer.body[0].creatives[0].cpm = 0; + var c = spec.interpretResponse(badServer, request); + expect(c).to.be.undefined; + }); + + it('should return undefined due to a bad response', function () { + var badServer = deepClone(serverResponse); + badServer.body[0].creatives[0].code = null; + var c = spec.interpretResponse(badServer, request); + expect(c).to.be.undefined; + }); + + it('should return a valid response with a dealId', function () { + var dealServer = deepClone(serverResponse); + dealServer.body[0].creatives[0].dealId = 'deal'; + expect(spec.interpretResponse(dealServer, request)[0].dealId).to.equal('deal'); + }); + }); +}); diff --git a/test/spec/modules/criteoBidAdapter_spec.js b/test/spec/modules/criteoBidAdapter_spec.js index e232bf0e3d9..e5d789ff60d 100755 --- a/test/spec/modules/criteoBidAdapter_spec.js +++ b/test/spec/modules/criteoBidAdapter_spec.js @@ -1,8 +1,25 @@ import { expect } from 'chai'; -import { spec } from 'modules/criteoBidAdapter'; +import { tryGetCriteoFastBid, spec, PROFILE_ID_PUBLISHERTAG, ADAPTER_VERSION, PUBLISHER_TAG_URL } from 'modules/criteoBidAdapter'; +import { createBid } from 'src/bidfactory'; +import CONSTANTS from 'src/constants.json'; import * as utils from 'src/utils'; +import { config } from '../../../src/config'; +import { VIDEO } from '../../../src/mediaTypes'; describe('The Criteo bidding adapter', function () { + let utilsMock; + + beforeEach(function () { + // Remove FastBid to avoid side effects + localStorage.removeItem('criteo_fast_bid'); + utilsMock = sinon.mock(utils); + }); + + afterEach(function() { + global.Criteo = undefined; + utilsMock.restore(); + }); + describe('isBidRequestValid', function () { it('should return false when given an invalid bid', function () { const bid = { @@ -45,10 +62,318 @@ describe('The Criteo bidding adapter', function () { const isValid = spec.isBidRequestValid(bid); expect(isValid).to.equal(true); }); + + it('should return true when given a valid video bid request', function () { + expect(spec.isBidRequestValid({ + bidder: 'criteo', + mediaTypes: { + video: { + context: 'instream', + mimes: ['video/mpeg'], + playerSize: [640, 480], + protocols: [5, 6], + maxduration: 30, + api: [1, 2] + } + }, + params: { + networkId: 456, + video: { + skip: 1, + placement: 1, + playbackmethod: 1 + } + }, + })).to.equal(true); + + expect(spec.isBidRequestValid({ + bidder: 'criteo', + mediaTypes: { + video: { + context: 'outstream', + mimes: ['video/mpeg'], + playerSize: [640, 480], + protocols: [5, 6], + maxduration: 30, + api: [1, 2] + } + }, + params: { + networkId: 456, + video: { + skip: 1, + placement: 2, + playbackmethod: 1 + } + }, + })).to.equal(true); + }); + + it('should return false when given an invalid video bid request', function () { + expect(spec.isBidRequestValid({ + bidder: 'criteo', + mediaTypes: { + video: { + mimes: ['video/mpeg'], + playerSize: [640, 480], + protocols: [5, 6], + maxduration: 30, + api: [1, 2] + } + }, + params: { + networkId: 456, + video: { + skip: 1, + placement: 1, + playbackmethod: 1 + } + }, + })).to.equal(false); + + expect(spec.isBidRequestValid({ + bidder: 'criteo', + mediaTypes: { + video: { + context: 'instream', + mimes: ['video/mpeg'], + playerSize: [640, 480], + protocols: [5, 6], + maxduration: 30, + api: [1, 2] + } + }, + params: { + networkId: 456, + video: { + skip: 1, + placement: 2, + playbackmethod: 1 + } + }, + })).to.equal(false); + + expect(spec.isBidRequestValid({ + bidder: 'criteo', + mediaTypes: { + video: { + context: 'outstream', + mimes: ['video/mpeg'], + playerSize: [640, 480], + protocols: [5, 6], + maxduration: 30, + api: [1, 2] + } + }, + params: { + networkId: 456, + video: { + skip: 1, + placement: 1, + playbackmethod: 1 + } + }, + })).to.equal(false); + + expect(spec.isBidRequestValid({ + bidder: 'criteo', + mediaTypes: { + video: { + context: 'adpod', + mimes: ['video/mpeg'], + playerSize: [640, 480], + protocols: [5, 6], + maxduration: 30, + api: [1, 2] + } + }, + params: { + networkId: 456, + video: { + skip: 1, + placement: 1, + playbackmethod: 1 + } + }, + })).to.equal(false); + + expect(spec.isBidRequestValid({ + bidder: 'criteo', + mediaTypes: { + video: { + context: 'instream', + playerSize: [640, 480], + protocols: [5, 6], + maxduration: 30, + api: [1, 2] + } + }, + params: { + networkId: 456, + video: { + skip: 1, + placement: 1, + playbackmethod: 1 + } + }, + })).to.equal(false); + + expect(spec.isBidRequestValid({ + bidder: 'criteo', + mediaTypes: { + video: { + context: 'instream', + mimes: ['video/mpeg'], + protocols: [5, 6], + maxduration: 30, + api: [1, 2] + } + }, + params: { + networkId: 456, + video: { + skip: 1, + placement: 1, + playbackmethod: 1 + } + }, + })).to.equal(false); + + expect(spec.isBidRequestValid({ + bidder: 'criteo', + mediaTypes: { + video: { + context: 'instream', + mimes: ['video/mpeg'], + playerSize: [640, 480], + maxduration: 30, + api: [1, 2] + } + }, + params: { + networkId: 456, + video: { + skip: 1, + placement: 1, + playbackmethod: 1 + } + }, + })).to.equal(false); + + expect(spec.isBidRequestValid({ + bidder: 'criteo', + mediaTypes: { + video: { + context: 'instream', + mimes: ['video/mpeg'], + playerSize: [640, 480], + protocols: [5, 6], + api: [1, 2] + } + }, + params: { + networkId: 456, + video: { + skip: 1, + placement: 1, + playbackmethod: 1 + } + }, + })).to.equal(false); + + expect(spec.isBidRequestValid({ + bidder: 'criteo', + mediaTypes: { + video: { + context: 'instream', + mimes: ['video/mpeg'], + playerSize: [640, 480], + protocols: [5, 6], + maxduration: 30 + } + }, + params: { + networkId: 456, + video: { + skip: 1, + placement: 1, + playbackmethod: 1 + } + }, + })).to.equal(false); + + expect(spec.isBidRequestValid({ + bidder: 'criteo', + mediaTypes: { + video: { + context: 'instream', + mimes: ['video/mpeg'], + playerSize: [640, 480], + protocols: [5, 6], + maxduration: 30, + api: [1, 2] + } + }, + params: { + networkId: 456, + video: { + placement: 1, + playbackmethod: 1 + } + }, + })).to.equal(false); + + expect(spec.isBidRequestValid({ + bidder: 'criteo', + mediaTypes: { + video: { + context: 'instream', + mimes: ['video/mpeg'], + playerSize: [640, 480], + protocols: [5, 6], + maxduration: 30, + api: [1, 2] + } + }, + params: { + networkId: 456, + video: { + skip: 1, + playbackmethod: 1 + } + }, + })).to.equal(false); + + expect(spec.isBidRequestValid({ + bidder: 'criteo', + mediaTypes: { + video: { + context: 'instream', + mimes: ['video/mpeg'], + playerSize: [640, 480], + protocols: [5, 6], + maxduration: 30, + api: [1, 2] + } + }, + params: { + networkId: 456, + video: { + skip: 1, + placement: 1 + } + }, + })).to.equal(false); + }); }); describe('buildRequests', function () { - const bidderRequest = { timeout: 3000, + const refererUrl = 'https://criteo.com?pbt_debug=1&pbt_nolog=1'; + const bidderRequest = { + refererInfo: { + referer: refererUrl + }, + timeout: 3000, gdprConsent: { gdprApplies: 1, consentString: 'concentDataString', @@ -60,6 +385,26 @@ describe('The Criteo bidding adapter', function () { }, }; + afterEach(function () { + config.resetConfig(); + }); + + it('should properly build a request if refererInfo is not provided', function () { + const bidderRequest = {}; + const bidRequests = [ + { + bidder: 'criteo', + adUnitCode: 'bid-123', + transactionId: 'transaction-123', + sizes: [[728, 90]], + params: {} + }, + ]; + const request = spec.buildRequests(bidRequests, bidderRequest); + const ortbRequest = request.data; + expect(ortbRequest.publisher.url).to.equal(''); + }); + it('should properly build a zoneId request', function () { const bidRequests = [ { @@ -69,14 +414,17 @@ describe('The Criteo bidding adapter', function () { sizes: [[728, 90]], params: { zoneId: 123, + publisherSubId: '123', + nativeCallback: function() {}, + integrationMode: 'amp' }, }, ]; const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.url).to.match(/^\/\/bidder\.criteo\.com\/cdb\?profileId=207&av=\d+&wv=[^&]+&cb=\d/); + expect(request.url).to.match(/^https:\/\/bidder\.criteo\.com\/cdb\?profileId=207&av=\d+&wv=[^&]+&cb=\d+&im=1&debug=1&nolog=1/); expect(request.method).to.equal('POST'); const ortbRequest = request.data; - expect(ortbRequest.publisher.url).to.equal(utils.getTopWindowUrl()); + expect(ortbRequest.publisher.url).to.equal(refererUrl); expect(ortbRequest.slots).to.have.lengthOf(1); expect(ortbRequest.slots[0].impid).to.equal('bid-123'); expect(ortbRequest.slots[0].transactionid).to.equal('transaction-123'); @@ -90,6 +438,9 @@ describe('The Criteo bidding adapter', function () { it('should properly build a networkId request', function () { const bidderRequest = { + refererInfo: { + referer: refererUrl + }, timeout: 3000, gdprConsent: { gdprApplies: 0, @@ -106,17 +457,21 @@ describe('The Criteo bidding adapter', function () { bidder: 'criteo', adUnitCode: 'bid-123', transactionId: 'transaction-123', - sizes: [[300, 250], [728, 90]], + mediaTypes: { + banner: { + sizes: [[300, 250], [728, 90]] + } + }, params: { networkId: 456, }, }, ]; const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.url).to.match(/^\/\/bidder\.criteo\.com\/cdb\?profileId=207&av=\d+&wv=[^&]+&cb=\d/); + expect(request.url).to.match(/^https:\/\/bidder\.criteo\.com\/cdb\?profileId=207&av=\d+&wv=[^&]+&cb=\d/); expect(request.method).to.equal('POST'); const ortbRequest = request.data; - expect(ortbRequest.publisher.url).to.equal(utils.getTopWindowUrl()); + expect(ortbRequest.publisher.url).to.equal(refererUrl); expect(ortbRequest.publisher.networkid).to.equal(456); expect(ortbRequest.slots).to.have.lengthOf(1); expect(ortbRequest.slots[0].impid).to.equal('bid-123'); @@ -130,7 +485,12 @@ describe('The Criteo bidding adapter', function () { }); it('should properly build a mixed request', function () { - const bidderRequest = { timeout: 3000 }; + const bidderRequest = { + refererInfo: { + referer: refererUrl + }, + timeout: 3000 + }; const bidRequests = [ { bidder: 'criteo', @@ -152,10 +512,10 @@ describe('The Criteo bidding adapter', function () { }, ]; const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.url).to.match(/^\/\/bidder\.criteo\.com\/cdb\?profileId=207&av=\d+&wv=[^&]+&cb=\d/); + expect(request.url).to.match(/^https:\/\/bidder\.criteo\.com\/cdb\?profileId=207&av=\d+&wv=[^&]+&cb=\d/); expect(request.method).to.equal('POST'); const ortbRequest = request.data; - expect(ortbRequest.publisher.url).to.equal(utils.getTopWindowUrl()); + expect(ortbRequest.publisher.url).to.equal(refererUrl); expect(ortbRequest.publisher.networkid).to.equal(456); expect(ortbRequest.slots).to.have.lengthOf(2); expect(ortbRequest.slots[0].impid).to.equal('bid-123'); @@ -182,9 +542,9 @@ describe('The Criteo bidding adapter', function () { }, }, ]; - const bidderRequest = { timeout: 3000, - gdprConsent: { - }, + const bidderRequest = { + timeout: 3000, + gdprConsent: {}, }; const ortbRequest = spec.buildRequests(bidRequests, bidderRequest).data; @@ -192,6 +552,116 @@ describe('The Criteo bidding adapter', function () { expect(ortbRequest.gdprConsent.gdprApplies).to.equal(undefined); expect(ortbRequest.gdprConsent.consentGiven).to.equal(undefined); }); + + it('should properly build a video request', function () { + const bidRequests = [ + { + bidder: 'criteo', + adUnitCode: 'bid-123', + transactionId: 'transaction-123', + sizes: [[728, 90]], + mediaTypes: { + video: { + playerSize: [640, 480], + mimes: ['video/mp4', 'video/x-flv'], + maxduration: 30, + api: [1, 2], + protocols: [2, 3] + } + }, + params: { + zoneId: 123, + video: { + skip: 1, + minduration: 5, + startdelay: 5, + playbackmethod: [1, 3], + placement: 2 + } + }, + }, + ]; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.url).to.match(/^https:\/\/bidder\.criteo\.com\/cdb\?profileId=207&av=\d+&wv=[^&]+&cb=\d/); + expect(request.method).to.equal('POST'); + const ortbRequest = request.data; + expect(ortbRequest.slots[0].video.mimes).to.deep.equal(['video/mp4', 'video/x-flv']); + expect(ortbRequest.slots[0].video.playersizes).to.deep.equal(['640x480']); + expect(ortbRequest.slots[0].video.maxduration).to.equal(30); + expect(ortbRequest.slots[0].video.api).to.deep.equal([1, 2]); + expect(ortbRequest.slots[0].video.protocols).to.deep.equal([2, 3]); + expect(ortbRequest.slots[0].video.skip).to.equal(1); + expect(ortbRequest.slots[0].video.minduration).to.equal(5); + expect(ortbRequest.slots[0].video.startdelay).to.equal(5); + expect(ortbRequest.slots[0].video.playbackmethod).to.deep.equal([1, 3]); + expect(ortbRequest.slots[0].video.placement).to.equal(2); + }); + + it('should properly build a video request with more than one player size', function () { + const bidRequests = [ + { + bidder: 'criteo', + adUnitCode: 'bid-123', + transactionId: 'transaction-123', + sizes: [[728, 90]], + mediaTypes: { + video: { + playerSize: [[640, 480], [800, 600]], + mimes: ['video/mp4', 'video/x-flv'], + maxduration: 30, + api: [1, 2], + protocols: [2, 3] + } + }, + params: { + zoneId: 123, + video: { + skip: 1, + minduration: 5, + startdelay: 5, + playbackmethod: [1, 3], + placement: 2 + } + }, + }, + ]; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.url).to.match(/^https:\/\/bidder\.criteo\.com\/cdb\?profileId=207&av=\d+&wv=[^&]+&cb=\d/); + expect(request.method).to.equal('POST'); + const ortbRequest = request.data; + expect(ortbRequest.slots[0].video.mimes).to.deep.equal(['video/mp4', 'video/x-flv']); + expect(ortbRequest.slots[0].video.playersizes).to.deep.equal(['640x480', '800x600']); + expect(ortbRequest.slots[0].video.maxduration).to.equal(30); + expect(ortbRequest.slots[0].video.api).to.deep.equal([1, 2]); + expect(ortbRequest.slots[0].video.protocols).to.deep.equal([2, 3]); + expect(ortbRequest.slots[0].video.skip).to.equal(1); + expect(ortbRequest.slots[0].video.minduration).to.equal(5); + expect(ortbRequest.slots[0].video.startdelay).to.equal(5); + expect(ortbRequest.slots[0].video.playbackmethod).to.deep.equal([1, 3]); + expect(ortbRequest.slots[0].video.placement).to.equal(2); + }); + + it('should properly build a request with ceh', function () { + const bidRequests = [ + { + bidder: 'criteo', + adUnitCode: 'bid-123', + transactionId: 'transaction-123', + sizes: [[728, 90]], + params: { + zoneId: 123, + }, + }, + ]; + config.setConfig({ + criteo: { + ceh: 'hashedemail' + } + }); + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.user).to.not.be.null; + expect(request.data.user.ceh).to.equal('hashedemail'); + }); }); describe('interpretResponse', function () { @@ -211,6 +681,7 @@ describe('The Criteo bidding adapter', function () { creative: 'test-ad', width: 728, height: 90, + dealCode: 'myDealCode', }], }, }; @@ -230,6 +701,7 @@ describe('The Criteo bidding adapter', function () { expect(bids[0].ad).to.equal('test-ad'); expect(bids[0].width).to.equal(728); expect(bids[0].height).to.equal(90); + expect(bids[0].dealId).to.equal('myDealCode'); }); it('should properly parse a bid responsewith with a zoneId', function () { @@ -237,6 +709,7 @@ describe('The Criteo bidding adapter', function () { body: { slots: [{ impid: 'test-requestId', + bidId: 'abc123', cpm: 1.23, creative: 'test-ad', width: 728, @@ -257,12 +730,46 @@ describe('The Criteo bidding adapter', function () { const bids = spec.interpretResponse(response, request); expect(bids).to.have.lengthOf(1); expect(bids[0].requestId).to.equal('test-bidId'); + expect(bids[0].adId).to.equal('abc123'); expect(bids[0].cpm).to.equal(1.23); expect(bids[0].ad).to.equal('test-ad'); expect(bids[0].width).to.equal(728); expect(bids[0].height).to.equal(90); }); + it('should properly parse a bid responsewith with a video', function () { + const response = { + body: { + slots: [{ + impid: 'test-requestId', + bidId: 'abc123', + cpm: 1.23, + displayurl: 'http://test-ad', + width: 728, + height: 90, + zoneid: 123, + video: true + }], + }, + }; + const request = { + bidRequests: [{ + adUnitCode: 'test-requestId', + bidId: 'test-bidId', + params: { + zoneId: 123, + }, + }] + }; + const bids = spec.interpretResponse(response, request); + expect(bids).to.have.lengthOf(1); + expect(bids[0].requestId).to.equal('test-bidId'); + expect(bids[0].adId).to.equal('abc123'); + expect(bids[0].cpm).to.equal(1.23); + expect(bids[0].vastUrl).to.equal('http://test-ad'); + expect(bids[0].mediaType).to.equal(VIDEO); + }); + it('should properly parse a bid responsewith with a zoneId passed as a string', function () { const response = { body: { @@ -293,5 +800,223 @@ describe('The Criteo bidding adapter', function () { expect(bids[0].width).to.equal(728); expect(bids[0].height).to.equal(90); }); + + it('should generate unique adIds if none are returned by the endpoint', function () { + const response = { + body: { + slots: [{ + impid: 'test-requestId', + cpm: 1.23, + creative: 'test-ad', + width: 300, + height: 250, + }, { + impid: 'test-requestId', + cpm: 4.56, + creative: 'test-ad', + width: 728, + height: 90, + }], + }, + }; + const request = { + bidRequests: [{ + adUnitCode: 'test-requestId', + bidId: 'test-bidId', + sizes: [[300, 250], [728, 90]], + params: { + networkId: 456, + } + }] + }; + const bids = spec.interpretResponse(response, request); + expect(bids).to.have.lengthOf(2); + const prebidBids = bids.map(bid => Object.assign(createBid(CONSTANTS.STATUS.GOOD, request.bidRequests[0]), bid)); + expect(prebidBids[0].adId).to.not.equal(prebidBids[1].adId); + }); + }); + + describe('tryGetCriteoFastBid', function () { + const VALID_HASH = 'vBeD8Q7GU6lypFbzB07W8hLGj7NL+p7dI9ro2tCxkrmyv0F6stNuoNd75Us33iNKfEoW+cFWypelr6OJPXxki2MXWatRhJuUJZMcK4VBFnxi3Ro+3a0xEfxE4jJm4eGe98iC898M+/YFHfp+fEPEnS6pEyw124ONIFZFrcejpHU='; + const INVALID_HASH = 'invalid'; + const VALID_PUBLISHER_TAG = 'test'; + const INVALID_PUBLISHER_TAG = 'test invalid'; + + const FASTBID_LOCAL_STORAGE_KEY = 'criteo_fast_bid'; + + it('should verify valid hash with valid publisher tag', function () { + localStorage.setItem(FASTBID_LOCAL_STORAGE_KEY, '// Hash: ' + VALID_HASH + '\n' + VALID_PUBLISHER_TAG); + + utilsMock.expects('logInfo').withExactArgs('Using Criteo FastBid').once(); + utilsMock.expects('logWarn').withExactArgs('No hash found in FastBid').never(); + utilsMock.expects('logWarn').withExactArgs('Invalid Criteo FastBid found').never(); + + tryGetCriteoFastBid(); + + expect(localStorage.getItem(FASTBID_LOCAL_STORAGE_KEY)).to.equals('// Hash: ' + VALID_HASH + '\n' + VALID_PUBLISHER_TAG); + utilsMock.verify(); + }); + + it('should verify valid hash with invalid publisher tag', function () { + localStorage.setItem(FASTBID_LOCAL_STORAGE_KEY, '// Hash: ' + VALID_HASH + '\n' + INVALID_PUBLISHER_TAG); + + utilsMock.expects('logInfo').withExactArgs('Using Criteo FastBid').never(); + utilsMock.expects('logWarn').withExactArgs('No hash found in FastBid').never(); + utilsMock.expects('logWarn').withExactArgs('Invalid Criteo FastBid found').once(); + + tryGetCriteoFastBid(); + + expect(localStorage.getItem(FASTBID_LOCAL_STORAGE_KEY)).to.be.null; + utilsMock.verify(); + }); + + it('should verify invalid hash with valid publisher tag', function () { + localStorage.setItem(FASTBID_LOCAL_STORAGE_KEY, '// Hash: ' + INVALID_HASH + '\n' + VALID_PUBLISHER_TAG); + + utilsMock.expects('logInfo').withExactArgs('Using Criteo FastBid').never(); + utilsMock.expects('logWarn').withExactArgs('No hash found in FastBid').never(); + utilsMock.expects('logWarn').withExactArgs('Invalid Criteo FastBid found').once(); + + tryGetCriteoFastBid(); + + expect(localStorage.getItem(FASTBID_LOCAL_STORAGE_KEY)).to.be.null; + utilsMock.verify(); + }); + + it('should verify missing hash', function () { + localStorage.setItem(FASTBID_LOCAL_STORAGE_KEY, VALID_PUBLISHER_TAG); + + utilsMock.expects('logInfo').withExactArgs('Using Criteo FastBid').never(); + utilsMock.expects('logWarn').withExactArgs('No hash found in FastBid').once(); + utilsMock.expects('logWarn').withExactArgs('Invalid Criteo FastBid found').never(); + + tryGetCriteoFastBid(); + + expect(localStorage.getItem(FASTBID_LOCAL_STORAGE_KEY)).to.be.null; + utilsMock.verify(); + }); + }); + + describe('when pubtag prebid adapter is available', function () { + it('should forward response to pubtag when calling interpretResponse', () => { + const response = {}; + const request = {}; + + const adapter = { interpretResponse: function() {} }; + const adapterMock = sinon.mock(adapter); + adapterMock.expects('interpretResponse').withExactArgs(response, request).once().returns('ok'); + const prebidAdapter = { GetAdapter: function() {} }; + const prebidAdapterMock = sinon.mock(prebidAdapter); + prebidAdapterMock.expects('GetAdapter').withExactArgs(request).once().returns(adapter); + + global.Criteo = { + PubTag: { + Adapters: { + Prebid: prebidAdapter + } + } + }; + + expect(spec.interpretResponse(response, request)).equal('ok'); + adapterMock.verify(); + prebidAdapterMock.verify(); + }); + + it('should forward bid to pubtag when calling onBidWon', () => { + const bid = { auctionId: 123 }; + + const adapter = { handleBidWon: function() {} }; + const adapterMock = sinon.mock(adapter); + adapterMock.expects('handleBidWon').withExactArgs(bid).once(); + const prebidAdapter = { GetAdapter: function() {} }; + const prebidAdapterMock = sinon.mock(prebidAdapter); + prebidAdapterMock.expects('GetAdapter').withExactArgs(bid.auctionId).once().returns(adapter); + + global.Criteo = { + PubTag: { + Adapters: { + Prebid: prebidAdapter + } + } + }; + + spec.onBidWon(bid); + adapterMock.verify(); + prebidAdapterMock.verify(); + }); + + it('should forward bid to pubtag when calling onSetTargeting', () => { + const bid = { auctionId: 123 }; + + const adapter = { handleSetTargeting: function() {} }; + const adapterMock = sinon.mock(adapter); + adapterMock.expects('handleSetTargeting').withExactArgs(bid).once(); + const prebidAdapter = { GetAdapter: function() {} }; + const prebidAdapterMock = sinon.mock(prebidAdapter); + prebidAdapterMock.expects('GetAdapter').withExactArgs(bid.auctionId).once().returns(adapter); + + global.Criteo = { + PubTag: { + Adapters: { + Prebid: prebidAdapter + } + } + }; + + spec.onSetTargeting(bid); + adapterMock.verify(); + prebidAdapterMock.verify(); + }); + + it('should forward bid to pubtag when calling onTimeout', () => { + const timeoutData = { auctionId: 123 }; + + const adapter = { handleBidTimeout: function() {} }; + const adapterMock = sinon.mock(adapter); + adapterMock.expects('handleBidTimeout').once(); + const prebidAdapter = { GetAdapter: function() {} }; + const prebidAdapterMock = sinon.mock(prebidAdapter); + prebidAdapterMock.expects('GetAdapter').withExactArgs(timeoutData.auctionId).once().returns(adapter); + + global.Criteo = { + PubTag: { + Adapters: { + Prebid: prebidAdapter + } + } + }; + + spec.onTimeout(timeoutData); + adapterMock.verify(); + prebidAdapterMock.verify(); + }); + + it('should return a POST method with url & data from pubtag', () => { + const bidRequests = { }; + const bidderRequest = { }; + + const prebidAdapter = { buildCdbUrl: function() {}, buildCdbRequest: function() {} }; + const prebidAdapterMock = sinon.mock(prebidAdapter); + prebidAdapterMock.expects('buildCdbUrl').once().returns('cdbUrl'); + prebidAdapterMock.expects('buildCdbRequest').once().returns('cdbRequest'); + + const adapters = { Prebid: function() {} }; + const adaptersMock = sinon.mock(adapters); + adaptersMock.expects('Prebid').withExactArgs(PROFILE_ID_PUBLISHERTAG, ADAPTER_VERSION, bidRequests, bidderRequest, '$prebid.version$').once().returns(prebidAdapter); + + global.Criteo = { + PubTag: { + Adapters: adapters + } + }; + + const buildRequestsResult = spec.buildRequests(bidRequests, bidderRequest); + expect(buildRequestsResult.method).equal('POST'); + expect(buildRequestsResult.url).equal('cdbUrl'); + expect(buildRequestsResult.data).equal('cdbRequest'); + + adaptersMock.verify(); + prebidAdapterMock.verify(); + }); }); }); diff --git a/test/spec/modules/criteortusIdSystem_spec.js b/test/spec/modules/criteortusIdSystem_spec.js new file mode 100644 index 00000000000..217a2f86ba7 --- /dev/null +++ b/test/spec/modules/criteortusIdSystem_spec.js @@ -0,0 +1,90 @@ +import { criteortusIdSubmodule } from 'modules/criteortusIdSystem'; +import * as utils from 'src/utils'; + +describe('Criteo RTUS', function() { + let xhr; + let requests; + let getCookieStub; + let logErrorStub; + + beforeEach(function () { + xhr = sinon.useFakeXMLHttpRequest(); + requests = []; + xhr.onCreate = request => requests.push(request); + getCookieStub = sinon.stub(utils, 'getCookie'); + logErrorStub = sinon.stub(utils, 'logError'); + }); + + afterEach(function () { + xhr.restore(); + getCookieStub.restore(); + logErrorStub.restore(); + }); + + it('should log error when configParams are not passed', function() { + criteortusIdSubmodule.getId(); + expect(logErrorStub.calledOnce).to.be.true; + }) + + it('should call criteo endpoint to get user id', function() { + getCookieStub.returns(null); + let configParams = { + clientIdentifier: { + 'sampleBidder': 1 + } + } + + let response = { 'status': 'ok', 'userid': 'sample-userid' } + let callBackSpy = sinon.spy(); + const idResp = criteortusIdSubmodule.getId(configParams); + const submoduleCallback = idResp.callback; + submoduleCallback(callBackSpy); + requests[0].respond( + 200, + { 'Content-Type': 'text/plain' }, + JSON.stringify(response) + ); + expect(callBackSpy.calledOnce).to.be.true; + expect(callBackSpy.calledWith({'sampleBidder': response})).to.be.true; + }) + + it('should get uid from cookie and not call endpoint', function() { + let response = {'appnexus': {'status': 'ok', 'userid': 'sample-userid'}} + getCookieStub.returns(JSON.stringify(response)); + let configParams = { + clientIdentifier: { + 'sampleBidder': 1 + } + } + let uid = criteortusIdSubmodule.getId(configParams); + expect(requests.length).to.equal(0); + }) + + it('should call criteo endpoint for multiple bidders', function() { + getCookieStub.returns(null); + let configParams = { + clientIdentifier: { + 'sampleBidder': 1, + 'sampleBidder2': 2 + } + } + + let response = { 'status': 'ok', 'userid': 'sample-userid' } + let callBackSpy = sinon.spy(); + const idResp = criteortusIdSubmodule.getId(configParams); + const submoduleCallback = idResp.callback; + submoduleCallback(callBackSpy); + requests[0].respond( + 200, + { 'Content-Type': 'text/plain' }, + JSON.stringify(response) + ); + expect(callBackSpy.calledOnce).to.be.false; + requests[1].respond( + 200, + { 'Content-Type': 'text/plain' }, + JSON.stringify(response) + ); + expect(callBackSpy.calledOnce).to.be.true; + }) +}); diff --git a/test/spec/modules/currency_spec.js b/test/spec/modules/currency_spec.js index e96b15d11e9..98f51b72251 100644 --- a/test/spec/modules/currency_spec.js +++ b/test/spec/modules/currency_spec.js @@ -10,16 +10,15 @@ import { currencyRates } from 'modules/currency'; -import { createHook } from 'src/hook'; - var assert = require('chai').assert; var expect = require('chai').expect; describe('currency', function () { let fakeCurrencyFileServer; + let sandbox; + let clock; let fn = sinon.spy(); - let hookFn = createHook('asyncSeries', fn, 'addBidResponse'); beforeEach(function () { fakeCurrencyFileServer = sinon.fakeServer.create(); @@ -27,9 +26,20 @@ describe('currency', function () { afterEach(function () { fakeCurrencyFileServer.restore(); + setConfig({}); }); describe('setConfig', function () { + beforeEach(function() { + sandbox = sinon.sandbox.create(); + clock = sinon.useFakeTimers(1046952000000); // 2003-03-06T12:00:00Z + }); + + afterEach(function () { + sandbox.restore(); + clock.restore(); + }); + it('results in currencySupportEnabled = false when currency not configured', function () { setConfig({}); expect(currencySupportEnabled).to.equal(false); @@ -42,6 +52,76 @@ describe('currency', function () { expect(currencyRates.dataAsOf).to.equal('2017-04-25'); expect(currencySupportEnabled).to.equal(true); }); + + it('currency file is called even when default rates are specified', function() { + // RESET to request currency file (specifically url value for this test) + setConfig({ 'adServerCurrency': undefined }); + + // DO NOT SET DEFAULT RATES, currency file should be requested + setConfig({ + 'adServerCurrency': 'JPY' + }); + fakeCurrencyFileServer.respond(); + expect(fakeCurrencyFileServer.requests.length).to.equal(1); + expect(fakeCurrencyFileServer.requests[0].url).to.equal('https://cdn.jsdelivr.net/gh/prebid/currency-file@1/latest.json?date=20030306'); + + // RESET to request currency file (specifically url value for this test) + setConfig({ 'adServerCurrency': undefined }); + + // SET DEFAULT RATES, currency file should STILL be requested + setConfig({ + 'adServerCurrency': 'JPY', + 'defaultRates': { + 'GBP': { 'CNY': 66, 'JPY': 132, 'USD': 264 }, + 'USD': { 'CNY': 60, 'GBP': 120, 'JPY': 240 } + } }); + fakeCurrencyFileServer.respond(); + expect(fakeCurrencyFileServer.requests.length).to.equal(2); + expect(fakeCurrencyFileServer.requests[1].url).to.equal('https://cdn.jsdelivr.net/gh/prebid/currency-file@1/latest.json?date=20030306'); + }); + + it('date macro token $$TODAY$$ is replaced by current date (formatted as yyyymmdd)', function () { + // RESET to request currency file (specifically url value for this test) + setConfig({ 'adServerCurrency': undefined }); + + // date macro should replace $$TODAY$$ with date when DEFAULT_CURRENCY_RATE_URL is used + setConfig({ 'adServerCurrency': 'JPY' }); + fakeCurrencyFileServer.respond(); + expect(fakeCurrencyFileServer.requests[0].url).to.equal('https://cdn.jsdelivr.net/gh/prebid/currency-file@1/latest.json?date=20030306'); + + // RESET to request currency file (specifically url value for this test) + setConfig({ 'adServerCurrency': undefined }); + + // date macro should not modify 'conversionRateFile' if TOKEN is not found + setConfig({ + 'adServerCurrency': 'JPY', + 'conversionRateFile': 'http://test.net/currency.json?date=foobar' + }); + fakeCurrencyFileServer.respond(); + expect(fakeCurrencyFileServer.requests[1].url).to.equal('http://test.net/currency.json?date=foobar'); + + // RESET to request currency file (specifically url value for this test) + setConfig({ 'adServerCurrency': undefined }); + + // date macro should replace $$TODAY$$ with date for 'conversionRateFile' is configured + setConfig({ + 'adServerCurrency': 'JPY', + 'conversionRateFile': 'http://test.net/currency.json?date=$$TODAY$$' + }); + fakeCurrencyFileServer.respond(); + expect(fakeCurrencyFileServer.requests[2].url).to.equal('http://test.net/currency.json?date=20030306'); + + // RESET to request currency file (specifically url value for this test) + setConfig({ 'adServerCurrency': undefined }); + + // MULTIPLE TOKENS used in a url is not supported. Only the TOKEN at left-most position is REPLACED + setConfig({ + 'adServerCurrency': 'JPY', + 'conversionRateFile': 'http://test.net/$$TODAY$$/currency.json?date=$$TODAY$$' + }); + fakeCurrencyFileServer.respond(); + expect(fakeCurrencyFileServer.requests[3].url).to.equal('http://test.net/20030306/currency.json?date=$$TODAY$$'); + }); }); describe('bidder override', function () { @@ -58,9 +138,9 @@ describe('currency', function () { } }); - addBidResponseHook('elementId', bid, function(adCodeId, bid) { - innerBid = bid; - }); + addBidResponseHook(function(adCodeId, bid) { + innerBid = bid; + }, 'elementId', bid); expect(innerBid.currency).to.equal('GBP'); expect(typeof innerBid.getCpmInNewCurrency).to.equal('function'); @@ -80,9 +160,9 @@ describe('currency', function () { } }); - addBidResponseHook('elementId', bid, function(adCodeId, bid) { - innerBid = bid; - }); + addBidResponseHook(function(adCodeId, bid) { + innerBid = bid; + }, 'elementId', bid); expect(innerBid.currency).to.equal('JPY'); expect(typeof innerBid.getCpmInNewCurrency).to.equal('function'); @@ -102,13 +182,41 @@ describe('currency', function () { var bid = { cpm: 100, currency: 'JPY', bidder: 'rubicon' }; var innerBid; - addBidResponseHook('elementId', bid, function(adCodeId, bid) { - innerBid = bid; + addBidResponseHook(function(adCodeId, bid) { + innerBid = bid; + }, 'elementId', bid); + + expect(innerBid.cpm).to.equal('1.0000'); + expect(typeof innerBid.getCpmInNewCurrency).to.equal('function'); + expect(innerBid.getCpmInNewCurrency('JPY')).to.equal('100.000'); + }); + + it('uses rates specified in json when provided and consider boosted bid', function () { + setConfig({ + adServerCurrency: 'USD', + rates: { + USD: { + JPY: 100 + } + } }); + var bid = { cpm: 100, currency: 'JPY', bidder: 'rubicon' }; + var innerBid; + + addBidResponseHook(function(adCodeId, bid) { + innerBid = bid; + }, 'elementId', bid); + expect(innerBid.cpm).to.equal('1.0000'); expect(typeof innerBid.getCpmInNewCurrency).to.equal('function'); expect(innerBid.getCpmInNewCurrency('JPY')).to.equal('100.000'); + + // Boosting the bid now + innerBid.cpm *= 10; + expect(innerBid.cpm).to.equal(10.0000); + expect(typeof innerBid.getCpmInNewCurrency).to.equal('function'); + expect(innerBid.getCpmInNewCurrency('JPY')).to.equal('1000.000'); }); it('uses default rates when currency file fails to load', function () { @@ -129,9 +237,9 @@ describe('currency', function () { var bid = { cpm: 100, currency: 'JPY', bidder: 'rubicon' }; var innerBid; - addBidResponseHook('elementId', bid, function(adCodeId, bid) { - innerBid = bid; - }); + addBidResponseHook(function(adCodeId, bid) { + innerBid = bid; + }, 'elementId', bid); expect(innerBid.cpm).to.equal('1.0000'); expect(typeof innerBid.getCpmInNewCurrency).to.equal('function'); @@ -150,9 +258,9 @@ describe('currency', function () { setConfig({ 'adServerCurrency': 'JPY' }); var marker = false; - addBidResponseHook('elementId', bid, function() { - marker = true; - }); + addBidResponseHook(function() { + marker = true; + }, 'elementId', bid); expect(marker).to.equal(false); @@ -166,19 +274,20 @@ describe('currency', function () { setConfig({}); var bid = { 'cpm': 1, 'currency': 'USD' }; var innerBid; - addBidResponseHook('elementId', bid, function(adCodeId, bid) { - innerBid = bid; - }); + addBidResponseHook(function(adCodeId, bid) { + innerBid = bid; + }, 'elementId', bid); expect(innerBid.cpm).to.equal(1); }); it('should result in NO_BID when currency support is not enabled and fromCurrency is not USD', function () { setConfig({}); + var bid = { 'cpm': 1, 'currency': 'GBP' }; var innerBid; - addBidResponseHook('elementId', bid, function(adCodeId, bid) { - innerBid = bid; - }); + addBidResponseHook(function(adCodeId, bid) { + innerBid = bid; + }, 'elementId', bid); expect(innerBid.statusMessage).to.equal('Bid returned empty or error response'); }); @@ -188,21 +297,24 @@ describe('currency', function () { }); var bid = { 'cpm': 1, 'currency': 'USD' }; var innerBid; - addBidResponseHook('elementId', bid, function(adCodeId, bid) { - innerBid = bid; - }); + addBidResponseHook(function(adCodeId, bid) { + innerBid = bid; + }, 'elementId', bid); expect(bid).to.equal(innerBid); }); it('should result in NO_BID when fromCurrency is not supported in file', function () { + // RESET to request currency file + setConfig({ 'adServerCurrency': undefined }); + fakeCurrencyFileServer.respondWith(JSON.stringify(getCurrencyRates())); setConfig({ 'adServerCurrency': 'JPY' }); fakeCurrencyFileServer.respond(); var bid = { 'cpm': 1, 'currency': 'ABC' }; var innerBid; - addBidResponseHook('elementId', bid, function(adCodeId, bid) { - innerBid = bid; - }); + addBidResponseHook(function(adCodeId, bid) { + innerBid = bid; + }, 'elementId', bid); expect(innerBid.statusMessage).to.equal('Bid returned empty or error response'); }); @@ -212,9 +324,9 @@ describe('currency', function () { fakeCurrencyFileServer.respond(); var bid = { 'cpm': 1, 'currency': 'GBP' }; var innerBid; - addBidResponseHook('elementId', bid, function(adCodeId, bid) { - innerBid = bid; - }); + addBidResponseHook(function(adCodeId, bid) { + innerBid = bid; + }, 'elementId', bid); expect(innerBid.statusMessage).to.equal('Bid returned empty or error response'); }); @@ -224,9 +336,9 @@ describe('currency', function () { fakeCurrencyFileServer.respond(); var bid = { 'cpm': 1, 'currency': 'JPY' }; var innerBid; - addBidResponseHook('elementId', bid, function(adCodeId, bid) { - innerBid = bid; - }); + addBidResponseHook(function(adCodeId, bid) { + innerBid = bid; + }, 'elementId', bid); expect(innerBid.cpm).to.equal(1); expect(innerBid.currency).to.equal('JPY'); }); @@ -237,9 +349,9 @@ describe('currency', function () { fakeCurrencyFileServer.respond(); var bid = { 'cpm': 1, 'currency': 'USD' }; var innerBid; - addBidResponseHook('elementId', bid, function(adCodeId, bid) { - innerBid = bid; - }); + addBidResponseHook(function(adCodeId, bid) { + innerBid = bid; + }, 'elementId', bid); expect(innerBid.cpm).to.equal('0.7798'); expect(innerBid.currency).to.equal('GBP'); }); @@ -250,9 +362,9 @@ describe('currency', function () { fakeCurrencyFileServer.respond(); var bid = { 'cpm': 1, 'currency': 'CNY' }; var innerBid; - addBidResponseHook('elementId', bid, function(adCodeId, bid) { - innerBid = bid; - }); + addBidResponseHook(function(adCodeId, bid) { + innerBid = bid; + }, 'elementId', bid); expect(innerBid.cpm).to.equal('0.1133'); expect(innerBid.currency).to.equal('GBP'); }); @@ -263,9 +375,9 @@ describe('currency', function () { fakeCurrencyFileServer.respond(); var bid = { 'cpm': 1, 'currency': 'JPY' }; var innerBid; - addBidResponseHook('elementId', bid, function(adCodeId, bid) { - innerBid = bid; - }); + addBidResponseHook(function(adCodeId, bid) { + innerBid = bid; + }, 'elementId', bid); expect(innerBid.cpm).to.equal('0.0623'); expect(innerBid.currency).to.equal('CNY'); }); diff --git a/test/spec/modules/datablocksBidAdapter_spec.js b/test/spec/modules/datablocksBidAdapter_spec.js new file mode 100644 index 00000000000..d39116ccb71 --- /dev/null +++ b/test/spec/modules/datablocksBidAdapter_spec.js @@ -0,0 +1,334 @@ +import { expect } from 'chai'; +import { spec } from '../../../modules/datablocksBidAdapter'; + +let bid = { + bidId: '2dd581a2b6281d', + bidder: 'datablocks', + bidderRequestId: '145e1d6a7837c9', + params: { + sourceId: 7560, + host: 'v5demo.datablocks.net' + }, + adUnitCode: '/19968336/header-bid-tag-0', + auctionId: '74f78609-a92d-4cf1-869f-1b244bbfb5d2', + mediaTypes: { + banner: { + sizes: [ + [300, 250] + ] + } + }, + sizes: [ + [300, 250] + ], + transactionId: '1ccbee15-f6f6-46ce-8998-58fe5542e8e1' +}; + +let bid2 = { + bidId: '2dd581a2b624324g', + bidder: 'datablocks', + bidderRequestId: '145e1d6a7837543', + params: { + sourceId: 7560, + host: 'v5demo.datablocks.net' + }, + adUnitCode: '/19968336/header-bid-tag-0', + auctionId: '74f78609-a92d-4cf1-869f-1b244bbfb5d2', + mediaTypes: { + banner: { + sizes: + [728, 90] + } + }, + transactionId: '1ccbee15-f6f6-46ce-8998-58fe55425432' +}; + +let nativeBid = { + adUnitCode: '/19968336/header-bid-tag-0', + auctionId: '160c78a4-f808-410f-b682-d8728f3a79ee', + bidId: '332045ee374a99', + bidder: 'datablocks', + bidderRequestId: '15d9012765e36c', + mediaTypes: { + native: { + title: { + required: true + }, + body: { + required: true + }, + image: { + required: true + } + } + }, + nativeParams: { + title: { + required: true + }, + body: { + required: true, + data: { + len: 250 + } + }, + image: { + required: true, + sizes: [728, 90] + } + }, + params: { + sourceId: 7560, + host: 'v5demo.datablocks.net' + }, + transactionId: '0a4e9788-4def-4b94-bc25-564d7cac99f6' +} + +let videoBid = { + adUnitCode: '/19968336/header-bid-tag-0', + auctionId: '160c78a4-f808-410f-b682-d8728f3a79e1', + bidId: '332045ee374b99', + bidder: 'datablocks', + bidderRequestId: '15d9012765e36d', + mediaTypes: { + video: { + context: 'instream', + playerSize: [501, 400], + durationRangeSec: [15, 60] + } + }, + params: { + sourceId: 7560, + host: 'v5demo.datablocks.net', + video: { + minduration: 14 + } + }, + transactionId: '0a4e9788-4def-4b94-bc25-564d7cac99f7' +} + +const bidderRequest = { + auctionId: '8bfef1be-d3ac-4d18-8859-754c7b4cf017', + auctionStart: Date.now(), + biddeCode: 'datablocks', + bidderRequestId: '10c47a5fc3c41', + bids: [bid, bid2, nativeBid, videoBid], + refererInfo: { + numIframes: 0, + reachedTop: true, + referer: 'http://v5demo.datablocks.net/test', + stack: ['http://v5demo.datablocks.net/test'] + }, + start: Date.now(), + timeout: 10000 +}; + +let resObject = { + body: { + id: '10c47a5fc3c41', + bidid: '166895245-28-11347-1', + seatbid: [{ + seat: '7560', + bid: [{ + id: '1090738570', + impid: '2966b257c81d27', + price: 24.000000, + adm: 'RON', + cid: '55', + adid: '177654', + crid: '177656', + cat: [], + api: [], + w: 300, + h: 250 + }, { + id: '1090738571', + impid: '2966b257c81d28', + price: 24.000000, + adm: 'RON', + cid: '55', + adid: '177654', + crid: '177656', + cat: [], + api: [], + w: 728, + h: 90 + }, { + id: '1090738570', + impid: '15d9012765e36c', + price: 24.000000, + adm: '{"native":{"ver":"1.2","assets":[{"id":1,"required":1,"title":{"text":"Example Title"}},{"id":2,"required":1,"data":{"value":"Example Body"}},{"id":3,"required":1,"img":{"url":"http://example.image.com/"}}],"link":{"url":"http://click.example.com/c/264597/?fcid=29699699045816"},"imptrackers":["http://impression.example.com/i/264597/?fcid=29699699045816"]}}', + cid: '132145', + adid: '154321', + crid: '177432', + cat: [], + api: [] + }, { + id: '1090738575', + impid: '15d9012765e36f', + price: 25.000000, + cid: '12345', + adid: '12345', + crid: '123456', + nurl: 'http://click.v5demo.datablocks.net/m//?fcid=435235435432', + cat: [], + api: [], + w: 500, + h: 400 + }] + }], + cur: 'USD', + ext: {} + } +}; +let bidRequest = { + method: 'POST', + url: '//v5demo.datablocks.net/search/?sid=7560', + options: { + withCredentials: false + }, + data: { + device: { + ip: 'peer', + ua: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) Ap…ML, like Gecko) Chrome/73.0.3683.86 Safari/537.36', + js: 1, + language: 'en' + }, + id: '10c47a5fc3c41', + imp: [{ + banner: { w: 300, h: 250 }, + id: '2966b257c81d27', + secure: false, + tagid: '/19968336/header-bid-tag-0' + }, { + banner: { w: 728, h: 90 }, + id: '2966b257c81d28', + secure: false, + tagid: '/19968336/header-bid-tag-0' + }, { + id: '15d9012765e36c', + native: {request: '{"native":{"assets":[{"id":"1","required":true,"title":{"len":140}},{"id":"2","required":true,"data":{"type":2}},{"id":"3","img":{"w":728,"h":90,"type":3}}]}}'}, + secure: false, + tagid: '/19968336/header-bid-tag-0' + }, { + id: '15d9012765e36f', + video: {w: 500, h: 400, minduration: 15, maxduration: 60}, + secure: false, + tagid: '/19968336/header-bid-tag-0' + }], + site: { + domain: '', + id: 'blank', + page: 'http://v5demo.datablocks.net/test' + } + } +} + +describe('DatablocksAdapter', function() { + describe('isBidRequestValid', function() { + it('Should return true when sourceId and Host are set', function() { + expect(spec.isBidRequestValid(bid)).to.be.true; + }); + it('Should return false when host/sourceId is not set', function() { + let moddedBid = Object.assign({}, bid); + delete moddedBid.params.sourceId; + delete moddedBid.params.host; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + }); + + describe('buildRequests', function() { + let requests = spec.buildRequests([bid, bid2, nativeBid, videoBid], bidderRequest); + it('Creates an array of request objects', function() { + expect(requests).to.be.an('array').that.is.not.empty; + }); + + requests.forEach(request => { + expect(request).to.exist; + it('Returns POST method', function() { + expect(request.method).to.exist; + expect(request.method).to.equal('POST'); + }); + it('Returns valid URL', function() { + expect(request.url).to.exist; + expect(request.url).to.equal('//v5demo.datablocks.net/search/?sid=7560'); + }); + + it('Should be a valid openRTB request', function() { + let data = request.data; + expect(data).to.be.an('object'); + expect(data).to.have.all.keys('device', 'imp', 'site', 'id'); + expect(data.id).to.be.a('string'); + + let imps = data['imp']; + imps.forEach((imp, index) => { + let curBid = bidderRequest.bids[index]; + if (imp.banner) { + expect(imp).to.have.all.keys('banner', 'id', 'secure', 'tagid'); + expect(imp.banner).to.be.a('object'); + } else if (imp.native) { + expect(imp).to.have.all.keys('native', 'id', 'secure', 'tagid'); + expect(imp.native).to.have.all.keys('request'); + expect(imp.native.request).to.be.a('string'); + let native = JSON.parse(imp.native.request); + expect(native).to.be.a('object'); + } else if (imp.video) { + expect(imp).to.have.all.keys('video', 'id', 'secure', 'tagid'); + expect(imp.video).to.have.all.keys('w', 'h', 'minduration', 'maxduration') + } else { + expect(true).to.equal(false); + } + + expect(imp.id).to.be.a('string'); + expect(imp.id).to.equal(curBid.bidId); + expect(imp.tagid).to.be.a('string'); + expect(imp.tagid).to.equal(curBid.adUnitCode); + expect(imp.secure).to.equal(false); + }) + + expect(data.device.ip).to.equal('peer'); + }); + }) + + it('Returns empty data if no valid requests are passed', function() { + let request = spec.buildRequests([]); + expect(request).to.be.an('array').that.is.empty; + }); + }); + describe('interpretResponse', function() { + let serverResponses = spec.interpretResponse(resObject, bidRequest); + it('Returns an array of valid server responses if response object is valid', function() { + expect(serverResponses).to.be.an('array').that.is.not.empty; + for (let i = 0; i < serverResponses.length; i++) { + let dataItem = serverResponses[i]; + expect(Object.keys(dataItem)).to.include('cpm', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'mediaType', 'requestId'); + expect(dataItem.requestId).to.be.a('string'); + expect(dataItem.cpm).to.be.a('number'); + expect(dataItem.ttl).to.be.a('number'); + expect(dataItem.creativeId).to.be.a('string'); + expect(dataItem.netRevenue).to.be.a('boolean'); + expect(dataItem.currency).to.be.a('string'); + expect(dataItem.mediaType).to.be.a('string'); + + if (dataItem.mediaType == 'banner') { + expect(dataItem.ad).to.be.a('string'); + expect(dataItem.width).to.be.a('number'); + expect(dataItem.height).to.be.a('number'); + } else if (dataItem.mediaType == 'native') { + expect(dataItem.native.title).to.be.a('string'); + expect(dataItem.native.body).to.be.a('string'); + expect(dataItem.native.clickUrl).to.be.a('string'); + } else if (dataItem.mediaType == 'video') { + expect(dataItem.vastUrl).to.be.a('string'); + expect(dataItem.width).to.be.a('number'); + expect(dataItem.height).to.be.a('number'); + } + } + it('Returns an empty array if invalid response is passed', function() { + serverResponses = spec.interpretResponse('invalid_response'); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + }); + }); +}); diff --git a/test/spec/modules/decenteradsBidAdapter_spec.js b/test/spec/modules/decenteradsBidAdapter_spec.js new file mode 100644 index 00000000000..3c8569b3b61 --- /dev/null +++ b/test/spec/modules/decenteradsBidAdapter_spec.js @@ -0,0 +1,207 @@ +import { expect } from 'chai' +import { spec } from '../../../modules/decenteradsBidAdapter' +import { deepStrictEqual, notEqual, ok, strictEqual } from 'assert' + +describe('DecenteradsAdapter', () => { + const bid = { + bidId: '9ec5b177515ee2e5', + bidder: 'decenterads', + params: { + placementId: 0, + traffic: 'banner' + } + } + + describe('isBidRequestValid', () => { + it('Should return true if there are bidId, params and placementId parameters present', () => { + strictEqual(true, spec.isBidRequestValid(bid)) + }) + + it('Should return false if at least one of parameters is not present', () => { + const b = { ...bid } + delete b.params.placementId + strictEqual(false, spec.isBidRequestValid(b)) + }) + }) + + describe('buildRequests', () => { + const serverRequest = spec.buildRequests([bid]) + + it('Creates a ServerRequest object with method, URL and data', () => { + ok(serverRequest) + ok(serverRequest.method) + ok(serverRequest.url) + ok(serverRequest.data) + }) + + it('Returns POST method', () => { + strictEqual('POST', serverRequest.method) + }) + + it('Returns valid URL', () => { + strictEqual('//supply.decenterads.com/?c=o&m=multi', serverRequest.url) + }) + + it('Returns valid data if array of bids is valid', () => { + const { data } = serverRequest + strictEqual('object', typeof data) + deepStrictEqual(['deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements'], Object.keys(data)) + strictEqual('number', typeof data.deviceWidth) + strictEqual('number', typeof data.deviceHeight) + strictEqual('string', typeof data.language) + strictEqual('string', typeof data.host) + strictEqual('string', typeof data.page) + notEqual(-1, [0, 1].indexOf(data.secure)) + + const placement = data.placements[0] + deepStrictEqual(['placementId', 'bidId', 'traffic'], Object.keys(placement)) + strictEqual(0, placement.placementId) + strictEqual('9ec5b177515ee2e5', placement.bidId) + strictEqual('banner', placement.traffic) + }) + + it('Returns empty data if no valid requests are passed', () => { + const { placements } = spec.buildRequests([]).data + + expect(spec.buildRequests([]).data.placements).to.be.an('array') + strictEqual(0, placements.length) + }) + }) + + describe('interpretResponse', () => { + const validData = [ + { + body: [{ + mediaType: 'banner', + width: 300, + height: 250, + cpm: 0.4, + ad: 'Test', + requestId: '9ec5b177515ee2e5', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }, + { + body: [{ + vastUrl: 'decenterads.com', + mediaType: 'video', + cpm: 0.5, + requestId: '9ec5b177515ee2e5', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }, + { + body: [{ + mediaType: 'native', + clickUrl: 'decenterads.com', + title: 'Test', + image: 'decenterads.com', + creativeId: '2', + impressionTrackers: ['decenterads.com'], + ttl: 120, + cpm: 0.4, + requestId: '9ec5b177515ee2e5', + netRevenue: true, + currency: 'USD', + }] + } + ] + + for (const obj of validData) { + const { mediaType } = obj.body[0] + + it(`Should interpret ${mediaType} response`, () => { + const response = spec.interpretResponse(obj) + + expect(response).to.be.an('array') + strictEqual(1, response.length) + + const copy = { ...obj.body[0] } + delete copy.mediaType + deepStrictEqual(copy, response[0]) + }) + } + + const invalidData = [ + { + body: [{ + width: 300, + cpm: 0.4, + ad: 'Test', + requestId: '9ec5b177515ee2e5', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }, + { + body: [{ + mediaType: 'video', + cpm: 0.5, + requestId: '9ec5b177515ee2e5', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }, + { + body: [{ + mediaType: 'native', + clickUrl: 'decenterads.com', + title: 'Test', + impressionTrackers: ['decenterads.com'], + ttl: 120, + requestId: '9ec5b177515ee2e5', + creativeId: '2', + netRevenue: true, + currency: 'USD', + }] + } + ] + + for (const obj of invalidData) { + const { mediaType } = obj.body[0] + + it(`Should return an empty array if invalid ${mediaType} response is passed `, () => { + const response = spec.interpretResponse(obj) + + expect(response).to.be.an('array') + strictEqual(0, response.length) + }) + } + + it('Should return an empty array if invalid response is passed', () => { + const response = spec.interpretResponse({ + body: [{ + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }) + + expect(response).to.be.an('array') + strictEqual(0, response.length) + }) + }) + + describe('getUserSyncs', () => { + it('Returns valid URL and type', () => { + const expectedResult = [{ type: 'image', url: '//supply.decenterads.com/?c=o&m=cookie' }] + deepStrictEqual(expectedResult, spec.getUserSyncs()) + }) + }) +}) diff --git a/test/spec/modules/dfpAdServerVideo_spec.js b/test/spec/modules/dfpAdServerVideo_spec.js index 8afc597d3b4..6271c9b38f4 100644 --- a/test/spec/modules/dfpAdServerVideo_spec.js +++ b/test/spec/modules/dfpAdServerVideo_spec.js @@ -1,16 +1,21 @@ import { expect } from 'chai'; import parse from 'url-parse'; -import buildDfpVideoUrl from 'modules/dfpAdServerVideo'; +import { buildDfpVideoUrl, buildAdpodVideoUrl } from 'modules/dfpAdServerVideo'; import { parseQS } from 'src/url'; import adUnit from 'test/fixtures/video/adUnit'; import * as utils from 'src/utils'; import { config } from 'src/config'; import { targeting } from 'src/targeting'; +import { auctionManager } from 'src/auctionManager'; +import * as adpod from 'modules/adpod'; const bid = { videoCacheKey: 'abc', - adserverTargeting: { }, + adserverTargeting: { + hb_uuid: 'abc', + hb_cache_id: 'abc', + }, }; describe('The DFP video support module', function () { @@ -25,7 +30,7 @@ describe('The DFP video support module', function () { })); expect(url.protocol).to.equal('https:'); - expect(url.host).to.equal('pubads.g.doubleclick.net'); + expect(url.host).to.equal('securepubads.g.doubleclick.net'); const queryParams = parseQS(url.query); expect(queryParams).to.have.property('correlator'); @@ -40,7 +45,7 @@ describe('The DFP video support module', function () { }); it('can take an adserver url as a parameter', function () { - const bidCopy = Object.assign({ }, bid); + const bidCopy = utils.deepClone(bid); bidCopy.vastUrl = 'vastUrl.example'; const url = parse(buildDfpVideoUrl({ @@ -90,10 +95,10 @@ describe('The DFP video support module', function () { }); it('should include the cache key and adserver targeting in cust_params', function () { - const bidCopy = Object.assign({ }, bid); - bidCopy.adserverTargeting = { + const bidCopy = utils.deepClone(bid); + bidCopy.adserverTargeting = Object.assign(bidCopy.adserverTargeting, { hb_adid: 'ad_id', - }; + }); const url = parse(buildDfpVideoUrl({ adUnit: adUnit, @@ -160,10 +165,10 @@ describe('The DFP video support module', function () { } }); - const bidCopy = Object.assign({ }, bid); - bidCopy.adserverTargeting = { + const bidCopy = utils.deepClone(bid); + bidCopy.adserverTargeting = Object.assign(bidCopy.adserverTargeting, { hb_adid: 'ad_id', - }; + }); const url = parse(buildDfpVideoUrl({ adUnit: adUnitsCopy, @@ -184,10 +189,10 @@ describe('The DFP video support module', function () { }); it('should merge the user-provided cust_params with the default ones', function () { - const bidCopy = Object.assign({ }, bid); - bidCopy.adserverTargeting = { + const bidCopy = utils.deepClone(bid); + bidCopy.adserverTargeting = Object.assign(bidCopy.adserverTargeting, { hb_adid: 'ad_id', - }; + }); const url = parse(buildDfpVideoUrl({ adUnit: adUnit, @@ -207,10 +212,10 @@ describe('The DFP video support module', function () { }); it('should merge the user-provided cust-params with the default ones when using url object', function () { - const bidCopy = Object.assign({ }, bid); - bidCopy.adserverTargeting = { + const bidCopy = utils.deepClone(bid); + bidCopy.adserverTargeting = Object.assign(bidCopy.adserverTargeting, { hb_adid: 'ad_id', - }; + }); const url = parse(buildDfpVideoUrl({ adUnit: adUnit, @@ -229,7 +234,7 @@ describe('The DFP video support module', function () { }); it('should not overwrite an existing description_url for object input and cache disabled', function () { - const bidCopy = Object.assign({}, bid); + const bidCopy = utils.deepClone(bid); bidCopy.vastUrl = 'vastUrl.example'; const url = parse(buildDfpVideoUrl({ @@ -253,4 +258,286 @@ describe('The DFP video support module', function () { expect(url).to.be.a('string'); }); + + it('should include hb_uuid and hb_cache_id in cust_params when both keys are exluded from overwritten bidderSettings', function () { + const bidCopy = utils.deepClone(bid); + delete bidCopy.adserverTargeting.hb_uuid; + delete bidCopy.adserverTargeting.hb_cache_id; + + const url = parse(buildDfpVideoUrl({ + adUnit: adUnit, + bid: bidCopy, + params: { + 'iu': 'my/adUnit' + } + })); + const queryObject = parseQS(url.query); + const customParams = parseQS('?' + decodeURIComponent(queryObject.cust_params)); + + expect(customParams).to.have.property('hb_uuid', bid.videoCacheKey); + expect(customParams).to.have.property('hb_cache_id', bid.videoCacheKey); + }); + + it('should include hb_uuid and hb_cache_id in cust params from overwritten standard bidderSettings', function () { + const bidCopy = utils.deepClone(bid); + bidCopy.adserverTargeting = Object.assign(bidCopy.adserverTargeting, { + hb_uuid: 'def', + hb_cache_id: 'def' + }); + + const url = parse(buildDfpVideoUrl({ + adUnit: adUnit, + bid: bidCopy, + params: { + 'iu': 'my/adUnit' + } + })); + const queryObject = parseQS(url.query); + const customParams = parseQS('?' + decodeURIComponent(queryObject.cust_params)); + + expect(customParams).to.have.property('hb_uuid', 'def'); + expect(customParams).to.have.property('hb_cache_id', 'def'); + }); + + describe('adpod unit tests', function () { + let amStub; + let amGetAdUnitsStub; + let xhr; + let requests; + + before(function () { + let adUnits = [{ + code: 'adUnitCode-1', + mediaTypes: { + video: { + context: 'adpod', + playerSize: [640, 480], + adPodDurationSec: 60, + durationRangeSec: [15, 30], + requireExactDuration: true + } + }, + bids: [ + { + bidder: 'appnexus', + params: { + placementId: 14542875, + } + } + ] + }]; + + amGetAdUnitsStub = sinon.stub(auctionManager, 'getAdUnits'); + amGetAdUnitsStub.returns(adUnits); + amStub = sinon.stub(auctionManager, 'getBidsReceived'); + }); + + beforeEach(function () { + xhr = sinon.useFakeXMLHttpRequest(); + requests = []; + xhr.onCreate = request => requests.push(request); + + config.setConfig({ + adpod: { + brandCategoryExclusion: true, + deferCaching: false + } + }); + }) + + afterEach(function() { + config.resetConfig(); + xhr.restore(); + }); + + after(function () { + amGetAdUnitsStub.restore(); + amStub.restore(); + }); + + it('should return masterTag url', function() { + amStub.returns(getBidsReceived()); + let url; + parse(buildAdpodVideoUrl({ + code: 'adUnitCode-1', + callback: handleResponse, + params: { + 'iu': 'my/adUnit', + 'description_url': 'someUrl.com', + } + })); + + function handleResponse(err, masterTag) { + if (err) { + return; + } + url = parse(masterTag); + + expect(url.protocol).to.equal('https:'); + expect(url.host).to.equal('securepubads.g.doubleclick.net'); + + const queryParams = parseQS(url.query); + expect(queryParams).to.have.property('correlator'); + expect(queryParams).to.have.property('description_url', 'someUrl.com'); + expect(queryParams).to.have.property('env', 'vp'); + expect(queryParams).to.have.property('gdfp_req', '1'); + expect(queryParams).to.have.property('iu', 'my/adUnit'); + expect(queryParams).to.have.property('output', 'xml_vast3'); + expect(queryParams).to.have.property('sz', '640x480'); + expect(queryParams).to.have.property('unviewed_position_start', '1'); + expect(queryParams).to.have.property('url'); + expect(queryParams).to.have.property('cust_params'); + + const custParams = 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'); + } + }); + + it('should return masterTag url with correct custom params when brandCategoryExclusion is false', function() { + config.setConfig({ + adpod: { + brandCategoryExclusion: false, + } + }); + function getBids() { + let bids = [ + createBid(10, 'adUnitCode-1', 15, '10.00_15s', '123', '395', '10.00'), + createBid(15, 'adUnitCode-1', 15, '15.00_15s', '123', '395', '15.00'), + createBid(25, 'adUnitCode-1', 30, '15.00_30s', '123', '406', '25.00'), + ]; + bids.forEach((bid) => { + delete bid.meta; + }); + return bids; + } + amStub.returns(getBids()); + let url; + parse(buildAdpodVideoUrl({ + code: 'adUnitCode-1', + callback: handleResponse, + params: { + 'iu': 'my/adUnit', + 'description_url': 'someUrl.com', + } + })); + + function handleResponse(err, masterTag) { + if (err) { + return; + } + url = parse(masterTag); + expect(url.protocol).to.equal('https:'); + expect(url.host).to.equal('securepubads.g.doubleclick.net'); + + const queryParams = parseQS(url.query); + expect(queryParams).to.have.property('correlator'); + expect(queryParams).to.have.property('description_url', 'someUrl.com'); + expect(queryParams).to.have.property('env', 'vp'); + expect(queryParams).to.have.property('gdfp_req', '1'); + expect(queryParams).to.have.property('iu', 'my/adUnit'); + expect(queryParams).to.have.property('output', 'xml_vast3'); + expect(queryParams).to.have.property('sz', '640x480'); + expect(queryParams).to.have.property('unviewed_position_start', '1'); + expect(queryParams).to.have.property('url'); + expect(queryParams).to.have.property('cust_params'); + + const custParams = parseQS(decodeURIComponent(queryParams.cust_params)); + expect(custParams).to.have.property('hb_cache_id', '123'); + expect(custParams).to.have.property('hb_pb_cat_dur', '10.00_15s,15.00_15s,15.00_30s'); + } + }); + + it('should handle error when cache fails', function() { + config.setConfig({ + adpod: { + brandCategoryExclusion: true, + deferCaching: true + } + }); + amStub.returns(getBidsReceived()); + + parse(buildAdpodVideoUrl({ + code: 'adUnitCode-1', + callback: handleResponse, + params: { + 'iu': 'my/adUnit', + 'description_url': 'someUrl.com', + } + })); + + requests[0].respond(503, { + 'Content-Type': 'plain/text', + }, 'The server could not save anything at the moment.'); + + function handleResponse(err, masterTag) { + expect(masterTag).to.be.null; + expect(err).to.be.an('error'); + } + }); + }) }); + +function getBidsReceived() { + return [ + createBid(10, 'adUnitCode-1', 15, '10.00_395_15s', '123', '395', '10.00'), + createBid(15, 'adUnitCode-1', 15, '15.00_395_15s', '123', '395', '15.00'), + createBid(25, 'adUnitCode-1', 30, '15.00_406_30s', '123', '406', '25.00'), + ] +} + +function createBid(cpm, adUnitCode, durationBucket, priceIndustryDuration, uuid, label, hbpb) { + return { + 'bidderCode': 'appnexus', + 'width': 640, + 'height': 360, + 'statusMessage': 'Bid available', + 'adId': '28f24ced14586c', + 'mediaType': 'video', + 'source': 'client', + 'requestId': '28f24ced14586c', + 'cpm': cpm, + 'creativeId': 97517771, + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 3600, + 'adUnitCode': adUnitCode, + 'video': { + 'context': 'adpod', + 'durationBucket': durationBucket + }, + 'appnexus': { + 'buyerMemberId': 9325 + }, + 'vastUrl': 'http://some-vast-url.com', + 'vastImpUrl': 'http://some-vast-imp-url.com', + 'auctionId': 'ec266b31-d652-49c5-8295-e83fafe5532b', + 'responseTimestamp': 1548442460888, + 'requestTimestamp': 1548442460827, + 'bidder': 'appnexus', + 'timeToRespond': 61, + 'pbLg': '5.00', + 'pbMg': '5.00', + 'pbHg': '5.00', + 'pbAg': '5.00', + 'pbDg': '5.00', + 'pbCg': '', + 'size': '640x360', + 'adserverTargeting': { + 'hb_bidder': 'appnexus', + 'hb_adid': '28f24ced14586c', + 'hb_pb': hbpb, + 'hb_size': '640x360', + 'hb_source': 'client', + 'hb_format': 'video', + 'hb_pb_cat_dur': priceIndustryDuration, + 'hb_cache_id': uuid + }, + 'customCacheKey': `${priceIndustryDuration}_${uuid}`, + 'meta': { + 'iabSubCatId': 'iab-1', + 'adServerCatId': label + }, + 'videoCacheKey': '4cf395af-8fee-4960-af0e-88d44e399f14' + } +} diff --git a/test/spec/modules/dgadsBidAdapter_spec.js b/test/spec/modules/dgadsBidAdapter_spec.js index 25f484678a0..2454885217d 100644 --- a/test/spec/modules/dgadsBidAdapter_spec.js +++ b/test/spec/modules/dgadsBidAdapter_spec.js @@ -1,11 +1,12 @@ import {expect} from 'chai'; import * as utils from 'src/utils'; -import {spec} from 'modules/dgadsBidAdapter'; +import {spec, getCookieUid} from 'modules/dgadsBidAdapter'; import {newBidder} from 'src/adapters/bidderFactory'; import { BANNER, NATIVE } from 'src/mediaTypes'; describe('dgadsBidAdapter', function () { const adapter = newBidder(spec); + const UID_NAME = 'dgads_uid'; const VALID_ENDPOINT = 'https://ads-tr.bigmining.com/ad/p/bid'; describe('inherited functions', function () { @@ -101,23 +102,30 @@ describe('dgadsBidAdapter', function () { const noBidRequests = []; expect(Object.keys(spec.buildRequests(noBidRequests)).length).to.equal(0); }); + it('getCookieUid return empty if cookie not found', function () { + expect(getCookieUid(UID_NAME)).to.equal(''); + }); const data = { location_id: '1', site_id: '1', transaction_id: 'c1f1eff6-23c6-4844-a321-575212939e37', - bid_id: '2db3101abaec66' + bid_id: '2db3101abaec66', + referer: utils.getTopWindowUrl(), + _uid: '' }; - it('sends bid request to VALID_ENDPOINT via POST', function () { + it('sends bid request to VALID_ENDPOINT via GET', function () { const request = spec.buildRequests(bidRequests)[0]; expect(request.url).to.equal(VALID_ENDPOINT); - expect(request.method).to.equal('POST'); + expect(request.method).to.equal('GET'); }); it('should attache params to the request', function () { const request = spec.buildRequests(bidRequests)[0]; - expect(request.data['location_id']).to.equal(data['location_id']); - expect(request.data['site_id']).to.equal(data['site_id']); + expect(request.data['_loc']).to.equal(data['location_id']); + expect(request.data['_medium']).to.equal(data['site_id']); expect(request.data['transaction_id']).to.equal(data['transaction_id']); expect(request.data['bid_id']).to.equal(data['bid_id']); + expect(request.data['referer']).to.equal(data['referer']); + expect(request.data['_uid']).to.equal(data['_uid']); }); }); diff --git a/test/spec/modules/digitrustIdSystem_spec.js b/test/spec/modules/digitrustIdSystem_spec.js new file mode 100644 index 00000000000..55035bc4b4e --- /dev/null +++ b/test/spec/modules/digitrustIdSystem_spec.js @@ -0,0 +1,50 @@ +import { + digiTrustIdSubmodule, + surfaceTestHook +} from 'modules/digiTrustIdSystem'; + +let assert = require('chai').assert; +let expect = require('chai').expect; + +var testHook = null; + +describe('DigiTrust Id System', function () { + it('Should create the test hook', function (done) { + testHook = surfaceTestHook(); + assert.isNotNull(testHook, 'The test hook failed to surface'); + var conf = { + init: { + member: 'unit_test', + site: 'foo' + }, + callback: function (result) { + } + }; + testHook.initDigitrustFacade(conf); + window.DigiTrust.getUser(conf); + expect(window.DigiTrust).to.exist; + expect(window.DigiTrust.isMock).to.be.true; + done(); + }); + + it('Should report as client', function (done) { + delete window.DigiTrust; + testHook = surfaceTestHook(); + + var conf = { + init: { + member: 'unit_test', + site: 'foo' + }, + callback: function (result) { + expect(window.DigiTrust).to.exist; + expect(result).to.exist; + expect(window.DigiTrust.isMock).to.be.true; + } + }; + testHook.initDigitrustFacade(conf); + expect(window.DigiTrust).to.exist; + expect(window.DigiTrust.isClient).to.be.true; + done(); + }); +}); diff --git a/test/spec/modules/divreachBidAdapter_spec.js b/test/spec/modules/divreachBidAdapter_spec.js deleted file mode 100644 index f874a206a87..00000000000 --- a/test/spec/modules/divreachBidAdapter_spec.js +++ /dev/null @@ -1,117 +0,0 @@ -import {expect} from 'chai'; -import {spec} from 'modules/divreachBidAdapter'; -import {newBidder} from 'src/adapters/bidderFactory'; - -const BIDDER_CODE = 'divreach'; -const ENDPOINT_URL = '//ads.divreach.com/prebid.1.0.aspx'; -const ZONE_ID = '2eb6bd58-865c-47ce-af7f-a918108c3fd2'; - -describe('DivReachAdapter', function () { - const adapter = newBidder(spec); - - describe('inherited functions', function () { - it('exists and is a function', function () { - expect(adapter.callBids).to.be.exist.and.to.be.a('function'); - }); - }); - - describe('isBidRequestValid', function () { - let bid = { - 'bidder': BIDDER_CODE, - 'params': { - 'zone': ZONE_ID - }, - 'adUnitCode': 'adunit-code', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - }; - - it('should return true when required params found', function () { - expect(spec.isBidRequestValid(bid)).to.equal(true); - }); - - it('should return false when required params are not passed', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { - 'placementId': 0 - }; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - }); - - describe('buildRequests', function () { - let bidRequests = [ - { - 'bidder': BIDDER_CODE, - 'params': { - 'zone': ZONE_ID - }, - 'adUnitCode': 'adunit-code', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - } - ]; - - it('should add referrer and imp to be equal bidRequest', function () { - const request = spec.buildRequests(bidRequests); - const payload = JSON.parse(request.data.substr(5)); - expect(payload.referrer).to.not.be.undefined; - expect(payload.imps[0]).to.deep.equal(bidRequests[0]); - }); - - it('sends bid request to ENDPOINT via GET', function () { - const request = spec.buildRequests(bidRequests); - expect(request.url).to.equal(ENDPOINT_URL); - expect(request.method).to.equal('GET'); - }); - }); - - describe('interpretResponse', function () { - let response = { - body: [{ - 'currency': 'USD', - 'cpm': 6.210000, - 'ad': '
    ad
    ', - 'width': 300, - 'height': 600, - 'creativeId': 'ccca3e5e-0c54-4761-9667-771322fbdffc', - 'ttl': 360, - 'netRevenue': false, - 'bidId': '5e4e763b6bc60b' - }] - }; - - it('should get correct bid response', function () { - const body = response.body; - let expectedResponse = [ - { - 'requestId': body[0].bidId, - 'cpm': body[0].cpm, - 'creativeId': body[0].creativeId, - 'width': body[0].width, - 'height': body[0].height, - 'ad': body[0].ad, - 'vastUrl': undefined, - 'currency': body[0].currency, - 'netRevenue': body[0].netRevenue, - 'ttl': body[0].ttl, - } - ]; - - let result = spec.interpretResponse(response); - expect(result[0]).to.deep.equal(expectedResponse[0]); - }); - - it('handles nobid responses', function () { - let response = []; - - let result = spec.interpretResponse(response); - expect(result.length).to.equal(0); - }); - }); -}); diff --git a/test/spec/modules/djaxBidAdapter_spec.js b/test/spec/modules/djaxBidAdapter_spec.js new file mode 100644 index 00000000000..82955ba43bc --- /dev/null +++ b/test/spec/modules/djaxBidAdapter_spec.js @@ -0,0 +1,159 @@ +import { expect } from 'chai'; +import { spec } from 'modules/djaxBidAdapter'; + +const ENDPOINT = 'https://demo.reviveadservermod.com/headerbidding_adminshare/www/admin/plugins/Prebid/getAd.php'; + +describe('The Djax bidding adapter', function () { + describe('isBidRequestValid', function () { + it('should return false when given an invalid bid', function () { + const bid = { + bidder: 'djax', + }; + const isValid = spec.isBidRequestValid(bid); + expect(isValid).to.equal(false); + }); + + it('should return true when given a publisherId in bid', function () { + const bid = { + bidder: 'djax', + params: { + publisherId: 2 + }, + }; + const isValid = spec.isBidRequestValid(bid); + expect(isValid).to.equal(true); + }); + }); + + describe('buildRequests', function () { + const bidRequests = [{ + 'bidder': 'djax', + 'params': { + 'publisherId': 2 + }, + 'adUnitCode': 'adunit-code', + 'sizes': [ + [300, 250], + [300, 600] + ] + }]; + + const request = spec.buildRequests(bidRequests); + + it('sends bid request to our endpoint via POST', function () { + expect(request.method).to.equal('POST'); + }); + + it('check endpoint url', function () { + expect(request.url).to.equal(ENDPOINT) + }); + + it('sets the proper banner object', function () { + expect(bidRequests[0].params.publisherId).to.equal(2); + }) + }); + const response = { + body: [ + { + 'requestId': '2ee937f15015c6', + 'cpm': '0.2000', + 'width': 300, + 'height': 600, + 'creativeId': '4', + 'currency': 'USD', + 'netRevenue': true, + 'ad': 'ads.html', + 'mediaType': 'banner' + }, + { + 'requestId': '3e1af92622bdc', + 'cpm': '0.2000', + 'creativeId': '4', + 'context': 'outstream', + 'currency': 'USD', + 'netRevenue': true, + 'vastUrl': 'tezt.xml', + 'width': 640, + 'height': 480, + 'mediaType': 'video' + }] + }; + + const request = [ + { + 'bidder': 'djax', + 'params': { + 'publisherId': 2 + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [300, 600] + ] + } + }, + 'bidId': '2ee937f15015c6', + 'src': 'client', + }, + { + 'bidder': 'djax', + 'params': { + 'publisherId': 2 + }, + 'mediaTypes': { + 'video': { + 'context': 'outstream', + 'playerSize': [ + [640, 480] + ] + } + }, + 'bidId': '3e1af92622bdc', + 'src': 'client', + } + ]; + + describe('interpretResponse', function () { + it('return empty array when no ad found', function () { + const response = {}; + const request = { bidRequests: [] }; + const bids = spec.interpretResponse(response, request); + expect(bids).to.have.lengthOf(0); + }); + + it('check response for banner and video', function () { + const bids = spec.interpretResponse(response, request); + expect(bids).to.have.lengthOf(2); + expect(bids[0].requestId).to.equal('2ee937f15015c6'); + expect(bids[0].cpm).to.equal('0.2000'); + expect(bids[1].cpm).to.equal('0.2000'); + expect(bids[0].width).to.equal(300); + expect(bids[0].height).to.equal(600); + expect(bids[1].vastUrl).to.not.equal(''); + expect(bids[0].ad).to.not.equal(''); + expect(bids[1].adResponse).to.not.equal(''); + expect(bids[1].renderer).not.to.be.an('undefined'); + }); + }); + + describe('On winning bid', function () { + const bids = spec.interpretResponse(response, request); + spec.onBidWon(bids); + }); + + describe('On bid Time out', function () { + const bids = spec.interpretResponse(response, request); + spec.onTimeout(bids); + }); + + describe('user sync', function () { + it('to check the user sync iframe', function () { + let syncs = spec.getUserSyncs({ + iframeEnabled: true + }); + expect(syncs).to.not.be.an('undefined'); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('iframe'); + }); + }); +}); diff --git a/test/spec/modules/dspxBidAdapter_spec.js b/test/spec/modules/dspxBidAdapter_spec.js new file mode 100644 index 00000000000..7eeac43680a --- /dev/null +++ b/test/spec/modules/dspxBidAdapter_spec.js @@ -0,0 +1,130 @@ +import { expect } from 'chai'; +import { spec } from 'modules/dspxBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; + +const ENDPOINT_URL = 'https://buyer.dspx.tv/request/'; + +describe('dspxAdapter', function () { + const adapter = newBidder(spec); + + describe('isBidRequestValid', function () { + let bid = { + 'bidder': 'dspx', + 'params': { + 'placement': '6682', + 'pfilter': { + 'floorprice': 1000000 + }, + 'bcat': 'IAB2,IAB4', + 'dvt': 'desktop' + }, + 'sizes': [ + [300, 250] + ], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475' + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not passed', function () { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'someIncorrectParam': 0 + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + let bidRequests = [{ + 'bidder': 'dspx', + 'params': { + 'placement': '6682', + 'pfilter': { + 'floorprice': 1000000, + 'private_auction': 0, + 'geo': { + 'country': 'DE' + } + }, + 'bcat': 'IAB2,IAB4', + 'dvt': 'desktop' + }, + 'sizes': [ + [300, 250] + ], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475' + }]; + + let bidderRequest = { + refererInfo: { + referer: 'some_referrer.net' + } + } + + const request = spec.buildRequests(bidRequests, bidderRequest); + it('sends bid request to our endpoint via GET', function () { + expect(request[0].method).to.equal('GET'); + let data = request[0].data.replace(/rnd=\d+\&/g, '').replace(/ref=.*\&bid/g, 'bid'); + expect(data).to.equal('_f=html&alternative=prebid_js&inventory_item_id=6682&srw=300&srh=250&idt=100&bid_id=30b31c1838de1e&pfilter%5Bfloorprice%5D=1000000&pfilter%5Bprivate_auction%5D=0&pfilter%5Bgeo%5D%5Bcountry%5D=DE&bcat=IAB2%2CIAB4&dvt=desktop'); + }); + }); + + describe('interpretResponse', function () { + let serverResponse = { + 'body': { + 'cpm': 5000000, + 'crid': 100500, + 'width': '300', + 'height': '250', + 'tag': '', + 'requestId': '220ed41385952a', + 'currency': 'EUR', + 'ttl': 60, + 'netRevenue': true, + 'zone': '6682' + } + }; + + let expectedResponse = [{ + requestId: '23beaa6af6cdde', + cpm: 0.5, + width: 0, + height: 0, + creativeId: 100500, + dealId: '', + currency: 'EUR', + netRevenue: true, + ttl: 300, + referrer: '', + ad: '' + }]; + + it('should get the correct bid response by display ad', function () { + let bidRequest = [{ + 'method': 'GET', + 'url': ENDPOINT_URL, + 'data': { + 'bid_id': '30b31c1838de1e' + } + }]; + let result = spec.interpretResponse(serverResponse, bidRequest[0]); + expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); + }); + + it('handles empty bid response', function () { + let response = { + body: {} + }; + let result = spec.interpretResponse(response); + expect(result.length).to.equal(0); + }); + }); +}); diff --git a/test/spec/modules/emoteevBidAdapter_spec.js b/test/spec/modules/emoteevBidAdapter_spec.js new file mode 100644 index 00000000000..b6a62c16963 --- /dev/null +++ b/test/spec/modules/emoteevBidAdapter_spec.js @@ -0,0 +1,868 @@ +import { + assert, expect +} from 'chai'; +import { + ADAPTER_VERSION, + DOMAIN, + DOMAIN_DEVELOPMENT, + DOMAIN_STAGING, + domain, + BIDDER_PATH, + bidderUrl, + buildRequests, + conformBidRequest, + DEFAULT_ENV, + DEVELOPMENT, + EVENTS_PATH, + eventsUrl, + FOOTER, + gdprConsent, + getDeviceDimensions, + getDeviceInfo, + getDocumentDimensions, + getUserSyncs, + getViewDimensions, + IN_CONTENT, + interpretResponse, + isBidRequestValid, + ON_ADAPTER_CALLED, + ON_BID_WON, + ON_BIDDER_TIMEOUT, + onBidWon, + onAdapterCalled, + onTimeout, + OVERLAY, + PRODUCTION, + requestsPayload, + resolveDebug, + resolveEnv, + spec, + STAGING, + USER_SYNC_IFRAME_PATH, + USER_SYNC_IMAGE_PATH, + userSyncIframeUrl, + userSyncImageUrl, + validateSizes, + validateContext, + validateExternalId, + VENDOR_ID, + WALLPAPER, +} from 'modules/emoteevBidAdapter'; +import * as url from '../../../src/url'; +import * as utils from '../../../src/utils'; +import {config} from '../../../src/config'; + +const cannedValidBidRequests = [{ + adUnitCode: '/19968336/header-bid-tag-1', + auctionId: 'fcbf2b27-a951-496f-b5bb-1324ce7c0558', + bidId: '2b8de6572e8193', + bidRequestsCount: 1, + bidder: 'emoteev', + bidderRequestId: '1203b39fecc6a5', + crumbs: {pubcid: 'f3371d16-4e8b-42b5-a770-7e5be1fdf03d'}, + params: { + adSpaceId: 5084, + context: IN_CONTENT, + externalId: 42 + }, + sizes: [[300, 250], [250, 300], [300, 600]], + transactionId: '58dbd732-7a39-45f1-b23e-1c24051a941c', +}]; +const cannedBidderRequest = { + auctionId: 'fcbf2b27-a951-496f-b5bb-1324ce7c0558', + auctionStart: 1544200122837, + bidderCode: 'emoteev', + bidderRequestId: '1203b39fecc6a5', + doneCbCallCount: 0, + refererInfo: { + canonicalUrl: undefined, + numIframes: 0, + reachedTop: true, + referer: 'http://localhost:9999/integrationExamples/gpt/hello_world_emoteev.html', + stack: ['http://localhost:9999/integrationExamples/gpt/hello_world_emoteev.html'] + }, + start: 1544200012839, + timeout: 3000, + gdprConsent: { + gdprApplies: true, + vendorData: {vendorConsents: {[VENDOR_ID]: true}}, + } +}; +const serverResponse = + { + body: [ + { + requestId: cannedValidBidRequests[0].bidId, + cpm: 1, + width: cannedValidBidRequests[0].sizes[0][0], + height: cannedValidBidRequests[0].sizes[0][1], + ad: '
    ', + ttl: 360, + creativeId: 123, + netRevenue: false, + currency: 'EUR', + } + ] + }; + +describe('emoteevBidAdapter', function () { + describe('isBidRequestValid', function () { + it('should return true when valid', function () { + const validBid = { + bidder: 'emoteev', + bidId: '23a45b4e3', + params: { + adSpaceId: 12345, + context: IN_CONTENT, + externalId: 42 + }, + mediaTypes: { + banner: { + sizes: [[750, 200]] + } + }, + }; + expect(isBidRequestValid(validBid)).to.equal(true); + + expect(spec.isBidRequestValid(validBid)).to.exist.and.to.be.a('boolean'); + expect(spec.isBidRequestValid({})).to.exist.and.to.be.a('boolean'); + }); + + it('should return false when required params are invalid', function () { + expect(isBidRequestValid({ + bidder: '', // invalid bidder + params: { + adSpaceId: 12345, + context: IN_CONTENT, + externalId: 42 + }, + mediaTypes: { + banner: { + sizes: [[750, 200]] + } + }, + })).to.equal(false); + expect(isBidRequestValid({ + bidder: 'emoteev', + params: { + adSpaceId: '', // invalid adSpaceId + context: IN_CONTENT, + externalId: 42 + }, + mediaTypes: { + banner: { + sizes: [[750, 200]] + } + }, + })).to.equal(false); + expect(isBidRequestValid({ + bidder: 'emoteev', + params: { + adSpaceId: 12345, + context: 'something', // invalid context + externalId: 42 + }, + mediaTypes: { + banner: { + sizes: [[750, 200]] + } + }, + })).to.equal(false); + expect(isBidRequestValid({ + bidder: 'emoteev', + params: { + adSpaceId: 12345, + context: IN_CONTENT, + externalId: 'lol' // invalid externalId + }, + mediaTypes: { + banner: { + sizes: [[750, 200]] + } + }, + })).to.equal(false); + expect(isBidRequestValid({ + bidder: 'emoteev', + params: { + adSpaceId: 12345, + context: IN_CONTENT, + externalId: 42 + }, + mediaTypes: { + banner: { + sizes: [[750]] // invalid size + } + }, + })).to.equal(false); + }); + }); + + describe('buildRequests', function () { + const + env = DEFAULT_ENV, + debug = true, + currency = 'EUR', + request = buildRequests(env, debug, currency, cannedValidBidRequests, cannedBidderRequest); + + expect(request).to.exist.and.have.all.keys( + 'method', + 'url', + 'data', + ); + + expect(request.method).to.equal('POST'); + expect(request.url).to.equal(bidderUrl(env)); + + expect(spec.buildRequests(cannedValidBidRequests, cannedBidderRequest)).to.exist.and.to.be.an('object'); + }); + + describe('interpretResponse', function () { + it('bid objects from response', function () { + const bidResponses = interpretResponse(serverResponse); + expect(bidResponses).to.be.an('array').that.is.not.empty; + expect(bidResponses[0]).to.have.property('requestId', cannedValidBidRequests[0].bidId); + expect(bidResponses[0]).to.have.property('cpm', serverResponse.body[0].cpm); + expect(bidResponses[0]).to.have.property('width', serverResponse.body[0].width); + expect(bidResponses[0]).to.have.property('height', serverResponse.body[0].height); + expect(bidResponses[0]).to.have.property('ad', serverResponse.body[0].ad); + expect(bidResponses[0]).to.have.property('ttl', serverResponse.body[0].ttl); + expect(bidResponses[0]).to.have.property('creativeId', serverResponse.body[0].creativeId); + expect(bidResponses[0]).to.have.property('netRevenue', serverResponse.body[0].netRevenue); + expect(bidResponses[0]).to.have.property('currency', serverResponse.body[0].currency); + }); + }); + + describe('onAdapterCalled', function () { + const + bidRequest = cannedValidBidRequests[0], + url = onAdapterCalled(DEFAULT_ENV, bidRequest); + + expect(url).to.have.property('protocol'); + expect(url).to.have.property('hostname'); + expect(url).to.have.property('pathname', EVENTS_PATH); + expect(url).to.have.nested.property('search.eventName', ON_ADAPTER_CALLED); + expect(url).to.have.nested.property('search.pubcId', bidRequest.crumbs.pubcid); + expect(url).to.have.nested.property('search.bidId', bidRequest.bidId); + expect(url).to.have.nested.property('search.adSpaceId', bidRequest.params.adSpaceId); + expect(url).to.have.nested.property('search.cache_buster'); + }); + + describe('onBidWon', function () { + const + pubcId = cannedValidBidRequests[0].crumbs.pubcid, + bidObject = serverResponse.body[0], + url = onBidWon(DEFAULT_ENV, pubcId, bidObject); + + expect(url).to.have.property('protocol'); + expect(url).to.have.property('hostname'); + expect(url).to.have.property('pathname', EVENTS_PATH); + expect(url).to.have.nested.property('search.eventName', ON_BID_WON); + expect(url).to.have.nested.property('search.pubcId', pubcId); + expect(url).to.have.nested.property('search.bidId', bidObject.requestId); + expect(url).to.have.nested.property('search.cache_buster'); + }); + + describe('onTimeout', function () { + const + data = { + ...cannedValidBidRequests[0], + timeout: 123, + }, + url = onTimeout(DEFAULT_ENV, data); + + expect(url).to.have.property('protocol'); + expect(url).to.have.property('hostname'); + expect(url).to.have.property('pathname', EVENTS_PATH); + expect(url).to.have.nested.property('search.eventName', ON_BIDDER_TIMEOUT); + expect(url).to.have.nested.property('search.bidId', data.bidId); + expect(url).to.have.nested.property('search.pubcId', data.crumbs.pubcid); + expect(url).to.have.nested.property('search.adSpaceId', data.params.adSpaceId); + expect(url).to.have.nested.property('search.timeout', data.timeout); + expect(url).to.have.nested.property('search.cache_buster'); + }); + + describe('getUserSyncs', function () { + expect(getUserSyncs( + DEFAULT_ENV, + { + iframeEnabled: false, + pixelEnabled: false + })).to.deep.equal([]); + expect(getUserSyncs( + PRODUCTION, + { + iframeEnabled: false, + pixelEnabled: true + })).to.deep.equal([{ + type: 'image', + url: userSyncImageUrl(PRODUCTION) + }]); + expect(getUserSyncs( + STAGING, + { + iframeEnabled: true, + pixelEnabled: false + })).to.deep.equal([{ + type: 'iframe', + url: userSyncIframeUrl(STAGING) + }]); + expect(getUserSyncs( + DEVELOPMENT, + { + iframeEnabled: true, + pixelEnabled: true + })).to.deep.equal([{ + type: 'image', + url: userSyncImageUrl(DEVELOPMENT) + }, { + type: 'iframe', + url: userSyncIframeUrl(DEVELOPMENT) + }]); + }); + + describe('domain', function () { + expect(domain(null)).to.deep.equal(DOMAIN); + expect(domain('anything')).to.deep.equal(DOMAIN); + expect(domain(PRODUCTION)).to.deep.equal(DOMAIN); + expect(domain(STAGING)).to.deep.equal(DOMAIN_STAGING); + expect(domain(DEVELOPMENT)).to.deep.equal(DOMAIN_DEVELOPMENT); + }); + + describe('eventsUrl', function () { + expect(eventsUrl(null)).to.deep.equal(url.format({ + protocol: 'https', + hostname: domain(DEFAULT_ENV), + pathname: EVENTS_PATH + })); + expect(eventsUrl('anything')).to.deep.equal(url.format({ + protocol: 'https', + hostname: domain(DEFAULT_ENV), + pathname: EVENTS_PATH + })); + expect(eventsUrl(PRODUCTION)).to.deep.equal(url.format({ + protocol: 'https', + hostname: domain(PRODUCTION), + pathname: EVENTS_PATH + })); + expect(eventsUrl(STAGING)).to.deep.equal(url.format({ + protocol: 'https', + hostname: domain(STAGING), + pathname: EVENTS_PATH + })); + expect(eventsUrl(DEVELOPMENT)).to.deep.equal(url.format({ + hostname: domain(DEVELOPMENT), + pathname: EVENTS_PATH + })); + }); + + describe('bidderUrl', function () { + expect(bidderUrl(null)).to.deep.equal(url.format({ + protocol: 'https', + hostname: domain(DEFAULT_ENV), + pathname: BIDDER_PATH + })); + expect(bidderUrl('anything')).to.deep.equal(url.format({ + protocol: 'https', + hostname: domain(DEFAULT_ENV), + pathname: BIDDER_PATH + })); + expect(bidderUrl(PRODUCTION)).to.deep.equal(url.format({ + protocol: 'https', + hostname: domain(PRODUCTION), + pathname: BIDDER_PATH + })); + expect(bidderUrl(STAGING)).to.deep.equal(url.format({ + protocol: 'https', + hostname: domain(STAGING), + pathname: BIDDER_PATH + })); + expect(bidderUrl(DEVELOPMENT)).to.deep.equal(url.format({ + hostname: domain(DEVELOPMENT), + pathname: BIDDER_PATH + })); + }); + + describe('userSyncIframeUrl', function () { + expect(userSyncIframeUrl(null)).to.deep.equal(url.format({ + protocol: 'https', + hostname: domain(DEFAULT_ENV), + pathname: USER_SYNC_IFRAME_PATH + })); + expect(userSyncIframeUrl('anything')).to.deep.equal(url.format({ + protocol: 'https', + hostname: domain(DEFAULT_ENV), + pathname: USER_SYNC_IFRAME_PATH + })); + expect(userSyncIframeUrl(PRODUCTION)).to.deep.equal(url.format({ + protocol: 'https', + hostname: domain(PRODUCTION), + pathname: USER_SYNC_IFRAME_PATH + })); + expect(userSyncIframeUrl(STAGING)).to.deep.equal(url.format({ + protocol: 'https', + hostname: domain(STAGING), + pathname: USER_SYNC_IFRAME_PATH + })); + expect(userSyncIframeUrl(DEVELOPMENT)).to.deep.equal(url.format({ + hostname: domain(DEVELOPMENT), + pathname: USER_SYNC_IFRAME_PATH + })); + }); + + describe('userSyncImageUrl', function () { + expect(userSyncImageUrl(null)).to.deep.equal(url.format({ + protocol: 'https', + hostname: domain(DEFAULT_ENV), + pathname: USER_SYNC_IMAGE_PATH + })); + expect(userSyncImageUrl('anything')).to.deep.equal(url.format({ + protocol: 'https', + hostname: domain(DEFAULT_ENV), + pathname: USER_SYNC_IMAGE_PATH + })); + expect(userSyncImageUrl(PRODUCTION)).to.deep.equal(url.format({ + protocol: 'https', + hostname: domain(PRODUCTION), + pathname: USER_SYNC_IMAGE_PATH + })); + expect(userSyncImageUrl(STAGING)).to.deep.equal(url.format({ + protocol: 'https', + hostname: domain(STAGING), + pathname: USER_SYNC_IMAGE_PATH + })); + expect(userSyncImageUrl(DEVELOPMENT)).to.deep.equal(url.format({ + hostname: domain(DEVELOPMENT), + pathname: USER_SYNC_IMAGE_PATH + })); + }); + + describe('conformBidRequest', function () { + expect(conformBidRequest(cannedValidBidRequests[0])).to.deep.equal({ + params: cannedValidBidRequests[0].params, + crumbs: cannedValidBidRequests[0].crumbs, + sizes: cannedValidBidRequests[0].sizes, + bidId: cannedValidBidRequests[0].bidId, + bidderRequestId: cannedValidBidRequests[0].bidderRequestId, + }); + }); + + describe('gdprConsent', function () { + describe('gdpr applies, consent given', function () { + const bidderRequest = { + ...cannedBidderRequest, + gdprConsent: { + gdprApplies: true, + vendorData: {vendorConsents: {[VENDOR_ID]: true}}, + } + }; + expect(gdprConsent(bidderRequest)).to.deep.equal(true); + }); + describe('gdpr applies, consent withdrawn', function () { + const bidderRequest = { + ...cannedBidderRequest, + gdprConsent: { + gdprApplies: true, + vendorData: {vendorConsents: {[VENDOR_ID]: false}}, + } + }; + expect(gdprConsent(bidderRequest)).to.deep.equal(false); + }); + describe('gdpr applies, consent unknown', function () { + const bidderRequest = { + ...cannedBidderRequest, + gdprConsent: { + gdprApplies: true, + vendorData: {}, + } + }; + expect(gdprConsent(bidderRequest)).to.deep.equal(undefined); + }); + }); + + describe('requestsPayload', function () { + const + currency = 'EUR', + debug = true; + + const payload = requestsPayload(debug, currency, cannedValidBidRequests, cannedBidderRequest); + + expect(payload).to.exist.and.have.all.keys( + 'akPbjsVersion', + 'bidRequests', + 'currency', + 'debug', + 'language', + 'refererInfo', + 'deviceInfo', + 'userAgent', + 'gdprApplies', + 'gdprConsent', + ); + + expect(payload.bidRequests[0]).to.exist.and.have.all.keys( + 'params', + 'crumbs', + 'sizes', + 'bidId', + 'bidderRequestId', + ); + + expect(payload.akPbjsVersion).to.deep.equal(ADAPTER_VERSION); + expect(payload.bidRequests[0].params).to.deep.equal(cannedValidBidRequests[0].params); + expect(payload.bidRequests[0].crumbs).to.deep.equal(cannedValidBidRequests[0].crumbs); + expect(payload.bidRequests[0].mediaTypes).to.deep.equal(cannedValidBidRequests[0].mediaTypes); + expect(payload.bidRequests[0].bidId).to.deep.equal(cannedValidBidRequests[0].bidId); + expect(payload.bidRequests[0].bidderRequestId).to.deep.equal(cannedValidBidRequests[0].bidderRequestId); + expect(payload.currency).to.deep.equal(currency); + expect(payload.debug).to.deep.equal(debug); + expect(payload.language).to.deep.equal(navigator.language); + expect(payload.deviceInfo).to.exist.and.have.all.keys( + 'browserWidth', + 'browserHeight', + 'deviceWidth', + 'deviceHeight', + 'documentWidth', + 'documentHeight', + 'webGL', + ); + expect(payload.userAgent).to.deep.equal(navigator.userAgent); + expect(payload.gdprApplies).to.deep.equal(cannedBidderRequest.gdprConsent.gdprApplies); + }); + + describe('getViewDimensions', function () { + const window = { + innerWidth: 1024, + innerHeight: 768 + }; + const documentWithElement = { + documentElement: + { + clientWidth: 512, + clientHeight: 384 + } + }; + const documentWithBody = { + body: + { + clientWidth: 512, + clientHeight: 384 + } + }; + expect(getViewDimensions(window, documentWithElement)).to.deep.equal({ + width: 1024, + height: 768 + }); + expect(getViewDimensions(window, documentWithBody)).to.deep.equal({width: 1024, height: 768}); + expect(getViewDimensions(window, documentWithElement)).to.deep.equal({ + width: 1024, + height: 768 + }); + expect(getViewDimensions(window, documentWithBody)).to.deep.equal({width: 1024, height: 768}); + expect(getViewDimensions({}, documentWithElement)).to.deep.equal({width: 512, height: 384}); + expect(getViewDimensions({}, documentWithBody)).to.deep.equal({width: 512, height: 384}); + }); + + describe('getDeviceDimensions', function () { + const window = {screen: {width: 1024, height: 768}}; + expect(getDeviceDimensions(window)).to.deep.equal({width: 1024, height: 768}); + expect(getDeviceDimensions({})).to.deep.equal({width: '', height: ''}); + }); + + describe('getDocumentDimensions', function () { + expect(getDocumentDimensions({ + documentElement: { + clientWidth: 1, + clientHeight: 1, + offsetWidth: 0, + offsetHeight: 0, + scrollWidth: 0, + scrollHeight: 0, + }, + })).to.deep.equal({width: 1, height: 1}); + + expect(getDocumentDimensions({ + documentElement: { + clientWidth: 1, + clientHeight: 1, + offsetWidth: 0, + offsetHeight: 0, + scrollWidth: 0, + scrollHeight: 0, + }, + body: { + scrollHeight: 0, + offsetHeight: 0, + } + })).to.deep.equal({width: 1, height: 1}); + + expect(getDocumentDimensions({ + documentElement: { + clientWidth: 0, + clientHeight: 0, + offsetWidth: 1, + offsetHeight: 1, + scrollWidth: 0, + scrollHeight: 0, + }, + body: { + scrollHeight: 0, + offsetHeight: 0, + } + })).to.deep.equal({width: 1, height: 1}); + + expect(getDocumentDimensions({ + documentElement: { + clientWidth: 0, + clientHeight: 0, + offsetWidth: 0, + offsetHeight: 0, + scrollWidth: 1, + scrollHeight: 1, + }, + body: { + scrollHeight: 0, + offsetHeight: 0, + } + })).to.deep.equal({width: 1, height: 1}); + + expect(getDocumentDimensions({ + documentElement: { + clientWidth: undefined, + clientHeight: undefined, + offsetWidth: undefined, + offsetHeight: undefined, + scrollWidth: undefined, + scrollHeight: undefined, + }, + body: { + scrollHeight: undefined, + offsetHeight: undefined, + } + })).to.deep.equal({width: '', height: ''}); + }); + + // describe('isWebGLEnabled', function () { + // it('handles no webgl', function () { + // const + // document = new Document(), + // canvas = sinon.createStubInstance(HTMLCanvasElement); + // sinon.stub(document, 'createElement').withArgs('canvas').returns(canvas); + // canvas.getContext.withArgs('webgl').returns(undefined); + // canvas.getContext.withArgs('experimental-webgl').returns(undefined); + // expect(isWebGLEnabled(document)).to.equal(false); + // }); + // + // it('handles webgl exception', function () { + // const + // document = new Document(), + // canvas = sinon.createStubInstance(HTMLCanvasElement); + // sinon.stub(document, 'createElement').withArgs('canvas').returns(canvas); + // canvas.getContext.withArgs('webgl').throws(DOMException); + // expect(isWebGLEnabled(document)).to.equal(false); + // }); + // + // it('handles experimental webgl', function () { + // const + // document = new Document(), + // canvas = sinon.createStubInstance(HTMLCanvasElement); + // sinon.stub(document, 'createElement').withArgs('canvas').returns(canvas); + // canvas.getContext.withArgs('webgl').returns(undefined); + // canvas.getContext.withArgs('experimental-webgl').returns(true); + // expect(isWebGLEnabled(document)).to.equal(true); + // }); + // + // it('handles experimental webgl exception', function () { + // const + // document = new Document(), + // canvas = sinon.createStubInstance(HTMLCanvasElement); + // sinon.stub(document, 'createElement').withArgs('canvas').returns(canvas); + // canvas.getContext.withArgs('webgl').returns(undefined); + // canvas.getContext.withArgs('experimental-webgl').throws(DOMException); + // expect(isWebGLEnabled(document)).to.equal(false); + // }); + // + // it('handles webgl', function () { + // const + // document = new Document(), + // canvas = sinon.createStubInstance(HTMLCanvasElement); + // sinon.stub(document, 'createElement').withArgs('canvas').returns(canvas); + // canvas.getContext.withArgs('webgl').returns(true); + // expect(isWebGLEnabled(document)).to.equal(true); + // }); + // }); + + describe('getDeviceInfo', function () { + expect(getDeviceInfo( + {width: 1, height: 2}, + {width: 3, height: 4}, + {width: 5, height: 6}, + true + )).to.deep.equal({ + deviceWidth: 1, + deviceHeight: 2, + browserWidth: 3, + browserHeight: 4, + documentWidth: 5, + documentHeight: 6, + webGL: true + }); + }); + + describe('resolveEnv', function () { + it('defaults to production', function () { + expect(resolveEnv({}, null)).to.deep.equal(DEFAULT_ENV); + }); + expect(resolveEnv({}, PRODUCTION)).to.deep.equal(PRODUCTION); + expect(resolveEnv({}, STAGING)).to.deep.equal(STAGING); + expect(resolveEnv({}, DEVELOPMENT)).to.deep.equal(DEVELOPMENT); + expect(resolveEnv({emoteev: {env: PRODUCTION}}, null)).to.deep.equal(PRODUCTION); + expect(resolveEnv({emoteev: {env: STAGING}}, null)).to.deep.equal(STAGING); + expect(resolveEnv({emoteev: {env: DEVELOPMENT}}, null)).to.deep.equal(DEVELOPMENT); + it('prioritizes parameter over configuration', function () { + expect(resolveEnv({emoteev: {env: STAGING}}, DEVELOPMENT)).to.deep.equal(DEVELOPMENT); + }); + }); + + describe('resolveDebug', function () { + it('defaults to production', function () { + expect(resolveDebug({}, null)).to.deep.equal(false); + }); + expect(resolveDebug({}, 'false')).to.deep.equal(false); + expect(resolveDebug({}, 'true')).to.deep.equal(true); + expect(resolveDebug({debug: true}, null)).to.deep.equal(true); + it('prioritizes parameter over configuration', function () { + expect(resolveDebug({debug: true}, 'false')).to.deep.equal(false); + }); + }); + + describe('side effects', function () { + let triggerPixelSpy; + let getCookieSpy; + let getConfigSpy; + let getParameterByNameSpy; + beforeEach(function () { + triggerPixelSpy = sinon.spy(utils, 'triggerPixel'); + getCookieSpy = sinon.spy(utils, 'getCookie'); + getConfigSpy = sinon.spy(config, 'getConfig'); + getParameterByNameSpy = sinon.spy(utils, 'getParameterByName'); + }); + afterEach(function () { + triggerPixelSpy.restore(); + getCookieSpy.restore(); + getConfigSpy.restore(); + getParameterByNameSpy.restore(); + }); + + describe('isBidRequestValid', function () { + it('has intended side-effects', function () { + const validBidRequest = { + bidder: 'emoteev', + bidId: '23a45b4e3', + params: { + adSpaceId: 12345, + }, + mediaTypes: { + banner: { + sizes: [[750, 200]] + } + }, + }; + spec.isBidRequestValid(validBidRequest); + sinon.assert.notCalled(utils.triggerPixel); + sinon.assert.notCalled(utils.getCookie); + sinon.assert.notCalled(config.getConfig); + sinon.assert.notCalled(utils.getParameterByName); + }); + }); + describe('isBidRequestValid empty request', function() { + it('has intended side-effects empty request', function () { + const invalidBidRequest = {}; + spec.isBidRequestValid(invalidBidRequest); + sinon.assert.notCalled(utils.triggerPixel); + sinon.assert.notCalled(utils.getCookie); + sinon.assert.notCalled(config.getConfig); + sinon.assert.notCalled(utils.getParameterByName); + }); + }); + describe('buildRequests', function () { + it('has intended side-effects', function () { + spec.buildRequests(cannedValidBidRequests, cannedBidderRequest); + sinon.assert.notCalled(utils.triggerPixel); + sinon.assert.notCalled(utils.getCookie); + sinon.assert.callCount(config.getConfig, 3); + sinon.assert.callCount(utils.getParameterByName, 2); + }); + }); + describe('interpretResponse', function () { + it('has intended side-effects', function () { + spec.interpretResponse(serverResponse); + sinon.assert.notCalled(utils.triggerPixel); + sinon.assert.notCalled(utils.getCookie); + sinon.assert.notCalled(config.getConfig); + sinon.assert.notCalled(utils.getParameterByName); + }); + }); + describe('onBidWon', function () { + it('has intended side-effects', function () { + const bidObject = serverResponse.body[0]; + spec.onBidWon(bidObject); + sinon.assert.calledOnce(utils.triggerPixel); + sinon.assert.calledOnce(utils.getCookie); + sinon.assert.calledOnce(config.getConfig); + sinon.assert.calledOnce(utils.getParameterByName); + }); + }); + describe('onTimeout', function () { + it('has intended side-effects', function () { + spec.onTimeout(cannedValidBidRequests[0]); + sinon.assert.calledOnce(utils.triggerPixel); + sinon.assert.notCalled(utils.getCookie); + sinon.assert.calledOnce(config.getConfig); + sinon.assert.calledOnce(utils.getParameterByName); + }); + }); + describe('getUserSyncs', function () { + it('has intended side-effects', function () { + spec.getUserSyncs({}); + sinon.assert.notCalled(utils.triggerPixel); + sinon.assert.notCalled(utils.getCookie); + sinon.assert.calledOnce(config.getConfig); + sinon.assert.calledOnce(utils.getParameterByName); + }); + }); + }); + + describe('validateSizes', function () { + it('only accepts valid array of sizes', function () { + expect(validateSizes([])).to.deep.equal(false); + expect(validateSizes([[]])).to.deep.equal(false); + expect(validateSizes([[450, 450], undefined])).to.deep.equal(false); + expect(validateSizes([[450, 450], 'size'])).to.deep.equal(false); + expect(validateSizes([[1, 1]])).to.deep.equal(true); + expect(validateSizes([[1, 1], [450, 450]])).to.deep.equal(true); + }); + }); + + describe('validateContext', function () { + it('only accepts valid context', function () { + expect(validateContext(IN_CONTENT)).to.deep.equal(true); + expect(validateContext(FOOTER)).to.deep.equal(true); + expect(validateContext(OVERLAY)).to.deep.equal(true); + expect(validateContext(WALLPAPER)).to.deep.equal(true); + expect(validateContext(null)).to.deep.equal(false); + expect(validateContext('anything else')).to.deep.equal(false); + }); + }); + + describe('validateExternalId', function () { + it('only accepts a positive integer or null', function () { + expect(validateExternalId(0)).to.deep.equal(false); + expect(validateExternalId(42)).to.deep.equal(true); + expect(validateExternalId(42.0)).to.deep.equal(true); // edge case: valid externalId + expect(validateExternalId(3.14159)).to.deep.equal(false); + expect(validateExternalId('externalId')).to.deep.equal(false); + expect(validateExternalId(undefined)).to.deep.equal(true); + expect(validateExternalId(null)).to.deep.equal(true); + }); + }); +}); diff --git a/test/spec/modules/emx_digitalBidAdapter_spec.js b/test/spec/modules/emx_digitalBidAdapter_spec.js new file mode 100644 index 00000000000..80fd12a237c --- /dev/null +++ b/test/spec/modules/emx_digitalBidAdapter_spec.js @@ -0,0 +1,550 @@ +import { expect } from 'chai'; +import { spec } from 'modules/emx_digitalBidAdapter'; +import * as utils from 'src/utils'; +import { newBidder } from 'src/adapters/bidderFactory'; + +describe('emx_digital Adapter', function () { + describe('callBids', function () { + const adapter = newBidder(spec); + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function () { + describe('banner request validity', function () { + let bid = { + 'bidder': 'emx_digital', + 'params': { + 'tagid': '25251' + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250]] + } + }, + 'adUnitCode': 'adunit-code', + 'sizes': [ + [300, 250], + [300, 600] + ], + 'bidId': '30b31c2501de1e', + 'bidderRequestId': '22edbae3120bf6', + 'auctionId': '1d1a01234a475' + }; + let badBid = { + 'bidder': 'emx_digital', + 'params': { + 'tagid': '25251' + }, + 'mediaTypes': { + 'banner': { + } + }, + 'adUnitCode': 'adunit-code', + 'bidId': '30b31c2501de1e', + 'bidderRequestId': '22edbae3120bf6', + 'auctionId': '1d1a01234a475' + }; + let noBid = {}; + let otherBid = { + 'bidder': 'emxdigital', + 'params': { + 'tagid': '25251' + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250]] + } + }, + 'adUnitCode': 'adunit-code', + 'sizes': [ + [300, 250], + [300, 600] + ], + 'bidId': '30b31c2501de1e', + 'bidderRequestId': '22edbae3120bf6', + 'auctionId': '1d1a01234a475' + }; + let noMediaSizeBid = { + 'bidder': 'emxdigital', + 'params': { + 'tagid': '25251' + }, + 'mediaTypes': { + 'banner': {} + }, + 'adUnitCode': 'adunit-code', + 'sizes': [ + [300, 250], + [300, 600] + ], + 'bidId': '30b31c2501de1e', + 'bidderRequestId': '22edbae3120bf6', + 'auctionId': '1d1a01234a475' + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + expect(spec.isBidRequestValid(badBid)).to.equal(false); + expect(spec.isBidRequestValid(noBid)).to.equal(false); + expect(spec.isBidRequestValid(otherBid)).to.equal(false); + expect(spec.isBidRequestValid(noMediaSizeBid)).to.equal(false); + }); + }); + + describe('video request validity', function () { + let bid = { + 'bidder': 'emx_digital', + 'params': { + 'tagid': '25251', + 'video': {} + }, + 'mediaTypes': { + 'video': { + 'context': 'instream', + 'playerSize': [640, 480] + } + }, + 'adUnitCode': 'adunit-code', + 'sizes': [ + [300, 250], + [300, 600] + ], + 'bidId': '30b31c2501de1e', + 'bidderRequestId': '22edbae3120bf6', + 'auctionId': '1d1a01234a475' + }; + let noInstreamBid = { + 'bidder': 'emx_digital', + 'params': { + 'tagid': '25251', + 'video': { + 'protocols': [1, 7] + } + }, + 'mediaTypes': { + 'video': { + 'context': 'something_random' + } + }, + 'adUnitCode': 'adunit-code', + 'sizes': [ + [300, 250], + [300, 600] + ], + 'bidId': '30b31c2501de1e', + 'bidderRequestId': '22edbae3120bf6', + 'auctionId': '1d1a01234a475' + }; + + let outstreamBid = { + 'bidder': 'emx_digital', + 'params': { + 'tagid': '25251', + 'video': {} + }, + 'mediaTypes': { + 'video': { + 'context': 'outstream', + 'playerSize': [640, 480] + } + }, + 'adUnitCode': 'adunit-code', + 'sizes': [ + [300, 250], + [300, 600] + ], + 'bidId': '30b31c2501de1e', + 'bidderRequestId': '22edbae3120bf6', + 'auctionId': '1d1a01234a475' + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + expect(spec.isBidRequestValid(noInstreamBid)).to.equal(false); + expect(spec.isBidRequestValid(outstreamBid)).to.equal(true); + }); + + it('should contain tagid param', function () { + expect(spec.isBidRequestValid({ + bidder: 'emx_digital', + params: {}, + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + } + })).to.equal(false); + expect(spec.isBidRequestValid({ + bidder: 'emx_digital', + params: { + tagid: '' + }, + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + } + })).to.equal(false); + expect(spec.isBidRequestValid({ + bidder: 'emx_digital', + params: { + tagid: '123' + }, + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + } + })).to.equal(true); + }); + }); + }); + + describe('buildRequests', function () { + let bidderRequest = { + 'bidderCode': 'emx_digital', + 'auctionId': 'e19f1eff-8b27-42a6-888d-9674e5a6130c', + 'bidderRequestId': '22edbae3120bf6', + 'timeout': 1500, + 'refererInfo': { + 'numIframes': 0, + 'reachedTop': true, + 'referer': 'https://example.com/index.html?pbjs_debug=true' + }, + 'bids': [{ + 'bidder': 'emx_digital', + 'params': { + 'tagid': '25251' + }, + 'adUnitCode': 'adunit-code', + 'mediaTypes': { + 'banner': { + 'sizes': [ + [300, 250], + [300, 600] + ] + } + }, + 'sizes': [ + [300, 250], + [300, 600] + ], + 'bidId': '30b31c2501de1e', + 'auctionId': 'e19f1eff-8b27-42a6-888d-9674e5a6130c', + 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + }] + }; + let request = spec.buildRequests(bidderRequest.bids, bidderRequest); + + it('sends bid request to ENDPOINT via POST', function () { + expect(request.method).to.equal('POST'); + }); + + it('contains the correct options', function () { + expect(request.options.withCredentials).to.equal(true); + }); + + it('contains a properly formatted endpoint url', function () { + const url = request.url.split('?'); + const queryParams = url[1].split('&'); + expect(queryParams[0]).to.match(new RegExp('^t=\d*', 'g')); + expect(queryParams[1]).to.match(new RegExp('^ts=\d*', 'g')); + }); + + it('builds with bid floor', function () { + const bidRequestWithBidFloor = utils.deepClone(bidderRequest.bids); + bidRequestWithBidFloor[0].params.bidfloor = 1; + const requestWithFloor = spec.buildRequests(bidRequestWithBidFloor, bidderRequest); + const data = JSON.parse(requestWithFloor.data); + expect(data.imp[0].bidfloor).to.equal(bidRequestWithBidFloor[0].params.bidfloor); + }); + + it('builds request properly', function () { + const data = JSON.parse(request.data); + expect(Array.isArray(data.imp)).to.equal(true); + expect(data.id).to.equal(bidderRequest.auctionId); + expect(data.imp.length).to.equal(1); + expect(data.imp[0].id).to.equal('30b31c2501de1e'); + expect(data.imp[0].tid).to.equal('d7b773de-ceaa-484d-89ca-d9f51b8d61ec'); + expect(data.imp[0].tagid).to.equal('25251'); + expect(data.imp[0].secure).to.equal(0); + expect(data.imp[0].vastXml).to.equal(undefined); + }); + + it('properly sends site information and protocol', function () { + request = spec.buildRequests(bidderRequest.bids, bidderRequest); + request = JSON.parse(request.data); + expect(request.site.domain).to.equal(utils.getTopWindowLocation().hostname); + expect(decodeURIComponent(request.site.page)).to.equal(bidderRequest.refererInfo.referer); + expect(request.site.ref).to.equal(window.top.document.referrer); + }); + + it('builds correctly formatted request banner object', function () { + let bidRequestWithBanner = utils.deepClone(bidderRequest.bids); + let request = spec.buildRequests(bidRequestWithBanner, bidderRequest); + const data = JSON.parse(request.data); + expect(data.imp[0].video).to.equal(undefined); + expect(data.imp[0].banner).to.exist.and.to.be.a('object'); + expect(data.imp[0].banner.w).to.equal(bidRequestWithBanner[0].mediaTypes.banner.sizes[0][0]); + expect(data.imp[0].banner.h).to.equal(bidRequestWithBanner[0].mediaTypes.banner.sizes[0][1]); + expect(data.imp[0].banner.format[0].w).to.equal(bidRequestWithBanner[0].mediaTypes.banner.sizes[0][0]); + expect(data.imp[0].banner.format[0].h).to.equal(bidRequestWithBanner[0].mediaTypes.banner.sizes[0][1]); + expect(data.imp[0].banner.format[1].w).to.equal(bidRequestWithBanner[0].mediaTypes.banner.sizes[1][0]); + expect(data.imp[0].banner.format[1].h).to.equal(bidRequestWithBanner[0].mediaTypes.banner.sizes[1][1]); + }); + + it('builds correctly formatted request video object for instream', function () { + let bidRequestWithVideo = utils.deepClone(bidderRequest.bids); + bidRequestWithVideo[0].mediaTypes = { + video: { + context: 'instream', + playerSize: [[640, 480]] + }, + }; + bidRequestWithVideo[0].params.video = {}; + let request = spec.buildRequests(bidRequestWithVideo, bidderRequest); + const data = JSON.parse(request.data); + expect(data.imp[0].video).to.exist.and.to.be.a('object'); + expect(data.imp[0].video.w).to.equal(bidRequestWithVideo[0].mediaTypes.video.playerSize[0][0]); + expect(data.imp[0].video.h).to.equal(bidRequestWithVideo[0].mediaTypes.video.playerSize[0][1]); + }); + + it('builds correctly formatted request video object for outstream', function () { + let bidRequestWithOutstreamVideo = utils.deepClone(bidderRequest.bids); + bidRequestWithOutstreamVideo[0].mediaTypes = { + video: { + context: 'outstream', + playerSize: [[640, 480]] + }, + }; + bidRequestWithOutstreamVideo[0].params.video = {}; + let request = spec.buildRequests(bidRequestWithOutstreamVideo, bidderRequest); + const data = JSON.parse(request.data); + expect(data.imp[0].video).to.exist.and.to.be.a('object'); + expect(data.imp[0].video.w).to.equal(bidRequestWithOutstreamVideo[0].mediaTypes.video.playerSize[0][0]); + expect(data.imp[0].video.h).to.equal(bidRequestWithOutstreamVideo[0].mediaTypes.video.playerSize[0][1]); + }); + + it('shouldn\'t contain a user obj without GDPR information', function () { + let request = spec.buildRequests(bidderRequest.bids, bidderRequest) + request = JSON.parse(request.data) + expect(request).to.not.have.property('user'); + }); + + it('should have the right gdpr info when enabled', function () { + let consentString = 'OIJSZsOAFsABAB8EMXZZZZZ+A=='; + bidderRequest.gdprConsent = { + 'consentString': consentString, + 'gdprApplies': true + }; + let request = spec.buildRequests(bidderRequest.bids, bidderRequest); + + request = JSON.parse(request.data) + expect(request.regs.ext).to.have.property('gdpr', 1); + expect(request.user.ext).to.have.property('consent', consentString); + }); + + it('should\'t contain consent string if gdpr isn\'t applied', function () { + bidderRequest.gdprConsent = { + 'gdprApplies': false + }; + let request = spec.buildRequests(bidderRequest.bids, bidderRequest); + request = JSON.parse(request.data) + expect(request.regs.ext).to.have.property('gdpr', 0); + expect(request).to.not.have.property('user'); + }); + }); + + describe('interpretResponse', function () { + let bid = { + 'bidder': 'emx_digital', + 'params': { + 'tagid': '25251', + 'video': {} + }, + 'mediaTypes': { + 'video': { + 'context': 'instream', + 'playerSize': [640, 480] + } + }, + 'adUnitCode': 'adunit-code', + 'sizes': [ + [300, 250], + [300, 600] + ], + 'bidId': '30b31c2501de1e', + 'bidderRequestId': '22edbae3120bf6', + 'auctionId': '1d1a01234a475' + }; + + const serverResponse = { + 'id': '12819a18-56e1-4256-b836-b69a10202668', + 'seatbid': [{ + 'bid': [{ + 'adid': '123456abcde', + 'adm': '', + 'crid': '3434abab34', + 'h': 250, + 'id': '987654321cba', + 'price': 0.5, + 'ttl': 300, + 'w': 300 + }], + 'seat': '1356' + }, { + 'bid': [{ + 'adid': '123456abcdf', + 'adm': '', + 'crid': '3434abab35', + 'h': 600, + 'id': '987654321cba', + 'price': 0.5, + 'ttl': 300, + 'w': 300 + }] + }] + }; + + const expectedResponse = [{ + 'requestId': '12819a18-56e1-4256-b836-b69a10202668', + 'cpm': 0.5, + 'width': 300, + 'height': 250, + 'creativeId': '3434abab34', + 'dealId': null, + 'currency': 'USD', + 'netRevneue': true, + 'mediaType': 'banner', + 'ad': '', + 'ttl': 300 + }, { + 'requestId': '12819a18-56e1-4256-b836-b69a10202668', + 'cpm': 0.7, + 'width': 300, + 'height': 600, + 'creativeId': '3434abab35', + 'dealId': null, + 'currency': 'USD', + 'netRevneue': true, + 'mediaType': 'banner', + 'ad': '', + 'ttl': 300 + }]; + + it('should properly format bid response', function () { + let result = spec.interpretResponse({ + body: serverResponse + }); + expect(Object.keys(result[0]).length).to.equal(Object.keys(expectedResponse[0]).length); + expect(Object.keys(result[0]).requestId).to.equal(Object.keys(expectedResponse[0]).requestId); + expect(Object.keys(result[0]).bidderCode).to.equal(Object.keys(expectedResponse[0]).bidderCode); + expect(Object.keys(result[0]).cpm).to.equal(Object.keys(expectedResponse[0]).cpm); + expect(Object.keys(result[0]).creativeId).to.equal(Object.keys(expectedResponse[0]).creativeId); + expect(Object.keys(result[0]).width).to.equal(Object.keys(expectedResponse[0]).width); + expect(Object.keys(result[0]).height).to.equal(Object.keys(expectedResponse[0]).height); + expect(Object.keys(result[0]).ttl).to.equal(Object.keys(expectedResponse[0]).ttl); + expect(Object.keys(result[0]).adId).to.equal(Object.keys(expectedResponse[0]).adId); + expect(Object.keys(result[0]).currency).to.equal(Object.keys(expectedResponse[0]).currency); + expect(Object.keys(result[0]).netRevenue).to.equal(Object.keys(expectedResponse[0]).netRevenue); + expect(Object.keys(result[0]).ad).to.equal(Object.keys(expectedResponse[0]).ad); + }); + + it('should return multiple bids', function () { + let result = spec.interpretResponse({ + body: serverResponse + }); + expect(Array.isArray(result.seatbid)) + + const ad0 = result[0]; + const ad1 = result[1]; + expect(ad0.ad).to.equal(serverResponse.seatbid[0].bid[0].adm); + expect(ad0.cpm).to.equal(serverResponse.seatbid[0].bid[0].price); + expect(ad0.creativeId).to.equal(serverResponse.seatbid[0].bid[0].crid); + expect(ad0.currency).to.equal('USD'); + expect(ad0.netRevenue).to.equal(true); + expect(ad0.requestId).to.equal(serverResponse.seatbid[0].bid[0].id); + expect(ad0.ttl).to.equal(300); + + expect(ad1.ad).to.equal(serverResponse.seatbid[1].bid[0].adm); + expect(ad1.cpm).to.equal(serverResponse.seatbid[1].bid[0].price); + expect(ad1.creativeId).to.equal(serverResponse.seatbid[1].bid[0].crid); + expect(ad1.currency).to.equal('USD'); + expect(ad1.netRevenue).to.equal(true); + expect(ad1.requestId).to.equal(serverResponse.seatbid[1].bid[0].id); + expect(ad1.ttl).to.equal(300); + }); + + it('returns a banner bid for non-xml creatives', function () { + let result = spec.interpretResponse({ + body: serverResponse + }, { bidRequest: bid } + ); + const ad0 = result[0]; + const ad1 = result[1]; + expect(ad0.mediaType).to.equal('banner'); + expect(ad0.ad.indexOf(''; + serverResponse.seatbid[1].bid[0].adm = ''; + + let result = spec.interpretResponse({ + body: serverResponse + }, { bidRequest: bid } + ); + const ad0 = result[0]; + const ad1 = result[1]; + expect(ad0.mediaType).to.equal('video'); + expect(ad0.ad.indexOf(' -1).to.equal(true); + expect(ad0.vastXml).to.equal(serverResponse.seatbid[0].bid[0].adm); + expect(ad0.ad).to.exist.and.to.be.a('string'); + expect(ad1.mediaType).to.equal('video'); + expect(ad1.ad.indexOf(' -1).to.equal(true); + expect(ad1.vastXml).to.equal(serverResponse.seatbid[1].bid[0].adm); + expect(ad1.ad).to.exist.and.to.be.a('string'); + }); + + it('handles nobid responses', function () { + let serverResponse = { + 'bids': [] + }; + + let result = spec.interpretResponse({ + body: serverResponse + }); + expect(result.length).to.equal(0); + }); + + it('should not throw an error when decoding an improperly encoded adm', function () { + serverResponse.seatbid[0].bid[0].adm = '\\<\\/script\\>'; + serverResponse.seatbid[1].bid[0].adm = '%3F%%3Demx%3C3prebid' + + assert.doesNotThrow(() => spec.interpretResponse({ + body: serverResponse + })); + }); + }); + + describe('getUserSyncs', function () { + let syncOptionsIframe = { iframeEnabled: true }; + let syncOptionsPixel = { pixelEnabled: true }; + it('Should push the correct sync type depending on the config', function () { + let iframeSync = spec.getUserSyncs(syncOptionsIframe); + expect(iframeSync.length).to.equal(1); + expect(iframeSync[0].type).to.equal('iframe'); + }); + }); +}); diff --git a/test/spec/modules/eplanningAnalyticsAdapter_spec.js b/test/spec/modules/eplanningAnalyticsAdapter_spec.js index 2b10f10adf2..8609024c7d9 100644 --- a/test/spec/modules/eplanningAnalyticsAdapter_spec.js +++ b/test/spec/modules/eplanningAnalyticsAdapter_spec.js @@ -2,7 +2,7 @@ import eplAnalyticsAdapter from 'modules/eplanningAnalyticsAdapter'; import includes from 'core-js/library/fn/array/includes'; import { expect } from 'chai'; import {parse as parseURL} from 'src/url'; -let adaptermanager = require('src/adaptermanager'); +let adapterManager = require('src/adapterManager').default; let events = require('src/events'); let constants = require('src/constants.json'); @@ -75,12 +75,12 @@ describe('eplanning analytics adapter', function () { } ]; - adaptermanager.registerAnalyticsAdapter({ + adapterManager.registerAnalyticsAdapter({ code: 'eplanning', adapter: eplAnalyticsAdapter }); - adaptermanager.enableAnalytics({ + adapterManager.enableAnalytics({ provider: 'eplanning', options: initOptions }); diff --git a/test/spec/modules/eplanningBidAdapter_spec.js b/test/spec/modules/eplanningBidAdapter_spec.js index 80129a03bd2..93290229ce3 100644 --- a/test/spec/modules/eplanningBidAdapter_spec.js +++ b/test/spec/modules/eplanningBidAdapter_spec.js @@ -6,10 +6,10 @@ import * as utils from 'src/utils'; describe('E-Planning Adapter', function () { const adapter = newBidder('spec'); const CI = '12345'; - const ADUNIT_CODE = 'adunit-code'; + const ADUNIT_CODE = 'adunit-co:de'; const ADUNIT_CODE2 = 'adunit-code-dos'; const CLEAN_ADUNIT_CODE2 = 'adunitcodedos'; - const CLEAN_ADUNIT_CODE = 'adunitcode'; + const CLEAN_ADUNIT_CODE = 'adunitco_de'; const BID_ID = '123456789'; const BID_ID2 = '987654321'; const CPM = 1.3; diff --git a/test/spec/modules/eywamediaBidAdapter_spec.js b/test/spec/modules/eywamediaBidAdapter_spec.js new file mode 100644 index 00000000000..945c0dd0d01 --- /dev/null +++ b/test/spec/modules/eywamediaBidAdapter_spec.js @@ -0,0 +1,253 @@ +import { expect } from 'chai'; +import { spec } from 'modules/eywamediaBidAdapter'; + +describe('EywamediaAdapter', function () { + let serverResponse, bidRequests, bidRequest, bidResponses; + const ENDPOINT = 'https://adtarbostg.eywamedia.com/auctions/prebidjs/3000'; + + bidRequests = [ + { + 'auctionId': 'fc917230-a5e1-4a42-b7d9-8fb776124e43', + 'sizes': [[300, 250]], + 'bidRequestsCount': 1, + 'params': { + 'publisherId': '1234_abcd' + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250]] + } + }, + 'crumbs': { + 'pubcid': '8b640d4e-1f6d-4fd3-b63f-2570572d8100' + }, + 'bidId': '28b09d0543d671', + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': 'd909c39c-ecc9-41a4-897c-2d2fdfdf41b1', + 'src': 'client', + 'bidder': 'eywamedia', + 'bidderRequestId': '14d8cbc769114b' + }, + { + 'auctionId': 'fc917230-a5e1-4a42-b7d9-8fb776124e43', + 'sizes': [[728, 90]], + 'bidRequestsCount': 1, + 'params': { + 'publisherId': '1234_abcd' + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[728, 90]] + } + }, + 'crumbs': { + 'pubcid': '8b640d4e-1f6d-4fd3-b63f-2570572d8100' + }, + 'bidId': '28b09d0543d672', + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': 'd909c39c-ecc9-41a4-897c-2d2fdfdf41b2', + 'src': 'client', + 'bidder': 'eywamedia', + 'bidderRequestId': '14d8cbc769114b' + } + ]; + + bidRequest = { + 'auctionId': 'c88115a4-7e71-43d0-9c96-a9b43ebd143d', + 'auctionStart': 1564725164517, + 'bidderCode': 'eywamedia', + 'bidderRequestId': '191afa18994fdd', + 'bids': [], + 'refererInfo': { + 'canonicalUrl': '', + 'numIframes': 0, + 'reachedTop': true, + 'referer': '' + }, + 'stack': [ + '' + ], + 'start': 1564725164520, + 'timeout': 3000 + }; + + let testBid = { + 'bidder': 'eywamedia', + 'params': { + 'publisherId': '1234_abcd' + } + }; + + describe('isBidRequestValid', function () { + it('should return true when required params found', function () { + assert(spec.isBidRequestValid(testBid)); + }); + + it('should return false when required params are missing', function () { + testBid.params = { + test: '231212312' + }; + assert.isFalse(spec.isBidRequestValid(testBid)); + }); + }); + + describe('buildRequests', function () { + it('should attempt to send bid requests to the endpoint via POST', function () { + const requests = spec.buildRequests(bidRequests, bidRequest); + expect(requests.method).to.equal('POST'); + expect(requests.url).to.be.equal(ENDPOINT); + }); + + it('should not blow up if crumbs is undefined', function () { + let bidArray = [ + { ...testBid, crumbs: undefined } + ] + expect(function () { spec.buildRequests(bidArray, bidRequest) }).not.to.throw() + }) + + it('should return true when required params found', function () { + testBid.params.publisherId = '1234_abcd'; + assert(spec.isBidRequestValid(testBid)); + }); + }); + + describe('interpretResponse', function () { + beforeEach(function () { + serverResponse = { + 'body': + [ + { + 'ad': '', + 'adSlot': '', + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'adUrl': 'http://eywamedia.com', + 'bidId': '28b09d0543d671', + 'bidder': 'eywamedia', + 'bidderCode': 'eywamedia', + 'cpm': 1, + 'height': 250, + 'requestTimestamp': 1564725162, + 'respType': 'banner', + 'size': '300X250', + 'statusMessage': 'Bid available', + 'transactionId': 'd909c39c-ecc9-41a4-897c-2d2fdfdf41b1', + 'usesGenericKeys': true, + 'width': 300 + }, + { + 'ad': '', + 'adSlot': '', + 'adUnitCode': 'div-gpt-ad-1460505748561-1', + 'adUrl': 'http://eywamedia.com', + 'bidId': '28b09d0543d672', + 'bidder': 'eywamedia', + 'bidderCode': 'eywamedia', + 'cpm': 1, + 'height': 90, + 'requestTimestamp': 1564725164, + 'respType': 'banner', + 'size': '728X90', + 'statusMessage': 'Bid available', + 'transactionId': 'd909c39c-ecc9-41a4-897c-2d2fdfdf41b2', + 'usesGenericKeys': true, + 'width': 728 + } + ], + 'headers': 'header?' + }; + + bidRequest = { + 'data': + { + 'bidPayload': + [ + { + 'auctionId': 'fc917230-a5e1-4a42-b7d9-8fb776124e43', + 'sizes': [[300, 250]], + 'bidRequestsCount': 1, + 'params': { + 'publisherId': '1234_abcd' + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250]] + } + }, + 'crumbs': { + 'pubcid': '8b640d4e-1f6d-4fd3-b63f-2570572d8100' + }, + 'bidId': '28b09d0543d671', + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': 'd909c39c-ecc9-41a4-897c-2d2fdfdf41b1', + 'src': 'client', + 'bidder': 'eywamedia', + 'bidderRequestId': '14d8cbc769114b' + }, + { + 'auctionId': 'fc917230-a5e1-4a42-b7d9-8fb776124e43', + 'sizes': [[728, 90]], + 'bidRequestsCount': 1, + 'params': { + 'publisherId': '1234_abcd' + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[728, 90]] + } + }, + 'crumbs': { + 'pubcid': '8b640d4e-1f6d-4fd3-b63f-2570572d8100' + }, + 'bidId': '28b09d0543d672', + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': 'd909c39c-ecc9-41a4-897c-2d2fdfdf41b2', + 'src': 'client', + 'bidder': 'eywamedia', + 'bidderRequestId': '14d8cbc769114b' + } + ] + } + }; + bidResponses = [ + { + 'ad': '', + 'bidderCode': 'eywamedia', + 'cpm': 1, + 'creativeId': '28b09d0543d671', + 'currency': 'USD', + 'height': 250, + 'mediaType': 'banner', + 'netRevenue': true, + 'requestId': '28b09d0543d671', + 'transactionId': 'd909c39c-ecc9-41a4-897c-2d2fdfdf41b1', + 'ttl': 360, + 'width': 300 + }, + { + 'ad': '', + 'bidderCode': 'eywamedia', + 'cpm': 1, + 'creativeId': '28b09d0543d672', + 'currency': 'USD', + 'height': 90, + 'mediaType': 'banner', + 'netRevenue': true, + 'requestId': '28b09d0543d672', + 'transactionId': 'd909c39c-ecc9-41a4-897c-2d2fdfdf41b2', + 'ttl': 360, + 'width': 728 + } + ] + }); + + it('should respond with empty response when there is empty serverResponse', function () { + let result = spec.interpretResponse({ body: {} }, bidRequest); + assert.deepEqual(result, []); + }); + + it('should respond with multile response when there is multiple serverResponse', function () { + let result = spec.interpretResponse(serverResponse, bidRequest); + assert.deepEqual(result, bidResponses); + }); + }); +}); diff --git a/test/spec/modules/feedadBidAdapter_spec.js b/test/spec/modules/feedadBidAdapter_spec.js new file mode 100644 index 00000000000..3432a40eca4 --- /dev/null +++ b/test/spec/modules/feedadBidAdapter_spec.js @@ -0,0 +1,433 @@ +import {expect} from 'chai'; +import {spec} from 'modules/feedadBidAdapter'; +import {BANNER, NATIVE, VIDEO} from '../../../src/mediaTypes'; +import * as sinon from 'sinon'; + +const CODE = 'feedad'; + +describe('FeedAdAdapter', function () { + describe('Public API', function () { + it('should have the FeedAd bidder code', function () { + expect(spec.code).to.equal(CODE); + }); + it('should only support video and banner ads', function () { + expect(spec.supportedMediaTypes).to.be.a('array'); + expect(spec.supportedMediaTypes).to.include(BANNER); + expect(spec.supportedMediaTypes).to.include(VIDEO); + expect(spec.supportedMediaTypes).not.to.include(NATIVE); + }); + it('should export the BidderSpec functions', function () { + expect(spec.isBidRequestValid).to.be.a('function'); + expect(spec.buildRequests).to.be.a('function'); + expect(spec.interpretResponse).to.be.a('function'); + expect(spec.onTimeout).to.be.a('function'); + expect(spec.onBidWon).to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function () { + it('should detect missing params', function () { + let result = spec.isBidRequestValid({ + bidder: 'feedad', + sizes: [] + }); + expect(result).to.equal(false); + }); + it('should detect missing client token', function () { + let result = spec.isBidRequestValid({ + bidder: 'feedad', + sizes: [], + params: {placementId: 'placement'} + }); + expect(result).to.equal(false); + }); + it('should detect zero length client token', function () { + let result = spec.isBidRequestValid({ + bidder: 'feedad', + sizes: [], + params: {clientToken: '', placementId: 'placement'} + }); + expect(result).to.equal(false); + }); + it('should detect missing placement id', function () { + let result = spec.isBidRequestValid({ + bidder: 'feedad', + sizes: [], + params: {clientToken: 'clientToken'} + }); + expect(result).to.equal(false); + }); + it('should detect zero length placement id', function () { + let result = spec.isBidRequestValid({ + bidder: 'feedad', + sizes: [], + params: {clientToken: 'clientToken', placementId: ''} + }); + expect(result).to.equal(false); + }); + it('should detect too long placement id', function () { + var placementId = ''; + for (var i = 0; i < 300; i++) { + placementId += 'a'; + } + let result = spec.isBidRequestValid({ + bidder: 'feedad', + sizes: [], + params: {clientToken: 'clientToken', placementId} + }); + expect(result).to.equal(false); + }); + it('should detect invalid placement id', function () { + [ + 'placement id with spaces', + 'some|id', + 'PLACEMENTID', + 'placeme:ntId' + ].forEach(id => { + let result = spec.isBidRequestValid({ + bidder: 'feedad', + sizes: [], + params: {clientToken: 'clientToken', placementId: id} + }); + expect(result).to.equal(false); + }); + }); + it('should accept valid parameters', function () { + let result = spec.isBidRequestValid({ + bidder: 'feedad', + sizes: [], + params: {clientToken: 'clientToken', placementId: 'placement-id'} + }); + expect(result).to.equal(true); + }); + }); + + describe('buildRequests', function () { + const bidderRequest = { + refererInfo: { + referer: 'the referer' + }, + some: 'thing' + }; + + it('should accept empty lists', function () { + let result = spec.buildRequests([], bidderRequest); + expect(result).to.be.empty; + }); + it('should filter native media types', function () { + let bid = { + code: 'feedad', + mediaTypes: { + native: { + sizes: [[300, 250], [300, 600]], + } + }, + params: {clientToken: 'clientToken', placementId: 'placement-id'} + }; + let result = spec.buildRequests([bid], bidderRequest); + expect(result).to.be.empty; + }); + it('should filter video media types without outstream context', function () { + let bid = { + code: 'feedad', + mediaTypes: { + video: { + context: 'instream' + } + }, + params: {clientToken: 'clientToken', placementId: 'placement-id'} + }; + let result = spec.buildRequests([bid], bidderRequest); + expect(result).to.be.empty; + }); + it('should pass through outstream video media', function () { + let bid = { + code: 'feedad', + mediaTypes: { + video: { + context: 'outstream' + } + }, + params: {clientToken: 'clientToken', placementId: 'placement-id'} + }; + let result = spec.buildRequests([bid], bidderRequest); + expect(result.data.bids).to.be.lengthOf(1); + expect(result.data.bids[0]).to.deep.equal(bid); + }); + it('should pass through banner media', function () { + let bid = { + code: 'feedad', + mediaTypes: { + banner: { + sizes: [[320, 250]] + } + }, + params: {clientToken: 'clientToken', placementId: 'placement-id'} + }; + let result = spec.buildRequests([bid], bidderRequest); + expect(result.data.bids).to.be.lengthOf(1); + expect(result.data.bids[0]).to.deep.equal(bid); + }); + it('should detect empty media types', function () { + let bid = { + code: 'feedad', + mediaTypes: { + banner: undefined, + video: undefined, + native: undefined + }, + params: {clientToken: 'clientToken', placementId: 'placement-id'} + }; + let result = spec.buildRequests([bid], bidderRequest); + expect(result).to.be.empty; + }); + it('should use POST', function () { + let bid = { + code: 'feedad', + mediaTypes: { + banner: { + sizes: [[320, 250]] + } + }, + params: {clientToken: 'clientToken', placementId: 'placement-id'} + }; + let result = spec.buildRequests([bid], bidderRequest); + expect(result.method).to.equal('POST'); + }); + it('should use the correct URL', function () { + let bid = { + code: 'feedad', + mediaTypes: { + banner: { + sizes: [[320, 250]] + } + }, + params: {clientToken: 'clientToken', placementId: 'placement-id'} + }; + let result = spec.buildRequests([bid], bidderRequest); + expect(result.url).to.equal('https://api.feedad.com/1/prebid/web/bids'); + }); + it('should specify the content type explicitly', function () { + let bid = { + code: 'feedad', + mediaTypes: { + banner: { + sizes: [[320, 250]] + } + }, + params: {clientToken: 'clientToken', placementId: 'placement-id'} + }; + let result = spec.buildRequests([bid], bidderRequest); + expect(result.options).to.deep.equal({ + contentType: 'application/json' + }) + }); + it('should include the bidder request', function () { + let bid = { + code: 'feedad', + mediaTypes: { + banner: { + sizes: [[320, 250]] + } + }, + params: {clientToken: 'clientToken', placementId: 'placement-id'} + }; + let result = spec.buildRequests([bid, bid, bid], bidderRequest); + expect(result.data).to.deep.include(bidderRequest); + }); + it('should detect missing bidder request parameter', function () { + let bid = { + code: 'feedad', + mediaTypes: { + banner: { + sizes: [[320, 250]] + } + }, + params: {clientToken: 'clientToken', placementId: 'placement-id'} + }; + let result = spec.buildRequests([bid, bid, bid]); + expect(result).to.be.empty; + }); + }); + + describe('interpretResponse', function () { + const body = [{ + foo: 'bar', + sub: { + obj: 5 + } + }, { + bar: 'foo' + }]; + + it('should convert string bodies to JSON', function () { + let result = spec.interpretResponse({body: JSON.stringify(body)}); + expect(result).to.deep.equal(body); + }); + + it('should pass through body objects', function () { + let result = spec.interpretResponse({body}); + expect(result).to.deep.equal(body); + }); + }); + + describe('event tracking calls', function () { + const clientToken = 'clientToken'; + const placementId = 'placement id'; + const auctionId = 'the auction id'; + const bidId = 'the bid id'; + const transactionId = 'the transaction id'; + const referer = 'the referer'; + const bidderRequest = { + refererInfo: { + referer: referer + }, + some: 'thing' + }; + const bid = { + 'bidder': 'feedad', + 'params': { + 'clientToken': 'fupp', + 'placementId': 'prebid-test' + }, + 'crumbs': { + 'pubcid': '6254a85f-bded-489a-9736-83c45d45ef1d' + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 250 + ] + ] + } + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': transactionId, + 'sizes': [ + [ + 300, + 250 + ] + ], + 'bidId': bidId, + 'bidderRequestId': '10739fe6fe2127', + 'auctionId': '5ac67dff-d971-4b56-84a3-345a87a1f786', + 'src': 'client', + 'bidRequestsCount': 1 + }; + const timeoutData = { + 'bidId': bidId, + 'bidder': 'feedad', + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'auctionId': auctionId, + 'params': [ + { + 'clientToken': clientToken, + 'placementId': placementId + } + ], + 'timeout': 3000 + }; + const bidWonData = { + 'bidderCode': 'feedad', + 'width': 300, + 'height': 250, + 'statusMessage': 'Bid available', + 'adId': '3a4529aa05114d', + 'requestId': bidId, + 'mediaType': 'banner', + 'source': 'client', + 'cpm': 0.5, + 'ad': 'ad content', + 'ttl': 60, + 'creativeId': 'feedad-21-0', + 'netRevenue': true, + 'currency': 'EUR', + 'auctionId': auctionId, + 'responseTimestamp': 1558365914596, + 'requestTimestamp': 1558365914506, + 'bidder': 'feedad', + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'timeToRespond': 90, + 'pbLg': '0.50', + 'pbMg': '0.50', + 'pbHg': '0.50', + 'pbAg': '0.50', + 'pbDg': '0.50', + 'pbCg': '', + 'size': '300x250', + 'adserverTargeting': { + 'hb_bidder': 'feedad', + 'hb_adid': '3a4529aa05114d', + 'hb_pb': '0.50', + 'hb_size': '300x250', + 'hb_source': 'client', + 'hb_format': 'banner' + }, + 'status': 'rendered', + 'params': [ + { + 'clientToken': clientToken, + 'placementId': placementId + } + ] + }; + const cases = [ + ['onTimeout', timeoutData, 'prebid_bidTimeout'], + ['onBidWon', bidWonData, 'prebid_bidWon'], + ]; + + cases.forEach(([name, data, eventKlass]) => { + let subject = spec[name]; + describe(name + ' handler', function () { + let xhr; + let requests; + + beforeEach(function () { + xhr = sinon.useFakeXMLHttpRequest(); + requests = []; + xhr.onCreate = xhr => requests.push(xhr); + }); + + afterEach(function () { + xhr.restore(); + }); + + it('should do nothing on empty data', function () { + subject(undefined); + subject(null); + expect(requests.length).to.equal(0); + }); + + it('should do nothing when bid metadata is not set', function () { + subject(data); + expect(requests.length).to.equal(0); + }); + + it('should send tracking params when correct metadata was set', function () { + spec.buildRequests([bid], bidderRequest); + let expectedData = { + app_hybrid: false, + client_token: clientToken, + placement_id: placementId, + klass: eventKlass, + prebid_auction_id: auctionId, + prebid_bid_id: bidId, + prebid_transaction_id: transactionId, + referer, + sdk_version: '1.0.0' + }; + subject(data); + expect(requests.length).to.equal(1); + let call = requests[0]; + expect(call.url).to.equal('https://api.feedad.com/1/prebid/web/events'); + expect(JSON.parse(call.requestBody)).to.deep.equal(expectedData); + expect(call.method).to.equal('POST'); + expect(call.requestHeaders).to.include({'Content-Type': 'application/json;charset=utf-8'}); + }) + }); + }); + }); +}); diff --git a/test/spec/modules/fintezaAnalyticsAdapter_spec.js b/test/spec/modules/fintezaAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..2cc2f380016 --- /dev/null +++ b/test/spec/modules/fintezaAnalyticsAdapter_spec.js @@ -0,0 +1,241 @@ +import fntzAnalyticsAdapter from 'modules/fintezaAnalyticsAdapter'; +import includes from 'core-js/library/fn/array/includes'; +import { expect } from 'chai'; +import { parse as parseURL } from 'src/url'; + +let adapterManager = require('src/adapterManager').default; +let events = require('src/events'); +let constants = require('src/constants.json'); + +function setCookie(name, value, expires) { + document.cookie = name + '=' + value + + '; path=/' + + (expires ? ('; expires=' + expires.toUTCString()) : '') + + '; SameSite=None'; +} + +describe('finteza analytics adapter', function () { + const clientId = 'fntz-client-32145'; + const uniqCookie = '5045380421580287382'; + + let xhr; + let requests; + + beforeEach(function () { + setCookie('_fz_uniq', uniqCookie); + xhr = sinon.useFakeXMLHttpRequest(); + requests = []; + xhr.onCreate = request => { requests.push(request) }; + sinon.stub(events, 'getEvents').returns([]); + sinon.spy(fntzAnalyticsAdapter, 'track'); + + adapterManager.registerAnalyticsAdapter({ + code: 'finteza', + adapter: fntzAnalyticsAdapter + }); + + adapterManager.enableAnalytics({ + provider: 'finteza', + options: { + id: clientId, // Client ID (required) + bidRequestTrack: 'Bid Request %BIDDER%', + bidResponseTimeTrack: 'Bid Response Time %bidder%', + bidResponsePriceTrack: 'Bid Response Price %bidder%', + bidTimeoutTrack: 'Bid Timeout %Bidder%', + bidWonTrack: 'Bid Won %BIDDER%', + } + }); + }); + + afterEach(function () { + setCookie('_fz_uniq', '', new Date(0)); + xhr.restore(); + events.getEvents.restore(); + fntzAnalyticsAdapter.track.restore(); + fntzAnalyticsAdapter.disableAnalytics(); + }); + + describe('track', () => { + describe('bid request', () => { + it('builds and sends data', function () { + const bidderCode = 'Bidder789'; + const pauctionId = '5018eb39-f900-4370-b71e-3bb5b48d324f'; + + const bidRequest = { + bidderCode: bidderCode, + auctionId: pauctionId, + bidderRequestId: '1a6fc81528d0f6', + bids: [{ + bidder: bidderCode, + placementCode: 'container-1', + bidId: '208750227436c1', + bidderRequestId: '1a6fc81528d0f6', + auctionId: pauctionId, + startTime: 1509369418389, + sizes: [[300, 250]], + }], + auctionStart: 1509369418387, + timeout: 3000, + start: 1509369418389 + }; + + // Emit the events with the "real" arguments + events.emit(constants.EVENTS.BID_REQUESTED, bidRequest); + + expect(requests.length).to.equal(1); + + expect(requests[0].method).to.equal('GET'); + expect(requests[0].withCredentials).to.equal(true); + + const url = parseURL(requests[0].url); + + expect(url.protocol).to.equal('https'); + expect(url.hostname).to.equal('content.mql5.com'); + expect(url.pathname).to.equal('/tr'); + expect(url.search.id).to.equal(clientId); + expect(url.search.fz_uniq).to.equal(uniqCookie); + expect(decodeURIComponent(url.search.event)).to.equal(`Bid Request ${bidderCode.toUpperCase()}`); + + sinon.assert.callCount(fntzAnalyticsAdapter.track, 1); + }); + }); + + describe('bid response', () => { + it('builds and sends data', function () { + const bidderCode = 'Bidder789'; + const pauctionId = '5018eb39-f900-4370-b71e-3bb5b48d324f'; + + const timeToRespond = 443; + const cpm = 0.015; + + const bidResponse = { + bidderCode: bidderCode, + adId: '208750227436c1', + cpm: cpm, + auctionId: pauctionId, + responseTimestamp: 1509369418832, + requestTimestamp: 1509369418389, + bidder: bidderCode, + timeToRespond: timeToRespond, + size: '300x250', + width: 300, + height: 250, + }; + + // Emit the events with the "real" arguments + events.emit(constants.EVENTS.BID_RESPONSE, bidResponse); + + expect(requests.length).to.equal(2); + + expect(requests[0].method).to.equal('GET'); + expect(requests[0].withCredentials).to.equal(true); + + let url = parseURL(requests[0].url); + + expect(url.protocol).to.equal('https'); + expect(url.hostname).to.equal('content.mql5.com'); + expect(url.pathname).to.equal('/tr'); + expect(url.search.id).to.equal(clientId); + expect(url.search.fz_uniq).to.equal(uniqCookie); + expect(decodeURIComponent(url.search.event)).to.equal(`Bid Response Price ${bidderCode.toLowerCase()}`); + expect(url.search.value).to.equal(String(cpm)); + expect(url.search.unit).to.equal('usd'); + + expect(requests[1].method).to.equal('GET'); + expect(requests[1].withCredentials).to.equal(true); + + url = parseURL(requests[1].url); + + expect(url.protocol).to.equal('https'); + expect(url.hostname).to.equal('content.mql5.com'); + expect(url.pathname).to.equal('/tr'); + expect(url.search.id).to.equal(clientId); + expect(url.search.fz_uniq).to.equal(uniqCookie); + expect(decodeURIComponent(url.search.event)).to.equal(`Bid Response Time ${bidderCode.toLowerCase()}`); + expect(url.search.value).to.equal(String(timeToRespond)); + expect(url.search.unit).to.equal('ms'); + + sinon.assert.callCount(fntzAnalyticsAdapter.track, 1); + }); + }); + + describe('bid won', () => { + it('builds and sends data', function () { + const bidderCode = 'Bidder789'; + const pauctionId = '5018eb39-f900-4370-b71e-3bb5b48d324f'; + + const cpm = 0.015; + + const bidWon = { + bidderCode: bidderCode, + cpm: cpm, + adId: 'adIdData', + ad: 'adContent', + auctionId: pauctionId, + width: 300, + height: 250 + } + + // Emit the events with the "real" arguments + events.emit(constants.EVENTS.BID_WON, bidWon); + + expect(requests.length).to.equal(1); + + expect(requests[0].method).to.equal('GET'); + expect(requests[0].withCredentials).to.equal(true); + + const url = parseURL(requests[0].url); + + expect(url.protocol).to.equal('https'); + expect(url.hostname).to.equal('content.mql5.com'); + expect(url.pathname).to.equal('/tr'); + expect(url.search.id).to.equal(clientId); + expect(url.search.fz_uniq).to.equal(uniqCookie); + expect(decodeURIComponent(url.search.event)).to.equal(`Bid Won ${bidderCode.toUpperCase()}`); + expect(url.search.value).to.equal(String(cpm)); + expect(url.search.unit).to.equal('usd'); + + sinon.assert.callCount(fntzAnalyticsAdapter.track, 1); + }); + }); + + describe('bid timeout', () => { + it('builds and sends data', function () { + const bidderCode = 'biDDer789'; + const pauctionId = '5018eb39-f900-4370-b71e-3bb5b48d324f'; + + const timeout = 2540; + + const bidTimeout = [ + { + bidId: '208750227436c1', + bidder: bidderCode, + auctionId: pauctionId, + timeout: timeout, + } + ]; + + // Emit the events with the "real" arguments + events.emit(constants.EVENTS.BID_TIMEOUT, bidTimeout); + + expect(requests.length).to.equal(1); + + expect(requests[0].method).to.equal('GET'); + expect(requests[0].withCredentials).to.equal(true); + + const url = parseURL(requests[0].url); + + expect(url.protocol).to.equal('https'); + expect(url.hostname).to.equal('content.mql5.com'); + expect(url.pathname).to.equal('/tr'); + expect(url.search.id).to.equal(clientId); + expect(url.search.fz_uniq).to.equal(uniqCookie); + expect(decodeURIComponent(url.search.event)).to.equal(`Bid Timeout Bidder789`); + expect(url.search.value).to.equal(String(timeout)); + expect(url.search.unit).to.equal('ms'); + + sinon.assert.callCount(fntzAnalyticsAdapter.track, 1); + }); + }); + }); +}); diff --git a/test/spec/modules/freeWheelAdserverVideo_spec.js b/test/spec/modules/freeWheelAdserverVideo_spec.js new file mode 100644 index 00000000000..f958a2733db --- /dev/null +++ b/test/spec/modules/freeWheelAdserverVideo_spec.js @@ -0,0 +1,266 @@ +import { expect } from 'chai'; +import { adpodUtils } from 'modules/freeWheelAdserverVideo'; +import { auctionManager } from 'src/auctionManager'; +import { config } from 'src/config'; + +describe('freeWheel adserver module', function() { + let amStub; + let amGetAdUnitsStub; + let xhr; + let requests; + + before(function () { + let adUnits = [{ + code: 'preroll_1', + mediaTypes: { + video: { + context: 'adpod', + playerSize: [640, 480], + adPodDurationSec: 60, + durationRangeSec: [15, 30], + requireExactDuration: true + } + }, + bids: [ + { + bidder: 'appnexus', + params: { + placementId: 14542875, + } + } + ] + }, { + code: 'midroll_1', + mediaTypes: { + video: { + context: 'adpod', + playerSize: [640, 480], + adPodDurationSec: 60, + durationRangeSec: [15, 30], + requireExactDuration: true + } + }, + bids: [ + { + bidder: 'appnexus', + params: { + placementId: 14542875, + } + } + ] + }]; + + amGetAdUnitsStub = sinon.stub(auctionManager, 'getAdUnits'); + amGetAdUnitsStub.returns(adUnits); + amStub = sinon.stub(auctionManager, 'getBidsReceived'); + }); + + beforeEach(function () { + xhr = sinon.useFakeXMLHttpRequest(); + requests = []; + xhr.onCreate = request => requests.push(request); + + config.setConfig({ + adpod: { + brandCategoryExclusion: false, + deferCaching: false + } + }); + }) + + afterEach(function() { + config.resetConfig(); + xhr.restore(); + }); + + after(function () { + amGetAdUnitsStub.restore(); + amStub.restore(); + }); + + it('should return targeting for all adunits', function() { + amStub.returns(getBidsReceived()); + let targeting; + adpodUtils.getTargeting({ + callback: function(errorMsg, targetingResult) { + targeting = targetingResult; + } + }); + + expect(targeting['preroll_1'].length).to.equal(3); + expect(targeting['midroll_1'].length).to.equal(3); + }); + + it('should return targeting for passed adunit code', function() { + amStub.returns(getBidsReceived()); + let targeting; + adpodUtils.getTargeting({ + codes: ['preroll_1'], + callback: function(errorMsg, targetingResult) { + targeting = targetingResult; + } + }); + + expect(targeting['preroll_1']).to.exist; + expect(targeting['midroll_1']).to.not.exist; + }); + + it('should only use adpod bids', function() { + let bannerBid = [{ + 'ad': 'creative', + 'cpm': '1.99', + 'width': 300, + 'height': 250, + 'requestId': '1', + 'creativeId': 'some-id', + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 360, + 'bidderCode': 'appnexus', + 'statusMessage': 'Bid available', + 'adId': '28f24ced14586c', + 'adUnitCode': 'preroll_1' + }]; + amStub.returns(getBidsReceived().concat(bannerBid)); + let targeting; + adpodUtils.getTargeting({ + callback: function(errorMsg, targetingResult) { + targeting = targetingResult; + } + }); + + expect(targeting['preroll_1'].length).to.equal(3); + expect(targeting['midroll_1'].length).to.equal(3); + }); + + it('should return unique category bids when competitive exclusion is enabled', function() { + config.setConfig({ + adpod: { + brandCategoryExclusion: true, + deferCaching: false + } + }); + amStub.returns([ + createBid(10, 'preroll_1', 30, '10.00_395_30s', '123', '395'), + createBid(15, 'preroll_1', 30, '15.00_395_30s', '123', '395'), + createBid(15, 'midroll_1', 60, '15.00_406_60s', '123', '406'), + createBid(10, 'preroll_1', 30, '10.00_395_30s', '123', '395') + ]); + let targeting; + adpodUtils.getTargeting({ + callback: function(errorMsg, targetingResult) { + targeting = targetingResult; + } + }); + + expect(targeting['preroll_1'].length).to.equal(3); + expect(targeting['midroll_1'].length).to.equal(2); + }); + + it('should only select bids less than adpod duration', function() { + amStub.returns([ + createBid(10, 'preroll_1', 90, '10.00_395_90s', '123', '395'), + createBid(15, 'preroll_1', 90, '15.00_395_90s', '123', '395'), + createBid(15, 'midroll_1', 90, '15.00_406_90s', '123', '406') + ]); + let targeting; + adpodUtils.getTargeting({ + callback: function(errorMsg, targetingResult) { + targeting = targetingResult; + } + }); + + expect(targeting['preroll_1']).to.be.empty; + expect(targeting['midroll_1']).to.be.empty; + }); + + it('should select bids when deferCaching is enabled', function() { + config.setConfig({ + adpod: { + deferCaching: true + } + }); + amStub.returns(getBidsReceived()); + let targeting; + adpodUtils.getTargeting({ + callback: function(errorMsg, targetingResult) { + targeting = targetingResult; + } + }); + + requests[0].respond( + 200, + { 'Content-Type': 'text/plain' }, + JSON.stringify({'responses': getBidsReceived().slice(0, 4)}) + ); + + expect(targeting['preroll_1'].length).to.equal(3); + expect(targeting['midroll_1'].length).to.equal(3); + }); +}); + +function getBidsReceived() { + return [ + createBid(10, 'preroll_1', 15, '10.00_395_15s', '123', '395'), + createBid(15, 'preroll_1', 15, '15.00_395_15s', '123', '395'), + createBid(15, 'midroll_1', 30, '15.00_406_30s', '123', '406'), + createBid(5, 'midroll_1', 5, '5.00_406_5s', '123', '406'), + createBid(20, 'midroll_1', 60, '20.00_406_60s', '123', '406'), + ] +} + +function createBid(cpm, adUnitCode, durationBucket, priceIndustryDuration, uuid, industry) { + return { + 'bidderCode': 'appnexus', + 'width': 640, + 'height': 360, + 'statusMessage': 'Bid available', + 'adId': '28f24ced14586c', + 'mediaType': 'video', + 'source': 'client', + 'requestId': '28f24ced14586c', + 'cpm': cpm, + 'creativeId': 97517771, + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 3600, + 'adUnitCode': adUnitCode, + 'video': { + 'context': 'adpod', + 'durationBucket': durationBucket + }, + 'appnexus': { + 'buyerMemberId': 9325 + }, + 'vastUrl': 'http://some-vast-url.com', + 'vastImpUrl': 'http://some-vast-imp-url.com', + 'auctionId': 'ec266b31-d652-49c5-8295-e83fafe5532b', + 'responseTimestamp': 1548442460888, + 'requestTimestamp': 1548442460827, + 'bidder': 'appnexus', + 'timeToRespond': 61, + 'pbLg': '5.00', + 'pbMg': '5.00', + 'pbHg': '5.00', + 'pbAg': '5.00', + 'pbDg': '5.00', + 'pbCg': '', + 'size': '640x360', + 'adserverTargeting': { + 'hb_bidder': 'appnexus', + 'hb_adid': '28f24ced14586c', + 'hb_pb': '5.00', + 'hb_size': '640x360', + 'hb_source': 'client', + 'hb_format': 'video', + 'hb_pb_cat_dur': priceIndustryDuration, + 'hb_cache_id': uuid + }, + 'customCacheKey': `${priceIndustryDuration}_${uuid}`, + 'meta': { + 'iabSubCatId': 'iab-1', + 'adServerCatId': industry + }, + 'videoCacheKey': '4cf395af-8fee-4960-af0e-88d44e399f14' + } +} diff --git a/test/spec/modules/freewheel-sspBidAdapter_spec.js b/test/spec/modules/freewheel-sspBidAdapter_spec.js index adc6e1bcde4..45754d0250c 100644 --- a/test/spec/modules/freewheel-sspBidAdapter_spec.js +++ b/test/spec/modules/freewheel-sspBidAdapter_spec.js @@ -1,197 +1,239 @@ -import { expect } from 'chai'; -import { spec } from 'modules/freewheel-sspBidAdapter'; -import { newBidder } from 'src/adapters/bidderFactory'; - -const ENDPOINT = '//ads.stickyadstv.com/www/delivery/swfIndex.php'; - -describe('freewheel-ssp BidAdapter Test', function () { - const adapter = newBidder(spec); - - describe('inherited functions', function () { - it('exists and is a function', function () { - expect(adapter.callBids).to.exist.and.to.be.a('function'); - }); - }); - - describe('isBidRequestValid', function () { - let bid = { - 'bidder': 'freewheel-ssp', - 'params': { - 'zoneId': '277225' - }, - 'adUnitCode': 'adunit-code', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - }; - - it('should return true when required params found', function () { - expect(spec.isBidRequestValid(bid)).to.equal(true); - }); - - it('should return false when required params are not passed', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { - wrong: 'missing zone id' - }; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - }); - - describe('buildRequests', function () { - let bidRequests = [ - { - 'bidder': 'freewheel-ssp', - 'params': { - 'zoneId': '277225' - }, - 'adUnitCode': 'adunit-code', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - 'gdprConsent': { - 'consentString': 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==', - 'gdprApplies': true - } - } - ]; - - it('should add parameters to the tag', function () { - const request = spec.buildRequests(bidRequests, bidRequests[0]); - const payload = request.data; - expect(payload.reqType).to.equal('AdsSetup'); - expect(payload.protocolVersion).to.equal('2.0'); - expect(payload.zoneId).to.equal('277225'); - expect(payload.componentId).to.equal('mustang'); - expect(payload.playerSize).to.equal('300x600'); - expect(payload._fw_gdpr).to.equal(true); - expect(payload._fw_gdpr_consent).to.equal('BOJ/P2HOJ/P2HABABMAAAAAZ+A=='); - }); - - it('sends bid request to ENDPOINT via GET', function () { - const request = spec.buildRequests(bidRequests, bidRequests[0]); - expect(request.url).to.contain(ENDPOINT); - expect(request.method).to.equal('GET'); - }); - }) - - describe('interpretResponse', function () { - let bidRequests = [ - { - 'bidder': 'freewheel-ssp', - 'params': { - 'zoneId': '277225' - }, - 'adUnitCode': 'adunit-code', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - } - ]; - - let formattedBidRequests = [ - { - 'bidder': 'freewheel-ssp', - 'params': { - 'zoneId': '277225', - 'format': 'floorad' - }, - 'adUnitCode': 'adunit-code', - 'sizes': [[600, 250], [300, 600]], - 'bidId': '30b3other1c1838de1e', - 'bidderRequestId': '22edbae273other3bf6', - 'auctionId': '1d1a03079test0a475', - }, - { - 'bidder': 'stickyadstv', - 'params': { - 'zoneId': '277225', - 'format': 'test' - }, - 'adUnitCode': 'adunit-code', - 'sizes': [[300, 600]], - 'bidId': '2', - 'bidderRequestId': '3', - 'auctionId': '4', - } - ]; - - let response = '' + - '' + - ' ' + - ' Adswizz' + - ' ' + - ' ' + - ' ' + - ' 00:00:09' + - ' ' + - ' ' + - ' ' + - ' ' + - ' ' + - ' ' + - ' ' + - ' 0.2000' + - ' ' + - ' ' + - ' ' + - ''; - - let ad = '
    '; - let formattedAd = '
    '; - - it('should get correct bid response', function () { - var request = spec.buildRequests(formattedBidRequests, formattedBidRequests[0]); - - let expectedResponse = [ - { - requestId: '30b31c1838de1e', - cpm: '0.2000', - width: 300, - height: 600, - creativeId: '28517153', - currency: 'EUR', - netRevenue: true, - ttl: 360, - ad: ad - } - ]; - - let result = spec.interpretResponse(response, request); - expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedResponse[0])); - }); - - it('should get correct bid response with formated ad', function () { - var request = spec.buildRequests(formattedBidRequests, formattedBidRequests[0]); - - let expectedResponse = [ - { - requestId: '30b31c1838de1e', - cpm: '0.2000', - width: 300, - height: 600, - creativeId: '28517153', - currency: 'EUR', - netRevenue: true, - ttl: 360, - ad: formattedAd - } - ]; - - let result = spec.interpretResponse(response, request); - expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedResponse[0])); - }); - - it('handles nobid responses', function () { - var reqest = spec.buildRequests(formattedBidRequests, formattedBidRequests[0]); - let response = ''; - - let result = spec.interpretResponse(response, reqest); - expect(result.length).to.equal(0); - }); - }); -}); +import { expect } from 'chai'; +import { spec } from 'modules/freewheel-sspBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; + +const ENDPOINT = '//ads.stickyadstv.com/www/delivery/swfIndex.php'; + +describe('freewheel-ssp BidAdapter Test', function () { + const adapter = newBidder(spec); + + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function () { + let bid = { + 'bidder': 'freewheel-ssp', + 'params': { + 'zoneId': '277225' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not passed', function () { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + wrong: 'missing zone id' + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + let bidRequests = [ + { + 'bidder': 'freewheel-ssp', + 'params': { + 'zoneId': '277225', + 'gdpr_consented_providers': '123,345', + 'vastUrlParams': {'ownerId': 'kombRJ'} + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + 'gdprConsent': { + 'consentString': 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==', + 'gdprApplies': true + } + } + ]; + + it('should add parameters to the tag', function () { + const request = spec.buildRequests(bidRequests, bidRequests[0]); + const payload = request.data; + expect(payload.reqType).to.equal('AdsSetup'); + expect(payload.protocolVersion).to.equal('2.0'); + expect(payload.zoneId).to.equal('277225'); + expect(payload.componentId).to.equal('mustang'); + expect(payload.ownerId).to.equal('kombRJ'); + expect(payload.playerSize).to.equal('300x600'); + expect(payload._fw_gdpr).to.equal(true); + expect(payload._fw_gdpr_consent).to.equal('BOJ/P2HOJ/P2HABABMAAAAAZ+A=='); + expect(payload._fw_gdpr_consented_providers).to.equal('123,345'); + }); + + it('sends bid request to ENDPOINT via GET', function () { + const request = spec.buildRequests(bidRequests, bidRequests[0]); + expect(request.url).to.contain(ENDPOINT); + expect(request.method).to.equal('GET'); + }); + }); + + describe('interpretResponse - formated', function () { + let intextBidRequests = [ + { + 'bidder': 'freewheel-ssp', + 'params': { + 'zoneId': '277225', + 'format': 'intext-roll' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[600, 250], [300, 600]], + 'bidId': '30b3other1c1838de1e', + 'bidderRequestId': '22edbae273other3bf6', + 'auctionId': '1d1a03079test0a475', + }, + { + 'bidder': 'stickyadstv', + 'params': { + 'zoneId': '277225', + 'format': 'test' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 600]], + 'bidId': '2', + 'bidderRequestId': '3', + 'auctionId': '4', + } + ]; + + let expandBidRequests = [ + { + 'bidder': 'freewheel-ssp', + 'params': { + 'zoneId': '277225', + 'format': 'expand-banner', + 'vastUrlParams': {'ownerId': 'kombRJ'} + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[600, 250], [300, 600]], + 'bidId': '30b3other1c1838de1e', + 'bidderRequestId': '22edbae273other3bf6', + 'auctionId': '1d1a03079test0a475', + } + ]; + + let bannerBidRequests = [ + { + 'bidder': 'freewheel-ssp', + 'params': { + 'zoneId': '277225' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[600, 250], [300, 600]], + 'bidId': '30b3other1c1838de1e', + 'bidderRequestId': '22edbae273other3bf6', + 'auctionId': '1d1a03079test0a475', + } + ]; + + let response = '' + + '' + + ' ' + + ' Adswizz' + + ' ' + + ' ' + + ' ' + + ' 00:00:09' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' 0.2000' + + ' ' + + ' ' + + ' ' + + ''; + + let ad = '
    '; + let intextAd = '
    '; + let expandAd = '
    '; + + it('should get correct intext bid response', function () { + var request = spec.buildRequests(bannerBidRequests, bannerBidRequests[0]); + + let expectedResponse = [ + { + requestId: '30b31c1838de1e', + cpm: '0.2000', + width: 300, + height: 600, + creativeId: '28517153', + currency: 'EUR', + netRevenue: true, + ttl: 360, + ad: ad + } + ]; + + let result = spec.interpretResponse(response, request); + expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedResponse[0])); + }); + + it('should get correct expand bid response', function () { + var request = spec.buildRequests(expandBidRequests, expandBidRequests[0]); + + let expectedResponse = [ + { + requestId: '30b31c1838de1e', + cpm: '0.2000', + width: 300, + height: 600, + creativeId: '28517153', + currency: 'EUR', + netRevenue: true, + ttl: 360, + ad: expandAd + } + ]; + + let result = spec.interpretResponse(response, request); + expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedResponse[0])); + }); + + it('should get correct bid response with formated ad', function () { + var request = spec.buildRequests(intextBidRequests, intextBidRequests[0]); + + let expectedResponse = [ + { + requestId: '30b31c1838de1e', + cpm: '0.2000', + width: 300, + height: 600, + creativeId: '28517153', + currency: 'EUR', + netRevenue: true, + ttl: 360, + ad: intextAd + } + ]; + + let result = spec.interpretResponse(response, request); + expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedResponse[0])); + }); + + it('handles nobid responses', function () { + var reqest = spec.buildRequests(intextBidRequests, intextBidRequests[0]); + let response = ''; + + let result = spec.interpretResponse(response, reqest); + expect(result.length).to.equal(0); + }); + }); +}); diff --git a/test/spec/modules/gambidBidAdapter_spec.js b/test/spec/modules/gambidBidAdapter_spec.js deleted file mode 100644 index 06118f7f7d8..00000000000 --- a/test/spec/modules/gambidBidAdapter_spec.js +++ /dev/null @@ -1,338 +0,0 @@ -import { expect } from 'chai'; -import { spec } from 'modules/gambidBidAdapter'; -import * as utils from 'src/utils'; - -const supplyPartnerId = '123'; - -describe('GambidAdapter', function () { - describe('isBidRequestValid', function () { - it('should validate supply-partner ID', function () { - expect(spec.isBidRequestValid({ params: {} })).to.equal(false); - expect(spec.isBidRequestValid({ params: { supplyPartnerId: 123 } })).to.equal(false); - expect(spec.isBidRequestValid({ params: { supplyPartnerId: '123' } })).to.equal(true); - }); - it('should validate RTB endpoint', function () { - expect(spec.isBidRequestValid({ params: { supplyPartnerId: '123' } })).to.equal(true); // RTB endpoint has a default - expect(spec.isBidRequestValid({ params: { supplyPartnerId: '123', rtbEndpoint: 123 } })).to.equal(false); - expect(spec.isBidRequestValid({ params: { supplyPartnerId: '123', rtbEndpoint: 'https://some.url.com' } })).to.equal(true); - }); - it('should validate bid floor', function () { - expect(spec.isBidRequestValid({ params: { supplyPartnerId: '123' } })).to.equal(true); // bidfloor has a default - expect(spec.isBidRequestValid({ params: { supplyPartnerId: '123', bidfloor: '123' } })).to.equal(false); - expect(spec.isBidRequestValid({ params: { supplyPartnerId: '123', bidfloor: 0.1 } })).to.equal(true); - }); - it('should validate adpos', function () { - expect(spec.isBidRequestValid({ params: { supplyPartnerId: '123' } })).to.equal(true); // adpos has a default - expect(spec.isBidRequestValid({ params: { supplyPartnerId: '123', adpos: '123' } })).to.equal(false); - expect(spec.isBidRequestValid({ params: { supplyPartnerId: '123', adpos: 0.1 } })).to.equal(true); - }); - it('should validate instl', function () { - expect(spec.isBidRequestValid({ params: { supplyPartnerId: '123' } })).to.equal(true); // adpos has a default - expect(spec.isBidRequestValid({ params: { supplyPartnerId: '123', instl: '123' } })).to.equal(false); - expect(spec.isBidRequestValid({ params: { supplyPartnerId: '123', instl: -1 } })).to.equal(false); - expect(spec.isBidRequestValid({ params: { supplyPartnerId: '123', instl: 0 } })).to.equal(true); - expect(spec.isBidRequestValid({ params: { supplyPartnerId: '123', instl: 1 } })).to.equal(true); - expect(spec.isBidRequestValid({ params: { supplyPartnerId: '123', instl: 2 } })).to.equal(false); - }); - }); - describe('buildRequests', function () { - const bidRequest = { - 'adUnitCode': 'adunit-code', - 'auctionId': '1d1a030790a475', - 'mediaTypes': { - banner: {} - }, - 'params': { - 'supplyPartnerId': supplyPartnerId - }, - 'sizes': [ [ 300, 250 ], [ 300, 600 ] ], - 'transactionId': 'a123456789' - }; - - it('returns an array', function () { - let response; - - response = spec.buildRequests([]); - expect(Array.isArray(response)).to.equal(true); - expect(response.length).to.equal(0); - - response = spec.buildRequests([ bidRequest ]); - expect(Array.isArray(response)).to.equal(true); - expect(response.length).to.equal(1); - - const adUnit1 = Object.assign({}, utils.deepClone(bidRequest), { auctionId: '1', adUnitCode: 'a' }); - const adUnit2 = Object.assign({}, utils.deepClone(bidRequest), { auctionId: '1', adUnitCode: 'b' }); - response = spec.buildRequests([adUnit1, adUnit2]); - expect(Array.isArray(response)).to.equal(true); - expect(response.length).to.equal(2); - }); - - it('targets correct endpoint', function () { - let response; - - response = spec.buildRequests([ bidRequest ])[ 0 ]; - expect(response.method).to.equal('POST'); - expect(response.url).to.match(new RegExp(`^https://rtb\\.gambid\\.io/r/${supplyPartnerId}/bidr\\?rformat=open_rtb&reqformat=rtb_json&bidder=prebid$`, 'g')); - expect(response.data.id).to.equal(bidRequest.auctionId); - - const bidRequestWithEndpoint = utils.deepClone(bidRequest); - bidRequestWithEndpoint.params.rtbEndpoint = 'https://rtb2.gambid.io/a12'; - response = spec.buildRequests([ bidRequestWithEndpoint ])[ 0 ]; - expect(response.url).to.match(new RegExp(`^https://rtb2\\.gambid\\.io/a12/r/${supplyPartnerId}/bidr\\?rformat=open_rtb&reqformat=rtb_json&bidder=prebid$`, 'g')); - }); - - it('builds request correctly', function () { - let stub = sinon.stub(utils, 'getTopWindowUrl').returns('http://www.test.com/page.html'); - - let response; - response = spec.buildRequests([ bidRequest ])[ 0 ]; - expect(response.data.site.domain).to.equal('www.test.com'); - expect(response.data.site.page).to.equal('http://www.test.com/page.html'); - expect(response.data.site.ref).to.equal(''); - expect(response.data.imp.length).to.equal(1); - expect(response.data.imp[ 0 ].id).to.equal(bidRequest.transactionId); - expect(response.data.imp[ 0 ].instl).to.equal(0); - expect(response.data.imp[ 0 ].tagid).to.equal(bidRequest.adUnitCode); - expect(response.data.imp[ 0 ].bidfloor).to.equal(0); - expect(response.data.imp[ 0 ].bidfloorcur).to.equal('USD'); - - const bidRequestWithInstlEquals1 = utils.deepClone(bidRequest); - bidRequestWithInstlEquals1.params.instl = 1; - response = spec.buildRequests([ bidRequestWithInstlEquals1 ])[ 0 ]; - expect(response.data.imp[ 0 ].instl).to.equal(bidRequestWithInstlEquals1.params.instl); - - const bidRequestWithInstlEquals0 = utils.deepClone(bidRequest); - bidRequestWithInstlEquals0.params.instl = 1; - response = spec.buildRequests([ bidRequestWithInstlEquals0 ])[ 0 ]; - expect(response.data.imp[ 0 ].instl).to.equal(bidRequestWithInstlEquals0.params.instl); - - const bidRequestWithBidfloorEquals1 = utils.deepClone(bidRequest); - bidRequestWithBidfloorEquals1.params.bidfloor = 1; - response = spec.buildRequests([ bidRequestWithBidfloorEquals1 ])[ 0 ]; - expect(response.data.imp[ 0 ].bidfloor).to.equal(bidRequestWithBidfloorEquals1.params.bidfloor); - - stub.restore(); - }); - - it('builds request banner object correctly', function () { - let response; - - const bidRequestWithBanner = utils.deepClone(bidRequest); - bidRequestWithBanner.mediaTypes = { - banner: { - sizes: [ [ 300, 250 ], [ 120, 600 ] ] - } - }; - - response = spec.buildRequests([ bidRequestWithBanner ])[ 0 ]; - expect(response.data.imp[ 0 ].banner.w).to.equal(bidRequestWithBanner.mediaTypes.banner.sizes[ 0 ][ 0 ]); - expect(response.data.imp[ 0 ].banner.h).to.equal(bidRequestWithBanner.mediaTypes.banner.sizes[ 0 ][ 1 ]); - expect(response.data.imp[ 0 ].banner.pos).to.equal(0); - expect(response.data.imp[ 0 ].banner.topframe).to.equal(0); - - const bidRequestWithPosEquals1 = utils.deepClone(bidRequestWithBanner); - bidRequestWithPosEquals1.params.pos = 1; - response = spec.buildRequests([ bidRequestWithPosEquals1 ])[ 0 ]; - expect(response.data.imp[ 0 ].banner.pos).to.equal(bidRequestWithPosEquals1.params.pos); - }); - - it('builds request video object correctly', function () { - let response; - - const bidRequestWithVideo = utils.deepClone(bidRequest); - bidRequestWithVideo.mediaTypes = { - video: { - sizes: [ [ 300, 250 ], [ 120, 600 ] ] - } - }; - - response = spec.buildRequests([ bidRequestWithVideo ])[ 0 ]; - expect(response.data.imp[ 0 ].video.w).to.equal(bidRequestWithVideo.mediaTypes.video.sizes[ 0 ][ 0 ]); - expect(response.data.imp[ 0 ].video.h).to.equal(bidRequestWithVideo.mediaTypes.video.sizes[ 0 ][ 1 ]); - expect(response.data.imp[ 0 ].video.pos).to.equal(0); - expect(response.data.imp[ 0 ].video.topframe).to.equal(0); - - const bidRequestWithPosEquals1 = utils.deepClone(bidRequestWithVideo); - bidRequestWithPosEquals1.params.pos = 1; - response = spec.buildRequests([ bidRequestWithPosEquals1 ])[ 0 ]; - expect(response.data.imp[ 0 ].video.pos).to.equal(bidRequestWithPosEquals1.params.pos); - }); - }); - describe('interpretResponse', function () { - const bannerBidRequest = { - 'adUnitCode': 'adunit-code', - 'auctionId': '1d1a030790a475', - 'mediaTypes': { - banner: {} - }, - 'params': { - 'supplyPartnerId': supplyPartnerId - }, - 'sizes': [ [ 300, 250 ], [ 300, 600 ] ], - 'transactionId': 'a123456789', - 'bidId': '111' - }; - const videoBidRequest = { - 'adUnitCode': 'adunit-code', - 'auctionId': '1d1a030790a475', - 'mediaTypes': { - video: {} - }, - 'params': { - 'supplyPartnerId': supplyPartnerId - }, - 'sizes': [ [ 300, 250 ], [ 300, 600 ] ], - 'transactionId': 'a123456789', - 'bidId': '111' - }; - const rtbResponse = { - 'id': 'imp_5b05b9fde4b09084267a556f', - 'bidid': 'imp_5b05b9fde4b09084267a556f', - 'cur': 'USD', - 'ext': { - 'utrk': [ - { 'type': 'iframe', 'url': '//p.gsh.io/user/sync/1' }, - { 'type': 'image', 'url': '//p.gsh.io/user/sync/2' } - ] - }, - 'seatbid': [ - { - 'seat': 'seat1', - 'group': 0, - 'bid': [ - { - 'id': '0', - 'impid': '1', - 'price': 2.016, - 'adid': '579ef31bfa788b9d2000d562', - 'nurl': 'https://p.gsh.io/pix/monitoring/win_notice/imp_5b05b9fde4b09084267a556f/im.gif?r=imp_5b05b9fde4b09084267a556f&i=1&a=579ef31bfa788b9d2000d562&b=0&p=${AUCTION_PRICE}', - 'adm': ' ', - 'adomain': [ 'aaa.com' ], - 'cid': '579ef268fa788b9d2000d55c', - 'crid': '579ef31bfa788b9d2000d562', - 'attr': [], - 'h': 600, - 'w': 120, - 'ext': { - 'vast_url': 'http://my.vast.com', - 'utrk': [ - { 'type': 'iframe', 'url': '//p.partner1.io/user/sync/1' } - ] - } - } - ] - }, - { - 'seat': 'seat2', - 'group': 0, - 'bid': [ - { - 'id': '1', - 'impid': '1', - 'price': 3, - 'adid': '542jlhdfd2112jnjf3x', - 'nurl': 'https://p.gsh.io/pix/monitoring/win_notice/imp_5b05b9fde4b09084267a556f/im.gif?r=imp_5b05b9fde4b09084267a556f&i=1&a=579ef31bfa788b9d2000d562&b=0&p=${AUCTION_PRICE}', - 'adm': ' ', - 'adomain': [ 'bbb.com' ], - 'cid': 'fgdlwjh2498ydjhg1', - 'crid': 'kjh34297ydh2133d', - 'attr': [], - 'h': 250, - 'w': 300, - 'ext': { - 'utrk': [ - { 'type': 'image', 'url': '//p.partner2.io/user/sync/1' } - ] - } - } - ] - } - ] - }; - it('returns an empty array on missing response', function () { - let response; - - response = spec.interpretResponse(undefined, { bidRequest: bannerBidRequest }); - expect(Array.isArray(response)).to.equal(true); - expect(response.length).to.equal(0); - - response = spec.interpretResponse({}, { bidRequest: bannerBidRequest }); - expect(Array.isArray(response)).to.equal(true); - expect(response.length).to.equal(0); - }); - it('aggregates banner bids from all seat bids', function () { - const response = spec.interpretResponse({ body: rtbResponse }, { bidRequest: bannerBidRequest }); - expect(Array.isArray(response)).to.equal(true); - expect(response.length).to.equal(2); - - const ad0 = response[ 0 ], ad1 = response[ 1 ]; - expect(ad0.requestId).to.equal(bannerBidRequest.bidId); - expect(ad0.cpm).to.equal(rtbResponse.seatbid[ 0 ].bid[ 0 ].price); - expect(ad0.width).to.equal(rtbResponse.seatbid[ 0 ].bid[ 0 ].w); - expect(ad0.height).to.equal(rtbResponse.seatbid[ 0 ].bid[ 0 ].h); - expect(ad0.ttl).to.equal(60 * 10); - expect(ad0.creativeId).to.equal(rtbResponse.seatbid[ 0 ].bid[ 0 ].crid); - expect(ad0.netRevenue).to.equal(true); - expect(ad0.currency).to.equal(rtbResponse.seatbid[ 0 ].bid[ 0 ].cur || rtbResponse.cur || 'USD'); - expect(ad0.ad).to.equal(rtbResponse.seatbid[ 0 ].bid[ 0 ].adm); - expect(ad0.vastXml).to.be.an('undefined'); - - expect(ad1.requestId).to.equal(bannerBidRequest.bidId); - expect(ad1.cpm).to.equal(rtbResponse.seatbid[ 1 ].bid[ 0 ].price); - expect(ad1.width).to.equal(rtbResponse.seatbid[ 1 ].bid[ 0 ].w); - expect(ad1.height).to.equal(rtbResponse.seatbid[ 1 ].bid[ 0 ].h); - expect(ad1.ttl).to.equal(60 * 10); - expect(ad1.creativeId).to.equal(rtbResponse.seatbid[ 1 ].bid[ 0 ].crid); - expect(ad1.netRevenue).to.equal(true); - expect(ad1.currency).to.equal(rtbResponse.seatbid[ 1 ].bid[ 0 ].cur || rtbResponse.cur || 'USD'); - expect(ad1.ad).to.equal(rtbResponse.seatbid[ 1 ].bid[ 0 ].adm); - expect(ad1.vastXml).to.be.an('undefined'); - - // expect(ad1.ad).to.be.an('undefined'); - // expect(ad1.vastXml).to.equal(rtbResponse.seatbid[ 1 ].bid[ 0 ].adm); - }); - it('aggregates video bids from all seat bids', function () { - const response = spec.interpretResponse({ body: rtbResponse }, { bidRequest: videoBidRequest }); - expect(Array.isArray(response)).to.equal(true); - expect(response.length).to.equal(2); - - const ad0 = response[ 0 ], ad1 = response[ 1 ]; - expect(ad0.requestId).to.equal(videoBidRequest.bidId); - expect(ad0.cpm).to.equal(rtbResponse.seatbid[ 0 ].bid[ 0 ].price); - expect(ad0.width).to.equal(rtbResponse.seatbid[ 0 ].bid[ 0 ].w); - expect(ad0.height).to.equal(rtbResponse.seatbid[ 0 ].bid[ 0 ].h); - expect(ad0.ttl).to.equal(60 * 10); - expect(ad0.creativeId).to.equal(rtbResponse.seatbid[ 0 ].bid[ 0 ].crid); - expect(ad0.netRevenue).to.equal(true); - expect(ad0.currency).to.equal(rtbResponse.seatbid[ 0 ].bid[ 0 ].cur || rtbResponse.cur || 'USD'); - expect(ad0.ad).to.be.an('undefined'); - expect(ad0.vastXml).to.equal(rtbResponse.seatbid[ 0 ].bid[ 0 ].adm); - expect(ad0.vastUrl).to.equal(rtbResponse.seatbid[ 0 ].bid[ 0 ].ext.vast_url); - - expect(ad1.requestId).to.equal(videoBidRequest.bidId); - expect(ad1.cpm).to.equal(rtbResponse.seatbid[ 1 ].bid[ 0 ].price); - expect(ad1.width).to.equal(rtbResponse.seatbid[ 1 ].bid[ 0 ].w); - expect(ad1.height).to.equal(rtbResponse.seatbid[ 1 ].bid[ 0 ].h); - expect(ad1.ttl).to.equal(60 * 10); - expect(ad1.creativeId).to.equal(rtbResponse.seatbid[ 1 ].bid[ 0 ].crid); - expect(ad1.netRevenue).to.equal(true); - expect(ad1.currency).to.equal(rtbResponse.seatbid[ 1 ].bid[ 0 ].cur || rtbResponse.cur || 'USD'); - expect(ad1.ad).to.be.an('undefined'); - expect(ad1.vastXml).to.equal(rtbResponse.seatbid[ 1 ].bid[ 0 ].adm); - expect(ad1.vastUrl).to.equal(rtbResponse.seatbid[ 1 ].bid[ 0 ].ext.vast_url); - }); - it('aggregates user-sync pixels', function () { - const response = spec.getUserSyncs({}, [ { body: rtbResponse } ]); - expect(Array.isArray(response)).to.equal(true); - expect(response.length).to.equal(4); - expect(response[ 0 ].type).to.equal(rtbResponse.ext.utrk[ 0 ].type); - expect(response[ 0 ].url).to.equal(rtbResponse.ext.utrk[ 0 ].url + '?gc=missing'); - expect(response[ 1 ].type).to.equal(rtbResponse.ext.utrk[ 1 ].type); - expect(response[ 1 ].url).to.equal(rtbResponse.ext.utrk[ 1 ].url + '?gc=missing'); - expect(response[ 2 ].type).to.equal(rtbResponse.seatbid[ 0 ].bid[ 0 ].ext.utrk[ 0 ].type); - expect(response[ 2 ].url).to.equal(rtbResponse.seatbid[ 0 ].bid[ 0 ].ext.utrk[ 0 ].url + '?gc=missing'); - expect(response[ 3 ].type).to.equal(rtbResponse.seatbid[ 1 ].bid[ 0 ].ext.utrk[ 0 ].type); - expect(response[ 3 ].url).to.equal(rtbResponse.seatbid[ 1 ].bid[ 0 ].ext.utrk[ 0 ].url + '?gc=missing'); - }); - }); -}); diff --git a/test/spec/modules/gamoshiBidAdapter_spec.js b/test/spec/modules/gamoshiBidAdapter_spec.js new file mode 100644 index 00000000000..2d63d47a73e --- /dev/null +++ b/test/spec/modules/gamoshiBidAdapter_spec.js @@ -0,0 +1,543 @@ +import {expect} from 'chai'; +import {spec} from 'modules/gamoshiBidAdapter'; +import {helper} from 'modules/gamoshiBidAdapter'; +import * as utils from 'src/utils'; +import {newBidder} from '../../../src/adapters/bidderFactory'; +import {deepClone} from 'src/utils'; + +const supplyPartnerId = '123'; +const adapter = newBidder(spec); +describe('GamoshiAdapter', function () { + describe('Get top Frame', function () { + it('check if you are in the top frame', function () { + expect(helper.getTopFrame()).to.equal(0); + }); + it('verify domain parsing', function () { + expect(helper.getTopWindowDomain('http://www.domain.com')).to.equal('www.domain.com'); + }); + }); + describe('Is String start with search ', function () { + it('check if a string started with', function () { + expect(helper.startsWith('gamoshi.com', 'gamo')).to.equal(true); + }); + }); + + 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 validate supply-partner ID', function () { + expect(spec.isBidRequestValid({params: {}})).to.equal(false); + expect(spec.isBidRequestValid({params: {supplyPartnerId: 123}})).to.equal(false); + expect(spec.isBidRequestValid({params: {supplyPartnerId: '123'}})).to.equal(true); + }); + + it('should validate RTB endpoint', function () { + expect(spec.isBidRequestValid({params: {supplyPartnerId: '123'}})).to.equal(true); // RTB endpoint has a default + expect(spec.isBidRequestValid({params: {supplyPartnerId: '123', rtbEndpoint: 123}})).to.equal(false); + expect(spec.isBidRequestValid({ + params: { + supplyPartnerId: '123', + rtbEndpoint: 'https://some.url.com' + } + })).to.equal(true); + }); + + it('should validate bid floor', function () { + expect(spec.isBidRequestValid({params: {supplyPartnerId: '123'}})).to.equal(true); // bidfloor has a default + expect(spec.isBidRequestValid({params: {supplyPartnerId: '123', bidfloor: '123'}})).to.equal(false); + expect(spec.isBidRequestValid({params: {supplyPartnerId: '123', bidfloor: 0.1}})).to.equal(true); + }); + + it('should validate adpos', function () { + expect(spec.isBidRequestValid({params: {supplyPartnerId: '123'}})).to.equal(true); // adpos has a default + expect(spec.isBidRequestValid({params: {supplyPartnerId: '123', adpos: '123'}})).to.equal(false); + expect(spec.isBidRequestValid({params: {supplyPartnerId: '123', adpos: 0.1}})).to.equal(true); + }); + + it('should validate instl', function () { + expect(spec.isBidRequestValid({params: {supplyPartnerId: '123'}})).to.equal(true); // adpos has a default + expect(spec.isBidRequestValid({params: {supplyPartnerId: '123', instl: '123'}})).to.equal(false); + expect(spec.isBidRequestValid({params: {supplyPartnerId: '123', instl: -1}})).to.equal(false); + expect(spec.isBidRequestValid({params: {supplyPartnerId: '123', instl: 0}})).to.equal(true); + expect(spec.isBidRequestValid({params: {supplyPartnerId: '123', instl: 1}})).to.equal(true); + expect(spec.isBidRequestValid({params: {supplyPartnerId: '123', instl: 2}})).to.equal(false); + }); + }); + + describe('buildRequests', function () { + const bidRequest = { + 'adUnitCode': 'adunit-code', + 'auctionId': '1d1a030790a475', + 'mediaTypes': { + banner: {} + }, + 'params': { + 'supplyPartnerId': supplyPartnerId + }, + 'sizes': [[300, 250], [300, 600]], + 'transactionId': 'a123456789', + refererInfo: {referer: 'http://examplereferer.com'}, + gdprConsent: { + consentString: 'some string', + gdprApplies: true + } + }; + it('returns an array', function () { + let response; + response = spec.buildRequests([]); + expect(Array.isArray(response)).to.equal(true); + expect(response.length).to.equal(0); + response = spec.buildRequests([bidRequest], bidRequest); + expect(Array.isArray(response)).to.equal(true); + expect(response.length).to.equal(1); + const adUnit1 = Object.assign({}, utils.deepClone(bidRequest), {auctionId: '1', adUnitCode: 'a'}); + const adUnit2 = Object.assign({}, utils.deepClone(bidRequest), {auctionId: '1', adUnitCode: 'b'}); + response = spec.buildRequests([adUnit1, adUnit2], bidRequest); + expect(Array.isArray(response)).to.equal(true); + expect(response.length).to.equal(2); + }); + + it('targets correct endpoint', function () { + let response; + response = spec.buildRequests([bidRequest], bidRequest)[0]; + expect(response.method).to.equal('POST'); + expect(response.url).to.match(new RegExp(`^https://rtb\\.gamoshi\\.io/r/${supplyPartnerId}/bidr\\?rformat=open_rtb&reqformat=rtb_json&bidder=prebid$`, 'g')); + expect(response.data.id).to.equal(bidRequest.auctionId); + const bidRequestWithEndpoint = utils.deepClone(bidRequest); + bidRequestWithEndpoint.params.rtbEndpoint = 'https://rtb2.gamoshi.io/a12'; + response = spec.buildRequests([bidRequestWithEndpoint], bidRequest)[0]; + expect(response.url).to.match(new RegExp(`^https://rtb2\\.gamoshi\\.io/a12/r/${supplyPartnerId}/bidr\\?rformat=open_rtb&reqformat=rtb_json&bidder=prebid$`, 'g')); + }); + + it('builds request correctly', function () { + let stub = sinon.stub(utils, 'getTopWindowUrl').returns('http://www.test.com/page.html'); + let bidRequest2 = deepClone(bidRequest); + bidRequest2.refererInfo.referer = 'http://www.test.com/page.html'; + let response = spec.buildRequests([bidRequest], bidRequest2)[0]; + expect(response.data.site.domain).to.equal('www.test.com'); + expect(response.data.site.page).to.equal('http://www.test.com/page.html'); + expect(response.data.site.ref).to.equal('http://www.test.com/page.html'); + expect(response.data.imp.length).to.equal(1); + expect(response.data.imp[0].id).to.equal(bidRequest.transactionId); + expect(response.data.imp[0].instl).to.equal(0); + expect(response.data.imp[0].tagid).to.equal(bidRequest.adUnitCode); + expect(response.data.imp[0].bidfloor).to.equal(0); + expect(response.data.imp[0].bidfloorcur).to.equal('USD'); + const bidRequestWithInstlEquals1 = utils.deepClone(bidRequest); + bidRequestWithInstlEquals1.params.instl = 1; + response = spec.buildRequests([bidRequestWithInstlEquals1], bidRequest2)[0]; + expect(response.data.imp[0].instl).to.equal(bidRequestWithInstlEquals1.params.instl); + const bidRequestWithInstlEquals0 = utils.deepClone(bidRequest); + bidRequestWithInstlEquals0.params.instl = 1; + response = spec.buildRequests([bidRequestWithInstlEquals0], bidRequest2)[0]; + expect(response.data.imp[0].instl).to.equal(bidRequestWithInstlEquals0.params.instl); + const bidRequestWithBidfloorEquals1 = utils.deepClone(bidRequest); + bidRequestWithBidfloorEquals1.params.bidfloor = 1; + response = spec.buildRequests([bidRequestWithBidfloorEquals1], bidRequest2)[0]; + expect(response.data.imp[0].bidfloor).to.equal(bidRequestWithBidfloorEquals1.params.bidfloor); + stub.restore(); + }); + + it('builds request banner object correctly', function () { + let response; + const bidRequestWithBanner = utils.deepClone(bidRequest); + bidRequestWithBanner.mediaTypes = { + banner: { + sizes: [[300, 250], [120, 600]] + } + }; + response = spec.buildRequests([bidRequestWithBanner], bidRequest)[0]; + expect(response.data.imp[0].banner.w).to.equal(bidRequestWithBanner.mediaTypes.banner.sizes[0][0]); + expect(response.data.imp[0].banner.h).to.equal(bidRequestWithBanner.mediaTypes.banner.sizes[0][1]); + expect(response.data.imp[0].banner.pos).to.equal(0); + expect(response.data.imp[0].banner.topframe).to.equal(0); + const bidRequestWithPosEquals1 = utils.deepClone(bidRequestWithBanner); + bidRequestWithPosEquals1.params.pos = 1; + response = spec.buildRequests([bidRequestWithPosEquals1], bidRequest)[0]; + expect(response.data.imp[0].banner.pos).to.equal(bidRequestWithPosEquals1.params.pos); + }); + + it('builds request video object correctly', function () { + let response; + const bidRequestWithVideo = utils.deepClone(bidRequest); + bidRequestWithVideo.mediaTypes = { + video: { + playerSize: [302, 252] + } + }; + response = spec.buildRequests([bidRequestWithVideo], bidRequest)[0]; + expect(response.data.imp[0].video.w).to.equal(bidRequestWithVideo.mediaTypes.video.playerSize[0][0]); + expect(response.data.imp[0].video.h).to.equal(bidRequestWithVideo.mediaTypes.video.playerSize[0][1]); + expect(response.data.imp[0].video.pos).to.equal(0); + const bidRequestWithPosEquals1 = utils.deepClone(bidRequestWithVideo); + bidRequestWithPosEquals1.params.pos = 1; + response = spec.buildRequests([bidRequestWithPosEquals1], bidRequest)[0]; + expect(response.data.imp[0].video.pos).to.equal(bidRequestWithPosEquals1.params.pos); + }); + + it('builds request video object correctly with context', function () { + let response; + const bidRequestWithVideo = utils.deepClone(bidRequest); + bidRequestWithVideo.mediaTypes = { + video: { + context: 'instream' + } + }; + response = spec.buildRequests([bidRequestWithVideo], bidRequest)[0]; + expect(response.data.imp[0].video.ext.context).to.equal('instream'); + bidRequestWithVideo.mediaTypes.video.context = 'outstream'; + + const bidRequestWithPosEquals1 = utils.deepClone(bidRequestWithVideo); + bidRequestWithPosEquals1.mediaTypes.video.context = 'outstream'; + response = spec.buildRequests([bidRequestWithPosEquals1], bidRequest)[0]; + expect(response.data.imp[0].video.ext.context).to.equal('outstream'); + + const bidRequestWithPosEquals2 = utils.deepClone(bidRequestWithVideo); + bidRequestWithPosEquals2.mediaTypes.video.context = null; + response = spec.buildRequests([bidRequestWithPosEquals2], bidRequest)[0]; + expect(response.data.imp[0].video.ext.context).to.equal(null); + }); + + it('builds request video object correctly with multi-dimensions size array', function () { + let response; + const bidRequestWithVideo = utils.deepClone(bidRequest); + bidRequestWithVideo.mediaTypes.video = { + playerSize: [[304, 254], [305, 255]], + context: 'instream' + }; + + response = spec.buildRequests([bidRequestWithVideo], bidRequest)[0]; + expect(response.data.imp[1].video.ext.context).to.equal('instream'); + bidRequestWithVideo.mediaTypes.video.context = 'outstream'; + + const bidRequestWithPosEquals1 = utils.deepClone(bidRequestWithVideo); + bidRequestWithPosEquals1.mediaTypes.video.context = 'outstream'; + response = spec.buildRequests([bidRequestWithPosEquals1], bidRequest)[0]; + expect(response.data.imp[1].video.ext.context).to.equal('outstream'); + + const bidRequestWithPosEquals2 = utils.deepClone(bidRequestWithVideo); + bidRequestWithPosEquals2.mediaTypes.video.context = null; + response = spec.buildRequests([bidRequestWithPosEquals2], bidRequest)[0]; + expect(response.data.imp[1].video.ext.context).to.equal(null); + }); + + it('builds request with gdpr consent', function () { + let response = spec.buildRequests([bidRequest], bidRequest)[0]; + expect(response.data.ext.gdpr_consent).to.exist; + expect(response.data.ext).to.have.property('gdpr_consent'); + expect(response.data.ext.gdpr_consent.consent_string).to.equal('some string'); + expect(response.data.ext.gdpr_consent.consent_required).to.equal(true); + + expect(response.data.regs.ext.gdpr).to.exist; + expect(response.data.user.ext.consent).to.equal('some string'); + }); + + it('build request with ID5 Id', function () { + const bidRequestClone = utils.deepClone(bidRequest); + bidRequestClone.userId = {}; + bidRequestClone.userId.id5id = 'id5-user-id'; + let request = spec.buildRequests([bidRequestClone], bidRequestClone)[0]; + expect(request.data.user.ext.eids).to.deep.equal([{ + 'source': 'id5-sync.com', + 'uids': [{ + 'id': 'id5-user-id', + 'ext': { + 'rtiPartner': 'ID5ID' + } + }] + }]); + }); + + it('build request with unified Id', function () { + const bidRequestClone = utils.deepClone(bidRequest); + bidRequestClone.userId = {}; + bidRequestClone.userId.tdid = 'tdid-user-id'; + let request = spec.buildRequests([bidRequestClone], bidRequestClone)[0]; + expect(request.data.user.ext.eids).to.deep.equal([{ + 'source': 'adserver.org', + 'uids': [{ + 'id': 'tdid-user-id', + 'ext': { + 'rtiPartner': 'TDID' + } + }] + }]); + }); + }); + + describe('interpretResponse', () => { + const bannerBidRequest = { + 'adUnitCode': 'adunit-code', + 'auctionId': '1d1a030790a475', + 'mediaTypes': { + banner: {} + }, + 'params': { + 'supplyPartnerId': supplyPartnerId + }, + 'sizes': [[300, 250], [300, 600]], + 'transactionId': 'a123456789', + 'bidId': '111', + refererInfo: {referer: 'http://examplereferer.com'} + }; + + const videoBidRequest = { + 'adUnitCode': 'adunit-code', + 'auctionId': '1d1a030790a475', + 'mediaTypes': { + video: {} + }, + 'params': { + 'supplyPartnerId': supplyPartnerId + }, + 'sizes': [[300, 250], [300, 600]], + 'transactionId': 'a123456789', + 'bidId': '111', + refererInfo: {referer: 'http://examplereferer.com'} + }; + + const rtbResponse = { + 'id': 'imp_5b05b9fde4b09084267a556f', + 'bidid': 'imp_5b05b9fde4b09084267a556f', + 'cur': 'USD', + 'ext': { + 'utrk': [ + {'type': 'iframe', 'url': '//rtb.gamoshi.io/user/sync/1'}, + {'type': 'image', 'url': '//rtb.gamoshi.io/user/sync/2'} + ] + }, + 'seatbid': [ + { + 'seat': 'seat1', + 'group': 0, + 'bid': [ + { + 'id': '0', + 'impid': '1', + 'price': 2.016, + 'adid': '579ef31bfa788b9d2000d562', + 'nurl': 'https://rtb.gamoshi.io/pix/monitoring/win_notice/imp_5b05b9fde4b09084267a556f/im.gif?r=imp_5b05b9fde4b09084267a556f&i=1&a=579ef31bfa788b9d2000d562&b=0&p=${AUCTION_PRICE}', + 'adm': '', + 'adomain': ['aaa.com'], + 'cid': '579ef268fa788b9d2000d55c', + 'crid': '579ef31bfa788b9d2000d562', + 'attr': [], + 'h': 600, + 'w': 120, + 'ext': { + 'vast_url': 'http://my.vast.com', + 'utrk': [ + {'type': 'iframe', 'url': '//p.partner1.io/user/sync/1'} + ] + } + } + ] + }, + { + 'seat': 'seat2', + 'group': 0, + 'bid': [ + { + 'id': '1', + 'impid': '1', + 'price': 3, + 'adid': '542jlhdfd2112jnjf3x', + 'nurl': 'https://rtb.gamoshi.io/pix/monitoring/win_notice/imp_5b05b9fde4b09084267a556f/im.gif?r=imp_5b05b9fde4b09084267a556f&i=1&a=579ef31bfa788b9d2000d562&b=0&p=${AUCTION_PRICE}', + 'adm': ' ', + 'adomain': ['bbb.com'], + 'cid': 'fgdlwjh2498ydjhg1', + 'crid': 'kjh34297ydh2133d', + 'attr': [], + 'h': 250, + 'w': 300, + 'ext': { + 'utrk': [ + {'type': 'image', 'url': '//p.partner2.io/user/sync/1'} + ] + } + } + ] + } + ] + }; + + const TTL = 360; + + it('returns an empty array on missing response', function () { + let response; + + response = spec.interpretResponse(undefined, {bidRequest: bannerBidRequest}); + expect(Array.isArray(response)).to.equal(true); + expect(response.length).to.equal(0); + + response = spec.interpretResponse({}, {bidRequest: bannerBidRequest}); + expect(Array.isArray(response)).to.equal(true); + expect(response.length).to.equal(0); + }); + + it('aggregates banner bids from all seat bids', function () { + const response = spec.interpretResponse({body: rtbResponse}, {bidRequest: bannerBidRequest}); + expect(Array.isArray(response)).to.equal(true); + expect(response.length).to.equal(1); + const ad0 = response[0]; + expect(ad0.requestId).to.equal(bannerBidRequest.bidId); + expect(ad0.cpm).to.equal(rtbResponse.seatbid[1].bid[0].price); + expect(ad0.width).to.equal(rtbResponse.seatbid[1].bid[0].w); + expect(ad0.height).to.equal(rtbResponse.seatbid[1].bid[0].h); + expect(ad0.ttl).to.equal(TTL); + expect(ad0.creativeId).to.equal(rtbResponse.seatbid[1].bid[0].crid); + expect(ad0.netRevenue).to.equal(true); + expect(ad0.currency).to.equal(rtbResponse.seatbid[1].bid[0].cur || rtbResponse.cur || 'USD'); + expect(ad0.ad).to.equal(rtbResponse.seatbid[1].bid[0].adm); + expect(ad0.vastXml).to.be.an('undefined'); + expect(ad0.vastUrl).to.be.an('undefined'); + }); + + it('aggregates video bids from all seat bids', function () { + const response = spec.interpretResponse({body: rtbResponse}, {bidRequest: videoBidRequest}); + expect(Array.isArray(response)).to.equal(true); + expect(response.length).to.equal(1); + const ad0 = response[0]; + expect(ad0.requestId).to.equal(videoBidRequest.bidId); + expect(ad0.cpm).to.equal(rtbResponse.seatbid[0].bid[0].price); + expect(ad0.width).to.equal(rtbResponse.seatbid[0].bid[0].w); + expect(ad0.height).to.equal(rtbResponse.seatbid[0].bid[0].h); + expect(ad0.ttl).to.equal(TTL); + expect(ad0.creativeId).to.equal(rtbResponse.seatbid[0].bid[0].crid); + expect(ad0.netRevenue).to.equal(true); + expect(ad0.currency).to.equal(rtbResponse.seatbid[0].bid[0].cur || rtbResponse.cur || 'USD'); + expect(ad0.ad).to.be.an('undefined'); + expect(ad0.vastXml).to.equal(rtbResponse.seatbid[0].bid[0].adm); + expect(ad0.vastUrl).to.equal(rtbResponse.seatbid[0].bid[0].ext.vast_url); + }); + + it('aggregates user-sync pixels', function () { + const response = spec.getUserSyncs({}, [{body: rtbResponse}]); + expect(Array.isArray(response)).to.equal(true); + expect(response.length).to.equal(4); + expect(response[0].type).to.equal(rtbResponse.ext.utrk[0].type); + expect(response[0].url).to.equal(rtbResponse.ext.utrk[0].url + '?gc=missing'); + expect(response[1].type).to.equal(rtbResponse.ext.utrk[1].type); + expect(response[1].url).to.equal(rtbResponse.ext.utrk[1].url + '?gc=missing'); + expect(response[2].type).to.equal(rtbResponse.seatbid[0].bid[0].ext.utrk[0].type); + expect(response[2].url).to.equal(rtbResponse.seatbid[0].bid[0].ext.utrk[0].url + '?gc=missing'); + expect(response[3].type).to.equal(rtbResponse.seatbid[1].bid[0].ext.utrk[0].type); + expect(response[3].url).to.equal(rtbResponse.seatbid[1].bid[0].ext.utrk[0].url + '?gc=missing'); + }); + + it('supports configuring outstream renderers', function () { + const videoResponse = { + 'id': '64f32497-b2f7-48ec-9205-35fc39894d44', + 'bidid': 'imp_5c24924de4b0d106447af333', + 'cur': 'USD', + 'seatbid': [ + { + 'seat': '3668', + 'group': 0, + 'bid': [ + { + 'id': 'gb_1', + 'impid': 'afbb5852-7cea-4a81-aa9a-a41aab505c23', + 'price': 5.0, + 'adid': '1274', + 'nurl': 'https://rtb.gamoshi.io/pix/1275/win_notice/imp_5c24924de4b0d106447af333/im.gif?r=imp_5c24924de4b0d106447af333&i=afbb5852-7cea-4a81-aa9a-a41aab505c23&a=1274&b=gb_1&p=${AUCTION_PRICE}', + 'adomain': [], + 'adm': '\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n', + 'cid': '3668', + 'crid': '1274', + 'cat': [], + 'attr': [], + 'h': 250, + 'w': 300, + 'ext': { + 'vast_url': 'https://rtb.gamoshi.io/pix/1275/vast_o/imp_5c24924de4b0d106447af333/im.xml?r=imp_5c24924de4b0d106447af333&i=afbb5852-7cea-4a81-aa9a-a41aab505c23&a=1274&b=gb_1&w=300&h=250&vatu=aHR0cHM6Ly9zdGF0aWMuZ2FtYmlkLmlvL2RlbW8vdmFzdC54bWw&vwarv', + 'imptrackers': [ + 'https://rtb.gamoshi.io/pix/1275/imp/imp_5c24924de4b0d106447af333/im.gif?r=imp_5c24924de4b0d106447af333&i=afbb5852-7cea-4a81-aa9a-a41aab505c23&a=1274&b=gb_1'] + } + } + ] + } + ], + 'ext': { + 'utrk': [{ + 'type': 'image', + 'url': 'https://rtb.gamoshi.io/pix/1275/scm?cb=1545900621675' + }] + } + }; + const videoRequest = deepClone(videoBidRequest); + videoRequest.mediaTypes.video.context = 'outstream'; + const result = spec.interpretResponse({body: videoResponse}, {bidRequest: videoRequest}); + expect(result[0].renderer).to.not.equal(undefined); + }); + + it('validates in/existing of gdpr consent', function () { + let videoResponse = { + 'id': '64f32497-b2f7-48ec-9205-35fc39894d44', + 'bidid': 'imp_5c24924de4b0d106447af333', + 'cur': 'USD', + 'seatbid': [ + { + 'seat': '3668', + 'group': 0, + 'bid': [ + { + 'id': 'gb_1', + 'impid': 'afbb5852-7cea-4a81-aa9a-a41aab505c23', + 'price': 5.0, + 'adid': '1274', + 'nurl': 'https://rtb.gamoshi.io/pix/1275/win_notice/imp_5c24924de4b0d106447af333/im.gif?r=imp_5c24924de4b0d106447af333&i=afbb5852-7cea-4a81-aa9a-a41aab505c23&a=1274&b=gb_1&p=${AUCTION_PRICE}', + 'adomain': [], + 'adm': '\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n', + 'cid': '3668', + 'crid': '1274', + 'cat': [], + 'attr': [], + 'h': 250, + 'w': 300, + 'ext': { + 'vast_url': 'https://rtb.gamoshi.io/pix/1275/vast_o/imp_5c24924de4b0d106447af333/im.xml?r=imp_5c24924de4b0d106447af333&i=afbb5852-7cea-4a81-aa9a-a41aab505c23&a=1274&b=gb_1&w=300&h=250&vatu=aHR0cHM6Ly9zdGF0aWMuZ2FtYmlkLmlvL2RlbW8vdmFzdC54bWw&vwarv', + 'imptrackers': [ + 'https://rtb.gamoshi.io/pix/1275/imp/imp_5c24924de4b0d106447af333/im.gif?r=imp_5c24924de4b0d106447af333&i=afbb5852-7cea-4a81-aa9a-a41aab505c23&a=1274&b=gb_1'] + } + } + ] + } + ], + 'ext': { + 'utrk': [{ + 'type': 'image', + 'url': 'https://rtb.gamoshi.io/pix/1275/scm?cb=1545900621675' + }] + } + }; + let gdprConsent = { + gdprApplies: true, + consentString: 'consent string' + }; + let result = spec.getUserSyncs({}, [{body: videoResponse}], gdprConsent); + expect(result).to.be.an('array'); + expect(result.length).to.equal(1); + expect(result[0].type).to.equal('image'); + expect(result[0].url).to.equal('https://rtb.gamoshi.io/pix/1275/scm?cb=1545900621675&gc=consent%20string'); + + gdprConsent.gdprApplies = false; + result = spec.getUserSyncs({}, [{body: videoResponse}], gdprConsent); + expect(result).to.be.an('array'); + expect(result.length).to.equal(1); + expect(result[0].type).to.equal('image'); + expect(result[0].url).to.equal('https://rtb.gamoshi.io/pix/1275/scm?cb=1545900621675&gc=missing'); + + videoResponse.ext.utrk[0].url = 'https://rtb.gamoshi.io/pix/1275/scm'; + result = spec.getUserSyncs({}, [{body: videoResponse}], gdprConsent); + expect(result).to.be.an('array'); + expect(result.length).to.equal(1); + expect(result[0].type).to.equal('image'); + expect(result[0].url).to.equal('https://rtb.gamoshi.io/pix/1275/scm?gc=missing'); + }); + }); +}); diff --git a/test/spec/modules/gjirafaBidAdapter_spec.js b/test/spec/modules/gjirafaBidAdapter_spec.js index d3aaf9765b2..a18ea1b99c3 100644 --- a/test/spec/modules/gjirafaBidAdapter_spec.js +++ b/test/spec/modules/gjirafaBidAdapter_spec.js @@ -53,9 +53,7 @@ describe('gjirafaAdapterTest', function () { 'sizes': [[728, 90], [980, 200], [980, 150], [970, 90], [970, 250]], 'bidId': '10bdc36fe0b48c8', 'bidderRequestId': '70deaff71c281d', - 'auctionId': 'f9012acc-b6b7-4748-9098-97252914f9dc', - 'consent_string': 'consentString', - 'consent_required': 'true' + 'auctionId': 'f9012acc-b6b7-4748-9098-97252914f9dc' }, { 'bidder': 'gjirafa', @@ -69,11 +67,17 @@ describe('gjirafaAdapterTest', function () { 'sizes': [[300, 250]], 'bidId': '81a6dcb65e2bd9', 'bidderRequestId': '70deaff71c281d', - 'auctionId': 'f9012acc-b6b7-4748-9098-97252914f9dc', - 'consent_string': 'consentString', - 'consent_required': 'true' + 'auctionId': 'f9012acc-b6b7-4748-9098-97252914f9dc' }]; + const bidderRequest = { + 'bids': bidRequests, + 'gdprConsent': { + 'consentString': 'consentString', + 'gdprApplies': true + } + }; + it('bidRequest HTTP method', function () { const requests = spec.buildRequests(bidRequests); requests.forEach(function(requestItem) { @@ -92,7 +96,7 @@ describe('gjirafaAdapterTest', function () { it('bidRequest data', function () { const requests = spec.buildRequests(bidRequests); requests.forEach(function(requestItem) { - expect(requestItem.data).to.exists; + expect(requestItem.data).to.exist; }); }); @@ -103,11 +107,11 @@ describe('gjirafaAdapterTest', function () { }); it('should add GDPR data', function () { - const requests = spec.buildRequests(bidRequests); - expect(requests[0].data.consent_string).to.exists; - expect(requests[0].data.consent_required).to.exists; - expect(requests[1].data.consent_string).to.exists; - expect(requests[1].data.consent_required).to.exists; + const requests = spec.buildRequests(bidRequests, bidderRequest); + expect(requests[0].data.consent_string).to.exist; + expect(requests[0].data.consent_required).to.exist; + expect(requests[1].data.consent_string).to.exist; + expect(requests[1].data.consent_required).to.exist; }); }); diff --git a/test/spec/modules/googleAnalyticsAdapter_spec.js b/test/spec/modules/googleAnalyticsAdapter_spec.js index 4260a831cad..6bc0d4e192d 100644 --- a/test/spec/modules/googleAnalyticsAdapter_spec.js +++ b/test/spec/modules/googleAnalyticsAdapter_spec.js @@ -1,14 +1,27 @@ +import ga from 'modules/googleAnalyticsAdapter'; + var assert = require('assert'); -var ga = require('modules/googleAnalyticsAdapter'); describe('Ga', function () { describe('enableAnalytics', function () { - it('should accept a tracker name option and output prefixed send string', function () { - var config = { options: { trackerName: 'foo' } }; - ga.enableAnalytics(config); + var cpmDistribution = function(cpm) { + return cpm <= 1 ? '<= 1$' : '> 1$'; + } + var config = { options: { trackerName: 'foo', enableDistribution: true, cpmDistribution: cpmDistribution } }; + + // enableAnalytics can only be called once + ga.enableAnalytics(config); + it('should accept a tracker name option and output prefixed send string', function () { var output = ga.getTrackerSend(); assert.equal(output, 'foo.send'); }); + + it('should use the custom cpm distribution', function() { + assert.equal(ga.getCpmDistribution(0.5), '<= 1$'); + assert.equal(ga.getCpmDistribution(1), '<= 1$'); + assert.equal(ga.getCpmDistribution(2), '> 1$'); + assert.equal(ga.getCpmDistribution(5.23), '> 1$'); + }); }); }); diff --git a/test/spec/modules/gridBidAdapter_spec.js b/test/spec/modules/gridBidAdapter_spec.js new file mode 100644 index 00000000000..36aaddb8b42 --- /dev/null +++ b/test/spec/modules/gridBidAdapter_spec.js @@ -0,0 +1,580 @@ +import { expect } from 'chai'; +import { spec } from 'modules/gridBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; + +describe('TheMediaGrid Adapter', function () { + const adapter = newBidder(spec); + + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function () { + let bid = { + 'bidder': 'grid', + 'params': { + 'uid': '1' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not passed', function () { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'uid': 0 + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + function parseRequest(url) { + const res = {}; + url.split('&').forEach((it) => { + const couple = it.split('='); + res[couple[0]] = decodeURIComponent(couple[1]); + }); + return res; + } + const bidderRequest = {refererInfo: {referer: 'http://example.com'}}; + const referrer = bidderRequest.refererInfo.referer; + let bidRequests = [ + { + 'bidder': 'grid', + 'params': { + 'uid': '1' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }, + { + 'bidder': 'grid', + 'params': { + 'uid': '1' + }, + 'adUnitCode': 'adunit-code-2', + 'sizes': [[728, 90]], + 'bidId': '3150ccb55da321', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }, + { + 'bidder': 'grid', + 'params': { + 'uid': '2' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '42dbe3a7168a6a', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + } + ]; + + it('should attach valid params to the tag', function () { + const request = spec.buildRequests([bidRequests[0]], bidderRequest); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload).to.have.property('u', referrer); + expect(payload).to.have.property('auids', '1'); + expect(payload).to.have.property('sizes', '300x250,300x600'); + expect(payload).to.have.property('r', '22edbae2733bf6'); + expect(payload).to.have.property('wrapperType', 'Prebid_js'); + expect(payload).to.have.property('wrapperVersion', '$prebid.version$'); + }); + + it('sizes must not be duplicated', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload).to.have.property('u', referrer); + expect(payload).to.have.property('auids', '1,1,2'); + expect(payload).to.have.property('sizes', '300x250,300x600,728x90'); + expect(payload).to.have.property('r', '22edbae2733bf6'); + }); + + it('if gdprConsent is present payload must have gdpr params', function () { + const request = spec.buildRequests(bidRequests, {gdprConsent: {consentString: 'AAA', gdprApplies: true}, refererInfo: bidderRequest.refererInfo}); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload).to.have.property('u', referrer); + expect(payload).to.have.property('gdpr_consent', 'AAA'); + expect(payload).to.have.property('gdpr_applies', '1'); + }); + + it('if gdprApplies is false gdpr_applies must be 0', function () { + const request = spec.buildRequests(bidRequests, {gdprConsent: {consentString: 'AAA', gdprApplies: false}}); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload).to.have.property('gdpr_consent', 'AAA'); + expect(payload).to.have.property('gdpr_applies', '0'); + }); + + it('if gdprApplies is undefined gdpr_applies must be 1', function () { + const request = spec.buildRequests(bidRequests, {gdprConsent: {consentString: 'AAA'}}); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload).to.have.property('gdpr_consent', 'AAA'); + expect(payload).to.have.property('gdpr_applies', '1'); + }); + }); + + describe('interpretResponse', function () { + const responses = [ + {'bid': [{'price': 1.15, 'adm': '
    test content 1
    ', 'auid': 1, 'h': 250, 'w': 300, dealid: 11}], 'seat': '1'}, + {'bid': [{'price': 0.5, 'adm': '
    test content 2
    ', 'auid': 2, 'h': 600, 'w': 300}], 'seat': '1'}, + {'bid': [{'price': 0.15, 'adm': '
    test content 3
    ', 'auid': 1, 'h': 90, 'w': 728}], 'seat': '1'}, + {'bid': [{'price': 0, 'auid': 3, 'h': 250, 'w': 300}], 'seat': '1'}, + {'bid': [{'price': 0, 'adm': '
    test content 5
    ', 'h': 250, 'w': 300}], 'seat': '1'}, + undefined, + {'bid': [], 'seat': '1'}, + {'seat': '1'}, + ]; + + it('should get correct bid response', function () { + const bidRequests = [ + { + 'bidder': 'grid', + 'params': { + 'uid': '1' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '659423fff799cb', + 'bidderRequestId': '5f2009617a7c0a', + 'auctionId': '1cbd2feafe5e8b', + } + ]; + const request = spec.buildRequests(bidRequests); + const expectedResponse = [ + { + 'requestId': '659423fff799cb', + 'cpm': 1.15, + 'creativeId': 1, + 'dealId': 11, + 'width': 300, + 'height': 250, + 'ad': '
    test content 1
    ', + 'bidderCode': 'grid', + 'currency': 'USD', + 'mediaType': 'banner', + 'netRevenue': false, + 'ttl': 360, + } + ]; + + const result = spec.interpretResponse({'body': {'seatbid': [responses[0]]}}, request); + expect(result).to.deep.equal(expectedResponse); + }); + + it('should get correct multi bid response', function () { + const bidRequests = [ + { + 'bidder': 'grid', + 'params': { + 'uid': '1' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '300bfeb0d71a5b', + 'bidderRequestId': '2c2bb1972df9a', + 'auctionId': '1fa09aee5c8c99', + }, + { + 'bidder': 'grid', + 'params': { + 'uid': '2' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '4dff80cc4ee346', + 'bidderRequestId': '2c2bb1972df9a', + 'auctionId': '1fa09aee5c8c99', + }, + { + 'bidder': 'grid', + 'params': { + 'uid': '1' + }, + 'adUnitCode': 'adunit-code-2', + 'sizes': [[728, 90]], + 'bidId': '5703af74d0472a', + 'bidderRequestId': '2c2bb1972df9a', + 'auctionId': '1fa09aee5c8c99', + } + ]; + const request = spec.buildRequests(bidRequests); + const expectedResponse = [ + { + 'requestId': '300bfeb0d71a5b', + 'cpm': 1.15, + 'creativeId': 1, + 'dealId': 11, + 'width': 300, + 'height': 250, + 'ad': '
    test content 1
    ', + 'bidderCode': 'grid', + 'currency': 'USD', + 'mediaType': 'banner', + 'netRevenue': false, + 'ttl': 360, + }, + { + 'requestId': '4dff80cc4ee346', + 'cpm': 0.5, + 'creativeId': 2, + 'dealId': undefined, + 'width': 300, + 'height': 600, + 'ad': '
    test content 2
    ', + 'bidderCode': 'grid', + 'currency': 'USD', + 'mediaType': 'banner', + 'netRevenue': false, + 'ttl': 360, + }, + { + 'requestId': '5703af74d0472a', + 'cpm': 0.15, + 'creativeId': 1, + 'dealId': undefined, + 'width': 728, + 'height': 90, + 'ad': '
    test content 3
    ', + 'bidderCode': 'grid', + 'currency': 'USD', + 'mediaType': 'banner', + 'netRevenue': false, + 'ttl': 360, + } + ]; + + const result = spec.interpretResponse({'body': {'seatbid': responses.slice(0, 3)}}, request); + expect(result).to.deep.equal(expectedResponse); + }); + + it('should get correct video bid response', function () { + const bidRequests = [ + { + 'bidder': 'grid', + 'params': { + 'uid': '11' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '659423fff799cb', + 'bidderRequestId': '5f2009617a7c0a', + 'auctionId': '1cbd2feafe5e8b', + 'mediaTypes': { + 'video': { + 'context': 'instream' + } + } + }, + { + 'bidder': 'grid', + 'params': { + 'uid': '12' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '2bc598e42b6a', + 'bidderRequestId': '5f2009617a7c0a', + 'auctionId': '1cbd2feafe5e8b', + 'mediaTypes': { + 'video': { + 'context': 'instream' + } + } + } + ]; + const response = [ + {'bid': [{'price': 1.15, 'adm': '\n<\/Ad>\n<\/VAST>', 'auid': 11, content_type: 'video', w: 300, h: 600}], 'seat': '2'}, + {'bid': [{'price': 1.00, 'adm': '\n<\/Ad>\n<\/VAST>', 'auid': 12, content_type: 'video'}], 'seat': '2'} + ]; + const request = spec.buildRequests(bidRequests); + const expectedResponse = [ + { + 'requestId': '659423fff799cb', + 'cpm': 1.15, + 'creativeId': 11, + 'dealId': undefined, + 'width': 300, + 'height': 600, + 'bidderCode': 'grid', + 'currency': 'USD', + 'mediaType': 'video', + 'netRevenue': false, + 'ttl': 360, + 'vastXml': '\n<\/Ad>\n<\/VAST>', + 'adResponse': { + 'content': '\n<\/Ad>\n<\/VAST>' + } + } + ]; + + const result = spec.interpretResponse({'body': {'seatbid': response}}, request); + expect(result).to.deep.equal(expectedResponse); + }); + + it('handles wrong and nobid responses', function () { + const bidRequests = [ + { + 'bidder': 'grid', + 'params': { + 'uid': '3' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '300bfeb0d7190gf', + 'bidderRequestId': '2c2bb1972d23af', + 'auctionId': '1fa09aee5c84d34', + }, + { + 'bidder': 'grid', + 'params': { + 'uid': '4' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '300bfeb0d71321', + 'bidderRequestId': '2c2bb1972d23af', + 'auctionId': '1fa09aee5c84d34', + }, + { + 'bidder': 'grid', + 'params': { + 'uid': '5' + }, + 'adUnitCode': 'adunit-code-2', + 'sizes': [[728, 90]], + 'bidId': '300bfeb0d7183bb', + 'bidderRequestId': '2c2bb1972d23af', + 'auctionId': '1fa09aee5c84d34', + } + ]; + const request = spec.buildRequests(bidRequests); + const result = spec.interpretResponse({'body': {'seatbid': responses.slice(2)}}, request); + expect(result.length).to.equal(0); + }); + + it('complicated case', function () { + const fullResponse = [ + {'bid': [{'price': 1.15, 'adm': '
    test content 1
    ', 'auid': 1, 'h': 250, 'w': 300, dealid: 11}], 'seat': '1'}, + {'bid': [{'price': 0.5, 'adm': '
    test content 2
    ', 'auid': 2, 'h': 600, 'w': 300, dealid: 12}], 'seat': '1'}, + {'bid': [{'price': 0.15, 'adm': '
    test content 3
    ', 'auid': 1, 'h': 90, 'w': 728}], 'seat': '1'}, + {'bid': [{'price': 0.15, 'adm': '
    test content 4
    ', 'auid': 1, 'h': 600, 'w': 300}], 'seat': '1'}, + {'bid': [{'price': 0.5, 'adm': '
    test content 5
    ', 'auid': 2, 'h': 600, 'w': 350}], 'seat': '1'}, + ]; + const bidRequests = [ + { + 'bidder': 'grid', + 'params': { + 'uid': '1' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '2164be6358b9', + 'bidderRequestId': '106efe3247', + 'auctionId': '32a1f276cb87cb8', + }, + { + 'bidder': 'grid', + 'params': { + 'uid': '1' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '326bde7fbf69', + 'bidderRequestId': '106efe3247', + 'auctionId': '32a1f276cb87cb8', + }, + { + 'bidder': 'grid', + 'params': { + 'uid': '2' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '4e111f1b66e4', + 'bidderRequestId': '106efe3247', + 'auctionId': '32a1f276cb87cb8', + }, + { + 'bidder': 'grid', + 'params': { + 'uid': '1' + }, + 'adUnitCode': 'adunit-code-2', + 'sizes': [[728, 90]], + 'bidId': '26d6f897b516', + 'bidderRequestId': '106efe3247', + 'auctionId': '32a1f276cb87cb8', + }, + { + 'bidder': 'grid', + 'params': { + 'uid': '2' + }, + 'adUnitCode': 'adunit-code-2', + 'sizes': [[728, 90]], + 'bidId': '1751cd90161', + 'bidderRequestId': '106efe3247', + 'auctionId': '32a1f276cb87cb8', + } + ]; + const request = spec.buildRequests(bidRequests); + const expectedResponse = [ + { + 'requestId': '2164be6358b9', + 'cpm': 1.15, + 'creativeId': 1, + 'dealId': 11, + 'width': 300, + 'height': 250, + 'ad': '
    test content 1
    ', + 'bidderCode': 'grid', + 'currency': 'USD', + 'mediaType': 'banner', + 'netRevenue': false, + 'ttl': 360, + }, + { + 'requestId': '4e111f1b66e4', + 'cpm': 0.5, + 'creativeId': 2, + 'dealId': 12, + 'width': 300, + 'height': 600, + 'ad': '
    test content 2
    ', + 'bidderCode': 'grid', + 'currency': 'USD', + 'mediaType': 'banner', + 'netRevenue': false, + 'ttl': 360, + }, + { + 'requestId': '26d6f897b516', + 'cpm': 0.15, + 'creativeId': 1, + 'dealId': undefined, + 'width': 728, + 'height': 90, + 'ad': '
    test content 3
    ', + 'bidderCode': 'grid', + 'currency': 'USD', + 'mediaType': 'banner', + 'netRevenue': false, + 'ttl': 360, + }, + { + 'requestId': '326bde7fbf69', + 'cpm': 0.15, + 'creativeId': 1, + 'dealId': undefined, + 'width': 300, + 'height': 600, + 'ad': '
    test content 4
    ', + 'bidderCode': 'grid', + 'currency': 'USD', + 'mediaType': 'banner', + 'netRevenue': false, + 'ttl': 360, + } + ]; + + const result = spec.interpretResponse({'body': {'seatbid': fullResponse}}, request); + expect(result).to.deep.equal(expectedResponse); + }); + + it('dublicate uids and sizes in one slot', function () { + const fullResponse = [ + {'bid': [{'price': 1.15, 'adm': '
    test content 1
    ', 'auid': 1, 'h': 250, 'w': 300}], 'seat': '1'}, + {'bid': [{'price': 0.5, 'adm': '
    test content 2
    ', 'auid': 1, 'h': 250, 'w': 300}], 'seat': '1'}, + ]; + const bidRequests = [ + { + 'bidder': 'grid', + 'params': { + 'uid': '1' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '5126e301f4be', + 'bidderRequestId': '171c5405a390', + 'auctionId': '35bcbc0f7e79c', + }, + { + 'bidder': 'grid', + 'params': { + 'uid': '1' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '57b2ebe70e16', + 'bidderRequestId': '171c5405a390', + 'auctionId': '35bcbc0f7e79c', + }, + { + 'bidder': 'grid', + 'params': { + 'uid': '1' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '225fcd44b18c', + 'bidderRequestId': '171c5405a390', + 'auctionId': '35bcbc0f7e79c', + } + ]; + const request = spec.buildRequests(bidRequests); + const expectedResponse = [ + { + 'requestId': '5126e301f4be', + 'cpm': 1.15, + 'creativeId': 1, + 'dealId': undefined, + 'width': 300, + 'height': 250, + 'ad': '
    test content 1
    ', + 'bidderCode': 'grid', + 'currency': 'USD', + 'mediaType': 'banner', + 'netRevenue': false, + 'ttl': 360, + }, + { + 'requestId': '57b2ebe70e16', + 'cpm': 0.5, + 'creativeId': 1, + 'dealId': undefined, + 'width': 300, + 'height': 250, + 'ad': '
    test content 2
    ', + 'bidderCode': 'grid', + 'currency': 'USD', + 'mediaType': 'banner', + 'netRevenue': false, + 'ttl': 360, + } + ]; + + const result = spec.interpretResponse({'body': {'seatbid': fullResponse}}, request); + expect(result).to.deep.equal(expectedResponse); + }); + }); +}); diff --git a/test/spec/modules/gumgumBidAdapter_spec.js b/test/spec/modules/gumgumBidAdapter_spec.js index 0c1431b71a5..82c8e494533 100644 --- a/test/spec/modules/gumgumBidAdapter_spec.js +++ b/test/spec/modules/gumgumBidAdapter_spec.js @@ -17,7 +17,8 @@ describe('gumgumAdapter', function () { let bid = { 'bidder': 'gumgum', 'params': { - 'inScreen': '10433394' + 'inScreen': '10433394', + 'bidfloor': 0.05 }, 'adUnitCode': 'adunit-code', 'sizes': [[300, 250], [300, 600], [1, 1]], @@ -40,7 +41,7 @@ describe('gumgumAdapter', function () { expect(spec.isBidRequestValid(bid)).to.equal(true); }); - it('should return false when required params are not passed', function () { + it('should return false when no unit type is specified', function () { let bid = Object.assign({}, bid); delete bid.params; bid.params = { @@ -48,6 +49,16 @@ describe('gumgumAdapter', function () { }; expect(spec.isBidRequestValid(bid)).to.equal(false); }); + + it('should return false when bidfloor is not a number', function () { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'inSlot': '789', + 'bidfloor': '0.50' + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); }); describe('buildRequests', function () { @@ -69,11 +80,40 @@ describe('gumgumAdapter', function () { expect(request.method).to.equal('GET'); expect(request.id).to.equal('30b31c1838de1e'); }); + it('should correctly set the request paramters depending on params field', function () { + const request = Object.assign({}, bidRequests[0]); + delete request.params; + request.params = { + 'inScreen': '10433394', + 'bidfloor': 0.05 + }; + const bidRequest = spec.buildRequests([request])[0]; + expect(bidRequest.data.pi).to.equal(2); + expect(bidRequest.data).to.include.any.keys('t'); + expect(bidRequest.data).to.include.any.keys('fp'); + }); + it('should correctly set the request paramters depending on params field', function () { + const request = Object.assign({}, bidRequests[0]); + delete request.params; + request.params = { + 'ICV': '10433395' + }; + const bidRequest = spec.buildRequests([request])[0]; + expect(bidRequest.data.pi).to.equal(5); + expect(bidRequest.data).to.include.any.keys('ni'); + }); + it('should not add additional parameters depending on params field', function () { + const request = spec.buildRequests(bidRequests)[0]; + expect(request.data).to.not.include.any.keys('ni'); + expect(request.data).to.not.include.any.keys('t'); + expect(request.data).to.not.include.any.keys('eAdBuyId'); + expect(request.data).to.not.include.any.keys('adBuyId'); + }); it('should add consent parameters if gdprConsent is present', function () { const gdprConsent = { consentString: 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==', gdprApplies: true }; const fakeBidRequest = { gdprConsent: gdprConsent }; const bidRequest = spec.buildRequests(bidRequests, fakeBidRequest)[0]; - expect(bidRequest.data.gdprApplies).to.eq(true); + expect(bidRequest.data.gdprApplies).to.eq(1); expect(bidRequest.data.gdprConsent).to.eq('BOJ/P2HOJ/P2HABABMAAAAAZ+A=='); }); it('should handle gdprConsent is present but values are undefined case', function () { @@ -82,6 +122,31 @@ describe('gumgumAdapter', function () { const bidRequest = spec.buildRequests(bidRequests, fakeBidRequest)[0]; expect(bidRequest.data).to.not.include.any.keys('gdprConsent') }); + it('should add a tdid parameter if request contains unified id from TradeDesk', function () { + const unifiedId = { + 'userId': { + 'tdid': 'tradedesk-id' + } + } + const request = Object.assign(unifiedId, bidRequests[0]); + const bidRequest = spec.buildRequests([request])[0]; + expect(bidRequest.data.tdid).to.eq(unifiedId.userId.tdid); + }); + it('should not add a tdid parameter if unified id is not found', function () { + const request = spec.buildRequests(bidRequests)[0]; + expect(request.data).to.not.include.any.keys('tdid'); + }); + it('should send ns parameter if browser contains navigator.connection property', function () { + const bidRequest = spec.buildRequests(bidRequests)[0]; + const connection = window.navigator && window.navigator.connection; + if (connection) { + const downlink = connection.downlink || connection.bandwidth; + expect(bidRequest.data).to.include.any.keys('ns'); + expect(bidRequest.data.ns).to.eq(Math.round(downlink * 1024)); + } else { + expect(bidRequest.data).to.not.include.any.keys('ns'); + } + }); }) describe('interpretResponse', function () { diff --git a/test/spec/modules/hpmdnetworkBidAdapter_spec.js b/test/spec/modules/hpmdnetworkBidAdapter_spec.js new file mode 100644 index 00000000000..37ec44f07c4 --- /dev/null +++ b/test/spec/modules/hpmdnetworkBidAdapter_spec.js @@ -0,0 +1,148 @@ +import { expect } from 'chai'; +import { spec } from 'modules/hpmdnetworkBidAdapter'; + +describe('HPMDNetwork Adapter', function() { + describe('isBidRequestValid', function () { + it('should return true when required params found', function () { + const validBid = { + bidder: 'hpmdnetwork', + params: { + placementId: '1' + } + }; + + expect(spec.isBidRequestValid(validBid)).to.equal(true); + }); + + it('should return false for when required params are not passed', function () { + const invalidBid = { + bidder: 'hpmdnetwork', + params: {} + }; + + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + const bidRequests = [ + { + bidId: 'bid1', + bidder: 'hpmdnetwork', + params: { + placementId: '1' + } + }, + { + bidId: 'bid2', + bidder: 'hpmdnetwork', + params: { + placementId: '2', + } + } + ]; + const bidderRequest = { + refererInfo: { + referer: 'https://example.com?foo=bar' + } + }; + + const bidRequest = spec.buildRequests(bidRequests, bidderRequest); + + it('should build single POST request for multiple bids', function() { + expect(bidRequest.method).to.equal('POST'); + expect(bidRequest.url).to.equal('//banner.hpmdnetwork.ru/bidder/request'); + expect(bidRequest.data).to.be.an('object'); + expect(bidRequest.data.places).to.be.an('array'); + expect(bidRequest.data.places).to.have.lengthOf(2); + }); + + it('should pass bid parameters', function() { + const place1 = bidRequest.data.places[0]; + const place2 = bidRequest.data.places[1]; + + expect(place1.placementId).to.equal('1'); + expect(place2.placementId).to.equal('2'); + expect(place1.id).to.equal('bid1'); + expect(place2.id).to.equal('bid2'); + }); + + it('should pass site parameters', function() { + const url = bidRequest.data.url; + + expect(url).to.be.an('String'); + expect(url).to.equal('https://example.com?foo=bar'); + }); + + it('should pass settings', function() { + const settings = bidRequest.data.settings; + + expect(settings).to.be.an('object'); + expect(settings.currency).to.equal('RUB'); + }); + }); + + describe('interpretResponse', function () { + const serverResponse = { + body: { + 'bids': + [ + { + 'cpm': 20, + 'currency': 'RUB', + 'displayUrl': '//banner.hpmdnetwork.ru/bidder/display?dbid=0&vbid=168', + 'id': '1', + 'creativeId': '11111', + }, + { + 'cpm': 30, + 'currency': 'RUB', + 'displayUrl': '//banner.hpmdnetwork.ru/bidder/display?dbid=0&vbid=170', + 'id': '2', + 'creativeId': '22222', + 'width': 300, + 'height': 250, + }, + ] + } + }; + + const bids = spec.interpretResponse(serverResponse); + + it('should return empty array for response with no bids', function() { + const emptyBids = spec.interpretResponse({ body: {} }); + + expect(emptyBids).to.have.lengthOf(0); + }); + + it('should parse all bids from response', function() { + expect(bids).to.have.lengthOf(2); + }); + + it('should parse bid without sizes', function() { + expect(bids[0].requestId).to.equal('1'); + expect(bids[0].cpm).to.equal(20); + expect(bids[0].width).to.equal(1); + expect(bids[0].height).to.equal(1); + expect(bids[0].ttl).to.equal(300); + expect(bids[0].currency).to.equal('RUB'); + expect(bids[0]).to.have.property('creativeId'); + expect(bids[0].creativeId).to.equal('11111'); + expect(bids[0].netRevenue).to.equal(true); + expect(bids[0].ad).to.include(''); + }); + + it('should parse bid with sizes', function() { + expect(bids[1].requestId).to.equal('2'); + expect(bids[1].cpm).to.equal(30); + expect(bids[1].width).to.equal(300); + expect(bids[1].height).to.equal(250); + expect(bids[1].ttl).to.equal(300); + expect(bids[1].currency).to.equal('RUB'); + expect(bids[1]).to.have.property('creativeId'); + expect(bids[1].creativeId).to.equal('22222'); + expect(bids[1].netRevenue).to.equal(true); + expect(bids[1].ad).to.include(''); + }); + }); +}); diff --git a/test/spec/modules/iasBidAdapter_spec.js b/test/spec/modules/iasBidAdapter_spec.js index 21ef9f8e15a..8197ee25856 100644 --- a/test/spec/modules/iasBidAdapter_spec.js +++ b/test/spec/modules/iasBidAdapter_spec.js @@ -116,6 +116,9 @@ describe('iasBidAdapter is an adapter that', function () { it('screen size', function () { expect(val).to.match(/.*sr=[0-9]*\.[0-9]*/); }); + it('url value', function () { + expect(val).to.match(/.*url=https?%3A%2F%2F[^\s$.?#].[^\s]*/); + }); }); it('has property `bidRequest` that is the first passed in bid request', function () { expect(spec.buildRequests(bidRequests)).to.deep.include({ @@ -229,5 +232,112 @@ describe('iasBidAdapter is an adapter that', function () { expect(adapter.interpretResponse(serverResponse, requests)).to.length(2); }); }); + describe('returns a list of bid response that with custom value', function () { + let bidRequests, bidResponse, slots, custom, serverResponse; + beforeEach(function () { + bidRequests = [ + { + adUnitCode: 'one-div-id', + auctionId: 'someAuctionId', + bidId: 'someBidId1', + bidder: 'ias', + bidderRequestId: 'someBidderRequestId', + params: { + pubId: '1234', + adUnitPath: '/a/b/c' + }, + sizes: [ + [10, 20], + [300, 400] + ], + transactionId: 'someTransactionId' + }, + { + adUnitCode: 'two-div-id', + auctionId: 'someAuctionId', + bidId: 'someBidId2', + bidder: 'ias', + bidderRequestId: 'someBidderRequestId', + params: { + pubId: '1234', + adUnitPath: '/d/e/f' + }, + sizes: [ + [50, 60] + ], + transactionId: 'someTransactionId' + } + ]; + const request = { + bidRequest: { + bidId: '102938' + } + }; + slots = {}; + slots['test-div-id'] = { + id: '1234', + vw: ['60', '70'] + }; + slots['test-div-id-two'] = { + id: '5678', + vw: ['80', '90'] + }; + custom = {}; + custom['ias-kw'] = ['IAS_1_KW', 'IAS_2_KW']; + serverResponse = { + body: { + brandSafety: { + adt: 'adtVal', + alc: 'alcVal', + dlm: 'dlmVal', + drg: 'drgVal', + hat: 'hatVal', + off: 'offVal', + vio: 'vioVal' + }, + fr: 'false', + slots: slots, + custom: custom + }, + headers: {} + }; + bidResponse = spec.interpretResponse(serverResponse, request); + }); + it('has IAS keyword `adt` as property', function () { + expect(bidResponse[0]).to.deep.include({ adt: 'adtVal' }); + }); + it('has IAS keyword `alc` as property', function () { + expect(bidResponse[0]).to.deep.include({ alc: 'alcVal' }); + }); + it('has IAS keyword `dlm` as property', function () { + expect(bidResponse[0]).to.deep.include({ dlm: 'dlmVal' }); + }); + it('has IAS keyword `drg` as property', function () { + expect(bidResponse[0]).to.deep.include({ drg: 'drgVal' }); + }); + it('has IAS keyword `hat` as property', function () { + expect(bidResponse[0]).to.deep.include({ hat: 'hatVal' }); + }); + it('has IAS keyword `off` as property', function () { + expect(bidResponse[0]).to.deep.include({ off: 'offVal' }); + }); + it('has IAS keyword `vio` as property', function () { + expect(bidResponse[0]).to.deep.include({ vio: 'vioVal' }); + }); + it('has IAS keyword `fr` as property', function () { + expect(bidResponse[0]).to.deep.include({ fr: 'false' }); + }); + it('has property `slots`', function () { + expect(bidResponse[0]).to.deep.include({ slots: slots }); + }); + it('has property `custom`', function () { + expect(bidResponse[0]).to.deep.include({ custom: custom }); + }); + it('response is the same for multiple slots', function () { + var adapter = spec; + var requests = adapter.buildRequests(bidRequests); + expect(adapter.interpretResponse(serverResponse, requests)).to.length(3); + }); + }); }); }); diff --git a/test/spec/modules/imonomyBidAdapter_spec.js b/test/spec/modules/imonomyBidAdapter_spec.js new file mode 100644 index 00000000000..206e227a3b1 --- /dev/null +++ b/test/spec/modules/imonomyBidAdapter_spec.js @@ -0,0 +1,164 @@ +import { expect } from 'chai'; +import { spec } from 'modules/imonomyBidAdapter'; + +describe('Imonomy Adapter Tests', function () { + const bidsRequest = [ + { + bidder: 'imonomy', + params: { + placementId: '170577', + hbid: '14567718624', + }, + placementCode: 'div-gpt-ad-1460505748561-0', + transactionId: '9f801c02-bbe8-4683-8ed4-bc816ea186bb', + sizes: [ + [300, 250] + ], + bidId: '2faedf1095f815', + bidderRequestId: '18065867f8ae39', + auctionId: '529e1518-b872-45cf-807c-2d41dfa5bcd3' + }, + { + bidder: 'imonomy', + params: { + placementId: '281277', + hbid: '14567718624', + floorPrice: 0.5 + }, + placementCode: 'div-gpt-ad-1460505748561-0', + transactionId: '9f801c02-bbe8-4683-8ed4-bc816ea186bb', + sizes: [ + [728, 90] + ], + bidId: '3c34e2367a3f59', + bidderRequestId: '18065867f8ae39', + auctionId: '529e1518-b872-45cf-807c-2d41dfa5bcd3' + }]; + + const bidsResponse = { + body: { + bids: [ + { + placementid: '170577', + uuid: '2faedf1095f815', + width: 300, + height: 250, + cpm: 0.51, + creative: '', + ttl: 360, + currency: 'USD', + netRevenue: true, + creativeId: 'd30b58c2ba' + } + ] + } + }; + + it('Verifies imonomyAdapter bidder code', function () { + expect(spec.code).to.equal('imonomy'); + }); + + it('Verifies imonomyAdapter bid request validation', function () { + expect(spec.isBidRequestValid(bidsRequest[0])).to.equal(true); + expect(spec.isBidRequestValid(bidsRequest[1])).to.equal(true); + expect(spec.isBidRequestValid({})).to.equal(false); + expect(spec.isBidRequestValid({ params: {} })).to.equal(false); + expect(spec.isBidRequestValid({ params: { hbid: 12345 } })).to.equal(false); + expect(spec.isBidRequestValid({ params: { placementid: 12345 } })).to.equal(false); + expect(spec.isBidRequestValid({ params: { hbid: 12345, placementId: 67890 } })).to.equal(true); + expect(spec.isBidRequestValid({ params: { hbid: 12345, placementId: 67890, floorPrice: 0.8 } })).to.equal(true); + }); + + it('Verify imonomyAdapter build request', function () { + var startTime = new Date().getTime(); + + const request = spec.buildRequests(bidsRequest); + expect(request.url).to.equal('//b.imonomy.com/openrtb/hb/14567718624'); + expect(request.method).to.equal('POST'); + const requestData = JSON.parse(request.data); + + // bids object + let bids = requestData.bids; + expect(bids).to.have.lengthOf(2); + + // first bid request: no floor price + expect(bids[0].uuid).to.equal('2faedf1095f815'); + expect(bids[0].floorprice).to.be.undefined; + expect(bids[0].placementid).to.equal('170577'); + expect(bids[0].hbid).to.equal('14567718624'); + expect(bids[0].trid).to.equal('9f801c02-bbe8-4683-8ed4-bc816ea186bb'); + expect(bids[0].sizes).to.have.lengthOf(1); + expect(bids[0].sizes[0][0]).to.equal(300); + expect(bids[0].sizes[0][1]).to.equal(250); + + // second bid request: with floor price + expect(bids[1].uuid).to.equal('3c34e2367a3f59'); + expect(bids[1].floorprice).to.equal(0.5); + expect(bids[1].placementid).to.equal('281277'); + expect(bids[1].hbid).to.equal('14567718624'); + expect(bids[1].trid).to.equal('9f801c02-bbe8-4683-8ed4-bc816ea186bb'); + expect(bids[1]).to.have.property('sizes') + .that.is.an('array') + .of.length(1) + .that.deep.equals([[728, 90]]); + + // kbConf object + let kbConf = requestData.kbConf; + expect(kbConf.hdbdid).to.equal(bids[0].hbid); + expect(kbConf.hdbdid).to.equal(bids[1].hbid); + expect(kbConf.encode_bid).to.be.undefined; + // kbConf timezone and cb + expect(kbConf.cb).not.to.be.undefined; + expect(kbConf.ts_as).to.be.above(startTime - 1); + expect(kbConf.tz).to.equal(new Date().getTimezoneOffset()); + // kbConf bid ids + expect(kbConf.hb_placement_bidids) + .to.have.property(bids[0].placementid) + .that.equal(bids[0].uuid); + expect(kbConf.hb_placement_bidids) + .to.have.property(bids[1].placementid) + .that.equal(bids[1].uuid); + // kbConf floor price + expect(kbConf.hb_floors).not.to.have.property(bids[0].placementid) + expect(kbConf.hb_floors).to.have.property(bids[1].placementid).that.equal(bids[1].floorprice); + // kbConf placement ids + expect(kbConf.hb_placements).to.have.lengthOf(2); + expect(kbConf.hb_placements[0]).to.equal(bids[0].placementid); + expect(kbConf.hb_placements[1]).to.equal(bids[1].placementid); + }); + + it('Verify imonomyAdapter build response', function () { + const request = spec.buildRequests(bidsRequest); + const bids = spec.interpretResponse(bidsResponse, request); + + // 'server' return single bid + expect(bids).to.have.lengthOf(1); + + // verify bid object + const bid = bids[0]; + const responseBids = bidsResponse.body.bids; + + expect(bid.cpm).to.equal(responseBids[0].cpm); + expect(bid.ad).to.equal(responseBids[0].creative); + expect(bid.requestId).equal(responseBids[0].uuid); + expect(bid.uuid).equal(responseBids[0].uuid); + expect(bid.width).to.equal(responseBids[0].width); + expect(bid.height).to.equal(responseBids[0].height); + expect(bid.ttl).to.equal(responseBids[0].ttl); + expect(bid.currency).to.equal('USD'); + expect(bid.netRevenue).to.equal(true); + expect(bid.creativeId).to.equal(responseBids[0].creativeId); + }); + + it('Verifies imonomyAdapter sync options', function () { + // user sync disabled + expect(spec.getUserSyncs({})).to.be.undefined; + expect(spec.getUserSyncs({ iframeEnabled: false })).to.be.undefined; + // user sync enabled + const options = spec.getUserSyncs({ iframeEnabled: true }); + expect(options).to.not.be.undefined; + expect(options).to.have.lengthOf(1); + expect(options[0].type).to.equal('iframe'); + expect(options[0].url).to.equal('//b.imonomy.com/UserMatching/b/'); + }); +}); diff --git a/test/spec/modules/improvedigitalBidAdapter_spec.js b/test/spec/modules/improvedigitalBidAdapter_spec.js index bab469b936e..2d9d51c0d68 100644 --- a/test/spec/modules/improvedigitalBidAdapter_spec.js +++ b/test/spec/modules/improvedigitalBidAdapter_spec.js @@ -7,7 +7,7 @@ describe('Improve Digital Adapter Tests', function () { let idClient = new ImproveDigitalAdServerJSClient('hb'); const METHOD = 'GET'; - const URL = '//ad.360yield.com/hb'; + const URL = 'https://ice.360yield.com/hb'; const PARAM_PREFIX = 'jsonp='; const simpleBidRequest = { @@ -19,7 +19,8 @@ describe('Improve Digital Adapter Tests', function () { transactionId: 'f183e871-fbed-45f0-a427-c8a63c4c01eb', bidId: '33e9500b21129f', bidderRequestId: '2772c1e566670b', - auctionId: '192721e36a0239' + auctionId: '192721e36a0239', + sizes: [[300, 250], [160, 600], ['blah', 150], [-1, 300], [300, -5]] }; const simpleSmartTagBidRequest = { @@ -32,11 +33,17 @@ describe('Improve Digital Adapter Tests', function () { } }; - const bidderRequest = { - 'gdprConsent': { - 'consentString': 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==', - 'vendorData': {}, - 'gdprApplies': true + const bidderRequestGdpr = { + gdprConsent: { + consentString: 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==', + vendorData: {}, + gdprApplies: true + }, + }; + + const bidderRequestReferrer = { + refererInfo: { + referer: 'https://blah.com/test.html', }, }; @@ -92,7 +99,7 @@ describe('Improve Digital Adapter Tests', function () { expect(request.url).to.equal(URL); expect(request.data.substring(0, PARAM_PREFIX.length)).to.equal(PARAM_PREFIX); - const params = JSON.parse(request.data.substring(PARAM_PREFIX.length)); + const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); expect(params.bid_request).to.be.an('object'); expect(params.bid_request.id).to.be.a('string'); expect(params.bid_request.version).to.equal(`${spec.version}-${idClient.CONSTANTS.CLIENT_VERSION}`); @@ -108,7 +115,7 @@ describe('Improve Digital Adapter Tests', function () { it('should set placementKey and publisherId for smart tags', function () { const requests = spec.buildRequests([simpleSmartTagBidRequest]); - const params = JSON.parse(requests[0].data.substring(PARAM_PREFIX.length)); + const params = JSON.parse(decodeURIComponent(requests[0].data.substring(PARAM_PREFIX.length))); expect(params.bid_request.imp[0].pubid).to.equal(1032); expect(params.bid_request.imp[0].pkey).to.equal('data_team_test_hb_smoke_test'); }); @@ -122,11 +129,11 @@ describe('Improve Digital Adapter Tests', function () { }; bidRequest.params.keyValues = keyValues; const request = spec.buildRequests([bidRequest])[0]; - const params = JSON.parse(request.data.substring(PARAM_PREFIX.length)); + const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); expect(params.bid_request.imp[0].kvw).to.deep.equal(keyValues); }); - it('should add size', function () { + it('should add single size filter', function () { let bidRequest = Object.assign({}, simpleBidRequest); const size = { w: 800, @@ -134,26 +141,99 @@ describe('Improve Digital Adapter Tests', function () { }; bidRequest.params.size = size; const request = spec.buildRequests([bidRequest])[0]; - const params = JSON.parse(request.data.substring(PARAM_PREFIX.length)); + const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); expect(params.bid_request.imp[0].banner).to.deep.equal(size); + // When single size filter is set, format shouldn't be populated. This + // is to maintain backward compatibily + expect(params.bid_request.imp[0].banner.format).to.not.exist; }); it('should add currency', function () { const bidRequest = Object.assign({}, simpleBidRequest); const getConfigStub = sinon.stub(config, 'getConfig').returns('JPY'); const request = spec.buildRequests([bidRequest])[0]; - const params = JSON.parse(request.data.substring(PARAM_PREFIX.length)); + const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); expect(params.bid_request.imp[0].currency).to.equal('JPY'); getConfigStub.restore(); }); + it('should add bid floor', function () { + const bidRequest = Object.assign({}, simpleBidRequest); + let request = spec.buildRequests([bidRequest])[0]; + let params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); + // Floor price currency shouldn't be populated without a floor price + expect(params.bid_request.imp[0].bidfloorcur).to.not.exist; + + // Default floor price currency + bidRequest.params.bidFloor = 0.05; + request = spec.buildRequests([bidRequest])[0]; + params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); + expect(params.bid_request.imp[0].bidfloor).to.equal(0.05); + expect(params.bid_request.imp[0].bidfloorcur).to.equal('USD'); + + // Floor price currency + bidRequest.params.bidFloorCur = 'eUR'; + request = spec.buildRequests([bidRequest])[0]; + params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); + expect(params.bid_request.imp[0].bidfloor).to.equal(0.05); + expect(params.bid_request.imp[0].bidfloorcur).to.equal('EUR'); + }); + it('should add GDPR consent string', function () { const bidRequest = Object.assign({}, simpleBidRequest); - const request = spec.buildRequests([bidRequest], bidderRequest)[0]; - const params = JSON.parse(request.data.substring(PARAM_PREFIX.length)); + const request = spec.buildRequests([bidRequest], bidderRequestGdpr)[0]; + const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); expect(params.bid_request.gdpr).to.equal('BOJ/P2HOJ/P2HABABMAAAAAZ+A=='); }); + it('should add referrer', function () { + const bidRequest = Object.assign({}, simpleBidRequest); + const request = spec.buildRequests([bidRequest], bidderRequestReferrer)[0]; + const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); + expect(params.bid_request.referrer).to.equal('https://blah.com/test.html'); + }); + + it('should add ad type for instream video', function () { + let bidRequest = Object.assign({}, simpleBidRequest); + bidRequest.mediaType = 'video'; + let request = spec.buildRequests([bidRequest])[0]; + let params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); + expect(params.bid_request.imp[0].ad_types).to.deep.equal(['video']); + + bidRequest = Object.assign({}, simpleBidRequest); + bidRequest.mediaTypes = { + video: { + context: 'instream', + playerSize: [640, 480] + } + }; + request = spec.buildRequests([bidRequest])[0]; + params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); + expect(params.bid_request.imp[0].ad_types).to.deep.equal(['video']); + }); + + it('should not set ad type for outstream video', function() { + const bidRequest = Object.assign({}, simpleBidRequest); + bidRequest.mediaTypes = { + video: { + context: 'outstream', + playerSize: [640, 480] + } + }; + const request = spec.buildRequests([bidRequest])[0]; + const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); + expect(params.bid_request.imp[0].ad_types).to.not.exist; + }); + + it('should add schain', function () { + const schain = '{"ver":"1.0","complete":1,"nodes":[{"asi":"headerlift.com","sid":"xyz","hp":1}]}'; + const bidRequest = Object.assign({}, simpleBidRequest); + bidRequest.schain = schain; + const request = spec.buildRequests([bidRequest], bidderRequestReferrer)[0]; + const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); + expect(params.bid_request.schain).to.equal(schain); + }); + it('should return 2 requests', function () { const requests = spec.buildRequests([ simpleBidRequest, @@ -162,6 +242,52 @@ describe('Improve Digital Adapter Tests', function () { expect(requests).to.be.an('array'); expect(requests.length).to.equal(2); }); + + it('should return one request in a single request mode', function () { + const getConfigStub = sinon.stub(config, 'getConfig'); + getConfigStub.withArgs('improvedigital.singleRequest').returns(true); + const requests = spec.buildRequests([ + simpleBidRequest, + simpleSmartTagBidRequest + ]); + expect(requests).to.be.an('array'); + expect(requests.length).to.equal(1); + getConfigStub.restore(); + }); + + it('should set Prebid sizes in bid request', function () { + const getConfigStub = sinon.stub(config, 'getConfig'); + getConfigStub.withArgs('improvedigital.usePrebidSizes').returns(true); + const request = spec.buildRequests([simpleBidRequest])[0]; + const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); + expect(params.bid_request.imp[0].banner).to.deep.equal({ + format: [ + { w: 300, h: 250 }, + { w: 160, h: 600 } + ] + }); + getConfigStub.restore(); + }); + + it('should not add single size filter when using Prebid sizes', function () { + const getConfigStub = sinon.stub(config, 'getConfig'); + getConfigStub.withArgs('improvedigital.usePrebidSizes').returns(true); + const bidRequest = Object.assign({}, simpleBidRequest); + const size = { + w: 800, + h: 600 + }; + bidRequest.params.size = size; + const request = spec.buildRequests([bidRequest])[0]; + const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); + expect(params.bid_request.imp[0].banner).to.deep.equal({ + format: [ + { w: 300, h: 250 }, + { w: 160, h: 600 } + ] + }); + getConfigStub.restore(); + }); }); const serverResponse = { @@ -174,7 +300,7 @@ describe('Improve Digital Adapter Tests', function () { 'id': '33e9500b21129f', 'advid': '5279', 'price': 1.45888594164456, - 'nurl': 'http://ad.360yield.com/imp_pixel?ic=wVmhKI07hCVyGC1sNdFp.6buOSiGYOw8jPyZLlcMY2RCwD4ek3Fy6.xUI7U002skGBs3objMBoNU-Frpvmb9js3NKIG0YZJgWaNdcpXY9gOXE9hY4-wxybCjVSNzhOQB-zic73hzcnJnKeoGgcfvt8fMy18-yD0aVdYWt4zbqdoITOkKNCPBEgbPFu1rcje-o7a64yZ7H3dKvtnIixXQYc1Ep86xGSBGXY6xW2KfUOMT6vnkemxO72divMkMdhR8cAuqIubbx-ZID8-xf5c9k7p6DseeBW0I8ionrlTHx.rGosgxhiFaMqtr7HiA7PBzKvPdeEYN0hQ8RYo8JzYL82hA91A3V2m9Ij6y0DfIJnnrKN8YORffhxmJ6DzwEl1zjrVFbD01bqB3Vdww8w8PQJSkKQkd313tr-atU8LS26fnBmOngEkVHwAr2WCKxuUvxHmuVBTA-Lgz7wKwMoOJCA3hFxMavVb0ZFB7CK0BUTVU6z0De92Q.FJKNCHLMbjX3vcAQ90=', + 'nurl': 'http://ice.360yield.com/imp_pixel?ic=wVmhKI07hCVyGC1sNdFp.6buOSiGYOw8jPyZLlcMY2RCwD4ek3Fy6.xUI7U002skGBs3objMBoNU-Frpvmb9js3NKIG0YZJgWaNdcpXY9gOXE9hY4-wxybCjVSNzhOQB-zic73hzcnJnKeoGgcfvt8fMy18-yD0aVdYWt4zbqdoITOkKNCPBEgbPFu1rcje-o7a64yZ7H3dKvtnIixXQYc1Ep86xGSBGXY6xW2KfUOMT6vnkemxO72divMkMdhR8cAuqIubbx-ZID8-xf5c9k7p6DseeBW0I8ionrlTHx.rGosgxhiFaMqtr7HiA7PBzKvPdeEYN0hQ8RYo8JzYL82hA91A3V2m9Ij6y0DfIJnnrKN8YORffhxmJ6DzwEl1zjrVFbD01bqB3Vdww8w8PQJSkKQkd313tr-atU8LS26fnBmOngEkVHwAr2WCKxuUvxHmuVBTA-Lgz7wKwMoOJCA3hFxMavVb0ZFB7CK0BUTVU6z0De92Q.FJKNCHLMbjX3vcAQ90=', 'h': 290, 'pid': 1053688, 'sync': [ @@ -184,7 +310,7 @@ describe('Improve Digital Adapter Tests', function () { 'crid': '422031', 'w': 600, 'cid': '99006', - 'adm': 'document.writeln(\"\\\"\\\"\\/<\\/a>\");document.writeln(\"<\\/improvedigital_ad_output_information>\");' + 'adm': 'document.writeln(\"\\\"\\\"\\/<\\/a>\");document.writeln(\"<\\/improvedigital_ad_output_information>\");' } ], 'debug': '' @@ -211,22 +337,208 @@ describe('Improve Digital Adapter Tests', function () { 'crid': '422033', 'w': 700, 'cid': '99006', - 'adm': 'document.writeln(\"\\\"\\\"\\/<\\/a>\");document.writeln(\"<\\/improvedigital_ad_output_information>\");' + 'adm': 'document.writeln(\"\\\"\\\"\\/<\\/a>\");document.writeln(\"<\\/improvedigital_ad_output_information>\");' + } + ], + 'debug': '' + } + }; + + const serverResponseNative = { + body: { + id: '687a06c541d8d1', + site_id: 191642, + bid: [ + { + isNet: false, + id: '33e9500b21129f', + advid: '5279', + price: 1.45888594164456, + nurl: 'http://ice.360yield.com/imp_pixel?ic=wVm', + h: 290, + pid: 1053688, + sync: [ + 'http://link1', + 'http://link2' + ], + crid: '422031', + w: 600, + cid: '99006', + native: { + assets: [ + { + title: { + text: 'Native title' + } + }, + { + data: { + type: 1, + value: 'Improve Digital' + } + }, + { + data: { + type: 2, + value: 'Native body' + } + }, + { + data: { + type: 3, + value: '4' // rating + } + }, + { + data: { + type: 4, + value: '10105' // likes + } + }, + { + data: { + type: 5, + value: '150000' // downloads + } + }, + { + data: { + type: 6, + value: '3.99' // price + } + }, + { + data: { + type: 7, + value: '4.49' // salePrice + } + }, + { + data: { + type: 8, + value: '(123) 456-7890' // phone + } + }, + { + data: { + type: 9, + value: '123 Main Street, Anywhere USA' // address + } + }, + { + data: { + type: 10, + value: 'body2' + } + }, + { + data: { + type: 11, + value: 'https://myurl.com' // displayUrl + } + }, + { + data: { + type: 12, + value: 'Do it' // cta + } + }, + { + img: { + type: 1, + url: 'Should get ignored', + h: 300, + w: 400 + } + }, + { + img: { + type: 2, + url: 'http://blah.com/icon.jpg', + h: 30, + w: 40 + } + + }, + { + img: { + type: 3, + url: 'http://blah.com/image.jpg', + h: 200, + w: 800 + } + } + ], + link: { + url: 'http://advertiser.com', + clicktrackers: [ + 'http://click.tracker.com/click?impid=123' + ] + }, + imptrackers: [ + 'http://imptrack1.com', + 'http://imptrack2.com' + ], + jstracker: '', + privacy: 'https://www.myprivacyurl.com' + } + } + ], + debug: '' + } + }; + + const serverResponseVideo = { + 'body': { + 'id': '687a06c541d8d1', + 'site_id': 191642, + 'bid': [ + { + 'isNet': false, + 'id': '33e9500b21129f', + 'advid': '5279', + 'price': 1.45888594164456, + 'nurl': 'http://ice.360yield.com/imp_pixel?ic=wVmhKI07hCVyGC1sNdFp.6buOSiGYOw8jPyZLlcMY2RCwD4ek3Fy6.xUI7U002skGBs3objMBoNU-Frpvmb9js3NKIG0YZJgWaNdcpXY9gOXE9hY4-wxybCjVSNzhOQB-zic73hzcnJnKeoGgcfvt8fMy18-yD0aVdYWt4zbqdoITOkKNCPBEgbPFu1rcje-o7a64yZ7H3dKvtnIixXQYc1Ep86xGSBGXY6xW2KfUOMT6vnkemxO72divMkMdhR8cAuqIubbx-ZID8-xf5c9k7p6DseeBW0I8ionrlTHx.rGosgxhiFaMqtr7HiA7PBzKvPdeEYN0hQ8RYo8JzYL82hA91A3V2m9Ij6y0DfIJnnrKN8YORffhxmJ6DzwEl1zjrVFbD01bqB3Vdww8w8PQJSkKQkd313tr-atU8LS26fnBmOngEkVHwAr2WCKxuUvxHmuVBTA-Lgz7wKwMoOJCA3hFxMavVb0ZFB7CK0BUTVU6z0De92Q.FJKNCHLMbjX3vcAQ90=', + 'h': 290, + 'pid': 1053688, + 'sync': [ + 'http://link1', + 'http://link2' + ], + 'crid': '422031', + 'w': 600, + 'cid': '99006', + 'adm': '', + 'ad_type': 'video' } ], 'debug': '' } }; + const nativeEventtrackers = [ + { + event: 1, + method: 1, + url: 'http://www.mytracker.com/imptracker' + }, + { + event: 1, + method: 2, + url: 'http://www.mytracker.com/tracker.js' + } + ]; + describe('interpretResponse', function () { let expectedBid = [ { - 'ad': '', + 'ad': '', 'adId': '33e9500b21129f', 'creativeId': '422031', 'cpm': 1.45888594164456, 'currency': 'USD', 'height': 290, + 'mediaType': 'banner', 'netRevenue': false, 'requestId': '33e9500b21129f', 'ttl': 300, @@ -237,12 +549,13 @@ describe('Improve Digital Adapter Tests', function () { let expectedTwoBids = [ expectedBid[0], { - 'ad': '', + 'ad': '', 'adId': '1234', 'creativeId': '422033', 'cpm': 1.23, 'currency': 'USD', 'height': 400, + 'mediaType': 'banner', 'netRevenue': true, 'requestId': '1234', 'ttl': 300, @@ -250,6 +563,71 @@ describe('Improve Digital Adapter Tests', function () { } ]; + let expectedBidNative = [ + { + mediaType: 'native', + adId: '33e9500b21129f', + creativeId: '422031', + cpm: 1.45888594164456, + currency: 'USD', + height: 290, + netRevenue: false, + requestId: '33e9500b21129f', + ttl: 300, + width: 600, + native: { + title: 'Native title', + body: 'Native body', + body2: 'body2', + cta: 'Do it', + sponsoredBy: 'Improve Digital', + rating: '4', + likes: '10105', + downloads: '150000', + price: '3.99', + salePrice: '4.49', + phone: '(123) 456-7890', + address: '123 Main Street, Anywhere USA', + displayUrl: 'https://myurl.com', + icon: { + url: 'http://blah.com/icon.jpg', + height: 30, + width: 40 + }, + image: { + url: 'http://blah.com/image.jpg', + height: 200, + width: 800 + }, + clickUrl: 'http://advertiser.com', + clickTrackers: ['http://click.tracker.com/click?impid=123'], + impressionTrackers: [ + 'http://ice.360yield.com/imp_pixel?ic=wVm', + 'http://imptrack1.com', + 'http://imptrack2.com' + ], + javascriptTrackers: '', + privacyLink: 'https://www.myprivacyurl.com' + } + } + ]; + + let expectedBidVideo = [ + { + 'vastXml': '', + 'adId': '33e9500b21129f', + 'creativeId': '422031', + 'cpm': 1.45888594164456, + 'currency': 'USD', + 'height': 290, + 'mediaType': 'video', + 'netRevenue': false, + 'requestId': '33e9500b21129f', + 'ttl': 300, + 'width': 600 + } + ]; + it('should return a well-formed bid', function () { const bids = spec.interpretResponse(serverResponse); expect(bids).to.deep.equal(expectedBid); @@ -264,19 +642,40 @@ describe('Improve Digital Adapter Tests', function () { let response = JSON.parse(JSON.stringify(serverResponse)); let bids; - response.body.bid[0].lid = 'xyz'; + delete response.body.bid[0].lid; + response.body.bid[0].buying_type = 'deal_id'; bids = spec.interpretResponse(response); expect(bids[0].dealId).to.not.exist; response.body.bid[0].lid = 268515; + delete response.body.bid[0].buying_type; bids = spec.interpretResponse(response); - expect(bids[0].dealId).to.equal(268515); + expect(bids[0].dealId).to.not.exist; - response.body.bid[0].lid = { - 1: 268515 - }; + response.body.bid[0].lid = 268515; + response.body.bid[0].buying_type = 'classic'; + bids = spec.interpretResponse(response); + expect(bids[0].dealId).to.not.exist; + + response.body.bid[0].lid = 268515; + response.body.bid[0].buying_type = 'deal_id'; bids = spec.interpretResponse(response); expect(bids[0].dealId).to.equal(268515); + + response.body.bid[0].lid = [ 268515, 12456, 34567 ]; + response.body.bid[0].buying_type = 'deal_id'; + bids = spec.interpretResponse(response); + expect(bids[0].dealId).to.not.exist; + + response.body.bid[0].lid = [ 268515, 12456, 34567 ]; + response.body.bid[0].buying_type = [ 'deal_id', 'classic' ]; + bids = spec.interpretResponse(response); + expect(bids[0].dealId).to.not.exist; + + response.body.bid[0].lid = [ 268515, 12456, 34567 ]; + response.body.bid[0].buying_type = [ 'classic', 'deal_id', 'deal_id' ]; + bids = spec.interpretResponse(response); + expect(bids[0].dealId).to.equal(12456); }); it('should set currency', function () { @@ -307,7 +706,7 @@ describe('Improve Digital Adapter Tests', function () { bids = spec.interpretResponse(response); expect(bids).to.deep.equal([]); - // Adm missing or bad + // adm and native missing response = JSON.parse(JSON.stringify(serverResponse)); delete response.body.bid[0].adm; bids = spec.interpretResponse(response); @@ -315,12 +714,6 @@ describe('Improve Digital Adapter Tests', function () { response.body.bid[0].adm = null; bids = spec.interpretResponse(response); expect(bids).to.deep.equal([]); - response.body.bid[0].adm = 1234; - bids = spec.interpretResponse(response); - expect(bids).to.deep.equal([]); - response.body.bid[0].adm = {}; - bids = spec.interpretResponse(response); - expect(bids).to.deep.equal([]); }); it('should set netRevenue', function () { @@ -329,6 +722,33 @@ describe('Improve Digital Adapter Tests', function () { const bids = spec.interpretResponse(response); expect(bids[0].netRevenue).to.equal(true); }); + + // Native ads + it('should return a well-formed native ad bid', function () { + let bids = spec.interpretResponse(serverResponseNative); + expect(bids[0].ortbNative).to.deep.equal(serverResponseNative.body.bid[0].native); + delete bids[0].ortbNative; + expect(bids).to.deep.equal(expectedBidNative); + + // eventtrackers + const response = JSON.parse(JSON.stringify(serverResponseNative)); + const expectedBids = JSON.parse(JSON.stringify(expectedBidNative)); + response.body.bid[0].native.eventtrackers = nativeEventtrackers; + expectedBids[0].native.impressionTrackers = [ + 'http://ice.360yield.com/imp_pixel?ic=wVm', + 'http://www.mytracker.com/imptracker' + ]; + expectedBids[0].native.javascriptTrackers = ''; + bids = spec.interpretResponse(response); + delete bids[0].ortbNative; + expect(bids).to.deep.equal(expectedBids); + }); + + // Video + it('should return a well-formed video bid', function () { + const bids = spec.interpretResponse(serverResponseVideo); + expect(bids).to.deep.equal(expectedBidVideo); + }); }); describe('getUserSyncs', function () { diff --git a/test/spec/modules/inskinBidAdapter_spec.js b/test/spec/modules/inskinBidAdapter_spec.js index 24ae9321954..896fe36d443 100644 --- a/test/spec/modules/inskinBidAdapter_spec.js +++ b/test/spec/modules/inskinBidAdapter_spec.js @@ -1,7 +1,6 @@ import { expect } from 'chai'; import { spec } from 'modules/inskinBidAdapter'; - -var bidFactory = require('src/bidfactory.js'); +import { createBid } from 'src/bidfactory'; const ENDPOINT = 'https://mfad.inskinad.com/api/v2'; @@ -83,6 +82,9 @@ const RESPONSE = { 'type': 'html', 'body': '', 'data': { + 'customData': { + 'pubCPM': 1 + }, 'height': 90, 'width': 728, 'imageUrl': 'https://static.adzerk.net/Advertisers/b0ab77db8a7848c8b78931aed022a5ef.gif', @@ -213,7 +215,7 @@ describe('InSkin BidAdapter', function () { describe('interpretResponse validation', function () { it('response should have valid bidderCode', function () { let bidRequest = spec.buildRequests(REQUEST.bidRequest); - let bid = bidFactory.createBid(1, bidRequest.bidRequest[0]); + let bid = createBid(1, bidRequest.bidRequest[0]); expect(bid.bidderCode).to.equal('inskin'); }); @@ -242,6 +244,13 @@ describe('InSkin BidAdapter', function () { }); }); + it('cpm is correctly set', function () { + let bids = spec.interpretResponse(RESPONSE, REQUEST); + + expect(bids[0].cpm).to.equal(0.5); + expect(bids[1].cpm).to.equal(1); + }); + it('handles nobid responses', function () { let EMPTY_RESP = Object.assign({}, RESPONSE, {'body': {'decisions': null}}) let bids = spec.interpretResponse(EMPTY_RESP, REQUEST); diff --git a/test/spec/modules/invibesBidAdapter_spec.js b/test/spec/modules/invibesBidAdapter_spec.js index d0fa627929e..6391f168599 100644 --- a/test/spec/modules/invibesBidAdapter_spec.js +++ b/test/spec/modules/invibesBidAdapter_spec.js @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { spec, resetInvibes } from 'modules/invibesBidAdapter'; +import { spec, resetInvibes, stubDomainOptions } from 'modules/invibesBidAdapter'; describe('invibesBidAdapter:', function () { const BIDDER_CODE = 'invibes'; @@ -40,6 +40,21 @@ describe('invibesBidAdapter:', function () { } ]; + let StubbedPersistence = function(initialValue) { + var value = initialValue; + return { + load: function () { + let str = value || ''; + try { + return JSON.parse(str); + } catch (e) { } + }, + save: function (obj) { + value = JSON.stringify(obj); + } + } + }; + beforeEach(function () { resetInvibes(); document.cookie = ''; @@ -83,6 +98,18 @@ describe('invibesBidAdapter:', function () { expect(spec.isBidRequestValid(invalidBid)).to.be.false; }); + + it('returns false when bid response was previously received', function() { + const validBid = { + bidder: BIDDER_CODE, + params: { + placementId: PLACEMENT_ID + } + } + + top.window.invibes.bidResponse = { prop: 'prop' }; + expect(spec.isBidRequestValid(validBid)).to.be.false; + }); }); }); @@ -126,38 +153,52 @@ describe('invibesBidAdapter:', function () { expect(request.data.lId).to.not.exist; }); + it('try to graduate but not enough count - doesnt send the domain id', function () { + global.document.cookie = 'ivbsdid={"id":"dvdjkams6nkq","cr":1521818537626,"hc":5}'; + let bidderRequest = { gdprConsent: { vendorData: { vendorConsents: { 436: true } } } }; + let request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.lId).to.not.exist; + }); + + it('try to graduate but not old enough - doesnt send the domain id', function () { + let bidderRequest = { gdprConsent: { vendorData: { vendorConsents: { 436: true } } } }; + global.document.cookie = 'ivbsdid={"id":"dvdjkams6nkq","cr":' + Date.now() + ',"hc":5}'; + let request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.lId).to.not.exist; + }); + it('graduate and send the domain id', function () { - top.window.invibes.optIn = 1; - global.document.cookie = 'ivbsdid={"id":"dvdjkams6nkq","cr":1521818537626,"hc":7}'; - let request = spec.buildRequests(bidRequests); + let bidderRequest = { gdprConsent: { vendorData: { vendorConsents: { 436: true } } } }; + stubDomainOptions(new StubbedPersistence('{"id":"dvdjkams6nkq","cr":1521818537626,"hc":7}')); + let request = spec.buildRequests(bidRequests, bidderRequest); expect(request.data.lId).to.exist; }); it('send the domain id if already graduated', function () { - top.window.invibes.optIn = 1; - global.document.cookie = 'ivbsdid={"id":"f8zoh044p9oi"}'; - let request = spec.buildRequests(bidRequests); + let bidderRequest = { gdprConsent: { vendorData: { vendorConsents: { 436: true } } } }; + stubDomainOptions(new StubbedPersistence('{"id":"f8zoh044p9oi"}')); + let request = spec.buildRequests(bidRequests, bidderRequest); expect(request.data.lId).to.exist; }); it('send the domain id after replacing it with new format', function () { - top.window.invibes.optIn = 1; - global.document.cookie = 'ivbsdid={"id":"f8zoh044p9oi.8537626"}'; - let request = spec.buildRequests(bidRequests); + let bidderRequest = { gdprConsent: { vendorData: { vendorConsents: { 436: true } } } }; + stubDomainOptions(new StubbedPersistence('{"id":"f8zoh044p9oi.8537626"}')); + let request = spec.buildRequests(bidRequests, bidderRequest); expect(request.data.lId).to.exist; }); - it('try to graduate but not enough count - doesnt send the domain id', function () { - top.window.invibes.optIn = 1; - global.document.cookie = 'ivbsdid={"id":"dvdjkams6nkq","cr":1521818537626,"hc":5}'; - let request = spec.buildRequests(bidRequests); + it('dont send the domain id if consent declined', function () { + let bidderRequest = { gdprConsent: { vendorData: { vendorConsents: { 436: false } } } }; + stubDomainOptions(new StubbedPersistence('{"id":"f8zoh044p9oi.8537626"}')); + let request = spec.buildRequests(bidRequests, bidderRequest); expect(request.data.lId).to.not.exist; }); - it('try to graduate but not old enough - doesnt send the domain id', function () { - top.window.invibes.optIn = 1; - global.document.cookie = 'ivbsdid={"id":"dvdjkams6nkq","cr":' + Date.now() + ',"hc":5}'; - let request = spec.buildRequests(bidRequests); + it('dont send the domain id if no consent', function () { + let bidderRequest = { }; + stubDomainOptions(new StubbedPersistence('{"id":"f8zoh044p9oi.8537626"}')); + let request = spec.buildRequests(bidRequests, bidderRequest); expect(request.data.lId).to.not.exist; }); }); diff --git a/test/spec/modules/ixBidAdapter_spec.js b/test/spec/modules/ixBidAdapter_spec.js index 8e0df9959ef..120d02408d7 100644 --- a/test/spec/modules/ixBidAdapter_spec.js +++ b/test/spec/modules/ixBidAdapter_spec.js @@ -5,7 +5,7 @@ import { newBidder } from 'src/adapters/bidderFactory'; import { spec } from 'modules/ixBidAdapter'; describe('IndexexchangeAdapter', function () { - const IX_ENDPOINT = 'http://as.casalemedia.com/cygnus'; + const IX_SECURE_ENDPOINT = 'https://as-sec.casalemedia.com/cygnus'; const BIDDER_VERSION = 7.2; const DEFAULT_BANNER_VALID_BID = [ @@ -28,6 +28,17 @@ describe('IndexexchangeAdapter', function () { auctionId: '1aa2bb3cc4dd' } ]; + const DEFAULT_BANNER_OPTION = { + gdprConsent: { + gdprApplies: true, + consentString: '3huaa11=qu3198ae', + vendorData: {} + }, + refererInfo: { + referer: 'http://www.prebid.org', + canonicalUrl: 'http://www.prebid.org/the/link/to/the/page' + } + }; const DEFAULT_BANNER_BID_RESPONSE = { cur: 'USD', id: '11a22b33c44d', @@ -57,6 +68,19 @@ describe('IndexexchangeAdapter', function () { } ] }; + const DEFAULT_IDENTITY_RESPONSE = { + IdentityIp: { + responsePending: false, + data: { + source: 'identityinc.com', + uids: [ + { + id: 'identityid' + } + ] + } + } + }; describe('inherited functions', function () { it('should exists and is a function', function () { @@ -197,8 +221,173 @@ describe('IndexexchangeAdapter', function () { }); }); + describe('buildRequestsIdentity', function () { + let request; + let query; + let testCopy; + + beforeEach(function() { + window.headertag = {}; + window.headertag.getIdentityInfo = function() { + return testCopy; + }; + request = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_BANNER_OPTION); + query = request.data; + }); + afterEach(function() { + delete window.headertag; + }); + describe('buildRequestSingleRTI', function() { + before(function() { + testCopy = JSON.parse(JSON.stringify(DEFAULT_IDENTITY_RESPONSE)); + }); + it('payload should have correct format and value (single identity partner)', function () { + const payload = JSON.parse(query.r); + + expect(payload.user).to.exist; + expect(payload.user.eids).to.exist; + expect(payload.user.eids).to.be.an('array'); + expect(payload.user.eids).to.have.lengthOf(1); + }); + + it('identity data in impression should have correct format and value (single identity partner)', function () { + const impression = JSON.parse(query.r).user.eids; + expect(impression[0].source).to.equal(testCopy.IdentityIp.data.source); + expect(impression[0].uids[0].id).to.equal(testCopy.IdentityIp.data.uids[0].id); + }); + }); + + describe('buildRequestMultipleIds', function() { + before(function() { + testCopy = JSON.parse(JSON.stringify(DEFAULT_IDENTITY_RESPONSE)); + testCopy.IdentityIp.data.uids.push({ + id: '1234567' + }, + { + id: '2019-04-01TF2:34:41' + }); + }); + it('payload should have correct format and value (single identity w/ multi ids)', function () { + const payload = JSON.parse(query.r); + + expect(payload.user).to.exist; + expect(payload.user.eids).to.exist; + expect(payload.user.eids).to.be.an('array'); + expect(payload.user.eids).to.have.lengthOf(1); + }); + + it('identity data in impression should have correct format and value (single identity w/ multi ids)', function () { + const impression = JSON.parse(query.r).user.eids; + + expect(impression[0].source).to.equal(testCopy.IdentityIp.data.source); + expect(impression[0].uids).to.have.lengthOf(3); + }); + }); + + describe('buildRequestMultipleRTI', function() { + before(function() { + testCopy = JSON.parse(JSON.stringify(DEFAULT_IDENTITY_RESPONSE)); + testCopy.JackIp = { + responsePending: false, + data: { + source: 'jackinc.com', + uids: [ + { + id: 'jackid' + } + ] + } + } + testCopy.GenericIp = { + responsePending: false, + data: { + source: 'genericip.com', + uids: [ + { + id: 'genericipenvelope' + } + ] + } + } + }); + it('payload should have correct format and value (multiple identity partners)', function () { + const payload = JSON.parse(query.r); + + expect(payload.user).to.exist; + expect(payload.user.eids).to.exist; + expect(payload.user.eids).to.be.an('array'); + expect(payload.user.eids).to.have.lengthOf(3); + }); + + it('identity data in impression should have correct format and value (multiple identity partners)', function () { + const impression = JSON.parse(query.r).user.eids; + + expect(impression[0].source).to.equal(testCopy.IdentityIp.data.source); + expect(impression[0].uids).to.have.lengthOf(1); + + expect(impression[1].source).to.equal(testCopy.JackIp.data.source); + expect(impression[1].uids).to.have.lengthOf(1); + + expect(impression[2].source).to.equal(testCopy.GenericIp.data.source); + expect(impression[2].uids).to.have.lengthOf(1); + }); + }); + + describe('buildRequestNoData', function() { + beforeEach(function() { + testCopy = JSON.parse(JSON.stringify(DEFAULT_IDENTITY_RESPONSE)); + }); + + it('payload should not have any user eids with an undefined identity data response', function () { + window.headertag.getIdentityInfo = function() { + return undefined; + }; + request = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_BANNER_OPTION); + query = request.data; + const payload = JSON.parse(query.r); + + expect(payload.user).to.exist; + expect(payload.user.eids).to.not.exist; + }); + + it('payload should have user eids if least one partner data is available', function () { + testCopy.GenericIp = { + responsePending: true, + data: {} + } + request = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_BANNER_OPTION); + query = request.data; + const payload = JSON.parse(query.r); + + expect(payload.user).to.exist; + expect(payload.user.eids).to.exist; + }); + + it('payload should not have any user eids if identity data is pending for all partners', function () { + testCopy.IdentityIp.responsePending = true; + request = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_BANNER_OPTION); + query = request.data; + const payload = JSON.parse(query.r); + + expect(payload.user).to.exist; + expect(payload.user.eids).to.not.exist; + }); + + it('payload should not have any user eids if identity data is pending or not available for all partners', function () { + testCopy.IdentityIp.responsePending = false; + testCopy.IdentityIp.data = {}; + request = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_BANNER_OPTION); + query = request.data; + const payload = JSON.parse(query.r); + + expect(payload.user).to.exist; + expect(payload.user.eids).to.not.exist; + }); + }); + }); + describe('buildRequestsBanner', function () { - const request = spec.buildRequests(DEFAULT_BANNER_VALID_BID); + const request = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_BANNER_OPTION); const requestUrl = request.url; const requestMethod = request.method; const query = request.data; @@ -206,12 +395,12 @@ describe('IndexexchangeAdapter', function () { const bidWithoutMediaType = utils.deepClone(DEFAULT_BANNER_VALID_BID); delete bidWithoutMediaType[0].mediaTypes; bidWithoutMediaType[0].sizes = [[300, 250], [300, 600]]; - const requestWithoutMediaType = spec.buildRequests(bidWithoutMediaType); + const requestWithoutMediaType = spec.buildRequests(bidWithoutMediaType, DEFAULT_BANNER_OPTION); const queryWithoutMediaType = requestWithoutMediaType.data; it('request should be made to IX endpoint with GET method', function () { expect(requestMethod).to.equal('GET'); - expect(requestUrl).to.equal(IX_ENDPOINT); + expect(requestUrl).to.equal(IX_SECURE_ENDPOINT); }); it('query object (version, siteID and request) should be correct', function () { @@ -227,10 +416,8 @@ describe('IndexexchangeAdapter', function () { expect(payload.id).to.equal(DEFAULT_BANNER_VALID_BID[0].bidderRequestId); expect(payload.site).to.exist; - expect(payload.site.page).to.exist; - expect(payload.site.page).to.contain('http'); - expect(payload.site.ref).to.exist; - expect(payload.site.ref).to.be.a('string'); + expect(payload.site.page).to.equal(DEFAULT_BANNER_OPTION.refererInfo.referer); + expect(payload.site.ref).to.equal(document.referrer); expect(payload.ext).to.exist; expect(payload.ext.source).to.equal('prebid'); expect(payload.imp).to.exist; @@ -269,10 +456,8 @@ describe('IndexexchangeAdapter', function () { expect(payload.id).to.equal(DEFAULT_BANNER_VALID_BID[0].bidderRequestId); expect(payload.site).to.exist; - expect(payload.site.page).to.exist; - expect(payload.site.page).to.contain('http'); - expect(payload.site.ref).to.exist; - expect(payload.site.ref).to.be.a('string'); + expect(payload.site.page).to.equal(DEFAULT_BANNER_OPTION.refererInfo.referer); + expect(payload.site.ref).to.equal(document.referrer); expect(payload.ext).to.exist; expect(payload.ext.source).to.equal('prebid'); expect(payload.imp).to.exist; @@ -340,9 +525,9 @@ describe('IndexexchangeAdapter', function () { } }); - const requestWithFirstPartyData = spec.buildRequests(DEFAULT_BANNER_VALID_BID); + const requestWithFirstPartyData = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_BANNER_OPTION); const pageUrl = JSON.parse(requestWithFirstPartyData.data.r).site.page; - const expectedPageUrl = `${utils.getTopWindowUrl()}?ab=123&cd=123%23ab&e%2Ff=456&h%3Fg=456%23cd`; + const expectedPageUrl = DEFAULT_BANNER_OPTION.refererInfo.referer + '?ab=123&cd=123%23ab&e%2Ff=456&h%3Fg=456%23cd'; expect(pageUrl).to.equal(expectedPageUrl); }); @@ -354,10 +539,10 @@ describe('IndexexchangeAdapter', function () { } }); - const requestFirstPartyDataNumber = spec.buildRequests(DEFAULT_BANNER_VALID_BID); + const requestFirstPartyDataNumber = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_BANNER_OPTION); const pageUrl = JSON.parse(requestFirstPartyDataNumber.data.r).site.page; - expect(pageUrl).to.equal(utils.getTopWindowUrl()); + expect(pageUrl).to.equal(DEFAULT_BANNER_OPTION.refererInfo.referer); }); it('should not set first party or timeout if it is not present', function () { @@ -365,18 +550,18 @@ describe('IndexexchangeAdapter', function () { ix: {} }); - const requestWithoutConfig = spec.buildRequests(DEFAULT_BANNER_VALID_BID); + const requestWithoutConfig = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_BANNER_OPTION); const pageUrl = JSON.parse(requestWithoutConfig.data.r).site.page; - expect(pageUrl).to.equal(utils.getTopWindowUrl()); + expect(pageUrl).to.equal(DEFAULT_BANNER_OPTION.refererInfo.referer); expect(requestWithoutConfig.data.t).to.be.undefined; }); it('should not set first party or timeout if it is setConfig is not called', function () { - const requestWithoutConfig = spec.buildRequests(DEFAULT_BANNER_VALID_BID); + const requestWithoutConfig = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_BANNER_OPTION); const pageUrl = JSON.parse(requestWithoutConfig.data.r).site.page; - expect(pageUrl).to.equal(utils.getTopWindowUrl()); + expect(pageUrl).to.equal(DEFAULT_BANNER_OPTION.refererInfo.referer); expect(requestWithoutConfig.data.t).to.be.undefined; }); @@ -416,7 +601,12 @@ describe('IndexexchangeAdapter', function () { currency: 'USD', ttl: 35, netRevenue: true, - dealId: undefined + dealId: undefined, + meta: { + networkId: 50, + brandId: 303325, + brandName: 'OECTA' + } } ]; const result = spec.interpretResponse({ body: DEFAULT_BANNER_BID_RESPONSE }); @@ -437,7 +627,12 @@ describe('IndexexchangeAdapter', function () { currency: 'USD', ttl: 35, netRevenue: true, - dealId: undefined + dealId: undefined, + meta: { + networkId: 50, + brandId: 303325, + brandName: 'OECTA' + } } ]; const result = spec.interpretResponse({ body: bidResponse }); @@ -458,7 +653,12 @@ describe('IndexexchangeAdapter', function () { currency: 'JPY', ttl: 35, netRevenue: true, - dealId: undefined + dealId: undefined, + meta: { + networkId: 50, + brandId: 303325, + brandName: 'OECTA' + } } ]; const result = spec.interpretResponse({ body: bidResponse }); @@ -479,7 +679,12 @@ describe('IndexexchangeAdapter', function () { currency: 'USD', ttl: 35, netRevenue: true, - dealId: 'deal' + dealId: 'deal', + meta: { + networkId: 50, + brandId: 303325, + brandName: 'OECTA' + } } ]; const result = spec.interpretResponse({ body: bidResponse }); @@ -487,14 +692,7 @@ describe('IndexexchangeAdapter', function () { }); it('bidrequest should have consent info if gdprApplies and consentString exist', function () { - const options = { - gdprConsent: { - gdprApplies: true, - consentString: '3huaa11=qu3198ae', - vendorData: {} - } - }; - const validBidWithConsent = spec.buildRequests(DEFAULT_BANNER_VALID_BID, options); + const validBidWithConsent = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_BANNER_OPTION); const requestWithConsent = JSON.parse(validBidWithConsent.data.r); expect(requestWithConsent.regs.ext.gdpr).to.equal(1); @@ -537,5 +735,38 @@ describe('IndexexchangeAdapter', function () { expect(requestWithConsent.regs).to.be.undefined; expect(requestWithConsent.user).to.be.undefined; }); + + it('bidrequest should not have page if options is undefined', function () { + const options = {}; + const validBidWithoutreferInfo = spec.buildRequests(DEFAULT_BANNER_VALID_BID, options); + const requestWithoutreferInfo = JSON.parse(validBidWithoutreferInfo.data.r); + + expect(requestWithoutreferInfo.site.page).to.be.undefined; + expect(validBidWithoutreferInfo.url).to.equal(IX_SECURE_ENDPOINT); + }); + + it('bidrequest should not have page if options.refererInfo is an empty object', function () { + const options = { + refererInfo: {} + }; + const validBidWithoutreferInfo = spec.buildRequests(DEFAULT_BANNER_VALID_BID, options); + const requestWithoutreferInfo = JSON.parse(validBidWithoutreferInfo.data.r); + + expect(requestWithoutreferInfo.site.page).to.be.undefined; + expect(validBidWithoutreferInfo.url).to.equal(IX_SECURE_ENDPOINT); + }); + + it('bidrequest should sent to secure endpoint if page url is secure', function () { + const options = { + refererInfo: { + referer: 'https://www.prebid.org' + } + }; + const validBidWithoutreferInfo = spec.buildRequests(DEFAULT_BANNER_VALID_BID, options); + const requestWithoutreferInfo = JSON.parse(validBidWithoutreferInfo.data.r); + + expect(requestWithoutreferInfo.site.page).to.equal(options.refererInfo.referer); + expect(validBidWithoutreferInfo.url).to.equal(IX_SECURE_ENDPOINT); + }); }); }); diff --git a/test/spec/modules/justpremiumBidAdapter_spec.js b/test/spec/modules/justpremiumBidAdapter_spec.js index da0e147bd29..c3cd015b6e3 100644 --- a/test/spec/modules/justpremiumBidAdapter_spec.js +++ b/test/spec/modules/justpremiumBidAdapter_spec.js @@ -1,11 +1,35 @@ import { expect } from 'chai' -import { spec } from 'modules/justpremiumBidAdapter' +import { spec, pixel } from 'modules/justpremiumBidAdapter' describe('justpremium adapter', function () { + let sandbox; + let pixelStub; + + beforeEach(function() { + sandbox = sinon.sandbox.create(); + pixelStub = sandbox.stub(pixel, 'fire'); + }); + + afterEach(function() { + sandbox.restore(); + }); + let adUnits = [ { adUnitCode: 'div-gpt-ad-1471513102552-1', bidder: 'justpremium', + crumbs: { + pubcid: '0000000' + }, + userId: { + tdid: '1111111', + id5id: '2222222', + digitrustid: { + data: { + id: '3333333' + } + } + }, params: { zone: 28313, allow: ['lb', 'wp'] @@ -21,6 +45,12 @@ describe('justpremium adapter', function () { }, ] + let bidderRequest = { + refererInfo: { + referer: 'http://justpremium.com' + } + } + describe('isBidRequestValid', function () { it('Verifies bidder code', function () { expect(spec.code).to.equal('justpremium') @@ -36,15 +66,14 @@ describe('justpremium adapter', function () { describe('buildRequests', function () { it('Verify build request and parameters', function () { - const request = spec.buildRequests(adUnits) + const request = spec.buildRequests(adUnits, bidderRequest) expect(request.method).to.equal('POST') expect(request.url).to.match(/pre.ads.justpremium.com\/v\/2.0\/t\/xhr/) const jpxRequest = JSON.parse(request.data) expect(jpxRequest).to.not.equal(null) expect(jpxRequest.zone).to.not.equal('undefined') - expect(jpxRequest.hostname).to.equal(top.document.location.hostname) - expect(jpxRequest.protocol).to.equal(top.document.location.protocol.replace(':', '')) + expect(bidderRequest.refererInfo.referer).to.equal('http://justpremium.com') expect(jpxRequest.sw).to.equal(window.top.screen.width) expect(jpxRequest.sh).to.equal(window.top.screen.height) expect(jpxRequest.ww).to.equal(window.top.innerWidth) @@ -53,12 +82,16 @@ describe('justpremium adapter', function () { expect(jpxRequest.id).to.equal(adUnits[0].params.zone) expect(jpxRequest.sizes).to.not.equal('undefined') expect(jpxRequest.version.prebid).to.equal('$prebid.version$') - expect(jpxRequest.version.jp_adapter).to.equal('1.2') + expect(jpxRequest.version.jp_adapter).to.equal('1.4') + expect(jpxRequest.pubcid).to.equal('0000000') + expect(jpxRequest.uids.tdid).to.equal('1111111') + expect(jpxRequest.uids.id5id).to.equal('2222222') + expect(jpxRequest.uids.digitrustid.data.id).to.equal('3333333') }) }) describe('interpretResponse', function () { - const request = spec.buildRequests(adUnits) + const request = spec.buildRequests(adUnits, bidderRequest) it('Verify server response', function () { let response = { 'bid': { @@ -132,7 +165,7 @@ describe('justpremium adapter', function () { }) describe('onTimeout', function () { - it('onTimeout', (done) => { + it('onTimeout', function(done) { spec.onTimeout([{ 'bidId': '25cd3ec3fd6ed7', 'bidder': 'justpremium', @@ -153,7 +186,9 @@ describe('justpremium adapter', function () { 'zone': 21521 }], 'timeout': 1 - }]) + }]); + + expect(pixelStub.calledOnce).to.equal(true); done() }) diff --git a/test/spec/modules/kargoBidAdapter_spec.js b/test/spec/modules/kargoBidAdapter_spec.js index eafb5a9c0f3..f661dbadb9a 100644 --- a/test/spec/modules/kargoBidAdapter_spec.js +++ b/test/spec/modules/kargoBidAdapter_spec.js @@ -1,6 +1,5 @@ import {expect, assert} from 'chai'; import {spec} from 'modules/kargoBidAdapter'; -import {registerBidder} from 'src/adapters/bidderFactory'; import {config} from 'src/config'; describe('kargo adapter tests', function () { @@ -35,12 +34,20 @@ describe('kargo adapter tests', function () { }); describe('build request', function() { - var bids, cookies = [], localStorageItems = []; + var bids, undefinedCurrency, noAdServerCurrency, cookies = [], localStorageItems = [], sessionIds = [], requestCount = 0; beforeEach(function () { + undefinedCurrency = false; + noAdServerCurrency = false; sandbox.stub(config, 'getConfig').callsFake(function(key) { if (key === 'currency') { - return 'USD'; + if (undefinedCurrency) { + return undefined; + } + if (noAdServerCurrency) { + return {}; + } + return {adServerCurrency: 'USD'}; } throw new Error(`Config stub incomplete! Missing key "${key}"`) }); @@ -50,19 +57,25 @@ describe('kargo adapter tests', function () { params: { placementId: 'foo' }, - bidId: 1 + bidId: 1, + userId: { + tdid: 'fake-tdid' + }, + sizes: [[320, 50], [300, 250], [300, 600]] }, { params: { placementId: 'bar' }, - bidId: 2 + bidId: 2, + sizes: [[320, 50], [300, 250], [300, 600]] }, { params: { placementId: 'bar' }, - bidId: 3 + bidId: 3, + sizes: [[320, 50], [300, 250], [300, 600]] } ]; }); @@ -109,6 +122,16 @@ describe('kargo adapter tests', function () { return sandbox.stub(localStorage, 'getItem').throws(); } + function simulateNoCurrencyObject() { + undefinedCurrency = true; + noAdServerCurrency = false; + } + + function simulateNoAdServerCurrency() { + undefinedCurrency = false; + noAdServerCurrency = true; + } + function initializeKruxUser() { setLocalStorageItem('kxkar_user', 'rsgr9pnij'); } @@ -117,20 +140,19 @@ describe('kargo adapter tests', function () { setLocalStorageItem('kxkar_segs', 'qv9v984dy,rpx2gy365,qrd5u4axv,rnub9nmtd,reha00jnu'); } - function initializeKrgUid() { - setCookie('krg_uid', '%7B%22v%22%3A%7B%22userId%22%3A%225f108831-302d-11e7-bf6b-4595acd3bf6c%22%2C%22clientId%22%3A%222410d8f2-c111-4811-88a5-7b5e190e475f%22%2C%22optOut%22%3Afalse%7D%7D'); - } - function getKrgCrb() { - return '%7B%22v%22%3A%22eyJzeW5jSWRzIjp7IjIiOiI4MmZhMjU1NS01OTY5LTQ2MTQtYjRjZS00ZGNmMTA4MGU5ZjkiLCIxNiI6IlZveElrOEFvSnowQUFFZENleUFBQUFDMiY1MDIiLCIyMyI6ImQyYTg1NWE1LTFiMWMtNDMwMC05NDBlLWE3MDhmYTFmMWJkZSIsIjI0IjoiVm94SWs4QW9KejBBQUVkQ2V5QUFBQUMyJjUwMiIsIjI1IjoiNWVlMjQxMzgtNWUwMy00YjlkLWE5NTMtMzhlODMzZjI4NDlmIiwiMl84MCI6ImQyYTg1NWE1LTFiMWMtNDMwMC05NDBlLWE3MDhmYTFmMWJkZSIsIjJfOTMiOiI1ZWUyNDEzOC01ZTAzLTRiOWQtYTk1My0zOGU4MzNmMjg0OWYifSwiZXhwaXJlVGltZSI6MTQ5NzQ0OTM4MjY2OCwibGFzdFN5bmNlZEF0IjoxNDk3MzYyOTc5MDEyfQ%3D%3D%22%7D'; + return 'eyJzeW5jSWRzIjp7IjIiOiI4MmZhMjU1NS01OTY5LTQ2MTQtYjRjZS00ZGNmMTA4MGU5ZjkiLCIxNiI6IlZveElrOEFvSnowQUFFZENleUFBQUFDMiY1MDIiLCIyMyI6ImQyYTg1NWE1LTFiMWMtNDMwMC05NDBlLWE3MDhmYTFmMWJkZSIsIjI0IjoiVm94SWs4QW9KejBBQUVkQ2V5QUFBQUMyJjUwMiIsIjI1IjoiNWVlMjQxMzgtNWUwMy00YjlkLWE5NTMtMzhlODMzZjI4NDlmIiwiMl84MCI6ImQyYTg1NWE1LTFiMWMtNDMwMC05NDBlLWE3MDhmYTFmMWJkZSIsIjJfOTMiOiI1ZWUyNDEzOC01ZTAzLTRiOWQtYTk1My0zOGU4MzNmMjg0OWYifSwidXNlcklkIjoiNWYxMDg4MzEtMzAyZC0xMWU3LWJmNmItNDU5NWFjZDNiZjZjIiwiY2xpZW50SWQiOiIyNDEwZDhmMi1jMTExLTQ4MTEtODhhNS03YjVlMTkwZTQ3NWYiLCJvcHRPdXQiOmZhbHNlLCJleHBpcmVUaW1lIjoxNDk3NDQ5MzgyNjY4LCJsYXN0U3luY2VkQXQiOjE0OTczNjI5NzkwMTJ9'; } - function initializeKrgCrb() { - setCookie('krg_crb', getKrgCrb()); + function getKrgCrbOldStyle() { + return '%7B%22v%22%3A%22eyJzeW5jSWRzIjp7IjIiOiI4MmZhMjU1NS01OTY5LTQ2MTQtYjRjZS00ZGNmMTA4MGU5ZjkiLCIxNiI6IlZveElrOEFvSnowQUFFZENleUFBQUFDMiY1MDIiLCIyMyI6ImQyYTg1NWE1LTFiMWMtNDMwMC05NDBlLWE3MDhmYTFmMWJkZSIsIjI0IjoiVm94SWs4QW9KejBBQUVkQ2V5QUFBQUMyJjUwMiIsIjI1IjoiNWVlMjQxMzgtNWUwMy00YjlkLWE5NTMtMzhlODMzZjI4NDlmIiwiMl84MCI6ImQyYTg1NWE1LTFiMWMtNDMwMC05NDBlLWE3MDhmYTFmMWJkZSIsIjJfOTMiOiI1ZWUyNDEzOC01ZTAzLTRiOWQtYTk1My0zOGU4MzNmMjg0OWYifSwidXNlcklkIjoiNWYxMDg4MzEtMzAyZC0xMWU3LWJmNmItNDU5NWFjZDNiZjZjIiwiY2xpZW50SWQiOiIyNDEwZDhmMi1jMTExLTQ4MTEtODhhNS03YjVlMTkwZTQ3NWYiLCJvcHRPdXQiOmZhbHNlLCJleHBpcmVUaW1lIjoxNDk3NDQ5MzgyNjY4LCJsYXN0U3luY2VkQXQiOjE0OTczNjI5NzkwMTJ9%22%7D'; } - function initializeInvalidKrgUid() { - setCookie('krg_uid', 'invalid-krg-uid'); + function initializeKrgCrb(cookieOnly) { + if (!cookieOnly) { + setLocalStorageItem('krg_crb', getKrgCrb()); + } + setCookie('krg_crb', getKrgCrbOldStyle()); } function getInvalidKrgCrbType1() { @@ -138,40 +160,69 @@ describe('kargo adapter tests', function () { } function initializeInvalidKrgCrbType1() { + setLocalStorageItem('krg_crb', getInvalidKrgCrbType1()); + } + + function initializeInvalidKrgCrbType1Cookie() { setCookie('krg_crb', getInvalidKrgCrbType1()); } function getInvalidKrgCrbType2() { + return 'Ly8v'; + } + + function getInvalidKrgCrbType2OldStyle() { return '%7B%22v%22%3A%22%26%26%26%26%26%26%22%7D'; } function initializeInvalidKrgCrbType2() { - setCookie('krg_crb', getInvalidKrgCrbType2()); + setLocalStorageItem('krg_crb', getInvalidKrgCrbType2()); + } + + function initializeInvalidKrgCrbType2Cookie() { + setCookie('krg_crb', getInvalidKrgCrbType2OldStyle()); } - function getInvalidKrgCrbType3() { + function getInvalidKrgCrbType3OldStyle() { return '%7B%22v%22%3A%22Ly8v%22%7D'; } - function initializeInvalidKrgCrbType3() { - setCookie('krg_crb', getInvalidKrgCrbType3()); + function initializeInvalidKrgCrbType3Cookie() { + setCookie('krg_crb', getInvalidKrgCrbType3OldStyle()); } - function initializeEmptyKrgUid() { - setCookie('krg_uid', '%7B%7D'); + function getInvalidKrgCrbType4OldStyle() { + return '%7B%22v%22%3A%22bnVsbA%3D%3D%22%7D'; + } + + function initializeInvalidKrgCrbType4Cookie() { + setCookie('krg_crb', getInvalidKrgCrbType4OldStyle()); } function getEmptyKrgCrb() { + return 'eyJleHBpcmVUaW1lIjoxNDk3NDQ5MzgyNjY4LCJsYXN0U3luY2VkQXQiOjE0OTczNjI5NzkwMTJ9'; + } + + function getEmptyKrgCrbOldStyle() { return '%7B%22v%22%3A%22eyJleHBpcmVUaW1lIjoxNDk3NDQ5MzgyNjY4LCJsYXN0U3luY2VkQXQiOjE0OTczNjI5NzkwMTJ9%22%7D'; } function initializeEmptyKrgCrb() { - setCookie('krg_crb', getEmptyKrgCrb()); + setLocalStorageItem('krg_crb', getEmptyKrgCrb()); + } + + function initializeEmptyKrgCrbCookie() { + setCookie('krg_crb', getEmptyKrgCrbOldStyle()); } - function getExpectedKrakenParams(excludeUserIds, excludeKrux, expectedRawCRB) { + function getSessionId() { + return spec._getSessionId(); + } + + function getExpectedKrakenParams(excludeUserIds, excludeKrux, expectedRawCRB, expectedRawCRBCookie) { var base = { timeout: 200, + requestCount: requestCount++, currency: 'USD', cpmGranularity: 1, timestamp: frozenNow.getTime(), @@ -184,9 +235,15 @@ describe('kargo adapter tests', function () { 2: 'bar', 3: 'bar' }, + bidSizes: { + 1: [[320, 50], [300, 250], [300, 600]], + 2: [[320, 50], [300, 250], [300, 600]], + 3: [[320, 50], [300, 250], [300, 600]] + }, userIDs: { kargoID: '5f108831-302d-11e7-bf6b-4595acd3bf6c', clientID: '2410d8f2-c111-4811-88a5-7b5e190e475f', + tdID: 'fake-tdid', crbIDs: { 2: '82fa2555-5969-4614-b4ce-4dcf1080e9f9', 16: 'VoxIk8AoJz0AAEdCeyAAAAC2&502', @@ -208,24 +265,42 @@ describe('kargo adapter tests', function () { 'reha00jnu' ] }, - pageURL: window.location.href, - rawCRB: expectedRawCRB + pageURL: window.originalLocation.href, + prebidRawBidRequests: [ + { + bidId: 1, + params: { + placementId: 'foo' + }, + userId: { + tdid: 'fake-tdid' + }, + sizes: [[320, 50], [300, 250], [300, 600]] + }, + { + bidId: 2, + params: { + placementId: 'bar' + }, + sizes: [[320, 50], [300, 250], [300, 600]] + }, + { + bidId: 3, + params: { + placementId: 'bar' + }, + sizes: [[320, 50], [300, 250], [300, 600]] + } + ], + rawCRB: expectedRawCRBCookie, + rawCRBLocalStorage: expectedRawCRB }; if (excludeUserIds === true) { base.userIDs = { crbIDs: {} }; - } else if (excludeUserIds) { - if (excludeUserIds.uid) { - delete base.userIDs.kargoID; - delete base.userIDs.clientID; - delete base.userIDs.optOut; - } - - if (excludeUserIds.crb) { - base.userIDs.crbIDs = {}; - } + delete base.prebidRawBidRequests[0].userId.tdid; } if (excludeKrux) { @@ -238,8 +313,14 @@ describe('kargo adapter tests', function () { return base; } - function testBuildRequests(expected) { - var request = spec.buildRequests(bids, {timeout: 200, foo: 'bar'}); + function testBuildRequests(excludeTdid, expected) { + var clonedBids = JSON.parse(JSON.stringify(bids)); + if (excludeTdid) { + delete clonedBids[0].userId.tdid; + } + var request = spec.buildRequests(clonedBids, {timeout: 200, foo: 'bar'}); + expected.sessionId = getSessionId(); + sessionIds.push(expected.sessionId); var krakenParams = JSON.parse(decodeURIComponent(request.data.slice(5))); expect(request.data.slice(0, 5)).to.equal('json='); expect(request.url).to.equal('https://krk.kargo.com/api/v2/bid'); @@ -248,65 +329,103 @@ describe('kargo adapter tests', function () { expect(request.timeout).to.equal(200); expect(request.foo).to.equal('bar'); expect(krakenParams).to.deep.equal(expected); + // Make sure session ID stays the same across requests simulating multiple auctions on one page load + for (let i in sessionIds) { + if (i == 0) { + continue; + } + let sessionId = sessionIds[i]; + expect(sessionIds[0]).to.equal(sessionId); + } } - it('works when all params and cookies are correctly set', function() { + it('works when all params and localstorage and cookies are correctly set', function() { initializeKruxUser(); initializeKruxSegments(); - initializeKrgUid(); initializeKrgCrb(); - testBuildRequests(getExpectedKrakenParams(undefined, undefined, getKrgCrb())); + testBuildRequests(false, getExpectedKrakenParams(undefined, undefined, getKrgCrb(), getKrgCrbOldStyle())); + }); + + it('works when all params and cookies are correctly set but no localstorage', function() { + initializeKruxUser(); + initializeKruxSegments(); + initializeKrgCrb(true); + testBuildRequests(false, getExpectedKrakenParams(undefined, undefined, null, getKrgCrbOldStyle())); }); it('gracefully handles nothing being set', function() { - testBuildRequests(getExpectedKrakenParams(true, true, null)); + testBuildRequests(true, getExpectedKrakenParams(true, true, null, null)); }); it('gracefully handles browsers without localStorage', function() { simulateNoLocalStorage(); - initializeKrgUid(); - initializeKrgCrb(); - testBuildRequests(getExpectedKrakenParams(false, true, getKrgCrb())); + testBuildRequests(true, getExpectedKrakenParams(true, true, null, null)); }); - it('handles empty yet valid Kargo CRBs and UIDs', function() { + it('handles empty yet valid Kargo CRB', function() { initializeKruxUser(); initializeKruxSegments(); - initializeEmptyKrgUid(); initializeEmptyKrgCrb(); - testBuildRequests(getExpectedKrakenParams(true, undefined, getEmptyKrgCrb())); + initializeEmptyKrgCrbCookie(); + testBuildRequests(true, getExpectedKrakenParams(true, undefined, getEmptyKrgCrb(), getEmptyKrgCrbOldStyle())); }); - it('handles broken Kargo UIDs', function() { + it('handles broken Kargo CRBs where base64 encoding is invalid', function() { initializeKruxUser(); initializeKruxSegments(); - initializeInvalidKrgUid(); - initializeKrgCrb(); - testBuildRequests(getExpectedKrakenParams({uid: true}, undefined, getKrgCrb())); + initializeInvalidKrgCrbType1(); + testBuildRequests(true, getExpectedKrakenParams(true, undefined, getInvalidKrgCrbType1(), null)); }); - it('handles broken Kargo CRBs where top level JSON is invalid', function() { + it('handles broken Kargo CRBs where top level JSON is invalid on cookie', function() { initializeKruxUser(); initializeKruxSegments(); - initializeKrgUid(); - initializeInvalidKrgCrbType1(); - testBuildRequests(getExpectedKrakenParams({crb: true}, undefined, getInvalidKrgCrbType1())); + initializeInvalidKrgCrbType1Cookie(); + testBuildRequests(true, getExpectedKrakenParams(true, undefined, null, getInvalidKrgCrbType1())); }); - it('handles broken Kargo CRBs where inner base 64 is invalid', function() { + it('handles broken Kargo CRBs where decoded JSON is invalid', function() { initializeKruxUser(); initializeKruxSegments(); - initializeKrgUid(); initializeInvalidKrgCrbType2(); - testBuildRequests(getExpectedKrakenParams({crb: true}, undefined, getInvalidKrgCrbType2())); + testBuildRequests(true, getExpectedKrakenParams(true, undefined, getInvalidKrgCrbType2(), null)); + }); + + it('handles broken Kargo CRBs where inner base 64 is invalid on cookie', function() { + initializeKruxUser(); + initializeKruxSegments(); + initializeInvalidKrgCrbType2Cookie(); + testBuildRequests(true, getExpectedKrakenParams(true, undefined, null, getInvalidKrgCrbType2OldStyle())); }); - it('handles broken Kargo CRBs where inner JSON is invalid', function() { + it('handles broken Kargo CRBs where inner JSON is invalid on cookie', function() { initializeKruxUser(); initializeKruxSegments(); - initializeKrgUid(); - initializeInvalidKrgCrbType3(); - testBuildRequests(getExpectedKrakenParams({crb: true}, undefined, getInvalidKrgCrbType3())); + initializeInvalidKrgCrbType3Cookie(); + testBuildRequests(true, getExpectedKrakenParams(true, undefined, null, getInvalidKrgCrbType3OldStyle())); + }); + + it('handles broken Kargo CRBs where inner JSON is falsey', function() { + initializeKruxUser(); + initializeKruxSegments(); + initializeInvalidKrgCrbType4Cookie(); + testBuildRequests(true, getExpectedKrakenParams(true, undefined, null, getInvalidKrgCrbType4OldStyle())); + }); + + it('handles a non-existant currency object on the config', function() { + simulateNoCurrencyObject(); + initializeKruxUser(); + initializeKruxSegments(); + initializeKrgCrb(); + testBuildRequests(false, getExpectedKrakenParams(undefined, undefined, getKrgCrb(), getKrgCrbOldStyle())); + }); + + it('handles no ad server currency being set on the currency object in the config', function() { + simulateNoAdServerCurrency(); + initializeKruxUser(); + initializeKruxSegments(); + initializeKrgCrb(); + testBuildRequests(false, getExpectedKrakenParams(undefined, undefined, getKrgCrb(), getKrgCrbOldStyle())); }); }); @@ -325,7 +444,8 @@ describe('kargo adapter tests', function () { cpm: 2.5, adm: '
    ', width: 300, - height: 250 + height: 250, + targetingCustom: 'dmpmptest1234' }, 3: { id: 'bar', @@ -361,6 +481,7 @@ describe('kargo adapter tests', function () { ad: '
    ', ttl: 300, creativeId: 'foo', + dealId: undefined, netRevenue: true, currency: 'USD' }, { @@ -371,6 +492,7 @@ describe('kargo adapter tests', function () { ad: '
    ', ttl: 300, creativeId: 'bar', + dealId: 'dmpmptest1234', netRevenue: true, currency: 'USD' }, { @@ -381,10 +503,104 @@ describe('kargo adapter tests', function () { ad: '
    ', ttl: 300, creativeId: 'bar', + dealId: undefined, netRevenue: true, currency: 'USD' }]; expect(resp).to.deep.equal(expectation); }); }); + + describe('user sync handler', function() { + const clientId = '74c81cbb-7d07-46d9-be9b-68ccb291c949'; + var shouldSimulateOutdatedBrowser, crb, isActuallyOutdatedBrowser; + + beforeEach(() => { + crb = {}; + shouldSimulateOutdatedBrowser = false; + isActuallyOutdatedBrowser = false; + + // IE11 fails these tests in the Prebid test suite. Since this + // browser won't support any of this stuff we expect all user + // syncing to fail gracefully. Kargo is mobile only, so this + // doesn't really matter. + if (!window.crypto) { + isActuallyOutdatedBrowser = true; + } else { + sandbox.stub(crypto, 'getRandomValues').callsFake(function(buf) { + if (shouldSimulateOutdatedBrowser) { + throw new Error('Could not generate random values'); + } + var bytes = [50, 5, 232, 133, 141, 55, 49, 57, 244, 126, 248, 44, 255, 38, 128, 0]; + for (var i = 0; i < bytes.length; i++) { + buf[i] = bytes[i]; + } + return buf; + }); + } + + sandbox.stub(spec, '_getCrb').callsFake(function() { + return crb; + }); + }); + + function getUserSyncsWhenAllowed() { + return spec.getUserSyncs({iframeEnabled: true}); + } + + function getUserSyncsWhenForbidden() { + return spec.getUserSyncs({}); + } + + function turnOnClientId() { + crb.clientId = clientId; + } + + function simulateOutdatedBrowser() { + shouldSimulateOutdatedBrowser = true; + } + + function getSyncUrl(index) { + return { + type: 'iframe', + url: `https://crb.kargo.com/api/v1/initsyncrnd/${clientId}?seed=3205e885-8d37-4139-b47e-f82cff268000&idx=${index}` + }; + } + + function getSyncUrls() { + var syncs = []; + for (var i = 0; i < 5; i++) { + syncs[i] = getSyncUrl(i); + } + return syncs; + } + + function safelyRun(runExpectation) { + if (isActuallyOutdatedBrowser) { + expect(getUserSyncsWhenAllowed()).to.be.an('array').that.is.empty; + } else { + runExpectation(); + } + } + + it('handles user syncs when there is a client id', function() { + turnOnClientId(); + safelyRun(() => expect(getUserSyncsWhenAllowed()).to.deep.equal(getSyncUrls())); + }); + + it('no user syncs when there is no client id', function() { + safelyRun(() => expect(getUserSyncsWhenAllowed()).to.be.an('array').that.is.empty); + }); + + it('no user syncs when there is outdated browser', function() { + turnOnClientId(); + simulateOutdatedBrowser(); + safelyRun(() => expect(getUserSyncsWhenAllowed()).to.be.an('array').that.is.empty); + }); + + it('no user syncs when no iframe syncing allowed', function() { + turnOnClientId(); + safelyRun(() => expect(getUserSyncsWhenForbidden()).to.be.an('array').that.is.empty); + }); + }); }); diff --git a/test/spec/modules/konduitWrapper_spec.js b/test/spec/modules/konduitWrapper_spec.js new file mode 100644 index 00000000000..bcc65ddd683 --- /dev/null +++ b/test/spec/modules/konduitWrapper_spec.js @@ -0,0 +1,127 @@ +import { expect } from 'chai'; + +import parse from 'url-parse'; +import { buildVastUrl } from 'modules/konduitWrapper'; +import { parseQS } from 'src/url'; +import { config } from 'src/config'; + +describe('The Konduit vast wrapper module', function () { + it('should make a wrapped request url when `bid` passed', function () { + const bid = createBid(10, 'video1', 15, '10.00_15s', '123', '395'); + + const url = parse(buildVastUrl({ + bid, + params: { 'konduit_id': 'testId' }, + })); + + expect(url.protocol).to.equal('https:'); + expect(url.host).to.equal('p.konduit.me'); + + const queryParams = parseQS(url.query); + expect(queryParams).to.have.property('konduit_url', encodeURIComponent('http://some-vast-url.com')); + expect(queryParams).to.have.property('konduit_header_bidding', '1'); + expect(queryParams).to.have.property('konduit_id', 'testId'); + }); + + it('should return null when no `konduit_id` (required param) passed', function () { + const bid = createBid(10, 'video1', 15, '10.00_15s', '123', '395'); + + const url = buildVastUrl({ bid }); + + expect(url).to.equal(null); + }); + + it('should return null when either bid or adUnit is not passed', function () { + const url = buildVastUrl({ params: { 'konduit_id': 'testId' } }); + + expect(url).to.equal(null); + }); + + it('should return null when bid does not contain vastUrl', function () { + const bid = createBid(10, 'video1', 15, '10.00_15s', '123', '395'); + + delete bid.vastUrl; + + const url = buildVastUrl({ + bid, + params: { 'konduit_id': 'testId' }, + }); + + expect(url).to.equal(null); + }); + + it('should return wrapped vastUrl based on cached url in params', function () { + config.setConfig({ cache: { url: 'https://cached.url.com' } }); + const bid = createBid(10, 'video1', 15, '10.00_15s', '123', '395'); + + delete bid.vastUrl; + + const expectedUrl = encodeURIComponent(`https://cached.url.com?uuid=${bid.videoCacheKey}`); + + const url = parse(buildVastUrl({ + bid, + params: { 'konduit_id': 'testId' }, + })); + const queryParams = parseQS(url.query); + + expect(queryParams).to.have.property('konduit_url', expectedUrl); + + config.resetConfig(); + }); +}); + +function createBid(cpm, adUnitCode, durationBucket, priceIndustryDuration, uuid, label) { + return { + 'bidderCode': 'appnexus', + 'width': 640, + 'height': 360, + 'statusMessage': 'Bid available', + 'adId': '28f24ced14586c', + 'mediaType': 'video', + 'source': 'client', + 'requestId': '28f24ced14586c', + 'cpm': cpm, + 'creativeId': 97517771, + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 3600, + 'adUnitCode': adUnitCode, + 'video': { + 'context': 'adpod', + 'durationBucket': durationBucket + }, + 'appnexus': { + 'buyerMemberId': 9325 + }, + 'vastUrl': 'http://some-vast-url.com', + 'vastImpUrl': 'http://some-vast-imp-url.com', + 'auctionId': 'ec266b31-d652-49c5-8295-e83fafe5532b', + 'responseTimestamp': 1548442460888, + 'requestTimestamp': 1548442460827, + 'bidder': 'appnexus', + 'timeToRespond': 61, + 'pbLg': '5.00', + 'pbMg': '5.00', + 'pbHg': '5.00', + 'pbAg': '5.00', + 'pbDg': '5.00', + 'pbCg': '', + 'size': '640x360', + 'adserverTargeting': { + 'hb_bidder': 'appnexus', + 'hb_adid': '28f24ced14586c', + 'hb_pb': '5.00', + 'hb_size': '640x360', + 'hb_source': 'client', + 'hb_format': 'video', + 'hb_pb_cat_dur': priceIndustryDuration, + 'hb_cache_id': uuid + }, + 'customCacheKey': `${priceIndustryDuration}_${uuid}`, + 'meta': { + 'iabSubCatId': 'iab-1', + 'adServerCatId': label + }, + 'videoCacheKey': '4cf395af-8fee-4960-af0e-88d44e399f14' + } +} diff --git a/test/spec/modules/kummaBidAdapter_spec.js b/test/spec/modules/kummaBidAdapter_spec.js index 82076717dcc..7d33bd085b5 100644 --- a/test/spec/modules/kummaBidAdapter_spec.js +++ b/test/spec/modules/kummaBidAdapter_spec.js @@ -192,8 +192,8 @@ describe('Kumma Adapter Tests', function () { { id: 1, title: { text: 'Ad Title' } }, { id: 2, data: { value: 'Test description' } }, { id: 3, data: { value: 'Brand' } }, - { id: 4, img: { url: 'https://s3.amazonaws.com/adx1public/creatives_icon.png', w: 100, h: 100 } }, - { id: 5, img: { url: 'https://s3.amazonaws.com/adx1public/creatives_image.png', w: 300, h: 300 } } + { id: 4, img: { url: 'https://adx1public.s3.amazonaws.com/creatives_icon.png', w: 100, h: 100 } }, + { id: 5, img: { url: 'https://adx1public.s3.amazonaws.com/creatives_image.png', w: 300, h: 300 } } ], link: { url: 'http://brand.com/' } } @@ -220,8 +220,8 @@ describe('Kumma Adapter Tests', function () { expect(nativeBid).to.not.equal(null); expect(nativeBid.title).to.equal('Ad Title'); expect(nativeBid.sponsoredBy).to.equal('Brand'); - expect(nativeBid.icon.url).to.equal('https://s3.amazonaws.com/adx1public/creatives_icon.png'); - expect(nativeBid.image.url).to.equal('https://s3.amazonaws.com/adx1public/creatives_image.png'); + expect(nativeBid.icon.url).to.equal('https://adx1public.s3.amazonaws.com/creatives_icon.png'); + expect(nativeBid.image.url).to.equal('https://adx1public.s3.amazonaws.com/creatives_image.png'); expect(nativeBid.image.width).to.equal(300); expect(nativeBid.image.height).to.equal(300); expect(nativeBid.icon.width).to.equal(100); diff --git a/test/spec/modules/lemmaBidAdapter_spec.js b/test/spec/modules/lemmaBidAdapter_spec.js new file mode 100644 index 00000000000..624e763ebe1 --- /dev/null +++ b/test/spec/modules/lemmaBidAdapter_spec.js @@ -0,0 +1,335 @@ +import { expect } from 'chai'; +import { spec } from 'modules/lemmaBidAdapter'; +import * as utils from 'src/utils'; +const constants = require('src/constants.json'); + +describe('lemmaBidAdapter', function() { + var bidRequests; + var videoBidRequests; + var bidResponses; + beforeEach(function() { + bidRequests = [{ + bidder: 'lemma', + mediaType: 'banner', + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 600] + ], + } + }, + params: { + pubId: 1001, + adunitId: 1, + currency: 'AUD', + geo: { + lat: '12.3', + lon: '23.7', + } + }, + sizes: [ + [300, 250], + [300, 600] + ] + }]; + videoBidRequests = [{ + code: 'video1', + mediaType: 'video', + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'instream' + } + }, + bidder: 'lemma', + params: { + pubId: 1001, + adunitId: 1, + video: { + mimes: ['video/mp4', 'video/x-flv'], + skippable: true, + minduration: 5, + maxduration: 30 + } + } + }]; + bidResponses = { + 'body': { + 'id': '93D3BAD6-E2E2-49FB-9D89-920B1761C865', + 'seatbid': [{ + 'bid': [{ + 'id': '74858439-49D7-4169-BA5D-44A046315B2F', + 'impid': '22bddb28db77d', + 'price': 1.3, + 'adm': '

    lemma"Connecting Advertisers and Publishers directly"

    ', + 'adomain': ['amazon.com'], + 'iurl': 'https://thetradedesk-t-general.s3.amazonaws.com/AdvertiserLogos/vgl908z.png', + 'cid': '22918', + 'crid': 'v55jutrh', + 'h': 250, + 'w': 300, + 'ext': {} + }] + }] + } + }; + }); + describe('implementation', function() { + describe('Bid validations', function() { + it('valid bid case', function() { + var validBid = { + bidder: 'lemma', + params: { + pubId: 1001, + adunitId: 1 + } + }, + isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(true); + }); + it('invalid bid case', function() { + var isValid = spec.isBidRequestValid(); + expect(isValid).to.equal(false); + }); + it('invalid bid case: pubId not passed', function() { + var validBid = { + bidder: 'lemma', + params: { + adunitId: 1 + } + }, + isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(false); + }); + it('invalid bid case: pubId is not number', function() { + var validBid = { + bidder: 'lemma', + params: { + pubId: '301', + adunitId: 1 + } + }, + isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(false); + }); + it('invalid bid case: adunitId is not passed', function() { + var validBid = { + bidder: 'lemma', + params: { + pubId: 1001 + } + }, + isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(false); + }); + it('invalid bid case: video bid request mimes is not passed', function() { + var validBid = { + bidder: 'lemma', + params: { + pubId: 1001, + adunitId: 1, + video: { + skippable: true, + minduration: 5, + maxduration: 30 + } + } + }, + isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(false); + validBid.params.video.mimes = []; + isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(false); + }); + }); + describe('Request formation', function() { + it('buildRequests function should not modify original bidRequests object', function() { + var originalBidRequests = utils.deepClone(bidRequests); + var request = spec.buildRequests(bidRequests); + expect(bidRequests).to.deep.equal(originalBidRequests); + }); + it('Endpoint checking', function() { + var request = spec.buildRequests(bidRequests); + expect(request.url).to.equal('//ads.lemmatechnologies.com/lemma/servad?pid=1001&aid=1'); + expect(request.method).to.equal('POST'); + }); + it('Request params check', function() { + var request = spec.buildRequests(bidRequests); + var data = JSON.parse(request.data); + expect(data.site.domain).to.be.a('string'); // domain should be set + expect(data.site.publisher.id).to.equal(bidRequests[0].params.pubId.toString()); // publisher Id + expect(data.imp[0].tagid).to.equal('1'); // tagid + expect(data.imp[0].bidfloorcur).to.equal(bidRequests[0].params.currency); + }); + it('Request params check without mediaTypes object', function() { + var bidRequests = [{ + bidder: 'lemma', + params: { + pubId: 1001, + adunitId: 1, + currency: 'AUD' + }, + sizes: [ + [300, 250], + [300, 600] + ] + }]; + var request = spec.buildRequests(bidRequests); + var data = JSON.parse(request.data); + expect(data.imp[0].banner.w).to.equal(300); // width + expect(data.imp[0].banner.h).to.equal(250); // height + expect(data.imp[0].banner.format).exist.and.to.be.an('array'); + expect(data.imp[0].banner.format[0]).exist.and.to.be.an('object'); + expect(data.imp[0].banner.format[0].w).to.equal(300); // width + expect(data.imp[0].banner.format[0].h).to.equal(600); // height + }); + it('Request params check: without tagId', function() { + delete bidRequests[0].params.adunitId; + var request = spec.buildRequests(bidRequests); + var data = JSON.parse(request.data); + expect(data.site.domain).to.be.a('string'); // domain should be set + expect(data.site.publisher.id).to.equal(bidRequests[0].params.pubId.toString()); // publisher Id + expect(data.imp[0].tagid).to.equal(undefined); // tagid + expect(data.imp[0].bidfloorcur).to.equal(bidRequests[0].params.currency); + }); + it('Request params multi size format object check', function() { + var bidRequests = [{ + bidder: 'lemma', + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 600] + ], + } + }, + params: { + pubId: 1001, + adunitId: 1, + currency: 'AUD' + }, + sizes: [ + [300, 250], + [300, 600] + ] + }]; + /* case 1 - size passed in adslot */ + var request = spec.buildRequests(bidRequests); + var data = JSON.parse(request.data); + expect(data.imp[0].banner.w).to.equal(300); // width + expect(data.imp[0].banner.h).to.equal(250); // height + /* case 2 - size passed in adslot as well as in sizes array */ + bidRequests[0].sizes = [ + [300, 600], + [300, 250] + ]; + bidRequests[0].mediaTypes = { + banner: { + sizes: [ + [300, 600], + [300, 250] + ] + } + }; + request = spec.buildRequests(bidRequests); + data = JSON.parse(request.data); + expect(data.imp[0].banner.w).to.equal(300); // width + expect(data.imp[0].banner.h).to.equal(600); // height + /* case 3 - size passed in sizes but not in adslot */ + bidRequests[0].params.adunitId = 1; + bidRequests[0].sizes = [ + [300, 250], + [300, 600] + ]; + bidRequests[0].mediaTypes = { + banner: { + sizes: [ + [300, 250], + [300, 600] + ] + } + }; + request = spec.buildRequests(bidRequests); + data = JSON.parse(request.data); + expect(data.imp[0].banner.w).to.equal(300); // width + expect(data.imp[0].banner.h).to.equal(250); // height + expect(data.imp[0].banner.format).exist.and.to.be.an('array'); + expect(data.imp[0].banner.format[0]).exist.and.to.be.an('object'); + expect(data.imp[0].banner.format[0].w).to.equal(300); // width + expect(data.imp[0].banner.format[0].h).to.equal(250); // height + }); + it('Request params currency check', function() { + var bidRequest = [{ + bidder: 'lemma', + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 600] + ], + } + }, + params: { + pubId: 1001, + adunitId: 1, + currency: 'AUD' + }, + sizes: [ + [300, 250], + [300, 600] + ] + }]; + /* case 1 - + currency specified in adunits + output: imp[0] use currency specified in bidRequests[0].params.currency + */ + var request = spec.buildRequests(bidRequest); + var data = JSON.parse(request.data); + expect(data.imp[0].bidfloorcur).to.equal(bidRequests[0].params.currency); + /* case 2 - + currency specified in adunit + output: imp[0] use default currency - USD + */ + delete bidRequest[0].params.currency; + request = spec.buildRequests(bidRequest); + data = JSON.parse(request.data); + expect(data.imp[0].bidfloorcur).to.equal('USD'); + }); + it('Request params check for video ad', function() { + var request = spec.buildRequests(videoBidRequests); + var data = JSON.parse(request.data); + expect(data.imp[0].video).to.exist; + expect(data.imp[0].tagid).to.equal('1'); + expect(data.imp[0]['video']['mimes']).to.exist.and.to.be.an('array'); + expect(data.imp[0]['video']['mimes'][0]).to.equal(videoBidRequests[0].params.video['mimes'][0]); + expect(data.imp[0]['video']['mimes'][1]).to.equal(videoBidRequests[0].params.video['mimes'][1]); + expect(data.imp[0]['video']['minduration']).to.equal(videoBidRequests[0].params.video['minduration']); + expect(data.imp[0]['video']['maxduration']).to.equal(videoBidRequests[0].params.video['maxduration']); + expect(data.imp[0]['video']['w']).to.equal(videoBidRequests[0].mediaTypes.video.playerSize[0]); + expect(data.imp[0]['video']['h']).to.equal(videoBidRequests[0].mediaTypes.video.playerSize[1]); + }); + describe('Response checking', function() { + it('should check for valid response values', function() { + var request = spec.buildRequests(bidRequests); + var data = JSON.parse(request.data); + var response = spec.interpretResponse(bidResponses, request); + expect(response).to.be.an('array').with.length.above(0); + expect(response[0].requestId).to.equal(bidResponses.body.seatbid[0].bid[0].impid); + expect(response[0].cpm).to.equal((bidResponses.body.seatbid[0].bid[0].price).toFixed(2)); + expect(response[0].width).to.equal(bidResponses.body.seatbid[0].bid[0].w); + expect(response[0].height).to.equal(bidResponses.body.seatbid[0].bid[0].h); + if (bidResponses.body.seatbid[0].bid[0].crid) { + expect(response[0].creativeId).to.equal(bidResponses.body.seatbid[0].bid[0].crid); + } else { + expect(response[0].creativeId).to.equal(bidResponses.body.seatbid[0].bid[0].id); + } + expect(response[0].dealId).to.equal(bidResponses.body.seatbid[0].bid[0].dealid); + expect(response[0].currency).to.equal('USD'); + expect(response[0].netRevenue).to.equal(false); + expect(response[0].ttl).to.equal(300); + }); + }); + }); + }); +}); diff --git a/test/spec/modules/lifestreetBidAdapter_spec.js b/test/spec/modules/lifestreetBidAdapter_spec.js index 6c9c6eeba31..7f8c5f6c44d 100644 --- a/test/spec/modules/lifestreetBidAdapter_spec.js +++ b/test/spec/modules/lifestreetBidAdapter_spec.js @@ -67,7 +67,7 @@ describe('LifestreetAdapter', function () { it('should not return request when no bids are present', function () { let [request] = spec.buildRequests([]); - expect(request).to.be.empty; + expect(request).to.be.undefined; }); let bidRequest = getDefaultBidRequest(); diff --git a/test/spec/modules/liveIntentIdSystem_spec.js b/test/spec/modules/liveIntentIdSystem_spec.js new file mode 100644 index 00000000000..7620e8ac7b8 --- /dev/null +++ b/test/spec/modules/liveIntentIdSystem_spec.js @@ -0,0 +1,145 @@ +import { liveIntentIdSubmodule } from 'modules/liveIntentIdSystem'; +import * as utils from 'src/utils'; + +describe('LiveIntentId', function() { + let xhr; + let requests; + let getCookieStub; + let getDataFromLocalStorageStub; + let logErrorStub; + + const defaultConfigParams = {'publisherId': '89899'}; + const responseHeader = { 'Content-Type': 'application/json' } + + beforeEach(function () { + xhr = sinon.useFakeXMLHttpRequest(); + requests = []; + xhr.onCreate = request => requests.push(request); + getCookieStub = sinon.stub(utils, 'getCookie'); + getDataFromLocalStorageStub = sinon.stub(utils, 'getDataFromLocalStorage'); + logErrorStub = sinon.stub(utils, 'logError'); + }); + + afterEach(function () { + xhr.restore(); + getCookieStub.restore(); + getDataFromLocalStorageStub.restore(); + logErrorStub.restore(); + }); + + it('should log an error if no configParams were passed', function() { + liveIntentIdSubmodule.getId(); + expect(logErrorStub.calledOnce).to.be.true; + }); + + it('should log an error if publisherId configParam was not passed', function() { + liveIntentIdSubmodule.getId({}); + expect(logErrorStub.calledOnce).to.be.true; + }); + + it('should call the Custom URL of the LiveIntent Identity Exchange endpoint', function() { + getCookieStub.returns(null); + let callBackSpy = sinon.spy(); + let submoduleCallback = liveIntentIdSubmodule.getId({...defaultConfigParams, ...{'url': 'https://dummy.liveintent.com'}}).callback; + submoduleCallback(callBackSpy); + let request = 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({...defaultConfigParams, ...{'url': 'https://dummy.liveintent.com', 'partner': 'rubicon'}}).callback; + submoduleCallback(callBackSpy); + let request = 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 = requests[0]; + expect(request.url).to.be.eq('//idx.liadm.com/idex/prebid/89899?'); + request.respond( + 200, + responseHeader, + JSON.stringify({}) + ); + expect(callBackSpy.calledOnce).to.be.true; + }); + + it('should include the LiveConnect identifier when calling the LiveIntent Identity Exchange endpoint', function() { + getCookieStub.withArgs('_li_duid').returns('li-fpc'); + let callBackSpy = sinon.spy(); + let submoduleCallback = liveIntentIdSubmodule.getId(defaultConfigParams).callback; + submoduleCallback(callBackSpy); + let request = requests[0]; + expect(request.url).to.be.eq('//idx.liadm.com/idex/prebid/89899?duid=li-fpc&'); + request.respond( + 200, + responseHeader, + JSON.stringify({}) + ); + expect(callBackSpy.calledOnce).to.be.true; + }); + + it('should include the LiveConnect identifier and additional Identifiers to resolve', function() { + getCookieStub.withArgs('_li_duid').returns('li-fpc'); + getDataFromLocalStorageStub.withArgs('_thirdPC').returns('third-pc'); + let configParams = { + ...defaultConfigParams, + ...{ + 'identifiersToResolve': ['_thirdPC'] + } + }; + + let callBackSpy = sinon.spy(); + let submoduleCallback = liveIntentIdSubmodule.getId(configParams).callback; + submoduleCallback(callBackSpy); + let request = requests[0]; + expect(request.url).to.be.eq('//idx.liadm.com/idex/prebid/89899?_thirdPC=third-pc&duid=li-fpc&'); + 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'}); + let configParams = { + ...defaultConfigParams, + ...{ + 'identifiersToResolve': ['_thirdPC'] + } + }; + + let callBackSpy = sinon.spy(); + let submoduleCallback = liveIntentIdSubmodule.getId(configParams).callback; + submoduleCallback(callBackSpy); + let request = requests[0]; + expect(request.url).to.be.eq('//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/livewrappedAnalyticsAdapter_spec.js b/test/spec/modules/livewrappedAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..611ff95a036 --- /dev/null +++ b/test/spec/modules/livewrappedAnalyticsAdapter_spec.js @@ -0,0 +1,324 @@ +import livewrappedAnalyticsAdapter, { BID_WON_TIMEOUT } from 'modules/livewrappedAnalyticsAdapter'; +import CONSTANTS from 'src/constants.json'; +import { config } from 'src/config'; + +let events = require('src/events'); +let utils = require('src/utils'); +let adapterManager = require('src/adapterManager').default; + +const { + EVENTS: { + AUCTION_INIT, + AUCTION_END, + BID_REQUESTED, + BID_RESPONSE, + BIDDER_DONE, + BID_WON, + BID_TIMEOUT, + SET_TARGETING + }, + STATUS: { + GOOD + } +} = CONSTANTS; + +const BID1 = { + width: 980, + height: 240, + cpm: 1.1, + timeToRespond: 200, + bidId: '2ecff0db240757', + requestId: '2ecff0db240757', + adId: '2ecff0db240757', + auctionId: '25c6d7f5-699a-4bfc-87c9-996f915341fa', + getStatusCode() { + return CONSTANTS.STATUS.GOOD; + } +}; + +const BID2 = Object.assign({}, BID1, { + width: 300, + height: 250, + cpm: 2.2, + timeToRespond: 300, + bidId: '3ecff0db240757', + requestId: '3ecff0db240757', + adId: '3ecff0db240757', +}); + +const BID3 = { + bidId: '4ecff0db240757', + requestId: '4ecff0db240757', + adId: '4ecff0db240757', + auctionId: '25c6d7f5-699a-4bfc-87c9-996f915341fa', + getStatusCode() { + return CONSTANTS.STATUS.NO_BID; + } +}; + +const MOCK = { + AUCTION_INIT: { + 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa', + }, + BID_REQUESTED: { + 'bidder': 'livewrapped', + 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa', + 'bidderRequestId': '1be65d7958826a', + 'bids': [ + { + 'bidder': 'livewrapped', + 'adUnitCode': 'panorama_d_1', + 'bidId': '2ecff0db240757', + }, + { + 'bidder': 'livewrapped', + 'adUnitCode': 'box_d_1', + 'bidId': '3ecff0db240757', + }, + { + 'bidder': 'livewrapped', + 'adUnitCode': 'box_d_2', + 'bidId': '4ecff0db240757', + } + ], + 'start': 1519149562216 + }, + BID_RESPONSE: [ + BID1, + BID2 + ], + AUCTION_END: { + }, + BID_WON: [ + Object.assign({}, BID1, { + 'status': 'rendered', + 'requestId': '2ecff0db240757' + }), + Object.assign({}, BID2, { + 'status': 'rendered', + 'requestId': '3ecff0db240757' + }) + ], + BIDDER_DONE: { + 'bidderCode': 'livewrapped', + 'bids': [ + BID1, + BID2, + BID3 + ] + }, + BID_TIMEOUT: [ + { + 'bidId': '2ecff0db240757', + 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa' + } + ] +}; + +const ANALYTICS_MESSAGE = { + publisherId: 'CC411485-42BC-4F92-8389-42C503EE38D7', + bidAdUnits: [ + { + adUnit: 'panorama_d_1', + timeStamp: 1519149562216 + }, + { + adUnit: 'box_d_1', + timeStamp: 1519149562216 + } + ], + requests: [ + { + adUnit: 'panorama_d_1', + bidder: 'livewrapped', + timeStamp: 1519149562216 + }, + { + adUnit: 'box_d_1', + bidder: 'livewrapped', + timeStamp: 1519149562216 + }, + { + adUnit: 'box_d_2', + bidder: 'livewrapped', + timeStamp: 1519149562216 + } + ], + responses: [ + { + timeStamp: 1519149562216, + adUnit: 'panorama_d_1', + bidder: 'livewrapped', + width: 980, + height: 240, + cpm: 1.1, + ttr: 200, + IsBid: true + }, + { + timeStamp: 1519149562216, + adUnit: 'box_d_1', + bidder: 'livewrapped', + width: 300, + height: 250, + cpm: 2.2, + ttr: 300, + IsBid: true + }, + { + timeStamp: 1519149562216, + adUnit: 'box_d_2', + bidder: 'livewrapped', + ttr: 200, + IsBid: false + } + ], + timeouts: [], + wins: [ + { + timeStamp: 1519149562216, + adUnit: 'panorama_d_1', + bidder: 'livewrapped', + width: 980, + height: 240, + cpm: 1.1 + }, + { + timeStamp: 1519149562216, + adUnit: 'box_d_1', + bidder: 'livewrapped', + width: 300, + height: 250, + cpm: 2.2 + } + ] +}; + +function performStandardAuction() { + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[0]); + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[1]); + events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); + events.emit(AUCTION_END, MOCK.AUCTION_END); + events.emit(SET_TARGETING, MOCK.SET_TARGETING); + events.emit(BID_WON, MOCK.BID_WON[0]); + events.emit(BID_WON, MOCK.BID_WON[1]); +} + +describe('Livewrapped analytics adapter', function () { + let sandbox; + let xhr; + let requests; + let clock; + + beforeEach(function () { + sandbox = sinon.sandbox.create(); + + xhr = sandbox.useFakeXMLHttpRequest(); + requests = []; + xhr.onCreate = request => requests.push(request); + + sandbox.stub(events, 'getEvents').returns([]); + sandbox.stub(utils, 'timestamp').returns(1519149562416); + + clock = sandbox.useFakeTimers(1519767013781); + }); + + afterEach(function () { + sandbox.restore(); + config.resetConfig(); + }); + + describe('when handling events', function () { + adapterManager.registerAnalyticsAdapter({ + code: 'livewrapped', + adapter: livewrappedAnalyticsAdapter + }); + + beforeEach(function () { + adapterManager.enableAnalytics({ + provider: 'livewrapped', + options: { + publisherId: 'CC411485-42BC-4F92-8389-42C503EE38D7' + } + }); + }); + + afterEach(function () { + livewrappedAnalyticsAdapter.disableAnalytics(); + }); + + it('should build a batched message from prebid events', function () { + sandbox.stub(utils, 'getWindowTop').returns({}); + performStandardAuction(); + + clock.tick(BID_WON_TIMEOUT + 1000); + + expect(requests.length).to.equal(1); + let request = requests[0]; + + expect(request.url).to.equal('//lwadm.com/analytics/10'); + + let message = JSON.parse(request.requestBody); + + expect(message).to.deep.equal(ANALYTICS_MESSAGE); + }); + + it('should send batched message without BID_WON if necessary and further BID_WON events individually', function () { + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[0]); + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[1]); + events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); + events.emit(AUCTION_END, MOCK.AUCTION_END); + events.emit(SET_TARGETING, MOCK.SET_TARGETING); + events.emit(BID_WON, MOCK.BID_WON[0]); + + clock.tick(BID_WON_TIMEOUT + 1000); + + events.emit(BID_WON, MOCK.BID_WON[1]); + + expect(requests.length).to.equal(2); + + let message = JSON.parse(requests[0].requestBody); + expect(message.wins.length).to.equal(1); + expect(message.requests).to.deep.equal(ANALYTICS_MESSAGE.requests); + expect(message.wins[0]).to.deep.equal(ANALYTICS_MESSAGE.wins[0]); + + message = JSON.parse(requests[1].requestBody); + expect(message.wins.length).to.equal(1); + expect(message.wins[0]).to.deep.equal(ANALYTICS_MESSAGE.wins[1]); + }); + + it('should properly mark bids as timed out', function () { + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + events.emit(BID_TIMEOUT, MOCK.BID_TIMEOUT); + events.emit(AUCTION_END, MOCK.AUCTION_END); + + clock.tick(BID_WON_TIMEOUT + 1000); + + expect(requests.length).to.equal(1); + + let message = JSON.parse(requests[0].requestBody); + expect(message.timeouts.length).to.equal(1); + expect(message.timeouts[0].bidder).to.equal('livewrapped'); + expect(message.timeouts[0].adUnit).to.equal('panorama_d_1'); + }); + + it('should detect adblocker recovered request', function () { + sandbox.stub(utils, 'getWindowTop').returns({ I12C: { Morph: 1 } }); + performStandardAuction(); + + clock.tick(BID_WON_TIMEOUT + 1000); + + expect(requests.length).to.equal(1); + let request = requests[0]; + + let message = JSON.parse(request.requestBody); + + expect(message.rcv).to.equal(true); + }); + }); +}); diff --git a/test/spec/modules/livewrappedBidAdapter_spec.js b/test/spec/modules/livewrappedBidAdapter_spec.js new file mode 100644 index 00000000000..6db8bff4c3d --- /dev/null +++ b/test/spec/modules/livewrappedBidAdapter_spec.js @@ -0,0 +1,835 @@ +import {expect} from 'chai'; +import {spec} from 'modules/livewrappedBidAdapter'; +import {config} from 'src/config'; +import * as utils from 'src/utils'; + +describe('Livewrapped adapter tests', function () { + let sandbox, + bidderRequest; + + beforeEach(function () { + sandbox = sinon.sandbox.create(); + + bidderRequest = { + bidderCode: 'livewrapped', + auctionId: 'c45dd708-a418-42ec-b8a7-b70a6c6fab0a', + bidderRequestId: '178e34bad3658f', + bids: [ + { + bidder: 'livewrapped', + params: { + adUnitId: '9E153CED-61BC-479E-98DF-24DC0D01BA37', + publisherId: '26947112-2289-405D-88C1-A7340C57E63E', + userId: 'user id', + url: 'http://www.domain.com', + seats: {'dsp': ['seat 1']} + }, + adUnitCode: 'panorama_d_1', + sizes: [[980, 240], [980, 120]], + bidId: '2ffb201a808da7', + bidderRequestId: '178e34bad3658f', + auctionId: 'F7557995-65F5-4682-8782-7D5D34D82A8C', + transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D' + } + ], + start: 1472239426002, + auctionStart: 1472239426000, + timeout: 5000 + }; + }); + + afterEach(function () { + sandbox.restore(); + }); + + describe('isBidRequestValid', function() { + it('should accept a request with id only as valid', function() { + let bid = {params: {adUnitId: '9E153CED-61BC-479E-98DF-24DC0D01BA37'}}; + + let result = spec.isBidRequestValid(bid); + + expect(result).to.be.true; + }); + + it('should accept a request with adUnitName and PublisherId as valid', function() { + let bid = {params: {adUnitName: 'panorama_d_1', publisherId: '26947112-2289-405D-88C1-A7340C57E63E'}}; + + let result = spec.isBidRequestValid(bid); + + expect(result).to.be.true; + }); + + it('should accept a request with adUnitCode and PublisherId as valid', function() { + let bid = {adUnitCode: 'panorama_d_1', params: {publisherId: '26947112-2289-405D-88C1-A7340C57E63E'}}; + + let result = spec.isBidRequestValid(bid); + + expect(result).to.be.true; + }); + + it('should accept a request with placementCode and PublisherId as valid', function() { + let bid = {placementCode: 'panorama_d_1', params: {publisherId: '26947112-2289-405D-88C1-A7340C57E63E'}}; + + let result = spec.isBidRequestValid(bid); + + expect(result).to.be.true; + }); + + it('should not accept a request with adUnitName, adUnitCode, placementCode but no PublisherId as valid', function() { + let bid = {placementCode: 'panorama_d_1', adUnitCode: 'panorama_d_1', params: {adUnitName: 'panorama_d_1'}}; + + let result = spec.isBidRequestValid(bid); + + expect(result).to.be.false; + }); + }); + + describe('buildRequests', function() { + it('should make a well-formed single request object', function() { + sandbox.stub(utils, 'isSafariBrowser').callsFake(() => false); + sandbox.stub(utils, 'cookiesAreEnabled').callsFake(() => true); + let result = spec.buildRequests(bidderRequest.bids, bidderRequest); + let data = JSON.parse(result.data); + + expect(result.url).to.equal('//lwadm.com/ad'); + + let expectedQuery = { + auctionId: 'F7557995-65F5-4682-8782-7D5D34D82A8C', + publisherId: '26947112-2289-405D-88C1-A7340C57E63E', + userId: 'user id', + url: 'http://www.domain.com', + seats: {'dsp': ['seat 1']}, + version: '1.1', + cookieSupport: true, + adRequests: [{ + adUnitId: '9E153CED-61BC-479E-98DF-24DC0D01BA37', + callerAdUnitId: 'panorama_d_1', + bidId: '2ffb201a808da7', + transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D', + formats: [{width: 980, height: 240}, {width: 980, height: 120}] + }] + }; + + expect(data).to.deep.equal(expectedQuery); + }); + + it('should make a well-formed multiple request object', function() { + sandbox.stub(utils, 'isSafariBrowser').callsFake(() => false); + sandbox.stub(utils, 'cookiesAreEnabled').callsFake(() => true); + let multiplebidRequest = clone(bidderRequest); + multiplebidRequest.bids.push(clone(bidderRequest.bids[0])); + multiplebidRequest.bids[1].adUnitCode = 'box_d_1'; + multiplebidRequest.bids[1].sizes = [[300, 250]]; + multiplebidRequest.bids[1].bidId = '3ffb201a808da7'; + delete multiplebidRequest.bids[1].params.adUnitId; + + let result = spec.buildRequests(multiplebidRequest.bids, multiplebidRequest); + let data = JSON.parse(result.data); + + expect(result.url).to.equal('//lwadm.com/ad'); + + let expectedQuery = { + auctionId: 'F7557995-65F5-4682-8782-7D5D34D82A8C', + publisherId: '26947112-2289-405D-88C1-A7340C57E63E', + userId: 'user id', + url: 'http://www.domain.com', + seats: {'dsp': ['seat 1']}, + version: '1.1', + cookieSupport: true, + adRequests: [{ + adUnitId: '9E153CED-61BC-479E-98DF-24DC0D01BA37', + callerAdUnitId: 'panorama_d_1', + bidId: '2ffb201a808da7', + transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D', + formats: [{width: 980, height: 240}, {width: 980, height: 120}] + }, { + callerAdUnitId: 'box_d_1', + bidId: '3ffb201a808da7', + transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D', + formats: [{width: 300, height: 250}] + }] + }; + + expect(data).to.deep.equal(expectedQuery); + }); + + it('should make a well-formed single request object with AdUnitName', function() { + sandbox.stub(utils, 'isSafariBrowser').callsFake(() => false); + sandbox.stub(utils, 'cookiesAreEnabled').callsFake(() => true); + let testbidRequest = clone(bidderRequest); + testbidRequest.bids[0].params.adUnitName = 'caller id 1'; + delete testbidRequest.bids[0].params.adUnitId; + let result = spec.buildRequests(testbidRequest.bids, testbidRequest); + let data = JSON.parse(result.data); + + expect(result.url).to.equal('//lwadm.com/ad'); + + let expectedQuery = { + auctionId: 'F7557995-65F5-4682-8782-7D5D34D82A8C', + publisherId: '26947112-2289-405D-88C1-A7340C57E63E', + userId: 'user id', + url: 'http://www.domain.com', + seats: {'dsp': ['seat 1']}, + version: '1.1', + cookieSupport: true, + adRequests: [{ + callerAdUnitId: 'caller id 1', + bidId: '2ffb201a808da7', + transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D', + formats: [{width: 980, height: 240}, {width: 980, height: 120}] + }] + }; + + expect(data).to.deep.equal(expectedQuery); + }); + + it('should make a well-formed single request object with less parameters', function() { + sandbox.stub(utils, 'isSafariBrowser').callsFake(() => false); + sandbox.stub(utils, 'cookiesAreEnabled').callsFake(() => true); + let testbidRequest = clone(bidderRequest); + delete testbidRequest.bids[0].params.userId; + delete testbidRequest.bids[0].params.seats; + delete testbidRequest.bids[0].params.adUnitId; + let result = spec.buildRequests(testbidRequest.bids, testbidRequest); + let data = JSON.parse(result.data); + + expect(result.url).to.equal('//lwadm.com/ad'); + + let expectedQuery = { + auctionId: 'F7557995-65F5-4682-8782-7D5D34D82A8C', + publisherId: '26947112-2289-405D-88C1-A7340C57E63E', + url: 'http://www.domain.com', + version: '1.1', + cookieSupport: true, + adRequests: [{ + callerAdUnitId: 'panorama_d_1', + bidId: '2ffb201a808da7', + transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D', + formats: [{width: 980, height: 240}, {width: 980, height: 120}] + }] + }; + + expect(data).to.deep.equal(expectedQuery); + }); + + it('should make a well-formed single request object with less parameters, no publisherId', function() { + sandbox.stub(utils, 'isSafariBrowser').callsFake(() => false); + sandbox.stub(utils, 'cookiesAreEnabled').callsFake(() => true); + let testbidRequest = clone(bidderRequest); + delete testbidRequest.bids[0].params.userId; + delete testbidRequest.bids[0].params.seats; + delete testbidRequest.bids[0].params.publisherId; + let result = spec.buildRequests(testbidRequest.bids, testbidRequest); + let data = JSON.parse(result.data); + + expect(result.url).to.equal('//lwadm.com/ad'); + + let expectedQuery = { + auctionId: 'F7557995-65F5-4682-8782-7D5D34D82A8C', + url: 'http://www.domain.com', + version: '1.1', + cookieSupport: true, + adRequests: [{ + adUnitId: '9E153CED-61BC-479E-98DF-24DC0D01BA37', + callerAdUnitId: 'panorama_d_1', + bidId: '2ffb201a808da7', + transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D', + formats: [{width: 980, height: 240}, {width: 980, height: 120}] + }] + }; + + expect(data).to.deep.equal(expectedQuery); + }); + + it('should make a well-formed single request object with app parameters', function() { + sandbox.stub(utils, 'isSafariBrowser').callsFake(() => false); + sandbox.stub(utils, 'cookiesAreEnabled').callsFake(() => true); + let testbidRequest = clone(bidderRequest); + delete testbidRequest.bids[0].params.userId; + delete testbidRequest.bids[0].params.seats; + delete testbidRequest.bids[0].params.adUnitId; + testbidRequest.bids[0].params.deviceId = 'deviceid'; + testbidRequest.bids[0].params.ifa = 'ifa'; + let result = spec.buildRequests(testbidRequest.bids, testbidRequest); + let data = JSON.parse(result.data); + + let expectedQuery = { + auctionId: 'F7557995-65F5-4682-8782-7D5D34D82A8C', + publisherId: '26947112-2289-405D-88C1-A7340C57E63E', + url: 'http://www.domain.com', + version: '1.1', + deviceId: 'deviceid', + ifa: 'ifa', + cookieSupport: true, + adRequests: [{ + callerAdUnitId: 'panorama_d_1', + bidId: '2ffb201a808da7', + transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D', + formats: [{width: 980, height: 240}, {width: 980, height: 120}] + }] + }; + + expect(data).to.deep.equal(expectedQuery); + }); + + it('should make a well-formed single request object with debug parameters', function() { + sandbox.stub(utils, 'isSafariBrowser').callsFake(() => false); + sandbox.stub(utils, 'cookiesAreEnabled').callsFake(() => true); + let testbidRequest = clone(bidderRequest); + delete testbidRequest.bids[0].params.userId; + delete testbidRequest.bids[0].params.seats; + delete testbidRequest.bids[0].params.adUnitId; + testbidRequest.bids[0].params.tid = 'tracking id'; + testbidRequest.bids[0].params.test = true; + let result = spec.buildRequests(testbidRequest.bids, testbidRequest); + let data = JSON.parse(result.data); + + let expectedQuery = { + auctionId: 'F7557995-65F5-4682-8782-7D5D34D82A8C', + publisherId: '26947112-2289-405D-88C1-A7340C57E63E', + url: 'http://www.domain.com', + version: '1.1', + tid: 'tracking id', + test: true, + cookieSupport: true, + adRequests: [{ + callerAdUnitId: 'panorama_d_1', + bidId: '2ffb201a808da7', + transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D', + formats: [{width: 980, height: 240}, {width: 980, height: 120}] + }] + }; + + expect(data).to.deep.equal(expectedQuery); + }); + + it('should make a well-formed single request object with optional parameters', function() { + sandbox.stub(utils, 'isSafariBrowser').callsFake(() => false); + sandbox.stub(utils, 'cookiesAreEnabled').callsFake(() => true); + let testbidRequest = clone(bidderRequest); + delete testbidRequest.bids[0].params.userId; + delete testbidRequest.bids[0].params.seats; + delete testbidRequest.bids[0].params.adUnitId; + testbidRequest.bids[0].params.options = {keyvalues: [{key: 'key', value: 'value'}]}; + let result = spec.buildRequests(testbidRequest.bids, testbidRequest); + let data = JSON.parse(result.data); + + let expectedQuery = { + auctionId: 'F7557995-65F5-4682-8782-7D5D34D82A8C', + publisherId: '26947112-2289-405D-88C1-A7340C57E63E', + url: 'http://www.domain.com', + version: '1.1', + cookieSupport: true, + adRequests: [{ + callerAdUnitId: 'panorama_d_1', + bidId: '2ffb201a808da7', + transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D', + formats: [{width: 980, height: 240}, {width: 980, height: 120}], + options: {keyvalues: [{key: 'key', value: 'value'}]} + }] + }; + + expect(data).to.deep.equal(expectedQuery); + }); + + it('should make a well-formed single request object with ad blocker revovered parameter', function() { + sandbox.stub(utils, 'getWindowTop').returns({ I12C: { Morph: 1 } }); + sandbox.stub(utils, 'isSafariBrowser').callsFake(() => false); + sandbox.stub(utils, 'cookiesAreEnabled').callsFake(() => true); + let testbidRequest = clone(bidderRequest); + delete testbidRequest.bids[0].params.userId; + delete testbidRequest.bids[0].params.seats; + delete testbidRequest.bids[0].params.adUnitId; + let result = spec.buildRequests(testbidRequest.bids, testbidRequest); + let data = JSON.parse(result.data); + + let expectedQuery = { + auctionId: 'F7557995-65F5-4682-8782-7D5D34D82A8C', + publisherId: '26947112-2289-405D-88C1-A7340C57E63E', + url: 'http://www.domain.com', + version: '1.1', + cookieSupport: true, + rcv: true, + adRequests: [{ + callerAdUnitId: 'panorama_d_1', + bidId: '2ffb201a808da7', + transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D', + formats: [{width: 980, height: 240}, {width: 980, height: 120}] + }] + }; + + expect(data).to.deep.equal(expectedQuery); + }); + + it('should pass gdpr true parameters', function() { + sandbox.stub(utils, 'isSafariBrowser').callsFake(() => false); + sandbox.stub(utils, 'cookiesAreEnabled').callsFake(() => true); + let testRequest = clone(bidderRequest); + testRequest.gdprConsent = { + gdprApplies: true, + consentString: 'test' + }; + let result = spec.buildRequests(testRequest.bids, testRequest); + let data = JSON.parse(result.data); + + expect(result.url).to.equal('//lwadm.com/ad'); + + let expectedQuery = { + auctionId: 'F7557995-65F5-4682-8782-7D5D34D82A8C', + publisherId: '26947112-2289-405D-88C1-A7340C57E63E', + userId: 'user id', + url: 'http://www.domain.com', + seats: {'dsp': ['seat 1']}, + version: '1.1', + cookieSupport: true, + gdprApplies: true, + gdprConsent: 'test', + adRequests: [{ + adUnitId: '9E153CED-61BC-479E-98DF-24DC0D01BA37', + callerAdUnitId: 'panorama_d_1', + bidId: '2ffb201a808da7', + transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D', + formats: [{width: 980, height: 240}, {width: 980, height: 120}] + }] + }; + + expect(data).to.deep.equal(expectedQuery); + }); + + it('should pass gdpr false parameters', function() { + sandbox.stub(utils, 'isSafariBrowser').callsFake(() => false); + sandbox.stub(utils, 'cookiesAreEnabled').callsFake(() => true); + let testRequest = clone(bidderRequest); + testRequest.gdprConsent = { + gdprApplies: false + }; + let result = spec.buildRequests(testRequest.bids, testRequest); + let data = JSON.parse(result.data); + + expect(result.url).to.equal('//lwadm.com/ad'); + + let expectedQuery = { + auctionId: 'F7557995-65F5-4682-8782-7D5D34D82A8C', + publisherId: '26947112-2289-405D-88C1-A7340C57E63E', + userId: 'user id', + url: 'http://www.domain.com', + seats: {'dsp': ['seat 1']}, + version: '1.1', + cookieSupport: true, + gdprApplies: false, + adRequests: [{ + adUnitId: '9E153CED-61BC-479E-98DF-24DC0D01BA37', + callerAdUnitId: 'panorama_d_1', + bidId: '2ffb201a808da7', + transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D', + formats: [{width: 980, height: 240}, {width: 980, height: 120}] + }] + }; + + expect(data).to.deep.equal(expectedQuery); + }); + + it('should pass no cookie support', function() { + sandbox.stub(utils, 'cookiesAreEnabled').callsFake(() => false); + sandbox.stub(utils, 'isSafariBrowser').callsFake(() => false); + let result = spec.buildRequests(bidderRequest.bids, bidderRequest); + let data = JSON.parse(result.data); + + expect(result.url).to.equal('//lwadm.com/ad'); + + let expectedQuery = { + auctionId: 'F7557995-65F5-4682-8782-7D5D34D82A8C', + publisherId: '26947112-2289-405D-88C1-A7340C57E63E', + userId: 'user id', + url: 'http://www.domain.com', + seats: {'dsp': ['seat 1']}, + version: '1.1', + cookieSupport: false, + adRequests: [{ + adUnitId: '9E153CED-61BC-479E-98DF-24DC0D01BA37', + callerAdUnitId: 'panorama_d_1', + bidId: '2ffb201a808da7', + transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D', + formats: [{width: 980, height: 240}, {width: 980, height: 120}] + }] + }; + + expect(data).to.deep.equal(expectedQuery); + }); + + it('should pass no cookie support Safari', function() { + sandbox.stub(utils, 'cookiesAreEnabled').callsFake(() => true); + sandbox.stub(utils, 'isSafariBrowser').callsFake(() => true); + let result = spec.buildRequests(bidderRequest.bids, bidderRequest); + let data = JSON.parse(result.data); + + expect(result.url).to.equal('//lwadm.com/ad'); + + let expectedQuery = { + auctionId: 'F7557995-65F5-4682-8782-7D5D34D82A8C', + publisherId: '26947112-2289-405D-88C1-A7340C57E63E', + userId: 'user id', + url: 'http://www.domain.com', + seats: {'dsp': ['seat 1']}, + version: '1.1', + cookieSupport: false, + adRequests: [{ + adUnitId: '9E153CED-61BC-479E-98DF-24DC0D01BA37', + callerAdUnitId: 'panorama_d_1', + bidId: '2ffb201a808da7', + transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D', + formats: [{width: 980, height: 240}, {width: 980, height: 120}] + }] + }; + + expect(data).to.deep.equal(expectedQuery); + }); + + it('should use params.url, then config pageUrl, then getTopWindowUrl', function() { + let testRequest = clone(bidderRequest); + sandbox.stub(utils, 'getTopWindowUrl').callsFake(() => 'http://www.topurl.com'); + + let result = spec.buildRequests(testRequest.bids, testRequest); + let data = JSON.parse(result.data); + + expect(data.url).to.equal('http://www.domain.com'); + + delete testRequest.bids[0].params.url; + + result = spec.buildRequests(testRequest.bids, testRequest); + data = JSON.parse(result.data); + + expect(data.url).to.equal('http://www.topurl.com'); + + let origGetConfig = config.getConfig; + sandbox.stub(config, 'getConfig').callsFake(function (key) { + if (key === 'pageUrl') { + return 'http://www.configurl.com'; + } + return origGetConfig.apply(config, arguments); + }); + + result = spec.buildRequests(testRequest.bids, testRequest); + data = JSON.parse(result.data); + + expect(data.url).to.equal('http://www.configurl.com'); + }); + + it('should make use of pubcid if available', function() { + sandbox.stub(utils, 'isSafariBrowser').callsFake(() => false); + sandbox.stub(utils, 'cookiesAreEnabled').callsFake(() => true); + let testbidRequest = clone(bidderRequest); + delete testbidRequest.bids[0].params.userId; + testbidRequest.bids[0].crumbs = {pubcid: 'pubcid 123'}; + let result = spec.buildRequests(testbidRequest.bids, testbidRequest); + let data = JSON.parse(result.data); + + expect(result.url).to.equal('//lwadm.com/ad'); + + let expectedQuery = { + auctionId: 'F7557995-65F5-4682-8782-7D5D34D82A8C', + publisherId: '26947112-2289-405D-88C1-A7340C57E63E', + userId: 'pubcid 123', + url: 'http://www.domain.com', + seats: {'dsp': ['seat 1']}, + version: '1.1', + cookieSupport: true, + adRequests: [{ + adUnitId: '9E153CED-61BC-479E-98DF-24DC0D01BA37', + callerAdUnitId: 'panorama_d_1', + bidId: '2ffb201a808da7', + transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D', + formats: [{width: 980, height: 240}, {width: 980, height: 120}] + }] + }; + + expect(data).to.deep.equal(expectedQuery); + }); + + it('should make userId take precedence over pubcid', function() { + sandbox.stub(utils, 'isSafariBrowser').callsFake(() => false); + sandbox.stub(utils, 'cookiesAreEnabled').callsFake(() => true); + let testbidRequest = clone(bidderRequest); + testbidRequest.bids[0].crumbs = {pubcid: 'pubcid 123'}; + let result = spec.buildRequests(testbidRequest.bids, testbidRequest); + let data = JSON.parse(result.data); + + expect(result.url).to.equal('//lwadm.com/ad'); + + let expectedQuery = { + auctionId: 'F7557995-65F5-4682-8782-7D5D34D82A8C', + publisherId: '26947112-2289-405D-88C1-A7340C57E63E', + userId: 'user id', + url: 'http://www.domain.com', + seats: {'dsp': ['seat 1']}, + version: '1.1', + cookieSupport: true, + adRequests: [{ + adUnitId: '9E153CED-61BC-479E-98DF-24DC0D01BA37', + callerAdUnitId: 'panorama_d_1', + bidId: '2ffb201a808da7', + transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D', + formats: [{width: 980, height: 240}, {width: 980, height: 120}] + }] + }; + + expect(data).to.deep.equal(expectedQuery); + }); + }); + + it('should make use of Id5-Id if available', function() { + sandbox.stub(utils, 'isSafariBrowser').callsFake(() => false); + sandbox.stub(utils, 'cookiesAreEnabled').callsFake(() => true); + let testbidRequest = clone(bidderRequest); + delete testbidRequest.bids[0].params.userId; + testbidRequest.bids[0].userId = {}; + testbidRequest.bids[0].userId.id5id = '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(utils, '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': 'pubcommon', + 'uids': [{ + 'id': 'publisher-common-id', + 'atype': 1 + }] + }]); + }); + + describe('interpretResponse', function () { + it('should handle single success response', function() { + let lwResponse = { + ads: [ + { + id: '28e5ddf4-3c01-11e8-86a7-0a44794250d4', + callerId: 'site_outsider_0', + tag: 'ad', + width: 300, + height: 250, + cpmBid: 2.565917, + bidId: '32e50fad901ae89', + auctionId: '13e674db-d4d8-4e19-9d28-ff38177db8bf', + creativeId: '52cbd598-2715-4c43-a06f-229fc170f945:427077', + ttl: 120, + meta: undefined + } + ], + currency: 'USD' + }; + + let expectedResponse = [{ + requestId: '32e50fad901ae89', + bidderCode: 'livewrapped', + cpm: 2.565917, + width: 300, + height: 250, + ad: 'ad', + ttl: 120, + creativeId: '52cbd598-2715-4c43-a06f-229fc170f945:427077', + netRevenue: true, + currency: 'USD', + meta: undefined + }]; + + let bids = spec.interpretResponse({body: lwResponse}); + + expect(bids).to.deep.equal(expectedResponse); + }) + + it('should handle multiple success response', function() { + let lwResponse = { + ads: [ + { + id: '28e5ddf4-3c01-11e8-86a7-0a44794250d4', + callerId: 'site_outsider_0', + tag: 'ad1', + width: 300, + height: 250, + cpmBid: 2.565917, + bidId: '32e50fad901ae89', + auctionId: '13e674db-d4d8-4e19-9d28-ff38177db8bf', + creativeId: '52cbd598-2715-4c43-a06f-229fc170f945:427077', + ttl: 120, + meta: undefined + }, + { + id: '38e5ddf4-3c01-11e8-86a7-0a44794250d4', + callerId: 'site_outsider_1', + tag: 'ad2', + width: 980, + height: 240, + cpmBid: 3.565917, + bidId: '42e50fad901ae89', + auctionId: '13e674db-d4d8-4e19-9d28-ff38177db8bf', + creativeId: '62cbd598-2715-4c43-a06f-229fc170f945:427077', + ttl: 120, + meta: undefined + } + ], + currency: 'USD' + }; + + let expectedResponse = [{ + requestId: '32e50fad901ae89', + bidderCode: 'livewrapped', + cpm: 2.565917, + width: 300, + height: 250, + ad: 'ad1', + ttl: 120, + creativeId: '52cbd598-2715-4c43-a06f-229fc170f945:427077', + netRevenue: true, + currency: 'USD', + meta: undefined + }, { + requestId: '42e50fad901ae89', + bidderCode: 'livewrapped', + cpm: 3.565917, + width: 980, + height: 240, + ad: 'ad2', + ttl: 120, + creativeId: '62cbd598-2715-4c43-a06f-229fc170f945:427077', + netRevenue: true, + currency: 'USD', + meta: undefined + }]; + + let bids = spec.interpretResponse({body: lwResponse}); + + expect(bids).to.deep.equal(expectedResponse); + }) + + it('should return meta-data', function() { + let lwResponse = { + ads: [ + { + id: '28e5ddf4-3c01-11e8-86a7-0a44794250d4', + callerId: 'site_outsider_0', + tag: 'ad', + width: 300, + height: 250, + cpmBid: 2.565917, + bidId: '32e50fad901ae89', + auctionId: '13e674db-d4d8-4e19-9d28-ff38177db8bf', + creativeId: '52cbd598-2715-4c43-a06f-229fc170f945:427077', + ttl: 120, + meta: {metadata: 'metadata'} + } + ], + currency: 'USD' + }; + + let expectedResponse = [{ + requestId: '32e50fad901ae89', + bidderCode: 'livewrapped', + cpm: 2.565917, + width: 300, + height: 250, + ad: 'ad', + ttl: 120, + creativeId: '52cbd598-2715-4c43-a06f-229fc170f945:427077', + netRevenue: true, + currency: 'USD', + meta: {metadata: 'metadata'} + }]; + + let bids = spec.interpretResponse({body: lwResponse}); + + expect(bids).to.deep.equal(expectedResponse); + }) + }); + + describe('user sync', function () { + let serverResponses; + + beforeEach(function () { + serverResponses = [{ + body: { + pixels: [ + {type: 'Redirect', url: 'http://pixelsync'}, + {type: 'Iframe', url: 'http://iframesync'} + ] + } + }]; + }); + + it('should return empty if no server responses', function() { + let syncs = spec.getUserSyncs({ + pixelEnabled: true, + iframeEnabled: true + }, []); + + let expectedResponse = []; + + expect(syncs).to.deep.equal(expectedResponse) + }); + + it('should return empty if no user sync', function() { + let syncs = spec.getUserSyncs({ + pixelEnabled: true, + iframeEnabled: true + }, [{body: {}}]); + + let expectedResponse = []; + + expect(syncs).to.deep.equal(expectedResponse) + }); + + it('should returns pixel and iframe user sync', function() { + let syncs = spec.getUserSyncs({ + pixelEnabled: true, + iframeEnabled: true + }, serverResponses); + + let expectedResponse = [{type: 'image', url: 'http://pixelsync'}, {type: 'iframe', url: 'http://iframesync'}]; + + expect(syncs).to.deep.equal(expectedResponse) + }); + + it('should returns pixel only if iframe not supported user sync', function() { + let syncs = spec.getUserSyncs({ + pixelEnabled: true, + iframeEnabled: false + }, serverResponses); + + let expectedResponse = [{type: 'image', url: 'http://pixelsync'}]; + + expect(syncs).to.deep.equal(expectedResponse) + }); + + it('should returns iframe only if pixel not supported user sync', function() { + let syncs = spec.getUserSyncs({ + pixelEnabled: false, + iframeEnabled: true + }, serverResponses); + + let expectedResponse = [{type: 'iframe', url: 'http://iframesync'}]; + + expect(syncs).to.deep.equal(expectedResponse) + }); + }); +}); + +function clone(obj) { + return JSON.parse(JSON.stringify(obj)); +} diff --git a/test/spec/modules/liveyieldAnalyticsAdapter_spec.js b/test/spec/modules/liveyieldAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..36ade5ce67b --- /dev/null +++ b/test/spec/modules/liveyieldAnalyticsAdapter_spec.js @@ -0,0 +1,704 @@ +import CONSTANTS from 'src/constants.json'; +import liveyield from 'modules/liveyieldAnalyticsAdapter'; +import { expect } from 'chai'; +const events = require('src/events'); + +const { + EVENTS: { BID_REQUESTED, BID_TIMEOUT, BID_RESPONSE, BID_WON } +} = CONSTANTS; + +describe('liveyield analytics adapter', function() { + const rtaCalls = []; + + window.rta = function() { + rtaCalls.push({ callArgs: arguments }); + }; + + beforeEach(function() { + sinon.stub(events, 'getEvents').returns([]); + }); + afterEach(function() { + events.getEvents.restore(); + }); + + describe('initialization', function() { + afterEach(function() { + rtaCalls.length = 0; + }); + it('it should require provider', function() { + liveyield.enableAnalytics({}); + expect(rtaCalls).to.be.empty; + }); + it('should require config.options', function() { + liveyield.enableAnalytics({ provider: 'liveyield' }); + expect(rtaCalls).to.be.empty; + }); + it('should require options.customerId', function() { + liveyield.enableAnalytics({ provider: 'liveyield', options: {} }); + expect(rtaCalls).to.be.empty; + }); + it('should require options.customerName', function() { + liveyield.enableAnalytics({ + provider: 'liveyield', + options: { + customerId: 'd6a6f8da-190f-47d6-ae11-f1a4469083fa' + } + }); + expect(rtaCalls).to.be.empty; + }); + it('should require options.customerSite', function() { + liveyield.enableAnalytics({ + provider: 'liveyield', + options: { + customerId: 'd6a6f8da-190f-47d6-ae11-f1a4469083fa', + customerName: 'pubocean' + } + }); + expect(rtaCalls).to.be.empty; + }); + it('should require options.sessionTimezoneOffset', function() { + liveyield.enableAnalytics({ + provider: 'liveyield', + options: { + customerId: 'd6a6f8da-190f-47d6-ae11-f1a4469083fa', + customerName: 'pubocean', + customerSite: 'scribol.com' + } + }); + expect(rtaCalls).to.be.empty; + }); + it("should throw error, when 'rta' function is not defined ", function() { + const keepMe = window.rta; + + delete window.rta; + + liveyield.enableAnalytics({ + provider: 'liveyield', + options: { + customerId: 'd6a6f8da-190f-47d6-ae11-f1a4469083fa', + customerName: 'pubocean', + customerSite: 'scribol.com', + sessionTimezoneOffset: 12 + } + }); + expect(rtaCalls).to.be.empty; + + window.rta = keepMe; + }); + it('should initialize when all required parameters are passed', function() { + liveyield.enableAnalytics({ + provider: 'liveyield', + options: { + customerId: 'd6a6f8da-190f-47d6-ae11-f1a4469083fa', + customerName: 'pubocean', + customerSite: 'scribol.com', + sessionTimezoneOffset: 12 + } + }); + expect(rtaCalls[0].callArgs['0']).to.match(/create/); + expect(rtaCalls[0].callArgs['1']).to.match( + /d6a6f8da-190f-47d6-ae11-f1a4469083fa/ + ); + expect(rtaCalls[0].callArgs['2']).to.match(/pubocean/); + expect(rtaCalls[0].callArgs['4']).to.match(/12/); + liveyield.disableAnalytics(); + }); + it('should allow to redefine rta function name', function() { + const keepMe = window.rta; + window.abc = keepMe; + delete window.rta; + liveyield.enableAnalytics({ + provider: 'liveyield', + options: { + rtaFunctionName: 'abc', + customerId: 'd6a6f8da-190f-47d6-ae11-f1a4469083fa', + customerName: 'test', + customerSite: 'scribol.com', + sessionTimezoneOffset: 25 + } + }); + + liveyield.disableAnalytics(); + expect(rtaCalls[0].callArgs['0']).to.match(/create/); + expect(rtaCalls[0].callArgs['1']).to.match( + /d6a6f8da-190f-47d6-ae11-f1a4469083fa/ + ); + expect(rtaCalls[0].callArgs['2']).to.match(/test/); + expect(rtaCalls[0].callArgs['4']).to.match(/25/); + + window.rta = keepMe; + liveyield.disableAnalytics(); + }); + it('should handle custom parameters', function() { + liveyield.enableAnalytics({ + provider: 'liveyield', + options: { + customerId: 'd6a6f8da-190f-47d6-ae11-f1a4469083fa', + customerName: 'test2', + customerSite: 'scribol.com', + sessionTimezoneOffset: 38, + contentTitle: 'testTitle', + contentAuthor: 'testAuthor', + contentCategory: 'testCategory' + } + }); + + liveyield.disableAnalytics(); + expect(rtaCalls[0].callArgs['0']).to.match(/create/); + expect(rtaCalls[0].callArgs['2']).to.match(/test2/); + expect(rtaCalls[0].callArgs['4']).to.match(/38/); + expect(rtaCalls[0].callArgs['5'].contentTitle).to.match(/testTitle/); + expect(rtaCalls[0].callArgs['5'].contentAuthor).to.match(/testAuthor/); + expect(rtaCalls[0].callArgs['5'].contentCategory).to.match( + /testCategory/ + ); + liveyield.disableAnalytics(); + }); + }); + + describe('handling events', function() { + const options = { + provider: 'liveyield', + options: { + customerId: 'd6a6f8da-190f-47d6-ae11-f1a4469083fa', + customerName: 'pubocean', + customerSite: 'scribol.com', + sessionTimezoneOffset: 12 + } + }; + beforeEach(function() { + rtaCalls.length = 0; + liveyield.enableAnalytics(options); + }); + afterEach(function() { + liveyield.disableAnalytics(); + }); + it('should handle BID_REQUESTED event', function() { + const bidRequest = { + bidderCode: 'appnexus', + bids: [ + { + params: { + placementId: '10433394' + }, + adUnitCode: 'div-gpt-ad-1438287399331-0', + transactionId: '2f481ff1-8d20-4c28-8e36-e384e9e3eec6', + sizes: '300x250,300x600', + bidId: '2eddfdc0c791dc', + auctionId: 'a5b849e5-87d7-4205-8300-d063084fcfb7' + } + ] + }; + + events.emit(BID_REQUESTED, bidRequest); + + expect(rtaCalls[1].callArgs['0']).to.equal('bidRequested'); + expect(rtaCalls[1].callArgs['1']).to.equal('div-gpt-ad-1438287399331-0'); + expect(rtaCalls[1].callArgs['2']).to.equal('appnexus'); + }); + it('should handle BID_REQUESTED event with invalid args', function() { + const bidRequest = { + bids: [ + { + params: { + placementId: '10433394' + }, + transactionId: '2f481ff1-8d20-4c28-8e36-e384e9e3eec6', + sizes: '300x250,300x600', + bidId: '2eddfdc0c791dc', + auctionId: 'a5b849e5-87d7-4205-8300-d063084fcf' + }, + { + params: { + placementId: '31034023' + }, + transactionId: '2f481ff1-8d20-4c28-8e36-e384e9e3eec6', + sizes: '300x250,300x600', + bidId: '3dkg0404fmd0', + auctionId: 'a5b849e5-87d7-4205-8300-d063084fcf' + } + ] + }; + events.emit(BID_REQUESTED, bidRequest); + expect(rtaCalls[1].callArgs['0']).to.equal('bidRequested'); + expect(rtaCalls[1].callArgs['1']).to.equal(undefined); + expect(rtaCalls[1].callArgs['2']).to.equal(undefined); + expect(rtaCalls[1].callArgs['0']).to.equal('bidRequested'); + }); + it('should handle BID_RESPONSE event', function() { + const bidResponse = { + height: 250, + statusMessage: 'Bid available', + adId: '2eddfdc0c791dc', + mediaType: 'banner', + source: 'client', + requestId: '2eddfdc0c791dc', + cpm: 0.5, + creativeId: 29681110, + currency: 'USD', + netRevenue: true, + ttl: 300, + auctionId: 'a5b849e5-87d7-4205-8300-d063084fcfb7', + responseTimestamp: 1522265866110, + requestTimestamp: 1522265863600, + bidder: 'appnexus', + adUnitCode: 'div-gpt-ad-1438287399331-0', + timeToRespond: 2510, + size: '300x250' + }; + + events.emit(BID_RESPONSE, bidResponse); + expect(rtaCalls[1].callArgs['0']).to.equal('addBid'); + expect(rtaCalls[1].callArgs['1']).to.equal('div-gpt-ad-1438287399331-0'); + expect(rtaCalls[1].callArgs['2']).to.equal('appnexus'); + expect(rtaCalls[1].callArgs['3']).to.equal(500); + expect(rtaCalls[1].callArgs['4']).to.equal(false); + expect(rtaCalls[1].callArgs['5']).to.equal(false); + }); + it('should handle BID_RESPONSE event with undefined bidder and cpm', function() { + const bidResponse = { + height: 250, + statusMessage: 'Bid available', + adId: '2eddfdc0c791dc', + mediaType: 'banner', + source: 'client', + requestId: '2eddfdc0c791dc', + creativeId: 29681110, + currency: 'USD', + netRevenue: true, + ttl: 300, + auctionId: 'a5b849e5-87d7-4205-8300-d063084fcfb7', + responseTimestamp: 1522265866110, + requestTimestamp: 1522265863600, + adUnitCode: 'div-gpt-ad-1438287399331-0', + timeToRespond: 2510, + size: '300x250' + }; + events.emit(BID_RESPONSE, bidResponse); + expect(rtaCalls[1].callArgs['0']).to.equal('addBid'); + expect(rtaCalls[1].callArgs['2']).to.equal('unknown'); + expect(rtaCalls[1].callArgs['3']).to.equal(0); + expect(rtaCalls[1].callArgs['4']).to.equal(true); + }); + it('should handle BID_RESPONSE event with undefined status message and adUnitCode', function() { + const bidResponse = { + height: 250, + adId: '2eddfdc0c791dc', + mediaType: 'banner', + source: 'client', + requestId: '2eddfdc0c791dc', + cpm: 0.5, + creativeId: 29681110, + currency: 'USD', + netRevenue: true, + ttl: 300, + auctionId: 'a5b849e5-87d7-4205-8300-d063084fcfb7', + responseTimestamp: 1522265866110, + requestTimestamp: 1522265863600, + bidder: 'appnexus', + timeToRespond: 2510, + size: '300x250' + }; + events.emit(BID_RESPONSE, bidResponse); + expect(rtaCalls[1].callArgs['0']).to.equal('addBid'); + expect(rtaCalls[1].callArgs['1']).to.equal(undefined); + expect(rtaCalls[1].callArgs['3']).to.equal(0); + expect(rtaCalls[1].callArgs['5']).to.equal(true); + }); + it('should handle BID_TIMEOUT', function() { + const bidTimeout = [ + { + bidId: '2baa51527bd015', + bidder: 'bidderOne', + adUnitCode: '/19968336/header-bid-tag-0', + auctionId: '66529d4c-8998-47c2-ab3e-5b953490b98f' + }, + { + bidId: '6fe3b4c2c23092', + bidder: 'bidderTwo', + adUnitCode: '/19968336/header-bid-tag-0', + auctionId: '66529d4c-8998-47c2-ab3e-5b953490b98f' + } + ]; + events.emit(BID_TIMEOUT, bidTimeout); + expect(rtaCalls[1].callArgs['0']).to.equal('biddersTimeout'); + expect(rtaCalls[1].callArgs['1'].length).to.equal(2); + }); + it('should handle BID_WON event', function() { + const bidWon = { + adId: '4587fec4900b81', + mediaType: 'banner', + requestId: '4587fec4900b81', + cpm: 1.962, + creativeId: 2126, + currency: 'EUR', + netRevenue: true, + ttl: 302, + auctionId: '914bedad-b145-4e46-ba58-51365faea6cb', + statusMessage: 'Bid available', + responseTimestamp: 1530628534437, + requestTimestamp: 1530628534219, + bidderCode: 'testbidder4', + adUnitCode: 'div-gpt-ad-1438287399331-0', + timeToRespond: 218, + size: '300x250', + status: 'rendered' + }; + events.emit(BID_WON, bidWon); + expect(rtaCalls[1].callArgs['0']).to.equal('resolveSlot'); + expect(rtaCalls[1].callArgs['1']).to.equal('div-gpt-ad-1438287399331-0'); + expect(rtaCalls[1].callArgs['2'].prebidWon).to.equal(true); + expect(rtaCalls[1].callArgs['2'].prebidPartner).to.equal('testbidder4'); + expect(rtaCalls[1].callArgs['2'].prebidValue).to.equal(1962); + }); + it('should throw error, invoking BID_WON event without adUnitCode', function() { + const bidWon = { + adId: '4587fec4900b81', + mediaType: 'banner', + requestId: '4587fec4900b81', + cpm: 1.962, + creativeId: 2126, + currency: 'EUR', + netRevenue: true, + ttl: 302, + auctionId: '914bedad-b145-4e46-ba58-51365faea6cb', + statusMessage: 'Bid available', + responseTimestamp: 1530628534437, + requestTimestamp: 1530628534219, + timeToRespond: 218, + bidderCode: 'testbidder4', + size: '300x250', + status: 'rendered' + }; + events.emit(BID_WON, bidWon); + expect(rtaCalls[1]).to.be.undefined; + }); + it('should throw error, invoking BID_WON event without bidderCode', function() { + const bidWon = { + adId: '4587fec4900b81', + mediaType: 'banner', + requestId: '4587fec4900b81', + cpm: 1.962, + creativeId: 2126, + currency: 'EUR', + netRevenue: true, + ttl: 302, + auctionId: '914bedad-b145-4e46-ba58-51365faea6cb', + statusMessage: 'Bid available', + responseTimestamp: 1530628534437, + requestTimestamp: 1530628534219, + adUnitCode: 'div-gpt-ad-1438287399331-0', + timeToRespond: 218, + size: '300x250', + status: 'rendered' + }; + events.emit(BID_WON, bidWon); + expect(rtaCalls[1]).to.be.undefined; + }); + }); + + describe('googletag use case', function() { + beforeEach(function() { + rtaCalls.length = 0; + }); + afterEach(function() { + liveyield.disableAnalytics(); + }); + it('should ignore BID_WON events when gpt is provided', function() { + const options = { + provider: 'liveyield', + options: { + customerId: 'd6a6f8da-190f-47d6-ae11-f1a4469083fa', + customerName: 'pubocean', + customerSite: 'scribol.com', + sessionTimezoneOffset: 12, + googlePublisherTag: true, + wireGooglePublisherTag: function() { + return null; + } + } + }; + liveyield.enableAnalytics(options); + + const bidWon = { + adId: 'ignore_me', + mediaType: 'banner', + requestId: '4587fec4900b81', + cpm: 1.962, + creativeId: 2126, + currency: 'EUR', + netRevenue: true, + ttl: 302, + auctionId: '914bedad-b145-4e46-ba58-51365faea6cb', + statusMessage: 'Bid available', + responseTimestamp: 1530628534437, + requestTimestamp: 1530628534219, + bidderCode: 'hello', + adUnitCode: 'div-gpt-ad-1438287399331-0', + timeToRespond: 218, + size: '300x250', + status: 'rendered' + }; + events.emit(BID_WON, bidWon); + + expect(rtaCalls.length).to.equal(1); + }); + it('should subscribe to slotRenderEnded', function() { + var googletag; + var callback; + const options = { + provider: 'liveyield', + options: { + customerId: 'd6a6f8da-190f-47d6-ae11-f1a4469083fa', + customerName: 'pubocean', + customerSite: 'scribol.com', + sessionTimezoneOffset: 12, + googlePublisherTag: 'testGPT', + wireGooglePublisherTag: function(gpt, cb) { + googletag = gpt; + callback = cb; + } + } + }; + liveyield.enableAnalytics(options); + expect(googletag).to.equal('testGPT'); + expect(typeof callback).to.equal('function'); + }); + it('should handle BID_WON event for prebid', function() { + var call; + const slot = { + getResponseInformation: function() { + const dfpInfo = { + dfpAdvertiserId: 1, + dfpLineItemId: 2, + dfpCreativeId: 3 + }; + return dfpInfo; + }, + getTargeting: function(v) { + return ['4587fec4900b81']; + } + }; + const options = { + provider: 'liveyield', + options: { + customerId: 'd6a6f8da-190f-47d6-ae11-f1a4469083fa', + customerName: 'pubocean', + customerSite: 'scribol.com', + sessionTimezoneOffset: 12, + googlePublisherTag: 'testGPT', + wireGooglePublisherTag: function(gpt, cb) { + call = cb; + }, + getHighestPrebidAdImpressionPartner: function(slot, version) { + return 'testbidder4'; + }, + getHighestPrebidAdImpressionValue: function(slot, version) { + return 12; + }, + postProcessResolution: function( + resolution, + slot, + hbPartner, + hbValue, + version + ) { + return resolution; + }, + getAdUnitNameByGooglePublisherTagSlot: function(slot, version) { + return 'testUnit'; + } + } + }; + liveyield.enableAnalytics(options); + const bidWon = { + adId: '4587fec4900b81' + }; + events.emit(BID_WON, bidWon); + call(slot); + expect(rtaCalls[1].callArgs['2'].prebidWon).to.equal(true); + expect(rtaCalls[1].callArgs['2'].prebidPartner).to.equal('testbidder4'); + }); + it('should handle BID_WON event for dfp', function() { + let call; + const slot = { + getResponseInformation: function() { + const dfpInfo = { + dfpAdvertiserId: 1, + dfpLineItemId: 2, + dfpCreativeId: 3 + }; + return dfpInfo; + } + }; + const options = { + provider: 'liveyield', + options: { + customerId: 'd6a6f8da-190f-47d6-ae11-f1a4469083fa', + customerName: 'pubocean', + customerSite: 'scribol.com', + sessionTimezoneOffset: 12, + googlePublisherTag: 'testGPT', + wireGooglePublisherTag: function(gpt, cb) { + call = cb; + }, + getHighestPrebidAdImpressionPartner: function(slot, version) { + return 'testbidder4'; + }, + getHighestPrebidAdImpressionValue: function(slot, version) { + return 12; + }, + postProcessResolution: function(slot, version) { + return { partner: slot.prebidPartner, value: slot.prebidValue }; + }, + getAdUnitNameByGooglePublisherTagSlot: function(slot, version) { + return 'testUnit'; + }, + isPrebidAdImpression: function(slot) { + return true; + } + } + }; + liveyield.enableAnalytics(options); + call(slot); + expect(rtaCalls.length).to.equal(2); + expect(rtaCalls[1].callArgs[0]).to.equal('resolveSlot'); + expect(rtaCalls[1].callArgs[1]).to.equal('testUnit'); + expect(rtaCalls[1].callArgs[2].partner).to.equal('testbidder4'); + expect(rtaCalls[1].callArgs[2].value).to.equal(12000); + }); + it('should work with defaults: prebid won', () => { + let call; + const options = { + provider: 'liveyield', + options: { + customerId: 'd6a6f8da-190f-47d6-ae11-f1a4469083fa', + customerName: 'pubocean', + customerSite: 'scribol.com', + sessionTimezoneOffset: 12, + googlePublisherTag: 'testGPT', + wireGooglePublisherTag: (gpt, cb) => (call = cb), + getAdUnitName: adUnitCode => + adUnitCode === 'PREBID_UNIT' ? 'ADUNIT' : null, + getAdUnitNameByGooglePublisherTagSlot: slot => + slot.getSlotElementId() === 'div-gpt-ad-1438287399331-0' + ? 'ADUNIT' + : null + } + }; + liveyield.enableAnalytics(options); + + const bidResponse = { + adId: 'defaults_with_cache', + statusMessage: 'Bid available', + cpm: 0.5, + bidder: 'appnexus', + adUnitCode: 'PREBID_UNIT' + }; + events.emit(BID_RESPONSE, bidResponse); + + const bidWon = { + adId: bidResponse.adId, + cpm: 1.962, // adjusted, this one shall be used, not the one from bidResponse + bidderCode: 'appnexus_from_bid_won_event', + adUnitCode: 'PREBID_UNIT' + }; + events.emit(BID_WON, bidWon); + + const slot = { + getTargeting: key => (key === 'hb_adid' ? [bidResponse.adId] : []), + getResponseInformation: () => null, + getSlotElementId: () => 'div-gpt-ad-1438287399331-0' + }; + call(slot); + + expect(rtaCalls[2].callArgs[0]).to.equal('resolveSlot'); + expect(rtaCalls[2].callArgs[1]).to.equal('ADUNIT'); + expect(rtaCalls[2].callArgs[2]).to.deep.equal({ + targetings: [], + prebidWon: true, + prebidPartner: 'appnexus_from_bid_won_event', + prebidValue: 1962 + }); + }); + it('should work with defaults: dfp won, prebid bid response', () => { + let call; + const options = { + provider: 'liveyield', + options: { + customerId: 'd6a6f8da-190f-47d6-ae11-f1a4469083fa', + customerName: 'pubocean', + customerSite: 'scribol.com', + sessionTimezoneOffset: 12, + googlePublisherTag: 'testGPT', + wireGooglePublisherTag: (gpt, cb) => (call = cb), + getAdUnitName: adUnitCode => + adUnitCode === 'PREBID_UNIT' ? 'ADUNIT' : null, + getAdUnitNameByGooglePublisherTagSlot: slot => + slot.getSlotElementId() === 'div-gpt-ad-1438287399331-0' + ? 'ADUNIT' + : null + } + }; + liveyield.enableAnalytics(options); + + const bidResponse = { + adId: 'defaults_with_cache_no_prebid_win', + statusMessage: 'Bid available', + cpm: 0.5, + bidder: 'appnexus', + adUnitCode: 'PREBID_UNIT' + }; + events.emit(BID_RESPONSE, bidResponse); + + const slot = { + getTargeting: key => (key === 'hb_adid' ? [bidResponse.adId] : []), + getResponseInformation: () => null, + getSlotElementId: () => 'div-gpt-ad-1438287399331-0' + }; + call(slot); + + expect(rtaCalls[2].callArgs[0]).to.equal('resolveSlot'); + expect(rtaCalls[2].callArgs[1]).to.equal('ADUNIT'); + expect(rtaCalls[2].callArgs[2]).to.deep.equal({ + targetings: [], + prebidPartner: 'appnexus', + prebidValue: 500 + }); + }); + it('should work with defaults: dfp won, without prebid', () => { + let call; + const options = { + provider: 'liveyield', + options: { + customerId: 'd6a6f8da-190f-47d6-ae11-f1a4469083fa', + customerName: 'pubocean', + customerSite: 'scribol.com', + sessionTimezoneOffset: 12, + googlePublisherTag: 'testGPT', + wireGooglePublisherTag: (gpt, cb) => (call = cb), + getAdUnitName: adUnitCode => + adUnitCode === 'PREBID_UNIT' ? 'ADUNIT' : null, + getAdUnitNameByGooglePublisherTagSlot: slot => + slot.getSlotElementId() === 'div-gpt-ad-1438287399331-0' + ? 'ADUNIT' + : null + } + }; + liveyield.enableAnalytics(options); + + const slot = { + getTargeting: key => (key === 'hb_adid' ? ['does-not-exist'] : []), + getResponseInformation: () => null, + getSlotElementId: () => 'div-gpt-ad-1438287399331-0' + }; + call(slot); + + expect(rtaCalls[1].callArgs[0]).to.equal('resolveSlot'); + expect(rtaCalls[1].callArgs[1]).to.equal('ADUNIT'); + expect(rtaCalls[1].callArgs[2]).to.deep.equal({ + targetings: [] + }); + }); + }); +}); diff --git a/test/spec/modules/lkqdBidAdapter_spec.js b/test/spec/modules/lkqdBidAdapter_spec.js index 0cebb2651a9..73a4824f0ef 100644 --- a/test/spec/modules/lkqdBidAdapter_spec.js +++ b/test/spec/modules/lkqdBidAdapter_spec.js @@ -40,7 +40,7 @@ describe('LKQD Bid Adapter Test', function () { }); }); - describe('buildRequests', function () { + describe('buildRequests', () => { const ENDPOINT = 'https://v.lkqd.net/ad'; let bidRequests = [ { @@ -101,14 +101,12 @@ describe('LKQD Bid Adapter Test', function () { expect(requests.length).to.equal(2); const r1 = requests[0].data; expect(r1).to.not.have.property('dnt'); - expect(r1).to.not.have.property('pageurl'); expect(r1).to.not.have.property('contentid'); expect(r1).to.not.have.property('contenttitle'); expect(r1).to.not.have.property('contentlength'); expect(r1).to.not.have.property('contenturl'); const r2 = requests[1].data; expect(r2).to.not.have.property('dnt'); - expect(r2).to.not.have.property('pageurl'); expect(r2).to.not.have.property('contentid'); expect(r2).to.not.have.property('contenttitle'); expect(r2).to.not.have.property('contentlength'); diff --git a/test/spec/modules/lockerdomeBidAdapter_spec.js b/test/spec/modules/lockerdomeBidAdapter_spec.js index 1cd6778b01f..a108b25e2ff 100644 --- a/test/spec/modules/lockerdomeBidAdapter_spec.js +++ b/test/spec/modules/lockerdomeBidAdapter_spec.js @@ -6,7 +6,7 @@ describe('LockerDomeAdapter', function () { const bidRequests = [{ bidder: 'lockerdome', params: { - adUnitId: 10809467961050726 + adUnitId: 'LD10809467961050726' }, mediaTypes: { banner: { @@ -15,14 +15,13 @@ describe('LockerDomeAdapter', function () { }, adUnitCode: 'ad-1', transactionId: 'b55e97d7-792c-46be-95a5-3df40b115734', - sizes: [[300, 250]], bidId: '2652ca954bce9', bidderRequestId: '14a54fade69854', auctionId: 'd4c83108-615d-4c2c-9384-dac9ffd4fd72' }, { bidder: 'lockerdome', params: { - adUnitId: 9434769725128806 + adUnitId: 'LD9434769725128806' }, mediaTypes: { banner: { @@ -31,7 +30,6 @@ describe('LockerDomeAdapter', function () { }, adUnitCode: 'ad-2', transactionId: '73459f05-c482-4706-b2b7-72e6f6264ce6', - sizes: [[300, 600]], bidId: '4510f2834773ce', bidderRequestId: '14a54fade69854', auctionId: 'd4c83108-615d-4c2c-9384-dac9ffd4fd72' @@ -51,29 +49,35 @@ describe('LockerDomeAdapter', function () { describe('buildRequests', function () { it('should generate a valid single POST request for multiple bid requests', function () { - const request = spec.buildRequests(bidRequests); + const bidderRequest = { + refererInfo: { + canonicalUrl: 'https://example.com/canonical', + referer: 'https://example.com' + } + }; + const request = spec.buildRequests(bidRequests, bidderRequest); expect(request.method).to.equal('POST'); expect(request.url).to.equal('https://lockerdome.com/ladbid/prebid'); expect(request.data).to.exist; const requestData = JSON.parse(request.data); - expect(requestData.url).to.equal(utils.getTopWindowLocation().href); - expect(requestData.referrer).to.equal(utils.getTopWindowReferrer()); - const bids = requestData.bidRequests; expect(bids).to.have.lengthOf(2); + expect(requestData.url).to.equal(encodeURIComponent(bidderRequest.refererInfo.canonicalUrl)); + expect(requestData.referrer).to.equal(encodeURIComponent(bidderRequest.refererInfo.referer)); + expect(bids[0].requestId).to.equal('2652ca954bce9'); expect(bids[0].adUnitCode).to.equal('ad-1'); - expect(bids[0].adUnitId).to.equal(10809467961050726); + expect(bids[0].adUnitId).to.equal('LD10809467961050726'); expect(bids[0].sizes).to.have.lengthOf(1); expect(bids[0].sizes[0][0]).to.equal(300); expect(bids[0].sizes[0][1]).to.equal(250); expect(bids[1].requestId).to.equal('4510f2834773ce'); expect(bids[1].adUnitCode).to.equal('ad-2'); - expect(bids[1].adUnitId).to.equal(9434769725128806); + expect(bids[1].adUnitId).to.equal('LD9434769725128806'); expect(bids[1].sizes).to.have.lengthOf(1); expect(bids[1].sizes[0][0]).to.equal(300); expect(bids[1].sizes[0][1]).to.equal(600); @@ -84,6 +88,10 @@ describe('LockerDomeAdapter', function () { gdprConsent: { consentString: 'AAABBB', gdprApplies: true + }, + refererInfo: { + canonicalUrl: 'https://example.com/canonical', + referer: 'https://example.com' } }; const request = spec.buildRequests(bidRequests, bidderRequest); @@ -129,7 +137,14 @@ describe('LockerDomeAdapter', function () { } }; - const request = spec.buildRequests(bidRequests); + const bidderRequest = { + refererInfo: { + canonicalUrl: 'https://example.com/canonical', + referer: 'https://example.com' + } + }; + + const request = spec.buildRequests(bidRequests, bidderRequest); const interpretedResponse = spec.interpretResponse(serverResponse, request); expect(interpretedResponse).to.have.lengthOf(2); diff --git a/test/spec/modules/logicadBidAdapter_spec.js b/test/spec/modules/logicadBidAdapter_spec.js new file mode 100644 index 00000000000..effb7334b69 --- /dev/null +++ b/test/spec/modules/logicadBidAdapter_spec.js @@ -0,0 +1,119 @@ +import {expect} from 'chai'; +import {spec} from '../../../modules/logicadBidAdapter'; +import * as utils from 'src/utils'; + +describe('LogicadAdapter', function () { + const bidRequests = [{ + bidder: 'logicad', + bidId: '51ef8751f9aead', + params: { + tid: 'PJ2P', + page: 'http://www.logicad.com/' + }, + adUnitCode: 'div-gpt-ad-1460505748561-0', + transactionId: 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + sizes: [[300, 250], [300, 600]], + bidderRequestId: '418b37f85e772c', + auctionId: '18fd8b8b0bd757', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + } + }]; + const bidderRequest = { + refererInfo: { + referer: 'fakeReferer', + reachedTop: true, + numIframes: 1, + stack: [] + }, + auctionStart: 1563337198010 + }; + const serverResponse = { + body: { + seatbid: + [{ + bid: { + requestId: '51ef8751f9aead', + cpm: 101.0234, + width: 300, + height: 250, + creativeId: '2019', + currency: 'JPY', + netRevenue: true, + ttl: 60, + ad: '
    TEST
    ' + } + }], + userSync: { + type: 'image', + url: 'https://cr-p31.ladsp.jp/cookiesender/31' + } + } + }; + + describe('isBidRequestValid', function () { + it('should return true if the tid parameter is present', function () { + expect(spec.isBidRequestValid(bidRequests[0])).to.be.true; + }); + + it('should return false if the tid parameter is not present', function () { + let bidRequest = utils.deepClone(bidRequests[0]); + delete bidRequest.params.tid; + expect(spec.isBidRequestValid(bidRequest)).to.be.false; + }); + + it('should return false if the params object is not present', function () { + let bidRequest = utils.deepClone(bidRequests); + delete bidRequest[0].params; + expect(spec.isBidRequestValid(bidRequest)).to.be.false; + }); + }); + + describe('buildRequests', function () { + it('should generate a valid single POST request for multiple bid requests', function () { + const request = spec.buildRequests(bidRequests, bidderRequest)[0]; + expect(request.method).to.equal('POST'); + expect(request.url).to.equal('https://pb.ladsp.com/adrequest/prebid'); + expect(request.data).to.exist; + }); + }); + + describe('interpretResponse', function () { + it('should return an empty array if an invalid response is passed', function () { + const interpretedResponse = spec.interpretResponse({}, {}); + expect(interpretedResponse).to.be.an('array').that.is.empty; + }); + + it('should return valid response when passed valid server response', function () { + const request = spec.buildRequests(bidRequests, bidderRequest)[0]; + const interpretedResponse = spec.interpretResponse(serverResponse, request); + + expect(interpretedResponse).to.have.lengthOf(1); + + expect(interpretedResponse[0].requestId).to.equal(serverResponse.body.seatbid[0].bid.requestId); + expect(interpretedResponse[0].cpm).to.equal(serverResponse.body.seatbid[0].bid.cpm); + expect(interpretedResponse[0].width).to.equal(serverResponse.body.seatbid[0].bid.width); + expect(interpretedResponse[0].height).to.equal(serverResponse.body.seatbid[0].bid.height); + expect(interpretedResponse[0].creativeId).to.equal(serverResponse.body.seatbid[0].bid.creativeId); + expect(interpretedResponse[0].currency).to.equal(serverResponse.body.seatbid[0].bid.currency); + expect(interpretedResponse[0].netRevenue).to.equal(serverResponse.body.seatbid[0].bid.netRevenue); + expect(interpretedResponse[0].ad).to.equal(serverResponse.body.seatbid[0].bid.ad); + expect(interpretedResponse[0].ttl).to.equal(serverResponse.body.seatbid[0].bid.ttl); + }); + }); + + describe('getUserSyncs', function () { + it('should perform usersync', function () { + let syncs = spec.getUserSyncs({pixelEnabled: false}, [serverResponse]); + expect(syncs).to.have.length(0); + + syncs = spec.getUserSyncs({pixelEnabled: true}, [serverResponse]); + expect(syncs).to.have.length(1); + + expect(syncs[0]).to.have.property('type', 'image'); + expect(syncs[0]).to.have.property('url', 'https://cr-p31.ladsp.jp/cookiesender/31'); + }); + }); +}); diff --git a/test/spec/modules/loopmeBidAdapter_spec.js b/test/spec/modules/loopmeBidAdapter_spec.js new file mode 100644 index 00000000000..0d7e71c22db --- /dev/null +++ b/test/spec/modules/loopmeBidAdapter_spec.js @@ -0,0 +1,167 @@ +import { expect } from 'chai'; +import { spec } from '../../../modules/loopmeBidAdapter'; +import * as utils from 'src/utils'; + +describe('LoopMeAdapter', function () { + const bidRequests = [{ + bidder: 'loopme', + params: { + ak: 'b510d5bcda' + }, + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + adUnitCode: 'ad-1', + bidId: '2652ca954bce9' + }, { + bidder: 'loopme', + params: { + ak: 'b510d5bcda' + }, + mediaTypes: { + video: { + playerSize: [[640, 480]], + context: 'outstream' + } + }, + adUnitCode: 'ad-1', + bidId: '2652ca954bce9' + } + ]; + + describe('isBidRequestValid', function () { + it('should return true if the ak parameter is present', function () { + expect(spec.isBidRequestValid(bidRequests[0])).to.be.true; + expect(spec.isBidRequestValid(bidRequests[1])).to.be.true; + }); + + it('should return false if the ak parameter is not present', function () { + let bannerBidRequest = utils.deepClone(bidRequests[0]); + delete bannerBidRequest.params.ak; + expect(spec.isBidRequestValid(bannerBidRequest)).to.be.false; + + let videoBidRequest = utils.deepClone(bidRequests[1]); + delete videoBidRequest.params.ak; + expect(spec.isBidRequestValid(videoBidRequest)).to.be.false; + }); + + it('should return false if the params object is not present', function () { + let bannerBidRequest = utils.deepClone(bidRequests)[0]; + delete bannerBidRequest.params; + expect(spec.isBidRequestValid(bannerBidRequest)).to.be.false; + + let videoBidRequest = utils.deepClone(bidRequests)[1]; + delete videoBidRequest.params; + expect(spec.isBidRequestValid(videoBidRequest)).to.be.false; + }); + }); + + describe('buildRequests', function () { + it('should generate a valid single GET request for multiple bid requests', function () { + const bannerRequest = spec.buildRequests(bidRequests)[0]; + expect(bannerRequest.method).to.equal('GET'); + expect(bannerRequest.url).to.equal('https://loopme.me/api/hb'); + expect(bannerRequest.bidId).to.equal('2652ca954bce9'); + expect(bannerRequest.data).to.exist; + + const bannerRequestData = bannerRequest.data; + expect(bannerRequestData).to.contain('ak=b510d5bcda'); + expect(bannerRequestData).to.contain('sizes=300x250'); + + const videoRequest = spec.buildRequests(bidRequests)[1]; + expect(videoRequest.method).to.equal('GET'); + expect(videoRequest.url).to.equal('https://loopme.me/api/hb'); + expect(videoRequest.bidId).to.equal('2652ca954bce9'); + expect(videoRequest.data).to.exist; + + const videoRquestData = videoRequest.data; + expect(videoRquestData).to.contain('ak=b510d5bcda'); + expect(videoRquestData).to.contain('sizes=640x480'); + expect(videoRquestData).to.contain('media_type=video'); + }); + + it('should add GDPR data to request if available', function () { + const bidderRequest = { + gdprConsent: { + consentString: 'AAABBB' + } + }; + const request = spec.buildRequests(bidRequests, bidderRequest)[0]; + const requestData = request.data; + + expect(requestData).to.contain('user_consent=AAABBB'); + }); + }); + + describe('interpretResponse', function () { + it('should return an empty array if an invalid response is passed', function () { + const interpretedResponse = spec.interpretResponse({}); + expect(interpretedResponse).to.be.an('array').that.is.empty; + }); + + xit('should return valid response when passed valid server response', function () { + const serverResponse = { + body: { + 'requestId': '2652ca954bce9', + 'cpm': 1, + 'width': 480, + 'height': 320, + 'creativeId': '20154', + 'currency': 'USD', + 'netRevenue': false, + 'ttl': 360, + 'ad': `
    Hello
    ` + } + }; + + const request = spec.buildRequests(bidRequests)[0]; + const interpretedResponse = spec.interpretResponse(serverResponse, request); + + expect(interpretedResponse).to.have.lengthOf(1); + + expect(interpretedResponse[0].requestId).to.equal(serverResponse.body.requestId); + expect(interpretedResponse[0].cpm).to.equal(serverResponse.body.cpm); + expect(interpretedResponse[0].width).to.equal(serverResponse.body.width); + expect(interpretedResponse[0].height).to.equal(serverResponse.body.height); + expect(interpretedResponse[0].creativeId).to.equal(serverResponse.body.creativeId); + expect(interpretedResponse[0].currency).to.equal(serverResponse.body.currency); + expect(interpretedResponse[0].netRevenue).to.equal(serverResponse.body.netRevenue); + expect(interpretedResponse[0].ad).to.equal(serverResponse.body.ad); + expect(interpretedResponse[0].ttl).to.equal(serverResponse.body.ttl); + }); + + it('should return valid VAST response when passed valid server response', function () { + const serverResponse = { + body: { + 'requestId': '2652ca954bce9', + 'cpm': 1, + 'width': 640, + 'height': 480, + 'creativeId': '20154', + 'currency': 'USD', + 'netRevenue': false, + 'ttl': 360, + 'vastUrl': 'https://loopme.me/ads/vast?ak=cc885e3acc' + } + }; + + const request = spec.buildRequests(bidRequests)[1]; + const interpretedResponse = spec.interpretResponse(serverResponse, request); + + expect(interpretedResponse).to.have.lengthOf(1); + + expect(interpretedResponse[0].requestId).to.equal(serverResponse.body.requestId); + expect(interpretedResponse[0].cpm).to.equal(serverResponse.body.cpm); + expect(interpretedResponse[0].width).to.equal(serverResponse.body.width); + expect(interpretedResponse[0].height).to.equal(serverResponse.body.height); + expect(interpretedResponse[0].creativeId).to.equal(serverResponse.body.creativeId); + expect(interpretedResponse[0].currency).to.equal(serverResponse.body.currency); + expect(interpretedResponse[0].netRevenue).to.equal(serverResponse.body.netRevenue); + expect(interpretedResponse[0].vastUrl).to.equal(serverResponse.body.vastUrl); + expect(interpretedResponse[0].ttl).to.equal(serverResponse.body.ttl); + expect(interpretedResponse[0].renderer).to.have.property('render'); + }); + }); +}); diff --git a/test/spec/modules/mantisBidAdapter_spec.js b/test/spec/modules/mantisBidAdapter_spec.js index 464cea2aab3..df2c6bb9a13 100644 --- a/test/spec/modules/mantisBidAdapter_spec.js +++ b/test/spec/modules/mantisBidAdapter_spec.js @@ -128,6 +128,81 @@ describe('MantisAdapter', function () { }); describe('interpretResponse', function () { + it('use ad ttl if provided', function () { + let response = { + body: { + ttl: 360, + uuid: 'uuid', + ads: [ + { + bid: 'bid', + cpm: 1, + view: 'view', + width: 300, + ttl: 250, + height: 250, + html: '' + } + ] + } + }; + + let expectedResponse = [ + { + requestId: 'bid', + cpm: 1, + width: 300, + height: 250, + ttl: 250, + ad: '', + creativeId: 'view', + netRevenue: true, + currency: 'USD' + } + ]; + let bidderRequest; + + let result = spec.interpretResponse(response, {bidderRequest}); + expect(result[0]).to.deep.equal(expectedResponse[0]); + }); + + it('use global ttl if provded', function () { + let response = { + body: { + ttl: 360, + uuid: 'uuid', + ads: [ + { + bid: 'bid', + cpm: 1, + view: 'view', + width: 300, + height: 250, + html: '' + } + ] + } + }; + + let expectedResponse = [ + { + requestId: 'bid', + cpm: 1, + width: 300, + height: 250, + ttl: 360, + ad: '', + creativeId: 'view', + netRevenue: true, + currency: 'USD' + } + ]; + let bidderRequest; + + let result = spec.interpretResponse(response, {bidderRequest}); + expect(result[0]).to.deep.equal(expectedResponse[0]); + }); + it('display ads returned', function () { let response = { body: { diff --git a/test/spec/modules/marsmediaBidAdapter_spec.js b/test/spec/modules/marsmediaBidAdapter_spec.js new file mode 100644 index 00000000000..a58857e0f3f --- /dev/null +++ b/test/spec/modules/marsmediaBidAdapter_spec.js @@ -0,0 +1,122 @@ +import { expect } from 'chai' +import { spec, _getPlatform } from 'modules/marsmediaBidAdapter' +import { newBidder } from 'src/adapters/bidderFactory' + +describe('marsmediaBidAdapter', function () { + const adapter = newBidder(spec) + + let bidRequest = { + 'bidId': '123', + 'sizes': [[ 300, 250 ]], + 'params': { + 'publisherID': 9999, + 'floor': 0.1 + } + } + + describe('codes', function () { + it('should return a bidder code of marsmedia', function () { + expect(spec.code).to.equal('marsmedia') + }) + it('should alias mars', function () { + expect(spec.aliases.length > 0 && spec.aliases[0] === 'mars').to.be.true + }) + }) + + describe('isBidRequestValid', function () { + it('should return true if all params present', function () { + expect(spec.isBidRequestValid(bidRequest)).to.be.true + }) + + it('should return false if any parameter missing', function () { + expect(spec.isBidRequestValid(Object.assign(bidRequest, { params: { publisherID: null } }))).to.be.false + }) + }) + + describe('buildRequests', function () { + let req = spec.buildRequests([ bidRequest ], { refererInfo: { } }) + let rdata + + it('should return request object', function () { + expect(req).to.not.be.null + }) + + it('should build request data', function () { + expect(req.data).to.not.be.null + }) + + it('should include one request', function () { + rdata = JSON.parse(req.data) + expect(rdata.imp.length).to.equal(1) + }) + + it('should include all publisher params', function () { + let r = rdata.imp[0] + expect(r.publisherID !== null).to.be.true + }) + }) + + describe('interpretResponse', function () { + let response; + beforeEach(function () { + response = { + body: { + 'id': '37386aade21a71', + 'seatbid': [{ + 'bid': [{ + 'id': '1', + 'impid': '1', + 'cid': '1', + 'price': 0.1, + 'nurl': '', + 'adm': '', + 'w': 320, + 'h': 250 + }] + }] + } + }; + }); + + it('should get the correct bid response', function () { + let expectedResponse = [{ + 'requestId': '37386aade21a71', + 'cpm': 0.1, + 'width': 320, + 'height': 250, + 'creativeId': '1', + 'currency': 'USD', + 'netRevenue': true, + 'ad': ``, + 'ttl': 60 + }]; + + let result = spec.interpretResponse(response); + expect(result[0]).to.deep.equal(expectedResponse[0]); + }); + + it('handles empty bid response', function () { + let response = { + body: '' + }; + let result = spec.interpretResponse(response); + expect(result.length).to.equal(0); + }); + }); + + describe('getUserSyncs', function () { + /* it('should return iframe sync', function () { + let sync = spec.getUserSyncs({ iframeEnabled: true }) + expect(sync.length).to.equal(1) + expect(sync[0].type === 'iframe') + expect(typeof sync[0].url === 'string') + }) + + it('should return pixel sync', function () { + let sync = spec.getUserSyncs({ pixelEnabled: true }) + expect(sync.length).to.equal(1) + expect(sync[0].type === 'image') + expect(typeof sync[0].url === 'string') + }) */ + }) +}) diff --git a/test/spec/modules/meazyBidAdapter_spec.js b/test/spec/modules/meazyBidAdapter_spec.js new file mode 100644 index 00000000000..9abe37ece62 --- /dev/null +++ b/test/spec/modules/meazyBidAdapter_spec.js @@ -0,0 +1,177 @@ +import * as utils from 'src/utils'; +import { expect } from 'chai'; +import { spec } from 'modules/meazyBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; + +const MEAZY_PID = '6910b7344ae566a1' +const VALID_ENDPOINT = `//rtb-filter.meazy.co/pbjs?host=${utils.getOrigin()}&api_key=${MEAZY_PID}`; + +const bidderRequest = { + refererInfo: { + referer: 'page', + stack: ['page', 'page1'] + } +}; + +const bidRequest = { + bidder: 'meazy', + adUnitCode: 'test-div', + sizes: [[300, 250], [300, 600]], + params: { + pid: MEAZY_PID + }, + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', +}; + +const bidContent = { + 'id': '30b31c1838de1e', + 'bidid': '9780a52ff05c0e92780f5baf9cf3f4e8', + 'cur': 'USD', + 'seatbid': [{ + 'bid': [{ + 'id': 'ccf05fb8effb3d02', + 'impid': 'B19C34BBD69DAF9F', + 'burl': 'https://track.meazy.co/imp?bidid=9780a52ff05c0e92780f5baf9cf3f4e8&user=fdc401a2-92f1-42bd-ac22-d570520ad0ec&burl=1&ssp=5&project=2&cost=${AUCTION_PRICE}', + 'adm': '', + 'adid': 'ad-2.6.75.300x250', + 'price': 1.5, + 'w': 300, + 'h': 250, + 'cid': '2.6.75', + 'crid': '2.6.75.300x250', + 'dealid': 'default' + }], + 'seat': '2' + }] +}; + +const bidContentExt = { + ...bidContent, + ext: { + 'syncUrl': '//sync.meazy.co/sync/img?api_key=6910b7344ae566a1' + } +}; + +const bidResponse = { + body: bidContent +}; + +const noBidResponse = { body: {'nbr': 2} }; + +describe('meazyBidAdapter', 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 false', function () { + let bid = Object.assign({}, bidRequest); + bid.params = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return true', function () { + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }); + }); + + describe('buildRequests', function () { + it('should format valid url', function () { + const request = spec.buildRequests([bidRequest], bidderRequest); + expect(request.url).to.equal(VALID_ENDPOINT); + }); + + it('should format valid url', function () { + const request = spec.buildRequests([bidRequest], bidderRequest); + expect(request.url).to.equal(VALID_ENDPOINT); + }); + + it('should format valid request body', function () { + const request = spec.buildRequests([bidRequest], bidderRequest); + const payload = JSON.parse(request.data); + expect(payload.id).to.exist; + expect(payload.imp).to.exist; + expect(payload.imp[0]).to.exist; + expect(payload.imp[0].banner).to.exist; + expect(payload.imp[0].banner.format).to.exist; + expect(payload.device).to.exist; + expect(payload.site).to.exist; + expect(payload.site.domain).to.exist; + expect(payload.cur).to.exist; + }); + + it('should format valid url', function () { + const request = spec.buildRequests([bidRequest], bidderRequest); + expect(request.url).to.equal(VALID_ENDPOINT); + }); + + it('should not fill user.ext object', function () { + const request = spec.buildRequests([bidRequest], bidderRequest); + const payload = JSON.parse(request.data); + expect(payload.user.ext).to.equal(undefined); + }); + + it('should fill user.ext object', function () { + const consentString = 'hellogdpr'; + const request = spec.buildRequests([bidRequest], { ...bidderRequest, gdprConsent: { gdprApplies: true, consentString } }); + const payload = JSON.parse(request.data); + expect(payload.user.ext).to.exist.and.to.be.a('object'); + expect(payload.user.ext.consent).to.equal(consentString); + expect(payload.user.ext.gdpr).to.equal(1); + }); + }); + + describe('interpretResponse', function () { + it('should get correct bid response', function () { + const result = spec.interpretResponse(bidResponse); + const validResponse = [{ + requestId: '30b31c1838de1e', + cpm: 1.5, + width: 300, + height: 250, + creativeId: '2.6.75.300x250', + netRevenue: true, + dealId: 'default', + currency: 'USD', + ttl: 900, + ad: '' + }]; + + expect(result).to.deep.equal(validResponse); + }); + + it('handles nobid responses', function () { + let result = spec.interpretResponse(noBidResponse); + expect(result.length).to.equal(0); + }); + }); + + describe('getUserSyncs', function () { + const syncOptionsFF = { iframeEnabled: false }; + const syncOptionsEF = { iframeEnabled: true }; + const syncOptionsEE = { pixelEnabled: true, iframeEnabled: true }; + const syncOptionsFE = { pixelEnabled: true, iframeEnabled: false }; + + const successIFrame = { type: 'iframe', url: '//sync.meazy.co/sync/iframe' }; + const successPixel = { type: 'image', url: '//sync.meazy.co/sync/img?api_key=6910b7344ae566a1' }; + + it('should return an empty array', function () { + expect(spec.getUserSyncs(syncOptionsFF, [])).to.be.empty; + expect(spec.getUserSyncs(syncOptionsFF, [ bidResponse ])).to.be.empty; + expect(spec.getUserSyncs(syncOptionsFE, [ bidResponse ])).to.be.empty; + }); + + it('should be equal to the expected result', function () { + expect(spec.getUserSyncs(syncOptionsEF, [ bidResponse ])).to.deep.equal([successIFrame]); + expect(spec.getUserSyncs(syncOptionsFE, [ { body: bidContentExt } ])).to.deep.equal([successPixel]); + expect(spec.getUserSyncs(syncOptionsEE, [ { body: bidContentExt } ])).to.deep.equal([successPixel, successIFrame]); + expect(spec.getUserSyncs(syncOptionsEE, [])).to.deep.equal([successIFrame]); + }) + }); +}); diff --git a/test/spec/modules/medianetBidAdapter_spec.js b/test/spec/modules/medianetBidAdapter_spec.js index bb55ed99e02..336a86a7793 100644 --- a/test/spec/modules/medianetBidAdapter_spec.js +++ b/test/spec/modules/medianetBidAdapter_spec.js @@ -73,6 +73,101 @@ let VALID_BID_REQUEST = [{ 'auctionId': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', 'bidRequestsCount': 1 }], + VALID_NATIVE_BID_REQUEST = [{ + 'bidder': 'medianet', + 'params': { + 'cid': 'customer_id', + 'site': { + 'page': 'http://media.net/prebidtest', + 'domain': 'media.net', + 'ref': 'http://media.net/prebidtest' + } + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': '277b631f-92f5-4844-8b19-ea13c095d3f1', + 'sizes': [[300, 250]], + 'bidId': '28f8f8130a583e', + 'bidderRequestId': '1e9b1f07797c1c', + 'auctionId': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', + 'bidRequestsCount': 1, + 'nativeParams': { + 'image': { + 'required': true, + 'sizes': [ + 150, + 50 + ], + 'wmin': 50 + }, + 'title': { + 'required': true, + 'len': 80 + }, + 'sponsoredBy': { + 'required': true + }, + 'clickUrl': { + 'required': true + }, + 'body': { + 'required': true + }, + 'icon': { + 'required': true, + 'sizes': [ + 50, + 50 + ] + } + } + }, { + 'bidder': 'medianet', + 'params': { + 'cid': 'customer_id', + 'site': { + 'page': 'http://media.net/prebidtest', + 'domain': 'media.net', + 'ref': 'http://media.net/prebidtest' + } + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-123', + 'transactionId': 'c52a5c62-3c2b-4b90-9ff8-ec1487754822', + 'sizes': [[300, 251]], + 'bidId': '3f97ca71b1e5c2', + 'bidderRequestId': '1e9b1f07797c1c', + 'auctionId': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', + 'bidRequestsCount': 1, + 'nativeParams': { + 'image': { + 'required': true, + 'sizes': [ + 150, + 50 + ], + 'wmin': 50 + }, + 'title': { + 'required': true, + 'len': 80 + }, + 'sponsoredBy': { + 'required': true + }, + 'clickUrl': { + 'required': true + }, + 'body': { + 'required': true + }, + 'icon': { + 'required': true, + 'sizes': [ + 50, + 50 + ] + } + } + }], VALID_AUCTIONDATA = { 'timeout': config.getConfig('bidderTimeout'), }, @@ -156,6 +251,87 @@ let VALID_BID_REQUEST = [{ }], 'tmax': config.getConfig('bidderTimeout') }, + VALID_PAYLOAD_NATIVE = { + 'site': { + 'page': 'http://media.net/prebidtest', + 'domain': 'media.net', + 'ref': 'http://media.net/prebidtest' + }, + 'ext': { + 'customer_id': 'customer_id', + 'prebid_version': $$PREBID_GLOBAL$$.version, + 'gdpr_applies': false, + 'screen': { + 'w': 1000, + 'h': 1000 + } + }, + 'id': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', + 'imp': [{ + 'id': '28f8f8130a583e', + 'ext': { + 'dfp_id': 'div-gpt-ad-1460505748561-0', + 'visibility': 1, + 'viewability': 1, + 'coordinates': { + 'top_left': { + x: 50, + y: 50 + }, + 'bottom_right': { + x: 100, + y: 100 + } + }, + 'display_count': 1 + }, + 'banner': [{ + 'w': 300, + 'h': 250 + }], + 'native': '{\"image\":{\"required\":true,\"sizes\":[150,50],\"wmin\":50},\"title\":{\"required\":true,\"len\":80},\"sponsoredBy\":{\"required\":true},\"clickUrl\":{\"required\":true},\"body\":{\"required\":true},\"icon\":{\"required\":true,\"sizes\":[50,50]}}', + 'all': { + 'cid': 'customer_id', + 'site': { + 'page': 'http://media.net/prebidtest', + 'domain': 'media.net', + 'ref': 'http://media.net/prebidtest' + } + } + }, { + 'id': '3f97ca71b1e5c2', + 'ext': { + 'dfp_id': 'div-gpt-ad-1460505748561-123', + 'visibility': 1, + 'viewability': 1, + 'coordinates': { + 'top_left': { + x: 50, + y: 50 + }, + 'bottom_right': { + x: 100, + y: 100 + } + }, + 'display_count': 1 + }, + 'banner': [{ + 'w': 300, + 'h': 251 + }], + 'native': '{\"image\":{\"required\":true,\"sizes\":[150,50],\"wmin\":50},\"title\":{\"required\":true,\"len\":80},\"sponsoredBy\":{\"required\":true},\"clickUrl\":{\"required\":true},\"body\":{\"required\":true},\"icon\":{\"required\":true,\"sizes\":[50,50]}}', + 'all': { + 'cid': 'customer_id', + 'site': { + 'page': 'http://media.net/prebidtest', + 'domain': 'media.net', + 'ref': 'http://media.net/prebidtest' + } + } + }], + 'tmax': config.getConfig('bidderTimeout') + }, VALID_PAYLOAD = { 'site': { 'page': 'http://media.net/prebidtest', @@ -562,8 +738,16 @@ describe('Media.net bid adapter', function () { expect(JSON.parse(bidReq.data)).to.deep.equal(VALID_PAYLOAD_FOR_GDPR); }); - describe('build requests: when page meta-data is available', function () { - it('should pass canonical, twitter and fb paramters if available', function () { + it('should parse params for native request', function () { + let bidReq = spec.buildRequests(VALID_NATIVE_BID_REQUEST, VALID_AUCTIONDATA); + expect(JSON.parse(bidReq.data)).to.deep.equal(VALID_PAYLOAD_NATIVE); + }); + + describe('build requests: when page meta-data is available', () => { + beforeEach(() => { + spec.clearMnData(); + }); + it('should pass canonical, twitter and fb paramters if available', () => { let documentStub = sandbox.stub(window.top.document, 'querySelector'); documentStub.withArgs('link[rel="canonical"]').returns({ href: 'http://localhost:9999/canonical-test' diff --git a/test/spec/modules/mgidBidAdapter_spec.js b/test/spec/modules/mgidBidAdapter_spec.js new file mode 100644 index 00000000000..4a9f25a29a7 --- /dev/null +++ b/test/spec/modules/mgidBidAdapter_spec.js @@ -0,0 +1,697 @@ +import {assert, expect} from 'chai'; +import {spec} from 'modules/mgidBidAdapter'; +import * as utils from '../../../src/utils'; +import * as urlUtils from '../../../src/url'; + +describe('Mgid bid adapter', function () { + let sandbox; + let logErrorSpy; + let logWarnSpy; + beforeEach(function () { + sandbox = sinon.sandbox.create(); + logErrorSpy = sinon.spy(utils, 'logError'); + logWarnSpy = sinon.spy(utils, 'logWarn'); + }); + + afterEach(function () { + sandbox.restore(); + utils.logError.restore(); + utils.logWarn.restore(); + }); + const ua = navigator.userAgent; + const screenHeight = screen.height; + const screenWidth = screen.width; + const dnt = (navigator.doNotTrack == 'yes' || navigator.doNotTrack == '1' || navigator.msDoNotTrack == '1') ? 1 : 0; + const language = navigator.language ? 'language' : 'userLanguage'; + let lang = navigator[language].split('-')[0]; + if (lang.length != 2 && lang.length != 3) { + lang = ''; + } + const secure = window.location.protocol === 'https:' ? 1 : 0; + const mgid_ver = spec.VERSION; + const prebid_ver = $$PREBID_GLOBAL$$.version; + const utcOffset = (new Date()).getTimezoneOffset().toString(); + + describe('isBidRequestValid', function () { + let bid = { + 'adUnitCode': 'div', + 'bidder': 'mgid', + 'params': { + 'property': '10433394', + 'zone': 'zone' + }, + }; + + it('should not accept bid without required params', function () { + let isValid = spec.isBidRequestValid(bid); + expect(isValid).to.equal(false); + }); + + it('should return false when params are not passed', function () { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when valid params are not passed', function () { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = {accountId: '', placementId: ''}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when valid params are not passed', function () { + let bid = Object.assign({}, bid); + delete bid.params; + bid.adUnitCode = ''; + bid.mediaTypes = { + banner: { + sizes: [[300, 250]] + } + }; + bid.params = {accountId: 2, placementId: 1}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when adUnitCode not passed', function () { + let bid = Object.assign({}, bid); + delete bid.params; + bid.adUnitCode = ''; + bid.mediaTypes = { + banner: { + sizes: [[300, 250]] + } + }; + bid.params = {accountId: 2, placementId: 1}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return true when valid params are passed as nums', function () { + let bid = Object.assign({}, bid); + delete bid.params; + bid.adUnitCode = 'div'; + bid.mediaTypes = { + banner: { + sizes: [[300, 250]] + } + }; + bid.params = {accountId: 2, placementId: 1}; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when valid params are not passed', function () { + let bid = Object.assign({}, bid); + delete bid.params; + bid.mediaTypes = { + native: { + sizes: [[300, 250]] + } + }; + bid.params = {accountId: '0', placementId: '00'}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when valid mediaTypes are not passed', function () { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = {accountId: '1', placementId: '1'}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when valid mediaTypes.banner are not passed', function () { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = {accountId: '1', placementId: '1'}; + bid.mediaTypes = { + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when valid mediaTypes.banner.sizes are not passed', function () { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = {accountId: '1', placementId: '1'}; + bid.mediaTypes = { + sizes: [] + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when valid mediaTypes.banner.sizes are not valid', function () { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = {accountId: '1', placementId: '1'}; + bid.mediaTypes = { + sizes: [300, 250] + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return true when valid params are passed as strings', function () { + let bid = Object.assign({}, bid); + delete bid.params; + bid.adUnitCode = 'div'; + bid.params = {accountId: '1', placementId: '1'}; + bid.mediaTypes = { + banner: { + sizes: [[300, 250]] + } + }; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when valid mediaTypes.native is not object', function () { + let bid = Object.assign({}, bid); + bid.params = {accountId: '1', placementId: '1'}; + bid.mediaTypes = { + native: [] + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when mediaTypes.native is empty object', function () { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = {accountId: '1', placementId: '1'}; + bid.mediaTypes = { + native: {} + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when mediaTypes.native is invalid object', function () { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = {accountId: '1', placementId: '1'}; + bid.mediaTypes = { + native: { + image: { + sizes: [80, 80] + }, + } + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when mediaTypes.native has unsupported required asset', function () { + let bid = Object.assign({}, bid); + bid.params = {accountId: '2', placementId: '1'}; + bid.mediaTypes = { + native: { + title: {required: true}, + image: {required: false, sizes: [80, 80]}, + sponsored: {required: false}, + }, + }; + bid.nativeParams = { + title: {required: true}, + image: {required: false, sizes: [80, 80]}, + sponsored: {required: false}, + unsupported: {required: true}, + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return true when mediaTypes.native all assets needed', function () { + let bid = Object.assign({}, bid); + bid.adUnitCode = 'div'; + bid.params = {accountId: '2', placementId: '1'}; + bid.mediaTypes = { + native: { + title: {required: true}, + image: {required: false, sizes: [80, 80]}, + sponsored: {required: false}, + }, + }; + bid.nativeParams = { + title: {required: true}, + image: {required: false, sizes: [80, 80]}, + sponsored: {required: false}, + }; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + }); + + describe('override defaults', function () { + let bid = { + bidder: 'mgid', + params: { + accountId: '1', + placementId: '2', + }, + }; + it('should return object', function () { + let bid = Object.assign({}, bid); + bid.mediaTypes = { + banner: { + sizes: [[300, 250]] + } + }; + let bidRequests = [bid]; + const request = spec.buildRequests(bidRequests); + expect(request).to.exist.and.to.be.a('object'); + }); + + it('should return overwrite default bidurl', function () { + let bid = Object.assign({}, bid); + bid.params = { + bidUrl: '//newbidurl.com/', + accountId: '1', + placementId: '2', + }; + bid.mediaTypes = { + banner: { + sizes: [[300, 250]] + } + }; + let bidRequests = [bid]; + const request = spec.buildRequests(bidRequests); + expect(request.url).to.include('//newbidurl.com/1'); + }); + it('should return overwrite default bidFloor', function () { + let bid = Object.assign({}, bid); + bid.params = { + bidFloor: 1.1, + accountId: '1', + placementId: '2', + }; + bid.mediaTypes = { + banner: { + sizes: [[300, 250]] + } + }; + let bidRequests = [bid]; + const request = spec.buildRequests(bidRequests); + expect(request.data).to.be.a('string'); + const data = JSON.parse(request.data); + expect(data).to.be.a('object'); + expect(data.imp).to.be.a('array'); + expect(data.imp).to.have.lengthOf(1); + expect(data.imp[0].bidfloor).to.deep.equal(1.1); + }); + it('should return overwrite default currency', function () { + let bid = Object.assign({}, bid); + bid.params = { + cur: 'GBP', + accountId: '1', + placementId: '2', + }; + bid.mediaTypes = { + banner: { + sizes: [[300, 250]] + } + }; + let bidRequests = [bid]; + const request = spec.buildRequests(bidRequests); + expect(request.data).to.be.a('string'); + const data = JSON.parse(request.data); + expect(data).to.be.a('object'); + expect(data.cur).to.deep.equal(['GBP']); + }); + }); + + describe('buildRequests', function () { + it('should return undefined if no validBidRequests passed', function () { + expect(spec.buildRequests([])).to.be.undefined; + }); + + let abid = { + adUnitCode: 'div', + bidder: 'mgid', + params: { + accountId: '1', + placementId: '2', + }, + }; + it('should return proper request url', function () { + localStorage.setItem('mgMuidn', 'xxx'); + let bid = Object.assign({}, abid); + bid.mediaTypes = { + banner: { + sizes: [[300, 250]] + } + }; + let bidRequests = [bid]; + const request = spec.buildRequests(bidRequests); + expect(request.url).deep.equal('https://prebid.mgid.com/prebid/1?muid=xxx'); + localStorage.removeItem('mgMuidn') + }); + it('should proper handle gdpr', function () { + let bid = Object.assign({}, abid); + bid.mediaTypes = { + banner: { + sizes: [[300, 250]] + } + }; + let bidRequests = [bid]; + const request = spec.buildRequests(bidRequests, {gdprConsent: {consentString: 'gdpr', gdprApplies: true}}); + expect(request.url).deep.equal('https://prebid.mgid.com/prebid/1'); + expect(request.method).deep.equal('POST'); + const data = JSON.parse(request.data); + expect(data.user).deep.equal({ext: {consent: 'gdpr'}}); + expect(data.regs).deep.equal({ext: {gdpr: 1}}); + }); + it('should return proper banner imp', function () { + let bid = Object.assign({}, abid); + bid.mediaTypes = { + banner: { + sizes: [[300, 250]] + } + }; + let bidRequests = [bid]; + const page = top.location.href; + const domain = urlUtils.parse(page).hostname; + const request = spec.buildRequests(bidRequests); + expect(request.url).deep.equal('https://prebid.mgid.com/prebid/1'); + expect(request.method).deep.equal('POST'); + const data = JSON.parse(request.data); + expect(data.site.domain).to.deep.equal(domain); + expect(data.site.page).to.deep.equal(page); + expect(data.cur).to.deep.equal(['USD']); + expect(data.device.ua).to.deep.equal(ua); + expect(data.device.dnt).equal(dnt); + expect(data.device.h).equal(screenHeight); + expect(data.device.w).equal(screenWidth); + expect(data.device.language).to.deep.equal(lang); + expect(data.imp[0].tagid).to.deep.equal('2/div'); + expect(data.imp[0].banner).to.deep.equal({w: 300, h: 250}); + expect(data.imp[0].secure).to.deep.equal(secure); + expect(request).to.deep.equal({ + 'method': 'POST', + 'url': 'https://prebid.mgid.com/prebid/1', + 'data': '{\"site\":{\"domain\":\"' + domain + '\",\"page\":\"' + page + '\"},\"cur\":[\"USD\"],\"geo\":{\"utcoffset\":' + utcOffset + '},\"device\":{\"ua\":\"' + ua + '\",\"js\":1,\"dnt\":' + dnt + ',\"h\":' + screenHeight + ',\"w\":' + screenWidth + ',\"language\":\"' + lang + '\"},\"ext\":{\"mgid_ver\":\"' + mgid_ver + '\",\"prebid_ver\":\"' + prebid_ver + '\"},\"imp\":[{\"tagid\":\"2/div\",\"secure\":' + secure + ',\"banner\":{\"w\":300,\"h\":250}}]}', + }); + }); + it('should not return native imp if minimum asset list not requested', function () { + let bid = Object.assign({}, abid); + bid.mediaTypes = { + native: '', + }; + bid.nativeParams = { + title: {required: true}, + image: {sizes: [80, 80]}, + }; + let bidRequests = [bid]; + const request = spec.buildRequests(bidRequests); + expect(request).to.be.undefined; + }); + it('should return proper native imp', function () { + let bid = Object.assign({}, abid); + bid.mediaTypes = { + native: '', + }; + bid.nativeParams = { + title: {required: true}, + image: {sizes: [80, 80]}, + sponsored: { }, + }; + + let bidRequests = [bid]; + const page = top.location.href; + const domain = urlUtils.parse(page).hostname; + const request = spec.buildRequests(bidRequests); + expect(request).to.be.a('object'); + expect(request.url).deep.equal('https://prebid.mgid.com/prebid/1'); + expect(request.method).deep.equal('POST'); + const data = JSON.parse(request.data); + expect(data.site.domain).to.deep.equal(domain); + expect(data.site.page).to.deep.equal(page); + expect(data.cur).to.deep.equal(['USD']); + expect(data.device.ua).to.deep.equal(ua); + expect(data.device.dnt).equal(dnt); + expect(data.device.h).equal(screenHeight); + expect(data.device.w).equal(screenWidth); + expect(data.device.language).to.deep.equal(lang); + expect(data.imp[0].tagid).to.deep.equal('2/div'); + expect(data.imp[0].native).is.a('object').and.to.deep.equal({'request': {'assets': [{'id': 1, 'required': 1, 'title': {'len': 80}}, {'id': 2, 'img': {'h': 80, 'type': 3, 'w': 80}, 'required': 0}, {'data': {'type': 1}, 'id': 11, 'required': 0}], 'plcmtcnt': 1}}); + expect(data.imp[0].secure).to.deep.equal(secure); + expect(request).to.deep.equal({ + 'method': 'POST', + 'url': 'https://prebid.mgid.com/prebid/1', + 'data': '{\"site\":{\"domain\":\"' + domain + '\",\"page\":\"' + page + '\"},\"cur\":[\"USD\"],\"geo\":{\"utcoffset\":' + utcOffset + '},\"device\":{\"ua\":\"' + ua + '\",\"js\":1,\"dnt\":' + dnt + ',\"h\":' + screenHeight + ',\"w\":' + screenWidth + ',\"language\":\"' + lang + '\"},\"ext\":{\"mgid_ver\":\"' + mgid_ver + '\",\"prebid_ver\":\"' + prebid_ver + '\"},\"imp\":[{\"tagid\":\"2/div\",\"secure\":' + secure + ',\"native\":{\"request\":{\"plcmtcnt\":1,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":80}},{\"id\":2,\"required\":0,\"img\":{\"type\":3,\"w\":80,\"h\":80}},{\"id\":11,\"required\":0,\"data\":{\"type\":1}}]}}}]}', + }); + }); + it('should return proper native imp', function () { + let bid = Object.assign({}, abid); + bid.mediaTypes = { + native: '', + }; + bid.nativeParams = { + title: {required: true}, + image: {wmin: 50, hmin: 50, required: true}, + icon: {}, + sponsored: { }, + }; + + let bidRequests = [bid]; + const page = top.location.href; + const domain = urlUtils.parse(page).hostname; + const request = spec.buildRequests(bidRequests); + expect(request).to.be.a('object'); + expect(request.url).deep.equal('https://prebid.mgid.com/prebid/1'); + expect(request.method).deep.equal('POST'); + const data = JSON.parse(request.data); + expect(data.site.domain).to.deep.equal(domain); + expect(data.site.page).to.deep.equal(page); + expect(data.cur).to.deep.equal(['USD']); + expect(data.device.ua).to.deep.equal(ua); + expect(data.device.dnt).equal(dnt); + expect(data.device.h).equal(screenHeight); + expect(data.device.w).equal(screenWidth); + expect(data.device.language).to.deep.equal(lang); + expect(data.imp[0].tagid).to.deep.equal('2/div'); + expect(data.imp[0].native).is.a('object').and.to.deep.equal({'request': {'assets': [{'id': 1, 'required': 1, 'title': {'len': 80}}, {'id': 2, 'img': {'h': 328, hmin: 50, 'type': 3, 'w': 492, wmin: 50}, 'required': 1}, {'id': 3, 'img': {'h': 50, 'type': 1, 'w': 50}, 'required': 0}, {'data': {'type': 1}, 'id': 11, 'required': 0}], 'plcmtcnt': 1}}); + expect(data.imp[0].secure).to.deep.equal(secure); + expect(request).to.deep.equal({ + 'method': 'POST', + 'url': 'https://prebid.mgid.com/prebid/1', + 'data': '{\"site\":{\"domain\":\"' + domain + '\",\"page\":\"' + page + '\"},\"cur\":[\"USD\"],\"geo\":{\"utcoffset\":' + utcOffset + '},\"device\":{\"ua\":\"' + ua + '\",\"js\":1,\"dnt\":' + dnt + ',\"h\":' + screenHeight + ',\"w\":' + screenWidth + ',\"language\":\"' + lang + '\"},\"ext\":{\"mgid_ver\":\"' + mgid_ver + '\",\"prebid_ver\":\"' + prebid_ver + '\"},\"imp\":[{\"tagid\":\"2/div\",\"secure\":' + secure + ',\"native\":{\"request\":{\"plcmtcnt\":1,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":80}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"w\":492,\"h\":328,\"wmin\":50,\"hmin\":50}},{\"id\":3,\"required\":0,\"img\":{\"type\":1,\"w\":50,\"h\":50}},{\"id\":11,\"required\":0,\"data\":{\"type\":1}}]}}}]}', + }); + }); + it('should return proper native imp with sponsoredBy', function () { + let bid = Object.assign({}, abid); + bid.mediaTypes = { + native: '', + }; + bid.nativeParams = { + title: {required: true}, + image: {sizes: [80, 80]}, + sponsoredBy: { }, + }; + + let bidRequests = [bid]; + const page = top.location.href; + const domain = urlUtils.parse(page).hostname; + const request = spec.buildRequests(bidRequests); + expect(request).to.be.a('object'); + expect(request.url).deep.equal('https://prebid.mgid.com/prebid/1'); + expect(request.method).deep.equal('POST'); + const data = JSON.parse(request.data); + expect(data.site.domain).to.deep.equal(domain); + expect(data.site.page).to.deep.equal(page); + expect(data.cur).to.deep.equal(['USD']); + expect(data.device.ua).to.deep.equal(ua); + expect(data.device.dnt).equal(dnt); + expect(data.device.h).equal(screenHeight); + expect(data.device.w).equal(screenWidth); + expect(data.device.language).to.deep.equal(lang); + expect(data.imp[0].tagid).to.deep.equal('2/div'); + expect(data.imp[0].native).is.a('object').and.to.deep.equal({'request': {'assets': [{'id': 1, 'required': 1, 'title': {'len': 80}}, {'id': 2, 'img': {'h': 80, 'type': 3, 'w': 80}, 'required': 0}, {'data': {'type': 1}, 'id': 4, 'required': 0}], 'plcmtcnt': 1}}); + expect(data.imp[0].secure).to.deep.equal(secure); + expect(request).to.deep.equal({ + 'method': 'POST', + 'url': 'https://prebid.mgid.com/prebid/1', + 'data': '{\"site\":{\"domain\":\"' + domain + '\",\"page\":\"' + page + '\"},\"cur\":[\"USD\"],\"geo\":{\"utcoffset\":' + utcOffset + '},\"device\":{\"ua\":\"' + ua + '\",\"js\":1,\"dnt\":' + dnt + ',\"h\":' + screenHeight + ',\"w\":' + screenWidth + ',\"language\":\"' + lang + '\"},\"ext\":{\"mgid_ver\":\"' + mgid_ver + '\",\"prebid_ver\":\"' + prebid_ver + '\"},\"imp\":[{\"tagid\":\"2/div\",\"secure\":' + secure + ',\"native\":{\"request\":{\"plcmtcnt\":1,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":80}},{\"id\":2,\"required\":0,\"img\":{\"type\":3,\"w\":80,\"h\":80}},{\"id\":4,\"required\":0,\"data\":{\"type\":1}}]}}}]}', + }); + }); + it('should return proper banner request', function () { + let bid = Object.assign({}, abid); + bid.mediaTypes = { + banner: { + sizes: [[300, 600], [300, 250]], + } + }; + let bidRequests = [bid]; + const request = spec.buildRequests(bidRequests); + + const page = top.location.href; + const domain = urlUtils.parse(page).hostname; + expect(request.url).deep.equal('https://prebid.mgid.com/prebid/1'); + expect(request.method).deep.equal('POST'); + const data = JSON.parse(request.data); + expect(data.site.domain).to.deep.equal(domain); + expect(data.site.page).to.deep.equal(page); + expect(data.cur).to.deep.equal(['USD']); + expect(data.device.ua).to.deep.equal(ua); + expect(data.device.dnt).equal(dnt); + expect(data.device.h).equal(screenHeight); + expect(data.device.w).equal(screenWidth); + expect(data.device.language).to.deep.equal(lang); + expect(data.imp[0].tagid).to.deep.equal('2/div'); + expect(data.imp[0].banner).to.deep.equal({w: 300, h: 600, format: [{w: 300, h: 600}, {w: 300, h: 250}]}); + expect(data.imp[0].secure).to.deep.equal(secure); + + expect(request).to.deep.equal({ + 'method': 'POST', + 'url': 'https://prebid.mgid.com/prebid/1', + 'data': '{\"site\":{\"domain\":\"' + domain + '\",\"page\":\"' + page + '\"},\"cur\":[\"USD\"],\"geo\":{\"utcoffset\":' + utcOffset + '},\"device\":{\"ua\":\"' + ua + '\",\"js\":1,\"dnt\":' + dnt + ',\"h\":' + screenHeight + ',\"w\":' + screenWidth + ',\"language\":\"' + lang + '\"},\"ext\":{\"mgid_ver\":\"' + mgid_ver + '\",\"prebid_ver\":\"' + prebid_ver + '\"},\"imp\":[{\"tagid\":\"2/div\",\"secure\":' + secure + ',\"banner\":{\"w\":300,\"h\":600,\"format\":[{\"w\":300,\"h\":600},{\"w\":300,\"h\":250}]}}]}', + }); + }); + }); + describe('interpretResponse banner', function () { + it('should not push bid response', function () { + let bids = spec.interpretResponse(); + expect(bids).to.be.undefined; + }); + it('should push proper banner bid response', function () { + let resp = { + body: {'id': '57c0c2b1b732ca', 'bidid': '57c0c2b1b732ca', 'cur': '', 'seatbid': [{'bid': [{'price': 1.5, 'h': 600, 'w': 300, 'id': '1', 'impid': '61e40632c53fc2', 'adid': '2898532/2419121/2592854/2499195', 'nurl': 'http: nurl', 'burl': 'http: burl', 'adm': 'html: adm', 'cid': '44082', 'crid': '2898532/2419121/2592854/2499195', 'cat': ['IAB7', 'IAB14', 'IAB18-3', 'IAB1-2']}], 'seat': '44082'}]} + }; + let bids = spec.interpretResponse(resp); + expect(bids).to.deep.equal([ + { + 'ad': 'html: adm', + 'cpm': 1.5, + 'creativeId': '2898532/2419121/2592854/2499195', + 'currency': 'USD', + 'dealId': '', + 'height': 600, + 'isBurl': true, + 'mediaType': 'banner', + 'netRevenue': true, + 'nurl': 'http: nurl', + 'burl': 'http: burl', + 'requestId': '61e40632c53fc2', + 'ttl': 300, + 'width': 300, + } + ]); + }); + }); + describe('interpretResponse native', function () { + it('should not push proper native bid response if adm is missing', function () { + let resp = { + body: {'id': '57c0c2b1b732ca', 'bidid': '57c0c2b1b732ca', 'cur': 'GBP', 'seatbid': [{'bid': [{'price': 1.5, 'h': 600, 'w': 300, 'id': '1', 'impid': '61e40632c53fc2', 'adid': '2898532/2419121/2592854/2499195', 'nurl': 'http: nurl', 'burl': 'http: burl', 'cid': '44082', 'crid': '2898532/2419121/2592854/2499195', 'cat': ['IAB7', 'IAB14', 'IAB18-3', 'IAB1-2'], 'ext': {'place': 0, 'crtype': 'native'}}], 'seat': '44082'}]} + }; + let bids = spec.interpretResponse(resp); + expect(bids).to.deep.equal([]) + }); + it('should not push proper native bid response if assets is empty', function () { + let resp = { + body: {'id': '57c0c2b1b732ca', 'bidid': '57c0c2b1b732ca', 'cur': 'GBP', 'seatbid': [{'bid': [{'price': 1.5, 'h': 600, 'w': 300, 'id': '1', 'impid': '61e40632c53fc2', 'adid': '2898532/2419121/2592854/2499195', 'nurl': 'http: nurl', 'burl': 'http: burl', 'adm': '{\"native\":{\"ver\":\"1.1\",\"link\":{\"url\":\"link_url\"},\"assets\":[],\"imptrackers\":[\"imptrackers1\"]}}', 'cid': '44082', 'crid': '2898532/2419121/2592854/2499195', 'cat': ['IAB7', 'IAB14', 'IAB18-3', 'IAB1-2'], 'ext': {'place': 0, 'crtype': 'native'}}], 'seat': '44082'}]} + }; + let bids = spec.interpretResponse(resp); + expect(bids).to.deep.equal([]) + }); + it('should push proper native bid response', function () { + let resp = { + body: {'id': '57c0c2b1b732ca', 'bidid': '57c0c2b1b732ca', 'cur': 'GBP', 'seatbid': [{'bid': [{'price': 1.5, 'h': 600, 'w': 300, 'id': '1', 'impid': '61e40632c53fc2', 'adid': '2898532/2419121/2592854/2499195', 'nurl': 'http: nurl', 'burl': 'http: burl', 'adm': '{\"native\":{\"ver\":\"1.1\",\"link\":{\"url\":\"link_url\"},\"assets\":[{\"id\":1,\"required\":0,\"title\":{\"text\":\"title1\"}},{\"id\":2,\"required\":0,\"img\":{\"w\":80,\"h\":80,\"type\":3,\"url\":\"image_src\"}},{\"id\":3,\"required\":0,\"img\":{\"w\":50,\"h\":50,\"type\":1,\"url\":\"icon_src\"}},{\"id\":4,\"required\":0,\"data\":{\"type\":4,\"value\":\"sponsored\"}},{\"id\":5,\"required\":0,\"data\":{\"type\":6,\"value\":\"price1\"}},{\"id\":6,\"required\":0,\"data\":{\"type\":7,\"value\":\"price2\"}}],\"imptrackers\":[\"imptrackers1\"]}}', 'cid': '44082', 'crid': '2898532/2419121/2592854/2499195', 'cat': ['IAB7', 'IAB14', 'IAB18-3', 'IAB1-2'], 'ext': {'place': 0, 'crtype': 'native'}}], 'seat': '44082'}], ext: {'muidn': 'userid'}} + }; + let bids = spec.interpretResponse(resp); + expect(bids).to.deep.equal([{ + 'ad': '{\"native\":{\"ver\":\"1.1\",\"link\":{\"url\":\"link_url\"},\"assets\":[{\"id\":1,\"required\":0,\"title\":{\"text\":\"title1\"}},{\"id\":2,\"required\":0,\"img\":{\"w\":80,\"h\":80,\"type\":3,\"url\":\"image_src\"}},{\"id\":3,\"required\":0,\"img\":{\"w\":50,\"h\":50,\"type\":1,\"url\":\"icon_src\"}},{\"id\":4,\"required\":0,\"data\":{\"type\":4,\"value\":\"sponsored\"}},{\"id\":5,\"required\":0,\"data\":{\"type\":6,\"value\":\"price1\"}},{\"id\":6,\"required\":0,\"data\":{\"type\":7,\"value\":\"price2\"}}],\"imptrackers\":[\"imptrackers1\"]}}', + 'burl': 'http: burl', + 'cpm': 1.5, + 'creativeId': '2898532/2419121/2592854/2499195', + 'currency': 'GBP', + 'dealId': '', + 'height': 0, + 'isBurl': true, + 'mediaType': 'native', + 'native': { + 'clickTrackers': [], + 'clickUrl': 'link_url', + 'data': 'price1', + 'icon': { + 'height': 50, + 'url': 'icon_src', + 'width': 50 + }, + 'image': { + 'height': 80, + 'url': 'image_src', + 'width': 80 + }, + 'impressionTrackers': [ + 'imptrackers1' + ], + 'jstracker': [], + 'sponsoredBy': 'sponsored', + 'title': 'title1' + }, + 'netRevenue': true, + 'nurl': 'http: nurl', + 'requestId': '61e40632c53fc2', + 'ttl': 300, + 'width': 0 + }]) + }); + it('should push proper native bid response', function () { + let resp = { + body: {'id': '57c0c2b1b732ca', 'bidid': '57c0c2b1b732ca', 'cur': 'GBP', 'seatbid': [{'bid': [{'price': 1.5, 'h': 600, 'w': 300, 'id': '1', 'impid': '61e40632c53fc2', 'adid': '2898532/2419121/2592854/2499195', 'nurl': 'http: nurl', 'burl': 'http: burl', 'adm': '{\"native\":{\"ver\":\"1.1\",\"link\":{\"url\":\"link_url\"},\"assets\":[{\"id\":1,\"required\":0,\"title\":{\"text\":\"title1\"}},{\"id\":2,\"required\":0,\"img\":{\"w\":80,\"h\":80,\"type\":3,\"url\":\"image_src\"}},{\"id\":3,\"required\":0,\"img\":{\"w\":50,\"h\":50,\"type\":1,\"url\":\"icon_src\"}}],\"imptrackers\":[\"imptrackers1\"]}}', 'cid': '44082', 'crid': '2898532/2419121/2592854/2499195', 'cat': ['IAB7', 'IAB14', 'IAB18-3', 'IAB1-2'], 'ext': {'place': 0, 'crtype': 'native'}}], 'seat': '44082'}]} + }; + let bids = spec.interpretResponse(resp); + expect(bids).to.deep.equal([ + { + 'ad': '{\"native\":{\"ver\":\"1.1\",\"link\":{\"url\":\"link_url\"},\"assets\":[{\"id\":1,\"required\":0,\"title\":{\"text\":\"title1\"}},{\"id\":2,\"required\":0,\"img\":{\"w\":80,\"h\":80,\"type\":3,\"url\":\"image_src\"}},{\"id\":3,\"required\":0,\"img\":{\"w\":50,\"h\":50,\"type\":1,\"url\":\"icon_src\"}}],\"imptrackers\":[\"imptrackers1\"]}}', + 'cpm': 1.5, + 'creativeId': '2898532/2419121/2592854/2499195', + 'currency': 'GBP', + 'dealId': '', + 'height': 0, + 'isBurl': true, + 'mediaType': 'native', + 'netRevenue': true, + 'nurl': 'http: nurl', + 'burl': 'http: burl', + 'requestId': '61e40632c53fc2', + 'ttl': 300, + 'width': 0, + 'native': { + clickTrackers: [], + title: 'title1', + image: { + url: 'image_src', + width: 80, + height: 80, + }, + icon: { + url: 'icon_src', + width: 50, + height: 50, + }, + impressionTrackers: ['imptrackers1'], + jstracker: [], + clickUrl: 'link_url', + } + } + ]); + }); + }); + + describe('getUserSyncs', function () { + it('should do nothing on getUserSyncs', function () { + spec.getUserSyncs() + }); + }); + describe('on bidWon', function () { + it('should replace nurl and burl for native', function () { + const burl = 'burl&s=${' + 'AUCTION_PRICE}'; + const nurl = 'nurl&s=${' + 'AUCTION_PRICE}'; + const bid = {'bidderCode': 'mgid', 'width': 0, 'height': 0, 'statusMessage': 'Bid available', 'adId': '3d0b6ff1dda89', 'requestId': '2a423489e058a1', 'mediaType': 'native', 'source': 'client', 'ad': '{\"native\":{\"ver\":\"1.1\",\"link\":{\"url\":\"LinkURL\"},\"assets\":[{\"id\":1,\"required\":0,\"title\":{\"text\":\"TITLE\"}},{\"id\":2,\"required\":0,\"img\":{\"w\":80,\"h\":80,\"type\":3,\"url\":\"ImageURL\"}},{\"id\":3,\"required\":0,\"img\":{\"w\":50,\"h\":50,\"type\":1,\"url\":\"IconURL\"}},{\"id\":11,\"required\":0,\"data\":{\"type\":1,\"value\":\"sponsored\"}}],\"imptrackers\":[\"ImpTrackerURL\"]}}', 'cpm': 0.66, 'creativeId': '353538_591471', 'currency': 'USD', 'dealId': '', 'netRevenue': true, 'ttl': 300, 'nurl': nurl, 'burl': burl, 'isBurl': true, 'native': {'title': 'TITLE', 'image': {'url': 'ImageURL', 'height': 80, 'width': 80}, 'icon': {'url': 'IconURL', 'height': 50, 'width': 50}, 'sponsored': 'sponsored', 'clickUrl': 'LinkURL', 'clickTrackers': [], 'impressionTrackers': ['ImpTrackerURL'], 'jstracker': []}, 'auctionId': 'a92bffce-14d2-4f8f-a78a-7b9b5e4d28fa', 'responseTimestamp': 1556867386065, 'requestTimestamp': 1556867385916, 'bidder': 'mgid', 'adUnitCode': 'div-gpt-ad-1555415275793-0', 'timeToRespond': 149, 'pbLg': '0.50', 'pbMg': '0.60', 'pbHg': '0.66', 'pbAg': '0.65', 'pbDg': '0.66', 'pbCg': '', 'size': '0x0', 'adserverTargeting': {'hb_bidder': 'mgid', 'hb_adid': '3d0b6ff1dda89', 'hb_pb': '0.66', 'hb_size': '0x0', 'hb_source': 'client', 'hb_format': 'native', 'hb_native_title': 'TITLE', 'hb_native_image': 'hb_native_image:3d0b6ff1dda89', 'hb_native_icon': 'IconURL', 'hb_native_linkurl': 'hb_native_linkurl:3d0b6ff1dda89'}, 'status': 'targetingSet', 'params': [{'accountId': '184', 'placementId': '353538'}]}; + spec.onBidWon(bid); + expect(bid.nurl).to.deep.equal('nurl&s=0.66'); + expect(bid.burl).to.deep.equal('burl&s=0.66'); + }); + it('should replace nurl and burl for banner', function () { + const burl = 'burl&s=${' + 'AUCTION_PRICE}'; + const nurl = 'nurl&s=${' + 'AUCTION_PRICE}'; + const bid = {'bidderCode': 'mgid', 'width': 0, 'height': 0, 'statusMessage': 'Bid available', 'adId': '3d0b6ff1dda89', 'requestId': '2a423489e058a1', 'mediaType': 'banner', 'source': 'client', 'ad': burl, 'cpm': 0.66, 'creativeId': '353538_591471', 'currency': 'USD', 'dealId': '', 'netRevenue': true, 'ttl': 300, 'nurl': nurl, 'burl': burl, 'isBurl': true, 'auctionId': 'a92bffce-14d2-4f8f-a78a-7b9b5e4d28fa', 'responseTimestamp': 1556867386065, 'requestTimestamp': 1556867385916, 'bidder': 'mgid', 'adUnitCode': 'div-gpt-ad-1555415275793-0', 'timeToRespond': 149, 'pbLg': '0.50', 'pbMg': '0.60', 'pbHg': '0.66', 'pbAg': '0.65', 'pbDg': '0.66', 'pbCg': '', 'size': '0x0', 'adserverTargeting': {'hb_bidder': 'mgid', 'hb_adid': '3d0b6ff1dda89', 'hb_pb': '0.66', 'hb_size': '0x0', 'hb_source': 'client', 'hb_format': 'banner', 'hb_banner_title': 'TITLE', 'hb_banner_image': 'hb_banner_image:3d0b6ff1dda89', 'hb_banner_icon': 'IconURL', 'hb_banner_linkurl': 'hb_banner_linkurl:3d0b6ff1dda89'}, 'status': 'targetingSet', 'params': [{'accountId': '184', 'placementId': '353538'}]}; + spec.onBidWon(bid); + expect(bid.nurl).to.deep.equal('nurl&s=0.66'); + expect(bid.burl).to.deep.equal(burl); + expect(bid.ad).to.deep.equal('burl&s=0.66'); + }); + }); +}); diff --git a/test/spec/modules/microadBidAdapter_spec.js b/test/spec/modules/microadBidAdapter_spec.js new file mode 100644 index 00000000000..32bf15d53b9 --- /dev/null +++ b/test/spec/modules/microadBidAdapter_spec.js @@ -0,0 +1,388 @@ +import { expect } from 'chai'; +import { spec } from 'modules/microadBidAdapter'; +import * as utils from 'src/utils'; + +describe('microadBidAdapter', () => { + const bidRequestTemplate = { + bidder: 'microad', + mediaTypes: { + banner: {} + }, + params: { + spot: 'spot-code' + }, + bidId: 'bid-id', + transactionId: 'transaction-id' + }; + + describe('isBidRequestValid', () => { + it('should return true when required parameters are set', () => { + const validBids = [ + bidRequestTemplate, + Object.assign({}, bidRequestTemplate, { + mediaTypes: { + native: {} + } + }), + Object.assign({}, bidRequestTemplate, { + mediaTypes: { + video: {} + } + }) + ]; + validBids.forEach(validBid => { + expect(spec.isBidRequestValid(validBid)).to.equal(true); + }); + }); + + it('should return false when required parameters are not set', () => { + const bidWithoutParams = utils.deepClone(bidRequestTemplate); + delete bidWithoutParams.params; + const bidWithoutSpot = utils.deepClone(bidRequestTemplate); + delete bidWithoutSpot.params.spot; + const bidWithoutMediaTypes = utils.deepClone(bidRequestTemplate); + delete bidWithoutMediaTypes.mediaTypes; + + const invalidBids = [ + {}, + bidWithoutParams, + bidWithoutSpot, + bidWithoutMediaTypes, + Object.assign({}, bidRequestTemplate, { + mediaTypes: {} + }) + ]; + invalidBids.forEach(invalidBid => { + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); + }); + }); + }); + + describe('buildRequests', () => { + const bidderRequest = { + refererInfo: { + canonicalUrl: 'http://example.com/to', + referer: 'http://example.com/from' + } + }; + const expectedResultTemplate = { + spot: 'spot-code', + url: 'http://example.com/to', + referrer: 'http://example.com/from', + bid_id: 'bid-id', + transaction_id: 'transaction-id', + media_types: 1 + }; + + it('should generate valid media_types', () => { + const bidRequests = [ + bidRequestTemplate, + Object.assign({}, bidRequestTemplate, { + mediaTypes: { + banner: {}, native: {} + } + }), + Object.assign({}, bidRequestTemplate, { + mediaTypes: { + banner: {}, native: {}, video: {} + } + }), + Object.assign({}, bidRequestTemplate, { + mediaTypes: { + native: {} + } + }), + Object.assign({}, bidRequestTemplate, { + mediaTypes: { + native: {}, video: {} + } + }), + Object.assign({}, bidRequestTemplate, { + mediaTypes: { + video: {} + } + }), + Object.assign({}, bidRequestTemplate, { + mediaTypes: { + banner: {}, video: {} + } + }) + ]; + + const results = bidRequests.map(bid => { + const requests = spec.buildRequests([bid], bidderRequest); + return requests[0].data.media_types; + }); + expect(results).to.deep.equal([ + 1, // BANNER + 3, // BANNER + NATIVE + 7, // BANNER + NATIVE + VIDEO + 2, // NATIVE + 6, // NATIVE + VIDEO + 4, // VIDEO + 5 // BANNER + VIDEO + ]); + }); + + it('should use window.location.href if there is no canonicalUrl', () => { + const bidderRequestWithoutCanonicalUrl = { + refererInfo: { + referer: 'http://example.com/from' + } + }; + const requests = spec.buildRequests([bidRequestTemplate], bidderRequestWithoutCanonicalUrl); + requests.forEach(request => { + expect(request.data).to.deep.equal( + Object.assign({}, expectedResultTemplate, { + cbt: request.data.cbt, + url: window.location.href + }) + ); + }); + }); + + it('should generate valid request with no optional parameters', () => { + const requests = spec.buildRequests([bidRequestTemplate], bidderRequest); + requests.forEach(request => { + expect(request.data).to.deep.equal( + Object.assign({}, expectedResultTemplate, { + cbt: request.data.cbt + }) + ); + }); + }); + + it('should add url_macro parameter to response if request parameters contain url', () => { + const bidRequestWithUrl = Object.assign({}, bidRequestTemplate, { + params: { + spot: 'spot-code', + url: '${COMPASS_EXT_URL}url-macro' + } + }); + const requests = spec.buildRequests([bidRequestWithUrl], bidderRequest); + requests.forEach(request => { + expect(request.data).to.deep.equal( + Object.assign({}, expectedResultTemplate, { + cbt: request.data.cbt, + url_macro: 'url-macro' + }) + ); + }); + }); + + it('should add referrer_macro parameter to response if request parameters contain referrer', () => { + const bidRequestWithReferrer = Object.assign({}, bidRequestTemplate, { + params: { + spot: 'spot-code', + referrer: '${COMPASS_EXT_REF}referrer-macro' + } + }); + const requests = spec.buildRequests([bidRequestWithReferrer], bidderRequest); + requests.forEach(request => { + expect(request.data).to.deep.equal( + Object.assign({}, expectedResultTemplate, { + cbt: request.data.cbt, + referrer_macro: 'referrer-macro' + }) + ); + }); + }); + + it('should add ifa parameter to response if request parameters contain ifa', () => { + const bidRequestWithIfa = Object.assign({}, bidRequestTemplate, { + params: { + spot: 'spot-code', + ifa: '${COMPASS_EXT_IFA}ifa' + } + }); + const requests = spec.buildRequests([bidRequestWithIfa], bidderRequest); + requests.forEach(request => { + expect(request.data).to.deep.equal( + Object.assign({}, expectedResultTemplate, { + cbt: request.data.cbt, + ifa: 'ifa' + }) + ); + }); + }); + + it('should add appid parameter to response if request parameters contain appid', () => { + const bidRequestWithAppid = Object.assign({}, bidRequestTemplate, { + params: { + spot: 'spot-code', + appid: '${COMPASS_EXT_APPID}appid' + } + }); + const requests = spec.buildRequests([bidRequestWithAppid], bidderRequest); + requests.forEach(request => { + expect(request.data).to.deep.equal( + Object.assign({}, expectedResultTemplate, { + cbt: request.data.cbt, + appid: 'appid' + }) + ); + }); + }); + + it('should add geo parameter to response if request parameters contain geo', () => { + const bidRequestWithGeo = Object.assign({}, bidRequestTemplate, { + params: { + spot: 'spot-code', + geo: '${COMPASS_EXT_GEO}35.655275,139.693771' + } + }); + const requests = spec.buildRequests([bidRequestWithGeo], bidderRequest); + requests.forEach(request => { + expect(request.data).to.deep.equal( + Object.assign({}, expectedResultTemplate, { + cbt: request.data.cbt, + geo: '35.655275,139.693771' + }) + ); + }); + }); + + it('should not add geo parameter to response if request parameters contain invalid geo', () => { + const bidRequestWithGeo = Object.assign({}, bidRequestTemplate, { + params: { + spot: 'spot-code', + geo: '${COMPASS_EXT_GEO}invalid format geo' + } + }); + const requests = spec.buildRequests([bidRequestWithGeo], bidderRequest); + requests.forEach(request => { + expect(request.data).to.deep.equal( + Object.assign({}, expectedResultTemplate, { + cbt: request.data.cbt + }) + ); + }); + }); + + it('should always use the HTTPS endpoint https://s-rtb-pb.send.microad.jp/prebid even if it is served via HTTP', () => { + const requests = spec.buildRequests([bidRequestTemplate], bidderRequest); + requests.forEach(request => { + expect(request.url.lastIndexOf('https', 0) === 0).to.be.true; + }); + }); + }); + + describe('interpretResponse', () => { + const serverResponseTemplate = { + body: { + requestId: 'request-id', + cpm: 0.1, + width: 200, + height: 100, + ad: '
    test
    ', + ttl: 10, + creativeId: 'creative-id', + netRevenue: true, + currency: 'JPY' + } + }; + const expectedBidResponseTemplate = { + requestId: 'request-id', + cpm: 0.1, + width: 200, + height: 100, + ad: '
    test
    ', + ttl: 10, + creativeId: 'creative-id', + netRevenue: true, + currency: 'JPY' + }; + + it('should return nothing if server response body does not contain cpm', () => { + const emptyResponse = { + body: {} + }; + + expect(spec.interpretResponse(emptyResponse)).to.deep.equal([]); + }); + + it('should return nothing if returned cpm is zero', () => { + const serverResponse = { + body: { + cpm: 0 + } + }; + + expect(spec.interpretResponse(serverResponse)).to.deep.equal([]); + }); + + it('should return a valid bidResponse without deal id if serverResponse is valid, has a nonzero cpm and no deal id', () => { + expect(spec.interpretResponse(serverResponseTemplate)).to.deep.equal([expectedBidResponseTemplate]); + }); + + it('should return a valid bidResponse with deal id if serverResponse is valid, has a nonzero cpm and a deal id', () => { + const serverResponseWithDealId = Object.assign({}, utils.deepClone(serverResponseTemplate)); + serverResponseWithDealId.body['dealId'] = 10001; + const expectedBidResponse = Object.assign({}, expectedBidResponseTemplate, { + dealId: 10001 + }); + + expect(spec.interpretResponse(serverResponseWithDealId)).to.deep.equal([expectedBidResponse]); + }); + }); + + describe('getUserSyncs', () => { + const BOTH_ENABLED = { + iframeEnabled: true, pixelEnabled: true + }; + const IFRAME_ENABLED = { + iframeEnabled: true, pixelEnabled: false + }; + const PIXEL_ENABLED = { + iframeEnabled: false, pixelEnabled: true + }; + const BOTH_DISABLED = { + iframeEnabled: false, pixelEnabled: false + }; + const serverResponseTemplate = { + body: { + syncUrls: { + iframe: ['https://www.exmaple.com/iframe1', 'https://www.exmaple.com/iframe2'], + image: ['https://www.exmaple.com/image1', 'https://www.exmaple.com/image2'] + } + } + }; + const expectedIframeSyncs = [ + {type: 'iframe', url: 'https://www.exmaple.com/iframe1'}, + {type: 'iframe', url: 'https://www.exmaple.com/iframe2'} + ]; + const expectedImageSyncs = [ + {type: 'image', url: 'https://www.exmaple.com/image1'}, + {type: 'image', url: 'https://www.exmaple.com/image2'} + ]; + + it('should return nothing if no sync urls are set', () => { + const serverResponse = utils.deepClone(serverResponseTemplate); + serverResponse.body.syncUrls.iframe = []; + serverResponse.body.syncUrls.image = []; + + const syncs = spec.getUserSyncs(BOTH_ENABLED, [serverResponse]); + expect(syncs).to.deep.equal([]); + }); + + it('should return nothing if sync is disabled', () => { + const syncs = spec.getUserSyncs(BOTH_DISABLED, [serverResponseTemplate]); + expect(syncs).to.deep.equal([]); + }); + + it('should register iframe and image sync urls if sync is enabled', () => { + const syncs = spec.getUserSyncs(BOTH_ENABLED, [serverResponseTemplate]); + expect(syncs).to.deep.equal(expectedIframeSyncs.concat(expectedImageSyncs)); + }); + + it('should register iframe sync urls if iframe is enabled', () => { + const syncs = spec.getUserSyncs(IFRAME_ENABLED, [serverResponseTemplate]); + expect(syncs).to.deep.equal(expectedIframeSyncs); + }); + + it('should register image sync urls if image is enabled', () => { + const syncs = spec.getUserSyncs(PIXEL_ENABLED, [serverResponseTemplate]); + expect(syncs).to.deep.equal(expectedImageSyncs); + }); + }); +}); diff --git a/test/spec/modules/my6senseBidAdapter_spec.js b/test/spec/modules/my6senseBidAdapter_spec.js index 80671305aca..d2c875dad07 100644 --- a/test/spec/modules/my6senseBidAdapter_spec.js +++ b/test/spec/modules/my6senseBidAdapter_spec.js @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import spec from 'modules/my6senseBidAdapter'; +import { spec } from 'modules/my6senseBidAdapter'; describe('My6sense Bid adapter test', function () { let bidRequests, serverResponses; diff --git a/test/spec/modules/mytargetBidAdapter_spec.js b/test/spec/modules/mytargetBidAdapter_spec.js new file mode 100644 index 00000000000..4e478fee1f0 --- /dev/null +++ b/test/spec/modules/mytargetBidAdapter_spec.js @@ -0,0 +1,229 @@ +import { expect } from 'chai'; +import { config } from 'src/config'; +import { spec } from 'modules/mytargetBidAdapter'; + +describe('MyTarget Adapter', function() { + describe('isBidRequestValid', function () { + it('should return true when required params found', function () { + let validBid = { + bidder: 'mytarget', + params: { + placementId: '1' + } + }; + + expect(spec.isBidRequestValid(validBid)).to.equal(true); + }); + + it('should return false for when required params are not passed', function () { + let invalidBid = { + bidder: 'mytarget', + params: {} + }; + + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + let bidRequests = [ + { + bidId: 'bid1', + bidder: 'mytarget', + params: { + placementId: '1' + } + }, + { + bidId: 'bid2', + bidder: 'mytarget', + params: { + placementId: '2', + position: 1, + response: 1, + bidfloor: 10000 + } + } + ]; + let bidderRequest = { + refererInfo: { + referer: 'https://example.com?param=value' + } + }; + + let bidRequest = spec.buildRequests(bidRequests, bidderRequest); + + it('should build single POST request for multiple bids', function() { + expect(bidRequest.method).to.equal('POST'); + expect(bidRequest.url).to.equal('//ad.mail.ru/hbid_prebid/'); + expect(bidRequest.data).to.be.an('object'); + expect(bidRequest.data.places).to.be.an('array'); + expect(bidRequest.data.places).to.have.lengthOf(2); + }); + + it('should pass bid parameters', function() { + let place1 = bidRequest.data.places[0]; + let place2 = bidRequest.data.places[1]; + + expect(place1.placementId).to.equal('1'); + expect(place2.placementId).to.equal('2'); + expect(place1.id).to.equal('bid1'); + expect(place2.id).to.equal('bid2'); + }); + + it('should pass default position and response type', function() { + let place = bidRequest.data.places[0]; + + expect(place.position).to.equal(0); + expect(place.response).to.equal(0); + }); + + it('should pass provided position and response type', function() { + let place = bidRequest.data.places[1]; + + expect(place.position).to.equal(1); + expect(place.response).to.equal(1); + }); + + it('should not pass default bidfloor', function() { + let place = bidRequest.data.places[0]; + + expect(place.bidfloor).not.to.exist; + }); + + it('should not pass provided bidfloor', function() { + let place = bidRequest.data.places[1]; + + expect(place.bidfloor).to.exist; + expect(place.bidfloor).to.equal(10000); + }); + + it('should pass site parameters', function() { + let site = bidRequest.data.site; + + expect(site).to.be.an('object'); + expect(site.sitename).to.equal('example.com'); + expect(site.page).to.equal('https://example.com?param=value'); + }); + + it('should pass settings', function() { + let settings = bidRequest.data.settings; + + expect(settings).to.be.an('object'); + expect(settings.currency).to.equal('RUB'); + expect(settings.windowSize).to.be.an('object'); + expect(settings.windowSize.width).to.equal(window.screen.width); + expect(settings.windowSize.height).to.equal(window.screen.height); + }); + + it('should pass currency from currency.adServerCurrency', function() { + const configStub = sinon.stub(config, 'getConfig').callsFake( + key => key === 'currency.adServerCurrency' ? 'USD' : ''); + + let bidRequest = spec.buildRequests(bidRequests, bidderRequest); + let settings = bidRequest.data.settings; + + expect(settings).to.be.an('object'); + expect(settings.currency).to.equal('USD'); + expect(settings.windowSize).to.be.an('object'); + expect(settings.windowSize.width).to.equal(window.screen.width); + expect(settings.windowSize.height).to.equal(window.screen.height); + + configStub.restore(); + }); + + it('should ignore currency other than "RUB" or "USD"', function() { + const configStub = sinon.stub(config, 'getConfig').callsFake( + key => key === 'currency.adServerCurrency' ? 'EUR' : ''); + + let bidRequest = spec.buildRequests(bidRequests, bidderRequest); + let settings = bidRequest.data.settings; + + expect(settings).to.be.an('object'); + expect(settings.currency).to.equal('RUB'); + + configStub.restore(); + }); + }); + + describe('interpretResponse', function () { + let serverResponse = { + body: { + 'bidder_status': + [ + { + 'bidder': 'mail.ru', + 'response_time_ms': 100, + 'num_bids': 2 + } + ], + 'bids': + [ + { + 'displayUrl': 'https://ad.mail.ru/hbid_imp/12345', + 'size': + { + 'height': '400', + 'width': '240' + }, + 'id': '1', + 'currency': 'RUB', + 'price': 100, + 'ttl': 360, + 'creativeId': '123456' + }, + { + 'adm': '

    Ad

    ', + 'size': + { + 'height': '250', + 'width': '300' + }, + 'id': '2', + 'price': 200 + } + ] + } + }; + + let bids = spec.interpretResponse(serverResponse); + + it('should return empty array for response with no bids', function() { + let emptyBids = spec.interpretResponse({ body: {} }); + + expect(emptyBids).to.have.lengthOf(0); + }); + + it('should parse all bids from response', function() { + expect(bids).to.have.lengthOf(2); + }); + + it('should parse bid with ad url', function() { + expect(bids[0].requestId).to.equal('1'); + expect(bids[0].cpm).to.equal(100); + expect(bids[0].width).to.equal('240'); + expect(bids[0].height).to.equal('400'); + expect(bids[0].ttl).to.equal(360); + expect(bids[0].currency).to.equal('RUB'); + expect(bids[0]).to.have.property('creativeId'); + expect(bids[0].creativeId).to.equal('123456'); + expect(bids[0].netRevenue).to.equal(true); + expect(bids[0].adUrl).to.equal('https://ad.mail.ru/hbid_imp/12345'); + expect(bids[0]).to.not.have.property('ad'); + }); + + it('should parse bid with ad markup', function() { + expect(bids[1].requestId).to.equal('2'); + expect(bids[1].cpm).to.equal(200); + expect(bids[1].width).to.equal('300'); + expect(bids[1].height).to.equal('250'); + expect(bids[1].ttl).to.equal(180); + expect(bids[1].currency).to.equal('RUB'); + expect(bids[1]).to.have.property('creativeId'); + expect(bids[1].creativeId).not.to.equal('123456'); + expect(bids[1].netRevenue).to.equal(true); + expect(bids[1].ad).to.equal('

    Ad

    '); + expect(bids[1]).to.not.have.property('adUrl'); + }); + }); +}); diff --git a/test/spec/modules/nafdigitalBidAdapter_spec.js b/test/spec/modules/nafdigitalBidAdapter_spec.js new file mode 100644 index 00000000000..ca486c632c7 --- /dev/null +++ b/test/spec/modules/nafdigitalBidAdapter_spec.js @@ -0,0 +1,283 @@ +import { expect } from 'chai'; +import * as utils from 'src/utils'; +import { spec } from 'modules/nafdigitalBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; + +const URL = 'https://nafdigitalbidder.com/hb'; + +describe('nafdigitalBidAdapter', function() { + const adapter = newBidder(spec); + let element, win; + let bidRequests; + let sandbox; + + beforeEach(function() { + element = { + x: 0, + y: 0, + + width: 0, + height: 0, + + getBoundingClientRect: () => { + return { + width: element.width, + height: element.height, + + left: element.x, + top: element.y, + right: element.x + element.width, + bottom: element.y + element.height + }; + } + }; + win = { + document: { + visibilityState: 'visible' + }, + + innerWidth: 800, + innerHeight: 600 + }; + bidRequests = [{ + 'bidder': 'nafdigital', + 'params': { + 'publisherId': 1234567 + }, + 'adUnitCode': 'adunit-code', + 'sizes': [ + [300, 250], + [300, 600] + ], + 'bidId': '5fb26ac22bde4', + 'bidderRequestId': '4bf93aeb730cb9', + 'auctionId': 'ffe9a1f7-7b67-4bda-a8e0-9ee5dc9f442e' + }]; + + sandbox = sinon.sandbox.create(); + sandbox.stub(document, 'getElementById').withArgs('adunit-code').returns(element); + sandbox.stub(utils, 'getWindowTop').returns(win); + sandbox.stub(utils, 'getWindowSelf').returns(win); + }); + + afterEach(function() { + sandbox.restore(); + }); + + describe('isBidRequestValid', function () { + let bid = { + 'bidder': 'nafdigital', + 'params': { + 'publisherId': 1234567 + }, + 'adUnitCode': 'adunit-code', + 'sizes': [ + [300, 250], + [300, 600] + ], + 'bidId': '5fb26ac22bde4', + 'bidderRequestId': '4bf93aeb730cb9', + 'auctionId': 'ffe9a1f7-7b67-4bda-a8e0-9ee5dc9f442e', + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when tagid not passed correctly', function () { + bid.params.publisherId = undefined; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when require params are not passed', function () { + let bid = Object.assign({}, bid); + bid.params = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + it('sends bid request to our endpoint via POST', function () { + const request = spec.buildRequests(bidRequests); + expect(request.method).to.equal('POST'); + }); + + it('request url should match our endpoint url', function () { + const request = spec.buildRequests(bidRequests); + expect(request.url).to.equal(URL); + }); + + it('sets the proper banner object', function() { + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.format).to.deep.equal([{w: 300, h: 250}, {w: 300, h: 600}]); + }); + + it('accepts a single array as a size', function() { + bidRequests[0].sizes = [300, 250]; + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.format).to.deep.equal([{w: 300, h: 250}]); + }); + + it('sends bidfloor param if present', function () { + bidRequests[0].params.bidFloor = 0.05; + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].bidfloor).to.equal(0.05); + }); + + it('sends tagid', function () { + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].tagid).to.equal('adunit-code'); + }); + + it('sends publisher id', function () { + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.site.publisher.id).to.equal(1234567); + }); + + context('when element is fully in view', function() { + it('returns 100', function() { + Object.assign(element, { width: 600, height: 400 }); + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.ext.viewability).to.equal(100); + }); + }); + + context('when element is out of view', function() { + it('returns 0', function() { + Object.assign(element, { x: -300, y: 0, width: 207, height: 320 }); + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.ext.viewability).to.equal(0); + }); + }); + + context('when element is partially in view', function() { + it('returns percentage', function() { + Object.assign(element, { width: 800, height: 800 }); + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.ext.viewability).to.equal(75); + }); + }); + + context('when width or height of the element is zero', function() { + it('try to use alternative values', function() { + Object.assign(element, { width: 0, height: 0 }); + bidRequests[0].sizes = [[800, 2400]]; + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.ext.viewability).to.equal(25); + }); + }); + + context('when nested iframes', function() { + it('returns \'na\'', function() { + Object.assign(element, { width: 600, height: 400 }); + + utils.getWindowTop.restore(); + utils.getWindowSelf.restore(); + sandbox.stub(utils, 'getWindowTop').returns(win); + sandbox.stub(utils, 'getWindowSelf').returns({}); + + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.ext.viewability).to.equal('na'); + }); + }); + + context('when tab is inactive', function() { + it('returns 0', function() { + Object.assign(element, { width: 600, height: 400 }); + + utils.getWindowTop.restore(); + win.document.visibilityState = 'hidden'; + sandbox.stub(utils, 'getWindowTop').returns(win); + + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.ext.viewability).to.equal(0); + }); + }); + }); + + describe('interpretResponse', function () { + let response; + beforeEach(function () { + response = { + body: { + 'id': '37386aade21a71', + 'seatbid': [{ + 'bid': [{ + 'id': '376874781', + 'impid': '283a9f4cd2415d', + 'price': 0.35743275, + 'nurl': '', + 'adm': '', + 'w': 300, + 'h': 250 + }] + }] + } + }; + }); + + it('should get the correct bid response', function () { + let expectedResponse = [{ + 'requestId': '283a9f4cd2415d', + 'cpm': 0.35743275, + 'width': 300, + 'height': 250, + 'creativeId': '376874781', + 'currency': 'USD', + 'netRevenue': true, + 'mediaType': 'banner', + 'ad': `
    `, + 'ttl': 60 + }]; + + let result = spec.interpretResponse(response); + expect(result[0]).to.deep.equal(expectedResponse[0]); + }); + + it('crid should default to the bid id if not on the response', function () { + let expectedResponse = [{ + 'requestId': '283a9f4cd2415d', + 'cpm': 0.35743275, + 'width': 300, + 'height': 250, + 'creativeId': response.body.seatbid[0].bid[0].id, + 'currency': 'USD', + 'netRevenue': true, + 'mediaType': 'banner', + 'ad': `
    `, + 'ttl': 60 + }]; + + let result = spec.interpretResponse(response); + expect(result[0]).to.deep.equal(expectedResponse[0]); + }); + + it('handles empty bid response', function () { + let response = { + body: '' + }; + let result = spec.interpretResponse(response); + expect(result.length).to.equal(0); + }); + }); + + describe('getUserSyncs ', () => { + let syncOptions = {iframeEnabled: true, pixelEnabled: true}; + + it('should not return', () => { + let returnStatement = spec.getUserSyncs(syncOptions, []); + expect(returnStatement).to.be.empty; + }); + }); +}); diff --git a/test/spec/modules/nanointeractiveBidAdapter_spec.js b/test/spec/modules/nanointeractiveBidAdapter_spec.js index 3731535b88a..a357327fe96 100644 --- a/test/spec/modules/nanointeractiveBidAdapter_spec.js +++ b/test/spec/modules/nanointeractiveBidAdapter_spec.js @@ -1,4 +1,4 @@ -import { expect } from 'chai'; +import {expect} from 'chai'; import * as utils from 'src/utils'; import * as sinon from 'sinon'; @@ -6,8 +6,8 @@ import { BIDDER_CODE, CATEGORY, CATEGORY_NAME, - DATA_PARTNER_PIXEL_ID, - ENGINE_BASE_URL, + SSP_PLACEMENT_ID, + END_POINT_URL, NQ, NQ_NAME, REF, @@ -19,8 +19,6 @@ describe('nanointeractive adapter tests', function () { const SIZES_PARAM = 'sizes'; const BID_ID_PARAM = 'bidId'; const BID_ID_VALUE = '24a1c9ec270973'; - const CORS_PARAM = 'cors'; - const LOC_PARAM = 'loc'; const DATA_PARTNER_PIXEL_ID_VALUE = 'testPID'; const NQ_VALUE = 'rumpelstiltskin'; const NQ_NAME_QUERY_PARAM = 'nqName'; @@ -37,21 +35,7 @@ describe('nanointeractive adapter tests', function () { const AD = '
    '; const CPM = 1; - function getBidResponse (pid, nq, category, subId, cors, ref, loc) { - return { - [DATA_PARTNER_PIXEL_ID]: pid, - [NQ]: nq, - [CATEGORY]: category, - [SUB_ID]: subId, - [REF]: ref, - [SIZES_PARAM]: [WIDTH1 + 'x' + HEIGHT1, WIDTH2 + 'x' + HEIGHT2], - [BID_ID_PARAM]: BID_ID_VALUE, - [CORS_PARAM]: cors, - [LOC_PARAM]: loc, - }; - } - - function getBidRequest (params) { + function getBidRequest(params) { return { bidder: BIDDER_CODE, params: params, @@ -70,85 +54,85 @@ describe('nanointeractive adapter tests', function () { describe('Methods', function () { it('Test isBidRequestValid() with valid param(s): pid', function () { expect(nanoBidAdapter.isBidRequestValid(getBidRequest({ - [DATA_PARTNER_PIXEL_ID]: DATA_PARTNER_PIXEL_ID_VALUE, + [SSP_PLACEMENT_ID]: DATA_PARTNER_PIXEL_ID_VALUE, }))).to.equal(true); }); it('Test isBidRequestValid() with valid param(s): pid, nq', function () { expect(nanoBidAdapter.isBidRequestValid(getBidRequest({ - [DATA_PARTNER_PIXEL_ID]: DATA_PARTNER_PIXEL_ID_VALUE, + [SSP_PLACEMENT_ID]: DATA_PARTNER_PIXEL_ID_VALUE, [NQ]: NQ, }))).to.equal(true); }); it('Test isBidRequestValid() with valid param(s): pid, nq, category', function () { expect(nanoBidAdapter.isBidRequestValid(getBidRequest({ - [DATA_PARTNER_PIXEL_ID]: DATA_PARTNER_PIXEL_ID_VALUE, + [SSP_PLACEMENT_ID]: DATA_PARTNER_PIXEL_ID_VALUE, [NQ]: NQ, [CATEGORY]: CATEGORY_VALUE, }))).to.equal(true); }); it('Test isBidRequestValid() with valid param(s): pid, nq, categoryName', function () { expect(nanoBidAdapter.isBidRequestValid(getBidRequest({ - [DATA_PARTNER_PIXEL_ID]: DATA_PARTNER_PIXEL_ID_VALUE, + [SSP_PLACEMENT_ID]: DATA_PARTNER_PIXEL_ID_VALUE, [NQ]: NQ, [CATEGORY_NAME_QUERY_PARAM]: CATEGORY_NAME_QUERY_PARAM, }))).to.equal(true); }); it('Test isBidRequestValid() with valid param(s): pid, nq, subId', function () { expect(nanoBidAdapter.isBidRequestValid(getBidRequest({ - [DATA_PARTNER_PIXEL_ID]: DATA_PARTNER_PIXEL_ID_VALUE, + [SSP_PLACEMENT_ID]: DATA_PARTNER_PIXEL_ID_VALUE, [NQ]: NQ, [SUB_ID]: SUB_ID_VALUE, }))).to.equal(true); }); it('Test isBidRequestValid() with valid param(s): pid, nqName', function () { expect(nanoBidAdapter.isBidRequestValid(getBidRequest({ - [DATA_PARTNER_PIXEL_ID]: DATA_PARTNER_PIXEL_ID_VALUE, + [SSP_PLACEMENT_ID]: DATA_PARTNER_PIXEL_ID_VALUE, [NQ_NAME]: NQ_NAME_QUERY_PARAM, }))).to.equal(true); }); it('Test isBidRequestValid() with valid param(s): pid, nqName, category', function () { expect(nanoBidAdapter.isBidRequestValid(getBidRequest({ - [DATA_PARTNER_PIXEL_ID]: DATA_PARTNER_PIXEL_ID_VALUE, + [SSP_PLACEMENT_ID]: DATA_PARTNER_PIXEL_ID_VALUE, [NQ_NAME]: NQ_NAME_QUERY_PARAM, [CATEGORY]: CATEGORY_VALUE, }))).to.equal(true); }); it('Test isBidRequestValid() with valid param(s): pid, nqName, categoryName', function () { expect(nanoBidAdapter.isBidRequestValid(getBidRequest({ - [DATA_PARTNER_PIXEL_ID]: DATA_PARTNER_PIXEL_ID_VALUE, + [SSP_PLACEMENT_ID]: DATA_PARTNER_PIXEL_ID_VALUE, [NQ_NAME]: NQ_NAME_QUERY_PARAM, [CATEGORY_NAME_QUERY_PARAM]: CATEGORY_NAME_QUERY_PARAM, }))).to.equal(true); }); it('Test isBidRequestValid() with valid param(s): pid, nqName, subId', function () { expect(nanoBidAdapter.isBidRequestValid(getBidRequest({ - [DATA_PARTNER_PIXEL_ID]: DATA_PARTNER_PIXEL_ID_VALUE, + [SSP_PLACEMENT_ID]: DATA_PARTNER_PIXEL_ID_VALUE, [NQ_NAME]: NQ_NAME_QUERY_PARAM, [SUB_ID]: SUB_ID_VALUE, }))).to.equal(true); }); it('Test isBidRequestValid() with valid param(s): pid, category', function () { expect(nanoBidAdapter.isBidRequestValid(getBidRequest({ - [DATA_PARTNER_PIXEL_ID]: DATA_PARTNER_PIXEL_ID_VALUE, + [SSP_PLACEMENT_ID]: DATA_PARTNER_PIXEL_ID_VALUE, [CATEGORY]: CATEGORY_VALUE, }))).to.equal(true); }); it('Test isBidRequestValid() with valid param(s): pid, category, subId', function () { expect(nanoBidAdapter.isBidRequestValid(getBidRequest({ - [DATA_PARTNER_PIXEL_ID]: DATA_PARTNER_PIXEL_ID_VALUE, + [SSP_PLACEMENT_ID]: DATA_PARTNER_PIXEL_ID_VALUE, [CATEGORY]: CATEGORY_VALUE, [SUB_ID]: SUB_ID_VALUE, }))).to.equal(true); }); it('Test isBidRequestValid() with valid param(s): pid, subId', function () { expect(nanoBidAdapter.isBidRequestValid(getBidRequest({ - [DATA_PARTNER_PIXEL_ID]: DATA_PARTNER_PIXEL_ID_VALUE, + [SSP_PLACEMENT_ID]: DATA_PARTNER_PIXEL_ID_VALUE, [SUB_ID]: SUB_ID_VALUE, }))).to.equal(true); }); it('Test isBidRequestValid() with valid param(s): pid, nq, category, subId', function () { expect(nanoBidAdapter.isBidRequestValid(getBidRequest({ - [DATA_PARTNER_PIXEL_ID]: DATA_PARTNER_PIXEL_ID_VALUE, + [SSP_PLACEMENT_ID]: DATA_PARTNER_PIXEL_ID_VALUE, [NQ]: NQ_VALUE, [CATEGORY]: CATEGORY_VALUE, [SUB_ID]: SUB_ID_VALUE, @@ -156,7 +140,7 @@ describe('nanointeractive adapter tests', function () { }); it('Test isBidRequestValid() with valid param(s): pid, nqName, categoryName, subId', function () { expect(nanoBidAdapter.isBidRequestValid(getBidRequest({ - [DATA_PARTNER_PIXEL_ID]: DATA_PARTNER_PIXEL_ID_VALUE, + [SSP_PLACEMENT_ID]: DATA_PARTNER_PIXEL_ID_VALUE, [NQ_NAME]: NQ_NAME_QUERY_PARAM, [CATEGORY_NAME]: CATEGORY_NAME_QUERY_PARAM, [SUB_ID]: SUB_ID_VALUE, @@ -164,7 +148,7 @@ describe('nanointeractive adapter tests', function () { }); it('Test isBidRequestValid() with valid param(s): pid, nq, category, subId, ref (value none)', function () { expect(nanoBidAdapter.isBidRequestValid(getBidRequest({ - [DATA_PARTNER_PIXEL_ID]: DATA_PARTNER_PIXEL_ID_VALUE, + [SSP_PLACEMENT_ID]: DATA_PARTNER_PIXEL_ID_VALUE, [NQ]: NQ_VALUE, [CATEGORY]: CATEGORY_VALUE, [SUB_ID]: SUB_ID_VALUE, @@ -173,7 +157,7 @@ describe('nanointeractive adapter tests', function () { }); it('Test isBidRequestValid() with valid param(s): pid, nq, category, subId, ref (value other)', function () { expect(nanoBidAdapter.isBidRequestValid(getBidRequest({ - [DATA_PARTNER_PIXEL_ID]: DATA_PARTNER_PIXEL_ID_VALUE, + [SSP_PLACEMENT_ID]: DATA_PARTNER_PIXEL_ID_VALUE, [NQ]: NQ_VALUE, [CATEGORY]: CATEGORY_VALUE, [SUB_ID]: SUB_ID_VALUE, @@ -193,30 +177,22 @@ describe('nanointeractive adapter tests', function () { let sandbox; - function getMocks () { - let mockWindowLocationAddress = 'http://some-location.test'; + function getMocks() { let mockOriginAddress = 'http://localhost'; let mockRefAddress = 'http://some-ref.test'; return { - 'windowLocationAddress': mockWindowLocationAddress, + 'windowLocationAddress': mockRefAddress, 'originAddress': mockOriginAddress, - 'refAddress': mockRefAddress, + 'refAddress': '', }; } - function setUpMocks (mockRefAddress = null) { + function setUpMocks() { + sinon.sandbox.restore(); sandbox = sinon.sandbox.create(); sandbox.stub(utils, 'getOrigin').callsFake(() => getMocks()['originAddress']); - sandbox.stub(utils, 'getTopWindowLocation').callsFake(() => { - return { - 'href': getMocks()['windowLocationAddress'] - }; - }); - if (mockRefAddress == null) { - sandbox.stub(utils, 'getTopWindowReferrer').callsFake(() => getMocks()['refAddress']); - } else { - sandbox.stub(utils, 'getTopWindowReferrer').callsFake(() => mockRefAddress); - } + sandbox.stub(utils, 'deepAccess').callsFake(() => getMocks()['windowLocationAddress']); + sandbox.stub(utils, 'getParameterByName').callsFake((arg) => { switch (arg) { case CATEGORY_NAME_QUERY_PARAM: @@ -227,39 +203,31 @@ describe('nanointeractive adapter tests', function () { }); } - function assert ( + function assert( request, expectedPid, expectedNq, expectedCategory, - expectedSubId, - expectedRef = getMocks()['refAddress'], - expectedOrigin = getMocks()['originAddress'], - expectedLocation = getMocks()['windowLocationAddress'] + expectedSubId ) { + const requestData = JSON.parse(request.data); + expect(request.method).to.equal('POST'); - expect(request.url).to.equal(ENGINE_BASE_URL); - expect(request.data).to.equal(JSON.stringify([ - getBidResponse( - expectedPid, - expectedNq, - expectedCategory, - expectedSubId, - expectedOrigin, - expectedRef, - expectedLocation, - ), - ])); + expect(request.url).to.equal(END_POINT_URL + '/hb'); + expect(requestData[0].pid).to.equal(expectedPid); + expect(requestData[0].nq.toString()).to.equal(expectedNq.toString()); + expect(requestData[0].category.toString()).to.equal(expectedCategory.toString()); + expect(requestData[0].subId).to.equal(expectedSubId); } - function tearDownMocks () { + function tearDownMocks() { sandbox.restore(); } it('Test buildRequest() - pid', function () { setUpMocks(); let requestParams = { - [DATA_PARTNER_PIXEL_ID]: DATA_PARTNER_PIXEL_ID_VALUE, + [SSP_PLACEMENT_ID]: DATA_PARTNER_PIXEL_ID_VALUE, }; let expectedPid = DATA_PARTNER_PIXEL_ID_VALUE; let expectedNq = [null]; @@ -274,7 +242,7 @@ describe('nanointeractive adapter tests', function () { it('Test buildRequest() - pid, nq', function () { setUpMocks(); let requestParams = { - [DATA_PARTNER_PIXEL_ID]: DATA_PARTNER_PIXEL_ID_VALUE, + [SSP_PLACEMENT_ID]: DATA_PARTNER_PIXEL_ID_VALUE, [NQ]: NQ_VALUE, }; let expectedPid = DATA_PARTNER_PIXEL_ID_VALUE; @@ -290,7 +258,7 @@ describe('nanointeractive adapter tests', function () { it('Test buildRequest() - pid, nq, category', function () { setUpMocks(); let requestParams = { - [DATA_PARTNER_PIXEL_ID]: DATA_PARTNER_PIXEL_ID_VALUE, + [SSP_PLACEMENT_ID]: DATA_PARTNER_PIXEL_ID_VALUE, [NQ]: NQ_VALUE, [CATEGORY]: CATEGORY_VALUE, }; @@ -308,7 +276,7 @@ describe('nanointeractive adapter tests', function () { setUpMocks(); let requestParams = { - [DATA_PARTNER_PIXEL_ID]: DATA_PARTNER_PIXEL_ID_VALUE, + [SSP_PLACEMENT_ID]: DATA_PARTNER_PIXEL_ID_VALUE, [NQ]: NQ_VALUE, [CATEGORY_NAME]: CATEGORY_NAME_QUERY_PARAM, }; @@ -325,7 +293,7 @@ describe('nanointeractive adapter tests', function () { it('Test buildRequest() - pid, nq, subId', function () { setUpMocks(); let requestParams = { - [DATA_PARTNER_PIXEL_ID]: DATA_PARTNER_PIXEL_ID_VALUE, + [SSP_PLACEMENT_ID]: DATA_PARTNER_PIXEL_ID_VALUE, [NQ]: NQ_VALUE, [SUB_ID]: SUB_ID_VALUE, }; @@ -342,7 +310,7 @@ describe('nanointeractive adapter tests', function () { it('Test buildRequest() - pid, category', function () { setUpMocks(); let requestParams = { - [DATA_PARTNER_PIXEL_ID]: DATA_PARTNER_PIXEL_ID_VALUE, + [SSP_PLACEMENT_ID]: DATA_PARTNER_PIXEL_ID_VALUE, [CATEGORY]: CATEGORY_VALUE, }; let expectedPid = DATA_PARTNER_PIXEL_ID_VALUE; @@ -358,7 +326,7 @@ describe('nanointeractive adapter tests', function () { it('Test buildRequest() - pid, category, subId', function () { setUpMocks(); let requestParams = { - [DATA_PARTNER_PIXEL_ID]: DATA_PARTNER_PIXEL_ID_VALUE, + [SSP_PLACEMENT_ID]: DATA_PARTNER_PIXEL_ID_VALUE, [CATEGORY]: CATEGORY_VALUE, [SUB_ID]: SUB_ID_VALUE, }; @@ -375,7 +343,7 @@ describe('nanointeractive adapter tests', function () { it('Test buildRequest() - pid, subId', function () { setUpMocks(); let requestParams = { - [DATA_PARTNER_PIXEL_ID]: DATA_PARTNER_PIXEL_ID_VALUE, + [SSP_PLACEMENT_ID]: DATA_PARTNER_PIXEL_ID_VALUE, [SUB_ID]: SUB_ID_VALUE, }; let expectedPid = DATA_PARTNER_PIXEL_ID_VALUE; @@ -391,7 +359,7 @@ describe('nanointeractive adapter tests', function () { it('Test buildRequest() - pid, nq, category, subId', function () { setUpMocks(); let requestParams = { - [DATA_PARTNER_PIXEL_ID]: DATA_PARTNER_PIXEL_ID_VALUE, + [SSP_PLACEMENT_ID]: DATA_PARTNER_PIXEL_ID_VALUE, [NQ]: NQ_VALUE, [CATEGORY]: CATEGORY_VALUE, [SUB_ID]: SUB_ID_VALUE, @@ -409,7 +377,7 @@ describe('nanointeractive adapter tests', function () { it('Test buildRequest() - pid, nqName, categoryName, subId', function () { setUpMocks(); let requestParams = { - [DATA_PARTNER_PIXEL_ID]: DATA_PARTNER_PIXEL_ID_VALUE, + [SSP_PLACEMENT_ID]: DATA_PARTNER_PIXEL_ID_VALUE, [NQ_NAME]: NQ_NAME_QUERY_PARAM, [CATEGORY_NAME]: CATEGORY_NAME_QUERY_PARAM, [SUB_ID]: SUB_ID_VALUE, @@ -424,44 +392,6 @@ describe('nanointeractive adapter tests', function () { assert(request, expectedPid, expectedNq, expectedCategory, expectedSubId); tearDownMocks(); }); - it('Test buildRequest() - pid, nq, category, subId, ref (value none)', function () { - setUpMocks(null); - let requestParams = { - [DATA_PARTNER_PIXEL_ID]: DATA_PARTNER_PIXEL_ID_VALUE, - [NQ]: NQ_VALUE, - [CATEGORY]: CATEGORY_VALUE, - [SUB_ID]: SUB_ID_VALUE, - [REF]: REF_NO_VALUE, - }; - let expectedPid = DATA_PARTNER_PIXEL_ID_VALUE; - let expectedNq = [NQ_VALUE]; - let expectedCategory = [CATEGORY_VALUE]; - let expectedSubId = SUB_ID_VALUE; - - let request = nanoBidAdapter.buildRequests([getBidRequest(requestParams)]); - - assert(request, expectedPid, expectedNq, expectedCategory, expectedSubId, null); - tearDownMocks(); - }); - it('Test buildRequest() - pid, nq, category, subId, ref (value other)', function () { - setUpMocks(null); - let requestParams = { - [DATA_PARTNER_PIXEL_ID]: DATA_PARTNER_PIXEL_ID_VALUE, - [NQ]: NQ_VALUE, - [CATEGORY]: CATEGORY_VALUE, - [SUB_ID]: SUB_ID_VALUE, - [REF]: REF_OTHER_VALUE, - }; - let expectedPid = DATA_PARTNER_PIXEL_ID_VALUE; - let expectedNq = [NQ_VALUE]; - let expectedCategory = [CATEGORY_VALUE]; - let expectedSubId = SUB_ID_VALUE; - - let request = nanoBidAdapter.buildRequests([getBidRequest(requestParams)]); - - assert(request, expectedPid, expectedNq, expectedCategory, expectedSubId, null); - tearDownMocks(); - }); it('Test interpretResponse() length', function () { let bids = nanoBidAdapter.interpretResponse({ body: [ diff --git a/test/spec/modules/nextMillenniumBidAdapter_spec.js b/test/spec/modules/nextMillenniumBidAdapter_spec.js new file mode 100644 index 00000000000..087f06d7e8e --- /dev/null +++ b/test/spec/modules/nextMillenniumBidAdapter_spec.js @@ -0,0 +1,91 @@ +import { expect } from 'chai'; +import { spec } from 'modules/nextMillenniumBidAdapter'; + +describe('nextMillenniumBidAdapterTests', function() { + let bidRequestData = { + bids: [ + { + bidId: 'transaction_1234', + bidder: 'nextMillennium', + params: { + placement_id: 12345 + }, + sizes: [[300, 250]] + } + ] + }; + let request = []; + + it('validate_pub_params', function() { + expect( + spec.isBidRequestValid({ + bidder: 'nextMillennium', + params: { + placement_id: 12345 + } + }) + ).to.equal(true); + }); + + it('validate_generated_params', function() { + let bidRequestData = [ + { + bidId: 'bid1234', + bidder: 'nextMillennium', + params: { placement_id: -1 }, + sizes: [[300, 250]] + } + ]; + let request = spec.buildRequests(bidRequestData); + expect(request[0].bidId).to.equal('bid1234'); + }); + + it('validate_getUserSyncs_function', function() { + expect(spec.getUserSyncs({ iframeEnabled: true })).to.have.lengthOf(1); + expect(spec.getUserSyncs({ iframeEnabled: false })).to.have.lengthOf(0); + + let pixel = spec.getUserSyncs({ iframeEnabled: true }); + expect(pixel[0].type).to.equal('iframe'); + expect(pixel[0].url).to.equal('https://brainlyads.com/hb/s2s/matching'); + }); + + it('validate_response_params', function() { + let serverResponse = { + body: { + cpm: 1.7, + width: 300, + height: 250, + creativeId: 'p35t0enob6twbt9mofjc8e', + ad: 'Hello! It\'s a test ad!' + } + }; + + let bids = spec.interpretResponse(serverResponse, bidRequestData.bids[0]); + expect(bids).to.have.lengthOf(1); + + let bid = bids[0]; + + expect(bid.creativeId).to.equal('p35t0enob6twbt9mofjc8e'); + expect(bid.ad).to.equal('Hello! It\'s a test ad!'); + expect(bid.cpm).to.equal(1.7); + expect(bid.width).to.equal(300); + expect(bid.height).to.equal(250); + expect(bid.currency).to.equal('USD'); + }); + + it('validate_response_params_with passback', function() { + let serverResponse = { + body: [ + { + hash: '1e100887dd614b0909bf6c49ba7f69fdd1360437', + content: 'Ad html passback', + size: [300, 250], + is_passback: 1 + } + ] + }; + let bids = spec.interpretResponse(serverResponse); + + expect(bids).to.have.lengthOf(0); + }); +}); diff --git a/test/spec/modules/nobidBidAdapter_spec.js b/test/spec/modules/nobidBidAdapter_spec.js new file mode 100644 index 00000000000..5134958d218 --- /dev/null +++ b/test/spec/modules/nobidBidAdapter_spec.js @@ -0,0 +1,256 @@ +import { expect } from 'chai'; +import { spec } from 'modules/nobidBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; +import { deepClone } from 'src/utils'; + +describe('Nobid Adapter', function () { + const adapter = newBidder(spec); + + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function () { + let bid = { + 'bidder': 'nobid', + 'params': { + 'siteId': 2 + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return true when required params found', function () { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'siteId': 2 + }; + + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not passed', function () { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'siteId': 0 + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + const SITE_ID = 2; + const REFERER = 'https://www.examplereferer.com'; + let bidRequests = [ + { + 'bidder': 'nobid', + 'params': { + 'siteId': SITE_ID + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + } + ]; + + let bidderRequest = { + refererInfo: {referer: REFERER} + } + + it('should add source and verison to the tag', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = JSON.parse(request.data); + expect(payload.sid).to.equal(SITE_ID); + expect(payload.l).to.exist.and.to.equal(encodeURIComponent(REFERER)); + expect(payload.tt).to.exist; + expect(payload.a).to.exist; + expect(payload.t).to.exist; + expect(payload.tz).to.exist; + expect(payload.r).to.exist; + expect(payload.lang).to.exist; + expect(payload.ref).to.exist; + expect(payload.gdpr).to.exist; + }); + + it('sends bid request to ENDPOINT via POST', function () { + const request = spec.buildRequests(bidRequests); + expect(request.url).to.contain('ads.servenobid.com/adreq'); + expect(request.method).to.equal('POST'); + }); + + it('should add gdpr consent information to the request', function () { + let consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; + let bidderRequest = { + 'bidderCode': 'nobid', + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'gdprConsent': { + consentString: consentString, + gdprApplies: true + } + }; + bidderRequest.bids = bidRequests; + + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.gdpr).to.exist; + expect(payload.gdpr.consentString).to.exist.and.to.equal(consentString); + expect(payload.gdpr.consentRequired).to.exist.and.to.be.true; + }); + }); + + describe('interpretResponse', function () { + const CREATIVE_ID_300x250 = 'CREATIVE-100'; + const ADUNIT_300x250 = 'ADUNIT-1'; + const ADMARKUP_300x250 = 'ADMARKUP-300x250'; + const PRICE_300x250 = 0.51; + const REQUEST_ID = '3db3773286ee59'; + const DEAL_ID = 'deal123'; + let response = { + country: 'US', + ip: '68.83.15.75', + device: 'COMPUTER', + site: 2, + bids: [ + {id: 1, + bdrid: 101, + divid: ADUNIT_300x250, + dealid: DEAL_ID, + creativeid: CREATIVE_ID_300x250, + size: {'w': 300, 'h': 250}, + adm: ADMARKUP_300x250, + price: '' + PRICE_300x250 + } + ] + }; + + it('should get correct bid response', function () { + let expectedResponse = [ + { + requestId: REQUEST_ID, + cpm: PRICE_300x250, + width: 300, + height: 250, + creativeId: CREATIVE_ID_300x250, + dealId: DEAL_ID, + currency: 'USD', + netRevenue: true, + ttl: 300, + ad: ADMARKUP_300x250, + mediaType: 'banner' + } + ]; + + let bidderRequest = { + bids: [{ + bidId: REQUEST_ID, + adUnitCode: ADUNIT_300x250 + }] + } + let result = spec.interpretResponse({ body: response }, {bidderRequest: bidderRequest}); + expect(result.length).to.equal(expectedResponse.length); + expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); + expect(result[0].requestId).to.equal(expectedResponse[0].requestId); + expect(result[0].cpm).to.equal(expectedResponse[0].cpm); + }); + + it('should get correct empty response', function () { + let bidderRequest = { + bids: [{ + bidId: REQUEST_ID, + adUnitCode: ADUNIT_300x250 + '1' + }] + } + let result = spec.interpretResponse({ body: response }, {bidderRequest: bidderRequest}); + expect(result.length).to.equal(0); + }); + + it('should get correct deal id', function () { + let expectedResponse = [ + { + requestId: REQUEST_ID, + cpm: PRICE_300x250, + width: 300, + height: 250, + creativeId: CREATIVE_ID_300x250, + dealId: DEAL_ID, + currency: 'USD', + netRevenue: true, + ttl: 300, + ad: ADMARKUP_300x250, + mediaType: 'banner' + } + ]; + + let bidderRequest = { + bids: [{ + bidId: REQUEST_ID, + adUnitCode: ADUNIT_300x250 + }] + } + let result = spec.interpretResponse({ body: response }, {bidderRequest: bidderRequest}); + expect(result.length).to.equal(expectedResponse.length); + expect(result[0].dealId).to.equal(expectedResponse[0].dealId); + }); + }); + + describe('getUserSyncs', function () { + const GDPR_CONSENT_STRING = 'GDPR_CONSENT_STRING'; + it('should get correct user sync when iframeEnabled', function () { + let pixel = spec.getUserSyncs({iframeEnabled: true}) + expect(pixel[0].type).to.equal('iframe'); + expect(pixel[0].url).to.equal('https://s3.amazonaws.com/nobid-public/sync.html'); + }); + + it('should get correct user sync when iframeEnabled and pixelEnabled', function () { + let pixel = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}) + expect(pixel[0].type).to.equal('iframe'); + expect(pixel[0].url).to.equal('https://s3.amazonaws.com/nobid-public/sync.html'); + }); + + it('should get correct user sync when iframeEnabled', function () { + let pixel = spec.getUserSyncs({iframeEnabled: true}, {}, {gdprApplies: true, consentString: GDPR_CONSENT_STRING}) + expect(pixel[0].type).to.equal('iframe'); + expect(pixel[0].url).to.equal('https://s3.amazonaws.com/nobid-public/sync.html?gdpr=1&gdpr_consent=' + GDPR_CONSENT_STRING); + }); + + it('should get correct user sync when !iframeEnabled', function () { + let pixel = spec.getUserSyncs({iframeEnabled: false}) + expect(pixel.length).to.equal(0); + }); + + it('should get correct user sync when !iframeEnabled', function () { + let pixel = spec.getUserSyncs({}) + expect(pixel.length).to.equal(0); + }); + }); + + describe('onTimeout', function (syncOptions) { + it('should increment timeoutTotal', function () { + let timeoutTotal = spec.onTimeout() + expect(timeoutTotal).to.equal(1); + }); + }); + + describe('onBidWon', function (syncOptions) { + it('should increment bidWonTotal', function () { + let bidWonTotal = spec.onBidWon() + expect(bidWonTotal).to.equal(1); + }); + }); +}); diff --git a/test/spec/modules/oneVideoBidAdapter_spec.js b/test/spec/modules/oneVideoBidAdapter_spec.js index f67105751df..58b90b0a017 100644 --- a/test/spec/modules/oneVideoBidAdapter_spec.js +++ b/test/spec/modules/oneVideoBidAdapter_spec.js @@ -10,9 +10,16 @@ describe('OneVideoBidAdapter', function () { beforeEach(function () { bidRequest = { + mediaTypes: { + video: { + context: 'instream', + playerSize: [640, 480] + } + }, bidder: 'oneVideo', sizes: [640, 480], bidId: '30b3efwfwe1e', + adUnitCode: 'video1', params: { video: { playerWidth: 640, @@ -21,7 +28,11 @@ describe('OneVideoBidAdapter', function () { protocols: [2, 5], api: [2], position: 1, - delivery: [2] + delivery: [2], + playbackmethod: [1, 5], + placement: 123, + sid: 134, + rewarded: 1 }, site: { id: 1, @@ -54,11 +65,34 @@ describe('OneVideoBidAdapter', function () { protocols: [2, 5], api: [2], position: 1, - delivery: [2] + delivery: [2], + playbackmethod: [1, 5], + placement: 123, + sid: 134, + rewarded: 1 } }; expect(spec.isBidRequestValid(bidRequest)).to.equal(false); }); + it('should return true when the "pubId" param is missing', function () { + bidRequest.params = { + video: { + playerWidth: 480, + playerHeight: 640, + mimes: ['video/mp4', 'application/javascript'], + protocols: [2, 5], + api: [2], + position: 1, + delivery: [2], + playbackmethod: [1, 5], + placement: 123, + sid: 134, + rewarded: 1 + }, + pubId: 'brxd' + }; + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }); it('should return false when no bid params are passed', function () { bidRequest.params = {}; @@ -82,9 +116,13 @@ describe('OneVideoBidAdapter', function () { const requests = spec.buildRequests([ bidRequest ]); const data = requests[0].data; const [ width, height ] = bidRequest.sizes; + const placement = bidRequest.params.video.placement; + const rewarded = bidRequest.params.video.rewarded; expect(data.imp[0].video.w).to.equal(width); expect(data.imp[0].video.h).to.equal(height); + expect(data.imp[0].ext.placement).to.equal(placement); expect(data.imp[0].bidfloor).to.equal(bidRequest.params.bidfloor); + expect(data.imp[0].ext.rewarded).to.equal(rewarded); }); it('must parse bid size from a nested array', function () { @@ -117,20 +155,23 @@ describe('OneVideoBidAdapter', function () { }); it('should return a valid bid response with just "adm"', function () { - const serverResponse = {seatbid: [{bid: [{id: 1, price: 6.01, adm: ''}]}], cur: 'USD'}; + 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, - creativeId: serverResponse.seatbid[0].bid[0].id, + 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, - netRevenue: true + netRevenue: true, + adUnitCode: bidRequest.adUnitCode, + renderer: (bidRequest.mediaTypes.video.context === 'outstream') ? newRenderer(bidRequest, bidResponse) : undefined, }; expect(bidResponse).to.deep.equal(o); }); @@ -163,5 +204,12 @@ describe('OneVideoBidAdapter', function () { const request = spec.buildRequests([ bidRequest ], bidderRequest); expect(request[0].data.user.ext.consent).to.equal(bidderRequest.gdprConsent.consentString); }); + + it('should send schain object', function () { + const requests = spec.buildRequests([ bidRequest ]); + const data = requests[0].data; + expect(data.source.ext.schain.nodes[0].sid).to.equal(bidRequest.params.video.sid); + expect(data.source.ext.schain.nodes[0].rid).to.equal(data.id); + }); }); }); diff --git a/test/spec/modules/onetagBidAdapter_spec.js b/test/spec/modules/onetagBidAdapter_spec.js index d56ad9e6dc5..93284566069 100644 --- a/test/spec/modules/onetagBidAdapter_spec.js +++ b/test/spec/modules/onetagBidAdapter_spec.js @@ -53,7 +53,7 @@ describe('onetag', function () { const data = JSON.parse(d); it('Should contains all keys', function () { expect(data).to.be.an('object'); - expect(data).to.have.all.keys('location', 'masked', 'referrer', 'sHeight', 'sWidth', 'timeOffset', 'date', 'wHeight', 'wWidth', 'bids'); + expect(data).to.have.all.keys('location', 'masked', 'referrer', 'sHeight', 'sWidth', 'timeOffset', 'date', 'wHeight', 'wWidth', 'oHeight', 'oWidth', 'aWidth', 'aHeight', 'sLeft', 'sTop', 'hLength', 'bids'); expect(data.location).to.be.a('string'); expect(data.masked).to.be.a('number'); expect(data.referrer).to.be.a('string'); @@ -61,6 +61,13 @@ describe('onetag', function () { expect(data.sWidth).to.be.a('number'); expect(data.wWidth).to.be.a('number'); expect(data.wHeight).to.be.a('number'); + expect(data.oHeight).to.be.a('number'); + expect(data.oWidth).to.be.a('number'); + expect(data.aWidth).to.be.a('number'); + expect(data.aHeight).to.be.a('number'); + expect(data.sLeft).to.be.a('number'); + expect(data.sTop).to.be.a('number'); + expect(data.hLength).to.be.a('number'); expect(data.timeOffset).to.be.a('number'); expect(data.date).to.be.a('string'); expect(data.bids).to.be.an('array'); @@ -146,4 +153,56 @@ describe('onetag', function () { }); }); }); + describe('getUserSyncs', function () { + const sync_endpoint = 'https://onetag-sys.com/usync/'; + it('Returns an iframe if iframeEnabled is true', function () { + const syncs = spec.getUserSyncs({iframeEnabled: true}); + expect(syncs).to.be.an('array'); + expect(syncs.length).to.equal(1); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.include(sync_endpoint); + }); + it('Returns an empty array if iframeEnabled is false', function () { + const syncs = spec.getUserSyncs({ iframeEnabled: false }); + expect(syncs).to.be.an('array').that.is.empty; + }); + it('Must pass gdpr params when gdprApplies is true', function () { + const syncs = spec.getUserSyncs({ iframeEnabled: true }, {}, { + gdprApplies: true, consentString: 'foo' + }); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.include(sync_endpoint); + expect(syncs[0].url).to.match(/(?:[?&](?:gdpr_consent=foo([^&]*)|gdpr=1([^&]*)|[^&]*))+$/); + }); + it('Must pass gdpr params when gdprApplies is false', function () { + const syncs = spec.getUserSyncs({ iframeEnabled: true }, {}, { + gdprApplies: false, consentString: 'foo' + }); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.include(sync_endpoint); + expect(syncs[0].url).to.match(/(?:[?&](?:gdpr_consent=foo([^&]*)|gdpr=0([^&]*)))+$/); + }); + it('Must pass gdpr consent string param when gdprApplies is undefined', function () { + const syncs = spec.getUserSyncs({ iframeEnabled: true }, {}, { + consentString: 'foo' + }); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.include(sync_endpoint); + expect(syncs[0].url).to.match(/(?:[?&](?:gdpr_consent=foo([^&]*)))+$/); + }); + it('Must pass no gdpr params when consentString is null', function () { + const syncs = spec.getUserSyncs({ iframeEnabled: true }, {}, { + consentString: null + }); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.include(sync_endpoint); + expect(syncs[0].url).to.not.match(/(?:[?&](?:gdpr_consent=([^&]*)|gdpr=([^&]*)))+$/); + }); + it('Must pass no gdpr param when gdprConsent is empty', function () { + const syncs = spec.getUserSyncs({ iframeEnabled: true }, {}, {}); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.include(sync_endpoint); + expect(syncs[0].url).to.not.match(/(?:[?&](?:gdpr_consent=([^&]*)|gdpr=([^&]*)))+$/); + }); + }); }); diff --git a/test/spec/modules/open8BidAdapter_spec.js b/test/spec/modules/open8BidAdapter_spec.js new file mode 100644 index 00000000000..e26811ed71c --- /dev/null +++ b/test/spec/modules/open8BidAdapter_spec.js @@ -0,0 +1,251 @@ +import { spec } from 'modules/open8BidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; + +const ENDPOINT = '//as.vt.open8.com/v1/control/prebid'; + +describe('Open8Adapter', function() { + const adapter = newBidder(spec); + + describe('isBidRequestValid', function() { + let bid = { + 'bidder': 'open8', + 'params': { + 'slotKey': 'slotkey1234' + }, + 'adUnitCode': 'adunit', + 'sizes': [[300, 250]], + 'bidId': 'bidid1234', + 'bidderRequestId': 'requestid1234', + 'auctionId': 'auctionid1234', + }; + + it('should return true when required params found', function() { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not passed', function() { + bid.params = { + ' slotKey': 0 + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function() { + let bidRequests = [ + { + 'bidder': 'open8', + 'params': { + 'slotKey': 'slotkey1234' + }, + 'adUnitCode': 'adunit', + 'sizes': [[300, 250]], + 'bidId': 'bidid1234', + 'bidderRequestId': 'requestid1234', + 'auctionId': 'auctionid1234', + } + ]; + + it('sends bid request to ENDPOINT via GET', function() { + const requests = spec.buildRequests(bidRequests); + expect(requests[0].url).to.equal(ENDPOINT); + expect(requests[0].method).to.equal('GET'); + }); + }); + describe('interpretResponse', function() { + const bannerResponse = { + slotKey: 'slotkey1234', + userId: 'userid1234', + impId: 'impid1234', + media: 'TEST_MEDIA', + nurl: '//example/win', + isAdReturn: true, + syncPixels: ['//example/sync/pixel.gif'], + syncIFs: [], + ad: { + bidId: 'TEST_BID_ID', + price: 1234.56, + creativeId: 'creativeid1234', + dealId: 'TEST_DEAL_ID', + currency: 'JPY', + ds: 876, + spd: 1234, + fa: 5678, + pr: 'pr1234', + mr: 'mr1234', + nurl: '//example/win', + adType: 2, + banner: { + w: 300, + h: 250, + adm: '
    ', + imps: ['//example.com/imp'] + } + } + }; + const videoResponse = { + slotKey: 'slotkey1234', + userId: 'userid1234', + impId: 'impid1234', + media: 'TEST_MEDIA', + isAdReturn: true, + syncPixels: ['//example/sync/pixel.gif'], + syncIFs: [], + ad: { + bidId: 'TEST_BID_ID', + price: 1234.56, + creativeId: 'creativeid1234', + dealId: 'TEST_DEAL_ID', + currency: 'JPY', + ds: 876, + spd: 1234, + fa: 5678, + pr: 'pr1234', + mr: 'mr1234', + nurl: '//example/win', + adType: 1, + video: { + purl: '//playerexample.js', + vastXml: '', + w: 320, + h: 180 + }, + } + }; + + it('should get correct banner bid response', function() { + let expectedResponse = [{ + 'slotKey': 'slotkey1234', + 'userId': 'userid1234', + 'impId': 'impid1234', + 'media': 'TEST_MEDIA', + 'ds': 876, + 'spd': 1234, + 'fa': 5678, + 'pr': 'pr1234', + 'mr': 'mr1234', + 'nurl': '//example/win', + 'requestId': 'requestid1234', + 'cpm': 1234.56, + 'creativeId': 'creativeid1234', + 'dealId': 'TEST_DEAL_ID', + 'width': 300, + 'height': 250, + 'ad': "
    ", + 'mediaType': 'banner', + 'currency': 'JPY', + 'ttl': 360, + 'netRevenue': true + }]; + + let bidderRequest; + let result = spec.interpretResponse({ body: bannerResponse }, { bidderRequest }); + expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); + }); + + it('handles video responses', function() { + let expectedResponse = [{ + 'slotKey': 'slotkey1234', + 'userId': 'userid1234', + 'impId': 'impid1234', + 'media': 'TEST_MEDIA', + 'ds': 876, + 'spd': 1234, + 'fa': 5678, + 'pr': 'pr1234', + 'mr': 'mr1234', + 'nurl': '//example/win', + 'requestId': 'requestid1234', + 'cpm': 1234.56, + 'creativeId': 'creativeid1234', + 'dealId': 'TEST_DEAL_ID', + 'width': 320, + 'height': 180, + 'vastXml': '', + 'mediaType': 'video', + 'renderer': {}, + 'adResponse': {}, + 'currency': 'JPY', + 'ttl': 360, + 'netRevenue': true + }]; + + let bidderRequest; + let result = spec.interpretResponse({ body: videoResponse }, { bidderRequest }); + expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); + }); + + it('handles nobid responses', function() { + let response = { + isAdReturn: false, + 'ad': {} + }; + + let bidderRequest; + let result = spec.interpretResponse({ body: response }, { bidderRequest }); + expect(result.length).to.equal(0); + }); + }); + + describe('getUserSyncs', function() { + const imgResponse1 = { + body: { + 'isAdReturn': true, + 'ad': { /* ad body */ }, + 'syncPixels': [ + 'https://example.test/1' + ] + } + }; + + const imgResponse2 = { + body: { + 'isAdReturn': true, + 'ad': { /* ad body */ }, + 'syncPixels': [ + 'https://example.test/2' + ] + } + }; + + const ifResponse = { + body: { + 'isAdReturn': true, + 'ad': { /* ad body */ }, + 'syncIFs': [ + 'https://example.test/3' + ] + } + }; + + it('should use a sync img url from first response', function() { + const syncs = spec.getUserSyncs({ pixelEnabled: true }, [imgResponse1, imgResponse2, ifResponse]); + expect(syncs).to.deep.equal([ + { + type: 'image', + url: 'https://example.test/1' + } + ]); + }); + + it('handle ifs response', function() { + const syncs = spec.getUserSyncs({ iframeEnabled: true }, [ifResponse]); + expect(syncs).to.deep.equal([ + { + type: 'iframe', + url: 'https://example.test/3' + } + ]); + }); + + it('handle empty response (e.g. timeout)', function() { + const syncs = spec.getUserSyncs({ pixelEnabled: true }, []); + expect(syncs).to.deep.equal([]); + }); + + it('returns empty syncs when not enabled', function() { + const syncs = spec.getUserSyncs({ pixelEnabled: false }, [imgResponse1]); + expect(syncs).to.deep.equal([]); + }); + }); +}); diff --git a/test/spec/modules/openxAnalyticsAdapter_spec.js b/test/spec/modules/openxAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..88ab3c4c580 --- /dev/null +++ b/test/spec/modules/openxAnalyticsAdapter_spec.js @@ -0,0 +1,449 @@ +import { expect } from 'chai'; +import openxAdapter from 'modules/openxAnalyticsAdapter'; +import { config } from 'src/config'; +import events from 'src/events'; +import CONSTANTS from 'src/constants.json'; +import * as utils from 'src/utils'; + +const { + EVENTS: { AUCTION_INIT, BID_REQUESTED, BID_RESPONSE, BID_TIMEOUT, BID_WON } +} = CONSTANTS; + +const SLOT_LOADED = 'slotOnload'; + +describe('openx analytics adapter', function() { + it('should require publisher id', function() { + sinon.spy(utils, 'logError'); + + openxAdapter.enableAnalytics(); + expect( + utils.logError.calledWith( + 'OpenX analytics adapter: publisherId is required.' + ) + ).to.be.true; + + utils.logError.restore(); + }); + + describe('sending analytics event', function() { + const auctionInit = { auctionId: 'add5eb0f-587d-441d-86ec-bbb722c70f79' }; + + const bidRequestedOpenX = { + auctionId: 'add5eb0f-587d-441d-86ec-bbb722c70f79', + auctionStart: 1540944528017, + bids: [ + { + adUnitCode: 'div-1', + bidId: '2f0c647b904e25', + bidder: 'openx', + params: { unit: '540249866' }, + transactionId: 'ac66c3e6-3118-4213-a3ae-8cdbe4f72873' + } + ], + start: 1540944528021 + }; + + const bidRequestedCloseX = { + auctionId: 'add5eb0f-587d-441d-86ec-bbb722c70f79', + auctionStart: 1540944528017, + bids: [ + { + adUnitCode: 'div-1', + bidId: '43d454020e9409', + bidder: 'closex', + params: { unit: '513144370' }, + transactionId: 'ac66c3e6-3118-4213-a3ae-8cdbe4f72873' + } + ], + start: 1540944528026 + }; + + const bidResponseOpenX = { + requestId: '2f0c647b904e25', + adId: '33dddbb61d359a', + adUnitCode: 'div-1', + auctionId: 'add5eb0f-587d-441d-86ec-bbb722c70f79', + cpm: 0.5, + creativeId: 'openx-crid', + responseTimestamp: 1540944528184, + ts: '2DAABBgABAAECAAIBAAsAAgAAAJccGApKSGt6NUZxRXYyHBbinsLj' + }; + + const bidResponseCloseX = { + requestId: '43d454020e9409', + adId: '43dddbb61d359a', + adUnitCode: 'div-1', + auctionId: 'add5eb0f-587d-441d-86ec-bbb722c70f79', + cpm: 0.3, + creativeId: 'closex-crid', + responseTimestamp: 1540944528196, + ts: 'hu1QWo6iD3MHs6NG_AQAcFtyNqsj9y4S0YRbX7Kb06IrGns0BABb' + }; + + const bidTimeoutOpenX = { + 0: { + adUnitCode: 'div-1', + auctionId: 'add5eb0f-587d-441d-86ec-bbb722c70f79', + bidId: '2f0c647b904e25' + } + }; + + const bidTimeoutCloseX = { + 0: { + adUnitCode: 'div-1', + auctionId: 'add5eb0f-587d-441d-86ec-bbb722c70f79', + bidId: '43d454020e9409' + } + }; + + const bidWonOpenX = { + requestId: '2f0c647b904e25', + adId: '33dddbb61d359a', + adUnitCode: 'div-1', + auctionId: 'add5eb0f-587d-441d-86ec-bbb722c70f79' + }; + + const bidWonCloseX = { + requestId: '43d454020e9409', + adId: '43dddbb61d359a', + adUnitCode: 'div-1', + auctionId: 'add5eb0f-587d-441d-86ec-bbb722c70f79' + }; + + function simulateAuction(events) { + let highestBid; + + events.forEach(event => { + const [eventType, args] = event; + openxAdapter.track({ eventType, args }); + if (eventType === BID_RESPONSE) { + highestBid = highestBid || args; + if (highestBid.cpm < args.cpm) { + highestBid = args; + } + } + }); + + openxAdapter.track({ + eventType: SLOT_LOADED, + args: { + slot: { + getAdUnitPath: () => { + return '/90577858/test_ad_unit'; + }, + getTargetingKeys: () => { + return []; + }, + getTargeting: sinon + .stub() + .withArgs('hb_adid') + .returns(highestBid ? [highestBid.adId] : []) + } + } + }); + } + + function getQueryData(url) { + const queryArgs = url.split('?')[1].split('&'); + return queryArgs.reduce((data, arg) => { + const [key, val] = arg.split('='); + data[key] = val; + return data; + }, {}); + } + + let xhr; + let requests; + + before(function() { + xhr = sinon.useFakeXMLHttpRequest(); + xhr.onCreate = request => requests.push(request); + sinon.stub(events, 'getEvents').returns([]); + openxAdapter.enableAnalytics({ + options: { + publisherId: 'test123' + } + }); + }); + + after(function() { + xhr.restore(); + events.getEvents.restore(); + openxAdapter.disableAnalytics(); + }); + + beforeEach(function() { + requests = []; + openxAdapter.reset(); + }); + + afterEach(function() {}); + + it('should not send request if no bid response', function() { + simulateAuction([ + [AUCTION_INIT, auctionInit], + [BID_REQUESTED, bidRequestedOpenX] + ]); + + expect(requests.length).to.equal(0); + }); + + it('should send 1 request to the right endpoint', function() { + simulateAuction([ + [AUCTION_INIT, auctionInit], + [BID_REQUESTED, bidRequestedOpenX], + [BID_RESPONSE, bidResponseOpenX] + ]); + + expect(requests.length).to.equal(1); + + const endpoint = requests[0].url.split('?')[0]; + expect(endpoint).to.equal('http://ads.openx.net/w/1.0/pban'); + }); + + describe('hb.ct, hb.rid, dddid, hb.asiid, hb.pubid', function() { + it('should always be in the query string', function() { + simulateAuction([ + [AUCTION_INIT, auctionInit], + [BID_REQUESTED, bidRequestedOpenX], + [BID_RESPONSE, bidResponseOpenX] + ]); + + const queryData = getQueryData(requests[0].url); + expect(queryData).to.include({ + 'hb.ct': String(bidRequestedOpenX.auctionStart), + 'hb.rid': auctionInit.auctionId, + dddid: bidRequestedOpenX.bids[0].transactionId, + 'hb.asiid': '/90577858/test_ad_unit', + 'hb.pubid': 'test123' + }); + }); + }); + + describe('hb.cur', function() { + it('should be in the query string if currency is set', function() { + sinon + .stub(config, 'getConfig') + .withArgs('currency.adServerCurrency') + .returns('bitcoin'); + + simulateAuction([ + [AUCTION_INIT, auctionInit], + [BID_REQUESTED, bidRequestedOpenX], + [BID_RESPONSE, bidResponseOpenX] + ]); + + config.getConfig.restore(); + + const queryData = getQueryData(requests[0].url); + expect(queryData).to.include({ + 'hb.cur': 'bitcoin' + }); + }); + + it('should not be in the query string if currency is not set', function() { + simulateAuction([ + [AUCTION_INIT, auctionInit], + [BID_REQUESTED, bidRequestedOpenX], + [BID_RESPONSE, bidResponseOpenX] + ]); + + const queryData = getQueryData(requests[0].url); + expect(queryData).to.not.have.key('hb.cur'); + }); + }); + + describe('hb.dcl, hb.dl, hb.tta, hb.ttr', function() { + it('should be in the query string if browser supports performance API', function() { + const timing = { + fetchStart: 1540944528000, + domContentLoadedEventEnd: 1540944528010, + loadEventEnd: 1540944528110 + }; + const originalPerf = window.top.performance; + window.top.performance = { timing }; + + const renderTime = 1540944528100; + sinon.stub(Date, 'now').returns(renderTime); + + simulateAuction([ + [AUCTION_INIT, auctionInit], + [BID_REQUESTED, bidRequestedOpenX], + [BID_RESPONSE, bidResponseOpenX] + ]); + + window.top.performance = originalPerf; + Date.now.restore(); + + const queryData = getQueryData(requests[0].url); + expect(queryData).to.include({ + 'hb.dcl': String(timing.domContentLoadedEventEnd - timing.fetchStart), + 'hb.dl': String(timing.loadEventEnd - timing.fetchStart), + 'hb.tta': String(bidRequestedOpenX.auctionStart - timing.fetchStart), + 'hb.ttr': String(renderTime - timing.fetchStart) + }); + }); + + it('should not be in the query string if browser does not support performance API', function() { + const originalPerf = window.top.performance; + window.top.performance = undefined; + + simulateAuction([ + [AUCTION_INIT, auctionInit], + [BID_REQUESTED, bidRequestedOpenX], + [BID_RESPONSE, bidResponseOpenX] + ]); + + window.top.performance = originalPerf; + + const queryData = getQueryData(requests[0].url); + expect(queryData).to.not.have.keys( + 'hb.dcl', + 'hb.dl', + 'hb.tta', + 'hb.ttr' + ); + }); + }); + + describe('ts, auid', function() { + it('OpenX is in auction and has a bid response', function() { + simulateAuction([ + [AUCTION_INIT, auctionInit], + [BID_REQUESTED, bidRequestedOpenX], + [BID_REQUESTED, bidRequestedCloseX], + [BID_RESPONSE, bidResponseOpenX], + [BID_RESPONSE, bidResponseCloseX] + ]); + + const queryData = getQueryData(requests[0].url); + expect(queryData).to.include({ + ts: bidResponseOpenX.ts, + auid: bidRequestedOpenX.bids[0].params.unit + }); + }); + + it('OpenX is in auction but no bid response', function() { + simulateAuction([ + [AUCTION_INIT, auctionInit], + [BID_REQUESTED, bidRequestedOpenX], + [BID_REQUESTED, bidRequestedCloseX], + [BID_RESPONSE, bidResponseCloseX] + ]); + + const queryData = getQueryData(requests[0].url); + expect(queryData).to.include({ + auid: bidRequestedOpenX.bids[0].params.unit + }); + expect(queryData).to.not.have.key('ts'); + }); + + it('OpenX is not in auction', function() { + simulateAuction([ + [AUCTION_INIT, auctionInit], + [BID_REQUESTED, bidRequestedCloseX], + [BID_RESPONSE, bidResponseCloseX] + ]); + + const queryData = getQueryData(requests[0].url); + expect(queryData).to.not.have.keys('auid', 'ts'); + }); + }); + + describe('hb.exn, hb.sts, hb.ets, hb.bv, hb.crid, hb.to', function() { + it('2 bidders in auction', function() { + simulateAuction([ + [AUCTION_INIT, auctionInit], + [BID_REQUESTED, bidRequestedOpenX], + [BID_REQUESTED, bidRequestedCloseX], + [BID_RESPONSE, bidResponseOpenX], + [BID_RESPONSE, bidResponseCloseX] + ]); + + const queryData = getQueryData(requests[0].url); + const auctionStart = bidRequestedOpenX.auctionStart; + expect(queryData).to.include({ + 'hb.exn': [ + bidRequestedOpenX.bids[0].bidder, + bidRequestedCloseX.bids[0].bidder + ].join(','), + 'hb.sts': [ + bidRequestedOpenX.start - auctionStart, + bidRequestedCloseX.start - auctionStart + ].join(','), + 'hb.ets': [ + bidResponseOpenX.responseTimestamp - auctionStart, + bidResponseCloseX.responseTimestamp - auctionStart + ].join(','), + 'hb.bv': [bidResponseOpenX.cpm, bidResponseCloseX.cpm].join(','), + 'hb.crid': [ + bidResponseOpenX.creativeId, + bidResponseCloseX.creativeId + ].join(','), + 'hb.to': [false, false].join(',') + }); + }); + + it('OpenX timed out', function() { + simulateAuction([ + [AUCTION_INIT, auctionInit], + [BID_REQUESTED, bidRequestedOpenX], + [BID_REQUESTED, bidRequestedCloseX], + [BID_RESPONSE, bidResponseCloseX], + [BID_TIMEOUT, bidTimeoutOpenX] + ]); + + const queryData = getQueryData(requests[0].url); + const auctionStart = bidRequestedOpenX.auctionStart; + expect(queryData).to.include({ + 'hb.exn': [ + bidRequestedOpenX.bids[0].bidder, + bidRequestedCloseX.bids[0].bidder + ].join(','), + 'hb.sts': [ + bidRequestedOpenX.start - auctionStart, + bidRequestedCloseX.start - auctionStart + ].join(','), + 'hb.ets': [ + undefined, + bidResponseCloseX.responseTimestamp - auctionStart + ].join(','), + 'hb.bv': [0, bidResponseCloseX.cpm].join(','), + 'hb.crid': [undefined, bidResponseCloseX.creativeId].join(','), + 'hb.to': [true, false].join(',') + }); + }); + }); + + describe('hb.we, hb.g1', function() { + it('OpenX won', function() { + simulateAuction([ + [AUCTION_INIT, auctionInit], + [BID_REQUESTED, bidRequestedOpenX], + [BID_RESPONSE, bidResponseOpenX], + [BID_WON, bidWonOpenX] + ]); + + const queryData = getQueryData(requests[0].url); + expect(queryData).to.include({ + 'hb.we': '0', + 'hb.g1': 'false' + }); + }); + + it('DFP won', function() { + simulateAuction([ + [AUCTION_INIT, auctionInit], + [BID_REQUESTED, bidRequestedOpenX], + [BID_RESPONSE, bidResponseOpenX] + ]); + + const queryData = getQueryData(requests[0].url); + expect(queryData).to.include({ + 'hb.we': '-1', + 'hb.g1': 'true' + }); + }); + }); + }); +}); diff --git a/test/spec/modules/openxBidAdapter_spec.js b/test/spec/modules/openxBidAdapter_spec.js index bce6b2e4acf..0002d25c37d 100644 --- a/test/spec/modules/openxBidAdapter_spec.js +++ b/test/spec/modules/openxBidAdapter_spec.js @@ -116,7 +116,7 @@ describe('OpenxAdapter', function () { ads: { version: 0, count: 1, - pixels: 'http://testpixels.net', + pixels: 'https://testpixels.net', ad: [DEFAULT_TEST_ARJ_AD_UNIT] } }; @@ -178,58 +178,135 @@ describe('OpenxAdapter', function () { }); }); - describe('when request is for a video ad', function () { - const videoBidWithMediaTypes = { - bidder: 'openx', - params: { - unit: '12345678', - delDomain: 'test-del-domain' - }, - adUnitCode: 'adunit-code', - mediaTypes: { - video: { - playerSize: [640, 480] - } - }, - bidId: '30b31c1838de1e', - bidderRequestId: '22edbae2733bf6', - auctionId: '1d1a030790a475', - transactionId: '4008d88a-8137-410b-aa35-fbfdabcb478e' - }; - - const videoBidWithMediaType = { - 'bidder': 'openx', - 'params': { - 'unit': '12345678', - 'delDomain': 'test-del-domain' - }, - 'adUnitCode': 'adunit-code', - 'mediaType': 'video', - 'sizes': [640, 480], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - 'transactionId': '4008d88a-8137-410b-aa35-fbfdabcb478e' - }; - it('should return true when required params found', function () { - expect(spec.isBidRequestValid(videoBidWithMediaTypes)).to.equal(true); + describe('when request is for a multiformat ad', function () { + describe('and request config uses mediaTypes video and banner', () => { + const multiformatBid = { + bidder: 'openx', + params: { + unit: '12345678', + delDomain: 'test-del-domain' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250]] + }, + video: { + playerSize: [300, 250] + } + }, + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + transactionId: '4008d88a-8137-410b-aa35-fbfdabcb478e' + }; + it('should return true multisize when required params found', function () { + expect(spec.isBidRequestValid(multiformatBid)).to.equal(true); + }); }); + }); + + describe('when request is for a video ad', function () { + describe('and request config uses mediaTypes', () => { + const videoBidWithMediaTypes = { + bidder: 'openx', + params: { + unit: '12345678', + delDomain: 'test-del-domain' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + video: { + playerSize: [640, 480] + } + }, + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + transactionId: '4008d88a-8137-410b-aa35-fbfdabcb478e' + }; + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(videoBidWithMediaTypes)).to.equal(true); + }); - it('should return false when required params are not passed', function () { - let videoBidWithMediaTypes = Object.assign({}, videoBidWithMediaTypes); - videoBidWithMediaTypes.params = {}; - expect(spec.isBidRequestValid(videoBidWithMediaTypes)).to.equal(false); + it('should return false when required params are not passed', function () { + let videoBidWithMediaTypes = Object.assign({}, videoBidWithMediaTypes); + videoBidWithMediaTypes.params = {}; + expect(spec.isBidRequestValid(videoBidWithMediaTypes)).to.equal(false); + }); + it('should send bid request to openx url via GET, with mediaType specified as video', function () { + const request = spec.buildRequests([videoBidWithMediaTypes]); + expect(request[0].url).to.equal(`https://${videoBidWithMediaTypes.params.delDomain}${URLBASEVIDEO}`); + expect(request[0].data.ph).to.be.undefined; + expect(request[0].method).to.equal('GET'); + }); }); + describe('and request config uses both delDomain and platform', () => { + const videoBidWithDelDomainAndPlatform = { + bidder: 'openx', + params: { + unit: '12345678', + delDomain: 'test-del-domain', + platform: '1cabba9e-cafe-3665-beef-f00f00f00f00' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + video: { + playerSize: [640, 480] + } + }, + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + transactionId: '4008d88a-8137-410b-aa35-fbfdabcb478e' + }; + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(videoBidWithDelDomainAndPlatform)).to.equal(true); + }); - it('should return true when required params found', function () { - expect(spec.isBidRequestValid(videoBidWithMediaType)).to.equal(true); + it('should return false when required params are not passed', function () { + let videoBidWithMediaTypes = Object.assign({}, videoBidWithDelDomainAndPlatform); + videoBidWithMediaTypes.params = {}; + expect(spec.isBidRequestValid(videoBidWithMediaTypes)).to.equal(false); + }); + it('should send bid request to openx url via GET, with mediaType specified as video', function () { + const request = spec.buildRequests([videoBidWithDelDomainAndPlatform]); + expect(request[0].url).to.equal(`https://u.openx.net${URLBASEVIDEO}`); + expect(request[0].data.ph).to.equal(videoBidWithDelDomainAndPlatform.params.platform); + expect(request[0].method).to.equal('GET'); + }); }); + describe('and request config uses mediaType', () => { + const videoBidWithMediaType = { + 'bidder': 'openx', + 'params': { + 'unit': '12345678', + 'delDomain': 'test-del-domain' + }, + 'adUnitCode': 'adunit-code', + 'mediaType': 'video', + 'sizes': [640, 480], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + 'transactionId': '4008d88a-8137-410b-aa35-fbfdabcb478e' + }; + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(videoBidWithMediaType)).to.equal(true); + }); - it('should return false when required params are not passed', function () { - let videoBidWithMediaType = Object.assign({}, videoBidWithMediaType); - delete videoBidWithMediaType.params; - videoBidWithMediaType.params = {}; - expect(spec.isBidRequestValid(videoBidWithMediaType)).to.equal(false); + it('should return false when required params are not passed', function () { + let videoBidWithMediaType = Object.assign({}, videoBidWithMediaType); + delete videoBidWithMediaType.params; + videoBidWithMediaType.params = {}; + expect(spec.isBidRequestValid(videoBidWithMediaType)).to.equal(false); + }); + it('should send bid request to openx url via GET, with mediaType specified as video', function () { + const request = spec.buildRequests([videoBidWithMediaType]); + expect(request[0].url).to.equal(`https://${videoBidWithMediaType.params.delDomain}${URLBASEVIDEO}`); + expect(request[0].data.ph).to.be.undefined; + expect(request[0].method).to.equal('GET'); + }); }); }); }); @@ -279,16 +356,97 @@ describe('OpenxAdapter', function () { 'bidderRequestId': 'test-bid-request-2', 'auctionId': 'test-auction-2' }]; + const bidRequestsWithPlatform = [{ + 'bidder': 'openx', + 'params': { + 'unit': '11', + 'platform': '1cabba9e-cafe-3665-beef-f00f00f00f00' + }, + 'adUnitCode': '/adunit-code/test-path', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + 'bidId': 'test-bid-id-1', + 'bidderRequestId': 'test-bid-request-1', + 'auctionId': 'test-auction-1' + }, { + 'bidder': 'openx', + 'params': { + 'unit': '11', + 'platform': '1cabba9e-cafe-3665-beef-f00f00f00f00' + }, + 'adUnitCode': '/adunit-code/test-path', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + 'bidId': 'test-bid-id-1', + 'bidderRequestId': 'test-bid-request-1', + 'auctionId': 'test-auction-1' + }]; it('should send bid request to openx url via GET, with mediaType specified as banner', function () { const request = spec.buildRequests(bidRequestsWithMediaType); - expect(request[0].url).to.equal('//' + bidRequestsWithMediaType[0].params.delDomain + URLBASE); + expect(request[0].url).to.equal('https://' + bidRequestsWithMediaType[0].params.delDomain + URLBASE); + expect(request[0].data.ph).to.be.undefined; expect(request[0].method).to.equal('GET'); }); it('should send bid request to openx url via GET, with mediaTypes specified with banner type', function () { const request = spec.buildRequests(bidRequestsWithMediaTypes); - expect(request[0].url).to.equal('//' + bidRequestsWithMediaTypes[0].params.delDomain + URLBASE); + expect(request[0].url).to.equal('https://' + bidRequestsWithMediaTypes[0].params.delDomain + URLBASE); + expect(request[0].data.ph).to.be.undefined; + expect(request[0].method).to.equal('GET'); + }); + + it('should send bid request to openx platform url via GET, if platform is present', function () { + const request = spec.buildRequests(bidRequestsWithPlatform); + expect(request[0].url).to.equal(`https://u.openx.net${URLBASE}`); + expect(request[0].data.ph).to.equal(bidRequestsWithPlatform[0].params.platform); + expect(request[0].method).to.equal('GET'); + }); + + it('should send bid request to openx platform url via GET, if both params present', function () { + const bidRequestsWithPlatformAndDelDomain = [{ + 'bidder': 'openx', + 'params': { + 'unit': '11', + 'delDomain': 'test-del-domain', + 'platform': '1cabba9e-cafe-3665-beef-f00f00f00f00' + }, + 'adUnitCode': '/adunit-code/test-path', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + 'bidId': 'test-bid-id-1', + 'bidderRequestId': 'test-bid-request-1', + 'auctionId': 'test-auction-1' + }, { + 'bidder': 'openx', + 'params': { + 'unit': '11', + 'delDomain': 'test-del-domain', + 'platform': '1cabba9e-cafe-3665-beef-f00f00f00f00' + }, + 'adUnitCode': '/adunit-code/test-path', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + 'bidId': 'test-bid-id-1', + 'bidderRequestId': 'test-bid-request-1', + 'auctionId': 'test-auction-1' + }]; + + const request = spec.buildRequests(bidRequestsWithPlatformAndDelDomain); + expect(request[0].url).to.equal(`https://u.openx.net${URLBASE}`); + expect(request[0].data.ph).to.equal(bidRequestsWithPlatform[0].params.platform); expect(request[0].method).to.equal('GET'); }); @@ -421,7 +579,7 @@ describe('OpenxAdapter', function () { params: { 'unit': '12345678', 'delDomain': 'test-del-domain', - 'customFloor': 1.5 + 'customFloor': 1.500001 } } ); @@ -781,6 +939,233 @@ describe('OpenxAdapter', function () { const request = spec.buildRequests(bidRequestsWithDnt); expect(request[0].data.ns).to.equal(1); }); + + describe('when schain is provided', function () { + let bidRequests; + let schainConfig; + const supplyChainNodePropertyOrder = ['asi', 'sid', 'hp', 'rid', 'name', 'domain']; + + beforeEach(function () { + schainConfig = { + 'ver': '1.0', + 'complete': 1, + 'nodes': [ + { + 'asi': 'exchange1.com', + 'sid': '1234', + 'hp': 1, + 'rid': 'bid-request-1', + 'name': 'publisher', + 'domain': 'publisher.com' + // omitted ext + }, + { + 'asi': 'exchange2.com', + 'sid': 'abcd', + 'hp': 1, + 'rid': 'bid-request-2', + // name field missing + 'domain': 'intermediary.com' + }, + { + 'asi': 'exchange3.com', + 'sid': '4321', + 'hp': 1, + // request id + // name field missing + 'domain': 'intermediary-2.com' + } + ] + }; + + bidRequests = [{ + 'bidder': 'openx', + 'params': { + 'unit': '11', + 'delDomain': 'test-del-domain' + }, + 'adUnitCode': '/adunit-code/test-path', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + 'bidId': 'test-bid-id-1', + 'bidderRequestId': 'test-bid-request-1', + 'auctionId': 'test-auction-1', + 'schain': schainConfig + }]; + }); + + it('should send a schain parameter with the proper delimiter symbols', function () { + const request = spec.buildRequests(bidRequests); + const dataParams = request[0].data; + const numNodes = schainConfig.nodes.length; + + // each node will have a ! to denote beginning of a new node + expect(dataParams.schain.match(/!/g).length).to.equal(numNodes); + + // 1 comma in the front for version + // 5 commas per node + expect(dataParams.schain.match(/,/g).length).to.equal(numNodes * 5 + 1); + }); + + it('should send a schain with the right version', function () { + const request = spec.buildRequests(bidRequests); + const dataParams = request[0].data; + let serializedSupplyChain = dataParams.schain.split('!'); + let version = serializedSupplyChain.shift().split(',')[0]; + + expect(version).to.equal(bidRequests[0].schain.ver); + }); + + it('should send a schain with the right complete value', function () { + const request = spec.buildRequests(bidRequests); + const dataParams = request[0].data; + let serializedSupplyChain = dataParams.schain.split('!'); + let isComplete = serializedSupplyChain.shift().split(',')[1]; + + expect(isComplete).to.equal(String(bidRequests[0].schain.complete)); + }); + + it('should send all available params in the right order', function () { + const request = spec.buildRequests(bidRequests); + const dataParams = request[0].data; + let serializedSupplyChain = dataParams.schain.split('!'); + serializedSupplyChain.shift(); + + serializedSupplyChain.forEach((serializedNode, nodeIndex) => { + let nodeProperties = serializedNode.split(','); + + nodeProperties.forEach((nodeProperty, propertyIndex) => { + let node = schainConfig.nodes[nodeIndex]; + let key = supplyChainNodePropertyOrder[propertyIndex]; + + expect(nodeProperty).to.equal(node[key] ? String(node[key]) : '', + `expected node '${nodeIndex}' property '${nodeProperty}' to key '${key}' to be the same value`) + }); + }); + }); + }); + + describe('when there are userid providers', function () { + describe('with publisher common id', function () { + it('should not send a pubcid query param when there is no crumbs.pubcid and no userId.pubcid defined in the bid requests', function () { + const request = spec.buildRequests(bidRequestsWithMediaType); + expect(request[0].data).to.not.have.any.keys('pubcid'); + }); + + it('should send a pubcid query param when crumbs.pubcid is defined in the bid requests', function () { + const bidRequestsWithPubcid = [{ + bidder: 'openx', + params: { + unit: '11', + delDomain: 'test-del-domain' + }, + crumbs: { + pubcid: 'c4a4c843-2368-4b5e-b3b1-6ee4702b9ad6' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + bidId: 'test-bid-id-1', + bidderRequestId: 'test-bid-request-1', + auctionId: 'test-auction-1' + }]; + const request = spec.buildRequests(bidRequestsWithPubcid); + expect(request[0].data.pubcid).to.equal('c4a4c843-2368-4b5e-b3b1-6ee4702b9ad6'); + }); + + it('should send a pubcid query param when userId.pubcid is defined in the bid requests', function () { + const bidRequestsWithPubcid = [{ + bidder: 'openx', + params: { + unit: '11', + delDomain: 'test-del-domain' + }, + userId: { + pubcid: 'c1a4c843-2368-4b5e-b3b1-6ee4702b9ad6' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + bidId: 'test-bid-id-1', + bidderRequestId: 'test-bid-request-1', + auctionId: 'test-auction-1' + }]; + const request = spec.buildRequests(bidRequestsWithPubcid); + expect(request[0].data.pubcid).to.equal('c1a4c843-2368-4b5e-b3b1-6ee4702b9ad6'); + }); + }); + + describe('with the trade desk unified id', function () { + it('should not send a tdid query param when there is no userId.tdid defined in the bid requests', function () { + const request = spec.buildRequests(bidRequestsWithMediaType); + expect(request[0].data).to.not.have.any.keys('ttduuid'); + }); + + it('should send a tdid query param when userId.tdid is defined in the bid requests', function () { + const bidRequestsWithTdid = [{ + bidder: 'openx', + params: { + unit: '11', + delDomain: 'test-del-domain' + }, + userId: { + tdid: '00000000-aaaa-1111-bbbb-222222222222' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + bidId: 'test-bid-id-1', + bidderRequestId: 'test-bid-request-1', + auctionId: 'test-auction-1' + }]; + const request = spec.buildRequests(bidRequestsWithTdid); + expect(request[0].data.ttduuid).to.equal('00000000-aaaa-1111-bbbb-222222222222'); + }); + }); + + describe('with the liveRamp identity link envelope', function () { + it('should not send a tdid query param when there is no userId.lre defined in the bid requests', function () { + const request = spec.buildRequests(bidRequestsWithMediaType); + expect(request[0].data).to.not.have.any.keys('lre'); + }); + + it('should send a lre query param when userId.lre is defined in the bid requests', function () { + const bidRequestsWithLiveRampEnvelope = [{ + bidder: 'openx', + params: { + unit: '11', + delDomain: 'test-del-domain' + }, + userId: { + idl_env: '00000000-aaaa-1111-bbbb-222222222222' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + bidId: 'test-bid-id-1', + bidderRequestId: 'test-bid-request-1', + auctionId: 'test-auction-1' + }]; + const request = spec.buildRequests(bidRequestsWithLiveRampEnvelope); + expect(request[0].data.lre).to.equal('00000000-aaaa-1111-bbbb-222222222222'); + }); + }); + }); }); describe('buildRequests for video', function () { @@ -820,13 +1205,13 @@ describe('OpenxAdapter', function () { it('should send bid request to openx url via GET, with mediaType as video', function () { const request = spec.buildRequests(bidRequestsWithMediaType); - expect(request[0].url).to.equal('//' + bidRequestsWithMediaType[0].params.delDomain + URLBASEVIDEO); + expect(request[0].url).to.equal('https://' + bidRequestsWithMediaType[0].params.delDomain + URLBASEVIDEO); expect(request[0].method).to.equal('GET'); }); it('should send bid request to openx url via GET, with mediaTypes having video parameter', function () { const request = spec.buildRequests(bidRequestsWithMediaTypes); - expect(request[0].url).to.equal('//' + bidRequestsWithMediaTypes[0].params.delDomain + URLBASEVIDEO); + expect(request[0].url).to.equal('https://' + bidRequestsWithMediaTypes[0].params.delDomain + URLBASEVIDEO); expect(request[0].method).to.equal('GET'); }); @@ -933,6 +1318,34 @@ describe('OpenxAdapter', function () { }); }); + describe('buildRequest for multi-format ad', function () { + const multiformatBid = { + bidder: 'openx', + params: { + unit: '12345678', + delDomain: 'test-del-domain' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250]] + }, + video: { + playerSize: [300, 250] + } + }, + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + transactionId: '4008d88a-8137-410b-aa35-fbfdabcb478e' + }; + + it('should send bid request to openx url via GET, with mediaType specified as banner', function () { + const request = spec.buildRequests([multiformatBid]); + expect(request[0].url).to.equal(`https://${multiformatBid.params.delDomain}${URLBASE}`); + }); + }); + describe('interpretResponse for banner ads', function () { beforeEach(function () { sinon.spy(userSync, 'registerSync'); @@ -948,7 +1361,7 @@ describe('OpenxAdapter', function () { width: '300', height: '250', tracking: { - impression: 'http://openx-d.openx.net/v/1.0/ri?ts=ts' + impression: 'https://openx-d.openx.net/v/1.0/ri?ts=ts' } }; @@ -983,7 +1396,7 @@ describe('OpenxAdapter', function () { bidRequest = { method: 'GET', - url: '//openx-d.openx.net/v/1.0/arj', + url: 'https://openx-d.openx.net/v/1.0/arj', data: {}, payload: {'bids': bidRequestConfigs, 'startTime': new Date()} }; @@ -1030,10 +1443,18 @@ describe('OpenxAdapter', function () { expect(bid.ts).to.equal(adUnitOverride.ts); }); + it('should return a brand ID', function () { + expect(bid.meta.brandId).to.equal(DEFAULT_TEST_ARJ_AD_UNIT.brand_id); + }); + + it('should return a brand ID', function () { + expect(bid.meta.dspid).to.equal(DEFAULT_TEST_ARJ_AD_UNIT.adv_id); + }); + it('should register a beacon', function () { resetBoPixel(); spec.interpretResponse({body: bidResponse}, bidRequest); - sinon.assert.calledWith(userSync.registerSync, 'image', 'openx', sinon.match(new RegExp(`\/\/openx-d\.openx\.net.*\/bo\?.*ts=${adUnitOverride.ts}`))); + sinon.assert.calledWith(userSync.registerSync, 'image', 'openx', sinon.match(new RegExp(`https:\/\/openx-d\.openx\.net.*\/bo\?.*ts=${adUnitOverride.ts}`))); }); }); @@ -1065,7 +1486,7 @@ describe('OpenxAdapter', function () { bidRequest = { method: 'GET', - url: '//openx-d.openx.net/v/1.0/arj', + url: 'https://openx-d.openx.net/v/1.0/arj', data: {}, payload: {'bids': bidRequestConfigs, 'startTime': new Date()} }; @@ -1101,7 +1522,7 @@ describe('OpenxAdapter', function () { bidRequest = { method: 'GET', - url: '//openx-d.openx.net/v/1.0/arj', + url: 'https://openx-d.openx.net/v/1.0/arj', data: {}, payload: {'bids': bidRequestConfigs, 'startTime': new Date()} }; @@ -1113,7 +1534,7 @@ describe('OpenxAdapter', function () { { 'version': 1, 'count': 1, - 'pixels': 'http://testpixels.net', + 'pixels': 'https://testpixels.net', 'ad': [] } }; @@ -1172,7 +1593,7 @@ describe('OpenxAdapter', function () { }]; const bidRequest = { method: 'GET', - url: '//openx-d.openx.net/v/1.0/arj', + url: 'https://openx-d.openx.net/v/1.0/arj', data: {}, payload: {'bids': bidRequests, 'startTime': new Date()} }; @@ -1254,13 +1675,13 @@ describe('OpenxAdapter', function () { }]; const bidRequestsWithMediaTypes = { method: 'GET', - url: '//openx-d.openx.net/v/1.0/avjp', + url: 'https://openx-d.openx.net/v/1.0/avjp', data: {}, payload: {'bid': bidsWithMediaTypes[0], 'startTime': new Date()} }; const bidRequestsWithMediaType = { method: 'GET', - url: '//openx-d.openx.net/v/1.0/avjp', + url: 'https://openx-d.openx.net/v/1.0/avjp', data: {}, payload: {'bid': bidsWithMediaType[0], 'startTime': new Date()} }; @@ -1269,21 +1690,20 @@ describe('OpenxAdapter', function () { 'width': '640', 'height': '480', 'adid': '5678', - 'vastUrl': 'http://testvast.com/vastpath?colo=http://test-colo.com&ph=test-ph&ts=test-ts', - 'pixels': 'http://testpixels.net' + 'vastUrl': 'https://testvast.com/vastpath?colo=https://test-colo.com&ph=test-ph&ts=test-ts', + 'pixels': 'https://testpixels.net' }; it('should return correct bid response with MediaTypes', function () { const expectedResponse = [ { 'requestId': '30b31c1838de1e', - 'bidderCode': 'openx', 'cpm': 1, 'width': '640', 'height': '480', 'mediaType': 'video', 'creativeId': '5678', - 'vastUrl': 'http://testvast.com', + 'vastUrl': 'https://testvast.com', 'ttl': 300, 'netRevenue': true, 'currency': 'USD' @@ -1298,13 +1718,12 @@ describe('OpenxAdapter', function () { const expectedResponse = [ { 'requestId': '30b31c1838de1e', - 'bidderCode': 'openx', 'cpm': 1, 'width': '640', 'height': '480', 'mediaType': 'video', 'creativeId': '5678', - 'vastUrl': 'http://testvast.com', + 'vastUrl': 'https://testvast.com', 'ttl': 300, 'netRevenue': true, 'currency': 'USD' @@ -1330,37 +1749,73 @@ describe('OpenxAdapter', function () { it('should register a beacon', function () { resetBoPixel(); spec.interpretResponse({body: bidResponse}, bidRequestsWithMediaTypes); - sinon.assert.calledWith(userSync.registerSync, 'image', 'openx', sinon.match(/^\/\/test-colo\.com/)) + sinon.assert.calledWith(userSync.registerSync, 'image', 'openx', sinon.match(/^https:\/\/test-colo\.com/)); sinon.assert.calledWith(userSync.registerSync, 'image', 'openx', sinon.match(/ph=test-ph/)); sinon.assert.calledWith(userSync.registerSync, 'image', 'openx', sinon.match(/ts=test-ts/)); }); }); describe('user sync', function () { - const syncUrl = 'http://testpixels.net'; + const syncUrl = 'https://testpixels.net'; + + describe('iframe sync', function () { + it('should register the pixel iframe from banner ad response', function () { + let syncs = spec.getUserSyncs( + {iframeEnabled: true}, + [{body: {ads: {pixels: syncUrl}}}] + ); + expect(syncs).to.deep.equal([{type: 'iframe', url: syncUrl}]); + }); - it('should register the pixel iframe from banner ad response', function () { - let syncs = spec.getUserSyncs( - {iframeEnabled: true}, - [{body: {ads: {pixels: syncUrl}}}] - ); - expect(syncs).to.deep.equal([{type: 'iframe', url: syncUrl}]); + it('should register the pixel iframe from video ad response', function () { + let syncs = spec.getUserSyncs( + {iframeEnabled: true}, + [{body: {pixels: syncUrl}}] + ); + expect(syncs).to.deep.equal([{type: 'iframe', url: syncUrl}]); + }); + + it('should register the default iframe if no pixels available', function () { + let syncs = spec.getUserSyncs( + {iframeEnabled: true}, + [] + ); + expect(syncs).to.deep.equal([{type: 'iframe', url: 'https://u.openx.net/w/1.0/pd'}]); + }); }); - it('should register the pixel iframe from video ad response', function () { - let syncs = spec.getUserSyncs( - {iframeEnabled: true}, - [{body: {pixels: syncUrl}}] - ); - expect(syncs).to.deep.equal([{type: 'iframe', url: syncUrl}]); + describe('pixel sync', function () { + it('should register the image pixel from banner ad response', function () { + let syncs = spec.getUserSyncs( + {pixelEnabled: true}, + [{body: {ads: {pixels: syncUrl}}}] + ); + expect(syncs).to.deep.equal([{type: 'image', url: syncUrl}]); + }); + + it('should register the image pixel from video ad response', function () { + let syncs = spec.getUserSyncs( + {pixelEnabled: true}, + [{body: {pixels: syncUrl}}] + ); + expect(syncs).to.deep.equal([{type: 'image', url: syncUrl}]); + }); + + it('should register the default image pixel if no pixels available', function () { + let syncs = spec.getUserSyncs( + {pixelEnabled: true}, + [] + ); + expect(syncs).to.deep.equal([{type: 'image', url: 'https://u.openx.net/w/1.0/pd'}]); + }); }); - it('should register the default iframe if no pixels available', function () { + it('should prioritize iframe over image for user sync', function () { let syncs = spec.getUserSyncs( - {iframeEnabled: true}, - [] + {iframeEnabled: true, pixelEnabled: true}, + [{body: {ads: {pixels: syncUrl}}}] ); - expect(syncs).to.deep.equal([{type: 'iframe', url: '//u.openx.net/w/1.0/pd'}]); + expect(syncs).to.deep.equal([{type: 'iframe', url: syncUrl}]); }); }); @@ -1402,7 +1857,7 @@ describe('OpenxAdapter', function () { if (adUnits.length) { mockedArjResponse.ads.count = adUnits.length; - mockedArjResponse.ads.ad = adUnits.map((adUnit, index) => { + mockedArjResponse.ads.ad = adUnits.map((adUnit) => { overrideKeyCheck(adUnit, DEFAULT_TEST_ARJ_AD_UNIT, 'OxArjAdUnit'); return Object.assign(utils.deepClone(DEFAULT_TEST_ARJ_AD_UNIT), adUnit); }); @@ -1433,4 +1888,5 @@ describe('OpenxAdapter', function () { return mockedAdUnit; } -}); +}) +; diff --git a/test/spec/modules/openxoutstreamBidAdapter_spec.js b/test/spec/modules/openxoutstreamBidAdapter_spec.js new file mode 100644 index 00000000000..634df1c8c6a --- /dev/null +++ b/test/spec/modules/openxoutstreamBidAdapter_spec.js @@ -0,0 +1,243 @@ +import {expect} from 'chai'; +import {spec} from 'modules/openxoutstreamBidAdapter'; +import {newBidder} from 'src/adapters/bidderFactory'; + +describe('OpenXOutstreamAdapter', function () { + const adapter = newBidder(spec); + const URLBASE = '/v/1.0/avjp'; + const BIDDER = 'openxoutstream'; + const div = document.createElement('div'); + const PLACEMENT_ID = '1986307928000988495'; + const YM_SCRIPT = `!function(e,t){if(void 0===t._ym){var a=Math.round(5*Math.random()/3)+'';t._ym='';var m=e.createElement('script');m.type='text/javascript',m.async=!0,m.src='//static.yieldmo.com/ym.'+a+'.js',(e.getElementsByTagName('head')[0]||e.getElementsByTagName('body')[0]).appendChild(m)}else t._ym instanceof String||void 0===t._ym.chkPls||t._ym.chkPls()}(document,window);`; + const PUBLISHER_ID = '1986307525700126029'; + const CR_ID = '2052941939925262540'; + const AD_ID = '1991358644725162800'; + + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function () { + describe('when request is for a banner ad', function () { + let bannerBid; + beforeEach(function () { + bannerBid = { + bidder: BIDDER, + params: {}, + adUnitCode: 'adunit-code', + mediaTypes: {banner: {}}, + sizes: [[300, 250], [300, 600]], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475' + }; + }); + it('should return false when there is no delivery domain', function () { + bannerBid.params = {'unit': '12345678'}; + expect(spec.isBidRequestValid(bannerBid)).to.equal(false); + }); + + describe('when there is a delivery domain', function () { + beforeEach(function () { + bannerBid.params = {delDomain: 'test-delivery-domain'} + }); + + it('should return false if there is no adunit id and sizes are defined', function () { + bannerBid.mediaTypes.banner.sizes = [720, 90]; + expect(spec.isBidRequestValid(bannerBid)).to.equal(false); + }); + + it('should return true if there is delivery domain and unit', function () { + bannerBid.params.unit = '12345678'; + expect(spec.isBidRequestValid(bannerBid)).to.equal(true); + }); + it('should return false if there is unit but no delivery domain', function () { + bannerBid.params = {unit: '12345678'}; + expect(spec.isBidRequestValid(bannerBid)).to.equal(false); + }); + it('shoud return false if there is no delivery domain and no unit', function () { + bannerBid.params = {}; + expect(spec.isBidRequestValid(bannerBid)).to.equal(false); + }) + }); + }); + }); + + describe('buildRequests for banner ads', function () { + const bidRequestsWithMediaType = [{ + 'bidder': BIDDER, + 'params': { + 'unit': '12345678', + 'delDomain': 'test-del-domain' + }, + 'adUnitCode': 'adunit-code', + 'mediaType': 'banner', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475' + }]; + + it('should send bid request to openx url via GET, with mediaType specified as banner', function () { + const request = spec.buildRequests(bidRequestsWithMediaType); + const params = bidRequestsWithMediaType[0].params; + expect(request[0].url).to.equal(`https://` + params.delDomain + URLBASE); + expect(request[0].method).to.equal('GET'); + }); + + it('should send ad unit ids, height, and width when any are defined', function () { + const bidRequestsWithUnitIds = [{ + 'bidder': BIDDER, + 'params': { + 'unit': '540141567', + 'height': '200', + 'width': '250', + 'delDomain': 'test-del-domain' + }, + 'adUnitCode': 'adunit-code', + sizes: [300, 250], + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, + 'bidId': 'test-bid-id-2', + 'bidderRequestId': 'test-bid-request-2', + 'auctionId': 'test-auction-2' + }, { + 'bidder': BIDDER, + 'params': { + 'delDomain': 'test-del-domain' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [300, 250], + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + 'bidId': 'test-bid-id-1', + 'bidderRequestId': 'test-bid-request-1', + 'auctionId': 'test-auction-1' + }]; + const request = spec.buildRequests(bidRequestsWithUnitIds); + expect(request[0].data.auid).to.equal(`${bidRequestsWithUnitIds[0].params.unit}`); + expect(request[0].data.vht).to.not.equal(`${bidRequestsWithUnitIds[0].params.height}`); + expect(request[0].data.vwd).to.not.equal(`${bidRequestsWithUnitIds[0].params.width}`); + expect(request[0].data.vht).to.equal('184'); + expect(request[0].data.vwd).to.equal('414'); + expect(request[0].data.aus).to.equal('304x184%7C412x184%7C375x184%7C414x184'); + }); + + describe('interpretResponse', function () { + let serverResponse; + let serverRequest; + + beforeEach(function () { + serverResponse = { + body: { + width: 300, + height: 250, + pub_rev: 3000, + bidderCode: 'openxoutstream', + vastUrl: 'test.vast.url', + mediaType: 'banner', + adid: '9874652394875' + }, + header: 'header?', + }; + serverRequest = { + payload: { + bid: { + bidId: '2d36ac90d654af', + }, + } + }; + }); + + it('should correctly reorder the server response', function () { + const newResponse = spec.interpretResponse(serverResponse, serverRequest); + const openHtmlTag = ''; + const closeHtmlTag = ''; + const sdkScript = createSdkScript().outerHTML; + const placementDiv = createPlacementDiv(); + placementDiv.dataset.pId = PUBLISHER_ID; + const placementDivString = placementDiv.outerHTML; + const adResponse = getTemplateAdResponse(serverResponse.body.vastUrl, PLACEMENT_ID); + const adResponseString = JSON.stringify(adResponse); + const ymAdsScript = ''; + expect(newResponse.length).to.be.equal(1); + expect(newResponse[0]).to.deep.equal({ + requestId: '2d36ac90d654af', + bidderCode: 'openxoutstream', + vastUrl: 'test.vast.url', + mediaType: 'banner', + cpm: 3, + width: 300, + height: 250, + creativeId: '9874652394875', + currency: 'USD', + netRevenue: true, + ttl: 300, + ad: openHtmlTag + placementDivString + ymAdsScript + sdkScript + closeHtmlTag + }); + }); + + it('should not add responses if the cpm is 0 or null', function () { + serverResponse.body.pub_rev = 0; + let response = spec.interpretResponse(serverResponse, serverRequest); + expect(response).to.deep.equal([]); + + serverResponse.body.pub_rev = null; + response = spec.interpretResponse(serverResponse, serverRequest); + expect(response).to.deep.equal([]) + }); + }); + }) + + function createSdkScript() { + const script = document.createElement('script'); + script.innerHTML = YM_SCRIPT; + return script; + } + function createPlacementDiv() { + div.id = `ym_${PLACEMENT_ID}`; + div.classList.add('ym'); + div.dataset.lfId = CR_ID; + return div + } + const getTemplateAdResponse = (vastUrl) => { + return { + loader: 'openxoutstream', + availability_zone: '', + data: [ + { + ads: [ + { + actions: {}, + adv_id: AD_ID, + configurables: { + cta_button_copy: 'Learn More', + vast_click_tracking: 'true', + vast_url: vastUrl, + }, + cr_id: CR_ID, + } + ], + column_count: 1, + configs: { + allowable_height: '248', + header_copy: 'You May Like', + ping: 'true', + }, + creative_format_id: 40, + css: '', + placement_id: PLACEMENT_ID, + } + ], + nc: 0, + }; + }; +}); diff --git a/test/spec/modules/optimeraBidAdapter_spec.js b/test/spec/modules/optimeraBidAdapter_spec.js index ff5793b5040..a0111ca9944 100644 --- a/test/spec/modules/optimeraBidAdapter_spec.js +++ b/test/spec/modules/optimeraBidAdapter_spec.js @@ -15,7 +15,7 @@ describe('OptimeraAdapter', function () { let bid = { 'bidder': 'optimera', 'params': { - 'clientID': '0' + 'clientID': '9999' }, 'adUnitCode': 'div-0', 'sizes': [[300, 250], [300, 600]], @@ -47,13 +47,12 @@ describe('OptimeraAdapter', function () { expect(request).to.exist; expect(request.method).to.equal('GET'); expect(request.payload).to.exist; - expect(request.data.t).to.exist; }); }) describe('interpretResponse', function () { let serverResponse = {}; - serverResponse.body = JSON.parse('{"div-0":["RB_K","728x90K"], "timestamp":["RB_K","1507565666"]}'); + serverResponse.body = JSON.parse('{"div-0":["RB_K","728x90K"], "timestamp":["RB_K","1507565666"], "device": { "de": { "div-0":["A1","728x90K"] }, "mo": { "div-0":["RB_K","728x90K"] }, "tb": { "div-0":["RB_K","728x90K"] } } }'); var bidRequest = { 'method': 'get', 'payload': [ @@ -73,4 +72,28 @@ describe('OptimeraAdapter', function () { expect(bidResponses[0].dealId[1]).to.equal('728x90K'); }); }); + + describe('interpretResponse with optional device param', function () { + let serverResponse = {}; + serverResponse.body = JSON.parse('{"div-0":["RB_K","728x90K"], "timestamp":["RB_K","1507565666"], "device": { "de": { "div-0":["A1","728x90K"] }, "mo": { "div-0":["RB_K","728x90K"] }, "tb": { "div-0":["RB_K","728x90K"] } } }'); + var bidRequest = { + 'method': 'get', + 'payload': [ + { + 'bidder': 'optimera', + 'params': { + 'clientID': '0', + 'device': 'de' + }, + 'adUnitCode': 'div-0', + 'bidId': '307440db8538ab' + } + ] + } + it('interpresResponse fires', function () { + let bidResponses = spec.interpretResponse(serverResponse, bidRequest); + expect(bidResponses[0].dealId[0]).to.equal('A1'); + expect(bidResponses[0].dealId[1]).to.equal('728x90K'); + }); + }); }); diff --git a/test/spec/modules/orbidderBidAdapter_spec.js b/test/spec/modules/orbidderBidAdapter_spec.js new file mode 100644 index 00000000000..1b76de9841a --- /dev/null +++ b/test/spec/modules/orbidderBidAdapter_spec.js @@ -0,0 +1,244 @@ +import {expect} from 'chai'; +import {spec} from 'modules/orbidderBidAdapter'; +import {newBidder} from 'src/adapters/bidderFactory'; +import openxAdapter from '../../../modules/openxAnalyticsAdapter'; +import {detectReferer} from 'src/refererDetection'; + +describe('orbidderBidAdapter', () => { + const adapter = newBidder(spec); + const defaultBidRequest = { + bidId: 'd66fa86787e0b0ca900a96eacfd5f0bb', + auctionId: 'ccc4c7cdfe11cfbd74065e6dd28413d8', + transactionId: 'd58851660c0c4461e4aa06344fc9c0c6', + bidRequestCount: 1, + adUnitCode: 'adunit-code', + sizes: [[300, 250], [300, 600]], + params: { + 'accountId': 'string1', + 'placementId': 'string2' + } + }; + + 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: 'http://localhost:9876/' + } + })[0]; + }; + + describe('inherited functions', () => { + it('exists and is a function', () => { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', () => { + it('should return true when required params found', () => { + expect(spec.isBidRequestValid(defaultBidRequest)).to.equal(true); + }); + + it('accepts optional profile object', () => { + const bidRequest = deepClone(defaultBidRequest); + bidRequest.params.profile = {'key': 'value'}; + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }); + + it('performs type checking', () => { + const bidRequest = deepClone(defaultBidRequest); + bidRequest.params.accountId = 1; // supposed to be a string + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + + it('doesn\'t accept malformed profile', () => { + const bidRequest = deepClone(defaultBidRequest); + bidRequest.params.profile = 'another not usable 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); + }); + + it('accepts optional bidfloor', () => { + const bidRequest = deepClone(defaultBidRequest); + bidRequest.params.bidfloor = 123; + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + + bidRequest.params.bidfloor = 1.23; + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }); + + it('doesn\'t accept malformed bidfloor', () => { + const bidRequest = deepClone(defaultBidRequest); + bidRequest.params.bidfloor = 'another not usable string'; + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + }); + + describe('buildRequests', () => { + const request = buildRequest(defaultBidRequest); + + 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.orbidderHost}/bid`); + }); + + it('sends correct bid parameters', () => { + // we add one, because we add referer information from bidderRequest object + expect(Object.keys(request.data).length).to.equal(Object.keys(defaultBidRequest).length + 1); + expect(request.data.pageUrl).to.equal('http://localhost:9876/'); + // expect(request.data.referrer).to.equal(''); + Object.keys(defaultBidRequest).forEach((key) => { + expect(defaultBidRequest[key]).to.equal(request.data[key]); + }); + }); + + it('handles empty gdpr object', () => { + const request = buildRequest(defaultBidRequest, { + gdprConsent: {} + }); + expect(request.data.gdprConsent.consentRequired).to.be.equal(false); + }); + + it('handles non-existent gdpr object', () => { + const request = buildRequest(defaultBidRequest, { + gdprConsent: null + }); + expect(request.data.gdprConsent).to.be.undefined; + }); + + it('handles properly filled gdpr object where gdpr applies', () => { + const consentString = 'someWeirdString'; + const request = buildRequest(defaultBidRequest, { + gdprConsent: { + gdprApplies: true, + consentString: consentString + } + }); + + const gdprConsent = request.data.gdprConsent; + expect(gdprConsent.consentRequired).to.be.equal(true); + expect(gdprConsent.consentString).to.be.equal(consentString); + }); + + it('handles properly filled gdpr object where gdpr does not apply', () => { + const consentString = 'someWeirdString'; + const request = buildRequest(defaultBidRequest, { + gdprConsent: { + gdprApplies: false, + consentString: consentString + } + }); + + const gdprConsent = request.data.gdprConsent; + expect(gdprConsent.consentRequired).to.be.equal(false); + expect(gdprConsent.consentString).to.be.equal(consentString); + }); + }); + + describe('onCallbackHandler', () => { + let ajaxStub; + const bidObj = { + adId: 'testId', + test: 1, + pageUrl: 'www.someurl.de', + referrer: 'www.somereferrer.de', + requestId: '123req456' + }; + + spec.bidParams['123req456'] = {'accountId': '123acc456'}; + + let bidObjClone = deepClone(bidObj); + bidObjClone.pageUrl = detectReferer(window)().referer; + bidObjClone.params = [{'accountId': '123acc456'}]; + + beforeEach(() => { + ajaxStub = sinon.stub(spec, 'ajaxCall'); + }); + + afterEach(() => { + ajaxStub.restore(); + }); + + it('calls orbidder\'s callback endpoint', () => { + spec.onBidWon(bidObj); + expect(ajaxStub.calledOnce).to.equal(true); + expect(ajaxStub.firstCall.args[0].indexOf('https://')).to.equal(0); + expect(ajaxStub.firstCall.args[0]).to.equal(`${spec.orbidderHost}/win`); + expect(ajaxStub.firstCall.args[1]).to.equal(JSON.stringify(bidObjClone)); + }); + }); + + describe('interpretResponse', () => { + it('should get correct bid response', () => { + const serverResponse = [ + { + 'width': 300, + 'height': 250, + 'creativeId': '29681110', + 'ad': '', + 'cpm': 0.5, + 'requestId': '30b31c1838de1e', + 'ttl': 60, + 'netRevenue': true, + 'currency': 'EUR' + } + ]; + + const expectedResponse = [ + { + 'requestId': '30b31c1838de1e', + 'cpm': 0.5, + 'creativeId': '29681110', + 'width': 300, + 'height': 250, + 'ttl': 60, + 'currency': 'EUR', + 'ad': '', + 'netRevenue': true + } + ]; + + const result = spec.interpretResponse({body: serverResponse}); + + expect(result.length).to.equal(expectedResponse.length); + Object.keys(expectedResponse[0]).forEach((key) => { + expect(result[0][key]).to.equal(expectedResponse[0][key]); + }); + }); + + it('handles broken server response', () => { + const serverResponse = [ + { + 'ad': '', + 'cpm': 0.5, + 'requestId': '30b31c1838de1e', + '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/otmBidAdapter_spec.js b/test/spec/modules/otmBidAdapter_spec.js new file mode 100644 index 00000000000..f3a98d43e57 --- /dev/null +++ b/test/spec/modules/otmBidAdapter_spec.js @@ -0,0 +1,105 @@ +import {expect} from 'chai'; +import {spec} from 'modules/otmBidAdapter'; + +describe('otmBidAdapterTests', function () { + it('validate_pub_params', function () { + expect(spec.isBidRequestValid({ + bidder: 'otm', + params: { + tid: '123', + bidfloor: 20 + } + })).to.equal(true); + }); + + it('validate_generated_params', function () { + let bidRequestData = [{ + bidId: 'bid1234', + bidder: 'otm', + params: { + tid: '123', + bidfloor: 20 + }, + sizes: [[240, 400]] + }]; + + let request = spec.buildRequests(bidRequestData); + let req_data = request[0].data; + + expect(req_data.bidid).to.equal('bid1234'); + }); + + it('validate_best_size_select', function () { + // when: + let bidRequestData = [{ + bidId: 'bid1234', + bidder: 'otm', + params: { + tid: '123', + bidfloor: 20 + }, + sizes: [[300, 500], [300, 600], [240, 400], [300, 50]] + }]; + + let request = spec.buildRequests(bidRequestData); + let req_data = request[0].data; + + // then: + expect(req_data.w).to.equal(240); + expect(req_data.h).to.equal(400); + + // when: + bidRequestData = [{ + bidId: 'bid1234', + bidder: 'otm', + params: { + tid: '123', + bidfloor: 20 + }, + sizes: [[200, 240], [400, 440]] + }]; + + request = spec.buildRequests(bidRequestData); + req_data = request[0].data; + + // then: + expect(req_data.w).to.equal(200); + expect(req_data.h).to.equal(240); + }); + + it('validate_response_params', function () { + let bidRequestData = { + data: { + bidId: 'bid1234' + } + }; + + let serverResponse = { + body: [ + { + 'auctionid': '3c6f8e22-541b-485c-9214-e974d9fb1b6f', + 'cpm': 847.097, + 'ad': 'test html', + 'w': 240, + 'h': 400, + 'currency': 'RUB', + 'ttl': 300, + 'creativeid': '1_7869053', + 'bidid': '101f211def7c99', + 'transactionid': 'transaction_id_1' + } + ] + }; + + let bids = spec.interpretResponse(serverResponse, bidRequestData); + expect(bids).to.have.lengthOf(1); + let bid = bids[0]; + expect(bid.cpm).to.equal(847.097); + expect(bid.currency).to.equal('RUB'); + expect(bid.width).to.equal(240); + expect(bid.height).to.equal(400); + expect(bid.netRevenue).to.equal(true); + expect(bid.requestId).to.equal('101f211def7c99'); + expect(bid.ad).to.equal('test html'); + }); +}); diff --git a/test/spec/modules/outconBidAdapter_spec.js b/test/spec/modules/outconBidAdapter_spec.js new file mode 100644 index 00000000000..a263bc9dbf9 --- /dev/null +++ b/test/spec/modules/outconBidAdapter_spec.js @@ -0,0 +1,103 @@ +import { expect } from 'chai'; +import { spec } from '../../../modules/outconBidAdapter'; + +describe('outconBidAdapter', function () { + describe('bidRequestValidity', function () { + it('Check the bidRequest with pod param', function () { + expect(spec.isBidRequestValid({ + bidder: 'outcon', + params: { + pod: '5d603538eba7192ae14e39a4', + env: 'test' + } + })).to.equal(true); + }); + it('Check the bidRequest with internalID and publisherID params', function () { + expect(spec.isBidRequestValid({ + bidder: 'outcon', + params: { + internalId: '12345678', + publisher: '5d5d66f2306ea4114a37c7c2', + env: 'test' + } + })).to.equal(true); + }); + }); + + describe('buildRequests', function () { + it('Build requests with pod param', function () { + expect(spec.buildRequests([{ + bidder: 'outcon', + params: { + pod: '5d603538eba7192ae14e39a4', + env: 'test' + } + }])).to.have.keys('method', 'url', 'data'); + }); + + it('Build requests with internalID and publisherID params', function () { + expect(spec.buildRequests([{ + bidder: 'outcon', + params: { + internalId: '12345678', + publisher: '5d5d66f2306ea4114a37c7c2', + env: 'test' + } + }])).to.have.keys('method', 'url', 'data'); + }); + }); + + describe('interpretResponse', function () { + const bidRequest = { + method: 'GET', + url: 'http://test.outcondigital.com:8048/ad/', + data: { + pod: '5d603538eba7192ae14e39a4', + env: 'test', + vast: 'true' + } + }; + const bidResponse = { + body: { + cpm: 0.10, + cur: 'USD', + exp: 10, + creatives: [ + { + url: 'http://test.outcondigital.com/uploads/5d42e7a7306ea4689b67c122/frutas.mp4', + size: 3, + width: 1920, + height: 1080, + codec: 'video/mp4' + } + ], + ad: '5d6e6aef22063e392bf7f564', + type: 'video', + campaign: '5d42e44b306ea469593c76a2', + trackingURL: 'http://test.outcondigital.com:8048/ad/track?track=5d6e6aef22063e392bf7f564', + vastURL: 'http://test.outcondigital.com:8048/outcon.xml?impression=5d6e6aef22063e392bf7f564&demo=true' + }, + }; + it('check all the keys that are needed to interpret the response', function () { + const result = spec.interpretResponse(bidResponse, bidRequest); + let requiredKeys = [ + 'requestId', + 'cpm', + 'width', + 'height', + 'creativeId', + 'currency', + 'netRevenue', + 'ttl', + 'ad', + 'vastImpUrl', + 'mediaType', + 'vastUrl' + ]; + let resultKeys = Object.keys(result[0]); + resultKeys.forEach(function(key) { + expect(requiredKeys.indexOf(key) !== -1).to.equal(true); + }); + }) + }); +}); diff --git a/test/spec/modules/ozoneBidAdapter_spec.js b/test/spec/modules/ozoneBidAdapter_spec.js new file mode 100644 index 00000000000..a0b51ff7a9f --- /dev/null +++ b/test/spec/modules/ozoneBidAdapter_spec.js @@ -0,0 +1,992 @@ +import { expect } from 'chai'; +import { spec, getWidthAndHeightFromVideoObject, playerSizeIsNestedArray, defaultSize } from 'modules/ozoneBidAdapter'; +import { config } from 'src/config'; +import {Renderer} from '../../../src/Renderer'; +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 validBidRequests = [ + { + adUnitCode: 'div-gpt-ad-1460505748561-0', + auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', + bidId: '2899ec066a91ff8', + bidRequestsCount: 1, + bidder: 'ozone', + bidderRequestId: '1c1586b27a1b5c8', + crumbs: {pubcid: '203a0692-f728-4856-87f6-9a25a6b63715'}, + params: { publisherId: '9876abcd12-3', customData: {'gender': 'bart', 'age': 'low'}, lotameData: {'Profile': {'tpid': 'c8ef27a0d4ba771a81159f0d2e792db4', 'Audiences': {'Audience': [{'id': '99999', 'abbr': 'sports'}, {'id': '88888', 'abbr': 'movie'}, {'id': '77777', 'abbr': 'blogger'}], 'ThirdPartyAudience': [{'id': '123', 'name': 'Automobiles'}, {'id': '456', 'name': 'Ages: 30-39'}]}}}, placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { id: '2899ec066a91ff8', tagid: 'undefined', secure: 1, banner: { format: [{ w: 300, h: 250 }, { w: 300, h: 600 }], h: 250, topframe: 1, w: 300 } } ] }, + sizes: [[300, 250], [300, 600]], + transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87' + } +]; +var validBidRequestsMinimal = [ + { + adUnitCode: 'div-gpt-ad-1460505748561-0', + auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', + bidId: '2899ec066a91ff8', + bidRequestsCount: 1, + bidder: 'ozone', + bidderRequestId: '1c1586b27a1b5c8', + params: { publisherId: '9876abcd12-3', placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { id: '2899ec066a91ff8', tagid: 'undefined', secure: 1, banner: { format: [{ w: 300, h: 250 }, { w: 300, h: 600 }], h: 250, topframe: 1, w: 300 } } ] }, + sizes: [[300, 250], [300, 600]], + transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87' + } +]; +var validBidRequestsNoSizes = [ + { + adUnitCode: 'div-gpt-ad-1460505748561-0', + auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', + bidId: '2899ec066a91ff8', + bidRequestsCount: 1, + bidder: 'ozone', + bidderRequestId: '1c1586b27a1b5c8', + crumbs: {pubcid: '203a0692-f728-4856-87f6-9a25a6b63715'}, + params: { publisherId: '9876abcd12-3', customData: {'gender': 'bart', 'age': 'low'}, lotameData: {'Profile': {'tpid': 'c8ef27a0d4ba771a81159f0d2e792db4', 'Audiences': {'Audience': [{'id': '99999', 'abbr': 'sports'}, {'id': '88888', 'abbr': 'movie'}, {'id': '77777', 'abbr': 'blogger'}], 'ThirdPartyAudience': [{'id': '123', 'name': 'Automobiles'}, {'id': '456', 'name': 'Ages: 30-39'}]}}}, placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { id: '2899ec066a91ff8', tagid: 'undefined', secure: 1, banner: { format: [{ w: 300, h: 250 }, { w: 300, h: 600 }], h: 250, topframe: 1, w: 300 } } ] }, + transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87' + } +]; + +var validBidRequestsWithBannerMediaType = [ + { + adUnitCode: 'div-gpt-ad-1460505748561-0', + auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', + bidId: '2899ec066a91ff8', + bidRequestsCount: 1, + bidder: 'ozone', + bidderRequestId: '1c1586b27a1b5c8', + crumbs: {pubcid: '203a0692-f728-4856-87f6-9a25a6b63715'}, + params: { publisherId: '9876abcd12-3', customData: {'gender': 'bart', 'age': 'low'}, lotameData: {'Profile': {'tpid': 'c8ef27a0d4ba771a81159f0d2e792db4', 'Audiences': {'Audience': [{'id': '99999', 'abbr': 'sports'}, {'id': '88888', 'abbr': 'movie'}, {'id': '77777', 'abbr': 'blogger'}], 'ThirdPartyAudience': [{'id': '123', 'name': 'Automobiles'}, {'id': '456', 'name': 'Ages: 30-39'}]}}}, placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { id: '2899ec066a91ff8', tagid: 'undefined', secure: 1, banner: { format: [{ w: 300, h: 250 }, { w: 300, h: 600 }], h: 250, topframe: 1, w: 300 } } ] }, + mediaTypes: {banner: {sizes: [[300, 250], [300, 600]]}}, + transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87' + } +]; +var validBidRequestsWithNonBannerMediaTypesAndValidOutstreamVideo = [ + { + adUnitCode: 'div-gpt-ad-1460505748561-0', + auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', + bidId: '2899ec066a91ff8', + bidRequestsCount: 1, + bidder: 'ozone', + bidderRequestId: '1c1586b27a1b5c8', + crumbs: {pubcid: '203a0692-f728-4856-87f6-9a25a6b63715'}, + params: { publisherId: '9876abcd12-3', customData: {'gender': 'bart', 'age': 'low'}, lotameData: {'Profile': {'tpid': 'c8ef27a0d4ba771a81159f0d2e792db4', 'Audiences': {'Audience': [{'id': '99999', 'abbr': 'sports'}, {'id': '88888', 'abbr': 'movie'}, {'id': '77777', 'abbr': 'blogger'}], 'ThirdPartyAudience': [{'id': '123', 'name': 'Automobiles'}, {'id': '456', 'name': 'Ages: 30-39'}]}}}, 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]}, native: {info: 'dummy data'}}, + transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87' + } +]; + +var validBidderRequest = { + auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', + auctionStart: 1536838908986, + bidderCode: 'ozone', + bidderRequestId: '1c1586b27a1b5c8', + bids: [{ + adUnitCode: 'div-gpt-ad-1460505748561-0', + auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', + bidId: '2899ec066a91ff8', + bidRequestsCount: 1, + bidder: 'ozone', + bidderRequestId: '1c1586b27a1b5c8', + crumbs: {pubcid: '203a0692-f728-4856-87f6-9a25a6b63715'}, + params: { publisherId: '9876abcd12-3', customData: {'gender': 'bart', 'age': 'low'}, lotameData: {'Profile': {'tpid': 'c8ef27a0d4ba771a81159f0d2e792db4', 'Audiences': {'Audience': [{'id': '99999', 'abbr': 'sports'}, {'id': '88888', 'abbr': 'movie'}, {'id': '77777', 'abbr': 'blogger'}], 'ThirdPartyAudience': [{'id': '123', 'name': 'Automobiles'}, {'id': '456', 'name': 'Ages: 30-39'}]}}}, placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { banner: { topframe: 1, w: 300, h: 250, format: [{ w: 300, h: 250 }, { w: 300, h: 600 }] }, id: '2899ec066a91ff8', secure: 1, tagid: 'undefined' } ] }, + sizes: [[300, 250], [300, 600]], + transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87' + }], + doneCbCallCount: 1, + start: 1536838908987, + timeout: 3000 +}; + +// bidder request with GDPR - change the values for testing: +// gdprConsent.gdprApplies (true/false) +// gdprConsent.vendorData.purposeConsents (make empty, make null, remove it) +// gdprConsent.vendorData.vendorConsents (remove 524, remove all, make the element null, remove it) +var bidderRequestWithFullGdpr = { + auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', + auctionStart: 1536838908986, + bidderCode: 'ozone', + bidderRequestId: '1c1586b27a1b5c8', + bids: [{ + adUnitCode: 'div-gpt-ad-1460505748561-0', + auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', + bidId: '2899ec066a91ff8', + bidRequestsCount: 1, + bidder: 'ozone', + bidderRequestId: '1c1586b27a1b5c8', + crumbs: {pubcid: '203a0692-f728-4856-87f6-9a25a6b63715'}, + params: { publisherId: '9876abcd12-3', customData: {'gender': 'bart', 'age': 'low'}, lotameData: {'Profile': {'tpid': 'c8ef27a0d4ba771a81159f0d2e792db4', 'Audiences': {'Audience': [{'id': '99999', 'abbr': 'sports'}, {'id': '88888', 'abbr': 'movie'}, {'id': '77777', 'abbr': 'blogger'}], 'ThirdPartyAudience': [{'id': '123', 'name': 'Automobiles'}, {'id': '456', 'name': 'Ages: 30-39'}]}}}, placementId: '1310000099', siteId: '1234567890', id: 'fea37168-78f1-4a23-a40e-88437a99377e', auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', imp: [ { banner: { topframe: 1, w: 300, h: 250, format: [{ w: 300, h: 250 }, { w: 300, h: 600 }] }, id: '2899ec066a91ff8', secure: 1, tagid: 'undefined' } ] }, + sizes: [[300, 250], [300, 600]], + transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87' + }], + doneCbCallCount: 1, + start: 1536838908987, + timeout: 3000, + gdprConsent: { + 'consentString': 'BOh7mtYOh7mtYAcABBENCU-AAAAncgPIXJiiAoao0PxBFkgCAC8ACIAAQAQQAAIAAAIAAAhBGAAAQAQAEQgAAAAAAABAAAAAAAAAAAAAAACAAAAAAAACgAAAAABAAAAQAAAAAAA', + 'vendorData': { + 'metadata': 'BOh7mtYOh7mtYAcABBENCU-AAAAncgPIXJiiAoao0PxBFkgCAC8ACIAAQAQQAAIAAAIAAAhBGAAAQAQAEQgAAAAAAABAAAAAAAAAAAAAAACAAAAAAAACgAAAAABAAAAQAAAAAAA', + 'gdprApplies': true, + 'hasGlobalScope': false, + 'cookieVersion': '1', + 'created': '2019-05-31T12:46:48.825', + 'lastUpdated': '2019-05-31T12:46:48.825', + 'cmpId': '28', + 'cmpVersion': '1', + 'consentLanguage': 'en', + 'consentScreen': '1', + 'vendorListVersion': 148, + 'maxVendorId': 631, + 'purposeConsents': { + '1': true, + '2': true, + '3': true, + '4': true, + '5': true + }, + 'vendorConsents': { + '468': true, + '522': true, + '524': true, /* 524 is ozone */ + '565': true, + '591': true + } + }, + 'gdprApplies': true + }, +}; + +// make sure the impid matches the request bidId +var validResponse = { + 'body': { + 'id': 'd6198807-7a53-4141-b2db-d2cb754d68ba', + 'seatbid': [ + { + 'bid': [ + { + 'id': '677903815252395017', + 'impid': '2899ec066a91ff8', + 'price': 0.5, + 'adm': '', + 'adid': '98493581', + 'adomain': [ + 'http://prebid.org' + ], + 'iurl': 'https://fra1-ib.adnxs.com/cr?id=98493581', + 'cid': '9325', + 'crid': '98493581', + 'cat': [ + 'IAB3-1' + ], + 'w': 300, + 'h': 600, + 'ext': { + 'prebid': { + 'type': 'banner' + }, + 'bidder': { + 'appnexus': { + 'brand_id': 555545, + 'auction_id': 6500448734132353000, + 'bidder_id': 2, + 'bid_ad_type': 0 + } + } + } + } + ], + 'seat': 'appnexus' + } + ], + 'ext': { + 'responsetimemillis': { + 'appnexus': 47, + 'openx': 30 + } + }, + 'timing': { + 'start': 1536848078.089177, + 'end': 1536848078.142203, + 'TimeTaken': 0.05302619934082031 + } + }, + 'headers': {} +}; +var validOutstreamResponse = { + 'body': { + 'id': 'd6198807-7a53-4141-b2db-d2cb754d68ba', + 'seatbid': [ + { + 'bid': [ + { + 'id': '677903815252395017', + 'impid': '2899ec066a91ff8', + 'price': 0.5, + 'adm': '', + 'adid': '98493581', + 'adomain': [ + 'http://prebid.org' + ], + 'iurl': 'https://fra1-ib.adnxs.com/cr?id=98493581', + 'cid': '9325', + 'crid': '98493581', + 'cat': [ + 'IAB3-1' + ], + 'w': 300, + 'h': 600, + 'ext': { + 'prebid': { + 'type': 'video' + }, + 'bidder': { + 'unruly': { + 'renderer': { + 'config': { + 'targetingUUID': 'aafd3388-afaf-41f4-b271-0ac8e0325a7f', + 'siteId': 1052815, + 'featureOverrides': {} + }, + 'url': 'https://video.unrulymedia.com/native/native-loader.js#supplyMode=prebid?cb=6284685353877994', + 'id': 'unruly_inarticle' + }, + 'vast_url': 'data:text/xml;base64,PD94bWwgdmVyc2lvbj0i' + } + } + } + } + ], + 'seat': 'unruly' + } + ], + 'ext': { + 'responsetimemillis': { + 'appnexus': 47, + 'openx': 30 + } + }, + 'timing': { + 'start': 1536848078.089177, + 'end': 1536848078.142203, + 'TimeTaken': 0.05302619934082031 + } + }, + 'headers': {} +}; +var validBidResponse1adWith2Bidders = { + 'body': { + 'id': '91221f96-b931-4acc-8f05-c2a1186fa5ac', + 'seatbid': [ + { + 'bid': [ + { + 'id': 'd6198807-7a53-4141-b2db-d2cb754d68ba', + 'impid': '2899ec066a91ff8', + 'price': 0.36754, + 'adm': '', + 'adid': '134928661', + 'adomain': [ + 'somecompany.com' + ], + 'iurl': 'https:\/\/ams1-ib.adnxs.com\/cr?id=134928661', + 'cid': '8825', + 'crid': '134928661', + 'cat': [ + 'IAB8-15', + 'IAB8-16', + 'IAB8-4', + 'IAB8-1', + 'IAB8-14', + 'IAB8-6', + 'IAB8-13', + 'IAB8-3', + 'IAB8-17', + 'IAB8-12', + 'IAB8-8', + 'IAB8-7', + 'IAB8-2', + 'IAB8-9', + 'IAB8', + 'IAB8-11' + ], + 'w': 300, + 'h': 250, + 'ext': { + 'prebid': { + 'type': 'banner' + }, + 'bidder': { + 'appnexus': { + 'brand_id': 14640, + 'auction_id': 1.8369641905139e+18, + 'bidder_id': 2, + 'bid_ad_type': 0 + } + } + } + } + ], + 'seat': 'appnexus' + }, + { + 'bid': [ + { + 'id': '75665207-a1ca-49db-ba0e-a5e9c7d26f32', + 'impid': '37fff511779365a', + 'price': 1.046, + 'adm': '
    removed
    ', + 'adomain': [ + 'kx.com' + ], + 'crid': '13005', + 'w': 300, + 'h': 250, + 'ext': { + 'prebid': { + 'type': 'banner' + } + } + } + ], + 'seat': 'openx' + } + ], + 'ext': { + 'responsetimemillis': { + 'appnexus': 91, + 'openx': 109, + 'ozappnexus': 46, + 'ozbeeswax': 2, + 'pangaea': 91 + } + } + }, + 'headers': {} +}; + +describe('ozone Adapter', function () { + describe('isBidRequestValid', function () { + // A test ad unit that will consistently return test creatives + let validBidReq = { + bidder: BIDDER_CODE, + params: { + placementId: '1310000099', + publisherId: '9876abcd12-3', + siteId: '1234567890' + } + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(validBidReq)).to.equal(true); + }); + + var validBidReq2 = { + + bidder: BIDDER_CODE, + params: { + placementId: '1310000099', + publisherId: '9876abcd12-3', + siteId: '1234567890', + customData: {'gender': 'bart', 'age': 'low'}, + lotameData: {'Profile': {'tpid': 'c8ef27a0d4ba771a81159f0d2e792db4', 'Audiences': {'Audience': [{'id': '99999', 'abbr': 'sports'}, {'id': '88888', 'abbr': 'movie'}, {'id': '77777', 'abbr': 'blogger'}], 'ThirdPartyAudience': [{'id': '123', 'name': 'Automobiles'}, {'id': '456', 'name': 'Ages: 30-39'}]}}}, + }, + siteId: 1234567890 + } + + it('should return true when required params found and all optional params are valid', function () { + expect(spec.isBidRequestValid(validBidReq2)).to.equal(true); + }); + + var xEmptyPlacement = { + bidder: BIDDER_CODE, + params: { + placementId: '', + publisherId: '9876abcd12-3', + siteId: '1234567890' + } + }; + + it('should not validate empty placementId', function () { + expect(spec.isBidRequestValid(xEmptyPlacement)).to.equal(false); + }); + + var xMissingPlacement = { + bidder: BIDDER_CODE, + params: { + publisherId: '9876abcd12-3', + siteId: '1234567890' + } + }; + + it('should not validate missing placementId', function () { + expect(spec.isBidRequestValid(xMissingPlacement)).to.equal(false); + }); + + var xBadPlacement = { + bidder: BIDDER_CODE, + params: { + placementId: '123X45', + publisherId: '9876abcd12-3', + siteId: '1234567890' + } + }; + + it('should not validate placementId with a non-numeric value', function () { + expect(spec.isBidRequestValid(xBadPlacement)).to.equal(false); + }); + + var xBadPlacementTooShort = { + bidder: BIDDER_CODE, + params: { + placementId: 123456789, /* should be exactly 10 chars */ + publisherId: '9876abcd12-3', + siteId: '1234567890' + } + }; + + it('should not validate placementId with a numeric value of wrong length', function () { + expect(spec.isBidRequestValid(xBadPlacementTooShort)).to.equal(false); + }); + + var xBadPlacementTooLong = { + bidder: BIDDER_CODE, + params: { + placementId: 12345678901, /* should be exactly 10 chars */ + publisherId: '9876abcd12-3', + siteId: '1234567890' + } + }; + + it('should not validate placementId with a numeric value of wrong length', function () { + expect(spec.isBidRequestValid(xBadPlacementTooLong)).to.equal(false); + }); + + var xMissingPublisher = { + bidder: BIDDER_CODE, + params: { + placementId: '1234567890', + siteId: '1234567890' + } + }; + + it('should not validate missing publisherId', function () { + expect(spec.isBidRequestValid(xMissingPublisher)).to.equal(false); + }); + + var xMissingSiteId = { + bidder: BIDDER_CODE, + params: { + publisherId: '9876abcd12-3', + placementId: '1234567890', + } + }; + + it('should not validate missing sitetId', function () { + expect(spec.isBidRequestValid(xMissingSiteId)).to.equal(false); + }); + + var xBadPublisherTooShort = { + bidder: BIDDER_CODE, + params: { + placementId: '1234567890', + publisherId: '9876abcd12a', + siteId: '1234567890' + } + }; + + it('should not validate publisherId being too short', function () { + expect(spec.isBidRequestValid(xBadPublisherTooShort)).to.equal(false); + }); + + var xBadPublisherTooLong = { + bidder: BIDDER_CODE, + params: { + placementId: '1234567890', + publisherId: '9876abcd12abc', + siteId: '1234567890' + } + }; + + it('should not validate publisherId being too long', function () { + expect(spec.isBidRequestValid(xBadPublisherTooLong)).to.equal(false); + }); + + var publisherNumericOk = { + bidder: BIDDER_CODE, + params: { + placementId: '1234567890', + publisherId: 123456789012, + siteId: '1234567890' + } + }; + + it('should validate publisherId being 12 digits', function () { + expect(spec.isBidRequestValid(publisherNumericOk)).to.equal(true); + }); + + var xEmptyPublisher = { + bidder: BIDDER_CODE, + params: { + placementId: '1234567890', + publisherId: '', + siteId: '1234567890' + } + }; + + it('should not validate empty publisherId', function () { + expect(spec.isBidRequestValid(xEmptyPublisher)).to.equal(false); + }); + + var xBadSite = { + bidder: BIDDER_CODE, + params: { + placementId: '1234567890', + publisherId: '9876abcd12-3', + siteId: '12345Z' + } + }; + + it('should not validate bad siteId', function () { + expect(spec.isBidRequestValid(xBadSite)).to.equal(false); + }); + + var xBadSiteTooLong = { + bidder: BIDDER_CODE, + params: { + placementId: '1234567890', + publisherId: '9876abcd12-3', + siteId: '12345678901' + } + }; + + it('should not validate siteId too long', function () { + expect(spec.isBidRequestValid(xBadSite)).to.equal(false); + }); + + var xBadSiteTooShort = { + bidder: BIDDER_CODE, + params: { + placementId: '1234567890', + publisherId: '9876abcd12-3', + siteId: '123456789' + } + }; + + it('should not validate siteId too short', function () { + expect(spec.isBidRequestValid(xBadSite)).to.equal(false); + }); + + var allNonStrings = { + bidder: BIDDER_CODE, + params: { + placementId: 1234567890, + publisherId: '9876abcd12-3', + siteId: 1234567890 + } + }; + + it('should validate all numeric values being sent as non-string numbers', function () { + expect(spec.isBidRequestValid(allNonStrings)).to.equal(true); + }); + + var emptySiteId = { + bidder: BIDDER_CODE, + params: { + placementId: 1234567890, + publisherId: '9876abcd12-3', + siteId: '' + } + }; + + it('should not validate siteId being empty string (it is required now)', function () { + expect(spec.isBidRequestValid(emptySiteId)).to.equal(false); + }); + + var xBadCustomData = { + bidder: BIDDER_CODE, + params: { + 'placementId': '1234567890', + 'publisherId': '9876abcd12-3', + 'siteId': '1234567890', + 'customData': 'this aint gonna work' + } + }; + + it('should not validate customData not being an object', function () { + expect(spec.isBidRequestValid(xBadCustomData)).to.equal(false); + }); + + var xCustomParams = { + bidder: BIDDER_CODE, + params: { + 'placementId': '1234567890', + 'publisherId': '9876abcd12-3', + 'customParams': {'info': 'this is not allowed'}, + siteId: '1234567890' + } + }; + + it('should not validate customParams being sent', function () { + expect(spec.isBidRequestValid(xCustomParams)).to.equal(false); + }); + + var xBadCustomData = { + bidder: BIDDER_CODE, + params: { + 'placementId': '1234567890', + 'publisherId': '9876abcd12-3', + 'customData': 'this should be an object', + siteId: '1234567890' + } + }; + it('should not validate ozoneData being sent', function () { + expect(spec.isBidRequestValid(xBadCustomData)).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 xBadVideoContext = { + bidder: BIDDER_CODE, + params: { + 'placementId': '1234567890', + 'publisherId': '9876abcd12-3', + 'lotameData': {}, + siteId: '1234567890' + }, + mediaTypes: { + video: { + mimes: ['video/mp4'], + 'context': 'instream'}, + } + }; + + it('should not validate video instream being sent', function () { + expect(spec.isBidRequestValid(xBadVideoContext)).to.equal(false); + }); + + var xBadVideoContext2 = { + bidder: BIDDER_CODE, + params: { + 'placementId': '1234567890', + 'publisherId': '9876abcd12-3', + 'lotameData': {}, + siteId: '1234567890' + }, + mediaTypes: { + video: { + mimes: ['video/mp4']} + } + }; + + it('should not validate video without context attribute', function () { + expect(spec.isBidRequestValid(xBadVideoContext2)).to.equal(false); + }); + + let validVideoBidReq = { + bidder: BIDDER_CODE, + params: { + placementId: '1310000099', + publisherId: '9876abcd12-3', + siteId: '1234567890' + }, + mediaTypes: { + video: { + mimes: ['video/mp4'], + 'context': 'outstream'}, + } + }; + + it('should validate video outstream being sent', function () { + expect(spec.isBidRequestValid(validVideoBidReq)).to.equal(true); + }); + }); + + describe('buildRequests', function () { + it('sends bid request to OZONEURI via POST', function () { + const request = spec.buildRequests(validBidRequests, validBidderRequest); + expect(request.url).to.equal(OZONEURI); + expect(request.method).to.equal('POST'); + }); + + it('sends data as a string', function () { + const request = spec.buildRequests(validBidRequests, validBidderRequest); + expect(request.data).to.be.a('string'); + }); + + it('sends all bid parameters', function () { + const request = spec.buildRequests(validBidRequests, validBidderRequest); + expect(request).to.have.all.keys(['bidderRequest', 'data', 'method', 'url']); + }); + + it('adds all parameters inside the ext object only', function () { + const request = spec.buildRequests(validBidRequests, validBidderRequest); + expect(request.data).to.be.a('string'); + var data = JSON.parse(request.data); + expect(data.imp[0].ext.ozone.lotameData).to.be.an('object'); + expect(data.imp[0].ext.ozone.customData).to.be.an('object'); + 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; + 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); + expect(request.data).to.be.a('string'); + var data = JSON.parse(request.data); + expect(data.imp[0].ext.ozone.lotameData).to.be.an('object'); + expect(data.imp[0].ext.ozone.customData).to.be.an('object'); + expect(data.imp[0].ext.ozone.ozoneData).to.be.undefined; + expect(request).not.to.have.key('lotameData'); + expect(request).not.to.have.key('customData'); + }); + + it('has correct bidder', function () { + const request = spec.buildRequests(validBidRequests, validBidderRequest); + expect(request.bidderRequest.bids[0].bidder).to.equal(BIDDER_CODE); + }); + + it('handles mediaTypes element correctly', function () { + const request = spec.buildRequests(validBidRequestsWithBannerMediaType, validBidderRequest); + expect(request).to.have.all.keys(['bidderRequest', 'data', 'method', 'url']); + }); + + it('handles no ozone, lotame or custom data', function () { + const request = spec.buildRequests(validBidRequestsMinimal, validBidderRequest); + expect(request).to.have.all.keys(['bidderRequest', 'data', 'method', 'url']); + }); + + it('handles video mediaType element correctly, with outstream video', function () { + const request = spec.buildRequests(validBidRequestsWithNonBannerMediaTypesAndValidOutstreamVideo, validBidderRequest); + expect(request).to.have.all.keys(['bidderRequest', 'data', 'method', 'url']); + }); + + it('should not crash when there is no sizes element at all', function () { + const request = spec.buildRequests(validBidRequestsNoSizes, validBidderRequest); + expect(request).to.have.all.keys(['bidderRequest', 'data', 'method', 'url']); + }); + + it('should be able to handle non-single requests', function () { + config.setConfig({'ozone': {'singleRequest': false}}); + const request = spec.buildRequests(validBidRequestsNoSizes, validBidderRequest); + expect(request).to.be.a('array'); + expect(request[0]).to.have.all.keys(['bidderRequest', 'data', 'method', 'url']); + // need to reset the singleRequest config flag: + config.setConfig({'ozone': {'singleRequest': true}}); + }); + + it('should add gdpr consent information to the request when ozone is true', 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.gdprConsent = { + consentString: consentString, + gdprApplies: true, + vendorData: { + vendorConsents: {524: true}, + purposeConsents: {1: true, 2: true, 3: true, 4: true, 5: true} + } + } + + const request = spec.buildRequests(validBidRequestsNoSizes, bidderRequest); + const payload = JSON.parse(request.data); + expect(payload.regs.ext.gdpr).to.equal(1); + expect(payload.regs.ext.oz_con).to.exist.and.to.equal(1); + expect(payload.regs.ext.gap).to.exist.and.to.be.an('array').and.to.eql([1, 2, 3, 4, 5]); + }); + + it('should add correct gdpr consent information to the request when user has accepted only some purpose consents', 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.gdprConsent = { + consentString: consentString, + gdprApplies: true, + vendorData: { + vendorConsents: {524: true}, + purposeConsents: {1: true, 4: true, 5: true} + } + } + + const request = spec.buildRequests(validBidRequestsNoSizes, bidderRequest); + const payload = JSON.parse(request.data); + expect(payload.regs.ext.gdpr).to.equal(1); + expect(payload.regs.ext.oz_con).to.exist.and.to.equal(1); + expect(payload.regs.ext.gap).to.exist.and.to.be.an('array').and.to.eql([1, 4, 5]); + }); + + it('should add gdpr consent information to the request when ozone 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.gdprConsent = { + consentString: consentString, + gdprApplies: true, + vendorData: { + vendorConsents: {}, /* 524 is not present */ + purposeConsents: {1: true, 2: true, 3: true, 4: true, 5: true} + } + }; + + const request = spec.buildRequests(validBidRequestsNoSizes, bidderRequest); + const payload = JSON.parse(request.data); + expect(payload.regs.ext.gdpr).to.equal(1); + expect(payload.regs.ext.oz_con).to.exist.and.to.equal(0); + expect(payload.regs.ext.gap).to.exist.and.to.be.an('array').and.to.eql([1, 2, 3, 4, 5]); + }); + + 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.gdprConsent = { + consentString: consentString, + gdprApplies: false, + vendorData: { + vendorConsents: {}, /* 524 is not present */ + purposeConsents: {1: true, 2: true, 3: true, 4: true, 5: true} + } + }; + + const request = spec.buildRequests(validBidRequestsNoSizes, bidderRequest); + const payload = JSON.parse(request.data); + expect(payload.regs.ext.gdpr).to.equal(0); + expect(payload.regs.ext.oz_con).to.be.undefined; + expect(payload.regs.ext.gap).to.be.undefined; + }); + }); + + describe('interpretResponse', function () { + it('should build bid array', function () { + const request = spec.buildRequests(validBidRequests, validBidderRequest); + const result = spec.interpretResponse(validResponse, request); + expect(result.length).to.equal(1); + }); + + it('should have all relevant fields', function () { + const request = spec.buildRequests(validBidRequests, validBidderRequest); + const result = spec.interpretResponse(validResponse, request); + const bid = result[0]; + expect(bid.cpm).to.equal(validResponse.body.seatbid[0].bid[0].cpm); + expect(bid.width).to.equal(validResponse.body.seatbid[0].bid[0].width); + expect(bid.height).to.equal(validResponse.body.seatbid[0].bid[0].height); + }); + + it('should build bid array with gdpr', function () { + var validBidderRequestWithGdpr = validBidderRequest; + validBidderRequestWithGdpr.gdprConsent = {'gdprApplies': 1, 'consentString': 'This is the gdpr consent string'}; + const request = spec.buildRequests(validBidRequests, validBidderRequestWithGdpr); + const result = spec.interpretResponse(validResponse, request); + expect(result.length).to.equal(1); + }); + + it('should fail ok if no seatbid in server response', function () { + const result = spec.interpretResponse({}, {}); + expect(result).to.be.an('array'); + expect(result).to.be.empty; + }); + + it('should fail ok if seatbid is not an array', function () { + const result = spec.interpretResponse({'body': {'seatbid': 'nothing_here'}}, {}); + expect(result).to.be.an('array'); + expect(result).to.be.empty; + }); + + it('should have video renderer', function () { + const request = spec.buildRequests(validBidRequestsWithNonBannerMediaTypesAndValidOutstreamVideo, validBidderRequest); + const result = spec.interpretResponse(validOutstreamResponse, request); + const bid = result[0]; + expect(bid.renderer).to.be.an.instanceOf(Renderer); + }); + + it('should correctly parse response where there are more bidders than ad slots', function () { + const request = spec.buildRequests(validBidRequests, validBidderRequest); + const result = spec.interpretResponse(validBidResponse1adWith2Bidders, request); + expect(result.length).to.equal(2); + }); + }); + + describe('userSyncs', function () { + it('should fail gracefully if no server response', function () { + const result = spec.getUserSyncs('bad', false); + expect(result).to.be.empty; + }); + it('should fail gracefully if server response is empty', function () { + const result = spec.getUserSyncs('bad', []); + expect(result).to.be.empty; + }); + }); + + describe('video object utils', function () { + it('should find width & height from video object', function () { + let obj = {'playerSize': [640, 480], 'mimes': ['video/mp4'], 'context': 'outstream'}; + const result = getWidthAndHeightFromVideoObject(obj); + expect(result.w).to.equal(640); + expect(result.h).to.equal(480); + }); + it('should find null from bad video object', function () { + let obj = {'playerSize': [], 'mimes': ['video/mp4'], 'context': 'outstream'}; + const result = getWidthAndHeightFromVideoObject(obj); + expect(result).to.be.null; + }); + it('should find null from bad video object2', function () { + let obj = {'mimes': ['video/mp4'], 'context': 'outstream'}; + const result = getWidthAndHeightFromVideoObject(obj); + expect(result).to.be.null; + }); + it('should find null from bad video object3', function () { + let obj = {'playerSize': 'should be an array', 'mimes': ['video/mp4'], 'context': 'outstream'}; + const result = getWidthAndHeightFromVideoObject(obj); + expect(result).to.be.null; + }); + it('should find that player size is nested', function () { + let obj = {'playerSize': [[640, 480]], 'mimes': ['video/mp4'], 'context': 'outstream'}; + const result = getWidthAndHeightFromVideoObject(obj); + expect(result.w).to.equal(640); + expect(result.h).to.equal(480); + }); + it('should fail if player size is 2 x nested', function () { + let obj = {'playerSize': [[[640, 480]]], 'mimes': ['video/mp4'], 'context': 'outstream'}; + const result = getWidthAndHeightFromVideoObject(obj); + expect(result).to.be.null; + }); + it('should find that player size is nested', function () { + let obj = {'playerSize': [[640, 480]], 'mimes': ['video/mp4'], 'context': 'outstream'}; + const result = playerSizeIsNestedArray(obj); + expect(result).to.be.true; + }); + it('should find null from bad video object', function () { + let obj = {'playerSize': [], 'mimes': ['video/mp4'], 'context': 'outstream'}; + const result = playerSizeIsNestedArray(obj); + expect(result).to.be.null; + }); + it('should find null from bad video object2', function () { + let obj = {'mimes': ['video/mp4'], 'context': 'outstream'}; + const result = playerSizeIsNestedArray(obj); + expect(result).to.be.null; + }); + it('should find null from bad video object3', function () { + let obj = {'playerSize': 'should be an array', 'mimes': ['video/mp4'], 'context': 'outstream'}; + const result = playerSizeIsNestedArray(obj); + expect(result).to.be.null; + }); + }); + describe('default size', function () { + it('should should return default sizes if no obj is sent', function () { + let obj = ''; + const result = defaultSize(obj); + expect(result.defaultHeight).to.equal(250); + expect(result.defaultWidth).to.equal(300); + }); + }); +}); diff --git a/test/spec/modules/padsquadBidAdapter_spec.js b/test/spec/modules/padsquadBidAdapter_spec.js new file mode 100644 index 00000000000..aba1efea32f --- /dev/null +++ b/test/spec/modules/padsquadBidAdapter_spec.js @@ -0,0 +1,261 @@ +import {expect} from 'chai'; +import {spec} from 'modules/padsquadBidAdapter'; + +const REQUEST = { + 'bidderCode': 'padsquad', + 'auctionId': 'auctionId-56a2-4f71-9098-720a68f2f708', + 'bidderRequestId': 'requestId', + 'bidRequest': [{ + 'bidder': 'padsquad', + 'params': { + 'unitId': 123456, + }, + 'placementCode': 'div-gpt-dummy-placement-code', + 'sizes': [ + [300, 250] + ], + 'bidId': 'bidId1', + 'bidderRequestId': 'bidderRequestId', + 'auctionId': 'auctionId-56a2-4f71-9098-720a68f2f708' + }, + { + 'bidder': 'padsquad', + 'params': { + 'unitId': 123456, + }, + 'placementCode': 'div-gpt-dummy-placement-code', + 'sizes': [ + [300, 250] + ], + 'bidId': 'bidId2', + 'bidderRequestId': 'bidderRequestId', + 'auctionId': 'auctionId-56a2-4f71-9098-720a68f2f708' + }], + 'start': 1487883186070, + 'auctionStart': 1487883186069, + 'timeout': 3000 +}; + +const RESPONSE = { + 'headers': null, + 'body': { + 'id': 'responseId', + 'seatbid': [ + { + 'bid': [ + { + 'id': 'bidId1', + 'impid': 'bidId1', + 'price': 0.18, + 'adm': '', + 'adid': '144762342', + 'adomain': [ + 'http://dummydomain.com' + ], + 'iurl': 'iurl', + 'cid': '109', + 'crid': 'creativeId', + 'cat': [], + 'w': 300, + 'h': 250, + 'ext': { + 'prebid': { + 'type': 'banner' + }, + 'bidder': { + 'appnexus': { + 'brand_id': 334553, + 'auction_id': 514667951122925701, + 'bidder_id': 2, + 'bid_ad_type': 0 + } + } + } + }, + { + 'id': 'bidId2', + 'impid': 'bidId2', + 'price': 0.1, + 'adm': '', + 'adid': '144762342', + 'adomain': [ + 'http://dummydomain.com' + ], + 'iurl': 'iurl', + 'cid': '109', + 'crid': 'creativeId', + 'cat': [], + 'w': 300, + 'h': 250, + 'ext': { + 'prebid': { + 'type': 'banner' + }, + 'bidder': { + 'appnexus': { + 'brand_id': 386046, + 'auction_id': 517067951122925501, + 'bidder_id': 2, + 'bid_ad_type': 0 + } + } + } + } + ], + 'seat': 'seat' + } + ], + 'ext': { + 'usersync': { + 'sovrn': { + 'status': 'none', + 'syncs': [ + { + 'url': 'urlsovrn', + 'type': 'iframe' + } + ] + }, + 'appnexus': { + 'status': 'none', + 'syncs': [ + { + 'url': 'urlappnexus', + 'type': 'pixel' + } + ] + } + }, + 'responsetimemillis': { + 'appnexus': 127 + } + } + } +}; + +describe('Padsquad bid adapter', function () { + describe('isBidRequestValid', function () { + it('should accept request if only unitId is passed', function () { + let bid = { + bidder: 'padsquad', + params: { + unitId: 'unitId', + } + }; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + it('should accept request if only networkId is passed', function () { + let bid = { + bidder: 'padsquad', + params: { + networkId: 'networkId', + } + }; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + it('should accept request if only publisherId is passed', function () { + let bid = { + bidder: 'padsquad', + params: { + publisherId: 'publisherId', + } + }; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('reject requests without params', function () { + let bid = { + bidder: 'padsquad', + params: {} + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + it('creates request data', function () { + let request = spec.buildRequests(REQUEST.bidRequest, REQUEST); + + expect(request).to.exist.and.to.be.a('object'); + const payload = JSON.parse(request.data); + expect(payload.imp[0]).to.have.property('id', REQUEST.bidRequest[0].bidId); + expect(payload.imp[1]).to.have.property('id', REQUEST.bidRequest[1].bidId); + }); + + it('has gdpr data if applicable', function () { + const req = Object.assign({}, REQUEST, { + gdprConsent: { + consentString: 'consentString', + gdprApplies: true, + } + }); + let request = spec.buildRequests(REQUEST.bidRequest, req); + + const payload = JSON.parse(request.data); + expect(payload.user.ext).to.have.property('consent', req.gdprConsent.consentString); + expect(payload.regs.ext).to.have.property('gdpr', 1); + }); + }); + + describe('interpretResponse', function () { + it('have bids', function () { + let bids = spec.interpretResponse(RESPONSE, REQUEST); + expect(bids).to.be.an('array').that.is.not.empty; + validateBidOnIndex(0); + validateBidOnIndex(1); + + function validateBidOnIndex(index) { + expect(bids[index]).to.have.property('currency', 'USD'); + expect(bids[index]).to.have.property('requestId', RESPONSE.body.seatbid[0].bid[index].impid); + expect(bids[index]).to.have.property('cpm', RESPONSE.body.seatbid[0].bid[index].price); + expect(bids[index]).to.have.property('width', RESPONSE.body.seatbid[0].bid[index].w); + expect(bids[index]).to.have.property('height', RESPONSE.body.seatbid[0].bid[index].h); + expect(bids[index]).to.have.property('ad', RESPONSE.body.seatbid[0].bid[index].adm); + expect(bids[index]).to.have.property('creativeId', RESPONSE.body.seatbid[0].bid[index].crid); + expect(bids[index]).to.have.property('ttl', 30); + expect(bids[index]).to.have.property('netRevenue', true); + } + }); + + it('handles empty response', function () { + const EMPTY_RESP = Object.assign({}, RESPONSE, {'body': {}}); + const bids = spec.interpretResponse(EMPTY_RESP, REQUEST); + + expect(bids).to.be.empty; + }); + }); + + describe('getUserSyncs', function () { + it('handles no parameters', function () { + let opts = spec.getUserSyncs({}); + expect(opts).to.be.an('array').that.is.empty; + }); + it('returns non if sync is not allowed', function () { + let opts = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: false}); + + expect(opts).to.be.an('array').that.is.empty; + }); + + it('iframe sync enabled should return results', function () { + let opts = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: false}, [RESPONSE]); + + expect(opts.length).to.equal(1); + expect(opts[0].type).to.equal('iframe'); + expect(opts[0].url).to.equal(RESPONSE.body.ext.usersync['sovrn'].syncs[0].url); + }); + + it('pixel sync enabled should return results', function () { + let opts = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: true}, [RESPONSE]); + + expect(opts.length).to.equal(1); + expect(opts[0].type).to.equal('image'); + expect(opts[0].url).to.equal(RESPONSE.body.ext.usersync['appnexus'].syncs[0].url); + }); + + it('all sync enabled should return all results', function () { + let opts = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, [RESPONSE]); + + expect(opts.length).to.equal(2); + }); + }); +}); diff --git a/test/spec/modules/parrableIdSystem_spec.js b/test/spec/modules/parrableIdSystem_spec.js new file mode 100644 index 00000000000..540e63aa630 --- /dev/null +++ b/test/spec/modules/parrableIdSystem_spec.js @@ -0,0 +1,77 @@ +import { expect } from 'chai'; +import {config} from 'src/config'; +import * as utils from 'src/utils'; +import { init, requestBidsHook, setSubmoduleRegistry } from 'modules/userId/index.js'; +import { parrableIdSubmodule } from 'modules/parrableIdSystem'; + +const EXPIRED_COOKIE_DATE = 'Thu, 01 Jan 1970 00:00:01 GMT'; +const P_COOKIE_NAME = '_parrable_eid'; +const P_COOKIE_VALUE = '01.1563917337.test-eid'; +const P_CONFIG_MOCK = { + name: 'parrableId', + params: { + partner: 'parrable_test_partner_123,parrable_test_partner_456' + }, + storage: { + name: '_parrable_eid', + type: 'cookie', + expires: 364 + } +}; + +describe('Parrable ID System', function() { + function getConfigMock() { + return { + userSync: { + syncDelay: 0, + userIds: [P_CONFIG_MOCK] + } + } + } + function getAdUnitMock(code = 'adUnit-code') { + return { + code, + mediaTypes: {banner: {}, native: {}}, + sizes: [ + [300, 200], + [300, 600] + ], + bids: [{ + bidder: 'sampleBidder', + params: { placementId: 'banner-only-bidder' } + }] + }; + } + + describe('Parrable ID in Bid Request', function() { + let adUnits; + + beforeEach(function() { + adUnits = [getAdUnitMock()]; + }); + + it('should append parrableid to bid request', function(done) { + // simulate existing browser local storage values + utils.setCookie( + P_COOKIE_NAME, + P_COOKIE_VALUE, + (new Date(Date.now() + 5000).toUTCString()) + ); + + setSubmoduleRegistry([parrableIdSubmodule]); + init(config); + config.setConfig(getConfigMock()); + + requestBidsHook(function() { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.parrableid'); + expect(bid.userId.parrableid).to.equal(P_COOKIE_VALUE); + }); + }); + utils.setCookie(P_COOKIE_NAME, '', EXPIRED_COOKIE_DATE); + done(); + }, { adUnits }); + }); + }); +}); diff --git a/test/spec/modules/piximediaBidAdapter_spec.js b/test/spec/modules/piximediaBidAdapter_spec.js new file mode 100644 index 00000000000..95e03734345 --- /dev/null +++ b/test/spec/modules/piximediaBidAdapter_spec.js @@ -0,0 +1,103 @@ +import { expect } from 'chai'; +import { spec } from 'modules/piximediaBidAdapter'; + +describe('piximediaAdapterTest', function() { + describe('bidRequestValidity', function() { + it('bidRequest with site ID and placement ID param', function() { + expect(spec.isBidRequestValid({ + bidder: 'piximedia', + params: { + 'siteId': 'PIXIMEDIA_PREBID10', + 'placementId': 'RG' + }, + })).to.equal(true); + }); + + it('bidRequest with no required params', function() { + expect(spec.isBidRequestValid({ + bidder: 'piximedia', + params: { + }, + })).to.equal(false); + }); + }); + + describe('bidRequest', function() { + const bidRequests = [{ + 'bidder': 'piximedia', + 'params': { + 'siteId': 'PIXIMEDIA_PREBID10', + 'placementId': 'RG' + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + 'sizes': [300, 250], + 'bidId': '51ef8751f9aead', + 'bidderRequestId': '418b37f85e772c', + 'auctionId': '18fd8b8b0bd757' + }]; + + it('bidRequest HTTP method', function() { + const requests = spec.buildRequests(bidRequests); + requests.forEach(function(requestItem) { + expect(requestItem.method).to.equal('GET'); + }); + }); + + it('bidRequest data', function() { + const requests = spec.buildRequests(bidRequests); + expect(typeof requests[0].data.timestamp).to.equal('number'); + expect(requests[0].data.pbsizes).to.equal('["300x250"]'); + expect(requests[0].data.pver).to.equal('1.0'); + expect(requests[0].data.pbparams).to.equal(JSON.stringify(bidRequests[0].params)); + expect(requests[0].data.pbwidth).to.equal('300'); + expect(requests[0].data.pbheight).to.equal('250'); + expect(requests[0].data.pbbidid).to.equal('51ef8751f9aead'); + }); + }); + + describe('interpretResponse', function() { + const bidRequest = { + 'method': 'GET', + 'url': 'https://ad.piximedia.com/', + 'data': { + 'ver': 2, + 'hb': 1, + 'output': 'js', + 'pub': 267, + 'zone': 62546, + 'width': '300', + 'height': '250', + 'callback': 'json', + 'callback_uid': '51ef8751f9aead', + 'url': 'https://example.com', + 'cb': '', + } + }; + + const bidResponse = { + body: { + 'bidId': '51ef8751f9aead', + 'cpm': 4.2, + 'width': '300', + 'height': '250', + 'creative_id': '1234', + 'currency': 'EUR', + 'adm': '
    ', + }, + headers: {} + }; + + it('result is correct', function() { + const result = spec.interpretResponse(bidResponse, bidRequest); + expect(result[0].requestId).to.equal('51ef8751f9aead'); + expect(result[0].cpm).to.equal(4.2); + expect(result[0].width).to.equal('300'); + expect(result[0].height).to.equal('250'); + expect(result[0].creativeId).to.equal('1234'); + expect(result[0].currency).to.equal('EUR'); + expect(result[0].ttl).to.equal(300); + expect(result[0].ad).to.equal('
    '); + }); + }); +}); diff --git a/test/spec/modules/platformioBidAdapter_spec.js b/test/spec/modules/platformioBidAdapter_spec.js index f3754654cf1..4ef2dc1bba0 100644 --- a/test/spec/modules/platformioBidAdapter_spec.js +++ b/test/spec/modules/platformioBidAdapter_spec.js @@ -192,8 +192,8 @@ describe('Platform.io Adapter Tests', function () { { id: 1, title: { text: 'Ad Title' } }, { id: 2, data: { value: 'Test description' } }, { id: 3, data: { value: 'Brand' } }, - { id: 4, img: { url: 'https://s3.amazonaws.com/adx1public/creatives_icon.png', w: 100, h: 100 } }, - { id: 5, img: { url: 'https://s3.amazonaws.com/adx1public/creatives_image.png', w: 300, h: 300 } } + { id: 4, img: { url: 'https://adx1public.s3.amazonaws.com/creatives_icon.png', w: 100, h: 100 } }, + { id: 5, img: { url: 'https://adx1public.s3.amazonaws.com/creatives_image.png', w: 300, h: 300 } } ], link: { url: 'http://brand.com/' } } @@ -220,8 +220,8 @@ describe('Platform.io Adapter Tests', function () { expect(nativeBid).to.not.equal(null); expect(nativeBid.title).to.equal('Ad Title'); expect(nativeBid.sponsoredBy).to.equal('Brand'); - expect(nativeBid.icon.url).to.equal('https://s3.amazonaws.com/adx1public/creatives_icon.png'); - expect(nativeBid.image.url).to.equal('https://s3.amazonaws.com/adx1public/creatives_image.png'); + expect(nativeBid.icon.url).to.equal('https://adx1public.s3.amazonaws.com/creatives_icon.png'); + expect(nativeBid.image.url).to.equal('https://adx1public.s3.amazonaws.com/creatives_image.png'); expect(nativeBid.image.width).to.equal(300); expect(nativeBid.image.height).to.equal(300); expect(nativeBid.icon.width).to.equal(100); diff --git a/test/spec/modules/playgroundxyzBidAdapter_spec.js b/test/spec/modules/playgroundxyzBidAdapter_spec.js index ac0922ef82e..a90564003f4 100644 --- a/test/spec/modules/playgroundxyzBidAdapter_spec.js +++ b/test/spec/modules/playgroundxyzBidAdapter_spec.js @@ -3,7 +3,7 @@ import { spec } from 'modules/playgroundxyzBidAdapter'; import { newBidder } from 'src/adapters/bidderFactory'; import { deepClone } from 'src/utils'; -const URL = 'https://ads.playground.xyz/host-config/prebid'; +const URL = 'https://ads.playground.xyz/host-config/prebid?v=2'; const GDPR_CONSENT = 'XYZ-CONSENT'; describe('playgroundxyzBidAdapter', function () { @@ -64,7 +64,7 @@ describe('playgroundxyzBidAdapter', function () { const data = JSON.parse(request.data); const banner = data.imp[0].banner; - expect(Object.keys(data.imp[0].ext)).to.have.members(['appnexus']); + expect(Object.keys(data.imp[0].ext)).to.have.members(['appnexus', 'pxyz']); expect([banner.w, banner.h]).to.deep.equal([300, 250]); expect(banner.format).to.deep.equal([{w: 300, h: 250}, {w: 300, h: 600}]); expect(request.url).to.equal(URL); @@ -102,7 +102,7 @@ describe('playgroundxyzBidAdapter', function () { 'seat': '4321' }], 'bidid': '6894227111893743356', - 'cur': 'USD' + 'cur': 'AUD' }; let bidderRequest = { @@ -119,7 +119,7 @@ describe('playgroundxyzBidAdapter', function () { 'height': 50, 'ad': '', 'mediaType': 'banner', - 'currency': 'USD', + 'currency': 'AUD', 'ttl': 300, 'netRevenue': true } @@ -128,8 +128,8 @@ describe('playgroundxyzBidAdapter', function () { expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); }); - it('handles nobid responses', function () { - let response = ''; + it('handles nobid response', function () { + const response = undefined; let result = spec.interpretResponse({ body: response }, {bidderRequest}); expect(result.length).to.equal(0); }); @@ -182,4 +182,32 @@ describe('playgroundxyzBidAdapter', function () { expect(data.user.ext.consent).to.equal('XYZ-CONSENT'); }); }); + + describe('getUserSyncs', function () { + const syncUrl = '//ib.adnxs.com/getuidnb?https://ads.playground.xyz/usersync?partner=appnexus&uid=$UID'; + + describe('when iframeEnabled is true', function () { + const syncOptions = { + 'iframeEnabled': true + } + it('should return one image type user sync pixel', function () { + let result = spec.getUserSyncs(syncOptions); + expect(result.length).to.equal(1); + expect(result[0].type).to.equal('image') + expect(result[0].url).to.equal(syncUrl); + }); + }); + + describe('when iframeEnabled is false', function () { + const syncOptions = { + 'iframeEnabled': false + } + it('should return one image type user sync pixel', function () { + let result = spec.getUserSyncs(syncOptions); + expect(result.length).to.equal(1); + expect(result[0].type).to.equal('image') + expect(result[0].url).to.equal(syncUrl); + }); + }); + }) }); diff --git a/test/spec/modules/polymorphBidAdapter_spec.js b/test/spec/modules/polymorphBidAdapter_spec.js index e2df44e8cfc..6fd4bd90288 100644 --- a/test/spec/modules/polymorphBidAdapter_spec.js +++ b/test/spec/modules/polymorphBidAdapter_spec.js @@ -5,6 +5,9 @@ import { newBidder } from 'src/adapters/bidderFactory'; const BIDDER_CODE = 'polymorph'; const ENDPOINT_URL = '//api.adsnative.com/v1/ad-template.json'; const PLACEMENT_ID = 'ping'; +const NETWORK_KEY = 'abcd1234'; +const WIDGET_ID = 'xyz'; +const CATEGORIES = 'IAB1,IAB2'; const spec = newBidder(polymorphAdapterSpec).getSpec(); @@ -31,6 +34,19 @@ const bidRequests = [{ 'bidId': '30b31c1838de1d', 'bidderRequestId': '22edbae2733bf7', 'auctionId': '1d1a030790a476', +}, +{ + 'bidder': BIDDER_CODE, + 'params': { + 'network_key': NETWORK_KEY, + 'widget_id': WIDGET_ID, + 'cat': CATEGORIES + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[700, 250], [300, 600]], + 'bidId': '30b31c1838de1f', + 'bidderRequestId': '22edbae2733bf7', + 'auctionId': '1d1a030790a476', }]; describe('Polymorph adapter test', function () { @@ -45,6 +61,10 @@ describe('Polymorph adapter test', function () { expect(spec.isBidRequestValid(bidRequests[0])).to.equal(true); }); + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bidRequests[2])).to.equal(true); + }); + it('should return false if req has no placementId', function () { const invalidBidRequest = { bidder: BIDDER_CODE, @@ -79,6 +99,7 @@ describe('Polymorph adapter test', function () { expect(payload1.hb_source).to.equal('prebid'); expect(payload1.zid).to.equal(PLACEMENT_ID); expect(payload1.sizes).to.equal('300,250,300,600'); + expect(payload1.bid_id).to.equal('30b31c1838de1e'); var payload2 = {}; requests[1].data.replace(/([^=&]+)=([^&]*)/g, function(m, key, value) { @@ -90,6 +111,21 @@ describe('Polymorph adapter test', function () { expect(payload2.hb_source).to.equal('prebid'); expect(payload2.zid).to.equal(PLACEMENT_ID); expect(payload2.sizes).to.equal('700,250,300,600'); + expect(payload2.bid_id).to.equal('30b31c1838de1d'); + + var payload3 = {}; + requests[2].data.replace(/([^=&]+)=([^&]*)/g, function(m, key, value) { + payload3[decodeURIComponent(key)] = decodeURIComponent(value); + }); + expect(payload3.ref).to.not.be.undefined; + expect(payload3.url).to.not.be.undefined; + expect(payload3.hb).to.equal('1'); + expect(payload3.hb_source).to.equal('prebid'); + expect(payload3.network_key).to.equal(NETWORK_KEY); + expect(payload3.widget_id).to.equal(WIDGET_ID); + expect(payload3.cat).to.equal(CATEGORIES); + expect(payload3.sizes).to.equal('700,250,300,600'); + expect(payload3.bid_id).to.equal('30b31c1838de1f'); }); it('sends bid request to ENDPOINT via GET', function () { diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index a22006e5a81..7945be68282 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -1,12 +1,9 @@ import { expect } from 'chai'; import { PrebidServer as Adapter, resetSyncedStatus } from 'modules/prebidServerBidAdapter/index.js'; -import adapterManager from 'src/adaptermanager'; +import adapterManager from 'src/adapterManager'; import * as utils from 'src/utils'; -import cookie from 'src/cookie'; -import { userSync } from 'src/userSync'; import { ajax } from 'src/ajax'; import { config } from 'src/config'; -import { requestBidsHook } from 'modules/consentManagement'; import events from 'src/events'; import CONSTANTS from 'src/constants'; @@ -33,7 +30,20 @@ const REQUEST = { 'sizes': [[300, 250], [300, 600]], 'mediaTypes': { 'banner': { - 'sizes': [[ 300, 250 ], [ 300, 300 ]] + 'sizes': [[300, 250], [300, 300]] + }, + 'native': { + 'title': { + 'required': true, + 'len': 800 + }, + 'image': { + 'required': true, + 'sizes': [989, 742], + }, + 'sponsoredBy': { + 'required': true + } } }, 'transactionId': '4ef956ad-fd83-406d-bd35-e4bb786ab86c', @@ -65,7 +75,7 @@ const VIDEO_REQUEST = { 'sizes': [640, 480], 'mediaTypes': { 'video': { - 'playerSize': [[ 640, 480 ]], + 'playerSize': [[640, 480]], 'mimes': ['video/mp4'] } }, @@ -81,6 +91,69 @@ const VIDEO_REQUEST = { ] }; +const OUTSTREAM_VIDEO_REQUEST = { + 'account_id': '1', + 'tid': '437fbbf5-33f5-487a-8e16-a7112903cfe5', + 'max_bids': 1, + 'timeout_millis': 1000, + 'secure': 0, + 'url': '', + 'prebid_version': '1.4.0-pre', + 'ad_units': [ + { + 'code': 'div-gpt-ad-1460505748561-0', + 'sizes': [640, 480], + 'mediaTypes': { + 'video': { + playerSize: [[640, 480]], + context: 'outstream', + mimes: ['video/mp4'] + }, + banner: { sizes: [[300, 250]] } + }, + 'transactionId': '4ef956ad-fd83-406d-bd35-e4bb786ab86c', + 'bids': [ + { + 'bid_id': '123', + 'bidder': 'appnexus', + 'params': { 'placementId': '12349520' } + } + ] + }, + { + code: 'video1', + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'outstream', + mimes: ['video/mp4'] + } + }, + bids: [ + { + bidder: 'appnexus', + params: { + placementId: 13232385, + video: { + skippable: true, + playback_method: ['auto_play_sound_off'] + } + } + } + ], + renderer: { + url: 'http://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js', + render: function (bid) { + ANOutstreamVideo.renderAd({ + targetId: bid.adUnitCode, + adResponse: bid.adResponse, + }); + } + } + } + ] +}; + let BID_REQUESTS; const RESPONSE = { @@ -250,7 +323,7 @@ const RESPONSE_OPENRTB = { 'price': 0.5, 'adm': '', 'adid': '29681110', - 'adomain': [ 'appnexus.com' ], + 'adomain': ['appnexus.com'], 'iurl': 'http://lax1-ib.adnxs.com/cr?id=2968111', 'cid': '958', 'crid': '2968111', @@ -320,6 +393,93 @@ const RESPONSE_OPENRTB_VIDEO = { }, }; +const RESPONSE_OPENRTB_NATIVE = { + 'id': 'c7dcf14f', + 'seatbid': [ + { + 'bid': [ + { + 'id': '6451317310275562039', + 'impid': 'div-gpt-ad-1460505748561-0', + 'price': 10, + 'adm': { + 'ver': '1.2', + 'assets': [ + { + 'id': 1, + 'img': { + 'url': 'https://vcdn.adnxs.com/p/creative-image/f8/7f/0f/13/f87f0f13-230c-4f05-8087-db9216e393de.jpg', + 'w': 989, + 'h': 742, + 'ext': { + 'appnexus': { + 'prevent_crop': 0 + } + } + } + }, + { + 'id': 0, + 'title': { + 'text': 'This is a Prebid Native Creative' + } + }, + { + 'id': 2, + 'data': { + 'value': 'Prebid.org' + } + } + ], + 'link': { + 'url': 'https://lax1-ib.adnxs.com/click?AAAAAAAAJEAAAAAAAAAkQAAAAAAAACRAAAAAAAAAJEAAAAAAAAAkQGdce2vBWudAJZpFu1er1zA7ZzddAAAAAOLoyQBtJAAAbSQAAAIAAAC8pM8FnPgWAAAAAABVU0QAVVNEAAEAAQBNXQAAAAABAgMCAAAAALsAuhVqdgAAAAA./cpcpm=AAAAAAAAAAA=/bcr=AAAAAAAA8D8=/pp=${AUCTION_PRICE}/cnd=%213Q5HCQj8-LwKELzJvi4YnPFbIAQoADEAAAAAAAAkQDoJTEFYMTo0MDc3QKcPSQAAAAAAAPA_UQAAAAAAAAAAWQAAAAAAAAAAYQAAAAAAAAAAaQAAAAAAAAAAcQAAAAAAAAAA/cca=OTMyNSNMQVgxOjQwNzc=/bn=84305/test=1/clickenc=http%3A%2F%2Fprebid.org%2Fdev-docs%2Fshow-native-ads.html' + }, + 'eventtrackers': [ + { + 'event': 1, + 'method': 1, + 'url': 'https://lax1-ib.adnxs.com/it?an_audit=0&test=1&referrer=http%3A%2F%2Flocalhost%3A9999%2FintegrationExamples%2Fgpt%2Fdemo_native.html&e=wqT_3QKCCKACBAAAAwDWAAUBCLvO3ekFEOe47duW2NbzQBiltJba--rq6zAqNgkAAAECCCRAEQEHEAAAJEAZEQkAIREJACkRCQAxEQmoMOLRpwY47UhA7UhIAlC8yb4uWJzxW2AAaM26dXjRkgWAAQGKAQNVU0SSAQEG8FKYAQGgAQGoAQGwAQC4AQLAAQPIAQLQAQnYAQDgAQHwAQCKAjt1ZignYScsIDI1Mjk4ODUsIDE1NjM5MTE5OTUpO3VmKCdyJywgOTc0OTQyMDQsIC4eAPQ0AZICnQIhb2pkaWlnajgtTHdLRUx6SnZpNFlBQ0NjOFZzd0FEZ0FRQVJJN1VoUTR0R25CbGdBWVAwQmFBQndBSGdBZ0FFQWlBRUFrQUVCbUFFQm9BRUJxQUVEc0FFQXVRSHpyV3FrQUFBa1FNRUI4NjFxcEFBQUpFREpBVVZpYmxDaFpRQkEyUUVBQUFBQUFBRHdQLUFCQVBVQkFBQUFBUGdCQUpnQ0FLQUNBTFVDQUFBQUFMMENBQUFBQU1BQ0FNZ0NBT0FDQU9nQ0FQZ0NBSUFEQVpBREFKZ0RBYWdEX1BpOENyb0RDVXhCV0RFNk5EQTNOLUFEcHctUUJBQ1lCQUhCQkFBQUFBQUFBQUFBeVFRQUFBQUFBQUFBQU5nRUFBLi6aAoUBITNRNUhDUWo4LUx3S0VMeiUhJG5QRmJJQVFvQUQRvVhBa1FEb0pURUZZTVRvME1EYzNRS2NQUxFUDFBBX1URDAxBQUFXHQwAWR0MAGEdDABjHQz0FwHYAgDgAq2YSOoCPmh0dHA6Ly9sb2NhbGhvc3Q6OTk5OS9pbnRlZ3JhdGlvbkV4YW1wbGVzL2dwdC9kZW1vX25hdGl2ZS5odG1sgAMAiAMBkAMAmAMUoAMBqgMAwAPgqAHIAwDYAwDgAwDoAwD4AwOABACSBAkvb3BlbnJ0YjKYBACiBA0xNzMuMjQ0LjM2LjQwqATtoySyBAwIABAAGAAgADAAOAC4BADABADIBADSBA45MzI1I0xBWDE6NDA3N9oEAggB4AQA8AS8yb4uiAUBmAUAoAX___________8BqgUkZTU5YzNlYjYtNmRkNi00MmQ5LWExMWEtM2FhMTFjOTc5MGUwwAUAyQUAAAAAAADwP9IFCQkAaVh0ANgFAeAFAfAFmfQh-gUECAAQAJAGAZgGALgGAMEGCSQk8D_IBgDaBhYKEAkQGQEBwTTgBgzyBgIIAIAHAYgHAA..&s=11ababa390e9f7983de260493fc5b91ec5b1b3d4&pp=${AUCTION_PRICE}' + } + ] + }, + 'adid': '97494204', + 'adomain': [ + 'http://prebid.org' + ], + 'iurl': 'https://lax1-ib.adnxs.com/cr?id=97494204', + 'cid': '9325', + 'crid': '97494204', + 'cat': [ + 'IAB3-1' + ], + 'ext': { + 'prebid': { + 'targeting': { + 'hb_bidder': 'appnexus', + 'hb_pb': '10.00' + }, + 'type': 'native', + 'video': { + 'duration': 0, + 'primary_category': '' + } + }, + 'bidder': { + 'appnexus': { + 'brand_id': 555545, + 'auction_id': 4676806524825984103, + 'bidder_id': 2, + 'bid_ad_type': 3 + } + } + } + } + ], + 'seat': 'appnexus' + } + ] +}; + const RESPONSE_UNSUPPORTED_BIDDER = { 'tid': '437fbbf5-33f5-487a-8e16-a7112903cfe5', 'status': 'OK', @@ -355,6 +515,11 @@ describe('S2S Adapter', function () { }, 'bid_id': '123', 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250]] + } + }, 'transactionId': '4ef956ad-fd83-406d-bd35-e4bb786ab86c', 'sizes': [300, 250], 'bidId': '123', @@ -391,12 +556,24 @@ describe('S2S Adapter', function () { xhr.restore(); }); + it('should not add outstrean without renderer', function () { + let ortb2Config = utils.deepClone(CONFIG); + ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' + + config.setConfig({ s2sConfig: ortb2Config }); + adapter.callBids(OUTSTREAM_VIDEO_REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + + const requestBid = JSON.parse(requests[0].requestBody); + expect(requestBid.imp[0].banner).to.exist; + expect(requestBid.imp[0].video).to.not.exist; + }); + it('exists and is a function', function () { expect(adapter.callBids).to.exist.and.to.be.a('function'); }); it('exists converts types', function () { - config.setConfig({s2sConfig: CONFIG}); + config.setConfig({ s2sConfig: CONFIG }); adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); const requestBid = JSON.parse(requests[0].requestBody); expect(requestBid).to.have.property('cache_markup', 2); @@ -407,7 +584,7 @@ describe('S2S Adapter', function () { describe('gdpr tests', function () { afterEach(function () { config.resetConfig(); - $$PREBID_GLOBAL$$.requestBids.removeHook(requestBidsHook); + $$PREBID_GLOBAL$$.requestBids.removeAll(); }); it('adds gdpr consent information to ortb2 request depending on presence of module', function () { @@ -430,7 +607,7 @@ describe('S2S Adapter', function () { expect(requestBid.user.ext.consent).is.equal('abc123'); config.resetConfig(); - config.setConfig({s2sConfig: CONFIG}); + config.setConfig({ s2sConfig: CONFIG }); adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); requestBid = JSON.parse(requests[1].requestBody); @@ -458,6 +635,8 @@ describe('S2S Adapter', function () { expect(requestBid.gdpr).is.equal(1); expect(requestBid.gdpr_consent).is.equal('abc123def'); + expect(requestBid.bidders).to.contain('appnexus').and.to.have.lengthOf(1); + expect(requestBid.account).is.equal('1'); }); it('check gdpr info gets added into cookie_sync request: have consent data but gdprApplies is false', function () { @@ -505,49 +684,49 @@ describe('S2S Adapter', function () { const s2sConfig = Object.assign({}, CONFIG, { cacheMarkup: 999 }); - config.setConfig({s2sConfig: s2sConfig}); + config.setConfig({ s2sConfig: s2sConfig }); adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); const requestBid = JSON.parse(requests[0].requestBody); expect(requestBid).to.have.property('cache_markup', 0); }); it('adds digitrust id is present and user is not optout', function () { + let ortb2Config = utils.deepClone(CONFIG); + ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; + + let consentConfig = { s2sConfig: ortb2Config }; + config.setConfig(consentConfig); + let digiTrustObj = { - success: true, - identity: { - privacy: { - optout: false - }, - id: 'testId', - keyv: 'testKeyV' - } + privacy: { + optout: false + }, + id: 'testId', + keyv: 'testKeyV' }; - window.DigiTrust = { - getUser: () => digiTrustObj - }; + let digiTrustBidRequest = utils.deepClone(BID_REQUESTS); + digiTrustBidRequest[0].bids[0].userId = { digitrustid: { data: digiTrustObj } }; - adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + adapter.callBids(REQUEST, digiTrustBidRequest, addBidResponse, done, ajax); let requestBid = JSON.parse(requests[0].requestBody); - expect(requestBid.digiTrust).to.deep.equal({ - id: digiTrustObj.identity.id, - keyv: digiTrustObj.identity.keyv, - pref: 0 + expect(requestBid.user.ext.digitrust).to.deep.equal({ + id: digiTrustObj.id, + keyv: digiTrustObj.keyv }); - digiTrustObj.identity.privacy.optout = true; + digiTrustObj.privacy.optout = true; - adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + adapter.callBids(REQUEST, digiTrustBidRequest, addBidResponse, done, ajax); requestBid = JSON.parse(requests[1].requestBody); - expect(requestBid.digiTrust).to.not.exist; - - delete window.DigiTrust; + expect(requestBid.user && request.user.ext && requestBid.user.ext.digitrust).to.not.exist; }); it('adds device and app objects to request', function () { - const _config = { s2sConfig: CONFIG, + const _config = { + s2sConfig: CONFIG, device: { ifa: '6D92078A-8246-4BA4-AE5B-76104861E7DC' }, app: { bundle: 'com.test.app' }, }; @@ -557,14 +736,16 @@ describe('S2S Adapter', function () { const requestBid = JSON.parse(requests[0].requestBody); expect(requestBid.device).to.deep.equal({ ifa: '6D92078A-8246-4BA4-AE5B-76104861E7DC', + w: window.innerWidth, + h: window.innerHeight }); expect(requestBid.app).to.deep.equal({ bundle: 'com.test.app', - publisher: {'id': '1'} + publisher: { 'id': '1' } }); }); - it('adds device and app objects to request for ORTB', function () { + it('adds device and app objects to request for OpenRTB', function () { const s2sConfig = Object.assign({}, CONFIG, { endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' }); @@ -580,10 +761,83 @@ describe('S2S Adapter', function () { const requestBid = JSON.parse(requests[0].requestBody); expect(requestBid.device).to.deep.equal({ ifa: '6D92078A-8246-4BA4-AE5B-76104861E7DC', + w: window.innerWidth, + h: window.innerHeight }); expect(requestBid.app).to.deep.equal({ bundle: 'com.test.app', - publisher: {'id': '1'} + publisher: { 'id': '1' } + }); + }); + + 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' + }); + + const _config = { + s2sConfig: s2sConfig, + app: { bundle: 'com.test.app' }, + }; + + config.setConfig(_config); + adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + const requestBid = JSON.parse(requests[0].requestBody); + expect(requestBid.device).to.deep.equal({ + w: window.innerWidth, + h: window.innerHeight + }); + expect(requestBid.app).to.deep.equal({ + bundle: 'com.test.app', + publisher: { 'id': '1' } + }); + }); + + 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 + }; + + config.setConfig(_config); + adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + const requestBid = JSON.parse(requests[0].requestBody); + + expect(requestBid.imp[0].native).to.deep.equal({ + request: JSON.stringify({ + 'context': 1, + 'plcmttype': 1, + 'eventtrackers': [{ + event: 1, + methods: [1] + }], + 'assets': [ + { + 'required': 1, + 'title': { + 'len': 800 + } + }, + { + 'required': 1, + 'img': { + 'type': 3, + 'w': 989, + 'h': 742 + } + }, + { + 'required': 1, + 'data': { + 'type': 1 + } + } + ] + }), + ver: '1.2' }); }); @@ -608,7 +862,7 @@ describe('S2S Adapter', function () { const s2sConfig = Object.assign({}, CONFIG, { endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' }); - config.setConfig({s2sConfig: s2sConfig}); + config.setConfig({ s2sConfig: s2sConfig }); const aliasBidder = { bidder: 'brealtime', @@ -626,6 +880,10 @@ describe('S2S Adapter', function () { prebid: { aliases: { brealtime: 'appnexus' + }, + targeting: { + includebidderkeys: false, + includewinners: true } } }); @@ -635,7 +893,7 @@ describe('S2S Adapter', function () { const s2sConfig = Object.assign({}, CONFIG, { endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' }); - config.setConfig({s2sConfig: s2sConfig}); + config.setConfig({ s2sConfig: s2sConfig }); const alias = 'foobar'; const aliasBidder = { @@ -656,6 +914,10 @@ describe('S2S Adapter', function () { prebid: { aliases: { [alias]: 'appnexus' + }, + targeting: { + includebidderkeys: false, + includewinners: true } } }); @@ -665,7 +927,7 @@ describe('S2S Adapter', function () { const s2sConfig = Object.assign({}, CONFIG, { endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' }); - config.setConfig({s2sConfig: s2sConfig}); + config.setConfig({ s2sConfig: s2sConfig }); const myRequest = utils.deepClone(REQUEST); myRequest.ad_units[0].bids[0].params.usePaymentRule = true; @@ -691,7 +953,7 @@ describe('S2S Adapter', function () { config.resetConfig(); const oldS2sConfig = Object.assign({}, CONFIG); - config.setConfig({s2sConfig: oldS2sConfig}); + config.setConfig({ s2sConfig: oldS2sConfig }); const myRequest2 = utils.deepClone(REQUEST); myRequest2.ad_units[0].bids[0].params.keywords = { @@ -710,6 +972,308 @@ describe('S2S Adapter', function () { value: ['buzz'] }]); }); + + it('adds limit to the cookie_sync request if userSyncLimit is greater than 0', function () { + let cookieSyncConfig = utils.deepClone(CONFIG); + cookieSyncConfig.syncEndpoint = 'https://prebid.adnxs.com/pbs/v1/cookie_sync'; + cookieSyncConfig.userSyncLimit = 1; + + config.setConfig({ s2sConfig: cookieSyncConfig }); + + let bidRequest = utils.deepClone(BID_REQUESTS); + adapter.callBids(REQUEST, bidRequest, addBidResponse, done, ajax); + let requestBid = JSON.parse(requests[0].requestBody); + + expect(requestBid.bidders).to.contain('appnexus').and.to.have.lengthOf(1); + expect(requestBid.account).is.equal('1'); + expect(requestBid.limit).is.equal(1); + }); + + it('does not add limit to cooke_sync request if userSyncLimit is missing or 0', function () { + let cookieSyncConfig = utils.deepClone(CONFIG); + cookieSyncConfig.syncEndpoint = 'https://prebid.adnxs.com/pbs/v1/cookie_sync'; + config.setConfig({ s2sConfig: cookieSyncConfig }); + + let bidRequest = utils.deepClone(BID_REQUESTS); + adapter.callBids(REQUEST, bidRequest, addBidResponse, done, ajax); + let requestBid = JSON.parse(requests[0].requestBody); + + expect(requestBid.bidders).to.contain('appnexus').and.to.have.lengthOf(1); + expect(requestBid.account).is.equal('1'); + expect(requestBid.limit).is.undefined; + + cookieSyncConfig.userSyncLimit = 0; + config.resetConfig(); + config.setConfig({ s2sConfig: cookieSyncConfig }); + + bidRequest = utils.deepClone(BID_REQUESTS); + adapter.callBids(REQUEST, bidRequest, addBidResponse, done, ajax); + requestBid = JSON.parse(requests[0].requestBody); + + expect(requestBid.bidders).to.contain('appnexus').and.to.have.lengthOf(1); + expect(requestBid.account).is.equal('1'); + expect(requestBid.limit).is.undefined; + }); + + 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' + } + } + }); + const _config = { + s2sConfig: s2sConfig, + device: { ifa: '6D92078A-8246-4BA4-AE5B-76104861E7DC' }, + app: { bundle: 'com.test.app' }, + }; + + config.setConfig(_config); + adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + const requestBid = JSON.parse(requests[0].requestBody); + expect(requestBid.imp[0].ext.appnexus).to.haveOwnProperty('key'); + expect(requestBid.imp[0].ext.appnexus.key).to.be.equal('value') + }); + + 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 }; + config.setConfig(consentConfig); + + let userIdBidRequest = utils.deepClone(BID_REQUESTS); + userIdBidRequest[0].bids[0].userId = { + tdid: 'abc123', + pubcid: '1234', + parrableid: '01.1563917337.test-eid', + lipb: { + lipbid: 'li-xyz' + } + }; + + adapter.callBids(REQUEST, userIdBidRequest, addBidResponse, done, ajax); + let requestBid = JSON.parse(requests[0].requestBody); + expect(typeof requestBid.user.ext.eids).is.equal('object'); + expect(Array.isArray(requestBid.user.ext.eids)).to.be.true; + expect(requestBid.user.ext.eids.filter(eid => eid.source === 'adserver.org')).is.not.empty; + expect(requestBid.user.ext.eids.filter(eid => eid.source === 'adserver.org')[0].uids[0].id).is.equal('abc123'); + expect(requestBid.user.ext.eids.filter(eid => eid.source === 'pubcommon')).is.not.empty; + expect(requestBid.user.ext.eids.filter(eid => eid.source === 'pubcommon')[0].uids[0].id).is.equal('1234'); + expect(requestBid.user.ext.eids.filter(eid => eid.source === 'parrable.com')).is.not.empty; + expect(requestBid.user.ext.eids.filter(eid => eid.source === 'parrable.com')[0].uids[0].id).is.equal('01.1563917337.test-eid'); + expect(requestBid.user.ext.eids.filter(eid => eid.source === 'liveintent.com')).is.not.empty; + expect(requestBid.user.ext.eids.filter(eid => eid.source === 'liveintent.com')[0].uids[0].id).is.equal('li-xyz'); + }); + + 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); + adapter.callBids(REQUEST, bidRequests, addBidResponse, done, ajax); + + const parsedRequestBody = JSON.parse(requests[0].requestBody); + expect(parsedRequestBody.cur).to.deep.equal(['USD']); + }); + + 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); + adapter.callBids(REQUEST, bidRequests, addBidResponse, done, ajax); + + const parsedRequestBody = JSON.parse(requests[1].requestBody); + expect(parsedRequestBody.cur).to.deep.equal(['NZ']); + }); + + 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 }); + + const bidRequests = utils.deepClone(BID_REQUESTS); + adapter.callBids(REQUEST, bidRequests, addBidResponse, done, ajax); + + const parsedRequestBody = JSON.parse(requests[0].requestBody); + expect(typeof parsedRequestBody.cur).to.equal('undefined'); + }); + + 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' + } + } + }); + const _config = { + s2sConfig: s2sConfig, + device: { ifa: '6D92078A-8246-4BA4-AE5B-76104861E7DC' }, + app: { bundle: 'com.test.app' }, + }; + + config.setConfig(_config); + adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + const requestBid = JSON.parse(requests[0].requestBody); + + expect(requestBid.ext.prebid.targeting).to.haveOwnProperty('includebidderkeys'); + expect(requestBid.ext.prebid.targeting.includebidderkeys).to.equal(false); + }); + + 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' + } + } + }); + const _config = { + s2sConfig: s2sConfig, + device: { ifa: '6D92078A-8246-4BA4-AE5B-76104861E7DC' }, + app: { bundle: 'com.test.app' }, + }; + + config.setConfig(_config); + adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + const requestBid = JSON.parse(requests[0].requestBody); + + expect(requestBid.ext.prebid.targeting).to.haveOwnProperty('includewinners'); + expect(requestBid.ext.prebid.targeting.includewinners).to.equal(true); + }); + + 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' + } + }); + const _config = { + s2sConfig: s2sConfig, + device: { ifa: '6D92078A-8246-4BA4-AE5B-76104861E7DC' }, + app: { bundle: 'com.test.app' }, + }; + + config.setConfig(_config); + adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + const requestBid = JSON.parse(requests[0].requestBody); + + expect(requestBid).to.haveOwnProperty('ext'); + expect(requestBid.ext).to.haveOwnProperty('prebid'); + expect(requestBid.ext.prebid).to.deep.equal({ + foo: 'bar', + targeting: { + includewinners: true, + includebidderkeys: false + } + }); + }); + + 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, + includebidderkeys: true + } + } + }); + const _config = { + s2sConfig: s2sConfig, + device: { ifa: '6D92078A-8246-4BA4-AE5B-76104861E7DC' }, + app: { bundle: 'com.test.app' }, + }; + + config.setConfig(_config); + adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + const requestBid = JSON.parse(requests[0].requestBody); + + expect(requestBid).to.haveOwnProperty('ext'); + expect(requestBid.ext).to.haveOwnProperty('prebid'); + expect(requestBid.ext.prebid).to.deep.equal({ + targeting: { + includewinners: false, + includebidderkeys: true + } + }); + }); + + 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' + }, + targeting: { + includewinners: false, + includebidderkeys: false + } + } + }); + const _config = { + s2sConfig: s2sConfig, + device: { ifa: '6D92078A-8246-4BA4-AE5B-76104861E7DC' }, + app: { bundle: 'com.test.app' }, + }; + + config.setConfig(_config); + adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + const requestBid = JSON.parse(requests[0].requestBody); + + expect(requestBid).to.haveOwnProperty('ext'); + expect(requestBid.ext).to.haveOwnProperty('prebid'); + expect(requestBid.ext.prebid).to.deep.equal({ + cache: { + vastxml: 'vastxml-set-though-extPrebid.cache.vastXml' + }, + targeting: { + includewinners: false, + includebidderkeys: false + } + }); + }); + + it('passes schain object in request', function () { + const bidRequests = utils.deepClone(BID_REQUESTS); + const schainObject = { + 'ver': '1.0', + 'complete': 1, + 'nodes': [ + { + 'asi': 'indirectseller.com', + 'sid': '00001', + 'hp': 1 + }, + + { + 'asi': 'indirectseller-2.com', + 'sid': '00002', + 'hp': 2 + } + ] + }; + bidRequests[0].bids[0].schain = schainObject; + adapter.callBids(REQUEST, bidRequests, addBidResponse, done, ajax); + const parsedRequestBody = JSON.parse(requests[0].requestBody); + expect(parsedRequestBody.source.ext.schain).to.deep.equal(schainObject); + }) }); describe('response handler', function () { @@ -721,7 +1285,6 @@ describe('S2S Adapter', function () { sinon.stub(utils, 'triggerPixel'); sinon.stub(utils, 'insertUserSyncIframe'); sinon.stub(utils, 'logError'); - sinon.stub(cookie, 'cookieSet'); sinon.stub(events, 'emit'); logWarnSpy = sinon.spy(utils, 'logWarn'); }); @@ -732,7 +1295,6 @@ describe('S2S Adapter', function () { utils.insertUserSyncIframe.restore(); utils.logError.restore(); events.emit.restore(); - cookie.cookieSet.restore(); logWarnSpy.restore(); }); @@ -740,7 +1302,7 @@ describe('S2S Adapter', function () { it('registers bids and calls BIDDER_DONE', function () { server.respondWith(JSON.stringify(RESPONSE)); - config.setConfig({s2sConfig: CONFIG}); + config.setConfig({ s2sConfig: CONFIG }); adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); server.respond(); sinon.assert.calledOnce(addBidResponse); @@ -753,7 +1315,7 @@ describe('S2S Adapter', function () { const response = addBidResponse.firstCall.args[1]; expect(response).to.have.property('statusMessage', 'Bid available'); expect(response).to.have.property('cpm', 0.5); - expect(response).to.have.property('adId', '123'); + expect(response).to.have.property('requestId', '123'); expect(response).to.not.have.property('videoCacheKey'); expect(response).to.have.property('cache_id', '7654321'); expect(response).to.have.property('cache_url', 'http://www.test.com/cache?uuid=7654321'); @@ -763,7 +1325,7 @@ describe('S2S Adapter', function () { it('registers video bids', function () { server.respondWith(JSON.stringify(VIDEO_RESPONSE)); - config.setConfig({s2sConfig: CONFIG}); + config.setConfig({ s2sConfig: CONFIG }); adapter.callBids(VIDEO_REQUEST, BID_REQUESTS, addBidResponse, done, ajax); server.respond(); sinon.assert.calledOnce(addBidResponse); @@ -771,7 +1333,7 @@ describe('S2S Adapter', function () { const response = addBidResponse.firstCall.args[1]; expect(response).to.have.property('statusMessage', 'Bid available'); expect(response).to.have.property('cpm', 0.5); - expect(response).to.have.property('adId', '123'); + expect(response).to.have.property('requestId', '123'); expect(response).to.have.property('videoCacheKey', 'video_cache_id'); expect(response).to.have.property('cache_id', 'video_cache_id'); expect(response).to.have.property('cache_url', 'video_cache_url'); @@ -781,7 +1343,7 @@ describe('S2S Adapter', function () { it('does not call addBidResponse and calls done when ad unit not set', function () { server.respondWith(JSON.stringify(RESPONSE_NO_BID_NO_UNIT)); - config.setConfig({s2sConfig: CONFIG}); + config.setConfig({ s2sConfig: CONFIG }); adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); server.respond(); @@ -792,7 +1354,7 @@ describe('S2S Adapter', function () { it('does not call addBidResponse and calls done when server requests cookie sync', function () { server.respondWith(JSON.stringify(RESPONSE_NO_COOKIE)); - config.setConfig({s2sConfig: CONFIG}); + config.setConfig({ s2sConfig: CONFIG }); adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); server.respond(); @@ -803,7 +1365,7 @@ describe('S2S Adapter', function () { it('does not call addBidResponse and calls done when ad unit is set', function () { server.respondWith(JSON.stringify(RESPONSE_NO_BID_UNIT_SET)); - config.setConfig({s2sConfig: CONFIG}); + config.setConfig({ s2sConfig: CONFIG }); adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); server.respond(); @@ -814,7 +1376,7 @@ describe('S2S Adapter', function () { it('registers successful bids and calls done when there are less bids than requests', function () { server.respondWith(JSON.stringify(RESPONSE)); - config.setConfig({s2sConfig: CONFIG}); + config.setConfig({ s2sConfig: CONFIG }); adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); server.respond(); @@ -823,7 +1385,7 @@ describe('S2S Adapter', function () { expect(addBidResponse.firstCall.args[0]).to.equal('div-gpt-ad-1460505748561-0'); - expect(addBidResponse.firstCall.args[1]).to.have.property('adId', '123'); + expect(addBidResponse.firstCall.args[1]).to.have.property('requestId', '123'); expect(addBidResponse.firstCall.args[1]) .to.have.property('statusMessage', 'Bid available'); @@ -832,7 +1394,7 @@ describe('S2S Adapter', function () { it('should have dealId in bidObject', function () { server.respondWith(JSON.stringify(RESPONSE)); - config.setConfig({s2sConfig: CONFIG}); + config.setConfig({ s2sConfig: CONFIG }); adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); server.respond(); const response = addBidResponse.firstCall.args[1]; @@ -842,11 +1404,11 @@ describe('S2S Adapter', function () { it('should pass through default adserverTargeting if present in bidObject', function () { server.respondWith(JSON.stringify(RESPONSE)); - config.setConfig({s2sConfig: CONFIG}); + config.setConfig({ s2sConfig: CONFIG }); adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); server.respond(); const response = addBidResponse.firstCall.args[1]; - expect(response).to.have.property('adserverTargeting').that.deep.equals({'foo': 'bar'}); + expect(response).to.have.property('adserverTargeting').that.deep.equals({ 'foo': 'bar' }); }); it('registers client user syncs when client bid adapter is present', function () { @@ -857,7 +1419,7 @@ describe('S2S Adapter', function () { server.respondWith(JSON.stringify(RESPONSE_NO_PBS_COOKIE)); - config.setConfig({s2sConfig: CONFIG}); + config.setConfig({ s2sConfig: CONFIG }); adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); server.respond(); @@ -875,7 +1437,7 @@ describe('S2S Adapter', function () { const s2sConfig = Object.assign({}, CONFIG, { endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' }); - config.setConfig({s2sConfig}); + config.setConfig({ s2sConfig }); server.respondWith(JSON.stringify(RESPONSE_OPENRTB)); adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); @@ -889,7 +1451,7 @@ describe('S2S Adapter', function () { it('registers bid responses when server requests cookie sync', function () { server.respondWith(JSON.stringify(RESPONSE_NO_PBS_COOKIE)); - config.setConfig({s2sConfig: CONFIG}); + config.setConfig({ s2sConfig: CONFIG }); adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); server.respond(); sinon.assert.calledOnce(addBidResponse); @@ -902,38 +1464,14 @@ describe('S2S Adapter', function () { expect(response).to.have.property('source', 's2s'); const bid_request_passed = addBidResponse.firstCall.args[1]; - expect(bid_request_passed).to.have.property('adId', '123'); - }); - - it('does not call cookieSet cookie sync when no_cookie response && not opted in', function () { - server.respondWith(JSON.stringify(RESPONSE_NO_PBS_COOKIE)); - - let myConfig = Object.assign({}, CONFIG); - - config.setConfig({s2sConfig: myConfig}); - adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); - server.respond(); - sinon.assert.notCalled(cookie.cookieSet); - }); - - it('calls cookieSet cookie sync when no_cookie response && opted in', function () { - server.respondWith(JSON.stringify(RESPONSE_NO_PBS_COOKIE)); - let myConfig = Object.assign({ - cookieSet: true, - cookieSetUrl: 'https://acdn.adnxs.com/cookieset/cs.js' - }, CONFIG); - - config.setConfig({s2sConfig: myConfig}); - adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); - server.respond(); - sinon.assert.calledOnce(cookie.cookieSet); + expect(bid_request_passed).to.have.property('requestId', '123'); }); 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({ s2sConfig }); server.respondWith(JSON.stringify(RESPONSE_OPENRTB)); adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); @@ -948,7 +1486,7 @@ describe('S2S Adapter', function () { const response = addBidResponse.firstCall.args[1]; expect(response).to.have.property('statusMessage', 'Bid available'); expect(response).to.have.property('bidderCode', 'appnexus'); - expect(response).to.have.property('adId', '123'); + expect(response).to.have.property('requestId', '123'); expect(response).to.have.property('cpm', 0.5); }); @@ -956,7 +1494,7 @@ describe('S2S Adapter', function () { const s2sConfig = Object.assign({}, CONFIG, { endpoint: 'https://prebidserverurl/openrtb2/auction?querystring=param' }); - config.setConfig({s2sConfig}); + config.setConfig({ s2sConfig }); server.respondWith(JSON.stringify(RESPONSE_OPENRTB_VIDEO)); adapter.callBids(VIDEO_REQUEST, BID_REQUESTS, addBidResponse, done, ajax); @@ -968,8 +1506,114 @@ describe('S2S Adapter', function () { expect(response).to.have.property('vastXml', RESPONSE_OPENRTB_VIDEO.seatbid[0].bid[0].adm); expect(response).to.have.property('mediaType', 'video'); expect(response).to.have.property('bidderCode', 'appnexus'); - expect(response).to.have.property('adId', '123'); + expect(response).to.have.property('requestId', '123'); + expect(response).to.have.property('cpm', 10); + }); + + it('handles response cache from ext.prebid.cache.vastXml', function () { + const s2sConfig = Object.assign({}, CONFIG, { + endpoint: 'https://prebidserverurl/openrtb2/auction?querystring=param' + }); + config.setConfig({ s2sConfig }); + const cacheResponse = utils.deepClone(RESPONSE_OPENRTB_VIDEO); + cacheResponse.seatbid.forEach(item => { + item.bid[0].ext.prebid.cache = { + vastXml: { + cacheId: 'abcd1234', + url: 'https://prebid-cache.net/cache?uuid=abcd1234' + } + } + }); + server.respondWith(JSON.stringify(cacheResponse)); + adapter.callBids(VIDEO_REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + server.respond(); + + sinon.assert.calledOnce(addBidResponse); + const response = addBidResponse.firstCall.args[1]; + + expect(response).to.have.property('statusMessage', 'Bid available'); + expect(response).to.have.property('videoCacheKey', 'abcd1234'); + expect(response).to.have.property('vastUrl', 'https://prebid-cache.net/cache?uuid=abcd1234'); + }); + + it('add adserverTargeting object to bids when ext.prebid.targeting is defined', function () { + const s2sConfig = Object.assign({}, CONFIG, { + endpoint: 'https://prebidserverurl/openrtb2/auction?querystring=param' + }); + config.setConfig({ s2sConfig }); + const cacheResponse = utils.deepClone(RESPONSE_OPENRTB_VIDEO); + const targetingTestData = { + hb_cache_path: '/cache', + hb_cache_host: 'prebid-cache.testurl.com' + }; + + cacheResponse.seatbid.forEach(item => { + item.bid[0].ext.prebid.targeting = targetingTestData + }); + server.respondWith(JSON.stringify(cacheResponse)); + adapter.callBids(VIDEO_REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + server.respond(); + + sinon.assert.calledOnce(addBidResponse); + const response = addBidResponse.firstCall.args[1]; + + expect(response).to.have.property('adserverTargeting'); + expect(response.adserverTargeting).to.deep.equal({ + 'hb_cache_path': '/cache', + 'hb_cache_host': 'prebid-cache.testurl.com' + }); + }); + + it('handles response cache from ext.prebid.targeting', function () { + const s2sConfig = Object.assign({}, CONFIG, { + endpoint: 'https://prebidserverurl/openrtb2/auction?querystring=param' + }); + config.setConfig({ s2sConfig }); + const cacheResponse = utils.deepClone(RESPONSE_OPENRTB_VIDEO); + cacheResponse.seatbid.forEach(item => { + item.bid[0].ext.prebid.targeting = { + hb_uuid: 'a5ad3993', + hb_cache_host: 'prebid-cache.net', + hb_cache_path: '/cache' + } + }); + server.respondWith(JSON.stringify(cacheResponse)); + adapter.callBids(VIDEO_REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + server.respond(); + + sinon.assert.calledOnce(addBidResponse); + const response = addBidResponse.firstCall.args[1]; + + expect(response).to.have.property('statusMessage', 'Bid available'); + expect(response).to.have.property('videoCacheKey', 'a5ad3993'); + expect(response).to.have.property('vastUrl', 'https://prebid-cache.net/cache?uuid=a5ad3993'); + }); + + it('handles OpenRTB native responses', function () { + sinon.stub(utils, 'getBidRequest').returns({ + adUnitCode: 'div-gpt-ad-1460505748561-0', + bidder: 'appnexus', + bidId: '123' + }); + const s2sConfig = Object.assign({}, CONFIG, { + endpoint: 'https://prebidserverurl/openrtb2/auction?querystring=param' + }); + config.setConfig({ s2sConfig }); + + server.respondWith(JSON.stringify(RESPONSE_OPENRTB_NATIVE)); + adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + server.respond(); + + sinon.assert.calledOnce(addBidResponse); + const response = addBidResponse.firstCall.args[1]; + expect(response).to.have.property('statusMessage', 'Bid available'); + expect(response).to.have.property('adm').deep.equal(RESPONSE_OPENRTB_NATIVE.seatbid[0].bid[0].adm); + expect(response).to.have.property('mediaType', 'native'); + expect(response).to.have.property('bidderCode', 'appnexus'); + expect(response).to.have.property('requestId', '123'); expect(response).to.have.property('cpm', 10); + + utils.getBidRequest.restore(); }); it('should log warning for unsupported bidder', function () { @@ -984,7 +1628,7 @@ describe('S2S Adapter', function () { } config.setConfig(_config); - config.setConfig({s2sConfig: CONFIG}); + config.setConfig({ s2sConfig: CONFIG }); adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); server.respond(); @@ -993,13 +1637,20 @@ describe('S2S Adapter', function () { }); describe('s2sConfig', function () { + let xhr; + let requests; let logErrorSpy; beforeEach(function () { + xhr = sinon.useFakeXMLHttpRequest(); + requests = []; + xhr.onCreate = request => requests.push(request); logErrorSpy = sinon.spy(utils, 'logError'); + resetSyncedStatus(); }); afterEach(function () { + xhr.restore(); utils.logError.restore(); }); @@ -1068,8 +1719,6 @@ describe('S2S Adapter', function () { expect(vendorConfig).to.have.property('accountId', '123'); expect(vendorConfig).to.have.property('adapter', 'prebidServer'); expect(vendorConfig.bidders).to.deep.equal(['appnexus']); - expect(vendorConfig.cookieSet).to.be.false; - expect(vendorConfig.cookieSetUrl).to.be.undefined; expect(vendorConfig.enabled).to.be.true; expect(vendorConfig).to.have.property('endpoint', '//prebid.adnxs.com/pbs/v1/openrtb2/auction'); expect(vendorConfig).to.have.property('syncEndpoint', '//prebid.adnxs.com/pbs/v1/cookie_sync'); @@ -1091,12 +1740,148 @@ describe('S2S Adapter', function () { expect(vendorConfig).to.have.property('accountId', 'abc'); expect(vendorConfig).to.have.property('adapter', 'prebidServer'); expect(vendorConfig.bidders).to.deep.equal(['rubicon']); - expect(vendorConfig.cookieSet).to.be.false; - expect(vendorConfig.cookieSetUrl).to.be.undefined; expect(vendorConfig.enabled).to.be.true; expect(vendorConfig).to.have.property('endpoint', '//prebid-server.rubiconproject.com/openrtb2/auction'); expect(vendorConfig).to.have.property('syncEndpoint', '//prebid-server.rubiconproject.com/cookie_sync'); expect(vendorConfig).to.have.property('timeout', 750); }); + + it('should return proper defaults', function () { + expect(config.getConfig('s2sConfig')).to.deep.equal({ + 'accountId': 'abc', + 'adapter': 'prebidServer', + 'bidders': ['rubicon'], + 'defaultVendor': 'rubicon', + 'enabled': true, + 'endpoint': '//prebid-server.rubiconproject.com/openrtb2/auction', + 'syncEndpoint': '//prebid-server.rubiconproject.com/cookie_sync', + 'timeout': 750 + }) + }); + + it('should return default adapterOptions if not set', function () { + config.setConfig({ + s2sConfig: { + accountId: 'abc', + bidders: ['rubicon'], + defaultVendor: 'rubicon', + timeout: 750 + } + }); + expect(config.getConfig('s2sConfig')).to.deep.equal({ + enabled: true, + timeout: 750, + adapter: 'prebidServer', + accountId: 'abc', + bidders: ['rubicon'], + defaultVendor: 'rubicon', + endpoint: '//prebid-server.rubiconproject.com/openrtb2/auction', + syncEndpoint: '//prebid-server.rubiconproject.com/cookie_sync' + }) + }); + + it('should set adapterOptions', function () { + config.setConfig({ + s2sConfig: { + adapterOptions: { + rubicon: { + singleRequest: true, + foo: 'bar' + } + } + } + }); + expect(config.getConfig('s2sConfig').adapterOptions).to.deep.equal({ + rubicon: { + singleRequest: true, + foo: 'bar' + } + }) + }); + + it('should set syncUrlModifier', function () { + config.setConfig({ + s2sConfig: { + syncUrlModifier: { + appnexus: () => { } + } + } + }); + expect(typeof config.getConfig('s2sConfig').syncUrlModifier.appnexus).to.equal('function') + }); + + it('should set correct bidder names to bidders property when using an alias for that bidder', function () { + const s2sConfig = utils.deepClone(CONFIG); + + // Add syncEndpoint so that the request goes to the User Sync endpoint + // Modify the bidders property to include an alias for Rubicon adapter + s2sConfig.syncEndpoint = 'https://prebid.adnxs.com/pbs/v1/cookie_sync'; + s2sConfig.bidders = ['appnexus', 'rubicon-alias']; + + const request = utils.deepClone(REQUEST); + + // Add another bidder, `rubicon-alias` + request.ad_units[0].bids.push({ + bidder: 'rubicon-alias', + params: { + accoundId: 14062, + siteId: 70608, + zoneId: 498816 + } + }); + + // 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', + 'auctionId': '4146ab2b-9422-4040-9b1c-966fffbfe2d4', + 'bidderRequestId': '4b1a4f9c3e4546', + 'tid': 'd7fa8342-ae22-4ca1-b237-331169350f84', + 'bids': [ + { + 'bidder': 'rubicon-alias', + 'params': { + 'accountId': 14062, + 'siteId': 70608, + 'zoneId': 498816 + }, + 'bid_id': '2a9523915411c3', + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 250 + ] + ] + } + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': '78ddc106-b7d8-45d1-bd29-86993098e53d', + 'sizes': [ + [ + 300, + 250 + ] + ], + 'bidId': '2a9523915411c3', + 'bidderRequestId': '4b1a4f9c3e4546', + 'auctionId': '4146ab2b-9422-4040-9b1c-966fffbfe2d4' + } + ], + 'auctionStart': 1569234122602, + 'timeout': 1000, + 'src': 's2s' + }); + + adapter.callBids(request, bidRequest, addBidResponse, done, ajax); + + const requestBid = JSON.parse(requests[0].requestBody); + expect(requestBid.bidders).to.deep.equal(['appnexus', 'rubicon']); + }); }); }); diff --git a/test/spec/modules/prebidmanagerAnalyticsAdapter_spec.js b/test/spec/modules/prebidmanagerAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..cd414a70236 --- /dev/null +++ b/test/spec/modules/prebidmanagerAnalyticsAdapter_spec.js @@ -0,0 +1,117 @@ +import prebidmanagerAnalytics from 'modules/prebidmanagerAnalyticsAdapter'; +import {expect} from 'chai'; +let events = require('src/events'); +let constants = require('src/constants.json'); + +describe('Prebid Manager Analytics Adapter', function () { + let xhr; + let requests; + + let bidWonEvent = { + 'bidderCode': 'appnexus', + 'width': 300, + 'height': 250, + 'adId': '1ebb82ec35375e', + 'mediaType': 'banner', + 'cpm': 0.5, + 'requestId': '1582271863760569973', + 'creative_id': '96846035', + 'creativeId': '96846035', + 'ttl': 60, + 'currency': 'USD', + 'netRevenue': true, + 'auctionId': '9c7b70b9-b6ab-4439-9e71-b7b382797c18', + 'responseTimestamp': 1537521629657, + 'requestTimestamp': 1537521629331, + 'bidder': 'appnexus', + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'timeToRespond': 326, + 'size': '300x250', + 'status': 'rendered', + 'eventType': 'bidWon', + 'ad': 'some ad', + 'adUrl': 'ad url' + }; + + before(function () { + xhr = sinon.useFakeXMLHttpRequest(); + xhr.onCreate = request => requests.push(request); + }); + + after(function () { + xhr.restore(); + }); + + describe('Prebid Manager Analytic tests', function () { + beforeEach(function () { + requests = []; + sinon.stub(events, 'getEvents').returns([]); + }); + + afterEach(function () { + prebidmanagerAnalytics.disableAnalytics(); + events.getEvents.restore(); + }); + + it('support custom endpoint', function () { + let custom_url = 'custom url'; + prebidmanagerAnalytics.enableAnalytics({ + provider: 'prebidmanager', + options: { + url: custom_url, + bundleId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' + } + }); + + expect(prebidmanagerAnalytics.getOptions().url).to.equal(custom_url); + }); + + it('bid won event', function() { + xhr.onCreate = request => requests.push(request); + let bundleId = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'; + prebidmanagerAnalytics.enableAnalytics({ + provider: 'prebidmanager', + options: { + bundleId: bundleId + } + }); + + events.emit(constants.EVENTS.BID_WON, bidWonEvent); + prebidmanagerAnalytics.flush(); + + expect(requests.length).to.equal(1); + expect(requests[0].url).to.equal('https://endpoint.prebidmanager.com/endpoint'); + expect(requests[0].requestBody.substring(0, 2)).to.equal('1:'); + + const pmEvents = JSON.parse(requests[0].requestBody.substring(2)); + expect(pmEvents.pageViewId).to.exist; + expect(pmEvents.bundleId).to.equal(bundleId); + expect(pmEvents.ver).to.equal(1); + expect(pmEvents.events.length).to.equal(2); + expect(pmEvents.events[0].eventType).to.equal('pageView'); + expect(pmEvents.events[1].eventType).to.equal('bidWon'); + expect(pmEvents.events[1].ad).to.be.undefined; + expect(pmEvents.events[1].adUrl).to.be.undefined; + }); + + it('track event without errors', function () { + sinon.spy(prebidmanagerAnalytics, 'track'); + + prebidmanagerAnalytics.enableAnalytics({ + provider: 'prebidmanager', + options: { + bundleId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' + } + }); + + events.emit(constants.EVENTS.AUCTION_INIT, {}); + events.emit(constants.EVENTS.BID_REQUESTED, {}); + events.emit(constants.EVENTS.BID_RESPONSE, {}); + events.emit(constants.EVENTS.BID_WON, {}); + events.emit(constants.EVENTS.AUCTION_END, {}); + events.emit(constants.EVENTS.BID_TIMEOUT, {}); + + sinon.assert.callCount(prebidmanagerAnalytics.track, 6); + }); + }); +}); diff --git a/test/spec/modules/projectLimeLightBidAdapter_spec.js b/test/spec/modules/projectLimeLightBidAdapter_spec.js new file mode 100644 index 00000000000..434b3fbccb5 --- /dev/null +++ b/test/spec/modules/projectLimeLightBidAdapter_spec.js @@ -0,0 +1,170 @@ +import {expect} from 'chai'; +import {spec} from '../../../modules/projectLimeLightBidAdapter'; + +describe('ProjectLimeLightAdapter', function () { + let bid = { + bidId: '2dd581a2b6281d', + bidder: 'project-limelight', + bidderRequestId: '145e1d6a7837c9', + params: { + adUnitId: 123, + adUnitType: 'banner' + }, + placementCode: 'placement_0', + auctionId: '74f78609-a92d-4cf1-869f-1b244bbfb5d2', + sizes: [[300, 250]], + transactionId: '3bb2f6da-87a6-4029-aeb0-bfe951372e62' + }; + + describe('buildRequests', function () { + let serverRequest = spec.buildRequests([bid]); + 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('//ads.project-limelight.com/hb'); + }); + 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', 'secure', 'adUnits'); + expect(data.deviceWidth).to.be.a('number'); + expect(data.deviceHeight).to.be.a('number'); + expect(data.secure).to.be.a('boolean'); + let adUnits = data['adUnits']; + for (let i = 0; i < adUnits.length; i++) { + let adUnit = adUnits[i]; + expect(adUnit).to.have.all.keys('id', 'bidId', 'type', 'sizes', 'transactionId'); + expect(adUnit.id).to.be.a('number'); + expect(adUnit.bidId).to.be.a('string'); + expect(adUnit.type).to.be.a('string'); + expect(adUnit.transactionId).to.be.a('string'); + expect(adUnit.sizes).to.be.an('array'); + } + }); + it('Returns empty data if no valid requests are passed', function () { + serverRequest = spec.buildRequests([]); + let data = serverRequest.data; + expect(data.adUnits).to.be.an('array').that.is.empty; + }); + }); + describe('interpretBannerResponse', function () { + let resObject = { + body: [ { + requestId: '123', + mediaType: 'banner', + cpm: 0.3, + width: 320, + height: 50, + ad: '

    Hello ad

    ', + ttl: 1000, + creativeId: '123asd', + netRevenue: true, + currency: 'USD' + } ] + }; + let serverResponses = spec.interpretResponse(resObject); + it('Returns an array of valid server responses if response object is valid', function () { + expect(serverResponses).to.be.an('array').that.is.not.empty; + for (let i = 0; i < serverResponses.length; i++) { + let dataItem = serverResponses[i]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'mediaType'); + expect(dataItem.requestId).to.be.a('string'); + expect(dataItem.cpm).to.be.a('number'); + expect(dataItem.width).to.be.a('number'); + expect(dataItem.height).to.be.a('number'); + expect(dataItem.ad).to.be.a('string'); + expect(dataItem.ttl).to.be.a('number'); + expect(dataItem.creativeId).to.be.a('string'); + expect(dataItem.netRevenue).to.be.a('boolean'); + expect(dataItem.currency).to.be.a('string'); + expect(dataItem.mediaType).to.be.a('string'); + } + it('Returns an empty array if invalid response is passed', function () { + serverResponses = spec.interpretResponse('invalid_response'); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + }); + }); + describe('interpretVideoResponse', function () { + let resObject = { + body: [ { + requestId: '123', + mediaType: 'video', + cpm: 0.3, + width: 320, + height: 50, + vastXml: '', + ttl: 1000, + creativeId: '123asd', + netRevenue: true, + currency: 'USD' + } ] + }; + let serverResponses = spec.interpretResponse(resObject); + it('Returns an array of valid server responses if response object is valid', function () { + expect(serverResponses).to.be.an('array').that.is.not.empty; + for (let i = 0; i < serverResponses.length; i++) { + let dataItem = serverResponses[i]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'vastXml', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'mediaType'); + expect(dataItem.requestId).to.be.a('string'); + expect(dataItem.cpm).to.be.a('number'); + expect(dataItem.width).to.be.a('number'); + expect(dataItem.height).to.be.a('number'); + expect(dataItem.vastXml).to.be.a('string'); + expect(dataItem.ttl).to.be.a('number'); + expect(dataItem.creativeId).to.be.a('string'); + expect(dataItem.netRevenue).to.be.a('boolean'); + expect(dataItem.currency).to.be.a('string'); + expect(dataItem.mediaType).to.be.a('string'); + } + it('Returns an empty array if invalid response is passed', function () { + serverResponses = spec.interpretResponse('invalid_response'); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + }); + }); + describe('isBidRequestValid', function() { + let bid = { + bidId: '2dd581a2b6281d', + bidder: 'project-limelight', + bidderRequestId: '145e1d6a7837c9', + params: { + adUnitId: 123, + adUnitType: 'banner' + }, + placementCode: 'placement_0', + auctionId: '74f78609-a92d-4cf1-869f-1b244bbfb5d2', + sizes: [[300, 250]], + transactionId: '3bb2f6da-87a6-4029-aeb0-bfe951372e62' + }; + + it('should return true when required params found', function() { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not passed', function() { + let bidFailed = { + bidder: 'project-limelight', + bidderRequestId: '145e1d6a7837c9', + params: { + adUnitId: 123, + adUnitType: 'banner' + }, + placementCode: 'placement_0', + auctionId: '74f78609-a92d-4cf1-869f-1b244bbfb5d2', + sizes: [[300, 250]], + transactionId: '3bb2f6da-87a6-4029-aeb0-bfe951372e62' + }; + expect(spec.isBidRequestValid(bidFailed)).to.equal(false); + }); + }); +}); diff --git a/test/spec/modules/pubCommonId_spec.js b/test/spec/modules/pubCommonId_spec.js index aaf296cfb43..fd2bff5bac6 100644 --- a/test/spec/modules/pubCommonId_spec.js +++ b/test/spec/modules/pubCommonId_spec.js @@ -5,25 +5,44 @@ import { setConfig, isPubcidEnabled, getExpInterval, - initPubcid } from 'modules/pubCommonId'; + initPubcid, + setStorageItem, + getStorageItem, + removeStorageItem, + getPubcidConfig } from 'modules/pubCommonId'; import { getAdUnits } from 'test/fixtures/fixtures'; import * as auctionModule from 'src/auction'; import { registerBidder } from 'src/adapters/bidderFactory'; import * as utils from 'src/utils'; +let events = require('src/events'); +let constants = require('src/constants.json'); + var assert = require('chai').assert; var expect = require('chai').expect; -const COOKIE_NAME = '_pubcid'; +const ID_NAME = '_pubcid'; +const EXP = '_exp'; const TIMEOUT = 2000; +const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89a-f][0-9a-f]{3}-[0-9a-f]{12}$/; + +function cleanUp() { + window.document.cookie = ID_NAME + '=; expires=Thu, 01 Jan 1970 00:00:01 GMT;'; + localStorage.removeItem(ID_NAME); + localStorage.removeItem(ID_NAME + EXP); +} + describe('Publisher Common ID', function () { afterEach(function () { - $$PREBID_GLOBAL$$.requestBids.removeHook(requestBidHook); + $$PREBID_GLOBAL$$.requestBids.removeAll(); }); describe('Decorate adUnits', function () { - before(function() { - window.document.cookie = COOKIE_NAME + '=; expires=Thu, 01 Jan 1970 00:00:01 GMT;'; + beforeEach(function() { + cleanUp(); + }); + afterEach(function() { + cleanUp(); }); it('Check same cookie', function () { @@ -31,20 +50,26 @@ describe('Publisher Common ID', function () { let adUnits2 = getAdUnits(); let innerAdUnits1; let innerAdUnits2; - let pubcid = getCookie(COOKIE_NAME); + let pubcid; - expect(pubcid).to.be.null; // there should be no cookie initially + expect(getCookie(ID_NAME)).to.be.null; // there should be no cookie initially + expect(localStorage.getItem(ID_NAME)).to.be.null; // there should be no local storage item either - requestBidHook({adUnits: adUnits1}, (config) => { innerAdUnits1 = config.adUnits }); - pubcid = getCookie(COOKIE_NAME); // cookies is created after requestbidHook + requestBidHook((config) => { innerAdUnits1 = config.adUnits }, {adUnits: adUnits1}); + pubcid = localStorage.getItem(ID_NAME); // local storage item is created after requestbidHook innerAdUnits1.forEach((unit) => { unit.bids.forEach((bid) => { - expect(bid).to.have.deep.property('crumbs.pubcid'); + expect(bid).to.have.deep.nested.property('crumbs.pubcid'); expect(bid.crumbs.pubcid).to.equal(pubcid); }); }); - requestBidHook({adUnits: adUnits2}, (config) => { innerAdUnits2 = config.adUnits }); + + // verify cookie is null + expect(getCookie(ID_NAME)).to.be.null; + + // verify same pubcid is preserved + requestBidHook((config) => { innerAdUnits2 = config.adUnits }, {adUnits: adUnits2}); assert.deepEqual(innerAdUnits1, innerAdUnits2); }); @@ -56,27 +81,30 @@ describe('Publisher Common ID', function () { let pubcid1; let pubcid2; - requestBidHook({adUnits: adUnits1}, (config) => { innerAdUnits1 = config.adUnits }); - pubcid1 = getCookie(COOKIE_NAME); // get first cookie - setCookie(COOKIE_NAME, '', -1); // erase cookie + requestBidHook((config) => { innerAdUnits1 = config.adUnits }, {adUnits: adUnits1}); + pubcid1 = localStorage.getItem(ID_NAME); // get first pubcid + removeStorageItem(ID_NAME); // remove storage + + expect(pubcid1).to.not.be.null; innerAdUnits1.forEach((unit) => { unit.bids.forEach((bid) => { - expect(bid).to.have.deep.property('crumbs.pubcid'); + expect(bid).to.have.deep.nested.property('crumbs.pubcid'); expect(bid.crumbs.pubcid).to.equal(pubcid1); }); }); - requestBidHook({adUnits: adUnits2}, (config) => { innerAdUnits2 = config.adUnits }); - pubcid2 = getCookie(COOKIE_NAME); // get second cookie + requestBidHook((config) => { innerAdUnits2 = config.adUnits }, {adUnits: adUnits2}); + pubcid2 = localStorage.getItem(ID_NAME); // get second pubcid innerAdUnits2.forEach((unit) => { unit.bids.forEach((bid) => { - expect(bid).to.have.deep.property('crumbs.pubcid'); + expect(bid).to.have.deep.nested.property('crumbs.pubcid'); expect(bid.crumbs.pubcid).to.equal(pubcid2); }); }); + expect(pubcid2).to.not.be.null; expect(pubcid1).to.not.equal(pubcid2); }); @@ -85,28 +113,91 @@ describe('Publisher Common ID', function () { let innerAdUnits; let pubcid = utils.generateUUID(); - setCookie(COOKIE_NAME, pubcid, 600); - requestBidHook({adUnits}, (config) => { innerAdUnits = config.adUnits }); + setCookie(ID_NAME, pubcid, 600); + requestBidHook((config) => { innerAdUnits = config.adUnits }, {adUnits}); innerAdUnits.forEach((unit) => { unit.bids.forEach((bid) => { - expect(bid).to.have.deep.property('crumbs.pubcid'); + expect(bid).to.have.deep.nested.property('crumbs.pubcid'); expect(bid.crumbs.pubcid).to.equal(pubcid); }); }); }); + + it('Replicate cookie to storage', function() { + let adUnits = getAdUnits(); + let innerAdUnits; + let pubcid = utils.generateUUID(); + + setCookie(ID_NAME, pubcid, 600); + requestBidHook((config) => { innerAdUnits = config.adUnits }, {adUnits}); + + expect(getStorageItem(ID_NAME)).to.equal(pubcid); + }); + + it('Does not replicate storage to cookie', function() { + let adUnits = getAdUnits(); + let innerAdUnits; + let pubcid = utils.generateUUID(); + + setStorageItem(ID_NAME, pubcid, 600); + requestBidHook((config) => { innerAdUnits = config.adUnits }, {adUnits}); + + expect(getCookie(ID_NAME)).to.be.null; + }); + + it('Cookie only', function() { + setConfig({type: 'cookie'}); + let adUnits = getAdUnits(); + let innerAdUnits; + + requestBidHook((config) => { innerAdUnits = config.adUnits }, {adUnits}); + + expect(getCookie(ID_NAME)).to.match(uuidPattern); + expect(getStorageItem(ID_NAME)).to.be.null; + }); + + it('Storage only', function() { + setConfig({type: 'html5'}); + let adUnits = getAdUnits(); + let innerAdUnits; + + requestBidHook((config) => { innerAdUnits = config.adUnits }, {adUnits}); + + expect(getCookie(ID_NAME)).to.be.null; + expect(getStorageItem(ID_NAME)).to.match(uuidPattern); + }); + + it('Bad id recovery', function() { + let adUnits = getAdUnits(); + let innerAdUnits; + + setStorageItem(ID_NAME, 'undefined', 600); + requestBidHook((config) => { innerAdUnits = config.adUnits }, {adUnits}); + + expect(getStorageItem(ID_NAME)).to.match(uuidPattern); + }); }); describe('Configuration', function () { + beforeEach(() => { + setConfig(); + cleanUp(); + }); + afterEach(() => { + setConfig(); + cleanUp(); + }); + it('empty config', function () { // this should work as usual setConfig({}); let adUnits = getAdUnits(); let innerAdUnits; - requestBidHook({adUnits}, (config) => { innerAdUnits = config.adUnits }); - let pubcid = getCookie(COOKIE_NAME); + requestBidHook((config) => { innerAdUnits = config.adUnits }, {adUnits}); + let pubcid = localStorage.getItem(ID_NAME); innerAdUnits.forEach((unit) => { unit.bids.forEach((bid) => { - expect(bid).to.have.deep.property('crumbs.pubcid'); + expect(bid).to.have.deep.nested.property('crumbs.pubcid'); expect(bid.crumbs.pubcid).to.equal(pubcid); }); }); @@ -114,35 +205,50 @@ describe('Publisher Common ID', function () { it('disable', function () { setConfig({enable: false}); - setCookie(COOKIE_NAME, '', -1); // erase cookie let adUnits = getAdUnits(); let unmodified = getAdUnits(); let innerAdUnits; expect(isPubcidEnabled()).to.be.false; - requestBidHook({adUnits}, (config) => { innerAdUnits = config.adUnits }); - expect(getCookie(COOKIE_NAME)).to.be.null; + requestBidHook((config) => { innerAdUnits = config.adUnits }, {adUnits}); + expect(getCookie(ID_NAME)).to.be.null; assert.deepEqual(innerAdUnits, unmodified); setConfig({enable: true}); // reset - requestBidHook({adUnits}, (config) => { innerAdUnits = config.adUnits }); + requestBidHook((config) => { innerAdUnits = config.adUnits }, {adUnits}); innerAdUnits.forEach((unit) => { unit.bids.forEach((bid) => { - expect(bid).to.have.deep.property('crumbs.pubcid'); + expect(bid).to.have.deep.nested.property('crumbs.pubcid'); }); }); }); it('change expiration time', function () { setConfig({expInterval: 100}); - setCookie(COOKIE_NAME, '', -1); // erase cookie expect(getExpInterval()).to.equal(100); let adUnits = getAdUnits(); let innerAdUnits; - requestBidHook({adUnits}, (config) => { innerAdUnits = config.adUnits }); + requestBidHook((config) => { innerAdUnits = config.adUnits }, {adUnits}); innerAdUnits.every((unit) => { unit.bids.forEach((bid) => { - expect(bid).to.have.deep.property('crumbs.pubcid'); + expect(bid).to.have.deep.nested.property('crumbs.pubcid'); }); - }) + }); + }); + + it('disable auto create', function() { + setConfig({ + create: false + }); + + const config = getPubcidConfig(); + expect(config.create).to.be.false; + expect(config.typeEnabled).to.equal('html5'); + + let adUnits = getAdUnits(); + let innerAdUnits; + requestBidHook((config) => { innerAdUnits = config.adUnits }, {adUnits}); + + const pubcid = localStorage.getItem(ID_NAME); + expect(pubcid).to.be.null; }); }); @@ -187,9 +293,78 @@ describe('Publisher Common ID', function () { $$PREBID_GLOBAL$$.requestBids({adUnits}); adUnits.forEach((unit) => { unit.bids.forEach((bid) => { - expect(bid).to.have.deep.property('crumbs.pubcid'); + expect(bid).to.have.deep.nested.property('crumbs.pubcid'); }); }); }); }); + + describe('Storage item functions', () => { + beforeEach(() => { cleanUp(); }); + afterEach(() => { cleanUp(); }); + + it('Test set', () => { + const key = ID_NAME; + const val = 'test-set-value'; + // Set item in localStorage + const now = Date.now(); + setStorageItem(key, val, 100); + // Check both item and expiry time are stored + const expVal = localStorage.getItem(key + EXP); + const storedVal = localStorage.getItem(key); + // Verify expiry + expect(expVal).to.not.be.null; + const expDate = new Date(expVal); + expect((expDate.getTime() - now) / 1000).to.be.closeTo(100 * 60, 5); + // Verify value + expect(storedVal).to.equal(val); + }); + + it('Test get and remove', () => { + const key = ID_NAME; + const val = 'test-get-remove'; + setStorageItem(key, val, 10); + expect(getStorageItem(key)).to.equal(val); + removeStorageItem(key); + expect(getStorageItem(key)).to.be.null; + }); + + it('Test expiry', () => { + const key = ID_NAME; + const val = 'test-expiry'; + setStorageItem(key, val, -1); + expect(localStorage.getItem(key)).to.equal(val); + expect(getStorageItem(key)).to.be.null; + expect(localStorage.getItem(key)).to.be.null; + }); + }); + + describe('event callback', () => { + beforeEach(() => { + setConfig(); + cleanUp(); + sinon.stub(events, 'getEvents').returns([]); + sinon.stub(utils, 'triggerPixel'); + }); + afterEach(() => { + setConfig(); + cleanUp(); + events.getEvents.restore(); + utils.triggerPixel.restore(); + }); + it('auction end trigger', () => { + setConfig({ + pixelUrl: '/any/url' + }); + + let adUnits = getAdUnits(); + let innerAdUnits; + requestBidHook((config) => { innerAdUnits = config.adUnits }, {adUnits}); + + expect(utils.triggerPixel.called).to.be.false; + events.emit(constants.EVENTS.AUCTION_END, {}); + expect(utils.triggerPixel.called).to.be.true; + expect(utils.triggerPixel.getCall(0).args[0]).to.include('/any/url'); + }); + }); }); diff --git a/test/spec/modules/pubmaticBidAdapter_spec.js b/test/spec/modules/pubmaticBidAdapter_spec.js index e67ec1f11a2..feb64dfe4ac 100644 --- a/test/spec/modules/pubmaticBidAdapter_spec.js +++ b/test/spec/modules/pubmaticBidAdapter_spec.js @@ -1,6 +1,7 @@ import {expect} from 'chai'; import {spec} from 'modules/pubmaticBidAdapter'; import * as utils from 'src/utils'; +import {config} from 'src/config'; const constants = require('src/constants.json'); describe('PubMatic adapter', function () { @@ -8,11 +9,50 @@ describe('PubMatic adapter', function () { let videoBidRequests; let multipleMediaRequests; let bidResponses; + let nativeBidRequests; + let nativeBidRequestsWithAllParams; + let nativeBidRequestsWithoutAsset; + let nativeBidRequestsWithRequiredParam; + let nativeBidResponse; + let validnativeBidImpression; + let validnativeBidImpressionWithRequiredParam; + let nativeBidImpressionWithoutRequiredParams; + let validnativeBidImpressionWithAllParams; + let bannerAndVideoBidRequests; + let bannerAndNativeBidRequests; + let videoAndNativeBidRequests; + let bannerVideoAndNativeBidRequests; + let bannerBidResponse; + let videoBidResponse; + let schainConfig; beforeEach(function () { + schainConfig = { + 'ver': '1.0', + 'complete': 1, + 'nodes': [ + { + 'asi': 'indirectseller.com', + 'sid': '00001', + 'hp': 1 + }, + + { + 'asi': 'indirectseller-2.com', + 'sid': '00002', + 'hp': 2 + } + ] + }; + bidRequests = [ { bidder: 'pubmatic', + mediaTypes: { + banner: { + sizes: [[728, 90], [160, 600]] + } + }, params: { publisherId: '301', adSlot: '/15671365/DMDemo@300x250:0', @@ -34,7 +74,8 @@ describe('PubMatic adapter', function () { bidId: '23acc48ad47af5', requestId: '0fb4905b-9456-4152-86be-c6f6d259ba99', bidderRequestId: '1c56ad30b9b8ca8', - transactionId: '92489f71-1bf2-49a0-adf9-000cea934729' + transactionId: '92489f71-1bf2-49a0-adf9-000cea934729', + schain: schainConfig } ]; @@ -49,6 +90,7 @@ describe('PubMatic adapter', function () { } }, bidder: 'pubmatic', + bidId: '22bddb28db77d', params: { publisherId: '5890', adSlot: 'Div1@0x0', // ad_id or tagid @@ -71,8 +113,7 @@ describe('PubMatic adapter', function () { } ]; - multipleMediaRequests = - [ + multipleMediaRequests = [ { bidder: 'pubmatic', params: { @@ -123,6 +164,371 @@ describe('PubMatic adapter', function () { } ]; + nativeBidRequests = [{ + code: '/19968336/prebid_native_example_1', + sizes: [ + [300, 250] + ], + mediaTypes: { + native: { + title: { + required: true, + length: 80 + }, + image: { + required: true, + sizes: [300, 250] + }, + sponsoredBy: { + required: true + } + } + }, + nativeParams: { + title: { required: true, length: 80 }, + image: { required: true, sizes: [300, 250] }, + sponsoredBy: { required: true } + }, + bidder: 'pubmatic', + params: { + publisherId: '5670', + adSlot: '/43743431/NativeAutomationPrebid@1x1', + }, + bidId: '2a5571261281d4', + requestId: 'B68287E1-DC39-4B38-9790-FE4F179739D6', + bidderRequestId: '1c56ad30b9b8ca8', + }]; + + nativeBidRequestsWithAllParams = [{ + code: '/19968336/prebid_native_example_1', + sizes: [ + [300, 250] + ], + mediaTypes: { + native: { + title: {required: true, len: 80, ext: {'title1': 'title2'}}, + icon: {required: true, sizes: [50, 50], ext: {'icon1': 'icon2'}}, + image: {required: true, sizes: [728, 90], ext: {'image1': 'image2'}, 'mimes': ['image/png', 'image/gif']}, + sponsoredBy: {required: true, len: 10, ext: {'sponsor1': 'sponsor2'}}, + body: {required: true, len: 10, ext: {'body1': 'body2'}}, + rating: {required: true, len: 10, ext: {'rating1': 'rating2'}}, + likes: {required: true, len: 10, ext: {'likes1': 'likes2'}}, + downloads: {required: true, len: 10, ext: {'downloads1': 'downloads2'}}, + price: {required: true, len: 10, ext: {'price1': 'price2'}}, + saleprice: {required: true, len: 10, ext: {'saleprice1': 'saleprice2'}}, + phone: {required: true, len: 10, ext: {'phone1': 'phone2'}}, + address: {required: true, len: 10, ext: {'address1': 'address2'}}, + desc2: {required: true, len: 10, ext: {'desc21': 'desc22'}}, + displayurl: {required: true, len: 10, ext: {'displayurl1': 'displayurl2'}} + } + }, + nativeParams: { + title: {required: true, len: 80, ext: {'title1': 'title2'}}, + icon: {required: true, sizes: [50, 50], ext: {'icon1': 'icon2'}}, + image: {required: true, sizes: [728, 90], ext: {'image1': 'image2'}, 'mimes': ['image/png', 'image/gif']}, + sponsoredBy: {required: true, len: 10, ext: {'sponsor1': 'sponsor2'}}, + body: {required: true, len: 10, ext: {'body1': 'body2'}}, + rating: {required: true, len: 10, ext: {'rating1': 'rating2'}}, + likes: {required: true, len: 10, ext: {'likes1': 'likes2'}}, + downloads: {required: true, len: 10, ext: {'downloads1': 'downloads2'}}, + price: {required: true, len: 10, ext: {'price1': 'price2'}}, + saleprice: {required: true, len: 10, ext: {'saleprice1': 'saleprice2'}}, + phone: {required: true, len: 10, ext: {'phone1': 'phone2'}}, + address: {required: true, len: 10, ext: {'address1': 'address2'}}, + desc2: {required: true, len: 10, ext: {'desc21': 'desc22'}}, + displayurl: {required: true, len: 10, ext: {'displayurl1': 'displayurl2'}} + }, + bidder: 'pubmatic', + params: { + publisherId: '5670', + adSlot: '/43743431/NativeAutomationPrebid@1x1', + }, + bidId: '2a5571261281d4', + requestId: 'B68287E1-DC39-4B38-9790-FE4F179739D6', + bidderRequestId: '1c56ad30b9b8ca8', + }]; + + nativeBidRequestsWithoutAsset = [{ + code: '/19968336/prebid_native_example_1', + sizes: [ + [300, 250] + ], + mediaTypes: { + native: { + type: 'image' + } + }, + nativeParams: { + title: { required: true }, + image: { required: true }, + sponsoredBy: { required: true }, + clickUrl: { required: true } + }, + bidder: 'pubmatic', + params: { + publisherId: '5670', + adSlot: '/43743431/NativeAutomationPrebid@1x1', + } + }]; + + nativeBidRequestsWithRequiredParam = [{ + code: '/19968336/prebid_native_example_1', + sizes: [ + [300, 250] + ], + mediaTypes: { + native: { + title: { + required: false, + length: 80 + }, + image: { + required: false, + sizes: [300, 250] + }, + sponsoredBy: { + required: true + } + } + }, + nativeParams: { + title: { required: false, length: 80 }, + image: { required: false, sizes: [300, 250] }, + sponsoredBy: { required: true } + }, + bidder: 'pubmatic', + params: { + publisherId: '5670', + adSlot: '/43743431/NativeAutomationPrebid@1x1', + } + }]; + + bannerAndVideoBidRequests = [ + { + code: 'div-banner-video', + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'instream' + }, + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + bidder: 'pubmatic', + params: { + publisherId: '301', + adSlot: '/15671365/DMDemo@300x250:0', + kadfloor: '1.2', + pmzoneid: 'aabc, ddef', + kadpageurl: 'www.publisher.com', + yob: '1986', + gender: 'M', + lat: '12.3', + lon: '23.7', + wiid: '1234567890', + profId: '100', + verId: '200', + currency: 'AUD', + dctr: 'key1:val1,val2|key2:val1', + video: { + mimes: ['video/mp4', 'video/x-flv'], + skippable: true, + minduration: 5, + maxduration: 30, + startdelay: 15, + playbackmethod: [1, 3], + api: [1, 2], + protocols: [2, 3], + w: 640, + h: 480, + battr: [13, 14], + linearity: 1, + placement: 2, + minbitrate: 100, + maxbitrate: 4096 + } + }, + placementCode: '/19968336/header-bid-tag-1', + sizes: [[728, 90]], + bidId: '23acc48ad47af5', + requestId: '0fb4905b-9456-4152-86be-c6f6d259ba99', + bidderRequestId: '1c56ad30b9b8ca8', + transactionId: '92489f71-1bf2-49a0-adf9-000cea934729' + } + ]; + + bannerAndNativeBidRequests = [ + { + code: 'div-banner-native', + mediaTypes: { + native: { + title: { + required: true, + length: 80 + }, + image: { + required: true, + sizes: [300, 250] + }, + sponsoredBy: { + required: true + } + }, + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + nativeParams: { + title: { required: true, length: 80 }, + image: { required: true, sizes: [300, 250] }, + sponsoredBy: { required: true } + }, + bidder: 'pubmatic', + params: { + publisherId: '301', + adSlot: '/15671365/DMDemo@300x250:0', + kadfloor: '1.2', + pmzoneid: 'aabc, ddef', + kadpageurl: 'www.publisher.com', + yob: '1986', + gender: 'M', + lat: '12.3', + lon: '23.7', + wiid: '1234567890', + profId: '100', + verId: '200', + currency: 'AUD', + dctr: 'key1:val1,val2|key2:val1' + }, + placementCode: '/19968336/header-bid-tag-1', + sizes: [[728, 90]], + bidId: '23acc48ad47af5', + requestId: '0fb4905b-9456-4152-86be-c6f6d259ba99', + bidderRequestId: '1c56ad30b9b8ca8', + transactionId: '92489f71-1bf2-49a0-adf9-000cea934729' + } + ]; + + videoAndNativeBidRequests = [ + { + code: 'div-video-native', + mediaTypes: { + native: { + title: { + required: true, + length: 80 + }, + image: { + required: true, + sizes: [300, 250] + }, + sponsoredBy: { + required: true + } + }, + video: { + playerSize: [640, 480], + context: 'instream' + } + }, + nativeParams: { + title: { required: true, length: 80 }, + image: { required: true, sizes: [300, 250] }, + sponsoredBy: { required: true } + }, + bidder: 'pubmatic', + params: { + publisherId: '301', + adSlot: '/15671365/DMDemo@300x250:0', + video: { + mimes: ['video/mp4', 'video/x-flv'], + skippable: true, + minduration: 5, + maxduration: 30, + startdelay: 15, + playbackmethod: [1, 3], + api: [1, 2], + protocols: [2, 3], + w: 640, + h: 480, + battr: [13, 14], + linearity: 1, + placement: 2, + minbitrate: 100, + maxbitrate: 4096 + } + }, + placementCode: '/19968336/header-bid-tag-1', + sizes: [[728, 90]], + bidId: '23acc48ad47af5', + requestId: '0fb4905b-9456-4152-86be-c6f6d259ba99', + bidderRequestId: '1c56ad30b9b8ca8', + transactionId: '92489f71-1bf2-49a0-adf9-000cea934729' + } + ]; + + bannerVideoAndNativeBidRequests = [ + { + code: 'div-video-native', + mediaTypes: { + native: { + title: { + required: true, + length: 80 + }, + image: { + required: true, + sizes: [300, 250] + }, + sponsoredBy: { + required: true + } + }, + video: { + playerSize: [640, 480], + context: 'instream' + }, + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + nativeParams: { + title: { required: true, length: 80 }, + image: { required: true, sizes: [300, 250] }, + sponsoredBy: { required: true } + }, + bidder: 'pubmatic', + params: { + publisherId: '301', + adSlot: '/15671365/DMDemo@300x250:0', + video: { + mimes: ['video/mp4', 'video/x-flv'], + skippable: true, + minduration: 5, + maxduration: 30, + startdelay: 15, + playbackmethod: [1, 3], + api: [1, 2], + protocols: [2, 3], + w: 640, + h: 480, + battr: [13, 14], + linearity: 1, + placement: 2, + minbitrate: 100, + maxbitrate: 4096 + } + }, + placementCode: '/19968336/header-bid-tag-1', + sizes: [[728, 90]], + bidId: '23acc48ad47af5', + requestId: '0fb4905b-9456-4152-86be-c6f6d259ba99', + bidderRequestId: '1c56ad30b9b8ca8', + transactionId: '92489f71-1bf2-49a0-adf9-000cea934729' + } + ]; + bidResponses = { 'body': { 'id': '93D3BAD6-E2E2-49FB-9D89-920B1761C865', @@ -132,10 +538,13 @@ describe('PubMatic adapter', function () { 'impid': '22bddb28db77d', 'price': 1.3, 'adm': 'image3.pubmatic.com Layer based creative', + 'adomain': ['blackrock.com'], 'h': 250, 'w': 300, 'ext': { - 'deal_channel': 6 + 'deal_channel': 6, + 'advid': 976, + 'dspid': 123 } }] }, { @@ -144,15 +553,102 @@ describe('PubMatic adapter', function () { 'impid': '22bddb28db77e', 'price': 1.7, 'adm': 'image3.pubmatic.com Layer based creative', + 'adomain': ['hivehome.com'], + 'h': 250, + 'w': 300, + 'ext': { + 'deal_channel': 5, + 'advid': 832, + 'dspid': 422 + } + }] + }] + } + }; + + nativeBidResponse = { + 'body': { + 'id': '1544691825939', + 'seatbid': [{ + 'bid': [{ + 'id': 'B68287E1-DC39-4B38-9790-FE4F179739D6', + 'impid': '2a5571261281d4', + 'price': 0.01, + 'adm': "{\"native\":{\"assets\":[{\"id\":1,\"title\":{\"text\":\"Native Test Title\"}},{\"id\":2,\"img\":{\"h\":627,\"url\":\"http://stagingpub.net/native_ads/PM-Native-Ad-1200x627.png\",\"w\":1200}},{\"data\":{\"value\":\"Sponsored By PubMatic\"},\"id\":4}],\"imptrackers\":[\"http://imptracker.com/main/9bde02d0-6017-11e4-9df7-005056967c35\",\"http://172.16.4.213/AdServer/AdDisplayTrackerServlet?operId=1&pubId=5890&siteId=5892&adId=6016&adType=12&adServerId=243&kefact=0.010000&kaxefact=0.010000&kadNetFrequecy=0&kadwidth=0&kadheight=0&kadsizeid=7&kltstamp=1544692761&indirectAdId=0&adServerOptimizerId=2&ranreq=0.1&kpbmtpfact=1.000000&dcId=1&tldId=0&passback=0&svr=MADS1107&ekefact=GSQSXOLKDgBAvRnoiNj0LxtpAnNEO30u1ZI5sITloOsP7gzh&ekaxefact=GSQSXAXLDgD0fOZLCjgbnVJiyS3D65dqDkxfs2ArpC3iugXw&ekpbmtpfact=GSQSXCDLDgB5mcooOvXtCKmx7TnNDJDY2YuHFOL3o9ceoU4H&crID=campaign111&lpu=advertiserdomain.com&ucrid=273354366805642829&campaignId=16981&creativeId=0&pctr=0.000000&wDSPByrId=511&wDspId=6&wbId=0&wrId=0&wAdvID=1&isRTB=1&rtbId=C09BB577-B8C1-4C3E-A0FF-73F6F631C80A&imprId=B68287E1-DC39-4B38-9790-FE4F179739D6&oid=B68287E1-DC39-4B38-9790-FE4F179739D6&pageURL=http%3A%2F%2Ftest.com%2FTestPages%2Fnativead.html\"],\"jstracker\":\" ', 'h': 250, 'w': 300, 'ext': { - 'deal_channel': 5 + 'deal_channel': 6 } }] }] } }; + + videoBidResponse = { + 'body': { + 'id': '93D3BAD6-E2E2-49FB-9D89-920B1761C865', + 'seatbid': [{ + 'bid': [{ + 'id': '74858439-49D7-4169-BA5D-44A046315B2F', + 'impid': '22bddb28db77d', + 'price': 1.3, + 'adm': 'Acudeo CompatibleVAST 2.0 Instream Test 1VAST 2.0 Instream Test 1https://dsptracker.com/{PSPM}00:00:04https://www.pubmatic.com', + 'h': 250, + 'w': 300, + 'ext': { + 'deal_channel': 6 + } + }] + }] + } + } }); describe('implementation', function () { @@ -192,34 +688,34 @@ describe('PubMatic adapter', function () { expect(isValid).to.equal(false); }); - it('invalid bid case: adSlot not passed', function () { - let validBid = { - bidder: 'pubmatic', - params: { - publisherId: '301' - } - }, - isValid = spec.isBidRequestValid(validBid); - expect(isValid).to.equal(false); - }); - - it('invalid bid case: adSlot is not string', function () { + it('valid bid case: adSlot is not passed', function () { let validBid = { bidder: 'pubmatic', params: { - publisherId: '301', - adSlot: 15671365 + publisherId: '301' } }, isValid = spec.isBidRequestValid(validBid); - expect(isValid).to.equal(false); + expect(isValid).to.equal(true); }); }); describe('Request formation', function () { - it('Endpoint checking', function () { + 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('Endpoint checking', function () { let request = spec.buildRequests(bidRequests); - expect(request.url).to.equal('//hbopenbid.pubmatic.com/translator?source=prebid-client'); + expect(request.url).to.equal('https://hbopenbid.pubmatic.com/translator?source=prebid-client'); expect(request.method).to.equal('POST'); }); @@ -239,7 +735,7 @@ describe('PubMatic adapter', function () { expect(data.device.geo.lon).to.equal(parseFloat(bidRequests[0].params.lon)); // Lognitude expect(data.user.geo.lat).to.equal(parseFloat(bidRequests[0].params.lat)); // Latitude expect(data.user.geo.lon).to.equal(parseFloat(bidRequests[0].params.lon)); // Lognitude - expect(data.ext.wrapper.wv).to.equal(constants.REPO_AND_VERSION); // Wrapper Version + expect(data.ext.wrapper.wv).to.equal($$REPO_AND_VERSION$$); // Wrapper Version expect(data.ext.wrapper.transactionId).to.equal(bidRequests[0].transactionId); // Prebid TransactionId expect(data.ext.wrapper.wiid).to.equal(bidRequests[0].params.wiid); // OpenWrap: Wrapper Impression ID expect(data.ext.wrapper.profile).to.equal(parseInt(bidRequests[0].params.profId)); // OpenWrap: Wrapper Profile ID @@ -252,8 +748,42 @@ describe('PubMatic adapter', function () { expect(data.imp[0].banner.h).to.equal(250); // height expect(data.imp[0].ext.pmZoneId).to.equal(bidRequests[0].params.pmzoneid.split(',').slice(0, 50).map(id => id.trim()).join()); // pmzoneid expect(data.imp[0].bidfloorcur).to.equal(bidRequests[0].params.currency); + expect(data.source.ext.schain).to.deep.equal(bidRequests[0].schain); }); + it('Request params check: without adSlot', function () { + delete bidRequests[0].params.adSlot; + let request = spec.buildRequests(bidRequests); + 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.page).to.equal(bidRequests[0].params.kadpageurl); // forced pageURL + expect(data.site.publisher.id).to.equal(bidRequests[0].params.publisherId); // publisher Id + expect(data.site.ext).to.exist.and.to.be.an('object'); // dctr parameter + expect(data.site.ext.key_val).to.exist.and.to.equal(bidRequests[0].params.dctr); + expect(data.user.yob).to.equal(parseInt(bidRequests[0].params.yob)); // YOB + expect(data.user.gender).to.equal(bidRequests[0].params.gender); // Gender + expect(data.device.geo.lat).to.equal(parseFloat(bidRequests[0].params.lat)); // Latitude + expect(data.device.geo.lon).to.equal(parseFloat(bidRequests[0].params.lon)); // Lognitude + expect(data.user.geo.lat).to.equal(parseFloat(bidRequests[0].params.lat)); // Latitude + expect(data.user.geo.lon).to.equal(parseFloat(bidRequests[0].params.lon)); // Lognitude + expect(data.ext.wrapper.wv).to.equal($$REPO_AND_VERSION$$); // Wrapper Version + expect(data.ext.wrapper.transactionId).to.equal(bidRequests[0].transactionId); // Prebid TransactionId + expect(data.ext.wrapper.wiid).to.equal(bidRequests[0].params.wiid); // OpenWrap: Wrapper Impression ID + expect(data.ext.wrapper.profile).to.equal(parseInt(bidRequests[0].params.profId)); // OpenWrap: Wrapper Profile ID + expect(data.ext.wrapper.version).to.equal(parseInt(bidRequests[0].params.verId)); // OpenWrap: Wrapper Profile Version ID + + 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.kadfloor)); // kadfloor + expect(data.imp[0].tagid).to.deep.equal(undefined); // tagid + expect(data.imp[0].banner.w).to.equal(728); // width + expect(data.imp[0].banner.h).to.equal(90); // height + expect(data.imp[0].banner.format).to.deep.equal([{w: 160, h: 600}]); + expect(data.imp[0].ext.pmZoneId).to.equal(bidRequests[0].params.pmzoneid.split(',').slice(0, 50).map(id => id.trim()).join()); // pmzoneid + expect(data.imp[0].bidfloorcur).to.equal(bidRequests[0].params.currency); + }); + it('Request params multi size format object check', function () { let bidRequests = [ { @@ -289,6 +819,11 @@ describe('PubMatic adapter', function () { /* case 2 - size passed in adslot as well as in sizes array */ bidRequests[0].sizes = [[300, 600], [300, 250]]; + bidRequests[0].mediaTypes = { + banner: { + sizes: [[300, 600], [300, 250]] + } + }; request = spec.buildRequests(bidRequests); data = JSON.parse(request.data); @@ -298,6 +833,11 @@ describe('PubMatic adapter', function () { /* case 3 - size passed in sizes but not in adslot */ bidRequests[0].params.adSlot = '/15671365/DMDemo'; bidRequests[0].sizes = [[300, 250], [300, 600]]; + bidRequests[0].mediaTypes = { + banner: { + sizes: [[300, 250], [300, 600]] + } + }; request = spec.buildRequests(bidRequests); data = JSON.parse(request.data); @@ -361,86 +901,878 @@ describe('PubMatic adapter', function () { } ]; - /* case 1 - - currency specified in both adunits - output: imp[0] and imp[1] both use currency specified in bidRequests[0].params.currency + /* case 1 - + currency specified in both adunits + output: imp[0] and imp[1] both use currency specified in bidRequests[0].params.currency + + */ + let request = spec.buildRequests(multipleBidRequests); + let data = JSON.parse(request.data); + + expect(data.imp[0].bidfloorcur).to.equal(bidRequests[0].params.currency); + expect(data.imp[1].bidfloorcur).to.equal(bidRequests[0].params.currency); + + /* case 2 - + currency specified in only 1st adunit + output: imp[0] and imp[1] both use currency specified in bidRequests[0].params.currency + + */ + delete multipleBidRequests[1].params.currency; + request = spec.buildRequests(multipleBidRequests); + data = JSON.parse(request.data); + expect(data.imp[0].bidfloorcur).to.equal(bidRequests[0].params.currency); + expect(data.imp[1].bidfloorcur).to.equal(bidRequests[0].params.currency); + + /* case 3 - + currency specified in only 1st adunit + output: imp[0] and imp[1] both use default currency - USD + + */ + delete multipleBidRequests[0].params.currency; + request = spec.buildRequests(multipleBidRequests); + data = JSON.parse(request.data); + expect(data.imp[0].bidfloorcur).to.equal('USD'); + expect(data.imp[1].bidfloorcur).to.equal('USD'); + + /* case 4 - + currency not specified in 1st adunit but specified in 2nd adunit + output: imp[0] and imp[1] both use default currency - USD + + */ + multipleBidRequests[1].params.currency = 'AUD'; + request = spec.buildRequests(multipleBidRequests); + data = JSON.parse(request.data); + expect(data.imp[0].bidfloorcur).to.equal('USD'); + expect(data.imp[1].bidfloorcur).to.equal('USD'); + }); + + 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.regs.ext.gdpr).to.equal(1); + expect(data.at).to.equal(1); // auction type + expect(data.cur[0]).to.equal('USD'); // currency + expect(data.site.domain).to.be.a('string'); // domain should be set + expect(data.site.page).to.equal(bidRequests[0].params.kadpageurl); // forced pageURL + expect(data.site.publisher.id).to.equal(bidRequests[0].params.publisherId); // publisher Id + expect(data.user.yob).to.equal(parseInt(bidRequests[0].params.yob)); // YOB + expect(data.user.gender).to.equal(bidRequests[0].params.gender); // Gender + expect(data.device.geo.lat).to.equal(parseFloat(bidRequests[0].params.lat)); // Latitude + expect(data.device.geo.lon).to.equal(parseFloat(bidRequests[0].params.lon)); // Lognitude + expect(data.user.geo.lat).to.equal(parseFloat(bidRequests[0].params.lat)); // Latitude + expect(data.user.geo.lon).to.equal(parseFloat(bidRequests[0].params.lon)); // Lognitude + expect(data.ext.wrapper.wv).to.equal($$REPO_AND_VERSION$$); // Wrapper Version + expect(data.ext.wrapper.transactionId).to.equal(bidRequests[0].transactionId); // Prebid TransactionId + expect(data.ext.wrapper.wiid).to.equal(bidRequests[0].params.wiid); // OpenWrap: Wrapper Impression ID + expect(data.ext.wrapper.profile).to.equal(parseInt(bidRequests[0].params.profId)); // OpenWrap: Wrapper Profile ID + expect(data.ext.wrapper.version).to.equal(parseInt(bidRequests[0].params.verId)); // OpenWrap: Wrapper Profile Version ID + + 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.kadfloor)); // kadfloor + expect(data.imp[0].tagid).to.equal('/15671365/DMDemo'); // tagid + expect(data.imp[0].banner.w).to.equal(300); // width + expect(data.imp[0].banner.h).to.equal(250); // height + expect(data.imp[0].ext.pmZoneId).to.equal(bidRequests[0].params.pmzoneid.split(',').slice(0, 50).map(id => id.trim()).join()); // pmzoneid + }); + + it('Request should have digitrust params', function() { + window.DigiTrust = { + getUser: function () { + } + }; + var bidRequest = {}; + let sandbox = sinon.sandbox.create(); + sandbox.stub(window.DigiTrust, 'getUser').callsFake(() => + ({ + success: true, + identity: { + privacy: {optout: false}, + id: 'testId', + keyv: 4 + } + }) + ); + + let request = spec.buildRequests(bidRequests, bidRequest); + let data = JSON.parse(request.data); + expect(data.user.eids).to.deep.equal([{ + 'source': 'digitru.st', + 'uids': [{ + 'id': 'testId', + 'atype': 1, + 'ext': { + 'keyv': 4 + } + }] + }]); + sandbox.restore(); + delete window.DigiTrust; + }); + + it('Request should not have digitrust params when DigiTrust not loaded', function() { + let request = spec.buildRequests(bidRequests, {}); + let data = JSON.parse(request.data); + expect(data.user.eids).to.deep.equal(undefined); + }); + + it('Request should not have digitrust params due to optout', function() { + window.DigiTrust = { + getUser: function () { + } + }; + let sandbox = sinon.sandbox.create(); + sandbox.stub(window.DigiTrust, 'getUser').callsFake(() => + ({ + success: true, + identity: { + privacy: {optout: true}, + id: 'testId', + keyv: 4 + } + }) + ); + + let request = spec.buildRequests(bidRequests, {}); + let data = JSON.parse(request.data); + expect(data.user.eids).to.deep.equal(undefined); + sandbox.restore(); + delete window.DigiTrust; + }); + + it('Request should not have digitrust params due to failure', function() { + window.DigiTrust = { + getUser: function () { + } + }; + let sandbox = sinon.sandbox.create(); + sandbox.stub(window.DigiTrust, 'getUser').callsFake(() => + ({ + success: false, + identity: { + privacy: {optout: false}, + id: 'testId', + keyv: 4 + } + }) + ); + + let request = spec.buildRequests(bidRequests, {}); + let data = JSON.parse(request.data); + expect(data.user.eids).to.deep.equal(undefined); + sandbox.restore(); + delete window.DigiTrust; + }); + + describe('DigiTrustId from config', function() { + var origGetConfig; + let sandbox; + beforeEach(() => { + sandbox = sinon.sandbox.create(); + window.DigiTrust = { + getUser: sandbox.spy() + }; + }); + + afterEach(() => { + sandbox.restore(); + delete window.DigiTrust; + }); + + it('Request should have digiTrustId config params', function() { + sandbox.stub(config, 'getConfig').callsFake((key) => { + var config = { + digiTrustId: { + success: true, + identity: { + privacy: {optout: false}, + id: 'testId', + keyv: 4 + } + } + }; + return config[key]; + }); + + let request = spec.buildRequests(bidRequests, {}); + let data = JSON.parse(request.data); + expect(data.user.eids).to.deep.equal([{ + 'source': 'digitru.st', + 'uids': [{ + 'id': 'testId', + 'atype': 1, + 'ext': { + 'keyv': 4 + } + }] + }]); + // should not have called DigiTrust.getUser() + expect(window.DigiTrust.getUser.notCalled).to.equal(true); + }); + + it('Request should not have digiTrustId config params due to optout', function() { + sandbox.stub(config, 'getConfig').callsFake((key) => { + var config = { + digiTrustId: { + success: true, + identity: { + privacy: {optout: true}, + id: 'testId', + keyv: 4 + } + } + } + return config[key]; + }); + let request = spec.buildRequests(bidRequests, {}); + let data = JSON.parse(request.data); + expect(data.user.eids).to.deep.equal(undefined); + // should not have called DigiTrust.getUser() + expect(window.DigiTrust.getUser.notCalled).to.equal(true); + }); + + it('Request should not have digiTrustId config params due to failure', function() { + sandbox.stub(config, 'getConfig').callsFake((key) => { + var config = { + digiTrustId: { + success: false, + identity: { + privacy: {optout: false}, + id: 'testId', + keyv: 4 + } + } + } + return config[key]; + }); + + let request = spec.buildRequests(bidRequests, {}); + let data = JSON.parse(request.data); + expect(data.user.eids).to.deep.equal(undefined); + // should not have called DigiTrust.getUser() + expect(window.DigiTrust.getUser.notCalled).to.equal(true); + }); + + it('Request should not have digiTrustId config params if they do not exist', function() { + sandbox.stub(config, 'getConfig').callsFake((key) => { + var config = {}; + return config[key]; + }); + + let request = spec.buildRequests(bidRequests, {}); + let data = JSON.parse(request.data); + expect(data.user.eids).to.deep.equal(undefined); + // should have called DigiTrust.getUser() once + expect(window.DigiTrust.getUser.calledOnce).to.equal(true); + }); + + 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', () => { + 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); + }); + + it('should NOT include coppa flag in bid request if coppa is set to false', () => { + 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); + } + }); + }); + + describe('AdsrvrOrgId from config', function() { + let sandbox; + beforeEach(() => { + sandbox = sinon.sandbox.create(); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('Request should have adsrvrOrgId config params', function() { + sandbox.stub(config, 'getConfig').callsFake((key) => { + var config = { + adsrvrOrgId: { + 'TDID': '5e740345-c25e-436d-b466-5f2f9fa95c17', + 'TDID_LOOKUP': 'TRUE', + 'TDID_CREATED_AT': '2018-10-01T07:05:40' + } + }; + return config[key]; + }); + + let request = spec.buildRequests(bidRequests, {}); + let data = JSON.parse(request.data); + expect(data.user.eids).to.deep.equal([{ + 'source': 'adserver.org', + 'uids': [{ + 'id': '5e740345-c25e-436d-b466-5f2f9fa95c17', + 'atype': 1, + 'ext': { + 'rtiPartner': 'TDID' + } + }] + }]); + }); + + it('Request should NOT have adsrvrOrgId config params if id in adsrvrOrgId is NOT string', function() { + sandbox.stub(config, 'getConfig').callsFake((key) => { + var config = { + adsrvrOrgId: { + 'TDID': 1, + 'TDID_LOOKUP': 'TRUE', + 'TDID_CREATED_AT': '2018-10-01T07:05:40' + } + }; + return config[key]; + }); + + let request = spec.buildRequests(bidRequests, {}); + let data = JSON.parse(request.data); + expect(data.user.eids).to.deep.equal(undefined); + }); + + it('Request should NOT have adsrvrOrgId config params if adsrvrOrgId is NOT object', function() { + sandbox.stub(config, 'getConfig').callsFake((key) => { + var config = { + adsrvrOrgId: null + }; + return config[key]; + }); + + let request = spec.buildRequests(bidRequests, {}); + let data = JSON.parse(request.data); + expect(data.user.eids).to.deep.equal(undefined); + }); + + it('Request should NOT have adsrvrOrgId config params if id in adsrvrOrgId is NOT set', function() { + sandbox.stub(config, 'getConfig').callsFake((key) => { + var config = { + adsrvrOrgId: { + 'TDID_LOOKUP': 'TRUE', + 'TDID_CREATED_AT': '2018-10-01T07:05:40' + } + }; + return config[key]; + }); + + let request = spec.buildRequests(bidRequests, {}); + let data = JSON.parse(request.data); + expect(data.user.eids).to.deep.equal(undefined); + }); + }); + + describe('AdsrvrOrgId from userId module', function() { + let sandbox; + beforeEach(() => { + sandbox = sinon.sandbox.create(); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('Request should have AdsrvrOrgId config params', function() { + bidRequests[0].userId = {}; + bidRequests[0].userId.tdid = 'TTD_ID_FROM_USER_ID_MODULE'; + let request = spec.buildRequests(bidRequests, {}); + let data = JSON.parse(request.data); + expect(data.user.eids).to.deep.equal([{ + 'source': 'adserver.org', + 'uids': [{ + 'id': 'TTD_ID_FROM_USER_ID_MODULE', + 'atype': 1, + 'ext': { + 'rtiPartner': 'TDID' + } + }] + }]); + }); + + it('Request should have adsrvrOrgId from UserId Module if config and userId module both have TTD ID', function() { + sandbox.stub(config, 'getConfig').callsFake((key) => { + var config = { + adsrvrOrgId: { + 'TDID': 'TTD_ID_FROM_CONFIG', + 'TDID_LOOKUP': 'TRUE', + 'TDID_CREATED_AT': '2018-10-01T07:05:40' + } + }; + return config[key]; + }); + bidRequests[0].userId = {}; + bidRequests[0].userId.tdid = 'TTD_ID_FROM_USER_ID_MODULE'; + let request = spec.buildRequests(bidRequests, {}); + let data = JSON.parse(request.data); + expect(data.user.eids).to.deep.equal([{ + 'source': 'adserver.org', + 'uids': [{ + 'id': 'TTD_ID_FROM_USER_ID_MODULE', + 'atype': 1, + 'ext': { + 'rtiPartner': 'TDID' + } + }] + }]); + }); - */ - let request = spec.buildRequests(multipleBidRequests); - let data = JSON.parse(request.data); + it('Request should NOT have adsrvrOrgId params if userId is NOT object', function() { + let request = spec.buildRequests(bidRequests, {}); + let data = JSON.parse(request.data); + expect(data.user.eids).to.deep.equal(undefined); + }); - expect(data.imp[0].bidfloorcur).to.equal(bidRequests[0].params.currency); - expect(data.imp[1].bidfloorcur).to.equal(bidRequests[0].params.currency); + it('Request should NOT have adsrvrOrgId params if userId.tdid is NOT string', function() { + bidRequests[0].userId = { + tdid: 1234 + }; + let request = spec.buildRequests(bidRequests, {}); + let data = JSON.parse(request.data); + expect(data.user.eids).to.deep.equal(undefined); + }); + }); - /* case 2 - - currency specified in only 1st adunit - output: imp[0] and imp[1] both use currency specified in bidRequests[0].params.currency + describe('AdsrvrOrgId and Digitrust', function() { + // here we are considering cases only of accepting DigiTrustId from config + let sandbox; + beforeEach(() => { + sandbox = sinon.sandbox.create(); + window.DigiTrust = { + getUser: sandbox.spy() + }; + }); - */ - delete multipleBidRequests[1].params.currency; - request = spec.buildRequests(multipleBidRequests); - data = JSON.parse(request.data); - expect(data.imp[0].bidfloorcur).to.equal(bidRequests[0].params.currency); - expect(data.imp[1].bidfloorcur).to.equal(bidRequests[0].params.currency); + afterEach(() => { + sandbox.restore(); + delete window.DigiTrust; + }); - /* case 3 - - currency specified in only 1st adunit - output: imp[0] and imp[1] both use default currency - USD + it('Request should have id of both AdsrvrOrgId and Digitrust if both have returned valid ids', function() { + sandbox.stub(config, 'getConfig').callsFake((key) => { + var config = { + adsrvrOrgId: { + 'TDID': '5e740345-c25e-436d-b466-5f2f9fa95c17', + 'TDID_LOOKUP': 'TRUE', + 'TDID_CREATED_AT': '2018-10-01T07:05:40' + }, + digiTrustId: { + success: true, + identity: { + privacy: {optout: false}, + id: 'testId', + keyv: 4 + } + } + }; + return config[key]; + }); - */ - delete multipleBidRequests[0].params.currency; - request = spec.buildRequests(multipleBidRequests); - data = JSON.parse(request.data); - expect(data.imp[0].bidfloorcur).to.equal('USD'); - expect(data.imp[1].bidfloorcur).to.equal('USD'); + let request = spec.buildRequests(bidRequests, {}); + let data = JSON.parse(request.data); + expect(data.user.eids).to.deep.equal([{ + 'source': 'digitru.st', + 'uids': [{ + 'id': 'testId', + 'atype': 1, + 'ext': { + 'keyv': 4 + } + }] + }, { + 'source': 'adserver.org', + 'uids': [{ + 'id': '5e740345-c25e-436d-b466-5f2f9fa95c17', + 'atype': 1, + 'ext': { + 'rtiPartner': 'TDID' + } + }] + }]); + }); - /* case 4 - - currency not specified in 1st adunit but specified in 2nd adunit - output: imp[0] and imp[1] both use default currency - USD + it('Request should have id of only AdsrvrOrgId and NOT Digitrust if only AdsrvrOrgId have returned valid id', function() { + sandbox.stub(config, 'getConfig').callsFake((key) => { + var config = { + adsrvrOrgId: { + 'TDID': '5e740345-c25e-436d-b466-5f2f9fa95c17', + 'TDID_LOOKUP': 'TRUE', + 'TDID_CREATED_AT': '2018-10-01T07:05:40' + }, + digiTrustId: { + success: true, + identity: { + privacy: {optout: true}, + id: 'testId', + keyv: 4 + } + } + }; + return config[key]; + }); - */ - multipleBidRequests[1].params.currency = 'AUD'; - request = spec.buildRequests(multipleBidRequests); - data = JSON.parse(request.data); - expect(data.imp[0].bidfloorcur).to.equal('USD'); - expect(data.imp[1].bidfloorcur).to.equal('USD'); + let request = spec.buildRequests(bidRequests, {}); + let data = JSON.parse(request.data); + expect(data.user.eids).to.deep.equal([{ + 'source': 'adserver.org', + 'uids': [{ + 'id': '5e740345-c25e-436d-b466-5f2f9fa95c17', + 'atype': 1, + 'ext': { + 'rtiPartner': 'TDID' + } + }] + }]); + }); + + it('Request should have id of only Digitrust and NOT AdsrvrOrgId if only Digitrust have returned valid id', function() { + sandbox.stub(config, 'getConfig').callsFake((key) => { + var config = { + adsrvrOrgId: { + 'TDID_LOOKUP': 'TRUE', + 'TDID_CREATED_AT': '2018-10-01T07:05:40' + }, + digiTrustId: { + success: true, + identity: { + privacy: {optout: false}, + id: 'testId', + keyv: 4 + } + } + }; + return config[key]; + }); + + let request = spec.buildRequests(bidRequests, {}); + let data = JSON.parse(request.data); + expect(data.user.eids).to.deep.equal([{ + 'source': 'digitru.st', + 'uids': [{ + 'id': 'testId', + 'atype': 1, + 'ext': { + 'keyv': 4 + } + }] + }]); + }); + + it('Request should NOT have id of Digitrust and NOT AdsrvrOrgId if only both have NOT returned valid ids', function() { + sandbox.stub(config, 'getConfig').callsFake((key) => { + var config = { + adsrvrOrgId: { + 'TDID_LOOKUP': 'TRUE', + 'TDID_CREATED_AT': '2018-10-01T07:05:40' + }, + digiTrustId: { + success: true, + identity: { + privacy: {optout: true}, + id: 'testId', + keyv: 4 + } + } + }; + return config[key]; + }); + + let request = spec.buildRequests(bidRequests, {}); + let data = JSON.parse(request.data); + expect(data.user.eids).to.deep.equal(undefined); + }); }); - 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.regs.ext.gdpr).to.equal(1); - expect(data.at).to.equal(1); // auction type - expect(data.cur[0]).to.equal('USD'); // currency - expect(data.site.domain).to.be.a('string'); // domain should be set - expect(data.site.page).to.equal(bidRequests[0].params.kadpageurl); // forced pageURL - expect(data.site.publisher.id).to.equal(bidRequests[0].params.publisherId); // publisher Id - expect(data.user.yob).to.equal(parseInt(bidRequests[0].params.yob)); // YOB - expect(data.user.gender).to.equal(bidRequests[0].params.gender); // Gender - expect(data.device.geo.lat).to.equal(parseFloat(bidRequests[0].params.lat)); // Latitude - expect(data.device.geo.lon).to.equal(parseFloat(bidRequests[0].params.lon)); // Lognitude - expect(data.user.geo.lat).to.equal(parseFloat(bidRequests[0].params.lat)); // Latitude - expect(data.user.geo.lon).to.equal(parseFloat(bidRequests[0].params.lon)); // Lognitude - expect(data.ext.wrapper.wv).to.equal(constants.REPO_AND_VERSION); // Wrapper Version - expect(data.ext.wrapper.transactionId).to.equal(bidRequests[0].transactionId); // Prebid TransactionId - expect(data.ext.wrapper.wiid).to.equal(bidRequests[0].params.wiid); // OpenWrap: Wrapper Impression ID - expect(data.ext.wrapper.profile).to.equal(parseInt(bidRequests[0].params.profId)); // OpenWrap: Wrapper Profile ID - expect(data.ext.wrapper.version).to.equal(parseInt(bidRequests[0].params.verId)); // OpenWrap: Wrapper Profile Version ID + describe('UserIds from request', function() { + describe('pubcommon Id', function() { + it('send the pubcommon id if it is present', function() { + bidRequests[0].userId = {}; + bidRequests[0].userId.pubcid = 'pub_common_user_id'; + let request = spec.buildRequests(bidRequests, {}); + let data = JSON.parse(request.data); + expect(data.user.eids).to.deep.equal([{ + 'source': 'pubcommon', + 'uids': [{ + 'id': 'pub_common_user_id', + 'atype': 1 + }] + }]); + }); - 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.kadfloor)); // kadfloor - expect(data.imp[0].tagid).to.equal('/15671365/DMDemo'); // tagid - expect(data.imp[0].banner.w).to.equal(300); // width - expect(data.imp[0].banner.h).to.equal(250); // height - expect(data.imp[0].ext.pmZoneId).to.equal(bidRequests[0].params.pmzoneid.split(',').slice(0, 50).map(id => id.trim()).join()); // pmzoneid - }); + it('do not pass if not string', function() { + bidRequests[0].userId = {}; + bidRequests[0].userId.pubcid = 1; + let request = spec.buildRequests(bidRequests, {}); + let data = JSON.parse(request.data); + expect(data.user.eids).to.equal(undefined); + bidRequests[0].userId.pubcid = []; + request = spec.buildRequests(bidRequests, {}); + data = JSON.parse(request.data); + expect(data.user.eids).to.equal(undefined); + bidRequests[0].userId.pubcid = null; + request = spec.buildRequests(bidRequests, {}); + data = JSON.parse(request.data); + expect(data.user.eids).to.equal(undefined); + bidRequests[0].userId.pubcid = {}; + request = spec.buildRequests(bidRequests, {}); + data = JSON.parse(request.data); + expect(data.user.eids).to.equal(undefined); + }); + }); + + describe('Digitrust Id', function() { + it('send the digitrust id if it is present', function() { + bidRequests[0].userId = {}; + bidRequests[0].userId.digitrustid = {data: {id: 'digitrust_user_id'}}; + let request = spec.buildRequests(bidRequests, {}); + let data = JSON.parse(request.data); + expect(data.user.eids).to.deep.equal([{ + 'source': 'digitru.st', + 'uids': [{ + 'id': 'digitrust_user_id', + 'atype': 1 + }] + }]); + }); + + it('do not pass if not string', function() { + bidRequests[0].userId = {}; + bidRequests[0].userId.digitrustid = {data: {id: 1}}; + let request = spec.buildRequests(bidRequests, {}); + let data = JSON.parse(request.data); + expect(data.user.eids).to.equal(undefined); + bidRequests[0].userId.digitrustid = {data: {id: []}}; + request = spec.buildRequests(bidRequests, {}); + data = JSON.parse(request.data); + expect(data.user.eids).to.equal(undefined); + bidRequests[0].userId.digitrustid = {data: {id: null}}; + request = spec.buildRequests(bidRequests, {}); + data = JSON.parse(request.data); + expect(data.user.eids).to.equal(undefined); + bidRequests[0].userId.digitrustid = {data: {id: {}}}; + request = spec.buildRequests(bidRequests, {}); + data = JSON.parse(request.data); + expect(data.user.eids).to.equal(undefined); + }); + }); + + describe('ID5 Id', function() { + it('send the id5 id if it is present', function() { + bidRequests[0].userId = {}; + bidRequests[0].userId.id5id = 'id5-user-id'; + let request = spec.buildRequests(bidRequests, {}); + let data = JSON.parse(request.data); + expect(data.user.eids).to.deep.equal([{ + 'source': 'id5-sync.com', + 'uids': [{ + 'id': 'id5-user-id', + 'atype': 1 + }] + }]); + }); + + it('do not pass if not string', function() { + bidRequests[0].userId = {}; + bidRequests[0].userId.id5id = 1; + let request = spec.buildRequests(bidRequests, {}); + let data = JSON.parse(request.data); + expect(data.user.eids).to.equal(undefined); + bidRequests[0].userId.id5id = []; + request = spec.buildRequests(bidRequests, {}); + data = JSON.parse(request.data); + expect(data.user.eids).to.equal(undefined); + bidRequests[0].userId.id5id = null; + request = spec.buildRequests(bidRequests, {}); + data = JSON.parse(request.data); + expect(data.user.eids).to.equal(undefined); + bidRequests[0].userId.id5id = {}; + request = spec.buildRequests(bidRequests, {}); + data = JSON.parse(request.data); + expect(data.user.eids).to.equal(undefined); + }); + }); + + describe('CriteoRTUS Id', function() { + it('send the criteo id if it is present', function() { + bidRequests[0].userId = {}; + bidRequests[0].userId.criteortus = {pubmatic: {userid: 'criteo-rtus-user-id'}}; + let request = spec.buildRequests(bidRequests, {}); + let data = JSON.parse(request.data); + expect(data.user.eids).to.deep.equal([{ + 'source': 'criteortus', + 'uids': [{ + 'id': 'criteo-rtus-user-id', + 'atype': 1 + }] + }]); + }); + + it('do not pass if not string', function() { + bidRequests[0].userId = {}; + bidRequests[0].userId.criteortus = {appnexus: {userid: 'criteo-rtus-user-id'}}; + let request = spec.buildRequests(bidRequests, {}); + let data = JSON.parse(request.data); + expect(data.user.eids).to.equal(undefined); + bidRequests[0].userId.criteortus = {pubmatic: {userid: 1}}; + request = spec.buildRequests(bidRequests, {}); + data = JSON.parse(request.data); + expect(data.user.eids).to.equal(undefined); + bidRequests[0].userId.criteortus = {pubmatic: {userid: []}}; + request = spec.buildRequests(bidRequests, {}); + data = JSON.parse(request.data); + expect(data.user.eids).to.equal(undefined); + bidRequests[0].userId.criteortus = {pubmatic: {userid: null}}; + request = spec.buildRequests(bidRequests, {}); + data = JSON.parse(request.data); + expect(data.user.eids).to.equal(undefined); + bidRequests[0].userId.criteortus = {pubmatic: {userid: {}}}; + request = spec.buildRequests(bidRequests, {}); + data = JSON.parse(request.data); + expect(data.user.eids).to.equal(undefined); + }); + }); + + describe('IdentityLink Id', function() { + it('send the identity-link id if it is present', function() { + bidRequests[0].userId = {}; + bidRequests[0].userId.idl_env = 'identity-link-user-id'; + let request = spec.buildRequests(bidRequests, {}); + let data = JSON.parse(request.data); + expect(data.user.eids).to.deep.equal([{ + 'source': 'liveramp.com', + 'uids': [{ + 'id': 'identity-link-user-id', + 'atype': 1 + }] + }]); + }); + + it('do not pass if not string', function() { + bidRequests[0].userId = {}; + bidRequests[0].userId.idl_env = 1; + let request = spec.buildRequests(bidRequests, {}); + let data = JSON.parse(request.data); + expect(data.user.eids).to.equal(undefined); + bidRequests[0].userId.idl_env = []; + request = spec.buildRequests(bidRequests, {}); + data = JSON.parse(request.data); + expect(data.user.eids).to.equal(undefined); + bidRequests[0].userId.idl_env = null; + request = spec.buildRequests(bidRequests, {}); + data = JSON.parse(request.data); + expect(data.user.eids).to.equal(undefined); + bidRequests[0].userId.idl_env = {}; + request = spec.buildRequests(bidRequests, {}); + data = JSON.parse(request.data); + expect(data.user.eids).to.equal(undefined); + }); + }); + + describe('LiveIntent Id', function() { + it('send the LiveIntent id if it is present', function() { + bidRequests[0].userId = {}; + bidRequests[0].userId.lipb = { lipbid: 'live-intent-user-id' }; + let request = spec.buildRequests(bidRequests, {}); + let data = JSON.parse(request.data); + expect(data.user.eids).to.deep.equal([{ + 'source': 'liveintent.com', + 'uids': [{ + 'id': 'live-intent-user-id', + 'atype': 1 + }] + }]); + }); + + it('do not pass if not string', function() { + bidRequests[0].userId = {}; + bidRequests[0].userId.lipb = { lipbid: 1 }; + let request = spec.buildRequests(bidRequests, {}); + let data = JSON.parse(request.data); + expect(data.user.eids).to.equal(undefined); + bidRequests[0].userId.lipb.lipbid = []; + request = spec.buildRequests(bidRequests, {}); + data = JSON.parse(request.data); + expect(data.user.eids).to.equal(undefined); + bidRequests[0].userId.lipb.lipbid = null; + request = spec.buildRequests(bidRequests, {}); + data = JSON.parse(request.data); + expect(data.user.eids).to.equal(undefined); + bidRequests[0].userId.lipb.lipbid = {}; + request = spec.buildRequests(bidRequests, {}); + data = JSON.parse(request.data); + expect(data.user.eids).to.equal(undefined); + }); + }); + + describe('Parrable Id', function() { + it('send the Parrable id if it is present', function() { + bidRequests[0].userId = {}; + bidRequests[0].userId.parrableid = 'parrable-user-id'; + let request = spec.buildRequests(bidRequests, {}); + let data = JSON.parse(request.data); + expect(data.user.eids).to.deep.equal([{ + 'source': 'parrable.com', + 'uids': [{ + 'id': 'parrable-user-id', + 'atype': 1 + }] + }]); + }); + + it('do not pass if not string', function() { + bidRequests[0].userId = {}; + bidRequests[0].userId.parrableid = 1; + let request = spec.buildRequests(bidRequests, {}); + let data = JSON.parse(request.data); + expect(data.user.eids).to.equal(undefined); + bidRequests[0].userId.parrableid = []; + request = spec.buildRequests(bidRequests, {}); + data = JSON.parse(request.data); + expect(data.user.eids).to.equal(undefined); + bidRequests[0].userId.parrableid = null; + request = spec.buildRequests(bidRequests, {}); + data = JSON.parse(request.data); + expect(data.user.eids).to.equal(undefined); + bidRequests[0].userId.parrableid = {}; + request = spec.buildRequests(bidRequests, {}); + data = JSON.parse(request.data); + expect(data.user.eids).to.equal(undefined); + }); + }); + }); it('Request params check for video ad', function () { let request = spec.buildRequests(videoBidRequests); @@ -498,7 +1830,7 @@ describe('PubMatic adapter', function () { expect(data.device.geo.lon).to.equal(parseFloat(multipleMediaRequests[0].params.lon)); // Lognitude expect(data.user.geo.lat).to.equal(parseFloat(multipleMediaRequests[0].params.lat)); // Latitude expect(data.user.geo.lon).to.equal(parseFloat(multipleMediaRequests[0].params.lon)); // Lognitude - expect(data.ext.wrapper.wv).to.equal(constants.REPO_AND_VERSION); // Wrapper Version + expect(data.ext.wrapper.wv).to.equal($$REPO_AND_VERSION$$); // Wrapper Version expect(data.ext.wrapper.transactionId).to.equal(multipleMediaRequests[0].transactionId); // Prebid TransactionId expect(data.ext.wrapper.wiid).to.equal(multipleMediaRequests[0].params.wiid); // OpenWrap: Wrapper Impression ID expect(data.ext.wrapper.profile).to.equal(parseInt(multipleMediaRequests[0].params.profId)); // OpenWrap: Wrapper Profile ID @@ -547,6 +1879,257 @@ describe('PubMatic adapter', function () { expect(data.imp[1]['video']['w']).to.equal(multipleMediaRequests[1].mediaTypes.video.playerSize[0]); expect(data.imp[1]['video']['h']).to.equal(multipleMediaRequests[1].mediaTypes.video.playerSize[1]); }); + + it('Request params should have valid native bid request for all valid params', function () { + let request = spec.buildRequests(nativeBidRequests); + let data = JSON.parse(request.data); + expect(data.imp[0].native).to.exist; + expect(data.imp[0].native['request']).to.exist; + expect(data.imp[0].tagid).to.equal('/43743431/NativeAutomationPrebid'); + expect(data.imp[0]['native']['request']).to.exist.and.to.be.an('string'); + expect(data.imp[0]['native']['request']).to.exist.and.to.equal(validnativeBidImpression.native.request); + }); + + it('Request params should not have valid native bid request for non native request', function () { + let request = spec.buildRequests(bidRequests); + let data = JSON.parse(request.data); + expect(data.imp[0].native).to.not.exist; + }); + + it('Request params should have valid native bid request with valid required param values for all valid params', function () { + let request = spec.buildRequests(nativeBidRequestsWithRequiredParam); + let data = JSON.parse(request.data); + expect(data.imp[0].native).to.exist; + expect(data.imp[0].native['request']).to.exist; + expect(data.imp[0].tagid).to.equal('/43743431/NativeAutomationPrebid'); + expect(data.imp[0]['native']['request']).to.exist.and.to.be.an('string'); + expect(data.imp[0]['native']['request']).to.exist.and.to.equal(validnativeBidImpressionWithRequiredParam.native.request); + }); + + it('should not have valid native request if assets are not defined with minimum required params and only native is the slot', function () { + let request = spec.buildRequests(nativeBidRequestsWithoutAsset); + expect(request).to.deep.equal(undefined); + }); + + it('Request params should have valid native bid request for all native params', function () { + let request = spec.buildRequests(nativeBidRequestsWithAllParams); + let data = JSON.parse(request.data); + expect(data.imp[0].native).to.exist; + expect(data.imp[0].native['request']).to.exist; + expect(data.imp[0].tagid).to.equal('/43743431/NativeAutomationPrebid'); + expect(data.imp[0]['native']['request']).to.exist.and.to.be.an('string'); + expect(data.imp[0]['native']['request']).to.exist.and.to.equal(validnativeBidImpressionWithAllParams.native.request); + }); + + it('Request params - should handle banner and video format in single adunit', function() { + let request = spec.buildRequests(bannerAndVideoBidRequests); + let data = JSON.parse(request.data); + data = data.imp[0]; + expect(data.banner).to.exist; + expect(data.banner.w).to.equal(300); + expect(data.banner.h).to.equal(250); + expect(data.banner.format).to.exist; + expect(data.banner.format.length).to.equal(bannerAndVideoBidRequests[0].mediaTypes.banner.sizes.length); + + // Case: when size is not present in adslo + bannerAndVideoBidRequests[0].params.adSlot = '/15671365/DMDemo'; + request = spec.buildRequests(bannerAndVideoBidRequests); + data = JSON.parse(request.data); + data = data.imp[0]; + expect(data.banner).to.exist; + expect(data.banner.w).to.equal(bannerAndVideoBidRequests[0].mediaTypes.banner.sizes[0][0]); + expect(data.banner.h).to.equal(bannerAndVideoBidRequests[0].mediaTypes.banner.sizes[0][1]); + expect(data.banner.format).to.exist; + expect(data.banner.format.length).to.equal(bannerAndVideoBidRequests[0].mediaTypes.banner.sizes.length - 1); + + expect(data.video).to.exist; + expect(data.video.w).to.equal(bannerAndVideoBidRequests[0].mediaTypes.video.playerSize[0]); + expect(data.video.h).to.equal(bannerAndVideoBidRequests[0].mediaTypes.video.playerSize[1]); + }); + + it('Request params - banner and video req in single adslot - should ignore banner imp if banner size is set to fluid and send video imp object', function () { + /* Adslot configured for banner and video. + banner size is set to [['fluid'], [300, 250]] + adslot specifies a size as 300x250 + => banner imp object should have primary w and h set to 300 and 250. fluid is ignored + */ + bannerAndVideoBidRequests[0].mediaTypes.banner.sizes = [['fluid'], [160, 600]]; + + let request = spec.buildRequests(bannerAndVideoBidRequests); + let data = JSON.parse(request.data); + data = data.imp[0]; + + expect(data.banner).to.exist; + expect(data.banner.w).to.equal(300); + expect(data.banner.h).to.equal(250); + expect(data.banner.format).to.exist; + expect(data.banner.format[0].w).to.equal(160); + expect(data.banner.format[0].h).to.equal(600); + + /* Adslot configured for banner and video. + banner size is set to [['fluid'], [300, 250]] + adslot does not specify any size + => banner imp object should have primary w and h set to 300 and 250. fluid is ignored + */ + bannerAndVideoBidRequests[0].mediaTypes.banner.sizes = [['fluid'], [160, 600]]; + bannerAndVideoBidRequests[0].params.adSlot = '/15671365/DMDemo'; + + request = spec.buildRequests(bannerAndVideoBidRequests); + data = JSON.parse(request.data); + data = data.imp[0]; + + expect(data.banner).to.exist; + expect(data.banner.w).to.equal(160); + expect(data.banner.h).to.equal(600); + expect(data.banner.format).to.not.exist; + + /* Adslot configured for banner and video. + banner size is set to [[728 90], ['fluid'], [300, 250]] + adslot does not specify any size + => banner imp object should have primary w and h set to 728 and 90. + banner.format should have 300, 250 set in it + fluid is ignore + */ + + bannerAndVideoBidRequests[0].mediaTypes.banner.sizes = [[728, 90], ['fluid'], [300, 250]]; + request = spec.buildRequests(bannerAndVideoBidRequests); + data = JSON.parse(request.data); + data = data.imp[0]; + + expect(data.banner).to.exist; + expect(data.banner.w).to.equal(728); + expect(data.banner.h).to.equal(90); + expect(data.banner.format).to.exist; + expect(data.banner.format[0].w).to.equal(300); + expect(data.banner.format[0].h).to.equal(250); + + /* Adslot configured for banner and video. + banner size is set to [['fluid']] + adslot does not specify any size + => banner object should not be sent in the request. only video should be sent. + */ + + bannerAndVideoBidRequests[0].mediaTypes.banner.sizes = [['fluid']]; + request = spec.buildRequests(bannerAndVideoBidRequests); + data = JSON.parse(request.data); + data = data.imp[0]; + + expect(data.banner).to.not.exist; + expect(data.video).to.exist; + }); + + it('Request params - should not contain banner imp if mediaTypes.banner is not present and sizes is specified in bid.sizes', function() { + delete bannerAndVideoBidRequests[0].mediaTypes.banner; + bannerAndVideoBidRequests[0].params.sizes = [300, 250]; + + let request = spec.buildRequests(bannerAndVideoBidRequests); + let data = JSON.parse(request.data); + data = data.imp[0]; + expect(data.banner).to.not.exist; + }); + + it('Request params - should handle banner and native format in single adunit', function() { + let request = spec.buildRequests(bannerAndNativeBidRequests); + let data = JSON.parse(request.data); + data = data.imp[0]; + + expect(data.banner).to.exist; + expect(data.banner.w).to.equal(300); + expect(data.banner.h).to.equal(250); + expect(data.banner.format).to.exist; + expect(data.banner.format.length).to.equal(bannerAndNativeBidRequests[0].mediaTypes.banner.sizes.length); + + expect(data.native).to.exist; + expect(data.native.request).to.exist; + }); + + it('Request params - should handle video and native format in single adunit', function() { + let request = spec.buildRequests(videoAndNativeBidRequests); + let data = JSON.parse(request.data); + data = data.imp[0]; + + expect(data.video).to.exist; + expect(data.video.w).to.equal(bannerAndVideoBidRequests[0].mediaTypes.video.playerSize[0]); + expect(data.video.h).to.equal(bannerAndVideoBidRequests[0].mediaTypes.video.playerSize[1]); + + expect(data.native).to.exist; + expect(data.native.request).to.exist; + }); + + it('Request params - should handle banner, video and native format in single adunit', function() { + let request = spec.buildRequests(bannerVideoAndNativeBidRequests); + let data = JSON.parse(request.data); + data = data.imp[0]; + + expect(data.banner).to.exist; + expect(data.banner.w).to.equal(300); + expect(data.banner.h).to.equal(250); + expect(data.banner.format).to.exist; + expect(data.banner.format.length).to.equal(bannerAndNativeBidRequests[0].mediaTypes.banner.sizes.length); + + expect(data.video).to.exist; + expect(data.video.w).to.equal(bannerAndVideoBidRequests[0].mediaTypes.video.playerSize[0]); + expect(data.video.h).to.equal(bannerAndVideoBidRequests[0].mediaTypes.video.playerSize[1]); + + expect(data.native).to.exist; + expect(data.native.request).to.exist; + }); + + it('Request params - should not add banner object if mediaTypes.banner is missing, but adunits.sizes is present', function() { + delete bannerAndNativeBidRequests[0].mediaTypes.banner; + bannerAndNativeBidRequests[0].sizes = [729, 90]; + + let request = spec.buildRequests(bannerAndNativeBidRequests); + let data = JSON.parse(request.data); + data = data.imp[0]; + + expect(data.banner).to.not.exist; + + expect(data.native).to.exist; + expect(data.native.request).to.exist; + }); + + it('Request params - banner and native multiformat request - should not have native object incase of invalid config present', function() { + bannerAndNativeBidRequests[0].mediaTypes.native = { + title: { required: true }, + image: { required: true }, + sponsoredBy: { required: true }, + clickUrl: { required: true } + }; + bannerAndNativeBidRequests[0].nativeParams = { + title: { required: true }, + image: { required: true }, + sponsoredBy: { required: true }, + clickUrl: { required: true } + } + let request = spec.buildRequests(bannerAndNativeBidRequests); + let data = JSON.parse(request.data); + data = data.imp[0]; + + expect(data.banner).to.exist; + expect(data.native).to.not.exist; + }); + + it('Request params - video and native multiformat request - should not have native object incase of invalid config present', function() { + videoAndNativeBidRequests[0].mediaTypes.native = { + title: { required: true }, + image: { required: true }, + sponsoredBy: { required: true }, + clickUrl: { required: true } + }; + videoAndNativeBidRequests[0].nativeParams = { + title: { required: true }, + image: { required: true }, + sponsoredBy: { required: true }, + clickUrl: { required: true } + } + let request = spec.buildRequests(videoAndNativeBidRequests); + let data = JSON.parse(request.data); + data = data.imp[0]; + + expect(data.video).to.exist; + expect(data.native).to.not.exist; + }); }); it('Request params dctr check', function () { @@ -632,9 +2215,104 @@ describe('PubMatic adapter', function () { expect(data.site.ext).to.not.exist; }); + describe('Request param bcat checking', function() { + let multipleBidRequests = [ + { + bidder: 'pubmatic', + params: { + publisherId: '301', + adSlot: '/15671365/DMDemo@300x250:0', + kadfloor: '1.2', + pmzoneid: 'aabc, ddef', + kadpageurl: 'www.publisher.com', + yob: '1986', + gender: 'M', + lat: '12.3', + lon: '23.7', + wiid: '1234567890', + profId: '100', + verId: '200', + currency: 'AUD', + dctr: 'key1=val1|key2=val2,!val3' + }, + placementCode: '/19968336/header-bid-tag-1', + sizes: [[300, 250], [300, 600]], + bidId: '23acc48ad47af5', + requestId: '0fb4905b-9456-4152-86be-c6f6d259ba99', + bidderRequestId: '1c56ad30b9b8ca8', + transactionId: '92489f71-1bf2-49a0-adf9-000cea934729' + }, + { + bidder: 'pubmatic', + params: { + publisherId: '301', + adSlot: '/15671365/DMDemo@300x250:0', + kadfloor: '1.2', + pmzoneid: 'aabc, ddef', + kadpageurl: 'www.publisher.com', + yob: '1986', + gender: 'M', + lat: '12.3', + lon: '23.7', + wiid: '1234567890', + profId: '100', + verId: '200', + currency: 'GBP', + dctr: 'key1=val3|key2=val1,!val3|key3=val123' + }, + placementCode: '/19968336/header-bid-tag-1', + sizes: [[300, 250], [300, 600]], + bidId: '23acc48ad47af5', + requestId: '0fb4905b-9456-4152-86be-c6f6d259ba99', + bidderRequestId: '1c56ad30b9b8ca8', + transactionId: '92489f71-1bf2-49a0-adf9-000cea934729' + } + ]; + + it('bcat: pass only strings', function() { + multipleBidRequests[0].params.bcat = [1, 2, 3, 'IAB1', 'IAB2']; + let request = spec.buildRequests(multipleBidRequests); + let data = JSON.parse(request.data); + expect(data.bcat).to.exist.and.to.deep.equal(['IAB1', 'IAB2']); + }); + + it('bcat: pass strings with length greater than 3', function() { + multipleBidRequests[0].params.bcat = ['AB', 'CD', 'IAB1', 'IAB2']; + let request = spec.buildRequests(multipleBidRequests); + let data = JSON.parse(request.data); + expect(data.bcat).to.exist.and.to.deep.equal(['IAB1', 'IAB2']); + }); + + it('bcat: trim the strings', function() { + multipleBidRequests[0].params.bcat = [' IAB1 ', ' IAB2 ']; + let request = spec.buildRequests(multipleBidRequests); + let data = JSON.parse(request.data); + expect(data.bcat).to.exist.and.to.deep.equal(['IAB1', 'IAB2']); + }); + + it('bcat: pass only unique strings', function() { + // multi slot + multipleBidRequests[0].params.bcat = ['IAB1', 'IAB2', 'IAB1', 'IAB2', 'IAB1', 'IAB2']; + multipleBidRequests[1].params.bcat = ['IAB1', 'IAB2', 'IAB1', 'IAB2', 'IAB1', 'IAB3']; + let request = spec.buildRequests(multipleBidRequests); + let data = JSON.parse(request.data); + expect(data.bcat).to.exist.and.to.deep.equal(['IAB1', 'IAB2', 'IAB3']); + }); + + it('bcat: do not pass bcat if all entries are invalid', function() { + // multi slot + multipleBidRequests[0].params.bcat = ['', 'IAB', 'IAB']; + multipleBidRequests[1].params.bcat = [' ', 22, 99999, 'IA']; + let request = spec.buildRequests(multipleBidRequests); + let data = JSON.parse(request.data); + expect(data.bcat).to.deep.equal(undefined); + }); + }); + describe('Response checking', function () { it('should check for valid response values', function () { let request = spec.buildRequests(bidRequests); + 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); @@ -650,7 +2328,10 @@ describe('PubMatic adapter', function () { expect(response[0].currency).to.equal('USD'); expect(response[0].netRevenue).to.equal(false); expect(response[0].ttl).to.equal(300); - expect(response[0].referrer).to.include(utils.getTopWindowUrl()); + expect(response[0].meta.networkId).to.equal(123); + expect(response[0].meta.buyerId).to.equal(976); + expect(response[0].meta.clickUrl).to.equal('blackrock.com'); + expect(response[0].referrer).to.include(data.site.ref); expect(response[0].ad).to.equal(bidResponses.body.seatbid[0].bid[0].adm); expect(response[1].requestId).to.equal(bidResponses.body.seatbid[1].bid[0].impid); @@ -666,7 +2347,10 @@ describe('PubMatic adapter', function () { expect(response[1].currency).to.equal('USD'); expect(response[1].netRevenue).to.equal(false); expect(response[1].ttl).to.equal(300); - expect(response[1].referrer).to.include(utils.getTopWindowUrl()); + expect(response[1].meta.networkId).to.equal(422); + expect(response[1].meta.buyerId).to.equal(832); + expect(response[1].meta.clickUrl).to.equal('hivehome.com'); + expect(response[1].referrer).to.include(data.site.ref); expect(response[1].ad).to.equal(bidResponses.body.seatbid[1].bid[0].adm); }); @@ -688,6 +2372,45 @@ describe('PubMatic adapter', function () { expect(response).to.be.an('array').with.length.above(0); expect(response[0].dealChannel).to.equal(null); }); + + it('should have a valid native bid response', function() { + let request = spec.buildRequests(nativeBidRequests); + let data = JSON.parse(request.data); + data.imp[0].id = '2a5571261281d4'; + request.data = JSON.stringify(data); + let response = spec.interpretResponse(nativeBidResponse, request); + expect(response).to.be.an('array').with.length.above(0); + expect(response[0].native).to.exist.and.to.be.an('object'); + expect(response[0].mediaType).to.exist.and.to.equal('native'); + expect(response[0].native.title).to.exist.and.to.be.an('string'); + expect(response[0].native.image).to.exist.and.to.be.an('object'); + expect(response[0].native.image.url).to.exist.and.to.be.an('string'); + expect(response[0].native.image.height).to.exist; + expect(response[0].native.image.width).to.exist; + expect(response[0].native.sponsoredBy).to.exist.and.to.be.an('string'); + expect(response[0].native.clickUrl).to.exist.and.to.be.an('string'); + }); + + it('should check for valid banner mediaType in case of multiformat request', function() { + let request = spec.buildRequests(bidRequests); + let response = spec.interpretResponse(bannerBidResponse, request); + + expect(response[0].mediaType).to.equal('banner'); + }); + + it('should check for valid video mediaType in case of multiformat request', function() { + let request = spec.buildRequests(videoBidRequests); + let response = spec.interpretResponse(videoBidResponse, request); + + expect(response[0].mediaType).to.equal('video'); + }); + + it('should check for valid native mediaType in case of multiformat request', function() { + let request = spec.buildRequests(nativeBidRequests); + let response = spec.interpretResponse(nativeBidResponse, request); + + expect(response[0].mediaType).to.equal('native'); + }); }); }); }); diff --git a/test/spec/modules/pubnxBidAdapter_spec.js b/test/spec/modules/pubnxBidAdapter_spec.js new file mode 100644 index 00000000000..002922fa066 --- /dev/null +++ b/test/spec/modules/pubnxBidAdapter_spec.js @@ -0,0 +1,112 @@ +import { expect } from 'chai'; +import { spec } from 'modules/pubnxBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; + +const BASE_URI = '//hb.pubnxserv.com/vzhbidder/bid?'; + +describe('PubNXAdapter', function () { + const adapter = newBidder(spec); + + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function () { + let bid = { + 'bidder': 'pubnx', + 'params': { + 'placementId': 'PNX-HB-G396432V4809F3' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not passed', function () { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'placementId': 0 + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + let bidRequests = [ + { + 'bidder': 'pubnx', + 'params': { + 'placementId': '10433394' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + } + ]; + + it('sends bid request to ENDPOINT via POST', function () { + const request = spec.buildRequests(bidRequests)[0]; + expect(request.url).to.equal(BASE_URI); + expect(request.method).to.equal('POST'); + }); + }); + + describe('interpretResponse', function () { + let response = { + 'vzhPlacementId': 'PNX-HB-G396432V4809F3', + 'bid': '76021e56-adaf-4114-b68d-ccacd1b3e551_1', + 'adWidth': '300', + 'adHeight': '250', + 'cpm': '0.16312590000000002', + 'ad': '', + 'slotBidId': '44b3fcfd24aa93', + 'nurl': '', + 'statusText': 'Success' + }; + + it('should get correct bid response', function () { + let expectedResponse = [ + { + 'requestId': '44b3fcfd24aa93', + 'cpm': 0.16312590000000002, + 'width': 300, + 'height': 250, + 'netRevenue': true, + 'mediaType': 'banner', + 'currency': 'USD', + 'dealId': null, + 'creativeId': null, + 'ttl': 300, + 'ad': '' + } + ]; + let bidderRequest; + let result = spec.interpretResponse({body: response}); + expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); + expect(result[0].cpm).to.not.equal(null); + }); + + it('handles nobid responses', function () { + let response = { + 'vzhPlacementId': 'PNX-HB-G396432V4809F3', + 'slotBidId': 'f00412ac86b79', + 'statusText': 'NO_BIDS' + }; + let bidderRequest; + + let result = spec.interpretResponse({body: response}); + expect(result.length).to.equal(0); + }); + }); +}); diff --git a/test/spec/modules/pubwiseAnalyticsAdapter_spec.js b/test/spec/modules/pubwiseAnalyticsAdapter_spec.js index e7e31fccc43..b77788fff4a 100644 --- a/test/spec/modules/pubwiseAnalyticsAdapter_spec.js +++ b/test/spec/modules/pubwiseAnalyticsAdapter_spec.js @@ -1,6 +1,6 @@ import pubwiseAnalytics from 'modules/pubwiseAnalyticsAdapter'; let events = require('src/events'); -let adaptermanager = require('src/adaptermanager'); +let adapterManager = require('src/adapterManager').default; let constants = require('src/constants.json'); describe('PubWise Prebid Analytics', function () { @@ -27,12 +27,12 @@ describe('PubWise Prebid Analytics', function () { it('should catch all events', function () { sinon.spy(pubwiseAnalytics, 'track'); - adaptermanager.registerAnalyticsAdapter({ + adapterManager.registerAnalyticsAdapter({ code: 'pubwiseanalytics', adapter: pubwiseAnalytics }); - adaptermanager.enableAnalytics({ + adapterManager.enableAnalytics({ provider: 'pubwiseanalytics', options: { site: ['test-test-test-test'] diff --git a/test/spec/modules/pulsepointBidAdapter_spec.js b/test/spec/modules/pulsepointBidAdapter_spec.js index ebeedde7783..9ed6d3631f5 100644 --- a/test/spec/modules/pulsepointBidAdapter_spec.js +++ b/test/spec/modules/pulsepointBidAdapter_spec.js @@ -1,8 +1,7 @@ /* eslint dot-notation:0, quote-props:0 */ import {expect} from 'chai'; import {spec} from 'modules/pulsepointBidAdapter'; -import {getTopWindowLocation} from 'src/utils'; -import {newBidder} from 'src/adapters/bidderFactory'; +import {deepClone} from 'src/utils'; describe('PulsePoint Adapter Tests', function () { const slotConfigs = [{ @@ -48,18 +47,114 @@ describe('PulsePoint Adapter Tests', function () { } } }]; + const videoSlotConfig = [{ + placementCode: '/DfpAccount1/slotVideo', + bidId: 'bid12345', + params: { + cp: 'p10000', + ct: 't10000', + video: { + w: 400, + h: 300, + minduration: 5, + maxduration: 10, + startdelay: 0, + skip: 1, + minbitrate: 200, + protocols: [1, 2, 4] + } + } + }]; + const additionalParamsConfig = [{ + placementCode: '/DfpAccount1/slot1', + bidId: 'bid12345', + params: { + cp: 'p10000', + ct: 't10000', + cf: '1x1', + extra_key1: 'extra_val1', + extra_key2: 12345, + extra_key3: { + key1: 'val1', + key2: 23456, + }, + extra_key4: [1, 2, 3] + } + }]; + + const ortbParamsSlotConfig = [{ + placementCode: '/DfpAccount1/slot1', + bidId: 'bid12345', + params: { + cp: 'p10000', + ct: 't10000', + cf: '1x1', + bcat: ['IAB-1', 'IAB-20'], + battr: [1, 2, 3], + bidfloor: 1.5, + badv: ['cocacola.com', 'lays.com'] + } + }, { + placementCode: '/DfpAccount1/slotVideo', + bidId: 'bid12345', + params: { + cp: 'p10000', + ct: 't10000', + video: { + w: 400, + h: 300, + minduration: 5, + maxduration: 10, + }, + battr: [2, 3, 4], + bidfloor: 2.5, + } + }]; + + const outstreamSlotConfig = [{ + placementCode: '/DfpAccount1/slot1', + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'outstream' + } + }, + bidId: 'bid12345', + params: { + cp: 'p10000', + ct: 't10000', + cf: '1x1', + video: { + h: 300, + w: 400, + minduration: 1, + maxduration: 210, + linearity: 1, + } + }, + renderer: { + options: { + text: 'PulsePoint Outstream' + } + } + }]; + const bidderRequest = { + refererInfo: { + referer: 'https://publisher.com/home' + } + }; it('Verify build request', function () { - const request = spec.buildRequests(slotConfigs); - expect(request.url).to.equal('//bid.contextweb.com/header/ortb'); + const request = spec.buildRequests(slotConfigs, bidderRequest); + expect(request.url).to.equal('https://bid.contextweb.com/header/ortb?src=prebid'); expect(request.method).to.equal('POST'); - const ortbRequest = JSON.parse(request.data); + const ortbRequest = request.data; // site object expect(ortbRequest.site).to.not.equal(null); expect(ortbRequest.site.publisher).to.not.equal(null); expect(ortbRequest.site.publisher.id).to.equal('p10000'); expect(ortbRequest.site.ref).to.equal(window.top.document.referrer); - expect(ortbRequest.site.page).to.equal(getTopWindowLocation().href); + expect(ortbRequest.site.page).to.equal('https://publisher.com/home'); expect(ortbRequest.imp).to.have.lengthOf(2); // device object expect(ortbRequest.device).to.not.equal(null); @@ -77,14 +172,15 @@ describe('PulsePoint Adapter Tests', function () { }); it('Verify parse response', function () { - const request = spec.buildRequests(slotConfigs); - const ortbRequest = JSON.parse(request.data); + const request = spec.buildRequests(slotConfigs, bidderRequest); + const ortbRequest = request.data; const ortbResponse = { seatbid: [{ bid: [{ impid: ortbRequest.imp[0].id, price: 1.25, - adm: 'This is an Ad' + adm: 'This is an Ad', + crid: 'Creative#123' }] }] }; @@ -97,16 +193,16 @@ describe('PulsePoint Adapter Tests', function () { expect(bid.width).to.equal(300); expect(bid.height).to.equal(250); expect(bid.adId).to.equal('bid12345'); - expect(bid.creative_id).to.equal('bid12345'); - expect(bid.creativeId).to.equal('bid12345'); + expect(bid.creative_id).to.equal('Creative#123'); + expect(bid.creativeId).to.equal('Creative#123'); expect(bid.netRevenue).to.equal(true); expect(bid.currency).to.equal('USD'); expect(bid.ttl).to.equal(20); }); it('Verify use ttl in ext', function () { - const request = spec.buildRequests(slotConfigs); - const ortbRequest = JSON.parse(request.data); + const request = spec.buildRequests(slotConfigs, bidderRequest); + const ortbRequest = request.data; const ortbResponse = { seatbid: [{ bid: [{ @@ -131,16 +227,16 @@ describe('PulsePoint Adapter Tests', function () { }); it('Verify full passback', function () { - const request = spec.buildRequests(slotConfigs); + const request = spec.buildRequests(slotConfigs, bidderRequest); const bids = spec.interpretResponse({ body: null }, request) expect(bids).to.have.lengthOf(0); }); it('Verify Native request', function () { - const request = spec.buildRequests(nativeSlotConfig); - expect(request.url).to.equal('//bid.contextweb.com/header/ortb'); + const request = spec.buildRequests(nativeSlotConfig, bidderRequest); + expect(request.url).to.equal('https://bid.contextweb.com/header/ortb?src=prebid'); expect(request.method).to.equal('POST'); - const ortbRequest = JSON.parse(request.data); + const ortbRequest = request.data; // native impression expect(ortbRequest.imp[0].tagid).to.equal('t10000'); expect(ortbRequest.imp[0].banner).to.equal(null); @@ -175,10 +271,10 @@ describe('PulsePoint Adapter Tests', function () { }); it('Verify Native response', function () { - const request = spec.buildRequests(nativeSlotConfig); - expect(request.url).to.equal('//bid.contextweb.com/header/ortb'); + const request = spec.buildRequests(nativeSlotConfig, bidderRequest); + expect(request.url).to.equal('https://bid.contextweb.com/header/ortb?src=prebid'); expect(request.method).to.equal('POST'); - const ortbRequest = JSON.parse(request.data); + const ortbRequest = request.data; const nativeResponse = { 'native': { assets: [ @@ -228,8 +324,10 @@ describe('PulsePoint Adapter Tests', function () { }); it('Verifies supported media types', function () { - expect(spec.supportedMediaTypes).to.have.lengthOf(2); + expect(spec.supportedMediaTypes).to.have.lengthOf(3); + expect(spec.supportedMediaTypes[0]).to.equal('banner'); expect(spec.supportedMediaTypes[1]).to.equal('native'); + expect(spec.supportedMediaTypes[2]).to.equal('video'); }); it('Verifies if bid request valid', function () { @@ -250,7 +348,7 @@ describe('PulsePoint Adapter Tests', function () { expect(options).to.not.be.undefined; expect(options).to.have.lengthOf(1); expect(options[0].type).to.equal('iframe'); - expect(options[0].url).to.equal('//bh.contextweb.com/visitormatch'); + expect(options[0].url).to.equal('https://bh.contextweb.com/visitormatch'); }); it('Verifies image pixel sync', function () { @@ -258,12 +356,12 @@ describe('PulsePoint Adapter Tests', function () { expect(options).to.not.be.undefined; expect(options).to.have.lengthOf(1); expect(options[0].type).to.equal('image'); - expect(options[0].url).to.equal('//bh.contextweb.com/visitormatch/prebid'); + expect(options[0].url).to.equal('https://bh.contextweb.com/visitormatch/prebid'); }); it('Verify app requests', function () { - const request = spec.buildRequests(appSlotConfig); - const ortbRequest = JSON.parse(request.data); + const request = spec.buildRequests(appSlotConfig, bidderRequest); + const ortbRequest = request.data; // site object expect(ortbRequest.site).to.equal(null); expect(ortbRequest.app).to.not.be.null; @@ -275,16 +373,16 @@ describe('PulsePoint Adapter Tests', function () { }); it('Verify GDPR', function () { - const bidderRequest = { + const bidderRequestGdpr = { gdprConsent: { gdprApplies: true, consentString: 'serialized_gpdr_data' } }; - const request = spec.buildRequests(slotConfigs, bidderRequest); - expect(request.url).to.equal('//bid.contextweb.com/header/ortb'); + const request = spec.buildRequests(slotConfigs, Object.assign({}, bidderRequest, bidderRequestGdpr)); + expect(request.url).to.equal('https://bid.contextweb.com/header/ortb?src=prebid'); expect(request.method).to.equal('POST'); - const ortbRequest = JSON.parse(request.data); + const ortbRequest = request.data; // user object expect(ortbRequest.user).to.not.equal(null); expect(ortbRequest.user.ext).to.not.equal(null); @@ -294,4 +392,158 @@ describe('PulsePoint Adapter Tests', function () { expect(ortbRequest.regs.ext).to.not.equal(null); expect(ortbRequest.regs.ext.gdpr).to.equal(1); }); + + it('Verify Video request', function () { + const request = spec.buildRequests(videoSlotConfig, bidderRequest); + expect(request.url).to.equal('https://bid.contextweb.com/header/ortb?src=prebid'); + expect(request.method).to.equal('POST'); + const ortbRequest = request.data; + expect(ortbRequest).to.not.equal(null); + expect(ortbRequest.imp).to.have.lengthOf(1); + expect(ortbRequest.imp[0].video).to.not.be.null; + expect(ortbRequest.imp[0].native).to.be.null; + expect(ortbRequest.imp[0].banner).to.be.null; + expect(ortbRequest.imp[0].video.w).to.equal(400); + expect(ortbRequest.imp[0].video.h).to.equal(300); + expect(ortbRequest.imp[0].video.minduration).to.equal(5); + expect(ortbRequest.imp[0].video.maxduration).to.equal(10); + expect(ortbRequest.imp[0].video.startdelay).to.equal(0); + expect(ortbRequest.imp[0].video.skip).to.equal(1); + expect(ortbRequest.imp[0].video.minbitrate).to.equal(200); + expect(ortbRequest.imp[0].video.protocols).to.eql([1, 2, 4]); + }); + + it('Verify Video response', function () { + const request = spec.buildRequests(videoSlotConfig, bidderRequest); + expect(request.url).to.equal('https://bid.contextweb.com/header/ortb?src=prebid'); + expect(request.method).to.equal('POST'); + const ortbRequest = request.data; + const ortbResponse = { + seatbid: [{ + bid: [{ + impid: ortbRequest.imp[0].id, + price: 1.25, + adm: 'http://pulsepoint.video.mp4' + }] + }] + }; + const bids = spec.interpretResponse({ body: ortbResponse }, request); + const bid = bids[0]; + expect(bid.cpm).to.equal(1.25); + expect(bid.adId).to.equal('bid12345'); + expect(bid.ad).to.be.undefined; + expect(bid['native']).to.be.undefined; + expect(bid.mediaType).to.equal('video'); + expect(bid.vastXml).to.equal(ortbResponse.seatbid[0].bid[0].adm); + }); + + it('Verify extra parameters', function () { + let request = spec.buildRequests(additionalParamsConfig, bidderRequest); + let ortbRequest = request.data; + expect(ortbRequest).to.not.equal(null); + expect(ortbRequest.imp).to.have.lengthOf(1); + expect(ortbRequest.imp[0].ext).to.not.equal(null); + expect(ortbRequest.imp[0].ext.prebid).to.not.equal(null); + expect(ortbRequest.imp[0].ext.prebid).to.not.be.null; + expect(ortbRequest.imp[0].ext.prebid.extra_key1).to.equal('extra_val1'); + expect(ortbRequest.imp[0].ext.prebid.extra_key2).to.equal(12345); + expect(ortbRequest.imp[0].ext.prebid.extra_key3).to.not.be.null; + expect(ortbRequest.imp[0].ext.prebid.extra_key3.key1).to.equal('val1'); + expect(ortbRequest.imp[0].ext.prebid.extra_key3.key2).to.equal(23456); + expect(ortbRequest.imp[0].ext.prebid.extra_key4).to.eql([1, 2, 3]); + expect(Object.keys(ortbRequest.imp[0].ext.prebid)).to.eql(['extra_key1', 'extra_key2', 'extra_key3', 'extra_key4']); + // attempting with a configuration with no unknown params. + request = spec.buildRequests(outstreamSlotConfig, bidderRequest); + ortbRequest = request.data; + expect(ortbRequest).to.not.equal(null); + expect(ortbRequest.imp).to.have.lengthOf(1); + expect(ortbRequest.imp[0].ext).to.equal(null); + }); + + it('Verify ortb parameters', function () { + const request = spec.buildRequests(ortbParamsSlotConfig, bidderRequest); + const ortbRequest = request.data; + expect(ortbRequest).to.not.equal(null); + expect(ortbRequest.bcat).to.eql(['IAB-1', 'IAB-20']); + expect(ortbRequest.badv).to.eql(['cocacola.com', 'lays.com']); + expect(ortbRequest.imp).to.have.lengthOf(2); + expect(ortbRequest.imp[0].bidfloor).to.equal(1.5); + expect(ortbRequest.imp[0].banner.battr).to.eql([1, 2, 3]); + expect(ortbRequest.imp[0].ext).to.be.null; + // slot 2 + expect(ortbRequest.imp[1].bidfloor).to.equal(2.5); + expect(ortbRequest.imp[1].video.battr).to.eql([2, 3, 4]); + expect(ortbRequest.imp[1].ext).to.be.null; + }); + + it('Verify outstream renderer', function () { + const bidderRequestOutstream = Object.assign({}, bidderRequest, {bids: [outstreamSlotConfig[0]]}); + const request = spec.buildRequests(outstreamSlotConfig, bidderRequestOutstream); + const ortbRequest = request.data; + expect(ortbRequest).to.not.be.null; + expect(ortbRequest.imp[0]).to.not.be.null; + expect(ortbRequest.imp[0].video).to.not.be.null; + const ortbResponse = { + seatbid: [{ + bid: [{ + impid: ortbRequest.imp[0].id, + price: 1.25, + adm: 'http://pulsepoint.video.mp4', + ext: { + outstream: { + type: 'Inline', + config: { + text: 'ADVERTISEMENT', + skipaftersec: 5 + }, + rendererUrl: 'http://tag.contextweb.com/hb-outstr-renderer.js' + } + } + }] + }] + }; + const bids = spec.interpretResponse({ body: ortbResponse }, request); + const bid = bids[0]; + expect(bid.cpm).to.equal(1.25); + expect(bid.renderer).to.not.be.null; + expect(bid.renderer.url).to.equal('http://tag.contextweb.com/hb-outstr-renderer.js'); + expect(bid.renderer.getConfig()).to.not.be.null; + expect(bid.renderer.getConfig().defaultOptions).to.eql(ortbResponse.seatbid[0].bid[0].ext.outstream.config); + expect(bid.renderer.getConfig().rendererOptions).to.eql(outstreamSlotConfig[0].renderer.options); + expect(bid.renderer.getConfig().type).to.equal('Inline'); + }); + it('Verify common id parameters', function () { + const bidRequests = deepClone(slotConfigs); + bidRequests[0].userId = { + pubcid: 'userid_pubcid', + tdid: 'userid_ttd', + digitrustid: { + data: { + id: 'userid_digitrust', + keyv: 4, + privacy: {optout: false}, + producer: 'ABC', + version: 2 + } + } + }; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request).to.be.not.null; + const ortbRequest = request.data; + expect(request.data).to.be.not.null; + // user object + expect(ortbRequest.user).to.not.be.undefined; + expect(ortbRequest.user.ext).to.not.be.undefined; + expect(ortbRequest.user.ext.eids).to.not.be.undefined; + expect(ortbRequest.user.ext.eids).to.have.lengthOf(3); + expect(ortbRequest.user.ext.eids[0].source).to.equal('pubcommon'); + expect(ortbRequest.user.ext.eids[0].uids).to.have.lengthOf(1); + expect(ortbRequest.user.ext.eids[0].uids[0].id).to.equal('userid_pubcid'); + expect(ortbRequest.user.ext.eids[1].source).to.equal('ttdid'); + expect(ortbRequest.user.ext.eids[1].uids).to.have.lengthOf(1); + expect(ortbRequest.user.ext.eids[1].uids[0].id).to.equal('userid_ttd'); + expect(ortbRequest.user.ext.eids[2].source).to.equal('digitrust'); + expect(ortbRequest.user.ext.eids[2].uids).to.have.lengthOf(1); + expect(ortbRequest.user.ext.eids[2].uids[0].id).to.equal('userid_digitrust'); + }); }); diff --git a/test/spec/modules/quantcastBidAdapter_spec.js b/test/spec/modules/quantcastBidAdapter_spec.js index f5a7602c7ab..a4a5515c6ae 100644 --- a/test/spec/modules/quantcastBidAdapter_spec.js +++ b/test/spec/modules/quantcastBidAdapter_spec.js @@ -1,4 +1,3 @@ -import * as utils from 'src/utils'; import { expect } from 'chai'; import { QUANTCAST_DOMAIN, @@ -16,6 +15,7 @@ import { parse } from 'src/url'; describe('Quantcast adapter', function () { const quantcastAdapter = newBidder(qcSpec); let bidRequest; + let bidderRequest; beforeEach(function () { bidRequest = { @@ -30,8 +30,29 @@ describe('Quantcast adapter', function () { }, sizes: [[300, 250]] }; + + bidderRequest = { + refererInfo: { + referer: 'http://example.com/hello.html', + canonicalUrl: 'http://example.com/hello.html' + } + }; }); + function setupVideoBidRequest(videoParams) { + bidRequest.params = { + publisherId: 'test-publisher', // REQUIRED - Publisher ID provided by Quantcast + // Video object as specified in OpenRTB 2.5 + video: videoParams + }; + bidRequest['mediaTypes'] = { + video: { + context: 'instream', + playerSize: [600, 300] + } + } + }; + describe('inherited functions', function () { it('exists and is a function', function () { expect(quantcastAdapter.callBids).to.exist.and.to.be.a('function'); @@ -39,26 +60,31 @@ describe('Quantcast adapter', function () { }); describe('`isBidRequestValid`', function () { - it('should return `false` when bid is not passed', function () { - expect(qcSpec.isBidRequestValid()).to.equal(false); - }); - - it('should return `false` when bid `mediaType` is `video`', function () { - const bidRequest = { mediaType: 'video' }; + it('should return `true` when bid has publisherId', function () { + const bidRequest = { + bidder: 'quantcast', + params: { + publisherId: 'my_publisher_id' + } + }; - expect(qcSpec.isBidRequestValid(bidRequest)).to.equal(false); + expect(qcSpec.isBidRequestValid(bidRequest)).to.equal(true); }); - it('should return `true` when bid contains required params', function () { - const bidRequest = { mediaType: 'banner' }; + it('should return `false` when bid has no publisherId', function () { + const bidRequest = { + bidder: 'quantcast', + params: { + } + }; - expect(qcSpec.isBidRequestValid(bidRequest)).to.equal(true); + expect(qcSpec.isBidRequestValid(bidRequest)).to.equal(false); }); }); describe('`buildRequests`', function () { it('selects protocol and port', function () { - switch (window.location.protocol) { + switch (window.originalLocation.protocol) { case 'https:': expect(QUANTCAST_PROTOCOL).to.equal('https'); expect(QUANTCAST_PORT).to.equal('8443'); @@ -94,13 +120,9 @@ describe('Quantcast adapter', function () { expect(requests[0].method).to.equal('POST'); }); - it('sends bid requests contains all the required parameters', function () { - const referrer = utils.getTopWindowUrl(); - const loc = utils.getTopWindowLocation(); - const domain = loc.hostname; - - const requests = qcSpec.buildRequests([bidRequest]); - const expectedBidRequest = { + it('sends banner bid requests contains all the required parameters', function () { + const requests = qcSpec.buildRequests([bidRequest], bidderRequest); + const expectedBannerBidRequest = { publisherId: QUANTCAST_TEST_PUBLISHER, requestId: '2f7b179d443f14', imp: [ @@ -114,12 +136,195 @@ describe('Quantcast adapter', function () { } ], site: { - page: loc.href, - referrer, - domain + page: 'http://example.com/hello.html', + referrer: 'http://example.com/hello.html', + domain: 'example.com' + }, + bidId: '2f7b179d443f14', + gdprSignal: 0, + prebidJsVersion: '$prebid.version$' + }; + + expect(requests[0].data).to.equal(JSON.stringify(expectedBannerBidRequest)); + }); + + it('sends video bid requests containing all the required parameters', function () { + setupVideoBidRequest({ + mimes: ['video/mp4'], // required + minduration: 3, // optional + maxduration: 5, // optional + protocols: [3], // optional + startdelay: 1, // optional + linearity: 1, // optinal + battr: [1, 2], // optional + maxbitrate: 10, // optional + playbackmethod: [1], // optional + delivery: [1], // optional + placement: 1, // optional + api: [2, 3] // optional + }); + + const requests = qcSpec.buildRequests([bidRequest], bidderRequest); + const expectedVideoBidRequest = { + publisherId: QUANTCAST_TEST_PUBLISHER, + requestId: '2f7b179d443f14', + imp: [ + { + video: { + mimes: ['video/mp4'], + minduration: 3, + maxduration: 5, + protocols: [3], + startdelay: 1, + linearity: 1, + battr: [1, 2], + maxbitrate: 10, + playbackmethod: [1], + delivery: [1], + placement: 1, + api: [2, 3], + w: 600, + h: 300 + }, + placementCode: 'div-gpt-ad-1438287399331-0', + bidFloor: 1e-10 + } + ], + site: { + page: 'http://example.com/hello.html', + referrer: 'http://example.com/hello.html', + domain: 'example.com' + }, + bidId: '2f7b179d443f14', + gdprSignal: 0, + prebidJsVersion: '$prebid.version$' + }; + + expect(requests[0].data).to.equal(JSON.stringify(expectedVideoBidRequest)); + }); + + it('overrides video parameters with parameters from adunit', function() { + setupVideoBidRequest({ + mimes: ['video/mp4'] + }); + bidRequest.mediaTypes.video.mimes = ['video/webm']; + + const requests = qcSpec.buildRequests([bidRequest], bidderRequest); + const expectedVideoBidRequest = { + publisherId: QUANTCAST_TEST_PUBLISHER, + requestId: '2f7b179d443f14', + imp: [ + { + video: { + mimes: ['video/webm'], + w: 600, + h: 300 + }, + placementCode: 'div-gpt-ad-1438287399331-0', + bidFloor: 1e-10 + } + ], + site: { + page: 'http://example.com/hello.html', + referrer: 'http://example.com/hello.html', + domain: 'example.com' + }, + bidId: '2f7b179d443f14', + gdprSignal: 0, + prebidJsVersion: '$prebid.version$' + }; + + expect(requests[0].data).to.equal(JSON.stringify(expectedVideoBidRequest)); + }); + + it('sends video bid request when no video parameters are given', function () { + setupVideoBidRequest(null); + + const requests = qcSpec.buildRequests([bidRequest], bidderRequest); + const expectedVideoBidRequest = { + publisherId: QUANTCAST_TEST_PUBLISHER, + requestId: '2f7b179d443f14', + imp: [ + { + video: { + w: 600, + h: 300 + }, + placementCode: 'div-gpt-ad-1438287399331-0', + bidFloor: 1e-10 + } + ], + site: { + page: 'http://example.com/hello.html', + referrer: 'http://example.com/hello.html', + domain: 'example.com' }, bidId: '2f7b179d443f14', - gdprSignal: 0 + gdprSignal: 0, + prebidJsVersion: '$prebid.version$' + }; + + expect(requests[0].data).to.equal(JSON.stringify(expectedVideoBidRequest)); + }); + + it('ignores unsupported video bid requests', function () { + bidRequest.mediaTypes = { + video: { + context: 'outstream', + playerSize: [[550, 310]] + } + }; + + const requests = qcSpec.buildRequests([bidRequest], bidderRequest); + + expect(requests).to.be.empty; + }); + + it('parses multi-format bid request', function () { + bidRequest.mediaTypes = { + banner: {sizes: [[300, 250], [728, 90], [250, 250], [468, 60], [320, 50]]}, + native: { + image: {required: true, sizes: [150, 50]}, + title: {required: true, len: 80}, + sponsoredBy: {required: true}, + clickUrl: {required: true}, + privacyLink: {required: false}, + body: {required: true}, + icon: {required: true, sizes: [50, 50]} + }, + video: { + context: 'outstream', + playerSize: [[550, 310]] + } + }; + bidRequest.sizes = [[300, 250], [728, 90], [250, 250], [468, 60], [320, 50]]; + + const requests = qcSpec.buildRequests([bidRequest], bidderRequest); + const expectedBidRequest = { + publisherId: QUANTCAST_TEST_PUBLISHER, + requestId: '2f7b179d443f14', + imp: [{ + banner: { + battr: [1, 2], + sizes: [ + {width: 300, height: 250}, + {width: 728, height: 90}, + {width: 250, height: 250}, + {width: 468, height: 60}, + {width: 320, height: 50} + ] + }, + placementCode: 'div-gpt-ad-1438287399331-0', + bidFloor: 1e-10 + }], + site: { + page: 'http://example.com/hello.html', + referrer: 'http://example.com/hello.html', + domain: 'example.com' + }, + bidId: '2f7b179d443f14', + gdprSignal: 0, + prebidJsVersion: '$prebid.version$' }; expect(requests[0].data).to.equal(JSON.stringify(expectedBidRequest)); @@ -159,6 +364,27 @@ describe('Quantcast adapter', function () { headers: {} }; + const videoBody = { + bidderCode: 'qcx', + requestId: 'erlangcluster@qa-rtb002.us-ec.adtech.com-11417780270886458', + bids: [ + { + statusCode: 1, + placementCode: 'video1', + cpm: 4.5, + currency: 'USD', + videoUrl: 'https://vast.quantserve.com/vast?p=&r=&gdpr=&gdpr_consent=&rand=1337&d=H4sIAAAAAAAAAONi4mIQcrzFqGLi5OzibOzmpGtm4eyia-LoaqDraGRupOtobGJhYuni6GRiYLmLiYWrp5f_BBPDDybGScxcPs7-aRYmpmVVoVJgCSXBkozMYl0gKslI1S1Izk9JBQALkFy_YAAAAA&h=uRnsTjyXbOrXJtBQiaMn239i9GI', + width: 600, + height: 300 + } + ] + }; + + const videoResponse = { + body: videoBody, + headers: {} + }; + it('should return an empty array if `serverResponse` is `undefined`', function () { const interpretedResponse = qcSpec.interpretResponse(); @@ -195,6 +421,45 @@ describe('Quantcast adapter', function () { expect(interpretedResponse[0]).to.deep.equal(expectedResponse); }); + it('should include dealId in bid response', function () { + response.body.bids[0].dealId = 'test-dealid'; + const expectedResponse = { + requestId: 'erlangcluster@qa-rtb002.us-ec.adtech.com-11417780270886458', + cpm: 4.5, + width: 300, + height: 250, + ad: + '
    Quantcast
    ', + ttl: QUANTCAST_TTL, + creativeId: 1001, + netRevenue: QUANTCAST_NET_REVENUE, + currency: 'USD', + dealId: 'test-dealid' + }; + const interpretedResponse = qcSpec.interpretResponse(response); + + expect(interpretedResponse[0]).to.deep.equal(expectedResponse); + }); + + it('should get correct bid response for instream video', function() { + const expectedResponse = { + requestId: 'erlangcluster@qa-rtb002.us-ec.adtech.com-11417780270886458', + cpm: 4.5, + width: 600, + height: 300, + vastUrl: 'https://vast.quantserve.com/vast?p=&r=&gdpr=&gdpr_consent=&rand=1337&d=H4sIAAAAAAAAAONi4mIQcrzFqGLi5OzibOzmpGtm4eyia-LoaqDraGRupOtobGJhYuni6GRiYLmLiYWrp5f_BBPDDybGScxcPs7-aRYmpmVVoVJgCSXBkozMYl0gKslI1S1Izk9JBQALkFy_YAAAAA&h=uRnsTjyXbOrXJtBQiaMn239i9GI', + mediaType: 'video', + ttl: QUANTCAST_TTL, + creativeId: undefined, + ad: undefined, + netRevenue: QUANTCAST_NET_REVENUE, + currency: 'USD' + }; + const interpretedResponse = qcSpec.interpretResponse(videoResponse); + + expect(interpretedResponse[0]).to.deep.equal(expectedResponse); + }); + it('handles no bid response', function () { const body = { bidderCode: 'qcx', // Renaming it to use CamelCase since that is what is used in the Prebid.js variable name @@ -205,7 +470,6 @@ describe('Quantcast adapter', function () { body, headers: {} }; - const expectedResponse = []; const interpretedResponse = qcSpec.interpretResponse(response); expect(interpretedResponse.length).to.equal(0); diff --git a/test/spec/modules/radsBidAdapter_spec.js b/test/spec/modules/radsBidAdapter_spec.js new file mode 100644 index 00000000000..6981955f261 --- /dev/null +++ b/test/spec/modules/radsBidAdapter_spec.js @@ -0,0 +1,206 @@ +import { expect } from 'chai'; +import { spec } from 'modules/radsBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; + +const RADS_ENDPOINT_URL = 'https://rads.recognified.net/md.request.php'; + +describe('radsAdapter', function () { + const adapter = newBidder(spec); + + describe('isBidRequestValid', function () { + let bid = { + 'bidder': 'rads', + 'params': { + 'placement': '6682', + 'pfilter': { + 'floorprice': 1000000 + }, + 'bcat': 'IAB2,IAB4', + 'dvt': 'desktop', + 'ip': '1.1.1.1' + }, + 'sizes': [ + [300, 250] + ], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475' + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not passed', function () { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'someIncorrectParam': 0 + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + let bidRequests = [{ + 'bidder': 'rads', + 'params': { + 'placement': '6682', + 'pfilter': { + 'floorprice': 1000000, + 'geo': { + 'country': 'DE' + } + }, + 'bcat': 'IAB2,IAB4', + 'dvt': 'desktop', + 'ip': '1.1.1.1' + }, + 'sizes': [ + [300, 250] + ], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475' + }, { + 'bidder': 'rads', + 'params': { + 'placement': '6682', + 'pfilter': { + 'floorprice': 1000000, + 'geo': { + 'country': 'DE', + 'region': 'DE-BE' + }, + }, + 'bcat': 'IAB2,IAB4', + 'dvt': 'desktop' + }, + 'mediaTypes': { + 'video': { + 'playerSize': [640, 480], + 'context': 'instream' + } + }, + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475' + }]; + + let bidderRequest = { + refererInfo: { + referer: 'some_referrer.net' + } + } + + const request = spec.buildRequests(bidRequests, bidderRequest); + it('sends bid request to our endpoint via GET', function () { + expect(request[0].method).to.equal('GET'); + let data = request[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&bcat=IAB2%2CIAB4&dvt=desktop&i=1.1.1.1'); + }); + + it('sends bid video request to our rads endpoint via GET', function () { + expect(request[1].method).to.equal('GET'); + 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'); + }); + }); + + describe('interpretResponse', function () { + let serverBannerResponse = { + 'body': { + 'cpm': 5000000, + 'crid': 100500, + 'width': '300', + 'height': '250', + 'adTag': '', + 'requestId': '220ed41385952a', + 'currency': 'EUR', + 'ttl': 60, + 'netRevenue': true, + 'zone': '6682' + } + }; + let serverVideoResponse = { + 'body': { + 'cpm': 5000000, + 'crid': 100500, + 'width': '300', + 'height': '250', + 'vastXml': '{"reason":7001,"status":"accepted"}', + 'requestId': '220ed41385952a', + 'currency': 'EUR', + 'ttl': 60, + 'netRevenue': true, + 'zone': '6682' + } + }; + + let expectedResponse = [{ + requestId: '23beaa6af6cdde', + cpm: 0.5, + width: 0, + height: 0, + creativeId: 100500, + dealId: '', + currency: 'EUR', + netRevenue: true, + ttl: 300, + ad: '' + }, { + requestId: '23beaa6af6cdde', + cpm: 0.5, + width: 0, + height: 0, + creativeId: 100500, + dealId: '', + currency: 'EUR', + netRevenue: true, + ttl: 300, + vastXml: '{"reason":7001,"status":"accepted"}', + mediaType: 'video' + }]; + + it('should get the correct bid response by display ad', function () { + let bidRequest = [{ + 'method': 'GET', + 'url': RADS_ENDPOINT_URL, + 'refererInfo': { + 'referer': '' + }, + 'data': { + 'bid_id': '30b31c1838de1e' + } + }]; + let result = spec.interpretResponse(serverBannerResponse, bidRequest[0]); + expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); + }); + + it('should get the correct rads video bid response by display ad', function () { + let bidRequest = [{ + 'method': 'GET', + 'url': RADS_ENDPOINT_URL, + 'mediaTypes': { + 'video': { + 'playerSize': [640, 480], + 'context': 'instream' + } + }, + 'data': { + 'bid_id': '30b31c1838de1e' + } + }]; + let result = spec.interpretResponse(serverVideoResponse, bidRequest[0]); + expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[1])); + }); + + it('handles empty bid response', function () { + let response = { + body: {} + }; + let result = spec.interpretResponse(response); + expect(result.length).to.equal(0); + }); + }); +}); diff --git a/test/spec/modules/rdnBidAdapter_spec.js b/test/spec/modules/rdnBidAdapter_spec.js new file mode 100644 index 00000000000..8f53502bc44 --- /dev/null +++ b/test/spec/modules/rdnBidAdapter_spec.js @@ -0,0 +1,156 @@ +import { expect } from 'chai' +import * as utils from 'src/utils' +import { spec } from 'modules/rdnBidAdapter' +import { newBidder } from 'src/adapters/bidderFactory' +import {config} from '../../../src/config'; + +describe('rdnBidAdapter', function() { + const adapter = newBidder(spec); + const ENDPOINT = 'https://s-bid.rmp.rakuten.co.jp/h'; + let sandbox; + + beforeEach(function() { + config.resetConfig(); + }); + + afterEach(function () { + config.resetConfig(); + }); + + describe('inherited functions', () => { + it('exists and is a function', () => { + expect(adapter.callBids).to.exist.and.to.be.a('function') + }) + }); + + describe('isBidRequestValid', () => { + let bid = { + bidder: 'rdn', + params: { + adSpotId: '56789' + } + }; + + it('should return true when required params found', () => { + expect(spec.isBidRequestValid(bid)).to.equal(true) + }); + + it('should return false when required params are not passed', () => { + bid.params.adSpotId = ''; + expect(spec.isBidRequestValid(bid)).to.equal(false) + }); + + it('should return false when required params are not passed', () => { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false) + }) + }); + + describe('buildRequests', () => { + const bidRequests = [ + { + // banner + params: { + adSpotId: '58278' + } + } + ]; + + it('sends bid request to ENDPOINT via GET', () => { + const request = spec.buildRequests(bidRequests)[0]; + expect(request.url).to.equal(ENDPOINT); + expect(request.method).to.equal('GET') + }) + + it('allows url override', () => { + config.setConfig({ + rdn: { + endpoint: '//test.rakuten.com' + } + }); + const request = spec.buildRequests(bidRequests)[0]; + expect(request.url).to.equal('//test.rakuten.com'); + }) + }); + + describe('interpretResponse', () => { + const bidRequests = { + banner: { + method: 'GET', + url: '', + data: { + t: '56789', + s: 'https', + ua: + 'Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Mobile Safari/537.36', + l: 'ja', + d: 'examples.com', + tp: 'https://examples.com/foo/fuga', + pp: 'https://examples.com/hoge/muga' + } + } + }; + + const serverResponse = { + noAd: [], + noAd2: { + requestId: 'biequa9oaph4we' + }, + banner: { + requestId: 'biequa9oaph4we', + cpm: 37.66, + width: 300, + height: 250, + creativeId: 140281, + dealId: 'phoh3pad-ai4ah-xoh7x-ahk7cheasae3oh', + currency: 'JPY', + netRevenue: 300, + ttl: 3000, + referrer: utils.getTopWindowUrl(), + ad: '' + } + }; + + it('handles nobid responses', () => { + const result = spec.interpretResponse( + { body: serverResponse.noAd }, + bidRequests.banner + ); + expect(result.length).to.equal(0); + + const result2 = spec.interpretResponse( + { body: serverResponse.noAd2 }, + bidRequests.banner + ); + expect(result2.length).to.equal(0); + }) + }); + describe('spec.getUserSyncs', function () { + const syncResponse = [{ + body: { + request_id: 'biequa9oaph4we', + sync_urls: ['https://rdn1.test/sync?uid=9876543210', 'https://rdn2.test/sync?uid=9876543210'] + } + }]; + const nosyncResponse = [{ + body: { + request_id: 'biequa9oaph4we', + sync_urls: [] + } + }]; + let syncOptions + beforeEach(function () { + syncOptions = { + pixelEnabled: true + } + }); + it('sucess usersync url', function () { + const result = []; + result.push({type: 'image', url: 'https://rdn1.test/sync?uid=9876543210'}); + result.push({type: 'image', url: 'https://rdn2.test/sync?uid=9876543210'}); + expect(spec.getUserSyncs(syncOptions, syncResponse)).to.deep.equal(result); + }); + }); +}); diff --git a/test/spec/modules/readpeakBidAdapter_spec.js b/test/spec/modules/readpeakBidAdapter_spec.js index 572c2b73f8c..5fcdf9b5836 100644 --- a/test/spec/modules/readpeakBidAdapter_spec.js +++ b/test/spec/modules/readpeakBidAdapter_spec.js @@ -1,6 +1,7 @@ import { expect } from 'chai'; import { spec, ENDPOINT } from 'modules/readpeakBidAdapter'; import * as utils from 'src/utils'; +import {config} from 'src/config'; describe('ReadPeakAdapter', function () { let bidRequest @@ -149,6 +150,12 @@ describe('ReadPeakAdapter', function () { }); it('should attach request data', function () { + config.setConfig({ + currency: { + adServerCurrency: 'EUR' + } + }); + const request = spec.buildRequests([ bidRequest ]); const data = JSON.parse(request.data); @@ -167,7 +174,8 @@ describe('ReadPeakAdapter', function () { page: utils.getTopWindowLocation().href, domain: utils.getTopWindowLocation().hostname, }); - expect(data.device).to.deep.contain({ ua: navigator.userAgent }); + expect(data.device).to.deep.contain({ ua: navigator.userAgent, language: navigator.language }); + expect(data.cur).to.deep.equal(['EUR']); }); }); diff --git a/test/spec/modules/realvuAnalyticsAdapter_spec.js b/test/spec/modules/realvuAnalyticsAdapter_spec.js index 1d0fcf9be1a..359fb329359 100644 --- a/test/spec/modules/realvuAnalyticsAdapter_spec.js +++ b/test/spec/modules/realvuAnalyticsAdapter_spec.js @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import realvuAnalyticsAdapter from 'modules/realvuAnalyticsAdapter'; +import realvuAnalyticsAdapter, { lib } from 'modules/realvuAnalyticsAdapter'; import CONSTANTS from 'src/constants.json'; function addDiv(id) { @@ -22,142 +22,170 @@ function addDiv(id) { return dv; } -describe('RealVu Analytics Adapter.', function () { - before(function () { +describe('RealVu', function() { + let sandbox; + beforeEach(function () { + sandbox = sinon.sandbox.create(); addDiv('ad1'); addDiv('ad2'); + sandbox.stub(lib, 'scr'); }); - after(function () { + + afterEach(function () { let a1 = document.getElementById('ad1'); document.body.removeChild(a1); let a2 = document.getElementById('ad2'); document.body.removeChild(a2); + sandbox.restore(); + realvuAnalyticsAdapter.disableAnalytics(); }); - it('enableAnalytics', function () { - const config = { - options: { - partnerId: '1Y', - regAllUnits: true - // unitIds: ['ad1', 'ad2'] - } - }; - let p = realvuAnalyticsAdapter.enableAnalytics(config); - expect(p).to.equal('1Y'); + after(function () { + delete window.top1; + delete window.realvu_aa_fifo; + delete window.realvu_aa; + clearInterval(window.boost_poll); + delete window.boost_poll; }); - it('checkIn', function () { - const bid = { - adUnitCode: 'ad1', - sizes: [ - [728, 90], - [970, 250], - [970, 90] - ] - }; - let result = realvuAnalyticsAdapter.checkIn(bid, '1Y'); - const b = window.top1.realvu_aa; - let a = b.ads[0]; - // console.log('a: ' + a.x + ', ' + a.y + ', ' + a.w + ', ' + a.h); - // console.log('b: ' + b.x1 + ', ' + b.y1 + ', ' + b.x2 + ', ' + b.y2); - expect(result).to.equal('yes'); - - result = realvuAnalyticsAdapter.checkIn(bid); // test invalid partnerId 'undefined' - result = realvuAnalyticsAdapter.checkIn(bid, ''); // test invalid partnerId '' - }); + describe('Analytics Adapter.', function () { + it('enableAnalytics', function () { + const config = { + options: { + partnerId: '1Y', + regAllUnits: true + // unitIds: ['ad1', 'ad2'] + } + }; + let p = realvuAnalyticsAdapter.enableAnalytics(config); + expect(p).to.equal('1Y'); + }); - it.skip('isInView returns "yes"', () => { - let inview = realvuAnalyticsAdapter.isInView('ad1'); - expect(inview).to.equal('yes'); - }); + it('checkIn', function () { + const bid = { + adUnitCode: 'ad1', + sizes: [ + [728, 90], + [970, 250], + [970, 90] + ] + }; + let result = realvuAnalyticsAdapter.checkIn(bid, '1Y'); + const b = window.top1.realvu_aa; + let a = b.ads[0]; + // console.log('a: ' + a.x + ', ' + a.y + ', ' + a.w + ', ' + a.h); + // console.log('b: ' + b.x1 + ', ' + b.y1 + ', ' + b.x2 + ', ' + b.y2); + expect(result).to.equal('yes'); + + result = realvuAnalyticsAdapter.checkIn(bid); // test invalid partnerId 'undefined' + result = realvuAnalyticsAdapter.checkIn(bid, ''); // test invalid partnerId '' + }); - it('isInView return "NA"', function () { - const adUnitCode = '1234'; - let result = realvuAnalyticsAdapter.isInView(adUnitCode); - expect(result).to.equal('NA'); - }); + it.skip('isInView returns "yes"', () => { + let inview = realvuAnalyticsAdapter.isInView('ad1'); + expect(inview).to.equal('yes'); + }); - it('bid response event', function () { - const config = { - options: { - partnerId: '1Y', - regAllUnits: true - // unitIds: ['ad1', 'ad2'] - } - }; - realvuAnalyticsAdapter.enableAnalytics(config); - const args = { - 'biddercode': 'realvu', - 'adUnitCode': 'ad1', - 'width': 300, - 'height': 250, - 'statusMessage': 'Bid available', - 'adId': '7ba299eba818c1', - 'mediaType': 'banner', - 'creative_id': 85792851, - 'cpm': 0.4308 - }; - realvuAnalyticsAdapter.track({ - eventType: CONSTANTS.EVENTS.BID_RESPONSE, - args: args + it('isInView return "NA"', function () { + const adUnitCode = '1234'; + let result = realvuAnalyticsAdapter.isInView(adUnitCode); + expect(result).to.equal('NA'); }); - const boost = window.top1.realvu_aa; - expect(boost.ads[boost.len - 1].bids.length).to.equal(1); - realvuAnalyticsAdapter.track({ - eventType: CONSTANTS.EVENTS.BID_WON, - args: args + it('bid response event', function () { + const config = { + options: { + partnerId: '1Y', + regAllUnits: true + // unitIds: ['ad1', 'ad2'] + } + }; + realvuAnalyticsAdapter.enableAnalytics(config); + const args = { + 'biddercode': 'realvu', + 'adUnitCode': 'ad1', + 'width': 300, + 'height': 250, + 'statusMessage': 'Bid available', + 'adId': '7ba299eba818c1', + 'mediaType': 'banner', + 'creative_id': 85792851, + 'cpm': 0.4308 + }; + realvuAnalyticsAdapter.track({ + eventType: CONSTANTS.EVENTS.BID_RESPONSE, + args: args + }); + const boost = window.top1.realvu_aa; + expect(boost.ads[boost.len - 1].bids.length).to.equal(1); + + realvuAnalyticsAdapter.track({ + eventType: CONSTANTS.EVENTS.BID_WON, + args: args + }); + expect(boost.ads[boost.len - 1].bids[0].winner).to.equal(1); }); - expect(boost.ads[boost.len - 1].bids[0].winner).to.equal(1); }); -}); -describe('RealVu Boost.', function () { - before(function () { - addDiv('ad1'); - addDiv('ad2'); - }); - after(function () { - let a1 = document.getElementById('ad1'); - document.body.removeChild(a1); - let a2 = document.getElementById('ad2'); - document.body.removeChild(a2); - }); + describe('Boost.', function () { + const boost = window.top1.realvu_aa; - const boost = window.top1.realvu_aa; + it('brd', function () { + let a1 = document.getElementById('ad1'); + let p = boost.brd(a1, 'Left'); + expect(typeof p).to.not.equal('undefined'); + }); - it('brd', function () { - let a1 = document.getElementById('ad1'); - let p = boost.brd(a1, 'Left'); - expect(typeof p).to.not.equal('undefined'); - }); + it('addUnitById', function () { + let a1 = document.getElementById('ad1'); + let p = boost.addUnitById('1Y', 'ad1'); + expect(typeof p).to.not.equal('undefined'); + }); - it('addUnitById', function () { - let a1 = document.getElementById('ad1'); - let p = boost.addUnitById('1Y', 'ad1'); - expect(typeof p).to.not.equal('undefined'); - }); + it('questA', function () { + const dv = document.getElementById('ad1'); + let q = boost.questA(dv); + expect(q).to.not.equal(null); + }); - it('questA', function () { - const dv = document.getElementById('ad1'); - let q = boost.questA(dv); - expect(q).to.not.equal(null); - }); + it('render', function () { + let dv = document.getElementById('ad1'); + // dv.style.width = '728px'; + // dv.style.height = '90px'; + // dv.style.display = 'block'; + dv.getBoundingClientRect = false; + // document.body.appendChild(dv); + let q = boost.findPosG(dv); + expect(q).to.not.equal(null); + }); - it('render', function () { - let dv = document.getElementById('ad1'); - // dv.style.width = '728px'; - // dv.style.height = '90px'; - // dv.style.display = 'block'; - dv.getBoundingClientRect = false; - // document.body.appendChild(dv); - let q = boost.findPosG(dv); - expect(q).to.not.equal(null); - }); + it('readPos', function () { + const a = boost.ads[boost.len - 1]; + let r = boost.readPos(a); + expect(r).to.equal(true); + }); - it('readPos', function () { - const a = boost.ads[boost.len - 1]; - let r = boost.readPos(a); - expect(r).to.equal(true); + it('send_track', function () { + const a = boost.ads[boost.len - 1]; + boost.track(a, 'show', ''); + boost.sr = 'a'; + boost.send_track(); + expect(boost.beacons.length).to.equal(0); + }); + + it('questA text', function () { + let p = document.createElement('p'); + p.innerHTML = 'ABC'; + document.body.appendChild(p); + let r = boost.questA(p.firstChild); + document.body.removeChild(p); + expect(r).to.not.equal(null); + }); + + it('_f=conf', function () { + const a = boost.ads[boost.len - 1]; + let r = boost.tru(a, 'conf'); + expect(r).to.not.include('_ps='); + }); }); }); diff --git a/test/spec/modules/reklamstoreBidAdapter_spec.js b/test/spec/modules/reklamstoreBidAdapter_spec.js new file mode 100644 index 00000000000..3ac40e20eaf --- /dev/null +++ b/test/spec/modules/reklamstoreBidAdapter_spec.js @@ -0,0 +1,85 @@ +import { expect } from 'chai'; +import { spec } from 'modules/reklamstoreBidAdapter'; + +describe('reklamstoreBidAdapterTests', function() { + let bidRequestData = { + bids: [ + { + bidder: 'reklamstore', + params: { + regionId: 532211 + }, + sizes: [[300, 250]] + } + ] + }; + let request = []; + + it('validate_params', function() { + expect( + spec.isBidRequestValid({ + bidder: 'reklamstore', + params: { + regionId: 532211 + } + }) + ).to.equal(true); + }); + + it('validate_generated_params', function() { + let bidderRequest = { + refererInfo: { + referer: 'http://reklamstore.com' + } + }; + request = spec.buildRequests(bidRequestData.bids, bidderRequest); + let req_data = request[0].data; + + expect(req_data.regionId).to.equal(532211); + }); + + const serverResponse = { + body: + { + cpm: 1.2, + ad: 'Ad html', + w: 300, + h: 250, + syncs: [{ + type: 'image', + url: 'http://link1' + }, + { + type: 'iframe', + url: 'http://link2' + } + ] + } + }; + + it('validate_response_params', function() { + let bids = spec.interpretResponse(serverResponse, bidRequestData.bids[0]); + expect(bids).to.have.lengthOf(1); + + let bid = bids[0]; + expect(bid.ad).to.equal('Ad html'); + expect(bid.cpm).to.equal(1.2); + expect(bid.width).to.equal(300); + expect(bid.height).to.equal(250); + expect(bid.currency).to.equal('USD'); + }); + + it('should return no syncs when pixel syncing is disabled', function () { + const syncs = spec.getUserSyncs({ pixelEnabled: false }, [serverResponse]); + expect(syncs).to.deep.equal([]); + }); + + it('should return user syncs', function () { + const syncs = spec.getUserSyncs({pixelEnabled: true, iframeEnabled: true}, [serverResponse]); + const expected = [ + { type: 'image', url: 'http://link1' }, + { type: 'iframe', url: 'http://link2' }, + ]; + expect(syncs).to.deep.equal(expected); + }); +}); diff --git a/test/spec/modules/reloadBidAdapter_spec.js b/test/spec/modules/reloadBidAdapter_spec.js new file mode 100644 index 00000000000..674c810d48a --- /dev/null +++ b/test/spec/modules/reloadBidAdapter_spec.js @@ -0,0 +1,295 @@ +import { expect } from 'chai'; +import { spec } from 'modules/reloadBidAdapter'; + +let getParams = () => { + return JSON.parse(JSON.stringify({ + 'plcmID': 'placement_01', + 'partID': 'part00', + 'opdomID': 1, + 'bsrvID': 1, + 'type': 'pcm' + })); +}; + +let getBidderRequest = () => { + return JSON.parse(JSON.stringify({ + bidderCode: 'reload', + auctionId: 'd3e07445-ab06-44c8-a9dd-5ef9af06d2a6', + bidderRequestId: '7101db09af0db2', + start: new Date().getTime(), + bids: [{ + bidder: 'reload', + bidId: '84ab500420319d', + bidderRequestId: '7101db09af0db2', + auctionId: 'd3e07445-ab06-44c8-a9dd-5ef9af06d2a6', + params: getParams() + }] + })); +}; + +let getValidBidRequests = () => { + return JSON.parse(JSON.stringify([ + { + 'bidder': 'reload', + 'params': getParams(), + 'mediaTypes': { + 'banner': { + 'sizes': [[160, 600]] + } + }, + 'adUnitCode': '1b243858-3c53-43dc-9fdf-89f839ea4a0f', + 'transactionId': '8cbafa10-123d-4673-a1a5-04a1c7d62ded', + 'sizes': [[160, 600]], + 'bidId': '2236e11dc09931', + 'bidderRequestId': '1266bb886c2267', + 'auctionId': '4fb72c4d-94dc-4db1-8fac-3c2090ceeec0', + 'src': 'client', + 'bidRequestsCount': 1 + } + ])); +} + +let getExt1ServerResponse = () => { + return JSON.parse(JSON.stringify({ + 'pcmdata': { + 'thisVer': '100', + 'plcmSett': { + 'name': 'zz_test_mariano_adapter', + 'Version': '210', + 'lifeSpan': '100', + 'versionFolder': 'v4.14q', + 'versionFolderA': 'v4.14q', + 'versionFolderB': '', + 'stage': 'zz_test_mariano_adapter', + 'synchro': 1556916507000, + 'localCache': 'true', + 'testCase': 'A:00_B:100', + 'opdomain': '1', + 'checksum': '6378', + 'cpm': '0', + 'bstfct': '100', + 'totstop': 'false', + 'pcmurl': 'bidsrv01.reload.net' + }, + 'srvUrl': 'bidsrv01.reload.net', + 'instr': {'go': true, 'prc': 32, 'cur': 'USD'}, + 'statStr': 'eyN4aHYnQCk5OTotOC', + 'status': 'ok', + 'message': '', + 'log': '---- LOG ----' + }, + 'plcmID': 'zz_test_mariano_adapter', + 'partID': 'prx_part', + 'opdomID': '0', + 'bsrvID': 1, + 'adUnitCode': '1b243858-3c53-43dc-9fdf-89f839ea4a0f', + 'banner': {'w': 300, 'h': 250} + })); +} + +let getExt2ServerResponse = () => { + return JSON.parse(JSON.stringify({ + 'pcmdata': { + 'thisVer': '100', + 'plcmSett': { + 'name': 'placement_01', + 'Version': '210', + 'lifeSpan': '100', + 'versionFolder': 'v4.14q', + 'versionFolderA': 'v4.14q', + 'versionFolderB': '', + 'stage': 'placement_01', + 'synchro': 1556574760000, + 'localCache': 'true', + 'testCase': 'A:00_B:100', + 'opdomain': '1', + 'checksum': '6378', + 'cpm': '0', + 'bstfct': '100', + 'totstop': 'false', + 'pcmurl': 'bidsrv00.reload.net' + }, + 'srvUrl': 'bidsrv00.reload.net', + 'log': 'incomp_input_obj_version', + 'message': 'incomp_input_obj_version', + 'status': 'error' + }, + 'plcmID': 'placement_01', + 'partID': 'prx_part', + 'opdomID': '0', + 'bsrvID': 1, + 'adUnitCode': '1b243858-3c53-43dc-9fdf-89f839ea4a0f', + 'banner': {'w': 160, 'h': 600} + })); +} + +let getServerResponse = (pExt) => { + return JSON.parse(JSON.stringify({ + 'body': { + 'id': '2759340f70210d', + 'bidid': 'fbs-br-3mzdbycetjv8f8079', + 'seatbid': [ + { + 'bid': [ + { + 'id': 'fbs-br-stbd-bd-3mzdbycetjv8f807b', + 'price': 0, + 'nurl': '', + 'adm': '', + 'ext': pExt + } + ], + 'seat': 'fbs-br-stbd-3mzdbycetjv8f807a', + 'group': 0 + } + ] + }, + 'headers': {} + })); +} + +describe('ReloadAdapter', function () { + describe('isBidRequestValid', function () { + var bid = { + 'bidder': 'reload', + 'params': { + 'plcmID': 'placement_01', + 'partID': 'part00', + 'opdomID': 1, + 'bsrvID': 23, + 'type': 'pcm' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when bsrvID is not number', function () { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'plcmID': 'placement_01', + 'partID': 'part00', + 'opdomID': 1, + 'bsrvID': 'abc' + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when bsrvID > 99', function () { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'plcmID': 'placement_01', + 'partID': 'part00', + 'opdomID': 1, + 'bsrvID': 230 + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when bsrvID < 0', function () { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'plcmID': 'placement_01', + 'partID': 'part00', + 'opdomID': 1, + 'bsrvID': -3, + 'type': 'pcm' + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when required params are not passed', function () { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'plcmID': 'placement_01' + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests()', function () { + let vRequests = spec.buildRequests(getValidBidRequests(), {}); + let vData = JSON.parse(vRequests[0].data); + + it('should send one requests', () => { + expect(vRequests.length).to.equal(1); + }); + + it('should send one requests, one impression', () => { + expect(vData.imp.length).to.equal(1); + }); + + it('should exists ext.type and ext.pcmdata', () => { + expect(vData.imp[0].banner).to.exist; + expect(vData.imp[0].banner.ext).to.exist; + expect(vData.imp[0].banner.ext.type).to.exist; + expect(vData.imp[0].banner.ext.pcmdata).to.exist; + expect(vData.imp[0].banner.ext.type).to.equal('pcm'); + }); + }); + + describe('interpretResponse()', function () { + it('Returns an empty array', () => { + let vData = spec.interpretResponse(getServerResponse(getExt2ServerResponse()), {}); + + expect(vData.length).to.equal(0); + }); + + it('Returns an array with one response', () => { + let vData = spec.interpretResponse(getServerResponse(getExt1ServerResponse()), {}); + expect(vData.length).to.equal(1); + }); + + it('required fileds', () => { + let vData = spec.interpretResponse(getServerResponse(getExt1ServerResponse()), {}); + expect(vData.length).to.equal(1); + expect(vData[0]).to.have.all.keys(['requestId', 'ad', 'cpm', 'width', 'height', 'creativeId', 'currency', 'ttl', 'netRevenue']); + }); + + it('CPM great than 0', () => { + let vData = spec.interpretResponse(getServerResponse(getExt1ServerResponse()), {}); + expect(vData[0].cpm).to.greaterThan(0); + }); + + it('instruction empty', () => { + let vResponse = Object.assign({}, getServerResponse(getExt1ServerResponse())); + vResponse.body.seatbid[0].bid[0].ext.pcmdata.instr = null; + let vData = spec.interpretResponse(vResponse, {}); + expect(vData.length).to.equal(0); + + vResponse = Object.assign({}, getServerResponse(getExt1ServerResponse())); + vResponse.body.seatbid[0].bid[0].ext.pcmdata.instr = undefined; + vData = spec.interpretResponse(vResponse, {}); + expect(vData.length).to.equal(0); + + vResponse = Object.assign({}, getServerResponse(getExt1ServerResponse())); + vResponse.body.seatbid[0].bid[0].ext.pcmdata.instr.go = undefined; + vData = spec.interpretResponse(vResponse, {}); + expect(vData.length).to.equal(0); + }); + + it('instruction with go = false', () => { + let vResponse = getServerResponse(getExt1ServerResponse()); + vResponse.body.seatbid[0].bid[0].ext.pcmdata.instr.go = false; + let vData = spec.interpretResponse(vResponse, {}); + expect(vData.length).to.equal(0); + }); + + it('incompatibility output object version (thisVer)', () => { + let vResponse = getServerResponse(getExt1ServerResponse()); + vResponse.body.seatbid[0].bid[0].ext.pcmdata.thisVer = '200'; + let vData = spec.interpretResponse(vResponse, {}); + expect(vData.length).to.equal(0); + }); + }); +}); diff --git a/test/spec/modules/rxrtbBidAdapter_spec.js b/test/spec/modules/rexrtbBidAdapter_spec.js similarity index 85% rename from test/spec/modules/rxrtbBidAdapter_spec.js rename to test/spec/modules/rexrtbBidAdapter_spec.js index b56ef0544b2..b35e05bbf46 100644 --- a/test/spec/modules/rxrtbBidAdapter_spec.js +++ b/test/spec/modules/rexrtbBidAdapter_spec.js @@ -1,11 +1,11 @@ import {expect} from 'chai'; -import {spec} from 'modules/rxrtbBidAdapter'; +import {spec} from 'modules/rexrtbBidAdapter'; -describe('rxrtb adapater', function () { +describe('rexrtb adapater', function () { describe('Test validate req', function () { it('should accept minimum valid bid', function () { let bid = { - bidder: 'rxrtb', + bidder: 'rexrtb', params: { id: 89, token: '658f11a5efbbce2f9be3f1f146fcbc22', @@ -19,7 +19,7 @@ describe('rxrtb adapater', function () { it('should reject missing id', function () { let bid = { - bidder: 'rxrtb', + bidder: 'rexrtb', params: { token: '658f11a5efbbce2f9be3f1f146fcbc22', source: 'prebidtest' @@ -32,7 +32,7 @@ describe('rxrtb adapater', function () { it('should reject id not Integer', function () { let bid = { - bidder: 'rxrtb', + bidder: 'rexrtb', params: { id: '123', token: '658f11a5efbbce2f9be3f1f146fcbc22', @@ -43,25 +43,12 @@ describe('rxrtb adapater', function () { expect(isValid).to.equal(false); }); - - it('should reject missing source', function () { - let bid = { - bidder: 'rxrtb', - params: { - id: 89, - token: '658f11a5efbbce2f9be3f1f146fcbc22' - } - }; - const isValid = spec.isBidRequestValid(bid); - - expect(isValid).to.equal(false); - }); }); describe('Test build request', function () { it('minimum request', function () { let bid = { - bidder: 'rxrtb', + bidder: 'rexrtb', sizes: [[728, 90]], bidId: '4d0a6829338a07', adUnitCode: 'div-gpt-ad-1460505748561-0', diff --git a/test/spec/modules/rhythmoneBidAdapter_spec.js b/test/spec/modules/rhythmoneBidAdapter_spec.js index 2f06e7f8288..b6ac09a6207 100644 --- a/test/spec/modules/rhythmoneBidAdapter_spec.js +++ b/test/spec/modules/rhythmoneBidAdapter_spec.js @@ -1,119 +1,745 @@ import {spec} from '../../../modules/rhythmoneBidAdapter'; -var assert = require('assert'); +import * as utils from '../../../src/utils'; +import * as sinon from 'sinon'; + +var r1adapter = spec; describe('rhythmone adapter tests', function () { - describe('auditBeacon', function() { - var z = spec; - var beaconURL = z.getUserSyncs({pixelEnabled: true})[0]; + beforeEach(function() { + this.defaultBidderRequest = { + 'refererInfo': { + 'referer': 'Reference Page', + 'stack': [ + 'aodomain.dvl', + 'page.dvl' + ] + } + }; + }); + + describe('Verify 1.0 POST Banner Bid Request', function () { + it('buildRequests works', function () { + var bidRequestList = [ + { + 'bidder': 'rhythmone', + 'params': { + 'placementId': 'myplacement', + 'zone': 'myzone', + 'path': 'mypath' + }, + '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); + + expect(bidRequest.url).to.have.string('//tag.1rx.io/rmp/myplacement/0/mypath?z=myzone&hbv='); + expect(bidRequest.method).to.equal('POST'); + const openrtbRequest = JSON.parse(bidRequest.data); + expect(openrtbRequest.site).to.not.equal(null); + expect(openrtbRequest.site.ref).to.equal('Reference Page'); + expect(openrtbRequest.device).to.not.equal(null); + expect(openrtbRequest.device.ua).to.equal(navigator.userAgent); + expect(openrtbRequest.device.dnt).to.equal(0); + expect(openrtbRequest.imp[0].banner).to.not.equal(null); + expect(openrtbRequest.imp[0].banner.format[0].w).to.equal(300); + expect(openrtbRequest.imp[0].banner.format[0].h).to.equal(250); + expect(openrtbRequest.imp[0].ext.bidder.zone).to.equal('myzone'); + expect(openrtbRequest.imp[0].ext.bidder.path).to.equal('mypath'); + }); + + it('interpretResponse works', function() { + var bidList = { + 'body': [ + { + 'impid': 'div-gpt-ad-1438287399331-0', + 'w': 300, + 'h': 250, + 'adm': '
    My Compelling Ad
    ', + 'price': 1, + 'crid': 'cr-cfy24' + } + ] + }; + + var bannerBids = r1adapter.interpretResponse(bidList); + + expect(bannerBids.length).to.equal(1); + const bid = bannerBids[0]; + expect(bid.width).to.equal(300); + expect(bid.height).to.equal(250); + expect(bid.creativeId).to.equal('cr-cfy24'); + expect(bid.currency).to.equal('USD'); + 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() { + it('buildRequests works', function () { + var bidRequestList = [ + { + 'bidder': 'rhythmone', + 'params': { + 'placementId': 'myplacement', + 'zone': 'myzone', + 'path': 'mypath' + }, + 'mediaTypes': { + 'video': { + 'playerSize': [640, 480], + 'context': 'instream' + } + }, + 'adUnitCode': 'div-gpt-ad-1438287399331-1', + 'sizes': [ + [300, 250] + ], + 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + 'bidderRequestId': '418b37f85e772c', + 'auctionId': '18fd8b8b0bd757', + 'bidRequestsCount': 1, + 'bidId': '51ef8751f9aead' + } + ]; + + var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + + expect(bidRequest.url).to.have.string('//tag.1rx.io/rmp/myplacement/0/mypath?z=myzone&hbv='); + expect(bidRequest.method).to.equal('POST'); + const openrtbRequest = JSON.parse(bidRequest.data); + expect(openrtbRequest.site).to.not.equal(null); + expect(openrtbRequest.device).to.not.equal(null); + expect(openrtbRequest.device.ua).to.equal(navigator.userAgent); + expect(openrtbRequest.device).to.have.property('dnt'); + expect(openrtbRequest.imp[0].video).to.not.equal(null); + expect(openrtbRequest.imp[0].video.w).to.equal(640); + expect(openrtbRequest.imp[0].video.h).to.equal(480); + expect(openrtbRequest.imp[0].video.mimes[0]).to.equal('video/mp4'); + expect(openrtbRequest.imp[0].video.protocols).to.eql([2, 3, 5, 6]); + expect(openrtbRequest.imp[0].video.startdelay).to.equal(0); + expect(openrtbRequest.imp[0].video.skip).to.equal(0); + expect(openrtbRequest.imp[0].video.playbackmethod).to.eql([1, 2, 3, 4]); + expect(openrtbRequest.imp[0].video.delivery[0]).to.equal(1); + expect(openrtbRequest.imp[0].video.api).to.eql([1, 2, 5]); + }); + + it('interpretResponse works', function() { + var bidList = { + 'body': [ + { + 'impid': 'div-gpt-ad-1438287399331-1', + 'price': 1, + 'nurl': 'http://testdomain/rmp/placementid/0/path?reqId=1636037', + 'adomain': [ + 'test.com' + ], + 'cid': '467415', + 'crid': 'cr-vid', + 'w': 800, + 'h': 600 + } + ] + }; + + var videoBids = r1adapter.interpretResponse(bidList); + + expect(videoBids.length).to.equal(1); + const bid = videoBids[0]; + expect(bid.width).to.equal(800); + expect(bid.height).to.equal(600); + expect(bid.vastUrl).to.equal('http://testdomain/rmp/placementid/0/path?reqId=1636037'); + expect(bid.mediaType).to.equal('video'); + expect(bid.creativeId).to.equal('cr-vid'); + expect(bid.currency).to.equal('USD'); + expect(bid.netRevenue).to.equal(true); + expect(bid.cpm).to.equal(1.0); + expect(bid.ttl).to.equal(600); + }); + }); + + describe('Verify Multi-Format ads and Multiple Size Bid Request', function() { + it('buildRequests works', function () { + var bidRequestList = [ + { + 'bidder': 'rhythmone', + 'params': { + 'placementId': 'myplacement', + 'zone': 'myzone', + 'path': 'mypath', + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [300, 250], + [300, 600] + ] + }, + 'video': { + 'playerSize': [[640, 480]], + 'context': 'instream' + } + }, + 'adUnitCode': 'div-gpt-ad-1438287399331-5', + 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + 'bidderRequestId': '418b37f85e772c', + 'auctionId': '18fd8b8b0bd757', + 'bidRequestsCount': 1, + 'bidId': '51ef8751f9aead' + } + ]; + + var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + + const openrtbRequest = JSON.parse(bidRequest.data); + expect(openrtbRequest.site).to.not.equal(null); + expect(openrtbRequest.site.ref).to.equal('Reference Page'); + expect(openrtbRequest.device).to.not.equal(null); + expect(openrtbRequest.device.ua).to.equal(navigator.userAgent); + expect(openrtbRequest.device).to.have.property('dnt'); + expect(openrtbRequest.imp[0].video).to.not.equal(null); + expect(openrtbRequest.imp[0].video.w).to.equal(640); + expect(openrtbRequest.imp[0].video.h).to.equal(480); + expect(openrtbRequest.imp[0].video.mimes[0]).to.equal('video/mp4'); + expect(openrtbRequest.imp[0].video.protocols).to.eql([2, 3, 5, 6]); + expect(openrtbRequest.imp[0].video.startdelay).to.equal(0); + expect(openrtbRequest.imp[0].video.skip).to.equal(0); + expect(openrtbRequest.imp[0].video.playbackmethod).to.eql([1, 2, 3, 4]); + expect(openrtbRequest.imp[0].video.delivery[0]).to.equal(1); + expect(openrtbRequest.imp[0].video.api).to.eql([1, 2, 5]); + expect(openrtbRequest.imp[0].banner).to.not.equal(null); + expect(openrtbRequest.imp[0].banner.format[0].w).to.equal(300); + expect(openrtbRequest.imp[0].banner.format[0].h).to.equal(250); + expect(openrtbRequest.imp[0].banner.format[1].w).to.equal(300); + expect(openrtbRequest.imp[0].banner.format[1].h).to.equal(600); + expect(openrtbRequest.imp[0].ext.bidder.zone).to.equal('myzone'); + expect(openrtbRequest.imp[0].ext.bidder.path).to.equal('mypath'); + }); - it('should contain the correct path', function() { - var u = '//hbevents.1rx.io/audit?' - assert.equal(beaconURL.url.substring(0, u.length), u); + it('interpretResponse works', function() { + var bidList = { + 'body': { + 'id': '1e810245dd1779', + 'seatbid': [ + { + 'bid': [ + { + 'impid': 'div-gpt-ad-1438287399331-5', + 'price': 1, + 'nurl': 'http://testdomain/rmp/placementid/0/path?reqId=1636037', + 'adomain': [ + 'test.com' + ], + 'cid': '467415', + 'crid': 'cr-vid', + 'w': 800, + 'h': 600 + } + ] + } + ] + } + }; + + var forRMPMultiFormatResponse = r1adapter.interpretResponse(bidList); + + expect(forRMPMultiFormatResponse.length).to.equal(1); + const bid = forRMPMultiFormatResponse[0]; + expect(bid.width).to.equal(800); + expect(bid.height).to.equal(600); + expect(bid.vastUrl).to.equal('http://testdomain/rmp/placementid/0/path?reqId=1636037'); + expect(bid.mediaType).to.equal('video'); + expect(bid.creativeId).to.equal('cr-vid'); + expect(bid.currency).to.equal('USD'); + expect(bid.netRevenue).to.equal(true); + expect(bid.cpm).to.equal(1.0); + expect(bid.ttl).to.equal(600); }); }); - describe('rhythmoneResponse', function () { - var z = spec; + describe('misc buildRequests', function() { + it('should send GDPR Consent data to RhythmOne tag', function () { + var bidRequestList = [ + { + 'bidder': 'rhythmone', + 'params': { + 'placementId': 'myplacement', + 'zone': 'myzone', + 'path': 'mypath' + }, + '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 = { + 'gdprApplies': true, + 'consentString': consentString + }; + + var bidRequest = r1adapter.buildRequests(bidRequestList, gdprBidderRequest); - var rmpRequest = z.buildRequests( - [ + const openrtbRequest = JSON.parse(bidRequest.data); + expect(openrtbRequest.user.ext.consent).to.equal(consentString); + expect(openrtbRequest.regs.ext.gdpr).to.equal(true); + }); + + it('prefer 2.0 sizes', function () { + var bidRequestList = [ { 'bidder': 'rhythmone', 'params': { - 'placementId': 'xyz', - 'keywords': '', - 'categories': [], - 'trace': true, - 'method': 'get', - 'endpoint': 'http://fakedomain.com' + 'placementId': 'myplacement', + 'zone': 'myzone', + 'path': 'mypath' + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 600]] + } }, - 'mediaType': 'video', 'adUnitCode': 'div-gpt-ad-1438287399331-0', - 'sizes': [[300, 250]] + 'sizes': [[300, 250]], + 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + 'bidderRequestId': '418b37f85e772c', + 'auctionId': '18fd8b8b0bd757', + 'bidRequestsCount': 1, + 'bidId': '51ef8751f9aead' } - ] - ); + ]; + + var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); - it('should have one request to RMP', function() { - assert.equal(rmpRequest.length, 1); + const openrtbRequest = JSON.parse(bidRequest.data); + expect(openrtbRequest.imp[0].banner.format[0].w).to.equal(300); + expect(openrtbRequest.imp[0].banner.format[0].h).to.equal(600); }); - var mangoRequest = z.buildRequests( - [ + it('does not return request for invalid banner size configuration', function () { + var bidRequestList = [ { 'bidder': 'rhythmone', 'params': { - 'placementId': 'xyz', - 'keywords': '', - 'categories': [], - 'trace': true, - 'method': 'get', - 'api': 'mango', - 'endpoint': 'http://fakedomain.com' + 'placementId': 'myplacement', + 'zone': 'myzone', + 'path': 'mypath' + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[300]] + } }, 'adUnitCode': 'div-gpt-ad-1438287399331-0', - 'sizes': [[300, 250]] + 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + 'bidderRequestId': '418b37f85e772c', + 'auctionId': '18fd8b8b0bd757', + 'bidRequestsCount': 1, + 'bidId': '51ef8751f9aead' } - ] - ); + ]; - it('should have one request to Mango', function() { - assert.equal(mangoRequest.length, 1); + var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + expect(bidRequest.method).to.be.undefined; }); - it('should send GDPR Consent data to RhythmOne tag', function () { - let _consentString = 'testConsentString'; - var request = z.buildRequests( - [ - { - 'bidder': 'rhythmone', - 'params': { - 'placementId': 'xyz', - 'keywords': '', - 'categories': [], - 'trace': true, - 'method': 'get', - 'api': 'mango', - 'endpoint': 'http://fakedomain.com?' - }, - 'adUnitCode': 'div-gpt-ad-1438287399331-0', - 'sizes': [[300, 250]] - } - ], {gdprConsent: {gdprApplies: 1, consentString: _consentString}} - ); - assert.equal(getURLParam(request[0].url, 'gdpr'), 'true'); - assert.equal(getURLParam(request[0].url, 'gdpr_consent'), 'testConsentString'); + it('does not return request for missing banner size configuration', function () { + var bidRequestList = [ + { + 'bidder': 'rhythmone', + 'params': { + 'placementId': 'myplacement', + 'zone': 'myzone', + 'path': 'mypath' + }, + 'mediaTypes': { + 'banner': {} + }, + 'adUnitCode': 'div-gpt-ad-1438287399331-0', + 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + 'bidderRequestId': '418b37f85e772c', + 'auctionId': '18fd8b8b0bd757', + 'bidRequestsCount': 1, + 'bidId': '51ef8751f9aead' + } + ]; + + var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + expect(bidRequest.method).to.be.undefined; }); - var bids = z.interpretResponse({ - body: [ + it('reject bad sizes', function () { + var bidRequestList = [ { - 'impid': 'div-gpt-ad-1438287399331-0', - 'w': 300, - 'h': 250, - 'adm': '
    My ad4 with cpm of a4ab3485f434f74f
    ', - 'price': 1, - 'crid': 'cr-cfy24' + 'bidder': 'rhythmone', + 'params': { + 'placementId': 'myplacement', + 'zone': 'myzone', + 'path': 'mypath' + }, + 'mediaTypes': { + 'banner': {'sizes': [['400', '500'], ['4n0', '5g0']]} + }, + 'adUnitCode': 'div-gpt-ad-1438287399331-0', + 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + 'bidderRequestId': '418b37f85e772c', + 'auctionId': '18fd8b8b0bd757', + 'bidRequestsCount': 1, + 'bidId': '51ef8751f9aead' } - ] + ]; + + var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + const openrtbRequest = JSON.parse(bidRequest.data); + expect(openrtbRequest.imp[0].banner.format.length).to.equal(1); }); - it('should register one bid', function() { - assert.equal(bids.length, 1); + it('dnt is correctly set to 1', function () { + var bidRequestList = [ + { + 'bidder': 'rhythmone', + 'params': { + 'placementId': 'myplacement', + }, + '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); + + dntStub.restore(); + + const openrtbRequest = JSON.parse(bidRequest.data); + expect(openrtbRequest.device.dnt).to.equal(1); }); - function getURLParam(url, key) { - let val = ''; - if (url.indexOf('?') > -1) { - let qs = url.substr(url.indexOf('?')); - let qsArr = qs.split('&'); - for (let i = 0; i < qsArr.length; i++) { - if (qsArr[i].indexOf(key.toLowerCase() + '=') > -1) { - val = qsArr[i].split('=')[1] - break; - } + + it('sets floor', function () { + var bidRequestList = [ + { + 'bidder': 'rhythmone', + 'params': { + 'placementId': 'myplacement', + 'floor': 100.0 + }, + '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 bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + + const openrtbRequest = JSON.parse(bidRequest.data); + expect(openrtbRequest.imp[0].bidfloor).to.equal(100.0); + }); + + it('supports string video sizes', function () { + var bidRequestList = [ + { + 'bidder': 'rhythmone', + 'params': { + 'placementId': 'myplacement', + }, + 'mediaTypes': { + 'video': { + 'context': 'instream', + 'playerSize': ['600', '300'] + } + }, + 'adUnitCode': 'div-gpt-ad-1438287399331-1', + 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + 'bidderRequestId': '418b37f85e772c', + 'auctionId': '18fd8b8b0bd757', + 'bidRequestsCount': 1, + 'bidId': '51ef8751f9aead' + } + ]; + + var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + + const openrtbRequest = JSON.parse(bidRequest.data); + expect(openrtbRequest.imp[0].video.w).to.equal(600); + expect(openrtbRequest.imp[0].video.h).to.equal(300); + }); + + it('rejects bad video sizes', function () { + var bidRequestList = [ + { + 'bidder': 'rhythmone', + 'params': { + 'placementId': 'myplacement', + }, + 'mediaTypes': { + 'video': { + 'context': 'instream', + 'playerSize': ['badWidth', 'badHeight'] + } + }, + 'adUnitCode': 'div-gpt-ad-1438287399331-1', + 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + 'bidderRequestId': '418b37f85e772c', + 'auctionId': '18fd8b8b0bd757', + 'bidRequestsCount': 1, + 'bidId': '51ef8751f9aead' + } + ]; + + var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + + const openrtbRequest = JSON.parse(bidRequest.data); + expect(openrtbRequest.imp[0].video.w).to.be.undefined; + expect(openrtbRequest.imp[0].video.h).to.be.undefined; + }); + + it('supports missing video size', function () { + var bidRequestList = [ + { + 'bidder': 'rhythmone', + 'params': { + 'placementId': 'myplacement', + }, + 'mediaTypes': { + 'video': { + 'context': 'instream' + } + }, + 'adUnitCode': 'div-gpt-ad-1438287399331-1', + 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + 'bidderRequestId': '418b37f85e772c', + 'auctionId': '18fd8b8b0bd757', + 'bidRequestsCount': 1, + 'bidId': '51ef8751f9aead' } + ]; + + var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + + const openrtbRequest = JSON.parse(bidRequest.data); + expect(openrtbRequest.imp[0].video.w).to.be.undefined; + expect(openrtbRequest.imp[0].video.h).to.be.undefined; + }); + + it('uses default zone and path', function () { + var bidRequestList = [ + { + 'bidder': 'rhythmone', + 'params': { + 'placementId': 'myplacement' + }, + '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 bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + + const openrtbRequest = JSON.parse(bidRequest.data); + expect(openrtbRequest.imp[0].ext.bidder.zone).to.equal('1r'); + expect(openrtbRequest.imp[0].ext.bidder.path).to.equal('mvo'); + }); + + it('should return empty when required params not found', function () { + var bidRequestList = [ + { + 'bidder': 'rhythmone', + 'params': { + 'zone': 'myzone', + 'path': 'mypath' + }, + '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 bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + + expect(bidRequest).to.be.empty; + }); + + it('should return empty site data when refererInfo is missing', function() { + delete this.defaultBidderRequest.refererInfo; + var bidRequestList = [ + { + 'bidder': 'rhythmone', + 'params': { + 'placementId': 'myplacement', + 'zone': 'myzone', + 'path': 'mypath' + }, + '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); + const openrtbRequest = JSON.parse(bidRequest.data); + + expect(openrtbRequest.site.domain).to.equal(''); + expect(openrtbRequest.site.page).to.equal(''); + expect(openrtbRequest.site.ref).to.equal(''); + }); + }); + + it('should return empty site.domain and site.page when refererInfo.stack is empty', function() { + this.defaultBidderRequest.refererInfo.stack = []; + var bidRequestList = [ + { + 'bidder': 'rhythmone', + 'params': { + 'placementId': 'myplacement', + 'zone': 'myzone', + 'path': 'mypath' + }, + '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); + const openrtbRequest = JSON.parse(bidRequest.data); + + expect(openrtbRequest.site.domain).to.equal(''); + expect(openrtbRequest.site.page).to.equal(''); + expect(openrtbRequest.site.ref).to.equal('Reference Page'); + }); + + it('should secure correctly', function() { + this.defaultBidderRequest.refererInfo.stack[0] = ['https://securesite.dvl']; + var bidRequestList = [ + { + 'bidder': 'rhythmone', + 'params': { + 'placementId': 'myplacement', + 'zone': 'myzone', + 'path': 'mypath' + }, + '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' } - return val; - } + ]; + + var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + const openrtbRequest = JSON.parse(bidRequest.data); + + expect(openrtbRequest.imp[0].secure).to.equal(1); + }); + + describe('misc interpretResponse', function () { + it('No bid response', function() { + var noBidResponse = r1adapter.interpretResponse({ + 'body': '' + }); + expect(noBidResponse.length).to.equal(0); + }); + }); + + describe('isBidRequestValid', function () { + var bid = { + 'bidder': 'rhythmone', + 'params': { + 'placementId': 'myplacement', + 'path': 'mypath', + 'zone': 'myzone' + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250]] + } + }, + 'adUnitCode': 'bannerDiv' + }; + + it('should return true when required params found', function () { + expect(r1adapter.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when placementId missing', function () { + delete bid.params.placementId; + expect(r1adapter.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('getUserSyncs', function () { + it('returns an empty string', function () { + expect(r1adapter.getUserSyncs()).to.deep.equal([]); + }); }); }); diff --git a/test/spec/modules/richaudienceBidAdapter_spec.js b/test/spec/modules/richaudienceBidAdapter_spec.js new file mode 100644 index 00000000000..16d67ce7ceb --- /dev/null +++ b/test/spec/modules/richaudienceBidAdapter_spec.js @@ -0,0 +1,347 @@ +// import or require modules necessary for the test, e.g.: +import {expect} from 'chai'; // may prefer 'assert' in place of 'expect' +import { + spec +} from 'modules/richaudienceBidAdapter'; +import {config} from 'src/config'; +import * as utils from 'src/utils'; + +describe('Richaudience adapter tests', function () { + var DEFAULT_PARAMS = [{ + adUnitCode: 'test-div', + bidId: '2c7c8e9c900244', + sizes: [ + [300, 250], + [300, 600], + [728, 90], + [970, 250] + ], + bidder: 'richaudience', + params: { + bidfloor: 0.5, + pid: 'ADb1f40rmi', + supplyType: 'site' + }, + auctionId: '0cb3144c-d084-4686-b0d6-f5dbe917c563', + bidRequestsCount: 1, + bidderRequestId: '1858b7382993ca', + transactionId: '29df2112-348b-4961-8863-1b33684d95e6' + }]; + + var DEFAULT_PARAMS_APP = [{ + adUnitCode: 'test-div', + bidId: '2c7c8e9c900244', + sizes: [ + [300, 250], + [300, 600], + [728, 90], + [970, 250] + ], + bidder: 'richaudience', + params: { + bidfloor: 0.5, + ifa: 'AAAAAAAAA-BBBB-CCCC-1111-222222220000', + pid: 'ADb1f40rmi', + supplyType: 'app', + }, + auctionId: '0cb3144c-d084-4686-b0d6-f5dbe917c563', + bidRequestsCount: 1, + bidderRequestId: '1858b7382993ca', + transactionId: '29df2112-348b-4961-8863-1b33684d95e6' + }]; + + var DEFAULT_PARAMS_WO_OPTIONAL = [{ + adUnitCode: 'test-div', + bidId: '2c7c8e9c900244', + sizes: [ + [300, 250], + [300, 600], + [728, 90], + [970, 250] + ], + bidder: 'richaudience', + params: { + pid: 'ADb1f40rmi', + supplyType: 'site', + }, + auctionId: '851adee7-d843-48f9-a7e9-9ff00573fcbf', + bidRequestsCount: 1, + bidderRequestId: '1858b7382993ca', + transactionId: '29df2112-348b-4961-8863-1b33684d95e6' + }]; + + var BID_RESPONSE = { + body: { + cpm: 1.50, + adm: '', + media_type: 'js', + width: 300, + height: 250, + creative_id: '189198063', + netRevenue: true, + currency: 'USD', + ttl: 300, + dealId: 'dealId' + + } + }; + + var BID_RESPONSE_VIDEO = { + body: { + cpm: 1.50, + media_type: 'video', + width: 1, + height: 1, + creative_id: '189198063', + netRevenue: true, + currency: 'USD', + ttl: 300, + vastXML: '', + dealId: 'dealId' + } + }; + + it('Verify build request', function () { + config.setConfig({ + 'currency': { + 'adServerCurrency': 'USD' + } + }); + + const request = spec.buildRequests(DEFAULT_PARAMS, { + 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: 'http://domain.com', + numIframes: 0 + } + }); + + expect(request[0]).to.have.property('method').and.to.equal('POST'); + const requestContent = JSON.parse(request[0].data); + expect(requestContent).to.have.property('bidfloor').and.to.equal(0.5); + expect(requestContent).to.have.property('pid').and.to.equal('ADb1f40rmi'); + expect(requestContent).to.have.property('supplyType').and.to.equal('site'); + expect(requestContent).to.have.property('auctionId').and.to.equal('0cb3144c-d084-4686-b0d6-f5dbe917c563'); + expect(requestContent).to.have.property('bidId').and.to.equal('2c7c8e9c900244'); + expect(requestContent).to.have.property('BidRequestsCount').and.to.equal(1); + expect(requestContent).to.have.property('bidder').and.to.equal('richaudience'); + expect(requestContent).to.have.property('bidderRequestId').and.to.equal('1858b7382993ca'); + expect(requestContent).to.have.property('tagId').and.to.equal('test-div'); + expect(requestContent).to.have.property('referer').and.to.equal('http%3A%2F%2Fdomain.com'); + expect(requestContent).to.have.property('sizes'); + expect(requestContent.sizes[0]).to.have.property('w').and.to.equal(300); + expect(requestContent.sizes[0]).to.have.property('h').and.to.equal(250); + expect(requestContent.sizes[1]).to.have.property('w').and.to.equal(300); + expect(requestContent.sizes[1]).to.have.property('h').and.to.equal(600); + expect(requestContent).to.have.property('transactionId').and.to.equal('29df2112-348b-4961-8863-1b33684d95e6'); + expect(requestContent).to.have.property('timeout').and.to.equal(3000); + }); + + describe('gdpr test', function () { + it('Verify build request with GDPR', function () { + config.setConfig({ + 'currency': { + 'adServerCurrency': 'USD' + }, + consentManagement: { + cmpApi: 'iab', + timeout: 8000, + allowAuctionWithoutConsent: true + } + }); + + const request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, { + 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: 'http://domain.com', + numIframes: 0 + } + }); + const requestContent = JSON.parse(request[0].data); + expect(requestContent).to.have.property('gdpr_consent').and.to.equal('BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA'); + }); + + it('Verify adding ifa when supplyType equal to app', function () { + const request = spec.buildRequests(DEFAULT_PARAMS_APP, { + 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: 'http://domain.com', + numIframes: 0 + } + }); + }); + + it('Verify build request with GDPR without gdprApplies', function () { + config.setConfig({ + 'currency': { + 'adServerCurrency': 'EUR' + }, + consentManagement: { + cmp: 'iab', + consentRequired: true, + timeout: 8000, + allowAuctionWithoutConsent: true + } + }); + const request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, { + gdprConsent: { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA' + }, + refererInfo: { + referer: 'http://domain.com', + numIframes: 0 + } + }); + const requestContent = JSON.parse(request[0].data); + expect(requestContent).to.have.property('gdpr_consent').and.to.equal('BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA'); + }); + }); + + it('Verify interprete response', function () { + const request = spec.buildRequests(DEFAULT_PARAMS, { + 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: 'http://domain.com', + numIframes: 0 + } + }); + + const bids = spec.interpretResponse(BID_RESPONSE, request[0]); + expect(bids).to.have.lengthOf(1); + const bid = bids[0]; + expect(bid.cpm).to.equal(1.50); + expect(bid.ad).to.equal(''); + expect(bid.mediaType).to.equal('js'); + expect(bid.width).to.equal(300); + expect(bid.height).to.equal(250); + expect(bid.creativeId).to.equal('189198063'); + expect(bid.netRevenue).to.equal(true); + expect(bid.currency).to.equal('USD'); + expect(bid.ttl).to.equal(300); + expect(bid.dealId).to.equal('dealId'); + }); + + it('no banner media response', function () { + const request = spec.buildRequests(DEFAULT_PARAMS, { + 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: 'http://domain.com', + numIframes: 0 + } + }); + + const bids = spec.interpretResponse(BID_RESPONSE_VIDEO, request[0]); + const bid = bids[0]; + expect(bid.vastXml).to.equal(''); + }); + + it('Verifies bidder_code', function () { + expect(spec.code).to.equal('richaudience'); + }); + + it('Verifies bidder aliases', function () { + expect(spec.aliases).to.have.lengthOf(1); + expect(spec.aliases[0]).to.equal('ra'); + }); + + it('Verifies if bid request is valid', function () { + expect(spec.isBidRequestValid(DEFAULT_PARAMS[0])).to.equal(true); + expect(spec.isBidRequestValid(DEFAULT_PARAMS_WO_OPTIONAL[0])).to.equal(true); + expect(spec.isBidRequestValid({})).to.equal(false); + expect(spec.isBidRequestValid({ + params: {} + })).to.equal(false); + expect(spec.isBidRequestValid({ + params: { + pid: 'ADb1f40rmi' + } + })).to.equal(false); + expect(spec.isBidRequestValid({ + params: { + supplyType: 'site' + } + })).to.equal(false); + expect(spec.isBidRequestValid({ + params: { + supplyType: 'app' + } + })).to.equal(false); + expect(spec.isBidRequestValid({ + params: { + pid: 'ADb1f40rmi', + supplyType: 'site' + } + })).to.equal(true); + expect(spec.isBidRequestValid({ + params: { + pid: ['1gCB5ZC4XL', '1a40xk8qSV'], + supplyType: 'site' + } + })).to.equal(true); + expect(spec.isBidRequestValid({ + params: { + pid: 'ADb1f40rmi', + supplyType: 'site' + } + })).to.equal(true); + expect(spec.isBidRequestValid({ + params: { + pid: 'ADb1f40rmi', + supplyType: 'app', + ifa: 'AAAAAAAAA-BBBB-CCCC-1111-222222220000', + } + })).to.equal(true); + expect(spec.isBidRequestValid({ + params: { + pid: 'ADb1f40rmi', + supplyType: 'site', + bidfloor: 0.50, + } + })).to.equal(true); + expect(spec.isBidRequestValid({ + params: { + pid: 'ADb1f40rmi', + supplyType: 'site', + bidfloor: 0.50, + } + })).to.equal(true); + }); + + it('Verifies user sync', function () { + var syncs = spec.getUserSyncs({ + iframeEnabled: true + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('iframe'); + syncs = spec.getUserSyncs({ + iframeEnabled: false + }, [BID_RESPONSE], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: true + }, [], {consentString: '', gdprApplies: false}); + expect(syncs).to.have.lengthOf(1); + }); +}); diff --git a/test/spec/modules/rivrAnalyticsAdapter_spec.js b/test/spec/modules/rivrAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..2676c3a59b6 --- /dev/null +++ b/test/spec/modules/rivrAnalyticsAdapter_spec.js @@ -0,0 +1,150 @@ +import * as utils from 'src/utils'; +import analyticsAdapter from 'modules/rivrAnalyticsAdapter'; +import { + sendImpressions, + handleClickEventWithClosureScope, + createUnOptimisedParamsField, + dataLoaderForHandler, + pinHandlerToHTMLElement, + setAuctionAbjectPosition, + createNewAuctionObject, + concatAllUnits, + trackAuctionEnd, + handleImpression, + getCookie, + storeAndReturnRivrUsrIdCookie, + arrayDifference, + activelyWaitForBannersToRender, +} from 'modules/rivrAnalyticsAdapter'; +import {expect} from 'chai'; +import adapterManager from 'src/adapterManager'; +import * as ajax from 'src/ajax'; +import CONSTANTS from 'src/constants.json'; + +const events = require('../../../src/events'); + +describe('RIVR Analytics adapter', () => { + const EXPIRING_QUEUE_TIMEOUT = 4000; + const EXPIRING_QUEUE_TIMEOUT_MOCK = 100; + const RVR_CLIENT_ID_MOCK = 'aCliendId'; + const SITE_CATEGORIES_MOCK = ['cat1', 'cat2']; + const EMITTED_AUCTION_ID = 1; + const TRACKER_BASE_URL_MOCK = 'tracker.rivr.simplaex.com'; + const UUID_REG_EXP = new RegExp('[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}', 'i'); + const MOCK_RIVRADDON_CONTEXT = {}; + let sandbox; + let ajaxStub; + let rivraddonsEnableAnalyticsStub; + let rivraddonsTrackPbjsEventStub; + let timer; + + before(() => { + sandbox = sinon.sandbox.create(); + window.rivraddon = { + analytics: { + enableAnalytics: () => {}, + getContext: () => { return MOCK_RIVRADDON_CONTEXT; }, + trackPbjsEvent: () => {}, + } + }; + rivraddonsEnableAnalyticsStub = sandbox.stub(window.rivraddon.analytics, 'enableAnalytics'); + }); + + beforeEach(() => { + timer = sandbox.useFakeTimers(0); + ajaxStub = sandbox.stub(ajax, 'ajax'); + sinon.stub(events, 'getEvents').returns([]); + + adapterManager.registerAnalyticsAdapter({ + code: 'rivr', + adapter: analyticsAdapter + }); + adapterManager.enableAnalytics({ + provider: 'rivr', + options: { + clientID: RVR_CLIENT_ID_MOCK, + adUnits: [utils.deepClone(BANNER_AD_UNITS_MOCK)], + siteCategories: SITE_CATEGORIES_MOCK, + } + }); + }); + + afterEach(() => { + analyticsAdapter.disableAnalytics(); + events.getEvents.restore(); + ajaxStub.restore(); + timer.restore(); + }); + + after(() => { + sandbox.restore(); + delete window.rivraddon; + }); + + it('enableAnalytics - should call rivraddon enableAnalytics with the correct arguments', () => { + // adapterManager.enableAnalytics() is called in beforeEach. If just called here it doesn't seem to work. + const firstArgument = rivraddonsEnableAnalyticsStub.getCall(0).args[0]; + const secondArgument = rivraddonsEnableAnalyticsStub.getCall(0).args[1]; + + expect(firstArgument.provider).to.be.equal('rivr'); + + expect(secondArgument).to.have.property('utils'); + expect(secondArgument).to.have.property('ajax'); + }); + + it('Firing an event when rivraddon context is not defined it should do nothing', () => { + let rivraddonsGetContextStub = sandbox.stub(window.rivraddon.analytics, 'getContext'); + rivraddonsTrackPbjsEventStub = sandbox.stub(window.rivraddon.analytics, 'trackPbjsEvent'); + + expect(rivraddonsTrackPbjsEventStub.callCount).to.be.equal(0); + + events.emit(CONSTANTS.EVENTS.AUCTION_INIT, {auctionId: EMITTED_AUCTION_ID, config: {}, timeout: 3000}); + + expect(rivraddonsTrackPbjsEventStub.callCount).to.be.equal(0); + + window.rivraddon.analytics.getContext.restore(); + window.rivraddon.analytics.trackPbjsEvent.restore(); + }); + + it('Firing AUCTION_INIT should call rivraddon trackPbjsEvent passing the parameters', () => { + rivraddonsTrackPbjsEventStub = sandbox.stub(window.rivraddon.analytics, 'trackPbjsEvent'); + + expect(rivraddonsTrackPbjsEventStub.callCount).to.be.equal(0); + + events.emit(CONSTANTS.EVENTS.AUCTION_INIT, {auctionId: EMITTED_AUCTION_ID, config: {}, timeout: 3000}); + + expect(rivraddonsTrackPbjsEventStub.callCount).to.be.equal(1); + + const firstArgument = rivraddonsTrackPbjsEventStub.getCall(0).args[0]; + expect(firstArgument.eventType).to.be.equal(CONSTANTS.EVENTS.AUCTION_INIT); + expect(firstArgument.args.auctionId).to.be.equal(EMITTED_AUCTION_ID); + + window.rivraddon.analytics.trackPbjsEvent.restore(); + }); + + const BANNER_AD_UNITS_MOCK = [ + { + code: 'banner-container1', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 200], [300, 600]] + } + }, + bids: [ + { + bidder: 'appnexus', + params: { + placementId: '10433394', + reserve: 0.5 + } + }, + { + bidder: 'huddledmasses', + params: { + placement_id: 0 + } + }, + ] + } + ]; +}); diff --git a/test/spec/modules/rtbhouseBidAdapter_spec.js b/test/spec/modules/rtbhouseBidAdapter_spec.js index b1d20ebc203..707a5f91bec 100644 --- a/test/spec/modules/rtbhouseBidAdapter_spec.js +++ b/test/spec/modules/rtbhouseBidAdapter_spec.js @@ -1,23 +1,8 @@ import { expect } from 'chai'; -import { spec } from 'modules/rtbhouseBidAdapter'; +import { OPENRTB, spec } from 'modules/rtbhouseBidAdapter'; import { newBidder } from 'src/adapters/bidderFactory'; -const REGIONS = ['prebid-eu', 'prebid-us', 'prebid-asia']; -const ENDPOINT_URL = 'creativecdn.com/bidder/prebid/bids'; -const consentStr = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; -/** - * Helpers - */ - -function buildEndpointUrl(region) { - return 'https://' + region + '.' + ENDPOINT_URL; -} - -/** - * endof Helpers - */ - -describe('RTBHouseAdapter', function () { +describe('RTBHouseAdapter', () => { const adapter = newBidder(spec); describe('inherited functions', function () { @@ -57,37 +42,62 @@ describe('RTBHouseAdapter', function () { describe('buildRequests', function () { let bidRequests = [ { - 'bidder': 'rtbhouse', - 'params': { - 'publisherId': 'PREBID_TEST', - 'region': 'prebid-eu', - 'test': 1 - }, + 'bidder': 'rtbhouse', + 'params': { + 'publisherId': 'PREBID_TEST', + 'region': 'prebid-eu', + 'test': 1 + }, 'adUnitCode': 'adunit-code', 'sizes': [[300, 250], [300, 600]], 'bidId': '30b31c1838de1e', 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475' + 'auctionId': '1d1a030790a475', + 'transactionId': 'example-transaction-id', } ]; + const bidderRequest = { + 'refererInfo': { + 'numIframes': 0, + 'reachedTop': true, + 'referer': 'http://example.com', + 'stack': ['http://example.com'] + } + }; + + it('should build test param into the request', () => { + let builtTestRequest = spec.buildRequests(bidRequests, bidderRequest).data; + expect(JSON.parse(builtTestRequest).test).to.equal(1); + }); - it('should build test param into the request', function () { - let builtTestRequest = spec.buildRequests(bidRequests).data; - expect(JSON.parse(builtTestRequest).test).to.equal(1); + it('should build valid OpenRTB banner object', () => { + const request = JSON.parse(spec.buildRequests(bidRequests, bidderRequest).data); + const imp = request.imp[0]; + expect(imp.banner).to.deep.equal({ + w: 300, + h: 250, + format: [{ + w: 300, + h: 250 + }, { + w: 300, + h: 600 + }] + }) }); it('sends bid request to ENDPOINT via POST', function () { let bidRequest = Object.assign([], bidRequests); delete bidRequest[0].params.test; - const request = spec.buildRequests(bidRequest); - expect(request.url).to.equal(buildEndpointUrl(bidRequest[0].params.region)); + const request = spec.buildRequests(bidRequest, bidderRequest); + expect(request.url).to.equal('https://prebid-eu.creativecdn.com/bidder/prebid/bids'); expect(request.method).to.equal('POST'); }); it('should not populate GDPR if for non-EEA users', function () { let bidRequest = Object.assign([], bidRequests); delete bidRequest[0].params.test; - const request = spec.buildRequests(bidRequest); + const request = spec.buildRequests(bidRequest, bidderRequest); let data = JSON.parse(request.data); expect(data).to.not.have.property('regs'); expect(data).to.not.have.property('user'); @@ -96,7 +106,15 @@ describe('RTBHouseAdapter', function () { it('should populate GDPR and consent string if available for EEA users', function () { let bidRequest = Object.assign([], bidRequests); delete bidRequest[0].params.test; - const request = spec.buildRequests(bidRequest, {gdprConsent: {gdprApplies: true, consentString: consentStr}}); + const request = spec.buildRequests( + bidRequest, + Object.assign({}, bidderRequest, { + gdprConsent: { + gdprApplies: true, + consentString: 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A==' + } + }) + ); let data = JSON.parse(request.data); expect(data.regs.ext.gdpr).to.equal(1); expect(data.user.ext.consent).to.equal('BOJ8RZsOJ8RZsABAB8AAAAAZ-A'); @@ -105,24 +123,236 @@ describe('RTBHouseAdapter', function () { it('should populate GDPR and empty consent string if available for EEA users without consent string but with consent', function () { let bidRequest = Object.assign([], bidRequests); delete bidRequest[0].params.test; - const request = spec.buildRequests(bidRequest, {gdprConsent: {gdprApplies: true}}); + const request = spec.buildRequests( + bidRequest, + Object.assign({}, bidderRequest, { + gdprConsent: { + gdprApplies: true + } + }) + ); let data = JSON.parse(request.data); expect(data.regs.ext.gdpr).to.equal(1); expect(data.user.ext.consent).to.equal(''); }); + + it('should include banner imp in request', () => { + const bidRequest = Object.assign([], bidRequests); + const request = spec.buildRequests(bidRequest, bidderRequest); + const data = JSON.parse(request.data); + expect(data.imp[0].banner).to.not.be.empty; + }); + + it('should include source.tid in request', () => { + const bidRequest = Object.assign([], bidRequests); + const request = spec.buildRequests(bidRequest, bidderRequest); + const data = JSON.parse(request.data); + expect(data.source.tid).to.equal('example-transaction-id'); + }); + + it('should include bidfloor in request if available', () => { + const bidRequest = Object.assign([], bidRequests); + bidRequest[0].params.bidfloor = 0.01; + const request = spec.buildRequests(bidRequest, bidderRequest); + const data = JSON.parse(request.data); + expect(data.imp[0].bidfloor).to.equal(0.01) + }); + + describe('native imp', () => { + function basicRequest(extension) { + return Object.assign({ + bidder: 'bidder', + adUnitCode: 'adunit-code', + bidId: '1', + params: { + publisherId: 'PREBID_TEST', + region: 'prebid-eu', + test: 1 + } + }, extension); + } + + function buildImp(request) { + const resultRequest = spec.buildRequests([request], bidderRequest); + return JSON.parse(resultRequest.data).imp[0]; + } + + it('should extract native params when single mediaType', () => { + const imp = buildImp(basicRequest({ + mediaType: 'native', + nativeParams: { + title: { + required: true, + len: 100 + } + } + })); + expect(imp.native.request.assets[0]).to.deep.equal({ + id: OPENRTB.NATIVE.ASSET_ID.TITLE, + required: 1, + title: { + len: 100 + } + }) + }); + + it('should extract native params when many mediaTypes', () => { + const imp = buildImp(basicRequest({ + mediaTypes: { + native: { + title: { + len: 100 + } + } + } + })); + expect(imp.native.request.assets[0]).to.deep.equal({ + id: OPENRTB.NATIVE.ASSET_ID.TITLE, + required: 0, + title: { + len: 100 + } + }) + }); + + it('should not contain banner in imp', () => { + const imp = buildImp(basicRequest({ + mediaTypes: { + native: { + title: { + required: true + } + } + } + })); + expect(imp.banner).to.be.undefined; + }); + + describe('image sizes', () => { + it('should parse single image size', () => { + const imp = buildImp(basicRequest({ + mediaTypes: { + native: { + image: { + sizes: [300, 250] + } + } + } + })); + expect(imp.native.request.assets[0]).to.deep.equal({ + id: OPENRTB.NATIVE.ASSET_ID.IMAGE, + required: 0, + img: { + w: 300, + h: 250, + type: OPENRTB.NATIVE.IMAGE_TYPE.MAIN, + } + }) + }); + + it('should parse multiple image sizes', () => { + const imp = buildImp(basicRequest({ + mediaTypes: { + native: { + image: { + sizes: [[300, 250], [100, 100]] + } + } + } + })); + expect(imp.native.request.assets[0]).to.deep.equal({ + id: OPENRTB.NATIVE.ASSET_ID.IMAGE, + required: 0, + img: { + w: 300, + h: 250, + type: OPENRTB.NATIVE.IMAGE_TYPE.MAIN, + } + }) + }) + }); + + it('should parse aspect ratios with min_width', () => { + const imp = buildImp(basicRequest({ + mediaTypes: { + native: { + icon: { + aspect_ratios: [{ + min_width: 300, + ratio_width: 2, + ratio_height: 3, + }] + } + } + } + })); + expect(imp.native.request.assets[0]).to.deep.equal({ + id: OPENRTB.NATIVE.ASSET_ID.ICON, + required: 0, + img: { + type: OPENRTB.NATIVE.IMAGE_TYPE.ICON, + wmin: 300, + hmin: 450, + } + }) + }); + + it('should parse aspect ratios without min_width', () => { + const imp = buildImp(basicRequest({ + mediaTypes: { + native: { + icon: { + aspect_ratios: [{ + ratio_width: 2, + ratio_height: 3, + }] + } + } + } + })); + expect(imp.native.request.assets[0]).to.deep.equal({ + id: OPENRTB.NATIVE.ASSET_ID.ICON, + required: 0, + img: { + type: OPENRTB.NATIVE.IMAGE_TYPE.ICON, + wmin: 100, + hmin: 150, + } + }) + }); + + it('should handle all native assets', () => { + const imp = buildImp(basicRequest({ + mediaTypes: { + native: { + title: {}, + image: {}, + icon: {}, + sponsoredBy: {}, + body: {}, + cta: {}, + } + } + })); + expect(imp.native.request.assets.length).to.equal(6); + imp.native.request.assets.forEach(asset => { + expect(asset.id).to.be.at.least(1) + }) + }); + }); }); describe('interpretResponse', function () { let response = [{ - 'id': 'bidder_imp_identifier', - 'impid': '552b8922e28f27', - 'price': 0.5, - 'adid': 'Ad_Identifier', - 'adm': '', - 'adomain': ['rtbhouse.com'], - 'cid': 'Ad_Identifier', - 'w': 300, - 'h': 250 + 'id': 'bidder_imp_identifier', + 'impid': '552b8922e28f27', + 'price': 0.5, + 'adid': 'Ad_Identifier', + 'adm': '', + 'adomain': ['rtbhouse.com'], + 'cid': 'Ad_Identifier', + 'w': 300, + 'h': 250 }]; it('should get correct bid response', function () { @@ -141,15 +371,76 @@ describe('RTBHouseAdapter', function () { } ]; let bidderRequest; - let result = spec.interpretResponse({ body: response }, {bidderRequest}); + let result = spec.interpretResponse({body: response}, {bidderRequest}); expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); }); it('handles nobid responses', function () { let response = ''; let bidderRequest; - let result = spec.interpretResponse({ body: response }, {bidderRequest}); + let result = spec.interpretResponse({body: response}, {bidderRequest}); expect(result.length).to.equal(0); }); + + describe('native', () => { + const adm = { + native: { + ver: 1.1, + link: { + url: 'http://example.com' + }, + imptrackers: [ + 'http://example.com/imptracker' + ], + assets: [{ + id: OPENRTB.NATIVE.ASSET_ID.TITLE, + required: 1, + title: { + text: 'Title text' + } + }, { + id: OPENRTB.NATIVE.ASSET_ID.IMAGE, + required: 1, + img: { + url: 'http://example.com/image.jpg', + w: 150, + h: 50 + } + }, { + id: OPENRTB.NATIVE.ASSET_ID.BODY, + required: 0, + data: { + value: 'Body text' + } + }], + } + }; + const response = [{ + 'id': 'id', + 'impid': 'impid', + 'price': 1, + 'adid': 'adid', + 'adm': JSON.stringify(adm), + 'adomain': ['rtbhouse.com'], + 'cid': 'cid', + 'w': 1, + 'h': 1 + }]; + + it('should contain native assets in valid format', () => { + const bids = spec.interpretResponse({body: response}, {}); + expect(bids[0].native).to.deep.equal({ + title: 'Title text', + clickUrl: encodeURIComponent('http://example.com'), + impressionTrackers: ['http://example.com/imptracker'], + image: { + url: encodeURIComponent('http://example.com/image.jpg'), + width: 150, + height: 50 + }, + body: 'Body text' + }); + }); + }); }); }); diff --git a/test/spec/modules/rubiconAnalyticsAdapter_spec.js b/test/spec/modules/rubiconAnalyticsAdapter_spec.js index fa64513730a..dd34245bd8e 100644 --- a/test/spec/modules/rubiconAnalyticsAdapter_spec.js +++ b/test/spec/modules/rubiconAnalyticsAdapter_spec.js @@ -1,7 +1,12 @@ -import rubiconAnalyticsAdapter, { SEND_TIMEOUT } from 'modules/rubiconAnalyticsAdapter'; +import rubiconAnalyticsAdapter, { SEND_TIMEOUT, parseBidResponse } from 'modules/rubiconAnalyticsAdapter'; import CONSTANTS from 'src/constants.json'; import { config } from 'src/config'; +import { + setConfig, + addBidResponseHook, +} from 'modules/currency'; + let Ajv = require('ajv'); let schema = require('./rubiconAnalyticsSchema.json'); let ajv = new Ajv({ @@ -40,7 +45,7 @@ const BID = { 'mediaType': 'video', 'statusMessage': 'Bid available', 'bidId': '2ecff0db240757', - 'adId': '2ecff0db240757', + 'adId': 'fake_ad_id', 'source': 'client', 'requestId': '2ecff0db240757', 'currency': 'USD', @@ -80,13 +85,14 @@ const BID = { const BID2 = Object.assign({}, BID, { adUnitCode: '/19968336/header-bid-tag1', bidId: '3bd4ebb1c900e2', - adId: '3bd4ebb1c900e2', + adId: 'fake_ad_id', requestId: '3bd4ebb1c900e2', width: 728, height: 90, mediaType: 'banner', cpm: 1.52, source: 'server', + seatBidId: 'aaaa-bbbb-cccc-dddd', rubiconTargeting: { 'rpfl_elemid': '/19968336/header-bid-tag1', 'rpfl_14062': '2_tier0100' @@ -106,9 +112,58 @@ const MOCK = { [BID2.adUnitCode]: BID2.adserverTargeting }, AUCTION_INIT: { - 'timestamp': 1519767010567, 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa', - 'timeout': 3000 + 'timestamp': 1519767010567, + 'auctionStatus': 'inProgress', + 'adUnits': [ { + 'code': '/19968336/header-bid-tag1', + 'sizes': [[640, 480]], + 'bids': [ { + 'bidder': 'rubicon', + 'params': { + 'accountId': 1001, 'siteId': 113932, 'zoneId': 535512 + } + } ], + 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014' + } + ], + 'adUnitCodes': ['/19968336/header-bid-tag1'], + 'bidderRequests': [ { + 'bidderCode': 'rubicon', + 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa', + 'bidderRequestId': '1be65d7958826a', + 'bids': [ { + 'bidder': 'rubicon', + 'params': { + 'accountId': 1001, 'siteId': 113932, 'zoneId': 535512 + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[640, 480]] + } + }, + 'adUnitCode': '/19968336/header-bid-tag1', + 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014', + 'sizes': [[640, 480]], + 'bidId': '2ecff0db240757', + 'bidderRequestId': '1be65d7958826a', + 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa', + 'src': 'client', + 'bidRequestsCount': 1 + } + ], + 'timeout': 3000, + 'refererInfo': { + 'referer': 'http://www.test.com/page.html', 'reachedTop': true, 'numIframes': 0, 'stack': ['http://www.test.com/page.html'] + } + } + ], + 'bidsReceived': [], + 'winningBids': [], + 'timeout': 3000, + 'config': { + 'accountId': 1001, 'endpoint': '//localhost:9999/event' + } }, BID_REQUESTED: { 'bidder': 'rubicon', @@ -118,7 +173,7 @@ const MOCK = { { 'bidder': 'rubicon', 'params': { - 'accountId': '14062', + 'accountId': '1001', 'siteId': '70608', 'zoneId': '335918', 'userId': '12346', @@ -168,6 +223,7 @@ const MOCK = { 'transactionId': 'c116413c-9e3f-401a-bee1-d56aec29a1d4', 'sizes': [[1000, 300], [970, 250], [728, 90]], 'bidId': '3bd4ebb1c900e2', + 'seatBidId': 'aaaa-bbbb-cccc-dddd', 'bidderRequestId': '1be65d7958826a', 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa' } @@ -236,6 +292,9 @@ const ANALYTICS_MESSAGE = { } ], 'status': 'success', + 'accountId': 1001, + 'siteId': 70608, + 'zoneId': 335918, 'adserverTargeting': { 'hb_bidder': 'rubicon', 'hb_adid': '2ecff0db240757', @@ -251,7 +310,7 @@ const ANALYTICS_MESSAGE = { 'source': 'client', 'clientLatencyMillis': 3214, 'params': { - 'accountId': '14062', + 'accountId': '1001', 'siteId': '70608', 'zoneId': '335918' }, @@ -297,7 +356,7 @@ const ANALYTICS_MESSAGE = { 'bids': [ { 'bidder': 'rubicon', - 'bidId': '3bd4ebb1c900e2', + 'bidId': 'aaaa-bbbb-cccc-dddd', 'status': 'success', 'source': 'server', 'clientLatencyMillis': 3214, @@ -332,8 +391,10 @@ const ANALYTICS_MESSAGE = { 'clientLatencyMillis': 3214, 'samplingFactor': 1, 'accountId': 1001, + 'siteId': 70608, + 'zoneId': 335918, 'params': { - 'accountId': '14062', + 'accountId': '1001', 'siteId': '70608', 'zoneId': '335918' }, @@ -362,7 +423,7 @@ const ANALYTICS_MESSAGE = { 'bidder': 'rubicon', 'transactionId': 'c116413c-9e3f-401a-bee1-d56aec29a1d4', 'adUnitCode': '/19968336/header-bid-tag1', - 'bidId': '3bd4ebb1c900e2', + 'bidId': 'aaaa-bbbb-cccc-dddd', 'status': 'success', 'source': 'server', 'clientLatencyMillis': 3214, @@ -394,7 +455,8 @@ const ANALYTICS_MESSAGE = { }, 'bidwonStatus': 'success' } - ] + ], + 'wrapperName': '10000_fakewrapper_test' }; function performStandardAuction() { @@ -433,6 +495,9 @@ describe('rubicon analytics adapter', function () { s2sConfig: { timeout: 1000, accountId: 10000, + }, + rubicon: { + wrapperName: '10000_fakewrapper_test' } }) }); @@ -571,7 +636,7 @@ describe('rubicon analytics adapter', function () { rubiconAnalyticsAdapter.enableAnalytics({ options: { endpoint: '//localhost:9999/event', - accountId: '1001' + accountId: 1001 } }); }); @@ -594,6 +659,56 @@ describe('rubicon analytics adapter', function () { expect(message).to.deep.equal(ANALYTICS_MESSAGE); }); + it('should pick the highest cpm bid if more than one bid per bidRequestId', function () { + // Only want one bid request in our mock auction + let bidRequested = utils.deepClone(MOCK.BID_REQUESTED); + bidRequested.bids.shift(); + let auctionInit = utils.deepClone(MOCK.AUCTION_INIT); + auctionInit.adUnits.shift(); + + // clone the mock bidResponse and duplicate + let duplicateResponse1 = utils.deepClone(BID2); + duplicateResponse1.cpm = 1.0; + duplicateResponse1.adserverTargeting.hb_pb = '1.0'; + duplicateResponse1.adserverTargeting.hb_adid = '1111'; + let duplicateResponse2 = utils.deepClone(BID2); + duplicateResponse2.cpm = 5.5; + duplicateResponse2.adserverTargeting.hb_pb = '5.5'; + duplicateResponse2.adserverTargeting.hb_adid = '5555'; + let duplicateResponse3 = utils.deepClone(BID2); + duplicateResponse3.cpm = 0.1; + duplicateResponse3.adserverTargeting.hb_pb = '0.1'; + duplicateResponse3.adserverTargeting.hb_adid = '3333'; + + const setTargeting = { + [duplicateResponse2.adUnitCode]: duplicateResponse2.adserverTargeting + }; + + const bidWon = Object.assign({}, duplicateResponse2, { + 'status': 'rendered' + }); + + // spoof the auction with just our duplicates + events.emit(AUCTION_INIT, auctionInit); + events.emit(BID_REQUESTED, bidRequested); + events.emit(BID_RESPONSE, duplicateResponse1); + events.emit(BID_RESPONSE, duplicateResponse2); + events.emit(BID_RESPONSE, duplicateResponse3); + events.emit(AUCTION_END, MOCK.AUCTION_END); + events.emit(SET_TARGETING, setTargeting); + events.emit(BID_WON, bidWon); + + let message = JSON.parse(requests[0].requestBody); + validate(message); + expect(message.auctions[0].adUnits[0].bids[0].bidResponse.bidPriceUSD).to.equal(5.5); + expect(message.auctions[0].adUnits[0].adserverTargeting.hb_pb).to.equal('5.5'); + expect(message.auctions[0].adUnits[0].adserverTargeting.hb_adid).to.equal('5555'); + expect(message.bidsWon.length).to.equal(1); + expect(message.bidsWon[0].bidResponse.bidPriceUSD).to.equal(5.5); + expect(message.bidsWon[0].adserverTargeting.hb_pb).to.equal('5.5'); + expect(message.bidsWon[0].adserverTargeting.hb_adid).to.equal('5555'); + }); + it('should send batched message without BID_WON if necessary and further BID_WON events individually', function () { events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); @@ -640,5 +755,60 @@ describe('rubicon analytics adapter', function () { expect(timedOutBid.error.code).to.equal('timeout-error'); expect(timedOutBid).to.not.have.property('bidResponse'); }); + + it('should successfully convert bid price to USD in parseBidResponse', function () { + // Set the rates + setConfig({ + adServerCurrency: 'JPY', + rates: { + USD: { + JPY: 100 + } + } + }); + + // set our bid response to JPY + const bidCopy = utils.deepClone(BID2); + bidCopy.currency = 'JPY'; + bidCopy.cpm = 100; + + // Now add the bidResponse hook which hooks on the currenct conversion function onto the bid response + let innerBid; + addBidResponseHook(function(adCodeId, bid) { + innerBid = bid; + }, 'elementId', bidCopy); + + // Use the rubi analytics parseBidResponse Function to get the resulting cpm from the bid response! + const bidResponseObj = parseBidResponse(innerBid); + expect(bidResponseObj).to.have.property('bidPriceUSD'); + expect(bidResponseObj.bidPriceUSD).to.equal(1.0); + }); + }); + + describe('config with integration type', () => { + it('should use the integration type provided in the config instead of the default', () => { + sandbox.stub(config, 'getConfig').callsFake(function (key) { + const config = { + 'rubicon.int_type': 'testType' + }; + return config[key]; + }); + + rubiconAnalyticsAdapter.enableAnalytics({ + options: { + endpoint: '//localhost:9999/event', + accountId: 1001 + } + }); + + performStandardAuction(); + + expect(requests.length).to.equal(1); + const request = requests[0]; + const message = JSON.parse(request.requestBody); + expect(message.integration).to.equal('testType'); + + rubiconAnalyticsAdapter.disableAnalytics(); + }); }); }); diff --git a/test/spec/modules/rubiconAnalyticsSchema.json b/test/spec/modules/rubiconAnalyticsSchema.json index cc4ad20db19..686aced840f 100644 --- a/test/spec/modules/rubiconAnalyticsSchema.json +++ b/test/spec/modules/rubiconAnalyticsSchema.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "http://json-schema.org/draft-06/schema#", "title": "Prebid Auctions", "description": "A batched data object describing the lifecycle of an auction or multiple auction across a single page view.", "type": "object", @@ -113,6 +113,18 @@ "items": { "$ref": "#/definitions/bid" } + }, + "accountId": { + "type": "number", + "description": "The Rubicon AccountId associated with this adUnit - Removed if null" + }, + "siteId": { + "type": "number", + "description": "The Rubicon siteId associated with this adUnit - Removed if null" + }, + "zoneId": { + "type": "number", + "description": "The Rubicon zoneId associated with this adUnit - Removed if null" } } } @@ -166,11 +178,22 @@ "success", "error" ] + }, + "siteId": { + "type": "number", + "description": "The Rubicon siteId associated with this adUnit - Removed if null" + }, + "zoneId": { + "type": "number", + "description": "The Rubicon zoneId associated with this adUnit - Removed if null" } } } ] } + }, + "wrapperName": { + "type": "string" } }, "definitions": { diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index 3afb424c824..0390d4598ae 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -1,6 +1,6 @@ import {expect} from 'chai'; -import adapterManager from 'src/adaptermanager'; -import {spec, masSizeOrdering, resetUserSync, hasVideoMediaType} from 'modules/rubiconBidAdapter'; +import adapterManager from 'src/adapterManager'; +import {spec, getPriceGranularity, masSizeOrdering, resetUserSync, hasVideoMediaType, FASTLANE_ENDPOINT} from 'modules/rubiconBidAdapter'; import {parse as parseQuery} from 'querystring'; import {newBidder} from 'src/adapters/bidderFactory'; import {userSync} from 'src/userSync'; @@ -149,40 +149,28 @@ describe('the rubicon adapter', function () { let bid = bidderRequest.bids[0]; bid.mediaTypes = { video: { - context: 'instream' + context: 'instream', + mimes: ['video/mp4', 'video/x-flv'], + api: [2], + minduration: 15, + playerSize: [640, 480], + maxduration: 30, + startdelay: 0, + playbackmethod: [2], + linearity: 1, + skip: 1, + skipafter: 15, + pos: 1, + protocols: [1, 2, 3, 4, 5, 6] } }; bid.params.video = { 'language': 'en', - 'p_aso.video.ext.skip': true, - 'p_aso.video.ext.skipdelay': 15, - 'playerHeight': 320, + 'skip': 1, + 'skipafter': 15, + 'playerHeight': 480, 'playerWidth': 640, 'size_id': 201, - 'aeParams': { - 'p_aso.video.ext.skip': '1', - 'p_aso.video.ext.skipdelay': '15' - } - }; - } - - function createLegacyVideoBidderRequest() { - createGdprBidderRequest(true); - - let bid = bidderRequest.bids[0]; - // Legacy property (Prebid <1.0) - bid.mediaType = 'video'; - bid.params.video = { - 'language': 'en', - 'p_aso.video.ext.skip': true, - 'p_aso.video.ext.skipdelay': 15, - 'playerHeight': 320, - 'playerWidth': 640, - 'size_id': 201, - 'aeParams': { - 'p_aso.video.ext.skip': '1', - 'p_aso.video.ext.skipdelay': '15' - } }; } @@ -196,64 +184,35 @@ describe('the rubicon adapter', function () { bid.params.video = ''; } - function createLegacyVideoBidderRequestNoVideo() { - let bid = bidderRequest.bids[0]; - bid.mediaType = 'video'; - bid.params.video = ''; - } - function createVideoBidderRequestOutstream() { let bid = bidderRequest.bids[0]; bid.mediaTypes = { video: { - context: 'outstream' + context: 'outstream', + mimes: ['video/mp4', 'video/x-flv'], + api: [2], + minduration: 15, + playerSize: [640, 480], + maxduration: 30, + startdelay: 0, + playbackmethod: [2], + linearity: 1, + skip: 1, + skipafter: 15, + pos: 1, + protocols: [1, 2, 3, 4, 5, 6] }, }; + bid.params.accountId = 14062; + bid.params.siteId = 70608; + bid.params.zoneId = 335918; bid.params.video = { 'language': 'en', - 'p_aso.video.ext.skip': true, - 'p_aso.video.ext.skipdelay': 15, + 'skip': 1, + 'skipafter': 15, 'playerHeight': 320, 'playerWidth': 640, - 'size_id': 203, - 'aeParams': { - 'p_aso.video.ext.skip': '1', - 'p_aso.video.ext.skipdelay': '15' - } - }; - } - - function createVideoBidderRequestNoPlayer() { - let bid = bidderRequest.bids[0]; - bid.mediaTypes = { - video: { - context: 'instream' - }, - }; - bid.params.video = { - 'language': 'en', - 'p_aso.video.ext.skip': true, - 'p_aso.video.ext.skipdelay': 15, - 'size_id': 201, - 'aeParams': { - 'p_aso.video.ext.skip': '1', - 'p_aso.video.ext.skipdelay': '15' - } - }; - } - - function createLegacyVideoBidderRequestNoPlayer() { - let bid = bidderRequest.bids[0]; - bid.mediaType = 'video'; - bid.params.video = { - 'language': 'en', - 'p_aso.video.ext.skip': true, - 'p_aso.video.ext.skipdelay': 15, - 'size_id': 201, - 'aeParams': { - 'p_aso.video.ext.skip': '1', - 'p_aso.video.ext.skipdelay': '15' - } + 'size_id': 203 }; } @@ -274,7 +233,7 @@ describe('the rubicon adapter', function () { userId: '12346', keywords: ['a', 'b', 'c'], inventory: { - rating: '5-star', + rating: '5-star', // This actually should not be sent to frank!! causes 400 prodtype: ['tech', 'mobile'] }, visitor: { @@ -394,6 +353,64 @@ describe('the rubicon adapter', function () { }); }); + 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; + + let [request] = spec.buildRequests(noposRequest.bids, noposRequest); + let data = parseQuery(request.data); + + expect(data['site_id']).to.equal('70608'); + expect(data['p_pos']).to.equal(undefined); + }); + + it('should not send p_pos to AE if not params.position is invalid', function() { + var badposRequest = utils.deepClone(bidderRequest); + badposRequest.bids[0].params.position = 'bad'; + + let [request] = spec.buildRequests(badposRequest.bids, badposRequest); + let data = parseQuery(request.data); + + expect(data['site_id']).to.equal('70608'); + expect(data['p_pos']).to.equal(undefined); + }); + + it('should correctly send p_pos in sra fashion', function() { + sandbox.stub(config, 'getConfig').callsFake((key) => { + const config = { + 'rubicon.singleRequest': true + }; + return config[key]; + }); + // first one is atf + var sraPosRequest = utils.deepClone(bidderRequest); + + // second is not present + const bidCopy = utils.deepClone(sraPosRequest.bids[0]); + delete bidCopy.params.position; + sraPosRequest.bids.push(bidCopy); + + // third is btf + const bidCopy1 = utils.deepClone(sraPosRequest.bids[0]); + bidCopy1.params.position = 'btf'; + sraPosRequest.bids.push(bidCopy1); + + // fourth is invalid (aka not atf or btf) + const bidCopy2 = utils.deepClone(sraPosRequest.bids[0]); + bidCopy2.params.position = 'unknown'; + sraPosRequest.bids.push(bidCopy2); + + // fifth is not present + const bidCopy3 = utils.deepClone(sraPosRequest.bids[0]); + delete bidCopy3.params.position; + sraPosRequest.bids.push(bidCopy3); + + let [request] = spec.buildRequests(sraPosRequest.bids, sraPosRequest); + let data = parseQuery(request.data); + + expect(data['p_pos']).to.equal('atf;;btf;;'); + }); + it('ad engine query params should be ordered correctly', function () { sandbox.stub(Math, 'random').callsFake(() => 0.1); let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); @@ -434,8 +451,8 @@ describe('the rubicon adapter', function () { sandbox.stub(Math, 'random').callsFake(() => 0.1); delete bidderRequest.bids[0].params.latLong; - [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - data = parseQuery(request.data); + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + let data = parseQuery(request.data); expect(request.url).to.equal('//fastlane.rubiconproject.com/a/api/fastlane.json'); @@ -450,8 +467,8 @@ describe('the rubicon adapter', function () { }); bidderRequest.bids[0].params.latLong = []; - let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - let data = parseQuery(request.data); + [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + data = parseQuery(request.data); expect(request.url).to.equal('//fastlane.rubiconproject.com/a/api/fastlane.json'); @@ -466,13 +483,32 @@ describe('the rubicon adapter', function () { }); }); - it('page_url should use params.referrer, config.getConfig("pageUrl"), utils.getTopWindowUrl() in that order', function () { - sandbox.stub(utils, 'getTopWindowUrl').callsFake(() => 'http://www.prebid.org'); + it('should add referer info to request data', function () { + let refererInfo = { + referer: 'http://www.prebid.org', + reachedTop: true, + numIframes: 1, + stack: [ + 'http://www.prebid.org/page.html', + 'http://www.prebid.org/iframe1.html', + ] + }; + bidderRequest = Object.assign({refererInfo}, bidderRequest); + delete bidderRequest.bids[0].params.referrer; + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + + expect(parseQuery(request.data).rf).to.exist; + expect(parseQuery(request.data).rf).to.equal('http://www.prebid.org'); + }); + + it('page_url should use params.referrer, config.getConfig("pageUrl"), bidderRequest.refererInfo in that order', function () { let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); expect(parseQuery(request.data).rf).to.equal('localhost'); delete bidderRequest.bids[0].params.referrer; + let refererInfo = { referer: 'http://www.prebid.org' }; + bidderRequest = Object.assign({refererInfo}, bidderRequest); [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); expect(parseQuery(request.data).rf).to.equal('http://www.prebid.org'); @@ -492,7 +528,7 @@ describe('the rubicon adapter', function () { }); it('should use rubicon sizes if present (including non-mappable sizes)', function () { - var sizesBidderRequest = clone(bidderRequest); + var sizesBidderRequest = utils.deepClone(bidderRequest); sizesBidderRequest.bids[0].params.sizes = [55, 57, 59, 801]; let [request] = spec.buildRequests(sizesBidderRequest.bids, sizesBidderRequest); @@ -503,7 +539,7 @@ describe('the rubicon adapter', function () { }); it('should not validate bid request if no valid sizes', function () { - var sizesBidderRequest = clone(bidderRequest); + var sizesBidderRequest = utils.deepClone(bidderRequest); sizesBidderRequest.bids[0].sizes = [[621, 250], [300, 251]]; let result = spec.isBidRequestValid(sizesBidderRequest.bids[0]); @@ -512,7 +548,7 @@ describe('the rubicon adapter', function () { }); it('should not validate bid request if no account id is present', function () { - var noAccountBidderRequest = clone(bidderRequest); + var noAccountBidderRequest = utils.deepClone(bidderRequest); delete noAccountBidderRequest.bids[0].params.accountId; let result = spec.isBidRequestValid(noAccountBidderRequest.bids[0]); @@ -521,7 +557,7 @@ describe('the rubicon adapter', function () { }); it('should allow a floor override', function () { - var floorBidderRequest = clone(bidderRequest); + var floorBidderRequest = utils.deepClone(bidderRequest); floorBidderRequest.bids[0].params.floor = 2; let [request] = spec.buildRequests(floorBidderRequest.bids, floorBidderRequest); @@ -635,7 +671,6 @@ describe('the rubicon adapter', function () { }); describe('digiTrustId config', function () { - var origGetConfig; beforeEach(function () { window.DigiTrust = { getUser: sandbox.spy() @@ -798,6 +833,72 @@ describe('the rubicon adapter', function () { }); }); + describe('first party data', function () { + it('should not have any tg_v or tg_i params if all are undefined', function () { + let params = { + inventory: { + rating: null, + prodtype: undefined + }, + visitor: { + ucat: undefined, + lastsearch: null, + likes: undefined + }, + }; + + // Overwrite the bidder request params with the above ones + Object.assign(bidderRequest.bids[0].params, params); + + // get the built request + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + let data = parseQuery(request.data); + + // make sure that no tg_v or tg_i keys are present in the request + let matchingExp = RegExp('^tg_(i|v)\..*$') + Object.keys(data).forEach(key => { + expect(key).to.not.match(matchingExp); + }); + }); + + it('should contain valid params when some are undefined', function () { + let params = { + inventory: { + rating: undefined, + prodtype: ['tech', 'mobile'] + }, + visitor: { + ucat: null, + lastsearch: 'iphone', + likes: undefined + }, + }; + let undefinedKeys = ['tg_i.rating', 'tg_v.ucat', 'tg_v.likes'] + let expectedQuery = { + 'tg_v.lastsearch': 'iphone', + 'tg_i.prodtype': 'tech,mobile', + } + + // Overwrite the bidder request params with the above ones + Object.assign(bidderRequest.bids[0].params, params); + + // get the built request + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + let data = parseQuery(request.data); + + // make sure none of the undefined keys are in query + undefinedKeys.forEach(key => { + expect(typeof data[key]).to.equal('undefined'); + }); + + // make sure the expected and defined ones do show up still + Object.keys(expectedQuery).forEach(key => { + let value = expectedQuery[key]; + expect(data[key]).to.equal(value); + }); + }); + }); + describe('singleRequest config', function () { it('should group all bid requests with the same site id', function () { sandbox.stub(Math, 'random').callsFake(() => 0.1); @@ -833,17 +934,17 @@ describe('the rubicon adapter', function () { 'rf': 'localhost' }; - const bidCopy = clone(bidderRequest.bids[0]); + const bidCopy = utils.deepClone(bidderRequest.bids[0]); bidCopy.params.siteId = '70608'; bidCopy.params.zoneId = '1111'; bidderRequest.bids.push(bidCopy); - const bidCopy2 = clone(bidderRequest.bids[0]); + const bidCopy2 = utils.deepClone(bidderRequest.bids[0]); bidCopy2.params.siteId = '99999'; bidCopy2.params.zoneId = '2222'; bidderRequest.bids.push(bidCopy2); - const bidCopy3 = clone(bidderRequest.bids[0]); + const bidCopy3 = utils.deepClone(bidderRequest.bids[0]); bidCopy3.params.siteId = '99999'; bidCopy3.params.zoneId = '3333'; bidderRequest.bids.push(bidCopy3); @@ -914,7 +1015,7 @@ describe('the rubicon adapter', function () { }); }); - it('should not send more than 10 bids in a request', function () { + it('should not send more than 10 bids in a request (split into separate requests with <= 10 bids each)', function () { sandbox.stub(config, 'getConfig').callsFake((key) => { const config = { 'rubicon.singleRequest': true @@ -922,27 +1023,44 @@ describe('the rubicon adapter', function () { return config[key]; }); - for (let i = 0; i < 20; i++) { - let bidCopy = clone(bidderRequest.bids[0]); + let serverRequests; + let data; + + // TEST '10' BIDS, add 9 to 1 existing bid + for (let i = 0; i < 9; i++) { + let bidCopy = utils.deepClone(bidderRequest.bids[0]); bidCopy.params.zoneId = `${i}0000`; bidderRequest.bids.push(bidCopy); } - - const serverRequests = spec.buildRequests(bidderRequest.bids, bidderRequest); - - // if bids are greater than 10, additional bids are dropped + serverRequests = spec.buildRequests(bidderRequest.bids, bidderRequest); + // '10' bids per SRA request: so there should be 1 request + expect(serverRequests.length).to.equal(1); + // and that one request should have data from 10 bids expect(serverRequests[0].bidRequest).to.have.lengthOf(10); - // check that slots param value matches - const foundSlotsCount = serverRequests[0].data.indexOf('&slots=10&'); - expect(foundSlotsCount !== -1).to.equal(true); - + expect(serverRequests[0].data.indexOf('&slots=10&') !== -1).to.equal(true); // check that zone_id has 10 values (since all zone_ids are unique all should exist in get param) - const data = parseQuery(serverRequests[0].data); - + data = parseQuery(serverRequests[0].data); expect(data).to.be.a('object'); expect(data).to.have.property('zone_id'); expect(data.zone_id.split(';')).to.have.lengthOf(10); + + // TEST '100' BIDS, add 90 to the previously added 10 + for (let i = 0; i < 90; i++) { + let bidCopy = utils.deepClone(bidderRequest.bids[0]); + bidCopy.params.zoneId = `${(i + 10)}0000`; + bidderRequest.bids.push(bidCopy); + } + serverRequests = spec.buildRequests(bidderRequest.bids, bidderRequest); + // '100' bids: should be '10' SRA requests + expect(serverRequests.length).to.equal(10); + // check that each request has 10 items + serverRequests.forEach((serverRequest) => { + // and that one request should have data from 10 bids + expect(serverRequest.bidRequest).to.have.lengthOf(10); + // check that slots param value matches + expect(serverRequest.data.indexOf('&slots=10&') !== -1).to.equal(true); + }); }); it('should not group bid requests if singleRequest does not equal true', function () { @@ -953,14 +1071,14 @@ describe('the rubicon adapter', function () { return config[key]; }); - const bidCopy = clone(bidderRequest.bids[0]); + const bidCopy = utils.deepClone(bidderRequest.bids[0]); bidderRequest.bids.push(bidCopy); - const bidCopy2 = clone(bidderRequest.bids[0]); + const bidCopy2 = utils.deepClone(bidderRequest.bids[0]); bidCopy2.params.siteId = '32001'; bidderRequest.bids.push(bidCopy2); - const bidCopy3 = clone(bidderRequest.bids[0]); + const bidCopy3 = utils.deepClone(bidderRequest.bids[0]); bidCopy3.params.siteId = '32001'; bidderRequest.bids.push(bidCopy3); @@ -976,19 +1094,29 @@ describe('the rubicon adapter', function () { return config[key]; }); - const bidCopy = clone(bidderRequest.bids[0]); + const bidCopy = utils.deepClone(bidderRequest.bids[0]); bidderRequest.bids.push(bidCopy); - const bidCopy2 = clone(bidderRequest.bids[0]); + const bidCopy2 = utils.deepClone(bidderRequest.bids[0]); bidCopy2.params.siteId = '32001'; bidderRequest.bids.push(bidCopy2); - const bidCopy3 = clone(bidderRequest.bids[0]); + const bidCopy3 = utils.deepClone(bidderRequest.bids[0]); bidCopy3.params.siteId = '32001'; bidderRequest.bids.push(bidCopy3); - const bidCopy4 = clone(bidderRequest.bids[0]); - bidCopy4.mediaType = 'video'; + const bidCopy4 = utils.deepClone(bidderRequest.bids[0]); + bidCopy4.mediaTypes = { + video: { + context: 'instream', + playerSize: [640, 480], + mimes: ['video/mp4', 'video/x-ms-wmv'], + protocols: [2, 5], + maxduration: 30, + linearity: 1, + api: [2] + } + }; bidCopy4.params.video = { 'language': 'en', 'p_aso.video.ext.skip': true, @@ -1007,11 +1135,24 @@ describe('the rubicon adapter', function () { expect(serverRequests).that.is.an('array').of.length(3); }); }); + + describe('user id config', function() { + it('should send tpid_tdid when userId defines tdid', function () { + const clonedBid = utils.deepClone(bidderRequest.bids[0]); + clonedBid.userId = { + tdid: 'abcd-efgh-ijkl-mnop-1234' + }; + let [request] = spec.buildRequests([clonedBid], bidderRequest); + let data = parseQuery(request.data); + + expect(data['tpid_tdid']).to.equal('abcd-efgh-ijkl-mnop-1234'); + }); + }) }); describe('for video requests', function () { - it('should make a well-formed video request with legacy mediaType config', function () { - createLegacyVideoBidderRequest(); + it('should make a well-formed video request', function () { + createVideoBidderRequest(); sandbox.stub(Date, 'now').callsFake(() => bidderRequest.auctionStart + 100 @@ -1020,257 +1161,194 @@ describe('the rubicon adapter', function () { let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); let post = request.data; - let url = request.url; - - expect(url).to.equal('//fastlane-adv.rubiconproject.com/v1/auction/video'); - - expect(post).to.have.property('page_url').that.is.a('string'); - expect(post.resolution).to.match(/\d+x\d+/); - expect(post.account_id).to.equal('14062'); - expect(post.integration).to.equal(INTEGRATION); - expect(post['x_source.tid']).to.equal('d45dd707-a418-42ec-b8a7-b70a6c6fab0b'); - expect(post).to.have.property('timeout').that.is.a('number'); - expect(post.timeout < 5000).to.equal(true); - expect(post.stash_creatives).to.equal(true); - expect(post.gdpr_consent).to.equal('BOJ/P2HOJ/P2HABABMAAAAAZ+A=='); - expect(post.gdpr).to.equal(1); - - expect(post).to.have.property('ae_pass_through_parameters'); - expect(post.ae_pass_through_parameters) - .to.have.property('p_aso.video.ext.skip') - .that.equals('1'); - expect(post.ae_pass_through_parameters) - .to.have.property('p_aso.video.ext.skipdelay') - .that.equals('15'); - - expect(post).to.have.property('slots') - .with.length.of(1); - - let slot = post.slots[0]; - - expect(slot.site_id).to.equal('70608'); - expect(slot.zone_id).to.equal('335918'); - expect(slot.position).to.equal('atf'); - expect(slot.floor).to.equal(0.01); - expect(slot.element_id).to.equal(bidderRequest.bids[0].adUnitCode); - expect(slot.name).to.equal(bidderRequest.bids[0].adUnitCode); - expect(slot.language).to.equal('en'); - expect(slot.width).to.equal(640); - expect(slot.height).to.equal(320); - expect(slot.size_id).to.equal(201); - - expect(slot).to.have.property('inventory').that.is.an('object'); - expect(slot.inventory).to.have.property('rating').that.equals('5-star'); - expect(slot.inventory).to.have.property('prodtype').that.deep.equals(['tech', 'mobile']); - - expect(slot).to.have.property('keywords') - .that.is.an('array') - .of.length(3) - .that.deep.equals(['a', 'b', 'c']); - - expect(slot).to.have.property('visitor').that.is.an('object'); - expect(slot.visitor).to.have.property('ucat').that.equals('new'); - expect(slot.visitor).to.have.property('lastsearch').that.equals('iphone'); - expect(slot.visitor).to.have.property('likes').that.deep.equals(['sports', 'video games']); + expect(post).to.have.property('imp') + // .with.length.of(1); + let imp = post.imp[0]; + expect(imp.id).to.equal(bidderRequest.bids[0].adUnitCode); + expect(imp.exp).to.equal(300); + expect(imp.video.w).to.equal(640); + expect(imp.video.h).to.equal(480); + expect(imp.video.pos).to.equal(1); + expect(imp.video.context).to.equal('instream'); + expect(imp.video.minduration).to.equal(15); + expect(imp.video.maxduration).to.equal(30); + expect(imp.video.startdelay).to.equal(0); + expect(imp.video.skip).to.equal(1); + expect(imp.video.skipafter).to.equal(15); + expect(imp.ext.rubicon.video.playerWidth).to.equal(640); + expect(imp.ext.rubicon.video.playerHeight).to.equal(480); + expect(imp.ext.rubicon.video.size_id).to.equal(201); + expect(imp.ext.rubicon.video.language).to.equal('en'); + // Also want it to be in post.site.content.language + expect(post.site.content.language).to.equal('en'); + expect(imp.ext.rubicon.video.skip).to.equal(1); + expect(imp.ext.rubicon.video.skipafter).to.equal(15); + expect(post.user.ext.consent).to.equal('BOJ/P2HOJ/P2HABABMAAAAAZ+A=='); + expect(post.regs.ext.gdpr).to.equal(1); + expect(post).to.have.property('ext').that.is.an('object'); + expect(post.ext.prebid.targeting.includewinners).to.equal(true); + expect(post.ext.prebid).to.have.property('cache').that.is.an('object') + expect(post.ext.prebid.cache).to.have.property('vastxml').that.is.an('object') + expect(post.ext.prebid.cache.vastxml).to.have.property('returnCreative').that.is.an('boolean') + expect(post.ext.prebid.cache.vastxml.returnCreative).to.equal(false) }); - it('should make a well-formed video request', function () { + it('should send correct bidfloor to PBS', function() { createVideoBidderRequest(); - sandbox.stub(Date, 'now').callsFake(() => - bidderRequest.auctionStart + 100 - ); - + bidderRequest.bids[0].params.floor = 0.1; let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - let post = request.data; + expect(request.data.imp[0].bidfloor).to.equal(0.1); - let url = request.url; - - expect(url).to.equal('//fastlane-adv.rubiconproject.com/v1/auction/video'); - - expect(post).to.have.property('page_url').that.is.a('string'); - expect(post.resolution).to.match(/\d+x\d+/); - expect(post.account_id).to.equal('14062'); - expect(post.integration).to.equal(INTEGRATION); - expect(post['x_source.tid']).to.equal('d45dd707-a418-42ec-b8a7-b70a6c6fab0b'); - expect(post).to.have.property('timeout').that.is.a('number'); - expect(post.timeout < 5000).to.equal(true); - expect(post.stash_creatives).to.equal(true); - expect(post.gdpr_consent).to.equal('BOJ/P2HOJ/P2HABABMAAAAAZ+A=='); - expect(post.gdpr).to.equal(1); - - expect(post).to.have.property('ae_pass_through_parameters'); - expect(post.ae_pass_through_parameters) - .to.have.property('p_aso.video.ext.skip') - .that.equals('1'); - expect(post.ae_pass_through_parameters) - .to.have.property('p_aso.video.ext.skipdelay') - .that.equals('15'); - - expect(post).to.have.property('slots') - .with.length.of(1); - - let slot = post.slots[0]; - - expect(slot.site_id).to.equal('70608'); - expect(slot.zone_id).to.equal('335918'); - expect(slot.position).to.equal('atf'); - expect(slot.floor).to.equal(0.01); - expect(slot.element_id).to.equal(bidderRequest.bids[0].adUnitCode); - expect(slot.name).to.equal(bidderRequest.bids[0].adUnitCode); - expect(slot.language).to.equal('en'); - expect(slot.width).to.equal(640); - expect(slot.height).to.equal(320); - expect(slot.size_id).to.equal(201); - - expect(slot).to.have.property('inventory').that.is.an('object'); - expect(slot.inventory).to.have.property('rating').that.equals('5-star'); - expect(slot.inventory).to.have.property('prodtype').that.deep.equals(['tech', 'mobile']); - - expect(slot).to.have.property('keywords') - .that.is.an('array') - .of.length(3) - .that.deep.equals(['a', 'b', 'c']); - - expect(slot).to.have.property('visitor').that.is.an('object'); - expect(slot.visitor).to.have.property('ucat').that.equals('new'); - expect(slot.visitor).to.have.property('lastsearch').that.equals('iphone'); - expect(slot.visitor).to.have.property('likes').that.deep.equals(['sports', 'video games']); + bidderRequest.bids[0].params.floor = 5.5; + [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(request.data.imp[0].bidfloor).to.equal(5.5); + + bidderRequest.bids[0].params.floor = '1.7'; + [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(request.data.imp[0].bidfloor).to.equal(1.7); + + bidderRequest.bids[0].params.floor = 0; + [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(request.data.imp[0].bidfloor).to.equal(0); + + bidderRequest.bids[0].params.floor = undefined; + [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(request.data.imp[0]).to.not.haveOwnProperty('bidfloor'); + + bidderRequest.bids[0].params.floor = null; + [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(request.data.imp[0]).to.not.haveOwnProperty('bidfloor'); }); it('should send request with proper ad position', function () { createVideoBidderRequest(); - var positionBidderRequest = clone(bidderRequest); - positionBidderRequest.bids[0].params.position = 'atf'; + let positionBidderRequest = utils.deepClone(bidderRequest); + positionBidderRequest.bids[0].mediaTypes.video.pos = 1; let [request] = spec.buildRequests(positionBidderRequest.bids, positionBidderRequest); - let post = request.data; - let slot = post.slots[0]; + expect(request.data.imp[0].video.pos).to.equal(1); - expect(slot.position).to.equal('atf'); - - positionBidderRequest = clone(bidderRequest); - positionBidderRequest.bids[0].params.position = 'btf'; + positionBidderRequest = utils.deepClone(bidderRequest); + positionBidderRequest.bids[0].params.position = undefined; + positionBidderRequest.bids[0].mediaTypes.video.pos = undefined; [request] = spec.buildRequests(positionBidderRequest.bids, positionBidderRequest); - post = request.data; - slot = post.slots[0]; - - expect(slot.position).to.equal('btf'); + expect(request.data.imp[0].video.pos).to.equal(undefined); - positionBidderRequest = clone(bidderRequest); - positionBidderRequest.bids[0].params.position = 'unknown'; + positionBidderRequest = utils.deepClone(bidderRequest); + positionBidderRequest.bids[0].params.position = 'atf' + positionBidderRequest.bids[0].mediaTypes.video.pos = undefined; [request] = spec.buildRequests(positionBidderRequest.bids, positionBidderRequest); - post = request.data; - slot = post.slots[0]; + expect(request.data.imp[0].video.pos).to.equal(1); - expect(slot.position).to.equal('unknown'); - - positionBidderRequest = clone(bidderRequest); - positionBidderRequest.bids[0].params.position = '123'; + positionBidderRequest = utils.deepClone(bidderRequest); + positionBidderRequest.bids[0].params.position = 'btf'; + positionBidderRequest.bids[0].mediaTypes.video.pos = undefined; [request] = spec.buildRequests(positionBidderRequest.bids, positionBidderRequest); - post = request.data; - slot = post.slots[0]; + expect(request.data.imp[0].video.pos).to.equal(3); - expect(slot.position).to.equal('unknown'); - - positionBidderRequest = clone(bidderRequest); - delete positionBidderRequest.bids[0].params.position; - expect(positionBidderRequest.bids[0].params.position).to.equal(undefined); + positionBidderRequest = utils.deepClone(bidderRequest); + positionBidderRequest.bids[0].params.position = 'foobar'; + positionBidderRequest.bids[0].mediaTypes.video.pos = undefined; [request] = spec.buildRequests(positionBidderRequest.bids, positionBidderRequest); - post = request.data; - slot = post.slots[0]; - - expect(slot.position).to.equal('unknown'); + expect(request.data.imp[0].video.pos).to.equal(undefined); }); - it('should allow a floor price override', function () { - createVideoBidderRequest(); + it('should properly enforce video.context to be either instream or outstream', function () { + let bid = bidderRequest.bids[0]; + bid.mediaTypes = { + video: { + context: 'instream', + mimes: ['video/mp4', 'video/x-ms-wmv'], + protocols: [2, 5], + maxduration: 30, + linearity: 1, + api: [2] + } + } + bid.params.video = {}; sandbox.stub(Date, 'now').callsFake(() => bidderRequest.auctionStart + 100 ); - var floorBidderRequest = clone(bidderRequest); + const bidRequestCopy = utils.deepClone(bidderRequest.bids[0]); + expect(spec.isBidRequestValid(bidRequestCopy)).to.equal(true); - // enter an explicit floor price // - floorBidderRequest.bids[0].params.floor = 3.25; + // change context to outstream, still true + bidRequestCopy.mediaTypes.video.context = 'outstream'; + expect(spec.isBidRequestValid(bidRequestCopy)).to.equal(true); - let [request] = spec.buildRequests(floorBidderRequest.bids, floorBidderRequest); - let post = request.data; + // change context to random, false now + bidRequestCopy.mediaTypes.video.context = 'random'; + expect(spec.isBidRequestValid(bidRequestCopy)).to.equal(false); - let floor = post.slots[0].floor; + // change context to undefined, still false + bidRequestCopy.mediaTypes.video.context = undefined; + expect(spec.isBidRequestValid(bidRequestCopy)).to.equal(false); - expect(floor).to.equal(3.25); + // remove context, still false + delete bidRequestCopy.mediaTypes.video.context; + expect(spec.isBidRequestValid(bidRequestCopy)).to.equal(false); }); - it('should validate bid request with invalid video if a mediaTypes banner property is defined', function () { - const bidRequest = { - mediaTypes: { - video: { - context: 'instream' - }, - banner: { - sizes: [[300, 250]] - } - }, - params: { - accountId: 1001, - video: { - size_id: 201 - } - }, - sizes: [[300, 250]] - } - sandbox.stub(Date, 'now').callsFake(() => - bidderRequest.auctionStart + 100 - ); - expect(spec.isBidRequestValid(bidRequest)).to.equal(true); - }); + it('should enforce the new required mediaTypes.video params', function () { + createVideoBidderRequest(); - it('should not validate bid request when a invalid video object and no banner object is passed in', function () { - createVideoBidderRequestNoVideo(); sandbox.stub(Date, 'now').callsFake(() => bidderRequest.auctionStart + 100 ); - const bidRequestCopy = clone(bidderRequest.bids[0]); - expect(spec.isBidRequestValid(bidRequestCopy)).to.equal(false); - - bidRequestCopy.params.video = {}; - expect(spec.isBidRequestValid(bidRequestCopy)).to.equal(false); + expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(true); - bidRequestCopy.params.video = undefined; - expect(spec.isBidRequestValid(bidRequestCopy)).to.equal(false); + // change mimes to a non array, no good + createVideoBidderRequest(); + bidderRequest.bids[0].mediaTypes.video.mimes = 'video/mp4'; + expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(false); - bidRequestCopy.params.video = 123; - expect(spec.isBidRequestValid(bidRequestCopy)).to.equal(false); + // delete mimes, no good + createVideoBidderRequest(); + delete bidderRequest.bids[0].mediaTypes.video.mimes; + expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(false); - bidRequestCopy.params.video = {size_id: undefined}; - expect(spec.isBidRequestValid(bidRequestCopy)).to.equal(false); + // change protocols to an int not array of ints, no good + createVideoBidderRequest(); + bidderRequest.bids[0].mediaTypes.video.protocols = 1; + expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(false); - delete bidRequestCopy.params.video; - expect(spec.isBidRequestValid(bidRequestCopy)).to.equal(false); - }); + // delete protocols, no good + createVideoBidderRequest(); + delete bidderRequest.bids[0].mediaTypes.video.protocols; + expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(false); - it('should not validate bid request when an invalid video object is passed in with legacy config mediaType', function () { - createLegacyVideoBidderRequestNoVideo(); - sandbox.stub(Date, 'now').callsFake(() => - bidderRequest.auctionStart + 100 - ); + // change maxduration to an string, no good + createVideoBidderRequest(); + bidderRequest.bids[0].mediaTypes.video.maxduration = 'string'; + expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(false); - const bidderRequestCopy = clone(bidderRequest); - expect(spec.isBidRequestValid(bidderRequestCopy.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); - bidderRequestCopy.bids[0].params.video = {}; - expect(spec.isBidRequestValid(bidderRequestCopy.bids[0])).to.equal(false); + // change linearity to an string, no good + createVideoBidderRequest(); + bidderRequest.bids[0].mediaTypes.video.linearity = 'string'; + expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(false); - bidderRequestCopy.bids[0].params.video = undefined; - expect(spec.isBidRequestValid(bidderRequestCopy.bids[0])).to.equal(false); + // delete linearity, no good + createVideoBidderRequest(); + delete bidderRequest.bids[0].mediaTypes.video.linearity; + expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(false); - bidderRequestCopy.bids[0].params.video = NaN; - expect(spec.isBidRequestValid(bidderRequestCopy.bids[0])).to.equal(false); + // change api to an string, no good + createVideoBidderRequest(); + bidderRequest.bids[0].mediaTypes.video.api = 'string'; + expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(false); - delete bidderRequestCopy.bids[0].params.video; - expect(spec.isBidRequestValid(bidderRequestCopy.bids[0])).to.equal(false); + // delete api, no good + createVideoBidderRequest(); + delete bidderRequest.bids[0].mediaTypes.video.api; + expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(false); }); it('bid request is valid when video context is outstream', function () { @@ -1279,39 +1357,77 @@ describe('the rubicon adapter', function () { bidderRequest.auctionStart + 100 ); - const bidRequestCopy = clone(bidderRequest); + const bidRequestCopy = utils.deepClone(bidderRequest); let [request] = spec.buildRequests(bidRequestCopy.bids, bidRequestCopy); expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(true); - expect(request.data.slots[0].size_id).to.equal(203); + expect(request.data.imp[0].ext.rubicon.video.size_id).to.equal(203); }); - it('should get size from bid.sizes too', function () { - createVideoBidderRequestNoPlayer(); + it('should send banner request when outstream or instream video included but no rubicon video obect is present', function () { + // add banner and video mediaTypes + bidderRequest.mediaTypes = { + banner: { + sizes: [[300, 250]] + }, + video: { + context: 'outstream' + } + }; + // no video object in rubicon params, so we should see one call made for banner + sandbox.stub(Date, 'now').callsFake(() => bidderRequest.auctionStart + 100 ); - const bidRequestCopy = clone(bidderRequest); + let requests = spec.buildRequests(bidderRequest.bids, bidderRequest); - let [request] = spec.buildRequests(bidRequestCopy.bids, bidRequestCopy); + expect(requests.length).to.equal(1); + expect(requests[0].url).to.equal(FASTLANE_ENDPOINT); + + bidderRequest.mediaTypes.video.context = 'instream'; + + requests = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(request.data.slots[0].width).to.equal(300); - expect(request.data.slots[0].height).to.equal(250); + expect(requests.length).to.equal(1); + expect(requests[0].url).to.equal(FASTLANE_ENDPOINT); }); - it('should get size from bid.sizes too with legacy config mediaType', function () { - createLegacyVideoBidderRequestNoPlayer(); + it('should send request as banner when invalid video bid in multiple mediaType bidRequest', function () { + createVideoBidderRequestNoVideo(); + + let bid = bidderRequest.bids[0]; + bid.mediaTypes.banner = { + sizes: [[300, 250]] + }; + sandbox.stub(Date, 'now').callsFake(() => bidderRequest.auctionStart + 100 ); - const bidRequestCopy = clone(bidderRequest); + const bidRequestCopy = utils.deepClone(bidderRequest); - let [request] = spec.buildRequests(bidRequestCopy.bids, bidRequestCopy); + let requests = spec.buildRequests(bidRequestCopy.bids, bidRequestCopy); + expect(requests.length).to.equal(1); + expect(requests[0].url).to.equal(FASTLANE_ENDPOINT); + }); + + it('should include coppa flag in video bid request', () => { + createVideoBidderRequest(); + + sandbox.stub(Date, 'now').callsFake(() => + bidderRequest.auctionStart + 100 + ); - expect(request.data.slots[0].width).to.equal(300); - expect(request.data.slots[0].height).to.equal(250); + sandbox.stub(config, 'getConfig').callsFake(key => { + const config = { + 'coppa': true + }; + return config[key]; + }); + + const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(request.data.regs.coppa).to.equal(1); }); }); @@ -1387,14 +1503,12 @@ describe('the rubicon adapter', function () { expect(legacyVideoTypeBidRequest).is.equal(true); }); - it('should return false if mediaType is video and size_id is not defined', function () { - expect(spec.isBidRequestValid({ - bid: 99, - mediaType: 'video', - params: { - video: {} - } - })).is.equal(false); + it('should return false if trying to use legacy mediaType with video', function () { + createVideoBidderRequest(); + delete bidderRequest.bids[0].mediaTypes; + bidderRequest.bids[0].mediaType = 'video'; + const legacyVideoTypeBidRequest = hasVideoMediaType(bidderRequest.bids[0]); + expect(legacyVideoTypeBidRequest).is.equal(false); }); it('should return false if bidRequest.mediaType is not equal to video', function () { @@ -1534,6 +1648,129 @@ describe('the rubicon adapter', function () { expect(bids[1].rubiconTargeting.rpfl_14062).to.equal('15_tier_all_test'); }); + it('should use "network-advertiser" if no creative_id', function () { + let response = { + 'status': 'ok', + 'account_id': 14062, + 'site_id': 70608, + 'zone_id': 530022, + 'size_id': 15, + 'alt_size_ids': [ + 43, 10, 2 + ], + 'tracking': '', + 'inventory': {} + }; + + response.ads = [ + { + 'status': 'ok', + 'impression_id': '153dc240-8229-4604-b8f5-256933b9374c', + 'size_id': '15', + 'ad_id': '6', + 'advertiser': 7, + 'network': 8, + 'type': 'script', + 'script': 'alert(\'foo\')', + 'campaign_id': 10, + 'cpm': 0.811, + 'targeting': [ + { + 'key': 'rpfl_14062', + 'values': [ + '15_tier_all_test' + ] + } + ] + } + ]; + + let bids = spec.interpretResponse({body: response}, { + bidRequest: bidderRequest.bids[0] + }); + expect(bids[0].creativeId).to.equal('8-7'); + + response.ads = [ + { + 'status': 'ok', + 'impression_id': '153dc240-8229-4604-b8f5-256933b9374d', + 'size_id': '43', + 'ad_id': '7', + 'type': 'script', + 'script': 'alert(\'foo\')', + 'campaign_id': 10, + 'cpm': 0.911, + 'targeting': [ + { + 'key': 'rpfl_14062', + 'values': [ + '43_tier_all_test' + ] + } + ] + } + ]; + + bids = spec.interpretResponse({body: response}, { + bidRequest: bidderRequest.bids[0] + }); + expect(bids[0].creativeId).to.equal('-'); + + response.ads = [ + { + 'status': 'ok', + 'impression_id': '153dc240-8229-4604-b8f5-256933b9374d', + 'size_id': '10', + 'ad_id': '7', + 'network': 8, + 'type': 'script', + 'script': 'alert(\'foo\')', + 'campaign_id': 10, + 'cpm': 0.911, + 'targeting': [ + { + 'key': 'rpfl_14062', + 'values': [ + '10_tier_all_test' + ] + } + ] + } + ]; + + bids = spec.interpretResponse({body: response}, { + bidRequest: bidderRequest.bids[0] + }); + expect(bids[0].creativeId).to.equal('8-'); + + response.ads = [ + { + 'status': 'ok', + 'impression_id': '153dc240-8229-4604-b8f5-256933b9374d', + 'size_id': '2', + 'ad_id': '7', + 'advertiser': 7, + 'type': 'script', + 'script': 'alert(\'foo\')', + 'campaign_id': 10, + 'cpm': 0.911, + 'targeting': [ + { + 'key': 'rpfl_14062', + 'values': [ + '2_tier_all_test' + ] + } + ] + } + ]; + + bids = spec.interpretResponse({body: response}, { + bidRequest: bidderRequest.bids[0] + }); + expect(bids[0].creativeId).to.equal('-7'); + }); + it('should be fine with a CPM of 0', function () { let response = { 'status': 'ok', @@ -1637,7 +1874,7 @@ describe('the rubicon adapter', function () { }; let bids = spec.interpretResponse({ body: response }, { - bidRequest: [clone(bidderRequest.bids[0])] + bidRequest: [utils.deepClone(bidderRequest.bids[0])] }); expect(bids).to.be.lengthOf(1); @@ -1802,30 +2039,31 @@ describe('the rubicon adapter', function () { it('should register a successful bid', function () { let response = { - 'status': 'ok', - 'ads': { - '/19968336/header-bid-tag-0': [ - { - 'status': 'ok', - 'cpm': 1, - 'tier': 'tier0200', - 'targeting': { - 'rpfl_8000': '201_tier0200', - 'rpfl_elemid': '/19968336/header-bid-tag-0' + cur: 'USD', + seatbid: [{ + bid: [{ + id: '0', + impid: 'instream_video1', + price: 2, + crid: '4259970', + ext: { + bidder: { + rp: { + mime: 'application/javascript', + size_id: 201 + } }, - 'impression_id': 'a40fe16e-d08d-46a9-869d-2e1573599e0c', - 'site_id': 88888, - 'zone_id': 54321, - 'creative_type': 'video', - 'creative_depot_url': 'https://fastlane-adv.rubiconproject.com/v1/creative/a40fe16e-d08d-46a9-869d-2e1573599e0c.xml', - 'ad_id': 999999, - 'creative_id': 'crid-999999', - 'size_id': 201, - 'advertiser': 12345 + prebid: { + targeting: { + hb_uuid: '0c498f63-5111-4bed-98e2-9be7cb932a64' + }, + type: 'video' + } } - ] - }, - 'account_id': 7780 + }], + group: 0, + seat: 'rubicon' + }], }; let bids = spec.interpretResponse({body: response}, { @@ -1834,16 +2072,30 @@ describe('the rubicon adapter', function () { expect(bids).to.be.lengthOf(1); - expect(bids[0].creativeId).to.equal('crid-999999'); - expect(bids[0].cpm).to.equal(1); + expect(bids[0].seatBidId).to.equal('0'); + expect(bids[0].creativeId).to.equal('4259970'); + expect(bids[0].cpm).to.equal(2); expect(bids[0].ttl).to.equal(300); - expect(bids[0].netRevenue).to.equal(false); - expect(bids[0].vastUrl).to.equal( - 'https://fastlane-adv.rubiconproject.com/v1/creative/a40fe16e-d08d-46a9-869d-2e1573599e0c.xml' - ); - expect(bids[0].impression_id).to.equal('a40fe16e-d08d-46a9-869d-2e1573599e0c'); + expect(bids[0].netRevenue).to.equal(true); + expect(bids[0].adserverTargeting).to.deep.equal({hb_uuid: '0c498f63-5111-4bed-98e2-9be7cb932a64'}); expect(bids[0].mediaType).to.equal('video'); - expect(bids[0].videoCacheKey).to.equal('a40fe16e-d08d-46a9-869d-2e1573599e0c'); + expect(bids[0].bidderCode).to.equal('rubicon'); + expect(bids[0].currency).to.equal('USD'); + expect(bids[0].width).to.equal(640); + expect(bids[0].height).to.equal(480); + }); + }); + + describe('config with integration type', () => { + it('should use the integration type provided in the config instead of the default', () => { + sandbox.stub(config, 'getConfig').callsFake(function (key) { + const config = { + 'rubicon.int_type': 'testType' + }; + return config[key]; + }); + const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(parseQuery(request.data).tk_flint).to.equal('testType_v$prebid.version$'); }); }); }); @@ -1935,8 +2187,38 @@ describe('the rubicon adapter', function () { }); }); }); -}); -function clone(obj) { - return JSON.parse(JSON.stringify(obj)); -} + describe('get price granularity', function() { + it('should return correct buckets for all price granularity values', function() { + const CUSTOM_PRICE_BUCKET_ITEM = {min: 0, max: 5, increment: 0.5}; + + const mockConfig = { + priceGranularity: undefined, + customPriceBucket: { + buckets: [CUSTOM_PRICE_BUCKET_ITEM] + } + }; + sandbox.stub(config, 'getConfig').callsFake(key => { + return mockConfig[key]; + }); + + [ + {key: 'low', val: {max: 5.00, increment: 0.50}}, + {key: 'medium', val: {max: 20.00, increment: 0.10}}, + {key: 'high', val: {max: 20.00, increment: 0.01}}, + {key: 'auto', val: {max: 5.00, increment: 0.05}}, + {key: 'dense', val: {max: 3.00, increment: 0.01}}, + {key: 'custom', val: CUSTOM_PRICE_BUCKET_ITEM}, + + ].forEach(kvPair => { + mockConfig.priceGranularity = kvPair.key; + const result = getPriceGranularity(config); + expect(typeof result).to.equal('object'); + expect(result).to.haveOwnProperty('ranges'); + expect(Array.isArray(result.ranges)).to.equal(true); + expect(result.ranges.length).to.be.greaterThan(0) + expect(result.ranges[0]).to.deep.equal(kvPair.val); + }); + }); + }); +}); diff --git a/test/spec/modules/s2sTesting_spec.js b/test/spec/modules/s2sTesting_spec.js index b2b35a585c7..52377dcabf2 100644 --- a/test/spec/modules/s2sTesting_spec.js +++ b/test/spec/modules/s2sTesting_spec.js @@ -1,31 +1,15 @@ -import { getSourceBidderMap, calculateBidSources, getSource } from 'modules/s2sTesting'; +import s2sTesting from 'modules/s2sTesting'; import { config } from 'src/config'; -import find from 'core-js/library/fn/array/find'; - -var events = require('src/events'); -var CONSTANTS = require('src/constants.json'); -const BID_ADJUSTMENT = CONSTANTS.EVENTS.BID_ADJUSTMENT; var expect = require('chai').expect; describe('s2sTesting', function () { - let mathRandomStub; - let randomNumber = 0; - - beforeEach(function () { - mathRandomStub = sinon.stub(Math, 'random').callsFake(() => { return randomNumber; }); - }); - - afterEach(function () { - mathRandomStub.restore(); - }); - - describe('getSource', function () { + describe('s2sTesting.getSource', function () { // helper function to set random number and get the source function getExpectedSource(randNumber, sourceWeights, sources) { // set random number for testing - randomNumber = randNumber; - return getSource(sourceWeights, sources); + s2sTesting.globalRand = randNumber; + return s2sTesting.getSource(sourceWeights, sources); } it('returns undefined if no sources', function () { @@ -89,11 +73,11 @@ describe('s2sTesting', function () { }); }); - describe('getSourceBidderMap', function () { + describe('s2sTesting.getSourceBidderMap', function () { describe('setting source through s2sConfig', function () { beforeEach(function () { // set random number for testing - randomNumber = 0.7; + s2sTesting.globalRand = 0.7; }); it('does not work if testing is "false"', function () { @@ -102,7 +86,7 @@ describe('s2sTesting', function () { testing: false, bidderControl: {rubicon: {bidSource: {server: 1, client: 1}}} }}); - expect(getSourceBidderMap()).to.eql({ + expect(s2sTesting.getSourceBidderMap()).to.eql({ server: [], client: [] }); @@ -114,7 +98,7 @@ describe('s2sTesting', function () { testing: true, bidderControl: {rubicon: {bidSource: {server: 1, client: 1}}} }}); - expect(getSourceBidderMap()).to.eql({ + expect(s2sTesting.getSourceBidderMap()).to.eql({ server: [], client: ['rubicon'] }); @@ -126,7 +110,7 @@ describe('s2sTesting', function () { testing: true, bidderControl: {rubicon: {bidSource: {server: 4, client: 1}}} }}); - expect(getSourceBidderMap()).to.eql({ + expect(s2sTesting.getSourceBidderMap()).to.eql({ server: ['rubicon'], client: [] }); @@ -137,7 +121,7 @@ describe('s2sTesting', function () { bidders: ['rubicon'], testing: true }}); - expect(getSourceBidderMap()).to.eql({ + expect(s2sTesting.getSourceBidderMap()).to.eql({ server: ['rubicon'], client: [] }); @@ -151,10 +135,54 @@ describe('s2sTesting', function () { rubicon: {bidSource: {server: 3, client: 1}}, appnexus: {bidSource: {server: 1, client: 1}} }}}); - var serverClientBidders = getSourceBidderMap(); + var serverClientBidders = s2sTesting.getSourceBidderMap(); expect(serverClientBidders.server).to.eql(['rubicon']); expect(serverClientBidders.client).to.have.members(['appnexus']); }); + + it('sends both bidders to same source when weights are the same', function () { + s2sTesting.globalRand = 0.01; + + config.setConfig({s2sConfig: { + bidders: ['rubicon', 'appnexus'], + testing: true, + bidderControl: { + rubicon: {bidSource: {server: 1, client: 99}}, + appnexus: {bidSource: {server: 1, client: 99}} + }}}); + expect(s2sTesting.getSourceBidderMap()).to.eql({ + client: ['rubicon', 'appnexus'], + server: [] + }); + expect(s2sTesting.getSourceBidderMap()).to.eql({ + client: ['rubicon', 'appnexus'], + server: [] + }); + expect(s2sTesting.getSourceBidderMap()).to.eql({ + client: ['rubicon', 'appnexus'], + server: [] + }); + + config.setConfig({s2sConfig: { + bidders: ['rubicon', 'appnexus'], + testing: true, + bidderControl: { + rubicon: {bidSource: {server: 99, client: 1}}, + appnexus: {bidSource: {server: 99, client: 1}} + }}}); + expect(s2sTesting.getSourceBidderMap()).to.eql({ + server: ['rubicon', 'appnexus'], + client: [] + }); + expect(s2sTesting.getSourceBidderMap()).to.eql({ + server: ['rubicon', 'appnexus'], + client: [] + }); + expect(s2sTesting.getSourceBidderMap()).to.eql({ + server: ['rubicon', 'appnexus'], + client: [] + }); + }); }); describe('setting source through adUnits', function () { @@ -162,7 +190,7 @@ describe('s2sTesting', function () { // reset s2sconfig bid sources config.setConfig({s2sConfig: {testing: true}}); // set random number for testing - randomNumber = 0.7; + s2sTesting.globalRand = 0.7; }); it('sets one bidder source from one adUnit', function () { @@ -171,7 +199,7 @@ describe('s2sTesting', function () { {bidder: 'rubicon', bidSource: {server: 4, client: 1}} ]} ]; - expect(getSourceBidderMap(adUnits)).to.eql({ + expect(s2sTesting.getSourceBidderMap(adUnits)).to.eql({ server: ['rubicon'], client: [] }); @@ -184,7 +212,7 @@ describe('s2sTesting', function () { {bidder: 'rubicon', bidSource: {server: 1, client: 1}} ]} ]; - expect(getSourceBidderMap(adUnits)).to.eql({ + expect(s2sTesting.getSourceBidderMap(adUnits)).to.eql({ server: [], client: ['rubicon'] }); @@ -199,7 +227,7 @@ describe('s2sTesting', function () { {bidder: 'rubicon', bidSource: {}} ]} ]; - expect(getSourceBidderMap(adUnits)).to.eql({ + expect(s2sTesting.getSourceBidderMap(adUnits)).to.eql({ server: [], client: ['rubicon'] }); @@ -215,7 +243,7 @@ describe('s2sTesting', function () { {bidder: 'appnexus', bidSource: {server: 3, client: 1}} ]} ]; - var serverClientBidders = 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 @@ -236,7 +264,7 @@ describe('s2sTesting', function () { {bidder: 'bidder3', bidSource: {client: 1}} ]} ]; - var serverClientBidders = 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']); @@ -259,7 +287,7 @@ describe('s2sTesting', function () { {bidder: 'bidder3', calcSource: 'server', bidSource: {client: 1}} ]} ]; - var serverClientBidders = getSourceBidderMap(adUnits); + var serverClientBidders = s2sTesting.getSourceBidderMap(adUnits); expect(serverClientBidders.server).to.have.members(['appnexus', 'bidder3']); expect(serverClientBidders.server).to.not.have.members(['rubicon']); @@ -280,7 +308,7 @@ describe('s2sTesting', function () { // reset s2sconfig bid sources config.setConfig({s2sConfig: {testing: true}}); // set random number for testing - randomNumber = 0.7; + s2sTesting.globalRand = 0.7; }); it('should get sources from both', function () { @@ -302,7 +330,7 @@ describe('s2sTesting', function () { } }}); - var serverClientBidders = getSourceBidderMap(adUnits); + 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/scaleableAnalyticsAdapter_spec.js b/test/spec/modules/scaleableAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..300f72751a4 --- /dev/null +++ b/test/spec/modules/scaleableAnalyticsAdapter_spec.js @@ -0,0 +1,136 @@ +import scaleableAnalytics from 'modules/scaleableAnalyticsAdapter'; +import { expect } from 'chai'; +import events from 'src/events'; +import CONSTANTS from 'src/constants.json'; +import adapterManager from 'src/adapterManager'; + +const BID_TIMEOUT = CONSTANTS.EVENTS.BID_TIMEOUT; +const AUCTION_INIT = CONSTANTS.EVENTS.AUCTION_INIT; +const BID_RESPONSE = CONSTANTS.EVENTS.BID_RESPONSE; +const BID_WON = CONSTANTS.EVENTS.BID_WON; +const AUCTION_END = CONSTANTS.EVENTS.AUCTION_END; + +describe('Scaleable Analytics Adapter', function() { + const MOCK_DATA = { + adUnitCode: '12345', + site: '5c4fab7a829e955d6c265e72', + bidResponse: { + adUnitCode: '12345', + bidderCode: 'test-code', + cpm: 3.14, + timeToRespond: 285 + }, + bidTimeout: [ + { + adUnitCode: '67890', + bidder: 'test-code' + } + ] + }; + + MOCK_DATA.expectedBidResponse = { + event: 'bids', + bids: [{ + code: MOCK_DATA.bidResponse.bidderCode, + cpm: MOCK_DATA.bidResponse.cpm, + ttr: MOCK_DATA.bidResponse.timeToRespond + }], + adunit: MOCK_DATA.adUnitCode, + site: MOCK_DATA.site + }; + + MOCK_DATA.expectedBidTimeout = { + event: 'bids', + bids: [], + timeouts: [MOCK_DATA.bidTimeout[0].bidder], + adunit: MOCK_DATA.bidTimeout[0].adUnitCode, + site: MOCK_DATA.site + }; + + let xhr; + let requests; + + before(function() { + xhr = sinon.useFakeXMLHttpRequest(); + xhr.onCreate = request => requests.push(request); + }); + + after(function() { + xhr.restore(); + }); + + describe('Event Handling', function() { + beforeEach(function() { + requests = []; + sinon.stub(events, 'getEvents').returns([]); + + scaleableAnalytics.enableAnalytics({ + provider: 'scaleable', + options: { + site: MOCK_DATA.site + } + }); + }); + + afterEach(function() { + events.getEvents.restore(); + scaleableAnalytics.disableAnalytics(); + }); + + it('should handle the auction init event', function(done) { + events.emit(AUCTION_INIT, { + adUnitCodes: [MOCK_DATA.adUnitCode] + }); + + const result = JSON.parse(requests[0].requestBody); + expect(result).to.deep.equal({ + event: 'request', + site: MOCK_DATA.site, + adunit: MOCK_DATA.adUnitCode + }); + + done(); + }); + + it('should handle the bid response event', function() { + events.emit(BID_RESPONSE, MOCK_DATA.bidResponse); + + const actual = scaleableAnalytics.getAuctionData(); + + expect(actual[MOCK_DATA.adUnitCode]).to.deep.equal(MOCK_DATA.expectedBidResponse); + }); + + it('should handle the bid timeout event', function() { + events.emit(BID_TIMEOUT, MOCK_DATA.bidTimeout); + + const actual = scaleableAnalytics.getAuctionData(); + + expect(actual[MOCK_DATA.bidTimeout[0].adUnitCode]).to.deep.equal(MOCK_DATA.expectedBidTimeout); + }); + + it('should handle the bid won event', function(done) { + events.emit(BID_WON, MOCK_DATA.bidResponse); + + const result = JSON.parse(requests[0].requestBody); + expect(result).to.deep.equal({ + adunit: MOCK_DATA.adUnitCode, + code: MOCK_DATA.bidResponse.bidderCode, + cpm: MOCK_DATA.bidResponse.cpm, + ttr: MOCK_DATA.bidResponse.timeToRespond, + event: 'win', + site: MOCK_DATA.site + }); + + done(); + }); + + it('should handle the auction end event', function(done) { + events.emit(AUCTION_END, {}); + + const result = JSON.parse(requests[0].requestBody); + expect(result).to.deep.equal(MOCK_DATA.expectedBidResponse); + + done(); + }); + }); +}); diff --git a/test/spec/modules/schain_spec.js b/test/spec/modules/schain_spec.js new file mode 100644 index 00000000000..8f5104f1822 --- /dev/null +++ b/test/spec/modules/schain_spec.js @@ -0,0 +1,286 @@ +import {isValidSchainConfig, isSchainObjectValid, copySchainObjectInAdunits} from '../../../modules/schain'; +import { expect } from 'chai'; + +describe('#isValidSchainConfig: module config validation', function() { + it('if config is undefined or not an objct then return false', function() { + expect(isValidSchainConfig()).to.false; + expect(isValidSchainConfig('')).to.false; + expect(isValidSchainConfig([])).to.false; + expect(isValidSchainConfig(12)).to.false; + expect(isValidSchainConfig(3.14)).to.false; + }) + + it('if config is an object then return true', function() { + expect(isValidSchainConfig({})).to.true; + }) +}); + +describe('#isSchainObjectValid: schain object validation', function() { + let schainConfig; + + beforeEach(function() { + schainConfig = { + 'ver': '1.0', + 'complete': 1, + 'nodes': [ + { + 'asi': 'indirectseller.com', + 'sid': '00001', + 'hp': 1 + }, + + { + 'asi': 'indirectseller-2.com', + 'sid': '00002', + 'hp': 2 + } + ] + }; + }); + + it('Return true for correct config', function() { + expect(isSchainObjectValid(schainConfig, true)).to.true; + }); + + it('Return false for string config', function() { + schainConfig = JSON.stringify(schainConfig); + expect(isSchainObjectValid(schainConfig, true)).to.false; + }); + + it('Returns false if complete param is not an Integer', function() { + schainConfig.complete = 1; // integer + expect(isSchainObjectValid(schainConfig, true)).to.true; + schainConfig.complete = '1'; // string + expect(isSchainObjectValid(schainConfig, true)).to.false; + schainConfig.complete = 1.1; // float + expect(isSchainObjectValid(schainConfig, true)).to.false; + schainConfig.complete = {}; // object + expect(isSchainObjectValid(schainConfig, true)).to.false; + delete schainConfig.complete; // undefined + expect(isSchainObjectValid(schainConfig, true)).to.false; + schainConfig.complete = true; // boolean + expect(isSchainObjectValid(schainConfig, true)).to.false; + schainConfig.complete = []; // array + expect(isSchainObjectValid(schainConfig, true)).to.false; + }); + + it('Returns false if version param is not a String', function() { + schainConfig.ver = 1; // Integer + expect(isSchainObjectValid(schainConfig, true)).to.false; + schainConfig.ver = 1.1; // float + expect(isSchainObjectValid(schainConfig, true)).to.false; + schainConfig.ver = {}; // object + expect(isSchainObjectValid(schainConfig, true)).to.false; + delete schainConfig.ver; // undefined + expect(isSchainObjectValid(schainConfig, true)).to.false; + schainConfig.ver = true; // boolean + expect(isSchainObjectValid(schainConfig, true)).to.false; + schainConfig.ver = []; // array + expect(isSchainObjectValid(schainConfig, true)).to.false; + }); + + it('Returns false if ext param is not an Object', function() { + schainConfig.ext = 1; // Integer + expect(isSchainObjectValid(schainConfig, true)).to.false; + schainConfig.ext = 1.1; // float + expect(isSchainObjectValid(schainConfig, true)).to.false; + schainConfig.ext = {}; // object + expect(isSchainObjectValid(schainConfig, true)).to.true; + delete schainConfig.ext; // undefined // param is optional thus this will result true + expect(isSchainObjectValid(schainConfig, true)).to.true; + schainConfig.ext = true; // boolean + expect(isSchainObjectValid(schainConfig, true)).to.false; + schainConfig.ext = []; // array + expect(isSchainObjectValid(schainConfig, true)).to.false; + }); + + it('Returns false if nodes param is not an Array', function() { + // by default schainConfig.nodes is array + expect(isSchainObjectValid(schainConfig, true)).to.true; + schainConfig.nodes = 1; // Integer + expect(isSchainObjectValid(schainConfig, true)).to.false; + schainConfig.nodes = 1.1; // float + expect(isSchainObjectValid(schainConfig, true)).to.false; + schainConfig.nodes = {}; // object + expect(isSchainObjectValid(schainConfig, true)).to.false; + delete schainConfig.nodes; // undefined + expect(isSchainObjectValid(schainConfig, true)).to.false; + schainConfig.nodes = true; // boolean + expect(isSchainObjectValid(schainConfig, true)).to.false; + }); + + it('Returns false if nodes[].asi is not a String', function() { + schainConfig.nodes[0].asi = 1; // Integer + expect(isSchainObjectValid(schainConfig, true)).to.false; + schainConfig.nodes[0].asi = 1.1; // float + expect(isSchainObjectValid(schainConfig, true)).to.false; + schainConfig.nodes[0].asi = {}; // object + expect(isSchainObjectValid(schainConfig, true)).to.false; + delete schainConfig.nodes[0].asi; // undefined + expect(isSchainObjectValid(schainConfig, true)).to.false; + schainConfig.nodes[0].asi = true; // boolean + expect(isSchainObjectValid(schainConfig, true)).to.false; + schainConfig.nodes[0].asi = []; // array + expect(isSchainObjectValid(schainConfig, true)).to.false; + }); + + it('Returns false if nodes[].sid is not a String', function() { + schainConfig.nodes[1].sid = 1; // Integer + expect(isSchainObjectValid(schainConfig, true)).to.false; + schainConfig.nodes[1].sid = 1.1; // float + expect(isSchainObjectValid(schainConfig, true)).to.false; + schainConfig.nodes[1].sid = {}; // object + expect(isSchainObjectValid(schainConfig, true)).to.false; + delete schainConfig.nodes[0].sid; // undefined + expect(isSchainObjectValid(schainConfig, true)).to.false; + schainConfig.nodes[1].sid = true; // boolean + expect(isSchainObjectValid(schainConfig, true)).to.false; + schainConfig.nodes[1].sid = []; // array + expect(isSchainObjectValid(schainConfig, true)).to.false; + }); + + it('Returns false if nodes[].hp is not an Integer', function() { + schainConfig.nodes[0].hp = '1'; // string + expect(isSchainObjectValid(schainConfig, true)).to.false; + schainConfig.nodes[0].hp = 1.1; // float + expect(isSchainObjectValid(schainConfig, true)).to.false; + schainConfig.nodes[0].hp = {}; // object + expect(isSchainObjectValid(schainConfig, true)).to.false; + delete schainConfig.nodes[0].hp; // undefined + expect(isSchainObjectValid(schainConfig, true)).to.false; + schainConfig.nodes[0].hp = true; // boolean + expect(isSchainObjectValid(schainConfig, true)).to.false; + schainConfig.nodes[0].hp = []; // array + expect(isSchainObjectValid(schainConfig, true)).to.false; + }); + + it('Returns false if nodes[].rid is not a String', function() { + schainConfig.nodes[1].rid = 'rid value'; // string + expect(isSchainObjectValid(schainConfig, true)).to.true; + schainConfig.nodes[1].rid = 1; // Integer + expect(isSchainObjectValid(schainConfig, true)).to.false; + schainConfig.nodes[1].rid = 1.1; // float + expect(isSchainObjectValid(schainConfig, true)).to.false; + schainConfig.nodes[1].rid = {}; // object + expect(isSchainObjectValid(schainConfig, true)).to.false; + delete schainConfig.nodes[1].rid; // undefined // param is optional thus this will result true + expect(isSchainObjectValid(schainConfig, true)).to.true; + schainConfig.nodes[1].rid = true; // boolean + expect(isSchainObjectValid(schainConfig, true)).to.false; + schainConfig.nodes[1].rid = []; // array + expect(isSchainObjectValid(schainConfig, true)).to.false; + }); + + it('Returns false if nodes[].name is not a String', function() { + schainConfig.nodes[0].name = 'name value'; // string + expect(isSchainObjectValid(schainConfig, true)).to.true; + schainConfig.nodes[0].name = 1; // Integer + expect(isSchainObjectValid(schainConfig, true)).to.false; + schainConfig.nodes[0].name = 1.1; // float + expect(isSchainObjectValid(schainConfig, true)).to.false; + schainConfig.nodes[0].name = {}; // object + expect(isSchainObjectValid(schainConfig, true)).to.false; + delete schainConfig.nodes[0].name; // undefined // param is optional thus this will result true + expect(isSchainObjectValid(schainConfig, true)).to.true; + schainConfig.nodes[0].name = true; // boolean + expect(isSchainObjectValid(schainConfig, true)).to.false; + schainConfig.nodes[0].name = []; // array + expect(isSchainObjectValid(schainConfig, true)).to.false; + }); + + it('Returns false if nodes[].domain is not a String', function() { + schainConfig.nodes[1].domain = 'domain value'; // string + expect(isSchainObjectValid(schainConfig, true)).to.true; + schainConfig.nodes[1].domain = 1; // Integer + expect(isSchainObjectValid(schainConfig, true)).to.false; + schainConfig.nodes[1].domain = 1.1; // float + expect(isSchainObjectValid(schainConfig, true)).to.false; + schainConfig.nodes[1].domain = {}; // object + expect(isSchainObjectValid(schainConfig, true)).to.false; + delete schainConfig.nodes[1].domain; // undefined // param is optional thus this will result true + expect(isSchainObjectValid(schainConfig, true)).to.true; + schainConfig.nodes[1].domain = true; // boolean + expect(isSchainObjectValid(schainConfig, true)).to.false; + schainConfig.nodes[1].domain = []; // array + expect(isSchainObjectValid(schainConfig, true)).to.false; + }); + + it('Returns false if nodes[].ext param is not an Object', function() { + schainConfig.nodes[0].ext = 1; // Integer + expect(isSchainObjectValid(schainConfig, true)).to.false; + schainConfig.nodes[0].ext = 1.1; // float + expect(isSchainObjectValid(schainConfig, true)).to.false; + schainConfig.nodes[0].ext = {}; // object + expect(isSchainObjectValid(schainConfig, true)).to.true; + delete schainConfig.nodes[0].ext; // undefined // param is optional thus this will result true + expect(isSchainObjectValid(schainConfig, true)).to.true; + schainConfig.nodes[0].ext = true; // boolean + expect(isSchainObjectValid(schainConfig, true)).to.false; + schainConfig.nodes[0].ext = []; // array + expect(isSchainObjectValid(schainConfig, true)).to.false; + }); + + it('Relaxed mode: Returns true even for invalid config if second argument is set to false', function() { + schainConfig = { + 'ver': 1.0, // invalid + 'complete': '1', // invalid + 'nodes': [ + { + 'asi': 'indirectseller.com', + 'sid': 1, // invalid + 'hp': '1' // invalid + }, + + { + 'asi': 'indirectseller-2.com', + 'sid': '00002', + 'hp': 2 + } + ] + }; + expect(isSchainObjectValid(schainConfig, false)).to.true; + + schainConfig = {}; + expect(isSchainObjectValid(schainConfig, false)).to.true; + }) +}); + +describe('Passing schain object to adUnits', function() { + let schainConfig; + + beforeEach(function() { + schainConfig = { + 'ver': '1.0', + 'complete': 1, + 'nodes': [ + { + 'asi': 'indirectseller.com', + 'sid': '00001', + 'hp': 1 + }, + + { + 'asi': 'indirectseller-2.com', + 'sid': '00002', + 'hp': 2 + } + ] + }; + }); + + it('schain object should be applied to all adUnits', function() { + let adUnits = [ + { + bids: [{}, {}] + }, + { + bids: [{}, {}] + } + ]; + copySchainObjectInAdunits(adUnits, schainConfig); + expect(adUnits[0].bids[0].schain).to.equal(schainConfig); + expect(adUnits[0].bids[1].schain).to.equal(schainConfig); + expect(adUnits[1].bids[0].schain).to.equal(schainConfig); + expect(adUnits[1].bids[1].schain).to.equal(schainConfig); + }); +}); diff --git a/test/spec/modules/seedtagBidAdapter_spec.js b/test/spec/modules/seedtagBidAdapter_spec.js new file mode 100644 index 00000000000..4bd4b599c55 --- /dev/null +++ b/test/spec/modules/seedtagBidAdapter_spec.js @@ -0,0 +1,396 @@ +import { expect } from 'chai' +import { spec, getTimeoutUrl } from 'modules/seedtagBidAdapter' + +function getSlotConfigs(mediaTypes, params) { + return { + params: params, + sizes: [[300, 250], [300, 600]], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + bidRequestsCount: 1, + bidder: 'seedtag', + mediaTypes: mediaTypes, + src: 'client', + transactionId: 'd704d006-0d6e-4a09-ad6c-179e7e758096' + } +} + +describe('Seedtag Adapter', function() { + describe('isBidRequestValid method', function() { + const PUBLISHER_ID = '0000-0000-01' + const ADUNIT_ID = '000000' + describe('returns true', function() { + describe('when banner slot config has all mandatory params', () => { + describe('and placement has the correct value', function() { + const createBannerSlotConfig = placement => { + return getSlotConfigs( + { banner: {} }, + { + publisherId: PUBLISHER_ID, + adUnitId: ADUNIT_ID, + placement + } + ) + } + const placements = ['banner', 'video', 'inImage', 'inScreen'] + placements.forEach(placement => { + it('should be ' + placement, function() { + const isBidRequestValid = spec.isBidRequestValid( + createBannerSlotConfig(placement) + ) + 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: [[600, 200]] + } + }, + { + publisherId: PUBLISHER_ID, + adUnitId: ADUNIT_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 PublisherToken.', function() { + const isBidRequestValid = spec.isBidRequestValid( + createSlotconfig({ + adUnitId: '000000', + placement: 'banner' + }) + ) + expect(isBidRequestValid).to.equal(false) + }) + it('does not have the AdUnitId.', function() { + const isBidRequestValid = spec.isBidRequestValid( + createSlotconfig({ + publisherId: '0000-0000-01', + placement: 'banner' + }) + ) + expect(isBidRequestValid).to.equal(false) + }) + it('does not have the placement.', function() { + const isBidRequestValid = spec.isBidRequestValid( + createSlotconfig({ + publisherId: '0000-0000-01', + adUnitId: '000000' + }) + ) + expect(isBidRequestValid).to.equal(false) + }) + it('does not have a the correct placement.', function() { + const isBidRequestValid = spec.isBidRequestValid( + createSlotconfig({ + publisherId: '0000-0000-01', + adUnitId: '000000', + placement: 'another_thing' + }) + ) + expect(isBidRequestValid).to.equal(false) + }) + }) + describe('when video mediaType object is not correct.', function() { + function createVideoSlotconfig(mediaType) { + return getSlotConfigs(mediaType, { + publisherId: PUBLISHER_ID, + adUnitId: ADUNIT_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('is not instream ', function() { + const isBidRequestValid = spec.isBidRequestValid( + createVideoSlotconfig({ + video: { + context: 'outstream', + playerSize: [[600, 200]] + } + }) + ) + expect(isBidRequestValid).to.equal(false) + }) + }) + }) + }) + + describe('buildRequests method', function() { + const bidderRequest = { + refererInfo: { referer: 'referer' }, + timeout: 1000 + } + const mandatoryParams = { + publisherId: '0000-0000-01', + adUnitId: '000000', + placement: 'banner' + } + const inStreamParams = Object.assign({}, mandatoryParams, { + video: { + mimes: 'mp4' + } + }) + const validBidRequests = [ + getSlotConfigs({ banner: {} }, mandatoryParams), + getSlotConfigs( + { video: { context: 'instream', playerSize: [[300, 200]] } }, + inStreamParams + ) + ] + 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://s.seedtag.com/c/hb/bid') + }) + + it('Common data request should be correct', function() { + const request = spec.buildRequests(validBidRequests, bidderRequest) + const data = JSON.parse(request.data) + expect(data.url).to.equal('referer') + expect(data.publisherToken).to.equal('0000-0000-01') + expect(typeof data.version).to.equal('string') + }) + + describe('adPosition param', function() { + it('should sended when publisher set adPosition param', function() { + const params = Object.assign({}, mandatoryParams, { + adPosition: 1 + }) + const validBidRequests = [getSlotConfigs({ banner: {} }, params)] + const request = spec.buildRequests(validBidRequests, bidderRequest) + const data = JSON.parse(request.data) + expect(data.bidRequests[0].adPosition).to.equal(1) + }) + it('should not sended when publisher has not set adPosition param', function() { + const validBidRequests = [ + getSlotConfigs({ banner: {} }, mandatoryParams) + ] + const request = spec.buildRequests(validBidRequests, bidderRequest) + const data = JSON.parse(request.data) + expect(data.bidRequests[0].adPosition).to.equal(undefined) + }) + }) + + describe('GDPR params', function() { + describe('when there arent 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.cd).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.cd).to.equal('consentString') + }) + }) + }) + + describe('BidRequests params', function() { + const request = spec.buildRequests(validBidRequests, bidderRequest) + const data = JSON.parse(request.data) + const bidRequests = data.bidRequests + it('should request a Banner', function() { + const bannerBid = bidRequests[0] + expect(bannerBid.id).to.equal('30b31c1838de1e') + expect(bannerBid.transactionId).to.equal( + 'd704d006-0d6e-4a09-ad6c-179e7e758096' + ) + expect(bannerBid.supplyTypes[0]).to.equal('display') + expect(bannerBid.adUnitId).to.equal('000000') + expect(bannerBid.sizes[0][0]).to.equal(300) + expect(bannerBid.sizes[0][1]).to.equal(250) + expect(bannerBid.sizes[1][0]).to.equal(300) + expect(bannerBid.sizes[1][1]).to.equal(600) + }) + it('should request an InStream Video', function() { + const videoBid = bidRequests[1] + expect(videoBid.id).to.equal('30b31c1838de1e') + expect(videoBid.transactionId).to.equal( + 'd704d006-0d6e-4a09-ad6c-179e7e758096' + ) + expect(videoBid.supplyTypes[0]).to.equal('video') + expect(videoBid.adUnitId).to.equal('000000') + expect(videoBid.videoParams.mimes).to.equal('mp4') + expect(videoBid.videoParams.w).to.equal(300) + expect(videoBid.videoParams.h).to.equal(200) + expect(videoBid.sizes[0][0]).to.equal(300) + expect(videoBid.sizes[0][1]).to.equal(250) + expect(videoBid.sizes[1][0]).to.equal(300) + expect(videoBid.sizes[1][1]).to.equal(600) + }) + }) + }) + + 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 = { data: JSON.stringify({}) } + const serverResponse = { + body: { + bids: [ + { + bidId: '2159a54dc2566f', + price: 0.5, + currency: 'USD', + content: 'content', + width: 728, + height: 90, + mediaType: 'display', + ttl: 360 + } + ], + cookieSync: { url: '' } + } + } + const bids = spec.interpretResponse(serverResponse, request) + expect(bids.length).to.equal(1) + expect(bids[0].requestId).to.equal('2159a54dc2566f') + expect(bids[0].cpm).to.equal(0.5) + expect(bids[0].width).to.equal(728) + expect(bids[0].height).to.equal(90) + expect(bids[0].currency).to.equal('USD') + expect(bids[0].netRevenue).to.equal(true) + expect(bids[0].ad).to.equal('content') + }) + }) + describe('the bid is a video', function() { + it('should return a instream bid', function() { + const request = { data: JSON.stringify({}) } + const serverResponse = { + body: { + bids: [ + { + bidId: '2159a54dc2566f', + price: 0.5, + currency: 'USD', + content: 'content', + width: 728, + height: 90, + mediaType: 'video', + ttl: 360 + } + ], + cookieSync: { url: '' } + } + } + const bids = spec.interpretResponse(serverResponse, request) + expect(bids.length).to.equal(1) + expect(bids[0].requestId).to.equal('2159a54dc2566f') + expect(bids[0].cpm).to.equal(0.5) + expect(bids[0].width).to.equal(728) + expect(bids[0].height).to.equal(90) + expect(bids[0].currency).to.equal('USD') + expect(bids[0].netRevenue).to.equal(true) + expect(bids[0].vastXml).to.equal('content') + }) + }) + }) + }) + + describe('user syncs method', function() { + it('should return empty array, when iframe sync option are disabled.', function() { + const syncOption = { iframeEnabled: false } + const serverResponses = [{ body: { cookieSync: 'someUrl' } }] + const cookieSyncArray = spec.getUserSyncs(syncOption, serverResponses) + expect(cookieSyncArray.length).to.equal(0) + }) + it('should return empty array, when the server response are wrong.', function() { + const syncOption = { iframeEnabled: true } + const serverResponses = [{ body: {} }] + const cookieSyncArray = spec.getUserSyncs(syncOption, serverResponses) + expect(cookieSyncArray.length).to.equal(0) + }) + it('should return empty array, when the server response are void.', function() { + const syncOption = { iframeEnabled: true } + const serverResponses = [{ body: { cookieSync: '' } }] + const cookieSyncArray = spec.getUserSyncs(syncOption, serverResponses) + expect(cookieSyncArray.length).to.equal(0) + }) + it('should return a array with the cookie sync, when the server response with a cookie sync.', function() { + const syncOption = { iframeEnabled: true } + const serverResponses = [{ body: { cookieSync: 'someUrl' } }] + const cookieSyncArray = spec.getUserSyncs(syncOption, serverResponses) + expect(cookieSyncArray.length).to.equal(1) + expect(cookieSyncArray[0].type).to.equal('iframe') + expect(cookieSyncArray[0].url).to.equal('someUrl') + }) + }) + + describe('onTimeout', function () { + it('should return the correct endpoint', function () { + const params = { publisherId: '0000', adUnitId: '11111' } + const timeoutData = [{ params: [ params ] }]; + const timeoutUrl = getTimeoutUrl(timeoutData); + expect(timeoutUrl).to.equal( + 'https://s.seedtag.com/se/hb/timeout?publisherToken=' + + params.publisherId + + '&adUnitId=' + + params.adUnitId + ) + }) + }) +}) diff --git a/test/spec/modules/serverbidBidAdapter_spec.js b/test/spec/modules/serverbidBidAdapter_spec.js index aa40ee31ce5..8949463e151 100644 --- a/test/spec/modules/serverbidBidAdapter_spec.js +++ b/test/spec/modules/serverbidBidAdapter_spec.js @@ -1,7 +1,6 @@ import { expect } from 'chai'; import { spec } from 'modules/serverbidBidAdapter'; - -var bidFactory = require('src/bidfactory.js'); +import { createBid } from 'src/bidfactory'; const ENDPOINT = 'https://e.serverbid.com/api/v2'; const SMARTSYNC_CALLBACK = 'serverbidCallBids'; @@ -194,7 +193,7 @@ describe('Serverbid BidAdapter', function () { describe('interpretResponse validation', function () { it('response should have valid bidderCode', function () { let bidRequest = spec.buildRequests(REQUEST.bidRequest); - let bid = bidFactory.createBid(1, bidRequest.bidRequest[0]); + let bid = createBid(1, bidRequest.bidRequest[0]); expect(bid.bidderCode).to.equal('serverbid'); }); @@ -242,7 +241,7 @@ describe('Serverbid BidAdapter', function () { it('handles empty sync options', function () { let opts = spec.getUserSyncs({}); - expect(opts).to.be.empty; + expect(opts).to.be.undefined; }); it('should return a sync url if iframe syncs are enabled', function () { diff --git a/test/spec/modules/serverbidServerBidAdapter_spec.js b/test/spec/modules/serverbidServerBidAdapter_spec.js index 7c428647f62..1190648bb84 100644 --- a/test/spec/modules/serverbidServerBidAdapter_spec.js +++ b/test/spec/modules/serverbidServerBidAdapter_spec.js @@ -243,7 +243,7 @@ describe('ServerBid S2S Adapter', function () { const response = addBidResponse.firstCall.args[1]; expect(response).to.have.property('statusMessage', 'Bid available'); expect(response).to.have.property('cpm', 0.5); - expect(response).to.have.property('adId', '123'); + expect(response).to.have.property('requestId', '123'); }); it('registers no-bid response when ad unit not set', function () { @@ -261,7 +261,7 @@ describe('ServerBid S2S Adapter', function () { expect(response).to.have.property('statusMessage', 'Bid returned empty or error response'); const bid_request_passed = addBidResponse.firstCall.args[1]; - expect(bid_request_passed).to.have.property('adId', '123'); + expect(bid_request_passed).to.have.property('requestId', '123'); }); it('registers no-bid response when ad unit is set', function () { @@ -291,8 +291,8 @@ describe('ServerBid S2S Adapter', function () { expect(addBidResponse.firstCall.args[0]).to.equal('div-gpt-ad-1460505748561-0'); expect(addBidResponse.secondCall.args[0]).to.equal('div-gpt-ad-1460505748561-1'); - expect(addBidResponse.firstCall.args[1]).to.have.property('adId', '123'); - expect(addBidResponse.secondCall.args[1]).to.have.property('adId', '101111'); + expect(addBidResponse.firstCall.args[1]).to.have.property('requestId', '123'); + expect(addBidResponse.secondCall.args[1]).to.have.property('requestId', '101111'); expect(addBidResponse.firstCall.args[1]) .to.have.property('statusMessage', 'Bid available'); diff --git a/test/spec/modules/shBidAdapter_spec.js b/test/spec/modules/shBidAdapter_spec.js new file mode 100644 index 00000000000..525642da7d6 --- /dev/null +++ b/test/spec/modules/shBidAdapter_spec.js @@ -0,0 +1,380 @@ +import {expect} from 'chai' +import {spec} from 'modules/shBidAdapter' +import {newBidder} from 'src/adapters/bidderFactory' +import {VIDEO, BANNER} from 'src/mediaTypes' + +const bidderRequest = { + refererInfo: { + referer: 'http://example.com' + } +} + +const gdpr = { + 'gdprConsent': { + 'consentString': 'BOEFEAyOEFEAyAHABDENAI4AAAB9vABAASA', + 'gdprApplies': true + } +} + +const bidRequestCommonParams = { + 'bidder': 'showheroes-bs', + 'params': { + 'playerId': '47427aa0-f11a-4d24-abca-1295a46a46cd', + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[640, 480]], + 'bidId': '38b373e1e31c18', + 'bidderRequestId': '12e3ade2543ba6', + 'auctionId': '43aa080090a47f', +} + +const bidRequestVideo = { + ...bidRequestCommonParams, + ...{ + 'mediaTypes': { + 'video': { + 'playerSize': [640, 480], + 'context': 'instream', + } + } + } +} + +const bidRequestOutstream = { + ...bidRequestCommonParams, + ...{ + 'mediaTypes': { + 'video': { + 'playerSize': [640, 480], + 'context': 'outstream', + } + } + } +} + +const bidRequestVideoVpaid = { + ...bidRequestCommonParams, + ...{ + 'params': { + 'playerId': '47427aa0-f11a-4d24-abca-1295a46a46cd', + 'vpaidMode': true, + }, + 'mediaTypes': { + 'video': { + 'playerSize': [640, 480], + 'context': 'instream', + } + } + } +} + +const bidRequestBanner = { + ...bidRequestCommonParams, + ...{ + 'mediaTypes': { + 'banner': { + 'sizes': [[640, 360]] + } + } + } +} + +describe('shBidAdapter', 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': { + 'playerId': '47427aa0-f11a-4d24-abca-1295a46a46cd', + } + } + expect(spec.isBidRequestValid(request)).to.equal(true) + }) + + it('should return false when required params are not passed', function () { + const request = { + 'params': {} + } + expect(spec.isBidRequestValid(request)).to.equal(false) + }) + }) + + describe('buildRequests', function () { + it('sends bid request to ENDPOINT via POST', function () { + const request = spec.buildRequests([bidRequestVideo], bidderRequest) + expect(request.method).to.equal('POST') + }) + + it('check sizes formats', function () { + const request = spec.buildRequests([{ + 'params': {}, + 'mediaTypes': {}, + 'sizes': [[640, 480]], + }], bidderRequest) + const payload = request.data.requests[0]; + expect(payload).to.be.an('object'); + expect(payload.video).to.have.property('width', 640); + expect(payload.video).to.have.property('height', 480); + + const request2 = spec.buildRequests([{ + 'params': {}, + 'mediaTypes': {}, + 'sizes': [320, 240], + }], bidderRequest) + const payload2 = request2.data.requests[0]; + expect(payload).to.be.an('object'); + expect(payload2.video).to.have.property('width', 320); + expect(payload2.video).to.have.property('height', 240); + }) + + it('should get size from mediaTypes when sizes property is empty', function () { + const request = spec.buildRequests([{ + 'params': {}, + 'mediaTypes': { + 'video': { + 'playerSize': [640, 480] + } + }, + 'sizes': [], + }], bidderRequest) + const payload = request.data.requests[0]; + expect(payload).to.be.an('object'); + expect(payload.video).to.have.property('width', 640); + expect(payload.video).to.have.property('height', 480); + + const request2 = spec.buildRequests([{ + 'params': {}, + 'mediaTypes': { + 'banner': { + 'sizes': [[320, 240]] + } + }, + 'sizes': [], + }], bidderRequest) + const payload2 = request2.data.requests[0]; + expect(payload).to.be.an('object'); + expect(payload2.video).to.have.property('width', 320); + expect(payload2.video).to.have.property('height', 240); + }) + + it('should attach valid params to the payload when type is video', function () { + const request = spec.buildRequests([bidRequestVideo], bidderRequest) + const payload = request.data.requests[0]; + expect(payload).to.be.an('object'); + expect(payload).to.have.property('playerId', '47427aa0-f11a-4d24-abca-1295a46a46cd'); + expect(payload).to.have.property('mediaType', VIDEO); + expect(payload).to.have.property('type', 2); + }) + + it('should attach valid params to the payload when type is video & vpaid mode on', function () { + const request = spec.buildRequests([bidRequestVideoVpaid], bidderRequest) + const payload = request.data.requests[0]; + expect(payload).to.be.an('object'); + expect(payload).to.have.property('playerId', '47427aa0-f11a-4d24-abca-1295a46a46cd'); + expect(payload).to.have.property('mediaType', VIDEO); + expect(payload).to.have.property('type', 1); + }) + + it('should attach valid params to the payload when type is banner', function () { + const request = spec.buildRequests([bidRequestBanner], bidderRequest) + const payload = request.data.requests[0]; + expect(payload).to.be.an('object'); + expect(payload).to.have.property('playerId', '47427aa0-f11a-4d24-abca-1295a46a46cd'); + expect(payload).to.have.property('mediaType', BANNER); + expect(payload).to.have.property('type', 5); + }) + + it('passes gdpr if present', function () { + const request = spec.buildRequests([bidRequestVideo], {...bidderRequest, ...gdpr}) + const payload = request.data.requests[0]; + expect(payload).to.be.an('object'); + expect(payload.gdprConsent).to.eql(gdpr.gdprConsent) + }) + }) + + describe('interpretResponse', function () { + it('handles nobid responses', function () { + expect(spec.interpretResponse({body: {}}, {data: {meta: {}}}).length).to.equal(0) + expect(spec.interpretResponse({body: []}, {data: {meta: {}}}).length).to.equal(0) + }) + + const vastTag = 'https://video-library.stage.showheroes.com/commercial/wrapper?player_id=47427aa0-f11a-4d24-abca-1295a46a46cd&ad_bidder=showheroes-bs&master_shadt=1&description_url=https%3A%2F%2Fbid-service.stage.showheroes.com%2Fvast%2Fad%2Fcache%2F4840b920-40e1-4e09-9231-60bbf088c8d6' + const vastXml = '' + + const response = { + 'bids': [{ + 'cpm': 5, + 'currency': 'EUR', + 'bidId': '38b373e1e31c18', + 'video': {'width': 640, 'height': 480}, + 'vastTag': 'https:\/\/video-library.stage.showheroes.com\/commercial\/wrapper?player_id=47427aa0-f11a-4d24-abca-1295a46a46cd&ad_bidder=showheroes-bs&master_shadt=1&description_url=https%3A%2F%2Fbid-service.stage.showheroes.com%2Fvast%2Fad%2Fcache%2F4840b920-40e1-4e09-9231-60bbf088c8d6', + 'vastXml': vastXml, + }], + } + + it('should get correct bid response when type is video', function () { + const request = spec.buildRequests([bidRequestVideo], bidderRequest) + const expectedResponse = [ + { + 'cpm': 5, + 'creativeId': 'c_38b373e1e31c18', + 'currency': 'EUR', + 'width': 640, + 'height': 480, + 'mediaType': 'video', + 'netRevenue': true, + 'vastUrl': vastTag, + 'vastXml': vastXml, + 'requestId': '38b373e1e31c18', + 'ttl': 300, + 'adResponse': { + 'content': vastXml + } + } + ] + + const result = spec.interpretResponse({'body': response}, request) + expect(result).to.deep.equal(expectedResponse) + }) + + it('should get correct bid response when type is banner', function () { + const request = spec.buildRequests([bidRequestBanner], bidderRequest) + + const result = spec.interpretResponse({'body': response}, request) + expect(result[0]).to.have.property('mediaType', BANNER); + expect(result[0].ad).to.include('"'; + +describe('slimcutBidAdapter', function() { + const adapter = newBidder(spec); + + describe('inherited functions', function() { + it('exists and is a function', function() { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function() { + let bid = { + 'bidder': 'slimcut', + 'params': { + 'placementId': 83 + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '3c871ffa8ef14c', + 'bidderRequestId': 'b41642f1aee381', + 'auctionId': '4e156668c977d7' + }; + + it('should return true when required params found', function() { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when placementId is not valid (letters)', function() { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'placementId': 'ABCD' + }; + + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when placementId < 0', function() { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'placementId': -1 + }; + + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when required params are not passed', function() { + let bid = Object.assign({}, bid); + delete bid.params; + + bid.params = {}; + + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function() { + let bidRequests = [ + { + 'bidder': 'teads', + 'params': { + 'placementId': 10433394 + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '3c871ffa8ef14c', + 'bidderRequestId': 'b41642f1aee381', + 'auctionId': '4e156668c977d7', + 'deviceWidth': 1680 + } + ]; + + let bidderResquestDefault = { + 'auctionId': '4e156668c977d7', + 'bidderRequestId': 'b41642f1aee381', + 'timeout': 3000 + }; + + it('sends bid request to ENDPOINT via POST', function() { + const request = spec.buildRequests(bidRequests, bidderResquestDefault); + + expect(request.url).to.equal(ENDPOINT); + expect(request.method).to.equal('POST'); + }); + + it('should send GDPR to endpoint', function() { + let consentString = 'JRJ8RKfDeBNsERRDCSAAZ+A=='; + let bidderRequest = { + 'auctionId': '4e156668c977d7', + 'bidderRequestId': 'b41642f1aee381', + 'timeout': 3000, + 'gdprConsent': { + 'consentString': consentString, + 'gdprApplies': true, + 'vendorData': { + 'hasGlobalConsent': false + } + } + }; + + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.gdpr_iab).to.exist; + expect(payload.gdpr_iab.consent).to.equal(consentString); + }); + + it('should add referer info to payload', function () { + const bidRequest = Object.assign({}, bidRequests[0]) + const bidderRequest = { + refererInfo: { + referer: 'http://example.com/page.html', + reachedTop: true, + numIframes: 2 + } + } + const request = spec.buildRequests([bidRequest], bidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.referrer).to.exist; + expect(payload.referrer).to.deep.equal('http://example.com/page.html') + }); + }); + + describe('getUserSyncs', () => { + let bids = { + 'body': { + 'responses': [{ + 'ad': AD_SCRIPT, + 'cpm': 0.5, + 'currency': 'USD', + 'height': 250, + 'netRevenue': true, + 'requestId': '3ede2a3fa0db94', + 'ttl': 360, + 'width': 300, + 'creativeId': 'er2ee', + 'transactionId': 'deadb33f', + 'winUrl': 'https://sb.freeskreen.com/win' + }] + } + }; + + it('should get the correct number of sync urls', () => { + let urls = spec.getUserSyncs({iframeEnabled: true}, bids); + expect(urls.length).to.equal(1); + expect(urls[0].url).to.equal('//sb.freeskreen.com/async_usersync.html'); + }); + + it('should return no url if not iframe enabled', () => { + let urls = spec.getUserSyncs({iframeEnabled: false}, bids); + expect(urls.length).to.equal(0); + }); + }); + + describe('interpretResponse', function() { + let bids = { + 'body': { + 'responses': [{ + 'ad': AD_SCRIPT, + 'cpm': 0.5, + 'currency': 'USD', + 'height': 250, + 'netRevenue': true, + 'requestId': '3ede2a3fa0db94', + 'ttl': 360, + 'width': 300, + 'creativeId': 'er2ee', + 'transactionId': 'deadb33f', + 'winUrl': 'https://sb.freeskreen.com/win' + }] + } + }; + + it('should get correct bid response', function() { + let expectedResponse = [{ + 'cpm': 0.5, + 'width': 300, + 'height': 250, + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 360, + 'ad': AD_SCRIPT, + 'requestId': '3ede2a3fa0db94', + 'creativeId': 'er2ee', + 'transactionId': 'deadb33f', + 'winUrl': 'https://sb.freeskreen.com/win' + }]; + + let result = spec.interpretResponse(bids); + expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedResponse[0])); + }); + + it('handles nobid responses', function() { + let bids = { + 'body': { + 'responses': [] + } + }; + + let result = spec.interpretResponse(bids); + expect(result.length).to.equal(0); + }); + }); +}); diff --git a/test/spec/modules/smartadserverBidAdapter_spec.js b/test/spec/modules/smartadserverBidAdapter_spec.js index 91fb4e3e6a7..c5ae9e59054 100644 --- a/test/spec/modules/smartadserverBidAdapter_spec.js +++ b/test/spec/modules/smartadserverBidAdapter_spec.js @@ -103,7 +103,7 @@ describe('Smart bid adapter tests', function () { describe('gdpr tests', function () { afterEach(function () { config.resetConfig(); - $$PREBID_GLOBAL$$.requestBids.removeHook(requestBidsHook); + $$PREBID_GLOBAL$$.requestBids.removeAll(); }); it('Verify build request with GDPR', function () { diff --git a/test/spec/modules/smartrtbBidAdapter_spec.js b/test/spec/modules/smartrtbBidAdapter_spec.js new file mode 100644 index 00000000000..7477dbf9418 --- /dev/null +++ b/test/spec/modules/smartrtbBidAdapter_spec.js @@ -0,0 +1,161 @@ +import { expect } from 'chai' +import { spec, _getPlatform } from 'modules/smartrtbBidAdapter' +import { newBidder } from 'src/adapters/bidderFactory' + +const br = { + body: { + bids: [{ + bid_id: '123', + cpm: 1.23, + w: 300, + h: 250, + html: 'deadbeef', + crid: 'crid' + }], + pixels: [ + { type: 'image', url: 'http://smrtb.com/image' }, + { type: 'iframe', url: 'http://smrtb.com/iframe' } + ] + } +} + +const vr = { + body: { + bids: [{ + bid_id: 'abc', + cpm: 2.34, + w: 640, + h: 480, + vast_url: 'https://demo.tremorvideo.com/proddev/vast/vast_inline_nonlinear.xml', + crid: 'video_crid' + }], + pixels: [ + { type: 'image', url: 'http://smrtb.com/image' }, + { type: 'iframe', url: 'http://smrtb.com/iframe' } + ] + } +} + +describe('SmartRTBBidAdapter', function () { + const adapter = newBidder(spec) + + let bannerRequest = { + bidId: '123', + transactionId: '456', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + params: { + zoneId: 'N4zTDq3PPEHBIODv7cXK' + } + } + + let videoRequest = { + bidId: 'abc', + transactionId: 'def', + mediaTypes: { + video: { + playerDimension: [640, 480] + } + }, + params: { + zoneId: 'CK6gUYp58EGopLJnUvM2' + } + } + + describe('codes', 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 () { + it('should return true if all params present', 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 + }) + }) + + describe('buildRequests', function () { + let req = spec.buildRequests([ bannerRequest ], { refererInfo: { } }) + let rdata + + it('should return request object', function () { + expect(req).to.not.be.null + }) + + it('should build request data', function () { + expect(req.data).to.not.be.null + }) + + it('should include one request', function () { + rdata = JSON.parse(req.data) + expect(rdata.imps.length).to.equal(1) + }) + + it('should include all publisher params', function () { + expect(rdata.imps[0].zone_id !== null).to.be.true + }) + + it('should include media types', function () { + expect(rdata.imps[0].media_types !== null).to.be.true + }) + }) + + describe('interpretResponse', function () { + it('should form compliant banner bid object response', function () { + let ir = spec.interpretResponse(br, bannerRequest) + + expect(ir.length).to.equal(1) + + let en = ir[0] + + expect(en.requestId != null && + en.cpm != null && typeof en.cpm === 'number' && + en.width != null && typeof en.width === 'number' && + en.height != null && typeof en.height === 'number' && + en.ad != null && + en.creativeId != null + ).to.be.true + }) + it('should form compliant video object response', function () { + let ir = spec.interpretResponse(vr, videoRequest) + + expect(ir.length).to.equal(1) + + let en = ir[0] + + expect(en.requestId != null && + en.cpm != null && typeof en.cpm === 'number' && + en.width != null && typeof en.width === 'number' && + en.height != null && typeof en.height === 'number' && + (en.vastUrl != null || en.vastXml != null) && + en.creativeId != null + ).to.be.true + }) + }) + + describe('getUserSyncs', function () { + it('should return iframe sync', function () { + let sync = spec.getUserSyncs({ iframeEnabled: true }, [br]) + expect(sync.length).to.equal(1) + expect(sync[0].type === 'iframe') + expect(typeof sync[0].url === 'string') + }) + + it('should return pixel sync', function () { + let sync = spec.getUserSyncs({ pixelEnabled: true }, [br]) + expect(sync.length).to.equal(1) + expect(sync[0].type === 'image') + expect(typeof sync[0].url === 'string') + }) + }) +}) diff --git a/test/spec/modules/smilewantedBidAdapter_spec.js b/test/spec/modules/smilewantedBidAdapter_spec.js new file mode 100644 index 00000000000..144c7ca60f6 --- /dev/null +++ b/test/spec/modules/smilewantedBidAdapter_spec.js @@ -0,0 +1,325 @@ +import { expect } from 'chai'; +import { spec } from 'modules/smilewantedBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; +import { config } from 'src/config'; +import * as utils from 'src/utils'; +import { requestBidsHook } from 'modules/consentManagement'; + +const DISPLAY_REQUEST = [{ + adUnitCode: 'sw_300x250', + bidId: '12345', + sizes: [ + [300, 250], + [300, 200] + ], + bidder: 'smilewanted', + params: { + zoneId: 1, + bidfloor: 2.50 + }, + requestId: 'request_abcd1234', + transactionId: 'trans_abcd1234' +}]; + +const BID_RESPONSE_DISPLAY = { + body: { + cpm: 3, + width: 300, + height: 250, + creativeId: 'crea_sw_1', + currency: 'EUR', + isNetCpm: true, + ttl: 300, + ad: '< --- sw script --- >', + cSyncUrl: 'https://csync.smilewanted.com' + } +}; + +const VIDEO_INSTREAM_REQUEST = [{ + code: 'video1', + mediaTypes: { + video: {} + }, + sizes: [ + [640, 480] + ], + bidder: 'smilewanted', + params: { + zoneId: 2, + bidfloor: 2.50 + }, + requestId: 'request_abcd1234', + transactionId: 'trans_abcd1234' +}]; + +const BID_RESPONSE_VIDEO_INSTREAM = { + body: { + cpm: 3, + width: 640, + height: 480, + creativeId: 'crea_sw_2', + currency: 'EUR', + isNetCpm: true, + ttl: 300, + ad: 'https://vast.smilewanted.com', + cSyncUrl: 'https://csync.smilewanted.com', + formatTypeSw: 'video_instream' + } +}; + +const VIDEO_OUTSTREAM_REQUEST = [{ + code: 'video1', + mediaTypes: { + video: {} + }, + sizes: [ + [640, 480] + ], + bidder: 'smilewanted', + params: { + zoneId: 3, + bidfloor: 2.50 + }, + requestId: 'request_abcd1234', + transactionId: 'trans_abcd1234' +}]; + +const BID_RESPONSE_VIDEO_OUTSTREAM = { + body: { + cpm: 3, + width: 640, + height: 480, + creativeId: 'crea_sw_3', + currency: 'EUR', + isNetCpm: true, + ttl: 300, + ad: 'https://vast.smilewanted.com', + cSyncUrl: 'https://csync.smilewanted.com', + OustreamTemplateUrl: 'https://prebid.smilewanted.com/scripts_outstream/infeed.js', + formatTypeSw: 'video_outstream' + } +}; + +// Default params with optional ones +describe('smilewantedBidAdapterTests', function () { + it('SmileWanted - Verify build request', function () { + config.setConfig({ + 'currency': { + 'adServerCurrency': 'EUR' + } + }); + + const requestDisplay = spec.buildRequests(DISPLAY_REQUEST); + expect(requestDisplay[0]).to.have.property('url').and.to.equal('https://prebid.smilewanted.com'); + expect(requestDisplay[0]).to.have.property('method').and.to.equal('POST'); + const requestDisplayContent = JSON.parse(requestDisplay[0].data); + expect(requestDisplayContent).to.have.property('zoneId').and.to.equal(1); + expect(requestDisplayContent).to.have.property('currencyCode').and.to.equal('EUR'); + expect(requestDisplayContent).to.have.property('bidfloor').and.to.equal(2.50); + expect(requestDisplayContent).to.have.property('sizes'); + expect(requestDisplayContent.sizes[0]).to.have.property('w').and.to.equal(300); + expect(requestDisplayContent.sizes[0]).to.have.property('h').and.to.equal(250); + expect(requestDisplayContent.sizes[1]).to.have.property('w').and.to.equal(300); + expect(requestDisplayContent.sizes[1]).to.have.property('h').and.to.equal(200); + expect(requestDisplayContent).to.have.property('transactionId').and.to.not.equal(null).and.to.not.be.undefined; + + const requestVideoInstream = spec.buildRequests(VIDEO_INSTREAM_REQUEST); + expect(requestVideoInstream[0]).to.have.property('url').and.to.equal('https://prebid.smilewanted.com'); + expect(requestVideoInstream[0]).to.have.property('method').and.to.equal('POST'); + const requestVideoInstreamContent = JSON.parse(requestVideoInstream[0].data); + expect(requestVideoInstreamContent).to.have.property('zoneId').and.to.equal(2); + expect(requestVideoInstreamContent).to.have.property('currencyCode').and.to.equal('EUR'); + expect(requestVideoInstreamContent).to.have.property('bidfloor').and.to.equal(2.50); + expect(requestVideoInstreamContent).to.have.property('sizes'); + expect(requestVideoInstreamContent.sizes[0]).to.have.property('w').and.to.equal(640); + expect(requestVideoInstreamContent.sizes[0]).to.have.property('h').and.to.equal(480); + expect(requestVideoInstreamContent).to.have.property('transactionId').and.to.not.equal(null).and.to.not.be.undefined; + + const requestVideoOutstream = spec.buildRequests(VIDEO_OUTSTREAM_REQUEST); + expect(requestVideoOutstream[0]).to.have.property('url').and.to.equal('https://prebid.smilewanted.com'); + expect(requestVideoOutstream[0]).to.have.property('method').and.to.equal('POST'); + const requestVideoOutstreamContent = JSON.parse(requestVideoOutstream[0].data); + expect(requestVideoOutstreamContent).to.have.property('zoneId').and.to.equal(3); + expect(requestVideoOutstreamContent).to.have.property('currencyCode').and.to.equal('EUR'); + expect(requestVideoOutstreamContent).to.have.property('bidfloor').and.to.equal(2.50); + expect(requestVideoOutstreamContent).to.have.property('sizes'); + expect(requestVideoOutstreamContent.sizes[0]).to.have.property('w').and.to.equal(640); + expect(requestVideoOutstreamContent.sizes[0]).to.have.property('h').and.to.equal(480); + expect(requestVideoOutstreamContent).to.have.property('transactionId').and.to.not.equal(null).and.to.not.be.undefined; + }); + + it('SmileWanted - Verify build request with referrer', function () { + const request = spec.buildRequests(DISPLAY_REQUEST, { + refererInfo: { + referer: 'http://localhost/Prebid.js/integrationExamples/gpt/hello_world.html' + } + }); + const requestContent = JSON.parse(request[0].data); + expect(requestContent).to.have.property('pageDomain').and.to.equal('http://localhost/Prebid.js/integrationExamples/gpt/hello_world.html'); + }); + + describe('gdpr tests', function () { + afterEach(function () { + config.resetConfig(); + $$PREBID_GLOBAL$$.requestBids.removeAll(); + }); + + it('SmileWanted - Verify build request with GDPR', function () { + config.setConfig({ + 'currency': { + 'adServerCurrency': 'EUR' + }, + consentManagement: { + cmp: 'iab', + consentRequired: true, + timeout: 1000, + allowAuctionWithoutConsent: true + } + }); + const request = spec.buildRequests(DISPLAY_REQUEST, { + gdprConsent: { + consentString: 'BOO_ch7OO_ch7AKABBENA2-AAAAZ97_______9______9uz_Gv_r_f__33e8_39v_h_7_u___m_-zzV4-_lvQV1yPA1OrfArgFA', + gdprApplies: true + } + }); + const requestContent = JSON.parse(request[0].data); + expect(requestContent).to.have.property('gdpr').and.to.equal(true); + expect(requestContent).to.have.property('gdpr_consent').and.to.equal('BOO_ch7OO_ch7AKABBENA2-AAAAZ97_______9______9uz_Gv_r_f__33e8_39v_h_7_u___m_-zzV4-_lvQV1yPA1OrfArgFA'); + }); + + it('SmileWanted - Verify build request with GDPR without gdprApplies', function () { + config.setConfig({ + 'currency': { + 'adServerCurrency': 'EUR' + }, + consentManagement: { + cmp: 'iab', + consentRequired: true, + timeout: 1000, + allowAuctionWithoutConsent: true + } + }); + const request = spec.buildRequests(DISPLAY_REQUEST, { + gdprConsent: { + consentString: 'BOO_ch7OO_ch7AKABBENA2-AAAAZ97_______9______9uz_Gv_r_f__33e8_39v_h_7_u___m_-zzV4-_lvQV1yPA1OrfArgFA' + } + }); + const requestContent = JSON.parse(request[0].data); + expect(requestContent).to.not.have.property('gdpr'); + expect(requestContent).to.have.property('gdpr_consent').and.to.equal('BOO_ch7OO_ch7AKABBENA2-AAAAZ97_______9______9uz_Gv_r_f__33e8_39v_h_7_u___m_-zzV4-_lvQV1yPA1OrfArgFA'); + }); + }); + + it('SmileWanted - Verify parse response - Display', function () { + const request = spec.buildRequests(DISPLAY_REQUEST); + const bids = spec.interpretResponse(BID_RESPONSE_DISPLAY, request[0]); + expect(bids).to.have.lengthOf(1); + const bid = bids[0]; + expect(bid.cpm).to.equal(3); + expect(bid.ad).to.equal('< --- sw script --- >'); + expect(bid.width).to.equal(300); + expect(bid.height).to.equal(250); + expect(bid.creativeId).to.equal('crea_sw_1'); + expect(bid.currency).to.equal('EUR'); + expect(bid.netRevenue).to.equal(true); + expect(bid.ttl).to.equal(300); + expect(bid.requestId).to.equal(DISPLAY_REQUEST[0].bidId); + + expect(function () { + spec.interpretResponse(BID_RESPONSE_DISPLAY, { + data: 'invalid Json' + }) + }).to.not.throw(); + }); + + it('SmileWanted - Verify parse response - Video Instream', function () { + const request = spec.buildRequests(VIDEO_INSTREAM_REQUEST); + const bids = spec.interpretResponse(BID_RESPONSE_VIDEO_INSTREAM, request[0]); + expect(bids).to.have.lengthOf(1); + const bid = bids[0]; + expect(bid.cpm).to.equal(3); + expect(bid.ad).to.equal(null); + expect(bid.vastUrl).to.equal('https://vast.smilewanted.com'); + expect(bid.width).to.equal(640); + expect(bid.height).to.equal(480); + expect(bid.creativeId).to.equal('crea_sw_2'); + expect(bid.currency).to.equal('EUR'); + expect(bid.netRevenue).to.equal(true); + expect(bid.ttl).to.equal(300); + expect(bid.requestId).to.equal(VIDEO_INSTREAM_REQUEST[0].bidId); + + expect(function () { + spec.interpretResponse(BID_RESPONSE_VIDEO_INSTREAM, { + data: 'invalid Json' + }) + }).to.not.throw(); + }); + + it('SmileWanted - Verify parse response - Video Oustream', function () { + const request = spec.buildRequests(VIDEO_OUTSTREAM_REQUEST); + const bids = spec.interpretResponse(BID_RESPONSE_VIDEO_OUTSTREAM, request[0]); + expect(bids).to.have.lengthOf(1); + const bid = bids[0]; + expect(bid.cpm).to.equal(3); + expect(bid.vastUrl).to.equal('https://vast.smilewanted.com'); + expect(bid.renderer.url).to.equal('https://prebid.smilewanted.com/scripts_outstream/infeed.js'); + expect(bid.width).to.equal(640); + expect(bid.height).to.equal(480); + expect(bid.creativeId).to.equal('crea_sw_3'); + expect(bid.currency).to.equal('EUR'); + expect(bid.netRevenue).to.equal(true); + expect(bid.ttl).to.equal(300); + expect(bid.requestId).to.equal(VIDEO_OUTSTREAM_REQUEST[0].bidId); + + expect(function () { + spec.interpretResponse(BID_RESPONSE_VIDEO_OUTSTREAM, { + data: 'invalid Json' + }) + }).to.not.throw(); + }); + + it('SmileWanted - Verify bidder code', function () { + expect(spec.code).to.equal('smilewanted'); + }); + + it('SmileWanted - Verify bidder aliases', function () { + expect(spec.aliases).to.have.lengthOf(2); + expect(spec.aliases[0]).to.equal('smile'); + expect(spec.aliases[1]).to.equal('sw'); + }); + + it('SmileWanted - Verify if bid request valid', function () { + expect(spec.isBidRequestValid(DISPLAY_REQUEST[0])).to.equal(true); + expect(spec.isBidRequestValid({ + params: { + zoneId: 1234 + } + })).to.equal(true); + }); + + it('SmileWanted - Verify if params(zoneId) is not passed', function () { + expect(spec.isBidRequestValid({})).to.equal(false); + expect(spec.isBidRequestValid({ + params: {} + })).to.equal(false); + }); + + it('SmileWanted - Verify user sync', function () { + var syncs = spec.getUserSyncs({ + iframeEnabled: true + }, [BID_RESPONSE_DISPLAY]); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.equal('https://csync.smilewanted.com'); + + syncs = spec.getUserSyncs({ + iframeEnabled: false + }, [BID_RESPONSE_DISPLAY]); + expect(syncs).to.have.lengthOf(0); + + syncs = spec.getUserSyncs({ + iframeEnabled: true + }, []); + expect(syncs).to.have.lengthOf(0); + }); +}); diff --git a/test/spec/modules/somoaudienceBidAdapter_spec.js b/test/spec/modules/somoBidAdapter_spec.js similarity index 96% rename from test/spec/modules/somoaudienceBidAdapter_spec.js rename to test/spec/modules/somoBidAdapter_spec.js index bdd2dade96f..16fd43841b7 100644 --- a/test/spec/modules/somoaudienceBidAdapter_spec.js +++ b/test/spec/modules/somoBidAdapter_spec.js @@ -1,19 +1,19 @@ import {expect} from 'chai'; -import {spec} from 'modules/somoaudienceBidAdapter'; +import {spec} from 'modules/somoBidAdapter'; import * as utils from 'src/utils'; describe('Somo Audience Adapter Tests', function () { describe('isBidRequestValid', function () { it('should return false when given an invalid bid', function () { const bid = { - bidder: 'somoaudience', + bidder: 'somo', }; const isValid = spec.isBidRequestValid(bid); expect(isValid).to.equal(false); }); it('should return true when given a placementId bid', function () { const bid = { - bidder: 'somoaudience', + bidder: 'somo', params: { placementId: 'test' } @@ -27,7 +27,7 @@ describe('Somo Audience Adapter Tests', function () { describe('buildBannerRequests', function () { it('should properly build a banner request with type not defined and sizes not defined', function () { const bidRequests = [{ - bidder: 'somoaudience', + bidder: 'somo', params: { placementId: 'test' } @@ -49,7 +49,7 @@ describe('Somo Audience Adapter Tests', function () { it('should properly build a banner request with sizes defined in 2d array', function () { const bidRequests = [{ - bidder: 'somoaudience', + bidder: 'somo', sizes: [[300, 250]], params: { placementId: 'test' @@ -71,7 +71,7 @@ describe('Somo Audience Adapter Tests', function () { }); it('should properly build a banner request with sizes defined in 1d array', function () { const bidRequests = [{ - bidder: 'somoaudience', + bidder: 'somo', sizes: [300, 250], params: { placementId: 'test' @@ -99,7 +99,7 @@ describe('Somo Audience Adapter Tests', function () { it('should populate optional banner parameters', function () { const bidRequests = [ { - bidder: 'somoaudience', + bidder: 'somo', sizes: [[300, 200]], mediaType: 'banner', params: { @@ -128,7 +128,7 @@ describe('Somo Audience Adapter Tests', function () { describe('buildVideoRequests', function () { it('should properly build a video request with sizes defined', function () { const bidRequests = [{ - bidder: 'somoaudience', + bidder: 'somo', mediaTypes: { video: {} }, @@ -151,7 +151,7 @@ describe('Somo Audience Adapter Tests', function () { it('should properly build a video request with sizes defined in 2d array', function () { const bidRequests = [{ - bidder: 'somoaudience', + bidder: 'somo', mediaTypes: { video: {} }, @@ -173,7 +173,7 @@ describe('Somo Audience Adapter Tests', function () { }); it('should properly build a video request with sizes not defined', function () { const bidRequests = [{ - bidder: 'somoaudience', + bidder: 'somo', mediaType: 'video', params: { placementId: 'test' @@ -199,7 +199,7 @@ describe('Somo Audience Adapter Tests', function () { it('should populate optional video parameters', function () { const bidRequests = [ { - bidder: 'somoaudience', + bidder: 'somo', sizes: [[200, 300]], mediaType: 'video', params: { @@ -242,7 +242,7 @@ describe('Somo Audience Adapter Tests', function () { describe('buildSiteRequests', function () { it('should fill in basic site parameters', function () { const bidRequests = [{ - bidder: 'somoaudience', + bidder: 'somo', params: { placementId: 'test' } @@ -258,7 +258,7 @@ describe('Somo Audience Adapter Tests', function () { it('should fill in optional site parameters', function () { const bidRequests = [{ - bidder: 'somoaudience', + bidder: 'somo', params: { placementId: 'test', site: { @@ -285,7 +285,7 @@ describe('Somo Audience Adapter Tests', function () { describe('buildAppRequests', function () { it('should fill in app parameters', function () { const bidRequests = [{ - bidder: 'somoaudience', + bidder: 'somo', params: { placementId: 'test', app: { @@ -325,7 +325,7 @@ describe('Somo Audience Adapter Tests', function () { it('should properly build request with gdpr consent', function () { const bidRequests = [{ - bidder: 'somoaudience', + bidder: 'somo', params: { placementId: 'test' } @@ -342,7 +342,7 @@ describe('Somo Audience Adapter Tests', function () { it('should properly build request with gdpr not applies', function () { bidderRequest.gdprConsent.gdprApplies = false; const bidRequests = [{ - bidder: 'somoaudience', + bidder: 'somo', params: { placementId: 'test' } @@ -362,7 +362,7 @@ describe('Somo Audience Adapter Tests', function () { it('should populate optional parameters', function () { const bidRequests = [ { - bidder: 'somoaudience', + bidder: 'somo', params: { placementId: 'test', bcat: ['IAB-2', 'IAB-7'], @@ -389,7 +389,7 @@ describe('Somo Audience Adapter Tests', function () { it('Verify banner parse response', function () { const bidRequests = [ { - bidder: 'somoaudience', + bidder: 'somo', params: { placementId: 'test', }, @@ -417,7 +417,7 @@ describe('Somo Audience Adapter Tests', function () { it('Verify video parse response', function () { const bidRequests = [ { - bidder: 'somoaudience', + bidder: 'somo', mediaTypes: { video: { } diff --git a/test/spec/modules/sonobiAnalyticsAdapter_spec.js b/test/spec/modules/sonobiAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..389dfee34f9 --- /dev/null +++ b/test/spec/modules/sonobiAnalyticsAdapter_spec.js @@ -0,0 +1,89 @@ +import sonobiAnalytics from 'modules/sonobiAnalyticsAdapter'; +import {expect} from 'chai'; +let events = require('src/events'); +let adapterManager = require('src/adapterManager').default; +let constants = require('src/constants.json'); + +describe('Sonobi Prebid Analytic', function () { + let xhr; + let requests = []; + var clock; + + describe('enableAnalytics', function () { + beforeEach(function () { + requests = []; + xhr = sinon.useFakeXMLHttpRequest(); + xhr.onCreate = request => requests.push(request); + sinon.stub(events, 'getEvents').returns([]); + clock = sinon.useFakeTimers(Date.now()); + }); + + afterEach(function () { + xhr.restore(); + events.getEvents.restore(); + clock.restore(); + }); + + after(function () { + sonobiAnalytics.disableAnalytics(); + }); + + it('should catch all events', function (done) { + const initOptions = { + pubId: 'A3B254F', + siteId: '1234', + delay: 100 + }; + + sonobiAnalytics.enableAnalytics(initOptions) + + const bid = { + bidderCode: 'sonobi_test_bid', + width: 300, + height: 250, + statusMessage: 'Bid available', + adId: '1234', + auctionId: '13', + responseTimestamp: 1496410856397, + requestTimestamp: 1496410856295, + cpm: 1.13, + bidder: 'sonobi', + adUnitCode: 'dom-sample-id', + timeToRespond: 100, + placementCode: 'placementtest' + }; + + // Step 1: Initialize adapter + adapterManager.enableAnalytics({ + provider: 'sonobi', + options: initOptions + }); + + // Step 2: Send init auction event + events.emit(constants.EVENTS.AUCTION_INIT, {config: initOptions, auctionId: '13', timestamp: Date.now()}); + + expect(sonobiAnalytics.initOptions).to.have.property('pubId', 'A3B254F'); + expect(sonobiAnalytics.initOptions).to.have.property('siteId', '1234'); + expect(sonobiAnalytics.initOptions).to.have.property('delay', 100); + // Step 3: Send bid requested event + events.emit(constants.EVENTS.BID_REQUESTED, { bids: [bid], auctionId: '13' }); + + // Step 4: Send bid response event + events.emit(constants.EVENTS.BID_RESPONSE, bid); + + // Step 5: Send bid won event + events.emit(constants.EVENTS.BID_WON, bid); + + // Step 6: Send bid timeout event + events.emit(constants.EVENTS.BID_TIMEOUT, {auctionId: '13'}); + + // Step 7: Send auction end event + events.emit(constants.EVENTS.AUCTION_END, {auctionId: '13', bidsReceived: [bid]}); + + clock.tick(5000); + expect(requests).to.have.length(1); + expect(JSON.parse(requests[0].requestBody)).to.have.length(3) + done(); + }); + }); +}); diff --git a/test/spec/modules/sonobiBidAdapter_spec.js b/test/spec/modules/sonobiBidAdapter_spec.js index 349a2e80263..a6bf88cfc74 100644 --- a/test/spec/modules/sonobiBidAdapter_spec.js +++ b/test/spec/modules/sonobiBidAdapter_spec.js @@ -1,6 +1,7 @@ import { expect } from 'chai' import { spec, _getPlatform } from 'modules/sonobiBidAdapter' import { newBidder } from 'src/adapters/bidderFactory' +import {userSync} from '../../../src/userSync'; describe('SonobiBidAdapter', function () { const adapter = newBidder(spec) @@ -18,90 +19,247 @@ describe('SonobiBidAdapter', function () { }) describe('.isBidRequestValid', function () { - let bid = { - 'bidder': 'sonobi', - 'params': { - 'ad_unit': '/7780971/sparks_prebid_MR', - 'sizes': [[300, 250], [300, 600]], - 'floor': '1' - }, - 'adUnitCode': 'adunit-code', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - } + it('should return false if there are no params', () => { + const bid = { + 'bidder': 'sonobi', + 'adUnitCode': 'adunit-code', + 'mediaTypes': { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); - it('should return true when required params found', function () { - expect(spec.isBidRequestValid(bid)).to.equal(true) - }) + it('should return false if there is no placement_id param and no ad_unit param', () => { + const bid = { + 'bidder': 'sonobi', + 'adUnitCode': 'adunit-code', + params: { + placementId: '1a2b3c4d5e6f1a2b3c4d', + }, + 'mediaTypes': { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); - it('should return true when bid.params.placement_id and bid.params.sizes are found', function () { - let bid = Object.assign({}, bid) - delete bid.params - delete bid.sizes - bid.params = { - 'placement_id': '1a2b3c4d5e6f1a2b3c4d', - 'sizes': [[300, 250], [300, 600]], - } + it('should return false if there is no mediaTypes', () => { + const bid = { + 'bidder': 'sonobi', + 'adUnitCode': 'adunit-code', + params: { + placement_id: '1a2b3c4d5e6f1a2b3c4d' + }, + 'mediaTypes': { + }, + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); - expect(spec.isBidRequestValid(bid)).to.equal(true) - }) + it('should return true if the bid is valid', () => { + const bid = { + 'bidder': 'sonobi', + 'adUnitCode': 'adunit-code', + params: { + placement_id: '1a2b3c4d5e6f1a2b3c4d' + }, + 'mediaTypes': { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); - it('should return true when bid.params.placement_id and bid.sizes are found', function () { - let bid = Object.assign({}, bid) - delete bid.params - bid.sizes = [[300, 250], [300, 600]] - bid.params = { - 'placement_id': '1a2b3c4d5e6f1a2b3c4d', - } + describe('banner', () => { + it('should return false if there are no banner sizes and no param sizes', () => { + const bid = { + 'bidder': 'sonobi', + 'adUnitCode': 'adunit-code', + params: { + placement_id: '1a2b3c4d5e6f1a2b3c4d' + }, + 'mediaTypes': { + banner: { - expect(spec.isBidRequestValid(bid)).to.equal(true) - }) + } + }, + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); - it('should return true when bid.params.ad_unit and bid.params.sizes are found', function () { - let bid = Object.assign({}, bid) - delete bid.params - delete bid.sizes - bid.params = { - 'ad_unit': '/7780971/sparks_prebid_MR', - 'sizes': [[300, 250], [300, 600]], - } + it('should return true if there is banner sizes and no param sizes', () => { + const bid = { + 'bidder': 'sonobi', + 'adUnitCode': 'adunit-code', + params: { + placement_id: '1a2b3c4d5e6f1a2b3c4d' + }, + 'mediaTypes': { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); - expect(spec.isBidRequestValid(bid)).to.equal(true) - }) + it('should return true if there is param sizes and no banner sizes', () => { + const bid = { + 'bidder': 'sonobi', + 'adUnitCode': 'adunit-code', + params: { + placement_id: '1a2b3c4d5e6f1a2b3c4d', + sizes: [[300, 250], [300, 600]] + }, + 'mediaTypes': { + banner: { + } + }, + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + }); - it('should return true when bid.params.ad_unit and bid.sizes are found', function () { - let bid = Object.assign({}, bid) - delete bid.params - bid.sizes = [[300, 250], [300, 600]] - bid.params = { - 'ad_unit': '/7780971/sparks_prebid_MR', - } + describe('video', () => { + describe('instream', () => { + it('should return false if there is no playerSize defined in the video mediaType', () => { + const bid = { + 'bidder': 'sonobi', + 'adUnitCode': 'adunit-code', + params: { + placement_id: '1a2b3c4d5e6f1a2b3c4d', + sizes: [[300, 250], [300, 600]] + }, + 'mediaTypes': { + video: { + context: 'instream' + } + }, + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); - expect(spec.isBidRequestValid(bid)).to.equal(true) - }) + it('should return true if there is playerSize defined on the video mediaType', () => { + const bid = { + 'bidder': 'sonobi', + 'adUnitCode': 'adunit-code', + params: { + placement_id: '1a2b3c4d5e6f1a2b3c4d', + }, + 'mediaTypes': { + video: { + context: 'instream', + playerSize: [300, 250] + } + }, + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + }); - it('should return false when no params are found', function () { - let bid = Object.assign({}, bid) - delete bid.params - expect(spec.isBidRequestValid(bid)).to.equal(false) - }) + describe('outstream', () => { + it('should return false if there is no param sizes', () => { + const bid = { + 'bidder': 'sonobi', + 'adUnitCode': 'adunit-code', + params: { + placement_id: '1a2b3c4d5e6f1a2b3c4d', + }, + 'mediaTypes': { + video: { + context: 'outstream', + playerSize: [300, 250] + } + }, + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); - it('should return false when bid.params.placement_id and bid.params.ad_unit are not found', function () { - let bid = Object.assign({}, bid) - delete bid.params - bid.params = { - 'placement_id': 0, - 'ad_unit': 0, - 'sizes': [[300, 250], [300, 600]], - } - expect(spec.isBidRequestValid(bid)).to.equal(false) - }) - }) + it('should return true if there is param sizes', () => { + const bid = { + 'bidder': 'sonobi', + 'adUnitCode': 'adunit-code', + params: { + placement_id: '1a2b3c4d5e6f1a2b3c4d', + sizes: [300, 250] + + }, + 'mediaTypes': { + video: { + context: 'outstream' + } + }, + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + }); + }); + }); describe('.buildRequests', function () { + beforeEach(function() { + sinon.stub(userSync, 'canBidderRegisterSync'); + }); + afterEach(function() { + userSync.canBidderRegisterSync.restore(); + }); let bidRequest = [{ + 'schain': { + 'ver': '1.0', + 'complete': 1, + 'nodes': [ + { + 'asi': 'indirectseller.com', + 'sid': '00001', + 'hp': 1 + }, + { + 'asi': 'indirectseller-2.com', + 'sid': '00002', + 'hp': 0 + }, + ] + }, 'bidder': 'sonobi', 'params': { 'placement_id': '1a2b3c4d5e6f1a2b3c4d', @@ -136,11 +294,58 @@ describe('SonobiBidAdapter', function () { 'vendorData': {}, 'gdprApplies': true }, + 'refererInfo': { + 'numIframes': 0, + 'reachedTop': true, + 'referer': 'http://example.com', + 'stack': ['http://example.com'] + } }; + it('should include the digitrust id and keyv', () => { + window.DigiTrust = { + getUser: function () { + } + }; + let sandbox = sinon.sandbox.create(); + sandbox.stub(window.DigiTrust, 'getUser').callsFake(() => + ({ + success: true, + identity: { + id: 'Vb0YJIxTMJV4W0GHRdJ3MwyiOVYJjYEgc2QYdBSG', + keyv: 4, + version: 2, + privacy: {} + } + }) + ); + const bidRequests = spec.buildRequests(bidRequest, bidderRequests) + expect(bidRequests.data.digid).to.equal('Vb0YJIxTMJV4W0GHRdJ3MwyiOVYJjYEgc2QYdBSG'); + expect(bidRequests.data.digkeyv).to.equal(4); + sandbox.restore(); + delete window.DigiTrust; + }); + + it('should not include the digitrust id and keyv', () => { + window.DigiTrust = { + getUser: function () { + } + }; + let sandbox = sinon.sandbox.create(); + sandbox.stub(window.DigiTrust, 'getUser').callsFake(() => + ({ + success: false + }) + ); + const bidRequests = spec.buildRequests(bidRequest, bidderRequests) + expect(bidRequests.data.digid).to.be.undefined; + expect(bidRequests.data.digkeyv).to.be.undefined; + sandbox.restore(); + delete window.DigiTrust; + }) it('should return a properly formatted request', function () { - const bidRequests = spec.buildRequests(bidRequest) - const bidRequestsPageViewID = spec.buildRequests(bidRequest) + const bidRequests = spec.buildRequests(bidRequest, bidderRequests) + const bidRequestsPageViewID = 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.key_maker).to.deep.equal(JSON.stringify(keyMakerData)) @@ -161,6 +366,12 @@ describe('SonobiBidAdapter', function () { expect(bidRequests.data.consent_string).to.equal('BOJ/P2HOJ/P2HABABMAAAAAZ+A==') }) + it('should return a properly formatted request with referer', function () { + bidRequest[0].params.referrer = '' + const bidRequests = spec.buildRequests(bidRequest, bidderRequests) + expect(bidRequests.data.ref).to.equal('http://example.com') + }) + it('should return a properly formatted request with GDPR applies set to false', function () { bidderRequests.gdprConsent.gdprApplies = false; const bidRequests = spec.buildRequests(bidRequest, bidderRequests) @@ -176,6 +387,12 @@ describe('SonobiBidAdapter', function () { 'vendorData': {}, 'gdprApplies': false }, + 'refererInfo': { + 'numIframes': 0, + 'reachedTop': true, + 'referer': 'http://example.com', + 'stack': ['http://example.com'] + } }; const bidRequests = spec.buildRequests(bidRequest, bidderRequests) expect(bidRequests.url).to.equal('https://apex.go.sonobi.com/trinity.json') @@ -190,6 +407,12 @@ describe('SonobiBidAdapter', function () { 'vendorData': {}, 'gdprApplies': true }, + 'refererInfo': { + 'numIframes': 0, + 'reachedTop': true, + 'referer': 'http://example.com', + 'stack': ['http://example.com'] + } }; const bidRequests = spec.buildRequests(bidRequest, bidderRequests) expect(bidRequests.url).to.equal('https://apex.go.sonobi.com/trinity.json') @@ -200,7 +423,7 @@ describe('SonobiBidAdapter', function () { it('should return a properly formatted request with hfa', function () { bidRequest[0].params.hfa = 'hfakey' bidRequest[1].params.hfa = 'hfakey' - const bidRequests = spec.buildRequests(bidRequest) + 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 @@ -209,9 +432,113 @@ describe('SonobiBidAdapter', function () { }) it('should return null if there is nothing to bid on', function () { - const bidRequests = spec.buildRequests([{params: {}}]) + const bidRequests = spec.buildRequests([{params: {}}], bidderRequests) expect(bidRequests).to.equal(null); }) + + it('should return a properly formatted request with commonid as hfa', function () { + delete bidRequest[0].params.hfa; + delete bidRequest[1].params.hfa; + bidRequest[0].crumbs = {'pubcid': 'abcd-efg-0101'}; + bidRequest[1].crumbs = {'pubcid': 'abcd-efg-0101'}; + const bidRequests = spec.buildRequests(bidRequest, bidderRequests); + expect(bidRequests.url).to.equal('https://apex.go.sonobi.com/trinity.json'); + expect(bidRequests.method).to.equal('GET'); + expect(bidRequests.data.ref).not.to.be.empty; + expect(bidRequests.data.s).not.to.be.empty; + expect(bidRequests.data.hfa).to.equal('PRE-abcd-efg-0101'); + }); + + it('should return a properly formatted request with commonid from User ID as hfa', function () { + delete bidRequest[0].params.hfa; + delete bidRequest[1].params.hfa; + bidRequest[0].userId = {'pubcid': 'abcd-efg-0101'}; + bidRequest[1].userId = {'pubcid': 'abcd-efg-0101'}; + const bidRequests = spec.buildRequests(bidRequest, bidderRequests); + expect(bidRequests.url).to.equal('https://apex.go.sonobi.com/trinity.json'); + expect(bidRequests.method).to.equal('GET'); + expect(bidRequests.data.ref).not.to.be.empty; + expect(bidRequests.data.s).not.to.be.empty; + expect(bidRequests.data.hfa).to.equal('PRE-abcd-efg-0101'); + delete bidRequest[0].userId; + delete bidRequest[1].userId; + }) + + it('should return a properly formatted request with unified id from User ID as tdid', function () { + delete bidRequest[0].params.tdid; + delete bidRequest[1].params.tdid; + bidRequest[0].userId = {'tdid': 'td-abcd-efg-0101'}; + bidRequest[1].userId = {'tdid': 'td-abcd-efg-0101'}; + const bidRequests = spec.buildRequests(bidRequest, bidderRequests); + expect(bidRequests.url).to.equal('https://apex.go.sonobi.com/trinity.json'); + expect(bidRequests.method).to.equal('GET'); + expect(bidRequests.data.ref).not.to.be.empty; + expect(bidRequests.data.s).not.to.be.empty; + expect(bidRequests.data.tdid).to.equal('td-abcd-efg-0101'); + }) + + it('should return a properly formatted request with hfa preferred over commonid', function () { + bidRequest[0].params.hfa = 'hfakey'; + bidRequest[1].params.hfa = 'hfakey'; + bidRequest[0].crumbs = {'pubcid': 'abcd-efg-0101'}; + bidRequest[1].crumbs = {'pubcid': 'abcd-efg-0101'}; + const bidRequests = spec.buildRequests(bidRequest, bidderRequests) + expect(bidRequests.url).to.equal('https://apex.go.sonobi.com/trinity.json') + expect(bidRequests.method).to.equal('GET') + expect(bidRequests.data.ref).not.to.be.empty + expect(bidRequests.data.s).not.to.be.empty + expect(bidRequests.data.hfa).to.equal('hfakey') + }) + + it('should set ius as 0 if Sonobi cannot drop iframe pixels', function () { + userSync.canBidderRegisterSync.returns(false); + const bidRequests = spec.buildRequests(bidRequest, bidderRequests); + expect(bidRequests.data.ius).to.equal(0); + }); + + it('should set ius as 1 if Sonobi can drop iframe pixels', function() { + userSync.canBidderRegisterSync.returns(true); + const bidRequests = spec.buildRequests(bidRequest, bidderRequests); + expect(bidRequests.data.ius).to.equal(1); + }); + + it('should return a properly formatted request with schain defined', function () { + const bidRequests = spec.buildRequests(bidRequest, bidderRequests); + expect(JSON.parse(bidRequests.data.schain)).to.deep.equal(bidRequest[0].schain) + }); + + 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'}; + 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'}); + }); + + it('should return a properly formatted request with userid omitted if there are no userIds', function () { + bidRequest[0].userId = {}; + bidRequest[1].userId = {}; + const bidRequests = spec.buildRequests(bidRequest, bidderRequests); + expect(bidRequests.url).to.equal('https://apex.go.sonobi.com/trinity.json'); + expect(bidRequests.method).to.equal('GET'); + expect(bidRequests.data.ref).not.to.be.empty; + expect(bidRequests.data.s).not.to.be.empty; + expect(bidRequests.data.userid).to.equal(undefined); + }); + + it('should return a properly formatted request with userid omitted', function () { + bidRequest[0].userId = undefined; + bidRequest[1].userId = undefined; + const bidRequests = spec.buildRequests(bidRequest, bidderRequests); + expect(bidRequests.url).to.equal('https://apex.go.sonobi.com/trinity.json'); + expect(bidRequests.method).to.equal('GET'); + expect(bidRequests.data.ref).not.to.be.empty; + expect(bidRequests.data.s).not.to.be.empty; + expect(bidRequests.data.userid).to.equal(undefined); + }); }) describe('.interpretResponse', function () { @@ -242,8 +569,7 @@ describe('SonobiBidAdapter', function () { }, 'adUnitCode': 'adunit-code-2', 'sizes': [[120, 600], [300, 600], [160, 600]], - 'bidId': '30b31c1838de1e', - 'mediaType': 'video' + 'bidId': '30b31c1838de1e' }, { 'bidder': 'sonobi', @@ -254,6 +580,20 @@ describe('SonobiBidAdapter', function () { 'adUnitCode': 'adunit-code-3', 'sizes': [[120, 600], [300, 600], [160, 600]], 'bidId': '30b31c1838de1g' + }, + { + 'bidId': '30b31c1838de1zzzz', + 'adUnitCode': 'outstream-dom-id', + bidder: 'sonobi', + mediaTypes: { + video: { + context: 'outstream' + } + }, + params: { + placement_id: '92e95368e86639dbd86d', + sizes: [[640, 480]] + } } ] }; @@ -274,8 +614,27 @@ describe('SonobiBidAdapter', function () { 'sbi_aid': '30292e432662bd5f86d90774b944b038', 'sbi_mouse': 1.25, 'sbi_dozer': 'dozerkey', + 'sbi_ct': 'video' + }, + '/7780971/sparks_prebid_LB_OUTSTREAM|30b31c1838de1g': { + 'sbi_size': '300x600', + 'sbi_apoc': 'remnant', + 'sbi_crid': '1234abcd', + 'sbi_aid': '30292e432662bd5f86d90774b944b038', + 'sbi_mouse': 1.07, }, '/7780971/sparks_prebid_LB|30b31c1838de1g': {}, + '30b31c1838de1zzzz': { + sbi_aid: 'force_1550072228_da1c5d030cb49150c5db8a2136175755', + sbi_apoc: 'premium', + sbi_ct: 'video', + sbi_curr: 'USD', + sbi_mouse: 1.25, + sbi_size: 'preroll', + 'sbi_crid': 'somecrid', + + } + }, 'sbi_dc': 'mco-1-', 'sbi_px': [{ @@ -294,7 +653,7 @@ describe('SonobiBidAdapter', function () { 'cpm': 1.07, 'width': 300, 'height': 600, - 'ad': '', + 'ad': ``, 'ttl': 500, 'creativeId': '1234abcd', 'netRevenue': true, @@ -306,25 +665,73 @@ describe('SonobiBidAdapter', function () { 'cpm': 1.25, 'width': 300, 'height': 250, - 'ad': 'https://mco-1-apex.go.sonobi.com/vast.xml?vid=30292e432662bd5f86d90774b944b038&ref=http://localhost/', + 'vastUrl': 'https://mco-1-apex.go.sonobi.com/vast.xml?vid=30292e432662bd5f86d90774b944b038&ref=http%3A%2F%2Flocalhost%2F', 'ttl': 500, 'creativeId': '30292e432662bd5f86d90774b944b038', 'netRevenue': true, 'currency': 'USD', 'dealId': 'dozerkey', + 'aid': '30292e432662bd5f86d90774b944b038', + 'mediaType': 'video' + }, + { + 'requestId': '30b31c1838de1g', + 'cpm': 1.07, + 'width': 300, + 'height': 600, + 'ad': ``, + 'ttl': 500, + 'creativeId': '1234abcd', + 'netRevenue': true, + 'currency': 'USD', 'aid': '30292e432662bd5f86d90774b944b038' - } + }, + { + 'requestId': '30b31c1838de1zzzz', + 'cpm': 1.25, + 'width': 640, + 'height': 480, + 'vastUrl': 'https://mco-1-apex.go.sonobi.com/vast.xml?vid=30292e432662bd5f86d90774b944b038&ref=http%3A%2F%2Flocalhost%2F', + 'ttl': 500, + 'creativeId': 'somecrid', + 'netRevenue': true, + 'currency': 'USD', + 'dealId': 'dozerkey', + 'aid': 'force_1550072228_da1c5d030cb49150c5db8a2136175755', + 'mediaType': 'video', + renderer: () => {} + }, ]; it('should map bidResponse to prebidResponse', function () { const response = spec.interpretResponse(bidResponse, bidRequests); - response.forEach(resp => { - let regx = /http:\/\/localhost:9876\/.*?(?="|$)/ - resp.ad = resp.ad.replace(regx, 'http://localhost/'); + response.forEach((resp, i) => { + expect(resp.requestId).to.equal(prebidResponse[i].requestId); + expect(resp.cpm).to.equal(prebidResponse[i].cpm); + + expect(resp.ttl).to.equal(prebidResponse[i].ttl); + expect(resp.creativeId).to.equal(prebidResponse[i].creativeId); + expect(resp.netRevenue).to.equal(prebidResponse[i].netRevenue); + expect(resp.currency).to.equal(prebidResponse[i].currency); + expect(resp.aid).to.equal(prebidResponse[i].aid); + if (resp.mediaType === 'video' && resp.renderer) { + expect(resp.vastUrl.indexOf('vast.xml')).to.be.greaterThan(0); + expect(resp.width).to.equal(prebidResponse[i].width); + expect(resp.height).to.equal(prebidResponse[i].height); + expect(resp.renderer).to.be.ok; + } else if (resp.mediaType === 'video') { + expect(resp.vastUrl.indexOf('vast.xml')).to.be.greaterThan(0); + expect(resp.ad).to.be.undefined; + expect(resp.width).to.be.undefined; + expect(resp.height).to.be.undefined; + } else { + expect(resp.ad.indexOf('localhost')).to.be.greaterThan(0); + expect(resp.width).to.equal(prebidResponse[i].width); + expect(resp.height).to.equal(prebidResponse[i].height); + } }); - expect(response).to.deep.equal(prebidResponse); - }) - }) + }); + }); describe('.getUserSyncs', function () { let bidResponse = [{ diff --git a/test/spec/modules/sortableAnalyticsAdapter_spec.js b/test/spec/modules/sortableAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..90bd5fcdf22 --- /dev/null +++ b/test/spec/modules/sortableAnalyticsAdapter_spec.js @@ -0,0 +1,307 @@ +import {expect} from 'chai'; +import sortableAnalyticsAdapter, {TIMEOUT_FOR_REGISTRY, DEFAULT_PBID_TIMEOUT} from 'modules/sortableAnalyticsAdapter'; +import events from 'src/events'; +import CONSTANTS from 'src/constants.json'; +import * as prebidGlobal from 'src/prebidGlobal'; + +describe('Sortable Analytics Adapter', function() { + let requests; + let sandbox; + let xhr; + let clock; + + const initialConfig = { + provider: 'sortable', + options: { + siteId: 'testkey' + } + }; + + const TEST_DATA = { + AUCTION_INIT: { + auctionId: 'fb8d579a-5c3f-4705-ab94-3cff39005d9e', + timeout: 3000 + }, + BID_REQUESTED: { + refererInfo: { + referer: 'test.com', + reachedTop: true, + numIframes: 1 + }, + bidderCode: 'sortable', + auctionId: 'fb8d579a-5c3f-4705-ab94-3cff39005d9e', + bids: [{ + bidder: 'sortable', + params: { + tagId: 'medrec_1' + }, + adUnitCode: '300x250', + transactionId: 'aa02b498-8a99-418e-bc59-6b6fd45f32de', + sizes: [ + [300, 250] + ], + bidId: '26721042674416', + bidderRequestId: '10141593b1d84a', + auctionId: 'fb8d579a-5c3f-4705-ab94-3cff39005d9e', + bidRequestsCount: 1 + }, { + bidder: 'sortable', + params: { + tagId: 'lead_1' + }, + adUnitCode: '728x90', + transactionId: 'b7e9e957-af4f-4c47-8ca7-41f01cb4f105', + sizes: [ + [728, 90] + ], + bidId: '50fa575b41e596', + bidderRequestId: '37a8760be6db23', + auctionId: 'fb8d579a-5c3f-4705-ab94-3cff39005d9e', + bidRequestsCount: 1 + }], + start: 1553529405788 + }, + BID_ADJUSTMENT_1: { + bidderCode: 'sortable', + adId: '88221d316425f7', + mediaType: 'banner', + cpm: 0.70, + dealId: null, + currency: 'USD', + netRevenue: true, + ttl: 60, + auctionId: 'fb8d579a-5c3f-4705-ab94-3cff39005d9e', + responseTimestamp: 1553534161763, + bidder: 'sortable', + adUnitCode: '300x250', + timeToRespond: 331, + width: '300', + height: '250' + }, + AUCTION_END: { + auctionId: 'fb8d579a-5c3f-4705-ab94-3cff39005d9e' + }, + BID_ADJUSTMENT_2: { + bidderCode: 'sortable', + adId: '88221d316425f8', + mediaType: 'banner', + cpm: 0.50, + dealId: null, + currency: 'USD', + netRevenue: true, + ttl: 60, + auctionId: 'fb8d579a-5c3f-4705-ab94-3cff39005d9e', + responseTimestamp: 1553534161770, + bidder: 'sortable', + adUnitCode: '728x90', + timeToRespond: 338, + width: '728', + height: '90' + }, + BID_WON_1: { + bidderCode: 'sortable', + adId: '88221d316425f7', + mediaType: 'banner', + cpm: 0.70, + dealId: null, + currency: 'USD', + netRevenue: true, + ttl: 60, + auctionId: 'fb8d579a-5c3f-4705-ab94-3cff39005d9e', + responseTimestamp: 1553534161763, + bidder: 'sortable', + adUnitCode: '300x250', + timeToRespond: 331 + }, + BID_WON_2: { + bidderCode: 'sortable', + adId: '88221d316425f8', + mediaType: 'banner', + cpm: 0.50, + dealId: null, + currency: 'USD', + netRevenue: true, + ttl: 60, + auctionId: 'fb8d579a-5c3f-4705-ab94-3cff39005d9e', + responseTimestamp: 1553534161770, + bidder: 'sortable', + adUnitCode: '728x90', + timeToRespond: 338 + }, + BID_TIMEOUT: [{ + auctionId: 'fb8d579a-5c3f-4705-ab94-3cff39005d9e', + adUnitCode: '300x250', + bidder: 'sortable' + }] + }; + + beforeEach(function() { + sandbox = sinon.sandbox.create(); + xhr = sandbox.useFakeXMLHttpRequest(); + xhr.onCreate = (request) => requests.push(request); + clock = sandbox.useFakeTimers(); + sandbox.stub(events, 'getEvents').returns([]); + sandbox.stub(prebidGlobal, 'getGlobal').returns({ + version: '1.0', + bidderSettings: { + 'sortable': { + bidCpmAdjustment: function (number) { + return number * 0.95; + } + } + } + }); + + requests = []; + sortableAnalyticsAdapter.enableAnalytics(initialConfig); + }); + + afterEach(function() { + sandbox.restore(); + sortableAnalyticsAdapter.disableAnalytics(); + }); + + describe('initialize adapter', function() { + const settings = sortableAnalyticsAdapter.getOptions(); + + it('should init settings correctly and apply defaults', function() { + expect(settings).to.include({ + 'disableSessionTracking': false, + 'key': initialConfig.options.siteId, + 'protocol': 'https', + 'url': `https://pa.deployads.com/pae/${initialConfig.options.siteId}`, + 'timeoutForPbid': DEFAULT_PBID_TIMEOUT + }); + }); + it('should assign a pageview ID', function() { + expect(settings).to.have.own.property('pageviewId'); + }); + }); + + describe('events tracking', function() { + it('should send the PBID event', function() { + events.emit(CONSTANTS.EVENTS.AUCTION_INIT, TEST_DATA.AUCTION_INIT); + events.emit(CONSTANTS.EVENTS.BID_REQUESTED, TEST_DATA.BID_REQUESTED); + events.emit(CONSTANTS.EVENTS.BID_ADJUSTMENT, TEST_DATA.BID_ADJUSTMENT_1); + events.emit(CONSTANTS.EVENTS.BID_ADJUSTMENT, TEST_DATA.BID_ADJUSTMENT_2); + events.emit(CONSTANTS.EVENTS.AUCTION_END, TEST_DATA.AUCTION_END); + events.emit(CONSTANTS.EVENTS.BID_WON, TEST_DATA.BID_WON_1); + events.emit(CONSTANTS.EVENTS.BID_WON, TEST_DATA.BID_WON_2); + + clock.tick(DEFAULT_PBID_TIMEOUT); + + expect(requests.length).to.equal(1); + let result = JSON.parse(requests[0].requestBody); + expect(result).to.have.own.property('pbid'); + expect(result.pbid).to.deep.include({ + ai: 'fb8d579a-5c3f-4705-ab94-3cff39005d9e', + ac: ['300x250', '728x90'], + adi: ['88221d316425f7', '88221d316425f8'], + bs: 'sortable', + bid: ['26721042674416', '50fa575b41e596'], + bif: 0.95, + brc: 1, + brid: ['10141593b1d84a', '37a8760be6db23'], + rs: ['300x250', '728x90'], + btcp: [0.70, 0.50], + btcc: 'USD', + btin: true, + btsrc: 'sortable', + c: [0.70, 0.50], + cc: 'USD', + did: null, + inr: true, + it: true, + iw: true, + ito: false, + mt: 'banner', + rtp: true, + nif: 1, + pbv: '1.0', + siz: ['300x250', '728x90'], + st: 1553529405788, + tgid: ['medrec_1', 'lead_1'], + to: 3000, + trid: ['aa02b498-8a99-418e-bc59-6b6fd45f32de', 'b7e9e957-af4f-4c47-8ca7-41f01cb4f105'], + ttl: 60, + ttr: [331, 338], + u: 'test.com', + _count: 2 + }); + }); + + it('should track a late bidWon event', function() { + events.emit(CONSTANTS.EVENTS.AUCTION_INIT, TEST_DATA.AUCTION_INIT); + events.emit(CONSTANTS.EVENTS.BID_REQUESTED, TEST_DATA.BID_REQUESTED); + events.emit(CONSTANTS.EVENTS.BID_ADJUSTMENT, TEST_DATA.BID_ADJUSTMENT_1); + events.emit(CONSTANTS.EVENTS.AUCTION_END, TEST_DATA.AUCTION_END); + + clock.tick(DEFAULT_PBID_TIMEOUT); + + events.emit(CONSTANTS.EVENTS.BID_WON, TEST_DATA.BID_WON_1); + + clock.tick(TIMEOUT_FOR_REGISTRY); + + expect(requests.length).to.equal(2); + const pbid_req = JSON.parse(requests[0].requestBody); + expect(pbid_req).to.have.own.property('pbid'); + const pbwon_req = JSON.parse(requests[1].requestBody); + expect(pbwon_req).to.have.own.property('pbrw'); + expect(pbwon_req.pbrw).to.deep.equal({ + ac: '300x250', + ai: 'fb8d579a-5c3f-4705-ab94-3cff39005d9e', + bif: 0.95, + bs: 'sortable', + s: initialConfig.options.siteId, + cc: 'USD', + c: 0.70, + inr: true, + _count: 1, + _type: 'pbrw' + }); + }); + + it('should track late bidder timeouts', function() { + events.emit(CONSTANTS.EVENTS.AUCTION_INIT, TEST_DATA.AUCTION_INIT); + events.emit(CONSTANTS.EVENTS.BID_REQUESTED, TEST_DATA.BID_REQUESTED); + events.emit(CONSTANTS.EVENTS.AUCTION_END, TEST_DATA.AUCTION_END); + clock.tick(DEFAULT_PBID_TIMEOUT); + events.emit(CONSTANTS.EVENTS.BID_TIMEOUT, TEST_DATA.BID_TIMEOUT); + + clock.tick(TIMEOUT_FOR_REGISTRY); + + expect(requests.length).to.equal(2); + const pbid_req = JSON.parse(requests[0].requestBody); + expect(pbid_req).to.have.own.property('pbid'); + const pbto_req = JSON.parse(requests[1].requestBody); + expect(pbto_req).to.have.own.property('pbto'); + expect(pbto_req.pbto).to.deep.equal({ + ai: 'fb8d579a-5c3f-4705-ab94-3cff39005d9e', + s: initialConfig.options.siteId, + ac: '300x250', + bs: 'sortable', + _type: 'pbto', + _count: 1 + }); + }); + + it('should track errors', function() { + events.emit(CONSTANTS.EVENTS.AUCTION_INIT, TEST_DATA.AUCTION_INIT); + events.emit(CONSTANTS.EVENTS.BID_REQUESTED, {}); + + clock.tick(TIMEOUT_FOR_REGISTRY); + + expect(requests.length).to.equal(1); + const err_req = JSON.parse(requests[0].requestBody); + expect(err_req).to.have.own.property('pber'); + expect(err_req.pber).to.include({ + args: '{}', + s: initialConfig.options.siteId, + _count: 1, + ti: 'bidRequested', + _type: 'pber' + }); + expect(err_req.pber.msg).to.be.a('string'); + }); + }); +}); diff --git a/test/spec/modules/sortableBidAdapter_spec.js b/test/spec/modules/sortableBidAdapter_spec.js index 09f5b4f7514..98695f44ee0 100644 --- a/test/spec/modules/sortableBidAdapter_spec.js +++ b/test/spec/modules/sortableBidAdapter_spec.js @@ -1,10 +1,9 @@ import { expect } from 'chai'; import { spec } from 'modules/sortableBidAdapter'; import { newBidder } from 'src/adapters/bidderFactory'; -import { REPO_AND_VERSION } from 'src/constants'; import * as utils from 'src/utils'; -const ENDPOINT = `//c.deployads.com/openrtb2/auction?src=${REPO_AND_VERSION}&host=${utils.getTopWindowLocation().host}`; +const ENDPOINT = `//c.deployads.com/openrtb2/auction?src=$$REPO_AND_VERSION$$&host=${utils.getTopWindowLocation().host}`; describe('sortableBidAdapter', function() { const adapter = newBidder(spec); diff --git a/test/spec/modules/sovrnAnalyticsAdapter_spec.js b/test/spec/modules/sovrnAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..299e22ca790 --- /dev/null +++ b/test/spec/modules/sovrnAnalyticsAdapter_spec.js @@ -0,0 +1,546 @@ +import sovrnAnalyticsAdapter from '../../../modules/sovrnAnalyticsAdapter' +import { expect } from 'chai' +import {config} from 'src/config' +import adaptermanager from 'src/adapterManager' +var assert = require('assert'); + +let events = require('src/events'); +let constants = require('src/constants.json'); + +/** + * Emit analytics events + * @param {array} eventArr - array of objects to define the events that will fire + * @param {object} eventObj - key is eventType, value is event + * @param {string} auctionId - the auction id to attached to the events + */ +function emitEvent(eventType, event, auctionId) { + event.auctionId = auctionId; + events.emit(constants.EVENTS[eventType], event); +} + +let auctionStartTimestamp = Date.now(); +let timeout = 3000; +let auctionInit = { + timestamp: auctionStartTimestamp, + timeout: timeout +}; +let bidderCode = 'sovrn'; +let bidderRequestId = '123bri'; +let adUnitCode = 'div'; +let adUnitCode2 = 'div2'; +let bidId = 'bidid'; +let bidId2 = 'bidid2'; +let tId = '7aafa3ee-a80a-46d7-a4a0-cbcba463d97a'; +let tId2 = '99dca3ee-a80a-46d7-a4a0-cbcba463d97e'; +let bidRequested = { + auctionStart: auctionStartTimestamp, + bidderCode: bidderCode, + bidderRequestId: bidderRequestId, + bids: [ + { + adUnitCode: adUnitCode, + bidId: bidId, + bidder: bidderCode, + bidderRequestId: '10340af0c7dc72', + sizes: [[300, 250]], + startTime: auctionStartTimestamp + 100, + transactionId: tId + }, + { + adUnitCode: adUnitCode2, + bidId: bidId2, + bidder: bidderCode, + bidderRequestId: '10340af0c7dc72', + sizes: [[300, 250]], + startTime: auctionStartTimestamp + 100, + transactionId: tId2 + } + ], + doneCbCallCount: 1, + start: auctionStartTimestamp, + timeout: timeout +}; +let bidResponse = { + bidderCode: bidderCode, + width: 300, + height: 250, + statusMessage: 'Bid available', + adId: '3870e27a5752fb', + mediaType: 'banner', + source: 'client', + requestId: bidId, + cpm: 0.8584999918937682, + creativeId: 'cridprebidrtb', + dealId: null, + currency: 'USD', + netRevenue: true, + ad: '
    divvy mcdiv
    ', + ttl: 60000, + responseTimestamp: auctionStartTimestamp + 150, + requestTimestamp: auctionStartTimestamp + 100, + bidder: bidderCode, + adUnitCode: adUnitCode, + timeToRespond: 50, + pbLg: '0.50', + pbMg: '0.80', + pbHg: '0.85', + pbAg: '0.85', + pbDg: '0.85', + pbCg: '', + size: '300x250', + adserverTargeting: { + hb_bidder: bidderCode, + hb_adid: '3870e27a5752fb', + hb_pb: '0.85' + }, + status: 'rendered' +}; + +let bidResponse2 = { + bidderCode: bidderCode, + width: 300, + height: 250, + statusMessage: 'Bid available', + adId: '9999e27a5752fb', + mediaType: 'banner', + source: 'client', + requestId: bidId2, + cpm: 0.12, + creativeId: 'cridprebidrtb', + dealId: null, + currency: 'USD', + netRevenue: true, + ad: '
    divvy mcdiv
    ', + ttl: 60000, + responseTimestamp: auctionStartTimestamp + 150, + requestTimestamp: auctionStartTimestamp + 100, + bidder: bidderCode, + adUnitCode: adUnitCode2, + timeToRespond: 50, + pbLg: '0.10', + pbMg: '0.10', + pbHg: '0.10', + pbAg: '0.10', + pbDg: '0.10', + pbCg: '', + size: '300x250', + adserverTargeting: { + hb_bidder: bidderCode, + hb_adid: '9999e27a5752fb', + hb_pb: '0.10' + }, + status: 'rendered' +}; +let bidAdjustment = {}; +for (var k in bidResponse) bidAdjustment[k] = bidResponse[k]; +bidAdjustment.cpm = 0.8; +let bidAdjustmentNoMatchingRequest = { + bidderCode: 'not-sovrn', + width: 300, + height: 250, + statusMessage: 'Bid available', + adId: '1', + mediaType: 'banner', + source: 'client', + requestId: '1', + cpm: 0.10, + creativeId: '', + dealId: null, + currency: 'USD', + netRevenue: true, + ad: '
    divvy mcdiv
    ', + ttl: 60000, + responseTimestamp: auctionStartTimestamp + 150, + requestTimestamp: auctionStartTimestamp + 100, + bidder: 'not-sovrn', + adUnitCode: '', + timeToRespond: 50, + pbLg: '0.00', + pbMg: '0.10', + pbHg: '0.10', + pbAg: '0.10', + pbDg: '0.10', + pbCg: '', + size: '300x250', + adserverTargeting: { + hb_bidder: 'not-sovrn', + hb_adid: '1', + hb_pb: '0.10' + }, +}; +let bidResponseNoMatchingRequest = bidAdjustmentNoMatchingRequest; + +describe('Sovrn Analytics Adapter', function () { + let xhr; + let requests; + beforeEach(() => { + xhr = sinon.useFakeXMLHttpRequest(); + xhr.onCreate = request => requests.push(request); + requests = []; + sinon.stub(events, 'getEvents').returns([]); + }); + afterEach(() => { + xhr.restore(); + events.getEvents.restore(); + }); + + describe('enableAnalytics ', function () { + beforeEach(() => { + sinon.spy(sovrnAnalyticsAdapter, 'track'); + }); + afterEach(() => { + sovrnAnalyticsAdapter.disableAnalytics(); + sovrnAnalyticsAdapter.track.restore(); + }); + + it('should catch all events if affiliate id present', function () { + adaptermanager.enableAnalytics({ + provider: 'sovrn', + options: { + sovrnId: 123 + } + }); + + events.emit(constants.EVENTS.AUCTION_INIT, {}); + events.emit(constants.EVENTS.AUCTION_END, {}); + events.emit(constants.EVENTS.BID_REQUESTED, {}); + events.emit(constants.EVENTS.BID_RESPONSE, {}); + events.emit(constants.EVENTS.BID_WON, {}); + + sinon.assert.callCount(sovrnAnalyticsAdapter.track, 5); + }); + + it('should catch no events if no affiliate id', function () { + adaptermanager.enableAnalytics({ + provider: 'sovrn', + options: { + } + }); + + events.emit(constants.EVENTS.AUCTION_INIT, {}); + events.emit(constants.EVENTS.AUCTION_END, {}); + events.emit(constants.EVENTS.BID_REQUESTED, {}); + events.emit(constants.EVENTS.BID_RESPONSE, {}); + events.emit(constants.EVENTS.BID_WON, {}); + + sinon.assert.callCount(sovrnAnalyticsAdapter.track, 0); + }); + }); + + describe('sovrnAnalyticsAdapter ', function() { + beforeEach(() => { + sovrnAnalyticsAdapter.enableAnalytics({ + provider: 'sovrn', + options: { + sovrnId: 123 + } + }); + sinon.spy(sovrnAnalyticsAdapter, 'track'); + }); + afterEach(() => { + sovrnAnalyticsAdapter.disableAnalytics(); + sovrnAnalyticsAdapter.track.restore(); + }); + it('should have correct type', function () { + assert.equal(sovrnAnalyticsAdapter.getAdapterType(), 'endpoint') + }) + }); + + describe('auction data collector ', function() { + beforeEach(() => { + sovrnAnalyticsAdapter.enableAnalytics({ + provider: 'sovrn', + options: { + sovrnId: 123 + } + }); + sinon.spy(sovrnAnalyticsAdapter, 'track'); + }); + afterEach(() => { + sovrnAnalyticsAdapter.disableAnalytics(); + sovrnAnalyticsAdapter.track.restore(); + }); + it('should create auctiondata record from init ', function () { + let auctionId = '123.123.123.123'; + emitEvent('AUCTION_INIT', auctionInit, auctionId); + + let auctionData = sovrnAnalyticsAdapter.getAuctions(); + let currentAuction = auctionData[auctionId]; + assert(currentAuction); + let expectedTimeOutData = { + buffer: config.getConfig('timeoutBuffer'), + bidder: config.getConfig('bidderTimeout'), + }; + expect(currentAuction.auction.timeouts).to.deep.equal(expectedTimeOutData); + assert.equal(currentAuction.auction.payload, 'auction'); + assert.equal(currentAuction.auction.priceGranularity, config.getConfig('priceGranularity')) + assert.equal(currentAuction.auction.auctionId, auctionId); + assert.equal(currentAuction.auction.sovrnId, 123); + }); + it('should create a bidrequest object ', function() { + let auctionId = '234.234.234.234'; + emitEvent('AUCTION_INIT', auctionInit, auctionId); + emitEvent('BID_REQUESTED', bidRequested, auctionId); + + let auctionData = sovrnAnalyticsAdapter.getAuctions(); + let currentAuction = auctionData[auctionId]; + assert(currentAuction); + let requests = currentAuction.auction.requests; + assert(requests); + assert.equal(requests.length, 1); + assert.equal(requests[0].bidderCode, bidderCode); + assert.equal(requests[0].bidderRequestId, bidderRequestId); + assert.equal(requests[0].timeout, timeout); + let bids = requests[0].bids; + assert(bids); + assert.equal(bids.length, 2); + assert.equal(bids[0].bidId, bidId); + assert.equal(bids[0].bidder, bidderCode); + assert.equal(bids[0].transactionId, tId); + assert.equal(bids[0].sizes.length, 1); + assert.equal(bids[0].sizes[0][0], 300); + assert.equal(bids[0].sizes[0][1], 250); + expect(requests[0]).to.not.have.property('doneCbCallCount'); + expect(requests[0]).to.not.have.property('auctionId'); + }); + it('should add results to the bid with response ', function () { + let auctionId = '345.345.345.345'; + emitEvent('AUCTION_INIT', auctionInit, auctionId); + emitEvent('BID_REQUESTED', bidRequested, auctionId); + emitEvent('BID_RESPONSE', bidResponse, auctionId); + + let auctionData = sovrnAnalyticsAdapter.getAuctions(); + let currentAuction = auctionData[auctionId]; + let returnedBid = currentAuction.auction.requests[0].bids[0]; + assert.equal(returnedBid.bidId, bidId); + assert.equal(returnedBid.bidder, bidderCode); + assert.equal(returnedBid.transactionId, tId); + assert.equal(returnedBid.sizes.length, 1); + assert.equal(returnedBid.sizes[0][0], 300); + assert.equal(returnedBid.sizes[0][1], 250); + assert.equal(returnedBid.adserverTargeting.hb_adid, '3870e27a5752fb'); + assert.equal(returnedBid.adserverTargeting.hb_bidder, bidderCode); + assert.equal(returnedBid.adserverTargeting.hb_pb, '0.85'); + assert.equal(returnedBid.cpm, 0.8584999918937682); + }); + it('should add new unsynced bid if no request exists for response ', function () { + let auctionId = '456.456.456.456'; + emitEvent('AUCTION_INIT', auctionInit, auctionId); + emitEvent('BID_REQUESTED', bidRequested, auctionId); + emitEvent('BID_RESPONSE', bidResponseNoMatchingRequest, auctionId); + + let auctionData = sovrnAnalyticsAdapter.getAuctions(); + let currentAuction = auctionData[auctionId]; + let requests = currentAuction.auction.requests; + assert(requests); + assert.equal(requests.length, 1); + let bidRequest = requests[0].bids[0]; + expect(bidRequest).to.not.have.property('adserverTargeting'); + expect(bidRequest).to.not.have.property('cpm'); + expect(currentAuction.auction.unsynced[0]).to.deep.equal(bidResponseNoMatchingRequest); + }); + it('should adjust the bid ', function () { + let auctionId = '567.567.567.567'; + emitEvent('AUCTION_INIT', auctionInit, auctionId); + emitEvent('BID_REQUESTED', bidRequested, auctionId); + emitEvent('BID_ADJUSTMENT', bidResponse, auctionId); + emitEvent('BID_RESPONSE', bidAdjustment, auctionId); + + let auctionData = sovrnAnalyticsAdapter.getAuctions(); + let currentAuction = auctionData[auctionId]; + let returnedBid = currentAuction.auction.requests[0].bids[0]; + assert.equal(returnedBid.cpm, 0.8); + assert.equal(returnedBid.originalValues.cpm, 0.8584999918937682); + }); + }); + describe('auction data send ', function() { + let expectedPostBody = { + sovrnId: 123, + auctionId: '678.678.678.678', + payload: 'auction', + priceGranularity: 'medium', + }; + let expectedRequests = { + bidderCode: 'sovrn', + bidderRequestId: '123bri', + timeout: 3000 + }; + let expectedBids = { + adUnitCode: 'div', + bidId: 'bidid', + bidder: 'sovrn', + bidderRequestId: '10340af0c7dc72', + transactionId: '7aafa3ee-a80a-46d7-a4a0-cbcba463d97a', + width: 300, + height: 250, + statusMessage: 'Bid available', + adId: '3870e27a5752fb', + mediaType: 'banner', + source: 'client', + cpm: 0.8584999918937682, + creativeId: 'cridprebidrtb', + dealId: null, + currency: 'USD', + netRevenue: true, + ttl: 60000, + timeToRespond: 50, + size: '300x250', + status: 'rendered', + isAuctionWinner: true + }; + let SecondAdUnitExpectedBids = { + adUnitCode: 'div2', + bidId: 'bidid2', + bidder: 'sovrn', + bidderRequestId: '10340af0c7dc72', + transactionId: '99dca3ee-a80a-46d7-a4a0-cbcba463d97e', + width: 300, + height: 250, + statusMessage: 'Bid available', + adId: '9999e27a5752fb', + mediaType: 'banner', + source: 'client', + cpm: 0.12, + creativeId: 'cridprebidrtb', + dealId: null, + currency: 'USD', + netRevenue: true, + ttl: 60000, + timeToRespond: 50, + size: '300x250', + status: 'rendered', + isAuctionWinner: true + }; + let expectedAdServerTargeting = { + hb_bidder: 'sovrn', + hb_adid: '3870e27a5752fb', + hb_pb: '0.85' + }; + beforeEach(() => { + sovrnAnalyticsAdapter.enableAnalytics({ + provider: 'sovrn', + options: { + sovrnId: 123 + } + }); + sinon.spy(sovrnAnalyticsAdapter, 'track'); + }); + afterEach(() => { + sovrnAnalyticsAdapter.disableAnalytics(); + sovrnAnalyticsAdapter.track.restore(); + }); + it('should send auction data ', function () { + let auctionId = '678.678.678.678'; + emitEvent('AUCTION_INIT', auctionInit, auctionId); + emitEvent('BID_REQUESTED', bidRequested, auctionId); + emitEvent('BID_RESPONSE', bidResponse, auctionId); + emitEvent('BID_RESPONSE', bidResponse2, auctionId) + emitEvent('AUCTION_END', {}, auctionId); + let requestBody = JSON.parse(requests[0].requestBody); + let requestsFromRequestBody = requestBody.requests[0]; + let bidsFromRequests = requestsFromRequestBody.bids[0]; + expect(requestBody).to.deep.include(expectedPostBody); + expect(requestBody.timeouts).to.deep.equal({buffer: 400, bidder: 3000}); + expect(requestsFromRequestBody).to.deep.include(expectedRequests); + expect(bidsFromRequests).to.deep.include(expectedBids); + let bidsFromRequests2 = requestsFromRequestBody.bids[1]; + expect(bidsFromRequests2).to.deep.include(SecondAdUnitExpectedBids); + expect(bidsFromRequests.adserverTargeting).to.deep.include(expectedAdServerTargeting); + }); + }); + describe('bid won data send ', function() { + let auctionId = '789.789.789.789'; + let creativeId = 'cridprebidrtb'; + let requestId = 'requestId69'; + let bidWonEvent = { + ad: 'html', + adId: 'adId', + adUnitCode: adUnitCode, + auctionId: auctionId, + bidder: bidderCode, + bidderCode: bidderCode, + cpm: 1.01, + creativeId: creativeId, + currency: 'USD', + height: 250, + mediaType: 'banner', + requestId: requestId, + size: '300x250', + source: 'client', + status: 'rendered', + statusMessage: 'Bid available', + timeToRespond: 421, + ttl: 60, + width: 300 + }; + let expectedBidWonBody = { + sovrnId: 123, + payload: 'winner' + }; + let expectedWinningBid = { + bidderCode: bidderCode, + width: 300, + height: 250, + statusMessage: 'Bid available', + adId: 'adId', + mediaType: 'banner', + source: 'client', + requestId: requestId, + cpm: 1.01, + creativeId: creativeId, + currency: 'USD', + ttl: 60, + auctionId: auctionId, + bidder: bidderCode, + adUnitCode: adUnitCode, + timeToRespond: 421, + size: '300x250', + }; + beforeEach(() => { + sovrnAnalyticsAdapter.enableAnalytics({ + provider: 'sovrn', + options: { + sovrnId: 123 + } + }); + sinon.spy(sovrnAnalyticsAdapter, 'track'); + }); + afterEach(() => { + sovrnAnalyticsAdapter.disableAnalytics(); + sovrnAnalyticsAdapter.track.restore(); + }); + it('should send bid won data ', function () { + emitEvent('AUCTION_INIT', auctionInit, auctionId); + emitEvent('BID_WON', bidWonEvent, auctionId); + let requestBody = JSON.parse(requests[0].requestBody); + expect(requestBody).to.deep.include(expectedBidWonBody); + expect(requestBody.winningBid).to.deep.include(expectedWinningBid); + }); + }); + describe('Error Tracking', function() { + beforeEach(() => { + sovrnAnalyticsAdapter.enableAnalytics({ + provider: 'sovrn', + options: { + sovrnId: 123 + } + }); + sinon.spy(sovrnAnalyticsAdapter, 'track'); + }); + afterEach(() => { + sovrnAnalyticsAdapter.disableAnalytics() + sovrnAnalyticsAdapter.track.restore() + }); + it('should send an error message when a bid is received for a closed auction', function() { + let auctionId = '678.678.678.678'; + emitEvent('AUCTION_INIT', auctionInit, auctionId) + emitEvent('BID_REQUESTED', bidRequested, auctionId) + emitEvent('AUCTION_END', {}, auctionId) + requests[0].respond(200) + emitEvent('BID_RESPONSE', bidResponse, auctionId) + let requestBody = JSON.parse(requests[1].requestBody) + expect(requestBody.payload).to.equal('error') + expect(requestBody.message).to.include('Event Received after Auction Close Auction Id') + }) + }) +}) diff --git a/test/spec/modules/sovrnBidAdapter_spec.js b/test/spec/modules/sovrnBidAdapter_spec.js index 22c93505ecf..7179ec00bc3 100644 --- a/test/spec/modules/sovrnBidAdapter_spec.js +++ b/test/spec/modules/sovrnBidAdapter_spec.js @@ -1,9 +1,9 @@ import { expect } from 'chai'; -import { spec } from 'modules/sovrnBidAdapter'; +import { spec, LogError } from 'modules/sovrnBidAdapter'; import { newBidder } from 'src/adapters/bidderFactory'; -import { REPO_AND_VERSION } from 'src/constants'; +import { SSL_OP_SINGLE_ECDH_USE } from 'constants'; -const ENDPOINT = `//ap.lijit.com/rtb/bid?src=${REPO_AND_VERSION}`; +const ENDPOINT = `//ap.lijit.com/rtb/bid?src=$$REPO_AND_VERSION$$`; describe('sovrnBidAdapter', function() { const adapter = newBidder(spec); @@ -16,7 +16,8 @@ describe('sovrnBidAdapter', function() { }, 'adUnitCode': 'adunit-code', 'sizes': [ - [300, 250] + [300, 250], + [300, 600] ], 'bidId': '30b31c1838de1e', 'bidderRequestId': '22edbae2733bf6', @@ -47,7 +48,8 @@ describe('sovrnBidAdapter', function() { }, 'adUnitCode': 'adunit-code', 'sizes': [ - [300, 250] + [300, 250], + [300, 600] ], 'bidId': '30b31c1838de1e', 'bidderRequestId': '22edbae2733bf6', @@ -64,6 +66,33 @@ describe('sovrnBidAdapter', function() { expect(request.url).to.equal(ENDPOINT) }); + it('sets the proper banner object', function() { + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.format).to.deep.equal([{w: 300, h: 250}, {w: 300, h: 600}]) + expect(payload.imp[0].banner.w).to.equal(1) + expect(payload.imp[0].banner.h).to.equal(1) + }) + + it('accepts a single array as a size', function() { + const singleSize = [{ + 'bidder': 'sovrn', + 'params': { + 'tagid': '403370', + 'iv': 'vet' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [300, 250], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475' + }]; + const request = spec.buildRequests(singleSize) + const payload = JSON.parse(request.data) + expect(payload.imp[0].banner.format).to.deep.equal([{w: 300, h: 250}]) + expect(payload.imp[0].banner.w).to.equal(1) + expect(payload.imp[0].banner.h).to.equal(1) + }) + it('sends \'iv\' as query param if present', function () { const ivBidRequests = [{ 'bidder': 'sovrn', @@ -73,7 +102,8 @@ describe('sovrnBidAdapter', function() { }, 'adUnitCode': 'adunit-code', 'sizes': [ - [300, 250] + [300, 250], + [300, 600] ], 'bidId': '30b31c1838de1e', 'bidderRequestId': '22edbae2733bf6', @@ -116,7 +146,8 @@ describe('sovrnBidAdapter', function() { }, 'adUnitCode': 'adunit-code', 'sizes': [ - [300, 250] + [300, 250], + [300, 600] ], 'bidId': '30b31c1838de1e', 'bidderRequestId': '22edbae2733bf6', @@ -268,19 +299,99 @@ describe('sovrnBidAdapter', function() { 'type': 'iframe', 'url': '//ap.lijit.com/beacon?informer=13487408&gdpr_consent=', } - ]; + ] let returnStatement = spec.getUserSyncs(syncOptions, serverResponse); expect(returnStatement[0]).to.deep.equal(expectedReturnStatement[0]); - }); + }) it('should not return if iid missing on server response', () => { - let returnStatement = spec.getUserSyncs(syncOptions, []); + let returnStatement = spec.getUserSyncs(syncOptions, []) expect(returnStatement).to.be.empty; - }); + }) it('should not return if iframe syncs disabled', () => { - let returnStatement = spec.getUserSyncs(iframeDisabledSyncOptions, serverResponse); - expect(returnStatement).to.be.empty; - }); - }); -}); + let returnStatement = spec.getUserSyncs(iframeDisabledSyncOptions, serverResponse) + expect(returnStatement).to.be.empty + }) + }) + describe('LogError', () => { + it('should build and append an error object', () => { + const thrown = { + message: 'message', + stack: 'stack' + } + const data = {name: 'Oscar Hathenswiotch'} + const err = new LogError(thrown, data) + err.append() + const errList = LogError.getErrPxls() + expect(errList.length).to.equal(1) + const errdata = JSON.parse(atob(errList[0].url.split('=')[1])) + expect(errdata.d.name).to.equal('Oscar Hathenswiotch') + }) + it('should drop data when there is too much', () => { + const thrown = { + message: 'message', + stack: 'stack' + } + const tooLong = () => { + let str = '' + for (let i = 0; i < 10000; i++) { + str = str + String.fromCharCode(i % 100) + } + return str + } + const data = {name: 'Oscar Hathenswiotch', tooLong: tooLong()} + const err = new LogError(thrown, data) + err.append() + const errList = LogError.getErrPxls() + expect(errList.length).to.equal(2) + const errdata = JSON.parse(atob(errList[1].url.split('=')[1])) + expect(errdata.d).to.be.an('undefined') + }) + it('should drop data and stack when there is too much', () => { + const thrown = { + message: 'message', + stack: 'stack' + } + const tooLong = () => { + let str = '' + for (let i = 0; i < 10000; i++) { + str = str + String.fromCharCode(i % 100) + } + return str + } + const data = {name: 'Oscar Hathenswiotch'} + thrown.stack = tooLong() + const err = new LogError(thrown, data) + err.append() + const errList = LogError.getErrPxls() + expect(errList.length).to.equal(3) + const errdata = JSON.parse(atob(errList[2].url.split('=')[1])) + expect(errdata.d).to.be.an('undefined') + expect(errdata.s).to.be.an('undefined') + }) + it('should drop send a reduced message when other reduction methods fail', () => { + const thrown = { + message: 'message', + stack: 'stack' + } + const tooLong = () => { + let str = '' + for (let i = 0; i < 10000; i++) { + str = str + String.fromCharCode(i % 100) + } + return str + } + const data = {name: 'Oscar Hathenswiotch'} + thrown.message = tooLong() + const err = new LogError(thrown, data) + err.append() + const errList = LogError.getErrPxls() + expect(errList.length).to.equal(4) + const errdata = JSON.parse(atob(errList[3].url.split('=')[1])) + expect(errdata.d).to.be.an('undefined') + expect(errdata.s).to.be.an('undefined') + expect(errdata.m).to.equal('unknown error message') + }) + }) +}) diff --git a/test/spec/modules/spotxBidAdapter_spec.js b/test/spec/modules/spotxBidAdapter_spec.js new file mode 100644 index 00000000000..d1662e162aa --- /dev/null +++ b/test/spec/modules/spotxBidAdapter_spec.js @@ -0,0 +1,479 @@ +import {expect} from 'chai'; +import {spec, GOOGLE_CONSENT} from 'modules/spotxBidAdapter'; + +describe('the spotx adapter', function () { + function getValidBidObject() { + return { + bidId: 123, + mediaTypes: { + video: { + playerSize: [['300', '200']] + } + }, + params: { + channel_id: 12345, + } + }; + }; + + describe('isBidRequestValid', function() { + var bid; + + beforeEach(function() { + bid = getValidBidObject(); + }); + + it('should fail validation if the bid isn\'t defined or not an object', function() { + var result = spec.isBidRequestValid(); + + expect(result).to.equal(false); + + result = spec.isBidRequestValid('not an object'); + + expect(result).to.equal(false); + }); + + it('should succeed validation with all the right parameters', function() { + expect(spec.isBidRequestValid(getValidBidObject())).to.equal(true); + }); + + it('should succeed validation with mediaType and outstream_function or outstream_options', function() { + bid.mediaType = 'video'; + bid.params.outstream_function = 'outstream_func'; + + expect(spec.isBidRequestValid(bid)).to.equal(true); + + delete bid.params.outstream_function; + bid.params.outstream_options = { + slot: 'elemID' + }; + + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should succeed with ad_unit outstream and outstream function set', function() { + bid.params.ad_unit = 'outstream'; + bid.params.outstream_function = function() {}; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should succeed with ad_unit outstream, options set for outstream and slot provided', function() { + bid.params.ad_unit = 'outstream'; + bid.params.outstream_options = {slot: 'ad_container_id'}; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should fail without a channel_id', function() { + delete bid.params.channel_id; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should fail without playerSize', function() { + delete bid.mediaTypes.video.playerSize; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should fail without video', function() { + delete bid.mediaTypes.video; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should fail with ad_unit outstream but no options set for outstream', function() { + bid.params.ad_unit = 'outstream'; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should fail with ad_unit outstream, options set for outstream but no slot provided', function() { + bid.params.ad_unit = 'outstream'; + bid.params.outstream_options = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + describe('buildRequests', function() { + var bid, bidRequestObj; + + beforeEach(function() { + bid = getValidBidObject(); + bidRequestObj = {refererInfo: {referer: 'prebid.js'}}; + }); + + it('should build a very basic request', function() { + var request = spec.buildRequests([bid], bidRequestObj)[0]; + expect(request.method).to.equal('POST'); + expect(request.url).to.equal('//search.spotxchange.com/openrtb/2.3/dados/12345'); + expect(request.bidRequest).to.equal(bidRequestObj); + expect(request.data.id).to.equal(12345); + expect(request.data.ext.wrap_response).to.equal(1); + expect(request.data.imp.id).to.match(/\d+/); + expect(request.data.imp.secure).to.equal(0); + expect(request.data.imp.video).to.deep.equal({ + ext: { + sdk_name: 'Prebid 1+', + versionOrtb: '2.3' + }, + h: '200', + mimes: [ + 'application/javascript', + 'video/mp4', + 'video/webm' + ], + w: '300' + }); + expect(request.data.site).to.deep.equal({ + content: 'content', + id: '', + page: 'prebid.js' + }); + }); + it('should change request parameters based on options sent', function() { + var request = spec.buildRequests([bid], bidRequestObj)[0]; + expect(request.data.imp.video.ext).to.deep.equal({ + sdk_name: 'Prebid 1+', + versionOrtb: '2.3' + }); + + bid.params = { + channel_id: 54321, + ad_mute: 1, + hide_skin: 1, + ad_volume: 1, + ad_unit: 'incontent', + outstream_options: {foo: 'bar'}, + outstream_function: '987', + custom: {bar: 'foo'}, + price_floor: 123, + start_delay: true, + number_of_ads: 2, + spotx_all_google_consent: 1 + }; + + bid.userId = { + id5id: 'id5id_1' + }; + + bid.crumbs = { + pubcid: 'pubcid_1' + }; + + bid.schain = { + complete: 1, + nodes: [ + { + asi: 'indirectseller.com', + sid: '00001', + hp: 1 + } + ] + } + + request = spec.buildRequests([bid], bidRequestObj)[0]; + expect(request.data.id).to.equal(54321); + expect(request.data.imp.video.ext).to.deep.equal({ + ad_volume: 1, + hide_skin: 1, + ad_unit: 'incontent', + outstream_options: {foo: 'bar'}, + outstream_function: '987', + custom: {bar: 'foo'}, + sdk_name: 'Prebid 1+', + versionOrtb: '2.3' + }); + + expect(request.data.imp.video.startdelay).to.equal(1); + expect(request.data.imp.bidfloor).to.equal(123); + expect(request.data.ext).to.deep.equal({ + number_of_ads: 2, + wrap_response: 1 + }); + expect(request.data.user.ext).to.deep.equal({ + consented_providers_settings: GOOGLE_CONSENT, + eids: [{ + source: 'id5-sync.com', + uids: [{ + id: 'id5id_1' + }] + }], + fpc: 'pubcid_1' + }) + + expect(request.data.source).to.deep.equal({ + ext: { + schain: { + complete: 1, + nodes: [ + { + asi: 'indirectseller.com', + sid: '00001', + hp: 1 + } + ] + } + } + }) + }); + + it('should process premarket bids', function() { + var request; + sinon.stub(Date, 'now').returns(1000); + + bid.params.pre_market_bids = [{ + vast_url: 'prebid.js', + deal_id: '123abc', + price: 12, + currency: 'USD' + }]; + + request = spec.buildRequests([bid], bidRequestObj)[0]; + expect(request.data.imp.video.ext.pre_market_bids).to.deep.equal([ + { + 'cur': 'USD', + 'ext': { + 'event_log': [ + {} + ] + }, + 'id': '123abc', + 'seatbid': [ + { + 'bid': [ + { + 'adm': 'prebid.js', + 'dealid': '123abc', + 'impid': 1000, + 'price': 12, + } + ] + } + ] + } + ]); + Date.now.restore(); + }); + + it('should pass GDPR params', function() { + var request; + + bidRequestObj.gdprConsent = { + consentString: 'consent123', + gdprApplies: true + }; + + request = spec.buildRequests([bid], bidRequestObj)[0]; + + expect(request.data.regs.ext.gdpr).to.equal(1); + expect(request.data.user.ext.consent).to.equal('consent123'); + }); + }); + + describe('interpretResponse', function() { + var serverResponse, bidderRequestObj; + + beforeEach(function() { + bidderRequestObj = { + bidRequest: { + bids: [{ + mediaTypes: { + video: { + playerSize: [['400', '300']] + } + }, + bidId: 123, + params: { + player_width: 400, + player_height: 300, + content_page_url: 'prebid.js', + ad_mute: 1, + outstream_options: {foo: 'bar'}, + outstream_function: 'function' + } + }, { + mediaTypes: { + video: { + playerSize: [['200', '100']] + } + }, + bidId: 124, + params: { + player_width: 200, + player_height: 100, + content_page_url: 'prebid.js', + ad_mute: 1, + outstream_options: {foo: 'bar'}, + outstream_function: 'function' + } + }] + } + }; + + serverResponse = { + body: { + id: 12345, + seatbid: [{ + bid: [{ + impid: 123, + cur: 'USD', + price: 12, + crid: 321, + w: 400, + h: 300, + ext: { + cache_key: 'cache123', + slot: 'slot123' + } + }, { + impid: 124, + cur: 'USD', + price: 13, + w: 200, + h: 100, + ext: { + cache_key: 'cache124', + slot: 'slot124' + } + }] + }] + } + }; + }); + + 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(responses[0].cache_key).to.equal('cache123'); + expect(responses[0].channel_id).to.equal(12345); + expect(responses[0].cpm).to.equal(12); + expect(responses[0].creativeId).to.equal(321); + expect(responses[0].currency).to.equal('USD'); + expect(responses[0].height).to.equal(300); + expect(responses[0].mediaType).to.equal('video'); + expect(responses[0].netRevenue).to.equal(true); + expect(responses[0].requestId).to.equal(123); + expect(responses[0].ttl).to.equal(360); + expect(responses[0].vastUrl).to.equal('//search.spotxchange.com/ad/vast.html?key=cache123'); + expect(responses[0].width).to.equal(400); + expect(responses[1].cache_key).to.equal('cache124'); + expect(responses[1].channel_id).to.equal(12345); + expect(responses[1].cpm).to.equal(13); + expect(responses[1].creativeId).to.equal(''); + expect(responses[1].currency).to.equal('USD'); + expect(responses[1].height).to.equal(100); + expect(responses[1].mediaType).to.equal('video'); + expect(responses[1].netRevenue).to.equal(true); + expect(responses[1].requestId).to.equal(124); + expect(responses[1].ttl).to.equal(360); + expect(responses[1].vastUrl).to.equal('//search.spotxchange.com/ad/vast.html?key=cache124'); + expect(responses[1].width).to.equal(200); + }); + }); + + describe('oustreamRender', function() { + var serverResponse, bidderRequestObj; + + beforeEach(function() { + bidderRequestObj = { + bidRequest: { + bids: [{ + mediaTypes: { + video: { + playerSize: [['400', '300']] + } + }, + bidId: 123, + params: { + ad_unit: 'outstream', + player_width: 400, + player_height: 300, + content_page_url: 'prebid.js', + outstream_options: { + ad_mute: 1, + foo: 'bar', + slot: 'slot123', + playersize_auto_adapt: true, + custom_override: { + digitrust_opt_out: 1, + vast_url: 'bad_vast' + } + }, + } + }] + } + }; + + serverResponse = { + body: { + id: 12345, + seatbid: [{ + bid: [{ + impid: 123, + cur: 'USD', + price: 12, + crid: 321, + w: 400, + h: 300, + ext: { + cache_key: 'cache123', + slot: 'slot123' + } + }] + }] + } + }; + }); + + 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 }) + }); + 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('//js.spotx.tv/easi/v1/12345.js'); + expect(scriptTag.getAttribute('data-spotx_channel_id')).to.equal('12345'); + expect(scriptTag.getAttribute('data-spotx_vast_url')).to.equal('//search.spotxchange.com/ad/vast.html?key=cache123'); + expect(scriptTag.getAttribute('data-spotx_ad_unit')).to.equal('incontent'); + expect(scriptTag.getAttribute('data-spotx_collapse')).to.equal('0'); + expect(scriptTag.getAttribute('data-spotx_autoplay')).to.equal('1'); + expect(scriptTag.getAttribute('data-spotx_blocked_autoplay_override_mode')).to.equal('1'); + expect(scriptTag.getAttribute('data-spotx_video_slot_can_autoplay')).to.equal('1'); + expect(scriptTag.getAttribute('data-spotx_digitrust_opt_out')).to.equal('1'); + expect(scriptTag.getAttribute('data-spotx_content_width')).to.equal('400'); + expect(scriptTag.getAttribute('data-spotx_content_height')).to.equal('300'); + window.document.getElementById.restore(); + }); + + it('should append into an iframe', function() { + var scriptTag; + sinon.stub(window.document, 'getElementById').returns({ + nodeName: 'IFRAME', + contentDocument: { + body: { + appendChild: sinon.stub().callsFake(function(script) { scriptTag = script }) + } + } + }); + + bidderRequestObj.bidRequest.bids[0].params.outstream_options.in_iframe = 'iframeId'; + + 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('//js.spotx.tv/easi/v1/12345.js'); + expect(scriptTag.getAttribute('data-spotx_channel_id')).to.equal('12345'); + expect(scriptTag.getAttribute('data-spotx_vast_url')).to.equal('//search.spotxchange.com/ad/vast.html?key=cache123'); + expect(scriptTag.getAttribute('data-spotx_ad_unit')).to.equal('incontent'); + expect(scriptTag.getAttribute('data-spotx_collapse')).to.equal('0'); + expect(scriptTag.getAttribute('data-spotx_autoplay')).to.equal('1'); + expect(scriptTag.getAttribute('data-spotx_blocked_autoplay_override_mode')).to.equal('1'); + expect(scriptTag.getAttribute('data-spotx_video_slot_can_autoplay')).to.equal('1'); + expect(scriptTag.getAttribute('data-spotx_digitrust_opt_out')).to.equal('1'); + expect(scriptTag.getAttribute('data-spotx_content_width')).to.equal('400'); + expect(scriptTag.getAttribute('data-spotx_content_height')).to.equal('300'); + window.document.getElementById.restore(); + }); + }); +}); diff --git a/test/spec/modules/staqAnalyticsAdapter_spec.js b/test/spec/modules/staqAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..33c85d11431 --- /dev/null +++ b/test/spec/modules/staqAnalyticsAdapter_spec.js @@ -0,0 +1,301 @@ +import analyticsAdapter, {ExpiringQueue, getUmtSource, storage} from 'modules/staqAnalyticsAdapter'; +import {expect} from 'chai'; +import adapterManager from 'src/adapterManager'; +import CONSTANTS from 'src/constants.json'; + +const events = require('../../../src/events'); + +const DIRECT = { + source: '(direct)', + medium: '(direct)', + campaign: '(direct)' +}; +const REFERRER = { + source: 'lander.com', + medium: '(referral)', + campaign: '(referral)', + content: '/lander.html' +}; +const GOOGLE_ORGANIC = { + source: 'google', + medium: '(organic)', + campaign: '(organic)' +}; +const CAMPAIGN = { + source: 'adkernel', + medium: 'email', + campaign: 'new_campaign', + c1: '1', + c2: '2', + c3: '3', + c4: '4', + c5: '5' + +}; +describe('', function () { + let sandbox; + + before(function () { + sandbox = sinon.sandbox.create(); + }); + + after(function () { + sandbox.restore(); + analyticsAdapter.disableAnalytics(); + }); + + describe('UTM source parser', function () { + let stubSetItem; + let stubGetItem; + + before(function () { + stubSetItem = sandbox.stub(storage, 'setItem'); + stubGetItem = sandbox.stub(storage, 'getItem'); + }); + + afterEach(function () { + sandbox.reset(); + }); + + it('should parse first direct visit as (direct)', function () { + stubGetItem.withArgs('adk_dpt_analytics').returns(undefined); + stubSetItem.returns(undefined); + let source = getUmtSource('http://example.com'); + expect(source).to.be.eql(DIRECT); + }); + + it('should parse visit from google as organic', function () { + stubGetItem.withArgs('adk_dpt_analytics').returns(undefined); + stubSetItem.returns(undefined); + let source = getUmtSource('http://example.com', 'https://www.google.com/search?q=pikachu'); + expect(source).to.be.eql(GOOGLE_ORGANIC); + }); + + it('should parse referral visit', function () { + stubGetItem.withArgs('adk_dpt_analytics').returns(undefined); + stubSetItem.returns(undefined); + let source = getUmtSource('http://example.com', 'http://lander.com/lander.html'); + expect(source).to.be.eql(REFERRER); + }); + + it('should parse referral visit from same domain as direct', function () { + stubGetItem.withArgs('adk_dpt_analytics').returns(undefined); + stubSetItem.returns(undefined); + let source = getUmtSource('http://lander.com/news.html', 'http://lander.com/lander.html'); + expect(source).to.be.eql(DIRECT); + }); + + it('should parse campaign visit', function () { + stubGetItem.withArgs('adk_dpt_analytics').returns(undefined); + stubSetItem.returns(undefined); + let source = getUmtSource('http://lander.com/index.html?utm_campaign=new_campaign&utm_source=adkernel&utm_medium=email&utm_c1=1&utm_c2=2&utm_c3=3&utm_c4=4&utm_c5=5'); + expect(source).to.be.eql(CAMPAIGN); + }); + }); + + describe('ExpiringQueue', function () { + let timer; + before(function () { + timer = sandbox.useFakeTimers(0); + }); + after(function () { + timer.restore(); + }); + + it('should notify after timeout period', (done) => { + let queue = new ExpiringQueue(() => { + let elements = queue.popAll(); + expect(elements).to.be.eql([1, 2, 3, 4]); + elements = queue.popAll(); + expect(elements).to.have.lengthOf(0); + expect(Date.now()).to.be.equal(200); + done(); + }, 100); + + queue.push(1); + setTimeout(() => { + queue.push([2, 3]); + timer.tick(50); + }, 50); + setTimeout(() => { + queue.push([4]); + timer.tick(100); + }, 100); + timer.tick(50); + }); + }); + + const REQUEST = { + bidderCode: 'AppNexus', + bidderName: 'AppNexus', + auctionId: '5018eb39-f900-4370-b71e-3bb5b48d324f', + bidderRequestId: '1a6fc81528d0f6', + bids: [{ + bidder: 'AppNexus', + params: {}, + adUnitCode: 'container-1', + transactionId: 'de90df62-7fd0-4fbc-8787-92d133a7dc06', + sizes: [[300, 250]], + bidId: '208750227436c1', + bidderRequestId: '1a6fc81528d0f6', + auctionId: '5018eb39-f900-4370-b71e-3bb5b48d324f' + }], + auctionStart: 1509369418387, + timeout: 3000, + start: 1509369418389 + }; + + const RESPONSE = { + bidderCode: 'AppNexus', + width: 300, + height: 250, + statusMessage: 'Bid available', + adId: '208750227436c1', + mediaType: 'banner', + cpm: 0.015, + ad: '', + auctionId: '5018eb39-f900-4370-b71e-3bb5b48d324f', + responseTimestamp: 1509369418832, + requestTimestamp: 1509369418389, + bidder: 'AppNexus', + adUnitCode: 'container-1', + timeToRespond: 443, + size: '300x250' + }; + + const bidTimeoutArgsV1 = [{ + bidId: '2baa51527bd015', + bidderCode: 'AppNexus', + adUnitCode: 'container-1', + auctionId: '66529d4c-8998-47c2-ab3e-5b953490b98f' + }, + { + bidId: '6fe3b4c2c23092', + bidderCode: 'AppNexus', + adUnitCode: 'container-2', + auctionId: '66529d4c-8998-47c2-ab3e-5b953490b98f' + }]; + + describe('Analytics adapter', function () { + let ajaxStub; + let timer; + + before(function () { + ajaxStub = sandbox.stub(analyticsAdapter, 'ajaxCall'); + timer = sandbox.useFakeTimers(0); + }); + + beforeEach(function () { + sandbox.stub(events, 'getEvents').callsFake(() => { + return [] + }); + }); + + afterEach(function () { + events.getEvents.restore(); + }); + + it('should be configurable', function () { + adapterManager.registerAnalyticsAdapter({ + code: 'staq', + adapter: analyticsAdapter + }); + + adapterManager.enableAnalytics({ + provider: 'staq', + options: { + connId: 777, + queueTimeout: 1000, + url: 'http://localhost/prebid' + } + }); + + expect(analyticsAdapter.context).to.have.property('connectionId', 777); + }); + + it('should handle auction init event', function () { + events.emit(CONSTANTS.EVENTS.AUCTION_INIT, {config: {}, timeout: 3000}); + const ev = analyticsAdapter.context.queue.peekAll(); + expect(ev).to.have.length(1); + expect(ev[0]).to.be.eql({event: 'auctionInit', auctionId: undefined}); + }); + + it('should handle bid request event', function () { + events.emit(CONSTANTS.EVENTS.BID_REQUESTED, REQUEST); + const ev = analyticsAdapter.context.queue.peekAll(); + expect(ev).to.have.length(2); + expect(ev[1]).to.be.eql({ + adUnitCode: 'container-1', + auctionId: '5018eb39-f900-4370-b71e-3bb5b48d324f', + event: 'bidRequested', + adapter: 'AppNexus', + bidderName: 'AppNexus' + }); + }); + + it('should handle bid response event', function () { + events.emit(CONSTANTS.EVENTS.BID_RESPONSE, RESPONSE); + const ev = analyticsAdapter.context.queue.peekAll(); + expect(ev).to.have.length(3); + expect(ev[2]).to.be.eql({ + adId: '208750227436c1', + event: 'bidResponse', + adapter: 'AppNexus', + bidderName: 'AppNexus', + auctionId: '5018eb39-f900-4370-b71e-3bb5b48d324f', + adUnitCode: 'container-1', + cpm: 0.015, + timeToRespond: 0.443, + height: 250, + width: 300, + bidWon: false, + }); + }); + + it('should handle timeouts properly', function() { + events.emit(CONSTANTS.EVENTS.BID_TIMEOUT, bidTimeoutArgsV1); + + const ev = analyticsAdapter.context.queue.peekAll(); + expect(ev).to.have.length(5); // remember, we added 2 timeout events + expect(ev[3]).to.be.eql({ + adapter: 'AppNexus', + auctionId: '66529d4c-8998-47c2-ab3e-5b953490b98f', + bidderName: 'AppNexus', + event: 'adapterTimedOut' + }) + }); + + it('should handle winning bid', function () { + events.emit(CONSTANTS.EVENTS.BID_WON, RESPONSE); + const ev = analyticsAdapter.context.queue.peekAll(); + expect(ev).to.have.length(6); + expect(ev[5]).to.be.eql({ + auctionId: '5018eb39-f900-4370-b71e-3bb5b48d324f', + adId: '208750227436c1', + event: 'bidWon', + adapter: 'AppNexus', + bidderName: 'AppNexus', + adUnitCode: 'container-1', + cpm: 0.015, + height: 250, + width: 300, + bidWon: true, + }); + }); + + it('should handle auction end event', function () { + timer.tick(447); + events.emit(CONSTANTS.EVENTS.AUCTION_END, RESPONSE); + let ev = analyticsAdapter.context.queue.peekAll(); + expect(ev).to.have.length(0); + expect(ajaxStub.calledOnce).to.be.equal(true); + let firstCallArgs0 = ajaxStub.firstCall.args[0]; + ev = JSON.parse(firstCallArgs0); + // console.log('AUCTION END EVENT SHAPE ' + JSON.stringify(ev)); + const ev6 = ev[6]; + expect(ev6.connId).to.be.eql(777); + expect(ev6.auctionId).to.be.eql('5018eb39-f900-4370-b71e-3bb5b48d324f'); + expect(ev6.event).to.be.eql('auctionEnd'); + }); + }); +}); diff --git a/test/spec/modules/stvBidAdapter_spec.js b/test/spec/modules/stvBidAdapter_spec.js new file mode 100644 index 00000000000..72ea428304a --- /dev/null +++ b/test/spec/modules/stvBidAdapter_spec.js @@ -0,0 +1,138 @@ +import { expect } from 'chai'; +import { spec } from 'modules/stvBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; + +const VADS_ENDPOINT_URL = 'https://ads.smartstream.tv/r/'; + +describe('stvAdapter', function () { + const adapter = newBidder(spec); + + describe('isBidRequestValid', function () { + let bid = { + 'bidder': 'stv', + 'params': { + 'placement': '6682', + 'pfilter': { + 'floorprice': 1000000 + }, + 'bcat': 'IAB2,IAB4', + 'dvt': 'desktop' + }, + 'sizes': [ + [300, 250] + ], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475' + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not passed', function () { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'someIncorrectParam': 0 + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + let bidRequests = [{ + 'bidder': 'stv', + 'params': { + 'source': 'vads', + 'placement': 'prer0-0%3D4137', + 'pfilter': { + 'min_duration': 1, + 'max_duration': 100, + 'min_bitrate': 32, + 'max_bitrate': 128, + }, + 'noskip': 1 + }, + 'mediaTypes': { + 'video': { + 'playerSize': [640, 480], + 'context': 'instream' + } + }, + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475' + }]; + + let bidderRequest = { + refererInfo: { + referer: 'some_referrer.net' + } + } + + const request = spec.buildRequests(bidRequests, bidderRequest); + it('sends bid video request to our vads endpoint via GET', function () { + expect(request[0].method).to.equal('GET'); + let data = request[0].data.replace(/rnd=\d+\&/g, '').replace(/ref=.*\&bid/g, 'bid'); + expect(data).to.equal('_f=vast2&alternative=prebid_js&_ps=prer0-0%3D4137&srw=640&srh=480&idt=100&bid_id=30b31c1838de1e&pfilter%5Bmin_duration%5D=1&pfilter%5Bmax_duration%5D=100&pfilter%5Bmin_bitrate%5D=32&pfilter%5Bmax_bitrate%5D=128&noskip=1'); + }); + }); + + describe('interpretResponse', function () { + let vadsServerVideoResponse = { + 'body': { + 'cpm': 5000000, + 'crid': 100500, + 'width': '300', + 'height': '250', + 'vastXml': '{"reason":7001,"status":"accepted"}', + 'requestId': '220ed41385952a', + 'currency': 'EUR', + 'ttl': 60, + 'netRevenue': true, + 'zone': '6682' + } + }; + + let expectedResponse = [{ + requestId: '23beaa6af6cdde', + cpm: 0.5, + width: 0, + height: 0, + creativeId: 100500, + dealId: '', + currency: 'EUR', + netRevenue: true, + ttl: 300, + vastXml: '{"reason":7001,"status":"accepted"}', + mediaType: 'video' + }]; + + it('should get the correct vads video bid response by display ad', function () { + let bidRequest = [{ + 'method': 'GET', + 'url': VADS_ENDPOINT_URL, + 'mediaTypes': { + 'video': { + 'playerSize': [640, 480], + 'context': 'instream' + } + }, + 'data': { + 'bid_id': '30b31c1838de1e' + } + }]; + let result = spec.interpretResponse(vadsServerVideoResponse, bidRequest[0]); + expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); + }); + + it('handles empty bid response', function () { + let response = { + body: {} + }; + let result = spec.interpretResponse(response); + expect(result.length).to.equal(0); + }); + }); +}); diff --git a/test/spec/modules/sublimeBidAdapter_spec.js b/test/spec/modules/sublimeBidAdapter_spec.js new file mode 100644 index 00000000000..45173b09953 --- /dev/null +++ b/test/spec/modules/sublimeBidAdapter_spec.js @@ -0,0 +1,255 @@ +import { expect } from 'chai'; +import { spec } from 'modules/sublimeBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; + +describe('Sublime Adapter', function() { + const adapter = newBidder(spec); + + describe('inherited functions', function() { + it('exists and is a function', function() { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function() { + let bid = { + bidder: 'sublime', + params: { + zoneId: 24549, + endpoint: '', + }, + }; + + it('should return true when required params found', function() { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not passed', function() { + let bid = Object.assign({}, bid); + bid.params = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function() { + let bidRequests = [ + { + bidder: 'sublime', + adUnitCode: 'sublime_code', + bidId: 'abc1234', + sizes: [[1800, 1000], [640, 300]], + requestId: 'xyz654', + params: { + zoneId: 123, + callbackName: 'false' + } + }, { + bidder: 'sublime', + adUnitCode: 'sublime_code_2', + bidId: 'abc1234_2', + sizes: [[1, 1]], + requestId: 'xyz654_2', + params: { + zoneId: 456, + } + } + ]; + + let bidderRequest = { + gdprConsent: { + consentString: 'EOHEIRCOUCOUIEHZIOEIU-TEST', + gdprApplies: true + }, + refererInfo: { + referer: 'https://example.com', + numIframes: 2, + } + }; + + let request = spec.buildRequests(bidRequests, bidderRequest); + + it('should have a post method', function() { + expect(request[0].method).to.equal('POST'); + expect(request[1].method).to.equal('POST'); + }); + + 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); + }); + + it('should have an url that contains bid keyword', function() { + expect(request[0].url).to.match(/bid/); + expect(request[1].url).to.match(/bid/); + }); + }); + + describe('buildRequests: default arguments', function() { + let bidRequests = [{ + bidder: 'sublime', + adUnitCode: 'sublime_code', + bidId: 'abc1234', + sizes: [[1800, 1000], [640, 300]], + requestId: 'xyz654', + params: { + zoneId: 123 + } + }]; + + let request = spec.buildRequests(bidRequests); + + it('should have an url that match the default endpoint', function() { + expect(request[0].url).to.equal('https://pbjs.sskzlabs.com/bid'); + }); + }); + + describe('interpretResponse', function() { + let serverResponse = { + 'request_id': '3db3773286ee59', + 'cpm': 0.5, + 'ad': '', + }; + + it('should get correct bid response', function() { + // Mock the fire method + top.window.sublime = { + analytics: { + fire: function() {} + } + }; + + let expectedResponse = [ + { + requestId: '', + cpm: 0.5, + width: 1800, + height: 1000, + creativeId: 1, + dealId: 1, + currency: 'USD', + netRevenue: true, + ttl: 600, + ad: '', + }, + ]; + let result = spec.interpretResponse({body: serverResponse}); + expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); + }); + + it('should get correct default size for 1x1', function() { + let serverResponse = { + 'requestId': 'xyz654_2', + 'cpm': 0.5, + 'ad': '', + }; + + let bidRequest = { + bidder: 'sublime', + adUnitCode: 'sublime_code_2', + bidId: 'abc1234_2', + data: { + w: 1, + h: 1, + }, + requestId: 'xyz654_2', + params: { + zoneId: 456, + } + }; + + let result = spec.interpretResponse({body: serverResponse}, bidRequest); + + let expectedResponse = { + requestId: 'xyz654_2', + cpm: 0.5, + width: 1, + height: 1, + creativeId: 1, + dealId: 1, + currency: 'EUR', + netRevenue: true, + ttl: 600, + ad: '', + }; + + expect(result[0]).to.deep.equal(expectedResponse); + }); + + it('should return bid empty response', function () { + let serverResponse = ''; + let bidRequest = {}; + + let result = spec.interpretResponse({ body: serverResponse }, bidRequest); + + let expectedResponse = []; + + expect(result).to.deep.equal(expectedResponse); + }); + + it('should return bid with default value in response', function () { + let serverResponse = { + 'requestId': 'xyz654_2', + 'ad': '', + }; + + let bidRequest = { + bidder: 'sublime', + adUnitCode: 'sublime_code_2', + bidId: 'abc1234_2', + data: { + w: 1, + h: 1, + }, + requestId: 'xyz654_2', + params: { + zoneId: 456, + } + }; + + let result = spec.interpretResponse({ body: serverResponse }, bidRequest); + + let expectedResponse = { + requestId: 'xyz654_2', + cpm: 0, + width: 1, + height: 1, + creativeId: 1, + dealId: 1, + currency: 'EUR', + netRevenue: true, + ttl: 600, + ad: '', + }; + + expect(result[0]).to.deep.equal(expectedResponse); + }); + + it('should return empty bid response because of timeout', function () { + let serverResponse = { + 'requestId': 'xyz654_2', + 'timeout': true, + 'ad': '', + }; + + let bidRequest = { + bidder: 'sublime', + adUnitCode: 'sublime_code_2', + bidId: 'abc1234_2', + data: { + w: 1, + h: 1, + }, + requestId: 'xyz654_2', + params: { + zoneId: 456, + } + }; + + let result = spec.interpretResponse({ body: serverResponse }, bidRequest); + + let expectedResponse = []; + + expect(result).to.deep.equal(expectedResponse); + }); + }); +}); diff --git a/test/spec/modules/supply2BidAdapter_spec.js b/test/spec/modules/supply2BidAdapter_spec.js new file mode 100644 index 00000000000..8abbecd801c --- /dev/null +++ b/test/spec/modules/supply2BidAdapter_spec.js @@ -0,0 +1,332 @@ +import { expect } from 'chai'; +import { spec } from 'modules/supply2BidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; + +describe('Supply2Adapter', function () { + const adapter = newBidder(spec); + + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function () { + let bid = { + 'bidder': 'supply2', + 'params': { + 'uid': '16' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not passed', function () { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'uid': 0 + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + function parseRequest(url) { + const res = {}; + url.split('&').forEach((it) => { + const couple = it.split('='); + res[couple[0]] = decodeURIComponent(couple[1]); + }); + return res; + } + let bidRequests = [ + { + 'bidder': 'supply2', + 'params': { + 'uid': '16' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }, + { + 'bidder': 'supply2', + 'params': { + 'uid': '16' + }, + 'adUnitCode': 'adunit-code-2', + 'sizes': [[728, 90]], + 'bidId': '3150ccb55da321', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }, + { + 'bidder': 'supply2', + 'params': { + 'uid': '17' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '42dbe3a7168a6a', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + } + ]; + + it('should attach valid params to the tag', function () { + const request = spec.buildRequests([bidRequests[0]]); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload).to.have.property('u').that.is.a('string'); + expect(payload).to.have.property('pt', 'net'); + expect(payload).to.have.property('auids', '16'); + expect(payload).to.have.property('r', '22edbae2733bf6'); + }); + + it('auids must not be duplicated', function () { + const request = spec.buildRequests(bidRequests); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload).to.have.property('u').that.is.a('string'); + expect(payload).to.have.property('pt', 'net'); + expect(payload).to.have.property('auids', '16,17'); + expect(payload).to.have.property('r', '22edbae2733bf6'); + }); + + it('if timeout is present, payload must have wtimeout parameter', function () { + const request = spec.buildRequests(bidRequests, {timeout: 2000}); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload).to.have.property('wtimeout', '2000'); + }); + + it('pt parameter must be "gross" if params.priceType === "gross"', function () { + bidRequests[1].params.priceType = 'gross'; + const request = spec.buildRequests(bidRequests); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload).to.have.property('u').that.is.a('string'); + expect(payload).to.have.property('pt', 'gross'); + expect(payload).to.have.property('auids', '16,17'); + expect(payload).to.have.property('r', '22edbae2733bf6'); + delete bidRequests[1].params.priceType; + }); + + it('pt parameter must be "net" or "gross"', function () { + bidRequests[1].params.priceType = 'some'; + const request = spec.buildRequests(bidRequests); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload).to.have.property('u').that.is.a('string'); + expect(payload).to.have.property('pt', 'net'); + expect(payload).to.have.property('auids', '16,17'); + expect(payload).to.have.property('r', '22edbae2733bf6'); + delete bidRequests[1].params.priceType; + }); + + it('if gdprConsent is present payload must have gdpr params', function () { + const request = spec.buildRequests(bidRequests, {gdprConsent: {consentString: 'AAA', gdprApplies: true}}); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload).to.have.property('gdpr_consent', 'AAA'); + expect(payload).to.have.property('gdpr_applies', '1'); + }); + + it('if gdprApplies is false gdpr_applies must be 0', function () { + const request = spec.buildRequests(bidRequests, {gdprConsent: {consentString: 'AAA', gdprApplies: false}}); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload).to.have.property('gdpr_consent', 'AAA'); + expect(payload).to.have.property('gdpr_applies', '0'); + }); + + it('if gdprApplies is undefined gdpr_applies must be 1', function () { + const request = spec.buildRequests(bidRequests, {gdprConsent: {consentString: 'AAA'}}); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload).to.have.property('gdpr_consent', 'AAA'); + expect(payload).to.have.property('gdpr_applies', '1'); + }); + }); + + describe('interpretResponse', function () { + const responses = [ + {'bid': [{'price': 1.15, 'adm': '
    test content 1
    ', 'auid': 23, 'h': 250, 'w': 300}], 'seat': '1'}, + {'bid': [{'price': 0.5, 'adm': '
    test content 2
    ', 'auid': 24, 'h': 90, 'w': 728}], 'seat': '1'}, + {'bid': [{'price': 0, 'auid': 25, 'h': 250, 'w': 300}], 'seat': '1'}, + {'bid': [{'price': 0, 'adm': '
    test content 4
    ', 'h': 250, 'w': 300}], 'seat': '1'}, + undefined, + {'bid': [], 'seat': '1'}, + {'seat': '1'}, + ]; + + it('should get correct bid response', function () { + const bidRequests = [ + { + 'bidder': 'supply2', + 'params': { + 'uid': '23' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '659423fff799cb', + 'bidderRequestId': '5f2009617a7c0a', + 'auctionId': '1cbd2feafe5e8b', + } + ]; + const request = spec.buildRequests(bidRequests); + const expectedResponse = [ + { + 'requestId': '659423fff799cb', + 'cpm': 1.15, + 'creativeId': 23, + 'dealId': undefined, + 'width': 300, + 'height': 250, + 'ad': '
    test content 1
    ', + 'bidderCode': 'supply2', + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 360, + } + ]; + + const result = spec.interpretResponse({'body': {'seatbid': [responses[0]]}}, request); + expect(result).to.deep.equal(expectedResponse); + }); + + it('should get correct multi bid response', function () { + const bidRequests = [ + { + 'bidder': 'supply2', + 'params': { + 'uid': '23' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '300bfeb0d71a5b', + 'bidderRequestId': '2c2bb1972df9a', + 'auctionId': '1fa09aee5c8c99', + }, + { + 'bidder': 'supply2', + 'params': { + 'uid': '24' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '4dff80cc4ee346', + 'bidderRequestId': '2c2bb1972df9a', + 'auctionId': '1fa09aee5c8c99', + }, + { + 'bidder': 'supply2', + 'params': { + 'uid': '23' + }, + 'adUnitCode': 'adunit-code-2', + 'sizes': [[728, 90]], + 'bidId': '5703af74d0472a', + 'bidderRequestId': '2c2bb1972df9a', + 'auctionId': '1fa09aee5c8c99', + } + ]; + const request = spec.buildRequests(bidRequests); + const expectedResponse = [ + { + 'requestId': '300bfeb0d71a5b', + 'cpm': 1.15, + 'creativeId': 23, + 'dealId': undefined, + 'width': 300, + 'height': 250, + 'ad': '
    test content 1
    ', + 'bidderCode': 'supply2', + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 360, + }, + { + 'requestId': '5703af74d0472a', + 'cpm': 1.15, + 'creativeId': 23, + 'dealId': undefined, + 'width': 300, + 'height': 250, + 'ad': '
    test content 1
    ', + 'bidderCode': 'supply2', + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 360, + }, + { + 'requestId': '4dff80cc4ee346', + 'cpm': 0.5, + 'creativeId': 24, + 'dealId': undefined, + 'width': 728, + 'height': 90, + 'ad': '
    test content 2
    ', + 'bidderCode': 'supply2', + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 360, + } + ]; + + const result = spec.interpretResponse({'body': {'seatbid': [responses[0], responses[1]]}}, request); + expect(result).to.deep.equal(expectedResponse); + }); + + it('handles wrong and nobid responses', function () { + const bidRequests = [ + { + 'bidder': 'supply2', + 'params': { + 'uid': '25' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '300bfeb0d7190gf', + 'bidderRequestId': '2c2bb1972d23af', + 'auctionId': '1fa09aee5c84d34', + }, + { + 'bidder': 'supply2', + 'params': { + 'uid': '26' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '300bfeb0d71321', + 'bidderRequestId': '2c2bb1972d23af', + 'auctionId': '1fa09aee5c84d34', + }, + { + 'bidder': 'supply2', + 'params': { + 'uid': '27' + }, + 'adUnitCode': 'adunit-code-2', + 'sizes': [[728, 90]], + 'bidId': '300bfeb0d7183bb', + 'bidderRequestId': '2c2bb1972d23af', + 'auctionId': '1fa09aee5c84d34', + } + ]; + const request = spec.buildRequests(bidRequests); + const result = spec.interpretResponse({'body': {'seatbid': responses.slice(2)}}, request); + expect(result.length).to.equal(0); + }); + }); +}); diff --git a/test/spec/modules/synacormediaBidAdapter_spec.js b/test/spec/modules/synacormediaBidAdapter_spec.js new file mode 100644 index 00000000000..d2f024181a4 --- /dev/null +++ b/test/spec/modules/synacormediaBidAdapter_spec.js @@ -0,0 +1,698 @@ +import { assert, expect } from 'chai'; +import { BANNER } from 'src/mediaTypes'; +import { spec } from 'modules/synacormediaBidAdapter'; + +describe('synacormediaBidAdapter ', function () { + describe('isBidRequestValid', function () { + let bid; + beforeEach(function () { + bid = { + sizes: [300, 250], + params: { + seatId: 'prebid', + placementId: '1234' + } + }; + }); + + it('should return true when params placementId and seatId are truthy', function () { + assert(spec.isBidRequestValid(bid)); + }); + + it('should return false when sizes are missing', function () { + delete bid.sizes; + assert.isFalse(spec.isBidRequestValid(bid)); + }); + + it('should return false when the only size is unwanted', function () { + bid.sizes = [[1, 1]]; + assert.isFalse(spec.isBidRequestValid(bid)); + }); + + it('should return false when seatId param is missing', function () { + delete bid.params.seatId; + assert.isFalse(spec.isBidRequestValid(bid)); + }); + + it('should return false when placementId param is missing', function () { + delete bid.params.placementId; + assert.isFalse(spec.isBidRequestValid(bid)); + }); + + it('should return false when params is missing or null', function () { + assert.isFalse(spec.isBidRequestValid({ params: null })); + assert.isFalse(spec.isBidRequestValid({})); + assert.isFalse(spec.isBidRequestValid(null)); + }); + }); + + describe('impression type', function() { + let nonVideoReq = { + bidId: '9876abcd', + sizes: [[300, 250], [300, 600]], + params: { + seatId: 'prebid', + placementId: '1234', + bidfloor: '0.50' + } + }; + + let bannerReq = { + bidId: '9876abcd', + sizes: [[300, 250], [300, 600]], + params: { + seatId: 'prebid', + placementId: '1234', + bidfloor: '0.50' + }, + mediaTypes: { + banner: { + h: 600, + pos: 0, + w: 300, + } + }, + }; + + let videoReq = { + bidId: '9876abcd', + sizes: [[640, 480]], + params: { + seatId: 'prebid', + placementId: '1234', + bidfloor: '0.50' + }, + mediaTypes: { + video: { + context: 'instream', + playerSize: [ + [ + 640, + 480 + ] + ] + } + }, + }; + it('should return correct impression type video/banner', function() { + assert.isFalse(spec.isVideoBid(nonVideoReq)); + assert.isFalse(spec.isVideoBid(bannerReq)); + assert.isTrue(spec.isVideoBid(videoReq)); + }); + }); + describe('buildRequests', function () { + let validBidRequestVideo = { + bidder: 'synacormedia', + params: { + seatId: 'prebid', + placementId: '1234', + video: { + minduration: 30 + } + }, + mediaTypes: { + video: { + context: 'instream', + playerSize: [[ 640, 480 ]] + } + }, + adUnitCode: 'video1', + transactionId: '93e5def8-29aa-4fe8-bd3a-0298c39f189a', + sizes: [[ 640, 480 ]], + bidId: '2624fabbb078e8', + bidderRequestId: '117954d20d7c9c', + auctionId: 'defd525f-4f1e-4416-a4cb-ae53be90e706', + src: 'client', + bidRequestsCount: 1 + }; + + let bidderRequestVideo = { + bidderCode: 'synacormedia', + auctionId: 'VideoAuctionId124', + bidderRequestId: '117954d20d7c9c', + auctionStart: 1553624929697, + timeout: 700, + refererInfo: { + referer: 'http://localhost:9999/test/pages/video.html?pbjs_debug=true', + reachedTop: true, + numIframes: 0, + stack: [ 'http://localhost:9999/test/pages/video.html?pbjs_debug=true' ] + }, + start: 1553624929700 + }; + + bidderRequestVideo.bids = validBidRequestVideo; + let expectedDataVideo1 = { + id: 'v2624fabbb078e8-640x480', + tagid: '1234', + video: { + w: 640, + h: 480, + pos: 0, + minduration: 30 + } + }; + + let validBidRequest = { + bidId: '9876abcd', + sizes: [[300, 250], [300, 600]], + params: { + seatId: 'prebid', + placementId: '1234', + bidfloor: '0.50' + } + }; + + let bidderRequest = { + auctionId: 'xyz123', + refererInfo: { + referer: 'https://test.com/foo/bar' + } + }; + + let expectedDataImp1 = { + banner: { + h: 250, + pos: 0, + w: 300, + }, + id: 'b9876abcd-300x250', + tagid: '1234', + bidfloor: 0.5 + }; + let expectedDataImp2 = { + banner: { + h: 600, + pos: 0, + w: 300, + }, + id: 'b9876abcd-300x600', + tagid: '1234', + bidfloor: 0.5 + }; + + it('should return valid request when valid bids are used', function () { + // banner test + let req = spec.buildRequests([validBidRequest], bidderRequest); + expect(req).be.an('object'); + expect(req).to.have.property('method', 'POST'); + expect(req).to.have.property('url'); + expect(req.url).to.contain('//prebid.technoratimedia.com/openrtb/bids/prebid?'); + expect(req.data).to.exist.and.to.be.an('object'); + expect(req.data.id).to.equal('xyz123'); + expect(req.data.imp).to.eql([expectedDataImp1, expectedDataImp2]); + + // video test + let reqVideo = spec.buildRequests([validBidRequestVideo], bidderRequestVideo); + expect(reqVideo).be.an('object'); + expect(reqVideo).to.have.property('method', 'POST'); + expect(reqVideo).to.have.property('url'); + expect(reqVideo.url).to.contain('//prebid.technoratimedia.com/openrtb/bids/prebid?'); + expect(reqVideo.data).to.exist.and.to.be.an('object'); + expect(reqVideo.data.id).to.equal('VideoAuctionId124'); + expect(reqVideo.data.imp).to.eql([expectedDataVideo1]); + }); + + it('should return multiple bids when multiple valid requests with the same seatId are used', function () { + let secondBidRequest = { + bidId: 'foobar', + sizes: [[300, 600]], + params: { + seatId: validBidRequest.params.seatId, + placementId: '5678', + bidfloor: '0.50' + } + }; + let req = spec.buildRequests([validBidRequest, secondBidRequest], bidderRequest); + expect(req).to.exist.and.be.an('object'); + expect(req).to.have.property('method', 'POST'); + expect(req).to.have.property('url'); + expect(req.url).to.contain('//prebid.technoratimedia.com/openrtb/bids/prebid?'); + expect(req.data.id).to.equal('xyz123'); + expect(req.data.imp).to.eql([expectedDataImp1, expectedDataImp2, { + banner: { + h: 600, + pos: 0, + w: 300, + }, + id: 'bfoobar-300x600', + tagid: '5678', + bidfloor: 0.5 + }]); + }); + + it('should return only first bid when different seatIds are used', function () { + let mismatchedSeatBidRequest = { + bidId: 'foobar', + sizes: [[300, 250]], + params: { + seatId: 'somethingelse', + placementId: '5678', + bidfloor: '0.50' + } + }; + let req = spec.buildRequests([mismatchedSeatBidRequest, validBidRequest], bidderRequest); + expect(req).to.have.property('method', 'POST'); + expect(req).to.have.property('url'); + expect(req.url).to.contain('//prebid.technoratimedia.com/openrtb/bids/somethingelse?'); + expect(req.data.id).to.equal('xyz123'); + expect(req.data.imp).to.eql([ + { + banner: { + h: 250, + pos: 0, + w: 300, + }, + id: 'bfoobar-300x250', + tagid: '5678', + bidfloor: 0.5 + } + ]); + }); + + it('should not use bidfloor when the value is not a number', function () { + let badFloorBidRequest = { + bidId: '9876abcd', + sizes: [[300, 250]], + params: { + seatId: 'prebid', + placementId: '1234', + bidfloor: 'abcd' + } + }; + let req = spec.buildRequests([badFloorBidRequest], 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$$'); + expect(req.data.id).to.equal('xyz123'); + expect(req.data.imp).to.eql([ + { + banner: { + h: 250, + pos: 0, + w: 300, + }, + id: 'b9876abcd-300x250', + tagid: '1234', + } + ]); + }); + + it('should not use bidfloor when there is no value', function () { + let badFloorBidRequest = { + bidId: '9876abcd', + sizes: [[300, 250]], + params: { + seatId: 'prebid', + placementId: '1234' + } + }; + let req = spec.buildRequests([badFloorBidRequest], 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$$'); + expect(req.data.id).to.equal('xyz123'); + expect(req.data.imp).to.eql([ + { + banner: { + h: 250, + pos: 0, + w: 300, + }, + id: 'b9876abcd-300x250', + tagid: '1234', + } + ]); + }); + + it('should use the pos given by the bid request', function () { + let newPosBidRequest = { + bidId: '9876abcd', + sizes: [[300, 250]], + params: { + seatId: 'prebid', + placementId: '1234', + pos: 1 + } + }; + let req = spec.buildRequests([newPosBidRequest], 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$$'); + expect(req.data.id).to.equal('xyz123'); + expect(req.data.imp).to.eql([ + { + banner: { + h: 250, + w: 300, + pos: 1, + }, + id: 'b9876abcd-300x250', + tagid: '1234' + } + ]); + }); + + it('should use the default pos if none in bid request', function () { + let newPosBidRequest = { + bidId: '9876abcd', + sizes: [[300, 250]], + params: { + seatId: 'prebid', + placementId: '1234', + } + }; + let req = spec.buildRequests([newPosBidRequest], 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$$'); + expect(req.data.id).to.equal('xyz123'); + expect(req.data.imp).to.eql([ + { + banner: { + h: 250, + w: 300, + pos: 0, + }, + id: 'b9876abcd-300x250', + tagid: '1234' + } + ]); + }); + it('should not return a request when no valid bid request used', function () { + expect(spec.buildRequests([], bidderRequest)).to.be.undefined; + expect(spec.buildRequests([validBidRequest], null)).to.be.undefined; + }); + + it('should return empty impression when there is no valid sizes in bidrequest', function() { + let validBidReqWithoutSize = { + bidId: '9876abcd', + sizes: [], + params: { + seatId: 'prebid', + placementId: '1234', + bidfloor: '0.50' + } + }; + + let validBidReqInvalidSize = { + bidId: '9876abcd', + sizes: [[300]], + params: { + seatId: 'prebid', + placementId: '1234', + bidfloor: '0.50' + } + }; + + let bidderRequest = { + auctionId: 'xyz123', + refererInfo: { + referer: 'https://test.com/foo/bar' + } + }; + + let req = spec.buildRequests([validBidReqWithoutSize], bidderRequest); + assert.isUndefined(req); + req = spec.buildRequests([validBidReqInvalidSize], bidderRequest); + assert.isUndefined(req); + }); + it('should use all the video params in the impression request', function () { + let validBidRequestVideo = { + bidder: 'synacormedia', + params: { + seatId: 'prebid', + placementId: '1234', + video: { + minduration: 30, + maxduration: 45, + startdelay: 1, + linearity: 1, + placement: 1, + mimes: ['video/mp4'], + protocols: [1], + api: 1 + } + }, + mediaTypes: { + video: { + context: 'instream', + playerSize: [[ 640, 480 ]] + } + }, + adUnitCode: 'video1', + transactionId: '93e5def8-29aa-4fe8-bd3a-0298c39f189a', + sizes: [[ 640, 480 ]], + bidId: '2624fabbb078e8', + bidderRequestId: '117954d20d7c9c', + auctionId: 'defd525f-4f1e-4416-a4cb-ae53be90e706', + src: 'client', + bidRequestsCount: 1 + }; + + let req = spec.buildRequests([validBidRequestVideo], 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$$'); + expect(req.data.id).to.equal('xyz123'); + expect(req.data.imp).to.eql([ + { + video: { + h: 480, + pos: 0, + w: 640, + minduration: 30, + maxduration: 45, + startdelay: 1, + linearity: 1, + placement: 1, + mimes: ['video/mp4'], + protocols: [1], + api: 1 + }, + id: 'v2624fabbb078e8-640x480', + tagid: '1234', + } + ]); + }); + it('should move any video params in the mediaTypes object to params.video object', function () { + let validBidRequestVideo = { + bidder: 'synacormedia', + params: { + seatId: 'prebid', + placementId: '1234', + video: { + minduration: 30, + maxduration: 45, + protocols: [1], + api: 1 + } + }, + mediaTypes: { + video: { + context: 'instream', + playerSize: [[ 640, 480 ]], + startdelay: 1, + linearity: 1, + placement: 1, + mimes: ['video/mp4'] + } + }, + adUnitCode: 'video1', + transactionId: '93e5def8-29aa-4fe8-bd3a-0298c39f189a', + sizes: [[ 640, 480 ]], + bidId: '2624fabbb078e8', + bidderRequestId: '117954d20d7c9c', + auctionId: 'defd525f-4f1e-4416-a4cb-ae53be90e706', + src: 'client', + bidRequestsCount: 1 + }; + + let req = spec.buildRequests([validBidRequestVideo], 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$$'); + expect(req.data.id).to.equal('xyz123'); + expect(req.data.imp).to.eql([ + { + video: { + h: 480, + pos: 0, + w: 640, + minduration: 30, + maxduration: 45, + startdelay: 1, + linearity: 1, + placement: 1, + mimes: ['video/mp4'], + protocols: [1], + api: 1 + }, + id: 'v2624fabbb078e8-640x480', + tagid: '1234', + } + ]); + }); + }); + + describe('interpretResponse', function () { + let bidResponse = { + id: '10865933907263896~9998~0', + impid: 'b9876abcd-300x250', + price: 0.13, + crid: '1022-250', + adm: '', + nurl: '//uat-net.technoratimedia.com/openrtb/tags?ID=k5JkFVQ1RJT05fSU1QX0lEPXYyZjczN&AUCTION_PRICE=${AUCTION_PRICE}' + }; + let bidResponse2 = { + id: '10865933907263800~9999~0', + impid: 'b9876abcd-300x600', + price: 1.99, + crid: '9993-013', + adm: '', + nurl: '//uat-net.technoratimedia.com/openrtb/tags?ID=OTk5OX4wJkFVQ1RJT05fU0VBVF9JR&AUCTION_PRICE=${AUCTION_PRICE}' + }; + + let serverResponse; + beforeEach(function() { + serverResponse = { + body: { + id: 'abc123', + seatbid: [{ + seat: '9998', + bid: [], + }] + } + }; + }); + + it('should return 1 video bid when 1 bid is in the video response', function () { + let serverRespVideo = { + body: { + id: 'abcd1234', + seatbid: [ + { + bid: [ + { + id: '11339128001692337~9999~0', + impid: 'v2da7322b2df61f-640x480', + 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' ], + cid: 'bidder-crid', + crid: 'bidder-cid', + cat: [] + } + ], + seat: '9999' + } + ] + } + }; + + // serverResponse.body.seatbid[0].bid.push(bidResponse); + let resp = spec.interpretResponse(serverRespVideo); + 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, + creativeId: '9999_bidder-cid', + currency: 'USD', + netRevenue: true, + 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, + videoCacheKey: 'QVVDVElPTl9JRD1lOTBhYWU1My1hZDkwLTRkNDEtYTQxMC1lZDY1MjIxMDc0ZGMmQVVDVElPTl9CSURfSUQ9MTEzMzkxMjgwMDE2OTIzMzd-OTk5OX4wJkFVQ1RJT05fU0VBVF9JRD05OTk5JkFVQ1RJT05fSU1QX0lEPXYyZGE3MzIyYjJkZjYxZi02NDB4NDgwJkFDVE9SX1JFRj1ha2thLnRjcDovL2F3cy1lYXN0MUBhZHMxMy5jYXAtdXNlMS5zeW5hY29yLmNvbToyNTUxL3VzZXIvJGNMYmZiIy0xOTk4NTIzNTk3JlNFQVRfSUQ9cHJlYmlk', + vastUrl: 'https://uat-net.technoratimedia.com/openrtb/tags?ID=QVVDVElPTl9JRD1lOTBhYWU1My1hZDkwLTRkNDEtYTQxMC1lZDY1MjIxMDc0ZGMmQVVDVElPTl9CSURfSUQ9MTEzMzkxMjgwMDE2OTIzMzd-OTk5OX4wJkFVQ1RJT05fU0VBVF9JRD05OTk5JkFVQ1RJT05fSU1QX0lEPXYyZGE3MzIyYjJkZjYxZi02NDB4NDgwJkFDVE9SX1JFRj1ha2thLnRjcDovL2F3cy1lYXN0MUBhZHMxMy5jYXAtdXNlMS5zeW5hY29yLmNvbToyNTUxL3VzZXIvJGNMYmZiIy0xOTk4NTIzNTk3JlNFQVRfSUQ9cHJlYmlk&AUCTION_PRICE=0.45' + }); + }); + + it('should return 1 bid when 1 bid is in the response', function () { + serverResponse.body.seatbid[0].bid.push(bidResponse); + let resp = spec.interpretResponse(serverResponse); + 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, + creativeId: '9998_1022-250', + currency: 'USD', + netRevenue: true, + mediaType: BANNER, + ad: '', + ttl: 60 + }); + }); + + it('should return 2 bids when 2 bids are in the response', function () { + serverResponse.body.seatbid[0].bid.push(bidResponse); + serverResponse.body.seatbid.push({ + seat: '9999', + bid: [bidResponse2], + }); + let resp = spec.interpretResponse(serverResponse); + 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, + creativeId: '9998_1022-250', + currency: 'USD', + netRevenue: true, + mediaType: BANNER, + ad: '', + ttl: 60 + }); + + expect(resp[1]).to.eql({ + requestId: '9876abcd', + adId: '10865933907263800-9999-0', + cpm: 1.99, + width: 300, + height: 600, + creativeId: '9999_9993-013', + currency: 'USD', + netRevenue: true, + mediaType: BANNER, + ad: '', + ttl: 60 + }); + }); + + it('should not return a bid when no bid is in the response', function () { + let resp = spec.interpretResponse(serverResponse); + expect(resp).to.be.an('array').that.is.empty; + }); + + it('should not return a bid when there is no response body', function () { + expect(spec.interpretResponse({ body: null })).to.not.exist; + expect(spec.interpretResponse({ body: 'some error text' })).to.not.exist; + }); + }); + describe('getUserSyncs', function () { + it('should return a usersync when iframes is enabled', function () { + let usersyncs = spec.getUserSyncs({ + iframeEnabled: true + }, null); + expect(usersyncs).to.be.an('array').that.is.not.empty; + expect(usersyncs[0]).to.have.property('type', 'iframe'); + expect(usersyncs[0]).to.have.property('url'); + expect(usersyncs[0].url).to.contain('//ad-cdn.technoratimedia.com/html/usersync.html'); + }); + + it('should not return a usersync when iframes are not enabled', function () { + let usersyncs = spec.getUserSyncs({ + pixelEnabled: true + }, null); + expect(usersyncs).to.be.an('array').that.is.empty; + }); + }); +}); diff --git a/test/spec/modules/taphypeBidAdapter_spec.js b/test/spec/modules/taphypeBidAdapter_spec.js new file mode 100644 index 00000000000..2fcdd964520 --- /dev/null +++ b/test/spec/modules/taphypeBidAdapter_spec.js @@ -0,0 +1,56 @@ +import { expect } from 'chai'; +import { spec } from 'modules/taphypeBidAdapter'; + +describe('taphypeBidAdapterTests', function () { + it('validate_pub_params', function () { + expect(spec.isBidRequestValid({ + bidder: 'taphype', + params: { + placementId: 12345 + } + })).to.equal(true); + }); + + it('validate_generated_params', function () { + let bidRequestData = [{ + bidId: 'bid12345', + bidder: 'taphype', + params: { + placementId: 12345 + }, + sizes: [[300, 250]] + }]; + + let request = spec.buildRequests(bidRequestData); + let req_data = request[0].data; + + expect(req_data.bidId).to.equal('bid12345'); + }); + + it('validate_response_params', function () { + let bidRequestData = { + data: { + bidId: 'bid12345' + } + }; + + let serverResponse = { + body: { + price: 1.23, + ad: '', + size: '300,250' + } + }; + + let bids = spec.interpretResponse(serverResponse, bidRequestData); + expect(bids).to.have.lengthOf(1); + let bid = bids[0]; + expect(bid.cpm).to.equal(1.23); + expect(bid.currency).to.equal('USD'); + expect(bid.width).to.equal('300'); + expect(bid.height).to.equal('250'); + expect(bid.netRevenue).to.equal(true); + expect(bid.requestId).to.equal('bid12345'); + expect(bid.ad).to.equal(''); + }); +}); diff --git a/test/spec/modules/teadsBidAdapter_spec.js b/test/spec/modules/teadsBidAdapter_spec.js new file mode 100644 index 00000000000..af1c7a9c01b --- /dev/null +++ b/test/spec/modules/teadsBidAdapter_spec.js @@ -0,0 +1,394 @@ +import {expect} from 'chai'; +import {spec} from 'modules/teadsBidAdapter'; +import {newBidder} from 'src/adapters/bidderFactory'; + +const ENDPOINT = '//a.teads.tv/hb/bid-request'; +const AD_SCRIPT = '"'; + +describe('teadsBidAdapter', () => { + const adapter = newBidder(spec); + + describe('inherited functions', () => { + it('exists and is a function', () => { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function() { + let bid = { + 'bidder': 'teads', + 'params': { + 'placementId': 10433394, + 'pageId': 1234 + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + 'creativeId': 'er2ee' + }; + + it('should return true when required params found', function() { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when pageId is not valid (letters)', function() { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'placementId': 1234, + 'pageId': 'ABCD' + }; + + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when placementId is not valid (letters)', function() { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'placementId': 'FCP', + 'pageId': 1234 + }; + + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when placementId < 0 or pageId < 0', function() { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'placementId': -1, + 'pageId': -1 + }; + + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when required params are not passed', function() { + let bid = Object.assign({}, bid); + delete bid.params; + + bid.params = { + 'placementId': 0 + }; + + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function() { + let bidRequests = [ + { + 'bidder': 'teads', + 'params': { + 'placementId': 10433394, + 'pageId': 1234 + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + 'creativeId': 'er2ee', + 'deviceWidth': 1680 + } + ]; + + let bidderResquestDefault = { + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000 + }; + + it('sends bid request to ENDPOINT via POST', function() { + const request = spec.buildRequests(bidRequests, bidderResquestDefault); + + expect(request.url).to.equal(ENDPOINT); + expect(request.method).to.equal('POST'); + }); + + it('should send GDPR to endpoint', function() { + let consentString = 'JRJ8RKfDeBNsERRDCSAAZ+A=='; + let bidderRequest = { + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'gdprConsent': { + 'consentString': consentString, + 'gdprApplies': true, + 'vendorData': { + 'hasGlobalConsent': false + } + } + }; + + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.gdpr_iab).to.exist; + expect(payload.gdpr_iab.consent).to.equal(consentString); + expect(payload.gdpr_iab.status).to.equal(12); + }); + + it('should add referer info to payload', function () { + const bidRequest = Object.assign({}, bidRequests[0]) + const bidderRequest = { + refererInfo: { + referer: 'http://example.com/page.html', + reachedTop: true, + numIframes: 2 + } + } + const request = spec.buildRequests([bidRequest], bidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.referrer).to.exist; + expect(payload.referrer).to.deep.equal('http://example.com/page.html') + }); + + it('should send GDPR to endpoint with 11 status', function() { + let consentString = 'JRJ8RKfDeBNsERRDCSAAZ+A=='; + let bidderRequest = { + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'gdprConsent': { + 'consentString': consentString, + 'gdprApplies': true, + 'vendorData': { + 'hasGlobalScope': true + } + } + }; + + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.gdpr_iab).to.exist; + expect(payload.gdpr_iab.consent).to.equal(consentString); + expect(payload.gdpr_iab.status).to.equal(11); + }); + + it('should send GDPR to endpoint with 22 status', function() { + let consentString = 'JRJ8RKfDeBNsERRDCSAAZ+A=='; + let bidderRequest = { + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'gdprConsent': { + 'consentString': undefined, + 'gdprApplies': undefined, + 'vendorData': undefined + } + }; + + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.gdpr_iab).to.exist; + expect(payload.gdpr_iab.consent).to.equal(''); + expect(payload.gdpr_iab.status).to.equal(22); + }); + + it('should send GDPR to endpoint with 0 status', function() { + let consentString = 'JRJ8RKfDeBNsERRDCSAAZ+A=='; + let bidderRequest = { + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'gdprConsent': { + 'consentString': consentString, + 'gdprApplies': false, + 'vendorData': { + 'hasGlobalScope': false + } + } + }; + + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.gdpr_iab).to.exist; + expect(payload.gdpr_iab.consent).to.equal(consentString); + expect(payload.gdpr_iab.status).to.equal(0); + }); + + it('should use good mediaTypes video playerSizes', function() { + const mediaTypesPlayerSize = { + 'mediaTypes': { + 'video': { + 'playerSize': [32, 34] + } + } + }; + checkMediaTypesSizes(mediaTypesPlayerSize, '32x34') + }); + + it('should use good mediaTypes video sizes', function() { + const mediaTypesVideoSizes = { + 'mediaTypes': { + 'video': { + 'sizes': [12, 14] + } + } + }; + checkMediaTypesSizes(mediaTypesVideoSizes, '12x14') + }); + + it('should use good mediaTypes banner sizes', function() { + const mediaTypesBannerSize = { + 'mediaTypes': { + 'banner': { + 'sizes': [46, 48] + } + } + }; + checkMediaTypesSizes(mediaTypesBannerSize, '46x48') + }); + + it('should use good mediaTypes for both video and banner sizes', function() { + const hybridMediaTypes = { + 'mediaTypes': { + 'banner': { + 'sizes': [46, 48] + }, + 'video': { + 'sizes': [[50, 34], [45, 45]] + } + } + }; + checkMediaTypesSizes(hybridMediaTypes, ['46x48', '50x34', '45x45']) + }); + + function checkMediaTypesSizes(mediaTypes, expectedSizes) { + const bidRequestWithBannerSizes = Object.assign(bidRequests[0], mediaTypes); + const requestWithBannerSizes = spec.buildRequests([bidRequestWithBannerSizes], bidderResquestDefault); + const payloadWithBannerSizes = JSON.parse(requestWithBannerSizes.data); + + return payloadWithBannerSizes.data.forEach(bid => { + if (Array.isArray(expectedSizes)) { + expect(JSON.stringify(bid.sizes)).to.equal(JSON.stringify(expectedSizes)); + } else { + expect(bid.sizes[0]).to.equal(expectedSizes); + } + }); + } + }); + + describe('interpretResponse', function() { + let bids = { + 'body': { + 'responses': [{ + 'ad': AD_SCRIPT, + 'cpm': 0.5, + 'currency': 'USD', + 'height': 250, + 'netRevenue': true, + 'bidId': '3ede2a3fa0db94', + 'ttl': 360, + 'width': 300, + 'creativeId': 'er2ee', + 'placementId': 34 + }] + } + }; + + it('should get correct bid response', function() { + let expectedResponse = { + 'cpm': 0.5, + 'width': 300, + 'height': 250, + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 360, + 'ad': AD_SCRIPT, + 'requestId': '3ede2a3fa0db94', + 'creativeId': 'er2ee', + 'placementId': 34 + }; + + let result = spec.interpretResponse(bids); + expect(result[0]).to.deep.equal(expectedResponse); + }); + + it('handles nobid responses', function() { + let bids = { + 'body': { + 'responses': [] + } + }; + + let result = spec.interpretResponse(bids); + expect(result.length).to.equal(0); + }); + }); + + it('should call userSync with good params', function() { + let bids = [{ + 'body': { + 'responses': [{ + 'ad': '', + 'adomain': [''], + 'cid': '1', + 'crid': '700', + 'w': 300, + 'h': 250 + }]}], + 'bidid': 'bidid', + 'cur': 'USD' + }, + 'headers': {} + }; + it('required keys', function () { + const result = spec.interpretResponse(validServerResponse, validBidRequest); + + let requiredKeys = [ + 'requestId', + 'creativeId', + 'adId', + 'cpm', + 'width', + 'height', + 'currency', + 'netRevenue', + 'ttl', + 'ad' + ]; + + let resultKeys = Object.keys(result[0]); + requiredKeys.forEach(function(key) { + expect(resultKeys.indexOf(key) !== -1).to.equal(true); + }); + }) + }); + + describe('getUserSyncs', function () { + it('check empty response getUserSyncs', function () { + const result = spec.getUserSyncs('', ''); + expect(result).to.deep.equal([]); + }); + }); +}); diff --git a/test/spec/modules/topRTBBidAdapter_spec.js b/test/spec/modules/topRTBBidAdapter_spec.js new file mode 100644 index 00000000000..83e5de3e4b9 --- /dev/null +++ b/test/spec/modules/topRTBBidAdapter_spec.js @@ -0,0 +1,67 @@ +import { expect } from 'chai'; +import { spec } from 'modules/topRTBBidAdapter'; + +describe('topRTBBidAdapterTests', function () { + it('validate_pub_params', function () { + expect(spec.isBidRequestValid({ + bidder: 'topRTB', + params: { + adUnitId: 'c5c06f77430c4c33814a0577cb4cc978' + }, + adName: 'banner' + })); + }); + + it('validate_generated_params', function () { + let bidRequestData = [{ + bidId: 'bid12345', + bidder: 'topRTB', + adName: 'banner', + adType: '{"banner":{"sizes":[[]]}}', + params: { + adUnitId: 'c5c06f77430c4c33814a0577cb4cc978' + }, + sizes: [[728, 90]] + }]; + + let request = spec.buildRequests(bidRequestData); + const current_url = request.url; + const search_params = current_url.searchParams; + }); + + it('validate_response_params', function () { + let bidRequestData = { + data: { + bidId: 'bid12345' + } + }; + + let serverResponse = { + body: [{ + 'cpm': 1, + 'mediadata': "Banner 728x90", + 'width': 728, + 'currency': 'USD', + 'id': 'cd95dffec6b645afbc4e5aa9f68f2ff3', + 'type': 'RICHMEDIA', + 'ttl': 4000, + 'bidId': 'bid12345', + 'status': 'success', + 'height': 90}], + 'adName': 'banner', + 'vastXml': '', + 'mediaType': 'banner', + 'tracking': 'https://ssp.toprtb.com/ssp/tracking?F0cloTiKIw%2BjZ2UNDvlKGn5%2FWoAO9cnlAUDm6gFBM8bImY2fKo%2BMTvI0XvXzFTZSb5v8o4EUbPId9hckptTqA4QPaWvpVYCRKRZceXNa4kjtvfm4j2e%2FcRKgkns2goHXi7IZC0sBIbE77WWg%2BPBYv%2BCu84H%2FSH69mi%2FDaWcQlfaEOdkaJdstJEkaZtkgWnFnS7aagte%2BfdEbOqcTxq5hzj%2BZ4NZbwgReuWTQZbfrMWjkXFbn%2B35vZuI319o6XH9n9fKLS4xp8zstXfQT2oSgjw1NmrwqRKf1efB1UaWlS1TbkSqxZ7Kcy7nJvAZrDk0tzcSeIxe4VfHpwgPPs%2BueUeGwz3o7OCh7H1sCmogSrmJFB9JTeXudFjC13iANAtu4SvG9bGIbiJxS%2BNfkjy2mLFm8kSIcIobjNkMEcUAwmoqJNRndwb66a3Iovk2NTo0Ly%2FV7Y5ECPcS5%2FPBrIEOuQXS5SNUPRWKoklX5nexHtOc%3D', + 'impression': 'https://ssp.toprtb.com/ssp/impression?id=64f29f7b226249f19925a680a506b32d' + }; + + let bids = spec.interpretResponse(serverResponse, bidRequestData); + expect(bids).to.have.lengthOf(1); + let bid = bids[0]; + expect(bid.cpm).to.equal(1); + expect(bid.currency).to.equal('USD'); + expect(bid.width).to.equal(728); + expect(bid.height).to.equal(90); + expect(bid.requestId).to.equal('bid12345'); + }); +}); diff --git a/test/spec/modules/trafficrootsBidAdapter_spec.js b/test/spec/modules/trafficrootsBidAdapter_spec.js new file mode 100644 index 00000000000..54c102d33ef --- /dev/null +++ b/test/spec/modules/trafficrootsBidAdapter_spec.js @@ -0,0 +1,149 @@ +import { expect } from 'chai'; +import { spec } from 'modules/trafficrootsBidAdapter'; + +describe('trafficrootsAdapterTests', () => { + describe('bidRequestValidity', () => { + it('bidRequest with zoneId and deliveryUrl params', () => { + expect(spec.isBidRequestValid({ + bidder: 'trafficroots', + params: { + zoneId: 'aa0444af31', + deliveryUrl: 'https://service.trafficroosts.com/prebid' + } + })).to.equal(true); + }); + + it('bidRequest with only zoneId', () => { + expect(spec.isBidRequestValid({ + bidder: 'trafficroots', + params: { + zoneId: '8f527a4835' + } + })).to.equal(true); + }); + + it('bidRequest with only deliveryUrl', () => { + expect(spec.isBidRequestValid({ + bidder: 'trafficroots', + params: { + deliveryUrl: 'https://service.trafficroosts.com/prebid' + } + })).to.equal(false); + }); + }); + + describe('bidRequest', () => { + const bidRequests = [{ + 'bidder': 'trafficroots', + 'bidId': '29fa5c08928bde', + 'params': { + 'zoneId': 'aa0444af31', + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + 'sizes': [[300, 250], [300, 600]], + 'bidderRequestId': '418b37f85e772c', + 'auctionId': '18fd8b8b0bd757' + }, { + 'bidder': 'trafficroots', + 'bidId': '29fa5c08928bde', + 'params': { + 'zoneId': '8f527a4835', + 'deliveryUrl': 'https://service.trafficroosts.com/prebid' + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + 'sizes': [[300, 250], [300, 600]], + 'bidderRequestId': '418b37f85e772c', + 'auctionId': '18fd8b8b0bd757' + }]; + + it('bidRequest method', () => { + const request = spec.buildRequests(bidRequests); + expect(request.method).to.equal('GET'); + }); + + it('bidRequest url', () => { + const request = spec.buildRequests(bidRequests); + expect(request.url).to.match(new RegExp(`${bidRequests[1].params.deliveryUrl}`)); + }); + + it('bidRequest data', () => { + const request = spec.buildRequests(bidRequests); + expect(request.data).to.exist; + }); + + it('bidRequest zoneIds', () => { + const request = spec.buildRequests(bidRequests); + expect(request.data.zoneId).to.equal('aa0444af31;8f527a4835'); + }); + + it('bidRequest gdpr consent', () => { + const consentString = 'consentString'; + const bidderRequest = { + bidderCode: 'trafficroots', + auctionId: '18fd8b8b0bd757', + bidderRequestId: '418b37f85e772c', + timeout: 3000, + gdprConsent: { + consentString: consentString, + gdprApplies: true + } + }; + + const request = spec.buildRequests(bidRequests, bidderRequest); + + expect(request.data.gdpr).to.exist; + expect(request.data.gdpr.applies).to.exist.and.to.be.true; + expect(request.data.gdpr.consent).to.exist.and.to.equal(consentString); + }); + }); + + describe('interpretResponse', () => { + const bidRequest = [{ + 'bidder': 'trafficroots', + 'bidId': '29fa5c08928bde', + 'params': { + 'zoneId': 'aa0444af31', + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + 'sizes': [[300, 250], [300, 600]], + 'bidderRequestId': '418b37f85e772c', + 'auctionId': '18fd8b8b0bd757' + }]; + + const bidResponse = { + body: [{ + 'id': 'div-gpt-ad-1460505748561-0', + 'ad': 'test ad', + 'width': 320, + 'height': 250, + 'cpm': 5.2 + }], + headers: {} + }; + + it('required keys', () => { + const result = spec.interpretResponse(bidResponse, bidRequest); + + let requiredKeys = [ + 'requestId', + 'creativeId', + 'adId', + 'cpm', + 'width', + 'height', + 'currency', + 'netRevenue', + 'ttl', + 'ad' + ]; + + let resultKeys = Object.keys(result[0]); + resultKeys.forEach(function(key) { + expect(requiredKeys.indexOf(key) !== -1).to.equal(true); + }); + }) + }); +}); diff --git a/test/spec/modules/tripleliftBidAdapter_spec.js b/test/spec/modules/tripleliftBidAdapter_spec.js index d3013d9be22..12a2f66dde2 100644 --- a/test/spec/modules/tripleliftBidAdapter_spec.js +++ b/test/spec/modules/tripleliftBidAdapter_spec.js @@ -2,8 +2,9 @@ import { expect } from 'chai'; import { tripleliftAdapterSpec } from 'modules/tripleliftBidAdapter'; import { newBidder } from 'src/adapters/bidderFactory'; import { deepClone } from 'src/utils'; +import prebid from '../../../package.json'; -const ENDPOINT = document.location.protocol + '//tlx.3lift.com/header/auction?'; +const ENDPOINT = 'https://tlx.3lift.com/header/auction?'; describe('triplelift adapter', function () { const adapter = newBidder(tripleliftAdapterSpec); @@ -53,34 +54,78 @@ describe('triplelift adapter', function () { }); describe('buildRequests', function () { - let bidRequests = [ - { - bidder: 'triplelift', - params: { - inventoryCode: '12345', - floor: 1.0, - }, - adUnitCode: 'adunit-code', - sizes: [[300, 250], [300, 600], [1, 1, 1], ['flex']], - bidId: '30b31c1838de1e', - bidderRequestId: '22edbae2733bf6', - auctionId: '1d1a030790a475', + let bidRequests; + let bidderRequest; + const schain = { + validation: 'strict', + config: { + ver: '1.0', + complete: 1, + nodes: [ + { + asi: 'indirectseller.com', + sid: '00001', + hp: 1, + } + ] } - ]; + }; + + this.beforeEach(() => { + bidRequests = [ + { + bidder: 'triplelift', + params: { + inventoryCode: '12345', + floor: 1.0, + }, + adUnitCode: 'adunit-code', + sizes: [[300, 250], [300, 600], [1, 1, 1], ['flex']], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + userId: {}, + schain, + } + ]; + + bidderRequest = { + bidderCode: 'triplelift', + auctionId: 'a7ebcd1d-66ff-4b5c-a82c-6a21a6ee5a18', + bidderRequestId: '5c55612f99bc11', + bids: [ + { + imp_id: 0, + cpm: 1.062, + width: 300, + height: 250, + ad: 'ad-markup', + iurl: 'https://s.adroll.com/a/IYR/N36/IYRN366MFVDITBAGNNT5U6.jpg' + } + ], + refererInfo: { + referer: 'http://examplereferer.com' + }, + gdprConsent: { + consentString: 'BOONm0NOONm0NABABAENAa-AAAARh7______b9_3__7_9uz_Kv_K7Vf7nnG072lPVA9LTOQ6gEaY', + gdprApplies: true + }, + }; + }); it('exists and is an object', function () { - const request = tripleliftAdapterSpec.buildRequests(bidRequests); + const request = tripleliftAdapterSpec.buildRequests(bidRequests, bidderRequest); expect(request).to.exist.and.to.be.a('object'); }); it('should only parse sizes that are of the proper length and format', function () { - const request = tripleliftAdapterSpec.buildRequests(bidRequests); + const request = tripleliftAdapterSpec.buildRequests(bidRequests, bidderRequest); expect(request.data.imp[0].banner.format).to.have.length(2); expect(request.data.imp[0].banner.format).to.deep.equal([{w: 300, h: 250}, {w: 300, h: 600}]); }); it('should be a post request and populate the payload', function () { - const request = tripleliftAdapterSpec.buildRequests(bidRequests); + const request = tripleliftAdapterSpec.buildRequests(bidRequests, bidderRequest); const payload = request.data; expect(payload).to.exist; expect(payload.imp[0].tagid).to.equal('12345'); @@ -88,17 +133,119 @@ describe('triplelift adapter', function () { expect(payload.imp[0].banner.format).to.deep.equal([{w: 300, h: 250}, {w: 300, h: 600}]); }); + it('should add tdid to the payload if included', function () { + const id = '6bca7f6b-a98a-46c0-be05-6020f7604598'; + bidRequests[0].userId.tdid = id; + const request = tripleliftAdapterSpec.buildRequests(bidRequests, bidderRequest); + const payload = request.data; + expect(payload).to.exist; + expect(payload.user).to.deep.equal({ext: {eids: [{source: 'adserver.org', uids: [{id, ext: {rtiPartner: 'TDID'}}]}]}}); + }); + + it('should add idl_env to the payload if included', function () { + const id = 'XY6104gr0njcH9UDIR7ysFFJcm2XNpqeJTYslleJ_cMlsFOfZI'; + bidRequests[0].userId.idl_env = id; + const request = tripleliftAdapterSpec.buildRequests(bidRequests, bidderRequest); + const payload = request.data; + expect(payload).to.exist; + expect(payload.user).to.deep.equal({ext: {eids: [{source: 'liveramp.com', uids: [{id, ext: {rtiPartner: 'idl'}}]}]}}); + }); + + it('should add both tdid and idl_env to the payload if both are included', function () { + const tdidId = '6bca7f6b-a98a-46c0-be05-6020f7604598'; + const idlEnvId = 'XY6104gr0njcH9UDIR7ysFFJcm2XNpqeJTYslleJ_cMlsFOfZI'; + bidRequests[0].userId.tdid = tdidId; + bidRequests[0].userId.idl_env = idlEnvId; + + const request = tripleliftAdapterSpec.buildRequests(bidRequests, bidderRequest); + const payload = request.data; + + expect(payload).to.exist; + expect(payload.user).to.deep.equal({ + ext: { + eids: [ + { + source: 'adserver.org', + uids: [ + { + id: tdidId, + ext: { rtiPartner: 'TDID' } + } + ], + }, + { + source: 'liveramp.com', + uids: [ + { + id: idlEnvId, + ext: { rtiPartner: 'idl' } + } + ] + } + ] + } + }); + }); + + it('should add user ids from multiple bid requests', function () { + const tdidId = '6bca7f6b-a98a-46c0-be05-6020f7604598'; + const idlEnvId = 'XY6104gr0njcH9UDIR7ysFFJcm2XNpqeJTYslleJ_cMlsFOfZI'; + + const bidRequestsMultiple = [ + { ...bidRequests[0], userId: { tdid: tdidId } }, + { ...bidRequests[0], userId: { idl_env: idlEnvId } }, + ]; + + const request = tripleliftAdapterSpec.buildRequests(bidRequestsMultiple, bidderRequest); + const payload = request.data; + + expect(payload.user).to.deep.equal({ + ext: { + eids: [ + { + source: 'adserver.org', + uids: [ + { + id: tdidId, + ext: { rtiPartner: 'TDID' } + } + ], + }, + { + source: 'liveramp.com', + uids: [ + { + id: idlEnvId, + ext: { rtiPartner: 'idl' } + } + ] + } + ] + } + }); + }); + it('should return a query string for TL call', function () { - const request = tripleliftAdapterSpec.buildRequests(bidRequests); + const request = tripleliftAdapterSpec.buildRequests(bidRequests, bidderRequest); const url = request.url; expect(url).to.exist; expect(url).to.be.a('string'); expect(url).to.match(/(?:tlx.3lift.com\/header\/auction)/) expect(url).to.match(/(?:lib=prebid)/) - expect(url).to.match(/(?:prebid.version)/) - // expect(url).to.match(/(?:fe=)/) // - expect(url).to.match(/(?:referrer)/) - }) + expect(url).to.match(new RegExp('(?:' + prebid.version + ')')) + expect(url).to.match(/(?:referrer)/); + }); + it('should return schain when present', function() { + const request = tripleliftAdapterSpec.buildRequests(bidRequests, bidderRequest); + const { data: payload } = request; + expect(payload.ext.schain).to.deep.equal(schain); + }); + it('should not create root level ext when schain is not present', function() { + bidRequests[0].schain = undefined; + const request = tripleliftAdapterSpec.buildRequests(bidRequests, bidderRequest); + const { data: payload } = request; + expect(payload.ext).to.deep.equal(undefined); + }); }); describe('interpretResponse', function () { @@ -130,8 +277,11 @@ describe('triplelift adapter', function () { iurl: 'https://s.adroll.com/a/IYR/N36/IYRN366MFVDITBAGNNT5U6.jpg' } ], + refererInfo: { + referer: 'http://examplereferer.com' + }, gdprConsent: { - consentString: 'BOONm0NOONma-AAAARh7______b9_3__7_9uz_Kv_K7Vf7nnG072lPVOQ6gEaY', + consentString: 'BOONm0NOONm0NABABAENAa-AAAARh7______b9_3__7_9uz_Kv_K7Vf7nnG072lPVA9LTOQ6gEaY', gdprApplies: true } }; @@ -156,7 +306,7 @@ describe('triplelift adapter', function () { expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); }); - it('should return multile responses to support SRA', function () { + it('should return multiple responses to support SRA', function () { let response = { body: { bids: [ @@ -201,6 +351,9 @@ describe('triplelift adapter', function () { iurl: 'https://s.adroll.com/a/IYR/N36/IYRN366MFVDITBAGNNT5U6.jpg' } ], + refererInfo: { + referer: 'http://examplereferer.com' + }, gdprConsent: { consentString: 'BOONm0NOONm0NABABAENAa-AAAARh7______b9_3__7_9uz_Kv_K7Vf7nnG072lPVA9LTOQ6gEaY', gdprApplies: true diff --git a/test/spec/modules/trustxBidAdapter_spec.js b/test/spec/modules/trustxBidAdapter_spec.js index 9f2fdca6a99..4256012ba0b 100644 --- a/test/spec/modules/trustxBidAdapter_spec.js +++ b/test/spec/modules/trustxBidAdapter_spec.js @@ -47,6 +47,14 @@ describe('TrustXAdapter', function () { }); return res; } + + const bidderRequest = { + refererInfo: { + referer: 'http://example.com' + } + }; + const referrer = bidderRequest.refererInfo.referer; + let bidRequests = [ { 'bidder': 'trustx', @@ -65,7 +73,7 @@ describe('TrustXAdapter', function () { 'uid': '43' }, 'adUnitCode': 'adunit-code-2', - 'sizes': [[728, 90]], + 'sizes': [[728, 90], [300, 250]], 'bidId': '3150ccb55da321', 'bidderRequestId': '22edbae2733bf6', 'auctionId': '1d1a030790a475', @@ -84,51 +92,58 @@ describe('TrustXAdapter', function () { ]; it('should attach valid params to the tag', function () { - const request = spec.buildRequests([bidRequests[0]]); + const request = spec.buildRequests([bidRequests[0]], bidderRequest); expect(request.data).to.be.an('string'); const payload = parseRequest(request.data); - expect(payload).to.have.property('u').that.is.a('string'); + expect(payload).to.have.property('u', referrer); expect(payload).to.have.property('pt', 'net'); expect(payload).to.have.property('auids', '43'); + expect(payload).to.have.property('sizes', '300x250,300x600'); expect(payload).to.have.property('r', '22edbae2733bf6'); + expect(payload).to.have.property('wrapperType', 'Prebid_js'); + expect(payload).to.have.property('wrapperVersion', '$prebid.version$'); }); - it('auids must not be duplicated', function () { - const request = spec.buildRequests(bidRequests); + it('sizes must not be duplicated', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); expect(request.data).to.be.an('string'); const payload = parseRequest(request.data); - expect(payload).to.have.property('u').that.is.a('string'); + expect(payload).to.have.property('u', referrer); expect(payload).to.have.property('pt', 'net'); - expect(payload).to.have.property('auids', '43,45'); + expect(payload).to.have.property('auids', '43,43,45'); + expect(payload).to.have.property('sizes', '300x250,300x600,728x90'); expect(payload).to.have.property('r', '22edbae2733bf6'); }); it('pt parameter must be "gross" if params.priceType === "gross"', function () { bidRequests[1].params.priceType = 'gross'; - const request = spec.buildRequests(bidRequests); + const request = spec.buildRequests(bidRequests, bidderRequest); expect(request.data).to.be.an('string'); const payload = parseRequest(request.data); - expect(payload).to.have.property('u').that.is.a('string'); + expect(payload).to.have.property('u', referrer); expect(payload).to.have.property('pt', 'gross'); - expect(payload).to.have.property('auids', '43,45'); + expect(payload).to.have.property('auids', '43,43,45'); + expect(payload).to.have.property('sizes', '300x250,300x600,728x90'); expect(payload).to.have.property('r', '22edbae2733bf6'); delete bidRequests[1].params.priceType; }); it('pt parameter must be "net" or "gross"', function () { bidRequests[1].params.priceType = 'some'; - const request = spec.buildRequests(bidRequests); + const request = spec.buildRequests(bidRequests, bidderRequest); expect(request.data).to.be.an('string'); const payload = parseRequest(request.data); - expect(payload).to.have.property('u').that.is.a('string'); + expect(payload).to.have.property('u', referrer); expect(payload).to.have.property('pt', 'net'); - expect(payload).to.have.property('auids', '43,45'); + expect(payload).to.have.property('auids', '43,43,45'); + expect(payload).to.have.property('sizes', '300x250,300x600,728x90'); expect(payload).to.have.property('r', '22edbae2733bf6'); delete bidRequests[1].params.priceType; }); it('if gdprConsent is present payload must have gdpr params', function () { - const request = spec.buildRequests(bidRequests, {gdprConsent: {consentString: 'AAA', gdprApplies: true}}); + const bidderRequestWithGDPR = Object.assign({gdprConsent: {consentString: 'AAA', gdprApplies: true}}, bidderRequest); + const request = spec.buildRequests(bidRequests, bidderRequestWithGDPR); expect(request.data).to.be.an('string'); const payload = parseRequest(request.data); expect(payload).to.have.property('gdpr_consent', 'AAA'); @@ -136,7 +151,8 @@ describe('TrustXAdapter', function () { }); it('if gdprApplies is false gdpr_applies must be 0', function () { - const request = spec.buildRequests(bidRequests, {gdprConsent: {consentString: 'AAA', gdprApplies: false}}); + const bidderRequestWithGDPR = Object.assign({gdprConsent: {consentString: 'AAA', gdprApplies: false}}, bidderRequest); + const request = spec.buildRequests(bidRequests, bidderRequestWithGDPR); expect(request.data).to.be.an('string'); const payload = parseRequest(request.data); expect(payload).to.have.property('gdpr_consent', 'AAA'); @@ -144,20 +160,71 @@ describe('TrustXAdapter', function () { }); it('if gdprApplies is undefined gdpr_applies must be 1', function () { - const request = spec.buildRequests(bidRequests, {gdprConsent: {consentString: 'AAA'}}); + const bidderRequestWithGDPR = Object.assign({gdprConsent: {consentString: 'AAA'}}, bidderRequest); + const request = spec.buildRequests(bidRequests, bidderRequestWithGDPR); expect(request.data).to.be.an('string'); const payload = parseRequest(request.data); expect(payload).to.have.property('gdpr_consent', 'AAA'); expect(payload).to.have.property('gdpr_applies', '1'); }); + + it('should convert keyword params to proper form and attaches to request', function () { + const bidRequestWithKeywords = [].concat(bidRequests); + bidRequestWithKeywords[1] = Object.assign({}, + bidRequests[1], + { + params: { + uid: '43', + keywords: { + single: 'val', + singleArr: ['val'], + singleArrNum: [5], + multiValMixed: ['value1', 2, 'value3'], + singleValNum: 123, + emptyStr: '', + emptyArr: [''], + badValue: {'foo': 'bar'} // should be dropped + } + } + } + ); + + const request = spec.buildRequests(bidRequestWithKeywords, bidderRequest); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload.keywords).to.be.an('string'); + payload.keywords = JSON.parse(payload.keywords); + + expect(payload.keywords).to.deep.equal([{ + 'key': 'single', + 'value': ['val'] + }, { + 'key': 'singleArr', + 'value': ['val'] + }, { + 'key': 'singleArrNum', + 'value': ['5'] + }, { + 'key': 'multiValMixed', + 'value': ['value1', '2', 'value3'] + }, { + 'key': 'singleValNum', + 'value': ['123'] + }, { + 'key': 'emptyStr' + }, { + 'key': 'emptyArr' + }]); + }); }); describe('interpretResponse', function () { const responses = [ {'bid': [{'price': 1.15, 'adm': '
    test content 1
    ', 'auid': 43, 'h': 250, 'w': 300}], 'seat': '1'}, - {'bid': [{'price': 0.5, 'adm': '
    test content 2
    ', 'auid': 44, 'h': 90, 'w': 728}], 'seat': '1'}, + {'bid': [{'price': 0.5, 'adm': '
    test content 2
    ', 'auid': 44, 'h': 600, 'w': 300}], 'seat': '1'}, + {'bid': [{'price': 0.15, 'adm': '
    test content 3
    ', 'auid': 43, 'h': 90, 'w': 728}], 'seat': '1'}, {'bid': [{'price': 0, 'auid': 45, 'h': 250, 'w': 300}], 'seat': '1'}, - {'bid': [{'price': 0, 'adm': '
    test content 4
    ', 'h': 250, 'w': 300}], 'seat': '1'}, + {'bid': [{'price': 0, 'adm': '
    test content 5
    ', 'h': 250, 'w': 300}], 'seat': '1'}, undefined, {'bid': [], 'seat': '1'}, {'seat': '1'}, @@ -189,6 +256,7 @@ describe('TrustXAdapter', function () { 'ad': '
    test content 1
    ', 'bidderCode': 'trustx', 'currency': 'USD', + 'mediaType': 'banner', 'netRevenue': true, 'ttl': 360, } @@ -246,38 +314,41 @@ describe('TrustXAdapter', function () { 'ad': '
    test content 1
    ', 'bidderCode': 'trustx', 'currency': 'USD', + 'mediaType': 'banner', 'netRevenue': true, 'ttl': 360, }, { - 'requestId': '5703af74d0472a', - 'cpm': 1.15, - 'creativeId': 43, + 'requestId': '4dff80cc4ee346', + 'cpm': 0.5, + 'creativeId': 44, 'dealId': undefined, 'width': 300, - 'height': 250, - 'ad': '
    test content 1
    ', + 'height': 600, + 'ad': '
    test content 2
    ', 'bidderCode': 'trustx', 'currency': 'USD', + 'mediaType': 'banner', 'netRevenue': true, 'ttl': 360, }, { - 'requestId': '4dff80cc4ee346', - 'cpm': 0.5, - 'creativeId': 44, + 'requestId': '5703af74d0472a', + 'cpm': 0.15, + 'creativeId': 43, 'dealId': undefined, 'width': 728, 'height': 90, - 'ad': '
    test content 2
    ', + 'ad': '
    test content 3
    ', 'bidderCode': 'trustx', 'currency': 'USD', + 'mediaType': 'banner', 'netRevenue': true, 'ttl': 360, } ]; - const result = spec.interpretResponse({'body': {'seatbid': [responses[0], responses[1]]}}, request); + const result = spec.interpretResponse({'body': {'seatbid': responses.slice(0, 3)}}, request); expect(result).to.deep.equal(expectedResponse); }); @@ -318,8 +389,410 @@ describe('TrustXAdapter', function () { } ]; const request = spec.buildRequests(bidRequests); - const result = spec.interpretResponse({'body': {'seatbid': responses.slice(2)}}, request); + const result = spec.interpretResponse({'body': {'seatbid': responses.slice(3)}}, request); expect(result.length).to.equal(0); }); + + it('complicated case', function () { + const fullResponse = [ + {'bid': [{'price': 1.15, 'adm': '
    test content 1
    ', 'auid': 43, 'h': 250, 'w': 300}], 'seat': '1'}, + {'bid': [{'price': 0.5, 'adm': '
    test content 2
    ', 'auid': 44, 'h': 600, 'w': 300}], 'seat': '1'}, + {'bid': [{'price': 0.15, 'adm': '
    test content 3
    ', 'auid': 43, 'h': 90, 'w': 728}], 'seat': '1'}, + {'bid': [{'price': 0.15, 'adm': '
    test content 4
    ', 'auid': 43, 'h': 600, 'w': 300}], 'seat': '1'}, + {'bid': [{'price': 0.5, 'adm': '
    test content 5
    ', 'auid': 44, 'h': 600, 'w': 350}], 'seat': '1'}, + ]; + const bidRequests = [ + { + 'bidder': 'trustx', + 'params': { + 'uid': '43' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '2164be6358b9', + 'bidderRequestId': '106efe3247', + 'auctionId': '32a1f276cb87cb8', + }, + { + 'bidder': 'trustx', + 'params': { + 'uid': '43' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '326bde7fbf69', + 'bidderRequestId': '106efe3247', + 'auctionId': '32a1f276cb87cb8', + }, + { + 'bidder': 'trustx', + 'params': { + 'uid': '44' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '4e111f1b66e4', + 'bidderRequestId': '106efe3247', + 'auctionId': '32a1f276cb87cb8', + }, + { + 'bidder': 'trustx', + 'params': { + 'uid': '43' + }, + 'adUnitCode': 'adunit-code-2', + 'sizes': [[728, 90]], + 'bidId': '26d6f897b516', + 'bidderRequestId': '106efe3247', + 'auctionId': '32a1f276cb87cb8', + }, + { + 'bidder': 'trustx', + 'params': { + 'uid': '44' + }, + 'adUnitCode': 'adunit-code-2', + 'sizes': [[728, 90]], + 'bidId': '1751cd90161', + 'bidderRequestId': '106efe3247', + 'auctionId': '32a1f276cb87cb8', + } + ]; + const request = spec.buildRequests(bidRequests); + const expectedResponse = [ + { + 'requestId': '2164be6358b9', + 'cpm': 1.15, + 'creativeId': 43, + 'dealId': undefined, + 'width': 300, + 'height': 250, + 'ad': '
    test content 1
    ', + 'bidderCode': 'trustx', + 'currency': 'USD', + 'mediaType': 'banner', + 'netRevenue': true, + 'ttl': 360, + }, + { + 'requestId': '4e111f1b66e4', + 'cpm': 0.5, + 'creativeId': 44, + 'dealId': undefined, + 'width': 300, + 'height': 600, + 'ad': '
    test content 2
    ', + 'bidderCode': 'trustx', + 'currency': 'USD', + 'mediaType': 'banner', + 'netRevenue': true, + 'ttl': 360, + }, + { + 'requestId': '26d6f897b516', + 'cpm': 0.15, + 'creativeId': 43, + 'dealId': undefined, + 'width': 728, + 'height': 90, + 'ad': '
    test content 3
    ', + 'bidderCode': 'trustx', + 'currency': 'USD', + 'mediaType': 'banner', + 'netRevenue': true, + 'ttl': 360, + }, + { + 'requestId': '326bde7fbf69', + 'cpm': 0.15, + 'creativeId': 43, + 'dealId': undefined, + 'width': 300, + 'height': 600, + 'ad': '
    test content 4
    ', + 'bidderCode': 'trustx', + 'currency': 'USD', + 'mediaType': 'banner', + 'netRevenue': true, + 'ttl': 360, + } + ]; + + const result = spec.interpretResponse({'body': {'seatbid': fullResponse}}, request); + expect(result).to.deep.equal(expectedResponse); + }); + + it('dublicate uids and sizes in one slot', function () { + const fullResponse = [ + {'bid': [{'price': 1.15, 'adm': '
    test content 1
    ', 'auid': 43, 'h': 250, 'w': 300}], 'seat': '1'}, + {'bid': [{'price': 0.5, 'adm': '
    test content 2
    ', 'auid': 43, 'h': 250, 'w': 300}], 'seat': '1'}, + ]; + const bidRequests = [ + { + 'bidder': 'trustx', + 'params': { + 'uid': '43' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '5126e301f4be', + 'bidderRequestId': '171c5405a390', + 'auctionId': '35bcbc0f7e79c', + }, + { + 'bidder': 'trustx', + 'params': { + 'uid': '43' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '57b2ebe70e16', + 'bidderRequestId': '171c5405a390', + 'auctionId': '35bcbc0f7e79c', + }, + { + 'bidder': 'trustx', + 'params': { + 'uid': '43' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '225fcd44b18c', + 'bidderRequestId': '171c5405a390', + 'auctionId': '35bcbc0f7e79c', + } + ]; + const request = spec.buildRequests(bidRequests); + const expectedResponse = [ + { + 'requestId': '5126e301f4be', + 'cpm': 1.15, + 'creativeId': 43, + 'dealId': undefined, + 'width': 300, + 'height': 250, + 'ad': '
    test content 1
    ', + 'bidderCode': 'trustx', + 'currency': 'USD', + 'mediaType': 'banner', + 'netRevenue': true, + 'ttl': 360, + }, + { + 'requestId': '57b2ebe70e16', + 'cpm': 0.5, + 'creativeId': 43, + 'dealId': undefined, + 'width': 300, + 'height': 250, + 'ad': '
    test content 2
    ', + 'bidderCode': 'trustx', + 'currency': 'USD', + 'mediaType': 'banner', + 'netRevenue': true, + 'ttl': 360, + } + ]; + + const result = spec.interpretResponse({'body': {'seatbid': fullResponse}}, request); + expect(result).to.deep.equal(expectedResponse); + }); + }); + + it('should get correct video bid response', function () { + const bidRequests = [ + { + 'bidder': 'trustx', + 'params': { + 'uid': '50' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '57dfefb80eca', + 'bidderRequestId': '20394420a762a2', + 'auctionId': '140132d07b031', + 'mediaTypes': { + 'video': { + 'context': 'instream' + } + } + }, + { + 'bidder': 'trustx', + 'params': { + 'uid': '51' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': 'e893c787c22dd', + 'bidderRequestId': '20394420a762a2', + 'auctionId': '140132d07b031', + 'mediaTypes': { + 'video': { + 'context': 'instream' + } + } + } + ]; + const response = [ + {'bid': [{'price': 1.15, 'adm': '\n<\/Ad>\n<\/VAST>', 'auid': 50, content_type: 'video', w: 300, h: 600}], 'seat': '2'}, + {'bid': [{'price': 1.00, 'adm': '\n<\/Ad>\n<\/VAST>', 'auid': 51, content_type: 'video'}], 'seat': '2'} + ]; + const request = spec.buildRequests(bidRequests); + const expectedResponse = [ + { + 'requestId': '57dfefb80eca', + 'cpm': 1.15, + 'creativeId': 50, + 'dealId': undefined, + 'width': 300, + 'height': 600, + 'bidderCode': 'trustx', + 'currency': 'USD', + 'mediaType': 'video', + 'netRevenue': true, + 'ttl': 360, + 'vastXml': '\n<\/Ad>\n<\/VAST>', + 'adResponse': { + 'content': '\n<\/Ad>\n<\/VAST>' + } + } + ]; + + const result = spec.interpretResponse({'body': {'seatbid': response}}, request); + expect(result).to.deep.equal(expectedResponse); + }); + + it('should have right renderer in the bid response', function () { + const spySetRenderer = sinon.spy(); + const stubRenderer = { + setRender: spySetRenderer + }; + const spyRendererInstall = sinon.spy(function() { return stubRenderer; }); + const stubRendererConst = { + install: spyRendererInstall + }; + const bidRequests = [ + { + 'bidder': 'trustx', + 'params': { + 'uid': '50' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': 'e6e65553fc8', + 'bidderRequestId': '1380f393215dc7', + 'auctionId': '10b8d2f3c697e3', + 'mediaTypes': { + 'video': { + 'context': 'outstream' + } + } + }, + { + 'bidder': 'trustx', + 'params': { + 'uid': '51' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': 'c8fdcb3f269f', + 'bidderRequestId': '1380f393215dc7', + 'auctionId': '10b8d2f3c697e3' + }, + { + 'bidder': 'trustx', + 'params': { + 'uid': '52' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '1de036c37685', + 'bidderRequestId': '1380f393215dc7', + 'auctionId': '10b8d2f3c697e3', + 'renderer': {} + } + ]; + const response = [ + {'bid': [{'price': 1.15, 'adm': '\n<\/Ad>\n<\/VAST>', 'auid': 50, content_type: 'video', w: 300, h: 600}], 'seat': '2'}, + {'bid': [{'price': 1.00, 'adm': '\n<\/Ad>\n<\/VAST>', 'auid': 51, content_type: 'video', w: 300, h: 250}], 'seat': '2'}, + {'bid': [{'price': 1.20, 'adm': '\n<\/Ad>\n<\/VAST>', 'auid': 52, content_type: 'video', w: 300, h: 250}], 'seat': '2'} + ]; + const request = spec.buildRequests(bidRequests); + const expectedResponse = [ + { + 'requestId': 'e6e65553fc8', + 'cpm': 1.15, + 'creativeId': 50, + 'dealId': undefined, + 'width': 300, + 'height': 600, + 'bidderCode': 'trustx', + 'currency': 'USD', + 'mediaType': 'video', + 'netRevenue': true, + 'ttl': 360, + 'vastXml': '\n<\/Ad>\n<\/VAST>', + 'adResponse': { + 'content': '\n<\/Ad>\n<\/VAST>' + }, + 'renderer': stubRenderer + }, + { + 'requestId': 'c8fdcb3f269f', + 'cpm': 1.00, + 'creativeId': 51, + 'dealId': undefined, + 'width': 300, + 'height': 250, + 'bidderCode': 'trustx', + 'currency': 'USD', + 'mediaType': 'video', + 'netRevenue': true, + 'ttl': 360, + 'vastXml': '\n<\/Ad>\n<\/VAST>', + 'adResponse': { + 'content': '\n<\/Ad>\n<\/VAST>' + }, + 'renderer': stubRenderer + }, + { + 'requestId': '1de036c37685', + 'cpm': 1.20, + 'creativeId': 52, + 'dealId': undefined, + 'width': 300, + 'height': 250, + 'bidderCode': 'trustx', + 'currency': 'USD', + 'mediaType': 'video', + 'netRevenue': true, + 'ttl': 360, + 'vastXml': '\n<\/Ad>\n<\/VAST>', + 'adResponse': { + 'content': '\n<\/Ad>\n<\/VAST>' + } + } + ]; + + const result = spec.interpretResponse({'body': {'seatbid': response}}, request, stubRendererConst); + + expect(spySetRenderer.calledTwice).to.equal(true); + expect(spySetRenderer.getCall(0).args[0]).to.be.a('function'); + expect(spySetRenderer.getCall(1).args[0]).to.be.a('function'); + + expect(spyRendererInstall.calledTwice).to.equal(true); + expect(spyRendererInstall.getCall(0).args[0]).to.deep.equal({ + id: 'e6e65553fc8', + url: '//acdn.adnxs.com/video/outstream/ANOutstreamVideo.js', + loaded: false + }); + expect(spyRendererInstall.getCall(1).args[0]).to.deep.equal({ + id: 'c8fdcb3f269f', + url: '//acdn.adnxs.com/video/outstream/ANOutstreamVideo.js', + loaded: false + }); + + expect(result).to.deep.equal(expectedResponse); }); }); diff --git a/test/spec/modules/turktelekomBidAdapter_spec.js b/test/spec/modules/turktelekomBidAdapter_spec.js new file mode 100644 index 00000000000..066d2724a97 --- /dev/null +++ b/test/spec/modules/turktelekomBidAdapter_spec.js @@ -0,0 +1,749 @@ +import { expect } from 'chai'; +import { spec } from 'modules/turktelekomBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; + +describe('TurkTelekomAdapter', function () { + const adapter = newBidder(spec); + + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function () { + let bid = { + 'bidder': 'turktelekom', + 'params': { + 'uid': '17' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not passed', function () { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'uid': 0 + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + function parseRequest(url) { + const res = {}; + url.split('&').forEach((it) => { + const couple = it.split('='); + res[couple[0]] = decodeURIComponent(couple[1]); + }); + return res; + } + + const bidderRequest = { + refererInfo: { + referer: 'http://example.com' + } + }; + const referrer = bidderRequest.refererInfo.referer; + + let bidRequests = [ + { + 'bidder': 'turktelekom', + 'params': { + 'uid': '18' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }, + { + 'bidder': 'turktelekom', + 'params': { + 'uid': '18' + }, + 'adUnitCode': 'adunit-code-2', + 'sizes': [[728, 90], [300, 250]], + 'bidId': '3150ccb55da321', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }, + { + 'bidder': 'turktelekom', + 'params': { + 'uid': '20' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '42dbe3a7168a6a', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + } + ]; + + it('should attach valid params to the tag', function () { + const request = spec.buildRequests([bidRequests[0]], bidderRequest); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload).to.have.property('u', referrer); + expect(payload).to.have.property('pt', 'net'); + expect(payload).to.have.property('auids', '18'); + expect(payload).to.have.property('sizes', '300x250,300x600'); + expect(payload).to.have.property('r', '22edbae2733bf6'); + expect(payload).to.have.property('wrapperType', 'Prebid_js'); + expect(payload).to.have.property('wrapperVersion', '$prebid.version$'); + }); + + it('sizes must not be duplicated', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload).to.have.property('u', referrer); + expect(payload).to.have.property('pt', 'net'); + expect(payload).to.have.property('auids', '18,18,20'); + expect(payload).to.have.property('sizes', '300x250,300x600,728x90'); + expect(payload).to.have.property('r', '22edbae2733bf6'); + }); + + it('pt parameter must be "gross" if params.priceType === "gross"', function () { + bidRequests[1].params.priceType = 'gross'; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload).to.have.property('u', referrer); + expect(payload).to.have.property('pt', 'gross'); + expect(payload).to.have.property('auids', '18,18,20'); + expect(payload).to.have.property('sizes', '300x250,300x600,728x90'); + expect(payload).to.have.property('r', '22edbae2733bf6'); + delete bidRequests[1].params.priceType; + }); + + it('pt parameter must be "net" or "gross"', function () { + bidRequests[1].params.priceType = 'some'; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload).to.have.property('u', referrer); + expect(payload).to.have.property('pt', 'net'); + expect(payload).to.have.property('auids', '18,18,20'); + expect(payload).to.have.property('sizes', '300x250,300x600,728x90'); + expect(payload).to.have.property('r', '22edbae2733bf6'); + delete bidRequests[1].params.priceType; + }); + + it('if gdprConsent is present payload must have gdpr params', function () { + const bidderRequestWithGDPR = Object.assign({gdprConsent: {consentString: 'AAA', gdprApplies: true}}, bidderRequest); + const request = spec.buildRequests(bidRequests, bidderRequestWithGDPR); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload).to.have.property('gdpr_consent', 'AAA'); + expect(payload).to.have.property('gdpr_applies', '1'); + }); + + it('if gdprApplies is false gdpr_applies must be 0', function () { + const bidderRequestWithGDPR = Object.assign({gdprConsent: {consentString: 'AAA', gdprApplies: false}}, bidderRequest); + const request = spec.buildRequests(bidRequests, bidderRequestWithGDPR); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload).to.have.property('gdpr_consent', 'AAA'); + expect(payload).to.have.property('gdpr_applies', '0'); + }); + + it('if gdprApplies is undefined gdpr_applies must be 1', function () { + const bidderRequestWithGDPR = Object.assign({gdprConsent: {consentString: 'AAA'}}, bidderRequest); + const request = spec.buildRequests(bidRequests, bidderRequestWithGDPR); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload).to.have.property('gdpr_consent', 'AAA'); + expect(payload).to.have.property('gdpr_applies', '1'); + }); + }); + + describe('interpretResponse', function () { + const responses = [ + {'bid': [{'price': 1.15, 'adm': '
    test content 1
    ', 'auid': 17, 'h': 250, 'w': 300}], 'seat': '1'}, + {'bid': [{'price': 0.5, 'adm': '
    test content 2
    ', 'auid': 18, 'h': 600, 'w': 300}], 'seat': '1'}, + {'bid': [{'price': 0.15, 'adm': '
    test content 3
    ', 'auid': 17, 'h': 90, 'w': 728}], 'seat': '1'}, + {'bid': [{'price': 0, 'auid': 19, 'h': 250, 'w': 300}], 'seat': '1'}, + {'bid': [{'price': 0, 'adm': '
    test content 5
    ', 'h': 250, 'w': 300}], 'seat': '1'}, + undefined, + {'bid': [], 'seat': '1'}, + {'seat': '1'}, + ]; + + it('should get correct bid response', function () { + const bidRequests = [ + { + 'bidder': 'turktelekom', + 'params': { + 'uid': '17' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '659423fff799cb', + 'bidderRequestId': '5f2009617a7c0a', + 'auctionId': '1cbd2feafe5e8b', + } + ]; + const request = spec.buildRequests(bidRequests); + const expectedResponse = [ + { + 'requestId': '659423fff799cb', + 'cpm': 1.15, + 'creativeId': 17, + 'dealId': undefined, + 'width': 300, + 'height': 250, + 'ad': '
    test content 1
    ', + 'bidderCode': 'turktelekom', + 'currency': 'TRY', + 'mediaType': 'banner', + 'netRevenue': true, + 'ttl': 360, + } + ]; + + const result = spec.interpretResponse({'body': {'seatbid': [responses[0]]}}, request); + expect(result).to.deep.equal(expectedResponse); + }); + + it('should get correct multi bid response', function () { + const bidRequests = [ + { + 'bidder': 'turktelekom', + 'params': { + 'uid': '17' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '300bfeb0d71a5b', + 'bidderRequestId': '2c2bb1972df9a', + 'auctionId': '1fa09aee5c8c99', + }, + { + 'bidder': 'turktelekom', + 'params': { + 'uid': '18' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '4dff80cc4ee346', + 'bidderRequestId': '2c2bb1972df9a', + 'auctionId': '1fa09aee5c8c99', + }, + { + 'bidder': 'turktelekom', + 'params': { + 'uid': '17' + }, + 'adUnitCode': 'adunit-code-2', + 'sizes': [[728, 90]], + 'bidId': '5703af74d0472a', + 'bidderRequestId': '2c2bb1972df9a', + 'auctionId': '1fa09aee5c8c99', + } + ]; + const request = spec.buildRequests(bidRequests); + const expectedResponse = [ + { + 'requestId': '300bfeb0d71a5b', + 'cpm': 1.15, + 'creativeId': 17, + 'dealId': undefined, + 'width': 300, + 'height': 250, + 'ad': '
    test content 1
    ', + 'bidderCode': 'turktelekom', + 'currency': 'TRY', + 'mediaType': 'banner', + 'netRevenue': true, + 'ttl': 360, + }, + { + 'requestId': '4dff80cc4ee346', + 'cpm': 0.5, + 'creativeId': 18, + 'dealId': undefined, + 'width': 300, + 'height': 600, + 'ad': '
    test content 2
    ', + 'bidderCode': 'turktelekom', + 'currency': 'TRY', + 'mediaType': 'banner', + 'netRevenue': true, + 'ttl': 360, + }, + { + 'requestId': '5703af74d0472a', + 'cpm': 0.15, + 'creativeId': 17, + 'dealId': undefined, + 'width': 728, + 'height': 90, + 'ad': '
    test content 3
    ', + 'bidderCode': 'turktelekom', + 'currency': 'TRY', + 'mediaType': 'banner', + 'netRevenue': true, + 'ttl': 360, + } + ]; + + const result = spec.interpretResponse({'body': {'seatbid': responses.slice(0, 3)}}, request); + expect(result).to.deep.equal(expectedResponse); + }); + + it('handles wrong and nobid responses', function () { + const bidRequests = [ + { + 'bidder': 'turktelekom', + 'params': { + 'uid': '19' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '300bfeb0d7190gf', + 'bidderRequestId': '2c2bb1972d23af', + 'auctionId': '1fa09aee5c84d34', + }, + { + 'bidder': 'turktelekom', + 'params': { + 'uid': '20' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '300bfeb0d71321', + 'bidderRequestId': '2c2bb1972d23af', + 'auctionId': '1fa09aee5c84d34', + }, + { + 'bidder': 'turktelekom', + 'params': { + 'uid': '25' + }, + 'adUnitCode': 'adunit-code-2', + 'sizes': [[728, 90]], + 'bidId': '300bfeb0d7183bb', + 'bidderRequestId': '2c2bb1972d23af', + 'auctionId': '1fa09aee5c84d34', + } + ]; + const request = spec.buildRequests(bidRequests); + const result = spec.interpretResponse({'body': {'seatbid': responses.slice(3)}}, request); + expect(result.length).to.equal(0); + }); + + it('complicated case', function () { + const fullResponse = [ + {'bid': [{'price': 1.15, 'adm': '
    test content 1
    ', 'auid': 17, 'h': 250, 'w': 300}], 'seat': '1'}, + {'bid': [{'price': 0.5, 'adm': '
    test content 2
    ', 'auid': 18, 'h': 600, 'w': 300}], 'seat': '1'}, + {'bid': [{'price': 0.15, 'adm': '
    test content 3
    ', 'auid': 17, 'h': 90, 'w': 728}], 'seat': '1'}, + {'bid': [{'price': 0.15, 'adm': '
    test content 4
    ', 'auid': 17, 'h': 600, 'w': 300}], 'seat': '1'}, + {'bid': [{'price': 0.5, 'adm': '
    test content 5
    ', 'auid': 18, 'h': 600, 'w': 350}], 'seat': '1'}, + ]; + const bidRequests = [ + { + 'bidder': 'turktelekom', + 'params': { + 'uid': '17' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '2164be6358b9', + 'bidderRequestId': '106efe3247', + 'auctionId': '32a1f276cb87cb8', + }, + { + 'bidder': 'turktelekom', + 'params': { + 'uid': '17' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '326bde7fbf69', + 'bidderRequestId': '106efe3247', + 'auctionId': '32a1f276cb87cb8', + }, + { + 'bidder': 'turktelekom', + 'params': { + 'uid': '18' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '4e111f1b66e4', + 'bidderRequestId': '106efe3247', + 'auctionId': '32a1f276cb87cb8', + }, + { + 'bidder': 'turktelekom', + 'params': { + 'uid': '17' + }, + 'adUnitCode': 'adunit-code-2', + 'sizes': [[728, 90]], + 'bidId': '26d6f897b516', + 'bidderRequestId': '106efe3247', + 'auctionId': '32a1f276cb87cb8', + }, + { + 'bidder': 'turktelekom', + 'params': { + 'uid': '44' + }, + 'adUnitCode': 'adunit-code-2', + 'sizes': [[728, 90]], + 'bidId': '1751cd90161', + 'bidderRequestId': '106efe3247', + 'auctionId': '32a1f276cb87cb8', + } + ]; + const request = spec.buildRequests(bidRequests); + const expectedResponse = [ + { + 'requestId': '2164be6358b9', + 'cpm': 1.15, + 'creativeId': 17, + 'dealId': undefined, + 'width': 300, + 'height': 250, + 'ad': '
    test content 1
    ', + 'bidderCode': 'turktelekom', + 'currency': 'TRY', + 'mediaType': 'banner', + 'netRevenue': true, + 'ttl': 360, + }, + { + 'requestId': '4e111f1b66e4', + 'cpm': 0.5, + 'creativeId': 18, + 'dealId': undefined, + 'width': 300, + 'height': 600, + 'ad': '
    test content 2
    ', + 'bidderCode': 'turktelekom', + 'currency': 'TRY', + 'mediaType': 'banner', + 'netRevenue': true, + 'ttl': 360, + }, + { + 'requestId': '26d6f897b516', + 'cpm': 0.15, + 'creativeId': 17, + 'dealId': undefined, + 'width': 728, + 'height': 90, + 'ad': '
    test content 3
    ', + 'bidderCode': 'turktelekom', + 'currency': 'TRY', + 'mediaType': 'banner', + 'netRevenue': true, + 'ttl': 360, + }, + { + 'requestId': '326bde7fbf69', + 'cpm': 0.15, + 'creativeId': 17, + 'dealId': undefined, + 'width': 300, + 'height': 600, + 'ad': '
    test content 4
    ', + 'bidderCode': 'turktelekom', + 'currency': 'TRY', + 'mediaType': 'banner', + 'netRevenue': true, + 'ttl': 360, + } + ]; + + const result = spec.interpretResponse({'body': {'seatbid': fullResponse}}, request); + expect(result).to.deep.equal(expectedResponse); + }); + + it('dublicate uids and sizes in one slot', function () { + const fullResponse = [ + {'bid': [{'price': 1.15, 'adm': '
    test content 1
    ', 'auid': 17, 'h': 250, 'w': 300}], 'seat': '1'}, + {'bid': [{'price': 0.5, 'adm': '
    test content 2
    ', 'auid': 17, 'h': 250, 'w': 300}], 'seat': '1'}, + ]; + const bidRequests = [ + { + 'bidder': 'turktelekom', + 'params': { + 'uid': '17' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '5126e301f4be', + 'bidderRequestId': '171c5405a390', + 'auctionId': '35bcbc0f7e79c', + }, + { + 'bidder': 'turktelekom', + 'params': { + 'uid': '17' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '57b2ebe70e16', + 'bidderRequestId': '171c5405a390', + 'auctionId': '35bcbc0f7e79c', + }, + { + 'bidder': 'turktelekom', + 'params': { + 'uid': '17' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '225fcd44b18c', + 'bidderRequestId': '171c5405a390', + 'auctionId': '35bcbc0f7e79c', + } + ]; + const request = spec.buildRequests(bidRequests); + const expectedResponse = [ + { + 'requestId': '5126e301f4be', + 'cpm': 1.15, + 'creativeId': 17, + 'dealId': undefined, + 'width': 300, + 'height': 250, + 'ad': '
    test content 1
    ', + 'bidderCode': 'turktelekom', + 'currency': 'TRY', + 'mediaType': 'banner', + 'netRevenue': true, + 'ttl': 360, + }, + { + 'requestId': '57b2ebe70e16', + 'cpm': 0.5, + 'creativeId': 17, + 'dealId': undefined, + 'width': 300, + 'height': 250, + 'ad': '
    test content 2
    ', + 'bidderCode': 'turktelekom', + 'currency': 'TRY', + 'mediaType': 'banner', + 'netRevenue': true, + 'ttl': 360, + } + ]; + + const result = spec.interpretResponse({'body': {'seatbid': fullResponse}}, request); + expect(result).to.deep.equal(expectedResponse); + }); + }); + + it('should get correct video bid response', function () { + const bidRequests = [ + { + 'bidder': 'turktelekom', + 'params': { + 'uid': '25' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '57dfefb80eca', + 'bidderRequestId': '20394420a762a2', + 'auctionId': '140132d07b031', + 'mediaTypes': { + 'video': { + 'context': 'instream' + } + } + }, + { + 'bidder': 'turktelekom', + 'params': { + 'uid': '26' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': 'e893c787c22dd', + 'bidderRequestId': '20394420a762a2', + 'auctionId': '140132d07b031', + 'mediaTypes': { + 'video': { + 'context': 'instream' + } + } + } + ]; + const response = [ + {'bid': [{'price': 1.15, 'adm': '\n<\/Ad>\n<\/VAST>', 'auid': 25, content_type: 'video', w: 300, h: 600}], 'seat': '2'}, + {'bid': [{'price': 1.00, 'adm': '\n<\/Ad>\n<\/VAST>', 'auid': 26, content_type: 'video'}], 'seat': '2'} + ]; + const request = spec.buildRequests(bidRequests); + const expectedResponse = [ + { + 'requestId': '57dfefb80eca', + 'cpm': 1.15, + 'creativeId': 25, + 'dealId': undefined, + 'width': 300, + 'height': 600, + 'bidderCode': 'turktelekom', + 'currency': 'TRY', + 'mediaType': 'video', + 'netRevenue': true, + 'ttl': 360, + 'vastXml': '\n<\/Ad>\n<\/VAST>', + 'adResponse': { + 'content': '\n<\/Ad>\n<\/VAST>' + } + } + ]; + + const result = spec.interpretResponse({'body': {'seatbid': response}}, request); + expect(result).to.deep.equal(expectedResponse); + }); + + it('should have right renderer in the bid response', function () { + const spySetRenderer = sinon.spy(); + const stubRenderer = { + setRender: spySetRenderer + }; + const spyRendererInstall = sinon.spy(function() { return stubRenderer; }); + const stubRendererConst = { + install: spyRendererInstall + }; + const bidRequests = [ + { + 'bidder': 'turktelekom', + 'params': { + 'uid': '25' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': 'e6e65553fc8', + 'bidderRequestId': '1380f393215dc7', + 'auctionId': '10b8d2f3c697e3', + 'mediaTypes': { + 'video': { + 'context': 'outstream' + } + } + }, + { + 'bidder': 'turktelekom', + 'params': { + 'uid': '26' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': 'c8fdcb3f269f', + 'bidderRequestId': '1380f393215dc7', + 'auctionId': '10b8d2f3c697e3' + }, + { + 'bidder': 'turktelekom', + 'params': { + 'uid': '27' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '1de036c37685', + 'bidderRequestId': '1380f393215dc7', + 'auctionId': '10b8d2f3c697e3', + 'renderer': {} + } + ]; + const response = [ + {'bid': [{'price': 1.15, 'adm': '\n<\/Ad>\n<\/VAST>', 'auid': 25, content_type: 'video', w: 300, h: 600}], 'seat': '2'}, + {'bid': [{'price': 1.00, 'adm': '\n<\/Ad>\n<\/VAST>', 'auid': 26, content_type: 'video', w: 300, h: 250}], 'seat': '2'}, + {'bid': [{'price': 1.20, 'adm': '\n<\/Ad>\n<\/VAST>', 'auid': 27, content_type: 'video', w: 300, h: 250}], 'seat': '2'} + ]; + const request = spec.buildRequests(bidRequests); + const expectedResponse = [ + { + 'requestId': 'e6e65553fc8', + 'cpm': 1.15, + 'creativeId': 25, + 'dealId': undefined, + 'width': 300, + 'height': 600, + 'bidderCode': 'turktelekom', + 'currency': 'TRY', + 'mediaType': 'video', + 'netRevenue': true, + 'ttl': 360, + 'vastXml': '\n<\/Ad>\n<\/VAST>', + 'adResponse': { + 'content': '\n<\/Ad>\n<\/VAST>' + }, + 'renderer': stubRenderer + }, + { + 'requestId': 'c8fdcb3f269f', + 'cpm': 1.00, + 'creativeId': 26, + 'dealId': undefined, + 'width': 300, + 'height': 250, + 'bidderCode': 'turktelekom', + 'currency': 'TRY', + 'mediaType': 'video', + 'netRevenue': true, + 'ttl': 360, + 'vastXml': '\n<\/Ad>\n<\/VAST>', + 'adResponse': { + 'content': '\n<\/Ad>\n<\/VAST>' + }, + 'renderer': stubRenderer + }, + { + 'requestId': '1de036c37685', + 'cpm': 1.20, + 'creativeId': 27, + 'dealId': undefined, + 'width': 300, + 'height': 250, + 'bidderCode': 'turktelekom', + 'currency': 'TRY', + 'mediaType': 'video', + 'netRevenue': true, + 'ttl': 360, + 'vastXml': '\n<\/Ad>\n<\/VAST>', + 'adResponse': { + 'content': '\n<\/Ad>\n<\/VAST>' + } + } + ]; + + const result = spec.interpretResponse({'body': {'seatbid': response}}, request, stubRendererConst); + + expect(spySetRenderer.calledTwice).to.equal(true); + expect(spySetRenderer.getCall(0).args[0]).to.be.a('function'); + expect(spySetRenderer.getCall(1).args[0]).to.be.a('function'); + + expect(spyRendererInstall.calledTwice).to.equal(true); + expect(spyRendererInstall.getCall(0).args[0]).to.deep.equal({ + id: 'e6e65553fc8', + url: '//acdn.adnxs.com/video/outstream/ANOutstreamVideo.js', + loaded: false + }); + expect(spyRendererInstall.getCall(1).args[0]).to.deep.equal({ + id: 'c8fdcb3f269f', + url: '//acdn.adnxs.com/video/outstream/ANOutstreamVideo.js', + loaded: false + }); + + expect(result).to.deep.equal(expectedResponse); + }); +}); diff --git a/test/spec/modules/undertoneBidAdapter_spec.js b/test/spec/modules/undertoneBidAdapter_spec.js index 4b816d851d9..676f2714693 100644 --- a/test/spec/modules/undertoneBidAdapter_spec.js +++ b/test/spec/modules/undertoneBidAdapter_spec.js @@ -11,7 +11,7 @@ const validBidReq = { }, sizes: [[300, 250], [300, 600]], bidId: '263be71e91dd9d', - auctionId: '9ad1fa8d-2297-4660-a018-b39945054746', + auctionId: '9ad1fa8d-2297-4660-a018-b39945054747', }; const invalidBidReq = { @@ -44,6 +44,46 @@ const bidReq = [{ auctionId: '6c22f5a5-59df-4dc6-b92c-f433bcf0a874' }]; +const bidReqUserIds = [{ + bidder: BIDDER_CODE, + params: { + placementId: '10433394', + publisherId: 12345 + }, + sizes: [[300, 250], [300, 600]], + bidId: '263be71e91dd9d', + auctionId: '9ad1fa8d-2297-4660-a018-b39945054746', + userId: { + tdid: '123456', + digitrustid: {data: {id: 'DTID', keyv: 4, privacy: {optout: false}, producer: 'ABC', version: 2}} + } +}, +{ + bidder: BIDDER_CODE, + params: { + publisherId: 12345 + }, + sizes: [[1, 1]], + bidId: '453cf42d72bb3c', + auctionId: '6c22f5a5-59df-4dc6-b92c-f433bcf0a874' +}]; + +const bidderReq = { + refererInfo: { + referer: 'http://prebid.org/dev-docs/bidder-adaptor.html' + } +}; + +const bidderReqGdpr = { + refererInfo: { + referer: 'http://prebid.org/dev-docs/bidder-adaptor.html' + }, + gdprConsent: { + gdprApplies: true, + consentString: 'acdefgh' + } +}; + const validBidRes = { ad: '
    Hello
    ', publisherId: 12345, @@ -97,15 +137,27 @@ describe('Undertone Adapter', function () { }); }); describe('build request', function () { - it('should send request to correct url via POST', function () { - const request = spec.buildRequests(bidReq); - const domain = null; + it('should send request to correct url via POST not in GDPR', function () { + const request = spec.buildRequests(bidReq, bidderReq); + const domainStart = bidderReq.refererInfo.referer.indexOf('//'); + const domainEnd = bidderReq.refererInfo.referer.indexOf('/', domainStart + 2); + const domain = bidderReq.refererInfo.referer.substring(domainStart + 2, domainEnd); const REQ_URL = `${URL}?pid=${bidReq[0].params.publisherId}&domain=${domain}`; expect(request.url).to.equal(REQ_URL); expect(request.method).to.equal('POST'); }); + it('should send request to correct url via POST when in GDPR', function () { + const request = spec.buildRequests(bidReq, bidderReqGdpr); + const domainStart = bidderReq.refererInfo.referer.indexOf('//'); + const domainEnd = bidderReq.refererInfo.referer.indexOf('/', domainStart + 2); + const domain = bidderReq.refererInfo.referer.substring(domainStart + 2, domainEnd); + let gdpr = bidderReqGdpr.gdprConsent.gdprApplies ? 1 : 0 + const REQ_URL = `${URL}?pid=${bidReq[0].params.publisherId}&domain=${domain}&gdpr=${gdpr}&gdprstr=${bidderReqGdpr.gdprConsent.consentString}`; + expect(request.url).to.equal(REQ_URL); + expect(request.method).to.equal('POST'); + }); it('should have all relevant fields', function () { - const request = spec.buildRequests(bidReq); + const request = spec.buildRequests(bidReq, bidderReq); const bid1 = JSON.parse(request.data)['x-ut-hb-params'][0]; expect(bid1.bidRequestId).to.equal('263be71e91dd9d'); expect(bid1.sizes.length).to.equal(2); @@ -119,6 +171,14 @@ describe('Undertone Adapter', function () { expect(bid2.publisherId).to.equal(12345); expect(bid2.params).to.be.an('object'); }); + it('should send all userIds data to server', function () { + const request = spec.buildRequests(bidReqUserIds, bidderReq); + const bidCommons = JSON.parse(request.data)['commons']; + expect(bidCommons).to.be.an('object'); + expect(bidCommons.uids).to.be.an('object'); + expect(bidCommons.uids.tdid).to.equal('123456'); + expect(bidCommons.uids.digitrustid.data.id).to.equal('DTID'); + }); }); describe('interpretResponse', function () { @@ -150,4 +210,72 @@ describe('Undertone Adapter', function () { expect(spec.interpretResponse({ body: bidResArray }).length).to.equal(1); }); }); + + describe('getUserSyncs', () => { + let testParams = [ + { + name: 'with iframe and no gdpr data', + arguments: [{ iframeEnabled: true, pixelEnabled: true }, {}, null], + expect: { + type: 'iframe', + pixels: ['//cdn.undertone.com/js/usersync.html'] + } + }, + { + name: 'with iframe and gdpr on', + arguments: [{ iframeEnabled: true, pixelEnabled: true }, {}, {gdprApplies: true, consentString: '234234'}], + expect: { + type: 'iframe', + pixels: ['//cdn.undertone.com/js/usersync.html?gdpr=1&gdprstr=234234'] + } + }, + { + name: 'with iframe and no gdpr off', + arguments: [{ iframeEnabled: true, pixelEnabled: true }, {}, {gdprApplies: false}], + expect: { + type: 'iframe', + pixels: ['//cdn.undertone.com/js/usersync.html?gdpr=0&gdprstr='] + } + }, + { + name: 'with pixels and no gdpr data', + arguments: [{ pixelEnabled: true }, {}, null], + expect: { + type: 'image', + pixels: ['//usr.undertone.com/userPixel/syncOne?id=1&of=2', + '//usr.undertone.com/userPixel/syncOne?id=2&of=2'] + } + }, + { + name: 'with pixels and gdpr on', + arguments: [{ pixelEnabled: true }, {}, {gdprApplies: true, consentString: '234234'}], + expect: { + type: 'image', + pixels: ['//usr.undertone.com/userPixel/syncOne?id=1&of=2&gdpr=1&gdprstr=234234', + '//usr.undertone.com/userPixel/syncOne?id=2&of=2&gdpr=1&gdprstr=234234'] + } + }, + { + name: 'with pixels and gdpr off', + arguments: [{ pixelEnabled: true }, {}, {gdprApplies: false}], + expect: { + type: 'image', + pixels: ['//usr.undertone.com/userPixel/syncOne?id=1&of=2&gdpr=0&gdprstr=', + '//usr.undertone.com/userPixel/syncOne?id=2&of=2&gdpr=0&gdprstr='] + } + } + ]; + + for (let i = 0; i < testParams.length; i++) { + let currParams = testParams[i]; + it(currParams.name, function () { + const result = spec.getUserSyncs.apply(this, currParams.arguments); + expect(result).to.have.lengthOf(currParams.expect.pixels.length); + for (let ix = 0; ix < currParams.expect.pixels.length; ix++) { + expect(result[ix].url).to.equal(currParams.expect.pixels[ix]); + expect(result[ix].type).to.equal(currParams.expect.type); + } + }); + } + }); }); diff --git a/test/spec/modules/unrulyBidAdapter_spec.js b/test/spec/modules/unrulyBidAdapter_spec.js index 2c8fd9071d6..cb30fcf1a9d 100644 --- a/test/spec/modules/unrulyBidAdapter_spec.js +++ b/test/spec/modules/unrulyBidAdapter_spec.js @@ -18,7 +18,10 @@ describe('UnrulyAdapter', function () { 'statusCode': statusCode, 'renderer': { 'id': 'unruly_inarticle', - 'config': {}, + 'config': { + 'siteId': 123456, + 'targetingUUID': 'xxx-yyy-zzz' + }, 'url': 'https://video.unrulymedia.com/native/prebid-loader.js' }, 'adUnitCode': adUnitCode @@ -107,10 +110,10 @@ describe('UnrulyAdapter', function () { const mockBidRequests = ['mockBid']; expect(adapter.buildRequests(mockBidRequests).method).to.equal('POST'); }); - it('should ensure contentType is `application/json`', function () { + it('should ensure contentType is `text/plain`', function () { const mockBidRequests = ['mockBid']; expect(adapter.buildRequests(mockBidRequests).options).to.deep.equal({ - contentType: 'application/json' + contentType: 'text/plain' }); }); it('should return a server request with valid payload', function () { @@ -125,10 +128,10 @@ describe('UnrulyAdapter', function () { it('should be a function', function () { expect(typeof adapter.interpretResponse).to.equal('function'); }); - it('should return empty array when serverResponse is undefined', function () { + it('should return [] when serverResponse is undefined', function () { expect(adapter.interpretResponse()).to.deep.equal([]); }); - it('should return empty array when serverResponse has no bids', function () { + it('should return [] when serverResponse has no bids', function () { const mockServerResponse = { body: { bids: [] } }; expect(adapter.interpretResponse(mockServerResponse)).to.deep.equal([]) }); @@ -146,23 +149,30 @@ describe('UnrulyAdapter', function () { creativeId: 'mockBidId', ttl: 360, currency: 'USD', - renderer: fakeRenderer + renderer: fakeRenderer, + mediaType: 'video' } ]) }); it('should initialize and set the renderer', function () { - expect(Renderer.install).not.to.have.been.called; - expect(fakeRenderer.setRender).not.to.have.been.called; + expect(Renderer.install.called).to.be.false; + expect(fakeRenderer.setRender.called).to.be.false; const mockReturnedBid = createOutStreamExchangeBid({adUnitCode: 'video1', bidId: 'mockBidId'}); - const mockRenderer = { url: 'value: mockRendererURL' }; + const mockRenderer = { + url: 'value: mockRendererURL', + config: { + siteId: 123456, + targetingUUID: 'xxx-yyy-zzz' + } + }; mockReturnedBid.ext.renderer = mockRenderer; const mockServerResponse = createExchangeResponse(mockReturnedBid); adapter.interpretResponse(mockServerResponse); - expect(Renderer.install).to.have.been.calledOnce; + expect(Renderer.install.calledOnce).to.be.true; sinon.assert.calledWithExactly( Renderer.install, Object.assign({}, mockRenderer, {callback: sinon.match.func}) @@ -172,6 +182,58 @@ describe('UnrulyAdapter', function () { sinon.assert.calledWithExactly(fakeRenderer.setRender, sinon.match.func) }); + it('should return [] and log if bidResponse renderer config is not available', function () { + sinon.assert.notCalled(utils.logError) + + expect(Renderer.install.called).to.be.false; + expect(fakeRenderer.setRender.called).to.be.false; + + const mockReturnedBid = createOutStreamExchangeBid({adUnitCode: 'video1', bidId: 'mockBidId'}); + const mockRenderer = { + url: 'value: mockRendererURL' + }; + mockReturnedBid.ext.renderer = mockRenderer; + const mockServerResponse = createExchangeResponse(mockReturnedBid); + + expect(adapter.interpretResponse(mockServerResponse)).to.deep.equal([]); + + const logErrorCalls = utils.logError.getCalls(); + expect(logErrorCalls.length).to.equal(2); + + const [ configErrorCall, siteIdErrorCall ] = logErrorCalls; + + expect(configErrorCall.args.length).to.equal(1); + expect(configErrorCall.args[0].message).to.equal('UnrulyBidAdapter: Missing renderer config.'); + + expect(siteIdErrorCall.args.length).to.equal(1); + expect(siteIdErrorCall.args[0].message).to.equal('UnrulyBidAdapter: Missing renderer siteId.'); + }); + + it('should return [] and log if siteId is not available', function () { + sinon.assert.notCalled(utils.logError) + + expect(Renderer.install.called).to.be.false; + expect(fakeRenderer.setRender.called).to.be.false; + + const mockReturnedBid = createOutStreamExchangeBid({adUnitCode: 'video1', bidId: 'mockBidId'}); + const mockRenderer = { + url: 'value: mockRendererURL', + config: {} + }; + mockReturnedBid.ext.renderer = mockRenderer; + const mockServerResponse = createExchangeResponse(mockReturnedBid); + + expect(adapter.interpretResponse(mockServerResponse)).to.deep.equal([]); + + const logErrorCalls = utils.logError.getCalls(); + expect(logErrorCalls.length).to.equal(1); + + const [ siteIdErrorCall ] = logErrorCalls; + + expect(siteIdErrorCall.args.length).to.equal(1); + expect(siteIdErrorCall.args[0].message).to.equal('UnrulyBidAdapter: Missing renderer siteId.'); + }); + it('bid is placed on the bid queue when render is called', function () { const exchangeBid = createOutStreamExchangeBid({ adUnitCode: 'video', vastUrl: 'value: vastUrl' }); const exchangeResponse = createExchangeResponse(exchangeBid); @@ -181,7 +243,7 @@ describe('UnrulyAdapter', function () { sinon.assert.calledOnce(fakeRenderer.setRender); fakeRenderer.setRender.firstCall.args[0](); - expect(window.top).to.have.deep.property('unruly.native.prebid.uq'); + expect(window.top).to.have.deep.nested.property('unruly.native.prebid.uq'); const uq = window.top.unruly.native.prebid.uq; const sentRendererConfig = uq[0][1]; @@ -190,7 +252,7 @@ describe('UnrulyAdapter', function () { expect(sentRendererConfig.vastUrl).to.equal('value: vastUrl'); expect(sentRendererConfig.renderer).to.equal(fakeRenderer); expect(sentRendererConfig.adUnitCode).to.equal('video') - }) + }); it('should ensure that renderer is placed in Prebid supply mode', function () { const mockExchangeBid = createOutStreamExchangeBid({adUnitCode: 'video1', bidId: 'mockBidId'}); @@ -205,4 +267,64 @@ describe('UnrulyAdapter', function () { expect(supplyMode).to.equal('prebid'); }); }); + + describe('getUserSyncs', () => { + it('should push user sync iframe if enabled', () => { + const mockConsent = {} + const response = {} + const syncOptions = { iframeEnabled: true } + const syncs = adapter.getUserSyncs(syncOptions, response, mockConsent) + expect(syncs[0]).to.deep.equal({ + type: 'iframe', + url: 'https://video.unrulymedia.com/iframes/third-party-iframes.html' + }); + }) + + it('should not push user sync iframe if not enabled', () => { + const mockConsent = {} + const response = {} + const syncOptions = { iframeEnabled: false } + const syncs = adapter.getUserSyncs(syncOptions, response, mockConsent); + expect(syncs).to.be.empty; + }); + }); + + it('should not append consent params if gdpr does not apply', () => { + const mockConsent = {} + const response = {} + const syncOptions = { iframeEnabled: true } + const syncs = adapter.getUserSyncs(syncOptions, response, mockConsent) + expect(syncs[0]).to.deep.equal({ + type: 'iframe', + url: 'https://video.unrulymedia.com/iframes/third-party-iframes.html' + }) + }); + + it('should append consent params if gdpr does apply and consent is given', () => { + const mockConsent = { + gdprApplies: true, + consentString: 'hello' + }; + const response = {} + const syncOptions = { iframeEnabled: true } + const syncs = adapter.getUserSyncs(syncOptions, response, mockConsent) + expect(syncs[0]).to.deep.equal({ + type: 'iframe', + url: 'https://video.unrulymedia.com/iframes/third-party-iframes.html?gdpr=1&gdpr_consent=hello' + }) + }); + + it('should append consent param if gdpr applies and no consent is given', () => { + const mockConsent = { + gdprApplies: true, + consentString: {} + }; + const response = {}; + const syncOptions = { iframeEnabled: true } + const syncs = adapter.getUserSyncs(syncOptions, response, mockConsent) + expect(syncs[0]).to.deep.equal({ + type: 'iframe', + url: 'https://video.unrulymedia.com/iframes/third-party-iframes.html?gdpr=0' + }) + }) }); diff --git a/test/spec/modules/uolBidAdapter_spec.js b/test/spec/modules/uolBidAdapter_spec.js index 1733afc91f9..e9341772e7d 100644 --- a/test/spec/modules/uolBidAdapter_spec.js +++ b/test/spec/modules/uolBidAdapter_spec.js @@ -1,11 +1,20 @@ import { expect } from 'chai'; import { spec } from 'modules/uolBidAdapter'; -import { newBidder } from 'src/adapters/bidderFactory'; const ENDPOINT = 'https://prebid.adilligo.com/v1/prebid.json'; describe('UOL Bid Adapter', function () { - const adapter = newBidder(spec); + let sandbox; + let queryStub; + let getCurrentPositionStub; + + beforeEach(function() { + sandbox = sinon.sandbox.create(); + }); + + afterEach(function() { + sandbox.restore(); + }); describe('isBidRequestValid', function () { let bid = { @@ -88,31 +97,6 @@ describe('UOL Bid Adapter', function () { }); describe('buildRequests', function () { - let queryPermission; - let cleanup = function() { - navigator.permissions.query = queryPermission; - }; - let grantTriangulation = function() { - queryPermission = navigator.permissions.query; - navigator.permissions.query = function(data) { - return new Promise((resolve, reject) => { - resolve({state: 'granted'}); - }); - } - }; - let denyTriangulation = function() { - queryPermission = navigator.permissions.query; - navigator.permissions.query = function(data) { - return new Promise((resolve, reject) => { - resolve({state: 'prompt'}); - }); - } - }; - let removeQuerySupport = function() { - queryPermission = navigator.permissions.query; - navigator.permissions.query = undefined; - } - let bidRequests = [ { 'bidder': 'uol', @@ -173,31 +157,42 @@ describe('UOL Bid Adapter', function () { describe('buildRequest geolocation param', function () { // shall only be tested if browser engine supports geolocation and permissions API. let geolocation = { lat: 4, long: 3, timestamp: 123121451 }; - it('should contain user coordinates if (i) DNT is off; (ii) browser supports implementation; (iii) localStorage contains geolocation history', function () { + beforeEach(function() { + getCurrentPositionStub = sandbox.stub(navigator.geolocation, 'getCurrentPosition'); + queryStub = sandbox.stub(navigator.permissions, 'query'); + }); + + it('should not contain user coordinates if browser doesnt support permission query', function () { localStorage.setItem('uolLocationTracker', JSON.stringify(geolocation)); - grantTriangulation(); + navigator.permissions.query = undefined; const requestObject = spec.buildRequests(bidRequests, bidderRequest); const payload = JSON.parse(requestObject.data); - expect(payload.geolocation).to.exist.and.not.be.empty; - cleanup(); + expect(payload.geolocation).to.not.exist; }) - it('should not contain user coordinates if localStorage is empty', function () { - localStorage.removeItem('uolLocationTracker'); - denyTriangulation(); + it('should contain user coordinates if (i) DNT is off; (ii) browser supports implementation; (iii) localStorage contains geolocation history', function (done) { + localStorage.setItem('uolLocationTracker', JSON.stringify(geolocation)); + queryStub.callsFake(function() { + return new Promise((resolve, reject) => { + resolve({state: 'granted'}); + }); + }); + getCurrentPositionStub.callsFake(() => done()); const requestObject = spec.buildRequests(bidRequests, bidderRequest); const payload = JSON.parse(requestObject.data); - expect(payload.geolocation).to.not.exist; - cleanup(); + expect(payload.geolocation).to.exist.and.not.be.empty; }) - it('should not contain user coordinates if browser doesnt support permission query', function () { - localStorage.setItem('uolLocationTracker', JSON.stringify(geolocation)); - removeQuerySupport(); + it('should not contain user coordinates if localStorage is empty', function () { + localStorage.removeItem('uolLocationTracker'); + queryStub.callsFake(function() { + return new Promise((resolve, reject) => { + resolve({state: 'prompt'}); + }); + }); const requestObject = spec.buildRequests(bidRequests, bidderRequest); const payload = JSON.parse(requestObject.data); expect(payload.geolocation).to.not.exist; - cleanup(); }) }) } diff --git a/test/spec/modules/userId_spec.js b/test/spec/modules/userId_spec.js new file mode 100644 index 00000000000..cd72ca9670f --- /dev/null +++ b/test/spec/modules/userId_spec.js @@ -0,0 +1,1046 @@ +import { + attachIdSystem, + auctionDelay, + init, + requestBidsHook, + setSubmoduleRegistry, + syncDelay +} from 'modules/userId/index.js'; +import {config} from 'src/config'; +import * as utils from 'src/utils'; +import events from 'src/events'; +import CONSTANTS from 'src/constants.json'; +import {unifiedIdSubmodule} from 'modules/userId/unifiedIdSystem'; +import {pubCommonIdSubmodule} from 'modules/userId/pubCommonIdSystem'; +import {id5IdSubmodule} from 'modules/id5IdSystem'; +import {identityLinkSubmodule} from 'modules/identityLinkIdSystem'; +import {liveIntentIdSubmodule} from 'modules/liveIntentIdSystem'; + +let assert = require('chai').assert; +let expect = require('chai').expect; +const EXPIRED_COOKIE_DATE = 'Thu, 01 Jan 1970 00:00:01 GMT'; + +describe('User ID', function() { + function getConfigMock(configArr1, configArr2, configArr3, configArr4) { + return { + userSync: { + syncDelay: 0, + userIds: [ + (configArr1 && configArr1.length >= 3) ? getStorageMock.apply(null, configArr1) : null, + (configArr2 && configArr2.length >= 3) ? getStorageMock.apply(null, configArr2) : null, + (configArr3 && configArr3.length >= 3) ? getStorageMock.apply(null, configArr3) : null, + (configArr4 && configArr4.length >= 3) ? getStorageMock.apply(null, configArr4) : null + ].filter(i => i)} + } + } + function getStorageMock(name = 'pubCommonId', key = 'pubcid', type = 'cookie', expires = 30, refreshInSeconds) { + return { name: name, storage: { name: key, type: type, expires: expires, refreshInSeconds: refreshInSeconds } } + } + function getConfigValueMock(name, value) { + return { + userSync: { syncDelay: 0, userIds: [{ name: name, value: value }] } + } + } + + function getAdUnitMock(code = 'adUnit-code') { + return { + code, + mediaTypes: {banner: {}, native: {}}, + sizes: [[300, 200], [300, 600]], + bids: [{bidder: 'sampleBidder', params: {placementId: 'banner-only-bidder'}}] + }; + } + + function addConfig(cfg, name, value) { + if (cfg && cfg.userSync && cfg.userSync.userIds) { + cfg.userSync.userIds.forEach(element => { + if (element[name] !== undefined) { element[name] = Object.assign(element[name], value); } else { element[name] = value; } + }); + } + + return cfg; + } + + before(function() { + utils.setCookie('_pubcid_optout', '', EXPIRED_COOKIE_DATE); + localStorage.removeItem('_pbjs_id_optout'); + localStorage.removeItem('_pubcid_optout'); + }); + + describe('Decorate Ad Units', function() { + beforeEach(function() { + utils.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); + utils.setCookie('pubcid_alt', 'altpubcid200000', (new Date(Date.now() + 5000).toUTCString())); + sinon.spy(utils, 'setCookie'); + }); + + afterEach(function () { + $$PREBID_GLOBAL$$.requestBids.removeAll(); + config.resetConfig(); + utils.setCookie.restore(); + }); + + after(function() { + utils.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); + utils.setCookie('pubcid_alt', '', EXPIRED_COOKIE_DATE); + }); + + it('Check same cookie behavior', function () { + let adUnits1 = [getAdUnitMock()]; + let adUnits2 = [getAdUnitMock()]; + let innerAdUnits1; + let innerAdUnits2; + + let pubcid = utils.getCookie('pubcid'); + expect(pubcid).to.be.null; // there should be no cookie initially + + setSubmoduleRegistry([pubCommonIdSubmodule]); + init(config); + config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'cookie'])); + + requestBidsHook(config => { innerAdUnits1 = config.adUnits }, {adUnits: adUnits1}); + pubcid = utils.getCookie('pubcid'); // cookies is created after requestbidHook + + innerAdUnits1.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.pubcid'); + expect(bid.userId.pubcid).to.equal(pubcid); + }); + }); + + requestBidsHook(config => { innerAdUnits2 = config.adUnits }, {adUnits: adUnits2}); + assert.deepEqual(innerAdUnits1, innerAdUnits2); + }); + + it('Check different cookies', function () { + let adUnits1 = [getAdUnitMock()]; + let adUnits2 = [getAdUnitMock()]; + let innerAdUnits1; + let innerAdUnits2; + let pubcid1; + let pubcid2; + + setSubmoduleRegistry([pubCommonIdSubmodule]); + init(config); + config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'cookie'])); + requestBidsHook((config) => { innerAdUnits1 = config.adUnits }, {adUnits: adUnits1}); + pubcid1 = utils.getCookie('pubcid'); // get first cookie + utils.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); // erase cookie + + innerAdUnits1.forEach((unit) => { + unit.bids.forEach((bid) => { + expect(bid).to.have.deep.nested.property('userId.pubcid'); + expect(bid.userId.pubcid).to.equal(pubcid1); + }); + }); + + setSubmoduleRegistry([pubCommonIdSubmodule]); + init(config); + config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'cookie'])); + requestBidsHook((config) => { innerAdUnits2 = config.adUnits }, {adUnits: adUnits2}); + + pubcid2 = utils.getCookie('pubcid'); // get second cookie + + innerAdUnits2.forEach((unit) => { + unit.bids.forEach((bid) => { + expect(bid).to.have.deep.nested.property('userId.pubcid'); + expect(bid.userId.pubcid).to.equal(pubcid2); + }); + }); + + expect(pubcid1).to.not.equal(pubcid2); + }); + + it('Use existing cookie', function () { + let adUnits = [getAdUnitMock()]; + let innerAdUnits; + + setSubmoduleRegistry([pubCommonIdSubmodule]); + init(config); + config.setConfig(getConfigMock(['pubCommonId', 'pubcid_alt', 'cookie'])); + requestBidsHook((config) => { innerAdUnits = config.adUnits }, {adUnits}); + innerAdUnits.forEach((unit) => { + unit.bids.forEach((bid) => { + expect(bid).to.have.deep.nested.property('userId.pubcid'); + expect(bid.userId.pubcid).to.equal('altpubcid200000'); + }); + }); + // Because the cookie exists already, there should be no setCookie call by default + expect(utils.setCookie.callCount).to.equal(0); + }); + + it('Extend cookie', function () { + let adUnits = [getAdUnitMock()]; + let innerAdUnits; + let customConfig = getConfigMock(['pubCommonId', 'pubcid_alt', 'cookie']); + customConfig = addConfig(customConfig, 'params', {extend: true}); + + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule]); + init(config); + config.setConfig(customConfig); + requestBidsHook((config) => { innerAdUnits = config.adUnits }, {adUnits}); + innerAdUnits.forEach((unit) => { + unit.bids.forEach((bid) => { + expect(bid).to.have.deep.nested.property('userId.pubcid'); + expect(bid.userId.pubcid).to.equal('altpubcid200000'); + }); + }); + // Because extend is true, the cookie will be updated even if it exists already + expect(utils.setCookie.callCount).to.equal(1); + }); + + it('Disable auto create', function () { + let adUnits = [getAdUnitMock()]; + let innerAdUnits; + let customConfig = getConfigMock(['pubCommonId', 'pubcid', 'cookie']); + customConfig = addConfig(customConfig, 'params', {create: false}); + + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule]); + init(config); + config.setConfig(customConfig); + requestBidsHook((config) => { innerAdUnits = config.adUnits }, {adUnits}); + innerAdUnits.forEach((unit) => { + unit.bids.forEach((bid) => { + expect(bid).to.not.have.deep.nested.property('userId.pubcid'); + }); + }); + expect(utils.setCookie.callCount).to.equal(0); + }); + }); + + describe('Opt out', function () { + before(function () { + utils.setCookie('_pbjs_id_optout', '1', (new Date(Date.now() + 5000).toUTCString())); + }); + + beforeEach(function () { + sinon.stub(utils, 'logInfo'); + }); + + afterEach(function () { + // removed cookie + utils.setCookie('_pbjs_id_optout', '', EXPIRED_COOKIE_DATE); + $$PREBID_GLOBAL$$.requestBids.removeAll(); + utils.logInfo.restore(); + config.resetConfig(); + }); + + after(function () { + utils.setCookie('_pbjs_id_optout', '', EXPIRED_COOKIE_DATE); + }); + + it('fails initialization if opt out cookie exists', function () { + setSubmoduleRegistry([pubCommonIdSubmodule]); + init(config); + config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'cookie'])); + expect(utils.logInfo.args[0][0]).to.exist.and.to.equal('User ID - opt-out cookie found, exit module'); + }); + + it('initializes if no opt out cookie exists', function () { + setSubmoduleRegistry([pubCommonIdSubmodule]); + init(config); + config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'cookie'])); + expect(utils.logInfo.args[0][0]).to.exist.and.to.equal('User ID - usersync config updated for 1 submodules'); + }); + }); + + describe('Handle variations of config values', function () { + beforeEach(function () { + sinon.stub(utils, 'logInfo'); + }); + + afterEach(function () { + $$PREBID_GLOBAL$$.requestBids.removeAll(); + utils.logInfo.restore(); + config.resetConfig(); + }); + + it('handles config with no usersync object', function () { + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule]); + init(config); + config.setConfig({}); + // usersync is undefined, and no logInfo message for 'User ID - usersync config updated' + expect(typeof utils.logInfo.args[0]).to.equal('undefined'); + }); + + it('handles config with empty usersync object', function () { + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule]); + 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]); + init(config); + config.setConfig({ + usersync: { + userIds: [{}] + } + }); + expect(typeof utils.logInfo.args[0]).to.equal('undefined'); + }); + + it('handles config with usersync and userIds with empty names or that dont match a submodule.name', function () { + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule]); + init(config); + config.setConfig({ + usersync: { + userIds: [{ + name: '', + value: { test: '1' } + }, { + name: 'foo', + value: { test: '1' } + }] + } + }); + expect(typeof utils.logInfo.args[0]).to.equal('undefined'); + }); + + it('config with 1 configuration should create 1 submodule', function () { + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule]); + init(config); + config.setConfig(getConfigMock(['unifiedId', 'unifiedid', 'cookie'])); + + expect(utils.logInfo.args[0][0]).to.exist.and.to.equal('User ID - usersync config updated for 1 submodules'); + }); + + it('config with 5 configurations should result in 5 submodules add', function () { + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, liveIntentIdSubmodule]); + init(config); + config.setConfig({ + usersync: { + syncDelay: 0, + userIds: [{ + name: 'pubCommonId', value: {'pubcid': '11111'} + }, { + name: 'unifiedId', + storage: { name: 'unifiedid', type: 'cookie' } + }, { + name: 'id5Id', + storage: { name: 'id5id', type: 'cookie' } + }, { + name: 'identityLink', + storage: { name: 'idl_env', type: 'cookie' } + }, { + name: 'liveIntentId', + storage: { name: '_li_pbid', type: 'cookie' } + }] + } + }); + expect(utils.logInfo.args[0][0]).to.exist.and.to.equal('User ID - usersync config updated for 5 submodules'); + }); + + it('config syncDelay updates module correctly', function () { + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule]); + init(config); + config.setConfig({ + usersync: { + syncDelay: 99, + userIds: [{ + name: 'unifiedId', + storage: { name: 'unifiedid', type: 'cookie' } + }] + } + }); + expect(syncDelay).to.equal(99); + }); + + it('config auctionDelay updates module correctly', function () { + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule]); + init(config); + config.setConfig({ + usersync: { + auctionDelay: 100, + userIds: [{ + name: 'unifiedId', + storage: { name: 'unifiedid', type: 'cookie' } + }] + } + }); + expect(auctionDelay).to.equal(100); + }); + + it('config auctionDelay defaults to 0 if not a number', function () { + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule]); + init(config); + config.setConfig({ + usersync: { + auctionDelay: '', + userIds: [{ + name: 'unifiedId', + storage: { name: 'unifiedid', type: 'cookie' } + }] + } + }); + expect(auctionDelay).to.equal(0); + }); + }); + + describe('auction and user sync delays', function() { + let sandbox; + let adUnits; + let mockIdCallback; + let auctionSpy; + + before(function() { + sandbox = sinon.createSandbox(); + sandbox.stub(global, 'setTimeout'); + sandbox.stub(events, 'on'); + }); + + beforeEach(function() { + // remove cookie + utils.setCookie('MOCKID', '', EXPIRED_COOKIE_DATE); + + adUnits = [getAdUnitMock()]; + + auctionSpy = sandbox.spy(); + mockIdCallback = sandbox.stub(); + const mockIdSystem = { + name: 'mockId', + decode: function(value) { + return { + 'mid': value['MOCKID'] + }; + }, + getId: function() { + const storedId = utils.getCookie('MOCKID'); + if (storedId) { + return {id: {'MOCKID': storedId}}; + } + return {callback: mockIdCallback}; + } + }; + + init(config); + + attachIdSystem(mockIdSystem, true); + }); + + afterEach(function () { + $$PREBID_GLOBAL$$.requestBids.removeAll(); + config.resetConfig(); + sandbox.resetHistory(); + }); + + after(function() { + sandbox.restore(); + }); + + it('delays auction if auctionDelay is set, timing out at auction delay', function() { + config.setConfig({ + usersync: { + auctionDelay: 33, + syncDelay: 77, + userIds: [{ + name: 'mockId', storage: { name: 'MOCKID', type: 'cookie' } + }] + } + }); + + requestBidsHook(auctionSpy, {adUnits}); + + // check auction was delayed + global.setTimeout.calledOnce.should.equal(true); + global.setTimeout.calledWith(sinon.match.func, 33); + auctionSpy.calledOnce.should.equal(false); + + // check ids were fetched + mockIdCallback.calledOnce.should.equal(true); + + // callback to continue auction if timed out + global.setTimeout.callArg(0); + auctionSpy.calledOnce.should.equal(true); + + // does not call auction again once ids are synced + mockIdCallback.callArgWith(0, {'MOCKID': '1234'}); + auctionSpy.calledOnce.should.equal(true); + + // no sync after auction ends + events.on.called.should.equal(false); + }); + + it('delays auction if auctionDelay is set, continuing auction if ids are fetched before timing out', function(done) { + config.setConfig({ + usersync: { + auctionDelay: 33, + syncDelay: 77, + userIds: [{ + name: 'mockId', storage: { name: 'MOCKID', type: 'cookie' } + }] + } + }); + + requestBidsHook(auctionSpy, {adUnits}); + + // check auction was delayed + global.setTimeout.calledOnce.should.equal(true); + global.setTimeout.calledWith(sinon.match.func, 33); + auctionSpy.calledOnce.should.equal(false); + + // check ids were fetched + mockIdCallback.calledOnce.should.equal(true); + + // if ids returned, should continue auction + mockIdCallback.callArgWith(0, {'MOCKID': '1234'}); + auctionSpy.calledOnce.should.equal(true); + + // check ids were copied to bids + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.mid'); + expect(bid.userId.mid).to.equal('1234'); + }); + done(); + }); + + // no sync after auction ends + events.on.called.should.equal(false); + }); + + it('does not delay auction if not set, delays id fetch after auction ends with syncDelay', function() { + config.setConfig({ + usersync: { + syncDelay: 77, + userIds: [{ + name: 'mockId', storage: { name: 'MOCKID', type: 'cookie' } + }] + } + }); + + // check config has been set correctly + expect(auctionDelay).to.equal(0); + expect(syncDelay).to.equal(77); + + requestBidsHook(auctionSpy, {adUnits}); + + // should not delay auction + global.setTimeout.calledOnce.should.equal(false); + auctionSpy.calledOnce.should.equal(true); + + // check user sync is delayed after auction is ended + mockIdCallback.calledOnce.should.equal(false); + events.on.calledOnce.should.equal(true); + events.on.calledWith(CONSTANTS.EVENTS.AUCTION_END, sinon.match.func); + + // once auction is ended, sync user ids after delay + events.on.callArg(1); + global.setTimeout.calledOnce.should.equal(true); + global.setTimeout.calledWith(sinon.match.func, 77); + mockIdCallback.calledOnce.should.equal(false); + + // once sync delay is over, ids should be fetched + global.setTimeout.callArg(0); + mockIdCallback.calledOnce.should.equal(true); + }); + + it('does not delay user id sync after auction ends if set to 0', function() { + config.setConfig({ + usersync: { + syncDelay: 0, + userIds: [{ + name: 'mockId', storage: { name: 'MOCKID', type: 'cookie' } + }] + } + }); + + expect(syncDelay).to.equal(0); + + requestBidsHook(auctionSpy, {adUnits}); + + // auction should not be delayed + global.setTimeout.calledOnce.should.equal(false); + auctionSpy.calledOnce.should.equal(true); + + // sync delay after auction is ended + mockIdCallback.calledOnce.should.equal(false); + events.on.calledOnce.should.equal(true); + events.on.calledWith(CONSTANTS.EVENTS.AUCTION_END, sinon.match.func); + + // once auction is ended, if no sync delay, fetch ids + events.on.callArg(1); + global.setTimeout.calledOnce.should.equal(false); + mockIdCallback.calledOnce.should.equal(true); + }); + + it('does not delay auction if there are no ids to fetch', function() { + utils.setCookie('MOCKID', JSON.stringify({'MOCKID': '123456778'}), new Date(Date.now() + 5000).toUTCString()); + + config.setConfig({ + usersync: { + auctionDelay: 33, + syncDelay: 77, + userIds: [{ + name: 'mockId', storage: { name: 'MOCKID', type: 'cookie' } + }] + } + }); + + requestBidsHook(auctionSpy, {adUnits}); + + global.setTimeout.calledOnce.should.equal(false); + auctionSpy.calledOnce.should.equal(true); + mockIdCallback.calledOnce.should.equal(false); + + // no sync after auction ends + events.on.called.should.equal(false); + }); + }); + + describe('Request bids hook appends userId to bid objs in adapters', function() { + let adUnits; + + beforeEach(function() { + adUnits = [getAdUnitMock()]; + }); + + it('test hook from pubcommonid cookie', function(done) { + utils.setCookie('pubcid', 'testpubcid', (new Date(Date.now() + 100000).toUTCString())); + + setSubmoduleRegistry([pubCommonIdSubmodule]); + init(config); + config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'cookie'])); + + requestBidsHook(function() { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.pubcid'); + expect(bid.userId.pubcid).to.equal('testpubcid'); + }); + }); + utils.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); + done(); + }, {adUnits}); + }); + + it('test hook from pubcommonid config value object', function(done) { + setSubmoduleRegistry([pubCommonIdSubmodule]); + init(config); + config.setConfig(getConfigValueMock('pubCommonId', {'pubcidvalue': 'testpubcidvalue'})); + + requestBidsHook(function() { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.pubcidvalue'); + expect(bid.userId.pubcidvalue).to.equal('testpubcidvalue'); + }); + }); + done(); + }, {adUnits}); + }); + + it('test hook from pubcommonid html5', function(done) { + // simulate existing browser local storage values + localStorage.setItem('unifiedid_alt', JSON.stringify({'TDID': 'testunifiedid_alt'})); + localStorage.setItem('unifiedid_alt_exp', ''); + + setSubmoduleRegistry([unifiedIdSubmodule]); + init(config); + config.setConfig(getConfigMock(['unifiedId', 'unifiedid_alt', 'html5'])); + + requestBidsHook(function() { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.tdid'); + expect(bid.userId.tdid).to.equal('testunifiedid_alt'); + }); + }); + localStorage.removeItem('unifiedid_alt'); + localStorage.removeItem('unifiedid_alt_exp'); + done(); + }, {adUnits}); + }); + + it('test hook from identityLink html5', function(done) { + // simulate existing browser local storage values + localStorage.setItem('idl_env', 'AiGNC8Z5ONyZKSpIPf'); + localStorage.setItem('idl_env_exp', ''); + + setSubmoduleRegistry([identityLinkSubmodule]); + init(config); + config.setConfig(getConfigMock(['identityLink', 'idl_env', 'html5'])); + requestBidsHook(function() { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.idl_env'); + expect(bid.userId.idl_env).to.equal('AiGNC8Z5ONyZKSpIPf'); + }); + }); + localStorage.removeItem('idl_env'); + localStorage.removeItem('idl_env_exp'); + done(); + }, {adUnits}); + }); + + it('test hook from identityLink cookie', function(done) { + utils.setCookie('idl_env', 'AiGNC8Z5ONyZKSpIPf', (new Date(Date.now() + 100000).toUTCString())); + + setSubmoduleRegistry([identityLinkSubmodule]); + init(config); + config.setConfig(getConfigMock(['identityLink', 'idl_env', 'cookie'])); + + requestBidsHook(function() { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.idl_env'); + expect(bid.userId.idl_env).to.equal('AiGNC8Z5ONyZKSpIPf'); + }); + }); + utils.setCookie('idl_env', '', 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'})); + localStorage.setItem('_li_pbid_exp', ''); + + setSubmoduleRegistry([liveIntentIdSubmodule]); + init(config); + config.setConfig(getConfigMock(['liveIntentId', '_li_pbid', 'html5'])); + requestBidsHook(function() { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.lipb'); + expect(bid.userId.lipb.lipbid).to.equal('random-ls-identifier'); + }); + }); + localStorage.removeItem('_li_pbid'); + localStorage.removeItem('_li_pbid_exp'); + done(); + }, {adUnits}); + }); + + it('test hook from liveIntentId cookie', function(done) { + utils.setCookie('_li_pbid', JSON.stringify({'unifiedId': 'random-cookie-identifier'}), (new Date(Date.now() + 100000).toUTCString())); + + setSubmoduleRegistry([liveIntentIdSubmodule]); + init(config); + config.setConfig(getConfigMock(['liveIntentId', '_li_pbid', 'cookie'])); + + requestBidsHook(function() { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.lipb'); + expect(bid.userId.lipb.lipbid).to.equal('random-cookie-identifier'); + }); + }); + utils.setCookie('_li_pbid', '', EXPIRED_COOKIE_DATE); + done(); + }, {adUnits}); + }); + + it('test hook from id5id cookies when refresh needed', function(done) { + // simulate existing browser local storage values + utils.setCookie('id5id', JSON.stringify({'ID5ID': 'testid5id'}), (new Date(Date.now() + 5000).toUTCString())); + utils.setCookie('id5id_last', (new Date(Date.now() - 7200 * 1000)).toUTCString(), (new Date(Date.now() + 5000).toUTCString())); + + sinon.stub(utils, 'logError'); // getId should failed with a logError as it has no partnerId + + setSubmoduleRegistry([id5IdSubmodule]); + init(config); + config.setConfig(getConfigMock(['id5Id', 'id5id', 'cookie', 10, 3600])); + + requestBidsHook(function() { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.id5id'); + expect(bid.userId.id5id).to.equal('testid5id'); + }); + }); + sinon.assert.calledOnce(utils.logError); + utils.setCookie('id5id', '', EXPIRED_COOKIE_DATE); + utils.logError.restore(); + done(); + }, {adUnits}); + }); + + it('test hook from id5id value-based config', function(done) { + setSubmoduleRegistry([id5IdSubmodule]); + init(config); + config.setConfig(getConfigValueMock('id5Id', {'id5id': 'testid5id'})); + + requestBidsHook(function() { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.id5id'); + expect(bid.userId.id5id).to.equal('testid5id'); + }); + }); + 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', 'segments': ['123']})); + localStorage.setItem('_li_pbid_exp', ''); + + setSubmoduleRegistry([liveIntentIdSubmodule]); + init(config); + config.setConfig(getConfigMock(['liveIntentId', '_li_pbid', 'html5'])); + requestBidsHook(function() { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.lipb'); + expect(bid.userId.lipb.lipbid).to.equal('random-ls-identifier'); + expect(bid.userId.lipb.segments).to.include('123'); + }); + }); + localStorage.removeItem('_li_pbid'); + localStorage.removeItem('_li_pbid_exp'); + done(); + }, {adUnits}); + }); + + it('test hook from liveIntentId cookie', function(done) { + utils.setCookie('_li_pbid', JSON.stringify({'unifiedId': 'random-cookie-identifier', 'segments': ['123']}), (new Date(Date.now() + 100000).toUTCString())); + + setSubmoduleRegistry([liveIntentIdSubmodule]); + init(config); + config.setConfig(getConfigMock(['liveIntentId', '_li_pbid', 'cookie'])); + + requestBidsHook(function() { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.lipb'); + expect(bid.userId.lipb.lipbid).to.equal('random-cookie-identifier'); + expect(bid.userId.lipb.segments).to.include('123'); + }); + }); + utils.setCookie('_li_pbid', '', EXPIRED_COOKIE_DATE); + done(); + }, {adUnits}); + }); + + it('test hook when pubCommonId, unifiedId and id5Id have data to pass', function(done) { + utils.setCookie('pubcid', 'testpubcid', (new Date(Date.now() + 5000).toUTCString())); + utils.setCookie('unifiedid', JSON.stringify({'TDID': 'testunifiedid'}), (new Date(Date.now() + 5000).toUTCString())); + utils.setCookie('id5id', JSON.stringify({'ID5ID': 'testid5id'}), (new Date(Date.now() + 5000).toUTCString())); + utils.setCookie('idl_env', 'AiGNC8Z5ONyZKSpIPf', (new Date(Date.now() + 5000).toUTCString())); + + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule]); + init(config); + config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'cookie'], + ['unifiedId', 'unifiedid', 'cookie'], + ['id5Id', 'id5id', 'cookie'], + ['identityLink', 'idl_env', 'cookie'])); + + requestBidsHook(function() { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + // verify that the PubCommonId id data was copied to bid + expect(bid).to.have.deep.nested.property('userId.pubcid'); + expect(bid.userId.pubcid).to.equal('testpubcid'); + // also check that UnifiedId id data was copied to bid + expect(bid).to.have.deep.nested.property('userId.tdid'); + expect(bid.userId.tdid).to.equal('testunifiedid'); + // also check that Id5Id id data was copied to bid + expect(bid).to.have.deep.nested.property('userId.id5id'); + expect(bid.userId.id5id).to.equal('testid5id'); + // check that identityLink id data was copied to bid + expect(bid).to.have.deep.nested.property('userId.idl_env'); + expect(bid.userId.idl_env).to.equal('AiGNC8Z5ONyZKSpIPf'); + }); + }); + utils.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); + utils.setCookie('unifiedid', '', EXPIRED_COOKIE_DATE); + utils.setCookie('id5id', '', EXPIRED_COOKIE_DATE); + utils.setCookie('idl_env', '', EXPIRED_COOKIE_DATE); + done(); + }, {adUnits}); + }); + + it('test hook when pubCommonId, unifiedId and id5Id have their modules added before and after init', function(done) { + utils.setCookie('pubcid', 'testpubcid', (new Date(Date.now() + 5000).toUTCString())); + utils.setCookie('unifiedid', JSON.stringify({'TDID': 'cookie-value-add-module-variations'}), new Date(Date.now() + 5000).toUTCString()); + utils.setCookie('id5id', JSON.stringify({'ID5ID': 'testid5id'}), (new Date(Date.now() + 5000).toUTCString())); + utils.setCookie('idl_env', 'AiGNC8Z5ONyZKSpIPf', new Date(Date.now() + 5000).toUTCString()); + + setSubmoduleRegistry([]); + + // attaching before init + attachIdSystem(pubCommonIdSubmodule); + + init(config); + + // attaching after init + attachIdSystem(unifiedIdSubmodule); + attachIdSystem(id5IdSubmodule); + attachIdSystem(identityLinkSubmodule); + + config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'cookie'], + ['unifiedId', 'unifiedid', 'cookie'], + ['id5Id', 'id5id', 'cookie'], + ['identityLink', 'idl_env', 'cookie'])); + + requestBidsHook(function() { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + // verify that the PubCommonId id data was copied to bid + expect(bid).to.have.deep.nested.property('userId.pubcid'); + expect(bid.userId.pubcid).to.equal('testpubcid'); + // also check that UnifiedId id data was copied to bid + expect(bid).to.have.deep.nested.property('userId.tdid'); + expect(bid.userId.tdid).to.equal('cookie-value-add-module-variations'); + // also check that Id5Id id data was copied to bid + expect(bid).to.have.deep.nested.property('userId.id5id'); + expect(bid.userId.id5id).to.equal('testid5id'); + // also check that identityLink id data was copied to bid + expect(bid).to.have.deep.nested.property('userId.idl_env'); + expect(bid.userId.idl_env).to.equal('AiGNC8Z5ONyZKSpIPf'); + }); + }); + utils.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); + utils.setCookie('unifiedid', '', EXPIRED_COOKIE_DATE); + utils.setCookie('id5id', '', EXPIRED_COOKIE_DATE); + utils.setCookie('idl_env', '', EXPIRED_COOKIE_DATE); + done(); + }, {adUnits}); + }); + + it('should add new id system ', function(done) { + utils.setCookie('pubcid', 'testpubcid', (new Date(Date.now() + 5000).toUTCString())); + utils.setCookie('unifiedid', JSON.stringify({'TDID': 'cookie-value-add-module-variations'}), new Date(Date.now() + 5000).toUTCString()); + utils.setCookie('id5id', JSON.stringify({'ID5ID': 'testid5id'}), (new Date(Date.now() + 5000).toUTCString())); + utils.setCookie('idl_env', 'AiGNC8Z5ONyZKSpIPf', new Date(Date.now() + 5000).toUTCString()); + utils.setCookie('MOCKID', JSON.stringify({'MOCKID': '123456778'}), new Date(Date.now() + 5000).toUTCString()); + + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule]); + init(config); + + config.setConfig({ + usersync: { + syncDelay: 0, + userIds: [{ + name: 'pubCommonId', storage: { name: 'pubcid', type: 'cookie' } + }, { + name: 'unifiedId', storage: { name: 'unifiedid', type: 'cookie' } + }, { + name: 'id5Id', storage: { name: 'id5id', type: 'cookie' } + }, { + name: 'identityLink', storage: { name: 'idl_env', type: 'cookie' } + }, { + name: 'mockId', storage: { name: 'MOCKID', type: 'cookie' } + }] + } + }); + + // Add new submodule named 'mockId' + attachIdSystem({ + name: 'mockId', + decode: function(value) { + return { + 'mid': value['MOCKID'] + }; + }, + getId: function(params, storedId) { + if (storedId) return {}; + return {id: {'MOCKID': '1234'}}; + } + }); + + requestBidsHook(function() { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + // check PubCommonId id data was copied to bid + expect(bid).to.have.deep.nested.property('userId.pubcid'); + expect(bid.userId.pubcid).to.equal('testpubcid'); + // check UnifiedId id data was copied to bid + expect(bid).to.have.deep.nested.property('userId.tdid'); + expect(bid.userId.tdid).to.equal('cookie-value-add-module-variations'); + // also check that Id5Id id data was copied to bid + expect(bid).to.have.deep.nested.property('userId.id5id'); + expect(bid.userId.id5id).to.equal('testid5id'); + // also check that identityLink id data was copied to bid + expect(bid).to.have.deep.nested.property('userId.idl_env'); + expect(bid.userId.idl_env).to.equal('AiGNC8Z5ONyZKSpIPf'); + // check MockId data was copied to bid + expect(bid).to.have.deep.nested.property('userId.mid'); + expect(bid.userId.mid).to.equal('123456778'); + }); + }); + utils.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); + utils.setCookie('unifiedid', '', EXPIRED_COOKIE_DATE); + utils.setCookie('id5id', '', EXPIRED_COOKIE_DATE); + utils.setCookie('idl_env', '', EXPIRED_COOKIE_DATE); + utils.setCookie('MOCKID', '', EXPIRED_COOKIE_DATE); + done(); + }, {adUnits}); + }); + }); + + describe('callbacks at the end of auction', function() { + let xhr; + let requests; + + beforeEach(function() { + requests = []; + xhr = sinon.useFakeXMLHttpRequest(); + xhr.onCreate = request => requests.push(request); + sinon.stub(events, 'getEvents').returns([]); + sinon.stub(utils, 'triggerPixel'); + utils.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); + utils.setCookie('unifiedid', '', EXPIRED_COOKIE_DATE); + }); + + afterEach(function() { + xhr.restore(); + events.getEvents.restore(); + utils.triggerPixel.restore(); + utils.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); + utils.setCookie('unifiedid', '', EXPIRED_COOKIE_DATE); + }); + + it('pubcid callback with url', function () { + let adUnits = [getAdUnitMock()]; + let innerAdUnits; + let customCfg = getConfigMock(['pubCommonId', 'pubcid_alt', 'cookie']); + customCfg = addConfig(customCfg, 'params', {pixelUrl: '/any/pubcid/url'}); + + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule]); + init(config); + config.setConfig(customCfg); + requestBidsHook((config) => { innerAdUnits = config.adUnits }, {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'); + }); + + it('unifiedid callback with url', function () { + let adUnits = [getAdUnitMock()]; + let innerAdUnits; + let customCfg = getConfigMock(['unifiedId', 'unifiedid', 'cookie']); + addConfig(customCfg, 'params', {url: '/any/unifiedid/url'}); + + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule]); + init(config); + config.setConfig(customCfg); + requestBidsHook((config) => { innerAdUnits = config.adUnits }, {adUnits}); + + expect(requests).to.be.empty; + events.emit(CONSTANTS.EVENTS.AUCTION_END, {}); + expect(requests[0].url).to.equal('/any/unifiedid/url'); + }); + + it('unifiedid callback with partner', function () { + let adUnits = [getAdUnitMock()]; + let innerAdUnits; + let customCfg = getConfigMock(['unifiedId', 'unifiedid', 'cookie']); + addConfig(customCfg, 'params', {partner: 'rubicon'}); + + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule]); + init(config); + config.setConfig(customCfg); + requestBidsHook((config) => { innerAdUnits = config.adUnits }, {adUnits}); + + expect(requests).to.be.empty; + events.emit(CONSTANTS.EVENTS.AUCTION_END, {}); + expect(requests[0].url).to.equal('//match.adsrvr.org/track/rid?ttd_pid=rubicon&fmt=json'); + }); + }); +}); diff --git a/test/spec/modules/viBidAdapter_spec.js b/test/spec/modules/viBidAdapter_spec.js index 2468da0cfaf..b12d534ff02 100644 --- a/test/spec/modules/viBidAdapter_spec.js +++ b/test/spec/modules/viBidAdapter_spec.js @@ -1,139 +1,894 @@ -import { expect } from 'chai'; -import { spec } from 'modules/viBidAdapter'; -import { newBidder } from 'src/adapters/bidderFactory'; - -const ENDPOINT = `//pb.vi-serve.com/prebid/bid`; - -describe('viBidAdapter', function() { - newBidder(spec); - - describe('isBidRequestValid', function () { - let bid = { - 'bidder': 'vi', - 'params': { - 'pubId': 'sb_test', - 'lang': 'en-US', - 'cat': 'IAB1', - 'bidFloor': 0.05 - }, - 'adUnitCode': 'adunit-code', - 'sizes': [ - [320, 480] - ], - 'bidId': '29b891ad542377', - 'bidderRequestId': '1dc9a08206a57b', - 'requestId': '24176695-e3f0-44db-815b-ed97cf5ad49b', - 'placementCode': 'div-gpt-ad-1460505748561-0', - 'transactionId': '474da635-9cf0-4188-a3d9-58961be8f905' - }; +import { + ratioToPercentageCeil, + merge, + getDocumentHeight, + getOffset, + getWindowParents, + getRectCuts, + getTopmostReachableWindow, + topDocumentIsReachable, + isInsideIframe, + isInsideSafeframe, + getIframeType, + getFrameElements, + getElementCuts, + getInViewRatio, + getMayBecomeVisible, + getInViewPercentage, + getInViewRatioInsideTopFrame, + getOffsetTopDocument, + getOffsetTopDocumentPercentage, + getOffsetToView, + getOffsetToViewPercentage, + area, + get, + getViewabilityDescription, + mergeArrays +} from 'modules/viBidAdapter'; - it('should return true when required params found', function () { - expect(spec.isBidRequestValid(bid)).to.equal(true); - }); +describe('ratioToPercentageCeil', () => { + it('1 converts to percentage', () => + expect(ratioToPercentageCeil(0.01)).to.equal(1)); + it('2 converts to percentage', () => + expect(ratioToPercentageCeil(0.00000000001)).to.equal(1)); + it('3 converts to percentage', () => + expect(ratioToPercentageCeil(0.5)).to.equal(50)); + it('4 converts to percentage', () => + expect(ratioToPercentageCeil(1)).to.equal(100)); + it('5 converts to percentage', () => + expect(ratioToPercentageCeil(0.99)).to.equal(99)); + it('6 converts to percentage', () => + expect(ratioToPercentageCeil(0.990000000000001)).to.equal(100)); +}); - it('should return false when pubId not passed', function () { - bid.params.pubId = undefined; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); +describe('merge', () => { + it('merges two objects', () => { + expect( + merge({ a: 1, b: 2, d: 0 }, { a: 2, b: 2, c: 3 }, (a, b) => a + b) + ).to.deep.equal({ a: 3, b: 4, c: 3, d: 0 }); }); +}); - describe('buildRequests', function () { - let bidRequests = [{ - 'bidder': 'vi', - 'params': { - 'pubId': 'sb_test', - 'lang': 'en-US', - 'cat': 'IAB1', - 'bidFloor': 0.05 +describe('getDocumentHeight', () => { + [ + { + curDocument: { + body: { + clientHeight: 0, + offsetHeight: 0, + scrollHeight: 0 + }, + documentElement: { + clientHeight: 0, + offsetHeight: 0, + scrollHeight: 0 + } }, - 'adUnitCode': 'adunit-code', - 'sizes': [ - [320, 480] - ], - 'bidId': '29b891ad542377', - 'bidderRequestId': '1dc9a08206a57b', - 'requestId': '24176695-e3f0-44db-815b-ed97cf5ad49b', - 'placementCode': 'div-gpt-ad-1460505748561-0', - 'transactionId': '474da635-9cf0-4188-a3d9-58961be8f905' - }]; - - const request = spec.buildRequests(bidRequests); - - it('POST bid request to vi', function () { - expect(request.method).to.equal('POST'); - }); + expected: 0 + }, + { + curDocument: { + body: { + clientHeight: 0, + offsetHeight: 13, + scrollHeight: 24 + }, + documentElement: { + clientHeight: 0, + offsetHeight: 0, + scrollHeight: 0 + } + }, + expected: 24 + }, + { + curDocument: { + body: { + clientHeight: 0, + offsetHeight: 13, + scrollHeight: 24 + }, + documentElement: { + clientHeight: 100, + offsetHeight: 50, + scrollHeight: 30 + } + }, + expected: 100 + } + ].forEach(({ curDocument, expected }) => + expect(getDocumentHeight(curDocument)).to.be.equal(expected) + ); +}); - it('check endpoint URL', function () { - expect(request.url).to.equal(ENDPOINT) - }); +describe('getOffset', () => { + [ + { + element: { + ownerDocument: { + defaultView: { + pageXOffset: 0, + pageYOffset: 0 + } + }, + getBoundingClientRect: () => ({ + top: 0, + right: 0, + bottom: 0, + left: 0 + }) + }, + expected: { + top: 0, + right: 0, + bottom: 0, + left: 0 + } + } + ].forEach(({ description, element, expected }, i) => + it( + 'returns element offsets from the document edges (including scroll): ' + + i, + () => expect(getOffset(element)).to.be.deep.equal(expected) + ) + ); + it('Throws when there is no window', () => + expect( + getOffset.bind(null, { + ownerDocument: { + defaultView: null + }, + getBoundingClientRect: () => ({ + top: 0, + right: 0, + bottom: 0, + left: 0 + }) + }) + ).to.throw()); +}); + +describe('getWindowParents', () => { + const win = {}; + win.top = win; + win.parent = win; + const win1 = { top: win, parent: win }; + const win2 = { top: win, parent: win1 }; + const win3 = { top: win, parent: win2 }; + + it('get parents up to the top', () => + expect(getWindowParents(win3)).to.be.deep.equal([win2, win1, win])); +}); + +describe('getTopmostReachableWindow', () => { + const win = {}; + win.top = win; + win.parent = win; + const win1 = { top: win, parent: win }; + const win2 = { top: win, parent: win1 }; + const win3 = { top: win, parent: win2 }; + + it('get parents up to the top', () => + expect(getTopmostReachableWindow(win3)).to.be.equal(win)); +}); + +const topWindow = { document, frameElement: 0 }; +topWindow.top = topWindow; +topWindow.parent = topWindow; +const topFrameElement = { + ownerDocument: { + defaultView: topWindow + } +}; +const frameWindow1 = { + top: topWindow, + parent: topWindow, + frameElement: topFrameElement +}; +const frameElement1 = { + ownerDocument: { + defaultView: frameWindow1 + } +}; +const frameWindow2 = { + top: topWindow, + parent: frameWindow1, + frameElement: frameElement1 +}; +const frameElement2 = { + ownerDocument: { + defaultView: frameWindow2 + } +}; +const frameWindow3 = { + top: topWindow, + parent: frameWindow2, + frameElement: frameElement2 +}; + +describe('topDocumentIsReachable', () => { + it('returns true if it no inside iframe', () => + expect(topDocumentIsReachable(topWindow)).to.be.true); + it('returns true if it can access top document', () => + expect(topDocumentIsReachable(frameWindow3)).to.be.true); +}); + +describe('isInsideIframe', () => { + it('returns true if window !== window.top', () => + expect(isInsideIframe(topWindow)).to.be.false); + it('returns true if window !== window.top', () => + expect(isInsideIframe(frameWindow1)).to.be.true); +}); + +const safeframeWindow = { $sf: {} }; + +describe('isInsideSafeframe', () => { + it('returns true if top window is not reachable and window.$sf is defined', () => + expect(isInsideSafeframe(safeframeWindow)).to.be.true); +}); + +const hostileFrameWindow = {}; + +describe('getIframeType', () => { + it('returns undefined when is not inside iframe', () => + expect(getIframeType(topWindow)).to.be.undefined); + it("returns 'safeframe' when inside sf", () => + expect(getIframeType(safeframeWindow)).to.be.equal('safeframe')); + it("returns 'friendly' when inside friendly iframe and can reach top window", () => + expect(getIframeType(frameWindow3)).to.be.equal('friendly')); + it("returns 'nonfriendly' when cannot get top window", () => + expect(getIframeType(hostileFrameWindow)).to.be.equal('nonfriendly')); +}); + +describe('getFrameElements', () => { + it('it returns a list iframe elements up to the top, topmost goes first', () => { + expect(getFrameElements(frameWindow3)).to.be.deep.equal([ + topFrameElement, + frameElement1, + frameElement2 + ]); + }); +}); + +describe('area', () => { + it('calculates area', () => expect(area(10, 10)).to.be.equal(100)); + it('calculates area', () => + expect( + area(10, 10, { top: -2, left: -2, bottom: 0, right: 0 }) + ).to.be.equal(64)); +}); + +describe('getElementCuts', () => { + it('returns element cuts', () => + expect( + getElementCuts({ + getBoundingClientRect() { + return { + top: 0, + right: 200, + bottom: 200, + left: 0 + }; + }, + ownerDocument: { + defaultView: { + innerHeight: 1000, + innerWidth: 1000 + } + } + }) + ).to.be.deep.equal({ + top: 0, + right: 0, + bottom: 0, + left: 0 + })); +}); + +describe('getInViewRatio', () => { + it('returns inViewRatio', () => + expect( + getInViewRatio({ + ownerDocument: { + defaultView: { + innerHeight: 1000, + innerWidth: 1000 + } + }, + offsetWidth: 200, + offsetHeight: 200, + getBoundingClientRect() { + return { + top: 0, + right: 200, + bottom: 200, + left: 0 + }; + } + }) + ).to.be.deep.equal(1)); +}); + +describe('getMayBecomeVisible', () => { + it('returns true if not inside iframe of visible inside the iframe', () => + expect( + getMayBecomeVisible({ + ownerDocument: { + defaultView: { + innerHeight: 1000, + innerWidth: 1000 + } + }, + offsetWidth: 200, + offsetHeight: 200, + getBoundingClientRect() { + return { + top: 0, + right: 200, + bottom: 200, + left: 0 + }; + } + }) + ).to.be.true); +}); + +describe('getInViewPercentage', () => { + it('returns inViewRatioPercentage', () => + expect( + getInViewPercentage({ + ownerDocument: { + defaultView: { + innerHeight: 1000, + innerWidth: 1000 + } + }, + offsetWidth: 200, + offsetHeight: 200, + getBoundingClientRect() { + return { + top: 0, + right: 200, + bottom: 200, + left: 0 + }; + } + }) + ).to.be.deep.equal(100)); +}); + +describe('getInViewRatioInsideTopFrame', () => { + it('returns inViewRatio', () => + expect( + getInViewRatioInsideTopFrame({ + ownerDocument: { + defaultView: { + innerHeight: 1000, + innerWidth: 1000 + } + }, + offsetWidth: 200, + offsetHeight: 200, + getBoundingClientRect() { + return { + top: 0, + right: 200, + bottom: 200, + left: 0 + }; + } + }) + ).to.be.deep.equal(1)); +}); + +describe('getOffsetTopDocument', () => { + it('returns offset relative to the top document', () => + expect( + getOffsetTopDocument({ + ownerDocument: { + defaultView: { + pageXOffset: 0, + pageYOffset: 0 + } + }, + getBoundingClientRect: () => ({ + top: 0, + right: 0, + bottom: 0, + left: 0 + }) + }) + ).to.be.deep.equal({ + top: 0, + right: 0, + bottom: 0, + left: 0 + })); +}); + +describe('getOffsetTopDocumentPercentage', () => { + it('returns offset from the top as a percentage of the page length', () => { + const topWindow = { + pageXOffset: 0, + pageYOffset: 100, + document: { + body: { + clientHeight: 1000 + } + } + }; + topWindow.top = topWindow; + topWindow.parent = topWindow; + expect( + getOffsetTopDocumentPercentage({ + ownerDocument: { + defaultView: topWindow + }, + getBoundingClientRect: () => ({ + top: 100, + right: 0, + bottom: 0, + left: 0 + }) + }) + ).to.be.equal(20); + }); + it('throws when cannot get window', () => + expect(() => + getOffsetTopDocumentPercentage({ + ownerDocument: {} + }) + ).to.throw()); + it("throw when top document isn't reachable", () => { + const topWindow = { ...topWindow, document: null }; + expect(() => + getOffsetTopDocumentPercentage({ + ownerDocument: { + defaultView: { + top: topWindow + } + } + }) + ).to.throw(); }); +}); + +describe('getOffsetToView', () => { + expect( + getOffsetToView({ + ownerDocument: { + defaultView: { + scrollY: 0, + pageXOffset: 0, + pageYOffset: 0 + } + }, + getBoundingClientRect: () => ({ + top: 0, + right: 0, + bottom: 0, + left: 0 + }) + }) + ).to.be.equal(0); +}); - describe('buildRequests can handle size in 1-dim array', function () { - let bidRequests = [{ - 'bidder': 'vi', - 'params': { - 'pubId': 'sb_test', - 'lang': 'en-US', - 'cat': 'IAB1', - 'bidFloor': 0.05 +describe('getOffsetToView', () => { + expect( + getOffsetToViewPercentage({ + ownerDocument: { + defaultView: { + scrollY: 0, + pageXOffset: 0, + pageYOffset: 0, + document: { + body: { + clientHeight: 1000 + } + } + } }, - 'adUnitCode': 'adunit-code', - 'sizes': [320, 480], - 'bidId': '29b891ad542377', - 'bidderRequestId': '1dc9a08206a57b', - 'requestId': '24176695-e3f0-44db-815b-ed97cf5ad49b', - 'placementCode': 'div-gpt-ad-1460505748561-0', - 'transactionId': '474da635-9cf0-4188-a3d9-58961be8f905' - }]; - - const request = spec.buildRequests(bidRequests); - - it('POST bid request to vi', function () { - expect(request.method).to.equal('POST'); + getBoundingClientRect: () => ({ + top: 0, + right: 0, + bottom: 0, + left: 0 + }) + }) + ).to.be.equal(0); +}); + +describe('getCuts without vCuts', () => { + const cases = { + 'completely in view 1': { + top: 0, + bottom: 200, + right: 200, + left: 0, + vw: 300, + vh: 300, + expected: { + top: 0, + right: 0, + bottom: 0, + left: 0 + } + }, + 'completely in view 2': { + top: 100, + bottom: 200, + right: 200, + left: 0, + vw: 300, + vh: 300, + expected: { + top: 0, + right: 0, + bottom: 0, + left: 0 + } + }, + 'half cut from the top': { + top: -200, + bottom: 200, + right: 200, + left: 0, + vw: 300, + vh: 300, + expected: { + top: -200, + right: 0, + bottom: 0, + left: 0 + } + }, + 'half cut from the bottom': { + top: 0, + bottom: 600, + right: 200, + left: 0, + vw: 300, + vh: 300, + expected: { + top: 0, + right: 0, + bottom: -300, + left: 0 + } + }, + 'quarter cut from top and bottom': { + top: -25, + bottom: 75, + right: 200, + left: 0, + vw: 300, + vh: 50, + expected: { + top: -25, + right: 0, + bottom: -25, + left: 0 + } + }, + 'out of view top': { + top: -200, + bottom: -5, + right: 200, + left: 0, + vw: 300, + vh: 200, + expected: { + top: -200, + right: 0, + bottom: 0, + left: 0 + } + }, + 'out of view bottom': { + top: 250, + bottom: 500, + right: 200, + left: 0, + vw: 300, + vh: 200, + expected: { + top: 0, + right: 0, + bottom: -300, + left: 0 + } + }, + 'half cut from left': { + top: 0, + bottom: 200, + left: -200, + right: 200, + vw: 300, + vh: 300, + expected: { + top: 0, + right: 0, + bottom: 0, + left: -200 + } + }, + 'half cut from left and top': { + top: -100, + bottom: 100, + left: -200, + right: 200, + vw: 300, + vh: 300, + expected: { + top: -100, + right: 0, + bottom: 0, + left: -200 + } + }, + 'quarter cut from all sides': { + top: -100, + left: -100, + bottom: 300, + right: 300, + vw: 200, + vh: 200, + expected: { + top: -100, + right: -100, + bottom: -100, + left: -100 + } + } + }; + for (let descr in cases) { + it(descr, () => { + const { expected, vh, vw, ...rect } = cases[descr]; + expect(getRectCuts(rect, vh, vw)).to.deep.equal(expected); }); + } +}); - it('check endpoint URL', function () { - expect(request.url).to.equal(ENDPOINT) +describe('getCuts with vCuts', () => { + const cases = { + 'completely in view 1, half-cut viewport from top': { + top: 0, + right: 200, + bottom: 200, + left: 0, + vw: 200, + vh: 200, + vCuts: { + top: -100, + right: 0, + bottom: 0, + left: 0 + }, + expected: { + top: -100, + right: 0, + bottom: 0, + left: 0 + } + }, + 'completely in view 2, half-cut viewport from bottom': { + top: 100, + bottom: 200, + right: 200, + left: 0, + vw: 300, + vh: 300, + vCuts: { + top: 0, + right: 0, + bottom: -150, + left: 0 + }, + expected: { + top: 0, + right: 0, + bottom: -50, + left: 0 + } + }, + 'half cut from the top, 1/3 viewport cut from the bottom': { + top: -200, + bottom: 200, + right: 200, + left: 0, + vw: 300, + vh: 300, + vCuts: { + top: 0, + right: 0, + bottom: -100, + left: 0 + }, + expected: { + top: -200, + right: 0, + bottom: 0, + left: 0 + } + }, + 'half cut from the bottom': { + top: 0, + bottom: 600, + right: 200, + left: 0, + vw: 300, + vh: 300, + expected: { + top: 0, + right: 0, + bottom: -300, + left: 0 + } + }, + 'quarter cut from top and bottom': { + top: -25, + bottom: 75, + right: 200, + left: 0, + vw: 300, + vh: 50, + expected: { + top: -25, + right: 0, + bottom: -25, + left: 0 + } + }, + 'out of view top': { + top: -200, + bottom: -5, + right: 200, + left: 0, + vw: 300, + vh: 200, + expected: { + top: -200, + right: 0, + bottom: 0, + left: 0 + } + }, + 'out of view bottom': { + top: 250, + bottom: 500, + right: 200, + left: 0, + vw: 300, + vh: 200, + expected: { + top: 0, + right: 0, + bottom: -300, + left: 0 + } + }, + 'half cut from left': { + top: 0, + bottom: 200, + left: -200, + right: 200, + vw: 300, + vh: 300, + expected: { + top: 0, + right: 0, + bottom: 0, + left: -200 + } + }, + 'half cut from left and top': { + top: -100, + bottom: 100, + left: -200, + right: 200, + vw: 300, + vh: 300, + expected: { + top: -100, + right: 0, + bottom: 0, + left: -200 + } + }, + 'quarter cut from all sides': { + top: -100, + left: -100, + bottom: 300, + right: 300, + vw: 200, + vh: 200, + expected: { + top: -100, + right: -100, + bottom: -100, + left: -100 + } + } + }; + for (let descr in cases) { + it(descr, () => { + const { expected, vh, vw, vCuts, ...rect } = cases[descr]; + expect(getRectCuts(rect, vh, vw, vCuts)).to.deep.equal(expected); }); - }); + } +}); - describe('interpretResponse', function () { - let response = { - body: [{ - 'id': '29b891ad542377', - 'price': 0.1, - 'width': 320, - 'height': 480, - 'ad': '', - 'creativeId': 'dZsPGv' - }] - }; +describe('get', () => { + it('returns a property in a nested object 1', () => + expect(get(['a'], { a: 1 })).to.equal(1)); + it('returns a property in a nested object 2', () => + expect(get(['a', 'b'], { a: { b: 1 } })).to.equal(1)); + it('returns a property in a nested object 3', () => + expect(get(['a', 'b'], { a: { b: 1 } })).to.equal(1)); + it('returns undefined if property does not exist', () => + expect(get(['a', 'b'], { b: 1 })).to.equal(undefined)); + it('returns undefined if property does not exist', () => + expect(get(['a', 'b'], undefined)).to.equal(undefined)); + it('returns undefined if property does not exist', () => + expect(get(['a', 'b'], 1213)).to.equal(undefined)); + const DEFAULT = -5; + it('returns defaultValue if property does not exist', () => + expect(get(['a', 'b'], { b: 1 }, DEFAULT)).to.equal(DEFAULT)); + it('returns defaultValue if property does not exist', () => + expect(get(['a', 'b'], undefined, DEFAULT)).to.equal(DEFAULT)); + it('returns defaultValue if property does not exist', () => + expect(get(['a', 'b'], 1213, DEFAULT)).to.equal(DEFAULT)); + it('can work with arrays 1', () => expect(get([0, 1], [[1, 2]])).to.equal(2)); + it('can work with arrays 2', () => + expect(get([0, 'a'], [{ a: 42 }])).to.equal(42)); +}); - it('should get the correct bid response', function () { - let expectedResponse = [{ - 'requestId': '29b891ad542377', - 'cpm': 0.1, - 'width': 320, - 'height': 480, - 'creativeId': 'dZsPGv', - 'dealId': null, - 'currency': 'USD', - 'netRevenue': true, - 'mediaType': 'banner', - 'ad': decodeURIComponent(``), - 'ttl': 60000 - }]; - - let result = spec.interpretResponse(response); - expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedResponse[0])); +describe('getViewabilityDescription', () => { + it('returns error when there is no element', () => { + expect(getViewabilityDescription(null)).to.deep.equal({ + error: 'no element' }); - - it('handles empty bid response', function () { - let response = { - body: [] - }; - let result = spec.interpretResponse(response); - expect(result.length).to.equal(0); + }); + it('returns only iframe type for nonfrienly iframe', () => { + expect( + getViewabilityDescription({ + ownerDocument: { + defaultView: {} + } + }) + ).to.deep.equal({ + iframeType: 'nonfriendly' + }); + }); + it('returns only iframe type for safeframe iframe', () => { + expect( + getViewabilityDescription({ + ownerDocument: { + defaultView: { + $sf: true + } + } + }) + ).to.deep.equal({ + iframeType: 'safeframe' }); }); }); + +describe('mergeSizes', () => { + it('merges provides arrays of tuples, leaving only unique', () => { + expect( + mergeArrays(x => x.join(','), [[1, 2], [2, 4]], [[1, 2]]) + ).to.deep.equal([[1, 2], [2, 4]]); + }); + it('merges provides arrays of tuples, leaving only unique', () => { + expect( + mergeArrays( + x => x.join(','), + [[1, 2], [2, 4]], + [[1, 2]], + [[400, 500], [500, 600]] + ) + ).to.deep.equal([[1, 2], [2, 4], [400, 500], [500, 600]]); + }); +}); diff --git a/test/spec/modules/vidazooBidAdapter_spec.js b/test/spec/modules/vidazooBidAdapter_spec.js index d9c08ad924c..3251a1ef851 100644 --- a/test/spec/modules/vidazooBidAdapter_spec.js +++ b/test/spec/modules/vidazooBidAdapter_spec.js @@ -23,6 +23,9 @@ const BID = { const BIDDER_REQUEST = { 'gdprConsent': { 'consentString': 'consent_string' + }, + 'refererInfo': { + 'referer': 'http://www.greatsite.com' } }; @@ -111,7 +114,6 @@ describe('VidazooBidAdapter', function () { let sandbox; before(function () { sandbox = sinon.sandbox.create(); - sandbox.stub(utils, 'getTopWindowUrl').returns('http://www.greatsite.com'); sandbox.stub(Date, 'now').returns(1000); }); @@ -125,7 +127,7 @@ describe('VidazooBidAdapter', function () { consent: 'consent_string', width: '300', height: '250', - url: 'http://www.greatsite.com', + url: 'http%3A%2F%2Fwww.greatsite.com', cb: 1000, bidFloor: 0.1, bidId: '2d52001cabd527', @@ -141,7 +143,7 @@ describe('VidazooBidAdapter', function () { consent: 'consent_string', width: '300', height: '600', - url: 'http://www.greatsite.com', + url: 'http%3A%2F%2Fwww.greatsite.com', cb: 1000, bidFloor: 0.1, bidId: '2d52001cabd527', diff --git a/test/spec/modules/videoNowBidAdapter_spec.js b/test/spec/modules/videoNowBidAdapter_spec.js new file mode 100644 index 00000000000..337960c6edd --- /dev/null +++ b/test/spec/modules/videoNowBidAdapter_spec.js @@ -0,0 +1,566 @@ +import { expect } from 'chai' +import { spec } from 'modules/videoNowBidAdapter' +import { replaceAuctionPrice } from '../../../src/utils' + +const placementId = 'div-gpt-ad-1438287399331-1' +const LS_ITEM_NAME = 'videonow-config' + +const getValidServerResponse = () => { + const serverResponse = { + body: { + id: '111-111', + bidid: '2955a162-699e-4811-ce88-5c3ac973e73c', + cur: 'RUB', + seatbid: [ + { + bid: [ + { + id: 'e3bf2b82e3e9485113fad6c9b27f8768.1', + impid: '1', + price: 10.97, + nurl: 'http://localhost:8086/event/nurl', + netRevenue: false, + ttl: 800, + adm: '', + crid: 'e3bf2b82e3e9485113fad6c9b27f8768.1', + h: 640, + w: 480, + ext: { + init: 'http://localhost:8086/vn_init.js', + module: { + min: 'http://localhost:8086/vn_module.js', + log: 'http://localhost:8086/vn_module.js?log=1' + }, + format: { + name: 'flyRoll', + }, + }, + + }, + ], + group: 0, + }, + ], + price: 10, + ext: { + placementId, + pixels: [ + 'http://localhost:8086/event/pxlcookiematching?uiid=1', + 'http://localhost:8086/event/pxlcookiematching?uiid=2', + ], + iframes: [ + 'http://localhost:8086/event/ifrcookiematching?uiid=1', + 'http://localhost:8086/event/ifrcookiematching?uiid=2', + ], + }, + }, + headers: {}, + } + + return JSON.parse(JSON.stringify(serverResponse)) +} + +describe('videonowAdapterTests', function() { + describe('bidRequestValidity', function() { + it('bidRequest with pId', function() { + expect(spec.isBidRequestValid({ + bidder: 'videonow', + params: { + pId: '86858', + }, + })).to.equal(true) + }) + + it('bidRequest without pId', function() { + expect(spec.isBidRequestValid({ + bidder: 'videonow', + params: { + nomater: 86858, + }, + })).to.equal(false) + + it('bidRequest is empty', function() { + expect(spec.isBidRequestValid({})).to.equal(false) + }) + + it('bidRequest is undefned', function() { + expect(spec.isBidRequestValid(undefined)).to.equal(false) + }) + }) + + describe('bidRequest', function() { + const validBidRequests = [ + { + bidder: 'videonow', + params: { + pId: '1', + placementId, + url: 'http://localhost:8086/bid?p=exists', + bidFloor: 10, + cur: 'RUB' + }, + crumbs: { + pubcid: 'feded041-35dd-4b54-979a-6d7805abfa75', + }, + mediaTypes: { + banner: { + sizes: [[640, 480], [320, 200]] + }, + }, + adUnitCode: 'test-ad', + transactionId: '676403c7-09c9-4b56-be82-e7cae81f40b9', + sizes: [[640, 480], [320, 200]], + bidId: '268c309f46390d', + bidderRequestId: '1dfdd514c36ef6', + auctionId: '4d523546-889a-4029-9a79-13d3c69f9922', + src: 'client', + bidRequestsCount: 1, + }, + ] + + const bidderRequest = { + bidderCode: 'videonow', + auctionId: '4d523546-889a-4029-9a79-13d3c69f9922', + bidderRequestId: '1dfdd514c36ef6', + bids: [ + { + bidder: 'videonow', + params: { + pId: '1', + placementId, + url: 'http://localhost:8086/bid', + bidFloor: 10, + cur: 'RUB', + }, + crumbs: { + pubcid: 'feded041-35dd-4b54-979a-6d7805abfa75', + }, + mediaTypes: { + banner: { + sizes: [[640, 480], [320, 200]], + }, + }, + adUnitCode: 'test-ad', + transactionId: '676403c7-09c9-4b56-be82-e7cae81f40b9', + sizes: [[640, 480], [320, 200]], + bidId: '268c309f46390d', + bidderRequestId: '1dfdd514c36ef6', + auctionId: '4d523546-889a-4029-9a79-13d3c69f9922', + src: 'client', + bidRequestsCount: 1, + }, + ], + auctionStart: 1565794308584, + timeout: 3000, + refererInfo: { + referer: 'http://localhost:8086/page', + reachedTop: true, + numIframes: 0, + stack: [ + 'http://localhost:8086/page', + ], + }, + start: 1565794308589, + } + + const requests = spec.buildRequests(validBidRequests, bidderRequest) + const request = (requests && requests.length && requests[0]) || {} + + it('bidRequest count', function() { + expect(requests.length).to.equal(1) + }) + + it('bidRequest method', function() { + expect(request.method).to.equal('POST') + }) + + it('bidRequest url', function() { + expect(request.url).to.equal('http://localhost:8086/bid?p=exists&profile_id=1') + }) + + it('bidRequest data', function() { + const data = request.data + expect(data.aid).to.be.eql(validBidRequests[0].params.aid) + expect(data.id).to.be.eql(validBidRequests[0].bidId) + expect(data.sizes).to.be.eql(validBidRequests[0].sizes) + }) + + describe('bidRequest advanced', function() { + const bidderRequestEmptyParamsAndExtParams = { + bidder: 'videonow', + params: { + pId: '1', + }, + ext: { + p1: 'ext1', + p2: 'ext2', + }, + } + + it('bidRequest count', function() { + const requests = spec.buildRequests([bidderRequestEmptyParamsAndExtParams], bidderRequest) + expect(requests.length).to.equal(1) + }) + + it('bidRequest default url', function() { + const requests = spec.buildRequests([bidderRequestEmptyParamsAndExtParams], bidderRequest) + const request = (requests && requests.length && requests[0]) || {} + expect(request.url).to.equal('https://bidder.videonow.ru/prebid?profile_id=1') + }) + + it('bidRequest default currency', function() { + const requests = spec.buildRequests([bidderRequestEmptyParamsAndExtParams], bidderRequest) + const request = (requests && requests.length && requests[0]) || {} + const data = (request && request.data) || {} + expect(data.cur).to.equal('RUB') + }) + + it('bidRequest ext parameters ', function() { + const requests = spec.buildRequests([bidderRequestEmptyParamsAndExtParams], bidderRequest) + const request = (requests && requests.length && requests[0]) || {} + const data = (request && request.data) || {} + expect(data['ext_p1']).to.equal('ext1') + expect(data['ext_p2']).to.equal('ext2') + }) + + it('bidRequest without params', function() { + const bidderReq = { + bidder: 'videonow', + } + const requests = spec.buildRequests([bidderReq], bidderRequest) + expect(requests.length).to.equal(1) + }) + }) + }) + + describe('onBidWon', function() { + const cpm = 10 + const nurl = 'http://fakedomain.nld?price=${AUCTION_PRICE}' + const imgSrc = replaceAuctionPrice(nurl, cpm) + const foundPixels = () => window.document.body.querySelectorAll(`img[src="${imgSrc}"]`) + + it('Should not create nurl pixel if bid is undefined', function() { + spec.onBidWon() + expect(foundPixels().length).to.equal(0) + }) + + it('Should not create nurl pixel if bid does not contains nurl', function() { + spec.onBidWon({}) + expect(foundPixels().length).to.equal(0) + }) + + it('Should create nurl pixel if bid nurl', function() { + spec.onBidWon({ nurl, cpm }) + expect(foundPixels().length).to.equal(1) + }) + }) + + describe('getUserSyncs', function() { + it('Should return an empty array if not get serverResponses', function() { + expect(spec.getUserSyncs({}).length).to.equal(0) + }) + + it('Should return an empty array if get serverResponses as empty array', function() { + expect(spec.getUserSyncs({}, []).length).to.equal(0) + }) + + it('Should return an empty array if serverResponses has no body', function() { + const serverResp = getValidServerResponse() + delete serverResp.body + const syncs = spec.getUserSyncs({}, [serverResp]) + expect(syncs.length).to.equal(0) + }) + + it('Should return an empty array if serverResponses has no ext', function() { + const serverResp = getValidServerResponse() + delete serverResp.body.ext + const syncs = spec.getUserSyncs({}, [serverResp]) + expect(syncs.length).to.equal(0) + }) + + it('Should return an array', function() { + const serverResp = getValidServerResponse() + const syncs = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, [serverResp]) + expect(syncs.length).to.equal(4) + }) + + it('Should return pixels', function() { + const serverResp = getValidServerResponse() + const syncs = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: true}, [serverResp]) + expect(syncs.length).to.equal(2) + expect(syncs[0].type).to.equal('image') + expect(syncs[1].type).to.equal('image') + }) + + it('Should return iframes', function() { + const serverResp = getValidServerResponse() + const syncs = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: false}, [serverResp]) + expect(syncs.length).to.equal(2) + expect(syncs[0].type).to.equal('iframe') + expect(syncs[1].type).to.equal('iframe') + }) + }) + + describe('interpretResponse', function() { + const bidRequest = { + method: 'POST', + url: 'http://localhost:8086/bid?profile_id=1', + data: { + id: '217b8ab59a18e8', + cpm: 10, + sizes: [[640, 480], [320, 200]], + cur: 'RUB', + placementId, + ref: 'http://localhost:8086/page', + }, + } + + it('Should have only one bid', function() { + const serverResponse = getValidServerResponse() + const result = spec.interpretResponse(serverResponse, bidRequest) + expect(result.length).to.equal(1) + }) + + it('Should have required keys', function() { + const serverResponse = getValidServerResponse() + const result = spec.interpretResponse(serverResponse, bidRequest) + const bid = serverResponse.body.seatbid[0].bid[0] + const res = result[0] + expect(res.requestId).to.be.eql(bidRequest.data.id) + expect(res.cpm).to.be.eql(bid.price) + expect(res.creativeId).to.be.eql(bid.crid) + expect(res.netRevenue).to.be.a('boolean') + expect(res.ttl).to.be.eql(bid.ttl) + expect(res.renderer).to.be.a('Object') + expect(res.renderer.render).to.be.a('function') + }) + + it('Should return an empty array if empty or no bids in response', function() { + expect(spec.interpretResponse({ body: '' }, {}).length).to.equal(0) + }) + + it('Should return an empty array if bidRequest\'s data is absent', function() { + const serverResponse = getValidServerResponse() + expect(spec.interpretResponse(serverResponse, undefined).length).to.equal(0) + }) + + it('Should return an empty array if bidRequest\'s data is not contains bidId ', function() { + const serverResponse = getValidServerResponse() + expect(spec.interpretResponse(serverResponse, { data: {} }).length).to.equal(0) + }) + + it('Should return an empty array if bidRequest\'s data bidId is undefined', function() { + const serverResponse = getValidServerResponse() + expect(spec.interpretResponse(serverResponse, { data: { id: null } }).length).to.equal(0) + }) + + it('Should return an empty array if serverResponse do not contains seatbid', function() { + expect(spec.interpretResponse({ body: {} }, bidRequest).length).to.equal(0) + }) + + it('Should return an empty array if serverResponse\'s seatbid is empty', function() { + expect(spec.interpretResponse({ body: { seatbid: [] } }, bidRequest).length).to.equal(0) + }) + + it('Should return an empty array if serverResponse\'s placementId is undefined', function() { + expect(spec.interpretResponse({ body: { seatbid: [1, 2] } }, bidRequest).length).to.equal(0) + }) + + it('Should return an empty array if serverResponse\'s id in the bid is undefined', function() { + const serverResp = getValidServerResponse() + delete serverResp.body.seatbid[0].bid[0].id + let res = spec.interpretResponse(serverResp, bidRequest) + expect(res.length).to.equal(0) + }) + + it('Should return an empty array if serverResponse\'s price in the bid is undefined', function() { + const serverResp = getValidServerResponse() + delete serverResp.body.seatbid[0].bid[0].price + const res = spec.interpretResponse(serverResp, bidRequest) + expect(res.length).to.equal(0) + }) + + it('Should return an empty array if serverResponse\'s price in the bid is 0', function() { + const serverResp = getValidServerResponse() + serverResp.body.seatbid[0].bid[0].price = 0 + const res = spec.interpretResponse(serverResp, bidRequest) + + expect(res.length).to.equal(0) + }) + + it('Should return an empty array if serverResponse\'s init in the bid\'s ext is undefined', function() { + const serverResp = getValidServerResponse() + delete serverResp.body.seatbid[0].bid[0].ext.init + const res = spec.interpretResponse(serverResp, bidRequest) + + expect(res.length).to.equal(0) + }) + + it('Should return an empty array if serverResponse\'s module in the bid\'s ext is undefined', function() { + const serverResp = getValidServerResponse() + delete serverResp.body.seatbid[0].bid[0].ext.module + const res = spec.interpretResponse(serverResp, bidRequest) + + expect(res.length).to.equal(0) + }) + + it('Should return an empty array if serverResponse\'s adm in the bid is undefined', function() { + const serverResp = getValidServerResponse() + delete serverResp.body.seatbid[0].bid[0].adm + const res = spec.interpretResponse(serverResp, bidRequest) + + expect(res.length).to.equal(0) + }) + + it('Should return an empty array if serverResponse\'s the bid\'s ext is undefined', function() { + const serverResp = getValidServerResponse() + delete serverResp.body.seatbid[0].bid[0].ext + const res = spec.interpretResponse(serverResp, bidRequest) + + expect(res.length).to.equal(0) + }) + + it('Default ttl is 300', function() { + const serverResp = getValidServerResponse() + delete serverResp.body.seatbid[0].bid[0].ttl + const res = spec.interpretResponse(serverResp, bidRequest) + expect(res.length).to.equal(1) + expect(res[0].ttl).to.equal(300) + }) + + it('Default netRevenue is true', function() { + const serverResp = getValidServerResponse() + delete serverResp.body.seatbid[0].bid[0].netRevenue + const res = spec.interpretResponse(serverResp, bidRequest) + expect(res.length).to.equal(1) + expect(res[0].netRevenue).to.be.true; + }) + + it('Default currency is RUB', function() { + const serverResp = getValidServerResponse() + delete serverResp.body.cur + const res = spec.interpretResponse(serverResp, bidRequest) + expect(res.length).to.equal(1) + expect(res[0].currency).to.equal('RUB') + }) + + describe('different module paths', function() { + beforeEach(function() { + window.localStorage && localStorage.setItem(LS_ITEM_NAME, '{}') + }) + + afterEach(function() { + const serverResp = getValidServerResponse() + const { module: { log, min }, init } = serverResp.body.seatbid[0].bid[0].ext + remove(init) + remove(log) + remove(min) + + function remove(src) { + if (!src) return + const d = document.querySelectorAll(`script[src^="${src}"]`) + d && d.length && Array.from(d).forEach(el => el && el.remove()) + } + }) + + it('should use prod module by default', function() { + const serverResp = getValidServerResponse() + const res = spec.interpretResponse(serverResp, bidRequest) + expect(res.length).to.equal(1) + + const renderer = res[0].renderer + expect(renderer).to.be.an('object') + expect(renderer.url).to.equal(serverResp.body.seatbid[0].bid[0].ext.module.min) + }) + + it('should use "log" module if "prod" is not exists', function() { + const serverResp = getValidServerResponse() + delete serverResp.body.seatbid[0].bid[0].ext.module.min + const res = spec.interpretResponse(serverResp, bidRequest) + expect(res.length).to.equal(1) + + const renderer = res[0].renderer + expect(renderer).to.be.an('object') + expect(renderer.url).to.equal(serverResp.body.seatbid[0].bid[0].ext.module.log) + }) + + it('should correct combine src for init', function() { + const serverResp = getValidServerResponse() + + const src = `${serverResp.body.seatbid[0].bid[0].ext.init}?profileId=1` + const placementElement = document.createElement('div') + placementElement.setAttribute('id', placementId) + + const resp = spec.interpretResponse(serverResp, bidRequest) + expect(resp.length).to.equal(1) + + const renderer = resp[0].renderer + expect(renderer).to.be.an('object') + + document.body.appendChild(placementElement) + + renderer.render() + + const res = document.querySelectorAll(`script[src="${src}"]`) + expect(res.length).to.equal(1) + }) + + it('should correct combine src for init if init url contains "?"', function() { + const serverResp = getValidServerResponse() + + serverResp.body.seatbid[0].bid[0].ext.init += '?div=1' + const src = `${serverResp.body.seatbid[0].bid[0].ext.init}&profileId=1` + + const placementElement = document.createElement('div') + placementElement.setAttribute('id', placementId) + + const resp = spec.interpretResponse(serverResp, bidRequest) + expect(resp.length).to.equal(1) + + const renderer = resp[0].renderer + expect(renderer).to.be.an('object') + + document.body.appendChild(placementElement) + + renderer.render() + + const res = document.querySelectorAll(`script[src="${src}"]`) + expect(res.length).to.equal(1) + }) + }) + + describe('renderer object', function() { + it('execute renderer.render() should create window.videonow object', function() { + const serverResp = getValidServerResponse() + const res = spec.interpretResponse(serverResp, bidRequest) + expect(res.length).to.equal(1) + + const renderer = res[0].renderer + expect(renderer).to.be.an('object') + expect(renderer.render).to.a('function') + + const doc = window.document + const placementElement = doc.createElement('div') + placementElement.setAttribute('id', placementId) + doc.body.appendChild(placementElement) + + renderer.render() + expect(window.videonow).to.an('object') + }) + }) + + it('execute renderer.render() should not create window.videonow object if placement element not found', function() { + const serverResp = getValidServerResponse() + const res = spec.interpretResponse(serverResp, bidRequest) + expect(res.length).to.equal(1) + + const renderer = res[0].renderer + expect(renderer).to.be.an('object') + expect(renderer.render).to.a('function') + + renderer.render() + expect(window.videonow).to.be.undefined + }) + }) + }) +}) diff --git a/test/spec/modules/videoreachBidAdapter_spec.js b/test/spec/modules/videoreachBidAdapter_spec.js new file mode 100644 index 00000000000..237821f7102 --- /dev/null +++ b/test/spec/modules/videoreachBidAdapter_spec.js @@ -0,0 +1,141 @@ +import {expect} from 'chai'; +import {spec} from 'modules/videoreachBidAdapter'; +import {newBidder} from 'src/adapters/bidderFactory'; + +const ENDPOINT_URL = '//a.videoreach.com/hb/'; + +describe('videoreachBidAdapter', function () { + describe('isBidRequestValid', function () { + let bid = { + 'params': { + 'TagId': 'ABCDE' + }, + 'bidId': '242d506d4e4f15', + 'bidderRequestId': '1893a2136a84a2', + 'auctionId': '8fb7b1c7-317b-4edf-83f0-c4669a318522', + 'transactionId': '85a2e190-0684-4f95-ad32-6c90757ed622' + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not passed', function () { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'TagId': '' + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + let bidRequests = [ + { + 'bidder': 'videoreach', + 'params': { + 'TagId': 'ABCDE' + }, + 'adUnitCode': 'adzone', + 'auctionId': '8fb7b1c7-317b-4edf-83f0-c4669a318522', + 'sizes': [[1, 1]], + 'bidId': '242d506d4e4f15', + 'bidderRequestId': '1893a2136a84a2', + 'transactionId': '85a2e190-0684-4f95-ad32-6c90757ed622', + 'mediaTypes': { + 'banner': { + 'sizes': [1, 1] + }, + } + } + ]; + + it('send bid request to endpoint', function () { + const request = spec.buildRequests(bidRequests); + + expect(request.url).to.equal(ENDPOINT_URL); + expect(request.method).to.equal('POST'); + }); + + it('send bid request with GDPR to endpoint', function () { + let consentString = 'BOEFEAyOEFEAyAHABDENAI4AAAB9vABAASA'; + + let bidderRequest = { + 'gdprConsent': { + 'consentString': consentString, + 'gdprApplies': true + } + }; + + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.gdpr.consent_required).to.exist; + expect(payload.gdpr.consent_string).to.equal(consentString); + }); + }); + + describe('interpretResponse', function () { + let serverResponse = + { + 'body': { + 'responses': [{ + 'bidId': '242d506d4e4f15', + 'transactionId': '85a2e190-0684-4f95-ad32-6c90757ed622', + 'cpm': 10.0, + 'width': '1', + 'height': '1', + 'ad': '', + 'ttl': 360, + 'creativeId': '5cb5dc9375c0e', + 'netRevenue': true, + 'currency': 'EUR', + 'sync': ['https:\/\/SYNC_URL'] + }] + } + }; + + it('should handle response', function() { + let expectedResponse = [ + { + cpm: 10.0, + width: '1', + height: '1', + currency: 'EUR', + netRevenue: true, + ttl: 360, + ad: '', + requestId: '242d506d4e4f15', + creativeId: '5cb5dc9375c0e' + } + ]; + + let result = spec.interpretResponse(serverResponse); + expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedResponse[0])); + }); + + it('should handles empty response', function() { + let serverResponse = { + 'body': { + 'responses': [] + } + }; + + let result = spec.interpretResponse(serverResponse); + expect(result.length).to.equal(0); + }); + + describe('getUserSyncs', () => { + it('should push user sync images if enabled', () => { + const syncOptions = { pixelEnabled: true }; + const syncs = spec.getUserSyncs(syncOptions, [serverResponse]); + + expect(syncs[0]).to.deep.equal({ + type: 'image', + url: 'https://SYNC_URL' + }); + }) + }); + }); +}); diff --git a/test/spec/modules/visxBidAdapter_spec.js b/test/spec/modules/visxBidAdapter_spec.js index bf8d4cc7d13..167d54e37a6 100755 --- a/test/spec/modules/visxBidAdapter_spec.js +++ b/test/spec/modules/visxBidAdapter_spec.js @@ -40,6 +40,12 @@ describe('VisxAdapter', function () { }); describe('buildRequests', function () { + const bidderRequest = { + refererInfo: { + referer: 'http://example.com' + } + }; + const referrer = bidderRequest.refererInfo.referer; let bidRequests = [ { 'bidder': 'visx', @@ -58,7 +64,7 @@ describe('VisxAdapter', function () { 'uid': '903535' }, 'adUnitCode': 'adunit-code-2', - 'sizes': [[728, 90]], + 'sizes': [[728, 90], [300, 250]], 'bidId': '3150ccb55da321', 'bidderRequestId': '22edbae2733bf6', 'auctionId': '1d1a030790a475', @@ -77,78 +83,100 @@ describe('VisxAdapter', function () { ]; it('should attach valid params to the tag', function () { - const request = spec.buildRequests([bidRequests[0]]); + const request = spec.buildRequests([bidRequests[0]], bidderRequest); const payload = request.data; expect(payload).to.be.an('object'); - expect(payload).to.have.property('u').that.is.a('string'); + expect(payload).to.have.property('u', referrer); expect(payload).to.have.property('pt', 'net'); expect(payload).to.have.property('auids', '903535'); + expect(payload).to.have.property('sizes', '300x250,300x600'); expect(payload).to.have.property('r', '22edbae2733bf6'); expect(payload).to.have.property('cur', 'EUR'); }); - it('auids must not be duplicated', function () { - const request = spec.buildRequests(bidRequests); + it('sizes must not be duplicated', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); const payload = request.data; expect(payload).to.be.an('object'); - expect(payload).to.have.property('u').that.is.a('string'); + expect(payload).to.have.property('u', referrer); expect(payload).to.have.property('pt', 'net'); - expect(payload).to.have.property('auids', '903535,903536'); + expect(payload).to.have.property('auids', '903535,903535,903536'); + expect(payload).to.have.property('sizes', '300x250,300x600,728x90'); expect(payload).to.have.property('r', '22edbae2733bf6'); expect(payload).to.have.property('cur', 'EUR'); }); - it('pt parameter must be "gross" if params.priceType === "gross"', function () { + it('pt parameter must be "net" if params.priceType === "gross"', function () { bidRequests[1].params.priceType = 'gross'; - const request = spec.buildRequests(bidRequests); + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = request.data; + expect(payload).to.be.an('object'); + expect(payload).to.have.property('u', referrer); + expect(payload).to.have.property('pt', 'net'); + expect(payload).to.have.property('auids', '903535,903535,903536'); + expect(payload).to.have.property('sizes', '300x250,300x600,728x90'); + expect(payload).to.have.property('r', '22edbae2733bf6'); + expect(payload).to.have.property('cur', 'EUR'); + delete bidRequests[1].params.priceType; + }); + it('pt parameter must be "net" if params.priceType === "net"', function () { + bidRequests[1].params.priceType = 'net'; + const request = spec.buildRequests(bidRequests, bidderRequest); const payload = request.data; expect(payload).to.be.an('object'); - expect(payload).to.have.property('u').that.is.a('string'); - expect(payload).to.have.property('pt', 'gross'); - expect(payload).to.have.property('auids', '903535,903536'); + expect(payload).to.have.property('u', referrer); + expect(payload).to.have.property('pt', 'net'); + expect(payload).to.have.property('auids', '903535,903535,903536'); + expect(payload).to.have.property('sizes', '300x250,300x600,728x90'); expect(payload).to.have.property('r', '22edbae2733bf6'); expect(payload).to.have.property('cur', 'EUR'); delete bidRequests[1].params.priceType; }); - it('pt parameter must be "net" or "gross"', function () { - bidRequests[1].params.priceType = 'some'; - const request = spec.buildRequests(bidRequests); + it('pt parameter must be "net" if params.priceType === "undefined"', function () { + bidRequests[1].params.priceType = 'undefined'; + const request = spec.buildRequests(bidRequests, bidderRequest); const payload = request.data; expect(payload).to.be.an('object'); - expect(payload).to.have.property('u').that.is.a('string'); + expect(payload).to.have.property('u', referrer); expect(payload).to.have.property('pt', 'net'); - expect(payload).to.have.property('auids', '903535,903536'); + expect(payload).to.have.property('auids', '903535,903535,903536'); + expect(payload).to.have.property('sizes', '300x250,300x600,728x90'); expect(payload).to.have.property('r', '22edbae2733bf6'); expect(payload).to.have.property('cur', 'EUR'); delete bidRequests[1].params.priceType; }); + it('should add currency from currency.bidderCurrencyDefault', function () { const getConfigStub = sinon.stub(config, 'getConfig').callsFake( - arg => arg === 'currency.bidderCurrencyDefault.visx' ? 'JPY' : 'USD'); - const request = spec.buildRequests(bidRequests); + arg => arg === 'currency.bidderCurrencyDefault.visx' ? 'GBP' : 'USD'); + const request = spec.buildRequests(bidRequests, bidderRequest); const payload = request.data; expect(payload).to.be.an('object'); - expect(payload).to.have.property('u').that.is.a('string'); + expect(payload).to.have.property('u', referrer); expect(payload).to.have.property('pt', 'net'); - expect(payload).to.have.property('auids', '903535,903536'); + expect(payload).to.have.property('auids', '903535,903535,903536'); + expect(payload).to.have.property('sizes', '300x250,300x600,728x90'); expect(payload).to.have.property('r', '22edbae2733bf6'); - expect(payload).to.have.property('cur', 'JPY'); + expect(payload).to.have.property('cur', 'GBP'); getConfigStub.restore(); }); + it('should add currency from currency.adServerCurrency', function () { const getConfigStub = sinon.stub(config, 'getConfig').callsFake( arg => arg === 'currency.bidderCurrencyDefault.visx' ? '' : 'USD'); - const request = spec.buildRequests(bidRequests); + const request = spec.buildRequests(bidRequests, bidderRequest); const payload = request.data; expect(payload).to.be.an('object'); - expect(payload).to.have.property('u').that.is.a('string'); + expect(payload).to.have.property('u', referrer); expect(payload).to.have.property('pt', 'net'); - expect(payload).to.have.property('auids', '903535,903536'); + expect(payload).to.have.property('auids', '903535,903535,903536'); + expect(payload).to.have.property('sizes', '300x250,300x600,728x90'); expect(payload).to.have.property('r', '22edbae2733bf6'); expect(payload).to.have.property('cur', 'USD'); getConfigStub.restore(); }); + it('if gdprConsent is present payload must have gdpr params', function () { const request = spec.buildRequests(bidRequests, {gdprConsent: {consentString: 'AAA', gdprApplies: true}}); const payload = request.data; @@ -176,10 +204,11 @@ describe('VisxAdapter', function () { describe('interpretResponse', function () { const responses = [ - {'bid': [{'price': 1.15, 'adm': '
    test content 1
    ', 'auid': 903535, 'h': 250, 'w': 300}], 'seat': '1'}, - {'bid': [{'price': 0.5, 'adm': '
    test content 2
    ', 'auid': 903536, 'h': 90, 'w': 728}], 'seat': '1'}, - {'bid': [{'price': 0, 'auid': 903536, 'h': 250, 'w': 300}], 'seat': '1'}, - {'bid': [{'price': 0, 'adm': '
    test content 4
    ', 'h': 250, 'w': 300}], 'seat': '1'}, + {'bid': [{'price': 1.15, 'adm': '
    test content 1
    ', 'auid': 903535, 'h': 250, 'w': 300, 'cur': 'EUR'}], 'seat': '1'}, + {'bid': [{'price': 0.5, 'adm': '
    test content 2
    ', 'auid': 903536, 'h': 600, 'w': 300, 'cur': 'EUR'}], 'seat': '1'}, + {'bid': [{'price': 0.15, 'adm': '
    test content 3
    ', 'auid': 903535, 'h': 90, 'w': 728, 'cur': 'EUR'}], 'seat': '1'}, + {'bid': [{'price': 0, 'auid': 903537, 'h': 250, 'w': 300, 'cur': 'EUR'}], 'seat': '1'}, + {'bid': [{'price': 0, 'adm': '
    test content 5
    ', 'h': 250, 'w': 300, 'cur': 'EUR'}], 'seat': '1'}, undefined, {'bid': [], 'seat': '1'}, {'seat': '1'}, @@ -209,6 +238,7 @@ describe('VisxAdapter', function () { 'width': 300, 'height': 250, 'ad': '
    test content 1
    ', + 'bidderCode': 'visx', 'currency': 'EUR', 'netRevenue': true, 'ttl': 360, @@ -265,37 +295,40 @@ describe('VisxAdapter', function () { 'width': 300, 'height': 250, 'ad': '
    test content 1
    ', + 'bidderCode': 'visx', 'currency': 'EUR', 'netRevenue': true, 'ttl': 360, }, { - 'requestId': '5703af74d0472a', - 'cpm': 1.15, - 'creativeId': 903535, + 'requestId': '4dff80cc4ee346', + 'cpm': 0.5, + 'creativeId': 903536, 'dealId': undefined, 'width': 300, - 'height': 250, - 'ad': '
    test content 1
    ', + 'height': 600, + 'ad': '
    test content 2
    ', + 'bidderCode': 'visx', 'currency': 'EUR', 'netRevenue': true, 'ttl': 360, }, { - 'requestId': '4dff80cc4ee346', - 'cpm': 0.5, - 'creativeId': 903536, + 'requestId': '5703af74d0472a', + 'cpm': 0.15, + 'creativeId': 903535, 'dealId': undefined, 'width': 728, 'height': 90, - 'ad': '
    test content 2
    ', + 'ad': '
    test content 3
    ', + 'bidderCode': 'visx', 'currency': 'EUR', 'netRevenue': true, 'ttl': 360, } ]; - const result = spec.interpretResponse({'body': {'seatbid': [responses[0], responses[1]]}}, request); + const result = spec.interpretResponse({'body': {'seatbid': responses.slice(0, 3)}}, request); expect(result).to.deep.equal(expectedResponse); }); @@ -313,7 +346,7 @@ describe('VisxAdapter', function () { 'auctionId': '1cbd2feafe5e8b', } ]; - const getConfigStub = sinon.stub(config, 'getConfig').returns('JPY'); + const getConfigStub = sinon.stub(config, 'getConfig').returns('PLN'); const request = spec.buildRequests(bidRequests); const expectedResponse = [ { @@ -324,13 +357,16 @@ describe('VisxAdapter', function () { 'width': 300, 'height': 250, 'ad': '
    test content 1
    ', - 'currency': 'JPY', + 'bidderCode': 'visx', + 'currency': 'PLN', 'netRevenue': true, 'ttl': 360, } ]; - const result = spec.interpretResponse({'body': {'seatbid': [responses[0]]}}, request); + const response = Object.assign({}, responses[0]); + Object.assign(response.bid[0], {'cur': 'PLN'}); + const result = spec.interpretResponse({'body': {'seatbid': [response]}}, request); expect(result).to.deep.equal(expectedResponse); getConfigStub.restore(); }); @@ -340,7 +376,7 @@ describe('VisxAdapter', function () { { 'bidder': 'visx', 'params': { - 'uid': '903536' + 'uid': '903537' }, 'adUnitCode': 'adunit-code-1', 'sizes': [[300, 250], [300, 600]], @@ -372,8 +408,220 @@ describe('VisxAdapter', function () { } ]; const request = spec.buildRequests(bidRequests); - const result = spec.interpretResponse({'body': {'seatbid': responses.slice(2)}}, request); + const result = spec.interpretResponse({'body': {'seatbid': responses.slice(3)}}, request); expect(result.length).to.equal(0); }); + + it('complicated case', function () { + const fullResponse = [ + {'bid': [{'price': 1.15, 'adm': '
    test content 1
    ', 'auid': 903535, 'h': 250, 'w': 300, 'cur': 'EUR'}], 'seat': '1'}, + {'bid': [{'price': 0.5, 'adm': '
    test content 2
    ', 'auid': 903536, 'h': 600, 'w': 300, 'cur': 'EUR'}], 'seat': '1'}, + {'bid': [{'price': 0.15, 'adm': '
    test content 3
    ', 'auid': 903535, 'h': 90, 'w': 728, 'cur': 'EUR'}], 'seat': '1'}, + {'bid': [{'price': 0.15, 'adm': '
    test content 4
    ', 'auid': 903535, 'h': 600, 'w': 300, 'cur': 'EUR'}], 'seat': '1'}, + {'bid': [{'price': 0.5, 'adm': '
    test content 5
    ', 'auid': 903536, 'h': 600, 'w': 350, 'cur': 'EUR'}], 'seat': '1'}, + ]; + const bidRequests = [ + { + 'bidder': 'visx', + 'params': { + 'uid': '903535' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '2164be6358b9', + 'bidderRequestId': '106efe3247', + 'auctionId': '32a1f276cb87cb8', + }, + { + 'bidder': 'visx', + 'params': { + 'uid': '903535' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '326bde7fbf69', + 'bidderRequestId': '106efe3247', + 'auctionId': '32a1f276cb87cb8', + }, + { + 'bidder': 'visx', + 'params': { + 'uid': '903536' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '4e111f1b66e4', + 'bidderRequestId': '106efe3247', + 'auctionId': '32a1f276cb87cb8', + }, + { + 'bidder': 'visx', + 'params': { + 'uid': '903535' + }, + 'adUnitCode': 'adunit-code-2', + 'sizes': [[728, 90]], + 'bidId': '26d6f897b516', + 'bidderRequestId': '106efe3247', + 'auctionId': '32a1f276cb87cb8', + }, + { + 'bidder': 'visx', + 'params': { + 'uid': '903536' + }, + 'adUnitCode': 'adunit-code-2', + 'sizes': [[728, 90]], + 'bidId': '1751cd90161', + 'bidderRequestId': '106efe3247', + 'auctionId': '32a1f276cb87cb8', + } + ]; + const request = spec.buildRequests(bidRequests); + const expectedResponse = [ + { + 'requestId': '2164be6358b9', + 'cpm': 1.15, + 'creativeId': 903535, + 'dealId': undefined, + 'width': 300, + 'height': 250, + 'ad': '
    test content 1
    ', + 'bidderCode': 'visx', + 'currency': 'EUR', + 'netRevenue': true, + 'ttl': 360, + }, + { + 'requestId': '4e111f1b66e4', + 'cpm': 0.5, + 'creativeId': 903536, + 'dealId': undefined, + 'width': 300, + 'height': 600, + 'ad': '
    test content 2
    ', + 'bidderCode': 'visx', + 'currency': 'EUR', + 'netRevenue': true, + 'ttl': 360, + }, + { + 'requestId': '26d6f897b516', + 'cpm': 0.15, + 'creativeId': 903535, + 'dealId': undefined, + 'width': 728, + 'height': 90, + 'ad': '
    test content 3
    ', + 'bidderCode': 'visx', + 'currency': 'EUR', + 'netRevenue': true, + 'ttl': 360, + }, + { + 'requestId': '326bde7fbf69', + 'cpm': 0.15, + 'creativeId': 903535, + 'dealId': undefined, + 'width': 300, + 'height': 600, + 'ad': '
    test content 4
    ', + 'bidderCode': 'visx', + 'currency': 'EUR', + 'netRevenue': true, + 'ttl': 360, + }, + { + 'requestId': '1751cd90161', + 'cpm': 0.5, + 'creativeId': 903536, + 'dealId': undefined, + 'width': 350, + 'height': 600, + 'ad': '
    test content 5
    ', + 'bidderCode': 'visx', + 'currency': 'EUR', + 'netRevenue': true, + 'ttl': 360, + } + ]; + + const result = spec.interpretResponse({'body': {'seatbid': fullResponse}}, request); + expect(result).to.deep.equal(expectedResponse); + }); + + it('dublicate uids and sizes in one slot', function () { + const fullResponse = [ + {'bid': [{'price': 1.15, 'adm': '
    test content 1
    ', 'auid': 903535, 'h': 250, 'w': 300, 'cur': 'EUR'}], 'seat': '1'}, + {'bid': [{'price': 0.5, 'adm': '
    test content 2
    ', 'auid': 903535, 'h': 250, 'w': 300, 'cur': 'EUR'}], 'seat': '1'}, + ]; + const bidRequests = [ + { + 'bidder': 'visx', + 'params': { + 'uid': '903535' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '5126e301f4be', + 'bidderRequestId': '171c5405a390', + 'auctionId': '35bcbc0f7e79c', + }, + { + 'bidder': 'visx', + 'params': { + 'uid': '903535' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '57b2ebe70e16', + 'bidderRequestId': '171c5405a390', + 'auctionId': '35bcbc0f7e79c', + }, + { + 'bidder': 'visx', + 'params': { + 'uid': '903535' + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '225fcd44b18c', + 'bidderRequestId': '171c5405a390', + 'auctionId': '35bcbc0f7e79c', + } + ]; + const request = spec.buildRequests(bidRequests); + const expectedResponse = [ + { + 'requestId': '5126e301f4be', + 'cpm': 1.15, + 'creativeId': 903535, + 'dealId': undefined, + 'width': 300, + 'height': 250, + 'ad': '
    test content 1
    ', + 'bidderCode': 'visx', + 'currency': 'EUR', + 'netRevenue': true, + 'ttl': 360, + }, + { + 'requestId': '57b2ebe70e16', + 'cpm': 0.5, + 'creativeId': 903535, + 'dealId': undefined, + 'width': 300, + 'height': 250, + 'ad': '
    test content 2
    ', + 'bidderCode': 'visx', + 'currency': 'EUR', + 'netRevenue': true, + 'ttl': 360, + } + ]; + + const result = spec.interpretResponse({'body': {'seatbid': fullResponse}}, request); + expect(result).to.deep.equal(expectedResponse); + }); }); }); diff --git a/test/spec/modules/vmgBidAdapter_spec.js b/test/spec/modules/vmgBidAdapter_spec.js new file mode 100644 index 00000000000..688c03577fd --- /dev/null +++ b/test/spec/modules/vmgBidAdapter_spec.js @@ -0,0 +1,98 @@ +import { expect } from 'chai'; +import { spec } from 'modules/vmgBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory'; + +describe('VmgAdapter', function () { + const adapter = newBidder(spec); + + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }) + + describe('isBidRequestValid', function () { + let bidRequest = { + adUnitCode: 'div-0', + auctionId: 'd69cdd3f-75e3-42dc-b313-e54c0a99c757', + bidId: '280e2eb8ac3891', + bidRequestsCount: 1, + bidder: 'vmg', + bidderRequestId: '14690d27b056c8', + mediaTypes: { + banner: { + sizes: [ [ 970, 250 ] ] + } + }, + sizes: [ 970, 250 ], + src: 'client', + transactionId: 'af62f065-dfa7-4564-8cb2-d277dc6069f2' + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }); + }) + + describe('buildRequests', function () { + let validBidRequests = [ + { + adUnitCode: 'div-0', + auctionId: 'd69cdd3f-75e3-42dc-b313-e54c0a99c757', + bidId: '280e2eb8ac3891', + bidRequestsCount: 1, + bidder: 'vmg', + bidderRequestId: '14690d27b056c8', + mediaTypes: { + banner: { + sizes: [ [ 970, 250 ] ] + } + }, + sizes: [ 970, 250 ], + src: 'client', + transactionId: 'af62f065-dfa7-4564-8cb2-d277dc6069f2' + } + ]; + + let bidderRequest = { + auctionId: 'd69cdd3f-75e3-42dc-b313-e54c0a99c757', + auctionStart: 1549316149227, + bidderCode: 'vmg', + bidderRequestId: '14690d27b056c8', + refererInfo: { + canonicalUrl: undefined, + numIframes: 0, + reachedTop: true, + referer: 'https://vmg.nyc/public_assets/adapt/prebid.html', + stack: [ 'https://vmg.nyc/public_assets/adapt/prebid.html' ] + }, + start: 1549316149229, + timeout: 1000 + }; + + it('buildRequests fires', function () { + let request = spec.buildRequests(validBidRequests, bidderRequest); + expect(request).to.exist; + expect(request.method).to.equal('POST'); + expect(request.data).to.exist; + }); + }) + + describe('interpretResponse', function () { + let serverResponse = {}; + serverResponse.body = { + 'div-0': ['test'] + }; + + var bidRequest = { + data: '[{"adUnitCode":"div-0","referer":"https://vmg.nyc/public_assets/adapt/prebid.html","bidId":"280e2eb8ac3891"}]', + method: 'POST', + url: 'https://predict.vmg.nyc' + }; + + it('interpresResponse fires', function () { + let bidResponses = spec.interpretResponse(serverResponse, bidRequest); + expect(bidResponses[0].dealId[0]).to.equal('test'); + }); + }); +}); diff --git a/test/spec/modules/vrtcalBidAdapter_spec.js b/test/spec/modules/vrtcalBidAdapter_spec.js new file mode 100644 index 00000000000..7b37e393575 --- /dev/null +++ b/test/spec/modules/vrtcalBidAdapter_spec.js @@ -0,0 +1,135 @@ +import {expect} from 'chai'; +import {spec} from '../../../modules/vrtcalBidAdapter'; + +describe('Vrtcal Adapter', function () { + let bid = { + bidId: 'bidID0001', + bidder: 'vrtcal', + bidderRequestId: 'brID0001', + auctionId: 'auID0001', + sizes: [[300, 250]], + transactionId: 'tid0001', + adUnitCode: 'vrtcal-test-adunit' + }; + + describe('isBidRequestValid', function () { + it('Should return true when base params as set', function () { + expect(spec.isBidRequestValid(bid)).to.be.true; + }); + it('Should return false when bid.bidId is blank', function () { + bid.bidId = ''; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + it('Should return false when bid.auctionId is blank', function () { + bid.auctionId = ''; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + }); + + describe('buildRequests', function () { + let serverRequests = spec.buildRequests([bid]); + + let serverRequest = serverRequests[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; + 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://rtb.vrtcal.com/bidder_prebid.vap?ssp=1804'); + }); + + 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('prebidJS', 'prebidAdUnitCode', 'id', 'imp', 'site', 'device'); + expect(data.prebidJS).to.not.equal(''); + expect(data.prebidAdUnitCode).to.not.equal(''); + }); + + it('Sets width and height based on existence of bid.mediaTypes.banner', function () { + let data = JSON.parse(serverRequest.data); + if (typeof (bid.mediaTypes) !== 'undefined' && typeof (bid.mediaTypes.banner) !== 'undefined' && typeof (bid.mediaTypes.banner.sizes) !== 'undefined') { + expect(data.imp[0].banner.w).to.equal(bid.mediaTypes.banner.sizes[0][0]); + expect(data.imp[0].banner.h).to.equal(bid.mediaTypes.banner.sizes[0][1]); + } else { + expect(data.imp[0].banner.w).to.equal(bid.sizes[0][0]); + expect(data.imp[0].banner.h).to.equal(bid.sizes[0][1]); + } + }); + + it('Returns empty data if no valid requests are passed', function () { + serverRequests = spec.buildRequests([]); + expect(serverRequests).to.be.an('array').that.is.empty; + }); + }); + + describe('interpretResponse', function () { + let bid = { + bidId: 'bidID0001', + bidder: 'vrtcal', + bidderRequestId: 'brID0001', + auctionId: 'auID0001', + sizes: [[300, 250]], + transactionId: 'tid0001', + adUnitCode: 'vrtcal-test-adunit' + }; + + let serverRequests = spec.buildRequests([bid]); + + let resObject = {body: {id: 'vrtcal-test-id', width: 300, height: 250, seatbid: [{bid: [{price: 3.0, w: 300, h: 250, crid: 'testcrid', adm: 'testad', nurl: 'https://vrtcal.com/faketracker'}]}], currency: 'USD', netRevenue: true, ttl: 900}}; + + let serverResponses = spec.interpretResponse(resObject, serverRequests); + + it('Returns an array of valid server responses if response object is valid', function () { + expect(serverResponses).to.be.an('array').that.is.not.empty; + for (let i = 0; i < serverResponses.length; i++) { + let dataItem = serverResponses[i]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'nurl'); + expect(dataItem.requestId).to.be.a('string'); + expect(dataItem.cpm).to.be.a('number'); + expect(dataItem.width).to.be.a('number'); + expect(dataItem.height).to.be.a('number'); + expect(dataItem.ad).to.be.a('string'); + expect(dataItem.ttl).to.be.a('number'); + expect(dataItem.creativeId).to.be.a('string'); + expect(dataItem.netRevenue).to.be.a('boolean'); + expect(dataItem.currency).to.be.a('string'); + expect(dataItem.nurl).to.be.a('string'); + } + + it('Returns an empty array if invalid response is passed', function () { + serverResponses = spec.interpretResponse('invalid_response'); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + }); + }); + + describe('onBidWon', function () { + let bid = { + bidId: '2dd581a2b6281d', + bidder: 'vrtcal', + bidderRequestId: '145e1d6a7837c9', + auctionId: '74f78609-a92d-4cf1-869f-1b244bbfb5d2', + sizes: [[300, 250]], + transactionId: '3bb2f6da-87a6-4029-aeb0-bfe951372e62', + adUnitCode: 'vrtcal-test-adunit' + }; + + let serverRequests = spec.buildRequests([bid]); + let resObject = {body: {id: 'vrtcal-test-id', width: 300, height: 250, seatbid: [{bid: [{price: 3.0, w: 300, h: 250, crid: 'testcrid', adm: 'testad', nurl: 'https://vrtcal.com/faketracker'}]}], currency: 'USD', netRevenue: true, ttl: 900}}; + let serverResponses = spec.interpretResponse(resObject, serverRequests); + let wonbid = serverResponses[0]; + + it('Returns true is nurl is good/not blank', function () { + expect(wonbid.nurl).to.not.equal(''); + expect(spec.onBidWon(wonbid)).to.be.true; + }); + }); +}); diff --git a/test/spec/modules/vubleAnalyticsAdapter_spec.js b/test/spec/modules/vubleAnalyticsAdapter_spec.js index fe84c0a6b04..841a53c6dee 100644 --- a/test/spec/modules/vubleAnalyticsAdapter_spec.js +++ b/test/spec/modules/vubleAnalyticsAdapter_spec.js @@ -1,7 +1,7 @@ import vubleAnalytics from 'modules/vubleAnalyticsAdapter'; import { expect } from 'chai'; let events = require('src/events'); -let adaptermanager = require('src/adaptermanager'); +let adapterManager = require('src/adapterManager').default; let constants = require('src/constants.json'); describe('Vuble Prebid Analytic', function () { @@ -25,12 +25,12 @@ describe('Vuble Prebid Analytic', function () { events.getEvents.restore(); }); it('should catch all events', function () { - adaptermanager.registerAnalyticsAdapter({ + adapterManager.registerAnalyticsAdapter({ code: 'vuble', adapter: vubleAnalytics }); - adaptermanager.enableAnalytics({ + adapterManager.enableAnalytics({ provider: 'vuble', options: { pubId: 18, diff --git a/test/spec/modules/vubleBidAdapter_spec.js b/test/spec/modules/vubleBidAdapter_spec.js index 8996c1b4957..b38ad8f8584 100644 --- a/test/spec/modules/vubleBidAdapter_spec.js +++ b/test/spec/modules/vubleBidAdapter_spec.js @@ -280,4 +280,56 @@ describe('VubleAdapter', function () { expect(adapter.getUserSyncs(syncOptions, [response])).to.deep.equal([result]); }) }); + + describe('Check outstream scenario with renderer', function () { + // bid Request + let bid = { + data: { + context: 'outstream', + env: 'net', + width: '640', + height: '360', + pub_id: '3', + zone_id: '12345', + bid_id: 'abdc', + floor_price: 5.50, // optional + adUnitCode: 'code' + }, + method: 'POST', + url: '//player.mediabong.net/prebid/request' + }; + // Server's response + let response = { + body: { + status: 'ok', + cpm: 5.00, + creativeId: '2468', + url: 'https//player.mediabong.net/prebid/ad/a1b2c3d4', + dealId: 'MDB-TEST-1357', + renderer_id: 0, + renderer_url: 'vuble_renderer.js', + content: 'test' + } + }; + + let adResponse = { + ad: { + video: { + content: 'test' + } + } + }; + let adUnitCode = 'code'; + let rendererUrl = 'vuble_renderer.js'; + let rendererId = 0; + + let formattedResponses = adapter.interpretResponse(response, bid); + it('should equal to the expected format result', function () { + expect(formattedResponses[0].adResponse).to.deep.equal(adResponse); + expect(formattedResponses[0].adUnitCode).to.deep.equal(adUnitCode); + expect(formattedResponses[0].renderer.url).to.equal(rendererUrl); + expect(formattedResponses[0].renderer.id).to.equal(rendererId); + expect(formattedResponses[0].renderer.render).to.exist.and.to.be.a('function'); + }); + }); }); diff --git a/test/spec/modules/widespaceBidAdapter_spec.js b/test/spec/modules/widespaceBidAdapter_spec.js index dc0d547d47a..b3884a90b84 100644 --- a/test/spec/modules/widespaceBidAdapter_spec.js +++ b/test/spec/modules/widespaceBidAdapter_spec.js @@ -144,6 +144,35 @@ describe('+widespaceAdatperTest', function () { const request = spec.buildRequests(bidRequest, bidderRequest); const UrlRegExp = /^((ftp|http|https):)?\/\/[^ "]+$/; + let fakeLocalStorage = {}; + let lsSetStub; + let lsGetStub; + let lsRemoveStub; + + beforeEach(function() { + lsSetStub = sinon.stub(window.localStorage, 'setItem').callsFake(function (name, value) { + fakeLocalStorage[name] = value; + }); + + lsGetStub = sinon.stub(window.localStorage, 'getItem').callsFake(function (key) { + return fakeLocalStorage[key] || null; + }); + + lsRemoveStub = sinon.stub(window.localStorage, 'removeItem').callsFake(function (key) { + if (key && (fakeLocalStorage[key] !== null || fakeLocalStorage[key] !== undefined)) { + delete fakeLocalStorage[key]; + } + return true; + }); + }); + + afterEach(function() { + lsSetStub.restore(); + lsGetStub.restore(); + lsRemoveStub.restore(); + fakeLocalStorage = {}; + }); + it('-bidRequest method is POST', function () { expect(request[0].method).to.equal('POST'); }); @@ -153,7 +182,7 @@ describe('+widespaceAdatperTest', function () { }); it('-bidRequest data exist', function () { - expect(request[0].data).to.exists; + expect(request[0].data).to.exist; }); it('-bidRequest data is form data', function () { @@ -161,7 +190,7 @@ describe('+widespaceAdatperTest', function () { }); it('-bidRequest options have header type', function () { - expect(request[0].options.contentType).to.exists; + expect(request[0].options.contentType).to.exist; }); it('-cookie test for wsCustomData ', function () { diff --git a/test/spec/modules/yieldNexusBidAdapter_spec.js b/test/spec/modules/yieldNexusBidAdapter_spec.js deleted file mode 100644 index b966d890e7a..00000000000 --- a/test/spec/modules/yieldNexusBidAdapter_spec.js +++ /dev/null @@ -1,310 +0,0 @@ -import { expect } from 'chai'; -import { spec } from 'modules/yieldNexusBidAdapter'; -import * as utils from 'src/utils'; - -const spid = '123'; - -describe('YieldNexusAdapter', () => { - describe('isBidRequestValid', () => { - it('should validate supply', () => { - expect(spec.isBidRequestValid({ params: {} })).to.equal(false); - expect(spec.isBidRequestValid({ params: { spid: 123 } })).to.equal(false); - expect(spec.isBidRequestValid({ params: { spid: '123' } })).to.equal(true); - }); - it('should validate bid floor', () => { - expect(spec.isBidRequestValid({ params: { spid: '123' } })).to.equal(true); // bidfloor has a default - expect(spec.isBidRequestValid({ params: { spid: '123', bidfloor: '123' } })).to.equal(false); - expect(spec.isBidRequestValid({ params: { spid: '123', bidfloor: 0.1 } })).to.equal(true); - }); - it('should validate adpos', () => { - expect(spec.isBidRequestValid({ params: { spid: '123' } })).to.equal(true); // adpos has a default - expect(spec.isBidRequestValid({ params: { spid: '123', adpos: '123' } })).to.equal(false); - expect(spec.isBidRequestValid({ params: { spid: '123', adpos: 0.1 } })).to.equal(true); - }); - it('should validate instl', () => { - expect(spec.isBidRequestValid({ params: { spid: '123' } })).to.equal(true); // adpos has a default - expect(spec.isBidRequestValid({ params: { spid: '123', instl: '123' } })).to.equal(false); - expect(spec.isBidRequestValid({ params: { spid: '123', instl: -1 } })).to.equal(false); - expect(spec.isBidRequestValid({ params: { spid: '123', instl: 0 } })).to.equal(true); - expect(spec.isBidRequestValid({ params: { spid: '123', instl: 1 } })).to.equal(true); - expect(spec.isBidRequestValid({ params: { spid: '123', instl: 2 } })).to.equal(false); - }); - }); - describe('buildRequests', () => { - const bidRequest = { - 'adUnitCode': 'adunit-code', - 'auctionId': 'fdkhjg3s7ahjja', - 'mediaTypes': { - banner: {} - }, - 'params': { spid }, - 'sizes': [ [ 300, 250 ], [ 300, 600 ] ] - }; - - it('returns an array', () => { - let response; - - response = spec.buildRequests([]); - expect(Array.isArray(response)).to.equal(true); - expect(response.length).to.equal(0); - - response = spec.buildRequests([ bidRequest ]); - expect(Array.isArray(response)).to.equal(true); - expect(response.length).to.equal(1); - - const adUnit1 = Object.assign({}, utils.deepClone(bidRequest), { auctionId: '1', adUnitCode: 'a' }); - const adUnit2 = Object.assign({}, utils.deepClone(bidRequest), { auctionId: '1', adUnitCode: 'b' }); - response = spec.buildRequests([adUnit1, adUnit2]); - expect(Array.isArray(response)).to.equal(true); - expect(response.length).to.equal(2); - }); - - it('uses yieldnexus dns', () => { - const response = spec.buildRequests([ bidRequest ])[ 0 ]; - expect(response.method).to.equal('POST'); - expect(response.url).to.match(new RegExp(`^https://ssp\\.ynxs\\.io/r/${spid}/bidr\\?bidder=prebid&rformat=open_rtb&reqformat=rtb_json$`, 'g')); - expect(response.data.id).to.equal(bidRequest.auctionId); - }); - - it('builds request correctly', () => { - let stub = sinon.stub(utils, 'getTopWindowUrl').returns('http://www.test.com/page.html'); - - let response; - response = spec.buildRequests([ bidRequest ])[ 0 ]; - expect(response.data.site.domain).to.equal('www.test.com'); - expect(response.data.site.page).to.equal('http://www.test.com/page.html'); - expect(response.data.site.ref).to.equal(''); - expect(response.data.imp.length).to.equal(1); - expect(response.data.imp[ 0 ].id).to.equal(bidRequest.transactionId); - expect(response.data.imp[ 0 ].instl).to.equal(0); - expect(response.data.imp[ 0 ].tagid).to.equal(bidRequest.adUnitCode); - expect(response.data.imp[ 0 ].bidfloor).to.equal(0); - expect(response.data.imp[ 0 ].bidfloorcur).to.equal('USD'); - - const bidRequestWithInstlEquals1 = utils.deepClone(bidRequest); - bidRequestWithInstlEquals1.params.instl = 1; - response = spec.buildRequests([ bidRequestWithInstlEquals1 ])[ 0 ]; - expect(response.data.imp[ 0 ].instl).to.equal(bidRequestWithInstlEquals1.params.instl); - - const bidRequestWithInstlEquals0 = utils.deepClone(bidRequest); - bidRequestWithInstlEquals0.params.instl = 1; - response = spec.buildRequests([ bidRequestWithInstlEquals0 ])[ 0 ]; - expect(response.data.imp[ 0 ].instl).to.equal(bidRequestWithInstlEquals0.params.instl); - - const bidRequestWithBidfloorEquals1 = utils.deepClone(bidRequest); - bidRequestWithBidfloorEquals1.params.bidfloor = 1; - response = spec.buildRequests([ bidRequestWithBidfloorEquals1 ])[ 0 ]; - expect(response.data.imp[ 0 ].bidfloor).to.equal(bidRequestWithBidfloorEquals1.params.bidfloor); - - stub.restore(); - }); - - it('builds request banner object correctly', () => { - let response; - - const bidRequestWithBanner = utils.deepClone(bidRequest); - bidRequestWithBanner.mediaTypes = { - banner: { - sizes: [ [ 300, 250 ], [ 120, 600 ] ] - } - }; - - response = spec.buildRequests([ bidRequestWithBanner ])[ 0 ]; - expect(response.data.imp[ 0 ].banner.w).to.equal(bidRequestWithBanner.mediaTypes.banner.sizes[ 0 ][ 0 ]); - expect(response.data.imp[ 0 ].banner.h).to.equal(bidRequestWithBanner.mediaTypes.banner.sizes[ 0 ][ 1 ]); - expect(response.data.imp[ 0 ].banner.pos).to.equal(0); - expect(response.data.imp[ 0 ].banner.topframe).to.equal(0); - - const bidRequestWithPosEquals1 = utils.deepClone(bidRequestWithBanner); - bidRequestWithPosEquals1.params.pos = 1; - response = spec.buildRequests([ bidRequestWithPosEquals1 ])[ 0 ]; - expect(response.data.imp[ 0 ].banner.pos).to.equal(bidRequestWithPosEquals1.params.pos); - }); - - it('builds request video object correctly', () => { - let response; - - const bidRequestWithVideo = utils.deepClone(bidRequest); - bidRequestWithVideo.mediaTypes = { - video: { - sizes: [ [ 300, 250 ], [ 120, 600 ] ] - } - }; - - response = spec.buildRequests([ bidRequestWithVideo ])[ 0 ]; - expect(response.data.imp[ 0 ].video.w).to.equal(bidRequestWithVideo.mediaTypes.video.sizes[ 0 ][ 0 ]); - expect(response.data.imp[ 0 ].video.h).to.equal(bidRequestWithVideo.mediaTypes.video.sizes[ 0 ][ 1 ]); - expect(response.data.imp[ 0 ].video.pos).to.equal(0); - expect(response.data.imp[ 0 ].video.topframe).to.equal(0); - - const bidRequestWithPosEquals1 = utils.deepClone(bidRequestWithVideo); - bidRequestWithPosEquals1.params.pos = 1; - response = spec.buildRequests([ bidRequestWithPosEquals1 ])[ 0 ]; - expect(response.data.imp[ 0 ].video.pos).to.equal(bidRequestWithPosEquals1.params.pos); - }); - }); - describe('interpretResponse', () => { - const bannerBidRequest = { - 'adUnitCode': 'adunit-code', - 'auctionId': 'fdkhjg3s7ahjja', - 'mediaTypes': { - banner: {} - }, - 'params': { - 'spid': spid - }, - 'sizes': [ [ 300, 250 ], [ 300, 600 ] ], - 'bidId': '111' - }; - const videoBidRequest = { - 'adUnitCode': 'adunit-code', - 'auctionId': 'fdkhjg3s7ahjja', - 'mediaTypes': { - video: {} - }, - 'params': { - 'spid': spid - }, - 'sizes': [ [ 300, 250 ], [ 300, 600 ] ], - 'bidId': '111' - }; - const rtbResponse = { - 'id': 'imp_5b05b9fde4b09084267a556f', - 'bidid': 'imp_5b05b9fde4b09084267a556f', - 'cur': 'USD', - 'ext': { - 'utrk': [ - { 'type': 'iframe', 'url': '//ssp.ynxs.io/user/sync/1' }, - { 'type': 'image', 'url': '//ssp.ynxs.io/user/sync/2' } - ] - }, - 'seatbid': [ - { - 'seat': 'testSeatBidA', - 'bid': [ - { - 'id': '0', - 'impid': '1', - 'price': 2.016, - 'adm': '', - 'adomain': [ 'nike.com' ], - 'h': 600, - 'w': 120, - 'ext': { - 'vast_url': 'http://vast.tag.com', - 'utrk': [ - { 'type': 'iframe', 'url': '//pix.usersync.io/user-sync' } - ] - } - } - ] - }, - { - 'seat': 'testSeatBidB', - 'bid': [ - { - 'id': '1', - 'impid': '1', - 'price': 3, - 'adid': '542jlhdfd2112jnjf3x', - 'adm': '', - 'adomain': [ 'adidas.com' ], - 'h': 250, - 'w': 300, - 'ext': { - 'utrk': [ - { 'type': 'image', 'url': '//pix.usersync.io/user-sync' } - ] - } - } - ] - } - ] - }; - it('fails gracefully on empty response body', () => { - let response; - - response = spec.interpretResponse(undefined, { bidRequest: bannerBidRequest }); - expect(Array.isArray(response)).to.equal(true); - expect(response.length).to.equal(0); - - response = spec.interpretResponse({}, { bidRequest: bannerBidRequest }); - expect(Array.isArray(response)).to.equal(true); - expect(response.length).to.equal(0); - }); - it('collects banner bids', () => { - const response = spec.interpretResponse({ body: rtbResponse }, { bidRequest: bannerBidRequest }); - expect(Array.isArray(response)).to.equal(true); - expect(response.length).to.equal(2); - - const ad0 = response[ 0 ], ad1 = response[ 1 ]; - expect(ad0.requestId).to.equal(bannerBidRequest.bidId); - expect(ad0.cpm).to.equal(rtbResponse.seatbid[ 0 ].bid[ 0 ].price); - expect(ad0.width).to.equal(rtbResponse.seatbid[ 0 ].bid[ 0 ].w); - expect(ad0.height).to.equal(rtbResponse.seatbid[ 0 ].bid[ 0 ].h); - expect(ad0.ttl).to.equal(15 * 60); - expect(ad0.creativeId).to.equal(rtbResponse.seatbid[ 0 ].bid[ 0 ].crid); - expect(ad0.netRevenue).to.equal(true); - expect(ad0.currency).to.equal(rtbResponse.seatbid[ 0 ].bid[ 0 ].cur || rtbResponse.cur || 'USD'); - expect(ad0.ad).to.equal(rtbResponse.seatbid[ 0 ].bid[ 0 ].adm); - expect(ad0.vastXml).to.be.an('undefined'); - - expect(ad1.requestId).to.equal(bannerBidRequest.bidId); - expect(ad1.cpm).to.equal(rtbResponse.seatbid[ 1 ].bid[ 0 ].price); - expect(ad1.width).to.equal(rtbResponse.seatbid[ 1 ].bid[ 0 ].w); - expect(ad1.height).to.equal(rtbResponse.seatbid[ 1 ].bid[ 0 ].h); - expect(ad1.ttl).to.equal(15 * 60); - expect(ad1.creativeId).to.equal(rtbResponse.seatbid[ 1 ].bid[ 0 ].crid); - expect(ad1.netRevenue).to.equal(true); - expect(ad1.currency).to.equal(rtbResponse.seatbid[ 1 ].bid[ 0 ].cur || rtbResponse.cur || 'USD'); - expect(ad1.ad).to.equal(rtbResponse.seatbid[ 1 ].bid[ 0 ].adm); - expect(ad1.vastXml).to.be.an('undefined'); - - // expect(ad1.ad).to.be.an('undefined'); - // expect(ad1.vastXml).to.equal(rtbResponse.seatbid[ 1 ].bid[ 0 ].adm); - }); - it('collects video bids', () => { - const response = spec.interpretResponse({ body: rtbResponse }, { bidRequest: videoBidRequest }); - expect(Array.isArray(response)).to.equal(true); - expect(response.length).to.equal(2); - - const ad0 = response[ 0 ], ad1 = response[ 1 ]; - expect(ad0.requestId).to.equal(videoBidRequest.bidId); - expect(ad0.cpm).to.equal(rtbResponse.seatbid[ 0 ].bid[ 0 ].price); - expect(ad0.width).to.equal(rtbResponse.seatbid[ 0 ].bid[ 0 ].w); - expect(ad0.height).to.equal(rtbResponse.seatbid[ 0 ].bid[ 0 ].h); - expect(ad0.ttl).to.equal(15 * 60); - expect(ad0.creativeId).to.equal(rtbResponse.seatbid[ 0 ].bid[ 0 ].crid); - expect(ad0.netRevenue).to.equal(true); - expect(ad0.currency).to.equal(rtbResponse.seatbid[ 0 ].bid[ 0 ].cur || rtbResponse.cur || 'USD'); - expect(ad0.ad).to.be.an('undefined'); - expect(ad0.vastXml).to.equal(rtbResponse.seatbid[ 0 ].bid[ 0 ].adm); - expect(ad0.vastUrl).to.equal(rtbResponse.seatbid[ 0 ].bid[ 0 ].ext.vast_url); - - expect(ad1.requestId).to.equal(videoBidRequest.bidId); - expect(ad1.cpm).to.equal(rtbResponse.seatbid[ 1 ].bid[ 0 ].price); - expect(ad1.width).to.equal(rtbResponse.seatbid[ 1 ].bid[ 0 ].w); - expect(ad1.height).to.equal(rtbResponse.seatbid[ 1 ].bid[ 0 ].h); - expect(ad1.ttl).to.equal(15 * 60); - expect(ad1.creativeId).to.equal(rtbResponse.seatbid[ 1 ].bid[ 0 ].crid); - expect(ad1.netRevenue).to.equal(true); - expect(ad1.currency).to.equal(rtbResponse.seatbid[ 1 ].bid[ 0 ].cur || rtbResponse.cur || 'USD'); - expect(ad1.ad).to.be.an('undefined'); - expect(ad1.vastXml).to.equal(rtbResponse.seatbid[ 1 ].bid[ 0 ].adm); - expect(ad1.vastUrl).to.equal(rtbResponse.seatbid[ 1 ].bid[ 0 ].ext.vast_url); - }); - it('applies user-syncs', () => { - const response = spec.getUserSyncs({}, [ { body: rtbResponse } ]); - expect(Array.isArray(response)).to.equal(true); - expect(response.length).to.equal(4); - expect(response[ 0 ].type).to.equal(rtbResponse.ext.utrk[ 0 ].type); - expect(response[ 0 ].url).to.equal(rtbResponse.ext.utrk[ 0 ].url + '?gc=missing'); - expect(response[ 1 ].type).to.equal(rtbResponse.ext.utrk[ 1 ].type); - expect(response[ 1 ].url).to.equal(rtbResponse.ext.utrk[ 1 ].url + '?gc=missing'); - expect(response[ 2 ].type).to.equal(rtbResponse.seatbid[ 0 ].bid[ 0 ].ext.utrk[ 0 ].type); - expect(response[ 2 ].url).to.equal(rtbResponse.seatbid[ 0 ].bid[ 0 ].ext.utrk[ 0 ].url + '?gc=missing'); - expect(response[ 3 ].type).to.equal(rtbResponse.seatbid[ 1 ].bid[ 0 ].ext.utrk[ 0 ].type); - expect(response[ 3 ].url).to.equal(rtbResponse.seatbid[ 1 ].bid[ 0 ].ext.utrk[ 0 ].url + '?gc=missing'); - }); - }); -}); diff --git a/test/spec/modules/yieldbotBidAdapter_spec.js b/test/spec/modules/yieldbotBidAdapter_spec.js index 2977e4ef30d..2548bb31fdc 100644 --- a/test/spec/modules/yieldbotBidAdapter_spec.js +++ b/test/spec/modules/yieldbotBidAdapter_spec.js @@ -1,7 +1,7 @@ import { expect } from 'chai'; import find from 'core-js/library/fn/array/find'; import { newBidder } from 'src/adapters/bidderFactory'; -import AdapterManager from 'src/adaptermanager'; +import AdapterManager from 'src/adapterManager'; import { newAuctionManager } from 'src/auctionManager'; import * as utils from 'src/utils'; import * as urlUtils from 'src/url'; @@ -1059,6 +1059,16 @@ describe('Yieldbot Adapter Unit Tests', function() { expect(edgeServerUrlPrefix).to.match(beginsRegex); expect(responses[0].ad).to.match(containsRegex); }); + + it('should not use document.open() in ad markup', function() { + FIXTURE_SERVER_RESPONSE.body.url_prefix = 'http://close.edge.adserver.com/'; + const responses = YieldbotAdapter.interpretResponse( + FIXTURE_SERVER_RESPONSE, + FIXTURE_BID_REQUEST + ); + expect(responses[0].ad).to.not.match(/var innerFrameDoc=innerFrame\.contentWindow\.document;innerFrameDoc\.open\(\);innerFrameDoc\.write\(iframeHtml\);innerFrameDoc\.close\(\);/); + expect(responses[0].ad).to.match(/var innerFrameDoc=innerFrame\.contentWindow\.document;innerFrameDoc\.write\(iframeHtml\);innerFrameDoc\.close\(\);/); + }); }); describe('getUserSyncs', function() { diff --git a/test/spec/modules/yieldlabBidAdapter_spec.js b/test/spec/modules/yieldlabBidAdapter_spec.js index 0e97910bbb7..c8709969e00 100644 --- a/test/spec/modules/yieldlabBidAdapter_spec.js +++ b/test/spec/modules/yieldlabBidAdapter_spec.js @@ -11,7 +11,8 @@ const REQUEST = { 'targeting': { 'key1': 'value1', 'key2': 'value2' - } + }, + 'extId': 'abc' }, 'bidderRequestId': '143346cf0f1731', 'auctionId': '2e41f65424c87c', @@ -85,17 +86,13 @@ describe('yieldlabBidAdapter', function () { }) describe('interpretResponse', function () { - const validRequests = { - validBidRequests: [REQUEST] - } - it('handles nobid responses', function () { expect(spec.interpretResponse({body: {}}, {validBidRequests: []}).length).to.equal(0) expect(spec.interpretResponse({body: []}, {validBidRequests: []}).length).to.equal(0) }) it('should get correct bid response', function () { - const result = spec.interpretResponse({body: [RESPONSE]}, validRequests) + const result = spec.interpretResponse({body: [RESPONSE]}, {validBidRequests: [REQUEST]}) expect(result[0].requestId).to.equal('2d925f27f5079f') expect(result[0].cpm).to.equal(0.01) @@ -108,6 +105,31 @@ describe('yieldlabBidAdapter', function () { expect(result[0].ttl).to.equal(300) expect(result[0].referrer).to.equal('') expect(result[0].ad).to.include('' + } +}; + +const bidWithUndefinedFields = { + native: { + title: 'Native Creative', + body: undefined, + cta: undefined, sponsoredBy: 'AppNexus', clickUrl: 'https://www.link.example', clickTrackers: ['https://tracker.example'], @@ -31,9 +56,35 @@ describe('native.js', function () { it('gets native targeting keys', function () { const targeting = getNativeTargeting(bid); - expect(targeting.hb_native_title).to.equal(bid.native.title); - expect(targeting.hb_native_body).to.equal(bid.native.body); - expect(targeting.hb_native_linkurl).to.equal(bid.native.clickUrl); + 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); + }); + + it('sends placeholders for configured assets', function () { + const bidRequest = { + mediaTypes: { + native: { + body: { sendId: true }, + clickUrl: { sendId: true }, + } + } + }; + const targeting = getNativeTargeting(bid, bidRequest); + + 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'); + }); + + it('should only include native targeting keys with values', function () { + const targeting = getNativeTargeting(bidWithUndefinedFields); + + expect(Object.keys(targeting)).to.deep.equal([ + CONSTANTS.NATIVE_KEYS.title, + CONSTANTS.NATIVE_KEYS.sponsoredBy, + CONSTANTS.NATIVE_KEYS.clickUrl + ]); }); it('fires impression trackers', function () { @@ -44,10 +95,36 @@ describe('native.js', function () { }); it('fires click trackers', function () { - fireNativeTrackers({ action: 'click' }, bid); + const trackerType = fireNativeTrackers({ action: 'click' }, bid); + expect(trackerType).to.equal('click'); sinon.assert.calledOnce(triggerPixelStub); sinon.assert.calledWith(triggerPixelStub, bid.native.clickTrackers[0]); }); + + it('creates native asset message', function() { + const messageRequest = { + message: 'Prebid Native', + action: 'assetRequest', + adId: '123', + assets: ['hb_native_body', 'hb_native_image', 'hb_native_linkurl'], + }; + + const message = getAssetMessage(messageRequest, bid); + + expect(message.assets.length).to.equal(3); + expect(message.assets).to.deep.include({ + key: 'body', + value: bid.native.body + }); + expect(message.assets).to.deep.include({ + key: 'image', + value: bid.native.image.url + }); + expect(message.assets).to.deep.include({ + key: 'clickUrl', + value: bid.native.clickUrl + }); + }); }); describe('validate native', function () { @@ -78,7 +155,8 @@ describe('validate native', function () { }]; let validBid = { - adId: 'test_bid_id', + adId: 'abc123', + requestId: 'test_bid_id', adUnitCode: '123/prebid_native_adunit', bidder: 'test_bidder', native: { @@ -102,7 +180,8 @@ describe('validate native', function () { }; let noIconDimBid = { - adId: 'test_bid_id', + adId: 'abc234', + requestId: 'test_bid_id', adUnitCode: '123/prebid_native_adunit', bidder: 'test_bidder', native: { @@ -126,7 +205,8 @@ describe('validate native', function () { }; let noImgDimBid = { - adId: 'test_bid_id', + adId: 'abc345', + requestId: 'test_bid_id', adUnitCode: '123/prebid_native_adunit', bidder: 'test_bidder', native: { diff --git a/test/spec/refererDetection_spec.js b/test/spec/refererDetection_spec.js new file mode 100644 index 00000000000..b2ef4e2058f --- /dev/null +++ b/test/spec/refererDetection_spec.js @@ -0,0 +1,87 @@ +import { detectReferer } from 'src/refererDetection'; +import { expect } from 'chai'; + +var mocks = { + createFakeWindow: function (referrer, href) { + return { + document: { + referrer: referrer + }, + location: { + href: href, + // TODO: add ancestorOrigins to increase test coverage + }, + parent: null, + top: null + }; + } +} + +describe('referer detection', () => { + it('should return referer details in nested friendly iframes', function() { + // Fake window object to test friendly iframes + // - Main page http://example.com/page.html + // - - Iframe1 http://example.com/iframe1.html + // - - - Iframe2 http://example.com/iframe2.html + let mockIframe2WinObject = mocks.createFakeWindow('http://example.com/iframe1.html', 'http://example.com/iframe2.html'); + let mockIframe1WinObject = mocks.createFakeWindow('http://example.com/page.html', 'http://example.com/iframe1.html'); + let mainWinObject = mocks.createFakeWindow('http://example.com/page.html', 'http://example.com/page.html'); + mainWinObject.document.querySelector = function() { + return { + href: 'http://prebid.org' + } + } + mockIframe2WinObject.parent = mockIframe1WinObject; + mockIframe2WinObject.top = mainWinObject; + mockIframe1WinObject.parent = mainWinObject; + mockIframe1WinObject.top = mainWinObject; + mainWinObject.top = mainWinObject; + + const getRefererInfo = detectReferer(mockIframe2WinObject); + let result = getRefererInfo(); + let expectedResult = { + referer: 'http://example.com/page.html', + reachedTop: true, + numIframes: 2, + stack: [ + 'http://example.com/page.html', + 'http://example.com/iframe1.html', + 'http://example.com/iframe2.html' + ], + canonicalUrl: 'http://prebid.org' + }; + expect(result).to.deep.equal(expectedResult); + }); + + it('should return referer details in nested cross domain iframes', function() { + // Fake window object to test cross domain iframes. + // - Main page http://example.com/page.html + // - - Iframe1 http://aaa.com/iframe1.html + // - - - Iframe2 http://bbb.com/iframe2.html + let mockIframe2WinObject = mocks.createFakeWindow('http://aaa.com/iframe1.html', 'http://bbb.com/iframe2.html'); + // Sinon cannot throw exception when accessing a propery so passing null to create cross domain + // environment for refererDetection module + let mockIframe1WinObject = mocks.createFakeWindow(null, null); + let mainWinObject = mocks.createFakeWindow(null, null); + mockIframe2WinObject.parent = mockIframe1WinObject; + mockIframe2WinObject.top = mainWinObject; + mockIframe1WinObject.parent = mainWinObject; + mockIframe1WinObject.top = mainWinObject; + mainWinObject.top = mainWinObject; + + const getRefererInfo = detectReferer(mockIframe2WinObject); + let result = getRefererInfo(); + let expectedResult = { + referer: 'http://aaa.com/iframe1.html', + reachedTop: false, + numIframes: 2, + stack: [ + null, + 'http://aaa.com/iframe1.html', + 'http://bbb.com/iframe2.html' + ], + canonicalUrl: undefined + }; + expect(result).to.deep.equal(expectedResult); + }); +}); diff --git a/test/spec/renderer_spec.js b/test/spec/renderer_spec.js index 7f95af1a257..f9a670c1315 100644 --- a/test/spec/renderer_spec.js +++ b/test/spec/renderer_spec.js @@ -1,107 +1,131 @@ import { expect } from 'chai'; import { Renderer } from 'src/Renderer'; -const adloader = require('../../src/adloader'); - -describe('Renderer: A renderer installed on a bid response', function () { - let testRenderer1; - let testRenderer2; - let spyRenderFn; - let spyEventHandler; - - let loadScriptStub; - - beforeEach(function () { - loadScriptStub = sinon.stub(adloader, 'loadScript').callsFake((...args) => { - args[1](); +import * as utils from 'src/utils'; + +describe('Renderer', function () { + describe('Renderer: A renderer installed on a bid response', function () { + let testRenderer1; + let testRenderer2; + let spyRenderFn; + let spyEventHandler; + + beforeEach(function () { + testRenderer1 = Renderer.install({ + url: 'https://httpbin.org/post', + config: { test: 'config1' }, + id: 1 + }); + testRenderer2 = Renderer.install({ + url: 'https://httpbin.org/post', + config: { test: 'config2' }, + id: 2 + }); + + spyRenderFn = sinon.spy(); + spyEventHandler = sinon.spy(); }); - testRenderer1 = Renderer.install({ - url: 'https://httpbin.org/post', - config: { test: 'config1' }, - id: 1 + it('is an instance of Renderer', function () { + expect(testRenderer1 instanceof Renderer).to.equal(true); }); - testRenderer2 = Renderer.install({ - url: 'https://httpbin.org/post', - config: { test: 'config2' }, - id: 2 + + it('has expected properties ', function () { + expect(testRenderer1.url).to.equal('https://httpbin.org/post'); + expect(testRenderer1.config).to.deep.equal({ test: 'config1' }); + expect(testRenderer1.id).to.equal(1); }); - spyRenderFn = sinon.spy(); - spyEventHandler = sinon.spy(); - }); + it('returns config from getConfig method', function () { + expect(testRenderer1.getConfig()).to.deep.equal({ test: 'config1' }); + expect(testRenderer2.getConfig()).to.deep.equal({ test: 'config2' }); + }); - afterEach(function () { - loadScriptStub.restore(); - }); + it('sets a render function with setRender method', function () { + testRenderer1.setRender(spyRenderFn); + expect(typeof testRenderer1.render).to.equal('function'); + testRenderer1.render(); + expect(spyRenderFn.called).to.equal(true); + }); - it('is an instance of Renderer', function () { - expect(testRenderer1 instanceof Renderer).to.equal(true); - }); + it('sets event handlers with setEventHandlers method and handles events with installed handlers', function () { + testRenderer1.setEventHandlers({ + testEvent: spyEventHandler + }); - it('has expected properties ', function () { - expect(testRenderer1.url).to.equal('https://httpbin.org/post'); - expect(testRenderer1.config).to.deep.equal({ test: 'config1' }); - expect(testRenderer1.id).to.equal(1); - }); + expect(testRenderer1.handlers).to.deep.equal({ + testEvent: spyEventHandler + }); - it('returns config from getConfig method', function () { - expect(testRenderer1.getConfig()).to.deep.equal({ test: 'config1' }); - expect(testRenderer2.getConfig()).to.deep.equal({ test: 'config2' }); - }); + testRenderer1.handleVideoEvent({ id: 1, eventName: 'testEvent' }); + expect(spyEventHandler.called).to.equal(true); + }); - it('sets a render function with setRender method', function () { - testRenderer1.setRender(spyRenderFn); - expect(typeof testRenderer1.render).to.equal('function'); - testRenderer1.render(); - expect(spyRenderFn.called).to.equal(true); - }); + it('pushes commands to queue if renderer is not loaded', function () { + testRenderer1.loaded = false; + testRenderer1.push(spyRenderFn); + expect(testRenderer1.cmd.length).to.equal(1); - it('sets event handlers with setEventHandlers method and handles events with installed handlers', function () { - testRenderer1.setEventHandlers({ - testEvent: spyEventHandler + // clear queue for next tests + testRenderer1.cmd = []; }); - expect(testRenderer1.handlers).to.deep.equal({ - testEvent: spyEventHandler - }); + it('fires commands immediately if the renderer is loaded', function () { + const func = sinon.spy(); - testRenderer1.handleVideoEvent({ id: 1, eventName: 'testEvent' }); - expect(spyEventHandler.called).to.equal(true); - }); + testRenderer1.loaded = true; + testRenderer1.push(func); - it('pushes commands to queue if renderer is not loaded', function () { - testRenderer1.loaded = false; - testRenderer1.push(spyRenderFn); - expect(testRenderer1.cmd.length).to.equal(1); + expect(testRenderer1.cmd.length).to.equal(0); - // clear queue for next tests - testRenderer1.cmd = []; - }); + sinon.assert.calledOnce(func); + }); - it('fires commands immediately if the renderer is loaded', function () { - const func = sinon.spy(); + it('processes queue by calling each function in queue', function () { + testRenderer1.loaded = false; + const func1 = sinon.spy(); + const func2 = sinon.spy(); - testRenderer1.loaded = true; - testRenderer1.push(func); + testRenderer1.push(func1); + testRenderer1.push(func2); + expect(testRenderer1.cmd.length).to.equal(2); - expect(testRenderer1.cmd.length).to.equal(0); + testRenderer1.process(); - sinon.assert.calledOnce(func); + sinon.assert.calledOnce(func1); + sinon.assert.calledOnce(func2); + expect(testRenderer1.cmd.length).to.equal(0); + }); }); - it('processes queue by calling each function in queue', function () { - testRenderer1.loaded = false; - const func1 = sinon.spy(); - const func2 = sinon.spy(); - - testRenderer1.push(func1); - testRenderer1.push(func2); - expect(testRenderer1.cmd.length).to.equal(2); + describe('3rd party renderer', function () { + let adUnitsOld; + let utilsSpy; + before(function () { + adUnitsOld = $$PREBID_GLOBAL$$.adUnits; + utilsSpy = sinon.spy(utils, 'logWarn'); + }); - testRenderer1.process(); + after(function() { + $$PREBID_GLOBAL$$.adUnits = adUnitsOld; + utilsSpy.restore(); + }); - sinon.assert.calledOnce(func1); - sinon.assert.calledOnce(func2); - expect(testRenderer1.cmd.length).to.equal(0); + it('should not load renderer and log warn message', function() { + $$PREBID_GLOBAL$$.adUnits = [{ + code: 'video1', + renderer: { + url: 'http://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js', + render: sinon.spy() + } + }] + + let testRenderer = Renderer.install({ + url: 'https://httpbin.org/post', + config: { test: 'config1' }, + id: 1, + adUnitCode: 'video1' + }); + expect(utilsSpy.callCount).to.equal(1); + }); }); }); diff --git a/test/spec/sizeMapping_spec.js b/test/spec/sizeMapping_spec.js index 15213958296..254dcb8003e 100644 --- a/test/spec/sizeMapping_spec.js +++ b/test/spec/sizeMapping_spec.js @@ -1,12 +1,16 @@ import { expect } from 'chai'; -import { resolveStatus, setSizeConfig } from 'src/sizeMapping'; +import { resolveStatus, setSizeConfig, sizeSupported } from 'src/sizeMapping'; import includes from 'core-js/library/fn/array/includes'; let utils = require('src/utils'); let deepClone = utils.deepClone; describe('sizeMapping', function () { - var testSizes = [[970, 90], [728, 90], [300, 250], [300, 100], [80, 80]]; + var testSizes = { + banner: { + sizes: [[970, 90], [728, 90], [300, 250], [300, 100], [80, 80]] + } + }; var sizeConfig = [{ 'mediaQuery': '(min-width: 1200px)', @@ -56,9 +60,9 @@ describe('sizeMapping', function () { matchMediaOverride = {matches: false}; - sandbox.stub(window, 'matchMedia').callsFake((...args) => { + sandbox.stub(utils.getWindowTop(), 'matchMedia').callsFake((...args) => { if (typeof matchMediaOverride === 'function') { - return matchMediaOverride.apply(window, args); + return matchMediaOverride.apply(utils.getWindowTop(), args); } return matchMediaOverride; }); @@ -71,6 +75,13 @@ describe('sizeMapping', function () { }); describe('when handling sizes', function () { + it('should allow us to validate a single size', function() { + matchMediaOverride = (str) => str === '(min-width: 1200px)' ? {matches: true} : {matches: false}; + + expect(sizeSupported([300, 250])).to.equal(true); + expect(sizeSupported([80, 80])).to.equal(false); + }); + it('should log a warning when mediaQuery property missing from sizeConfig', function () { let errorConfig = deepClone(sizeConfig); @@ -78,19 +89,34 @@ describe('sizeMapping', function () { sandbox.stub(utils, 'logWarn'); - resolveStatus(undefined, testSizes, errorConfig); + resolveStatus(undefined, testSizes, undefined, errorConfig); expect(utils.logWarn.firstCall.args[0]).to.match(/missing.+?mediaQuery/); }); + it('should allow deprecated adUnit.sizes', function() { + matchMediaOverride = (str) => str === '(min-width: 1200px)' ? {matches: true} : {matches: false}; + + let status = resolveStatus(undefined, undefined, testSizes.banner.sizes, sizeConfig); + + expect(status.active).to.equal(true); + expect(status.mediaTypes).to.deep.equal({ + banner: { + sizes: [[970, 90], [728, 90], [300, 250]] + } + }); + }); + it('when one mediaQuery block matches, it should filter the adUnit.sizes passed in', function () { matchMediaOverride = (str) => str === '(min-width: 1200px)' ? {matches: true} : {matches: false}; - let status = resolveStatus(undefined, testSizes, sizeConfig); + let status = resolveStatus(undefined, testSizes, undefined, sizeConfig); - expect(status).to.deep.equal({ - active: true, - sizes: [[970, 90], [728, 90], [300, 250]] - }) + expect(status.active).to.equal(true); + expect(status.mediaTypes).to.deep.equal({ + banner: { + sizes: [[970, 90], [728, 90], [300, 250]] + } + }); }); it('when multiple mediaQuery block matches, it should filter a union of the matched sizesSupported', function () { @@ -99,41 +125,59 @@ describe('sizeMapping', function () { '(min-width: 768px) and (max-width: 1199px)' ], str) ? {matches: true} : {matches: false}; - let status = resolveStatus(undefined, testSizes, sizeConfig); - expect(status).to.deep.equal({ - active: true, - sizes: [[970, 90], [728, 90], [300, 250], [300, 100]] - }) + let status = resolveStatus(undefined, testSizes, undefined, sizeConfig); + expect(status.active).to.equal(true); + expect(status.mediaTypes).to.deep.equal({ + banner: { + sizes: [[970, 90], [728, 90], [300, 250], [300, 100]] + } + }); }); it('if no mediaQueries match, it should allow all sizes specified', function () { matchMediaOverride = () => ({matches: false}); - let status = resolveStatus(undefined, testSizes, sizeConfig); - expect(status).to.deep.equal({ - active: true, - sizes: testSizes - }) + let status = resolveStatus(undefined, testSizes, undefined, sizeConfig); + expect(status.active).to.equal(true); + expect(status.mediaTypes).to.deep.equal(testSizes); }); it('if a mediaQuery matches and has sizesSupported: [], it should filter all sizes', function () { matchMediaOverride = (str) => str === '(min-width: 0px) and (max-width: 767px)' ? {matches: true} : {matches: false}; - let status = resolveStatus(undefined, testSizes, sizeConfig); - expect(status).to.deep.equal({ - active: false, - sizes: [] - }) + let status = resolveStatus(undefined, testSizes, undefined, sizeConfig); + expect(status.active).to.equal(false); + expect(status.mediaTypes).to.deep.equal({ + banner: { + sizes: [] + } + }); }); - it('if a mediaQuery matches and no sizesSupported specified, it should not effect adUnit.sizes', function () { + it('should filter all banner sizes and should disable the adUnit even if other mediaTypes are present', function () { + matchMediaOverride = (str) => str === '(min-width: 0px) and (max-width: 767px)' ? {matches: true} : {matches: false}; + let status = resolveStatus(undefined, Object.assign({}, testSizes, { + native: { + type: 'image' + } + }), undefined, sizeConfig); + expect(status.active).to.equal(false); + expect(status.mediaTypes).to.deep.equal({ + banner: { + sizes: [] + }, + native: { + type: 'image' + } + }); + }); + + it('if a mediaQuery matches and no sizesSupported specified, it should not affect adUnit.sizes', function () { matchMediaOverride = (str) => str === '(min-width: 1200px)' ? {matches: true} : {matches: false}; - let status = resolveStatus(undefined, testSizes, sizeConfigWithLabels); - expect(status).to.deep.equal({ - active: true, - sizes: testSizes - }) + let status = resolveStatus(undefined, testSizes, undefined, sizeConfigWithLabels); + expect(status.active).to.equal(true); + expect(status.mediaTypes).to.deep.equal(testSizes); }); }); @@ -143,20 +187,107 @@ describe('sizeMapping', function () { let status = resolveStatus({ labels: ['desktop'] - }, testSizes, sizeConfigWithLabels); + }, testSizes, undefined, sizeConfigWithLabels); expect(status).to.deep.equal({ active: true, - sizes: testSizes + mediaTypes: testSizes }); status = resolveStatus({ labels: ['tablet'] - }, testSizes, sizeConfigWithLabels); + }, testSizes, undefined, sizeConfigWithLabels); - expect(status).to.deep.equal({ - active: false, - sizes: testSizes + expect(status.active).to.equal(false); + expect(status.mediaTypes).to.deep.equal(testSizes); + }); + + it('should activate/decactivate adUnits/bidders based on labels with multiformat ads', function () { + matchMediaOverride = (str) => str === '(min-width: 768px) and (max-width: 1199px)' ? {matches: true} : {matches: false}; + + let multiFormatSizes = { + banner: { + sizes: [[728, 90], [300, 300]] + }, + native: { + type: 'image' + }, + video: { + context: 'outstream', + playerSize: [300, 300] + } + }; + + let status = resolveStatus({ + labels: ['tablet', 'test'], + labelAll: true + }, multiFormatSizes, undefined, sizeConfigWithLabels); + + expect(status.active).to.equal(false); + expect(status.mediaTypes).to.deep.equal({ + banner: { + sizes: [[728, 90]] + }, + native: { + type: 'image' + }, + video: { + context: 'outstream', + playerSize: [300, 300] + } + }); + + status = resolveStatus({ + labels: ['tablet'] + }, multiFormatSizes, undefined, sizeConfigWithLabels); + + expect(status.active).to.equal(true); + expect(status.mediaTypes).to.deep.equal({ + banner: { + sizes: [[728, 90]] + }, + native: { + type: 'image' + }, + video: { + context: 'outstream', + playerSize: [300, 300] + } + }); + + multiFormatSizes.banner.sizes.splice(0, 1, [728, 80]); + status = resolveStatus({ + labels: ['tablet'] + }, multiFormatSizes, undefined, sizeConfigWithLabels); + + expect(status.active).to.equal(false); + expect(status.mediaTypes).to.deep.equal({ + banner: { + sizes: [] + }, + native: { + type: 'image' + }, + video: { + context: 'outstream', + playerSize: [300, 300] + } + }); + + delete multiFormatSizes.banner; + status = resolveStatus({ + labels: ['tablet'] + }, multiFormatSizes, undefined, sizeConfigWithLabels); + + expect(status.active).to.equal(true); + expect(status.mediaTypes).to.deep.equal({ + native: { + type: 'image' + }, + video: { + context: 'outstream', + playerSize: [300, 300] + } }); }); @@ -164,46 +295,38 @@ describe('sizeMapping', function () { let activeLabels = ['us-visitor', 'desktop', 'smart']; let status = resolveStatus({ - labels: ['uk-visitor'], - activeLabels - }, testSizes, sizeConfigWithLabels); + labels: ['uk-visitor'], // from adunit + activeLabels // from requestBids.labels + }, testSizes, undefined, sizeConfigWithLabels); - expect(status).to.deep.equal({ - active: false, - sizes: testSizes - }); + expect(status.active).to.equal(false); + expect(status.mediaTypes).to.deep.equal(testSizes); status = resolveStatus({ labels: ['us-visitor'], activeLabels - }, testSizes, sizeConfigWithLabels); + }, testSizes, undefined, sizeConfigWithLabels); - expect(status).to.deep.equal({ - active: true, - sizes: testSizes - }); + expect(status.active).to.equal(true); + expect(status.mediaTypes).to.deep.equal(testSizes); status = resolveStatus({ labels: ['us-visitor', 'tablet'], labelAll: true, activeLabels - }, testSizes, sizeConfigWithLabels); + }, testSizes, undefined, sizeConfigWithLabels); - expect(status).to.deep.equal({ - active: false, - sizes: testSizes - }); + expect(status.active).to.equal(false); + expect(status.mediaTypes).to.deep.equal(testSizes); status = resolveStatus({ labels: ['us-visitor', 'desktop'], labelAll: true, activeLabels - }, testSizes, sizeConfigWithLabels); + }, testSizes, undefined, sizeConfigWithLabels); - expect(status).to.deep.equal({ - active: true, - sizes: testSizes - }); + expect(status.active).to.equal(true); + expect(status.mediaTypes).to.deep.equal(testSizes); }); }); }); diff --git a/test/spec/unit/adServerManager_spec.js b/test/spec/unit/adServerManager_spec.js index 928050ee93c..4ae475ac013 100644 --- a/test/spec/unit/adServerManager_spec.js +++ b/test/spec/unit/adServerManager_spec.js @@ -5,7 +5,11 @@ import { registerVideoSupport } from 'src/adServerManager'; const prebid = getGlobal(); describe('The ad server manager', function () { - beforeEach(function () { + before(function () { + delete prebid.adServers; + }); + + afterEach(function () { delete prebid.adServers; }); @@ -27,4 +31,13 @@ describe('The ad server manager', function () { expect(prebid.adServers).to.have.property('dfp'); expect(prebid.adServers.dfp).to.have.property('buildVideoUrl', videoSupport); }); + + it('should support any custom named property in the public API', function () { + function getTestAdServerTargetingKeys() { }; + registerVideoSupport('testAdServer', { getTargetingKeys: getTestAdServerTargetingKeys }); + + expect(prebid).to.have.property('adServers'); + expect(prebid.adServers).to.have.property('testAdServer'); + expect(prebid.adServers.testAdServer).to.have.property('getTargetingKeys', getTestAdServerTargetingKeys); + }); }); diff --git a/test/spec/unit/core/adapterManager_spec.js b/test/spec/unit/core/adapterManager_spec.js index fc4ef023743..1933e4a736d 100644 --- a/test/spec/unit/core/adapterManager_spec.js +++ b/test/spec/unit/core/adapterManager_spec.js @@ -1,7 +1,10 @@ import { expect } from 'chai'; -import AdapterManager from 'src/adaptermanager'; -import { checkBidRequestSizes } from 'src/adaptermanager'; -import { getAdUnits } from 'test/fixtures/fixtures'; +import adapterManager, { gdprDataHandler } from 'src/adapterManager'; +import { + getAdUnits, + getServerTestingConfig, + getServerTestingsAds +} from 'test/fixtures/fixtures'; import CONSTANTS from 'src/constants.json'; import * as utils from 'src/utils'; import { config } from 'src/config'; @@ -9,9 +12,8 @@ import { registerBidder } from 'src/adapters/bidderFactory'; import { setSizeConfig } from 'src/sizeMapping'; import find from 'core-js/library/fn/array/find'; import includes from 'core-js/library/fn/array/includes'; -var s2sTesting = require('../../../../modules/s2sTesting'); +import s2sTesting from 'modules/s2sTesting'; var events = require('../../../../src/events'); -const adloader = require('../../../../src/adloader'); const CONFIG = { enabled: true, @@ -39,7 +41,6 @@ var rubiconAdapterMock = { bidder: 'rubicon', callBids: sinon.stub() }; -let loadScriptStub; describe('adapterManager tests', function () { let orgAppnexusAdapter; @@ -47,21 +48,17 @@ describe('adapterManager tests', function () { let orgPrebidServerAdapter; let orgRubiconAdapter; before(function () { - orgAppnexusAdapter = AdapterManager.bidderRegistry['appnexus']; - orgAdequantAdapter = AdapterManager.bidderRegistry['adequant']; - orgPrebidServerAdapter = AdapterManager.bidderRegistry['prebidServer']; - orgRubiconAdapter = AdapterManager.bidderRegistry['rubicon']; - loadScriptStub = sinon.stub(adloader, 'loadScript').callsFake((...args) => { - args[1](); - }); + orgAppnexusAdapter = adapterManager.bidderRegistry['appnexus']; + orgAdequantAdapter = adapterManager.bidderRegistry['adequant']; + orgPrebidServerAdapter = adapterManager.bidderRegistry['prebidServer']; + orgRubiconAdapter = adapterManager.bidderRegistry['rubicon']; }); after(function () { - AdapterManager.bidderRegistry['appnexus'] = orgAppnexusAdapter; - AdapterManager.bidderRegistry['adequant'] = orgAdequantAdapter; - AdapterManager.bidderRegistry['prebidServer'] = orgPrebidServerAdapter; - AdapterManager.bidderRegistry['rubicon'] = orgRubiconAdapter; - loadScriptStub.restore(); + adapterManager.bidderRegistry['appnexus'] = orgAppnexusAdapter; + adapterManager.bidderRegistry['adequant'] = orgAdequantAdapter; + adapterManager.bidderRegistry['prebidServer'] = orgPrebidServerAdapter; + adapterManager.bidderRegistry['rubicon'] = orgRubiconAdapter; config.setConfig({s2sConfig: { enabled: false }}); }); @@ -73,12 +70,12 @@ describe('adapterManager tests', function () { beforeEach(function () { sinon.stub(utils, 'logError'); appnexusAdapterMock.callBids.reset(); - AdapterManager.bidderRegistry['appnexus'] = appnexusAdapterMock; + adapterManager.bidderRegistry['appnexus'] = appnexusAdapterMock; }); afterEach(function () { utils.logError.restore(); - delete AdapterManager.bidderRegistry['appnexus']; + delete adapterManager.bidderRegistry['appnexus']; }); it('should log an error if a bidder is used that does not exist', function () { @@ -91,7 +88,7 @@ describe('adapterManager tests', function () { ] }]; - let bidRequests = AdapterManager.makeBidRequests(adUnits, 1111, 2222, 1000); + let bidRequests = adapterManager.makeBidRequests(adUnits, 1111, 2222, 1000); expect(bidRequests.length).to.equal(1); expect(bidRequests[0].bidderCode).to.equal('appnexus'); sinon.assert.called(utils.logError); @@ -133,17 +130,119 @@ describe('adapterManager tests', function () { {bidder: 'appnexus', params: {placementId: 'id'}}, ] }]; - AdapterManager.callBids(adUnits, bidRequests, () => {}, () => {}); + adapterManager.callBids(adUnits, bidRequests, () => {}, () => {}); expect(cnt).to.equal(1); sinon.assert.calledOnce(appnexusAdapterMock.callBids); events.off(CONSTANTS.EVENTS.BID_REQUESTED, count); }); }); + describe('callTimedOutBidders', function () { + var criteoSpec = { onTimeout: 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 onTimeout callback when callTimedOutBidders is called', function () { + const adUnits = [{ + code: 'adUnit-code', + sizes: [[728, 90]], + bids: [ + {bidder: 'criteo', params: {placementId: 'id'}}, + ] + }]; + const timedOutBidders = [{ + bidId: 'bidId', + bidder: 'criteo', + adUnitCode: adUnits[0].code, + auctionId: 'auctionId', + }]; + adapterManager.callTimedOutBidders(adUnits, timedOutBidders, CONFIG.timeout); + sinon.assert.called(criteoSpec.onTimeout); + }); + }); // end callTimedOutBidders + + describe('onBidWon', function () { + var criteoSpec = { onBidWon: 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 onBidWon callback when a bid is won', function () { + const bids = [ + {bidder: 'criteo', params: {placementId: 'id'}}, + ]; + const adUnits = [{ + code: 'adUnit-code', + sizes: [[728, 90]], + bids + }]; + + adapterManager.callBidWonBidder(bids[0].bidder, bids[0], adUnits); + sinon.assert.called(criteoSpec.onBidWon); + }); + }); // end onBidWon + + describe('onSetTargeting', function () { + var criteoSpec = { onSetTargeting: sinon.stub() } + var criteoAdapter = { + bidder: 'criteo', + getSpec: function() { return criteoSpec; } + } + before(function () { + config.setConfig({s2sConfig: { enabled: false }}); + }); + + beforeEach(function () { + adapterManager.bidderRegistry['criteo'] = criteoAdapter; + }); + + afterEach(function () { + delete adapterManager.bidderRegistry['criteo']; + }); + + it('should call spec\'s onSetTargeting callback when setTargeting is called', function () { + const bids = [ + {bidder: 'criteo', params: {placementId: 'id'}}, + ]; + const adUnits = [{ + code: 'adUnit-code', + sizes: [[728, 90]], + bids + }]; + adapterManager.callSetTargetingBidder(bids[0].bidder, bids[0], adUnits); + sinon.assert.called(criteoSpec.onSetTargeting); + }); + }); // end onSetTargeting + describe('S2S tests', function () { beforeEach(function () { config.setConfig({s2sConfig: CONFIG}); - AdapterManager.bidderRegistry['prebidServer'] = prebidServerAdapterMock; + adapterManager.bidderRegistry['prebidServer'] = prebidServerAdapterMock; prebidServerAdapterMock.callBids.reset(); }); @@ -175,7 +274,7 @@ describe('adapterManager tests', function () { 'placementId': '543221', 'test': 'me' }, - 'placementCode': '/19968336/header-bid-tag1', + 'adUnitCode': '/19968336/header-bid-tag1', 'sizes': [ [ 728, @@ -213,7 +312,7 @@ describe('adapterManager tests', function () { 'params': { 'placementId': '5324321' }, - 'placementCode': '/19968336/header-bid-tag-0', + 'adUnitCode': '/19968336/header-bid-tag-0', 'sizes': [ [ 300, @@ -285,7 +384,7 @@ describe('adapterManager tests', function () { 'start': 1462918897460 }]; - AdapterManager.callBids( + adapterManager.callBids( getAdUnits(), bidRequests, () => {}, @@ -339,7 +438,7 @@ describe('adapterManager tests', function () { 'placementId': '543221', 'test': 'me' }, - 'placementCode': '/19968336/header-bid-tag1', + 'adUnitCode': '/19968336/header-bid-tag1', 'sizes': [ [ 728, @@ -377,7 +476,7 @@ describe('adapterManager tests', function () { 'params': { 'placementId': '5324321' }, - 'placementCode': '/19968336/header-bid-tag-0', + 'adUnitCode': '/19968336/header-bid-tag-0', 'sizes': [ [ 300, @@ -449,7 +548,7 @@ describe('adapterManager tests', function () { 'start': 1462918897460 }]; - AdapterManager.callBids( + adapterManager.callBids( adUnits, bidRequests, () => {}, @@ -479,25 +578,25 @@ describe('adapterManager tests', function () { adUnit.bids = adUnit.bids.filter(bid => includes(['appnexus'], bid.bidder)); return adUnit; }) - let bidRequests = AdapterManager.makeBidRequests(adUnits, 1111, 2222, 1000); - AdapterManager.callBids(adUnits, bidRequests, () => {}, () => {}); + let bidRequests = adapterManager.makeBidRequests(adUnits, 1111, 2222, 1000); + adapterManager.callBids(adUnits, bidRequests, () => {}, () => {}); expect(cnt).to.equal(1); sinon.assert.calledOnce(prebidServerAdapterMock.callBids); }); it('should fire for simultaneous s2s and client requests', function () { - AdapterManager.bidderRegistry['adequant'] = adequantAdapterMock; + adapterManager.bidderRegistry['adequant'] = adequantAdapterMock; let adUnits = utils.deepClone(getAdUnits()).map(adUnit => { adUnit.bids = adUnit.bids.filter(bid => includes(['adequant', 'appnexus'], bid.bidder)); return adUnit; }) - let bidRequests = AdapterManager.makeBidRequests(adUnits, 1111, 2222, 1000); - AdapterManager.callBids(adUnits, bidRequests, () => {}, () => {}); + let bidRequests = adapterManager.makeBidRequests(adUnits, 1111, 2222, 1000); + adapterManager.callBids(adUnits, bidRequests, () => {}, () => {}); expect(cnt).to.equal(2); sinon.assert.calledOnce(prebidServerAdapterMock.callBids); sinon.assert.calledOnce(adequantAdapterMock.callBids); adequantAdapterMock.callBids.reset(); - delete AdapterManager.bidderRegistry['adequant']; + delete adapterManager.bidderRegistry['adequant']; }); }); }); // end s2s tests @@ -516,8 +615,8 @@ describe('adapterManager tests', function () { } function callBids(adUnits = getTestAdUnits()) { - let bidRequests = AdapterManager.makeBidRequests(adUnits, 1111, 2222, 1000); - AdapterManager.callBids(adUnits, bidRequests, doneStub, ajaxStub); + let bidRequests = adapterManager.makeBidRequests(adUnits, 1111, 2222, 1000); + adapterManager.callBids(adUnits, bidRequests, doneStub, ajaxStub); } function checkServerCalled(numAdUnits, numBids) { @@ -545,10 +644,10 @@ describe('adapterManager tests', function () { beforeEach(function () { config.setConfig({s2sConfig: TESTING_CONFIG}); - AdapterManager.bidderRegistry['prebidServer'] = prebidServerAdapterMock; - AdapterManager.bidderRegistry['adequant'] = adequantAdapterMock; - AdapterManager.bidderRegistry['appnexus'] = appnexusAdapterMock; - AdapterManager.bidderRegistry['rubicon'] = rubiconAdapterMock; + adapterManager.bidderRegistry['prebidServer'] = prebidServerAdapterMock; + adapterManager.bidderRegistry['adequant'] = adequantAdapterMock; + adapterManager.bidderRegistry['appnexus'] = appnexusAdapterMock; + adapterManager.bidderRegistry['rubicon'] = rubiconAdapterMock; stubGetSourceBidderMap = sinon.stub(s2sTesting, 'getSourceBidderMap'); @@ -660,10 +759,10 @@ describe('adapterManager tests', function () { 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; + // 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 @@ -716,9 +815,9 @@ describe('adapterManager tests', function () { let thisSpec = Object.assign(spec, { supportedMediaTypes: ['video'] }); registerBidder(thisSpec); const alias = 'aliasBidder'; - AdapterManager.aliasBidAdapter(CODE, alias); - expect(AdapterManager.bidderRegistry).to.have.property(alias); - expect(AdapterManager.videoAdapters).to.include(alias); + adapterManager.aliasBidAdapter(CODE, alias); + expect(adapterManager.bidderRegistry).to.have.property(alias); + expect(adapterManager.videoAdapters).to.include(alias); }); }); @@ -737,8 +836,8 @@ describe('adapterManager tests', function () { testS2sConfig.bidders = ['s2sAlias']; config.setConfig({s2sConfig: testS2sConfig}); - AdapterManager.aliasBidAdapter('s2sBidder', 's2sAlias'); - expect(AdapterManager.aliasRegistry).to.have.property('s2sAlias'); + adapterManager.aliasBidAdapter('s2sBidder', 's2sAlias'); + expect(adapterManager.aliasRegistry).to.have.property('s2sAlias'); }); it('should throw an error if alias + bidder are unknown and not part of s2sConfig.bidders', function () { @@ -746,9 +845,9 @@ describe('adapterManager tests', function () { testS2sConfig.bidders = ['s2sAlias']; config.setConfig({s2sConfig: testS2sConfig}); - AdapterManager.aliasBidAdapter('s2sBidder1', 's2sAlias1'); + adapterManager.aliasBidAdapter('s2sBidder1', 's2sAlias1'); sinon.assert.calledOnce(utils.logError); - expect(AdapterManager.aliasRegistry).to.not.have.property('s2sAlias1'); + expect(adapterManager.aliasRegistry).to.not.have.property('s2sAlias1'); }); }); }); @@ -762,6 +861,26 @@ describe('adapterManager tests', function () { }) }); + it('should make separate bidder request objects for each bidder', () => { + adUnits = [utils.deepClone(getAdUnits()[0])]; + + let bidRequests = adapterManager.makeBidRequests( + adUnits, + Date.now(), + utils.getUniqueIdentifierStr(), + function callback() {}, + [] + ); + + let sizes1 = bidRequests[1].bids[0].sizes; + let sizes2 = bidRequests[0].bids[0].sizes; + + // mutate array + sizes1.splice(0, 1); + + expect(sizes1).not.to.deep.equal(sizes2); + }); + describe('setBidderSequence', function () { beforeEach(function () { sinon.spy(utils, 'shuffle'); @@ -774,7 +893,7 @@ describe('adapterManager tests', function () { it('setting to `random` uses shuffled order of adUnits', function () { config.setConfig({ bidderSequence: 'random' }); - let bidRequests = AdapterManager.makeBidRequests( + let bidRequests = adapterManager.makeBidRequests( adUnits, Date.now(), utils.getUniqueIdentifierStr(), @@ -796,8 +915,8 @@ describe('adapterManager tests', function () { setSizeConfig([]); }); - it('should not filter bids w/ no labels', function () { - let bidRequests = AdapterManager.makeBidRequests( + it('should not filter banner bids w/ no labels', function () { + let bidRequests = adapterManager.makeBidRequests( adUnits, Date.now(), utils.getUniqueIdentifierStr(), @@ -808,12 +927,91 @@ describe('adapterManager tests', function () { expect(bidRequests.length).to.equal(2); let rubiconBidRequests = find(bidRequests, bidRequest => bidRequest.bidderCode === 'rubicon'); expect(rubiconBidRequests.bids.length).to.equal(1); - expect(rubiconBidRequests.bids[0].sizes).to.deep.equal(find(adUnits, adUnit => adUnit.code === rubiconBidRequests.bids[0].adUnitCode).sizes); + expect(rubiconBidRequests.bids[0].mediaTypes).to.deep.equal(find(adUnits, adUnit => adUnit.code === rubiconBidRequests.bids[0].adUnitCode).mediaTypes); let appnexusBidRequests = find(bidRequests, bidRequest => bidRequest.bidderCode === 'appnexus'); expect(appnexusBidRequests.bids.length).to.equal(2); - expect(appnexusBidRequests.bids[0].sizes).to.deep.equal(find(adUnits, adUnit => adUnit.code === appnexusBidRequests.bids[0].adUnitCode).sizes); - expect(appnexusBidRequests.bids[1].sizes).to.deep.equal(find(adUnits, adUnit => adUnit.code === appnexusBidRequests.bids[1].adUnitCode).sizes); + expect(appnexusBidRequests.bids[0].mediaTypes).to.deep.equal(find(adUnits, adUnit => adUnit.code === appnexusBidRequests.bids[0].adUnitCode).mediaTypes); + expect(appnexusBidRequests.bids[1].mediaTypes).to.deep.equal(find(adUnits, adUnit => adUnit.code === appnexusBidRequests.bids[1].adUnitCode).mediaTypes); + }); + + it('should not filter video bids', function () { + setSizeConfig([{ + 'mediaQuery': '(min-width: 768px) and (max-width: 1199px)', + 'sizesSupported': [ + [728, 90], + [300, 250] + ], + 'labels': ['tablet', 'phone'] + }]); + + let videoAdUnits = [{ + code: 'test_video', + mediaTypes: { + video: { + playerSize: [300, 300], + context: 'outstream' + } + }, + bids: [{ + bidder: 'appnexus', + params: { + placementId: 13232385, + video: { + skippable: true, + playback_method: ['auto_play_sound_off'] + } + } + }] + }]; + let bidRequests = adapterManager.makeBidRequests( + videoAdUnits, + Date.now(), + utils.getUniqueIdentifierStr(), + function callback() {}, + [] + ); + expect(bidRequests[0].bids[0].sizes).to.deep.equal([300, 300]); + }); + + it('should not filter native bids', function () { + setSizeConfig([{ + 'mediaQuery': '(min-width: 768px) and (max-width: 1199px)', + 'sizesSupported': [ + [728, 90], + [300, 250] + ], + 'labels': ['tablet', 'phone'] + }]); + + let nativeAdUnits = [{ + code: 'test_native', + sizes: [[1, 1]], + mediaTypes: { + native: { + title: { required: true }, + body: { required: false }, + image: { required: true }, + icon: { required: false }, + sponsoredBy: { required: true }, + clickUrl: { required: true }, + }, + }, + bids: [ + { + bidder: 'appnexus', + params: { placementId: 13232354 } + }, + ] + }]; + let bidRequests = adapterManager.makeBidRequests( + nativeAdUnits, + Date.now(), + utils.getUniqueIdentifierStr(), + function callback() {}, + [] + ); + expect(bidRequests[0].bids[0].sizes).to.deep.equal([]); }); it('should filter sizes using size config', function () { @@ -833,7 +1031,7 @@ describe('adapterManager tests', function () { 'labels': ['tablet', 'phone'] }]); - let bidRequests = AdapterManager.makeBidRequests( + let bidRequests = adapterManager.makeBidRequests( adUnits, Date.now(), utils.getUniqueIdentifierStr(), @@ -856,7 +1054,7 @@ describe('adapterManager tests', function () { 'labels': ['tablet', 'phone'] }]); - bidRequests = AdapterManager.makeBidRequests( + bidRequests = adapterManager.makeBidRequests( adUnits, Date.now(), utils.getUniqueIdentifierStr(), @@ -874,7 +1072,7 @@ describe('adapterManager tests', function () { adUnits[1].bids[0].labelAny = ['mobile']; adUnits[1].bids[1].labelAll = ['desktop']; - let bidRequests = AdapterManager.makeBidRequests( + let bidRequests = adapterManager.makeBidRequests( adUnits, Date.now(), utils.getUniqueIdentifierStr(), @@ -899,7 +1097,7 @@ describe('adapterManager tests', function () { TESTING_CONFIG.bidders = ['appnexus', 'rubicon']; config.setConfig({ s2sConfig: TESTING_CONFIG }); - let bidRequests = AdapterManager.makeBidRequests( + let bidRequests = adapterManager.makeBidRequests( adUnits, Date.now(), utils.getUniqueIdentifierStr(), @@ -911,20 +1109,18 @@ describe('adapterManager tests', function () { expect(bidRequests[0].adUnitsS2SCopy.length).to.equal(1); expect(bidRequests[0].adUnitsS2SCopy[0].bids.length).to.equal(1); expect(bidRequests[0].adUnitsS2SCopy[0].bids[0].bidder).to.equal('rubicon'); - expect(bidRequests[0].adUnitsS2SCopy[0].bids[0].placementCode).to.equal(adUnits[1].code); - expect(bidRequests[0].adUnitsS2SCopy[0].bids[0].bid_id).to.equal(bidRequests[0].bids[0].bid_id); expect(bidRequests[0].adUnitsS2SCopy[0].labelAny).to.deep.equal(['visitor-uk', 'desktop']); }); }); describe('gdpr consent module', function () { it('inserts gdprConsent object to bidRequest only when module was enabled', function () { - AdapterManager.gdprDataHandler.setConsentData({ + gdprDataHandler.setConsentData({ consentString: 'abc123def456', consentRequired: true }); - let bidRequests = AdapterManager.makeBidRequests( + let bidRequests = adapterManager.makeBidRequests( adUnits, Date.now(), utils.getUniqueIdentifierStr(), @@ -934,9 +1130,9 @@ describe('adapterManager tests', function () { expect(bidRequests[0].gdprConsent.consentString).to.equal('abc123def456'); expect(bidRequests[0].gdprConsent.consentRequired).to.be.true; - AdapterManager.gdprDataHandler.setConsentData(null); + gdprDataHandler.setConsentData(null); - bidRequests = AdapterManager.makeBidRequests( + bidRequests = adapterManager.makeBidRequests( adUnits, Date.now(), utils.getUniqueIdentifierStr(), @@ -946,211 +1142,128 @@ describe('adapterManager tests', function () { expect(bidRequests[0].gdprConsent).to.be.undefined; }); }); - }); - describe('isValidBidRequest', function () { - describe('positive tests for validating bid request', function () { - beforeEach(function () { - sinon.stub(utils, 'logInfo'); + describe('s2sTesting - testServerOnly', () => { + beforeEach(() => { + config.setConfig({ s2sConfig: getServerTestingConfig(CONFIG) }); }); - afterEach(function () { - utils.logInfo.restore(); - }); + afterEach(() => config.resetConfig()); - it('should maintain adUnit structure and adUnits.sizes is replaced', function () { - let fullAdUnit = [{ - sizes: [[300, 250], [300, 600]], - mediaTypes: { - banner: { - sizes: [[300, 250]] - }, - video: { - playerSize: [[640, 480]] - }, - native: { - image: { - sizes: [150, 150], - aspect_ratios: [140, 140] - }, - icon: { - sizes: [75, 75] - } - } - } - }]; - let result = checkBidRequestSizes(fullAdUnit); - expect(result[0].sizes).to.deep.equal([[640, 480]]); - expect(result[0].mediaTypes.video.playerSize).to.deep.equal([[640, 480]]); - expect(result[0].mediaTypes.native.image.sizes).to.deep.equal([150, 150]); - expect(result[0].mediaTypes.native.icon.sizes).to.deep.equal([75, 75]); - expect(result[0].mediaTypes.native.image.aspect_ratios).to.deep.equal([140, 140]); - - let noOptnlFieldAdUnit = [{ - sizes: [[300, 250], [300, 600]], - mediaTypes: { - banner: { - sizes: [[300, 250]] - }, - video: { - context: 'outstream' - }, - native: { - image: { - required: true - }, - icon: { - required: true - } - } - } - }]; - result = checkBidRequestSizes(noOptnlFieldAdUnit); - expect(result[0].sizes).to.deep.equal([[300, 250]]); - expect(result[0].mediaTypes.video).to.exist; + const makeBidRequests = ads => { + let bidRequests = adapterManager.makeBidRequests( + ads, 1111, 2222, 1000 + ); - let mixedAdUnit = [{ - sizes: [[300, 250], [300, 600]], - mediaTypes: { - video: { - context: 'outstream', - playerSize: [[400, 350]] - }, - native: { - image: { - aspect_ratios: [200, 150], - required: true - } - } - } - }]; - result = checkBidRequestSizes(mixedAdUnit); - expect(result[0].sizes).to.deep.equal([[400, 350]]); - expect(result[0].mediaTypes.video).to.exist; + bidRequests.sort((a, b) => { + if (a.bidderCode < b.bidderCode) return -1; + if (a.bidderCode > b.bidderCode) return 1; + return 0; + }); - let altVideoPlayerSize = [{ - sizes: [[600, 600]], - mediaTypes: { - video: { - playerSize: [640, 480] - } - } - }]; - result = checkBidRequestSizes(altVideoPlayerSize); - expect(result[0].sizes).to.deep.equal([[640, 480]]); - expect(result[0].mediaTypes.video.playerSize).to.deep.equal([[640, 480]]); - expect(result[0].mediaTypes.video).to.exist; - sinon.assert.calledOnce(utils.logInfo); - }); + return bidRequests; + }; - it('should normalize adUnit.sizes and adUnit.mediaTypes.banner.sizes', function () { - let fullAdUnit = [{ - sizes: [300, 250], - mediaTypes: { - banner: { - sizes: [300, 250] - } - } - }]; - let result = checkBidRequestSizes(fullAdUnit); - expect(result[0].sizes).to.deep.equal([[300, 250]]); - expect(result[0].mediaTypes.banner.sizes).to.deep.equal([[300, 250]]); + 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; }); - }); - describe('negative tests for validating bid requests', function () { - beforeEach(function () { - sinon.stub(utils, 'logError'); + it('suppresses all client bids if there are server bids resulting from bidSource at the adUnit Level', () => { + const bidRequests = makeBidRequests(getServerTestingsAds()); + + 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[0].bids).lengthOf(1); + expect(bidRequests[1].bids[0].bidder).equals('rubicon'); + expect(bidRequests[1].bids[0].finalSource).equals('server'); }); - afterEach(function () { - utils.logError.restore(); + // 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'); }); - it('should throw error message and delete an object/property', function () { - let badBanner = [{ - sizes: [[300, 250], [300, 600]], - mediaTypes: { - banner: { - name: 'test' - } - } - }]; - let result = checkBidRequestSizes(badBanner); - expect(result[0].sizes).to.deep.equal([[300, 250], [300, 600]]); - expect(result[0].mediaTypes.banner).to.be.undefined; - sinon.assert.called(utils.logError); + // 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(); - let badVideo1 = [{ - sizes: [[600, 600]], - mediaTypes: { - video: { - playerSize: ['600x400'] - } - } - }]; - result = checkBidRequestSizes(badVideo1); - expect(result[0].sizes).to.deep.equal([[600, 600]]); - expect(result[0].mediaTypes.video.playerSize).to.be.undefined; - expect(result[0].mediaTypes.video).to.exist; - sinon.assert.called(utils.logError); - - let badVideo2 = [{ - sizes: [[600, 600]], - mediaTypes: { - video: { - playerSize: [['300', '200']] - } - } - }]; - result = checkBidRequestSizes(badVideo2); - expect(result[0].sizes).to.deep.equal([[600, 600]]); - expect(result[0].mediaTypes.video.playerSize).to.be.undefined; - expect(result[0].mediaTypes.video).to.exist; - sinon.assert.called(utils.logError); + // change this adUnit to be client based + ads[0].bids[0].bidSource.client = 100; + ads[0].bids[0].bidSource.server = 0; - let badNativeImgSize = [{ - mediaTypes: { - native: { - image: { - sizes: '300x250' - } - } - } - }]; - result = checkBidRequestSizes(badNativeImgSize); - expect(result[0].mediaTypes.native.image.sizes).to.be.undefined; - expect(result[0].mediaTypes.native.image).to.exist; - sinon.assert.called(utils.logError); + const bidRequests = makeBidRequests(ads); - let badNativeImgAspRat = [{ - mediaTypes: { - native: { - image: { - aspect_ratios: '300x250' - } - } - } - }]; - result = checkBidRequestSizes(badNativeImgAspRat); - expect(result[0].mediaTypes.native.image.aspect_ratios).to.be.undefined; - expect(result[0].mediaTypes.native.image).to.exist; - sinon.assert.called(utils.logError); + expect(bidRequests).lengthOf(4); - let badNativeIcon = [{ - mediaTypes: { - native: { - icon: { - sizes: '300x250' - } - } - } - }]; - result = checkBidRequestSizes(badNativeIcon); - expect(result[0].mediaTypes.native.icon.sizes).to.be.undefined; - expect(result[0].mediaTypes.native.icon).to.exist; - sinon.assert.called(utils.logError); + 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 6cc4a0b172c..fd54d2911e1 100644 --- a/test/spec/unit/core/bidderFactory_spec.js +++ b/test/spec/unit/core/bidderFactory_spec.js @@ -1,5 +1,5 @@ -import { newBidder, registerBidder } from 'src/adapters/bidderFactory'; -import adaptermanager from 'src/adaptermanager'; +import { newBidder, registerBidder, preloadBidderMappingFile } from 'src/adapters/bidderFactory'; +import adapterManager from 'src/adapterManager'; import * as ajax from 'src/ajax'; import { expect } from 'chai'; import { STATUS } from 'src/constants'; @@ -29,6 +29,10 @@ const MOCK_BIDS_REQUEST = { ] } +function onTimelyResponseStub() { + +} + describe('bidders created by newBidder', function () { let spec; let bidder; @@ -67,7 +71,7 @@ describe('bidders created by newBidder', function () { spec.getUserSyncs.returns([]); bidder.callBids({}); - bidder.callBids({ bids: 'nothing useful' }, addBidResponseStub, doneStub, ajaxStub); + bidder.callBids({ bids: 'nothing useful' }, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub); expect(ajaxStub.called).to.equal(false); expect(spec.isBidRequestValid.called).to.equal(false); @@ -81,7 +85,7 @@ describe('bidders created by newBidder', function () { spec.isBidRequestValid.returns(true); spec.buildRequests.returns([]); - bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub); + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub); expect(ajaxStub.called).to.equal(false); expect(spec.isBidRequestValid.calledTwice).to.equal(true); @@ -95,7 +99,7 @@ describe('bidders created by newBidder', function () { spec.isBidRequestValid.returns(false); spec.buildRequests.returns([]); - bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub); + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub); expect(ajaxStub.called).to.equal(false); expect(spec.isBidRequestValid.calledTwice).to.equal(true); @@ -109,7 +113,7 @@ describe('bidders created by newBidder', function () { spec.isBidRequestValid.onSecondCall().returns(false); spec.buildRequests.returns([]); - bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub); + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub); expect(ajaxStub.called).to.equal(false); expect(spec.isBidRequestValid.calledTwice).to.equal(true); @@ -123,7 +127,7 @@ describe('bidders created by newBidder', function () { spec.isBidRequestValid.returns(true); spec.buildRequests.returns([]); - bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub); + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub); expect(ajaxStub.called).to.equal(false); }); @@ -139,7 +143,7 @@ describe('bidders created by newBidder', function () { data: data }); - bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub); + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub); expect(ajaxStub.calledOnce).to.equal(true); expect(ajaxStub.firstCall.args[0]).to.equal(url); @@ -164,7 +168,7 @@ describe('bidders created by newBidder', function () { options: options }); - bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub); + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub); expect(ajaxStub.calledOnce).to.equal(true); expect(ajaxStub.firstCall.args[0]).to.equal(url); @@ -187,7 +191,7 @@ describe('bidders created by newBidder', function () { data: data }); - bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub); + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub); expect(ajaxStub.calledOnce).to.equal(true); expect(ajaxStub.firstCall.args[0]).to.equal(`${url}?arg=2&`); @@ -211,7 +215,7 @@ describe('bidders created by newBidder', function () { options: opt }); - bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub); + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub); expect(ajaxStub.calledOnce).to.equal(true); expect(ajaxStub.firstCall.args[0]).to.equal(`${url}?arg=2&`); @@ -240,7 +244,7 @@ describe('bidders created by newBidder', function () { } ]); - bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub); + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub); expect(ajaxStub.calledTwice).to.equal(true); }); @@ -253,7 +257,7 @@ describe('bidders created by newBidder', function () { spec.interpretResponse.returns([]); spec.getUserSyncs.returns([]); - bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub); + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub); expect(addBidResponseStub.callCount).to.equal(0); }); @@ -293,7 +297,7 @@ describe('bidders created by newBidder', function () { }); spec.getUserSyncs.returns([]); - bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub); + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub); expect(spec.interpretResponse.calledOnce).to.equal(true); const response = spec.interpretResponse.firstCall.args[0] @@ -325,7 +329,7 @@ describe('bidders created by newBidder', function () { ]); spec.getUserSyncs.returns([]); - bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub); + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub); expect(spec.interpretResponse.calledTwice).to.equal(true); expect(doneStub.calledOnce).to.equal(true); @@ -356,7 +360,7 @@ describe('bidders created by newBidder', function () { spec.interpretResponse.returns(bid); - bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub); + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub); expect(addBidResponseStub.calledOnce).to.equal(true); expect(addBidResponseStub.firstCall.args[0]).to.equal('mock/placement'); @@ -375,7 +379,7 @@ describe('bidders created by newBidder', function () { }); spec.getUserSyncs.returns([]); - bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub); + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub); expect(spec.getUserSyncs.calledOnce).to.equal(true); expect(spec.getUserSyncs.firstCall.args[1].length).to.equal(1); @@ -394,7 +398,7 @@ describe('bidders created by newBidder', function () { url: 'usersync.com' }]); - bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub); + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub); expect(userSyncStub.called).to.equal(true); expect(userSyncStub.firstCall.args[0]).to.equal('iframe'); @@ -423,7 +427,7 @@ describe('bidders created by newBidder', function () { spec.interpretResponse.returns(bid); - bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub); + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub); expect(logErrorSpy.calledOnce).to.equal(true); }); @@ -453,7 +457,7 @@ describe('bidders created by newBidder', function () { spec.interpretResponse.returns(bid); - bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub); + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub); expect(logErrorSpy.calledOnce).to.equal(true); }); @@ -485,7 +489,7 @@ describe('bidders created by newBidder', function () { }); spec.getUserSyncs.returns([]); - bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub); + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub); expect(spec.interpretResponse.called).to.equal(false); expect(doneStub.calledOnce).to.equal(true); @@ -503,7 +507,7 @@ describe('bidders created by newBidder', function () { spec.interpretResponse.returns([]); spec.getUserSyncs.returns([]); - bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub); + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub); expect(addBidResponseStub.callCount).to.equal(0); expect(doneStub.calledOnce).to.equal(true); @@ -520,7 +524,7 @@ describe('bidders created by newBidder', function () { }); spec.getUserSyncs.returns([]); - bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub); + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub); expect(spec.getUserSyncs.calledOnce).to.equal(true); expect(spec.getUserSyncs.firstCall.args[1]).to.deep.equal([]); @@ -534,8 +538,8 @@ describe('registerBidder', function () { let aliasBidAdapterStub; beforeEach(function () { - registerBidAdapterStub = sinon.stub(adaptermanager, 'registerBidAdapter'); - aliasBidAdapterStub = sinon.stub(adaptermanager, 'aliasBidAdapter'); + registerBidAdapterStub = sinon.stub(adapterManager, 'registerBidAdapter'); + aliasBidAdapterStub = sinon.stub(adapterManager, 'aliasBidAdapter'); }); afterEach(function () { @@ -639,7 +643,7 @@ describe('validate bid response: ', function () { it('should add native bids that do have required assets', function () { let bidRequest = { bids: [{ - bidId: 1, + bidId: '1', auctionId: 'first-bid-id', adUnitCode: 'mock/placement', params: { @@ -666,7 +670,7 @@ describe('validate bid response: ', function () { const bidder = newBidder(spec); spec.interpretResponse.returns(bids1); - bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub); + bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub); expect(addBidResponseStub.calledOnce).to.equal(true); expect(addBidResponseStub.firstCall.args[0]).to.equal('mock/placement'); @@ -676,7 +680,7 @@ describe('validate bid response: ', function () { it('should not add native bids that do not have required assets', function () { let bidRequest = { bids: [{ - bidId: 1, + bidId: '1', auctionId: 'first-bid-id', adUnitCode: 'mock/placement', params: { @@ -703,7 +707,7 @@ describe('validate bid response: ', function () { const bidder = newBidder(spec); spec.interpretResponse.returns(bids1); - bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub); + bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub); expect(addBidResponseStub.calledOnce).to.equal(false); expect(logErrorSpy.callCount).to.equal(1); @@ -712,7 +716,7 @@ describe('validate bid response: ', function () { it('should add bid when renderer is present on outstream bids', function () { let bidRequest = { bids: [{ - bidId: 1, + bidId: '1', auctionId: 'first-bid-id', adUnitCode: 'mock/placement', params: { @@ -736,7 +740,7 @@ describe('validate bid response: ', function () { const bidder = newBidder(spec); spec.interpretResponse.returns(bids1); - bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub); + bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub); expect(addBidResponseStub.calledOnce).to.equal(true); expect(addBidResponseStub.firstCall.args[0]).to.equal('mock/placement'); @@ -747,7 +751,7 @@ describe('validate bid response: ', function () { let bidRequest = { bids: [{ bidder: CODE, - bidId: 1, + bidId: '1', auctionId: 'first-bid-id', adUnitCode: 'mock/placement', params: { @@ -768,10 +772,135 @@ describe('validate bid response: ', function () { const bidder = newBidder(spec); spec.interpretResponse.returns(bids1); - bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub); + bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub); expect(addBidResponseStub.calledOnce).to.equal(true); expect(addBidResponseStub.firstCall.args[0]).to.equal('mock/placement'); expect(logErrorSpy.callCount).to.equal(0); }); }); + +describe('preload mapping url hook', function() { + let fakeTranslationServer; + let getLocalStorageStub; + let adapterManagerStub; + + beforeEach(function () { + fakeTranslationServer = sinon.fakeServer.create(); + getLocalStorageStub = sinon.stub(utils, 'getDataFromLocalStorage'); + adapterManagerStub = sinon.stub(adapterManager, 'getBidAdapter'); + }); + + afterEach(function() { + getLocalStorageStub.restore(); + adapterManagerStub.restore(); + config.resetConfig(); + }); + + it('should preload mapping url file', function() { + config.setConfig({ + 'adpod': { + 'brandCategoryExclusion': true + } + }); + let adUnits = [{ + code: 'midroll_1', + mediaTypes: { + video: { + context: 'adpod' + } + }, + bids: [ + { + bidder: 'sampleBidder1', + params: { + placementId: 14542875, + } + } + ] + }]; + getLocalStorageStub.returns(null); + adapterManagerStub.withArgs('sampleBidder1').returns({ + getSpec: function() { + return { + 'getMappingFileInfo': function() { + return { + url: 'http://sample.com', + refreshInDays: 7, + key: `sampleBidder1MappingFile` + } + } + } + } + }); + preloadBidderMappingFile(sinon.spy(), adUnits); + expect(fakeTranslationServer.requests.length).to.equal(1); + }); + + it('should preload mapping url file for all bidders', function() { + config.setConfig({ + 'adpod': { + 'brandCategoryExclusion': true + } + }); + let adUnits = [{ + code: 'midroll_1', + mediaTypes: { + video: { + context: 'adpod' + } + }, + bids: [ + { + bidder: 'sampleBidder1', + params: { + placementId: 14542875, + } + }, + { + bidder: 'sampleBidder2', + params: { + placementId: 123456, + } + } + ] + }]; + getLocalStorageStub.returns(null); + adapterManagerStub.withArgs('sampleBidder1').returns({ + getSpec: function() { + return { + 'getMappingFileInfo': function() { + return { + url: 'http://sample.com', + refreshInDays: 7, + key: `sampleBidder1MappingFile` + } + } + } + } + }); + adapterManagerStub.withArgs('sampleBidder2').returns({ + getSpec: function() { + return { + 'getMappingFileInfo': function() { + return { + url: 'http://sample.com', + refreshInDays: 7, + key: `sampleBidder2MappingFile` + } + } + } + } + }); + preloadBidderMappingFile(sinon.spy(), adUnits); + expect(fakeTranslationServer.requests.length).to.equal(2); + + config.setConfig({ + 'adpod': { + 'brandCategoryExclusion': false + } + }); + preloadBidderMappingFile(sinon.spy(), adUnits); + expect(fakeTranslationServer.requests.length).to.equal(2); + }); +}); diff --git a/test/spec/unit/core/targeting_spec.js b/test/spec/unit/core/targeting_spec.js index 2fdca462a35..ad94ebccfb2 100644 --- a/test/spec/unit/core/targeting_spec.js +++ b/test/spec/unit/core/targeting_spec.js @@ -1,10 +1,9 @@ import { expect } from 'chai'; -import { targeting as targetingInstance } from 'src/targeting'; +import { targeting as targetingInstance, filters, sortByDealAndPriceBucket } from 'src/targeting'; import { config } from 'src/config'; import { getAdUnits, createBidReceived } from 'test/fixtures/fixtures'; import CONSTANTS from 'src/constants.json'; import { auctionManager } from 'src/auctionManager'; -import * as targetingModule from 'src/targeting'; import * as utils from 'src/utils'; const bid1 = { @@ -27,11 +26,13 @@ const bid1 = { 'bidder': 'rubicon', 'size': '300x250', 'adserverTargeting': { - 'hb_bidder': 'rubicon', - 'hb_adid': '148018fe5e', - 'hb_pb': '0.53', - 'foobar': '300x250' + 'foobar': '300x250', + [CONSTANTS.TARGETING_KEYS.BIDDER]: 'rubicon', + [CONSTANTS.TARGETING_KEYS.AD_ID]: '148018fe5e', + [CONSTANTS.TARGETING_KEYS.PRICE_BUCKET]: '0.53', + [CONSTANTS.TARGETING_KEYS.DEAL]: '1234' }, + 'dealId': '1234', 'netRevenue': true, 'currency': 'USD', 'ttl': 300 @@ -57,10 +58,10 @@ const bid2 = { 'bidder': 'rubicon', 'size': '300x250', 'adserverTargeting': { - 'hb_bidder': 'rubicon', - 'hb_adid': '5454545', - 'hb_pb': '0.25', - 'foobar': '300x250' + 'foobar': '300x250', + [CONSTANTS.TARGETING_KEYS.BIDDER]: 'rubicon', + [CONSTANTS.TARGETING_KEYS.AD_ID]: '5454545', + [CONSTANTS.TARGETING_KEYS.PRICE_BUCKET]: '0.25' }, 'netRevenue': true, 'currency': 'USD', @@ -87,52 +88,467 @@ const bid3 = { 'bidder': 'rubicon', 'size': '300x600', 'adserverTargeting': { - 'hb_bidder': 'rubicon', - 'hb_adid': '48747745', - 'hb_pb': '0.75', - 'foobar': '300x600' + 'foobar': '300x600', + [CONSTANTS.TARGETING_KEYS.BIDDER]: 'rubicon', + [CONSTANTS.TARGETING_KEYS.AD_ID]: '48747745', + [CONSTANTS.TARGETING_KEYS.PRICE_BUCKET]: '0.75' }, 'netRevenue': true, 'currency': 'USD', 'ttl': 300 }; +const nativeBid1 = { + 'bidderCode': 'appnexus', + 'width': 0, + 'height': 0, + 'statusMessage': 'Bid available', + 'adId': '591e7c9354b633', + 'requestId': '24aae81e32d6f6', + 'mediaType': 'native', + 'source': 'client', + 'cpm': 10, + 'creativeId': 97494403, + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 300, + 'adUnitCode': '/19968336/prebid_native_example_1', + 'appnexus': { + 'buyerMemberId': 9325 + }, + 'meta': { + 'advertiserId': 2529885 + }, + 'native': { + 'title': 'This is a Prebid Native Creative', + 'body': 'This is a Prebid Native Creative. There are many like it, but this one is mine.', + 'sponsoredBy': 'Prebid.org', + 'clickUrl': 'http://prebid.org/dev-docs/show-native-ads.html', + 'clickTrackers': ['http://www.clickUrl.com/404'], + 'impressionTrackers': ['http://imp.trackerUrl.com/it1'], + 'javascriptTrackers': '', + 'image': { + 'url': 'http://vcdn.adnxs.com/p/creative-image/94/22/cd/0f/9422cd0f-f400-45d3-80f5-2b92629d9257.jpg', + 'height': 2250, + 'width': 3000 + }, + 'icon': { + 'url': 'http://vcdn.adnxs.com/p/creative-image/bd/59/a6/c6/bd59a6c6-0851-411d-a16d-031475a51312.png', + 'height': 83, + 'width': 127 + } + }, + 'auctionId': '72138a4a-b747-4192-9192-dcc41d675de8', + 'responseTimestamp': 1565785219461, + 'requestTimestamp': 1565785219405, + 'bidder': 'appnexus', + 'timeToRespond': 56, + 'pbLg': '5.00', + 'pbMg': '10.00', + 'pbHg': '10.00', + 'pbAg': '10.00', + 'pbDg': '10.00', + 'pbCg': '', + 'size': '0x0', + 'adserverTargeting': { + [CONSTANTS.TARGETING_KEYS.BIDDER]: 'appnexus', + [CONSTANTS.TARGETING_KEYS.AD_ID]: '591e7c9354b633', + [CONSTANTS.TARGETING_KEYS.PRICE_BUCKET]: '10.00', + [CONSTANTS.TARGETING_KEYS.SIZE]: '0x0', + [CONSTANTS.TARGETING_KEYS.SOURCE]: 'client', + [CONSTANTS.TARGETING_KEYS.FORMAT]: 'native', + [CONSTANTS.NATIVE_KEYS.title]: 'This is a Prebid Native Creative', + [CONSTANTS.NATIVE_KEYS.body]: 'This is a Prebid Native Creative. There are many like it, but this one is mine.', + [CONSTANTS.NATIVE_KEYS.sponsoredBy]: 'Prebid.org', + [CONSTANTS.NATIVE_KEYS.clickUrl]: 'http://prebid.org/dev-docs/show-native-ads.html', + [CONSTANTS.NATIVE_KEYS.image]: 'http://vcdn.adnxs.com/p/creative-image/94/22/cd/0f/9422cd0f-f400-45d3-80f5-2b92629d9257.jpg', + [CONSTANTS.NATIVE_KEYS.icon]: 'http://vcdn.adnxs.com/p/creative-image/bd/59/a6/c6/bd59a6c6-0851-411d-a16d-031475a51312.png' + } +}; +const nativeBid2 = { + 'bidderCode': 'dgads', + 'width': 0, + 'height': 0, + 'statusMessage': 'Bid available', + 'adId': '6e0aba55ed54e5', + 'requestId': '4de26ec83d9661', + 'mediaType': 'native', + 'source': 'client', + 'cpm': 1.90909091, + 'creativeId': 'xuidx6c901261b0x2b2', + 'currency': 'JPY', + 'netRevenue': true, + 'ttl': 60, + 'referrer': 'http://test.localhost:9999/integrationExamples/gpt/demo_native.html?pbjs_debug=true', + 'native': { + 'image': { + 'url': 'https://ads-tr.bigmining.com/img/300x250.png', + 'width': 300, + 'height': 250 + }, + 'title': 'Test Title', + 'body': 'Test Description', + 'sponsoredBy': 'test.com', + 'clickUrl': 'http://prebid.org/', + 'clickTrackers': ['https://www.clickUrl.com/404'], + 'impressionTrackers': [ + 'http://imp.trackerUrl.com/it2' + ] + }, + 'auctionId': '72138a4a-b747-4192-9192-dcc41d675de8', + 'responseTimestamp': 1565785219607, + 'requestTimestamp': 1565785219409, + 'bidder': 'dgads', + 'adUnitCode': '/19968336/prebid_native_example_1', + 'timeToRespond': 198, + 'pbLg': '1.50', + 'pbMg': '1.90', + 'pbHg': '1.90', + 'pbAg': '1.90', + 'pbDg': '1.90', + 'pbCg': '', + 'size': '0x0', + 'adserverTargeting': { + [CONSTANTS.TARGETING_KEYS.BIDDER]: 'dgads', + [CONSTANTS.TARGETING_KEYS.AD_ID]: '6e0aba55ed54e5', + [CONSTANTS.TARGETING_KEYS.PRICE_BUCKET]: '1.90', + [CONSTANTS.TARGETING_KEYS.SIZE]: '0x0', + [CONSTANTS.TARGETING_KEYS.SOURCE]: 'client', + [CONSTANTS.TARGETING_KEYS.FORMAT]: 'native', + [CONSTANTS.NATIVE_KEYS.image]: 'https://ads-tr.bigmining.com/img/300x250.png', + [CONSTANTS.NATIVE_KEYS.title]: 'Test Title', + [CONSTANTS.NATIVE_KEYS.body]: 'Test Description', + [CONSTANTS.NATIVE_KEYS.sponsoredBy]: 'test.com', + [CONSTANTS.NATIVE_KEYS.clickUrl]: 'http://prebid.org/' + } +}; + describe('targeting tests', function () { + let sandbox; + let enableSendAllBids = false; + let useBidCache; + + beforeEach(function() { + sandbox = sinon.sandbox.create(); + + useBidCache = true; + + let origGetConfig = config.getConfig; + sandbox.stub(config, 'getConfig').callsFake(function (key) { + if (key === 'enableSendAllBids') { + return enableSendAllBids; + } + if (key === 'useBidCache') { + return useBidCache; + } + return origGetConfig.apply(config, arguments); + }); + }); + + afterEach(function () { + sandbox.restore(); + }); + describe('getAllTargeting', function () { let amBidsReceivedStub; let amGetAdUnitsStub; let bidExpiryStub; + let logWarnStub; + let logErrorStub; + let bidsReceived; beforeEach(function () { - $$PREBID_GLOBAL$$._sendAllBids = false; - amBidsReceivedStub = sinon.stub(auctionManager, 'getBidsReceived').callsFake(function() { - return [bid1, bid2, bid3]; + bidsReceived = [bid1, bid2, bid3]; + + amBidsReceivedStub = sandbox.stub(auctionManager, 'getBidsReceived').callsFake(function() { + return bidsReceived; }); - amGetAdUnitsStub = sinon.stub(auctionManager, 'getAdUnitCodes').callsFake(function() { + amGetAdUnitsStub = sandbox.stub(auctionManager, 'getAdUnitCodes').callsFake(function() { return ['/123456/header-bid-tag-0']; }); - bidExpiryStub = sinon.stub(targetingModule, 'isBidNotExpired').returns(true); + bidExpiryStub = sandbox.stub(filters, 'isBidNotExpired').returns(true); + logWarnStub = sinon.stub(utils, 'logWarn'); + logErrorStub = sinon.stub(utils, 'logError'); }); - afterEach(function () { - auctionManager.getBidsReceived.restore(); - auctionManager.getAdUnitCodes.restore(); - targetingModule.isBidNotExpired.restore(); + afterEach(function() { + config.resetConfig(); + logWarnStub.restore(); + logErrorStub.restore(); + amBidsReceivedStub.restore(); + amGetAdUnitsStub.restore(); + bidExpiryStub.restore(); + }); + + describe('when hb_deal is present in bid.adserverTargeting', function () { + let bid4; + + beforeEach(function() { + bid4 = utils.deepClone(bid1); + bid4.adserverTargeting['hb_bidder'] = bid4.bidder = bid4.bidderCode = 'appnexus'; + bid4.cpm = 0; + enableSendAllBids = true; + + bidsReceived.push(bid4); + }); + + it('returns targeting with both hb_deal and hb_deal_{bidder_code}', function () { + const targeting = targetingInstance.getAllTargeting(['/123456/header-bid-tag-0']); + + // We should add both keys rather than one or the other + expect(targeting['/123456/header-bid-tag-0']).to.contain.keys('hb_deal', `hb_deal_${bid1.bidderCode}`, `hb_deal_${bid4.bidderCode}`); + + // We should assign both keys the same value + expect(targeting['/123456/header-bid-tag-0']['hb_deal']).to.deep.equal(targeting['/123456/header-bid-tag-0'][`hb_deal_${bid1.bidderCode}`]); + }); + }); + + it('will enforce a limit on the number of auction keys when auctionKeyMaxChars setting is active', function () { + config.setConfig({ + targetingControls: { + auctionKeyMaxChars: 150 + } + }); + + const targeting = targetingInstance.getAllTargeting(['/123456/header-bid-tag-0', '/123456/header-bid-tag-1']); + expect(targeting['/123456/header-bid-tag-1']).to.deep.equal({}); + expect(targeting['/123456/header-bid-tag-0']).to.contain.keys('hb_pb', 'hb_adid', 'hb_bidder', 'hb_deal'); + expect(targeting['/123456/header-bid-tag-0']['hb_adid']).to.equal(bid1.adId); + expect(logWarnStub.calledOnce).to.be.true; + }); + + it('will return an error when auctionKeyMaxChars setting is set too low for any auction keys to be allowed', function () { + config.setConfig({ + targetingControls: { + auctionKeyMaxChars: 50 + } + }); + + const targeting = targetingInstance.getAllTargeting(['/123456/header-bid-tag-0', '/123456/header-bid-tag-1']); + expect(targeting['/123456/header-bid-tag-1']).to.deep.equal({}); + expect(targeting['/123456/header-bid-tag-0']).to.deep.equal({}); + expect(logWarnStub.calledTwice).to.be.true; + expect(logErrorStub.calledOnce).to.be.true; + }); + + describe('when bidLimit is present in setConfig', function () { + let bid4; + + beforeEach(function() { + bid4 = utils.deepClone(bid1); + bid4.adserverTargeting['hb_bidder'] = bid4.bidder = bid4.bidderCode = 'appnexus'; + bid4.cpm = 2.25; + enableSendAllBids = true; + + bidsReceived.push(bid4); + }); + + it('selects the top n number of bids when enableSendAllBids is true and and bitLimit is set', function () { + config.setConfig({ + sendBidsControl: { + bidLimit: 1 + } + }); + + const targeting = targetingInstance.getAllTargeting(['/123456/header-bid-tag-0']); + let limitedBids = Object.keys(targeting['/123456/header-bid-tag-0']).filter(key => key.indexOf(CONSTANTS.TARGETING_KEYS.PRICE_BUCKET + '_') != -1) + + expect(limitedBids.length).to.equal(1); + }); + + it('sends all bids when enableSendAllBids is true and and bitLimit is above total number of bids received', function () { + config.setConfig({ + sendBidsControl: { + bidLimit: 50 + } + }); + + const targeting = targetingInstance.getAllTargeting(['/123456/header-bid-tag-0']); + let limitedBids = Object.keys(targeting['/123456/header-bid-tag-0']).filter(key => key.indexOf(CONSTANTS.TARGETING_KEYS.PRICE_BUCKET + '_') != -1) + + expect(limitedBids.length).to.equal(2); + }); + + it('Sends all bids when enableSendAllBids is true and and bitLimit is set to 0', function () { + config.setConfig({ + sendBidsControl: { + bidLimit: 0 + } + }); + + const targeting = targetingInstance.getAllTargeting(['/123456/header-bid-tag-0']); + let limitedBids = Object.keys(targeting['/123456/header-bid-tag-0']).filter(key => key.indexOf(CONSTANTS.TARGETING_KEYS.PRICE_BUCKET + '_') != -1) + + expect(limitedBids.length).to.equal(2); + }); + }); + + describe('targetingControls.alwaysIncludeDeals', function () { + let bid4; + + beforeEach(function() { + bid4 = utils.deepClone(bid1); + bid4.adserverTargeting = { + hb_deal: '4321', + hb_pb: '0.1', + hb_adid: '567891011', + hb_bidder: 'appnexus', + }; + bid4.bidder = bid4.bidderCode = 'appnexus'; + bid4.cpm = 0.1; // losing bid so not included if enableSendAllBids === false + bid4.dealId = '4321'; + enableSendAllBids = false; + + bidsReceived.push(bid4); + }); + + it('does not include losing deals when alwaysIncludeDeals not set', function () { + const targeting = targetingInstance.getAllTargeting(['/123456/header-bid-tag-0']); + + // Rubicon wins bid and has deal, but alwaysIncludeDeals is false, so only top bid plus deal_id + // appnexus does not get sent since alwaysIncludeDeals is not defined + expect(targeting['/123456/header-bid-tag-0']).to.deep.equal({ + 'hb_deal_rubicon': '1234', + 'hb_deal': '1234', + 'hb_pb': '0.53', + 'hb_adid': '148018fe5e', + 'hb_bidder': 'rubicon', + 'foobar': '300x250' + }); + }); + + it('does not include losing deals when alwaysIncludeDeals set to false', function () { + config.setConfig({ + targetingControls: { + alwaysIncludeDeals: false + } + }); + + const targeting = targetingInstance.getAllTargeting(['/123456/header-bid-tag-0']); + + // Rubicon wins bid and has deal, but alwaysIncludeDeals is false, so only top bid plus deal_id + // appnexus does not get sent since alwaysIncludeDeals is false + expect(targeting['/123456/header-bid-tag-0']).to.deep.equal({ + 'hb_deal_rubicon': '1234', // This is just how it works before this PR, always added no matter what for winner if they have deal + 'hb_deal': '1234', + 'hb_pb': '0.53', + 'hb_adid': '148018fe5e', + 'hb_bidder': 'rubicon', + 'foobar': '300x250' + }); + }); + + it('includes losing deals when alwaysIncludeDeals set to true and also winning deals bidder KVPs', function () { + config.setConfig({ + targetingControls: { + alwaysIncludeDeals: true + } + }); + const targeting = targetingInstance.getAllTargeting(['/123456/header-bid-tag-0']); + + // Rubicon wins bid and has a deal, so all KVPs for them are passed (top plus bidder specific) + // Appnexus had deal so passed through + expect(targeting['/123456/header-bid-tag-0']).to.deep.equal({ + 'hb_deal_rubicon': '1234', + 'hb_deal': '1234', + 'hb_pb': '0.53', + 'hb_adid': '148018fe5e', + 'hb_bidder': 'rubicon', + 'foobar': '300x250', + 'hb_pb_rubicon': '0.53', + 'hb_adid_rubicon': '148018fe5e', + 'hb_bidder_rubicon': 'rubicon', + 'hb_deal_appnexus': '4321', + 'hb_pb_appnexus': '0.1', + 'hb_adid_appnexus': '567891011', + 'hb_bidder_appnexus': 'appnexus' + }); + }); + + it('includes winning bid even when it is not a deal, plus other deal KVPs', function () { + config.setConfig({ + targetingControls: { + alwaysIncludeDeals: true + } + }); + let bid5 = utils.deepClone(bid4); + bid5.adserverTargeting = { + hb_pb: '3.0', + hb_adid: '111111', + hb_bidder: 'pubmatic', + }; + bid5.bidder = bid5.bidderCode = 'pubmatic'; + bid5.cpm = 3.0; // winning bid! + delete bid5.dealId; // no deal with winner + bidsReceived.push(bid5); + + const targeting = targetingInstance.getAllTargeting(['/123456/header-bid-tag-0']); + + // Pubmatic wins but no deal. So only top bid KVPs for them is sent + // Rubicon has a dealId so passed through + // Appnexus has a dealId so passed through + expect(targeting['/123456/header-bid-tag-0']).to.deep.equal({ + 'hb_bidder': 'pubmatic', + 'hb_adid': '111111', + 'hb_pb': '3.0', + 'foobar': '300x250', + 'hb_deal_rubicon': '1234', + 'hb_pb_rubicon': '0.53', + 'hb_adid_rubicon': '148018fe5e', + 'hb_bidder_rubicon': 'rubicon', + 'hb_deal_appnexus': '4321', + 'hb_pb_appnexus': '0.1', + 'hb_adid_appnexus': '567891011', + 'hb_bidder_appnexus': 'appnexus' + }); + }); }); - it('selects the top bid when _sendAllBids true', function () { - config.setConfig({ enableSendAllBids: true }); + it('selects the top bid when enableSendAllBids true', function () { + enableSendAllBids = true; let targeting = targetingInstance.getAllTargeting(['/123456/header-bid-tag-0']); // we should only get the targeting data for the one requested adunit expect(Object.keys(targeting).length).to.equal(1); - let sendAllBidCpm = Object.keys(targeting['/123456/header-bid-tag-0']).filter(key => key.indexOf('hb_pb_') != -1) + let sendAllBidCpm = Object.keys(targeting['/123456/header-bid-tag-0']).filter(key => key.indexOf(CONSTANTS.TARGETING_KEYS.PRICE_BUCKET + '_') != -1) // we shouldn't get more than 1 key for hb_pb_${bidder} expect(sendAllBidCpm.length).to.equal(1); // expect the winning CPM to be equal to the sendAllBidCPM - expect(targeting['/123456/header-bid-tag-0']['hb_pb_rubicon']).to.deep.equal(targeting['/123456/header-bid-tag-0']['hb_pb']); + expect(targeting['/123456/header-bid-tag-0'][CONSTANTS.TARGETING_KEYS.PRICE_BUCKET + '_rubicon']).to.deep.equal(targeting['/123456/header-bid-tag-0'][CONSTANTS.TARGETING_KEYS.PRICE_BUCKET]); + }); + + it('ensures keys are properly generated when enableSendAllBids is true and multiple bidders use native', function() { + const nativeAdUnitCode = '/19968336/prebid_native_example_1'; + enableSendAllBids = true; + + // update mocks for this test to return native bids + amBidsReceivedStub.callsFake(function() { + return [nativeBid1, nativeBid2]; + }); + amGetAdUnitsStub.callsFake(function() { + return [nativeAdUnitCode]; + }); + + let targeting = targetingInstance.getAllTargeting([nativeAdUnitCode]); + expect(targeting[nativeAdUnitCode].hb_native_image).to.equal(nativeBid1.native.image.url); + expect(targeting[nativeAdUnitCode].hb_native_linkurl).to.equal(nativeBid1.native.clickUrl); + expect(targeting[nativeAdUnitCode].hb_native_title).to.equal(nativeBid1.native.title); + expect(targeting[nativeAdUnitCode].hb_native_image_dgad).to.exist.and.to.equal(nativeBid2.native.image.url); + expect(targeting[nativeAdUnitCode].hb_pb_dgads).to.exist.and.to.equal(nativeBid2.pbMg); + expect(targeting[nativeAdUnitCode].hb_native_body_appne).to.exist.and.to.equal(nativeBid1.native.body); + }); + + it('does not include adpod type bids in the getBidsReceived results', function () { + let adpodBid = utils.deepClone(bid1); + adpodBid.video = { context: 'adpod', durationSeconds: 15, durationBucket: 15 }; + adpodBid.cpm = 5; + bidsReceived.push(adpodBid); + + const targeting = targetingInstance.getAllTargeting(['/123456/header-bid-tag-0']); + expect(targeting['/123456/header-bid-tag-0']).to.contain.keys('hb_deal', 'hb_adid', 'hb_bidder'); + expect(targeting['/123456/header-bid-tag-0']['hb_adid']).to.equal(bid1.adId); }); }); // end getAllTargeting tests @@ -142,20 +558,13 @@ describe('targeting tests', function () { let bidExpiryStub; beforeEach(function () { - $$PREBID_GLOBAL$$._sendAllBids = false; - amBidsReceivedStub = sinon.stub(auctionManager, 'getBidsReceived').callsFake(function() { + amBidsReceivedStub = sandbox.stub(auctionManager, 'getBidsReceived').callsFake(function() { return []; }); - amGetAdUnitsStub = sinon.stub(auctionManager, 'getAdUnitCodes').callsFake(function() { + amGetAdUnitsStub = sandbox.stub(auctionManager, 'getAdUnitCodes').callsFake(function() { return ['/123456/header-bid-tag-0']; }); - bidExpiryStub = sinon.stub(targetingModule, 'isBidNotExpired').returns(true); - }); - - afterEach(function () { - auctionManager.getBidsReceived.restore(); - auctionManager.getAdUnitCodes.restore(); - targetingModule.isBidNotExpired.restore(); + bidExpiryStub = sandbox.stub(filters, 'isBidNotExpired').returns(true); }); it('returns targetingSet correctly', function () { @@ -171,13 +580,8 @@ describe('targeting tests', function () { let bidExpiryStub; let auctionManagerStub; beforeEach(function () { - bidExpiryStub = sinon.stub(targetingModule, 'isBidNotExpired').returns(true); - auctionManagerStub = sinon.stub(auctionManager, 'getBidsReceived'); - }); - - afterEach(function () { - bidExpiryStub.restore(); - auctionManagerStub.restore(); + bidExpiryStub = sandbox.stub(filters, 'isBidNotExpired').returns(true); + auctionManagerStub = sandbox.stub(auctionManager, 'getBidsReceived'); }); it('should use bids from pool to get Winning Bid', function () { @@ -196,6 +600,30 @@ describe('targeting tests', function () { expect(bids[1].adId).to.equal('adid-2'); }); + it('should honor useBidCache', function() { + useBidCache = true; + + auctionManagerStub.returns([ + createBidReceived({bidder: 'appnexus', cpm: 7, auctionId: 1, responseTimestamp: 100, adUnitCode: 'code-0', adId: 'adid-1'}), + createBidReceived({bidder: 'appnexus', cpm: 5, auctionId: 2, responseTimestamp: 102, adUnitCode: 'code-0', adId: 'adid-2'}), + ]); + + let adUnitCodes = ['code-0']; + targetingInstance.setLatestAuctionForAdUnit('code-0', 2); + + let bids = targetingInstance.getWinningBids(adUnitCodes); + + expect(bids.length).to.equal(1); + expect(bids[0].adId).to.equal('adid-1'); + + useBidCache = false; + + bids = targetingInstance.getWinningBids(adUnitCodes); + + expect(bids.length).to.equal(1); + expect(bids[0].adId).to.equal('adid-2'); + }); + it('should not use rendered bid to get winning bid', function () { let bidsReceived = [ createBidReceived({bidder: 'appnexus', cpm: 8, auctionId: 1, responseTimestamp: 100, adUnitCode: 'code-0', adId: 'adid-1', status: 'rendered'}), @@ -235,14 +663,10 @@ describe('targeting tests', function () { let auctionManagerStub; let timestampStub; beforeEach(function () { - auctionManagerStub = sinon.stub(auctionManager, 'getBidsReceived'); - timestampStub = sinon.stub(utils, 'timestamp'); + auctionManagerStub = sandbox.stub(auctionManager, 'getBidsReceived'); + timestampStub = sandbox.stub(utils, 'timestamp'); }); - afterEach(function () { - auctionManagerStub.restore(); - timestampStub.restore(); - }); it('should not include expired bids in the auction', function () { timestampStub.returns(200000); // Pool is having 4 bids from 2 auctions. All the bids are expired and only bid #3 is passing the bidExpiry check. @@ -262,4 +686,164 @@ describe('targeting tests', function () { }); }); }); + + describe('sortByDealAndPriceBucket', function() { + it('will properly sort bids when some bids have deals and some do not', function () { + let bids = [{ + adUnitTargeting: { + hb_adid: 'abc', + hb_pb: '1.00', + hb_deal: '1234' + } + }, { + adUnitTargeting: { + hb_adid: 'def', + hb_pb: '0.50', + } + }, { + adUnitTargeting: { + hb_adid: 'ghi', + hb_pb: '20.00', + hb_deal: '4532' + } + }, { + adUnitTargeting: { + hb_adid: 'jkl', + hb_pb: '9.00', + hb_deal: '9864' + } + }, { + adUnitTargeting: { + hb_adid: 'mno', + hb_pb: '50.00', + } + }, { + adUnitTargeting: { + hb_adid: 'pqr', + hb_pb: '100.00', + } + }]; + bids.sort(sortByDealAndPriceBucket); + expect(bids[0].adUnitTargeting.hb_adid).to.equal('ghi'); + expect(bids[1].adUnitTargeting.hb_adid).to.equal('jkl'); + expect(bids[2].adUnitTargeting.hb_adid).to.equal('abc'); + expect(bids[3].adUnitTargeting.hb_adid).to.equal('pqr'); + expect(bids[4].adUnitTargeting.hb_adid).to.equal('mno'); + expect(bids[5].adUnitTargeting.hb_adid).to.equal('def'); + }); + + it('will properly sort bids when all bids have deals', function () { + let bids = [{ + adUnitTargeting: { + hb_adid: 'abc', + hb_pb: '1.00', + hb_deal: '1234' + } + }, { + adUnitTargeting: { + hb_adid: 'def', + hb_pb: '0.50', + hb_deal: '4321' + } + }, { + adUnitTargeting: { + hb_adid: 'ghi', + hb_pb: '2.50', + hb_deal: '4532' + } + }, { + adUnitTargeting: { + hb_adid: 'jkl', + hb_pb: '2.00', + hb_deal: '9864' + } + }]; + bids.sort(sortByDealAndPriceBucket); + expect(bids[0].adUnitTargeting.hb_adid).to.equal('ghi'); + expect(bids[1].adUnitTargeting.hb_adid).to.equal('jkl'); + expect(bids[2].adUnitTargeting.hb_adid).to.equal('abc'); + expect(bids[3].adUnitTargeting.hb_adid).to.equal('def'); + }); + + it('will properly sort bids when no bids have deals', function () { + let bids = [{ + adUnitTargeting: { + hb_adid: 'abc', + hb_pb: '1.00' + } + }, { + adUnitTargeting: { + hb_adid: 'def', + hb_pb: '0.10' + } + }, { + adUnitTargeting: { + hb_adid: 'ghi', + hb_pb: '10.00' + } + }, { + adUnitTargeting: { + hb_adid: 'jkl', + hb_pb: '10.01' + } + }, { + adUnitTargeting: { + hb_adid: 'mno', + hb_pb: '1.00' + } + }, { + adUnitTargeting: { + hb_adid: 'pqr', + hb_pb: '100.00' + } + }]; + bids.sort(sortByDealAndPriceBucket); + expect(bids[0].adUnitTargeting.hb_adid).to.equal('pqr'); + expect(bids[1].adUnitTargeting.hb_adid).to.equal('jkl'); + expect(bids[2].adUnitTargeting.hb_adid).to.equal('ghi'); + expect(bids[3].adUnitTargeting.hb_adid).to.equal('abc'); + expect(bids[4].adUnitTargeting.hb_adid).to.equal('mno'); + expect(bids[5].adUnitTargeting.hb_adid).to.equal('def'); + }); + }); + + describe('setTargetingForAst', function () { + let sandbox, + apnTagStub; + beforeEach(function() { + sandbox = sinon.createSandbox(); + sandbox.stub(targetingInstance, 'resetPresetTargetingAST'); + apnTagStub = sandbox.stub(window.apntag, 'setKeywords'); + }); + afterEach(function () { + sandbox.restore(); + }); + + it('should set single addUnit code', function() { + let adUnitCode = 'testdiv-abc-ad-123456-0'; + sandbox.stub(targetingInstance, 'getAllTargeting').returns({ + 'testdiv1-abc-ad-123456-0': {hb_bidder: 'appnexus'} + }); + targetingInstance.setTargetingForAst(adUnitCode); + expect(targetingInstance.getAllTargeting.called).to.equal(true); + expect(targetingInstance.resetPresetTargetingAST.called).to.equal(true); + expect(apnTagStub.callCount).to.equal(1); + expect(apnTagStub.getCall(0).args[0]).to.deep.equal('testdiv1-abc-ad-123456-0'); + expect(apnTagStub.getCall(0).args[1]).to.deep.equal({HB_BIDDER: 'appnexus'}); + }); + + it('should set array of addUnit codes', function() { + let adUnitCodes = ['testdiv1-abc-ad-123456-0', 'testdiv2-abc-ad-123456-0'] + sandbox.stub(targetingInstance, 'getAllTargeting').returns({ + 'testdiv1-abc-ad-123456-0': {hb_bidder: 'appnexus'}, + 'testdiv2-abc-ad-123456-0': {hb_bidder: 'appnexus'} + }); + targetingInstance.setTargetingForAst(adUnitCodes); + expect(targetingInstance.getAllTargeting.called).to.equal(true); + expect(targetingInstance.resetPresetTargetingAST.called).to.equal(true); + expect(apnTagStub.callCount).to.equal(2); + expect(apnTagStub.getCall(1).args[0]).to.deep.equal('testdiv2-abc-ad-123456-0'); + expect(apnTagStub.getCall(1).args[1]).to.deep.equal({HB_BIDDER: 'appnexus'}); + }); + }); }); diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index 41e40100011..485dd5cf077 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -9,12 +9,12 @@ import { createBidReceived } from 'test/fixtures/fixtures'; import { auctionManager, newAuctionManager } from 'src/auctionManager'; -import { targeting, newTargeting, RENDERED } from 'src/targeting'; +import { targeting, newTargeting, filters } from 'src/targeting'; import { config as configObj } from 'src/config'; import * as ajaxLib from 'src/ajax'; import * as auctionModule from 'src/auction'; import { newBidder, registerBidder } from 'src/adapters/bidderFactory'; -import * as targetingModule from 'src/targeting'; +import find from 'core-js/library/fn/array/find'; var assert = require('chai').assert; var expect = require('chai').expect; @@ -23,10 +23,9 @@ var urlParse = require('url-parse'); var prebid = require('src/prebid'); var utils = require('src/utils'); -// var bidmanager = require('src/bidmanager'); var bidfactory = require('src/bidfactory'); -var adloader = require('src/adloader'); -var adaptermanager = require('src/adaptermanager'); +var adloader = require('test/mocks/adloaderStub'); +var adapterManager = require('src/adapterManager').default; var events = require('src/events'); var adserver = require('src/adserver'); var CONSTANTS = require('src/constants.json'); @@ -109,6 +108,7 @@ var createSlotArrayScenario2 = function createSlotArrayScenario2() { window.googletag = { _slots: [], + _targeting: {}, pubads: function () { var self = this; return { @@ -118,6 +118,18 @@ window.googletag = { setSlots: function (slots) { self._slots = slots; + }, + + setTargeting: function(key, arrayOfValues) { + self._targeting[key] = arrayOfValues; + }, + + getTargeting: function() { + return self._targeting; + }, + + clearTargeting: function() { + self._targeting = {}; } }; } @@ -134,28 +146,58 @@ var createTagAST = function() { window.apntag = { keywords: [], tags: createTagAST(), - setKeywords: function(key, params) { + setKeywords: function(key, params, options) { var self = this; if (!self.tags.hasOwnProperty(key)) { return; } self.tags[key].keywords = this.tags[key].keywords || {}; + if (typeof options === 'object' && options !== null && options.overrideKeyValue === true) { + utils._each(params, function(param, id) { + self.tags[key].keywords[id] = param; + }); + } else { + utils._each(params, function (param, id) { + if (!self.tags[key].keywords.hasOwnProperty(id)) { + self.tags[key].keywords[id] = param; + } else if (!utils.isArray(self.tags[key].keywords[id])) { + self.tags[key].keywords[id] = [self.tags[key].keywords[id]].concat(param); + } else { + self.tags[key].keywords[id] = self.tags[key].keywords[id].concat(param); + } + }) + } + }, + getTag: function(tagId) { + return this.tags[tagId]; + }, + modifyTag: function(tagId, params) { + var output = {}; + + utils._each(this.tags[tagId], function(tag, id) { + output[id] = tag; + }); + utils._each(params, function(param, id) { - if (!self.tags[key].keywords.hasOwnProperty(id)) { self.tags[key].keywords[id] = param; } else if (!utils.isArray(self.tags[key].keywords[id])) { self.tags[key].keywords[id] = [self.tags[key].keywords[id]].concat(param); } else { self.tags[key].keywords[id] = self.tags[key].keywords[id].concat(param); } + output[id] = param; }); + + this.tags[tagId] = output; } -}; +} describe('Unit: Prebid Module', function () { let bidExpiryStub; - before(function () { - bidExpiryStub = sinon.stub(targetingModule, 'isBidNotExpired').callsFake(() => true); + beforeEach(function () { + bidExpiryStub = sinon.stub(filters, 'isBidNotExpired').callsFake(() => true); + configObj.setConfig({ useBidCache: true }); }); - after(function() { + afterEach(function() { $$PREBID_GLOBAL$$.adUnits = []; - targetingModule.isBidNotExpired.restore(); + bidExpiryStub.restore(); + configObj.setConfig({ useBidCache: false }); }); describe('getAdserverTargetingForAdUnitCodeStr', function () { @@ -166,7 +208,7 @@ describe('Unit: Prebid Module', function () { it('should return targeting info as a string', function () { const adUnitCode = config.adUnitCodes[0]; $$PREBID_GLOBAL$$.setConfig({ enableSendAllBids: true }); - var expected = 'foobar=0x0%2C300x250%2C300x600&hb_size=300x250&hb_pb=10.00&hb_adid=233bcbee889d46d&hb_bidder=appnexus&hb_size_triplelift=0x0&hb_pb_triplelift=10.00&hb_adid_triplelift=222bb26f9e8bd&hb_bidder_triplelift=triplelift&hb_size_appnexus=300x250&hb_pb_appnexus=10.00&hb_adid_appnexus=233bcbee889d46d&hb_bidder_appnexus=appnexus&hb_size_pagescience=300x250&hb_pb_pagescience=10.00&hb_adid_pagescience=25bedd4813632d7&hb_bidder_pagescienc=pagescience&hb_size_brightcom=300x250&hb_pb_brightcom=10.00&hb_adid_brightcom=26e0795ab963896&hb_bidder_brightcom=brightcom&hb_size_brealtime=300x250&hb_pb_brealtime=10.00&hb_adid_brealtime=275bd666f5a5a5d&hb_bidder_brealtime=brealtime&hb_size_pubmatic=300x250&hb_pb_pubmatic=10.00&hb_adid_pubmatic=28f4039c636b6a7&hb_bidder_pubmatic=pubmatic&hb_size_rubicon=300x600&hb_pb_rubicon=10.00&hb_adid_rubicon=29019e2ab586a5a&hb_bidder_rubicon=rubicon'; + var expected = 'foobar=0x0%2C300x250%2C300x600&' + CONSTANTS.TARGETING_KEYS.SIZE + '=300x250&' + CONSTANTS.TARGETING_KEYS.PRICE_BUCKET + '=10.00&' + CONSTANTS.TARGETING_KEYS.AD_ID + '=233bcbee889d46d&' + CONSTANTS.TARGETING_KEYS.BIDDER + '=appnexus&' + CONSTANTS.TARGETING_KEYS.SIZE + '_triplelift=0x0&' + CONSTANTS.TARGETING_KEYS.PRICE_BUCKET + '_triplelift=10.00&' + CONSTANTS.TARGETING_KEYS.AD_ID + '_triplelift=222bb26f9e8bd&' + CONSTANTS.TARGETING_KEYS.BIDDER + '_triplelift=triplelift&' + CONSTANTS.TARGETING_KEYS.SIZE + '_appnexus=300x250&' + CONSTANTS.TARGETING_KEYS.PRICE_BUCKET + '_appnexus=10.00&' + CONSTANTS.TARGETING_KEYS.AD_ID + '_appnexus=233bcbee889d46d&' + CONSTANTS.TARGETING_KEYS.BIDDER + '_appnexus=appnexus&' + CONSTANTS.TARGETING_KEYS.SIZE + '_pagescience=300x250&' + CONSTANTS.TARGETING_KEYS.PRICE_BUCKET + '_pagescience=10.00&' + CONSTANTS.TARGETING_KEYS.AD_ID + '_pagescience=25bedd4813632d7&' + CONSTANTS.TARGETING_KEYS.BIDDER + '_pagescienc=pagescience&' + CONSTANTS.TARGETING_KEYS.SIZE + '_brightcom=300x250&' + CONSTANTS.TARGETING_KEYS.PRICE_BUCKET + '_brightcom=10.00&' + CONSTANTS.TARGETING_KEYS.AD_ID + '_brightcom=26e0795ab963896&' + CONSTANTS.TARGETING_KEYS.BIDDER + '_brightcom=brightcom&' + CONSTANTS.TARGETING_KEYS.SIZE + '_brealtime=300x250&' + CONSTANTS.TARGETING_KEYS.PRICE_BUCKET + '_brealtime=10.00&' + CONSTANTS.TARGETING_KEYS.AD_ID + '_brealtime=275bd666f5a5a5d&' + CONSTANTS.TARGETING_KEYS.BIDDER + '_brealtime=brealtime&' + CONSTANTS.TARGETING_KEYS.SIZE + '_pubmatic=300x250&' + CONSTANTS.TARGETING_KEYS.PRICE_BUCKET + '_pubmatic=10.00&' + CONSTANTS.TARGETING_KEYS.AD_ID + '_pubmatic=28f4039c636b6a7&' + CONSTANTS.TARGETING_KEYS.BIDDER + '_pubmatic=pubmatic&' + CONSTANTS.TARGETING_KEYS.SIZE + '_rubicon=300x600&' + CONSTANTS.TARGETING_KEYS.PRICE_BUCKET + '_rubicon=10.00&' + CONSTANTS.TARGETING_KEYS.AD_ID + '_rubicon=29019e2ab586a5a&' + CONSTANTS.TARGETING_KEYS.BIDDER + '_rubicon=rubicon'; var result = $$PREBID_GLOBAL$$.getAdserverTargetingForAdUnitCodeStr(adUnitCode); assert.equal(expected, result, 'returns expected string of ad targeting info'); }); @@ -212,17 +254,17 @@ describe('Unit: Prebid Module', function () { var expected = { '/19968336/header-bid-tag-0': { foobar: '0x0,300x250,300x600', - hb_size: '300x250', - hb_pb: '10.00', - hb_adid: '233bcbee889d46d', - hb_bidder: 'appnexus' + [CONSTANTS.TARGETING_KEYS.SIZE]: '300x250', + [CONSTANTS.TARGETING_KEYS.PRICE_BUCKET]: '10.00', + [CONSTANTS.TARGETING_KEYS.AD_ID]: '233bcbee889d46d', + [CONSTANTS.TARGETING_KEYS.BIDDER]: 'appnexus' }, '/19968336/header-bid-tag1': { foobar: '728x90', - hb_size: '728x90', - hb_pb: '10.00', - hb_adid: '24bd938435ec3fc', - hb_bidder: 'appnexus' + [CONSTANTS.TARGETING_KEYS.SIZE]: '728x90', + [CONSTANTS.TARGETING_KEYS.PRICE_BUCKET]: '10.00', + [CONSTANTS.TARGETING_KEYS.AD_ID]: '24bd938435ec3fc', + [CONSTANTS.TARGETING_KEYS.BIDDER]: 'appnexus' } }; assert.deepEqual(targeting, expected); @@ -259,18 +301,18 @@ describe('Unit: Prebid Module', function () { var expected = { '/19968336/header-bid-tag-0': { foobar: '300x250,300x600', - hb_size: '300x250', - hb_pb: '10.00', - hb_adid: '233bcbee889d46d', - hb_bidder: 'appnexus', - always_use_me: 'abc' + always_use_me: 'abc', + [CONSTANTS.TARGETING_KEYS.SIZE]: '300x250', + [CONSTANTS.TARGETING_KEYS.PRICE_BUCKET]: '10.00', + [CONSTANTS.TARGETING_KEYS.AD_ID]: '233bcbee889d46d', + [CONSTANTS.TARGETING_KEYS.BIDDER]: 'appnexus' }, '/19968336/header-bid-tag1': { foobar: '728x90', - hb_size: '728x90', - hb_pb: '10.00', - hb_adid: '24bd938435ec3fc', - hb_bidder: 'appnexus' + [CONSTANTS.TARGETING_KEYS.SIZE]: '728x90', + [CONSTANTS.TARGETING_KEYS.PRICE_BUCKET]: '10.00', + [CONSTANTS.TARGETING_KEYS.AD_ID]: '24bd938435ec3fc', + [CONSTANTS.TARGETING_KEYS.BIDDER]: 'appnexus' } }; assert.deepEqual(targeting, expected); @@ -289,7 +331,7 @@ describe('Unit: Prebid Module', function () { $$PREBID_GLOBAL$$.bidderSettings = { 'standard': { adserverTargeting: [{ - key: 'hb_bidder', + key: CONSTANTS.TARGETING_KEYS.BIDDER, val: function(bidResponse) { return bidResponse.bidderCode; } @@ -299,7 +341,7 @@ describe('Unit: Prebid Module', function () { return bidResponse.adId; } }, { - key: 'hb_pb', + key: CONSTANTS.TARGETING_KEYS.PRICE_BUCKET, val: function(bidResponse) { return bidResponse.pbMg; } @@ -317,22 +359,21 @@ describe('Unit: Prebid Module', function () { var expected = { '/19968336/header-bid-tag-0': { foobar: '300x250', - hb_size: '300x250', - hb_pb: '10.00', - hb_adid: '233bcbee889d46d', - hb_bidder: 'appnexus', - custom_ad_id: '233bcbee889d46d' + custom_ad_id: '233bcbee889d46d', + [CONSTANTS.TARGETING_KEYS.SIZE]: '300x250', + [CONSTANTS.TARGETING_KEYS.PRICE_BUCKET]: '10.00', + [CONSTANTS.TARGETING_KEYS.AD_ID]: '233bcbee889d46d', + [CONSTANTS.TARGETING_KEYS.BIDDER]: 'appnexus' }, '/19968336/header-bid-tag1': { foobar: '728x90', - hb_size: '728x90', - hb_pb: '10.00', - hb_adid: '24bd938435ec3fc', - hb_bidder: 'appnexus', + [CONSTANTS.TARGETING_KEYS.SIZE]: '728x90', + [CONSTANTS.TARGETING_KEYS.PRICE_BUCKET]: '10.00', + [CONSTANTS.TARGETING_KEYS.AD_ID]: '24bd938435ec3fc', + [CONSTANTS.TARGETING_KEYS.BIDDER]: 'appnexus', custom_ad_id: '24bd938435ec3fc' } }; - assert.deepEqual(targeting, expected); $$PREBID_GLOBAL$$.bidderSettings = {}; }); @@ -406,7 +447,9 @@ describe('Unit: Prebid Module', function () { 'trackers': [{ 'impression_urls': ['http://lax1-ib.adnxs.com/impression'] }] - } + }, + 'viewability': { + 'config': ''} }] }] }; @@ -415,7 +458,7 @@ describe('Unit: Prebid Module', function () { $$PREBID_GLOBAL$$.bidderSettings = {}; currentPriceBucket = configObj.getConfig('priceGranularity'); configObj.setConfig({ priceGranularity: customConfigObject }); - sinon.stub(adaptermanager, 'makeBidRequests').callsFake(() => ([{ + sinon.stub(adapterManager, 'makeBidRequests').callsFake(() => ([{ 'bidderCode': 'appnexus', 'auctionId': '20882439e3238c', 'bidderRequestId': '331f3cf3f1d9c8', @@ -449,7 +492,7 @@ describe('Unit: Prebid Module', function () { after(function () { configObj.setConfig({ priceGranularity: currentPriceBucket }); - adaptermanager.makeBidRequests.restore(); + adapterManager.makeBidRequests.restore(); }) beforeEach(function () { @@ -480,32 +523,32 @@ describe('Unit: Prebid Module', function () { ajaxStub.restore(); }); - it('should get correct hb_pb when using bid.cpm is between 0 to 5', function () { + it('should get correct ' + CONSTANTS.TARGETING_KEYS.PRICE_BUCKET + ' when using bid.cpm is between 0 to 5', function() { RESPONSE.tags[0].ads[0].cpm = 2.1234; auction.callBids(cbTimeout); let bidTargeting = targeting.getAllTargeting(); - expect(bidTargeting['div-gpt-ad-1460505748561-0']['hb_pb']).to.equal('2.12'); + expect(bidTargeting['div-gpt-ad-1460505748561-0'][CONSTANTS.TARGETING_KEYS.PRICE_BUCKET]).to.equal('2.12'); }); - it('should get correct hb_pb when using bid.cpm is between 5 to 8', function () { + it('should get correct ' + CONSTANTS.TARGETING_KEYS.PRICE_BUCKET + ' when using bid.cpm is between 5 to 8', function() { RESPONSE.tags[0].ads[0].cpm = 6.78; auction.callBids(cbTimeout); let bidTargeting = targeting.getAllTargeting(); - expect(bidTargeting['div-gpt-ad-1460505748561-0']['hb_pb']).to.equal('6.75'); + expect(bidTargeting['div-gpt-ad-1460505748561-0'][CONSTANTS.TARGETING_KEYS.PRICE_BUCKET]).to.equal('6.75'); }); - it('should get correct hb_pb when using bid.cpm is between 8 to 20', function () { + it('should get correct ' + CONSTANTS.TARGETING_KEYS.PRICE_BUCKET + ' when using bid.cpm is between 8 to 20', function() { RESPONSE.tags[0].ads[0].cpm = 19.5234; auction.callBids(cbTimeout); let bidTargeting = targeting.getAllTargeting(); - expect(bidTargeting['div-gpt-ad-1460505748561-0']['hb_pb']).to.equal('19.50'); + expect(bidTargeting['div-gpt-ad-1460505748561-0'][CONSTANTS.TARGETING_KEYS.PRICE_BUCKET]).to.equal('19.50'); }); - it('should get correct hb_pb when using bid.cpm is between 20 to 25', function () { + it('should get correct ' + CONSTANTS.TARGETING_KEYS.PRICE_BUCKET + ' when using bid.cpm is between 20 to 25', function() { RESPONSE.tags[0].ads[0].cpm = 21.5234; auction.callBids(cbTimeout); let bidTargeting = targeting.getAllTargeting(); - expect(bidTargeting['div-gpt-ad-1460505748561-0']['hb_pb']).to.equal('21.00'); + expect(bidTargeting['div-gpt-ad-1460505748561-0'][CONSTANTS.TARGETING_KEYS.PRICE_BUCKET]).to.equal('21.00'); }); }); @@ -546,7 +589,9 @@ describe('Unit: Prebid Module', function () { 'trackers': [{ 'impression_urls': ['http://lax1-ib.adnxs.com/impression'] }] - } + }, + 'viewability': { + 'config': ''} }] }] }; @@ -578,7 +623,9 @@ describe('Unit: Prebid Module', function () { 'trackers': [{ 'impression_urls': ['http://lax1-ib.adnxs.com/impression'] }] - } + }, + 'viewability': { + 'config': ''} }] }] }; @@ -680,7 +727,7 @@ describe('Unit: Prebid Module', function () { before(function () { currentPriceBucket = configObj.getConfig('priceGranularity'); - sinon.stub(adaptermanager, 'makeBidRequests').callsFake(() => ([{ + sinon.stub(adapterManager, 'makeBidRequests').callsFake(() => ([{ 'bidderCode': 'appnexus', 'auctionId': '20882439e3238c', 'bidderRequestId': '331f3cf3f1d9c8', @@ -713,14 +760,14 @@ describe('Unit: Prebid Module', function () { after(function () { configObj.setConfig({ priceGranularity: currentPriceBucket }); - adaptermanager.makeBidRequests.restore(); + adapterManager.makeBidRequests.restore(); }) afterEach(function () { ajaxStub.restore(); }); - it('should get correct hb_pb with cpm between 0 - 5', function () { + it('should get correct ' + CONSTANTS.TARGETING_KEYS.PRICE_BUCKET + ' with cpm between 0 - 5', function() { initTestConfig({ adUnits: [createAdUnit('div-gpt-ad-1460505748561-0')], adUnitCodes: ['div-gpt-ad-1460505748561-0'] @@ -731,10 +778,10 @@ describe('Unit: Prebid Module', function () { auction.callBids(cbTimeout); let bidTargeting = targeting.getAllTargeting(); - expect(bidTargeting['div-gpt-ad-1460505748561-0']['hb_pb']).to.equal('3.25'); + expect(bidTargeting['div-gpt-ad-1460505748561-0'][CONSTANTS.TARGETING_KEYS.PRICE_BUCKET]).to.equal('3.25'); }); - it('should get correct hb_pb with cpm between 21 - 100', function () { + it('should get correct ' + CONSTANTS.TARGETING_KEYS.PRICE_BUCKET + ' with cpm between 21 - 100', function() { initTestConfig({ adUnits: [createAdUnit('div-gpt-ad-1460505748561-0')], adUnitCodes: ['div-gpt-ad-1460505748561-0'] @@ -745,7 +792,7 @@ describe('Unit: Prebid Module', function () { auction.callBids(cbTimeout); let bidTargeting = targeting.getAllTargeting(); - expect(bidTargeting['div-gpt-ad-1460505748561-0']['hb_pb']).to.equal('43.00'); + expect(bidTargeting['div-gpt-ad-1460505748561-0'][CONSTANTS.TARGETING_KEYS.PRICE_BUCKET]).to.equal('43.00'); }); it('should only apply price granularity if bid media type matches', function () { @@ -759,18 +806,25 @@ describe('Unit: Prebid Module', function () { auction.callBids(cbTimeout); let bidTargeting = targeting.getAllTargeting(); - expect(bidTargeting['div-gpt-ad-1460505748561-0']['hb_pb']).to.equal('3.00'); + expect(bidTargeting['div-gpt-ad-1460505748561-0'][CONSTANTS.TARGETING_KEYS.PRICE_BUCKET]).to.equal('3.00'); }); }); describe('getBidResponses', function () { + it('should return empty obj when last auction Id had no responses', function () { + auctionManager.getLastAuctionId = () => 999994; + var result = $$PREBID_GLOBAL$$.getBidResponses(); + assert.deepEqual(result, {}, 'expected bid responses are returned'); + }); + it('should return expected bid responses when not passed an adunitCode', function () { + auctionManager.getLastAuctionId = () => 654321; var result = $$PREBID_GLOBAL$$.getBidResponses(); var compare = getBidResponsesFromAPI(); assert.deepEqual(result, compare, 'expected bid responses are returned'); }); - it('should return bid responses for most recent requestId only', function () { + it('should return bid responses for most recent auctionId only', function () { const responses = $$PREBID_GLOBAL$$.getBidResponses(); assert.equal(responses[Object.keys(responses)[0]].bids.length, 4); }); @@ -821,7 +875,7 @@ describe('Unit: Prebid Module', function () { $$PREBID_GLOBAL$$.setConfig({ enableSendAllBids: true }); $$PREBID_GLOBAL$$.setTargetingForGPTAsync('/19968336/header-bid-tag-0'); - expect(slots[0].spySetTargeting.args).to.deep.contain.members([['hb_bidder', 'appnexus'], ['hb_adid_appnexus', '233bcbee889d46d'], ['hb_pb_appnexus', '10.00']]); + expect(slots[0].spySetTargeting.args).to.deep.contain.members([[CONSTANTS.TARGETING_KEYS.BIDDER, 'appnexus'], [CONSTANTS.TARGETING_KEYS.AD_ID + '_appnexus', '233bcbee889d46d'], [CONSTANTS.TARGETING_KEYS.PRICE_BUCKET + '_appnexus', '10.00']]); }); it('should set targeting when passed an array of ad unit codes with enableSendAllBids', function () { @@ -830,7 +884,7 @@ describe('Unit: Prebid Module', function () { $$PREBID_GLOBAL$$.setConfig({ enableSendAllBids: true }); $$PREBID_GLOBAL$$.setTargetingForGPTAsync(['/19968336/header-bid-tag-0']); - expect(slots[0].spySetTargeting.args).to.deep.contain.members([['hb_bidder', 'appnexus'], ['hb_adid_appnexus', '233bcbee889d46d'], ['hb_pb_appnexus', '10.00']]); + expect(slots[0].spySetTargeting.args).to.deep.contain.members([[CONSTANTS.TARGETING_KEYS.BIDDER, 'appnexus'], [CONSTANTS.TARGETING_KEYS.AD_ID + '_appnexus', '233bcbee889d46d'], [CONSTANTS.TARGETING_KEYS.PRICE_BUCKET + '_appnexus', '10.00']]); }); it('should set targeting from googletag data', function () { @@ -877,19 +931,19 @@ describe('Unit: Prebid Module', function () { var expected = [ [ - 'hb_bidder', + CONSTANTS.TARGETING_KEYS.BIDDER, 'appnexus' ], [ - 'hb_adid', + CONSTANTS.TARGETING_KEYS.AD_ID, '233bcbee889d46d' ], [ - 'hb_pb', + CONSTANTS.TARGETING_KEYS.PRICE_BUCKET, '10.00' ], [ - 'hb_size', + CONSTANTS.TARGETING_KEYS.SIZE, '300x250' ], [ @@ -936,7 +990,7 @@ describe('Unit: Prebid Module', function () { var spyLogError = null; var spyLogMessage = null; var inIframe = true; - let triggerPixelStub; + var triggerPixelStub; function pushBidResponseToAuction(obj) { adResponse = Object.assign({ @@ -976,7 +1030,7 @@ describe('Unit: Prebid Module', function () { inIframe = true; sinon.stub(utils, 'inIframe').callsFake(() => inIframe); - triggerPixelStub = sinon.stub(utils, 'triggerPixel'); + triggerPixelStub = sinon.stub(utils.internal, 'triggerPixel'); }); afterEach(function () { @@ -984,7 +1038,7 @@ describe('Unit: Prebid Module', function () { utils.logError.restore(); utils.logMessage.restore(); utils.inIframe.restore(); - utils.triggerPixel.restore(); + triggerPixelStub.restore(); }); it('should require doc and id params', function () { @@ -1130,7 +1184,7 @@ describe('Unit: Prebid Module', function () { beforeEach(function () { logMessageSpy = sinon.spy(utils, 'logMessage'); - makeRequestsStub = sinon.stub(adaptermanager, 'makeBidRequests'); + makeRequestsStub = sinon.stub(adapterManager, 'makeBidRequests'); makeRequestsStub.returns(bidRequests); xhr = sinon.useFakeXMLHttpRequest(); @@ -1153,7 +1207,7 @@ describe('Unit: Prebid Module', function () { afterEach(function () { clock.restore(); - adaptermanager.makeBidRequests.restore(); + adapterManager.makeBidRequests.restore(); auctionModule.newAuction.restore(); utils.logMessage.restore(); xhr.restore(); @@ -1165,7 +1219,8 @@ describe('Unit: Prebid Module', function () { isBidRequestValid: sinon.stub(), buildRequests: sinon.stub(), interpretResponse: sinon.stub(), - getUserSyncs: sinon.stub() + getUserSyncs: sinon.stub(), + onTimeout: sinon.stub() }; registerBidder(spec); @@ -1190,9 +1245,90 @@ describe('Unit: Prebid Module', function () { expect(bidsBackHandlerStub.getCall(0).args[1]).to.equal(true, 'bidsBackHandler should be called with timedOut=true'); + + sinon.assert.called(spec.onTimeout); + }); + + it('should execute callback after setTargeting', function () { + let spec = { + code: BIDDER_CODE, + isBidRequestValid: sinon.stub(), + buildRequests: sinon.stub(), + interpretResponse: sinon.stub(), + onSetTargeting: sinon.stub() + }; + + registerBidder(spec); + spec.buildRequests.returns([{'id': 123, 'method': 'POST'}]); + spec.isBidRequestValid.returns(true); + spec.interpretResponse.returns(bids); + + const bidId = 1; + const auctionId = 1; + let adResponse = Object.assign({ + auctionId: auctionId, + adId: String(bidId), + width: 300, + height: 250, + adUnitCode: bidRequests[0].bids[0].adUnitCode, + adserverTargeting: { + 'hb_bidder': BIDDER_CODE, + 'hb_adid': bidId, + 'hb_pb': bids[0].cpm, + 'hb_size': '300x250', + }, + bidder: bids[0].bidderCode, + }, bids[0]); + auction.getBidsReceived = function() { return [adResponse]; } + auction.getAuctionId = () => auctionId; + + clock = sinon.useFakeTimers(); + let requestObj = { + bidsBackHandler: null, // does not need to be defined because of newAuction mock in beforeEach + timeout: 2000, + adUnits: adUnits + }; + + $$PREBID_GLOBAL$$.requestBids(requestObj); + $$PREBID_GLOBAL$$.setTargetingForGPTAsync(); + + sinon.assert.called(spec.onSetTargeting); }); }) + describe('requestBids', function () { + let sandbox; + beforeEach(function () { + sandbox = sinon.sandbox.create(); + }); + afterEach(function () { + sandbox.restore(); + }); + describe('bidRequests is empty', function () { + it('should log warning message and execute callback if bidRequests is empty', function () { + let bidsBackHandler = function bidsBackHandlerCallback() {}; + let spyExecuteCallback = sinon.spy(bidsBackHandler); + let logWarnSpy = sandbox.spy(utils, 'logWarn'); + + $$PREBID_GLOBAL$$.requestBids({ + adUnits: [ + { + code: 'test1', + bids: [], + }, { + code: 'test2', + bids: [], + } + ], + bidsBackHandler: spyExecuteCallback + }); + + assert.ok(logWarnSpy.calledWith('No valid bid requests returned for auction'), 'expected warning message was logged'); + assert.ok(spyExecuteCallback.calledOnce, 'callback executed when bidRequests is empty'); + }); + }); + }); + describe('requestBids', function () { let xhr; let requests; @@ -1208,7 +1344,9 @@ describe('Unit: Prebid Module', function () { }); var adUnitsBackup; var auctionManagerStub; - let logMessageSpy + let logMessageSpy; + let logInfoSpy; + let logErrorSpy; let spec = { code: 'sampleBidder', @@ -1229,12 +1367,16 @@ describe('Unit: Prebid Module', function () { return auction; }); logMessageSpy = sinon.spy(utils, 'logMessage'); + logInfoSpy = sinon.spy(utils, 'logInfo'); + logErrorSpy = sinon.spy(utils, 'logError'); }); afterEach(function () { auction.getAdUnits = adUnitsBackup; auctionManager.createAuction.restore(); utils.logMessage.restore(); + utils.logInfo.restore(); + utils.logError.restore(); resetAuction(); }); @@ -1248,7 +1390,7 @@ describe('Unit: Prebid Module', function () { assert.ok(logMessageSpy.calledWith('No adUnits configured. No bids requested.'), 'expected message was logged'); }); - it('should attach transactionIds to ads (or pass through transactionId if it already exists)', function () { + it('should always attach new transactionIds to adUnits passed to requestBids', function () { $$PREBID_GLOBAL$$.requestBids({ adUnits: [ { @@ -1263,11 +1405,35 @@ describe('Unit: Prebid Module', function () { }); expect(auctionArgs.adUnits[0]).to.have.property('transactionId') - .and.to.equal('d0676a3c-ff32-45a5-af65-8175a8e7ddca'); + .and.to.match(/[a-f0-9\-]{36}/i) + .and.not.to.equal('d0676a3c-ff32-45a5-af65-8175a8e7ddca'); expect(auctionArgs.adUnits[1]).to.have.property('transactionId') .and.to.match(/[a-f0-9\-]{36}/i); }); + it('should notify targeting of the latest auction for each adUnit', function () { + let latestStub = sinon.stub(targeting, 'setLatestAuctionForAdUnit'); + let getAuctionStub = sinon.stub(auction, 'getAuctionId').returns(2); + + $$PREBID_GLOBAL$$.requestBids({ + adUnits: [ + { + code: 'test1', + bids: [] + }, { + code: 'test2', + bids: [] + } + ] + }); + + expect(latestStub.firstCall.calledWith('test1', 2)).to.equal(true); + expect(latestStub.secondCall.calledWith('test2', 2)).to.equal(true); + + latestStub.restore(); + getAuctionStub.restore(); + }); + it('should execute callback immediately if adUnits is empty', function () { var bidsBackHandler = function bidsBackHandlerCallback() {}; var spyExecuteCallback = sinon.spy(bidsBackHandler); @@ -1295,6 +1461,240 @@ describe('Unit: Prebid Module', function () { $$PREBID_GLOBAL$$.requestBids(requestObj); }).not.to.throw(); }); + + describe('checkAdUnitSetup', function() { + describe('positive tests for validating adUnits', function() { + it('should maintain adUnit structure and adUnit.sizes is replaced', function () { + let fullAdUnit = [{ + code: 'test1', + sizes: [[300, 250], [300, 600]], + mediaTypes: { + banner: { + sizes: [[300, 250]] + }, + video: { + playerSize: [[640, 480]] + }, + native: { + image: { + sizes: [150, 150], + aspect_ratios: [140, 140] + }, + icon: { + sizes: [75, 75] + } + } + }, + bids: [] + }]; + $$PREBID_GLOBAL$$.requestBids({ + adUnits: fullAdUnit + }); + expect(auctionArgs.adUnits[0].sizes).to.deep.equal([[640, 480]]); + expect(auctionArgs.adUnits[0].mediaTypes.video.playerSize).to.deep.equal([[640, 480]]); + expect(auctionArgs.adUnits[0].mediaTypes.native.image.sizes).to.deep.equal([150, 150]); + expect(auctionArgs.adUnits[0].mediaTypes.native.icon.sizes).to.deep.equal([75, 75]); + expect(auctionArgs.adUnits[0].mediaTypes.native.image.aspect_ratios).to.deep.equal([140, 140]); + + let noOptnlFieldAdUnit = [{ + code: 'test2', + bids: [], + sizes: [[300, 250], [300, 600]], + mediaTypes: { + banner: { + sizes: [[300, 250]] + }, + video: { + context: 'outstream' + }, + native: { + image: { + required: true + }, + icon: { + required: true + } + } + } + }]; + $$PREBID_GLOBAL$$.requestBids({ + adUnits: noOptnlFieldAdUnit + }); + expect(auctionArgs.adUnits[0].sizes).to.deep.equal([[300, 250]]); + expect(auctionArgs.adUnits[0].mediaTypes.video).to.exist; + + let mixedAdUnit = [{ + code: 'test3', + bids: [], + sizes: [[300, 250], [300, 600]], + mediaTypes: { + video: { + context: 'outstream', + playerSize: [[400, 350]] + }, + native: { + image: { + aspect_ratios: [200, 150], + required: true + } + } + } + }]; + $$PREBID_GLOBAL$$.requestBids({ + adUnits: mixedAdUnit + }); + expect(auctionArgs.adUnits[0].sizes).to.deep.equal([[400, 350]]); + expect(auctionArgs.adUnits[0].mediaTypes.video).to.exist; + + let altVideoPlayerSize = [{ + code: 'test4', + bids: [], + sizes: [[600, 600]], + mediaTypes: { + video: { + playerSize: [640, 480] + } + } + }]; + $$PREBID_GLOBAL$$.requestBids({ + adUnits: altVideoPlayerSize + }); + expect(auctionArgs.adUnits[0].sizes).to.deep.equal([[640, 480]]); + expect(auctionArgs.adUnits[0].mediaTypes.video.playerSize).to.deep.equal([[640, 480]]); + expect(auctionArgs.adUnits[0].mediaTypes.video).to.exist; + assert.ok(logInfoSpy.calledWith('Transforming video.playerSize from [640,480] to [[640,480]] so it\'s in the proper format.'), 'expected message was logged'); + }); + + it('should normalize adUnit.sizes and adUnit.mediaTypes.banner.sizes', function () { + let normalizeAdUnit = [{ + code: 'test5', + bids: [], + sizes: [300, 250], + mediaTypes: { + banner: { + sizes: [300, 250] + } + } + }]; + $$PREBID_GLOBAL$$.requestBids({ + adUnits: normalizeAdUnit + }); + expect(auctionArgs.adUnits[0].sizes).to.deep.equal([[300, 250]]); + expect(auctionArgs.adUnits[0].mediaTypes.banner.sizes).to.deep.equal([[300, 250]]); + }); + }); + + describe('negative tests for validating adUnits', function() { + it('should throw error message and delete an object/property', function () { + let badBanner = [{ + code: 'testb1', + bids: [], + sizes: [[300, 250], [300, 600]], + mediaTypes: { + banner: { + name: 'test' + } + } + }]; + $$PREBID_GLOBAL$$.requestBids({ + adUnits: badBanner + }); + expect(auctionArgs.adUnits[0].sizes).to.deep.equal([[300, 250], [300, 600]]); + expect(auctionArgs.adUnits[0].mediaTypes.banner).to.be.undefined; + assert.ok(logErrorSpy.calledWith('Detected a mediaTypes.banner object did not include sizes. This is a required field for the mediaTypes.banner object. Removing invalid mediaTypes.banner object from request.')); + + let badVideo1 = [{ + code: 'testb2', + bids: [], + sizes: [[600, 600]], + mediaTypes: { + video: { + playerSize: ['600x400'] + } + } + }]; + $$PREBID_GLOBAL$$.requestBids({ + adUnits: badVideo1 + }); + expect(auctionArgs.adUnits[0].sizes).to.deep.equal([[600, 600]]); + expect(auctionArgs.adUnits[0].mediaTypes.video.playerSize).to.be.undefined; + expect(auctionArgs.adUnits[0].mediaTypes.video).to.exist; + assert.ok(logErrorSpy.calledWith('Detected incorrect configuration of mediaTypes.video.playerSize. Please specify only one set of dimensions in a format like: [[640, 480]]. Removing invalid mediaTypes.video.playerSize property from request.')); + + let badVideo2 = [{ + code: 'testb3', + bids: [], + sizes: [[600, 600]], + mediaTypes: { + video: { + playerSize: [['300', '200']] + } + } + }]; + $$PREBID_GLOBAL$$.requestBids({ + adUnits: badVideo2 + }); + expect(auctionArgs.adUnits[0].sizes).to.deep.equal([[600, 600]]); + expect(auctionArgs.adUnits[0].mediaTypes.video.playerSize).to.be.undefined; + expect(auctionArgs.adUnits[0].mediaTypes.video).to.exist; + assert.ok(logErrorSpy.calledWith('Detected incorrect configuration of mediaTypes.video.playerSize. Please specify only one set of dimensions in a format like: [[640, 480]]. Removing invalid mediaTypes.video.playerSize property from request.')); + + let badNativeImgSize = [{ + code: 'testb4', + bids: [], + mediaTypes: { + native: { + image: { + sizes: '300x250' + } + } + } + }]; + $$PREBID_GLOBAL$$.requestBids({ + adUnits: badNativeImgSize + }); + expect(auctionArgs.adUnits[0].mediaTypes.native.image.sizes).to.be.undefined; + expect(auctionArgs.adUnits[0].mediaTypes.native.image).to.exist; + assert.ok(logErrorSpy.calledWith('Please use an array of sizes for native.image.sizes field. Removing invalid mediaTypes.native.image.sizes property from request.')); + + let badNativeImgAspRat = [{ + code: 'testb5', + bids: [], + mediaTypes: { + native: { + image: { + aspect_ratios: '300x250' + } + } + } + }]; + $$PREBID_GLOBAL$$.requestBids({ + adUnits: badNativeImgAspRat + }); + expect(auctionArgs.adUnits[0].mediaTypes.native.image.aspect_ratios).to.be.undefined; + expect(auctionArgs.adUnits[0].mediaTypes.native.image).to.exist; + assert.ok(logErrorSpy.calledWith('Please use an array of sizes for native.image.aspect_ratios field. Removing invalid mediaTypes.native.image.aspect_ratios property from request.')); + + let badNativeIcon = [{ + code: 'testb6', + bids: [], + mediaTypes: { + native: { + icon: { + sizes: '300x250' + } + } + } + }]; + $$PREBID_GLOBAL$$.requestBids({ + adUnits: badNativeIcon + }); + expect(auctionArgs.adUnits[0].mediaTypes.native.icon.sizes).to.be.undefined; + expect(auctionArgs.adUnits[0].mediaTypes.native.icon).to.exist; + assert.ok(logErrorSpy.calledWith('Please use an array of sizes for native.icon.sizes field. Removing invalid mediaTypes.native.icon.sizes property from request.')); + }); + }); + }); }); describe('multiformat requests', function () { @@ -1306,7 +1706,9 @@ describe('Unit: Prebid Module', function () { adUnits = [{ code: 'adUnit-code', mediaTypes: { - banner: {}, + banner: { + sizes: [[300, 250]] + }, native: {}, }, sizes: [[300, 250], [300, 600]], @@ -1318,21 +1720,21 @@ describe('Unit: Prebid Module', function () { adUnitCodes = ['adUnit-code']; configObj.setConfig({maxRequestsPerOrigin: Number.MAX_SAFE_INTEGER || 99999999}); let auction = auctionModule.newAuction({adUnits, adUnitCodes, callback: function() {}, cbTimeout: timeout}); - spyCallBids = sinon.spy(adaptermanager, 'callBids'); + spyCallBids = sinon.spy(adapterManager, 'callBids'); createAuctionStub = sinon.stub(auctionModule, 'newAuction'); createAuctionStub.returns(auction); }) afterEach(function () { auctionModule.newAuction.restore(); - adaptermanager.callBids.restore(); + adapterManager.callBids.restore(); }); it('bidders that support one of the declared formats are allowed to participate', function () { $$PREBID_GLOBAL$$.requestBids({adUnits}); - sinon.assert.calledOnce(adaptermanager.callBids); + sinon.assert.calledOnce(adapterManager.callBids); - const spyArgs = adaptermanager.callBids.getCall(0); + const spyArgs = adapterManager.callBids.getCall(0); const biddersCalled = spyArgs.args[0][0].bids; // appnexus and sampleBidder both support banner @@ -1343,9 +1745,9 @@ describe('Unit: Prebid Module', function () { delete adUnits[0].mediaTypes.banner; $$PREBID_GLOBAL$$.requestBids({adUnits}); - sinon.assert.calledOnce(adaptermanager.callBids); + sinon.assert.calledOnce(adapterManager.callBids); - const spyArgs = adaptermanager.callBids.getCall(0); + const spyArgs = adapterManager.callBids.getCall(0); const biddersCalled = spyArgs.args[0][0].bids; // only appnexus supports native @@ -1394,19 +1796,19 @@ describe('Unit: Prebid Module', function () { }); beforeEach(function () { - spyCallBids = sinon.spy(adaptermanager, 'callBids'); + spyCallBids = sinon.spy(adapterManager, 'callBids'); }) afterEach(function () { - adaptermanager.callBids.restore(); + adapterManager.callBids.restore(); }) it('should callBids if a native adUnit has all native bidders', function () { $$PREBID_GLOBAL$$.requestBids({adUnits}); - sinon.assert.calledOnce(adaptermanager.callBids); + sinon.assert.calledOnce(adapterManager.callBids); }); - it('should call callBids function on adaptermanager', function () { + it('should call callBids function on adapterManager', function () { let adUnits = [{ code: 'adUnit-code', sizes: [[300, 250], [300, 600]], @@ -1415,7 +1817,7 @@ describe('Unit: Prebid Module', function () { ] }]; $$PREBID_GLOBAL$$.requestBids({adUnits}); - assert.ok(spyCallBids.called, 'called adaptermanager.callBids'); + assert.ok(spyCallBids.called, 'called adapterManager.callBids'); }); it('splits native type to individual native assets', function () { @@ -1428,7 +1830,7 @@ describe('Unit: Prebid Module', function () { ] }]; $$PREBID_GLOBAL$$.requestBids({adUnits}); - const spyArgs = adaptermanager.callBids.getCall(0); + const spyArgs = adapterManager.callBids.getCall(0); const nativeRequest = spyArgs.args[1][0].bids[0].nativeParams; expect(nativeRequest).to.deep.equal({ image: {required: true}, @@ -1491,7 +1893,7 @@ describe('Unit: Prebid Module', function () { }; beforeEach(function() { - spyCallBids = sinon.spy(adaptermanager, 'callBids'); + spyCallBids = sinon.spy(adapterManager, 'callBids'); auctionManagerStub = sinon.stub(auctionManager, 'createAuction'); auctionManagerStub.onCall(0).returns(auction1); auctionManagerStub.onCall(1).returns(auction2); @@ -1499,7 +1901,7 @@ describe('Unit: Prebid Module', function () { afterEach(function() { auctionManager.createAuction.restore(); - adaptermanager.callBids.restore(); + adapterManager.callBids.restore(); }); it('should not queue bid requests when a previous bid request is in process', function () { @@ -1527,16 +1929,16 @@ describe('Unit: Prebid Module', function () { let expected = { '/19968336/header-bid-tag-0': { 'foobar': '0x0,300x250,300x600', - 'hb_size': '300x250', - 'hb_pb': '10.00', - 'hb_adid': '233bcbee889d46d', - 'hb_bidder': 'appnexus' + [CONSTANTS.TARGETING_KEYS.SIZE]: '300x250', + [CONSTANTS.TARGETING_KEYS.PRICE_BUCKET]: '10.00', + [CONSTANTS.TARGETING_KEYS.AD_ID]: '233bcbee889d46d', + [CONSTANTS.TARGETING_KEYS.BIDDER]: 'appnexus' }, '/19968336/header-bid-tag1': { - 'hb_bidder': 'appnexus', - 'hb_adid': '24bd938435ec3fc', - 'hb_pb': '10.00', - 'hb_size': '728x90', + [CONSTANTS.TARGETING_KEYS.BIDDER]: 'appnexus', + [CONSTANTS.TARGETING_KEYS.AD_ID]: '24bd938435ec3fc', + [CONSTANTS.TARGETING_KEYS.PRICE_BUCKET]: '10.00', + [CONSTANTS.TARGETING_KEYS.SIZE]: '728x90', 'foobar': '728x90' } } @@ -1591,18 +1993,18 @@ describe('Unit: Prebid Module', function () { describe('emit', function () { it('should be able to emit event without arguments', function () { var spyEventsEmit = sinon.spy(events, 'emit'); - events.emit(CONSTANTS.EVENTS.AUCTION_END); - assert.ok(spyEventsEmit.calledWith('auctionEnd')); + events.emit(CONSTANTS.EVENTS.REQUEST_BIDS); + assert.ok(spyEventsEmit.calledWith('requestBids')); events.emit.restore(); }); }); describe('registerBidAdapter', function () { - it('should register bidAdaptor with adaptermanager', function () { - var registerBidAdapterSpy = sinon.spy(adaptermanager, 'registerBidAdapter'); + it('should register bidAdaptor with adapterManager', function () { + var registerBidAdapterSpy = sinon.spy(adapterManager, 'registerBidAdapter'); $$PREBID_GLOBAL$$.registerBidAdapter(Function, 'biddercode'); - assert.ok(registerBidAdapterSpy.called, 'called adaptermanager.registerBidAdapter'); - adaptermanager.registerBidAdapter.restore(); + assert.ok(registerBidAdapterSpy.called, 'called adapterManager.registerBidAdapter'); + adapterManager.registerBidAdapter.restore(); }); it('should catch thrown errors', function () { @@ -1633,26 +2035,24 @@ describe('Unit: Prebid Module', function () { describe('loadScript', function () { it('should call adloader.loadScript', function () { - const loadScriptSpy = sinon.spy(adloader, 'loadScript'); const tagSrc = ''; const callback = Function; const useCache = false; $$PREBID_GLOBAL$$.loadScript(tagSrc, callback, useCache); - assert.ok(loadScriptSpy.calledWith(tagSrc, callback, useCache), 'called adloader.loadScript'); - adloader.loadScript.restore(); + assert.ok(adloader.loadScriptStub.calledWith(tagSrc, callback, useCache), 'called adloader.loadScript'); }); }); describe('aliasBidder', function () { - it('should call adaptermanager.aliasBidder', function () { - const aliasBidAdapterSpy = sinon.spy(adaptermanager, 'aliasBidAdapter'); + it('should call adapterManager.aliasBidder', function () { + const aliasBidAdapterSpy = sinon.spy(adapterManager, 'aliasBidAdapter'); const bidderCode = 'testcode'; const alias = 'testalias'; $$PREBID_GLOBAL$$.aliasBidder(bidderCode, alias); - assert.ok(aliasBidAdapterSpy.calledWith(bidderCode, alias), 'called adaptermanager.aliasBidAdapterSpy'); - adaptermanager.aliasBidAdapter.restore(); + assert.ok(aliasBidAdapterSpy.calledWith(bidderCode, alias), 'called adapterManager.aliasBidAdapterSpy'); + adapterManager.aliasBidAdapter(); }); it('should log error when not passed correct arguments', function () { @@ -1757,6 +2157,65 @@ describe('Unit: Prebid Module', function () { $$PREBID_GLOBAL$$.removeAdUnit('adUnit1'); assert.deepEqual($$PREBID_GLOBAL$$.adUnits, [adUnit2]); }); + it('should remove all adUnits in adUnits array if no adUnits are given', function () { + const adUnit1 = { + code: 'adUnit1', + bids: [{ + bidder: 'appnexus', + params: { placementId: '123' } + }] + }; + const adUnit2 = { + code: 'adUnit2', + bids: [{ + bidder: 'rubicon', + params: { + accountId: '1234', + siteId: '1234', + zoneId: '1234' + } + }] + }; + const adUnits = [adUnit1, adUnit2]; + $$PREBID_GLOBAL$$.adUnits = adUnits; + $$PREBID_GLOBAL$$.removeAdUnit(); + assert.deepEqual($$PREBID_GLOBAL$$.adUnits, []); + }); + it('should remove adUnits which match addUnitCodes in adUnit array argument', function () { + const adUnit1 = { + code: 'adUnit1', + bids: [{ + bidder: 'appnexus', + params: { placementId: '123' } + }] + }; + const adUnit2 = { + code: 'adUnit2', + bids: [{ + bidder: 'rubicon', + params: { + accountId: '1234', + siteId: '1234', + zoneId: '1234' + } + }] + }; + const adUnit3 = { + code: 'adUnit3', + bids: [{ + bidder: 'rubicon3', + params: { + accountId: '12345', + siteId: '12345', + zoneId: '12345' + } + }] + }; + const adUnits = [adUnit1, adUnit2, adUnit3]; + $$PREBID_GLOBAL$$.adUnits = adUnits; + $$PREBID_GLOBAL$$.removeAdUnit([adUnit1.code, adUnit2.code]); + assert.deepEqual($$PREBID_GLOBAL$$.adUnits, [adUnit3]); + }); }); describe('getDealTargeting', function () { @@ -1793,12 +2252,12 @@ describe('Unit: Prebid Module', function () { 'alwaysUseBid': true, 'auctionId': 123456, 'adserverTargeting': { - 'hb_bidder': 'appnexus', - 'hb_adid': '233bcbee889d46d', - 'hb_pb': '10.00', - 'hb_size': '300x250', 'foobar': '300x250', - 'hb_deal_appnexusDummyName': '1234' + [CONSTANTS.TARGETING_KEYS.BIDDER]: 'appnexus', + [CONSTANTS.TARGETING_KEYS.AD_ID]: '233bcbee889d46d', + [CONSTANTS.TARGETING_KEYS.PRICE_BUCKET]: '10.00', + [CONSTANTS.TARGETING_KEYS.SIZE]: '300x250', + [CONSTANTS.TARGETING_KEYS.DEAL + '_appnexusDummyName']: '1234' } } ]; @@ -1850,11 +2309,10 @@ describe('Unit: Prebid Module', function () { // mark the bid and verify the state has changed to RENDERED const winningBid = targeting.getWinningBids(adUnitCode)[0]; $$PREBID_GLOBAL$$.markWinningBidAsUsed({ adUnitCode, adId: winningBid.adId }); - const markedBid = $$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode(adUnitCode) - .bids - .find(bid => bid.adId === winningBid.adId); + const markedBid = find($$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode(adUnitCode).bids, + bid => bid.adId === winningBid.adId); - expect(markedBid.status).to.equal(RENDERED); + expect(markedBid.status).to.equal(CONSTANTS.BID_STATUS.RENDERED); resetAuction(); }); @@ -1865,11 +2323,10 @@ describe('Unit: Prebid Module', function () { const winningBid = targeting.getWinningBids(adUnitCode)[0]; $$PREBID_GLOBAL$$.markWinningBidAsUsed({ adUnitCode, adId: 'miss' }); - const markedBid = $$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode(adUnitCode) - .bids - .find(bid => bid.adId === winningBid.adId); + const markedBid = find($$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode(adUnitCode).bids, + bid => bid.adId === winningBid.adId); - expect(markedBid.status).to.not.equal(RENDERED); + expect(markedBid.status).to.not.equal(CONSTANTS.BID_STATUS.RENDERED); resetAuction(); }); @@ -1882,11 +2339,10 @@ describe('Unit: Prebid Module', function () { // mark the bid and verify the state has changed to RENDERED const winningBid = targeting.getWinningBids(adUnitCode)[0]; $$PREBID_GLOBAL$$.markWinningBidAsUsed({ adUnitCode }); - const markedBid = $$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode(adUnitCode) - .bids - .find(bid => bid.adId === winningBid.adId); + const markedBid = find($$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode(adUnitCode).bids, + bid => bid.adId === winningBid.adId); - expect(markedBid.status).to.equal(RENDERED); + expect(markedBid.status).to.equal(CONSTANTS.BID_STATUS.RENDERED); resetAuction(); }); @@ -1899,11 +2355,10 @@ describe('Unit: Prebid Module', function () { // mark the bid and verify the state has changed to RENDERED const winningBid = targeting.getWinningBids(adUnitCode)[0]; $$PREBID_GLOBAL$$.markWinningBidAsUsed({ adId: winningBid.adId }); - const markedBid = $$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode(adUnitCode) - .bids - .find(bid => bid.adId === winningBid.adId); + const markedBid = find($$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode(adUnitCode).bids, + bid => bid.adId === winningBid.adId); - expect(markedBid.status).to.equal(RENDERED); + expect(markedBid.status).to.equal(CONSTANTS.BID_STATUS.RENDERED); resetAuction(); }); }); @@ -1952,11 +2407,32 @@ describe('Unit: Prebid Module', function () { expect(newAdserverTargeting).to.deep.equal(window.apntag.tags[adUnitCode].keywords); }); - it('should not find hb_adid key in lowercase for all bidders', function () { + it('should reset targeting for appnexus apntag object', function () { + const bids = auctionManagerInstance.getBidsReceived(); + const adUnitCode = '/19968336/header-bid-tag-0'; + + var expectedAdserverTargeting = bids[0].adserverTargeting; + var newAdserverTargeting = {}; + let regex = /pt[0-9]/; + + for (var key in expectedAdserverTargeting) { + if (key.search(regex) < 0) { + newAdserverTargeting[key.toUpperCase()] = expectedAdserverTargeting[key]; + } else { + newAdserverTargeting[key] = expectedAdserverTargeting[key]; + } + } + targeting.setTargetingForAst(); + expect(newAdserverTargeting).to.deep.equal(window.apntag.tags[adUnitCode].keywords); + targeting.resetPresetTargetingAST(); + expect(window.apntag.tags[adUnitCode].keywords).to.deep.equal({}); + }); + + it('should not find ' + CONSTANTS.TARGETING_KEYS.AD_ID + ' key in lowercase for all bidders', function() { const adUnitCode = '/19968336/header-bid-tag-0'; $$PREBID_GLOBAL$$.setConfig({ enableSendAllBids: true }); targeting.setTargetingForAst(); - const keywords = Object.keys(window.apntag.tags[adUnitCode].keywords).filter(keyword => (keyword.substring(0, 'hb_adid'.length) === 'hb_adid')); + const keywords = Object.keys(window.apntag.tags[adUnitCode].keywords).filter(keyword => (keyword.substring(0, CONSTANTS.TARGETING_KEYS.AD_ID.length) === CONSTANTS.TARGETING_KEYS.AD_ID)); expect(keywords.length).to.equal(0); }); }); @@ -2007,10 +2483,10 @@ describe('Unit: Prebid Module', function () { it('should return prebid auction winning bids', function () { let bidsReceived = [ - createBidReceived({bidder: 'appnexus', cpm: 7, auctionId: 1, responseTimestamp: 100, adUnitCode: 'code-0', adId: 'adid-1', status: 'targetingSet'}), - createBidReceived({bidder: 'rubicon', cpm: 6, auctionId: 1, responseTimestamp: 101, adUnitCode: 'code-1', adId: 'adid-2'}), - createBidReceived({bidder: 'appnexus', cpm: 6, auctionId: 2, responseTimestamp: 102, adUnitCode: 'code-0', adId: 'adid-3'}), - createBidReceived({bidder: 'rubicon', cpm: 6, auctionId: 2, responseTimestamp: 103, adUnitCode: 'code-1', adId: 'adid-4'}), + createBidReceived({bidder: 'appnexus', cpm: 7, auctionId: 1, responseTimestamp: 100, adUnitCode: 'code-0', adId: 'adid-1', status: 'targetingSet', requestId: 'reqid-1'}), + createBidReceived({bidder: 'rubicon', cpm: 6, auctionId: 1, responseTimestamp: 101, adUnitCode: 'code-1', adId: 'adid-2', requestId: 'reqid-2'}), + createBidReceived({bidder: 'appnexus', cpm: 6, auctionId: 2, responseTimestamp: 102, adUnitCode: 'code-0', adId: 'adid-3', requestId: 'reqid-3'}), + createBidReceived({bidder: 'rubicon', cpm: 6, auctionId: 2, responseTimestamp: 103, adUnitCode: 'code-1', adId: 'adid-4', requestId: 'reqid-4'}), ]; auctionManagerStub.returns(bidsReceived) let bids = $$PREBID_GLOBAL$$.getAllPrebidWinningBids(); diff --git a/test/spec/unit/secureCreatives_spec.js b/test/spec/unit/secureCreatives_spec.js new file mode 100644 index 00000000000..f0f26bf5653 --- /dev/null +++ b/test/spec/unit/secureCreatives_spec.js @@ -0,0 +1,45 @@ +import { + _sendAdToCreative +} from '../../../src/secureCreatives'; +import { expect } from 'chai'; +import * as utils from 'src/utils'; + +describe('secureCreatives', () => { + describe('_sendAdToCreative', () => { + beforeEach(function () { + sinon.stub(utils, 'logError'); + sinon.stub(utils, 'logWarn'); + }); + + afterEach(function () { + utils.logError.restore(); + utils.logWarn.restore(); + }); + it('should macro replace ${AUCTION_PRICE} with the winning bid for ad and adUrl', () => { + const oldVal = window.googletag; + const oldapntag = window.apntag; + window.apntag = null + window.googletag = null; + const mockAdObject = { + adId: 'someAdId', + ad: '', + adUrl: 'http://creative.prebid.org/${AUCTION_PRICE}', + width: 300, + height: 250, + renderer: null, + cpm: '1.00', + adUnitCode: 'some_dom_id' + }; + const remoteDomain = '*'; + const source = { + postMessage: sinon.stub() + }; + + _sendAdToCreative(mockAdObject, remoteDomain, source); + expect(JSON.parse(source.postMessage.args[0][0]).ad).to.equal(''); + expect(JSON.parse(source.postMessage.args[0][0]).adUrl).to.equal('http://creative.prebid.org/1.00'); + window.googletag = oldVal; + window.apntag = oldapntag; + }); + }); +}); diff --git a/test/spec/userSync_spec.js b/test/spec/userSync_spec.js index 7040256ccd6..f55fe13c528 100644 --- a/test/spec/userSync_spec.js +++ b/test/spec/userSync_spec.js @@ -98,15 +98,18 @@ describe('user sync', function () { expect(insertUserSyncIframeStub.getCall(0).args[0]).to.equal('http://example.com/iframe'); }); - it('should only trigger syncs once per page', function () { + it('should only trigger syncs once per page per bidder', function () { const userSync = newTestUserSync({pixelEnabled: true}); userSync.registerSync('image', 'testBidder', 'http://example.com/1'); userSync.syncUsers(); userSync.registerSync('image', 'testBidder', 'http://example.com/2'); + userSync.registerSync('image', 'testBidder2', 'http://example.com/3'); userSync.syncUsers(); + expect(triggerPixelStub.callCount).to.equal(2); expect(triggerPixelStub.getCall(0)).to.not.be.null; expect(triggerPixelStub.getCall(0).args[0]).to.exist.and.to.equal('http://example.com/1'); - expect(triggerPixelStub.getCall(1)).to.be.null; + expect(triggerPixelStub.getCall(1)).to.not.be.null; + expect(triggerPixelStub.getCall(1).args[0]).to.exist.and.to.equal('http://example.com/3'); }); it('should not fire syncs if cookies are not supported', function () { @@ -135,7 +138,7 @@ describe('user sync', function () { expect(syncUsersSpy.called).to.be.true; }); - it('should limit the sync per bidder', function () { + it('should limit the number of syncs per bidder', function () { const userSync = newTestUserSync({syncsPerBidder: 2}); userSync.registerSync('image', 'testBidder', 'http://example.com/1'); userSync.registerSync('image', 'testBidder', 'http://example.com/2'); @@ -148,6 +151,20 @@ describe('user sync', function () { expect(triggerPixelStub.getCall(2)).to.be.null; }); + it('should not limit the number of syncs per bidder when set to 0', function() { + const userSync = newTestUserSync({syncsPerBidder: 0}); + userSync.registerSync('image', 'testBidder', 'http://example.com/1'); + userSync.registerSync('image', 'testBidder', 'http://example.com/2'); + userSync.registerSync('image', 'testBidder', 'http://example.com/3'); + userSync.syncUsers(); + expect(triggerPixelStub.getCall(0)).to.not.be.null; + expect(triggerPixelStub.getCall(0).args[0]).to.exist.and.to.match(/^http:\/\/example\.com\/[1|2|3]/); + expect(triggerPixelStub.getCall(1)).to.not.be.null; + expect(triggerPixelStub.getCall(1).args[0]).to.exist.and.to.match(/^http:\/\/example\.com\/[1|2|3]/); + expect(triggerPixelStub.getCall(2)).to.not.be.null; + expect(triggerPixelStub.getCall(2).args[0]).to.exist.and.to.match(/^http:\/\/example\.com\/[1|2|3]/); + }); + it('should balance out bidder requests', function () { const userSync = newTestUserSync(); userSync.registerSync('image', 'atestBidder', 'http://example.com/1'); @@ -341,4 +358,105 @@ describe('user sync', function () { expect(triggerPixelStub.getCall(0).args[0]).to.exist.and.to.equal('http://example.com/1'); expect(insertUserSyncIframeStub.getCall(0)).to.be.null; }); + + describe('publicAPI', function () { + describe('canBidderRegisterSync', function() { + describe('with filterSettings', function() { + it('should return false if filter settings does not allow it', function () { + const userSync = newUserSync({ + config: { + filterSettings: { + image: { + bidders: '*', + filter: 'include' + }, + iframe: { + bidders: ['testBidder'], + filter: 'include' + } + } + } + }); + expect(userSync.canBidderRegisterSync('iframe', 'otherTestBidder')).to.equal(false); + }); + it('should return true if filter settings does allow it', function () { + const userSync = newUserSync({ + config: { + filterSettings: { + image: { + bidders: '*', + filter: 'include' + }, + iframe: { + bidders: ['testBidder'], + filter: 'include' + } + } + } + }); + expect(userSync.canBidderRegisterSync('iframe', 'testBidder')).to.equal(true); + }); + }); + describe('almost deprecated - without filterSettings', function() { + describe('enabledBidders contains testBidder', function() { + it('should return false if type is iframe and iframeEnabled is false', function () { + const userSync = newUserSync({ + config: { + pixelEnabled: true, + iframeEnabled: false, + enabledBidders: ['testBidder'], + } + }); + expect(userSync.canBidderRegisterSync('iframe', 'testBidder')).to.equal(false); + }); + + it('should return true if type is iframe and iframeEnabled is true', function () { + const userSync = newUserSync({ + config: { + pixelEnabled: true, + iframeEnabled: true, + enabledBidders: ['testBidder'], + } + }); + expect(userSync.canBidderRegisterSync('iframe', 'testBidder')).to.equal(true); + }); + + it('should return false if type is image and pixelEnabled is false', function () { + const userSync = newUserSync({ + config: { + pixelEnabled: false, + iframeEnabled: true, + enabledBidders: ['testBidder'], + } + }); + expect(userSync.canBidderRegisterSync('image', 'testBidder')).to.equal(false); + }); + + it('should return true if type is image and pixelEnabled is true', function () { + const userSync = newUserSync({ + config: { + pixelEnabled: true, + iframeEnabled: true, + enabledBidders: ['testBidder'], + } + }); + expect(userSync.canBidderRegisterSync('image', 'testBidder')).to.equal(true); + }); + }); + + describe('enabledBidders does not container testBidder', function() { + it('should return false since testBidder is not in enabledBidders', function() { + const userSync = newUserSync({ + config: { + pixelEnabled: true, + iframeEnabled: true, + enabledBidders: ['otherTestBidder'], + } + }); + expect(userSync.canBidderRegisterSync('iframe', 'testBidder')).to.equal(false); + }); + }); + }); + }); + }); }); diff --git a/test/spec/utils_spec.js b/test/spec/utils_spec.js index 7891eff5f33..e1e6ff90df8 100755 --- a/test/spec/utils_spec.js +++ b/test/spec/utils_spec.js @@ -1,8 +1,9 @@ import { getAdServerTargeting } from 'test/fixtures/fixtures'; import { expect } from 'chai'; +import CONSTANTS from 'src/constants.json'; +import * as utils from 'src/utils'; var assert = require('assert'); -var utils = require('src/utils'); describe('Utils', function () { var obj_string = 's', @@ -101,7 +102,7 @@ describe('Utils', function () { var obj = getAdServerTargeting(); var output = utils.transformAdServerTargetingObj(obj[Object.keys(obj)[0]]); - var expected = 'foobar=0x0%2C300x250%2C300x600&hb_size=300x250&hb_pb=10.00&hb_adid=233bcbee889d46d&hb_bidder=appnexus&hb_size_triplelift=0x0&hb_pb_triplelift=10.00&hb_adid_triplelift=222bb26f9e8bd&hb_bidder_triplelift=triplelift&hb_size_appnexus=300x250&hb_pb_appnexus=10.00&hb_adid_appnexus=233bcbee889d46d&hb_bidder_appnexus=appnexus&hb_size_pagescience=300x250&hb_pb_pagescience=10.00&hb_adid_pagescience=25bedd4813632d7&hb_bidder_pagescienc=pagescience&hb_size_brightcom=300x250&hb_pb_brightcom=10.00&hb_adid_brightcom=26e0795ab963896&hb_bidder_brightcom=brightcom&hb_size_brealtime=300x250&hb_pb_brealtime=10.00&hb_adid_brealtime=275bd666f5a5a5d&hb_bidder_brealtime=brealtime&hb_size_pubmatic=300x250&hb_pb_pubmatic=10.00&hb_adid_pubmatic=28f4039c636b6a7&hb_bidder_pubmatic=pubmatic&hb_size_rubicon=300x600&hb_pb_rubicon=10.00&hb_adid_rubicon=29019e2ab586a5a&hb_bidder_rubicon=rubicon'; + var expected = 'foobar=0x0%2C300x250%2C300x600&' + CONSTANTS.TARGETING_KEYS.SIZE + '=300x250&' + CONSTANTS.TARGETING_KEYS.PRICE_BUCKET + '=10.00&' + CONSTANTS.TARGETING_KEYS.AD_ID + '=233bcbee889d46d&' + CONSTANTS.TARGETING_KEYS.BIDDER + '=appnexus&' + CONSTANTS.TARGETING_KEYS.SIZE + '_triplelift=0x0&' + CONSTANTS.TARGETING_KEYS.PRICE_BUCKET + '_triplelift=10.00&' + CONSTANTS.TARGETING_KEYS.AD_ID + '_triplelift=222bb26f9e8bd&' + CONSTANTS.TARGETING_KEYS.BIDDER + '_triplelift=triplelift&' + CONSTANTS.TARGETING_KEYS.SIZE + '_appnexus=300x250&' + CONSTANTS.TARGETING_KEYS.PRICE_BUCKET + '_appnexus=10.00&' + CONSTANTS.TARGETING_KEYS.AD_ID + '_appnexus=233bcbee889d46d&' + CONSTANTS.TARGETING_KEYS.BIDDER + '_appnexus=appnexus&' + CONSTANTS.TARGETING_KEYS.SIZE + '_pagescience=300x250&' + CONSTANTS.TARGETING_KEYS.PRICE_BUCKET + '_pagescience=10.00&' + CONSTANTS.TARGETING_KEYS.AD_ID + '_pagescience=25bedd4813632d7&' + CONSTANTS.TARGETING_KEYS.BIDDER + '_pagescienc=pagescience&' + CONSTANTS.TARGETING_KEYS.SIZE + '_brightcom=300x250&' + CONSTANTS.TARGETING_KEYS.PRICE_BUCKET + '_brightcom=10.00&' + CONSTANTS.TARGETING_KEYS.AD_ID + '_brightcom=26e0795ab963896&' + CONSTANTS.TARGETING_KEYS.BIDDER + '_brightcom=brightcom&' + CONSTANTS.TARGETING_KEYS.SIZE + '_brealtime=300x250&' + CONSTANTS.TARGETING_KEYS.PRICE_BUCKET + '_brealtime=10.00&' + CONSTANTS.TARGETING_KEYS.AD_ID + '_brealtime=275bd666f5a5a5d&' + CONSTANTS.TARGETING_KEYS.BIDDER + '_brealtime=brealtime&' + CONSTANTS.TARGETING_KEYS.SIZE + '_pubmatic=300x250&' + CONSTANTS.TARGETING_KEYS.PRICE_BUCKET + '_pubmatic=10.00&' + CONSTANTS.TARGETING_KEYS.AD_ID + '_pubmatic=28f4039c636b6a7&' + CONSTANTS.TARGETING_KEYS.BIDDER + '_pubmatic=pubmatic&' + CONSTANTS.TARGETING_KEYS.SIZE + '_rubicon=300x600&' + CONSTANTS.TARGETING_KEYS.PRICE_BUCKET + '_rubicon=10.00&' + CONSTANTS.TARGETING_KEYS.AD_ID + '_rubicon=29019e2ab586a5a&' + CONSTANTS.TARGETING_KEYS.BIDDER + '_rubicon=rubicon'; assert.equal(output, expected); }); @@ -231,6 +232,56 @@ describe('Utils', function () { }); }); + describe('parseGPTSingleSizeArrayToRtbSize', function () { + it('should return size string with input single size array', function () { + var size = [300, 250]; + var output = utils.parseGPTSingleSizeArrayToRtbSize(size); + assert.deepEqual(output, {w: 300, h: 250}); + }); + + it('should return size string with input single size array', function () { + var size = ['300', '250']; + var output = utils.parseGPTSingleSizeArrayToRtbSize(size); + assert.deepEqual(output, {w: 300, h: 250}); + }); + + it('return undefined using string input', function () { + var size = '1'; + var output = utils.parseGPTSingleSizeArrayToRtbSize(size); + assert.equal(output, undefined); + }); + + it('return undefined using number input', function () { + var size = 1; + var output = utils.parseGPTSingleSizeArrayToRtbSize(size); + assert.equal(output, undefined); + }); + + it('return undefined using one length single array', function () { + var size = [300]; + var output = utils.parseGPTSingleSizeArrayToRtbSize(size); + assert.equal(output, undefined); + }); + + it('return undefined if the input is empty', function () { + var size = ''; + var output = utils.parseGPTSingleSizeArrayToRtbSize(size); + assert.equal(output, undefined); + }); + + it('return undefined if the input is not a number', function () { + var size = ['foo', 'bar']; + var output = utils.parseGPTSingleSizeArrayToRtbSize(size); + assert.equal(output, undefined); + }); + + it('return undefined if the input is not a number 2', function () { + var size = [300, 'foo']; + var output = utils.parseGPTSingleSizeArrayToRtbSize(size); + assert.equal(output, undefined); + }); + }); + describe('isA', function () { it('should return true with string object', function () { var output = utils.isA(obj_string, type_string); @@ -610,7 +661,7 @@ describe('Utils', function () { var value2 = utils.deepAccess(obj, 'test.first'); assert.equal(value2, 11); - var value3 = utils.deepAccess(obj, 1); + var value3 = utils.deepAccess(obj, '1'); assert.equal(value3, 2); }); @@ -625,6 +676,29 @@ describe('Utils', function () { }); }); + describe('deepSetValue', function() { + it('should set existing properties at various depths', function() { + const testObj = { + prop: 'value', + nestedObj: { + nestedProp: 'nestedValue' + } + }; + utils.deepSetValue(testObj, 'prop', 'newValue'); + assert.equal(testObj.prop, 'newValue'); + utils.deepSetValue(testObj, 'nestedObj.nestedProp', 'newNestedValue'); + assert.equal(testObj.nestedObj.nestedProp, 'newNestedValue'); + }); + + it('should create object levels between top and bottom of given path if they do not exist', function() { + const testObj = {}; + utils.deepSetValue(testObj, 'level1.level2', 'value'); + assert.notEqual(testObj.level1, undefined); + assert.notEqual(testObj.level1.level2, undefined); + assert.equal(testObj.level1.level2, 'value'); + }); + }); + describe('createContentToExecuteExtScriptInFriendlyFrame', function () { it('should return empty string if url is not passed', function () { var output = utils.createContentToExecuteExtScriptInFriendlyFrame(); @@ -730,8 +804,8 @@ describe('Utils', function () { sandbox.restore(); }); - it('returns window.location if not in iFrame', function () { - sandbox.stub(utils, 'getWindowLocation').returns({ + it('returns window.originalLocation if not in iFrame', function () { + sandbox.stub(utils.internal, 'getWindowLocation').returns({ href: 'https://www.google.com/', ancestorOrigins: {}, origin: 'https://www.google.com', @@ -744,10 +818,10 @@ describe('Utils', function () { hash: '' }); let windowSelfAndTopObject = { self: 'is same as top' }; - sandbox.stub(utils, 'getWindowSelf').returns( + sandbox.stub(utils.internal, 'getWindowSelf').returns( windowSelfAndTopObject ); - sandbox.stub(utils, 'getWindowTop').returns( + sandbox.stub(utils.internal, 'getWindowTop').returns( windowSelfAndTopObject ); var topWindowLocation = utils.getTopWindowLocation(); @@ -763,13 +837,13 @@ describe('Utils', function () { }); it('returns parsed dom string from ancestorOrigins if in iFrame & ancestorOrigins is populated', function () { - sandbox.stub(utils, 'getWindowSelf').returns( + sandbox.stub(utils.internal, 'getWindowSelf').returns( { self: 'is not same as top' } ); - sandbox.stub(utils, 'getWindowTop').returns( + sandbox.stub(utils.internal, 'getWindowTop').returns( { top: 'is not same as self' } ); - sandbox.stub(utils, 'getAncestorOrigins').returns('https://www.google.com/a/umich.edu/acs'); + sandbox.stub(utils.internal, 'getAncestorOrigins').returns('https://www.google.com/a/umich.edu/acs'); var topWindowLocation = utils.getTopWindowLocation(); expect(topWindowLocation).to.be.a('object'); expect(topWindowLocation.pathname).to.equal('/a/umich.edu/acs'); @@ -784,14 +858,14 @@ describe('Utils', function () { }); it('returns parsed referrer string if in iFrame but no ancestorOrigins', function () { - sandbox.stub(utils, 'getWindowSelf').returns( + sandbox.stub(utils.internal, 'getWindowSelf').returns( { self: 'is not same as top' } ); - sandbox.stub(utils, 'getWindowTop').returns( + sandbox.stub(utils.internal, 'getWindowTop').returns( { top: 'is not same as self' } ); - sandbox.stub(utils, 'getAncestorOrigins').returns(null); - sandbox.stub(utils, 'getTopFrameReferrer').returns('https://www.example.com/'); + sandbox.stub(utils.internal, 'getAncestorOrigins').returns(null); + sandbox.stub(utils.internal, 'getTopFrameReferrer').returns('https://www.example.com/'); var topWindowLocation = utils.getTopWindowLocation(); expect(topWindowLocation).to.be.a('object'); expect(topWindowLocation.href).to.equal('https://www.example.com/'); @@ -845,4 +919,94 @@ describe('Utils', function () { expect(sizes).to.deep.equal([[300, 250], [300, 600]]); }); }); + + describe('transformBidderParamKeywords', function () { + it('returns an array of objects when keyvalue is an array', function () { + let keywords = { + genre: ['rock', 'pop'] + }; + let result = utils.transformBidderParamKeywords(keywords); + expect(result).to.deep.equal([{ + key: 'genre', + value: ['rock', 'pop'] + }]); + }); + + it('returns an array of objects when keyvalue is a string', function () { + let keywords = { + genre: 'opera' + }; + let result = utils.transformBidderParamKeywords(keywords); + expect(result).to.deep.equal([{ + key: 'genre', + value: ['opera'] + }]); + }); + + it('returns an array of objects when keyvalue is a number', function () { + let keywords = { + age: 15 + }; + let result = utils.transformBidderParamKeywords(keywords); + expect(result).to.deep.equal([{ + key: 'age', + value: ['15'] + }]); + }); + + it('returns an array of objects when using multiple keys with values of differing types', function () { + let keywords = { + genre: 'classical', + mix: ['1', 2, '3', 4], + age: 10 + }; + let result = utils.transformBidderParamKeywords(keywords); + expect(result).to.deep.equal([{ + key: 'genre', + value: ['classical'] + }, { + key: 'mix', + value: ['1', '2', '3', '4'] + }, { + key: 'age', + value: ['10'] + }]); + }); + + it('returns an array of objects when the keyvalue uses an empty string', function() { + let keywords = { + test: [''], + test2: '' + }; + let result = utils.transformBidderParamKeywords(keywords); + expect(result).to.deep.equal([{ + key: 'test', + value: [''] + }, { + key: 'test2', + value: [''] + }]); + }); + + describe('insertElement', function () { + it('returns a node at the top of the target by default', function () { + const toInsert = document.createElement('div'); + const target = document.getElementsByTagName('body')[0]; + const inserted = utils.insertElement(toInsert, document, 'body'); + expect(inserted).to.equal(target.firstChild); + }); + it('returns a node at bottom of target if 4th argument is true', function () { + const toInsert = document.createElement('div'); + const target = document.getElementsByTagName('html')[0]; + const inserted = utils.insertElement(toInsert, document, 'html', true); + expect(inserted).to.equal(target.lastChild); + }); + it('returns a node at top of the head if no target is given', function () { + const toInsert = document.createElement('div'); + const target = document.getElementsByTagName('head')[0]; + const inserted = utils.insertElement(toInsert); + expect(inserted).to.equal(target.firstChild); + }); + }); + }); }); diff --git a/test/spec/videoCache_spec.js b/test/spec/videoCache_spec.js index c9052fbbf9d..8f423a799ac 100644 --- a/test/spec/videoCache_spec.js +++ b/test/spec/videoCache_spec.js @@ -1,4 +1,4 @@ -import 'mocha'; +import 'mocha/mocha'; import chai from 'chai'; import { getCacheUrl, store } from 'src/videoCache'; import { config } from 'src/config'; @@ -32,7 +32,7 @@ describe('The video cache', function () { it('should execute the callback with an error when store() is called', function () { const callback = sinon.spy(); - store([ { vastUrl: 'my-mock-url.com' } ], callback); + store([{ vastUrl: 'my-mock-url.com' }], callback); requests[0].respond(503, { 'Content-Type': 'plain/text', @@ -100,7 +100,7 @@ describe('The video cache', function () {
    `; - assertRequestMade({ vastUrl: 'my-mock-url.com' }, expectedValue) + assertRequestMade({ vastUrl: 'my-mock-url.com', ttl: 25 }, expectedValue) }); it('should make the expected request when store() is called on an ad with a vastUrl and a vastImpUrl', function () { @@ -114,16 +114,106 @@ describe('The video cache', function () {
    `; - assertRequestMade({ vastUrl: 'my-mock-url.com', vastImpUrl: 'imptracker.com' }, expectedValue) + assertRequestMade({ vastUrl: 'my-mock-url.com', vastImpUrl: 'imptracker.com', ttl: 25 }, expectedValue) }); it('should make the expected request when store() is called on an ad with vastXml', function () { const vastXml = ''; - assertRequestMade({ vastXml: vastXml }, vastXml); + assertRequestMade({ vastXml: vastXml, ttl: 25 }, vastXml); + }); + + it('should make the expected request when store() is called while supplying a custom key param', function () { + const customKey1 = 'keyword_abc_123'; + const customKey2 = 'other_xyz_789'; + const vastXml1 = 'test1'; + const vastXml2 = 'test2'; + + const bids = [{ + vastXml: vastXml1, + ttl: 25, + customCacheKey: customKey1 + }, { + vastXml: vastXml2, + ttl: 25, + customCacheKey: customKey2 + }]; + + store(bids, function () { }); + const request = requests[0]; + request.method.should.equal('POST'); + request.url.should.equal('https://prebid.adnxs.com/pbc/v1/cache'); + request.requestHeaders['Content-Type'].should.equal('text/plain;charset=utf-8'); + let payload = { + puts: [{ + type: 'xml', + value: vastXml1, + ttlseconds: 25, + key: customKey1 + }, { + type: 'xml', + value: vastXml2, + ttlseconds: 25, + key: customKey2 + }] + }; + JSON.parse(request.requestBody).should.deep.equal(payload); + }); + + it('should include additional params in request payload should config.cache.vasttrack be true', () => { + config.setConfig({ + cache: { + url: 'https://prebid.adnxs.com/pbc/v1/cache', + vasttrack: true + } + }); + + const customKey1 = 'vasttrack_123'; + const customKey2 = 'vasttrack_abc'; + const vastXml1 = 'testvast1'; + const vastXml2 = 'testvast2'; + + const bids = [{ + vastXml: vastXml1, + ttl: 25, + customCacheKey: customKey1, + requestId: '12345abc', + bidder: 'appnexus' + }, { + vastXml: vastXml2, + ttl: 25, + customCacheKey: customKey2, + requestId: 'cba54321', + bidder: 'rubicon' + }]; + + store(bids, function () { }); + const request = requests[0]; + request.method.should.equal('POST'); + request.url.should.equal('https://prebid.adnxs.com/pbc/v1/cache'); + request.requestHeaders['Content-Type'].should.equal('text/plain;charset=utf-8'); + let payload = { + puts: [{ + type: 'xml', + value: vastXml1, + ttlseconds: 25, + key: customKey1, + bidid: '12345abc', + bidder: 'appnexus' + }, { + type: 'xml', + value: vastXml2, + ttlseconds: 25, + key: customKey2, + bidid: 'cba54321', + bidder: 'rubicon' + }] + }; + + JSON.parse(request.requestBody).should.deep.equal(payload); }); function assertRequestMade(bid, expectedValue) { - store([bid], function() { }); + store([bid], function () { }); const request = requests[0]; request.method.should.equal('POST'); @@ -134,13 +224,14 @@ describe('The video cache', function () { puts: [{ type: 'xml', value: expectedValue, + ttlseconds: 25 }], }); } function fakeServerCall(bid, responseBody) { const callback = sinon.spy(); - store([ bid ], callback); + store([bid], callback); requests[0].respond( 200, { diff --git a/test/spec/video_spec.js b/test/spec/video_spec.js index 3d6ed04ffae..dc6c11a7491 100644 --- a/test/spec/video_spec.js +++ b/test/spec/video_spec.js @@ -3,8 +3,9 @@ import { isValidVideoBid } from 'src/video'; describe('video.js', function () { it('validates valid instream bids', function () { const bid = { - adId: '123abc', - vastUrl: 'http://www.example.com/vastUrl' + adId: '456xyz', + vastUrl: 'http://www.example.com/vastUrl', + requestId: '123abc' }; const bidRequests = [{ bids: [{ @@ -21,7 +22,7 @@ describe('video.js', function () { it('catches invalid instream bids', function () { const bid = { - adId: '123abc' + requestId: '123abc' }; const bidRequests = [{ bids: [{ @@ -51,7 +52,7 @@ describe('video.js', function () { it('validates valid outstream bids', function () { const bid = { - adId: '123abc', + requestId: '123abc', renderer: { url: 'render.url', render: () => true, @@ -72,7 +73,7 @@ describe('video.js', function () { it('catches invalid outstream bids', function () { const bid = { - adId: '123abc' + requestId: '123abc' }; const bidRequests = [{ bids: [{ diff --git a/test/test_index.js b/test/test_index.js index 51323d87437..206f1be1f52 100644 --- a/test/test_index.js +++ b/test/test_index.js @@ -1,4 +1,7 @@ require('test/helpers/prebidGlobal.js'); +require('test/mocks/adloaderStub.js'); var testsContext = require.context('.', true, /_spec$/); testsContext.keys().forEach(testsContext); + +window.$$PREBID_GLOBAL$$.processQueue(); diff --git a/wdio.conf.js b/wdio.conf.js new file mode 100644 index 00000000000..dd94e82cf90 --- /dev/null +++ b/wdio.conf.js @@ -0,0 +1,61 @@ +const browsers = require('./browsers.json'); + +function getCapabilities() { + function getPlatform(os) { + const platformMap = { + 'Windows': 'WINDOWS', + 'OS X': 'MAC', + } + return platformMap[os]; + } + + // only Edge 16, Chrome 74 & Firefox 66 run as part of functional tests + // rest of the browsers are discarded. + delete browsers['bs_ie_11_windows_10']; + delete browsers['bs_edge_17_windows_10']; + delete browsers['bs_chrome_75_windows_10']; + delete browsers['bs_firefox_67_windows_10']; + delete browsers['bs_safari_11_mac_high_sierra']; + delete browsers['bs_safari_12_mac_mojave']; + + let capabilities = [] + Object.keys(browsers).forEach(key => { + let browser = browsers[key]; + capabilities.push({ + browserName: browser.browser, + platform: getPlatform(browser.os), + version: browser.browser_version, + acceptSslCerts: true, + 'browserstack.networkLogs': true, + 'browserstack.console': 'verbose', + build: 'Prebidjs E2E ' + new Date().toLocaleString() + }); + }); + return capabilities; +} + +exports.config = { + specs: [ + './test/spec/e2e/**/*.spec.js' + ], + services: ['browserstack'], + user: process.env.BROWSERSTACK_USERNAME, + key: process.env.BROWSERSTACK_ACCESS_KEY, + browserstackLocal: true, + // Do not increase this, since we have only 5 parallel tests in browserstack account + maxInstances: 5, + capabilities: getCapabilities(), + logLevel: 'silent', // Level of logging verbosity: silent | verbose | command | data | result | error + coloredLogs: true, + waitforTimeout: 60000, // Default timeout for all waitFor* commands. + connectionRetryTimeout: 60000, // Default timeout in milliseconds for request if Selenium Grid doesn't send response + connectionRetryCount: 3, // Default request retries count + framework: 'mocha', + mochaOpts: { + ui: 'bdd', + timeout: 60000, + compilers: ['js:babel-register'], + }, + // if you see error, update this to spec reporter and logLevel above to get detailed report. + reporters: ['concise'] +}; diff --git a/webpack.conf.js b/webpack.conf.js index 4b53aabef22..a5c75fa8a1a 100644 --- a/webpack.conf.js +++ b/webpack.conf.js @@ -1,15 +1,45 @@ var prebid = require('./package.json'); -var StringReplacePlugin = require('string-replace-webpack-plugin'); var path = require('path'); var webpack = require('webpack'); var helpers = require('./gulpHelpers'); var RequireEnsureWithoutJsonp = require('./plugins/RequireEnsureWithoutJsonp.js'); +var { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer'); +var argv = require('yargs').argv; +var allowedModules = require('./allowedModules'); // list of module names to never include in the common bundle chunk var neverBundle = [ 'AnalyticsAdapter.js' ]; +var plugins = [ + new RequireEnsureWithoutJsonp() +]; + +if (argv.analyze) { + plugins.push( + new BundleAnalyzerPlugin() + ) +} + +plugins.push( // this plugin must be last so it can be easily removed for karma unit tests + new webpack.optimize.CommonsChunkPlugin({ + name: 'prebid', + filename: 'prebid-core.js', + minChunks: function(module) { + return ( + ( + module.context && module.context.startsWith(path.resolve('./src')) && + !(module.resource && neverBundle.some(name => module.resource.includes(name))) + ) || + module.resource && (allowedModules.src.concat(['core-js'])).some( + name => module.resource.includes(path.resolve('./node_modules/' + name)) + ) + ); + } + }) +); + module.exports = { devtool: 'source-map', resolve: { @@ -19,7 +49,7 @@ module.exports = { ], }, output: { - jsonpFunction: prebid.globalVarName+"Chunk" + jsonpFunction: prebid.globalVarName + "Chunk" }, module: { rules: [ @@ -29,6 +59,7 @@ module.exports = { use: [ { loader: 'babel-loader', + options: helpers.getAnalyticsOptions(), } ] }, @@ -40,56 +71,8 @@ module.exports = { loader: 'babel-loader', } ], - }, - { - test: /\.json$/, - loader: 'json-loader' - }, - { - test: /\.md$/, - loader: 'ignore-loader' - }, - { - test: /constants.json$/, - include: /(src)/, - loader: StringReplacePlugin.replace({ - replacements: [ - { - pattern: /%%REPO_AND_VERSION%%/g, - replacement: function (match, p1, offset, string) { - return `${prebid.repository.url.split('/')[3]}_prebid_${prebid.version}`; - } - } - ] - }) - }, - { - test: /\.js$/, - include: /(src|test|modules|integrationExamples)/, - loader: StringReplacePlugin.replace({ - replacements: [ - { - pattern: /\$\$PREBID_GLOBAL\$\$/g, - replacement: function (match, p1, offset, string) { - return prebid.globalVarName; - } - } - ] - }) } ] }, - plugins: [ - new StringReplacePlugin(), - new RequireEnsureWithoutJsonp(), - - // this plugin must be last so it can be easily removed for karma unit tests - new webpack.optimize.CommonsChunkPlugin({ - name: 'prebid', - filename: 'prebid-core.js', - minChunks: function(module, count) { - return !(count < 2 || neverBundle.includes(path.basename(module.resource))) - } - }) - ] + plugins };